From 3a40664bc73892c54955ce1f2c11e6596bdf07f9 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Fri, 7 Jul 2023 22:38:10 -0600 Subject: [PATCH 0001/1797] 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/1797] 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/1797] 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/1797] 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/1797] 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/1797] 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/1797] 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/1797] 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/1797] 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/1797] 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/1797] 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/1797] 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/1797] 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/1797] 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/1797] 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/1797] 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/1797] 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/1797] 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/1797] 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/1797] 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/1797] 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/1797] 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/1797] 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/1797] 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/1797] 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 bb6bf5e67b31820760325f2f5f3e9c6c83094d3f Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Fri, 25 Aug 2023 13:58:03 -0400 Subject: [PATCH 0026/1797] add highs support --- pyomo/contrib/mindtpy/config_options.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/mindtpy/config_options.py b/pyomo/contrib/mindtpy/config_options.py index ed0c86baae9..713ab539660 100644 --- a/pyomo/contrib/mindtpy/config_options.py +++ b/pyomo/contrib/mindtpy/config_options.py @@ -538,7 +538,7 @@ def _add_subsolver_configs(CONFIG): 'cplex_persistent', 'appsi_cplex', 'appsi_gurobi', - # 'appsi_highs', TODO: feasibility pump now fails with appsi_highs #2951 + 'appsi_highs' ] ), description='MIP subsolver name', @@ -620,7 +620,7 @@ def _add_subsolver_configs(CONFIG): 'cplex_persistent', 'appsi_cplex', 'appsi_gurobi', - # 'appsi_highs', + 'appsi_highs', ] ), description='MIP subsolver for regularization problem', From da8a56e837fb43dec321af088fd498c980429db5 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Fri, 25 Aug 2023 14:03:07 -0400 Subject: [PATCH 0027/1797] change test mip solver to highs --- 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 | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy.py b/pyomo/contrib/mindtpy/tests/test_mindtpy.py index e872eccc670..4efd9493b8a 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy.py @@ -56,7 +56,7 @@ QCP_model._generate_model() extreme_model_list = [LP_model.model, QCP_model.model] -required_solvers = ('ipopt', 'glpk') +required_solvers = ('ipopt', 'appsi_highs') if all(SolverFactory(s).available(exception_flag=False) for s in required_solvers): subsolvers_available = True else: diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py index b5bfbe62553..95516af11fd 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py @@ -12,7 +12,7 @@ from pyomo.environ import SolverFactory, value from pyomo.opt import TerminationCondition -required_solvers = ('ipopt', 'glpk') +required_solvers = ('ipopt', 'appsi_highs') if all(SolverFactory(s).available(exception_flag=False) for s in required_solvers): subsolvers_available = True else: diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py index 697a63d17c8..18b7a420674 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py @@ -17,8 +17,7 @@ from pyomo.contrib.mindtpy.tests.feasibility_pump1 import FeasPump1 from pyomo.contrib.mindtpy.tests.feasibility_pump2 import FeasPump2 -required_solvers = ('ipopt', 'cplex') -# TODO: 'appsi_highs' will fail here. +required_solvers = ('ipopt', 'appsi_highs') if all(SolverFactory(s).available(exception_flag=False) for s in required_solvers): subsolvers_available = True else: From 95f4cc2a281fdf235800ae9d82bc27c11d6f1f18 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Fri, 25 Aug 2023 14:13:24 -0400 Subject: [PATCH 0028/1797] black format --- pyomo/contrib/mindtpy/config_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/mindtpy/config_options.py b/pyomo/contrib/mindtpy/config_options.py index 713ab539660..2769d336e31 100644 --- a/pyomo/contrib/mindtpy/config_options.py +++ b/pyomo/contrib/mindtpy/config_options.py @@ -538,7 +538,7 @@ def _add_subsolver_configs(CONFIG): 'cplex_persistent', 'appsi_cplex', 'appsi_gurobi', - 'appsi_highs' + 'appsi_highs', ] ), description='MIP subsolver name', From 23cc0d69b6bce53f7f19984f56a3da03bd0cc4bd Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Tue, 29 Aug 2023 08:30:15 -0600 Subject: [PATCH 0029/1797] 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 0030/1797] 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 0031/1797] 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 0032/1797] 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 0033/1797] 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 0034/1797] 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 0035/1797] 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 0036/1797] 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 0037/1797] 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 0038/1797] 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 0039/1797] 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 0040/1797] 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 0041/1797] 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 0042/1797] 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 0043/1797] 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 0044/1797] 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 0045/1797] 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 0046/1797] 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 0047/1797] 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 0048/1797] 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 0049/1797] 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 0050/1797] 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 0051/1797] 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 0052/1797] 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 0053/1797] 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 0054/1797] 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 0055/1797] 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 0056/1797] 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 0057/1797] 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 0058/1797] 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 0059/1797] 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 0060/1797] 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 0061/1797] 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 0062/1797] 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 0063/1797] 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 0064/1797] 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 0065/1797] 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 0066/1797] 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 0067/1797] 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 0068/1797] 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 0069/1797] 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 0070/1797] 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 0071/1797] 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 0072/1797] 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 0073/1797] 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 0074/1797] 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 0075/1797] 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 0076/1797] 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 0077/1797] 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 0078/1797] 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 0079/1797] 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 0080/1797] 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 0081/1797] 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 0082/1797] 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 0083/1797] 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 0084/1797] 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 0085/1797] 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 0086/1797] 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 0087/1797] 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 0088/1797] 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 0089/1797] 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 0090/1797] 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 0091/1797] 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 0092/1797] 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 0093/1797] 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 553c8bb45741c9a8c55a9ee68f4aa349aaa144aa Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Fri, 1 Sep 2023 15:34:14 -0400 Subject: [PATCH 0094/1797] add deactivate_trivial_constraints for feasibility subproblem --- 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 7def1dcaab3..584d796d069 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -1311,6 +1311,20 @@ def solve_feasibility_subproblem(self): update_solver_timelimit( self.feasibility_nlp_opt, config.nlp_solver, self.timing, config ) + try: + TransformationFactory('contrib.deactivate_trivial_constraints').apply_to( + self.fixed_nlp, + tmp=True, + ignore_infeasible=False, + tolerance=config.constraint_tolerance, + ) + except InfeasibleConstraintException as e: + config.logger.error( + str(e) + '\nInfeasibility detected in deactivate_trivial_constraints.' + ) + results = SolverResults() + results.solver.termination_condition = tc.infeasible + return self.fixed_nlp, results with SuppressInfeasibleWarning(): try: with time_code(self.timing, 'feasibility subproblem'): @@ -1341,6 +1355,9 @@ def solve_feasibility_subproblem(self): self.handle_feasibility_subproblem_tc( feas_soln.solver.termination_condition, MindtPy ) + TransformationFactory('contrib.deactivate_trivial_constraints').revert( + self.fixed_nlp + ) MindtPy.feas_opt.deactivate() for constr in MindtPy.nonlinear_constraint_list: constr.activate() From 6526d77ddcf380dc95700c17a1534c8dd0077c96 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Tue, 5 Sep 2023 21:48:26 -0600 Subject: [PATCH 0095/1797] 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 0096/1797] 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 0097/1797] 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 0098/1797] 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 0099/1797] 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 0100/1797] 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 0101/1797] 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 0102/1797] 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 0103/1797] 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 0104/1797] 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 0105/1797] 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 0106/1797] 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 0107/1797] 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 0108/1797] 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 0109/1797] 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 0110/1797] 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 0111/1797] 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 0112/1797] 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 0113/1797] 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 0114/1797] 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 0115/1797] 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 0116/1797] 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 0117/1797] 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 0118/1797] 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 0119/1797] 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 0120/1797] 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 0121/1797] 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 0122/1797] 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 0123/1797] 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 0124/1797] 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 0125/1797] 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 0126/1797] 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 0127/1797] 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 0128/1797] 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 b7a446c70dcbadd43a4da5e330a8e39e785f0d71 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 8 Sep 2023 11:08:59 -0600 Subject: [PATCH 0129/1797] 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 0130/1797] 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 0131/1797] 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 3054a4431bfdde777b87d3c2d2ea75c51632ed97 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Fri, 8 Sep 2023 16:05:08 -0600 Subject: [PATCH 0132/1797] 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 0133/1797] 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 0134/1797] 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 0135/1797] 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 0136/1797] 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 0137/1797] 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 0138/1797] 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 0139/1797] 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 0140/1797] 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 0141/1797] 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 0142/1797] 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 0143/1797] 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 0144/1797] 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 0145/1797] 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 0146/1797] 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 0147/1797] 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 0148/1797] 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 0149/1797] 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 0150/1797] 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 0151/1797] 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 0152/1797] 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 0153/1797] 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 0154/1797] 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 0155/1797] 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 0156/1797] 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 0157/1797] 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 0158/1797] 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 f818de7351d1785c587c6ff8c340dedf04ac84eb Mon Sep 17 00:00:00 2001 From: gomesraphael7d Date: Mon, 11 Sep 2023 12:20:39 -0300 Subject: [PATCH 0159/1797] 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 0160/1797] 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 0161/1797] 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 0162/1797] 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 0163/1797] 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 0164/1797] 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 0165/1797] 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 0166/1797] 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 0167/1797] 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 0168/1797] 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 0169/1797] 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 0170/1797] 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 0171/1797] 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 0172/1797] 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 0173/1797] 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 0174/1797] 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 0175/1797] 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 14def43dbbddeb51873fb9b64f76e6230e13ae92 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Sep 2023 10:28:52 -0600 Subject: [PATCH 0176/1797] 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 f31e0050e14764a17f86fa5052837bd7032fe672 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 21 Sep 2023 15:14:12 -0600 Subject: [PATCH 0177/1797] 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 0178/1797] 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 0179/1797] 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 0180/1797] 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 89596661fb641287e64263d95925cb1d065a3d85 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 21 Sep 2023 19:28:09 -0400 Subject: [PATCH 0181/1797] 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 0182/1797] 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 0183/1797] 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 0184/1797] 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 0185/1797] 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 0186/1797] 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 0187/1797] 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 0188/1797] 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 0189/1797] 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 0190/1797] 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 0191/1797] 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 e0c245bb9b4b9b0433e422a280866d5b62945718 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 25 Sep 2023 12:36:16 -0400 Subject: [PATCH 0192/1797] 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 0193/1797] 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 0194/1797] 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 0195/1797] 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 0196/1797] 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 0197/1797] 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 0198/1797] 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 0199/1797] 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 0200/1797] 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 0201/1797] 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 0202/1797] 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 0203/1797] 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 0204/1797] 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 0205/1797] 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 0206/1797] 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 0207/1797] 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 0208/1797] 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 0209/1797] 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 0210/1797] 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 0211/1797] 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 0212/1797] 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 0213/1797] 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 0214/1797] 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 0215/1797] 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 0216/1797] 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 0217/1797] 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 0218/1797] 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 0219/1797] 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 0220/1797] 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 0221/1797] 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 0222/1797] 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 0223/1797] 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 0224/1797] 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 0225/1797] 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 0226/1797] 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 0227/1797] 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 0228/1797] 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 0229/1797] 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 0230/1797] 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 0231/1797] 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 0232/1797] 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 0233/1797] 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 0234/1797] 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 0235/1797] 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 0236/1797] 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 0237/1797] 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 0238/1797] 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 0239/1797] 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 0240/1797] 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 0241/1797] 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 0242/1797] 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 0243/1797] 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 0244/1797] 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 0245/1797] 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 0246/1797] 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 0247/1797] 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 0248/1797] 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 0249/1797] 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 0250/1797] 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 0251/1797] 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 0252/1797] 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 0253/1797] 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 0254/1797] 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 0255/1797] 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 0256/1797] 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 0257/1797] 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 0258/1797] 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 0259/1797] 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 a523eea90f835b79cc62c1a63da1a2382733da7a Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 10 Oct 2023 12:08:13 -0600 Subject: [PATCH 0260/1797] 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 0261/1797] 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 0262/1797] 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 0263/1797] 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 0264/1797] 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 0265/1797] 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 6b4c1eac538077387423cde3e87bcc8b3a45cc6c Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 10 Oct 2023 14:46:53 -0600 Subject: [PATCH 0266/1797] 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 0267/1797] 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 0268/1797] 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 0269/1797] 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 0270/1797] 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 0271/1797] 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 0272/1797] 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 0273/1797] 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 0274/1797] 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 0275/1797] 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 0276/1797] 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 0277/1797] 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 0278/1797] 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 0fdd4dc6e64b5c58fac1d59f8b4634e6f7f97a6c Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 12 Oct 2023 11:35:01 -0400 Subject: [PATCH 0279/1797] 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 0280/1797] 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 0281/1797] 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 0282/1797] 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 0283/1797] 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 3a4399d69b621f007ba0fc0e5cffdbfb55f2fd5b Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 19 Oct 2023 13:17:54 -0600 Subject: [PATCH 0284/1797] 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 393fe7fe99c544ed75ed89668b9e2b99291d258b Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 20 Oct 2023 12:51:05 -0600 Subject: [PATCH 0285/1797] 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 0286/1797] 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 0287/1797] 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 0288/1797] 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 0289/1797] 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 0290/1797] 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 0291/1797] 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 0292/1797] 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 0293/1797] 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 0294/1797] 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 0295/1797] 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 0296/1797] 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 126d3d8537b7f93293cbc11ecc94822a19b8fd7f Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 27 Oct 2023 12:46:46 -0600 Subject: [PATCH 0297/1797] 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 0298/1797] 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 0299/1797] 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 0300/1797] 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 0301/1797] 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 0302/1797] 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 0303/1797] 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 0304/1797] 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 0305/1797] 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 0306/1797] 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 0307/1797] 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 0308/1797] 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 0309/1797] 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 0310/1797] 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 0311/1797] 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 0312/1797] 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 0313/1797] 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 0314/1797] 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 0315/1797] 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 0316/1797] 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 c0e5eeffbca9d0b42fe160e4f1bfd32d0538e636 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 30 Oct 2023 08:50:14 -0600 Subject: [PATCH 0317/1797] 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 0318/1797] 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 0319/1797] 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 0320/1797] 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 0321/1797] 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 0322/1797] 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 0323/1797] 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 0324/1797] 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 0325/1797] 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 0326/1797] 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 52cb54f9418b081f44f1a3887e6dae03ebf9710f Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 1 Nov 2023 11:45:20 -0600 Subject: [PATCH 0327/1797] 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 0328/1797] 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 0329/1797] 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 0330/1797] 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 0331/1797] 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 0332/1797] 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 87c4742f57f6d51052e78bd705168394baa71220 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Thu, 2 Nov 2023 12:54:00 -0600 Subject: [PATCH 0333/1797] 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 0334/1797] 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 0335/1797] 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 0336/1797] 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 0337/1797] 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 0338/1797] 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 0339/1797] 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 0340/1797] 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 0341/1797] 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 0342/1797] 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 0343/1797] 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 0344/1797] 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 0345/1797] 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 0346/1797] 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 0347/1797] 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 0348/1797] 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 0349/1797] 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 0350/1797] 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 0351/1797] 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 0352/1797] 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 0353/1797] 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 0354/1797] 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 0355/1797] 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 8d1e68e533df01dda7ac4c4f9911db2ab388b7cf Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 8 Nov 2023 07:41:44 -0700 Subject: [PATCH 0356/1797] working on simplification contrib package --- pyomo/contrib/simplification/__init__.py | 0 pyomo/contrib/simplification/build.py | 33 +++++++++++++++++++ .../simplification/ginac_interface.cpp | 0 pyomo/contrib/simplification/simplify.py | 12 +++++++ 4 files changed, 45 insertions(+) create mode 100644 pyomo/contrib/simplification/__init__.py create mode 100644 pyomo/contrib/simplification/build.py create mode 100644 pyomo/contrib/simplification/ginac_interface.cpp create mode 100644 pyomo/contrib/simplification/simplify.py diff --git a/pyomo/contrib/simplification/__init__.py b/pyomo/contrib/simplification/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/pyomo/contrib/simplification/build.py b/pyomo/contrib/simplification/build.py new file mode 100644 index 00000000000..0b0b9828cd3 --- /dev/null +++ b/pyomo/contrib/simplification/build.py @@ -0,0 +1,33 @@ +from pybind11.setup_helpers import Pybind11Extension, build_ext +from pyomo.common.fileutils import this_file_dir +import os +from distutils.dist import Distribution +import sys + + +def build_ginac_interface(args=[]): + dname = this_file_dir() + _sources = [ + 'ginac_interface.cpp', + ] + sources = list() + for fname in _sources: + sources.append(os.path.join(dname, fname)) + extra_args = ['-std=c++11'] + ext = Pybind11Extension('ginac_interface', sources, extra_compile_args=extra_args) + + package_config = { + 'name': 'ginac_interface', + 'packages': [], + 'ext_modules': [ext], + 'cmdclass': {"build_ext": build_ext}, + } + + dist = Distribution(package_config) + dist.script_args = ['build_ext'] + args + dist.parse_command_line() + dist.run_command('build_ext') + + +if __name__ == '__main__': + build_ginac_interface(sys.argv[1:]) diff --git a/pyomo/contrib/simplification/ginac_interface.cpp b/pyomo/contrib/simplification/ginac_interface.cpp new file mode 100644 index 00000000000..e69de29bb2d diff --git a/pyomo/contrib/simplification/simplify.py b/pyomo/contrib/simplification/simplify.py new file mode 100644 index 00000000000..70d5dfcd9ac --- /dev/null +++ b/pyomo/contrib/simplification/simplify.py @@ -0,0 +1,12 @@ +from pyomo.core.expr.sympy_tools import sympy2pyomo_expression, sympyify_expression +from pyomo.core.expr.numeric_expr import NumericExpression +from pyomo.core.expr.numvalue import is_fixed, value + + +def simplify_with_sympy(expr: NumericExpression): + om, se = sympyify_expression(expr) + se = se.simplify() + new_expr = sympy2pyomo_expression(se, om) + if is_fixed(new_expr): + new_expr = value(new_expr) + return new_expr \ No newline at end of file From b2a6a3e7b228637f0ea0e5d81d6804696b1ce5a3 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Wed, 8 Nov 2023 11:34:10 -0600 Subject: [PATCH 0357/1797] 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 8015d7ece05ee4608c8ddd6924218f40fd635f89 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 8 Nov 2023 11:39:43 -0700 Subject: [PATCH 0358/1797] working on simplification contrib package --- pyomo/contrib/simplification/build.py | 65 ++++++- .../simplification/ginac_interface.cpp | 149 ++++++++++++++++ .../simplification/ginac_interface.hpp | 165 ++++++++++++++++++ 3 files changed, 376 insertions(+), 3 deletions(-) create mode 100644 pyomo/contrib/simplification/ginac_interface.hpp diff --git a/pyomo/contrib/simplification/build.py b/pyomo/contrib/simplification/build.py index 0b0b9828cd3..6f16607e22b 100644 --- a/pyomo/contrib/simplification/build.py +++ b/pyomo/contrib/simplification/build.py @@ -1,8 +1,12 @@ from pybind11.setup_helpers import Pybind11Extension, build_ext -from pyomo.common.fileutils import this_file_dir +from pyomo.common.fileutils import this_file_dir, find_library import os from distutils.dist import Distribution import sys +import shutil +import glob +import tempfile +from pyomo.common.envvar import PYOMO_CONFIG_DIR def build_ginac_interface(args=[]): @@ -13,14 +17,69 @@ def build_ginac_interface(args=[]): sources = list() for fname in _sources: sources.append(os.path.join(dname, fname)) + + ginac_lib = find_library('ginac') + if ginac_lib is None: + raise RuntimeError('could not find GiNaC library; please make sure it is in the LD_LIBRARY_PATH environment variable') + ginac_lib_dir = os.path.dirname(ginac_lib) + ginac_build_dir = os.path.dirname(ginac_lib_dir) + ginac_include_dir = os.path.join(ginac_build_dir, 'include') + if not os.path.exists(os.path.join(ginac_include_dir, 'ginac', 'ginac.h')): + raise RuntimeError('could not find GiNaC include directory') + + cln_lib = find_library('cln') + if cln_lib is None: + raise RuntimeError('could not find CLN library; please make sure it is in the LD_LIBRARY_PATH environment variable') + cln_lib_dir = os.path.dirname(cln_lib) + cln_build_dir = os.path.dirname(cln_lib_dir) + cln_include_dir = os.path.join(cln_build_dir, 'include') + if not os.path.exists(os.path.join(cln_include_dir, 'cln', 'cln.h')): + raise RuntimeError('could not find CLN include directory') + extra_args = ['-std=c++11'] - ext = Pybind11Extension('ginac_interface', sources, extra_compile_args=extra_args) + ext = Pybind11Extension( + 'ginac_interface', + sources=sources, + language='c++', + include_dirs=[cln_include_dir, ginac_include_dir], + library_dirs=[cln_lib_dir, ginac_lib_dir], + libraries=['cln', 'ginac'], + extra_compile_args=extra_args, + ) + + class ginac_build_ext(build_ext): + def run(self): + basedir = os.path.abspath(os.path.curdir) + if self.inplace: + tmpdir = this_file_dir() + else: + tmpdir = os.path.abspath(tempfile.mkdtemp()) + print("Building in '%s'" % tmpdir) + os.chdir(tmpdir) + try: + super(ginac_build_ext, self).run() + if not self.inplace: + library = glob.glob("build/*/ginac_interface.*")[0] + target = os.path.join( + PYOMO_CONFIG_DIR, + 'lib', + 'python%s.%s' % sys.version_info[:2], + 'site-packages', + '.', + ) + if not os.path.exists(target): + os.makedirs(target) + shutil.copy(library, target) + finally: + os.chdir(basedir) + if not self.inplace: + shutil.rmtree(tmpdir, onerror=handleReadonly) package_config = { 'name': 'ginac_interface', 'packages': [], 'ext_modules': [ext], - 'cmdclass': {"build_ext": build_ext}, + 'cmdclass': {"build_ext": ginac_build_ext}, } dist = Distribution(package_config) diff --git a/pyomo/contrib/simplification/ginac_interface.cpp b/pyomo/contrib/simplification/ginac_interface.cpp index e69de29bb2d..ccbc98d3586 100644 --- a/pyomo/contrib/simplification/ginac_interface.cpp +++ b/pyomo/contrib/simplification/ginac_interface.cpp @@ -0,0 +1,149 @@ +#include "ginac_interface.hpp" + +ex ginac_expr_from_pyomo_node(py::handle expr, std::unordered_map &leaf_map, PyomoExprTypes &expr_types) { + ex res; + ExprType tmp_type = + expr_types.expr_type_map[py::type::of(expr)].cast(); + + switch (tmp_type) { + case py_float: { + res = numeric(expr.cast()); + break; + } + case var: { + long expr_id = expr_types.id(expr).cast(); + if (leaf_map.count(expr_id) == 0) { + leaf_map[expr_id] = symbol("x" + std::to_string(expr_id)); + } + res = leaf_map[expr_id]; + break; + } + case param: { + long expr_id = expr_types.id(expr).cast(); + if (leaf_map.count(expr_id) == 0) { + leaf_map[expr_id] = symbol("p" + std::to_string(expr_id)); + } + res = leaf_map[expr_id]; + break; + } + case product: { + py::list pyomo_args = expr.attr("args"); + res = ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types) * ginac_expr_from_pyomo_node(pyomo_args[1], leaf_map, expr_types); + break; + } + case sum: { + py::list pyomo_args = expr.attr("args"); + for (py::handle arg : pyomo_args) { + res += ginac_expr_from_pyomo_node(arg, leaf_map, expr_types); + } + break; + } + case negation: { + py::list pyomo_args = expr.attr("args"); + res = - ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types); + break; + } + case external_func: { + long expr_id = expr_types.id(expr).cast(); + if (leaf_map.count(expr_id) == 0) { + leaf_map[expr_id] = symbol("f" + std::to_string(expr_id)); + } + res = leaf_map[expr_id]; + break; + } + case ExprType::power: { + py::list pyomo_args = expr.attr("args"); + res = pow(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types), ginac_expr_from_pyomo_node(pyomo_args[1], leaf_map, expr_types)); + break; + } + case division: { + py::list pyomo_args = expr.attr("args"); + res = ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types) / ginac_expr_from_pyomo_node(pyomo_args[1], leaf_map, expr_types); + break; + } + case unary_func: { + std::string function_name = expr.attr("getname")().cast(); + py::list pyomo_args = expr.attr("args"); + if (function_name == "exp") + res = exp(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types)); + else if (function_name == "log") + res = log(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types)); + else if (function_name == "sin") + res = sin(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types)); + else if (function_name == "cos") + res = cos(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types)); + else if (function_name == "tan") + res = tan(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types)); + else if (function_name == "asin") + res = asin(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types)); + else if (function_name == "acos") + res = acos(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types)); + else if (function_name == "atan") + res = atan(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types)); + else if (function_name == "sqrt") + res = sqrt(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types)); + else + throw py::value_error("Unrecognized expression type: " + function_name); + break; + } + case linear: { + py::list pyomo_args = expr.attr("args"); + for (py::handle arg : pyomo_args) { + res += ginac_expr_from_pyomo_node(arg, leaf_map, expr_types); + } + break; + } + case named_expr: { + res = ginac_expr_from_pyomo_node(expr.attr("expr"), leaf_map, expr_types); + break; + } + case numeric_constant: { + res = numeric(expr.attr("value").cast()); + break; + } + case pyomo_unit: { + res = numeric(1.0); + break; + } + case unary_abs: { + py::list pyomo_args = expr.attr("args"); + res = abs(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types)); + break; + } + default: { + throw py::value_error("Unrecognized expression type: " + + expr_types.builtins.attr("str")(py::type::of(expr)) + .cast()); + break; + } + } + return res; +} + +ex ginac_expr_from_pyomo_expr(py::handle expr, PyomoExprTypes &expr_types) { + std::unordered_map leaf_map; + ex res = ginac_expr_from_pyomo_node(expr, leaf_map, expr_types); + return res; +} + + +PYBIND11_MODULE(ginac_interface, m) { + m.def("ginac_expr_from_pyomo_expr", &ginac_expr_from_pyomo_expr); + py::class_(m, "PyomoExprTypes").def(py::init<>()); + py::class_(m, "ex"); + py::enum_(m, "ExprType") + .value("py_float", ExprType::py_float) + .value("var", ExprType::var) + .value("param", ExprType::param) + .value("product", ExprType::product) + .value("sum", ExprType::sum) + .value("negation", ExprType::negation) + .value("external_func", ExprType::external_func) + .value("power", ExprType::power) + .value("division", ExprType::division) + .value("unary_func", ExprType::unary_func) + .value("linear", ExprType::linear) + .value("named_expr", ExprType::named_expr) + .value("numeric_constant", ExprType::numeric_constant) + .export_values(); +} diff --git a/pyomo/contrib/simplification/ginac_interface.hpp b/pyomo/contrib/simplification/ginac_interface.hpp new file mode 100644 index 00000000000..de77e66d0c7 --- /dev/null +++ b/pyomo/contrib/simplification/ginac_interface.hpp @@ -0,0 +1,165 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define PYBIND11_DETAILED_ERROR_MESSAGES + +namespace py = pybind11; +using namespace pybind11::literals; +using namespace GiNaC; + +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] = ExprType::power; + expr_type_map[NPV_PowExpression] = ExprType::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; +}; + +ex ginac_expr_from_pyomo_expr(py::handle expr, PyomoExprTypes &expr_types); From d43765c5bf9609c5f74e1a58b2ab1b32d595f101 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 8 Nov 2023 15:11:29 -0700 Subject: [PATCH 0359/1797] 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 0360/1797] 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 0361/1797] 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 0362/1797] 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 0363/1797] 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 0364/1797] 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 0365/1797] 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 0366/1797] 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 0367/1797] 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 0368/1797] 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 0369/1797] 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 0370/1797] 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 0371/1797] 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 0372/1797] 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 0373/1797] 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 0374/1797] 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 0375/1797] 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 0376/1797] 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 0377/1797] 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 0378/1797] 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 0379/1797] 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 0380/1797] 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 0381/1797] 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 0382/1797] 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 0383/1797] 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 0384/1797] 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 0385/1797] 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 0386/1797] 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 0387/1797] 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 0388/1797] 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 0389/1797] 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 0390/1797] 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 0391/1797] 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 0392/1797] 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 0393/1797] 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 0394/1797] 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 0395/1797] 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 0396/1797] 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 0397/1797] 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 0398/1797] 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 0399/1797] 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 0400/1797] 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 0401/1797] 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 0402/1797] 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 0403/1797] 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 0404/1797] 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 0405/1797] 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 0406/1797] 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 0407/1797] 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 0408/1797] 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 0409/1797] 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 0410/1797] 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 0411/1797] 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 56e8ac84e72bbd59d57e6307ef62b6dbabe09e37 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 20 Nov 2023 11:43:42 -0700 Subject: [PATCH 0412/1797] working on ginac interface for simplification --- .../simplification/ginac_interface.cpp | 213 ++++++++++++++++-- .../simplification/ginac_interface.hpp | 27 ++- 2 files changed, 214 insertions(+), 26 deletions(-) diff --git a/pyomo/contrib/simplification/ginac_interface.cpp b/pyomo/contrib/simplification/ginac_interface.cpp index ccbc98d3586..9a84521ff91 100644 --- a/pyomo/contrib/simplification/ginac_interface.cpp +++ b/pyomo/contrib/simplification/ginac_interface.cpp @@ -1,6 +1,12 @@ #include "ginac_interface.hpp" -ex ginac_expr_from_pyomo_node(py::handle expr, std::unordered_map &leaf_map, PyomoExprTypes &expr_types) { +ex ginac_expr_from_pyomo_node( + py::handle expr, + std::unordered_map &leaf_map, + std::unordered_map &ginac_pyomo_map, + PyomoExprTypes &expr_types, + bool symbolic_solver_labels + ) { ex res; ExprType tmp_type = expr_types.expr_type_map[py::type::of(expr)].cast(); @@ -13,7 +19,21 @@ ex ginac_expr_from_pyomo_node(py::handle expr, std::unordered_map &lea case var: { long expr_id = expr_types.id(expr).cast(); if (leaf_map.count(expr_id) == 0) { - leaf_map[expr_id] = symbol("x" + std::to_string(expr_id)); + std::string vname; + if (symbolic_solver_labels) { + vname = expr.attr("name").cast(); + } + else { + vname = "x" + std::to_string(expr_id); + } + py::object lb = expr.attr("lb"); + if (lb.is_none() || lb.cast() < 0) { + leaf_map[expr_id] = realsymbol(vname); + } + else { + leaf_map[expr_id] = possymbol(vname); + } + ginac_pyomo_map[leaf_map[expr_id]] = expr.cast(); } res = leaf_map[expr_id]; break; @@ -21,67 +41,76 @@ ex ginac_expr_from_pyomo_node(py::handle expr, std::unordered_map &lea case param: { long expr_id = expr_types.id(expr).cast(); if (leaf_map.count(expr_id) == 0) { - leaf_map[expr_id] = symbol("p" + std::to_string(expr_id)); + std::string pname; + if (symbolic_solver_labels) { + pname = expr.attr("name").cast(); + } + else { + pname = "p" + std::to_string(expr_id); + } + leaf_map[expr_id] = realsymbol(pname); + ginac_pyomo_map[leaf_map[expr_id]] = expr.cast(); } res = leaf_map[expr_id]; break; } case product: { py::list pyomo_args = expr.attr("args"); - res = ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types) * ginac_expr_from_pyomo_node(pyomo_args[1], leaf_map, expr_types); + res = ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, ginac_pyomo_map, expr_types, symbolic_solver_labels) * ginac_expr_from_pyomo_node(pyomo_args[1], leaf_map, ginac_pyomo_map, expr_types, symbolic_solver_labels); break; } case sum: { py::list pyomo_args = expr.attr("args"); for (py::handle arg : pyomo_args) { - res += ginac_expr_from_pyomo_node(arg, leaf_map, expr_types); + res += ginac_expr_from_pyomo_node(arg, leaf_map, ginac_pyomo_map, expr_types, symbolic_solver_labels); } break; } case negation: { py::list pyomo_args = expr.attr("args"); - res = - ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types); + res = - ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, ginac_pyomo_map, expr_types, symbolic_solver_labels); break; } case external_func: { long expr_id = expr_types.id(expr).cast(); if (leaf_map.count(expr_id) == 0) { - leaf_map[expr_id] = symbol("f" + std::to_string(expr_id)); + leaf_map[expr_id] = realsymbol("f" + std::to_string(expr_id)); + ginac_pyomo_map[leaf_map[expr_id]] = expr.cast(); } res = leaf_map[expr_id]; break; } case ExprType::power: { py::list pyomo_args = expr.attr("args"); - res = pow(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types), ginac_expr_from_pyomo_node(pyomo_args[1], leaf_map, expr_types)); + res = pow(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, ginac_pyomo_map, expr_types, symbolic_solver_labels), ginac_expr_from_pyomo_node(pyomo_args[1], leaf_map, ginac_pyomo_map, expr_types, symbolic_solver_labels)); break; } case division: { py::list pyomo_args = expr.attr("args"); - res = ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types) / ginac_expr_from_pyomo_node(pyomo_args[1], leaf_map, expr_types); + res = ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, ginac_pyomo_map, expr_types, symbolic_solver_labels) / ginac_expr_from_pyomo_node(pyomo_args[1], leaf_map, ginac_pyomo_map, expr_types, symbolic_solver_labels); break; } case unary_func: { std::string function_name = expr.attr("getname")().cast(); py::list pyomo_args = expr.attr("args"); if (function_name == "exp") - res = exp(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types)); + res = exp(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, ginac_pyomo_map, expr_types, symbolic_solver_labels)); else if (function_name == "log") - res = log(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types)); + res = log(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, ginac_pyomo_map, expr_types, symbolic_solver_labels)); else if (function_name == "sin") - res = sin(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types)); + res = sin(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, ginac_pyomo_map, expr_types, symbolic_solver_labels)); else if (function_name == "cos") - res = cos(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types)); + res = cos(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, ginac_pyomo_map, expr_types, symbolic_solver_labels)); else if (function_name == "tan") - res = tan(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types)); + res = tan(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, ginac_pyomo_map, expr_types, symbolic_solver_labels)); else if (function_name == "asin") - res = asin(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types)); + res = asin(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, ginac_pyomo_map, expr_types, symbolic_solver_labels)); else if (function_name == "acos") - res = acos(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types)); + res = acos(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, ginac_pyomo_map, expr_types, symbolic_solver_labels)); else if (function_name == "atan") - res = atan(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types)); + res = atan(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, ginac_pyomo_map, expr_types, symbolic_solver_labels)); else if (function_name == "sqrt") - res = sqrt(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types)); + res = sqrt(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, ginac_pyomo_map, expr_types, symbolic_solver_labels)); else throw py::value_error("Unrecognized expression type: " + function_name); break; @@ -89,12 +118,12 @@ ex ginac_expr_from_pyomo_node(py::handle expr, std::unordered_map &lea case linear: { py::list pyomo_args = expr.attr("args"); for (py::handle arg : pyomo_args) { - res += ginac_expr_from_pyomo_node(arg, leaf_map, expr_types); + res += ginac_expr_from_pyomo_node(arg, leaf_map, ginac_pyomo_map, expr_types, symbolic_solver_labels); } break; } case named_expr: { - res = ginac_expr_from_pyomo_node(expr.attr("expr"), leaf_map, expr_types); + res = ginac_expr_from_pyomo_node(expr.attr("expr"), leaf_map, ginac_pyomo_map, expr_types, symbolic_solver_labels); break; } case numeric_constant: { @@ -107,7 +136,7 @@ ex ginac_expr_from_pyomo_node(py::handle expr, std::unordered_map &lea } case unary_abs: { py::list pyomo_args = expr.attr("args"); - res = abs(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, expr_types)); + res = abs(ginac_expr_from_pyomo_node(pyomo_args[0], leaf_map, ginac_pyomo_map, expr_types, symbolic_solver_labels)); break; } default: { @@ -120,17 +149,151 @@ ex ginac_expr_from_pyomo_node(py::handle expr, std::unordered_map &lea return res; } -ex ginac_expr_from_pyomo_expr(py::handle expr, PyomoExprTypes &expr_types) { +ex pyomo_expr_to_ginac_expr( + py::handle expr, + std::unordered_map &leaf_map, + std::unordered_map &ginac_pyomo_map, + PyomoExprTypes &expr_types, + bool symbolic_solver_labels + ) { + ex res = ginac_expr_from_pyomo_node(expr, leaf_map, ginac_pyomo_map, expr_types, symbolic_solver_labels); + return res; + } + +ex pyomo_to_ginac(py::handle expr, PyomoExprTypes &expr_types) { std::unordered_map leaf_map; - ex res = ginac_expr_from_pyomo_node(expr, leaf_map, expr_types); + std::unordered_map ginac_pyomo_map; + ex res = ginac_expr_from_pyomo_node(expr, leaf_map, ginac_pyomo_map, expr_types, true); return res; } +class GinacToPyomoVisitor +: public visitor, + public symbol::visitor, + public numeric::visitor, + public add::visitor, + public mul::visitor, + public GiNaC::power::visitor, + public function::visitor, + public basic::visitor +{ + public: + std::unordered_map *leaf_map; + std::unordered_map node_map; + PyomoExprTypes *expr_types; + + GinacToPyomoVisitor(std::unordered_map *_leaf_map, PyomoExprTypes *_expr_types) : leaf_map(_leaf_map), expr_types(_expr_types) {} + ~GinacToPyomoVisitor() = default; + + void visit(const symbol& e) { + node_map[e] = leaf_map->at(e); + } + + void visit(const numeric& e) { + double val = e.to_double(); + node_map[e] = expr_types->NumericConstant(py::cast(val)); + } + + void visit(const add& e) { + size_t n = e.nops(); + py::object pe = node_map[e.op(0)]; + for (unsigned long ndx=1; ndx < n; ++ndx) { + pe = pe.attr("__add__")(node_map[e.op(ndx)]); + } + node_map[e] = pe; + } + + void visit(const mul& e) { + size_t n = e.nops(); + py::object pe = node_map[e.op(0)]; + for (unsigned long ndx=1; ndx < n; ++ndx) { + pe = pe.attr("__mul__")(node_map[e.op(ndx)]); + } + node_map[e] = pe; + } + + void visit(const GiNaC::power& e) { + py::object arg1 = node_map[e.op(0)]; + py::object arg2 = node_map[e.op(1)]; + py::object pe = arg1.attr("__pow__")(arg2); + node_map[e] = pe; + } + + void visit(const function& e) { + py::object arg = node_map[e.op(0)]; + std::string func_type = e.get_name(); + py::object pe; + if (func_type == "exp") { + pe = expr_types->exp(arg); + } + else if (func_type == "log") { + pe = expr_types->log(arg); + } + else if (func_type == "sin") { + pe = expr_types->sin(arg); + } + else if (func_type == "cos") { + pe = expr_types->cos(arg); + } + else if (func_type == "tan") { + pe = expr_types->tan(arg); + } + else if (func_type == "asin") { + pe = expr_types->asin(arg); + } + else if (func_type == "acos") { + pe = expr_types->acos(arg); + } + else if (func_type == "atan") { + pe = expr_types->atan(arg); + } + else if (func_type == "sqrt") { + pe = expr_types->sqrt(arg); + } + else { + throw py::value_error("unrecognized unary function: " + func_type); + } + node_map[e] = pe; + } + + void visit(const basic& e) { + throw py::value_error("unrecognized ginac expression type"); + } +}; + + +ex GinacInterface::to_ginac(py::handle expr) { + return pyomo_expr_to_ginac_expr(expr, leaf_map, ginac_pyomo_map, expr_types, symbolic_solver_labels); +} + +py::object GinacInterface::from_ginac(ex &ge) { + GinacToPyomoVisitor v(&ginac_pyomo_map, &expr_types); + ge.traverse_postorder(v); + return v.node_map[ge]; +} + PYBIND11_MODULE(ginac_interface, m) { - m.def("ginac_expr_from_pyomo_expr", &ginac_expr_from_pyomo_expr); + m.def("pyomo_to_ginac", &pyomo_to_ginac); py::class_(m, "PyomoExprTypes").def(py::init<>()); - py::class_(m, "ex"); + py::class_(m, "ginac_expression") + .def("expand", [](ex &ge) { + // exmap m; + // ex q; + // q = ge.to_polynomial(m).normal(); + // return q.subs(m); + // return factor(ge.normal()); + return ge.expand(); + }) + .def("__str__", [](ex &ge) { + std::ostringstream stream; + stream << ge; + return stream.str(); + }); + py::class_(m, "GinacInterface") + .def(py::init()) + .def("to_ginac", &GinacInterface::to_ginac) + .def("from_ginac", &GinacInterface::from_ginac); py::enum_(m, "ExprType") .value("py_float", ExprType::py_float) .value("var", ExprType::var) diff --git a/pyomo/contrib/simplification/ginac_interface.hpp b/pyomo/contrib/simplification/ginac_interface.hpp index de77e66d0c7..bc5b0d7b6fc 100644 --- a/pyomo/contrib/simplification/ginac_interface.hpp +++ b/pyomo/contrib/simplification/ginac_interface.hpp @@ -156,10 +156,35 @@ class PyomoExprTypes { py::module_::import("pyomo.dae.integral").attr("Integral"); py::object _PyomoUnit = py::module_::import("pyomo.core.base.units_container").attr("_PyomoUnit"); + py::object exp = numeric_expr.attr("exp"); + py::object log = numeric_expr.attr("log"); + py::object sin = numeric_expr.attr("sin"); + py::object cos = numeric_expr.attr("cos"); + py::object tan = numeric_expr.attr("tan"); + py::object asin = numeric_expr.attr("asin"); + py::object acos = numeric_expr.attr("acos"); + py::object atan = numeric_expr.attr("atan"); + py::object sqrt = numeric_expr.attr("sqrt"); py::object builtins = py::module_::import("builtins"); py::object id = builtins.attr("id"); py::object len = builtins.attr("len"); py::dict expr_type_map; }; -ex ginac_expr_from_pyomo_expr(py::handle expr, PyomoExprTypes &expr_types); +ex pyomo_to_ginac(py::handle expr, PyomoExprTypes &expr_types); + + +class GinacInterface { + public: + std::unordered_map leaf_map; + std::unordered_map ginac_pyomo_map; + PyomoExprTypes expr_types; + bool symbolic_solver_labels = false; + + GinacInterface() = default; + GinacInterface(bool _symbolic_solver_labels) : symbolic_solver_labels(_symbolic_solver_labels) {} + ~GinacInterface() = default; + + ex to_ginac(py::handle expr); + py::object from_ginac(ex &ginac_expr); +}; From 932d3d6a8a7a1cd95f2e227fe9f3a63849ad02a4 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 20 Nov 2023 13:07:37 -0700 Subject: [PATCH 0413/1797] ginac interface improvements --- .../simplification/ginac_interface.cpp | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/pyomo/contrib/simplification/ginac_interface.cpp b/pyomo/contrib/simplification/ginac_interface.cpp index 9a84521ff91..690885dc513 100644 --- a/pyomo/contrib/simplification/ginac_interface.cpp +++ b/pyomo/contrib/simplification/ginac_interface.cpp @@ -1,5 +1,11 @@ #include "ginac_interface.hpp" + +bool is_integer(double x) { + return std::floor(x) == x; +} + + ex ginac_expr_from_pyomo_node( py::handle expr, std::unordered_map &leaf_map, @@ -13,7 +19,13 @@ ex ginac_expr_from_pyomo_node( switch (tmp_type) { case py_float: { - res = numeric(expr.cast()); + double val = expr.cast(); + if (is_integer(val)) { + res = numeric(expr.cast()); + } + else { + res = numeric(val); + } break; } case var: { @@ -278,13 +290,9 @@ PYBIND11_MODULE(ginac_interface, m) { py::class_(m, "PyomoExprTypes").def(py::init<>()); py::class_(m, "ginac_expression") .def("expand", [](ex &ge) { - // exmap m; - // ex q; - // q = ge.to_polynomial(m).normal(); - // return q.subs(m); - // return factor(ge.normal()); return ge.expand(); }) + .def("normal", &ex::normal) .def("__str__", [](ex &ge) { std::ostringstream stream; stream << ge; From 208a5dac01ca7dab7830f70119eeb0e1ba94918e Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 20 Nov 2023 13:27:31 -0700 Subject: [PATCH 0414/1797] simplification interface --- pyomo/contrib/simplification/simplify.py | 35 +++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/simplification/simplify.py b/pyomo/contrib/simplification/simplify.py index 70d5dfcd9ac..1de228fb444 100644 --- a/pyomo/contrib/simplification/simplify.py +++ b/pyomo/contrib/simplification/simplify.py @@ -1,6 +1,17 @@ from pyomo.core.expr.sympy_tools import sympy2pyomo_expression, sympyify_expression from pyomo.core.expr.numeric_expr import NumericExpression from pyomo.core.expr.numvalue import is_fixed, value +import logging +import warnings +try: + from pyomo.contrib.simplification.ginac_interface import GinacInterface + ginac_available = True +except: + GinacInterface = None + ginac_available = False + + +logger = logging.getLogger(__name__) def simplify_with_sympy(expr: NumericExpression): @@ -9,4 +20,26 @@ def simplify_with_sympy(expr: NumericExpression): new_expr = sympy2pyomo_expression(se, om) if is_fixed(new_expr): new_expr = value(new_expr) - return new_expr \ No newline at end of file + return new_expr + + +def simplify_with_ginac(expr: NumericExpression, ginac_interface): + gi = ginac_interface + return gi.from_ginac(gi.to_ginac(expr).normal()) + + +class Simplifier(object): + def __init__(self, supress_no_ginac_warnings: bool = False) -> None: + if ginac_available: + self.gi = GinacInterface() + self.suppress_no_ginac_warnings = supress_no_ginac_warnings + + def simplify(self, expr: NumericExpression): + if ginac_available: + return simplify_with_ginac(expr, self.gi) + else: + if not self.suppress_no_ginac_warnings: + msg = f"GiNaC does not seem to be available. Using SymPy. Note that the GiNac interface is significantly faster." + logger.warning(msg) + warnings.warn(msg) + return simplify_with_sympy(expr) From 6d945dad9c51736441586aca5fe52e8b21325804 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 20 Nov 2023 13:27:51 -0700 Subject: [PATCH 0415/1797] 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 0416/1797] 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 0417/1797] 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 0418/1797] 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 0419/1797] 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 0420/1797] 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 0421/1797] 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 0422/1797] 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 0423/1797] 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 0424/1797] 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 0425/1797] 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 6ef89144c5b088574736e86553cb8c22d2dd9645 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 20 Nov 2023 21:08:25 -0700 Subject: [PATCH 0426/1797] bugs --- pyomo/contrib/simplification/__init__.py | 1 + pyomo/contrib/simplification/simplify.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/simplification/__init__.py b/pyomo/contrib/simplification/__init__.py index e69de29bb2d..c09e8b8b5e5 100644 --- a/pyomo/contrib/simplification/__init__.py +++ b/pyomo/contrib/simplification/__init__.py @@ -0,0 +1 @@ +from .simplify import Simplifier \ No newline at end of file diff --git a/pyomo/contrib/simplification/simplify.py b/pyomo/contrib/simplification/simplify.py index 1de228fb444..938bff6b4b9 100644 --- a/pyomo/contrib/simplification/simplify.py +++ b/pyomo/contrib/simplification/simplify.py @@ -31,7 +31,7 @@ def simplify_with_ginac(expr: NumericExpression, ginac_interface): class Simplifier(object): def __init__(self, supress_no_ginac_warnings: bool = False) -> None: if ginac_available: - self.gi = GinacInterface() + self.gi = GinacInterface(False) self.suppress_no_ginac_warnings = supress_no_ginac_warnings def simplify(self, expr: NumericExpression): From af47ef791888b6327c295a06544c4c0771e712d9 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 21 Nov 2023 00:48:31 -0700 Subject: [PATCH 0427/1797] simplification tests --- .../contrib/simplification/tests/__init__.py | 0 .../tests/test_simplification.py | 62 +++++++++++++++++++ pyomo/core/expr/compare.py | 14 ++++- 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 pyomo/contrib/simplification/tests/__init__.py create mode 100644 pyomo/contrib/simplification/tests/test_simplification.py diff --git a/pyomo/contrib/simplification/tests/__init__.py b/pyomo/contrib/simplification/tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/pyomo/contrib/simplification/tests/test_simplification.py b/pyomo/contrib/simplification/tests/test_simplification.py new file mode 100644 index 00000000000..02107ba1d6c --- /dev/null +++ b/pyomo/contrib/simplification/tests/test_simplification.py @@ -0,0 +1,62 @@ +from pyomo.common.unittest import TestCase +from pyomo.contrib.simplification import Simplifier +from pyomo.core.expr.compare import assertExpressionsEqual, compare_expressions +import pyomo.environ as pe +from pyomo.core.expr.calculus.diff_with_pyomo import reverse_sd + + +class TestSimplification(TestCase): + def test_simplify(self): + m = pe.ConcreteModel() + x = m.x = pe.Var(bounds=(0, None)) + e = x*pe.log(x) + der1 = reverse_sd(e)[x] + der2 = reverse_sd(der1)[x] + simp = Simplifier() + der2_simp = simp.simplify(der2) + expected = x**-1.0 + assertExpressionsEqual(self, expected, der2_simp) + + def test_param(self): + m = pe.ConcreteModel() + x = m.x = pe.Var() + p = m.p = pe.Param(mutable=True) + e1 = p*x**2 + p*x + p*x**2 + simp = Simplifier() + e2 = simp.simplify(e1) + exp1 = p*x**2.0*2.0 + p*x + exp2 = p*x + p*x**2.0*2.0 + self.assertTrue( + compare_expressions(e2, exp1) + or compare_expressions(e2, exp2) + or compare_expressions(e2, p*x + x**2.0*p*2.0) + or compare_expressions(e2, x**2.0*p*2.0 + p*x) + ) + + def test_mul(self): + m = pe.ConcreteModel() + x = m.x = pe.Var() + e = 2*x + simp = Simplifier() + e2 = simp.simplify(e) + expected = 2.0*x + assertExpressionsEqual(self, expected, e2) + + def test_sum(self): + m = pe.ConcreteModel() + x = m.x = pe.Var() + e = 2 + x + simp = Simplifier() + e2 = simp.simplify(e) + expected = x + 2.0 + assertExpressionsEqual(self, expected, e2) + + def test_neg(self): + m = pe.ConcreteModel() + x = m.x = pe.Var() + e = -pe.log(x) + simp = Simplifier() + e2 = simp.simplify(e) + expected = pe.log(x)*(-1.0) + assertExpressionsEqual(self, expected, e2) + diff --git a/pyomo/core/expr/compare.py b/pyomo/core/expr/compare.py index ec8d56896b8..96913f1de39 100644 --- a/pyomo/core/expr/compare.py +++ b/pyomo/core/expr/compare.py @@ -195,7 +195,19 @@ def compare_expressions(expr1, expr2, include_named_exprs=True): expr2, include_named_exprs=include_named_exprs ) try: - res = pn1 == pn2 + res = True + if len(pn1) != len(pn2): + res = False + if res: + for a, b in zip(pn1, pn2): + if a.__class__ is not b.__class__: + res = False + break + if a == b: + continue + else: + res = False + break except PyomoException: res = False return res From d750dfb3a6be955a4827c6d23c49afa11f1a5d22 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 21 Nov 2023 10:12:14 -0700 Subject: [PATCH 0428/1797] 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 0429/1797] 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 0430/1797] 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 0431/1797] 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 0432/1797] 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 0433/1797] 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 0434/1797] 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 0435/1797] 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 0436/1797] 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 0437/1797] 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 0438/1797] 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 0439/1797] 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 0440/1797] 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 0441/1797] 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 0442/1797] 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 0443/1797] 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 0444/1797] 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 0445/1797] 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 0446/1797] 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 0447/1797] 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 0448/1797] 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 0449/1797] 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 0450/1797] 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 0451/1797] 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 0452/1797] 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 0453/1797] 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 0454/1797] 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 0455/1797] 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 0456/1797] 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 0457/1797] 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 0458/1797] 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 0459/1797] 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 0460/1797] 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 0461/1797] 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 0462/1797] 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 0463/1797] 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 0464/1797] 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 0465/1797] 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 0466/1797] 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 0467/1797] 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 0468/1797] 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 0469/1797] 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 0470/1797] 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 0471/1797] 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 0472/1797] 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 0473/1797] 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 0474/1797] 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 0475/1797] 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 0476/1797] 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 0477/1797] 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 0478/1797] 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 0479/1797] 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 0480/1797] 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 0481/1797] 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 0482/1797] 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 0483/1797] 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 0484/1797] 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 0485/1797] 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 0486/1797] 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 0487/1797] 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 0488/1797] 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 0489/1797] 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 0490/1797] 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 0491/1797] 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 0492/1797] 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 0493/1797] 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 0494/1797] 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 0495/1797] 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 0496/1797] 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 0497/1797] 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 0498/1797] 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 0499/1797] 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 0500/1797] 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 0501/1797] 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 0502/1797] 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 0503/1797] 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 0504/1797] 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 0505/1797] 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 0506/1797] 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 0507/1797] 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 6114a3c9048863bc372c5d47644c75f5a2ed49b4 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 1 Dec 2023 09:32:56 -0700 Subject: [PATCH 0508/1797] Hack around #3045 by just ignoring things that don't have a ctype when I collect components in the docplex writer --- pyomo/contrib/cp/repn/docplex_writer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index 51c3f66140e..50a2d72aed8 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -1005,6 +1005,9 @@ def collect_valid_components(model, active=True, sort=None, valid=set(), targets unrecognized = {} components = {k: [] for k in targets} for obj in model.component_data_objects(active=True, descend_into=True, sort=sort): + # HACK around #3045 + if not hasattr(obj, 'ctype'): + continue ctype = obj.ctype if ctype in components: components[ctype].append(obj) From 1a3ddbb831fafecc156d2f144a7cf308b8340e1b Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 1 Dec 2023 15:11:10 -0700 Subject: [PATCH 0509/1797] Adding SequenceVar component and a couple tests --- pyomo/contrib/cp/sequence_var.py | 133 ++++++++++++++++++++ pyomo/contrib/cp/tests/test_sequence_var.py | 56 +++++++++ 2 files changed, 189 insertions(+) create mode 100644 pyomo/contrib/cp/sequence_var.py create mode 100644 pyomo/contrib/cp/tests/test_sequence_var.py diff --git a/pyomo/contrib/cp/sequence_var.py b/pyomo/contrib/cp/sequence_var.py new file mode 100644 index 00000000000..d5553cacd20 --- /dev/null +++ b/pyomo/contrib/cp/sequence_var.py @@ -0,0 +1,133 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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.log import is_debug_set +from pyomo.common.modeling import NOTSET +from pyomo.contrib.cp import IntervalVar +from pyomo.core import ModelComponentFactory +from pyomo.core.base.component import ActiveComponentData +from pyomo.core.base.global_set import UnindexedComponent_index +from pyomo.core.base.indexed_component import ActiveIndexedComponent + +import sys +from weakref import ref as weakref_ref + +logger = logging.getLogger(__name__) + + +class _SequenceVarData(ActiveComponentData): + """This class defines the abstract interface for a single sequence variable.""" + __slots__ = ('interval_vars',) + def __init__(self, component=None): + # in-lining ActiveComponentData and ComponentData constructors, as is + # traditional: + self._component = weakref_ref(component) if (component is not None) else None + self._index = NOTSET + self._active = True + + # This thing is really just an ordered set of interval vars that we can + # write constraints over. + self.interval_vars = [] + + def set_value(self, expr): + # We'll demand expr be a list for now--it needs to be ordered so this + # doesn't seem like too much to ask + if expr.__class__ is not list: + raise ValueError( + "'expr' for SequenceVar must be a list of IntervalVars. " + "Encountered type '%s' constructing '%s'" % (type(expr), + self.name)) + for v in expr: + if not hasattr(v, 'ctype') or v.ctype is not IntervalVar: + raise ValueError( + "The SequenceVar 'expr' argument must be a list of " + "IntervalVars. The 'expr' for SequenceVar '%s' included " + "an object of type '%s'" % (self.name, type(v))) + self.interval_vars.append(v) + + +@ModelComponentFactory.register("Sequences of IntervalVars") +class SequenceVar(ActiveIndexedComponent): + _ComponentDataClass = _SequenceVarData + + def __new__(cls, *args, **kwds): + if cls != SequenceVar: + return super(SequenceVar, cls).__new__(cls) + if args == (): + return ScalarSequenceVar.__new__(ScalarSequenceVar) + else: + return IndexedSequenceVar.__new__(IndexedSequenceVar) + + def __init__(self, *args, **kwargs): + self._init_rule = kwargs.pop('rule', None) + self._init_expr = kwargs.pop('expr', None) + kwargs.setdefault('ctype', SequenceVar) + super(SequenceVar, self).__init__(*args, **kwargs) + + if self._init_expr is not None and self._init_rule is not None: + raise ValueError( + "Cannot specify both rule= and expr= for SequenceVar %s" % (self.name,) + ) + + def _getitem_when_not_present(self, index): + if index is None and not self.is_indexed(): + obj = self._data[index] = self + else: + obj = self._data[index] = self._ComponentDataClass(component=self) + parent = self.parent_block() + obj._index = index + + if self._init_rule is not None: + obj.interval_vars = self._init_rule(parent, index) + if self._init_expr is not None: + obj.interval_vars = self._init_expr + + return obj + + def construct(self, data=None): + """ + Construct the _SequenceVarData objects for this SequenceVar + """ + if self._constructed: + return + self._constructed = True + + if is_debug_set(logger): + logger.debug("Constructing SequenceVar %s" % self.name) + + # Initialize index in case we hit the exception below + index = None + try: + if not self.is_indexed(): + self._getitem_when_not_present(None) + if self._init_rule is not None: + for index in self.index_set(): + self._getitem_when_not_present(index) + except Exception: + err = sys.exc_info()[1] + logger.error( + "Rule failed when initializing sequence variable for " + "SequenceVar %s with index %s:\n%s: %s" + % (self.name, str(index), type(err).__name__, err) + ) + raise + +class ScalarSequenceVar(_SequenceVarData, SequenceVar): + def __init__(self, *args, **kwds): + _SequenceVarData.__init__(self, component=self) + SequenceVar.__init__(self, *args, **kwds) + self._index = UnindexedComponent_index + + +class IndexedSequenceVar(SequenceVar): + pass diff --git a/pyomo/contrib/cp/tests/test_sequence_var.py b/pyomo/contrib/cp/tests/test_sequence_var.py new file mode 100644 index 00000000000..9a2278d2de3 --- /dev/null +++ b/pyomo/contrib/cp/tests/test_sequence_var.py @@ -0,0 +1,56 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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.cp.interval_var import IntervalVar +from pyomo.contrib.cp.sequence_var import SequenceVar, IndexedSequenceVar +from pyomo.environ import ConcreteModel, Integers, Set, value, Var + + +class TestScalarSequenceVar(unittest.TestCase): + def test_initialize_with_no_data(self): + m = ConcreteModel() + m.i = SequenceVar() + + self.assertIsInstance(m.i, SequenceVar) + self.assertIsInstance(m.i.interval_vars, list) + self.assertEqual(len(m.i.interval_vars), 0) + + def test_initialize_with_expr(self): + m = ConcreteModel() + m.S = Set(initialize=range(3)) + m.i = IntervalVar(m.S, start=(0, 5)) + m.seq = SequenceVar(expr=[m.i[j] for j in m.S]) + self.assertEqual(len(m.seq.interval_vars), 3) + for j in m.S: + self.assertIs(m.seq.interval_vars[j], m.i[j]) + + +class TestIndexedSequenceVar(unittest.TestCase): + def test_initialize_with_rule(self): + m = ConcreteModel() + m.alph = Set(initialize=['a', 'b']) + m.num = Set(initialize=[1, 2]) + m.i = IntervalVar(m.alph, m.num) + + def the_rule(m, j): + return [m.i[j, k] for k in m.num] + m.seq = SequenceVar(m.alph, rule=the_rule) + m.seq.pprint() + + self.assertIsInstance(m.seq, IndexedSequenceVar) + self.assertEqual(len(m.seq), 2) + for j in m.alph: + self.assertTrue(j in m.seq) + self.assertEqual(len(m.seq[j].interval_vars), 2) + for k in m.num: + self.assertIs(m.seq[j].interval_vars[k - 1], m.i[j, k]) + From 9dc5aa6bab909504c6042e2a0c2bd793506896c4 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 1 Dec 2023 15:30:49 -0700 Subject: [PATCH 0510/1797] Adding a pretty pprint --- pyomo/contrib/cp/sequence_var.py | 15 ++++++ pyomo/contrib/cp/tests/test_sequence_var.py | 51 +++++++++++++++++++-- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/cp/sequence_var.py b/pyomo/contrib/cp/sequence_var.py index d5553cacd20..412c34e9176 100644 --- a/pyomo/contrib/cp/sequence_var.py +++ b/pyomo/contrib/cp/sequence_var.py @@ -122,6 +122,21 @@ def construct(self, data=None): ) raise + def _pprint(self): + """Print component information.""" + headers = [ + ("Size", len(self)), + ("Index", self._index_set if self.is_indexed() else None), + ] + return ( + headers, + self._data.items(), + ("IntervalVars",), + lambda k, v: [ + '[' + ', '.join(iv.name for iv in v.interval_vars) + ']', + ] + ) + class ScalarSequenceVar(_SequenceVarData, SequenceVar): def __init__(self, *args, **kwds): _SequenceVarData.__init__(self, component=self) diff --git a/pyomo/contrib/cp/tests/test_sequence_var.py b/pyomo/contrib/cp/tests/test_sequence_var.py index 9a2278d2de3..da9b5a298d3 100644 --- a/pyomo/contrib/cp/tests/test_sequence_var.py +++ b/pyomo/contrib/cp/tests/test_sequence_var.py @@ -9,6 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +from io import StringIO import pyomo.common.unittest as unittest from pyomo.contrib.cp.interval_var import IntervalVar from pyomo.contrib.cp.sequence_var import SequenceVar, IndexedSequenceVar @@ -24,18 +25,44 @@ def test_initialize_with_no_data(self): self.assertIsInstance(m.i.interval_vars, list) self.assertEqual(len(m.i.interval_vars), 0) - def test_initialize_with_expr(self): + def get_model(self): m = ConcreteModel() m.S = Set(initialize=range(3)) m.i = IntervalVar(m.S, start=(0, 5)) m.seq = SequenceVar(expr=[m.i[j] for j in m.S]) + + return m + + def test_initialize_with_expr(self): + m = self.get_model() self.assertEqual(len(m.seq.interval_vars), 3) for j in m.S: self.assertIs(m.seq.interval_vars[j], m.i[j]) + def test_pprint(self): + m = self.get_model() + buf = StringIO() + m.seq.pprint(ostream=buf) + self.assertEqual( + buf.getvalue().strip(), + """ +seq : Size=1, Index=None + Key : IntervalVars + None : [i[0], i[1], i[2]] + """.strip() + ) class TestIndexedSequenceVar(unittest.TestCase): - def test_initialize_with_rule(self): + def test_initialize_with_not_data(self): + m = ConcreteModel() + m.i = SequenceVar([1, 2]) + + self.assertIsInstance(m.i, IndexedSequenceVar) + for j in [1, 2]: + self.assertIsInstance(m.i[j].interval_vars, list) + self.assertEqual(len(m.i[j].interval_vars), 0) + + def make_model(self): m = ConcreteModel() m.alph = Set(initialize=['a', 'b']) m.num = Set(initialize=[1, 2]) @@ -44,7 +71,11 @@ def test_initialize_with_rule(self): def the_rule(m, j): return [m.i[j, k] for k in m.num] m.seq = SequenceVar(m.alph, rule=the_rule) - m.seq.pprint() + + return m + + def test_initialize_with_rule(self): + m = self.make_model() self.assertIsInstance(m.seq, IndexedSequenceVar) self.assertEqual(len(m.seq), 2) @@ -54,3 +85,17 @@ def the_rule(m, j): for k in m.num: self.assertIs(m.seq[j].interval_vars[k - 1], m.i[j, k]) + def test_pprint(self): + m = self.make_model() + m.seq.pprint() + + buf = StringIO() + m.seq.pprint(ostream=buf) + self.assertEqual( + buf.getvalue().strip(), + """ +seq : Size=2, Index=alph + Key : IntervalVars + a : [i[a,1], i[a,2]] + b : [i[b,1], i[b,2]]""".strip() + ) From 2e6ce49469f3cc868d940aa1b65fb629d69cc719 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Fri, 1 Dec 2023 15:41:33 -0700 Subject: [PATCH 0511/1797] 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 0512/1797] 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 749b4e81dd725a919cc945af039d02baad145af0 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Mon, 4 Dec 2023 07:21:08 -0700 Subject: [PATCH 0513/1797] Adding some sequence var tests, making sure we hit set_value when we want error checking --- pyomo/contrib/cp/sequence_var.py | 6 ++-- pyomo/contrib/cp/tests/test_sequence_var.py | 40 +++++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/cp/sequence_var.py b/pyomo/contrib/cp/sequence_var.py index 412c34e9176..b0691fbd74a 100644 --- a/pyomo/contrib/cp/sequence_var.py +++ b/pyomo/contrib/cp/sequence_var.py @@ -42,7 +42,7 @@ def __init__(self, component=None): def set_value(self, expr): # We'll demand expr be a list for now--it needs to be ordered so this # doesn't seem like too much to ask - if expr.__class__ is not list: + if not hasattr(expr, '__iter__'): raise ValueError( "'expr' for SequenceVar must be a list of IntervalVars. " "Encountered type '%s' constructing '%s'" % (type(expr), @@ -88,9 +88,9 @@ def _getitem_when_not_present(self, index): obj._index = index if self._init_rule is not None: - obj.interval_vars = self._init_rule(parent, index) + obj.set_value(self._init_rule(parent, index)) if self._init_expr is not None: - obj.interval_vars = self._init_expr + obj.set_value(self._init_expr) return obj diff --git a/pyomo/contrib/cp/tests/test_sequence_var.py b/pyomo/contrib/cp/tests/test_sequence_var.py index da9b5a298d3..852d9f2134a 100644 --- a/pyomo/contrib/cp/tests/test_sequence_var.py +++ b/pyomo/contrib/cp/tests/test_sequence_var.py @@ -25,6 +25,15 @@ def test_initialize_with_no_data(self): self.assertIsInstance(m.i.interval_vars, list) self.assertEqual(len(m.i.interval_vars), 0) + m.iv1 = IntervalVar() + m.iv2 = IntervalVar() + m.i.set_value(expr=[m.iv1, m.iv2]) + + self.assertIsInstance(m.i.interval_vars, list) + self.assertEqual(len(m.i.interval_vars), 2) + self.assertIs(m.i.interval_vars[0], m.iv1) + self.assertIs(m.i.interval_vars[1], m.iv2) + def get_model(self): m = ConcreteModel() m.S = Set(initialize=range(3)) @@ -52,6 +61,27 @@ def test_pprint(self): """.strip() ) + def test_interval_vars_not_a_list(self): + m = self.get_model() + + with self.assertRaisesRegex( + ValueError, + "'expr' for SequenceVar must be a list of IntervalVars. " + "Encountered type '' constructing 'seq2'" + ): + m.seq2 = SequenceVar(expr=1) + + def test_interval_vars_list_includes_things_that_are_not_interval_vars(self): + m = self.get_model() + + with self.assertRaisesRegex( + ValueError, + "The SequenceVar 'expr' argument must be a list of " + "IntervalVars. The 'expr' for SequenceVar 'seq2' included " + "an object of type ''" + ): + m.seq2 = SequenceVar(expr=m.i) + class TestIndexedSequenceVar(unittest.TestCase): def test_initialize_with_not_data(self): m = ConcreteModel() @@ -62,6 +92,16 @@ def test_initialize_with_not_data(self): self.assertIsInstance(m.i[j].interval_vars, list) self.assertEqual(len(m.i[j].interval_vars), 0) + m.iv = IntervalVar() + m.iv2 = IntervalVar([0, 1]) + m.i[2] = [m.iv] + [m.iv2[i] for i in [0, 1]] + + self.assertEqual(len(m.i[2].interval_vars), 3) + self.assertEqual(len(m.i[1].interval_vars), 0) + self.assertIs(m.i[2].interval_vars[0], m.iv) + for i in [0, 1]: + self.assertIs(m.i[2].interval_vars[i + 1], m.iv2[i]) + def make_model(self): m = ConcreteModel() m.alph = Set(initialize=['a', 'b']) From 3f3b3c364fb20c740f83982081d4deccb50ffc2a Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 4 Dec 2023 09:44:11 -0700 Subject: [PATCH 0514/1797] 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 0515/1797] 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 0516/1797] 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 0517/1797] 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 0518/1797] 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 0519/1797] 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 0520/1797] 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 0521/1797] 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 0522/1797] 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 0523/1797] 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 0524/1797] 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 0525/1797] 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 0526/1797] 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 0527/1797] 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 0528/1797] 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 0529/1797] 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 0530/1797] 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 0531/1797] 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 0532/1797] 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 0533/1797] 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 0534/1797] 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 0535/1797] 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 0536/1797] 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 0537/1797] 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 0538/1797] 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 0539/1797] 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 0540/1797] 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 0541/1797] 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 0542/1797] 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 0543/1797] 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 0544/1797] 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 0545/1797] 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 0546/1797] 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 0547/1797] 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 0548/1797] 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 0549/1797] 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 0550/1797] 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 0551/1797] 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 0552/1797] 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 0553/1797] 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 0554/1797] 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 0555/1797] 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 0556/1797] 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 0557/1797] 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 23c1ce3e2e869ab7bc6388c6c35cecb53c92492a Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Sun, 10 Dec 2023 13:00:29 -0700 Subject: [PATCH 0558/1797] Adding some expression nodes involving sequence vars and some tests for them --- pyomo/contrib/cp/__init__.py | 8 + .../scheduling_expr/sequence_expressions.py | 173 ++++++++++++++++++ .../cp/tests/test_sequence_expressions.py | 104 +++++++++++ 3 files changed, 285 insertions(+) create mode 100644 pyomo/contrib/cp/scheduling_expr/sequence_expressions.py create mode 100644 pyomo/contrib/cp/tests/test_sequence_expressions.py diff --git a/pyomo/contrib/cp/__init__.py b/pyomo/contrib/cp/__init__.py index c51160bf931..03196537446 100644 --- a/pyomo/contrib/cp/__init__.py +++ b/pyomo/contrib/cp/__init__.py @@ -6,6 +6,14 @@ IntervalVarPresence, ) from pyomo.contrib.cp.repn.docplex_writer import DocplexWriter, CPOptimizerSolver +from pyomo.contrib.cp.sequence_var import SequenceVar +from pyomo.contrib.cp.scheduling_expr.sequence_expressions import ( + no_overlap, + first_in_sequence, + last_in_sequence, + before_in_sequence, + predecessor_to, +) from pyomo.contrib.cp.scheduling_expr.step_function_expressions import ( AlwaysIn, Step, diff --git a/pyomo/contrib/cp/scheduling_expr/sequence_expressions.py b/pyomo/contrib/cp/scheduling_expr/sequence_expressions.py new file mode 100644 index 00000000000..0a49198c1d1 --- /dev/null +++ b/pyomo/contrib/cp/scheduling_expr/sequence_expressions.py @@ -0,0 +1,173 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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.logical_expr import BooleanExpression + +# ESJ TODO: The naming in this file needs more thought, and it appears I do not +# need the base class. + +class SequenceVarExpression(BooleanExpression): + pass + +class NoOverlapExpression(SequenceVarExpression): + """ + Expression representing that none of the IntervalVars in a SequenceVar overlap + (if they are scheduled) + + args: + args (tuple): Child node of type SequenceVar + """ + def nargs(self): + return 1 + + def _to_string(self, values, verbose, smap): + return "no_overlap(%s)" % values[0] + + +class FirstInSequenceExpression(SequenceVarExpression): + """ + Expression representing that the specified IntervalVar is the first in the + sequence specified by SequenceVar (if it is scheduled) + + args: + args (tuple): Child nodes, the first of type IntervalVar, the second of type + SequenceVar + """ + def nargs(self): + return 2 + + def _to_string(self, values, verbose, smap): + return "first_in(%s, %s)" % (values[0], values[1]) + + +class LastInSequenceExpression(SequenceVarExpression): + """ + Expression representing that the specified IntervalVar is the last in the + sequence specified by SequenceVar (if it is scheduled) + + args: + args (tuple): Child nodes, the first of type IntervalVar, the second of type + SequenceVar + """ + def nargs(self): + return 2 + + def _to_string(self, values, verbose, smap): + return "last_in(%s, %s)" % (values[0], values[1]) + + +class BeforeInSequenceExpression(SequenceVarExpression): + """ + Expression representing that one IntervalVar occurs before another in the + sequence specified by the given SequenceVar (if both are scheduled) + + args: + args (tuple): Child nodes, the IntervalVar that must be before, the + IntervalVar that must be after, and the SequenceVar + """ + def nargs(self): + return 3 + + def _to_string(self, values, verbose, smap): + return "before_in(%s, %s, %s)" % (values[0], values[1], values[2]) + + +class PredecessorToExpression(SequenceVarExpression): + """ + Expression representing that one IntervalVar is a direct predecessor to another + in the sequence specified by the given SequenceVar (if both are scheduled) + + args: + args (tuple): Child nodes, the predecessor IntervalVar, the successor + IntervalVar, and the SequenceVar + """ + def nargs(self): + return 3 + + def _to_string(self, values, verbose, smap): + return "predecessor_to(%s, %s, %s)" % (values[0], values[1], values[2]) + + +def no_overlap(sequence_var): + """ + Creates a new NoOverlapExpression + + Requires that none of the scheduled intervals in the SequenceVar overlap each other + + args: + sequence_var: A SequenceVar + """ + return NoOverlapExpression((sequence_var,)) + + +def first_in_sequence(interval_var, sequence_var): + """ + Creates a new FirstInSequenceExpression + + Requires that 'interval_var' be the first in the sequence specified by + 'sequence_var' if it is scheduled + + args: + interval_var (IntervalVar): The activity that should be scheduled first + if it is scheduled at all + sequence_var (SequenceVar): The sequence of activities + """ + return FirstInSequenceExpression((interval_var, sequence_var,)) + + +def last_in_sequence(interval_var, sequence_var): + """ + Creates a new LastInSequenceExpression + + Requires that 'interval_var' be the last in the sequence specified by + 'sequence_var' if it is scheduled + + args: + interval_var (IntervalVar): The activity that should be scheduled last + if it is scheduled at all + sequence_var (SequenceVar): The sequence of activities + """ + + return LastInSequenceExpression((interval_var, sequence_var,)) + + +def before_in_sequence(before_var, after_var, sequence_var): + """ + Creates a new BeforeInSequenceExpression + + Requires that 'before_var' be scheduled to start before 'after_var' in the + sequence spcified bv 'sequence_var', if both are scheduled + + args: + before_var (IntervalVar): The activity that should be scheduled earlier in + the sequence + after_var (IntervalVar): The activity that should be scheduled later in the + sequence + sequence_var (SequenceVar): The sequence of activities + """ + return BeforeInSequenceExpression((before_var, after_var, sequence_var,)) + + +def predecessor_to(before_var, after_var, sequence_var): + """ + Creates a new PredecessorToExpression + + Requires that 'before_var' be a direct predecessor to 'after_var' in the + sequence specified by 'sequence_var', if both are scheduled + + args: + before_var (IntervalVar): The activity that should be scheduled as the + predecessor + after_var (IntervalVar): The activity that should be scheduled as the + successor + sequence_var (SequenceVar): The sequence of activities + """ + return PredecessorToExpression((before_var, after_var, sequence_var,)) diff --git a/pyomo/contrib/cp/tests/test_sequence_expressions.py b/pyomo/contrib/cp/tests/test_sequence_expressions.py new file mode 100644 index 00000000000..a35eb9b67af --- /dev/null +++ b/pyomo/contrib/cp/tests/test_sequence_expressions.py @@ -0,0 +1,104 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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 io import StringIO +import pyomo.common.unittest as unittest +from pyomo.contrib.cp.interval_var import IntervalVar +from pyomo.contrib.cp.scheduling_expr.sequence_expressions import ( + NoOverlapExpression, + FirstInSequenceExpression, + LastInSequenceExpression, + BeforeInSequenceExpression, + PredecessorToExpression, + no_overlap, + predecessor_to, + before_in_sequence, + first_in_sequence, + last_in_sequence, +) +from pyomo.contrib.cp.sequence_var import SequenceVar, IndexedSequenceVar +from pyomo.environ import ConcreteModel, Integers, LogicalConstraint, Set, value, Var + + +class TestSequenceVarExpressions(unittest.TestCase): + def get_model(self): + m = ConcreteModel() + m.S = Set(initialize=range(3)) + m.i = IntervalVar(m.S, start=(0, 5)) + m.seq = SequenceVar(expr=[m.i[j] for j in m.S]) + + return m + + def test_no_overlap(self): + m = self.get_model() + m.c = LogicalConstraint(expr=no_overlap(m.seq)) + e = m.c.expr + + self.assertIsInstance(e, NoOverlapExpression) + self.assertEqual(e.nargs(), 1) + self.assertEqual(len(e.args), 1) + self.assertIs(e.args[0], m.seq) + + self.assertEqual(str(e), "no_overlap(seq)") + + def test_first_in_sequence(self): + m = self.get_model() + m.c = LogicalConstraint(expr=first_in_sequence(m.i[2], m.seq)) + e = m.c.expr + + self.assertIsInstance(e, FirstInSequenceExpression) + self.assertEqual(e.nargs(), 2) + self.assertEqual(len(e.args), 2) + self.assertIs(e.args[0], m.i[2]) + self.assertIs(e.args[1], m.seq) + + self.assertEqual(str(e), "first_in(i[2], seq)") + + def test_last_in_sequence(self): + m = self.get_model() + m.c = LogicalConstraint(expr=last_in_sequence(m.i[0], m.seq)) + e = m.c.expr + + self.assertIsInstance(e, LastInSequenceExpression) + self.assertEqual(e.nargs(), 2) + self.assertEqual(len(e.args), 2) + self.assertIs(e.args[0], m.i[0]) + self.assertIs(e.args[1], m.seq) + + self.assertEqual(str(e), "last_in(i[0], seq)") + + def test_before_in_sequence(self): + m = self.get_model() + m.c = LogicalConstraint(expr=before_in_sequence(m.i[1], m.i[0], m.seq)) + e = m.c.expr + + self.assertIsInstance(e, BeforeInSequenceExpression) + self.assertEqual(e.nargs(), 3) + self.assertEqual(len(e.args), 3) + self.assertIs(e.args[0], m.i[1]) + self.assertIs(e.args[1], m.i[0]) + self.assertIs(e.args[2], m.seq) + + self.assertEqual(str(e), "before_in(i[1], i[0], seq)") + + def test_predecessor_in_sequence(self): + m = self.get_model() + m.c = LogicalConstraint(expr=predecessor_to(m.i[0], m.i[1], m.seq)) + e = m.c.expr + + self.assertIsInstance(e, PredecessorToExpression) + self.assertEqual(e.nargs(), 3) + self.assertEqual(len(e.args), 3) + self.assertIs(e.args[0], m.i[0]) + self.assertIs(e.args[1], m.i[1]) + self.assertIs(e.args[2], m.seq) + + self.assertEqual(str(e), "predecessor_to(i[0], i[1], seq)") From 05d9020e292b2d3013b532a566f86addba6b22d3 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Mon, 11 Dec 2023 15:12:53 -0700 Subject: [PATCH 0559/1797] Adding handling for sequence var expressions in the docplex writer --- pyomo/contrib/cp/repn/docplex_writer.py | 83 +++++++++++++++++++ pyomo/contrib/cp/tests/test_docplex_walker.py | 78 ++++++++++++++++- 2 files changed, 160 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index 50a2d72aed8..38e2a0ae94f 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -30,10 +30,22 @@ IntervalVarData, IndexedIntervalVar, ) +from pyomo.contrib.cp.sequence_var import( + ScalarSequenceVar, + IndexedSequenceVar, + _SequenceVarData, +) from pyomo.contrib.cp.scheduling_expr.precedence_expressions import ( BeforeExpression, AtExpression, ) +from pyomo.contrib.cp.scheduling_expr.sequence_expressions import ( + NoOverlapExpression, + FirstInSequenceExpression, + LastInSequenceExpression, + BeforeInSequenceExpression, + PredecessorToExpression, +) from pyomo.contrib.cp.scheduling_expr.step_function_expressions import ( AlwaysIn, StepAt, @@ -491,6 +503,16 @@ def _create_docplex_interval_var(visitor, interval_var): return cpx_interval_var +def _create_docplex_sequence_var(visitor, sequence_var): + nm = sequence_var.name if visitor.symbolic_solver_labels else None + + cpx_seq_var = cp.sequence_var(name=nm, + vars=[_get_docplex_interval_var(visitor, v) + for v in sequence_var.interval_vars]) + visitor.var_map[id(sequence_var)] = cpx_seq_var + return cpx_seq_var + + def _get_docplex_interval_var(visitor, interval_var): # We might already have the interval_var and just need to retrieve it if id(interval_var) in visitor.var_map: @@ -501,6 +523,37 @@ def _get_docplex_interval_var(visitor, interval_var): return cpx_interval_var +def _get_docplex_sequence_var(visitor, sequence_var): + if id(sequence_var) in visitor.var_map: + cpx_seq_var = visitor.var_map[id(sequence_var)] + else: + cpx_seq_var = _create_docplex_sequence_var(visitor, sequence_var) + visitor.cpx.add(cpx_seq_var) + return cpx_seq_var + + +def _before_sequence_var(visitor, child): + _id = id(child) + if _id not in visitor.var_map: + cpx_seq_var = _get_docplex_sequence_var(visitor, child) + visitor.var_map[_id] = cpx_seq_var + visitor.pyomo_to_docplex[child] = cpx_seq_var + + return False, (_GENERAL, visitor.var_map[_id]) + + +def _before_indexed_sequence_var(visitor, child): + # ESJ TODO: I'm not sure we can encounter an indexed sequence var in an + # expression right now? + cpx_vars = {} + for i, v in child.items(): + cpx_sequence_var = _get_docplex_sequence_var(visitor, v) + visitor.var_map[id(v)] = cpx_sequence_var + visitor.pyomo_to_docplex[v] = cpx_sequence_var + cpx_vars[i] = cpx_sequence_var + return False, (_GENERAL, cpx_vars) + + def _before_interval_var(visitor, child): _id = id(child) if _id not in visitor.var_map: @@ -902,6 +955,28 @@ def _handle_always_in_node(visitor, node, cumul_func, lb, ub, start, end): ) +def _handle_no_overlap_expression_node(visitor, node, seq_var): + return _GENERAL, cp.no_overlap(seq_var[1]) + + +def _handle_first_in_sequence_expression_node(visitor, node, interval_var, seq_var): + return _GENERAL, cp.first(seq_var[1], interval_var[1]) + + +def _handle_last_in_sequence_expression_node(visitor, node, interval_var, seq_var): + return _GENERAL, cp.last(seq_var[1], interval_var[1]) + + +def _handle_before_in_sequence_expression_node(visitor, node, before_var, + after_var, seq_var): + return _GENERAL, cp.before(seq_var[1], before_var[1], after_var[1]) + + +def _handle_predecessor_to_expression_node(visitor, node, before_var, after_var, + seq_var): + return _GENERAL, cp.previous(seq_var[1], before_var[1], after_var[1]) + + class LogicalToDoCplex(StreamBasedExpressionVisitor): _operator_handles = { EXPR.GetItemExpression: _handle_getitem, @@ -941,6 +1016,11 @@ class LogicalToDoCplex(StreamBasedExpressionVisitor): AlwaysIn: _handle_always_in_node, _GeneralExpressionData: _handle_named_expression_node, ScalarExpression: _handle_named_expression_node, + NoOverlapExpression: _handle_no_overlap_expression_node, + FirstInSequenceExpression: _handle_first_in_sequence_expression_node, + LastInSequenceExpression: _handle_last_in_sequence_expression_node, + BeforeInSequenceExpression: _handle_before_in_sequence_expression_node, + PredecessorToExpression: _handle_predecessor_to_expression_node, } _var_handles = { IntervalVarStartTime: _before_interval_var_start_time, @@ -950,6 +1030,9 @@ class LogicalToDoCplex(StreamBasedExpressionVisitor): ScalarIntervalVar: _before_interval_var, IntervalVarData: _before_interval_var, IndexedIntervalVar: _before_indexed_interval_var, + ScalarSequenceVar: _before_sequence_var, + _SequenceVarData: _before_sequence_var, + IndexedSequenceVar: _before_indexed_sequence_var, ScalarVar: _before_var, _GeneralVarData: _before_var, IndexedVar: _before_indexed_var, diff --git a/pyomo/contrib/cp/tests/test_docplex_walker.py b/pyomo/contrib/cp/tests/test_docplex_walker.py index 97bc538c827..dc35a60050c 100644 --- a/pyomo/contrib/cp/tests/test_docplex_walker.py +++ b/pyomo/contrib/cp/tests/test_docplex_walker.py @@ -11,7 +11,15 @@ import pyomo.common.unittest as unittest -from pyomo.contrib.cp import IntervalVar +from pyomo.contrib.cp import ( + IntervalVar, + SequenceVar, + no_overlap, + first_in_sequence, + last_in_sequence, + before_in_sequence, + predecessor_to, +) from pyomo.contrib.cp.scheduling_expr.step_function_expressions import ( AlwaysIn, Step, @@ -769,6 +777,74 @@ def test_interval_var_fixed_start_and_end(self): self.assertEqual(i.get_end(), (6, 6)) +@unittest.skipIf(not docplex_available, "docplex is not available") +class TestCPExpressionWalker_SequenceVars(CommonTest): + def get_model(self): + m = super().get_model() + m.seq = SequenceVar(expr=[m.i, m.i2[1], m.i2[2]]) + + return m + + def check_scalar_sequence_var(self, m, visitor): + self.assertIn(id(m.seq), visitor.var_map) + seq = visitor.var_map[id(m.seq)] + + i = visitor.var_map[id(m.i)] + i21 = visitor.var_map[id(m.i2[1])] + i22 = visitor.var_map[id(m.i2[2])] + + ivs = seq.get_interval_variables() + self.assertEqual(len(ivs), 3) + self.assertIs(ivs[0], i) + self.assertIs(ivs[1], i21) + self.assertIs(ivs[2], i22) + + return seq, i, i21, i22 + + def test_scalar_sequence_var(self): + m = self.get_model() + + visitor = self.get_visitor() + expr = visitor.walk_expression((m.seq, m.seq, 0)) + self.check_scalar_sequence_var(m, visitor) + + def test_no_overlap(self): + m = self.get_model() + e = no_overlap(m.seq) + visitor = self.get_visitor() + expr = visitor.walk_expression((e, e, 0)) + + seq, i, i21, i22 = self.check_scalar_sequence_var(m, visitor) + self.assertTrue(expr[1].equals(cp.no_overlap(seq))) + + def test_first_in_sequence(self): + m = self.get_model() + e = first_in_sequence(m.i2[1], m.seq) + visitor = self.get_visitor() + expr = visitor.walk_expression((e, e, 0)) + + seq, i, i21, i22 = self.check_scalar_sequence_var(m, visitor) + self.assertTrue(expr[1].equals(cp.first(seq, i21))) + + def test_before_in_sequence(self): + m = self.get_model() + e = last_in_sequence(m.i, m.seq) + visitor = self.get_visitor() + expr = visitor.walk_expression((e, e, 0)) + + seq, i, i21, i22 = self.check_scalar_sequence_var(m, visitor) + self.assertTrue(expr[1].equals(cp.last(seq, i))) + + def test_last_in_sequence(self): + m = self.get_model() + e = last_in_sequence(m.i2[1], m.seq) + visitor = self.get_visitor() + expr = visitor.walk_expression((e, e, 0)) + + seq, i, i21, i22 = self.check_scalar_sequence_var(m, visitor) + self.assertTrue(expr[1].equals(cp.last(seq, i21))) + + @unittest.skipIf(not docplex_available, "docplex is not available") class TestCPExpressionWalker_PrecedenceExpressions(CommonTest): def test_start_before_start(self): From 1cbadc533368a1394a229dfee2bfe759953c96f3 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Mon, 11 Dec 2023 15:15:31 -0700 Subject: [PATCH 0560/1797] Blackify --- pyomo/contrib/cp/repn/docplex_writer.py | 21 ++++++++++------- .../scheduling_expr/sequence_expressions.py | 23 ++++++++++++------- pyomo/contrib/cp/sequence_var.py | 16 +++++++------ .../cp/tests/test_sequence_expressions.py | 6 ++--- pyomo/contrib/cp/tests/test_sequence_var.py | 20 ++++++++-------- 5 files changed, 51 insertions(+), 35 deletions(-) diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index 38e2a0ae94f..fb816240c1e 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -30,7 +30,7 @@ IntervalVarData, IndexedIntervalVar, ) -from pyomo.contrib.cp.sequence_var import( +from pyomo.contrib.cp.sequence_var import ( ScalarSequenceVar, IndexedSequenceVar, _SequenceVarData, @@ -506,9 +506,12 @@ def _create_docplex_interval_var(visitor, interval_var): def _create_docplex_sequence_var(visitor, sequence_var): nm = sequence_var.name if visitor.symbolic_solver_labels else None - cpx_seq_var = cp.sequence_var(name=nm, - vars=[_get_docplex_interval_var(visitor, v) - for v in sequence_var.interval_vars]) + cpx_seq_var = cp.sequence_var( + name=nm, + vars=[ + _get_docplex_interval_var(visitor, v) for v in sequence_var.interval_vars + ], + ) visitor.var_map[id(sequence_var)] = cpx_seq_var return cpx_seq_var @@ -967,13 +970,15 @@ def _handle_last_in_sequence_expression_node(visitor, node, interval_var, seq_va return _GENERAL, cp.last(seq_var[1], interval_var[1]) -def _handle_before_in_sequence_expression_node(visitor, node, before_var, - after_var, seq_var): +def _handle_before_in_sequence_expression_node( + visitor, node, before_var, after_var, seq_var +): return _GENERAL, cp.before(seq_var[1], before_var[1], after_var[1]) -def _handle_predecessor_to_expression_node(visitor, node, before_var, after_var, - seq_var): +def _handle_predecessor_to_expression_node( + visitor, node, before_var, after_var, seq_var +): return _GENERAL, cp.previous(seq_var[1], before_var[1], after_var[1]) diff --git a/pyomo/contrib/cp/scheduling_expr/sequence_expressions.py b/pyomo/contrib/cp/scheduling_expr/sequence_expressions.py index 0a49198c1d1..b39322322da 100644 --- a/pyomo/contrib/cp/scheduling_expr/sequence_expressions.py +++ b/pyomo/contrib/cp/scheduling_expr/sequence_expressions.py @@ -14,9 +14,11 @@ # ESJ TODO: The naming in this file needs more thought, and it appears I do not # need the base class. + class SequenceVarExpression(BooleanExpression): pass + class NoOverlapExpression(SequenceVarExpression): """ Expression representing that none of the IntervalVars in a SequenceVar overlap @@ -25,6 +27,7 @@ class NoOverlapExpression(SequenceVarExpression): args: args (tuple): Child node of type SequenceVar """ + def nargs(self): return 1 @@ -41,6 +44,7 @@ class FirstInSequenceExpression(SequenceVarExpression): args (tuple): Child nodes, the first of type IntervalVar, the second of type SequenceVar """ + def nargs(self): return 2 @@ -57,6 +61,7 @@ class LastInSequenceExpression(SequenceVarExpression): args (tuple): Child nodes, the first of type IntervalVar, the second of type SequenceVar """ + def nargs(self): return 2 @@ -70,9 +75,10 @@ class BeforeInSequenceExpression(SequenceVarExpression): sequence specified by the given SequenceVar (if both are scheduled) args: - args (tuple): Child nodes, the IntervalVar that must be before, the + args (tuple): Child nodes, the IntervalVar that must be before, the IntervalVar that must be after, and the SequenceVar """ + def nargs(self): return 3 @@ -86,9 +92,10 @@ class PredecessorToExpression(SequenceVarExpression): in the sequence specified by the given SequenceVar (if both are scheduled) args: - args (tuple): Child nodes, the predecessor IntervalVar, the successor + args (tuple): Child nodes, the predecessor IntervalVar, the successor IntervalVar, and the SequenceVar """ + def nargs(self): return 3 @@ -120,7 +127,7 @@ def first_in_sequence(interval_var, sequence_var): if it is scheduled at all sequence_var (SequenceVar): The sequence of activities """ - return FirstInSequenceExpression((interval_var, sequence_var,)) + return FirstInSequenceExpression((interval_var, sequence_var)) def last_in_sequence(interval_var, sequence_var): @@ -136,24 +143,24 @@ def last_in_sequence(interval_var, sequence_var): sequence_var (SequenceVar): The sequence of activities """ - return LastInSequenceExpression((interval_var, sequence_var,)) + return LastInSequenceExpression((interval_var, sequence_var)) def before_in_sequence(before_var, after_var, sequence_var): """ Creates a new BeforeInSequenceExpression - Requires that 'before_var' be scheduled to start before 'after_var' in the + Requires that 'before_var' be scheduled to start before 'after_var' in the sequence spcified bv 'sequence_var', if both are scheduled args: - before_var (IntervalVar): The activity that should be scheduled earlier in + before_var (IntervalVar): The activity that should be scheduled earlier in the sequence after_var (IntervalVar): The activity that should be scheduled later in the sequence sequence_var (SequenceVar): The sequence of activities """ - return BeforeInSequenceExpression((before_var, after_var, sequence_var,)) + return BeforeInSequenceExpression((before_var, after_var, sequence_var)) def predecessor_to(before_var, after_var, sequence_var): @@ -170,4 +177,4 @@ def predecessor_to(before_var, after_var, sequence_var): successor sequence_var (SequenceVar): The sequence of activities """ - return PredecessorToExpression((before_var, after_var, sequence_var,)) + return PredecessorToExpression((before_var, after_var, sequence_var)) diff --git a/pyomo/contrib/cp/sequence_var.py b/pyomo/contrib/cp/sequence_var.py index b0691fbd74a..a77f4c2c415 100644 --- a/pyomo/contrib/cp/sequence_var.py +++ b/pyomo/contrib/cp/sequence_var.py @@ -27,7 +27,9 @@ class _SequenceVarData(ActiveComponentData): """This class defines the abstract interface for a single sequence variable.""" + __slots__ = ('interval_vars',) + def __init__(self, component=None): # in-lining ActiveComponentData and ComponentData constructors, as is # traditional: @@ -45,14 +47,15 @@ def set_value(self, expr): if not hasattr(expr, '__iter__'): raise ValueError( "'expr' for SequenceVar must be a list of IntervalVars. " - "Encountered type '%s' constructing '%s'" % (type(expr), - self.name)) + "Encountered type '%s' constructing '%s'" % (type(expr), self.name) + ) for v in expr: if not hasattr(v, 'ctype') or v.ctype is not IntervalVar: raise ValueError( "The SequenceVar 'expr' argument must be a list of " "IntervalVars. The 'expr' for SequenceVar '%s' included " - "an object of type '%s'" % (self.name, type(v))) + "an object of type '%s'" % (self.name, type(v)) + ) self.interval_vars.append(v) @@ -101,7 +104,7 @@ def construct(self, data=None): if self._constructed: return self._constructed = True - + if is_debug_set(logger): logger.debug("Constructing SequenceVar %s" % self.name) @@ -132,11 +135,10 @@ def _pprint(self): headers, self._data.items(), ("IntervalVars",), - lambda k, v: [ - '[' + ', '.join(iv.name for iv in v.interval_vars) + ']', - ] + lambda k, v: ['[' + ', '.join(iv.name for iv in v.interval_vars) + ']'], ) + class ScalarSequenceVar(_SequenceVarData, SequenceVar): def __init__(self, *args, **kwds): _SequenceVarData.__init__(self, component=self) diff --git a/pyomo/contrib/cp/tests/test_sequence_expressions.py b/pyomo/contrib/cp/tests/test_sequence_expressions.py index a35eb9b67af..0ef2a9e3072 100644 --- a/pyomo/contrib/cp/tests/test_sequence_expressions.py +++ b/pyomo/contrib/cp/tests/test_sequence_expressions.py @@ -74,7 +74,7 @@ def test_last_in_sequence(self): self.assertIs(e.args[1], m.seq) self.assertEqual(str(e), "last_in(i[0], seq)") - + def test_before_in_sequence(self): m = self.get_model() m.c = LogicalConstraint(expr=before_in_sequence(m.i[1], m.i[0], m.seq)) @@ -93,12 +93,12 @@ def test_predecessor_in_sequence(self): m = self.get_model() m.c = LogicalConstraint(expr=predecessor_to(m.i[0], m.i[1], m.seq)) e = m.c.expr - + self.assertIsInstance(e, PredecessorToExpression) self.assertEqual(e.nargs(), 3) self.assertEqual(len(e.args), 3) self.assertIs(e.args[0], m.i[0]) self.assertIs(e.args[1], m.i[1]) self.assertIs(e.args[2], m.seq) - + self.assertEqual(str(e), "predecessor_to(i[0], i[1], seq)") diff --git a/pyomo/contrib/cp/tests/test_sequence_var.py b/pyomo/contrib/cp/tests/test_sequence_var.py index 852d9f2134a..385ad2dd7ec 100644 --- a/pyomo/contrib/cp/tests/test_sequence_var.py +++ b/pyomo/contrib/cp/tests/test_sequence_var.py @@ -58,16 +58,16 @@ def test_pprint(self): seq : Size=1, Index=None Key : IntervalVars None : [i[0], i[1], i[2]] - """.strip() + """.strip(), ) def test_interval_vars_not_a_list(self): m = self.get_model() with self.assertRaisesRegex( - ValueError, - "'expr' for SequenceVar must be a list of IntervalVars. " - "Encountered type '' constructing 'seq2'" + ValueError, + "'expr' for SequenceVar must be a list of IntervalVars. " + "Encountered type '' constructing 'seq2'", ): m.seq2 = SequenceVar(expr=1) @@ -75,13 +75,14 @@ def test_interval_vars_list_includes_things_that_are_not_interval_vars(self): m = self.get_model() with self.assertRaisesRegex( - ValueError, - "The SequenceVar 'expr' argument must be a list of " - "IntervalVars. The 'expr' for SequenceVar 'seq2' included " - "an object of type ''" + ValueError, + "The SequenceVar 'expr' argument must be a list of " + "IntervalVars. The 'expr' for SequenceVar 'seq2' included " + "an object of type ''", ): m.seq2 = SequenceVar(expr=m.i) + class TestIndexedSequenceVar(unittest.TestCase): def test_initialize_with_not_data(self): m = ConcreteModel() @@ -110,6 +111,7 @@ def make_model(self): def the_rule(m, j): return [m.i[j, k] for k in m.num] + m.seq = SequenceVar(m.alph, rule=the_rule) return m @@ -137,5 +139,5 @@ def test_pprint(self): seq : Size=2, Index=alph Key : IntervalVars a : [i[a,1], i[a,2]] - b : [i[b,1], i[b,2]]""".strip() + b : [i[b,1], i[b,2]]""".strip(), ) From 857105a01706237efb233d531ba5c5f2c7e070f4 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Mon, 11 Dec 2023 17:05:03 -0700 Subject: [PATCH 0561/1797] 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 70a4c5ef956140566b992e95149bf064e4ffb4e3 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Mon, 11 Dec 2023 17:10:23 -0700 Subject: [PATCH 0562/1797] Fixing a typo --- pyomo/contrib/cp/scheduling_expr/sequence_expressions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/cp/scheduling_expr/sequence_expressions.py b/pyomo/contrib/cp/scheduling_expr/sequence_expressions.py index b39322322da..d88504ac7e4 100644 --- a/pyomo/contrib/cp/scheduling_expr/sequence_expressions.py +++ b/pyomo/contrib/cp/scheduling_expr/sequence_expressions.py @@ -151,7 +151,7 @@ def before_in_sequence(before_var, after_var, sequence_var): Creates a new BeforeInSequenceExpression Requires that 'before_var' be scheduled to start before 'after_var' in the - sequence spcified bv 'sequence_var', if both are scheduled + sequence specified bv 'sequence_var', if both are scheduled args: before_var (IntervalVar): The activity that should be scheduled earlier in From f1ad1d0ecead8e42f8bdf289f21507224a1f1e5e Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Mon, 11 Dec 2023 17:11:40 -0700 Subject: [PATCH 0563/1797] The linter won't let me name something 'alph' --- pyomo/contrib/cp/tests/test_sequence_var.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pyomo/contrib/cp/tests/test_sequence_var.py b/pyomo/contrib/cp/tests/test_sequence_var.py index 385ad2dd7ec..8167c9f5b3b 100644 --- a/pyomo/contrib/cp/tests/test_sequence_var.py +++ b/pyomo/contrib/cp/tests/test_sequence_var.py @@ -105,14 +105,14 @@ def test_initialize_with_not_data(self): def make_model(self): m = ConcreteModel() - m.alph = Set(initialize=['a', 'b']) - m.num = Set(initialize=[1, 2]) - m.i = IntervalVar(m.alph, m.num) + m.alphabetic = Set(initialize=['a', 'b']) + m.numeric = Set(initialize=[1, 2]) + m.i = IntervalVar(m.alphabetic, m.numeric) def the_rule(m, j): - return [m.i[j, k] for k in m.num] + return [m.i[j, k] for k in m.numeric] - m.seq = SequenceVar(m.alph, rule=the_rule) + m.seq = SequenceVar(m.alphabetic, rule=the_rule) return m @@ -121,10 +121,10 @@ def test_initialize_with_rule(self): self.assertIsInstance(m.seq, IndexedSequenceVar) self.assertEqual(len(m.seq), 2) - for j in m.alph: + for j in m.alphabetic: self.assertTrue(j in m.seq) self.assertEqual(len(m.seq[j].interval_vars), 2) - for k in m.num: + for k in m.numeric: self.assertIs(m.seq[j].interval_vars[k - 1], m.i[j, k]) def test_pprint(self): @@ -136,7 +136,7 @@ def test_pprint(self): self.assertEqual( buf.getvalue().strip(), """ -seq : Size=2, Index=alph +seq : Size=2, Index=alphabetic Key : IntervalVars a : [i[a,1], i[a,2]] b : [i[b,1], i[b,2]]""".strip(), From c571f5c2d1952db77ea980d472a90d64463a355f Mon Sep 17 00:00:00 2001 From: robbybp Date: Tue, 12 Dec 2023 08:19:04 -0700 Subject: [PATCH 0564/1797] 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 0565/1797] 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 0566/1797] 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 0567/1797] 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 0568/1797] 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 0569/1797] 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 0570/1797] 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 0571/1797] 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 0572/1797] 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 0573/1797] 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 0574/1797] 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 0575/1797] 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 0576/1797] 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 0577/1797] 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 0578/1797] 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 4354754edb50c88d1671ecd7678237821455eeab Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 13 Dec 2023 12:48:31 -0700 Subject: [PATCH 0579/1797] Adding a test for multidimensional indexed sequence vars --- pyomo/contrib/cp/tests/test_sequence_var.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pyomo/contrib/cp/tests/test_sequence_var.py b/pyomo/contrib/cp/tests/test_sequence_var.py index 8167c9f5b3b..37190ebca89 100644 --- a/pyomo/contrib/cp/tests/test_sequence_var.py +++ b/pyomo/contrib/cp/tests/test_sequence_var.py @@ -141,3 +141,17 @@ def test_pprint(self): a : [i[a,1], i[a,2]] b : [i[b,1], i[b,2]]""".strip(), ) + + def test_multidimensional_index(self): + m = self.make_model() + @m.SequenceVar(m.alphabetic, m.numeric) + def s(m, i, j): + return [m.i[i, j],] + + self.assertIsInstance(m.s, IndexedSequenceVar) + self.assertEqual(len(m.s), 4) + for i in m.alphabetic: + for j in m.numeric: + self.assertTrue((i, j) in m.s) + self.assertEqual(len(m.s[i, j]), 1) + self.assertIs(m.s[i, j].interval_vars[0], m.i[i, j]) From 6675566fe7f4c7bf19832a5a72c09e7235cb6c8e Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Wed, 13 Dec 2023 13:19:16 -0700 Subject: [PATCH 0580/1797] 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 0581/1797] 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 0582/1797] 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 0583/1797] 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 0584/1797] 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 0585/1797] 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 0586/1797] 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 0587/1797] 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 0588/1797] 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 0589/1797] 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 0590/1797] 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 0591/1797] 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 0592/1797] 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 0593/1797] 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 0594/1797] 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 0595/1797] 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 0596/1797] 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 0597/1797] 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 0598/1797] 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 0599/1797] 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 0600/1797] 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 0601/1797] 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 0602/1797] 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 0603/1797] 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 0604/1797] 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 0605/1797] 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 0606/1797] 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 0607/1797] 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 0608/1797] 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 0609/1797] 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 0610/1797] 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 0611/1797] 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 0612/1797] 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 0613/1797] 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 0614/1797] 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 0615/1797] 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 0616/1797] 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 0617/1797] 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 0618/1797] 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 0619/1797] 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 0620/1797] 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 0621/1797] 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 0622/1797] 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 0623/1797] 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 d24286a3c2068772e6f900e5e843e70e2546a2a6 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 21 Dec 2023 14:10:53 -0700 Subject: [PATCH 0624/1797] Fixing a bug for multi-dimensionally indexed SequenceVars --- pyomo/contrib/cp/sequence_var.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/cp/sequence_var.py b/pyomo/contrib/cp/sequence_var.py index a77f4c2c415..587106c8107 100644 --- a/pyomo/contrib/cp/sequence_var.py +++ b/pyomo/contrib/cp/sequence_var.py @@ -91,7 +91,7 @@ def _getitem_when_not_present(self, index): obj._index = index if self._init_rule is not None: - obj.set_value(self._init_rule(parent, index)) + obj.set_value(self._init_rule(parent, *index)) if self._init_expr is not None: obj.set_value(self._init_expr) From d5fcd687f16ca09e0e89f3eddad4a970abbaaaaf Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 21 Dec 2023 14:11:04 -0700 Subject: [PATCH 0625/1797] Fixing a typo in a test --- pyomo/contrib/cp/tests/test_sequence_var.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/cp/tests/test_sequence_var.py b/pyomo/contrib/cp/tests/test_sequence_var.py index 37190ebca89..e3d69153355 100644 --- a/pyomo/contrib/cp/tests/test_sequence_var.py +++ b/pyomo/contrib/cp/tests/test_sequence_var.py @@ -153,5 +153,5 @@ def s(m, i, j): for i in m.alphabetic: for j in m.numeric: self.assertTrue((i, j) in m.s) - self.assertEqual(len(m.s[i, j]), 1) + self.assertEqual(len(m.s[i, j].interval_vars), 1) self.assertIs(m.s[i, j].interval_vars[0], m.i[i, j]) From 0547fbc7081a98f0c4efb94853b848dbae7a1468 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 21 Dec 2023 14:11:33 -0700 Subject: [PATCH 0626/1797] NFC: black --- pyomo/contrib/cp/tests/test_sequence_var.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/cp/tests/test_sequence_var.py b/pyomo/contrib/cp/tests/test_sequence_var.py index e3d69153355..404e21ca39c 100644 --- a/pyomo/contrib/cp/tests/test_sequence_var.py +++ b/pyomo/contrib/cp/tests/test_sequence_var.py @@ -144,9 +144,10 @@ def test_pprint(self): def test_multidimensional_index(self): m = self.make_model() + @m.SequenceVar(m.alphabetic, m.numeric) def s(m, i, j): - return [m.i[i, j],] + return [m.i[i, j]] self.assertIsInstance(m.s, IndexedSequenceVar) self.assertEqual(len(m.s), 4) From 17f4837e1cddd77e4dee31cc5201c810d0e5dec4 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 21 Dec 2023 15:27:33 -0700 Subject: [PATCH 0627/1797] Remembering that initializers exist --- pyomo/contrib/cp/sequence_var.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/cp/sequence_var.py b/pyomo/contrib/cp/sequence_var.py index 587106c8107..b242b362f9d 100644 --- a/pyomo/contrib/cp/sequence_var.py +++ b/pyomo/contrib/cp/sequence_var.py @@ -18,6 +18,7 @@ from pyomo.core.base.component import ActiveComponentData from pyomo.core.base.global_set import UnindexedComponent_index from pyomo.core.base.indexed_component import ActiveIndexedComponent +from pyomo.core.base.initializer import Initializer import sys from weakref import ref as weakref_ref @@ -72,7 +73,7 @@ def __new__(cls, *args, **kwds): return IndexedSequenceVar.__new__(IndexedSequenceVar) def __init__(self, *args, **kwargs): - self._init_rule = kwargs.pop('rule', None) + self._init_rule = Initializer(kwargs.pop('rule', None)) self._init_expr = kwargs.pop('expr', None) kwargs.setdefault('ctype', SequenceVar) super(SequenceVar, self).__init__(*args, **kwargs) @@ -91,7 +92,7 @@ def _getitem_when_not_present(self, index): obj._index = index if self._init_rule is not None: - obj.set_value(self._init_rule(parent, *index)) + obj.set_value(self._init_rule(parent, index)) if self._init_expr is not None: obj.set_value(self._init_expr) From b68cfe2c9061a6d305726612bf4037cc9b040ea8 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 21 Dec 2023 15:49:00 -0700 Subject: [PATCH 0628/1797] 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 687f754c1073e943b7d7280ad653a2d81328f8f5 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 21 Dec 2023 16:03:49 -0700 Subject: [PATCH 0629/1797] Fixing some bugs with SequenceVars in the writer and solution parsing --- pyomo/contrib/cp/repn/docplex_writer.py | 8 +++++- pyomo/contrib/cp/tests/test_docplex_writer.py | 27 +++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index 002039b46dd..b1b8708c757 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -31,6 +31,7 @@ IndexedIntervalVar, ) from pyomo.contrib.cp.sequence_var import ( + SequenceVar, ScalarSequenceVar, IndexedSequenceVar, _SequenceVarData, @@ -1157,7 +1158,8 @@ def write(self, model, **options): RangeSet, Port, }, - targets={Objective, Constraint, LogicalConstraint, IntervalVar}, + targets={Objective, Constraint, LogicalConstraint, IntervalVar, + SequenceVar}, ) if unknown: raise ValueError( @@ -1386,6 +1388,10 @@ def solve(self, model, **kwds): ) else: sol = sol.get_value() + if py_var.ctype is SequenceVar: + # They don't actually have values--the IntervalVars will get + # set. + continue if py_var.ctype is IntervalVar: if len(sol) == 0: # The interval_var is absent diff --git a/pyomo/contrib/cp/tests/test_docplex_writer.py b/pyomo/contrib/cp/tests/test_docplex_writer.py index b563052ef3a..a3326b19cf4 100644 --- a/pyomo/contrib/cp/tests/test_docplex_writer.py +++ b/pyomo/contrib/cp/tests/test_docplex_writer.py @@ -12,7 +12,10 @@ import pyomo.common.unittest as unittest from pyomo.common.fileutils import Executable -from pyomo.contrib.cp import IntervalVar, Pulse, Step, AlwaysIn +from pyomo.contrib.cp import ( + IntervalVar, SequenceVar, Pulse, Step, AlwaysIn, + first_in_sequence, predecessor_to, no_overlap +) from pyomo.contrib.cp.repn.docplex_writer import LogicalToDoCplex from pyomo.environ import ( all_different, @@ -360,7 +363,6 @@ def test_matching_problem(self): 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) @@ -392,3 +394,24 @@ def test_matching_problem(self): results.solver.termination_condition, TerminationCondition.optimal ) self.assertEqual(value(m.obj), perfect) + + def test_scheduling_with_sequence_vars(self): + m = ConcreteModel() + m.Steps = Set(initialize=[1, 2, 3]) + def length_rule(m, j): + return 2*j + m.i = IntervalVar(m.Steps, start=(0, 12), end=(0, 12), length=length_rule) + m.seq = SequenceVar(expr=[m.i[j] for j in m.Steps]) + m.first = LogicalConstraint(expr=first_in_sequence(m.i[1], m.seq)) + m.seq_order1 = LogicalConstraint(expr=predecessor_to(m.i[1], m.i[2], m.seq)) + m.seq_order2 = LogicalConstraint(expr=predecessor_to(m.i[2], m.i[3], m.seq)) + m.no_ovlerpa = LogicalConstraint(expr=no_overlap(m.seq)) + + results = SolverFactory('cp_optimizer').solve(m) + self.assertEqual( + results.solver.termination_condition, TerminationCondition.feasible + ) + self.assertEqual(value(m.i[1].start_time), 0) + self.assertEqual(value(m.i[2].start_time), 2) + self.assertEqual(value(m.i[3].start_time), 6) + From ff23a4d7db83adbf78029095522a4bbb0c3f0a07 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 21 Dec 2023 16:04:30 -0700 Subject: [PATCH 0630/1797] NFC: black --- pyomo/contrib/cp/repn/docplex_writer.py | 9 +++++++-- pyomo/contrib/cp/tests/test_docplex_writer.py | 15 +++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index b1b8708c757..27e2f7ef8b9 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -1158,8 +1158,13 @@ def write(self, model, **options): RangeSet, Port, }, - targets={Objective, Constraint, LogicalConstraint, IntervalVar, - SequenceVar}, + targets={ + Objective, + Constraint, + LogicalConstraint, + IntervalVar, + SequenceVar, + }, ) if unknown: raise ValueError( diff --git a/pyomo/contrib/cp/tests/test_docplex_writer.py b/pyomo/contrib/cp/tests/test_docplex_writer.py index a3326b19cf4..20511a8aa3d 100644 --- a/pyomo/contrib/cp/tests/test_docplex_writer.py +++ b/pyomo/contrib/cp/tests/test_docplex_writer.py @@ -13,8 +13,14 @@ from pyomo.common.fileutils import Executable from pyomo.contrib.cp import ( - IntervalVar, SequenceVar, Pulse, Step, AlwaysIn, - first_in_sequence, predecessor_to, no_overlap + IntervalVar, + SequenceVar, + Pulse, + Step, + AlwaysIn, + first_in_sequence, + predecessor_to, + no_overlap, ) from pyomo.contrib.cp.repn.docplex_writer import LogicalToDoCplex from pyomo.environ import ( @@ -398,8 +404,10 @@ def test_matching_problem(self): def test_scheduling_with_sequence_vars(self): m = ConcreteModel() m.Steps = Set(initialize=[1, 2, 3]) + def length_rule(m, j): - return 2*j + return 2 * j + m.i = IntervalVar(m.Steps, start=(0, 12), end=(0, 12), length=length_rule) m.seq = SequenceVar(expr=[m.i[j] for j in m.Steps]) m.first = LogicalConstraint(expr=first_in_sequence(m.i[1], m.seq)) @@ -414,4 +422,3 @@ def length_rule(m, j): self.assertEqual(value(m.i[1].start_time), 0) self.assertEqual(value(m.i[2].start_time), 2) self.assertEqual(value(m.i[3].start_time), 6) - From 14b1c5defa98b55f34425aacff48bf82f82b03a8 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 22 Dec 2023 11:32:44 -0700 Subject: [PATCH 0631/1797] Fixing a bug with printing precedence expressions with Param-valued delays. --- .../cp/scheduling_expr/precedence_expressions.py | 10 +++++----- .../cp/tests/test_precedence_constraints.py | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/pyomo/contrib/cp/scheduling_expr/precedence_expressions.py b/pyomo/contrib/cp/scheduling_expr/precedence_expressions.py index 5340583a216..1b7693605c9 100644 --- a/pyomo/contrib/cp/scheduling_expr/precedence_expressions.py +++ b/pyomo/contrib/cp/scheduling_expr/precedence_expressions.py @@ -21,13 +21,13 @@ def delay(self): return self._args_[2] def _to_string_impl(self, values, relation): - delay = int(values[2]) - if delay == 0: + delay = values[2] + if delay == '0': first = values[0] - elif delay > 0: - first = "%s + %s" % (values[0], delay) + elif delay[0] in '-+': + first = "%s %s %s" % (values[0], delay[0], delay[1:]) else: - first = "%s - %s" % (values[0], abs(delay)) + first = "%s + %s" % (values[0], delay) return "%s %s %s" % (first, relation, values[1]) diff --git a/pyomo/contrib/cp/tests/test_precedence_constraints.py b/pyomo/contrib/cp/tests/test_precedence_constraints.py index 461dabf564c..471b5bca512 100644 --- a/pyomo/contrib/cp/tests/test_precedence_constraints.py +++ b/pyomo/contrib/cp/tests/test_precedence_constraints.py @@ -15,7 +15,7 @@ BeforeExpression, AtExpression, ) -from pyomo.environ import ConcreteModel, LogicalConstraint +from pyomo.environ import ConcreteModel, LogicalConstraint, Param class TestPrecedenceRelationships(unittest.TestCase): @@ -173,3 +173,17 @@ def test_end_after_end(self): self.assertEqual(m.c.expr.delay, 0) self.assertEqual(str(m.c.expr), "b.end_time <= a.end_time") + + def test_end_before_start_param_delay(self): + m = self.get_model() + m.PrepTime = Param(initialize=5) + m.c = LogicalConstraint(expr=m.a.end_time.before(m.b.start_time, + delay=m.PrepTime)) + self.assertIsInstance(m.c.expr, BeforeExpression) + self.assertEqual(len(m.c.expr.args), 3) + self.assertIs(m.c.expr.args[0], m.a.end_time) + self.assertIs(m.c.expr.args[1], m.b.start_time) + self.assertIs(m.c.expr.delay, m.PrepTime) + + self.assertEqual(str(m.c.expr), "a.end_time + PrepTime <= b.start_time") + From 171f2db4fd703b888dd447abbe60a25d7fba0774 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 22 Dec 2023 13:08:11 -0700 Subject: [PATCH 0632/1797] Fixing a bug with single-step-function cumulative functions in alwaysin --- pyomo/contrib/cp/repn/docplex_writer.py | 14 ++++++----- pyomo/contrib/cp/tests/test_docplex_walker.py | 23 +++++++++++++++++++ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index 27e2f7ef8b9..2bfc96faa7d 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -621,22 +621,22 @@ def _before_interval_var_presence(visitor, child): def _handle_step_at_node(visitor, node): - return cp.step_at(node._time, node._height) + return False, (_GENERAL, cp.step_at(node._time, node._height)) def _handle_step_at_start_node(visitor, node): cpx_var = _get_docplex_interval_var(visitor, node._time) - return cp.step_at_start(cpx_var, node._height) + return False, (_GENERAL, cp.step_at_start(cpx_var, node._height)) def _handle_step_at_end_node(visitor, node): cpx_var = _get_docplex_interval_var(visitor, node._time) - return cp.step_at_end(cpx_var, node._height) + return False, (_GENERAL, cp.step_at_end(cpx_var, node._height)) def _handle_pulse_node(visitor, node): cpx_var = _get_docplex_interval_var(visitor, node._interval_var) - return cp.pulse(cpx_var, node._height) + return False, (_GENERAL, cp.pulse(cpx_var, node._height)) def _handle_negated_step_function_node(visitor, node): @@ -647,9 +647,9 @@ def _handle_cumulative_function(visitor, node): expr = 0 for arg in node.args: if arg.__class__ is NegatedStepFunction: - expr -= _handle_negated_step_function_node(visitor, arg) + expr -= _handle_negated_step_function_node(visitor, arg)[1][1] else: - expr += _step_function_handles[arg.__class__](visitor, arg) + expr += _step_function_handles[arg.__class__](visitor, arg)[1][1] return False, (_GENERAL, expr) @@ -1223,6 +1223,8 @@ def write(self, model, **options): # Write logical constraints for cons in components[LogicalConstraint]: + print(cons) + print(cons.expr) expr = visitor.walk_expression((cons.expr, cons, 0)) if expr[0] is _ELEMENT_CONSTRAINT: # Make the expression into a docplex-approved boolean-valued diff --git a/pyomo/contrib/cp/tests/test_docplex_walker.py b/pyomo/contrib/cp/tests/test_docplex_walker.py index 560142ff410..dcc86033cc1 100644 --- a/pyomo/contrib/cp/tests/test_docplex_walker.py +++ b/pyomo/contrib/cp/tests/test_docplex_walker.py @@ -1358,6 +1358,29 @@ def test_always_in(self): ) ) + def test_always_in_single_pulse(self): + # This is a bit silly as you can tell whether or not it is feasible + # structurally, but there's not reason it couldn't happen. + m = self.get_model() + f = Pulse((m.i, 3)) + m.c = LogicalConstraint(expr=f.within((0, 3), (0, 10))) + visitor = self.get_visitor() + expr = visitor.walk_expression((m.c.expr, m.c, 0)) + + self.assertIn(id(m.i), visitor.var_map) + + i = visitor.var_map[id(m.i)] + + self.assertTrue( + expr[1].equals( + cp.always_in( + cp.pulse(i, 3), + interval=(0, 10), + min=0, + max=3, + ) + ) + ) @unittest.skipIf(not docplex_available, "docplex is not available") class TestCPExpressionWalker_NamedExpressions(CommonTest): From a75c9c6db7dbf9ccacd51c6e3058e0fce2b50310 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 22 Dec 2023 13:11:11 -0700 Subject: [PATCH 0633/1797] Removing debugging --- pyomo/contrib/cp/repn/docplex_writer.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index 2bfc96faa7d..de71e4e98dd 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -1223,8 +1223,6 @@ def write(self, model, **options): # Write logical constraints for cons in components[LogicalConstraint]: - print(cons) - print(cons.expr) expr = visitor.walk_expression((cons.expr, cons, 0)) if expr[0] is _ELEMENT_CONSTRAINT: # Make the expression into a docplex-approved boolean-valued From b5db4422bf4959e1628ec98817dd30b377ec171d Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 22 Dec 2023 15:19:27 -0700 Subject: [PATCH 0634/1797] Removing a *very* old (Pyomo 4.0) deprecation message that IntervalVars hit for convoluted reasons--but basically because they have no kwd args named 'rule' --- pyomo/core/base/block.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index fd5322ba686..ba23a6af654 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -1112,26 +1112,13 @@ def add_component(self, name, val): # Error, for disabled support implicit rule names # if '_rule' in val.__dict__ and val._rule is None: - _found = False try: _test = val.local_name + '_rule' for i in (1, 2): frame = sys._getframe(i) - _found |= _test in frame.f_locals except: pass - if _found: - # JDS: Do not blindly reformat this message. The - # formatter inserts arbitrarily-long names(), which can - # cause the resulting logged message to be very poorly - # formatted due to long lines. - logger.warning( - """As of Pyomo 4.0, Pyomo components no longer support implicit rules. -You defined a component (%s) that appears -to rely on an implicit rule (%s). -Components must now specify their rules explicitly using 'rule=' keywords.""" - % (val.name, _test) - ) + # # Don't reconstruct if this component has already been constructed. # This allows a user to move a component from one block to From f9e62781d6a5afb4edeb75b0a9191f843eeb0fc8 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 22 Dec 2023 15:22:17 -0700 Subject: [PATCH 0635/1797] NFC: Would you believe that black doesn't approve --- pyomo/contrib/cp/tests/test_docplex_walker.py | 10 ++-------- pyomo/contrib/cp/tests/test_precedence_constraints.py | 6 +++--- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/pyomo/contrib/cp/tests/test_docplex_walker.py b/pyomo/contrib/cp/tests/test_docplex_walker.py index dcc86033cc1..0b2057217c0 100644 --- a/pyomo/contrib/cp/tests/test_docplex_walker.py +++ b/pyomo/contrib/cp/tests/test_docplex_walker.py @@ -1372,16 +1372,10 @@ def test_always_in_single_pulse(self): i = visitor.var_map[id(m.i)] self.assertTrue( - expr[1].equals( - cp.always_in( - cp.pulse(i, 3), - interval=(0, 10), - min=0, - max=3, - ) - ) + expr[1].equals(cp.always_in(cp.pulse(i, 3), interval=(0, 10), min=0, max=3)) ) + @unittest.skipIf(not docplex_available, "docplex is not available") class TestCPExpressionWalker_NamedExpressions(CommonTest): def test_named_expression(self): diff --git a/pyomo/contrib/cp/tests/test_precedence_constraints.py b/pyomo/contrib/cp/tests/test_precedence_constraints.py index 471b5bca512..b4b9b8fee40 100644 --- a/pyomo/contrib/cp/tests/test_precedence_constraints.py +++ b/pyomo/contrib/cp/tests/test_precedence_constraints.py @@ -177,8 +177,9 @@ def test_end_after_end(self): def test_end_before_start_param_delay(self): m = self.get_model() m.PrepTime = Param(initialize=5) - m.c = LogicalConstraint(expr=m.a.end_time.before(m.b.start_time, - delay=m.PrepTime)) + m.c = LogicalConstraint( + expr=m.a.end_time.before(m.b.start_time, delay=m.PrepTime) + ) self.assertIsInstance(m.c.expr, BeforeExpression) self.assertEqual(len(m.c.expr.args), 3) self.assertIs(m.c.expr.args[0], m.a.end_time) @@ -186,4 +187,3 @@ def test_end_before_start_param_delay(self): self.assertIs(m.c.expr.delay, m.PrepTime) self.assertEqual(str(m.c.expr), "a.end_time + PrepTime <= b.start_time") - From 34ec87ccef8ae9bbddfbfd6105a8821ccd728baf Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Dec 2023 11:49:06 -0700 Subject: [PATCH 0636/1797] 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 0637/1797] 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 0638/1797] 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 0639/1797] 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 0640/1797] 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 0641/1797] 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 0642/1797] 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 0643/1797] 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 0644/1797] 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 0645/1797] 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 0646/1797] 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 0647/1797] 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 0648/1797] 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 0649/1797] 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 0650/1797] 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 0651/1797] 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 0652/1797] 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 0653/1797] 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 0654/1797] 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 0655/1797] 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 0656/1797] 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 0657/1797] 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 0658/1797] 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 0659/1797] 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 0660/1797] 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 0661/1797] 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 0662/1797] 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 0663/1797] 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 0664/1797] 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 0665/1797] 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 0666/1797] 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 0667/1797] 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 0668/1797] 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 0669/1797] 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 0670/1797] 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 0671/1797] 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 0672/1797] 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 0673/1797] 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 0674/1797] 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 0675/1797] 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 0676/1797] 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 0677/1797] 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 0678/1797] 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 0679/1797] 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 0680/1797] 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 0681/1797] 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 0682/1797] 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 0683/1797] 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 0684/1797] 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 e889912eea041cc4f07337588151fb219d0d2d16 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 9 Jan 2024 12:19:41 -0700 Subject: [PATCH 0685/1797] Added interface to ensure parmest backwards compatiblity. --- .../parmest/examples_deprecated/__init__.py | 10 + .../reaction_kinetics/__init__.py | 10 + .../simple_reaction_parmest_example.py | 118 ++ .../reactor_design/__init__.py | 10 + .../reactor_design/bootstrap_example.py | 60 + .../reactor_design/datarec_example.py | 100 ++ .../reactor_design/leaveNout_example.py | 98 ++ .../likelihood_ratio_example.py | 64 + .../multisensor_data_example.py | 51 + .../parameter_estimation_example.py | 58 + .../reactor_design/reactor_data.csv | 20 + .../reactor_data_multisensor.csv | 20 + .../reactor_data_timeseries.csv | 20 + .../reactor_design/reactor_design.py | 104 ++ .../reactor_design/timeseries_data_example.py | 55 + .../rooney_biegler/__init__.py | 10 + .../rooney_biegler/bootstrap_example.py | 57 + .../likelihood_ratio_example.py | 62 + .../parameter_estimation_example.py | 60 + .../rooney_biegler/rooney_biegler.py | 60 + .../rooney_biegler_with_constraint.py | 63 + .../examples_deprecated/semibatch/__init__.py | 10 + .../semibatch/bootstrap_theta.csv | 101 ++ .../semibatch/obj_at_theta.csv | 1009 ++++++++++++ .../semibatch/parallel_example.py | 57 + .../semibatch/parameter_estimation_example.py | 42 + .../semibatch/scenario_example.py | 52 + .../semibatch/scenarios.csv | 11 + .../semibatch/semibatch.py | 287 ++++ pyomo/contrib/parmest/parmest.py | 101 +- pyomo/contrib/parmest/parmest_deprecated.py | 1366 +++++++++++++++++ pyomo/contrib/parmest/scenariocreator.py | 28 +- .../parmest/scenariocreator_deprecated.py | 166 ++ 33 files changed, 4328 insertions(+), 12 deletions(-) create mode 100644 pyomo/contrib/parmest/examples_deprecated/__init__.py create mode 100644 pyomo/contrib/parmest/examples_deprecated/reaction_kinetics/__init__.py create mode 100644 pyomo/contrib/parmest/examples_deprecated/reaction_kinetics/simple_reaction_parmest_example.py create mode 100644 pyomo/contrib/parmest/examples_deprecated/reactor_design/__init__.py create mode 100644 pyomo/contrib/parmest/examples_deprecated/reactor_design/bootstrap_example.py create mode 100644 pyomo/contrib/parmest/examples_deprecated/reactor_design/datarec_example.py create mode 100644 pyomo/contrib/parmest/examples_deprecated/reactor_design/leaveNout_example.py create mode 100644 pyomo/contrib/parmest/examples_deprecated/reactor_design/likelihood_ratio_example.py create mode 100644 pyomo/contrib/parmest/examples_deprecated/reactor_design/multisensor_data_example.py create mode 100644 pyomo/contrib/parmest/examples_deprecated/reactor_design/parameter_estimation_example.py create mode 100644 pyomo/contrib/parmest/examples_deprecated/reactor_design/reactor_data.csv create mode 100644 pyomo/contrib/parmest/examples_deprecated/reactor_design/reactor_data_multisensor.csv create mode 100644 pyomo/contrib/parmest/examples_deprecated/reactor_design/reactor_data_timeseries.csv create mode 100644 pyomo/contrib/parmest/examples_deprecated/reactor_design/reactor_design.py create mode 100644 pyomo/contrib/parmest/examples_deprecated/reactor_design/timeseries_data_example.py create mode 100644 pyomo/contrib/parmest/examples_deprecated/rooney_biegler/__init__.py create mode 100644 pyomo/contrib/parmest/examples_deprecated/rooney_biegler/bootstrap_example.py create mode 100644 pyomo/contrib/parmest/examples_deprecated/rooney_biegler/likelihood_ratio_example.py create mode 100644 pyomo/contrib/parmest/examples_deprecated/rooney_biegler/parameter_estimation_example.py create mode 100644 pyomo/contrib/parmest/examples_deprecated/rooney_biegler/rooney_biegler.py create mode 100644 pyomo/contrib/parmest/examples_deprecated/rooney_biegler/rooney_biegler_with_constraint.py create mode 100644 pyomo/contrib/parmest/examples_deprecated/semibatch/__init__.py create mode 100644 pyomo/contrib/parmest/examples_deprecated/semibatch/bootstrap_theta.csv create mode 100644 pyomo/contrib/parmest/examples_deprecated/semibatch/obj_at_theta.csv create mode 100644 pyomo/contrib/parmest/examples_deprecated/semibatch/parallel_example.py create mode 100644 pyomo/contrib/parmest/examples_deprecated/semibatch/parameter_estimation_example.py create mode 100644 pyomo/contrib/parmest/examples_deprecated/semibatch/scenario_example.py create mode 100644 pyomo/contrib/parmest/examples_deprecated/semibatch/scenarios.csv create mode 100644 pyomo/contrib/parmest/examples_deprecated/semibatch/semibatch.py create mode 100644 pyomo/contrib/parmest/parmest_deprecated.py create mode 100644 pyomo/contrib/parmest/scenariocreator_deprecated.py diff --git a/pyomo/contrib/parmest/examples_deprecated/__init__.py b/pyomo/contrib/parmest/examples_deprecated/__init__.py new file mode 100644 index 00000000000..d93cfd77b3c --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/__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/parmest/examples_deprecated/reaction_kinetics/__init__.py b/pyomo/contrib/parmest/examples_deprecated/reaction_kinetics/__init__.py new file mode 100644 index 00000000000..d93cfd77b3c --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/reaction_kinetics/__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/parmest/examples_deprecated/reaction_kinetics/simple_reaction_parmest_example.py b/pyomo/contrib/parmest/examples_deprecated/reaction_kinetics/simple_reaction_parmest_example.py new file mode 100644 index 00000000000..719a930251c --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/reaction_kinetics/simple_reaction_parmest_example.py @@ -0,0 +1,118 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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 Y. Bard, "Nonlinear Parameter Estimation", (pg. 124) + +This example shows: +1. How to define the unknown (to be regressed parameters) with an index +2. How to call parmest to only estimate some of the parameters (and fix the rest) + +Code provided by Paul Akula. +''' + +from pyomo.environ import ( + ConcreteModel, + Param, + Var, + PositiveReals, + Objective, + Constraint, + RangeSet, + Expression, + minimize, + exp, + value, +) +import pyomo.contrib.parmest.parmest as parmest + + +def simple_reaction_model(data): + # Create the concrete model + model = ConcreteModel() + + model.x1 = Param(initialize=float(data['x1'])) + model.x2 = Param(initialize=float(data['x2'])) + + # Rate constants + model.rxn = RangeSet(2) + initial_guess = {1: 750, 2: 1200} + model.k = Var(model.rxn, initialize=initial_guess, within=PositiveReals) + + # reaction product + model.y = Expression(expr=exp(-model.k[1] * model.x1 * exp(-model.k[2] / model.x2))) + + # fix all of the regressed parameters + model.k.fix() + + # =================================================================== + # Stage-specific cost computations + def ComputeFirstStageCost_rule(model): + return 0 + + model.FirstStageCost = Expression(rule=ComputeFirstStageCost_rule) + + def AllMeasurements(m): + return (float(data['y']) - m.y) ** 2 + + model.SecondStageCost = Expression(rule=AllMeasurements) + + def total_cost_rule(m): + return m.FirstStageCost + m.SecondStageCost + + model.Total_Cost_Objective = Objective(rule=total_cost_rule, sense=minimize) + + return model + + +def main(): + # Data from Table 5.2 in Y. Bard, "Nonlinear Parameter Estimation", (pg. 124) + data = [ + {'experiment': 1, 'x1': 0.1, 'x2': 100, 'y': 0.98}, + {'experiment': 2, 'x1': 0.2, 'x2': 100, 'y': 0.983}, + {'experiment': 3, 'x1': 0.3, 'x2': 100, 'y': 0.955}, + {'experiment': 4, 'x1': 0.4, 'x2': 100, 'y': 0.979}, + {'experiment': 5, 'x1': 0.5, 'x2': 100, 'y': 0.993}, + {'experiment': 6, 'x1': 0.05, 'x2': 200, 'y': 0.626}, + {'experiment': 7, 'x1': 0.1, 'x2': 200, 'y': 0.544}, + {'experiment': 8, 'x1': 0.15, 'x2': 200, 'y': 0.455}, + {'experiment': 9, 'x1': 0.2, 'x2': 200, 'y': 0.225}, + {'experiment': 10, 'x1': 0.25, 'x2': 200, 'y': 0.167}, + {'experiment': 11, 'x1': 0.02, 'x2': 300, 'y': 0.566}, + {'experiment': 12, 'x1': 0.04, 'x2': 300, 'y': 0.317}, + {'experiment': 13, 'x1': 0.06, 'x2': 300, 'y': 0.034}, + {'experiment': 14, 'x1': 0.08, 'x2': 300, 'y': 0.016}, + {'experiment': 15, 'x1': 0.1, 'x2': 300, 'y': 0.006}, + ] + + # ======================================================================= + # Parameter estimation without covariance estimate + # Only estimate the parameter k[1]. The parameter k[2] will remain fixed + # at its initial value + theta_names = ['k[1]'] + pest = parmest.Estimator(simple_reaction_model, data, theta_names) + obj, theta = pest.theta_est() + print(obj) + print(theta) + print() + + # ======================================================================= + # Estimate both k1 and k2 and compute the covariance matrix + theta_names = ['k'] + pest = parmest.Estimator(simple_reaction_model, data, theta_names) + n = 15 # total number of data points used in the objective (y in 15 scenarios) + obj, theta, cov = pest.theta_est(calc_cov=True, cov_n=n) + print(obj) + print(theta) + print(cov) + + +if __name__ == "__main__": + main() diff --git a/pyomo/contrib/parmest/examples_deprecated/reactor_design/__init__.py b/pyomo/contrib/parmest/examples_deprecated/reactor_design/__init__.py new file mode 100644 index 00000000000..d93cfd77b3c --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/reactor_design/__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/parmest/examples_deprecated/reactor_design/bootstrap_example.py b/pyomo/contrib/parmest/examples_deprecated/reactor_design/bootstrap_example.py new file mode 100644 index 00000000000..e2d172f34f6 --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/reactor_design/bootstrap_example.py @@ -0,0 +1,60 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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 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 ( + reactor_design_model, +) + + +def main(): + # Vars to estimate + theta_names = ["k1", "k2", "k3"] + + # Data + file_dirname = dirname(abspath(str(__file__))) + 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 + ) + return expr + + # Create an instance of the parmest estimator + pest = parmest.Estimator(reactor_design_model, data, theta_names, SSE) + + # Parameter estimation + obj, theta = pest.theta_est() + + # Parameter estimation with bootstrap resampling + bootstrap_theta = pest.theta_est_bootstrap(50) + + # Plot results + 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", + ) + + +if __name__ == "__main__": + main() diff --git a/pyomo/contrib/parmest/examples_deprecated/reactor_design/datarec_example.py b/pyomo/contrib/parmest/examples_deprecated/reactor_design/datarec_example.py new file mode 100644 index 00000000000..cfd3891c00e --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/reactor_design/datarec_example.py @@ -0,0 +1,100 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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 pandas as pd +import pyomo.contrib.parmest.parmest as parmest +from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( + reactor_design_model, +) + +np.random.seed(1234) + + +def reactor_design_model_for_datarec(data): + # Unfix inlet concentration for data rec + model = reactor_design_model(data) + model.caf.fixed = False + + return model + + +def generate_data(): + ### Generate data based on real sv, caf, ca, cb, cc, and cd + sv_real = 1.05 + caf_real = 10000 + ca_real = 3458.4 + cb_real = 1060.8 + cc_real = 1683.9 + cd_real = 1898.5 + + data = pd.DataFrame() + ndata = 200 + # Normal distribution, mean = 3400, std = 500 + data["ca"] = 500 * np.random.randn(ndata) + 3400 + # Random distribution between 500 and 1500 + data["cb"] = np.random.rand(ndata) * 1000 + 500 + # Lognormal distribution + 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["sv"] = sv_real + data["caf"] = caf_real + + return data + + +def main(): + # Generate data + data = generate_data() + data_std = data.std() + + # 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 + ) + return expr + + ### Data reconciliation + theta_names = [] # no variables to estimate, use initialized values + + 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"]) + print(obj) + print(theta) + + parmest.graphics.grouped_boxplot( + 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"] + + 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} + print(theta_real) + + +if __name__ == "__main__": + main() diff --git a/pyomo/contrib/parmest/examples_deprecated/reactor_design/leaveNout_example.py b/pyomo/contrib/parmest/examples_deprecated/reactor_design/leaveNout_example.py new file mode 100644 index 00000000000..6952a7fc733 --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/reactor_design/leaveNout_example.py @@ -0,0 +1,98 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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 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 ( + reactor_design_model, +) + + +def main(): + # Vars to estimate + theta_names = ["k1", "k2", "k3"] + + # Data + file_dirname = dirname(abspath(str(__file__))) + file_name = abspath(join(file_dirname, "reactor_data.csv")) + data = pd.read_csv(file_name) + + # Create more data for the example + N = 50 + df_std = data.std().to_frame().transpose() + df_rand = pd.DataFrame(np.random.normal(size=N)) + df_sample = data.sample(N, replace=True).reset_index(drop=True) + data = df_sample + df_rand.dot(df_std) / 10 + + # 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 + ) + return expr + + # Create an instance of the parmest estimator + pest = parmest.Estimator(reactor_design_model, data, theta_names, SSE) + + # Parameter estimation + obj, theta = pest.theta_est() + print(obj) + print(theta) + + ### Parameter estimation with 'leave-N-out' + # Example use case: For each combination of data where one data point is left + # out, estimate theta + lNo_theta = pest.theta_est_leaveNout(1) + print(lNo_theta.head()) + + parmest.graphics.pairwise_plot(lNo_theta, theta) + + ### Leave one out/boostrap analysis + # Example use case: leave 25 data points out, run 20 bootstrap samples with the + # remaining points, determine if the theta estimate using the points left out + # is inside or outside an alpha region based on the bootstrap samples, repeat + # 5 times. Results are stored as a list of tuples, see API docs for information. + lNo = 25 + lNo_samples = 5 + bootstrap_samples = 20 + dist = "MVN" + alphas = [0.7, 0.8, 0.9] + + results = pest.leaveNout_bootstrap_test( + lNo, lNo_samples, bootstrap_samples, dist, alphas, seed=524 + ) + + # Plot results for a single value of alpha + alpha = 0.8 + for i in range(lNo_samples): + theta_est_N = results[i][1] + bootstrap_results = results[i][2] + parmest.graphics.pairwise_plot( + bootstrap_results, + theta_est_N, + alpha, + ["MVN"], + title="Alpha: " + str(alpha) + ", " + str(theta_est_N.loc[0, alpha]), + ) + + # Extract the percent of points that are within the alpha region + r = [results[i][1].loc[0, alpha] for i in range(lNo_samples)] + percent_true = sum(r) / len(r) + print(percent_true) + + +if __name__ == "__main__": + main() diff --git a/pyomo/contrib/parmest/examples_deprecated/reactor_design/likelihood_ratio_example.py b/pyomo/contrib/parmest/examples_deprecated/reactor_design/likelihood_ratio_example.py new file mode 100644 index 00000000000..a0fe6f22305 --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/reactor_design/likelihood_ratio_example.py @@ -0,0 +1,64 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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 pandas as pd +from itertools import product +from os.path import join, abspath, dirname +import pyomo.contrib.parmest.parmest as parmest +from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( + reactor_design_model, +) + + +def main(): + # Vars to estimate + theta_names = ["k1", "k2", "k3"] + + # Data + file_dirname = dirname(abspath(str(__file__))) + 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 + ) + return expr + + # Create an instance of the parmest estimator + pest = parmest.Estimator(reactor_design_model, data, theta_names, SSE) + + # Parameter estimation + obj, theta = pest.theta_est() + + # Find the objective value at each theta estimate + 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"]) + obj_at_theta = pest.objective_at_theta(theta_vals) + + # Run the likelihood ratio test + LR = pest.likelihood_ratio_test(obj_at_theta, obj, [0.8, 0.85, 0.9, 0.95]) + + # Plot results + parmest.graphics.pairwise_plot( + LR, theta, 0.9, title="LR results within 90% confidence region" + ) + + +if __name__ == "__main__": + main() diff --git a/pyomo/contrib/parmest/examples_deprecated/reactor_design/multisensor_data_example.py b/pyomo/contrib/parmest/examples_deprecated/reactor_design/multisensor_data_example.py new file mode 100644 index 00000000000..a92ac626fae --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/reactor_design/multisensor_data_example.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. +# ___________________________________________________________________________ + +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 ( + reactor_design_model, +) + + +def main(): + # Parameter estimation using multisensor data + + # Vars to estimate + 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")) + 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 + ) + return expr + + pest = parmest.Estimator(reactor_design_model, data, theta_names, SSE_multisensor) + obj, theta = pest.theta_est() + print(obj) + print(theta) + + +if __name__ == "__main__": + main() diff --git a/pyomo/contrib/parmest/examples_deprecated/reactor_design/parameter_estimation_example.py b/pyomo/contrib/parmest/examples_deprecated/reactor_design/parameter_estimation_example.py new file mode 100644 index 00000000000..581d3904c04 --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/reactor_design/parameter_estimation_example.py @@ -0,0 +1,58 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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 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 ( + reactor_design_model, +) + + +def main(): + # Vars to estimate + theta_names = ["k1", "k2", "k3"] + + # Data + file_dirname = dirname(abspath(str(__file__))) + 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 + ) + return expr + + # Create an instance of the parmest estimator + pest = parmest.Estimator(reactor_design_model, data, theta_names, SSE) + + # Parameter estimation + obj, theta = pest.theta_est() + + # Assert statements compare parameter estimation (theta) to an expected value + 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 + assert relative_error < 0.05 + relative_error = abs(theta["k2"] - k2_expected) / k2_expected + assert relative_error < 0.05 + relative_error = abs(theta["k3"] - k3_expected) / k3_expected + assert relative_error < 0.05 + + +if __name__ == "__main__": + main() diff --git a/pyomo/contrib/parmest/examples_deprecated/reactor_design/reactor_data.csv b/pyomo/contrib/parmest/examples_deprecated/reactor_design/reactor_data.csv new file mode 100644 index 00000000000..c0695c049c4 --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/reactor_design/reactor_data.csv @@ -0,0 +1,20 @@ +sv,caf,ca,cb,cc,cd +1.06,10010,3407.4,945.4,1717.1,1931.9 +1.11,10010,3631.6,1247.2,1694.1,1960.6 +1.16,10010,3645.3,971.4,1552.3,1898.8 +1.21,10002,3536.2,1225.9,1351.1,1757.0 +1.26,10002,3755.6,1263.8,1562.3,1952.2 +1.30,10007,3598.3,1153.4,1413.4,1903.3 +1.35,10007,3939.0,971.4,1416.9,1794.9 +1.41,10009,4227.9,986.3,1188.7,1821.5 +1.45,10001,4163.1,972.5,1085.6,1908.7 +1.50,10002,3896.3,977.3,1132.9,2080.5 +1.56,10004,3801.6,1040.6,1157.7,1780.0 +1.60,10008,4128.4,1198.6,1150.0,1581.9 +1.66,10002,4385.4,1158.7,970.0,1629.8 +1.70,10007,3960.8,1194.9,1091.2,1835.5 +1.76,10007,4180.8,1244.2,1034.8,1739.5 +1.80,10001,4212.3,1240.7,1010.3,1739.6 +1.85,10004,4200.2,1164.0,931.5,1783.7 +1.90,10009,4748.6,1037.9,1065.9,1685.6 +1.96,10009,4941.3,1038.5,996.0,1855.7 diff --git a/pyomo/contrib/parmest/examples_deprecated/reactor_design/reactor_data_multisensor.csv b/pyomo/contrib/parmest/examples_deprecated/reactor_design/reactor_data_multisensor.csv new file mode 100644 index 00000000000..9df745a8422 --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/reactor_design/reactor_data_multisensor.csv @@ -0,0 +1,20 @@ +sv,caf,ca1,ca2,ca3,cb,cc1,cc2,cd +1.06,10010,3407.4,3363.1,3759.1,945.4,1717.1,1695.1,1931.9 +1.11,10010,3631.6,3345.2,3906.0,1247.2,1694.1,1536.7,1960.6 +1.16,10010,3645.3,3784.9,3301.3,971.4,1552.3,1496.2,1898.8 +1.21,10002,3536.2,3718.3,3678.5,1225.9,1351.1,1549.7,1757.0 +1.26,10002,3755.6,3731.8,3854.7,1263.8,1562.3,1410.1,1952.2 +1.30,10007,3598.3,3751.6,3722.5,1153.4,1413.4,1291.6,1903.3 +1.35,10007,3939.0,3969.5,3827.2,971.4,1416.9,1276.8,1794.9 +1.41,10009,4227.9,3721.3,4046.7,986.3,1188.7,1221.0,1821.5 +1.45,10001,4163.1,4142.7,4512.1,972.5,1085.6,1212.1,1908.7 +1.50,10002,3896.3,3953.7,4028.0,977.3,1132.9,1167.7,2080.5 +1.56,10004,3801.6,4263.3,4015.3,1040.6,1157.7,1236.5,1780.0 +1.60,10008,4128.4,4061.1,3914.8,1198.6,1150.0,1032.2,1581.9 +1.66,10002,4385.4,4344.7,4006.8,1158.7,970.0,1155.1,1629.8 +1.70,10007,3960.8,4259.1,4274.7,1194.9,1091.2,958.6,1835.5 +1.76,10007,4180.8,4071.1,4598.7,1244.2,1034.8,1086.8,1739.5 +1.80,10001,4212.3,4541.8,4440.0,1240.7,1010.3,920.8,1739.6 +1.85,10004,4200.2,4444.9,4667.2,1164.0,931.5,850.7,1783.7 +1.90,10009,4748.6,4813.4,4753.2,1037.9,1065.9,898.5,1685.6 +1.96,10009,4941.3,4511.8,4405.4,1038.5,996.0,921.9,1855.7 diff --git a/pyomo/contrib/parmest/examples_deprecated/reactor_design/reactor_data_timeseries.csv b/pyomo/contrib/parmest/examples_deprecated/reactor_design/reactor_data_timeseries.csv new file mode 100644 index 00000000000..1421cfef6a0 --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/reactor_design/reactor_data_timeseries.csv @@ -0,0 +1,20 @@ +experiment,time,sv,caf,ca,cb,cc,cd +0,18000,1.075,10008,3537.5,1077.2,1591.2,1938.7 +0,18060,1.121,10002,3547.7,1186.2,1766.3,1946.9 +0,18120,1.095,10005,3614.4,1009.9,1702.9,1841.8 +0,18180,1.102,10007,3443.7,863.1,1666.2,1918.7 +0,18240,1.105,10002,3687.1,1052.1,1501.7,1905.0 +0,18300,1.084,10008,3452.7,1000.5,1512.0,2043.4 +1,18360,1.159,10009,3427.8,1133.1,1481.1,1837.1 +1,18420,1.432,10010,4029.8,1058.8,1213.0,1911.1 +1,18480,1.413,10005,3953.1,960.1,1304.8,1754.3 +1,18540,1.475,10008,4034.8,1121.2,1351.0,1992.0 +1,18600,1.433,10002,4029.8,1100.6,1199.5,1713.9 +1,18660,1.488,10006,3972.8,1148.0,1380.7,1992.1 +1,18720,1.456,10003,4031.2,1145.2,1133.1,1812.6 +2,18780,1.821,10008,4499.1,980.8,924.7,1840.9 +2,18840,1.856,10005,4370.9,1000.7,833.4,1848.4 +2,18900,1.846,10002,4438.6,1038.6,1042.8,1703.3 +2,18960,1.852,10002,4468.4,1151.8,1119.1,1564.8 +2,19020,1.865,10009,4341.6,1060.5,844.2,1974.8 +2,19080,1.872,10002,4427.0,964.6,840.2,1928.5 diff --git a/pyomo/contrib/parmest/examples_deprecated/reactor_design/reactor_design.py b/pyomo/contrib/parmest/examples_deprecated/reactor_design/reactor_design.py new file mode 100644 index 00000000000..16f65e236eb --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/reactor_design/reactor_design.py @@ -0,0 +1,104 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain +# rights in this software. +# This software is distributed under the 3-clause BSD License. +# ___________________________________________________________________________ +""" +Continuously stirred tank reactor model, based on +pyomo/examples/doc/pyomobook/nonlinear-ch/react_design/ReactorDesign.py +""" +import pandas as pd +from pyomo.environ import ( + ConcreteModel, + Param, + Var, + PositiveReals, + Objective, + Constraint, + maximize, + SolverFactory, +) + + +def reactor_design_model(data): + # Create the concrete model + model = ConcreteModel() + + # Rate constants + model.k1 = Param(initialize=5.0 / 6.0, within=PositiveReals, mutable=True) # min^-1 + model.k2 = Param(initialize=5.0 / 3.0, within=PositiveReals, mutable=True) # min^-1 + model.k3 = Param( + initialize=1.0 / 6000.0, within=PositiveReals, mutable=True + ) # m^3/(gmol min) + + # 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) + 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) or isinstance(data, pd.Series): + 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) + 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) + model.cc = Var(initialize=2000.0, within=PositiveReals) + model.cd = Var(initialize=1000.0, within=PositiveReals) + + # Objective + model.obj = Objective(expr=model.cb, sense=maximize) + + # Constraints + model.ca_bal = Constraint( + expr=( + 0 + == model.sv * model.caf + - model.sv * model.ca + - model.k1 * model.ca + - 2.0 * model.k3 * model.ca**2.0 + ) + ) + + model.cb_bal = Constraint( + expr=(0 == -model.sv * model.cb + model.k1 * model.ca - model.k2 * model.cb) + ) + + model.cc_bal = Constraint(expr=(0 == -model.sv * model.cc + model.k2 * model.cb)) + + model.cd_bal = Constraint( + expr=(0 == -model.sv * model.cd + model.k3 * model.ca**2.0) + ) + + return model + + +def main(): + # For a range of sv values, return ca, cb, cc, and cd + results = [] + 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") + 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"]) + print(results) + + +if __name__ == "__main__": + main() diff --git a/pyomo/contrib/parmest/examples_deprecated/reactor_design/timeseries_data_example.py b/pyomo/contrib/parmest/examples_deprecated/reactor_design/timeseries_data_example.py new file mode 100644 index 00000000000..da2ab1874c9 --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/reactor_design/timeseries_data_example.py @@ -0,0 +1,55 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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 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 ( + reactor_design_model, +) + + +def main(): + # Parameter estimation using timeseries data + + # Vars to estimate + 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_timeseries.csv')) + data = pd.read_csv(file_name) + + # Group time series data into experiments, return the mean value for sv and caf + # Returns a list of dictionaries + data_ts = parmest.group_data(data, 'experiment', ['sv', 'caf']) + + def SSE_timeseries(model, data): + expr = 0 + for val in data['ca']: + expr = expr + ((float(val) - model.ca) ** 2) * (1 / len(data['ca'])) + for val in data['cb']: + expr = expr + ((float(val) - model.cb) ** 2) * (1 / len(data['cb'])) + for val in data['cc']: + expr = expr + ((float(val) - model.cc) ** 2) * (1 / len(data['cc'])) + for val in data['cd']: + expr = expr + ((float(val) - model.cd) ** 2) * (1 / len(data['cd'])) + return expr + + pest = parmest.Estimator(reactor_design_model, data_ts, theta_names, SSE_timeseries) + obj, theta = pest.theta_est() + print(obj) + print(theta) + + +if __name__ == "__main__": + main() diff --git a/pyomo/contrib/parmest/examples_deprecated/rooney_biegler/__init__.py b/pyomo/contrib/parmest/examples_deprecated/rooney_biegler/__init__.py new file mode 100644 index 00000000000..d93cfd77b3c --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/rooney_biegler/__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/parmest/examples_deprecated/rooney_biegler/bootstrap_example.py b/pyomo/contrib/parmest/examples_deprecated/rooney_biegler/bootstrap_example.py new file mode 100644 index 00000000000..f686bbd933d --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/rooney_biegler/bootstrap_example.py @@ -0,0 +1,57 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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 pandas as pd +import pyomo.contrib.parmest.parmest as parmest +from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import ( + rooney_biegler_model, +) + + +def main(): + # Vars to estimate + theta_names = ['asymptote', 'rate_constant'] + + # Data + 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'], + ) + + # Sum of squared error function + def SSE(model, data): + expr = sum( + (data.y[i] - model.response_function[data.hour[i]]) ** 2 for i in data.index + ) + return expr + + # Create an instance of the parmest estimator + pest = parmest.Estimator(rooney_biegler_model, data, theta_names, SSE) + + # Parameter estimation + obj, theta = pest.theta_est() + + # Parameter estimation with bootstrap resampling + bootstrap_theta = pest.theta_est_bootstrap(50, seed=4581) + + # Plot results + 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', + ) + + +if __name__ == "__main__": + main() diff --git a/pyomo/contrib/parmest/examples_deprecated/rooney_biegler/likelihood_ratio_example.py b/pyomo/contrib/parmest/examples_deprecated/rooney_biegler/likelihood_ratio_example.py new file mode 100644 index 00000000000..5e54a33abda --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/rooney_biegler/likelihood_ratio_example.py @@ -0,0 +1,62 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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 pandas as pd +from itertools import product +import pyomo.contrib.parmest.parmest as parmest +from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import ( + rooney_biegler_model, +) + + +def main(): + # Vars to estimate + theta_names = ['asymptote', 'rate_constant'] + + # Data + 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'], + ) + + # Sum of squared error function + def SSE(model, data): + expr = sum( + (data.y[i] - model.response_function[data.hour[i]]) ** 2 for i in data.index + ) + return expr + + # Create an instance of the parmest estimator + pest = parmest.Estimator(rooney_biegler_model, data, theta_names, SSE) + + # Parameter estimation + obj, theta = pest.theta_est() + + # Find the objective value at each theta estimate + asym = np.arange(10, 30, 2) + rate = np.arange(0, 1.5, 0.1) + theta_vals = pd.DataFrame( + list(product(asym, rate)), columns=['asymptote', 'rate_constant'] + ) + obj_at_theta = pest.objective_at_theta(theta_vals) + + # Run the likelihood ratio test + LR = pest.likelihood_ratio_test(obj_at_theta, obj, [0.8, 0.85, 0.9, 0.95]) + + # Plot results + parmest.graphics.pairwise_plot( + LR, theta, 0.8, title='LR results within 80% confidence region' + ) + + +if __name__ == "__main__": + main() diff --git a/pyomo/contrib/parmest/examples_deprecated/rooney_biegler/parameter_estimation_example.py b/pyomo/contrib/parmest/examples_deprecated/rooney_biegler/parameter_estimation_example.py new file mode 100644 index 00000000000..9af33217fe4 --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/rooney_biegler/parameter_estimation_example.py @@ -0,0 +1,60 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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 pandas as pd +import pyomo.contrib.parmest.parmest as parmest +from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import ( + rooney_biegler_model, +) + + +def main(): + # Vars to estimate + theta_names = ['asymptote', 'rate_constant'] + + # Data + 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'], + ) + + # Sum of squared error function + def SSE(model, data): + expr = sum( + (data.y[i] - model.response_function[data.hour[i]]) ** 2 for i in data.index + ) + return expr + + # Create an instance of the parmest estimator + pest = parmest.Estimator(rooney_biegler_model, data, theta_names, SSE) + + # Parameter estimation and covariance + n = 6 # total number of data points used in the objective (y in 6 scenarios) + obj, theta, cov = pest.theta_est(calc_cov=True, cov_n=n) + + # Plot theta estimates using a multivariate Gaussian distribution + parmest.graphics.pairwise_plot( + (theta, cov, 100), + theta_star=theta, + alpha=0.8, + distributions=['MVN'], + title='Theta estimates within 80% confidence region', + ) + + # Assert statements compare parameter estimation (theta) to an expected value + relative_error = abs(theta['asymptote'] - 19.1426) / 19.1426 + assert relative_error < 0.01 + relative_error = abs(theta['rate_constant'] - 0.5311) / 0.5311 + assert relative_error < 0.01 + + +if __name__ == "__main__": + main() diff --git a/pyomo/contrib/parmest/examples_deprecated/rooney_biegler/rooney_biegler.py b/pyomo/contrib/parmest/examples_deprecated/rooney_biegler/rooney_biegler.py new file mode 100644 index 00000000000..5a0e1238e85 --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/rooney_biegler/rooney_biegler.py @@ -0,0 +1,60 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain +# rights in this software. +# This software is distributed under the 3-clause BSD License. +# ___________________________________________________________________________ + +""" +Rooney Biegler model, based on Rooney, W. C. and Biegler, L. T. (2001). Design for +model parameter uncertainty using nonlinear confidence regions. AIChE Journal, +47(8), 1794-1804. +""" + +import pandas as pd +import pyomo.environ as pyo + + +def rooney_biegler_model(data): + model = pyo.ConcreteModel() + + model.asymptote = pyo.Var(initialize=15) + model.rate_constant = pyo.Var(initialize=0.5) + + def response_rule(m, h): + expr = m.asymptote * (1 - pyo.exp(-m.rate_constant * h)) + return expr + + model.response_function = pyo.Expression(data.hour, rule=response_rule) + + def SSE_rule(m): + return sum( + (data.y[i] - m.response_function[data.hour[i]]) ** 2 for i in data.index + ) + + model.SSE = pyo.Objective(rule=SSE_rule, sense=pyo.minimize) + + return model + + +def main(): + # These were taken from Table A1.4 in Bates and Watts (1988). + 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'], + ) + + model = rooney_biegler_model(data) + solver = pyo.SolverFactory('ipopt') + solver.solve(model) + + print('asymptote = ', model.asymptote()) + print('rate constant = ', model.rate_constant()) + + +if __name__ == '__main__': + main() diff --git a/pyomo/contrib/parmest/examples_deprecated/rooney_biegler/rooney_biegler_with_constraint.py b/pyomo/contrib/parmest/examples_deprecated/rooney_biegler/rooney_biegler_with_constraint.py new file mode 100644 index 00000000000..2582e3fe928 --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/rooney_biegler/rooney_biegler_with_constraint.py @@ -0,0 +1,63 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain +# rights in this software. +# This software is distributed under the 3-clause BSD License. +# ___________________________________________________________________________ + +""" +Rooney Biegler model, based on Rooney, W. C. and Biegler, L. T. (2001). Design for +model parameter uncertainty using nonlinear confidence regions. AIChE Journal, +47(8), 1794-1804. +""" + +import pandas as pd +import pyomo.environ as pyo + + +def rooney_biegler_model_with_constraint(data): + model = pyo.ConcreteModel() + + model.asymptote = pyo.Var(initialize=15) + model.rate_constant = pyo.Var(initialize=0.5) + model.response_function = pyo.Var(data.hour, initialize=0.0) + + # changed from expression to constraint + def response_rule(m, h): + return m.response_function[h] == m.asymptote * ( + 1 - pyo.exp(-m.rate_constant * h) + ) + + model.response_function_constraint = pyo.Constraint(data.hour, rule=response_rule) + + def SSE_rule(m): + return sum( + (data.y[i] - m.response_function[data.hour[i]]) ** 2 for i in data.index + ) + + model.SSE = pyo.Objective(rule=SSE_rule, sense=pyo.minimize) + + return model + + +def main(): + # These were taken from Table A1.4 in Bates and Watts (1988). + 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'], + ) + + model = rooney_biegler_model_with_constraint(data) + solver = pyo.SolverFactory('ipopt') + solver.solve(model) + + print('asymptote = ', model.asymptote()) + print('rate constant = ', model.rate_constant()) + + +if __name__ == '__main__': + main() diff --git a/pyomo/contrib/parmest/examples_deprecated/semibatch/__init__.py b/pyomo/contrib/parmest/examples_deprecated/semibatch/__init__.py new file mode 100644 index 00000000000..d93cfd77b3c --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/semibatch/__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/parmest/examples_deprecated/semibatch/bootstrap_theta.csv b/pyomo/contrib/parmest/examples_deprecated/semibatch/bootstrap_theta.csv new file mode 100644 index 00000000000..29923a782c5 --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/semibatch/bootstrap_theta.csv @@ -0,0 +1,101 @@ +,k1,k2,E1,E2 +0,23.8359813557911,149.99999125263844,31164.260824269295,41489.69422529956 +1,19.251987486659512,105.3374117880675,30505.86059307485,40516.897897740404 +2,19.31940450911214,105.78105886426505,30509.636888745794,40539.53548872927 +3,8.754357429283429,149.99988037658665,28334.500331107014,41482.01554893696 +4,23.016722464092286,80.03743984792878,31091.61503716734,39770.08415278276 +5,6.612337410520649,140.0259411600077,27521.46259880474,41302.159495413296 +6,14.29348509961158,147.0817016641302,29605.749859593245,41443.12009807534 +7,14.152069480386153,149.9914759675382,29676.633227079245,41483.41029455195 +8,19.081046896092914,125.55106106390114,30586.57857977985,41005.60243351924 +9,3.063566173952205,149.9999684548014,25473.079370305273,41483.426370389796 +10,17.79494440791066,108.52425726918327,30316.710830136202,40618.63715404914 +11,97.307579412204,149.99998972597675,35084.37589956093,41485.835559276136 +12,20.793042577945116,91.124365144131,30782.17494940993,40138.215713547994 +13,12.740540794730641,89.86327635412908,29396.65520336387,40086.14665722912 +14,6.930810780299319,149.99999327266906,27667.0240033497,41480.188987754496 +15,20.29404799567638,101.07539817765885,30697.087258737916,40443.316578889426 +16,85.77501788788223,149.99996482096984,34755.77375009206,41499.23448818336 +17,24.13150325243255,77.06876294766496,31222.03914354306,39658.418332258894 +18,16.026645517712712,149.99993094015056,30015.46332620076,41490.69892111652 +19,31.020018442708537,62.11558789585982,31971.311996398897,39089.828285017575 +20,20.815008037484656,87.35968459422139,30788.643843293,40007.78137819648 +21,19.007148519616447,96.44320176694993,30516.36933261116,40284.23312198372 +22,22.232021812057308,89.71692873746096,30956.252845068626,40095.009765519 +23,16.830765834427297,120.65209863104229,30139.92208332896,40912.673450399234 +24,15.274799190396566,129.82767733073857,29780.055282261117,41078.04749417758 +25,22.37343657709118,82.32861355430458,31013.57952062852,39853.06284595207 +26,9.055694749134819,149.99987339406314,28422.482259116612,41504.97564187301 +27,19.909770949417275,86.5634026379812,30705.60369894775,39996.134938503914 +28,20.604557306290886,87.96473948102359,30786.467003867263,40051.28176004557 +29,21.94101237923462,88.18216423767153,30942.372558158557,40051.20357069738 +30,3.200663718121338,149.99997712051055,25472.46099917771,41450.884180452646 +31,20.5812467558026,86.36098672832426,30802.74421085271,40010.76777825347 +32,18.776139793586893,108.99943042186453,30432.474809193136,40641.48011315501 +33,17.14246930769276,112.29370332257908,30164.332101438307,40684.867629869856 +34,20.52146255576043,99.7078140453859,30727.90573864389,40401.20730725967 +35,17.05073306185531,66.00385439687035,30257.075479935145,39247.26647870223 +36,7.1238843213074015,51.05163218895348,27811.250260416655,38521.11199236329 +37,10.54291332571747,76.74902426944477,28763.52244085013,39639.92644514267 +38,16.329028964122656,107.60037882134996,30073.5111433796,40592.825374177235 +39,18.0923131790489,107.75659679748213,30355.62290415686,40593.10521263782 +40,15.477264179087811,149.99995828085014,29948.62617372307,41490.770726165414 +41,23.190670255199933,76.5654091811839,31107.96477489951,39635.650879492074 +42,20.34720227734719,90.07051780196629,30716.131795936217,40096.932765428995 +43,23.60627359054596,80.0847207027996,31130.449736501876,39756.06693747353 +44,22.54968153535252,83.72995448206636,31038.51932262643,39906.60181934743 +45,24.951320839961582,67.97010976959977,31356.00147390564,39307.75709154711 +46,61.216667588824386,149.9999967830529,33730.22100500659,41474.80665231048 +47,9.797300324197744,136.33054557076974,28588.83540859912,41222.22413163186 +48,21.75078861615545,139.82641444329093,30894.847060525986,41290.16131583715 +49,21.76324066920255,99.57885291658233,30860.292260186063,40386.00605205238 +50,20.244262248110417,86.2553098058883,30742.054735645124,39981.83946305757 +51,21.859217291379004,72.89837327878459,30999.703939831277,39514.23768439393 +52,20.902111153308944,88.36862895882298,30782.76240691508,40033.44884393017 +53,59.58504995089654,149.9999677447201,33771.647879014425,41496.69202917452 +54,21.63994234351529,80.9641923004028,30933.578583737795,39809.523930207484 +55,9.804873383156298,149.9995892138235,28729.93818644509,41500.94496844104 +56,9.517359502437172,149.99308840029815,28505.329315103318,41470.65218792529 +57,19.923610217578116,88.23847592895486,30636.024864041487,40020.79650218989 +58,20.366495228182394,85.1991151089578,30752.560133063143,39947.719888972904 +59,12.242715793208157,149.99998097746882,29308.42752633667,41512.25071862387 +60,19.677765799324447,97.30674967097808,30618.37668428642,40323.0499230797 +61,19.03651315222424,109.20775378637025,30455.39615515442,40614.722801684395 +62,21.37660531151217,149.99999616215425,30806.121697474813,41479.3976433347 +63,21.896838392882998,86.86206456282005,30918.823491874144,39986.262281131254 +64,5.030122322262226,149.99991736085678,26792.302062236955,41480.579525893794 +65,17.851755694421776,53.33521102556455,30419.017295420916,38644.47349861614 +66,20.963796542255896,90.72302887846234,30795.751244616677,40114.19163802526 +67,23.082992539267945,77.24345020180209,31107.07485019312,39665.22410226011 +68,18.953050386839383,90.80802949182345,30529.280393040182,40113.73467038244 +69,20.710937910951355,83.16996057131982,30805.892332796295,39876.270184728084 +70,18.18549080794899,65.72657652078952,30416.294615296756,39223.21339606898 +71,12.147892028456324,45.12945045196771,29302.888575028635,38194.144730342545 +72,4.929663537166405,133.89086200105797,26635.8524254091,41163.82082194103 +73,20.512731504598662,106.98199797354127,30660.67479570742,40560.70063653076 +74,21.006700520199008,93.35471748418676,30761.272887418058,40178.10564855804 +75,19.73635577733317,98.75362910260881,30599.64039254174,40346.31274388047 +76,3.6393630101175565,149.99998305638113,25806.925407145678,41446.42489819377 +77,14.430958212981363,149.9999928114441,29710.277666486683,41478.96029884101 +78,21.138173237661093,90.73414659450283,30833.36092609432,40128.61898313504 +79,19.294823672883208,104.69324605284973,30510.371654343133,40510.84889949937 +80,2.607050470695225,69.22680095813037,25000.001468502505,39333.142090801295 +81,16.949842823156228,118.76691429120146,30074.04126731665,40824.66852388976 +82,21.029588811317897,95.27115352081795,30770.753828753943,40243.47156167542 +83,18.862418349044077,111.08370690591005,30421.17882623639,40670.941374189555 +84,24.708015660945147,76.24225941680999,31286.7038829574,39632.545034540664 +85,21.58937721477476,92.6329553952883,30871.989108388123,40181.7478528116 +86,21.091322126816706,96.07721666941696,30765.91144819689,40265.321194575095 +87,19.337815749868728,96.50567420686403,30604.551156564357,40318.12321325275 +88,17.77732130279279,108.5062535737451,30287.456682982094,40602.76307166587 +89,15.259532609396405,134.79914728383426,29793.69015375863,41199.11159557717 +90,21.616910309091583,90.65235108674251,30848.137718134392,40096.0776408459 +91,3.3372937891220475,149.99991062247588,25630.388452101062,41483.30064805118 +92,20.652437906744403,97.86062128528714,30747.864718937744,40330.11871286893 +93,22.134113060054425,73.68464943802763,31013.225174702933,39535.65213713519 +94,20.297310066178802,93.79207093658654,30684.309981457223,40191.747572763874 +95,6.007958386675472,149.99997175883215,27126.707007542,41465.75099589974 +96,16.572749402536758,40.75746000309888,30154.396795028595,37923.85448825053 +97,21.235697111801056,98.97798760165126,30807.097617165928,40373.550932032136 +98,20.10350615639414,96.19608053749371,30632.029399836003,40258.3813340696 +99,18.274272179970747,96.49060573948069,30456.872524151822,40305.258325587834 diff --git a/pyomo/contrib/parmest/examples_deprecated/semibatch/obj_at_theta.csv b/pyomo/contrib/parmest/examples_deprecated/semibatch/obj_at_theta.csv new file mode 100644 index 00000000000..79f03e07dcd --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/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_deprecated/semibatch/parallel_example.py b/pyomo/contrib/parmest/examples_deprecated/semibatch/parallel_example.py new file mode 100644 index 00000000000..ff1287811cf --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/semibatch/parallel_example.py @@ -0,0 +1,57 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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 following script can be used to run semibatch parameter estimation in +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 itertools import product +from os.path import join, abspath, dirname +import pyomo.contrib.parmest.parmest as parmest +from pyomo.contrib.parmest.examples.semibatch.semibatch import generate_model + + +def main(): + # Vars to estimate + theta_names = ['k1', 'k2', 'E1', 'E2'] + + # Data, list of json file names + data = [] + file_dirname = dirname(abspath(str(__file__))) + for exp_num in range(10): + file_name = abspath(join(file_dirname, 'exp' + str(exp_num + 1) + '.out')) + data.append(file_name) + + # Note, the model already includes a 'SecondStageCost' expression + # for sum of squared error that will be used in parameter estimation + + pest = parmest.Estimator(generate_model, data, theta_names) + + ### Parameter estimation with bootstrap resampling + bootstrap_theta = pest.theta_est_bootstrap(100) + bootstrap_theta.to_csv('bootstrap_theta.csv') + + ### Compute objective at theta for likelihood ratio test + k1 = np.arange(4, 24, 3) + k2 = np.arange(40, 160, 40) + E1 = np.arange(29000, 32000, 500) + E2 = np.arange(38000, 42000, 500) + theta_vals = pd.DataFrame(list(product(k1, k2, E1, E2)), columns=theta_names) + + obj_at_theta = pest.objective_at_theta(theta_vals) + obj_at_theta.to_csv('obj_at_theta.csv') + + +if __name__ == "__main__": + main() diff --git a/pyomo/contrib/parmest/examples_deprecated/semibatch/parameter_estimation_example.py b/pyomo/contrib/parmest/examples_deprecated/semibatch/parameter_estimation_example.py new file mode 100644 index 00000000000..fc4c9f5c675 --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/semibatch/parameter_estimation_example.py @@ -0,0 +1,42 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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 +from os.path import join, abspath, dirname +import pyomo.contrib.parmest.parmest as parmest +from pyomo.contrib.parmest.examples.semibatch.semibatch import generate_model + + +def main(): + # Vars to estimate + theta_names = ['k1', 'k2', 'E1', 'E2'] + + # Data, list of dictionaries + data = [] + file_dirname = dirname(abspath(str(__file__))) + for exp_num in range(10): + file_name = abspath(join(file_dirname, 'exp' + str(exp_num + 1) + '.out')) + with open(file_name, 'r') as infile: + d = json.load(infile) + data.append(d) + + # Note, the model already includes a 'SecondStageCost' expression + # for sum of squared error that will be used in parameter estimation + + pest = parmest.Estimator(generate_model, data, theta_names) + + obj, theta = pest.theta_est() + print(obj) + print(theta) + + +if __name__ == '__main__': + main() diff --git a/pyomo/contrib/parmest/examples_deprecated/semibatch/scenario_example.py b/pyomo/contrib/parmest/examples_deprecated/semibatch/scenario_example.py new file mode 100644 index 00000000000..071e53236c4 --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/semibatch/scenario_example.py @@ -0,0 +1,52 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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 +from os.path import join, abspath, dirname +import pyomo.contrib.parmest.parmest as parmest +from pyomo.contrib.parmest.examples.semibatch.semibatch import generate_model +import pyomo.contrib.parmest.scenariocreator as sc + + +def main(): + # Vars to estimate in parmest + theta_names = ['k1', 'k2', 'E1', 'E2'] + + # Data: list of dictionaries + data = [] + file_dirname = dirname(abspath(str(__file__))) + for exp_num in range(10): + fname = join(file_dirname, 'exp' + str(exp_num + 1) + '.out') + with open(fname, 'r') as infile: + d = json.load(infile) + data.append(d) + + pest = parmest.Estimator(generate_model, data, theta_names) + + scenmaker = sc.ScenarioCreator(pest, "ipopt") + + # Make one scenario per experiment and write to a csv file + output_file = "scenarios.csv" + experimentscens = sc.ScenarioSet("Experiments") + scenmaker.ScenariosFromExperiments(experimentscens) + experimentscens.write_csv(output_file) + + # Use the bootstrap to make 3 scenarios and print + bootscens = sc.ScenarioSet("Bootstrap") + scenmaker.ScenariosFromBootstrap(bootscens, 3) + for s in bootscens.ScensIterator(): + print("{}, {}".format(s.name, s.probability)) + for n, v in s.ThetaVals.items(): + print(" {}={}".format(n, v)) + + +if __name__ == "__main__": + main() diff --git a/pyomo/contrib/parmest/examples_deprecated/semibatch/scenarios.csv b/pyomo/contrib/parmest/examples_deprecated/semibatch/scenarios.csv new file mode 100644 index 00000000000..22f9a651bc3 --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/semibatch/scenarios.csv @@ -0,0 +1,11 @@ +Name,Probability,k1,k2,E1,E2 +ExpScen0,0.1,25.800350800448314,14.14421520525348,31505.74905064048,35000.0 +ExpScen1,0.1,25.128373083865036,149.99999951481198,31452.336651974012,41938.781301641866 +ExpScen2,0.1,22.225574065344002,130.92739780265404,30948.669111672247,41260.15420929141 +ExpScen3,0.1,100.0,149.99999970011854,35182.73130744844,41444.52600373733 +ExpScen4,0.1,82.99114366189944,45.95424665995078,34810.857217141674,38300.633349887314 +ExpScen5,0.1,100.0,150.0,35142.20219150486,41495.41105795494 +ExpScen6,0.1,2.8743643265301118,149.99999477176598,25000.0,41431.61195969211 +ExpScen7,0.1,2.754580914035567,14.381786096822475,25000.0,35000.0 +ExpScen8,0.1,2.8743643265301118,149.99999477176598,25000.0,41431.61195969211 +ExpScen9,0.1,2.669780822294865,150.0,25000.0,41514.7476113499 diff --git a/pyomo/contrib/parmest/examples_deprecated/semibatch/semibatch.py b/pyomo/contrib/parmest/examples_deprecated/semibatch/semibatch.py new file mode 100644 index 00000000000..6762531a338 --- /dev/null +++ b/pyomo/contrib/parmest/examples_deprecated/semibatch/semibatch.py @@ -0,0 +1,287 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain +# rights in this software. +# This software is distributed under the 3-clause BSD License. +# ___________________________________________________________________________ +""" +Semibatch model, based on Nicholson et al. (2018). pyomo.dae: A modeling and +automatic discretization framework for optimization with di +erential and +algebraic equations. Mathematical Programming Computation, 10(2), 187-223. +""" +import json +from os.path import join, abspath, dirname +from pyomo.environ import ( + ConcreteModel, + Set, + Param, + Var, + Constraint, + ConstraintList, + Expression, + Objective, + TransformationFactory, + SolverFactory, + exp, + minimize, +) +from pyomo.dae import ContinuousSet, DerivativeVar + + +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"] + ccmeastemp = data["Cc_meas"] + trmeastemp = data["Tr_meas"] + + cameas = {} + cbmeas = {} + ccmeas = {} + trmeas = {} + for i in cameastemp.keys(): + cameas[float(i)] = cameastemp[i] + cbmeas[float(i)] = cbmeastemp[i] + ccmeas[float(i)] = ccmeastemp[i] + trmeas[float(i)] = trmeastemp[i] + + m = ConcreteModel() + + # + # Measurement Data + # + m.measT = Set(initialize=sorted(cameas.keys())) + m.Ca_meas = Param(m.measT, initialize=cameas) + m.Cb_meas = Param(m.measT, initialize=cbmeas) + m.Cc_meas = Param(m.measT, initialize=ccmeas) + m.Tr_meas = Param(m.measT, initialize=trmeas) + + # + # Parameters for semi-batch reactor model + # + m.R = Param(initialize=8.314) # kJ/kmol/K + m.Mwa = Param(initialize=50.0) # kg/kmol + m.rhor = Param(initialize=1000.0) # kg/m^3 + m.cpr = Param(initialize=3.9) # kJ/kg/K + m.Tf = Param(initialize=300) # K + m.deltaH1 = Param(initialize=-40000.0) # kJ/kmol + m.deltaH2 = Param(initialize=-50000.0) # kJ/kmol + m.alphaj = Param(initialize=0.8) # kJ/s/m^2/K + m.alphac = Param(initialize=0.7) # kJ/s/m^2/K + m.Aj = Param(initialize=5.0) # m^2 + m.Ac = Param(initialize=3.0) # m^2 + m.Vj = Param(initialize=0.9) # m^3 + 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.Tr0 = Param(initialize=300.0) # K + m.Vr0 = Param(initialize=1.0) # m^3 + + m.time = ContinuousSet(bounds=(0, 21600), initialize=m.measT) # Time in seconds + + # + # Control Inputs + # + def _initTc(m, t): + if t < 10800: + return data["Tc1"] + else: + return data["Tc2"] + + m.Tc = Param( + m.time, initialize=_initTc, default=_initTc + ) # bounds= (288,432) Cooling coil temp, control input + + def _initFa(m, t): + if t < 10800: + return data["Fa1"] + else: + return data["Fa2"] + + m.Fa = Param( + m.time, initialize=_initFa, default=_initFa + ) # bounds=(0,0.05) Inlet flow rate, control input + + # + # Parameters being estimated + # + m.k1 = Var(initialize=14, bounds=(2, 100)) # 1/s Actual: 15.01 + m.k2 = Var(initialize=90, bounds=(2, 150)) # 1/s Actual: 85.01 + m.E1 = Var(initialize=27000.0, bounds=(25000, 40000)) # kJ/kmol Actual: 30000 + m.E2 = Var(initialize=45000.0, bounds=(35000, 50000)) # kJ/kmol Actual: 40000 + # m.E1.fix(30000) + # m.E2.fix(40000) + + # + # Time dependent variables + # + m.Ca = Var(m.time, initialize=m.Ca0, bounds=(0, 25)) + m.Cb = Var(m.time, initialize=m.Cb0, bounds=(0, 25)) + m.Cc = Var(m.time, initialize=m.Cc0, bounds=(0, 25)) + m.Vr = Var(m.time, initialize=m.Vr0) + m.Tr = Var(m.time, initialize=m.Tr0) + m.Tj = Var( + m.time, initialize=310.0, bounds=(288, None) + ) # Cooling jacket temp, follows coil temp until failure + + # + # Derivatives in the model + # + m.dCa = DerivativeVar(m.Ca) + m.dCb = DerivativeVar(m.Cb) + m.dCc = DerivativeVar(m.Cc) + m.dVr = DerivativeVar(m.Vr) + m.dTr = DerivativeVar(m.Tr) + + # + # Differential Equations in the model + # + + def _dCacon(m, t): + if t == 0: + return Constraint.Skip + return ( + m.dCa[t] + == m.Fa[t] / m.Vr[t] - m.k1 * exp(-m.E1 / (m.R * m.Tr[t])) * m.Ca[t] + ) + + m.dCacon = Constraint(m.time, rule=_dCacon) + + def _dCbcon(m, t): + if t == 0: + return Constraint.Skip + return ( + m.dCb[t] + == m.k1 * exp(-m.E1 / (m.R * m.Tr[t])) * m.Ca[t] + - m.k2 * exp(-m.E2 / (m.R * m.Tr[t])) * m.Cb[t] + ) + + m.dCbcon = Constraint(m.time, rule=_dCbcon) + + def _dCccon(m, t): + if t == 0: + return Constraint.Skip + return m.dCc[t] == m.k2 * exp(-m.E2 / (m.R * m.Tr[t])) * m.Cb[t] + + m.dCccon = Constraint(m.time, rule=_dCccon) + + def _dVrcon(m, t): + if t == 0: + return Constraint.Skip + return m.dVr[t] == m.Fa[t] * m.Mwa / m.rhor + + m.dVrcon = Constraint(m.time, rule=_dVrcon) + + def _dTrcon(m, t): + if t == 0: + return Constraint.Skip + return m.rhor * m.cpr * m.dTr[t] == m.Fa[t] * m.Mwa * m.cpr / m.Vr[t] * ( + m.Tf - m.Tr[t] + ) - m.k1 * exp(-m.E1 / (m.R * m.Tr[t])) * m.Ca[t] * m.deltaH1 - m.k2 * exp( + -m.E2 / (m.R * m.Tr[t]) + ) * m.Cb[ + t + ] * m.deltaH2 + m.alphaj * m.Aj / m.Vr0 * ( + m.Tj[t] - m.Tr[t] + ) + m.alphac * m.Ac / m.Vr0 * ( + m.Tc[t] - m.Tr[t] + ) + + m.dTrcon = Constraint(m.time, rule=_dTrcon) + + def _singlecooling(m, t): + return m.Tc[t] == m.Tj[t] + + m.singlecooling = Constraint(m.time, rule=_singlecooling) + + # Initial Conditions + def _initcon(m): + yield m.Ca[m.time.first()] == m.Ca0 + yield m.Cb[m.time.first()] == m.Cb0 + yield m.Cc[m.time.first()] == m.Cc0 + yield m.Vr[m.time.first()] == m.Vr0 + yield m.Tr[m.time.first()] == m.Tr0 + + m.initcon = ConstraintList(rule=_initcon) + + # + # Stage-specific cost computations + # + def ComputeFirstStageCost_rule(model): + return 0 + + m.FirstStageCost = Expression(rule=ComputeFirstStageCost_rule) + + def AllMeasurements(m): + return sum( + (m.Ca[t] - m.Ca_meas[t]) ** 2 + + (m.Cb[t] - m.Cb_meas[t]) ** 2 + + (m.Cc[t] - m.Cc_meas[t]) ** 2 + + 0.01 * (m.Tr[t] - m.Tr_meas[t]) ** 2 + for t in m.measT + ) + + def MissingMeasurements(m): + if data["experiment"] == 1: + return sum( + (m.Ca[t] - m.Ca_meas[t]) ** 2 + + (m.Cb[t] - m.Cb_meas[t]) ** 2 + + (m.Cc[t] - m.Cc_meas[t]) ** 2 + + (m.Tr[t] - m.Tr_meas[t]) ** 2 + for t in m.measT + ) + elif data["experiment"] == 2: + return sum((m.Tr[t] - m.Tr_meas[t]) ** 2 for t in m.measT) + else: + return sum( + (m.Cb[t] - m.Cb_meas[t]) ** 2 + (m.Tr[t] - m.Tr_meas[t]) ** 2 + for t in m.measT + ) + + m.SecondStageCost = Expression(rule=MissingMeasurements) + + def total_cost_rule(model): + return model.FirstStageCost + model.SecondStageCost + + m.Total_Cost_Objective = Objective(rule=total_cost_rule, sense=minimize) + + # Discretize model + disc = TransformationFactory("dae.collocation") + disc.apply_to(m, nfe=20, ncp=4) + return m + + +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: + data = json.load(infile) + data["experiment"] = 2 + + model = generate_model(data) + solver = SolverFactory("ipopt") + solver.solve(model) + print("k1 = ", model.k1()) + print("E1 = ", model.E1()) + + +if __name__ == "__main__": + main() diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index cbdc9179f35..dc747217b31 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -63,6 +63,8 @@ import pyomo.contrib.parmest.graphics as graphics from pyomo.dae import ContinuousSet +import pyomo.contrib.parmest.parmest_deprecated as parmest_deprecated + parmest_available = numpy_available & pandas_available & scipy_available inverse_reduced_hessian, inverse_reduced_hessian_available = attempt_import( @@ -336,16 +338,32 @@ class Estimator(object): Provides options to the solver (also the name of an attribute) """ - def __init__( - self, - model_function, - data, - theta_names, - obj_function=None, - tee=False, - diagnostic_mode=False, - solver_options=None, - ): + # backwards compatible constructor will accept the old inputs + # from parmest_deprecated as well as the new inputs using experiment lists + def __init__(self, *args, **kwargs): + + # use deprecated interface + self.pest_deprecated = None + if len(args) > 1: + logger.warning('Using deprecated parmest inputs (model_function, ' + + 'data, theta_names), please use experiment lists instead.') + self.pest_deprecated = parmest_deprecated.Estimator(*args, **kwargs) + return + + print("New parmest interface using Experiment lists coming soon!") + exit() + + # def __init__( + # self, + # model_function, + # data, + # theta_names, + # obj_function=None, + # tee=False, + # diagnostic_mode=False, + # solver_options=None, + # ): + self.model_function = model_function assert isinstance( @@ -906,6 +924,15 @@ def theta_est( cov: pd.DataFrame Covariance matrix of the fitted parameters (only for solver='ef_ipopt') """ + + # check if we are using deprecated parmest + if self.pest_deprecated is not None: + return self.pest_deprecated.theta_est( + solver=solver, + return_values=return_values, + calc_cov=calc_cov, + cov_n=cov_n) + assert isinstance(solver, str) assert isinstance(return_values, list) assert isinstance(calc_cov, bool) @@ -956,6 +983,16 @@ def theta_est_bootstrap( Theta values for each sample and (if return_samples = True) the sample numbers used in each estimation """ + + # check if we are using deprecated parmest + if self.pest_deprecated is not None: + return self.pest_deprecated.theta_est_bootstrap( + bootstrap_samples, + samplesize=samplesize, + replacement=replacement, + seed=seed, + return_samples=return_samples) + assert isinstance(bootstrap_samples, int) assert isinstance(samplesize, (type(None), int)) assert isinstance(replacement, bool) @@ -1011,6 +1048,15 @@ def theta_est_leaveNout( Theta values for each sample and (if return_samples = True) the sample numbers left out of each estimation """ + + # check if we are using deprecated parmest + if self.pest_deprecated is not None: + return self.pest_deprecated.theta_est_leaveNout( + lNo, + lNo_samples=lNo_samples, + seed=seed, + return_samples=return_samples) + assert isinstance(lNo, int) assert isinstance(lNo_samples, (type(None), int)) assert isinstance(seed, (type(None), int)) @@ -1084,6 +1130,16 @@ def leaveNout_bootstrap_test( indicates if the theta estimate is in (True) or out (False) of the alpha region for a given distribution (based on the bootstrap results) """ + + # check if we are using deprecated parmest + if self.pest_deprecated is not None: + return self.pest_deprecated.leaveNout_bootstrap_test( + lNo, + lNo_samples, + bootstrap_samples, + distribution, alphas, + seed=seed) + assert isinstance(lNo, int) assert isinstance(lNo_samples, (type(None), int)) assert isinstance(bootstrap_samples, int) @@ -1144,6 +1200,13 @@ def objective_at_theta(self, theta_values=None, initialize_parmest_model=False): Objective value for each theta (infeasible solutions are omitted). """ + + # check if we are using deprecated parmest + if self.pest_deprecated is not None: + return self.pest_deprecated.objective_at_theta( + theta_values=theta_values, + initialize_parmest_model=initialize_parmest_model) + if len(self.theta_names) == 1 and self.theta_names[0] == 'parmest_dummy_var': pass # skip assertion if model has no fitted parameters else: @@ -1258,6 +1321,15 @@ def likelihood_ratio_test( thresholds: pd.Series If return_threshold = True, the thresholds are also returned. """ + + # check if we are using deprecated parmest + if self.pest_deprecated is not None: + return self.pest_deprecated.likelihood_ratio_test( + obj_at_theta, + obj_value, + alphas, + return_thresholds=return_thresholds) + assert isinstance(obj_at_theta, pd.DataFrame) assert isinstance(obj_value, (int, float)) assert isinstance(alphas, list) @@ -1310,6 +1382,15 @@ def confidence_region_test( If test_theta_values is not None, returns test theta value along with True (inside) or False (outside) for each alpha """ + + # check if we are using deprecated parmest + if self.pest_deprecated is not None: + return self.pest_deprecated.confidence_region_test( + theta_values, + distribution, + alphas, + test_theta_values=test_theta_values) + assert isinstance(theta_values, pd.DataFrame) assert distribution in ['Rect', 'MVN', 'KDE'] assert isinstance(alphas, list) diff --git a/pyomo/contrib/parmest/parmest_deprecated.py b/pyomo/contrib/parmest/parmest_deprecated.py new file mode 100644 index 00000000000..cbdc9179f35 --- /dev/null +++ b/pyomo/contrib/parmest/parmest_deprecated.py @@ -0,0 +1,1366 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain +# rights in this software. +# This software is distributed under the 3-clause BSD License. +# ___________________________________________________________________________ +#### Using mpi-sppy instead of PySP; May 2020 +#### Adding option for "local" EF starting Sept 2020 +#### Wrapping mpi-sppy functionality and local option Jan 2021, Feb 2021 + +# TODO: move use_mpisppy to a Pyomo configuration option +# +# False implies always use the EF that is local to parmest +use_mpisppy = True # Use it if we can but use local if not. +if use_mpisppy: + try: + # MPI-SPPY has an unfortunate side effect of outputting + # "[ 0.00] Initializing mpi-sppy" when it is imported. This can + # cause things like doctests to fail. We will suppress that + # information here. + from pyomo.common.tee import capture_output + + with capture_output(): + import mpisppy.utils.sputils as sputils + except ImportError: + use_mpisppy = False # we can't use it +if use_mpisppy: + # These things should be outside the try block. + sputils.disable_tictoc_output() + import mpisppy.opt.ef as st + import mpisppy.scenario_tree as scenario_tree +else: + import pyomo.contrib.parmest.utils.create_ef as local_ef + import pyomo.contrib.parmest.utils.scenario_tree as scenario_tree + +import re +import importlib as im +import logging +import types +import json +from itertools import combinations + +from pyomo.common.dependencies import ( + attempt_import, + numpy as np, + numpy_available, + pandas as pd, + pandas_available, + scipy, + scipy_available, +) + +import pyomo.environ as pyo + +from pyomo.opt import SolverFactory +from pyomo.environ import Block, ComponentUID + +import pyomo.contrib.parmest.utils as utils +import pyomo.contrib.parmest.graphics as graphics +from pyomo.dae import ContinuousSet + +parmest_available = numpy_available & pandas_available & scipy_available + +inverse_reduced_hessian, inverse_reduced_hessian_available = attempt_import( + 'pyomo.contrib.interior_point.inverse_reduced_hessian' +) + +logger = logging.getLogger(__name__) + + +def ef_nonants(ef): + # Wrapper to call someone's ef_nonants + # (the function being called is very short, but it might be changed) + if use_mpisppy: + return sputils.ef_nonants(ef) + else: + return local_ef.ef_nonants(ef) + + +def _experiment_instance_creation_callback( + scenario_name, node_names=None, cb_data=None +): + """ + This is going to be called by mpi-sppy or the local EF and it will call into + the user's model's callback. + + Parameters: + ----------- + scenario_name: `str` Scenario name should end with a number + node_names: `None` ( Not used here ) + cb_data : dict with ["callback"], ["BootList"], + ["theta_names"], ["cb_data"], etc. + "cb_data" is passed through to user's callback function + that is the "callback" value. + "BootList" is None or bootstrap experiment number list. + (called cb_data by mpisppy) + + + Returns: + -------- + instance: `ConcreteModel` + instantiated scenario + + Note: + ---- + There is flexibility both in how the function is passed and its signature. + """ + assert cb_data is not None + outer_cb_data = cb_data + scen_num_str = re.compile(r'(\d+)$').search(scenario_name).group(1) + scen_num = int(scen_num_str) + basename = scenario_name[: -len(scen_num_str)] # to reconstruct name + + CallbackFunction = outer_cb_data["callback"] + + if callable(CallbackFunction): + callback = CallbackFunction + else: + cb_name = CallbackFunction + + if "CallbackModule" not in outer_cb_data: + raise RuntimeError( + "Internal Error: need CallbackModule in parmest callback" + ) + else: + modname = outer_cb_data["CallbackModule"] + + if isinstance(modname, str): + cb_module = im.import_module(modname, package=None) + elif isinstance(modname, types.ModuleType): + cb_module = modname + else: + print("Internal Error: bad CallbackModule") + raise + + try: + callback = getattr(cb_module, cb_name) + except: + print("Error getting function=" + cb_name + " from module=" + str(modname)) + raise + + if "BootList" in outer_cb_data: + bootlist = outer_cb_data["BootList"] + # print("debug in callback: using bootlist=",str(bootlist)) + # assuming bootlist itself is zero based + exp_num = bootlist[scen_num] + else: + exp_num = scen_num + + scen_name = basename + str(exp_num) + + cb_data = outer_cb_data["cb_data"] # cb_data might be None. + + # at least three signatures are supported. The first is preferred + try: + instance = callback(experiment_number=exp_num, cb_data=cb_data) + except TypeError: + raise RuntimeError( + "Only one callback signature is supported: " + "callback(experiment_number, cb_data) " + ) + """ + try: + instance = callback(scenario_tree_model, scen_name, node_names) + except TypeError: # deprecated signature? + try: + instance = callback(scen_name, node_names) + except: + print("Failed to create instance using callback; TypeError+") + raise + except: + print("Failed to create instance using callback.") + raise + """ + if hasattr(instance, "_mpisppy_node_list"): + raise RuntimeError(f"scenario for experiment {exp_num} has _mpisppy_node_list") + nonant_list = [ + instance.find_component(vstr) for vstr in outer_cb_data["theta_names"] + ] + if use_mpisppy: + instance._mpisppy_node_list = [ + scenario_tree.ScenarioNode( + name="ROOT", + cond_prob=1.0, + stage=1, + cost_expression=instance.FirstStageCost, + nonant_list=nonant_list, + scen_model=instance, + ) + ] + else: + instance._mpisppy_node_list = [ + scenario_tree.ScenarioNode( + name="ROOT", + cond_prob=1.0, + stage=1, + cost_expression=instance.FirstStageCost, + scen_name_list=None, + nonant_list=nonant_list, + scen_model=instance, + ) + ] + + if "ThetaVals" in outer_cb_data: + thetavals = outer_cb_data["ThetaVals"] + + # dlw august 2018: see mea code for more general theta + for vstr in thetavals: + theta_cuid = ComponentUID(vstr) + theta_object = theta_cuid.find_component_on(instance) + if thetavals[vstr] is not None: + # print("Fixing",vstr,"at",str(thetavals[vstr])) + theta_object.fix(thetavals[vstr]) + else: + # print("Freeing",vstr) + theta_object.unfix() + + return instance + + +# ============================================= +def _treemaker(scenlist): + """ + Makes a scenario tree (avoids dependence on daps) + + Parameters + ---------- + scenlist (list of `int`): experiment (i.e. scenario) numbers + + Returns + ------- + a `ConcreteModel` that is the scenario tree + """ + + num_scenarios = len(scenlist) + m = scenario_tree.tree_structure_model.CreateAbstractScenarioTreeModel() + m = m.create_instance() + m.Stages.add('Stage1') + m.Stages.add('Stage2') + m.Nodes.add('RootNode') + for i in scenlist: + m.Nodes.add('LeafNode_Experiment' + str(i)) + m.Scenarios.add('Experiment' + str(i)) + m.NodeStage['RootNode'] = 'Stage1' + m.ConditionalProbability['RootNode'] = 1.0 + for node in m.Nodes: + if node != 'RootNode': + m.NodeStage[node] = 'Stage2' + m.Children['RootNode'].add(node) + m.Children[node].clear() + m.ConditionalProbability[node] = 1.0 / num_scenarios + m.ScenarioLeafNode[node.replace('LeafNode_', '')] = node + + return m + + +def group_data(data, groupby_column_name, use_mean=None): + """ + Group data by scenario + + Parameters + ---------- + data: DataFrame + Data + groupby_column_name: strings + Name of data column which contains scenario numbers + use_mean: list of column names or None, optional + Name of data columns which should be reduced to a single value per + scenario by taking the mean + + Returns + ---------- + grouped_data: list of dictionaries + Grouped data + """ + if use_mean is None: + use_mean_list = [] + else: + use_mean_list = use_mean + + grouped_data = [] + for exp_num, group in data.groupby(data[groupby_column_name]): + d = {} + for col in group.columns: + if col in use_mean_list: + d[col] = group[col].mean() + else: + d[col] = list(group[col]) + grouped_data.append(d) + + return grouped_data + + +class _SecondStageCostExpr(object): + """ + Class to pass objective expression into the Pyomo model + """ + + def __init__(self, ssc_function, data): + self._ssc_function = ssc_function + self._data = data + + def __call__(self, model): + return self._ssc_function(model, self._data) + + +class Estimator(object): + """ + Parameter estimation class + + Parameters + ---------- + model_function: function + Function that generates an instance of the Pyomo model using 'data' + as the input argument + data: pd.DataFrame, list of dictionaries, list of dataframes, or list of json file names + Data that is used to build an instance of the Pyomo model and build + the objective function + theta_names: list of strings + List of Var names to estimate + obj_function: function, optional + Function used to formulate parameter estimation objective, generally + sum of squared error between measurements and model variables. + If no function is specified, the model is used + "as is" and should be defined with a "FirstStageCost" and + "SecondStageCost" expression that are used to build an objective. + tee: bool, optional + Indicates that ef solver output should be teed + diagnostic_mode: bool, optional + If True, print diagnostics from the solver + solver_options: dict, optional + Provides options to the solver (also the name of an attribute) + """ + + def __init__( + self, + model_function, + data, + theta_names, + obj_function=None, + tee=False, + diagnostic_mode=False, + solver_options=None, + ): + self.model_function = model_function + + assert isinstance( + data, (list, pd.DataFrame) + ), "Data must be a list or DataFrame" + # convert dataframe into a list of dataframes, each row = one scenario + if isinstance(data, pd.DataFrame): + self.callback_data = [ + data.loc[i, :].to_frame().transpose() for i in data.index + ] + else: + self.callback_data = data + assert isinstance( + self.callback_data[0], (dict, pd.DataFrame, str) + ), "The scenarios in data must be a dictionary, DataFrame or filename" + + if len(theta_names) == 0: + self.theta_names = ['parmest_dummy_var'] + else: + self.theta_names = theta_names + + self.obj_function = obj_function + self.tee = tee + self.diagnostic_mode = diagnostic_mode + self.solver_options = solver_options + + self._second_stage_cost_exp = "SecondStageCost" + # boolean to indicate if model is initialized using a square solve + self.model_initialized = False + + def _return_theta_names(self): + """ + Return list of fitted model parameter names + """ + # if fitted model parameter names differ from theta_names created when Estimator object is created + if hasattr(self, 'theta_names_updated'): + return self.theta_names_updated + + else: + return ( + self.theta_names + ) # default theta_names, created when Estimator object is created + + def _create_parmest_model(self, data): + """ + Modify the Pyomo model for parameter estimation + """ + model = self.model_function(data) + + if (len(self.theta_names) == 1) and ( + self.theta_names[0] == 'parmest_dummy_var' + ): + model.parmest_dummy_var = pyo.Var(initialize=1.0) + + # Add objective function (optional) + if self.obj_function: + for obj in model.component_objects(pyo.Objective): + if obj.name in ["Total_Cost_Objective"]: + raise RuntimeError( + "Parmest will not override the existing model Objective named " + + obj.name + ) + obj.deactivate() + + for expr in model.component_data_objects(pyo.Expression): + if expr.name in ["FirstStageCost", "SecondStageCost"]: + raise RuntimeError( + "Parmest will not override the existing model Expression named " + + expr.name + ) + model.FirstStageCost = pyo.Expression(expr=0) + model.SecondStageCost = pyo.Expression( + rule=_SecondStageCostExpr(self.obj_function, data) + ) + + def TotalCost_rule(model): + return model.FirstStageCost + model.SecondStageCost + + model.Total_Cost_Objective = pyo.Objective( + rule=TotalCost_rule, sense=pyo.minimize + ) + + # Convert theta Params to Vars, and unfix theta Vars + model = utils.convert_params_to_vars(model, self.theta_names) + + # Update theta names list to use CUID string representation + for i, theta in enumerate(self.theta_names): + var_cuid = ComponentUID(theta) + var_validate = var_cuid.find_component_on(model) + if var_validate is None: + logger.warning( + "theta_name[%s] (%s) was not found on the model", (i, theta) + ) + else: + try: + # If the component is not a variable, + # this will generate an exception (and the warning + # in the 'except') + var_validate.unfix() + self.theta_names[i] = repr(var_cuid) + except: + logger.warning(theta + ' is not a variable') + + self.parmest_model = model + + return model + + def _instance_creation_callback(self, experiment_number=None, cb_data=None): + # cb_data is a list of dictionaries, list of dataframes, OR list of json file names + exp_data = cb_data[experiment_number] + if isinstance(exp_data, (dict, pd.DataFrame)): + pass + elif isinstance(exp_data, str): + try: + with open(exp_data, 'r') as infile: + exp_data = json.load(infile) + except: + raise RuntimeError(f'Could not read {exp_data} as json') + else: + raise RuntimeError(f'Unexpected data format for cb_data={cb_data}') + model = self._create_parmest_model(exp_data) + + return model + + def _Q_opt( + self, + ThetaVals=None, + solver="ef_ipopt", + return_values=[], + bootlist=None, + calc_cov=False, + cov_n=None, + ): + """ + Set up all thetas as first stage Vars, return resulting theta + values as well as the objective function value. + + """ + if solver == "k_aug": + raise RuntimeError("k_aug no longer supported.") + + # (Bootstrap scenarios will use indirection through the bootlist) + if bootlist is None: + scenario_numbers = list(range(len(self.callback_data))) + scen_names = ["Scenario{}".format(i) for i in scenario_numbers] + else: + scen_names = ["Scenario{}".format(i) for i in range(len(bootlist))] + + # tree_model.CallbackModule = None + outer_cb_data = dict() + outer_cb_data["callback"] = self._instance_creation_callback + if ThetaVals is not None: + outer_cb_data["ThetaVals"] = ThetaVals + if bootlist is not None: + outer_cb_data["BootList"] = bootlist + outer_cb_data["cb_data"] = self.callback_data # None is OK + outer_cb_data["theta_names"] = self.theta_names + + options = {"solver": "ipopt"} + scenario_creator_options = {"cb_data": outer_cb_data} + if use_mpisppy: + ef = sputils.create_EF( + scen_names, + _experiment_instance_creation_callback, + EF_name="_Q_opt", + suppress_warnings=True, + scenario_creator_kwargs=scenario_creator_options, + ) + else: + ef = local_ef.create_EF( + scen_names, + _experiment_instance_creation_callback, + EF_name="_Q_opt", + suppress_warnings=True, + scenario_creator_kwargs=scenario_creator_options, + ) + self.ef_instance = ef + + # Solve the extensive form with ipopt + if solver == "ef_ipopt": + if not calc_cov: + # Do not calculate the reduced hessian + + solver = SolverFactory('ipopt') + if self.solver_options is not None: + for key in self.solver_options: + solver.options[key] = self.solver_options[key] + + solve_result = solver.solve(self.ef_instance, tee=self.tee) + + # The import error will be raised when we attempt to use + # inv_reduced_hessian_barrier below. + # + # elif not asl_available: + # raise ImportError("parmest requires ASL to calculate the " + # "covariance matrix with solver 'ipopt'") + else: + # parmest makes the fitted parameters stage 1 variables + ind_vars = [] + 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, + ) + + if self.diagnostic_mode: + print( + ' Solver termination condition = ', + str(solve_result.solver.termination_condition), + ) + + # assume all first stage are thetas... + thetavals = {} + for ndname, Var, solval in ef_nonants(ef): + # process the name + # the scenarios are blocks, so strip the scenario name + vname = Var.name[Var.name.find(".") + 1 :] + thetavals[vname] = solval + + objval = pyo.value(ef.EF_Obj) + + if calc_cov: + # Calculate the covariance matrix + + # Number of data points considered + n = cov_n + + # Extract number of fitted parameters + l = len(thetavals) + + # Assumption: Objective value is sum of squared errors + sse = objval + + '''Calculate covariance assuming experimental observation errors are + independent and follow a Gaussian + distribution with constant variance. + + The formula used in parmest was verified against equations (7-5-15) and + (7-5-16) in "Nonlinear Parameter Estimation", Y. Bard, 1974. + + This formula is also applicable if the objective is scaled by a constant; + the constant cancels out. (was scaled by 1/n because it computes an + expected value.) + ''' + cov = 2 * sse / (n - l) * inv_red_hes + cov = pd.DataFrame( + cov, index=thetavals.keys(), columns=thetavals.keys() + ) + + thetavals = pd.Series(thetavals) + + if len(return_values) > 0: + var_values = [] + if len(scen_names) > 1: # multiple scenarios + block_objects = self.ef_instance.component_objects( + Block, descend_into=False + ) + else: # single scenario + block_objects = [self.ef_instance] + for exp_i in block_objects: + vals = {} + for var in return_values: + exp_i_var = exp_i.find_component(str(var)) + if ( + exp_i_var is None + ): # we might have a block such as _mpisppy_data + continue + # if value to return is ContinuousSet + if type(exp_i_var) == ContinuousSet: + temp = list(exp_i_var) + else: + temp = [pyo.value(_) for _ in exp_i_var.values()] + if len(temp) == 1: + vals[var] = temp[0] + else: + vals[var] = temp + if len(vals) > 0: + var_values.append(vals) + var_values = pd.DataFrame(var_values) + if calc_cov: + return objval, thetavals, var_values, cov + else: + return objval, thetavals, var_values + + if calc_cov: + return objval, thetavals, cov + else: + return objval, thetavals + + else: + raise RuntimeError("Unknown solver in Q_Opt=" + solver) + + def _Q_at_theta(self, thetavals, initialize_parmest_model=False): + """ + Return the objective function value with fixed theta values. + + Parameters + ---------- + thetavals: dict + A dictionary of theta values. + + initialize_parmest_model: boolean + If True: Solve square problem instance, build extensive form of the model for + parameter estimation, and set flag model_initialized to True + + Returns + ------- + objectiveval: float + The objective function value. + thetavals: dict + A dictionary of all values for theta that were input. + solvertermination: Pyomo TerminationCondition + Tries to return the "worst" solver status across the scenarios. + pyo.TerminationCondition.optimal is the best and + pyo.TerminationCondition.infeasible is the worst. + """ + + optimizer = pyo.SolverFactory('ipopt') + + if len(thetavals) > 0: + dummy_cb = { + "callback": self._instance_creation_callback, + "ThetaVals": thetavals, + "theta_names": self._return_theta_names(), + "cb_data": self.callback_data, + } + else: + dummy_cb = { + "callback": self._instance_creation_callback, + "theta_names": self._return_theta_names(), + "cb_data": self.callback_data, + } + + if self.diagnostic_mode: + if len(thetavals) > 0: + print(' Compute objective at theta = ', str(thetavals)) + else: + print(' Compute objective at initial theta') + + # start block of code to deal with models with no constraints + # (ipopt will crash or complain on such problems without special care) + instance = _experiment_instance_creation_callback("FOO0", None, dummy_cb) + try: # deal with special problems so Ipopt will not crash + first = next(instance.component_objects(pyo.Constraint, active=True)) + active_constraints = True + except: + active_constraints = False + # end block of code to deal with models with no constraints + + WorstStatus = pyo.TerminationCondition.optimal + totobj = 0 + scenario_numbers = list(range(len(self.callback_data))) + if initialize_parmest_model: + # create dictionary to store pyomo model instances (scenarios) + scen_dict = dict() + + for snum in scenario_numbers: + sname = "scenario_NODE" + str(snum) + instance = _experiment_instance_creation_callback(sname, None, dummy_cb) + + if initialize_parmest_model: + # list to store fitted parameter names that will be unfixed + # after initialization + theta_init_vals = [] + # use appropriate theta_names member + theta_ref = self._return_theta_names() + + for i, theta in enumerate(theta_ref): + # Use parser in ComponentUID to locate the component + var_cuid = ComponentUID(theta) + var_validate = var_cuid.find_component_on(instance) + if var_validate is None: + logger.warning( + "theta_name %s was not found on the model", (theta) + ) + else: + try: + if len(thetavals) == 0: + var_validate.fix() + else: + var_validate.fix(thetavals[theta]) + theta_init_vals.append(var_validate) + except: + logger.warning( + 'Unable to fix model parameter value for %s (not a Pyomo model Var)', + (theta), + ) + + if active_constraints: + 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 + ) + print( + " status_obj, solved, iters, time, regularization_stat = ", + str(status_obj), + str(solved), + str(iters), + str(time), + str(regu), + ) + + results = optimizer.solve(instance) + if self.diagnostic_mode: + print( + 'standard solve solver termination condition=', + str(results.solver.termination_condition), + ) + + if ( + results.solver.termination_condition + != pyo.TerminationCondition.optimal + ): + # DLW: Aug2018: not distinguishing "middlish" conditions + if WorstStatus != pyo.TerminationCondition.infeasible: + WorstStatus = results.solver.termination_condition + if initialize_parmest_model: + if self.diagnostic_mode: + print( + "Scenario {:d} infeasible with initialized parameter values".format( + snum + ) + ) + else: + if initialize_parmest_model: + if self.diagnostic_mode: + print( + "Scenario {:d} initialization successful with initial parameter values".format( + snum + ) + ) + if initialize_parmest_model: + # unfix parameters after initialization + for theta in theta_init_vals: + theta.unfix() + scen_dict[sname] = instance + else: + if initialize_parmest_model: + # unfix parameters after initialization + for theta in theta_init_vals: + theta.unfix() + scen_dict[sname] = instance + + objobject = getattr(instance, self._second_stage_cost_exp) + objval = pyo.value(objobject) + totobj += objval + + retval = totobj / len(scenario_numbers) # -1?? + if initialize_parmest_model and not hasattr(self, 'ef_instance'): + # create extensive form of the model using scenario dictionary + if len(scen_dict) > 0: + for scen in scen_dict.values(): + scen._mpisppy_probability = 1 / len(scen_dict) + + if use_mpisppy: + EF_instance = sputils._create_EF_from_scen_dict( + scen_dict, + EF_name="_Q_at_theta", + # suppress_warnings=True + ) + else: + EF_instance = local_ef._create_EF_from_scen_dict( + scen_dict, EF_name="_Q_at_theta", nonant_for_fixed_vars=True + ) + + self.ef_instance = EF_instance + # set self.model_initialized flag to True to skip extensive form model + # creation using theta_est() + self.model_initialized = True + + # return initialized theta values + if len(thetavals) == 0: + # use appropriate theta_names member + theta_ref = self._return_theta_names() + for i, theta in enumerate(theta_ref): + thetavals[theta] = theta_init_vals[i]() + + return retval, thetavals, WorstStatus + + def _get_sample_list(self, samplesize, num_samples, replacement=True): + samplelist = list() + + scenario_numbers = list(range(len(self.callback_data))) + + if num_samples is None: + # This could get very large + for i, l in enumerate(combinations(scenario_numbers, samplesize)): + samplelist.append((i, np.sort(l))) + else: + for i in range(num_samples): + attempts = 0 + unique_samples = 0 # check for duplicates in each sample + duplicate = False # check for duplicates between samples + while (unique_samples <= len(self._return_theta_names())) and ( + not duplicate + ): + sample = np.random.choice( + scenario_numbers, samplesize, replace=replacement + ) + sample = np.sort(sample).tolist() + unique_samples = len(np.unique(sample)) + if sample in samplelist: + duplicate = True + + attempts += 1 + if attempts > num_samples: # arbitrary timeout limit + raise RuntimeError( + """Internal error: timeout constructing + a sample, the dim of theta may be too + close to the samplesize""" + ) + + samplelist.append((i, sample)) + + return samplelist + + def theta_est( + self, solver="ef_ipopt", return_values=[], calc_cov=False, cov_n=None + ): + """ + Parameter estimation using all scenarios in the data + + Parameters + ---------- + solver: string, optional + Currently only "ef_ipopt" is supported. Default is "ef_ipopt". + return_values: list, optional + List of Variable names, used to return values from the model for data reconciliation + calc_cov: boolean, optional + If True, calculate and return the covariance matrix (only for "ef_ipopt" solver) + cov_n: int, optional + If calc_cov=True, then the user needs to supply the number of datapoints + that are used in the objective function + + Returns + ------- + objectiveval: float + The objective function value + thetavals: pd.Series + Estimated values for theta + variable values: pd.DataFrame + Variable values for each variable name in return_values (only for solver='ef_ipopt') + cov: pd.DataFrame + Covariance matrix of the fitted parameters (only for solver='ef_ipopt') + """ + assert isinstance(solver, str) + assert isinstance(return_values, list) + assert isinstance(calc_cov, bool) + if calc_cov: + assert isinstance( + cov_n, int + ), "The number of datapoints that are used in the objective function is required to calculate the covariance matrix" + assert cov_n > len( + self._return_theta_names() + ), "The number of datapoints must be greater than the number of parameters to estimate" + + return self._Q_opt( + solver=solver, + return_values=return_values, + bootlist=None, + calc_cov=calc_cov, + cov_n=cov_n, + ) + + def theta_est_bootstrap( + self, + bootstrap_samples, + samplesize=None, + replacement=True, + seed=None, + return_samples=False, + ): + """ + Parameter estimation using bootstrap resampling of the data + + Parameters + ---------- + bootstrap_samples: int + Number of bootstrap samples to draw from the data + samplesize: int or None, optional + Size of each bootstrap sample. If samplesize=None, samplesize will be + set to the number of samples in the data + replacement: bool, optional + Sample with or without replacement + seed: int or None, optional + Random seed + return_samples: bool, optional + Return a list of sample numbers used in each bootstrap estimation + + Returns + ------- + bootstrap_theta: pd.DataFrame + Theta values for each sample and (if return_samples = True) + the sample numbers used in each estimation + """ + assert isinstance(bootstrap_samples, int) + assert isinstance(samplesize, (type(None), int)) + assert isinstance(replacement, bool) + assert isinstance(seed, (type(None), int)) + assert isinstance(return_samples, bool) + + if samplesize is None: + samplesize = len(self.callback_data) + + if seed is not None: + np.random.seed(seed) + + global_list = self._get_sample_list(samplesize, bootstrap_samples, replacement) + + task_mgr = utils.ParallelTaskManager(bootstrap_samples) + local_list = task_mgr.global_to_local_data(global_list) + + bootstrap_theta = list() + for idx, sample in local_list: + objval, thetavals = self._Q_opt(bootlist=list(sample)) + thetavals['samples'] = sample + bootstrap_theta.append(thetavals) + + global_bootstrap_theta = task_mgr.allgather_global_data(bootstrap_theta) + bootstrap_theta = pd.DataFrame(global_bootstrap_theta) + + if not return_samples: + del bootstrap_theta['samples'] + + return bootstrap_theta + + def theta_est_leaveNout( + self, lNo, lNo_samples=None, seed=None, return_samples=False + ): + """ + Parameter estimation where N data points are left out of each sample + + Parameters + ---------- + lNo: int + Number of data points to leave out for parameter estimation + lNo_samples: int + Number of leave-N-out samples. If lNo_samples=None, the maximum + number of combinations will be used + seed: int or None, optional + Random seed + return_samples: bool, optional + Return a list of sample numbers that were left out + + Returns + ------- + lNo_theta: pd.DataFrame + Theta values for each sample and (if return_samples = True) + the sample numbers left out of each estimation + """ + assert isinstance(lNo, int) + assert isinstance(lNo_samples, (type(None), int)) + assert isinstance(seed, (type(None), int)) + assert isinstance(return_samples, bool) + + samplesize = len(self.callback_data) - lNo + + if seed is not None: + np.random.seed(seed) + + global_list = self._get_sample_list(samplesize, lNo_samples, replacement=False) + + task_mgr = utils.ParallelTaskManager(len(global_list)) + local_list = task_mgr.global_to_local_data(global_list) + + lNo_theta = list() + for idx, sample in local_list: + objval, thetavals = self._Q_opt(bootlist=list(sample)) + lNo_s = list(set(range(len(self.callback_data))) - set(sample)) + thetavals['lNo'] = np.sort(lNo_s) + lNo_theta.append(thetavals) + + global_bootstrap_theta = task_mgr.allgather_global_data(lNo_theta) + lNo_theta = pd.DataFrame(global_bootstrap_theta) + + if not return_samples: + del lNo_theta['lNo'] + + return lNo_theta + + def leaveNout_bootstrap_test( + self, lNo, lNo_samples, bootstrap_samples, distribution, alphas, seed=None + ): + """ + Leave-N-out bootstrap test to compare theta values where N data points are + left out to a bootstrap analysis using the remaining data, + results indicate if theta is within a confidence region + determined by the bootstrap analysis + + Parameters + ---------- + lNo: int + Number of data points to leave out for parameter estimation + lNo_samples: int + Leave-N-out sample size. If lNo_samples=None, the maximum number + of combinations will be used + bootstrap_samples: int: + Bootstrap sample size + distribution: string + Statistical distribution used to define a confidence region, + options = 'MVN' for multivariate_normal, 'KDE' for gaussian_kde, + and 'Rect' for rectangular. + alphas: list + List of alpha values used to determine if theta values are inside + or outside the region. + seed: int or None, optional + Random seed + + Returns + ---------- + List of tuples with one entry per lNo_sample: + + * The first item in each tuple is the list of N samples that are left + out. + * The second item in each tuple is a DataFrame of theta estimated using + the N samples. + * The third item in each tuple is a DataFrame containing results from + the bootstrap analysis using the remaining samples. + + For each DataFrame a column is added for each value of alpha which + indicates if the theta estimate is in (True) or out (False) of the + alpha region for a given distribution (based on the bootstrap results) + """ + assert isinstance(lNo, int) + assert isinstance(lNo_samples, (type(None), int)) + assert isinstance(bootstrap_samples, int) + assert distribution in ['Rect', 'MVN', 'KDE'] + assert isinstance(alphas, list) + assert isinstance(seed, (type(None), int)) + + if seed is not None: + np.random.seed(seed) + + data = self.callback_data.copy() + + global_list = self._get_sample_list(lNo, lNo_samples, replacement=False) + + results = [] + for idx, sample in global_list: + # Reset callback_data to only include the sample + self.callback_data = [data[i] for i in sample] + + obj, theta = self.theta_est() + + # Reset callback_data to include all scenarios except the sample + self.callback_data = [data[i] for i in range(len(data)) if i not in sample] + + bootstrap_theta = self.theta_est_bootstrap(bootstrap_samples) + + training, test = self.confidence_region_test( + bootstrap_theta, + distribution=distribution, + alphas=alphas, + test_theta_values=theta, + ) + + results.append((sample, test, training)) + + # Reset callback_data (back to full data set) + self.callback_data = data + + return results + + def objective_at_theta(self, theta_values=None, initialize_parmest_model=False): + """ + Objective value for each theta + + Parameters + ---------- + theta_values: pd.DataFrame, columns=theta_names + Values of theta used to compute the objective + + initialize_parmest_model: boolean + If True: Solve square problem instance, build extensive form of the model for + parameter estimation, and set flag model_initialized to True + + + Returns + ------- + obj_at_theta: pd.DataFrame + Objective value for each theta (infeasible solutions are + omitted). + """ + if len(self.theta_names) == 1 and self.theta_names[0] == 'parmest_dummy_var': + pass # skip assertion if model has no fitted parameters + else: + # create a local instance of the pyomo model to access model variables and parameters + model_temp = self._create_parmest_model(self.callback_data[0]) + model_theta_list = [] # list to store indexed and non-indexed parameters + # iterate over original theta_names + for theta_i in self.theta_names: + var_cuid = ComponentUID(theta_i) + var_validate = var_cuid.find_component_on(model_temp) + # check if theta in theta_names are indexed + try: + # get component UID of Set over which theta is defined + set_cuid = ComponentUID(var_validate.index_set()) + # access and iterate over the Set to generate theta names as they appear + # in the pyomo model + set_validate = set_cuid.find_component_on(model_temp) + for s in set_validate: + self_theta_temp = repr(var_cuid) + "[" + repr(s) + "]" + # generate list of theta names + model_theta_list.append(self_theta_temp) + # if theta is not indexed, copy theta name to list as-is + except AttributeError: + self_theta_temp = repr(var_cuid) + model_theta_list.append(self_theta_temp) + except: + raise + # if self.theta_names is not the same as temp model_theta_list, + # create self.theta_names_updated + if set(self.theta_names) == set(model_theta_list) and len( + self.theta_names + ) == set(model_theta_list): + pass + else: + self.theta_names_updated = model_theta_list + + if theta_values is None: + all_thetas = {} # dictionary to store fitted variables + # use appropriate theta names member + theta_names = self._return_theta_names() + else: + assert isinstance(theta_values, pd.DataFrame) + # for parallel code we need to use lists and dicts in the loop + theta_names = theta_values.columns + # # check if theta_names are in model + for theta in list(theta_names): + theta_temp = theta.replace("'", "") # cleaning quotes from theta_names + + assert theta_temp in [ + t.replace("'", "") for t in model_theta_list + ], "Theta name {} in 'theta_values' not in 'theta_names' {}".format( + theta_temp, model_theta_list + ) + assert len(list(theta_names)) == len(model_theta_list) + + all_thetas = theta_values.to_dict('records') + + if all_thetas: + task_mgr = utils.ParallelTaskManager(len(all_thetas)) + local_thetas = task_mgr.global_to_local_data(all_thetas) + else: + if initialize_parmest_model: + task_mgr = utils.ParallelTaskManager( + 1 + ) # initialization performed using just 1 set of theta values + # walk over the mesh, return objective function + all_obj = list() + if len(all_thetas) > 0: + for Theta in local_thetas: + obj, thetvals, worststatus = self._Q_at_theta( + Theta, initialize_parmest_model=initialize_parmest_model + ) + if worststatus != pyo.TerminationCondition.infeasible: + all_obj.append(list(Theta.values()) + [obj]) + # DLW, Aug2018: should we also store the worst solver status? + else: + obj, thetvals, worststatus = self._Q_at_theta( + thetavals={}, initialize_parmest_model=initialize_parmest_model + ) + if worststatus != pyo.TerminationCondition.infeasible: + all_obj.append(list(thetvals.values()) + [obj]) + + global_all_obj = task_mgr.allgather_global_data(all_obj) + dfcols = list(theta_names) + ['obj'] + obj_at_theta = pd.DataFrame(data=global_all_obj, columns=dfcols) + return obj_at_theta + + def likelihood_ratio_test( + self, obj_at_theta, obj_value, alphas, return_thresholds=False + ): + r""" + Likelihood ratio test to identify theta values within a confidence + region using the :math:`\chi^2` distribution + + Parameters + ---------- + obj_at_theta: pd.DataFrame, columns = theta_names + 'obj' + Objective values for each theta value (returned by + objective_at_theta) + obj_value: int or float + Objective value from parameter estimation using all data + alphas: list + List of alpha values to use in the chi2 test + return_thresholds: bool, optional + Return the threshold value for each alpha + + Returns + ------- + LR: pd.DataFrame + Objective values for each theta value along with True or False for + each alpha + thresholds: pd.Series + If return_threshold = True, the thresholds are also returned. + """ + assert isinstance(obj_at_theta, pd.DataFrame) + assert isinstance(obj_value, (int, float)) + assert isinstance(alphas, list) + assert isinstance(return_thresholds, bool) + + LR = obj_at_theta.copy() + S = len(self.callback_data) + thresholds = {} + for a in alphas: + chi2_val = scipy.stats.chi2.ppf(a, 2) + thresholds[a] = obj_value * ((chi2_val / (S - 2)) + 1) + LR[a] = LR['obj'] < thresholds[a] + + thresholds = pd.Series(thresholds) + + if return_thresholds: + return LR, thresholds + else: + return LR + + def confidence_region_test( + self, theta_values, distribution, alphas, test_theta_values=None + ): + """ + Confidence region test to determine if theta values are within a + rectangular, multivariate normal, or Gaussian kernel density distribution + for a range of alpha values + + Parameters + ---------- + theta_values: pd.DataFrame, columns = theta_names + Theta values used to generate a confidence region + (generally returned by theta_est_bootstrap) + distribution: string + Statistical distribution used to define a confidence region, + options = 'MVN' for multivariate_normal, 'KDE' for gaussian_kde, + and 'Rect' for rectangular. + alphas: list + List of alpha values used to determine if theta values are inside + or outside the region. + test_theta_values: pd.Series or pd.DataFrame, keys/columns = theta_names, optional + Additional theta values that are compared to the confidence region + to determine if they are inside or outside. + + Returns + training_results: pd.DataFrame + Theta value used to generate the confidence region along with True + (inside) or False (outside) for each alpha + test_results: pd.DataFrame + If test_theta_values is not None, returns test theta value along + with True (inside) or False (outside) for each alpha + """ + assert isinstance(theta_values, pd.DataFrame) + assert distribution in ['Rect', 'MVN', 'KDE'] + assert isinstance(alphas, list) + assert isinstance( + test_theta_values, (type(None), dict, pd.Series, pd.DataFrame) + ) + + if isinstance(test_theta_values, (dict, pd.Series)): + test_theta_values = pd.Series(test_theta_values).to_frame().transpose() + + training_results = theta_values.copy() + + if test_theta_values is not None: + test_result = test_theta_values.copy() + + for a in alphas: + if distribution == 'Rect': + lb, ub = graphics.fit_rect_dist(theta_values, a) + training_results[a] = (theta_values > lb).all(axis=1) & ( + theta_values < ub + ).all(axis=1) + + if test_theta_values is not None: + # use upper and lower bound from the training set + test_result[a] = (test_theta_values > lb).all(axis=1) & ( + test_theta_values < ub + ).all(axis=1) + + elif distribution == 'MVN': + dist = graphics.fit_mvn_dist(theta_values) + Z = dist.pdf(theta_values) + score = scipy.stats.scoreatpercentile(Z, (1 - a) * 100) + training_results[a] = Z >= score + + if test_theta_values is not None: + # use score from the training set + Z = dist.pdf(test_theta_values) + test_result[a] = Z >= score + + elif distribution == 'KDE': + dist = graphics.fit_kde_dist(theta_values) + Z = dist.pdf(theta_values.transpose()) + score = scipy.stats.scoreatpercentile(Z, (1 - a) * 100) + training_results[a] = Z >= score + + if test_theta_values is not None: + # use score from the training set + Z = dist.pdf(test_theta_values.transpose()) + test_result[a] = Z >= score + + if test_theta_values is not None: + return training_results, test_result + else: + return training_results diff --git a/pyomo/contrib/parmest/scenariocreator.py b/pyomo/contrib/parmest/scenariocreator.py index 58d2d4da722..18c27ad1c86 100644 --- a/pyomo/contrib/parmest/scenariocreator.py +++ b/pyomo/contrib/parmest/scenariocreator.py @@ -14,6 +14,10 @@ import pyomo.environ as pyo +import pyomo.contrib.parmest.scenariocreator_deprecated as scen_deprecated + +import logging +logger = logging.getLogger(__name__) class ScenarioSet(object): """ @@ -119,8 +123,17 @@ class ScenarioCreator(object): """ def __init__(self, pest, solvername): - self.pest = pest - self.solvername = solvername + + # is this a deprecated pest object? + self.scen_deprecated = None + if pest.pest_deprecated is not None: + logger.warning("Using a deprecated parmest object for scenario " + + "creator, please recreate object using experiment lists.") + self.scen_deprecated = scen_deprecated.ScenarioCreator( + pest.pest_deprecated, solvername) + else: + self.pest = pest + self.solvername = solvername def ScenariosFromExperiments(self, addtoSet): """Creates new self.Scenarios list using the experiments only. @@ -131,6 +144,11 @@ def ScenariosFromExperiments(self, addtoSet): a ScenarioSet """ + # check if using deprecated pest object + if self.scen_deprecated is not None: + self.scen_deprecated.ScenariosFromExperiments(addtoSet) + return + assert isinstance(addtoSet, ScenarioSet) scenario_numbers = list(range(len(self.pest.callback_data))) @@ -160,6 +178,12 @@ def ScenariosFromBootstrap(self, addtoSet, numtomake, seed=None): numtomake (int) : number of scenarios to create """ + # check if using deprecated pest object + if self.scen_deprecated is not None: + self.scen_deprecated.ScenariosFromBootstrap( + addtoSet, numtomake, seed=seed) + return + assert isinstance(addtoSet, ScenarioSet) bootstrap_thetas = self.pest.theta_est_bootstrap(numtomake, seed=seed) diff --git a/pyomo/contrib/parmest/scenariocreator_deprecated.py b/pyomo/contrib/parmest/scenariocreator_deprecated.py new file mode 100644 index 00000000000..af084d0712c --- /dev/null +++ b/pyomo/contrib/parmest/scenariocreator_deprecated.py @@ -0,0 +1,166 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain +# rights in this software. +# This software is distributed under the 3-clause BSD License. +# ___________________________________________________________________________ + +# ScenariosCreator.py - Class to create and deliver scenarios using parmest +# DLW March 2020 + +import pyomo.environ as pyo + + +class ScenarioSet(object): + """ + Class to hold scenario sets + + Args: + name (str): name of the set (might be "") + + """ + + def __init__(self, name): + # Note: If there was a use-case, the list could be a dataframe. + self._scens = list() # use a df instead? + self.name = name # might be "" + + def _firstscen(self): + # Return the first scenario for testing and to get Theta names. + assert len(self._scens) > 0 + return self._scens[0] + + def ScensIterator(self): + """Usage: for scenario in ScensIterator()""" + return iter(self._scens) + + def ScenarioNumber(self, scennum): + """Returns the scenario with the given, zero-based number""" + return self._scens[scennum] + + def addone(self, scen): + """Add a scenario to the set + + Args: + scen (ParmestScen): the scenario to add + """ + assert isinstance(self._scens, list) + self._scens.append(scen) + + def append_bootstrap(self, bootstrap_theta): + """Append a bootstrap theta df to the scenario set; equally likely + + Args: + bootstrap_theta (dataframe): created by the bootstrap + Note: this can be cleaned up a lot with the list becomes a df, + which is why I put it in the ScenarioSet class. + """ + assert len(bootstrap_theta) > 0 + prob = 1.0 / len(bootstrap_theta) + + # dict of ThetaVal dicts + dfdict = bootstrap_theta.to_dict(orient='index') + + for index, ThetaVals in dfdict.items(): + name = "Bootstrap" + str(index) + self.addone(ParmestScen(name, ThetaVals, prob)) + + def write_csv(self, filename): + """write a csv file with the scenarios in the set + + Args: + filename (str): full path and full name of file + """ + if len(self._scens) == 0: + print("Empty scenario set, not writing file={}".format(filename)) + return + with open(filename, "w") as f: + f.write("Name,Probability") + for n in self._firstscen().ThetaVals.keys(): + f.write(",{}".format(n)) + f.write('\n') + for s in self.ScensIterator(): + f.write("{},{}".format(s.name, s.probability)) + for v in s.ThetaVals.values(): + f.write(",{}".format(v)) + f.write('\n') + + +class ParmestScen(object): + """A little container for scenarios; the Args are the attributes. + + Args: + name (str): name for reporting; might be "" + ThetaVals (dict): ThetaVals[name]=val + probability (float): probability of occurrence "near" these ThetaVals + """ + + def __init__(self, name, ThetaVals, probability): + self.name = name + assert isinstance(ThetaVals, dict) + self.ThetaVals = ThetaVals + self.probability = probability + + +############################################################ + + +class ScenarioCreator(object): + """Create scenarios from parmest. + + Args: + pest (Estimator): the parmest object + solvername (str): name of the solver (e.g. "ipopt") + + """ + + def __init__(self, pest, solvername): + self.pest = pest + self.solvername = solvername + + def ScenariosFromExperiments(self, addtoSet): + """Creates new self.Scenarios list using the experiments only. + + Args: + addtoSet (ScenarioSet): the scenarios will be added to this set + Returns: + a ScenarioSet + """ + + # assert isinstance(addtoSet, ScenarioSet) + + scenario_numbers = list(range(len(self.pest.callback_data))) + + prob = 1.0 / len(scenario_numbers) + for exp_num in scenario_numbers: + ##print("Experiment number=", exp_num) + model = self.pest._instance_creation_callback( + exp_num, self.pest.callback_data + ) + opt = pyo.SolverFactory(self.solvername) + results = opt.solve(model) # solves and updates model + ## pyo.check_termination_optimal(results) + ThetaVals = dict() + for theta in self.pest.theta_names: + tvar = eval('model.' + theta) + tval = pyo.value(tvar) + ##print(" theta, tval=", tvar, tval) + ThetaVals[theta] = tval + addtoSet.addone(ParmestScen("ExpScen" + str(exp_num), ThetaVals, prob)) + + def ScenariosFromBootstrap(self, addtoSet, numtomake, seed=None): + """Creates new self.Scenarios list using the experiments only. + + Args: + addtoSet (ScenarioSet): the scenarios will be added to this set + numtomake (int) : number of scenarios to create + """ + + # assert isinstance(addtoSet, ScenarioSet) + + bootstrap_thetas = self.pest.theta_est_bootstrap(numtomake, seed=seed) + addtoSet.append_bootstrap(bootstrap_thetas) From 87aa19df3deebbb0bf987fa6628d6acb3f86a24c Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 9 Jan 2024 14:00:25 -0700 Subject: [PATCH 0686/1797] Moved parmest deprecated files to folder. --- .../examples}/__init__.py | 0 .../examples}/reaction_kinetics/__init__.py | 0 .../simple_reaction_parmest_example.py | 0 .../examples}/reactor_design/__init__.py | 0 .../reactor_design/bootstrap_example.py | 0 .../reactor_design/datarec_example.py | 0 .../reactor_design/leaveNout_example.py | 0 .../likelihood_ratio_example.py | 0 .../multisensor_data_example.py | 0 .../parameter_estimation_example.py | 0 .../examples}/reactor_design/reactor_data.csv | 0 .../reactor_data_multisensor.csv | 0 .../reactor_data_timeseries.csv | 0 .../reactor_design/reactor_design.py | 0 .../reactor_design/timeseries_data_example.py | 0 .../examples}/rooney_biegler/__init__.py | 0 .../rooney_biegler/bootstrap_example.py | 0 .../likelihood_ratio_example.py | 0 .../parameter_estimation_example.py | 0 .../rooney_biegler/rooney_biegler.py | 0 .../rooney_biegler_with_constraint.py | 0 .../examples}/semibatch/__init__.py | 0 .../examples}/semibatch/bootstrap_theta.csv | 0 .../examples}/semibatch/obj_at_theta.csv | 0 .../examples}/semibatch/parallel_example.py | 0 .../semibatch/parameter_estimation_example.py | 0 .../examples}/semibatch/scenario_example.py | 0 .../examples}/semibatch/scenarios.csv | 0 .../examples}/semibatch/semibatch.py | 0 .../parmest.py} | 0 .../scenariocreator.py} | 0 .../parmest/deprecated/tests/__init__.py | 10 + .../parmest/deprecated/tests/scenarios.csv | 11 + .../parmest/deprecated/tests/test_examples.py | 192 ++++ .../parmest/deprecated/tests/test_graphics.py | 68 ++ .../parmest/deprecated/tests/test_parmest.py | 958 ++++++++++++++++++ .../deprecated/tests/test_scenariocreator.py | 146 +++ .../parmest/deprecated/tests/test_solver.py | 75 ++ .../parmest/deprecated/tests/test_utils.py | 68 ++ pyomo/contrib/parmest/parmest.py | 21 +- pyomo/contrib/parmest/scenariocreator.py | 2 +- 41 files changed, 1543 insertions(+), 8 deletions(-) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/__init__.py (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/reaction_kinetics/__init__.py (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/reaction_kinetics/simple_reaction_parmest_example.py (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/reactor_design/__init__.py (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/reactor_design/bootstrap_example.py (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/reactor_design/datarec_example.py (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/reactor_design/leaveNout_example.py (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/reactor_design/likelihood_ratio_example.py (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/reactor_design/multisensor_data_example.py (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/reactor_design/parameter_estimation_example.py (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/reactor_design/reactor_data.csv (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/reactor_design/reactor_data_multisensor.csv (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/reactor_design/reactor_data_timeseries.csv (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/reactor_design/reactor_design.py (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/reactor_design/timeseries_data_example.py (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/rooney_biegler/__init__.py (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/rooney_biegler/bootstrap_example.py (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/rooney_biegler/likelihood_ratio_example.py (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/rooney_biegler/parameter_estimation_example.py (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/rooney_biegler/rooney_biegler.py (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/rooney_biegler/rooney_biegler_with_constraint.py (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/semibatch/__init__.py (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/semibatch/bootstrap_theta.csv (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/semibatch/obj_at_theta.csv (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/semibatch/parallel_example.py (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/semibatch/parameter_estimation_example.py (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/semibatch/scenario_example.py (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/semibatch/scenarios.csv (100%) rename pyomo/contrib/parmest/{examples_deprecated => deprecated/examples}/semibatch/semibatch.py (100%) rename pyomo/contrib/parmest/{parmest_deprecated.py => deprecated/parmest.py} (100%) rename pyomo/contrib/parmest/{scenariocreator_deprecated.py => deprecated/scenariocreator.py} (100%) create mode 100644 pyomo/contrib/parmest/deprecated/tests/__init__.py create mode 100644 pyomo/contrib/parmest/deprecated/tests/scenarios.csv create mode 100644 pyomo/contrib/parmest/deprecated/tests/test_examples.py create mode 100644 pyomo/contrib/parmest/deprecated/tests/test_graphics.py create mode 100644 pyomo/contrib/parmest/deprecated/tests/test_parmest.py create mode 100644 pyomo/contrib/parmest/deprecated/tests/test_scenariocreator.py create mode 100644 pyomo/contrib/parmest/deprecated/tests/test_solver.py create mode 100644 pyomo/contrib/parmest/deprecated/tests/test_utils.py diff --git a/pyomo/contrib/parmest/examples_deprecated/__init__.py b/pyomo/contrib/parmest/deprecated/examples/__init__.py similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/__init__.py rename to pyomo/contrib/parmest/deprecated/examples/__init__.py diff --git a/pyomo/contrib/parmest/examples_deprecated/reaction_kinetics/__init__.py b/pyomo/contrib/parmest/deprecated/examples/reaction_kinetics/__init__.py similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/reaction_kinetics/__init__.py rename to pyomo/contrib/parmest/deprecated/examples/reaction_kinetics/__init__.py diff --git a/pyomo/contrib/parmest/examples_deprecated/reaction_kinetics/simple_reaction_parmest_example.py b/pyomo/contrib/parmest/deprecated/examples/reaction_kinetics/simple_reaction_parmest_example.py similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/reaction_kinetics/simple_reaction_parmest_example.py rename to pyomo/contrib/parmest/deprecated/examples/reaction_kinetics/simple_reaction_parmest_example.py diff --git a/pyomo/contrib/parmest/examples_deprecated/reactor_design/__init__.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/__init__.py similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/reactor_design/__init__.py rename to pyomo/contrib/parmest/deprecated/examples/reactor_design/__init__.py diff --git a/pyomo/contrib/parmest/examples_deprecated/reactor_design/bootstrap_example.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/bootstrap_example.py similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/reactor_design/bootstrap_example.py rename to pyomo/contrib/parmest/deprecated/examples/reactor_design/bootstrap_example.py diff --git a/pyomo/contrib/parmest/examples_deprecated/reactor_design/datarec_example.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/datarec_example.py similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/reactor_design/datarec_example.py rename to pyomo/contrib/parmest/deprecated/examples/reactor_design/datarec_example.py diff --git a/pyomo/contrib/parmest/examples_deprecated/reactor_design/leaveNout_example.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/leaveNout_example.py similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/reactor_design/leaveNout_example.py rename to pyomo/contrib/parmest/deprecated/examples/reactor_design/leaveNout_example.py diff --git a/pyomo/contrib/parmest/examples_deprecated/reactor_design/likelihood_ratio_example.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/likelihood_ratio_example.py similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/reactor_design/likelihood_ratio_example.py rename to pyomo/contrib/parmest/deprecated/examples/reactor_design/likelihood_ratio_example.py diff --git a/pyomo/contrib/parmest/examples_deprecated/reactor_design/multisensor_data_example.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/multisensor_data_example.py similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/reactor_design/multisensor_data_example.py rename to pyomo/contrib/parmest/deprecated/examples/reactor_design/multisensor_data_example.py diff --git a/pyomo/contrib/parmest/examples_deprecated/reactor_design/parameter_estimation_example.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/parameter_estimation_example.py similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/reactor_design/parameter_estimation_example.py rename to pyomo/contrib/parmest/deprecated/examples/reactor_design/parameter_estimation_example.py diff --git a/pyomo/contrib/parmest/examples_deprecated/reactor_design/reactor_data.csv b/pyomo/contrib/parmest/deprecated/examples/reactor_design/reactor_data.csv similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/reactor_design/reactor_data.csv rename to pyomo/contrib/parmest/deprecated/examples/reactor_design/reactor_data.csv diff --git a/pyomo/contrib/parmest/examples_deprecated/reactor_design/reactor_data_multisensor.csv b/pyomo/contrib/parmest/deprecated/examples/reactor_design/reactor_data_multisensor.csv similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/reactor_design/reactor_data_multisensor.csv rename to pyomo/contrib/parmest/deprecated/examples/reactor_design/reactor_data_multisensor.csv diff --git a/pyomo/contrib/parmest/examples_deprecated/reactor_design/reactor_data_timeseries.csv b/pyomo/contrib/parmest/deprecated/examples/reactor_design/reactor_data_timeseries.csv similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/reactor_design/reactor_data_timeseries.csv rename to pyomo/contrib/parmest/deprecated/examples/reactor_design/reactor_data_timeseries.csv diff --git a/pyomo/contrib/parmest/examples_deprecated/reactor_design/reactor_design.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/reactor_design.py similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/reactor_design/reactor_design.py rename to pyomo/contrib/parmest/deprecated/examples/reactor_design/reactor_design.py diff --git a/pyomo/contrib/parmest/examples_deprecated/reactor_design/timeseries_data_example.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/timeseries_data_example.py similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/reactor_design/timeseries_data_example.py rename to pyomo/contrib/parmest/deprecated/examples/reactor_design/timeseries_data_example.py diff --git a/pyomo/contrib/parmest/examples_deprecated/rooney_biegler/__init__.py b/pyomo/contrib/parmest/deprecated/examples/rooney_biegler/__init__.py similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/rooney_biegler/__init__.py rename to pyomo/contrib/parmest/deprecated/examples/rooney_biegler/__init__.py diff --git a/pyomo/contrib/parmest/examples_deprecated/rooney_biegler/bootstrap_example.py b/pyomo/contrib/parmest/deprecated/examples/rooney_biegler/bootstrap_example.py similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/rooney_biegler/bootstrap_example.py rename to pyomo/contrib/parmest/deprecated/examples/rooney_biegler/bootstrap_example.py diff --git a/pyomo/contrib/parmest/examples_deprecated/rooney_biegler/likelihood_ratio_example.py b/pyomo/contrib/parmest/deprecated/examples/rooney_biegler/likelihood_ratio_example.py similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/rooney_biegler/likelihood_ratio_example.py rename to pyomo/contrib/parmest/deprecated/examples/rooney_biegler/likelihood_ratio_example.py diff --git a/pyomo/contrib/parmest/examples_deprecated/rooney_biegler/parameter_estimation_example.py b/pyomo/contrib/parmest/deprecated/examples/rooney_biegler/parameter_estimation_example.py similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/rooney_biegler/parameter_estimation_example.py rename to pyomo/contrib/parmest/deprecated/examples/rooney_biegler/parameter_estimation_example.py diff --git a/pyomo/contrib/parmest/examples_deprecated/rooney_biegler/rooney_biegler.py b/pyomo/contrib/parmest/deprecated/examples/rooney_biegler/rooney_biegler.py similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/rooney_biegler/rooney_biegler.py rename to pyomo/contrib/parmest/deprecated/examples/rooney_biegler/rooney_biegler.py diff --git a/pyomo/contrib/parmest/examples_deprecated/rooney_biegler/rooney_biegler_with_constraint.py b/pyomo/contrib/parmest/deprecated/examples/rooney_biegler/rooney_biegler_with_constraint.py similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/rooney_biegler/rooney_biegler_with_constraint.py rename to pyomo/contrib/parmest/deprecated/examples/rooney_biegler/rooney_biegler_with_constraint.py diff --git a/pyomo/contrib/parmest/examples_deprecated/semibatch/__init__.py b/pyomo/contrib/parmest/deprecated/examples/semibatch/__init__.py similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/semibatch/__init__.py rename to pyomo/contrib/parmest/deprecated/examples/semibatch/__init__.py diff --git a/pyomo/contrib/parmest/examples_deprecated/semibatch/bootstrap_theta.csv b/pyomo/contrib/parmest/deprecated/examples/semibatch/bootstrap_theta.csv similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/semibatch/bootstrap_theta.csv rename to pyomo/contrib/parmest/deprecated/examples/semibatch/bootstrap_theta.csv diff --git a/pyomo/contrib/parmest/examples_deprecated/semibatch/obj_at_theta.csv b/pyomo/contrib/parmest/deprecated/examples/semibatch/obj_at_theta.csv similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/semibatch/obj_at_theta.csv rename to pyomo/contrib/parmest/deprecated/examples/semibatch/obj_at_theta.csv diff --git a/pyomo/contrib/parmest/examples_deprecated/semibatch/parallel_example.py b/pyomo/contrib/parmest/deprecated/examples/semibatch/parallel_example.py similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/semibatch/parallel_example.py rename to pyomo/contrib/parmest/deprecated/examples/semibatch/parallel_example.py diff --git a/pyomo/contrib/parmest/examples_deprecated/semibatch/parameter_estimation_example.py b/pyomo/contrib/parmest/deprecated/examples/semibatch/parameter_estimation_example.py similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/semibatch/parameter_estimation_example.py rename to pyomo/contrib/parmest/deprecated/examples/semibatch/parameter_estimation_example.py diff --git a/pyomo/contrib/parmest/examples_deprecated/semibatch/scenario_example.py b/pyomo/contrib/parmest/deprecated/examples/semibatch/scenario_example.py similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/semibatch/scenario_example.py rename to pyomo/contrib/parmest/deprecated/examples/semibatch/scenario_example.py diff --git a/pyomo/contrib/parmest/examples_deprecated/semibatch/scenarios.csv b/pyomo/contrib/parmest/deprecated/examples/semibatch/scenarios.csv similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/semibatch/scenarios.csv rename to pyomo/contrib/parmest/deprecated/examples/semibatch/scenarios.csv diff --git a/pyomo/contrib/parmest/examples_deprecated/semibatch/semibatch.py b/pyomo/contrib/parmest/deprecated/examples/semibatch/semibatch.py similarity index 100% rename from pyomo/contrib/parmest/examples_deprecated/semibatch/semibatch.py rename to pyomo/contrib/parmest/deprecated/examples/semibatch/semibatch.py diff --git a/pyomo/contrib/parmest/parmest_deprecated.py b/pyomo/contrib/parmest/deprecated/parmest.py similarity index 100% rename from pyomo/contrib/parmest/parmest_deprecated.py rename to pyomo/contrib/parmest/deprecated/parmest.py diff --git a/pyomo/contrib/parmest/scenariocreator_deprecated.py b/pyomo/contrib/parmest/deprecated/scenariocreator.py similarity index 100% rename from pyomo/contrib/parmest/scenariocreator_deprecated.py rename to pyomo/contrib/parmest/deprecated/scenariocreator.py diff --git a/pyomo/contrib/parmest/deprecated/tests/__init__.py b/pyomo/contrib/parmest/deprecated/tests/__init__.py new file mode 100644 index 00000000000..d93cfd77b3c --- /dev/null +++ b/pyomo/contrib/parmest/deprecated/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/parmest/deprecated/tests/scenarios.csv b/pyomo/contrib/parmest/deprecated/tests/scenarios.csv new file mode 100644 index 00000000000..22f9a651bc3 --- /dev/null +++ b/pyomo/contrib/parmest/deprecated/tests/scenarios.csv @@ -0,0 +1,11 @@ +Name,Probability,k1,k2,E1,E2 +ExpScen0,0.1,25.800350800448314,14.14421520525348,31505.74905064048,35000.0 +ExpScen1,0.1,25.128373083865036,149.99999951481198,31452.336651974012,41938.781301641866 +ExpScen2,0.1,22.225574065344002,130.92739780265404,30948.669111672247,41260.15420929141 +ExpScen3,0.1,100.0,149.99999970011854,35182.73130744844,41444.52600373733 +ExpScen4,0.1,82.99114366189944,45.95424665995078,34810.857217141674,38300.633349887314 +ExpScen5,0.1,100.0,150.0,35142.20219150486,41495.41105795494 +ExpScen6,0.1,2.8743643265301118,149.99999477176598,25000.0,41431.61195969211 +ExpScen7,0.1,2.754580914035567,14.381786096822475,25000.0,35000.0 +ExpScen8,0.1,2.8743643265301118,149.99999477176598,25000.0,41431.61195969211 +ExpScen9,0.1,2.669780822294865,150.0,25000.0,41514.7476113499 diff --git a/pyomo/contrib/parmest/deprecated/tests/test_examples.py b/pyomo/contrib/parmest/deprecated/tests/test_examples.py new file mode 100644 index 00000000000..67e06130384 --- /dev/null +++ b/pyomo/contrib/parmest/deprecated/tests/test_examples.py @@ -0,0 +1,192 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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.contrib.parmest.parmest as parmest +from pyomo.contrib.parmest.graphics import matplotlib_available, seaborn_available +from pyomo.opt import SolverFactory + +ipopt_available = SolverFactory("ipopt").available() + + +@unittest.skipIf( + not parmest.parmest_available, + "Cannot test parmest: required dependencies are missing", +) +@unittest.skipIf(not ipopt_available, "The 'ipopt' solver is not available") +class TestRooneyBieglerExamples(unittest.TestCase): + @classmethod + def setUpClass(self): + pass + + @classmethod + def tearDownClass(self): + pass + + def test_model(self): + from pyomo.contrib.parmest.examples.rooney_biegler import rooney_biegler + + rooney_biegler.main() + + def test_model_with_constraint(self): + from pyomo.contrib.parmest.examples.rooney_biegler import ( + rooney_biegler_with_constraint, + ) + + rooney_biegler_with_constraint.main() + + @unittest.skipUnless(seaborn_available, "test requires seaborn") + def test_parameter_estimation_example(self): + from pyomo.contrib.parmest.examples.rooney_biegler import ( + parameter_estimation_example, + ) + + parameter_estimation_example.main() + + @unittest.skipUnless(seaborn_available, "test requires seaborn") + def test_bootstrap_example(self): + from pyomo.contrib.parmest.examples.rooney_biegler import bootstrap_example + + bootstrap_example.main() + + @unittest.skipUnless(seaborn_available, "test requires seaborn") + def test_likelihood_ratio_example(self): + from pyomo.contrib.parmest.examples.rooney_biegler import ( + likelihood_ratio_example, + ) + + likelihood_ratio_example.main() + + +@unittest.skipIf( + not parmest.parmest_available, + "Cannot test parmest: required dependencies are missing", +) +@unittest.skipIf(not ipopt_available, "The 'ipopt' solver is not available") +class TestReactionKineticsExamples(unittest.TestCase): + @classmethod + def setUpClass(self): + pass + + @classmethod + def tearDownClass(self): + pass + + def test_example(self): + from pyomo.contrib.parmest.examples.reaction_kinetics import ( + simple_reaction_parmest_example, + ) + + simple_reaction_parmest_example.main() + + +@unittest.skipIf( + not parmest.parmest_available, + "Cannot test parmest: required dependencies are missing", +) +@unittest.skipIf(not ipopt_available, "The 'ipopt' solver is not available") +class TestSemibatchExamples(unittest.TestCase): + @classmethod + def setUpClass(self): + pass + + @classmethod + def tearDownClass(self): + pass + + def test_model(self): + from pyomo.contrib.parmest.examples.semibatch import semibatch + + semibatch.main() + + def test_parameter_estimation_example(self): + from pyomo.contrib.parmest.examples.semibatch import ( + parameter_estimation_example, + ) + + parameter_estimation_example.main() + + def test_scenario_example(self): + from pyomo.contrib.parmest.examples.semibatch import scenario_example + + scenario_example.main() + + +@unittest.skipIf( + not parmest.parmest_available, + "Cannot test parmest: required dependencies are missing", +) +@unittest.skipIf(not ipopt_available, "The 'ipopt' solver is not available") +class TestReactorDesignExamples(unittest.TestCase): + @classmethod + def setUpClass(self): + pass + + @classmethod + def tearDownClass(self): + pass + + @unittest.pytest.mark.expensive + def test_model(self): + from pyomo.contrib.parmest.examples.reactor_design import reactor_design + + reactor_design.main() + + def test_parameter_estimation_example(self): + from pyomo.contrib.parmest.examples.reactor_design import ( + parameter_estimation_example, + ) + + parameter_estimation_example.main() + + @unittest.skipUnless(seaborn_available, "test requires seaborn") + def test_bootstrap_example(self): + from pyomo.contrib.parmest.examples.reactor_design import bootstrap_example + + bootstrap_example.main() + + @unittest.pytest.mark.expensive + def test_likelihood_ratio_example(self): + from pyomo.contrib.parmest.examples.reactor_design import ( + likelihood_ratio_example, + ) + + likelihood_ratio_example.main() + + @unittest.pytest.mark.expensive + def test_leaveNout_example(self): + from pyomo.contrib.parmest.examples.reactor_design import leaveNout_example + + leaveNout_example.main() + + def test_timeseries_data_example(self): + from pyomo.contrib.parmest.examples.reactor_design import ( + timeseries_data_example, + ) + + timeseries_data_example.main() + + def test_multisensor_data_example(self): + from pyomo.contrib.parmest.examples.reactor_design import ( + multisensor_data_example, + ) + + multisensor_data_example.main() + + @unittest.skipUnless(matplotlib_available, "test requires matplotlib") + def test_datarec_example(self): + from pyomo.contrib.parmest.examples.reactor_design import datarec_example + + datarec_example.main() + + +if __name__ == "__main__": + unittest.main() diff --git a/pyomo/contrib/parmest/deprecated/tests/test_graphics.py b/pyomo/contrib/parmest/deprecated/tests/test_graphics.py new file mode 100644 index 00000000000..c18659e9948 --- /dev/null +++ b/pyomo/contrib/parmest/deprecated/tests/test_graphics.py @@ -0,0 +1,68 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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, + pandas as pd, + pandas_available, + scipy, + scipy_available, + matplotlib, + matplotlib_available, +) + +import platform + +is_osx = platform.mac_ver()[0] != '' + +import pyomo.common.unittest as unittest +import sys +import os + +import pyomo.contrib.parmest.parmest as parmest +import pyomo.contrib.parmest.graphics as graphics + +testdir = os.path.dirname(os.path.abspath(__file__)) + + +@unittest.skipIf( + not parmest.parmest_available, + "Cannot test parmest: required dependencies are missing", +) +@unittest.skipIf( + not graphics.imports_available, "parmest.graphics imports are unavailable" +) +@unittest.skipIf( + is_osx, + "Disabling graphics tests on OSX due to issue in Matplotlib, see Pyomo PR #1337", +) +class TestGraphics(unittest.TestCase): + def setUp(self): + self.A = pd.DataFrame( + np.random.randint(0, 100, size=(100, 4)), columns=list('ABCD') + ) + self.B = pd.DataFrame( + np.random.randint(0, 100, size=(100, 4)), columns=list('ABCD') + ) + + def test_pairwise_plot(self): + graphics.pairwise_plot(self.A, alpha=0.8, distributions=['Rect', 'MVN', 'KDE']) + + def test_grouped_boxplot(self): + graphics.grouped_boxplot(self.A, self.B, normalize=True, group_names=['A', 'B']) + + def test_grouped_violinplot(self): + graphics.grouped_violinplot(self.A, self.B) + + +if __name__ == '__main__': + unittest.main() diff --git a/pyomo/contrib/parmest/deprecated/tests/test_parmest.py b/pyomo/contrib/parmest/deprecated/tests/test_parmest.py new file mode 100644 index 00000000000..7e692989b0c --- /dev/null +++ b/pyomo/contrib/parmest/deprecated/tests/test_parmest.py @@ -0,0 +1,958 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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, + pandas as pd, + pandas_available, + scipy, + scipy_available, + matplotlib, + matplotlib_available, +) + +import platform + +is_osx = platform.mac_ver()[0] != "" + +import pyomo.common.unittest as unittest +import sys +import os +import subprocess +from itertools import product + +import pyomo.contrib.parmest.parmest as parmest +import pyomo.contrib.parmest.graphics as graphics +import pyomo.contrib.parmest as parmestbase +import pyomo.environ as pyo +import pyomo.dae as dae + +from pyomo.opt import SolverFactory + +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 + +testdir = os.path.dirname(os.path.abspath(__file__)) + + +@unittest.skipIf( + not parmest.parmest_available, + "Cannot test parmest: required dependencies are missing", +) +@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") +class TestRooneyBiegler(unittest.TestCase): + def setUp(self): + from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import ( + rooney_biegler_model, + ) + + # 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"], + ) + + theta_names = ["asymptote", "rate_constant"] + + def SSE(model, data): + expr = sum( + (data.y[i] - model.response_function[data.hour[i]]) ** 2 + for i in data.index + ) + return expr + + solver_options = {"tol": 1e-8} + + self.data = data + self.pest = parmest.Estimator( + rooney_biegler_model, + data, + theta_names, + SSE, + solver_options=solver_options, + tee=True, + ) + + def test_theta_est(self): + objval, thetavals = self.pest.theta_est() + + self.assertAlmostEqual(objval, 4.3317112, places=2) + self.assertAlmostEqual( + thetavals["asymptote"], 19.1426, places=2 + ) # 19.1426 from the paper + self.assertAlmostEqual( + thetavals["rate_constant"], 0.5311, places=2 + ) # 0.5311 from the paper + + @unittest.skipIf( + not graphics.imports_available, "parmest.graphics imports are unavailable" + ) + def test_bootstrap(self): + objval, thetavals = self.pest.theta_est() + + num_bootstraps = 10 + theta_est = self.pest.theta_est_bootstrap(num_bootstraps, return_samples=True) + + 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"] + + # apply confidence region test + 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) + self.assertTrue(CR[0.75].sum() == 7) + self.assertTrue(CR[1.0].sum() == 10) # all true + + graphics.pairwise_plot(theta_est) + graphics.pairwise_plot(theta_est, thetavals) + graphics.pairwise_plot(theta_est, thetavals, 0.8, ["MVN", "KDE", "Rect"]) + + @unittest.skipIf( + not graphics.imports_available, "parmest.graphics imports are unavailable" + ) + def test_likelihood_ratio(self): + objval, thetavals = self.pest.theta_est() + + asym = np.arange(10, 30, 2) + rate = np.arange(0, 1.5, 0.25) + theta_vals = pd.DataFrame( + list(product(asym, rate)), columns=self.pest._return_theta_names() + ) + + obj_at_theta = self.pest.objective_at_theta(theta_vals) + + LR = self.pest.likelihood_ratio_test(obj_at_theta, objval, [0.8, 0.9, 1.0]) + + self.assertTrue(set(LR.columns) >= set([0.8, 0.9, 1.0])) + self.assertTrue(LR[0.8].sum() == 6) + self.assertTrue(LR[0.9].sum() == 10) + self.assertTrue(LR[1.0].sum() == 60) # all true + + graphics.pairwise_plot(LR, thetavals, 0.8) + + def test_leaveNout(self): + lNo_theta = self.pest.theta_est_leaveNout(1) + self.assertTrue(lNo_theta.shape == (6, 2)) + + results = self.pest.leaveNout_bootstrap_test( + 1, None, 3, "Rect", [0.5, 1.0], seed=5436 + ) + self.assertTrue(len(results) == 6) # 6 lNo samples + i = 1 + samples = results[i][0] # list of N samples that are left out + lno_theta = results[i][1] + bootstrap_theta = results[i][2] + self.assertTrue(samples == [1]) # sample 1 was left out + self.assertTrue(lno_theta.shape[0] == 1) # lno estimate for sample 1 + self.assertTrue(set(lno_theta.columns) >= set([0.5, 1.0])) + self.assertTrue(lno_theta[1.0].sum() == 1) # all true + self.assertTrue(bootstrap_theta.shape[0] == 3) # bootstrap for sample 1 + self.assertTrue(bootstrap_theta[1.0].sum() == 3) # all true + + def test_diagnostic_mode(self): + self.pest.diagnostic_mode = True + + objval, thetavals = self.pest.theta_est() + + asym = np.arange(10, 30, 2) + rate = np.arange(0, 1.5, 0.25) + theta_vals = pd.DataFrame( + list(product(asym, rate)), columns=self.pest._return_theta_names() + ) + + obj_at_theta = self.pest.objective_at_theta(theta_vals) + + self.pest.diagnostic_mode = False + + @unittest.skip("Presently having trouble with mpiexec on appveyor") + def test_parallel_parmest(self): + """use mpiexec and mpi4py""" + p = str(parmestbase.__path__) + l = p.find("'") + r = p.find("'", l + 1) + parmestpath = p[l + 1 : r] + rbpath = ( + parmestpath + + os.sep + + "examples" + + os.sep + + "rooney_biegler" + + os.sep + + "rooney_biegler_parmest.py" + ) + rbpath = os.path.abspath(rbpath) # paranoia strikes deep... + rlist = ["mpiexec", "--allow-run-as-root", "-n", "2", sys.executable, rbpath] + if sys.version_info >= (3, 5): + ret = subprocess.run(rlist) + retcode = ret.returncode + else: + retcode = subprocess.call(rlist) + assert retcode == 0 + + @unittest.skip("Most folks don't have k_aug installed") + def test_theta_k_aug_for_Hessian(self): + # this will fail if k_aug is not installed + objval, thetavals, Hessian = self.pest.theta_est(solver="k_aug") + self.assertAlmostEqual(objval, 4.4675, places=2) + + @unittest.skipIf(not pynumero_ASL_available, "pynumero ASL is not available") + @unittest.skipIf( + not parmest.inverse_reduced_hessian_available, + "Cannot test covariance matrix: required ASL dependency is missing", + ) + def test_theta_est_cov(self): + objval, thetavals, cov = self.pest.theta_est(calc_cov=True, cov_n=6) + + self.assertAlmostEqual(objval, 4.3317112, places=2) + self.assertAlmostEqual( + thetavals["asymptote"], 19.1426, places=2 + ) # 19.1426 from the paper + self.assertAlmostEqual( + thetavals["rate_constant"], 0.5311, places=2 + ) # 0.5311 from the paper + + # Covariance matrix + self.assertAlmostEqual( + cov.iloc[0, 0], 6.30579403, places=2 + ) # 6.22864 from paper + self.assertAlmostEqual( + cov.iloc[0, 1], -0.4395341, places=2 + ) # -0.4322 from paper + self.assertAlmostEqual( + cov.iloc[1, 0], -0.4395341, places=2 + ) # -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 + 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 + t: independent variable [hours] + + 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() + + # 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 + ) + theta_hat = sol.x + + self.assertAlmostEqual( + theta_hat[0], 19.1426, places=2 + ) # 19.1426 from the paper + self.assertAlmostEqual(theta_hat[1], 0.5311, places=2) # 0.5311 from the paper + + # calculate residuals + r = residual(theta_hat, t, y) + + # calculate variance of the residuals + # -2 because there are 2 fitted parameters + sigre = np.matmul(r.T, r / (len(y) - 2)) + + # approximate covariance + # Need to divide by 2 because optimize.least_squares scaled the objective by 1/2 + cov = sigre * np.linalg.inv(np.matmul(sol.jac.T, sol.jac)) + + self.assertAlmostEqual(cov[0, 0], 6.22864, places=2) # 6.22864 from paper + self.assertAlmostEqual(cov[0, 1], -0.4322, places=2) # -0.4322 from paper + self.assertAlmostEqual(cov[1, 0], -0.4322, places=2) # -0.4322 from paper + 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() + + # define initial guess + theta_guess = np.array([15, 0.5]) + + theta_hat, cov = scipy.optimize.curve_fit(model, t, y, p0=theta_guess) + + self.assertAlmostEqual( + theta_hat[0], 19.1426, places=2 + ) # 19.1426 from the paper + self.assertAlmostEqual(theta_hat[1], 0.5311, places=2) # 0.5311 from the paper + + self.assertAlmostEqual(cov[0, 0], 6.22864, places=2) # 6.22864 from paper + self.assertAlmostEqual(cov[0, 1], -0.4322, places=2) # -0.4322 from paper + self.assertAlmostEqual(cov[1, 0], -0.4322, places=2) # -0.4322 from paper + self.assertAlmostEqual(cov[1, 1], 0.04124, places=2) # 0.04124 from paper + + +@unittest.skipIf( + not parmest.parmest_available, + "Cannot test parmest: required dependencies are missing", +) +@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") +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"], + ) + + def rooney_biegler_params(data): + model = pyo.ConcreteModel() + + model.asymptote = pyo.Param(initialize=15, mutable=True) + model.rate_constant = pyo.Param(initialize=0.5, mutable=True) + + def response_rule(m, h): + expr = m.asymptote * (1 - pyo.exp(-m.rate_constant * h)) + return expr + + model.response_function = pyo.Expression(data.hour, rule=response_rule) + + return model + + def rooney_biegler_indexed_params(data): + model = pyo.ConcreteModel() + + model.param_names = pyo.Set(initialize=["asymptote", "rate_constant"]) + model.theta = pyo.Param( + model.param_names, + 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) + ) + return expr + + model.response_function = pyo.Expression(data.hour, rule=response_rule) + + return model + + def rooney_biegler_vars(data): + model = pyo.ConcreteModel() + + model.asymptote = pyo.Var(initialize=15) + model.rate_constant = pyo.Var(initialize=0.5) + model.asymptote.fixed = True # parmest will unfix theta variables + model.rate_constant.fixed = True + + def response_rule(m, h): + expr = m.asymptote * (1 - pyo.exp(-m.rate_constant * h)) + return expr + + model.response_function = pyo.Expression(data.hour, rule=response_rule) + + return model + + def rooney_biegler_indexed_vars(data): + model = pyo.ConcreteModel() + + model.var_names = pyo.Set(initialize=["asymptote", "rate_constant"]) + model.theta = pyo.Var( + model.var_names, initialize={"asymptote": 15, "rate_constant": 0.5} + ) + model.theta[ + "asymptote" + ].fixed = ( + True # parmest will unfix theta variables, even when they are indexed + ) + model.theta["rate_constant"].fixed = True + + def response_rule(m, h): + expr = m.theta["asymptote"] * ( + 1 - pyo.exp(-m.theta["rate_constant"] * h) + ) + return expr + + model.response_function = pyo.Expression(data.hour, rule=response_rule) + + return model + + def SSE(model, data): + expr = sum( + (data.y[i] - model.response_function[data.hour[i]]) ** 2 + for i in data.index + ) + return expr + + self.objective_function = SSE + + 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_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_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_str_index": { + "model": rooney_biegler_indexed_vars, + "theta_names": ["theta[asymptote]", "theta[rate_constant]"], + "theta_vals": theta_vals_index, + }, + } + + @unittest.skipIf(not pynumero_ASL_available, "pynumero ASL is not available") + @unittest.skipIf( + not parmest.inverse_reduced_hessian_available, + "Cannot test covariance matrix: required ASL dependency is missing", + ) + def test_parmest_basics(self): + for model_type, parmest_input in self.input.items(): + pest = parmest.Estimator( + parmest_input["model"], + self.data, + parmest_input["theta_names"], + self.objective_function, + ) + + objval, thetavals, cov = pest.theta_est(calc_cov=True, cov_n=6) + + self.assertAlmostEqual(objval, 4.3317112, places=2) + self.assertAlmostEqual( + cov.iloc[0, 0], 6.30579403, places=2 + ) # 6.22864 from paper + self.assertAlmostEqual( + cov.iloc[0, 1], -0.4395341, places=2 + ) # -0.4322 from paper + self.assertAlmostEqual( + cov.iloc[1, 0], -0.4395341, places=2 + ) # -0.4322 from paper + self.assertAlmostEqual( + 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) + + 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"], + self.data, + parmest_input["theta_names"], + self.objective_function, + ) + + objval, thetavals, cov = pest.theta_est(calc_cov=True, cov_n=6) + + self.assertAlmostEqual(objval, 4.3317112, places=2) + self.assertAlmostEqual( + cov.iloc[0, 0], 6.30579403, places=2 + ) # 6.22864 from paper + self.assertAlmostEqual( + cov.iloc[0, 1], -0.4395341, places=2 + ) # -0.4322 from paper + self.assertAlmostEqual( + cov.iloc[1, 0], -0.4395341, places=2 + ) # -0.4322 from paper + self.assertAlmostEqual( + cov.iloc[1, 1], 0.04193591, places=2 + ) # 0.04124 from paper + + obj_at_theta = pest.objective_at_theta( + parmest_input["theta_vals"], initialize_parmest_model=True + ) + + 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"], + self.data, + parmest_input["theta_names"], + self.objective_function, + ) + + obj_at_theta = pest.objective_at_theta( + parmest_input["theta_vals"], initialize_parmest_model=True + ) + + objval, thetavals, cov = pest.theta_est(calc_cov=True, cov_n=6) + + self.assertAlmostEqual(objval, 4.3317112, places=2) + self.assertAlmostEqual( + cov.iloc[0, 0], 6.30579403, places=2 + ) # 6.22864 from paper + self.assertAlmostEqual( + cov.iloc[0, 1], -0.4395341, places=2 + ) # -0.4322 from paper + self.assertAlmostEqual( + cov.iloc[1, 0], -0.4395341, places=2 + ) # -0.4322 from paper + self.assertAlmostEqual( + cov.iloc[1, 1], 0.04193591, places=2 + ) # 0.04124 from paper + + 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"], + self.data, + parmest_input["theta_names"], + self.objective_function, + ) + + obj_at_theta = pest.objective_at_theta(initialize_parmest_model=True) + + objval, thetavals, cov = pest.theta_est(calc_cov=True, cov_n=6) + + self.assertAlmostEqual(objval, 4.3317112, places=2) + self.assertAlmostEqual( + cov.iloc[0, 0], 6.30579403, places=2 + ) # 6.22864 from paper + self.assertAlmostEqual( + cov.iloc[0, 1], -0.4395341, places=2 + ) # -0.4322 from paper + self.assertAlmostEqual( + cov.iloc[1, 0], -0.4395341, places=2 + ) # -0.4322 from paper + self.assertAlmostEqual( + cov.iloc[1, 1], 0.04193591, places=2 + ) # 0.04124 from paper + + +@unittest.skipIf( + not parmest.parmest_available, + "Cannot test parmest: required dependencies are missing", +) +@unittest.skipIf(not ipopt_available, "The 'ipopt' solver is not available") +class TestReactorDesign(unittest.TestCase): + def setUp(self): + from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( + reactor_design_model, + ) + + # Data from the design + data = pd.DataFrame( + data=[ + [1.05, 10000, 3458.4, 1060.8, 1683.9, 1898.5], + [1.10, 10000, 3535.1, 1064.8, 1613.3, 1893.4], + [1.15, 10000, 3609.1, 1067.8, 1547.5, 1887.8], + [1.20, 10000, 3680.7, 1070.0, 1486.1, 1881.6], + [1.25, 10000, 3750.0, 1071.4, 1428.6, 1875.0], + [1.30, 10000, 3817.1, 1072.2, 1374.6, 1868.0], + [1.35, 10000, 3882.2, 1072.4, 1324.0, 1860.7], + [1.40, 10000, 3945.4, 1072.1, 1276.3, 1853.1], + [1.45, 10000, 4006.7, 1071.3, 1231.4, 1845.3], + [1.50, 10000, 4066.4, 1070.1, 1189.0, 1837.3], + [1.55, 10000, 4124.4, 1068.5, 1148.9, 1829.1], + [1.60, 10000, 4180.9, 1066.5, 1111.0, 1820.8], + [1.65, 10000, 4235.9, 1064.3, 1075.0, 1812.4], + [1.70, 10000, 4289.5, 1061.8, 1040.9, 1803.9], + [1.75, 10000, 4341.8, 1059.0, 1008.5, 1795.3], + [1.80, 10000, 4392.8, 1056.0, 977.7, 1786.7], + [1.85, 10000, 4442.6, 1052.8, 948.4, 1778.1], + [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"], + ) + + 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 + ) + return expr + + solver_options = {"max_iter": 6000} + + self.pest = parmest.Estimator( + reactor_design_model, data, theta_names, SSE, solver_options=solver_options + ) + + 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) + + def test_return_values(self): + objval, thetavals, data_rec = self.pest.theta_est( + return_values=["ca", "cb", "cc", "cd", "caf"] + ) + self.assertAlmostEqual(data_rec["cc"].loc[18], 893.84924, places=3) + + +@unittest.skipIf( + not parmest.parmest_available, + "Cannot test parmest: required dependencies are missing", +) +@unittest.skipIf(not ipopt_available, "The 'ipopt' solver is not available") +class TestReactorDesign_DAE(unittest.TestCase): + # Based on a reactor example in `Chemical Reactor Analysis and Design Fundamentals`, + # https://sites.engineering.ucsb.edu/~jbraw/chemreacfun/ + # https://sites.engineering.ucsb.edu/~jbraw/chemreacfun/fig-html/appendix/fig-A-10.html + + def setUp(self): + def ABC_model(data): + ca_meas = data["ca"] + cb_meas = data["cb"] + cc_meas = data["cc"] + + if isinstance(data, pd.DataFrame): + meas_t = data.index # time index + else: # dictionary + meas_t = list(ca_meas.keys()) # nested dictionary + + ca0 = 1.0 + cb0 = 0.0 + cc0 = 0.0 + + m = pyo.ConcreteModel() + + m.k1 = pyo.Var(initialize=0.5, bounds=(1e-4, 10)) + m.k2 = pyo.Var(initialize=3.0, bounds=(1e-4, 10)) + + m.time = dae.ContinuousSet(bounds=(0.0, 5.0), initialize=meas_t) + + # initialization and bounds + m.ca = pyo.Var(m.time, initialize=ca0, bounds=(-1e-3, ca0 + 1e-3)) + m.cb = pyo.Var(m.time, initialize=cb0, bounds=(-1e-3, ca0 + 1e-3)) + m.cc = pyo.Var(m.time, initialize=cc0, bounds=(-1e-3, ca0 + 1e-3)) + + m.dca = dae.DerivativeVar(m.ca, wrt=m.time) + m.dcb = dae.DerivativeVar(m.cb, wrt=m.time) + m.dcc = dae.DerivativeVar(m.cc, wrt=m.time) + + def _dcarate(m, t): + if t == 0: + return pyo.Constraint.Skip + else: + return m.dca[t] == -m.k1 * m.ca[t] + + m.dcarate = pyo.Constraint(m.time, rule=_dcarate) + + def _dcbrate(m, t): + if t == 0: + return pyo.Constraint.Skip + else: + return m.dcb[t] == m.k1 * m.ca[t] - m.k2 * m.cb[t] + + m.dcbrate = pyo.Constraint(m.time, rule=_dcbrate) + + def _dccrate(m, t): + if t == 0: + return pyo.Constraint.Skip + else: + return m.dcc[t] == m.k2 * m.cb[t] + + m.dccrate = pyo.Constraint(m.time, rule=_dccrate) + + def ComputeFirstStageCost_rule(m): + return 0 + + m.FirstStageCost = pyo.Expression(rule=ComputeFirstStageCost_rule) + + def ComputeSecondStageCost_rule(m): + return sum( + (m.ca[t] - ca_meas[t]) ** 2 + + (m.cb[t] - cb_meas[t]) ** 2 + + (m.cc[t] - cc_meas[t]) ** 2 + for t in meas_t + ) + + m.SecondStageCost = pyo.Expression(rule=ComputeSecondStageCost_rule) + + def total_cost_rule(model): + return model.FirstStageCost + model.SecondStageCost + + m.Total_Cost_Objective = pyo.Objective( + rule=total_cost_rule, sense=pyo.minimize + ) + + disc = pyo.TransformationFactory("dae.collocation") + disc.apply_to(m, nfe=20, ncp=2) + + return m + + # This example tests data formatted in 3 ways + # Each format holds 1 scenario + # 1. dataframe with time index + # 2. nested dictionary {ca: {t, val pairs}, ... } + data = [ + [0.000, 0.957, -0.031, -0.015], + [0.263, 0.557, 0.330, 0.044], + [0.526, 0.342, 0.512, 0.156], + [0.789, 0.224, 0.499, 0.310], + [1.053, 0.123, 0.428, 0.454], + [1.316, 0.079, 0.396, 0.556], + [1.579, 0.035, 0.303, 0.651], + [1.842, 0.029, 0.287, 0.658], + [2.105, 0.025, 0.221, 0.750], + [2.368, 0.017, 0.148, 0.854], + [2.632, -0.002, 0.182, 0.845], + [2.895, 0.009, 0.116, 0.893], + [3.158, -0.023, 0.079, 0.942], + [3.421, 0.006, 0.078, 0.899], + [3.684, 0.016, 0.059, 0.942], + [3.947, 0.014, 0.036, 0.991], + [4.211, -0.009, 0.014, 0.988], + [4.474, -0.030, 0.036, 0.941], + [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_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)}, + } + + 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) + + # Estimator object with multiple scenarios + self.pest_df_multiple = parmest.Estimator( + ABC_model, [data_df, data_df], theta_names + ) + self.pest_dict_multiple = parmest.Estimator( + ABC_model, [data_dict, data_dict], theta_names + ) + + # Create an instance of the model + self.m_df = ABC_model(data_df) + self.m_dict = ABC_model(data_dict) + + def test_dataformats(self): + obj1, theta1 = self.pest_df.theta_est() + 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) + + 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) + + 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"] + ) + obj2, theta2, return_vals2 = self.pest_dict_multiple.theta_est( + 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) + + def test_covariance(self): + from pyomo.contrib.interior_point.inverse_reduced_hessian import ( + inv_reduced_hessian_barrier, + ) + + # Number of datapoints. + # 3 data components (ca, cb, cc), 20 timesteps, 1 scenario = 60 + # In this example, this is the number of data points in data_df, but that's + # only because the data is indexed by time and contains no additional information. + n = 60 + + # Compute covariance using parmest + obj, theta, cov = self.pest_df.theta_est(calc_cov=True, cov_n=n) + + # Compute covariance using interior_point + vars_list = [self.m_df.k1, self.m_df.k2] + solve_result, inv_red_hes = inv_reduced_hessian_barrier( + self.m_df, independent_variables=vars_list, tee=True + ) + 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_diff = (cov - cov_interior_point).abs().sum().sum() + + self.assertTrue(cov.loc["k1", "k1"] > 0) + self.assertTrue(cov.loc["k2", "k2"] > 0) + self.assertAlmostEqual(cov_diff, 0, places=6) + + +@unittest.skipIf( + not parmest.parmest_available, + "Cannot test parmest: required dependencies are missing", +) +@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") +class TestSquareInitialization_RooneyBiegler(unittest.TestCase): + def setUp(self): + from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler_with_constraint import ( + rooney_biegler_model_with_constraint, + ) + + # 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"], + ) + + theta_names = ["asymptote", "rate_constant"] + + def SSE(model, data): + expr = sum( + (data.y[i] - model.response_function[data.hour[i]]) ** 2 + for i in data.index + ) + return expr + + solver_options = {"tol": 1e-8} + + self.data = data + self.pest = parmest.Estimator( + rooney_biegler_model_with_constraint, + data, + theta_names, + SSE, + solver_options=solver_options, + tee=True, + ) + + def test_theta_est_with_square_initialization(self): + obj_init = self.pest.objective_at_theta(initialize_parmest_model=True) + objval, thetavals = self.pest.theta_est() + + self.assertAlmostEqual(objval, 4.3317112, places=2) + self.assertAlmostEqual( + thetavals["asymptote"], 19.1426, places=2 + ) # 19.1426 from the paper + self.assertAlmostEqual( + 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"] + ) + obj_init = self.pest.objective_at_theta( + theta_values=theta_vals_init, initialize_parmest_model=True + ) + objval, thetavals = self.pest.theta_est() + self.assertAlmostEqual(objval, 4.3317112, places=2) + self.assertAlmostEqual( + thetavals["asymptote"], 19.1426, places=2 + ) # 19.1426 from the paper + self.assertAlmostEqual( + thetavals["rate_constant"], 0.5311, places=2 + ) # 0.5311 from the paper + + def test_theta_est_with_square_initialization_diagnostic_mode_true(self): + self.pest.diagnostic_mode = True + obj_init = self.pest.objective_at_theta(initialize_parmest_model=True) + objval, thetavals = self.pest.theta_est() + + self.assertAlmostEqual(objval, 4.3317112, places=2) + self.assertAlmostEqual( + thetavals["asymptote"], 19.1426, places=2 + ) # 19.1426 from the paper + self.assertAlmostEqual( + thetavals["rate_constant"], 0.5311, places=2 + ) # 0.5311 from the paper + + self.pest.diagnostic_mode = False + + +if __name__ == "__main__": + unittest.main() diff --git a/pyomo/contrib/parmest/deprecated/tests/test_scenariocreator.py b/pyomo/contrib/parmest/deprecated/tests/test_scenariocreator.py new file mode 100644 index 00000000000..22a851ae32e --- /dev/null +++ b/pyomo/contrib/parmest/deprecated/tests/test_scenariocreator.py @@ -0,0 +1,146 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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 pandas as pd, pandas_available + +uuid_available = True +try: + import uuid +except: + uuid_available = False + +import pyomo.common.unittest as unittest +import os +import pyomo.contrib.parmest.parmest as parmest +import pyomo.contrib.parmest.scenariocreator as sc +import pyomo.environ as pyo +from pyomo.environ import SolverFactory + +ipopt_available = SolverFactory("ipopt").available() + +testdir = os.path.dirname(os.path.abspath(__file__)) + + +@unittest.skipIf( + not parmest.parmest_available, + "Cannot test parmest: required dependencies are missing", +) +@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") +class TestScenarioReactorDesign(unittest.TestCase): + def setUp(self): + from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( + reactor_design_model, + ) + + # Data from the design + data = pd.DataFrame( + data=[ + [1.05, 10000, 3458.4, 1060.8, 1683.9, 1898.5], + [1.10, 10000, 3535.1, 1064.8, 1613.3, 1893.4], + [1.15, 10000, 3609.1, 1067.8, 1547.5, 1887.8], + [1.20, 10000, 3680.7, 1070.0, 1486.1, 1881.6], + [1.25, 10000, 3750.0, 1071.4, 1428.6, 1875.0], + [1.30, 10000, 3817.1, 1072.2, 1374.6, 1868.0], + [1.35, 10000, 3882.2, 1072.4, 1324.0, 1860.7], + [1.40, 10000, 3945.4, 1072.1, 1276.3, 1853.1], + [1.45, 10000, 4006.7, 1071.3, 1231.4, 1845.3], + [1.50, 10000, 4066.4, 1070.1, 1189.0, 1837.3], + [1.55, 10000, 4124.4, 1068.5, 1148.9, 1829.1], + [1.60, 10000, 4180.9, 1066.5, 1111.0, 1820.8], + [1.65, 10000, 4235.9, 1064.3, 1075.0, 1812.4], + [1.70, 10000, 4289.5, 1061.8, 1040.9, 1803.9], + [1.75, 10000, 4341.8, 1059.0, 1008.5, 1795.3], + [1.80, 10000, 4392.8, 1056.0, 977.7, 1786.7], + [1.85, 10000, 4442.6, 1052.8, 948.4, 1778.1], + [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"], + ) + + 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 + ) + return expr + + self.pest = parmest.Estimator(reactor_design_model, data, theta_names, SSE) + + def test_scen_from_exps(self): + scenmaker = sc.ScenarioCreator(self.pest, "ipopt") + experimentscens = sc.ScenarioSet("Experiments") + scenmaker.ScenariosFromExperiments(experimentscens) + experimentscens.write_csv("delme_exp_csv.csv") + df = pd.read_csv("delme_exp_csv.csv") + os.remove("delme_exp_csv.csv") + # March '20: all reactor_design experiments have the same theta values! + k1val = df.loc[5].at["k1"] + self.assertAlmostEqual(k1val, 5.0 / 6.0, places=2) + tval = experimentscens.ScenarioNumber(0).ThetaVals["k1"] + self.assertAlmostEqual(tval, 5.0 / 6.0, places=2) + + @unittest.skipIf(not uuid_available, "The uuid module is not available") + def test_no_csv_if_empty(self): + # low level test of scenario sets + # verify that nothing is written, but no errors with empty set + + emptyset = sc.ScenarioSet("empty") + tfile = uuid.uuid4().hex + ".csv" + emptyset.write_csv(tfile) + self.assertFalse( + os.path.exists(tfile), "ScenarioSet wrote csv in spite of empty set" + ) + + +@unittest.skipIf( + not parmest.parmest_available, + "Cannot test parmest: required dependencies are missing", +) +@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") +class TestScenarioSemibatch(unittest.TestCase): + def setUp(self): + import pyomo.contrib.parmest.examples.semibatch.semibatch as sb + import json + + # Vars to estimate in parmest + theta_names = ["k1", "k2", "E1", "E2"] + + self.fbase = os.path.join(testdir, "..", "examples", "semibatch") + # Data, list of dictionaries + data = [] + 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: + d = json.load(infile) + data.append(d) + + # Note, the model already includes a 'SecondStageCost' expression + # for the sum of squared error that will be used in parameter estimation + + self.pest = parmest.Estimator(sb.generate_model, data, theta_names) + + def test_semibatch_bootstrap(self): + scenmaker = sc.ScenarioCreator(self.pest, "ipopt") + bootscens = sc.ScenarioSet("Bootstrap") + numtomake = 2 + scenmaker.ScenariosFromBootstrap(bootscens, numtomake, seed=1134) + tval = bootscens.ScenarioNumber(0).ThetaVals["k1"] + self.assertAlmostEqual(tval, 20.64, places=1) + + +if __name__ == "__main__": + unittest.main() diff --git a/pyomo/contrib/parmest/deprecated/tests/test_solver.py b/pyomo/contrib/parmest/deprecated/tests/test_solver.py new file mode 100644 index 00000000000..eb655023b9b --- /dev/null +++ b/pyomo/contrib/parmest/deprecated/tests/test_solver.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.dependencies import ( + numpy as np, + numpy_available, + pandas as pd, + pandas_available, + scipy, + scipy_available, + matplotlib, + matplotlib_available, +) + +import platform + +is_osx = platform.mac_ver()[0] != '' + +import pyomo.common.unittest as unittest +import os + +import pyomo.contrib.parmest.parmest as parmest +import pyomo.contrib.parmest as parmestbase +import pyomo.environ as pyo + +from pyomo.opt import SolverFactory + +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 + +testdir = os.path.dirname(os.path.abspath(__file__)) + + +@unittest.skipIf( + not parmest.parmest_available, + "Cannot test parmest: required dependencies are missing", +) +@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") +class TestSolver(unittest.TestCase): + def setUp(self): + pass + + def test_ipopt_solve_with_stats(self): + from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import ( + rooney_biegler_model, + ) + from pyomo.contrib.parmest.utils import ipopt_solve_with_stats + + 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'], + ) + + model = rooney_biegler_model(data) + solver = pyo.SolverFactory('ipopt') + solver.solve(model) + + status_obj, solved, iters, time, regu = ipopt_solve_with_stats(model, solver) + + self.assertEqual(solved, True) + + +if __name__ == '__main__': + unittest.main() diff --git a/pyomo/contrib/parmest/deprecated/tests/test_utils.py b/pyomo/contrib/parmest/deprecated/tests/test_utils.py new file mode 100644 index 00000000000..514c14b1e82 --- /dev/null +++ b/pyomo/contrib/parmest/deprecated/tests/test_utils.py @@ -0,0 +1,68 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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 pandas as pd, pandas_available + +import pyomo.environ as pyo +import pyomo.common.unittest as unittest +import pyomo.contrib.parmest.parmest as parmest +from pyomo.opt import SolverFactory + +ipopt_available = SolverFactory("ipopt").available() + + +@unittest.skipIf( + not parmest.parmest_available, + "Cannot test parmest: required dependencies are missing", +) +@unittest.skipIf(not ipopt_available, "The 'ipopt' solver is not available") +class TestUtils(unittest.TestCase): + @classmethod + def setUpClass(self): + pass + + @classmethod + def tearDownClass(self): + pass + + @unittest.pytest.mark.expensive + def test_convert_param_to_var(self): + from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( + reactor_design_model, + ) + + data = pd.DataFrame( + data=[ + [1.05, 10000, 3458.4, 1060.8, 1683.9, 1898.5], + [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"], + ) + + theta_names = ["k1", "k2", "k3"] + + instance = reactor_design_model(data.loc[0]) + solver = pyo.SolverFactory("ipopt") + solver.solve(instance) + + instance_vars = parmest.utils.convert_params_to_vars( + instance, theta_names, fix_vars=True + ) + solver.solve(instance_vars) + + assert instance.k1() == instance_vars.k1() + assert instance.k2() == instance_vars.k2() + assert instance.k3() == instance_vars.k3() + + +if __name__ == "__main__": + unittest.main() diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index dc747217b31..1f9b8b645b8 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -63,7 +63,7 @@ import pyomo.contrib.parmest.graphics as graphics from pyomo.dae import ContinuousSet -import pyomo.contrib.parmest.parmest_deprecated as parmest_deprecated +import pyomo.contrib.parmest.deprecated.parmest as parmest_deprecated parmest_available = numpy_available & pandas_available & scipy_available @@ -398,14 +398,21 @@ def _return_theta_names(self): """ Return list of fitted model parameter names """ - # if fitted model parameter names differ from theta_names created when Estimator object is created - if hasattr(self, 'theta_names_updated'): - return self.theta_names_updated + # check for deprecated inputs + if self.pest_deprecated is not None: + + # if fitted model parameter names differ from theta_names + # created when Estimator object is created + if hasattr(self, 'theta_names_updated'): + return self.pest_deprecated.theta_names_updated + + else: + return ( + self.pest_deprecated.theta_names + ) # default theta_names, created when Estimator object is created else: - return ( - self.theta_names - ) # default theta_names, created when Estimator object is created + return None def _create_parmest_model(self, data): """ diff --git a/pyomo/contrib/parmest/scenariocreator.py b/pyomo/contrib/parmest/scenariocreator.py index 18c27ad1c86..b849bfdfd5b 100644 --- a/pyomo/contrib/parmest/scenariocreator.py +++ b/pyomo/contrib/parmest/scenariocreator.py @@ -14,7 +14,7 @@ import pyomo.environ as pyo -import pyomo.contrib.parmest.scenariocreator_deprecated as scen_deprecated +import pyomo.contrib.parmest.deprecated.scenariocreator as scen_deprecated import logging logger = logging.getLogger(__name__) From ec301e3b480b2f1ccb2837275fe85f95fd562138 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 9 Jan 2024 14:49:44 -0700 Subject: [PATCH 0687/1797] 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 0688/1797] 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 0689/1797] 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 0690/1797] 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 0691/1797] 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 0692/1797] 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 0693/1797] 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 0694/1797] 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 0695/1797] 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 0696/1797] 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 0697/1797] 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 0698/1797] 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 0699/1797] 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 0700/1797] 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 0701/1797] 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 0702/1797] 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 0703/1797] 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 0704/1797] 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 0705/1797] 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 0706/1797] 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 0707/1797] 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 0708/1797] 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 0709/1797] 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 0710/1797] 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 0711/1797] 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 0712/1797] 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 475ec06fb243b9afa4f59a8cefbbacd56d6634f3 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 10:35:33 -0700 Subject: [PATCH 0713/1797] simplification tests --- .../simplification/ginac_interface.cpp | 2 +- pyomo/contrib/simplification/simplify.py | 5 +- .../tests/test_simplification.py | 79 ++++++++++++++++--- 3 files changed, 73 insertions(+), 13 deletions(-) diff --git a/pyomo/contrib/simplification/ginac_interface.cpp b/pyomo/contrib/simplification/ginac_interface.cpp index 690885dc513..32bea8dadd0 100644 --- a/pyomo/contrib/simplification/ginac_interface.cpp +++ b/pyomo/contrib/simplification/ginac_interface.cpp @@ -21,7 +21,7 @@ ex ginac_expr_from_pyomo_node( case py_float: { double val = expr.cast(); if (is_integer(val)) { - res = numeric(expr.cast()); + res = numeric((long) val); } else { res = numeric(val); diff --git a/pyomo/contrib/simplification/simplify.py b/pyomo/contrib/simplification/simplify.py index 938bff6b4b9..66a3dad0b06 100644 --- a/pyomo/contrib/simplification/simplify.py +++ b/pyomo/contrib/simplification/simplify.py @@ -25,7 +25,10 @@ def simplify_with_sympy(expr: NumericExpression): def simplify_with_ginac(expr: NumericExpression, ginac_interface): gi = ginac_interface - return gi.from_ginac(gi.to_ginac(expr).normal()) + ginac_expr = gi.to_ginac(expr) + ginac_expr = ginac_expr.normal() + new_expr = gi.from_ginac(ginac_expr) + return new_expr class Simplifier(object): diff --git a/pyomo/contrib/simplification/tests/test_simplification.py b/pyomo/contrib/simplification/tests/test_simplification.py index 02107ba1d6c..4d9b0cec0d2 100644 --- a/pyomo/contrib/simplification/tests/test_simplification.py +++ b/pyomo/contrib/simplification/tests/test_simplification.py @@ -6,6 +6,14 @@ class TestSimplification(TestCase): + def compare_against_possible_results(self, got, expected_list): + success = False + for exp in expected_list: + if compare_expressions(got, exp): + success = True + break + self.assertTrue(success) + def test_simplify(self): m = pe.ConcreteModel() x = m.x = pe.Var(bounds=(0, None)) @@ -24,13 +32,16 @@ def test_param(self): e1 = p*x**2 + p*x + p*x**2 simp = Simplifier() e2 = simp.simplify(e1) - exp1 = p*x**2.0*2.0 + p*x - exp2 = p*x + p*x**2.0*2.0 - self.assertTrue( - compare_expressions(e2, exp1) - or compare_expressions(e2, exp2) - or compare_expressions(e2, p*x + x**2.0*p*2.0) - or compare_expressions(e2, x**2.0*p*2.0 + p*x) + self.compare_against_possible_results( + e2, + [ + p*x**2.0*2.0 + p*x, + p*x + p*x**2.0*2.0, + 2.0*p*x**2.0 + p*x, + p*x + 2.0*p*x**2.0, + x**2.0*p*2.0 + p*x, + p*x + x**2.0*p*2.0 + ] ) def test_mul(self): @@ -48,8 +59,13 @@ def test_sum(self): e = 2 + x simp = Simplifier() e2 = simp.simplify(e) - expected = x + 2.0 - assertExpressionsEqual(self, expected, e2) + self.compare_against_possible_results( + e2, + [ + 2.0 + x, + x + 2.0, + ] + ) def test_neg(self): m = pe.ConcreteModel() @@ -57,6 +73,47 @@ def test_neg(self): e = -pe.log(x) simp = Simplifier() e2 = simp.simplify(e) - expected = pe.log(x)*(-1.0) - assertExpressionsEqual(self, expected, e2) + self.compare_against_possible_results( + e2, + [ + (-1.0)*pe.log(x), + pe.log(x)*(-1.0), + -pe.log(x), + ] + ) + + def test_pow(self): + m = pe.ConcreteModel() + x = m.x = pe.Var() + e = x**2.0 + simp = Simplifier() + e2 = simp.simplify(e) + assertExpressionsEqual(self, e, e2) + def test_div(self): + m = pe.ConcreteModel() + x = m.x = pe.Var() + y = m.y = pe.Var() + e = x/y + y/x - x/y + simp = Simplifier() + e2 = simp.simplify(e) + print(e2) + self.compare_against_possible_results( + e2, + [ + y/x, + y*(1.0/x), + y*x**-1.0, + x**-1.0 * y, + ], + ) + + def test_unary(self): + m = pe.ConcreteModel() + x = m.x = pe.Var() + func_list = [pe.log, pe.sin, pe.cos, pe.tan, pe.asin, pe.acos, pe.atan] + for func in func_list: + e = func(x) + simp = Simplifier() + e2 = simp.simplify(e) + assertExpressionsEqual(self, e, e2) From 7c5f1e1c77992bdf93712f89716698585308dcfb Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 10 Jan 2024 10:37:36 -0700 Subject: [PATCH 0714/1797] 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 0715/1797] 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 0716/1797] 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 0717/1797] 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 0718/1797] 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 0719/1797] 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 0720/1797] 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 0721/1797] 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 0722/1797] 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 0723/1797] 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 0724/1797] 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 0725/1797] 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 0726/1797] 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 0727/1797] 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 0728/1797] 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 0729/1797] 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 491db9f6793dc6d84fc3b771073d00d938b3a2f2 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 15:57:34 -0700 Subject: [PATCH 0730/1797] update GHA to install ginac --- .github/workflows/test_pr_and_main.yml | 21 ++++++++++++++++++- .../tests/test_simplification.py | 2 ++ setup.cfg | 1 + 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 2885fd107a8..12dc7c1daac 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -98,7 +98,7 @@ jobs: - os: ubuntu-latest python: 3.11 other: /singletest - category: "-m 'neos or importtest'" + category: "-m 'neos or importtest or simplification'" skip_doctest: 1 TARGET: linux PYENV: pip @@ -179,6 +179,25 @@ jobs: # path: cache/os # key: pkg-${{env.CACHE_VER}}.0-${{runner.os}} + - name: install ginac + if: ${{ matrix.other == "singletest" }} + run: | + pwd + cd .. + curl https://www.ginac.de/CLN/cln-1.3.6.tar.bz2 >cln-1.3.6.tar.bz2 + tar -xvf cln-1.3.6.tar.bz2 + cd cln-1.3.6 + ./configure + make + make install + cd + curl https://www.ginac.de/ginac-1.8.7.tar.bz2 >ginac-1.8.7.tar.bz2 + tar -xvf ginac-1.8.7.tar.bz2 + cd ginac-1.8.7 + ./configure + make + make install + - name: TPL package download cache uses: actions/cache@v3 if: ${{ ! matrix.slim }} diff --git a/pyomo/contrib/simplification/tests/test_simplification.py b/pyomo/contrib/simplification/tests/test_simplification.py index 4d9b0cec0d2..ed59064022c 100644 --- a/pyomo/contrib/simplification/tests/test_simplification.py +++ b/pyomo/contrib/simplification/tests/test_simplification.py @@ -1,10 +1,12 @@ from pyomo.common.unittest import TestCase +from pyomo.common import unittest from pyomo.contrib.simplification import Simplifier from pyomo.core.expr.compare import assertExpressionsEqual, compare_expressions import pyomo.environ as pe from pyomo.core.expr.calculus.diff_with_pyomo import reverse_sd +@unittest.pytest.mark.simplification class TestSimplification(TestCase): def compare_against_possible_results(self, got, expected_list): success = False diff --git a/setup.cfg b/setup.cfg index b606138f38c..a431e0cd601 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,3 +22,4 @@ markers = lp: marks lp tests gams: marks gams tests bar: marks bar tests + simplification: marks simplification tests that have expensive (to install) dependencies From b3a1ff9b06e3fb2e8fd944bb27d2bf736a9cfd54 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 16:02:28 -0700 Subject: [PATCH 0731/1797] run black --- pyomo/contrib/simplification/build.py | 16 +++--- pyomo/contrib/simplification/simplify.py | 2 + .../tests/test_simplification.py | 49 ++++++------------- 3 files changed, 27 insertions(+), 40 deletions(-) diff --git a/pyomo/contrib/simplification/build.py b/pyomo/contrib/simplification/build.py index 6f16607e22b..e8bd645756b 100644 --- a/pyomo/contrib/simplification/build.py +++ b/pyomo/contrib/simplification/build.py @@ -11,16 +11,16 @@ def build_ginac_interface(args=[]): dname = this_file_dir() - _sources = [ - 'ginac_interface.cpp', - ] + _sources = ['ginac_interface.cpp'] sources = list() for fname in _sources: sources.append(os.path.join(dname, fname)) ginac_lib = find_library('ginac') if ginac_lib is None: - raise RuntimeError('could not find GiNaC library; please make sure it is in the LD_LIBRARY_PATH environment variable') + raise RuntimeError( + 'could not find GiNaC library; please make sure it is in the LD_LIBRARY_PATH environment variable' + ) ginac_lib_dir = os.path.dirname(ginac_lib) ginac_build_dir = os.path.dirname(ginac_lib_dir) ginac_include_dir = os.path.join(ginac_build_dir, 'include') @@ -29,7 +29,9 @@ def build_ginac_interface(args=[]): cln_lib = find_library('cln') if cln_lib is None: - raise RuntimeError('could not find CLN library; please make sure it is in the LD_LIBRARY_PATH environment variable') + raise RuntimeError( + 'could not find CLN library; please make sure it is in the LD_LIBRARY_PATH environment variable' + ) cln_lib_dir = os.path.dirname(cln_lib) cln_build_dir = os.path.dirname(cln_lib_dir) cln_include_dir = os.path.join(cln_build_dir, 'include') @@ -38,8 +40,8 @@ def build_ginac_interface(args=[]): extra_args = ['-std=c++11'] ext = Pybind11Extension( - 'ginac_interface', - sources=sources, + 'ginac_interface', + sources=sources, language='c++', include_dirs=[cln_include_dir, ginac_include_dir], library_dirs=[cln_lib_dir, ginac_lib_dir], diff --git a/pyomo/contrib/simplification/simplify.py b/pyomo/contrib/simplification/simplify.py index 66a3dad0b06..8f7f15f3826 100644 --- a/pyomo/contrib/simplification/simplify.py +++ b/pyomo/contrib/simplification/simplify.py @@ -3,8 +3,10 @@ from pyomo.core.expr.numvalue import is_fixed, value import logging import warnings + try: from pyomo.contrib.simplification.ginac_interface import GinacInterface + ginac_available = True except: GinacInterface = None diff --git a/pyomo/contrib/simplification/tests/test_simplification.py b/pyomo/contrib/simplification/tests/test_simplification.py index ed59064022c..cc278db4d43 100644 --- a/pyomo/contrib/simplification/tests/test_simplification.py +++ b/pyomo/contrib/simplification/tests/test_simplification.py @@ -19,7 +19,7 @@ def compare_against_possible_results(self, got, expected_list): def test_simplify(self): m = pe.ConcreteModel() x = m.x = pe.Var(bounds=(0, None)) - e = x*pe.log(x) + e = x * pe.log(x) der1 = reverse_sd(e)[x] der2 = reverse_sd(der1)[x] simp = Simplifier() @@ -31,28 +31,28 @@ def test_param(self): m = pe.ConcreteModel() x = m.x = pe.Var() p = m.p = pe.Param(mutable=True) - e1 = p*x**2 + p*x + p*x**2 + e1 = p * x**2 + p * x + p * x**2 simp = Simplifier() e2 = simp.simplify(e1) self.compare_against_possible_results( - e2, + e2, [ - p*x**2.0*2.0 + p*x, - p*x + p*x**2.0*2.0, - 2.0*p*x**2.0 + p*x, - p*x + 2.0*p*x**2.0, - x**2.0*p*2.0 + p*x, - p*x + x**2.0*p*2.0 - ] + p * x**2.0 * 2.0 + p * x, + p * x + p * x**2.0 * 2.0, + 2.0 * p * x**2.0 + p * x, + p * x + 2.0 * p * x**2.0, + x**2.0 * p * 2.0 + p * x, + p * x + x**2.0 * p * 2.0, + ], ) def test_mul(self): m = pe.ConcreteModel() x = m.x = pe.Var() - e = 2*x + e = 2 * x simp = Simplifier() e2 = simp.simplify(e) - expected = 2.0*x + expected = 2.0 * x assertExpressionsEqual(self, expected, e2) def test_sum(self): @@ -61,13 +61,7 @@ def test_sum(self): e = 2 + x simp = Simplifier() e2 = simp.simplify(e) - self.compare_against_possible_results( - e2, - [ - 2.0 + x, - x + 2.0, - ] - ) + self.compare_against_possible_results(e2, [2.0 + x, x + 2.0]) def test_neg(self): m = pe.ConcreteModel() @@ -76,12 +70,7 @@ def test_neg(self): simp = Simplifier() e2 = simp.simplify(e) self.compare_against_possible_results( - e2, - [ - (-1.0)*pe.log(x), - pe.log(x)*(-1.0), - -pe.log(x), - ] + e2, [(-1.0) * pe.log(x), pe.log(x) * (-1.0), -pe.log(x)] ) def test_pow(self): @@ -96,18 +85,12 @@ def test_div(self): m = pe.ConcreteModel() x = m.x = pe.Var() y = m.y = pe.Var() - e = x/y + y/x - x/y + e = x / y + y / x - x / y simp = Simplifier() e2 = simp.simplify(e) print(e2) self.compare_against_possible_results( - e2, - [ - y/x, - y*(1.0/x), - y*x**-1.0, - x**-1.0 * y, - ], + e2, [y / x, y * (1.0 / x), y * x**-1.0, x**-1.0 * y] ) def test_unary(self): From de8743a84c4902867a802756ab64fbd62eed920e Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 16:03:42 -0700 Subject: [PATCH 0732/1797] syntax --- .github/workflows/test_pr_and_main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 12dc7c1daac..7345fd45e10 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -180,7 +180,7 @@ jobs: # key: pkg-${{env.CACHE_VER}}.0-${{runner.os}} - name: install ginac - if: ${{ matrix.other == "singletest" }} + if: ${{ matrix.other == 'singletest' }} run: | pwd cd .. From ee9f830984ad20446b28c8aa65674d5cbf264ab3 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 16:07:25 -0700 Subject: [PATCH 0733/1797] install ginac in GHA --- .github/workflows/test_branches.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index e773587ec85..3270b3e8a95 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -94,6 +94,14 @@ jobs: PYENV: conda PACKAGES: mpi4py + - os: ubuntu-latest + python: 3.11 + other: /singletest + category: "-m 'neos or importtest or simplification'" + skip_doctest: 1 + TARGET: linux + PYENV: pip + - os: ubuntu-latest python: '3.10' other: /cython @@ -149,6 +157,25 @@ jobs: # path: cache/os # key: pkg-${{env.CACHE_VER}}.0-${{runner.os}} + - name: install ginac + if: ${{ matrix.other == 'singletest' }} + run: | + pwd + cd .. + curl https://www.ginac.de/CLN/cln-1.3.6.tar.bz2 >cln-1.3.6.tar.bz2 + tar -xvf cln-1.3.6.tar.bz2 + cd cln-1.3.6 + ./configure + make + make install + cd + curl https://www.ginac.de/ginac-1.8.7.tar.bz2 >ginac-1.8.7.tar.bz2 + tar -xvf ginac-1.8.7.tar.bz2 + cd ginac-1.8.7 + ./configure + make + make install + - name: TPL package download cache uses: actions/cache@v3 if: ${{ ! matrix.slim }} From 36cfd6388d16d6f2077d51b9035c9e363b4e4e28 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 16:09:53 -0700 Subject: [PATCH 0734/1797] install ginac in GHA --- .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 3270b3e8a95..97b9c6ed1dc 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -158,7 +158,7 @@ jobs: # key: pkg-${{env.CACHE_VER}}.0-${{runner.os}} - name: install ginac - if: ${{ matrix.other == 'singletest' }} + if: matrix.TARGET == 'singletest' run: | pwd cd .. From 8269539ff67b48e4c7b652b8feabb14c64634fc6 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 16:11:27 -0700 Subject: [PATCH 0735/1797] install ginac in GHA --- .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 97b9c6ed1dc..c56ec398134 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -158,7 +158,7 @@ jobs: # key: pkg-${{env.CACHE_VER}}.0-${{runner.os}} - name: install ginac - if: matrix.TARGET == 'singletest' + if: matrix.other == 'singletest' run: | pwd cd .. From 7bb0ff501344dfc9bb162f9ed339293ad320d0d1 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 16:14:07 -0700 Subject: [PATCH 0736/1797] install ginac in GHA --- .github/workflows/test_branches.yml | 2 +- pyomo/contrib/simplification/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index c56ec398134..bea57f314e5 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -158,7 +158,7 @@ jobs: # key: pkg-${{env.CACHE_VER}}.0-${{runner.os}} - name: install ginac - if: matrix.other == 'singletest' + if: matrix.other == '/singletest' run: | pwd cd .. diff --git a/pyomo/contrib/simplification/__init__.py b/pyomo/contrib/simplification/__init__.py index c09e8b8b5e5..3abe5a25ba0 100644 --- a/pyomo/contrib/simplification/__init__.py +++ b/pyomo/contrib/simplification/__init__.py @@ -1 +1 @@ -from .simplify import Simplifier \ No newline at end of file +from .simplify import Simplifier From 546dad1d46cc1a60c6fae711a6dbe8710f53220c Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 16:23:06 -0700 Subject: [PATCH 0737/1797] install ginac in GHA --- .github/workflows/test_branches.yml | 10 ++++++++-- pyomo/contrib/simplification/simplify.py | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index bea57f314e5..16ce6a44003 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -167,14 +167,14 @@ jobs: cd cln-1.3.6 ./configure make - make install + sudo make install cd curl https://www.ginac.de/ginac-1.8.7.tar.bz2 >ginac-1.8.7.tar.bz2 tar -xvf ginac-1.8.7.tar.bz2 cd ginac-1.8.7 ./configure make - make install + sudo make install - name: TPL package download cache uses: actions/cache@v3 @@ -630,6 +630,12 @@ jobs: echo "" pyomo build-extensions --parallel 2 + - name: Install GiNaC Interface + if: matrix.other == '/singletest' + run: | + cd pyomo/contrib/simplification/ + $PYTHON_EXE build.py --inplace + - name: Report pyomo plugin information run: | echo "$PATH" diff --git a/pyomo/contrib/simplification/simplify.py b/pyomo/contrib/simplification/simplify.py index 8f7f15f3826..4002f1a233f 100644 --- a/pyomo/contrib/simplification/simplify.py +++ b/pyomo/contrib/simplification/simplify.py @@ -34,10 +34,10 @@ def simplify_with_ginac(expr: NumericExpression, ginac_interface): class Simplifier(object): - def __init__(self, supress_no_ginac_warnings: bool = False) -> None: + def __init__(self, suppress_no_ginac_warnings: bool = False) -> None: if ginac_available: self.gi = GinacInterface(False) - self.suppress_no_ginac_warnings = supress_no_ginac_warnings + self.suppress_no_ginac_warnings = suppress_no_ginac_warnings def simplify(self, expr: NumericExpression): if ginac_available: From 37a955bdfaccec8508887dbf037eb5ea72b5b5cb Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 16:24:16 -0700 Subject: [PATCH 0738/1797] install ginac in GHA --- .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 16ce6a44003..477361683ac 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -634,7 +634,7 @@ jobs: if: matrix.other == '/singletest' run: | cd pyomo/contrib/simplification/ - $PYTHON_EXE build.py --inplace + $PYTHON_EXE build.py --inplace - name: Report pyomo plugin information run: | From 05134ce49621f1efd16d961dc20e34c7cff23475 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 16:45:55 -0700 Subject: [PATCH 0739/1797] install ginac in GHA --- .github/workflows/test_branches.yml | 1 + pyomo/contrib/simplification/build.py | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 477361683ac..7933aa522d8 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -633,6 +633,7 @@ jobs: - name: Install GiNaC Interface if: matrix.other == '/singletest' run: | + ls /usr/local/include/ginac/ cd pyomo/contrib/simplification/ $PYTHON_EXE build.py --inplace diff --git a/pyomo/contrib/simplification/build.py b/pyomo/contrib/simplification/build.py index e8bd645756b..39742e1e351 100644 --- a/pyomo/contrib/simplification/build.py +++ b/pyomo/contrib/simplification/build.py @@ -17,6 +17,7 @@ def build_ginac_interface(args=[]): sources.append(os.path.join(dname, fname)) ginac_lib = find_library('ginac') + print(ginac_lib) if ginac_lib is None: raise RuntimeError( 'could not find GiNaC library; please make sure it is in the LD_LIBRARY_PATH environment variable' From c22276fc824338991faed503f472965a15ff1749 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 10 Jan 2024 16:52:09 -0700 Subject: [PATCH 0740/1797] 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 1151927df204f6969704ec7b671e25a1d9083031 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 17:51:34 -0700 Subject: [PATCH 0741/1797] install ginac in GHA --- .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 7933aa522d8..f2bf057da51 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -633,7 +633,7 @@ jobs: - name: Install GiNaC Interface if: matrix.other == '/singletest' run: | - ls /usr/local/include/ginac/ + export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH cd pyomo/contrib/simplification/ $PYTHON_EXE build.py --inplace From 2c4fdbee83ab76b35a863748048ddf0d091f2f3c Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 18:19:05 -0700 Subject: [PATCH 0742/1797] skip tests when dependencies are not available --- pyomo/contrib/simplification/tests/test_simplification.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyomo/contrib/simplification/tests/test_simplification.py b/pyomo/contrib/simplification/tests/test_simplification.py index cc278db4d43..c50a906afe7 100644 --- a/pyomo/contrib/simplification/tests/test_simplification.py +++ b/pyomo/contrib/simplification/tests/test_simplification.py @@ -1,11 +1,17 @@ from pyomo.common.unittest import TestCase from pyomo.common import unittest from pyomo.contrib.simplification import Simplifier +from pyomo.contrib.simplification.simplify import ginac_available from pyomo.core.expr.compare import assertExpressionsEqual, compare_expressions import pyomo.environ as pe from pyomo.core.expr.calculus.diff_with_pyomo import reverse_sd +from pyomo.common.dependencies import attempt_import +sympy, sympy_available = attempt_import('sympy') + + +@unittest.skipIf((not sympy_available) and (not ginac_available), 'neither sympy nor ginac are available') @unittest.pytest.mark.simplification class TestSimplification(TestCase): def compare_against_possible_results(self, got, expected_list): From 9ebd79b898aea6827232220f59c615c771686ddb Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 18:26:40 -0700 Subject: [PATCH 0743/1797] install ginac in GHA --- .github/workflows/test_branches.yml | 2 +- .github/workflows/test_pr_and_main.yml | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index f2bf057da51..b97b3a682af 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -168,7 +168,7 @@ jobs: ./configure make sudo make install - cd + cd .. curl https://www.ginac.de/ginac-1.8.7.tar.bz2 >ginac-1.8.7.tar.bz2 tar -xvf ginac-1.8.7.tar.bz2 cd ginac-1.8.7 diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 7345fd45e10..b99d39cf6c7 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -180,23 +180,22 @@ jobs: # key: pkg-${{env.CACHE_VER}}.0-${{runner.os}} - name: install ginac - if: ${{ matrix.other == 'singletest' }} + if: matrix.other == '/singletest' run: | - pwd cd .. curl https://www.ginac.de/CLN/cln-1.3.6.tar.bz2 >cln-1.3.6.tar.bz2 tar -xvf cln-1.3.6.tar.bz2 cd cln-1.3.6 ./configure make - make install - cd + sudo make install + cd .. curl https://www.ginac.de/ginac-1.8.7.tar.bz2 >ginac-1.8.7.tar.bz2 tar -xvf ginac-1.8.7.tar.bz2 cd ginac-1.8.7 ./configure make - make install + sudo make install - name: TPL package download cache uses: actions/cache@v3 @@ -652,6 +651,13 @@ jobs: echo "" pyomo build-extensions --parallel 2 + - name: Install GiNaC Interface + if: matrix.other == '/singletest' + run: | + export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH + cd pyomo/contrib/simplification/ + $PYTHON_EXE build.py --inplace + - name: Report pyomo plugin information run: | echo "$PATH" From 0bd156351b09d93288d618715817fcc4c530114a Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 18:28:39 -0700 Subject: [PATCH 0744/1797] update simplification tests --- .github/workflows/test_branches.yml | 2 +- .github/workflows/test_pr_and_main.yml | 2 +- pyomo/contrib/simplification/tests/test_simplification.py | 1 - setup.cfg | 1 - 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index b97b3a682af..13a5653f9f5 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -97,7 +97,7 @@ jobs: - os: ubuntu-latest python: 3.11 other: /singletest - category: "-m 'neos or importtest or simplification'" + category: "-m 'neos or importtest'" skip_doctest: 1 TARGET: linux PYENV: pip diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index b99d39cf6c7..8a8a9b08030 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -98,7 +98,7 @@ jobs: - os: ubuntu-latest python: 3.11 other: /singletest - category: "-m 'neos or importtest or simplification'" + category: "-m 'neos or importtest'" skip_doctest: 1 TARGET: linux PYENV: pip diff --git a/pyomo/contrib/simplification/tests/test_simplification.py b/pyomo/contrib/simplification/tests/test_simplification.py index c50a906afe7..f3bce9cee54 100644 --- a/pyomo/contrib/simplification/tests/test_simplification.py +++ b/pyomo/contrib/simplification/tests/test_simplification.py @@ -12,7 +12,6 @@ @unittest.skipIf((not sympy_available) and (not ginac_available), 'neither sympy nor ginac are available') -@unittest.pytest.mark.simplification class TestSimplification(TestCase): def compare_against_possible_results(self, got, expected_list): success = False diff --git a/setup.cfg b/setup.cfg index a431e0cd601..b606138f38c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,4 +22,3 @@ markers = lp: marks lp tests gams: marks gams tests bar: marks bar tests - simplification: marks simplification tests that have expensive (to install) dependencies From 26007ac42688cbe7554807198ceb20e9d121d68e Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 18:30:57 -0700 Subject: [PATCH 0745/1797] run black --- pyomo/contrib/simplification/tests/test_simplification.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/simplification/tests/test_simplification.py b/pyomo/contrib/simplification/tests/test_simplification.py index f3bce9cee54..e6b5ae863f6 100644 --- a/pyomo/contrib/simplification/tests/test_simplification.py +++ b/pyomo/contrib/simplification/tests/test_simplification.py @@ -11,7 +11,10 @@ sympy, sympy_available = attempt_import('sympy') -@unittest.skipIf((not sympy_available) and (not ginac_available), 'neither sympy nor ginac are available') +@unittest.skipIf( + (not sympy_available) and (not ginac_available), + 'neither sympy nor ginac are available', +) class TestSimplification(TestCase): def compare_against_possible_results(self, got, expected_list): success = False From fc36411c1c57aaf4720938cf7926cfcf7048bc75 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 18:56:21 -0700 Subject: [PATCH 0746/1797] add pytest marker for simplification --- .github/workflows/test_branches.yml | 2 +- .github/workflows/test_pr_and_main.yml | 2 +- pyomo/contrib/simplification/tests/test_simplification.py | 1 + setup.cfg | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 13a5653f9f5..b97b3a682af 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -97,7 +97,7 @@ jobs: - os: ubuntu-latest python: 3.11 other: /singletest - category: "-m 'neos or importtest'" + category: "-m 'neos or importtest or simplification'" skip_doctest: 1 TARGET: linux PYENV: pip diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 8a8a9b08030..b99d39cf6c7 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -98,7 +98,7 @@ jobs: - os: ubuntu-latest python: 3.11 other: /singletest - category: "-m 'neos or importtest'" + category: "-m 'neos or importtest or simplification'" skip_doctest: 1 TARGET: linux PYENV: pip diff --git a/pyomo/contrib/simplification/tests/test_simplification.py b/pyomo/contrib/simplification/tests/test_simplification.py index e6b5ae863f6..152db93a358 100644 --- a/pyomo/contrib/simplification/tests/test_simplification.py +++ b/pyomo/contrib/simplification/tests/test_simplification.py @@ -15,6 +15,7 @@ (not sympy_available) and (not ginac_available), 'neither sympy nor ginac are available', ) +@unittest.pytest.mark.simplification class TestSimplification(TestCase): def compare_against_possible_results(self, got, expected_list): success = False diff --git a/setup.cfg b/setup.cfg index b606138f38c..855717490b3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,3 +22,4 @@ markers = lp: marks lp tests gams: marks gams tests bar: marks bar tests + simplification: tests for expression simplification that have expensive (to install) dependencies From 5ba03e215c51fe2feba6a5cff7b2baa162fcb87f Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 19:30:50 -0700 Subject: [PATCH 0747/1797] update GHA --- .github/workflows/test_branches.yml | 1 + .github/workflows/test_pr_and_main.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 243f57ea7aa..b73a9cabc81 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -641,6 +641,7 @@ jobs: if: matrix.other == '/singletest' run: | export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH + echo "LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV cd pyomo/contrib/simplification/ $PYTHON_EXE build.py --inplace diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 93919ca6bc3..1c36b89710c 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -662,6 +662,7 @@ jobs: if: matrix.other == '/singletest' run: | export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH + echo "LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV cd pyomo/contrib/simplification/ $PYTHON_EXE build.py --inplace From 90cdeba86b2deaa76ce7b62cd9f21f906b057462 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 19:32:33 -0700 Subject: [PATCH 0748/1797] update GHA --- .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 b73a9cabc81..e3e0c3e6caf 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -166,14 +166,14 @@ jobs: tar -xvf cln-1.3.6.tar.bz2 cd cln-1.3.6 ./configure - make + make -j 2 sudo make install cd .. curl https://www.ginac.de/ginac-1.8.7.tar.bz2 >ginac-1.8.7.tar.bz2 tar -xvf ginac-1.8.7.tar.bz2 cd ginac-1.8.7 ./configure - make + make -j 2 sudo make install - name: TPL package download cache diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 1c36b89710c..9edb8b1c65f 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -187,14 +187,14 @@ jobs: tar -xvf cln-1.3.6.tar.bz2 cd cln-1.3.6 ./configure - make + make -j 2 sudo make install cd .. curl https://www.ginac.de/ginac-1.8.7.tar.bz2 >ginac-1.8.7.tar.bz2 tar -xvf ginac-1.8.7.tar.bz2 cd ginac-1.8.7 ./configure - make + make -j 2 sudo make install - name: TPL package download cache From 131fd3f2977000ceadc903d88e9deac2c724bb81 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 10 Jan 2024 19:46:08 -0700 Subject: [PATCH 0749/1797] 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 17cb11d31d72d7ac9ac9f37e1ec306f8d114cec4 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 19:50:50 -0700 Subject: [PATCH 0750/1797] debugging GHA --- .github/workflows/test_branches.yml | 1 + .github/workflows/test_pr_and_main.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index e3e0c3e6caf..4e3c14cb70e 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -655,6 +655,7 @@ jobs: - name: Run Pyomo tests if: matrix.mpi == 0 run: | + $PYTHON_EXE -c "from pyomo.contrib.simplification.ginac_interface import GinacInterface" $PYTHON_EXE -m pytest -v \ -W ignore::Warning ${{matrix.category}} \ pyomo `pwd`/pyomo-model-libraries \ diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 9edb8b1c65f..1626964a7e9 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -676,6 +676,7 @@ jobs: - name: Run Pyomo tests if: matrix.mpi == 0 run: | + $PYTHON_EXE -c "from pyomo.contrib.simplification.ginac_interface import GinacInterface" $PYTHON_EXE -m pytest -v \ -W ignore::Warning ${{matrix.category}} \ pyomo `pwd`/pyomo-model-libraries \ From d1fe24400ed424afdfdf65e3f9918faefc98db96 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 20:02:34 -0700 Subject: [PATCH 0751/1797] test simplification with ginac and sympy --- .../simplification/tests/test_simplification.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/simplification/tests/test_simplification.py b/pyomo/contrib/simplification/tests/test_simplification.py index 152db93a358..096d776460d 100644 --- a/pyomo/contrib/simplification/tests/test_simplification.py +++ b/pyomo/contrib/simplification/tests/test_simplification.py @@ -12,11 +12,10 @@ @unittest.skipIf( - (not sympy_available) and (not ginac_available), - 'neither sympy nor ginac are available', + (not sympy_available) or (ginac_available), + 'sympy is not available', ) -@unittest.pytest.mark.simplification -class TestSimplification(TestCase): +class TestSimplificationSympy(TestCase): def compare_against_possible_results(self, got, expected_list): success = False for exp in expected_list: @@ -111,3 +110,12 @@ def test_unary(self): simp = Simplifier() e2 = simp.simplify(e) assertExpressionsEqual(self, e, e2) + + +@unittest.skipIf( + not ginac_available, + 'GiNaC is not available', +) +@unittest.pytest.mark.simplification +class TestSimplificationGiNaC(TestSimplificationSympy): + pass From 9159c3c5854c7a9d5437f3308321a9fd4a61be6f Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 20:03:18 -0700 Subject: [PATCH 0752/1797] test simplification with ginac and sympy --- .../tests/test_simplification.py | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/pyomo/contrib/simplification/tests/test_simplification.py b/pyomo/contrib/simplification/tests/test_simplification.py index 096d776460d..3124d856784 100644 --- a/pyomo/contrib/simplification/tests/test_simplification.py +++ b/pyomo/contrib/simplification/tests/test_simplification.py @@ -35,25 +35,6 @@ def test_simplify(self): expected = x**-1.0 assertExpressionsEqual(self, expected, der2_simp) - def test_param(self): - m = pe.ConcreteModel() - x = m.x = pe.Var() - p = m.p = pe.Param(mutable=True) - e1 = p * x**2 + p * x + p * x**2 - simp = Simplifier() - e2 = simp.simplify(e1) - self.compare_against_possible_results( - e2, - [ - p * x**2.0 * 2.0 + p * x, - p * x + p * x**2.0 * 2.0, - 2.0 * p * x**2.0 + p * x, - p * x + 2.0 * p * x**2.0, - x**2.0 * p * 2.0 + p * x, - p * x + x**2.0 * p * 2.0, - ], - ) - def test_mul(self): m = pe.ConcreteModel() x = m.x = pe.Var() @@ -118,4 +99,21 @@ def test_unary(self): ) @unittest.pytest.mark.simplification class TestSimplificationGiNaC(TestSimplificationSympy): - pass + def test_param(self): + m = pe.ConcreteModel() + x = m.x = pe.Var() + p = m.p = pe.Param(mutable=True) + e1 = p * x**2 + p * x + p * x**2 + simp = Simplifier() + e2 = simp.simplify(e1) + self.compare_against_possible_results( + e2, + [ + p * x**2.0 * 2.0 + p * x, + p * x + p * x**2.0 * 2.0, + 2.0 * p * x**2.0 + p * x, + p * x + 2.0 * p * x**2.0, + x**2.0 * p * 2.0 + p * x, + p * x + x**2.0 * p * 2.0, + ], + ) From 457f2b378b772eae16b47fa789423bca70de43c5 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 22:17:46 -0700 Subject: [PATCH 0753/1797] fixing simplification tests --- .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 4e3c14cb70e..0eb6fe166d9 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -655,7 +655,8 @@ jobs: - name: Run Pyomo tests if: matrix.mpi == 0 run: | - $PYTHON_EXE -c "from pyomo.contrib.simplification.ginac_interface import GinacInterface" + $PYTHON_EXE -c "from pyomo.contrib.simplification.ginac_interface import GinacInterface; print(GinacInterface)" + $PYTHON_EXE -c "from pyomo.contrib.simplification.simplify import ginac_available; print(ginac_available)" $PYTHON_EXE -m pytest -v \ -W ignore::Warning ${{matrix.category}} \ pyomo `pwd`/pyomo-model-libraries \ From 1e7c3f1b2ecf562000088df80f215ddf6d992420 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 22:44:54 -0700 Subject: [PATCH 0754/1797] fixing simplification tests --- .../simplification/tests/test_simplification.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/pyomo/contrib/simplification/tests/test_simplification.py b/pyomo/contrib/simplification/tests/test_simplification.py index 3124d856784..6badc76b957 100644 --- a/pyomo/contrib/simplification/tests/test_simplification.py +++ b/pyomo/contrib/simplification/tests/test_simplification.py @@ -11,11 +11,7 @@ sympy, sympy_available = attempt_import('sympy') -@unittest.skipIf( - (not sympy_available) or (ginac_available), - 'sympy is not available', -) -class TestSimplificationSympy(TestCase): +class SimplificationMixin: def compare_against_possible_results(self, got, expected_list): success = False for exp in expected_list: @@ -93,12 +89,20 @@ def test_unary(self): assertExpressionsEqual(self, e, e2) +@unittest.skipIf( + (not sympy_available) or (ginac_available), + 'sympy is not available', +) +class TestSimplificationSympy(TestCase, SimplificationMixin): + pass + + @unittest.skipIf( not ginac_available, 'GiNaC is not available', ) @unittest.pytest.mark.simplification -class TestSimplificationGiNaC(TestSimplificationSympy): +class TestSimplificationGiNaC(TestCase, SimplificationMixin): def test_param(self): m = pe.ConcreteModel() x = m.x = pe.Var() From b2d969f73e4dbf1e80d453dd7be4dcd474fc32be Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 22:46:36 -0700 Subject: [PATCH 0755/1797] fixing simplification tests --- .../simplification/tests/test_simplification.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/pyomo/contrib/simplification/tests/test_simplification.py b/pyomo/contrib/simplification/tests/test_simplification.py index 6badc76b957..e3c60cb02ca 100644 --- a/pyomo/contrib/simplification/tests/test_simplification.py +++ b/pyomo/contrib/simplification/tests/test_simplification.py @@ -89,18 +89,12 @@ def test_unary(self): assertExpressionsEqual(self, e, e2) -@unittest.skipIf( - (not sympy_available) or (ginac_available), - 'sympy is not available', -) +@unittest.skipIf((not sympy_available) or (ginac_available), 'sympy is not available') class TestSimplificationSympy(TestCase, SimplificationMixin): pass -@unittest.skipIf( - not ginac_available, - 'GiNaC is not available', -) +@unittest.skipIf(not ginac_available, 'GiNaC is not available') @unittest.pytest.mark.simplification class TestSimplificationGiNaC(TestCase, SimplificationMixin): def test_param(self): From da2fe3d714b34c7b03a41a2c63af8590db87d2e4 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 10 Jan 2024 23:04:15 -0700 Subject: [PATCH 0756/1797] fixing simplification tests --- .github/workflows/test_branches.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 0eb6fe166d9..1124a253ac8 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -657,6 +657,7 @@ jobs: run: | $PYTHON_EXE -c "from pyomo.contrib.simplification.ginac_interface import GinacInterface; print(GinacInterface)" $PYTHON_EXE -c "from pyomo.contrib.simplification.simplify import ginac_available; print(ginac_available)" + pytest -v pyomo/contrib/simplification/tests/test_simplification.py $PYTHON_EXE -m pytest -v \ -W ignore::Warning ${{matrix.category}} \ pyomo `pwd`/pyomo-model-libraries \ From 317dae89cdb4eac00e006bc808d32d887a14d787 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 11 Jan 2024 14:30:08 -0500 Subject: [PATCH 0757/1797] 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 0758/1797] 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 0759/1797] 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 0760/1797] 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 0761/1797] 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 0762/1797] 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 0763/1797] 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 0764/1797] 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 0765/1797] 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 0766/1797] 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 0767/1797] 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 0768/1797] 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 0769/1797] 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 0770/1797] 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 0771/1797] 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 0772/1797] 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 0773/1797] 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 0774/1797] 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 0775/1797] 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 0776/1797] 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 0777/1797] 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 0778/1797] 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 0779/1797] 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 0780/1797] 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 0781/1797] 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 0782/1797] 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 0783/1797] 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 0784/1797] 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 0785/1797] 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 0786/1797] 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 0787/1797] 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 0788/1797] 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 0789/1797] 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 0790/1797] 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 0791/1797] 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 0792/1797] 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 0793/1797] 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 0794/1797] 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 0795/1797] 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 0796/1797] 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 0797/1797] 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 0798/1797] 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 0799/1797] 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 0800/1797] 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 0801/1797] 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 0802/1797] 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 0803/1797] 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 0804/1797] 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 0805/1797] 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 0806/1797] 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 0807/1797] 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 0808/1797] 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 0809/1797] 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 0810/1797] 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 0811/1797] 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 0812/1797] 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 0813/1797] 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 0814/1797] 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 0815/1797] 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 0816/1797] 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 0817/1797] 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 0818/1797] 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 0819/1797] 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 0820/1797] 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 0821/1797] 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 0822/1797] 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 0823/1797] 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 0824/1797] 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 0825/1797] 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 0826/1797] 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 0827/1797] 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 0828/1797] 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 0829/1797] 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 0830/1797] 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 0831/1797] 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 0832/1797] 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 0833/1797] 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 0834/1797] 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 0835/1797] 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 0836/1797] 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 2b1596b7ccda4b3a68747d5c6916b0c7570dae85 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 22 Jan 2024 04:50:02 -0700 Subject: [PATCH 0837/1797] fixing simplification tests --- .github/workflows/test_branches.yml | 2 +- pyomo/contrib/simplification/simplify.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 89cb12d3eac..9743e45be41 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -655,7 +655,7 @@ jobs: run: | $PYTHON_EXE -c "from pyomo.contrib.simplification.ginac_interface import GinacInterface; print(GinacInterface)" $PYTHON_EXE -c "from pyomo.contrib.simplification.simplify import ginac_available; print(ginac_available)" - pytest -v pyomo/contrib/simplification/tests/test_simplification.py + pytest -v -m 'simplification' pyomo/contrib/simplification/tests/test_simplification.py $PYTHON_EXE -m pytest -v \ -W ignore::Warning ${{matrix.category}} \ pyomo `pwd`/pyomo-model-libraries \ diff --git a/pyomo/contrib/simplification/simplify.py b/pyomo/contrib/simplification/simplify.py index 4002f1a233f..5c0c5b859e7 100644 --- a/pyomo/contrib/simplification/simplify.py +++ b/pyomo/contrib/simplification/simplify.py @@ -6,7 +6,6 @@ try: from pyomo.contrib.simplification.ginac_interface import GinacInterface - ginac_available = True except: GinacInterface = None From d75c5f892a5dce5d6841c11c9dc02494c61e87b5 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 22 Jan 2024 04:55:31 -0700 Subject: [PATCH 0838/1797] fixing simplification tests --- .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 9743e45be41..2384ea58e2a 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -162,9 +162,9 @@ jobs: run: | pwd cd .. - curl https://www.ginac.de/CLN/cln-1.3.6.tar.bz2 >cln-1.3.6.tar.bz2 - tar -xvf cln-1.3.6.tar.bz2 - cd cln-1.3.6 + curl https://www.ginac.de/CLN/cln-1.3.7.tar.bz2 >cln-1.3.7.tar.bz2 + tar -xvf cln-1.3.7.tar.bz2 + cd cln-1.3.7 ./configure make -j 2 sudo make install From dae02054827c2ae23d608e07a2e6913a15180631 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 22 Jan 2024 05:00:16 -0700 Subject: [PATCH 0839/1797] run black --- pyomo/contrib/simplification/simplify.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/contrib/simplification/simplify.py b/pyomo/contrib/simplification/simplify.py index 5c0c5b859e7..4002f1a233f 100644 --- a/pyomo/contrib/simplification/simplify.py +++ b/pyomo/contrib/simplification/simplify.py @@ -6,6 +6,7 @@ try: from pyomo.contrib.simplification.ginac_interface import GinacInterface + ginac_available = True except: GinacInterface = None From 313108d131f04e80deb59862e95099a731c84006 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 22 Jan 2024 05:15:38 -0700 Subject: [PATCH 0840/1797] fixing simplification 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 2384ea58e2a..60f971c0c51 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -657,7 +657,7 @@ jobs: $PYTHON_EXE -c "from pyomo.contrib.simplification.simplify import ginac_available; print(ginac_available)" pytest -v -m 'simplification' pyomo/contrib/simplification/tests/test_simplification.py $PYTHON_EXE -m pytest -v \ - -W ignore::Warning ${{matrix.category}} \ + ${{matrix.category}} \ pyomo `pwd`/pyomo-model-libraries \ `pwd`/examples `pwd`/doc --junitxml="TEST-pyomo.xml" From 5ff4d4d3b03370580da4578e141b98d2bdaaadf0 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 22 Jan 2024 05:24:22 -0700 Subject: [PATCH 0841/1797] fixing simplification tests --- .github/workflows/test_branches.yml | 2 +- setup.cfg | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 60f971c0c51..5ed5f908d28 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -97,7 +97,7 @@ jobs: - os: ubuntu-latest python: 3.11 other: /singletest - category: "-m 'neos or importtest or simplification'" + category: "-m 'simplification'" skip_doctest: 1 TARGET: linux PYENV: pip diff --git a/setup.cfg b/setup.cfg index 855717490b3..e8b6933bbbc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,7 +5,6 @@ license_files = LICENSE.md universal=1 [tool:pytest] -filterwarnings = ignore::RuntimeWarning junit_family = xunit2 markers = default: mark a test that should always run by default From 010a997ff78390af504d2dc0493cf8e96ee5fd7f Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 22 Jan 2024 05:47:50 -0700 Subject: [PATCH 0842/1797] fixing simplification tests --- .github/workflows/test_branches.yml | 9 +++------ setup.cfg | 1 + 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 5ed5f908d28..b766583e259 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -97,7 +97,7 @@ jobs: - os: ubuntu-latest python: 3.11 other: /singletest - category: "-m 'simplification'" + category: "-m 'neos or importtest or simplification'" skip_doctest: 1 TARGET: linux PYENV: pip @@ -653,11 +653,8 @@ jobs: - name: Run Pyomo tests if: matrix.mpi == 0 run: | - $PYTHON_EXE -c "from pyomo.contrib.simplification.ginac_interface import GinacInterface; print(GinacInterface)" - $PYTHON_EXE -c "from pyomo.contrib.simplification.simplify import ginac_available; print(ginac_available)" - pytest -v -m 'simplification' pyomo/contrib/simplification/tests/test_simplification.py - $PYTHON_EXE -m pytest -v \ - ${{matrix.category}} \ + pytest -v \ + -W ignore::Warning ${{matrix.category}} \ pyomo `pwd`/pyomo-model-libraries \ `pwd`/examples `pwd`/doc --junitxml="TEST-pyomo.xml" diff --git a/setup.cfg b/setup.cfg index e8b6933bbbc..855717490b3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,6 +5,7 @@ license_files = LICENSE.md universal=1 [tool:pytest] +filterwarnings = ignore::RuntimeWarning junit_family = xunit2 markers = default: mark a test that should always run by default From 2c59b2930d2d1b41dde9f919810a3028f248ed1d Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 22 Jan 2024 06:22:27 -0700 Subject: [PATCH 0843/1797] fixing simplification tests --- .github/workflows/test_branches.yml | 9 +++++++-- pyomo/core/expr/compare.py | 14 +------------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index b766583e259..15880896961 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -97,7 +97,7 @@ jobs: - os: ubuntu-latest python: 3.11 other: /singletest - category: "-m 'neos or importtest or simplification'" + category: "-m 'neos or importtest'" skip_doctest: 1 TARGET: linux PYENV: pip @@ -653,11 +653,16 @@ jobs: - name: Run Pyomo tests if: matrix.mpi == 0 run: | - pytest -v \ + $PYTHON_EXE -m pytest -v \ -W ignore::Warning ${{matrix.category}} \ pyomo `pwd`/pyomo-model-libraries \ `pwd`/examples `pwd`/doc --junitxml="TEST-pyomo.xml" + - name: Run Simplification Tests + if: matrix.other == '/singletest' + run: | + pytest -v -m 'simplification' pyomo.contrib.simplification/tests/test_simplification.py --junitxml="TEST-pyomo-simplify.xml" + - name: Run Pyomo MPI tests if: matrix.mpi != 0 run: | diff --git a/pyomo/core/expr/compare.py b/pyomo/core/expr/compare.py index 96913f1de39..ec8d56896b8 100644 --- a/pyomo/core/expr/compare.py +++ b/pyomo/core/expr/compare.py @@ -195,19 +195,7 @@ def compare_expressions(expr1, expr2, include_named_exprs=True): expr2, include_named_exprs=include_named_exprs ) try: - res = True - if len(pn1) != len(pn2): - res = False - if res: - for a, b in zip(pn1, pn2): - if a.__class__ is not b.__class__: - res = False - break - if a == b: - continue - else: - res = False - break + res = pn1 == pn2 except PyomoException: res = False return res From d67d90d8c1226d0afa96ff700e173819eb822b7f Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 22 Jan 2024 06:25:16 -0700 Subject: [PATCH 0844/1797] fixing simplification tests --- .github/workflows/test_branches.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 15880896961..5eac447fd02 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -650,6 +650,11 @@ jobs: pyomo help --transformations || exit 1 pyomo help --writers || exit 1 + - name: Run Simplification Tests + if: matrix.other == '/singletest' + run: | + pytest -v -m 'simplification' pyomo.contrib.simplification/tests/test_simplification.py --junitxml="TEST-pyomo-simplify.xml" + - name: Run Pyomo tests if: matrix.mpi == 0 run: | @@ -658,11 +663,6 @@ jobs: pyomo `pwd`/pyomo-model-libraries \ `pwd`/examples `pwd`/doc --junitxml="TEST-pyomo.xml" - - name: Run Simplification Tests - if: matrix.other == '/singletest' - run: | - pytest -v -m 'simplification' pyomo.contrib.simplification/tests/test_simplification.py --junitxml="TEST-pyomo-simplify.xml" - - name: Run Pyomo MPI tests if: matrix.mpi != 0 run: | From 3fcec359e1f70842af03fe9fdb8b0629785a1b64 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 22 Jan 2024 06:31:28 -0700 Subject: [PATCH 0845/1797] fixing simplification tests --- .github/workflows/test_branches.yml | 1 - .github/workflows/test_pr_and_main.yml | 14 +++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 5eac447fd02..d66451a00a5 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -160,7 +160,6 @@ jobs: - name: install ginac if: matrix.other == '/singletest' run: | - pwd cd .. curl https://www.ginac.de/CLN/cln-1.3.7.tar.bz2 >cln-1.3.7.tar.bz2 tar -xvf cln-1.3.7.tar.bz2 diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 9b82c565c32..bda6014b352 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -98,7 +98,7 @@ jobs: - os: ubuntu-latest python: '3.11' other: /singletest - category: "-m 'neos or importtest or simplification'" + category: "-m 'neos or importtest'" skip_doctest: 1 TARGET: linux PYENV: pip @@ -183,9 +183,9 @@ jobs: if: matrix.other == '/singletest' run: | cd .. - curl https://www.ginac.de/CLN/cln-1.3.6.tar.bz2 >cln-1.3.6.tar.bz2 - tar -xvf cln-1.3.6.tar.bz2 - cd cln-1.3.6 + curl https://www.ginac.de/CLN/cln-1.3.7.tar.bz2 >cln-1.3.7.tar.bz2 + tar -xvf cln-1.3.7.tar.bz2 + cd cln-1.3.7 ./configure make -j 2 sudo make install @@ -671,10 +671,14 @@ jobs: pyomo help --transformations || exit 1 pyomo help --writers || exit 1 + - name: Run Simplification Tests + if: matrix.other == '/singletest' + run: | + pytest -v -m 'simplification' pyomo.contrib.simplification/tests/test_simplification.py --junitxml="TEST-pyomo-simplify.xml" + - name: Run Pyomo tests if: matrix.mpi == 0 run: | - $PYTHON_EXE -c "from pyomo.contrib.simplification.ginac_interface import GinacInterface" $PYTHON_EXE -m pytest -v \ -W ignore::Warning ${{matrix.category}} \ pyomo `pwd`/pyomo-model-libraries \ From b5550fecb393d2ee9eeebba0e3df66b7360a10d9 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 22 Jan 2024 06:38:34 -0700 Subject: [PATCH 0846/1797] fixing simplification 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 d66451a00a5..83e652cbef8 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -652,7 +652,7 @@ jobs: - name: Run Simplification Tests if: matrix.other == '/singletest' run: | - pytest -v -m 'simplification' pyomo.contrib.simplification/tests/test_simplification.py --junitxml="TEST-pyomo-simplify.xml" + pytest -v -m 'simplification' pyomo/contrib/simplification/tests/test_simplification.py --junitxml="TEST-pyomo-simplify.xml" - name: Run Pyomo tests if: matrix.mpi == 0 diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index bda6014b352..6df28fbadc9 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -674,7 +674,7 @@ jobs: - name: Run Simplification Tests if: matrix.other == '/singletest' run: | - pytest -v -m 'simplification' pyomo.contrib.simplification/tests/test_simplification.py --junitxml="TEST-pyomo-simplify.xml" + pytest -v -m 'simplification' pyomo/contrib/simplification/tests/test_simplification.py --junitxml="TEST-pyomo-simplify.xml" - name: Run Pyomo tests if: matrix.mpi == 0 From 8984389e71d57fe7b9d56c80331ea207166f1290 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 22 Jan 2024 06:52:04 -0700 Subject: [PATCH 0847/1797] fixing simplification tests --- pyomo/core/expr/compare.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/core/expr/compare.py b/pyomo/core/expr/compare.py index ec8d56896b8..61ff8660a8b 100644 --- a/pyomo/core/expr/compare.py +++ b/pyomo/core/expr/compare.py @@ -196,7 +196,7 @@ def compare_expressions(expr1, expr2, include_named_exprs=True): ) try: res = pn1 == pn2 - except PyomoException: + except (PyomoException, AttributeError): res = False return res From 7d64e1725b1af5173c9ff94cb885913ee1c834c2 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Mon, 22 Jan 2024 15:10:07 -0700 Subject: [PATCH 0848/1797] 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 0849/1797] 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 0850/1797] 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 0851/1797] 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 0852/1797] 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 0853/1797] 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 0854/1797] 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 0855/1797] 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 0856/1797] 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 0857/1797] 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 0858/1797] 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 a49eb25f03380f6b871303d61591b0cc7be0b110 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 24 Jan 2024 13:01:06 -0500 Subject: [PATCH 0859/1797] add mindtpy call_before_subproblem_solve --- pyomo/contrib/mindtpy/algorithm_base_class.py | 13 +++++++++++++ pyomo/contrib/mindtpy/config_options.py | 9 +++++++++ pyomo/contrib/mindtpy/single_tree.py | 6 ++++++ 3 files changed, 28 insertions(+) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 7e8d390976c..f7f5e7601e5 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -2958,6 +2958,10 @@ def MindtPy_iteration_loop(self): skip_fixed=False, ) if self.curr_int_sol not in set(self.integer_list): + # Call the NLP pre-solve callback + with time_code(self.timing, 'Call after subproblem solve'): + config.call_before_subproblem_solve(self.fixed_nlp) + fixed_nlp, fixed_nlp_result = self.solve_subproblem() self.handle_nlp_subproblem_tc(fixed_nlp, fixed_nlp_result) @@ -2969,6 +2973,10 @@ def MindtPy_iteration_loop(self): # Solve NLP subproblem # The constraint linearization happens in the handlers if not config.solution_pool: + # Call the NLP pre-solve callback + with time_code(self.timing, 'Call after subproblem solve'): + config.call_before_subproblem_solve(self.fixed_nlp) + fixed_nlp, fixed_nlp_result = self.solve_subproblem() self.handle_nlp_subproblem_tc(fixed_nlp, fixed_nlp_result) @@ -3001,6 +3009,11 @@ def MindtPy_iteration_loop(self): continue else: self.integer_list.append(self.curr_int_sol) + + # Call the NLP pre-solve callback + with time_code(self.timing, 'Call after subproblem solve'): + config.call_before_subproblem_solve(self.fixed_nlp) + fixed_nlp, fixed_nlp_result = self.solve_subproblem() self.handle_nlp_subproblem_tc(fixed_nlp, fixed_nlp_result) diff --git a/pyomo/contrib/mindtpy/config_options.py b/pyomo/contrib/mindtpy/config_options.py index ed0c86baae9..b6dbbedd79c 100644 --- a/pyomo/contrib/mindtpy/config_options.py +++ b/pyomo/contrib/mindtpy/config_options.py @@ -312,6 +312,15 @@ def _add_common_configs(CONFIG): doc='Callback hook after a solution of the main problem.', ), ) + CONFIG.declare( + 'call_before_subproblem_solve', + ConfigValue( + default=_DoNothing(), + domain=None, + description='Function to be executed after every subproblem', + doc='Callback hook after a solution of the nonlinear subproblem.', + ), + ) CONFIG.declare( 'call_after_subproblem_solve', ConfigValue( diff --git a/pyomo/contrib/mindtpy/single_tree.py b/pyomo/contrib/mindtpy/single_tree.py index 228810a8f90..a82bb1ce541 100644 --- a/pyomo/contrib/mindtpy/single_tree.py +++ b/pyomo/contrib/mindtpy/single_tree.py @@ -774,6 +774,9 @@ def __call__(self): mindtpy_solver.integer_list.append(mindtpy_solver.curr_int_sol) # solve subproblem + # Call the NLP pre-solve callback + with time_code(self.timing, 'Call after subproblem solve'): + config.call_before_subproblem_solve(mindtpy_solver.fixed_nlp) # The constraint linearization happens in the handlers fixed_nlp, fixed_nlp_result = mindtpy_solver.solve_subproblem() # add oa cuts @@ -920,6 +923,9 @@ def LazyOACallback_gurobi(cb_m, cb_opt, cb_where, mindtpy_solver, config): cut_ind = len(mindtpy_solver.mip.MindtPy_utils.cuts.oa_cuts) # solve subproblem + # Call the NLP pre-solve callback + with time_code(self.timing, 'Call after subproblem solve'): + config.call_before_subproblem_solve(mindtpy_solver.fixed_nlp) # The constraint linearization happens in the handlers fixed_nlp, fixed_nlp_result = mindtpy_solver.solve_subproblem() From 1b9ac7ec32f1d5967c802d9e7568af0f3b77b593 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 24 Jan 2024 13:28:03 -0700 Subject: [PATCH 0860/1797] 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 3dc499592476144c69790c8fcead1da63958b874 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 24 Jan 2024 16:58:41 -0500 Subject: [PATCH 0861/1797] fix bug --- pyomo/contrib/mindtpy/single_tree.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/mindtpy/single_tree.py b/pyomo/contrib/mindtpy/single_tree.py index a82bb1ce541..77740ff15e3 100644 --- a/pyomo/contrib/mindtpy/single_tree.py +++ b/pyomo/contrib/mindtpy/single_tree.py @@ -775,7 +775,7 @@ def __call__(self): # solve subproblem # Call the NLP pre-solve callback - with time_code(self.timing, 'Call after subproblem solve'): + with time_code(mindtpy_solver.timing, 'Call after subproblem solve'): config.call_before_subproblem_solve(mindtpy_solver.fixed_nlp) # The constraint linearization happens in the handlers fixed_nlp, fixed_nlp_result = mindtpy_solver.solve_subproblem() @@ -924,7 +924,7 @@ def LazyOACallback_gurobi(cb_m, cb_opt, cb_where, mindtpy_solver, config): # solve subproblem # Call the NLP pre-solve callback - with time_code(self.timing, 'Call after subproblem solve'): + with time_code(mindtpy_solver.timing, 'Call after subproblem solve'): config.call_before_subproblem_solve(mindtpy_solver.fixed_nlp) # The constraint linearization happens in the handlers fixed_nlp, fixed_nlp_result = mindtpy_solver.solve_subproblem() From 19ed448f228647a2bb44dd47d1df1124dc14b3c0 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 24 Jan 2024 16:34:40 -0700 Subject: [PATCH 0862/1797] Finished parmest reactor_design example using new interface. --- .../reactor_design/bootstrap_example.py | 2 +- .../reactor_design/datarec_example.py | 2 +- .../reactor_design/leaveNout_example.py | 2 +- .../likelihood_ratio_example.py | 2 +- .../multisensor_data_example.py | 2 +- .../parameter_estimation_example.py | 6 +- .../reactor_design/timeseries_data_example.py | 5 +- .../reactor_design/bootstrap_example.py | 32 +- .../confidence_region_example.py | 49 +++ .../reactor_design/datarec_example.py | 98 ++++- .../reactor_design/leaveNout_example.py | 29 +- .../likelihood_ratio_example.py | 32 +- .../multisensor_data_example.py | 73 +++- .../parameter_estimation_example.py | 53 +-- .../examples/reactor_design/reactor_design.py | 137 ++++-- .../reactor_design/timeseries_data_example.py | 96 +++- pyomo/contrib/parmest/experiment.py | 15 + pyomo/contrib/parmest/parmest.py | 411 +++++++++--------- 18 files changed, 647 insertions(+), 399 deletions(-) create mode 100644 pyomo/contrib/parmest/examples/reactor_design/confidence_region_example.py create mode 100644 pyomo/contrib/parmest/experiment.py diff --git a/pyomo/contrib/parmest/deprecated/examples/reactor_design/bootstrap_example.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/bootstrap_example.py index e2d172f34f6..3820b78c9b1 100644 --- a/pyomo/contrib/parmest/deprecated/examples/reactor_design/bootstrap_example.py +++ b/pyomo/contrib/parmest/deprecated/examples/reactor_design/bootstrap_example.py @@ -12,7 +12,7 @@ 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 ( +from pyomo.contrib.parmest.deprecated.examples.reactor_design.reactor_design import ( reactor_design_model, ) diff --git a/pyomo/contrib/parmest/deprecated/examples/reactor_design/datarec_example.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/datarec_example.py index cfd3891c00e..bae538f364c 100644 --- a/pyomo/contrib/parmest/deprecated/examples/reactor_design/datarec_example.py +++ b/pyomo/contrib/parmest/deprecated/examples/reactor_design/datarec_example.py @@ -12,7 +12,7 @@ import numpy as np import pandas as pd import pyomo.contrib.parmest.parmest as parmest -from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( +from pyomo.contrib.parmest.deprecated.examples.reactor_design.reactor_design import ( reactor_design_model, ) diff --git a/pyomo/contrib/parmest/deprecated/examples/reactor_design/leaveNout_example.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/leaveNout_example.py index 6952a7fc733..d4ca9651753 100644 --- a/pyomo/contrib/parmest/deprecated/examples/reactor_design/leaveNout_example.py +++ b/pyomo/contrib/parmest/deprecated/examples/reactor_design/leaveNout_example.py @@ -13,7 +13,7 @@ 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 ( +from pyomo.contrib.parmest.deprecated.examples.reactor_design.reactor_design import ( reactor_design_model, ) diff --git a/pyomo/contrib/parmest/deprecated/examples/reactor_design/likelihood_ratio_example.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/likelihood_ratio_example.py index a0fe6f22305..c47acf7f932 100644 --- a/pyomo/contrib/parmest/deprecated/examples/reactor_design/likelihood_ratio_example.py +++ b/pyomo/contrib/parmest/deprecated/examples/reactor_design/likelihood_ratio_example.py @@ -14,7 +14,7 @@ from itertools import product from os.path import join, abspath, dirname import pyomo.contrib.parmest.parmest as parmest -from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( +from pyomo.contrib.parmest.deprecated.examples.reactor_design.reactor_design import ( reactor_design_model, ) diff --git a/pyomo/contrib/parmest/deprecated/examples/reactor_design/multisensor_data_example.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/multisensor_data_example.py index a92ac626fae..84c4abdf92a 100644 --- a/pyomo/contrib/parmest/deprecated/examples/reactor_design/multisensor_data_example.py +++ b/pyomo/contrib/parmest/deprecated/examples/reactor_design/multisensor_data_example.py @@ -12,7 +12,7 @@ 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 ( +from pyomo.contrib.parmest.deprecated.examples.reactor_design.reactor_design import ( reactor_design_model, ) diff --git a/pyomo/contrib/parmest/deprecated/examples/reactor_design/parameter_estimation_example.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/parameter_estimation_example.py index 581d3904c04..67b69c73555 100644 --- a/pyomo/contrib/parmest/deprecated/examples/reactor_design/parameter_estimation_example.py +++ b/pyomo/contrib/parmest/deprecated/examples/reactor_design/parameter_estimation_example.py @@ -12,7 +12,7 @@ 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 ( +from pyomo.contrib.parmest.deprecated.examples.reactor_design.reactor_design import ( reactor_design_model, ) @@ -41,7 +41,9 @@ def SSE(model, data): # Parameter estimation obj, theta = pest.theta_est() - + print (obj) + print(theta) + # Assert statements compare parameter estimation (theta) to an expected value k1_expected = 5.0 / 6.0 k2_expected = 5.0 / 3.0 diff --git a/pyomo/contrib/parmest/deprecated/examples/reactor_design/timeseries_data_example.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/timeseries_data_example.py index da2ab1874c9..e7acefc2224 100644 --- a/pyomo/contrib/parmest/deprecated/examples/reactor_design/timeseries_data_example.py +++ b/pyomo/contrib/parmest/deprecated/examples/reactor_design/timeseries_data_example.py @@ -13,9 +13,10 @@ from os.path import join, abspath, dirname import pyomo.contrib.parmest.parmest as parmest -from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( +from pyomo.contrib.parmest.deprecated.examples.reactor_design.reactor_design import ( reactor_design_model, ) +from pyomo.contrib.parmest.deprecated.parmest import group_data def main(): @@ -31,7 +32,7 @@ def main(): # Group time series data into experiments, return the mean value for sv and caf # Returns a list of dictionaries - data_ts = parmest.group_data(data, 'experiment', ['sv', 'caf']) + data_ts = group_data(data, 'experiment', ['sv', 'caf']) def SSE_timeseries(model, data): expr = 0 diff --git a/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py b/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py index e2d172f34f6..b5cb4196456 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py @@ -13,31 +13,26 @@ from os.path import join, abspath, dirname import pyomo.contrib.parmest.parmest as parmest from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( - reactor_design_model, + ReactorDesignExperiment, ) - def main(): - # Vars to estimate - theta_names = ["k1", "k2", "k3"] - # Data + # Read in data file_dirname = dirname(abspath(str(__file__))) 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 - ) - return expr - - # Create an instance of the parmest estimator - pest = parmest.Estimator(reactor_design_model, data, theta_names, SSE) + + # Create an experiment list + exp_list= [] + for i in range(data.shape[0]): + exp_list.append(ReactorDesignExperiment(data, i)) + + # View one model + # exp0_model = exp_list[0].get_labeled_model() + # print(exp0_model.pprint()) + + pest = parmest.Estimator(exp_list, obj_function='SSE') # Parameter estimation obj, theta = pest.theta_est() @@ -55,6 +50,5 @@ def SSE(model, data): title="Bootstrap theta with confidence regions", ) - if __name__ == "__main__": main() diff --git a/pyomo/contrib/parmest/examples/reactor_design/confidence_region_example.py b/pyomo/contrib/parmest/examples/reactor_design/confidence_region_example.py new file mode 100644 index 00000000000..ff84279018d --- /dev/null +++ b/pyomo/contrib/parmest/examples/reactor_design/confidence_region_example.py @@ -0,0 +1,49 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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 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 ( + ReactorDesignExperiment, +) + +def main(): + + # Read in data + file_dirname = dirname(abspath(str(__file__))) + file_name = abspath(join(file_dirname, "reactor_data.csv")) + data = pd.read_csv(file_name) + + # Create an experiment list + exp_list= [] + for i in range(data.shape[0]): + exp_list.append(ReactorDesignExperiment(data, i)) + + # View one model + # exp0_model = exp_list[0].get_labeled_model() + # print(exp0_model.pprint()) + + pest = parmest.Estimator(exp_list, obj_function='SSE') + + # Parameter estimation + obj, theta = pest.theta_est() + + # Bootstrapping + bootstrap_theta = pest.theta_est_bootstrap(10) + print(bootstrap_theta) + + # Confidence region test + CR = pest.confidence_region_test(bootstrap_theta, "MVN", [0.5, 0.75, 1.0]) + print(CR) + +if __name__ == "__main__": + main() diff --git a/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py b/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py index cfd3891c00e..26185290ea6 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py @@ -11,23 +11,80 @@ import numpy as np import pandas as pd +import pyomo.environ as pyo import pyomo.contrib.parmest.parmest as parmest from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( reactor_design_model, + ReactorDesignExperiment, ) np.random.seed(1234) -def reactor_design_model_for_datarec(data): +def reactor_design_model_for_datarec(): + # Unfix inlet concentration for data rec - model = reactor_design_model(data) + model = reactor_design_model() model.caf.fixed = False return model +class ReactorDesignExperimentPreDataRec(ReactorDesignExperiment): + + def __init__(self, data, data_std, experiment_number): + + super().__init__(data, experiment_number) + self.data_std = data_std + + def create_model(self): + self.model = m = reactor_design_model_for_datarec() + return m + + def label_model(self): + + m = self.model + + # experiment outputs + m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) + m.experiment_outputs.update([(m.ca, self.data_i['ca'])]) + m.experiment_outputs.update([(m.cb, self.data_i['cb'])]) + m.experiment_outputs.update([(m.cc, self.data_i['cc'])]) + m.experiment_outputs.update([(m.cd, self.data_i['cd'])]) + + # experiment standard deviations + m.experiment_outputs_std = pyo.Suffix(direction=pyo.Suffix.LOCAL) + m.experiment_outputs_std.update([(m.ca, self.data_std['ca'])]) + m.experiment_outputs_std.update([(m.cb, self.data_std['cb'])]) + m.experiment_outputs_std.update([(m.cc, self.data_std['cc'])]) + m.experiment_outputs_std.update([(m.cd, self.data_std['cd'])]) + + # no unknowns (theta names) + m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) + + return m + +class ReactorDesignExperimentPostDataRec(ReactorDesignExperiment): + + def __init__(self, data, data_std, experiment_number): + + super().__init__(data, experiment_number) + self.data_std = data_std + + def label_model(self): + + m = super().label_model() + + # add experiment standard deviations + m.experiment_outputs_std = pyo.Suffix(direction=pyo.Suffix.LOCAL) + m.experiment_outputs_std.update([(m.ca, self.data_std['ca'])]) + m.experiment_outputs_std.update([(m.cb, self.data_std['cb'])]) + m.experiment_outputs_std.update([(m.cc, self.data_std['cc'])]) + m.experiment_outputs_std.update([(m.cd, self.data_std['cd'])]) + + return m def generate_data(): + ### Generate data based on real sv, caf, ca, cb, cc, and cd sv_real = 1.05 caf_real = 10000 @@ -54,29 +111,34 @@ def generate_data(): def main(): + # Generate data data = generate_data() data_std = data.std() + # Create an experiment list + exp_list= [] + for i in range(data.shape[0]): + exp_list.append(ReactorDesignExperimentPreDataRec(data, data_std, i)) + # 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 - ) + def SSE(model): + expr = sum(((y - yhat)/model.experiment_outputs_std[y])**2 + for y, yhat in model.experiment_outputs.items()) return expr - ### Data reconciliation - theta_names = [] # no variables to estimate, use initialized values + # View one model & SSE + # exp0_model = exp_list[0].get_labeled_model() + # print(exp0_model.pprint()) + # print(SSE(exp0_model)) - pest = parmest.Estimator(reactor_design_model_for_datarec, data, theta_names, SSE) + ### Data reconciliation + pest = parmest.Estimator(exp_list, obj_function=SSE) 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"]], @@ -84,14 +146,18 @@ def SSE(model, data): ) ### Parameter estimation using reconciled data - theta_names = ["k1", "k2", "k3"] data_rec["sv"] = data["sv"] - pest = parmest.Estimator(reactor_design_model, data_rec, theta_names, SSE) + # make a new list of experiments using reconciled data + exp_list= [] + for i in range(data_rec.shape[0]): + exp_list.append(ReactorDesignExperimentPostDataRec(data_rec, data_std, i)) + + pest = parmest.Estimator(exp_list, obj_function=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} 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 6952a7fc733..549233d8a84 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.py @@ -14,19 +14,17 @@ from os.path import join, abspath, dirname import pyomo.contrib.parmest.parmest as parmest from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( - reactor_design_model, + ReactorDesignExperiment, ) def main(): - # Vars to estimate - theta_names = ["k1", "k2", "k3"] - # Data + # Read in data file_dirname = dirname(abspath(str(__file__))) file_name = abspath(join(file_dirname, "reactor_data.csv")) data = pd.read_csv(file_name) - + # Create more data for the example N = 50 df_std = data.std().to_frame().transpose() @@ -34,18 +32,16 @@ def main(): df_sample = data.sample(N, replace=True).reset_index(drop=True) data = df_sample + df_rand.dot(df_std) / 10 - # 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 - ) - return expr + # Create an experiment list + exp_list= [] + for i in range(data.shape[0]): + exp_list.append(ReactorDesignExperiment(data, i)) + + # View one model + # exp0_model = exp_list[0].get_labeled_model() + # print(exp0_model.pprint()) - # Create an instance of the parmest estimator - pest = parmest.Estimator(reactor_design_model, data, theta_names, SSE) + pest = parmest.Estimator(exp_list, obj_function='SSE') # Parameter estimation obj, theta = pest.theta_est() @@ -93,6 +89,5 @@ def SSE(model, data): percent_true = sum(r) / len(r) print(percent_true) - if __name__ == "__main__": main() 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..8b6d9fcfecc 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.py @@ -15,31 +15,27 @@ from os.path import join, abspath, dirname import pyomo.contrib.parmest.parmest as parmest from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( - reactor_design_model, + ReactorDesignExperiment, ) def main(): - # Vars to estimate - theta_names = ["k1", "k2", "k3"] - - # Data + +# Read in data file_dirname = dirname(abspath(str(__file__))) 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 - ) - return expr - - # Create an instance of the parmest estimator - pest = parmest.Estimator(reactor_design_model, data, theta_names, SSE) + + # Create an experiment list + exp_list= [] + for i in range(data.shape[0]): + exp_list.append(ReactorDesignExperiment(data, i)) + + # View one model + # exp0_model = exp_list[0].get_labeled_model() + # print(exp0_model.pprint()) + + pest = parmest.Estimator(exp_list, obj_function='SSE') # Parameter estimation obj, theta = pest.theta_est() 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..f731032368e 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py @@ -11,37 +11,76 @@ import pandas as pd from os.path import join, abspath, dirname +import pyomo.environ as pyo import pyomo.contrib.parmest.parmest as parmest from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( - reactor_design_model, + ReactorDesignExperiment, ) +class MultisensorReactorDesignExperiment(ReactorDesignExperiment): + + def finalize_model(self): + + m = self.model + + # Experiment inputs values + m.sv = self.data_i['sv'] + m.caf = self.data_i['caf'] + + # Experiment output values + m.ca = (self.data_i['ca1'] + self.data_i['ca2'] + self.data_i['ca3']) * (1/3) + m.cb = self.data_i['cb'] + m.cc = (self.data_i['cc1'] + self.data_i['cc2']) * (1/2) + m.cd = self.data_i['cd'] + + return m + + def label_model(self): + + m = self.model + + m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) + m.experiment_outputs.update([(m.ca, [self.data_i['ca1'], self.data_i['ca2'], self.data_i['ca3']])]) + m.experiment_outputs.update([(m.cb, [self.data_i['cb']])]) + m.experiment_outputs.update([(m.cc, [self.data_i['cc1'], self.data_i['cc2']])]) + m.experiment_outputs.update([(m.cd, [self.data_i['cd']])]) + + m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) + m.unknown_parameters.update((k, pyo.ComponentUID(k)) + for k in [m.k1, m.k2, m.k3]) + + return m + + def main(): # Parameter estimation using multisensor data - # Vars to estimate - theta_names = ["k1", "k2", "k3"] - - # Data, includes multiple sensors for ca and cc + # Read in data file_dirname = dirname(abspath(str(__file__))) file_name = abspath(join(file_dirname, "reactor_data_multisensor.csv")) data = pd.read_csv(file_name) + + # Create an experiment list + exp_list= [] + for i in range(data.shape[0]): + exp_list.append(MultisensorReactorDesignExperiment(data, i)) - # 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 - ) + # Define sum of squared error + def SSE_multisensor(model): + expr = 0 + for y, yhat in model.experiment_outputs.items(): + num_outputs = len(yhat) + for i in range(num_outputs): + expr += ((y - yhat[i])**2) * (1 / num_outputs) return expr + + # View one model + # exp0_model = exp_list[0].get_labeled_model() + # print(exp0_model.pprint()) + # print(SSE_multisensor(exp0_model)) - pest = parmest.Estimator(reactor_design_model, data, theta_names, SSE_multisensor) + pest = parmest.Estimator(exp_list, obj_function=SSE_multisensor) obj, theta = pest.theta_est() print(obj) print(theta) 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..76744984cce 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.py @@ -13,46 +13,29 @@ from os.path import join, abspath, dirname import pyomo.contrib.parmest.parmest as parmest from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( - reactor_design_model, + ReactorDesignExperiment, ) def main(): - # Vars to estimate - theta_names = ["k1", "k2", "k3"] - # Data + # Read in data file_dirname = dirname(abspath(str(__file__))) 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 - ) - return expr - - # Create an instance of the parmest estimator - pest = parmest.Estimator(reactor_design_model, data, theta_names, SSE) - - # Parameter estimation - obj, theta = pest.theta_est() - - # Assert statements compare parameter estimation (theta) to an expected value - 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 - assert relative_error < 0.05 - relative_error = abs(theta["k2"] - k2_expected) / k2_expected - assert relative_error < 0.05 - relative_error = abs(theta["k3"] - k3_expected) / k3_expected - assert relative_error < 0.05 - - -if __name__ == "__main__": - main() + + # Create an experiment list + exp_list= [] + for i in range(data.shape[0]): + exp_list.append(ReactorDesignExperiment(data, i)) + + # View one model + # exp0_model = exp_list[0].get_labeled_model() + # print(exp0_model.pprint()) + + pest = parmest.Estimator(exp_list, obj_function='SSE') + + # Parameter estimation with covariance + obj, theta, cov = pest.theta_est(calc_cov=True, cov_n=17) + print(obj) + print(theta) \ No newline at end of file diff --git a/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py b/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py index 16f65e236eb..1479009abcc 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py +++ b/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py @@ -12,57 +12,42 @@ Continuously stirred tank reactor model, based on pyomo/examples/doc/pyomobook/nonlinear-ch/react_design/ReactorDesign.py """ +from os.path import join, abspath, dirname +from itertools import product import pandas as pd -from pyomo.environ import ( - ConcreteModel, - Param, - Var, - PositiveReals, - Objective, - Constraint, - maximize, - SolverFactory, -) - - -def reactor_design_model(data): + +import pyomo.environ as pyo +import pyomo.contrib.parmest.parmest as parmest + +from pyomo.contrib.parmest.experiment import Experiment + +def reactor_design_model(): + # Create the concrete model - model = ConcreteModel() + model = pyo.ConcreteModel() # Rate constants - model.k1 = Param(initialize=5.0 / 6.0, within=PositiveReals, mutable=True) # min^-1 - model.k2 = Param(initialize=5.0 / 3.0, within=PositiveReals, mutable=True) # min^-1 - model.k3 = Param( - initialize=1.0 / 6000.0, within=PositiveReals, mutable=True - ) # m^3/(gmol min) + model.k1 = pyo.Param(initialize=5.0 / 6.0, within=pyo.PositiveReals, mutable=True) # min^-1 + model.k2 = pyo.Param(initialize=5.0 / 3.0, within=pyo.PositiveReals, mutable=True) # min^-1 + model.k3 = pyo.Param(initialize=1.0 / 6000.0, within=pyo.PositiveReals, mutable=True) # m^3/(gmol min) # 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) - elif isinstance(data, pd.DataFrame): - model.caf = Param(initialize=float(data.iloc[0]["caf"]), within=PositiveReals) - else: - raise ValueError("Unrecognized data type.") - + model.caf = pyo.Param(initialize=10000, within=pyo.PositiveReals, mutable=True) + # Space velocity (flowrate/volume) - if isinstance(data, dict) or isinstance(data, pd.Series): - 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) - else: - raise ValueError("Unrecognized data type.") + model.sv = pyo.Param(initialize=1.0, within=pyo.PositiveReals, mutable=True) # Outlet concentration of each component - model.ca = Var(initialize=5000.0, within=PositiveReals) - model.cb = Var(initialize=2000.0, within=PositiveReals) - model.cc = Var(initialize=2000.0, within=PositiveReals) - model.cd = Var(initialize=1000.0, within=PositiveReals) + model.ca = pyo.Var(initialize=5000.0, within=pyo.PositiveReals) + model.cb = pyo.Var(initialize=2000.0, within=pyo.PositiveReals) + model.cc = pyo.Var(initialize=2000.0, within=pyo.PositiveReals) + model.cd = pyo.Var(initialize=1000.0, within=pyo.PositiveReals) # Objective - model.obj = Objective(expr=model.cb, sense=maximize) + model.obj = pyo.Objective(expr=model.cb, sense=pyo.maximize) # Constraints - model.ca_bal = Constraint( + model.ca_bal = pyo.Constraint( expr=( 0 == model.sv * model.caf @@ -72,33 +57,89 @@ def reactor_design_model(data): ) ) - model.cb_bal = Constraint( + model.cb_bal = pyo.Constraint( expr=(0 == -model.sv * model.cb + model.k1 * model.ca - model.k2 * model.cb) ) - model.cc_bal = Constraint(expr=(0 == -model.sv * model.cc + model.k2 * model.cb)) + model.cc_bal = pyo.Constraint(expr=(0 == -model.sv * model.cc + model.k2 * model.cb)) - model.cd_bal = Constraint( + model.cd_bal = pyo.Constraint( expr=(0 == -model.sv * model.cd + model.k3 * model.ca**2.0) ) return model + +class ReactorDesignExperiment(Experiment): + + def __init__(self, data, experiment_number): + self.data = data + self.experiment_number = experiment_number + self.data_i = data.loc[experiment_number,:] + self.model = None + + def create_model(self): + self.model = m = reactor_design_model() + return m + + def finalize_model(self): + m = self.model + + # Experiment inputs values + m.sv = self.data_i['sv'] + m.caf = self.data_i['caf'] + + # Experiment output values + m.ca = self.data_i['ca'] + m.cb = self.data_i['cb'] + m.cc = self.data_i['cc'] + m.cd = self.data_i['cd'] + + return m + + def label_model(self): + m = self.model + + m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) + m.experiment_outputs.update([(m.ca, self.data_i['ca'])]) + m.experiment_outputs.update([(m.cb, self.data_i['cb'])]) + m.experiment_outputs.update([(m.cc, self.data_i['cc'])]) + m.experiment_outputs.update([(m.cd, self.data_i['cd'])]) + + m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) + m.unknown_parameters.update((k, pyo.ComponentUID(k)) + for k in [m.k1, m.k2, m.k3]) + + return m + + def get_labeled_model(self): + m = self.create_model() + m = self.finalize_model() + m = self.label_model() + + return m +if __name__ == "__main__": -def main(): # For a range of sv values, return ca, cb, cc, and cd results = [] 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") + + # make model + model = reactor_design_model() + + # add caf, sv + model.caf = caf + model.sv = sv + + # solve model + solver = pyo.SolverFactory("ipopt") solver.solve(model) + + # save results results.append([sv, caf, model.ca(), model.cb(), model.cc(), model.cd()]) results = pd.DataFrame(results, columns=["sv", "caf", "ca", "cb", "cc", "cd"]) print(results) - - -if __name__ == "__main__": - main() + \ No newline at end of file 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..a9a5ab20b54 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/timeseries_data_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/timeseries_data_example.py @@ -14,16 +14,74 @@ import pyomo.contrib.parmest.parmest as parmest from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( - reactor_design_model, + ReactorDesignExperiment, ) +class TimeSeriesReactorDesignExperiment(ReactorDesignExperiment): + + def __init__(self, data, experiment_number): + self.data = data + self.experiment_number = experiment_number + self.data_i = data[experiment_number] + self.model = None + + def finalize_model(self): + m = self.model + + # Experiment inputs values + m.sv = self.data_i['sv'] + m.caf = self.data_i['caf'] + + # Experiment output values + m.ca = self.data_i['ca'][0] + m.cb = self.data_i['cb'][0] + m.cc = self.data_i['cc'][0] + m.cd = self.data_i['cd'][0] + + return m + + +def group_data(data, groupby_column_name, use_mean=None): + """ + Group data by scenario + + Parameters + ---------- + data: DataFrame + Data + groupby_column_name: strings + Name of data column which contains scenario numbers + use_mean: list of column names or None, optional + Name of data columns which should be reduced to a single value per + scenario by taking the mean + + Returns + ---------- + grouped_data: list of dictionaries + Grouped data + """ + if use_mean is None: + use_mean_list = [] + else: + use_mean_list = use_mean + + grouped_data = [] + for exp_num, group in data.groupby(data[groupby_column_name]): + d = {} + for col in group.columns: + if col in use_mean_list: + d[col] = group[col].mean() + else: + d[col] = list(group[col]) + grouped_data.append(d) + + return grouped_data + + def main(): # Parameter estimation using timeseries data - # Vars to estimate - 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_timeseries.csv')) @@ -31,21 +89,29 @@ def main(): # Group time series data into experiments, return the mean value for sv and caf # Returns a list of dictionaries - data_ts = parmest.group_data(data, 'experiment', ['sv', 'caf']) + data_ts = group_data(data, 'experiment', ['sv', 'caf']) + + # Create an experiment list + exp_list= [] + for i in range(len(data_ts)): + exp_list.append(TimeSeriesReactorDesignExperiment(data_ts, i)) + + def SSE_timeseries(model): - def SSE_timeseries(model, data): expr = 0 - for val in data['ca']: - expr = expr + ((float(val) - model.ca) ** 2) * (1 / len(data['ca'])) - for val in data['cb']: - expr = expr + ((float(val) - model.cb) ** 2) * (1 / len(data['cb'])) - for val in data['cc']: - expr = expr + ((float(val) - model.cc) ** 2) * (1 / len(data['cc'])) - for val in data['cd']: - expr = expr + ((float(val) - model.cd) ** 2) * (1 / len(data['cd'])) + for y, yhat in model.experiment_outputs.items(): + num_time_points = len(yhat) + for i in range(num_time_points): + expr += ((y - yhat[i])**2) * (1 / num_time_points) + return expr - pest = parmest.Estimator(reactor_design_model, data_ts, theta_names, SSE_timeseries) + # View one model & SSE + # exp0_model = exp_list[0].get_labeled_model() + # print(exp0_model.pprint()) + # print(SSE_timeseries(exp0_model)) + + pest = parmest.Estimator(exp_list, obj_function=SSE_timeseries) obj, theta = pest.theta_est() print(obj) print(theta) diff --git a/pyomo/contrib/parmest/experiment.py b/pyomo/contrib/parmest/experiment.py new file mode 100644 index 00000000000..73b18bb5975 --- /dev/null +++ b/pyomo/contrib/parmest/experiment.py @@ -0,0 +1,15 @@ +# The experiment class is a template for making experiment lists +# to pass to parmest. An experiment is a pyomo model "m" which has +# additional suffixes: +# m.experiment_outputs -- which variables are experiment outputs +# m.unknown_parameters -- which variables are parameters to estimate +# The experiment class has only one required method: +# get_labeled_model() +# which returns the labeled pyomo model. + +class Experiment: + def __init__(self, model=None): + self.model = model + + def get_labeled_model(self): + return self.model \ No newline at end of file diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index 1f9b8b645b8..a00671c2ea6 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -11,9 +11,22 @@ #### Using mpi-sppy instead of PySP; May 2020 #### Adding option for "local" EF starting Sept 2020 #### Wrapping mpi-sppy functionality and local option Jan 2021, Feb 2021 +#### Redesign with Experiment class Dec 2023 # TODO: move use_mpisppy to a Pyomo configuration option -# + +# Redesign TODOS +# TODO: remove group_data,this is only used in 1 example and should be handled by the user in Experiment +# TODO: _treemaker is not used in parmest, the code could be moved to scenario tree if needed +# TODO: Create additional built in objective expressions in an Enum class which includes SSE (see SSE function below) +# TODO: Clean up the use of theta_names through out the code. The Experiment returns the CUID of each theta and this can be used directly (instead of the name) +# TODO: Clean up the use of updated_theta_names, model_theta_names, estimator_theta_names. Not sure if estimator_theta_names is the union or intersect of thetas in each model +# TODO: _return_theta_names should no longer be needed +# TODO: generally, theta ordering is not preserved by pyomo, so we should check that ordering +# matches values for each function, otherwise results will be wrong and/or inconsistent +# TODO: return model object (m.k1) and CUIDs in dataframes instead of names ("k1") + + # False implies always use the EF that is local to parmest use_mpisppy = True # Use it if we can but use local if not. if use_mpisppy: @@ -224,90 +237,92 @@ def _experiment_instance_creation_callback( return instance -# ============================================= -def _treemaker(scenlist): - """ - Makes a scenario tree (avoids dependence on daps) - - Parameters - ---------- - scenlist (list of `int`): experiment (i.e. scenario) numbers - - Returns - ------- - a `ConcreteModel` that is the scenario tree - """ - - num_scenarios = len(scenlist) - m = scenario_tree.tree_structure_model.CreateAbstractScenarioTreeModel() - m = m.create_instance() - m.Stages.add('Stage1') - m.Stages.add('Stage2') - m.Nodes.add('RootNode') - for i in scenlist: - m.Nodes.add('LeafNode_Experiment' + str(i)) - m.Scenarios.add('Experiment' + str(i)) - m.NodeStage['RootNode'] = 'Stage1' - m.ConditionalProbability['RootNode'] = 1.0 - for node in m.Nodes: - if node != 'RootNode': - m.NodeStage[node] = 'Stage2' - m.Children['RootNode'].add(node) - m.Children[node].clear() - m.ConditionalProbability[node] = 1.0 / num_scenarios - m.ScenarioLeafNode[node.replace('LeafNode_', '')] = node - - return m - - -def group_data(data, groupby_column_name, use_mean=None): - """ - Group data by scenario - - Parameters - ---------- - data: DataFrame - Data - groupby_column_name: strings - Name of data column which contains scenario numbers - use_mean: list of column names or None, optional - Name of data columns which should be reduced to a single value per - scenario by taking the mean - - Returns - ---------- - grouped_data: list of dictionaries - Grouped data - """ - if use_mean is None: - use_mean_list = [] - else: - use_mean_list = use_mean - - grouped_data = [] - for exp_num, group in data.groupby(data[groupby_column_name]): - d = {} - for col in group.columns: - if col in use_mean_list: - d[col] = group[col].mean() - else: - d[col] = list(group[col]) - grouped_data.append(d) - - return grouped_data - +# # ============================================= +# def _treemaker(scenlist): +# """ +# Makes a scenario tree (avoids dependence on daps) + +# Parameters +# ---------- +# scenlist (list of `int`): experiment (i.e. scenario) numbers + +# Returns +# ------- +# a `ConcreteModel` that is the scenario tree +# """ + +# num_scenarios = len(scenlist) +# m = scenario_tree.tree_structure_model.CreateAbstractScenarioTreeModel() +# m = m.create_instance() +# m.Stages.add('Stage1') +# m.Stages.add('Stage2') +# m.Nodes.add('RootNode') +# for i in scenlist: +# m.Nodes.add('LeafNode_Experiment' + str(i)) +# m.Scenarios.add('Experiment' + str(i)) +# m.NodeStage['RootNode'] = 'Stage1' +# m.ConditionalProbability['RootNode'] = 1.0 +# for node in m.Nodes: +# if node != 'RootNode': +# m.NodeStage[node] = 'Stage2' +# m.Children['RootNode'].add(node) +# m.Children[node].clear() +# m.ConditionalProbability[node] = 1.0 / num_scenarios +# m.ScenarioLeafNode[node.replace('LeafNode_', '')] = node + +# return m + + +# def group_data(data, groupby_column_name, use_mean=None): +# """ +# Group data by scenario + +# Parameters +# ---------- +# data: DataFrame +# Data +# groupby_column_name: strings +# Name of data column which contains scenario numbers +# use_mean: list of column names or None, optional +# Name of data columns which should be reduced to a single value per +# scenario by taking the mean + +# Returns +# ---------- +# grouped_data: list of dictionaries +# Grouped data +# """ +# if use_mean is None: +# use_mean_list = [] +# else: +# use_mean_list = use_mean + +# grouped_data = [] +# for exp_num, group in data.groupby(data[groupby_column_name]): +# d = {} +# for col in group.columns: +# if col in use_mean_list: +# d[col] = group[col].mean() +# else: +# d[col] = list(group[col]) +# grouped_data.append(d) + +# return grouped_data + +def SSE(model): + expr = sum((y - yhat)**2 for y, yhat in model.experiment_outputs.items()) + return expr class _SecondStageCostExpr(object): """ Class to pass objective expression into the Pyomo model """ - def __init__(self, ssc_function, data): + def __init__(self, ssc_function): self._ssc_function = ssc_function - self._data = data def __call__(self, model): - return self._ssc_function(model, self._data) + return self._ssc_function(model) class Estimator(object): @@ -316,17 +331,12 @@ class Estimator(object): Parameters ---------- - model_function: function - Function that generates an instance of the Pyomo model using 'data' - as the input argument - data: pd.DataFrame, list of dictionaries, list of dataframes, or list of json file names - Data that is used to build an instance of the Pyomo model and build - the objective function - theta_names: list of strings - List of Var names to estimate - obj_function: function, optional - Function used to formulate parameter estimation objective, generally - sum of squared error between measurements and model variables. + experiement_list: list of Experiments + A list of experiment objects which creates one labeled model for + each expeirment + obj_function: string or function (optional) + Built in objective (currently only "SSE") or custom function used to + formulate parameter estimation objective. If no function is specified, the model is used "as is" and should be defined with a "FirstStageCost" and "SecondStageCost" expression that are used to build an objective. @@ -342,54 +352,49 @@ class Estimator(object): # from parmest_deprecated as well as the new inputs using experiment lists def __init__(self, *args, **kwargs): + # check that we have at least one argument + assert(len(args) > 0) + # use deprecated interface self.pest_deprecated = None - if len(args) > 1: + if callable(args[0]): logger.warning('Using deprecated parmest inputs (model_function, ' + 'data, theta_names), please use experiment lists instead.') self.pest_deprecated = parmest_deprecated.Estimator(*args, **kwargs) return - print("New parmest interface using Experiment lists coming soon!") - exit() - - # def __init__( - # self, - # model_function, - # data, - # theta_names, - # obj_function=None, - # tee=False, - # diagnostic_mode=False, - # solver_options=None, - # ): - - self.model_function = model_function - - assert isinstance( - data, (list, pd.DataFrame) - ), "Data must be a list or DataFrame" - # convert dataframe into a list of dataframes, each row = one scenario - if isinstance(data, pd.DataFrame): - self.callback_data = [ - data.loc[i, :].to_frame().transpose() for i in data.index - ] - else: - self.callback_data = data - assert isinstance( - self.callback_data[0], (dict, pd.DataFrame, str) - ), "The scenarios in data must be a dictionary, DataFrame or filename" - - if len(theta_names) == 0: - self.theta_names = ['parmest_dummy_var'] - else: - self.theta_names = theta_names - - self.obj_function = obj_function - self.tee = tee - self.diagnostic_mode = diagnostic_mode - self.solver_options = solver_options + # check that we have a (non-empty) list of experiments + assert (isinstance(args[0], list)) + assert (len(args[0]) > 0) + self.exp_list = args[0] + # check that an experiment has experiment_outputs and unknown_parameters + model = self.exp_list[0].get_labeled_model() + try: + outputs = [k.name for k,v in model.experiment_outputs.items()] + except: + RuntimeError('Experiment list model does not have suffix ' + + '"experiment_outputs".') + try: + parms = [k.name for k,v in model.unknown_parameters.items()] + except: + RuntimeError('Experiment list model does not have suffix ' + + '"unknown_parameters".') + + # populate keyword argument options + self.obj_function = kwargs.get('obj_function', None) + self.tee = kwargs.get('tee', False) + self.diagnostic_mode = kwargs.get('diagnostic_mode', False) + self.solver_options = kwargs.get('solver_options', None) + + # TODO This might not be needed here. + # We could collect the union (or intersect?) of thetas when the models are built + theta_names = [] + for experiment in self.exp_list: + model = experiment.get_labeled_model() + theta_names.extend([k.name for k,v in model.unknown_parameters.items()]) + self.estimator_theta_names = list(set(theta_names)) + self._second_stage_cost_exp = "SecondStageCost" # boolean to indicate if model is initialized using a square solve self.model_initialized = False @@ -412,17 +417,26 @@ def _return_theta_names(self): ) # default theta_names, created when Estimator object is created else: - return None - def _create_parmest_model(self, data): + # if fitted model parameter names differ from theta_names + # created when Estimator object is created + if hasattr(self, 'theta_names_updated'): + return self.theta_names_updated + + else: + return ( + self.estimator_theta_names + ) # default theta_names, created when Estimator object is created + + def _create_parmest_model(self, experiment_number): """ Modify the Pyomo model for parameter estimation """ - model = self.model_function(data) - if (len(self.theta_names) == 1) and ( - self.theta_names[0] == 'parmest_dummy_var' - ): + model = self.exp_list[experiment_number].get_labeled_model() + self.theta_names = [k.name for k,v in model.unknown_parameters.items()] + + if len(model.unknown_parameters) == 0: model.parmest_dummy_var = pyo.Var(initialize=1.0) # Add objective function (optional) @@ -441,10 +455,17 @@ def _create_parmest_model(self, data): "Parmest will not override the existing model Expression named " + expr.name ) + + # TODO, this needs to be turned a enum class of options that still support custom functions + if self.obj_function == 'SSE': + second_stage_rule=_SecondStageCostExpr(SSE) + else: + # A custom function uses model.experiment_outputs as data + second_stage_rule = _SecondStageCostExpr(self.obj_function) + model.FirstStageCost = pyo.Expression(expr=0) - model.SecondStageCost = pyo.Expression( - rule=_SecondStageCostExpr(self.obj_function, data) - ) + model.SecondStageCost = pyo.Expression(rule=second_stage_rule) + def TotalCost_rule(model): return model.FirstStageCost + model.SecondStageCost @@ -479,20 +500,7 @@ def TotalCost_rule(model): return model def _instance_creation_callback(self, experiment_number=None, cb_data=None): - # cb_data is a list of dictionaries, list of dataframes, OR list of json file names - exp_data = cb_data[experiment_number] - if isinstance(exp_data, (dict, pd.DataFrame)): - pass - elif isinstance(exp_data, str): - try: - with open(exp_data, 'r') as infile: - exp_data = json.load(infile) - except: - raise RuntimeError(f'Could not read {exp_data} as json') - else: - raise RuntimeError(f'Unexpected data format for cb_data={cb_data}') - model = self._create_parmest_model(exp_data) - + model = self._create_parmest_model(experiment_number) return model def _Q_opt( @@ -514,7 +522,7 @@ def _Q_opt( # (Bootstrap scenarios will use indirection through the bootlist) if bootlist is None: - scenario_numbers = list(range(len(self.callback_data))) + scenario_numbers = list(range(len(self.exp_list))) scen_names = ["Scenario{}".format(i) for i in scenario_numbers] else: scen_names = ["Scenario{}".format(i) for i in range(len(bootlist))] @@ -526,8 +534,8 @@ def _Q_opt( outer_cb_data["ThetaVals"] = ThetaVals if bootlist is not None: outer_cb_data["BootList"] = bootlist - outer_cb_data["cb_data"] = self.callback_data # None is OK - outer_cb_data["theta_names"] = self.theta_names + outer_cb_data["cb_data"] = None # None is OK + outer_cb_data["theta_names"] = self.estimator_theta_names options = {"solver": "ipopt"} scenario_creator_options = {"cb_data": outer_cb_data} @@ -702,13 +710,13 @@ def _Q_at_theta(self, thetavals, initialize_parmest_model=False): "callback": self._instance_creation_callback, "ThetaVals": thetavals, "theta_names": self._return_theta_names(), - "cb_data": self.callback_data, + "cb_data": None, } else: dummy_cb = { "callback": self._instance_creation_callback, "theta_names": self._return_theta_names(), - "cb_data": self.callback_data, + "cb_data": None, } if self.diagnostic_mode: @@ -729,7 +737,7 @@ def _Q_at_theta(self, thetavals, initialize_parmest_model=False): WorstStatus = pyo.TerminationCondition.optimal totobj = 0 - scenario_numbers = list(range(len(self.callback_data))) + scenario_numbers = list(range(len(self.exp_list))) if initialize_parmest_model: # create dictionary to store pyomo model instances (scenarios) scen_dict = dict() @@ -737,13 +745,14 @@ def _Q_at_theta(self, thetavals, initialize_parmest_model=False): for snum in scenario_numbers: sname = "scenario_NODE" + str(snum) instance = _experiment_instance_creation_callback(sname, None, dummy_cb) + model_theta_names = [k.name for k,v in instance.unknown_parameters.items()] if initialize_parmest_model: # list to store fitted parameter names that will be unfixed # after initialization theta_init_vals = [] # use appropriate theta_names member - theta_ref = self._return_theta_names() + theta_ref = model_theta_names for i, theta in enumerate(theta_ref): # Use parser in ComponentUID to locate the component @@ -868,7 +877,7 @@ def _Q_at_theta(self, thetavals, initialize_parmest_model=False): def _get_sample_list(self, samplesize, num_samples, replacement=True): samplelist = list() - scenario_numbers = list(range(len(self.callback_data))) + scenario_numbers = list(range(len(self.exp_list))) if num_samples is None: # This could get very large @@ -944,12 +953,12 @@ def theta_est( assert isinstance(return_values, list) assert isinstance(calc_cov, bool) if calc_cov: - assert isinstance( - cov_n, int - ), "The number of datapoints that are used in the objective function is required to calculate the covariance matrix" - assert cov_n > len( - self._return_theta_names() - ), "The number of datapoints must be greater than the number of parameters to estimate" + num_unknowns = max([len(experiment.get_labeled_model().unknown_parameters) + for experiment in self.exp_list]) + assert isinstance(cov_n, int), \ + "The number of datapoints that are used in the objective function is required to calculate the covariance matrix" + assert cov_n > num_unknowns, \ + "The number of datapoints must be greater than the number of parameters to estimate" return self._Q_opt( solver=solver, @@ -1007,7 +1016,7 @@ def theta_est_bootstrap( assert isinstance(return_samples, bool) if samplesize is None: - samplesize = len(self.callback_data) + samplesize = len(self.exp_list) if seed is not None: np.random.seed(seed) @@ -1069,7 +1078,7 @@ def theta_est_leaveNout( assert isinstance(seed, (type(None), int)) assert isinstance(return_samples, bool) - samplesize = len(self.callback_data) - lNo + samplesize = len(self.exp_list) - lNo if seed is not None: np.random.seed(seed) @@ -1082,7 +1091,7 @@ def theta_est_leaveNout( lNo_theta = list() for idx, sample in local_list: objval, thetavals = self._Q_opt(bootlist=list(sample)) - lNo_s = list(set(range(len(self.callback_data))) - set(sample)) + lNo_s = list(set(range(len(self.exp_list))) - set(sample)) thetavals['lNo'] = np.sort(lNo_s) lNo_theta.append(thetavals) @@ -1157,20 +1166,13 @@ def leaveNout_bootstrap_test( if seed is not None: np.random.seed(seed) - data = self.callback_data.copy() - global_list = self._get_sample_list(lNo, lNo_samples, replacement=False) results = [] for idx, sample in global_list: - # Reset callback_data to only include the sample - self.callback_data = [data[i] for i in sample] obj, theta = self.theta_est() - # Reset callback_data to include all scenarios except the sample - self.callback_data = [data[i] for i in range(len(data)) if i not in sample] - bootstrap_theta = self.theta_est_bootstrap(bootstrap_samples) training, test = self.confidence_region_test( @@ -1182,9 +1184,6 @@ def leaveNout_bootstrap_test( results.append((sample, test, training)) - # Reset callback_data (back to full data set) - self.callback_data = data - return results def objective_at_theta(self, theta_values=None, initialize_parmest_model=False): @@ -1214,37 +1213,39 @@ def objective_at_theta(self, theta_values=None, initialize_parmest_model=False): theta_values=theta_values, initialize_parmest_model=initialize_parmest_model) - if len(self.theta_names) == 1 and self.theta_names[0] == 'parmest_dummy_var': + if len(self.estimator_theta_names) == 0: pass # skip assertion if model has no fitted parameters else: # create a local instance of the pyomo model to access model variables and parameters - model_temp = self._create_parmest_model(self.callback_data[0]) - model_theta_list = [] # list to store indexed and non-indexed parameters - # iterate over original theta_names - for theta_i in self.theta_names: - var_cuid = ComponentUID(theta_i) - var_validate = var_cuid.find_component_on(model_temp) - # check if theta in theta_names are indexed - try: - # get component UID of Set over which theta is defined - set_cuid = ComponentUID(var_validate.index_set()) - # access and iterate over the Set to generate theta names as they appear - # in the pyomo model - set_validate = set_cuid.find_component_on(model_temp) - for s in set_validate: - self_theta_temp = repr(var_cuid) + "[" + repr(s) + "]" - # generate list of theta names - model_theta_list.append(self_theta_temp) - # if theta is not indexed, copy theta name to list as-is - except AttributeError: - self_theta_temp = repr(var_cuid) - model_theta_list.append(self_theta_temp) - except: - raise + model_temp = self._create_parmest_model(0) + model_theta_list = [k.name for k,v in model_temp.unknown_parameters.items()] + + # # iterate over original theta_names + # for theta_i in self.theta_names: + # var_cuid = ComponentUID(theta_i) + # var_validate = var_cuid.find_component_on(model_temp) + # # check if theta in theta_names are indexed + # try: + # # get component UID of Set over which theta is defined + # set_cuid = ComponentUID(var_validate.index_set()) + # # access and iterate over the Set to generate theta names as they appear + # # in the pyomo model + # set_validate = set_cuid.find_component_on(model_temp) + # for s in set_validate: + # self_theta_temp = repr(var_cuid) + "[" + repr(s) + "]" + # # generate list of theta names + # model_theta_list.append(self_theta_temp) + # # if theta is not indexed, copy theta name to list as-is + # except AttributeError: + # self_theta_temp = repr(var_cuid) + # model_theta_list.append(self_theta_temp) + # except: + # raise + # if self.theta_names is not the same as temp model_theta_list, # create self.theta_names_updated - if set(self.theta_names) == set(model_theta_list) and len( - self.theta_names + if set(self.estimator_theta_names) == set(model_theta_list) and len( + self.estimator_theta_names ) == set(model_theta_list): pass else: @@ -1253,7 +1254,7 @@ def objective_at_theta(self, theta_values=None, initialize_parmest_model=False): if theta_values is None: all_thetas = {} # dictionary to store fitted variables # use appropriate theta names member - theta_names = self._return_theta_names() + theta_names = self.estimator_theta_names() else: assert isinstance(theta_values, pd.DataFrame) # for parallel code we need to use lists and dicts in the loop @@ -1343,7 +1344,7 @@ def likelihood_ratio_test( assert isinstance(return_thresholds, bool) LR = obj_at_theta.copy() - S = len(self.callback_data) + S = len(self.exp_list) thresholds = {} for a in alphas: chi2_val = scipy.stats.chi2.ppf(a, 2) From 280cd8027510b89118b13417634d21b411a93597 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 25 Jan 2024 13:25:15 -0700 Subject: [PATCH 0863/1797] Fixed parmest reaction kinetics example to work with new interface. --- .../simple_reaction_parmest_example.py | 78 ++++++++++++++++++- 1 file changed, 74 insertions(+), 4 deletions(-) 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..140fceeb8a2 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 @@ -18,6 +18,7 @@ Code provided by Paul Akula. ''' +import pyomo.environ as pyo from pyomo.environ import ( ConcreteModel, Param, @@ -32,6 +33,7 @@ value, ) import pyomo.contrib.parmest.parmest as parmest +from pyomo.contrib.parmest.experiment import Experiment def simple_reaction_model(data): @@ -72,7 +74,62 @@ def total_cost_rule(m): return model +# For this experiment class, data is dictionary +class SimpleReactionExperiment(Experiment): + + def __init__(self, data): + self.data = data + self.model = None + + def create_model(self): + self.model = simple_reaction_model(self.data) + + def label_model(self): + + m = self.model + + m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) + m.experiment_outputs.update([(m.x1, self.data['x1'])]) + m.experiment_outputs.update([(m.x2, self.data['x2'])]) + m.experiment_outputs.update([(m.y, self.data['y'])]) + + return m + + def get_labeled_model(self): + self.create_model() + m = self.label_model() + + return m + +# k[2] fixed +class SimpleReactionExperimentK2Fixed(SimpleReactionExperiment): + + def label_model(self): + + m = super().label_model() + + m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) + m.unknown_parameters.update((k, pyo.ComponentUID(k)) + for k in [m.k[1]]) + + return m + +# k[2] variable +class SimpleReactionExperimentK2Variable(SimpleReactionExperiment): + + def label_model(self): + + m = super().label_model() + + m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) + m.unknown_parameters.update((k, pyo.ComponentUID(k)) + for k in [m.k[1], m.k[2]]) + + return m + + def main(): + # Data from Table 5.2 in Y. Bard, "Nonlinear Parameter Estimation", (pg. 124) data = [ {'experiment': 1, 'x1': 0.1, 'x2': 100, 'y': 0.98}, @@ -92,21 +149,34 @@ def main(): {'experiment': 15, 'x1': 0.1, 'x2': 300, 'y': 0.006}, ] + # Create an experiment list with k[2] fixed + exp_list= [] + for i in range(len(data)): + exp_list.append(SimpleReactionExperimentK2Fixed(data[i])) + + # View one model + # exp0_model = exp_list[0].get_labeled_model() + # print(exp0_model.pprint()) + # ======================================================================= # Parameter estimation without covariance estimate # Only estimate the parameter k[1]. The parameter k[2] will remain fixed # at its initial value - theta_names = ['k[1]'] - pest = parmest.Estimator(simple_reaction_model, data, theta_names) + + pest = parmest.Estimator(exp_list) obj, theta = pest.theta_est() print(obj) print(theta) print() + # Create an experiment list with k[2] variable + exp_list= [] + for i in range(len(data)): + exp_list.append(SimpleReactionExperimentK2Variable(data[i])) + # ======================================================================= # Estimate both k1 and k2 and compute the covariance matrix - theta_names = ['k'] - pest = parmest.Estimator(simple_reaction_model, data, theta_names) + pest = parmest.Estimator(exp_list) n = 15 # total number of data points used in the objective (y in 15 scenarios) obj, theta, cov = pest.theta_est(calc_cov=True, cov_n=n) print(obj) From b6395abfbc6db5198e04f15c361c745f7441a5af Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Fri, 26 Jan 2024 08:27:12 -0700 Subject: [PATCH 0864/1797] 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 0865/1797] 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 0866/1797] 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 0867/1797] 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 0868/1797] 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 0869/1797] 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 0870/1797] 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 0871/1797] 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 0872/1797] 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 0873/1797] 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 0874/1797] 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 c7caf805f93d206f14f3336b3f9f39e2c8862cec Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 29 Jan 2024 09:20:38 -0700 Subject: [PATCH 0875/1797] Added parmest rooney-biegler example wtih new interface. --- .../rooney_biegler/bootstrap_example.py | 22 ++++++---- .../likelihood_ratio_example.py | 22 ++++++---- .../parameter_estimation_example.py | 24 ++++++---- .../examples/rooney_biegler/rooney_biegler.py | 44 ++++++++++++++++++- .../rooney_biegler_with_constraint.py | 42 ++++++++++++++++++ 5 files changed, 128 insertions(+), 26 deletions(-) diff --git a/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.py b/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.py index f686bbd933d..1f15ab95779 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.py @@ -12,13 +12,11 @@ import pandas as pd import pyomo.contrib.parmest.parmest as parmest from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import ( - rooney_biegler_model, + RooneyBieglerExperiment, ) def main(): - # Vars to estimate - theta_names = ['asymptote', 'rate_constant'] # Data data = pd.DataFrame( @@ -27,14 +25,22 @@ def main(): ) # Sum of squared error function - def SSE(model, data): - expr = sum( - (data.y[i] - model.response_function[data.hour[i]]) ** 2 for i in data.index - ) + def SSE(model): + expr = (model.experiment_outputs[model.y] - \ + model.response_function[model.experiment_outputs[model.hour]]) ** 2 return expr + # Create an experiment list + exp_list= [] + for i in range(data.shape[0]): + exp_list.append(RooneyBieglerExperiment(data.loc[i,:].to_frame().transpose())) + + # View one model + # exp0_model = exp_list[0].get_labeled_model() + # print(exp0_model.pprint()) + # Create an instance of the parmest estimator - pest = parmest.Estimator(rooney_biegler_model, data, theta_names, SSE) + pest = parmest.Estimator(exp_list, obj_function=SSE) # Parameter estimation obj, theta = pest.theta_est() 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..869bb39efb9 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/likelihood_ratio_example.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/likelihood_ratio_example.py @@ -14,13 +14,11 @@ from itertools import product import pyomo.contrib.parmest.parmest as parmest from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import ( - rooney_biegler_model, + RooneyBieglerExperiment, ) def main(): - # Vars to estimate - theta_names = ['asymptote', 'rate_constant'] # Data data = pd.DataFrame( @@ -29,14 +27,22 @@ def main(): ) # Sum of squared error function - def SSE(model, data): - expr = sum( - (data.y[i] - model.response_function[data.hour[i]]) ** 2 for i in data.index - ) + def SSE(model): + expr = (model.experiment_outputs[model.y] - \ + model.response_function[model.experiment_outputs[model.hour]]) ** 2 return expr + # Create an experiment list + exp_list= [] + for i in range(data.shape[0]): + exp_list.append(RooneyBieglerExperiment(data.loc[i,:].to_frame().transpose())) + + # View one model + # exp0_model = exp_list[0].get_labeled_model() + # print(exp0_model.pprint()) + # Create an instance of the parmest estimator - pest = parmest.Estimator(rooney_biegler_model, data, theta_names, SSE) + pest = parmest.Estimator(exp_list, obj_function=SSE) # Parameter estimation obj, theta = pest.theta_est() 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..b6ca7af0ab6 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/parameter_estimation_example.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/parameter_estimation_example.py @@ -12,13 +12,11 @@ import pandas as pd import pyomo.contrib.parmest.parmest as parmest from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import ( - rooney_biegler_model, + RooneyBieglerExperiment, ) def main(): - # Vars to estimate - theta_names = ['asymptote', 'rate_constant'] # Data data = pd.DataFrame( @@ -27,15 +25,23 @@ def main(): ) # Sum of squared error function - def SSE(model, data): - expr = sum( - (data.y[i] - model.response_function[data.hour[i]]) ** 2 for i in data.index - ) + def SSE(model): + expr = (model.experiment_outputs[model.y] - \ + model.response_function[model.experiment_outputs[model.hour]]) ** 2 return expr - # Create an instance of the parmest estimator - pest = parmest.Estimator(rooney_biegler_model, data, theta_names, SSE) + # Create an experiment list + exp_list= [] + for i in range(data.shape[0]): + exp_list.append(RooneyBieglerExperiment(data.loc[i,:].to_frame().transpose())) + + # View one model + # exp0_model = exp_list[0].get_labeled_model() + # print(exp0_model.pprint()) + # Create an instance of the parmest estimator + pest = parmest.Estimator(exp_list, obj_function=SSE) + # Parameter estimation and covariance n = 6 # total number of data points used in the objective (y in 6 scenarios) obj, theta, cov = pest.theta_est(calc_cov=True, cov_n=n) diff --git a/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py b/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py index 5a0e1238e85..2ac03504260 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py @@ -17,6 +17,7 @@ import pandas as pd import pyomo.environ as pyo +from pyomo.contrib.parmest.experiment import Experiment def rooney_biegler_model(data): @@ -25,10 +26,13 @@ def rooney_biegler_model(data): model.asymptote = pyo.Var(initialize=15) model.rate_constant = pyo.Var(initialize=0.5) + model.hour = pyo.Param(within=pyo.PositiveReals, mutable=True) + model.y = pyo.Param(within=pyo.PositiveReals, mutable=True) + def response_rule(m, h): expr = m.asymptote * (1 - pyo.exp(-m.rate_constant * h)) return expr - + model.response_function = pyo.Expression(data.hour, rule=response_rule) def SSE_rule(m): @@ -41,6 +45,44 @@ def SSE_rule(m): return model +class RooneyBieglerExperiment(Experiment): + + def __init__(self, data): + self.data = data + self.model = None + + def create_model(self): + self.model = rooney_biegler_model(self.data) + + def label_model(self): + + m = self.model + + m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) + m.experiment_outputs.update([(m.hour, self.data.iloc[0]['hour'])]) + m.experiment_outputs.update([(m.y, self.data.iloc[0]['y'])]) + + + m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) + m.unknown_parameters.update((k, pyo.ComponentUID(k)) + for k in [m.asymptote, m.rate_constant]) + + def finalize_model(self): + + m = self.model + + # Experiment output values + m.hour = self.data.iloc[0]['hour'] + m.y = self.data.iloc[0]['y'] + + def get_labeled_model(self): + self.create_model() + self.label_model() + self.finalize_model() + + return self.model + + def main(): # These were taken from Table A1.4 in Bates and Watts (1988). data = pd.DataFrame( 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..1e213684a01 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 @@ -17,6 +17,7 @@ import pandas as pd import pyomo.environ as pyo +from pyomo.contrib.parmest.experiment import Experiment def rooney_biegler_model_with_constraint(data): @@ -24,6 +25,10 @@ def rooney_biegler_model_with_constraint(data): model.asymptote = pyo.Var(initialize=15) model.rate_constant = pyo.Var(initialize=0.5) + + model.hour = pyo.Param(within=pyo.PositiveReals, mutable=True) + model.y = pyo.Param(within=pyo.PositiveReals, mutable=True) + model.response_function = pyo.Var(data.hour, initialize=0.0) # changed from expression to constraint @@ -43,6 +48,43 @@ def SSE_rule(m): return model +class RooneyBieglerExperiment(Experiment): + + def __init__(self, data): + self.data = data + self.model = None + + def create_model(self): + self.model = rooney_biegler_model_with_constraint(self.data) + + def label_model(self): + + m = self.model + + m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) + m.experiment_outputs.update([(m.hour, self.data.iloc[0]['hour'])]) + m.experiment_outputs.update([(m.y, self.data.iloc[0]['y'])]) + + + m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) + m.unknown_parameters.update((k, pyo.ComponentUID(k)) + for k in [m.asymptote, m.rate_constant]) + + def finalize_model(self): + + m = self.model + + # Experiment output values + m.hour = self.data.iloc[0]['hour'] + m.y = self.data.iloc[0]['y'] + + def get_labeled_model(self): + self.create_model() + self.label_model() + self.finalize_model() + + return self.model + def main(): # These were taken from Table A1.4 in Bates and Watts (1988). From 4260d193bfeb9f2bd6616395824789cbdc637a92 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Mon, 29 Jan 2024 15:33:58 -0700 Subject: [PATCH 0876/1797] Adding 'spans' and 'alternative' expressions --- pyomo/contrib/cp/__init__.py | 1 + pyomo/contrib/cp/interval_var.py | 9 +++- pyomo/contrib/cp/repn/docplex_writer.py | 19 ++++++++ .../cp/scheduling_expr/scheduling_logic.py | 48 +++++++++++++++++++ 4 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 pyomo/contrib/cp/scheduling_expr/scheduling_logic.py diff --git a/pyomo/contrib/cp/__init__.py b/pyomo/contrib/cp/__init__.py index 03196537446..bd839f5d578 100644 --- a/pyomo/contrib/cp/__init__.py +++ b/pyomo/contrib/cp/__init__.py @@ -14,6 +14,7 @@ before_in_sequence, predecessor_to, ) +from pyomo.contrib.cp.scheduling_expr.scheduling_logic import alternative, spans from pyomo.contrib.cp.scheduling_expr.step_function_expressions import ( AlwaysIn, Step, diff --git a/pyomo/contrib/cp/interval_var.py b/pyomo/contrib/cp/interval_var.py index 911d9ba50ba..2379a58a1ff 100644 --- a/pyomo/contrib/cp/interval_var.py +++ b/pyomo/contrib/cp/interval_var.py @@ -11,6 +11,7 @@ from pyomo.common.collections import ComponentSet from pyomo.common.pyomo_typing import overload +from pyomo.contrib.cp.scheduling_expr.scheduling_logic import SpanExpression from pyomo.contrib.cp.scheduling_expr.precedence_expressions import ( BeforeExpression, AtExpression, @@ -24,6 +25,7 @@ from pyomo.core.base.indexed_component import IndexedComponent, UnindexedComponent_set from pyomo.core.base.initializer import BoundInitializer, Initializer from pyomo.core.expr import GetItemExpression +from pyomo.core.expr.logical_expr import _flattened class IntervalVarTimePoint(ScalarVar): @@ -80,7 +82,9 @@ class IntervalVarPresence(ScalarBooleanVar): __slots__ = () - def __init__(self): + def __init__(self, *args, **kwd): + # TODO: adding args and kwd above made Reference work, but we + # probably shouldn't just swallow them, right? super().__init__(ctype=IntervalVarPresence) def get_associated_interval_var(self): @@ -122,6 +126,9 @@ def optional(self, val): else: self.is_present.fix(True) + def spans(self, *args): + return SpanExpression([self] + list(_flattened(args))) + @ModelComponentFactory.register("Interval variables for scheduling.") class IntervalVar(Block): diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index de71e4e98dd..1f6bcc347e7 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -36,6 +36,10 @@ IndexedSequenceVar, _SequenceVarData, ) +from pyomo.contrib.cp.scheduling_expr.scheduling_logic import ( + AlternativeExpression, + SpanExpression, +) from pyomo.contrib.cp.scheduling_expr.precedence_expressions import ( BeforeExpression, AtExpression, @@ -462,6 +466,7 @@ def _create_docplex_interval_var(visitor, interval_var): nm = interval_var.name if visitor.symbolic_solver_labels else None cpx_interval_var = cp.interval_var(name=nm) visitor.var_map[id(interval_var)] = cpx_interval_var + visitor.pyomo_to_docplex[interval_var] = cpx_interval_var # Figure out if it exists if interval_var.is_present.fixed and not interval_var.is_present.value: @@ -991,6 +996,18 @@ def _handle_predecessor_to_expression_node( return _GENERAL, cp.previous(seq_var[1], before_var[1], after_var[1]) +def _handle_span_expression_node( + visitor, node, *args +): + return _GENERAL, cp.span(args[0][1], [arg[1] for arg in args[1:]]) + + +def _handle_alternative_expression_node( + visitor, node, *args +): + return _GENERAL, cp.alternative(args[0][1], [arg[1] for arg in args[1:]]) + + class LogicalToDoCplex(StreamBasedExpressionVisitor): _operator_handles = { EXPR.GetItemExpression: _handle_getitem, @@ -1037,6 +1054,8 @@ class LogicalToDoCplex(StreamBasedExpressionVisitor): LastInSequenceExpression: _handle_last_in_sequence_expression_node, BeforeInSequenceExpression: _handle_before_in_sequence_expression_node, PredecessorToExpression: _handle_predecessor_to_expression_node, + SpanExpression: _handle_span_expression_node, + AlternativeExpression: _handle_alternative_expression_node, } _var_handles = { IntervalVarStartTime: _before_interval_var_start_time, diff --git a/pyomo/contrib/cp/scheduling_expr/scheduling_logic.py b/pyomo/contrib/cp/scheduling_expr/scheduling_logic.py new file mode 100644 index 00000000000..a1f891a769f --- /dev/null +++ b/pyomo/contrib/cp/scheduling_expr/scheduling_logic.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. +# ___________________________________________________________________________ + + +from pyomo.core.expr.logical_expr import NaryBooleanExpression, _flattened + + +class SpanExpression(NaryBooleanExpression): + """ + Expression over IntervalVars representing that the first arg spans all the + following args in the schedule. The first arg is absent if and only if all + the others are absent. + + args: + args (tuple): Child nodes, of type IntervalVar + """ + def _to_string(self, values, verbose, smap): + return "%s.spans(%s)" % (values[0], ", ".join(values[1:])) + + +class AlternativeExpression(NaryBooleanExpression): + """ + TODO/ + """ + def _to_string(self, values, verbose, smap): + return "alternative(%s, [%s])" % (values[0], ", ".join(values[1:])) + + +def spans(*args): + """Creates a new SpanExpression + """ + + return SpanExpression(list(_flattened(args))) + + +def alternative(*args): + """Creates a new AlternativeExpression + """ + + return AlternativeExpression(list(_flattened(args))) From db4cb7dcc9e78471d9e3353ba75b6e79fa3d1651 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Mon, 29 Jan 2024 16:21:53 -0700 Subject: [PATCH 0877/1797] Adding tests for span and alternative --- pyomo/contrib/cp/tests/test_docplex_walker.py | 52 ++++++++++++++++++ .../cp/tests/test_sequence_expressions.py | 53 +++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/pyomo/contrib/cp/tests/test_docplex_walker.py b/pyomo/contrib/cp/tests/test_docplex_walker.py index 0b2057217c0..fc475190ade 100644 --- a/pyomo/contrib/cp/tests/test_docplex_walker.py +++ b/pyomo/contrib/cp/tests/test_docplex_walker.py @@ -19,6 +19,7 @@ last_in_sequence, before_in_sequence, predecessor_to, + alternative ) from pyomo.contrib.cp.scheduling_expr.step_function_expressions import ( AlwaysIn, @@ -1322,6 +1323,57 @@ def param_rule(m, i): self.assertTrue(expr[1].equals(cp.element([2, 4, 6], 0 + 1 * (x - 1) // 2) / a)) +@unittest.skipIf(not docplex_available, "docplex is not available") +class TestCPExpressionWalker_HierarchicalScheduling(CommonTest): + def get_model(self): + m = ConcreteModel() + def start_rule(m, i): + return 2*i + def length_rule(m, i): + return i + m.iv = IntervalVar([1, 2, 3], start=start_rule, length=length_rule, + optional=True) + m.whole_enchilada = IntervalVar() + + return m + + def test_spans(self): + m = self.get_model() + e = m.whole_enchilada.spans(m.iv[i] for i in [1, 2, 3]) + + visitor = self.get_visitor() + expr = visitor.walk_expression((e, e, 0)) + + self.assertIn(id(m.whole_enchilada), visitor.var_map) + whole_enchilada = visitor.var_map[id(m.whole_enchilada)] + iv = {} + for i in [1, 2, 3]: + self.assertIn(id(m.iv[i]), visitor.var_map) + iv[i] = visitor.var_map[id(m.iv[i])] + + self.assertTrue(expr[1].equals(cp.span(whole_enchilada, [iv[i] for i in + [1, 2, 3]]))) + + def test_alternative(self): + m = self.get_model() + e = alternative(m.whole_enchilada, [m.iv[i] for i in [1, 2, 3]]) + + visitor = self.get_visitor() + expr = visitor.walk_expression((e, e, 0)) + + self.assertIn(id(m.whole_enchilada), visitor.var_map) + whole_enchilada = visitor.var_map[id(m.whole_enchilada)] + iv = {} + for i in [1, 2, 3]: + self.assertIn(id(m.iv[i]), visitor.var_map) + iv[i] = visitor.var_map[id(m.iv[i])] + + self.assertTrue(expr[1].equals(cp.alternative(whole_enchilada, [iv[i] + for i in + [1, 2, + 3]]))) + + @unittest.skipIf(not docplex_available, "docplex is not available") class TestCPExpressionWalker_CumulFuncExpressions(CommonTest): def test_always_in(self): diff --git a/pyomo/contrib/cp/tests/test_sequence_expressions.py b/pyomo/contrib/cp/tests/test_sequence_expressions.py index 0ef2a9e3072..93a283c43d1 100644 --- a/pyomo/contrib/cp/tests/test_sequence_expressions.py +++ b/pyomo/contrib/cp/tests/test_sequence_expressions.py @@ -12,6 +12,12 @@ from io import StringIO import pyomo.common.unittest as unittest from pyomo.contrib.cp.interval_var import IntervalVar +from pyomo.contrib.cp.scheduling_expr.scheduling_logic import ( + AlternativeExpression, + SpanExpression, + alternative, + spans +) from pyomo.contrib.cp.scheduling_expr.sequence_expressions import ( NoOverlapExpression, FirstInSequenceExpression, @@ -102,3 +108,50 @@ def test_predecessor_in_sequence(self): self.assertIs(e.args[2], m.seq) self.assertEqual(str(e), "predecessor_to(i[0], i[1], seq)") + + +class TestHierarchicalSchedulingExpressions(unittest.TestCase): + def make_model(self): + m = ConcreteModel() + def start_rule(m, i): + return 2*i + def length_rule(m, i): + return i + m.iv = IntervalVar([1, 2, 3], start=start_rule, length=length_rule, + optional=True) + m.whole_enchilada = IntervalVar() + + return m + + def check_span_expression(self, m, e): + self.assertIsInstance(e, SpanExpression) + self.assertEqual(e.nargs(), 4) + self.assertEqual(len(e.args), 4) + self.assertIs(e.args[0], m.whole_enchilada) + for i in [1, 2, 3]: + self.assertIs(e.args[i], m.iv[i]) + + self.assertEqual(str(e), "whole_enchilada.spans(iv[1], iv[2], iv[3])") + + def test_spans(self): + m = self.make_model() + e = spans(m.whole_enchilada, [m.iv[i] for i in [1, 2, 3]]) + self.check_span_expression(m, e) + + def test_spans_method(self): + m = self.make_model() + e = m.whole_enchilada.spans(m.iv[i] for i in [1, 2, 3]) + self.check_span_expression(m, e) + + def test_alternative(self): + m = self.make_model() + e = alternative(m.whole_enchilada, [m.iv[i] for i in [1, 2, 3]]) + + self.assertIsInstance(e, AlternativeExpression) + self.assertEqual(e.nargs(), 4) + self.assertEqual(len(e.args), 4) + self.assertIs(e.args[0], m.whole_enchilada) + for i in [1, 2, 3]: + self.assertIs(e.args[i], m.iv[i]) + + self.assertEqual(str(e), "alternative(whole_enchilada, [iv[1], iv[2], iv[3]])") From bda572774a229eec120735966f9facb2647001e6 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Mon, 29 Jan 2024 16:24:46 -0700 Subject: [PATCH 0878/1797] Making References work for start_time, end_time, length, and is_present --- pyomo/contrib/cp/interval_var.py | 6 +++--- pyomo/contrib/cp/tests/test_interval_var.py | 23 +++++++++++++++++++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/cp/interval_var.py b/pyomo/contrib/cp/interval_var.py index 2379a58a1ff..fb88ab14832 100644 --- a/pyomo/contrib/cp/interval_var.py +++ b/pyomo/contrib/cp/interval_var.py @@ -51,7 +51,7 @@ class IntervalVarStartTime(IntervalVarTimePoint): """This class defines a single variable denoting a start time point of an IntervalVar""" - def __init__(self): + def __init__(self, *args, **kwd): super().__init__(domain=Integers, ctype=IntervalVarStartTime) @@ -59,7 +59,7 @@ class IntervalVarEndTime(IntervalVarTimePoint): """This class defines a single variable denoting an end time point of an IntervalVar""" - def __init__(self): + def __init__(self, *args, **kwd): super().__init__(domain=Integers, ctype=IntervalVarEndTime) @@ -69,7 +69,7 @@ class IntervalVarLength(ScalarVar): __slots__ = () - def __init__(self): + def __init__(self, *args, **kwd): super().__init__(domain=Integers, ctype=IntervalVarLength) def get_associated_interval_var(self): diff --git a/pyomo/contrib/cp/tests/test_interval_var.py b/pyomo/contrib/cp/tests/test_interval_var.py index edbf889fcda..1ebb87a67be 100644 --- a/pyomo/contrib/cp/tests/test_interval_var.py +++ b/pyomo/contrib/cp/tests/test_interval_var.py @@ -17,7 +17,7 @@ IntervalVarPresence, ) from pyomo.core.expr import GetItemExpression, GetAttrExpression -from pyomo.environ import ConcreteModel, Integers, Set, value, Var +from pyomo.environ import ConcreteModel, Integers, Reference, Set, value, Var class TestScalarIntervalVar(unittest.TestCase): @@ -217,5 +217,24 @@ def test_index_by_expr(self): self.assertIs(thing2.args[0], thing1) self.assertEqual(thing2.args[1], 'start_time') - # TODO: But this is where it dies. expr1 = m.act[m.i, 2].start_time.before(m.act[m.i**2, 1].end_time) + + def test_reference(self): + m = ConcreteModel() + m.act = IntervalVar([1, 2], end=[0, 10], optional=True) + + thing = Reference(m.act[:].is_present) + self.assertIs(thing[1], m.act[1].is_present) + self.assertIs(thing[2], m.act[2].is_present) + + thing = Reference(m.act[:].start_time) + self.assertIs(thing[1], m.act[1].start_time) + self.assertIs(thing[2], m.act[2].start_time) + + thing = Reference(m.act[:].end_time) + self.assertIs(thing[1], m.act[1].end_time) + self.assertIs(thing[2], m.act[2].end_time) + + thing = Reference(m.act[:].length) + self.assertIs(thing[1], m.act[1].length) + self.assertIs(thing[2], m.act[2].length) From b689a97dcec94fe040c4c7c786ce9f35ba9bee0f Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 30 Jan 2024 09:39:16 -0700 Subject: [PATCH 0879/1797] 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 0880/1797] 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 ed87c162cdb0239171a681344acc2d66b706b0dd Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 30 Jan 2024 12:06:28 -0700 Subject: [PATCH 0881/1797] Fixed parmest tests of new interface. --- .../parmest/deprecated/tests/test_examples.py | 34 +- .../parmest/deprecated/tests/test_parmest.py | 6 +- .../deprecated/tests/test_scenariocreator.py | 4 +- .../parmest/deprecated/tests/test_utils.py | 2 +- .../examples/reactor_design/reactor_design.py | 5 +- .../examples/rooney_biegler/rooney_biegler.py | 1 - .../semibatch/parameter_estimation_example.py | 20 +- .../examples/semibatch/scenario_example.py | 17 +- .../parmest/examples/semibatch/semibatch.py | 32 ++ pyomo/contrib/parmest/graphics.py | 2 +- pyomo/contrib/parmest/parmest.py | 31 +- pyomo/contrib/parmest/scenariocreator.py | 4 +- pyomo/contrib/parmest/tests/test_parmest.py | 348 +++++++++++------- .../parmest/tests/test_scenariocreator.py | 30 +- pyomo/contrib/parmest/tests/test_utils.py | 9 +- 15 files changed, 347 insertions(+), 198 deletions(-) diff --git a/pyomo/contrib/parmest/deprecated/tests/test_examples.py b/pyomo/contrib/parmest/deprecated/tests/test_examples.py index 67e06130384..04aff572529 100644 --- a/pyomo/contrib/parmest/deprecated/tests/test_examples.py +++ b/pyomo/contrib/parmest/deprecated/tests/test_examples.py @@ -32,12 +32,12 @@ def tearDownClass(self): pass def test_model(self): - from pyomo.contrib.parmest.examples.rooney_biegler import rooney_biegler + from pyomo.contrib.parmest.deprecated.examples.rooney_biegler import rooney_biegler rooney_biegler.main() def test_model_with_constraint(self): - from pyomo.contrib.parmest.examples.rooney_biegler import ( + from pyomo.contrib.parmest.deprecated.examples.rooney_biegler import ( rooney_biegler_with_constraint, ) @@ -45,7 +45,7 @@ def test_model_with_constraint(self): @unittest.skipUnless(seaborn_available, "test requires seaborn") def test_parameter_estimation_example(self): - from pyomo.contrib.parmest.examples.rooney_biegler import ( + from pyomo.contrib.parmest.deprecated.examples.rooney_biegler import ( parameter_estimation_example, ) @@ -53,13 +53,13 @@ def test_parameter_estimation_example(self): @unittest.skipUnless(seaborn_available, "test requires seaborn") def test_bootstrap_example(self): - from pyomo.contrib.parmest.examples.rooney_biegler import bootstrap_example + from pyomo.contrib.parmest.deprecated.examples.rooney_biegler import bootstrap_example bootstrap_example.main() @unittest.skipUnless(seaborn_available, "test requires seaborn") def test_likelihood_ratio_example(self): - from pyomo.contrib.parmest.examples.rooney_biegler import ( + from pyomo.contrib.parmest.deprecated.examples.rooney_biegler import ( likelihood_ratio_example, ) @@ -81,7 +81,7 @@ def tearDownClass(self): pass def test_example(self): - from pyomo.contrib.parmest.examples.reaction_kinetics import ( + from pyomo.contrib.parmest.deprecated.examples.reaction_kinetics import ( simple_reaction_parmest_example, ) @@ -103,19 +103,19 @@ def tearDownClass(self): pass def test_model(self): - from pyomo.contrib.parmest.examples.semibatch import semibatch + from pyomo.contrib.parmest.deprecated.examples.semibatch import semibatch semibatch.main() def test_parameter_estimation_example(self): - from pyomo.contrib.parmest.examples.semibatch import ( + from pyomo.contrib.parmest.deprecated.examples.semibatch import ( parameter_estimation_example, ) parameter_estimation_example.main() def test_scenario_example(self): - from pyomo.contrib.parmest.examples.semibatch import scenario_example + from pyomo.contrib.parmest.deprecated.examples.semibatch import scenario_example scenario_example.main() @@ -136,12 +136,12 @@ def tearDownClass(self): @unittest.pytest.mark.expensive def test_model(self): - from pyomo.contrib.parmest.examples.reactor_design import reactor_design + from pyomo.contrib.parmest.deprecated.examples.reactor_design import reactor_design reactor_design.main() def test_parameter_estimation_example(self): - from pyomo.contrib.parmest.examples.reactor_design import ( + from pyomo.contrib.parmest.deprecated.examples.reactor_design import ( parameter_estimation_example, ) @@ -149,13 +149,13 @@ def test_parameter_estimation_example(self): @unittest.skipUnless(seaborn_available, "test requires seaborn") def test_bootstrap_example(self): - from pyomo.contrib.parmest.examples.reactor_design import bootstrap_example + from pyomo.contrib.parmest.deprecated.examples.reactor_design import bootstrap_example bootstrap_example.main() @unittest.pytest.mark.expensive def test_likelihood_ratio_example(self): - from pyomo.contrib.parmest.examples.reactor_design import ( + from pyomo.contrib.parmest.deprecated.examples.reactor_design import ( likelihood_ratio_example, ) @@ -163,19 +163,19 @@ def test_likelihood_ratio_example(self): @unittest.pytest.mark.expensive def test_leaveNout_example(self): - from pyomo.contrib.parmest.examples.reactor_design import leaveNout_example + from pyomo.contrib.parmest.deprecated.examples.reactor_design import leaveNout_example leaveNout_example.main() def test_timeseries_data_example(self): - from pyomo.contrib.parmest.examples.reactor_design import ( + from pyomo.contrib.parmest.deprecated.examples.reactor_design import ( timeseries_data_example, ) timeseries_data_example.main() def test_multisensor_data_example(self): - from pyomo.contrib.parmest.examples.reactor_design import ( + from pyomo.contrib.parmest.deprecated.examples.reactor_design import ( multisensor_data_example, ) @@ -183,7 +183,7 @@ def test_multisensor_data_example(self): @unittest.skipUnless(matplotlib_available, "test requires matplotlib") def test_datarec_example(self): - from pyomo.contrib.parmest.examples.reactor_design import datarec_example + from pyomo.contrib.parmest.deprecated.examples.reactor_design import datarec_example datarec_example.main() diff --git a/pyomo/contrib/parmest/deprecated/tests/test_parmest.py b/pyomo/contrib/parmest/deprecated/tests/test_parmest.py index 7e692989b0c..40c98dac3af 100644 --- a/pyomo/contrib/parmest/deprecated/tests/test_parmest.py +++ b/pyomo/contrib/parmest/deprecated/tests/test_parmest.py @@ -54,7 +54,7 @@ @unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") class TestRooneyBiegler(unittest.TestCase): def setUp(self): - from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import ( + from pyomo.contrib.parmest.deprecated.examples.rooney_biegler.rooney_biegler import ( rooney_biegler_model, ) @@ -605,7 +605,7 @@ def test_parmest_basics_with_square_problem_solve_no_theta_vals(self): @unittest.skipIf(not ipopt_available, "The 'ipopt' solver is not available") class TestReactorDesign(unittest.TestCase): def setUp(self): - from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( + from pyomo.contrib.parmest.deprecated.examples.reactor_design.reactor_design import ( reactor_design_model, ) @@ -879,7 +879,7 @@ def test_covariance(self): @unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") class TestSquareInitialization_RooneyBiegler(unittest.TestCase): def setUp(self): - from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler_with_constraint import ( + from pyomo.contrib.parmest.deprecated.examples.rooney_biegler.rooney_biegler_with_constraint import ( rooney_biegler_model_with_constraint, ) diff --git a/pyomo/contrib/parmest/deprecated/tests/test_scenariocreator.py b/pyomo/contrib/parmest/deprecated/tests/test_scenariocreator.py index 22a851ae32e..54cbe80f73c 100644 --- a/pyomo/contrib/parmest/deprecated/tests/test_scenariocreator.py +++ b/pyomo/contrib/parmest/deprecated/tests/test_scenariocreator.py @@ -36,7 +36,7 @@ @unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") class TestScenarioReactorDesign(unittest.TestCase): def setUp(self): - from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( + from pyomo.contrib.parmest.deprecated.examples.reactor_design.reactor_design import ( reactor_design_model, ) @@ -112,7 +112,7 @@ def test_no_csv_if_empty(self): @unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") class TestScenarioSemibatch(unittest.TestCase): def setUp(self): - import pyomo.contrib.parmest.examples.semibatch.semibatch as sb + import pyomo.contrib.parmest.deprecated.examples.semibatch.semibatch as sb import json # Vars to estimate in parmest diff --git a/pyomo/contrib/parmest/deprecated/tests/test_utils.py b/pyomo/contrib/parmest/deprecated/tests/test_utils.py index 514c14b1e82..1a8247ddcc9 100644 --- a/pyomo/contrib/parmest/deprecated/tests/test_utils.py +++ b/pyomo/contrib/parmest/deprecated/tests/test_utils.py @@ -35,7 +35,7 @@ def tearDownClass(self): @unittest.pytest.mark.expensive def test_convert_param_to_var(self): - from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( + from pyomo.contrib.parmest.deprecated.examples.reactor_design.reactor_design import ( reactor_design_model, ) diff --git a/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py b/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py index 1479009abcc..db3b0e1d380 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py +++ b/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py @@ -118,7 +118,7 @@ def get_labeled_model(self): return m -if __name__ == "__main__": +def main(): # For a range of sv values, return ca, cb, cc, and cd results = [] @@ -142,4 +142,7 @@ def get_labeled_model(self): results = pd.DataFrame(results, columns=["sv", "caf", "ca", "cb", "cc", "cd"]) print(results) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py b/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py index 2ac03504260..6e7d6219a64 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py @@ -62,7 +62,6 @@ def label_model(self): m.experiment_outputs.update([(m.hour, self.data.iloc[0]['hour'])]) m.experiment_outputs.update([(m.y, self.data.iloc[0]['y'])]) - m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) m.unknown_parameters.update((k, pyo.ComponentUID(k)) for k in [m.asymptote, m.rate_constant]) diff --git a/pyomo/contrib/parmest/examples/semibatch/parameter_estimation_example.py b/pyomo/contrib/parmest/examples/semibatch/parameter_estimation_example.py index fc4c9f5c675..145569f7535 100644 --- a/pyomo/contrib/parmest/examples/semibatch/parameter_estimation_example.py +++ b/pyomo/contrib/parmest/examples/semibatch/parameter_estimation_example.py @@ -12,12 +12,11 @@ import json from os.path import join, abspath, dirname import pyomo.contrib.parmest.parmest as parmest -from pyomo.contrib.parmest.examples.semibatch.semibatch import generate_model - +from pyomo.contrib.parmest.examples.semibatch.semibatch import ( + SemiBatchExperiment, +) def main(): - # Vars to estimate - theta_names = ['k1', 'k2', 'E1', 'E2'] # Data, list of dictionaries data = [] @@ -28,11 +27,20 @@ def main(): d = json.load(infile) data.append(d) + # Create an experiment list + exp_list= [] + for i in range(len(data)): + exp_list.append(SemiBatchExperiment(data[i])) + + # View one model + # exp0_model = exp_list[0].get_labeled_model() + # print(exp0_model.pprint()) + # Note, the model already includes a 'SecondStageCost' expression # for sum of squared error that will be used in parameter estimation - pest = parmest.Estimator(generate_model, data, theta_names) - + pest = parmest.Estimator(exp_list) + obj, theta = pest.theta_est() print(obj) print(theta) diff --git a/pyomo/contrib/parmest/examples/semibatch/scenario_example.py b/pyomo/contrib/parmest/examples/semibatch/scenario_example.py index 071e53236c4..a80a82671bc 100644 --- a/pyomo/contrib/parmest/examples/semibatch/scenario_example.py +++ b/pyomo/contrib/parmest/examples/semibatch/scenario_example.py @@ -12,13 +12,13 @@ import json from os.path import join, abspath, dirname import pyomo.contrib.parmest.parmest as parmest -from pyomo.contrib.parmest.examples.semibatch.semibatch import generate_model +from pyomo.contrib.parmest.examples.semibatch.semibatch import ( + SemiBatchExperiment, +) import pyomo.contrib.parmest.scenariocreator as sc def main(): - # Vars to estimate in parmest - theta_names = ['k1', 'k2', 'E1', 'E2'] # Data: list of dictionaries data = [] @@ -29,7 +29,16 @@ def main(): d = json.load(infile) data.append(d) - pest = parmest.Estimator(generate_model, data, theta_names) + # Create an experiment list + exp_list= [] + for i in range(len(data)): + exp_list.append(SemiBatchExperiment(data[i])) + + # View one model + # exp0_model = exp_list[0].get_labeled_model() + # print(exp0_model.pprint()) + + pest = parmest.Estimator(exp_list) scenmaker = sc.ScenarioCreator(pest, "ipopt") diff --git a/pyomo/contrib/parmest/examples/semibatch/semibatch.py b/pyomo/contrib/parmest/examples/semibatch/semibatch.py index 6762531a338..3ef7bc01aa9 100644 --- a/pyomo/contrib/parmest/examples/semibatch/semibatch.py +++ b/pyomo/contrib/parmest/examples/semibatch/semibatch.py @@ -29,8 +29,11 @@ SolverFactory, exp, minimize, + Suffix, + ComponentUID, ) from pyomo.dae import ContinuousSet, DerivativeVar +from pyomo.contrib.parmest.experiment import Experiment def generate_model(data): @@ -268,6 +271,35 @@ def total_cost_rule(model): return m +class SemiBatchExperiment(Experiment): + + def __init__(self, data): + self.data = data + self.model = None + + def create_model(self): + self.model = generate_model(self.data) + + def label_model(self): + + m = self.model + + m.unknown_parameters = Suffix(direction=Suffix.LOCAL) + m.unknown_parameters.update((k, ComponentUID(k)) + for k in [m.k1, m.k2, m.E1, m.E2]) + + + def finalize_model(self): + pass + + def get_labeled_model(self): + self.create_model() + self.label_model() + self.finalize_model() + + return self.model + + def main(): # Data loaded from files file_dirname = dirname(abspath(str(__file__))) 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()) diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index a00671c2ea6..e256b0f38d7 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -745,7 +745,7 @@ def _Q_at_theta(self, thetavals, initialize_parmest_model=False): for snum in scenario_numbers: sname = "scenario_NODE" + str(snum) instance = _experiment_instance_creation_callback(sname, None, dummy_cb) - model_theta_names = [k.name for k,v in instance.unknown_parameters.items()] + model_theta_names = self._expand_indexed_unknowns(instance) if initialize_parmest_model: # list to store fitted parameter names that will be unfixed @@ -1186,6 +1186,28 @@ def leaveNout_bootstrap_test( return results + # expand indexed variables to get full list of thetas + def _expand_indexed_unknowns(self, model_temp): + + model_theta_list = [k.name for k,v in model_temp.unknown_parameters.items()] + + # check for indexed theta items + indexed_theta_list = [] + for theta_i in model_theta_list: + var_cuid = ComponentUID(theta_i) + var_validate = var_cuid.find_component_on(model_temp) + for ind in var_validate.index_set(): + if ind is not None: + indexed_theta_list.append(theta_i + '[' + str(ind) + ']') + else: + indexed_theta_list.append(theta_i) + + # if we found indexed thetas, use expanded list + if len(indexed_theta_list) > len(model_theta_list): + model_theta_list = indexed_theta_list + + return model_theta_list + def objective_at_theta(self, theta_values=None, initialize_parmest_model=False): """ Objective value for each theta @@ -1218,8 +1240,8 @@ def objective_at_theta(self, theta_values=None, initialize_parmest_model=False): else: # create a local instance of the pyomo model to access model variables and parameters model_temp = self._create_parmest_model(0) - model_theta_list = [k.name for k,v in model_temp.unknown_parameters.items()] - + model_theta_list = self._expand_indexed_unknowns(model_temp) + # # iterate over original theta_names # for theta_i in self.theta_names: # var_cuid = ComponentUID(theta_i) @@ -1254,7 +1276,7 @@ def objective_at_theta(self, theta_values=None, initialize_parmest_model=False): if theta_values is None: all_thetas = {} # dictionary to store fitted variables # use appropriate theta names member - theta_names = self.estimator_theta_names() + theta_names = model_theta_list else: assert isinstance(theta_values, pd.DataFrame) # for parallel code we need to use lists and dicts in the loop @@ -1262,7 +1284,6 @@ def objective_at_theta(self, theta_values=None, initialize_parmest_model=False): # # check if theta_names are in model for theta in list(theta_names): theta_temp = theta.replace("'", "") # cleaning quotes from theta_names - assert theta_temp in [ t.replace("'", "") for t in model_theta_list ], "Theta name {} in 'theta_values' not in 'theta_names' {}".format( diff --git a/pyomo/contrib/parmest/scenariocreator.py b/pyomo/contrib/parmest/scenariocreator.py index b849bfdfd5b..c48ac2bf027 100644 --- a/pyomo/contrib/parmest/scenariocreator.py +++ b/pyomo/contrib/parmest/scenariocreator.py @@ -151,13 +151,13 @@ def ScenariosFromExperiments(self, addtoSet): assert isinstance(addtoSet, ScenarioSet) - scenario_numbers = list(range(len(self.pest.callback_data))) + scenario_numbers = list(range(len(self.pest.exp_list))) prob = 1.0 / len(scenario_numbers) for exp_num in scenario_numbers: ##print("Experiment number=", exp_num) model = self.pest._instance_creation_callback( - exp_num, self.pest.callback_data + exp_num, ) opt = pyo.SolverFactory(self.solvername) results = opt.solve(model) # solves and updates model diff --git a/pyomo/contrib/parmest/tests/test_parmest.py b/pyomo/contrib/parmest/tests/test_parmest.py index 2cc8ad36b0a..b88871e0dbc 100644 --- a/pyomo/contrib/parmest/tests/test_parmest.py +++ b/pyomo/contrib/parmest/tests/test_parmest.py @@ -33,6 +33,7 @@ import pyomo.contrib.parmest.parmest as parmest import pyomo.contrib.parmest.graphics as graphics import pyomo.contrib.parmest as parmestbase +from pyomo.contrib.parmest.experiment import Experiment import pyomo.environ as pyo import pyomo.dae as dae @@ -46,7 +47,6 @@ testdir = os.path.dirname(os.path.abspath(__file__)) - @unittest.skipIf( not parmest.parmest_available, "Cannot test parmest: required dependencies are missing", @@ -55,7 +55,7 @@ class TestRooneyBiegler(unittest.TestCase): def setUp(self): from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import ( - rooney_biegler_model, + RooneyBieglerExperiment, ) # Note, the data used in this test has been corrected to use data.loc[5,'hour'] = 7 (instead of 6) @@ -64,23 +64,26 @@ def setUp(self): columns=["hour", "y"], ) - theta_names = ["asymptote", "rate_constant"] - - def SSE(model, data): - expr = sum( - (data.y[i] - model.response_function[data.hour[i]]) ** 2 - for i in data.index - ) + # Sum of squared error function + def SSE(model): + expr = (model.experiment_outputs[model.y] - \ + model.response_function[model.experiment_outputs[model.hour]]) ** 2 return expr + # Create an experiment list + exp_list= [] + for i in range(data.shape[0]): + exp_list.append(RooneyBieglerExperiment(data.loc[i,:].to_frame().transpose())) + + # Create an instance of the parmest estimator + pest = parmest.Estimator(exp_list, obj_function=SSE) + solver_options = {"tol": 1e-8} self.data = data self.pest = parmest.Estimator( - rooney_biegler_model, - data, - theta_names, - SSE, + exp_list, + obj_function=SSE, solver_options=solver_options, tee=True, ) @@ -229,15 +232,17 @@ def test_theta_est_cov(self): # Covariance matrix self.assertAlmostEqual( - cov.iloc[0, 0], 6.30579403, places=2 + cov['asymptote']['asymptote'], 6.30579403, places=2 ) # 6.22864 from paper self.assertAlmostEqual( - cov.iloc[0, 1], -0.4395341, places=2 + cov['asymptote']['rate_constant'], -0.4395341, places=2 ) # -0.4322 from paper self.assertAlmostEqual( - cov.iloc[1, 0], -0.4395341, places=2 + cov['rate_constant']['asymptote'], -0.4395341, places=2 ) # -0.4322 from paper - self.assertAlmostEqual(cov.iloc[1, 1], 0.04124, places=2) # 0.04124 from paper + self.assertAlmostEqual( + cov['rate_constant']['rate_constant'], 0.04124, places=2 + ) # 0.04124 from paper """ 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 @@ -348,7 +353,12 @@ def model(t, asymptote, rate_constant): ) @unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") class TestModelVariants(unittest.TestCase): + def setUp(self): + from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import ( + RooneyBieglerExperiment, + ) + 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"], @@ -360,6 +370,9 @@ def rooney_biegler_params(data): model.asymptote = pyo.Param(initialize=15, mutable=True) model.rate_constant = pyo.Param(initialize=0.5, mutable=True) + model.hour = pyo.Param(within=pyo.PositiveReals, mutable=True) + model.y = pyo.Param(within=pyo.PositiveReals, mutable=True) + def response_rule(m, h): expr = m.asymptote * (1 - pyo.exp(-m.rate_constant * h)) return expr @@ -368,6 +381,17 @@ def response_rule(m, h): return model + class RooneyBieglerExperimentParams(RooneyBieglerExperiment): + + def create_model(self): + self.model = rooney_biegler_params(self.data) + + rooney_biegler_params_exp_list = [] + for i in range(self.data.shape[0]): + rooney_biegler_params_exp_list.append( + RooneyBieglerExperimentParams(self.data.loc[i,:].to_frame().transpose()) + ) + def rooney_biegler_indexed_params(data): model = pyo.ConcreteModel() @@ -378,6 +402,9 @@ def rooney_biegler_indexed_params(data): mutable=True, ) + model.hour = pyo.Param(within=pyo.PositiveReals, mutable=True) + model.y = pyo.Param(within=pyo.PositiveReals, mutable=True) + def response_rule(m, h): expr = m.theta["asymptote"] * ( 1 - pyo.exp(-m.theta["rate_constant"] * h) @@ -388,6 +415,29 @@ def response_rule(m, h): return model + class RooneyBieglerExperimentIndexedParams(RooneyBieglerExperiment): + + def create_model(self): + self.model = rooney_biegler_indexed_params(self.data) + + def label_model(self): + + m = self.model + + m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) + m.experiment_outputs.update([(m.hour, self.data.iloc[0]['hour'])]) + m.experiment_outputs.update([(m.y, self.data.iloc[0]['y'])]) + + m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) + m.unknown_parameters.update((k, pyo.ComponentUID(k)) + for k in [m.theta]) + + rooney_biegler_indexed_params_exp_list = [] + for i in range(self.data.shape[0]): + rooney_biegler_indexed_params_exp_list.append( + RooneyBieglerExperimentIndexedParams(self.data.loc[i,:].to_frame().transpose()) + ) + def rooney_biegler_vars(data): model = pyo.ConcreteModel() @@ -396,6 +446,9 @@ def rooney_biegler_vars(data): model.asymptote.fixed = True # parmest will unfix theta variables model.rate_constant.fixed = True + model.hour = pyo.Param(within=pyo.PositiveReals, mutable=True) + model.y = pyo.Param(within=pyo.PositiveReals, mutable=True) + def response_rule(m, h): expr = m.asymptote * (1 - pyo.exp(-m.rate_constant * h)) return expr @@ -404,6 +457,17 @@ def response_rule(m, h): return model + class RooneyBieglerExperimentVars(RooneyBieglerExperiment): + + def create_model(self): + self.model = rooney_biegler_vars(self.data) + + rooney_biegler_vars_exp_list = [] + for i in range(self.data.shape[0]): + rooney_biegler_vars_exp_list.append( + RooneyBieglerExperimentVars(self.data.loc[i,:].to_frame().transpose()) + ) + def rooney_biegler_indexed_vars(data): model = pyo.ConcreteModel() @@ -418,6 +482,9 @@ def rooney_biegler_indexed_vars(data): ) model.theta["rate_constant"].fixed = True + model.hour = pyo.Param(within=pyo.PositiveReals, mutable=True) + model.y = pyo.Param(within=pyo.PositiveReals, mutable=True) + def response_rule(m, h): expr = m.theta["asymptote"] * ( 1 - pyo.exp(-m.theta["rate_constant"] * h) @@ -428,11 +495,34 @@ def response_rule(m, h): return model - def SSE(model, data): - expr = sum( - (data.y[i] - model.response_function[data.hour[i]]) ** 2 - for i in data.index + class RooneyBieglerExperimentIndexedVars(RooneyBieglerExperiment): + + def create_model(self): + self.model = rooney_biegler_indexed_vars(self.data) + + def label_model(self): + + m = self.model + + m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) + m.experiment_outputs.update([(m.hour, self.data.iloc[0]['hour'])]) + m.experiment_outputs.update([(m.y, self.data.iloc[0]['y'])]) + + m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) + m.unknown_parameters.update((k, pyo.ComponentUID(k)) + for k in [m.theta]) + + + rooney_biegler_indexed_vars_exp_list = [] + for i in range(self.data.shape[0]): + rooney_biegler_indexed_vars_exp_list.append( + RooneyBieglerExperimentIndexedVars(self.data.loc[i,:].to_frame().transpose()) ) + + # Sum of squared error function + def SSE(model): + expr = (model.experiment_outputs[model.y] - \ + model.response_function[model.experiment_outputs[model.hour]]) ** 2 return expr self.objective_function = SSE @@ -444,32 +534,32 @@ def SSE(model, data): self.input = { "param": { - "model": rooney_biegler_params, + "exp_list": rooney_biegler_params_exp_list, "theta_names": ["asymptote", "rate_constant"], "theta_vals": theta_vals, }, "param_index": { - "model": rooney_biegler_indexed_params, + "exp_list": rooney_biegler_indexed_params_exp_list, "theta_names": ["theta"], "theta_vals": theta_vals_index, }, "vars": { - "model": rooney_biegler_vars, + "exp_list": rooney_biegler_vars_exp_list, "theta_names": ["asymptote", "rate_constant"], "theta_vals": theta_vals, }, "vars_index": { - "model": rooney_biegler_indexed_vars, + "exp_list": rooney_biegler_indexed_vars_exp_list, "theta_names": ["theta"], "theta_vals": theta_vals_index, }, "vars_quoted_index": { - "model": rooney_biegler_indexed_vars, + "exp_list": rooney_biegler_indexed_vars_exp_list, "theta_names": ["theta['asymptote']", "theta['rate_constant']"], "theta_vals": theta_vals_index, }, "vars_str_index": { - "model": rooney_biegler_indexed_vars, + "exp_list": rooney_biegler_indexed_vars_exp_list, "theta_names": ["theta[asymptote]", "theta[rate_constant]"], "theta_vals": theta_vals_index, }, @@ -480,58 +570,52 @@ def SSE(model, data): not parmest.inverse_reduced_hessian_available, "Cannot test covariance matrix: required ASL dependency is missing", ) + + def check_rooney_biegler_results(self, objval, cov): + + # get indices in covariance matrix + cov_cols = cov.columns.to_list() + asymptote_index = [idx for idx, s in enumerate(cov_cols) if 'asymptote' in s][0] + rate_constant_index = [idx for idx, s in enumerate(cov_cols) if 'rate_constant' in s][0] + + self.assertAlmostEqual(objval, 4.3317112, places=2) + self.assertAlmostEqual( + cov.iloc[asymptote_index, asymptote_index], 6.30579403, places=2 + ) # 6.22864 from paper + self.assertAlmostEqual( + cov.iloc[asymptote_index, rate_constant_index], -0.4395341, places=2 + ) # -0.4322 from paper + self.assertAlmostEqual( + cov.iloc[rate_constant_index, asymptote_index], -0.4395341, places=2 + ) # -0.4322 from paper + self.assertAlmostEqual( + cov.iloc[rate_constant_index, rate_constant_index], 0.04193591, places=2 + ) # 0.04124 from paper + def test_parmest_basics(self): + for model_type, parmest_input in self.input.items(): pest = parmest.Estimator( - parmest_input["model"], - self.data, - parmest_input["theta_names"], - self.objective_function, + parmest_input["exp_list"], + obj_function=self.objective_function, ) objval, thetavals, cov = pest.theta_est(calc_cov=True, cov_n=6) - - self.assertAlmostEqual(objval, 4.3317112, places=2) - self.assertAlmostEqual( - cov.iloc[0, 0], 6.30579403, places=2 - ) # 6.22864 from paper - self.assertAlmostEqual( - cov.iloc[0, 1], -0.4395341, places=2 - ) # -0.4322 from paper - self.assertAlmostEqual( - cov.iloc[1, 0], -0.4395341, places=2 - ) # -0.4322 from paper - self.assertAlmostEqual( - cov.iloc[1, 1], 0.04193591, places=2 - ) # 0.04124 from paper + self.check_rooney_biegler_results(objval, cov) 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(): + + for model_type, parmest_input in self.input.items(): pest = parmest.Estimator( - parmest_input["model"], - self.data, - parmest_input["theta_names"], - self.objective_function, + parmest_input["exp_list"], + obj_function=self.objective_function, ) objval, thetavals, cov = pest.theta_est(calc_cov=True, cov_n=6) - - self.assertAlmostEqual(objval, 4.3317112, places=2) - self.assertAlmostEqual( - cov.iloc[0, 0], 6.30579403, places=2 - ) # 6.22864 from paper - self.assertAlmostEqual( - cov.iloc[0, 1], -0.4395341, places=2 - ) # -0.4322 from paper - self.assertAlmostEqual( - cov.iloc[1, 0], -0.4395341, places=2 - ) # -0.4322 from paper - self.assertAlmostEqual( - cov.iloc[1, 1], 0.04193591, places=2 - ) # 0.04124 from paper + self.check_rooney_biegler_results(objval, cov) obj_at_theta = pest.objective_at_theta( parmest_input["theta_vals"], initialize_parmest_model=True @@ -540,12 +624,11 @@ def test_parmest_basics_with_initialize_parmest_model_option(self): 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"], - self.data, - parmest_input["theta_names"], - self.objective_function, + parmest_input["exp_list"], + obj_function=self.objective_function, ) obj_at_theta = pest.objective_at_theta( @@ -553,50 +636,23 @@ def test_parmest_basics_with_square_problem_solve(self): ) objval, thetavals, cov = pest.theta_est(calc_cov=True, cov_n=6) - - self.assertAlmostEqual(objval, 4.3317112, places=2) - self.assertAlmostEqual( - cov.iloc[0, 0], 6.30579403, places=2 - ) # 6.22864 from paper - self.assertAlmostEqual( - cov.iloc[0, 1], -0.4395341, places=2 - ) # -0.4322 from paper - self.assertAlmostEqual( - cov.iloc[1, 0], -0.4395341, places=2 - ) # -0.4322 from paper - self.assertAlmostEqual( - cov.iloc[1, 1], 0.04193591, places=2 - ) # 0.04124 from paper + self.check_rooney_biegler_results(objval, cov) 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"], - self.data, - parmest_input["theta_names"], - self.objective_function, + parmest_input["exp_list"], + obj_function=self.objective_function, ) obj_at_theta = pest.objective_at_theta(initialize_parmest_model=True) objval, thetavals, cov = pest.theta_est(calc_cov=True, cov_n=6) - - self.assertAlmostEqual(objval, 4.3317112, places=2) - self.assertAlmostEqual( - cov.iloc[0, 0], 6.30579403, places=2 - ) # 6.22864 from paper - self.assertAlmostEqual( - cov.iloc[0, 1], -0.4395341, places=2 - ) # -0.4322 from paper - self.assertAlmostEqual( - cov.iloc[1, 0], -0.4395341, places=2 - ) # -0.4322 from paper - self.assertAlmostEqual( - cov.iloc[1, 1], 0.04193591, places=2 - ) # 0.04124 from paper - + self.check_rooney_biegler_results(objval, cov) @unittest.skipIf( not parmest.parmest_available, @@ -606,7 +662,7 @@ def test_parmest_basics_with_square_problem_solve_no_theta_vals(self): class TestReactorDesign(unittest.TestCase): def setUp(self): from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( - reactor_design_model, + ReactorDesignExperiment, ) # Data from the design @@ -635,22 +691,14 @@ def setUp(self): columns=["sv", "caf", "ca", "cb", "cc", "cd"], ) - 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 - ) - return expr + # Create an experiment list + exp_list= [] + for i in range(data.shape[0]): + exp_list.append(ReactorDesignExperiment(data, i)) solver_options = {"max_iter": 6000} - self.pest = parmest.Estimator( - reactor_design_model, data, theta_names, SSE, solver_options=solver_options - ) + self.pest = parmest.Estimator(exp_list, obj_function='SSE', solver_options=solver_options) def test_theta_est(self): # used in data reconciliation @@ -759,6 +807,30 @@ def total_cost_rule(model): return m + class ReactorDesignExperimentDAE(Experiment): + + def __init__(self, data): + + self.data = data + self.model = None + + def create_model(self): + self.model = ABC_model(self.data) + + def label_model(self): + + m = self.model + + m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) + m.unknown_parameters.update((k, pyo.ComponentUID(k)) + for k in [m.k1, m.k2]) + + def get_labeled_model(self): + self.create_model() + self.label_model() + + return self.model + # This example tests data formatted in 3 ways # Each format holds 1 scenario # 1. dataframe with time index @@ -793,18 +865,21 @@ def total_cost_rule(model): "cc": {k: v for (k, v) in zip(data.t, data.cc)}, } - theta_names = ["k1", "k2"] + # Create an experiment list + exp_list_df = [ReactorDesignExperimentDAE(data_df)] + exp_list_dict = [ReactorDesignExperimentDAE(data_dict)] - self.pest_df = parmest.Estimator(ABC_model, [data_df], theta_names) - self.pest_dict = parmest.Estimator(ABC_model, [data_dict], theta_names) + self.pest_df = parmest.Estimator(exp_list_df) + self.pest_dict = parmest.Estimator(exp_list_dict) # Estimator object with multiple scenarios - self.pest_df_multiple = parmest.Estimator( - ABC_model, [data_df, data_df], theta_names - ) - self.pest_dict_multiple = parmest.Estimator( - ABC_model, [data_dict, data_dict], theta_names - ) + exp_list_df_multiple = [ReactorDesignExperimentDAE(data_df), + ReactorDesignExperimentDAE(data_df)] + exp_list_dict_multiple = [ReactorDesignExperimentDAE(data_dict), + ReactorDesignExperimentDAE(data_dict)] + + self.pest_df_multiple = parmest.Estimator(exp_list_df_multiple) + self.pest_dict_multiple = parmest.Estimator(exp_list_dict_multiple) # Create an instance of the model self.m_df = ABC_model(data_df) @@ -880,7 +955,7 @@ def test_covariance(self): class TestSquareInitialization_RooneyBiegler(unittest.TestCase): def setUp(self): from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler_with_constraint import ( - rooney_biegler_model_with_constraint, + RooneyBieglerExperiment, ) # Note, the data used in this test has been corrected to use data.loc[5,'hour'] = 7 (instead of 6) @@ -888,24 +963,25 @@ def setUp(self): data=[[1, 8.3], [2, 10.3], [3, 19.0], [4, 16.0], [5, 15.6], [7, 19.8]], columns=["hour", "y"], ) + + # Sum of squared error function + def SSE(model): + expr = (model.experiment_outputs[model.y] - \ + model.response_function[model.experiment_outputs[model.hour]]) ** 2 + return expr - theta_names = ["asymptote", "rate_constant"] - - def SSE(model, data): - expr = sum( - (data.y[i] - model.response_function[data.hour[i]]) ** 2 - for i in data.index + exp_list = [] + for i in range(data.shape[0]): + exp_list.append( + RooneyBieglerExperiment(data.loc[i,:].to_frame().transpose()) ) - return expr solver_options = {"tol": 1e-8} self.data = data self.pest = parmest.Estimator( - rooney_biegler_model_with_constraint, - data, - theta_names, - SSE, + exp_list, + obj_function=SSE, solver_options=solver_options, tee=True, ) diff --git a/pyomo/contrib/parmest/tests/test_scenariocreator.py b/pyomo/contrib/parmest/tests/test_scenariocreator.py index 22a851ae32e..bf6fa12b8b1 100644 --- a/pyomo/contrib/parmest/tests/test_scenariocreator.py +++ b/pyomo/contrib/parmest/tests/test_scenariocreator.py @@ -37,7 +37,7 @@ class TestScenarioReactorDesign(unittest.TestCase): def setUp(self): from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( - reactor_design_model, + ReactorDesignExperiment, ) # Data from the design @@ -65,19 +65,13 @@ def setUp(self): ], columns=["sv", "caf", "ca", "cb", "cc", "cd"], ) + + # Create an experiment list + exp_list= [] + for i in range(data.shape[0]): + exp_list.append(ReactorDesignExperiment(data, i)) - 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 - ) - return expr - - self.pest = parmest.Estimator(reactor_design_model, data, theta_names, SSE) + self.pest = parmest.Estimator(exp_list, obj_function='SSE') def test_scen_from_exps(self): scenmaker = sc.ScenarioCreator(self.pest, "ipopt") @@ -115,9 +109,6 @@ def setUp(self): import pyomo.contrib.parmest.examples.semibatch.semibatch as sb import json - # Vars to estimate in parmest - theta_names = ["k1", "k2", "E1", "E2"] - self.fbase = os.path.join(testdir, "..", "examples", "semibatch") # Data, list of dictionaries data = [] @@ -131,7 +122,12 @@ def setUp(self): # Note, the model already includes a 'SecondStageCost' expression # for the sum of squared error that will be used in parameter estimation - self.pest = parmest.Estimator(sb.generate_model, data, theta_names) + # Create an experiment list + exp_list= [] + for i in range(len(data)): + exp_list.append(sb.SemiBatchExperiment(data[i])) + + self.pest = parmest.Estimator(exp_list) def test_semibatch_bootstrap(self): scenmaker = sc.ScenarioCreator(self.pest, "ipopt") diff --git a/pyomo/contrib/parmest/tests/test_utils.py b/pyomo/contrib/parmest/tests/test_utils.py index 514c14b1e82..99ba7b7cd90 100644 --- a/pyomo/contrib/parmest/tests/test_utils.py +++ b/pyomo/contrib/parmest/tests/test_utils.py @@ -48,12 +48,17 @@ def test_convert_param_to_var(self): columns=["sv", "caf", "ca", "cb", "cc", "cd"], ) - theta_names = ["k1", "k2", "k3"] + # make model + instance = reactor_design_model() + + # add caf, sv + instance.caf = data.iloc[0]['caf'] + instance.sv = data.iloc[0]['sv'] - instance = reactor_design_model(data.loc[0]) solver = pyo.SolverFactory("ipopt") solver.solve(instance) + theta_names = ['k1', 'k2', 'k3'] instance_vars = parmest.utils.convert_params_to_vars( instance, theta_names, fix_vars=True ) From 9d7e5c0b15e4758e3fe318995a990b04205cd226 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 30 Jan 2024 12:29:52 -0700 Subject: [PATCH 0882/1797] 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 0883/1797] 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 0884/1797] 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 8f7f95b2bcacb5e04910a81fe46643f9d5329162 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 30 Jan 2024 15:49:41 -0700 Subject: [PATCH 0885/1797] Adding tests for the pyomo-to-docplex map on the walker --- pyomo/contrib/cp/tests/test_docplex_walker.py | 224 ++++++++++++++++-- 1 file changed, 210 insertions(+), 14 deletions(-) diff --git a/pyomo/contrib/cp/tests/test_docplex_walker.py b/pyomo/contrib/cp/tests/test_docplex_walker.py index fc475190ade..73b4fd8e00c 100644 --- a/pyomo/contrib/cp/tests/test_docplex_walker.py +++ b/pyomo/contrib/cp/tests/test_docplex_walker.py @@ -107,6 +107,10 @@ def test_write_addition(self): expr[1].equals(cpx_x + cp.start_of(cpx_i) + cp.length_of(cpx_i2)) ) + self.assertIs(visitor.pyomo_to_docplex[m.x], cpx_x) + self.assertIs(visitor.pyomo_to_docplex[m.i], cpx_i) + self.assertIs(visitor.pyomo_to_docplex[m.i2[2]], cpx_i2) + def test_write_subtraction(self): m = self.get_model() m.a.domain = Binary @@ -122,6 +126,9 @@ def test_write_subtraction(self): self.assertTrue(expr[1].equals(x + (-1 * a1))) + self.assertIs(visitor.pyomo_to_docplex[m.x], x) + self.assertIs(visitor.pyomo_to_docplex[m.a[1]], a1) + def test_write_product(self): m = self.get_model() m.a.domain = PositiveIntegers @@ -137,6 +144,9 @@ def test_write_product(self): self.assertTrue(expr[1].equals(x * (a1 + 1))) + self.assertIs(visitor.pyomo_to_docplex[m.x], x) + self.assertIs(visitor.pyomo_to_docplex[m.a[1]], a1) + def test_write_floating_point_division(self): m = self.get_model() m.a.domain = NonNegativeIntegers @@ -152,6 +162,9 @@ def test_write_floating_point_division(self): self.assertTrue(expr[1].equals(x / (a1 + 1))) + self.assertIs(visitor.pyomo_to_docplex[m.x], x) + self.assertIs(visitor.pyomo_to_docplex[m.a[1]], a1) + def test_write_power_expression(self): m = self.get_model() m.c = Constraint(expr=m.x**2 <= 3) @@ -163,6 +176,8 @@ def test_write_power_expression(self): # .equals checks the equality of two expressions in docplex. self.assertTrue(expr[1].equals(cpx_x**2)) + self.assertIs(visitor.pyomo_to_docplex[m.x], cpx_x) + def test_write_absolute_value_expression(self): m = self.get_model() m.a.domain = NegativeIntegers @@ -176,6 +191,8 @@ def test_write_absolute_value_expression(self): self.assertTrue(expr[1].equals(cp.abs(a1) + 1)) + self.assertIs(visitor.pyomo_to_docplex[m.a[1]], a1) + def test_write_min_expression(self): m = self.get_model() m.a.domain = NonPositiveIntegers @@ -187,6 +204,7 @@ def test_write_min_expression(self): for i in m.I: self.assertIn(id(m.a[i]), visitor.var_map) a[i] = visitor.var_map[id(m.a[i])] + self.assertIs(visitor.pyomo_to_docplex[m.a[i]], a[i]) self.assertTrue(expr[1].equals(cp.min(a[i] for i in m.I))) @@ -201,6 +219,7 @@ def test_write_max_expression(self): for i in m.I: self.assertIn(id(m.a[i]), visitor.var_map) a[i] = visitor.var_map[id(m.a[i])] + self.assertIs(visitor.pyomo_to_docplex[m.a[i]], a[i]) self.assertTrue(expr[1].equals(cp.max(a[i] for i in m.I))) @@ -235,6 +254,14 @@ def test_write_logical_and(self): self.assertTrue(expr[1].equals(cp.logical_and(b, b2b))) + # ESJ: This is ludicrous, but I don't know how to get the args of a CP + # expression, so testing that we were correct in the pyomo to docplex + # map by checking that we can build an expression that is the same as b + # (because b is actually "b == 1" since docplex doesn't believe in + # Booleans) + self.assertTrue(b.equals(visitor.pyomo_to_docplex[m.b] == 1)) + self.assertTrue(b2b.equals(visitor.pyomo_to_docplex[m.b2['b']] == 1)) + def test_write_logical_or(self): m = self.get_model() m.c = LogicalConstraint(expr=m.b.lor(m.i.is_present)) @@ -248,6 +275,9 @@ def test_write_logical_or(self): self.assertTrue(expr[1].equals(cp.logical_or(b, cp.presence_of(i)))) + self.assertTrue(b.equals(visitor.pyomo_to_docplex[m.b] == 1)) + self.assertIs(visitor.pyomo_to_docplex[m.i], i) + def test_write_xor(self): m = self.get_model() m.c = LogicalConstraint(expr=m.b.xor(m.i2[2].start_time >= 5)) @@ -265,6 +295,9 @@ def test_write_xor(self): expr[1].equals(cp.count([b, cp.less_or_equal(5, cp.start_of(i22))], 1) == 1) ) + self.assertTrue(b.equals(visitor.pyomo_to_docplex[m.b] == 1)) + self.assertIs(visitor.pyomo_to_docplex[m.i2[2]], i22) + def test_write_logical_not(self): m = self.get_model() m.c = LogicalConstraint(expr=~m.b2['a']) @@ -276,6 +309,8 @@ def test_write_logical_not(self): self.assertTrue(expr[1].equals(cp.logical_not(b2a))) + self.assertTrue(b2a.equals(visitor.pyomo_to_docplex[m.b2['a']] == 1)) + def test_equivalence(self): m = self.get_model() m.c = LogicalConstraint(expr=equivalent(~m.b2['a'], m.b)) @@ -289,18 +324,8 @@ def test_equivalence(self): self.assertTrue(expr[1].equals(cp.equal(cp.logical_not(b2a), b))) - def test_implication(self): - m = self.get_model() - m.c = LogicalConstraint(expr=m.b2['a'].implies(~m.b)) - visitor = self.get_visitor() - expr = visitor.walk_expression((m.c.expr, m.c, 0)) - - self.assertIn(id(m.b), visitor.var_map) - self.assertIn(id(m.b2['a']), visitor.var_map) - b = visitor.var_map[id(m.b)] - b2a = visitor.var_map[id(m.b2['a'])] - - self.assertTrue(expr[1].equals(cp.if_then(b2a, cp.logical_not(b)))) + self.assertTrue(b.equals(visitor.pyomo_to_docplex[m.b] == 1)) + self.assertTrue(b2a.equals(visitor.pyomo_to_docplex[m.b2['a']] == 1)) def test_equality(self): m = self.get_model() @@ -317,6 +342,9 @@ def test_equality(self): self.assertTrue(expr[1].equals(cp.if_then(b, cp.equal(a3, 4)))) + self.assertTrue(b.equals(visitor.pyomo_to_docplex[m.b] == 1)) + self.assertIs(visitor.pyomo_to_docplex[m.a[3]], a3) + def test_inequality(self): m = self.get_model() m.a.domain = Integers @@ -334,6 +362,10 @@ def test_inequality(self): self.assertTrue(expr[1].equals(cp.if_then(b, cp.less_or_equal(a4, a3)))) + self.assertTrue(b.equals(visitor.pyomo_to_docplex[m.b] == 1)) + self.assertIs(visitor.pyomo_to_docplex[m.a[3]], a3) + self.assertIs(visitor.pyomo_to_docplex[m.a[4]], a4) + def test_ranged_inequality(self): m = self.get_model() m.a.domain = Integers @@ -364,6 +396,10 @@ def test_not_equal(self): self.assertTrue(expr[1].equals(cp.if_then(b, a3 != a4))) + self.assertTrue(b.equals(visitor.pyomo_to_docplex[m.b] == 1)) + self.assertIs(visitor.pyomo_to_docplex[m.a[3]], a3) + self.assertIs(visitor.pyomo_to_docplex[m.a[4]], a4) + def test_exactly_expression(self): m = self.get_model() m.a.domain = Integers @@ -376,6 +412,7 @@ def test_exactly_expression(self): for i in m.I: self.assertIn(id(m.a[i]), visitor.var_map) a[i] = visitor.var_map[id(m.a[i])] + self.assertIs(visitor.pyomo_to_docplex[m.a[i]], a[i]) self.assertTrue( expr[1].equals(cp.equal(cp.count([a[i] == 4 for i in m.I], 1), 3)) @@ -393,6 +430,7 @@ def test_atleast_expression(self): for i in m.I: self.assertIn(id(m.a[i]), visitor.var_map) a[i] = visitor.var_map[id(m.a[i])] + self.assertIs(visitor.pyomo_to_docplex[m.a[i]], a[i]) self.assertTrue( expr[1].equals( @@ -412,6 +450,7 @@ def test_atmost_expression(self): for i in m.I: self.assertIn(id(m.a[i]), visitor.var_map) a[i] = visitor.var_map[id(m.a[i])] + self.assertIs(visitor.pyomo_to_docplex[m.a[i]], a[i]) self.assertTrue( expr[1].equals(cp.less_or_equal(cp.count([a[i] == 4 for i in m.I], 1), 3)) @@ -430,6 +469,7 @@ def test_all_diff_expression(self): for i in m.I: self.assertIn(id(m.a[i]), visitor.var_map) a[i] = visitor.var_map[id(m.a[i])] + self.assertIs(visitor.pyomo_to_docplex[m.a[i]], a[i]) self.assertTrue(expr[1].equals(cp.all_diff(a[i] for i in m.I))) @@ -449,6 +489,9 @@ def test_Boolean_args_in_all_diff_expression(self): self.assertTrue(expr[1].equals(cp.all_diff(a0 == 13, b))) + self.assertIs(visitor.pyomo_to_docplex[m.a[1]], a0) + self.assertTrue(b.equals(visitor.pyomo_to_docplex[m.b] == 1)) + def test_count_if_expression(self): m = self.get_model() m.a.domain = Integers @@ -462,6 +505,7 @@ def test_count_if_expression(self): for i in m.I: self.assertIn(id(m.a[i]), visitor.var_map) a[i] = visitor.var_map[id(m.a[i])] + self.assertIs(visitor.pyomo_to_docplex[m.a[i]], a[i]) self.assertTrue(expr[1].equals(cp.count((a[i] == i for i in m.I), 1) == 5)) @@ -480,6 +524,9 @@ def test_interval_var_is_present(self): self.assertTrue(expr[1].equals(cp.if_then(cp.presence_of(i), a1 == 5))) + self.assertIs(visitor.pyomo_to_docplex[m.a[1]], a1) + self.assertIs(visitor.pyomo_to_docplex[m.i], i) + def test_interval_var_is_present_indirection(self): m = self.get_model() m.a.domain = Integers @@ -513,6 +560,11 @@ def test_interval_var_is_present_indirection(self): ) ) + self.assertIs(visitor.pyomo_to_docplex[m.a[1]], a1) + self.assertIs(visitor.pyomo_to_docplex[m.y], y) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) + self.assertIs(visitor.pyomo_to_docplex[m.i2[2]], i22) + def test_is_present_indirection_and_length(self): m = self.get_model() m.y = Var(domain=Integers, bounds=[1, 2]) @@ -547,6 +599,10 @@ def test_is_present_indirection_and_length(self): ) ) + self.assertIs(visitor.pyomo_to_docplex[m.y], y) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) + self.assertIs(visitor.pyomo_to_docplex[m.i2[2]], i22) + def test_handle_getattr_lor(self): m = self.get_model() m.y = Var(domain=Integers, bounds=(1, 2)) @@ -578,6 +634,11 @@ def test_handle_getattr_lor(self): ) ) + self.assertIs(visitor.pyomo_to_docplex[m.y], y) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) + self.assertIs(visitor.pyomo_to_docplex[m.i2[2]], i22) + self.assertTrue(b.equals(visitor.pyomo_to_docplex[m.b] == 1)) + def test_handle_getattr_xor(self): m = self.get_model() m.y = Var(domain=Integers, bounds=(1, 2)) @@ -616,6 +677,11 @@ def test_handle_getattr_xor(self): ) ) + self.assertIs(visitor.pyomo_to_docplex[m.y], y) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) + self.assertIs(visitor.pyomo_to_docplex[m.i2[2]], i22) + self.assertTrue(b.equals(visitor.pyomo_to_docplex[m.b] == 1)) + def test_handle_getattr_equivalent_to(self): m = self.get_model() m.y = Var(domain=Integers, bounds=(1, 2)) @@ -647,6 +713,11 @@ def test_handle_getattr_equivalent_to(self): ) ) + self.assertIs(visitor.pyomo_to_docplex[m.y], y) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) + self.assertIs(visitor.pyomo_to_docplex[m.i2[2]], i22) + self.assertTrue(b.equals(visitor.pyomo_to_docplex[m.b] == 1)) + def test_logical_or_on_indirection(self): m = ConcreteModel() m.b = BooleanVar([2, 3, 4, 5]) @@ -676,6 +747,11 @@ def test_logical_or_on_indirection(self): ) ) + self.assertIs(visitor.pyomo_to_docplex[m.x], x) + self.assertTrue(b3.equals(visitor.pyomo_to_docplex[m.b[3]] == 1)) + self.assertTrue(b4.equals(visitor.pyomo_to_docplex[m.b[4]] == 1)) + self.assertTrue(b5.equals(visitor.pyomo_to_docplex[m.b[5]] == 1)) + def test_logical_xor_on_indirection(self): m = ConcreteModel() m.b = BooleanVar([2, 3, 4, 5]) @@ -710,6 +786,10 @@ def test_logical_xor_on_indirection(self): ) ) + self.assertIs(visitor.pyomo_to_docplex[m.x], x) + self.assertTrue(b3.equals(visitor.pyomo_to_docplex[m.b[3]] == 1)) + self.assertTrue(b5.equals(visitor.pyomo_to_docplex[m.b[5]] == 1)) + def test_using_precedence_expr_as_boolean_expr(self): m = self.get_model() e = m.b.implies(m.i2[2].start_time.before(m.i2[1].start_time)) @@ -729,6 +809,10 @@ def test_using_precedence_expr_as_boolean_expr(self): expr[1].equals(cp.if_then(b, cp.start_of(i22) + 0 <= cp.start_of(i21))) ) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) + self.assertIs(visitor.pyomo_to_docplex[m.i2[2]], i22) + self.assertTrue(b.equals(visitor.pyomo_to_docplex[m.b] == 1)) + def test_using_precedence_expr_as_boolean_expr_positive_delay(self): m = self.get_model() e = m.b.implies(m.i2[2].start_time.before(m.i2[1].start_time, delay=4)) @@ -748,6 +832,10 @@ def test_using_precedence_expr_as_boolean_expr_positive_delay(self): expr[1].equals(cp.if_then(b, cp.start_of(i22) + 4 <= cp.start_of(i21))) ) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) + self.assertIs(visitor.pyomo_to_docplex[m.i2[2]], i22) + self.assertTrue(b.equals(visitor.pyomo_to_docplex[m.b] == 1)) + def test_using_precedence_expr_as_boolean_expr_negative_delay(self): m = self.get_model() e = m.b.implies(m.i2[2].start_time.at(m.i2[1].start_time, delay=-3)) @@ -767,6 +855,10 @@ def test_using_precedence_expr_as_boolean_expr_negative_delay(self): expr[1].equals(cp.if_then(b, cp.start_of(i22) + (-3) == cp.start_of(i21))) ) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) + self.assertIs(visitor.pyomo_to_docplex[m.i2[2]], i22) + self.assertTrue(b.equals(visitor.pyomo_to_docplex[m.b] == 1)) + @unittest.skipIf(not docplex_available, "docplex is not available") class TestCPExpressionWalker_IntervalVars(CommonTest): @@ -780,6 +872,7 @@ def test_interval_var_fixed_presences_correct(self): i = visitor.var_map[id(m.i)] # Check that docplex knows it's optional self.assertTrue(i.is_optional()) + self.assertIs(visitor.pyomo_to_docplex[m.i], i) # Now fix it to absent m.i.is_present.fix(False) @@ -790,8 +883,10 @@ def test_interval_var_fixed_presences_correct(self): self.assertIn(id(m.i2[1]), visitor.var_map) i21 = visitor.var_map[id(m.i2[1])] + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) self.assertIn(id(m.i), visitor.var_map) i = visitor.var_map[id(m.i)] + self.assertIs(visitor.pyomo_to_docplex[m.i], i) # Check that we passed on the presence info to docplex self.assertTrue(i.is_absent()) @@ -810,6 +905,7 @@ def test_interval_var_fixed_length(self): self.assertIn(id(m.i), visitor.var_map) i = visitor.var_map[id(m.i)] + self.assertIs(visitor.pyomo_to_docplex[m.i], i) self.assertTrue(i.is_optional()) self.assertEqual(i.get_length(), (4, 4)) @@ -827,6 +923,7 @@ def test_interval_var_fixed_start_and_end(self): self.assertIn(id(m.i), visitor.var_map) i = visitor.var_map[id(m.i)] + self.assertIs(visitor.pyomo_to_docplex[m.i], i) self.assertFalse(i.is_optional()) self.assertEqual(i.get_start(), (3, 3)) @@ -844,10 +941,14 @@ def get_model(self): def check_scalar_sequence_var(self, m, visitor): self.assertIn(id(m.seq), visitor.var_map) seq = visitor.var_map[id(m.seq)] + self.assertIs(visitor.pyomo_to_docplex[m.seq], seq) i = visitor.var_map[id(m.i)] i21 = visitor.var_map[id(m.i2[1])] i22 = visitor.var_map[id(m.i2[2])] + self.assertIs(visitor.pyomo_to_docplex[m.i], i) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) + self.assertIs(visitor.pyomo_to_docplex[m.i2[2]], i22) ivs = seq.get_interval_variables() self.assertEqual(len(ivs), 3) @@ -914,6 +1015,8 @@ def test_start_before_start(self): i = visitor.var_map[id(m.i)] i21 = visitor.var_map[id(m.i2[1])] + self.assertIs(visitor.pyomo_to_docplex[m.i], i) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) self.assertTrue(expr[1].equals(cp.start_before_start(i, i21, 0))) @@ -928,6 +1031,8 @@ def test_start_before_end(self): i = visitor.var_map[id(m.i)] i21 = visitor.var_map[id(m.i2[1])] + self.assertIs(visitor.pyomo_to_docplex[m.i], i) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) self.assertTrue(expr[1].equals(cp.start_before_end(i, i21, 3))) @@ -942,6 +1047,8 @@ def test_end_before_start(self): i = visitor.var_map[id(m.i)] i21 = visitor.var_map[id(m.i2[1])] + self.assertIs(visitor.pyomo_to_docplex[m.i], i) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) self.assertTrue(expr[1].equals(cp.end_before_start(i, i21, -2))) @@ -956,6 +1063,8 @@ def test_end_before_end(self): i = visitor.var_map[id(m.i)] i21 = visitor.var_map[id(m.i2[1])] + self.assertIs(visitor.pyomo_to_docplex[m.i], i) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) self.assertTrue(expr[1].equals(cp.end_before_end(i, i21, 6))) @@ -970,6 +1079,8 @@ def test_start_at_start(self): i = visitor.var_map[id(m.i)] i21 = visitor.var_map[id(m.i2[1])] + self.assertIs(visitor.pyomo_to_docplex[m.i], i) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) self.assertTrue(expr[1].equals(cp.start_at_start(i, i21, 0))) @@ -984,6 +1095,8 @@ def test_start_at_end(self): i = visitor.var_map[id(m.i)] i21 = visitor.var_map[id(m.i2[1])] + self.assertIs(visitor.pyomo_to_docplex[m.i], i) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) self.assertTrue(expr[1].equals(cp.start_at_end(i, i21, 3))) @@ -998,6 +1111,8 @@ def test_end_at_start(self): i = visitor.var_map[id(m.i)] i21 = visitor.var_map[id(m.i2[1])] + self.assertIs(visitor.pyomo_to_docplex[m.i], i) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) self.assertTrue(expr[1].equals(cp.end_at_start(i, i21, -2))) @@ -1012,6 +1127,8 @@ def test_end_at_end(self): i = visitor.var_map[id(m.i)] i21 = visitor.var_map[id(m.i2[1])] + self.assertIs(visitor.pyomo_to_docplex[m.i], i) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) self.assertTrue(expr[1].equals(cp.end_at_end(i, i21, 6))) @@ -1036,6 +1153,10 @@ def test_indirection_before_constraint(self): i21 = visitor.var_map[id(m.i2[1])] i22 = visitor.var_map[id(m.i2[2])] i = visitor.var_map[id(m.i)] + self.assertIs(visitor.pyomo_to_docplex[m.y], y) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) + self.assertIs(visitor.pyomo_to_docplex[m.i2[2]], i22) + self.assertIs(visitor.pyomo_to_docplex[m.i], i) self.assertTrue( expr[1].equals( @@ -1062,6 +1183,10 @@ def test_indirection_after_constraint(self): i21 = visitor.var_map[id(m.i2[1])] i22 = visitor.var_map[id(m.i2[2])] i = visitor.var_map[id(m.i)] + self.assertIs(visitor.pyomo_to_docplex[m.y], y) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) + self.assertIs(visitor.pyomo_to_docplex[m.i2[2]], i22) + self.assertIs(visitor.pyomo_to_docplex[m.i], i) self.assertTrue( expr[1].equals( @@ -1089,6 +1214,10 @@ def test_indirection_at_constraint(self): i21 = visitor.var_map[id(m.i2[1])] i22 = visitor.var_map[id(m.i2[2])] i = visitor.var_map[id(m.i)] + self.assertIs(visitor.pyomo_to_docplex[m.y], y) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) + self.assertIs(visitor.pyomo_to_docplex[m.i2[2]], i22) + self.assertIs(visitor.pyomo_to_docplex[m.i], i) self.assertTrue( expr[1].equals( @@ -1116,6 +1245,10 @@ def test_before_indirection_constraint(self): i21 = visitor.var_map[id(m.i2[1])] i22 = visitor.var_map[id(m.i2[2])] i = visitor.var_map[id(m.i)] + self.assertIs(visitor.pyomo_to_docplex[m.y], y) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) + self.assertIs(visitor.pyomo_to_docplex[m.i2[2]], i22) + self.assertIs(visitor.pyomo_to_docplex[m.i], i) self.assertTrue( expr[1].equals( @@ -1141,6 +1274,10 @@ def test_after_indirection_constraint(self): i21 = visitor.var_map[id(m.i2[1])] i22 = visitor.var_map[id(m.i2[2])] i = visitor.var_map[id(m.i)] + self.assertIs(visitor.pyomo_to_docplex[m.y], y) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) + self.assertIs(visitor.pyomo_to_docplex[m.i2[2]], i22) + self.assertIs(visitor.pyomo_to_docplex[m.i], i) self.assertTrue( expr[1].equals( @@ -1166,6 +1303,10 @@ def test_at_indirection_constraint(self): i21 = visitor.var_map[id(m.i2[1])] i22 = visitor.var_map[id(m.i2[2])] i = visitor.var_map[id(m.i)] + self.assertIs(visitor.pyomo_to_docplex[m.y], y) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) + self.assertIs(visitor.pyomo_to_docplex[m.i2[2]], i22) + self.assertIs(visitor.pyomo_to_docplex[m.i], i) self.assertTrue( expr[1].equals( @@ -1200,6 +1341,13 @@ def test_double_indirection_before_constraint(self): i33 = visitor.var_map[id(m.i3[1, 3])] i34 = visitor.var_map[id(m.i3[1, 4])] i35 = visitor.var_map[id(m.i3[1, 5])] + self.assertIs(visitor.pyomo_to_docplex[m.y], y) + self.assertIs(visitor.pyomo_to_docplex[m.x], x) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) + self.assertIs(visitor.pyomo_to_docplex[m.i2[2]], i22) + self.assertIs(visitor.pyomo_to_docplex[m.i3[1, 3]], i33) + self.assertIs(visitor.pyomo_to_docplex[m.i3[1, 4]], i34) + self.assertIs(visitor.pyomo_to_docplex[m.i3[1, 5]], i35) self.assertTrue( expr[1].equals( @@ -1237,6 +1385,13 @@ def test_double_indirection_after_constraint(self): i33 = visitor.var_map[id(m.i3[1, 3])] i34 = visitor.var_map[id(m.i3[1, 4])] i35 = visitor.var_map[id(m.i3[1, 5])] + self.assertIs(visitor.pyomo_to_docplex[m.y], y) + self.assertIs(visitor.pyomo_to_docplex[m.x], x) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) + self.assertIs(visitor.pyomo_to_docplex[m.i2[2]], i22) + self.assertIs(visitor.pyomo_to_docplex[m.i3[1, 3]], i33) + self.assertIs(visitor.pyomo_to_docplex[m.i3[1, 4]], i34) + self.assertIs(visitor.pyomo_to_docplex[m.i3[1, 5]], i35) self.assertTrue( expr[1].equals( @@ -1272,6 +1427,13 @@ def test_double_indirection_at_constraint(self): i33 = visitor.var_map[id(m.i3[1, 3])] i34 = visitor.var_map[id(m.i3[1, 4])] i35 = visitor.var_map[id(m.i3[1, 5])] + self.assertIs(visitor.pyomo_to_docplex[m.y], y) + self.assertIs(visitor.pyomo_to_docplex[m.x], x) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) + self.assertIs(visitor.pyomo_to_docplex[m.i2[2]], i22) + self.assertIs(visitor.pyomo_to_docplex[m.i3[1, 3]], i33) + self.assertIs(visitor.pyomo_to_docplex[m.i3[1, 4]], i34) + self.assertIs(visitor.pyomo_to_docplex[m.i3[1, 5]], i35) self.assertTrue( expr[1].equals( @@ -1319,6 +1481,8 @@ def param_rule(m, i): self.assertIn(id(m.a), visitor.var_map) x = visitor.var_map[id(m.x)] a = visitor.var_map[id(m.a)] + self.assertIs(visitor.pyomo_to_docplex[m.x], x) + self.assertIs(visitor.pyomo_to_docplex[m.a], a) self.assertTrue(expr[1].equals(cp.element([2, 4, 6], 0 + 1 * (x - 1) // 2) / a)) @@ -1346,6 +1510,8 @@ def test_spans(self): self.assertIn(id(m.whole_enchilada), visitor.var_map) whole_enchilada = visitor.var_map[id(m.whole_enchilada)] + self.assertIs(visitor.pyomo_to_docplex[m.whole_enchilada], whole_enchilada) + iv = {} for i in [1, 2, 3]: self.assertIn(id(m.iv[i]), visitor.var_map) @@ -1363,6 +1529,8 @@ def test_alternative(self): self.assertIn(id(m.whole_enchilada), visitor.var_map) whole_enchilada = visitor.var_map[id(m.whole_enchilada)] + self.assertIs(visitor.pyomo_to_docplex[m.whole_enchilada], whole_enchilada) + iv = {} for i in [1, 2, 3]: self.assertIn(id(m.iv[i]), visitor.var_map) @@ -1395,6 +1563,9 @@ def test_always_in(self): i = visitor.var_map[id(m.i)] i21 = visitor.var_map[id(m.i2[1])] i22 = visitor.var_map[id(m.i2[2])] + self.assertIs(visitor.pyomo_to_docplex[m.i], i) + self.assertIs(visitor.pyomo_to_docplex[m.i2[1]], i21) + self.assertIs(visitor.pyomo_to_docplex[m.i2[2]], i22) self.assertTrue( expr[1].equals( @@ -1422,6 +1593,7 @@ def test_always_in_single_pulse(self): self.assertIn(id(m.i), visitor.var_map) i = visitor.var_map[id(m.i)] + self.assertIs(visitor.pyomo_to_docplex[m.i], i) self.assertTrue( expr[1].equals(cp.always_in(cp.pulse(i, 3), interval=(0, 10), min=0, max=3)) @@ -1440,6 +1612,7 @@ def test_named_expression(self): self.assertIn(id(m.x), visitor.var_map) x = visitor.var_map[id(m.x)] + self.assertIs(visitor.pyomo_to_docplex[m.x], x) self.assertTrue(expr[1].equals(x**2 + 7)) @@ -1453,6 +1626,7 @@ def test_repeated_named_expression(self): self.assertIn(id(m.x), visitor.var_map) x = visitor.var_map[id(m.x)] + self.assertIs(visitor.pyomo_to_docplex[m.x], x) self.assertTrue(expr[1].equals(x**2 + 7 + (-1) * (8 * (x**2 + 7)))) @@ -1483,6 +1657,7 @@ def test_fixed_integer_var(self): self.assertIn(id(m.a[2]), visitor.var_map) a2 = visitor.var_map[id(m.a[2])] + self.assertIs(visitor.pyomo_to_docplex[m.a[2]], a2) self.assertTrue(expr[1].equals(3 + a2)) @@ -1497,6 +1672,7 @@ def test_fixed_boolean_var(self): self.assertIn(id(m.b2['b']), visitor.var_map) b2b = visitor.var_map[id(m.b2['b'])] + self.assertTrue(b2b.equals(visitor.pyomo_to_docplex[m.b2['b']] == 1)) self.assertTrue(expr[1].equals(cp.logical_or(False, cp.logical_and(True, b2b)))) @@ -1510,13 +1686,16 @@ def test_indirection_single_index(self): self.assertIn(id(m.x), visitor.var_map) x = visitor.var_map[id(m.x)] + self.assertIs(visitor.pyomo_to_docplex[m.x], x) a = [] # only need indices 6, 7, and 8 from a, since that's what x is capable # of selecting. for idx in [6, 7, 8]: v = m.a[idx] self.assertIn(id(v), visitor.var_map) - a.append(visitor.var_map[id(v)]) + cpx_v = visitor.var_map[id(v)] + self.assertIs(visitor.pyomo_to_docplex[v], cpx_v) + a.append(cpx_v) # since x is between 6 and 8, we subtract 6 from it for it to be the # right index self.assertTrue(expr[1].equals(cp.element(a, 0 + 1 * (x - 6) // 1))) @@ -1534,8 +1713,10 @@ def test_indirection_multi_index_second_constant(self): for i in [6, 7, 8]: self.assertIn(id(m.z[i, 3]), visitor.var_map) z[i, 3] = visitor.var_map[id(m.z[i, 3])] + self.assertIs(visitor.pyomo_to_docplex[m.z[i, 3]], z[i, 3]) self.assertIn(id(m.x), visitor.var_map) x = visitor.var_map[id(m.x)] + self.assertIs(visitor.pyomo_to_docplex[m.x], x) self.assertTrue( expr[1].equals( @@ -1556,8 +1737,11 @@ def test_indirection_multi_index_first_constant(self): for i in [6, 7, 8]: self.assertIn(id(m.z[3, i]), visitor.var_map) z[3, i] = visitor.var_map[id(m.z[3, i])] + self.assertIs(visitor.pyomo_to_docplex[m.z[3, i]], z[3, i]) + self.assertIn(id(m.x), visitor.var_map) x = visitor.var_map[id(m.x)] + self.assertIs(visitor.pyomo_to_docplex[m.x], x) self.assertTrue( expr[1].equals( @@ -1579,8 +1763,11 @@ def test_indirection_multi_index_neither_constant_same_var(self): for j in [6, 7, 8]: self.assertIn(id(m.z[i, j]), visitor.var_map) z[i, j] = visitor.var_map[id(m.z[i, j])] + self.assertIs(visitor.pyomo_to_docplex[m.z[i, j]], z[i, j]) + self.assertIn(id(m.x), visitor.var_map) x = visitor.var_map[id(m.x)] + self.assertIs(visitor.pyomo_to_docplex[m.x], x) self.assertTrue( expr[1].equals( @@ -1604,12 +1791,17 @@ def test_indirection_multi_index_neither_constant_diff_vars(self): z = {} for i in [6, 7, 8]: for j in [1, 3, 5]: - self.assertIn(id(m.z[i, 3]), visitor.var_map) + self.assertIn(id(m.z[i, j]), visitor.var_map) z[i, j] = visitor.var_map[id(m.z[i, j])] + self.assertIs(visitor.pyomo_to_docplex[m.z[i, j]], z[i, j]) + self.assertIn(id(m.x), visitor.var_map) x = visitor.var_map[id(m.x)] + self.assertIs(visitor.pyomo_to_docplex[m.x], x) + self.assertIn(id(m.y), visitor.var_map) y = visitor.var_map[id(m.y)] + self.assertIs(visitor.pyomo_to_docplex[m.y], y) self.assertTrue( expr[1].equals( @@ -1634,10 +1826,14 @@ def test_indirection_expression_index(self): for i in range(1, 8): self.assertIn(id(m.a[i]), visitor.var_map) a[i] = visitor.var_map[id(m.a[i])] + self.assertIs(visitor.pyomo_to_docplex[m.a[i]], a[i]) + self.assertIn(id(m.x), visitor.var_map) x = visitor.var_map[id(m.x)] + self.assertIs(visitor.pyomo_to_docplex[m.x], x) self.assertIn(id(m.y), visitor.var_map) y = visitor.var_map[id(m.y)] + self.assertIs(visitor.pyomo_to_docplex[m.y], y) self.assertTrue( expr[1].equals( From 1e103090bb7724d5d0b7486e7f59a20f44c172d2 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 30 Jan 2024 15:52:00 -0700 Subject: [PATCH 0886/1797] NFC: black --- pyomo/contrib/cp/interval_var.py | 3 +-- pyomo/contrib/cp/repn/docplex_writer.py | 8 ++---- .../cp/scheduling_expr/scheduling_logic.py | 10 +++---- pyomo/contrib/cp/tests/test_docplex_walker.py | 26 +++++++++++-------- .../cp/tests/test_sequence_expressions.py | 14 ++++++---- 5 files changed, 32 insertions(+), 29 deletions(-) diff --git a/pyomo/contrib/cp/interval_var.py b/pyomo/contrib/cp/interval_var.py index fb88ab14832..0e355d1847d 100644 --- a/pyomo/contrib/cp/interval_var.py +++ b/pyomo/contrib/cp/interval_var.py @@ -168,8 +168,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/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index 1f6bcc347e7..3440b522e60 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -996,15 +996,11 @@ def _handle_predecessor_to_expression_node( return _GENERAL, cp.previous(seq_var[1], before_var[1], after_var[1]) -def _handle_span_expression_node( - visitor, node, *args -): +def _handle_span_expression_node(visitor, node, *args): return _GENERAL, cp.span(args[0][1], [arg[1] for arg in args[1:]]) -def _handle_alternative_expression_node( - visitor, node, *args -): +def _handle_alternative_expression_node(visitor, node, *args): return _GENERAL, cp.alternative(args[0][1], [arg[1] for arg in args[1:]]) diff --git a/pyomo/contrib/cp/scheduling_expr/scheduling_logic.py b/pyomo/contrib/cp/scheduling_expr/scheduling_logic.py index a1f891a769f..fc9cefebf4d 100644 --- a/pyomo/contrib/cp/scheduling_expr/scheduling_logic.py +++ b/pyomo/contrib/cp/scheduling_expr/scheduling_logic.py @@ -22,6 +22,7 @@ class SpanExpression(NaryBooleanExpression): args: args (tuple): Child nodes, of type IntervalVar """ + def _to_string(self, values, verbose, smap): return "%s.spans(%s)" % (values[0], ", ".join(values[1:])) @@ -30,19 +31,18 @@ class AlternativeExpression(NaryBooleanExpression): """ TODO/ """ + def _to_string(self, values, verbose, smap): return "alternative(%s, [%s])" % (values[0], ", ".join(values[1:])) - + def spans(*args): - """Creates a new SpanExpression - """ + """Creates a new SpanExpression""" return SpanExpression(list(_flattened(args))) def alternative(*args): - """Creates a new AlternativeExpression - """ + """Creates a new AlternativeExpression""" return AlternativeExpression(list(_flattened(args))) diff --git a/pyomo/contrib/cp/tests/test_docplex_walker.py b/pyomo/contrib/cp/tests/test_docplex_walker.py index 73b4fd8e00c..9d027296654 100644 --- a/pyomo/contrib/cp/tests/test_docplex_walker.py +++ b/pyomo/contrib/cp/tests/test_docplex_walker.py @@ -19,7 +19,7 @@ last_in_sequence, before_in_sequence, predecessor_to, - alternative + alternative, ) from pyomo.contrib.cp.scheduling_expr.step_function_expressions import ( AlwaysIn, @@ -1491,12 +1491,16 @@ def param_rule(m, i): class TestCPExpressionWalker_HierarchicalScheduling(CommonTest): def get_model(self): m = ConcreteModel() + def start_rule(m, i): - return 2*i + return 2 * i + def length_rule(m, i): return i - m.iv = IntervalVar([1, 2, 3], start=start_rule, length=length_rule, - optional=True) + + m.iv = IntervalVar( + [1, 2, 3], start=start_rule, length=length_rule, optional=True + ) m.whole_enchilada = IntervalVar() return m @@ -1517,8 +1521,9 @@ def test_spans(self): self.assertIn(id(m.iv[i]), visitor.var_map) iv[i] = visitor.var_map[id(m.iv[i])] - self.assertTrue(expr[1].equals(cp.span(whole_enchilada, [iv[i] for i in - [1, 2, 3]]))) + self.assertTrue( + expr[1].equals(cp.span(whole_enchilada, [iv[i] for i in [1, 2, 3]])) + ) def test_alternative(self): m = self.get_model() @@ -1526,7 +1531,7 @@ def test_alternative(self): visitor = self.get_visitor() expr = visitor.walk_expression((e, e, 0)) - + self.assertIn(id(m.whole_enchilada), visitor.var_map) whole_enchilada = visitor.var_map[id(m.whole_enchilada)] self.assertIs(visitor.pyomo_to_docplex[m.whole_enchilada], whole_enchilada) @@ -1536,10 +1541,9 @@ def test_alternative(self): self.assertIn(id(m.iv[i]), visitor.var_map) iv[i] = visitor.var_map[id(m.iv[i])] - self.assertTrue(expr[1].equals(cp.alternative(whole_enchilada, [iv[i] - for i in - [1, 2, - 3]]))) + self.assertTrue( + expr[1].equals(cp.alternative(whole_enchilada, [iv[i] for i in [1, 2, 3]])) + ) @unittest.skipIf(not docplex_available, "docplex is not available") diff --git a/pyomo/contrib/cp/tests/test_sequence_expressions.py b/pyomo/contrib/cp/tests/test_sequence_expressions.py index 93a283c43d1..218a4c0e1a0 100644 --- a/pyomo/contrib/cp/tests/test_sequence_expressions.py +++ b/pyomo/contrib/cp/tests/test_sequence_expressions.py @@ -16,7 +16,7 @@ AlternativeExpression, SpanExpression, alternative, - spans + spans, ) from pyomo.contrib.cp.scheduling_expr.sequence_expressions import ( NoOverlapExpression, @@ -113,12 +113,16 @@ def test_predecessor_in_sequence(self): class TestHierarchicalSchedulingExpressions(unittest.TestCase): def make_model(self): m = ConcreteModel() + def start_rule(m, i): - return 2*i + return 2 * i + def length_rule(m, i): return i - m.iv = IntervalVar([1, 2, 3], start=start_rule, length=length_rule, - optional=True) + + m.iv = IntervalVar( + [1, 2, 3], start=start_rule, length=length_rule, optional=True + ) m.whole_enchilada = IntervalVar() return m @@ -137,7 +141,7 @@ def test_spans(self): m = self.make_model() e = spans(m.whole_enchilada, [m.iv[i] for i in [1, 2, 3]]) self.check_span_expression(m, e) - + def test_spans_method(self): m = self.make_model() e = m.whole_enchilada.spans(m.iv[i] for i in [1, 2, 3]) From 0f36c3f53f2256b4a632d781e108817f6adaf80d Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Tue, 30 Jan 2024 16:01:51 -0700 Subject: [PATCH 0887/1797] 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 0888/1797] 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 0889/1797] 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 bf0a54d5b8e97326caacad7363e35dd7a83ec4ff Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 31 Jan 2024 10:52:46 -0700 Subject: [PATCH 0890/1797] Adding function for debugging infeasible CPs without having to mess with the docplex model --- pyomo/contrib/cp/debugging.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 pyomo/contrib/cp/debugging.py diff --git a/pyomo/contrib/cp/debugging.py b/pyomo/contrib/cp/debugging.py new file mode 100644 index 00000000000..41c4d208de6 --- /dev/null +++ b/pyomo/contrib/cp/debugging.py @@ -0,0 +1,29 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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 WriterFactory + + +def write_conflict_set(m, filename): + """ + For debugging infeasible CPs: writes the conflict set found by CP optimizer + to a file with the specified filename. + + Args: + m: Pyomo CP model + filename: string filename + """ + + cpx_mod, var_map = WriterFactory('docplex_model').write( + m, symbolic_solver_labels=True + ) + conflict = cpx_mod.refine_conflict() + conflict.write(filename) From 0298371af4d6330be0635371ed2df486331d79a9 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 31 Jan 2024 11:11:19 -0700 Subject: [PATCH 0891/1797] 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 0892/1797] 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 0893/1797] 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 0894/1797] 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 0895/1797] 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 0896/1797] 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 0897/1797] 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 0898/1797] 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 0899/1797] 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 0900/1797] 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 0901/1797] 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 0902/1797] 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 0903/1797] 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 0904/1797] 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 0905/1797] 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 0906/1797] 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 0907/1797] 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 0908/1797] 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 0909/1797] 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 0910/1797] 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 0911/1797] 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 0912/1797] 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 0913/1797] 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 0914/1797] 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 0915/1797] 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 0916/1797] 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 0917/1797] 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 0918/1797] 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 0919/1797] 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 0920/1797] 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 0921/1797] 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 0922/1797] 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 0923/1797] 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 0924/1797] 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 0925/1797] 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 0926/1797] 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 0927/1797] 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 0928/1797] 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 0929/1797] 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 0930/1797] 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 0931/1797] 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 0932/1797] 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 0933/1797] 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 0934/1797] 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 0935/1797] 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 0936/1797] 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 0937/1797] 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 0938/1797] 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 0939/1797] 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 0940/1797] 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 0941/1797] 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 0942/1797] 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 0943/1797] 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 0944/1797] 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 0945/1797] 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 0946/1797] 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 0947/1797] 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 0948/1797] 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 0949/1797] 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 0950/1797] 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 0951/1797] 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 0952/1797] 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 0953/1797] 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 0954/1797] 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 0955/1797] 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 0956/1797] 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 0957/1797] 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 0958/1797] 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 0959/1797] 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 0960/1797] 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 0961/1797] 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 0962/1797] 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 0963/1797] 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 0964/1797] 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 0965/1797] 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 0966/1797] 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 0967/1797] 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 0968/1797] 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 0969/1797] 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 0970/1797] 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 0971/1797] 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 0972/1797] 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 0973/1797] 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 0974/1797] 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 0975/1797] 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 0976/1797] 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 0977/1797] 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 0978/1797] 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 0979/1797] 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 0980/1797] 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 0981/1797] 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 0982/1797] 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 90c98c85e254221b44b7b82d66f6ccb674958acb Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 12 Feb 2024 12:12:04 -0700 Subject: [PATCH 0983/1797] Reformatted parmest files using black. --- .../parameter_estimation_example.py | 4 +- pyomo/contrib/parmest/deprecated/parmest.py | 27 ++-- .../parmest/deprecated/tests/test_examples.py | 24 ++- .../parmest/deprecated/tests/test_parmest.py | 4 +- .../simple_reaction_parmest_example.py | 20 +-- .../reactor_design/bootstrap_example.py | 8 +- .../confidence_region_example.py | 8 +- .../reactor_design/datarec_example.py | 23 +-- .../reactor_design/leaveNout_example.py | 7 +- .../likelihood_ratio_example.py | 10 +- .../multisensor_data_example.py | 33 ++-- .../parameter_estimation_example.py | 10 +- .../examples/reactor_design/reactor_design.py | 54 ++++--- .../reactor_design/timeseries_data_example.py | 14 +- .../rooney_biegler/bootstrap_example.py | 10 +- .../likelihood_ratio_example.py | 10 +- .../parameter_estimation_example.py | 12 +- .../examples/rooney_biegler/rooney_biegler.py | 13 +- .../rooney_biegler_with_constraint.py | 13 +- .../semibatch/parameter_estimation_example.py | 9 +- .../examples/semibatch/scenario_example.py | 6 +- .../parmest/examples/semibatch/semibatch.py | 10 +- pyomo/contrib/parmest/experiment.py | 5 +- pyomo/contrib/parmest/parmest.py | 152 +++++++++--------- pyomo/contrib/parmest/scenariocreator.py | 20 +-- pyomo/contrib/parmest/tests/test_parmest.py | 115 +++++++------ .../parmest/tests/test_scenariocreator.py | 6 +- 27 files changed, 337 insertions(+), 290 deletions(-) diff --git a/pyomo/contrib/parmest/deprecated/examples/reactor_design/parameter_estimation_example.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/parameter_estimation_example.py index 67b69c73555..f5d9364097e 100644 --- a/pyomo/contrib/parmest/deprecated/examples/reactor_design/parameter_estimation_example.py +++ b/pyomo/contrib/parmest/deprecated/examples/reactor_design/parameter_estimation_example.py @@ -41,9 +41,9 @@ def SSE(model, data): # Parameter estimation obj, theta = pest.theta_est() - print (obj) + print(obj) print(theta) - + # Assert statements compare parameter estimation (theta) to an expected value k1_expected = 5.0 / 6.0 k2_expected = 5.0 / 3.0 diff --git a/pyomo/contrib/parmest/deprecated/parmest.py b/pyomo/contrib/parmest/deprecated/parmest.py index cbdc9179f35..82bf893dd06 100644 --- a/pyomo/contrib/parmest/deprecated/parmest.py +++ b/pyomo/contrib/parmest/deprecated/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/deprecated/tests/test_examples.py b/pyomo/contrib/parmest/deprecated/tests/test_examples.py index 04aff572529..6f5d9703f05 100644 --- a/pyomo/contrib/parmest/deprecated/tests/test_examples.py +++ b/pyomo/contrib/parmest/deprecated/tests/test_examples.py @@ -32,7 +32,9 @@ def tearDownClass(self): pass def test_model(self): - from pyomo.contrib.parmest.deprecated.examples.rooney_biegler import rooney_biegler + from pyomo.contrib.parmest.deprecated.examples.rooney_biegler import ( + rooney_biegler, + ) rooney_biegler.main() @@ -53,7 +55,9 @@ def test_parameter_estimation_example(self): @unittest.skipUnless(seaborn_available, "test requires seaborn") def test_bootstrap_example(self): - from pyomo.contrib.parmest.deprecated.examples.rooney_biegler import bootstrap_example + from pyomo.contrib.parmest.deprecated.examples.rooney_biegler import ( + bootstrap_example, + ) bootstrap_example.main() @@ -136,7 +140,9 @@ def tearDownClass(self): @unittest.pytest.mark.expensive def test_model(self): - from pyomo.contrib.parmest.deprecated.examples.reactor_design import reactor_design + from pyomo.contrib.parmest.deprecated.examples.reactor_design import ( + reactor_design, + ) reactor_design.main() @@ -149,7 +155,9 @@ def test_parameter_estimation_example(self): @unittest.skipUnless(seaborn_available, "test requires seaborn") def test_bootstrap_example(self): - from pyomo.contrib.parmest.deprecated.examples.reactor_design import bootstrap_example + from pyomo.contrib.parmest.deprecated.examples.reactor_design import ( + bootstrap_example, + ) bootstrap_example.main() @@ -163,7 +171,9 @@ def test_likelihood_ratio_example(self): @unittest.pytest.mark.expensive def test_leaveNout_example(self): - from pyomo.contrib.parmest.deprecated.examples.reactor_design import leaveNout_example + from pyomo.contrib.parmest.deprecated.examples.reactor_design import ( + leaveNout_example, + ) leaveNout_example.main() @@ -183,7 +193,9 @@ def test_multisensor_data_example(self): @unittest.skipUnless(matplotlib_available, "test requires matplotlib") def test_datarec_example(self): - from pyomo.contrib.parmest.deprecated.examples.reactor_design import datarec_example + from pyomo.contrib.parmest.deprecated.examples.reactor_design import ( + datarec_example, + ) datarec_example.main() diff --git a/pyomo/contrib/parmest/deprecated/tests/test_parmest.py b/pyomo/contrib/parmest/deprecated/tests/test_parmest.py index 40c98dac3af..27776bdc64c 100644 --- a/pyomo/contrib/parmest/deprecated/tests/test_parmest.py +++ b/pyomo/contrib/parmest/deprecated/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/parmest/examples/reaction_kinetics/simple_reaction_parmest_example.py b/pyomo/contrib/parmest/examples/reaction_kinetics/simple_reaction_parmest_example.py index 140fceeb8a2..4e9bc6079e7 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 @@ -98,32 +98,32 @@ def label_model(self): def get_labeled_model(self): self.create_model() m = self.label_model() - + return m + # k[2] fixed class SimpleReactionExperimentK2Fixed(SimpleReactionExperiment): def label_model(self): - + m = super().label_model() m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.unknown_parameters.update((k, pyo.ComponentUID(k)) - for k in [m.k[1]]) + m.unknown_parameters.update((k, pyo.ComponentUID(k)) for k in [m.k[1]]) return m + # k[2] variable class SimpleReactionExperimentK2Variable(SimpleReactionExperiment): def label_model(self): - + m = super().label_model() m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.unknown_parameters.update((k, pyo.ComponentUID(k)) - for k in [m.k[1], m.k[2]]) + m.unknown_parameters.update((k, pyo.ComponentUID(k)) for k in [m.k[1], m.k[2]]) return m @@ -150,7 +150,7 @@ def main(): ] # Create an experiment list with k[2] fixed - exp_list= [] + exp_list = [] for i in range(len(data)): exp_list.append(SimpleReactionExperimentK2Fixed(data[i])) @@ -162,7 +162,7 @@ def main(): # Parameter estimation without covariance estimate # Only estimate the parameter k[1]. The parameter k[2] will remain fixed # at its initial value - + pest = parmest.Estimator(exp_list) obj, theta = pest.theta_est() print(obj) @@ -170,7 +170,7 @@ def main(): print() # Create an experiment list with k[2] variable - exp_list= [] + exp_list = [] for i in range(len(data)): exp_list.append(SimpleReactionExperimentK2Variable(data[i])) diff --git a/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py b/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py index b5cb4196456..0935fbbba41 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py @@ -16,18 +16,19 @@ ReactorDesignExperiment, ) + def main(): # Read in data file_dirname = dirname(abspath(str(__file__))) file_name = abspath(join(file_dirname, "reactor_data.csv")) data = pd.read_csv(file_name) - + # Create an experiment list - exp_list= [] + exp_list = [] for i in range(data.shape[0]): exp_list.append(ReactorDesignExperiment(data, i)) - + # View one model # exp0_model = exp_list[0].get_labeled_model() # print(exp0_model.pprint()) @@ -50,5 +51,6 @@ def main(): title="Bootstrap theta with confidence regions", ) + if __name__ == "__main__": main() diff --git a/pyomo/contrib/parmest/examples/reactor_design/confidence_region_example.py b/pyomo/contrib/parmest/examples/reactor_design/confidence_region_example.py index ff84279018d..8aee6e9d67c 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/confidence_region_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/confidence_region_example.py @@ -16,18 +16,19 @@ ReactorDesignExperiment, ) + def main(): # Read in data file_dirname = dirname(abspath(str(__file__))) file_name = abspath(join(file_dirname, "reactor_data.csv")) data = pd.read_csv(file_name) - + # Create an experiment list - exp_list= [] + exp_list = [] for i in range(data.shape[0]): exp_list.append(ReactorDesignExperiment(data, i)) - + # View one model # exp0_model = exp_list[0].get_labeled_model() # print(exp0_model.pprint()) @@ -45,5 +46,6 @@ def main(): CR = pest.confidence_region_test(bootstrap_theta, "MVN", [0.5, 0.75, 1.0]) print(CR) + if __name__ == "__main__": main() diff --git a/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py b/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py index 26185290ea6..2945c284d4a 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py @@ -29,11 +29,12 @@ def reactor_design_model_for_datarec(): return model + class ReactorDesignExperimentPreDataRec(ReactorDesignExperiment): def __init__(self, data, data_std, experiment_number): - super().__init__(data, experiment_number) + super().__init__(data, experiment_number) self.data_std = data_std def create_model(self): @@ -43,7 +44,7 @@ def create_model(self): def label_model(self): m = self.model - + # experiment outputs m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) m.experiment_outputs.update([(m.ca, self.data_i['ca'])]) @@ -63,11 +64,12 @@ def label_model(self): return m + class ReactorDesignExperimentPostDataRec(ReactorDesignExperiment): def __init__(self, data, data_std, experiment_number): - super().__init__(data, experiment_number) + super().__init__(data, experiment_number) self.data_std = data_std def label_model(self): @@ -83,6 +85,7 @@ def label_model(self): return m + def generate_data(): ### Generate data based on real sv, caf, ca, cb, cc, and cd @@ -117,14 +120,16 @@ def main(): data_std = data.std() # Create an experiment list - exp_list= [] + exp_list = [] for i in range(data.shape[0]): exp_list.append(ReactorDesignExperimentPreDataRec(data, data_std, i)) # Define sum of squared error objective function for data rec def SSE(model): - expr = sum(((y - yhat)/model.experiment_outputs_std[y])**2 - for y, yhat in model.experiment_outputs.items()) + expr = sum( + ((y - yhat) / model.experiment_outputs_std[y]) ** 2 + for y, yhat in model.experiment_outputs.items() + ) return expr # View one model & SSE @@ -138,7 +143,7 @@ def SSE(model): 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"]], @@ -149,7 +154,7 @@ def SSE(model): data_rec["sv"] = data["sv"] # make a new list of experiments using reconciled data - exp_list= [] + exp_list = [] for i in range(data_rec.shape[0]): exp_list.append(ReactorDesignExperimentPostDataRec(data_rec, data_std, i)) @@ -157,7 +162,7 @@ def SSE(model): 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} 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 549233d8a84..97aad04c325 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.py @@ -24,7 +24,7 @@ def main(): file_dirname = dirname(abspath(str(__file__))) file_name = abspath(join(file_dirname, "reactor_data.csv")) data = pd.read_csv(file_name) - + # Create more data for the example N = 50 df_std = data.std().to_frame().transpose() @@ -33,10 +33,10 @@ def main(): data = df_sample + df_rand.dot(df_std) / 10 # Create an experiment list - exp_list= [] + exp_list = [] for i in range(data.shape[0]): exp_list.append(ReactorDesignExperiment(data, i)) - + # View one model # exp0_model = exp_list[0].get_labeled_model() # print(exp0_model.pprint()) @@ -89,5 +89,6 @@ def main(): percent_true = sum(r) / len(r) print(percent_true) + if __name__ == "__main__": main() 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 8b6d9fcfecc..7af37b64931 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.py @@ -20,17 +20,17 @@ def main(): - -# Read in data + + # Read in data file_dirname = dirname(abspath(str(__file__))) file_name = abspath(join(file_dirname, "reactor_data.csv")) data = pd.read_csv(file_name) - + # Create an experiment list - exp_list= [] + exp_list = [] for i in range(data.shape[0]): exp_list.append(ReactorDesignExperiment(data, i)) - + # View one model # exp0_model = exp_list[0].get_labeled_model() # print(exp0_model.pprint()) 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 f731032368e..a3802d40360 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py @@ -21,34 +21,37 @@ class MultisensorReactorDesignExperiment(ReactorDesignExperiment): def finalize_model(self): - + m = self.model - + # Experiment inputs values m.sv = self.data_i['sv'] m.caf = self.data_i['caf'] - + # Experiment output values - m.ca = (self.data_i['ca1'] + self.data_i['ca2'] + self.data_i['ca3']) * (1/3) + m.ca = (self.data_i['ca1'] + self.data_i['ca2'] + self.data_i['ca3']) * (1 / 3) m.cb = self.data_i['cb'] - m.cc = (self.data_i['cc1'] + self.data_i['cc2']) * (1/2) + m.cc = (self.data_i['cc1'] + self.data_i['cc2']) * (1 / 2) m.cd = self.data_i['cd'] - + return m def label_model(self): m = self.model - + m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs.update([(m.ca, [self.data_i['ca1'], self.data_i['ca2'], self.data_i['ca3']])]) + m.experiment_outputs.update( + [(m.ca, [self.data_i['ca1'], self.data_i['ca2'], self.data_i['ca3']])] + ) m.experiment_outputs.update([(m.cb, [self.data_i['cb']])]) m.experiment_outputs.update([(m.cc, [self.data_i['cc1'], self.data_i['cc2']])]) m.experiment_outputs.update([(m.cd, [self.data_i['cd']])]) - + m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.unknown_parameters.update((k, pyo.ComponentUID(k)) - for k in [m.k1, m.k2, m.k3]) + m.unknown_parameters.update( + (k, pyo.ComponentUID(k)) for k in [m.k1, m.k2, m.k3] + ) return m @@ -60,9 +63,9 @@ def main(): file_dirname = dirname(abspath(str(__file__))) file_name = abspath(join(file_dirname, "reactor_data_multisensor.csv")) data = pd.read_csv(file_name) - + # Create an experiment list - exp_list= [] + exp_list = [] for i in range(data.shape[0]): exp_list.append(MultisensorReactorDesignExperiment(data, i)) @@ -72,9 +75,9 @@ def SSE_multisensor(model): for y, yhat in model.experiment_outputs.items(): num_outputs = len(yhat) for i in range(num_outputs): - expr += ((y - yhat[i])**2) * (1 / num_outputs) + expr += ((y - yhat[i]) ** 2) * (1 / num_outputs) return expr - + # View one model # exp0_model = exp_list[0].get_labeled_model() # print(exp0_model.pprint()) 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 76744984cce..4da7dd13023 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.py @@ -23,19 +23,19 @@ def main(): file_dirname = dirname(abspath(str(__file__))) file_name = abspath(join(file_dirname, "reactor_data.csv")) data = pd.read_csv(file_name) - + # Create an experiment list - exp_list= [] + exp_list = [] for i in range(data.shape[0]): exp_list.append(ReactorDesignExperiment(data, i)) - + # View one model # exp0_model = exp_list[0].get_labeled_model() # print(exp0_model.pprint()) pest = parmest.Estimator(exp_list, obj_function='SSE') - + # Parameter estimation with covariance obj, theta, cov = pest.theta_est(calc_cov=True, cov_n=17) print(obj) - print(theta) \ No newline at end of file + print(theta) diff --git a/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py b/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py index db3b0e1d380..e524a6dd90e 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py +++ b/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py @@ -21,19 +21,26 @@ from pyomo.contrib.parmest.experiment import Experiment + def reactor_design_model(): # Create the concrete model model = pyo.ConcreteModel() # Rate constants - model.k1 = pyo.Param(initialize=5.0 / 6.0, within=pyo.PositiveReals, mutable=True) # min^-1 - model.k2 = pyo.Param(initialize=5.0 / 3.0, within=pyo.PositiveReals, mutable=True) # min^-1 - model.k3 = pyo.Param(initialize=1.0 / 6000.0, within=pyo.PositiveReals, mutable=True) # m^3/(gmol min) + model.k1 = pyo.Param( + initialize=5.0 / 6.0, within=pyo.PositiveReals, mutable=True + ) # min^-1 + model.k2 = pyo.Param( + initialize=5.0 / 3.0, within=pyo.PositiveReals, mutable=True + ) # min^-1 + model.k3 = pyo.Param( + initialize=1.0 / 6000.0, within=pyo.PositiveReals, mutable=True + ) # m^3/(gmol min) # Inlet concentration of A, gmol/m^3 model.caf = pyo.Param(initialize=10000, within=pyo.PositiveReals, mutable=True) - + # Space velocity (flowrate/volume) model.sv = pyo.Param(initialize=1.0, within=pyo.PositiveReals, mutable=True) @@ -61,63 +68,68 @@ def reactor_design_model(): expr=(0 == -model.sv * model.cb + model.k1 * model.ca - model.k2 * model.cb) ) - model.cc_bal = pyo.Constraint(expr=(0 == -model.sv * model.cc + model.k2 * model.cb)) + model.cc_bal = pyo.Constraint( + expr=(0 == -model.sv * model.cc + model.k2 * model.cb) + ) model.cd_bal = pyo.Constraint( expr=(0 == -model.sv * model.cd + model.k3 * model.ca**2.0) ) return model - + + class ReactorDesignExperiment(Experiment): - - def __init__(self, data, experiment_number): + + def __init__(self, data, experiment_number): self.data = data self.experiment_number = experiment_number - self.data_i = data.loc[experiment_number,:] + self.data_i = data.loc[experiment_number, :] self.model = None - + def create_model(self): self.model = m = reactor_design_model() return m - + def finalize_model(self): m = self.model - + # Experiment inputs values m.sv = self.data_i['sv'] m.caf = self.data_i['caf'] - + # Experiment output values m.ca = self.data_i['ca'] m.cb = self.data_i['cb'] m.cc = self.data_i['cc'] m.cd = self.data_i['cd'] - + return m def label_model(self): m = self.model - + m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) m.experiment_outputs.update([(m.ca, self.data_i['ca'])]) m.experiment_outputs.update([(m.cb, self.data_i['cb'])]) m.experiment_outputs.update([(m.cc, self.data_i['cc'])]) m.experiment_outputs.update([(m.cd, self.data_i['cd'])]) - + m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.unknown_parameters.update((k, pyo.ComponentUID(k)) - for k in [m.k1, m.k2, m.k3]) + m.unknown_parameters.update( + (k, pyo.ComponentUID(k)) for k in [m.k1, m.k2, m.k3] + ) return m - + def get_labeled_model(self): m = self.create_model() m = self.finalize_model() m = self.label_model() - + return m + def main(): # For a range of sv values, return ca, cb, cc, and cd @@ -143,6 +155,6 @@ def main(): results = pd.DataFrame(results, columns=["sv", "caf", "ca", "cb", "cc", "cd"]) print(results) + if __name__ == "__main__": main() - \ No newline at end of file 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 a9a5ab20b54..59d6a26ca23 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/timeseries_data_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/timeseries_data_example.py @@ -20,25 +20,25 @@ class TimeSeriesReactorDesignExperiment(ReactorDesignExperiment): - def __init__(self, data, experiment_number): + def __init__(self, data, experiment_number): self.data = data self.experiment_number = experiment_number self.data_i = data[experiment_number] self.model = None - + def finalize_model(self): m = self.model - + # Experiment inputs values m.sv = self.data_i['sv'] m.caf = self.data_i['caf'] - + # Experiment output values m.ca = self.data_i['ca'][0] m.cb = self.data_i['cb'][0] m.cc = self.data_i['cc'][0] m.cd = self.data_i['cd'][0] - + return m @@ -92,7 +92,7 @@ def main(): data_ts = group_data(data, 'experiment', ['sv', 'caf']) # Create an experiment list - exp_list= [] + exp_list = [] for i in range(len(data_ts)): exp_list.append(TimeSeriesReactorDesignExperiment(data_ts, i)) @@ -102,7 +102,7 @@ def SSE_timeseries(model): for y, yhat in model.experiment_outputs.items(): num_time_points = len(yhat) for i in range(num_time_points): - expr += ((y - yhat[i])**2) * (1 / num_time_points) + expr += ((y - yhat[i]) ** 2) * (1 / num_time_points) return expr diff --git a/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.py b/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.py index 1f15ab95779..b79bc8e4c5a 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.py @@ -26,14 +26,16 @@ def main(): # Sum of squared error function def SSE(model): - expr = (model.experiment_outputs[model.y] - \ - model.response_function[model.experiment_outputs[model.hour]]) ** 2 + expr = ( + model.experiment_outputs[model.y] + - model.response_function[model.experiment_outputs[model.hour]] + ) ** 2 return expr # Create an experiment list - exp_list= [] + exp_list = [] for i in range(data.shape[0]): - exp_list.append(RooneyBieglerExperiment(data.loc[i,:].to_frame().transpose())) + exp_list.append(RooneyBieglerExperiment(data.loc[i, :].to_frame().transpose())) # View one model # exp0_model = exp_list[0].get_labeled_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 869bb39efb9..a8daac79e23 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/likelihood_ratio_example.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/likelihood_ratio_example.py @@ -28,14 +28,16 @@ def main(): # Sum of squared error function def SSE(model): - expr = (model.experiment_outputs[model.y] - \ - model.response_function[model.experiment_outputs[model.hour]]) ** 2 + expr = ( + model.experiment_outputs[model.y] + - model.response_function[model.experiment_outputs[model.hour]] + ) ** 2 return expr # Create an experiment list - exp_list= [] + exp_list = [] for i in range(data.shape[0]): - exp_list.append(RooneyBieglerExperiment(data.loc[i,:].to_frame().transpose())) + exp_list.append(RooneyBieglerExperiment(data.loc[i, :].to_frame().transpose())) # View one model # exp0_model = exp_list[0].get_labeled_model() 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 b6ca7af0ab6..1f73f1cfdb0 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/parameter_estimation_example.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/parameter_estimation_example.py @@ -26,14 +26,16 @@ def main(): # Sum of squared error function def SSE(model): - expr = (model.experiment_outputs[model.y] - \ - model.response_function[model.experiment_outputs[model.hour]]) ** 2 + expr = ( + model.experiment_outputs[model.y] + - model.response_function[model.experiment_outputs[model.hour]] + ) ** 2 return expr # Create an experiment list - exp_list= [] + exp_list = [] for i in range(data.shape[0]): - exp_list.append(RooneyBieglerExperiment(data.loc[i,:].to_frame().transpose())) + exp_list.append(RooneyBieglerExperiment(data.loc[i, :].to_frame().transpose())) # View one model # exp0_model = exp_list[0].get_labeled_model() @@ -41,7 +43,7 @@ def SSE(model): # Create an instance of the parmest estimator pest = parmest.Estimator(exp_list, obj_function=SSE) - + # Parameter estimation and covariance n = 6 # total number of data points used in the objective (y in 6 scenarios) obj, theta, cov = pest.theta_est(calc_cov=True, cov_n=n) diff --git a/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py b/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py index 6e7d6219a64..2b75c8621a7 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py @@ -28,11 +28,11 @@ def rooney_biegler_model(data): model.hour = pyo.Param(within=pyo.PositiveReals, mutable=True) model.y = pyo.Param(within=pyo.PositiveReals, mutable=True) - + def response_rule(m, h): expr = m.asymptote * (1 - pyo.exp(-m.rate_constant * h)) return expr - + model.response_function = pyo.Expression(data.hour, rule=response_rule) def SSE_rule(m): @@ -63,13 +63,14 @@ def label_model(self): m.experiment_outputs.update([(m.y, self.data.iloc[0]['y'])]) m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.unknown_parameters.update((k, pyo.ComponentUID(k)) - for k in [m.asymptote, m.rate_constant]) + m.unknown_parameters.update( + (k, pyo.ComponentUID(k)) for k in [m.asymptote, m.rate_constant] + ) def finalize_model(self): m = self.model - + # Experiment output values m.hour = self.data.iloc[0]['hour'] m.y = self.data.iloc[0]['y'] @@ -78,7 +79,7 @@ def get_labeled_model(self): self.create_model() self.label_model() self.finalize_model() - + return self.model 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 1e213684a01..11100a8a40f 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 @@ -28,7 +28,7 @@ def rooney_biegler_model_with_constraint(data): model.hour = pyo.Param(within=pyo.PositiveReals, mutable=True) model.y = pyo.Param(within=pyo.PositiveReals, mutable=True) - + model.response_function = pyo.Var(data.hour, initialize=0.0) # changed from expression to constraint @@ -48,6 +48,7 @@ def SSE_rule(m): return model + class RooneyBieglerExperiment(Experiment): def __init__(self, data): @@ -65,15 +66,15 @@ def label_model(self): m.experiment_outputs.update([(m.hour, self.data.iloc[0]['hour'])]) m.experiment_outputs.update([(m.y, self.data.iloc[0]['y'])]) - m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.unknown_parameters.update((k, pyo.ComponentUID(k)) - for k in [m.asymptote, m.rate_constant]) + m.unknown_parameters.update( + (k, pyo.ComponentUID(k)) for k in [m.asymptote, m.rate_constant] + ) def finalize_model(self): m = self.model - + # Experiment output values m.hour = self.data.iloc[0]['hour'] m.y = self.data.iloc[0]['y'] @@ -82,7 +83,7 @@ def get_labeled_model(self): self.create_model() self.label_model() self.finalize_model() - + return self.model diff --git a/pyomo/contrib/parmest/examples/semibatch/parameter_estimation_example.py b/pyomo/contrib/parmest/examples/semibatch/parameter_estimation_example.py index 145569f7535..d74f094cd4f 100644 --- a/pyomo/contrib/parmest/examples/semibatch/parameter_estimation_example.py +++ b/pyomo/contrib/parmest/examples/semibatch/parameter_estimation_example.py @@ -12,9 +12,8 @@ import json from os.path import join, abspath, dirname import pyomo.contrib.parmest.parmest as parmest -from pyomo.contrib.parmest.examples.semibatch.semibatch import ( - SemiBatchExperiment, -) +from pyomo.contrib.parmest.examples.semibatch.semibatch import SemiBatchExperiment + def main(): @@ -28,7 +27,7 @@ def main(): data.append(d) # Create an experiment list - exp_list= [] + exp_list = [] for i in range(len(data)): exp_list.append(SemiBatchExperiment(data[i])) @@ -40,7 +39,7 @@ def main(): # for sum of squared error that will be used in parameter estimation pest = parmest.Estimator(exp_list) - + obj, theta = pest.theta_est() print(obj) print(theta) diff --git a/pyomo/contrib/parmest/examples/semibatch/scenario_example.py b/pyomo/contrib/parmest/examples/semibatch/scenario_example.py index a80a82671bc..1270ef49839 100644 --- a/pyomo/contrib/parmest/examples/semibatch/scenario_example.py +++ b/pyomo/contrib/parmest/examples/semibatch/scenario_example.py @@ -12,9 +12,7 @@ import json from os.path import join, abspath, dirname import pyomo.contrib.parmest.parmest as parmest -from pyomo.contrib.parmest.examples.semibatch.semibatch import ( - SemiBatchExperiment, -) +from pyomo.contrib.parmest.examples.semibatch.semibatch import SemiBatchExperiment import pyomo.contrib.parmest.scenariocreator as sc @@ -30,7 +28,7 @@ def main(): data.append(d) # Create an experiment list - exp_list= [] + exp_list = [] for i in range(len(data)): exp_list.append(SemiBatchExperiment(data[i])) diff --git a/pyomo/contrib/parmest/examples/semibatch/semibatch.py b/pyomo/contrib/parmest/examples/semibatch/semibatch.py index 3ef7bc01aa9..b882df7a015 100644 --- a/pyomo/contrib/parmest/examples/semibatch/semibatch.py +++ b/pyomo/contrib/parmest/examples/semibatch/semibatch.py @@ -283,11 +283,11 @@ def create_model(self): def label_model(self): m = self.model - - m.unknown_parameters = Suffix(direction=Suffix.LOCAL) - m.unknown_parameters.update((k, ComponentUID(k)) - for k in [m.k1, m.k2, m.E1, m.E2]) + m.unknown_parameters = Suffix(direction=Suffix.LOCAL) + m.unknown_parameters.update( + (k, ComponentUID(k)) for k in [m.k1, m.k2, m.E1, m.E2] + ) def finalize_model(self): pass @@ -296,7 +296,7 @@ def get_labeled_model(self): self.create_model() self.label_model() self.finalize_model() - + return self.model diff --git a/pyomo/contrib/parmest/experiment.py b/pyomo/contrib/parmest/experiment.py index 73b18bb5975..e16ad304e42 100644 --- a/pyomo/contrib/parmest/experiment.py +++ b/pyomo/contrib/parmest/experiment.py @@ -1,15 +1,16 @@ # The experiment class is a template for making experiment lists # to pass to parmest. An experiment is a pyomo model "m" which has # additional suffixes: -# m.experiment_outputs -- which variables are experiment outputs +# m.experiment_outputs -- which variables are experiment outputs # m.unknown_parameters -- which variables are parameters to estimate # The experiment class has only one required method: # get_labeled_model() # which returns the labeled pyomo model. + class Experiment: def __init__(self, model=None): self.model = model def get_labeled_model(self): - return self.model \ No newline at end of file + return self.model diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index e256b0f38d7..7e9e6cf90d4 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -309,10 +309,12 @@ def _experiment_instance_creation_callback( # return grouped_data + def SSE(model): - expr = sum((y - yhat)**2 for y, yhat in model.experiment_outputs.items()) + expr = sum((y - yhat) ** 2 for y, yhat in model.experiment_outputs.items()) return expr + class _SecondStageCostExpr(object): """ Class to pass objective expression into the Pyomo model @@ -332,10 +334,10 @@ class Estimator(object): Parameters ---------- experiement_list: list of Experiments - A list of experiment objects which creates one labeled model for + A list of experiment objects which creates one labeled model for each expeirment obj_function: string or function (optional) - Built in objective (currently only "SSE") or custom function used to + Built in objective (currently only "SSE") or custom function used to formulate parameter estimation objective. If no function is specified, the model is used "as is" and should be defined with a "FirstStageCost" and @@ -351,50 +353,54 @@ class Estimator(object): # backwards compatible constructor will accept the old inputs # from parmest_deprecated as well as the new inputs using experiment lists def __init__(self, *args, **kwargs): - + # check that we have at least one argument - assert(len(args) > 0) + assert len(args) > 0 # use deprecated interface self.pest_deprecated = None if callable(args[0]): - logger.warning('Using deprecated parmest inputs (model_function, ' + - 'data, theta_names), please use experiment lists instead.') + logger.warning( + 'Using deprecated parmest inputs (model_function, ' + + 'data, theta_names), please use experiment lists instead.' + ) self.pest_deprecated = parmest_deprecated.Estimator(*args, **kwargs) return # check that we have a (non-empty) list of experiments - assert (isinstance(args[0], list)) - assert (len(args[0]) > 0) + assert isinstance(args[0], list) + assert len(args[0]) > 0 self.exp_list = args[0] # check that an experiment has experiment_outputs and unknown_parameters model = self.exp_list[0].get_labeled_model() try: - outputs = [k.name for k,v in model.experiment_outputs.items()] + outputs = [k.name for k, v in model.experiment_outputs.items()] except: - RuntimeError('Experiment list model does not have suffix ' + - '"experiment_outputs".') + RuntimeError( + 'Experiment list model does not have suffix ' + '"experiment_outputs".' + ) try: - parms = [k.name for k,v in model.unknown_parameters.items()] + parms = [k.name for k, v in model.unknown_parameters.items()] except: - RuntimeError('Experiment list model does not have suffix ' + - '"unknown_parameters".') - + RuntimeError( + 'Experiment list model does not have suffix ' + '"unknown_parameters".' + ) + # populate keyword argument options self.obj_function = kwargs.get('obj_function', None) self.tee = kwargs.get('tee', False) self.diagnostic_mode = kwargs.get('diagnostic_mode', False) self.solver_options = kwargs.get('solver_options', None) - # TODO This might not be needed here. + # TODO This might not be needed here. # We could collect the union (or intersect?) of thetas when the models are built theta_names = [] for experiment in self.exp_list: model = experiment.get_labeled_model() - theta_names.extend([k.name for k,v in model.unknown_parameters.items()]) + theta_names.extend([k.name for k, v in model.unknown_parameters.items()]) self.estimator_theta_names = list(set(theta_names)) - + self._second_stage_cost_exp = "SecondStageCost" # boolean to indicate if model is initialized using a square solve self.model_initialized = False @@ -406,7 +412,7 @@ def _return_theta_names(self): # check for deprecated inputs if self.pest_deprecated is not None: - # if fitted model parameter names differ from theta_names + # if fitted model parameter names differ from theta_names # created when Estimator object is created if hasattr(self, 'theta_names_updated'): return self.pest_deprecated.theta_names_updated @@ -418,7 +424,7 @@ def _return_theta_names(self): else: - # if fitted model parameter names differ from theta_names + # if fitted model parameter names differ from theta_names # created when Estimator object is created if hasattr(self, 'theta_names_updated'): return self.theta_names_updated @@ -434,7 +440,7 @@ def _create_parmest_model(self, experiment_number): """ model = self.exp_list[experiment_number].get_labeled_model() - self.theta_names = [k.name for k,v in model.unknown_parameters.items()] + self.theta_names = [k.name for k, v in model.unknown_parameters.items()] if len(model.unknown_parameters) == 0: model.parmest_dummy_var = pyo.Var(initialize=1.0) @@ -458,7 +464,7 @@ def _create_parmest_model(self, experiment_number): # TODO, this needs to be turned a enum class of options that still support custom functions if self.obj_function == 'SSE': - second_stage_rule=_SecondStageCostExpr(SSE) + second_stage_rule = _SecondStageCostExpr(SSE) else: # A custom function uses model.experiment_outputs as data second_stage_rule = _SecondStageCostExpr(self.obj_function) @@ -466,7 +472,6 @@ def _create_parmest_model(self, experiment_number): model.FirstStageCost = pyo.Expression(expr=0) model.SecondStageCost = pyo.Expression(rule=second_stage_rule) - def TotalCost_rule(model): return model.FirstStageCost + model.SecondStageCost @@ -534,7 +539,7 @@ def _Q_opt( outer_cb_data["ThetaVals"] = ThetaVals if bootlist is not None: outer_cb_data["BootList"] = bootlist - outer_cb_data["cb_data"] = None # None is OK + outer_cb_data["cb_data"] = None # None is OK outer_cb_data["theta_names"] = self.estimator_theta_names options = {"solver": "ipopt"} @@ -581,14 +586,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: @@ -710,13 +714,13 @@ def _Q_at_theta(self, thetavals, initialize_parmest_model=False): "callback": self._instance_creation_callback, "ThetaVals": thetavals, "theta_names": self._return_theta_names(), - "cb_data": None, + "cb_data": None, } else: dummy_cb = { "callback": self._instance_creation_callback, "theta_names": self._return_theta_names(), - "cb_data": None, + "cb_data": None, } if self.diagnostic_mode: @@ -779,14 +783,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 = ", @@ -944,21 +944,28 @@ def theta_est( # check if we are using deprecated parmest if self.pest_deprecated is not None: return self.pest_deprecated.theta_est( - solver=solver, + solver=solver, return_values=return_values, calc_cov=calc_cov, - cov_n=cov_n) - + cov_n=cov_n, + ) + assert isinstance(solver, str) assert isinstance(return_values, list) assert isinstance(calc_cov, bool) if calc_cov: - num_unknowns = max([len(experiment.get_labeled_model().unknown_parameters) - for experiment in self.exp_list]) - assert isinstance(cov_n, int), \ - "The number of datapoints that are used in the objective function is required to calculate the covariance matrix" - assert cov_n > num_unknowns, \ - "The number of datapoints must be greater than the number of parameters to estimate" + num_unknowns = max( + [ + len(experiment.get_labeled_model().unknown_parameters) + for experiment in self.exp_list + ] + ) + assert isinstance( + cov_n, int + ), "The number of datapoints that are used in the objective function is required to calculate the covariance matrix" + assert ( + cov_n > num_unknowns + ), "The number of datapoints must be greater than the number of parameters to estimate" return self._Q_opt( solver=solver, @@ -1007,7 +1014,8 @@ def theta_est_bootstrap( samplesize=samplesize, replacement=replacement, seed=seed, - return_samples=return_samples) + return_samples=return_samples, + ) assert isinstance(bootstrap_samples, int) assert isinstance(samplesize, (type(None), int)) @@ -1068,10 +1076,8 @@ def theta_est_leaveNout( # check if we are using deprecated parmest if self.pest_deprecated is not None: return self.pest_deprecated.theta_est_leaveNout( - lNo, - lNo_samples=lNo_samples, - seed=seed, - return_samples=return_samples) + lNo, lNo_samples=lNo_samples, seed=seed, return_samples=return_samples + ) assert isinstance(lNo, int) assert isinstance(lNo_samples, (type(None), int)) @@ -1150,11 +1156,8 @@ def leaveNout_bootstrap_test( # check if we are using deprecated parmest if self.pest_deprecated is not None: return self.pest_deprecated.leaveNout_bootstrap_test( - lNo, - lNo_samples, - bootstrap_samples, - distribution, alphas, - seed=seed) + lNo, lNo_samples, bootstrap_samples, distribution, alphas, seed=seed + ) assert isinstance(lNo, int) assert isinstance(lNo_samples, (type(None), int)) @@ -1189,8 +1192,8 @@ def leaveNout_bootstrap_test( # expand indexed variables to get full list of thetas def _expand_indexed_unknowns(self, model_temp): - model_theta_list = [k.name for k,v in model_temp.unknown_parameters.items()] - + model_theta_list = [k.name for k, v in model_temp.unknown_parameters.items()] + # check for indexed theta items indexed_theta_list = [] for theta_i in model_theta_list: @@ -1198,7 +1201,7 @@ def _expand_indexed_unknowns(self, model_temp): var_validate = var_cuid.find_component_on(model_temp) for ind in var_validate.index_set(): if ind is not None: - indexed_theta_list.append(theta_i + '[' + str(ind) + ']') + indexed_theta_list.append(theta_i + '[' + str(ind) + ']') else: indexed_theta_list.append(theta_i) @@ -1233,15 +1236,16 @@ def objective_at_theta(self, theta_values=None, initialize_parmest_model=False): if self.pest_deprecated is not None: return self.pest_deprecated.objective_at_theta( theta_values=theta_values, - initialize_parmest_model=initialize_parmest_model) + initialize_parmest_model=initialize_parmest_model, + ) - if len(self.estimator_theta_names) == 0: + if len(self.estimator_theta_names) == 0: pass # skip assertion if model has no fitted parameters else: # create a local instance of the pyomo model to access model variables and parameters model_temp = self._create_parmest_model(0) model_theta_list = self._expand_indexed_unknowns(model_temp) - + # # iterate over original theta_names # for theta_i in self.theta_names: # var_cuid = ComponentUID(theta_i) @@ -1354,10 +1358,8 @@ def likelihood_ratio_test( # check if we are using deprecated parmest if self.pest_deprecated is not None: return self.pest_deprecated.likelihood_ratio_test( - obj_at_theta, - obj_value, - alphas, - return_thresholds=return_thresholds) + obj_at_theta, obj_value, alphas, return_thresholds=return_thresholds + ) assert isinstance(obj_at_theta, pd.DataFrame) assert isinstance(obj_value, (int, float)) @@ -1415,10 +1417,8 @@ def confidence_region_test( # check if we are using deprecated parmest if self.pest_deprecated is not None: return self.pest_deprecated.confidence_region_test( - theta_values, - distribution, - alphas, - test_theta_values=test_theta_values) + theta_values, distribution, alphas, test_theta_values=test_theta_values + ) assert isinstance(theta_values, pd.DataFrame) assert distribution in ['Rect', 'MVN', 'KDE'] diff --git a/pyomo/contrib/parmest/scenariocreator.py b/pyomo/contrib/parmest/scenariocreator.py index c48ac2bf027..82d13ce2007 100644 --- a/pyomo/contrib/parmest/scenariocreator.py +++ b/pyomo/contrib/parmest/scenariocreator.py @@ -17,8 +17,10 @@ import pyomo.contrib.parmest.deprecated.scenariocreator as scen_deprecated import logging + logger = logging.getLogger(__name__) + class ScenarioSet(object): """ Class to hold scenario sets @@ -127,10 +129,13 @@ def __init__(self, pest, solvername): # is this a deprecated pest object? self.scen_deprecated = None if pest.pest_deprecated is not None: - logger.warning("Using a deprecated parmest object for scenario " + - "creator, please recreate object using experiment lists.") + logger.warning( + "Using a deprecated parmest object for scenario " + + "creator, please recreate object using experiment lists." + ) self.scen_deprecated = scen_deprecated.ScenarioCreator( - pest.pest_deprecated, solvername) + pest.pest_deprecated, solvername + ) else: self.pest = pest self.solvername = solvername @@ -148,7 +153,7 @@ def ScenariosFromExperiments(self, addtoSet): if self.scen_deprecated is not None: self.scen_deprecated.ScenariosFromExperiments(addtoSet) return - + assert isinstance(addtoSet, ScenarioSet) scenario_numbers = list(range(len(self.pest.exp_list))) @@ -156,9 +161,7 @@ def ScenariosFromExperiments(self, addtoSet): prob = 1.0 / len(scenario_numbers) for exp_num in scenario_numbers: ##print("Experiment number=", exp_num) - model = self.pest._instance_creation_callback( - exp_num, - ) + model = self.pest._instance_creation_callback(exp_num) opt = pyo.SolverFactory(self.solvername) results = opt.solve(model) # solves and updates model ## pyo.check_termination_optimal(results) @@ -180,8 +183,7 @@ def ScenariosFromBootstrap(self, addtoSet, numtomake, seed=None): # check if using deprecated pest object if self.scen_deprecated is not None: - self.scen_deprecated.ScenariosFromBootstrap( - addtoSet, numtomake, seed=seed) + self.scen_deprecated.ScenariosFromBootstrap(addtoSet, numtomake, seed=seed) return assert isinstance(addtoSet, ScenarioSet) diff --git a/pyomo/contrib/parmest/tests/test_parmest.py b/pyomo/contrib/parmest/tests/test_parmest.py index b88871e0dbc..ff8d1663bc9 100644 --- a/pyomo/contrib/parmest/tests/test_parmest.py +++ b/pyomo/contrib/parmest/tests/test_parmest.py @@ -47,6 +47,7 @@ testdir = os.path.dirname(os.path.abspath(__file__)) + @unittest.skipIf( not parmest.parmest_available, "Cannot test parmest: required dependencies are missing", @@ -66,14 +67,18 @@ def setUp(self): # Sum of squared error function def SSE(model): - expr = (model.experiment_outputs[model.y] - \ - model.response_function[model.experiment_outputs[model.hour]]) ** 2 + expr = ( + model.experiment_outputs[model.y] + - model.response_function[model.experiment_outputs[model.hour]] + ) ** 2 return expr # Create an experiment list - exp_list= [] + exp_list = [] for i in range(data.shape[0]): - exp_list.append(RooneyBieglerExperiment(data.loc[i,:].to_frame().transpose())) + exp_list.append( + RooneyBieglerExperiment(data.loc[i, :].to_frame().transpose()) + ) # Create an instance of the parmest estimator pest = parmest.Estimator(exp_list, obj_function=SSE) @@ -82,10 +87,7 @@ def SSE(model): self.data = data self.pest = parmest.Estimator( - exp_list, - obj_function=SSE, - solver_options=solver_options, - tee=True, + exp_list, obj_function=SSE, solver_options=solver_options, tee=True ) def test_theta_est(self): @@ -372,7 +374,7 @@ def rooney_biegler_params(data): model.hour = pyo.Param(within=pyo.PositiveReals, mutable=True) model.y = pyo.Param(within=pyo.PositiveReals, mutable=True) - + def response_rule(m, h): expr = m.asymptote * (1 - pyo.exp(-m.rate_constant * h)) return expr @@ -389,7 +391,9 @@ def create_model(self): rooney_biegler_params_exp_list = [] for i in range(self.data.shape[0]): rooney_biegler_params_exp_list.append( - RooneyBieglerExperimentParams(self.data.loc[i,:].to_frame().transpose()) + RooneyBieglerExperimentParams( + self.data.loc[i, :].to_frame().transpose() + ) ) def rooney_biegler_indexed_params(data): @@ -429,13 +433,14 @@ def label_model(self): m.experiment_outputs.update([(m.y, self.data.iloc[0]['y'])]) m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.unknown_parameters.update((k, pyo.ComponentUID(k)) - for k in [m.theta]) + m.unknown_parameters.update((k, pyo.ComponentUID(k)) for k in [m.theta]) rooney_biegler_indexed_params_exp_list = [] for i in range(self.data.shape[0]): rooney_biegler_indexed_params_exp_list.append( - RooneyBieglerExperimentIndexedParams(self.data.loc[i,:].to_frame().transpose()) + RooneyBieglerExperimentIndexedParams( + self.data.loc[i, :].to_frame().transpose() + ) ) def rooney_biegler_vars(data): @@ -465,7 +470,7 @@ def create_model(self): rooney_biegler_vars_exp_list = [] for i in range(self.data.shape[0]): rooney_biegler_vars_exp_list.append( - RooneyBieglerExperimentVars(self.data.loc[i,:].to_frame().transpose()) + RooneyBieglerExperimentVars(self.data.loc[i, :].to_frame().transpose()) ) def rooney_biegler_indexed_vars(data): @@ -475,9 +480,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 @@ -509,20 +512,22 @@ def label_model(self): m.experiment_outputs.update([(m.y, self.data.iloc[0]['y'])]) m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.unknown_parameters.update((k, pyo.ComponentUID(k)) - for k in [m.theta]) - + m.unknown_parameters.update((k, pyo.ComponentUID(k)) for k in [m.theta]) rooney_biegler_indexed_vars_exp_list = [] for i in range(self.data.shape[0]): rooney_biegler_indexed_vars_exp_list.append( - RooneyBieglerExperimentIndexedVars(self.data.loc[i,:].to_frame().transpose()) + RooneyBieglerExperimentIndexedVars( + self.data.loc[i, :].to_frame().transpose() + ) ) # Sum of squared error function def SSE(model): - expr = (model.experiment_outputs[model.y] - \ - model.response_function[model.experiment_outputs[model.hour]]) ** 2 + expr = ( + model.experiment_outputs[model.y] + - model.response_function[model.experiment_outputs[model.hour]] + ) ** 2 return expr self.objective_function = SSE @@ -570,13 +575,14 @@ def SSE(model): not parmest.inverse_reduced_hessian_available, "Cannot test covariance matrix: required ASL dependency is missing", ) - def check_rooney_biegler_results(self, objval, cov): # get indices in covariance matrix cov_cols = cov.columns.to_list() asymptote_index = [idx for idx, s in enumerate(cov_cols) if 'asymptote' in s][0] - rate_constant_index = [idx for idx, s in enumerate(cov_cols) if 'rate_constant' in s][0] + rate_constant_index = [ + idx for idx, s in enumerate(cov_cols) if 'rate_constant' in s + ][0] self.assertAlmostEqual(objval, 4.3317112, places=2) self.assertAlmostEqual( @@ -596,8 +602,7 @@ def test_parmest_basics(self): for model_type, parmest_input in self.input.items(): pest = parmest.Estimator( - parmest_input["exp_list"], - obj_function=self.objective_function, + parmest_input["exp_list"], obj_function=self.objective_function ) objval, thetavals, cov = pest.theta_est(calc_cov=True, cov_n=6) @@ -608,10 +613,9 @@ def test_parmest_basics(self): def test_parmest_basics_with_initialize_parmest_model_option(self): - for model_type, parmest_input in self.input.items(): + for model_type, parmest_input in self.input.items(): pest = parmest.Estimator( - parmest_input["exp_list"], - obj_function=self.objective_function, + parmest_input["exp_list"], obj_function=self.objective_function ) objval, thetavals, cov = pest.theta_est(calc_cov=True, cov_n=6) @@ -627,8 +631,7 @@ def test_parmest_basics_with_square_problem_solve(self): for model_type, parmest_input in self.input.items(): pest = parmest.Estimator( - parmest_input["exp_list"], - obj_function=self.objective_function, + parmest_input["exp_list"], obj_function=self.objective_function ) obj_at_theta = pest.objective_at_theta( @@ -643,10 +646,9 @@ def test_parmest_basics_with_square_problem_solve(self): 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["exp_list"], - obj_function=self.objective_function, + parmest_input["exp_list"], obj_function=self.objective_function ) obj_at_theta = pest.objective_at_theta(initialize_parmest_model=True) @@ -654,6 +656,7 @@ def test_parmest_basics_with_square_problem_solve_no_theta_vals(self): objval, thetavals, cov = pest.theta_est(calc_cov=True, cov_n=6) self.check_rooney_biegler_results(objval, cov) + @unittest.skipIf( not parmest.parmest_available, "Cannot test parmest: required dependencies are missing", @@ -692,13 +695,15 @@ def setUp(self): ) # Create an experiment list - exp_list= [] + exp_list = [] for i in range(data.shape[0]): exp_list.append(ReactorDesignExperiment(data, i)) solver_options = {"max_iter": 6000} - self.pest = parmest.Estimator(exp_list, obj_function='SSE', solver_options=solver_options) + self.pest = parmest.Estimator( + exp_list, obj_function='SSE', solver_options=solver_options + ) def test_theta_est(self): # used in data reconciliation @@ -820,15 +825,16 @@ def create_model(self): def label_model(self): m = self.model - + m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.unknown_parameters.update((k, pyo.ComponentUID(k)) - for k in [m.k1, m.k2]) + m.unknown_parameters.update( + (k, pyo.ComponentUID(k)) for k in [m.k1, m.k2] + ) def get_labeled_model(self): self.create_model() self.label_model() - + return self.model # This example tests data formatted in 3 ways @@ -873,10 +879,14 @@ def get_labeled_model(self): self.pest_dict = parmest.Estimator(exp_list_dict) # Estimator object with multiple scenarios - exp_list_df_multiple = [ReactorDesignExperimentDAE(data_df), - ReactorDesignExperimentDAE(data_df)] - exp_list_dict_multiple = [ReactorDesignExperimentDAE(data_dict), - ReactorDesignExperimentDAE(data_dict)] + exp_list_df_multiple = [ + ReactorDesignExperimentDAE(data_df), + ReactorDesignExperimentDAE(data_df), + ] + exp_list_dict_multiple = [ + ReactorDesignExperimentDAE(data_dict), + ReactorDesignExperimentDAE(data_dict), + ] self.pest_df_multiple = parmest.Estimator(exp_list_df_multiple) self.pest_dict_multiple = parmest.Estimator(exp_list_dict_multiple) @@ -963,27 +973,26 @@ def setUp(self): data=[[1, 8.3], [2, 10.3], [3, 19.0], [4, 16.0], [5, 15.6], [7, 19.8]], columns=["hour", "y"], ) - + # Sum of squared error function def SSE(model): - expr = (model.experiment_outputs[model.y] - \ - model.response_function[model.experiment_outputs[model.hour]]) ** 2 + expr = ( + model.experiment_outputs[model.y] + - model.response_function[model.experiment_outputs[model.hour]] + ) ** 2 return expr exp_list = [] for i in range(data.shape[0]): exp_list.append( - RooneyBieglerExperiment(data.loc[i,:].to_frame().transpose()) + RooneyBieglerExperiment(data.loc[i, :].to_frame().transpose()) ) solver_options = {"tol": 1e-8} self.data = data self.pest = parmest.Estimator( - exp_list, - obj_function=SSE, - solver_options=solver_options, - tee=True, + exp_list, obj_function=SSE, solver_options=solver_options, tee=True ) def test_theta_est_with_square_initialization(self): diff --git a/pyomo/contrib/parmest/tests/test_scenariocreator.py b/pyomo/contrib/parmest/tests/test_scenariocreator.py index bf6fa12b8b1..0c0976a453f 100644 --- a/pyomo/contrib/parmest/tests/test_scenariocreator.py +++ b/pyomo/contrib/parmest/tests/test_scenariocreator.py @@ -65,9 +65,9 @@ def setUp(self): ], columns=["sv", "caf", "ca", "cb", "cc", "cd"], ) - + # Create an experiment list - exp_list= [] + exp_list = [] for i in range(data.shape[0]): exp_list.append(ReactorDesignExperiment(data, i)) @@ -123,7 +123,7 @@ def setUp(self): # for the sum of squared error that will be used in parameter estimation # Create an experiment list - exp_list= [] + exp_list = [] for i in range(len(data)): exp_list.append(sb.SemiBatchExperiment(data[i])) From 70d66e6d2fba6d6d2ca3b9ab5f347ca38c3176ef Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 12 Feb 2024 12:27:22 -0700 Subject: [PATCH 0984/1797] Fixed typo parmest.py. --- pyomo/contrib/parmest/parmest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index 7e9e6cf90d4..ccdec527c06 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -333,7 +333,7 @@ class Estimator(object): Parameters ---------- - experiement_list: list of Experiments + experiment_list: list of Experiments A list of experiment objects which creates one labeled model for each expeirment obj_function: string or function (optional) From 922c715013e62553f5551b338f6d6cd490358bb3 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 12 Feb 2024 12:32:19 -0700 Subject: [PATCH 0985/1797] Another typo in parmest.py. --- pyomo/contrib/parmest/parmest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index ccdec527c06..2e44b278423 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -381,7 +381,7 @@ def __init__(self, *args, **kwargs): 'Experiment list model does not have suffix ' + '"experiment_outputs".' ) try: - parms = [k.name for k, v in model.unknown_parameters.items()] + params = [k.name for k, v in model.unknown_parameters.items()] except: RuntimeError( 'Experiment list model does not have suffix ' + '"unknown_parameters".' From 9a208616cc54f844a41effa42bb60f788b8ad4ff Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 12 Feb 2024 14:51:47 -0700 Subject: [PATCH 0986/1797] 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 0987/1797] 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 0988/1797] 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 0989/1797] 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 0990/1797] 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 0991/1797] 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 0992/1797] 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 0993/1797] 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 0994/1797] 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 0995/1797] 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 0996/1797] 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 0997/1797] 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 0998/1797] 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 0999/1797] 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 1000/1797] 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 1001/1797] 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 1002/1797] 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 1003/1797] 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 1004/1797] 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 1005/1797] 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 1006/1797] 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 1007/1797] 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 1008/1797] 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 1009/1797] 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 1010/1797] 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 1011/1797] 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 1012/1797] 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 1013/1797] 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 1014/1797] 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 1015/1797] 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 1016/1797] 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 1017/1797] 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 1018/1797] 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 1019/1797] 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 1020/1797] 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 1021/1797] 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 1022/1797] 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 1023/1797] 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 1024/1797] 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 1025/1797] 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 1026/1797] 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 1027/1797] 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 1028/1797] 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 1029/1797] 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 1030/1797] 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 1031/1797] 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 1032/1797] 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 1033/1797] 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 1034/1797] 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 1035/1797] 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 1036/1797] 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 1037/1797] 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 1038/1797] 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 1039/1797] 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 1040/1797] 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 1041/1797] 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 1042/1797] 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 1043/1797] 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 1044/1797] 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 1045/1797] 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 1046/1797] 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 1047/1797] 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 1048/1797] 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 1049/1797] 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 1050/1797] 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 7965ac52b8b88bf14a0f8bac32b2f6c20d6eabce Mon Sep 17 00:00:00 2001 From: kaklise Date: Thu, 15 Feb 2024 15:27:31 -0800 Subject: [PATCH 1051/1797] removed group_data function --- pyomo/contrib/parmest/parmest.py | 39 -------------------------------- 1 file changed, 39 deletions(-) diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index 2e44b278423..83b24e39327 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -16,7 +16,6 @@ # TODO: move use_mpisppy to a Pyomo configuration option # Redesign TODOS -# TODO: remove group_data,this is only used in 1 example and should be handled by the user in Experiment # TODO: _treemaker is not used in parmest, the code could be moved to scenario tree if needed # TODO: Create additional built in objective expressions in an Enum class which includes SSE (see SSE function below) # TODO: Clean up the use of theta_names through out the code. The Experiment returns the CUID of each theta and this can be used directly (instead of the name) @@ -272,44 +271,6 @@ def _experiment_instance_creation_callback( # return m - -# def group_data(data, groupby_column_name, use_mean=None): -# """ -# Group data by scenario - -# Parameters -# ---------- -# data: DataFrame -# Data -# groupby_column_name: strings -# Name of data column which contains scenario numbers -# use_mean: list of column names or None, optional -# Name of data columns which should be reduced to a single value per -# scenario by taking the mean - -# Returns -# ---------- -# grouped_data: list of dictionaries -# Grouped data -# """ -# if use_mean is None: -# use_mean_list = [] -# else: -# use_mean_list = use_mean - -# grouped_data = [] -# for exp_num, group in data.groupby(data[groupby_column_name]): -# d = {} -# for col in group.columns: -# if col in use_mean_list: -# d[col] = group[col].mean() -# else: -# d[col] = list(group[col]) -# grouped_data.append(d) - -# return grouped_data - - def SSE(model): expr = sum((y - yhat) ** 2 for y, yhat in model.experiment_outputs.items()) return expr From 9157b8c048a26aef8b3637f979a16d01c3768cf1 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 15 Feb 2024 16:29:59 -0700 Subject: [PATCH 1052/1797] 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 1053/1797] 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 edfc620772a17274afc08cfea78031ec578319ef Mon Sep 17 00:00:00 2001 From: kaklise Date: Thu, 15 Feb 2024 15:57:46 -0800 Subject: [PATCH 1054/1797] Removed group_data from example, data is now grouped using data_i, and mean of sv and caf --- .../reactor_design/timeseries_data_example.py | 54 +++---------------- 1 file changed, 7 insertions(+), 47 deletions(-) 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 7ffca3696eb..cde7febf6cc 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/timeseries_data_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/timeseries_data_example.py @@ -23,15 +23,16 @@ class TimeSeriesReactorDesignExperiment(ReactorDesignExperiment): def __init__(self, data, experiment_number): self.data = data self.experiment_number = experiment_number - self.data_i = data[experiment_number] + data_i = data.loc[data['experiment'] == experiment_number,:] + self.data_i = data_i.reset_index() self.model = None def finalize_model(self): m = self.model # Experiment inputs values - m.sv = self.data_i['sv'] - m.caf = self.data_i['caf'] + m.sv = self.data_i['sv'].mean() + m.caf = self.data_i['caf'].mean() # Experiment output values m.ca = self.data_i['ca'][0] @@ -42,59 +43,18 @@ def finalize_model(self): return m -def group_data(data, groupby_column_name, use_mean=None): - """ - Group data by scenario - - Parameters - ---------- - data: DataFrame - Data - groupby_column_name: strings - Name of data column which contains scenario numbers - use_mean: list of column names or None, optional - Name of data columns which should be reduced to a single value per - scenario by taking the mean - - Returns - ---------- - grouped_data: list of dictionaries - Grouped data - """ - if use_mean is None: - use_mean_list = [] - else: - use_mean_list = use_mean - - grouped_data = [] - for exp_num, group in data.groupby(data[groupby_column_name]): - d = {} - for col in group.columns: - if col in use_mean_list: - d[col] = group[col].mean() - else: - d[col] = list(group[col]) - grouped_data.append(d) - - return grouped_data - - def main(): - # Parameter estimation using timeseries data + # Parameter estimation using timeseries data, grouped by experiment number # Data, includes multiple sensors for ca and cc file_dirname = dirname(abspath(str(__file__))) file_name = abspath(join(file_dirname, 'reactor_data_timeseries.csv')) data = pd.read_csv(file_name) - # Group time series data into experiments, return the mean value for sv and caf - # Returns a list of dictionaries - data_ts = group_data(data, 'experiment', ['sv', 'caf']) - # Create an experiment list exp_list = [] - for i in range(len(data_ts)): - exp_list.append(TimeSeriesReactorDesignExperiment(data_ts, i)) + for i in data['experiment'].unique(): + exp_list.append(TimeSeriesReactorDesignExperiment(data, i)) def SSE_timeseries(model): From 2bcb0e074dce33d2aa965e63e1d647fa4aa7403c Mon Sep 17 00:00:00 2001 From: kaklise Date: Thu, 15 Feb 2024 16:04:57 -0800 Subject: [PATCH 1055/1797] Data formatting moved to create_model --- .../rooney_biegler/bootstrap_example.py | 2 +- .../likelihood_ratio_example.py | 2 +- .../parameter_estimation_example.py | 2 +- .../examples/rooney_biegler/rooney_biegler.py | 12 ++++--- .../rooney_biegler_with_constraint.py | 12 ++++--- pyomo/contrib/parmest/tests/test_parmest.py | 32 +++++++++++-------- 6 files changed, 35 insertions(+), 27 deletions(-) diff --git a/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.py b/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.py index 953de98a48e..b9ef114c2b3 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.py @@ -35,7 +35,7 @@ def SSE(model): # Create an experiment list exp_list = [] for i in range(data.shape[0]): - exp_list.append(RooneyBieglerExperiment(data.loc[i, :].to_frame().transpose())) + exp_list.append(RooneyBieglerExperiment(data.loc[i, :])) # View one model # exp0_model = exp_list[0].get_labeled_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 b08d5456982..7799148389a 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/likelihood_ratio_example.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/likelihood_ratio_example.py @@ -36,7 +36,7 @@ def SSE(model): # Create an experiment list exp_list = [] for i in range(data.shape[0]): - exp_list.append(RooneyBieglerExperiment(data.loc[i, :].to_frame().transpose())) + exp_list.append(RooneyBieglerExperiment(data.loc[i, :])) # View one model # exp0_model = exp_list[0].get_labeled_model() 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 9c851ecd9c8..aa810453883 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/parameter_estimation_example.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/parameter_estimation_example.py @@ -35,7 +35,7 @@ def SSE(model): # Create an experiment list exp_list = [] for i in range(data.shape[0]): - exp_list.append(RooneyBieglerExperiment(data.loc[i, :].to_frame().transpose())) + exp_list.append(RooneyBieglerExperiment(data.loc[i, :])) # View one model # exp0_model = exp_list[0].get_labeled_model() diff --git a/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py b/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py index 8fa0fd70ec6..920bd2987e0 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py @@ -52,15 +52,17 @@ def __init__(self, data): self.model = None def create_model(self): - self.model = rooney_biegler_model(self.data) + # rooney_biegler_model expects a dataframe + data_df = self.data.to_frame().transpose() + self.model = rooney_biegler_model(data_df) def label_model(self): m = self.model m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs.update([(m.hour, self.data.iloc[0]['hour'])]) - m.experiment_outputs.update([(m.y, self.data.iloc[0]['y'])]) + m.experiment_outputs.update([(m.hour, self.data['hour'])]) + m.experiment_outputs.update([(m.y, self.data['y'])]) m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) m.unknown_parameters.update( @@ -72,8 +74,8 @@ def finalize_model(self): m = self.model # Experiment output values - m.hour = self.data.iloc[0]['hour'] - m.y = self.data.iloc[0]['y'] + m.hour = self.data['hour'] + m.y = self.data['y'] def get_labeled_model(self): self.create_model() 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 463c876dc43..499f6eb505b 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 @@ -56,15 +56,17 @@ def __init__(self, data): self.model = None def create_model(self): - self.model = rooney_biegler_model_with_constraint(self.data) + # rooney_biegler_model_with_constraint expects a dataframe + data_df = self.data.to_frame().transpose() + self.model = rooney_biegler_model_with_constraint(data_df) def label_model(self): m = self.model m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs.update([(m.hour, self.data.iloc[0]['hour'])]) - m.experiment_outputs.update([(m.y, self.data.iloc[0]['y'])]) + m.experiment_outputs.update([(m.hour, self.data['hour'])]) + m.experiment_outputs.update([(m.y, self.data['y'])]) m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) m.unknown_parameters.update( @@ -76,8 +78,8 @@ def finalize_model(self): m = self.model # Experiment output values - m.hour = self.data.iloc[0]['hour'] - m.y = self.data.iloc[0]['y'] + m.hour = self.data['hour'] + m.y = self.data['y'] def get_labeled_model(self): self.create_model() diff --git a/pyomo/contrib/parmest/tests/test_parmest.py b/pyomo/contrib/parmest/tests/test_parmest.py index ff8d1663bc9..bbffa982bcf 100644 --- a/pyomo/contrib/parmest/tests/test_parmest.py +++ b/pyomo/contrib/parmest/tests/test_parmest.py @@ -77,7 +77,7 @@ def SSE(model): exp_list = [] for i in range(data.shape[0]): exp_list.append( - RooneyBieglerExperiment(data.loc[i, :].to_frame().transpose()) + RooneyBieglerExperiment(data.loc[i, :]) ) # Create an instance of the parmest estimator @@ -386,13 +386,14 @@ def response_rule(m, h): class RooneyBieglerExperimentParams(RooneyBieglerExperiment): def create_model(self): - self.model = rooney_biegler_params(self.data) + data_df = self.data.to_frame().transpose() + self.model = rooney_biegler_params(data_df) rooney_biegler_params_exp_list = [] for i in range(self.data.shape[0]): rooney_biegler_params_exp_list.append( RooneyBieglerExperimentParams( - self.data.loc[i, :].to_frame().transpose() + self.data.loc[i, :] ) ) @@ -422,15 +423,16 @@ def response_rule(m, h): class RooneyBieglerExperimentIndexedParams(RooneyBieglerExperiment): def create_model(self): - self.model = rooney_biegler_indexed_params(self.data) + data_df = self.data.to_frame().transpose() + self.model = rooney_biegler_indexed_params(data_df) def label_model(self): m = self.model m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs.update([(m.hour, self.data.iloc[0]['hour'])]) - m.experiment_outputs.update([(m.y, self.data.iloc[0]['y'])]) + m.experiment_outputs.update([(m.hour, self.data['hour'])]) + m.experiment_outputs.update([(m.y, self.data['y'])]) m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) m.unknown_parameters.update((k, pyo.ComponentUID(k)) for k in [m.theta]) @@ -439,7 +441,7 @@ def label_model(self): for i in range(self.data.shape[0]): rooney_biegler_indexed_params_exp_list.append( RooneyBieglerExperimentIndexedParams( - self.data.loc[i, :].to_frame().transpose() + self.data.loc[i, :] ) ) @@ -465,12 +467,13 @@ def response_rule(m, h): class RooneyBieglerExperimentVars(RooneyBieglerExperiment): def create_model(self): - self.model = rooney_biegler_vars(self.data) + data_df = self.data.to_frame().transpose() + self.model = rooney_biegler_vars(data_df) rooney_biegler_vars_exp_list = [] for i in range(self.data.shape[0]): rooney_biegler_vars_exp_list.append( - RooneyBieglerExperimentVars(self.data.loc[i, :].to_frame().transpose()) + RooneyBieglerExperimentVars(self.data.loc[i, :]) ) def rooney_biegler_indexed_vars(data): @@ -501,15 +504,16 @@ def response_rule(m, h): class RooneyBieglerExperimentIndexedVars(RooneyBieglerExperiment): def create_model(self): - self.model = rooney_biegler_indexed_vars(self.data) + data_df = self.data.to_frame().transpose() + self.model = rooney_biegler_indexed_vars(data_df) def label_model(self): m = self.model m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs.update([(m.hour, self.data.iloc[0]['hour'])]) - m.experiment_outputs.update([(m.y, self.data.iloc[0]['y'])]) + m.experiment_outputs.update([(m.hour, self.data['hour'])]) + m.experiment_outputs.update([(m.y, self.data['y'])]) m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) m.unknown_parameters.update((k, pyo.ComponentUID(k)) for k in [m.theta]) @@ -518,7 +522,7 @@ def label_model(self): for i in range(self.data.shape[0]): rooney_biegler_indexed_vars_exp_list.append( RooneyBieglerExperimentIndexedVars( - self.data.loc[i, :].to_frame().transpose() + self.data.loc[i, :] ) ) @@ -985,7 +989,7 @@ def SSE(model): exp_list = [] for i in range(data.shape[0]): exp_list.append( - RooneyBieglerExperiment(data.loc[i, :].to_frame().transpose()) + RooneyBieglerExperiment(data.loc[i, :]) ) solver_options = {"tol": 1e-8} From ae28ceea6f4b255242d58847e0c92bba5536a87d Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Fri, 16 Feb 2024 07:29:27 -0700 Subject: [PATCH 1056/1797] 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 1057/1797] 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 1058/1797] 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 1059/1797] 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 1060/1797] 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 1061/1797] 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 1062/1797] 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 1063/1797] 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 1064/1797] 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 1065/1797] 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 1066/1797] 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 1067/1797] 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 1068/1797] 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 1069/1797] 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 1070/1797] 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 1071/1797] 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 1072/1797] 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 1073/1797] 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 1074/1797] 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 1075/1797] 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 1076/1797] 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 1077/1797] 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 1078/1797] 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 1079/1797] 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 1080/1797] 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 1081/1797] 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 1082/1797] 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 1083/1797] 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 1084/1797] 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 1085/1797] 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 1086/1797] 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 1087/1797] 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 1088/1797] 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 1089/1797] 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 1090/1797] 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 1091/1797] 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 1092/1797] 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 1093/1797] 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 1094/1797] 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 1095/1797] 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 1096/1797] 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 1097/1797] 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 1098/1797] 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 1099/1797] 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 1100/1797] 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 1101/1797] 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 1102/1797] 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 1103/1797] 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 1104/1797] 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 1105/1797] 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 1106/1797] 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 1107/1797] 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 1108/1797] 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 1109/1797] 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 1110/1797] 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 1111/1797] 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 1112/1797] 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 1113/1797] 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 1114/1797] 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 1115/1797] 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 1116/1797] 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 1117/1797] 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 1118/1797] 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 1119/1797] 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 1120/1797] 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 1121/1797] 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 1122/1797] 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 1123/1797] 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 1124/1797] 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 1125/1797] 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 1126/1797] 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 1127/1797] 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 1128/1797] 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 1129/1797] 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 1130/1797] 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 8f581df56f057101bf7fa41eb76c1ed87dd6b5e2 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 19 Feb 2024 16:17:42 -0700 Subject: [PATCH 1131/1797] Added __init__.py to parmest deprecated folder. --- pyomo/contrib/parmest/deprecated/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 pyomo/contrib/parmest/deprecated/__init__.py diff --git a/pyomo/contrib/parmest/deprecated/__init__.py b/pyomo/contrib/parmest/deprecated/__init__.py new file mode 100644 index 00000000000..d93cfd77b3c --- /dev/null +++ b/pyomo/contrib/parmest/deprecated/__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 48533dbc566525deb1c6f20acb93bdd394d8e162 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 19 Feb 2024 16:18:10 -0700 Subject: [PATCH 1132/1797] 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 030aa576776959a0b52ad40ef3f04cad38b73b55 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 19 Feb 2024 16:32:11 -0700 Subject: [PATCH 1133/1797] More parmest black formatting. --- .../reactor_design/timeseries_data_example.py | 2 +- pyomo/contrib/parmest/parmest.py | 1 + pyomo/contrib/parmest/tests/test_parmest.py | 20 +++++-------------- 3 files changed, 7 insertions(+), 16 deletions(-) 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 85095fb94de..1e457bf1e89 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/timeseries_data_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/timeseries_data_example.py @@ -23,7 +23,7 @@ class TimeSeriesReactorDesignExperiment(ReactorDesignExperiment): def __init__(self, data, experiment_number): self.data = data self.experiment_number = experiment_number - data_i = data.loc[data['experiment'] == experiment_number,:] + data_i = data.loc[data['experiment'] == experiment_number, :] self.data_i = data_i.reset_index() self.model = None diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index ed9bda232b4..90d42e68910 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -271,6 +271,7 @@ def _experiment_instance_creation_callback( # return m + def SSE(model): expr = sum((y - yhat) ** 2 for y, yhat in model.experiment_outputs.items()) return expr diff --git a/pyomo/contrib/parmest/tests/test_parmest.py b/pyomo/contrib/parmest/tests/test_parmest.py index 0893c9b4fde..15264a18989 100644 --- a/pyomo/contrib/parmest/tests/test_parmest.py +++ b/pyomo/contrib/parmest/tests/test_parmest.py @@ -76,9 +76,7 @@ def SSE(model): # Create an experiment list exp_list = [] for i in range(data.shape[0]): - exp_list.append( - RooneyBieglerExperiment(data.loc[i, :]) - ) + exp_list.append(RooneyBieglerExperiment(data.loc[i, :])) # Create an instance of the parmest estimator pest = parmest.Estimator(exp_list, obj_function=SSE) @@ -392,9 +390,7 @@ def create_model(self): rooney_biegler_params_exp_list = [] for i in range(self.data.shape[0]): rooney_biegler_params_exp_list.append( - RooneyBieglerExperimentParams( - self.data.loc[i, :] - ) + RooneyBieglerExperimentParams(self.data.loc[i, :]) ) def rooney_biegler_indexed_params(data): @@ -440,9 +436,7 @@ def label_model(self): rooney_biegler_indexed_params_exp_list = [] for i in range(self.data.shape[0]): rooney_biegler_indexed_params_exp_list.append( - RooneyBieglerExperimentIndexedParams( - self.data.loc[i, :] - ) + RooneyBieglerExperimentIndexedParams(self.data.loc[i, :]) ) def rooney_biegler_vars(data): @@ -521,9 +515,7 @@ def label_model(self): rooney_biegler_indexed_vars_exp_list = [] for i in range(self.data.shape[0]): rooney_biegler_indexed_vars_exp_list.append( - RooneyBieglerExperimentIndexedVars( - self.data.loc[i, :] - ) + RooneyBieglerExperimentIndexedVars(self.data.loc[i, :]) ) # Sum of squared error function @@ -988,9 +980,7 @@ def SSE(model): exp_list = [] for i in range(data.shape[0]): - exp_list.append( - RooneyBieglerExperiment(data.loc[i, :]) - ) + exp_list.append(RooneyBieglerExperiment(data.loc[i, :])) solver_options = {"tol": 1e-8} From 3828841bf5e327f69c5369acd9ce089e28a81751 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 19 Feb 2024 16:33:21 -0700 Subject: [PATCH 1134/1797] 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 1135/1797] 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 1136/1797] 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 3f787a3f5a2fb772caf11468f267fa2968190ca7 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 20 Feb 2024 06:52:47 -0700 Subject: [PATCH 1137/1797] Added exp1.out to exp14.out by force for deprecated semibatch examples in parmest. --- pyomo/contrib/parmest/deprecated/examples/semibatch/exp1.out | 1 + pyomo/contrib/parmest/deprecated/examples/semibatch/exp10.out | 1 + pyomo/contrib/parmest/deprecated/examples/semibatch/exp11.out | 1 + pyomo/contrib/parmest/deprecated/examples/semibatch/exp12.out | 1 + pyomo/contrib/parmest/deprecated/examples/semibatch/exp13.out | 1 + pyomo/contrib/parmest/deprecated/examples/semibatch/exp14.out | 1 + pyomo/contrib/parmest/deprecated/examples/semibatch/exp2.out | 1 + pyomo/contrib/parmest/deprecated/examples/semibatch/exp3.out | 1 + pyomo/contrib/parmest/deprecated/examples/semibatch/exp4.out | 1 + pyomo/contrib/parmest/deprecated/examples/semibatch/exp5.out | 1 + pyomo/contrib/parmest/deprecated/examples/semibatch/exp6.out | 1 + pyomo/contrib/parmest/deprecated/examples/semibatch/exp7.out | 1 + pyomo/contrib/parmest/deprecated/examples/semibatch/exp8.out | 1 + pyomo/contrib/parmest/deprecated/examples/semibatch/exp9.out | 1 + 14 files changed, 14 insertions(+) create mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp1.out create mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp10.out create mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp11.out create mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp12.out create mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp13.out create mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp14.out create mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp2.out create mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp3.out create mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp4.out create mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp5.out create mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp6.out create mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp7.out create mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp8.out create mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp9.out diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp1.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp1.out new file mode 100644 index 00000000000..f1d826085bf --- /dev/null +++ b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp1.out @@ -0,0 +1 @@ +{"experiment": 1, "Ca0": 0, "Ca_meas": {"17280.0": 1.0206137429621787, "0.0": 0.3179205033819153, "7560.0": 6.1190611079452015, "9720.0": 5.143125125521654, "19440.0": 0.6280402056097951, "16200.0": 0.8579867036528984, "11880.0": 3.7282923165210042, "2160.0": 4.63887641485678, "8640.0": 5.343033989137008, "14040.0": 2.053029378587664, "4320.0": 6.161603379127277, "6480.0": 6.175522427215327, "1080.0": 2.5735849991352358, "18360.0": 0.6351040530590654, "10800.0": 5.068206847862049, "21600.0": 0.40727295182614354, "20520.0": 0.6621175064161002, "5400.0": 6.567824349703669, "3240.0": 5.655458079751501, "12960.0": 2.654764659666162, "15120.0": 1.6757350275784135}, "alphac": 0.7, "Fa2": 0.0, "deltaH1": -40000, "Fa1": 0.003, "Tc2": 320, "Cc0": 0, "Tc1": 320, "Cc_meas": {"17280.0": 5.987636915771096, "0.0": 0.09558280565339891, "7560.0": 1.238130321602845, "9720.0": 1.9945247030805529, "19440.0": 6.695864385679773, "16200.0": 5.41645220805244, "11880.0": 2.719892366798277, "2160.0": 0.10805070272409367, "8640.0": 1.800229763433655, "14040.0": 4.156268601598023, "4320.0": -0.044818714779864405, "6480.0": 0.8106022415380871, "1080.0": -0.07327388848369068, "18360.0": 5.96868114596425, "10800.0": 2.0726982059573835, "21600.0": 7.269213818513372, "20520.0": 6.725777234409265, "5400.0": 0.18749831830326769, "3240.0": -0.10164819461093579, "12960.0": 3.745361461163259, "15120.0": 4.92464438752146}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"17280.0": 12.527811727243744, "0.0": 0.006198551839384427, "7560.0": 8.198459268980448, "9720.0": 10.884163155983586, "19440.0": 12.150552321810109, "16200.0": 13.247640677577017, "11880.0": 12.921639059281906, "2160.0": 1.2393091651113075, "8640.0": 9.76716833273541, "14040.0": 13.211149989298647, "4320.0": 3.803104433804622, "6480.0": 6.5810650565269375, "1080.0": 0.3042714459761661, "18360.0": 12.544400522361945, "10800.0": 11.737104197836604, "21600.0": 11.886358606219954, "20520.0": 11.832544691029744, "5400.0": 5.213810980890077, "3240.0": 2.1926632109587216, "12960.0": 13.113839789286594, "15120.0": 13.27982750652159}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"17280.0": 325.16059207223026, "0.0": 297.66636064503314, "7560.0": 331.3122493868531, "9720.0": 332.5912691607457, "19440.0": 325.80525903012233, "16200.0": 323.2692288871708, "11880.0": 334.0549081870754, "2160.0": 323.2373236557714, "8640.0": 333.4576764024497, "14040.0": 328.42212335544315, "4320.0": 327.6704558317418, "6480.0": 331.06042780025075, "1080.0": 316.2567029216892, "18360.0": 326.6647586865489, "10800.0": 334.23136746878185, "21600.0": 324.0057232633869, "20520.0": 324.4555288383823, "5400.0": 331.9568676813546, "3240.0": 326.12583828081813, "12960.0": 329.2382904744002, "15120.0": 327.3959354386782}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp10.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp10.out new file mode 100644 index 00000000000..7eb7980a7be --- /dev/null +++ b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp10.out @@ -0,0 +1 @@ +{"experiment": 10, "Ca0": 0, "Ca_meas": {"0.0": 0.17585381115505311, "4320.0": 6.0315379232850992, "7560.0": 5.4904391073929908, "21600.0": 0.28272237229599306, "9720.0": 5.2677178238115365, "16200.0": 1.1265897978788217, "20520.0": 0.057584376823091032, "3240.0": 5.6315955838815661, "11880.0": 3.328489578835276, "14040.0": 2.0226562017072607, "17280.0": 1.1268539727440208, "2160.0": 4.3030132489621638, "5400.0": 6.1094034780709618, "18360.0": 0.50886621390394615, "8640.0": 5.3773941013828752, "6480.0": 5.9760510402178078, "1080.0": 2.9280667525762598, "15120.0": 1.375026987701359, "12960.0": 2.5451999496997635, "19440.0": 0.79349685535634917, "10800.0": 4.3653401523141229}, "alphac": 0.7, "Fa2": 0.0, "deltaH1": -40000, "Fa1": 0.003, "Tc2": 320, "Cc0": 0, "Tc1": 320, "Cc_meas": {"0.0": -0.038024312530073517, "4320.0": 0.35646997778490641, "7560.0": 1.0986283962244756, "21600.0": 7.160087143091391, "9720.0": 2.129148884681515, "16200.0": 5.1383561968992435, "20520.0": 6.8451793517536901, "3240.0": 0.098714783055484312, "11880.0": 3.0269500169512602, "14040.0": 3.9370558676788283, "17280.0": 5.9641262824404357, "2160.0": -0.12281730158248855, "5400.0": 0.59307341448224149, "18360.0": 6.2121451052794248, "8640.0": 1.7607685730069123, "6480.0": 0.53516134735284115, "1080.0": 0.021830284057365701, "15120.0": 4.7082270119144871, "12960.0": 4.0629501433813449, "19440.0": 6.333023537154518, "10800.0": 2.2805891192921983}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"0.0": -0.063620932422370907, "4320.0": 3.5433262677921662, "7560.0": 8.6893371808300444, "21600.0": 11.870505989648386, "9720.0": 11.309193344250055, "16200.0": 12.739396897287321, "20520.0": 11.791007739959538, "3240.0": 2.1799284210951009, "11880.0": 12.669418985545658, "14040.0": 13.141014574935076, "17280.0": 12.645153211711902, "2160.0": 1.4830589148905116, "5400.0": 5.2739635750232985, "18360.0": 12.761210138866151, "8640.0": 9.9142303856203373, "6480.0": 7.0761548290524603, "1080.0": 0.43050918133895111, "15120.0": 12.66028651303237, "12960.0": 12.766057551719733, "19440.0": 12.385465894826957, "10800.0": 11.96758252080965}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"0.0": 301.47604965958681, "4320.0": 327.40308974239883, "7560.0": 332.84954650085029, "21600.0": 322.5157157706418, "9720.0": 333.3451281132692, "16200.0": 325.64418049630734, "20520.0": 321.94339225425767, "3240.0": 327.19623764314332, "11880.0": 330.24784399520615, "14040.0": 328.20666366981681, "17280.0": 324.73232197103431, "2160.0": 324.1280979971192, "5400.0": 330.87716394833132, "18360.0": 323.47482388751507, "8640.0": 332.49299626233665, "6480.0": 332.1275559564059, "1080.0": 319.29329353123859, "15120.0": 327.29837672678497, "12960.0": 329.24537484541742, "19440.0": 324.02559861322572, "10800.0": 335.57159429203386}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp11.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp11.out new file mode 100644 index 00000000000..d97442f031b --- /dev/null +++ b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp11.out @@ -0,0 +1 @@ +{"experiment": 11, "Ca0": 0, "Ca_meas": {"0.0": 0.0, "4320.0": 2.9214639882977118, "7560.0": 3.782748575728669, "21600.0": 1.0934559688831289, "9720.0": 3.9988602724163731, "16200.0": 1.3975369118671503, "20520.0": 1.1243254110346068, "3240.0": 2.4246081576650433, "11880.0": 3.4755989173839743, "14040.0": 1.9594736461287787, "17280.0": 1.2827751626299488, "2160.0": 1.7884659182526941, "5400.0": 3.3009088212806073, "18360.0": 1.2111862780556402, "8640.0": 3.9172974313558302, "6480.0": 3.5822886585117493, "1080.0": 0.9878678077907459, "15120.0": 1.5972116122332332, "12960.0": 2.5920858659103394, "19440.0": 1.1618336860102094, "10800.0": 4.0378190270354528}, "alphac": 0.7, "Fa2": 0.001, "deltaH1": -40000, "Fa1": 0.001, "Tc2": 340, "Cc0": 0, "Tc1": 310, "Cc_meas": {"0.0": 0.0, "4320.0": 0.023982318123109209, "7560.0": 0.12357850466238102, "21600.0": 6.5347744286121001, "9720.0": 0.24908724520575926, "16200.0": 3.2484340438243127, "20520.0": 5.9039946620569568, "3240.0": 0.0099959742916886744, "11880.0": 0.58037814751790051, "14040.0": 1.8149241834100336, "17280.0": 3.9360275733370447, "2160.0": 0.0028162554877217529, "5400.0": 0.046614709277751604, "18360.0": 4.6059327483829717, "8640.0": 0.17995429953061945, "6480.0": 0.079417046516631534, "1080.0": 0.00029995464126276485, "15120.0": 2.5396785773426069, "12960.0": 1.1193190878564474, "19440.0": 5.2613272888405183, "10800.0": 0.33137635542084787}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"0.0": 0.0, "4320.0": 0.96588936445839646, "7560.0": 2.5063363714379272, "21600.0": 7.0191274767686718, "9720.0": 3.6738114082888234, "16200.0": 7.2205659498627206, "20520.0": 7.0929480434376666, "3240.0": 0.56824903663740756, "11880.0": 5.2689545424581627, "14040.0": 6.8616827734843717, "17280.0": 7.2356915893004699, "2160.0": 0.25984959276122965, "5400.0": 1.4328144788513235, "18360.0": 7.2085405027857679, "8640.0": 3.0841896398822519, "6480.0": 1.9514434452676097, "1080.0": 0.063681239951285565, "15120.0": 7.1238797146821753, "12960.0": 6.279843675978368, "19440.0": 7.1578041487575703, "10800.0": 4.2664529460540459}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"0.0": 300.0, "4320.0": 312.23882460110946, "7560.0": 313.65588947948061, "21600.0": 347.47264008527429, "9720.0": 314.23459379740643, "16200.0": 349.53974420970837, "20520.0": 347.6739307915775, "3240.0": 311.5507050941913, "11880.0": 343.84565806826117, "14040.0": 351.77400891772504, "17280.0": 348.76203275606656, "2160.0": 310.56857709679934, "5400.0": 312.79850843986321, "18360.0": 348.2596349485566, "8640.0": 313.9757607933924, "6480.0": 313.26649244154709, "1080.0": 308.41052123547064, "15120.0": 350.65940715239384, "12960.0": 351.11078111562267, "19440.0": 347.92214750818005, "10800.0": 317.60632266285268}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp12.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp12.out new file mode 100644 index 00000000000..cbed2d89634 --- /dev/null +++ b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp12.out @@ -0,0 +1 @@ +{"experiment": 12, "Ca0": 0, "Ca_meas": {"0.0": -0.11670041274142862, "4320.0": 2.5843604031503551, "7560.0": 3.9909358380362421, "21600.0": 0.85803342268297711, "9720.0": 3.8011636431252702, "16200.0": 1.1002575863333801, "20520.0": 1.5248654901477563, "3240.0": 2.8626873137030655, "11880.0": 3.0876088469734766, "14040.0": 2.1233520630854348, "17280.0": 1.3118009790549121, "2160.0": 1.9396713478350336, "5400.0": 3.4898817799323192, "18360.0": 1.3214498335778544, "8640.0": 4.3166520687122008, "6480.0": 3.7430014080130212, "1080.0": 0.71502131244112932, "15120.0": 1.6869248126824714, "12960.0": 2.6199132141077999, "19440.0": 1.1934026369102775, "10800.0": 4.1662760005238244}, "alphac": 0.7, "Fa2": 0.001, "deltaH1": -40000, "Fa1": 0.001, "Tc2": 340, "Cc0": 0, "Tc1": 310, "Cc_meas": {"0.0": -0.14625373198142194, "4320.0": 0.50521240190855021, "7560.0": 0.072658369151157948, "21600.0": 6.5820277701997947, "9720.0": 0.14656574869775074, "16200.0": 3.2344935292984842, "20520.0": 6.1934992398225113, "3240.0": 0.073187422926812754, "11880.0": 0.68271223751792398, "14040.0": 1.5495094265280573, "17280.0": 3.5758298262936474, "2160.0": -0.052665808995189155, "5400.0": -0.067101579134353218, "18360.0": 4.6809081069906799, "8640.0": 0.58099071333349073, "6480.0": -0.34905530826754594, "1080.0": -0.13981627097780677, "15120.0": 2.7577781806493582, "12960.0": 0.9824219783559841, "19440.0": 5.2002977724609245, "10800.0": 0.0089889440138224419}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"0.0": -0.34864609349591541, "4320.0": 0.99561164186682227, "7560.0": 2.4688230226633143, "21600.0": 7.0564292657160461, "9720.0": 3.9961025255558669, "16200.0": 7.161739218807984, "20520.0": 6.8044634236188921, "3240.0": 0.21017912447011355, "11880.0": 5.1168427571991435, "14040.0": 7.0032016822280907, "17280.0": 7.11845364876228, "2160.0": 0.22241873726625405, "5400.0": 1.3174801426799267, "18360.0": 6.9581816529657257, "8640.0": 3.0438444011178785, "6480.0": 2.2558063612162291, "1080.0": 0.38969247867806489, "15120.0": 7.2125210995495488, "12960.0": 6.4014182164755429, "19440.0": 6.8128450220608308, "10800.0": 4.2649851849420299}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"0.0": 300.53274252012955, "4320.0": 311.15339067777529, "7560.0": 312.18867239090878, "21600.0": 346.00646319366473, "9720.0": 313.61710665630221, "16200.0": 352.81825567952984, "20520.0": 346.66248325950249, "3240.0": 311.37928873928001, "11880.0": 343.17457873193757, "14040.0": 352.88609842940627, "17280.0": 348.48519596719899, "2160.0": 312.70676562686674, "5400.0": 314.29841143358993, "18360.0": 347.81967845794014, "8640.0": 313.26528616120316, "6480.0": 314.33539425367189, "1080.0": 308.35955588183077, "15120.0": 350.62179387183005, "12960.0": 348.72513776404708, "19440.0": 346.65341312375318, "10800.0": 318.79995964600641}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp13.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp13.out new file mode 100644 index 00000000000..6ef514c951a --- /dev/null +++ b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp13.out @@ -0,0 +1 @@ +{"experiment": 13, "Ca0": 0, "Ca_meas": {"0.0": 0.0, "4320.0": 2.9015294551493156, "7560.0": 3.7456240714596114, "21600.0": 1.0893473942847287, "9720.0": 3.955661759695563, "16200.0": 1.3896514099314119, "20520.0": 1.1200835239496501, "3240.0": 2.4116751003091821, "11880.0": 3.4212519616089123, "14040.0": 1.9331934810250495, "17280.0": 1.2772682671606199, "2160.0": 1.7821342925311514, "5400.0": 3.2743367747379883, "18360.0": 1.2065189956942144, "8640.0": 3.8765800278544793, "6480.0": 3.5499054339456388, "1080.0": 0.98643229677676525, "15120.0": 1.583391359829623, "12960.0": 2.5471212725882397, "19440.0": 1.1574522594063252, "10800.0": 3.9931029485836738}, "alphac": 0.7, "Fa2": 0.001, "deltaH1": -40000, "Fa1": 0.001, "Tc2": 340, "Cc0": 0, "Tc1": 310, "Cc_meas": {"0.0": 0.0, "4320.0": 0.025294213033754839, "7560.0": 0.12897041691694733, "21600.0": 6.5640983670402253, "9720.0": 0.2583989163781088, "16200.0": 3.2753828012649455, "20520.0": 5.9325635171128175, "3240.0": 0.01057908838025531, "11880.0": 0.60077017176350533, "14040.0": 1.8465767045073862, "17280.0": 3.9624456898675042, "2160.0": 0.0029862658207987017, "5400.0": 0.048985517519306479, "18360.0": 4.6327886545170172, "8640.0": 0.1872203610374778, "6480.0": 0.083160507067847278, "1080.0": 0.0003160950335645278, "15120.0": 2.5686484578016535, "12960.0": 1.149697840345306, "19440.0": 5.2890172904133799, "10800.0": 0.3428648031290083}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"0.0": 0.0, "4320.0": 0.98451200269614558, "7560.0": 2.5380689634524152, "21600.0": 6.993912112938939, "9720.0": 3.7076982498372795, "16200.0": 7.2015026943578233, "20520.0": 7.0686210754667576, "3240.0": 0.58059897990470211, "11880.0": 5.3029094739876195, "14040.0": 6.8563104174907483, "17280.0": 7.2147803682393352, "2160.0": 0.26601120814969509, "5400.0": 1.4570157171523839, "18360.0": 7.1863518790131433, "8640.0": 3.1176409818767401, "6480.0": 1.9800832092825005, "1080.0": 0.06510061057296454, "15120.0": 7.1087300866267373, "12960.0": 6.294429516811606, "19440.0": 7.1344955737885876, "10800.0": 4.2996805767976616}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"0.0": 300.0, "4320.0": 312.83405362164399, "7560.0": 314.10857941130064, "21600.0": 347.58975942376372, "9720.0": 314.61233912755387, "16200.0": 349.56203914424219, "20520.0": 347.79317623855678, "3240.0": 312.2016171592112, "11880.0": 344.43453363173575, "14040.0": 351.72789167129031, "17280.0": 348.83759825667926, "2160.0": 311.27622091507072, "5400.0": 313.34232236658977, "18360.0": 348.36453268948424, "8640.0": 314.38892058203726, "6480.0": 313.76278431242508, "1080.0": 309.11491437259622, "15120.0": 350.61679816631096, "12960.0": 351.27269989378158, "19440.0": 348.03901542922108, "10800.0": 318.0555419842903}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp14.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp14.out new file mode 100644 index 00000000000..cc3f95da860 --- /dev/null +++ b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp14.out @@ -0,0 +1 @@ +{"experiment": 14, "Ca0": 0, "Ca_meas": {"0.0": 0.081520066870384211, "4320.0": 2.8337428652268013, "7560.0": 3.9530051428610888, "21600.0": 1.1807185641508762, "9720.0": 4.0236480913821637, "16200.0": 1.4376034521825525, "20520.0": 1.5413859123725682, "3240.0": 2.7181227248994633, "11880.0": 3.3903617547506242, "14040.0": 2.0159168723544196, "17280.0": 1.0638207528750085, "2160.0": 1.8959521419461316, "5400.0": 2.9705863021885124, "18360.0": 1.0674705545585839, "8640.0": 4.144391992823846, "6480.0": 3.5918471023904477, "1080.0": 1.0113708479421195, "15120.0": 1.6541373379275581, "12960.0": 2.6476139688086877, "19440.0": 1.2312633238777129, "10800.0": 3.8743606114727154}, "alphac": 0.7, "Fa2": 0.001, "deltaH1": -40000, "Fa1": 0.001, "Tc2": 340, "Cc0": 0, "Tc1": 310, "Cc_meas": {"0.0": -0.089503442052066576, "4320.0": 0.18208896954493076, "7560.0": 0.17697219964055824, "21600.0": 6.8780014679710426, "9720.0": 0.35693081777790492, "16200.0": 3.1977597178279002, "20520.0": 6.1361982627818481, "3240.0": 0.10972700122863582, "11880.0": 0.76070080263615369, "14040.0": 1.5671921543009262, "17280.0": 4.0972632572434122, "2160.0": -0.28322371444225319, "5400.0": -0.079382269802482266, "18360.0": 4.5706426776745905, "8640.0": 0.26888423813019369, "6480.0": 0.1979519809989283, "1080.0": 0.13979150229071807, "15120.0": 2.6867100129129424, "12960.0": 0.99472739483139283, "19440.0": 5.6554142424694902, "10800.0": 0.46709816548507599}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"0.0": 0.1139198104669558, "4320.0": 0.99036932502879282, "7560.0": 2.4258194495364482, "21600.0": 7.0446273994379434, "9720.0": 3.7506516413639113, "16200.0": 6.9192013751011503, "20520.0": 7.1555299371654808, "3240.0": 0.66230857796133746, "11880.0": 5.0716652323781499, "14040.0": 7.0971130388695007, "17280.0": 7.4091470358534082, "2160.0": -0.039078609338807413, "5400.0": 1.480378464133409, "18360.0": 7.1741052031883399, "8640.0": 3.4996110541636019, "6480.0": 2.0450173775271647, "1080.0": 0.13728827557251419, "15120.0": 6.7382150794212539, "12960.0": 6.3936782268937753, "19440.0": 7.3298178049407321, "10800.0": 4.6925893428035463}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"0.0": 301.43741888919669, "4320.0": 313.81215912108536, "7560.0": 313.22398065446896, "21600.0": 347.95979639817102, "9720.0": 316.38480427739029, "16200.0": 350.46267406552954, "20520.0": 349.96840569755773, "3240.0": 313.32903259260996, "11880.0": 345.61089172333249, "14040.0": 352.83446396320579, "17280.0": 347.23320123776085, "2160.0": 310.02897702317114, "5400.0": 314.70707924235739, "18360.0": 349.8524737596988, "8640.0": 314.13917383134913, "6480.0": 314.15959183349207, "1080.0": 307.97982604287193, "15120.0": 349.00176197155969, "12960.0": 350.41651244789142, "19440.0": 346.1591726550746, "10800.0": 317.90588794308121}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp2.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp2.out new file mode 100644 index 00000000000..e5245dff3f0 --- /dev/null +++ b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp2.out @@ -0,0 +1 @@ +{"experiment": 2, "Ca0": 0, "Ca_meas": {"12960.0": 2.6833775561963225, "21600.0": 1.1452663167259416, "17280.0": 1.2021595324165524, "19440.0": 1.0621392247792012, "0.0": -0.1316904398446574, "7560.0": 3.544605362880795, "11880.0": 3.1817551426501267, "14040.0": 2.066815570405579, "4320.0": 3.0488618589043432, "15120.0": 1.5896211475539537, "1080.0": 0.8608182979507091, "18360.0": 1.1317484585922248, "8640.0": 3.454602822099547, "2160.0": 1.7479951078246254, "20520.0": 1.2966191801491087, "9720.0": 4.0596636929917285, "6480.0": 3.9085446597134283, "3240.0": 2.5050366860794875, "5400.0": 3.2668528110981576, "10800.0": 4.004828727345138, "16200.0": 1.2860720212507326}, "alphac": 0.7, "Fa2": 0.001, "deltaH1": -40000, "Fa1": 0.001, "Tc2": 340, "Cc0": 0, "Tc1": 310, "Cc_meas": {"12960.0": 1.3318254182147888, "21600.0": 6.431089735352747, "17280.0": 3.9711701719678825, "19440.0": 5.321278981143728, "0.0": 0.10325037628575211, "7560.0": 0.10827803632010198, "11880.0": 0.5184920846420157, "14040.0": 1.7974496302186054, "4320.0": 0.03112694971654564, "15120.0": 2.5245584142423207, "1080.0": 0.27315169217241275, "18360.0": 4.587420104936772, "8640.0": 0.1841080751926184, "2160.0": -0.3018405210283593, "20520.0": 6.0086124912796794, "9720.0": 0.08100716578409362, "6480.0": 0.027897809479352567, "3240.0": 0.01973928836607919, "5400.0": 0.02694434057766582, "10800.0": 0.3021977838614568, "16200.0": 3.561159267372319}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"12960.0": 6.358162874770003, "21600.0": 7.190121324797683, "17280.0": 7.302887809357959, "19440.0": 7.249179120248633, "0.0": -0.2300456054526237, "7560.0": 2.5654112157969364, "11880.0": 5.3061363321784025, "14040.0": 6.84009925060659, "4320.0": 0.9644475554073758, "15120.0": 7.261439274435592, "1080.0": 0.2644823572584467, "18360.0": 7.3015136544611305, "8640.0": 3.065189548359326, "2160.0": 0.27122113581760515, "20520.0": 7.162520282520871, "9720.0": 3.701445814227159, "6480.0": 2.0255650533089735, "3240.0": 0.48891328422133334, "5400.0": 1.326135697891304, "10800.0": 4.031252283597449, "16200.0": 7.38249778574694}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"12960.0": 350.10381529626676, "21600.0": 345.04247547236935, "17280.0": 348.24859807524, "19440.0": 346.7076647696431, "0.0": 301.30135373385, "7560.0": 314.7140350191631, "11880.0": 343.608488145403, "14040.0": 352.2902404657956, "4320.0": 312.2349716351422, "15120.0": 351.28330527232634, "1080.0": 307.67178366332996, "18360.0": 347.3517733033304, "8640.0": 313.62143853358026, "2160.0": 310.2674222024707, "20520.0": 348.1662021459614, "9720.0": 314.8081875940964, "6480.0": 312.7396303616638, "3240.0": 310.9258419605079, "5400.0": 312.8448509580561, "10800.0": 317.33866255037736, "16200.0": 349.5494112095972}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp3.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp3.out new file mode 100644 index 00000000000..a9b013d476f --- /dev/null +++ b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp3.out @@ -0,0 +1 @@ +{"experiment": 3, "Ca0": 0, "Ca_meas": {"17280.0": 0.39426558381033217, "0.0": -0.25761950998771116, "7560.0": 7.638659217862309, "9720.0": 7.741721088143662, "19440.0": 0.12706787288438182, "16200.0": 0.15060928317089423, "11880.0": 4.534965302380243, "2160.0": 4.47101661859487, "8640.0": 7.562734803826617, "14040.0": 0.8456407304976143, "4320.0": 7.395023123698018, "6480.0": 7.952409415603349, "1080.0": 3.0448009666821947, "18360.0": 0.0754742404427045, "10800.0": 7.223420802364689, "21600.0": 0.181946676125186, "20520.0": 0.195256504023462, "5400.0": 7.844394030136843, "3240.0": 6.031994466757849, "12960.0": 1.813573590958129, "15120.0": 0.30408071219857113}, "alphac": 0.7, "Fa2": 0.0, "deltaH1": -40000, "Fa1": 0.003, "Tc2": 340, "Cc0": 0, "Tc1": 310, "Cc_meas": {"17280.0": 10.587758291638856, "0.0": -0.21796531802425348, "7560.0": 0.8680716299725404, "9720.0": 0.9085250272598128, "19440.0": 11.914154788848554, "16200.0": 9.737618534040658, "11880.0": 2.0705302921286064, "2160.0": -0.022903632391514165, "8640.0": 0.5195918959805059, "14040.0": 6.395605356788582, "4320.0": 0.010107836996695638, "6480.0": 0.09956884355869228, "1080.0": -0.21178603534213003, "18360.0": 10.990013628196317, "10800.0": 1.345982231414325, "21600.0": 12.771296192955955, "20520.0": 12.196513048345082, "5400.0": 0.04790148745481311, "3240.0": 0.318546588876358, "12960.0": 4.693433882970767, "15120.0": 8.491695125145553}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"17280.0": 8.731932027503914, "0.0": -0.1375816812387338, "7560.0": 6.8484762842626985, "9720.0": 9.333087689786101, "19440.0": 7.381577268709603, "16200.0": 9.502048821225666, "11880.0": 12.406853134672218, "2160.0": 0.8107944776900446, "8640.0": 8.158484571318013, "14040.0": 11.54445651179274, "4320.0": 2.8119825114954082, "6480.0": 5.520857819630275, "1080.0": 0.18414413253133835, "18360.0": 8.145712620219781, "10800.0": 10.75765409121092, "21600.0": 6.1356948865706356, "20520.0": 6.576247039355788, "5400.0": 3.92591568907661, "3240.0": 2.015632242014947, "12960.0": 12.71320139030468, "15120.0": 10.314039809497785}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"17280.0": 345.7556825478521, "0.0": 300.1125978749429, "7560.0": 319.7301093967675, "9720.0": 321.4474947517745, "19440.0": 345.1982881134514, "16200.0": 348.50691612993586, "11880.0": 357.2800598685144, "2160.0": 311.7652063627056, "8640.0": 323.6663990115871, "14040.0": 365.2497105829804, "4320.0": 317.1702037696461, "6480.0": 319.9056594806601, "1080.0": 309.6187369359568, "18360.0": 345.1427626681205, "10800.0": 323.9154289387584, "21600.0": 345.45022165546357, "20520.0": 344.5991879566869, "5400.0": 316.21958050333416, "3240.0": 314.95895736530616, "12960.0": 371.5669986462856, "15120.0": 355.3612054360245}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp4.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp4.out new file mode 100644 index 00000000000..e702db7d05b --- /dev/null +++ b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp4.out @@ -0,0 +1 @@ +{"experiment": 4, "Ca0": 0, "Ca_meas": {"17280.0": 1.1816674202534045, "0.0": -0.25414468591124584, "7560.0": 5.914582094251343, "9720.0": 5.185067133371561, "19440.0": 0.5307435290768995, "16200.0": 1.2011215994628408, "11880.0": 3.5778183914967925, "2160.0": 4.254440534150475, "8640.0": 5.473440610227645, "14040.0": 2.1475894664278354, "4320.0": 5.8795707148110266, "6480.0": 6.089523479429854, "1080.0": 2.4339586418303543, "18360.0": 0.545228377126232, "10800.0": 4.946406396626746, "21600.0": 0.3169450590438124, "20520.0": 0.5859997070045333, "5400.0": 6.15901928205937, "3240.0": 5.5559094344993225, "12960.0": 2.476561130612629, "15120.0": 1.35232620260846}, "alphac": 0.7, "Fa2": 0.0, "deltaH1": -40000, "Fa1": 0.003, "Tc2": 320, "Cc0": 0, "Tc1": 320, "Cc_meas": {"17280.0": 6.529641678005382, "0.0": 0.23057916607631007, "7560.0": 1.4209973582182187, "9720.0": 2.662002861314475, "19440.0": 7.612677904908142, "16200.0": 6.236740105453746, "11880.0": 3.6132373498432813, "2160.0": 0.41778377045750303, "8640.0": 2.3031169482702336, "14040.0": 4.857027337132376, "4320.0": 0.21846387467958883, "6480.0": 1.304912741118713, "1080.0": -0.002497120213976349, "18360.0": 7.207560113722179, "10800.0": 3.072350404943197, "21600.0": 8.437070128901182, "20520.0": 7.985790844633096, "5400.0": 0.5552902218354748, "3240.0": 0.4207298617922146, "12960.0": 4.791797355546968, "15120.0": 5.544868662346418}, "alphaj": 0.8, "Cb0": 2, "Vr0": 1, "Cb_meas": {"17280.0": 13.559468310792148, "0.0": 2.1040937779048963, "7560.0": 9.809117250733637, "9720.0": 12.404875593478181, "19440.0": 12.616477055699818, "16200.0": 13.94157106495499, "11880.0": 13.828382570964388, "2160.0": 3.1373485614417618, "8640.0": 11.053587506450443, "14040.0": 14.267859106799012, "4320.0": 5.6037726942190424, "6480.0": 8.499893646580981, "1080.0": 2.2930856900001535, "18360.0": 13.566545045105496, "10800.0": 13.397445458860116, "21600.0": 12.347983697813623, "20520.0": 12.422291796055749, "5400.0": 7.367727360896684, "3240.0": 3.8542518870239824, "12960.0": 14.038139660530316, "15120.0": 14.114308276615096}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"17280.0": 324.4465785902298, "0.0": 299.6733512720839, "7560.0": 333.04897592093835, "9720.0": 334.48916083151335, "19440.0": 323.5321646227951, "16200.0": 326.84528144052564, "11880.0": 332.77528830002086, "2160.0": 322.2453586799791, "8640.0": 334.2512423463752, "14040.0": 329.05431569837447, "4320.0": 327.99896899102527, "6480.0": 334.3262547857335, "1080.0": 317.32350372398014, "18360.0": 324.97573570445866, "10800.0": 334.23107235994195, "21600.0": 323.6424061352077, "20520.0": 324.00045995355015, "5400.0": 331.45112696032044, "3240.0": 326.33322784448785, "12960.0": 330.4778004752374, "15120.0": 326.8776604963411}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp5.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp5.out new file mode 100644 index 00000000000..6c4b1b1d9e0 --- /dev/null +++ b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp5.out @@ -0,0 +1 @@ +{"experiment": 5, "Ca0": 0, "Ca_meas": {"17280.0": 0.5384747196579402, "0.0": -0.14396911833443257, "7560.0": 6.038385982663388, "9720.0": 5.0792329539506, "19440.0": 0.3782801126758533, "16200.0": 1.0619887309834395, "11880.0": 3.6494330494296436, "2160.0": 4.775401751873804, "8640.0": 5.629577532845656, "14040.0": 2.0037718871692265, "4320.0": 5.889802624055117, "6480.0": 6.09724817816528, "1080.0": 2.875851853145854, "18360.0": 0.6780066197547887, "10800.0": 4.859469684381779, "21600.0": 0.3889400954173796, "20520.0": 0.3351378562274788, "5400.0": 6.127222815180268, "3240.0": 5.289726682847115, "12960.0": 2.830845316709853, "15120.0": 1.5312992911111707}, "alphac": 0.7, "Fa2": 0.0, "deltaH1": -40000, "Fa1": 0.003, "Tc2": 320, "Cc0": 2, "Tc1": 320, "Cc_meas": {"17280.0": 7.596710556194337, "0.0": 1.6504080367065743, "7560.0": 2.809410585275859, "9720.0": 3.8419183958835132, "19440.0": 8.137782633931637, "16200.0": 6.938967086259325, "11880.0": 5.022162589071362, "2160.0": 2.0515545922033964, "8640.0": 3.506455726732785, "14040.0": 6.010539749263416, "4320.0": 2.2056993474658584, "6480.0": 2.5775763528099858, "1080.0": 2.03522693402577, "18360.0": 8.083917616781594, "10800.0": 4.662851778068136, "21600.0": 9.279674687903626, "20520.0": 8.963676424956157, "5400.0": 2.293408505844697, "3240.0": 1.9216270432789067, "12960.0": 5.637375563057352, "15120.0": 6.3296720972633045}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"17280.0": 12.831256801432378, "0.0": 0.13194621122245662, "7560.0": 7.965534934436229, "9720.0": 10.908595985103954, "19440.0": 12.408596390398941, "16200.0": 12.975405069340143, "11880.0": 12.710800046234393, "2160.0": 0.9223242691530996, "8640.0": 9.454601468197033, "14040.0": 13.19437793062601, "4320.0": 3.713168763161746, "6480.0": 6.515936097446724, "1080.0": 0.031354105110323494, "18360.0": 12.821094923672087, "10800.0": 11.90520078370877, "21600.0": 11.81429953673305, "20520.0": 12.099271866573613, "5400.0": 4.982941965055916, "3240.0": 2.766378581935415, "12960.0": 13.074621618364043, "15120.0": 12.957819319226212}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"17280.0": 326.8759279818479, "0.0": 300.7736745288814, "7560.0": 333.6674031533901, "9720.0": 333.59854139744135, "19440.0": 324.5264772316974, "16200.0": 325.2454701315101, "11880.0": 332.9849253092768, "2160.0": 322.1940607068012, "8640.0": 331.78378240085084, "14040.0": 328.48981010099453, "4320.0": 327.3883510651506, "6480.0": 330.15101610436426, "1080.0": 318.24994073025096, "18360.0": 323.9527212120804, "10800.0": 333.3006916263996, "21600.0": 322.07065855783964, "20520.0": 324.3518907083261, "5400.0": 331.5429008148077, "3240.0": 324.52116111644654, "12960.0": 329.21899337854876, "15120.0": 328.26179934031467}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp6.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp6.out new file mode 100644 index 00000000000..c1630902e1a --- /dev/null +++ b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp6.out @@ -0,0 +1 @@ +{"experiment": 6, "Ca0": 2, "Ca_meas": {"17280.0": 1.1595382970987758, "0.0": 1.9187666718004224, "7560.0": 5.977461039170304, "9720.0": 5.164472215594892, "19440.0": 0.6528636977624275, "16200.0": 1.2106046606700225, "11880.0": 3.3229659243191296, "2160.0": 5.923887627906124, "8640.0": 5.225477003110976, "14040.0": 1.7878931129582107, "4320.0": 6.782806717544953, "6480.0": 6.27323507174512, "1080.0": 4.481633914987097, "18360.0": 0.8866911582721309, "10800.0": 4.481150474336123, "21600.0": 0.2170007972283953, "20520.0": 0.3199825651255196, "5400.0": 6.795886093698936, "3240.0": 6.288606047308427, "12960.0": 2.4509424000990685, "15120.0": 1.506568611506372}, "alphac": 0.7, "Fa2": 0.0, "deltaH1": -40000, "Fa1": 0.003, "Tc2": 320, "Cc0": 0, "Tc1": 320, "Cc_meas": {"17280.0": 6.588306395438309, "0.0": 0.24402401145820712, "7560.0": 1.4036889138374646, "9720.0": 2.4218855673847455, "19440.0": 7.764492558630308, "16200.0": 6.205315919403138, "11880.0": 3.593219427441702, "2160.0": -0.10553376629311664, "8640.0": 1.8628128392103824, "14040.0": 5.027532358914124, "4320.0": 0.2172961549286831, "6480.0": 0.875637414228913, "1080.0": 0.2688503672636328, "18360.0": 7.240866507350995, "10800.0": 3.26514503365032, "21600.0": 8.251445433411781, "20520.0": 7.987953548408583, "5400.0": 0.6458428001299884, "3240.0": 0.3201498579399834, "12960.0": 4.263491165240245, "15120.0": 5.885223860529403}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"17280.0": 13.604962664333257, "0.0": -0.20747860874385013, "7560.0": 10.013416230907982, "9720.0": 12.055665770517061, "19440.0": 13.289064414490856, "16200.0": 13.82240671329648, "11880.0": 13.783622629658064, "2160.0": 1.8287780310400052, "8640.0": 11.17018006067254, "14040.0": 14.064985893141849, "4320.0": 5.072613174963567, "6480.0": 8.597613514631933, "1080.0": 0.3932885074677299, "18360.0": 13.473844971871975, "10800.0": 13.343451941795923, "21600.0": 12.209822751574375, "20520.0": 12.483108442400093, "5400.0": 6.7290370118557545, "3240.0": 3.4163314305947527, "12960.0": 14.296996898861073, "15120.0": 14.543019390786785}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"17280.0": 324.3952638382081, "0.0": 300.9417777707309, "7560.0": 334.57634369064954, "9720.0": 336.13718804612154, "19440.0": 324.10564173791454, "16200.0": 324.97714743647435, "11880.0": 332.29384802281055, "2160.0": 324.243456129639, "8640.0": 335.85007440436317, "14040.0": 329.01187645109906, "4320.0": 331.1961476255781, "6480.0": 333.16262386818596, "1080.0": 319.0632107387995, "18360.0": 322.11836267923206, "10800.0": 332.8894634515628, "21600.0": 323.92451205164855, "20520.0": 323.319714630304, "5400.0": 334.21206737651613, "3240.0": 326.78695915581983, "12960.0": 329.6184998003745, "15120.0": 327.5414299857002}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp7.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp7.out new file mode 100644 index 00000000000..6ef879f3a17 --- /dev/null +++ b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp7.out @@ -0,0 +1 @@ +{"experiment": 7, "Ca0": 0, "Ca_meas": {"0.0": 0.0, "4320.0": 5.9967662103693256, "7560.0": 5.6550872327951165, "21600.0": 0.35323003565444244, "9720.0": 5.0380179697260221, "16200.0": 1.1574854069611118, "20520.0": 0.44526599556762764, "3240.0": 5.5465851989526014, "11880.0": 3.4091089779405226, "14040.0": 1.9309662288658835, "17280.0": 0.90614590071077117, "2160.0": 4.5361731979596218, "5400.0": 6.071094394377246, "18360.0": 0.71270371205373184, "8640.0": 5.3475100856285955, "6480.0": 5.9202343949662177, "1080.0": 2.7532264453848811, "15120.0": 1.4880794860689583, "12960.0": 2.5406275798785134, "19440.0": 0.56254756816886675, "10800.0": 4.6684283481238458}, "alphac": 0.7, "Fa2": 0.0, "deltaH1": -40000, "Fa1": 0.003, "Tc2": 320, "Cc0": 0, "Tc1": 320, "Cc_meas": {"0.0": 9.1835496157991212e-40, "4320.0": 0.2042152487619561, "7560.0": 1.0742319748668527, "21600.0": 7.2209534629193319, "9720.0": 2.0351470130435847, "16200.0": 5.2015141957639486, "20520.0": 6.8469618370195322, "3240.0": 0.080409363629683248, "11880.0": 3.1846111102658314, "14040.0": 4.26052274570326, "17280.0": 5.6380747523602874, "2160.0": 0.020484309410223334, "5400.0": 0.4082391087574655, "18360.0": 6.0566679041163232, "8640.0": 1.5236474138282798, "6480.0": 0.69922443474303053, "1080.0": 0.0017732304596560309, "15120.0": 4.7439891962421239, "12960.0": 3.7433194976361364, "19440.0": 6.4591935065135972, "10800.0": 2.5966732389812686}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"0.0": 9.1835496157991212e-41, "4320.0": 3.7902671698338786, "7560.0": 8.430643849127673, "21600.0": 11.611715650093123, "9720.0": 10.913795239302978, "16200.0": 12.826899545942249, "20520.0": 11.893671316079821, "3240.0": 2.2947643625752363, "11880.0": 12.592179060461286, "14040.0": 12.994410174098332, "17280.0": 12.641678495596169, "2160.0": 1.0564916422363797, "5400.0": 5.3872034016274126, "18360.0": 12.416527532497087, "8640.0": 9.7522120688862319, "6480.0": 6.9615062931011256, "1080.0": 0.24785349222969222, "15120.0": 12.953830466356314, "12960.0": 12.901952071152909, "19440.0": 12.164158073984597, "10800.0": 11.920797561562614}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"0.0": 300.0, "4320.0": 329.97137612867306, "7560.0": 333.16333833254259, "21600.0": 323.31193212521009, "9720.0": 333.2600929343854, "16200.0": 325.39648330671537, "20520.0": 323.56691057280642, "3240.0": 327.66452370998064, "11880.0": 331.65035718435655, "14040.0": 327.56747174535786, "17280.0": 324.74920150215411, "2160.0": 324.4585751379426, "5400.0": 331.60125794337375, "18360.0": 324.25971411725646, "8640.0": 333.33010300559897, "6480.0": 332.63089408099353, "1080.0": 318.87878296714581, "15120.0": 326.2893083756407, "12960.0": 329.38909200530219, "19440.0": 323.87612174726837, "10800.0": 333.08705569007822}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp8.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp8.out new file mode 100644 index 00000000000..6aa9fea17b3 --- /dev/null +++ b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp8.out @@ -0,0 +1 @@ +{"experiment": 8, "Ca0": 0, "Ca_meas": {"0.0": 0.30862088766711671, "4320.0": 6.0491110491659228, "7560.0": 5.7909310485601502, "21600.0": 0.232444399226299, "9720.0": 4.9320449060797475, "16200.0": 0.97134242753331668, "20520.0": 0.42847724332841963, "3240.0": 5.6988320807198498, "11880.0": 3.3235733576868514, "14040.0": 1.9846460628194049, "17280.0": 0.87715206210722585, "2160.0": 4.615346351863904, "5400.0": 6.384056703029386, "18360.0": 0.41688144324552118, "8640.0": 5.4121173109702099, "6480.0": 6.0660731346226324, "1080.0": 2.8379509025410488, "15120.0": 0.98831570466285279, "12960.0": 2.2167483934357417, "19440.0": 0.46284950985984985, "10800.0": 4.7377220491627412}, "alphac": 0.7, "Fa2": 0.0, "deltaH1": -40000, "Fa1": 0.003, "Tc2": 320, "Cc0": 0, "Tc1": 320, "Cc_meas": {"0.0": -0.050833820035522316, "4320.0": 0.21055066883154505, "7560.0": 1.3851246436045646, "21600.0": 7.3672985895890362, "9720.0": 2.0100502709379842, "16200.0": 5.1793087406376159, "20520.0": 6.840381847429823, "3240.0": 0.13411276227648503, "11880.0": 3.3052152545385454, "14040.0": 3.9431305823279708, "17280.0": 5.7290141848801586, "2160.0": 0.16719633749951002, "5400.0": 0.49872603502453117, "18360.0": 6.1508540969551078, "8640.0": 1.4737312345987361, "6480.0": 0.69437977126769512, "1080.0": -0.0093978134715377304, "15120.0": 4.9151661032041298, "12960.0": 4.0623539766149843, "19440.0": 6.3058400571478561, "10800.0": 2.820347355873587}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"0.0": 0.037166728983599892, "4320.0": 3.5183932586092856, "7560.0": 8.916682434428397, "21600.0": 11.534104657089006, "9720.0": 11.087958791247285, "16200.0": 13.02597376112109, "20520.0": 11.639999923137731, "3240.0": 2.346958004261503, "11880.0": 12.049613604010537, "14040.0": 12.906738918465997, "17280.0": 12.867691165140879, "2160.0": 1.3313958783841602, "5400.0": 5.3650409213472221, "18360.0": 12.405763004965722, "8640.0": 9.5635832344445717, "6480.0": 7.0954049721214671, "1080.0": 0.40883709280782765, "15120.0": 12.971506554625082, "12960.0": 12.829158718434032, "19440.0": 11.946615137583075, "10800.0": 11.373799750334223}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"0.0": 300.55141264078583, "4320.0": 329.42497918063066, "7560.0": 333.82602046942475, "21600.0": 323.68642879192487, "9720.0": 332.94208820576767, "16200.0": 325.82299128298814, "20520.0": 325.19753703643721, "3240.0": 329.66504941755875, "11880.0": 332.29546982118751, "14040.0": 326.51837436850099, "17280.0": 326.51851506890586, "2160.0": 323.70134945698589, "5400.0": 328.6805843225718, "18360.0": 324.79832692054578, "8640.0": 331.94068007914785, "6480.0": 332.75141896044545, "1080.0": 318.90722718736015, "15120.0": 325.85289150843209, "12960.0": 327.72250161440121, "19440.0": 325.17198606848808, "10800.0": 334.1255807822717}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp9.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp9.out new file mode 100644 index 00000000000..627f92b1f83 --- /dev/null +++ b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp9.out @@ -0,0 +1 @@ +{"experiment": 9, "Ca0": 0, "Ca_meas": {"0.0": 0.0, "4320.0": 5.9967662103693256, "7560.0": 5.6550872327951165, "21600.0": 0.35323003565444244, "9720.0": 5.0380179697260221, "16200.0": 1.1574854069611118, "20520.0": 0.44526599556762764, "3240.0": 5.5465851989526014, "11880.0": 3.4091089779405226, "14040.0": 1.9309662288658835, "17280.0": 0.90614590071077117, "2160.0": 4.5361731979596218, "5400.0": 6.071094394377246, "18360.0": 0.71270371205373184, "8640.0": 5.3475100856285955, "6480.0": 5.9202343949662177, "1080.0": 2.7532264453848811, "15120.0": 1.4880794860689583, "12960.0": 2.5406275798785134, "19440.0": 0.56254756816886675, "10800.0": 4.6684283481238458}, "alphac": 0.7, "Fa2": 0.0, "deltaH1": -40000, "Fa1": 0.003, "Tc2": 320, "Cc0": 0, "Tc1": 320, "Cc_meas": {"0.0": 9.1835496157991212e-40, "4320.0": 0.2042152487619561, "7560.0": 1.0742319748668527, "21600.0": 7.2209534629193319, "9720.0": 2.0351470130435847, "16200.0": 5.2015141957639486, "20520.0": 6.8469618370195322, "3240.0": 0.080409363629683248, "11880.0": 3.1846111102658314, "14040.0": 4.26052274570326, "17280.0": 5.6380747523602874, "2160.0": 0.020484309410223334, "5400.0": 0.4082391087574655, "18360.0": 6.0566679041163232, "8640.0": 1.5236474138282798, "6480.0": 0.69922443474303053, "1080.0": 0.0017732304596560309, "15120.0": 4.7439891962421239, "12960.0": 3.7433194976361364, "19440.0": 6.4591935065135972, "10800.0": 2.5966732389812686}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"0.0": 9.1835496157991212e-41, "4320.0": 3.7902671698338786, "7560.0": 8.430643849127673, "21600.0": 11.611715650093123, "9720.0": 10.913795239302978, "16200.0": 12.826899545942249, "20520.0": 11.893671316079821, "3240.0": 2.2947643625752363, "11880.0": 12.592179060461286, "14040.0": 12.994410174098332, "17280.0": 12.641678495596169, "2160.0": 1.0564916422363797, "5400.0": 5.3872034016274126, "18360.0": 12.416527532497087, "8640.0": 9.7522120688862319, "6480.0": 6.9615062931011256, "1080.0": 0.24785349222969222, "15120.0": 12.953830466356314, "12960.0": 12.901952071152909, "19440.0": 12.164158073984597, "10800.0": 11.920797561562614}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"0.0": 300.0, "4320.0": 329.97137612867306, "7560.0": 333.16333833254259, "21600.0": 323.31193212521009, "9720.0": 333.2600929343854, "16200.0": 325.39648330671537, "20520.0": 323.56691057280642, "3240.0": 327.66452370998064, "11880.0": 331.65035718435655, "14040.0": 327.56747174535786, "17280.0": 324.74920150215411, "2160.0": 324.4585751379426, "5400.0": 331.60125794337375, "18360.0": 324.25971411725646, "8640.0": 333.33010300559897, "6480.0": 332.63089408099353, "1080.0": 318.87878296714581, "15120.0": 326.2893083756407, "12960.0": 329.38909200530219, "19440.0": 323.87612174726837, "10800.0": 333.08705569007822}} \ No newline at end of file From 07b612a1ef1396b831a35ad18d86faacc67a16d0 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 20 Feb 2024 07:13:16 -0700 Subject: [PATCH 1138/1797] Removed group_data from parmest documentation. --- doc/OnlineDocs/contributed_packages/parmest/driver.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/OnlineDocs/contributed_packages/parmest/driver.rst b/doc/OnlineDocs/contributed_packages/parmest/driver.rst index 28238928b83..e8d2fcd44e5 100644 --- a/doc/OnlineDocs/contributed_packages/parmest/driver.rst +++ b/doc/OnlineDocs/contributed_packages/parmest/driver.rst @@ -42,7 +42,6 @@ results, and fit distributions to theta values. .. autosummary:: :nosignatures: - ~pyomo.contrib.parmest.parmest.group_data ~pyomo.contrib.parmest.graphics.pairwise_plot ~pyomo.contrib.parmest.graphics.grouped_boxplot ~pyomo.contrib.parmest.graphics.grouped_violinplot From ff5077b202bcdccf8d4175d8e0afdcce6812aabd Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 20 Feb 2024 08:34:36 -0700 Subject: [PATCH 1139/1797] Fixed parmest documentation to use examples with new UI. --- .../contributed_packages/parmest/datarec.rst | 13 ++++---- .../contributed_packages/parmest/driver.rst | 30 +++++++++++++------ 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/doc/OnlineDocs/contributed_packages/parmest/datarec.rst b/doc/OnlineDocs/contributed_packages/parmest/datarec.rst index 6b721377e46..a3ece17190c 100644 --- a/doc/OnlineDocs/contributed_packages/parmest/datarec.rst +++ b/doc/OnlineDocs/contributed_packages/parmest/datarec.rst @@ -38,9 +38,7 @@ is the response function that is defined in the model file): >>> import pandas as pd >>> import pyomo.contrib.parmest.parmest as parmest - >>> from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import rooney_biegler_model - - >>> theta_names = ['asymptote', 'rate_constant'] + >>> from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import RooneyBieglerExperiment >>> data = pd.DataFrame(data=[[1,8.3],[2,10.3],[3,19.0], ... [4,16.0],[5,15.6],[7,19.8]], @@ -51,8 +49,13 @@ is the response function that is defined in the model file): ... - model.response_function[data.hour[i]])**2 for i in data.index) ... return expr - >>> pest = parmest.Estimator(rooney_biegler_model, data, theta_names, SSE, - ... solver_options=None) + >>> def SSE(model): + ... expr = (model.experiment_outputs[model.y] + ... - model.response_function[model.experiment_outputs[model.hour]] + ... ) ** 2 + return expr + + >>> pest = parmest.Estimator(exp_list, obj_function=SSE, solver_options=None) >>> obj, theta, var_values = pest.theta_est(return_values=['response_function']) >>> #print(var_values) diff --git a/doc/OnlineDocs/contributed_packages/parmest/driver.rst b/doc/OnlineDocs/contributed_packages/parmest/driver.rst index e8d2fcd44e5..45533e9520c 100644 --- a/doc/OnlineDocs/contributed_packages/parmest/driver.rst +++ b/doc/OnlineDocs/contributed_packages/parmest/driver.rst @@ -57,21 +57,33 @@ Section. .. testsetup:: * :skipif: not __import__('pyomo.contrib.parmest.parmest').contrib.parmest.parmest.parmest_available + # Data import pandas as pd - from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import rooney_biegler_model as model_function - data = pd.DataFrame(data=[[1,8.3],[2,10.3],[3,19.0], - [4,16.0],[5,15.6],[6,19.8]], - columns=['hour', 'y']) - theta_names = ['asymptote', 'rate_constant'] - def objective_function(model, data): - expr = sum((data.y[i] - model.response_function[data.hour[i]])**2 for i in data.index) + 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'], + ) + + # Sum of squared error function + def SSE(model): + expr = ( + model.experiment_outputs[model.y] + - model.response_function[model.experiment_outputs[model.hour]] + ) ** 2 return expr + # Create an experiment list + from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import RooneyBieglerExperiment + exp_list = [] + for i in range(data.shape[0]): + exp_list.append(RooneyBieglerExperiment(data.loc[i, :])) + .. doctest:: :skipif: not __import__('pyomo.contrib.parmest.parmest').contrib.parmest.parmest.parmest_available >>> import pyomo.contrib.parmest.parmest as parmest - >>> pest = parmest.Estimator(model_function, data, theta_names, objective_function) + >>> pest = parmest.Estimator(exp_list, obj_function=SSE) Optionally, solver options can be supplied, e.g., @@ -79,7 +91,7 @@ Optionally, solver options can be supplied, e.g., :skipif: not __import__('pyomo.contrib.parmest.parmest').contrib.parmest.parmest.parmest_available >>> solver_options = {"max_iter": 6000} - >>> pest = parmest.Estimator(model_function, data, theta_names, objective_function, solver_options) + >>> pest = parmest.Estimator(exp_list, obj_function=SSE, solver_options=solver_options) From abbda5d91cf895e9342fd6891bc8508d1daa9921 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 20 Feb 2024 09:03:10 -0700 Subject: [PATCH 1140/1797] Fixed parmest datarec documentation for new UI. --- .../contributed_packages/parmest/datarec.rst | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/doc/OnlineDocs/contributed_packages/parmest/datarec.rst b/doc/OnlineDocs/contributed_packages/parmest/datarec.rst index a3ece17190c..6e9be904286 100644 --- a/doc/OnlineDocs/contributed_packages/parmest/datarec.rst +++ b/doc/OnlineDocs/contributed_packages/parmest/datarec.rst @@ -40,20 +40,22 @@ is the response function that is defined in the model file): >>> import pyomo.contrib.parmest.parmest as parmest >>> from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import RooneyBieglerExperiment + >>> # Generate data >>> 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']) - >>> def SSE(model, data): - ... expr = sum((data.y[i]\ - ... - model.response_function[data.hour[i]])**2 for i in data.index) - ... return expr + >>> # Create an experiment list + >>> exp_list = [] + >>> for i in range(data.shape[0]): + ... exp_list.append(RooneyBieglerExperiment(data.loc[i, :])) + >>> # Define objective >>> def SSE(model): ... expr = (model.experiment_outputs[model.y] ... - model.response_function[model.experiment_outputs[model.hour]] ... ) ** 2 - return expr + ... return expr >>> pest = parmest.Estimator(exp_list, obj_function=SSE, solver_options=None) >>> obj, theta, var_values = pest.theta_est(return_values=['response_function']) From 54c3ab197a4aee657f5ea161991fd8277b04b033 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 20 Feb 2024 08:49:07 -0700 Subject: [PATCH 1141/1797] 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 1142/1797] 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 1143/1797] 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 1144/1797] 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 1145/1797] 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 1146/1797] 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 1147/1797] 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 1148/1797] 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 1149/1797] 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 1150/1797] 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 1151/1797] 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 1152/1797] 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 1153/1797] 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 1154/1797] 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 1155/1797] 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 1156/1797] 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 1157/1797] 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 1158/1797] 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 1159/1797] 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 1160/1797] 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 1161/1797] 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 1162/1797] 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 1163/1797] 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 1164/1797] 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 1165/1797] 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 1166/1797] 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 1167/1797] 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 1168/1797] 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 92163f2989dc99d4e14f63c658c0cc77e3d9dc7a Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 21 Feb 2024 09:15:41 -0700 Subject: [PATCH 1169/1797] cleanup --- .github/workflows/test_branches.yml | 2 +- .github/workflows/test_pr_and_main.yml | 2 +- pyomo/contrib/simplification/__init__.py | 11 +++++++++++ pyomo/contrib/simplification/build.py | 11 +++++++++++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 83e652cbef8..57c24d99090 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -157,7 +157,7 @@ jobs: # path: cache/os # key: pkg-${{env.CACHE_VER}}.0-${{runner.os}} - - name: install ginac + - name: install GiNaC if: matrix.other == '/singletest' run: | cd .. diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 6df28fbadc9..a55d100a18f 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -179,7 +179,7 @@ jobs: # path: cache/os # key: pkg-${{env.CACHE_VER}}.0-${{runner.os}} - - name: install ginac + - name: install GiNaC if: matrix.other == '/singletest' run: | cd .. diff --git a/pyomo/contrib/simplification/__init__.py b/pyomo/contrib/simplification/__init__.py index 3abe5a25ba0..b4fa68eb386 100644 --- a/pyomo/contrib/simplification/__init__.py +++ b/pyomo/contrib/simplification/__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 .simplify import Simplifier diff --git a/pyomo/contrib/simplification/build.py b/pyomo/contrib/simplification/build.py index 39742e1e351..67ae1d37335 100644 --- a/pyomo/contrib/simplification/build.py +++ b/pyomo/contrib/simplification/build.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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 pybind11.setup_helpers import Pybind11Extension, build_ext from pyomo.common.fileutils import this_file_dir, find_library import os From 0dff80fb37b9052805d744f42236711067d69bcc Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 21 Feb 2024 09:16:59 -0700 Subject: [PATCH 1170/1797] cleanup --- pyomo/contrib/simplification/build.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/simplification/build.py b/pyomo/contrib/simplification/build.py index 67ae1d37335..4bf28a0fa33 100644 --- a/pyomo/contrib/simplification/build.py +++ b/pyomo/contrib/simplification/build.py @@ -9,15 +9,16 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from pybind11.setup_helpers import Pybind11Extension, build_ext -from pyomo.common.fileutils import this_file_dir, find_library +import glob import os -from distutils.dist import Distribution -import sys import shutil -import glob +import sys import tempfile +from distutils.dist import Distribution + +from pybind11.setup_helpers import Pybind11Extension, build_ext from pyomo.common.envvar import PYOMO_CONFIG_DIR +from pyomo.common.fileutils import find_library, this_file_dir def build_ginac_interface(args=[]): From c49c2df810775f1c64702a26e3cf75c36f717c99 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 21 Feb 2024 09:23:10 -0700 Subject: [PATCH 1171/1797] cleanup --- pyomo/contrib/simplification/build.py | 11 ++++++----- pyomo/contrib/simplification/ginac_interface.cpp | 11 +++++++++++ pyomo/contrib/simplification/simplify.py | 11 +++++++++++ pyomo/contrib/simplification/tests/__init__.py | 11 +++++++++++ .../simplification/tests/test_simplification.py | 11 +++++++++++ 5 files changed, 50 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/simplification/build.py b/pyomo/contrib/simplification/build.py index 4bf28a0fa33..d9d1e701290 100644 --- a/pyomo/contrib/simplification/build.py +++ b/pyomo/contrib/simplification/build.py @@ -21,7 +21,9 @@ from pyomo.common.fileutils import find_library, this_file_dir -def build_ginac_interface(args=[]): +def build_ginac_interface(args=None): + if args is None: + args = list() dname = this_file_dir() _sources = ['ginac_interface.cpp'] sources = list() @@ -29,7 +31,6 @@ def build_ginac_interface(args=[]): sources.append(os.path.join(dname, fname)) ginac_lib = find_library('ginac') - print(ginac_lib) if ginac_lib is None: raise RuntimeError( 'could not find GiNaC library; please make sure it is in the LD_LIBRARY_PATH environment variable' @@ -62,7 +63,7 @@ def build_ginac_interface(args=[]): extra_compile_args=extra_args, ) - class ginac_build_ext(build_ext): + class ginacBuildExt(build_ext): def run(self): basedir = os.path.abspath(os.path.curdir) if self.inplace: @@ -72,7 +73,7 @@ def run(self): print("Building in '%s'" % tmpdir) os.chdir(tmpdir) try: - super(ginac_build_ext, self).run() + super(ginacBuildExt, self).run() if not self.inplace: library = glob.glob("build/*/ginac_interface.*")[0] target = os.path.join( @@ -94,7 +95,7 @@ def run(self): 'name': 'ginac_interface', 'packages': [], 'ext_modules': [ext], - 'cmdclass': {"build_ext": ginac_build_ext}, + 'cmdclass': {"build_ext": ginacBuildExt}, } dist = Distribution(package_config) diff --git a/pyomo/contrib/simplification/ginac_interface.cpp b/pyomo/contrib/simplification/ginac_interface.cpp index 32bea8dadd0..489f281bc2c 100644 --- a/pyomo/contrib/simplification/ginac_interface.cpp +++ b/pyomo/contrib/simplification/ginac_interface.cpp @@ -1,3 +1,14 @@ +// ___________________________________________________________________________ +// +// Pyomo: Python Optimization Modeling Objects +// Copyright (c) 2008-2022 +// National Technology and Engineering Solutions of Sandia, LLC +// Under the terms of Contract DE-NA0003525 with National Technology and +// Engineering 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 "ginac_interface.hpp" diff --git a/pyomo/contrib/simplification/simplify.py b/pyomo/contrib/simplification/simplify.py index 4002f1a233f..b8cc4995f91 100644 --- a/pyomo/contrib/simplification/simplify.py +++ b/pyomo/contrib/simplification/simplify.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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.sympy_tools import sympy2pyomo_expression, sympyify_expression from pyomo.core.expr.numeric_expr import NumericExpression from pyomo.core.expr.numvalue import is_fixed, value diff --git a/pyomo/contrib/simplification/tests/__init__.py b/pyomo/contrib/simplification/tests/__init__.py index e69de29bb2d..9320e403e95 100644 --- a/pyomo/contrib/simplification/tests/__init__.py +++ b/pyomo/contrib/simplification/tests/__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/simplification/tests/test_simplification.py b/pyomo/contrib/simplification/tests/test_simplification.py index e3c60cb02ca..95402f98318 100644 --- a/pyomo/contrib/simplification/tests/test_simplification.py +++ b/pyomo/contrib/simplification/tests/test_simplification.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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.unittest import TestCase from pyomo.common import unittest from pyomo.contrib.simplification import Simplifier From 29d6a19d0f1704a294d62d5a89370d6110a4fbe7 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 21 Feb 2024 09:29:35 -0700 Subject: [PATCH 1172/1797] cleanup --- pyomo/contrib/simplification/tests/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyomo/contrib/simplification/tests/__init__.py b/pyomo/contrib/simplification/tests/__init__.py index 9320e403e95..d93cfd77b3c 100644 --- a/pyomo/contrib/simplification/tests/__init__.py +++ b/pyomo/contrib/simplification/tests/__init__.py @@ -8,4 +8,3 @@ # rights in this software. # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ - From 3e5cca27025a04d09ecf938433a9afb3534d501b Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 21 Feb 2024 09:30:29 -0700 Subject: [PATCH 1173/1797] 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 1174/1797] 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 1175/1797] 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 1176/1797] 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 1177/1797] 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 1178/1797] 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 1179/1797] 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 1180/1797] 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 1181/1797] 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 1182/1797] 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 1183/1797] 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 1184/1797] 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 04f68dcefedde1cab5b502778504e4189a647308 Mon Sep 17 00:00:00 2001 From: Shawn Martin Date: Wed, 21 Feb 2024 13:56:25 -0700 Subject: [PATCH 1185/1797] Integrated deprecated parmest UI into new UI. Removed deprecated folder. --- pyomo/contrib/parmest/deprecated/__init__.py | 10 - .../parmest/deprecated/examples/__init__.py | 10 - .../examples/reaction_kinetics/__init__.py | 10 - .../simple_reaction_parmest_example.py | 118 -- .../examples/reactor_design/__init__.py | 10 - .../reactor_design/bootstrap_example.py | 60 - .../reactor_design/datarec_example.py | 100 -- .../reactor_design/leaveNout_example.py | 98 -- .../likelihood_ratio_example.py | 64 - .../multisensor_data_example.py | 51 - .../parameter_estimation_example.py | 60 - .../examples/reactor_design/reactor_data.csv | 20 - .../reactor_data_multisensor.csv | 20 - .../reactor_data_timeseries.csv | 20 - .../examples/reactor_design/reactor_design.py | 104 -- .../reactor_design/timeseries_data_example.py | 56 - .../examples/rooney_biegler/__init__.py | 10 - .../rooney_biegler/bootstrap_example.py | 57 - .../likelihood_ratio_example.py | 62 - .../parameter_estimation_example.py | 60 - .../examples/rooney_biegler/rooney_biegler.py | 60 - .../rooney_biegler_with_constraint.py | 63 - .../deprecated/examples/semibatch/__init__.py | 10 - .../examples/semibatch/bootstrap_theta.csv | 101 -- .../deprecated/examples/semibatch/exp1.out | 1 - .../deprecated/examples/semibatch/exp10.out | 1 - .../deprecated/examples/semibatch/exp11.out | 1 - .../deprecated/examples/semibatch/exp12.out | 1 - .../deprecated/examples/semibatch/exp13.out | 1 - .../deprecated/examples/semibatch/exp14.out | 1 - .../deprecated/examples/semibatch/exp2.out | 1 - .../deprecated/examples/semibatch/exp3.out | 1 - .../deprecated/examples/semibatch/exp4.out | 1 - .../deprecated/examples/semibatch/exp5.out | 1 - .../deprecated/examples/semibatch/exp6.out | 1 - .../deprecated/examples/semibatch/exp7.out | 1 - .../deprecated/examples/semibatch/exp8.out | 1 - .../deprecated/examples/semibatch/exp9.out | 1 - .../examples/semibatch/obj_at_theta.csv | 1009 ------------ .../examples/semibatch/parallel_example.py | 57 - .../semibatch/parameter_estimation_example.py | 42 - .../examples/semibatch/scenario_example.py | 52 - .../examples/semibatch/scenarios.csv | 11 - .../examples/semibatch/semibatch.py | 287 ---- pyomo/contrib/parmest/deprecated/parmest.py | 1361 ----------------- .../parmest/deprecated/scenariocreator.py | 166 -- .../parmest/deprecated/tests/__init__.py | 10 - .../parmest/deprecated/tests/scenarios.csv | 11 - .../parmest/deprecated/tests/test_examples.py | 204 --- .../parmest/deprecated/tests/test_graphics.py | 68 - .../parmest/deprecated/tests/test_parmest.py | 956 ------------ .../deprecated/tests/test_scenariocreator.py | 146 -- .../parmest/deprecated/tests/test_solver.py | 75 - .../parmest/deprecated/tests/test_utils.py | 68 - pyomo/contrib/parmest/parmest.py | 1125 +++++++++++++- pyomo/contrib/parmest/scenariocreator.py | 74 +- pyomo/contrib/parmest/tests/test_parmest.py | 1048 ++++++++++++- .../parmest/tests/test_scenariocreator.py | 448 ++++++ 58 files changed, 2674 insertions(+), 5792 deletions(-) delete mode 100644 pyomo/contrib/parmest/deprecated/__init__.py delete mode 100644 pyomo/contrib/parmest/deprecated/examples/__init__.py delete mode 100644 pyomo/contrib/parmest/deprecated/examples/reaction_kinetics/__init__.py delete mode 100644 pyomo/contrib/parmest/deprecated/examples/reaction_kinetics/simple_reaction_parmest_example.py delete mode 100644 pyomo/contrib/parmest/deprecated/examples/reactor_design/__init__.py delete mode 100644 pyomo/contrib/parmest/deprecated/examples/reactor_design/bootstrap_example.py delete mode 100644 pyomo/contrib/parmest/deprecated/examples/reactor_design/datarec_example.py delete mode 100644 pyomo/contrib/parmest/deprecated/examples/reactor_design/leaveNout_example.py delete mode 100644 pyomo/contrib/parmest/deprecated/examples/reactor_design/likelihood_ratio_example.py delete mode 100644 pyomo/contrib/parmest/deprecated/examples/reactor_design/multisensor_data_example.py delete mode 100644 pyomo/contrib/parmest/deprecated/examples/reactor_design/parameter_estimation_example.py delete mode 100644 pyomo/contrib/parmest/deprecated/examples/reactor_design/reactor_data.csv delete mode 100644 pyomo/contrib/parmest/deprecated/examples/reactor_design/reactor_data_multisensor.csv delete mode 100644 pyomo/contrib/parmest/deprecated/examples/reactor_design/reactor_data_timeseries.csv delete mode 100644 pyomo/contrib/parmest/deprecated/examples/reactor_design/reactor_design.py delete mode 100644 pyomo/contrib/parmest/deprecated/examples/reactor_design/timeseries_data_example.py delete mode 100644 pyomo/contrib/parmest/deprecated/examples/rooney_biegler/__init__.py delete mode 100644 pyomo/contrib/parmest/deprecated/examples/rooney_biegler/bootstrap_example.py delete mode 100644 pyomo/contrib/parmest/deprecated/examples/rooney_biegler/likelihood_ratio_example.py delete mode 100644 pyomo/contrib/parmest/deprecated/examples/rooney_biegler/parameter_estimation_example.py delete mode 100644 pyomo/contrib/parmest/deprecated/examples/rooney_biegler/rooney_biegler.py delete mode 100644 pyomo/contrib/parmest/deprecated/examples/rooney_biegler/rooney_biegler_with_constraint.py delete mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/__init__.py delete mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/bootstrap_theta.csv delete mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp1.out delete mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp10.out delete mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp11.out delete mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp12.out delete mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp13.out delete mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp14.out delete mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp2.out delete mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp3.out delete mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp4.out delete mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp5.out delete mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp6.out delete mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp7.out delete mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp8.out delete mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/exp9.out delete mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/obj_at_theta.csv delete mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/parallel_example.py delete mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/parameter_estimation_example.py delete mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/scenario_example.py delete mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/scenarios.csv delete mode 100644 pyomo/contrib/parmest/deprecated/examples/semibatch/semibatch.py delete mode 100644 pyomo/contrib/parmest/deprecated/parmest.py delete mode 100644 pyomo/contrib/parmest/deprecated/scenariocreator.py delete mode 100644 pyomo/contrib/parmest/deprecated/tests/__init__.py delete mode 100644 pyomo/contrib/parmest/deprecated/tests/scenarios.csv delete mode 100644 pyomo/contrib/parmest/deprecated/tests/test_examples.py delete mode 100644 pyomo/contrib/parmest/deprecated/tests/test_graphics.py delete mode 100644 pyomo/contrib/parmest/deprecated/tests/test_parmest.py delete mode 100644 pyomo/contrib/parmest/deprecated/tests/test_scenariocreator.py delete mode 100644 pyomo/contrib/parmest/deprecated/tests/test_solver.py delete mode 100644 pyomo/contrib/parmest/deprecated/tests/test_utils.py diff --git a/pyomo/contrib/parmest/deprecated/__init__.py b/pyomo/contrib/parmest/deprecated/__init__.py deleted file mode 100644 index d93cfd77b3c..00000000000 --- a/pyomo/contrib/parmest/deprecated/__init__.py +++ /dev/null @@ -1,10 +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. -# ___________________________________________________________________________ diff --git a/pyomo/contrib/parmest/deprecated/examples/__init__.py b/pyomo/contrib/parmest/deprecated/examples/__init__.py deleted file mode 100644 index d93cfd77b3c..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/__init__.py +++ /dev/null @@ -1,10 +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. -# ___________________________________________________________________________ diff --git a/pyomo/contrib/parmest/deprecated/examples/reaction_kinetics/__init__.py b/pyomo/contrib/parmest/deprecated/examples/reaction_kinetics/__init__.py deleted file mode 100644 index d93cfd77b3c..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/reaction_kinetics/__init__.py +++ /dev/null @@ -1,10 +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. -# ___________________________________________________________________________ diff --git a/pyomo/contrib/parmest/deprecated/examples/reaction_kinetics/simple_reaction_parmest_example.py b/pyomo/contrib/parmest/deprecated/examples/reaction_kinetics/simple_reaction_parmest_example.py deleted file mode 100644 index 719a930251c..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/reaction_kinetics/simple_reaction_parmest_example.py +++ /dev/null @@ -1,118 +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. -# ___________________________________________________________________________ -''' -Example from Y. Bard, "Nonlinear Parameter Estimation", (pg. 124) - -This example shows: -1. How to define the unknown (to be regressed parameters) with an index -2. How to call parmest to only estimate some of the parameters (and fix the rest) - -Code provided by Paul Akula. -''' - -from pyomo.environ import ( - ConcreteModel, - Param, - Var, - PositiveReals, - Objective, - Constraint, - RangeSet, - Expression, - minimize, - exp, - value, -) -import pyomo.contrib.parmest.parmest as parmest - - -def simple_reaction_model(data): - # Create the concrete model - model = ConcreteModel() - - model.x1 = Param(initialize=float(data['x1'])) - model.x2 = Param(initialize=float(data['x2'])) - - # Rate constants - model.rxn = RangeSet(2) - initial_guess = {1: 750, 2: 1200} - model.k = Var(model.rxn, initialize=initial_guess, within=PositiveReals) - - # reaction product - model.y = Expression(expr=exp(-model.k[1] * model.x1 * exp(-model.k[2] / model.x2))) - - # fix all of the regressed parameters - model.k.fix() - - # =================================================================== - # Stage-specific cost computations - def ComputeFirstStageCost_rule(model): - return 0 - - model.FirstStageCost = Expression(rule=ComputeFirstStageCost_rule) - - def AllMeasurements(m): - return (float(data['y']) - m.y) ** 2 - - model.SecondStageCost = Expression(rule=AllMeasurements) - - def total_cost_rule(m): - return m.FirstStageCost + m.SecondStageCost - - model.Total_Cost_Objective = Objective(rule=total_cost_rule, sense=minimize) - - return model - - -def main(): - # Data from Table 5.2 in Y. Bard, "Nonlinear Parameter Estimation", (pg. 124) - data = [ - {'experiment': 1, 'x1': 0.1, 'x2': 100, 'y': 0.98}, - {'experiment': 2, 'x1': 0.2, 'x2': 100, 'y': 0.983}, - {'experiment': 3, 'x1': 0.3, 'x2': 100, 'y': 0.955}, - {'experiment': 4, 'x1': 0.4, 'x2': 100, 'y': 0.979}, - {'experiment': 5, 'x1': 0.5, 'x2': 100, 'y': 0.993}, - {'experiment': 6, 'x1': 0.05, 'x2': 200, 'y': 0.626}, - {'experiment': 7, 'x1': 0.1, 'x2': 200, 'y': 0.544}, - {'experiment': 8, 'x1': 0.15, 'x2': 200, 'y': 0.455}, - {'experiment': 9, 'x1': 0.2, 'x2': 200, 'y': 0.225}, - {'experiment': 10, 'x1': 0.25, 'x2': 200, 'y': 0.167}, - {'experiment': 11, 'x1': 0.02, 'x2': 300, 'y': 0.566}, - {'experiment': 12, 'x1': 0.04, 'x2': 300, 'y': 0.317}, - {'experiment': 13, 'x1': 0.06, 'x2': 300, 'y': 0.034}, - {'experiment': 14, 'x1': 0.08, 'x2': 300, 'y': 0.016}, - {'experiment': 15, 'x1': 0.1, 'x2': 300, 'y': 0.006}, - ] - - # ======================================================================= - # Parameter estimation without covariance estimate - # Only estimate the parameter k[1]. The parameter k[2] will remain fixed - # at its initial value - theta_names = ['k[1]'] - pest = parmest.Estimator(simple_reaction_model, data, theta_names) - obj, theta = pest.theta_est() - print(obj) - print(theta) - print() - - # ======================================================================= - # Estimate both k1 and k2 and compute the covariance matrix - theta_names = ['k'] - pest = parmest.Estimator(simple_reaction_model, data, theta_names) - n = 15 # total number of data points used in the objective (y in 15 scenarios) - obj, theta, cov = pest.theta_est(calc_cov=True, cov_n=n) - print(obj) - print(theta) - print(cov) - - -if __name__ == "__main__": - main() diff --git a/pyomo/contrib/parmest/deprecated/examples/reactor_design/__init__.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/__init__.py deleted file mode 100644 index d93cfd77b3c..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/reactor_design/__init__.py +++ /dev/null @@ -1,10 +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. -# ___________________________________________________________________________ diff --git a/pyomo/contrib/parmest/deprecated/examples/reactor_design/bootstrap_example.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/bootstrap_example.py deleted file mode 100644 index 3820b78c9b1..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/reactor_design/bootstrap_example.py +++ /dev/null @@ -1,60 +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. -# ___________________________________________________________________________ - -import pandas as pd -from os.path import join, abspath, dirname -import pyomo.contrib.parmest.parmest as parmest -from pyomo.contrib.parmest.deprecated.examples.reactor_design.reactor_design import ( - reactor_design_model, -) - - -def main(): - # Vars to estimate - theta_names = ["k1", "k2", "k3"] - - # Data - file_dirname = dirname(abspath(str(__file__))) - 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 - ) - return expr - - # Create an instance of the parmest estimator - pest = parmest.Estimator(reactor_design_model, data, theta_names, SSE) - - # Parameter estimation - obj, theta = pest.theta_est() - - # Parameter estimation with bootstrap resampling - bootstrap_theta = pest.theta_est_bootstrap(50) - - # Plot results - 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", - ) - - -if __name__ == "__main__": - main() diff --git a/pyomo/contrib/parmest/deprecated/examples/reactor_design/datarec_example.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/datarec_example.py deleted file mode 100644 index bae538f364c..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/reactor_design/datarec_example.py +++ /dev/null @@ -1,100 +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. -# ___________________________________________________________________________ - -import numpy as np -import pandas as pd -import pyomo.contrib.parmest.parmest as parmest -from pyomo.contrib.parmest.deprecated.examples.reactor_design.reactor_design import ( - reactor_design_model, -) - -np.random.seed(1234) - - -def reactor_design_model_for_datarec(data): - # Unfix inlet concentration for data rec - model = reactor_design_model(data) - model.caf.fixed = False - - return model - - -def generate_data(): - ### Generate data based on real sv, caf, ca, cb, cc, and cd - sv_real = 1.05 - caf_real = 10000 - ca_real = 3458.4 - cb_real = 1060.8 - cc_real = 1683.9 - cd_real = 1898.5 - - data = pd.DataFrame() - ndata = 200 - # Normal distribution, mean = 3400, std = 500 - data["ca"] = 500 * np.random.randn(ndata) + 3400 - # Random distribution between 500 and 1500 - data["cb"] = np.random.rand(ndata) * 1000 + 500 - # Lognormal distribution - 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["sv"] = sv_real - data["caf"] = caf_real - - return data - - -def main(): - # Generate data - data = generate_data() - data_std = data.std() - - # 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 - ) - return expr - - ### Data reconciliation - theta_names = [] # no variables to estimate, use initialized values - - 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"]) - print(obj) - print(theta) - - parmest.graphics.grouped_boxplot( - 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"] - - 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} - print(theta_real) - - -if __name__ == "__main__": - main() diff --git a/pyomo/contrib/parmest/deprecated/examples/reactor_design/leaveNout_example.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/leaveNout_example.py deleted file mode 100644 index d4ca9651753..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/reactor_design/leaveNout_example.py +++ /dev/null @@ -1,98 +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. -# ___________________________________________________________________________ - -import numpy as np -import pandas as pd -from os.path import join, abspath, dirname -import pyomo.contrib.parmest.parmest as parmest -from pyomo.contrib.parmest.deprecated.examples.reactor_design.reactor_design import ( - reactor_design_model, -) - - -def main(): - # Vars to estimate - theta_names = ["k1", "k2", "k3"] - - # Data - file_dirname = dirname(abspath(str(__file__))) - file_name = abspath(join(file_dirname, "reactor_data.csv")) - data = pd.read_csv(file_name) - - # Create more data for the example - N = 50 - df_std = data.std().to_frame().transpose() - df_rand = pd.DataFrame(np.random.normal(size=N)) - df_sample = data.sample(N, replace=True).reset_index(drop=True) - data = df_sample + df_rand.dot(df_std) / 10 - - # 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 - ) - return expr - - # Create an instance of the parmest estimator - pest = parmest.Estimator(reactor_design_model, data, theta_names, SSE) - - # Parameter estimation - obj, theta = pest.theta_est() - print(obj) - print(theta) - - ### Parameter estimation with 'leave-N-out' - # Example use case: For each combination of data where one data point is left - # out, estimate theta - lNo_theta = pest.theta_est_leaveNout(1) - print(lNo_theta.head()) - - parmest.graphics.pairwise_plot(lNo_theta, theta) - - ### Leave one out/boostrap analysis - # Example use case: leave 25 data points out, run 20 bootstrap samples with the - # remaining points, determine if the theta estimate using the points left out - # is inside or outside an alpha region based on the bootstrap samples, repeat - # 5 times. Results are stored as a list of tuples, see API docs for information. - lNo = 25 - lNo_samples = 5 - bootstrap_samples = 20 - dist = "MVN" - alphas = [0.7, 0.8, 0.9] - - results = pest.leaveNout_bootstrap_test( - lNo, lNo_samples, bootstrap_samples, dist, alphas, seed=524 - ) - - # Plot results for a single value of alpha - alpha = 0.8 - for i in range(lNo_samples): - theta_est_N = results[i][1] - bootstrap_results = results[i][2] - parmest.graphics.pairwise_plot( - bootstrap_results, - theta_est_N, - alpha, - ["MVN"], - title="Alpha: " + str(alpha) + ", " + str(theta_est_N.loc[0, alpha]), - ) - - # Extract the percent of points that are within the alpha region - r = [results[i][1].loc[0, alpha] for i in range(lNo_samples)] - percent_true = sum(r) / len(r) - print(percent_true) - - -if __name__ == "__main__": - main() diff --git a/pyomo/contrib/parmest/deprecated/examples/reactor_design/likelihood_ratio_example.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/likelihood_ratio_example.py deleted file mode 100644 index c47acf7f932..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/reactor_design/likelihood_ratio_example.py +++ /dev/null @@ -1,64 +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. -# ___________________________________________________________________________ - -import numpy as np -import pandas as pd -from itertools import product -from os.path import join, abspath, dirname -import pyomo.contrib.parmest.parmest as parmest -from pyomo.contrib.parmest.deprecated.examples.reactor_design.reactor_design import ( - reactor_design_model, -) - - -def main(): - # Vars to estimate - theta_names = ["k1", "k2", "k3"] - - # Data - file_dirname = dirname(abspath(str(__file__))) - 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 - ) - return expr - - # Create an instance of the parmest estimator - pest = parmest.Estimator(reactor_design_model, data, theta_names, SSE) - - # Parameter estimation - obj, theta = pest.theta_est() - - # Find the objective value at each theta estimate - 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"]) - obj_at_theta = pest.objective_at_theta(theta_vals) - - # Run the likelihood ratio test - LR = pest.likelihood_ratio_test(obj_at_theta, obj, [0.8, 0.85, 0.9, 0.95]) - - # Plot results - parmest.graphics.pairwise_plot( - LR, theta, 0.9, title="LR results within 90% confidence region" - ) - - -if __name__ == "__main__": - main() diff --git a/pyomo/contrib/parmest/deprecated/examples/reactor_design/multisensor_data_example.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/multisensor_data_example.py deleted file mode 100644 index 84c4abdf92a..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/reactor_design/multisensor_data_example.py +++ /dev/null @@ -1,51 +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. -# ___________________________________________________________________________ - -import pandas as pd -from os.path import join, abspath, dirname -import pyomo.contrib.parmest.parmest as parmest -from pyomo.contrib.parmest.deprecated.examples.reactor_design.reactor_design import ( - reactor_design_model, -) - - -def main(): - # Parameter estimation using multisensor data - - # Vars to estimate - 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")) - 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 - ) - return expr - - pest = parmest.Estimator(reactor_design_model, data, theta_names, SSE_multisensor) - obj, theta = pest.theta_est() - print(obj) - print(theta) - - -if __name__ == "__main__": - main() diff --git a/pyomo/contrib/parmest/deprecated/examples/reactor_design/parameter_estimation_example.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/parameter_estimation_example.py deleted file mode 100644 index f5d9364097e..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/reactor_design/parameter_estimation_example.py +++ /dev/null @@ -1,60 +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. -# ___________________________________________________________________________ - -import pandas as pd -from os.path import join, abspath, dirname -import pyomo.contrib.parmest.parmest as parmest -from pyomo.contrib.parmest.deprecated.examples.reactor_design.reactor_design import ( - reactor_design_model, -) - - -def main(): - # Vars to estimate - theta_names = ["k1", "k2", "k3"] - - # Data - file_dirname = dirname(abspath(str(__file__))) - 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 - ) - return expr - - # Create an instance of the parmest estimator - pest = parmest.Estimator(reactor_design_model, data, theta_names, SSE) - - # Parameter estimation - obj, theta = pest.theta_est() - print(obj) - print(theta) - - # Assert statements compare parameter estimation (theta) to an expected value - 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 - assert relative_error < 0.05 - relative_error = abs(theta["k2"] - k2_expected) / k2_expected - assert relative_error < 0.05 - relative_error = abs(theta["k3"] - k3_expected) / k3_expected - assert relative_error < 0.05 - - -if __name__ == "__main__": - main() diff --git a/pyomo/contrib/parmest/deprecated/examples/reactor_design/reactor_data.csv b/pyomo/contrib/parmest/deprecated/examples/reactor_design/reactor_data.csv deleted file mode 100644 index c0695c049c4..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/reactor_design/reactor_data.csv +++ /dev/null @@ -1,20 +0,0 @@ -sv,caf,ca,cb,cc,cd -1.06,10010,3407.4,945.4,1717.1,1931.9 -1.11,10010,3631.6,1247.2,1694.1,1960.6 -1.16,10010,3645.3,971.4,1552.3,1898.8 -1.21,10002,3536.2,1225.9,1351.1,1757.0 -1.26,10002,3755.6,1263.8,1562.3,1952.2 -1.30,10007,3598.3,1153.4,1413.4,1903.3 -1.35,10007,3939.0,971.4,1416.9,1794.9 -1.41,10009,4227.9,986.3,1188.7,1821.5 -1.45,10001,4163.1,972.5,1085.6,1908.7 -1.50,10002,3896.3,977.3,1132.9,2080.5 -1.56,10004,3801.6,1040.6,1157.7,1780.0 -1.60,10008,4128.4,1198.6,1150.0,1581.9 -1.66,10002,4385.4,1158.7,970.0,1629.8 -1.70,10007,3960.8,1194.9,1091.2,1835.5 -1.76,10007,4180.8,1244.2,1034.8,1739.5 -1.80,10001,4212.3,1240.7,1010.3,1739.6 -1.85,10004,4200.2,1164.0,931.5,1783.7 -1.90,10009,4748.6,1037.9,1065.9,1685.6 -1.96,10009,4941.3,1038.5,996.0,1855.7 diff --git a/pyomo/contrib/parmest/deprecated/examples/reactor_design/reactor_data_multisensor.csv b/pyomo/contrib/parmest/deprecated/examples/reactor_design/reactor_data_multisensor.csv deleted file mode 100644 index 9df745a8422..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/reactor_design/reactor_data_multisensor.csv +++ /dev/null @@ -1,20 +0,0 @@ -sv,caf,ca1,ca2,ca3,cb,cc1,cc2,cd -1.06,10010,3407.4,3363.1,3759.1,945.4,1717.1,1695.1,1931.9 -1.11,10010,3631.6,3345.2,3906.0,1247.2,1694.1,1536.7,1960.6 -1.16,10010,3645.3,3784.9,3301.3,971.4,1552.3,1496.2,1898.8 -1.21,10002,3536.2,3718.3,3678.5,1225.9,1351.1,1549.7,1757.0 -1.26,10002,3755.6,3731.8,3854.7,1263.8,1562.3,1410.1,1952.2 -1.30,10007,3598.3,3751.6,3722.5,1153.4,1413.4,1291.6,1903.3 -1.35,10007,3939.0,3969.5,3827.2,971.4,1416.9,1276.8,1794.9 -1.41,10009,4227.9,3721.3,4046.7,986.3,1188.7,1221.0,1821.5 -1.45,10001,4163.1,4142.7,4512.1,972.5,1085.6,1212.1,1908.7 -1.50,10002,3896.3,3953.7,4028.0,977.3,1132.9,1167.7,2080.5 -1.56,10004,3801.6,4263.3,4015.3,1040.6,1157.7,1236.5,1780.0 -1.60,10008,4128.4,4061.1,3914.8,1198.6,1150.0,1032.2,1581.9 -1.66,10002,4385.4,4344.7,4006.8,1158.7,970.0,1155.1,1629.8 -1.70,10007,3960.8,4259.1,4274.7,1194.9,1091.2,958.6,1835.5 -1.76,10007,4180.8,4071.1,4598.7,1244.2,1034.8,1086.8,1739.5 -1.80,10001,4212.3,4541.8,4440.0,1240.7,1010.3,920.8,1739.6 -1.85,10004,4200.2,4444.9,4667.2,1164.0,931.5,850.7,1783.7 -1.90,10009,4748.6,4813.4,4753.2,1037.9,1065.9,898.5,1685.6 -1.96,10009,4941.3,4511.8,4405.4,1038.5,996.0,921.9,1855.7 diff --git a/pyomo/contrib/parmest/deprecated/examples/reactor_design/reactor_data_timeseries.csv b/pyomo/contrib/parmest/deprecated/examples/reactor_design/reactor_data_timeseries.csv deleted file mode 100644 index 1421cfef6a0..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/reactor_design/reactor_data_timeseries.csv +++ /dev/null @@ -1,20 +0,0 @@ -experiment,time,sv,caf,ca,cb,cc,cd -0,18000,1.075,10008,3537.5,1077.2,1591.2,1938.7 -0,18060,1.121,10002,3547.7,1186.2,1766.3,1946.9 -0,18120,1.095,10005,3614.4,1009.9,1702.9,1841.8 -0,18180,1.102,10007,3443.7,863.1,1666.2,1918.7 -0,18240,1.105,10002,3687.1,1052.1,1501.7,1905.0 -0,18300,1.084,10008,3452.7,1000.5,1512.0,2043.4 -1,18360,1.159,10009,3427.8,1133.1,1481.1,1837.1 -1,18420,1.432,10010,4029.8,1058.8,1213.0,1911.1 -1,18480,1.413,10005,3953.1,960.1,1304.8,1754.3 -1,18540,1.475,10008,4034.8,1121.2,1351.0,1992.0 -1,18600,1.433,10002,4029.8,1100.6,1199.5,1713.9 -1,18660,1.488,10006,3972.8,1148.0,1380.7,1992.1 -1,18720,1.456,10003,4031.2,1145.2,1133.1,1812.6 -2,18780,1.821,10008,4499.1,980.8,924.7,1840.9 -2,18840,1.856,10005,4370.9,1000.7,833.4,1848.4 -2,18900,1.846,10002,4438.6,1038.6,1042.8,1703.3 -2,18960,1.852,10002,4468.4,1151.8,1119.1,1564.8 -2,19020,1.865,10009,4341.6,1060.5,844.2,1974.8 -2,19080,1.872,10002,4427.0,964.6,840.2,1928.5 diff --git a/pyomo/contrib/parmest/deprecated/examples/reactor_design/reactor_design.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/reactor_design.py deleted file mode 100644 index 16f65e236eb..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/reactor_design/reactor_design.py +++ /dev/null @@ -1,104 +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. -# ___________________________________________________________________________ -""" -Continuously stirred tank reactor model, based on -pyomo/examples/doc/pyomobook/nonlinear-ch/react_design/ReactorDesign.py -""" -import pandas as pd -from pyomo.environ import ( - ConcreteModel, - Param, - Var, - PositiveReals, - Objective, - Constraint, - maximize, - SolverFactory, -) - - -def reactor_design_model(data): - # Create the concrete model - model = ConcreteModel() - - # Rate constants - model.k1 = Param(initialize=5.0 / 6.0, within=PositiveReals, mutable=True) # min^-1 - model.k2 = Param(initialize=5.0 / 3.0, within=PositiveReals, mutable=True) # min^-1 - model.k3 = Param( - initialize=1.0 / 6000.0, within=PositiveReals, mutable=True - ) # m^3/(gmol min) - - # 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) - 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) or isinstance(data, pd.Series): - 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) - 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) - model.cc = Var(initialize=2000.0, within=PositiveReals) - model.cd = Var(initialize=1000.0, within=PositiveReals) - - # Objective - model.obj = Objective(expr=model.cb, sense=maximize) - - # Constraints - model.ca_bal = Constraint( - expr=( - 0 - == model.sv * model.caf - - model.sv * model.ca - - model.k1 * model.ca - - 2.0 * model.k3 * model.ca**2.0 - ) - ) - - model.cb_bal = Constraint( - expr=(0 == -model.sv * model.cb + model.k1 * model.ca - model.k2 * model.cb) - ) - - model.cc_bal = Constraint(expr=(0 == -model.sv * model.cc + model.k2 * model.cb)) - - model.cd_bal = Constraint( - expr=(0 == -model.sv * model.cd + model.k3 * model.ca**2.0) - ) - - return model - - -def main(): - # For a range of sv values, return ca, cb, cc, and cd - results = [] - 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") - 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"]) - print(results) - - -if __name__ == "__main__": - main() diff --git a/pyomo/contrib/parmest/deprecated/examples/reactor_design/timeseries_data_example.py b/pyomo/contrib/parmest/deprecated/examples/reactor_design/timeseries_data_example.py deleted file mode 100644 index e7acefc2224..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/reactor_design/timeseries_data_example.py +++ /dev/null @@ -1,56 +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. -# ___________________________________________________________________________ - -import pandas as pd -from os.path import join, abspath, dirname - -import pyomo.contrib.parmest.parmest as parmest -from pyomo.contrib.parmest.deprecated.examples.reactor_design.reactor_design import ( - reactor_design_model, -) -from pyomo.contrib.parmest.deprecated.parmest import group_data - - -def main(): - # Parameter estimation using timeseries data - - # Vars to estimate - 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_timeseries.csv')) - data = pd.read_csv(file_name) - - # Group time series data into experiments, return the mean value for sv and caf - # Returns a list of dictionaries - data_ts = group_data(data, 'experiment', ['sv', 'caf']) - - def SSE_timeseries(model, data): - expr = 0 - for val in data['ca']: - expr = expr + ((float(val) - model.ca) ** 2) * (1 / len(data['ca'])) - for val in data['cb']: - expr = expr + ((float(val) - model.cb) ** 2) * (1 / len(data['cb'])) - for val in data['cc']: - expr = expr + ((float(val) - model.cc) ** 2) * (1 / len(data['cc'])) - for val in data['cd']: - expr = expr + ((float(val) - model.cd) ** 2) * (1 / len(data['cd'])) - return expr - - pest = parmest.Estimator(reactor_design_model, data_ts, theta_names, SSE_timeseries) - obj, theta = pest.theta_est() - print(obj) - print(theta) - - -if __name__ == "__main__": - main() diff --git a/pyomo/contrib/parmest/deprecated/examples/rooney_biegler/__init__.py b/pyomo/contrib/parmest/deprecated/examples/rooney_biegler/__init__.py deleted file mode 100644 index d93cfd77b3c..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/rooney_biegler/__init__.py +++ /dev/null @@ -1,10 +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. -# ___________________________________________________________________________ diff --git a/pyomo/contrib/parmest/deprecated/examples/rooney_biegler/bootstrap_example.py b/pyomo/contrib/parmest/deprecated/examples/rooney_biegler/bootstrap_example.py deleted file mode 100644 index f686bbd933d..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/rooney_biegler/bootstrap_example.py +++ /dev/null @@ -1,57 +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. -# ___________________________________________________________________________ - -import pandas as pd -import pyomo.contrib.parmest.parmest as parmest -from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import ( - rooney_biegler_model, -) - - -def main(): - # Vars to estimate - theta_names = ['asymptote', 'rate_constant'] - - # Data - 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'], - ) - - # Sum of squared error function - def SSE(model, data): - expr = sum( - (data.y[i] - model.response_function[data.hour[i]]) ** 2 for i in data.index - ) - return expr - - # Create an instance of the parmest estimator - pest = parmest.Estimator(rooney_biegler_model, data, theta_names, SSE) - - # Parameter estimation - obj, theta = pest.theta_est() - - # Parameter estimation with bootstrap resampling - bootstrap_theta = pest.theta_est_bootstrap(50, seed=4581) - - # Plot results - 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', - ) - - -if __name__ == "__main__": - main() diff --git a/pyomo/contrib/parmest/deprecated/examples/rooney_biegler/likelihood_ratio_example.py b/pyomo/contrib/parmest/deprecated/examples/rooney_biegler/likelihood_ratio_example.py deleted file mode 100644 index 5e54a33abda..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/rooney_biegler/likelihood_ratio_example.py +++ /dev/null @@ -1,62 +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. -# ___________________________________________________________________________ - -import numpy as np -import pandas as pd -from itertools import product -import pyomo.contrib.parmest.parmest as parmest -from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import ( - rooney_biegler_model, -) - - -def main(): - # Vars to estimate - theta_names = ['asymptote', 'rate_constant'] - - # Data - 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'], - ) - - # Sum of squared error function - def SSE(model, data): - expr = sum( - (data.y[i] - model.response_function[data.hour[i]]) ** 2 for i in data.index - ) - return expr - - # Create an instance of the parmest estimator - pest = parmest.Estimator(rooney_biegler_model, data, theta_names, SSE) - - # Parameter estimation - obj, theta = pest.theta_est() - - # Find the objective value at each theta estimate - asym = np.arange(10, 30, 2) - rate = np.arange(0, 1.5, 0.1) - theta_vals = pd.DataFrame( - list(product(asym, rate)), columns=['asymptote', 'rate_constant'] - ) - obj_at_theta = pest.objective_at_theta(theta_vals) - - # Run the likelihood ratio test - LR = pest.likelihood_ratio_test(obj_at_theta, obj, [0.8, 0.85, 0.9, 0.95]) - - # Plot results - parmest.graphics.pairwise_plot( - LR, theta, 0.8, title='LR results within 80% confidence region' - ) - - -if __name__ == "__main__": - main() diff --git a/pyomo/contrib/parmest/deprecated/examples/rooney_biegler/parameter_estimation_example.py b/pyomo/contrib/parmest/deprecated/examples/rooney_biegler/parameter_estimation_example.py deleted file mode 100644 index 9af33217fe4..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/rooney_biegler/parameter_estimation_example.py +++ /dev/null @@ -1,60 +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. -# ___________________________________________________________________________ - -import pandas as pd -import pyomo.contrib.parmest.parmest as parmest -from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import ( - rooney_biegler_model, -) - - -def main(): - # Vars to estimate - theta_names = ['asymptote', 'rate_constant'] - - # Data - 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'], - ) - - # Sum of squared error function - def SSE(model, data): - expr = sum( - (data.y[i] - model.response_function[data.hour[i]]) ** 2 for i in data.index - ) - return expr - - # Create an instance of the parmest estimator - pest = parmest.Estimator(rooney_biegler_model, data, theta_names, SSE) - - # Parameter estimation and covariance - n = 6 # total number of data points used in the objective (y in 6 scenarios) - obj, theta, cov = pest.theta_est(calc_cov=True, cov_n=n) - - # Plot theta estimates using a multivariate Gaussian distribution - parmest.graphics.pairwise_plot( - (theta, cov, 100), - theta_star=theta, - alpha=0.8, - distributions=['MVN'], - title='Theta estimates within 80% confidence region', - ) - - # Assert statements compare parameter estimation (theta) to an expected value - relative_error = abs(theta['asymptote'] - 19.1426) / 19.1426 - assert relative_error < 0.01 - relative_error = abs(theta['rate_constant'] - 0.5311) / 0.5311 - assert relative_error < 0.01 - - -if __name__ == "__main__": - main() diff --git a/pyomo/contrib/parmest/deprecated/examples/rooney_biegler/rooney_biegler.py b/pyomo/contrib/parmest/deprecated/examples/rooney_biegler/rooney_biegler.py deleted file mode 100644 index 5a0e1238e85..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/rooney_biegler/rooney_biegler.py +++ /dev/null @@ -1,60 +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. -# ___________________________________________________________________________ - -""" -Rooney Biegler model, based on Rooney, W. C. and Biegler, L. T. (2001). Design for -model parameter uncertainty using nonlinear confidence regions. AIChE Journal, -47(8), 1794-1804. -""" - -import pandas as pd -import pyomo.environ as pyo - - -def rooney_biegler_model(data): - model = pyo.ConcreteModel() - - model.asymptote = pyo.Var(initialize=15) - model.rate_constant = pyo.Var(initialize=0.5) - - def response_rule(m, h): - expr = m.asymptote * (1 - pyo.exp(-m.rate_constant * h)) - return expr - - model.response_function = pyo.Expression(data.hour, rule=response_rule) - - def SSE_rule(m): - return sum( - (data.y[i] - m.response_function[data.hour[i]]) ** 2 for i in data.index - ) - - model.SSE = pyo.Objective(rule=SSE_rule, sense=pyo.minimize) - - return model - - -def main(): - # These were taken from Table A1.4 in Bates and Watts (1988). - 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'], - ) - - model = rooney_biegler_model(data) - solver = pyo.SolverFactory('ipopt') - solver.solve(model) - - print('asymptote = ', model.asymptote()) - print('rate constant = ', model.rate_constant()) - - -if __name__ == '__main__': - main() diff --git a/pyomo/contrib/parmest/deprecated/examples/rooney_biegler/rooney_biegler_with_constraint.py b/pyomo/contrib/parmest/deprecated/examples/rooney_biegler/rooney_biegler_with_constraint.py deleted file mode 100644 index 2582e3fe928..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/rooney_biegler/rooney_biegler_with_constraint.py +++ /dev/null @@ -1,63 +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. -# ___________________________________________________________________________ - -""" -Rooney Biegler model, based on Rooney, W. C. and Biegler, L. T. (2001). Design for -model parameter uncertainty using nonlinear confidence regions. AIChE Journal, -47(8), 1794-1804. -""" - -import pandas as pd -import pyomo.environ as pyo - - -def rooney_biegler_model_with_constraint(data): - model = pyo.ConcreteModel() - - model.asymptote = pyo.Var(initialize=15) - model.rate_constant = pyo.Var(initialize=0.5) - model.response_function = pyo.Var(data.hour, initialize=0.0) - - # changed from expression to constraint - def response_rule(m, h): - return m.response_function[h] == m.asymptote * ( - 1 - pyo.exp(-m.rate_constant * h) - ) - - model.response_function_constraint = pyo.Constraint(data.hour, rule=response_rule) - - def SSE_rule(m): - return sum( - (data.y[i] - m.response_function[data.hour[i]]) ** 2 for i in data.index - ) - - model.SSE = pyo.Objective(rule=SSE_rule, sense=pyo.minimize) - - return model - - -def main(): - # These were taken from Table A1.4 in Bates and Watts (1988). - 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'], - ) - - model = rooney_biegler_model_with_constraint(data) - solver = pyo.SolverFactory('ipopt') - solver.solve(model) - - print('asymptote = ', model.asymptote()) - print('rate constant = ', model.rate_constant()) - - -if __name__ == '__main__': - main() diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/__init__.py b/pyomo/contrib/parmest/deprecated/examples/semibatch/__init__.py deleted file mode 100644 index d93cfd77b3c..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/semibatch/__init__.py +++ /dev/null @@ -1,10 +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. -# ___________________________________________________________________________ diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/bootstrap_theta.csv b/pyomo/contrib/parmest/deprecated/examples/semibatch/bootstrap_theta.csv deleted file mode 100644 index 29923a782c5..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/semibatch/bootstrap_theta.csv +++ /dev/null @@ -1,101 +0,0 @@ -,k1,k2,E1,E2 -0,23.8359813557911,149.99999125263844,31164.260824269295,41489.69422529956 -1,19.251987486659512,105.3374117880675,30505.86059307485,40516.897897740404 -2,19.31940450911214,105.78105886426505,30509.636888745794,40539.53548872927 -3,8.754357429283429,149.99988037658665,28334.500331107014,41482.01554893696 -4,23.016722464092286,80.03743984792878,31091.61503716734,39770.08415278276 -5,6.612337410520649,140.0259411600077,27521.46259880474,41302.159495413296 -6,14.29348509961158,147.0817016641302,29605.749859593245,41443.12009807534 -7,14.152069480386153,149.9914759675382,29676.633227079245,41483.41029455195 -8,19.081046896092914,125.55106106390114,30586.57857977985,41005.60243351924 -9,3.063566173952205,149.9999684548014,25473.079370305273,41483.426370389796 -10,17.79494440791066,108.52425726918327,30316.710830136202,40618.63715404914 -11,97.307579412204,149.99998972597675,35084.37589956093,41485.835559276136 -12,20.793042577945116,91.124365144131,30782.17494940993,40138.215713547994 -13,12.740540794730641,89.86327635412908,29396.65520336387,40086.14665722912 -14,6.930810780299319,149.99999327266906,27667.0240033497,41480.188987754496 -15,20.29404799567638,101.07539817765885,30697.087258737916,40443.316578889426 -16,85.77501788788223,149.99996482096984,34755.77375009206,41499.23448818336 -17,24.13150325243255,77.06876294766496,31222.03914354306,39658.418332258894 -18,16.026645517712712,149.99993094015056,30015.46332620076,41490.69892111652 -19,31.020018442708537,62.11558789585982,31971.311996398897,39089.828285017575 -20,20.815008037484656,87.35968459422139,30788.643843293,40007.78137819648 -21,19.007148519616447,96.44320176694993,30516.36933261116,40284.23312198372 -22,22.232021812057308,89.71692873746096,30956.252845068626,40095.009765519 -23,16.830765834427297,120.65209863104229,30139.92208332896,40912.673450399234 -24,15.274799190396566,129.82767733073857,29780.055282261117,41078.04749417758 -25,22.37343657709118,82.32861355430458,31013.57952062852,39853.06284595207 -26,9.055694749134819,149.99987339406314,28422.482259116612,41504.97564187301 -27,19.909770949417275,86.5634026379812,30705.60369894775,39996.134938503914 -28,20.604557306290886,87.96473948102359,30786.467003867263,40051.28176004557 -29,21.94101237923462,88.18216423767153,30942.372558158557,40051.20357069738 -30,3.200663718121338,149.99997712051055,25472.46099917771,41450.884180452646 -31,20.5812467558026,86.36098672832426,30802.74421085271,40010.76777825347 -32,18.776139793586893,108.99943042186453,30432.474809193136,40641.48011315501 -33,17.14246930769276,112.29370332257908,30164.332101438307,40684.867629869856 -34,20.52146255576043,99.7078140453859,30727.90573864389,40401.20730725967 -35,17.05073306185531,66.00385439687035,30257.075479935145,39247.26647870223 -36,7.1238843213074015,51.05163218895348,27811.250260416655,38521.11199236329 -37,10.54291332571747,76.74902426944477,28763.52244085013,39639.92644514267 -38,16.329028964122656,107.60037882134996,30073.5111433796,40592.825374177235 -39,18.0923131790489,107.75659679748213,30355.62290415686,40593.10521263782 -40,15.477264179087811,149.99995828085014,29948.62617372307,41490.770726165414 -41,23.190670255199933,76.5654091811839,31107.96477489951,39635.650879492074 -42,20.34720227734719,90.07051780196629,30716.131795936217,40096.932765428995 -43,23.60627359054596,80.0847207027996,31130.449736501876,39756.06693747353 -44,22.54968153535252,83.72995448206636,31038.51932262643,39906.60181934743 -45,24.951320839961582,67.97010976959977,31356.00147390564,39307.75709154711 -46,61.216667588824386,149.9999967830529,33730.22100500659,41474.80665231048 -47,9.797300324197744,136.33054557076974,28588.83540859912,41222.22413163186 -48,21.75078861615545,139.82641444329093,30894.847060525986,41290.16131583715 -49,21.76324066920255,99.57885291658233,30860.292260186063,40386.00605205238 -50,20.244262248110417,86.2553098058883,30742.054735645124,39981.83946305757 -51,21.859217291379004,72.89837327878459,30999.703939831277,39514.23768439393 -52,20.902111153308944,88.36862895882298,30782.76240691508,40033.44884393017 -53,59.58504995089654,149.9999677447201,33771.647879014425,41496.69202917452 -54,21.63994234351529,80.9641923004028,30933.578583737795,39809.523930207484 -55,9.804873383156298,149.9995892138235,28729.93818644509,41500.94496844104 -56,9.517359502437172,149.99308840029815,28505.329315103318,41470.65218792529 -57,19.923610217578116,88.23847592895486,30636.024864041487,40020.79650218989 -58,20.366495228182394,85.1991151089578,30752.560133063143,39947.719888972904 -59,12.242715793208157,149.99998097746882,29308.42752633667,41512.25071862387 -60,19.677765799324447,97.30674967097808,30618.37668428642,40323.0499230797 -61,19.03651315222424,109.20775378637025,30455.39615515442,40614.722801684395 -62,21.37660531151217,149.99999616215425,30806.121697474813,41479.3976433347 -63,21.896838392882998,86.86206456282005,30918.823491874144,39986.262281131254 -64,5.030122322262226,149.99991736085678,26792.302062236955,41480.579525893794 -65,17.851755694421776,53.33521102556455,30419.017295420916,38644.47349861614 -66,20.963796542255896,90.72302887846234,30795.751244616677,40114.19163802526 -67,23.082992539267945,77.24345020180209,31107.07485019312,39665.22410226011 -68,18.953050386839383,90.80802949182345,30529.280393040182,40113.73467038244 -69,20.710937910951355,83.16996057131982,30805.892332796295,39876.270184728084 -70,18.18549080794899,65.72657652078952,30416.294615296756,39223.21339606898 -71,12.147892028456324,45.12945045196771,29302.888575028635,38194.144730342545 -72,4.929663537166405,133.89086200105797,26635.8524254091,41163.82082194103 -73,20.512731504598662,106.98199797354127,30660.67479570742,40560.70063653076 -74,21.006700520199008,93.35471748418676,30761.272887418058,40178.10564855804 -75,19.73635577733317,98.75362910260881,30599.64039254174,40346.31274388047 -76,3.6393630101175565,149.99998305638113,25806.925407145678,41446.42489819377 -77,14.430958212981363,149.9999928114441,29710.277666486683,41478.96029884101 -78,21.138173237661093,90.73414659450283,30833.36092609432,40128.61898313504 -79,19.294823672883208,104.69324605284973,30510.371654343133,40510.84889949937 -80,2.607050470695225,69.22680095813037,25000.001468502505,39333.142090801295 -81,16.949842823156228,118.76691429120146,30074.04126731665,40824.66852388976 -82,21.029588811317897,95.27115352081795,30770.753828753943,40243.47156167542 -83,18.862418349044077,111.08370690591005,30421.17882623639,40670.941374189555 -84,24.708015660945147,76.24225941680999,31286.7038829574,39632.545034540664 -85,21.58937721477476,92.6329553952883,30871.989108388123,40181.7478528116 -86,21.091322126816706,96.07721666941696,30765.91144819689,40265.321194575095 -87,19.337815749868728,96.50567420686403,30604.551156564357,40318.12321325275 -88,17.77732130279279,108.5062535737451,30287.456682982094,40602.76307166587 -89,15.259532609396405,134.79914728383426,29793.69015375863,41199.11159557717 -90,21.616910309091583,90.65235108674251,30848.137718134392,40096.0776408459 -91,3.3372937891220475,149.99991062247588,25630.388452101062,41483.30064805118 -92,20.652437906744403,97.86062128528714,30747.864718937744,40330.11871286893 -93,22.134113060054425,73.68464943802763,31013.225174702933,39535.65213713519 -94,20.297310066178802,93.79207093658654,30684.309981457223,40191.747572763874 -95,6.007958386675472,149.99997175883215,27126.707007542,41465.75099589974 -96,16.572749402536758,40.75746000309888,30154.396795028595,37923.85448825053 -97,21.235697111801056,98.97798760165126,30807.097617165928,40373.550932032136 -98,20.10350615639414,96.19608053749371,30632.029399836003,40258.3813340696 -99,18.274272179970747,96.49060573948069,30456.872524151822,40305.258325587834 diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp1.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp1.out deleted file mode 100644 index f1d826085bf..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp1.out +++ /dev/null @@ -1 +0,0 @@ -{"experiment": 1, "Ca0": 0, "Ca_meas": {"17280.0": 1.0206137429621787, "0.0": 0.3179205033819153, "7560.0": 6.1190611079452015, "9720.0": 5.143125125521654, "19440.0": 0.6280402056097951, "16200.0": 0.8579867036528984, "11880.0": 3.7282923165210042, "2160.0": 4.63887641485678, "8640.0": 5.343033989137008, "14040.0": 2.053029378587664, "4320.0": 6.161603379127277, "6480.0": 6.175522427215327, "1080.0": 2.5735849991352358, "18360.0": 0.6351040530590654, "10800.0": 5.068206847862049, "21600.0": 0.40727295182614354, "20520.0": 0.6621175064161002, "5400.0": 6.567824349703669, "3240.0": 5.655458079751501, "12960.0": 2.654764659666162, "15120.0": 1.6757350275784135}, "alphac": 0.7, "Fa2": 0.0, "deltaH1": -40000, "Fa1": 0.003, "Tc2": 320, "Cc0": 0, "Tc1": 320, "Cc_meas": {"17280.0": 5.987636915771096, "0.0": 0.09558280565339891, "7560.0": 1.238130321602845, "9720.0": 1.9945247030805529, "19440.0": 6.695864385679773, "16200.0": 5.41645220805244, "11880.0": 2.719892366798277, "2160.0": 0.10805070272409367, "8640.0": 1.800229763433655, "14040.0": 4.156268601598023, "4320.0": -0.044818714779864405, "6480.0": 0.8106022415380871, "1080.0": -0.07327388848369068, "18360.0": 5.96868114596425, "10800.0": 2.0726982059573835, "21600.0": 7.269213818513372, "20520.0": 6.725777234409265, "5400.0": 0.18749831830326769, "3240.0": -0.10164819461093579, "12960.0": 3.745361461163259, "15120.0": 4.92464438752146}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"17280.0": 12.527811727243744, "0.0": 0.006198551839384427, "7560.0": 8.198459268980448, "9720.0": 10.884163155983586, "19440.0": 12.150552321810109, "16200.0": 13.247640677577017, "11880.0": 12.921639059281906, "2160.0": 1.2393091651113075, "8640.0": 9.76716833273541, "14040.0": 13.211149989298647, "4320.0": 3.803104433804622, "6480.0": 6.5810650565269375, "1080.0": 0.3042714459761661, "18360.0": 12.544400522361945, "10800.0": 11.737104197836604, "21600.0": 11.886358606219954, "20520.0": 11.832544691029744, "5400.0": 5.213810980890077, "3240.0": 2.1926632109587216, "12960.0": 13.113839789286594, "15120.0": 13.27982750652159}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"17280.0": 325.16059207223026, "0.0": 297.66636064503314, "7560.0": 331.3122493868531, "9720.0": 332.5912691607457, "19440.0": 325.80525903012233, "16200.0": 323.2692288871708, "11880.0": 334.0549081870754, "2160.0": 323.2373236557714, "8640.0": 333.4576764024497, "14040.0": 328.42212335544315, "4320.0": 327.6704558317418, "6480.0": 331.06042780025075, "1080.0": 316.2567029216892, "18360.0": 326.6647586865489, "10800.0": 334.23136746878185, "21600.0": 324.0057232633869, "20520.0": 324.4555288383823, "5400.0": 331.9568676813546, "3240.0": 326.12583828081813, "12960.0": 329.2382904744002, "15120.0": 327.3959354386782}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp10.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp10.out deleted file mode 100644 index 7eb7980a7be..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp10.out +++ /dev/null @@ -1 +0,0 @@ -{"experiment": 10, "Ca0": 0, "Ca_meas": {"0.0": 0.17585381115505311, "4320.0": 6.0315379232850992, "7560.0": 5.4904391073929908, "21600.0": 0.28272237229599306, "9720.0": 5.2677178238115365, "16200.0": 1.1265897978788217, "20520.0": 0.057584376823091032, "3240.0": 5.6315955838815661, "11880.0": 3.328489578835276, "14040.0": 2.0226562017072607, "17280.0": 1.1268539727440208, "2160.0": 4.3030132489621638, "5400.0": 6.1094034780709618, "18360.0": 0.50886621390394615, "8640.0": 5.3773941013828752, "6480.0": 5.9760510402178078, "1080.0": 2.9280667525762598, "15120.0": 1.375026987701359, "12960.0": 2.5451999496997635, "19440.0": 0.79349685535634917, "10800.0": 4.3653401523141229}, "alphac": 0.7, "Fa2": 0.0, "deltaH1": -40000, "Fa1": 0.003, "Tc2": 320, "Cc0": 0, "Tc1": 320, "Cc_meas": {"0.0": -0.038024312530073517, "4320.0": 0.35646997778490641, "7560.0": 1.0986283962244756, "21600.0": 7.160087143091391, "9720.0": 2.129148884681515, "16200.0": 5.1383561968992435, "20520.0": 6.8451793517536901, "3240.0": 0.098714783055484312, "11880.0": 3.0269500169512602, "14040.0": 3.9370558676788283, "17280.0": 5.9641262824404357, "2160.0": -0.12281730158248855, "5400.0": 0.59307341448224149, "18360.0": 6.2121451052794248, "8640.0": 1.7607685730069123, "6480.0": 0.53516134735284115, "1080.0": 0.021830284057365701, "15120.0": 4.7082270119144871, "12960.0": 4.0629501433813449, "19440.0": 6.333023537154518, "10800.0": 2.2805891192921983}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"0.0": -0.063620932422370907, "4320.0": 3.5433262677921662, "7560.0": 8.6893371808300444, "21600.0": 11.870505989648386, "9720.0": 11.309193344250055, "16200.0": 12.739396897287321, "20520.0": 11.791007739959538, "3240.0": 2.1799284210951009, "11880.0": 12.669418985545658, "14040.0": 13.141014574935076, "17280.0": 12.645153211711902, "2160.0": 1.4830589148905116, "5400.0": 5.2739635750232985, "18360.0": 12.761210138866151, "8640.0": 9.9142303856203373, "6480.0": 7.0761548290524603, "1080.0": 0.43050918133895111, "15120.0": 12.66028651303237, "12960.0": 12.766057551719733, "19440.0": 12.385465894826957, "10800.0": 11.96758252080965}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"0.0": 301.47604965958681, "4320.0": 327.40308974239883, "7560.0": 332.84954650085029, "21600.0": 322.5157157706418, "9720.0": 333.3451281132692, "16200.0": 325.64418049630734, "20520.0": 321.94339225425767, "3240.0": 327.19623764314332, "11880.0": 330.24784399520615, "14040.0": 328.20666366981681, "17280.0": 324.73232197103431, "2160.0": 324.1280979971192, "5400.0": 330.87716394833132, "18360.0": 323.47482388751507, "8640.0": 332.49299626233665, "6480.0": 332.1275559564059, "1080.0": 319.29329353123859, "15120.0": 327.29837672678497, "12960.0": 329.24537484541742, "19440.0": 324.02559861322572, "10800.0": 335.57159429203386}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp11.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp11.out deleted file mode 100644 index d97442f031b..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp11.out +++ /dev/null @@ -1 +0,0 @@ -{"experiment": 11, "Ca0": 0, "Ca_meas": {"0.0": 0.0, "4320.0": 2.9214639882977118, "7560.0": 3.782748575728669, "21600.0": 1.0934559688831289, "9720.0": 3.9988602724163731, "16200.0": 1.3975369118671503, "20520.0": 1.1243254110346068, "3240.0": 2.4246081576650433, "11880.0": 3.4755989173839743, "14040.0": 1.9594736461287787, "17280.0": 1.2827751626299488, "2160.0": 1.7884659182526941, "5400.0": 3.3009088212806073, "18360.0": 1.2111862780556402, "8640.0": 3.9172974313558302, "6480.0": 3.5822886585117493, "1080.0": 0.9878678077907459, "15120.0": 1.5972116122332332, "12960.0": 2.5920858659103394, "19440.0": 1.1618336860102094, "10800.0": 4.0378190270354528}, "alphac": 0.7, "Fa2": 0.001, "deltaH1": -40000, "Fa1": 0.001, "Tc2": 340, "Cc0": 0, "Tc1": 310, "Cc_meas": {"0.0": 0.0, "4320.0": 0.023982318123109209, "7560.0": 0.12357850466238102, "21600.0": 6.5347744286121001, "9720.0": 0.24908724520575926, "16200.0": 3.2484340438243127, "20520.0": 5.9039946620569568, "3240.0": 0.0099959742916886744, "11880.0": 0.58037814751790051, "14040.0": 1.8149241834100336, "17280.0": 3.9360275733370447, "2160.0": 0.0028162554877217529, "5400.0": 0.046614709277751604, "18360.0": 4.6059327483829717, "8640.0": 0.17995429953061945, "6480.0": 0.079417046516631534, "1080.0": 0.00029995464126276485, "15120.0": 2.5396785773426069, "12960.0": 1.1193190878564474, "19440.0": 5.2613272888405183, "10800.0": 0.33137635542084787}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"0.0": 0.0, "4320.0": 0.96588936445839646, "7560.0": 2.5063363714379272, "21600.0": 7.0191274767686718, "9720.0": 3.6738114082888234, "16200.0": 7.2205659498627206, "20520.0": 7.0929480434376666, "3240.0": 0.56824903663740756, "11880.0": 5.2689545424581627, "14040.0": 6.8616827734843717, "17280.0": 7.2356915893004699, "2160.0": 0.25984959276122965, "5400.0": 1.4328144788513235, "18360.0": 7.2085405027857679, "8640.0": 3.0841896398822519, "6480.0": 1.9514434452676097, "1080.0": 0.063681239951285565, "15120.0": 7.1238797146821753, "12960.0": 6.279843675978368, "19440.0": 7.1578041487575703, "10800.0": 4.2664529460540459}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"0.0": 300.0, "4320.0": 312.23882460110946, "7560.0": 313.65588947948061, "21600.0": 347.47264008527429, "9720.0": 314.23459379740643, "16200.0": 349.53974420970837, "20520.0": 347.6739307915775, "3240.0": 311.5507050941913, "11880.0": 343.84565806826117, "14040.0": 351.77400891772504, "17280.0": 348.76203275606656, "2160.0": 310.56857709679934, "5400.0": 312.79850843986321, "18360.0": 348.2596349485566, "8640.0": 313.9757607933924, "6480.0": 313.26649244154709, "1080.0": 308.41052123547064, "15120.0": 350.65940715239384, "12960.0": 351.11078111562267, "19440.0": 347.92214750818005, "10800.0": 317.60632266285268}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp12.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp12.out deleted file mode 100644 index cbed2d89634..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp12.out +++ /dev/null @@ -1 +0,0 @@ -{"experiment": 12, "Ca0": 0, "Ca_meas": {"0.0": -0.11670041274142862, "4320.0": 2.5843604031503551, "7560.0": 3.9909358380362421, "21600.0": 0.85803342268297711, "9720.0": 3.8011636431252702, "16200.0": 1.1002575863333801, "20520.0": 1.5248654901477563, "3240.0": 2.8626873137030655, "11880.0": 3.0876088469734766, "14040.0": 2.1233520630854348, "17280.0": 1.3118009790549121, "2160.0": 1.9396713478350336, "5400.0": 3.4898817799323192, "18360.0": 1.3214498335778544, "8640.0": 4.3166520687122008, "6480.0": 3.7430014080130212, "1080.0": 0.71502131244112932, "15120.0": 1.6869248126824714, "12960.0": 2.6199132141077999, "19440.0": 1.1934026369102775, "10800.0": 4.1662760005238244}, "alphac": 0.7, "Fa2": 0.001, "deltaH1": -40000, "Fa1": 0.001, "Tc2": 340, "Cc0": 0, "Tc1": 310, "Cc_meas": {"0.0": -0.14625373198142194, "4320.0": 0.50521240190855021, "7560.0": 0.072658369151157948, "21600.0": 6.5820277701997947, "9720.0": 0.14656574869775074, "16200.0": 3.2344935292984842, "20520.0": 6.1934992398225113, "3240.0": 0.073187422926812754, "11880.0": 0.68271223751792398, "14040.0": 1.5495094265280573, "17280.0": 3.5758298262936474, "2160.0": -0.052665808995189155, "5400.0": -0.067101579134353218, "18360.0": 4.6809081069906799, "8640.0": 0.58099071333349073, "6480.0": -0.34905530826754594, "1080.0": -0.13981627097780677, "15120.0": 2.7577781806493582, "12960.0": 0.9824219783559841, "19440.0": 5.2002977724609245, "10800.0": 0.0089889440138224419}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"0.0": -0.34864609349591541, "4320.0": 0.99561164186682227, "7560.0": 2.4688230226633143, "21600.0": 7.0564292657160461, "9720.0": 3.9961025255558669, "16200.0": 7.161739218807984, "20520.0": 6.8044634236188921, "3240.0": 0.21017912447011355, "11880.0": 5.1168427571991435, "14040.0": 7.0032016822280907, "17280.0": 7.11845364876228, "2160.0": 0.22241873726625405, "5400.0": 1.3174801426799267, "18360.0": 6.9581816529657257, "8640.0": 3.0438444011178785, "6480.0": 2.2558063612162291, "1080.0": 0.38969247867806489, "15120.0": 7.2125210995495488, "12960.0": 6.4014182164755429, "19440.0": 6.8128450220608308, "10800.0": 4.2649851849420299}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"0.0": 300.53274252012955, "4320.0": 311.15339067777529, "7560.0": 312.18867239090878, "21600.0": 346.00646319366473, "9720.0": 313.61710665630221, "16200.0": 352.81825567952984, "20520.0": 346.66248325950249, "3240.0": 311.37928873928001, "11880.0": 343.17457873193757, "14040.0": 352.88609842940627, "17280.0": 348.48519596719899, "2160.0": 312.70676562686674, "5400.0": 314.29841143358993, "18360.0": 347.81967845794014, "8640.0": 313.26528616120316, "6480.0": 314.33539425367189, "1080.0": 308.35955588183077, "15120.0": 350.62179387183005, "12960.0": 348.72513776404708, "19440.0": 346.65341312375318, "10800.0": 318.79995964600641}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp13.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp13.out deleted file mode 100644 index 6ef514c951a..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp13.out +++ /dev/null @@ -1 +0,0 @@ -{"experiment": 13, "Ca0": 0, "Ca_meas": {"0.0": 0.0, "4320.0": 2.9015294551493156, "7560.0": 3.7456240714596114, "21600.0": 1.0893473942847287, "9720.0": 3.955661759695563, "16200.0": 1.3896514099314119, "20520.0": 1.1200835239496501, "3240.0": 2.4116751003091821, "11880.0": 3.4212519616089123, "14040.0": 1.9331934810250495, "17280.0": 1.2772682671606199, "2160.0": 1.7821342925311514, "5400.0": 3.2743367747379883, "18360.0": 1.2065189956942144, "8640.0": 3.8765800278544793, "6480.0": 3.5499054339456388, "1080.0": 0.98643229677676525, "15120.0": 1.583391359829623, "12960.0": 2.5471212725882397, "19440.0": 1.1574522594063252, "10800.0": 3.9931029485836738}, "alphac": 0.7, "Fa2": 0.001, "deltaH1": -40000, "Fa1": 0.001, "Tc2": 340, "Cc0": 0, "Tc1": 310, "Cc_meas": {"0.0": 0.0, "4320.0": 0.025294213033754839, "7560.0": 0.12897041691694733, "21600.0": 6.5640983670402253, "9720.0": 0.2583989163781088, "16200.0": 3.2753828012649455, "20520.0": 5.9325635171128175, "3240.0": 0.01057908838025531, "11880.0": 0.60077017176350533, "14040.0": 1.8465767045073862, "17280.0": 3.9624456898675042, "2160.0": 0.0029862658207987017, "5400.0": 0.048985517519306479, "18360.0": 4.6327886545170172, "8640.0": 0.1872203610374778, "6480.0": 0.083160507067847278, "1080.0": 0.0003160950335645278, "15120.0": 2.5686484578016535, "12960.0": 1.149697840345306, "19440.0": 5.2890172904133799, "10800.0": 0.3428648031290083}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"0.0": 0.0, "4320.0": 0.98451200269614558, "7560.0": 2.5380689634524152, "21600.0": 6.993912112938939, "9720.0": 3.7076982498372795, "16200.0": 7.2015026943578233, "20520.0": 7.0686210754667576, "3240.0": 0.58059897990470211, "11880.0": 5.3029094739876195, "14040.0": 6.8563104174907483, "17280.0": 7.2147803682393352, "2160.0": 0.26601120814969509, "5400.0": 1.4570157171523839, "18360.0": 7.1863518790131433, "8640.0": 3.1176409818767401, "6480.0": 1.9800832092825005, "1080.0": 0.06510061057296454, "15120.0": 7.1087300866267373, "12960.0": 6.294429516811606, "19440.0": 7.1344955737885876, "10800.0": 4.2996805767976616}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"0.0": 300.0, "4320.0": 312.83405362164399, "7560.0": 314.10857941130064, "21600.0": 347.58975942376372, "9720.0": 314.61233912755387, "16200.0": 349.56203914424219, "20520.0": 347.79317623855678, "3240.0": 312.2016171592112, "11880.0": 344.43453363173575, "14040.0": 351.72789167129031, "17280.0": 348.83759825667926, "2160.0": 311.27622091507072, "5400.0": 313.34232236658977, "18360.0": 348.36453268948424, "8640.0": 314.38892058203726, "6480.0": 313.76278431242508, "1080.0": 309.11491437259622, "15120.0": 350.61679816631096, "12960.0": 351.27269989378158, "19440.0": 348.03901542922108, "10800.0": 318.0555419842903}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp14.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp14.out deleted file mode 100644 index cc3f95da860..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp14.out +++ /dev/null @@ -1 +0,0 @@ -{"experiment": 14, "Ca0": 0, "Ca_meas": {"0.0": 0.081520066870384211, "4320.0": 2.8337428652268013, "7560.0": 3.9530051428610888, "21600.0": 1.1807185641508762, "9720.0": 4.0236480913821637, "16200.0": 1.4376034521825525, "20520.0": 1.5413859123725682, "3240.0": 2.7181227248994633, "11880.0": 3.3903617547506242, "14040.0": 2.0159168723544196, "17280.0": 1.0638207528750085, "2160.0": 1.8959521419461316, "5400.0": 2.9705863021885124, "18360.0": 1.0674705545585839, "8640.0": 4.144391992823846, "6480.0": 3.5918471023904477, "1080.0": 1.0113708479421195, "15120.0": 1.6541373379275581, "12960.0": 2.6476139688086877, "19440.0": 1.2312633238777129, "10800.0": 3.8743606114727154}, "alphac": 0.7, "Fa2": 0.001, "deltaH1": -40000, "Fa1": 0.001, "Tc2": 340, "Cc0": 0, "Tc1": 310, "Cc_meas": {"0.0": -0.089503442052066576, "4320.0": 0.18208896954493076, "7560.0": 0.17697219964055824, "21600.0": 6.8780014679710426, "9720.0": 0.35693081777790492, "16200.0": 3.1977597178279002, "20520.0": 6.1361982627818481, "3240.0": 0.10972700122863582, "11880.0": 0.76070080263615369, "14040.0": 1.5671921543009262, "17280.0": 4.0972632572434122, "2160.0": -0.28322371444225319, "5400.0": -0.079382269802482266, "18360.0": 4.5706426776745905, "8640.0": 0.26888423813019369, "6480.0": 0.1979519809989283, "1080.0": 0.13979150229071807, "15120.0": 2.6867100129129424, "12960.0": 0.99472739483139283, "19440.0": 5.6554142424694902, "10800.0": 0.46709816548507599}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"0.0": 0.1139198104669558, "4320.0": 0.99036932502879282, "7560.0": 2.4258194495364482, "21600.0": 7.0446273994379434, "9720.0": 3.7506516413639113, "16200.0": 6.9192013751011503, "20520.0": 7.1555299371654808, "3240.0": 0.66230857796133746, "11880.0": 5.0716652323781499, "14040.0": 7.0971130388695007, "17280.0": 7.4091470358534082, "2160.0": -0.039078609338807413, "5400.0": 1.480378464133409, "18360.0": 7.1741052031883399, "8640.0": 3.4996110541636019, "6480.0": 2.0450173775271647, "1080.0": 0.13728827557251419, "15120.0": 6.7382150794212539, "12960.0": 6.3936782268937753, "19440.0": 7.3298178049407321, "10800.0": 4.6925893428035463}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"0.0": 301.43741888919669, "4320.0": 313.81215912108536, "7560.0": 313.22398065446896, "21600.0": 347.95979639817102, "9720.0": 316.38480427739029, "16200.0": 350.46267406552954, "20520.0": 349.96840569755773, "3240.0": 313.32903259260996, "11880.0": 345.61089172333249, "14040.0": 352.83446396320579, "17280.0": 347.23320123776085, "2160.0": 310.02897702317114, "5400.0": 314.70707924235739, "18360.0": 349.8524737596988, "8640.0": 314.13917383134913, "6480.0": 314.15959183349207, "1080.0": 307.97982604287193, "15120.0": 349.00176197155969, "12960.0": 350.41651244789142, "19440.0": 346.1591726550746, "10800.0": 317.90588794308121}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp2.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp2.out deleted file mode 100644 index e5245dff3f0..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp2.out +++ /dev/null @@ -1 +0,0 @@ -{"experiment": 2, "Ca0": 0, "Ca_meas": {"12960.0": 2.6833775561963225, "21600.0": 1.1452663167259416, "17280.0": 1.2021595324165524, "19440.0": 1.0621392247792012, "0.0": -0.1316904398446574, "7560.0": 3.544605362880795, "11880.0": 3.1817551426501267, "14040.0": 2.066815570405579, "4320.0": 3.0488618589043432, "15120.0": 1.5896211475539537, "1080.0": 0.8608182979507091, "18360.0": 1.1317484585922248, "8640.0": 3.454602822099547, "2160.0": 1.7479951078246254, "20520.0": 1.2966191801491087, "9720.0": 4.0596636929917285, "6480.0": 3.9085446597134283, "3240.0": 2.5050366860794875, "5400.0": 3.2668528110981576, "10800.0": 4.004828727345138, "16200.0": 1.2860720212507326}, "alphac": 0.7, "Fa2": 0.001, "deltaH1": -40000, "Fa1": 0.001, "Tc2": 340, "Cc0": 0, "Tc1": 310, "Cc_meas": {"12960.0": 1.3318254182147888, "21600.0": 6.431089735352747, "17280.0": 3.9711701719678825, "19440.0": 5.321278981143728, "0.0": 0.10325037628575211, "7560.0": 0.10827803632010198, "11880.0": 0.5184920846420157, "14040.0": 1.7974496302186054, "4320.0": 0.03112694971654564, "15120.0": 2.5245584142423207, "1080.0": 0.27315169217241275, "18360.0": 4.587420104936772, "8640.0": 0.1841080751926184, "2160.0": -0.3018405210283593, "20520.0": 6.0086124912796794, "9720.0": 0.08100716578409362, "6480.0": 0.027897809479352567, "3240.0": 0.01973928836607919, "5400.0": 0.02694434057766582, "10800.0": 0.3021977838614568, "16200.0": 3.561159267372319}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"12960.0": 6.358162874770003, "21600.0": 7.190121324797683, "17280.0": 7.302887809357959, "19440.0": 7.249179120248633, "0.0": -0.2300456054526237, "7560.0": 2.5654112157969364, "11880.0": 5.3061363321784025, "14040.0": 6.84009925060659, "4320.0": 0.9644475554073758, "15120.0": 7.261439274435592, "1080.0": 0.2644823572584467, "18360.0": 7.3015136544611305, "8640.0": 3.065189548359326, "2160.0": 0.27122113581760515, "20520.0": 7.162520282520871, "9720.0": 3.701445814227159, "6480.0": 2.0255650533089735, "3240.0": 0.48891328422133334, "5400.0": 1.326135697891304, "10800.0": 4.031252283597449, "16200.0": 7.38249778574694}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"12960.0": 350.10381529626676, "21600.0": 345.04247547236935, "17280.0": 348.24859807524, "19440.0": 346.7076647696431, "0.0": 301.30135373385, "7560.0": 314.7140350191631, "11880.0": 343.608488145403, "14040.0": 352.2902404657956, "4320.0": 312.2349716351422, "15120.0": 351.28330527232634, "1080.0": 307.67178366332996, "18360.0": 347.3517733033304, "8640.0": 313.62143853358026, "2160.0": 310.2674222024707, "20520.0": 348.1662021459614, "9720.0": 314.8081875940964, "6480.0": 312.7396303616638, "3240.0": 310.9258419605079, "5400.0": 312.8448509580561, "10800.0": 317.33866255037736, "16200.0": 349.5494112095972}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp3.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp3.out deleted file mode 100644 index a9b013d476f..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp3.out +++ /dev/null @@ -1 +0,0 @@ -{"experiment": 3, "Ca0": 0, "Ca_meas": {"17280.0": 0.39426558381033217, "0.0": -0.25761950998771116, "7560.0": 7.638659217862309, "9720.0": 7.741721088143662, "19440.0": 0.12706787288438182, "16200.0": 0.15060928317089423, "11880.0": 4.534965302380243, "2160.0": 4.47101661859487, "8640.0": 7.562734803826617, "14040.0": 0.8456407304976143, "4320.0": 7.395023123698018, "6480.0": 7.952409415603349, "1080.0": 3.0448009666821947, "18360.0": 0.0754742404427045, "10800.0": 7.223420802364689, "21600.0": 0.181946676125186, "20520.0": 0.195256504023462, "5400.0": 7.844394030136843, "3240.0": 6.031994466757849, "12960.0": 1.813573590958129, "15120.0": 0.30408071219857113}, "alphac": 0.7, "Fa2": 0.0, "deltaH1": -40000, "Fa1": 0.003, "Tc2": 340, "Cc0": 0, "Tc1": 310, "Cc_meas": {"17280.0": 10.587758291638856, "0.0": -0.21796531802425348, "7560.0": 0.8680716299725404, "9720.0": 0.9085250272598128, "19440.0": 11.914154788848554, "16200.0": 9.737618534040658, "11880.0": 2.0705302921286064, "2160.0": -0.022903632391514165, "8640.0": 0.5195918959805059, "14040.0": 6.395605356788582, "4320.0": 0.010107836996695638, "6480.0": 0.09956884355869228, "1080.0": -0.21178603534213003, "18360.0": 10.990013628196317, "10800.0": 1.345982231414325, "21600.0": 12.771296192955955, "20520.0": 12.196513048345082, "5400.0": 0.04790148745481311, "3240.0": 0.318546588876358, "12960.0": 4.693433882970767, "15120.0": 8.491695125145553}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"17280.0": 8.731932027503914, "0.0": -0.1375816812387338, "7560.0": 6.8484762842626985, "9720.0": 9.333087689786101, "19440.0": 7.381577268709603, "16200.0": 9.502048821225666, "11880.0": 12.406853134672218, "2160.0": 0.8107944776900446, "8640.0": 8.158484571318013, "14040.0": 11.54445651179274, "4320.0": 2.8119825114954082, "6480.0": 5.520857819630275, "1080.0": 0.18414413253133835, "18360.0": 8.145712620219781, "10800.0": 10.75765409121092, "21600.0": 6.1356948865706356, "20520.0": 6.576247039355788, "5400.0": 3.92591568907661, "3240.0": 2.015632242014947, "12960.0": 12.71320139030468, "15120.0": 10.314039809497785}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"17280.0": 345.7556825478521, "0.0": 300.1125978749429, "7560.0": 319.7301093967675, "9720.0": 321.4474947517745, "19440.0": 345.1982881134514, "16200.0": 348.50691612993586, "11880.0": 357.2800598685144, "2160.0": 311.7652063627056, "8640.0": 323.6663990115871, "14040.0": 365.2497105829804, "4320.0": 317.1702037696461, "6480.0": 319.9056594806601, "1080.0": 309.6187369359568, "18360.0": 345.1427626681205, "10800.0": 323.9154289387584, "21600.0": 345.45022165546357, "20520.0": 344.5991879566869, "5400.0": 316.21958050333416, "3240.0": 314.95895736530616, "12960.0": 371.5669986462856, "15120.0": 355.3612054360245}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp4.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp4.out deleted file mode 100644 index e702db7d05b..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp4.out +++ /dev/null @@ -1 +0,0 @@ -{"experiment": 4, "Ca0": 0, "Ca_meas": {"17280.0": 1.1816674202534045, "0.0": -0.25414468591124584, "7560.0": 5.914582094251343, "9720.0": 5.185067133371561, "19440.0": 0.5307435290768995, "16200.0": 1.2011215994628408, "11880.0": 3.5778183914967925, "2160.0": 4.254440534150475, "8640.0": 5.473440610227645, "14040.0": 2.1475894664278354, "4320.0": 5.8795707148110266, "6480.0": 6.089523479429854, "1080.0": 2.4339586418303543, "18360.0": 0.545228377126232, "10800.0": 4.946406396626746, "21600.0": 0.3169450590438124, "20520.0": 0.5859997070045333, "5400.0": 6.15901928205937, "3240.0": 5.5559094344993225, "12960.0": 2.476561130612629, "15120.0": 1.35232620260846}, "alphac": 0.7, "Fa2": 0.0, "deltaH1": -40000, "Fa1": 0.003, "Tc2": 320, "Cc0": 0, "Tc1": 320, "Cc_meas": {"17280.0": 6.529641678005382, "0.0": 0.23057916607631007, "7560.0": 1.4209973582182187, "9720.0": 2.662002861314475, "19440.0": 7.612677904908142, "16200.0": 6.236740105453746, "11880.0": 3.6132373498432813, "2160.0": 0.41778377045750303, "8640.0": 2.3031169482702336, "14040.0": 4.857027337132376, "4320.0": 0.21846387467958883, "6480.0": 1.304912741118713, "1080.0": -0.002497120213976349, "18360.0": 7.207560113722179, "10800.0": 3.072350404943197, "21600.0": 8.437070128901182, "20520.0": 7.985790844633096, "5400.0": 0.5552902218354748, "3240.0": 0.4207298617922146, "12960.0": 4.791797355546968, "15120.0": 5.544868662346418}, "alphaj": 0.8, "Cb0": 2, "Vr0": 1, "Cb_meas": {"17280.0": 13.559468310792148, "0.0": 2.1040937779048963, "7560.0": 9.809117250733637, "9720.0": 12.404875593478181, "19440.0": 12.616477055699818, "16200.0": 13.94157106495499, "11880.0": 13.828382570964388, "2160.0": 3.1373485614417618, "8640.0": 11.053587506450443, "14040.0": 14.267859106799012, "4320.0": 5.6037726942190424, "6480.0": 8.499893646580981, "1080.0": 2.2930856900001535, "18360.0": 13.566545045105496, "10800.0": 13.397445458860116, "21600.0": 12.347983697813623, "20520.0": 12.422291796055749, "5400.0": 7.367727360896684, "3240.0": 3.8542518870239824, "12960.0": 14.038139660530316, "15120.0": 14.114308276615096}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"17280.0": 324.4465785902298, "0.0": 299.6733512720839, "7560.0": 333.04897592093835, "9720.0": 334.48916083151335, "19440.0": 323.5321646227951, "16200.0": 326.84528144052564, "11880.0": 332.77528830002086, "2160.0": 322.2453586799791, "8640.0": 334.2512423463752, "14040.0": 329.05431569837447, "4320.0": 327.99896899102527, "6480.0": 334.3262547857335, "1080.0": 317.32350372398014, "18360.0": 324.97573570445866, "10800.0": 334.23107235994195, "21600.0": 323.6424061352077, "20520.0": 324.00045995355015, "5400.0": 331.45112696032044, "3240.0": 326.33322784448785, "12960.0": 330.4778004752374, "15120.0": 326.8776604963411}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp5.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp5.out deleted file mode 100644 index 6c4b1b1d9e0..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp5.out +++ /dev/null @@ -1 +0,0 @@ -{"experiment": 5, "Ca0": 0, "Ca_meas": {"17280.0": 0.5384747196579402, "0.0": -0.14396911833443257, "7560.0": 6.038385982663388, "9720.0": 5.0792329539506, "19440.0": 0.3782801126758533, "16200.0": 1.0619887309834395, "11880.0": 3.6494330494296436, "2160.0": 4.775401751873804, "8640.0": 5.629577532845656, "14040.0": 2.0037718871692265, "4320.0": 5.889802624055117, "6480.0": 6.09724817816528, "1080.0": 2.875851853145854, "18360.0": 0.6780066197547887, "10800.0": 4.859469684381779, "21600.0": 0.3889400954173796, "20520.0": 0.3351378562274788, "5400.0": 6.127222815180268, "3240.0": 5.289726682847115, "12960.0": 2.830845316709853, "15120.0": 1.5312992911111707}, "alphac": 0.7, "Fa2": 0.0, "deltaH1": -40000, "Fa1": 0.003, "Tc2": 320, "Cc0": 2, "Tc1": 320, "Cc_meas": {"17280.0": 7.596710556194337, "0.0": 1.6504080367065743, "7560.0": 2.809410585275859, "9720.0": 3.8419183958835132, "19440.0": 8.137782633931637, "16200.0": 6.938967086259325, "11880.0": 5.022162589071362, "2160.0": 2.0515545922033964, "8640.0": 3.506455726732785, "14040.0": 6.010539749263416, "4320.0": 2.2056993474658584, "6480.0": 2.5775763528099858, "1080.0": 2.03522693402577, "18360.0": 8.083917616781594, "10800.0": 4.662851778068136, "21600.0": 9.279674687903626, "20520.0": 8.963676424956157, "5400.0": 2.293408505844697, "3240.0": 1.9216270432789067, "12960.0": 5.637375563057352, "15120.0": 6.3296720972633045}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"17280.0": 12.831256801432378, "0.0": 0.13194621122245662, "7560.0": 7.965534934436229, "9720.0": 10.908595985103954, "19440.0": 12.408596390398941, "16200.0": 12.975405069340143, "11880.0": 12.710800046234393, "2160.0": 0.9223242691530996, "8640.0": 9.454601468197033, "14040.0": 13.19437793062601, "4320.0": 3.713168763161746, "6480.0": 6.515936097446724, "1080.0": 0.031354105110323494, "18360.0": 12.821094923672087, "10800.0": 11.90520078370877, "21600.0": 11.81429953673305, "20520.0": 12.099271866573613, "5400.0": 4.982941965055916, "3240.0": 2.766378581935415, "12960.0": 13.074621618364043, "15120.0": 12.957819319226212}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"17280.0": 326.8759279818479, "0.0": 300.7736745288814, "7560.0": 333.6674031533901, "9720.0": 333.59854139744135, "19440.0": 324.5264772316974, "16200.0": 325.2454701315101, "11880.0": 332.9849253092768, "2160.0": 322.1940607068012, "8640.0": 331.78378240085084, "14040.0": 328.48981010099453, "4320.0": 327.3883510651506, "6480.0": 330.15101610436426, "1080.0": 318.24994073025096, "18360.0": 323.9527212120804, "10800.0": 333.3006916263996, "21600.0": 322.07065855783964, "20520.0": 324.3518907083261, "5400.0": 331.5429008148077, "3240.0": 324.52116111644654, "12960.0": 329.21899337854876, "15120.0": 328.26179934031467}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp6.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp6.out deleted file mode 100644 index c1630902e1a..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp6.out +++ /dev/null @@ -1 +0,0 @@ -{"experiment": 6, "Ca0": 2, "Ca_meas": {"17280.0": 1.1595382970987758, "0.0": 1.9187666718004224, "7560.0": 5.977461039170304, "9720.0": 5.164472215594892, "19440.0": 0.6528636977624275, "16200.0": 1.2106046606700225, "11880.0": 3.3229659243191296, "2160.0": 5.923887627906124, "8640.0": 5.225477003110976, "14040.0": 1.7878931129582107, "4320.0": 6.782806717544953, "6480.0": 6.27323507174512, "1080.0": 4.481633914987097, "18360.0": 0.8866911582721309, "10800.0": 4.481150474336123, "21600.0": 0.2170007972283953, "20520.0": 0.3199825651255196, "5400.0": 6.795886093698936, "3240.0": 6.288606047308427, "12960.0": 2.4509424000990685, "15120.0": 1.506568611506372}, "alphac": 0.7, "Fa2": 0.0, "deltaH1": -40000, "Fa1": 0.003, "Tc2": 320, "Cc0": 0, "Tc1": 320, "Cc_meas": {"17280.0": 6.588306395438309, "0.0": 0.24402401145820712, "7560.0": 1.4036889138374646, "9720.0": 2.4218855673847455, "19440.0": 7.764492558630308, "16200.0": 6.205315919403138, "11880.0": 3.593219427441702, "2160.0": -0.10553376629311664, "8640.0": 1.8628128392103824, "14040.0": 5.027532358914124, "4320.0": 0.2172961549286831, "6480.0": 0.875637414228913, "1080.0": 0.2688503672636328, "18360.0": 7.240866507350995, "10800.0": 3.26514503365032, "21600.0": 8.251445433411781, "20520.0": 7.987953548408583, "5400.0": 0.6458428001299884, "3240.0": 0.3201498579399834, "12960.0": 4.263491165240245, "15120.0": 5.885223860529403}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"17280.0": 13.604962664333257, "0.0": -0.20747860874385013, "7560.0": 10.013416230907982, "9720.0": 12.055665770517061, "19440.0": 13.289064414490856, "16200.0": 13.82240671329648, "11880.0": 13.783622629658064, "2160.0": 1.8287780310400052, "8640.0": 11.17018006067254, "14040.0": 14.064985893141849, "4320.0": 5.072613174963567, "6480.0": 8.597613514631933, "1080.0": 0.3932885074677299, "18360.0": 13.473844971871975, "10800.0": 13.343451941795923, "21600.0": 12.209822751574375, "20520.0": 12.483108442400093, "5400.0": 6.7290370118557545, "3240.0": 3.4163314305947527, "12960.0": 14.296996898861073, "15120.0": 14.543019390786785}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"17280.0": 324.3952638382081, "0.0": 300.9417777707309, "7560.0": 334.57634369064954, "9720.0": 336.13718804612154, "19440.0": 324.10564173791454, "16200.0": 324.97714743647435, "11880.0": 332.29384802281055, "2160.0": 324.243456129639, "8640.0": 335.85007440436317, "14040.0": 329.01187645109906, "4320.0": 331.1961476255781, "6480.0": 333.16262386818596, "1080.0": 319.0632107387995, "18360.0": 322.11836267923206, "10800.0": 332.8894634515628, "21600.0": 323.92451205164855, "20520.0": 323.319714630304, "5400.0": 334.21206737651613, "3240.0": 326.78695915581983, "12960.0": 329.6184998003745, "15120.0": 327.5414299857002}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp7.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp7.out deleted file mode 100644 index 6ef879f3a17..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp7.out +++ /dev/null @@ -1 +0,0 @@ -{"experiment": 7, "Ca0": 0, "Ca_meas": {"0.0": 0.0, "4320.0": 5.9967662103693256, "7560.0": 5.6550872327951165, "21600.0": 0.35323003565444244, "9720.0": 5.0380179697260221, "16200.0": 1.1574854069611118, "20520.0": 0.44526599556762764, "3240.0": 5.5465851989526014, "11880.0": 3.4091089779405226, "14040.0": 1.9309662288658835, "17280.0": 0.90614590071077117, "2160.0": 4.5361731979596218, "5400.0": 6.071094394377246, "18360.0": 0.71270371205373184, "8640.0": 5.3475100856285955, "6480.0": 5.9202343949662177, "1080.0": 2.7532264453848811, "15120.0": 1.4880794860689583, "12960.0": 2.5406275798785134, "19440.0": 0.56254756816886675, "10800.0": 4.6684283481238458}, "alphac": 0.7, "Fa2": 0.0, "deltaH1": -40000, "Fa1": 0.003, "Tc2": 320, "Cc0": 0, "Tc1": 320, "Cc_meas": {"0.0": 9.1835496157991212e-40, "4320.0": 0.2042152487619561, "7560.0": 1.0742319748668527, "21600.0": 7.2209534629193319, "9720.0": 2.0351470130435847, "16200.0": 5.2015141957639486, "20520.0": 6.8469618370195322, "3240.0": 0.080409363629683248, "11880.0": 3.1846111102658314, "14040.0": 4.26052274570326, "17280.0": 5.6380747523602874, "2160.0": 0.020484309410223334, "5400.0": 0.4082391087574655, "18360.0": 6.0566679041163232, "8640.0": 1.5236474138282798, "6480.0": 0.69922443474303053, "1080.0": 0.0017732304596560309, "15120.0": 4.7439891962421239, "12960.0": 3.7433194976361364, "19440.0": 6.4591935065135972, "10800.0": 2.5966732389812686}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"0.0": 9.1835496157991212e-41, "4320.0": 3.7902671698338786, "7560.0": 8.430643849127673, "21600.0": 11.611715650093123, "9720.0": 10.913795239302978, "16200.0": 12.826899545942249, "20520.0": 11.893671316079821, "3240.0": 2.2947643625752363, "11880.0": 12.592179060461286, "14040.0": 12.994410174098332, "17280.0": 12.641678495596169, "2160.0": 1.0564916422363797, "5400.0": 5.3872034016274126, "18360.0": 12.416527532497087, "8640.0": 9.7522120688862319, "6480.0": 6.9615062931011256, "1080.0": 0.24785349222969222, "15120.0": 12.953830466356314, "12960.0": 12.901952071152909, "19440.0": 12.164158073984597, "10800.0": 11.920797561562614}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"0.0": 300.0, "4320.0": 329.97137612867306, "7560.0": 333.16333833254259, "21600.0": 323.31193212521009, "9720.0": 333.2600929343854, "16200.0": 325.39648330671537, "20520.0": 323.56691057280642, "3240.0": 327.66452370998064, "11880.0": 331.65035718435655, "14040.0": 327.56747174535786, "17280.0": 324.74920150215411, "2160.0": 324.4585751379426, "5400.0": 331.60125794337375, "18360.0": 324.25971411725646, "8640.0": 333.33010300559897, "6480.0": 332.63089408099353, "1080.0": 318.87878296714581, "15120.0": 326.2893083756407, "12960.0": 329.38909200530219, "19440.0": 323.87612174726837, "10800.0": 333.08705569007822}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp8.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp8.out deleted file mode 100644 index 6aa9fea17b3..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp8.out +++ /dev/null @@ -1 +0,0 @@ -{"experiment": 8, "Ca0": 0, "Ca_meas": {"0.0": 0.30862088766711671, "4320.0": 6.0491110491659228, "7560.0": 5.7909310485601502, "21600.0": 0.232444399226299, "9720.0": 4.9320449060797475, "16200.0": 0.97134242753331668, "20520.0": 0.42847724332841963, "3240.0": 5.6988320807198498, "11880.0": 3.3235733576868514, "14040.0": 1.9846460628194049, "17280.0": 0.87715206210722585, "2160.0": 4.615346351863904, "5400.0": 6.384056703029386, "18360.0": 0.41688144324552118, "8640.0": 5.4121173109702099, "6480.0": 6.0660731346226324, "1080.0": 2.8379509025410488, "15120.0": 0.98831570466285279, "12960.0": 2.2167483934357417, "19440.0": 0.46284950985984985, "10800.0": 4.7377220491627412}, "alphac": 0.7, "Fa2": 0.0, "deltaH1": -40000, "Fa1": 0.003, "Tc2": 320, "Cc0": 0, "Tc1": 320, "Cc_meas": {"0.0": -0.050833820035522316, "4320.0": 0.21055066883154505, "7560.0": 1.3851246436045646, "21600.0": 7.3672985895890362, "9720.0": 2.0100502709379842, "16200.0": 5.1793087406376159, "20520.0": 6.840381847429823, "3240.0": 0.13411276227648503, "11880.0": 3.3052152545385454, "14040.0": 3.9431305823279708, "17280.0": 5.7290141848801586, "2160.0": 0.16719633749951002, "5400.0": 0.49872603502453117, "18360.0": 6.1508540969551078, "8640.0": 1.4737312345987361, "6480.0": 0.69437977126769512, "1080.0": -0.0093978134715377304, "15120.0": 4.9151661032041298, "12960.0": 4.0623539766149843, "19440.0": 6.3058400571478561, "10800.0": 2.820347355873587}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"0.0": 0.037166728983599892, "4320.0": 3.5183932586092856, "7560.0": 8.916682434428397, "21600.0": 11.534104657089006, "9720.0": 11.087958791247285, "16200.0": 13.02597376112109, "20520.0": 11.639999923137731, "3240.0": 2.346958004261503, "11880.0": 12.049613604010537, "14040.0": 12.906738918465997, "17280.0": 12.867691165140879, "2160.0": 1.3313958783841602, "5400.0": 5.3650409213472221, "18360.0": 12.405763004965722, "8640.0": 9.5635832344445717, "6480.0": 7.0954049721214671, "1080.0": 0.40883709280782765, "15120.0": 12.971506554625082, "12960.0": 12.829158718434032, "19440.0": 11.946615137583075, "10800.0": 11.373799750334223}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"0.0": 300.55141264078583, "4320.0": 329.42497918063066, "7560.0": 333.82602046942475, "21600.0": 323.68642879192487, "9720.0": 332.94208820576767, "16200.0": 325.82299128298814, "20520.0": 325.19753703643721, "3240.0": 329.66504941755875, "11880.0": 332.29546982118751, "14040.0": 326.51837436850099, "17280.0": 326.51851506890586, "2160.0": 323.70134945698589, "5400.0": 328.6805843225718, "18360.0": 324.79832692054578, "8640.0": 331.94068007914785, "6480.0": 332.75141896044545, "1080.0": 318.90722718736015, "15120.0": 325.85289150843209, "12960.0": 327.72250161440121, "19440.0": 325.17198606848808, "10800.0": 334.1255807822717}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp9.out b/pyomo/contrib/parmest/deprecated/examples/semibatch/exp9.out deleted file mode 100644 index 627f92b1f83..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/semibatch/exp9.out +++ /dev/null @@ -1 +0,0 @@ -{"experiment": 9, "Ca0": 0, "Ca_meas": {"0.0": 0.0, "4320.0": 5.9967662103693256, "7560.0": 5.6550872327951165, "21600.0": 0.35323003565444244, "9720.0": 5.0380179697260221, "16200.0": 1.1574854069611118, "20520.0": 0.44526599556762764, "3240.0": 5.5465851989526014, "11880.0": 3.4091089779405226, "14040.0": 1.9309662288658835, "17280.0": 0.90614590071077117, "2160.0": 4.5361731979596218, "5400.0": 6.071094394377246, "18360.0": 0.71270371205373184, "8640.0": 5.3475100856285955, "6480.0": 5.9202343949662177, "1080.0": 2.7532264453848811, "15120.0": 1.4880794860689583, "12960.0": 2.5406275798785134, "19440.0": 0.56254756816886675, "10800.0": 4.6684283481238458}, "alphac": 0.7, "Fa2": 0.0, "deltaH1": -40000, "Fa1": 0.003, "Tc2": 320, "Cc0": 0, "Tc1": 320, "Cc_meas": {"0.0": 9.1835496157991212e-40, "4320.0": 0.2042152487619561, "7560.0": 1.0742319748668527, "21600.0": 7.2209534629193319, "9720.0": 2.0351470130435847, "16200.0": 5.2015141957639486, "20520.0": 6.8469618370195322, "3240.0": 0.080409363629683248, "11880.0": 3.1846111102658314, "14040.0": 4.26052274570326, "17280.0": 5.6380747523602874, "2160.0": 0.020484309410223334, "5400.0": 0.4082391087574655, "18360.0": 6.0566679041163232, "8640.0": 1.5236474138282798, "6480.0": 0.69922443474303053, "1080.0": 0.0017732304596560309, "15120.0": 4.7439891962421239, "12960.0": 3.7433194976361364, "19440.0": 6.4591935065135972, "10800.0": 2.5966732389812686}, "alphaj": 0.8, "Cb0": 0, "Vr0": 1, "Cb_meas": {"0.0": 9.1835496157991212e-41, "4320.0": 3.7902671698338786, "7560.0": 8.430643849127673, "21600.0": 11.611715650093123, "9720.0": 10.913795239302978, "16200.0": 12.826899545942249, "20520.0": 11.893671316079821, "3240.0": 2.2947643625752363, "11880.0": 12.592179060461286, "14040.0": 12.994410174098332, "17280.0": 12.641678495596169, "2160.0": 1.0564916422363797, "5400.0": 5.3872034016274126, "18360.0": 12.416527532497087, "8640.0": 9.7522120688862319, "6480.0": 6.9615062931011256, "1080.0": 0.24785349222969222, "15120.0": 12.953830466356314, "12960.0": 12.901952071152909, "19440.0": 12.164158073984597, "10800.0": 11.920797561562614}, "Tf": 300, "Tr0": 300, "deltaH2": -50000, "Tr_meas": {"0.0": 300.0, "4320.0": 329.97137612867306, "7560.0": 333.16333833254259, "21600.0": 323.31193212521009, "9720.0": 333.2600929343854, "16200.0": 325.39648330671537, "20520.0": 323.56691057280642, "3240.0": 327.66452370998064, "11880.0": 331.65035718435655, "14040.0": 327.56747174535786, "17280.0": 324.74920150215411, "2160.0": 324.4585751379426, "5400.0": 331.60125794337375, "18360.0": 324.25971411725646, "8640.0": 333.33010300559897, "6480.0": 332.63089408099353, "1080.0": 318.87878296714581, "15120.0": 326.2893083756407, "12960.0": 329.38909200530219, "19440.0": 323.87612174726837, "10800.0": 333.08705569007822}} \ No newline at end of file diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/obj_at_theta.csv b/pyomo/contrib/parmest/deprecated/examples/semibatch/obj_at_theta.csv deleted file mode 100644 index 79f03e07dcd..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/semibatch/obj_at_theta.csv +++ /dev/null @@ -1,1009 +0,0 @@ -,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/deprecated/examples/semibatch/parallel_example.py b/pyomo/contrib/parmest/deprecated/examples/semibatch/parallel_example.py deleted file mode 100644 index ff1287811cf..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/semibatch/parallel_example.py +++ /dev/null @@ -1,57 +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. -# ___________________________________________________________________________ - -""" -The following script can be used to run semibatch parameter estimation in -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 itertools import product -from os.path import join, abspath, dirname -import pyomo.contrib.parmest.parmest as parmest -from pyomo.contrib.parmest.examples.semibatch.semibatch import generate_model - - -def main(): - # Vars to estimate - theta_names = ['k1', 'k2', 'E1', 'E2'] - - # Data, list of json file names - data = [] - file_dirname = dirname(abspath(str(__file__))) - for exp_num in range(10): - file_name = abspath(join(file_dirname, 'exp' + str(exp_num + 1) + '.out')) - data.append(file_name) - - # Note, the model already includes a 'SecondStageCost' expression - # for sum of squared error that will be used in parameter estimation - - pest = parmest.Estimator(generate_model, data, theta_names) - - ### Parameter estimation with bootstrap resampling - bootstrap_theta = pest.theta_est_bootstrap(100) - bootstrap_theta.to_csv('bootstrap_theta.csv') - - ### Compute objective at theta for likelihood ratio test - k1 = np.arange(4, 24, 3) - k2 = np.arange(40, 160, 40) - E1 = np.arange(29000, 32000, 500) - E2 = np.arange(38000, 42000, 500) - theta_vals = pd.DataFrame(list(product(k1, k2, E1, E2)), columns=theta_names) - - obj_at_theta = pest.objective_at_theta(theta_vals) - obj_at_theta.to_csv('obj_at_theta.csv') - - -if __name__ == "__main__": - main() diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/parameter_estimation_example.py b/pyomo/contrib/parmest/deprecated/examples/semibatch/parameter_estimation_example.py deleted file mode 100644 index fc4c9f5c675..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/semibatch/parameter_estimation_example.py +++ /dev/null @@ -1,42 +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. -# ___________________________________________________________________________ - -import json -from os.path import join, abspath, dirname -import pyomo.contrib.parmest.parmest as parmest -from pyomo.contrib.parmest.examples.semibatch.semibatch import generate_model - - -def main(): - # Vars to estimate - theta_names = ['k1', 'k2', 'E1', 'E2'] - - # Data, list of dictionaries - data = [] - file_dirname = dirname(abspath(str(__file__))) - for exp_num in range(10): - file_name = abspath(join(file_dirname, 'exp' + str(exp_num + 1) + '.out')) - with open(file_name, 'r') as infile: - d = json.load(infile) - data.append(d) - - # Note, the model already includes a 'SecondStageCost' expression - # for sum of squared error that will be used in parameter estimation - - pest = parmest.Estimator(generate_model, data, theta_names) - - obj, theta = pest.theta_est() - print(obj) - print(theta) - - -if __name__ == '__main__': - main() diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/scenario_example.py b/pyomo/contrib/parmest/deprecated/examples/semibatch/scenario_example.py deleted file mode 100644 index 071e53236c4..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/semibatch/scenario_example.py +++ /dev/null @@ -1,52 +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. -# ___________________________________________________________________________ - -import json -from os.path import join, abspath, dirname -import pyomo.contrib.parmest.parmest as parmest -from pyomo.contrib.parmest.examples.semibatch.semibatch import generate_model -import pyomo.contrib.parmest.scenariocreator as sc - - -def main(): - # Vars to estimate in parmest - theta_names = ['k1', 'k2', 'E1', 'E2'] - - # Data: list of dictionaries - data = [] - file_dirname = dirname(abspath(str(__file__))) - for exp_num in range(10): - fname = join(file_dirname, 'exp' + str(exp_num + 1) + '.out') - with open(fname, 'r') as infile: - d = json.load(infile) - data.append(d) - - pest = parmest.Estimator(generate_model, data, theta_names) - - scenmaker = sc.ScenarioCreator(pest, "ipopt") - - # Make one scenario per experiment and write to a csv file - output_file = "scenarios.csv" - experimentscens = sc.ScenarioSet("Experiments") - scenmaker.ScenariosFromExperiments(experimentscens) - experimentscens.write_csv(output_file) - - # Use the bootstrap to make 3 scenarios and print - bootscens = sc.ScenarioSet("Bootstrap") - scenmaker.ScenariosFromBootstrap(bootscens, 3) - for s in bootscens.ScensIterator(): - print("{}, {}".format(s.name, s.probability)) - for n, v in s.ThetaVals.items(): - print(" {}={}".format(n, v)) - - -if __name__ == "__main__": - main() diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/scenarios.csv b/pyomo/contrib/parmest/deprecated/examples/semibatch/scenarios.csv deleted file mode 100644 index 22f9a651bc3..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/semibatch/scenarios.csv +++ /dev/null @@ -1,11 +0,0 @@ -Name,Probability,k1,k2,E1,E2 -ExpScen0,0.1,25.800350800448314,14.14421520525348,31505.74905064048,35000.0 -ExpScen1,0.1,25.128373083865036,149.99999951481198,31452.336651974012,41938.781301641866 -ExpScen2,0.1,22.225574065344002,130.92739780265404,30948.669111672247,41260.15420929141 -ExpScen3,0.1,100.0,149.99999970011854,35182.73130744844,41444.52600373733 -ExpScen4,0.1,82.99114366189944,45.95424665995078,34810.857217141674,38300.633349887314 -ExpScen5,0.1,100.0,150.0,35142.20219150486,41495.41105795494 -ExpScen6,0.1,2.8743643265301118,149.99999477176598,25000.0,41431.61195969211 -ExpScen7,0.1,2.754580914035567,14.381786096822475,25000.0,35000.0 -ExpScen8,0.1,2.8743643265301118,149.99999477176598,25000.0,41431.61195969211 -ExpScen9,0.1,2.669780822294865,150.0,25000.0,41514.7476113499 diff --git a/pyomo/contrib/parmest/deprecated/examples/semibatch/semibatch.py b/pyomo/contrib/parmest/deprecated/examples/semibatch/semibatch.py deleted file mode 100644 index 6762531a338..00000000000 --- a/pyomo/contrib/parmest/deprecated/examples/semibatch/semibatch.py +++ /dev/null @@ -1,287 +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. -# ___________________________________________________________________________ -""" -Semibatch model, based on Nicholson et al. (2018). pyomo.dae: A modeling and -automatic discretization framework for optimization with di -erential and -algebraic equations. Mathematical Programming Computation, 10(2), 187-223. -""" -import json -from os.path import join, abspath, dirname -from pyomo.environ import ( - ConcreteModel, - Set, - Param, - Var, - Constraint, - ConstraintList, - Expression, - Objective, - TransformationFactory, - SolverFactory, - exp, - minimize, -) -from pyomo.dae import ContinuousSet, DerivativeVar - - -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"] - ccmeastemp = data["Cc_meas"] - trmeastemp = data["Tr_meas"] - - cameas = {} - cbmeas = {} - ccmeas = {} - trmeas = {} - for i in cameastemp.keys(): - cameas[float(i)] = cameastemp[i] - cbmeas[float(i)] = cbmeastemp[i] - ccmeas[float(i)] = ccmeastemp[i] - trmeas[float(i)] = trmeastemp[i] - - m = ConcreteModel() - - # - # Measurement Data - # - m.measT = Set(initialize=sorted(cameas.keys())) - m.Ca_meas = Param(m.measT, initialize=cameas) - m.Cb_meas = Param(m.measT, initialize=cbmeas) - m.Cc_meas = Param(m.measT, initialize=ccmeas) - m.Tr_meas = Param(m.measT, initialize=trmeas) - - # - # Parameters for semi-batch reactor model - # - m.R = Param(initialize=8.314) # kJ/kmol/K - m.Mwa = Param(initialize=50.0) # kg/kmol - m.rhor = Param(initialize=1000.0) # kg/m^3 - m.cpr = Param(initialize=3.9) # kJ/kg/K - m.Tf = Param(initialize=300) # K - m.deltaH1 = Param(initialize=-40000.0) # kJ/kmol - m.deltaH2 = Param(initialize=-50000.0) # kJ/kmol - m.alphaj = Param(initialize=0.8) # kJ/s/m^2/K - m.alphac = Param(initialize=0.7) # kJ/s/m^2/K - m.Aj = Param(initialize=5.0) # m^2 - m.Ac = Param(initialize=3.0) # m^2 - m.Vj = Param(initialize=0.9) # m^3 - 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.Tr0 = Param(initialize=300.0) # K - m.Vr0 = Param(initialize=1.0) # m^3 - - m.time = ContinuousSet(bounds=(0, 21600), initialize=m.measT) # Time in seconds - - # - # Control Inputs - # - def _initTc(m, t): - if t < 10800: - return data["Tc1"] - else: - return data["Tc2"] - - m.Tc = Param( - m.time, initialize=_initTc, default=_initTc - ) # bounds= (288,432) Cooling coil temp, control input - - def _initFa(m, t): - if t < 10800: - return data["Fa1"] - else: - return data["Fa2"] - - m.Fa = Param( - m.time, initialize=_initFa, default=_initFa - ) # bounds=(0,0.05) Inlet flow rate, control input - - # - # Parameters being estimated - # - m.k1 = Var(initialize=14, bounds=(2, 100)) # 1/s Actual: 15.01 - m.k2 = Var(initialize=90, bounds=(2, 150)) # 1/s Actual: 85.01 - m.E1 = Var(initialize=27000.0, bounds=(25000, 40000)) # kJ/kmol Actual: 30000 - m.E2 = Var(initialize=45000.0, bounds=(35000, 50000)) # kJ/kmol Actual: 40000 - # m.E1.fix(30000) - # m.E2.fix(40000) - - # - # Time dependent variables - # - m.Ca = Var(m.time, initialize=m.Ca0, bounds=(0, 25)) - m.Cb = Var(m.time, initialize=m.Cb0, bounds=(0, 25)) - m.Cc = Var(m.time, initialize=m.Cc0, bounds=(0, 25)) - m.Vr = Var(m.time, initialize=m.Vr0) - m.Tr = Var(m.time, initialize=m.Tr0) - m.Tj = Var( - m.time, initialize=310.0, bounds=(288, None) - ) # Cooling jacket temp, follows coil temp until failure - - # - # Derivatives in the model - # - m.dCa = DerivativeVar(m.Ca) - m.dCb = DerivativeVar(m.Cb) - m.dCc = DerivativeVar(m.Cc) - m.dVr = DerivativeVar(m.Vr) - m.dTr = DerivativeVar(m.Tr) - - # - # Differential Equations in the model - # - - def _dCacon(m, t): - if t == 0: - return Constraint.Skip - return ( - m.dCa[t] - == m.Fa[t] / m.Vr[t] - m.k1 * exp(-m.E1 / (m.R * m.Tr[t])) * m.Ca[t] - ) - - m.dCacon = Constraint(m.time, rule=_dCacon) - - def _dCbcon(m, t): - if t == 0: - return Constraint.Skip - return ( - m.dCb[t] - == m.k1 * exp(-m.E1 / (m.R * m.Tr[t])) * m.Ca[t] - - m.k2 * exp(-m.E2 / (m.R * m.Tr[t])) * m.Cb[t] - ) - - m.dCbcon = Constraint(m.time, rule=_dCbcon) - - def _dCccon(m, t): - if t == 0: - return Constraint.Skip - return m.dCc[t] == m.k2 * exp(-m.E2 / (m.R * m.Tr[t])) * m.Cb[t] - - m.dCccon = Constraint(m.time, rule=_dCccon) - - def _dVrcon(m, t): - if t == 0: - return Constraint.Skip - return m.dVr[t] == m.Fa[t] * m.Mwa / m.rhor - - m.dVrcon = Constraint(m.time, rule=_dVrcon) - - def _dTrcon(m, t): - if t == 0: - return Constraint.Skip - return m.rhor * m.cpr * m.dTr[t] == m.Fa[t] * m.Mwa * m.cpr / m.Vr[t] * ( - m.Tf - m.Tr[t] - ) - m.k1 * exp(-m.E1 / (m.R * m.Tr[t])) * m.Ca[t] * m.deltaH1 - m.k2 * exp( - -m.E2 / (m.R * m.Tr[t]) - ) * m.Cb[ - t - ] * m.deltaH2 + m.alphaj * m.Aj / m.Vr0 * ( - m.Tj[t] - m.Tr[t] - ) + m.alphac * m.Ac / m.Vr0 * ( - m.Tc[t] - m.Tr[t] - ) - - m.dTrcon = Constraint(m.time, rule=_dTrcon) - - def _singlecooling(m, t): - return m.Tc[t] == m.Tj[t] - - m.singlecooling = Constraint(m.time, rule=_singlecooling) - - # Initial Conditions - def _initcon(m): - yield m.Ca[m.time.first()] == m.Ca0 - yield m.Cb[m.time.first()] == m.Cb0 - yield m.Cc[m.time.first()] == m.Cc0 - yield m.Vr[m.time.first()] == m.Vr0 - yield m.Tr[m.time.first()] == m.Tr0 - - m.initcon = ConstraintList(rule=_initcon) - - # - # Stage-specific cost computations - # - def ComputeFirstStageCost_rule(model): - return 0 - - m.FirstStageCost = Expression(rule=ComputeFirstStageCost_rule) - - def AllMeasurements(m): - return sum( - (m.Ca[t] - m.Ca_meas[t]) ** 2 - + (m.Cb[t] - m.Cb_meas[t]) ** 2 - + (m.Cc[t] - m.Cc_meas[t]) ** 2 - + 0.01 * (m.Tr[t] - m.Tr_meas[t]) ** 2 - for t in m.measT - ) - - def MissingMeasurements(m): - if data["experiment"] == 1: - return sum( - (m.Ca[t] - m.Ca_meas[t]) ** 2 - + (m.Cb[t] - m.Cb_meas[t]) ** 2 - + (m.Cc[t] - m.Cc_meas[t]) ** 2 - + (m.Tr[t] - m.Tr_meas[t]) ** 2 - for t in m.measT - ) - elif data["experiment"] == 2: - return sum((m.Tr[t] - m.Tr_meas[t]) ** 2 for t in m.measT) - else: - return sum( - (m.Cb[t] - m.Cb_meas[t]) ** 2 + (m.Tr[t] - m.Tr_meas[t]) ** 2 - for t in m.measT - ) - - m.SecondStageCost = Expression(rule=MissingMeasurements) - - def total_cost_rule(model): - return model.FirstStageCost + model.SecondStageCost - - m.Total_Cost_Objective = Objective(rule=total_cost_rule, sense=minimize) - - # Discretize model - disc = TransformationFactory("dae.collocation") - disc.apply_to(m, nfe=20, ncp=4) - return m - - -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: - data = json.load(infile) - data["experiment"] = 2 - - model = generate_model(data) - solver = SolverFactory("ipopt") - solver.solve(model) - print("k1 = ", model.k1()) - print("E1 = ", model.E1()) - - -if __name__ == "__main__": - main() diff --git a/pyomo/contrib/parmest/deprecated/parmest.py b/pyomo/contrib/parmest/deprecated/parmest.py deleted file mode 100644 index 82bf893dd06..00000000000 --- a/pyomo/contrib/parmest/deprecated/parmest.py +++ /dev/null @@ -1,1361 +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. -# ___________________________________________________________________________ -#### Using mpi-sppy instead of PySP; May 2020 -#### Adding option for "local" EF starting Sept 2020 -#### Wrapping mpi-sppy functionality and local option Jan 2021, Feb 2021 - -# TODO: move use_mpisppy to a Pyomo configuration option -# -# False implies always use the EF that is local to parmest -use_mpisppy = True # Use it if we can but use local if not. -if use_mpisppy: - try: - # MPI-SPPY has an unfortunate side effect of outputting - # "[ 0.00] Initializing mpi-sppy" when it is imported. This can - # cause things like doctests to fail. We will suppress that - # information here. - from pyomo.common.tee import capture_output - - with capture_output(): - import mpisppy.utils.sputils as sputils - except ImportError: - use_mpisppy = False # we can't use it -if use_mpisppy: - # These things should be outside the try block. - sputils.disable_tictoc_output() - import mpisppy.opt.ef as st - import mpisppy.scenario_tree as scenario_tree -else: - import pyomo.contrib.parmest.utils.create_ef as local_ef - import pyomo.contrib.parmest.utils.scenario_tree as scenario_tree - -import re -import importlib as im -import logging -import types -import json -from itertools import combinations - -from pyomo.common.dependencies import ( - attempt_import, - numpy as np, - numpy_available, - pandas as pd, - pandas_available, - scipy, - scipy_available, -) - -import pyomo.environ as pyo - -from pyomo.opt import SolverFactory -from pyomo.environ import Block, ComponentUID - -import pyomo.contrib.parmest.utils as utils -import pyomo.contrib.parmest.graphics as graphics -from pyomo.dae import ContinuousSet - -parmest_available = numpy_available & pandas_available & scipy_available - -inverse_reduced_hessian, inverse_reduced_hessian_available = attempt_import( - 'pyomo.contrib.interior_point.inverse_reduced_hessian' -) - -logger = logging.getLogger(__name__) - - -def ef_nonants(ef): - # Wrapper to call someone's ef_nonants - # (the function being called is very short, but it might be changed) - if use_mpisppy: - return sputils.ef_nonants(ef) - else: - return local_ef.ef_nonants(ef) - - -def _experiment_instance_creation_callback( - scenario_name, node_names=None, cb_data=None -): - """ - This is going to be called by mpi-sppy or the local EF and it will call into - the user's model's callback. - - Parameters: - ----------- - scenario_name: `str` Scenario name should end with a number - node_names: `None` ( Not used here ) - cb_data : dict with ["callback"], ["BootList"], - ["theta_names"], ["cb_data"], etc. - "cb_data" is passed through to user's callback function - that is the "callback" value. - "BootList" is None or bootstrap experiment number list. - (called cb_data by mpisppy) - - - Returns: - -------- - instance: `ConcreteModel` - instantiated scenario - - Note: - ---- - There is flexibility both in how the function is passed and its signature. - """ - assert cb_data is not None - outer_cb_data = cb_data - scen_num_str = re.compile(r'(\d+)$').search(scenario_name).group(1) - scen_num = int(scen_num_str) - basename = scenario_name[: -len(scen_num_str)] # to reconstruct name - - CallbackFunction = outer_cb_data["callback"] - - if callable(CallbackFunction): - callback = CallbackFunction - else: - cb_name = CallbackFunction - - if "CallbackModule" not in outer_cb_data: - raise RuntimeError( - "Internal Error: need CallbackModule in parmest callback" - ) - else: - modname = outer_cb_data["CallbackModule"] - - if isinstance(modname, str): - cb_module = im.import_module(modname, package=None) - elif isinstance(modname, types.ModuleType): - cb_module = modname - else: - print("Internal Error: bad CallbackModule") - raise - - try: - callback = getattr(cb_module, cb_name) - except: - print("Error getting function=" + cb_name + " from module=" + str(modname)) - raise - - if "BootList" in outer_cb_data: - bootlist = outer_cb_data["BootList"] - # print("debug in callback: using bootlist=",str(bootlist)) - # assuming bootlist itself is zero based - exp_num = bootlist[scen_num] - else: - exp_num = scen_num - - scen_name = basename + str(exp_num) - - cb_data = outer_cb_data["cb_data"] # cb_data might be None. - - # at least three signatures are supported. The first is preferred - try: - instance = callback(experiment_number=exp_num, cb_data=cb_data) - except TypeError: - raise RuntimeError( - "Only one callback signature is supported: " - "callback(experiment_number, cb_data) " - ) - """ - try: - instance = callback(scenario_tree_model, scen_name, node_names) - except TypeError: # deprecated signature? - try: - instance = callback(scen_name, node_names) - except: - print("Failed to create instance using callback; TypeError+") - raise - except: - print("Failed to create instance using callback.") - raise - """ - if hasattr(instance, "_mpisppy_node_list"): - raise RuntimeError(f"scenario for experiment {exp_num} has _mpisppy_node_list") - nonant_list = [ - instance.find_component(vstr) for vstr in outer_cb_data["theta_names"] - ] - if use_mpisppy: - instance._mpisppy_node_list = [ - scenario_tree.ScenarioNode( - name="ROOT", - cond_prob=1.0, - stage=1, - cost_expression=instance.FirstStageCost, - nonant_list=nonant_list, - scen_model=instance, - ) - ] - else: - instance._mpisppy_node_list = [ - scenario_tree.ScenarioNode( - name="ROOT", - cond_prob=1.0, - stage=1, - cost_expression=instance.FirstStageCost, - scen_name_list=None, - nonant_list=nonant_list, - scen_model=instance, - ) - ] - - if "ThetaVals" in outer_cb_data: - thetavals = outer_cb_data["ThetaVals"] - - # dlw august 2018: see mea code for more general theta - for vstr in thetavals: - theta_cuid = ComponentUID(vstr) - theta_object = theta_cuid.find_component_on(instance) - if thetavals[vstr] is not None: - # print("Fixing",vstr,"at",str(thetavals[vstr])) - theta_object.fix(thetavals[vstr]) - else: - # print("Freeing",vstr) - theta_object.unfix() - - return instance - - -# ============================================= -def _treemaker(scenlist): - """ - Makes a scenario tree (avoids dependence on daps) - - Parameters - ---------- - scenlist (list of `int`): experiment (i.e. scenario) numbers - - Returns - ------- - a `ConcreteModel` that is the scenario tree - """ - - num_scenarios = len(scenlist) - m = scenario_tree.tree_structure_model.CreateAbstractScenarioTreeModel() - m = m.create_instance() - m.Stages.add('Stage1') - m.Stages.add('Stage2') - m.Nodes.add('RootNode') - for i in scenlist: - m.Nodes.add('LeafNode_Experiment' + str(i)) - m.Scenarios.add('Experiment' + str(i)) - m.NodeStage['RootNode'] = 'Stage1' - m.ConditionalProbability['RootNode'] = 1.0 - for node in m.Nodes: - if node != 'RootNode': - m.NodeStage[node] = 'Stage2' - m.Children['RootNode'].add(node) - m.Children[node].clear() - m.ConditionalProbability[node] = 1.0 / num_scenarios - m.ScenarioLeafNode[node.replace('LeafNode_', '')] = node - - return m - - -def group_data(data, groupby_column_name, use_mean=None): - """ - Group data by scenario - - Parameters - ---------- - data: DataFrame - Data - groupby_column_name: strings - Name of data column which contains scenario numbers - use_mean: list of column names or None, optional - Name of data columns which should be reduced to a single value per - scenario by taking the mean - - Returns - ---------- - grouped_data: list of dictionaries - Grouped data - """ - if use_mean is None: - use_mean_list = [] - else: - use_mean_list = use_mean - - grouped_data = [] - for exp_num, group in data.groupby(data[groupby_column_name]): - d = {} - for col in group.columns: - if col in use_mean_list: - d[col] = group[col].mean() - else: - d[col] = list(group[col]) - grouped_data.append(d) - - return grouped_data - - -class _SecondStageCostExpr(object): - """ - Class to pass objective expression into the Pyomo model - """ - - def __init__(self, ssc_function, data): - self._ssc_function = ssc_function - self._data = data - - def __call__(self, model): - return self._ssc_function(model, self._data) - - -class Estimator(object): - """ - Parameter estimation class - - Parameters - ---------- - model_function: function - Function that generates an instance of the Pyomo model using 'data' - as the input argument - data: pd.DataFrame, list of dictionaries, list of dataframes, or list of json file names - Data that is used to build an instance of the Pyomo model and build - the objective function - theta_names: list of strings - List of Var names to estimate - obj_function: function, optional - Function used to formulate parameter estimation objective, generally - sum of squared error between measurements and model variables. - If no function is specified, the model is used - "as is" and should be defined with a "FirstStageCost" and - "SecondStageCost" expression that are used to build an objective. - tee: bool, optional - Indicates that ef solver output should be teed - diagnostic_mode: bool, optional - If True, print diagnostics from the solver - solver_options: dict, optional - Provides options to the solver (also the name of an attribute) - """ - - def __init__( - self, - model_function, - data, - theta_names, - obj_function=None, - tee=False, - diagnostic_mode=False, - solver_options=None, - ): - self.model_function = model_function - - assert isinstance( - data, (list, pd.DataFrame) - ), "Data must be a list or DataFrame" - # convert dataframe into a list of dataframes, each row = one scenario - if isinstance(data, pd.DataFrame): - self.callback_data = [ - data.loc[i, :].to_frame().transpose() for i in data.index - ] - else: - self.callback_data = data - assert isinstance( - self.callback_data[0], (dict, pd.DataFrame, str) - ), "The scenarios in data must be a dictionary, DataFrame or filename" - - if len(theta_names) == 0: - self.theta_names = ['parmest_dummy_var'] - else: - self.theta_names = theta_names - - self.obj_function = obj_function - self.tee = tee - self.diagnostic_mode = diagnostic_mode - self.solver_options = solver_options - - self._second_stage_cost_exp = "SecondStageCost" - # boolean to indicate if model is initialized using a square solve - self.model_initialized = False - - def _return_theta_names(self): - """ - Return list of fitted model parameter names - """ - # if fitted model parameter names differ from theta_names created when Estimator object is created - if hasattr(self, 'theta_names_updated'): - return self.theta_names_updated - - else: - return ( - self.theta_names - ) # default theta_names, created when Estimator object is created - - def _create_parmest_model(self, data): - """ - Modify the Pyomo model for parameter estimation - """ - model = self.model_function(data) - - if (len(self.theta_names) == 1) and ( - self.theta_names[0] == 'parmest_dummy_var' - ): - model.parmest_dummy_var = pyo.Var(initialize=1.0) - - # Add objective function (optional) - if self.obj_function: - for obj in model.component_objects(pyo.Objective): - if obj.name in ["Total_Cost_Objective"]: - raise RuntimeError( - "Parmest will not override the existing model Objective named " - + obj.name - ) - obj.deactivate() - - for expr in model.component_data_objects(pyo.Expression): - if expr.name in ["FirstStageCost", "SecondStageCost"]: - raise RuntimeError( - "Parmest will not override the existing model Expression named " - + expr.name - ) - model.FirstStageCost = pyo.Expression(expr=0) - model.SecondStageCost = pyo.Expression( - rule=_SecondStageCostExpr(self.obj_function, data) - ) - - def TotalCost_rule(model): - return model.FirstStageCost + model.SecondStageCost - - model.Total_Cost_Objective = pyo.Objective( - rule=TotalCost_rule, sense=pyo.minimize - ) - - # Convert theta Params to Vars, and unfix theta Vars - model = utils.convert_params_to_vars(model, self.theta_names) - - # Update theta names list to use CUID string representation - for i, theta in enumerate(self.theta_names): - var_cuid = ComponentUID(theta) - var_validate = var_cuid.find_component_on(model) - if var_validate is None: - logger.warning( - "theta_name[%s] (%s) was not found on the model", (i, theta) - ) - else: - try: - # If the component is not a variable, - # this will generate an exception (and the warning - # in the 'except') - var_validate.unfix() - self.theta_names[i] = repr(var_cuid) - except: - logger.warning(theta + ' is not a variable') - - self.parmest_model = model - - return model - - def _instance_creation_callback(self, experiment_number=None, cb_data=None): - # cb_data is a list of dictionaries, list of dataframes, OR list of json file names - exp_data = cb_data[experiment_number] - if isinstance(exp_data, (dict, pd.DataFrame)): - pass - elif isinstance(exp_data, str): - try: - with open(exp_data, 'r') as infile: - exp_data = json.load(infile) - except: - raise RuntimeError(f'Could not read {exp_data} as json') - else: - raise RuntimeError(f'Unexpected data format for cb_data={cb_data}') - model = self._create_parmest_model(exp_data) - - return model - - def _Q_opt( - self, - ThetaVals=None, - solver="ef_ipopt", - return_values=[], - bootlist=None, - calc_cov=False, - cov_n=None, - ): - """ - Set up all thetas as first stage Vars, return resulting theta - values as well as the objective function value. - - """ - if solver == "k_aug": - raise RuntimeError("k_aug no longer supported.") - - # (Bootstrap scenarios will use indirection through the bootlist) - if bootlist is None: - scenario_numbers = list(range(len(self.callback_data))) - scen_names = ["Scenario{}".format(i) for i in scenario_numbers] - else: - scen_names = ["Scenario{}".format(i) for i in range(len(bootlist))] - - # tree_model.CallbackModule = None - outer_cb_data = dict() - outer_cb_data["callback"] = self._instance_creation_callback - if ThetaVals is not None: - outer_cb_data["ThetaVals"] = ThetaVals - if bootlist is not None: - outer_cb_data["BootList"] = bootlist - outer_cb_data["cb_data"] = self.callback_data # None is OK - outer_cb_data["theta_names"] = self.theta_names - - options = {"solver": "ipopt"} - scenario_creator_options = {"cb_data": outer_cb_data} - if use_mpisppy: - ef = sputils.create_EF( - scen_names, - _experiment_instance_creation_callback, - EF_name="_Q_opt", - suppress_warnings=True, - scenario_creator_kwargs=scenario_creator_options, - ) - else: - ef = local_ef.create_EF( - scen_names, - _experiment_instance_creation_callback, - EF_name="_Q_opt", - suppress_warnings=True, - scenario_creator_kwargs=scenario_creator_options, - ) - self.ef_instance = ef - - # Solve the extensive form with ipopt - if solver == "ef_ipopt": - if not calc_cov: - # Do not calculate the reduced hessian - - solver = SolverFactory('ipopt') - if self.solver_options is not None: - for key in self.solver_options: - solver.options[key] = self.solver_options[key] - - solve_result = solver.solve(self.ef_instance, tee=self.tee) - - # The import error will be raised when we attempt to use - # inv_reduced_hessian_barrier below. - # - # elif not asl_available: - # raise ImportError("parmest requires ASL to calculate the " - # "covariance matrix with solver 'ipopt'") - else: - # parmest makes the fitted parameters stage 1 variables - ind_vars = [] - 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, - ) - ) - - if self.diagnostic_mode: - print( - ' Solver termination condition = ', - str(solve_result.solver.termination_condition), - ) - - # assume all first stage are thetas... - thetavals = {} - for ndname, Var, solval in ef_nonants(ef): - # process the name - # the scenarios are blocks, so strip the scenario name - vname = Var.name[Var.name.find(".") + 1 :] - thetavals[vname] = solval - - objval = pyo.value(ef.EF_Obj) - - if calc_cov: - # Calculate the covariance matrix - - # Number of data points considered - n = cov_n - - # Extract number of fitted parameters - l = len(thetavals) - - # Assumption: Objective value is sum of squared errors - sse = objval - - '''Calculate covariance assuming experimental observation errors are - independent and follow a Gaussian - distribution with constant variance. - - The formula used in parmest was verified against equations (7-5-15) and - (7-5-16) in "Nonlinear Parameter Estimation", Y. Bard, 1974. - - This formula is also applicable if the objective is scaled by a constant; - the constant cancels out. (was scaled by 1/n because it computes an - expected value.) - ''' - cov = 2 * sse / (n - l) * inv_red_hes - cov = pd.DataFrame( - cov, index=thetavals.keys(), columns=thetavals.keys() - ) - - thetavals = pd.Series(thetavals) - - if len(return_values) > 0: - var_values = [] - if len(scen_names) > 1: # multiple scenarios - block_objects = self.ef_instance.component_objects( - Block, descend_into=False - ) - else: # single scenario - block_objects = [self.ef_instance] - for exp_i in block_objects: - vals = {} - for var in return_values: - exp_i_var = exp_i.find_component(str(var)) - if ( - exp_i_var is None - ): # we might have a block such as _mpisppy_data - continue - # if value to return is ContinuousSet - if type(exp_i_var) == ContinuousSet: - temp = list(exp_i_var) - else: - temp = [pyo.value(_) for _ in exp_i_var.values()] - if len(temp) == 1: - vals[var] = temp[0] - else: - vals[var] = temp - if len(vals) > 0: - var_values.append(vals) - var_values = pd.DataFrame(var_values) - if calc_cov: - return objval, thetavals, var_values, cov - else: - return objval, thetavals, var_values - - if calc_cov: - return objval, thetavals, cov - else: - return objval, thetavals - - else: - raise RuntimeError("Unknown solver in Q_Opt=" + solver) - - def _Q_at_theta(self, thetavals, initialize_parmest_model=False): - """ - Return the objective function value with fixed theta values. - - Parameters - ---------- - thetavals: dict - A dictionary of theta values. - - initialize_parmest_model: boolean - If True: Solve square problem instance, build extensive form of the model for - parameter estimation, and set flag model_initialized to True - - Returns - ------- - objectiveval: float - The objective function value. - thetavals: dict - A dictionary of all values for theta that were input. - solvertermination: Pyomo TerminationCondition - Tries to return the "worst" solver status across the scenarios. - pyo.TerminationCondition.optimal is the best and - pyo.TerminationCondition.infeasible is the worst. - """ - - optimizer = pyo.SolverFactory('ipopt') - - if len(thetavals) > 0: - dummy_cb = { - "callback": self._instance_creation_callback, - "ThetaVals": thetavals, - "theta_names": self._return_theta_names(), - "cb_data": self.callback_data, - } - else: - dummy_cb = { - "callback": self._instance_creation_callback, - "theta_names": self._return_theta_names(), - "cb_data": self.callback_data, - } - - if self.diagnostic_mode: - if len(thetavals) > 0: - print(' Compute objective at theta = ', str(thetavals)) - else: - print(' Compute objective at initial theta') - - # start block of code to deal with models with no constraints - # (ipopt will crash or complain on such problems without special care) - instance = _experiment_instance_creation_callback("FOO0", None, dummy_cb) - try: # deal with special problems so Ipopt will not crash - first = next(instance.component_objects(pyo.Constraint, active=True)) - active_constraints = True - except: - active_constraints = False - # end block of code to deal with models with no constraints - - WorstStatus = pyo.TerminationCondition.optimal - totobj = 0 - scenario_numbers = list(range(len(self.callback_data))) - if initialize_parmest_model: - # create dictionary to store pyomo model instances (scenarios) - scen_dict = dict() - - for snum in scenario_numbers: - sname = "scenario_NODE" + str(snum) - instance = _experiment_instance_creation_callback(sname, None, dummy_cb) - - if initialize_parmest_model: - # list to store fitted parameter names that will be unfixed - # after initialization - theta_init_vals = [] - # use appropriate theta_names member - theta_ref = self._return_theta_names() - - for i, theta in enumerate(theta_ref): - # Use parser in ComponentUID to locate the component - var_cuid = ComponentUID(theta) - var_validate = var_cuid.find_component_on(instance) - if var_validate is None: - logger.warning( - "theta_name %s was not found on the model", (theta) - ) - else: - try: - if len(thetavals) == 0: - var_validate.fix() - else: - var_validate.fix(thetavals[theta]) - theta_init_vals.append(var_validate) - except: - logger.warning( - 'Unable to fix model parameter value for %s (not a Pyomo model Var)', - (theta), - ) - - if active_constraints: - 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 - ) - ) - print( - " status_obj, solved, iters, time, regularization_stat = ", - str(status_obj), - str(solved), - str(iters), - str(time), - str(regu), - ) - - results = optimizer.solve(instance) - if self.diagnostic_mode: - print( - 'standard solve solver termination condition=', - str(results.solver.termination_condition), - ) - - if ( - results.solver.termination_condition - != pyo.TerminationCondition.optimal - ): - # DLW: Aug2018: not distinguishing "middlish" conditions - if WorstStatus != pyo.TerminationCondition.infeasible: - WorstStatus = results.solver.termination_condition - if initialize_parmest_model: - if self.diagnostic_mode: - print( - "Scenario {:d} infeasible with initialized parameter values".format( - snum - ) - ) - else: - if initialize_parmest_model: - if self.diagnostic_mode: - print( - "Scenario {:d} initialization successful with initial parameter values".format( - snum - ) - ) - if initialize_parmest_model: - # unfix parameters after initialization - for theta in theta_init_vals: - theta.unfix() - scen_dict[sname] = instance - else: - if initialize_parmest_model: - # unfix parameters after initialization - for theta in theta_init_vals: - theta.unfix() - scen_dict[sname] = instance - - objobject = getattr(instance, self._second_stage_cost_exp) - objval = pyo.value(objobject) - totobj += objval - - retval = totobj / len(scenario_numbers) # -1?? - if initialize_parmest_model and not hasattr(self, 'ef_instance'): - # create extensive form of the model using scenario dictionary - if len(scen_dict) > 0: - for scen in scen_dict.values(): - scen._mpisppy_probability = 1 / len(scen_dict) - - if use_mpisppy: - EF_instance = sputils._create_EF_from_scen_dict( - scen_dict, - EF_name="_Q_at_theta", - # suppress_warnings=True - ) - else: - EF_instance = local_ef._create_EF_from_scen_dict( - scen_dict, EF_name="_Q_at_theta", nonant_for_fixed_vars=True - ) - - self.ef_instance = EF_instance - # set self.model_initialized flag to True to skip extensive form model - # creation using theta_est() - self.model_initialized = True - - # return initialized theta values - if len(thetavals) == 0: - # use appropriate theta_names member - theta_ref = self._return_theta_names() - for i, theta in enumerate(theta_ref): - thetavals[theta] = theta_init_vals[i]() - - return retval, thetavals, WorstStatus - - def _get_sample_list(self, samplesize, num_samples, replacement=True): - samplelist = list() - - scenario_numbers = list(range(len(self.callback_data))) - - if num_samples is None: - # This could get very large - for i, l in enumerate(combinations(scenario_numbers, samplesize)): - samplelist.append((i, np.sort(l))) - else: - for i in range(num_samples): - attempts = 0 - unique_samples = 0 # check for duplicates in each sample - duplicate = False # check for duplicates between samples - while (unique_samples <= len(self._return_theta_names())) and ( - not duplicate - ): - sample = np.random.choice( - scenario_numbers, samplesize, replace=replacement - ) - sample = np.sort(sample).tolist() - unique_samples = len(np.unique(sample)) - if sample in samplelist: - duplicate = True - - attempts += 1 - if attempts > num_samples: # arbitrary timeout limit - raise RuntimeError( - """Internal error: timeout constructing - a sample, the dim of theta may be too - close to the samplesize""" - ) - - samplelist.append((i, sample)) - - return samplelist - - def theta_est( - self, solver="ef_ipopt", return_values=[], calc_cov=False, cov_n=None - ): - """ - Parameter estimation using all scenarios in the data - - Parameters - ---------- - solver: string, optional - Currently only "ef_ipopt" is supported. Default is "ef_ipopt". - return_values: list, optional - List of Variable names, used to return values from the model for data reconciliation - calc_cov: boolean, optional - If True, calculate and return the covariance matrix (only for "ef_ipopt" solver) - cov_n: int, optional - If calc_cov=True, then the user needs to supply the number of datapoints - that are used in the objective function - - Returns - ------- - objectiveval: float - The objective function value - thetavals: pd.Series - Estimated values for theta - variable values: pd.DataFrame - Variable values for each variable name in return_values (only for solver='ef_ipopt') - cov: pd.DataFrame - Covariance matrix of the fitted parameters (only for solver='ef_ipopt') - """ - assert isinstance(solver, str) - assert isinstance(return_values, list) - assert isinstance(calc_cov, bool) - if calc_cov: - assert isinstance( - cov_n, int - ), "The number of datapoints that are used in the objective function is required to calculate the covariance matrix" - assert cov_n > len( - self._return_theta_names() - ), "The number of datapoints must be greater than the number of parameters to estimate" - - return self._Q_opt( - solver=solver, - return_values=return_values, - bootlist=None, - calc_cov=calc_cov, - cov_n=cov_n, - ) - - def theta_est_bootstrap( - self, - bootstrap_samples, - samplesize=None, - replacement=True, - seed=None, - return_samples=False, - ): - """ - Parameter estimation using bootstrap resampling of the data - - Parameters - ---------- - bootstrap_samples: int - Number of bootstrap samples to draw from the data - samplesize: int or None, optional - Size of each bootstrap sample. If samplesize=None, samplesize will be - set to the number of samples in the data - replacement: bool, optional - Sample with or without replacement - seed: int or None, optional - Random seed - return_samples: bool, optional - Return a list of sample numbers used in each bootstrap estimation - - Returns - ------- - bootstrap_theta: pd.DataFrame - Theta values for each sample and (if return_samples = True) - the sample numbers used in each estimation - """ - assert isinstance(bootstrap_samples, int) - assert isinstance(samplesize, (type(None), int)) - assert isinstance(replacement, bool) - assert isinstance(seed, (type(None), int)) - assert isinstance(return_samples, bool) - - if samplesize is None: - samplesize = len(self.callback_data) - - if seed is not None: - np.random.seed(seed) - - global_list = self._get_sample_list(samplesize, bootstrap_samples, replacement) - - task_mgr = utils.ParallelTaskManager(bootstrap_samples) - local_list = task_mgr.global_to_local_data(global_list) - - bootstrap_theta = list() - for idx, sample in local_list: - objval, thetavals = self._Q_opt(bootlist=list(sample)) - thetavals['samples'] = sample - bootstrap_theta.append(thetavals) - - global_bootstrap_theta = task_mgr.allgather_global_data(bootstrap_theta) - bootstrap_theta = pd.DataFrame(global_bootstrap_theta) - - if not return_samples: - del bootstrap_theta['samples'] - - return bootstrap_theta - - def theta_est_leaveNout( - self, lNo, lNo_samples=None, seed=None, return_samples=False - ): - """ - Parameter estimation where N data points are left out of each sample - - Parameters - ---------- - lNo: int - Number of data points to leave out for parameter estimation - lNo_samples: int - Number of leave-N-out samples. If lNo_samples=None, the maximum - number of combinations will be used - seed: int or None, optional - Random seed - return_samples: bool, optional - Return a list of sample numbers that were left out - - Returns - ------- - lNo_theta: pd.DataFrame - Theta values for each sample and (if return_samples = True) - the sample numbers left out of each estimation - """ - assert isinstance(lNo, int) - assert isinstance(lNo_samples, (type(None), int)) - assert isinstance(seed, (type(None), int)) - assert isinstance(return_samples, bool) - - samplesize = len(self.callback_data) - lNo - - if seed is not None: - np.random.seed(seed) - - global_list = self._get_sample_list(samplesize, lNo_samples, replacement=False) - - task_mgr = utils.ParallelTaskManager(len(global_list)) - local_list = task_mgr.global_to_local_data(global_list) - - lNo_theta = list() - for idx, sample in local_list: - objval, thetavals = self._Q_opt(bootlist=list(sample)) - lNo_s = list(set(range(len(self.callback_data))) - set(sample)) - thetavals['lNo'] = np.sort(lNo_s) - lNo_theta.append(thetavals) - - global_bootstrap_theta = task_mgr.allgather_global_data(lNo_theta) - lNo_theta = pd.DataFrame(global_bootstrap_theta) - - if not return_samples: - del lNo_theta['lNo'] - - return lNo_theta - - def leaveNout_bootstrap_test( - self, lNo, lNo_samples, bootstrap_samples, distribution, alphas, seed=None - ): - """ - Leave-N-out bootstrap test to compare theta values where N data points are - left out to a bootstrap analysis using the remaining data, - results indicate if theta is within a confidence region - determined by the bootstrap analysis - - Parameters - ---------- - lNo: int - Number of data points to leave out for parameter estimation - lNo_samples: int - Leave-N-out sample size. If lNo_samples=None, the maximum number - of combinations will be used - bootstrap_samples: int: - Bootstrap sample size - distribution: string - Statistical distribution used to define a confidence region, - options = 'MVN' for multivariate_normal, 'KDE' for gaussian_kde, - and 'Rect' for rectangular. - alphas: list - List of alpha values used to determine if theta values are inside - or outside the region. - seed: int or None, optional - Random seed - - Returns - ---------- - List of tuples with one entry per lNo_sample: - - * The first item in each tuple is the list of N samples that are left - out. - * The second item in each tuple is a DataFrame of theta estimated using - the N samples. - * The third item in each tuple is a DataFrame containing results from - the bootstrap analysis using the remaining samples. - - For each DataFrame a column is added for each value of alpha which - indicates if the theta estimate is in (True) or out (False) of the - alpha region for a given distribution (based on the bootstrap results) - """ - assert isinstance(lNo, int) - assert isinstance(lNo_samples, (type(None), int)) - assert isinstance(bootstrap_samples, int) - assert distribution in ['Rect', 'MVN', 'KDE'] - assert isinstance(alphas, list) - assert isinstance(seed, (type(None), int)) - - if seed is not None: - np.random.seed(seed) - - data = self.callback_data.copy() - - global_list = self._get_sample_list(lNo, lNo_samples, replacement=False) - - results = [] - for idx, sample in global_list: - # Reset callback_data to only include the sample - self.callback_data = [data[i] for i in sample] - - obj, theta = self.theta_est() - - # Reset callback_data to include all scenarios except the sample - self.callback_data = [data[i] for i in range(len(data)) if i not in sample] - - bootstrap_theta = self.theta_est_bootstrap(bootstrap_samples) - - training, test = self.confidence_region_test( - bootstrap_theta, - distribution=distribution, - alphas=alphas, - test_theta_values=theta, - ) - - results.append((sample, test, training)) - - # Reset callback_data (back to full data set) - self.callback_data = data - - return results - - def objective_at_theta(self, theta_values=None, initialize_parmest_model=False): - """ - Objective value for each theta - - Parameters - ---------- - theta_values: pd.DataFrame, columns=theta_names - Values of theta used to compute the objective - - initialize_parmest_model: boolean - If True: Solve square problem instance, build extensive form of the model for - parameter estimation, and set flag model_initialized to True - - - Returns - ------- - obj_at_theta: pd.DataFrame - Objective value for each theta (infeasible solutions are - omitted). - """ - if len(self.theta_names) == 1 and self.theta_names[0] == 'parmest_dummy_var': - pass # skip assertion if model has no fitted parameters - else: - # create a local instance of the pyomo model to access model variables and parameters - model_temp = self._create_parmest_model(self.callback_data[0]) - model_theta_list = [] # list to store indexed and non-indexed parameters - # iterate over original theta_names - for theta_i in self.theta_names: - var_cuid = ComponentUID(theta_i) - var_validate = var_cuid.find_component_on(model_temp) - # check if theta in theta_names are indexed - try: - # get component UID of Set over which theta is defined - set_cuid = ComponentUID(var_validate.index_set()) - # access and iterate over the Set to generate theta names as they appear - # in the pyomo model - set_validate = set_cuid.find_component_on(model_temp) - for s in set_validate: - self_theta_temp = repr(var_cuid) + "[" + repr(s) + "]" - # generate list of theta names - model_theta_list.append(self_theta_temp) - # if theta is not indexed, copy theta name to list as-is - except AttributeError: - self_theta_temp = repr(var_cuid) - model_theta_list.append(self_theta_temp) - except: - raise - # if self.theta_names is not the same as temp model_theta_list, - # create self.theta_names_updated - if set(self.theta_names) == set(model_theta_list) and len( - self.theta_names - ) == set(model_theta_list): - pass - else: - self.theta_names_updated = model_theta_list - - if theta_values is None: - all_thetas = {} # dictionary to store fitted variables - # use appropriate theta names member - theta_names = self._return_theta_names() - else: - assert isinstance(theta_values, pd.DataFrame) - # for parallel code we need to use lists and dicts in the loop - theta_names = theta_values.columns - # # check if theta_names are in model - for theta in list(theta_names): - theta_temp = theta.replace("'", "") # cleaning quotes from theta_names - - assert theta_temp in [ - t.replace("'", "") for t in model_theta_list - ], "Theta name {} in 'theta_values' not in 'theta_names' {}".format( - theta_temp, model_theta_list - ) - assert len(list(theta_names)) == len(model_theta_list) - - all_thetas = theta_values.to_dict('records') - - if all_thetas: - task_mgr = utils.ParallelTaskManager(len(all_thetas)) - local_thetas = task_mgr.global_to_local_data(all_thetas) - else: - if initialize_parmest_model: - task_mgr = utils.ParallelTaskManager( - 1 - ) # initialization performed using just 1 set of theta values - # walk over the mesh, return objective function - all_obj = list() - if len(all_thetas) > 0: - for Theta in local_thetas: - obj, thetvals, worststatus = self._Q_at_theta( - Theta, initialize_parmest_model=initialize_parmest_model - ) - if worststatus != pyo.TerminationCondition.infeasible: - all_obj.append(list(Theta.values()) + [obj]) - # DLW, Aug2018: should we also store the worst solver status? - else: - obj, thetvals, worststatus = self._Q_at_theta( - thetavals={}, initialize_parmest_model=initialize_parmest_model - ) - if worststatus != pyo.TerminationCondition.infeasible: - all_obj.append(list(thetvals.values()) + [obj]) - - global_all_obj = task_mgr.allgather_global_data(all_obj) - dfcols = list(theta_names) + ['obj'] - obj_at_theta = pd.DataFrame(data=global_all_obj, columns=dfcols) - return obj_at_theta - - def likelihood_ratio_test( - self, obj_at_theta, obj_value, alphas, return_thresholds=False - ): - r""" - Likelihood ratio test to identify theta values within a confidence - region using the :math:`\chi^2` distribution - - Parameters - ---------- - obj_at_theta: pd.DataFrame, columns = theta_names + 'obj' - Objective values for each theta value (returned by - objective_at_theta) - obj_value: int or float - Objective value from parameter estimation using all data - alphas: list - List of alpha values to use in the chi2 test - return_thresholds: bool, optional - Return the threshold value for each alpha - - Returns - ------- - LR: pd.DataFrame - Objective values for each theta value along with True or False for - each alpha - thresholds: pd.Series - If return_threshold = True, the thresholds are also returned. - """ - assert isinstance(obj_at_theta, pd.DataFrame) - assert isinstance(obj_value, (int, float)) - assert isinstance(alphas, list) - assert isinstance(return_thresholds, bool) - - LR = obj_at_theta.copy() - S = len(self.callback_data) - thresholds = {} - for a in alphas: - chi2_val = scipy.stats.chi2.ppf(a, 2) - thresholds[a] = obj_value * ((chi2_val / (S - 2)) + 1) - LR[a] = LR['obj'] < thresholds[a] - - thresholds = pd.Series(thresholds) - - if return_thresholds: - return LR, thresholds - else: - return LR - - def confidence_region_test( - self, theta_values, distribution, alphas, test_theta_values=None - ): - """ - Confidence region test to determine if theta values are within a - rectangular, multivariate normal, or Gaussian kernel density distribution - for a range of alpha values - - Parameters - ---------- - theta_values: pd.DataFrame, columns = theta_names - Theta values used to generate a confidence region - (generally returned by theta_est_bootstrap) - distribution: string - Statistical distribution used to define a confidence region, - options = 'MVN' for multivariate_normal, 'KDE' for gaussian_kde, - and 'Rect' for rectangular. - alphas: list - List of alpha values used to determine if theta values are inside - or outside the region. - test_theta_values: pd.Series or pd.DataFrame, keys/columns = theta_names, optional - Additional theta values that are compared to the confidence region - to determine if they are inside or outside. - - Returns - training_results: pd.DataFrame - Theta value used to generate the confidence region along with True - (inside) or False (outside) for each alpha - test_results: pd.DataFrame - If test_theta_values is not None, returns test theta value along - with True (inside) or False (outside) for each alpha - """ - assert isinstance(theta_values, pd.DataFrame) - assert distribution in ['Rect', 'MVN', 'KDE'] - assert isinstance(alphas, list) - assert isinstance( - test_theta_values, (type(None), dict, pd.Series, pd.DataFrame) - ) - - if isinstance(test_theta_values, (dict, pd.Series)): - test_theta_values = pd.Series(test_theta_values).to_frame().transpose() - - training_results = theta_values.copy() - - if test_theta_values is not None: - test_result = test_theta_values.copy() - - for a in alphas: - if distribution == 'Rect': - lb, ub = graphics.fit_rect_dist(theta_values, a) - training_results[a] = (theta_values > lb).all(axis=1) & ( - theta_values < ub - ).all(axis=1) - - if test_theta_values is not None: - # use upper and lower bound from the training set - test_result[a] = (test_theta_values > lb).all(axis=1) & ( - test_theta_values < ub - ).all(axis=1) - - elif distribution == 'MVN': - dist = graphics.fit_mvn_dist(theta_values) - Z = dist.pdf(theta_values) - score = scipy.stats.scoreatpercentile(Z, (1 - a) * 100) - training_results[a] = Z >= score - - if test_theta_values is not None: - # use score from the training set - Z = dist.pdf(test_theta_values) - test_result[a] = Z >= score - - elif distribution == 'KDE': - dist = graphics.fit_kde_dist(theta_values) - Z = dist.pdf(theta_values.transpose()) - score = scipy.stats.scoreatpercentile(Z, (1 - a) * 100) - training_results[a] = Z >= score - - if test_theta_values is not None: - # use score from the training set - Z = dist.pdf(test_theta_values.transpose()) - test_result[a] = Z >= score - - if test_theta_values is not None: - return training_results, test_result - else: - return training_results diff --git a/pyomo/contrib/parmest/deprecated/scenariocreator.py b/pyomo/contrib/parmest/deprecated/scenariocreator.py deleted file mode 100644 index af084d0712c..00000000000 --- a/pyomo/contrib/parmest/deprecated/scenariocreator.py +++ /dev/null @@ -1,166 +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. -# ___________________________________________________________________________ - -# ScenariosCreator.py - Class to create and deliver scenarios using parmest -# DLW March 2020 - -import pyomo.environ as pyo - - -class ScenarioSet(object): - """ - Class to hold scenario sets - - Args: - name (str): name of the set (might be "") - - """ - - def __init__(self, name): - # Note: If there was a use-case, the list could be a dataframe. - self._scens = list() # use a df instead? - self.name = name # might be "" - - def _firstscen(self): - # Return the first scenario for testing and to get Theta names. - assert len(self._scens) > 0 - return self._scens[0] - - def ScensIterator(self): - """Usage: for scenario in ScensIterator()""" - return iter(self._scens) - - def ScenarioNumber(self, scennum): - """Returns the scenario with the given, zero-based number""" - return self._scens[scennum] - - def addone(self, scen): - """Add a scenario to the set - - Args: - scen (ParmestScen): the scenario to add - """ - assert isinstance(self._scens, list) - self._scens.append(scen) - - def append_bootstrap(self, bootstrap_theta): - """Append a bootstrap theta df to the scenario set; equally likely - - Args: - bootstrap_theta (dataframe): created by the bootstrap - Note: this can be cleaned up a lot with the list becomes a df, - which is why I put it in the ScenarioSet class. - """ - assert len(bootstrap_theta) > 0 - prob = 1.0 / len(bootstrap_theta) - - # dict of ThetaVal dicts - dfdict = bootstrap_theta.to_dict(orient='index') - - for index, ThetaVals in dfdict.items(): - name = "Bootstrap" + str(index) - self.addone(ParmestScen(name, ThetaVals, prob)) - - def write_csv(self, filename): - """write a csv file with the scenarios in the set - - Args: - filename (str): full path and full name of file - """ - if len(self._scens) == 0: - print("Empty scenario set, not writing file={}".format(filename)) - return - with open(filename, "w") as f: - f.write("Name,Probability") - for n in self._firstscen().ThetaVals.keys(): - f.write(",{}".format(n)) - f.write('\n') - for s in self.ScensIterator(): - f.write("{},{}".format(s.name, s.probability)) - for v in s.ThetaVals.values(): - f.write(",{}".format(v)) - f.write('\n') - - -class ParmestScen(object): - """A little container for scenarios; the Args are the attributes. - - Args: - name (str): name for reporting; might be "" - ThetaVals (dict): ThetaVals[name]=val - probability (float): probability of occurrence "near" these ThetaVals - """ - - def __init__(self, name, ThetaVals, probability): - self.name = name - assert isinstance(ThetaVals, dict) - self.ThetaVals = ThetaVals - self.probability = probability - - -############################################################ - - -class ScenarioCreator(object): - """Create scenarios from parmest. - - Args: - pest (Estimator): the parmest object - solvername (str): name of the solver (e.g. "ipopt") - - """ - - def __init__(self, pest, solvername): - self.pest = pest - self.solvername = solvername - - def ScenariosFromExperiments(self, addtoSet): - """Creates new self.Scenarios list using the experiments only. - - Args: - addtoSet (ScenarioSet): the scenarios will be added to this set - Returns: - a ScenarioSet - """ - - # assert isinstance(addtoSet, ScenarioSet) - - scenario_numbers = list(range(len(self.pest.callback_data))) - - prob = 1.0 / len(scenario_numbers) - for exp_num in scenario_numbers: - ##print("Experiment number=", exp_num) - model = self.pest._instance_creation_callback( - exp_num, self.pest.callback_data - ) - opt = pyo.SolverFactory(self.solvername) - results = opt.solve(model) # solves and updates model - ## pyo.check_termination_optimal(results) - ThetaVals = dict() - for theta in self.pest.theta_names: - tvar = eval('model.' + theta) - tval = pyo.value(tvar) - ##print(" theta, tval=", tvar, tval) - ThetaVals[theta] = tval - addtoSet.addone(ParmestScen("ExpScen" + str(exp_num), ThetaVals, prob)) - - def ScenariosFromBootstrap(self, addtoSet, numtomake, seed=None): - """Creates new self.Scenarios list using the experiments only. - - Args: - addtoSet (ScenarioSet): the scenarios will be added to this set - numtomake (int) : number of scenarios to create - """ - - # assert isinstance(addtoSet, ScenarioSet) - - bootstrap_thetas = self.pest.theta_est_bootstrap(numtomake, seed=seed) - addtoSet.append_bootstrap(bootstrap_thetas) diff --git a/pyomo/contrib/parmest/deprecated/tests/__init__.py b/pyomo/contrib/parmest/deprecated/tests/__init__.py deleted file mode 100644 index d93cfd77b3c..00000000000 --- a/pyomo/contrib/parmest/deprecated/tests/__init__.py +++ /dev/null @@ -1,10 +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. -# ___________________________________________________________________________ diff --git a/pyomo/contrib/parmest/deprecated/tests/scenarios.csv b/pyomo/contrib/parmest/deprecated/tests/scenarios.csv deleted file mode 100644 index 22f9a651bc3..00000000000 --- a/pyomo/contrib/parmest/deprecated/tests/scenarios.csv +++ /dev/null @@ -1,11 +0,0 @@ -Name,Probability,k1,k2,E1,E2 -ExpScen0,0.1,25.800350800448314,14.14421520525348,31505.74905064048,35000.0 -ExpScen1,0.1,25.128373083865036,149.99999951481198,31452.336651974012,41938.781301641866 -ExpScen2,0.1,22.225574065344002,130.92739780265404,30948.669111672247,41260.15420929141 -ExpScen3,0.1,100.0,149.99999970011854,35182.73130744844,41444.52600373733 -ExpScen4,0.1,82.99114366189944,45.95424665995078,34810.857217141674,38300.633349887314 -ExpScen5,0.1,100.0,150.0,35142.20219150486,41495.41105795494 -ExpScen6,0.1,2.8743643265301118,149.99999477176598,25000.0,41431.61195969211 -ExpScen7,0.1,2.754580914035567,14.381786096822475,25000.0,35000.0 -ExpScen8,0.1,2.8743643265301118,149.99999477176598,25000.0,41431.61195969211 -ExpScen9,0.1,2.669780822294865,150.0,25000.0,41514.7476113499 diff --git a/pyomo/contrib/parmest/deprecated/tests/test_examples.py b/pyomo/contrib/parmest/deprecated/tests/test_examples.py deleted file mode 100644 index 6f5d9703f05..00000000000 --- a/pyomo/contrib/parmest/deprecated/tests/test_examples.py +++ /dev/null @@ -1,204 +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. -# ___________________________________________________________________________ - -import pyomo.common.unittest as unittest -import pyomo.contrib.parmest.parmest as parmest -from pyomo.contrib.parmest.graphics import matplotlib_available, seaborn_available -from pyomo.opt import SolverFactory - -ipopt_available = SolverFactory("ipopt").available() - - -@unittest.skipIf( - not parmest.parmest_available, - "Cannot test parmest: required dependencies are missing", -) -@unittest.skipIf(not ipopt_available, "The 'ipopt' solver is not available") -class TestRooneyBieglerExamples(unittest.TestCase): - @classmethod - def setUpClass(self): - pass - - @classmethod - def tearDownClass(self): - pass - - def test_model(self): - from pyomo.contrib.parmest.deprecated.examples.rooney_biegler import ( - rooney_biegler, - ) - - rooney_biegler.main() - - def test_model_with_constraint(self): - from pyomo.contrib.parmest.deprecated.examples.rooney_biegler import ( - rooney_biegler_with_constraint, - ) - - rooney_biegler_with_constraint.main() - - @unittest.skipUnless(seaborn_available, "test requires seaborn") - def test_parameter_estimation_example(self): - from pyomo.contrib.parmest.deprecated.examples.rooney_biegler import ( - parameter_estimation_example, - ) - - parameter_estimation_example.main() - - @unittest.skipUnless(seaborn_available, "test requires seaborn") - def test_bootstrap_example(self): - from pyomo.contrib.parmest.deprecated.examples.rooney_biegler import ( - bootstrap_example, - ) - - bootstrap_example.main() - - @unittest.skipUnless(seaborn_available, "test requires seaborn") - def test_likelihood_ratio_example(self): - from pyomo.contrib.parmest.deprecated.examples.rooney_biegler import ( - likelihood_ratio_example, - ) - - likelihood_ratio_example.main() - - -@unittest.skipIf( - not parmest.parmest_available, - "Cannot test parmest: required dependencies are missing", -) -@unittest.skipIf(not ipopt_available, "The 'ipopt' solver is not available") -class TestReactionKineticsExamples(unittest.TestCase): - @classmethod - def setUpClass(self): - pass - - @classmethod - def tearDownClass(self): - pass - - def test_example(self): - from pyomo.contrib.parmest.deprecated.examples.reaction_kinetics import ( - simple_reaction_parmest_example, - ) - - simple_reaction_parmest_example.main() - - -@unittest.skipIf( - not parmest.parmest_available, - "Cannot test parmest: required dependencies are missing", -) -@unittest.skipIf(not ipopt_available, "The 'ipopt' solver is not available") -class TestSemibatchExamples(unittest.TestCase): - @classmethod - def setUpClass(self): - pass - - @classmethod - def tearDownClass(self): - pass - - def test_model(self): - from pyomo.contrib.parmest.deprecated.examples.semibatch import semibatch - - semibatch.main() - - def test_parameter_estimation_example(self): - from pyomo.contrib.parmest.deprecated.examples.semibatch import ( - parameter_estimation_example, - ) - - parameter_estimation_example.main() - - def test_scenario_example(self): - from pyomo.contrib.parmest.deprecated.examples.semibatch import scenario_example - - scenario_example.main() - - -@unittest.skipIf( - not parmest.parmest_available, - "Cannot test parmest: required dependencies are missing", -) -@unittest.skipIf(not ipopt_available, "The 'ipopt' solver is not available") -class TestReactorDesignExamples(unittest.TestCase): - @classmethod - def setUpClass(self): - pass - - @classmethod - def tearDownClass(self): - pass - - @unittest.pytest.mark.expensive - def test_model(self): - from pyomo.contrib.parmest.deprecated.examples.reactor_design import ( - reactor_design, - ) - - reactor_design.main() - - def test_parameter_estimation_example(self): - from pyomo.contrib.parmest.deprecated.examples.reactor_design import ( - parameter_estimation_example, - ) - - parameter_estimation_example.main() - - @unittest.skipUnless(seaborn_available, "test requires seaborn") - def test_bootstrap_example(self): - from pyomo.contrib.parmest.deprecated.examples.reactor_design import ( - bootstrap_example, - ) - - bootstrap_example.main() - - @unittest.pytest.mark.expensive - def test_likelihood_ratio_example(self): - from pyomo.contrib.parmest.deprecated.examples.reactor_design import ( - likelihood_ratio_example, - ) - - likelihood_ratio_example.main() - - @unittest.pytest.mark.expensive - def test_leaveNout_example(self): - from pyomo.contrib.parmest.deprecated.examples.reactor_design import ( - leaveNout_example, - ) - - leaveNout_example.main() - - def test_timeseries_data_example(self): - from pyomo.contrib.parmest.deprecated.examples.reactor_design import ( - timeseries_data_example, - ) - - timeseries_data_example.main() - - def test_multisensor_data_example(self): - from pyomo.contrib.parmest.deprecated.examples.reactor_design import ( - multisensor_data_example, - ) - - multisensor_data_example.main() - - @unittest.skipUnless(matplotlib_available, "test requires matplotlib") - def test_datarec_example(self): - from pyomo.contrib.parmest.deprecated.examples.reactor_design import ( - datarec_example, - ) - - datarec_example.main() - - -if __name__ == "__main__": - unittest.main() diff --git a/pyomo/contrib/parmest/deprecated/tests/test_graphics.py b/pyomo/contrib/parmest/deprecated/tests/test_graphics.py deleted file mode 100644 index c18659e9948..00000000000 --- a/pyomo/contrib/parmest/deprecated/tests/test_graphics.py +++ /dev/null @@ -1,68 +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.common.dependencies import ( - numpy as np, - numpy_available, - pandas as pd, - pandas_available, - scipy, - scipy_available, - matplotlib, - matplotlib_available, -) - -import platform - -is_osx = platform.mac_ver()[0] != '' - -import pyomo.common.unittest as unittest -import sys -import os - -import pyomo.contrib.parmest.parmest as parmest -import pyomo.contrib.parmest.graphics as graphics - -testdir = os.path.dirname(os.path.abspath(__file__)) - - -@unittest.skipIf( - not parmest.parmest_available, - "Cannot test parmest: required dependencies are missing", -) -@unittest.skipIf( - not graphics.imports_available, "parmest.graphics imports are unavailable" -) -@unittest.skipIf( - is_osx, - "Disabling graphics tests on OSX due to issue in Matplotlib, see Pyomo PR #1337", -) -class TestGraphics(unittest.TestCase): - def setUp(self): - self.A = pd.DataFrame( - np.random.randint(0, 100, size=(100, 4)), columns=list('ABCD') - ) - self.B = pd.DataFrame( - np.random.randint(0, 100, size=(100, 4)), columns=list('ABCD') - ) - - def test_pairwise_plot(self): - graphics.pairwise_plot(self.A, alpha=0.8, distributions=['Rect', 'MVN', 'KDE']) - - def test_grouped_boxplot(self): - graphics.grouped_boxplot(self.A, self.B, normalize=True, group_names=['A', 'B']) - - def test_grouped_violinplot(self): - graphics.grouped_violinplot(self.A, self.B) - - -if __name__ == '__main__': - unittest.main() diff --git a/pyomo/contrib/parmest/deprecated/tests/test_parmest.py b/pyomo/contrib/parmest/deprecated/tests/test_parmest.py deleted file mode 100644 index 27776bdc64c..00000000000 --- a/pyomo/contrib/parmest/deprecated/tests/test_parmest.py +++ /dev/null @@ -1,956 +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.common.dependencies import ( - numpy as np, - numpy_available, - pandas as pd, - pandas_available, - scipy, - scipy_available, - matplotlib, - matplotlib_available, -) - -import platform - -is_osx = platform.mac_ver()[0] != "" - -import pyomo.common.unittest as unittest -import sys -import os -import subprocess -from itertools import product - -import pyomo.contrib.parmest.parmest as parmest -import pyomo.contrib.parmest.graphics as graphics -import pyomo.contrib.parmest as parmestbase -import pyomo.environ as pyo -import pyomo.dae as dae - -from pyomo.opt import SolverFactory - -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 - -testdir = os.path.dirname(os.path.abspath(__file__)) - - -@unittest.skipIf( - not parmest.parmest_available, - "Cannot test parmest: required dependencies are missing", -) -@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") -class TestRooneyBiegler(unittest.TestCase): - def setUp(self): - from pyomo.contrib.parmest.deprecated.examples.rooney_biegler.rooney_biegler import ( - rooney_biegler_model, - ) - - # 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"], - ) - - theta_names = ["asymptote", "rate_constant"] - - def SSE(model, data): - expr = sum( - (data.y[i] - model.response_function[data.hour[i]]) ** 2 - for i in data.index - ) - return expr - - solver_options = {"tol": 1e-8} - - self.data = data - self.pest = parmest.Estimator( - rooney_biegler_model, - data, - theta_names, - SSE, - solver_options=solver_options, - tee=True, - ) - - def test_theta_est(self): - objval, thetavals = self.pest.theta_est() - - self.assertAlmostEqual(objval, 4.3317112, places=2) - self.assertAlmostEqual( - thetavals["asymptote"], 19.1426, places=2 - ) # 19.1426 from the paper - self.assertAlmostEqual( - thetavals["rate_constant"], 0.5311, places=2 - ) # 0.5311 from the paper - - @unittest.skipIf( - not graphics.imports_available, "parmest.graphics imports are unavailable" - ) - def test_bootstrap(self): - objval, thetavals = self.pest.theta_est() - - num_bootstraps = 10 - theta_est = self.pest.theta_est_bootstrap(num_bootstraps, return_samples=True) - - 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"] - - # apply confidence region test - 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) - self.assertTrue(CR[0.75].sum() == 7) - self.assertTrue(CR[1.0].sum() == 10) # all true - - graphics.pairwise_plot(theta_est) - graphics.pairwise_plot(theta_est, thetavals) - graphics.pairwise_plot(theta_est, thetavals, 0.8, ["MVN", "KDE", "Rect"]) - - @unittest.skipIf( - not graphics.imports_available, "parmest.graphics imports are unavailable" - ) - def test_likelihood_ratio(self): - objval, thetavals = self.pest.theta_est() - - asym = np.arange(10, 30, 2) - rate = np.arange(0, 1.5, 0.25) - theta_vals = pd.DataFrame( - list(product(asym, rate)), columns=self.pest._return_theta_names() - ) - - obj_at_theta = self.pest.objective_at_theta(theta_vals) - - LR = self.pest.likelihood_ratio_test(obj_at_theta, objval, [0.8, 0.9, 1.0]) - - self.assertTrue(set(LR.columns) >= set([0.8, 0.9, 1.0])) - self.assertTrue(LR[0.8].sum() == 6) - self.assertTrue(LR[0.9].sum() == 10) - self.assertTrue(LR[1.0].sum() == 60) # all true - - graphics.pairwise_plot(LR, thetavals, 0.8) - - def test_leaveNout(self): - lNo_theta = self.pest.theta_est_leaveNout(1) - self.assertTrue(lNo_theta.shape == (6, 2)) - - results = self.pest.leaveNout_bootstrap_test( - 1, None, 3, "Rect", [0.5, 1.0], seed=5436 - ) - self.assertTrue(len(results) == 6) # 6 lNo samples - i = 1 - samples = results[i][0] # list of N samples that are left out - lno_theta = results[i][1] - bootstrap_theta = results[i][2] - self.assertTrue(samples == [1]) # sample 1 was left out - self.assertTrue(lno_theta.shape[0] == 1) # lno estimate for sample 1 - self.assertTrue(set(lno_theta.columns) >= set([0.5, 1.0])) - self.assertTrue(lno_theta[1.0].sum() == 1) # all true - self.assertTrue(bootstrap_theta.shape[0] == 3) # bootstrap for sample 1 - self.assertTrue(bootstrap_theta[1.0].sum() == 3) # all true - - def test_diagnostic_mode(self): - self.pest.diagnostic_mode = True - - objval, thetavals = self.pest.theta_est() - - asym = np.arange(10, 30, 2) - rate = np.arange(0, 1.5, 0.25) - theta_vals = pd.DataFrame( - list(product(asym, rate)), columns=self.pest._return_theta_names() - ) - - obj_at_theta = self.pest.objective_at_theta(theta_vals) - - self.pest.diagnostic_mode = False - - @unittest.skip("Presently having trouble with mpiexec on appveyor") - def test_parallel_parmest(self): - """use mpiexec and mpi4py""" - p = str(parmestbase.__path__) - l = p.find("'") - r = p.find("'", l + 1) - parmestpath = p[l + 1 : r] - rbpath = ( - parmestpath - + os.sep - + "examples" - + os.sep - + "rooney_biegler" - + os.sep - + "rooney_biegler_parmest.py" - ) - rbpath = os.path.abspath(rbpath) # paranoia strikes deep... - rlist = ["mpiexec", "--allow-run-as-root", "-n", "2", sys.executable, rbpath] - if sys.version_info >= (3, 5): - ret = subprocess.run(rlist) - retcode = ret.returncode - else: - retcode = subprocess.call(rlist) - assert retcode == 0 - - @unittest.skip("Most folks don't have k_aug installed") - def test_theta_k_aug_for_Hessian(self): - # this will fail if k_aug is not installed - objval, thetavals, Hessian = self.pest.theta_est(solver="k_aug") - self.assertAlmostEqual(objval, 4.4675, places=2) - - @unittest.skipIf(not pynumero_ASL_available, "pynumero ASL is not available") - @unittest.skipIf( - not parmest.inverse_reduced_hessian_available, - "Cannot test covariance matrix: required ASL dependency is missing", - ) - def test_theta_est_cov(self): - objval, thetavals, cov = self.pest.theta_est(calc_cov=True, cov_n=6) - - self.assertAlmostEqual(objval, 4.3317112, places=2) - self.assertAlmostEqual( - thetavals["asymptote"], 19.1426, places=2 - ) # 19.1426 from the paper - self.assertAlmostEqual( - thetavals["rate_constant"], 0.5311, places=2 - ) # 0.5311 from the paper - - # Covariance matrix - self.assertAlmostEqual( - cov.iloc[0, 0], 6.30579403, places=2 - ) # 6.22864 from paper - self.assertAlmostEqual( - cov.iloc[0, 1], -0.4395341, places=2 - ) # -0.4322 from paper - self.assertAlmostEqual( - cov.iloc[1, 0], -0.4395341, places=2 - ) # -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 - 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 - t: independent variable [hours] - - 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() - - # 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 - ) - theta_hat = sol.x - - self.assertAlmostEqual( - theta_hat[0], 19.1426, places=2 - ) # 19.1426 from the paper - self.assertAlmostEqual(theta_hat[1], 0.5311, places=2) # 0.5311 from the paper - - # calculate residuals - r = residual(theta_hat, t, y) - - # calculate variance of the residuals - # -2 because there are 2 fitted parameters - sigre = np.matmul(r.T, r / (len(y) - 2)) - - # approximate covariance - # Need to divide by 2 because optimize.least_squares scaled the objective by 1/2 - cov = sigre * np.linalg.inv(np.matmul(sol.jac.T, sol.jac)) - - self.assertAlmostEqual(cov[0, 0], 6.22864, places=2) # 6.22864 from paper - self.assertAlmostEqual(cov[0, 1], -0.4322, places=2) # -0.4322 from paper - self.assertAlmostEqual(cov[1, 0], -0.4322, places=2) # -0.4322 from paper - 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() - - # define initial guess - theta_guess = np.array([15, 0.5]) - - theta_hat, cov = scipy.optimize.curve_fit(model, t, y, p0=theta_guess) - - self.assertAlmostEqual( - theta_hat[0], 19.1426, places=2 - ) # 19.1426 from the paper - self.assertAlmostEqual(theta_hat[1], 0.5311, places=2) # 0.5311 from the paper - - self.assertAlmostEqual(cov[0, 0], 6.22864, places=2) # 6.22864 from paper - self.assertAlmostEqual(cov[0, 1], -0.4322, places=2) # -0.4322 from paper - self.assertAlmostEqual(cov[1, 0], -0.4322, places=2) # -0.4322 from paper - self.assertAlmostEqual(cov[1, 1], 0.04124, places=2) # 0.04124 from paper - - -@unittest.skipIf( - not parmest.parmest_available, - "Cannot test parmest: required dependencies are missing", -) -@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") -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"], - ) - - def rooney_biegler_params(data): - model = pyo.ConcreteModel() - - model.asymptote = pyo.Param(initialize=15, mutable=True) - model.rate_constant = pyo.Param(initialize=0.5, mutable=True) - - def response_rule(m, h): - expr = m.asymptote * (1 - pyo.exp(-m.rate_constant * h)) - return expr - - model.response_function = pyo.Expression(data.hour, rule=response_rule) - - return model - - def rooney_biegler_indexed_params(data): - model = pyo.ConcreteModel() - - model.param_names = pyo.Set(initialize=["asymptote", "rate_constant"]) - model.theta = pyo.Param( - model.param_names, - 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) - ) - return expr - - model.response_function = pyo.Expression(data.hour, rule=response_rule) - - return model - - def rooney_biegler_vars(data): - model = pyo.ConcreteModel() - - model.asymptote = pyo.Var(initialize=15) - model.rate_constant = pyo.Var(initialize=0.5) - model.asymptote.fixed = True # parmest will unfix theta variables - model.rate_constant.fixed = True - - def response_rule(m, h): - expr = m.asymptote * (1 - pyo.exp(-m.rate_constant * h)) - return expr - - model.response_function = pyo.Expression(data.hour, rule=response_rule) - - return model - - def rooney_biegler_indexed_vars(data): - model = pyo.ConcreteModel() - - model.var_names = pyo.Set(initialize=["asymptote", "rate_constant"]) - model.theta = pyo.Var( - model.var_names, initialize={"asymptote": 15, "rate_constant": 0.5} - ) - model.theta["asymptote"].fixed = ( - True # parmest will unfix theta variables, even when they are indexed - ) - model.theta["rate_constant"].fixed = True - - def response_rule(m, h): - expr = m.theta["asymptote"] * ( - 1 - pyo.exp(-m.theta["rate_constant"] * h) - ) - return expr - - model.response_function = pyo.Expression(data.hour, rule=response_rule) - - return model - - def SSE(model, data): - expr = sum( - (data.y[i] - model.response_function[data.hour[i]]) ** 2 - for i in data.index - ) - return expr - - self.objective_function = SSE - - 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_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_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_str_index": { - "model": rooney_biegler_indexed_vars, - "theta_names": ["theta[asymptote]", "theta[rate_constant]"], - "theta_vals": theta_vals_index, - }, - } - - @unittest.skipIf(not pynumero_ASL_available, "pynumero ASL is not available") - @unittest.skipIf( - not parmest.inverse_reduced_hessian_available, - "Cannot test covariance matrix: required ASL dependency is missing", - ) - def test_parmest_basics(self): - for model_type, parmest_input in self.input.items(): - pest = parmest.Estimator( - parmest_input["model"], - self.data, - parmest_input["theta_names"], - self.objective_function, - ) - - objval, thetavals, cov = pest.theta_est(calc_cov=True, cov_n=6) - - self.assertAlmostEqual(objval, 4.3317112, places=2) - self.assertAlmostEqual( - cov.iloc[0, 0], 6.30579403, places=2 - ) # 6.22864 from paper - self.assertAlmostEqual( - cov.iloc[0, 1], -0.4395341, places=2 - ) # -0.4322 from paper - self.assertAlmostEqual( - cov.iloc[1, 0], -0.4395341, places=2 - ) # -0.4322 from paper - self.assertAlmostEqual( - 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) - - 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"], - self.data, - parmest_input["theta_names"], - self.objective_function, - ) - - objval, thetavals, cov = pest.theta_est(calc_cov=True, cov_n=6) - - self.assertAlmostEqual(objval, 4.3317112, places=2) - self.assertAlmostEqual( - cov.iloc[0, 0], 6.30579403, places=2 - ) # 6.22864 from paper - self.assertAlmostEqual( - cov.iloc[0, 1], -0.4395341, places=2 - ) # -0.4322 from paper - self.assertAlmostEqual( - cov.iloc[1, 0], -0.4395341, places=2 - ) # -0.4322 from paper - self.assertAlmostEqual( - cov.iloc[1, 1], 0.04193591, places=2 - ) # 0.04124 from paper - - obj_at_theta = pest.objective_at_theta( - parmest_input["theta_vals"], initialize_parmest_model=True - ) - - 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"], - self.data, - parmest_input["theta_names"], - self.objective_function, - ) - - obj_at_theta = pest.objective_at_theta( - parmest_input["theta_vals"], initialize_parmest_model=True - ) - - objval, thetavals, cov = pest.theta_est(calc_cov=True, cov_n=6) - - self.assertAlmostEqual(objval, 4.3317112, places=2) - self.assertAlmostEqual( - cov.iloc[0, 0], 6.30579403, places=2 - ) # 6.22864 from paper - self.assertAlmostEqual( - cov.iloc[0, 1], -0.4395341, places=2 - ) # -0.4322 from paper - self.assertAlmostEqual( - cov.iloc[1, 0], -0.4395341, places=2 - ) # -0.4322 from paper - self.assertAlmostEqual( - cov.iloc[1, 1], 0.04193591, places=2 - ) # 0.04124 from paper - - 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"], - self.data, - parmest_input["theta_names"], - self.objective_function, - ) - - obj_at_theta = pest.objective_at_theta(initialize_parmest_model=True) - - objval, thetavals, cov = pest.theta_est(calc_cov=True, cov_n=6) - - self.assertAlmostEqual(objval, 4.3317112, places=2) - self.assertAlmostEqual( - cov.iloc[0, 0], 6.30579403, places=2 - ) # 6.22864 from paper - self.assertAlmostEqual( - cov.iloc[0, 1], -0.4395341, places=2 - ) # -0.4322 from paper - self.assertAlmostEqual( - cov.iloc[1, 0], -0.4395341, places=2 - ) # -0.4322 from paper - self.assertAlmostEqual( - cov.iloc[1, 1], 0.04193591, places=2 - ) # 0.04124 from paper - - -@unittest.skipIf( - not parmest.parmest_available, - "Cannot test parmest: required dependencies are missing", -) -@unittest.skipIf(not ipopt_available, "The 'ipopt' solver is not available") -class TestReactorDesign(unittest.TestCase): - def setUp(self): - from pyomo.contrib.parmest.deprecated.examples.reactor_design.reactor_design import ( - reactor_design_model, - ) - - # Data from the design - data = pd.DataFrame( - data=[ - [1.05, 10000, 3458.4, 1060.8, 1683.9, 1898.5], - [1.10, 10000, 3535.1, 1064.8, 1613.3, 1893.4], - [1.15, 10000, 3609.1, 1067.8, 1547.5, 1887.8], - [1.20, 10000, 3680.7, 1070.0, 1486.1, 1881.6], - [1.25, 10000, 3750.0, 1071.4, 1428.6, 1875.0], - [1.30, 10000, 3817.1, 1072.2, 1374.6, 1868.0], - [1.35, 10000, 3882.2, 1072.4, 1324.0, 1860.7], - [1.40, 10000, 3945.4, 1072.1, 1276.3, 1853.1], - [1.45, 10000, 4006.7, 1071.3, 1231.4, 1845.3], - [1.50, 10000, 4066.4, 1070.1, 1189.0, 1837.3], - [1.55, 10000, 4124.4, 1068.5, 1148.9, 1829.1], - [1.60, 10000, 4180.9, 1066.5, 1111.0, 1820.8], - [1.65, 10000, 4235.9, 1064.3, 1075.0, 1812.4], - [1.70, 10000, 4289.5, 1061.8, 1040.9, 1803.9], - [1.75, 10000, 4341.8, 1059.0, 1008.5, 1795.3], - [1.80, 10000, 4392.8, 1056.0, 977.7, 1786.7], - [1.85, 10000, 4442.6, 1052.8, 948.4, 1778.1], - [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"], - ) - - 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 - ) - return expr - - solver_options = {"max_iter": 6000} - - self.pest = parmest.Estimator( - reactor_design_model, data, theta_names, SSE, solver_options=solver_options - ) - - 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) - - def test_return_values(self): - objval, thetavals, data_rec = self.pest.theta_est( - return_values=["ca", "cb", "cc", "cd", "caf"] - ) - self.assertAlmostEqual(data_rec["cc"].loc[18], 893.84924, places=3) - - -@unittest.skipIf( - not parmest.parmest_available, - "Cannot test parmest: required dependencies are missing", -) -@unittest.skipIf(not ipopt_available, "The 'ipopt' solver is not available") -class TestReactorDesign_DAE(unittest.TestCase): - # Based on a reactor example in `Chemical Reactor Analysis and Design Fundamentals`, - # https://sites.engineering.ucsb.edu/~jbraw/chemreacfun/ - # https://sites.engineering.ucsb.edu/~jbraw/chemreacfun/fig-html/appendix/fig-A-10.html - - def setUp(self): - def ABC_model(data): - ca_meas = data["ca"] - cb_meas = data["cb"] - cc_meas = data["cc"] - - if isinstance(data, pd.DataFrame): - meas_t = data.index # time index - else: # dictionary - meas_t = list(ca_meas.keys()) # nested dictionary - - ca0 = 1.0 - cb0 = 0.0 - cc0 = 0.0 - - m = pyo.ConcreteModel() - - m.k1 = pyo.Var(initialize=0.5, bounds=(1e-4, 10)) - m.k2 = pyo.Var(initialize=3.0, bounds=(1e-4, 10)) - - m.time = dae.ContinuousSet(bounds=(0.0, 5.0), initialize=meas_t) - - # initialization and bounds - m.ca = pyo.Var(m.time, initialize=ca0, bounds=(-1e-3, ca0 + 1e-3)) - m.cb = pyo.Var(m.time, initialize=cb0, bounds=(-1e-3, ca0 + 1e-3)) - m.cc = pyo.Var(m.time, initialize=cc0, bounds=(-1e-3, ca0 + 1e-3)) - - m.dca = dae.DerivativeVar(m.ca, wrt=m.time) - m.dcb = dae.DerivativeVar(m.cb, wrt=m.time) - m.dcc = dae.DerivativeVar(m.cc, wrt=m.time) - - def _dcarate(m, t): - if t == 0: - return pyo.Constraint.Skip - else: - return m.dca[t] == -m.k1 * m.ca[t] - - m.dcarate = pyo.Constraint(m.time, rule=_dcarate) - - def _dcbrate(m, t): - if t == 0: - return pyo.Constraint.Skip - else: - return m.dcb[t] == m.k1 * m.ca[t] - m.k2 * m.cb[t] - - m.dcbrate = pyo.Constraint(m.time, rule=_dcbrate) - - def _dccrate(m, t): - if t == 0: - return pyo.Constraint.Skip - else: - return m.dcc[t] == m.k2 * m.cb[t] - - m.dccrate = pyo.Constraint(m.time, rule=_dccrate) - - def ComputeFirstStageCost_rule(m): - return 0 - - m.FirstStageCost = pyo.Expression(rule=ComputeFirstStageCost_rule) - - def ComputeSecondStageCost_rule(m): - return sum( - (m.ca[t] - ca_meas[t]) ** 2 - + (m.cb[t] - cb_meas[t]) ** 2 - + (m.cc[t] - cc_meas[t]) ** 2 - for t in meas_t - ) - - m.SecondStageCost = pyo.Expression(rule=ComputeSecondStageCost_rule) - - def total_cost_rule(model): - return model.FirstStageCost + model.SecondStageCost - - m.Total_Cost_Objective = pyo.Objective( - rule=total_cost_rule, sense=pyo.minimize - ) - - disc = pyo.TransformationFactory("dae.collocation") - disc.apply_to(m, nfe=20, ncp=2) - - return m - - # This example tests data formatted in 3 ways - # Each format holds 1 scenario - # 1. dataframe with time index - # 2. nested dictionary {ca: {t, val pairs}, ... } - data = [ - [0.000, 0.957, -0.031, -0.015], - [0.263, 0.557, 0.330, 0.044], - [0.526, 0.342, 0.512, 0.156], - [0.789, 0.224, 0.499, 0.310], - [1.053, 0.123, 0.428, 0.454], - [1.316, 0.079, 0.396, 0.556], - [1.579, 0.035, 0.303, 0.651], - [1.842, 0.029, 0.287, 0.658], - [2.105, 0.025, 0.221, 0.750], - [2.368, 0.017, 0.148, 0.854], - [2.632, -0.002, 0.182, 0.845], - [2.895, 0.009, 0.116, 0.893], - [3.158, -0.023, 0.079, 0.942], - [3.421, 0.006, 0.078, 0.899], - [3.684, 0.016, 0.059, 0.942], - [3.947, 0.014, 0.036, 0.991], - [4.211, -0.009, 0.014, 0.988], - [4.474, -0.030, 0.036, 0.941], - [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_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)}, - } - - 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) - - # Estimator object with multiple scenarios - self.pest_df_multiple = parmest.Estimator( - ABC_model, [data_df, data_df], theta_names - ) - self.pest_dict_multiple = parmest.Estimator( - ABC_model, [data_dict, data_dict], theta_names - ) - - # Create an instance of the model - self.m_df = ABC_model(data_df) - self.m_dict = ABC_model(data_dict) - - def test_dataformats(self): - obj1, theta1 = self.pest_df.theta_est() - 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) - - 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) - - 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"] - ) - obj2, theta2, return_vals2 = self.pest_dict_multiple.theta_est( - 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) - - def test_covariance(self): - from pyomo.contrib.interior_point.inverse_reduced_hessian import ( - inv_reduced_hessian_barrier, - ) - - # Number of datapoints. - # 3 data components (ca, cb, cc), 20 timesteps, 1 scenario = 60 - # In this example, this is the number of data points in data_df, but that's - # only because the data is indexed by time and contains no additional information. - n = 60 - - # Compute covariance using parmest - obj, theta, cov = self.pest_df.theta_est(calc_cov=True, cov_n=n) - - # Compute covariance using interior_point - vars_list = [self.m_df.k1, self.m_df.k2] - solve_result, inv_red_hes = inv_reduced_hessian_barrier( - self.m_df, independent_variables=vars_list, tee=True - ) - 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_diff = (cov - cov_interior_point).abs().sum().sum() - - self.assertTrue(cov.loc["k1", "k1"] > 0) - self.assertTrue(cov.loc["k2", "k2"] > 0) - self.assertAlmostEqual(cov_diff, 0, places=6) - - -@unittest.skipIf( - not parmest.parmest_available, - "Cannot test parmest: required dependencies are missing", -) -@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") -class TestSquareInitialization_RooneyBiegler(unittest.TestCase): - def setUp(self): - from pyomo.contrib.parmest.deprecated.examples.rooney_biegler.rooney_biegler_with_constraint import ( - rooney_biegler_model_with_constraint, - ) - - # 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"], - ) - - theta_names = ["asymptote", "rate_constant"] - - def SSE(model, data): - expr = sum( - (data.y[i] - model.response_function[data.hour[i]]) ** 2 - for i in data.index - ) - return expr - - solver_options = {"tol": 1e-8} - - self.data = data - self.pest = parmest.Estimator( - rooney_biegler_model_with_constraint, - data, - theta_names, - SSE, - solver_options=solver_options, - tee=True, - ) - - def test_theta_est_with_square_initialization(self): - obj_init = self.pest.objective_at_theta(initialize_parmest_model=True) - objval, thetavals = self.pest.theta_est() - - self.assertAlmostEqual(objval, 4.3317112, places=2) - self.assertAlmostEqual( - thetavals["asymptote"], 19.1426, places=2 - ) # 19.1426 from the paper - self.assertAlmostEqual( - 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"] - ) - obj_init = self.pest.objective_at_theta( - theta_values=theta_vals_init, initialize_parmest_model=True - ) - objval, thetavals = self.pest.theta_est() - self.assertAlmostEqual(objval, 4.3317112, places=2) - self.assertAlmostEqual( - thetavals["asymptote"], 19.1426, places=2 - ) # 19.1426 from the paper - self.assertAlmostEqual( - thetavals["rate_constant"], 0.5311, places=2 - ) # 0.5311 from the paper - - def test_theta_est_with_square_initialization_diagnostic_mode_true(self): - self.pest.diagnostic_mode = True - obj_init = self.pest.objective_at_theta(initialize_parmest_model=True) - objval, thetavals = self.pest.theta_est() - - self.assertAlmostEqual(objval, 4.3317112, places=2) - self.assertAlmostEqual( - thetavals["asymptote"], 19.1426, places=2 - ) # 19.1426 from the paper - self.assertAlmostEqual( - thetavals["rate_constant"], 0.5311, places=2 - ) # 0.5311 from the paper - - self.pest.diagnostic_mode = False - - -if __name__ == "__main__": - unittest.main() diff --git a/pyomo/contrib/parmest/deprecated/tests/test_scenariocreator.py b/pyomo/contrib/parmest/deprecated/tests/test_scenariocreator.py deleted file mode 100644 index 54cbe80f73c..00000000000 --- a/pyomo/contrib/parmest/deprecated/tests/test_scenariocreator.py +++ /dev/null @@ -1,146 +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.common.dependencies import pandas as pd, pandas_available - -uuid_available = True -try: - import uuid -except: - uuid_available = False - -import pyomo.common.unittest as unittest -import os -import pyomo.contrib.parmest.parmest as parmest -import pyomo.contrib.parmest.scenariocreator as sc -import pyomo.environ as pyo -from pyomo.environ import SolverFactory - -ipopt_available = SolverFactory("ipopt").available() - -testdir = os.path.dirname(os.path.abspath(__file__)) - - -@unittest.skipIf( - not parmest.parmest_available, - "Cannot test parmest: required dependencies are missing", -) -@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") -class TestScenarioReactorDesign(unittest.TestCase): - def setUp(self): - from pyomo.contrib.parmest.deprecated.examples.reactor_design.reactor_design import ( - reactor_design_model, - ) - - # Data from the design - data = pd.DataFrame( - data=[ - [1.05, 10000, 3458.4, 1060.8, 1683.9, 1898.5], - [1.10, 10000, 3535.1, 1064.8, 1613.3, 1893.4], - [1.15, 10000, 3609.1, 1067.8, 1547.5, 1887.8], - [1.20, 10000, 3680.7, 1070.0, 1486.1, 1881.6], - [1.25, 10000, 3750.0, 1071.4, 1428.6, 1875.0], - [1.30, 10000, 3817.1, 1072.2, 1374.6, 1868.0], - [1.35, 10000, 3882.2, 1072.4, 1324.0, 1860.7], - [1.40, 10000, 3945.4, 1072.1, 1276.3, 1853.1], - [1.45, 10000, 4006.7, 1071.3, 1231.4, 1845.3], - [1.50, 10000, 4066.4, 1070.1, 1189.0, 1837.3], - [1.55, 10000, 4124.4, 1068.5, 1148.9, 1829.1], - [1.60, 10000, 4180.9, 1066.5, 1111.0, 1820.8], - [1.65, 10000, 4235.9, 1064.3, 1075.0, 1812.4], - [1.70, 10000, 4289.5, 1061.8, 1040.9, 1803.9], - [1.75, 10000, 4341.8, 1059.0, 1008.5, 1795.3], - [1.80, 10000, 4392.8, 1056.0, 977.7, 1786.7], - [1.85, 10000, 4442.6, 1052.8, 948.4, 1778.1], - [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"], - ) - - 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 - ) - return expr - - self.pest = parmest.Estimator(reactor_design_model, data, theta_names, SSE) - - def test_scen_from_exps(self): - scenmaker = sc.ScenarioCreator(self.pest, "ipopt") - experimentscens = sc.ScenarioSet("Experiments") - scenmaker.ScenariosFromExperiments(experimentscens) - experimentscens.write_csv("delme_exp_csv.csv") - df = pd.read_csv("delme_exp_csv.csv") - os.remove("delme_exp_csv.csv") - # March '20: all reactor_design experiments have the same theta values! - k1val = df.loc[5].at["k1"] - self.assertAlmostEqual(k1val, 5.0 / 6.0, places=2) - tval = experimentscens.ScenarioNumber(0).ThetaVals["k1"] - self.assertAlmostEqual(tval, 5.0 / 6.0, places=2) - - @unittest.skipIf(not uuid_available, "The uuid module is not available") - def test_no_csv_if_empty(self): - # low level test of scenario sets - # verify that nothing is written, but no errors with empty set - - emptyset = sc.ScenarioSet("empty") - tfile = uuid.uuid4().hex + ".csv" - emptyset.write_csv(tfile) - self.assertFalse( - os.path.exists(tfile), "ScenarioSet wrote csv in spite of empty set" - ) - - -@unittest.skipIf( - not parmest.parmest_available, - "Cannot test parmest: required dependencies are missing", -) -@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") -class TestScenarioSemibatch(unittest.TestCase): - def setUp(self): - import pyomo.contrib.parmest.deprecated.examples.semibatch.semibatch as sb - import json - - # Vars to estimate in parmest - theta_names = ["k1", "k2", "E1", "E2"] - - self.fbase = os.path.join(testdir, "..", "examples", "semibatch") - # Data, list of dictionaries - data = [] - 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: - d = json.load(infile) - data.append(d) - - # Note, the model already includes a 'SecondStageCost' expression - # for the sum of squared error that will be used in parameter estimation - - self.pest = parmest.Estimator(sb.generate_model, data, theta_names) - - def test_semibatch_bootstrap(self): - scenmaker = sc.ScenarioCreator(self.pest, "ipopt") - bootscens = sc.ScenarioSet("Bootstrap") - numtomake = 2 - scenmaker.ScenariosFromBootstrap(bootscens, numtomake, seed=1134) - tval = bootscens.ScenarioNumber(0).ThetaVals["k1"] - self.assertAlmostEqual(tval, 20.64, places=1) - - -if __name__ == "__main__": - unittest.main() diff --git a/pyomo/contrib/parmest/deprecated/tests/test_solver.py b/pyomo/contrib/parmest/deprecated/tests/test_solver.py deleted file mode 100644 index eb655023b9b..00000000000 --- a/pyomo/contrib/parmest/deprecated/tests/test_solver.py +++ /dev/null @@ -1,75 +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.common.dependencies import ( - numpy as np, - numpy_available, - pandas as pd, - pandas_available, - scipy, - scipy_available, - matplotlib, - matplotlib_available, -) - -import platform - -is_osx = platform.mac_ver()[0] != '' - -import pyomo.common.unittest as unittest -import os - -import pyomo.contrib.parmest.parmest as parmest -import pyomo.contrib.parmest as parmestbase -import pyomo.environ as pyo - -from pyomo.opt import SolverFactory - -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 - -testdir = os.path.dirname(os.path.abspath(__file__)) - - -@unittest.skipIf( - not parmest.parmest_available, - "Cannot test parmest: required dependencies are missing", -) -@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") -class TestSolver(unittest.TestCase): - def setUp(self): - pass - - def test_ipopt_solve_with_stats(self): - from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import ( - rooney_biegler_model, - ) - from pyomo.contrib.parmest.utils import ipopt_solve_with_stats - - 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'], - ) - - model = rooney_biegler_model(data) - solver = pyo.SolverFactory('ipopt') - solver.solve(model) - - status_obj, solved, iters, time, regu = ipopt_solve_with_stats(model, solver) - - self.assertEqual(solved, True) - - -if __name__ == '__main__': - unittest.main() diff --git a/pyomo/contrib/parmest/deprecated/tests/test_utils.py b/pyomo/contrib/parmest/deprecated/tests/test_utils.py deleted file mode 100644 index 1a8247ddcc9..00000000000 --- a/pyomo/contrib/parmest/deprecated/tests/test_utils.py +++ /dev/null @@ -1,68 +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.common.dependencies import pandas as pd, pandas_available - -import pyomo.environ as pyo -import pyomo.common.unittest as unittest -import pyomo.contrib.parmest.parmest as parmest -from pyomo.opt import SolverFactory - -ipopt_available = SolverFactory("ipopt").available() - - -@unittest.skipIf( - not parmest.parmest_available, - "Cannot test parmest: required dependencies are missing", -) -@unittest.skipIf(not ipopt_available, "The 'ipopt' solver is not available") -class TestUtils(unittest.TestCase): - @classmethod - def setUpClass(self): - pass - - @classmethod - def tearDownClass(self): - pass - - @unittest.pytest.mark.expensive - def test_convert_param_to_var(self): - from pyomo.contrib.parmest.deprecated.examples.reactor_design.reactor_design import ( - reactor_design_model, - ) - - data = pd.DataFrame( - data=[ - [1.05, 10000, 3458.4, 1060.8, 1683.9, 1898.5], - [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"], - ) - - theta_names = ["k1", "k2", "k3"] - - instance = reactor_design_model(data.loc[0]) - solver = pyo.SolverFactory("ipopt") - solver.solve(instance) - - instance_vars = parmest.utils.convert_params_to_vars( - instance, theta_names, fix_vars=True - ) - solver.solve(instance_vars) - - assert instance.k1() == instance_vars.k1() - assert instance.k2() == instance_vars.k2() - assert instance.k3() == instance_vars.k3() - - -if __name__ == "__main__": - unittest.main() diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index 90d42e68910..9e5b480332d 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -75,7 +75,10 @@ import pyomo.contrib.parmest.graphics as graphics from pyomo.dae import ContinuousSet -import pyomo.contrib.parmest.deprecated.parmest as parmest_deprecated +from pyomo.common.deprecation import deprecated +from pyomo.common.deprecation import deprecation_warning + +DEPRECATION_VERSION = '6.7.0' parmest_available = numpy_available & pandas_available & scipy_available @@ -312,8 +315,8 @@ class Estimator(object): Provides options to the solver (also the name of an attribute) """ - # backwards compatible constructor will accept the old inputs - # from parmest_deprecated as well as the new inputs using experiment lists + # backwards compatible constructor will accept the old deprecated inputs + # as well as the new inputs using experiment lists def __init__(self, *args, **kwargs): # check that we have at least one argument @@ -322,11 +325,12 @@ def __init__(self, *args, **kwargs): # use deprecated interface self.pest_deprecated = None if callable(args[0]): - logger.warning( + deprecation_warning( 'Using deprecated parmest inputs (model_function, ' - + 'data, theta_names), please use experiment lists instead.' + + 'data, theta_names), please use experiment lists instead.', + version=DEPRECATION_VERSION, ) - self.pest_deprecated = parmest_deprecated.Estimator(*args, **kwargs) + self.pest_deprecated = DeprecatedEstimator(*args, **kwargs) return # check that we have a (non-empty) list of experiments @@ -1436,3 +1440,1112 @@ def confidence_region_test( return training_results, test_result else: return training_results + + +################################ +# deprecated functions/classes # +################################ + + +@deprecated(version=DEPRECATION_VERSION) +def group_data(data, groupby_column_name, use_mean=None): + """ + Group data by scenario + + Parameters + ---------- + data: DataFrame + Data + groupby_column_name: strings + Name of data column which contains scenario numbers + use_mean: list of column names or None, optional + Name of data columns which should be reduced to a single value per + scenario by taking the mean + + Returns + ---------- + grouped_data: list of dictionaries + Grouped data + """ + if use_mean is None: + use_mean_list = [] + else: + use_mean_list = use_mean + + grouped_data = [] + for exp_num, group in data.groupby(data[groupby_column_name]): + d = {} + for col in group.columns: + if col in use_mean_list: + d[col] = group[col].mean() + else: + d[col] = list(group[col]) + grouped_data.append(d) + + return grouped_data + + +class _DeprecatedSecondStageCostExpr(object): + """ + Class to pass objective expression into the Pyomo model + """ + + def __init__(self, ssc_function, data): + self._ssc_function = ssc_function + self._data = data + + def __call__(self, model): + return self._ssc_function(model, self._data) + + +class DeprecatedEstimator(object): + """ + Parameter estimation class + + Parameters + ---------- + model_function: function + Function that generates an instance of the Pyomo model using 'data' + as the input argument + data: pd.DataFrame, list of dictionaries, list of dataframes, or list of json file names + Data that is used to build an instance of the Pyomo model and build + the objective function + theta_names: list of strings + List of Var names to estimate + obj_function: function, optional + Function used to formulate parameter estimation objective, generally + sum of squared error between measurements and model variables. + If no function is specified, the model is used + "as is" and should be defined with a "FirstStageCost" and + "SecondStageCost" expression that are used to build an objective. + tee: bool, optional + Indicates that ef solver output should be teed + diagnostic_mode: bool, optional + If True, print diagnostics from the solver + solver_options: dict, optional + Provides options to the solver (also the name of an attribute) + """ + + def __init__( + self, + model_function, + data, + theta_names, + obj_function=None, + tee=False, + diagnostic_mode=False, + solver_options=None, + ): + self.model_function = model_function + + assert isinstance( + data, (list, pd.DataFrame) + ), "Data must be a list or DataFrame" + # convert dataframe into a list of dataframes, each row = one scenario + if isinstance(data, pd.DataFrame): + self.callback_data = [ + data.loc[i, :].to_frame().transpose() for i in data.index + ] + else: + self.callback_data = data + assert isinstance( + self.callback_data[0], (dict, pd.DataFrame, str) + ), "The scenarios in data must be a dictionary, DataFrame or filename" + + if len(theta_names) == 0: + self.theta_names = ['parmest_dummy_var'] + else: + self.theta_names = theta_names + + self.obj_function = obj_function + self.tee = tee + self.diagnostic_mode = diagnostic_mode + self.solver_options = solver_options + + self._second_stage_cost_exp = "SecondStageCost" + # boolean to indicate if model is initialized using a square solve + self.model_initialized = False + + def _return_theta_names(self): + """ + Return list of fitted model parameter names + """ + # if fitted model parameter names differ from theta_names created when Estimator object is created + if hasattr(self, 'theta_names_updated'): + return self.theta_names_updated + + else: + return ( + self.theta_names + ) # default theta_names, created when Estimator object is created + + def _create_parmest_model(self, data): + """ + Modify the Pyomo model for parameter estimation + """ + model = self.model_function(data) + + if (len(self.theta_names) == 1) and ( + self.theta_names[0] == 'parmest_dummy_var' + ): + model.parmest_dummy_var = pyo.Var(initialize=1.0) + + # Add objective function (optional) + if self.obj_function: + for obj in model.component_objects(pyo.Objective): + if obj.name in ["Total_Cost_Objective"]: + raise RuntimeError( + "Parmest will not override the existing model Objective named " + + obj.name + ) + obj.deactivate() + + for expr in model.component_data_objects(pyo.Expression): + if expr.name in ["FirstStageCost", "SecondStageCost"]: + raise RuntimeError( + "Parmest will not override the existing model Expression named " + + expr.name + ) + model.FirstStageCost = pyo.Expression(expr=0) + model.SecondStageCost = pyo.Expression( + rule=_DeprecatedSecondStageCostExpr(self.obj_function, data) + ) + + def TotalCost_rule(model): + return model.FirstStageCost + model.SecondStageCost + + model.Total_Cost_Objective = pyo.Objective( + rule=TotalCost_rule, sense=pyo.minimize + ) + + # Convert theta Params to Vars, and unfix theta Vars + model = utils.convert_params_to_vars(model, self.theta_names) + + # Update theta names list to use CUID string representation + for i, theta in enumerate(self.theta_names): + var_cuid = ComponentUID(theta) + var_validate = var_cuid.find_component_on(model) + if var_validate is None: + logger.warning( + "theta_name[%s] (%s) was not found on the model", (i, theta) + ) + else: + try: + # If the component is not a variable, + # this will generate an exception (and the warning + # in the 'except') + var_validate.unfix() + self.theta_names[i] = repr(var_cuid) + except: + logger.warning(theta + ' is not a variable') + + self.parmest_model = model + + return model + + def _instance_creation_callback(self, experiment_number=None, cb_data=None): + # cb_data is a list of dictionaries, list of dataframes, OR list of json file names + exp_data = cb_data[experiment_number] + if isinstance(exp_data, (dict, pd.DataFrame)): + pass + elif isinstance(exp_data, str): + try: + with open(exp_data, 'r') as infile: + exp_data = json.load(infile) + except: + raise RuntimeError(f'Could not read {exp_data} as json') + else: + raise RuntimeError(f'Unexpected data format for cb_data={cb_data}') + model = self._create_parmest_model(exp_data) + + return model + + def _Q_opt( + self, + ThetaVals=None, + solver="ef_ipopt", + return_values=[], + bootlist=None, + calc_cov=False, + cov_n=None, + ): + """ + Set up all thetas as first stage Vars, return resulting theta + values as well as the objective function value. + + """ + if solver == "k_aug": + raise RuntimeError("k_aug no longer supported.") + + # (Bootstrap scenarios will use indirection through the bootlist) + if bootlist is None: + scenario_numbers = list(range(len(self.callback_data))) + scen_names = ["Scenario{}".format(i) for i in scenario_numbers] + else: + scen_names = ["Scenario{}".format(i) for i in range(len(bootlist))] + + # tree_model.CallbackModule = None + outer_cb_data = dict() + outer_cb_data["callback"] = self._instance_creation_callback + if ThetaVals is not None: + outer_cb_data["ThetaVals"] = ThetaVals + if bootlist is not None: + outer_cb_data["BootList"] = bootlist + outer_cb_data["cb_data"] = self.callback_data # None is OK + outer_cb_data["theta_names"] = self.theta_names + + options = {"solver": "ipopt"} + scenario_creator_options = {"cb_data": outer_cb_data} + if use_mpisppy: + ef = sputils.create_EF( + scen_names, + _experiment_instance_creation_callback, + EF_name="_Q_opt", + suppress_warnings=True, + scenario_creator_kwargs=scenario_creator_options, + ) + else: + ef = local_ef.create_EF( + scen_names, + _experiment_instance_creation_callback, + EF_name="_Q_opt", + suppress_warnings=True, + scenario_creator_kwargs=scenario_creator_options, + ) + self.ef_instance = ef + + # Solve the extensive form with ipopt + if solver == "ef_ipopt": + if not calc_cov: + # Do not calculate the reduced hessian + + solver = SolverFactory('ipopt') + if self.solver_options is not None: + for key in self.solver_options: + solver.options[key] = self.solver_options[key] + + solve_result = solver.solve(self.ef_instance, tee=self.tee) + + # The import error will be raised when we attempt to use + # inv_reduced_hessian_barrier below. + # + # elif not asl_available: + # raise ImportError("parmest requires ASL to calculate the " + # "covariance matrix with solver 'ipopt'") + else: + # parmest makes the fitted parameters stage 1 variables + ind_vars = [] + 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, + ) + ) + + if self.diagnostic_mode: + print( + ' Solver termination condition = ', + str(solve_result.solver.termination_condition), + ) + + # assume all first stage are thetas... + thetavals = {} + for ndname, Var, solval in ef_nonants(ef): + # process the name + # the scenarios are blocks, so strip the scenario name + vname = Var.name[Var.name.find(".") + 1 :] + thetavals[vname] = solval + + objval = pyo.value(ef.EF_Obj) + + if calc_cov: + # Calculate the covariance matrix + + # Number of data points considered + n = cov_n + + # Extract number of fitted parameters + l = len(thetavals) + + # Assumption: Objective value is sum of squared errors + sse = objval + + '''Calculate covariance assuming experimental observation errors are + independent and follow a Gaussian + distribution with constant variance. + + The formula used in parmest was verified against equations (7-5-15) and + (7-5-16) in "Nonlinear Parameter Estimation", Y. Bard, 1974. + + This formula is also applicable if the objective is scaled by a constant; + the constant cancels out. (was scaled by 1/n because it computes an + expected value.) + ''' + cov = 2 * sse / (n - l) * inv_red_hes + cov = pd.DataFrame( + cov, index=thetavals.keys(), columns=thetavals.keys() + ) + + thetavals = pd.Series(thetavals) + + if len(return_values) > 0: + var_values = [] + if len(scen_names) > 1: # multiple scenarios + block_objects = self.ef_instance.component_objects( + Block, descend_into=False + ) + else: # single scenario + block_objects = [self.ef_instance] + for exp_i in block_objects: + vals = {} + for var in return_values: + exp_i_var = exp_i.find_component(str(var)) + if ( + exp_i_var is None + ): # we might have a block such as _mpisppy_data + continue + # if value to return is ContinuousSet + if type(exp_i_var) == ContinuousSet: + temp = list(exp_i_var) + else: + temp = [pyo.value(_) for _ in exp_i_var.values()] + if len(temp) == 1: + vals[var] = temp[0] + else: + vals[var] = temp + if len(vals) > 0: + var_values.append(vals) + var_values = pd.DataFrame(var_values) + if calc_cov: + return objval, thetavals, var_values, cov + else: + return objval, thetavals, var_values + + if calc_cov: + return objval, thetavals, cov + else: + return objval, thetavals + + else: + raise RuntimeError("Unknown solver in Q_Opt=" + solver) + + def _Q_at_theta(self, thetavals, initialize_parmest_model=False): + """ + Return the objective function value with fixed theta values. + + Parameters + ---------- + thetavals: dict + A dictionary of theta values. + + initialize_parmest_model: boolean + If True: Solve square problem instance, build extensive form of the model for + parameter estimation, and set flag model_initialized to True + + Returns + ------- + objectiveval: float + The objective function value. + thetavals: dict + A dictionary of all values for theta that were input. + solvertermination: Pyomo TerminationCondition + Tries to return the "worst" solver status across the scenarios. + pyo.TerminationCondition.optimal is the best and + pyo.TerminationCondition.infeasible is the worst. + """ + + optimizer = pyo.SolverFactory('ipopt') + + if len(thetavals) > 0: + dummy_cb = { + "callback": self._instance_creation_callback, + "ThetaVals": thetavals, + "theta_names": self._return_theta_names(), + "cb_data": self.callback_data, + } + else: + dummy_cb = { + "callback": self._instance_creation_callback, + "theta_names": self._return_theta_names(), + "cb_data": self.callback_data, + } + + if self.diagnostic_mode: + if len(thetavals) > 0: + print(' Compute objective at theta = ', str(thetavals)) + else: + print(' Compute objective at initial theta') + + # start block of code to deal with models with no constraints + # (ipopt will crash or complain on such problems without special care) + instance = _experiment_instance_creation_callback("FOO0", None, dummy_cb) + try: # deal with special problems so Ipopt will not crash + first = next(instance.component_objects(pyo.Constraint, active=True)) + active_constraints = True + except: + active_constraints = False + # end block of code to deal with models with no constraints + + WorstStatus = pyo.TerminationCondition.optimal + totobj = 0 + scenario_numbers = list(range(len(self.callback_data))) + if initialize_parmest_model: + # create dictionary to store pyomo model instances (scenarios) + scen_dict = dict() + + for snum in scenario_numbers: + sname = "scenario_NODE" + str(snum) + instance = _experiment_instance_creation_callback(sname, None, dummy_cb) + + if initialize_parmest_model: + # list to store fitted parameter names that will be unfixed + # after initialization + theta_init_vals = [] + # use appropriate theta_names member + theta_ref = self._return_theta_names() + + for i, theta in enumerate(theta_ref): + # Use parser in ComponentUID to locate the component + var_cuid = ComponentUID(theta) + var_validate = var_cuid.find_component_on(instance) + if var_validate is None: + logger.warning( + "theta_name %s was not found on the model", (theta) + ) + else: + try: + if len(thetavals) == 0: + var_validate.fix() + else: + var_validate.fix(thetavals[theta]) + theta_init_vals.append(var_validate) + except: + logger.warning( + 'Unable to fix model parameter value for %s (not a Pyomo model Var)', + (theta), + ) + + if active_constraints: + 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 + ) + ) + print( + " status_obj, solved, iters, time, regularization_stat = ", + str(status_obj), + str(solved), + str(iters), + str(time), + str(regu), + ) + + results = optimizer.solve(instance) + if self.diagnostic_mode: + print( + 'standard solve solver termination condition=', + str(results.solver.termination_condition), + ) + + if ( + results.solver.termination_condition + != pyo.TerminationCondition.optimal + ): + # DLW: Aug2018: not distinguishing "middlish" conditions + if WorstStatus != pyo.TerminationCondition.infeasible: + WorstStatus = results.solver.termination_condition + if initialize_parmest_model: + if self.diagnostic_mode: + print( + "Scenario {:d} infeasible with initialized parameter values".format( + snum + ) + ) + else: + if initialize_parmest_model: + if self.diagnostic_mode: + print( + "Scenario {:d} initialization successful with initial parameter values".format( + snum + ) + ) + if initialize_parmest_model: + # unfix parameters after initialization + for theta in theta_init_vals: + theta.unfix() + scen_dict[sname] = instance + else: + if initialize_parmest_model: + # unfix parameters after initialization + for theta in theta_init_vals: + theta.unfix() + scen_dict[sname] = instance + + objobject = getattr(instance, self._second_stage_cost_exp) + objval = pyo.value(objobject) + totobj += objval + + retval = totobj / len(scenario_numbers) # -1?? + if initialize_parmest_model and not hasattr(self, 'ef_instance'): + # create extensive form of the model using scenario dictionary + if len(scen_dict) > 0: + for scen in scen_dict.values(): + scen._mpisppy_probability = 1 / len(scen_dict) + + if use_mpisppy: + EF_instance = sputils._create_EF_from_scen_dict( + scen_dict, + EF_name="_Q_at_theta", + # suppress_warnings=True + ) + else: + EF_instance = local_ef._create_EF_from_scen_dict( + scen_dict, EF_name="_Q_at_theta", nonant_for_fixed_vars=True + ) + + self.ef_instance = EF_instance + # set self.model_initialized flag to True to skip extensive form model + # creation using theta_est() + self.model_initialized = True + + # return initialized theta values + if len(thetavals) == 0: + # use appropriate theta_names member + theta_ref = self._return_theta_names() + for i, theta in enumerate(theta_ref): + thetavals[theta] = theta_init_vals[i]() + + return retval, thetavals, WorstStatus + + def _get_sample_list(self, samplesize, num_samples, replacement=True): + samplelist = list() + + scenario_numbers = list(range(len(self.callback_data))) + + if num_samples is None: + # This could get very large + for i, l in enumerate(combinations(scenario_numbers, samplesize)): + samplelist.append((i, np.sort(l))) + else: + for i in range(num_samples): + attempts = 0 + unique_samples = 0 # check for duplicates in each sample + duplicate = False # check for duplicates between samples + while (unique_samples <= len(self._return_theta_names())) and ( + not duplicate + ): + sample = np.random.choice( + scenario_numbers, samplesize, replace=replacement + ) + sample = np.sort(sample).tolist() + unique_samples = len(np.unique(sample)) + if sample in samplelist: + duplicate = True + + attempts += 1 + if attempts > num_samples: # arbitrary timeout limit + raise RuntimeError( + """Internal error: timeout constructing + a sample, the dim of theta may be too + close to the samplesize""" + ) + + samplelist.append((i, sample)) + + return samplelist + + def theta_est( + self, solver="ef_ipopt", return_values=[], calc_cov=False, cov_n=None + ): + """ + Parameter estimation using all scenarios in the data + + Parameters + ---------- + solver: string, optional + Currently only "ef_ipopt" is supported. Default is "ef_ipopt". + return_values: list, optional + List of Variable names, used to return values from the model for data reconciliation + calc_cov: boolean, optional + If True, calculate and return the covariance matrix (only for "ef_ipopt" solver) + cov_n: int, optional + If calc_cov=True, then the user needs to supply the number of datapoints + that are used in the objective function + + Returns + ------- + objectiveval: float + The objective function value + thetavals: pd.Series + Estimated values for theta + variable values: pd.DataFrame + Variable values for each variable name in return_values (only for solver='ef_ipopt') + cov: pd.DataFrame + Covariance matrix of the fitted parameters (only for solver='ef_ipopt') + """ + assert isinstance(solver, str) + assert isinstance(return_values, list) + assert isinstance(calc_cov, bool) + if calc_cov: + assert isinstance( + cov_n, int + ), "The number of datapoints that are used in the objective function is required to calculate the covariance matrix" + assert cov_n > len( + self._return_theta_names() + ), "The number of datapoints must be greater than the number of parameters to estimate" + + return self._Q_opt( + solver=solver, + return_values=return_values, + bootlist=None, + calc_cov=calc_cov, + cov_n=cov_n, + ) + + def theta_est_bootstrap( + self, + bootstrap_samples, + samplesize=None, + replacement=True, + seed=None, + return_samples=False, + ): + """ + Parameter estimation using bootstrap resampling of the data + + Parameters + ---------- + bootstrap_samples: int + Number of bootstrap samples to draw from the data + samplesize: int or None, optional + Size of each bootstrap sample. If samplesize=None, samplesize will be + set to the number of samples in the data + replacement: bool, optional + Sample with or without replacement + seed: int or None, optional + Random seed + return_samples: bool, optional + Return a list of sample numbers used in each bootstrap estimation + + Returns + ------- + bootstrap_theta: pd.DataFrame + Theta values for each sample and (if return_samples = True) + the sample numbers used in each estimation + """ + assert isinstance(bootstrap_samples, int) + assert isinstance(samplesize, (type(None), int)) + assert isinstance(replacement, bool) + assert isinstance(seed, (type(None), int)) + assert isinstance(return_samples, bool) + + if samplesize is None: + samplesize = len(self.callback_data) + + if seed is not None: + np.random.seed(seed) + + global_list = self._get_sample_list(samplesize, bootstrap_samples, replacement) + + task_mgr = utils.ParallelTaskManager(bootstrap_samples) + local_list = task_mgr.global_to_local_data(global_list) + + bootstrap_theta = list() + for idx, sample in local_list: + objval, thetavals = self._Q_opt(bootlist=list(sample)) + thetavals['samples'] = sample + bootstrap_theta.append(thetavals) + + global_bootstrap_theta = task_mgr.allgather_global_data(bootstrap_theta) + bootstrap_theta = pd.DataFrame(global_bootstrap_theta) + + if not return_samples: + del bootstrap_theta['samples'] + + return bootstrap_theta + + def theta_est_leaveNout( + self, lNo, lNo_samples=None, seed=None, return_samples=False + ): + """ + Parameter estimation where N data points are left out of each sample + + Parameters + ---------- + lNo: int + Number of data points to leave out for parameter estimation + lNo_samples: int + Number of leave-N-out samples. If lNo_samples=None, the maximum + number of combinations will be used + seed: int or None, optional + Random seed + return_samples: bool, optional + Return a list of sample numbers that were left out + + Returns + ------- + lNo_theta: pd.DataFrame + Theta values for each sample and (if return_samples = True) + the sample numbers left out of each estimation + """ + assert isinstance(lNo, int) + assert isinstance(lNo_samples, (type(None), int)) + assert isinstance(seed, (type(None), int)) + assert isinstance(return_samples, bool) + + samplesize = len(self.callback_data) - lNo + + if seed is not None: + np.random.seed(seed) + + global_list = self._get_sample_list(samplesize, lNo_samples, replacement=False) + + task_mgr = utils.ParallelTaskManager(len(global_list)) + local_list = task_mgr.global_to_local_data(global_list) + + lNo_theta = list() + for idx, sample in local_list: + objval, thetavals = self._Q_opt(bootlist=list(sample)) + lNo_s = list(set(range(len(self.callback_data))) - set(sample)) + thetavals['lNo'] = np.sort(lNo_s) + lNo_theta.append(thetavals) + + global_bootstrap_theta = task_mgr.allgather_global_data(lNo_theta) + lNo_theta = pd.DataFrame(global_bootstrap_theta) + + if not return_samples: + del lNo_theta['lNo'] + + return lNo_theta + + def leaveNout_bootstrap_test( + self, lNo, lNo_samples, bootstrap_samples, distribution, alphas, seed=None + ): + """ + Leave-N-out bootstrap test to compare theta values where N data points are + left out to a bootstrap analysis using the remaining data, + results indicate if theta is within a confidence region + determined by the bootstrap analysis + + Parameters + ---------- + lNo: int + Number of data points to leave out for parameter estimation + lNo_samples: int + Leave-N-out sample size. If lNo_samples=None, the maximum number + of combinations will be used + bootstrap_samples: int: + Bootstrap sample size + distribution: string + Statistical distribution used to define a confidence region, + options = 'MVN' for multivariate_normal, 'KDE' for gaussian_kde, + and 'Rect' for rectangular. + alphas: list + List of alpha values used to determine if theta values are inside + or outside the region. + seed: int or None, optional + Random seed + + Returns + ---------- + List of tuples with one entry per lNo_sample: + + * The first item in each tuple is the list of N samples that are left + out. + * The second item in each tuple is a DataFrame of theta estimated using + the N samples. + * The third item in each tuple is a DataFrame containing results from + the bootstrap analysis using the remaining samples. + + For each DataFrame a column is added for each value of alpha which + indicates if the theta estimate is in (True) or out (False) of the + alpha region for a given distribution (based on the bootstrap results) + """ + assert isinstance(lNo, int) + assert isinstance(lNo_samples, (type(None), int)) + assert isinstance(bootstrap_samples, int) + assert distribution in ['Rect', 'MVN', 'KDE'] + assert isinstance(alphas, list) + assert isinstance(seed, (type(None), int)) + + if seed is not None: + np.random.seed(seed) + + data = self.callback_data.copy() + + global_list = self._get_sample_list(lNo, lNo_samples, replacement=False) + + results = [] + for idx, sample in global_list: + # Reset callback_data to only include the sample + self.callback_data = [data[i] for i in sample] + + obj, theta = self.theta_est() + + # Reset callback_data to include all scenarios except the sample + self.callback_data = [data[i] for i in range(len(data)) if i not in sample] + + bootstrap_theta = self.theta_est_bootstrap(bootstrap_samples) + + training, test = self.confidence_region_test( + bootstrap_theta, + distribution=distribution, + alphas=alphas, + test_theta_values=theta, + ) + + results.append((sample, test, training)) + + # Reset callback_data (back to full data set) + self.callback_data = data + + return results + + def objective_at_theta(self, theta_values=None, initialize_parmest_model=False): + """ + Objective value for each theta + + Parameters + ---------- + theta_values: pd.DataFrame, columns=theta_names + Values of theta used to compute the objective + + initialize_parmest_model: boolean + If True: Solve square problem instance, build extensive form of the model for + parameter estimation, and set flag model_initialized to True + + + Returns + ------- + obj_at_theta: pd.DataFrame + Objective value for each theta (infeasible solutions are + omitted). + """ + if len(self.theta_names) == 1 and self.theta_names[0] == 'parmest_dummy_var': + pass # skip assertion if model has no fitted parameters + else: + # create a local instance of the pyomo model to access model variables and parameters + model_temp = self._create_parmest_model(self.callback_data[0]) + model_theta_list = [] # list to store indexed and non-indexed parameters + # iterate over original theta_names + for theta_i in self.theta_names: + var_cuid = ComponentUID(theta_i) + var_validate = var_cuid.find_component_on(model_temp) + # check if theta in theta_names are indexed + try: + # get component UID of Set over which theta is defined + set_cuid = ComponentUID(var_validate.index_set()) + # access and iterate over the Set to generate theta names as they appear + # in the pyomo model + set_validate = set_cuid.find_component_on(model_temp) + for s in set_validate: + self_theta_temp = repr(var_cuid) + "[" + repr(s) + "]" + # generate list of theta names + model_theta_list.append(self_theta_temp) + # if theta is not indexed, copy theta name to list as-is + except AttributeError: + self_theta_temp = repr(var_cuid) + model_theta_list.append(self_theta_temp) + except: + raise + # if self.theta_names is not the same as temp model_theta_list, + # create self.theta_names_updated + if set(self.theta_names) == set(model_theta_list) and len( + self.theta_names + ) == set(model_theta_list): + pass + else: + self.theta_names_updated = model_theta_list + + if theta_values is None: + all_thetas = {} # dictionary to store fitted variables + # use appropriate theta names member + theta_names = self._return_theta_names() + else: + assert isinstance(theta_values, pd.DataFrame) + # for parallel code we need to use lists and dicts in the loop + theta_names = theta_values.columns + # # check if theta_names are in model + for theta in list(theta_names): + theta_temp = theta.replace("'", "") # cleaning quotes from theta_names + + assert theta_temp in [ + t.replace("'", "") for t in model_theta_list + ], "Theta name {} in 'theta_values' not in 'theta_names' {}".format( + theta_temp, model_theta_list + ) + assert len(list(theta_names)) == len(model_theta_list) + + all_thetas = theta_values.to_dict('records') + + if all_thetas: + task_mgr = utils.ParallelTaskManager(len(all_thetas)) + local_thetas = task_mgr.global_to_local_data(all_thetas) + else: + if initialize_parmest_model: + task_mgr = utils.ParallelTaskManager( + 1 + ) # initialization performed using just 1 set of theta values + # walk over the mesh, return objective function + all_obj = list() + if len(all_thetas) > 0: + for Theta in local_thetas: + obj, thetvals, worststatus = self._Q_at_theta( + Theta, initialize_parmest_model=initialize_parmest_model + ) + if worststatus != pyo.TerminationCondition.infeasible: + all_obj.append(list(Theta.values()) + [obj]) + # DLW, Aug2018: should we also store the worst solver status? + else: + obj, thetvals, worststatus = self._Q_at_theta( + thetavals={}, initialize_parmest_model=initialize_parmest_model + ) + if worststatus != pyo.TerminationCondition.infeasible: + all_obj.append(list(thetvals.values()) + [obj]) + + global_all_obj = task_mgr.allgather_global_data(all_obj) + dfcols = list(theta_names) + ['obj'] + obj_at_theta = pd.DataFrame(data=global_all_obj, columns=dfcols) + return obj_at_theta + + def likelihood_ratio_test( + self, obj_at_theta, obj_value, alphas, return_thresholds=False + ): + r""" + Likelihood ratio test to identify theta values within a confidence + region using the :math:`\chi^2` distribution + + Parameters + ---------- + obj_at_theta: pd.DataFrame, columns = theta_names + 'obj' + Objective values for each theta value (returned by + objective_at_theta) + obj_value: int or float + Objective value from parameter estimation using all data + alphas: list + List of alpha values to use in the chi2 test + return_thresholds: bool, optional + Return the threshold value for each alpha + + Returns + ------- + LR: pd.DataFrame + Objective values for each theta value along with True or False for + each alpha + thresholds: pd.Series + If return_threshold = True, the thresholds are also returned. + """ + assert isinstance(obj_at_theta, pd.DataFrame) + assert isinstance(obj_value, (int, float)) + assert isinstance(alphas, list) + assert isinstance(return_thresholds, bool) + + LR = obj_at_theta.copy() + S = len(self.callback_data) + thresholds = {} + for a in alphas: + chi2_val = scipy.stats.chi2.ppf(a, 2) + thresholds[a] = obj_value * ((chi2_val / (S - 2)) + 1) + LR[a] = LR['obj'] < thresholds[a] + + thresholds = pd.Series(thresholds) + + if return_thresholds: + return LR, thresholds + else: + return LR + + def confidence_region_test( + self, theta_values, distribution, alphas, test_theta_values=None + ): + """ + Confidence region test to determine if theta values are within a + rectangular, multivariate normal, or Gaussian kernel density distribution + for a range of alpha values + + Parameters + ---------- + theta_values: pd.DataFrame, columns = theta_names + Theta values used to generate a confidence region + (generally returned by theta_est_bootstrap) + distribution: string + Statistical distribution used to define a confidence region, + options = 'MVN' for multivariate_normal, 'KDE' for gaussian_kde, + and 'Rect' for rectangular. + alphas: list + List of alpha values used to determine if theta values are inside + or outside the region. + test_theta_values: pd.Series or pd.DataFrame, keys/columns = theta_names, optional + Additional theta values that are compared to the confidence region + to determine if they are inside or outside. + + Returns + training_results: pd.DataFrame + Theta value used to generate the confidence region along with True + (inside) or False (outside) for each alpha + test_results: pd.DataFrame + If test_theta_values is not None, returns test theta value along + with True (inside) or False (outside) for each alpha + """ + assert isinstance(theta_values, pd.DataFrame) + assert distribution in ['Rect', 'MVN', 'KDE'] + assert isinstance(alphas, list) + assert isinstance( + test_theta_values, (type(None), dict, pd.Series, pd.DataFrame) + ) + + if isinstance(test_theta_values, (dict, pd.Series)): + test_theta_values = pd.Series(test_theta_values).to_frame().transpose() + + training_results = theta_values.copy() + + if test_theta_values is not None: + test_result = test_theta_values.copy() + + for a in alphas: + if distribution == 'Rect': + lb, ub = graphics.fit_rect_dist(theta_values, a) + training_results[a] = (theta_values > lb).all(axis=1) & ( + theta_values < ub + ).all(axis=1) + + if test_theta_values is not None: + # use upper and lower bound from the training set + test_result[a] = (test_theta_values > lb).all(axis=1) & ( + test_theta_values < ub + ).all(axis=1) + + elif distribution == 'MVN': + dist = graphics.fit_mvn_dist(theta_values) + Z = dist.pdf(theta_values) + score = scipy.stats.scoreatpercentile(Z, (1 - a) * 100) + training_results[a] = Z >= score + + if test_theta_values is not None: + # use score from the training set + Z = dist.pdf(test_theta_values) + test_result[a] = Z >= score + + elif distribution == 'KDE': + dist = graphics.fit_kde_dist(theta_values) + Z = dist.pdf(theta_values.transpose()) + score = scipy.stats.scoreatpercentile(Z, (1 - a) * 100) + training_results[a] = Z >= score + + if test_theta_values is not None: + # use score from the training set + Z = dist.pdf(test_theta_values.transpose()) + test_result[a] = Z >= score + + if test_theta_values is not None: + return training_results, test_result + else: + return training_results diff --git a/pyomo/contrib/parmest/scenariocreator.py b/pyomo/contrib/parmest/scenariocreator.py index 76ea2f2ab81..434e15e7f31 100644 --- a/pyomo/contrib/parmest/scenariocreator.py +++ b/pyomo/contrib/parmest/scenariocreator.py @@ -14,7 +14,10 @@ import pyomo.environ as pyo -import pyomo.contrib.parmest.deprecated.scenariocreator as scen_deprecated +from pyomo.common.deprecation import deprecated +from pyomo.common.deprecation import deprecation_warning + +DEPRECATION_VERSION = '6.7.0' import logging @@ -129,11 +132,12 @@ def __init__(self, pest, solvername): # is this a deprecated pest object? self.scen_deprecated = None if pest.pest_deprecated is not None: - logger.warning( + deprecation_warning( "Using a deprecated parmest object for scenario " - + "creator, please recreate object using experiment lists." + + "creator, please recreate object using experiment lists.", + version=DEPRECATION_VERSION, ) - self.scen_deprecated = scen_deprecated.ScenarioCreator( + self.scen_deprecated = ScenarioCreatorDeprecated( pest.pest_deprecated, solvername ) else: @@ -190,3 +194,65 @@ def ScenariosFromBootstrap(self, addtoSet, numtomake, seed=None): bootstrap_thetas = self.pest.theta_est_bootstrap(numtomake, seed=seed) addtoSet.append_bootstrap(bootstrap_thetas) + + +################################ +# deprecated functions/classes # +################################ + + +class ScenarioCreatorDeprecated(object): + """Create scenarios from parmest. + + Args: + pest (Estimator): the parmest object + solvername (str): name of the solver (e.g. "ipopt") + + """ + + def __init__(self, pest, solvername): + self.pest = pest + self.solvername = solvername + + def ScenariosFromExperiments(self, addtoSet): + """Creates new self.Scenarios list using the experiments only. + + Args: + addtoSet (ScenarioSet): the scenarios will be added to this set + Returns: + a ScenarioSet + """ + + # assert isinstance(addtoSet, ScenarioSet) + + scenario_numbers = list(range(len(self.pest.callback_data))) + + prob = 1.0 / len(scenario_numbers) + for exp_num in scenario_numbers: + ##print("Experiment number=", exp_num) + model = self.pest._instance_creation_callback( + exp_num, self.pest.callback_data + ) + opt = pyo.SolverFactory(self.solvername) + results = opt.solve(model) # solves and updates model + ## pyo.check_termination_optimal(results) + ThetaVals = dict() + for theta in self.pest.theta_names: + tvar = eval('model.' + theta) + tval = pyo.value(tvar) + ##print(" theta, tval=", tvar, tval) + ThetaVals[theta] = tval + addtoSet.addone(ParmestScen("ExpScen" + str(exp_num), ThetaVals, prob)) + + def ScenariosFromBootstrap(self, addtoSet, numtomake, seed=None): + """Creates new self.Scenarios list using the experiments only. + + Args: + addtoSet (ScenarioSet): the scenarios will be added to this set + numtomake (int) : number of scenarios to create + """ + + # assert isinstance(addtoSet, ScenarioSet) + + bootstrap_thetas = self.pest.theta_est_bootstrap(numtomake, seed=seed) + addtoSet.append_bootstrap(bootstrap_thetas) diff --git a/pyomo/contrib/parmest/tests/test_parmest.py b/pyomo/contrib/parmest/tests/test_parmest.py index 15264a18989..9c65a31352f 100644 --- a/pyomo/contrib/parmest/tests/test_parmest.py +++ b/pyomo/contrib/parmest/tests/test_parmest.py @@ -232,16 +232,16 @@ def test_theta_est_cov(self): # Covariance matrix self.assertAlmostEqual( - cov['asymptote']['asymptote'], 6.30579403, places=2 + cov["asymptote"]["asymptote"], 6.30579403, places=2 ) # 6.22864 from paper self.assertAlmostEqual( - cov['asymptote']['rate_constant'], -0.4395341, places=2 + cov["asymptote"]["rate_constant"], -0.4395341, places=2 ) # -0.4322 from paper self.assertAlmostEqual( - cov['rate_constant']['asymptote'], -0.4395341, places=2 + cov["rate_constant"]["asymptote"], -0.4395341, places=2 ) # -0.4322 from paper self.assertAlmostEqual( - cov['rate_constant']['rate_constant'], 0.04124, places=2 + cov["rate_constant"]["rate_constant"], 0.04124, places=2 ) # 0.04124 from paper """ Why does the covariance matrix from parmest not match the paper? Parmest is @@ -427,8 +427,8 @@ def label_model(self): m = self.model m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs.update([(m.hour, self.data['hour'])]) - m.experiment_outputs.update([(m.y, self.data['y'])]) + m.experiment_outputs.update([(m.hour, self.data["hour"])]) + m.experiment_outputs.update([(m.y, self.data["y"])]) m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) m.unknown_parameters.update((k, pyo.ComponentUID(k)) for k in [m.theta]) @@ -506,8 +506,8 @@ def label_model(self): m = self.model m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs.update([(m.hour, self.data['hour'])]) - m.experiment_outputs.update([(m.y, self.data['y'])]) + m.experiment_outputs.update([(m.hour, self.data["hour"])]) + m.experiment_outputs.update([(m.y, self.data["y"])]) m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) m.unknown_parameters.update((k, pyo.ComponentUID(k)) for k in [m.theta]) @@ -575,9 +575,9 @@ def check_rooney_biegler_results(self, objval, cov): # get indices in covariance matrix cov_cols = cov.columns.to_list() - asymptote_index = [idx for idx, s in enumerate(cov_cols) if 'asymptote' in s][0] + asymptote_index = [idx for idx, s in enumerate(cov_cols) if "asymptote" in s][0] rate_constant_index = [ - idx for idx, s in enumerate(cov_cols) if 'rate_constant' in s + idx for idx, s in enumerate(cov_cols) if "rate_constant" in s ][0] self.assertAlmostEqual(objval, 4.3317112, places=2) @@ -698,7 +698,7 @@ def setUp(self): solver_options = {"max_iter": 6000} self.pest = parmest.Estimator( - exp_list, obj_function='SSE', solver_options=solver_options + exp_list, obj_function="SSE", solver_options=solver_options ) def test_theta_est(self): @@ -1033,5 +1033,1031 @@ def test_theta_est_with_square_initialization_diagnostic_mode_true(self): self.pest.diagnostic_mode = False +########################### +# tests for deprecated UI # +########################### + + +@unittest.skipIf( + not parmest.parmest_available, + "Cannot test parmest: required dependencies are missing", +) +@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") +class TestRooneyBieglerDeprecated(unittest.TestCase): + def setUp(self): + + def rooney_biegler_model(data): + model = pyo.ConcreteModel() + + model.asymptote = pyo.Var(initialize=15) + model.rate_constant = pyo.Var(initialize=0.5) + + def response_rule(m, h): + expr = m.asymptote * (1 - pyo.exp(-m.rate_constant * h)) + return expr + + model.response_function = pyo.Expression(data.hour, rule=response_rule) + + def SSE_rule(m): + return sum( + (data.y[i] - m.response_function[data.hour[i]]) ** 2 + for i in data.index + ) + + model.SSE = pyo.Objective(rule=SSE_rule, sense=pyo.minimize) + + return model + + # 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"], + ) + + theta_names = ["asymptote", "rate_constant"] + + def SSE(model, data): + expr = sum( + (data.y[i] - model.response_function[data.hour[i]]) ** 2 + for i in data.index + ) + return expr + + solver_options = {"tol": 1e-8} + + self.data = data + self.pest = parmest.Estimator( + rooney_biegler_model, + data, + theta_names, + SSE, + solver_options=solver_options, + tee=True, + ) + + def test_theta_est(self): + objval, thetavals = self.pest.theta_est() + + self.assertAlmostEqual(objval, 4.3317112, places=2) + self.assertAlmostEqual( + thetavals["asymptote"], 19.1426, places=2 + ) # 19.1426 from the paper + self.assertAlmostEqual( + thetavals["rate_constant"], 0.5311, places=2 + ) # 0.5311 from the paper + + @unittest.skipIf( + not graphics.imports_available, "parmest.graphics imports are unavailable" + ) + def test_bootstrap(self): + objval, thetavals = self.pest.theta_est() + + num_bootstraps = 10 + theta_est = self.pest.theta_est_bootstrap(num_bootstraps, return_samples=True) + + 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"] + + # apply confidence region test + 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) + self.assertTrue(CR[0.75].sum() == 7) + self.assertTrue(CR[1.0].sum() == 10) # all true + + graphics.pairwise_plot(theta_est) + graphics.pairwise_plot(theta_est, thetavals) + graphics.pairwise_plot(theta_est, thetavals, 0.8, ["MVN", "KDE", "Rect"]) + + @unittest.skipIf( + not graphics.imports_available, "parmest.graphics imports are unavailable" + ) + def test_likelihood_ratio(self): + objval, thetavals = self.pest.theta_est() + + asym = np.arange(10, 30, 2) + rate = np.arange(0, 1.5, 0.25) + theta_vals = pd.DataFrame( + list(product(asym, rate)), columns=self.pest._return_theta_names() + ) + + obj_at_theta = self.pest.objective_at_theta(theta_vals) + + LR = self.pest.likelihood_ratio_test(obj_at_theta, objval, [0.8, 0.9, 1.0]) + + self.assertTrue(set(LR.columns) >= set([0.8, 0.9, 1.0])) + self.assertTrue(LR[0.8].sum() == 6) + self.assertTrue(LR[0.9].sum() == 10) + self.assertTrue(LR[1.0].sum() == 60) # all true + + graphics.pairwise_plot(LR, thetavals, 0.8) + + def test_leaveNout(self): + lNo_theta = self.pest.theta_est_leaveNout(1) + self.assertTrue(lNo_theta.shape == (6, 2)) + + results = self.pest.leaveNout_bootstrap_test( + 1, None, 3, "Rect", [0.5, 1.0], seed=5436 + ) + self.assertTrue(len(results) == 6) # 6 lNo samples + i = 1 + samples = results[i][0] # list of N samples that are left out + lno_theta = results[i][1] + bootstrap_theta = results[i][2] + self.assertTrue(samples == [1]) # sample 1 was left out + self.assertTrue(lno_theta.shape[0] == 1) # lno estimate for sample 1 + self.assertTrue(set(lno_theta.columns) >= set([0.5, 1.0])) + self.assertTrue(lno_theta[1.0].sum() == 1) # all true + self.assertTrue(bootstrap_theta.shape[0] == 3) # bootstrap for sample 1 + self.assertTrue(bootstrap_theta[1.0].sum() == 3) # all true + + def test_diagnostic_mode(self): + self.pest.diagnostic_mode = True + + objval, thetavals = self.pest.theta_est() + + asym = np.arange(10, 30, 2) + rate = np.arange(0, 1.5, 0.25) + theta_vals = pd.DataFrame( + list(product(asym, rate)), columns=self.pest._return_theta_names() + ) + + obj_at_theta = self.pest.objective_at_theta(theta_vals) + + self.pest.diagnostic_mode = False + + @unittest.skip("Presently having trouble with mpiexec on appveyor") + def test_parallel_parmest(self): + """use mpiexec and mpi4py""" + p = str(parmestbase.__path__) + l = p.find("'") + r = p.find("'", l + 1) + parmestpath = p[l + 1 : r] + rbpath = ( + parmestpath + + os.sep + + "examples" + + os.sep + + "rooney_biegler" + + os.sep + + "rooney_biegler_parmest.py" + ) + rbpath = os.path.abspath(rbpath) # paranoia strikes deep... + rlist = ["mpiexec", "--allow-run-as-root", "-n", "2", sys.executable, rbpath] + if sys.version_info >= (3, 5): + ret = subprocess.run(rlist) + retcode = ret.returncode + else: + retcode = subprocess.call(rlist) + assert retcode == 0 + + @unittest.skip("Most folks don't have k_aug installed") + def test_theta_k_aug_for_Hessian(self): + # this will fail if k_aug is not installed + objval, thetavals, Hessian = self.pest.theta_est(solver="k_aug") + self.assertAlmostEqual(objval, 4.4675, places=2) + + @unittest.skipIf(not pynumero_ASL_available, "pynumero ASL is not available") + @unittest.skipIf( + not parmest.inverse_reduced_hessian_available, + "Cannot test covariance matrix: required ASL dependency is missing", + ) + def test_theta_est_cov(self): + objval, thetavals, cov = self.pest.theta_est(calc_cov=True, cov_n=6) + + self.assertAlmostEqual(objval, 4.3317112, places=2) + self.assertAlmostEqual( + thetavals["asymptote"], 19.1426, places=2 + ) # 19.1426 from the paper + self.assertAlmostEqual( + thetavals["rate_constant"], 0.5311, places=2 + ) # 0.5311 from the paper + + # Covariance matrix + self.assertAlmostEqual( + cov.iloc[0, 0], 6.30579403, places=2 + ) # 6.22864 from paper + self.assertAlmostEqual( + cov.iloc[0, 1], -0.4395341, places=2 + ) # -0.4322 from paper + self.assertAlmostEqual( + cov.iloc[1, 0], -0.4395341, places=2 + ) # -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 + 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 + t: independent variable [hours] + + 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() + + # 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 + ) + theta_hat = sol.x + + self.assertAlmostEqual( + theta_hat[0], 19.1426, places=2 + ) # 19.1426 from the paper + self.assertAlmostEqual(theta_hat[1], 0.5311, places=2) # 0.5311 from the paper + + # calculate residuals + r = residual(theta_hat, t, y) + + # calculate variance of the residuals + # -2 because there are 2 fitted parameters + sigre = np.matmul(r.T, r / (len(y) - 2)) + + # approximate covariance + # Need to divide by 2 because optimize.least_squares scaled the objective by 1/2 + cov = sigre * np.linalg.inv(np.matmul(sol.jac.T, sol.jac)) + + self.assertAlmostEqual(cov[0, 0], 6.22864, places=2) # 6.22864 from paper + self.assertAlmostEqual(cov[0, 1], -0.4322, places=2) # -0.4322 from paper + self.assertAlmostEqual(cov[1, 0], -0.4322, places=2) # -0.4322 from paper + 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() + + # define initial guess + theta_guess = np.array([15, 0.5]) + + theta_hat, cov = scipy.optimize.curve_fit(model, t, y, p0=theta_guess) + + self.assertAlmostEqual( + theta_hat[0], 19.1426, places=2 + ) # 19.1426 from the paper + self.assertAlmostEqual(theta_hat[1], 0.5311, places=2) # 0.5311 from the paper + + self.assertAlmostEqual(cov[0, 0], 6.22864, places=2) # 6.22864 from paper + self.assertAlmostEqual(cov[0, 1], -0.4322, places=2) # -0.4322 from paper + self.assertAlmostEqual(cov[1, 0], -0.4322, places=2) # -0.4322 from paper + self.assertAlmostEqual(cov[1, 1], 0.04124, places=2) # 0.04124 from paper + + +@unittest.skipIf( + not parmest.parmest_available, + "Cannot test parmest: required dependencies are missing", +) +@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") +class TestModelVariantsDeprecated(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"], + ) + + def rooney_biegler_params(data): + model = pyo.ConcreteModel() + + model.asymptote = pyo.Param(initialize=15, mutable=True) + model.rate_constant = pyo.Param(initialize=0.5, mutable=True) + + def response_rule(m, h): + expr = m.asymptote * (1 - pyo.exp(-m.rate_constant * h)) + return expr + + model.response_function = pyo.Expression(data.hour, rule=response_rule) + + return model + + def rooney_biegler_indexed_params(data): + model = pyo.ConcreteModel() + + model.param_names = pyo.Set(initialize=["asymptote", "rate_constant"]) + model.theta = pyo.Param( + model.param_names, + 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) + ) + return expr + + model.response_function = pyo.Expression(data.hour, rule=response_rule) + + return model + + def rooney_biegler_vars(data): + model = pyo.ConcreteModel() + + model.asymptote = pyo.Var(initialize=15) + model.rate_constant = pyo.Var(initialize=0.5) + model.asymptote.fixed = True # parmest will unfix theta variables + model.rate_constant.fixed = True + + def response_rule(m, h): + expr = m.asymptote * (1 - pyo.exp(-m.rate_constant * h)) + return expr + + model.response_function = pyo.Expression(data.hour, rule=response_rule) + + return model + + def rooney_biegler_indexed_vars(data): + model = pyo.ConcreteModel() + + model.var_names = pyo.Set(initialize=["asymptote", "rate_constant"]) + model.theta = pyo.Var( + model.var_names, initialize={"asymptote": 15, "rate_constant": 0.5} + ) + model.theta["asymptote"].fixed = ( + True # parmest will unfix theta variables, even when they are indexed + ) + model.theta["rate_constant"].fixed = True + + def response_rule(m, h): + expr = m.theta["asymptote"] * ( + 1 - pyo.exp(-m.theta["rate_constant"] * h) + ) + return expr + + model.response_function = pyo.Expression(data.hour, rule=response_rule) + + return model + + def SSE(model, data): + expr = sum( + (data.y[i] - model.response_function[data.hour[i]]) ** 2 + for i in data.index + ) + return expr + + self.objective_function = SSE + + 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_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_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_str_index": { + "model": rooney_biegler_indexed_vars, + "theta_names": ["theta[asymptote]", "theta[rate_constant]"], + "theta_vals": theta_vals_index, + }, + } + + @unittest.skipIf(not pynumero_ASL_available, "pynumero ASL is not available") + @unittest.skipIf( + not parmest.inverse_reduced_hessian_available, + "Cannot test covariance matrix: required ASL dependency is missing", + ) + def test_parmest_basics(self): + for model_type, parmest_input in self.input.items(): + pest = parmest.Estimator( + parmest_input["model"], + self.data, + parmest_input["theta_names"], + self.objective_function, + ) + + objval, thetavals, cov = pest.theta_est(calc_cov=True, cov_n=6) + + self.assertAlmostEqual(objval, 4.3317112, places=2) + self.assertAlmostEqual( + cov.iloc[0, 0], 6.30579403, places=2 + ) # 6.22864 from paper + self.assertAlmostEqual( + cov.iloc[0, 1], -0.4395341, places=2 + ) # -0.4322 from paper + self.assertAlmostEqual( + cov.iloc[1, 0], -0.4395341, places=2 + ) # -0.4322 from paper + self.assertAlmostEqual( + 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) + + 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"], + self.data, + parmest_input["theta_names"], + self.objective_function, + ) + + objval, thetavals, cov = pest.theta_est(calc_cov=True, cov_n=6) + + self.assertAlmostEqual(objval, 4.3317112, places=2) + self.assertAlmostEqual( + cov.iloc[0, 0], 6.30579403, places=2 + ) # 6.22864 from paper + self.assertAlmostEqual( + cov.iloc[0, 1], -0.4395341, places=2 + ) # -0.4322 from paper + self.assertAlmostEqual( + cov.iloc[1, 0], -0.4395341, places=2 + ) # -0.4322 from paper + self.assertAlmostEqual( + cov.iloc[1, 1], 0.04193591, places=2 + ) # 0.04124 from paper + + obj_at_theta = pest.objective_at_theta( + parmest_input["theta_vals"], initialize_parmest_model=True + ) + + 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"], + self.data, + parmest_input["theta_names"], + self.objective_function, + ) + + obj_at_theta = pest.objective_at_theta( + parmest_input["theta_vals"], initialize_parmest_model=True + ) + + objval, thetavals, cov = pest.theta_est(calc_cov=True, cov_n=6) + + self.assertAlmostEqual(objval, 4.3317112, places=2) + self.assertAlmostEqual( + cov.iloc[0, 0], 6.30579403, places=2 + ) # 6.22864 from paper + self.assertAlmostEqual( + cov.iloc[0, 1], -0.4395341, places=2 + ) # -0.4322 from paper + self.assertAlmostEqual( + cov.iloc[1, 0], -0.4395341, places=2 + ) # -0.4322 from paper + self.assertAlmostEqual( + cov.iloc[1, 1], 0.04193591, places=2 + ) # 0.04124 from paper + + 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"], + self.data, + parmest_input["theta_names"], + self.objective_function, + ) + + obj_at_theta = pest.objective_at_theta(initialize_parmest_model=True) + + objval, thetavals, cov = pest.theta_est(calc_cov=True, cov_n=6) + + self.assertAlmostEqual(objval, 4.3317112, places=2) + self.assertAlmostEqual( + cov.iloc[0, 0], 6.30579403, places=2 + ) # 6.22864 from paper + self.assertAlmostEqual( + cov.iloc[0, 1], -0.4395341, places=2 + ) # -0.4322 from paper + self.assertAlmostEqual( + cov.iloc[1, 0], -0.4395341, places=2 + ) # -0.4322 from paper + self.assertAlmostEqual( + cov.iloc[1, 1], 0.04193591, places=2 + ) # 0.04124 from paper + + +@unittest.skipIf( + not parmest.parmest_available, + "Cannot test parmest: required dependencies are missing", +) +@unittest.skipIf(not ipopt_available, "The 'ipopt' solver is not available") +class TestReactorDesignDeprecated(unittest.TestCase): + def setUp(self): + + def reactor_design_model(data): + # Create the concrete model + model = pyo.ConcreteModel() + + # Rate constants + model.k1 = pyo.Param( + initialize=5.0 / 6.0, within=pyo.PositiveReals, mutable=True + ) # min^-1 + model.k2 = pyo.Param( + initialize=5.0 / 3.0, within=pyo.PositiveReals, mutable=True + ) # min^-1 + model.k3 = pyo.Param( + initialize=1.0 / 6000.0, within=pyo.PositiveReals, mutable=True + ) # m^3/(gmol min) + + # Inlet concentration of A, gmol/m^3 + if isinstance(data, dict) or isinstance(data, pd.Series): + model.caf = pyo.Param( + initialize=float(data["caf"]), within=pyo.PositiveReals + ) + elif isinstance(data, pd.DataFrame): + model.caf = pyo.Param( + initialize=float(data.iloc[0]["caf"]), within=pyo.PositiveReals + ) + else: + raise ValueError("Unrecognized data type.") + + # Space velocity (flowrate/volume) + if isinstance(data, dict) or isinstance(data, pd.Series): + model.sv = pyo.Param( + initialize=float(data["sv"]), within=pyo.PositiveReals + ) + elif isinstance(data, pd.DataFrame): + model.sv = pyo.Param( + initialize=float(data.iloc[0]["sv"]), within=pyo.PositiveReals + ) + else: + raise ValueError("Unrecognized data type.") + + # Outlet concentration of each component + model.ca = pyo.Var(initialize=5000.0, within=pyo.PositiveReals) + model.cb = pyo.Var(initialize=2000.0, within=pyo.PositiveReals) + model.cc = pyo.Var(initialize=2000.0, within=pyo.PositiveReals) + model.cd = pyo.Var(initialize=1000.0, within=pyo.PositiveReals) + + # Objective + model.obj = pyo.Objective(expr=model.cb, sense=pyo.maximize) + + # Constraints + model.ca_bal = pyo.Constraint( + expr=( + 0 + == model.sv * model.caf + - model.sv * model.ca + - model.k1 * model.ca + - 2.0 * model.k3 * model.ca**2.0 + ) + ) + + model.cb_bal = pyo.Constraint( + expr=( + 0 + == -model.sv * model.cb + model.k1 * model.ca - model.k2 * model.cb + ) + ) + + model.cc_bal = pyo.Constraint( + expr=(0 == -model.sv * model.cc + model.k2 * model.cb) + ) + + model.cd_bal = pyo.Constraint( + expr=(0 == -model.sv * model.cd + model.k3 * model.ca**2.0) + ) + + return model + + # Data from the design + data = pd.DataFrame( + data=[ + [1.05, 10000, 3458.4, 1060.8, 1683.9, 1898.5], + [1.10, 10000, 3535.1, 1064.8, 1613.3, 1893.4], + [1.15, 10000, 3609.1, 1067.8, 1547.5, 1887.8], + [1.20, 10000, 3680.7, 1070.0, 1486.1, 1881.6], + [1.25, 10000, 3750.0, 1071.4, 1428.6, 1875.0], + [1.30, 10000, 3817.1, 1072.2, 1374.6, 1868.0], + [1.35, 10000, 3882.2, 1072.4, 1324.0, 1860.7], + [1.40, 10000, 3945.4, 1072.1, 1276.3, 1853.1], + [1.45, 10000, 4006.7, 1071.3, 1231.4, 1845.3], + [1.50, 10000, 4066.4, 1070.1, 1189.0, 1837.3], + [1.55, 10000, 4124.4, 1068.5, 1148.9, 1829.1], + [1.60, 10000, 4180.9, 1066.5, 1111.0, 1820.8], + [1.65, 10000, 4235.9, 1064.3, 1075.0, 1812.4], + [1.70, 10000, 4289.5, 1061.8, 1040.9, 1803.9], + [1.75, 10000, 4341.8, 1059.0, 1008.5, 1795.3], + [1.80, 10000, 4392.8, 1056.0, 977.7, 1786.7], + [1.85, 10000, 4442.6, 1052.8, 948.4, 1778.1], + [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"], + ) + + 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 + ) + return expr + + solver_options = {"max_iter": 6000} + + self.pest = parmest.Estimator( + reactor_design_model, data, theta_names, SSE, solver_options=solver_options + ) + + 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) + + def test_return_values(self): + objval, thetavals, data_rec = self.pest.theta_est( + return_values=["ca", "cb", "cc", "cd", "caf"] + ) + self.assertAlmostEqual(data_rec["cc"].loc[18], 893.84924, places=3) + + +@unittest.skipIf( + not parmest.parmest_available, + "Cannot test parmest: required dependencies are missing", +) +@unittest.skipIf(not ipopt_available, "The 'ipopt' solver is not available") +class TestReactorDesign_DAE_Deprecated(unittest.TestCase): + # Based on a reactor example in `Chemical Reactor Analysis and Design Fundamentals`, + # https://sites.engineering.ucsb.edu/~jbraw/chemreacfun/ + # https://sites.engineering.ucsb.edu/~jbraw/chemreacfun/fig-html/appendix/fig-A-10.html + + def setUp(self): + def ABC_model(data): + ca_meas = data["ca"] + cb_meas = data["cb"] + cc_meas = data["cc"] + + if isinstance(data, pd.DataFrame): + meas_t = data.index # time index + else: # dictionary + meas_t = list(ca_meas.keys()) # nested dictionary + + ca0 = 1.0 + cb0 = 0.0 + cc0 = 0.0 + + m = pyo.ConcreteModel() + + m.k1 = pyo.Var(initialize=0.5, bounds=(1e-4, 10)) + m.k2 = pyo.Var(initialize=3.0, bounds=(1e-4, 10)) + + m.time = dae.ContinuousSet(bounds=(0.0, 5.0), initialize=meas_t) + + # initialization and bounds + m.ca = pyo.Var(m.time, initialize=ca0, bounds=(-1e-3, ca0 + 1e-3)) + m.cb = pyo.Var(m.time, initialize=cb0, bounds=(-1e-3, ca0 + 1e-3)) + m.cc = pyo.Var(m.time, initialize=cc0, bounds=(-1e-3, ca0 + 1e-3)) + + m.dca = dae.DerivativeVar(m.ca, wrt=m.time) + m.dcb = dae.DerivativeVar(m.cb, wrt=m.time) + m.dcc = dae.DerivativeVar(m.cc, wrt=m.time) + + def _dcarate(m, t): + if t == 0: + return pyo.Constraint.Skip + else: + return m.dca[t] == -m.k1 * m.ca[t] + + m.dcarate = pyo.Constraint(m.time, rule=_dcarate) + + def _dcbrate(m, t): + if t == 0: + return pyo.Constraint.Skip + else: + return m.dcb[t] == m.k1 * m.ca[t] - m.k2 * m.cb[t] + + m.dcbrate = pyo.Constraint(m.time, rule=_dcbrate) + + def _dccrate(m, t): + if t == 0: + return pyo.Constraint.Skip + else: + return m.dcc[t] == m.k2 * m.cb[t] + + m.dccrate = pyo.Constraint(m.time, rule=_dccrate) + + def ComputeFirstStageCost_rule(m): + return 0 + + m.FirstStageCost = pyo.Expression(rule=ComputeFirstStageCost_rule) + + def ComputeSecondStageCost_rule(m): + return sum( + (m.ca[t] - ca_meas[t]) ** 2 + + (m.cb[t] - cb_meas[t]) ** 2 + + (m.cc[t] - cc_meas[t]) ** 2 + for t in meas_t + ) + + m.SecondStageCost = pyo.Expression(rule=ComputeSecondStageCost_rule) + + def total_cost_rule(model): + return model.FirstStageCost + model.SecondStageCost + + m.Total_Cost_Objective = pyo.Objective( + rule=total_cost_rule, sense=pyo.minimize + ) + + disc = pyo.TransformationFactory("dae.collocation") + disc.apply_to(m, nfe=20, ncp=2) + + return m + + # This example tests data formatted in 3 ways + # Each format holds 1 scenario + # 1. dataframe with time index + # 2. nested dictionary {ca: {t, val pairs}, ... } + data = [ + [0.000, 0.957, -0.031, -0.015], + [0.263, 0.557, 0.330, 0.044], + [0.526, 0.342, 0.512, 0.156], + [0.789, 0.224, 0.499, 0.310], + [1.053, 0.123, 0.428, 0.454], + [1.316, 0.079, 0.396, 0.556], + [1.579, 0.035, 0.303, 0.651], + [1.842, 0.029, 0.287, 0.658], + [2.105, 0.025, 0.221, 0.750], + [2.368, 0.017, 0.148, 0.854], + [2.632, -0.002, 0.182, 0.845], + [2.895, 0.009, 0.116, 0.893], + [3.158, -0.023, 0.079, 0.942], + [3.421, 0.006, 0.078, 0.899], + [3.684, 0.016, 0.059, 0.942], + [3.947, 0.014, 0.036, 0.991], + [4.211, -0.009, 0.014, 0.988], + [4.474, -0.030, 0.036, 0.941], + [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_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)}, + } + + 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) + + # Estimator object with multiple scenarios + self.pest_df_multiple = parmest.Estimator( + ABC_model, [data_df, data_df], theta_names + ) + self.pest_dict_multiple = parmest.Estimator( + ABC_model, [data_dict, data_dict], theta_names + ) + + # Create an instance of the model + self.m_df = ABC_model(data_df) + self.m_dict = ABC_model(data_dict) + + def test_dataformats(self): + obj1, theta1 = self.pest_df.theta_est() + 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) + + 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) + + 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"] + ) + obj2, theta2, return_vals2 = self.pest_dict_multiple.theta_est( + 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) + + def test_covariance(self): + from pyomo.contrib.interior_point.inverse_reduced_hessian import ( + inv_reduced_hessian_barrier, + ) + + # Number of datapoints. + # 3 data components (ca, cb, cc), 20 timesteps, 1 scenario = 60 + # In this example, this is the number of data points in data_df, but that's + # only because the data is indexed by time and contains no additional information. + n = 60 + + # Compute covariance using parmest + obj, theta, cov = self.pest_df.theta_est(calc_cov=True, cov_n=n) + + # Compute covariance using interior_point + vars_list = [self.m_df.k1, self.m_df.k2] + solve_result, inv_red_hes = inv_reduced_hessian_barrier( + self.m_df, independent_variables=vars_list, tee=True + ) + 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_diff = (cov - cov_interior_point).abs().sum().sum() + + self.assertTrue(cov.loc["k1", "k1"] > 0) + self.assertTrue(cov.loc["k2", "k2"] > 0) + self.assertAlmostEqual(cov_diff, 0, places=6) + + +@unittest.skipIf( + not parmest.parmest_available, + "Cannot test parmest: required dependencies are missing", +) +@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") +class TestSquareInitialization_RooneyBiegler_Deprecated(unittest.TestCase): + def setUp(self): + + def rooney_biegler_model_with_constraint(data): + model = pyo.ConcreteModel() + + model.asymptote = pyo.Var(initialize=15) + model.rate_constant = pyo.Var(initialize=0.5) + model.response_function = pyo.Var(data.hour, initialize=0.0) + + # changed from expression to constraint + def response_rule(m, h): + return m.response_function[h] == m.asymptote * ( + 1 - pyo.exp(-m.rate_constant * h) + ) + + model.response_function_constraint = pyo.Constraint( + data.hour, rule=response_rule + ) + + def SSE_rule(m): + return sum( + (data.y[i] - m.response_function[data.hour[i]]) ** 2 + for i in data.index + ) + + model.SSE = pyo.Objective(rule=SSE_rule, sense=pyo.minimize) + + return model + + # 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"], + ) + + theta_names = ["asymptote", "rate_constant"] + + def SSE(model, data): + expr = sum( + (data.y[i] - model.response_function[data.hour[i]]) ** 2 + for i in data.index + ) + return expr + + solver_options = {"tol": 1e-8} + + self.data = data + self.pest = parmest.Estimator( + rooney_biegler_model_with_constraint, + data, + theta_names, + SSE, + solver_options=solver_options, + tee=True, + ) + + def test_theta_est_with_square_initialization(self): + obj_init = self.pest.objective_at_theta(initialize_parmest_model=True) + objval, thetavals = self.pest.theta_est() + + self.assertAlmostEqual(objval, 4.3317112, places=2) + self.assertAlmostEqual( + thetavals["asymptote"], 19.1426, places=2 + ) # 19.1426 from the paper + self.assertAlmostEqual( + 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"] + ) + obj_init = self.pest.objective_at_theta( + theta_values=theta_vals_init, initialize_parmest_model=True + ) + objval, thetavals = self.pest.theta_est() + self.assertAlmostEqual(objval, 4.3317112, places=2) + self.assertAlmostEqual( + thetavals["asymptote"], 19.1426, places=2 + ) # 19.1426 from the paper + self.assertAlmostEqual( + thetavals["rate_constant"], 0.5311, places=2 + ) # 0.5311 from the paper + + def test_theta_est_with_square_initialization_diagnostic_mode_true(self): + self.pest.diagnostic_mode = True + obj_init = self.pest.objective_at_theta(initialize_parmest_model=True) + objval, thetavals = self.pest.theta_est() + + self.assertAlmostEqual(objval, 4.3317112, places=2) + self.assertAlmostEqual( + thetavals["asymptote"], 19.1426, places=2 + ) # 19.1426 from the paper + self.assertAlmostEqual( + thetavals["rate_constant"], 0.5311, places=2 + ) # 0.5311 from the paper + + self.pest.diagnostic_mode = False + + if __name__ == "__main__": unittest.main() diff --git a/pyomo/contrib/parmest/tests/test_scenariocreator.py b/pyomo/contrib/parmest/tests/test_scenariocreator.py index 1f8ccdb20fe..af755e34b67 100644 --- a/pyomo/contrib/parmest/tests/test_scenariocreator.py +++ b/pyomo/contrib/parmest/tests/test_scenariocreator.py @@ -138,5 +138,453 @@ def test_semibatch_bootstrap(self): self.assertAlmostEqual(tval, 20.64, places=1) +########################### +# tests for deprecated UI # +########################### + + +@unittest.skipIf( + not parmest.parmest_available, + "Cannot test parmest: required dependencies are missing", +) +@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") +class TestScenarioReactorDesignDeprecated(unittest.TestCase): + def setUp(self): + + def reactor_design_model(data): + # Create the concrete model + model = pyo.ConcreteModel() + + # Rate constants + model.k1 = pyo.Param( + initialize=5.0 / 6.0, within=pyo.PositiveReals, mutable=True + ) # min^-1 + model.k2 = pyo.Param( + initialize=5.0 / 3.0, within=pyo.PositiveReals, mutable=True + ) # min^-1 + model.k3 = pyo.Param( + initialize=1.0 / 6000.0, within=pyo.PositiveReals, mutable=True + ) # m^3/(gmol min) + + # Inlet concentration of A, gmol/m^3 + if isinstance(data, dict) or isinstance(data, pd.Series): + model.caf = pyo.Param( + initialize=float(data["caf"]), within=pyo.PositiveReals + ) + elif isinstance(data, pd.DataFrame): + model.caf = pyo.Param( + initialize=float(data.iloc[0]["caf"]), within=pyo.PositiveReals + ) + else: + raise ValueError("Unrecognized data type.") + + # Space velocity (flowrate/volume) + if isinstance(data, dict) or isinstance(data, pd.Series): + model.sv = pyo.Param( + initialize=float(data["sv"]), within=pyo.PositiveReals + ) + elif isinstance(data, pd.DataFrame): + model.sv = pyo.Param( + initialize=float(data.iloc[0]["sv"]), within=pyo.PositiveReals + ) + else: + raise ValueError("Unrecognized data type.") + + # Outlet concentration of each component + model.ca = pyo.Var(initialize=5000.0, within=pyo.PositiveReals) + model.cb = pyo.Var(initialize=2000.0, within=pyo.PositiveReals) + model.cc = pyo.Var(initialize=2000.0, within=pyo.PositiveReals) + model.cd = pyo.Var(initialize=1000.0, within=pyo.PositiveReals) + + # Objective + model.obj = pyo.Objective(expr=model.cb, sense=pyo.maximize) + + # Constraints + model.ca_bal = pyo.Constraint( + expr=( + 0 + == model.sv * model.caf + - model.sv * model.ca + - model.k1 * model.ca + - 2.0 * model.k3 * model.ca**2.0 + ) + ) + + model.cb_bal = pyo.Constraint( + expr=( + 0 + == -model.sv * model.cb + model.k1 * model.ca - model.k2 * model.cb + ) + ) + + model.cc_bal = pyo.Constraint( + expr=(0 == -model.sv * model.cc + model.k2 * model.cb) + ) + + model.cd_bal = pyo.Constraint( + expr=(0 == -model.sv * model.cd + model.k3 * model.ca**2.0) + ) + + return model + + # Data from the design + data = pd.DataFrame( + data=[ + [1.05, 10000, 3458.4, 1060.8, 1683.9, 1898.5], + [1.10, 10000, 3535.1, 1064.8, 1613.3, 1893.4], + [1.15, 10000, 3609.1, 1067.8, 1547.5, 1887.8], + [1.20, 10000, 3680.7, 1070.0, 1486.1, 1881.6], + [1.25, 10000, 3750.0, 1071.4, 1428.6, 1875.0], + [1.30, 10000, 3817.1, 1072.2, 1374.6, 1868.0], + [1.35, 10000, 3882.2, 1072.4, 1324.0, 1860.7], + [1.40, 10000, 3945.4, 1072.1, 1276.3, 1853.1], + [1.45, 10000, 4006.7, 1071.3, 1231.4, 1845.3], + [1.50, 10000, 4066.4, 1070.1, 1189.0, 1837.3], + [1.55, 10000, 4124.4, 1068.5, 1148.9, 1829.1], + [1.60, 10000, 4180.9, 1066.5, 1111.0, 1820.8], + [1.65, 10000, 4235.9, 1064.3, 1075.0, 1812.4], + [1.70, 10000, 4289.5, 1061.8, 1040.9, 1803.9], + [1.75, 10000, 4341.8, 1059.0, 1008.5, 1795.3], + [1.80, 10000, 4392.8, 1056.0, 977.7, 1786.7], + [1.85, 10000, 4442.6, 1052.8, 948.4, 1778.1], + [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"], + ) + + 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 + ) + return expr + + self.pest = parmest.Estimator(reactor_design_model, data, theta_names, SSE) + + def test_scen_from_exps(self): + scenmaker = sc.ScenarioCreator(self.pest, "ipopt") + experimentscens = sc.ScenarioSet("Experiments") + scenmaker.ScenariosFromExperiments(experimentscens) + experimentscens.write_csv("delme_exp_csv.csv") + df = pd.read_csv("delme_exp_csv.csv") + os.remove("delme_exp_csv.csv") + # March '20: all reactor_design experiments have the same theta values! + k1val = df.loc[5].at["k1"] + self.assertAlmostEqual(k1val, 5.0 / 6.0, places=2) + tval = experimentscens.ScenarioNumber(0).ThetaVals["k1"] + self.assertAlmostEqual(tval, 5.0 / 6.0, places=2) + + @unittest.skipIf(not uuid_available, "The uuid module is not available") + def test_no_csv_if_empty(self): + # low level test of scenario sets + # verify that nothing is written, but no errors with empty set + + emptyset = sc.ScenarioSet("empty") + tfile = uuid.uuid4().hex + ".csv" + emptyset.write_csv(tfile) + self.assertFalse( + os.path.exists(tfile), "ScenarioSet wrote csv in spite of empty set" + ) + + +@unittest.skipIf( + not parmest.parmest_available, + "Cannot test parmest: required dependencies are missing", +) +@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available") +class TestScenarioSemibatchDeprecated(unittest.TestCase): + def setUp(self): + + import json + from pyomo.environ import ( + ConcreteModel, + Set, + Param, + Var, + Constraint, + ConstraintList, + Expression, + Objective, + TransformationFactory, + SolverFactory, + exp, + minimize, + ) + from pyomo.dae import ContinuousSet, DerivativeVar + + 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"] + ccmeastemp = data["Cc_meas"] + trmeastemp = data["Tr_meas"] + + cameas = {} + cbmeas = {} + ccmeas = {} + trmeas = {} + for i in cameastemp.keys(): + cameas[float(i)] = cameastemp[i] + cbmeas[float(i)] = cbmeastemp[i] + ccmeas[float(i)] = ccmeastemp[i] + trmeas[float(i)] = trmeastemp[i] + + m = ConcreteModel() + + # + # Measurement Data + # + m.measT = Set(initialize=sorted(cameas.keys())) + m.Ca_meas = Param(m.measT, initialize=cameas) + m.Cb_meas = Param(m.measT, initialize=cbmeas) + m.Cc_meas = Param(m.measT, initialize=ccmeas) + m.Tr_meas = Param(m.measT, initialize=trmeas) + + # + # Parameters for semi-batch reactor model + # + m.R = Param(initialize=8.314) # kJ/kmol/K + m.Mwa = Param(initialize=50.0) # kg/kmol + m.rhor = Param(initialize=1000.0) # kg/m^3 + m.cpr = Param(initialize=3.9) # kJ/kg/K + m.Tf = Param(initialize=300) # K + m.deltaH1 = Param(initialize=-40000.0) # kJ/kmol + m.deltaH2 = Param(initialize=-50000.0) # kJ/kmol + m.alphaj = Param(initialize=0.8) # kJ/s/m^2/K + m.alphac = Param(initialize=0.7) # kJ/s/m^2/K + m.Aj = Param(initialize=5.0) # m^2 + m.Ac = Param(initialize=3.0) # m^2 + m.Vj = Param(initialize=0.9) # m^3 + 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.Tr0 = Param(initialize=300.0) # K + m.Vr0 = Param(initialize=1.0) # m^3 + + m.time = ContinuousSet( + bounds=(0, 21600), initialize=m.measT + ) # Time in seconds + + # + # Control Inputs + # + def _initTc(m, t): + if t < 10800: + return data["Tc1"] + else: + return data["Tc2"] + + m.Tc = Param( + m.time, initialize=_initTc, default=_initTc + ) # bounds= (288,432) Cooling coil temp, control input + + def _initFa(m, t): + if t < 10800: + return data["Fa1"] + else: + return data["Fa2"] + + m.Fa = Param( + m.time, initialize=_initFa, default=_initFa + ) # bounds=(0,0.05) Inlet flow rate, control input + + # + # Parameters being estimated + # + m.k1 = Var(initialize=14, bounds=(2, 100)) # 1/s Actual: 15.01 + m.k2 = Var(initialize=90, bounds=(2, 150)) # 1/s Actual: 85.01 + m.E1 = Var( + initialize=27000.0, bounds=(25000, 40000) + ) # kJ/kmol Actual: 30000 + m.E2 = Var( + initialize=45000.0, bounds=(35000, 50000) + ) # kJ/kmol Actual: 40000 + # m.E1.fix(30000) + # m.E2.fix(40000) + + # + # Time dependent variables + # + m.Ca = Var(m.time, initialize=m.Ca0, bounds=(0, 25)) + m.Cb = Var(m.time, initialize=m.Cb0, bounds=(0, 25)) + m.Cc = Var(m.time, initialize=m.Cc0, bounds=(0, 25)) + m.Vr = Var(m.time, initialize=m.Vr0) + m.Tr = Var(m.time, initialize=m.Tr0) + m.Tj = Var( + m.time, initialize=310.0, bounds=(288, None) + ) # Cooling jacket temp, follows coil temp until failure + + # + # Derivatives in the model + # + m.dCa = DerivativeVar(m.Ca) + m.dCb = DerivativeVar(m.Cb) + m.dCc = DerivativeVar(m.Cc) + m.dVr = DerivativeVar(m.Vr) + m.dTr = DerivativeVar(m.Tr) + + # + # Differential Equations in the model + # + + def _dCacon(m, t): + if t == 0: + return Constraint.Skip + return ( + m.dCa[t] + == m.Fa[t] / m.Vr[t] - m.k1 * exp(-m.E1 / (m.R * m.Tr[t])) * m.Ca[t] + ) + + m.dCacon = Constraint(m.time, rule=_dCacon) + + def _dCbcon(m, t): + if t == 0: + return Constraint.Skip + return ( + m.dCb[t] + == m.k1 * exp(-m.E1 / (m.R * m.Tr[t])) * m.Ca[t] + - m.k2 * exp(-m.E2 / (m.R * m.Tr[t])) * m.Cb[t] + ) + + m.dCbcon = Constraint(m.time, rule=_dCbcon) + + def _dCccon(m, t): + if t == 0: + return Constraint.Skip + return m.dCc[t] == m.k2 * exp(-m.E2 / (m.R * m.Tr[t])) * m.Cb[t] + + m.dCccon = Constraint(m.time, rule=_dCccon) + + def _dVrcon(m, t): + if t == 0: + return Constraint.Skip + return m.dVr[t] == m.Fa[t] * m.Mwa / m.rhor + + m.dVrcon = Constraint(m.time, rule=_dVrcon) + + def _dTrcon(m, t): + if t == 0: + return Constraint.Skip + return m.rhor * m.cpr * m.dTr[t] == m.Fa[t] * m.Mwa * m.cpr / m.Vr[ + t + ] * (m.Tf - m.Tr[t]) - m.k1 * exp(-m.E1 / (m.R * m.Tr[t])) * m.Ca[ + t + ] * m.deltaH1 - m.k2 * exp( + -m.E2 / (m.R * m.Tr[t]) + ) * m.Cb[ + t + ] * m.deltaH2 + m.alphaj * m.Aj / m.Vr0 * ( + m.Tj[t] - m.Tr[t] + ) + m.alphac * m.Ac / m.Vr0 * ( + m.Tc[t] - m.Tr[t] + ) + + m.dTrcon = Constraint(m.time, rule=_dTrcon) + + def _singlecooling(m, t): + return m.Tc[t] == m.Tj[t] + + m.singlecooling = Constraint(m.time, rule=_singlecooling) + + # Initial Conditions + def _initcon(m): + yield m.Ca[m.time.first()] == m.Ca0 + yield m.Cb[m.time.first()] == m.Cb0 + yield m.Cc[m.time.first()] == m.Cc0 + yield m.Vr[m.time.first()] == m.Vr0 + yield m.Tr[m.time.first()] == m.Tr0 + + m.initcon = ConstraintList(rule=_initcon) + + # + # Stage-specific cost computations + # + def ComputeFirstStageCost_rule(model): + return 0 + + m.FirstStageCost = Expression(rule=ComputeFirstStageCost_rule) + + def AllMeasurements(m): + return sum( + (m.Ca[t] - m.Ca_meas[t]) ** 2 + + (m.Cb[t] - m.Cb_meas[t]) ** 2 + + (m.Cc[t] - m.Cc_meas[t]) ** 2 + + 0.01 * (m.Tr[t] - m.Tr_meas[t]) ** 2 + for t in m.measT + ) + + def MissingMeasurements(m): + if data["experiment"] == 1: + return sum( + (m.Ca[t] - m.Ca_meas[t]) ** 2 + + (m.Cb[t] - m.Cb_meas[t]) ** 2 + + (m.Cc[t] - m.Cc_meas[t]) ** 2 + + (m.Tr[t] - m.Tr_meas[t]) ** 2 + for t in m.measT + ) + elif data["experiment"] == 2: + return sum((m.Tr[t] - m.Tr_meas[t]) ** 2 for t in m.measT) + else: + return sum( + (m.Cb[t] - m.Cb_meas[t]) ** 2 + (m.Tr[t] - m.Tr_meas[t]) ** 2 + for t in m.measT + ) + + m.SecondStageCost = Expression(rule=MissingMeasurements) + + def total_cost_rule(model): + return model.FirstStageCost + model.SecondStageCost + + m.Total_Cost_Objective = Objective(rule=total_cost_rule, sense=minimize) + + # Discretize model + disc = TransformationFactory("dae.collocation") + disc.apply_to(m, nfe=20, ncp=4) + return m + + # Vars to estimate in parmest + theta_names = ["k1", "k2", "E1", "E2"] + + self.fbase = os.path.join(testdir, "..", "examples", "semibatch") + # Data, list of dictionaries + data = [] + 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: + d = json.load(infile) + data.append(d) + + # Note, the model already includes a 'SecondStageCost' expression + # for the sum of squared error that will be used in parameter estimation + + self.pest = parmest.Estimator(generate_model, data, theta_names) + + def test_semibatch_bootstrap(self): + scenmaker = sc.ScenarioCreator(self.pest, "ipopt") + bootscens = sc.ScenarioSet("Bootstrap") + numtomake = 2 + scenmaker.ScenariosFromBootstrap(bootscens, numtomake, seed=1134) + tval = bootscens.ScenarioNumber(0).ThetaVals["k1"] + self.assertAlmostEqual(tval, 20.64, places=1) + + if __name__ == "__main__": unittest.main() From 41d8197bbc2627aa6742d0358e16ade0a93a4747 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 21 Feb 2024 16:23:41 -0700 Subject: [PATCH 1186/1797] 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 df19b6bf869f94b792aa30733edfdff68b3da7ad Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Mon, 17 Jul 2023 11:36:32 -0600 Subject: [PATCH 1187/1797] add initial work on nested inner repn pw to gdp transformation. identify variables mode does not work, gives infeasible models --- .../tests/test_nested_inner_repn_gdp.py | 36 ++++ .../piecewise/transform/nested_inner_repn.py | 171 ++++++++++++++++++ 2 files changed, 207 insertions(+) create mode 100644 pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py create mode 100644 pyomo/contrib/piecewise/transform/nested_inner_repn.py diff --git a/pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py b/pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py new file mode 100644 index 00000000000..48357c828df --- /dev/null +++ b/pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py @@ -0,0 +1,36 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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.piecewise.tests import models +import pyomo.contrib.piecewise.tests.common_tests as ct +from pyomo.core.base import TransformationFactory +from pyomo.core.expr.compare import ( + assertExpressionsEqual, + assertExpressionsStructurallyEqual, +) +from pyomo.gdp import Disjunct, Disjunction +from pyomo.environ import Constraint, SolverFactory, Var + +from pyomo.contrib.piecewise.transform.nested_inner_repn import NestedInnerRepresentationGDPTransformation + +class TestTransformPiecewiseModelToNestedInnerRepnGDP(unittest.TestCase): + + def test_solve_log_model(self): + m = models.make_log_x_model() + TransformationFactory( + 'contrib.piecewise.nested_inner_repn_gdp' + ).apply_to(m) + TransformationFactory( + 'gdp.bigm' + ).apply_to(m) + SolverFactory('gurobi').solve(m) + ct.check_log_x_model_soln(self, m) \ No newline at end of file diff --git a/pyomo/contrib/piecewise/transform/nested_inner_repn.py b/pyomo/contrib/piecewise/transform/nested_inner_repn.py new file mode 100644 index 00000000000..b25ca3981a8 --- /dev/null +++ b/pyomo/contrib/piecewise/transform/nested_inner_repn.py @@ -0,0 +1,171 @@ +from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr +from pyomo.contrib.piecewise.transform.piecewise_to_gdp_transformation import ( + PiecewiseLinearToGDP, +) +from pyomo.core import Constraint, Binary, NonNegativeIntegers, Suffix, Var +from pyomo.core.base import TransformationFactory +from pyomo.gdp import Disjunct, Disjunction +from pyomo.common.errors import DeveloperError +from pyomo.core.expr.visitor import SimpleExpressionVisitor +from pyomo.core.expr.current import identify_components + +@TransformationFactory.register( + 'contrib.piecewise.nested_inner_repn_gdp', + doc="TODO document", +) +class NestedInnerRepresentationGDPTransformation(PiecewiseLinearToGDP): + """ + Represent a piecewise linear function "logarithmically" by using a nested + GDP to determine which polytope a point is in, then representing it as a + convex combination of extreme points, with multipliers "local" to that + particular polytope, i.e., not shared with neighbors. This method of + logarithmically formulating the piecewise linear function imposes no + restrictions on the family of polytopes. We rely on the identification of + variables to make this logarithmic in the number of binaries. This method + is due to Vielma et al., 2010. + """ + CONFIG = PiecewiseLinearToGDP.CONFIG() + _transformation_name = 'pw_linear_nested_inner_repn' + + # Implement to use PiecewiseLinearToGDP. This function returns the Var + # that replaces the transformed piecewise linear expr + def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_block): + self.DEBUG = True + identify_vars = True + # Get a new Block() in transformation_block.transformed_functions, which + # is a Block(Any) + transBlock = transformation_block.transformed_functions[ + len(transformation_block.transformed_functions) + ] + + # these copy-pasted lines (from inner_representation_gdp) seem useful + # adding some of this stuff to self so I don't have to pass it around + self.pw_linear_func = pw_linear_func + # map number -> list of Disjuncts which contain Disjunctions at that level + self.disjunct_levels = {} + self.dimension = pw_expr.nargs() + substitute_var = transBlock.substitute_var = Var() + pw_linear_func.map_transformation_var(pw_expr, substitute_var) + self.substitute_var_lb = float('inf') + self.substitute_var_ub = -float('inf') + + choices = list(zip(pw_linear_func._simplices, pw_linear_func._linear_functions)) + + if self.DEBUG: + print(f"dimension is {self.dimension}") + + # Add the disjunction + transBlock.disj = self._get_disjunction(choices, transBlock, pw_expr, transBlock, 1) + + # Widen bounds as determined when setting up the disjunction + if self.substitute_var_lb < float('inf'): + transBlock.substitute_var.setlb(self.substitute_var_lb) + if self.substitute_var_ub > -float('inf'): + transBlock.substitute_var.setub(self.substitute_var_ub) + + if self.DEBUG: + print(f"lb is {self.substitute_var_lb}, ub is {self.substitute_var_ub}") + + if identify_vars: + if self.DEBUG: + print("Now identifying variables") + for i in self.disjunct_levels.keys(): + print(f"level {i}: {len(self.disjunct_levels[i])} disjuncts") + transBlock.var_identifications_l = Constraint(NonNegativeIntegers, NonNegativeIntegers) + transBlock.var_identifications_r = Constraint(NonNegativeIntegers, NonNegativeIntegers) + for k in self.disjunct_levels.keys(): + disj_0 = self.disjunct_levels[k][0] + for i, disj in enumerate(self.disjunct_levels[k][1:]): + transBlock.var_identifications_l[k, i] = disj.d_l.binary_indicator_var == disj_0.d_l.binary_indicator_var + transBlock.var_identifications_r[k, i] = disj.d_r.binary_indicator_var == disj_0.d_r.binary_indicator_var + return substitute_var + + # Recursively form the Disjunctions and Disjuncts. This shouldn't blow up + # the stack, since the whole point is that we'll only go logarithmically + # many calls deep. + def _get_disjunction(self, choices, parent_block, pw_expr, root_block, level): + size = len(choices) + if self.DEBUG: + print(f"calling _get_disjunction with size={size}") + # Our base cases will be 3 and 2, since it would be silly to construct + # a Disjunction containing only one Disjunct. We can ensure that size + # is never 1 unless it was only passsed a single choice from the start, + # which we can handle before calling. + if size > 3: + half = size // 2 # (integer divide) + # This tree will be slightly heavier on the right side + choices_l = choices[:half] + choices_r = choices[half:] + # Is this valid Pyomo? + @parent_block.Disjunct() + def d_l(b): + b.inner_disjunction_l = self._get_disjunction(choices_l, b, pw_expr, root_block, level + 1) + @parent_block.Disjunct() + def d_r(b): + b.inner_disjunction_r = self._get_disjunction(choices_r, b, pw_expr, root_block, level + 1) + if level not in self.disjunct_levels.keys(): + self.disjunct_levels[level] = [] + self.disjunct_levels[level].append(parent_block.d_l) + self.disjunct_levels[level].append(parent_block.d_r) + return Disjunction(expr=[parent_block.d_l, parent_block.d_r]) + elif size == 3: + # Let's stay heavier on the right side for consistency. So the left + # Disjunct will be the one to contain constraints, rather than a + # Disjunction + @parent_block.Disjunct() + def d_l(b): + simplex, linear_func = choices[0] + self._set_disjunct_block_constraints(b, simplex, linear_func, pw_expr, root_block) + @parent_block.Disjunct() + def d_r(b): + b.inner_disjunction_r = self._get_disjunction(choices[1:], b, pw_expr, root_block, level + 1) + if level not in self.disjunct_levels.keys(): + self.disjunct_levels[level] = [] + self.disjunct_levels[level].append(parent_block.d_r) + return Disjunction(expr=[parent_block.d_l, parent_block.d_r]) + elif size == 2: + # In this case both sides are regular Disjuncts + @parent_block.Disjunct() + def d_l(b): + simplex, linear_func = choices[0] + self._set_disjunct_block_constraints(b, simplex, linear_func, pw_expr, root_block) + @parent_block.Disjunct() + def d_r(b): + simplex, linear_func = choices[1] + self._set_disjunct_block_constraints(b, simplex, linear_func, pw_expr, root_block) + return Disjunction(expr=[parent_block.d_l, parent_block.d_r]) + else: + raise DeveloperError("Unreachable: 1 or 0 choices were passed to " + "_get_disjunction in nested_inner_repn.py.") + + def _set_disjunct_block_constraints(self, b, simplex, linear_func, pw_expr, root_block): + # Define the lambdas sparsely like in the version I'm copying, + # only the first few will participate in constraints + b.lambdas = Var(NonNegativeIntegers, dense=False, bounds=(0, 1)) + # Get the extreme points to add up + extreme_pts = [] + for idx in simplex: + extreme_pts.append(self.pw_linear_func._points[idx]) + # Constrain sum(lambda_i) = 1 + b.convex_combo = Constraint( + expr=sum(b.lambdas[i] for i in range(len(extreme_pts))) == 1 + ) + linear_func_expr = linear_func(*pw_expr.args) + # Make the substitute Var equal the PWLE + b.set_substitute = Constraint(expr=root_block.substitute_var == linear_func_expr) + # Widen the variable bounds to those of this linear func expression + (lb, ub) = compute_bounds_on_expr(linear_func_expr) + if lb is not None and lb < self.substitute_var_lb: + self.substitute_var_lb = lb + if ub is not None and ub > self.substitute_var_ub: + self.substitute_var_ub = ub + # Constrain x = \sum \lambda_i v_i + @b.Constraint(range(self.dimension)) + def linear_combo(d, i): + return pw_expr.args[i] == sum( + d.lambdas[j] * pt[i] for j, pt in enumerate(extreme_pts) + ) + # Mark the lambdas as local in order to prevent disagreggating multiple + # times in the hull transformation + b.LocalVars = Suffix(direction=Suffix.LOCAL) + b.LocalVars[b] = [v for v in b.lambdas.values()] From 3e600ce5d32262053d46f1328a9008c6719cfe5b Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Tue, 1 Aug 2023 12:48:46 -0600 Subject: [PATCH 1188/1797] wip: working on some other pw linear representations --- .../transform/disagreggated_logarithmic.py | 102 ++++++++++++++++++ .../piecewise/transform/nested_inner_repn.py | 7 +- 2 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py diff --git a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py new file mode 100644 index 00000000000..fceb02d4d8c --- /dev/null +++ b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py @@ -0,0 +1,102 @@ +from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr +from pyomo.contrib.piecewise.transform.piecewise_to_gdp_transformation import ( + PiecewiseLinearToGDP, +) +from pyomo.core import Constraint, Binary, NonNegativeIntegers, Suffix, Var +from pyomo.core.base import TransformationFactory +from pyomo.gdp import Disjunct, Disjunction +from pyomo.common.errors import DeveloperError +from pyomo.core.expr.visitor import SimpleExpressionVisitor +from pyomo.core.expr.current import identify_components +from math import ceil, log2 + +@TransformationFactory.register( + 'contrib.piecewise.disaggregated_logarithmic', + doc="TODO document", +) +class NestedInnerRepresentationGDPTransformation(PiecewiseLinearToGDP): + """ + Represent a piecewise linear function "logarithmically" by using a MIP with + log_2(|P|) binary decision variables. This method of logarithmically + formulating the piecewise linear function imposes no restrictions on the + family of polytopes. This method is due to Vielma et al., 2010. + """ + CONFIG = PiecewiseLinearToGDP.CONFIG() + _transformation_name = 'pw_linear_disaggregated_log' + + # Implement to use PiecewiseLinearToGDP. This function returns the Var + # that replaces the transformed piecewise linear expr + def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_block): + self.DEBUG = False + # Get a new Block() in transformation_block.transformed_functions, which + # is a Block(Any) + transBlock = transformation_block.transformed_functions[ + len(transformation_block.transformed_functions) + ] + + dimension = pw_expr.nargs() + substitute_var = transBlock.substitute_var = Var() + pw_linear_func.map_transformation_var(pw_expr, substitute_var) + self.substitute_var_lb = float('inf') + self.substitute_var_ub = -float('inf') + + simplices = pw_linear_func._simplices + num_simplices = len(simplices) + simplex_indices = range(num_simplices) + # Assumption: the simplices are really simplices and all have the same number of points + simplex_point_indices = range(len(simplices[0])) + + choices = list(zip(pw_linear_func._simplices, pw_linear_func._linear_functions)) + + log_dimension = ceil(log2(num_simplices)) + binaries = transBlock.binaries = Var(range(log_dimension), domain=Binary) + + # injective function \mathcal{P} -> ceil(log_2(|P|)) used to identify simplices + # (really just polytopes are required) with binary vectors + B = {} + for i, p in enumerate(simplices): + B[id(p)] = self._get_binary_vector(i, log_dimension) + + # The lambdas \lambda_{P,v} + lambdas = transBlock.lambdas = Var(simplex_indices, simplex_point_indices, bounds=(0, 1)) + transBlock.convex_combo = Constraint(sum(lambdas[P, v] for P in simplex_indices for v in simplex_point_indices) == 1) + + # The branching rules, establishing using the binaries that only one simplex's lambdas + # may be nonzero + @transBlock.Constraint(range(log_dimension)) + def simplex_choice_1(b, l): + return ( + sum(lambdas[P, v] for P in self._P_plus(B, l) for v in simplex_point_indices) <= binaries[l] + ) + @transBlock.Constraint(range(log_dimension)) + def simplex_choice_2(b, l): + return ( + sum(lambdas[P, v] for P in self._P_0(B, l) for v in simplex_point_indices) <= 1 - binaries[l] + ) + + #for i, (simplex, pwlf) in enumerate(choices): + # x_i = sum(lambda_P,v v_i) + @transBlock.Constraint(range(dimension)) + def x_constraint(b, i): + return sum([stuff] for ) + + + #linear_func_expr = linear_func(*pw_expr.args) + ## Make the substitute Var equal the PWLE + #b.set_substitute = Constraint(expr=root_block.substitute_var == linear_func_expr) + + # Not a gray code, just a regular binary representation + # TODO this is probably not optimal, test the gray codes too + def _get_binary_vector(self, num, length): + if ceil(log2(num)) > length: + raise DeveloperError("Invalid input in _get_binary_vector") + # Use python's string formatting instead of bothering with modular + # arithmetic. May be slow. + return (int(x) for x in format(num, f'0{length}b')) + + # Return {P \in \mathcal{P} | B(P)_l = 0} + def _P_0(B, l, simplices): + return [p for p in simplices if B[id(p)][l] == 0] + # Return {P \in \mathcal{P} | B(P)_l = 1} + def _P_plus(B, l, simplices): + return [p for p in simplices if B[id(p)][l] == 1] \ No newline at end of file diff --git a/pyomo/contrib/piecewise/transform/nested_inner_repn.py b/pyomo/contrib/piecewise/transform/nested_inner_repn.py index b25ca3981a8..fc5761de434 100644 --- a/pyomo/contrib/piecewise/transform/nested_inner_repn.py +++ b/pyomo/contrib/piecewise/transform/nested_inner_repn.py @@ -30,8 +30,8 @@ class NestedInnerRepresentationGDPTransformation(PiecewiseLinearToGDP): # Implement to use PiecewiseLinearToGDP. This function returns the Var # that replaces the transformed piecewise linear expr def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_block): - self.DEBUG = True - identify_vars = True + self.DEBUG = False + identify_vars = False # Get a new Block() in transformation_block.transformed_functions, which # is a Block(Any) transBlock = transformation_block.transformed_functions[ @@ -66,6 +66,9 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc if self.DEBUG: print(f"lb is {self.substitute_var_lb}, ub is {self.substitute_var_ub}") + # NOTE - This functionality does not work. Even when we can choose the indicator + # variables, it seems that infeasibilities will always be generated. We may need + # to just directly transform to mip :( if identify_vars: if self.DEBUG: print("Now identifying variables") From c86220450129db5ac1f12020aae3d5e620b90014 Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Wed, 23 Aug 2023 16:34:57 -0400 Subject: [PATCH 1189/1797] properly handle one-simplex case instead of ignoring --- .../piecewise/transform/nested_inner_repn.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/piecewise/transform/nested_inner_repn.py b/pyomo/contrib/piecewise/transform/nested_inner_repn.py index fc5761de434..1e86a1406b4 100644 --- a/pyomo/contrib/piecewise/transform/nested_inner_repn.py +++ b/pyomo/contrib/piecewise/transform/nested_inner_repn.py @@ -54,8 +54,17 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc if self.DEBUG: print(f"dimension is {self.dimension}") - # Add the disjunction - transBlock.disj = self._get_disjunction(choices, transBlock, pw_expr, transBlock, 1) + # If there was only one choice, don't bother making a disjunction, just + # use the linear function directly (but still use the substitute_var for + # consistency). + if len(choices) == 1: + (_, linear_func) = choices[0] # simplex isn't important in this case + linear_func_expr = linear_func(*pw_expr.args) + transBlock.set_substitute = Constraint(expr=substitute_var == linear_func_expr) + (self.substitute_var_lb, self.substitute_var_ub) = compute_bounds_on_expr(linear_func_expr) + else: + # Add the disjunction + transBlock.disj = self._get_disjunction(choices, transBlock, pw_expr, transBlock, 1) # Widen bounds as determined when setting up the disjunction if self.substitute_var_lb < float('inf'): From 8b1997fd1a3604581937c50eb643d915b5b74bbb Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Wed, 23 Aug 2023 17:03:43 -0400 Subject: [PATCH 1190/1797] nested inner repn: remove non-working variable identification code --- .../piecewise/transform/nested_inner_repn.py | 55 +++++-------------- 1 file changed, 14 insertions(+), 41 deletions(-) diff --git a/pyomo/contrib/piecewise/transform/nested_inner_repn.py b/pyomo/contrib/piecewise/transform/nested_inner_repn.py index 1e86a1406b4..aaa0e03c79b 100644 --- a/pyomo/contrib/piecewise/transform/nested_inner_repn.py +++ b/pyomo/contrib/piecewise/transform/nested_inner_repn.py @@ -2,27 +2,25 @@ from pyomo.contrib.piecewise.transform.piecewise_to_gdp_transformation import ( PiecewiseLinearToGDP, ) -from pyomo.core import Constraint, Binary, NonNegativeIntegers, Suffix, Var +from pyomo.core import Constraint, NonNegativeIntegers, Suffix, Var from pyomo.core.base import TransformationFactory -from pyomo.gdp import Disjunct, Disjunction +from pyomo.gdp import Disjunction from pyomo.common.errors import DeveloperError -from pyomo.core.expr.visitor import SimpleExpressionVisitor -from pyomo.core.expr.current import identify_components @TransformationFactory.register( 'contrib.piecewise.nested_inner_repn_gdp', - doc="TODO document", + doc="TODO document", # TODO ) class NestedInnerRepresentationGDPTransformation(PiecewiseLinearToGDP): """ - Represent a piecewise linear function "logarithmically" by using a nested - GDP to determine which polytope a point is in, then representing it as a - convex combination of extreme points, with multipliers "local" to that - particular polytope, i.e., not shared with neighbors. This method of - logarithmically formulating the piecewise linear function imposes no - restrictions on the family of polytopes. We rely on the identification of - variables to make this logarithmic in the number of binaries. This method - is due to Vielma et al., 2010. + Represent a piecewise linear function by using a nested GDP to determine + which polytope a point is in, then representing it as a convex combination + of extreme points, with multipliers "local" to that particular polytope, + i.e., not shared with neighbors. This method of formulating the piecewise + linear function imposes no restrictions on the family of polytopes. Note + that this is NOT a logarithmic formulation - it has linearly many binaries. + This method was, however, inspired by the disagreggated logarithmic + formulation of Vielma et al., 2010. """ CONFIG = PiecewiseLinearToGDP.CONFIG() _transformation_name = 'pw_linear_nested_inner_repn' @@ -31,7 +29,6 @@ class NestedInnerRepresentationGDPTransformation(PiecewiseLinearToGDP): # that replaces the transformed piecewise linear expr def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_block): self.DEBUG = False - identify_vars = False # Get a new Block() in transformation_block.transformed_functions, which # is a Block(Any) transBlock = transformation_block.transformed_functions[ @@ -41,8 +38,6 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc # these copy-pasted lines (from inner_representation_gdp) seem useful # adding some of this stuff to self so I don't have to pass it around self.pw_linear_func = pw_linear_func - # map number -> list of Disjuncts which contain Disjunctions at that level - self.disjunct_levels = {} self.dimension = pw_expr.nargs() substitute_var = transBlock.substitute_var = Var() pw_linear_func.map_transformation_var(pw_expr, substitute_var) @@ -64,7 +59,7 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc (self.substitute_var_lb, self.substitute_var_ub) = compute_bounds_on_expr(linear_func_expr) else: # Add the disjunction - transBlock.disj = self._get_disjunction(choices, transBlock, pw_expr, transBlock, 1) + transBlock.disj = self._get_disjunction(choices, transBlock, pw_expr, transBlock) # Widen bounds as determined when setting up the disjunction if self.substitute_var_lb < float('inf'): @@ -75,21 +70,6 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc if self.DEBUG: print(f"lb is {self.substitute_var_lb}, ub is {self.substitute_var_ub}") - # NOTE - This functionality does not work. Even when we can choose the indicator - # variables, it seems that infeasibilities will always be generated. We may need - # to just directly transform to mip :( - if identify_vars: - if self.DEBUG: - print("Now identifying variables") - for i in self.disjunct_levels.keys(): - print(f"level {i}: {len(self.disjunct_levels[i])} disjuncts") - transBlock.var_identifications_l = Constraint(NonNegativeIntegers, NonNegativeIntegers) - transBlock.var_identifications_r = Constraint(NonNegativeIntegers, NonNegativeIntegers) - for k in self.disjunct_levels.keys(): - disj_0 = self.disjunct_levels[k][0] - for i, disj in enumerate(self.disjunct_levels[k][1:]): - transBlock.var_identifications_l[k, i] = disj.d_l.binary_indicator_var == disj_0.d_l.binary_indicator_var - transBlock.var_identifications_r[k, i] = disj.d_r.binary_indicator_var == disj_0.d_r.binary_indicator_var return substitute_var # Recursively form the Disjunctions and Disjuncts. This shouldn't blow up @@ -111,14 +91,10 @@ def _get_disjunction(self, choices, parent_block, pw_expr, root_block, level): # Is this valid Pyomo? @parent_block.Disjunct() def d_l(b): - b.inner_disjunction_l = self._get_disjunction(choices_l, b, pw_expr, root_block, level + 1) + b.inner_disjunction_l = self._get_disjunction(choices_l, b, pw_expr, root_block) @parent_block.Disjunct() def d_r(b): - b.inner_disjunction_r = self._get_disjunction(choices_r, b, pw_expr, root_block, level + 1) - if level not in self.disjunct_levels.keys(): - self.disjunct_levels[level] = [] - self.disjunct_levels[level].append(parent_block.d_l) - self.disjunct_levels[level].append(parent_block.d_r) + b.inner_disjunction_r = self._get_disjunction(choices_r, b, pw_expr, root_block) return Disjunction(expr=[parent_block.d_l, parent_block.d_r]) elif size == 3: # Let's stay heavier on the right side for consistency. So the left @@ -131,9 +107,6 @@ def d_l(b): @parent_block.Disjunct() def d_r(b): b.inner_disjunction_r = self._get_disjunction(choices[1:], b, pw_expr, root_block, level + 1) - if level not in self.disjunct_levels.keys(): - self.disjunct_levels[level] = [] - self.disjunct_levels[level].append(parent_block.d_r) return Disjunction(expr=[parent_block.d_l, parent_block.d_r]) elif size == 2: # In this case both sides are regular Disjuncts From b1cc43403dfe50beeca3023809cfcd0e1b23d3fe Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Wed, 23 Aug 2023 17:13:15 -0400 Subject: [PATCH 1191/1797] fix errors --- pyomo/contrib/piecewise/transform/nested_inner_repn.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/piecewise/transform/nested_inner_repn.py b/pyomo/contrib/piecewise/transform/nested_inner_repn.py index aaa0e03c79b..6c551818c84 100644 --- a/pyomo/contrib/piecewise/transform/nested_inner_repn.py +++ b/pyomo/contrib/piecewise/transform/nested_inner_repn.py @@ -75,7 +75,7 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc # Recursively form the Disjunctions and Disjuncts. This shouldn't blow up # the stack, since the whole point is that we'll only go logarithmically # many calls deep. - def _get_disjunction(self, choices, parent_block, pw_expr, root_block, level): + def _get_disjunction(self, choices, parent_block, pw_expr, root_block): size = len(choices) if self.DEBUG: print(f"calling _get_disjunction with size={size}") @@ -106,7 +106,7 @@ def d_l(b): self._set_disjunct_block_constraints(b, simplex, linear_func, pw_expr, root_block) @parent_block.Disjunct() def d_r(b): - b.inner_disjunction_r = self._get_disjunction(choices[1:], b, pw_expr, root_block, level + 1) + b.inner_disjunction_r = self._get_disjunction(choices[1:], b, pw_expr, root_block) return Disjunction(expr=[parent_block.d_l, parent_block.d_r]) elif size == 2: # In this case both sides are regular Disjuncts From 601abcbaf7b4d7db5cdf66c43a1f5199a3226a5d Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Thu, 12 Oct 2023 00:38:54 -0400 Subject: [PATCH 1192/1797] disaggregated logarithmic reworking --- .../transform/disagreggated_logarithmic.py | 186 +++++++++++++----- 1 file changed, 142 insertions(+), 44 deletions(-) diff --git a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py index fceb02d4d8c..e0b6d75d0e4 100644 --- a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py +++ b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py @@ -2,7 +2,7 @@ from pyomo.contrib.piecewise.transform.piecewise_to_gdp_transformation import ( PiecewiseLinearToGDP, ) -from pyomo.core import Constraint, Binary, NonNegativeIntegers, Suffix, Var +from pyomo.core import Constraint, Binary, NonNegativeIntegers, Suffix, Var, RangeSet from pyomo.core.base import TransformationFactory from pyomo.gdp import Disjunct, Disjunction from pyomo.common.errors import DeveloperError @@ -10,93 +10,191 @@ from pyomo.core.expr.current import identify_components from math import ceil, log2 + @TransformationFactory.register( - 'contrib.piecewise.disaggregated_logarithmic', - doc="TODO document", -) -class NestedInnerRepresentationGDPTransformation(PiecewiseLinearToGDP): - """ + "contrib.piecewise.disaggregated_logarithmic", + doc=""" Represent a piecewise linear function "logarithmically" by using a MIP with log_2(|P|) binary decision variables. This method of logarithmically formulating the piecewise linear function imposes no restrictions on the family of polytopes. This method is due to Vielma et al., 2010. + """, +) +class DisaggregatedLogarithmicInnerGDPTransformation(PiecewiseLinearToGDP): + """ + Represent a piecewise linear function "logarithmically" by using a MIP with + log_2(|P|) binary decision variables. This method of logarithmically + formulating the piecewise linear function imposes no restrictions on the + family of polytopes. This method is due to Vielma et al., 2010. """ + CONFIG = PiecewiseLinearToGDP.CONFIG() - _transformation_name = 'pw_linear_disaggregated_log' - + _transformation_name = "pw_linear_disaggregated_log" + # Implement to use PiecewiseLinearToGDP. This function returns the Var # that replaces the transformed piecewise linear expr def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_block): self.DEBUG = False + # Get a new Block() in transformation_block.transformed_functions, which - # is a Block(Any) + # is a Block(Any). This is where we will put our new components. transBlock = transformation_block.transformed_functions[ len(transformation_block.transformed_functions) ] + # Dimensionality of the PWLF dimension = pw_expr.nargs() + print(f"DIMENSIOn={dimension}") + transBlock.dimension_indices = RangeSet(0, dimension - 1) + + # Substitute Var that will hold the value of the PWLE substitute_var = transBlock.substitute_var = Var() pw_linear_func.map_transformation_var(pw_expr, substitute_var) - self.substitute_var_lb = float('inf') - self.substitute_var_ub = -float('inf') + # Bounds for the substitute_var that we will tighten + self.substitute_var_lb = float("inf") + self.substitute_var_ub = -float("inf") + + # Simplices are tuples of indices of points. Give them their own indices, too simplices = pw_linear_func._simplices num_simplices = len(simplices) - simplex_indices = range(num_simplices) - # Assumption: the simplices are really simplices and all have the same number of points - simplex_point_indices = range(len(simplices[0])) + transBlock.simplex_indices = RangeSet(0, num_simplices - 1) + # Assumption: the simplices are really simplices and all have the same number of points, + # which is dimension + 1 + transBlock.simplex_point_indices = RangeSet(0, dimension) + + # Enumeration of simplices, map from simplex number to simplex object + self.idx_to_simplex = {k: v for k, v in zip(transBlock.simplex_indices, simplices)} + # Inverse of previous enumeration + self.simplex_to_idx = {v: k for k, v in self.idx_to_simplex.items()} + + # List of tuples of simplices with their linear function + simplices_and_lin_funcs = list(zip(simplices, pw_linear_func._linear_functions)) - choices = list(zip(pw_linear_func._simplices, pw_linear_func._linear_functions)) + print("a") + print(f"Num_simplices: {num_simplices}") log_dimension = ceil(log2(num_simplices)) - binaries = transBlock.binaries = Var(range(log_dimension), domain=Binary) + transBlock.log_simplex_indices = RangeSet(0, log_dimension - 1) + binaries = transBlock.binaries = Var(transBlock.log_simplex_indices, domain=Binary) - # injective function \mathcal{P} -> ceil(log_2(|P|)) used to identify simplices - # (really just polytopes are required) with binary vectors + # Injective function \mathcal{P} -> {0,1}^ceil(log_2(|P|)) used to identify simplices + # (really just polytopes are required) with binary vectors. Any injective function + # is valid. B = {} - for i, p in enumerate(simplices): - B[id(p)] = self._get_binary_vector(i, log_dimension) - - # The lambdas \lambda_{P,v} - lambdas = transBlock.lambdas = Var(simplex_indices, simplex_point_indices, bounds=(0, 1)) - transBlock.convex_combo = Constraint(sum(lambdas[P, v] for P in simplex_indices for v in simplex_point_indices) == 1) + for i in transBlock.simplex_indices: + # map index(P) -> corresponding vector in {0, 1}^n + B[i] = self._get_binary_vector(i, log_dimension) + print(f"after construction, B = {B}") + + print("b") + # The lambda variables \lambda_{P,v} are indexed by the simplex and the point in it + transBlock.lambdas = Var(transBlock.simplex_indices, transBlock.simplex_point_indices, bounds=(0, 1)) + print("b1") + + # Sum of all lambdas is one (6b) + transBlock.convex_combo = Constraint( + expr=sum( + transBlock.lambdas[P, v] + for P in transBlock.simplex_indices + for v in transBlock.simplex_point_indices + ) + == 1 + ) + + print("c") # The branching rules, establishing using the binaries that only one simplex's lambdas # may be nonzero - @transBlock.Constraint(range(log_dimension)) + @transBlock.Constraint(transBlock.log_simplex_indices) # (6c.1) def simplex_choice_1(b, l): + print("entering constraint generator") + print(f"thing={self._P_plus(B, l, simplices)}") + print("returning") return ( - sum(lambdas[P, v] for P in self._P_plus(B, l) for v in simplex_point_indices) <= binaries[l] + sum( + transBlock.lambdas[self.simplex_to_idx[P], v] + for P in self._P_plus(B, l, simplices) + for v in transBlock.simplex_point_indices + ) + <= binaries[l] ) - @transBlock.Constraint(range(log_dimension)) + + print("c1") + + @transBlock.Constraint(transBlock.log_simplex_indices) # (6c.2) def simplex_choice_2(b, l): return ( - sum(lambdas[P, v] for P in self._P_0(B, l) for v in simplex_point_indices) <= 1 - binaries[l] + sum( + transBlock.lambdas[self.simplex_to_idx[P], v] + for P in self._P_0(B, l, simplices) + for v in transBlock.simplex_point_indices + ) + <= 1 - binaries[l] ) - - #for i, (simplex, pwlf) in enumerate(choices): - # x_i = sum(lambda_P,v v_i) - @transBlock.Constraint(range(dimension)) + + print("d") + + # for i, (simplex, pwlf) in enumerate(choices): + # x_i = sum(lambda_P,v v_i, P in polytopes, v in V(P)) + @transBlock.Constraint(transBlock.dimension_indices) # (6a.1) def x_constraint(b, i): - return sum([stuff] for ) + print(f"simplices are {[P for P in simplices]}") + print(f"points are {pw_linear_func._points}") + print(f"simplex_point_indices is {list(transBlock.simplex_point_indices)}") + print(f"i={i}") + + return pw_expr.args[i] == sum( + transBlock.lambdas[self.simplex_to_idx[P], v] + * pw_linear_func._points[P[v]][i] + for P in simplices + for v in transBlock.simplex_point_indices + ) + + # Make the substitute Var equal the PWLE (6a.2) + for P, linear_func in simplices_and_lin_funcs: + print(f"P, linear_func = {P}, {linear_func}") + for v in transBlock.simplex_point_indices: + print(f" v={v}") + print(f" pt={pw_linear_func._points[P[v]]}") + print( + f" lin_func_val = {linear_func(*pw_linear_func._points[P[v]])}" + ) + transBlock.set_substitute = Constraint( + expr=substitute_var + == sum( + sum( + transBlock.lambdas[self.simplex_to_idx[P], v] + * linear_func(*pw_linear_func._points[P[v]]) + for v in transBlock.simplex_point_indices + ) + for (P, linear_func) in simplices_and_lin_funcs + ) + ) + + print("f") + return substitute_var - #linear_func_expr = linear_func(*pw_expr.args) - ## Make the substitute Var equal the PWLE - #b.set_substitute = Constraint(expr=root_block.substitute_var == linear_func_expr) - # Not a gray code, just a regular binary representation # TODO this is probably not optimal, test the gray codes too def _get_binary_vector(self, num, length): - if ceil(log2(num)) > length: + if num != 0 and ceil(log2(num)) > length: raise DeveloperError("Invalid input in _get_binary_vector") - # Use python's string formatting instead of bothering with modular + # Hack: use python's string formatting instead of bothering with modular # arithmetic. May be slow. - return (int(x) for x in format(num, f'0{length}b')) + return tuple(int(x) for x in format(num, f"0{length}b")) # Return {P \in \mathcal{P} | B(P)_l = 0} - def _P_0(B, l, simplices): - return [p for p in simplices if B[id(p)][l] == 0] + def _P_0(self, B, l, simplices): + return [p for p in simplices if B[self.simplex_to_idx[p]][l] == 0] + # Return {P \in \mathcal{P} | B(P)_l = 1} - def _P_plus(B, l, simplices): - return [p for p in simplices if B[id(p)][l] == 1] \ No newline at end of file + def _P_plus(self, B, l, simplices): + print(f"p plus: B={B}, l={l}, simplices={simplices}") + for p in simplices: + print(f"for p={p}, simplex_to_idx[p]={self.simplex_to_idx[p]}") + print( + f"returning {[p for p in simplices if B[self.simplex_to_idx[p]][l] == 1]}" + ) + return [p for p in simplices if B[self.simplex_to_idx[p]][l] == 1] From ee616bf226d4d23b7b4ad7dfbacfcc94bfb4a715 Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Thu, 12 Oct 2023 01:34:14 -0400 Subject: [PATCH 1193/1797] remove printf debugging --- .../transform/disagreggated_logarithmic.py | 67 +++++++------------ 1 file changed, 26 insertions(+), 41 deletions(-) diff --git a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py index e0b6d75d0e4..00fb1546412 100644 --- a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py +++ b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py @@ -34,7 +34,6 @@ class DisaggregatedLogarithmicInnerGDPTransformation(PiecewiseLinearToGDP): # Implement to use PiecewiseLinearToGDP. This function returns the Var # that replaces the transformed piecewise linear expr def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_block): - self.DEBUG = False # Get a new Block() in transformation_block.transformed_functions, which # is a Block(Any). This is where we will put our new components. @@ -44,14 +43,13 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc # Dimensionality of the PWLF dimension = pw_expr.nargs() - print(f"DIMENSIOn={dimension}") transBlock.dimension_indices = RangeSet(0, dimension - 1) # Substitute Var that will hold the value of the PWLE substitute_var = transBlock.substitute_var = Var() pw_linear_func.map_transformation_var(pw_expr, substitute_var) - # Bounds for the substitute_var that we will tighten + # Bounds for the substitute_var that we will widen self.substitute_var_lb = float("inf") self.substitute_var_ub = -float("inf") @@ -71,26 +69,35 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc # List of tuples of simplices with their linear function simplices_and_lin_funcs = list(zip(simplices, pw_linear_func._linear_functions)) - print("a") - print(f"Num_simplices: {num_simplices}") + # We don't seem to get a convenient opportunity later, so let's just widen + # the bounds here. All we need to do is go through the corners of each simplex. + for P, linear_func in simplices_and_lin_funcs: + for v in transBlock.simplex_point_indices: + val = linear_func(*pw_linear_func._points[P[v]]) + if val < self.substitute_var_lb: + self.substitute_var_lb = val + if val > self.substitute_var_ub: + self.substitute_var_ub = val + # Now set those bounds + if self.substitute_var_lb < float('inf'): + transBlock.substitute_var.setlb(self.substitute_var_lb) + if self.substitute_var_ub > -float('inf'): + transBlock.substitute_var.setub(self.substitute_var_ub) log_dimension = ceil(log2(num_simplices)) transBlock.log_simplex_indices = RangeSet(0, log_dimension - 1) binaries = transBlock.binaries = Var(transBlock.log_simplex_indices, domain=Binary) - # Injective function \mathcal{P} -> {0,1}^ceil(log_2(|P|)) used to identify simplices + # Injective function B: \mathcal{P} -> {0,1}^ceil(log_2(|P|)) used to identify simplices # (really just polytopes are required) with binary vectors. Any injective function - # is valid. + # is enough here. B = {} for i in transBlock.simplex_indices: # map index(P) -> corresponding vector in {0, 1}^n B[i] = self._get_binary_vector(i, log_dimension) - print(f"after construction, B = {B}") - print("b") # The lambda variables \lambda_{P,v} are indexed by the simplex and the point in it transBlock.lambdas = Var(transBlock.simplex_indices, transBlock.simplex_point_indices, bounds=(0, 1)) - print("b1") # Sum of all lambdas is one (6b) transBlock.convex_combo = Constraint( @@ -102,15 +109,10 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc == 1 ) - print("c") - # The branching rules, establishing using the binaries that only one simplex's lambdas # may be nonzero @transBlock.Constraint(transBlock.log_simplex_indices) # (6c.1) def simplex_choice_1(b, l): - print("entering constraint generator") - print(f"thing={self._P_plus(B, l, simplices)}") - print("returning") return ( sum( transBlock.lambdas[self.simplex_to_idx[P], v] @@ -120,8 +122,6 @@ def simplex_choice_1(b, l): <= binaries[l] ) - print("c1") - @transBlock.Constraint(transBlock.log_simplex_indices) # (6c.2) def simplex_choice_2(b, l): return ( @@ -133,18 +133,10 @@ def simplex_choice_2(b, l): <= 1 - binaries[l] ) - print("d") - # for i, (simplex, pwlf) in enumerate(choices): # x_i = sum(lambda_P,v v_i, P in polytopes, v in V(P)) @transBlock.Constraint(transBlock.dimension_indices) # (6a.1) def x_constraint(b, i): - - print(f"simplices are {[P for P in simplices]}") - print(f"points are {pw_linear_func._points}") - print(f"simplex_point_indices is {list(transBlock.simplex_point_indices)}") - print(f"i={i}") - return pw_expr.args[i] == sum( transBlock.lambdas[self.simplex_to_idx[P], v] * pw_linear_func._points[P[v]][i] @@ -153,14 +145,14 @@ def x_constraint(b, i): ) # Make the substitute Var equal the PWLE (6a.2) - for P, linear_func in simplices_and_lin_funcs: - print(f"P, linear_func = {P}, {linear_func}") - for v in transBlock.simplex_point_indices: - print(f" v={v}") - print(f" pt={pw_linear_func._points[P[v]]}") - print( - f" lin_func_val = {linear_func(*pw_linear_func._points[P[v]])}" - ) + #for P, linear_func in simplices_and_lin_funcs: + # print(f"P, linear_func = {P}, {linear_func}") + # for v in transBlock.simplex_point_indices: + # print(f" v={v}") + # print(f" pt={pw_linear_func._points[P[v]]}") + # print( + # f" lin_func_val = {linear_func(*pw_linear_func._points[P[v]])}" + # ) transBlock.set_substitute = Constraint( expr=substitute_var == sum( @@ -173,11 +165,10 @@ def x_constraint(b, i): ) ) - print("f") return substitute_var # Not a gray code, just a regular binary representation - # TODO this is probably not optimal, test the gray codes too + # TODO this may not be optimal, test the gray codes too def _get_binary_vector(self, num, length): if num != 0 and ceil(log2(num)) > length: raise DeveloperError("Invalid input in _get_binary_vector") @@ -191,10 +182,4 @@ def _P_0(self, B, l, simplices): # Return {P \in \mathcal{P} | B(P)_l = 1} def _P_plus(self, B, l, simplices): - print(f"p plus: B={B}, l={l}, simplices={simplices}") - for p in simplices: - print(f"for p={p}, simplex_to_idx[p]={self.simplex_to_idx[p]}") - print( - f"returning {[p for p in simplices if B[self.simplex_to_idx[p]][l] == 1]}" - ) return [p for p in simplices if B[self.simplex_to_idx[p]][l] == 1] From dbecedd3a1644fd1b940cf1cf9ab95e994e307e6 Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Thu, 12 Oct 2023 02:23:00 -0400 Subject: [PATCH 1194/1797] fix strange reverse indexing --- .../transform/disagreggated_logarithmic.py | 70 +++++++++---------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py index 00fb1546412..e86d5539367 100644 --- a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py +++ b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py @@ -15,17 +15,19 @@ "contrib.piecewise.disaggregated_logarithmic", doc=""" Represent a piecewise linear function "logarithmically" by using a MIP with - log_2(|P|) binary decision variables. This method of logarithmically - formulating the piecewise linear function imposes no restrictions on the - family of polytopes. This method is due to Vielma et al., 2010. + log_2(|P|) binary decision variables. This is a direct-to-MIP transformation; + GDP is not used. This method of logarithmically formulating the piecewise + linear function imposes no restrictions on the family of polytopes, but we + assume we have simplces in this code. This method is due to Vielma et al., 2010. """, ) class DisaggregatedLogarithmicInnerGDPTransformation(PiecewiseLinearToGDP): """ Represent a piecewise linear function "logarithmically" by using a MIP with - log_2(|P|) binary decision variables. This method of logarithmically - formulating the piecewise linear function imposes no restrictions on the - family of polytopes. This method is due to Vielma et al., 2010. + log_2(|P|) binary decision variables. This is a direct-to-MIP transformation; + GDP is not used. This method of logarithmically formulating the piecewise + linear function imposes no restrictions on the family of polytopes, but we + assume we have simplces in this code. This method is due to Vielma et al., 2010. """ CONFIG = PiecewiseLinearToGDP.CONFIG() @@ -35,8 +37,8 @@ class DisaggregatedLogarithmicInnerGDPTransformation(PiecewiseLinearToGDP): # that replaces the transformed piecewise linear expr def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_block): - # Get a new Block() in transformation_block.transformed_functions, which - # is a Block(Any). This is where we will put our new components. + # Get a new Block for our transformationin transformation_block.transformed_functions, + # which is a Block(Any). This is where we will put our new components. transBlock = transformation_block.transformed_functions[ len(transformation_block.transformed_functions) ] @@ -61,32 +63,28 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc # which is dimension + 1 transBlock.simplex_point_indices = RangeSet(0, dimension) - # Enumeration of simplices, map from simplex number to simplex object + # Enumeration of simplices: map from simplex number to simplex object self.idx_to_simplex = {k: v for k, v in zip(transBlock.simplex_indices, simplices)} - # Inverse of previous enumeration - self.simplex_to_idx = {v: k for k, v in self.idx_to_simplex.items()} - # List of tuples of simplices with their linear function - simplices_and_lin_funcs = list(zip(simplices, pw_linear_func._linear_functions)) + # List of tuples of simplex indices with their linear function + simplex_indices_and_lin_funcs = list(zip(transBlock.simplex_indices, pw_linear_func._linear_functions)) # We don't seem to get a convenient opportunity later, so let's just widen # the bounds here. All we need to do is go through the corners of each simplex. - for P, linear_func in simplices_and_lin_funcs: + for P, linear_func in simplex_indices_and_lin_funcs: for v in transBlock.simplex_point_indices: - val = linear_func(*pw_linear_func._points[P[v]]) + val = linear_func(*pw_linear_func._points[self.idx_to_simplex[P][v]]) if val < self.substitute_var_lb: self.substitute_var_lb = val if val > self.substitute_var_ub: self.substitute_var_ub = val # Now set those bounds - if self.substitute_var_lb < float('inf'): - transBlock.substitute_var.setlb(self.substitute_var_lb) - if self.substitute_var_ub > -float('inf'): - transBlock.substitute_var.setub(self.substitute_var_ub) + transBlock.substitute_var.setlb(self.substitute_var_lb) + transBlock.substitute_var.setub(self.substitute_var_ub) log_dimension = ceil(log2(num_simplices)) transBlock.log_simplex_indices = RangeSet(0, log_dimension - 1) - binaries = transBlock.binaries = Var(transBlock.log_simplex_indices, domain=Binary) + transBlock.binaries = Var(transBlock.log_simplex_indices, domain=Binary) # Injective function B: \mathcal{P} -> {0,1}^ceil(log_2(|P|)) used to identify simplices # (really just polytopes are required) with binary vectors. Any injective function @@ -115,22 +113,22 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc def simplex_choice_1(b, l): return ( sum( - transBlock.lambdas[self.simplex_to_idx[P], v] - for P in self._P_plus(B, l, simplices) + transBlock.lambdas[P, v] + for P in self._P_plus(B, l, transBlock.simplex_indices) for v in transBlock.simplex_point_indices ) - <= binaries[l] + <= transBlock.binaries[l] ) @transBlock.Constraint(transBlock.log_simplex_indices) # (6c.2) def simplex_choice_2(b, l): return ( sum( - transBlock.lambdas[self.simplex_to_idx[P], v] - for P in self._P_0(B, l, simplices) + transBlock.lambdas[P, v] + for P in self._P_0(B, l, transBlock.simplex_indices) for v in transBlock.simplex_point_indices ) - <= 1 - binaries[l] + <= 1 - transBlock.binaries[l] ) # for i, (simplex, pwlf) in enumerate(choices): @@ -138,9 +136,9 @@ def simplex_choice_2(b, l): @transBlock.Constraint(transBlock.dimension_indices) # (6a.1) def x_constraint(b, i): return pw_expr.args[i] == sum( - transBlock.lambdas[self.simplex_to_idx[P], v] - * pw_linear_func._points[P[v]][i] - for P in simplices + transBlock.lambdas[P, v] + * pw_linear_func._points[self.idx_to_simplex[P][v]][i] + for P in transBlock.simplex_indices for v in transBlock.simplex_point_indices ) @@ -157,11 +155,11 @@ def x_constraint(b, i): expr=substitute_var == sum( sum( - transBlock.lambdas[self.simplex_to_idx[P], v] - * linear_func(*pw_linear_func._points[P[v]]) + transBlock.lambdas[P, v] + * linear_func(*pw_linear_func._points[self.idx_to_simplex[P][v]]) for v in transBlock.simplex_point_indices ) - for (P, linear_func) in simplices_and_lin_funcs + for (P, linear_func) in simplex_indices_and_lin_funcs ) ) @@ -177,9 +175,9 @@ def _get_binary_vector(self, num, length): return tuple(int(x) for x in format(num, f"0{length}b")) # Return {P \in \mathcal{P} | B(P)_l = 0} - def _P_0(self, B, l, simplices): - return [p for p in simplices if B[self.simplex_to_idx[p]][l] == 0] + def _P_0(self, B, l, simplex_indices): + return [p for p in simplex_indices if B[p][l] == 0] # Return {P \in \mathcal{P} | B(P)_l = 1} - def _P_plus(self, B, l, simplices): - return [p for p in simplices if B[self.simplex_to_idx[p]][l] == 1] + def _P_plus(self, B, l, simplex_indices): + return [p for p in simplex_indices if B[p][l] == 1] From 8ff5d5ccd59679ce0deb8a254cfdb85e76e2dab3 Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Thu, 9 Nov 2023 11:34:02 -0500 Subject: [PATCH 1195/1797] minor changes and add basic test for disaggregated log --- .../test_disaggregated_logarithmic_gdp.py | 36 +++++++++++++++++++ .../transform/disagreggated_logarithmic.py | 28 +++++---------- .../piecewise/transform/nested_inner_repn.py | 22 ++++++------ 3 files changed, 55 insertions(+), 31 deletions(-) create mode 100644 pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic_gdp.py diff --git a/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic_gdp.py b/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic_gdp.py new file mode 100644 index 00000000000..b3dc871882b --- /dev/null +++ b/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic_gdp.py @@ -0,0 +1,36 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering 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.piecewise.tests import models +import pyomo.contrib.piecewise.tests.common_tests as ct +from pyomo.core.base import TransformationFactory +from pyomo.core.expr.compare import ( + assertExpressionsEqual, + assertExpressionsStructurallyEqual, +) +from pyomo.gdp import Disjunct, Disjunction +from pyomo.environ import Constraint, SolverFactory, Var + +from pyomo.contrib.piecewise.transform.disagreggated_logarithmic import DisaggregatedLogarithmicInnerGDPTransformation + +class TestTransformPiecewiseModelToNestedInnerRepnGDP(unittest.TestCase): + + def test_solve_log_model(self): + m = models.make_log_x_model() + TransformationFactory( + 'contrib.piecewise.disaggregated_logarithmic' + ).apply_to(m) + TransformationFactory( + 'gdp.bigm' + ).apply_to(m) + SolverFactory('gurobi').solve(m) + ct.check_log_x_model_soln(self, m) \ No newline at end of file diff --git a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py index e86d5539367..a793ad77e38 100644 --- a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py +++ b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py @@ -1,13 +1,9 @@ -from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr from pyomo.contrib.piecewise.transform.piecewise_to_gdp_transformation import ( PiecewiseLinearToGDP, ) -from pyomo.core import Constraint, Binary, NonNegativeIntegers, Suffix, Var, RangeSet +from pyomo.core import Constraint, Binary, Var, RangeSet from pyomo.core.base import TransformationFactory -from pyomo.gdp import Disjunct, Disjunction from pyomo.common.errors import DeveloperError -from pyomo.core.expr.visitor import SimpleExpressionVisitor -from pyomo.core.expr.current import identify_components from math import ceil, log2 @@ -37,7 +33,7 @@ class DisaggregatedLogarithmicInnerGDPTransformation(PiecewiseLinearToGDP): # that replaces the transformed piecewise linear expr def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_block): - # Get a new Block for our transformationin transformation_block.transformed_functions, + # Get a new Block for our transformation in transformation_block.transformed_functions, # which is a Block(Any). This is where we will put our new components. transBlock = transformation_block.transformed_functions[ len(transformation_block.transformed_functions) @@ -78,7 +74,6 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc self.substitute_var_lb = val if val > self.substitute_var_ub: self.substitute_var_ub = val - # Now set those bounds transBlock.substitute_var.setlb(self.substitute_var_lb) transBlock.substitute_var.setub(self.substitute_var_ub) @@ -97,6 +92,9 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc # The lambda variables \lambda_{P,v} are indexed by the simplex and the point in it transBlock.lambdas = Var(transBlock.simplex_indices, transBlock.simplex_point_indices, bounds=(0, 1)) + # Numbered citations are from Vielma et al 2010, Mixed-Integer Models + # for Nonseparable Piecewise-Linear Optimization + # Sum of all lambdas is one (6b) transBlock.convex_combo = Constraint( expr=sum( @@ -143,14 +141,6 @@ def x_constraint(b, i): ) # Make the substitute Var equal the PWLE (6a.2) - #for P, linear_func in simplices_and_lin_funcs: - # print(f"P, linear_func = {P}, {linear_func}") - # for v in transBlock.simplex_point_indices: - # print(f" v={v}") - # print(f" pt={pw_linear_func._points[P[v]]}") - # print( - # f" lin_func_val = {linear_func(*pw_linear_func._points[P[v]])}" - # ) transBlock.set_substitute = Constraint( expr=substitute_var == sum( @@ -165,13 +155,13 @@ def x_constraint(b, i): return substitute_var - # Not a gray code, just a regular binary representation - # TODO this may not be optimal, test the gray codes too + # Not a Gray code, just a regular binary representation + # TODO test the Gray codes too def _get_binary_vector(self, num, length): if num != 0 and ceil(log2(num)) > length: raise DeveloperError("Invalid input in _get_binary_vector") - # Hack: use python's string formatting instead of bothering with modular - # arithmetic. May be slow. + # Use python's string formatting instead of bothering with modular + # arithmetic. Hopefully not slow. return tuple(int(x) for x in format(num, f"0{length}b")) # Return {P \in \mathcal{P} | B(P)_l = 0} diff --git a/pyomo/contrib/piecewise/transform/nested_inner_repn.py b/pyomo/contrib/piecewise/transform/nested_inner_repn.py index 6c551818c84..a5c9b5015d3 100644 --- a/pyomo/contrib/piecewise/transform/nested_inner_repn.py +++ b/pyomo/contrib/piecewise/transform/nested_inner_repn.py @@ -28,7 +28,7 @@ class NestedInnerRepresentationGDPTransformation(PiecewiseLinearToGDP): # Implement to use PiecewiseLinearToGDP. This function returns the Var # that replaces the transformed piecewise linear expr def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_block): - self.DEBUG = False + # Get a new Block() in transformation_block.transformed_functions, which # is a Block(Any) transBlock = transformation_block.transformed_functions[ @@ -46,9 +46,6 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc choices = list(zip(pw_linear_func._simplices, pw_linear_func._linear_functions)) - if self.DEBUG: - print(f"dimension is {self.dimension}") - # If there was only one choice, don't bother making a disjunction, just # use the linear function directly (but still use the substitute_var for # consistency). @@ -61,15 +58,12 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc # Add the disjunction transBlock.disj = self._get_disjunction(choices, transBlock, pw_expr, transBlock) - # Widen bounds as determined when setting up the disjunction + # Set bounds as determined when setting up the disjunction if self.substitute_var_lb < float('inf'): transBlock.substitute_var.setlb(self.substitute_var_lb) if self.substitute_var_ub > -float('inf'): transBlock.substitute_var.setub(self.substitute_var_ub) - if self.DEBUG: - print(f"lb is {self.substitute_var_lb}, ub is {self.substitute_var_ub}") - return substitute_var # Recursively form the Disjunctions and Disjuncts. This shouldn't blow up @@ -77,8 +71,7 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc # many calls deep. def _get_disjunction(self, choices, parent_block, pw_expr, root_block): size = len(choices) - if self.DEBUG: - print(f"calling _get_disjunction with size={size}") + # Our base cases will be 3 and 2, since it would be silly to construct # a Disjunction containing only one Disjunct. We can ensure that size # is never 1 unless it was only passsed a single choice from the start, @@ -88,7 +81,6 @@ def _get_disjunction(self, choices, parent_block, pw_expr, root_block): # This tree will be slightly heavier on the right side choices_l = choices[:half] choices_r = choices[half:] - # Is this valid Pyomo? @parent_block.Disjunct() def d_l(b): b.inner_disjunction_l = self._get_disjunction(choices_l, b, pw_expr, root_block) @@ -124,32 +116,38 @@ def d_r(b): "_get_disjunction in nested_inner_repn.py.") def _set_disjunct_block_constraints(self, b, simplex, linear_func, pw_expr, root_block): - # Define the lambdas sparsely like in the version I'm copying, + # Define the lambdas sparsely like in the normal inner repn, # only the first few will participate in constraints b.lambdas = Var(NonNegativeIntegers, dense=False, bounds=(0, 1)) + # Get the extreme points to add up extreme_pts = [] for idx in simplex: extreme_pts.append(self.pw_linear_func._points[idx]) + # Constrain sum(lambda_i) = 1 b.convex_combo = Constraint( expr=sum(b.lambdas[i] for i in range(len(extreme_pts))) == 1 ) linear_func_expr = linear_func(*pw_expr.args) + # Make the substitute Var equal the PWLE b.set_substitute = Constraint(expr=root_block.substitute_var == linear_func_expr) + # Widen the variable bounds to those of this linear func expression (lb, ub) = compute_bounds_on_expr(linear_func_expr) if lb is not None and lb < self.substitute_var_lb: self.substitute_var_lb = lb if ub is not None and ub > self.substitute_var_ub: self.substitute_var_ub = ub + # Constrain x = \sum \lambda_i v_i @b.Constraint(range(self.dimension)) def linear_combo(d, i): return pw_expr.args[i] == sum( d.lambdas[j] * pt[i] for j, pt in enumerate(extreme_pts) ) + # Mark the lambdas as local in order to prevent disagreggating multiple # times in the hull transformation b.LocalVars = Suffix(direction=Suffix.LOCAL) From 6f4de26da622a35f446516d57e29e6acf5ded9e8 Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Thu, 9 Nov 2023 11:38:21 -0500 Subject: [PATCH 1196/1797] apply black --- .../test_disaggregated_logarithmic_gdp.py | 18 ++- .../tests/test_nested_inner_repn_gdp.py | 18 ++- .../transform/disagreggated_logarithmic.py | 25 +++-- .../piecewise/transform/nested_inner_repn.py | 104 ++++++++++++------ 4 files changed, 99 insertions(+), 66 deletions(-) diff --git a/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic_gdp.py b/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic_gdp.py index b3dc871882b..d3b58f401f2 100644 --- a/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic_gdp.py +++ b/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic_gdp.py @@ -20,17 +20,15 @@ from pyomo.gdp import Disjunct, Disjunction from pyomo.environ import Constraint, SolverFactory, Var -from pyomo.contrib.piecewise.transform.disagreggated_logarithmic import DisaggregatedLogarithmicInnerGDPTransformation +from pyomo.contrib.piecewise.transform.disagreggated_logarithmic import ( + DisaggregatedLogarithmicInnerGDPTransformation, +) -class TestTransformPiecewiseModelToNestedInnerRepnGDP(unittest.TestCase): +class TestTransformPiecewiseModelToNestedInnerRepnGDP(unittest.TestCase): def test_solve_log_model(self): m = models.make_log_x_model() - TransformationFactory( - 'contrib.piecewise.disaggregated_logarithmic' - ).apply_to(m) - TransformationFactory( - 'gdp.bigm' - ).apply_to(m) - SolverFactory('gurobi').solve(m) - ct.check_log_x_model_soln(self, m) \ No newline at end of file + TransformationFactory("contrib.piecewise.disaggregated_logarithmic").apply_to(m) + TransformationFactory("gdp.bigm").apply_to(m) + SolverFactory("gurobi").solve(m) + ct.check_log_x_model_soln(self, m) diff --git a/pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py b/pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py index 48357c828df..f41233435d4 100644 --- a/pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py +++ b/pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py @@ -20,17 +20,15 @@ from pyomo.gdp import Disjunct, Disjunction from pyomo.environ import Constraint, SolverFactory, Var -from pyomo.contrib.piecewise.transform.nested_inner_repn import NestedInnerRepresentationGDPTransformation +from pyomo.contrib.piecewise.transform.nested_inner_repn import ( + NestedInnerRepresentationGDPTransformation, +) -class TestTransformPiecewiseModelToNestedInnerRepnGDP(unittest.TestCase): +class TestTransformPiecewiseModelToNestedInnerRepnGDP(unittest.TestCase): def test_solve_log_model(self): m = models.make_log_x_model() - TransformationFactory( - 'contrib.piecewise.nested_inner_repn_gdp' - ).apply_to(m) - TransformationFactory( - 'gdp.bigm' - ).apply_to(m) - SolverFactory('gurobi').solve(m) - ct.check_log_x_model_soln(self, m) \ No newline at end of file + TransformationFactory("contrib.piecewise.nested_inner_repn_gdp").apply_to(m) + TransformationFactory("gdp.bigm").apply_to(m) + SolverFactory("gurobi").solve(m) + ct.check_log_x_model_soln(self, m) diff --git a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py index a793ad77e38..8a9b493bdfe 100644 --- a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py +++ b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py @@ -20,9 +20,9 @@ class DisaggregatedLogarithmicInnerGDPTransformation(PiecewiseLinearToGDP): """ Represent a piecewise linear function "logarithmically" by using a MIP with - log_2(|P|) binary decision variables. This is a direct-to-MIP transformation; - GDP is not used. This method of logarithmically formulating the piecewise - linear function imposes no restrictions on the family of polytopes, but we + log_2(|P|) binary decision variables. This is a direct-to-MIP transformation; + GDP is not used. This method of logarithmically formulating the piecewise + linear function imposes no restrictions on the family of polytopes, but we assume we have simplces in this code. This method is due to Vielma et al., 2010. """ @@ -32,8 +32,7 @@ class DisaggregatedLogarithmicInnerGDPTransformation(PiecewiseLinearToGDP): # Implement to use PiecewiseLinearToGDP. This function returns the Var # that replaces the transformed piecewise linear expr def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_block): - - # Get a new Block for our transformation in transformation_block.transformed_functions, + # Get a new Block for our transformation in transformation_block.transformed_functions, # which is a Block(Any). This is where we will put our new components. transBlock = transformation_block.transformed_functions[ len(transformation_block.transformed_functions) @@ -60,12 +59,16 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc transBlock.simplex_point_indices = RangeSet(0, dimension) # Enumeration of simplices: map from simplex number to simplex object - self.idx_to_simplex = {k: v for k, v in zip(transBlock.simplex_indices, simplices)} + self.idx_to_simplex = { + k: v for k, v in zip(transBlock.simplex_indices, simplices) + } # List of tuples of simplex indices with their linear function - simplex_indices_and_lin_funcs = list(zip(transBlock.simplex_indices, pw_linear_func._linear_functions)) + simplex_indices_and_lin_funcs = list( + zip(transBlock.simplex_indices, pw_linear_func._linear_functions) + ) - # We don't seem to get a convenient opportunity later, so let's just widen + # We don't seem to get a convenient opportunity later, so let's just widen # the bounds here. All we need to do is go through the corners of each simplex. for P, linear_func in simplex_indices_and_lin_funcs: for v in transBlock.simplex_point_indices: @@ -90,9 +93,11 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc B[i] = self._get_binary_vector(i, log_dimension) # The lambda variables \lambda_{P,v} are indexed by the simplex and the point in it - transBlock.lambdas = Var(transBlock.simplex_indices, transBlock.simplex_point_indices, bounds=(0, 1)) + transBlock.lambdas = Var( + transBlock.simplex_indices, transBlock.simplex_point_indices, bounds=(0, 1) + ) - # Numbered citations are from Vielma et al 2010, Mixed-Integer Models + # Numbered citations are from Vielma et al 2010, Mixed-Integer Models # for Nonseparable Piecewise-Linear Optimization # Sum of all lambdas is one (6b) diff --git a/pyomo/contrib/piecewise/transform/nested_inner_repn.py b/pyomo/contrib/piecewise/transform/nested_inner_repn.py index a5c9b5015d3..af8728c0605 100644 --- a/pyomo/contrib/piecewise/transform/nested_inner_repn.py +++ b/pyomo/contrib/piecewise/transform/nested_inner_repn.py @@ -7,28 +7,29 @@ from pyomo.gdp import Disjunction from pyomo.common.errors import DeveloperError + @TransformationFactory.register( - 'contrib.piecewise.nested_inner_repn_gdp', - doc="TODO document", # TODO + "contrib.piecewise.nested_inner_repn_gdp", + doc="TODO document", # TODO ) class NestedInnerRepresentationGDPTransformation(PiecewiseLinearToGDP): """ - Represent a piecewise linear function by using a nested GDP to determine - which polytope a point is in, then representing it as a convex combination - of extreme points, with multipliers "local" to that particular polytope, - i.e., not shared with neighbors. This method of formulating the piecewise - linear function imposes no restrictions on the family of polytopes. Note - that this is NOT a logarithmic formulation - it has linearly many binaries. - This method was, however, inspired by the disagreggated logarithmic + Represent a piecewise linear function by using a nested GDP to determine + which polytope a point is in, then representing it as a convex combination + of extreme points, with multipliers "local" to that particular polytope, + i.e., not shared with neighbors. This method of formulating the piecewise + linear function imposes no restrictions on the family of polytopes. Note + that this is NOT a logarithmic formulation - it has linearly many binaries. + This method was, however, inspired by the disagreggated logarithmic formulation of Vielma et al., 2010. """ + CONFIG = PiecewiseLinearToGDP.CONFIG() - _transformation_name = 'pw_linear_nested_inner_repn' - + _transformation_name = "pw_linear_nested_inner_repn" + # Implement to use PiecewiseLinearToGDP. This function returns the Var # that replaces the transformed piecewise linear expr def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_block): - # Get a new Block() in transformation_block.transformed_functions, which # is a Block(Any) transBlock = transformation_block.transformed_functions[ @@ -41,29 +42,35 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc self.dimension = pw_expr.nargs() substitute_var = transBlock.substitute_var = Var() pw_linear_func.map_transformation_var(pw_expr, substitute_var) - self.substitute_var_lb = float('inf') - self.substitute_var_ub = -float('inf') - + self.substitute_var_lb = float("inf") + self.substitute_var_ub = -float("inf") + choices = list(zip(pw_linear_func._simplices, pw_linear_func._linear_functions)) # If there was only one choice, don't bother making a disjunction, just - # use the linear function directly (but still use the substitute_var for + # use the linear function directly (but still use the substitute_var for # consistency). if len(choices) == 1: - (_, linear_func) = choices[0] # simplex isn't important in this case + (_, linear_func) = choices[0] # simplex isn't important in this case linear_func_expr = linear_func(*pw_expr.args) - transBlock.set_substitute = Constraint(expr=substitute_var == linear_func_expr) - (self.substitute_var_lb, self.substitute_var_ub) = compute_bounds_on_expr(linear_func_expr) + transBlock.set_substitute = Constraint( + expr=substitute_var == linear_func_expr + ) + (self.substitute_var_lb, self.substitute_var_ub) = compute_bounds_on_expr( + linear_func_expr + ) else: # Add the disjunction - transBlock.disj = self._get_disjunction(choices, transBlock, pw_expr, transBlock) + transBlock.disj = self._get_disjunction( + choices, transBlock, pw_expr, transBlock + ) # Set bounds as determined when setting up the disjunction - if self.substitute_var_lb < float('inf'): + if self.substitute_var_lb < float("inf"): transBlock.substitute_var.setlb(self.substitute_var_lb) - if self.substitute_var_ub > -float('inf'): + if self.substitute_var_ub > -float("inf"): transBlock.substitute_var.setub(self.substitute_var_ub) - + return substitute_var # Recursively form the Disjunctions and Disjuncts. This shouldn't blow up @@ -76,17 +83,24 @@ def _get_disjunction(self, choices, parent_block, pw_expr, root_block): # a Disjunction containing only one Disjunct. We can ensure that size # is never 1 unless it was only passsed a single choice from the start, # which we can handle before calling. - if size > 3: - half = size // 2 # (integer divide) + if size > 3: + half = size // 2 # (integer divide) # This tree will be slightly heavier on the right side choices_l = choices[:half] choices_r = choices[half:] + @parent_block.Disjunct() def d_l(b): - b.inner_disjunction_l = self._get_disjunction(choices_l, b, pw_expr, root_block) + b.inner_disjunction_l = self._get_disjunction( + choices_l, b, pw_expr, root_block + ) + @parent_block.Disjunct() def d_r(b): - b.inner_disjunction_r = self._get_disjunction(choices_r, b, pw_expr, root_block) + b.inner_disjunction_r = self._get_disjunction( + choices_r, b, pw_expr, root_block + ) + return Disjunction(expr=[parent_block.d_l, parent_block.d_r]) elif size == 3: # Let's stay heavier on the right side for consistency. So the left @@ -95,27 +109,43 @@ def d_r(b): @parent_block.Disjunct() def d_l(b): simplex, linear_func = choices[0] - self._set_disjunct_block_constraints(b, simplex, linear_func, pw_expr, root_block) + self._set_disjunct_block_constraints( + b, simplex, linear_func, pw_expr, root_block + ) + @parent_block.Disjunct() def d_r(b): - b.inner_disjunction_r = self._get_disjunction(choices[1:], b, pw_expr, root_block) + b.inner_disjunction_r = self._get_disjunction( + choices[1:], b, pw_expr, root_block + ) + return Disjunction(expr=[parent_block.d_l, parent_block.d_r]) elif size == 2: # In this case both sides are regular Disjuncts @parent_block.Disjunct() def d_l(b): simplex, linear_func = choices[0] - self._set_disjunct_block_constraints(b, simplex, linear_func, pw_expr, root_block) + self._set_disjunct_block_constraints( + b, simplex, linear_func, pw_expr, root_block + ) + @parent_block.Disjunct() def d_r(b): simplex, linear_func = choices[1] - self._set_disjunct_block_constraints(b, simplex, linear_func, pw_expr, root_block) + self._set_disjunct_block_constraints( + b, simplex, linear_func, pw_expr, root_block + ) + return Disjunction(expr=[parent_block.d_l, parent_block.d_r]) else: - raise DeveloperError("Unreachable: 1 or 0 choices were passed to " - "_get_disjunction in nested_inner_repn.py.") + raise DeveloperError( + "Unreachable: 1 or 0 choices were passed to " + "_get_disjunction in nested_inner_repn.py." + ) - def _set_disjunct_block_constraints(self, b, simplex, linear_func, pw_expr, root_block): + def _set_disjunct_block_constraints( + self, b, simplex, linear_func, pw_expr, root_block + ): # Define the lambdas sparsely like in the normal inner repn, # only the first few will participate in constraints b.lambdas = Var(NonNegativeIntegers, dense=False, bounds=(0, 1)) @@ -125,14 +155,16 @@ def _set_disjunct_block_constraints(self, b, simplex, linear_func, pw_expr, root for idx in simplex: extreme_pts.append(self.pw_linear_func._points[idx]) - # Constrain sum(lambda_i) = 1 + # Constrain sum(lambda_i) = 1 b.convex_combo = Constraint( expr=sum(b.lambdas[i] for i in range(len(extreme_pts))) == 1 ) linear_func_expr = linear_func(*pw_expr.args) # Make the substitute Var equal the PWLE - b.set_substitute = Constraint(expr=root_block.substitute_var == linear_func_expr) + b.set_substitute = Constraint( + expr=root_block.substitute_var == linear_func_expr + ) # Widen the variable bounds to those of this linear func expression (lb, ub) = compute_bounds_on_expr(linear_func_expr) From c11fa709e5577fbff10b28d6b66aee0b71668304 Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Tue, 14 Nov 2023 13:16:51 -0500 Subject: [PATCH 1197/1797] rename: PiecewiseLinearToGDP->PiecewiseLinearTransformationBase, since it isn't GDP-specific --- .../transform/disagreggated_logarithmic.py | 6 +++--- .../transform/inner_representation_gdp.py | 6 +++--- .../piecewise/transform/nested_inner_repn.py | 17 +++++++++++++---- .../transform/outer_representation_gdp.py | 6 +++--- .../piecewise_to_gdp_transformation.py | 2 +- .../reduced_inner_representation_gdp.py | 6 +++--- 6 files changed, 26 insertions(+), 17 deletions(-) diff --git a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py index 8a9b493bdfe..e10c1ee8091 100644 --- a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py +++ b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py @@ -1,5 +1,5 @@ from pyomo.contrib.piecewise.transform.piecewise_to_gdp_transformation import ( - PiecewiseLinearToGDP, + PiecewiseLinearTransformationBase, ) from pyomo.core import Constraint, Binary, Var, RangeSet from pyomo.core.base import TransformationFactory @@ -17,7 +17,7 @@ assume we have simplces in this code. This method is due to Vielma et al., 2010. """, ) -class DisaggregatedLogarithmicInnerGDPTransformation(PiecewiseLinearToGDP): +class DisaggregatedLogarithmicInnerMIPTransformation(PiecewiseLinearTransformationBase): """ Represent a piecewise linear function "logarithmically" by using a MIP with log_2(|P|) binary decision variables. This is a direct-to-MIP transformation; @@ -26,7 +26,7 @@ class DisaggregatedLogarithmicInnerGDPTransformation(PiecewiseLinearToGDP): assume we have simplces in this code. This method is due to Vielma et al., 2010. """ - CONFIG = PiecewiseLinearToGDP.CONFIG() + CONFIG = PiecewiseLinearTransformationBase.CONFIG() _transformation_name = "pw_linear_disaggregated_log" # Implement to use PiecewiseLinearToGDP. This function returns the Var diff --git a/pyomo/contrib/piecewise/transform/inner_representation_gdp.py b/pyomo/contrib/piecewise/transform/inner_representation_gdp.py index f0be2d98825..25b8664ccf2 100644 --- a/pyomo/contrib/piecewise/transform/inner_representation_gdp.py +++ b/pyomo/contrib/piecewise/transform/inner_representation_gdp.py @@ -11,7 +11,7 @@ from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr from pyomo.contrib.piecewise.transform.piecewise_to_gdp_transformation import ( - PiecewiseLinearToGDP, + PiecewiseLinearTransformationBase, ) from pyomo.core import Constraint, NonNegativeIntegers, Suffix, Var from pyomo.core.base import TransformationFactory @@ -25,7 +25,7 @@ "simplices that are the domains of the linear " "functions.", ) -class InnerRepresentationGDPTransformation(PiecewiseLinearToGDP): +class InnerRepresentationGDPTransformation(PiecewiseLinearTransformationBase): """ Convert a model involving piecewise linear expressions into a GDP by representing the piecewise linear functions as Disjunctions where the @@ -49,7 +49,7 @@ class InnerRepresentationGDPTransformation(PiecewiseLinearToGDP): this mode, targets must be Blocks, Constraints, and/or Objectives. """ - CONFIG = PiecewiseLinearToGDP.CONFIG() + CONFIG = PiecewiseLinearTransformationBase.CONFIG() _transformation_name = 'pw_linear_inner_repn' def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_block): diff --git a/pyomo/contrib/piecewise/transform/nested_inner_repn.py b/pyomo/contrib/piecewise/transform/nested_inner_repn.py index af8728c0605..6900d1f3322 100644 --- a/pyomo/contrib/piecewise/transform/nested_inner_repn.py +++ b/pyomo/contrib/piecewise/transform/nested_inner_repn.py @@ -1,6 +1,6 @@ from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr from pyomo.contrib.piecewise.transform.piecewise_to_gdp_transformation import ( - PiecewiseLinearToGDP, + PiecewiseLinearTransformationBase, ) from pyomo.core import Constraint, NonNegativeIntegers, Suffix, Var from pyomo.core.base import TransformationFactory @@ -10,9 +10,18 @@ @TransformationFactory.register( "contrib.piecewise.nested_inner_repn_gdp", - doc="TODO document", # TODO + doc=""" + Represent a piecewise linear function by using a nested GDP to determine + which polytope a point is in, then representing it as a convex combination + of extreme points, with multipliers "local" to that particular polytope, + i.e., not shared with neighbors. This method of formulating the piecewise + linear function imposes no restrictions on the family of polytopes. Note + that this is NOT a logarithmic formulation - it has linearly many binaries. + This method was, however, inspired by the disagreggated logarithmic + formulation of Vielma et al., 2010. + """ ) -class NestedInnerRepresentationGDPTransformation(PiecewiseLinearToGDP): +class NestedInnerRepresentationGDPTransformation(PiecewiseLinearTransformationBase): """ Represent a piecewise linear function by using a nested GDP to determine which polytope a point is in, then representing it as a convex combination @@ -24,7 +33,7 @@ class NestedInnerRepresentationGDPTransformation(PiecewiseLinearToGDP): formulation of Vielma et al., 2010. """ - CONFIG = PiecewiseLinearToGDP.CONFIG() + CONFIG = PiecewiseLinearTransformationBase.CONFIG() _transformation_name = "pw_linear_nested_inner_repn" # Implement to use PiecewiseLinearToGDP. This function returns the Var diff --git a/pyomo/contrib/piecewise/transform/outer_representation_gdp.py b/pyomo/contrib/piecewise/transform/outer_representation_gdp.py index 7c81619430a..bd50b9c708f 100644 --- a/pyomo/contrib/piecewise/transform/outer_representation_gdp.py +++ b/pyomo/contrib/piecewise/transform/outer_representation_gdp.py @@ -13,7 +13,7 @@ from pyomo.common.dependencies.scipy import spatial from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr from pyomo.contrib.piecewise.transform.piecewise_to_gdp_transformation import ( - PiecewiseLinearToGDP, + PiecewiseLinearTransformationBase, ) from pyomo.core import Constraint, NonNegativeIntegers, Suffix, Var from pyomo.core.base import TransformationFactory @@ -27,7 +27,7 @@ "the simplices that are the domains of the " "linear functions.", ) -class OuterRepresentationGDPTransformation(PiecewiseLinearToGDP): +class OuterRepresentationGDPTransformation(PiecewiseLinearTransformationBase): """ Convert a model involving piecewise linear expressions into a GDP by representing the piecewise linear functions as Disjunctions where the @@ -49,7 +49,7 @@ class OuterRepresentationGDPTransformation(PiecewiseLinearToGDP): this mode, targets must be Blocks, Constraints, and/or Objectives. """ - CONFIG = PiecewiseLinearToGDP.CONFIG() + CONFIG = PiecewiseLinearTransformationBase.CONFIG() _transformation_name = 'pw_linear_outer_repn' def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_block): diff --git a/pyomo/contrib/piecewise/transform/piecewise_to_gdp_transformation.py b/pyomo/contrib/piecewise/transform/piecewise_to_gdp_transformation.py index 2e056c47a15..d8e46ad7311 100644 --- a/pyomo/contrib/piecewise/transform/piecewise_to_gdp_transformation.py +++ b/pyomo/contrib/piecewise/transform/piecewise_to_gdp_transformation.py @@ -40,7 +40,7 @@ from pyomo.network import Port -class PiecewiseLinearToGDP(Transformation): +class PiecewiseLinearTransformationBase(Transformation): """ Base class for transformations of piecewise-linear models to GDPs """ diff --git a/pyomo/contrib/piecewise/transform/reduced_inner_representation_gdp.py b/pyomo/contrib/piecewise/transform/reduced_inner_representation_gdp.py index 5c7dfa895ab..86c33e40623 100644 --- a/pyomo/contrib/piecewise/transform/reduced_inner_representation_gdp.py +++ b/pyomo/contrib/piecewise/transform/reduced_inner_representation_gdp.py @@ -11,7 +11,7 @@ from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr from pyomo.contrib.piecewise.transform.piecewise_to_gdp_transformation import ( - PiecewiseLinearToGDP, + PiecewiseLinearTransformationBase, ) from pyomo.core import Constraint, NonNegativeIntegers, Var from pyomo.core.base import TransformationFactory @@ -25,7 +25,7 @@ "simplices that are the domains of the linear " "functions.", ) -class ReducedInnerRepresentationGDPTransformation(PiecewiseLinearToGDP): +class ReducedInnerRepresentationGDPTransformation(PiecewiseLinearTransformationBase): """ Convert a model involving piecewise linear expressions into a GDP by representing the piecewise linear functions as Disjunctions where the @@ -51,7 +51,7 @@ class ReducedInnerRepresentationGDPTransformation(PiecewiseLinearToGDP): this mode, targets must be Blocks, Constraints, and/or Objectives. """ - CONFIG = PiecewiseLinearToGDP.CONFIG() + CONFIG = PiecewiseLinearTransformationBase.CONFIG() _transformation_name = 'pw_linear_reduced_inner_repn' def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_block): From f34bf30c73d3297cc88f33a21be0938379feafa3 Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Tue, 14 Nov 2023 13:21:37 -0500 Subject: [PATCH 1198/1797] apply black --- pyomo/contrib/piecewise/transform/nested_inner_repn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/piecewise/transform/nested_inner_repn.py b/pyomo/contrib/piecewise/transform/nested_inner_repn.py index 6900d1f3322..17b85604337 100644 --- a/pyomo/contrib/piecewise/transform/nested_inner_repn.py +++ b/pyomo/contrib/piecewise/transform/nested_inner_repn.py @@ -19,7 +19,7 @@ that this is NOT a logarithmic formulation - it has linearly many binaries. This method was, however, inspired by the disagreggated logarithmic formulation of Vielma et al., 2010. - """ + """, ) class NestedInnerRepresentationGDPTransformation(PiecewiseLinearTransformationBase): """ From 7acb187a0737c885d1fb4e78cf5ca13c18908ea3 Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Tue, 14 Nov 2023 14:15:13 -0500 Subject: [PATCH 1199/1797] fix typos and add title for reference --- .../transform/disagreggated_logarithmic.py | 14 +++++++++----- .../piecewise/transform/nested_inner_repn.py | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py index e10c1ee8091..c6c492c70a3 100644 --- a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py +++ b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py @@ -11,10 +11,12 @@ "contrib.piecewise.disaggregated_logarithmic", doc=""" Represent a piecewise linear function "logarithmically" by using a MIP with - log_2(|P|) binary decision variables. This is a direct-to-MIP transformation; - GDP is not used. This method of logarithmically formulating the piecewise - linear function imposes no restrictions on the family of polytopes, but we - assume we have simplces in this code. This method is due to Vielma et al., 2010. + log_2(|P|) binary decision variables. This is a direct-to-MIP transformation; + GDP is not used. This method of logarithmically formulating the piecewise + linear function imposes no restrictions on the family of polytopes, but we + assume we have simplices in this code. This method is due to Vielma, Ahmed, + and Nemhauser 2010, Mixed-Integer Models for Nonseparable Piecewise-Linear + Optimization. """, ) class DisaggregatedLogarithmicInnerMIPTransformation(PiecewiseLinearTransformationBase): @@ -23,7 +25,9 @@ class DisaggregatedLogarithmicInnerMIPTransformation(PiecewiseLinearTransformati log_2(|P|) binary decision variables. This is a direct-to-MIP transformation; GDP is not used. This method of logarithmically formulating the piecewise linear function imposes no restrictions on the family of polytopes, but we - assume we have simplces in this code. This method is due to Vielma et al., 2010. + assume we have simplices in this code. This method is due to Vielma, Ahmed, + and Nemhauser 2010, Mixed-Integer Models for Nonseparable Piecewise-Linear + Optimization. """ CONFIG = PiecewiseLinearTransformationBase.CONFIG() diff --git a/pyomo/contrib/piecewise/transform/nested_inner_repn.py b/pyomo/contrib/piecewise/transform/nested_inner_repn.py index 17b85604337..f6939a8a288 100644 --- a/pyomo/contrib/piecewise/transform/nested_inner_repn.py +++ b/pyomo/contrib/piecewise/transform/nested_inner_repn.py @@ -90,7 +90,7 @@ def _get_disjunction(self, choices, parent_block, pw_expr, root_block): # Our base cases will be 3 and 2, since it would be silly to construct # a Disjunction containing only one Disjunct. We can ensure that size - # is never 1 unless it was only passsed a single choice from the start, + # is never 1 unless it was only passed a single choice from the start, # which we can handle before calling. if size > 3: half = size // 2 # (integer divide) From 6548b6207217bd7ef48356275505242b8845fa15 Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Tue, 14 Nov 2023 14:18:31 -0500 Subject: [PATCH 1200/1797] fix incorrect imports and comments --- ...d_logarithmic_gdp.py => test_disaggregated_logarithmic.py} | 4 ++-- .../contrib/piecewise/transform/disagreggated_logarithmic.py | 2 +- pyomo/contrib/piecewise/transform/nested_inner_repn.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename pyomo/contrib/piecewise/tests/{test_disaggregated_logarithmic_gdp.py => test_disaggregated_logarithmic.py} (92%) diff --git a/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic_gdp.py b/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py similarity index 92% rename from pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic_gdp.py rename to pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py index d3b58f401f2..8a225985d82 100644 --- a/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic_gdp.py +++ b/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py @@ -21,11 +21,11 @@ from pyomo.environ import Constraint, SolverFactory, Var from pyomo.contrib.piecewise.transform.disagreggated_logarithmic import ( - DisaggregatedLogarithmicInnerGDPTransformation, + DisaggregatedLogarithmicInnerMIPTransformation ) -class TestTransformPiecewiseModelToNestedInnerRepnGDP(unittest.TestCase): +class TestTransformPiecewiseModelToNestedInnerRepnMIP(unittest.TestCase): def test_solve_log_model(self): m = models.make_log_x_model() TransformationFactory("contrib.piecewise.disaggregated_logarithmic").apply_to(m) diff --git a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py index c6c492c70a3..1104b1c265b 100644 --- a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py +++ b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py @@ -33,7 +33,7 @@ class DisaggregatedLogarithmicInnerMIPTransformation(PiecewiseLinearTransformati CONFIG = PiecewiseLinearTransformationBase.CONFIG() _transformation_name = "pw_linear_disaggregated_log" - # Implement to use PiecewiseLinearToGDP. This function returns the Var + # Implement to use PiecewiseLinearTransformationBase. This function returns the Var # that replaces the transformed piecewise linear expr def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_block): # Get a new Block for our transformation in transformation_block.transformed_functions, diff --git a/pyomo/contrib/piecewise/transform/nested_inner_repn.py b/pyomo/contrib/piecewise/transform/nested_inner_repn.py index f6939a8a288..7f5c54dd9a2 100644 --- a/pyomo/contrib/piecewise/transform/nested_inner_repn.py +++ b/pyomo/contrib/piecewise/transform/nested_inner_repn.py @@ -36,7 +36,7 @@ class NestedInnerRepresentationGDPTransformation(PiecewiseLinearTransformationBa CONFIG = PiecewiseLinearTransformationBase.CONFIG() _transformation_name = "pw_linear_nested_inner_repn" - # Implement to use PiecewiseLinearToGDP. This function returns the Var + # Implement to use PiecewiseLinearTransformationBase. This function returns the Var # that replaces the transformed piecewise linear expr def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_block): # Get a new Block() in transformation_block.transformed_functions, which From ae953e894f4e3be1e9ae07dcf487978e9dbba25a Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Tue, 14 Nov 2023 14:22:00 -0500 Subject: [PATCH 1201/1797] register transformations in __init__.py so they don't need to be imported --- pyomo/contrib/piecewise/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyomo/contrib/piecewise/__init__.py b/pyomo/contrib/piecewise/__init__.py index 37873c83b3b..de18e559a93 100644 --- a/pyomo/contrib/piecewise/__init__.py +++ b/pyomo/contrib/piecewise/__init__.py @@ -33,3 +33,9 @@ from pyomo.contrib.piecewise.transform.convex_combination import ( ConvexCombinationTransformation, ) +from pyomo.contrib.piecewise.transform.nested_inner_repn import ( + NestedInnerRepresentationGDPTransformation, +) +from pyomo.contrib.piecewise.transform.disagreggated_logarithmic import ( + DisaggregatedLogarithmicInnerMIPTransformation, +) From 63af6266ab73d74547d837ee33b260bdadc71183 Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Tue, 14 Nov 2023 14:26:32 -0500 Subject: [PATCH 1202/1797] remove unused (for now) imports --- .../piecewise/tests/test_disaggregated_logarithmic.py | 11 +---------- .../piecewise/tests/test_nested_inner_repn_gdp.py | 11 +---------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py b/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py index 8a225985d82..8fd4bebfc37 100644 --- a/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py +++ b/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py @@ -13,16 +13,7 @@ from pyomo.contrib.piecewise.tests import models import pyomo.contrib.piecewise.tests.common_tests as ct from pyomo.core.base import TransformationFactory -from pyomo.core.expr.compare import ( - assertExpressionsEqual, - assertExpressionsStructurallyEqual, -) -from pyomo.gdp import Disjunct, Disjunction -from pyomo.environ import Constraint, SolverFactory, Var - -from pyomo.contrib.piecewise.transform.disagreggated_logarithmic import ( - DisaggregatedLogarithmicInnerMIPTransformation -) +from pyomo.environ import SolverFactory class TestTransformPiecewiseModelToNestedInnerRepnMIP(unittest.TestCase): diff --git a/pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py b/pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py index f41233435d4..fd8e7ab201c 100644 --- a/pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py +++ b/pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py @@ -13,16 +13,7 @@ from pyomo.contrib.piecewise.tests import models import pyomo.contrib.piecewise.tests.common_tests as ct from pyomo.core.base import TransformationFactory -from pyomo.core.expr.compare import ( - assertExpressionsEqual, - assertExpressionsStructurallyEqual, -) -from pyomo.gdp import Disjunct, Disjunction -from pyomo.environ import Constraint, SolverFactory, Var - -from pyomo.contrib.piecewise.transform.nested_inner_repn import ( - NestedInnerRepresentationGDPTransformation, -) +from pyomo.environ import SolverFactory class TestTransformPiecewiseModelToNestedInnerRepnGDP(unittest.TestCase): From acfa476b9deb3ed3f2f141dd925fb2a54df3db3a Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Tue, 14 Nov 2023 14:34:26 -0500 Subject: [PATCH 1203/1797] do the reference properly --- .../transform/disagreggated_logarithmic.py | 24 ++++++++++--------- .../piecewise/transform/nested_inner_repn.py | 16 +++++++------ 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py index 1104b1c265b..4fa7a0eeeb1 100644 --- a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py +++ b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py @@ -12,22 +12,24 @@ doc=""" Represent a piecewise linear function "logarithmically" by using a MIP with log_2(|P|) binary decision variables. This is a direct-to-MIP transformation; - GDP is not used. This method of logarithmically formulating the piecewise - linear function imposes no restrictions on the family of polytopes, but we - assume we have simplices in this code. This method is due to Vielma, Ahmed, - and Nemhauser 2010, Mixed-Integer Models for Nonseparable Piecewise-Linear - Optimization. + GDP is not used. """, ) class DisaggregatedLogarithmicInnerMIPTransformation(PiecewiseLinearTransformationBase): """ Represent a piecewise linear function "logarithmically" by using a MIP with - log_2(|P|) binary decision variables. This is a direct-to-MIP transformation; - GDP is not used. This method of logarithmically formulating the piecewise - linear function imposes no restrictions on the family of polytopes, but we - assume we have simplices in this code. This method is due to Vielma, Ahmed, - and Nemhauser 2010, Mixed-Integer Models for Nonseparable Piecewise-Linear - Optimization. + log_2(|P|) binary decision variables, following the "disaggregated logarithmic" + method from [1]. This is a direct-to-MIP transformation; GDP is not used. + This method of logarithmically formulating the piecewise linear function + imposes no restrictions on the family of polytopes, but we assume we have + simplices in this code. + + References + ---------- + [1] J.P. Vielma, S. Ahmed, and G. Nemhauser, "Mixed-integer models + for nonseparable piecewise-linear optimization: unifying framework + and extensions," Operations Research, vol. 58, no. 2, pp. 305-315, + 2010. """ CONFIG = PiecewiseLinearTransformationBase.CONFIG() diff --git a/pyomo/contrib/piecewise/transform/nested_inner_repn.py b/pyomo/contrib/piecewise/transform/nested_inner_repn.py index 7f5c54dd9a2..67abd815c7f 100644 --- a/pyomo/contrib/piecewise/transform/nested_inner_repn.py +++ b/pyomo/contrib/piecewise/transform/nested_inner_repn.py @@ -14,11 +14,7 @@ Represent a piecewise linear function by using a nested GDP to determine which polytope a point is in, then representing it as a convex combination of extreme points, with multipliers "local" to that particular polytope, - i.e., not shared with neighbors. This method of formulating the piecewise - linear function imposes no restrictions on the family of polytopes. Note - that this is NOT a logarithmic formulation - it has linearly many binaries. - This method was, however, inspired by the disagreggated logarithmic - formulation of Vielma et al., 2010. + i.e., not shared with neighbors. This formulation has linearly many binaries. """, ) class NestedInnerRepresentationGDPTransformation(PiecewiseLinearTransformationBase): @@ -29,8 +25,14 @@ class NestedInnerRepresentationGDPTransformation(PiecewiseLinearTransformationBa i.e., not shared with neighbors. This method of formulating the piecewise linear function imposes no restrictions on the family of polytopes. Note that this is NOT a logarithmic formulation - it has linearly many binaries. - This method was, however, inspired by the disagreggated logarithmic - formulation of Vielma et al., 2010. + However, it is inspired by the disaggregated logarithmic formulation of [1]. + + References + ---------- + [1] J.P. Vielma, S. Ahmed, and G. Nemhauser, "Mixed-integer models + for nonseparable piecewise-linear optimization: unifying framework + and extensions," Operations Research, vol. 58, no. 2, pp. 305-315, + 2010. """ CONFIG = PiecewiseLinearTransformationBase.CONFIG() From 81960f146d22f54e00ab8db6a11e758545eca41f Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Thu, 14 Dec 2023 18:48:01 -0500 Subject: [PATCH 1204/1797] stop using `self` unnecessarily --- .../transform/disagreggated_logarithmic.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py index 4fa7a0eeeb1..4b388d9df1e 100644 --- a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py +++ b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py @@ -53,19 +53,19 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc pw_linear_func.map_transformation_var(pw_expr, substitute_var) # Bounds for the substitute_var that we will widen - self.substitute_var_lb = float("inf") - self.substitute_var_ub = -float("inf") + substitute_var_lb = float("inf") + substitute_var_ub = -float("inf") # Simplices are tuples of indices of points. Give them their own indices, too simplices = pw_linear_func._simplices num_simplices = len(simplices) transBlock.simplex_indices = RangeSet(0, num_simplices - 1) - # Assumption: the simplices are really simplices and all have the same number of points, - # which is dimension + 1 + # Assumption: the simplices are really full-dimensional simplices and all have the + # same number of points, which is dimension + 1 transBlock.simplex_point_indices = RangeSet(0, dimension) # Enumeration of simplices: map from simplex number to simplex object - self.idx_to_simplex = { + idx_to_simplex = { k: v for k, v in zip(transBlock.simplex_indices, simplices) } @@ -78,13 +78,13 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc # the bounds here. All we need to do is go through the corners of each simplex. for P, linear_func in simplex_indices_and_lin_funcs: for v in transBlock.simplex_point_indices: - val = linear_func(*pw_linear_func._points[self.idx_to_simplex[P][v]]) - if val < self.substitute_var_lb: - self.substitute_var_lb = val - if val > self.substitute_var_ub: - self.substitute_var_ub = val - transBlock.substitute_var.setlb(self.substitute_var_lb) - transBlock.substitute_var.setub(self.substitute_var_ub) + val = linear_func(*pw_linear_func._points[idx_to_simplex[P][v]]) + if val < substitute_var_lb: + substitute_var_lb = val + if val > substitute_var_ub: + substitute_var_ub = val + transBlock.substitute_var.setlb(substitute_var_lb) + transBlock.substitute_var.setub(substitute_var_ub) log_dimension = ceil(log2(num_simplices)) transBlock.log_simplex_indices = RangeSet(0, log_dimension - 1) @@ -146,7 +146,7 @@ def simplex_choice_2(b, l): def x_constraint(b, i): return pw_expr.args[i] == sum( transBlock.lambdas[P, v] - * pw_linear_func._points[self.idx_to_simplex[P][v]][i] + * pw_linear_func._points[idx_to_simplex[P][v]][i] for P in transBlock.simplex_indices for v in transBlock.simplex_point_indices ) @@ -157,7 +157,7 @@ def x_constraint(b, i): == sum( sum( transBlock.lambdas[P, v] - * linear_func(*pw_linear_func._points[self.idx_to_simplex[P][v]]) + * linear_func(*pw_linear_func._points[idx_to_simplex[P][v]]) for v in transBlock.simplex_point_indices ) for (P, linear_func) in simplex_indices_and_lin_funcs From c7975cdd48c1b058e15e81ec7e06bfa34c8bbe3a Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Thu, 14 Dec 2023 18:55:07 -0500 Subject: [PATCH 1205/1797] rename file to match refactored class name --- pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py | 2 +- pyomo/contrib/piecewise/transform/inner_representation_gdp.py | 2 +- pyomo/contrib/piecewise/transform/nested_inner_repn.py | 2 +- pyomo/contrib/piecewise/transform/outer_representation_gdp.py | 2 +- ...ransformation.py => piecewise_linear_transformation_base.py} | 0 .../piecewise/transform/reduced_inner_representation_gdp.py | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename pyomo/contrib/piecewise/transform/{piecewise_to_gdp_transformation.py => piecewise_linear_transformation_base.py} (100%) diff --git a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py index 4b388d9df1e..4e399d714f7 100644 --- a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py +++ b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py @@ -1,4 +1,4 @@ -from pyomo.contrib.piecewise.transform.piecewise_to_gdp_transformation import ( +from pyomo.contrib.piecewise.transform.piecewise_linear_transformation_base import ( PiecewiseLinearTransformationBase, ) from pyomo.core import Constraint, Binary, Var, RangeSet diff --git a/pyomo/contrib/piecewise/transform/inner_representation_gdp.py b/pyomo/contrib/piecewise/transform/inner_representation_gdp.py index 25b8664ccf2..e4818c1cbb9 100644 --- a/pyomo/contrib/piecewise/transform/inner_representation_gdp.py +++ b/pyomo/contrib/piecewise/transform/inner_representation_gdp.py @@ -10,7 +10,7 @@ # ___________________________________________________________________________ from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr -from pyomo.contrib.piecewise.transform.piecewise_to_gdp_transformation import ( +from pyomo.contrib.piecewise.transform.piecewise_linear_transformation_base import ( PiecewiseLinearTransformationBase, ) from pyomo.core import Constraint, NonNegativeIntegers, Suffix, Var diff --git a/pyomo/contrib/piecewise/transform/nested_inner_repn.py b/pyomo/contrib/piecewise/transform/nested_inner_repn.py index 67abd815c7f..e7e76dc3778 100644 --- a/pyomo/contrib/piecewise/transform/nested_inner_repn.py +++ b/pyomo/contrib/piecewise/transform/nested_inner_repn.py @@ -1,5 +1,5 @@ from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr -from pyomo.contrib.piecewise.transform.piecewise_to_gdp_transformation import ( +from pyomo.contrib.piecewise.transform.piecewise_linear_transformation_base import ( PiecewiseLinearTransformationBase, ) from pyomo.core import Constraint, NonNegativeIntegers, Suffix, Var diff --git a/pyomo/contrib/piecewise/transform/outer_representation_gdp.py b/pyomo/contrib/piecewise/transform/outer_representation_gdp.py index bd50b9c708f..6c26772fe6a 100644 --- a/pyomo/contrib/piecewise/transform/outer_representation_gdp.py +++ b/pyomo/contrib/piecewise/transform/outer_representation_gdp.py @@ -12,7 +12,7 @@ import pyomo.common.dependencies.numpy as np from pyomo.common.dependencies.scipy import spatial from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr -from pyomo.contrib.piecewise.transform.piecewise_to_gdp_transformation import ( +from pyomo.contrib.piecewise.transform.piecewise_linear_transformation_base import ( PiecewiseLinearTransformationBase, ) from pyomo.core import Constraint, NonNegativeIntegers, Suffix, Var diff --git a/pyomo/contrib/piecewise/transform/piecewise_to_gdp_transformation.py b/pyomo/contrib/piecewise/transform/piecewise_linear_transformation_base.py similarity index 100% rename from pyomo/contrib/piecewise/transform/piecewise_to_gdp_transformation.py rename to pyomo/contrib/piecewise/transform/piecewise_linear_transformation_base.py diff --git a/pyomo/contrib/piecewise/transform/reduced_inner_representation_gdp.py b/pyomo/contrib/piecewise/transform/reduced_inner_representation_gdp.py index 86c33e40623..a19507a93fd 100644 --- a/pyomo/contrib/piecewise/transform/reduced_inner_representation_gdp.py +++ b/pyomo/contrib/piecewise/transform/reduced_inner_representation_gdp.py @@ -10,7 +10,7 @@ # ___________________________________________________________________________ from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr -from pyomo.contrib.piecewise.transform.piecewise_to_gdp_transformation import ( +from pyomo.contrib.piecewise.transform.piecewise_linear_transformation_base import ( PiecewiseLinearTransformationBase, ) from pyomo.core import Constraint, NonNegativeIntegers, Var From 526305c6c3c87c918bed3f1be4e1f43bc325dea9 Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Thu, 14 Dec 2023 22:06:24 -0500 Subject: [PATCH 1206/1797] minor refactors --- .../piecewise/transform/nested_inner_repn.py | 57 +++++++++---------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/pyomo/contrib/piecewise/transform/nested_inner_repn.py b/pyomo/contrib/piecewise/transform/nested_inner_repn.py index e7e76dc3778..6ee0e6c9e80 100644 --- a/pyomo/contrib/piecewise/transform/nested_inner_repn.py +++ b/pyomo/contrib/piecewise/transform/nested_inner_repn.py @@ -14,7 +14,8 @@ Represent a piecewise linear function by using a nested GDP to determine which polytope a point is in, then representing it as a convex combination of extreme points, with multipliers "local" to that particular polytope, - i.e., not shared with neighbors. This formulation has linearly many binaries. + i.e., not shared with neighbors. This formulation has linearly many Boolean + variables, though up to variable substitution, it has logarithmically many. """, ) class NestedInnerRepresentationGDPTransformation(PiecewiseLinearTransformationBase): @@ -24,8 +25,10 @@ class NestedInnerRepresentationGDPTransformation(PiecewiseLinearTransformationBa of extreme points, with multipliers "local" to that particular polytope, i.e., not shared with neighbors. This method of formulating the piecewise linear function imposes no restrictions on the family of polytopes. Note - that this is NOT a logarithmic formulation - it has linearly many binaries. - However, it is inspired by the disaggregated logarithmic formulation of [1]. + that this is NOT a logarithmic formulation - it has linearly many Boolean + variables. However, it is inspired by the disaggregated logarithmic + formulation of [1]. Up to variable substitution, the amount of Boolean + variables is logarithmic, as in [1]. References ---------- @@ -47,14 +50,10 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc len(transformation_block.transformed_functions) ] - # these copy-pasted lines (from inner_representation_gdp) seem useful - # adding some of this stuff to self so I don't have to pass it around - self.pw_linear_func = pw_linear_func - self.dimension = pw_expr.nargs() substitute_var = transBlock.substitute_var = Var() pw_linear_func.map_transformation_var(pw_expr, substitute_var) - self.substitute_var_lb = float("inf") - self.substitute_var_ub = -float("inf") + substitute_var_lb = float("inf") + substitute_var_ub = -float("inf") choices = list(zip(pw_linear_func._simplices, pw_linear_func._linear_functions)) @@ -67,27 +66,27 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc transBlock.set_substitute = Constraint( expr=substitute_var == linear_func_expr ) - (self.substitute_var_lb, self.substitute_var_ub) = compute_bounds_on_expr( + (substitute_var_lb, substitute_var_ub) = compute_bounds_on_expr( linear_func_expr ) else: # Add the disjunction transBlock.disj = self._get_disjunction( - choices, transBlock, pw_expr, transBlock + choices, transBlock, pw_expr, pw_linear_func, transBlock ) # Set bounds as determined when setting up the disjunction - if self.substitute_var_lb < float("inf"): - transBlock.substitute_var.setlb(self.substitute_var_lb) - if self.substitute_var_ub > -float("inf"): - transBlock.substitute_var.setub(self.substitute_var_ub) + if substitute_var_lb < float("inf"): + transBlock.substitute_var.setlb(substitute_var_lb) + if substitute_var_ub > -float("inf"): + transBlock.substitute_var.setub(substitute_var_ub) return substitute_var # Recursively form the Disjunctions and Disjuncts. This shouldn't blow up # the stack, since the whole point is that we'll only go logarithmically # many calls deep. - def _get_disjunction(self, choices, parent_block, pw_expr, root_block): + def _get_disjunction(self, choices, parent_block, pw_expr, pw_linear_func, root_block): size = len(choices) # Our base cases will be 3 and 2, since it would be silly to construct @@ -103,13 +102,13 @@ def _get_disjunction(self, choices, parent_block, pw_expr, root_block): @parent_block.Disjunct() def d_l(b): b.inner_disjunction_l = self._get_disjunction( - choices_l, b, pw_expr, root_block + choices_l, b, pw_expr, pw_linear_func, root_block ) @parent_block.Disjunct() def d_r(b): b.inner_disjunction_r = self._get_disjunction( - choices_r, b, pw_expr, root_block + choices_r, b, pw_expr, pw_linear_func, root_block ) return Disjunction(expr=[parent_block.d_l, parent_block.d_r]) @@ -121,13 +120,13 @@ def d_r(b): def d_l(b): simplex, linear_func = choices[0] self._set_disjunct_block_constraints( - b, simplex, linear_func, pw_expr, root_block + b, simplex, linear_func, pw_expr, pw_linear_func, root_block ) @parent_block.Disjunct() def d_r(b): b.inner_disjunction_r = self._get_disjunction( - choices[1:], b, pw_expr, root_block + choices[1:], b, pw_expr, pw_linear_func, root_block ) return Disjunction(expr=[parent_block.d_l, parent_block.d_r]) @@ -137,14 +136,14 @@ def d_r(b): def d_l(b): simplex, linear_func = choices[0] self._set_disjunct_block_constraints( - b, simplex, linear_func, pw_expr, root_block + b, simplex, linear_func, pw_expr, pw_linear_func, root_block ) @parent_block.Disjunct() def d_r(b): simplex, linear_func = choices[1] self._set_disjunct_block_constraints( - b, simplex, linear_func, pw_expr, root_block + b, simplex, linear_func, pw_expr, pw_linear_func, root_block ) return Disjunction(expr=[parent_block.d_l, parent_block.d_r]) @@ -155,7 +154,7 @@ def d_r(b): ) def _set_disjunct_block_constraints( - self, b, simplex, linear_func, pw_expr, root_block + self, b, simplex, linear_func, pw_expr, pw_linear_func, root_block ): # Define the lambdas sparsely like in the normal inner repn, # only the first few will participate in constraints @@ -164,7 +163,7 @@ def _set_disjunct_block_constraints( # Get the extreme points to add up extreme_pts = [] for idx in simplex: - extreme_pts.append(self.pw_linear_func._points[idx]) + extreme_pts.append(pw_linear_func._points[idx]) # Constrain sum(lambda_i) = 1 b.convex_combo = Constraint( @@ -179,13 +178,13 @@ def _set_disjunct_block_constraints( # Widen the variable bounds to those of this linear func expression (lb, ub) = compute_bounds_on_expr(linear_func_expr) - if lb is not None and lb < self.substitute_var_lb: - self.substitute_var_lb = lb - if ub is not None and ub > self.substitute_var_ub: - self.substitute_var_ub = ub + if lb is not None and lb < substitute_var_lb: + substitute_var_lb = lb + if ub is not None and ub > substitute_var_ub: + substitute_var_ub = ub # Constrain x = \sum \lambda_i v_i - @b.Constraint(range(self.dimension)) + @b.Constraint(range(pw_expr.nargs())) # dimension def linear_combo(d, i): return pw_expr.args[i] == sum( d.lambdas[j] * pt[i] for j, pt in enumerate(extreme_pts) From d6b9f8829dfe1d436a754955d29d1030269d791a Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Thu, 21 Dec 2023 19:26:43 -0500 Subject: [PATCH 1207/1797] Fix needless quadratic loops I think this also could've been achieved by reordering the iterators, but using indexed Sets should be more clear --- .../transform/disagreggated_logarithmic.py | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py index 4e399d714f7..e22bba0215f 100644 --- a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py +++ b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py @@ -98,6 +98,18 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc # map index(P) -> corresponding vector in {0, 1}^n B[i] = self._get_binary_vector(i, log_dimension) + # Build up P_0 and P_plus ahead of time. + + # {P \in \mathcal{P} | B(P)_l = 0} + @transBlock.Set(transBlock.simplex_indices) + def P_0(l): + return [p for p in transBlock.simplex_indices if B[p][l] == 0] + + # {P \in \mathcal{P} | B(P)_l = 1} + @transBlock.Set(transBlock.simplex_indices) + def P_0(l): + return [p for p in transBlock.simplex_indices if B[p][l] == 1] + # The lambda variables \lambda_{P,v} are indexed by the simplex and the point in it transBlock.lambdas = Var( transBlock.simplex_indices, transBlock.simplex_point_indices, bounds=(0, 1) @@ -116,14 +128,14 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc == 1 ) - # The branching rules, establishing using the binaries that only one simplex's lambdas - # may be nonzero + # The branching rules, establishing using the binaries that only one simplex's lambda + # coefficients may be nonzero @transBlock.Constraint(transBlock.log_simplex_indices) # (6c.1) def simplex_choice_1(b, l): return ( sum( transBlock.lambdas[P, v] - for P in self._P_plus(B, l, transBlock.simplex_indices) + for P in transBlock.P_plus[l] for v in transBlock.simplex_point_indices ) <= transBlock.binaries[l] @@ -134,7 +146,7 @@ def simplex_choice_2(b, l): return ( sum( transBlock.lambdas[P, v] - for P in self._P_0(B, l, transBlock.simplex_indices) + for P in transBlock.P_0[l] for v in transBlock.simplex_point_indices ) <= 1 - transBlock.binaries[l] @@ -174,11 +186,3 @@ def _get_binary_vector(self, num, length): # Use python's string formatting instead of bothering with modular # arithmetic. Hopefully not slow. return tuple(int(x) for x in format(num, f"0{length}b")) - - # Return {P \in \mathcal{P} | B(P)_l = 0} - def _P_0(self, B, l, simplex_indices): - return [p for p in simplex_indices if B[p][l] == 0] - - # Return {P \in \mathcal{P} | B(P)_l = 1} - def _P_plus(self, B, l, simplex_indices): - return [p for p in simplex_indices if B[p][l] == 1] From 1248806195707f1a27e49ecc3dd9b1b1e2e9c794 Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Thu, 21 Dec 2023 20:41:43 -0500 Subject: [PATCH 1208/1797] fix scoping error --- .../piecewise/transform/nested_inner_repn.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pyomo/contrib/piecewise/transform/nested_inner_repn.py b/pyomo/contrib/piecewise/transform/nested_inner_repn.py index 6ee0e6c9e80..143bd827c58 100644 --- a/pyomo/contrib/piecewise/transform/nested_inner_repn.py +++ b/pyomo/contrib/piecewise/transform/nested_inner_repn.py @@ -52,8 +52,8 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc substitute_var = transBlock.substitute_var = Var() pw_linear_func.map_transformation_var(pw_expr, substitute_var) - substitute_var_lb = float("inf") - substitute_var_ub = -float("inf") + transBlock.substitute_var_lb = float("inf") + transBlock.substitute_var_ub = -float("inf") choices = list(zip(pw_linear_func._simplices, pw_linear_func._linear_functions)) @@ -66,7 +66,7 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc transBlock.set_substitute = Constraint( expr=substitute_var == linear_func_expr ) - (substitute_var_lb, substitute_var_ub) = compute_bounds_on_expr( + (transBlock.substitute_var_lb, transBlock.substitute_var_ub) = compute_bounds_on_expr( linear_func_expr ) else: @@ -76,10 +76,10 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc ) # Set bounds as determined when setting up the disjunction - if substitute_var_lb < float("inf"): - transBlock.substitute_var.setlb(substitute_var_lb) - if substitute_var_ub > -float("inf"): - transBlock.substitute_var.setub(substitute_var_ub) + if transBlock.substitute_var_lb < float("inf"): + transBlock.substitute_var.setlb(transBlock.substitute_var_lb) + if transBlock.substitute_var_ub > -float("inf"): + transBlock.substitute_var.setub(transBlock.substitute_var_ub) return substitute_var @@ -178,10 +178,10 @@ def _set_disjunct_block_constraints( # Widen the variable bounds to those of this linear func expression (lb, ub) = compute_bounds_on_expr(linear_func_expr) - if lb is not None and lb < substitute_var_lb: - substitute_var_lb = lb - if ub is not None and ub > substitute_var_ub: - substitute_var_ub = ub + if lb is not None and lb < root_block.substitute_var_lb: + root_block.substitute_var_lb = lb + if ub is not None and ub > root_block.substitute_var_ub: + root_block.substitute_var_ub = ub # Constrain x = \sum \lambda_i v_i @b.Constraint(range(pw_expr.nargs())) # dimension From 18821c7a4db6934705f6d7dc4ba463880e583137 Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Fri, 22 Dec 2023 13:55:48 -0500 Subject: [PATCH 1209/1797] proper testing for nested_inner_repn_gdp --- .../tests/test_nested_inner_repn_gdp.py | 169 +++++++++++++++++- 1 file changed, 167 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py b/pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py index fd8e7ab201c..4deeb9abe78 100644 --- a/pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py +++ b/pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py @@ -13,10 +13,175 @@ from pyomo.contrib.piecewise.tests import models import pyomo.contrib.piecewise.tests.common_tests as ct from pyomo.core.base import TransformationFactory -from pyomo.environ import SolverFactory - +from pyomo.environ import SolverFactory, Var, Constraint +from pyomo.gdp import Disjunction, Disjunct +from pyomo.core.expr.compare import assertExpressionsEqual +# Test the nested inner repn gdp model using the common_tests code class TestTransformPiecewiseModelToNestedInnerRepnGDP(unittest.TestCase): + + # Check one disjunct for proper contents. Disjunct structure should be + # identical to the version for the inner representation gdp + def check_log_disjunct(self, d, pts, f, substitute_var, x): + self.assertEqual(len(d.component_map(Constraint)), 3) + # lambdas and indicator_var + self.assertEqual(len(d.component_map(Var)), 2) + self.assertIsInstance(d.lambdas, Var) + self.assertEqual(len(d.lambdas), 2) + for lamb in d.lambdas.values(): + self.assertEqual(lamb.lb, 0) + self.assertEqual(lamb.ub, 1) + self.assertIsInstance(d.convex_combo, Constraint) + assertExpressionsEqual( + self, d.convex_combo.expr, d.lambdas[0] + d.lambdas[1] == 1 + ) + self.assertIsInstance(d.set_substitute, Constraint) + assertExpressionsEqual( + self, d.set_substitute.expr, substitute_var == f(x), places=7 + ) + self.assertIsInstance(d.linear_combo, Constraint) + self.assertEqual(len(d.linear_combo), 1) + assertExpressionsEqual( + self, + d.linear_combo[0].expr, + x == pts[0] * d.lambdas[0] + pts[1] * d.lambdas[1], + ) + + # Check one disjunct from the paraboloid block for proper contents. This should + # be identical to the inner_representation_gdp one + def check_paraboloid_disjunct(self, d, pts, f, substitute_var, x1, x2): + self.assertEqual(len(d.component_map(Constraint)), 3) + # lambdas and indicator_var + self.assertEqual(len(d.component_map(Var)), 2) + self.assertIsInstance(d.lambdas, Var) + self.assertEqual(len(d.lambdas), 3) + for lamb in d.lambdas.values(): + self.assertEqual(lamb.lb, 0) + self.assertEqual(lamb.ub, 1) + self.assertIsInstance(d.convex_combo, Constraint) + assertExpressionsEqual( + self, d.convex_combo.expr, d.lambdas[0] + d.lambdas[1] + d.lambdas[2] == 1 + ) + self.assertIsInstance(d.set_substitute, Constraint) + assertExpressionsEqual( + self, d.set_substitute.expr, substitute_var == f(x1, x2), places=7 + ) + self.assertIsInstance(d.linear_combo, Constraint) + self.assertEqual(len(d.linear_combo), 2) + assertExpressionsEqual( + self, + d.linear_combo[0].expr, + x1 + == pts[0][0] * d.lambdas[0] + + pts[1][0] * d.lambdas[1] + + pts[2][0] * d.lambdas[2], + ) + assertExpressionsEqual( + self, + d.linear_combo[1].expr, + x2 + == pts[0][1] * d.lambdas[0] + + pts[1][1] * d.lambdas[1] + + pts[2][1] * d.lambdas[2], + ) + + + # Check the structure of the log PWLF Block + def check_pw_log(self, m): + z = m.pw_log.get_transformation_var(m.log_expr) + self.assertIsInstance(z, Var) + # Now we can use those Vars to check on what the transformation created + log_block = z.parent_block() + + # Not using ct.check_trans_block_structure() because these are slightly + # different + # Two top-level disjuncts + self.assertEqual(len(log_block.component_map(Disjunct)), 2) + # One disjunction + self.assertEqual(len(log_block.component_map(Disjunction)), 1) + # The 'z' var (that we will substitute in for the function being + # approximated) is here: + self.assertEqual(len(log_block.component_map(Var)), 1) + self.assertIsInstance(log_block.substitute_var, Var) + + # Check the tree structure, which should be heavier on the right + # Parent disjunction + self.assertIsInstance(log_block.disj, Disjunction) + self.assertEqual(len(log_block.disj.disjuncts), 2) + + # Left disjunct with constraints + self.assertIsInstance(log_block.d_l, Disjunct) + self.check_log_disjunct(log_block.d_l, (1, 3), m.f1, log_block.substitute_var, m.x) + + # Right disjunct with disjunction + self.assertIsInstance(log_block.d_r, Disjunct) + self.assertIsInstance(log_block.d_r.inner_disjunction_r, Disjunction) + self.assertEqual(len(log_block.d_r.inner_disjunction_r.disjuncts), 2) + + # Left and right child disjuncts with constraints + self.assertIsInstance(log_block.d_r.d_l, Disjunct) + self.check_log_disjunct(log_block.d_r.d_l, (3, 6), m.f2, log_block.substitute_var, m.x) + self.assertIsInstance(log_block.d_r.d_r, Disjunct) + self.check_log_disjunct(log_block.d_r.d_r, (6, 10), m.f3, log_block.substitute_var, m.x) + + # Check that this also became the objective + self.assertIs(m.obj.expr.expr, log_block.substitute_var) + + # Check the structure of the paraboloid PWLF block + def check_pw_paraboloid(self, m): + z = m.pw_paraboloid.get_transformation_var(m.paraboloid_expr) + self.assertIsInstance(z, Var) + paraboloid_block = z.parent_block() + + # Two top-level disjuncts + self.assertEqual(len(paraboloid_block.component_map(Disjunct)), 2) + # One disjunction + self.assertEqual(len(paraboloid_block.component_map(Disjunction)), 1) + # The 'z' var (that we will substitute in for the function being + # approximated) is here: + self.assertEqual(len(paraboloid_block.component_map(Var)), 1) + self.assertIsInstance(paraboloid_block.substitute_var, Var) + + # This one should have an even tree with four leaf disjuncts + disjuncts_dict = { + paraboloid_block.d_l.d_l: ([(0, 1), (0, 4), (3, 4)], m.g1), + paraboloid_block.d_l.d_r: ([(0, 1), (3, 4), (3, 1)], m.g1), + paraboloid_block.d_r.d_l: ([(3, 4), (3, 7), (0, 7)], m.g2), + paraboloid_block.d_r.d_r: ([(0, 7), (0, 4), (3, 4)], m.g2), + } + for d, (pts, f) in disjuncts_dict.items(): + self.check_paraboloid_disjunct( + d, pts, f, paraboloid_block.substitute_var, m.x1, m.x2 + ) + + # And check the substitute Var is in the objective now. + self.assertIs(m.indexed_c[0].body.args[0].expr, paraboloid_block.substitute_var) + + # Test methods using the common_tests.py code. Copied in from test_inner_repn_gdp.py. + def test_transformation_do_not_descend(self): + ct.check_transformation_do_not_descend(self, 'contrib.piecewise.nested_inner_repn_gdp') + + def test_transformation_PiecewiseLinearFunction_targets(self): + ct.check_transformation_PiecewiseLinearFunction_targets( + self, 'contrib.piecewise.nested_inner_repn_gdp' + ) + + def test_descend_into_expressions(self): + ct.check_descend_into_expressions(self, 'contrib.piecewise.nested_inner_repn_gdp') + + def test_descend_into_expressions_constraint_target(self): + ct.check_descend_into_expressions_constraint_target( + self, 'contrib.piecewise.nested_inner_repn_gdp' + ) + + def test_descend_into_expressions_objective_target(self): + ct.check_descend_into_expressions_objective_target( + self, 'contrib.piecewise.nested_inner_repn_gdp' + ) + + # Check the solution of the log(x) model + @unittest.skipUnless(SolverFactory('gurobi').available(), 'Gurobi is not available') + @unittest.skipUnless(SolverFactory('gurobi').license_is_valid(), 'No license') def test_solve_log_model(self): m = models.make_log_x_model() TransformationFactory("contrib.piecewise.nested_inner_repn_gdp").apply_to(m) From d1a92e0789ec6c9bf217ddbae41b428b782aeb6f Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Fri, 22 Dec 2023 13:57:42 -0500 Subject: [PATCH 1210/1797] apply black --- .../tests/test_nested_inner_repn_gdp.py | 27 ++++++++++++------- .../transform/disagreggated_logarithmic.py | 6 ++--- .../piecewise/transform/nested_inner_repn.py | 17 +++++++----- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py b/pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py index 4deeb9abe78..8e8e4530d2c 100644 --- a/pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py +++ b/pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py @@ -17,10 +17,10 @@ from pyomo.gdp import Disjunction, Disjunct from pyomo.core.expr.compare import assertExpressionsEqual + # Test the nested inner repn gdp model using the common_tests code class TestTransformPiecewiseModelToNestedInnerRepnGDP(unittest.TestCase): - - # Check one disjunct for proper contents. Disjunct structure should be + # Check one disjunct for proper contents. Disjunct structure should be # identical to the version for the inner representation gdp def check_log_disjunct(self, d, pts, f, substitute_var, x): self.assertEqual(len(d.component_map(Constraint)), 3) @@ -85,7 +85,6 @@ def check_paraboloid_disjunct(self, d, pts, f, substitute_var, x1, x2): + pts[2][1] * d.lambdas[2], ) - # Check the structure of the log PWLF Block def check_pw_log(self, m): z = m.pw_log.get_transformation_var(m.log_expr) @@ -111,7 +110,9 @@ def check_pw_log(self, m): # Left disjunct with constraints self.assertIsInstance(log_block.d_l, Disjunct) - self.check_log_disjunct(log_block.d_l, (1, 3), m.f1, log_block.substitute_var, m.x) + self.check_log_disjunct( + log_block.d_l, (1, 3), m.f1, log_block.substitute_var, m.x + ) # Right disjunct with disjunction self.assertIsInstance(log_block.d_r, Disjunct) @@ -120,9 +121,13 @@ def check_pw_log(self, m): # Left and right child disjuncts with constraints self.assertIsInstance(log_block.d_r.d_l, Disjunct) - self.check_log_disjunct(log_block.d_r.d_l, (3, 6), m.f2, log_block.substitute_var, m.x) + self.check_log_disjunct( + log_block.d_r.d_l, (3, 6), m.f2, log_block.substitute_var, m.x + ) self.assertIsInstance(log_block.d_r.d_r, Disjunct) - self.check_log_disjunct(log_block.d_r.d_r, (6, 10), m.f3, log_block.substitute_var, m.x) + self.check_log_disjunct( + log_block.d_r.d_r, (6, 10), m.f3, log_block.substitute_var, m.x + ) # Check that this also became the objective self.assertIs(m.obj.expr.expr, log_block.substitute_var) @@ -156,10 +161,12 @@ def check_pw_paraboloid(self, m): # And check the substitute Var is in the objective now. self.assertIs(m.indexed_c[0].body.args[0].expr, paraboloid_block.substitute_var) - + # Test methods using the common_tests.py code. Copied in from test_inner_repn_gdp.py. def test_transformation_do_not_descend(self): - ct.check_transformation_do_not_descend(self, 'contrib.piecewise.nested_inner_repn_gdp') + ct.check_transformation_do_not_descend( + self, 'contrib.piecewise.nested_inner_repn_gdp' + ) def test_transformation_PiecewiseLinearFunction_targets(self): ct.check_transformation_PiecewiseLinearFunction_targets( @@ -167,7 +174,9 @@ def test_transformation_PiecewiseLinearFunction_targets(self): ) def test_descend_into_expressions(self): - ct.check_descend_into_expressions(self, 'contrib.piecewise.nested_inner_repn_gdp') + ct.check_descend_into_expressions( + self, 'contrib.piecewise.nested_inner_repn_gdp' + ) def test_descend_into_expressions_constraint_target(self): ct.check_descend_into_expressions_constraint_target( diff --git a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py index e22bba0215f..b54be3d0b75 100644 --- a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py +++ b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py @@ -60,14 +60,12 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc simplices = pw_linear_func._simplices num_simplices = len(simplices) transBlock.simplex_indices = RangeSet(0, num_simplices - 1) - # Assumption: the simplices are really full-dimensional simplices and all have the + # Assumption: the simplices are really full-dimensional simplices and all have the # same number of points, which is dimension + 1 transBlock.simplex_point_indices = RangeSet(0, dimension) # Enumeration of simplices: map from simplex number to simplex object - idx_to_simplex = { - k: v for k, v in zip(transBlock.simplex_indices, simplices) - } + idx_to_simplex = {k: v for k, v in zip(transBlock.simplex_indices, simplices)} # List of tuples of simplex indices with their linear function simplex_indices_and_lin_funcs = list( diff --git a/pyomo/contrib/piecewise/transform/nested_inner_repn.py b/pyomo/contrib/piecewise/transform/nested_inner_repn.py index 143bd827c58..97bfd9316f4 100644 --- a/pyomo/contrib/piecewise/transform/nested_inner_repn.py +++ b/pyomo/contrib/piecewise/transform/nested_inner_repn.py @@ -25,8 +25,8 @@ class NestedInnerRepresentationGDPTransformation(PiecewiseLinearTransformationBa of extreme points, with multipliers "local" to that particular polytope, i.e., not shared with neighbors. This method of formulating the piecewise linear function imposes no restrictions on the family of polytopes. Note - that this is NOT a logarithmic formulation - it has linearly many Boolean - variables. However, it is inspired by the disaggregated logarithmic + that this is NOT a logarithmic formulation - it has linearly many Boolean + variables. However, it is inspired by the disaggregated logarithmic formulation of [1]. Up to variable substitution, the amount of Boolean variables is logarithmic, as in [1]. @@ -66,9 +66,10 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc transBlock.set_substitute = Constraint( expr=substitute_var == linear_func_expr ) - (transBlock.substitute_var_lb, transBlock.substitute_var_ub) = compute_bounds_on_expr( - linear_func_expr - ) + ( + transBlock.substitute_var_lb, + transBlock.substitute_var_ub, + ) = compute_bounds_on_expr(linear_func_expr) else: # Add the disjunction transBlock.disj = self._get_disjunction( @@ -86,7 +87,9 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc # Recursively form the Disjunctions and Disjuncts. This shouldn't blow up # the stack, since the whole point is that we'll only go logarithmically # many calls deep. - def _get_disjunction(self, choices, parent_block, pw_expr, pw_linear_func, root_block): + def _get_disjunction( + self, choices, parent_block, pw_expr, pw_linear_func, root_block + ): size = len(choices) # Our base cases will be 3 and 2, since it would be silly to construct @@ -184,7 +187,7 @@ def _set_disjunct_block_constraints( root_block.substitute_var_ub = ub # Constrain x = \sum \lambda_i v_i - @b.Constraint(range(pw_expr.nargs())) # dimension + @b.Constraint(range(pw_expr.nargs())) # dimension def linear_combo(d, i): return pw_expr.args[i] == sum( d.lambdas[j] * pt[i] for j, pt in enumerate(extreme_pts) From 14aa6fcb6a065db500bcd47360cecb7e0659649a Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Fri, 22 Dec 2023 15:02:10 -0500 Subject: [PATCH 1211/1797] fix initialization bug --- .../piecewise/transform/disagreggated_logarithmic.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py index b54be3d0b75..b04f66584d2 100644 --- a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py +++ b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py @@ -1,7 +1,7 @@ from pyomo.contrib.piecewise.transform.piecewise_linear_transformation_base import ( PiecewiseLinearTransformationBase, ) -from pyomo.core import Constraint, Binary, Var, RangeSet +from pyomo.core import Constraint, Binary, Var, RangeSet, Set from pyomo.core.base import TransformationFactory from pyomo.common.errors import DeveloperError from math import ceil, log2 @@ -99,14 +99,14 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc # Build up P_0 and P_plus ahead of time. # {P \in \mathcal{P} | B(P)_l = 0} - @transBlock.Set(transBlock.simplex_indices) - def P_0(l): + def P_0_init(m, l): return [p for p in transBlock.simplex_indices if B[p][l] == 0] + transBlock.P_0 = Set(transBlock.log_simplex_indices, initialize=P_0_init) # {P \in \mathcal{P} | B(P)_l = 1} - @transBlock.Set(transBlock.simplex_indices) - def P_0(l): + def P_plus_init(m, l): return [p for p in transBlock.simplex_indices if B[p][l] == 1] + transBlock.P_plus = Set(transBlock.log_simplex_indices, initialize=P_plus_init) # The lambda variables \lambda_{P,v} are indexed by the simplex and the point in it transBlock.lambdas = Var( From 98ed11940aff5f7b0b82474712544d15d2f8a72f Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Thu, 15 Feb 2024 15:00:42 -0500 Subject: [PATCH 1212/1797] Fix up tests for disaggreggated logarithmic --- .../tests/test_disaggregated_logarithmic.py | 267 +++++++++++++++++- .../transform/disagreggated_logarithmic.py | 4 + 2 files changed, 269 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py b/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py index 8fd4bebfc37..cb77ec844fe 100644 --- a/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py +++ b/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py @@ -13,13 +13,276 @@ from pyomo.contrib.piecewise.tests import models import pyomo.contrib.piecewise.tests.common_tests as ct from pyomo.core.base import TransformationFactory -from pyomo.environ import SolverFactory +from pyomo.environ import SolverFactory, Var, Constraint +from pyomo.core.expr.compare import assertExpressionsEqual class TestTransformPiecewiseModelToNestedInnerRepnMIP(unittest.TestCase): + def check_pw_log(self, m): + z = m.pw_log.get_transformation_var(m.log_expr) + self.assertIsInstance(z, Var) + # Now we can use those Vars to check on what the transformation created + log_block = z.parent_block() + + # We should have three Vars, two of which are indexed, and five + # Constraints, three of which are indexed + + self.assertEqual(len(log_block.component_map(Var)), 3) + self.assertEqual(len(log_block.component_map(Constraint)), 5) + + # Constants + simplex_count = 3 + log_simplex_count = 2 + simplex_point_count = 2 + + # Substitute var + self.assertIsInstance(log_block.substitute_var, Var) + self.assertIs(m.obj.expr.expr, log_block.substitute_var) + # Binaries + self.assertIsInstance(log_block.binaries, Var) + self.assertEqual(len(log_block.binaries), log_simplex_count) + # Lambdas + self.assertIsInstance(log_block.lambdas, Var) + self.assertEqual(len(log_block.lambdas), simplex_count * simplex_point_count) + for l in log_block.lambdas.values(): + self.assertEqual(l.lb, 0) + self.assertEqual(l.ub, 1) + + # Convex combo constraint + self.assertIsInstance(log_block.convex_combo, Constraint) + assertExpressionsEqual( + self, + log_block.convex_combo.expr, + log_block.lambdas[0, 0] + + log_block.lambdas[0, 1] + + log_block.lambdas[1, 0] + + log_block.lambdas[1, 1] + + log_block.lambdas[2, 0] + + log_block.lambdas[2, 1] + == 1, + ) + + # Set substitute constraint + self.assertIsInstance(log_block.set_substitute, Constraint) + assertExpressionsEqual( + self, + log_block.set_substitute.expr, + log_block.substitute_var + == log_block.lambdas[0, 0] * m.f1(1) + + log_block.lambdas[0, 1] * m.f1(3) + + log_block.lambdas[1, 0] * m.f2(3) + + log_block.lambdas[1, 1] * m.f2(6) + + log_block.lambdas[2, 0] * m.f3(6) + + log_block.lambdas[2, 1] * m.f3(10), + places=7, + ) + + # x constraint + self.assertIsInstance(log_block.x_constraint, Constraint) + # one-dimensional case, so there is only one x variable here + self.assertEqual(len(log_block.x_constraint), 1) + assertExpressionsEqual( + self, + log_block.x_constraint[0].expr, + m.x + == 1 * log_block.lambdas[0, 0] + + 3 * log_block.lambdas[0, 1] + + 3 * log_block.lambdas[1, 0] + + 6 * log_block.lambdas[1, 1] + + 6 * log_block.lambdas[2, 0] + + 10 * log_block.lambdas[2, 1], + ) + + # simplex choice 1 constraint enables lambdas when binaries are on + self.assertEqual(len(log_block.simplex_choice_1), log_simplex_count) + assertExpressionsEqual( + self, + log_block.simplex_choice_1[0].expr, + log_block.lambdas[2, 0] + log_block.lambdas[2, 1] <= log_block.binaries[0], + ) + assertExpressionsEqual( + self, + log_block.simplex_choice_1[1].expr, + log_block.lambdas[1, 0] + log_block.lambdas[1, 1] <= log_block.binaries[1], + ) + # simplex choice 2 constraint enables lambdas when binaries are off + self.assertEqual(len(log_block.simplex_choice_2), log_simplex_count) + assertExpressionsEqual( + self, + log_block.simplex_choice_2[0].expr, + log_block.lambdas[0, 0] + + log_block.lambdas[0, 1] + + log_block.lambdas[1, 0] + + log_block.lambdas[1, 1] + <= 1 - log_block.binaries[0], + ) + assertExpressionsEqual( + self, + log_block.simplex_choice_2[1].expr, + log_block.lambdas[0, 0] + + log_block.lambdas[0, 1] + + log_block.lambdas[2, 0] + + log_block.lambdas[2, 1] + <= 1 - log_block.binaries[1], + ) + + def check_pw_paraboloid(self, m): + # This is a little larger, but at least test that the right numbers of + # everything are created + z = m.pw_paraboloid.get_transformation_var(m.paraboloid_expr) + self.assertIsInstance(z, Var) + paraboloid_block = z.parent_block() + + self.assertEqual(len(paraboloid_block.component_map(Var)), 3) + self.assertEqual(len(paraboloid_block.component_map(Constraint)), 5) + + # Constants + simplex_count = 4 + log_simplex_count = 2 + simplex_point_count = 3 + + # Substitute var + self.assertIsInstance(paraboloid_block.substitute_var, Var) + # assertExpressionsEqual( + # self, + # m.indexed_c[0].expr, + # m.x >= paraboloid_block.substitute_var + # ) + # Binaries + self.assertIsInstance(paraboloid_block.binaries, Var) + self.assertEqual(len(paraboloid_block.binaries), log_simplex_count) + # Lambdas + self.assertIsInstance(paraboloid_block.lambdas, Var) + # print(f"the lambdas are: {paraboloid_block.lambdas.pprint()}") + self.assertEqual( + len(paraboloid_block.lambdas), simplex_count * simplex_point_count + ) + for l in paraboloid_block.lambdas.values(): + self.assertEqual(l.lb, 0) + self.assertEqual(l.ub, 1) + + # Convex combo constraint + self.assertIsInstance(paraboloid_block.convex_combo, Constraint) + assertExpressionsEqual( + self, + paraboloid_block.convex_combo.expr, + paraboloid_block.lambdas[0, 0] + + paraboloid_block.lambdas[0, 1] + + paraboloid_block.lambdas[0, 2] + + paraboloid_block.lambdas[1, 0] + + paraboloid_block.lambdas[1, 1] + + paraboloid_block.lambdas[1, 2] + + paraboloid_block.lambdas[2, 0] + + paraboloid_block.lambdas[2, 1] + + paraboloid_block.lambdas[2, 2] + + paraboloid_block.lambdas[3, 0] + + paraboloid_block.lambdas[3, 1] + + paraboloid_block.lambdas[3, 2] + == 1, + ) + + # Set substitute constraint + self.assertIsInstance(paraboloid_block.set_substitute, Constraint) + assertExpressionsEqual( + self, + paraboloid_block.set_substitute.expr, + paraboloid_block.substitute_var + == paraboloid_block.lambdas[0, 0] * m.g1(0, 1) + + paraboloid_block.lambdas[0, 1] * m.g1(0, 4) + + paraboloid_block.lambdas[0, 2] * m.g1(3, 4) + + paraboloid_block.lambdas[1, 0] * m.g1(0, 1) + + paraboloid_block.lambdas[1, 1] * m.g1(3, 4) + + paraboloid_block.lambdas[1, 2] * m.g1(3, 1) + + paraboloid_block.lambdas[2, 0] * m.g2(3, 4) + + paraboloid_block.lambdas[2, 1] * m.g2(3, 7) + + paraboloid_block.lambdas[2, 2] * m.g2(0, 7) + + paraboloid_block.lambdas[3, 0] * m.g2(0, 7) + + paraboloid_block.lambdas[3, 1] * m.g2(0, 4) + + paraboloid_block.lambdas[3, 2] * m.g2(3, 4), + places=7, + ) + + # x constraint + self.assertIsInstance(paraboloid_block.x_constraint, Constraint) + # Here we have two x variables + self.assertEqual(len(paraboloid_block.x_constraint), 2) + assertExpressionsEqual( + self, + paraboloid_block.x_constraint[0].expr, + m.x1 + == 0 * paraboloid_block.lambdas[0, 0] + + 0 * paraboloid_block.lambdas[0, 1] + + 3 * paraboloid_block.lambdas[0, 2] + + 0 * paraboloid_block.lambdas[1, 0] + + 3 * paraboloid_block.lambdas[1, 1] + + 3 * paraboloid_block.lambdas[1, 2] + + 3 * paraboloid_block.lambdas[2, 0] + + 3 * paraboloid_block.lambdas[2, 1] + + 0 * paraboloid_block.lambdas[2, 2] + + 0 * paraboloid_block.lambdas[3, 0] + + 0 * paraboloid_block.lambdas[3, 1] + + 3 * paraboloid_block.lambdas[3, 2], + ) + assertExpressionsEqual( + self, + paraboloid_block.x_constraint[1].expr, + m.x2 + == 1 * paraboloid_block.lambdas[0, 0] + + 4 * paraboloid_block.lambdas[0, 1] + + 4 * paraboloid_block.lambdas[0, 2] + + 1 * paraboloid_block.lambdas[1, 0] + + 4 * paraboloid_block.lambdas[1, 1] + + 1 * paraboloid_block.lambdas[1, 2] + + 4 * paraboloid_block.lambdas[2, 0] + + 7 * paraboloid_block.lambdas[2, 1] + + 7 * paraboloid_block.lambdas[2, 2] + + 7 * paraboloid_block.lambdas[3, 0] + + 4 * paraboloid_block.lambdas[3, 1] + + 4 * paraboloid_block.lambdas[3, 2], + ) + + # The choices will get long, so let's just assert we have enough + self.assertEqual(len(paraboloid_block.simplex_choice_1), log_simplex_count) + self.assertEqual(len(paraboloid_block.simplex_choice_2), log_simplex_count) + + # Test methods using the common_tests.py code. + def test_transformation_do_not_descend(self): + ct.check_transformation_do_not_descend( + self, 'contrib.piecewise.disaggregated_logarithmic' + ) + + def test_transformation_PiecewiseLinearFunction_targets(self): + ct.check_transformation_PiecewiseLinearFunction_targets( + self, 'contrib.piecewise.disaggregated_logarithmic' + ) + + def test_descend_into_expressions(self): + ct.check_descend_into_expressions( + self, 'contrib.piecewise.disaggregated_logarithmic' + ) + + def test_descend_into_expressions_constraint_target(self): + ct.check_descend_into_expressions_constraint_target( + self, 'contrib.piecewise.disaggregated_logarithmic' + ) + + def test_descend_into_expressions_objective_target(self): + ct.check_descend_into_expressions_objective_target( + self, 'contrib.piecewise.disaggregated_logarithmic' + ) + + # Check solution of the log(x) model + @unittest.skipUnless(SolverFactory('gurobi').available(), 'Gurobi is not available') + @unittest.skipUnless(SolverFactory('gurobi').license_is_valid(), 'No license') def test_solve_log_model(self): m = models.make_log_x_model() TransformationFactory("contrib.piecewise.disaggregated_logarithmic").apply_to(m) - TransformationFactory("gdp.bigm").apply_to(m) SolverFactory("gurobi").solve(m) ct.check_log_x_model_soln(self, m) + + @unittest.skipIf(True, reason="because") + def test_test(self): + m = models.make_log_x_model() + TransformationFactory("contrib.piecewise.disaggregated_logarithmic").apply_to(m) + m.pprint() + assert False diff --git a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py index b04f66584d2..009282aa310 100644 --- a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py +++ b/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py @@ -101,11 +101,13 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc # {P \in \mathcal{P} | B(P)_l = 0} def P_0_init(m, l): return [p for p in transBlock.simplex_indices if B[p][l] == 0] + transBlock.P_0 = Set(transBlock.log_simplex_indices, initialize=P_0_init) # {P \in \mathcal{P} | B(P)_l = 1} def P_plus_init(m, l): return [p for p in transBlock.simplex_indices if B[p][l] == 1] + transBlock.P_plus = Set(transBlock.log_simplex_indices, initialize=P_plus_init) # The lambda variables \lambda_{P,v} are indexed by the simplex and the point in it @@ -128,6 +130,7 @@ def P_plus_init(m, l): # The branching rules, establishing using the binaries that only one simplex's lambda # coefficients may be nonzero + # Enabling lambdas when binaries are on @transBlock.Constraint(transBlock.log_simplex_indices) # (6c.1) def simplex_choice_1(b, l): return ( @@ -139,6 +142,7 @@ def simplex_choice_1(b, l): <= transBlock.binaries[l] ) + # Disabling lambdas when binaries are on @transBlock.Constraint(transBlock.log_simplex_indices) # (6c.2) def simplex_choice_2(b, l): return ( From 1312af5dd0fb1c97b69dd9345999ddc134f2391f Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Thu, 15 Feb 2024 15:30:51 -0500 Subject: [PATCH 1213/1797] fix name typo --- pyomo/contrib/piecewise/__init__.py | 2 +- .../piecewise/tests/test_disaggregated_logarithmic.py | 7 ------- ...ggated_logarithmic.py => disaggreggated_logarithmic.py} | 0 3 files changed, 1 insertion(+), 8 deletions(-) rename pyomo/contrib/piecewise/transform/{disagreggated_logarithmic.py => disaggreggated_logarithmic.py} (100%) diff --git a/pyomo/contrib/piecewise/__init__.py b/pyomo/contrib/piecewise/__init__.py index de18e559a93..8a66af89bad 100644 --- a/pyomo/contrib/piecewise/__init__.py +++ b/pyomo/contrib/piecewise/__init__.py @@ -36,6 +36,6 @@ from pyomo.contrib.piecewise.transform.nested_inner_repn import ( NestedInnerRepresentationGDPTransformation, ) -from pyomo.contrib.piecewise.transform.disagreggated_logarithmic import ( +from pyomo.contrib.piecewise.transform.disaggreggated_logarithmic import ( DisaggregatedLogarithmicInnerMIPTransformation, ) diff --git a/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py b/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py index cb77ec844fe..49a30d0996c 100644 --- a/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py +++ b/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py @@ -279,10 +279,3 @@ def test_solve_log_model(self): TransformationFactory("contrib.piecewise.disaggregated_logarithmic").apply_to(m) SolverFactory("gurobi").solve(m) ct.check_log_x_model_soln(self, m) - - @unittest.skipIf(True, reason="because") - def test_test(self): - m = models.make_log_x_model() - TransformationFactory("contrib.piecewise.disaggregated_logarithmic").apply_to(m) - m.pprint() - assert False diff --git a/pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py b/pyomo/contrib/piecewise/transform/disaggreggated_logarithmic.py similarity index 100% rename from pyomo/contrib/piecewise/transform/disagreggated_logarithmic.py rename to pyomo/contrib/piecewise/transform/disaggreggated_logarithmic.py From b9a3e4426a101ce6483b549fe26e9f8dc32d00ac Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Thu, 15 Feb 2024 15:52:12 -0500 Subject: [PATCH 1214/1797] satisfy black? --- pyomo/contrib/piecewise/transform/nested_inner_repn.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/piecewise/transform/nested_inner_repn.py b/pyomo/contrib/piecewise/transform/nested_inner_repn.py index 97bfd9316f4..b273c9776a2 100644 --- a/pyomo/contrib/piecewise/transform/nested_inner_repn.py +++ b/pyomo/contrib/piecewise/transform/nested_inner_repn.py @@ -66,10 +66,9 @@ def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_bloc transBlock.set_substitute = Constraint( expr=substitute_var == linear_func_expr ) - ( - transBlock.substitute_var_lb, - transBlock.substitute_var_ub, - ) = compute_bounds_on_expr(linear_func_expr) + (transBlock.substitute_var_lb, transBlock.substitute_var_ub) = ( + compute_bounds_on_expr(linear_func_expr) + ) else: # Add the disjunction transBlock.disj = self._get_disjunction( From 133d0ff3e7871d9cae5209c7577c947ba94b773e Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Tue, 20 Feb 2024 18:30:20 -0500 Subject: [PATCH 1215/1797] update and add copyright notices --- .../piecewise/tests/test_disaggregated_logarithmic.py | 2 +- .../piecewise/tests/test_nested_inner_repn_gdp.py | 2 +- .../piecewise/transform/disaggreggated_logarithmic.py | 11 +++++++++++ .../contrib/piecewise/transform/nested_inner_repn.py | 11 +++++++++++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py b/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py index 49a30d0996c..ab71679aac9 100644 --- a/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py +++ b/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# Copyright (c) 2008-2024 # National Technology and Engineering Solutions of Sandia, LLC # Under the terms 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_nested_inner_repn_gdp.py b/pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py index 8e8e4530d2c..e888db7eb72 100644 --- a/pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py +++ b/pyomo/contrib/piecewise/tests/test_nested_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/transform/disaggreggated_logarithmic.py b/pyomo/contrib/piecewise/transform/disaggreggated_logarithmic.py index 009282aa310..368b92d6424 100644 --- a/pyomo/contrib/piecewise/transform/disaggreggated_logarithmic.py +++ b/pyomo/contrib/piecewise/transform/disaggreggated_logarithmic.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.contrib.piecewise.transform.piecewise_linear_transformation_base import ( PiecewiseLinearTransformationBase, ) diff --git a/pyomo/contrib/piecewise/transform/nested_inner_repn.py b/pyomo/contrib/piecewise/transform/nested_inner_repn.py index b273c9776a2..dbbd8c73bad 100644 --- a/pyomo/contrib/piecewise/transform/nested_inner_repn.py +++ b/pyomo/contrib/piecewise/transform/nested_inner_repn.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.contrib.fbbt.fbbt import compute_bounds_on_expr from pyomo.contrib.piecewise.transform.piecewise_linear_transformation_base import ( PiecewiseLinearTransformationBase, From 423dbf8b4f8ef70e9bbec5af1c2cde7fd6f207aa Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Wed, 21 Feb 2024 20:12:14 -0500 Subject: [PATCH 1216/1797] rename and cleanup, try 2 --- pyomo/contrib/piecewise/__init__.py | 4 +- .../tests/test_disaggregated_logarithmic.py | 16 +- .../transform/disaggregated_logarithmic.py | 199 ++++++++++++++++++ 3 files changed, 209 insertions(+), 10 deletions(-) create mode 100644 pyomo/contrib/piecewise/transform/disaggregated_logarithmic.py diff --git a/pyomo/contrib/piecewise/__init__.py b/pyomo/contrib/piecewise/__init__.py index 8a66af89bad..b23200b3f7d 100644 --- a/pyomo/contrib/piecewise/__init__.py +++ b/pyomo/contrib/piecewise/__init__.py @@ -36,6 +36,6 @@ from pyomo.contrib.piecewise.transform.nested_inner_repn import ( NestedInnerRepresentationGDPTransformation, ) -from pyomo.contrib.piecewise.transform.disaggreggated_logarithmic import ( - DisaggregatedLogarithmicInnerMIPTransformation, +from pyomo.contrib.piecewise.transform.disaggregated_logarithmic import ( + DisaggregatedLogarithmicMIPTransformation, ) diff --git a/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py b/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py index ab71679aac9..2e0fa886361 100644 --- a/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py +++ b/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py @@ -69,10 +69,10 @@ def check_pw_log(self, m): log_block.set_substitute.expr, log_block.substitute_var == log_block.lambdas[0, 0] * m.f1(1) - + log_block.lambdas[0, 1] * m.f1(3) + log_block.lambdas[1, 0] * m.f2(3) - + log_block.lambdas[1, 1] * m.f2(6) + log_block.lambdas[2, 0] * m.f3(6) + + log_block.lambdas[0, 1] * m.f1(3) + + log_block.lambdas[1, 1] * m.f2(6) + log_block.lambdas[2, 1] * m.f3(10), places=7, ) @@ -188,16 +188,16 @@ def check_pw_paraboloid(self, m): paraboloid_block.set_substitute.expr, paraboloid_block.substitute_var == paraboloid_block.lambdas[0, 0] * m.g1(0, 1) - + paraboloid_block.lambdas[0, 1] * m.g1(0, 4) - + paraboloid_block.lambdas[0, 2] * m.g1(3, 4) + paraboloid_block.lambdas[1, 0] * m.g1(0, 1) - + paraboloid_block.lambdas[1, 1] * m.g1(3, 4) - + paraboloid_block.lambdas[1, 2] * m.g1(3, 1) + paraboloid_block.lambdas[2, 0] * m.g2(3, 4) - + paraboloid_block.lambdas[2, 1] * m.g2(3, 7) - + paraboloid_block.lambdas[2, 2] * m.g2(0, 7) + paraboloid_block.lambdas[3, 0] * m.g2(0, 7) + + paraboloid_block.lambdas[0, 1] * m.g1(0, 4) + + paraboloid_block.lambdas[1, 1] * m.g1(3, 4) + + paraboloid_block.lambdas[2, 1] * m.g2(3, 7) + paraboloid_block.lambdas[3, 1] * m.g2(0, 4) + + paraboloid_block.lambdas[0, 2] * m.g1(3, 4) + + paraboloid_block.lambdas[1, 2] * m.g1(3, 1) + + paraboloid_block.lambdas[2, 2] * m.g2(0, 7) + paraboloid_block.lambdas[3, 2] * m.g2(3, 4), places=7, ) diff --git a/pyomo/contrib/piecewise/transform/disaggregated_logarithmic.py b/pyomo/contrib/piecewise/transform/disaggregated_logarithmic.py new file mode 100644 index 00000000000..edb1a03afe6 --- /dev/null +++ b/pyomo/contrib/piecewise/transform/disaggregated_logarithmic.py @@ -0,0 +1,199 @@ +# ___________________________________________________________________________ +# +# 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.contrib.piecewise.transform.piecewise_linear_transformation_base import ( + PiecewiseLinearTransformationBase, +) +from pyomo.core import Constraint, Binary, Var, RangeSet, Set +from pyomo.core.base import TransformationFactory +from pyomo.common.errors import DeveloperError +from math import ceil, log2 + + +@TransformationFactory.register( + "contrib.piecewise.disaggregated_logarithmic", + doc=""" + Represent a piecewise linear function "logarithmically" by using a MIP with + log_2(|P|) binary decision variables. This is a direct-to-MIP transformation; + GDP is not used. + """, +) +class DisaggregatedLogarithmicMIPTransformation(PiecewiseLinearTransformationBase): + """ + Represent a piecewise linear function "logarithmically" by using a MIP with + log_2(|P|) binary decision variables, following the "disaggregated logarithmic" + method from [1]. This is a direct-to-MIP transformation; GDP is not used. + This method of logarithmically formulating the piecewise linear function + imposes no restrictions on the family of polytopes, but we assume we have + simplices in this code. + + References + ---------- + [1] J.P. Vielma, S. Ahmed, and G. Nemhauser, "Mixed-integer models + for nonseparable piecewise-linear optimization: unifying framework + and extensions," Operations Research, vol. 58, no. 2, pp. 305-315, + 2010. + """ + + CONFIG = PiecewiseLinearTransformationBase.CONFIG() + _transformation_name = "pw_linear_disaggregated_log" + + # Implement to use PiecewiseLinearTransformationBase. This function returns the Var + # that replaces the transformed piecewise linear expr + def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_block): + # Get a new Block for our transformation in transformation_block.transformed_functions, + # which is a Block(Any). This is where we will put our new components. + transBlock = transformation_block.transformed_functions[ + len(transformation_block.transformed_functions) + ] + + # Dimensionality of the PWLF + dimension = pw_expr.nargs() + transBlock.dimension_indices = RangeSet(0, dimension - 1) + + # Substitute Var that will hold the value of the PWLE + substitute_var = transBlock.substitute_var = Var() + pw_linear_func.map_transformation_var(pw_expr, substitute_var) + + # Bounds for the substitute_var that we will widen + substitute_var_lb = float("inf") + substitute_var_ub = -float("inf") + + # Simplices are tuples of indices of points. Give them their own indices, too + simplices = pw_linear_func._simplices + num_simplices = len(simplices) + transBlock.simplex_indices = RangeSet(0, num_simplices - 1) + # Assumption: the simplices are really full-dimensional simplices and all have the + # same number of points, which is dimension + 1 + transBlock.simplex_point_indices = RangeSet(0, dimension) + + # Enumeration of simplices: map from simplex number to simplex object + idx_to_simplex = {k: v for k, v in zip(transBlock.simplex_indices, simplices)} + + # List of tuples of simplex indices with their linear function + simplex_indices_and_lin_funcs = list( + zip(transBlock.simplex_indices, pw_linear_func._linear_functions) + ) + + # We don't seem to get a convenient opportunity later, so let's just widen + # the bounds here. All we need to do is go through the corners of each simplex. + for P, linear_func in simplex_indices_and_lin_funcs: + for v in transBlock.simplex_point_indices: + val = linear_func(*pw_linear_func._points[idx_to_simplex[P][v]]) + if val < substitute_var_lb: + substitute_var_lb = val + if val > substitute_var_ub: + substitute_var_ub = val + transBlock.substitute_var.setlb(substitute_var_lb) + transBlock.substitute_var.setub(substitute_var_ub) + + log_dimension = ceil(log2(num_simplices)) + transBlock.log_simplex_indices = RangeSet(0, log_dimension - 1) + transBlock.binaries = Var(transBlock.log_simplex_indices, domain=Binary) + + # Injective function B: \mathcal{P} -> {0,1}^ceil(log_2(|P|)) used to identify simplices + # (really just polytopes are required) with binary vectors. Any injective function + # is enough here. + B = {} + for i in transBlock.simplex_indices: + # map index(P) -> corresponding vector in {0, 1}^n + B[i] = self._get_binary_vector(i, log_dimension) + + # Build up P_0 and P_plus ahead of time. + + # {P \in \mathcal{P} | B(P)_l = 0} + def P_0_init(m, l): + return [p for p in transBlock.simplex_indices if B[p][l] == 0] + + transBlock.P_0 = Set(transBlock.log_simplex_indices, initialize=P_0_init) + + # {P \in \mathcal{P} | B(P)_l = 1} + def P_plus_init(m, l): + return [p for p in transBlock.simplex_indices if B[p][l] == 1] + + transBlock.P_plus = Set(transBlock.log_simplex_indices, initialize=P_plus_init) + + # The lambda variables \lambda_{P,v} are indexed by the simplex and the point in it + transBlock.lambdas = Var( + transBlock.simplex_indices, transBlock.simplex_point_indices, bounds=(0, 1) + ) + + # Numbered citations are from Vielma et al 2010, Mixed-Integer Models + # for Nonseparable Piecewise-Linear Optimization + + # Sum of all lambdas is one (6b) + transBlock.convex_combo = Constraint( + expr=sum( + transBlock.lambdas[P, v] + for P in transBlock.simplex_indices + for v in transBlock.simplex_point_indices + ) + == 1 + ) + + # The branching rules, establishing using the binaries that only one simplex's lambda + # coefficients may be nonzero + # Enabling lambdas when binaries are on + @transBlock.Constraint(transBlock.log_simplex_indices) # (6c.1) + def simplex_choice_1(b, l): + return ( + sum( + transBlock.lambdas[P, v] + for P in transBlock.P_plus[l] + for v in transBlock.simplex_point_indices + ) + <= transBlock.binaries[l] + ) + + # Disabling lambdas when binaries are on + @transBlock.Constraint(transBlock.log_simplex_indices) # (6c.2) + def simplex_choice_2(b, l): + return ( + sum( + transBlock.lambdas[P, v] + for P in transBlock.P_0[l] + for v in transBlock.simplex_point_indices + ) + <= 1 - transBlock.binaries[l] + ) + + # for i, (simplex, pwlf) in enumerate(choices): + # x_i = sum(lambda_P,v v_i, P in polytopes, v in V(P)) + @transBlock.Constraint(transBlock.dimension_indices) # (6a.1) + def x_constraint(b, i): + return pw_expr.args[i] == sum( + transBlock.lambdas[P, v] + * pw_linear_func._points[idx_to_simplex[P][v]][i] + for P in transBlock.simplex_indices + for v in transBlock.simplex_point_indices + ) + + # Make the substitute Var equal the PWLE (6a.2) + transBlock.set_substitute = Constraint( + expr=substitute_var + == sum( + transBlock.lambdas[P, v] + * linear_func(*pw_linear_func._points[idx_to_simplex[P][v]]) + for v in transBlock.simplex_point_indices + for (P, linear_func) in simplex_indices_and_lin_funcs + ) + ) + + return substitute_var + + # Not a Gray code, just a regular binary representation + # TODO test the Gray codes too + def _get_binary_vector(self, num, length): + if num != 0 and ceil(log2(num)) > length: + raise DeveloperError("Invalid input in _get_binary_vector") + # Use python's string formatting instead of bothering with modular + # arithmetic. Hopefully not slow. + return tuple(int(x) for x in format(num, f"0{length}b")) From 290be256363f2b1d98f382cfa79a8f542666f446 Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Wed, 21 Feb 2024 20:18:43 -0500 Subject: [PATCH 1217/1797] redo cleanup I managed to lose --- .../piecewise/tests/test_disaggregated_logarithmic.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py b/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py index 2e0fa886361..f848c610e9d 100644 --- a/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py +++ b/pyomo/contrib/piecewise/tests/test_disaggregated_logarithmic.py @@ -143,17 +143,11 @@ def check_pw_paraboloid(self, m): # Substitute var self.assertIsInstance(paraboloid_block.substitute_var, Var) - # assertExpressionsEqual( - # self, - # m.indexed_c[0].expr, - # m.x >= paraboloid_block.substitute_var - # ) # Binaries self.assertIsInstance(paraboloid_block.binaries, Var) self.assertEqual(len(paraboloid_block.binaries), log_simplex_count) # Lambdas self.assertIsInstance(paraboloid_block.lambdas, Var) - # print(f"the lambdas are: {paraboloid_block.lambdas.pprint()}") self.assertEqual( len(paraboloid_block.lambdas), simplex_count * simplex_point_count ) From dd6ed3e8a94939f68644f582c1c7e1d3bb53fd18 Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Wed, 21 Feb 2024 20:21:06 -0500 Subject: [PATCH 1218/1797] fix again something strange I did --- .../transform/disaggreggated_logarithmic.py | 201 ------------------ 1 file changed, 201 deletions(-) delete mode 100644 pyomo/contrib/piecewise/transform/disaggreggated_logarithmic.py diff --git a/pyomo/contrib/piecewise/transform/disaggreggated_logarithmic.py b/pyomo/contrib/piecewise/transform/disaggreggated_logarithmic.py deleted file mode 100644 index 368b92d6424..00000000000 --- a/pyomo/contrib/piecewise/transform/disaggreggated_logarithmic.py +++ /dev/null @@ -1,201 +0,0 @@ -# ___________________________________________________________________________ -# -# 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.contrib.piecewise.transform.piecewise_linear_transformation_base import ( - PiecewiseLinearTransformationBase, -) -from pyomo.core import Constraint, Binary, Var, RangeSet, Set -from pyomo.core.base import TransformationFactory -from pyomo.common.errors import DeveloperError -from math import ceil, log2 - - -@TransformationFactory.register( - "contrib.piecewise.disaggregated_logarithmic", - doc=""" - Represent a piecewise linear function "logarithmically" by using a MIP with - log_2(|P|) binary decision variables. This is a direct-to-MIP transformation; - GDP is not used. - """, -) -class DisaggregatedLogarithmicInnerMIPTransformation(PiecewiseLinearTransformationBase): - """ - Represent a piecewise linear function "logarithmically" by using a MIP with - log_2(|P|) binary decision variables, following the "disaggregated logarithmic" - method from [1]. This is a direct-to-MIP transformation; GDP is not used. - This method of logarithmically formulating the piecewise linear function - imposes no restrictions on the family of polytopes, but we assume we have - simplices in this code. - - References - ---------- - [1] J.P. Vielma, S. Ahmed, and G. Nemhauser, "Mixed-integer models - for nonseparable piecewise-linear optimization: unifying framework - and extensions," Operations Research, vol. 58, no. 2, pp. 305-315, - 2010. - """ - - CONFIG = PiecewiseLinearTransformationBase.CONFIG() - _transformation_name = "pw_linear_disaggregated_log" - - # Implement to use PiecewiseLinearTransformationBase. This function returns the Var - # that replaces the transformed piecewise linear expr - def _transform_pw_linear_expr(self, pw_expr, pw_linear_func, transformation_block): - # Get a new Block for our transformation in transformation_block.transformed_functions, - # which is a Block(Any). This is where we will put our new components. - transBlock = transformation_block.transformed_functions[ - len(transformation_block.transformed_functions) - ] - - # Dimensionality of the PWLF - dimension = pw_expr.nargs() - transBlock.dimension_indices = RangeSet(0, dimension - 1) - - # Substitute Var that will hold the value of the PWLE - substitute_var = transBlock.substitute_var = Var() - pw_linear_func.map_transformation_var(pw_expr, substitute_var) - - # Bounds for the substitute_var that we will widen - substitute_var_lb = float("inf") - substitute_var_ub = -float("inf") - - # Simplices are tuples of indices of points. Give them their own indices, too - simplices = pw_linear_func._simplices - num_simplices = len(simplices) - transBlock.simplex_indices = RangeSet(0, num_simplices - 1) - # Assumption: the simplices are really full-dimensional simplices and all have the - # same number of points, which is dimension + 1 - transBlock.simplex_point_indices = RangeSet(0, dimension) - - # Enumeration of simplices: map from simplex number to simplex object - idx_to_simplex = {k: v for k, v in zip(transBlock.simplex_indices, simplices)} - - # List of tuples of simplex indices with their linear function - simplex_indices_and_lin_funcs = list( - zip(transBlock.simplex_indices, pw_linear_func._linear_functions) - ) - - # We don't seem to get a convenient opportunity later, so let's just widen - # the bounds here. All we need to do is go through the corners of each simplex. - for P, linear_func in simplex_indices_and_lin_funcs: - for v in transBlock.simplex_point_indices: - val = linear_func(*pw_linear_func._points[idx_to_simplex[P][v]]) - if val < substitute_var_lb: - substitute_var_lb = val - if val > substitute_var_ub: - substitute_var_ub = val - transBlock.substitute_var.setlb(substitute_var_lb) - transBlock.substitute_var.setub(substitute_var_ub) - - log_dimension = ceil(log2(num_simplices)) - transBlock.log_simplex_indices = RangeSet(0, log_dimension - 1) - transBlock.binaries = Var(transBlock.log_simplex_indices, domain=Binary) - - # Injective function B: \mathcal{P} -> {0,1}^ceil(log_2(|P|)) used to identify simplices - # (really just polytopes are required) with binary vectors. Any injective function - # is enough here. - B = {} - for i in transBlock.simplex_indices: - # map index(P) -> corresponding vector in {0, 1}^n - B[i] = self._get_binary_vector(i, log_dimension) - - # Build up P_0 and P_plus ahead of time. - - # {P \in \mathcal{P} | B(P)_l = 0} - def P_0_init(m, l): - return [p for p in transBlock.simplex_indices if B[p][l] == 0] - - transBlock.P_0 = Set(transBlock.log_simplex_indices, initialize=P_0_init) - - # {P \in \mathcal{P} | B(P)_l = 1} - def P_plus_init(m, l): - return [p for p in transBlock.simplex_indices if B[p][l] == 1] - - transBlock.P_plus = Set(transBlock.log_simplex_indices, initialize=P_plus_init) - - # The lambda variables \lambda_{P,v} are indexed by the simplex and the point in it - transBlock.lambdas = Var( - transBlock.simplex_indices, transBlock.simplex_point_indices, bounds=(0, 1) - ) - - # Numbered citations are from Vielma et al 2010, Mixed-Integer Models - # for Nonseparable Piecewise-Linear Optimization - - # Sum of all lambdas is one (6b) - transBlock.convex_combo = Constraint( - expr=sum( - transBlock.lambdas[P, v] - for P in transBlock.simplex_indices - for v in transBlock.simplex_point_indices - ) - == 1 - ) - - # The branching rules, establishing using the binaries that only one simplex's lambda - # coefficients may be nonzero - # Enabling lambdas when binaries are on - @transBlock.Constraint(transBlock.log_simplex_indices) # (6c.1) - def simplex_choice_1(b, l): - return ( - sum( - transBlock.lambdas[P, v] - for P in transBlock.P_plus[l] - for v in transBlock.simplex_point_indices - ) - <= transBlock.binaries[l] - ) - - # Disabling lambdas when binaries are on - @transBlock.Constraint(transBlock.log_simplex_indices) # (6c.2) - def simplex_choice_2(b, l): - return ( - sum( - transBlock.lambdas[P, v] - for P in transBlock.P_0[l] - for v in transBlock.simplex_point_indices - ) - <= 1 - transBlock.binaries[l] - ) - - # for i, (simplex, pwlf) in enumerate(choices): - # x_i = sum(lambda_P,v v_i, P in polytopes, v in V(P)) - @transBlock.Constraint(transBlock.dimension_indices) # (6a.1) - def x_constraint(b, i): - return pw_expr.args[i] == sum( - transBlock.lambdas[P, v] - * pw_linear_func._points[idx_to_simplex[P][v]][i] - for P in transBlock.simplex_indices - for v in transBlock.simplex_point_indices - ) - - # Make the substitute Var equal the PWLE (6a.2) - transBlock.set_substitute = Constraint( - expr=substitute_var - == sum( - sum( - transBlock.lambdas[P, v] - * linear_func(*pw_linear_func._points[idx_to_simplex[P][v]]) - for v in transBlock.simplex_point_indices - ) - for (P, linear_func) in simplex_indices_and_lin_funcs - ) - ) - - return substitute_var - - # Not a Gray code, just a regular binary representation - # TODO test the Gray codes too - def _get_binary_vector(self, num, length): - if num != 0 and ceil(log2(num)) > length: - raise DeveloperError("Invalid input in _get_binary_vector") - # Use python's string formatting instead of bothering with modular - # arithmetic. Hopefully not slow. - return tuple(int(x) for x in format(num, f"0{length}b")) From 289915dcc11371a3f423b27adfb055824029b52b Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Thu, 22 Feb 2024 14:40:11 -0500 Subject: [PATCH 1219/1797] move duplicated methods into common file for inner-repn-GDP based transforms --- .../tests/common_inner_repn_tests.py | 80 ++++++++++++++++++ .../piecewise/tests/test_inner_repn_gdp.py | 70 ++-------------- .../tests/test_nested_inner_repn_gdp.py | 82 ++----------------- 3 files changed, 95 insertions(+), 137 deletions(-) create mode 100644 pyomo/contrib/piecewise/tests/common_inner_repn_tests.py diff --git a/pyomo/contrib/piecewise/tests/common_inner_repn_tests.py b/pyomo/contrib/piecewise/tests/common_inner_repn_tests.py new file mode 100644 index 00000000000..e0b8e878be3 --- /dev/null +++ b/pyomo/contrib/piecewise/tests/common_inner_repn_tests.py @@ -0,0 +1,80 @@ +# ___________________________________________________________________________ +# +# 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.core import Var +from pyomo.core.base import Constraint +from pyomo.core.expr.compare import assertExpressionsEqual + +# This file contains check methods shared between GDP inner representation-based +# transformations. Currently, those are the inner_representation_gdp and +# nested_inner_repn_gdp transformations, since each have disjuncts with the +# same structure. + + +# Check one disjunct from the log model for proper contents +def check_log_disjunct(test, d, pts, f, substitute_var, x): + test.assertEqual(len(d.component_map(Constraint)), 3) + # lambdas and indicator_var + test.assertEqual(len(d.component_map(Var)), 2) + test.assertIsInstance(d.lambdas, Var) + test.assertEqual(len(d.lambdas), 2) + for lamb in d.lambdas.values(): + test.assertEqual(lamb.lb, 0) + test.assertEqual(lamb.ub, 1) + test.assertIsInstance(d.convex_combo, Constraint) + assertExpressionsEqual(test, d.convex_combo.expr, d.lambdas[0] + d.lambdas[1] == 1) + test.assertIsInstance(d.set_substitute, Constraint) + assertExpressionsEqual( + test, d.set_substitute.expr, substitute_var == f(x), places=7 + ) + test.assertIsInstance(d.linear_combo, Constraint) + test.assertEqual(len(d.linear_combo), 1) + assertExpressionsEqual( + test, d.linear_combo[0].expr, x == pts[0] * d.lambdas[0] + pts[1] * d.lambdas[1] + ) + + +# Check one disjunct from the paraboloid model for proper contents. +def check_paraboloid_disjunct(test, d, pts, f, substitute_var, x1, x2): + test.assertEqual(len(d.component_map(Constraint)), 3) + # lambdas and indicator_var + test.assertEqual(len(d.component_map(Var)), 2) + test.assertIsInstance(d.lambdas, Var) + test.assertEqual(len(d.lambdas), 3) + for lamb in d.lambdas.values(): + test.assertEqual(lamb.lb, 0) + test.assertEqual(lamb.ub, 1) + test.assertIsInstance(d.convex_combo, Constraint) + assertExpressionsEqual( + test, d.convex_combo.expr, d.lambdas[0] + d.lambdas[1] + d.lambdas[2] == 1 + ) + test.assertIsInstance(d.set_substitute, Constraint) + assertExpressionsEqual( + test, d.set_substitute.expr, substitute_var == f(x1, x2), places=7 + ) + test.assertIsInstance(d.linear_combo, Constraint) + test.assertEqual(len(d.linear_combo), 2) + assertExpressionsEqual( + test, + d.linear_combo[0].expr, + x1 + == pts[0][0] * d.lambdas[0] + + pts[1][0] * d.lambdas[1] + + pts[2][0] * d.lambdas[2], + ) + assertExpressionsEqual( + test, + d.linear_combo[1].expr, + x2 + == pts[0][1] * d.lambdas[0] + + pts[1][1] * d.lambdas[1] + + pts[2][1] * d.lambdas[2], + ) diff --git a/pyomo/contrib/piecewise/tests/test_inner_repn_gdp.py b/pyomo/contrib/piecewise/tests/test_inner_repn_gdp.py index 27fe43e54d5..e7505bb92d3 100644 --- a/pyomo/contrib/piecewise/tests/test_inner_repn_gdp.py +++ b/pyomo/contrib/piecewise/tests/test_inner_repn_gdp.py @@ -12,6 +12,7 @@ import pyomo.common.unittest as unittest from pyomo.contrib.piecewise.tests import models import pyomo.contrib.piecewise.tests.common_tests as ct +import pyomo.contrib.piecewise.tests.common_inner_repn_tests as inner_repn_tests from pyomo.core.base import TransformationFactory from pyomo.core.expr.compare import ( assertExpressionsEqual, @@ -22,67 +23,6 @@ class TestTransformPiecewiseModelToInnerRepnGDP(unittest.TestCase): - def check_log_disjunct(self, d, pts, f, substitute_var, x): - self.assertEqual(len(d.component_map(Constraint)), 3) - # lambdas and indicator_var - self.assertEqual(len(d.component_map(Var)), 2) - self.assertIsInstance(d.lambdas, Var) - self.assertEqual(len(d.lambdas), 2) - for lamb in d.lambdas.values(): - self.assertEqual(lamb.lb, 0) - self.assertEqual(lamb.ub, 1) - self.assertIsInstance(d.convex_combo, Constraint) - assertExpressionsEqual( - self, d.convex_combo.expr, d.lambdas[0] + d.lambdas[1] == 1 - ) - self.assertIsInstance(d.set_substitute, Constraint) - assertExpressionsEqual( - self, d.set_substitute.expr, substitute_var == f(x), places=7 - ) - self.assertIsInstance(d.linear_combo, Constraint) - self.assertEqual(len(d.linear_combo), 1) - assertExpressionsEqual( - self, - d.linear_combo[0].expr, - x == pts[0] * d.lambdas[0] + pts[1] * d.lambdas[1], - ) - - def check_paraboloid_disjunct(self, d, pts, f, substitute_var, x1, x2): - self.assertEqual(len(d.component_map(Constraint)), 3) - # lambdas and indicator_var - self.assertEqual(len(d.component_map(Var)), 2) - self.assertIsInstance(d.lambdas, Var) - self.assertEqual(len(d.lambdas), 3) - for lamb in d.lambdas.values(): - self.assertEqual(lamb.lb, 0) - self.assertEqual(lamb.ub, 1) - self.assertIsInstance(d.convex_combo, Constraint) - assertExpressionsEqual( - self, d.convex_combo.expr, d.lambdas[0] + d.lambdas[1] + d.lambdas[2] == 1 - ) - self.assertIsInstance(d.set_substitute, Constraint) - assertExpressionsEqual( - self, d.set_substitute.expr, substitute_var == f(x1, x2), places=7 - ) - self.assertIsInstance(d.linear_combo, Constraint) - self.assertEqual(len(d.linear_combo), 2) - assertExpressionsEqual( - self, - d.linear_combo[0].expr, - x1 - == pts[0][0] * d.lambdas[0] - + pts[1][0] * d.lambdas[1] - + pts[2][0] * d.lambdas[2], - ) - assertExpressionsEqual( - self, - d.linear_combo[1].expr, - x2 - == pts[0][1] * d.lambdas[0] - + pts[1][1] * d.lambdas[1] - + pts[2][1] * d.lambdas[2], - ) - def check_pw_log(self, m): ## # Check the transformation of the approximation of log(x) @@ -101,7 +41,9 @@ def check_pw_log(self, m): log_block.disjuncts[2]: ((6, 10), m.f3), } for d, (pts, f) in disjuncts_dict.items(): - self.check_log_disjunct(d, pts, f, log_block.substitute_var, m.x) + inner_repn_tests.check_log_disjunct( + self, d, pts, f, log_block.substitute_var, m.x + ) # Check the Disjunction self.assertIsInstance(log_block.pick_a_piece, Disjunction) @@ -129,8 +71,8 @@ def check_pw_paraboloid(self, m): paraboloid_block.disjuncts[3]: ([(0, 7), (0, 4), (3, 4)], m.g2), } for d, (pts, f) in disjuncts_dict.items(): - self.check_paraboloid_disjunct( - d, pts, f, paraboloid_block.substitute_var, m.x1, m.x2 + inner_repn_tests.check_paraboloid_disjunct( + self, d, pts, f, paraboloid_block.substitute_var, m.x1, m.x2 ) # Check the Disjunction diff --git a/pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py b/pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py index e888db7eb72..2024f014f55 100644 --- a/pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py +++ b/pyomo/contrib/piecewise/tests/test_nested_inner_repn_gdp.py @@ -12,6 +12,7 @@ import pyomo.common.unittest as unittest from pyomo.contrib.piecewise.tests import models import pyomo.contrib.piecewise.tests.common_tests as ct +import pyomo.contrib.piecewise.tests.common_inner_repn_tests as inner_repn_tests from pyomo.core.base import TransformationFactory from pyomo.environ import SolverFactory, Var, Constraint from pyomo.gdp import Disjunction, Disjunct @@ -20,71 +21,6 @@ # Test the nested inner repn gdp model using the common_tests code class TestTransformPiecewiseModelToNestedInnerRepnGDP(unittest.TestCase): - # Check one disjunct for proper contents. Disjunct structure should be - # identical to the version for the inner representation gdp - def check_log_disjunct(self, d, pts, f, substitute_var, x): - self.assertEqual(len(d.component_map(Constraint)), 3) - # lambdas and indicator_var - self.assertEqual(len(d.component_map(Var)), 2) - self.assertIsInstance(d.lambdas, Var) - self.assertEqual(len(d.lambdas), 2) - for lamb in d.lambdas.values(): - self.assertEqual(lamb.lb, 0) - self.assertEqual(lamb.ub, 1) - self.assertIsInstance(d.convex_combo, Constraint) - assertExpressionsEqual( - self, d.convex_combo.expr, d.lambdas[0] + d.lambdas[1] == 1 - ) - self.assertIsInstance(d.set_substitute, Constraint) - assertExpressionsEqual( - self, d.set_substitute.expr, substitute_var == f(x), places=7 - ) - self.assertIsInstance(d.linear_combo, Constraint) - self.assertEqual(len(d.linear_combo), 1) - assertExpressionsEqual( - self, - d.linear_combo[0].expr, - x == pts[0] * d.lambdas[0] + pts[1] * d.lambdas[1], - ) - - # Check one disjunct from the paraboloid block for proper contents. This should - # be identical to the inner_representation_gdp one - def check_paraboloid_disjunct(self, d, pts, f, substitute_var, x1, x2): - self.assertEqual(len(d.component_map(Constraint)), 3) - # lambdas and indicator_var - self.assertEqual(len(d.component_map(Var)), 2) - self.assertIsInstance(d.lambdas, Var) - self.assertEqual(len(d.lambdas), 3) - for lamb in d.lambdas.values(): - self.assertEqual(lamb.lb, 0) - self.assertEqual(lamb.ub, 1) - self.assertIsInstance(d.convex_combo, Constraint) - assertExpressionsEqual( - self, d.convex_combo.expr, d.lambdas[0] + d.lambdas[1] + d.lambdas[2] == 1 - ) - self.assertIsInstance(d.set_substitute, Constraint) - assertExpressionsEqual( - self, d.set_substitute.expr, substitute_var == f(x1, x2), places=7 - ) - self.assertIsInstance(d.linear_combo, Constraint) - self.assertEqual(len(d.linear_combo), 2) - assertExpressionsEqual( - self, - d.linear_combo[0].expr, - x1 - == pts[0][0] * d.lambdas[0] - + pts[1][0] * d.lambdas[1] - + pts[2][0] * d.lambdas[2], - ) - assertExpressionsEqual( - self, - d.linear_combo[1].expr, - x2 - == pts[0][1] * d.lambdas[0] - + pts[1][1] * d.lambdas[1] - + pts[2][1] * d.lambdas[2], - ) - # Check the structure of the log PWLF Block def check_pw_log(self, m): z = m.pw_log.get_transformation_var(m.log_expr) @@ -110,8 +46,8 @@ def check_pw_log(self, m): # Left disjunct with constraints self.assertIsInstance(log_block.d_l, Disjunct) - self.check_log_disjunct( - log_block.d_l, (1, 3), m.f1, log_block.substitute_var, m.x + inner_repn_tests.check_log_disjunct( + self, log_block.d_l, (1, 3), m.f1, log_block.substitute_var, m.x ) # Right disjunct with disjunction @@ -121,12 +57,12 @@ def check_pw_log(self, m): # Left and right child disjuncts with constraints self.assertIsInstance(log_block.d_r.d_l, Disjunct) - self.check_log_disjunct( - log_block.d_r.d_l, (3, 6), m.f2, log_block.substitute_var, m.x + inner_repn_tests.check_log_disjunct( + self, log_block.d_r.d_l, (3, 6), m.f2, log_block.substitute_var, m.x ) self.assertIsInstance(log_block.d_r.d_r, Disjunct) - self.check_log_disjunct( - log_block.d_r.d_r, (6, 10), m.f3, log_block.substitute_var, m.x + inner_repn_tests.check_log_disjunct( + self, log_block.d_r.d_r, (6, 10), m.f3, log_block.substitute_var, m.x ) # Check that this also became the objective @@ -155,8 +91,8 @@ def check_pw_paraboloid(self, m): paraboloid_block.d_r.d_r: ([(0, 7), (0, 4), (3, 4)], m.g2), } for d, (pts, f) in disjuncts_dict.items(): - self.check_paraboloid_disjunct( - d, pts, f, paraboloid_block.substitute_var, m.x1, m.x2 + inner_repn_tests.check_paraboloid_disjunct( + self, d, pts, f, paraboloid_block.substitute_var, m.x1, m.x2 ) # And check the substitute Var is in the objective now. From fcddaf5a0a10503a73ecd1c30009eaefca6cee86 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 23 Feb 2024 08:02:57 -0700 Subject: [PATCH 1220/1797] 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 1221/1797] 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 1222/1797] 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 1223/1797] 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 1224/1797] 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 1225/1797] 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 1226/1797] 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 1227/1797] 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 1228/1797] 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 1229/1797] 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 1230/1797] 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 1231/1797] 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 1232/1797] 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 1233/1797] 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 1234/1797] 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 1235/1797] 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 1236/1797] 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 1237/1797] 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 1238/1797] 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 7d88fe4aee8a97e0e199e95c3f7e29064bccd3fa Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Mon, 26 Feb 2024 17:05:40 +0100 Subject: [PATCH 1239/1797] Added MAiNGO appsi-interface --- pyomo/contrib/appsi/solvers/__init__.py | 1 + pyomo/contrib/appsi/solvers/maingo.py | 653 ++++++++++++++++++++++++ 2 files changed, 654 insertions(+) create mode 100644 pyomo/contrib/appsi/solvers/maingo.py diff --git a/pyomo/contrib/appsi/solvers/__init__.py b/pyomo/contrib/appsi/solvers/__init__.py index c03523a69d4..c9e0a2a003d 100644 --- a/pyomo/contrib/appsi/solvers/__init__.py +++ b/pyomo/contrib/appsi/solvers/__init__.py @@ -15,3 +15,4 @@ from .cplex import Cplex from .highs import Highs from .wntr import Wntr, WntrResults +from .maingo import MAiNGO \ No newline at end of file diff --git a/pyomo/contrib/appsi/solvers/maingo.py b/pyomo/contrib/appsi/solvers/maingo.py new file mode 100644 index 00000000000..dcb8040eabe --- /dev/null +++ b/pyomo/contrib/appsi/solvers/maingo.py @@ -0,0 +1,653 @@ +from collections import namedtuple +import logging +import math +import sys +from typing import Optional, List, Dict + +from pyomo.contrib.appsi.base import ( + PersistentSolver, + Results, + TerminationCondition, + MIPSolverConfig, + PersistentBase, + PersistentSolutionLoader, +) +from pyomo.contrib.appsi.cmodel import cmodel, cmodel_available +from pyomo.common.collections import ComponentMap +from pyomo.common.config import ConfigValue, NonNegativeInt +from pyomo.common.dependencies import attempt_import +from pyomo.common.errors import PyomoException +from pyomo.common.log import LogStream +from pyomo.common.tee import capture_output, TeeStream +from pyomo.common.timing import HierarchicalTimer +from pyomo.core.base import SymbolMap, NumericLabeler, TextLabeler +from pyomo.core.base.constraint import _GeneralConstraintData +from pyomo.core.base.expression import ScalarExpression +from pyomo.core.base.param import _ParamData +from pyomo.core.base.sos import _SOSConstraintData +from pyomo.core.base.var import Var, _GeneralVarData +import pyomo.core.expr.expr_common as common +import pyomo.core.expr as EXPR +from pyomo.core.expr.numvalue import ( + value, + is_constant, + is_fixed, + native_numeric_types, + native_types, + nonpyomo_leaf_types, +) +from pyomo.core.kernel.objective import minimize, maximize +from pyomo.core.staleflag import StaleFlagManager +from pyomo.repn.util import valid_expr_ctypes_minlp + +_plusMinusOne = {-1, 1} + +MaingoVar = namedtuple("MaingoVar", "type name lb ub init") + +logger = logging.getLogger(__name__) + + +def _import_maingopy(): + try: + import maingopy + except ImportError: + MAiNGO._available = MAiNGO.Availability.NotFound + raise + return maingopy + + +maingopy, maingopy_available = attempt_import("maingopy", importer=_import_maingopy) + + +class MAiNGOConfig(MIPSolverConfig): + def __init__( + self, + description=None, + doc=None, + implicit=False, + implicit_domain=None, + visibility=0, + ): + super(MAiNGOConfig, self).__init__( + description=description, + doc=doc, + implicit=implicit, + implicit_domain=implicit_domain, + visibility=visibility, + ) + + self.declare("logfile", ConfigValue(domain=str)) + self.declare("solver_output_logger", ConfigValue()) + self.declare("log_level", ConfigValue(domain=NonNegativeInt)) + + self.logfile = "" + self.solver_output_logger = logger + self.log_level = logging.INFO + + +class MAiNGOSolutionLoader(PersistentSolutionLoader): + def load_vars(self, vars_to_load=None): + self._assert_solution_still_valid() + self._solver.load_vars(vars_to_load=vars_to_load) + + def get_primals(self, vars_to_load=None): + self._assert_solution_still_valid() + return self._solver.get_primals(vars_to_load=vars_to_load) + + +class MAiNGOResults(Results): + def __init__(self, solver): + super(MAiNGOResults, self).__init__() + self.wallclock_time = None + self.cpu_time = None + self.solution_loader = MAiNGOSolutionLoader(solver=solver) + + +class SolverModel(maingopy.MAiNGOmodel): + def __init__(self, var_list, objective, con_list, idmap): + maingopy.MAiNGOmodel.__init__(self) + self._var_list = var_list + self._con_list = con_list + self._objective = objective + self._idmap = idmap + + def build_maingo_objective(self, obj, visitor): + maingo_obj = visitor.dfs_postorder_stack(obj.expr) + if obj.sense == maximize: + maingo_obj *= -1 + return maingo_obj + + def build_maingo_constraints(self, cons, visitor): + eqs = [] + ineqs = [] + for con in cons: + if con.equality: + eqs += [visitor.dfs_postorder_stack(con.body - con.lower)] + elif con.has_ub() and con.has_lb(): + ineqs += [visitor.dfs_postorder_stack(con.body - con.upper)] + ineqs += [visitor.dfs_postorder_stack(con.lower - con.body)] + elif con.has_ub(): + ineqs += [visitor.dfs_postorder_stack(con.body - con.upper)] + elif con.has_ub(): + ineqs += [visitor.dfs_postorder_stack(con.lower - con.body)] + else: + raise ValueError( + "Constraint does not have a lower " + "or an upper bound: {0} \n".format(con) + ) + return eqs, ineqs + + def get_variables(self): + return [ + maingopy.OptimizationVariable( + maingopy.Bounds(var.lb, var.ub), var.type, var.name + ) + for var in self._var_list + ] + + def get_initial_point(self): + return [var.init if not var.init is None else var.lb for var in self._var_list] + + def evaluate(self, maingo_vars): + visitor = ToMAiNGOVisitor(maingo_vars, self._idmap) + result = maingopy.EvaluationContainer() + result.objective = self.build_maingo_objective(self._objective, visitor) + eqs, ineqs = self.build_maingo_constraints(self._con_list, visitor) + result.eq = eqs + result.ineq = ineqs + return result + + +LEFT_TO_RIGHT = common.OperatorAssociativity.LEFT_TO_RIGHT +RIGHT_TO_LEFT = common.OperatorAssociativity.RIGHT_TO_LEFT + + +class ToMAiNGOVisitor(EXPR.ExpressionValueVisitor): + def __init__(self, variables, idmap): + super(ToMAiNGOVisitor, self).__init__() + self.variables = variables + self.idmap = idmap + self._pyomo_func_to_maingo_func = { + "log": maingopy.log, + "log10": ToMAiNGOVisitor.maingo_log10, + "sin": maingopy.sin, + "cos": maingopy.cos, + "tan": maingopy.tan, + "cosh": maingopy.cosh, + "sinh": maingopy.sinh, + "tanh": maingopy.tanh, + "asin": maingopy.asin, + "acos": maingopy.acos, + "atan": maingopy.atan, + "exp": maingopy.exp, + "sqrt": maingopy.sqrt, + "asinh": ToMAiNGOVisitor.maingo_asinh, + "acosh": ToMAiNGOVisitor.maingo_acosh, + "atanh": ToMAiNGOVisitor.maingo_atanh, + } + + @classmethod + def maingo_log10(cls, x): + return maingopy.log(x) / math.log(10) + + @classmethod + def maingo_asinh(cls, x): + return maingopy.inv(maingopy.sinh(x)) + + @classmethod + def maingo_acosh(cls, x): + return maingopy.inv(maingopy.cosh(x)) + + @classmethod + def maingo_atanh(cls, x): + return maingopy.inv(maingopy.tanh(x)) + + def visit(self, node, values): + """Visit nodes that have been expanded""" + for i, val in enumerate(values): + arg = node._args_[i] + + if arg is None: + values[i] = "Undefined" + elif arg.__class__ in native_numeric_types: + pass + elif arg.__class__ in nonpyomo_leaf_types: + values[i] = val + else: + parens = False + if arg.is_expression_type() and node.PRECEDENCE is not None: + if arg.PRECEDENCE is None: + pass + elif node.PRECEDENCE < arg.PRECEDENCE: + parens = True + elif node.PRECEDENCE == arg.PRECEDENCE: + if i == 0: + parens = node.ASSOCIATIVITY != LEFT_TO_RIGHT + elif i == len(node._args_) - 1: + parens = node.ASSOCIATIVITY != RIGHT_TO_LEFT + else: + parens = True + if parens: + values[i] = val + + if node.__class__ in EXPR.NPV_expression_types: + return value(node) + + if node.__class__ in {EXPR.ProductExpression, EXPR.MonomialTermExpression}: + return values[0] * values[1] + + if node.__class__ in {EXPR.SumExpression}: + return sum(values) + + if node.__class__ in {EXPR.PowExpression}: + return maingopy.pow(values[0], values[1]) + + if node.__class__ in {EXPR.DivisionExpression}: + return values[0] / values[1] + + if node.__class__ in {EXPR.NegationExpression}: + return -values[0] + + if node.__class__ in {EXPR.AbsExpression}: + return maingopy.abs(values[0]) + + if node.__class__ in {EXPR.UnaryFunctionExpression}: + pyomo_func = node.getname() + maingo_func = self._pyomo_func_to_maingo_func[pyomo_func] + return maingo_func(values[0]) + + if node.__class__ in {ScalarExpression}: + return values[0] + + raise ValueError(f"Unknown function expression encountered: {node.getname()}") + + def visiting_potential_leaf(self, node): + """ + Visiting a potential leaf. + + Return True if the node is not expanded. + """ + if node.__class__ in native_types: + return True, node + + if node.is_expression_type(): + if node.__class__ is EXPR.MonomialTermExpression: + return True, self._monomial_to_maingo(node) + if node.__class__ is EXPR.LinearExpression: + return True, self._linear_to_maingo(node) + return False, None + + if node.is_component_type(): + if node.ctype not in valid_expr_ctypes_minlp: + # Make sure all components in active constraints + # are basic ctypes we know how to deal with. + raise RuntimeError( + "Unallowable component '%s' of type %s found in an active " + "constraint or objective.\nMAiNGO cannot export " + "expressions with this component type." + % (node.name, node.ctype.__name__) + ) + + if node.is_fixed(): + return True, node() + else: + assert node.is_variable_type() + maingo_var_id = self.idmap[id(node)] + maingo_var = self.variables[maingo_var_id] + return True, maingo_var + + def _monomial_to_maingo(self, node): + const, var = node.args + maingo_var_id = self.idmap[id(var)] + maingo_var = self.variables[maingo_var_id] + if const.__class__ not in native_types: + const = value(const) + if var.is_fixed(): + return const * var.value + if not const: + return 0 + if const in _plusMinusOne: + if const < 0: + return -maingo_var + else: + return maingo_var + return const * maingo_var + + def _linear_to_maingo(self, node): + values = [ + self._monomial_to_maingo(arg) + if ( + arg.__class__ is EXPR.MonomialTermExpression + and not arg.arg(1).is_fixed() + ) + else value(arg) + for arg in node.args + ] + return sum(values) + + +class MAiNGO(PersistentBase, PersistentSolver): + """ + Interface to MAiNGO + """ + + _available = None + + def __init__(self, only_child_vars=False): + super(MAiNGO, self).__init__(only_child_vars=only_child_vars) + self._config = MAiNGOConfig() + self._solver_options = dict() + self._solver_model = None + self._mymaingo = None + self._symbol_map = SymbolMap() + self._labeler = None + self._maingo_vars = [] + self._objective = None + self._cons = [] + self._pyomo_var_to_solver_var_id_map = dict() + self._last_results_object: Optional[MAiNGOResults] = None + + def available(self): + if not maingopy_available: + return self.Availability.NotFound + self._available = True + return self._available + + def version(self): + pass + + @property + def config(self) -> MAiNGOConfig: + return self._config + + @config.setter + def config(self, val: MAiNGOConfig): + self._config = val + + @property + def maingo_options(self): + """ + A dictionary mapping solver options to values for those options. These + are solver specific. + + Returns + ------- + dict + A dictionary mapping solver options to values for those options + """ + return self._solver_options + + @maingo_options.setter + def maingo_options(self, val: Dict): + self._solver_options = val + + @property + def symbol_map(self): + return self._symbol_map + + def _solve(self, timer: HierarchicalTimer): + ostreams = [ + LogStream( + level=self.config.log_level, logger=self.config.solver_output_logger + ) + ] + if self.config.stream_solver: + ostreams.append(sys.stdout) + + with TeeStream(*ostreams) as t: + with capture_output(output=t.STDOUT, capture_fd=False): + config = self.config + options = self.maingo_options + + self._mymaingo = maingopy.MAiNGO(self._solver_model) + + self._mymaingo.set_option("loggingDestination", 2) + self._mymaingo.set_log_file_name(config.logfile) + + if config.time_limit is not None: + self._mymaingo.set_option("maxTime", config.time_limit) + if config.mip_gap is not None: + self._mymaingo.set_option("epsilonA", config.mip_gap) + for key, option in options.items(): + self._mymaingo.set_option(key, option) + + timer.start("MAiNGO solve") + self._mymaingo.solve() + timer.stop("MAiNGO solve") + + return self._postsolve(timer) + + def solve(self, model, timer: HierarchicalTimer = None): + 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() + timer.start("set_instance") + self.set_instance(model) + timer.stop("set_instance") + res = self._solve(timer) + self._last_results_object = res + if self.config.report_timing: + logger.info("\n" + str(timer)) + return res + + def _process_domain_and_bounds(self, var): + _v, _lb, _ub, _fixed, _domain_interval, _value = self._vars[id(var)] + lb, ub, step = _domain_interval + if lb is None: + lb = -1e10 + if ub is None: + ub = 1e10 + if step == 0: + vtype = maingopy.VT_CONTINUOUS + elif step == 1: + if lb == 0 and ub == 1: + vtype = maingopy.VT_BINARY + else: + vtype = maingopy.VT_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: + lb = max(value(_lb), lb) + if _ub is not None: + ub = min(value(_ub), ub) + + return lb, ub, vtype + + def _add_variables(self, variables: List[_GeneralVarData]): + for ndx, var in enumerate(variables): + varname = self._symbol_map.getSymbol(var, self._labeler) + lb, ub, vtype = self._process_domain_and_bounds(var) + self._maingo_vars.append( + MaingoVar(name=varname, type=vtype, lb=lb, ub=ub, init=var.value) + ) + self._pyomo_var_to_solver_var_id_map[id(var)] = len(self._maingo_vars) - 1 + + def _add_params(self, params: List[_ParamData]): + pass + + def _reinit(self): + saved_config = self.config + saved_options = self.maingo_options + saved_update_config = self.update_config + self.__init__(only_child_vars=self._only_child_vars) + self.config = saved_config + self.maingo_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.add_block(model) + self._solver_model = SolverModel( + var_list=self._maingo_vars, + con_list=self._cons, + objective=self._objective, + idmap=self._pyomo_var_to_solver_var_id_map, + ) + + def _add_constraints(self, cons: List[_GeneralConstraintData]): + self._cons = cons + + def _add_sos_constraints(self, cons: List[_SOSConstraintData]): + pass + + def _remove_constraints(self, cons: List[_GeneralConstraintData]): + pass + + def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): + pass + + def _remove_variables(self, variables: List[_GeneralVarData]): + pass + + def _remove_params(self, params: List[_ParamData]): + pass + + def _update_variables(self, variables: List[_GeneralVarData]): + pass + + def update_params(self): + pass + + def _set_objective(self, obj): + if obj is None: + raise NotImplementedError( + "MAiNGO needs a objective. Please set a dummy objective." + ) + else: + if not obj.sense in {minimize, maximize}: + raise ValueError( + "Objective sense is not recognized: {0}".format(obj.sense) + ) + self._objective = obj + + def _postsolve(self, timer: HierarchicalTimer): + config = self.config + + mprob = self._mymaingo + status = mprob.get_status() + results = MAiNGOResults(solver=self) + results.wallclock_time = mprob.get_wallclock_solution_time() + results.cpu_time = mprob.get_cpu_solution_time() + + if status == maingopy.GLOBALLY_OPTIMAL: + results.termination_condition = TerminationCondition.optimal + elif status == maingopy.INFEASIBLE: + results.termination_condition = TerminationCondition.infeasible + else: + results.termination_condition = TerminationCondition.unknown + + results.best_feasible_objective = None + results.best_objective_bound = None + if self._objective is not None: + try: + if self._objective.sense == maximize: + results.best_feasible_objective = -mprob.get_objective_value() + else: + results.best_feasible_objective = mprob.get_objective_value() + except: + results.best_feasible_objective = None + try: + if self._objective.sense == maximize: + results.best_objective_bound = -mprob.get_final_LBD() + else: + results.best_objective_bound = mprob.get_final_LBD() + except: + if self._objective.sense == maximize: + results.best_objective_bound = math.inf + else: + results.best_objective_bound = -math.inf + + if results.best_feasible_objective is not None and not math.isfinite( + results.best_feasible_objective + ): + results.best_feasible_objective = None + + timer.start("load solution") + if config.load_solution: + if not results.best_feasible_objective is None: + if results.termination_condition != TerminationCondition.optimal: + logger.warning( + "Loading a feasible but suboptimal solution. " + "Please set load_solution=False and check " + "results.termination_condition and " + "results.found_feasible_solution() before loading a solution." + ) + 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 " + "results.best_feasible_objective before loading a solution." + ) + timer.stop("load solution") + + return results + + def load_vars(self, vars_to_load=None): + 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) + + def get_primals(self, vars_to_load=None): + if not self._mymaingo.get_status() in { + maingopy.GLOBALLY_OPTIMAL, + maingopy.FEASIBLE_POINT, + }: + raise RuntimeError( + "Solver does not currently have a valid solution." + "Please check the termination condition." + ) + + var_id_map = self._pyomo_var_to_solver_var_id_map + ref_vars = self._referenced_variables + if vars_to_load is None: + vars_to_load = var_id_map.keys() + else: + vars_to_load = [id(v) for v in vars_to_load] + + maingo_var_ids_to_load = [ + var_id_map[pyomo_var_id] for pyomo_var_id in vars_to_load + ] + + solution_point = self._mymaingo.get_solution_point() + vals = [solution_point[var_id] for var_id in maingo_var_ids_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): + raise ValueError("MAiNGO does not support returning Reduced Costs") + + def get_duals(self, cons_to_load=None): + raise ValueError("MAiNGO does not support returning Duals") From 0ea6a293df516acb7a02ba4ec56a51b51009cbc6 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 26 Feb 2024 10:25:06 -0700 Subject: [PATCH 1240/1797] 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 1241/1797] 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 1242/1797] 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 1243/1797] 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 1244/1797] 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 28cf0695f7fc9140f20dcb846c9deff8897cffe1 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Mon, 26 Feb 2024 12:32:01 -0700 Subject: [PATCH 1245/1797] timing calls and some performance improvements --- .../contrib/incidence_analysis/scc_solver.py | 97 ++++++++++++++++--- 1 file changed, 85 insertions(+), 12 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/scc_solver.py b/pyomo/contrib/incidence_analysis/scc_solver.py index 835e07c7c02..f6697b11567 100644 --- a/pyomo/contrib/incidence_analysis/scc_solver.py +++ b/pyomo/contrib/incidence_analysis/scc_solver.py @@ -13,18 +13,27 @@ from pyomo.core.base.constraint import Constraint from pyomo.util.calc_var_value import calculate_variable_from_constraint -from pyomo.util.subsystems import TemporarySubsystemManager, generate_subsystem_blocks +from pyomo.util.subsystems import ( + TemporarySubsystemManager, + generate_subsystem_blocks, + create_subsystem_block, +) from pyomo.contrib.incidence_analysis.interface import ( IncidenceGraphInterface, _generate_variables_in_constraints, ) +from pyomo.contrib.incidence_analysis.config import IncidenceMethod _log = logging.getLogger(__name__) +from pyomo.common.timing import HierarchicalTimer def generate_strongly_connected_components( - constraints, variables=None, include_fixed=False + constraints, + variables=None, + include_fixed=False, + timer=None, ): """Yield in order ``_BlockData`` that each contain the variables and constraints of a single diagonal block in a block lower triangularization @@ -53,27 +62,52 @@ def generate_strongly_connected_components( "input variables" for that block. """ - if variables is None: - variables = list( - _generate_variables_in_constraints(constraints, include_fixed=include_fixed) - ) + if timer is None: + timer = HierarchicalTimer() + + if isinstance(constraints, IncidenceGraphInterface): + igraph = constraints + variables = igraph.variables + constraints = igraph.constraints + else: + if variables is None: + timer.start("generate-variables") + variables = list( + _generate_variables_in_constraints(constraints, include_fixed=include_fixed) + ) + timer.stop("generate-variables") + timer.start("igraph") + igraph = IncidenceGraphInterface() + timer.stop("igraph") assert len(variables) == len(constraints) - igraph = IncidenceGraphInterface() + + timer.start("block-triang") var_blocks, con_blocks = igraph.block_triangularize( variables=variables, constraints=constraints ) + timer.stop("block-triang") subsets = [(cblock, vblock) for vblock, cblock in zip(var_blocks, con_blocks)] + timer.start("generate-block") for block, inputs in generate_subsystem_blocks( subsets, include_fixed=include_fixed ): + timer.stop("generate-block") # TODO: How does len scale for reference-to-list? assert len(block.vars) == len(block.cons) yield (block, inputs) + # Note that this code, after the last yield, I believe is only called + # at time of GC. + timer.start("generate-block") + timer.stop("generate-block") def solve_strongly_connected_components( - block, solver=None, solve_kwds=None, calc_var_kwds=None + block, + solver=None, + solve_kwds=None, + calc_var_kwds=None, + timer=None, ): """Solve a square system of variables and equality constraints by solving strongly connected components individually. @@ -110,24 +144,59 @@ def solve_strongly_connected_components( solve_kwds = {} if calc_var_kwds is None: calc_var_kwds = {} + if timer is None: + timer = HierarchicalTimer() + timer.start("igraph") igraph = IncidenceGraphInterface( - block, active=True, include_fixed=False, include_inequality=False + block, + active=True, + include_fixed=False, + include_inequality=False, + method=IncidenceMethod.ampl_repn, ) + timer.stop("igraph") + # Use IncidenceGraphInterface to get the constraints and variables constraints = igraph.constraints variables = igraph.variables + timer.start("block-triang") + var_blocks, con_blocks = igraph.block_triangularize() + timer.stop("block-triang") + timer.start("subsystem-blocks") + subsystem_blocks = [ + create_subsystem_block(conbl, varbl, timer=timer) if len(varbl) > 1 else None + for varbl, conbl in zip(var_blocks, con_blocks) + ] + timer.stop("subsystem-blocks") + res_list = [] log_blocks = _log.isEnabledFor(logging.DEBUG) - for scc, inputs in generate_strongly_connected_components(constraints, variables): + + #timer.start("generate-scc") + #for scc, inputs in generate_strongly_connected_components(igraph, timer=timer): + # timer.stop("generate-scc") + for i, scc in enumerate(subsystem_blocks): + if scc is None: + # Since a block is not necessary for 1x1 solve, we use the convention + # that None indicates a 1x1 SCC. + inputs = [] + var = var_blocks[i][0] + con = con_blocks[i][0] + else: + inputs = list(scc.input_vars.values()) + with TemporarySubsystemManager(to_fix=inputs): - N = len(scc.vars) + N = len(var_blocks[i]) if N == 1: if log_blocks: _log.debug(f"Solving 1x1 block: {scc.cons[0].name}.") + timer.start("calc-var") results = calculate_variable_from_constraint( - scc.vars[0], scc.cons[0], **calc_var_kwds + #scc.vars[0], scc.cons[0], **calc_var_kwds + var, con, **calc_var_kwds ) + timer.stop("calc-var") res_list.append(results) else: if solver is None: @@ -141,6 +210,10 @@ def solve_strongly_connected_components( ) if log_blocks: _log.debug(f"Solving {N}x{N} block.") + timer.start("solve") results = solver.solve(scc, **solve_kwds) + timer.stop("solve") res_list.append(results) + # timer.start("generate-scc") + #timer.stop("generate-scc") return res_list From 2b47212ab8c5db29033e4ffd8b7cd2edb86272cb Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Mon, 26 Feb 2024 12:32:23 -0700 Subject: [PATCH 1246/1797] dont repeat work for the same named expression in ExternalFunctionVisitor --- pyomo/util/subsystems.py | 78 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/pyomo/util/subsystems.py b/pyomo/util/subsystems.py index 70a0af1b2a7..43246da37a4 100644 --- a/pyomo/util/subsystems.py +++ b/pyomo/util/subsystems.py @@ -20,15 +20,32 @@ from pyomo.core.base.external import ExternalFunction from pyomo.core.expr.visitor import StreamBasedExpressionVisitor from pyomo.core.expr.numeric_expr import ExternalFunctionExpression -from pyomo.core.expr.numvalue import native_types +from pyomo.core.expr.numvalue import native_types, NumericValue class _ExternalFunctionVisitor(StreamBasedExpressionVisitor): + + def __init__(self, descend_into_named_expressions=True): + super().__init__() + self._descend_into_named_expressions = descend_into_named_expressions + self.named_expressions = [] + def initializeWalker(self, expr): self._functions = [] self._seen = set() return True, None + def beforeChild(self, parent, child, index): + if ( + not self._descend_into_named_expressions + and isinstance(child, NumericValue) + and child.is_named_expression_type() + ): + self.named_expressions.append(child) + return False, None + else: + return True, None + def exitNode(self, node, data): if type(node) is ExternalFunctionExpression: if id(node) not in self._seen: @@ -50,14 +67,47 @@ def acceptChildResult(self, node, data, child_result, child_idx): return child_result.is_expression_type(), None -def identify_external_functions(expr): - yield from _ExternalFunctionVisitor().walk_expression(expr) +def identify_external_functions( + expr, + descend_into_named_expressions=True, + named_expressions=None, +): + visitor = _ExternalFunctionVisitor( + descend_into_named_expressions=descend_into_named_expressions + ) + efs = list(visitor.walk_expression(expr)) + if not descend_into_named_expressions and named_expressions is not None: + named_expressions.extend(visitor.named_expressions) + return efs + #yield from _ExternalFunctionVisitor().walk_expression(expr) def add_local_external_functions(block): ef_exprs = [] + named_expressions = [] for comp in block.component_data_objects((Constraint, Expression), active=True): - ef_exprs.extend(identify_external_functions(comp.expr)) + ef_exprs.extend(identify_external_functions( + comp.expr, + descend_into_named_expressions=False, + named_expressions=named_expressions, + )) + named_expr_set = ComponentSet(named_expressions) + named_expressions = list(named_expr_set) + while named_expressions: + expr = named_expressions.pop() + local_named_exprs = [] + ef_exprs.extend(identify_external_functions( + expr, + descend_into_named_expressions=False, + named_expressions=local_named_exprs, + )) + # Only add to the stack named expressions that we have + # not encountered yet. + for local_expr in local_named_exprs: + if local_expr not in named_expr_set: + named_expressions.append(local_expr) + named_expr_set.add(local_expr) + unique_functions = [] fcn_set = set() for expr in ef_exprs: @@ -75,7 +125,13 @@ def add_local_external_functions(block): return fcn_comp_map -def create_subsystem_block(constraints, variables=None, include_fixed=False): +from pyomo.common.timing import HierarchicalTimer +def create_subsystem_block( + constraints, + variables=None, + include_fixed=False, + timer=None, +): """This function creates a block to serve as a subsystem with the specified variables and constraints. To satisfy certain writers, other variables that appear in the constraints must be added to the block as @@ -99,20 +155,32 @@ def create_subsystem_block(constraints, variables=None, include_fixed=False): as well as other variables present in the constraints """ + if timer is None: + timer = HierarchicalTimer() if variables is None: variables = [] + timer.start("block") block = Block(concrete=True) + timer.stop("block") + timer.start("reference") block.vars = Reference(variables) block.cons = Reference(constraints) + timer.stop("reference") var_set = ComponentSet(variables) input_vars = [] + timer.start("identify-vars") for con in constraints: for var in identify_variables(con.expr, include_fixed=include_fixed): if var not in var_set: input_vars.append(var) var_set.add(var) + timer.stop("identify-vars") + timer.start("reference") block.input_vars = Reference(input_vars) + timer.stop("reference") + timer.start("external-fcns") add_local_external_functions(block) + timer.stop("external-fcns") return block From 7aaaeed7f6d051a79a06435a4e0c4a0ad3f8a8bc Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Mon, 26 Feb 2024 14:19:19 -0700 Subject: [PATCH 1247/1797] scc_solver implementation using SccImplicitFunctionSolver --- .../contrib/incidence_analysis/scc_solver.py | 167 +++++++++++------- 1 file changed, 100 insertions(+), 67 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/scc_solver.py b/pyomo/contrib/incidence_analysis/scc_solver.py index f6697b11567..5d1e2e23f2d 100644 --- a/pyomo/contrib/incidence_analysis/scc_solver.py +++ b/pyomo/contrib/incidence_analysis/scc_solver.py @@ -147,73 +147,106 @@ def solve_strongly_connected_components( if timer is None: timer = HierarchicalTimer() - timer.start("igraph") - igraph = IncidenceGraphInterface( - block, - active=True, - include_fixed=False, - include_inequality=False, - method=IncidenceMethod.ampl_repn, - ) - timer.stop("igraph") - # Use IncidenceGraphInterface to get the constraints and variables - constraints = igraph.constraints - variables = igraph.variables + USE_IMPLICIT = True + if not USE_IMPLICIT: + timer.start("igraph") + igraph = IncidenceGraphInterface( + block, + active=True, + include_fixed=False, + include_inequality=False, + method=IncidenceMethod.ampl_repn, + ) + timer.stop("igraph") + # Use IncidenceGraphInterface to get the constraints and variables + constraints = igraph.constraints + variables = igraph.variables - timer.start("block-triang") - var_blocks, con_blocks = igraph.block_triangularize() - timer.stop("block-triang") - timer.start("subsystem-blocks") - subsystem_blocks = [ - create_subsystem_block(conbl, varbl, timer=timer) if len(varbl) > 1 else None - for varbl, conbl in zip(var_blocks, con_blocks) - ] - timer.stop("subsystem-blocks") - - res_list = [] - log_blocks = _log.isEnabledFor(logging.DEBUG) - - #timer.start("generate-scc") - #for scc, inputs in generate_strongly_connected_components(igraph, timer=timer): - # timer.stop("generate-scc") - for i, scc in enumerate(subsystem_blocks): - if scc is None: - # Since a block is not necessary for 1x1 solve, we use the convention - # that None indicates a 1x1 SCC. - inputs = [] - var = var_blocks[i][0] - con = con_blocks[i][0] - else: - inputs = list(scc.input_vars.values()) - - with TemporarySubsystemManager(to_fix=inputs): - N = len(var_blocks[i]) - if N == 1: - if log_blocks: - _log.debug(f"Solving 1x1 block: {scc.cons[0].name}.") - timer.start("calc-var") - results = calculate_variable_from_constraint( - #scc.vars[0], scc.cons[0], **calc_var_kwds - var, con, **calc_var_kwds - ) - timer.stop("calc-var") - res_list.append(results) + timer.start("block-triang") + var_blocks, con_blocks = igraph.block_triangularize() + timer.stop("block-triang") + timer.start("subsystem-blocks") + subsystem_blocks = [ + create_subsystem_block(conbl, varbl, timer=timer) if len(varbl) > 1 else None + for varbl, conbl in zip(var_blocks, con_blocks) + ] + timer.stop("subsystem-blocks") + + res_list = [] + log_blocks = _log.isEnabledFor(logging.DEBUG) + + #timer.start("generate-scc") + #for scc, inputs in generate_strongly_connected_components(igraph, timer=timer): + # timer.stop("generate-scc") + for i, scc in enumerate(subsystem_blocks): + if scc is None: + # Since a block is not necessary for 1x1 solve, we use the convention + # that None indicates a 1x1 SCC. + inputs = [] + var = var_blocks[i][0] + con = con_blocks[i][0] else: - if solver is None: - var_names = [var.name for var in scc.vars.values()][:10] - con_names = [con.name for con in scc.cons.values()][:10] - raise RuntimeError( - "An external solver is required if block has strongly\n" - "connected components of size greater than one (is not" - " a DAG).\nGot an SCC of size %sx%s including" - " components:\n%s\n%s" % (N, N, var_names, con_names) + inputs = list(scc.input_vars.values()) + + with TemporarySubsystemManager(to_fix=inputs): + N = len(var_blocks[i]) + if N == 1: + if log_blocks: + _log.debug(f"Solving 1x1 block: {scc.cons[0].name}.") + timer.start("calc-var") + results = calculate_variable_from_constraint( + #scc.vars[0], scc.cons[0], **calc_var_kwds + var, con, **calc_var_kwds ) - if log_blocks: - _log.debug(f"Solving {N}x{N} block.") - timer.start("solve") - results = solver.solve(scc, **solve_kwds) - timer.stop("solve") - res_list.append(results) - # timer.start("generate-scc") - #timer.stop("generate-scc") - return res_list + timer.stop("calc-var") + res_list.append(results) + else: + if solver is None: + var_names = [var.name for var in scc.vars.values()][:10] + con_names = [con.name for con in scc.cons.values()][:10] + raise RuntimeError( + "An external solver is required if block has strongly\n" + "connected components of size greater than one (is not" + " a DAG).\nGot an SCC of size %sx%s including" + " components:\n%s\n%s" % (N, N, var_names, con_names) + ) + if log_blocks: + _log.debug(f"Solving {N}x{N} block.") + timer.start("solve") + results = solver.solve(scc, **solve_kwds) + timer.stop("solve") + res_list.append(results) + # timer.start("generate-scc") + #timer.stop("generate-scc") + return res_list + else: + from pyomo.contrib.pynumero.algorithms.solvers.implicit_functions import ( + SccImplicitFunctionSolver, + ScipySolverWrapper, + ) + timer.start("igraph") + igraph = IncidenceGraphInterface( + block, + active=True, + include_fixed=False, + include_inequality=False, + method=IncidenceMethod.ampl_repn, + ) + timer.stop("igraph") + # Use IncidenceGraphInterface to get the constraints and variables + constraints = igraph.constraints + variables = igraph.variables + + # Construct an implicit function solver with no parameters. (This is just + # a square system solver.) + scc_solver = SccImplicitFunctionSolver( + variables, + constraints, + [], + solver_class=ScipySolverWrapper, + timer=timer, + use_calc_var=False, + ) + # set_parameters triggers the Newton solve. + scc_solver.set_parameters([]) + scc_solver.update_pyomo_model() From aaecb45022782bfcb00e60f29fb8895139c126fc Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Mon, 26 Feb 2024 14:19:34 -0700 Subject: [PATCH 1248/1797] additional timing calls in SccImplicitFunctionSolver --- .../algorithms/solvers/implicit_functions.py | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/pynumero/algorithms/solvers/implicit_functions.py b/pyomo/contrib/pynumero/algorithms/solvers/implicit_functions.py index e40580c1161..17b895507f2 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/implicit_functions.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/implicit_functions.py @@ -32,10 +32,8 @@ NewtonNlpSolver, SecantNewtonNlpSolver, ) +from pyomo.contrib.incidence_analysis.config import IncidenceMethod from pyomo.contrib.incidence_analysis import IncidenceGraphInterface -from pyomo.contrib.incidence_analysis.scc_solver import ( - generate_strongly_connected_components, -) class NlpSolverBase(object): @@ -133,7 +131,7 @@ class PyomoImplicitFunctionBase(object): """ - def __init__(self, variables, constraints, parameters): + def __init__(self, variables, constraints, parameters, timer=None): """ Arguments --------- @@ -145,11 +143,13 @@ def __init__(self, variables, constraints, parameters): Variables to be treated as inputs to the implicit function """ + if timer is None: + timer = HierarchicalTimer() self._variables = variables self._constraints = constraints self._parameters = parameters self._block_variables = variables + parameters - self._block = create_subsystem_block(constraints, self._block_variables) + self._block = create_subsystem_block(constraints, self._block_variables, timer=timer) def get_variables(self): return self._variables @@ -361,7 +361,9 @@ def __init__( self._solver_options = solver_options self._calc_var_cutoff = 1 if use_calc_var else 0 # NOTE: This super call is only necessary so the get_* methods work - super().__init__(variables, constraints, parameters) + timer.start("super.__init__") + super().__init__(variables, constraints, parameters, timer=timer) + timer.stop("super.__init__") subsystem_list = [ # Switch order in list for compatibility with generate_subsystem_blocks @@ -376,6 +378,7 @@ def __init__( # an equality constraint. constants = [] constant_set = ComponentSet() + timer.start("identify-vars") for con in constraints: for var in identify_variables(con.expr, include_fixed=False): if var not in constant_set and var not in var_param_set: @@ -383,6 +386,7 @@ def __init__( # a var nor param, treat it as a "constant" constant_set.add(var) constants.append(var) + timer.stop("identify-vars") with TemporarySubsystemManager(to_fix=constants): # Temporarily fix "constant" variables so (a) they don't show @@ -390,6 +394,7 @@ def __init__( # they don't appear as additional columns in the NLPs and # ProjectedNLPs. + timer.start("subsystem-blocks") self._subsystem_list = list(generate_subsystem_blocks(subsystem_list)) # These are subsystems that need an external solver, rather than # calculate_variable_from_constraint. _calc_var_cutoff should be either @@ -399,6 +404,7 @@ def __init__( for block, inputs in self._subsystem_list if len(block.vars) > self._calc_var_cutoff ] + timer.stop("subsystem-blocks") # Need a dummy objective to create an NLP for block, inputs in self._solver_subsystem_list: @@ -421,16 +427,20 @@ def __init__( # "Output variable" names are required to construct ProjectedNLPs. # Ideally, we can eventually replace these with variable indices. + timer.start("names") self._solver_subsystem_var_names = [ [var.name for var in block.vars.values()] for block, inputs in self._solver_subsystem_list ] + timer.stop("names") + timer.start("proj-ext-nlp") self._solver_proj_nlps = [ nlp_proj.ProjectedExtendedNLP(nlp, names) for nlp, names in zip( self._solver_subsystem_nlps, self._solver_subsystem_var_names ) ] + timer.stop("proj-ext-nlp") # We will solve the ProjectedNLPs rather than the original NLPs self._timer.start("NlpSolver") @@ -439,6 +449,7 @@ def __init__( for nlp in self._solver_proj_nlps ] self._timer.stop("NlpSolver") + timer.start("input-indices") self._solver_subsystem_input_coords = [ # Coordinates in the NLP, not ProjectedNLP nlp.get_primal_indices(inputs) @@ -446,6 +457,7 @@ def __init__( self._solver_subsystem_nlps, self._solver_subsystem_list ) ] + timer.stop("input-indices") self._n_variables = len(variables) self._n_constraints = len(constraints) @@ -465,6 +477,7 @@ def __init__( ) # Cache the global array-coordinates of each subset of "input" # variables. These are used for updating before each solve. + timer.start("coord-maps") self._local_input_global_coords = [ # If I do not fix "constants" above, I get errors here # that only show up in the CLC models. @@ -481,6 +494,7 @@ def __init__( ) for (block, _) in self._solver_subsystem_list ] + timer.stop("coord-maps") self._timer.stop("__init__") @@ -612,7 +626,7 @@ def update_pyomo_model(self): class SccImplicitFunctionSolver(DecomposedImplicitFunctionBase): def partition_system(self, variables, constraints): self._timer.start("partition") - igraph = IncidenceGraphInterface() + igraph = IncidenceGraphInterface(method=IncidenceMethod.ampl_repn) var_blocks, con_blocks = igraph.block_triangularize(variables, constraints) self._timer.stop("partition") return zip(var_blocks, con_blocks) From e0312525ed13dfd1e437bf65d9892f2975f3bf03 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 26 Feb 2024 15:22:42 -0700 Subject: [PATCH 1249/1797] 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 1250/1797] 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 1251/1797] 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 1252/1797] 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 1253/1797] 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 1254/1797] 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 1255/1797] 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 1256/1797] 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 1257/1797] 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 24f649334eb0d2291551c8b09923e251e7ef486c Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 27 Feb 2024 12:48:51 -0800 Subject: [PATCH 1258/1797] clean up, moved _expand_indexed_unknowns --- pyomo/contrib/parmest/parmest.py | 75 ++++++++++++-------------------- 1 file changed, 27 insertions(+), 48 deletions(-) diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index 9e5b480332d..ffe9afc059e 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -226,12 +226,12 @@ def _experiment_instance_creation_callback( thetavals = outer_cb_data["ThetaVals"] # dlw august 2018: see mea code for more general theta - for vstr in thetavals: - theta_cuid = ComponentUID(vstr) + for name, val in thetavals.items(): + theta_cuid = ComponentUID(name) theta_object = theta_cuid.find_component_on(instance) - if thetavals[vstr] is not None: + if val is not None: # print("Fixing",vstr,"at",str(thetavals[vstr])) - theta_object.fix(thetavals[vstr]) + theta_object.fix(val) else: # print("Freeing",vstr) theta_object.unfix() @@ -400,6 +400,29 @@ def _return_theta_names(self): self.estimator_theta_names ) # default theta_names, created when Estimator object is created + def _expand_indexed_unknowns(self, model_temp): + """ + Expand indexed variables to get full list of thetas + """ + model_theta_list = [k.name for k, v in model_temp.unknown_parameters.items()] + + # check for indexed theta items + indexed_theta_list = [] + for theta_i in model_theta_list: + var_cuid = ComponentUID(theta_i) + var_validate = var_cuid.find_component_on(model_temp) + for ind in var_validate.index_set(): + if ind is not None: + indexed_theta_list.append(theta_i + '[' + str(ind) + ']') + else: + indexed_theta_list.append(theta_i) + + # if we found indexed thetas, use expanded list + if len(indexed_theta_list) > len(model_theta_list): + model_theta_list = indexed_theta_list + + return model_theta_list + def _create_parmest_model(self, experiment_number): """ Modify the Pyomo model for parameter estimation @@ -1155,28 +1178,6 @@ def leaveNout_bootstrap_test( return results - # expand indexed variables to get full list of thetas - def _expand_indexed_unknowns(self, model_temp): - - model_theta_list = [k.name for k, v in model_temp.unknown_parameters.items()] - - # check for indexed theta items - indexed_theta_list = [] - for theta_i in model_theta_list: - var_cuid = ComponentUID(theta_i) - var_validate = var_cuid.find_component_on(model_temp) - for ind in var_validate.index_set(): - if ind is not None: - indexed_theta_list.append(theta_i + '[' + str(ind) + ']') - else: - indexed_theta_list.append(theta_i) - - # if we found indexed thetas, use expanded list - if len(indexed_theta_list) > len(model_theta_list): - model_theta_list = indexed_theta_list - - return model_theta_list - def objective_at_theta(self, theta_values=None, initialize_parmest_model=False): """ Objective value for each theta @@ -1212,28 +1213,6 @@ def objective_at_theta(self, theta_values=None, initialize_parmest_model=False): model_temp = self._create_parmest_model(0) model_theta_list = self._expand_indexed_unknowns(model_temp) - # # iterate over original theta_names - # for theta_i in self.theta_names: - # var_cuid = ComponentUID(theta_i) - # var_validate = var_cuid.find_component_on(model_temp) - # # check if theta in theta_names are indexed - # try: - # # get component UID of Set over which theta is defined - # set_cuid = ComponentUID(var_validate.index_set()) - # # access and iterate over the Set to generate theta names as they appear - # # in the pyomo model - # set_validate = set_cuid.find_component_on(model_temp) - # for s in set_validate: - # self_theta_temp = repr(var_cuid) + "[" + repr(s) + "]" - # # generate list of theta names - # model_theta_list.append(self_theta_temp) - # # if theta is not indexed, copy theta name to list as-is - # except AttributeError: - # self_theta_temp = repr(var_cuid) - # model_theta_list.append(self_theta_temp) - # except: - # raise - # if self.theta_names is not the same as temp model_theta_list, # create self.theta_names_updated if set(self.estimator_theta_names) == set(model_theta_list) and len( From 6699fde39928e0dc9eba0482bc018a8d1a540101 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 27 Feb 2024 16:07:43 -0700 Subject: [PATCH 1259/1797] 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 1260/1797] 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 1261/1797] 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 1262/1797] 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 1263/1797] 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 1264/1797] 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 1265/1797] 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 1266/1797] 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 1267/1797] 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 1268/1797] 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 1269/1797] 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 1270/1797] 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 1271/1797] 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 1272/1797] 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 caa688ed390e609b78ff3af305f87223dd3a69e6 Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Wed, 28 Feb 2024 11:12:00 +0100 Subject: [PATCH 1273/1797] Initial point calculation consistent with ALE syntax --- pyomo/contrib/appsi/solvers/maingo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/solvers/maingo.py b/pyomo/contrib/appsi/solvers/maingo.py index dcb8040eabe..530521f6b83 100644 --- a/pyomo/contrib/appsi/solvers/maingo.py +++ b/pyomo/contrib/appsi/solvers/maingo.py @@ -146,7 +146,7 @@ def get_variables(self): ] def get_initial_point(self): - return [var.init if not var.init is None else var.lb for var in self._var_list] + return [var.init if not var.init is None else (var.lb + var.ub)/2.0 for var in self._var_list] def evaluate(self, maingo_vars): visitor = ToMAiNGOVisitor(maingo_vars, self._idmap) From 365370a4220ff2fe0df818878f89b5f6fe36d7a3 Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Wed, 28 Feb 2024 11:24:17 +0100 Subject: [PATCH 1274/1797] Added warning for missing variable bounds --- pyomo/contrib/appsi/solvers/maingo.py | 35 ++++++++++++++++++--------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/maingo.py b/pyomo/contrib/appsi/solvers/maingo.py index 530521f6b83..52b12d434ef 100644 --- a/pyomo/contrib/appsi/solvers/maingo.py +++ b/pyomo/contrib/appsi/solvers/maingo.py @@ -436,10 +436,28 @@ def solve(self, model, timer: HierarchicalTimer = None): def _process_domain_and_bounds(self, var): _v, _lb, _ub, _fixed, _domain_interval, _value = self._vars[id(var)] lb, ub, step = _domain_interval - if lb is None: - lb = -1e10 - if ub is None: - ub = 1e10 + + if _fixed: + lb = _value + ub = _value + else: + if lb is None and _lb is None: + logger.warning("No lower bound for variable " + var.getname() + " set. Using -1e10 instead. Please consider setting a valid lower bound.") + if ub is None and _ub is None: + logger.warning("No upper bound for variable " + var.getname() + " set. Using +1e10 instead. Please consider setting a valid upper bound.") + + if _lb is None: + _lb = -1e10 + if _ub is None: + _ub = 1e10 + if lb is None: + lb = -1e10 + if ub is None: + ub = 1e10 + + lb = max(value(_lb), lb) + ub = min(value(_ub), ub) + if step == 0: vtype = maingopy.VT_CONTINUOUS elif step == 1: @@ -451,14 +469,7 @@ def _process_domain_and_bounds(self, var): 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: - lb = max(value(_lb), lb) - if _ub is not None: - ub = min(value(_ub), ub) + return lb, ub, vtype From c440037d95146caaa896404235ebd2b60f8d8926 Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Wed, 28 Feb 2024 11:32:55 +0100 Subject: [PATCH 1275/1797] Added: NotImplementedError for SOS constraints --- pyomo/contrib/appsi/solvers/maingo.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyomo/contrib/appsi/solvers/maingo.py b/pyomo/contrib/appsi/solvers/maingo.py index 52b12d434ef..99cff4b7aa9 100644 --- a/pyomo/contrib/appsi/solvers/maingo.py +++ b/pyomo/contrib/appsi/solvers/maingo.py @@ -525,12 +525,16 @@ def _add_constraints(self, cons: List[_GeneralConstraintData]): self._cons = cons def _add_sos_constraints(self, cons: List[_SOSConstraintData]): + if len(cons) >= 1: + raise NotImplementedError("MAiNGO does not currently support SOS constraints.") pass def _remove_constraints(self, cons: List[_GeneralConstraintData]): pass def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): + if len(cons) >= 1: + raise NotImplementedError("MAiNGO does not currently support SOS constraints.") pass def _remove_variables(self, variables: List[_GeneralVarData]): From 52a4cd9e59baf27bdab4df32b3a850d511ce894b Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Wed, 28 Feb 2024 11:39:28 +0100 Subject: [PATCH 1276/1797] Changed: Formulation of asinh, acosh, atanh --- pyomo/contrib/appsi/solvers/maingo.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/maingo.py b/pyomo/contrib/appsi/solvers/maingo.py index 99cff4b7aa9..099865f5a84 100644 --- a/pyomo/contrib/appsi/solvers/maingo.py +++ b/pyomo/contrib/appsi/solvers/maingo.py @@ -192,15 +192,15 @@ def maingo_log10(cls, x): @classmethod def maingo_asinh(cls, x): - return maingopy.inv(maingopy.sinh(x)) + return maingopy.log(x + maingopy.sqrt(maingopy.pow(x,2) + 1)) @classmethod def maingo_acosh(cls, x): - return maingopy.inv(maingopy.cosh(x)) + return maingopy.log(x + maingopy.sqrt(maingopy.pow(x,2) - 1)) @classmethod def maingo_atanh(cls, x): - return maingopy.inv(maingopy.tanh(x)) + return 0.5 * maingopy.log(x+1) - 0.5 * maingopy.log(1-x) def visit(self, node, values): """Visit nodes that have been expanded""" From 44311203d1b426a0894e8f9c7efbdbfa3c0eec85 Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Wed, 28 Feb 2024 11:49:15 +0100 Subject: [PATCH 1277/1797] Added: Warning for non-global solutions --- pyomo/contrib/appsi/solvers/maingo.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/solvers/maingo.py b/pyomo/contrib/appsi/solvers/maingo.py index 099865f5a84..7a153f938b7 100644 --- a/pyomo/contrib/appsi/solvers/maingo.py +++ b/pyomo/contrib/appsi/solvers/maingo.py @@ -570,8 +570,10 @@ def _postsolve(self, timer: HierarchicalTimer): results.wallclock_time = mprob.get_wallclock_solution_time() results.cpu_time = mprob.get_cpu_solution_time() - if status == maingopy.GLOBALLY_OPTIMAL: + if status in {maingopy.GLOBALLY_OPTIMAL, maingopy.FEASIBLE_POINT}: results.termination_condition = TerminationCondition.optimal + if status == maingopy.FEASIBLE_POINT: + logger.warning("MAiNGO did only find a feasible solution but did not prove its global optimality.") elif status == maingopy.INFEASIBLE: results.termination_condition = TerminationCondition.infeasible else: From ff2c9bd86eefe7b2693f60a165383b525a764984 Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Wed, 28 Feb 2024 11:57:01 +0100 Subject: [PATCH 1278/1797] Changed: absolute to relative MIP gap --- pyomo/contrib/appsi/solvers/maingo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/solvers/maingo.py b/pyomo/contrib/appsi/solvers/maingo.py index 7a153f938b7..cf09b42fbda 100644 --- a/pyomo/contrib/appsi/solvers/maingo.py +++ b/pyomo/contrib/appsi/solvers/maingo.py @@ -407,7 +407,7 @@ def _solve(self, timer: HierarchicalTimer): if config.time_limit is not None: self._mymaingo.set_option("maxTime", config.time_limit) if config.mip_gap is not None: - self._mymaingo.set_option("epsilonA", config.mip_gap) + self._mymaingo.set_option("epsilonR", config.mip_gap) for key, option in options.items(): self._mymaingo.set_option(key, option) From b833ac729aa7741331811c133d54d809f27e17be Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Wed, 28 Feb 2024 12:11:48 +0100 Subject: [PATCH 1279/1797] Added: Maingopy version --- pyomo/contrib/appsi/solvers/maingo.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/solvers/maingo.py b/pyomo/contrib/appsi/solvers/maingo.py index cf09b42fbda..05c5f50a295 100644 --- a/pyomo/contrib/appsi/solvers/maingo.py +++ b/pyomo/contrib/appsi/solvers/maingo.py @@ -354,7 +354,15 @@ def available(self): return self._available def version(self): - pass + # Check if Python >= 3.8 + if sys.version_info.major >= 3 and sys.version_info.minor >= 8: + from importlib.metadata import version + version = version('maingopy') + else: + import pkg_resources + version = pkg_resources.get_distribution('maingopy').version + + return tuple(int(k) for k in version.split('.')) @property def config(self) -> MAiNGOConfig: From c937b63e2e6e699efe24ef0e46cd36fd56da8134 Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Wed, 28 Feb 2024 12:21:12 +0100 Subject: [PATCH 1280/1797] Black Formatting --- pyomo/contrib/appsi/solvers/__init__.py | 2 +- pyomo/contrib/appsi/solvers/maingo.py | 42 ++++++++++++++++++------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/__init__.py b/pyomo/contrib/appsi/solvers/__init__.py index c9e0a2a003d..352571b98f8 100644 --- a/pyomo/contrib/appsi/solvers/__init__.py +++ b/pyomo/contrib/appsi/solvers/__init__.py @@ -15,4 +15,4 @@ from .cplex import Cplex from .highs import Highs from .wntr import Wntr, WntrResults -from .maingo import MAiNGO \ No newline at end of file +from .maingo import MAiNGO diff --git a/pyomo/contrib/appsi/solvers/maingo.py b/pyomo/contrib/appsi/solvers/maingo.py index 05c5f50a295..d98b33af998 100644 --- a/pyomo/contrib/appsi/solvers/maingo.py +++ b/pyomo/contrib/appsi/solvers/maingo.py @@ -146,7 +146,10 @@ def get_variables(self): ] def get_initial_point(self): - return [var.init if not var.init is None else (var.lb + var.ub)/2.0 for var in self._var_list] + return [ + var.init if not var.init is None else (var.lb + var.ub) / 2.0 + for var in self._var_list + ] def evaluate(self, maingo_vars): visitor = ToMAiNGOVisitor(maingo_vars, self._idmap) @@ -192,15 +195,15 @@ def maingo_log10(cls, x): @classmethod def maingo_asinh(cls, x): - return maingopy.log(x + maingopy.sqrt(maingopy.pow(x,2) + 1)) + return maingopy.log(x + maingopy.sqrt(maingopy.pow(x, 2) + 1)) @classmethod def maingo_acosh(cls, x): - return maingopy.log(x + maingopy.sqrt(maingopy.pow(x,2) - 1)) + return maingopy.log(x + maingopy.sqrt(maingopy.pow(x, 2) - 1)) @classmethod def maingo_atanh(cls, x): - return 0.5 * maingopy.log(x+1) - 0.5 * maingopy.log(1-x) + return 0.5 * maingopy.log(x + 1) - 0.5 * maingopy.log(1 - x) def visit(self, node, values): """Visit nodes that have been expanded""" @@ -357,11 +360,13 @@ def version(self): # Check if Python >= 3.8 if sys.version_info.major >= 3 and sys.version_info.minor >= 8: from importlib.metadata import version + version = version('maingopy') else: import pkg_resources + version = pkg_resources.get_distribution('maingopy').version - + return tuple(int(k) for k in version.split('.')) @property @@ -450,10 +455,18 @@ def _process_domain_and_bounds(self, var): ub = _value else: if lb is None and _lb is None: - logger.warning("No lower bound for variable " + var.getname() + " set. Using -1e10 instead. Please consider setting a valid lower bound.") + logger.warning( + "No lower bound for variable " + + var.getname() + + " set. Using -1e10 instead. Please consider setting a valid lower bound." + ) if ub is None and _ub is None: - logger.warning("No upper bound for variable " + var.getname() + " set. Using +1e10 instead. Please consider setting a valid upper bound.") - + logger.warning( + "No upper bound for variable " + + var.getname() + + " set. Using +1e10 instead. Please consider setting a valid upper bound." + ) + if _lb is None: _lb = -1e10 if _ub is None: @@ -478,7 +491,6 @@ def _process_domain_and_bounds(self, var): f"Unrecognized domain step: {step} (should be either 0 or 1)" ) - return lb, ub, vtype def _add_variables(self, variables: List[_GeneralVarData]): @@ -534,7 +546,9 @@ def _add_constraints(self, cons: List[_GeneralConstraintData]): def _add_sos_constraints(self, cons: List[_SOSConstraintData]): if len(cons) >= 1: - raise NotImplementedError("MAiNGO does not currently support SOS constraints.") + raise NotImplementedError( + "MAiNGO does not currently support SOS constraints." + ) pass def _remove_constraints(self, cons: List[_GeneralConstraintData]): @@ -542,7 +556,9 @@ def _remove_constraints(self, cons: List[_GeneralConstraintData]): def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): if len(cons) >= 1: - raise NotImplementedError("MAiNGO does not currently support SOS constraints.") + raise NotImplementedError( + "MAiNGO does not currently support SOS constraints." + ) pass def _remove_variables(self, variables: List[_GeneralVarData]): @@ -581,7 +597,9 @@ def _postsolve(self, timer: HierarchicalTimer): if status in {maingopy.GLOBALLY_OPTIMAL, maingopy.FEASIBLE_POINT}: results.termination_condition = TerminationCondition.optimal if status == maingopy.FEASIBLE_POINT: - logger.warning("MAiNGO did only find a feasible solution but did not prove its global optimality.") + logger.warning( + "MAiNGO did only find a feasible solution but did not prove its global optimality." + ) elif status == maingopy.INFEASIBLE: results.termination_condition = TerminationCondition.infeasible else: From ccb7723466f0bb06eb3abf552c44c084932704b3 Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Wed, 28 Feb 2024 12:44:02 +0100 Subject: [PATCH 1281/1797] Fixed: Black Formatting --- pyomo/contrib/appsi/solvers/maingo.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/maingo.py b/pyomo/contrib/appsi/solvers/maingo.py index d98b33af998..614e12d227b 100644 --- a/pyomo/contrib/appsi/solvers/maingo.py +++ b/pyomo/contrib/appsi/solvers/maingo.py @@ -318,12 +318,14 @@ def _monomial_to_maingo(self, node): def _linear_to_maingo(self, node): values = [ - self._monomial_to_maingo(arg) - if ( - arg.__class__ is EXPR.MonomialTermExpression - and not arg.arg(1).is_fixed() + ( + self._monomial_to_maingo(arg) + if ( + arg.__class__ is EXPR.MonomialTermExpression + and not arg.arg(1).is_fixed() + ) + else value(arg) ) - else value(arg) for arg in node.args ] return sum(values) From e28e14db71b7ee09a54af2882d80837289f5c448 Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Wed, 28 Feb 2024 14:33:06 +0100 Subject: [PATCH 1282/1797] Added: pip install maingopy to test_branches.yml --- .github/workflows/test_branches.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 77f47b505ff..1441cd53623 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -268,6 +268,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 maingopy \ + || echo "WARNING: MAiNGO is not available" if [[ ${{matrix.python}} == pypy* ]]; then echo "skipping wntr for pypy" else From c52bbc7f3c565d5d50ff98187efdb8d25de32e4b Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Wed, 28 Feb 2024 14:35:41 +0100 Subject: [PATCH 1283/1797] Added: pip install maingopy to test_pr_and_main.yml --- .github/workflows/test_pr_and_main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 87d6aa4d7a8..0214442d4e5 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 maingopy \ + || echo "WARNING: MAiNGO is not available" if [[ ${{matrix.python}} == pypy* ]]; then echo "skipping wntr for pypy" else From 84ca52464286faeb95fd5edda052b4d3459f021c Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 28 Feb 2024 08:35:37 -0700 Subject: [PATCH 1284/1797] 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 1285/1797] 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 1286/1797] 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 a931ace7b198e4db00970ab0c009d3c4e5805959 Mon Sep 17 00:00:00 2001 From: kaklise Date: Wed, 28 Feb 2024 09:33:43 -0800 Subject: [PATCH 1287/1797] minor updates to datarec example --- .../reactor_design/datarec_example.py | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py b/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py index 00287730c63..45f826f880d 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py @@ -20,15 +20,6 @@ np.random.seed(1234) -def reactor_design_model_for_datarec(): - - # Unfix inlet concentration for data rec - model = reactor_design_model() - model.caf.fixed = False - - return model - - class ReactorDesignExperimentPreDataRec(ReactorDesignExperiment): def __init__(self, data, data_std, experiment_number): @@ -37,7 +28,10 @@ def __init__(self, data, data_std, experiment_number): self.data_std = data_std def create_model(self): - self.model = m = reactor_design_model_for_datarec() + + self.model = m = reactor_design_model() + m.caf.fixed = False + return m def label_model(self): @@ -124,20 +118,15 @@ def main(): exp_list.append(ReactorDesignExperimentPreDataRec(data, data_std, i)) # Define sum of squared error objective function for data rec - def SSE(model): + def SSE_with_std(model): expr = sum( ((y - yhat) / model.experiment_outputs_std[y]) ** 2 for y, yhat in model.experiment_outputs.items() ) return expr - # View one model & SSE - # exp0_model = exp_list[0].get_labeled_model() - # print(exp0_model.pprint()) - # print(SSE(exp0_model)) - ### Data reconciliation - pest = parmest.Estimator(exp_list, obj_function=SSE) + pest = parmest.Estimator(exp_list, obj_function=SSE_with_std) obj, theta, data_rec = pest.theta_est(return_values=["ca", "cb", "cc", "cd", "caf"]) print(obj) @@ -157,7 +146,7 @@ def SSE(model): for i in range(data_rec.shape[0]): exp_list.append(ReactorDesignExperimentPostDataRec(data_rec, data_std, i)) - pest = parmest.Estimator(exp_list, obj_function=SSE) + pest = parmest.Estimator(exp_list, obj_function=SSE_with_std) obj, theta = pest.theta_est() print(obj) print(theta) From f5350a4f45982fb7661dd468d94df5bfc8880164 Mon Sep 17 00:00:00 2001 From: kaklise Date: Wed, 28 Feb 2024 09:34:28 -0800 Subject: [PATCH 1288/1797] Added API docs and made deprecated class private --- pyomo/contrib/parmest/parmest.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index ffe9afc059e..8f98ab5cd5a 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -276,6 +276,9 @@ def _experiment_instance_creation_callback( def SSE(model): + """ + Sum of squared error between `experiment_output` model and data values + """ expr = sum((y - yhat) ** 2 for y, yhat in model.experiment_outputs.items()) return expr @@ -330,7 +333,7 @@ def __init__(self, *args, **kwargs): + 'data, theta_names), please use experiment lists instead.', version=DEPRECATION_VERSION, ) - self.pest_deprecated = DeprecatedEstimator(*args, **kwargs) + self.pest_deprecated = _DeprecatedEstimator(*args, **kwargs) return # check that we have a (non-empty) list of experiments @@ -1477,7 +1480,7 @@ def __call__(self, model): return self._ssc_function(model, self._data) -class DeprecatedEstimator(object): +class _DeprecatedEstimator(object): """ Parameter estimation class From 1761879c34b93c8c6d573cdbdbd1527555d14817 Mon Sep 17 00:00:00 2001 From: kaklise Date: Wed, 28 Feb 2024 09:36:19 -0800 Subject: [PATCH 1289/1797] renamed class in datarec example --- .../parmest/examples/reactor_design/datarec_example.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py b/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py index 45f826f880d..e05b69aa4cc 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py @@ -20,7 +20,7 @@ np.random.seed(1234) -class ReactorDesignExperimentPreDataRec(ReactorDesignExperiment): +class ReactorDesignExperimentDataRec(ReactorDesignExperiment): def __init__(self, data, data_std, experiment_number): @@ -115,7 +115,7 @@ def main(): # Create an experiment list exp_list = [] for i in range(data.shape[0]): - exp_list.append(ReactorDesignExperimentPreDataRec(data, data_std, i)) + exp_list.append(ReactorDesignExperimentDataRec(data, data_std, i)) # Define sum of squared error objective function for data rec def SSE_with_std(model): From 97469b24ed63d4b635952b36008656162486f11e Mon Sep 17 00:00:00 2001 From: kaklise Date: Wed, 28 Feb 2024 09:43:25 -0800 Subject: [PATCH 1290/1797] parmest doc updates --- .../contributed_packages/parmest/datarec.rst | 35 +++--- .../contributed_packages/parmest/driver.rst | 105 ++++++++---------- .../contributed_packages/parmest/examples.rst | 2 +- .../parmest/scencreate.rst | 2 +- 4 files changed, 60 insertions(+), 84 deletions(-) diff --git a/doc/OnlineDocs/contributed_packages/parmest/datarec.rst b/doc/OnlineDocs/contributed_packages/parmest/datarec.rst index 6e9be904286..2260450192c 100644 --- a/doc/OnlineDocs/contributed_packages/parmest/datarec.rst +++ b/doc/OnlineDocs/contributed_packages/parmest/datarec.rst @@ -3,35 +3,27 @@ Data Reconciliation ==================== -The method :class:`~pyomo.contrib.parmest.parmest.Estimator.theta_est` -can optionally return model values. This feature can be used to return -reconciled data using a user specified objective. In this case, the list -of variable names the user wants to estimate (theta_names) is set to an -empty list and the objective function is defined to minimize +The optional argument ``return_values`` in :class:`~pyomo.contrib.parmest.parmest.Estimator.theta_est` +can be used for data reconciliation or to return model values based on the specified objective. + +For data reconciliation, the ``m.unknown_parameters`` is empty +and the objective function is defined to minimize measurement to model error. Note that the model used for data reconciliation may differ from the model used for parameter estimation. -The following example illustrates the use of parmest for data -reconciliation. The functions +The functions :class:`~pyomo.contrib.parmest.graphics.grouped_boxplot` or :class:`~pyomo.contrib.parmest.graphics.grouped_violinplot` can be used to visually compare the original and reconciled data. -Here's a stylized code snippet showing how box plots might be created: - -.. doctest:: - :skipif: True - - >>> import pyomo.contrib.parmest.parmest as parmest - >>> pest = parmest.Estimator(model_function, data, [], objective_function) - >>> obj, theta, data_rec = pest.theta_est(return_values=['A', 'B']) - >>> parmest.graphics.grouped_boxplot(data, data_rec) +The following example from the reactor design subdirectory returns reconciled values for experiment outputs +(`ca`, `cb`, `cc`, and `cd`) and then uses those values in +parameter estimation (`k1`, `k2`, and `k3`). -Returned Values -^^^^^^^^^^^^^^^ - -Here's a full program that can be run to see returned values (in this case it -is the response function that is defined in the model file): +.. literalinclude:: ../../../../pyomo/contrib/parmest/examples/reactor_design/datarec_example.py + :language: python + +The following example returns model values from a Pyomo Expression. .. doctest:: :skipif: not ipopt_available or not parmest_available @@ -60,4 +52,3 @@ is the response function that is defined in the model file): >>> pest = parmest.Estimator(exp_list, obj_function=SSE, solver_options=None) >>> obj, theta, var_values = pest.theta_est(return_values=['response_function']) >>> #print(var_values) - diff --git a/doc/OnlineDocs/contributed_packages/parmest/driver.rst b/doc/OnlineDocs/contributed_packages/parmest/driver.rst index 45533e9520c..695cab36e93 100644 --- a/doc/OnlineDocs/contributed_packages/parmest/driver.rst +++ b/doc/OnlineDocs/contributed_packages/parmest/driver.rst @@ -4,7 +4,7 @@ Parameter Estimation ================================== Parameter Estimation using parmest requires a Pyomo model, experimental -data which defines multiple scenarios, and a list of parameter names +data which defines multiple scenarios, and parameters (thetas) to estimate. parmest uses Pyomo [PyomoBookII]_ and (optionally) mpi-sppy [mpisppy]_ to solve a two-stage stochastic programming problem, where the experimental data is @@ -36,8 +36,8 @@ which includes the following methods: ~pyomo.contrib.parmest.parmest.Estimator.likelihood_ratio_test ~pyomo.contrib.parmest.parmest.Estimator.leaveNout_bootstrap_test -Additional functions are available in parmest to group data, plot -results, and fit distributions to theta values. +Additional functions are available in parmest to plot +results and fit distributions to theta values. .. autosummary:: :nosignatures: @@ -92,65 +92,43 @@ Optionally, solver options can be supplied, e.g., >>> solver_options = {"max_iter": 6000} >>> pest = parmest.Estimator(exp_list, obj_function=SSE, solver_options=solver_options) - - - -Model function --------------- - -The first argument is a function which uses data for a single scenario -to return a populated and initialized Pyomo model for that scenario. - -Parameters that the user would like to estimate can be defined as -**mutable parameters (Pyomo `Param`) or variables (Pyomo `Var`)**. -Within parmest, any parameters that are to be estimated are converted to unfixed variables. -Variables that are to be estimated are also unfixed. - -The model does not have to be specifically written as a -two-stage stochastic programming problem for parmest. -That is, parmest can modify the -objective, see :ref:`ObjFunction` below. - -Data ----- - -The second argument is the data which will be used to populate the Pyomo -model. Supported data formats include: - -* **Pandas Dataframe** where each row is a separate scenario and column - names refer to observed quantities. Pandas DataFrames are easily - stored and read in from csv, excel, or databases, or created directly - in Python. -* **List of Pandas Dataframe** where each entry in the list is a separate scenario. - Dataframes store observed quantities, referenced by index and column. -* **List of dictionaries** where each entry in the list is a separate - scenario and the keys (or nested keys) refer to observed quantities. - Dictionaries are often preferred over DataFrames when using static and - time series data. Dictionaries are easily stored and read in from - json or yaml files, or created directly in Python. -* **List of json file names** where each entry in the list contains a - json file name for a separate scenario. This format is recommended - when using large datasets in parallel computing. - -The data must be compatible with the model function that returns a -populated and initialized Pyomo model for a single scenario. Data can -include multiple entries per variable (time series and/or duplicate -sensors). This information can be included in custom objective -functions, see :ref:`ObjFunction` below. - -Theta names ------------ - -The third argument is a list of parameters or variable names that the user wants to -estimate. The list contains strings with `Param` and/or `Var` names from the Pyomo -model. + + +List of experiment objects +-------------------------- + +The first argument is a list of experiment objects which is used to +create one labeled model for each expeirment. +The template :class:`~pyomo.contrib.parmest.experiment.Experiment` +can be used to generate a list of experiment objects. + +A labeled Pyomo model ``m`` has the following additional suffixes (Pyomo `Suffix`): + +* ``m.experiment_outputs`` which defines experiment output (Pyomo `Param`, `Var`, or `Expression`) + and their associated data values (float, int). +* ``m.unknown_parameters`` which defines the mutable parameters or variables (Pyomo `Parm` or `Var`) + to estimate along with their component unique identifier (Pyomo `ComponentUID`). + Within parmest, any parameters that are to be estimated are converted to unfixed variables. + Variables that are to be estimated are also unfixed. + +The experiment class has one required method: + +* :class:`~pyomo.contrib.parmest.experiment.Experiment.get_labeled_model` which returns the labeled Pyomo model. + Note that the model does not have to be specifically written as a + two-stage stochastic programming problem for parmest. + That is, parmest can modify the + objective, see :ref:`ObjFunction` below. + +Parmest comes with several :ref:`examplesection` that illustrates how to set up the list of experiment objects. +The examples commonly include additional :class:`~pyomo.contrib.parmest.experiment.Experiment` class methods to +create the model, finalize the model, and label the model. The user can customize methods to suit their needs. .. _ObjFunction: Objective function ------------------ -The fourth argument is an optional argument which defines the +The second argument is an optional argument which defines the optimization objective function to use in parameter estimation. If no objective function is specified, the Pyomo model is used "as is" and @@ -161,20 +139,27 @@ stochastic programming problem. If the Pyomo model is not written as a two-stage stochastic programming problem in this format, and/or if the user wants to use an objective that is different than the original model, a custom objective function can be -defined for parameter estimation. The objective function arguments -include `model` and `data` and the objective function returns a Pyomo +defined for parameter estimation. The objective function has a single argument, +which is the model from a single experiment. +The objective function returns a Pyomo expression which is used to define "SecondStageCost". The objective function can be used to customize data points and weights that are used in parameter estimation. +Parmest includes one built in objective function to compute the sum of squared errors ("SSE") between the +``m.experiment_outputs`` model values and data values. + Suggested initialization procedure for parameter estimation problems -------------------------------------------------------------------- To check the quality of initial guess values provided for the fitted parameters, we suggest solving a square instance of the problem prior to solving the parameter estimation problem using the following steps: -1. Create :class:`~pyomo.contrib.parmest.parmest.Estimator` object. To initialize the parameter estimation solve from the square problem solution, set optional argument ``solver_options = {bound_push: 1e-8}``. +1. Create :class:`~pyomo.contrib.parmest.parmest.Estimator` object. To initialize the parameter +estimation solve from the square problem solution, set optional argument ``solver_options = {bound_push: 1e-8}``. -2. Call :class:`~pyomo.contrib.parmest.parmest.Estimator.objective_at_theta` with optional argument ``(initialize_parmest_model=True)``. Different initial guess values for the fitted parameters can be provided using optional argument `theta_values` (**Pandas Dataframe**) +2. Call :class:`~pyomo.contrib.parmest.parmest.Estimator.objective_at_theta` with optional +argument ``(initialize_parmest_model=True)``. Different initial guess values for the fitted +parameters can be provided using optional argument `theta_values` (**Pandas Dataframe**) 3. Solve parameter estimation problem by calling :class:`~pyomo.contrib.parmest.parmest.Estimator.theta_est` diff --git a/doc/OnlineDocs/contributed_packages/parmest/examples.rst b/doc/OnlineDocs/contributed_packages/parmest/examples.rst index 793ff3d0c8d..a59d79dfa2b 100644 --- a/doc/OnlineDocs/contributed_packages/parmest/examples.rst +++ b/doc/OnlineDocs/contributed_packages/parmest/examples.rst @@ -20,7 +20,7 @@ Additional use cases include: * Parameter estimation using mpi4py, the example saves results to a file for later analysis/graphics (semibatch example) -The description below uses the reactor design example. The file +The example below uses the reactor design example. The file **reactor_design.py** includes a function which returns an populated instance of the Pyomo model. Note that the model is defined to maximize `cb` and that `k1`, `k2`, and `k3` are fixed. The _main_ program is diff --git a/doc/OnlineDocs/contributed_packages/parmest/scencreate.rst b/doc/OnlineDocs/contributed_packages/parmest/scencreate.rst index 66d41d4c606..b63ac5893c2 100644 --- a/doc/OnlineDocs/contributed_packages/parmest/scencreate.rst +++ b/doc/OnlineDocs/contributed_packages/parmest/scencreate.rst @@ -18,5 +18,5 @@ scenarios to the screen, accessing them via the ``ScensItator`` a ``print`` :language: python .. note:: - This example may produce an error message your version of Ipopt is not based + This example may produce an error message if your version of Ipopt is not based on a good linear solver. From ba840aabf0c1281b68524cf11ed67ddbc940b89e Mon Sep 17 00:00:00 2001 From: kaklise Date: Wed, 28 Feb 2024 09:55:17 -0800 Subject: [PATCH 1291/1797] added header and updated API docs for Experiment --- pyomo/contrib/parmest/experiment.py | 30 +++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/pyomo/contrib/parmest/experiment.py b/pyomo/contrib/parmest/experiment.py index e16ad304e42..69474a32bb0 100644 --- a/pyomo/contrib/parmest/experiment.py +++ b/pyomo/contrib/parmest/experiment.py @@ -1,14 +1,28 @@ -# The experiment class is a template for making experiment lists -# to pass to parmest. An experiment is a pyomo model "m" which has -# additional suffixes: -# m.experiment_outputs -- which variables are experiment outputs -# m.unknown_parameters -- which variables are parameters to estimate -# The experiment class has only one required method: -# get_labeled_model() -# which returns the labeled pyomo model. +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ class Experiment: + """ + The experiment class is a template for making experiment lists + to pass to parmest. + + An experiment is a Pyomo model "m" which is labeled + with additional suffixes: + * m.experiment_outputs which defines experiment outputs + * m.unknown_parameters which defines parameters to estimate + + The experiment class has one required method: + * get_labeled_model() which returns the labeled Pyomo model + """ def __init__(self, model=None): self.model = model From be878d18b608b3bd1e0b67c70caefc5d83b86029 Mon Sep 17 00:00:00 2001 From: kaklise Date: Wed, 28 Feb 2024 09:55:32 -0800 Subject: [PATCH 1292/1797] made ScenarioCreatorDeprecated class private --- pyomo/contrib/parmest/scenariocreator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/parmest/scenariocreator.py b/pyomo/contrib/parmest/scenariocreator.py index 434e15e7f31..f2798ad2e94 100644 --- a/pyomo/contrib/parmest/scenariocreator.py +++ b/pyomo/contrib/parmest/scenariocreator.py @@ -137,7 +137,7 @@ def __init__(self, pest, solvername): + "creator, please recreate object using experiment lists.", version=DEPRECATION_VERSION, ) - self.scen_deprecated = ScenarioCreatorDeprecated( + self.scen_deprecated = _ScenarioCreatorDeprecated( pest.pest_deprecated, solvername ) else: @@ -201,7 +201,7 @@ def ScenariosFromBootstrap(self, addtoSet, numtomake, seed=None): ################################ -class ScenarioCreatorDeprecated(object): +class _ScenarioCreatorDeprecated(object): """Create scenarios from parmest. Args: From 46bfee38dbdcc11c76e8279207b7ac2d0dfeafb7 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 28 Feb 2024 11:02:12 -0700 Subject: [PATCH 1293/1797] 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 9cb26c7629c49b055ec699e505e4c5ab32d07d62 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 28 Feb 2024 11:16:41 -0700 Subject: [PATCH 1294/1797] NFC: Fix year in copyright assertion comment Co-authored-by: Miranda Mundt <55767766+mrmundt@users.noreply.github.com> --- pyomo/contrib/simplification/__init__.py | 2 +- pyomo/contrib/simplification/build.py | 2 +- pyomo/contrib/simplification/ginac_interface.cpp | 2 +- pyomo/contrib/simplification/simplify.py | 2 +- pyomo/contrib/simplification/tests/__init__.py | 2 +- pyomo/contrib/simplification/tests/test_simplification.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyomo/contrib/simplification/__init__.py b/pyomo/contrib/simplification/__init__.py index b4fa68eb386..c6111ddcb89 100644 --- a/pyomo/contrib/simplification/__init__.py +++ b/pyomo/contrib/simplification/__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/simplification/build.py b/pyomo/contrib/simplification/build.py index d9d1e701290..2c7b1830ff6 100644 --- a/pyomo/contrib/simplification/build.py +++ b/pyomo/contrib/simplification/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/simplification/ginac_interface.cpp b/pyomo/contrib/simplification/ginac_interface.cpp index 489f281bc2c..1060f87161c 100644 --- a/pyomo/contrib/simplification/ginac_interface.cpp +++ b/pyomo/contrib/simplification/ginac_interface.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/simplification/simplify.py b/pyomo/contrib/simplification/simplify.py index b8cc4995f91..00c5dde348e 100644 --- a/pyomo/contrib/simplification/simplify.py +++ b/pyomo/contrib/simplification/simplify.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# Copyright (c) 2008-2024 # National Technology and Engineering Solutions of Sandia, LLC # Under the terms of Contract DE-NA0003525 with National Technology and # Engineering Solutions of Sandia, LLC, the U.S. Government retains certain diff --git a/pyomo/contrib/simplification/tests/__init__.py b/pyomo/contrib/simplification/tests/__init__.py index d93cfd77b3c..a4a626013c4 100644 --- a/pyomo/contrib/simplification/tests/__init__.py +++ b/pyomo/contrib/simplification/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/simplification/tests/test_simplification.py b/pyomo/contrib/simplification/tests/test_simplification.py index 95402f98318..1a5ae1e0036 100644 --- a/pyomo/contrib/simplification/tests/test_simplification.py +++ b/pyomo/contrib/simplification/tests/test_simplification.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# Copyright (c) 2008-2024 # National Technology and Engineering Solutions of 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 8b2568804f83f89d24eb53330ffae61dbc95b3c7 Mon Sep 17 00:00:00 2001 From: kaklise Date: Wed, 28 Feb 2024 10:48:49 -0800 Subject: [PATCH 1295/1797] formatting updates --- pyomo/contrib/parmest/experiment.py | 9 +++++---- pyomo/contrib/parmest/parmest.py | 6 +++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/parmest/experiment.py b/pyomo/contrib/parmest/experiment.py index 69474a32bb0..4f797d6c89c 100644 --- a/pyomo/contrib/parmest/experiment.py +++ b/pyomo/contrib/parmest/experiment.py @@ -13,16 +13,17 @@ class Experiment: """ The experiment class is a template for making experiment lists - to pass to parmest. - - An experiment is a Pyomo model "m" which is labeled + to pass to parmest. + + An experiment is a Pyomo model "m" which is labeled with additional suffixes: * m.experiment_outputs which defines experiment outputs * m.unknown_parameters which defines parameters to estimate - + The experiment class has one required method: * get_labeled_model() which returns the labeled Pyomo model """ + def __init__(self, model=None): self.model = model diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index 8f98ab5cd5a..aecc9d5ebc2 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -408,7 +408,7 @@ def _expand_indexed_unknowns(self, model_temp): Expand indexed variables to get full list of thetas """ model_theta_list = [k.name for k, v in model_temp.unknown_parameters.items()] - + # check for indexed theta items indexed_theta_list = [] for theta_i in model_theta_list: @@ -419,11 +419,11 @@ def _expand_indexed_unknowns(self, model_temp): indexed_theta_list.append(theta_i + '[' + str(ind) + ']') else: indexed_theta_list.append(theta_i) - + # if we found indexed thetas, use expanded list if len(indexed_theta_list) > len(model_theta_list): model_theta_list = indexed_theta_list - + return model_theta_list def _create_parmest_model(self, experiment_number): From c5bdb0ba866bb0be1dd956fca35332ef84b88d5b Mon Sep 17 00:00:00 2001 From: kaklise Date: Wed, 28 Feb 2024 12:01:02 -0800 Subject: [PATCH 1296/1797] fixed typo --- doc/OnlineDocs/contributed_packages/parmest/driver.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/OnlineDocs/contributed_packages/parmest/driver.rst b/doc/OnlineDocs/contributed_packages/parmest/driver.rst index 695cab36e93..5881d2748f9 100644 --- a/doc/OnlineDocs/contributed_packages/parmest/driver.rst +++ b/doc/OnlineDocs/contributed_packages/parmest/driver.rst @@ -106,7 +106,7 @@ A labeled Pyomo model ``m`` has the following additional suffixes (Pyomo `Suffix * ``m.experiment_outputs`` which defines experiment output (Pyomo `Param`, `Var`, or `Expression`) and their associated data values (float, int). -* ``m.unknown_parameters`` which defines the mutable parameters or variables (Pyomo `Parm` or `Var`) +* ``m.unknown_parameters`` which defines the mutable parameters or variables (Pyomo `Param` or `Var`) to estimate along with their component unique identifier (Pyomo `ComponentUID`). Within parmest, any parameters that are to be estimated are converted to unfixed variables. Variables that are to be estimated are also unfixed. From 5b5f0046ab59accedee601deb51cfe14939298ec Mon Sep 17 00:00:00 2001 From: jasherma Date: Wed, 28 Feb 2024 15:24:45 -0500 Subject: [PATCH 1297/1797] 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 1298/1797] 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 1299/1797] 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 1300/1797] 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 1301/1797] 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 1302/1797] 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 1303/1797] 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 1304/1797] 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 0d3400ffeeb93ed1764b4ba5f4eb487a309ecb35 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Sun, 3 Mar 2024 05:09:56 -0700 Subject: [PATCH 1305/1797] add type hints to components --- pyomo/core/base/block.py | 22 ++++++++++++++++++++-- pyomo/core/base/constraint.py | 17 +++++++++++++++++ pyomo/core/base/indexed_component.py | 4 ++-- pyomo/core/base/param.py | 16 +++++++++++++++- pyomo/core/base/set.py | 16 +++++++++++++++- pyomo/core/base/var.py | 16 +++++++++++++++- 6 files changed, 84 insertions(+), 7 deletions(-) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index a0948c693d7..9ca2112d498 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -9,6 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +from __future__ import annotations import copy import logging import sys @@ -21,6 +22,7 @@ from io import StringIO from itertools import filterfalse, chain from operator import itemgetter, attrgetter +from typing import Union, Any, Type from pyomo.common.autoslots import AutoSlots from pyomo.common.collections import Mapping @@ -44,6 +46,7 @@ from pyomo.core.base.indexed_component import ( ActiveIndexedComponent, UnindexedComponent_set, + IndexedComponent, ) from pyomo.opt.base import ProblemFormat, guess_format @@ -539,7 +542,7 @@ def __init__(self, component): super(_BlockData, self).__setattr__('_decl_order', []) self._private_data = None - def __getattr__(self, val): + def __getattr__(self, val) -> Union[Component, IndexedComponent, Any]: if val in ModelComponentFactory: return _component_decorator(self, ModelComponentFactory.get_class(val)) # Since the base classes don't support getattr, we can just @@ -548,7 +551,7 @@ def __getattr__(self, val): "'%s' object has no attribute '%s'" % (self.__class__.__name__, val) ) - def __setattr__(self, name, val): + def __setattr__(self, name: str, val: Union[Component, IndexedComponent, Any]): """ Set an attribute of a block data object. """ @@ -2007,6 +2010,18 @@ class Block(ActiveIndexedComponent): _ComponentDataClass = _BlockData _private_data_initializers = defaultdict(lambda: dict) + @overload + def __new__(cls: Type[Block], *args, **kwds) -> Union[ScalarBlock, IndexedBlock]: + ... + + @overload + def __new__(cls: Type[ScalarBlock], *args, **kwds) -> ScalarBlock: + ... + + @overload + def __new__(cls: Type[IndexedBlock], *args, **kwds) -> IndexedBlock: + ... + def __new__(cls, *args, **kwds): if cls != Block: return super(Block, cls).__new__(cls) @@ -2251,6 +2266,9 @@ class IndexedBlock(Block): def __init__(self, *args, **kwds): Block.__init__(self, *args, **kwds) + def __getitem__(self, index) -> _BlockData: + return super().__getitem__(index) + # # Deprecated functions. diff --git a/pyomo/core/base/constraint.py b/pyomo/core/base/constraint.py index 8cf3c48ad0a..dcc90fd6280 100644 --- a/pyomo/core/base/constraint.py +++ b/pyomo/core/base/constraint.py @@ -9,10 +9,12 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +from __future__ import annotations import sys import logging from weakref import ref as weakref_ref from pyomo.common.pyomo_typing import overload +from typing import Union, Type from pyomo.common.deprecation import RenamedClass from pyomo.common.errors import DeveloperError @@ -728,6 +730,18 @@ class Infeasible(object): Violated = Infeasible Satisfied = Feasible + @overload + def __new__(cls: Type[Constraint], *args, **kwds) -> Union[ScalarConstraint, IndexedConstraint]: + ... + + @overload + def __new__(cls: Type[ScalarConstraint], *args, **kwds) -> ScalarConstraint: + ... + + @overload + def __new__(cls: Type[IndexedConstraint], *args, **kwds) -> IndexedConstraint: + ... + def __new__(cls, *args, **kwds): if cls != Constraint: return super(Constraint, cls).__new__(cls) @@ -1019,6 +1033,9 @@ class IndexedConstraint(Constraint): def add(self, index, expr): """Add a constraint with a given index.""" return self.__setitem__(index, expr) + + def __getitem__(self, index) -> _GeneralConstraintData: + return super().__getitem__(index) @ModelComponentFactory.register("A list of constraint expressions.") diff --git a/pyomo/core/base/indexed_component.py b/pyomo/core/base/indexed_component.py index 0d498da091d..e1be613d666 100644 --- a/pyomo/core/base/indexed_component.py +++ b/pyomo/core/base/indexed_component.py @@ -18,7 +18,7 @@ import pyomo.core.base as BASE 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.component import Component, ActiveComponent, ComponentData from pyomo.core.base.config import PyomoOptions from pyomo.core.base.enums import SortComponents from pyomo.core.base.global_set import UnindexedComponent_set @@ -606,7 +606,7 @@ def iteritems(self): """Return a list (index,data) tuples from the dictionary""" return self.items() - def __getitem__(self, index): + def __getitem__(self, index) -> ComponentData: """ This method returns the data corresponding to the given index. """ diff --git a/pyomo/core/base/param.py b/pyomo/core/base/param.py index 3ef33b9ee45..dde390661ab 100644 --- a/pyomo/core/base/param.py +++ b/pyomo/core/base/param.py @@ -9,11 +9,13 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +from __future__ import annotations import sys import types import logging from weakref import ref as weakref_ref from pyomo.common.pyomo_typing import overload +from typing import Union, Type from pyomo.common.autoslots import AutoSlots from pyomo.common.deprecation import deprecation_warning, RenamedClass @@ -291,6 +293,18 @@ class NoValue(object): pass + @overload + def __new__(cls: Type[Param], *args, **kwds) -> Union[ScalarParam, IndexedParam]: + ... + + @overload + def __new__(cls: Type[ScalarParam], *args, **kwds) -> ScalarParam: + ... + + @overload + def __new__(cls: Type[IndexedParam], *args, **kwds) -> IndexedParam: + ... + def __new__(cls, *args, **kwds): if cls != Param: return super(Param, cls).__new__(cls) @@ -983,7 +997,7 @@ def _create_objects_for_deepcopy(self, memo, component_list): # between potentially variable GetItemExpression objects and # "constant" GetItemExpression objects. That will need to wait for # the expression rework [JDS; Nov 22]. - def __getitem__(self, args): + def __getitem__(self, args) -> _ParamData: try: return super().__getitem__(args) except: diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index 2dc14460911..c52945dfd30 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -9,6 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +from __future__ import annotations import inspect import itertools import logging @@ -16,6 +17,8 @@ import sys import weakref from pyomo.common.pyomo_typing import overload +from typing import Union, Type, Any +from collections.abc import Iterator from pyomo.common.collections import ComponentSet from pyomo.common.deprecation import deprecated, deprecation_warning, RenamedClass @@ -569,7 +572,7 @@ def isordered(self): def subsets(self, expand_all_set_operators=None): return iter((self,)) - def __iter__(self): + def __iter__(self) -> Iterator[Any]: """Iterate over the set members Raises AttributeError for non-finite sets. This must be @@ -1967,6 +1970,14 @@ class SortedOrder(object): _ValidOrderedAuguments = {True, False, InsertionOrder, SortedOrder} _UnorderedInitializers = {set} + @overload + def __new__(cls: Type[Set], *args, **kwds) -> Union[_SetData, IndexedSet]: + ... + + @overload + def __new__(cls: Type[OrderedScalarSet], *args, **kwds) -> OrderedScalarSet: + ... + def __new__(cls, *args, **kwds): if cls is not Set: return super(Set, cls).__new__(cls) @@ -2373,6 +2384,9 @@ def data(self): "Return a dict containing the data() of each Set in this IndexedSet" return {k: v.data() for k, v in self.items()} + def __getitem__(self, index) -> _SetData: + return super().__getitem__(index) + class FiniteScalarSet(_FiniteSetData, Set): def __init__(self, **kwds): diff --git a/pyomo/core/base/var.py b/pyomo/core/base/var.py index f426c9c4f55..c92a4056667 100644 --- a/pyomo/core/base/var.py +++ b/pyomo/core/base/var.py @@ -9,10 +9,12 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +from __future__ import annotations import logging import sys from pyomo.common.pyomo_typing import overload from weakref import ref as weakref_ref +from typing import Union, Type from pyomo.common.deprecation import RenamedClass from pyomo.common.log import is_debug_set @@ -668,6 +670,18 @@ class Var(IndexedComponent, IndexedComponent_NDArrayMixin): _ComponentDataClass = _GeneralVarData + @overload + def __new__(cls: Type[Var], *args, **kwargs) -> Union[ScalarVar, IndexedVar]: + ... + + @overload + def __new__(cls: Type[ScalarVar], *args, **kwargs) -> ScalarVar: + ... + + @overload + def __new__(cls: Type[IndexedVar], *args, **kwargs) -> IndexedVar: + ... + def __new__(cls, *args, **kwargs): if cls is not Var: return super(Var, cls).__new__(cls) @@ -1046,7 +1060,7 @@ def domain(self, domain): # between potentially variable GetItemExpression objects and # "constant" GetItemExpression objects. That will need to wait for # the expression rework [JDS; Nov 22]. - def __getitem__(self, args): + def __getitem__(self, args) -> _GeneralVarData: try: return super().__getitem__(args) except RuntimeError: From ae5ddeb36428d0a77adeccbd15f9daa6fcce7c4e Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Sun, 3 Mar 2024 05:13:01 -0700 Subject: [PATCH 1306/1797] run black --- pyomo/core/base/block.py | 11 +++++------ pyomo/core/base/constraint.py | 13 ++++++------- pyomo/core/base/param.py | 11 +++++------ pyomo/core/base/set.py | 6 ++---- pyomo/core/base/var.py | 11 ++++------- 5 files changed, 22 insertions(+), 30 deletions(-) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index 9ca2112d498..908e0ef1abd 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -2011,16 +2011,15 @@ class Block(ActiveIndexedComponent): _private_data_initializers = defaultdict(lambda: dict) @overload - def __new__(cls: Type[Block], *args, **kwds) -> Union[ScalarBlock, IndexedBlock]: - ... + def __new__( + cls: Type[Block], *args, **kwds + ) -> Union[ScalarBlock, IndexedBlock]: ... @overload - def __new__(cls: Type[ScalarBlock], *args, **kwds) -> ScalarBlock: - ... + def __new__(cls: Type[ScalarBlock], *args, **kwds) -> ScalarBlock: ... @overload - def __new__(cls: Type[IndexedBlock], *args, **kwds) -> IndexedBlock: - ... + def __new__(cls: Type[IndexedBlock], *args, **kwds) -> IndexedBlock: ... def __new__(cls, *args, **kwds): if cls != Block: diff --git a/pyomo/core/base/constraint.py b/pyomo/core/base/constraint.py index dcc90fd6280..a36bc679e49 100644 --- a/pyomo/core/base/constraint.py +++ b/pyomo/core/base/constraint.py @@ -731,16 +731,15 @@ class Infeasible(object): Satisfied = Feasible @overload - def __new__(cls: Type[Constraint], *args, **kwds) -> Union[ScalarConstraint, IndexedConstraint]: - ... + def __new__( + cls: Type[Constraint], *args, **kwds + ) -> Union[ScalarConstraint, IndexedConstraint]: ... @overload - def __new__(cls: Type[ScalarConstraint], *args, **kwds) -> ScalarConstraint: - ... + def __new__(cls: Type[ScalarConstraint], *args, **kwds) -> ScalarConstraint: ... @overload - def __new__(cls: Type[IndexedConstraint], *args, **kwds) -> IndexedConstraint: - ... + def __new__(cls: Type[IndexedConstraint], *args, **kwds) -> IndexedConstraint: ... def __new__(cls, *args, **kwds): if cls != Constraint: @@ -1033,7 +1032,7 @@ class IndexedConstraint(Constraint): def add(self, index, expr): """Add a constraint with a given index.""" return self.__setitem__(index, expr) - + def __getitem__(self, index) -> _GeneralConstraintData: return super().__getitem__(index) diff --git a/pyomo/core/base/param.py b/pyomo/core/base/param.py index dde390661ab..5fcaf92b25a 100644 --- a/pyomo/core/base/param.py +++ b/pyomo/core/base/param.py @@ -294,16 +294,15 @@ class NoValue(object): pass @overload - def __new__(cls: Type[Param], *args, **kwds) -> Union[ScalarParam, IndexedParam]: - ... + def __new__( + cls: Type[Param], *args, **kwds + ) -> Union[ScalarParam, IndexedParam]: ... @overload - def __new__(cls: Type[ScalarParam], *args, **kwds) -> ScalarParam: - ... + def __new__(cls: Type[ScalarParam], *args, **kwds) -> ScalarParam: ... @overload - def __new__(cls: Type[IndexedParam], *args, **kwds) -> IndexedParam: - ... + def __new__(cls: Type[IndexedParam], *args, **kwds) -> IndexedParam: ... def __new__(cls, *args, **kwds): if cls != Param: diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index c52945dfd30..6373af97683 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -1971,12 +1971,10 @@ class SortedOrder(object): _UnorderedInitializers = {set} @overload - def __new__(cls: Type[Set], *args, **kwds) -> Union[_SetData, IndexedSet]: - ... + def __new__(cls: Type[Set], *args, **kwds) -> Union[_SetData, IndexedSet]: ... @overload - def __new__(cls: Type[OrderedScalarSet], *args, **kwds) -> OrderedScalarSet: - ... + def __new__(cls: Type[OrderedScalarSet], *args, **kwds) -> OrderedScalarSet: ... def __new__(cls, *args, **kwds): if cls is not Set: diff --git a/pyomo/core/base/var.py b/pyomo/core/base/var.py index c92a4056667..856a2dc0237 100644 --- a/pyomo/core/base/var.py +++ b/pyomo/core/base/var.py @@ -671,16 +671,13 @@ class Var(IndexedComponent, IndexedComponent_NDArrayMixin): _ComponentDataClass = _GeneralVarData @overload - def __new__(cls: Type[Var], *args, **kwargs) -> Union[ScalarVar, IndexedVar]: - ... + def __new__(cls: Type[Var], *args, **kwargs) -> Union[ScalarVar, IndexedVar]: ... @overload - def __new__(cls: Type[ScalarVar], *args, **kwargs) -> ScalarVar: - ... + def __new__(cls: Type[ScalarVar], *args, **kwargs) -> ScalarVar: ... @overload - def __new__(cls: Type[IndexedVar], *args, **kwargs) -> IndexedVar: - ... + def __new__(cls: Type[IndexedVar], *args, **kwargs) -> IndexedVar: ... def __new__(cls, *args, **kwargs): if cls is not Var: @@ -702,7 +699,7 @@ def __init__( dense=True, units=None, name=None, - doc=None + doc=None, ): ... def __init__(self, *args, **kwargs): From b28f4bb7179a614f2532b591e1ff38eb1df4ad6f Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Sun, 3 Mar 2024 05:20:32 -0700 Subject: [PATCH 1307/1797] name conflict --- pyomo/core/base/set.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index 6373af97683..b8ddae14e9f 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -17,7 +17,7 @@ import sys import weakref from pyomo.common.pyomo_typing import overload -from typing import Union, Type, Any +from typing import Union, Type, Any as typingAny from collections.abc import Iterator from pyomo.common.collections import ComponentSet @@ -572,7 +572,7 @@ def isordered(self): def subsets(self, expand_all_set_operators=None): return iter((self,)) - def __iter__(self) -> Iterator[Any]: + def __iter__(self) -> Iterator[typingAny]: """Iterate over the set members Raises AttributeError for non-finite sets. This must be From b1444017c3fd536b0630dda91f9290b6e3043798 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 3 Mar 2024 09:07:15 -0700 Subject: [PATCH 1308/1797] 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 1309/1797] 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 1310/1797] 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 1311/1797] 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 1312/1797] 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 1313/1797] 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 1314/1797] 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 1e609c067dc01e7cd6ea8c65a347e540f0534d02 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 4 Mar 2024 13:54:23 -0700 Subject: [PATCH 1315/1797] run black --- pyomo/contrib/simplification/simplify.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/simplification/simplify.py b/pyomo/contrib/simplification/simplify.py index b8cc4995f91..80e7c42549c 100644 --- a/pyomo/contrib/simplification/simplify.py +++ b/pyomo/contrib/simplification/simplify.py @@ -55,7 +55,11 @@ def simplify(self, expr: NumericExpression): return simplify_with_ginac(expr, self.gi) else: if not self.suppress_no_ginac_warnings: - msg = f"GiNaC does not seem to be available. Using SymPy. Note that the GiNac interface is significantly faster." + msg = ( + "GiNaC does not seem to be available. Using SymPy. " + + "Note that the GiNac interface is significantly faster." + ) logger.warning(msg) warnings.warn(msg) + self.suppress_no_ginac_warnings = True return simplify_with_sympy(expr) From 42fd802267b5f4e25606f844a966cceb569cbdd7 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 4 Mar 2024 14:20:57 -0700 Subject: [PATCH 1316/1797] 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 1317/1797] 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 1318/1797] 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 1319/1797] 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 1320/1797] 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 9d9c98ad02699535f0a446fb90683f8a3356f3b4 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 5 Mar 2024 20:05:43 -0700 Subject: [PATCH 1321/1797] simplify initialization of exit node dispatchers --- pyomo/repn/linear.py | 63 ++++++++--------------------- pyomo/repn/quadratic.py | 89 ++++++++--------------------------------- 2 files changed, 33 insertions(+), 119 deletions(-) diff --git a/pyomo/repn/linear.py b/pyomo/repn/linear.py index 6ab4abfdaf5..68dda60d3c0 100644 --- a/pyomo/repn/linear.py +++ b/pyomo/repn/linear.py @@ -208,9 +208,8 @@ def _handle_negation_ANY(visitor, node, arg): _exit_node_handlers[NegationExpression] = { + None: _handle_negation_ANY, (_CONSTANT,): _handle_negation_constant, - (_LINEAR,): _handle_negation_ANY, - (_GENERAL,): _handle_negation_ANY, } # @@ -284,15 +283,12 @@ def _handle_product_nonlinear(visitor, node, arg1, arg2): _exit_node_handlers[ProductExpression] = { + None: _handle_product_nonlinear, (_CONSTANT, _CONSTANT): _handle_product_constant_constant, (_CONSTANT, _LINEAR): _handle_product_constant_ANY, (_CONSTANT, _GENERAL): _handle_product_constant_ANY, (_LINEAR, _CONSTANT): _handle_product_ANY_constant, - (_LINEAR, _LINEAR): _handle_product_nonlinear, - (_LINEAR, _GENERAL): _handle_product_nonlinear, (_GENERAL, _CONSTANT): _handle_product_ANY_constant, - (_GENERAL, _LINEAR): _handle_product_nonlinear, - (_GENERAL, _GENERAL): _handle_product_nonlinear, } _exit_node_handlers[MonomialTermExpression] = _exit_node_handlers[ProductExpression] @@ -317,15 +313,10 @@ def _handle_division_nonlinear(visitor, node, arg1, arg2): _exit_node_handlers[DivisionExpression] = { + None: _handle_division_nonlinear, (_CONSTANT, _CONSTANT): _handle_division_constant_constant, - (_CONSTANT, _LINEAR): _handle_division_nonlinear, - (_CONSTANT, _GENERAL): _handle_division_nonlinear, (_LINEAR, _CONSTANT): _handle_division_ANY_constant, - (_LINEAR, _LINEAR): _handle_division_nonlinear, - (_LINEAR, _GENERAL): _handle_division_nonlinear, (_GENERAL, _CONSTANT): _handle_division_ANY_constant, - (_GENERAL, _LINEAR): _handle_division_nonlinear, - (_GENERAL, _GENERAL): _handle_division_nonlinear, } # @@ -366,15 +357,10 @@ def _handle_pow_nonlinear(visitor, node, arg1, arg2): _exit_node_handlers[PowExpression] = { + None: _handle_pow_nonlinear, (_CONSTANT, _CONSTANT): _handle_pow_constant_constant, - (_CONSTANT, _LINEAR): _handle_pow_nonlinear, - (_CONSTANT, _GENERAL): _handle_pow_nonlinear, (_LINEAR, _CONSTANT): _handle_pow_ANY_constant, - (_LINEAR, _LINEAR): _handle_pow_nonlinear, - (_LINEAR, _GENERAL): _handle_pow_nonlinear, (_GENERAL, _CONSTANT): _handle_pow_ANY_constant, - (_GENERAL, _LINEAR): _handle_pow_nonlinear, - (_GENERAL, _GENERAL): _handle_pow_nonlinear, } # @@ -397,9 +383,8 @@ def _handle_unary_nonlinear(visitor, node, arg): _exit_node_handlers[UnaryFunctionExpression] = { + None: _handle_unary_nonlinear, (_CONSTANT,): _handle_unary_constant, - (_LINEAR,): _handle_unary_nonlinear, - (_GENERAL,): _handle_unary_nonlinear, } _exit_node_handlers[AbsExpression] = _exit_node_handlers[UnaryFunctionExpression] @@ -422,9 +407,8 @@ def _handle_named_ANY(visitor, node, arg1): _exit_node_handlers[Expression] = { + None: _handle_named_ANY, (_CONSTANT,): _handle_named_constant, - (_LINEAR,): _handle_named_ANY, - (_GENERAL,): _handle_named_ANY, } # @@ -457,12 +441,7 @@ def _handle_expr_if_nonlinear(visitor, node, arg1, arg2, arg3): return _GENERAL, ans -_exit_node_handlers[Expr_ifExpression] = { - (i, j, k): _handle_expr_if_nonlinear - for i in (_LINEAR, _GENERAL) - for j in (_CONSTANT, _LINEAR, _GENERAL) - for k in (_CONSTANT, _LINEAR, _GENERAL) -} +_exit_node_handlers[Expr_ifExpression] = {None: _handle_expr_if_nonlinear} for j in (_CONSTANT, _LINEAR, _GENERAL): for k in (_CONSTANT, _LINEAR, _GENERAL): _exit_node_handlers[Expr_ifExpression][_CONSTANT, j, k] = _handle_expr_if_const @@ -495,11 +474,9 @@ def _handle_equality_general(visitor, node, arg1, arg2): _exit_node_handlers[EqualityExpression] = { - (i, j): _handle_equality_general - for i in (_CONSTANT, _LINEAR, _GENERAL) - for j in (_CONSTANT, _LINEAR, _GENERAL) + None: _handle_equality_general, + (_CONSTANT, _CONSTANT): _handle_equality_const, } -_exit_node_handlers[EqualityExpression][_CONSTANT, _CONSTANT] = _handle_equality_const def _handle_inequality_const(visitor, node, arg1, arg2): @@ -525,13 +502,9 @@ def _handle_inequality_general(visitor, node, arg1, arg2): _exit_node_handlers[InequalityExpression] = { - (i, j): _handle_inequality_general - for i in (_CONSTANT, _LINEAR, _GENERAL) - for j in (_CONSTANT, _LINEAR, _GENERAL) + None: _handle_inequality_general, + (_CONSTANT, _CONSTANT): _handle_inequality_const, } -_exit_node_handlers[InequalityExpression][ - _CONSTANT, _CONSTANT -] = _handle_inequality_const def _handle_ranged_const(visitor, node, arg1, arg2, arg3): @@ -562,14 +535,9 @@ def _handle_ranged_general(visitor, node, arg1, arg2, arg3): _exit_node_handlers[RangedExpression] = { - (i, j, k): _handle_ranged_general - for i in (_CONSTANT, _LINEAR, _GENERAL) - for j in (_CONSTANT, _LINEAR, _GENERAL) - for k in (_CONSTANT, _LINEAR, _GENERAL) + None: _handle_ranged_general, + (_CONSTANT, _CONSTANT, _CONSTANT): _handle_ranged_const, } -_exit_node_handlers[RangedExpression][ - _CONSTANT, _CONSTANT, _CONSTANT -] = _handle_ranged_const class LinearBeforeChildDispatcher(BeforeChildDispatcher): @@ -750,7 +718,10 @@ def _initialize_exit_node_dispatcher(exit_handlers): exit_dispatcher = {} for cls, handlers in exit_handlers.items(): for args, fcn in handlers.items(): - exit_dispatcher[(cls, *args)] = fcn + if args is None: + exit_dispatcher[cls] = fcn + else: + exit_dispatcher[(cls, *args)] = fcn return exit_dispatcher diff --git a/pyomo/repn/quadratic.py b/pyomo/repn/quadratic.py index c538d1efc7f..2ff3276f7f5 100644 --- a/pyomo/repn/quadratic.py +++ b/pyomo/repn/quadratic.py @@ -284,18 +284,11 @@ def _handle_product_nonlinear(visitor, node, arg1, arg2): _exit_node_handlers[ProductExpression].update( { + None: _handle_product_nonlinear, (_CONSTANT, _QUADRATIC): linear._handle_product_constant_ANY, - (_LINEAR, _QUADRATIC): _handle_product_nonlinear, - (_QUADRATIC, _QUADRATIC): _handle_product_nonlinear, - (_GENERAL, _QUADRATIC): _handle_product_nonlinear, (_QUADRATIC, _CONSTANT): linear._handle_product_ANY_constant, - (_QUADRATIC, _LINEAR): _handle_product_nonlinear, - (_QUADRATIC, _GENERAL): _handle_product_nonlinear, # Replace handler from the linear walker (_LINEAR, _LINEAR): _handle_product_linear_linear, - (_GENERAL, _GENERAL): _handle_product_nonlinear, - (_GENERAL, _LINEAR): _handle_product_nonlinear, - (_LINEAR, _GENERAL): _handle_product_nonlinear, } ) @@ -303,15 +296,7 @@ def _handle_product_nonlinear(visitor, node, arg1, arg2): # DIVISION # _exit_node_handlers[DivisionExpression].update( - { - (_CONSTANT, _QUADRATIC): linear._handle_division_nonlinear, - (_LINEAR, _QUADRATIC): linear._handle_division_nonlinear, - (_QUADRATIC, _QUADRATIC): linear._handle_division_nonlinear, - (_GENERAL, _QUADRATIC): linear._handle_division_nonlinear, - (_QUADRATIC, _CONSTANT): linear._handle_division_ANY_constant, - (_QUADRATIC, _LINEAR): linear._handle_division_nonlinear, - (_QUADRATIC, _GENERAL): linear._handle_division_nonlinear, - } + {(_QUADRATIC, _CONSTANT): linear._handle_division_ANY_constant} ) @@ -319,84 +304,42 @@ def _handle_product_nonlinear(visitor, node, arg1, arg2): # EXPONENTIATION # _exit_node_handlers[PowExpression].update( - { - (_CONSTANT, _QUADRATIC): linear._handle_pow_nonlinear, - (_LINEAR, _QUADRATIC): linear._handle_pow_nonlinear, - (_QUADRATIC, _QUADRATIC): linear._handle_pow_nonlinear, - (_GENERAL, _QUADRATIC): linear._handle_pow_nonlinear, - (_QUADRATIC, _CONSTANT): linear._handle_pow_ANY_constant, - (_QUADRATIC, _LINEAR): linear._handle_pow_nonlinear, - (_QUADRATIC, _GENERAL): linear._handle_pow_nonlinear, - } + {(_QUADRATIC, _CONSTANT): linear._handle_pow_ANY_constant} ) # # ABS and UNARY handlers # -_exit_node_handlers[AbsExpression][(_QUADRATIC,)] = linear._handle_unary_nonlinear -_exit_node_handlers[UnaryFunctionExpression][ - (_QUADRATIC,) -] = linear._handle_unary_nonlinear +# (no changes needed) # # NAMED EXPRESSION handlers # -_exit_node_handlers[Expression][(_QUADRATIC,)] = linear._handle_named_ANY +# (no changes needed) # # EXPR_IF handlers # # Note: it is easier to just recreate the entire data structure, rather # than update it -_exit_node_handlers[Expr_ifExpression] = { - (i, j, k): linear._handle_expr_if_nonlinear - for i in (_LINEAR, _QUADRATIC, _GENERAL) - for j in (_CONSTANT, _LINEAR, _QUADRATIC, _GENERAL) - for k in (_CONSTANT, _LINEAR, _QUADRATIC, _GENERAL) -} -for j in (_CONSTANT, _LINEAR, _QUADRATIC, _GENERAL): - for k in (_CONSTANT, _LINEAR, _QUADRATIC, _GENERAL): - _exit_node_handlers[Expr_ifExpression][ - _CONSTANT, j, k - ] = linear._handle_expr_if_const - -# -# RELATIONAL handlers -# -_exit_node_handlers[EqualityExpression].update( +_exit_node_handlers[Expr_ifExpression].update( { - (_CONSTANT, _QUADRATIC): linear._handle_equality_general, - (_LINEAR, _QUADRATIC): linear._handle_equality_general, - (_QUADRATIC, _QUADRATIC): linear._handle_equality_general, - (_GENERAL, _QUADRATIC): linear._handle_equality_general, - (_QUADRATIC, _CONSTANT): linear._handle_equality_general, - (_QUADRATIC, _LINEAR): linear._handle_equality_general, - (_QUADRATIC, _GENERAL): linear._handle_equality_general, + (_CONSTANT, i, _QUADRATIC): linear._handle_expr_if_const + for i in (_CONSTANT, _LINEAR, _QUADRATIC, _GENERAL) } ) -_exit_node_handlers[InequalityExpression].update( +_exit_node_handlers[Expr_ifExpression].update( { - (_CONSTANT, _QUADRATIC): linear._handle_inequality_general, - (_LINEAR, _QUADRATIC): linear._handle_inequality_general, - (_QUADRATIC, _QUADRATIC): linear._handle_inequality_general, - (_GENERAL, _QUADRATIC): linear._handle_inequality_general, - (_QUADRATIC, _CONSTANT): linear._handle_inequality_general, - (_QUADRATIC, _LINEAR): linear._handle_inequality_general, - (_QUADRATIC, _GENERAL): linear._handle_inequality_general, - } -) -_exit_node_handlers[RangedExpression].update( - { - (_CONSTANT, _QUADRATIC): linear._handle_ranged_general, - (_LINEAR, _QUADRATIC): linear._handle_ranged_general, - (_QUADRATIC, _QUADRATIC): linear._handle_ranged_general, - (_GENERAL, _QUADRATIC): linear._handle_ranged_general, - (_QUADRATIC, _CONSTANT): linear._handle_ranged_general, - (_QUADRATIC, _LINEAR): linear._handle_ranged_general, - (_QUADRATIC, _GENERAL): linear._handle_ranged_general, + (_CONSTANT, _QUADRATIC, i): linear._handle_expr_if_const + for i in (_CONSTANT, _LINEAR, _GENERAL) } ) +# +# RELATIONAL handlers +# +# (no changes needed) + class QuadraticRepnVisitor(linear.LinearRepnVisitor): Result = QuadraticRepn From 81f6e273d5585bc7ca29d5b3531e0bdb91e2e8eb Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 5 Mar 2024 20:06:55 -0700 Subject: [PATCH 1322/1797] rework ExitNodeDispatcher.__missing__ to make default fallback cleaner --- pyomo/repn/util.py | 55 +++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index 49cca32eaf9..7bba4041c70 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -401,6 +401,13 @@ def __init__(self, *args, **kwargs): def __missing__(self, key): if type(key) is tuple: node_class = key[0] + node_args = key[1:] + # Only lookup/cache argument-specific handlers for unary, + # binary and ternary operators + if len(key) > 3: + key = node_class + if key in self: + return self[key] else: node_class = key bases = node_class.__mro__ @@ -412,35 +419,33 @@ def __missing__(self, key): 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: + if key is not node_class: + if (base_type,) + node_args in self: + fcn = self[(base_type,) + node_args] + break + if 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." - ) + break if fcn is None: - fcn = self.unexpected_expression_type - if cache: - self[key] = fcn + partial_matches = set( + k[0] for k in self if type(k) is tuple and issubclass(node_class, k[0]) + ) + for base_type in node_class.__mro__: + if node_class is not key: + key = (base_type,) + node_args + if base_type in partial_matches: + raise DeveloperError( + f"Base expression key '{key}' not found when inserting " + f"dispatcher for node '{node_class.__name__}' while walking " + "expression tree." + ) + raise DeveloperError( + f"Unexpected expression node type '{node_class.__name__}' " + f"found while walking expression tree in {type(self).__name__}." + ) + self[key] = fcn return fcn - def unexpected_expression_type(self, visitor, node, *arg): - raise DeveloperError( - f"Unexpected expression node type '{type(node).__name__}' " - f"found while walking expression tree in {type(visitor).__name__}." - ) - def apply_node_operation(node, args): try: From 4c9d44b46966372a4031ca66e9110f907fbc55e6 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 5 Mar 2024 20:07:27 -0700 Subject: [PATCH 1323/1797] Minor dispatcher performance improvement --- pyomo/repn/linear.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/pyomo/repn/linear.py b/pyomo/repn/linear.py index 68dda60d3c0..ed1d8f6b32f 100644 --- a/pyomo/repn/linear.py +++ b/pyomo/repn/linear.py @@ -218,20 +218,18 @@ def _handle_negation_ANY(visitor, node, arg): def _handle_product_constant_constant(visitor, node, arg1, arg2): - _, arg1 = arg1 - _, arg2 = arg2 - ans = arg1 * arg2 + ans = arg1[1] * arg2[1] if ans != ans: - if not arg1 or not arg2: + if not arg1[1] or not arg2[1]: deprecation_warning( - f"Encountered {str(arg1)}*{str(arg2)} in expression tree. " + f"Encountered {str(arg1[1])}*{str(arg2[1])} 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 _, 0 - return _, arg1 * arg2 + return _CONSTANT, 0 + return _CONSTANT, ans def _handle_product_constant_ANY(visitor, node, arg1, arg2): @@ -324,8 +322,7 @@ def _handle_division_nonlinear(visitor, node, arg1, arg2): # -def _handle_pow_constant_constant(visitor, node, *args): - arg1, arg2 = args +def _handle_pow_constant_constant(visitor, node, arg1, arg2): ans = apply_node_operation(node, (arg1[1], arg2[1])) if ans.__class__ in native_complex_types: ans = complex_number_error(ans, visitor, node) From 795bb26fda37024bef96467b484ef138ebbfaa17 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 5 Mar 2024 20:08:17 -0700 Subject: [PATCH 1324/1797] update tests: we no longer cache the unknown error handler --- 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 b5e4cc4facf..e0fea0fb45c 100644 --- a/pyomo/repn/tests/test_util.py +++ b/pyomo/repn/tests/test_util.py @@ -718,16 +718,14 @@ class UnknownExpression(NumericExpression): DeveloperError, r".*Unexpected expression node type 'UnknownExpression'" ): end[node.__class__](None, node, *node.args) - self.assertEqual(len(end), 9) - self.assertIn(UnknownExpression, end) + 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), 10) - self.assertIn((UnknownExpression, 6, 7), end) + self.assertEqual(len(end), 8) def test_BeforeChildDispatcher_registration(self): class BeforeChildDispatcherTester(BeforeChildDispatcher): From 44b7ef2fca90843d807ff818ce36efea78a09713 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 5 Mar 2024 21:32:05 -0700 Subject: [PATCH 1325/1797] 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 8730e17a67541469c84a8d955ac5868c34da17a9 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 5 Mar 2024 21:56:42 -0700 Subject: [PATCH 1326/1797] restore unexpected_expression_type hook --- pyomo/repn/util.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index 7bba4041c70..a51ee1c6d64 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -439,13 +439,16 @@ def __missing__(self, key): f"dispatcher for node '{node_class.__name__}' while walking " "expression tree." ) - raise DeveloperError( - f"Unexpected expression node type '{node_class.__name__}' " - f"found while walking expression tree in {type(self).__name__}." - ) + return self.unexpected_expression_type self[key] = fcn return fcn + def unexpected_expression_type(self, visitor, node, *args): + raise DeveloperError( + f"Unexpected expression node type '{type(node).__name__}' " + f"found while walking expression tree in {type(self).__name__}." + ) + def apply_node_operation(node, args): try: From 53fe3455429b8ee004a05d6d643cc2955f17602b Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 5 Mar 2024 22:22:09 -0700 Subject: [PATCH 1327/1797] Improve automatic flattening of LinearExpression args --- pyomo/core/expr/numeric_expr.py | 33 +++++++------ pyomo/core/expr/template_expr.py | 6 +-- .../unit/test_numeric_expr_dispatcher.py | 8 ++-- .../unit/test_numeric_expr_zerofilter.py | 8 ++-- pyomo/core/tests/unit/test_template_expr.py | 48 +++++++++---------- 5 files changed, 53 insertions(+), 50 deletions(-) diff --git a/pyomo/core/expr/numeric_expr.py b/pyomo/core/expr/numeric_expr.py index c1199ffdcad..e8f7227208c 100644 --- a/pyomo/core/expr/numeric_expr.py +++ b/pyomo/core/expr/numeric_expr.py @@ -2283,8 +2283,11 @@ def _iadd_mutablenpvsum_mutable(a, b): def _iadd_mutablenpvsum_native(a, b): if not b: return a - a._args_.append(b) - a._nargs += 1 + if a._args_ and a._args_[-1].__class__ in native_numeric_types: + a._args_[-1] += b + else: + a._args_.append(b) + a._nargs += 1 return a @@ -2296,9 +2299,7 @@ def _iadd_mutablenpvsum_npv(a, b): def _iadd_mutablenpvsum_param(a, b): if b.is_constant(): - b = b.value - if not b: - return a + return _iadd_mutablesum_native(a, b.value) a._args_.append(b) a._nargs += 1 return a @@ -2379,8 +2380,11 @@ def _iadd_mutablelinear_mutable(a, b): def _iadd_mutablelinear_native(a, b): if not b: return a - a._args_.append(b) - a._nargs += 1 + if a._args_ and a._args_[-1].__class__ in native_numeric_types: + a._args_[-1] += b + else: + a._args_.append(b) + a._nargs += 1 return a @@ -2392,9 +2396,7 @@ def _iadd_mutablelinear_npv(a, b): def _iadd_mutablelinear_param(a, b): if b.is_constant(): - b = b.value - if not b: - return a + return _iadd_mutablesum_native(a, b.value) a._args_.append(b) a._nargs += 1 return a @@ -2478,8 +2480,11 @@ def _iadd_mutablesum_mutable(a, b): def _iadd_mutablesum_native(a, b): if not b: return a - a._args_.append(b) - a._nargs += 1 + if a._args_ and a._args_[-1].__class__ in native_numeric_types: + a._args_[-1] += b + else: + a._args_.append(b) + a._nargs += 1 return a @@ -2491,9 +2496,7 @@ def _iadd_mutablesum_npv(a, b): def _iadd_mutablesum_param(a, b): if b.is_constant(): - b = b.value - if not b: - return a + return _iadd_mutablesum_native(a, b.value) a._args_.append(b) a._nargs += 1 return a diff --git a/pyomo/core/expr/template_expr.py b/pyomo/core/expr/template_expr.py index f65a1f2b9b0..f982ef38d1d 100644 --- a/pyomo/core/expr/template_expr.py +++ b/pyomo/core/expr/template_expr.py @@ -116,7 +116,7 @@ def _to_string(self, values, verbose, smap): return "%s[%s]" % (values[0], ','.join(values[1:])) def _resolve_template(self, args): - return args[0].__getitem__(tuple(args[1:])) + return args[0][*args[1:]] def _apply_operation(self, result): args = tuple( @@ -127,7 +127,7 @@ def _apply_operation(self, result): ) for arg in result[1:] ) - return result[0].__getitem__(tuple(result[1:])) + return result[0][*result[1:]] class Numeric_GetItemExpression(GetItemExpression, NumericExpression): @@ -273,7 +273,7 @@ def _to_string(self, values, verbose, smap): return "%s.%s" % (values[0], attr) def _resolve_template(self, args): - return getattr(*tuple(args)) + return getattr(*args) class Numeric_GetAttrExpression(GetAttrExpression, NumericExpression): diff --git a/pyomo/core/tests/unit/test_numeric_expr_dispatcher.py b/pyomo/core/tests/unit/test_numeric_expr_dispatcher.py index 3787f00de47..7c6e2af9974 100644 --- a/pyomo/core/tests/unit/test_numeric_expr_dispatcher.py +++ b/pyomo/core/tests/unit/test_numeric_expr_dispatcher.py @@ -6548,11 +6548,11 @@ def test_mutable_nvp_iadd(self): (mutable_npv, self.invalid, NotImplemented), (mutable_npv, self.asbinary, _MutableLinearExpression([10, self.mon_bin])), (mutable_npv, self.zero, _MutableNPVSumExpression([10])), - (mutable_npv, self.one, _MutableNPVSumExpression([10, 1])), + (mutable_npv, self.one, _MutableNPVSumExpression([11])), # 4: - (mutable_npv, self.native, _MutableNPVSumExpression([10, 5])), + (mutable_npv, self.native, _MutableNPVSumExpression([15])), (mutable_npv, self.npv, _MutableNPVSumExpression([10, self.npv])), - (mutable_npv, self.param, _MutableNPVSumExpression([10, 6])), + (mutable_npv, self.param, _MutableNPVSumExpression([16])), ( mutable_npv, self.param_mut, @@ -6592,7 +6592,7 @@ def test_mutable_nvp_iadd(self): _MutableSumExpression([10] + self.mutable_l2.args), ), (mutable_npv, self.param0, _MutableNPVSumExpression([10])), - (mutable_npv, self.param1, _MutableNPVSumExpression([10, 1])), + (mutable_npv, self.param1, _MutableNPVSumExpression([11])), # 20: (mutable_npv, self.mutable_l3, _MutableNPVSumExpression([10, self.npv])), ] diff --git a/pyomo/core/tests/unit/test_numeric_expr_zerofilter.py b/pyomo/core/tests/unit/test_numeric_expr_zerofilter.py index 162d664e0f8..34d2e1cc2c2 100644 --- a/pyomo/core/tests/unit/test_numeric_expr_zerofilter.py +++ b/pyomo/core/tests/unit/test_numeric_expr_zerofilter.py @@ -6076,11 +6076,11 @@ def test_mutable_nvp_iadd(self): (mutable_npv, self.invalid, NotImplemented), (mutable_npv, self.asbinary, _MutableLinearExpression([10, self.mon_bin])), (mutable_npv, self.zero, _MutableNPVSumExpression([10])), - (mutable_npv, self.one, _MutableNPVSumExpression([10, 1])), + (mutable_npv, self.one, _MutableNPVSumExpression([11])), # 4: - (mutable_npv, self.native, _MutableNPVSumExpression([10, 5])), + (mutable_npv, self.native, _MutableNPVSumExpression([15])), (mutable_npv, self.npv, _MutableNPVSumExpression([10, self.npv])), - (mutable_npv, self.param, _MutableNPVSumExpression([10, 6])), + (mutable_npv, self.param, _MutableNPVSumExpression([16])), ( mutable_npv, self.param_mut, @@ -6120,7 +6120,7 @@ def test_mutable_nvp_iadd(self): _MutableSumExpression([10] + self.mutable_l2.args), ), (mutable_npv, self.param0, _MutableNPVSumExpression([10])), - (mutable_npv, self.param1, _MutableNPVSumExpression([10, 1])), + (mutable_npv, self.param1, _MutableNPVSumExpression([11])), # 20: (mutable_npv, self.mutable_l3, _MutableNPVSumExpression([10, self.npv])), ] diff --git a/pyomo/core/tests/unit/test_template_expr.py b/pyomo/core/tests/unit/test_template_expr.py index 4f255e3567a..4c872e1e11d 100644 --- a/pyomo/core/tests/unit/test_template_expr.py +++ b/pyomo/core/tests/unit/test_template_expr.py @@ -490,14 +490,14 @@ def c(m): self.assertEqual( str(resolve_template(template)), 'x[1,1,10] + ' - '(x[2,1,10] + x[2,1,20]) + ' - '(x[3,1,10] + x[3,1,20] + x[3,1,30]) + ' - '(x[1,2,10]) + ' - '(x[2,2,10] + x[2,2,20]) + ' - '(x[3,2,10] + x[3,2,20] + x[3,2,30]) + ' - '(x[1,3,10]) + ' - '(x[2,3,10] + x[2,3,20]) + ' - '(x[3,3,10] + x[3,3,20] + x[3,3,30]) <= 0', + 'x[2,1,10] + x[2,1,20] + ' + 'x[3,1,10] + x[3,1,20] + x[3,1,30] + ' + 'x[1,2,10] + ' + 'x[2,2,10] + x[2,2,20] + ' + 'x[3,2,10] + x[3,2,20] + x[3,2,30] + ' + 'x[1,3,10] + ' + 'x[2,3,10] + x[2,3,20] + ' + 'x[3,3,10] + x[3,3,20] + x[3,3,30] <= 0', ) def test_multidim_nested_sum_rule(self): @@ -566,14 +566,14 @@ def c(m): self.assertEqual( str(resolve_template(template)), 'x[1,1,10] + ' - '(x[2,1,10] + x[2,1,20]) + ' - '(x[3,1,10] + x[3,1,20] + x[3,1,30]) + ' - '(x[1,2,10]) + ' - '(x[2,2,10] + x[2,2,20]) + ' - '(x[3,2,10] + x[3,2,20] + x[3,2,30]) + ' - '(x[1,3,10]) + ' - '(x[2,3,10] + x[2,3,20]) + ' - '(x[3,3,10] + x[3,3,20] + x[3,3,30]) <= 0', + 'x[2,1,10] + x[2,1,20] + ' + 'x[3,1,10] + x[3,1,20] + x[3,1,30] + ' + 'x[1,2,10] + ' + 'x[2,2,10] + x[2,2,20] + ' + 'x[3,2,10] + x[3,2,20] + x[3,2,30] + ' + 'x[1,3,10] + ' + 'x[2,3,10] + x[2,3,20] + ' + 'x[3,3,10] + x[3,3,20] + x[3,3,30] <= 0', ) def test_multidim_nested_getattr_sum_rule(self): @@ -609,14 +609,14 @@ def c(m): self.assertEqual( str(resolve_template(template)), 'x[1,1,10] + ' - '(x[2,1,10] + x[2,1,20]) + ' - '(x[3,1,10] + x[3,1,20] + x[3,1,30]) + ' - '(x[1,2,10]) + ' - '(x[2,2,10] + x[2,2,20]) + ' - '(x[3,2,10] + x[3,2,20] + x[3,2,30]) + ' - '(x[1,3,10]) + ' - '(x[2,3,10] + x[2,3,20]) + ' - '(x[3,3,10] + x[3,3,20] + x[3,3,30]) <= 0', + 'x[2,1,10] + x[2,1,20] + ' + 'x[3,1,10] + x[3,1,20] + x[3,1,30] + ' + 'x[1,2,10] + ' + 'x[2,2,10] + x[2,2,20] + ' + 'x[3,2,10] + x[3,2,20] + x[3,2,30] + ' + 'x[1,3,10] + ' + 'x[2,3,10] + x[2,3,20] + ' + 'x[3,3,10] + x[3,3,20] + x[3,3,30] <= 0', ) def test_eval_getattr(self): From ac6949244d4fea5ae7e4b50750a1fb3d783c3fbd Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 5 Mar 2024 22:23:03 -0700 Subject: [PATCH 1328/1797] bugfix: resolution of TemplateSumExpression --- pyomo/core/expr/template_expr.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/pyomo/core/expr/template_expr.py b/pyomo/core/expr/template_expr.py index f982ef38d1d..6ac4c8c041f 100644 --- a/pyomo/core/expr/template_expr.py +++ b/pyomo/core/expr/template_expr.py @@ -19,11 +19,12 @@ from pyomo.core.expr.base import ExpressionBase, ExpressionArgs_Mixin, NPV_Mixin from pyomo.core.expr.logical_expr import BooleanExpression from pyomo.core.expr.numeric_expr import ( + ARG_TYPE, NumericExpression, - SumExpression, Numeric_NPV_Mixin, + SumExpression, + mutable_expression, register_arg_type, - ARG_TYPE, _balanced_parens, ) from pyomo.core.expr.numvalue import ( @@ -521,7 +522,15 @@ def _to_string(self, values, verbose, smap): return 'SUM(%s %s)' % (val, iterStr) def _resolve_template(self, args): - return SumExpression(args) + with mutable_expression() as e: + for arg in args: + e += arg + if e.nargs() > 1: + return e + elif not e.nargs(): + return 0 + else: + return e.arg(0) class IndexTemplate(NumericValue): From d46c90df00347927d2d56403db9c7016c0336ab3 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 5 Mar 2024 22:23:23 -0700 Subject: [PATCH 1329/1797] NFC: remove coverage pragma --- pyomo/core/expr/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/core/expr/base.py b/pyomo/core/expr/base.py index f506956e478..6e2066afcc5 100644 --- a/pyomo/core/expr/base.py +++ b/pyomo/core/expr/base.py @@ -360,7 +360,7 @@ def size(self): """ return visitor.sizeof_expression(self) - def _apply_operation(self, result): # pragma: no cover + def _apply_operation(self, result): """ Compute the values of this node given the values of its children. From dd27f662ecfe56f80343736a4839cb58faeaf22a Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Wed, 6 Mar 2024 17:30:17 -0700 Subject: [PATCH 1330/1797] add option to remove bounds from fixed variables to TemporarySubsystemManager --- pyomo/util/subsystems.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/pyomo/util/subsystems.py b/pyomo/util/subsystems.py index 70a0af1b2a7..3e4be46fca0 100644 --- a/pyomo/util/subsystems.py +++ b/pyomo/util/subsystems.py @@ -148,7 +148,14 @@ class TemporarySubsystemManager(object): """ - def __init__(self, to_fix=None, to_deactivate=None, to_reset=None, to_unfix=None): + def __init__( + self, + to_fix=None, + to_deactivate=None, + to_reset=None, + to_unfix=None, + remove_bounds_on_fix=False, + ): """ Arguments --------- @@ -168,6 +175,8 @@ def __init__(self, to_fix=None, to_deactivate=None, to_reset=None, to_unfix=None List of var data objects to be temporarily unfixed. These are restored to their original status on exit from this object's context manager. + remove_bounds_on_fix: Bool + Whether bounds should be removed temporarily for fixed variables """ if to_fix is None: @@ -194,6 +203,8 @@ def __init__(self, to_fix=None, to_deactivate=None, to_reset=None, to_unfix=None self._con_was_active = None self._comp_original_value = None self._var_was_unfixed = None + self._remove_bounds_on_fix = remove_bounds_on_fix + self._fixed_var_bounds = None def __enter__(self): to_fix = self._vars_to_fix @@ -203,8 +214,13 @@ def __enter__(self): self._var_was_fixed = [(var, var.fixed) for var in to_fix + to_unfix] self._con_was_active = [(con, con.active) for con in to_deactivate] self._comp_original_value = [(comp, comp.value) for comp in to_set] + self._fixed_var_bounds = [(var.lb, var.ub) for var in to_fix] for var in self._vars_to_fix: + if self._remove_bounds_on_fix: + # TODO: Potentially override var.domain as well? + var.setlb(None) + var.setub(None) var.fix() for con in self._cons_to_deactivate: @@ -223,6 +239,11 @@ def __exit__(self, ex_type, ex_val, ex_bt): var.fix() else: var.unfix() + if self._remove_bounds_on_fix: + for var, (lb, ub) in zip(self._vars_to_fix, self._fixed_var_bounds): + var.setlb(lb) + var.setub(ub) + for con, was_active in self._con_was_active: if was_active: con.activate() From 1a1c1a88966ebd2066dc6b0418f8c9b36eb0ced6 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Wed, 6 Mar 2024 17:30:39 -0700 Subject: [PATCH 1331/1797] timing calls and option to not use calculate_variable_from_constraint in solve_strongly_connected_components --- .../contrib/incidence_analysis/scc_solver.py | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/scc_solver.py b/pyomo/contrib/incidence_analysis/scc_solver.py index 835e07c7c02..d965fb38203 100644 --- a/pyomo/contrib/incidence_analysis/scc_solver.py +++ b/pyomo/contrib/incidence_analysis/scc_solver.py @@ -11,6 +11,7 @@ import logging +from pyomo.common.timing import HierarchicalTimer from pyomo.core.base.constraint import Constraint from pyomo.util.calc_var_value import calculate_variable_from_constraint from pyomo.util.subsystems import TemporarySubsystemManager, generate_subsystem_blocks @@ -18,6 +19,7 @@ IncidenceGraphInterface, _generate_variables_in_constraints, ) +from pyomo.contrib.incidence_analysis.config import IncidenceMethod _log = logging.getLogger(__name__) @@ -73,7 +75,13 @@ def generate_strongly_connected_components( def solve_strongly_connected_components( - block, solver=None, solve_kwds=None, calc_var_kwds=None + block, + *, + solver=None, + solve_kwds=None, + use_calc_var=False, + calc_var_kwds=None, + timer=None, ): """Solve a square system of variables and equality constraints by solving strongly connected components individually. @@ -98,6 +106,9 @@ def solve_strongly_connected_components( a solve method. solve_kwds: Dictionary Keyword arguments for the solver's solve method + use_calc_var: Bool + Whether to use ``calculate_variable_from_constraint`` for one-by-one + square system solves calc_var_kwds: Dictionary Keyword arguments for calculate_variable_from_constraint @@ -110,25 +121,36 @@ def solve_strongly_connected_components( solve_kwds = {} if calc_var_kwds is None: calc_var_kwds = {} + if timer is None: + timer = HierarchicalTimer() + timer.start("igraph") igraph = IncidenceGraphInterface( - block, active=True, include_fixed=False, include_inequality=False + block, + active=True, + include_fixed=False, + include_inequality=False, + method=IncidenceMethod.ampl_repn, ) + timer.stop("igraph") constraints = igraph.constraints variables = igraph.variables res_list = [] log_blocks = _log.isEnabledFor(logging.DEBUG) + timer.start("generate-scc") for scc, inputs in generate_strongly_connected_components(constraints, variables): - with TemporarySubsystemManager(to_fix=inputs): + timer.stop("generate-scc") + with TemporarySubsystemManager(to_fix=inputs, remove_bounds_on_fix=True): N = len(scc.vars) - if N == 1: + if N == 1 and use_calc_var: if log_blocks: _log.debug(f"Solving 1x1 block: {scc.cons[0].name}.") + timer.start("calc-var-from-con") results = calculate_variable_from_constraint( scc.vars[0], scc.cons[0], **calc_var_kwds ) - res_list.append(results) + timer.stop("calc-var-from-con") else: if solver is None: var_names = [var.name for var in scc.vars.values()][:10] @@ -141,6 +163,10 @@ def solve_strongly_connected_components( ) if log_blocks: _log.debug(f"Solving {N}x{N} block.") + timer.start("scc-subsolver") results = solver.solve(scc, **solve_kwds) - res_list.append(results) + timer.stop("scc-subsolver") + res_list.append(results) + timer.start("generate-scc") + timer.stop("generate-scc") return res_list From 3d7f2c30da36f12b3f49f5cef0f2f887fe19fec2 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Wed, 6 Mar 2024 17:50:17 -0700 Subject: [PATCH 1332/1797] timing calls in generate-scc and option to reuse an incidence graph if one already exists --- .../contrib/incidence_analysis/scc_solver.py | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/scc_solver.py b/pyomo/contrib/incidence_analysis/scc_solver.py index d965fb38203..1d59be0bc71 100644 --- a/pyomo/contrib/incidence_analysis/scc_solver.py +++ b/pyomo/contrib/incidence_analysis/scc_solver.py @@ -26,7 +26,11 @@ def generate_strongly_connected_components( - constraints, variables=None, include_fixed=False + constraints, + variables=None, + include_fixed=False, + igraph=None, + timer=None, ): """Yield in order ``_BlockData`` that each contain the variables and constraints of a single diagonal block in a block lower triangularization @@ -55,23 +59,38 @@ def generate_strongly_connected_components( "input variables" for that block. """ + if timer is None: + timer = HierarchicalTimer() if variables is None: + timer.start("generate-vars") variables = list( - _generate_variables_in_constraints(constraints, include_fixed=include_fixed) + _generate_variables_in_constraints( + constraints, + include_fixed=include_fixed, + #method=IncidenceMethod.ampl_repn + ) ) + timer.stop("generate-vars") assert len(variables) == len(constraints) - igraph = IncidenceGraphInterface() + if igraph is None: + igraph = IncidenceGraphInterface() + timer.start("block-triang") var_blocks, con_blocks = igraph.block_triangularize( variables=variables, constraints=constraints ) + timer.stop("block-triang") subsets = [(cblock, vblock) for vblock, cblock in zip(var_blocks, con_blocks)] + timer.start("subsystem-blocks") for block, inputs in generate_subsystem_blocks( subsets, include_fixed=include_fixed ): + timer.stop("subsystem-blocks") # TODO: How does len scale for reference-to-list? assert len(block.vars) == len(block.cons) yield (block, inputs) + timer.start("subsystem-blocks") + timer.stop("subsystem-blocks") def solve_strongly_connected_components( @@ -139,7 +158,9 @@ def solve_strongly_connected_components( res_list = [] log_blocks = _log.isEnabledFor(logging.DEBUG) timer.start("generate-scc") - for scc, inputs in generate_strongly_connected_components(constraints, variables): + for scc, inputs in generate_strongly_connected_components( + constraints, variables, timer=timer, igraph=igraph + ): timer.stop("generate-scc") with TemporarySubsystemManager(to_fix=inputs, remove_bounds_on_fix=True): N = len(scc.vars) From 3b5aaa66d0abd83064f48762d54f759a52a3a92d Mon Sep 17 00:00:00 2001 From: robbybp Date: Thu, 7 Mar 2024 09:06:56 -0700 Subject: [PATCH 1333/1797] remove unnecessary import --- pyomo/contrib/incidence_analysis/scc_solver.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyomo/contrib/incidence_analysis/scc_solver.py b/pyomo/contrib/incidence_analysis/scc_solver.py index e7ec2bfc75b..b95a9bb66a5 100644 --- a/pyomo/contrib/incidence_analysis/scc_solver.py +++ b/pyomo/contrib/incidence_analysis/scc_solver.py @@ -29,7 +29,6 @@ _log = logging.getLogger(__name__) -from pyomo.common.timing import HierarchicalTimer def generate_strongly_connected_components( constraints, variables=None, From 565a29ecdcbae605f381b3e7cd13ae112843babb Mon Sep 17 00:00:00 2001 From: robbybp Date: Thu, 7 Mar 2024 09:08:11 -0700 Subject: [PATCH 1334/1797] remove unnecessary imports --- pyomo/contrib/incidence_analysis/scc_solver.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/scc_solver.py b/pyomo/contrib/incidence_analysis/scc_solver.py index b95a9bb66a5..5e21631f2ef 100644 --- a/pyomo/contrib/incidence_analysis/scc_solver.py +++ b/pyomo/contrib/incidence_analysis/scc_solver.py @@ -14,11 +14,7 @@ from pyomo.common.timing import HierarchicalTimer from pyomo.core.base.constraint import Constraint from pyomo.util.calc_var_value import calculate_variable_from_constraint -from pyomo.util.subsystems import ( - TemporarySubsystemManager, - generate_subsystem_blocks, - create_subsystem_block, -) +from pyomo.util.subsystems import TemporarySubsystemManager, generate_subsystem_blocks from pyomo.contrib.incidence_analysis.interface import ( IncidenceGraphInterface, _generate_variables_in_constraints, From 1902e8e1f9ce04153a22d324fae5015342eb23ce Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Mar 2024 09:11:35 -0700 Subject: [PATCH 1335/1797] Allow bare variables in LinearExpression nodes --- pyomo/core/expr/numeric_expr.py | 39 ++++++++++++++++-------------- pyomo/repn/linear.py | 38 ++++++++++++++++------------- pyomo/repn/plugins/baron_writer.py | 19 ++++++++++++--- pyomo/repn/plugins/gams_writer.py | 10 +++++++- pyomo/repn/plugins/nl_writer.py | 14 +++++++++++ pyomo/repn/quadratic.py | 25 +++++++------------ pyomo/repn/standard_repn.py | 22 +++++++++++++++++ pyomo/repn/tests/test_linear.py | 2 +- 8 files changed, 112 insertions(+), 57 deletions(-) diff --git a/pyomo/core/expr/numeric_expr.py b/pyomo/core/expr/numeric_expr.py index e8f7227208c..2cf4073b49f 100644 --- a/pyomo/core/expr/numeric_expr.py +++ b/pyomo/core/expr/numeric_expr.py @@ -1298,8 +1298,14 @@ def _build_cache(self): if arg.__class__ is MonomialTermExpression: coef.append(arg._args_[0]) var.append(arg._args_[1]) - else: + elif arg.__class__ in native_numeric_types: const += arg + elif not arg.is_potentially_variable(): + const += arg + else: + assert arg.is_potentially_variable() + coef.append(1) + var.append(arg) LinearExpression._cache = (self, const, coef, var) @property @@ -1325,7 +1331,7 @@ def create_node_with_local_data(self, args, classtype=None): classtype = self.__class__ if type(args) is not list: args = list(args) - for i, arg in enumerate(args): + for arg in args: if arg.__class__ in self._allowable_linear_expr_arg_types: # 99% of the time, the arg type hasn't changed continue @@ -1336,8 +1342,7 @@ def create_node_with_local_data(self, args, classtype=None): # NPV expressions are OK pass elif arg.is_variable_type(): - # vars are OK, but need to be mapped to monomial terms - args[i] = MonomialTermExpression((1, arg)) + # vars are OK continue else: # For anything else, convert this to a general sum @@ -1820,7 +1825,7 @@ def _add_native_param(a, b): def _add_native_var(a, b): if not a: return b - return LinearExpression([a, MonomialTermExpression((1, b))]) + return LinearExpression([a, b]) def _add_native_monomial(a, b): @@ -1871,7 +1876,7 @@ def _add_npv_param(a, b): def _add_npv_var(a, b): - return LinearExpression([a, MonomialTermExpression((1, b))]) + return LinearExpression([a, b]) def _add_npv_monomial(a, b): @@ -1929,7 +1934,7 @@ def _add_param_var(a, b): a = a.value if not a: return b - return LinearExpression([a, MonomialTermExpression((1, b))]) + return LinearExpression([a, b]) def _add_param_monomial(a, b): @@ -1972,11 +1977,11 @@ def _add_param_other(a, b): def _add_var_native(a, b): if not b: return a - return LinearExpression([MonomialTermExpression((1, a)), b]) + return LinearExpression([a, b]) def _add_var_npv(a, b): - return LinearExpression([MonomialTermExpression((1, a)), b]) + return LinearExpression([a, b]) def _add_var_param(a, b): @@ -1984,21 +1989,19 @@ def _add_var_param(a, b): b = b.value if not b: return a - return LinearExpression([MonomialTermExpression((1, a)), b]) + return LinearExpression([a, b]) def _add_var_var(a, b): - return LinearExpression( - [MonomialTermExpression((1, a)), MonomialTermExpression((1, b))] - ) + return LinearExpression([a, b]) def _add_var_monomial(a, b): - return LinearExpression([MonomialTermExpression((1, a)), b]) + return LinearExpression([a, b]) def _add_var_linear(a, b): - return b._trunc_append(MonomialTermExpression((1, a))) + return b._trunc_append(a) def _add_var_sum(a, b): @@ -2033,7 +2036,7 @@ def _add_monomial_param(a, b): def _add_monomial_var(a, b): - return LinearExpression([a, MonomialTermExpression((1, b))]) + return LinearExpression([a, b]) def _add_monomial_monomial(a, b): @@ -2076,7 +2079,7 @@ def _add_linear_param(a, b): def _add_linear_var(a, b): - return a._trunc_append(MonomialTermExpression((1, b))) + return a._trunc_append(b) def _add_linear_monomial(a, b): @@ -2403,7 +2406,7 @@ def _iadd_mutablelinear_param(a, b): def _iadd_mutablelinear_var(a, b): - a._args_.append(MonomialTermExpression((1, b))) + a._args_.append(b) a._nargs += 1 return a diff --git a/pyomo/repn/linear.py b/pyomo/repn/linear.py index 6ab4abfdaf5..d601ccbcd7c 100644 --- a/pyomo/repn/linear.py +++ b/pyomo/repn/linear.py @@ -31,8 +31,8 @@ MonomialTermExpression, LinearExpression, SumExpression, - NPV_SumExpression, ExternalFunctionExpression, + mutable_expression, ) from pyomo.core.expr.relational_expr import ( EqualityExpression, @@ -120,22 +120,14 @@ def to_expression(self, visitor): ans = 0 if self.linear: var_map = visitor.var_map - if len(self.linear) == 1: - vid, coef = next(iter(self.linear.items())) - if coef == 1: - ans += var_map[vid] - elif coef: - ans += MonomialTermExpression((coef, var_map[vid])) - else: - pass - else: - ans += LinearExpression( - [ - MonomialTermExpression((coef, var_map[vid])) - for vid, coef in self.linear.items() - if coef - ] - ) + with mutable_expression() as e: + for vid, coef in self.linear.items(): + if coef: + e += coef * var_map[vid] + if e.nargs() > 1: + ans += e + elif e.nargs() == 1: + ans += e.arg(0) if self.constant: ans += self.constant if self.multiplier != 1: @@ -704,6 +696,18 @@ def _before_linear(visitor, child): linear[_id] = arg1 elif arg.__class__ in native_numeric_types: const += arg + elif arg.is_variable_type(): + _id = id(arg) + if _id not in var_map: + if arg.fixed: + const += visitor.check_constant(arg.value, arg) + continue + LinearBeforeChildDispatcher._record_var(visitor, arg) + linear[_id] = 1 + elif _id in linear: + linear[_id] += 1 + else: + linear[_id] = 1 else: try: const += visitor.check_constant(visitor.evaluate(arg), arg) diff --git a/pyomo/repn/plugins/baron_writer.py b/pyomo/repn/plugins/baron_writer.py index de19b5aad73..ab673b0c1c3 100644 --- a/pyomo/repn/plugins/baron_writer.py +++ b/pyomo/repn/plugins/baron_writer.py @@ -174,15 +174,26 @@ def _monomial_to_string(self, node): return self.smap.getSymbol(var) return ftoa(const, True) + '*' + self.smap.getSymbol(var) + def _var_to_string(self, node): + if node.is_fixed(): + return ftoa(node.value, True) + self.variables.add(id(node)) + return self.smap.getSymbol(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() + if arg.__class__ is EXPR.MonomialTermExpression + else ( + ftoa(arg) + if arg.__class__ in native_numeric_types + else ( + self._var_to_string(arg) + if arg.is_variable_type() + else ftoa(value(arg), True) + ) ) - else ftoa(value(arg)) ) for arg in node.args ] diff --git a/pyomo/repn/plugins/gams_writer.py b/pyomo/repn/plugins/gams_writer.py index 5f94f176762..0756cb64920 100644 --- a/pyomo/repn/plugins/gams_writer.py +++ b/pyomo/repn/plugins/gams_writer.py @@ -183,7 +183,15 @@ def _linear_to_string(self, node): ( self._monomial_to_string(arg) if arg.__class__ is EXPR.MonomialTermExpression - else ftoa(arg, True) + else ( + ftoa(arg, True) + if arg.__class__ in native_numeric_types + else ( + self.smap.getSymbol(arg) + if arg.is_variable_type() and (not arg.fixed or self.output_fixed_variables) + else ftoa(value(arg), True) + ) + ) ) for arg in node.args ] diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index a256cd1b900..b82d4df77e2 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -2780,6 +2780,20 @@ def _before_linear(visitor, child): linear[_id] = arg1 elif arg.__class__ in native_types: const += arg + elif arg.is_variable_type(): + _id = id(arg) + if _id not in var_map: + if arg.fixed: + if _id not in visitor.fixed_vars: + visitor.cache_fixed_var(_id, arg) + const += visitor.fixed_vars[_id] + continue + _before_child_handlers._record_var(visitor, arg) + linear[_id] = 1 + elif _id in linear: + linear[_id] += 1 + else: + linear[_id] = 1 else: try: const += visitor.check_constant(visitor.evaluate(arg), arg) diff --git a/pyomo/repn/quadratic.py b/pyomo/repn/quadratic.py index c538d1efc7f..0ddfda829ed 100644 --- a/pyomo/repn/quadratic.py +++ b/pyomo/repn/quadratic.py @@ -98,22 +98,15 @@ def to_expression(self, visitor): e += coef * (var_map[x1] * var_map[x2]) ans += e if self.linear: - if len(self.linear) == 1: - vid, coef = next(iter(self.linear.items())) - if coef == 1: - ans += var_map[vid] - elif coef: - ans += MonomialTermExpression((coef, var_map[vid])) - else: - pass - else: - ans += LinearExpression( - [ - MonomialTermExpression((coef, var_map[vid])) - for vid, coef in self.linear.items() - if coef - ] - ) + var_map = visitor.var_map + with mutable_expression() as e: + for vid, coef in self.linear.items(): + if coef: + e += coef * var_map[vid] + if e.nargs() > 1: + ans += e + elif e.nargs() == 1: + ans += e.arg(0) if self.constant: ans += self.constant if self.multiplier != 1: diff --git a/pyomo/repn/standard_repn.py b/pyomo/repn/standard_repn.py index 8700872f04f..8600a8a50f6 100644 --- a/pyomo/repn/standard_repn.py +++ b/pyomo/repn/standard_repn.py @@ -321,6 +321,16 @@ def generate_standard_repn( linear_vars[id_] = v elif arg.__class__ in native_numeric_types: C_ += arg + elif arg.is_variable_type(): + if arg.fixed: + C_ += arg.value + continue + id_ = id(arg) + if id_ in linear_coefs: + linear_coefs[id_] += 1 + else: + linear_coefs[id_] = 1 + linear_vars[id_] = arg else: C_ += EXPR.evaluate_expression(arg) else: # compute_values == False @@ -336,6 +346,18 @@ def generate_standard_repn( else: linear_coefs[id_] = c linear_vars[id_] = v + elif arg.__class__ in native_numeric_types: + C_ += arg + elif arg.is_variable_type(): + if arg.fixed: + C_ += arg + continue + id_ = id(arg) + if id_ in linear_coefs: + linear_coefs[id_] += 1 + else: + linear_coefs[id_] = 1 + linear_vars[id_] = arg else: C_ += arg diff --git a/pyomo/repn/tests/test_linear.py b/pyomo/repn/tests/test_linear.py index 6843650d0c2..0fd428fd8ee 100644 --- a/pyomo/repn/tests/test_linear.py +++ b/pyomo/repn/tests/test_linear.py @@ -1589,7 +1589,7 @@ def test_to_expression(self): expr.constant = 0 expr.linear[id(m.x)] = 0 expr.linear[id(m.y)] = 0 - assertExpressionsEqual(self, expr.to_expression(visitor), LinearExpression()) + assertExpressionsEqual(self, expr.to_expression(visitor), 0) @unittest.skipUnless(numpy_available, "Test requires numpy") def test_nonnumeric(self): From f36e31109b878178eea70c88d496ece1fd864316 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Mar 2024 09:34:52 -0700 Subject: [PATCH 1336/1797] NFC: apply black --- pyomo/repn/plugins/gams_writer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyomo/repn/plugins/gams_writer.py b/pyomo/repn/plugins/gams_writer.py index 0756cb64920..a0f407d7952 100644 --- a/pyomo/repn/plugins/gams_writer.py +++ b/pyomo/repn/plugins/gams_writer.py @@ -188,7 +188,8 @@ def _linear_to_string(self, node): if arg.__class__ in native_numeric_types else ( self.smap.getSymbol(arg) - if arg.is_variable_type() and (not arg.fixed or self.output_fixed_variables) + if arg.is_variable_type() + and (not arg.fixed or self.output_fixed_variables) else ftoa(value(arg), True) ) ) From df4f7af6d75e1a29f3a23c8144bb0945c8908f32 Mon Sep 17 00:00:00 2001 From: robbybp Date: Thu, 7 Mar 2024 10:03:45 -0700 Subject: [PATCH 1337/1797] pass timer to generate_subsystem_blocks --- pyomo/contrib/incidence_analysis/scc_solver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/incidence_analysis/scc_solver.py b/pyomo/contrib/incidence_analysis/scc_solver.py index 5e21631f2ef..117554c52de 100644 --- a/pyomo/contrib/incidence_analysis/scc_solver.py +++ b/pyomo/contrib/incidence_analysis/scc_solver.py @@ -84,7 +84,7 @@ def generate_strongly_connected_components( subsets = [(cblock, vblock) for vblock, cblock in zip(var_blocks, con_blocks)] timer.start("generate-block") for block, inputs in generate_subsystem_blocks( - subsets, include_fixed=include_fixed + subsets, include_fixed=include_fixed, timer=timer ): timer.stop("generate-block") # TODO: How does len scale for reference-to-list? From f47345cdaea937cd962a1db6f2ea6f5257437026 Mon Sep 17 00:00:00 2001 From: robbybp Date: Thu, 7 Mar 2024 10:04:45 -0700 Subject: [PATCH 1338/1797] accept timer argument in generate_subsystem_blocks, revert implementation of identify_external_functions to be a generator --- pyomo/util/subsystems.py | 53 +++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/pyomo/util/subsystems.py b/pyomo/util/subsystems.py index ba1d56a787b..9a2a8b6635d 100644 --- a/pyomo/util/subsystems.py +++ b/pyomo/util/subsystems.py @@ -17,6 +17,7 @@ from pyomo.core.base.constraint import Constraint from pyomo.core.base.expression import Expression +from pyomo.core.base.objective import Objective from pyomo.core.base.external import ExternalFunction from pyomo.core.expr.visitor import StreamBasedExpressionVisitor from pyomo.core.expr.numeric_expr import ExternalFunctionExpression @@ -67,47 +68,37 @@ def acceptChildResult(self, node, data, child_result, child_idx): return child_result.is_expression_type(), None -def identify_external_functions( - expr, - descend_into_named_expressions=True, - named_expressions=None, -): - visitor = _ExternalFunctionVisitor( - descend_into_named_expressions=descend_into_named_expressions - ) - efs = list(visitor.walk_expression(expr)) - if not descend_into_named_expressions and named_expressions is not None: - named_expressions.extend(visitor.named_expressions) - return efs - #yield from _ExternalFunctionVisitor().walk_expression(expr) +def identify_external_functions(expr): + # TODO: Potentially support descend_into_named_expressions argument here. + # This will likely require converting from a generator to a function. + yield from _ExternalFunctionVisitor().walk_expression(expr) def add_local_external_functions(block): ef_exprs = [] named_expressions = [] - for comp in block.component_data_objects((Constraint, Expression), active=True): - ef_exprs.extend(identify_external_functions( - comp.expr, - descend_into_named_expressions=False, - named_expressions=named_expressions, - )) - named_expr_set = ComponentSet(named_expressions) + visitor = _ExternalFunctionVisitor(descend_into_named_expressions=False) + for comp in block.component_data_objects( + (Constraint, Expression, Objective), + active=True, + ): + ef_exprs.extend(visitor.walk_expression(comp.expr)) + named_expr_set = ComponentSet(visitor.named_expressions) + # List of unique named expressions named_expressions = list(named_expr_set) while named_expressions: expr = named_expressions.pop() - local_named_exprs = [] - ef_exprs.extend(identify_external_functions( - expr, - descend_into_named_expressions=False, - named_expressions=local_named_exprs, - )) + # Clear named expression cache so we don't re-check named expressions + # we've seen before. + visitor.named_expressions.clear() + ef_exprs.extend(visitor.walk_expression(expr)) # Only add to the stack named expressions that we have # not encountered yet. - for local_expr in local_named_exprs: + for local_expr in visitor.named_expressions: if local_expr not in named_expr_set: named_expressions.append(local_expr) named_expr_set.add(local_expr) - + unique_functions = [] fcn_set = set() for expr in ef_exprs: @@ -184,7 +175,7 @@ def create_subsystem_block( return block -def generate_subsystem_blocks(subsystems, include_fixed=False): +def generate_subsystem_blocks(subsystems, include_fixed=False, timer=None): """Generates blocks that contain subsystems of variables and constraints. Arguments @@ -203,8 +194,10 @@ def generate_subsystem_blocks(subsystems, include_fixed=False): not specified are contained in the input_vars component. """ + if timer is None: + timer = HierarchicalTimer() for cons, vars in subsystems: - block = create_subsystem_block(cons, vars, include_fixed) + block = create_subsystem_block(cons, vars, include_fixed, timer=timer) yield block, list(block.input_vars.values()) From 9eebe2297961854783dd5feb57595b46d8aa3866 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 7 Mar 2024 10:41:49 -0700 Subject: [PATCH 1339/1797] This is a small bug fix to address when there is no objective --- pyomo/contrib/solver/base.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 13bd5ddb212..a2174fea237 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -22,6 +22,7 @@ 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 import ProblemSense 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 @@ -418,9 +419,15 @@ def _map_results(self, model, results): ] legacy_soln.status = legacy_solution_status_map[results.solution_status] legacy_results.solver.termination_message = str(results.termination_condition) + legacy_results.problem.number_of_constraints = model.nconstraints() + legacy_results.problem.number_of_variables = model.nvariables() obj = get_objective(model) - if len(list(obj)) > 0: + if not obj: + legacy_results.problem.sense = ProblemSense.unknown + legacy_results.problem.number_of_objectives = 0 + else: legacy_results.problem.sense = obj.sense + legacy_results.problem.number_of_objectives = len(obj) if obj.sense == minimize: legacy_results.problem.lower_bound = results.objective_bound From 28ecd960b60a2691a43c7eea688252046253e84a Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 7 Mar 2024 10:45:42 -0700 Subject: [PATCH 1340/1797] Cannot convert non-constant Pyomo to bool --- 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 a2174fea237..035d25bf97d 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -422,7 +422,7 @@ def _map_results(self, model, results): legacy_results.problem.number_of_constraints = model.nconstraints() legacy_results.problem.number_of_variables = model.nvariables() obj = get_objective(model) - if not obj: + if len(obj) == 0: legacy_results.problem.sense = ProblemSense.unknown legacy_results.problem.number_of_objectives = 0 else: From 191fadf94dd9ff1ea5e41ad3d15e193f0346d861 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 7 Mar 2024 10:55:55 -0700 Subject: [PATCH 1341/1797] Change way of checking number of objectives --- pyomo/contrib/solver/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 035d25bf97d..91a581c5998 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -421,11 +421,11 @@ def _map_results(self, model, results): legacy_results.solver.termination_message = str(results.termination_condition) legacy_results.problem.number_of_constraints = model.nconstraints() legacy_results.problem.number_of_variables = model.nvariables() - obj = get_objective(model) - if len(obj) == 0: + if model.nobjectives() == 0: legacy_results.problem.sense = ProblemSense.unknown legacy_results.problem.number_of_objectives = 0 else: + obj = get_objective(model) legacy_results.problem.sense = obj.sense legacy_results.problem.number_of_objectives = len(obj) From 2167deaa97de640e872d4004c979c08d21020ba1 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 7 Mar 2024 10:57:44 -0700 Subject: [PATCH 1342/1797] Problem sense is already unknown by default --- pyomo/contrib/solver/base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 91a581c5998..f9cd213bf73 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -422,7 +422,6 @@ def _map_results(self, model, results): legacy_results.problem.number_of_constraints = model.nconstraints() legacy_results.problem.number_of_variables = model.nvariables() if model.nobjectives() == 0: - legacy_results.problem.sense = ProblemSense.unknown legacy_results.problem.number_of_objectives = 0 else: obj = get_objective(model) From ff2ddad1f91713071221c9eaf003869b939d379f Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 7 Mar 2024 10:58:22 -0700 Subject: [PATCH 1343/1797] Remove import --- pyomo/contrib/solver/base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index f9cd213bf73..54871e90c2f 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -22,7 +22,6 @@ 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 import ProblemSense 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 From db4062419b56d810f30c05a96f72cca5eefdfd1a Mon Sep 17 00:00:00 2001 From: Bernard Knueven Date: Thu, 7 Mar 2024 11:07:40 -0700 Subject: [PATCH 1344/1797] 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 1345/1797] 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 1346/1797] 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 0b252aecfade0cd0e3327abef24cfb8e061ba164 Mon Sep 17 00:00:00 2001 From: robbybp Date: Thu, 7 Mar 2024 11:23:41 -0700 Subject: [PATCH 1347/1797] add tests with external functions in named expressions --- pyomo/util/tests/test_subsystems.py | 51 +++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/pyomo/util/tests/test_subsystems.py b/pyomo/util/tests/test_subsystems.py index 87a4fb3cf28..f102670ba62 100644 --- a/pyomo/util/tests/test_subsystems.py +++ b/pyomo/util/tests/test_subsystems.py @@ -292,7 +292,7 @@ def test_generate_dont_fix_inputs_with_fixed_var(self): self.assertFalse(m.v3.fixed) self.assertTrue(m.v4.fixed) - def _make_model_with_external_functions(self): + def _make_model_with_external_functions(self, named_expressions=False): m = pyo.ConcreteModel() gsl = find_GSL() m.bessel = pyo.ExternalFunction(library=gsl, function="gsl_sf_bessel_J0") @@ -300,9 +300,18 @@ def _make_model_with_external_functions(self): m.v1 = pyo.Var(initialize=1.0) m.v2 = pyo.Var(initialize=2.0) m.v3 = pyo.Var(initialize=3.0) + if named_expressions: + m.subexpr = pyo.Expression(pyo.PositiveIntegers) + subexpr1 = m.subexpr[1] = 2 * m.fermi(m.v1) + subexpr2 = m.subexpr[2] = m.bessel(m.v1) - m.bessel(m.v2) + subexpr3 = m.subexpr[3] = m.subexpr[2] + m.v3 ** 2 + else: + subexpr1 = 2 * m.fermi(m.v1) + subexpr2 = m.bessel(m.v1) - m.bessel(m.v2) + subexpr3 = m.subexpr[2] + m.v3 ** 2 m.con1 = pyo.Constraint(expr=m.v1 == 0.5) - m.con2 = pyo.Constraint(expr=2 * m.fermi(m.v1) + m.v2**2 - m.v3 == 1.0) - m.con3 = pyo.Constraint(expr=m.bessel(m.v1) - m.bessel(m.v2) + m.v3**2 == 2.0) + m.con2 = pyo.Constraint(expr=subexpr1 + m.v2**2 - m.v3 == 1.0) + m.con3 = pyo.Constraint(expr=subexpr3 == 2.0) return m @unittest.skipUnless(find_GSL(), "Could not find the AMPL GSL library") @@ -329,6 +338,15 @@ def test_identify_external_functions(self): pred_fcn_data = {(gsl, "gsl_sf_bessel_J0"), (gsl, "gsl_sf_fermi_dirac_m1")} self.assertEqual(fcn_data, pred_fcn_data) + @unittest.skipUnless(find_GSL(), "Could not find the AMPL GSL library") + def test_local_external_functions_with_named_expressions(self): + m = self._make_model_with_external_functions(named_expressions=True) + variables = list(pyo.component_data_objects(pyo.Var)) + constraints = list(pyo.component_data_objects(pyo.Constraint, active=True)) + b = create_subsystem_block(constraints, variables) + self.assertTrue(isinstance(m._gsl_sf_bessel_J0, pyo.ExternalFunction)) + self.assertTrue(isinstance(m._gsl_sf_fermi_dirac_m1, pyo.ExternalFunction)) + def _solve_ef_model_with_ipopt(self): m = self._make_model_with_external_functions() ipopt = pyo.SolverFactory("ipopt") @@ -362,6 +380,33 @@ def test_with_external_function(self): self.assertAlmostEqual(m.v2.value, m_full.v2.value) self.assertAlmostEqual(m.v3.value, m_full.v3.value) + @unittest.skipUnless(find_GSL(), "Could not find the AMPL GSL library") + @unittest.skipUnless( + pyo.SolverFactory("ipopt").available(), "ipopt is not available" + ) + def test_with_external_function_in_named_expression(self): + m = self._make_model_with_external_functions(named_expressions=True) + subsystem = ([m.con2, m.con3], [m.v2, m.v3]) + + m.v1.set_value(0.5) + block = create_subsystem_block(*subsystem) + ipopt = pyo.SolverFactory("ipopt") + with TemporarySubsystemManager(to_fix=list(block.input_vars.values())): + ipopt.solve(block) + + # Correct values obtained by solving with Ipopt directly + # in another script. + self.assertEqual(m.v1.value, 0.5) + self.assertFalse(m.v1.fixed) + self.assertAlmostEqual(m.v2.value, 1.04816, delta=1e-5) + self.assertAlmostEqual(m.v3.value, 1.34356, delta=1e-5) + + # Result obtained by solving the full system + m_full = self._solve_ef_model_with_ipopt() + self.assertAlmostEqual(m.v1.value, m_full.v1.value) + self.assertAlmostEqual(m.v2.value, m_full.v2.value) + self.assertAlmostEqual(m.v3.value, m_full.v3.value) + @unittest.skipUnless(find_GSL(), "Could not find the AMPL GSL library") def test_external_function_with_potential_name_collision(self): m = self._make_model_with_external_functions() From 912867918bdda9ac48642a0e24a51cc45489bcff Mon Sep 17 00:00:00 2001 From: robbybp Date: Thu, 7 Mar 2024 11:30:22 -0700 Subject: [PATCH 1348/1797] document igraph option in generate_scc --- pyomo/contrib/incidence_analysis/scc_solver.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/scc_solver.py b/pyomo/contrib/incidence_analysis/scc_solver.py index 117554c52de..6c556646a8c 100644 --- a/pyomo/contrib/incidence_analysis/scc_solver.py +++ b/pyomo/contrib/incidence_analysis/scc_solver.py @@ -47,9 +47,12 @@ def generate_strongly_connected_components( variables: List of Pyomo variable data objects Variables that may participate in strongly connected components. If not provided, all variables in the constraints will be used. - include_fixed: Bool + include_fixed: Bool, optional Indicates whether fixed variables will be included when identifying variables in constraints. + igraph: IncidenceGraphInterface, optional + Incidence graph containing (at least) the provided constraints + and variables. Yields ------ @@ -67,7 +70,7 @@ def generate_strongly_connected_components( _generate_variables_in_constraints( constraints, include_fixed=include_fixed, - #method=IncidenceMethod.ampl_repn + method=IncidenceMethod.ampl_repn, ) ) timer.stop("generate-vars") From 78431b71f895aa87c875d811b5e05cd933ba4f8a Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 7 Mar 2024 12:01:07 -0700 Subject: [PATCH 1349/1797] 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 313a31019b6de36c20cb0bac66ff0340c5fa6e36 Mon Sep 17 00:00:00 2001 From: robbybp Date: Thu, 7 Mar 2024 12:13:05 -0700 Subject: [PATCH 1350/1797] initial implementation of variable visitor that can exploit named expressions --- pyomo/core/expr/visitor.py | 79 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/pyomo/core/expr/visitor.py b/pyomo/core/expr/visitor.py index 6a9b7955281..51864044396 100644 --- a/pyomo/core/expr/visitor.py +++ b/pyomo/core/expr/visitor.py @@ -1388,6 +1388,85 @@ def visit(self, node): return node +class _StreamVariableVisitor(StreamBasedExpressionVisitor): + def __init__( + self, + include_fixed=False, + descend_into_named_expressions=True, + ): + self._include_fixed = include_fixed + self._descend_into_named_expressions = descend_into_named_expressions + self.named_expressions = [] + # Should we allow re-use of this visitor for multiple expressions? + + def initializeWalker(self, expr): + self._variables = [] + self._seen = set() + return True, None + + def beforeChild(self, parent, child, index): + if ( + not self._descend_into_named_expressions + and isinstance(child, NumericValue) + and child.is_named_expression_type() + ): + self.named_expressions.append(child) + return False, None + else: + return True, None + + def exitNode(self, node, data): + if node.is_variable_type() and (self._include_fixed or not node.fixed): + if id(node) not in self._seen: + self._seen.add(id(node)) + self._variables.append(node) + + def finalizeResult(self, result): + return self._variables + + def enterNode(self, node): + pass + + def acceptChildResult(self, node, data, child_result, child_idx): + if child_result.__class__ in native_types: + return False, None + return child_result.is_expression_type(), None + + +def identify_variables_in_components(components, include_fixed=True): + visitor = _StreamVariableVisitor( + include_fixed=include_fixed, descend_into_named_expressions=False + ) + all_variables = [] + for comp in components: + all_variables.extend(visitor.walk_expressions(comp.expr)) + + named_expr_set = set() + unique_named_exprs = [] + for expr in visitor.named_expressions: + if id(expr) in named_expr_set: + named_expr_set.add(id(expr)) + unique_named_exprs.append(expr) + + while unique_named_exprs: + expr = unique_named_exprs.pop() + visitor.named_expressions.clear() + all_variables.extend(visitor.walk_expression(expr.expr)) + + for new_expr in visitor.named_expressions: + if id(new_expr) not in named_expr_set: + named_expr_set.add(new_expr) + unique_named_exprs.append(new_expr) + + unique_vars = [] + var_set = set() + for var in all_variables: + if id(var) not in var_set: + var_set.add(id(var)) + unique_vars.append(var) + return unique_vars + + def identify_variables(expr, include_fixed=True): """ A generator that yields a sequence of variables From 07f5234575aeaf6905827fd7d89842e80200cdbd Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Mar 2024 12:58:56 -0700 Subject: [PATCH 1351/1797] Update tests to track change in LinearExpression arg types --- pyomo/core/tests/transform/test_add_slacks.py | 56 +-- pyomo/core/tests/unit/test_compare.py | 6 - pyomo/core/tests/unit/test_expression.py | 11 +- pyomo/core/tests/unit/test_numeric_expr.py | 329 ++++-------------- .../core/tests/unit/test_numeric_expr_api.py | 11 +- .../unit/test_numeric_expr_dispatcher.py | 278 ++++++--------- .../unit/test_numeric_expr_zerofilter.py | 274 ++++++--------- pyomo/core/tests/unit/test_visitor.py | 23 +- pyomo/gdp/tests/common_tests.py | 7 +- pyomo/gdp/tests/test_bigm.py | 5 +- pyomo/gdp/tests/test_binary_multiplication.py | 5 +- pyomo/gdp/tests/test_disjunct.py | 24 +- 12 files changed, 302 insertions(+), 727 deletions(-) diff --git a/pyomo/core/tests/transform/test_add_slacks.py b/pyomo/core/tests/transform/test_add_slacks.py index 7896cab7e88..a74a9b75c4f 100644 --- a/pyomo/core/tests/transform/test_add_slacks.py +++ b/pyomo/core/tests/transform/test_add_slacks.py @@ -102,10 +102,7 @@ def checkRule1(self, m): self, cons.body, EXPR.LinearExpression( - [ - EXPR.MonomialTermExpression((1, m.x)), - EXPR.MonomialTermExpression((-1, transBlock._slack_minus_rule1)), - ] + [m.x, EXPR.MonomialTermExpression((-1, transBlock._slack_minus_rule1))] ), ) @@ -118,14 +115,7 @@ def checkRule3(self, m): self.assertEqual(cons.lower, 0.1) assertExpressionsEqual( - self, - cons.body, - EXPR.LinearExpression( - [ - EXPR.MonomialTermExpression((1, m.x)), - EXPR.MonomialTermExpression((1, transBlock._slack_plus_rule3)), - ] - ), + self, cons.body, EXPR.LinearExpression([m.x, transBlock._slack_plus_rule3]) ) def test_ub_constraint_modified(self): @@ -154,8 +144,8 @@ def test_both_bounds_constraint_modified(self): cons.body, EXPR.LinearExpression( [ - EXPR.MonomialTermExpression((1, m.y)), - EXPR.MonomialTermExpression((1, transBlock._slack_plus_rule2)), + m.y, + transBlock._slack_plus_rule2, EXPR.MonomialTermExpression((-1, transBlock._slack_minus_rule2)), ] ), @@ -184,10 +174,10 @@ def test_new_obj_created(self): obj.expr, EXPR.LinearExpression( [ - EXPR.MonomialTermExpression((1, transBlock._slack_minus_rule1)), - EXPR.MonomialTermExpression((1, transBlock._slack_plus_rule2)), - EXPR.MonomialTermExpression((1, transBlock._slack_minus_rule2)), - EXPR.MonomialTermExpression((1, transBlock._slack_plus_rule3)), + transBlock._slack_minus_rule1, + transBlock._slack_plus_rule2, + transBlock._slack_minus_rule2, + transBlock._slack_plus_rule3, ] ), ) @@ -302,10 +292,7 @@ def checkTargetsObj(self, m): self, obj.expr, EXPR.LinearExpression( - [ - EXPR.MonomialTermExpression((1, transBlock._slack_minus_rule1)), - EXPR.MonomialTermExpression((1, transBlock._slack_plus_rule3)), - ] + [transBlock._slack_minus_rule1, transBlock._slack_plus_rule3] ), ) @@ -423,9 +410,9 @@ def test_transformed_constraints_sumexpression_body(self): c.body, EXPR.LinearExpression( [ - EXPR.MonomialTermExpression((1, m.x)), + m.x, EXPR.MonomialTermExpression((-2, m.y)), - EXPR.MonomialTermExpression((1, transBlock._slack_plus_rule4)), + transBlock._slack_plus_rule4, EXPR.MonomialTermExpression((-1, transBlock._slack_minus_rule4)), ] ), @@ -518,15 +505,9 @@ def checkTargetObj(self, m): obj.expr, EXPR.LinearExpression( [ - EXPR.MonomialTermExpression( - (1, transBlock.component("_slack_plus_rule1[1]")) - ), - EXPR.MonomialTermExpression( - (1, transBlock.component("_slack_plus_rule1[2]")) - ), - EXPR.MonomialTermExpression( - (1, transBlock.component("_slack_plus_rule1[3]")) - ), + transBlock.component("_slack_plus_rule1[1]"), + transBlock.component("_slack_plus_rule1[2]"), + transBlock.component("_slack_plus_rule1[3]"), ] ), ) @@ -558,14 +539,7 @@ def checkTransformedRule1(self, m, i): EXPR.LinearExpression( [ EXPR.MonomialTermExpression((2, m.x[i])), - EXPR.MonomialTermExpression( - ( - 1, - m._core_add_slack_variables.component( - "_slack_plus_rule1[%s]" % i - ), - ) - ), + m._core_add_slack_variables.component("_slack_plus_rule1[%s]" % i), ] ), ) diff --git a/pyomo/core/tests/unit/test_compare.py b/pyomo/core/tests/unit/test_compare.py index f80753bdb61..7c3536bc084 100644 --- a/pyomo/core/tests/unit/test_compare.py +++ b/pyomo/core/tests/unit/test_compare.py @@ -165,17 +165,11 @@ def test_expr_if(self): 0, (EqualityExpression, 2), (LinearExpression, 2), - (MonomialTermExpression, 2), - 1, m.y, - (MonomialTermExpression, 2), - 1, m.x, 0, (EqualityExpression, 2), (LinearExpression, 2), - (MonomialTermExpression, 2), - 1, m.y, (MonomialTermExpression, 2), -1, diff --git a/pyomo/core/tests/unit/test_expression.py b/pyomo/core/tests/unit/test_expression.py index c9afc6a1f76..678df4c01a8 100644 --- a/pyomo/core/tests/unit/test_expression.py +++ b/pyomo/core/tests/unit/test_expression.py @@ -738,10 +738,10 @@ def test_pprint_oldStyle(self): expr = model.e * model.x**2 + model.E[1] output = """\ -sum(prod(e{sum(mon(1, x), 2)}, pow(x, 2)), E[1]{sum(pow(x, 2), 1)}) +sum(prod(e{sum(x, 2)}, pow(x, 2)), E[1]{sum(pow(x, 2), 1)}) e : Size=1, Index=None Key : Expression - None : sum(mon(1, x), 2) + None : sum(x, 2) E : Size=2, Index={1, 2} Key : Expression 1 : sum(pow(x, 2), 1) @@ -951,12 +951,7 @@ def test_isub(self): assertExpressionsEqual( self, m.e.expr, - EXPR.LinearExpression( - [ - EXPR.MonomialTermExpression((1, m.x)), - EXPR.MonomialTermExpression((-1, m.y)), - ] - ), + EXPR.LinearExpression([m.x, EXPR.MonomialTermExpression((-1, m.y))]), ) self.assertTrue(compare_expressions(m.e.expr, m.x - m.y)) diff --git a/pyomo/core/tests/unit/test_numeric_expr.py b/pyomo/core/tests/unit/test_numeric_expr.py index c1066c292d7..968b3acb6a4 100644 --- a/pyomo/core/tests/unit/test_numeric_expr.py +++ b/pyomo/core/tests/unit/test_numeric_expr.py @@ -638,12 +638,7 @@ def test_simpleSum(self): m.b = Var() e = m.a + m.b # - self.assertExpressionsEqual( - e, - LinearExpression( - [MonomialTermExpression((1, m.a)), MonomialTermExpression((1, m.b))] - ), - ) + self.assertExpressionsEqual(e, LinearExpression([m.a, m.b])) self.assertRaises(KeyError, e.arg, 3) @@ -654,14 +649,7 @@ def test_simpleSum_API(self): e = m.a + m.b e += 2 * m.a self.assertExpressionsEqual( - e, - LinearExpression( - [ - MonomialTermExpression((1, m.a)), - MonomialTermExpression((1, m.b)), - MonomialTermExpression((2, m.a)), - ] - ), + e, LinearExpression([m.a, m.b, MonomialTermExpression((2, m.a))]) ) def test_constSum(self): @@ -669,13 +657,9 @@ def test_constSum(self): m = AbstractModel() m.a = Var() # - self.assertExpressionsEqual( - m.a + 5, LinearExpression([MonomialTermExpression((1, m.a)), 5]) - ) + self.assertExpressionsEqual(m.a + 5, LinearExpression([m.a, 5])) - self.assertExpressionsEqual( - 5 + m.a, LinearExpression([5, MonomialTermExpression((1, m.a))]) - ) + self.assertExpressionsEqual(5 + m.a, LinearExpression([5, m.a])) def test_nestedSum(self): # @@ -696,12 +680,7 @@ def test_nestedSum(self): # a b e1 = m.a + m.b e = e1 + 5 - self.assertExpressionsEqual( - e, - LinearExpression( - [MonomialTermExpression((1, m.a)), MonomialTermExpression((1, m.b)), 5] - ), - ) + self.assertExpressionsEqual(e, LinearExpression([m.a, m.b, 5])) # + # / \ @@ -710,12 +689,7 @@ def test_nestedSum(self): # a b e1 = m.a + m.b e = 5 + e1 - self.assertExpressionsEqual( - e, - LinearExpression( - [MonomialTermExpression((1, m.a)), MonomialTermExpression((1, m.b)), 5] - ), - ) + self.assertExpressionsEqual(e, LinearExpression([m.a, m.b, 5])) # + # / \ @@ -724,16 +698,7 @@ def test_nestedSum(self): # a b e1 = m.a + m.b e = e1 + m.c - self.assertExpressionsEqual( - e, - LinearExpression( - [ - MonomialTermExpression((1, m.a)), - MonomialTermExpression((1, m.b)), - MonomialTermExpression((1, m.c)), - ] - ), - ) + self.assertExpressionsEqual(e, LinearExpression([m.a, m.b, m.c])) # + # / \ @@ -742,16 +707,7 @@ def test_nestedSum(self): # a b e1 = m.a + m.b e = m.c + e1 - self.assertExpressionsEqual( - e, - LinearExpression( - [ - MonomialTermExpression((1, m.a)), - MonomialTermExpression((1, m.b)), - MonomialTermExpression((1, m.c)), - ] - ), - ) + self.assertExpressionsEqual(e, LinearExpression([m.a, m.b, m.c])) # + # / \ @@ -762,17 +718,7 @@ def test_nestedSum(self): e2 = m.c + m.d e = e1 + e2 # - self.assertExpressionsEqual( - e, - LinearExpression( - [ - MonomialTermExpression((1, m.a)), - MonomialTermExpression((1, m.b)), - MonomialTermExpression((1, m.c)), - MonomialTermExpression((1, m.d)), - ] - ), - ) + self.assertExpressionsEqual(e, LinearExpression([m.a, m.b, m.c, m.d])) def test_nestedSum2(self): # @@ -798,22 +744,7 @@ def test_nestedSum2(self): self.assertExpressionsEqual( e, - SumExpression( - [ - ProductExpression( - ( - 2, - LinearExpression( - [ - MonomialTermExpression((1, m.a)), - MonomialTermExpression((1, m.b)), - ] - ), - ) - ), - m.c, - ] - ), + SumExpression([ProductExpression((2, LinearExpression([m.a, m.b]))), m.c]), ) # * @@ -834,20 +765,7 @@ def test_nestedSum2(self): ( 3, SumExpression( - [ - ProductExpression( - ( - 2, - LinearExpression( - [ - MonomialTermExpression((1, m.a)), - MonomialTermExpression((1, m.b)), - ] - ), - ) - ), - m.c, - ] + [ProductExpression((2, LinearExpression([m.a, m.b]))), m.c] ), ) ), @@ -891,10 +809,7 @@ def test_sumOf_nestedTrivialProduct(self): e = e1 + m.b # self.assertExpressionsEqual( - e, - LinearExpression( - [MonomialTermExpression((5, m.a)), MonomialTermExpression((1, m.b))] - ), + e, LinearExpression([MonomialTermExpression((5, m.a)), m.b]) ) # + @@ -905,10 +820,7 @@ def test_sumOf_nestedTrivialProduct(self): e = m.b + e1 # self.assertExpressionsEqual( - e, - LinearExpression( - [MonomialTermExpression((1, m.b)), MonomialTermExpression((5, m.a))] - ), + e, LinearExpression([m.b, MonomialTermExpression((5, m.a))]) ) # + @@ -920,14 +832,7 @@ def test_sumOf_nestedTrivialProduct(self): e = e1 + e2 # self.assertExpressionsEqual( - e, - LinearExpression( - [ - MonomialTermExpression((1, m.b)), - MonomialTermExpression((1, m.c)), - MonomialTermExpression((5, m.a)), - ] - ), + e, LinearExpression([m.b, m.c, MonomialTermExpression((5, m.a))]) ) # + @@ -939,14 +844,7 @@ def test_sumOf_nestedTrivialProduct(self): e = e2 + e1 # self.assertExpressionsEqual( - e, - LinearExpression( - [ - MonomialTermExpression((1, m.b)), - MonomialTermExpression((1, m.c)), - MonomialTermExpression((5, m.a)), - ] - ), + e, LinearExpression([m.b, m.c, MonomialTermExpression((5, m.a))]) ) def test_simpleDiff(self): @@ -962,10 +860,7 @@ def test_simpleDiff(self): # a b e = m.a - m.b self.assertExpressionsEqual( - e, - LinearExpression( - [MonomialTermExpression((1, m.a)), MonomialTermExpression((-1, m.b))] - ), + e, LinearExpression([m.a, MonomialTermExpression((-1, m.b))]) ) def test_constDiff(self): @@ -978,9 +873,7 @@ def test_constDiff(self): # - # / \ # a 5 - self.assertExpressionsEqual( - m.a - 5, LinearExpression([MonomialTermExpression((1, m.a)), -5]) - ) + self.assertExpressionsEqual(m.a - 5, LinearExpression([m.a, -5])) # - # / \ @@ -1002,10 +895,7 @@ def test_paramDiff(self): # a p e = m.a - m.p self.assertExpressionsEqual( - e, - LinearExpression( - [MonomialTermExpression((1, m.a)), NPV_NegationExpression((m.p,))] - ), + e, LinearExpression([m.a, NPV_NegationExpression((m.p,))]) ) # - @@ -1079,14 +969,7 @@ def test_nestedDiff(self): e1 = m.a - m.b e = e1 - 5 self.assertExpressionsEqual( - e, - LinearExpression( - [ - MonomialTermExpression((1, m.a)), - MonomialTermExpression((-1, m.b)), - -5, - ] - ), + e, LinearExpression([m.a, MonomialTermExpression((-1, m.b)), -5]) ) # - @@ -1102,14 +985,7 @@ def test_nestedDiff(self): [ 5, NegationExpression( - ( - LinearExpression( - [ - MonomialTermExpression((1, m.a)), - MonomialTermExpression((-1, m.b)), - ] - ), - ) + (LinearExpression([m.a, MonomialTermExpression((-1, m.b))]),) ), ] ), @@ -1126,7 +1002,7 @@ def test_nestedDiff(self): e, LinearExpression( [ - MonomialTermExpression((1, m.a)), + m.a, MonomialTermExpression((-1, m.b)), MonomialTermExpression((-1, m.c)), ] @@ -1146,14 +1022,7 @@ def test_nestedDiff(self): [ m.c, NegationExpression( - ( - LinearExpression( - [ - MonomialTermExpression((1, m.a)), - MonomialTermExpression((-1, m.b)), - ] - ), - ) + (LinearExpression([m.a, MonomialTermExpression((-1, m.b))]),) ), ] ), @@ -1171,21 +1040,9 @@ def test_nestedDiff(self): e, SumExpression( [ - LinearExpression( - [ - MonomialTermExpression((1, m.a)), - MonomialTermExpression((-1, m.b)), - ] - ), + LinearExpression([m.a, MonomialTermExpression((-1, m.b))]), NegationExpression( - ( - LinearExpression( - [ - MonomialTermExpression((1, m.c)), - MonomialTermExpression((-1, m.d)), - ] - ), - ) + (LinearExpression([m.c, MonomialTermExpression((-1, m.d))]),) ), ] ), @@ -1382,10 +1239,7 @@ def test_sumOf_nestedTrivialProduct2(self): self.assertExpressionsEqual( e, LinearExpression( - [ - MonomialTermExpression((1, m.b)), - MonomialTermExpression((NPV_NegationExpression((m.p,)), m.a)), - ] + [m.b, MonomialTermExpression((NPV_NegationExpression((m.p,)), m.a))] ), ) @@ -1403,14 +1257,7 @@ def test_sumOf_nestedTrivialProduct2(self): [ MonomialTermExpression((m.p, m.a)), NegationExpression( - ( - LinearExpression( - [ - MonomialTermExpression((1, m.b)), - MonomialTermExpression((-1, m.c)), - ] - ), - ) + (LinearExpression([m.b, MonomialTermExpression((-1, m.c))]),) ), ] ), @@ -1428,7 +1275,7 @@ def test_sumOf_nestedTrivialProduct2(self): e, LinearExpression( [ - MonomialTermExpression((1, m.b)), + m.b, MonomialTermExpression((-1, m.c)), MonomialTermExpression((NPV_NegationExpression((m.p,)), m.a)), ] @@ -1598,22 +1445,7 @@ def test_nestedProduct2(self): self.assertExpressionsEqual( e, ProductExpression( - ( - LinearExpression( - [ - MonomialTermExpression((1, m.a)), - MonomialTermExpression((1, m.b)), - MonomialTermExpression((1, m.c)), - ] - ), - LinearExpression( - [ - MonomialTermExpression((1, m.a)), - MonomialTermExpression((1, m.b)), - MonomialTermExpression((1, m.d)), - ] - ), - ) + (LinearExpression([m.a, m.b, m.c]), LinearExpression([m.a, m.b, m.d])) ), ) # Verify shared args... @@ -1638,9 +1470,7 @@ def test_nestedProduct2(self): e3 = e1 * m.d e = e2 * e3 # - inner = LinearExpression( - [MonomialTermExpression((1, m.a)), MonomialTermExpression((1, m.b))] - ) + inner = LinearExpression([m.a, m.b]) self.assertExpressionsEqual( e, ProductExpression( @@ -2034,10 +1864,10 @@ def test_sum(self): model.p = Param(mutable=True) expr = 5 + model.a + model.a - self.assertEqual("sum(5, mon(1, a), mon(1, a))", str(expr)) + self.assertEqual("sum(5, a, a)", str(expr)) expr += 5 - self.assertEqual("sum(5, mon(1, a), mon(1, a), 5)", str(expr)) + self.assertEqual("sum(5, a, a, 5)", str(expr)) expr = 2 + model.p self.assertEqual("sum(2, p)", str(expr)) @@ -2053,24 +1883,18 @@ def test_linearsum(self): expr = quicksum(i * model.a[i] for i in A) self.assertEqual( - "sum(mon(0, a[0]), mon(1, a[1]), mon(2, a[2]), mon(3, a[3]), " - "mon(4, a[4]))", + "sum(mon(0, a[0]), a[1], mon(2, a[2]), mon(3, a[3]), " "mon(4, a[4]))", str(expr), ) expr = quicksum((i - 2) * model.a[i] for i in A) self.assertEqual( - "sum(mon(-2, a[0]), mon(-1, a[1]), mon(0, a[2]), mon(1, a[3]), " - "mon(2, a[4]))", + "sum(mon(-2, a[0]), mon(-1, a[1]), mon(0, a[2]), a[3], " "mon(2, a[4]))", str(expr), ) expr = quicksum(model.a[i] for i in A) - self.assertEqual( - "sum(mon(1, a[0]), mon(1, a[1]), mon(1, a[2]), mon(1, a[3]), " - "mon(1, a[4]))", - str(expr), - ) + self.assertEqual("sum(a[0], a[1], a[2], a[3], a[4])", str(expr)) model.p[1].value = 0 model.p[3].value = 3 @@ -2138,10 +1962,10 @@ def test_inequality(self): self.assertEqual("5 <= a < 10", str(expr)) expr = 5 <= model.a + 5 - self.assertEqual("5 <= sum(mon(1, a), 5)", str(expr)) + self.assertEqual("5 <= sum(a, 5)", str(expr)) expr = expr < 10 - self.assertEqual("5 <= sum(mon(1, a), 5) < 10", str(expr)) + self.assertEqual("5 <= sum(a, 5) < 10", str(expr)) def test_equality(self): # @@ -2166,10 +1990,10 @@ def test_equality(self): self.assertEqual("a == 10", str(expr)) expr = 5 == model.a + 5 - self.assertEqual("sum(mon(1, a), 5) == 5", str(expr)) + self.assertEqual("sum(a, 5) == 5", str(expr)) expr = model.a + 5 == 5 - self.assertEqual("sum(mon(1, a), 5) == 5", str(expr)) + self.assertEqual("sum(a, 5) == 5", str(expr)) def test_getitem(self): m = ConcreteModel() @@ -2206,7 +2030,7 @@ def test_small_expression(self): expr = abs(expr) self.assertEqual( "abs(neg(pow(2, div(2, prod(2, sum(1, neg(pow(div(prod(sum(" - "mon(1, a), 1, -1), a), a), b)), 1))))))", + "a, 1, -1), a), a), b)), 1))))))", str(expr), ) @@ -3754,13 +3578,7 @@ def test_summation1(self): self.assertExpressionsEqual( e, LinearExpression( - [ - MonomialTermExpression((1, self.m.a[1])), - MonomialTermExpression((1, self.m.a[2])), - MonomialTermExpression((1, self.m.a[3])), - MonomialTermExpression((1, self.m.a[4])), - MonomialTermExpression((1, self.m.a[5])), - ] + [self.m.a[1], self.m.a[2], self.m.a[3], self.m.a[4], self.m.a[5]] ), ) @@ -3872,16 +3690,16 @@ def test_summation_compression(self): e, LinearExpression( [ - MonomialTermExpression((1, self.m.a[1])), - MonomialTermExpression((1, self.m.a[2])), - MonomialTermExpression((1, self.m.a[3])), - MonomialTermExpression((1, self.m.a[4])), - MonomialTermExpression((1, self.m.a[5])), - MonomialTermExpression((1, self.m.b[1])), - MonomialTermExpression((1, self.m.b[2])), - MonomialTermExpression((1, self.m.b[3])), - MonomialTermExpression((1, self.m.b[4])), - MonomialTermExpression((1, self.m.b[5])), + self.m.a[1], + self.m.a[2], + self.m.a[3], + self.m.a[4], + self.m.a[5], + self.m.b[1], + self.m.b[2], + self.m.b[3], + self.m.b[4], + self.m.b[5], ] ), ) @@ -3912,13 +3730,7 @@ def test_deprecation(self): self.assertExpressionsEqual( e, LinearExpression( - [ - MonomialTermExpression((1, self.m.a[1])), - MonomialTermExpression((1, self.m.a[2])), - MonomialTermExpression((1, self.m.a[3])), - MonomialTermExpression((1, self.m.a[4])), - MonomialTermExpression((1, self.m.a[5])), - ] + [self.m.a[1], self.m.a[2], self.m.a[3], self.m.a[4], self.m.a[5]] ), ) @@ -3928,13 +3740,7 @@ def test_summation1(self): self.assertExpressionsEqual( e, LinearExpression( - [ - MonomialTermExpression((1, self.m.a[1])), - MonomialTermExpression((1, self.m.a[2])), - MonomialTermExpression((1, self.m.a[3])), - MonomialTermExpression((1, self.m.a[4])), - MonomialTermExpression((1, self.m.a[5])), - ] + [self.m.a[1], self.m.a[2], self.m.a[3], self.m.a[4], self.m.a[5]] ), ) @@ -4156,15 +3962,15 @@ def test_SumExpression(self): self.assertEqual(expr2(), 15) self.assertNotEqual(id(expr1), id(expr2)) self.assertNotEqual(id(expr1._args_), id(expr2._args_)) - self.assertIs(expr1.arg(0).arg(1), expr2.arg(0).arg(1)) - self.assertIs(expr1.arg(1).arg(1), expr2.arg(1).arg(1)) + self.assertIs(expr1.arg(0), expr2.arg(0)) + self.assertIs(expr1.arg(1), expr2.arg(1)) expr1 += self.m.b self.assertEqual(expr1(), 25) self.assertEqual(expr2(), 15) self.assertNotEqual(id(expr1), id(expr2)) self.assertNotEqual(id(expr1._args_), id(expr2._args_)) - self.assertIs(expr1.arg(0).arg(1), expr2.arg(0).arg(1)) - self.assertIs(expr1.arg(1).arg(1), expr2.arg(1).arg(1)) + self.assertIs(expr1.arg(0), expr2.arg(0)) + self.assertIs(expr1.arg(1), expr2.arg(1)) # total = counter.count - start self.assertEqual(total, 1) @@ -4341,9 +4147,9 @@ def test_productOfExpressions(self): self.assertEqual(expr1.arg(1).nargs(), 2) self.assertEqual(expr2.arg(1).nargs(), 2) - self.assertIs(expr1.arg(0).arg(0).arg(1), expr2.arg(0).arg(0).arg(1)) - self.assertIs(expr1.arg(0).arg(1).arg(1), expr2.arg(0).arg(1).arg(1)) - self.assertIs(expr1.arg(1).arg(0).arg(1), expr2.arg(1).arg(0).arg(1)) + self.assertIs(expr1.arg(0).arg(0), expr2.arg(0).arg(0)) + self.assertIs(expr1.arg(0).arg(1), expr2.arg(0).arg(1)) + self.assertIs(expr1.arg(1).arg(0), expr2.arg(1).arg(0)) expr1 *= self.m.b self.assertEqual(expr1(), 1500) @@ -4382,8 +4188,8 @@ def test_productOfExpressions_div(self): self.assertEqual(expr1.arg(1).nargs(), 2) self.assertEqual(expr2.arg(1).nargs(), 2) - self.assertIs(expr1.arg(0).arg(0).arg(1), expr2.arg(0).arg(0).arg(1)) - self.assertIs(expr1.arg(0).arg(1).arg(1), expr2.arg(0).arg(1).arg(1)) + self.assertIs(expr1.arg(0).arg(0), expr2.arg(0).arg(0)) + self.assertIs(expr1.arg(0).arg(1), expr2.arg(0).arg(1)) expr1 /= self.m.b self.assertAlmostEqual(expr1(), 0.15) @@ -5214,18 +5020,7 @@ def test_pow_other(self): e += m.v[0] + m.v[1] e = m.v[0] ** e self.assertExpressionsEqual( - e, - PowExpression( - ( - m.v[0], - LinearExpression( - [ - MonomialTermExpression((1, m.v[0])), - MonomialTermExpression((1, m.v[1])), - ] - ), - ) - ), + e, PowExpression((m.v[0], LinearExpression([m.v[0], m.v[1]]))) ) diff --git a/pyomo/core/tests/unit/test_numeric_expr_api.py b/pyomo/core/tests/unit/test_numeric_expr_api.py index 4e0af126315..923f78af1be 100644 --- a/pyomo/core/tests/unit/test_numeric_expr_api.py +++ b/pyomo/core/tests/unit/test_numeric_expr_api.py @@ -223,7 +223,7 @@ def test_negation(self): self.assertEqual(is_fixed(e), False) self.assertEqual(value(e), -15) self.assertEqual(str(e), "- (x + 2*x)") - self.assertEqual(e.to_string(verbose=True), "neg(sum(mon(1, x), mon(2, x)))") + self.assertEqual(e.to_string(verbose=True), "neg(sum(x, mon(2, x)))") # This can't occur through operator overloading, but could # through expression substitution @@ -634,8 +634,7 @@ def test_linear(self): self.assertEqual(value(e), 1 + 4 + 5 + 2) self.assertEqual(str(e), "0*x[0] + x[1] + 2*x[2] + 5 + y - 3") self.assertEqual( - e.to_string(verbose=True), - "sum(mon(0, x[0]), mon(1, x[1]), mon(2, x[2]), 5, mon(1, y), -3)", + e.to_string(verbose=True), "sum(mon(0, x[0]), x[1], mon(2, x[2]), 5, y, -3)" ) self.assertIs(type(e), LinearExpression) @@ -701,7 +700,7 @@ def test_expr_if(self): ) self.assertEqual( e.to_string(verbose=True), - "Expr_if( ( 5 <= y ), then=( sum(mon(1, x[0]), 5) ), else=( pow(x[1], 2) ) )", + "Expr_if( ( 5 <= y ), then=( sum(x[0], 5) ), else=( pow(x[1], 2) ) )", ) m.y.fix() @@ -972,9 +971,7 @@ def test_sum(self): f = e.create_node_with_local_data((m.p, m.x)) self.assertIsNot(f, e) self.assertIs(type(f), LinearExpression) - assertExpressionsStructurallyEqual( - self, f.args, [m.p, MonomialTermExpression((1, m.x))] - ) + assertExpressionsStructurallyEqual(self, f.args, [m.p, m.x]) f = e.create_node_with_local_data((m.p, m.x**2)) self.assertIsNot(f, e) diff --git a/pyomo/core/tests/unit/test_numeric_expr_dispatcher.py b/pyomo/core/tests/unit/test_numeric_expr_dispatcher.py index 7c6e2af9974..bb7a291e67d 100644 --- a/pyomo/core/tests/unit/test_numeric_expr_dispatcher.py +++ b/pyomo/core/tests/unit/test_numeric_expr_dispatcher.py @@ -123,8 +123,6 @@ def setUp(self): self.mutable_l3 = _MutableNPVSumExpression([self.npv]) # often repeated reference expressions - self.mon_bin = MonomialTermExpression((1, self.bin)) - self.mon_var = MonomialTermExpression((1, self.var)) self.minus_bin = MonomialTermExpression((-1, self.bin)) self.minus_npv = NPV_NegationExpression((self.npv,)) self.minus_param_mut = NPV_NegationExpression((self.param_mut,)) @@ -368,38 +366,34 @@ def test_add_asbinary(self): # BooleanVar objects do not support addition (self.asbinary, self.asbinary, NotImplemented), (self.asbinary, self.zero, self.bin), - (self.asbinary, self.one, LinearExpression([self.mon_bin, 1])), + (self.asbinary, self.one, LinearExpression([self.bin, 1])), # 4: - (self.asbinary, self.native, LinearExpression([self.mon_bin, 5])), - (self.asbinary, self.npv, LinearExpression([self.mon_bin, self.npv])), - (self.asbinary, self.param, LinearExpression([self.mon_bin, 6])), + (self.asbinary, self.native, LinearExpression([self.bin, 5])), + (self.asbinary, self.npv, LinearExpression([self.bin, self.npv])), + (self.asbinary, self.param, LinearExpression([self.bin, 6])), ( self.asbinary, self.param_mut, - LinearExpression([self.mon_bin, self.param_mut]), + LinearExpression([self.bin, self.param_mut]), ), # 8: - (self.asbinary, self.var, LinearExpression([self.mon_bin, self.mon_var])), + (self.asbinary, self.var, LinearExpression([self.bin, self.var])), ( self.asbinary, self.mon_native, - LinearExpression([self.mon_bin, self.mon_native]), + LinearExpression([self.bin, self.mon_native]), ), ( self.asbinary, self.mon_param, - LinearExpression([self.mon_bin, self.mon_param]), - ), - ( - self.asbinary, - self.mon_npv, - LinearExpression([self.mon_bin, self.mon_npv]), + LinearExpression([self.bin, self.mon_param]), ), + (self.asbinary, self.mon_npv, LinearExpression([self.bin, self.mon_npv])), # 12: ( self.asbinary, self.linear, - LinearExpression(self.linear.args + [self.mon_bin]), + LinearExpression(self.linear.args + [self.bin]), ), (self.asbinary, self.sum, SumExpression(self.sum.args + [self.bin])), (self.asbinary, self.other, SumExpression([self.bin, self.other])), @@ -408,7 +402,7 @@ def test_add_asbinary(self): ( self.asbinary, self.mutable_l1, - LinearExpression([self.mon_bin, self.mon_npv]), + LinearExpression([self.bin, self.mon_npv]), ), ( self.asbinary, @@ -416,13 +410,9 @@ def test_add_asbinary(self): SumExpression(self.mutable_l2.args + [self.bin]), ), (self.asbinary, self.param0, self.bin), - (self.asbinary, self.param1, LinearExpression([self.mon_bin, 1])), + (self.asbinary, self.param1, LinearExpression([self.bin, 1])), # 20: - ( - self.asbinary, - self.mutable_l3, - LinearExpression([self.mon_bin, self.npv]), - ), + (self.asbinary, self.mutable_l3, LinearExpression([self.bin, self.npv])), ] self._run_cases(tests, operator.add) self._run_cases(tests, operator.iadd) @@ -462,7 +452,7 @@ def test_add_zero(self): def test_add_one(self): tests = [ (self.one, self.invalid, NotImplemented), - (self.one, self.asbinary, LinearExpression([1, self.mon_bin])), + (self.one, self.asbinary, LinearExpression([1, self.bin])), (self.one, self.zero, 1), (self.one, self.one, 2), # 4: @@ -471,7 +461,7 @@ def test_add_one(self): (self.one, self.param, 7), (self.one, self.param_mut, NPV_SumExpression([1, self.param_mut])), # 8: - (self.one, self.var, LinearExpression([1, self.mon_var])), + (self.one, self.var, LinearExpression([1, self.var])), (self.one, self.mon_native, LinearExpression([1, self.mon_native])), (self.one, self.mon_param, LinearExpression([1, self.mon_param])), (self.one, self.mon_npv, LinearExpression([1, self.mon_npv])), @@ -494,7 +484,7 @@ def test_add_one(self): def test_add_native(self): tests = [ (self.native, self.invalid, NotImplemented), - (self.native, self.asbinary, LinearExpression([5, self.mon_bin])), + (self.native, self.asbinary, LinearExpression([5, self.bin])), (self.native, self.zero, 5), (self.native, self.one, 6), # 4: @@ -503,7 +493,7 @@ def test_add_native(self): (self.native, self.param, 11), (self.native, self.param_mut, NPV_SumExpression([5, self.param_mut])), # 8: - (self.native, self.var, LinearExpression([5, self.mon_var])), + (self.native, self.var, LinearExpression([5, self.var])), (self.native, self.mon_native, LinearExpression([5, self.mon_native])), (self.native, self.mon_param, LinearExpression([5, self.mon_param])), (self.native, self.mon_npv, LinearExpression([5, self.mon_npv])), @@ -530,7 +520,7 @@ def test_add_native(self): def test_add_npv(self): tests = [ (self.npv, self.invalid, NotImplemented), - (self.npv, self.asbinary, LinearExpression([self.npv, self.mon_bin])), + (self.npv, self.asbinary, LinearExpression([self.npv, self.bin])), (self.npv, self.zero, self.npv), (self.npv, self.one, NPV_SumExpression([self.npv, 1])), # 4: @@ -539,7 +529,7 @@ def test_add_npv(self): (self.npv, self.param, NPV_SumExpression([self.npv, 6])), (self.npv, self.param_mut, NPV_SumExpression([self.npv, self.param_mut])), # 8: - (self.npv, self.var, LinearExpression([self.npv, self.mon_var])), + (self.npv, self.var, LinearExpression([self.npv, self.var])), (self.npv, self.mon_native, LinearExpression([self.npv, self.mon_native])), (self.npv, self.mon_param, LinearExpression([self.npv, self.mon_param])), (self.npv, self.mon_npv, LinearExpression([self.npv, self.mon_npv])), @@ -570,7 +560,7 @@ def test_add_npv(self): def test_add_param(self): tests = [ (self.param, self.invalid, NotImplemented), - (self.param, self.asbinary, LinearExpression([6, self.mon_bin])), + (self.param, self.asbinary, LinearExpression([6, self.bin])), (self.param, self.zero, 6), (self.param, self.one, 7), # 4: @@ -579,7 +569,7 @@ def test_add_param(self): (self.param, self.param, 12), (self.param, self.param_mut, NPV_SumExpression([6, self.param_mut])), # 8: - (self.param, self.var, LinearExpression([6, self.mon_var])), + (self.param, self.var, LinearExpression([6, self.var])), (self.param, self.mon_native, LinearExpression([6, self.mon_native])), (self.param, self.mon_param, LinearExpression([6, self.mon_param])), (self.param, self.mon_npv, LinearExpression([6, self.mon_npv])), @@ -605,7 +595,7 @@ def test_add_param_mut(self): ( self.param_mut, self.asbinary, - LinearExpression([self.param_mut, self.mon_bin]), + LinearExpression([self.param_mut, self.bin]), ), (self.param_mut, self.zero, self.param_mut), (self.param_mut, self.one, NPV_SumExpression([self.param_mut, 1])), @@ -619,11 +609,7 @@ def test_add_param_mut(self): NPV_SumExpression([self.param_mut, self.param_mut]), ), # 8: - ( - self.param_mut, - self.var, - LinearExpression([self.param_mut, self.mon_var]), - ), + (self.param_mut, self.var, LinearExpression([self.param_mut, self.var])), ( self.param_mut, self.mon_native, @@ -674,37 +660,21 @@ def test_add_param_mut(self): def test_add_var(self): tests = [ (self.var, self.invalid, NotImplemented), - (self.var, self.asbinary, LinearExpression([self.mon_var, self.mon_bin])), + (self.var, self.asbinary, LinearExpression([self.var, self.bin])), (self.var, self.zero, self.var), - (self.var, self.one, LinearExpression([self.mon_var, 1])), + (self.var, self.one, LinearExpression([self.var, 1])), # 4: - (self.var, self.native, LinearExpression([self.mon_var, 5])), - (self.var, self.npv, LinearExpression([self.mon_var, self.npv])), - (self.var, self.param, LinearExpression([self.mon_var, 6])), - ( - self.var, - self.param_mut, - LinearExpression([self.mon_var, self.param_mut]), - ), + (self.var, self.native, LinearExpression([self.var, 5])), + (self.var, self.npv, LinearExpression([self.var, self.npv])), + (self.var, self.param, LinearExpression([self.var, 6])), + (self.var, self.param_mut, LinearExpression([self.var, self.param_mut])), # 8: - (self.var, self.var, LinearExpression([self.mon_var, self.mon_var])), - ( - self.var, - self.mon_native, - LinearExpression([self.mon_var, self.mon_native]), - ), - ( - self.var, - self.mon_param, - LinearExpression([self.mon_var, self.mon_param]), - ), - (self.var, self.mon_npv, LinearExpression([self.mon_var, self.mon_npv])), + (self.var, self.var, LinearExpression([self.var, self.var])), + (self.var, self.mon_native, LinearExpression([self.var, self.mon_native])), + (self.var, self.mon_param, LinearExpression([self.var, self.mon_param])), + (self.var, self.mon_npv, LinearExpression([self.var, self.mon_npv])), # 12: - ( - self.var, - self.linear, - LinearExpression(self.linear.args + [self.mon_var]), - ), + (self.var, self.linear, LinearExpression(self.linear.args + [self.var])), (self.var, self.sum, SumExpression(self.sum.args + [self.var])), (self.var, self.other, SumExpression([self.var, self.other])), (self.var, self.mutable_l0, self.var), @@ -712,7 +682,7 @@ def test_add_var(self): ( self.var, self.mutable_l1, - LinearExpression([self.mon_var] + self.mutable_l1.args), + LinearExpression([self.var] + self.mutable_l1.args), ), ( self.var, @@ -720,13 +690,9 @@ def test_add_var(self): SumExpression(self.mutable_l2.args + [self.var]), ), (self.var, self.param0, self.var), - (self.var, self.param1, LinearExpression([self.mon_var, 1])), + (self.var, self.param1, LinearExpression([self.var, 1])), # 20: - ( - self.var, - self.mutable_l3, - LinearExpression([MonomialTermExpression((1, self.var)), self.npv]), - ), + (self.var, self.mutable_l3, LinearExpression([self.var, self.npv])), ] self._run_cases(tests, operator.add) self._run_cases(tests, operator.iadd) @@ -737,7 +703,7 @@ def test_add_mon_native(self): ( self.mon_native, self.asbinary, - LinearExpression([self.mon_native, self.mon_bin]), + LinearExpression([self.mon_native, self.bin]), ), (self.mon_native, self.zero, self.mon_native), (self.mon_native, self.one, LinearExpression([self.mon_native, 1])), @@ -751,11 +717,7 @@ def test_add_mon_native(self): LinearExpression([self.mon_native, self.param_mut]), ), # 8: - ( - self.mon_native, - self.var, - LinearExpression([self.mon_native, self.mon_var]), - ), + (self.mon_native, self.var, LinearExpression([self.mon_native, self.var])), ( self.mon_native, self.mon_native, @@ -813,7 +775,7 @@ def test_add_mon_param(self): ( self.mon_param, self.asbinary, - LinearExpression([self.mon_param, self.mon_bin]), + LinearExpression([self.mon_param, self.bin]), ), (self.mon_param, self.zero, self.mon_param), (self.mon_param, self.one, LinearExpression([self.mon_param, 1])), @@ -827,11 +789,7 @@ def test_add_mon_param(self): LinearExpression([self.mon_param, self.param_mut]), ), # 8: - ( - self.mon_param, - self.var, - LinearExpression([self.mon_param, self.mon_var]), - ), + (self.mon_param, self.var, LinearExpression([self.mon_param, self.var])), ( self.mon_param, self.mon_native, @@ -882,11 +840,7 @@ def test_add_mon_param(self): def test_add_mon_npv(self): tests = [ (self.mon_npv, self.invalid, NotImplemented), - ( - self.mon_npv, - self.asbinary, - LinearExpression([self.mon_npv, self.mon_bin]), - ), + (self.mon_npv, self.asbinary, LinearExpression([self.mon_npv, self.bin])), (self.mon_npv, self.zero, self.mon_npv), (self.mon_npv, self.one, LinearExpression([self.mon_npv, 1])), # 4: @@ -899,7 +853,7 @@ def test_add_mon_npv(self): LinearExpression([self.mon_npv, self.param_mut]), ), # 8: - (self.mon_npv, self.var, LinearExpression([self.mon_npv, self.mon_var])), + (self.mon_npv, self.var, LinearExpression([self.mon_npv, self.var])), ( self.mon_npv, self.mon_native, @@ -949,7 +903,7 @@ def test_add_linear(self): ( self.linear, self.asbinary, - LinearExpression(self.linear.args + [self.mon_bin]), + LinearExpression(self.linear.args + [self.bin]), ), (self.linear, self.zero, self.linear), (self.linear, self.one, LinearExpression(self.linear.args + [1])), @@ -963,11 +917,7 @@ def test_add_linear(self): LinearExpression(self.linear.args + [self.param_mut]), ), # 8: - ( - self.linear, - self.var, - LinearExpression(self.linear.args + [self.mon_var]), - ), + (self.linear, self.var, LinearExpression(self.linear.args + [self.var])), ( self.linear, self.mon_native, @@ -1134,7 +1084,7 @@ def test_add_mutable_l1(self): ( self.mutable_l1, self.asbinary, - LinearExpression(self.mutable_l1.args + [self.mon_bin]), + LinearExpression(self.mutable_l1.args + [self.bin]), ), (self.mutable_l1, self.zero, self.mon_npv), (self.mutable_l1, self.one, LinearExpression(self.mutable_l1.args + [1])), @@ -1159,7 +1109,7 @@ def test_add_mutable_l1(self): ( self.mutable_l1, self.var, - LinearExpression(self.mutable_l1.args + [self.mon_var]), + LinearExpression(self.mutable_l1.args + [self.var]), ), ( self.mutable_l1, @@ -1341,7 +1291,7 @@ def test_add_param0(self): def test_add_param1(self): tests = [ (self.param1, self.invalid, NotImplemented), - (self.param1, self.asbinary, LinearExpression([1, self.mon_bin])), + (self.param1, self.asbinary, LinearExpression([1, self.bin])), (self.param1, self.zero, 1), (self.param1, self.one, 2), # 4: @@ -1350,7 +1300,7 @@ def test_add_param1(self): (self.param1, self.param, 7), (self.param1, self.param_mut, NPV_SumExpression([1, self.param_mut])), # 8: - (self.param1, self.var, LinearExpression([1, self.mon_var])), + (self.param1, self.var, LinearExpression([1, self.var])), (self.param1, self.mon_native, LinearExpression([1, self.mon_native])), (self.param1, self.mon_param, LinearExpression([1, self.mon_param])), (self.param1, self.mon_npv, LinearExpression([1, self.mon_npv])), @@ -1380,7 +1330,7 @@ def test_add_mutable_l3(self): ( self.mutable_l3, self.asbinary, - LinearExpression(self.mutable_l3.args + [self.mon_bin]), + LinearExpression(self.mutable_l3.args + [self.bin]), ), (self.mutable_l3, self.zero, self.npv), (self.mutable_l3, self.one, NPV_SumExpression(self.mutable_l3.args + [1])), @@ -1409,7 +1359,7 @@ def test_add_mutable_l3(self): ( self.mutable_l3, self.var, - LinearExpression(self.mutable_l3.args + [self.mon_var]), + LinearExpression(self.mutable_l3.args + [self.var]), ), ( self.mutable_l3, @@ -1515,32 +1465,32 @@ def test_sub_asbinary(self): # BooleanVar objects do not support addition (self.asbinary, self.asbinary, NotImplemented), (self.asbinary, self.zero, self.bin), - (self.asbinary, self.one, LinearExpression([self.mon_bin, -1])), + (self.asbinary, self.one, LinearExpression([self.bin, -1])), # 4: - (self.asbinary, self.native, LinearExpression([self.mon_bin, -5])), - (self.asbinary, self.npv, LinearExpression([self.mon_bin, self.minus_npv])), - (self.asbinary, self.param, LinearExpression([self.mon_bin, -6])), + (self.asbinary, self.native, LinearExpression([self.bin, -5])), + (self.asbinary, self.npv, LinearExpression([self.bin, self.minus_npv])), + (self.asbinary, self.param, LinearExpression([self.bin, -6])), ( self.asbinary, self.param_mut, - LinearExpression([self.mon_bin, self.minus_param_mut]), + LinearExpression([self.bin, self.minus_param_mut]), ), # 8: - (self.asbinary, self.var, LinearExpression([self.mon_bin, self.minus_var])), + (self.asbinary, self.var, LinearExpression([self.bin, self.minus_var])), ( self.asbinary, self.mon_native, - LinearExpression([self.mon_bin, self.minus_mon_native]), + LinearExpression([self.bin, self.minus_mon_native]), ), ( self.asbinary, self.mon_param, - LinearExpression([self.mon_bin, self.minus_mon_param]), + LinearExpression([self.bin, self.minus_mon_param]), ), ( self.asbinary, self.mon_npv, - LinearExpression([self.mon_bin, self.minus_mon_npv]), + LinearExpression([self.bin, self.minus_mon_npv]), ), # 12: (self.asbinary, self.linear, SumExpression([self.bin, self.minus_linear])), @@ -1551,7 +1501,7 @@ def test_sub_asbinary(self): ( self.asbinary, self.mutable_l1, - LinearExpression([self.mon_bin, self.minus_mon_npv]), + LinearExpression([self.bin, self.minus_mon_npv]), ), ( self.asbinary, @@ -1559,12 +1509,12 @@ def test_sub_asbinary(self): SumExpression([self.bin, self.minus_mutable_l2]), ), (self.asbinary, self.param0, self.bin), - (self.asbinary, self.param1, LinearExpression([self.mon_bin, -1])), + (self.asbinary, self.param1, LinearExpression([self.bin, -1])), # 20: ( self.asbinary, self.mutable_l3, - LinearExpression([self.mon_bin, self.minus_npv]), + LinearExpression([self.bin, self.minus_npv]), ), ] self._run_cases(tests, operator.sub) @@ -1837,35 +1787,31 @@ def test_sub_param_mut(self): def test_sub_var(self): tests = [ (self.var, self.invalid, NotImplemented), - (self.var, self.asbinary, LinearExpression([self.mon_var, self.minus_bin])), + (self.var, self.asbinary, LinearExpression([self.var, self.minus_bin])), (self.var, self.zero, self.var), - (self.var, self.one, LinearExpression([self.mon_var, -1])), + (self.var, self.one, LinearExpression([self.var, -1])), # 4: - (self.var, self.native, LinearExpression([self.mon_var, -5])), - (self.var, self.npv, LinearExpression([self.mon_var, self.minus_npv])), - (self.var, self.param, LinearExpression([self.mon_var, -6])), + (self.var, self.native, LinearExpression([self.var, -5])), + (self.var, self.npv, LinearExpression([self.var, self.minus_npv])), + (self.var, self.param, LinearExpression([self.var, -6])), ( self.var, self.param_mut, - LinearExpression([self.mon_var, self.minus_param_mut]), + LinearExpression([self.var, self.minus_param_mut]), ), # 8: - (self.var, self.var, LinearExpression([self.mon_var, self.minus_var])), + (self.var, self.var, LinearExpression([self.var, self.minus_var])), ( self.var, self.mon_native, - LinearExpression([self.mon_var, self.minus_mon_native]), + LinearExpression([self.var, self.minus_mon_native]), ), ( self.var, self.mon_param, - LinearExpression([self.mon_var, self.minus_mon_param]), - ), - ( - self.var, - self.mon_npv, - LinearExpression([self.mon_var, self.minus_mon_npv]), + LinearExpression([self.var, self.minus_mon_param]), ), + (self.var, self.mon_npv, LinearExpression([self.var, self.minus_mon_npv])), # 12: ( self.var, @@ -1879,7 +1825,7 @@ def test_sub_var(self): ( self.var, self.mutable_l1, - LinearExpression([self.mon_var, self.minus_mon_npv]), + LinearExpression([self.var, self.minus_mon_npv]), ), ( self.var, @@ -1887,13 +1833,9 @@ def test_sub_var(self): SumExpression([self.var, self.minus_mutable_l2]), ), (self.var, self.param0, self.var), - (self.var, self.param1, LinearExpression([self.mon_var, -1])), + (self.var, self.param1, LinearExpression([self.var, -1])), # 20: - ( - self.var, - self.mutable_l3, - LinearExpression([self.mon_var, self.minus_npv]), - ), + (self.var, self.mutable_l3, LinearExpression([self.var, self.minus_npv])), ] self._run_cases(tests, operator.sub) self._run_cases(tests, operator.isub) @@ -6511,7 +6453,7 @@ def test_mutable_nvp_iadd(self): mutable_npv = _MutableNPVSumExpression([]) tests = [ (mutable_npv, self.invalid, NotImplemented), - (mutable_npv, self.asbinary, _MutableLinearExpression([self.mon_bin])), + (mutable_npv, self.asbinary, _MutableLinearExpression([self.bin])), (mutable_npv, self.zero, _MutableNPVSumExpression([])), (mutable_npv, self.one, _MutableNPVSumExpression([1])), # 4: @@ -6520,7 +6462,7 @@ def test_mutable_nvp_iadd(self): (mutable_npv, self.param, _MutableNPVSumExpression([6])), (mutable_npv, self.param_mut, _MutableNPVSumExpression([self.param_mut])), # 8: - (mutable_npv, self.var, _MutableLinearExpression([self.mon_var])), + (mutable_npv, self.var, _MutableLinearExpression([self.var])), (mutable_npv, self.mon_native, _MutableLinearExpression([self.mon_native])), (mutable_npv, self.mon_param, _MutableLinearExpression([self.mon_param])), (mutable_npv, self.mon_npv, _MutableLinearExpression([self.mon_npv])), @@ -6546,7 +6488,7 @@ def test_mutable_nvp_iadd(self): mutable_npv = _MutableNPVSumExpression([10]) tests = [ (mutable_npv, self.invalid, NotImplemented), - (mutable_npv, self.asbinary, _MutableLinearExpression([10, self.mon_bin])), + (mutable_npv, self.asbinary, _MutableLinearExpression([10, self.bin])), (mutable_npv, self.zero, _MutableNPVSumExpression([10])), (mutable_npv, self.one, _MutableNPVSumExpression([11])), # 4: @@ -6559,7 +6501,7 @@ def test_mutable_nvp_iadd(self): _MutableNPVSumExpression([10, self.param_mut]), ), # 8: - (mutable_npv, self.var, _MutableLinearExpression([10, self.mon_var])), + (mutable_npv, self.var, _MutableLinearExpression([10, self.var])), ( mutable_npv, self.mon_native, @@ -6602,7 +6544,7 @@ def test_mutable_lin_iadd(self): mutable_lin = _MutableLinearExpression([]) tests = [ (mutable_lin, self.invalid, NotImplemented), - (mutable_lin, self.asbinary, _MutableLinearExpression([self.mon_bin])), + (mutable_lin, self.asbinary, _MutableLinearExpression([self.bin])), (mutable_lin, self.zero, _MutableLinearExpression([])), (mutable_lin, self.one, _MutableLinearExpression([1])), # 4: @@ -6611,7 +6553,7 @@ def test_mutable_lin_iadd(self): (mutable_lin, self.param, _MutableLinearExpression([6])), (mutable_lin, self.param_mut, _MutableLinearExpression([self.param_mut])), # 8: - (mutable_lin, self.var, _MutableLinearExpression([self.mon_var])), + (mutable_lin, self.var, _MutableLinearExpression([self.var])), (mutable_lin, self.mon_native, _MutableLinearExpression([self.mon_native])), (mutable_lin, self.mon_param, _MutableLinearExpression([self.mon_param])), (mutable_lin, self.mon_npv, _MutableLinearExpression([self.mon_npv])), @@ -6634,81 +6576,69 @@ def test_mutable_lin_iadd(self): ] self._run_iadd_cases(tests, operator.iadd) - mutable_lin = _MutableLinearExpression([self.mon_bin]) + mutable_lin = _MutableLinearExpression([self.bin]) tests = [ (mutable_lin, self.invalid, NotImplemented), ( mutable_lin, self.asbinary, - _MutableLinearExpression([self.mon_bin, self.mon_bin]), + _MutableLinearExpression([self.bin, self.bin]), ), - (mutable_lin, self.zero, _MutableLinearExpression([self.mon_bin])), - (mutable_lin, self.one, _MutableLinearExpression([self.mon_bin, 1])), + (mutable_lin, self.zero, _MutableLinearExpression([self.bin])), + (mutable_lin, self.one, _MutableLinearExpression([self.bin, 1])), # 4: - (mutable_lin, self.native, _MutableLinearExpression([self.mon_bin, 5])), - (mutable_lin, self.npv, _MutableLinearExpression([self.mon_bin, self.npv])), - (mutable_lin, self.param, _MutableLinearExpression([self.mon_bin, 6])), + (mutable_lin, self.native, _MutableLinearExpression([self.bin, 5])), + (mutable_lin, self.npv, _MutableLinearExpression([self.bin, self.npv])), + (mutable_lin, self.param, _MutableLinearExpression([self.bin, 6])), ( mutable_lin, self.param_mut, - _MutableLinearExpression([self.mon_bin, self.param_mut]), + _MutableLinearExpression([self.bin, self.param_mut]), ), # 8: - ( - mutable_lin, - self.var, - _MutableLinearExpression([self.mon_bin, self.mon_var]), - ), + (mutable_lin, self.var, _MutableLinearExpression([self.bin, self.var])), ( mutable_lin, self.mon_native, - _MutableLinearExpression([self.mon_bin, self.mon_native]), + _MutableLinearExpression([self.bin, self.mon_native]), ), ( mutable_lin, self.mon_param, - _MutableLinearExpression([self.mon_bin, self.mon_param]), + _MutableLinearExpression([self.bin, self.mon_param]), ), ( mutable_lin, self.mon_npv, - _MutableLinearExpression([self.mon_bin, self.mon_npv]), + _MutableLinearExpression([self.bin, self.mon_npv]), ), # 12: ( mutable_lin, self.linear, - _MutableLinearExpression([self.mon_bin] + self.linear.args), - ), - ( - mutable_lin, - self.sum, - _MutableSumExpression([self.mon_bin] + self.sum.args), - ), - ( - mutable_lin, - self.other, - _MutableSumExpression([self.mon_bin, self.other]), + _MutableLinearExpression([self.bin] + self.linear.args), ), - (mutable_lin, self.mutable_l0, _MutableLinearExpression([self.mon_bin])), + (mutable_lin, self.sum, _MutableSumExpression([self.bin] + self.sum.args)), + (mutable_lin, self.other, _MutableSumExpression([self.bin, self.other])), + (mutable_lin, self.mutable_l0, _MutableLinearExpression([self.bin])), # 16: ( mutable_lin, self.mutable_l1, - _MutableLinearExpression([self.mon_bin] + self.mutable_l1.args), + _MutableLinearExpression([self.bin] + self.mutable_l1.args), ), ( mutable_lin, self.mutable_l2, - _MutableSumExpression([self.mon_bin] + self.mutable_l2.args), + _MutableSumExpression([self.bin] + self.mutable_l2.args), ), - (mutable_lin, self.param0, _MutableLinearExpression([self.mon_bin])), - (mutable_lin, self.param1, _MutableLinearExpression([self.mon_bin, 1])), + (mutable_lin, self.param0, _MutableLinearExpression([self.bin])), + (mutable_lin, self.param1, _MutableLinearExpression([self.bin, 1])), # 20: ( mutable_lin, self.mutable_l3, - _MutableLinearExpression([self.mon_bin, self.npv]), + _MutableLinearExpression([self.bin, self.npv]), ), ] self._run_iadd_cases(tests, operator.iadd) @@ -6854,7 +6784,7 @@ def as_numeric(self): assertExpressionsEqual(self, PowExpression((self.var, 2)), e) e = obj + obj - assertExpressionsEqual(self, LinearExpression((self.mon_var, self.mon_var)), e) + assertExpressionsEqual(self, LinearExpression((self.var, self.var)), e) def test_categorize_arg_type(self): class CustomAsNumeric(NumericValue): diff --git a/pyomo/core/tests/unit/test_numeric_expr_zerofilter.py b/pyomo/core/tests/unit/test_numeric_expr_zerofilter.py index 34d2e1cc2c2..19968640a21 100644 --- a/pyomo/core/tests/unit/test_numeric_expr_zerofilter.py +++ b/pyomo/core/tests/unit/test_numeric_expr_zerofilter.py @@ -102,38 +102,34 @@ def test_add_asbinary(self): # BooleanVar objects do not support addition (self.asbinary, self.asbinary, NotImplemented), (self.asbinary, self.zero, self.bin), - (self.asbinary, self.one, LinearExpression([self.mon_bin, 1])), + (self.asbinary, self.one, LinearExpression([self.bin, 1])), # 4: - (self.asbinary, self.native, LinearExpression([self.mon_bin, 5])), - (self.asbinary, self.npv, LinearExpression([self.mon_bin, self.npv])), - (self.asbinary, self.param, LinearExpression([self.mon_bin, 6])), + (self.asbinary, self.native, LinearExpression([self.bin, 5])), + (self.asbinary, self.npv, LinearExpression([self.bin, self.npv])), + (self.asbinary, self.param, LinearExpression([self.bin, 6])), ( self.asbinary, self.param_mut, - LinearExpression([self.mon_bin, self.param_mut]), + LinearExpression([self.bin, self.param_mut]), ), # 8: - (self.asbinary, self.var, LinearExpression([self.mon_bin, self.mon_var])), + (self.asbinary, self.var, LinearExpression([self.bin, self.var])), ( self.asbinary, self.mon_native, - LinearExpression([self.mon_bin, self.mon_native]), + LinearExpression([self.bin, self.mon_native]), ), ( self.asbinary, self.mon_param, - LinearExpression([self.mon_bin, self.mon_param]), - ), - ( - self.asbinary, - self.mon_npv, - LinearExpression([self.mon_bin, self.mon_npv]), + LinearExpression([self.bin, self.mon_param]), ), + (self.asbinary, self.mon_npv, LinearExpression([self.bin, self.mon_npv])), # 12: ( self.asbinary, self.linear, - LinearExpression(self.linear.args + [self.mon_bin]), + LinearExpression(self.linear.args + [self.bin]), ), (self.asbinary, self.sum, SumExpression(self.sum.args + [self.bin])), (self.asbinary, self.other, SumExpression([self.bin, self.other])), @@ -142,7 +138,7 @@ def test_add_asbinary(self): ( self.asbinary, self.mutable_l1, - LinearExpression([self.mon_bin, self.mon_npv]), + LinearExpression([self.bin, self.mon_npv]), ), ( self.asbinary, @@ -150,13 +146,9 @@ def test_add_asbinary(self): SumExpression(self.mutable_l2.args + [self.bin]), ), (self.asbinary, self.param0, self.bin), - (self.asbinary, self.param1, LinearExpression([self.mon_bin, 1])), + (self.asbinary, self.param1, LinearExpression([self.bin, 1])), # 20: - ( - self.asbinary, - self.mutable_l3, - LinearExpression([self.mon_bin, self.npv]), - ), + (self.asbinary, self.mutable_l3, LinearExpression([self.bin, self.npv])), ] self._run_cases(tests, operator.add) self._run_cases(tests, operator.iadd) @@ -196,7 +188,7 @@ def test_add_zero(self): def test_add_one(self): tests = [ (self.one, self.invalid, NotImplemented), - (self.one, self.asbinary, LinearExpression([1, self.mon_bin])), + (self.one, self.asbinary, LinearExpression([1, self.bin])), (self.one, self.zero, 1), (self.one, self.one, 2), # 4: @@ -205,7 +197,7 @@ def test_add_one(self): (self.one, self.param, 7), (self.one, self.param_mut, NPV_SumExpression([1, self.param_mut])), # 8: - (self.one, self.var, LinearExpression([1, self.mon_var])), + (self.one, self.var, LinearExpression([1, self.var])), (self.one, self.mon_native, LinearExpression([1, self.mon_native])), (self.one, self.mon_param, LinearExpression([1, self.mon_param])), (self.one, self.mon_npv, LinearExpression([1, self.mon_npv])), @@ -228,7 +220,7 @@ def test_add_one(self): def test_add_native(self): tests = [ (self.native, self.invalid, NotImplemented), - (self.native, self.asbinary, LinearExpression([5, self.mon_bin])), + (self.native, self.asbinary, LinearExpression([5, self.bin])), (self.native, self.zero, 5), (self.native, self.one, 6), # 4: @@ -237,7 +229,7 @@ def test_add_native(self): (self.native, self.param, 11), (self.native, self.param_mut, NPV_SumExpression([5, self.param_mut])), # 8: - (self.native, self.var, LinearExpression([5, self.mon_var])), + (self.native, self.var, LinearExpression([5, self.var])), (self.native, self.mon_native, LinearExpression([5, self.mon_native])), (self.native, self.mon_param, LinearExpression([5, self.mon_param])), (self.native, self.mon_npv, LinearExpression([5, self.mon_npv])), @@ -264,7 +256,7 @@ def test_add_native(self): def test_add_npv(self): tests = [ (self.npv, self.invalid, NotImplemented), - (self.npv, self.asbinary, LinearExpression([self.npv, self.mon_bin])), + (self.npv, self.asbinary, LinearExpression([self.npv, self.bin])), (self.npv, self.zero, self.npv), (self.npv, self.one, NPV_SumExpression([self.npv, 1])), # 4: @@ -273,7 +265,7 @@ def test_add_npv(self): (self.npv, self.param, NPV_SumExpression([self.npv, 6])), (self.npv, self.param_mut, NPV_SumExpression([self.npv, self.param_mut])), # 8: - (self.npv, self.var, LinearExpression([self.npv, self.mon_var])), + (self.npv, self.var, LinearExpression([self.npv, self.var])), (self.npv, self.mon_native, LinearExpression([self.npv, self.mon_native])), (self.npv, self.mon_param, LinearExpression([self.npv, self.mon_param])), (self.npv, self.mon_npv, LinearExpression([self.npv, self.mon_npv])), @@ -304,7 +296,7 @@ def test_add_npv(self): def test_add_param(self): tests = [ (self.param, self.invalid, NotImplemented), - (self.param, self.asbinary, LinearExpression([6, self.mon_bin])), + (self.param, self.asbinary, LinearExpression([6, self.bin])), (self.param, self.zero, 6), (self.param, self.one, 7), # 4: @@ -313,7 +305,7 @@ def test_add_param(self): (self.param, self.param, 12), (self.param, self.param_mut, NPV_SumExpression([6, self.param_mut])), # 8: - (self.param, self.var, LinearExpression([6, self.mon_var])), + (self.param, self.var, LinearExpression([6, self.var])), (self.param, self.mon_native, LinearExpression([6, self.mon_native])), (self.param, self.mon_param, LinearExpression([6, self.mon_param])), (self.param, self.mon_npv, LinearExpression([6, self.mon_npv])), @@ -339,7 +331,7 @@ def test_add_param_mut(self): ( self.param_mut, self.asbinary, - LinearExpression([self.param_mut, self.mon_bin]), + LinearExpression([self.param_mut, self.bin]), ), (self.param_mut, self.zero, self.param_mut), (self.param_mut, self.one, NPV_SumExpression([self.param_mut, 1])), @@ -353,11 +345,7 @@ def test_add_param_mut(self): NPV_SumExpression([self.param_mut, self.param_mut]), ), # 8: - ( - self.param_mut, - self.var, - LinearExpression([self.param_mut, self.mon_var]), - ), + (self.param_mut, self.var, LinearExpression([self.param_mut, self.var])), ( self.param_mut, self.mon_native, @@ -408,37 +396,21 @@ def test_add_param_mut(self): def test_add_var(self): tests = [ (self.var, self.invalid, NotImplemented), - (self.var, self.asbinary, LinearExpression([self.mon_var, self.mon_bin])), + (self.var, self.asbinary, LinearExpression([self.var, self.bin])), (self.var, self.zero, self.var), - (self.var, self.one, LinearExpression([self.mon_var, 1])), + (self.var, self.one, LinearExpression([self.var, 1])), # 4: - (self.var, self.native, LinearExpression([self.mon_var, 5])), - (self.var, self.npv, LinearExpression([self.mon_var, self.npv])), - (self.var, self.param, LinearExpression([self.mon_var, 6])), - ( - self.var, - self.param_mut, - LinearExpression([self.mon_var, self.param_mut]), - ), + (self.var, self.native, LinearExpression([self.var, 5])), + (self.var, self.npv, LinearExpression([self.var, self.npv])), + (self.var, self.param, LinearExpression([self.var, 6])), + (self.var, self.param_mut, LinearExpression([self.var, self.param_mut])), # 8: - (self.var, self.var, LinearExpression([self.mon_var, self.mon_var])), - ( - self.var, - self.mon_native, - LinearExpression([self.mon_var, self.mon_native]), - ), - ( - self.var, - self.mon_param, - LinearExpression([self.mon_var, self.mon_param]), - ), - (self.var, self.mon_npv, LinearExpression([self.mon_var, self.mon_npv])), + (self.var, self.var, LinearExpression([self.var, self.var])), + (self.var, self.mon_native, LinearExpression([self.var, self.mon_native])), + (self.var, self.mon_param, LinearExpression([self.var, self.mon_param])), + (self.var, self.mon_npv, LinearExpression([self.var, self.mon_npv])), # 12: - ( - self.var, - self.linear, - LinearExpression(self.linear.args + [self.mon_var]), - ), + (self.var, self.linear, LinearExpression(self.linear.args + [self.var])), (self.var, self.sum, SumExpression(self.sum.args + [self.var])), (self.var, self.other, SumExpression([self.var, self.other])), (self.var, self.mutable_l0, self.var), @@ -446,7 +418,7 @@ def test_add_var(self): ( self.var, self.mutable_l1, - LinearExpression([self.mon_var] + self.mutable_l1.args), + LinearExpression([self.var] + self.mutable_l1.args), ), ( self.var, @@ -454,13 +426,9 @@ def test_add_var(self): SumExpression(self.mutable_l2.args + [self.var]), ), (self.var, self.param0, self.var), - (self.var, self.param1, LinearExpression([self.mon_var, 1])), + (self.var, self.param1, LinearExpression([self.var, 1])), # 20: - ( - self.var, - self.mutable_l3, - LinearExpression([MonomialTermExpression((1, self.var)), self.npv]), - ), + (self.var, self.mutable_l3, LinearExpression([self.var, self.npv])), ] self._run_cases(tests, operator.add) self._run_cases(tests, operator.iadd) @@ -471,7 +439,7 @@ def test_add_mon_native(self): ( self.mon_native, self.asbinary, - LinearExpression([self.mon_native, self.mon_bin]), + LinearExpression([self.mon_native, self.bin]), ), (self.mon_native, self.zero, self.mon_native), (self.mon_native, self.one, LinearExpression([self.mon_native, 1])), @@ -485,11 +453,7 @@ def test_add_mon_native(self): LinearExpression([self.mon_native, self.param_mut]), ), # 8: - ( - self.mon_native, - self.var, - LinearExpression([self.mon_native, self.mon_var]), - ), + (self.mon_native, self.var, LinearExpression([self.mon_native, self.var])), ( self.mon_native, self.mon_native, @@ -547,7 +511,7 @@ def test_add_mon_param(self): ( self.mon_param, self.asbinary, - LinearExpression([self.mon_param, self.mon_bin]), + LinearExpression([self.mon_param, self.bin]), ), (self.mon_param, self.zero, self.mon_param), (self.mon_param, self.one, LinearExpression([self.mon_param, 1])), @@ -561,11 +525,7 @@ def test_add_mon_param(self): LinearExpression([self.mon_param, self.param_mut]), ), # 8: - ( - self.mon_param, - self.var, - LinearExpression([self.mon_param, self.mon_var]), - ), + (self.mon_param, self.var, LinearExpression([self.mon_param, self.var])), ( self.mon_param, self.mon_native, @@ -616,11 +576,7 @@ def test_add_mon_param(self): def test_add_mon_npv(self): tests = [ (self.mon_npv, self.invalid, NotImplemented), - ( - self.mon_npv, - self.asbinary, - LinearExpression([self.mon_npv, self.mon_bin]), - ), + (self.mon_npv, self.asbinary, LinearExpression([self.mon_npv, self.bin])), (self.mon_npv, self.zero, self.mon_npv), (self.mon_npv, self.one, LinearExpression([self.mon_npv, 1])), # 4: @@ -633,7 +589,7 @@ def test_add_mon_npv(self): LinearExpression([self.mon_npv, self.param_mut]), ), # 8: - (self.mon_npv, self.var, LinearExpression([self.mon_npv, self.mon_var])), + (self.mon_npv, self.var, LinearExpression([self.mon_npv, self.var])), ( self.mon_npv, self.mon_native, @@ -683,7 +639,7 @@ def test_add_linear(self): ( self.linear, self.asbinary, - LinearExpression(self.linear.args + [self.mon_bin]), + LinearExpression(self.linear.args + [self.bin]), ), (self.linear, self.zero, self.linear), (self.linear, self.one, LinearExpression(self.linear.args + [1])), @@ -697,11 +653,7 @@ def test_add_linear(self): LinearExpression(self.linear.args + [self.param_mut]), ), # 8: - ( - self.linear, - self.var, - LinearExpression(self.linear.args + [self.mon_var]), - ), + (self.linear, self.var, LinearExpression(self.linear.args + [self.var])), ( self.linear, self.mon_native, @@ -868,7 +820,7 @@ def test_add_mutable_l1(self): ( self.mutable_l1, self.asbinary, - LinearExpression(self.mutable_l1.args + [self.mon_bin]), + LinearExpression(self.mutable_l1.args + [self.bin]), ), (self.mutable_l1, self.zero, self.mon_npv), (self.mutable_l1, self.one, LinearExpression(self.mutable_l1.args + [1])), @@ -893,7 +845,7 @@ def test_add_mutable_l1(self): ( self.mutable_l1, self.var, - LinearExpression(self.mutable_l1.args + [self.mon_var]), + LinearExpression(self.mutable_l1.args + [self.var]), ), ( self.mutable_l1, @@ -1075,7 +1027,7 @@ def test_add_param0(self): def test_add_param1(self): tests = [ (self.param1, self.invalid, NotImplemented), - (self.param1, self.asbinary, LinearExpression([1, self.mon_bin])), + (self.param1, self.asbinary, LinearExpression([1, self.bin])), (self.param1, self.zero, 1), (self.param1, self.one, 2), # 4: @@ -1084,7 +1036,7 @@ def test_add_param1(self): (self.param1, self.param, 7), (self.param1, self.param_mut, NPV_SumExpression([1, self.param_mut])), # 8: - (self.param1, self.var, LinearExpression([1, self.mon_var])), + (self.param1, self.var, LinearExpression([1, self.var])), (self.param1, self.mon_native, LinearExpression([1, self.mon_native])), (self.param1, self.mon_param, LinearExpression([1, self.mon_param])), (self.param1, self.mon_npv, LinearExpression([1, self.mon_npv])), @@ -1114,7 +1066,7 @@ def test_add_mutable_l3(self): ( self.mutable_l3, self.asbinary, - LinearExpression(self.mutable_l3.args + [self.mon_bin]), + LinearExpression(self.mutable_l3.args + [self.bin]), ), (self.mutable_l3, self.zero, self.npv), (self.mutable_l3, self.one, NPV_SumExpression(self.mutable_l3.args + [1])), @@ -1143,7 +1095,7 @@ def test_add_mutable_l3(self): ( self.mutable_l3, self.var, - LinearExpression(self.mutable_l3.args + [self.mon_var]), + LinearExpression(self.mutable_l3.args + [self.var]), ), ( self.mutable_l3, @@ -1249,32 +1201,32 @@ def test_sub_asbinary(self): # BooleanVar objects do not support addition (self.asbinary, self.asbinary, NotImplemented), (self.asbinary, self.zero, self.bin), - (self.asbinary, self.one, LinearExpression([self.mon_bin, -1])), + (self.asbinary, self.one, LinearExpression([self.bin, -1])), # 4: - (self.asbinary, self.native, LinearExpression([self.mon_bin, -5])), - (self.asbinary, self.npv, LinearExpression([self.mon_bin, self.minus_npv])), - (self.asbinary, self.param, LinearExpression([self.mon_bin, -6])), + (self.asbinary, self.native, LinearExpression([self.bin, -5])), + (self.asbinary, self.npv, LinearExpression([self.bin, self.minus_npv])), + (self.asbinary, self.param, LinearExpression([self.bin, -6])), ( self.asbinary, self.param_mut, - LinearExpression([self.mon_bin, self.minus_param_mut]), + LinearExpression([self.bin, self.minus_param_mut]), ), # 8: - (self.asbinary, self.var, LinearExpression([self.mon_bin, self.minus_var])), + (self.asbinary, self.var, LinearExpression([self.bin, self.minus_var])), ( self.asbinary, self.mon_native, - LinearExpression([self.mon_bin, self.minus_mon_native]), + LinearExpression([self.bin, self.minus_mon_native]), ), ( self.asbinary, self.mon_param, - LinearExpression([self.mon_bin, self.minus_mon_param]), + LinearExpression([self.bin, self.minus_mon_param]), ), ( self.asbinary, self.mon_npv, - LinearExpression([self.mon_bin, self.minus_mon_npv]), + LinearExpression([self.bin, self.minus_mon_npv]), ), # 12: (self.asbinary, self.linear, SumExpression([self.bin, self.minus_linear])), @@ -1285,7 +1237,7 @@ def test_sub_asbinary(self): ( self.asbinary, self.mutable_l1, - LinearExpression([self.mon_bin, self.minus_mon_npv]), + LinearExpression([self.bin, self.minus_mon_npv]), ), ( self.asbinary, @@ -1293,12 +1245,12 @@ def test_sub_asbinary(self): SumExpression([self.bin, self.minus_mutable_l2]), ), (self.asbinary, self.param0, self.bin), - (self.asbinary, self.param1, LinearExpression([self.mon_bin, -1])), + (self.asbinary, self.param1, LinearExpression([self.bin, -1])), # 20: ( self.asbinary, self.mutable_l3, - LinearExpression([self.mon_bin, self.minus_npv]), + LinearExpression([self.bin, self.minus_npv]), ), ] self._run_cases(tests, operator.sub) @@ -1571,35 +1523,31 @@ def test_sub_param_mut(self): def test_sub_var(self): tests = [ (self.var, self.invalid, NotImplemented), - (self.var, self.asbinary, LinearExpression([self.mon_var, self.minus_bin])), + (self.var, self.asbinary, LinearExpression([self.var, self.minus_bin])), (self.var, self.zero, self.var), - (self.var, self.one, LinearExpression([self.mon_var, -1])), + (self.var, self.one, LinearExpression([self.var, -1])), # 4: - (self.var, self.native, LinearExpression([self.mon_var, -5])), - (self.var, self.npv, LinearExpression([self.mon_var, self.minus_npv])), - (self.var, self.param, LinearExpression([self.mon_var, -6])), + (self.var, self.native, LinearExpression([self.var, -5])), + (self.var, self.npv, LinearExpression([self.var, self.minus_npv])), + (self.var, self.param, LinearExpression([self.var, -6])), ( self.var, self.param_mut, - LinearExpression([self.mon_var, self.minus_param_mut]), + LinearExpression([self.var, self.minus_param_mut]), ), # 8: - (self.var, self.var, LinearExpression([self.mon_var, self.minus_var])), + (self.var, self.var, LinearExpression([self.var, self.minus_var])), ( self.var, self.mon_native, - LinearExpression([self.mon_var, self.minus_mon_native]), + LinearExpression([self.var, self.minus_mon_native]), ), ( self.var, self.mon_param, - LinearExpression([self.mon_var, self.minus_mon_param]), - ), - ( - self.var, - self.mon_npv, - LinearExpression([self.mon_var, self.minus_mon_npv]), + LinearExpression([self.var, self.minus_mon_param]), ), + (self.var, self.mon_npv, LinearExpression([self.var, self.minus_mon_npv])), # 12: ( self.var, @@ -1613,7 +1561,7 @@ def test_sub_var(self): ( self.var, self.mutable_l1, - LinearExpression([self.mon_var, self.minus_mon_npv]), + LinearExpression([self.var, self.minus_mon_npv]), ), ( self.var, @@ -1621,13 +1569,9 @@ def test_sub_var(self): SumExpression([self.var, self.minus_mutable_l2]), ), (self.var, self.param0, self.var), - (self.var, self.param1, LinearExpression([self.mon_var, -1])), + (self.var, self.param1, LinearExpression([self.var, -1])), # 20: - ( - self.var, - self.mutable_l3, - LinearExpression([self.mon_var, self.minus_npv]), - ), + (self.var, self.mutable_l3, LinearExpression([self.var, self.minus_npv])), ] self._run_cases(tests, operator.sub) self._run_cases(tests, operator.isub) @@ -6039,7 +5983,7 @@ def test_mutable_nvp_iadd(self): mutable_npv = _MutableNPVSumExpression([]) tests = [ (mutable_npv, self.invalid, NotImplemented), - (mutable_npv, self.asbinary, _MutableLinearExpression([self.mon_bin])), + (mutable_npv, self.asbinary, _MutableLinearExpression([self.bin])), (mutable_npv, self.zero, _MutableNPVSumExpression([])), (mutable_npv, self.one, _MutableNPVSumExpression([1])), # 4: @@ -6048,7 +5992,7 @@ def test_mutable_nvp_iadd(self): (mutable_npv, self.param, _MutableNPVSumExpression([6])), (mutable_npv, self.param_mut, _MutableNPVSumExpression([self.param_mut])), # 8: - (mutable_npv, self.var, _MutableLinearExpression([self.mon_var])), + (mutable_npv, self.var, _MutableLinearExpression([self.var])), (mutable_npv, self.mon_native, _MutableLinearExpression([self.mon_native])), (mutable_npv, self.mon_param, _MutableLinearExpression([self.mon_param])), (mutable_npv, self.mon_npv, _MutableLinearExpression([self.mon_npv])), @@ -6074,7 +6018,7 @@ def test_mutable_nvp_iadd(self): mutable_npv = _MutableNPVSumExpression([10]) tests = [ (mutable_npv, self.invalid, NotImplemented), - (mutable_npv, self.asbinary, _MutableLinearExpression([10, self.mon_bin])), + (mutable_npv, self.asbinary, _MutableLinearExpression([10, self.bin])), (mutable_npv, self.zero, _MutableNPVSumExpression([10])), (mutable_npv, self.one, _MutableNPVSumExpression([11])), # 4: @@ -6087,7 +6031,7 @@ def test_mutable_nvp_iadd(self): _MutableNPVSumExpression([10, self.param_mut]), ), # 8: - (mutable_npv, self.var, _MutableLinearExpression([10, self.mon_var])), + (mutable_npv, self.var, _MutableLinearExpression([10, self.var])), ( mutable_npv, self.mon_native, @@ -6130,7 +6074,7 @@ def test_mutable_lin_iadd(self): mutable_lin = _MutableLinearExpression([]) tests = [ (mutable_lin, self.invalid, NotImplemented), - (mutable_lin, self.asbinary, _MutableLinearExpression([self.mon_bin])), + (mutable_lin, self.asbinary, _MutableLinearExpression([self.bin])), (mutable_lin, self.zero, _MutableLinearExpression([])), (mutable_lin, self.one, _MutableLinearExpression([1])), # 4: @@ -6139,7 +6083,7 @@ def test_mutable_lin_iadd(self): (mutable_lin, self.param, _MutableLinearExpression([6])), (mutable_lin, self.param_mut, _MutableLinearExpression([self.param_mut])), # 8: - (mutable_lin, self.var, _MutableLinearExpression([self.mon_var])), + (mutable_lin, self.var, _MutableLinearExpression([self.var])), (mutable_lin, self.mon_native, _MutableLinearExpression([self.mon_native])), (mutable_lin, self.mon_param, _MutableLinearExpression([self.mon_param])), (mutable_lin, self.mon_npv, _MutableLinearExpression([self.mon_npv])), @@ -6162,81 +6106,69 @@ def test_mutable_lin_iadd(self): ] self._run_iadd_cases(tests, operator.iadd) - mutable_lin = _MutableLinearExpression([self.mon_bin]) + mutable_lin = _MutableLinearExpression([self.bin]) tests = [ (mutable_lin, self.invalid, NotImplemented), ( mutable_lin, self.asbinary, - _MutableLinearExpression([self.mon_bin, self.mon_bin]), + _MutableLinearExpression([self.bin, self.bin]), ), - (mutable_lin, self.zero, _MutableLinearExpression([self.mon_bin])), - (mutable_lin, self.one, _MutableLinearExpression([self.mon_bin, 1])), + (mutable_lin, self.zero, _MutableLinearExpression([self.bin])), + (mutable_lin, self.one, _MutableLinearExpression([self.bin, 1])), # 4: - (mutable_lin, self.native, _MutableLinearExpression([self.mon_bin, 5])), - (mutable_lin, self.npv, _MutableLinearExpression([self.mon_bin, self.npv])), - (mutable_lin, self.param, _MutableLinearExpression([self.mon_bin, 6])), + (mutable_lin, self.native, _MutableLinearExpression([self.bin, 5])), + (mutable_lin, self.npv, _MutableLinearExpression([self.bin, self.npv])), + (mutable_lin, self.param, _MutableLinearExpression([self.bin, 6])), ( mutable_lin, self.param_mut, - _MutableLinearExpression([self.mon_bin, self.param_mut]), + _MutableLinearExpression([self.bin, self.param_mut]), ), # 8: - ( - mutable_lin, - self.var, - _MutableLinearExpression([self.mon_bin, self.mon_var]), - ), + (mutable_lin, self.var, _MutableLinearExpression([self.bin, self.var])), ( mutable_lin, self.mon_native, - _MutableLinearExpression([self.mon_bin, self.mon_native]), + _MutableLinearExpression([self.bin, self.mon_native]), ), ( mutable_lin, self.mon_param, - _MutableLinearExpression([self.mon_bin, self.mon_param]), + _MutableLinearExpression([self.bin, self.mon_param]), ), ( mutable_lin, self.mon_npv, - _MutableLinearExpression([self.mon_bin, self.mon_npv]), + _MutableLinearExpression([self.bin, self.mon_npv]), ), # 12: ( mutable_lin, self.linear, - _MutableLinearExpression([self.mon_bin] + self.linear.args), - ), - ( - mutable_lin, - self.sum, - _MutableSumExpression([self.mon_bin] + self.sum.args), - ), - ( - mutable_lin, - self.other, - _MutableSumExpression([self.mon_bin, self.other]), + _MutableLinearExpression([self.bin] + self.linear.args), ), - (mutable_lin, self.mutable_l0, _MutableLinearExpression([self.mon_bin])), + (mutable_lin, self.sum, _MutableSumExpression([self.bin] + self.sum.args)), + (mutable_lin, self.other, _MutableSumExpression([self.bin, self.other])), + (mutable_lin, self.mutable_l0, _MutableLinearExpression([self.bin])), # 16: ( mutable_lin, self.mutable_l1, - _MutableLinearExpression([self.mon_bin] + self.mutable_l1.args), + _MutableLinearExpression([self.bin] + self.mutable_l1.args), ), ( mutable_lin, self.mutable_l2, - _MutableSumExpression([self.mon_bin] + self.mutable_l2.args), + _MutableSumExpression([self.bin] + self.mutable_l2.args), ), - (mutable_lin, self.param0, _MutableLinearExpression([self.mon_bin])), - (mutable_lin, self.param1, _MutableLinearExpression([self.mon_bin, 1])), + (mutable_lin, self.param0, _MutableLinearExpression([self.bin])), + (mutable_lin, self.param1, _MutableLinearExpression([self.bin, 1])), # 20: ( mutable_lin, self.mutable_l3, - _MutableLinearExpression([self.mon_bin, self.npv]), + _MutableLinearExpression([self.bin, self.npv]), ), ] self._run_iadd_cases(tests, operator.iadd) diff --git a/pyomo/core/tests/unit/test_visitor.py b/pyomo/core/tests/unit/test_visitor.py index fada7d6f6b2..12fb98d1d19 100644 --- a/pyomo/core/tests/unit/test_visitor.py +++ b/pyomo/core/tests/unit/test_visitor.py @@ -437,9 +437,7 @@ def test_replacement_linear_expression_with_constant(self): sub_map = dict() sub_map[id(m.x)] = 5 e2 = replace_expressions(e, sub_map) - assertExpressionsEqual( - self, e2, LinearExpression([10, MonomialTermExpression((1, m.y))]) - ) + assertExpressionsEqual(self, e2, LinearExpression([10, m.y])) e = LinearExpression(linear_coefs=[2, 3], linear_vars=[m.x, m.y]) sub_map = dict() @@ -886,20 +884,7 @@ def test_replace(self): assertExpressionsEqual( self, SumExpression( - [ - LinearExpression( - [ - MonomialTermExpression((1, m.y[1])), - MonomialTermExpression((1, m.y[2])), - ] - ), - LinearExpression( - [ - MonomialTermExpression((1, m.y[2])), - MonomialTermExpression((1, m.y[3])), - ] - ), - ] + [LinearExpression([m.y[1], m.y[2]]), LinearExpression([m.y[2], m.y[3]])] ) == 0, f, @@ -930,9 +915,7 @@ def test_npv_sum(self): e3 = replace_expressions(e1, {id(m.p1): m.x}) assertExpressionsEqual(self, e2, m.p2 + 2) - assertExpressionsEqual( - self, e3, LinearExpression([MonomialTermExpression((1, m.x)), 2]) - ) + assertExpressionsEqual(self, e3, LinearExpression([m.x, 2])) def test_npv_negation(self): m = ConcreteModel() diff --git a/pyomo/gdp/tests/common_tests.py b/pyomo/gdp/tests/common_tests.py index 28025816262..5d0d6f6c21b 100644 --- a/pyomo/gdp/tests/common_tests.py +++ b/pyomo/gdp/tests/common_tests.py @@ -425,12 +425,7 @@ def check_two_term_disjunction_xor(self, xor, disj1, disj2): assertExpressionsEqual( self, xor.body, - EXPR.LinearExpression( - [ - EXPR.MonomialTermExpression((1, disj1.binary_indicator_var)), - EXPR.MonomialTermExpression((1, disj2.binary_indicator_var)), - ] - ), + EXPR.LinearExpression([disj1.binary_indicator_var, disj2.binary_indicator_var]), ) self.assertEqual(xor.lower, 1) self.assertEqual(xor.upper, 1) diff --git a/pyomo/gdp/tests/test_bigm.py b/pyomo/gdp/tests/test_bigm.py index 2383d4587f5..c6ac49f6d36 100644 --- a/pyomo/gdp/tests/test_bigm.py +++ b/pyomo/gdp/tests/test_bigm.py @@ -155,10 +155,7 @@ def test_or_constraints(self): self, orcons.body, EXPR.LinearExpression( - [ - EXPR.MonomialTermExpression((1, m.d[0].binary_indicator_var)), - EXPR.MonomialTermExpression((1, m.d[1].binary_indicator_var)), - ] + [m.d[0].binary_indicator_var, m.d[1].binary_indicator_var] ), ) self.assertEqual(orcons.lower, 1) diff --git a/pyomo/gdp/tests/test_binary_multiplication.py b/pyomo/gdp/tests/test_binary_multiplication.py index aa846c4710a..ae2c44b899e 100644 --- a/pyomo/gdp/tests/test_binary_multiplication.py +++ b/pyomo/gdp/tests/test_binary_multiplication.py @@ -146,10 +146,7 @@ def test_or_constraints(self): self, orcons.body, EXPR.LinearExpression( - [ - EXPR.MonomialTermExpression((1, m.d[0].binary_indicator_var)), - EXPR.MonomialTermExpression((1, m.d[1].binary_indicator_var)), - ] + [m.d[0].binary_indicator_var, m.d[1].binary_indicator_var] ), ) self.assertEqual(orcons.lower, 1) diff --git a/pyomo/gdp/tests/test_disjunct.py b/pyomo/gdp/tests/test_disjunct.py index d969b245ee7..f93ac31fb0f 100644 --- a/pyomo/gdp/tests/test_disjunct.py +++ b/pyomo/gdp/tests/test_disjunct.py @@ -632,19 +632,13 @@ def test_cast_to_binary(self): out = StringIO() with LoggingIntercept(out): e = m.iv + 1 - assertExpressionsEqual( - self, e, EXPR.LinearExpression([EXPR.MonomialTermExpression((1, m.biv)), 1]) - ) + assertExpressionsEqual(self, e, EXPR.LinearExpression([m.biv, 1])) self.assertIn(deprecation_msg, out.getvalue()) out = StringIO() with LoggingIntercept(out): e = m.iv - 1 - assertExpressionsEqual( - self, - e, - EXPR.LinearExpression([EXPR.MonomialTermExpression((1, m.biv)), -1]), - ) + assertExpressionsEqual(self, e, EXPR.LinearExpression([m.biv, -1])) self.assertIn(deprecation_msg, out.getvalue()) out = StringIO() @@ -665,9 +659,7 @@ def test_cast_to_binary(self): out = StringIO() with LoggingIntercept(out): e = 1 + m.iv - assertExpressionsEqual( - self, e, EXPR.LinearExpression([1, EXPR.MonomialTermExpression((1, m.biv))]) - ) + assertExpressionsEqual(self, e, EXPR.LinearExpression([1, m.biv])) self.assertIn(deprecation_msg, out.getvalue()) out = StringIO() @@ -699,20 +691,14 @@ def test_cast_to_binary(self): with LoggingIntercept(out): a = m.iv a += 1 - assertExpressionsEqual( - self, a, EXPR.LinearExpression([EXPR.MonomialTermExpression((1, m.biv)), 1]) - ) + assertExpressionsEqual(self, a, EXPR.LinearExpression([m.biv, 1])) self.assertIn(deprecation_msg, out.getvalue()) out = StringIO() with LoggingIntercept(out): a = m.iv a -= 1 - assertExpressionsEqual( - self, - a, - EXPR.LinearExpression([EXPR.MonomialTermExpression((1, m.biv)), -1]), - ) + assertExpressionsEqual(self, a, EXPR.LinearExpression([m.biv, -1])) self.assertIn(deprecation_msg, out.getvalue()) out = StringIO() From 7299a79db5481a5ac4faf683d1d71bba074c01b7 Mon Sep 17 00:00:00 2001 From: robbybp Date: Thu, 7 Mar 2024 13:05:52 -0700 Subject: [PATCH 1352/1797] remove redundant implementation of acceptChildResult --- pyomo/util/subsystems.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyomo/util/subsystems.py b/pyomo/util/subsystems.py index 9a2a8b6635d..35deaab7605 100644 --- a/pyomo/util/subsystems.py +++ b/pyomo/util/subsystems.py @@ -59,9 +59,6 @@ def finalizeResult(self, result): def enterNode(self, node): pass - def acceptChildResult(self, node, data, child_result, child_idx): - pass - def acceptChildResult(self, node, data, child_result, child_idx): if child_result.__class__ in native_types: return False, None From 8b66e10c08ac149272ebd94b7f847cfed8dba7b6 Mon Sep 17 00:00:00 2001 From: robbybp Date: Thu, 7 Mar 2024 13:06:47 -0700 Subject: [PATCH 1353/1797] comment out unnecessary walker methods --- pyomo/util/subsystems.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyomo/util/subsystems.py b/pyomo/util/subsystems.py index 35deaab7605..58921b37cca 100644 --- a/pyomo/util/subsystems.py +++ b/pyomo/util/subsystems.py @@ -56,13 +56,13 @@ def exitNode(self, node, data): def finalizeResult(self, result): return self._functions - def enterNode(self, node): - pass + #def enterNode(self, node): + # pass - def acceptChildResult(self, node, data, child_result, child_idx): - if child_result.__class__ in native_types: - return False, None - return child_result.is_expression_type(), None + #def acceptChildResult(self, node, data, child_result, child_idx): + # if child_result.__class__ in native_types: + # return False, None + # return child_result.is_expression_type(), None def identify_external_functions(expr): From e7a4c948e7e05455f44745c472b9af4f5265edd2 Mon Sep 17 00:00:00 2001 From: Bernard Knueven Date: Thu, 7 Mar 2024 16:09:42 -0700 Subject: [PATCH 1354/1797] 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 1355/1797] 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()) { ; From 46b89e657ac60867fa29f4c763bf8521097d3426 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 7 Mar 2024 16:37:47 -0700 Subject: [PATCH 1356/1797] Change around the logic to use nobjectives --- pyomo/contrib/solver/base.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 54871e90c2f..55b013facb1 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -41,7 +41,8 @@ class SolverBase(abc.ABC): """ 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. + - 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 non-persistent solvers. @@ -420,12 +421,11 @@ def _map_results(self, model, results): legacy_results.solver.termination_message = str(results.termination_condition) legacy_results.problem.number_of_constraints = model.nconstraints() legacy_results.problem.number_of_variables = model.nvariables() - if model.nobjectives() == 0: - legacy_results.problem.number_of_objectives = 0 - else: + number_of_objectives = model.nobjectives() + legacy_results.problem.number_of_objectives = number_of_objectives + if number_of_objectives > 0: obj = get_objective(model) legacy_results.problem.sense = obj.sense - legacy_results.problem.number_of_objectives = len(obj) if obj.sense == minimize: legacy_results.problem.lower_bound = results.objective_bound From 70f225e6c0f854252fb94ecfb1eef0d58a6b818d Mon Sep 17 00:00:00 2001 From: robbybp Date: Thu, 7 Mar 2024 16:46:08 -0700 Subject: [PATCH 1357/1797] apply black --- pyomo/util/subsystems.py | 7 ++++--- pyomo/util/tests/test_subsystems.py | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pyomo/util/subsystems.py b/pyomo/util/subsystems.py index 58921b37cca..ff5f6dedd58 100644 --- a/pyomo/util/subsystems.py +++ b/pyomo/util/subsystems.py @@ -25,7 +25,6 @@ class _ExternalFunctionVisitor(StreamBasedExpressionVisitor): - def __init__(self, descend_into_named_expressions=True): super().__init__() self._descend_into_named_expressions = descend_into_named_expressions @@ -56,10 +55,10 @@ def exitNode(self, node, data): def finalizeResult(self, result): return self._functions - #def enterNode(self, node): + # def enterNode(self, node): # pass - #def acceptChildResult(self, node, data, child_result, child_idx): + # def acceptChildResult(self, node, data, child_result, child_idx): # if child_result.__class__ in native_types: # return False, None # return child_result.is_expression_type(), None @@ -114,6 +113,8 @@ def add_local_external_functions(block): from pyomo.common.timing import HierarchicalTimer + + def create_subsystem_block( constraints, variables=None, diff --git a/pyomo/util/tests/test_subsystems.py b/pyomo/util/tests/test_subsystems.py index f102670ba62..a9b8a215fcc 100644 --- a/pyomo/util/tests/test_subsystems.py +++ b/pyomo/util/tests/test_subsystems.py @@ -304,11 +304,11 @@ def _make_model_with_external_functions(self, named_expressions=False): m.subexpr = pyo.Expression(pyo.PositiveIntegers) subexpr1 = m.subexpr[1] = 2 * m.fermi(m.v1) subexpr2 = m.subexpr[2] = m.bessel(m.v1) - m.bessel(m.v2) - subexpr3 = m.subexpr[3] = m.subexpr[2] + m.v3 ** 2 + subexpr3 = m.subexpr[3] = m.subexpr[2] + m.v3**2 else: subexpr1 = 2 * m.fermi(m.v1) subexpr2 = m.bessel(m.v1) - m.bessel(m.v2) - subexpr3 = m.subexpr[2] + m.v3 ** 2 + subexpr3 = m.subexpr[2] + m.v3**2 m.con1 = pyo.Constraint(expr=m.v1 == 0.5) m.con2 = pyo.Constraint(expr=subexpr1 + m.v2**2 - m.v3 == 1.0) m.con3 = pyo.Constraint(expr=subexpr3 == 2.0) From 1c6a6c79ec2297c142367da385145c6ce7ba4884 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Thu, 7 Mar 2024 17:02:03 -0700 Subject: [PATCH 1358/1797] type hints --- pyomo/core/base/block.py | 5 ++++- pyomo/core/base/constraint.py | 5 ++++- pyomo/core/base/set.py | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index 908e0ef1abd..f3d9c7458e1 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -2265,8 +2265,11 @@ class IndexedBlock(Block): def __init__(self, *args, **kwds): Block.__init__(self, *args, **kwds) + @overload def __getitem__(self, index) -> _BlockData: - return super().__getitem__(index) + ... + + __getitem__ = IndexedComponent.__getitem__ # type: ignore # diff --git a/pyomo/core/base/constraint.py b/pyomo/core/base/constraint.py index a36bc679e49..899bc8c9499 100644 --- a/pyomo/core/base/constraint.py +++ b/pyomo/core/base/constraint.py @@ -1033,8 +1033,11 @@ def add(self, index, expr): """Add a constraint with a given index.""" return self.__setitem__(index, expr) + @overload def __getitem__(self, index) -> _GeneralConstraintData: - return super().__getitem__(index) + ... + + __getitem__ = IndexedComponent.__getitem__ # type: ignore @ModelComponentFactory.register("A list of constraint expressions.") diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index b8ddae14e9f..9217c09866e 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -2382,8 +2382,11 @@ def data(self): "Return a dict containing the data() of each Set in this IndexedSet" return {k: v.data() for k, v in self.items()} + @overload def __getitem__(self, index) -> _SetData: - return super().__getitem__(index) + ... + + __getitem__ = IndexedComponent.__getitem__ # type: ignore class FiniteScalarSet(_FiniteSetData, Set): From 00d2a977471d04a032079c374eb6f4e9e7f2d6cc Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Thu, 7 Mar 2024 17:06:30 -0700 Subject: [PATCH 1359/1797] run black --- pyomo/core/base/block.py | 3 +-- pyomo/core/base/constraint.py | 3 +-- pyomo/core/base/set.py | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index f3d9c7458e1..2918ef78b00 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -2266,8 +2266,7 @@ def __init__(self, *args, **kwds): Block.__init__(self, *args, **kwds) @overload - def __getitem__(self, index) -> _BlockData: - ... + def __getitem__(self, index) -> _BlockData: ... __getitem__ = IndexedComponent.__getitem__ # type: ignore diff --git a/pyomo/core/base/constraint.py b/pyomo/core/base/constraint.py index 899bc8c9499..8916777e9c8 100644 --- a/pyomo/core/base/constraint.py +++ b/pyomo/core/base/constraint.py @@ -1034,8 +1034,7 @@ def add(self, index, expr): return self.__setitem__(index, expr) @overload - def __getitem__(self, index) -> _GeneralConstraintData: - ... + def __getitem__(self, index) -> _GeneralConstraintData: ... __getitem__ = IndexedComponent.__getitem__ # type: ignore diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index 9217c09866e..b3277ab3260 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -2383,8 +2383,7 @@ def data(self): return {k: v.data() for k, v in self.items()} @overload - def __getitem__(self, index) -> _SetData: - ... + def __getitem__(self, index) -> _SetData: ... __getitem__ = IndexedComponent.__getitem__ # type: ignore From b96d12eea18f9c0bf570ef0b420e4fcbd0f26370 Mon Sep 17 00:00:00 2001 From: robbybp Date: Thu, 7 Mar 2024 17:17:27 -0700 Subject: [PATCH 1360/1797] arguments on one line to keep black happy --- pyomo/contrib/incidence_analysis/scc_solver.py | 6 +----- pyomo/util/subsystems.py | 8 ++------ 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/scc_solver.py b/pyomo/contrib/incidence_analysis/scc_solver.py index 6c556646a8c..86b02c94194 100644 --- a/pyomo/contrib/incidence_analysis/scc_solver.py +++ b/pyomo/contrib/incidence_analysis/scc_solver.py @@ -26,11 +26,7 @@ def generate_strongly_connected_components( - constraints, - variables=None, - include_fixed=False, - igraph=None, - timer=None, + constraints, variables=None, include_fixed=False, igraph=None, timer=None ): """Yield in order ``_BlockData`` that each contain the variables and constraints of a single diagonal block in a block lower triangularization diff --git a/pyomo/util/subsystems.py b/pyomo/util/subsystems.py index ff5f6dedd58..79fbdd2d281 100644 --- a/pyomo/util/subsystems.py +++ b/pyomo/util/subsystems.py @@ -75,8 +75,7 @@ def add_local_external_functions(block): named_expressions = [] visitor = _ExternalFunctionVisitor(descend_into_named_expressions=False) for comp in block.component_data_objects( - (Constraint, Expression, Objective), - active=True, + (Constraint, Expression, Objective), active=True ): ef_exprs.extend(visitor.walk_expression(comp.expr)) named_expr_set = ComponentSet(visitor.named_expressions) @@ -116,10 +115,7 @@ def add_local_external_functions(block): def create_subsystem_block( - constraints, - variables=None, - include_fixed=False, - timer=None, + constraints, variables=None, include_fixed=False, timer=None ): """This function creates a block to serve as a subsystem with the specified variables and constraints. To satisfy certain writers, other From 67b9b2f958772e3e0fe699c0ca687912cf5585a4 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Mar 2024 17:37:13 -0700 Subject: [PATCH 1361/1797] Update PyROS to admit VarData in LinearExpressions --- .../contrib/pyros/pyros_algorithm_methods.py | 20 ++++++---- pyomo/contrib/pyros/tests/test_grcs.py | 40 +++++++++++-------- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/pyomo/contrib/pyros/pyros_algorithm_methods.py b/pyomo/contrib/pyros/pyros_algorithm_methods.py index 45b652447ff..f847a3a73dc 100644 --- a/pyomo/contrib/pyros/pyros_algorithm_methods.py +++ b/pyomo/contrib/pyros/pyros_algorithm_methods.py @@ -26,6 +26,7 @@ ) from pyomo.contrib.pyros.util import get_main_elapsed_time, coefficient_matching from pyomo.core.base import value +from pyomo.core.expr import MonomialTermExpression from pyomo.common.collections import ComponentSet, ComponentMap from pyomo.core.base.var import _VarData as VarData from itertools import chain @@ -69,14 +70,17 @@ def get_dr_var_to_scaled_expr_map( 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 + if isinstance(term, MonomialTermExpression): + 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 + elif isinstance(term, VarData): + var_to_scaled_expr_map[term] = MonomialTermExpression((1, term)) return var_to_scaled_expr_map diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index df3568e42a4..c308f0d6990 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -19,6 +19,7 @@ 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.base.var import _VarData from pyomo.core.expr import ( identify_variables, identify_mutable_parameters, @@ -571,22 +572,30 @@ def test_dr_eqns_form_correct(self): 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}" - ), - ) + # term should be either a monomial expression or scalar variable + if isinstance(term, MonomialTermExpression): + # should be 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] + dr_var_multiplicand = term.args[1] + else: + self.assertIsInstance(term, _VarData) + param_product_multiplicand = 1 + dr_var_multiplicand = term - # 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,) @@ -612,7 +621,6 @@ def test_dr_eqns_form_correct(self): # check that DR variable participating in the monomial # is as expected - dr_var_multiplicand = term.args[1] self.assertIs( dr_var_multiplicand, dr_var, From ec4f733a1b6c7de8ef9f624ece8befd78f081128 Mon Sep 17 00:00:00 2001 From: robbybp Date: Thu, 7 Mar 2024 18:02:27 -0700 Subject: [PATCH 1362/1797] use_calc_var default should be True --- pyomo/contrib/incidence_analysis/scc_solver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/incidence_analysis/scc_solver.py b/pyomo/contrib/incidence_analysis/scc_solver.py index 86b02c94194..76eb7f91cb0 100644 --- a/pyomo/contrib/incidence_analysis/scc_solver.py +++ b/pyomo/contrib/incidence_analysis/scc_solver.py @@ -100,7 +100,7 @@ def solve_strongly_connected_components( *, solver=None, solve_kwds=None, - use_calc_var=False, + use_calc_var=True, calc_var_kwds=None, timer=None, ): From cd54e6f72563f46adbfd8b61ac76e6d7f0d53824 Mon Sep 17 00:00:00 2001 From: robbybp Date: Thu, 7 Mar 2024 18:05:22 -0700 Subject: [PATCH 1363/1797] re-add exception I accidentally deleted --- pyomo/contrib/incidence_analysis/scc_solver.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/incidence_analysis/scc_solver.py b/pyomo/contrib/incidence_analysis/scc_solver.py index 76eb7f91cb0..eff4f5ae5fa 100644 --- a/pyomo/contrib/incidence_analysis/scc_solver.py +++ b/pyomo/contrib/incidence_analysis/scc_solver.py @@ -175,7 +175,15 @@ def solve_strongly_connected_components( ) timer.stop("calc-var-from-con") else: - inputs = list(scc.input_vars.values()) + if solver is None: + var_names = [var.name for var in scc.vars.values()][:10] + con_names = [con.name for con in scc.cons.values()][:10] + raise RuntimeError( + "An external solver is required if block has strongly\n" + "connected components of size greater than one (is not" + " a DAG).\nGot an SCC of size %sx%s including" + " components:\n%s\n%s" % (N, N, var_names, con_names) + ) if log_blocks: _log.debug(f"Solving {N}x{N} block.") timer.start("scc-subsolver") From c7b34d043876653b8a53d6d0aeca3937d45e5319 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Mar 2024 18:08:39 -0700 Subject: [PATCH 1364/1797] Resolve incompatibility with Python<=3.10 --- pyomo/core/expr/template_expr.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/pyomo/core/expr/template_expr.py b/pyomo/core/expr/template_expr.py index 6ac4c8c041f..a7f301e32f1 100644 --- a/pyomo/core/expr/template_expr.py +++ b/pyomo/core/expr/template_expr.py @@ -117,18 +117,10 @@ def _to_string(self, values, verbose, smap): return "%s[%s]" % (values[0], ','.join(values[1:])) def _resolve_template(self, args): - return args[0][*args[1:]] + return args[0].__getitem__(args[1:]) def _apply_operation(self, result): - args = tuple( - ( - arg - if arg.__class__ in native_types or not arg.is_numeric_type() - else value(arg) - ) - for arg in result[1:] - ) - return result[0][*result[1:]] + return result[0].__getitem__(result[1:]) class Numeric_GetItemExpression(GetItemExpression, NumericExpression): From fadca016f1efcf6f59bdfce2c6f7a5e926836a0e Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Mar 2024 18:09:06 -0700 Subject: [PATCH 1365/1797] Minor code readibility improvement --- pyomo/core/expr/template_expr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/core/expr/template_expr.py b/pyomo/core/expr/template_expr.py index a7f301e32f1..d30046e9d82 100644 --- a/pyomo/core/expr/template_expr.py +++ b/pyomo/core/expr/template_expr.py @@ -251,8 +251,8 @@ def nargs(self): return 2 def _apply_operation(self, result): - assert len(result) == 2 - return getattr(result[0], result[1]) + obj, attr = result + return getattr(obj, attr) def _to_string(self, values, verbose, smap): assert len(values) == 2 From f7b70038e58fe5c0eb3a98a8eaf005cb6113ae0a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Mar 2024 21:09:54 -0700 Subject: [PATCH 1366/1797] Update doc tests to track change in LinearExpression arg types --- doc/OnlineDocs/src/expr/managing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/OnlineDocs/src/expr/managing.py b/doc/OnlineDocs/src/expr/managing.py index 00d521d16ab..ff149e4fd5c 100644 --- a/doc/OnlineDocs/src/expr/managing.py +++ b/doc/OnlineDocs/src/expr/managing.py @@ -181,7 +181,7 @@ def clone_expression(expr): # x[0] + 5*x[1] print(str(ce)) # x[0] + 5*x[1] -print(e.arg(0) is not ce.arg(0)) +print(e.arg(0) is ce.arg(0)) # True print(e.arg(1) is not ce.arg(1)) # True From 7e66907075ccc0dc304270ebd059afda95012c5a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 8 Mar 2024 09:23:26 -0700 Subject: [PATCH 1367/1797] NFC: update docs --- pyomo/core/expr/numeric_expr.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pyomo/core/expr/numeric_expr.py b/pyomo/core/expr/numeric_expr.py index 2cf4073b49f..9b624d2b8bd 100644 --- a/pyomo/core/expr/numeric_expr.py +++ b/pyomo/core/expr/numeric_expr.py @@ -1234,9 +1234,11 @@ class LinearExpression(SumExpression): """An expression object for linear polynomials. This is a derived :py:class`SumExpression` that guarantees all - arguments are either not potentially variable (e.g., native types, - Params, or NPV expressions) OR :py:class:`MonomialTermExpression` - objects. + arguments are one of the following types: + + - not potentially variable (e.g., native types, Params, or NPV expressions) + - :py:class:`MonomialTermExpression` + - :py:class:`_VarData` Args: args (tuple): Children nodes @@ -1253,7 +1255,7 @@ def __init__(self, args=None, constant=None, linear_coefs=None, linear_vars=None You can specify `args` OR (`constant`, `linear_coefs`, and `linear_vars`). If `args` is provided, it should be a list that - contains only constants, NPV objects/expressions, or + contains only constants, NPV objects/expressions, variables, or :py:class:`MonomialTermExpression` objects. Alternatively, you can specify the constant, the list of linear_coefs and the list of linear_vars separately. Note that these lists are NOT From e5c8027420cb29333ba4e9fe30c5617387568c6f Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Fri, 8 Mar 2024 14:13:07 -0700 Subject: [PATCH 1368/1797] Add missing import --- pyomo/core/base/constraint.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/core/base/constraint.py b/pyomo/core/base/constraint.py index 8916777e9c8..fde1160e563 100644 --- a/pyomo/core/base/constraint.py +++ b/pyomo/core/base/constraint.py @@ -44,6 +44,7 @@ ActiveIndexedComponent, UnindexedComponent_set, rule_wrapper, + IndexedComponent, ) from pyomo.core.base.set import Set from pyomo.core.base.disable_methods import disable_methods From 476fa8d7bdc80c37ba0d3aeca5a2a9c5d586c849 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Mar 2024 09:11:35 -0700 Subject: [PATCH 1369/1797] Allow bare variables in LinearExpression nodes --- pyomo/core/expr/numeric_expr.py | 39 ++++++++++++++++-------------- pyomo/repn/linear.py | 38 ++++++++++++++++------------- pyomo/repn/plugins/baron_writer.py | 19 ++++++++++++--- pyomo/repn/plugins/gams_writer.py | 10 +++++++- pyomo/repn/plugins/nl_writer.py | 14 +++++++++++ pyomo/repn/quadratic.py | 25 +++++++------------ pyomo/repn/standard_repn.py | 22 +++++++++++++++++ pyomo/repn/tests/test_linear.py | 2 +- 8 files changed, 112 insertions(+), 57 deletions(-) diff --git a/pyomo/core/expr/numeric_expr.py b/pyomo/core/expr/numeric_expr.py index c1199ffdcad..8ce7ee81c9a 100644 --- a/pyomo/core/expr/numeric_expr.py +++ b/pyomo/core/expr/numeric_expr.py @@ -1298,8 +1298,14 @@ def _build_cache(self): if arg.__class__ is MonomialTermExpression: coef.append(arg._args_[0]) var.append(arg._args_[1]) - else: + elif arg.__class__ in native_numeric_types: const += arg + elif not arg.is_potentially_variable(): + const += arg + else: + assert arg.is_potentially_variable() + coef.append(1) + var.append(arg) LinearExpression._cache = (self, const, coef, var) @property @@ -1325,7 +1331,7 @@ def create_node_with_local_data(self, args, classtype=None): classtype = self.__class__ if type(args) is not list: args = list(args) - for i, arg in enumerate(args): + for arg in args: if arg.__class__ in self._allowable_linear_expr_arg_types: # 99% of the time, the arg type hasn't changed continue @@ -1336,8 +1342,7 @@ def create_node_with_local_data(self, args, classtype=None): # NPV expressions are OK pass elif arg.is_variable_type(): - # vars are OK, but need to be mapped to monomial terms - args[i] = MonomialTermExpression((1, arg)) + # vars are OK continue else: # For anything else, convert this to a general sum @@ -1820,7 +1825,7 @@ def _add_native_param(a, b): def _add_native_var(a, b): if not a: return b - return LinearExpression([a, MonomialTermExpression((1, b))]) + return LinearExpression([a, b]) def _add_native_monomial(a, b): @@ -1871,7 +1876,7 @@ def _add_npv_param(a, b): def _add_npv_var(a, b): - return LinearExpression([a, MonomialTermExpression((1, b))]) + return LinearExpression([a, b]) def _add_npv_monomial(a, b): @@ -1929,7 +1934,7 @@ def _add_param_var(a, b): a = a.value if not a: return b - return LinearExpression([a, MonomialTermExpression((1, b))]) + return LinearExpression([a, b]) def _add_param_monomial(a, b): @@ -1972,11 +1977,11 @@ def _add_param_other(a, b): def _add_var_native(a, b): if not b: return a - return LinearExpression([MonomialTermExpression((1, a)), b]) + return LinearExpression([a, b]) def _add_var_npv(a, b): - return LinearExpression([MonomialTermExpression((1, a)), b]) + return LinearExpression([a, b]) def _add_var_param(a, b): @@ -1984,21 +1989,19 @@ def _add_var_param(a, b): b = b.value if not b: return a - return LinearExpression([MonomialTermExpression((1, a)), b]) + return LinearExpression([a, b]) def _add_var_var(a, b): - return LinearExpression( - [MonomialTermExpression((1, a)), MonomialTermExpression((1, b))] - ) + return LinearExpression([a, b]) def _add_var_monomial(a, b): - return LinearExpression([MonomialTermExpression((1, a)), b]) + return LinearExpression([a, b]) def _add_var_linear(a, b): - return b._trunc_append(MonomialTermExpression((1, a))) + return b._trunc_append(a) def _add_var_sum(a, b): @@ -2033,7 +2036,7 @@ def _add_monomial_param(a, b): def _add_monomial_var(a, b): - return LinearExpression([a, MonomialTermExpression((1, b))]) + return LinearExpression([a, b]) def _add_monomial_monomial(a, b): @@ -2076,7 +2079,7 @@ def _add_linear_param(a, b): def _add_linear_var(a, b): - return a._trunc_append(MonomialTermExpression((1, b))) + return a._trunc_append(b) def _add_linear_monomial(a, b): @@ -2401,7 +2404,7 @@ def _iadd_mutablelinear_param(a, b): def _iadd_mutablelinear_var(a, b): - a._args_.append(MonomialTermExpression((1, b))) + a._args_.append(b) a._nargs += 1 return a diff --git a/pyomo/repn/linear.py b/pyomo/repn/linear.py index 6ab4abfdaf5..d601ccbcd7c 100644 --- a/pyomo/repn/linear.py +++ b/pyomo/repn/linear.py @@ -31,8 +31,8 @@ MonomialTermExpression, LinearExpression, SumExpression, - NPV_SumExpression, ExternalFunctionExpression, + mutable_expression, ) from pyomo.core.expr.relational_expr import ( EqualityExpression, @@ -120,22 +120,14 @@ def to_expression(self, visitor): ans = 0 if self.linear: var_map = visitor.var_map - if len(self.linear) == 1: - vid, coef = next(iter(self.linear.items())) - if coef == 1: - ans += var_map[vid] - elif coef: - ans += MonomialTermExpression((coef, var_map[vid])) - else: - pass - else: - ans += LinearExpression( - [ - MonomialTermExpression((coef, var_map[vid])) - for vid, coef in self.linear.items() - if coef - ] - ) + with mutable_expression() as e: + for vid, coef in self.linear.items(): + if coef: + e += coef * var_map[vid] + if e.nargs() > 1: + ans += e + elif e.nargs() == 1: + ans += e.arg(0) if self.constant: ans += self.constant if self.multiplier != 1: @@ -704,6 +696,18 @@ def _before_linear(visitor, child): linear[_id] = arg1 elif arg.__class__ in native_numeric_types: const += arg + elif arg.is_variable_type(): + _id = id(arg) + if _id not in var_map: + if arg.fixed: + const += visitor.check_constant(arg.value, arg) + continue + LinearBeforeChildDispatcher._record_var(visitor, arg) + linear[_id] = 1 + elif _id in linear: + linear[_id] += 1 + else: + linear[_id] = 1 else: try: const += visitor.check_constant(visitor.evaluate(arg), arg) diff --git a/pyomo/repn/plugins/baron_writer.py b/pyomo/repn/plugins/baron_writer.py index de19b5aad73..ab673b0c1c3 100644 --- a/pyomo/repn/plugins/baron_writer.py +++ b/pyomo/repn/plugins/baron_writer.py @@ -174,15 +174,26 @@ def _monomial_to_string(self, node): return self.smap.getSymbol(var) return ftoa(const, True) + '*' + self.smap.getSymbol(var) + def _var_to_string(self, node): + if node.is_fixed(): + return ftoa(node.value, True) + self.variables.add(id(node)) + return self.smap.getSymbol(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() + if arg.__class__ is EXPR.MonomialTermExpression + else ( + ftoa(arg) + if arg.__class__ in native_numeric_types + else ( + self._var_to_string(arg) + if arg.is_variable_type() + else ftoa(value(arg), True) + ) ) - else ftoa(value(arg)) ) for arg in node.args ] diff --git a/pyomo/repn/plugins/gams_writer.py b/pyomo/repn/plugins/gams_writer.py index 5f94f176762..0756cb64920 100644 --- a/pyomo/repn/plugins/gams_writer.py +++ b/pyomo/repn/plugins/gams_writer.py @@ -183,7 +183,15 @@ def _linear_to_string(self, node): ( self._monomial_to_string(arg) if arg.__class__ is EXPR.MonomialTermExpression - else ftoa(arg, True) + else ( + ftoa(arg, True) + if arg.__class__ in native_numeric_types + else ( + self.smap.getSymbol(arg) + if arg.is_variable_type() and (not arg.fixed or self.output_fixed_variables) + else ftoa(value(arg), True) + ) + ) ) for arg in node.args ] diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index a256cd1b900..b82d4df77e2 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -2780,6 +2780,20 @@ def _before_linear(visitor, child): linear[_id] = arg1 elif arg.__class__ in native_types: const += arg + elif arg.is_variable_type(): + _id = id(arg) + if _id not in var_map: + if arg.fixed: + if _id not in visitor.fixed_vars: + visitor.cache_fixed_var(_id, arg) + const += visitor.fixed_vars[_id] + continue + _before_child_handlers._record_var(visitor, arg) + linear[_id] = 1 + elif _id in linear: + linear[_id] += 1 + else: + linear[_id] = 1 else: try: const += visitor.check_constant(visitor.evaluate(arg), arg) diff --git a/pyomo/repn/quadratic.py b/pyomo/repn/quadratic.py index c538d1efc7f..0ddfda829ed 100644 --- a/pyomo/repn/quadratic.py +++ b/pyomo/repn/quadratic.py @@ -98,22 +98,15 @@ def to_expression(self, visitor): e += coef * (var_map[x1] * var_map[x2]) ans += e if self.linear: - if len(self.linear) == 1: - vid, coef = next(iter(self.linear.items())) - if coef == 1: - ans += var_map[vid] - elif coef: - ans += MonomialTermExpression((coef, var_map[vid])) - else: - pass - else: - ans += LinearExpression( - [ - MonomialTermExpression((coef, var_map[vid])) - for vid, coef in self.linear.items() - if coef - ] - ) + var_map = visitor.var_map + with mutable_expression() as e: + for vid, coef in self.linear.items(): + if coef: + e += coef * var_map[vid] + if e.nargs() > 1: + ans += e + elif e.nargs() == 1: + ans += e.arg(0) if self.constant: ans += self.constant if self.multiplier != 1: diff --git a/pyomo/repn/standard_repn.py b/pyomo/repn/standard_repn.py index 8700872f04f..8600a8a50f6 100644 --- a/pyomo/repn/standard_repn.py +++ b/pyomo/repn/standard_repn.py @@ -321,6 +321,16 @@ def generate_standard_repn( linear_vars[id_] = v elif arg.__class__ in native_numeric_types: C_ += arg + elif arg.is_variable_type(): + if arg.fixed: + C_ += arg.value + continue + id_ = id(arg) + if id_ in linear_coefs: + linear_coefs[id_] += 1 + else: + linear_coefs[id_] = 1 + linear_vars[id_] = arg else: C_ += EXPR.evaluate_expression(arg) else: # compute_values == False @@ -336,6 +346,18 @@ def generate_standard_repn( else: linear_coefs[id_] = c linear_vars[id_] = v + elif arg.__class__ in native_numeric_types: + C_ += arg + elif arg.is_variable_type(): + if arg.fixed: + C_ += arg + continue + id_ = id(arg) + if id_ in linear_coefs: + linear_coefs[id_] += 1 + else: + linear_coefs[id_] = 1 + linear_vars[id_] = arg else: C_ += arg diff --git a/pyomo/repn/tests/test_linear.py b/pyomo/repn/tests/test_linear.py index 6843650d0c2..0fd428fd8ee 100644 --- a/pyomo/repn/tests/test_linear.py +++ b/pyomo/repn/tests/test_linear.py @@ -1589,7 +1589,7 @@ def test_to_expression(self): expr.constant = 0 expr.linear[id(m.x)] = 0 expr.linear[id(m.y)] = 0 - assertExpressionsEqual(self, expr.to_expression(visitor), LinearExpression()) + assertExpressionsEqual(self, expr.to_expression(visitor), 0) @unittest.skipUnless(numpy_available, "Test requires numpy") def test_nonnumeric(self): From 0785422bb3f97359a48128434ddbeb4e99f6c5b1 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Mar 2024 09:34:52 -0700 Subject: [PATCH 1370/1797] NFC: apply black --- pyomo/repn/plugins/gams_writer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyomo/repn/plugins/gams_writer.py b/pyomo/repn/plugins/gams_writer.py index 0756cb64920..a0f407d7952 100644 --- a/pyomo/repn/plugins/gams_writer.py +++ b/pyomo/repn/plugins/gams_writer.py @@ -188,7 +188,8 @@ def _linear_to_string(self, node): if arg.__class__ in native_numeric_types else ( self.smap.getSymbol(arg) - if arg.is_variable_type() and (not arg.fixed or self.output_fixed_variables) + if arg.is_variable_type() + and (not arg.fixed or self.output_fixed_variables) else ftoa(value(arg), True) ) ) From 1507ffd2a6c32ca4d5352488997dddab251d282d Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Mar 2024 12:58:56 -0700 Subject: [PATCH 1371/1797] Update tests to track change in LinearExpression arg types --- pyomo/core/tests/transform/test_add_slacks.py | 56 +-- pyomo/core/tests/unit/test_compare.py | 6 - pyomo/core/tests/unit/test_expression.py | 11 +- pyomo/core/tests/unit/test_numeric_expr.py | 329 ++++-------------- .../core/tests/unit/test_numeric_expr_api.py | 11 +- .../unit/test_numeric_expr_dispatcher.py | 278 ++++++--------- .../unit/test_numeric_expr_zerofilter.py | 274 ++++++--------- pyomo/core/tests/unit/test_visitor.py | 23 +- pyomo/gdp/tests/common_tests.py | 7 +- pyomo/gdp/tests/test_bigm.py | 5 +- pyomo/gdp/tests/test_binary_multiplication.py | 5 +- pyomo/gdp/tests/test_disjunct.py | 24 +- 12 files changed, 302 insertions(+), 727 deletions(-) diff --git a/pyomo/core/tests/transform/test_add_slacks.py b/pyomo/core/tests/transform/test_add_slacks.py index 7896cab7e88..a74a9b75c4f 100644 --- a/pyomo/core/tests/transform/test_add_slacks.py +++ b/pyomo/core/tests/transform/test_add_slacks.py @@ -102,10 +102,7 @@ def checkRule1(self, m): self, cons.body, EXPR.LinearExpression( - [ - EXPR.MonomialTermExpression((1, m.x)), - EXPR.MonomialTermExpression((-1, transBlock._slack_minus_rule1)), - ] + [m.x, EXPR.MonomialTermExpression((-1, transBlock._slack_minus_rule1))] ), ) @@ -118,14 +115,7 @@ def checkRule3(self, m): self.assertEqual(cons.lower, 0.1) assertExpressionsEqual( - self, - cons.body, - EXPR.LinearExpression( - [ - EXPR.MonomialTermExpression((1, m.x)), - EXPR.MonomialTermExpression((1, transBlock._slack_plus_rule3)), - ] - ), + self, cons.body, EXPR.LinearExpression([m.x, transBlock._slack_plus_rule3]) ) def test_ub_constraint_modified(self): @@ -154,8 +144,8 @@ def test_both_bounds_constraint_modified(self): cons.body, EXPR.LinearExpression( [ - EXPR.MonomialTermExpression((1, m.y)), - EXPR.MonomialTermExpression((1, transBlock._slack_plus_rule2)), + m.y, + transBlock._slack_plus_rule2, EXPR.MonomialTermExpression((-1, transBlock._slack_minus_rule2)), ] ), @@ -184,10 +174,10 @@ def test_new_obj_created(self): obj.expr, EXPR.LinearExpression( [ - EXPR.MonomialTermExpression((1, transBlock._slack_minus_rule1)), - EXPR.MonomialTermExpression((1, transBlock._slack_plus_rule2)), - EXPR.MonomialTermExpression((1, transBlock._slack_minus_rule2)), - EXPR.MonomialTermExpression((1, transBlock._slack_plus_rule3)), + transBlock._slack_minus_rule1, + transBlock._slack_plus_rule2, + transBlock._slack_minus_rule2, + transBlock._slack_plus_rule3, ] ), ) @@ -302,10 +292,7 @@ def checkTargetsObj(self, m): self, obj.expr, EXPR.LinearExpression( - [ - EXPR.MonomialTermExpression((1, transBlock._slack_minus_rule1)), - EXPR.MonomialTermExpression((1, transBlock._slack_plus_rule3)), - ] + [transBlock._slack_minus_rule1, transBlock._slack_plus_rule3] ), ) @@ -423,9 +410,9 @@ def test_transformed_constraints_sumexpression_body(self): c.body, EXPR.LinearExpression( [ - EXPR.MonomialTermExpression((1, m.x)), + m.x, EXPR.MonomialTermExpression((-2, m.y)), - EXPR.MonomialTermExpression((1, transBlock._slack_plus_rule4)), + transBlock._slack_plus_rule4, EXPR.MonomialTermExpression((-1, transBlock._slack_minus_rule4)), ] ), @@ -518,15 +505,9 @@ def checkTargetObj(self, m): obj.expr, EXPR.LinearExpression( [ - EXPR.MonomialTermExpression( - (1, transBlock.component("_slack_plus_rule1[1]")) - ), - EXPR.MonomialTermExpression( - (1, transBlock.component("_slack_plus_rule1[2]")) - ), - EXPR.MonomialTermExpression( - (1, transBlock.component("_slack_plus_rule1[3]")) - ), + transBlock.component("_slack_plus_rule1[1]"), + transBlock.component("_slack_plus_rule1[2]"), + transBlock.component("_slack_plus_rule1[3]"), ] ), ) @@ -558,14 +539,7 @@ def checkTransformedRule1(self, m, i): EXPR.LinearExpression( [ EXPR.MonomialTermExpression((2, m.x[i])), - EXPR.MonomialTermExpression( - ( - 1, - m._core_add_slack_variables.component( - "_slack_plus_rule1[%s]" % i - ), - ) - ), + m._core_add_slack_variables.component("_slack_plus_rule1[%s]" % i), ] ), ) diff --git a/pyomo/core/tests/unit/test_compare.py b/pyomo/core/tests/unit/test_compare.py index f80753bdb61..7c3536bc084 100644 --- a/pyomo/core/tests/unit/test_compare.py +++ b/pyomo/core/tests/unit/test_compare.py @@ -165,17 +165,11 @@ def test_expr_if(self): 0, (EqualityExpression, 2), (LinearExpression, 2), - (MonomialTermExpression, 2), - 1, m.y, - (MonomialTermExpression, 2), - 1, m.x, 0, (EqualityExpression, 2), (LinearExpression, 2), - (MonomialTermExpression, 2), - 1, m.y, (MonomialTermExpression, 2), -1, diff --git a/pyomo/core/tests/unit/test_expression.py b/pyomo/core/tests/unit/test_expression.py index c9afc6a1f76..678df4c01a8 100644 --- a/pyomo/core/tests/unit/test_expression.py +++ b/pyomo/core/tests/unit/test_expression.py @@ -738,10 +738,10 @@ def test_pprint_oldStyle(self): expr = model.e * model.x**2 + model.E[1] output = """\ -sum(prod(e{sum(mon(1, x), 2)}, pow(x, 2)), E[1]{sum(pow(x, 2), 1)}) +sum(prod(e{sum(x, 2)}, pow(x, 2)), E[1]{sum(pow(x, 2), 1)}) e : Size=1, Index=None Key : Expression - None : sum(mon(1, x), 2) + None : sum(x, 2) E : Size=2, Index={1, 2} Key : Expression 1 : sum(pow(x, 2), 1) @@ -951,12 +951,7 @@ def test_isub(self): assertExpressionsEqual( self, m.e.expr, - EXPR.LinearExpression( - [ - EXPR.MonomialTermExpression((1, m.x)), - EXPR.MonomialTermExpression((-1, m.y)), - ] - ), + EXPR.LinearExpression([m.x, EXPR.MonomialTermExpression((-1, m.y))]), ) self.assertTrue(compare_expressions(m.e.expr, m.x - m.y)) diff --git a/pyomo/core/tests/unit/test_numeric_expr.py b/pyomo/core/tests/unit/test_numeric_expr.py index c1066c292d7..968b3acb6a4 100644 --- a/pyomo/core/tests/unit/test_numeric_expr.py +++ b/pyomo/core/tests/unit/test_numeric_expr.py @@ -638,12 +638,7 @@ def test_simpleSum(self): m.b = Var() e = m.a + m.b # - self.assertExpressionsEqual( - e, - LinearExpression( - [MonomialTermExpression((1, m.a)), MonomialTermExpression((1, m.b))] - ), - ) + self.assertExpressionsEqual(e, LinearExpression([m.a, m.b])) self.assertRaises(KeyError, e.arg, 3) @@ -654,14 +649,7 @@ def test_simpleSum_API(self): e = m.a + m.b e += 2 * m.a self.assertExpressionsEqual( - e, - LinearExpression( - [ - MonomialTermExpression((1, m.a)), - MonomialTermExpression((1, m.b)), - MonomialTermExpression((2, m.a)), - ] - ), + e, LinearExpression([m.a, m.b, MonomialTermExpression((2, m.a))]) ) def test_constSum(self): @@ -669,13 +657,9 @@ def test_constSum(self): m = AbstractModel() m.a = Var() # - self.assertExpressionsEqual( - m.a + 5, LinearExpression([MonomialTermExpression((1, m.a)), 5]) - ) + self.assertExpressionsEqual(m.a + 5, LinearExpression([m.a, 5])) - self.assertExpressionsEqual( - 5 + m.a, LinearExpression([5, MonomialTermExpression((1, m.a))]) - ) + self.assertExpressionsEqual(5 + m.a, LinearExpression([5, m.a])) def test_nestedSum(self): # @@ -696,12 +680,7 @@ def test_nestedSum(self): # a b e1 = m.a + m.b e = e1 + 5 - self.assertExpressionsEqual( - e, - LinearExpression( - [MonomialTermExpression((1, m.a)), MonomialTermExpression((1, m.b)), 5] - ), - ) + self.assertExpressionsEqual(e, LinearExpression([m.a, m.b, 5])) # + # / \ @@ -710,12 +689,7 @@ def test_nestedSum(self): # a b e1 = m.a + m.b e = 5 + e1 - self.assertExpressionsEqual( - e, - LinearExpression( - [MonomialTermExpression((1, m.a)), MonomialTermExpression((1, m.b)), 5] - ), - ) + self.assertExpressionsEqual(e, LinearExpression([m.a, m.b, 5])) # + # / \ @@ -724,16 +698,7 @@ def test_nestedSum(self): # a b e1 = m.a + m.b e = e1 + m.c - self.assertExpressionsEqual( - e, - LinearExpression( - [ - MonomialTermExpression((1, m.a)), - MonomialTermExpression((1, m.b)), - MonomialTermExpression((1, m.c)), - ] - ), - ) + self.assertExpressionsEqual(e, LinearExpression([m.a, m.b, m.c])) # + # / \ @@ -742,16 +707,7 @@ def test_nestedSum(self): # a b e1 = m.a + m.b e = m.c + e1 - self.assertExpressionsEqual( - e, - LinearExpression( - [ - MonomialTermExpression((1, m.a)), - MonomialTermExpression((1, m.b)), - MonomialTermExpression((1, m.c)), - ] - ), - ) + self.assertExpressionsEqual(e, LinearExpression([m.a, m.b, m.c])) # + # / \ @@ -762,17 +718,7 @@ def test_nestedSum(self): e2 = m.c + m.d e = e1 + e2 # - self.assertExpressionsEqual( - e, - LinearExpression( - [ - MonomialTermExpression((1, m.a)), - MonomialTermExpression((1, m.b)), - MonomialTermExpression((1, m.c)), - MonomialTermExpression((1, m.d)), - ] - ), - ) + self.assertExpressionsEqual(e, LinearExpression([m.a, m.b, m.c, m.d])) def test_nestedSum2(self): # @@ -798,22 +744,7 @@ def test_nestedSum2(self): self.assertExpressionsEqual( e, - SumExpression( - [ - ProductExpression( - ( - 2, - LinearExpression( - [ - MonomialTermExpression((1, m.a)), - MonomialTermExpression((1, m.b)), - ] - ), - ) - ), - m.c, - ] - ), + SumExpression([ProductExpression((2, LinearExpression([m.a, m.b]))), m.c]), ) # * @@ -834,20 +765,7 @@ def test_nestedSum2(self): ( 3, SumExpression( - [ - ProductExpression( - ( - 2, - LinearExpression( - [ - MonomialTermExpression((1, m.a)), - MonomialTermExpression((1, m.b)), - ] - ), - ) - ), - m.c, - ] + [ProductExpression((2, LinearExpression([m.a, m.b]))), m.c] ), ) ), @@ -891,10 +809,7 @@ def test_sumOf_nestedTrivialProduct(self): e = e1 + m.b # self.assertExpressionsEqual( - e, - LinearExpression( - [MonomialTermExpression((5, m.a)), MonomialTermExpression((1, m.b))] - ), + e, LinearExpression([MonomialTermExpression((5, m.a)), m.b]) ) # + @@ -905,10 +820,7 @@ def test_sumOf_nestedTrivialProduct(self): e = m.b + e1 # self.assertExpressionsEqual( - e, - LinearExpression( - [MonomialTermExpression((1, m.b)), MonomialTermExpression((5, m.a))] - ), + e, LinearExpression([m.b, MonomialTermExpression((5, m.a))]) ) # + @@ -920,14 +832,7 @@ def test_sumOf_nestedTrivialProduct(self): e = e1 + e2 # self.assertExpressionsEqual( - e, - LinearExpression( - [ - MonomialTermExpression((1, m.b)), - MonomialTermExpression((1, m.c)), - MonomialTermExpression((5, m.a)), - ] - ), + e, LinearExpression([m.b, m.c, MonomialTermExpression((5, m.a))]) ) # + @@ -939,14 +844,7 @@ def test_sumOf_nestedTrivialProduct(self): e = e2 + e1 # self.assertExpressionsEqual( - e, - LinearExpression( - [ - MonomialTermExpression((1, m.b)), - MonomialTermExpression((1, m.c)), - MonomialTermExpression((5, m.a)), - ] - ), + e, LinearExpression([m.b, m.c, MonomialTermExpression((5, m.a))]) ) def test_simpleDiff(self): @@ -962,10 +860,7 @@ def test_simpleDiff(self): # a b e = m.a - m.b self.assertExpressionsEqual( - e, - LinearExpression( - [MonomialTermExpression((1, m.a)), MonomialTermExpression((-1, m.b))] - ), + e, LinearExpression([m.a, MonomialTermExpression((-1, m.b))]) ) def test_constDiff(self): @@ -978,9 +873,7 @@ def test_constDiff(self): # - # / \ # a 5 - self.assertExpressionsEqual( - m.a - 5, LinearExpression([MonomialTermExpression((1, m.a)), -5]) - ) + self.assertExpressionsEqual(m.a - 5, LinearExpression([m.a, -5])) # - # / \ @@ -1002,10 +895,7 @@ def test_paramDiff(self): # a p e = m.a - m.p self.assertExpressionsEqual( - e, - LinearExpression( - [MonomialTermExpression((1, m.a)), NPV_NegationExpression((m.p,))] - ), + e, LinearExpression([m.a, NPV_NegationExpression((m.p,))]) ) # - @@ -1079,14 +969,7 @@ def test_nestedDiff(self): e1 = m.a - m.b e = e1 - 5 self.assertExpressionsEqual( - e, - LinearExpression( - [ - MonomialTermExpression((1, m.a)), - MonomialTermExpression((-1, m.b)), - -5, - ] - ), + e, LinearExpression([m.a, MonomialTermExpression((-1, m.b)), -5]) ) # - @@ -1102,14 +985,7 @@ def test_nestedDiff(self): [ 5, NegationExpression( - ( - LinearExpression( - [ - MonomialTermExpression((1, m.a)), - MonomialTermExpression((-1, m.b)), - ] - ), - ) + (LinearExpression([m.a, MonomialTermExpression((-1, m.b))]),) ), ] ), @@ -1126,7 +1002,7 @@ def test_nestedDiff(self): e, LinearExpression( [ - MonomialTermExpression((1, m.a)), + m.a, MonomialTermExpression((-1, m.b)), MonomialTermExpression((-1, m.c)), ] @@ -1146,14 +1022,7 @@ def test_nestedDiff(self): [ m.c, NegationExpression( - ( - LinearExpression( - [ - MonomialTermExpression((1, m.a)), - MonomialTermExpression((-1, m.b)), - ] - ), - ) + (LinearExpression([m.a, MonomialTermExpression((-1, m.b))]),) ), ] ), @@ -1171,21 +1040,9 @@ def test_nestedDiff(self): e, SumExpression( [ - LinearExpression( - [ - MonomialTermExpression((1, m.a)), - MonomialTermExpression((-1, m.b)), - ] - ), + LinearExpression([m.a, MonomialTermExpression((-1, m.b))]), NegationExpression( - ( - LinearExpression( - [ - MonomialTermExpression((1, m.c)), - MonomialTermExpression((-1, m.d)), - ] - ), - ) + (LinearExpression([m.c, MonomialTermExpression((-1, m.d))]),) ), ] ), @@ -1382,10 +1239,7 @@ def test_sumOf_nestedTrivialProduct2(self): self.assertExpressionsEqual( e, LinearExpression( - [ - MonomialTermExpression((1, m.b)), - MonomialTermExpression((NPV_NegationExpression((m.p,)), m.a)), - ] + [m.b, MonomialTermExpression((NPV_NegationExpression((m.p,)), m.a))] ), ) @@ -1403,14 +1257,7 @@ def test_sumOf_nestedTrivialProduct2(self): [ MonomialTermExpression((m.p, m.a)), NegationExpression( - ( - LinearExpression( - [ - MonomialTermExpression((1, m.b)), - MonomialTermExpression((-1, m.c)), - ] - ), - ) + (LinearExpression([m.b, MonomialTermExpression((-1, m.c))]),) ), ] ), @@ -1428,7 +1275,7 @@ def test_sumOf_nestedTrivialProduct2(self): e, LinearExpression( [ - MonomialTermExpression((1, m.b)), + m.b, MonomialTermExpression((-1, m.c)), MonomialTermExpression((NPV_NegationExpression((m.p,)), m.a)), ] @@ -1598,22 +1445,7 @@ def test_nestedProduct2(self): self.assertExpressionsEqual( e, ProductExpression( - ( - LinearExpression( - [ - MonomialTermExpression((1, m.a)), - MonomialTermExpression((1, m.b)), - MonomialTermExpression((1, m.c)), - ] - ), - LinearExpression( - [ - MonomialTermExpression((1, m.a)), - MonomialTermExpression((1, m.b)), - MonomialTermExpression((1, m.d)), - ] - ), - ) + (LinearExpression([m.a, m.b, m.c]), LinearExpression([m.a, m.b, m.d])) ), ) # Verify shared args... @@ -1638,9 +1470,7 @@ def test_nestedProduct2(self): e3 = e1 * m.d e = e2 * e3 # - inner = LinearExpression( - [MonomialTermExpression((1, m.a)), MonomialTermExpression((1, m.b))] - ) + inner = LinearExpression([m.a, m.b]) self.assertExpressionsEqual( e, ProductExpression( @@ -2034,10 +1864,10 @@ def test_sum(self): model.p = Param(mutable=True) expr = 5 + model.a + model.a - self.assertEqual("sum(5, mon(1, a), mon(1, a))", str(expr)) + self.assertEqual("sum(5, a, a)", str(expr)) expr += 5 - self.assertEqual("sum(5, mon(1, a), mon(1, a), 5)", str(expr)) + self.assertEqual("sum(5, a, a, 5)", str(expr)) expr = 2 + model.p self.assertEqual("sum(2, p)", str(expr)) @@ -2053,24 +1883,18 @@ def test_linearsum(self): expr = quicksum(i * model.a[i] for i in A) self.assertEqual( - "sum(mon(0, a[0]), mon(1, a[1]), mon(2, a[2]), mon(3, a[3]), " - "mon(4, a[4]))", + "sum(mon(0, a[0]), a[1], mon(2, a[2]), mon(3, a[3]), " "mon(4, a[4]))", str(expr), ) expr = quicksum((i - 2) * model.a[i] for i in A) self.assertEqual( - "sum(mon(-2, a[0]), mon(-1, a[1]), mon(0, a[2]), mon(1, a[3]), " - "mon(2, a[4]))", + "sum(mon(-2, a[0]), mon(-1, a[1]), mon(0, a[2]), a[3], " "mon(2, a[4]))", str(expr), ) expr = quicksum(model.a[i] for i in A) - self.assertEqual( - "sum(mon(1, a[0]), mon(1, a[1]), mon(1, a[2]), mon(1, a[3]), " - "mon(1, a[4]))", - str(expr), - ) + self.assertEqual("sum(a[0], a[1], a[2], a[3], a[4])", str(expr)) model.p[1].value = 0 model.p[3].value = 3 @@ -2138,10 +1962,10 @@ def test_inequality(self): self.assertEqual("5 <= a < 10", str(expr)) expr = 5 <= model.a + 5 - self.assertEqual("5 <= sum(mon(1, a), 5)", str(expr)) + self.assertEqual("5 <= sum(a, 5)", str(expr)) expr = expr < 10 - self.assertEqual("5 <= sum(mon(1, a), 5) < 10", str(expr)) + self.assertEqual("5 <= sum(a, 5) < 10", str(expr)) def test_equality(self): # @@ -2166,10 +1990,10 @@ def test_equality(self): self.assertEqual("a == 10", str(expr)) expr = 5 == model.a + 5 - self.assertEqual("sum(mon(1, a), 5) == 5", str(expr)) + self.assertEqual("sum(a, 5) == 5", str(expr)) expr = model.a + 5 == 5 - self.assertEqual("sum(mon(1, a), 5) == 5", str(expr)) + self.assertEqual("sum(a, 5) == 5", str(expr)) def test_getitem(self): m = ConcreteModel() @@ -2206,7 +2030,7 @@ def test_small_expression(self): expr = abs(expr) self.assertEqual( "abs(neg(pow(2, div(2, prod(2, sum(1, neg(pow(div(prod(sum(" - "mon(1, a), 1, -1), a), a), b)), 1))))))", + "a, 1, -1), a), a), b)), 1))))))", str(expr), ) @@ -3754,13 +3578,7 @@ def test_summation1(self): self.assertExpressionsEqual( e, LinearExpression( - [ - MonomialTermExpression((1, self.m.a[1])), - MonomialTermExpression((1, self.m.a[2])), - MonomialTermExpression((1, self.m.a[3])), - MonomialTermExpression((1, self.m.a[4])), - MonomialTermExpression((1, self.m.a[5])), - ] + [self.m.a[1], self.m.a[2], self.m.a[3], self.m.a[4], self.m.a[5]] ), ) @@ -3872,16 +3690,16 @@ def test_summation_compression(self): e, LinearExpression( [ - MonomialTermExpression((1, self.m.a[1])), - MonomialTermExpression((1, self.m.a[2])), - MonomialTermExpression((1, self.m.a[3])), - MonomialTermExpression((1, self.m.a[4])), - MonomialTermExpression((1, self.m.a[5])), - MonomialTermExpression((1, self.m.b[1])), - MonomialTermExpression((1, self.m.b[2])), - MonomialTermExpression((1, self.m.b[3])), - MonomialTermExpression((1, self.m.b[4])), - MonomialTermExpression((1, self.m.b[5])), + self.m.a[1], + self.m.a[2], + self.m.a[3], + self.m.a[4], + self.m.a[5], + self.m.b[1], + self.m.b[2], + self.m.b[3], + self.m.b[4], + self.m.b[5], ] ), ) @@ -3912,13 +3730,7 @@ def test_deprecation(self): self.assertExpressionsEqual( e, LinearExpression( - [ - MonomialTermExpression((1, self.m.a[1])), - MonomialTermExpression((1, self.m.a[2])), - MonomialTermExpression((1, self.m.a[3])), - MonomialTermExpression((1, self.m.a[4])), - MonomialTermExpression((1, self.m.a[5])), - ] + [self.m.a[1], self.m.a[2], self.m.a[3], self.m.a[4], self.m.a[5]] ), ) @@ -3928,13 +3740,7 @@ def test_summation1(self): self.assertExpressionsEqual( e, LinearExpression( - [ - MonomialTermExpression((1, self.m.a[1])), - MonomialTermExpression((1, self.m.a[2])), - MonomialTermExpression((1, self.m.a[3])), - MonomialTermExpression((1, self.m.a[4])), - MonomialTermExpression((1, self.m.a[5])), - ] + [self.m.a[1], self.m.a[2], self.m.a[3], self.m.a[4], self.m.a[5]] ), ) @@ -4156,15 +3962,15 @@ def test_SumExpression(self): self.assertEqual(expr2(), 15) self.assertNotEqual(id(expr1), id(expr2)) self.assertNotEqual(id(expr1._args_), id(expr2._args_)) - self.assertIs(expr1.arg(0).arg(1), expr2.arg(0).arg(1)) - self.assertIs(expr1.arg(1).arg(1), expr2.arg(1).arg(1)) + self.assertIs(expr1.arg(0), expr2.arg(0)) + self.assertIs(expr1.arg(1), expr2.arg(1)) expr1 += self.m.b self.assertEqual(expr1(), 25) self.assertEqual(expr2(), 15) self.assertNotEqual(id(expr1), id(expr2)) self.assertNotEqual(id(expr1._args_), id(expr2._args_)) - self.assertIs(expr1.arg(0).arg(1), expr2.arg(0).arg(1)) - self.assertIs(expr1.arg(1).arg(1), expr2.arg(1).arg(1)) + self.assertIs(expr1.arg(0), expr2.arg(0)) + self.assertIs(expr1.arg(1), expr2.arg(1)) # total = counter.count - start self.assertEqual(total, 1) @@ -4341,9 +4147,9 @@ def test_productOfExpressions(self): self.assertEqual(expr1.arg(1).nargs(), 2) self.assertEqual(expr2.arg(1).nargs(), 2) - self.assertIs(expr1.arg(0).arg(0).arg(1), expr2.arg(0).arg(0).arg(1)) - self.assertIs(expr1.arg(0).arg(1).arg(1), expr2.arg(0).arg(1).arg(1)) - self.assertIs(expr1.arg(1).arg(0).arg(1), expr2.arg(1).arg(0).arg(1)) + self.assertIs(expr1.arg(0).arg(0), expr2.arg(0).arg(0)) + self.assertIs(expr1.arg(0).arg(1), expr2.arg(0).arg(1)) + self.assertIs(expr1.arg(1).arg(0), expr2.arg(1).arg(0)) expr1 *= self.m.b self.assertEqual(expr1(), 1500) @@ -4382,8 +4188,8 @@ def test_productOfExpressions_div(self): self.assertEqual(expr1.arg(1).nargs(), 2) self.assertEqual(expr2.arg(1).nargs(), 2) - self.assertIs(expr1.arg(0).arg(0).arg(1), expr2.arg(0).arg(0).arg(1)) - self.assertIs(expr1.arg(0).arg(1).arg(1), expr2.arg(0).arg(1).arg(1)) + self.assertIs(expr1.arg(0).arg(0), expr2.arg(0).arg(0)) + self.assertIs(expr1.arg(0).arg(1), expr2.arg(0).arg(1)) expr1 /= self.m.b self.assertAlmostEqual(expr1(), 0.15) @@ -5214,18 +5020,7 @@ def test_pow_other(self): e += m.v[0] + m.v[1] e = m.v[0] ** e self.assertExpressionsEqual( - e, - PowExpression( - ( - m.v[0], - LinearExpression( - [ - MonomialTermExpression((1, m.v[0])), - MonomialTermExpression((1, m.v[1])), - ] - ), - ) - ), + e, PowExpression((m.v[0], LinearExpression([m.v[0], m.v[1]]))) ) diff --git a/pyomo/core/tests/unit/test_numeric_expr_api.py b/pyomo/core/tests/unit/test_numeric_expr_api.py index 4e0af126315..923f78af1be 100644 --- a/pyomo/core/tests/unit/test_numeric_expr_api.py +++ b/pyomo/core/tests/unit/test_numeric_expr_api.py @@ -223,7 +223,7 @@ def test_negation(self): self.assertEqual(is_fixed(e), False) self.assertEqual(value(e), -15) self.assertEqual(str(e), "- (x + 2*x)") - self.assertEqual(e.to_string(verbose=True), "neg(sum(mon(1, x), mon(2, x)))") + self.assertEqual(e.to_string(verbose=True), "neg(sum(x, mon(2, x)))") # This can't occur through operator overloading, but could # through expression substitution @@ -634,8 +634,7 @@ def test_linear(self): self.assertEqual(value(e), 1 + 4 + 5 + 2) self.assertEqual(str(e), "0*x[0] + x[1] + 2*x[2] + 5 + y - 3") self.assertEqual( - e.to_string(verbose=True), - "sum(mon(0, x[0]), mon(1, x[1]), mon(2, x[2]), 5, mon(1, y), -3)", + e.to_string(verbose=True), "sum(mon(0, x[0]), x[1], mon(2, x[2]), 5, y, -3)" ) self.assertIs(type(e), LinearExpression) @@ -701,7 +700,7 @@ def test_expr_if(self): ) self.assertEqual( e.to_string(verbose=True), - "Expr_if( ( 5 <= y ), then=( sum(mon(1, x[0]), 5) ), else=( pow(x[1], 2) ) )", + "Expr_if( ( 5 <= y ), then=( sum(x[0], 5) ), else=( pow(x[1], 2) ) )", ) m.y.fix() @@ -972,9 +971,7 @@ def test_sum(self): f = e.create_node_with_local_data((m.p, m.x)) self.assertIsNot(f, e) self.assertIs(type(f), LinearExpression) - assertExpressionsStructurallyEqual( - self, f.args, [m.p, MonomialTermExpression((1, m.x))] - ) + assertExpressionsStructurallyEqual(self, f.args, [m.p, m.x]) f = e.create_node_with_local_data((m.p, m.x**2)) self.assertIsNot(f, e) diff --git a/pyomo/core/tests/unit/test_numeric_expr_dispatcher.py b/pyomo/core/tests/unit/test_numeric_expr_dispatcher.py index 3787f00de47..37833d7e8a4 100644 --- a/pyomo/core/tests/unit/test_numeric_expr_dispatcher.py +++ b/pyomo/core/tests/unit/test_numeric_expr_dispatcher.py @@ -123,8 +123,6 @@ def setUp(self): self.mutable_l3 = _MutableNPVSumExpression([self.npv]) # often repeated reference expressions - self.mon_bin = MonomialTermExpression((1, self.bin)) - self.mon_var = MonomialTermExpression((1, self.var)) self.minus_bin = MonomialTermExpression((-1, self.bin)) self.minus_npv = NPV_NegationExpression((self.npv,)) self.minus_param_mut = NPV_NegationExpression((self.param_mut,)) @@ -368,38 +366,34 @@ def test_add_asbinary(self): # BooleanVar objects do not support addition (self.asbinary, self.asbinary, NotImplemented), (self.asbinary, self.zero, self.bin), - (self.asbinary, self.one, LinearExpression([self.mon_bin, 1])), + (self.asbinary, self.one, LinearExpression([self.bin, 1])), # 4: - (self.asbinary, self.native, LinearExpression([self.mon_bin, 5])), - (self.asbinary, self.npv, LinearExpression([self.mon_bin, self.npv])), - (self.asbinary, self.param, LinearExpression([self.mon_bin, 6])), + (self.asbinary, self.native, LinearExpression([self.bin, 5])), + (self.asbinary, self.npv, LinearExpression([self.bin, self.npv])), + (self.asbinary, self.param, LinearExpression([self.bin, 6])), ( self.asbinary, self.param_mut, - LinearExpression([self.mon_bin, self.param_mut]), + LinearExpression([self.bin, self.param_mut]), ), # 8: - (self.asbinary, self.var, LinearExpression([self.mon_bin, self.mon_var])), + (self.asbinary, self.var, LinearExpression([self.bin, self.var])), ( self.asbinary, self.mon_native, - LinearExpression([self.mon_bin, self.mon_native]), + LinearExpression([self.bin, self.mon_native]), ), ( self.asbinary, self.mon_param, - LinearExpression([self.mon_bin, self.mon_param]), - ), - ( - self.asbinary, - self.mon_npv, - LinearExpression([self.mon_bin, self.mon_npv]), + LinearExpression([self.bin, self.mon_param]), ), + (self.asbinary, self.mon_npv, LinearExpression([self.bin, self.mon_npv])), # 12: ( self.asbinary, self.linear, - LinearExpression(self.linear.args + [self.mon_bin]), + LinearExpression(self.linear.args + [self.bin]), ), (self.asbinary, self.sum, SumExpression(self.sum.args + [self.bin])), (self.asbinary, self.other, SumExpression([self.bin, self.other])), @@ -408,7 +402,7 @@ def test_add_asbinary(self): ( self.asbinary, self.mutable_l1, - LinearExpression([self.mon_bin, self.mon_npv]), + LinearExpression([self.bin, self.mon_npv]), ), ( self.asbinary, @@ -416,13 +410,9 @@ def test_add_asbinary(self): SumExpression(self.mutable_l2.args + [self.bin]), ), (self.asbinary, self.param0, self.bin), - (self.asbinary, self.param1, LinearExpression([self.mon_bin, 1])), + (self.asbinary, self.param1, LinearExpression([self.bin, 1])), # 20: - ( - self.asbinary, - self.mutable_l3, - LinearExpression([self.mon_bin, self.npv]), - ), + (self.asbinary, self.mutable_l3, LinearExpression([self.bin, self.npv])), ] self._run_cases(tests, operator.add) self._run_cases(tests, operator.iadd) @@ -462,7 +452,7 @@ def test_add_zero(self): def test_add_one(self): tests = [ (self.one, self.invalid, NotImplemented), - (self.one, self.asbinary, LinearExpression([1, self.mon_bin])), + (self.one, self.asbinary, LinearExpression([1, self.bin])), (self.one, self.zero, 1), (self.one, self.one, 2), # 4: @@ -471,7 +461,7 @@ def test_add_one(self): (self.one, self.param, 7), (self.one, self.param_mut, NPV_SumExpression([1, self.param_mut])), # 8: - (self.one, self.var, LinearExpression([1, self.mon_var])), + (self.one, self.var, LinearExpression([1, self.var])), (self.one, self.mon_native, LinearExpression([1, self.mon_native])), (self.one, self.mon_param, LinearExpression([1, self.mon_param])), (self.one, self.mon_npv, LinearExpression([1, self.mon_npv])), @@ -494,7 +484,7 @@ def test_add_one(self): def test_add_native(self): tests = [ (self.native, self.invalid, NotImplemented), - (self.native, self.asbinary, LinearExpression([5, self.mon_bin])), + (self.native, self.asbinary, LinearExpression([5, self.bin])), (self.native, self.zero, 5), (self.native, self.one, 6), # 4: @@ -503,7 +493,7 @@ def test_add_native(self): (self.native, self.param, 11), (self.native, self.param_mut, NPV_SumExpression([5, self.param_mut])), # 8: - (self.native, self.var, LinearExpression([5, self.mon_var])), + (self.native, self.var, LinearExpression([5, self.var])), (self.native, self.mon_native, LinearExpression([5, self.mon_native])), (self.native, self.mon_param, LinearExpression([5, self.mon_param])), (self.native, self.mon_npv, LinearExpression([5, self.mon_npv])), @@ -530,7 +520,7 @@ def test_add_native(self): def test_add_npv(self): tests = [ (self.npv, self.invalid, NotImplemented), - (self.npv, self.asbinary, LinearExpression([self.npv, self.mon_bin])), + (self.npv, self.asbinary, LinearExpression([self.npv, self.bin])), (self.npv, self.zero, self.npv), (self.npv, self.one, NPV_SumExpression([self.npv, 1])), # 4: @@ -539,7 +529,7 @@ def test_add_npv(self): (self.npv, self.param, NPV_SumExpression([self.npv, 6])), (self.npv, self.param_mut, NPV_SumExpression([self.npv, self.param_mut])), # 8: - (self.npv, self.var, LinearExpression([self.npv, self.mon_var])), + (self.npv, self.var, LinearExpression([self.npv, self.var])), (self.npv, self.mon_native, LinearExpression([self.npv, self.mon_native])), (self.npv, self.mon_param, LinearExpression([self.npv, self.mon_param])), (self.npv, self.mon_npv, LinearExpression([self.npv, self.mon_npv])), @@ -570,7 +560,7 @@ def test_add_npv(self): def test_add_param(self): tests = [ (self.param, self.invalid, NotImplemented), - (self.param, self.asbinary, LinearExpression([6, self.mon_bin])), + (self.param, self.asbinary, LinearExpression([6, self.bin])), (self.param, self.zero, 6), (self.param, self.one, 7), # 4: @@ -579,7 +569,7 @@ def test_add_param(self): (self.param, self.param, 12), (self.param, self.param_mut, NPV_SumExpression([6, self.param_mut])), # 8: - (self.param, self.var, LinearExpression([6, self.mon_var])), + (self.param, self.var, LinearExpression([6, self.var])), (self.param, self.mon_native, LinearExpression([6, self.mon_native])), (self.param, self.mon_param, LinearExpression([6, self.mon_param])), (self.param, self.mon_npv, LinearExpression([6, self.mon_npv])), @@ -605,7 +595,7 @@ def test_add_param_mut(self): ( self.param_mut, self.asbinary, - LinearExpression([self.param_mut, self.mon_bin]), + LinearExpression([self.param_mut, self.bin]), ), (self.param_mut, self.zero, self.param_mut), (self.param_mut, self.one, NPV_SumExpression([self.param_mut, 1])), @@ -619,11 +609,7 @@ def test_add_param_mut(self): NPV_SumExpression([self.param_mut, self.param_mut]), ), # 8: - ( - self.param_mut, - self.var, - LinearExpression([self.param_mut, self.mon_var]), - ), + (self.param_mut, self.var, LinearExpression([self.param_mut, self.var])), ( self.param_mut, self.mon_native, @@ -674,37 +660,21 @@ def test_add_param_mut(self): def test_add_var(self): tests = [ (self.var, self.invalid, NotImplemented), - (self.var, self.asbinary, LinearExpression([self.mon_var, self.mon_bin])), + (self.var, self.asbinary, LinearExpression([self.var, self.bin])), (self.var, self.zero, self.var), - (self.var, self.one, LinearExpression([self.mon_var, 1])), + (self.var, self.one, LinearExpression([self.var, 1])), # 4: - (self.var, self.native, LinearExpression([self.mon_var, 5])), - (self.var, self.npv, LinearExpression([self.mon_var, self.npv])), - (self.var, self.param, LinearExpression([self.mon_var, 6])), - ( - self.var, - self.param_mut, - LinearExpression([self.mon_var, self.param_mut]), - ), + (self.var, self.native, LinearExpression([self.var, 5])), + (self.var, self.npv, LinearExpression([self.var, self.npv])), + (self.var, self.param, LinearExpression([self.var, 6])), + (self.var, self.param_mut, LinearExpression([self.var, self.param_mut])), # 8: - (self.var, self.var, LinearExpression([self.mon_var, self.mon_var])), - ( - self.var, - self.mon_native, - LinearExpression([self.mon_var, self.mon_native]), - ), - ( - self.var, - self.mon_param, - LinearExpression([self.mon_var, self.mon_param]), - ), - (self.var, self.mon_npv, LinearExpression([self.mon_var, self.mon_npv])), + (self.var, self.var, LinearExpression([self.var, self.var])), + (self.var, self.mon_native, LinearExpression([self.var, self.mon_native])), + (self.var, self.mon_param, LinearExpression([self.var, self.mon_param])), + (self.var, self.mon_npv, LinearExpression([self.var, self.mon_npv])), # 12: - ( - self.var, - self.linear, - LinearExpression(self.linear.args + [self.mon_var]), - ), + (self.var, self.linear, LinearExpression(self.linear.args + [self.var])), (self.var, self.sum, SumExpression(self.sum.args + [self.var])), (self.var, self.other, SumExpression([self.var, self.other])), (self.var, self.mutable_l0, self.var), @@ -712,7 +682,7 @@ def test_add_var(self): ( self.var, self.mutable_l1, - LinearExpression([self.mon_var] + self.mutable_l1.args), + LinearExpression([self.var] + self.mutable_l1.args), ), ( self.var, @@ -720,13 +690,9 @@ def test_add_var(self): SumExpression(self.mutable_l2.args + [self.var]), ), (self.var, self.param0, self.var), - (self.var, self.param1, LinearExpression([self.mon_var, 1])), + (self.var, self.param1, LinearExpression([self.var, 1])), # 20: - ( - self.var, - self.mutable_l3, - LinearExpression([MonomialTermExpression((1, self.var)), self.npv]), - ), + (self.var, self.mutable_l3, LinearExpression([self.var, self.npv])), ] self._run_cases(tests, operator.add) self._run_cases(tests, operator.iadd) @@ -737,7 +703,7 @@ def test_add_mon_native(self): ( self.mon_native, self.asbinary, - LinearExpression([self.mon_native, self.mon_bin]), + LinearExpression([self.mon_native, self.bin]), ), (self.mon_native, self.zero, self.mon_native), (self.mon_native, self.one, LinearExpression([self.mon_native, 1])), @@ -751,11 +717,7 @@ def test_add_mon_native(self): LinearExpression([self.mon_native, self.param_mut]), ), # 8: - ( - self.mon_native, - self.var, - LinearExpression([self.mon_native, self.mon_var]), - ), + (self.mon_native, self.var, LinearExpression([self.mon_native, self.var])), ( self.mon_native, self.mon_native, @@ -813,7 +775,7 @@ def test_add_mon_param(self): ( self.mon_param, self.asbinary, - LinearExpression([self.mon_param, self.mon_bin]), + LinearExpression([self.mon_param, self.bin]), ), (self.mon_param, self.zero, self.mon_param), (self.mon_param, self.one, LinearExpression([self.mon_param, 1])), @@ -827,11 +789,7 @@ def test_add_mon_param(self): LinearExpression([self.mon_param, self.param_mut]), ), # 8: - ( - self.mon_param, - self.var, - LinearExpression([self.mon_param, self.mon_var]), - ), + (self.mon_param, self.var, LinearExpression([self.mon_param, self.var])), ( self.mon_param, self.mon_native, @@ -882,11 +840,7 @@ def test_add_mon_param(self): def test_add_mon_npv(self): tests = [ (self.mon_npv, self.invalid, NotImplemented), - ( - self.mon_npv, - self.asbinary, - LinearExpression([self.mon_npv, self.mon_bin]), - ), + (self.mon_npv, self.asbinary, LinearExpression([self.mon_npv, self.bin])), (self.mon_npv, self.zero, self.mon_npv), (self.mon_npv, self.one, LinearExpression([self.mon_npv, 1])), # 4: @@ -899,7 +853,7 @@ def test_add_mon_npv(self): LinearExpression([self.mon_npv, self.param_mut]), ), # 8: - (self.mon_npv, self.var, LinearExpression([self.mon_npv, self.mon_var])), + (self.mon_npv, self.var, LinearExpression([self.mon_npv, self.var])), ( self.mon_npv, self.mon_native, @@ -949,7 +903,7 @@ def test_add_linear(self): ( self.linear, self.asbinary, - LinearExpression(self.linear.args + [self.mon_bin]), + LinearExpression(self.linear.args + [self.bin]), ), (self.linear, self.zero, self.linear), (self.linear, self.one, LinearExpression(self.linear.args + [1])), @@ -963,11 +917,7 @@ def test_add_linear(self): LinearExpression(self.linear.args + [self.param_mut]), ), # 8: - ( - self.linear, - self.var, - LinearExpression(self.linear.args + [self.mon_var]), - ), + (self.linear, self.var, LinearExpression(self.linear.args + [self.var])), ( self.linear, self.mon_native, @@ -1134,7 +1084,7 @@ def test_add_mutable_l1(self): ( self.mutable_l1, self.asbinary, - LinearExpression(self.mutable_l1.args + [self.mon_bin]), + LinearExpression(self.mutable_l1.args + [self.bin]), ), (self.mutable_l1, self.zero, self.mon_npv), (self.mutable_l1, self.one, LinearExpression(self.mutable_l1.args + [1])), @@ -1159,7 +1109,7 @@ def test_add_mutable_l1(self): ( self.mutable_l1, self.var, - LinearExpression(self.mutable_l1.args + [self.mon_var]), + LinearExpression(self.mutable_l1.args + [self.var]), ), ( self.mutable_l1, @@ -1341,7 +1291,7 @@ def test_add_param0(self): def test_add_param1(self): tests = [ (self.param1, self.invalid, NotImplemented), - (self.param1, self.asbinary, LinearExpression([1, self.mon_bin])), + (self.param1, self.asbinary, LinearExpression([1, self.bin])), (self.param1, self.zero, 1), (self.param1, self.one, 2), # 4: @@ -1350,7 +1300,7 @@ def test_add_param1(self): (self.param1, self.param, 7), (self.param1, self.param_mut, NPV_SumExpression([1, self.param_mut])), # 8: - (self.param1, self.var, LinearExpression([1, self.mon_var])), + (self.param1, self.var, LinearExpression([1, self.var])), (self.param1, self.mon_native, LinearExpression([1, self.mon_native])), (self.param1, self.mon_param, LinearExpression([1, self.mon_param])), (self.param1, self.mon_npv, LinearExpression([1, self.mon_npv])), @@ -1380,7 +1330,7 @@ def test_add_mutable_l3(self): ( self.mutable_l3, self.asbinary, - LinearExpression(self.mutable_l3.args + [self.mon_bin]), + LinearExpression(self.mutable_l3.args + [self.bin]), ), (self.mutable_l3, self.zero, self.npv), (self.mutable_l3, self.one, NPV_SumExpression(self.mutable_l3.args + [1])), @@ -1409,7 +1359,7 @@ def test_add_mutable_l3(self): ( self.mutable_l3, self.var, - LinearExpression(self.mutable_l3.args + [self.mon_var]), + LinearExpression(self.mutable_l3.args + [self.var]), ), ( self.mutable_l3, @@ -1515,32 +1465,32 @@ def test_sub_asbinary(self): # BooleanVar objects do not support addition (self.asbinary, self.asbinary, NotImplemented), (self.asbinary, self.zero, self.bin), - (self.asbinary, self.one, LinearExpression([self.mon_bin, -1])), + (self.asbinary, self.one, LinearExpression([self.bin, -1])), # 4: - (self.asbinary, self.native, LinearExpression([self.mon_bin, -5])), - (self.asbinary, self.npv, LinearExpression([self.mon_bin, self.minus_npv])), - (self.asbinary, self.param, LinearExpression([self.mon_bin, -6])), + (self.asbinary, self.native, LinearExpression([self.bin, -5])), + (self.asbinary, self.npv, LinearExpression([self.bin, self.minus_npv])), + (self.asbinary, self.param, LinearExpression([self.bin, -6])), ( self.asbinary, self.param_mut, - LinearExpression([self.mon_bin, self.minus_param_mut]), + LinearExpression([self.bin, self.minus_param_mut]), ), # 8: - (self.asbinary, self.var, LinearExpression([self.mon_bin, self.minus_var])), + (self.asbinary, self.var, LinearExpression([self.bin, self.minus_var])), ( self.asbinary, self.mon_native, - LinearExpression([self.mon_bin, self.minus_mon_native]), + LinearExpression([self.bin, self.minus_mon_native]), ), ( self.asbinary, self.mon_param, - LinearExpression([self.mon_bin, self.minus_mon_param]), + LinearExpression([self.bin, self.minus_mon_param]), ), ( self.asbinary, self.mon_npv, - LinearExpression([self.mon_bin, self.minus_mon_npv]), + LinearExpression([self.bin, self.minus_mon_npv]), ), # 12: (self.asbinary, self.linear, SumExpression([self.bin, self.minus_linear])), @@ -1551,7 +1501,7 @@ def test_sub_asbinary(self): ( self.asbinary, self.mutable_l1, - LinearExpression([self.mon_bin, self.minus_mon_npv]), + LinearExpression([self.bin, self.minus_mon_npv]), ), ( self.asbinary, @@ -1559,12 +1509,12 @@ def test_sub_asbinary(self): SumExpression([self.bin, self.minus_mutable_l2]), ), (self.asbinary, self.param0, self.bin), - (self.asbinary, self.param1, LinearExpression([self.mon_bin, -1])), + (self.asbinary, self.param1, LinearExpression([self.bin, -1])), # 20: ( self.asbinary, self.mutable_l3, - LinearExpression([self.mon_bin, self.minus_npv]), + LinearExpression([self.bin, self.minus_npv]), ), ] self._run_cases(tests, operator.sub) @@ -1837,35 +1787,31 @@ def test_sub_param_mut(self): def test_sub_var(self): tests = [ (self.var, self.invalid, NotImplemented), - (self.var, self.asbinary, LinearExpression([self.mon_var, self.minus_bin])), + (self.var, self.asbinary, LinearExpression([self.var, self.minus_bin])), (self.var, self.zero, self.var), - (self.var, self.one, LinearExpression([self.mon_var, -1])), + (self.var, self.one, LinearExpression([self.var, -1])), # 4: - (self.var, self.native, LinearExpression([self.mon_var, -5])), - (self.var, self.npv, LinearExpression([self.mon_var, self.minus_npv])), - (self.var, self.param, LinearExpression([self.mon_var, -6])), + (self.var, self.native, LinearExpression([self.var, -5])), + (self.var, self.npv, LinearExpression([self.var, self.minus_npv])), + (self.var, self.param, LinearExpression([self.var, -6])), ( self.var, self.param_mut, - LinearExpression([self.mon_var, self.minus_param_mut]), + LinearExpression([self.var, self.minus_param_mut]), ), # 8: - (self.var, self.var, LinearExpression([self.mon_var, self.minus_var])), + (self.var, self.var, LinearExpression([self.var, self.minus_var])), ( self.var, self.mon_native, - LinearExpression([self.mon_var, self.minus_mon_native]), + LinearExpression([self.var, self.minus_mon_native]), ), ( self.var, self.mon_param, - LinearExpression([self.mon_var, self.minus_mon_param]), - ), - ( - self.var, - self.mon_npv, - LinearExpression([self.mon_var, self.minus_mon_npv]), + LinearExpression([self.var, self.minus_mon_param]), ), + (self.var, self.mon_npv, LinearExpression([self.var, self.minus_mon_npv])), # 12: ( self.var, @@ -1879,7 +1825,7 @@ def test_sub_var(self): ( self.var, self.mutable_l1, - LinearExpression([self.mon_var, self.minus_mon_npv]), + LinearExpression([self.var, self.minus_mon_npv]), ), ( self.var, @@ -1887,13 +1833,9 @@ def test_sub_var(self): SumExpression([self.var, self.minus_mutable_l2]), ), (self.var, self.param0, self.var), - (self.var, self.param1, LinearExpression([self.mon_var, -1])), + (self.var, self.param1, LinearExpression([self.var, -1])), # 20: - ( - self.var, - self.mutable_l3, - LinearExpression([self.mon_var, self.minus_npv]), - ), + (self.var, self.mutable_l3, LinearExpression([self.var, self.minus_npv])), ] self._run_cases(tests, operator.sub) self._run_cases(tests, operator.isub) @@ -6511,7 +6453,7 @@ def test_mutable_nvp_iadd(self): mutable_npv = _MutableNPVSumExpression([]) tests = [ (mutable_npv, self.invalid, NotImplemented), - (mutable_npv, self.asbinary, _MutableLinearExpression([self.mon_bin])), + (mutable_npv, self.asbinary, _MutableLinearExpression([self.bin])), (mutable_npv, self.zero, _MutableNPVSumExpression([])), (mutable_npv, self.one, _MutableNPVSumExpression([1])), # 4: @@ -6520,7 +6462,7 @@ def test_mutable_nvp_iadd(self): (mutable_npv, self.param, _MutableNPVSumExpression([6])), (mutable_npv, self.param_mut, _MutableNPVSumExpression([self.param_mut])), # 8: - (mutable_npv, self.var, _MutableLinearExpression([self.mon_var])), + (mutable_npv, self.var, _MutableLinearExpression([self.var])), (mutable_npv, self.mon_native, _MutableLinearExpression([self.mon_native])), (mutable_npv, self.mon_param, _MutableLinearExpression([self.mon_param])), (mutable_npv, self.mon_npv, _MutableLinearExpression([self.mon_npv])), @@ -6546,7 +6488,7 @@ def test_mutable_nvp_iadd(self): mutable_npv = _MutableNPVSumExpression([10]) tests = [ (mutable_npv, self.invalid, NotImplemented), - (mutable_npv, self.asbinary, _MutableLinearExpression([10, self.mon_bin])), + (mutable_npv, self.asbinary, _MutableLinearExpression([10, self.bin])), (mutable_npv, self.zero, _MutableNPVSumExpression([10])), (mutable_npv, self.one, _MutableNPVSumExpression([10, 1])), # 4: @@ -6559,7 +6501,7 @@ def test_mutable_nvp_iadd(self): _MutableNPVSumExpression([10, self.param_mut]), ), # 8: - (mutable_npv, self.var, _MutableLinearExpression([10, self.mon_var])), + (mutable_npv, self.var, _MutableLinearExpression([10, self.var])), ( mutable_npv, self.mon_native, @@ -6602,7 +6544,7 @@ def test_mutable_lin_iadd(self): mutable_lin = _MutableLinearExpression([]) tests = [ (mutable_lin, self.invalid, NotImplemented), - (mutable_lin, self.asbinary, _MutableLinearExpression([self.mon_bin])), + (mutable_lin, self.asbinary, _MutableLinearExpression([self.bin])), (mutable_lin, self.zero, _MutableLinearExpression([])), (mutable_lin, self.one, _MutableLinearExpression([1])), # 4: @@ -6611,7 +6553,7 @@ def test_mutable_lin_iadd(self): (mutable_lin, self.param, _MutableLinearExpression([6])), (mutable_lin, self.param_mut, _MutableLinearExpression([self.param_mut])), # 8: - (mutable_lin, self.var, _MutableLinearExpression([self.mon_var])), + (mutable_lin, self.var, _MutableLinearExpression([self.var])), (mutable_lin, self.mon_native, _MutableLinearExpression([self.mon_native])), (mutable_lin, self.mon_param, _MutableLinearExpression([self.mon_param])), (mutable_lin, self.mon_npv, _MutableLinearExpression([self.mon_npv])), @@ -6634,81 +6576,69 @@ def test_mutable_lin_iadd(self): ] self._run_iadd_cases(tests, operator.iadd) - mutable_lin = _MutableLinearExpression([self.mon_bin]) + mutable_lin = _MutableLinearExpression([self.bin]) tests = [ (mutable_lin, self.invalid, NotImplemented), ( mutable_lin, self.asbinary, - _MutableLinearExpression([self.mon_bin, self.mon_bin]), + _MutableLinearExpression([self.bin, self.bin]), ), - (mutable_lin, self.zero, _MutableLinearExpression([self.mon_bin])), - (mutable_lin, self.one, _MutableLinearExpression([self.mon_bin, 1])), + (mutable_lin, self.zero, _MutableLinearExpression([self.bin])), + (mutable_lin, self.one, _MutableLinearExpression([self.bin, 1])), # 4: - (mutable_lin, self.native, _MutableLinearExpression([self.mon_bin, 5])), - (mutable_lin, self.npv, _MutableLinearExpression([self.mon_bin, self.npv])), - (mutable_lin, self.param, _MutableLinearExpression([self.mon_bin, 6])), + (mutable_lin, self.native, _MutableLinearExpression([self.bin, 5])), + (mutable_lin, self.npv, _MutableLinearExpression([self.bin, self.npv])), + (mutable_lin, self.param, _MutableLinearExpression([self.bin, 6])), ( mutable_lin, self.param_mut, - _MutableLinearExpression([self.mon_bin, self.param_mut]), + _MutableLinearExpression([self.bin, self.param_mut]), ), # 8: - ( - mutable_lin, - self.var, - _MutableLinearExpression([self.mon_bin, self.mon_var]), - ), + (mutable_lin, self.var, _MutableLinearExpression([self.bin, self.var])), ( mutable_lin, self.mon_native, - _MutableLinearExpression([self.mon_bin, self.mon_native]), + _MutableLinearExpression([self.bin, self.mon_native]), ), ( mutable_lin, self.mon_param, - _MutableLinearExpression([self.mon_bin, self.mon_param]), + _MutableLinearExpression([self.bin, self.mon_param]), ), ( mutable_lin, self.mon_npv, - _MutableLinearExpression([self.mon_bin, self.mon_npv]), + _MutableLinearExpression([self.bin, self.mon_npv]), ), # 12: ( mutable_lin, self.linear, - _MutableLinearExpression([self.mon_bin] + self.linear.args), - ), - ( - mutable_lin, - self.sum, - _MutableSumExpression([self.mon_bin] + self.sum.args), - ), - ( - mutable_lin, - self.other, - _MutableSumExpression([self.mon_bin, self.other]), + _MutableLinearExpression([self.bin] + self.linear.args), ), - (mutable_lin, self.mutable_l0, _MutableLinearExpression([self.mon_bin])), + (mutable_lin, self.sum, _MutableSumExpression([self.bin] + self.sum.args)), + (mutable_lin, self.other, _MutableSumExpression([self.bin, self.other])), + (mutable_lin, self.mutable_l0, _MutableLinearExpression([self.bin])), # 16: ( mutable_lin, self.mutable_l1, - _MutableLinearExpression([self.mon_bin] + self.mutable_l1.args), + _MutableLinearExpression([self.bin] + self.mutable_l1.args), ), ( mutable_lin, self.mutable_l2, - _MutableSumExpression([self.mon_bin] + self.mutable_l2.args), + _MutableSumExpression([self.bin] + self.mutable_l2.args), ), - (mutable_lin, self.param0, _MutableLinearExpression([self.mon_bin])), - (mutable_lin, self.param1, _MutableLinearExpression([self.mon_bin, 1])), + (mutable_lin, self.param0, _MutableLinearExpression([self.bin])), + (mutable_lin, self.param1, _MutableLinearExpression([self.bin, 1])), # 20: ( mutable_lin, self.mutable_l3, - _MutableLinearExpression([self.mon_bin, self.npv]), + _MutableLinearExpression([self.bin, self.npv]), ), ] self._run_iadd_cases(tests, operator.iadd) @@ -6854,7 +6784,7 @@ def as_numeric(self): assertExpressionsEqual(self, PowExpression((self.var, 2)), e) e = obj + obj - assertExpressionsEqual(self, LinearExpression((self.mon_var, self.mon_var)), e) + assertExpressionsEqual(self, LinearExpression((self.var, self.var)), e) def test_categorize_arg_type(self): class CustomAsNumeric(NumericValue): diff --git a/pyomo/core/tests/unit/test_numeric_expr_zerofilter.py b/pyomo/core/tests/unit/test_numeric_expr_zerofilter.py index 162d664e0f8..8e75ccc3feb 100644 --- a/pyomo/core/tests/unit/test_numeric_expr_zerofilter.py +++ b/pyomo/core/tests/unit/test_numeric_expr_zerofilter.py @@ -102,38 +102,34 @@ def test_add_asbinary(self): # BooleanVar objects do not support addition (self.asbinary, self.asbinary, NotImplemented), (self.asbinary, self.zero, self.bin), - (self.asbinary, self.one, LinearExpression([self.mon_bin, 1])), + (self.asbinary, self.one, LinearExpression([self.bin, 1])), # 4: - (self.asbinary, self.native, LinearExpression([self.mon_bin, 5])), - (self.asbinary, self.npv, LinearExpression([self.mon_bin, self.npv])), - (self.asbinary, self.param, LinearExpression([self.mon_bin, 6])), + (self.asbinary, self.native, LinearExpression([self.bin, 5])), + (self.asbinary, self.npv, LinearExpression([self.bin, self.npv])), + (self.asbinary, self.param, LinearExpression([self.bin, 6])), ( self.asbinary, self.param_mut, - LinearExpression([self.mon_bin, self.param_mut]), + LinearExpression([self.bin, self.param_mut]), ), # 8: - (self.asbinary, self.var, LinearExpression([self.mon_bin, self.mon_var])), + (self.asbinary, self.var, LinearExpression([self.bin, self.var])), ( self.asbinary, self.mon_native, - LinearExpression([self.mon_bin, self.mon_native]), + LinearExpression([self.bin, self.mon_native]), ), ( self.asbinary, self.mon_param, - LinearExpression([self.mon_bin, self.mon_param]), - ), - ( - self.asbinary, - self.mon_npv, - LinearExpression([self.mon_bin, self.mon_npv]), + LinearExpression([self.bin, self.mon_param]), ), + (self.asbinary, self.mon_npv, LinearExpression([self.bin, self.mon_npv])), # 12: ( self.asbinary, self.linear, - LinearExpression(self.linear.args + [self.mon_bin]), + LinearExpression(self.linear.args + [self.bin]), ), (self.asbinary, self.sum, SumExpression(self.sum.args + [self.bin])), (self.asbinary, self.other, SumExpression([self.bin, self.other])), @@ -142,7 +138,7 @@ def test_add_asbinary(self): ( self.asbinary, self.mutable_l1, - LinearExpression([self.mon_bin, self.mon_npv]), + LinearExpression([self.bin, self.mon_npv]), ), ( self.asbinary, @@ -150,13 +146,9 @@ def test_add_asbinary(self): SumExpression(self.mutable_l2.args + [self.bin]), ), (self.asbinary, self.param0, self.bin), - (self.asbinary, self.param1, LinearExpression([self.mon_bin, 1])), + (self.asbinary, self.param1, LinearExpression([self.bin, 1])), # 20: - ( - self.asbinary, - self.mutable_l3, - LinearExpression([self.mon_bin, self.npv]), - ), + (self.asbinary, self.mutable_l3, LinearExpression([self.bin, self.npv])), ] self._run_cases(tests, operator.add) self._run_cases(tests, operator.iadd) @@ -196,7 +188,7 @@ def test_add_zero(self): def test_add_one(self): tests = [ (self.one, self.invalid, NotImplemented), - (self.one, self.asbinary, LinearExpression([1, self.mon_bin])), + (self.one, self.asbinary, LinearExpression([1, self.bin])), (self.one, self.zero, 1), (self.one, self.one, 2), # 4: @@ -205,7 +197,7 @@ def test_add_one(self): (self.one, self.param, 7), (self.one, self.param_mut, NPV_SumExpression([1, self.param_mut])), # 8: - (self.one, self.var, LinearExpression([1, self.mon_var])), + (self.one, self.var, LinearExpression([1, self.var])), (self.one, self.mon_native, LinearExpression([1, self.mon_native])), (self.one, self.mon_param, LinearExpression([1, self.mon_param])), (self.one, self.mon_npv, LinearExpression([1, self.mon_npv])), @@ -228,7 +220,7 @@ def test_add_one(self): def test_add_native(self): tests = [ (self.native, self.invalid, NotImplemented), - (self.native, self.asbinary, LinearExpression([5, self.mon_bin])), + (self.native, self.asbinary, LinearExpression([5, self.bin])), (self.native, self.zero, 5), (self.native, self.one, 6), # 4: @@ -237,7 +229,7 @@ def test_add_native(self): (self.native, self.param, 11), (self.native, self.param_mut, NPV_SumExpression([5, self.param_mut])), # 8: - (self.native, self.var, LinearExpression([5, self.mon_var])), + (self.native, self.var, LinearExpression([5, self.var])), (self.native, self.mon_native, LinearExpression([5, self.mon_native])), (self.native, self.mon_param, LinearExpression([5, self.mon_param])), (self.native, self.mon_npv, LinearExpression([5, self.mon_npv])), @@ -264,7 +256,7 @@ def test_add_native(self): def test_add_npv(self): tests = [ (self.npv, self.invalid, NotImplemented), - (self.npv, self.asbinary, LinearExpression([self.npv, self.mon_bin])), + (self.npv, self.asbinary, LinearExpression([self.npv, self.bin])), (self.npv, self.zero, self.npv), (self.npv, self.one, NPV_SumExpression([self.npv, 1])), # 4: @@ -273,7 +265,7 @@ def test_add_npv(self): (self.npv, self.param, NPV_SumExpression([self.npv, 6])), (self.npv, self.param_mut, NPV_SumExpression([self.npv, self.param_mut])), # 8: - (self.npv, self.var, LinearExpression([self.npv, self.mon_var])), + (self.npv, self.var, LinearExpression([self.npv, self.var])), (self.npv, self.mon_native, LinearExpression([self.npv, self.mon_native])), (self.npv, self.mon_param, LinearExpression([self.npv, self.mon_param])), (self.npv, self.mon_npv, LinearExpression([self.npv, self.mon_npv])), @@ -304,7 +296,7 @@ def test_add_npv(self): def test_add_param(self): tests = [ (self.param, self.invalid, NotImplemented), - (self.param, self.asbinary, LinearExpression([6, self.mon_bin])), + (self.param, self.asbinary, LinearExpression([6, self.bin])), (self.param, self.zero, 6), (self.param, self.one, 7), # 4: @@ -313,7 +305,7 @@ def test_add_param(self): (self.param, self.param, 12), (self.param, self.param_mut, NPV_SumExpression([6, self.param_mut])), # 8: - (self.param, self.var, LinearExpression([6, self.mon_var])), + (self.param, self.var, LinearExpression([6, self.var])), (self.param, self.mon_native, LinearExpression([6, self.mon_native])), (self.param, self.mon_param, LinearExpression([6, self.mon_param])), (self.param, self.mon_npv, LinearExpression([6, self.mon_npv])), @@ -339,7 +331,7 @@ def test_add_param_mut(self): ( self.param_mut, self.asbinary, - LinearExpression([self.param_mut, self.mon_bin]), + LinearExpression([self.param_mut, self.bin]), ), (self.param_mut, self.zero, self.param_mut), (self.param_mut, self.one, NPV_SumExpression([self.param_mut, 1])), @@ -353,11 +345,7 @@ def test_add_param_mut(self): NPV_SumExpression([self.param_mut, self.param_mut]), ), # 8: - ( - self.param_mut, - self.var, - LinearExpression([self.param_mut, self.mon_var]), - ), + (self.param_mut, self.var, LinearExpression([self.param_mut, self.var])), ( self.param_mut, self.mon_native, @@ -408,37 +396,21 @@ def test_add_param_mut(self): def test_add_var(self): tests = [ (self.var, self.invalid, NotImplemented), - (self.var, self.asbinary, LinearExpression([self.mon_var, self.mon_bin])), + (self.var, self.asbinary, LinearExpression([self.var, self.bin])), (self.var, self.zero, self.var), - (self.var, self.one, LinearExpression([self.mon_var, 1])), + (self.var, self.one, LinearExpression([self.var, 1])), # 4: - (self.var, self.native, LinearExpression([self.mon_var, 5])), - (self.var, self.npv, LinearExpression([self.mon_var, self.npv])), - (self.var, self.param, LinearExpression([self.mon_var, 6])), - ( - self.var, - self.param_mut, - LinearExpression([self.mon_var, self.param_mut]), - ), + (self.var, self.native, LinearExpression([self.var, 5])), + (self.var, self.npv, LinearExpression([self.var, self.npv])), + (self.var, self.param, LinearExpression([self.var, 6])), + (self.var, self.param_mut, LinearExpression([self.var, self.param_mut])), # 8: - (self.var, self.var, LinearExpression([self.mon_var, self.mon_var])), - ( - self.var, - self.mon_native, - LinearExpression([self.mon_var, self.mon_native]), - ), - ( - self.var, - self.mon_param, - LinearExpression([self.mon_var, self.mon_param]), - ), - (self.var, self.mon_npv, LinearExpression([self.mon_var, self.mon_npv])), + (self.var, self.var, LinearExpression([self.var, self.var])), + (self.var, self.mon_native, LinearExpression([self.var, self.mon_native])), + (self.var, self.mon_param, LinearExpression([self.var, self.mon_param])), + (self.var, self.mon_npv, LinearExpression([self.var, self.mon_npv])), # 12: - ( - self.var, - self.linear, - LinearExpression(self.linear.args + [self.mon_var]), - ), + (self.var, self.linear, LinearExpression(self.linear.args + [self.var])), (self.var, self.sum, SumExpression(self.sum.args + [self.var])), (self.var, self.other, SumExpression([self.var, self.other])), (self.var, self.mutable_l0, self.var), @@ -446,7 +418,7 @@ def test_add_var(self): ( self.var, self.mutable_l1, - LinearExpression([self.mon_var] + self.mutable_l1.args), + LinearExpression([self.var] + self.mutable_l1.args), ), ( self.var, @@ -454,13 +426,9 @@ def test_add_var(self): SumExpression(self.mutable_l2.args + [self.var]), ), (self.var, self.param0, self.var), - (self.var, self.param1, LinearExpression([self.mon_var, 1])), + (self.var, self.param1, LinearExpression([self.var, 1])), # 20: - ( - self.var, - self.mutable_l3, - LinearExpression([MonomialTermExpression((1, self.var)), self.npv]), - ), + (self.var, self.mutable_l3, LinearExpression([self.var, self.npv])), ] self._run_cases(tests, operator.add) self._run_cases(tests, operator.iadd) @@ -471,7 +439,7 @@ def test_add_mon_native(self): ( self.mon_native, self.asbinary, - LinearExpression([self.mon_native, self.mon_bin]), + LinearExpression([self.mon_native, self.bin]), ), (self.mon_native, self.zero, self.mon_native), (self.mon_native, self.one, LinearExpression([self.mon_native, 1])), @@ -485,11 +453,7 @@ def test_add_mon_native(self): LinearExpression([self.mon_native, self.param_mut]), ), # 8: - ( - self.mon_native, - self.var, - LinearExpression([self.mon_native, self.mon_var]), - ), + (self.mon_native, self.var, LinearExpression([self.mon_native, self.var])), ( self.mon_native, self.mon_native, @@ -547,7 +511,7 @@ def test_add_mon_param(self): ( self.mon_param, self.asbinary, - LinearExpression([self.mon_param, self.mon_bin]), + LinearExpression([self.mon_param, self.bin]), ), (self.mon_param, self.zero, self.mon_param), (self.mon_param, self.one, LinearExpression([self.mon_param, 1])), @@ -561,11 +525,7 @@ def test_add_mon_param(self): LinearExpression([self.mon_param, self.param_mut]), ), # 8: - ( - self.mon_param, - self.var, - LinearExpression([self.mon_param, self.mon_var]), - ), + (self.mon_param, self.var, LinearExpression([self.mon_param, self.var])), ( self.mon_param, self.mon_native, @@ -616,11 +576,7 @@ def test_add_mon_param(self): def test_add_mon_npv(self): tests = [ (self.mon_npv, self.invalid, NotImplemented), - ( - self.mon_npv, - self.asbinary, - LinearExpression([self.mon_npv, self.mon_bin]), - ), + (self.mon_npv, self.asbinary, LinearExpression([self.mon_npv, self.bin])), (self.mon_npv, self.zero, self.mon_npv), (self.mon_npv, self.one, LinearExpression([self.mon_npv, 1])), # 4: @@ -633,7 +589,7 @@ def test_add_mon_npv(self): LinearExpression([self.mon_npv, self.param_mut]), ), # 8: - (self.mon_npv, self.var, LinearExpression([self.mon_npv, self.mon_var])), + (self.mon_npv, self.var, LinearExpression([self.mon_npv, self.var])), ( self.mon_npv, self.mon_native, @@ -683,7 +639,7 @@ def test_add_linear(self): ( self.linear, self.asbinary, - LinearExpression(self.linear.args + [self.mon_bin]), + LinearExpression(self.linear.args + [self.bin]), ), (self.linear, self.zero, self.linear), (self.linear, self.one, LinearExpression(self.linear.args + [1])), @@ -697,11 +653,7 @@ def test_add_linear(self): LinearExpression(self.linear.args + [self.param_mut]), ), # 8: - ( - self.linear, - self.var, - LinearExpression(self.linear.args + [self.mon_var]), - ), + (self.linear, self.var, LinearExpression(self.linear.args + [self.var])), ( self.linear, self.mon_native, @@ -868,7 +820,7 @@ def test_add_mutable_l1(self): ( self.mutable_l1, self.asbinary, - LinearExpression(self.mutable_l1.args + [self.mon_bin]), + LinearExpression(self.mutable_l1.args + [self.bin]), ), (self.mutable_l1, self.zero, self.mon_npv), (self.mutable_l1, self.one, LinearExpression(self.mutable_l1.args + [1])), @@ -893,7 +845,7 @@ def test_add_mutable_l1(self): ( self.mutable_l1, self.var, - LinearExpression(self.mutable_l1.args + [self.mon_var]), + LinearExpression(self.mutable_l1.args + [self.var]), ), ( self.mutable_l1, @@ -1075,7 +1027,7 @@ def test_add_param0(self): def test_add_param1(self): tests = [ (self.param1, self.invalid, NotImplemented), - (self.param1, self.asbinary, LinearExpression([1, self.mon_bin])), + (self.param1, self.asbinary, LinearExpression([1, self.bin])), (self.param1, self.zero, 1), (self.param1, self.one, 2), # 4: @@ -1084,7 +1036,7 @@ def test_add_param1(self): (self.param1, self.param, 7), (self.param1, self.param_mut, NPV_SumExpression([1, self.param_mut])), # 8: - (self.param1, self.var, LinearExpression([1, self.mon_var])), + (self.param1, self.var, LinearExpression([1, self.var])), (self.param1, self.mon_native, LinearExpression([1, self.mon_native])), (self.param1, self.mon_param, LinearExpression([1, self.mon_param])), (self.param1, self.mon_npv, LinearExpression([1, self.mon_npv])), @@ -1114,7 +1066,7 @@ def test_add_mutable_l3(self): ( self.mutable_l3, self.asbinary, - LinearExpression(self.mutable_l3.args + [self.mon_bin]), + LinearExpression(self.mutable_l3.args + [self.bin]), ), (self.mutable_l3, self.zero, self.npv), (self.mutable_l3, self.one, NPV_SumExpression(self.mutable_l3.args + [1])), @@ -1143,7 +1095,7 @@ def test_add_mutable_l3(self): ( self.mutable_l3, self.var, - LinearExpression(self.mutable_l3.args + [self.mon_var]), + LinearExpression(self.mutable_l3.args + [self.var]), ), ( self.mutable_l3, @@ -1249,32 +1201,32 @@ def test_sub_asbinary(self): # BooleanVar objects do not support addition (self.asbinary, self.asbinary, NotImplemented), (self.asbinary, self.zero, self.bin), - (self.asbinary, self.one, LinearExpression([self.mon_bin, -1])), + (self.asbinary, self.one, LinearExpression([self.bin, -1])), # 4: - (self.asbinary, self.native, LinearExpression([self.mon_bin, -5])), - (self.asbinary, self.npv, LinearExpression([self.mon_bin, self.minus_npv])), - (self.asbinary, self.param, LinearExpression([self.mon_bin, -6])), + (self.asbinary, self.native, LinearExpression([self.bin, -5])), + (self.asbinary, self.npv, LinearExpression([self.bin, self.minus_npv])), + (self.asbinary, self.param, LinearExpression([self.bin, -6])), ( self.asbinary, self.param_mut, - LinearExpression([self.mon_bin, self.minus_param_mut]), + LinearExpression([self.bin, self.minus_param_mut]), ), # 8: - (self.asbinary, self.var, LinearExpression([self.mon_bin, self.minus_var])), + (self.asbinary, self.var, LinearExpression([self.bin, self.minus_var])), ( self.asbinary, self.mon_native, - LinearExpression([self.mon_bin, self.minus_mon_native]), + LinearExpression([self.bin, self.minus_mon_native]), ), ( self.asbinary, self.mon_param, - LinearExpression([self.mon_bin, self.minus_mon_param]), + LinearExpression([self.bin, self.minus_mon_param]), ), ( self.asbinary, self.mon_npv, - LinearExpression([self.mon_bin, self.minus_mon_npv]), + LinearExpression([self.bin, self.minus_mon_npv]), ), # 12: (self.asbinary, self.linear, SumExpression([self.bin, self.minus_linear])), @@ -1285,7 +1237,7 @@ def test_sub_asbinary(self): ( self.asbinary, self.mutable_l1, - LinearExpression([self.mon_bin, self.minus_mon_npv]), + LinearExpression([self.bin, self.minus_mon_npv]), ), ( self.asbinary, @@ -1293,12 +1245,12 @@ def test_sub_asbinary(self): SumExpression([self.bin, self.minus_mutable_l2]), ), (self.asbinary, self.param0, self.bin), - (self.asbinary, self.param1, LinearExpression([self.mon_bin, -1])), + (self.asbinary, self.param1, LinearExpression([self.bin, -1])), # 20: ( self.asbinary, self.mutable_l3, - LinearExpression([self.mon_bin, self.minus_npv]), + LinearExpression([self.bin, self.minus_npv]), ), ] self._run_cases(tests, operator.sub) @@ -1571,35 +1523,31 @@ def test_sub_param_mut(self): def test_sub_var(self): tests = [ (self.var, self.invalid, NotImplemented), - (self.var, self.asbinary, LinearExpression([self.mon_var, self.minus_bin])), + (self.var, self.asbinary, LinearExpression([self.var, self.minus_bin])), (self.var, self.zero, self.var), - (self.var, self.one, LinearExpression([self.mon_var, -1])), + (self.var, self.one, LinearExpression([self.var, -1])), # 4: - (self.var, self.native, LinearExpression([self.mon_var, -5])), - (self.var, self.npv, LinearExpression([self.mon_var, self.minus_npv])), - (self.var, self.param, LinearExpression([self.mon_var, -6])), + (self.var, self.native, LinearExpression([self.var, -5])), + (self.var, self.npv, LinearExpression([self.var, self.minus_npv])), + (self.var, self.param, LinearExpression([self.var, -6])), ( self.var, self.param_mut, - LinearExpression([self.mon_var, self.minus_param_mut]), + LinearExpression([self.var, self.minus_param_mut]), ), # 8: - (self.var, self.var, LinearExpression([self.mon_var, self.minus_var])), + (self.var, self.var, LinearExpression([self.var, self.minus_var])), ( self.var, self.mon_native, - LinearExpression([self.mon_var, self.minus_mon_native]), + LinearExpression([self.var, self.minus_mon_native]), ), ( self.var, self.mon_param, - LinearExpression([self.mon_var, self.minus_mon_param]), - ), - ( - self.var, - self.mon_npv, - LinearExpression([self.mon_var, self.minus_mon_npv]), + LinearExpression([self.var, self.minus_mon_param]), ), + (self.var, self.mon_npv, LinearExpression([self.var, self.minus_mon_npv])), # 12: ( self.var, @@ -1613,7 +1561,7 @@ def test_sub_var(self): ( self.var, self.mutable_l1, - LinearExpression([self.mon_var, self.minus_mon_npv]), + LinearExpression([self.var, self.minus_mon_npv]), ), ( self.var, @@ -1621,13 +1569,9 @@ def test_sub_var(self): SumExpression([self.var, self.minus_mutable_l2]), ), (self.var, self.param0, self.var), - (self.var, self.param1, LinearExpression([self.mon_var, -1])), + (self.var, self.param1, LinearExpression([self.var, -1])), # 20: - ( - self.var, - self.mutable_l3, - LinearExpression([self.mon_var, self.minus_npv]), - ), + (self.var, self.mutable_l3, LinearExpression([self.var, self.minus_npv])), ] self._run_cases(tests, operator.sub) self._run_cases(tests, operator.isub) @@ -6039,7 +5983,7 @@ def test_mutable_nvp_iadd(self): mutable_npv = _MutableNPVSumExpression([]) tests = [ (mutable_npv, self.invalid, NotImplemented), - (mutable_npv, self.asbinary, _MutableLinearExpression([self.mon_bin])), + (mutable_npv, self.asbinary, _MutableLinearExpression([self.bin])), (mutable_npv, self.zero, _MutableNPVSumExpression([])), (mutable_npv, self.one, _MutableNPVSumExpression([1])), # 4: @@ -6048,7 +5992,7 @@ def test_mutable_nvp_iadd(self): (mutable_npv, self.param, _MutableNPVSumExpression([6])), (mutable_npv, self.param_mut, _MutableNPVSumExpression([self.param_mut])), # 8: - (mutable_npv, self.var, _MutableLinearExpression([self.mon_var])), + (mutable_npv, self.var, _MutableLinearExpression([self.var])), (mutable_npv, self.mon_native, _MutableLinearExpression([self.mon_native])), (mutable_npv, self.mon_param, _MutableLinearExpression([self.mon_param])), (mutable_npv, self.mon_npv, _MutableLinearExpression([self.mon_npv])), @@ -6074,7 +6018,7 @@ def test_mutable_nvp_iadd(self): mutable_npv = _MutableNPVSumExpression([10]) tests = [ (mutable_npv, self.invalid, NotImplemented), - (mutable_npv, self.asbinary, _MutableLinearExpression([10, self.mon_bin])), + (mutable_npv, self.asbinary, _MutableLinearExpression([10, self.bin])), (mutable_npv, self.zero, _MutableNPVSumExpression([10])), (mutable_npv, self.one, _MutableNPVSumExpression([10, 1])), # 4: @@ -6087,7 +6031,7 @@ def test_mutable_nvp_iadd(self): _MutableNPVSumExpression([10, self.param_mut]), ), # 8: - (mutable_npv, self.var, _MutableLinearExpression([10, self.mon_var])), + (mutable_npv, self.var, _MutableLinearExpression([10, self.var])), ( mutable_npv, self.mon_native, @@ -6130,7 +6074,7 @@ def test_mutable_lin_iadd(self): mutable_lin = _MutableLinearExpression([]) tests = [ (mutable_lin, self.invalid, NotImplemented), - (mutable_lin, self.asbinary, _MutableLinearExpression([self.mon_bin])), + (mutable_lin, self.asbinary, _MutableLinearExpression([self.bin])), (mutable_lin, self.zero, _MutableLinearExpression([])), (mutable_lin, self.one, _MutableLinearExpression([1])), # 4: @@ -6139,7 +6083,7 @@ def test_mutable_lin_iadd(self): (mutable_lin, self.param, _MutableLinearExpression([6])), (mutable_lin, self.param_mut, _MutableLinearExpression([self.param_mut])), # 8: - (mutable_lin, self.var, _MutableLinearExpression([self.mon_var])), + (mutable_lin, self.var, _MutableLinearExpression([self.var])), (mutable_lin, self.mon_native, _MutableLinearExpression([self.mon_native])), (mutable_lin, self.mon_param, _MutableLinearExpression([self.mon_param])), (mutable_lin, self.mon_npv, _MutableLinearExpression([self.mon_npv])), @@ -6162,81 +6106,69 @@ def test_mutable_lin_iadd(self): ] self._run_iadd_cases(tests, operator.iadd) - mutable_lin = _MutableLinearExpression([self.mon_bin]) + mutable_lin = _MutableLinearExpression([self.bin]) tests = [ (mutable_lin, self.invalid, NotImplemented), ( mutable_lin, self.asbinary, - _MutableLinearExpression([self.mon_bin, self.mon_bin]), + _MutableLinearExpression([self.bin, self.bin]), ), - (mutable_lin, self.zero, _MutableLinearExpression([self.mon_bin])), - (mutable_lin, self.one, _MutableLinearExpression([self.mon_bin, 1])), + (mutable_lin, self.zero, _MutableLinearExpression([self.bin])), + (mutable_lin, self.one, _MutableLinearExpression([self.bin, 1])), # 4: - (mutable_lin, self.native, _MutableLinearExpression([self.mon_bin, 5])), - (mutable_lin, self.npv, _MutableLinearExpression([self.mon_bin, self.npv])), - (mutable_lin, self.param, _MutableLinearExpression([self.mon_bin, 6])), + (mutable_lin, self.native, _MutableLinearExpression([self.bin, 5])), + (mutable_lin, self.npv, _MutableLinearExpression([self.bin, self.npv])), + (mutable_lin, self.param, _MutableLinearExpression([self.bin, 6])), ( mutable_lin, self.param_mut, - _MutableLinearExpression([self.mon_bin, self.param_mut]), + _MutableLinearExpression([self.bin, self.param_mut]), ), # 8: - ( - mutable_lin, - self.var, - _MutableLinearExpression([self.mon_bin, self.mon_var]), - ), + (mutable_lin, self.var, _MutableLinearExpression([self.bin, self.var])), ( mutable_lin, self.mon_native, - _MutableLinearExpression([self.mon_bin, self.mon_native]), + _MutableLinearExpression([self.bin, self.mon_native]), ), ( mutable_lin, self.mon_param, - _MutableLinearExpression([self.mon_bin, self.mon_param]), + _MutableLinearExpression([self.bin, self.mon_param]), ), ( mutable_lin, self.mon_npv, - _MutableLinearExpression([self.mon_bin, self.mon_npv]), + _MutableLinearExpression([self.bin, self.mon_npv]), ), # 12: ( mutable_lin, self.linear, - _MutableLinearExpression([self.mon_bin] + self.linear.args), - ), - ( - mutable_lin, - self.sum, - _MutableSumExpression([self.mon_bin] + self.sum.args), - ), - ( - mutable_lin, - self.other, - _MutableSumExpression([self.mon_bin, self.other]), + _MutableLinearExpression([self.bin] + self.linear.args), ), - (mutable_lin, self.mutable_l0, _MutableLinearExpression([self.mon_bin])), + (mutable_lin, self.sum, _MutableSumExpression([self.bin] + self.sum.args)), + (mutable_lin, self.other, _MutableSumExpression([self.bin, self.other])), + (mutable_lin, self.mutable_l0, _MutableLinearExpression([self.bin])), # 16: ( mutable_lin, self.mutable_l1, - _MutableLinearExpression([self.mon_bin] + self.mutable_l1.args), + _MutableLinearExpression([self.bin] + self.mutable_l1.args), ), ( mutable_lin, self.mutable_l2, - _MutableSumExpression([self.mon_bin] + self.mutable_l2.args), + _MutableSumExpression([self.bin] + self.mutable_l2.args), ), - (mutable_lin, self.param0, _MutableLinearExpression([self.mon_bin])), - (mutable_lin, self.param1, _MutableLinearExpression([self.mon_bin, 1])), + (mutable_lin, self.param0, _MutableLinearExpression([self.bin])), + (mutable_lin, self.param1, _MutableLinearExpression([self.bin, 1])), # 20: ( mutable_lin, self.mutable_l3, - _MutableLinearExpression([self.mon_bin, self.npv]), + _MutableLinearExpression([self.bin, self.npv]), ), ] self._run_iadd_cases(tests, operator.iadd) diff --git a/pyomo/core/tests/unit/test_visitor.py b/pyomo/core/tests/unit/test_visitor.py index fada7d6f6b2..12fb98d1d19 100644 --- a/pyomo/core/tests/unit/test_visitor.py +++ b/pyomo/core/tests/unit/test_visitor.py @@ -437,9 +437,7 @@ def test_replacement_linear_expression_with_constant(self): sub_map = dict() sub_map[id(m.x)] = 5 e2 = replace_expressions(e, sub_map) - assertExpressionsEqual( - self, e2, LinearExpression([10, MonomialTermExpression((1, m.y))]) - ) + assertExpressionsEqual(self, e2, LinearExpression([10, m.y])) e = LinearExpression(linear_coefs=[2, 3], linear_vars=[m.x, m.y]) sub_map = dict() @@ -886,20 +884,7 @@ def test_replace(self): assertExpressionsEqual( self, SumExpression( - [ - LinearExpression( - [ - MonomialTermExpression((1, m.y[1])), - MonomialTermExpression((1, m.y[2])), - ] - ), - LinearExpression( - [ - MonomialTermExpression((1, m.y[2])), - MonomialTermExpression((1, m.y[3])), - ] - ), - ] + [LinearExpression([m.y[1], m.y[2]]), LinearExpression([m.y[2], m.y[3]])] ) == 0, f, @@ -930,9 +915,7 @@ def test_npv_sum(self): e3 = replace_expressions(e1, {id(m.p1): m.x}) assertExpressionsEqual(self, e2, m.p2 + 2) - assertExpressionsEqual( - self, e3, LinearExpression([MonomialTermExpression((1, m.x)), 2]) - ) + assertExpressionsEqual(self, e3, LinearExpression([m.x, 2])) def test_npv_negation(self): m = ConcreteModel() diff --git a/pyomo/gdp/tests/common_tests.py b/pyomo/gdp/tests/common_tests.py index 28025816262..5d0d6f6c21b 100644 --- a/pyomo/gdp/tests/common_tests.py +++ b/pyomo/gdp/tests/common_tests.py @@ -425,12 +425,7 @@ def check_two_term_disjunction_xor(self, xor, disj1, disj2): assertExpressionsEqual( self, xor.body, - EXPR.LinearExpression( - [ - EXPR.MonomialTermExpression((1, disj1.binary_indicator_var)), - EXPR.MonomialTermExpression((1, disj2.binary_indicator_var)), - ] - ), + EXPR.LinearExpression([disj1.binary_indicator_var, disj2.binary_indicator_var]), ) self.assertEqual(xor.lower, 1) self.assertEqual(xor.upper, 1) diff --git a/pyomo/gdp/tests/test_bigm.py b/pyomo/gdp/tests/test_bigm.py index 2383d4587f5..c6ac49f6d36 100644 --- a/pyomo/gdp/tests/test_bigm.py +++ b/pyomo/gdp/tests/test_bigm.py @@ -155,10 +155,7 @@ def test_or_constraints(self): self, orcons.body, EXPR.LinearExpression( - [ - EXPR.MonomialTermExpression((1, m.d[0].binary_indicator_var)), - EXPR.MonomialTermExpression((1, m.d[1].binary_indicator_var)), - ] + [m.d[0].binary_indicator_var, m.d[1].binary_indicator_var] ), ) self.assertEqual(orcons.lower, 1) diff --git a/pyomo/gdp/tests/test_binary_multiplication.py b/pyomo/gdp/tests/test_binary_multiplication.py index aa846c4710a..ae2c44b899e 100644 --- a/pyomo/gdp/tests/test_binary_multiplication.py +++ b/pyomo/gdp/tests/test_binary_multiplication.py @@ -146,10 +146,7 @@ def test_or_constraints(self): self, orcons.body, EXPR.LinearExpression( - [ - EXPR.MonomialTermExpression((1, m.d[0].binary_indicator_var)), - EXPR.MonomialTermExpression((1, m.d[1].binary_indicator_var)), - ] + [m.d[0].binary_indicator_var, m.d[1].binary_indicator_var] ), ) self.assertEqual(orcons.lower, 1) diff --git a/pyomo/gdp/tests/test_disjunct.py b/pyomo/gdp/tests/test_disjunct.py index d969b245ee7..f93ac31fb0f 100644 --- a/pyomo/gdp/tests/test_disjunct.py +++ b/pyomo/gdp/tests/test_disjunct.py @@ -632,19 +632,13 @@ def test_cast_to_binary(self): out = StringIO() with LoggingIntercept(out): e = m.iv + 1 - assertExpressionsEqual( - self, e, EXPR.LinearExpression([EXPR.MonomialTermExpression((1, m.biv)), 1]) - ) + assertExpressionsEqual(self, e, EXPR.LinearExpression([m.biv, 1])) self.assertIn(deprecation_msg, out.getvalue()) out = StringIO() with LoggingIntercept(out): e = m.iv - 1 - assertExpressionsEqual( - self, - e, - EXPR.LinearExpression([EXPR.MonomialTermExpression((1, m.biv)), -1]), - ) + assertExpressionsEqual(self, e, EXPR.LinearExpression([m.biv, -1])) self.assertIn(deprecation_msg, out.getvalue()) out = StringIO() @@ -665,9 +659,7 @@ def test_cast_to_binary(self): out = StringIO() with LoggingIntercept(out): e = 1 + m.iv - assertExpressionsEqual( - self, e, EXPR.LinearExpression([1, EXPR.MonomialTermExpression((1, m.biv))]) - ) + assertExpressionsEqual(self, e, EXPR.LinearExpression([1, m.biv])) self.assertIn(deprecation_msg, out.getvalue()) out = StringIO() @@ -699,20 +691,14 @@ def test_cast_to_binary(self): with LoggingIntercept(out): a = m.iv a += 1 - assertExpressionsEqual( - self, a, EXPR.LinearExpression([EXPR.MonomialTermExpression((1, m.biv)), 1]) - ) + assertExpressionsEqual(self, a, EXPR.LinearExpression([m.biv, 1])) self.assertIn(deprecation_msg, out.getvalue()) out = StringIO() with LoggingIntercept(out): a = m.iv a -= 1 - assertExpressionsEqual( - self, - a, - EXPR.LinearExpression([EXPR.MonomialTermExpression((1, m.biv)), -1]), - ) + assertExpressionsEqual(self, a, EXPR.LinearExpression([m.biv, -1])) self.assertIn(deprecation_msg, out.getvalue()) out = StringIO() From 9da87b6f0af339e116c898be6981e562a7c1d7e0 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Mar 2024 17:37:13 -0700 Subject: [PATCH 1372/1797] Update PyROS to admit VarData in LinearExpressions --- .../contrib/pyros/pyros_algorithm_methods.py | 20 ++++++---- pyomo/contrib/pyros/tests/test_grcs.py | 40 +++++++++++-------- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/pyomo/contrib/pyros/pyros_algorithm_methods.py b/pyomo/contrib/pyros/pyros_algorithm_methods.py index f0e32a284bb..5987db074e6 100644 --- a/pyomo/contrib/pyros/pyros_algorithm_methods.py +++ b/pyomo/contrib/pyros/pyros_algorithm_methods.py @@ -26,6 +26,7 @@ ) from pyomo.contrib.pyros.util import get_main_elapsed_time, coefficient_matching from pyomo.core.base import value +from pyomo.core.expr import MonomialTermExpression from pyomo.common.collections import ComponentSet, ComponentMap from pyomo.core.base.var import _VarData as VarData from itertools import chain @@ -69,14 +70,17 @@ def get_dr_var_to_scaled_expr_map( 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 + if isinstance(term, MonomialTermExpression): + 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 + elif isinstance(term, VarData): + var_to_scaled_expr_map[term] = MonomialTermExpression((1, term)) return var_to_scaled_expr_map diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index df3568e42a4..c308f0d6990 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -19,6 +19,7 @@ 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.base.var import _VarData from pyomo.core.expr import ( identify_variables, identify_mutable_parameters, @@ -571,22 +572,30 @@ def test_dr_eqns_form_correct(self): 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}" - ), - ) + # term should be either a monomial expression or scalar variable + if isinstance(term, MonomialTermExpression): + # should be 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] + dr_var_multiplicand = term.args[1] + else: + self.assertIsInstance(term, _VarData) + param_product_multiplicand = 1 + dr_var_multiplicand = term - # 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,) @@ -612,7 +621,6 @@ def test_dr_eqns_form_correct(self): # check that DR variable participating in the monomial # is as expected - dr_var_multiplicand = term.args[1] self.assertIs( dr_var_multiplicand, dr_var, From 976f88df1dcd7d75e0c331da105ac6e8b8ac7282 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Mar 2024 21:09:54 -0700 Subject: [PATCH 1373/1797] Update doc tests to track change in LinearExpression arg types --- doc/OnlineDocs/src/expr/managing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/OnlineDocs/src/expr/managing.py b/doc/OnlineDocs/src/expr/managing.py index 00d521d16ab..ff149e4fd5c 100644 --- a/doc/OnlineDocs/src/expr/managing.py +++ b/doc/OnlineDocs/src/expr/managing.py @@ -181,7 +181,7 @@ def clone_expression(expr): # x[0] + 5*x[1] print(str(ce)) # x[0] + 5*x[1] -print(e.arg(0) is not ce.arg(0)) +print(e.arg(0) is ce.arg(0)) # True print(e.arg(1) is not ce.arg(1)) # True From 1bfeebbbd91107da54c826b479e23f903c86ab21 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 8 Mar 2024 09:23:26 -0700 Subject: [PATCH 1374/1797] NFC: update docs --- pyomo/core/expr/numeric_expr.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pyomo/core/expr/numeric_expr.py b/pyomo/core/expr/numeric_expr.py index 8ce7ee81c9a..25d83ca20f4 100644 --- a/pyomo/core/expr/numeric_expr.py +++ b/pyomo/core/expr/numeric_expr.py @@ -1234,9 +1234,11 @@ class LinearExpression(SumExpression): """An expression object for linear polynomials. This is a derived :py:class`SumExpression` that guarantees all - arguments are either not potentially variable (e.g., native types, - Params, or NPV expressions) OR :py:class:`MonomialTermExpression` - objects. + arguments are one of the following types: + + - not potentially variable (e.g., native types, Params, or NPV expressions) + - :py:class:`MonomialTermExpression` + - :py:class:`_VarData` Args: args (tuple): Children nodes @@ -1253,7 +1255,7 @@ def __init__(self, args=None, constant=None, linear_coefs=None, linear_vars=None You can specify `args` OR (`constant`, `linear_coefs`, and `linear_vars`). If `args` is provided, it should be a list that - contains only constants, NPV objects/expressions, or + contains only constants, NPV objects/expressions, variables, or :py:class:`MonomialTermExpression` objects. Alternatively, you can specify the constant, the list of linear_coefs and the list of linear_vars separately. Note that these lists are NOT From 3879cf5ad0bc142de9ee92f15bef4af51af65b16 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 11 Mar 2024 11:37:19 -0600 Subject: [PATCH 1375/1797] Additional PyROS update to track change in LinearExpression args --- pyomo/contrib/pyros/master_problem_methods.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index abf02809396..4d2609576ff 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -398,10 +398,17 @@ def construct_dr_polishing_problem(model_data, config): all_ub_cons.append(polishing_absolute_value_ub_cons) # get monomials; ensure second-stage variable term excluded + # + # the dr_eq is a linear sum where teh first term is the + # second-stage variable: the remainder of the terms will be + # either MonomialTermExpressions or bare VarData 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] + if dr_eq_term.is_expression_type(): + dr_var_in_term = dr_eq_term.args[-1] + else: + dr_var_in_term = dr_eq_term dr_var_in_term_idx = dr_var_in_term.index() # get corresponding polishing variable From 8f9cf83a99b5238eb17b2fd77049f6132691fb59 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 11 Mar 2024 11:42:07 -0600 Subject: [PATCH 1376/1797] NFC: fix spelling --- 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 4d2609576ff..8b9e85b90e9 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -399,7 +399,7 @@ def construct_dr_polishing_problem(model_data, config): # get monomials; ensure second-stage variable term excluded # - # the dr_eq is a linear sum where teh first term is the + # the dr_eq is a linear sum where the first term is the # second-stage variable: the remainder of the terms will be # either MonomialTermExpressions or bare VarData dr_expr_terms = dr_eq.body.args[:-1] From a99165c1b4c724681b94f368c1155bd317bc25c3 Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 11 Mar 2024 13:29:31 -0600 Subject: [PATCH 1377/1797] fix attribute error --- pyomo/util/tests/test_subsystems.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/util/tests/test_subsystems.py b/pyomo/util/tests/test_subsystems.py index a9b8a215fcc..05b1bf9f8f4 100644 --- a/pyomo/util/tests/test_subsystems.py +++ b/pyomo/util/tests/test_subsystems.py @@ -304,11 +304,11 @@ def _make_model_with_external_functions(self, named_expressions=False): m.subexpr = pyo.Expression(pyo.PositiveIntegers) subexpr1 = m.subexpr[1] = 2 * m.fermi(m.v1) subexpr2 = m.subexpr[2] = m.bessel(m.v1) - m.bessel(m.v2) - subexpr3 = m.subexpr[3] = m.subexpr[2] + m.v3**2 + subexpr3 = m.subexpr[3] = subexpr2 + m.v3**2 else: subexpr1 = 2 * m.fermi(m.v1) subexpr2 = m.bessel(m.v1) - m.bessel(m.v2) - subexpr3 = m.subexpr[2] + m.v3**2 + subexpr3 = subexpr2 + m.v3**2 m.con1 = pyo.Constraint(expr=m.v1 == 0.5) m.con2 = pyo.Constraint(expr=subexpr1 + m.v2**2 - m.v3 == 1.0) m.con3 = pyo.Constraint(expr=subexpr3 == 2.0) From b3307c9abefe08a0409f51bed80f70adfd0310f6 Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 11 Mar 2024 14:29:25 -0600 Subject: [PATCH 1378/1797] check class in native_types rather than isinstance NumericValue --- pyomo/util/subsystems.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyomo/util/subsystems.py b/pyomo/util/subsystems.py index 79fbdd2d281..d497d132748 100644 --- a/pyomo/util/subsystems.py +++ b/pyomo/util/subsystems.py @@ -36,15 +36,15 @@ def initializeWalker(self, expr): return True, None def beforeChild(self, parent, child, index): - if ( + if child.__class__ in native_types: + return False, None + elif ( not self._descend_into_named_expressions - and isinstance(child, NumericValue) and child.is_named_expression_type() ): self.named_expressions.append(child) return False, None - else: - return True, None + return True, None def exitNode(self, node, data): if type(node) is ExternalFunctionExpression: From a27f90d537daa382cb67420876c2796a8282400e Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 11 Mar 2024 14:30:05 -0600 Subject: [PATCH 1379/1797] remove unnecessary exitNode and acceptChildResult implementations --- pyomo/util/subsystems.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pyomo/util/subsystems.py b/pyomo/util/subsystems.py index d497d132748..0ed6ade756d 100644 --- a/pyomo/util/subsystems.py +++ b/pyomo/util/subsystems.py @@ -55,14 +55,6 @@ def exitNode(self, node, data): def finalizeResult(self, result): return self._functions - # def enterNode(self, node): - # pass - - # def acceptChildResult(self, node, data, child_result, child_idx): - # if child_result.__class__ in native_types: - # return False, None - # return child_result.is_expression_type(), None - def identify_external_functions(expr): # TODO: Potentially support descend_into_named_expressions argument here. From 2ae71d7321e44c294e9ce9b0249fc1a6e514fd07 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 11 Mar 2024 14:32:48 -0600 Subject: [PATCH 1380/1797] Add clarity for specifically a single objective --- 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 55b013facb1..43d168a98a0 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -423,7 +423,7 @@ def _map_results(self, model, results): legacy_results.problem.number_of_variables = model.nvariables() number_of_objectives = model.nobjectives() legacy_results.problem.number_of_objectives = number_of_objectives - if number_of_objectives > 0: + if number_of_objectives == 1: obj = get_objective(model) legacy_results.problem.sense = obj.sense From 3ecd3bb80d145070e33eac81b348cf5a80eb529c Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 11 Mar 2024 14:34:24 -0600 Subject: [PATCH 1381/1797] remove deferred todo comment --- pyomo/util/subsystems.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyomo/util/subsystems.py b/pyomo/util/subsystems.py index 0ed6ade756d..f2e2eae8444 100644 --- a/pyomo/util/subsystems.py +++ b/pyomo/util/subsystems.py @@ -57,8 +57,6 @@ def finalizeResult(self, result): def identify_external_functions(expr): - # TODO: Potentially support descend_into_named_expressions argument here. - # This will likely require converting from a generator to a function. yield from _ExternalFunctionVisitor().walk_expression(expr) From fde5edac7cb378d5d06bd1e7b929f70597155981 Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 11 Mar 2024 14:36:42 -0600 Subject: [PATCH 1382/1797] remove HierarchicalTimer use from subsystem calls --- pyomo/util/subsystems.py | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/pyomo/util/subsystems.py b/pyomo/util/subsystems.py index f2e2eae8444..5789829ac54 100644 --- a/pyomo/util/subsystems.py +++ b/pyomo/util/subsystems.py @@ -101,12 +101,7 @@ def add_local_external_functions(block): return fcn_comp_map -from pyomo.common.timing import HierarchicalTimer - - -def create_subsystem_block( - constraints, variables=None, include_fixed=False, timer=None -): +def create_subsystem_block(constraints, variables=None, include_fixed=False): """This function creates a block to serve as a subsystem with the specified variables and constraints. To satisfy certain writers, other variables that appear in the constraints must be added to the block as @@ -130,36 +125,24 @@ def create_subsystem_block( as well as other variables present in the constraints """ - if timer is None: - timer = HierarchicalTimer() if variables is None: variables = [] - timer.start("block") block = Block(concrete=True) - timer.stop("block") - timer.start("reference") block.vars = Reference(variables) block.cons = Reference(constraints) - timer.stop("reference") var_set = ComponentSet(variables) input_vars = [] - timer.start("identify-vars") for con in constraints: for var in identify_variables(con.expr, include_fixed=include_fixed): if var not in var_set: input_vars.append(var) var_set.add(var) - timer.stop("identify-vars") - timer.start("reference") block.input_vars = Reference(input_vars) - timer.stop("reference") - timer.start("external-fcns") add_local_external_functions(block) - timer.stop("external-fcns") return block -def generate_subsystem_blocks(subsystems, include_fixed=False, timer=None): +def generate_subsystem_blocks(subsystems, include_fixed=False): """Generates blocks that contain subsystems of variables and constraints. Arguments @@ -178,10 +161,8 @@ def generate_subsystem_blocks(subsystems, include_fixed=False, timer=None): not specified are contained in the input_vars component. """ - if timer is None: - timer = HierarchicalTimer() for cons, vars in subsystems: - block = create_subsystem_block(cons, vars, include_fixed, timer=timer) + block = create_subsystem_block(cons, vars, include_fixed) yield block, list(block.input_vars.values()) From 40debe7f52d4370440824f43ef26c550a30f8713 Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 11 Mar 2024 14:40:32 -0600 Subject: [PATCH 1383/1797] remove hierarchical timer usage from scc_solver module --- .../contrib/incidence_analysis/scc_solver.py | 30 ++----------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/scc_solver.py b/pyomo/contrib/incidence_analysis/scc_solver.py index eff4f5ae5fa..8c38333e058 100644 --- a/pyomo/contrib/incidence_analysis/scc_solver.py +++ b/pyomo/contrib/incidence_analysis/scc_solver.py @@ -11,7 +11,6 @@ import logging -from pyomo.common.timing import HierarchicalTimer from pyomo.core.base.constraint import Constraint from pyomo.util.calc_var_value import calculate_variable_from_constraint from pyomo.util.subsystems import TemporarySubsystemManager, generate_subsystem_blocks @@ -26,7 +25,7 @@ def generate_strongly_connected_components( - constraints, variables=None, include_fixed=False, igraph=None, timer=None + constraints, variables=None, include_fixed=False, igraph=None ): """Yield in order ``_BlockData`` that each contain the variables and constraints of a single diagonal block in a block lower triangularization @@ -58,10 +57,7 @@ def generate_strongly_connected_components( "input variables" for that block. """ - if timer is None: - timer = HierarchicalTimer() if variables is None: - timer.start("generate-vars") variables = list( _generate_variables_in_constraints( constraints, @@ -69,30 +65,23 @@ def generate_strongly_connected_components( method=IncidenceMethod.ampl_repn, ) ) - timer.stop("generate-vars") assert len(variables) == len(constraints) if igraph is None: igraph = IncidenceGraphInterface() - timer.start("block-triang") var_blocks, con_blocks = igraph.block_triangularize( variables=variables, constraints=constraints ) - timer.stop("block-triang") subsets = [(cblock, vblock) for vblock, cblock in zip(var_blocks, con_blocks)] - timer.start("generate-block") for block, inputs in generate_subsystem_blocks( - subsets, include_fixed=include_fixed, timer=timer + subsets, include_fixed=include_fixed ): - timer.stop("generate-block") # TODO: How does len scale for reference-to-list? assert len(block.vars) == len(block.cons) yield (block, inputs) # Note that this code, after the last yield, I believe is only called # at time of GC. - timer.start("generate-block") - timer.stop("generate-block") def solve_strongly_connected_components( @@ -102,7 +91,6 @@ def solve_strongly_connected_components( solve_kwds=None, use_calc_var=True, calc_var_kwds=None, - timer=None, ): """Solve a square system of variables and equality constraints by solving strongly connected components individually. @@ -142,10 +130,7 @@ def solve_strongly_connected_components( solve_kwds = {} if calc_var_kwds is None: calc_var_kwds = {} - if timer is None: - timer = HierarchicalTimer() - timer.start("igraph") igraph = IncidenceGraphInterface( block, active=True, @@ -153,27 +138,22 @@ def solve_strongly_connected_components( include_inequality=False, method=IncidenceMethod.ampl_repn, ) - timer.stop("igraph") constraints = igraph.constraints variables = igraph.variables res_list = [] log_blocks = _log.isEnabledFor(logging.DEBUG) - timer.start("generate-scc") for scc, inputs in generate_strongly_connected_components( - constraints, variables, timer=timer, igraph=igraph + constraints, variables, igraph=igraph ): - timer.stop("generate-scc") with TemporarySubsystemManager(to_fix=inputs, remove_bounds_on_fix=True): N = len(scc.vars) if N == 1 and use_calc_var: if log_blocks: _log.debug(f"Solving 1x1 block: {scc.cons[0].name}.") - timer.start("calc-var-from-con") results = calculate_variable_from_constraint( scc.vars[0], scc.cons[0], **calc_var_kwds ) - timer.stop("calc-var-from-con") else: if solver is None: var_names = [var.name for var in scc.vars.values()][:10] @@ -186,10 +166,6 @@ def solve_strongly_connected_components( ) if log_blocks: _log.debug(f"Solving {N}x{N} block.") - timer.start("scc-subsolver") results = solver.solve(scc, **solve_kwds) - timer.stop("scc-subsolver") res_list.append(results) - timer.start("generate-scc") - timer.stop("generate-scc") return res_list From e9e63a00da4014195b6d82549f9f48f034d447b7 Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 11 Mar 2024 15:28:58 -0600 Subject: [PATCH 1384/1797] use new variable visitor in get_vars_from_components rather than identify_variables_in_expressions --- pyomo/core/expr/visitor.py | 34 ----------------------------- pyomo/util/vars_from_expressions.py | 31 +++++++++++++++++++------- 2 files changed, 23 insertions(+), 42 deletions(-) diff --git a/pyomo/core/expr/visitor.py b/pyomo/core/expr/visitor.py index 51864044396..bccf0eda899 100644 --- a/pyomo/core/expr/visitor.py +++ b/pyomo/core/expr/visitor.py @@ -1433,40 +1433,6 @@ def acceptChildResult(self, node, data, child_result, child_idx): return child_result.is_expression_type(), None -def identify_variables_in_components(components, include_fixed=True): - visitor = _StreamVariableVisitor( - include_fixed=include_fixed, descend_into_named_expressions=False - ) - all_variables = [] - for comp in components: - all_variables.extend(visitor.walk_expressions(comp.expr)) - - named_expr_set = set() - unique_named_exprs = [] - for expr in visitor.named_expressions: - if id(expr) in named_expr_set: - named_expr_set.add(id(expr)) - unique_named_exprs.append(expr) - - while unique_named_exprs: - expr = unique_named_exprs.pop() - visitor.named_expressions.clear() - all_variables.extend(visitor.walk_expression(expr.expr)) - - for new_expr in visitor.named_expressions: - if id(new_expr) not in named_expr_set: - named_expr_set.add(new_expr) - unique_named_exprs.append(new_expr) - - unique_vars = [] - var_set = set() - for var in all_variables: - if id(var) not in var_set: - var_set.add(id(var)) - unique_vars.append(var) - return unique_vars - - def identify_variables(expr, include_fixed=True): """ A generator that yields a sequence of variables diff --git a/pyomo/util/vars_from_expressions.py b/pyomo/util/vars_from_expressions.py index f9b3f1ab8ae..1fe614273ab 100644 --- a/pyomo/util/vars_from_expressions.py +++ b/pyomo/util/vars_from_expressions.py @@ -17,7 +17,7 @@ actually in the subtree or not. """ from pyomo.core import Block -import pyomo.core.expr as EXPR +from pyomo.core.expr.visitor import _StreamVariableVisitor def get_vars_from_components( @@ -42,7 +42,10 @@ def get_vars_from_components( descend_into: Ctypes to descend into when finding Constraints descent_order: Traversal strategy for finding the objects of type ctype """ - seen = set() + visitor = _StreamVariableVisitor( + include_fixed=include_fixed, descend_into_named_expressions=False + ) + variables = [] for constraint in block.component_data_objects( ctype, active=active, @@ -50,9 +53,21 @@ def get_vars_from_components( descend_into=descend_into, descent_order=descent_order, ): - for var in EXPR.identify_variables( - constraint.expr, include_fixed=include_fixed - ): - if id(var) not in seen: - seen.add(id(var)) - yield var + variables.extend(visitor.walk_expression(constraint.expr)) + seen_named_exprs = set() + named_expr_stack = list(visitor.named_expressions) + while named_expr_stack: + expr = named_expr_stack.pop() + # Clear visitor's named expression cache so we only identify new + # named expressions + visitor.named_expressions.clear() + variables.extend(visitor.walk_expression(expr.expr)) + for new_expr in visitor.named_expressions: + if id(new_expr) not in seen_named_exprs: + seen_named_exprs.add(id(new_expr)) + named_expr_stack.append(new_expr) + seen = set() + for var in variables: + if id(var) not in seen: + seen.add(id(var)) + yield var From 38c78a329b282b12a50be1df5055f2d1b6978a59 Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 11 Mar 2024 15:31:24 -0600 Subject: [PATCH 1385/1797] remove unnecessary walker callbacks --- pyomo/core/expr/visitor.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pyomo/core/expr/visitor.py b/pyomo/core/expr/visitor.py index bccf0eda899..3c1e486d38f 100644 --- a/pyomo/core/expr/visitor.py +++ b/pyomo/core/expr/visitor.py @@ -1424,14 +1424,6 @@ def exitNode(self, node, data): def finalizeResult(self, result): return self._variables - def enterNode(self, node): - pass - - def acceptChildResult(self, node, data, child_result, child_idx): - if child_result.__class__ in native_types: - return False, None - return child_result.is_expression_type(), None - def identify_variables(expr, include_fixed=True): """ From 0e9af931d9e4149728bc192bddc0aaf65d772168 Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 11 Mar 2024 18:03:43 -0600 Subject: [PATCH 1386/1797] fix typos in test --- pyomo/util/tests/test_subsystems.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyomo/util/tests/test_subsystems.py b/pyomo/util/tests/test_subsystems.py index 05b1bf9f8f4..fe093b4723f 100644 --- a/pyomo/util/tests/test_subsystems.py +++ b/pyomo/util/tests/test_subsystems.py @@ -341,11 +341,11 @@ def test_identify_external_functions(self): @unittest.skipUnless(find_GSL(), "Could not find the AMPL GSL library") def test_local_external_functions_with_named_expressions(self): m = self._make_model_with_external_functions(named_expressions=True) - variables = list(pyo.component_data_objects(pyo.Var)) - constraints = list(pyo.component_data_objects(pyo.Constraint, active=True)) + variables = list(m.component_data_objects(pyo.Var)) + constraints = list(m.component_data_objects(pyo.Constraint, active=True)) b = create_subsystem_block(constraints, variables) - self.assertTrue(isinstance(m._gsl_sf_bessel_J0, pyo.ExternalFunction)) - self.assertTrue(isinstance(m._gsl_sf_fermi_dirac_m1, pyo.ExternalFunction)) + self.assertTrue(isinstance(b._gsl_sf_bessel_J0, pyo.ExternalFunction)) + self.assertTrue(isinstance(b._gsl_sf_fermi_dirac_m1, pyo.ExternalFunction)) def _solve_ef_model_with_ipopt(self): m = self._make_model_with_external_functions() From 016d6d1b4536e6147984e792a23dbb0590dc5439 Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 11 Mar 2024 18:06:44 -0600 Subject: [PATCH 1387/1797] function args on single line --- pyomo/contrib/incidence_analysis/scc_solver.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/scc_solver.py b/pyomo/contrib/incidence_analysis/scc_solver.py index 8c38333e058..aa59b698ce9 100644 --- a/pyomo/contrib/incidence_analysis/scc_solver.py +++ b/pyomo/contrib/incidence_analysis/scc_solver.py @@ -85,12 +85,7 @@ def generate_strongly_connected_components( def solve_strongly_connected_components( - block, - *, - solver=None, - solve_kwds=None, - use_calc_var=True, - calc_var_kwds=None, + block, *, solver=None, solve_kwds=None, use_calc_var=True, calc_var_kwds=None ): """Solve a square system of variables and equality constraints by solving strongly connected components individually. From f32bd2e26ca0f2ecc6ea8d883b40851a4dcd2a4a Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 11 Mar 2024 18:15:38 -0600 Subject: [PATCH 1388/1797] method args on single line --- pyomo/core/expr/visitor.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pyomo/core/expr/visitor.py b/pyomo/core/expr/visitor.py index 3c1e486d38f..3d5608bda4c 100644 --- a/pyomo/core/expr/visitor.py +++ b/pyomo/core/expr/visitor.py @@ -1389,11 +1389,7 @@ def visit(self, node): class _StreamVariableVisitor(StreamBasedExpressionVisitor): - def __init__( - self, - include_fixed=False, - descend_into_named_expressions=True, - ): + def __init__(self, include_fixed=False, descend_into_named_expressions=True): self._include_fixed = include_fixed self._descend_into_named_expressions = descend_into_named_expressions self.named_expressions = [] From 87644ca8d4d9d31e05809b0492ade63b90e8c184 Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 11 Mar 2024 19:42:51 -0600 Subject: [PATCH 1389/1797] update condition for skipping named expression in variable visitor --- pyomo/core/expr/visitor.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyomo/core/expr/visitor.py b/pyomo/core/expr/visitor.py index 3d5608bda4c..b31fa20f77c 100644 --- a/pyomo/core/expr/visitor.py +++ b/pyomo/core/expr/visitor.py @@ -1401,9 +1401,10 @@ def initializeWalker(self, expr): return True, None def beforeChild(self, parent, child, index): - if ( + if child.__class__ in native_types: + return False, None + elif ( not self._descend_into_named_expressions - and isinstance(child, NumericValue) and child.is_named_expression_type() ): self.named_expressions.append(child) From e6e7259a258d560d633e8b2dcf5d93ae406d2337 Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 11 Mar 2024 19:47:41 -0600 Subject: [PATCH 1390/1797] super.__init__ call in variable visitor --- pyomo/core/expr/visitor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/core/expr/visitor.py b/pyomo/core/expr/visitor.py index b31fa20f77c..befdef0be71 100644 --- a/pyomo/core/expr/visitor.py +++ b/pyomo/core/expr/visitor.py @@ -1390,6 +1390,7 @@ def visit(self, node): class _StreamVariableVisitor(StreamBasedExpressionVisitor): def __init__(self, include_fixed=False, descend_into_named_expressions=True): + super().__init__() self._include_fixed = include_fixed self._descend_into_named_expressions = descend_into_named_expressions self.named_expressions = [] From 24644ff06414f689937551f204eea7fc81cef7b2 Mon Sep 17 00:00:00 2001 From: robbybp Date: Tue, 12 Mar 2024 10:30:19 -0600 Subject: [PATCH 1391/1797] remove outdated comment --- pyomo/contrib/incidence_analysis/scc_solver.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/scc_solver.py b/pyomo/contrib/incidence_analysis/scc_solver.py index aa59b698ce9..0c59fe8703e 100644 --- a/pyomo/contrib/incidence_analysis/scc_solver.py +++ b/pyomo/contrib/incidence_analysis/scc_solver.py @@ -80,8 +80,6 @@ def generate_strongly_connected_components( # TODO: How does len scale for reference-to-list? assert len(block.vars) == len(block.cons) yield (block, inputs) - # Note that this code, after the last yield, I believe is only called - # at time of GC. def solve_strongly_connected_components( From d12d69b4b1a409ef7450ae5f22229d19d3401dc1 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 12 Mar 2024 13:39:55 -0600 Subject: [PATCH 1392/1797] Temporarily pinning to highspy pre-release for testing --- .github/workflows/test_pr_and_main.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 76ec6de951a..a2060240391 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -605,7 +605,8 @@ jobs: if: ${{ ! matrix.slim }} shell: bash run: | - $PYTHON_EXE -m pip install --cache-dir cache/pip highspy \ + echo "NOTE: temporarily pinning to highspy pre-release for testing" + $PYTHON_EXE -m pip install --cache-dir cache/pip highspy==1.7.1.dev1 \ || echo "WARNING: highspy is not available" - name: Set up coverage tracking From de294ee874602cf1c94615dfa2a49ed162439464 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Tue, 12 Mar 2024 15:39:12 -0600 Subject: [PATCH 1393/1797] fix variable assignment --- pyomo/util/tests/test_subsystems.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pyomo/util/tests/test_subsystems.py b/pyomo/util/tests/test_subsystems.py index fe093b4723f..089888bd6a9 100644 --- a/pyomo/util/tests/test_subsystems.py +++ b/pyomo/util/tests/test_subsystems.py @@ -302,9 +302,12 @@ def _make_model_with_external_functions(self, named_expressions=False): m.v3 = pyo.Var(initialize=3.0) if named_expressions: m.subexpr = pyo.Expression(pyo.PositiveIntegers) - subexpr1 = m.subexpr[1] = 2 * m.fermi(m.v1) - subexpr2 = m.subexpr[2] = m.bessel(m.v1) - m.bessel(m.v2) - subexpr3 = m.subexpr[3] = subexpr2 + m.v3**2 + m.subexpr[1] = 2 * m.fermi(m.v1) + m.subexpr[2] = m.bessel(m.v1) - m.bessel(m.v2) + m.subexpr[3] = m.subexpr[2] + m.v3**2 + subexpr1 = m.subexpr[1] + subexpr2 = m.subexpr[2] + subexpr3 = m.subexpr[3] else: subexpr1 = 2 * m.fermi(m.v1) subexpr2 = m.bessel(m.v1) - m.bessel(m.v2) From 8371c8ae548aa57e80ec55fa69a3d4d7220bebb0 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 12 Mar 2024 16:18:57 -0600 Subject: [PATCH 1394/1797] Catch when NLv2 presolve identifies/removes independent linear subsystems --- pyomo/repn/plugins/nl_writer.py | 32 ++++++++++++- pyomo/repn/tests/ampl/test_nlv2.py | 72 ++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 2 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index b82d4df77e2..2db3e66cbb6 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -1081,9 +1081,37 @@ def write(self, model): # Update any eliminated variables to point to the (potentially # scaled) substituted variables - for _id, expr_info in eliminated_vars.items(): + for _id, expr_info in list(eliminated_vars.items()): nl, args, _ = expr_info.compile_repn(visitor) - _vmap[_id] = nl.rstrip() % tuple(_vmap[_id] for _id in args) + for _i in args: + # It is possible that the eliminated variable could + # reference another variable that is no longer part of + # the model and therefore does not have a _vmap entry. + # This can happen when there is an underdetermined + # independent linear subsystem and the presolve removed + # all the constraints from the subsystem. Because the + # free variables in the subsystem are not referenced + # anywhere else in the model, they are not part of the + # `varaibles` list. Implicitly "fix" it to an arbitrary + # valid value from the presolved domain (see #3192). + if _i not in _vmap: + lb, ub = var_bounds[_i] + if lb is None: + lb = -inf + if ub is None: + ub = inf + if lb <= 0 <= ub: + val = 0 + else: + val = lb if abs(lb) < abs(ub) else ub + eliminated_vars[_i] = AMPLRepn(val, {}, None) + _vmap[_i] = expr_info.compile_repn(visitor)[0] + logger.warning( + "presolve identified an underdetermined independent " + "linear subsystem that was removed from the model. " + f"Setting '{var_map[_i]}' == {val}" + ) + _vmap[_id] = nl.rstrip() % tuple(_vmap[_i] for _i 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 86eb43d9a37..be72025edcd 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -1812,6 +1812,78 @@ def test_presolve_zero_coef(self): ) ) + def test_presolve_independent_subsystem(self): + # This is derived from the example in #3192 + m = ConcreteModel() + m.x = Var() + m.y = Var() + m.z = Var() + m.d = Constraint(expr=m.z == m.y) + m.c = Constraint(expr=m.y == m.x) + m.o = Objective(expr=0) + + ref = """g3 1 1 0 #problem unknown + 0 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 0 #nonzeros in Jacobian, obj. gradient + 1 0 #max name lengths: constraints, variables + 0 0 0 0 0 #common exprs: b,c,o,c1,o1 +O0 0 #o +n0 +x0 #initial guess +r #0 ranges (rhs's) +b #0 bounds (on variables) +k-1 #intermediate Jacobian column lengths +""" + + 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(), + "presolve identified an underdetermined independent linear subsystem " + "that was removed from the model. Setting 'z' == 0\n", + ) + + self.assertEqual(*nl_diff(ref, OUT.getvalue())) + + m.x.lb = 5.0 + + 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(), + "presolve identified an underdetermined independent linear subsystem " + "that was removed from the model. Setting 'z' == 5.0\n", + ) + + self.assertEqual(*nl_diff(ref, OUT.getvalue())) + + m.x.lb = -5.0 + m.z.ub = -2.0 + + 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(), + "presolve identified an underdetermined independent linear subsystem " + "that was removed from the model. Setting 'z' == -2.0\n", + ) + + self.assertEqual(*nl_diff(ref, OUT.getvalue())) + def test_scaling(self): m = pyo.ConcreteModel() m.x = pyo.Var(initialize=0) From b3a4b06e9bd1cfb3c3cecc9a4b1a85c262cfbdd3 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 12 Mar 2024 16:23:05 -0600 Subject: [PATCH 1395/1797] NFC: 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 2db3e66cbb6..ee5b65149ae 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -1092,7 +1092,7 @@ def write(self, model): # all the constraints from the subsystem. Because the # free variables in the subsystem are not referenced # anywhere else in the model, they are not part of the - # `varaibles` list. Implicitly "fix" it to an arbitrary + # `variables` list. Implicitly "fix" it to an arbitrary # valid value from the presolved domain (see #3192). if _i not in _vmap: lb, ub = var_bounds[_i] From 440bf86c9cb9fabff38daa5d27cbdd95d18be1f4 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 13 Mar 2024 13:12:58 -0600 Subject: [PATCH 1396/1797] Minor logic reordering to make the intent more clear --- pyomo/repn/util.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index a51ee1c6d64..1b3056738bf 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -400,14 +400,15 @@ def __init__(self, *args, **kwargs): def __missing__(self, key): if type(key) is tuple: - node_class = key[0] - node_args = key[1:] # Only lookup/cache argument-specific handlers for unary, # binary and ternary operators - if len(key) > 3: - key = node_class - if key in self: - return self[key] + if len(key) <= 3: + node_class = key[0] + node_args = key[1:] + else: + node_class = key = key[0] + if node_class in self: + return self[node_class] else: node_class = key bases = node_class.__mro__ @@ -446,7 +447,7 @@ def __missing__(self, key): def unexpected_expression_type(self, visitor, node, *args): raise DeveloperError( f"Unexpected expression node type '{type(node).__name__}' " - f"found while walking expression tree in {type(self).__name__}." + f"found while walking expression tree in {type(visitor).__name__}." ) From e1fa2569252d849884da0569c95f8011716d3507 Mon Sep 17 00:00:00 2001 From: robbybp Date: Wed, 13 Mar 2024 23:06:44 -0600 Subject: [PATCH 1397/1797] [WIP] initial attempt at implementing identify_variables with a named expression cache --- pyomo/core/expr/visitor.py | 105 +++++++++++++++++++++------- pyomo/util/vars_from_expressions.py | 63 +++++++++++------ 2 files changed, 122 insertions(+), 46 deletions(-) diff --git a/pyomo/core/expr/visitor.py b/pyomo/core/expr/visitor.py index befdef0be71..20d8d72fcbb 100644 --- a/pyomo/core/expr/visitor.py +++ b/pyomo/core/expr/visitor.py @@ -1389,12 +1389,20 @@ def visit(self, node): class _StreamVariableVisitor(StreamBasedExpressionVisitor): - def __init__(self, include_fixed=False, descend_into_named_expressions=True): + def __init__( + self, + include_fixed=False, + #descend_into_named_expressions=True, + named_expression_cache=None, + ): super().__init__() self._include_fixed = include_fixed - self._descend_into_named_expressions = descend_into_named_expressions + #self._descend_into_named_expressions = descend_into_named_expressions self.named_expressions = [] - # Should we allow re-use of this visitor for multiple expressions? + if named_expression_cache is None: + named_expression_cache = {} + self._named_expression_cache = named_expression_cache + self._active_named_expressions = [] def initializeWalker(self, expr): self._variables = [] @@ -1404,12 +1412,26 @@ def initializeWalker(self, expr): def beforeChild(self, parent, child, index): if child.__class__ in native_types: return False, None - elif ( - not self._descend_into_named_expressions - and child.is_named_expression_type() - ): - self.named_expressions.append(child) - return False, None + #elif ( + # not self._descend_into_named_expressions + # and child.is_named_expression_type() + #): + # self.named_expressions.append(child) + # return False, None + elif child.is_named_expression_type(): + if id(child) in self._named_expression_cache: + # We have already encountered this named expression. We just add + # the cached variables to our list and don't descend. + for var in self._named_expression_cache[id(child)][0]: + if id(var) not in self._seen: + self._variables.append(var) + return False, None + else: + # If we are descending into a new named expression, initialize + # a cache to store the expression's local variables. + self._named_expression_cache[id(child)] = ([], set()) + self._active_named_expressions.append(id(child)) + return True, None else: return True, None @@ -1418,12 +1440,35 @@ def exitNode(self, node, data): if id(node) not in self._seen: self._seen.add(id(node)) self._variables.append(node) + if self._active_named_expressions: + # If we are in a named expression, add new variables to the cache. + eid = self._active_named_expressions[-1] + local_vars, local_var_set = self._named_expression_cache[eid] + if id(node) not in local_var_set: + local_var_set.add(id(node)) + local_vars.append(node) + elif node.is_named_expression_type(): + # If we are returning from a named expression, we have at least one + # active named expression. + eid = self._active_named_expressions.pop() + if self._active_named_expressions: + # If we still are in a named expression, we update that expression's + # cache with any new variables encountered. + new_eid = self._active_named_expressions[-1] + old_expr_vars, old_expr_var_set = self._named_expression_cache[eid] + new_expr_vars, new_expr_var_set = self._named_expression_cache[new_eid] + + for var in old_expr_vars: + if id(var) not in new_expr_var_set: + new_expr_var_set.add(id(var)) + new_expr_vars.append(var) def finalizeResult(self, result): return self._variables -def identify_variables(expr, include_fixed=True): +# TODO: descend_into_named_expressions option? +def identify_variables(expr, include_fixed=True, named_expression_cache=None): """ A generator that yields a sequence of variables in an expression tree. @@ -1437,22 +1482,34 @@ def identify_variables(expr, include_fixed=True): Yields: Each variable that is found. """ - visitor = _VariableVisitor() - if include_fixed: - for v in visitor.xbfs_yield_leaves(expr): - if isinstance(v, tuple): - yield from v - else: - yield v + if named_expression_cache is None: + named_expression_cache = {} + + NEW = True + if NEW: + visitor = _StreamVariableVisitor( + named_expression_cache=named_expression_cache, + include_fixed=False, + ) + variables = visitor.walk_expression(expr) + yield from variables else: - for v in visitor.xbfs_yield_leaves(expr): - if isinstance(v, tuple): - for v_i in v: - if not v_i.is_fixed(): - yield v_i - else: - if not v.is_fixed(): + visitor = _VariableVisitor() + if include_fixed: + for v in visitor.xbfs_yield_leaves(expr): + if isinstance(v, tuple): + yield from v + else: yield v + else: + for v in visitor.xbfs_yield_leaves(expr): + if isinstance(v, tuple): + for v_i in v: + if not v_i.is_fixed(): + yield v_i + else: + if not v.is_fixed(): + yield v # ===================================================== diff --git a/pyomo/util/vars_from_expressions.py b/pyomo/util/vars_from_expressions.py index 1fe614273ab..22e6e6dab8d 100644 --- a/pyomo/util/vars_from_expressions.py +++ b/pyomo/util/vars_from_expressions.py @@ -18,6 +18,7 @@ """ from pyomo.core import Block from pyomo.core.expr.visitor import _StreamVariableVisitor +from pyomo.core.expr import identify_variables def get_vars_from_components( @@ -42,10 +43,38 @@ def get_vars_from_components( descend_into: Ctypes to descend into when finding Constraints descent_order: Traversal strategy for finding the objects of type ctype """ - visitor = _StreamVariableVisitor( - include_fixed=include_fixed, descend_into_named_expressions=False - ) - variables = [] + #visitor = _StreamVariableVisitor( + # include_fixed=include_fixed, descend_into_named_expressions=False + #) + #variables = [] + #for constraint in block.component_data_objects( + # ctype, + # active=active, + # sort=sort, + # descend_into=descend_into, + # descent_order=descent_order, + #): + # variables.extend(visitor.walk_expression(constraint.expr)) + # seen_named_exprs = set() + # named_expr_stack = list(visitor.named_expressions) + # while named_expr_stack: + # expr = named_expr_stack.pop() + # # Clear visitor's named expression cache so we only identify new + # # named expressions + # visitor.named_expressions.clear() + # variables.extend(visitor.walk_expression(expr.expr)) + # for new_expr in visitor.named_expressions: + # if id(new_expr) not in seen_named_exprs: + # seen_named_exprs.add(id(new_expr)) + # named_expr_stack.append(new_expr) + #seen = set() + #for var in variables: + # if id(var) not in seen: + # seen.add(id(var)) + # yield var + + seen = set() + named_expression_cache = {} for constraint in block.component_data_objects( ctype, active=active, @@ -53,21 +82,11 @@ def get_vars_from_components( descend_into=descend_into, descent_order=descent_order, ): - variables.extend(visitor.walk_expression(constraint.expr)) - seen_named_exprs = set() - named_expr_stack = list(visitor.named_expressions) - while named_expr_stack: - expr = named_expr_stack.pop() - # Clear visitor's named expression cache so we only identify new - # named expressions - visitor.named_expressions.clear() - variables.extend(visitor.walk_expression(expr.expr)) - for new_expr in visitor.named_expressions: - if id(new_expr) not in seen_named_exprs: - seen_named_exprs.add(id(new_expr)) - named_expr_stack.append(new_expr) - seen = set() - for var in variables: - if id(var) not in seen: - seen.add(id(var)) - yield var + for var in identify_variables( + constraint.expr, + include_fixed=include_fixed, + named_expression_cache=named_expression_cache, + ): + if id(var) not in seen: + seen.add(id(var)) + yield var From 53899315af8ba53ae0c0e22851b7d7894a03ed33 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 14 Mar 2024 10:19:33 -0600 Subject: [PATCH 1398/1797] Add link to the companion notebooks for Hands-on Mathematival Optimization with Python --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 95558e52a42..12c3ce8ed9a 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,8 @@ version, we will remove testing for that Python version. * [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/) +* The [companion notebooks](https://mobook.github.io/MO-book/intro.html) + for *Hands-On Mathematical Optimization with Python* * [Pyomo Gallery](https://github.com/Pyomo/PyomoGallery) ### Getting Help From cca02a124c4d39fe38ff1e5fe749c089d9cc5f27 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 14 Mar 2024 10:26:48 -0600 Subject: [PATCH 1399/1797] Syncing tutorials/examples list between README and RTD --- doc/OnlineDocs/tutorial_examples.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/OnlineDocs/tutorial_examples.rst b/doc/OnlineDocs/tutorial_examples.rst index dc58b6a6f59..6a40949ef90 100644 --- a/doc/OnlineDocs/tutorial_examples.rst +++ b/doc/OnlineDocs/tutorial_examples.rst @@ -9,7 +9,9 @@ Additional Pyomo tutorials and examples can be found at the following links: `Prof. Jeffrey Kantor's Pyomo Cookbook `_ -`Pyomo Gallery -`_ +The `companion notebooks `_ +for *Hands-On Mathematical Optimization with Python* + +`Pyomo Gallery `_ From 5ea6ae75c4f92c6b66fd4c839c2d1a8825bb1a39 Mon Sep 17 00:00:00 2001 From: robbybp Date: Fri, 15 Mar 2024 16:56:44 -0600 Subject: [PATCH 1400/1797] potentially working implementation of identify-variables with efficient named expression handling --- pyomo/core/expr/visitor.py | 88 ++++++++++++++++++++++++++++---------- 1 file changed, 65 insertions(+), 23 deletions(-) diff --git a/pyomo/core/expr/visitor.py b/pyomo/core/expr/visitor.py index 20d8d72fcbb..7ae1900f9b8 100644 --- a/pyomo/core/expr/visitor.py +++ b/pyomo/core/expr/visitor.py @@ -1405,26 +1405,46 @@ def __init__( self._active_named_expressions = [] def initializeWalker(self, expr): - self._variables = [] - self._seen = set() - return True, None + if expr.__class__ in native_types: + return False, [] + elif expr.is_named_expression_type(): + eid = id(expr) + if eid in self._named_expression_cache: + variables, var_set = self._named_expression_cache[eid] + return False, variables + else: + self._variables = [] + self._seen = set() + self._named_expression_cache[eid] = [], set() + self._active_named_expressions.append(eid) + return True, expr + else: + self._variables = [] + self._seen = set() + return True, expr def beforeChild(self, parent, child, index): if child.__class__ in native_types: return False, None - #elif ( - # not self._descend_into_named_expressions - # and child.is_named_expression_type() - #): - # self.named_expressions.append(child) - # return False, None elif child.is_named_expression_type(): - if id(child) in self._named_expression_cache: + eid = id(child) + if eid in self._named_expression_cache: # We have already encountered this named expression. We just add # the cached variables to our list and don't descend. - for var in self._named_expression_cache[id(child)][0]: - if id(var) not in self._seen: - self._variables.append(var) + if self._active_named_expressions: + # If we are in another named expression, we update the + # parent expression's cache + parent_eid = self._active_named_expressions[-1] + variables, var_set = self._named_expression_cache[parent_eid] + else: + # If we are not in a named expression, we update the global + # list + variables = self._variables + var_set = self._seen + for var in self._named_expression_cache[eid][0]: + if id(var) not in var_set: + var_set.add(id(var)) + variables.append(var) return False, None else: # If we are descending into a new named expression, initialize @@ -1432,6 +1452,18 @@ def beforeChild(self, parent, child, index): self._named_expression_cache[id(child)] = ([], set()) self._active_named_expressions.append(id(child)) return True, None + elif child.is_variable_type() and (self._include_fixed or not child.fixed): + if id(child) not in self._seen: + self._seen.add(id(child)) + self._variables.append(child) + if self._active_named_expressions: + # If we are in a named expression, add new variables to the cache. + eid = self._active_named_expressions[-1] + local_vars, local_var_set = self._named_expression_cache[eid] + if id(child) not in local_var_set: + local_var_set.add(id(child)) + local_vars.append(child) + return False, None else: return True, None @@ -1449,19 +1481,29 @@ def exitNode(self, node, data): local_vars.append(node) elif node.is_named_expression_type(): # If we are returning from a named expression, we have at least one - # active named expression. + # active named expression. We must make sure that we properly + # handle the variables for the named expression we just exited. eid = self._active_named_expressions.pop() if self._active_named_expressions: # If we still are in a named expression, we update that expression's # cache with any new variables encountered. - new_eid = self._active_named_expressions[-1] - old_expr_vars, old_expr_var_set = self._named_expression_cache[eid] - new_expr_vars, new_expr_var_set = self._named_expression_cache[new_eid] - - for var in old_expr_vars: - if id(var) not in new_expr_var_set: - new_expr_var_set.add(id(var)) - new_expr_vars.append(var) + #new_eid = self._active_named_expressions[-1] + #old_expr_vars, old_expr_var_set = self._named_expression_cache[eid] + #new_expr_vars, new_expr_var_set = self._named_expression_cache[new_eid] + + #for var in old_expr_vars: + # if id(var) not in new_expr_var_set: + # new_expr_var_set.add(id(var)) + # new_expr_vars.append(var) + parent_eid = self._active_named_expressions[-1] + variables, var_set = self._named_expression_cache[parent_eid] + else: + variables = self._variables + var_set = self._seen + for var in self._named_expression_cache[eid][0]: + if id(var) not in var_set: + var_set.add(id(var)) + variables.append(var) def finalizeResult(self, result): return self._variables @@ -1489,7 +1531,7 @@ def identify_variables(expr, include_fixed=True, named_expression_cache=None): if NEW: visitor = _StreamVariableVisitor( named_expression_cache=named_expression_cache, - include_fixed=False, + include_fixed=include_fixed, ) variables = visitor.walk_expression(expr) yield from variables From a82c7509ade2c6fb65f69c3b38472ee81ce6519b Mon Sep 17 00:00:00 2001 From: robbybp Date: Fri, 15 Mar 2024 17:21:02 -0600 Subject: [PATCH 1401/1797] update identify_variables tests to use ComponentSet to not rely on variable order --- pyomo/core/tests/unit/test_visitor.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/pyomo/core/tests/unit/test_visitor.py b/pyomo/core/tests/unit/test_visitor.py index 12fb98d1d19..d6d83f84e67 100644 --- a/pyomo/core/tests/unit/test_visitor.py +++ b/pyomo/core/tests/unit/test_visitor.py @@ -145,7 +145,8 @@ def test_identify_vars_vars(self): self.assertEqual(list(identify_variables(m.a + m.b[1])), [m.a, m.b[1]]) self.assertEqual(list(identify_variables(m.a ** m.b[1])), [m.a, m.b[1]]) self.assertEqual( - list(identify_variables(m.a ** m.b[1] + m.b[2])), [m.b[2], m.a, m.b[1]] + ComponentSet(identify_variables(m.a ** m.b[1] + m.b[2])), + ComponentSet([m.b[2], m.a, m.b[1]]), ) self.assertEqual( list(identify_variables(m.a ** m.b[1] + m.b[2] * m.b[3] * m.b[2])), @@ -159,14 +160,20 @@ def test_identify_vars_vars(self): # Identify variables in the arguments to functions # self.assertEqual( - list(identify_variables(m.x(m.a, 'string_param', 1, []) * m.b[1])), - [m.b[1], m.a], + ComponentSet(identify_variables(m.x(m.a, 'string_param', 1, []) * m.b[1])), + ComponentSet([m.b[1], m.a]), ) self.assertEqual( list(identify_variables(m.x(m.p, 'string_param', 1, []) * m.b[1])), [m.b[1]] ) - self.assertEqual(list(identify_variables(tanh(m.a) * m.b[1])), [m.b[1], m.a]) - self.assertEqual(list(identify_variables(abs(m.a) * m.b[1])), [m.b[1], m.a]) + self.assertEqual( + ComponentSet(identify_variables(tanh(m.a) * m.b[1])), + ComponentSet([m.b[1], m.a]), + ) + self.assertEqual( + ComponentSet(identify_variables(abs(m.a) * m.b[1])), + ComponentSet([m.b[1], m.a]), + ) # # Check logic for allowing duplicates # From f8299f6bf6526b20d5085b34e44ea7321a668e43 Mon Sep 17 00:00:00 2001 From: robbybp Date: Fri, 15 Mar 2024 17:25:36 -0600 Subject: [PATCH 1402/1797] remove commented code and old identify_variables implementation --- pyomo/core/expr/visitor.py | 43 ++++++-------------------------------- 1 file changed, 6 insertions(+), 37 deletions(-) diff --git a/pyomo/core/expr/visitor.py b/pyomo/core/expr/visitor.py index 7ae1900f9b8..b284c7fa38f 100644 --- a/pyomo/core/expr/visitor.py +++ b/pyomo/core/expr/visitor.py @@ -1392,12 +1392,10 @@ class _StreamVariableVisitor(StreamBasedExpressionVisitor): def __init__( self, include_fixed=False, - #descend_into_named_expressions=True, named_expression_cache=None, ): super().__init__() self._include_fixed = include_fixed - #self._descend_into_named_expressions = descend_into_named_expressions self.named_expressions = [] if named_expression_cache is None: named_expression_cache = {} @@ -1487,14 +1485,6 @@ def exitNode(self, node, data): if self._active_named_expressions: # If we still are in a named expression, we update that expression's # cache with any new variables encountered. - #new_eid = self._active_named_expressions[-1] - #old_expr_vars, old_expr_var_set = self._named_expression_cache[eid] - #new_expr_vars, new_expr_var_set = self._named_expression_cache[new_eid] - - #for var in old_expr_vars: - # if id(var) not in new_expr_var_set: - # new_expr_var_set.add(id(var)) - # new_expr_vars.append(var) parent_eid = self._active_named_expressions[-1] variables, var_set = self._named_expression_cache[parent_eid] else: @@ -1509,7 +1499,6 @@ def finalizeResult(self, result): return self._variables -# TODO: descend_into_named_expressions option? def identify_variables(expr, include_fixed=True, named_expression_cache=None): """ A generator that yields a sequence of variables @@ -1526,32 +1515,12 @@ def identify_variables(expr, include_fixed=True, named_expression_cache=None): """ if named_expression_cache is None: named_expression_cache = {} - - NEW = True - if NEW: - visitor = _StreamVariableVisitor( - named_expression_cache=named_expression_cache, - include_fixed=include_fixed, - ) - variables = visitor.walk_expression(expr) - yield from variables - else: - visitor = _VariableVisitor() - if include_fixed: - for v in visitor.xbfs_yield_leaves(expr): - if isinstance(v, tuple): - yield from v - else: - yield v - else: - for v in visitor.xbfs_yield_leaves(expr): - if isinstance(v, tuple): - for v_i in v: - if not v_i.is_fixed(): - yield v_i - else: - if not v.is_fixed(): - yield v + visitor = _StreamVariableVisitor( + named_expression_cache=named_expression_cache, + include_fixed=include_fixed, + ) + variables = visitor.walk_expression(expr) + yield from variables # ===================================================== From d232fcab6a66add09f746febb706947df9f534ff Mon Sep 17 00:00:00 2001 From: robbybp Date: Fri, 15 Mar 2024 17:27:35 -0600 Subject: [PATCH 1403/1797] handle variable at root in initializeWalker rather than exitNode --- pyomo/core/expr/visitor.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/pyomo/core/expr/visitor.py b/pyomo/core/expr/visitor.py index b284c7fa38f..4cdc77df41e 100644 --- a/pyomo/core/expr/visitor.py +++ b/pyomo/core/expr/visitor.py @@ -1416,6 +1416,8 @@ def initializeWalker(self, expr): self._named_expression_cache[eid] = [], set() self._active_named_expressions.append(eid) return True, expr + elif expr.is_variable_type(): + return False, [expr] else: self._variables = [] self._seen = set() @@ -1466,18 +1468,7 @@ def beforeChild(self, parent, child, index): return True, None def exitNode(self, node, data): - if node.is_variable_type() and (self._include_fixed or not node.fixed): - if id(node) not in self._seen: - self._seen.add(id(node)) - self._variables.append(node) - if self._active_named_expressions: - # If we are in a named expression, add new variables to the cache. - eid = self._active_named_expressions[-1] - local_vars, local_var_set = self._named_expression_cache[eid] - if id(node) not in local_var_set: - local_var_set.add(id(node)) - local_vars.append(node) - elif node.is_named_expression_type(): + if node.is_named_expression_type(): # If we are returning from a named expression, we have at least one # active named expression. We must make sure that we properly # handle the variables for the named expression we just exited. From 796ccef2e5c69a104ef3a7e9e307e5c323a26907 Mon Sep 17 00:00:00 2001 From: robbybp Date: Fri, 15 Mar 2024 17:28:21 -0600 Subject: [PATCH 1404/1797] remove previous vars_from_expressions implementation --- pyomo/util/vars_from_expressions.py | 30 ----------------------------- 1 file changed, 30 deletions(-) diff --git a/pyomo/util/vars_from_expressions.py b/pyomo/util/vars_from_expressions.py index 22e6e6dab8d..62953af456b 100644 --- a/pyomo/util/vars_from_expressions.py +++ b/pyomo/util/vars_from_expressions.py @@ -43,36 +43,6 @@ def get_vars_from_components( descend_into: Ctypes to descend into when finding Constraints descent_order: Traversal strategy for finding the objects of type ctype """ - #visitor = _StreamVariableVisitor( - # include_fixed=include_fixed, descend_into_named_expressions=False - #) - #variables = [] - #for constraint in block.component_data_objects( - # ctype, - # active=active, - # sort=sort, - # descend_into=descend_into, - # descent_order=descent_order, - #): - # variables.extend(visitor.walk_expression(constraint.expr)) - # seen_named_exprs = set() - # named_expr_stack = list(visitor.named_expressions) - # while named_expr_stack: - # expr = named_expr_stack.pop() - # # Clear visitor's named expression cache so we only identify new - # # named expressions - # visitor.named_expressions.clear() - # variables.extend(visitor.walk_expression(expr.expr)) - # for new_expr in visitor.named_expressions: - # if id(new_expr) not in seen_named_exprs: - # seen_named_exprs.add(id(new_expr)) - # named_expr_stack.append(new_expr) - #seen = set() - #for var in variables: - # if id(var) not in seen: - # seen.add(id(var)) - # yield var - seen = set() named_expression_cache = {} for constraint in block.component_data_objects( From 7c130ef1b6d62b2f1a2ffdf6ae399dbb8fad047a Mon Sep 17 00:00:00 2001 From: robbybp Date: Fri, 15 Mar 2024 17:38:43 -0600 Subject: [PATCH 1405/1797] add docstring and comments to _StreamVariableVisitor --- pyomo/core/expr/visitor.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pyomo/core/expr/visitor.py b/pyomo/core/expr/visitor.py index 4cdc77df41e..6dd587cf2d4 100644 --- a/pyomo/core/expr/visitor.py +++ b/pyomo/core/expr/visitor.py @@ -1394,12 +1394,25 @@ def __init__( include_fixed=False, named_expression_cache=None, ): + """Visitor that collects all unique variables participating in an + expression + + Args: + include_fixed (bool): Whether to include fixed variables + named_expression_cache (optional, dict): Dict mapping ids of named + expressions to a tuple of the list of all variables and the + set of all variable ids contained in the named expression. + + """ super().__init__() self._include_fixed = include_fixed - self.named_expressions = [] if named_expression_cache is None: + # This cache will map named expression ids to the + # tuple: ([variables], {variable ids}) named_expression_cache = {} self._named_expression_cache = named_expression_cache + # Stack of active named expressions. This holds the id of + # expressions we are currently in. self._active_named_expressions = [] def initializeWalker(self, expr): From 0e3015dcf9e2daece51a485868537f9be8443e03 Mon Sep 17 00:00:00 2001 From: robbybp Date: Fri, 15 Mar 2024 17:44:10 -0600 Subject: [PATCH 1406/1797] arguments on single line --- pyomo/core/expr/visitor.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/pyomo/core/expr/visitor.py b/pyomo/core/expr/visitor.py index 6dd587cf2d4..1cd2ce3213a 100644 --- a/pyomo/core/expr/visitor.py +++ b/pyomo/core/expr/visitor.py @@ -1389,11 +1389,7 @@ def visit(self, node): class _StreamVariableVisitor(StreamBasedExpressionVisitor): - def __init__( - self, - include_fixed=False, - named_expression_cache=None, - ): + def __init__(self, include_fixed=False, named_expression_cache=None): """Visitor that collects all unique variables participating in an expression @@ -1520,8 +1516,7 @@ def identify_variables(expr, include_fixed=True, named_expression_cache=None): if named_expression_cache is None: named_expression_cache = {} visitor = _StreamVariableVisitor( - named_expression_cache=named_expression_cache, - include_fixed=include_fixed, + named_expression_cache=named_expression_cache, include_fixed=include_fixed ) variables = visitor.walk_expression(expr) yield from variables From c64dcf459c261ed615e054179cfd614627629dc4 Mon Sep 17 00:00:00 2001 From: robbybp Date: Fri, 15 Mar 2024 21:56:54 -0600 Subject: [PATCH 1407/1797] consolidate logic for adding variable to set --- pyomo/core/expr/visitor.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/pyomo/core/expr/visitor.py b/pyomo/core/expr/visitor.py index 1cd2ce3213a..7b519e0f63e 100644 --- a/pyomo/core/expr/visitor.py +++ b/pyomo/core/expr/visitor.py @@ -1417,9 +1417,14 @@ def initializeWalker(self, expr): elif expr.is_named_expression_type(): eid = id(expr) if eid in self._named_expression_cache: + # If we were given a named expression that is already cached, + # just do nothing and return the expression's variables variables, var_set = self._named_expression_cache[eid] return False, variables else: + # We were given a named expression that is not cached. + # Initialize data structures and add this expression to the + # stack. This expression will get popped in exitNode. self._variables = [] self._seen = set() self._named_expression_cache[eid] = [], set() @@ -1442,12 +1447,14 @@ def beforeChild(self, parent, child, index): # the cached variables to our list and don't descend. if self._active_named_expressions: # If we are in another named expression, we update the - # parent expression's cache + # parent expression's cache. We don't need to update the + # global list as we will do this when we exit the active + # named expression. parent_eid = self._active_named_expressions[-1] variables, var_set = self._named_expression_cache[parent_eid] else: # If we are not in a named expression, we update the global - # list + # list. variables = self._variables var_set = self._seen for var in self._named_expression_cache[eid][0]: @@ -1462,16 +1469,16 @@ def beforeChild(self, parent, child, index): self._active_named_expressions.append(id(child)) return True, None elif child.is_variable_type() and (self._include_fixed or not child.fixed): - if id(child) not in self._seen: - self._seen.add(id(child)) - self._variables.append(child) if self._active_named_expressions: # If we are in a named expression, add new variables to the cache. eid = self._active_named_expressions[-1] - local_vars, local_var_set = self._named_expression_cache[eid] - if id(child) not in local_var_set: - local_var_set.add(id(child)) - local_vars.append(child) + variables, var_set = self._named_expression_cache[eid] + else: + variables = self._variables + var_set = self._seen + if id(child) not in local_var_set: + var_set.add(id(child)) + variables.append(child) return False, None else: return True, None From aac6e2f1c49cfb1437b129f76f8edbb017eccda5 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sat, 16 Mar 2024 20:12:48 -0400 Subject: [PATCH 1408/1797] Standardize PyROS subordinate solver calls --- pyomo/contrib/pyros/master_problem_methods.py | 96 ++++++------------- .../pyros/separation_problem_methods.py | 45 +++------ pyomo/contrib/pyros/util.py | 78 +++++++++++++++ 3 files changed, 120 insertions(+), 99 deletions(-) diff --git a/pyomo/contrib/pyros/master_problem_methods.py b/pyomo/contrib/pyros/master_problem_methods.py index 8b9e85b90e9..2af38c1d582 100644 --- a/pyomo/contrib/pyros/master_problem_methods.py +++ b/pyomo/contrib/pyros/master_problem_methods.py @@ -27,6 +27,7 @@ from pyomo.core.expr import value from pyomo.core.base.set_types import NonNegativeIntegers, NonNegativeReals from pyomo.contrib.pyros.util import ( + call_solver, selective_clone, ObjectiveType, pyrosTerminationCondition, @@ -239,31 +240,18 @@ def solve_master_feasibility_problem(model_data, config): else: solver = config.local_solver - timer = TicTocTimer() - 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) - except ApplicationError: - # account for possible external subsolver errors - # (such as segmentation faults, function evaluation - # errors, etc.) - config.progress_logger.error( + results = call_solver( + model=model, + solver=solver, + config=config, + timing_obj=model_data.timing, + timer_name="main.master_feasibility", + err_msg=( f"Optimizer {repr(solver)} encountered exception " "attempting to solve master feasibility problem in iteration " f"{model_data.iteration}." - ) - 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 - ) + ), + ) feasible_terminations = { tc.optimal, @@ -482,28 +470,18 @@ def minimize_dr_vars(model_data, config): 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( - 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) - except ApplicationError: - config.progress_logger.error( + results = call_solver( + model=polishing_model, + solver=solver, + config=config, + timing_obj=model_data.timing, + timer_name="main.dr_polishing", + err_msg=( f"Optimizer {repr(solver)} encountered an exception " "attempting to solve decision rule polishing problem " f"in iteration {model_data.iteration}" - ) - 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 - ) + ), + ) # interested in the time and termination status for debugging # purposes @@ -726,7 +704,6 @@ def solver_call_master(model_data, config, solver, solve_data): solve_mode = "global" if config.solve_master_globally else "local" config.progress_logger.debug("Solving master problem") - timer = TicTocTimer() for idx, opt in enumerate(solvers): if idx > 0: config.progress_logger.warning( @@ -734,35 +711,18 @@ def solver_call_master(model_data, config, solver, solve_data): 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 - ) - model_data.timing.start_timer("main.master") - timer.tic(msg=None) - try: - results = opt.solve( - nlp_model, - tee=config.tee, - load_solutions=False, - symbolic_solver_labels=True, - ) - except ApplicationError: - # account for possible external subsolver errors - # (such as segmentation faults, function evaluation - # errors, etc.) - config.progress_logger.error( + results = call_solver( + model=nlp_model, + solver=opt, + config=config, + timing_obj=model_data.timing, + timer_name="main.master", + err_msg=( f"Optimizer {repr(opt)} ({idx + 1} of {len(solvers)}) " "encountered exception attempting to " f"solve master problem in iteration {model_data.iteration}" - ) - 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 - ) + ), + ) optimal_termination = check_optimal_termination(results) infeasible = results.solver.termination_condition == tc.infeasible diff --git a/pyomo/contrib/pyros/separation_problem_methods.py b/pyomo/contrib/pyros/separation_problem_methods.py index b5939ff5b19..18d0925bab0 100644 --- a/pyomo/contrib/pyros/separation_problem_methods.py +++ b/pyomo/contrib/pyros/separation_problem_methods.py @@ -18,7 +18,6 @@ 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 from pyomo.contrib.pyros.solve_data import ( DiscreteSeparationSolveCallResults, SeparationSolveCallResults, @@ -37,9 +36,11 @@ from pyomo.contrib.pyros.util import ABS_CON_CHECK_FEAS_TOL from pyomo.common.timing import TicTocTimer from pyomo.contrib.pyros.util import ( - TIC_TOC_SOLVE_TIME_ATTR, adjust_solver_time_settings, + call_solver, + ObjectiveType, revert_solver_max_time_adjustment, + TIC_TOC_SOLVE_TIME_ATTR, ) import os from copy import deepcopy @@ -1070,6 +1071,7 @@ def solver_call_separation( separation_obj.activate() + solve_mode_adverb = "globally" if solve_globally else "locally" solve_call_results = SeparationSolveCallResults( solved_globally=solve_globally, time_out=False, @@ -1077,7 +1079,6 @@ def solver_call_separation( found_violation=False, subsolver_error=False, ) - timer = TicTocTimer() for idx, opt in enumerate(solvers): if idx > 0: config.progress_logger.warning( @@ -1086,37 +1087,19 @@ def solver_call_separation( 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 - ) - model_data.timing.start_timer(f"main.{solve_mode}_separation") - timer.tic(msg=None) - try: - results = opt.solve( - nlp_model, - tee=config.tee, - load_solutions=False, - symbolic_solver_labels=True, - ) - except ApplicationError: - # 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( + results = call_solver( + model=nlp_model, + solver=opt, + config=config, + timing_obj=model_data.timing, + timer_name=f"main.{solve_mode}_separation", + err_msg=( f"Optimizer {repr(opt)} ({idx + 1} of {len(solvers)}) " f"encountered exception attempting " - f"to {adverb} solve separation problem for constraint " + f"to {solve_mode_adverb} solve separation problem for constraint " f"{con_name_repr} in iteration {model_data.iteration}." - ) - 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 - ) + ), + ) # record termination condition for this particular solver solver_status_dict[str(opt)] = results.solver.termination_condition diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index a3ab3464aa8..33551115148 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -16,7 +16,9 @@ import copy from enum import Enum, auto from pyomo.common.collections import ComponentSet, ComponentMap +from pyomo.common.errors import ApplicationError from pyomo.common.modeling import unique_component_name +from pyomo.common.timing import TicTocTimer from pyomo.core.base import ( Constraint, Var, @@ -1731,6 +1733,82 @@ def process_termination_condition_master_problem(config, results): ) +def call_solver(model, solver, config, timing_obj, timer_name, err_msg): + """ + Solve a model with a given optimizer, keeping track of + wall time requirements. + + Parameters + ---------- + model : ConcreteModel + Model of interest. + solver : Pyomo solver type + Subordinate optimizer. + config : ConfigDict + PyROS solver settings. + timing_obj : TimingData + PyROS solver timing data object. + timer_name : str + Name of sub timer under the hierarchical timer contained in + ``timing_obj`` to start/stop for keeping track of solve + time requirements. + err_msg : str + Message to log through ``config.progress_logger.exception()`` + in event an ApplicationError is raised while attempting to + solve the model. + + Returns + ------- + SolverResults + Solve results. Note that ``results.solver`` contains + an additional attribute, named after + ``TIC_TOC_SOLVE_TIME_ATTR``, of which the value is set to the + recorded solver wall time. + + Raises + ------ + ApplicationError + If ApplicationError is raised by the solver. + In this case, `err_msg` is logged through + ``config.progress_logger.exception()`` before + the excception is raised. + """ + tt_timer = TicTocTimer() + + orig_setting, custom_setting_present = adjust_solver_time_settings( + timing_obj, solver, config + ) + timing_obj.start_timer(timer_name) + tt_timer.tic(msg=None) + + try: + results = solver.solve( + model, + tee=config.tee, + load_solutions=False, + symbolic_solver_labels=True, + ) + except ApplicationError: + # account for possible external subsolver errors + # (such as segmentation faults, function evaluation + # errors, etc.) + config.progress_logger.error(err_msg) + raise + else: + setattr( + results.solver, + TIC_TOC_SOLVE_TIME_ATTR, + tt_timer.toc(msg=None, delta=True), + ) + finally: + timing_obj.stop_timer(timer_name) + revert_solver_max_time_adjustment( + solver, orig_setting, custom_setting_present, config + ) + + return results + + class IterationLogRecord: """ PyROS solver iteration log record. From d9f22516d0b79d204462ffb91095b408423de524 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 17 Mar 2024 15:57:18 -0400 Subject: [PATCH 1409/1797] Account for user settings in subsolver time limit adjustment --- pyomo/contrib/pyros/util.py | 68 +++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 18 deletions(-) diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index 33551115148..7d40d357863 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -232,15 +232,15 @@ def get_main_elapsed_time(timing_data_obj): def adjust_solver_time_settings(timing_data_obj, solver, config): """ - Adjust solver max time setting based on current PyROS elapsed - time. + Adjust maximum time allowed for subordinate solver, based + on total PyROS solver elapsed time up to this point. Parameters ---------- timing_data_obj : Bunch PyROS timekeeper. solver : solver type - Solver for which to adjust the max time setting. + Subordinate solver for which to adjust the max time setting. config : ConfigDict PyROS solver config. @@ -262,26 +262,40 @@ def adjust_solver_time_settings(timing_data_obj, solver, config): ---- (1) Adjustment only supported for GAMS, BARON, and IPOPT interfaces. This routine can be generalized to other solvers - after a generic interface to the time limit setting + after a generic Pyomo interface to the time limit setting is introduced. - (2) For IPOPT, and probably also BARON, the CPU time limit - rather than the wallclock time limit, is adjusted, as - no interface to wallclock limit available. - For this reason, extra 30s is added to time remaining - for subsolver time limit. - (The extra 30s is large enough to ensure solver - elapsed time is not beneath elapsed time - user time limit, - but not so large as to overshoot the user-specified time limit - by an inordinate margin.) + (2) For IPOPT and BARON, the CPU time limit, + rather than the wallclock time limit, may be adjusted, + as there may be no means by which to specify the wall time + limit explicitly. + (3) For GAMS, we adjust the time limit through the GAMS Reslim + option. However, this may be overriden by any user + specifications included in a GAMS optfile, which may be + difficult to track down. + (3) To ensure the time limit is specified to a strictly + positive value, the time limit is adjusted to a value of + at least 1 second. """ + # in case there is no time remaining: we set time limit + # to a minimum of 1s, as some solvers require a strictly + # positive time limit + time_limit_buffer = 1 + if config.time_limit is not None: time_remaining = config.time_limit - get_main_elapsed_time(timing_data_obj) if isinstance(solver, type(SolverFactory("gams", solver_io="shell"))): original_max_time_setting = solver.options["add_options"] custom_setting_present = "add_options" in solver.options - # adjust GAMS solver time - reslim_str = f"option reslim={max(30, 30 + time_remaining)};" + # note: our time limit will be overriden by any + # time limits specified by the user through a + # GAMS optfile, but tracking down the optfile + # and/or the GAMS subsolver specific option + # is more difficult + reslim_str = ( + "option reslim=" + f"{max(time_limit_buffer, time_remaining)};" + ) if isinstance(solver.options["add_options"], list): solver.options["add_options"].append(reslim_str) else: @@ -291,7 +305,13 @@ def adjust_solver_time_settings(timing_data_obj, solver, config): if isinstance(solver, SolverFactory.get_class("baron")): options_key = "MaxTime" elif isinstance(solver, SolverFactory.get_class("ipopt")): - options_key = "max_cpu_time" + options_key = ( + # IPOPT 3.14.0+ added support for specifying + # wall time limit explicitly; this is preferred + # over CPU time limit + "max_wall_time" if solver.version() >= (3, 14, 0, 0) + else "max_cpu_time" + ) else: options_key = None @@ -299,8 +319,20 @@ def adjust_solver_time_settings(timing_data_obj, solver, config): custom_setting_present = options_key in solver.options original_max_time_setting = solver.options[options_key] - # ensure positive value assigned to avoid application error - solver.options[options_key] = max(30, 30 + time_remaining) + # account for elapsed time remaining and + # original time limit setting. + # if no original time limit is set, then we assume + # there is no time limit, rather than tracking + # down the solver-specific default + orig_max_time = ( + float("inf") + if original_max_time_setting is None + else original_max_time_setting + ) + solver.options[options_key] = min( + max(time_limit_buffer, time_remaining), + orig_max_time, + ) else: custom_setting_present = False original_max_time_setting = None From 2593fce468e4f8095a2c2c35698323155035d2e8 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 17 Mar 2024 18:05:38 -0400 Subject: [PATCH 1410/1797] Fix test error message string --- pyomo/contrib/pyros/tests/test_grcs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index c308f0d6990..754ab6678ea 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -4398,8 +4398,8 @@ def test_gams_successful_time_limit(self): results.pyros_termination_condition, pyrosTerminationCondition.robust_optimal, msg=( - f"Returned termination condition with local " - "subsolver {idx + 1} of 2 is not robust_optimal." + "Returned termination condition with local " + f"subsolver {idx + 1} of 2 is not robust_optimal." ), ) From 679dc7ee4620a1d424d055250363e84d24bca86c Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 17 Mar 2024 18:38:48 -0400 Subject: [PATCH 1411/1797] Add support for SCIP time limit adjustment --- pyomo/contrib/pyros/tests/test_grcs.py | 77 +++++++------------------- pyomo/contrib/pyros/util.py | 4 ++ 2 files changed, 24 insertions(+), 57 deletions(-) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 754ab6678ea..92532677a80 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -4345,10 +4345,10 @@ def test_separation_terminate_time_limit(self): and SolverFactory('baron').license_is_valid(), "Global NLP solver is not available and licensed.", ) - def test_gams_successful_time_limit(self): + def test_pyros_subsolver_time_limit_adjustment(self): """ - Test PyROS time limit status returned in event - separation problem times out. + Check that PyROS does not ultimately alter state of + subordinate solver options due to time limit adjustments. """ m = ConcreteModel() m.x1 = Var(initialize=0, bounds=(0, None)) @@ -4367,20 +4367,26 @@ def test_gams_successful_time_limit(self): # Instantiate the PyROS solver pyros_solver = SolverFactory("pyros") - # Define subsolvers utilized in the algorithm - # two GAMS solvers, one of which has reslim set - # (overridden when invoked in PyROS) + # subordinate solvers to test. + # for testing, we pass each as the 'local' solver, + # and the BARON solver without custom options + # as the 'global' solver + baron_no_options = SolverFactory("baron") local_subsolvers = [ SolverFactory("gams:conopt"), SolverFactory("gams:conopt"), SolverFactory("ipopt"), + SolverFactory("ipopt", options={"max_cpu_time": 300}), + SolverFactory("scip"), + SolverFactory("scip", options={"limits/time": 300}), + baron_no_options, + SolverFactory("baron", options={"MaxTime": 300}), ] local_subsolvers[0].options["add_options"] = ["option reslim=100;"] - global_subsolver = SolverFactory("baron") - global_subsolver.options["MaxTime"] = 300 # Call the PyROS solver for idx, opt in enumerate(local_subsolvers): + original_solver_options = opt.options.copy() results = pyros_solver.solve( model=m, first_stage_variables=[m.x1, m.x2], @@ -4388,12 +4394,11 @@ def test_gams_successful_time_limit(self): uncertain_params=[m.u], uncertainty_set=interval, local_solver=opt, - global_solver=global_subsolver, + global_solver=baron_no_options, objective_focus=ObjectiveType.worst_case, solve_master_globally=True, time_limit=100, ) - self.assertEqual( results.pyros_termination_condition, pyrosTerminationCondition.robust_optimal, @@ -4402,54 +4407,12 @@ def test_gams_successful_time_limit(self): f"subsolver {idx + 1} of 2 is not robust_optimal." ), ) - - # check first local subsolver settings - # remain unchanged after PyROS exit - self.assertEqual( - len(list(local_subsolvers[0].options["add_options"])), - 1, - msg=( - f"Local subsolver {local_subsolvers[0]} options 'add_options'" - "were changed by PyROS" - ), - ) - self.assertEqual( - local_subsolvers[0].options["add_options"][0], - "option reslim=100;", - msg=( - f"Local subsolver {local_subsolvers[0]} setting " - "'add_options' was modified " - "by PyROS, but changes were not properly undone" - ), - ) - - # check global subsolver settings unchanged - self.assertEqual( - len(list(global_subsolver.options.keys())), - 1, - msg=(f"Global subsolver {global_subsolver} options were changed by PyROS"), - ) - self.assertEqual( - global_subsolver.options["MaxTime"], - 300, - msg=( - f"Global subsolver {global_subsolver} setting " - "'MaxTime' was modified " - "by PyROS, but changes were not properly undone" - ), - ) - - # check other local subsolvers remain unchanged - for slvr, key in zip(local_subsolvers[1:], ["add_options", "max_cpu_time"]): - # no custom options were added to the `options` - # attribute of the optimizer, so any attribute - # of `options` should be `None` - self.assertIs( - getattr(slvr.options, key, None), - None, + self.assertEqual( + opt.options, + original_solver_options, msg=( - f"Local subsolver {slvr} setting '{key}' was added " - "by PyROS, but not reverted" + f"Options for subordinate solver {opt} were changed " + "by PyROS, and the changes wee not properly reverted." ), ) diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index 7d40d357863..bdec2213d43 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -312,6 +312,8 @@ def adjust_solver_time_settings(timing_data_obj, solver, config): "max_wall_time" if solver.version() >= (3, 14, 0, 0) else "max_cpu_time" ) + elif isinstance(solver, SolverFactory.get_class("scip")): + options_key = "limits/time" else: options_key = None @@ -379,6 +381,8 @@ def revert_solver_max_time_adjustment( options_key = "MaxTime" elif isinstance(solver, SolverFactory.get_class("ipopt")): options_key = "max_cpu_time" + elif isinstance(solver, SolverFactory.get_class("scip")): + options_key = "limits/time" else: options_key = None From fcb28193147e55e18f69128d10060d2b8839ca8b Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 17 Mar 2024 18:42:21 -0400 Subject: [PATCH 1412/1797] Simplify time limit adjustment reversion --- pyomo/contrib/pyros/util.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index bdec2213d43..fa423e37450 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -397,12 +397,7 @@ def revert_solver_max_time_adjustment( if isinstance(solver, type(SolverFactory("gams", solver_io="shell"))): solver.options[options_key].pop() else: - # remove the max time specification introduced. - # All lines are needed here to completely remove the option - # from access through getattr and dictionary reference. delattr(solver.options, options_key) - if options_key in solver.options.keys(): - del solver.options[options_key] class PreformattedLogger(logging.Logger): From 0c8afa56489f3c32d987b75ab65038538d3e9735 Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 17 Mar 2024 18:56:22 -0400 Subject: [PATCH 1413/1797] Update solver test availability and license check --- pyomo/contrib/pyros/tests/test_grcs.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 92532677a80..41223b30899 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -4341,9 +4341,11 @@ def test_separation_terminate_time_limit(self): ) @unittest.skipUnless( - SolverFactory('gams').license_is_valid() - and SolverFactory('baron').license_is_valid(), - "Global NLP solver is not available and licensed.", + ipopt_available + and SolverFactory('gams').license_is_valid() + and SolverFactory('baron').license_is_valid() + and SolverFactory("scip").license_is_valid(), + "IPOPT not available or one of GAMS/BARON/SCIP not licensed", ) def test_pyros_subsolver_time_limit_adjustment(self): """ From ec830e6c19600419a3a187c7c63c9f1700bedfcf Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 17 Mar 2024 19:05:33 -0400 Subject: [PATCH 1414/1797] Move PyROS timer start to before argument validation --- pyomo/contrib/pyros/pyros.py | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index 6de42d7299e..c74daf34c5f 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -330,32 +330,24 @@ 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, - ) - ) - config, state_vars = self._resolve_and_validate_pyros_args(model, **kwds) - - # === Create data containers model_data = ROSolveResults() - model_data.timing = Bunch() - - # === Start timer, run the algorithm model_data.timing = TimingData() with time_code( timing_data_obj=model_data.timing, code_block_name="main", is_main_timer=True, ): - # output intro and disclaimer - self._log_intro(logger=config.progress_logger, level=logging.INFO) - self._log_disclaimer(logger=config.progress_logger, level=logging.INFO) + 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) self._log_config( logger=config.progress_logger, config=config, From 348a896bb77f2ad2634043647303e325edd1e06f Mon Sep 17 00:00:00 2001 From: jasherma Date: Sun, 17 Mar 2024 19:14:24 -0400 Subject: [PATCH 1415/1797] Fix typos --- pyomo/contrib/pyros/util.py | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index fa423e37450..306141e9829 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -269,7 +269,7 @@ def adjust_solver_time_settings(timing_data_obj, solver, config): as there may be no means by which to specify the wall time limit explicitly. (3) For GAMS, we adjust the time limit through the GAMS Reslim - option. However, this may be overriden by any user + option. However, this may be overridden by any user specifications included in a GAMS optfile, which may be difficult to track down. (3) To ensure the time limit is specified to a strictly @@ -287,15 +287,12 @@ def adjust_solver_time_settings(timing_data_obj, solver, config): original_max_time_setting = solver.options["add_options"] custom_setting_present = "add_options" in solver.options - # note: our time limit will be overriden by any + # note: our time limit will be overridden by any # time limits specified by the user through a # GAMS optfile, but tracking down the optfile # and/or the GAMS subsolver specific option # is more difficult - reslim_str = ( - "option reslim=" - f"{max(time_limit_buffer, time_remaining)};" - ) + reslim_str = "option reslim=" f"{max(time_limit_buffer, time_remaining)};" if isinstance(solver.options["add_options"], list): solver.options["add_options"].append(reslim_str) else: @@ -309,7 +306,8 @@ def adjust_solver_time_settings(timing_data_obj, solver, config): # IPOPT 3.14.0+ added support for specifying # wall time limit explicitly; this is preferred # over CPU time limit - "max_wall_time" if solver.version() >= (3, 14, 0, 0) + "max_wall_time" + if solver.version() >= (3, 14, 0, 0) else "max_cpu_time" ) elif isinstance(solver, SolverFactory.get_class("scip")): @@ -332,8 +330,7 @@ def adjust_solver_time_settings(timing_data_obj, solver, config): else original_max_time_setting ) solver.options[options_key] = min( - max(time_limit_buffer, time_remaining), - orig_max_time, + max(time_limit_buffer, time_remaining), orig_max_time ) else: custom_setting_present = False @@ -1814,10 +1811,7 @@ def call_solver(model, solver, config, timing_obj, timer_name, err_msg): try: results = solver.solve( - model, - tee=config.tee, - load_solutions=False, - symbolic_solver_labels=True, + model, tee=config.tee, load_solutions=False, symbolic_solver_labels=True ) except ApplicationError: # account for possible external subsolver errors @@ -1827,9 +1821,7 @@ def call_solver(model, solver, config, timing_obj, timer_name, err_msg): raise else: setattr( - results.solver, - TIC_TOC_SOLVE_TIME_ATTR, - tt_timer.toc(msg=None, delta=True), + results.solver, TIC_TOC_SOLVE_TIME_ATTR, tt_timer.toc(msg=None, delta=True) ) finally: timing_obj.stop_timer(timer_name) From fed3c33dc2626e78d51e6025af9d93751b5e4313 Mon Sep 17 00:00:00 2001 From: robbybp Date: Sun, 17 Mar 2024 20:38:50 -0600 Subject: [PATCH 1416/1797] fix typo --- pyomo/core/expr/visitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/core/expr/visitor.py b/pyomo/core/expr/visitor.py index 7b519e0f63e..2fddca22c5f 100644 --- a/pyomo/core/expr/visitor.py +++ b/pyomo/core/expr/visitor.py @@ -1476,7 +1476,7 @@ def beforeChild(self, parent, child, index): else: variables = self._variables var_set = self._seen - if id(child) not in local_var_set: + if id(child) not in var_set: var_set.add(id(child)) variables.append(child) return False, None From 27ac97ff96f37c4f43c10501c0448d3f42532c67 Mon Sep 17 00:00:00 2001 From: Utkarsh-Detha Date: Mon, 18 Mar 2024 12:25:46 +0100 Subject: [PATCH 1417/1797] Fix: mosek_direct updated to use putqconk instead of putqcon This fix concerns QCQP models when solved using mosek. In MOSEK's Optimizer API, the putqcon method resets the Q matrix entries for all constraints to zero, while putqconk does so only for k-th constraint. The _add_constraints method in mosek_direct would call putqcon, but this would lead to loss of Q info with every subsequent call to the _add_constraints (if new Q info was given). This is now fixed, because the Q matrix in each constraint is updated in its own call to putqconk. --- pyomo/solvers/plugins/solvers/mosek_direct.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pyomo/solvers/plugins/solvers/mosek_direct.py b/pyomo/solvers/plugins/solvers/mosek_direct.py index 5000a2f35c4..f4225651907 100644 --- a/pyomo/solvers/plugins/solvers/mosek_direct.py +++ b/pyomo/solvers/plugins/solvers/mosek_direct.py @@ -492,13 +492,10 @@ def _add_constraints(self, con_seq): ptrb = (0,) + ptre[:-1] asubs = tuple(itertools.chain.from_iterable(l_ids)) avals = tuple(itertools.chain.from_iterable(l_coefs)) - qcsubi = tuple(itertools.chain.from_iterable(q_is)) - qcsubj = tuple(itertools.chain.from_iterable(q_js)) - qcval = tuple(itertools.chain.from_iterable(q_vals)) - qcsubk = tuple(i for i in sub for j in range(len(q_is[i - con_num]))) self._solver_model.appendcons(num_lq) self._solver_model.putarowlist(sub, ptrb, ptre, asubs, avals) - self._solver_model.putqcon(qcsubk, qcsubi, qcsubj, qcval) + for k, i, j, v in zip(sub, q_is, q_js, q_vals): + self._solver_model.putqconk(k, i, j, v) self._solver_model.putconboundlist(sub, bound_types, lbs, ubs) for i, s_n in enumerate(sub_names): self._solver_model.putconname(sub[i], s_n) From dbe0529350c26de25f9acf71657e595ae22d90d9 Mon Sep 17 00:00:00 2001 From: jasherma Date: Mon, 18 Mar 2024 12:57:50 -0400 Subject: [PATCH 1418/1797] Update version number, changelog --- pyomo/contrib/pyros/CHANGELOG.txt | 11 +++++++++++ pyomo/contrib/pyros/pyros.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/CHANGELOG.txt b/pyomo/contrib/pyros/CHANGELOG.txt index 94f4848edb2..52cd7a6db47 100644 --- a/pyomo/contrib/pyros/CHANGELOG.txt +++ b/pyomo/contrib/pyros/CHANGELOG.txt @@ -2,6 +2,17 @@ PyROS CHANGELOG =============== +------------------------------------------------------------------------------- +PyROS 1.2.11 17 Mar 2024 +------------------------------------------------------------------------------- +- Standardize calls to subordinate solvers across all PyROS subproblem types +- Account for user-specified subsolver time limits when automatically + adjusting subsolver time limits +- Add support for automatic adjustment of SCIP subsolver time limit +- Move start point of main PyROS solver timer to just before argument + validation begins + + ------------------------------------------------------------------------------- PyROS 1.2.10 07 Feb 2024 ------------------------------------------------------------------------------- diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index c74daf34c5f..c3335588b7b 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -44,7 +44,7 @@ from datetime import datetime -__version__ = "1.2.10" +__version__ = "1.2.11" default_pyros_solver_logger = setup_pyros_logger() From 927c46c660189526e4728c8c0b37fcf82ae94bce Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 18 Mar 2024 12:25:11 -0600 Subject: [PATCH 1419/1797] Add 'mixed' option to standard form writer --- pyomo/repn/plugins/standard_form.py | 37 ++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/pyomo/repn/plugins/standard_form.py b/pyomo/repn/plugins/standard_form.py index 239cd845930..d0e1014d549 100644 --- a/pyomo/repn/plugins/standard_form.py +++ b/pyomo/repn/plugins/standard_form.py @@ -139,6 +139,15 @@ class LinearStandardFormCompiler(object): description='Add slack variables and return `min cTx s.t. Ax == b`', ), ) + CONFIG.declare( + 'mixed_form', + ConfigValue( + default=False, + domain=bool, + description='Return A in mixed form (the comparison operator is a ' + 'mix of <=, ==, and >=)', + ), + ) CONFIG.declare( 'show_section_timing', ConfigValue( @@ -332,6 +341,9 @@ def write(self, model): # Tabulate constraints # slack_form = self.config.slack_form + mixed_form = self.config.mixed_form + if slack_form and mixed_form: + raise ValueError("cannot specify both slack_form and mixed_form") rows = [] rhs = [] con_data = [] @@ -372,7 +384,30 @@ def write(self, model): f"model contains a trivially infeasible constraint, '{con.name}'" ) - if slack_form: + if mixed_form: + 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 == lb: + rows.append(RowEntry(con, 0)) + rhs.append(ub - offset) + con_data.append(_data) + con_index.append(_index) + con_index_ptr.append(con_index_ptr[-1] + N) + else: + if ub is not None: + rows.append(RowEntry(con, 1)) + rhs.append(ub - offset) + 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(lb - offset) + con_data.append(_data) + con_index.append(_index) + con_index_ptr.append(con_index_ptr[-1] + N) + elif slack_form: _data = list(repn.linear.values()) _index = list(map(var_order.__getitem__, repn.linear)) if lb == ub: # TODO: add tolerance? From 64211e187f5a3daa2d0d1c4c4061aae4866c4b44 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 18 Mar 2024 12:25:37 -0600 Subject: [PATCH 1420/1797] Fix error when removing unused variables --- pyomo/repn/plugins/standard_form.py | 26 ++++++++++++-------------- pyomo/repn/tests/test_standard_form.py | 13 +++++++++++++ 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/pyomo/repn/plugins/standard_form.py b/pyomo/repn/plugins/standard_form.py index d0e1014d549..ea7b6a6a9e6 100644 --- a/pyomo/repn/plugins/standard_form.py +++ b/pyomo/repn/plugins/standard_form.py @@ -472,24 +472,22 @@ def write(self, model): # 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) + active_var_mask = (A_ip[1:] > A_ip[:-1]) | (c_ip[1:] > c_ip[:-1]) + + # Masks on NumPy arrays are very fast. Build the reduced A + # indptr and then check if we actually have to manipulate the + # columns + augmented_mask = np.concatenate((active_var_mask, [True])) + reduced_A_indptr = A.indptr[augmented_mask] + nCol = len(reduced_A_indptr) - 1 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]) + columns = [v for k, v in zip(active_var_mask, columns) if k] c = scipy.sparse.csc_array( - (c.data, c.indices, c.indptr.take(active_var_idx)), [c.shape[0], nCol] + (c.data, c.indices, c.indptr[augmented_mask]), [c.shape[0], nCol] ) - active_var_idx[-1] = A.indptr[-1] + # active_var_idx[-1] = len(columns) A = scipy.sparse.csc_array( - (A.data, A.indices, A.indptr.take(active_var_idx)), [A.shape[0], nCol] + (A.data, A.indices, reduced_A_indptr), [A.shape[0], nCol] ) if self.config.nonnegative_vars: diff --git a/pyomo/repn/tests/test_standard_form.py b/pyomo/repn/tests/test_standard_form.py index e24195edfde..c8b914deca5 100644 --- a/pyomo/repn/tests/test_standard_form.py +++ b/pyomo/repn/tests/test_standard_form.py @@ -43,6 +43,19 @@ def test_linear_model(self): 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_almost_dense_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] + 4 * m.y[3] >= 10) + m.d = pyo.Constraint(expr=5 * m.x + 6 * m.y[1] + 8 * m.y[3] <= 20) + + 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, -4], [5, 6, 8]]))) + self.assertTrue(np.all(repn.rhs == np.array([-10, 20]))) + def test_linear_model_row_col_order(self): m = pyo.ConcreteModel() m.x = pyo.Var() From 4110f005d2f3a7b1bc97e9b5db997853da42c238 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 18 Mar 2024 12:34:00 -0600 Subject: [PATCH 1421/1797] Make LegacySolverWrapper compatible with Pyomo script --- pyomo/contrib/solver/base.py | 110 ++++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 42 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 43d168a98a0..29b2569278c 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -19,9 +19,10 @@ 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 document_kwargs_from_configdict +from pyomo.common.config import document_kwargs_from_configdict, ConfigValue from pyomo.common.errors import ApplicationError from pyomo.common.deprecation import deprecation_warning +from pyomo.common.modeling import NOTSET 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 @@ -347,6 +348,11 @@ class LegacySolverWrapper: interface. Necessary for backwards compatibility. """ + def __init__(self, solver_io=None, **kwargs): + if solver_io is not None: + raise NotImplementedError('Still working on this') + super().__init__(**kwargs) + # # Support "with" statements # @@ -358,51 +364,57 @@ def __exit__(self, t, v, traceback): def _map_config( self, - tee, - 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, - suffixes, - logfile, - keepfiles, - solnfile, - options, + tee=NOTSET, + load_solutions=NOTSET, + symbolic_solver_labels=NOTSET, + timelimit=NOTSET, + report_timing=NOTSET, + raise_exception_on_nonoptimal_result=NOTSET, + solver_io=NOTSET, + suffixes=NOTSET, + logfile=NOTSET, + keepfiles=NOTSET, + solnfile=NOTSET, + options=NOTSET, ): """Map between legacy and new interface configuration options""" self.config = self.config() - self.config.tee = tee - self.config.load_solutions = load_solutions - self.config.symbolic_solver_labels = symbolic_solver_labels - self.config.time_limit = timelimit - self.config.solver_options.set_value(options) + if tee is not NOTSET: + self.config.tee = tee + if load_solutions is not NOTSET: + self.config.load_solutions = load_solutions + if symbolic_solver_labels is not NOTSET: + self.config.symbolic_solver_labels = symbolic_solver_labels + if timelimit is not NOTSET: + self.config.time_limit = timelimit + if report_timing is not NOTSET: + self.config.report_timing = report_timing + if options is not NOTSET: + 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 = ( - raise_exception_on_nonoptimal_result - ) - if solver_io is not None: + if raise_exception_on_nonoptimal_result is not NOTSET: + self.config.raise_exception_on_nonoptimal_result = ( + raise_exception_on_nonoptimal_result + ) + if solver_io is not NOTSET: raise NotImplementedError('Still working on this') - if suffixes is not None: + if suffixes is not NOTSET: raise NotImplementedError('Still working on this') - if logfile is not None: + if logfile is not NOTSET: raise NotImplementedError('Still working on this') 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}`.", + "Use `working_dir` instead to designate a directory in which files " + f"should be generated and saved. Setting `working_dir` to `{cwd}`.", version='6.7.1', ) 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 solnfile is not NOTSET: if 'filename' in self.config: filename = os.path.splitext(solnfile)[0] self.config.filename = filename @@ -504,20 +516,24 @@ def solve( """ 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, - options, + + map_args = ( + 'tee', + 'load_solutions', + 'symbolic_solver_labels', + 'timelimit', + 'report_timing', + 'raise_exception_on_nonoptimal_result', + 'solver_io', + 'suffixes', + 'logfile', + 'keepfiles', + 'solnfile', + 'options', ) + loc = locals() + filtered_args = {k: loc[k] for k in map_args if loc.get(k, None) is not None} + self._map_config(**filtered_args) results: Results = super().solve(model) legacy_results, legacy_soln = self._map_results(model, results) @@ -555,3 +571,13 @@ def license_is_valid(self) -> bool: """ return bool(self.available()) + + def config_block(self, init=False): + from pyomo.scripting.solve_config import default_config_block + + return default_config_block(self, init)[0] + + def set_options(self, options): + opts = {k: v for k, v in options.value().items() if v is not None} + if opts: + self._map_config(**opts) From 62b861a9811021f9c54a6aed4fe73ca841164136 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 18 Mar 2024 12:34:33 -0600 Subject: [PATCH 1422/1797] Make report_timing 'work' in LegactSolverInterface --- pyomo/contrib/solver/base.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 29b2569278c..9e0356c9c21 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -379,6 +379,10 @@ def _map_config( ): """Map between legacy and new interface configuration options""" self.config = self.config() + if 'report_timing' not in self.config: + self.config.declare( + 'report_timing', ConfigValue(domain=bool, default=False) + ) if tee is not NOTSET: self.config.tee = tee if load_solutions is not NOTSET: @@ -537,11 +541,13 @@ def solve( 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 ) + if self.config.report_timing: + print(results.timing_info.timer) + self.config = original_config return legacy_results From c097f03d92a248835365823db52945ef05f3fe93 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 18 Mar 2024 12:37:06 -0600 Subject: [PATCH 1423/1797] Initial draft of a new numpy-based Gurobi Direct interface --- pyomo/contrib/solver/gurobi_direct.py | 349 ++++++++++++++++++++++++++ pyomo/contrib/solver/plugins.py | 6 + 2 files changed, 355 insertions(+) create mode 100644 pyomo/contrib/solver/gurobi_direct.py diff --git a/pyomo/contrib/solver/gurobi_direct.py b/pyomo/contrib/solver/gurobi_direct.py new file mode 100644 index 00000000000..56047b6c2c7 --- /dev/null +++ b/pyomo/contrib/solver/gurobi_direct.py @@ -0,0 +1,349 @@ +# ___________________________________________________________________________ +# +# 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 datetime +import io +import math + +from pyomo.common.config import ConfigValue +from pyomo.common.dependencies import attempt_import +from pyomo.common.shutdown import python_is_shutting_down +from pyomo.common.tee import capture_output, TeeStream +from pyomo.common.timing import HierarchicalTimer + +from pyomo.contrib.solver.base import SolverBase +from pyomo.contrib.solver.config import BranchAndBoundConfig +from pyomo.contrib.solver.results import Results, SolutionStatus, TerminationCondition +from pyomo.contrib.solver.solution import SolutionLoaderBase + +from pyomo.core.staleflag import StaleFlagManager + +from pyomo.repn.plugins.standard_form import LinearStandardFormCompiler + +gurobipy, gurobipy_available = attempt_import('gurobipy') + + +class GurobiConfig(BranchAndBoundConfig): + 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 GurobiDirectSolutionLoader(SolutionLoaderBase): + def __init__(self, grb_model, grb_vars, pyo_vars): + self._grb_model = grb_model + self._grb_vars = grb_vars + self._pyo_vars = pyo_vars + GurobiDirect._num_instances += 1 + + def __del__(self): + if not python_is_shutting_down(): + GurobiDirect._num_instances -= 1 + if GurobiDirect._num_instances == 0: + GurobiDirect.release_license() + + def load_vars(self, vars_to_load=None, solution_number=0): + assert vars_to_load is None + assert solution_number == 0 + for p_var, g_var in zip(self._pyo_vars, self._grb_vars.x.tolist()): + p_var.set_value(g_var, skip_validation=True) + + def get_primals(self, vars_to_load=None): + assert vars_to_load is None + assert solution_number == 0 + return ComponentMap(zip(self._pyo_vars, self._grb_vars.x.tolist())) + + +class GurobiDirect(SolverBase): + CONFIG = GurobiConfig() + + _available = None + _num_instances = 0 + + def __init__(self, **kwds): + super().__init__(**kwds) + GurobiDirect._num_instances += 1 + + 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() + avail = True + except gurobipy.GurobiError: + avail = False + + if avail: + if self._available is None: + self._available = GurobiDirect._check_full_license(m) + return self._available + else: + return self.Availability.BadLicense + + @classmethod + def _check_full_license(cls, model=None): + if model is None: + model = gurobipy.Model() + model.setParam('OutputFlag', 0) + try: + model.addVars(range(2001)) + model.optimize() + return cls.Availability.FullLicense + except gurobipy.GurobiError: + return cls.Availability.LimitedLicense + + def __del__(self): + if not python_is_shutting_down(): + GurobiDirect._num_instances -= 1 + if GurobiDirect._num_instances == 0: + self.release_license() + + @staticmethod + def release_license(): + if gurobipy_available: + with capture_output(capture_fd=True): + gurobipy.disposeDefaultEnv() + + def version(self): + version = ( + gurobipy.GRB.VERSION_MAJOR, + gurobipy.GRB.VERSION_MINOR, + gurobipy.GRB.VERSION_TECHNICAL, + ) + return version + + 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() + if config.timer is None: + config.timer = HierarchicalTimer() + timer = config.timer + + timer.start('compile_model') + repn = LinearStandardFormCompiler().write(model, mixed_form=True) + timer.stop('compile_model') + + timer.start('prepare_matrices') + inf = float('inf') + ninf = -inf + lb = [] + ub = [] + for v in repn.columns: + _l, _u = v.bounds + if _l is None: + _l = ninf + if _u is None: + _u = inf + lb.append(_l) + ub.append(_u) + vtype = [ + ( + gurobipy.GRB.CONTINUOUS + if v.is_continuous() + else ( + gurobipy.GRB.BINARY + if v.is_binary() + else gurobipy.GRB.INTEGER if v.is_integer() else '?' + ) + ) + for v in repn.columns + ] + sense_type = '>=<' + sense = [sense_type[r[1] + 1] for r in repn.rows] + timer.stop('prepare_matrices') + + ostreams = [io.StringIO()] + config.tee + + try: + orig_cwd = os.getcwd() + if self._config.working_directory: + os.chdir(self._config.working_directory) + with TeeStream(*ostreams) as t, capture_output(t.STDOUT, capture_fd=False): + gurobi_model = gurobipy.Model() + + timer.start('transfer_model') + x = gurobi_model.addMVar( + len(repn.columns), + lb=lb, + ub=ub, + obj=repn.c.todense()[0], + vtype=vtype, + ) + A = gurobi_model.addMConstr(repn.A, x, sense, repn.rhs) + # gurobi_model.update() + timer.stop('transfer_model') + + options = config.solver_options + + gurobi_model.setParam('LogToConsole', 1) + + if config.threads is not None: + gurobi_model.setParam('Threads', config.threads) + if config.time_limit is not None: + gurobi_model.setParam('TimeLimit', config.time_limit) + if config.rel_gap is not None: + gurobi_model.setParam('MIPGap', config.rel_gap) + if config.abs_gap is not None: + gurobi_model.setParam('MIPGapAbs', config.abs_gap) + + if config.use_mipstart: + raise MouseTrap("MIPSTART not yet supported") + + for key, option in options.items(): + gurobi_model.setParam(key, option) + + timer.start('optimize') + gurobi_model.optimize() + timer.stop('optimize') + finally: + os.chdir(orig_cwd) + + res = self._postsolve( + timer, GurobiDirectSolutionLoader(gurobi_model, x, repn.columns) + ) + res.solver_configuration = config + res.solver_name = 'Gurobi' + res.solver_version = self.version() + res.solver_log = ostreams[0].getvalue() + + 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 _postsolve(self, timer: HierarchicalTimer, loader): + config = self._config + + gprob = loader._grb_model + grb = gurobipy.GRB + status = gprob.Status + + results = Results() + results.solution_loader = loader + 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 + 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: + results.solution_loader.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 diff --git a/pyomo/contrib/solver/plugins.py b/pyomo/contrib/solver/plugins.py index c7da41463a2..b0beef185de 100644 --- a/pyomo/contrib/solver/plugins.py +++ b/pyomo/contrib/solver/plugins.py @@ -13,6 +13,7 @@ from .factory import SolverFactory from .ipopt import Ipopt from .gurobi import Gurobi +from .gurobi_direct import GurobiDirect def load(): @@ -22,3 +23,8 @@ def load(): SolverFactory.register( name='gurobi', legacy_name='gurobi_v2', doc='New interface to Gurobi' )(Gurobi) + SolverFactory.register( + name='gurobi_direct', + legacy_name='gurobi_direct_v2', + doc='Direct (scipy-based) interface to Gurobi', + )(GurobiDirect) From 3f2b62a2d9f31881f8130882d0d7fe22daa495ad Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 18 Mar 2024 12:35:12 -0600 Subject: [PATCH 1424/1797] Clean up automatic LegacySolverFactory registrations --- pyomo/contrib/solver/factory.py | 4 +++- pyomo/contrib/solver/ipopt.py | 2 -- pyomo/contrib/solver/plugins.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/solver/factory.py b/pyomo/contrib/solver/factory.py index 91ce92a9dee..8861534bd01 100644 --- a/pyomo/contrib/solver/factory.py +++ b/pyomo/contrib/solver/factory.py @@ -27,7 +27,9 @@ def decorator(cls): class LegacySolver(LegacySolverWrapper, cls): pass - LegacySolverFactory.register(legacy_name, doc)(LegacySolver) + LegacySolverFactory.register(legacy_name + " (new interface)", doc)( + LegacySolver + ) return cls diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index edc5799ae20..5f601b7a9f7 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -30,7 +30,6 @@ 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 pyomo.contrib.solver.sol_reader import parse_sol_file from pyomo.contrib.solver.solution import SolSolutionLoader @@ -197,7 +196,6 @@ def get_reduced_costs( } -@SolverFactory.register('ipopt_v2', doc='The ipopt NLP solver (new interface)') class Ipopt(SolverBase): CONFIG = IpoptConfig() diff --git a/pyomo/contrib/solver/plugins.py b/pyomo/contrib/solver/plugins.py index c7da41463a2..1a471d3bd06 100644 --- a/pyomo/contrib/solver/plugins.py +++ b/pyomo/contrib/solver/plugins.py @@ -17,8 +17,8 @@ def load(): SolverFactory.register( - name='ipopt', legacy_name='ipopt_v2', doc='The IPOPT NLP solver (new interface)' + name='ipopt', legacy_name='ipopt_v2', doc='The IPOPT NLP solver' )(Ipopt) SolverFactory.register( - name='gurobi', legacy_name='gurobi_v2', doc='New interface to Gurobi' + name='gurobi', legacy_name='gurobi_v2', doc='Persistent interface to Gurobi' )(Gurobi) From f10ef5654828975d532354178f5fa7f96ac037d8 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 18 Mar 2024 15:22:32 -0600 Subject: [PATCH 1425/1797] Adding missing import --- pyomo/contrib/solver/gurobi_direct.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/contrib/solver/gurobi_direct.py b/pyomo/contrib/solver/gurobi_direct.py index 56047b6c2c7..be06c17b63b 100644 --- a/pyomo/contrib/solver/gurobi_direct.py +++ b/pyomo/contrib/solver/gurobi_direct.py @@ -12,6 +12,7 @@ import datetime import io import math +import os from pyomo.common.config import ConfigValue from pyomo.common.dependencies import attempt_import From 7f0f3004a15baa555f9ae32e95c1ae6fa81b24c2 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 18 Mar 2024 16:05:41 -0600 Subject: [PATCH 1426/1797] Accept / ignore None in certain _map_config arguments --- pyomo/contrib/solver/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 9e0356c9c21..8840265763e 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -401,11 +401,11 @@ def _map_config( self.config.raise_exception_on_nonoptimal_result = ( raise_exception_on_nonoptimal_result ) - if solver_io is not NOTSET: + if solver_io is not NOTSET and solver_io is not None: raise NotImplementedError('Still working on this') - if suffixes is not NOTSET: + if suffixes is not NOTSET and suffixes is not None: raise NotImplementedError('Still working on this') - if logfile is not NOTSET: + if logfile is not NOTSET and logfile is not None: raise NotImplementedError('Still working on this') if keepfiles or 'keepfiles' in self.config: cwd = os.getcwd() From 2e210538774ab647486512f743a765f504a53f05 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 18 Mar 2024 16:06:05 -0600 Subject: [PATCH 1427/1797] Update tests to track changes in the LegacySolverWrapper --- pyomo/contrib/solver/tests/solvers/test_ipopt.py | 2 +- pyomo/contrib/solver/tests/unit/test_base.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/solver/tests/solvers/test_ipopt.py b/pyomo/contrib/solver/tests/solvers/test_ipopt.py index dc6bcf24855..d5d82981ed8 100644 --- a/pyomo/contrib/solver/tests/solvers/test_ipopt.py +++ b/pyomo/contrib/solver/tests/solvers/test_ipopt.py @@ -48,7 +48,7 @@ def test_ipopt_config(self): self.assertIsInstance(config.executable, ExecutableData) # Test custom initialization - solver = SolverFactory('ipopt_v2', executable='/path/to/exe') + solver = SolverFactory('ipopt', executable='/path/to/exe') self.assertFalse(solver.config.tee) self.assertTrue(solver.config.executable.startswith('/path')) diff --git a/pyomo/contrib/solver/tests/unit/test_base.py b/pyomo/contrib/solver/tests/unit/test_base.py index 74c495b86cc..5c138a6522b 100644 --- a/pyomo/contrib/solver/tests/unit/test_base.py +++ b/pyomo/contrib/solver/tests/unit/test_base.py @@ -178,7 +178,7 @@ 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', 'config_block', 'license_is_valid', 'set_options', 'solve'] method_list = [ method for method in dir(base.LegacySolverWrapper) @@ -207,9 +207,7 @@ def test_map_config(self): 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): - print(instance.config.report_timing) + self.assertEqual(instance.config.report_timing, True) # Keepfiles should not be created because we did not declare keepfiles on # the original config with self.assertRaises(AttributeError): From 0465c89d94b58223694fed84334ce603c9d66f15 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 18 Mar 2024 16:09:37 -0600 Subject: [PATCH 1428/1797] bugfix: correct option name --- pyomo/contrib/solver/gurobi_direct.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/solver/gurobi_direct.py b/pyomo/contrib/solver/gurobi_direct.py index be06c17b63b..7b5ec6ed904 100644 --- a/pyomo/contrib/solver/gurobi_direct.py +++ b/pyomo/contrib/solver/gurobi_direct.py @@ -196,8 +196,8 @@ def solve(self, model, **kwds) -> Results: try: orig_cwd = os.getcwd() - if self._config.working_directory: - os.chdir(self._config.working_directory) + if self._config.working_dir: + os.chdir(self._config.working_dir) with TeeStream(*ostreams) as t, capture_output(t.STDOUT, capture_fd=False): gurobi_model = gurobipy.Model() From 9d1b91de17f843b5625cf1292df85db7d19d2985 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 18 Mar 2024 21:27:27 -0600 Subject: [PATCH 1429/1797] NFC: apply black --- pyomo/contrib/solver/tests/unit/test_base.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/solver/tests/unit/test_base.py b/pyomo/contrib/solver/tests/unit/test_base.py index 5c138a6522b..179d9823679 100644 --- a/pyomo/contrib/solver/tests/unit/test_base.py +++ b/pyomo/contrib/solver/tests/unit/test_base.py @@ -178,7 +178,13 @@ def test_context_manager(self): class TestLegacySolverWrapper(unittest.TestCase): def test_class_method_list(self): - expected_list = ['available', 'config_block', 'license_is_valid', 'set_options', 'solve'] + expected_list = [ + 'available', + 'config_block', + 'license_is_valid', + 'set_options', + 'solve', + ] method_list = [ method for method in dir(base.LegacySolverWrapper) From 9fd202e7b4bca74a11befbd422da925015216c60 Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 18 Mar 2024 22:58:58 -0600 Subject: [PATCH 1430/1797] update GDP baselines to reflect change in variable order? --- pyomo/gdp/tests/jobshop_large_hull.lp | 356 +++++++++++++------------- pyomo/gdp/tests/jobshop_small_hull.lp | 68 ++--- 2 files changed, 212 insertions(+), 212 deletions(-) diff --git a/pyomo/gdp/tests/jobshop_large_hull.lp b/pyomo/gdp/tests/jobshop_large_hull.lp index ee8ee0a73d2..f0a9d3ccbf0 100644 --- a/pyomo/gdp/tests/jobshop_large_hull.lp +++ b/pyomo/gdp/tests/jobshop_large_hull.lp @@ -66,17 +66,17 @@ c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(3)_: = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(4)_: -+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(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(5)_: ++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(6)_: +1 t(D) -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(6)_disaggregatedVars__t(D)_ @@ -114,17 +114,17 @@ c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(11)_: = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(12)_: -+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(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(13)_: ++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(14)_: +1 t(F) -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(14)_disaggregatedVars__t(F)_ @@ -150,29 +150,29 @@ c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(17)_: = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(18)_: -+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(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)_: +c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(19)_: +1 t(B) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)_disaggregatedVars__t(B)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)_disaggregatedVars__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(21)_: +c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(20)_: +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(21)_: ++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(22)_: +1 t(D) -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(22)_disaggregatedVars__t(D)_ @@ -186,17 +186,17 @@ c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(23)_: = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(24)_: -+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(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(25)_: ++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(26)_: +1 t(E) -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(26)_disaggregatedVars__t(E)_ @@ -234,17 +234,17 @@ c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(31)_: = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(32)_: -+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(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(33)_: ++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(34)_: +1 t(G) -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(34)_disaggregatedVars__t(G)_ @@ -294,17 +294,17 @@ c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(41)_: = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(42)_: -+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(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(43)_: ++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(44)_: +1 t(F) -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(44)_disaggregatedVars__t(F)_ @@ -342,17 +342,17 @@ c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(49)_: = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(50)_: -+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(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(51)_: ++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(52)_: +1 t(E) -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(52)_disaggregatedVars__t(E)_ @@ -390,17 +390,17 @@ c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(57)_: = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(58)_: -+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(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(59)_: ++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(60)_: +1 t(G) -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(60)_disaggregatedVars__t(G)_ @@ -426,17 +426,17 @@ c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(63)_: = 0 c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(64)_: -+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(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(65)_: ++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(66)_: +1 t(G) -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(66)_disaggregatedVars__t(G)_ @@ -701,34 +701,34 @@ c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)__t(A)_bounds_(ub)_: <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(A)_ +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(C)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(A)_ +6.0 NoClash(A_C_1_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(A)_ +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(4)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(C)_ +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(5)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(A)_ -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(C)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(A)_ +3.0 NoClash(A_C_1_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(A)_ +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(5)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(C)_ +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 @@ -827,34 +827,34 @@ c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(11)__t(A)_bounds_(ub)_: <= 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(A)_ +2.0 NoClash(A_F_1_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(12)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(12)_disaggregatedVars__t(A)_ +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(12)__t(F)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(12)_disaggregatedVars__t(F)_ +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(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(A)_ +3.0 NoClash(A_F_1_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(13)__t(A)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(13)_disaggregatedVars__t(A)_ +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(13)__t(F)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(13)_disaggregatedVars__t(F)_ +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 @@ -923,66 +923,66 @@ c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(17)__t(A)_bounds_(ub)_: <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)_disaggregatedVars__t(B)_ +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)_disaggregatedVars__t(C)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)_disaggregatedVars__t(B)_ +9.0 NoClash(B_C_2_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)_disaggregatedVars__t(B)_ +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(18)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(18)_disaggregatedVars__t(C)_ +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(19)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)_disaggregatedVars__t(B)_ -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)_disaggregatedVars__t(C)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)_disaggregatedVars__t(B)_ -3.0 NoClash(B_C_2_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)_disaggregatedVars__t(B)_ +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(19)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)_disaggregatedVars__t(C)_ +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(20)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)_disaggregatedVars__t(B)_ +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)_disaggregatedVars__t(D)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)_disaggregatedVars__t(B)_ +8.0 NoClash(B_D_2_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)_disaggregatedVars__t(B)_ +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(20)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(20)_disaggregatedVars__t(D)_ +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(21)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)_disaggregatedVars__t(B)_ -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)_disaggregatedVars__t(D)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)_disaggregatedVars__t(B)_ +3.0 NoClash(B_D_2_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)_disaggregatedVars__t(B)_ +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(21)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)_disaggregatedVars__t(D)_ +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 @@ -1019,34 +1019,34 @@ c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(23)__t(B)_bounds_(ub)_: <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)_disaggregatedVars__t(B)_ +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)_disaggregatedVars__t(E)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)_disaggregatedVars__t(B)_ +4.0 NoClash(B_E_2_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)_disaggregatedVars__t(B)_ +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(24)__t(E)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(24)_disaggregatedVars__t(E)_ +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(25)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)_disaggregatedVars__t(B)_ -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)_disaggregatedVars__t(E)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)_disaggregatedVars__t(B)_ +3.0 NoClash(B_E_2_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)_disaggregatedVars__t(B)_ +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(25)__t(E)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)_disaggregatedVars__t(E)_ +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 @@ -1146,34 +1146,34 @@ c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(31)__t(B)_bounds_(ub)_: <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)_disaggregatedVars__t(B)_ +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)_disaggregatedVars__t(G)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)_disaggregatedVars__t(B)_ +8.0 NoClash(B_G_2_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)_disaggregatedVars__t(B)_ +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(32)__t(G)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(32)_disaggregatedVars__t(G)_ +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(33)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)_disaggregatedVars__t(B)_ -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)_disaggregatedVars__t(G)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)_disaggregatedVars__t(B)_ +3.0 NoClash(B_G_2_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)__t(B)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)_disaggregatedVars__t(B)_ +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(33)__t(G)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)_disaggregatedVars__t(G)_ +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 @@ -1306,34 +1306,34 @@ c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(41)__t(C)_bounds_(ub)_: <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)_disaggregatedVars__t(C)_ +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)_disaggregatedVars__t(F)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)_disaggregatedVars__t(C)_ +2.0 NoClash(C_F_1_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)_disaggregatedVars__t(C)_ +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(42)__t(F)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(42)_disaggregatedVars__t(F)_ +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(43)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)_disaggregatedVars__t(C)_ -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)_disaggregatedVars__t(F)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)_disaggregatedVars__t(C)_ +6.0 NoClash(C_F_1_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)_disaggregatedVars__t(C)_ +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(43)__t(F)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)_disaggregatedVars__t(F)_ +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 @@ -1434,34 +1434,34 @@ c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(49)__t(C)_bounds_(ub)_: <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)_disaggregatedVars__t(D)_ +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)_disaggregatedVars__t(E)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)_disaggregatedVars__t(D)_ +4.0 NoClash(D_E_2_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)_disaggregatedVars__t(D)_ +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(50)__t(E)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(50)_disaggregatedVars__t(E)_ +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(51)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)_disaggregatedVars__t(D)_ -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)_disaggregatedVars__t(E)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)_disaggregatedVars__t(D)_ +8.0 NoClash(D_E_2_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)_disaggregatedVars__t(D)_ +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(51)__t(E)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)_disaggregatedVars__t(E)_ +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 @@ -1562,34 +1562,34 @@ c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(57)__t(D)_bounds_(ub)_: <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)_disaggregatedVars__t(D)_ +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)_disaggregatedVars__t(G)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)_disaggregatedVars__t(D)_ +8.0 NoClash(D_G_2_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)_disaggregatedVars__t(D)_ +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(58)__t(G)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(58)_disaggregatedVars__t(G)_ +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(59)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)_disaggregatedVars__t(D)_ -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)_disaggregatedVars__t(G)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)_disaggregatedVars__t(D)_ +8.0 NoClash(D_G_2_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)__t(D)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)_disaggregatedVars__t(D)_ +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(59)__t(G)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)_disaggregatedVars__t(G)_ +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 @@ -1657,34 +1657,34 @@ c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(63)__t(E)_bounds_(ub)_: <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)_disaggregatedVars__t(E)_ +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)_disaggregatedVars__t(G)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)_disaggregatedVars__t(E)_ +8.0 NoClash(E_G_2_0)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)__t(E)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)_disaggregatedVars__t(E)_ +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(64)__t(G)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(64)_disaggregatedVars__t(G)_ +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(65)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)_disaggregatedVars__t(E)_ -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)_disaggregatedVars__t(G)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)_disaggregatedVars__t(E)_ +4.0 NoClash(E_G_2_1)_binary_indicator_var <= 0.0 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)__t(E)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)_disaggregatedVars__t(E)_ +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(65)__t(G)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)_disaggregatedVars__t(G)_ +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 @@ -1769,10 +1769,10 @@ bounds 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(4)_disaggregatedVars__t(A)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(A)_ <= 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 @@ -1785,10 +1785,10 @@ bounds 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(A)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(13)_disaggregatedVars__t(A)_ <= 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(A)_ <= 92 @@ -1797,22 +1797,22 @@ bounds 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(18)_disaggregatedVars__t(B)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(19)_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(20)_disaggregatedVars__t(B)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(21)_disaggregatedVars__t(B)_ <= 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(24)_disaggregatedVars__t(B)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(25)_disaggregatedVars__t(B)_ <= 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 @@ -1825,10 +1825,10 @@ bounds 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(32)_disaggregatedVars__t(B)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(33)_disaggregatedVars__t(B)_ <= 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 @@ -1845,10 +1845,10 @@ bounds 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(41)_disaggregatedVars__t(E)_ <= 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(42)_disaggregatedVars__t(C)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(43)_disaggregatedVars__t(C)_ <= 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 @@ -1861,10 +1861,10 @@ bounds 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(50)_disaggregatedVars__t(D)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(51)_disaggregatedVars__t(D)_ <= 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 @@ -1877,10 +1877,10 @@ bounds 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(57)_disaggregatedVars__t(F)_ <= 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(58)_disaggregatedVars__t(D)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(59)_disaggregatedVars__t(D)_ <= 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 @@ -1889,10 +1889,10 @@ bounds 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(64)_disaggregatedVars__t(E)_ <= 92 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(65)_disaggregatedVars__t(E)_ <= 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 diff --git a/pyomo/gdp/tests/jobshop_small_hull.lp b/pyomo/gdp/tests/jobshop_small_hull.lp index ae2d738d29c..eccaa800600 100644 --- a/pyomo/gdp/tests/jobshop_small_hull.lp +++ b/pyomo/gdp/tests/jobshop_small_hull.lp @@ -34,29 +34,29 @@ c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(1)_: = 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)_ += 0 + +c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(3)_: +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)_: +c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(4)_: +1 t(C) --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(C)_ --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__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(4)_: +c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(5)_: +1 t(B) -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(B)_ -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(B)_ = 0 -c_e__pyomo_gdp_hull_reformulation_disaggregationConstraints(5)_: -+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)_: +1 NoClash(A_B_3_0)_binary_indicator_var +1 NoClash(A_B_3_1)_binary_indicator_var @@ -104,66 +104,66 @@ c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)__t(A)_bounds_(ub)_: <= 0 c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(A)_ +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(C)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(2)_disaggregatedVars__t(A)_ +2.0 NoClash(A_C_1_0)_binary_indicator_var <= 0.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(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(3)_transformedConstraints(c_0_ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(A)_ -1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(C)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_disaggregatedVars__t(A)_ +5.0 NoClash(A_C_1_1)_binary_indicator_var <= 0.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(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(4)_transformedConstraints(c_0_ub)_: --1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(B)_ +1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(C)_ +-1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(B)_ +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)_ +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(4)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(C)_ +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)__t(B)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(B)_ -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(C)_ ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(B)_ +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)_ +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 -c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)__t(C)_bounds_(ub)_: -+1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(C)_ +c_u__pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)__t(B)_bounds_(ub)_: ++1 _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(B)_ -19 NoClash(B_C_2_1)_binary_indicator_var <= 0 @@ -176,14 +176,14 @@ bounds 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(1)_disaggregatedVars__t(B)_ <= 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(2)_disaggregatedVars__t(A)_ <= 19 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(3)_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 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(4)_disaggregatedVars__t(B)_ <= 19 + 0 <= _pyomo_gdp_hull_reformulation_relaxedDisjuncts(5)_disaggregatedVars__t(B)_ <= 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 ae439ad8be5df810443fea62d1d0ba6cffe41feb Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 18 Mar 2024 23:04:07 -0600 Subject: [PATCH 1431/1797] use get_vars_from_components in create_subsystem_block --- pyomo/util/subsystems.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pyomo/util/subsystems.py b/pyomo/util/subsystems.py index 5789829ac54..4a9b96fa89b 100644 --- a/pyomo/util/subsystems.py +++ b/pyomo/util/subsystems.py @@ -14,7 +14,7 @@ from pyomo.core.expr.visitor import identify_variables from pyomo.common.collections import ComponentSet, ComponentMap from pyomo.common.modeling import unique_component_name - +from pyomo.util.vars_from_expressions import get_vars_from_components from pyomo.core.base.constraint import Constraint from pyomo.core.base.expression import Expression from pyomo.core.base.objective import Objective @@ -131,12 +131,12 @@ def create_subsystem_block(constraints, variables=None, include_fixed=False): block.vars = Reference(variables) block.cons = Reference(constraints) var_set = ComponentSet(variables) - input_vars = [] - for con in constraints: - for var in identify_variables(con.expr, include_fixed=include_fixed): - if var not in var_set: - input_vars.append(var) - var_set.add(var) + input_vars = [ + var for var in get_vars_from_components( + block, Constraint, include_fixed=include_fixed + ) + if var not in var_set + ] block.input_vars = Reference(input_vars) add_local_external_functions(block) return block From be31a20946cd24a5a8b58ebff24992ea051427bf Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 18 Mar 2024 23:09:52 -0600 Subject: [PATCH 1432/1797] formatting fix --- pyomo/util/subsystems.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pyomo/util/subsystems.py b/pyomo/util/subsystems.py index 4a9b96fa89b..00c3b85ce47 100644 --- a/pyomo/util/subsystems.py +++ b/pyomo/util/subsystems.py @@ -131,12 +131,10 @@ def create_subsystem_block(constraints, variables=None, include_fixed=False): block.vars = Reference(variables) block.cons = Reference(constraints) var_set = ComponentSet(variables) - input_vars = [ - var for var in get_vars_from_components( - block, Constraint, include_fixed=include_fixed - ) - if var not in var_set - ] + input_vars = [] + for var in get_vars_from_components(block, Constraint, include_fixed=include_fixed): + if var not in var_set: + input_vars.append(var) block.input_vars = Reference(input_vars) add_local_external_functions(block) return block From 08b9d93c3635776948b6145b07e0b03dfde65c87 Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 18 Mar 2024 23:17:39 -0600 Subject: [PATCH 1433/1797] remove unused imports --- pyomo/util/vars_from_expressions.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyomo/util/vars_from_expressions.py b/pyomo/util/vars_from_expressions.py index 62953af456b..878a1a13b58 100644 --- a/pyomo/util/vars_from_expressions.py +++ b/pyomo/util/vars_from_expressions.py @@ -17,8 +17,7 @@ actually in the subtree or not. """ from pyomo.core import Block -from pyomo.core.expr.visitor import _StreamVariableVisitor -from pyomo.core.expr import identify_variables +import pyomo.core.expr as EXPR def get_vars_from_components( @@ -52,7 +51,7 @@ def get_vars_from_components( descend_into=descend_into, descent_order=descent_order, ): - for var in identify_variables( + for var in EXPR.identify_variables( constraint.expr, include_fixed=include_fixed, named_expression_cache=named_expression_cache, From 443826da5ab3485a20439369b167c5e6a363ceff Mon Sep 17 00:00:00 2001 From: robbybp Date: Mon, 18 Mar 2024 23:22:16 -0600 Subject: [PATCH 1434/1797] remove old _VariableVisitor and rename new visitor to _VariableVisitor --- pyomo/core/expr/visitor.py | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/pyomo/core/expr/visitor.py b/pyomo/core/expr/visitor.py index 2fddca22c5f..08015f8b42c 100644 --- a/pyomo/core/expr/visitor.py +++ b/pyomo/core/expr/visitor.py @@ -1373,22 +1373,7 @@ def identify_components(expr, component_types): # ===================================================== -class _VariableVisitor(SimpleExpressionVisitor): - def __init__(self): - self.seen = set() - - def visit(self, node): - if node.__class__ in nonpyomo_leaf_types: - return - - if node.is_variable_type(): - if id(node) in self.seen: - return - self.seen.add(id(node)) - return node - - -class _StreamVariableVisitor(StreamBasedExpressionVisitor): +class _VariableVisitor(StreamBasedExpressionVisitor): def __init__(self, include_fixed=False, named_expression_cache=None): """Visitor that collects all unique variables participating in an expression @@ -1522,7 +1507,7 @@ def identify_variables(expr, include_fixed=True, named_expression_cache=None): """ if named_expression_cache is None: named_expression_cache = {} - visitor = _StreamVariableVisitor( + visitor = _VariableVisitor( named_expression_cache=named_expression_cache, include_fixed=include_fixed ) variables = visitor.walk_expression(expr) From 3e7e7a2ad9559e3ffbfdeab2ca21932d55179d4a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 18 Mar 2024 23:22:17 -0600 Subject: [PATCH 1435/1797] bugfix: update doc not solver name --- pyomo/contrib/solver/factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/solver/factory.py b/pyomo/contrib/solver/factory.py index 8861534bd01..99fbcc3a6d0 100644 --- a/pyomo/contrib/solver/factory.py +++ b/pyomo/contrib/solver/factory.py @@ -27,7 +27,7 @@ def decorator(cls): class LegacySolver(LegacySolverWrapper, cls): pass - LegacySolverFactory.register(legacy_name + " (new interface)", doc)( + LegacySolverFactory.register(legacy_name, doc + " (new interface)")( LegacySolver ) From db5d6dc96181f734f5f3410987ce4c0a1e3a6e7b Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Wed, 20 Mar 2024 10:53:00 +0100 Subject: [PATCH 1436/1797] Fixed maingopy import --- pyomo/contrib/appsi/solvers/maingo.py | 246 +---------------- .../appsi/solvers/maingo_solvermodel.py | 257 ++++++++++++++++++ 2 files changed, 272 insertions(+), 231 deletions(-) create mode 100644 pyomo/contrib/appsi/solvers/maingo_solvermodel.py diff --git a/pyomo/contrib/appsi/solvers/maingo.py b/pyomo/contrib/appsi/solvers/maingo.py index 614e12d227b..29464e6a876 100644 --- a/pyomo/contrib/appsi/solvers/maingo.py +++ b/pyomo/contrib/appsi/solvers/maingo.py @@ -25,7 +25,7 @@ from pyomo.core.base.expression import ScalarExpression from pyomo.core.base.param import _ParamData from pyomo.core.base.sos import _SOSConstraintData -from pyomo.core.base.var import Var, _GeneralVarData +from pyomo.core.base.var import Var, ScalarVar, _GeneralVarData import pyomo.core.expr.expr_common as common import pyomo.core.expr as EXPR from pyomo.core.expr.numvalue import ( @@ -40,7 +40,18 @@ from pyomo.core.staleflag import StaleFlagManager from pyomo.repn.util import valid_expr_ctypes_minlp -_plusMinusOne = {-1, 1} + +def _import_SolverModel(): + try: + from . import maingo_solvermodel + except ImportError: + raise + return maingo_solvermodel + + +maingo_solvermodel, solvermodel_available = attempt_import( + "maingo_solvermodel", importer=_import_SolverModel +) MaingoVar = namedtuple("MaingoVar", "type name lb ub init") @@ -103,234 +114,6 @@ def __init__(self, solver): self.solution_loader = MAiNGOSolutionLoader(solver=solver) -class SolverModel(maingopy.MAiNGOmodel): - def __init__(self, var_list, objective, con_list, idmap): - maingopy.MAiNGOmodel.__init__(self) - self._var_list = var_list - self._con_list = con_list - self._objective = objective - self._idmap = idmap - - def build_maingo_objective(self, obj, visitor): - maingo_obj = visitor.dfs_postorder_stack(obj.expr) - if obj.sense == maximize: - maingo_obj *= -1 - return maingo_obj - - def build_maingo_constraints(self, cons, visitor): - eqs = [] - ineqs = [] - for con in cons: - if con.equality: - eqs += [visitor.dfs_postorder_stack(con.body - con.lower)] - elif con.has_ub() and con.has_lb(): - ineqs += [visitor.dfs_postorder_stack(con.body - con.upper)] - ineqs += [visitor.dfs_postorder_stack(con.lower - con.body)] - elif con.has_ub(): - ineqs += [visitor.dfs_postorder_stack(con.body - con.upper)] - elif con.has_ub(): - ineqs += [visitor.dfs_postorder_stack(con.lower - con.body)] - else: - raise ValueError( - "Constraint does not have a lower " - "or an upper bound: {0} \n".format(con) - ) - return eqs, ineqs - - def get_variables(self): - return [ - maingopy.OptimizationVariable( - maingopy.Bounds(var.lb, var.ub), var.type, var.name - ) - for var in self._var_list - ] - - def get_initial_point(self): - return [ - var.init if not var.init is None else (var.lb + var.ub) / 2.0 - for var in self._var_list - ] - - def evaluate(self, maingo_vars): - visitor = ToMAiNGOVisitor(maingo_vars, self._idmap) - result = maingopy.EvaluationContainer() - result.objective = self.build_maingo_objective(self._objective, visitor) - eqs, ineqs = self.build_maingo_constraints(self._con_list, visitor) - result.eq = eqs - result.ineq = ineqs - return result - - -LEFT_TO_RIGHT = common.OperatorAssociativity.LEFT_TO_RIGHT -RIGHT_TO_LEFT = common.OperatorAssociativity.RIGHT_TO_LEFT - - -class ToMAiNGOVisitor(EXPR.ExpressionValueVisitor): - def __init__(self, variables, idmap): - super(ToMAiNGOVisitor, self).__init__() - self.variables = variables - self.idmap = idmap - self._pyomo_func_to_maingo_func = { - "log": maingopy.log, - "log10": ToMAiNGOVisitor.maingo_log10, - "sin": maingopy.sin, - "cos": maingopy.cos, - "tan": maingopy.tan, - "cosh": maingopy.cosh, - "sinh": maingopy.sinh, - "tanh": maingopy.tanh, - "asin": maingopy.asin, - "acos": maingopy.acos, - "atan": maingopy.atan, - "exp": maingopy.exp, - "sqrt": maingopy.sqrt, - "asinh": ToMAiNGOVisitor.maingo_asinh, - "acosh": ToMAiNGOVisitor.maingo_acosh, - "atanh": ToMAiNGOVisitor.maingo_atanh, - } - - @classmethod - def maingo_log10(cls, x): - return maingopy.log(x) / math.log(10) - - @classmethod - def maingo_asinh(cls, x): - return maingopy.log(x + maingopy.sqrt(maingopy.pow(x, 2) + 1)) - - @classmethod - def maingo_acosh(cls, x): - return maingopy.log(x + maingopy.sqrt(maingopy.pow(x, 2) - 1)) - - @classmethod - def maingo_atanh(cls, x): - return 0.5 * maingopy.log(x + 1) - 0.5 * maingopy.log(1 - x) - - def visit(self, node, values): - """Visit nodes that have been expanded""" - for i, val in enumerate(values): - arg = node._args_[i] - - if arg is None: - values[i] = "Undefined" - elif arg.__class__ in native_numeric_types: - pass - elif arg.__class__ in nonpyomo_leaf_types: - values[i] = val - else: - parens = False - if arg.is_expression_type() and node.PRECEDENCE is not None: - if arg.PRECEDENCE is None: - pass - elif node.PRECEDENCE < arg.PRECEDENCE: - parens = True - elif node.PRECEDENCE == arg.PRECEDENCE: - if i == 0: - parens = node.ASSOCIATIVITY != LEFT_TO_RIGHT - elif i == len(node._args_) - 1: - parens = node.ASSOCIATIVITY != RIGHT_TO_LEFT - else: - parens = True - if parens: - values[i] = val - - if node.__class__ in EXPR.NPV_expression_types: - return value(node) - - if node.__class__ in {EXPR.ProductExpression, EXPR.MonomialTermExpression}: - return values[0] * values[1] - - if node.__class__ in {EXPR.SumExpression}: - return sum(values) - - if node.__class__ in {EXPR.PowExpression}: - return maingopy.pow(values[0], values[1]) - - if node.__class__ in {EXPR.DivisionExpression}: - return values[0] / values[1] - - if node.__class__ in {EXPR.NegationExpression}: - return -values[0] - - if node.__class__ in {EXPR.AbsExpression}: - return maingopy.abs(values[0]) - - if node.__class__ in {EXPR.UnaryFunctionExpression}: - pyomo_func = node.getname() - maingo_func = self._pyomo_func_to_maingo_func[pyomo_func] - return maingo_func(values[0]) - - if node.__class__ in {ScalarExpression}: - return values[0] - - raise ValueError(f"Unknown function expression encountered: {node.getname()}") - - def visiting_potential_leaf(self, node): - """ - Visiting a potential leaf. - - Return True if the node is not expanded. - """ - if node.__class__ in native_types: - return True, node - - if node.is_expression_type(): - if node.__class__ is EXPR.MonomialTermExpression: - return True, self._monomial_to_maingo(node) - if node.__class__ is EXPR.LinearExpression: - return True, self._linear_to_maingo(node) - return False, None - - if node.is_component_type(): - if node.ctype not in valid_expr_ctypes_minlp: - # Make sure all components in active constraints - # are basic ctypes we know how to deal with. - raise RuntimeError( - "Unallowable component '%s' of type %s found in an active " - "constraint or objective.\nMAiNGO cannot export " - "expressions with this component type." - % (node.name, node.ctype.__name__) - ) - - if node.is_fixed(): - return True, node() - else: - assert node.is_variable_type() - maingo_var_id = self.idmap[id(node)] - maingo_var = self.variables[maingo_var_id] - return True, maingo_var - - def _monomial_to_maingo(self, node): - const, var = node.args - maingo_var_id = self.idmap[id(var)] - maingo_var = self.variables[maingo_var_id] - if const.__class__ not in native_types: - const = value(const) - if var.is_fixed(): - return const * var.value - if not const: - return 0 - if const in _plusMinusOne: - if const < 0: - return -maingo_var - else: - return maingo_var - return const * maingo_var - - def _linear_to_maingo(self, node): - values = [ - ( - self._monomial_to_maingo(arg) - if ( - arg.__class__ is EXPR.MonomialTermExpression - and not arg.arg(1).is_fixed() - ) - else value(arg) - ) - for arg in node.args - ] - return sum(values) - - class MAiNGO(PersistentBase, PersistentSolver): """ Interface to MAiNGO @@ -536,7 +319,8 @@ def set_instance(self, model): self._labeler = NumericLabeler("x") self.add_block(model) - self._solver_model = SolverModel( + + self._solver_model = maingo_solvermodel.SolverModel( var_list=self._maingo_vars, con_list=self._cons, objective=self._objective, diff --git a/pyomo/contrib/appsi/solvers/maingo_solvermodel.py b/pyomo/contrib/appsi/solvers/maingo_solvermodel.py new file mode 100644 index 00000000000..4abc53ae290 --- /dev/null +++ b/pyomo/contrib/appsi/solvers/maingo_solvermodel.py @@ -0,0 +1,257 @@ +import math + +from pyomo.common.dependencies import attempt_import +from pyomo.core.base.var import ScalarVar +import pyomo.core.expr.expr_common as common +import pyomo.core.expr as EXPR +from pyomo.core.expr.numvalue import ( + value, + is_constant, + is_fixed, + native_numeric_types, + native_types, + nonpyomo_leaf_types, +) +from pyomo.core.kernel.objective import minimize, maximize +from pyomo.repn.util import valid_expr_ctypes_minlp + + +def _import_maingopy(): + try: + import maingopy + except ImportError: + raise + return maingopy + + +maingopy, maingopy_available = attempt_import("maingopy", importer=_import_maingopy) + +_plusMinusOne = {1, -1} + +LEFT_TO_RIGHT = common.OperatorAssociativity.LEFT_TO_RIGHT +RIGHT_TO_LEFT = common.OperatorAssociativity.RIGHT_TO_LEFT + + +class ToMAiNGOVisitor(EXPR.ExpressionValueVisitor): + def __init__(self, variables, idmap): + super(ToMAiNGOVisitor, self).__init__() + self.variables = variables + self.idmap = idmap + self._pyomo_func_to_maingo_func = { + "log": maingopy.log, + "log10": ToMAiNGOVisitor.maingo_log10, + "sin": maingopy.sin, + "cos": maingopy.cos, + "tan": maingopy.tan, + "cosh": maingopy.cosh, + "sinh": maingopy.sinh, + "tanh": maingopy.tanh, + "asin": maingopy.asin, + "acos": maingopy.acos, + "atan": maingopy.atan, + "exp": maingopy.exp, + "sqrt": maingopy.sqrt, + "asinh": ToMAiNGOVisitor.maingo_asinh, + "acosh": ToMAiNGOVisitor.maingo_acosh, + "atanh": ToMAiNGOVisitor.maingo_atanh, + } + + @classmethod + def maingo_log10(cls, x): + return maingopy.log(x) / math.log(10) + + @classmethod + def maingo_asinh(cls, x): + return maingopy.log(x + maingopy.sqrt(maingopy.pow(x, 2) + 1)) + + @classmethod + def maingo_acosh(cls, x): + return maingopy.log(x + maingopy.sqrt(maingopy.pow(x, 2) - 1)) + + @classmethod + def maingo_atanh(cls, x): + return 0.5 * maingopy.log(x + 1) - 0.5 * maingopy.log(1 - x) + + def visit(self, node, values): + """Visit nodes that have been expanded""" + for i, val in enumerate(values): + arg = node._args_[i] + + if arg is None: + values[i] = "Undefined" + elif arg.__class__ in native_numeric_types: + pass + elif arg.__class__ in nonpyomo_leaf_types: + values[i] = val + else: + parens = False + if arg.is_expression_type() and node.PRECEDENCE is not None: + if arg.PRECEDENCE is None: + pass + elif node.PRECEDENCE < arg.PRECEDENCE: + parens = True + elif node.PRECEDENCE == arg.PRECEDENCE: + if i == 0: + parens = node.ASSOCIATIVITY != LEFT_TO_RIGHT + elif i == len(node._args_) - 1: + parens = node.ASSOCIATIVITY != RIGHT_TO_LEFT + else: + parens = True + if parens: + values[i] = val + + if node.__class__ in EXPR.NPV_expression_types: + return value(node) + + if node.__class__ in {EXPR.ProductExpression, EXPR.MonomialTermExpression}: + return values[0] * values[1] + + if node.__class__ in {EXPR.SumExpression}: + return sum(values) + + if node.__class__ in {EXPR.PowExpression}: + return maingopy.pow(values[0], values[1]) + + if node.__class__ in {EXPR.DivisionExpression}: + return values[0] / values[1] + + if node.__class__ in {EXPR.NegationExpression}: + return -values[0] + + if node.__class__ in {EXPR.AbsExpression}: + return maingopy.abs(values[0]) + + if node.__class__ in {EXPR.UnaryFunctionExpression}: + pyomo_func = node.getname() + maingo_func = self._pyomo_func_to_maingo_func[pyomo_func] + return maingo_func(values[0]) + + if node.__class__ in {ScalarExpression}: + return values[0] + + raise ValueError(f"Unknown function expression encountered: {node.getname()}") + + def visiting_potential_leaf(self, node): + """ + Visiting a potential leaf. + + Return True if the node is not expanded. + """ + if node.__class__ in native_types: + return True, node + + if node.is_expression_type(): + if node.__class__ is EXPR.MonomialTermExpression: + return True, self._monomial_to_maingo(node) + if node.__class__ is EXPR.LinearExpression: + return True, self._linear_to_maingo(node) + return False, None + + if node.is_component_type(): + if node.ctype not in valid_expr_ctypes_minlp: + # Make sure all components in active constraints + # are basic ctypes we know how to deal with. + raise RuntimeError( + "Unallowable component '%s' of type %s found in an active " + "constraint or objective.\nMAiNGO cannot export " + "expressions with this component type." + % (node.name, node.ctype.__name__) + ) + + if node.is_fixed(): + return True, node() + else: + assert node.is_variable_type() + maingo_var_id = self.idmap[id(node)] + maingo_var = self.variables[maingo_var_id] + return True, maingo_var + + def _monomial_to_maingo(self, node): + if node.__class__ is ScalarVar: + var = node + const = 1 + else: + const, var = node.args + maingo_var_id = self.idmap[id(var)] + maingo_var = self.variables[maingo_var_id] + if const.__class__ not in native_types: + const = value(const) + if var.is_fixed(): + return const * var.value + if not const: + return 0 + if const in _plusMinusOne: + if const < 0: + return -maingo_var + else: + return maingo_var + return const * maingo_var + + def _linear_to_maingo(self, node): + values = [ + ( + self._monomial_to_maingo(arg) + if (arg.__class__ in {EXPR.MonomialTermExpression, ScalarVar}) + else (value(arg)) + ) + for arg in node.args + ] + return sum(values) + + +class SolverModel(maingopy.MAiNGOmodel): + def __init__(self, var_list, objective, con_list, idmap): + maingopy.MAiNGOmodel.__init__(self) + self._var_list = var_list + self._con_list = con_list + self._objective = objective + self._idmap = idmap + + def build_maingo_objective(self, obj, visitor): + maingo_obj = visitor.dfs_postorder_stack(obj.expr) + if obj.sense == maximize: + maingo_obj *= -1 + return maingo_obj + + def build_maingo_constraints(self, cons, visitor): + eqs = [] + ineqs = [] + for con in cons: + if con.equality: + eqs += [visitor.dfs_postorder_stack(con.body - con.lower)] + elif con.has_ub() and con.has_lb(): + ineqs += [visitor.dfs_postorder_stack(con.body - con.upper)] + ineqs += [visitor.dfs_postorder_stack(con.lower - con.body)] + elif con.has_ub(): + ineqs += [visitor.dfs_postorder_stack(con.body - con.upper)] + elif con.has_ub(): + ineqs += [visitor.dfs_postorder_stack(con.lower - con.body)] + else: + raise ValueError( + "Constraint does not have a lower " + "or an upper bound: {0} \n".format(con) + ) + return eqs, ineqs + + def get_variables(self): + return [ + maingopy.OptimizationVariable( + maingopy.Bounds(var.lb, var.ub), var.type, var.name + ) + for var in self._var_list + ] + + def get_initial_point(self): + return [ + var.init if not var.init is None else (var.lb + var.ub) / 2.0 + for var in self._var_list + ] + + def evaluate(self, maingo_vars): + visitor = ToMAiNGOVisitor(maingo_vars, self._idmap) + result = maingopy.EvaluationContainer() + result.objective = self.build_maingo_objective(self._objective, visitor) + eqs, ineqs = self.build_maingo_constraints(self._con_list, visitor) + result.eq = eqs + result.ineq = ineqs + return result From 56b513dcf2ec852ce993400d3245b58cb46c5fc4 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 15:38:29 -0600 Subject: [PATCH 1437/1797] add tests for mixed standard form --- pyomo/repn/tests/test_standard_form.py | 40 ++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/pyomo/repn/tests/test_standard_form.py b/pyomo/repn/tests/test_standard_form.py index c8b914deca5..9dee2b1d25d 100644 --- a/pyomo/repn/tests/test_standard_form.py +++ b/pyomo/repn/tests/test_standard_form.py @@ -42,6 +42,8 @@ def test_linear_model(self): 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]))) + self.assertEqual(repn.rows, [(m.c, -1), (m.d, 1)]) + self.assertEqual(repn.columns, [m.x, m.y[1], m.y[3]]) def test_almost_dense_linear_model(self): m = pyo.ConcreteModel() @@ -55,6 +57,8 @@ def test_almost_dense_linear_model(self): self.assertTrue(np.all(repn.c == np.array([0, 0, 0]))) self.assertTrue(np.all(repn.A == np.array([[-1, -2, -4], [5, 6, 8]]))) self.assertTrue(np.all(repn.rhs == np.array([-10, 20]))) + self.assertEqual(repn.rows, [(m.c, -1), (m.d, 1)]) + self.assertEqual(repn.columns, [m.x, m.y[1], m.y[3]]) def test_linear_model_row_col_order(self): m = pyo.ConcreteModel() @@ -70,6 +74,8 @@ def test_linear_model_row_col_order(self): 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]))) + self.assertEqual(repn.rows, [(m.d, 1), (m.c, -1)]) + self.assertEqual(repn.columns, [m.y[3], m.x, m.y[1]]) def test_suffix_warning(self): m = pyo.ConcreteModel() @@ -235,6 +241,40 @@ def test_alternative_forms(self): ) self._verify_solution(soln, repn, True) + repn = LinearStandardFormCompiler().write( + m, mixed_form=True, column_order=col_order + ) + + self.assertEqual(repn.rows, [(m.c, -1), (m.d, 1), (m.e, 1), (m.e, -1), (m.f, 0)]) + self.assertEqual( + list(map(str, repn.x)), + ['x', 'y[0]', 'y[1]', 'y[3]'], + ) + self.assertEqual( + list(v.bounds for v in repn.x), + [(None, None), (0, 10), (-5, 10), (-5, -2)], + ) + ref = np.array( + [ + [1, 0, 2, 0], + [0, 0, 1, 4], + [0, 1, 6, 0], + [0, 1, 6, 0], + [1, 1, 0, 0], + ] + ) + self.assertTrue(np.all(repn.A == ref)) + print(repn) + print(repn.b) + self.assertTrue(np.all(repn.b == np.array([3, 5, 6, -3, 8]))) + self.assertTrue( + np.all( + repn.c == np.array([[-1, 0, -5, 0], [1, 0, 0, 15]]) + ) + ) + # Note that the solution is a mix of inequality and equality constraints + # self._verify_solution(soln, repn, False) + repn = LinearStandardFormCompiler().write( m, slack_form=True, nonnegative_vars=True, column_order=col_order ) From a0b9a927e4997b767b267bba19d3598d561c2b8b Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:20:15 -0600 Subject: [PATCH 1438/1797] Renamed _ArcData -> ArcData --- pyomo/core/base/component.py | 2 +- pyomo/network/arc.py | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/pyomo/core/base/component.py b/pyomo/core/base/component.py index 22c2bc4b804..c91167379fd 100644 --- a/pyomo/core/base/component.py +++ b/pyomo/core/base/component.py @@ -806,7 +806,7 @@ class ComponentData(_ComponentBase): # _GeneralExpressionData, _LogicalConstraintData, # _GeneralLogicalConstraintData, _GeneralObjectiveData, # _ParamData,_GeneralVarData, _GeneralBooleanVarData, _DisjunctionData, - # _ArcData, _PortData, _LinearConstraintData, and + # ArcData, _PortData, _LinearConstraintData, and # _LinearMatrixConstraintData. Changes made here need to be made in those # constructors as well! def __init__(self, component): diff --git a/pyomo/network/arc.py b/pyomo/network/arc.py index 42b7c6ea075..5e68f181a38 100644 --- a/pyomo/network/arc.py +++ b/pyomo/network/arc.py @@ -52,7 +52,7 @@ def _iterable_to_dict(vals, directed, name): return vals -class _ArcData(ActiveComponentData): +class ArcData(ActiveComponentData): """ This class defines the data for a single Arc @@ -246,6 +246,11 @@ def _validate_ports(self, source, destination, ports): ) +class _ArcData(metaclass=RenamedClass): + __renamed__new_class__ = ArcData + __renamed__version__ = '6.7.2.dev0' + + @ModelComponentFactory.register("Component used for connecting two Ports.") class Arc(ActiveIndexedComponent): """ @@ -267,7 +272,7 @@ class Arc(ActiveIndexedComponent): or a two-member iterable of ports """ - _ComponentDataClass = _ArcData + _ComponentDataClass = ArcData def __new__(cls, *args, **kwds): if cls != Arc: @@ -373,9 +378,9 @@ def _pprint(self): ) -class ScalarArc(_ArcData, Arc): +class ScalarArc(ArcData, Arc): def __init__(self, *args, **kwds): - _ArcData.__init__(self, self) + ArcData.__init__(self, self) Arc.__init__(self, *args, **kwds) self.index = UnindexedComponent_index From 2f3e94039bfffac5da785978b0c7bf8c6aa20c9e Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:31:31 -0600 Subject: [PATCH 1439/1797] Renamed _BlockData -> BlockData --- pyomo/contrib/appsi/base.py | 12 ++-- pyomo/contrib/appsi/fbbt.py | 6 +- pyomo/contrib/appsi/solvers/cbc.py | 6 +- pyomo/contrib/appsi/solvers/cplex.py | 6 +- pyomo/contrib/appsi/solvers/ipopt.py | 6 +- pyomo/contrib/appsi/solvers/wntr.py | 4 +- pyomo/contrib/appsi/writers/lp_writer.py | 4 +- pyomo/contrib/appsi/writers/nl_writer.py | 4 +- pyomo/contrib/benders/benders_cuts.py | 6 +- pyomo/contrib/cp/interval_var.py | 6 +- .../logical_to_disjunctive_program.py | 4 +- pyomo/contrib/incidence_analysis/interface.py | 6 +- .../contrib/incidence_analysis/scc_solver.py | 4 +- .../tests/test_interface.py | 2 +- pyomo/contrib/latex_printer/latex_printer.py | 6 +- .../piecewise/piecewise_linear_function.py | 6 +- .../piecewise_to_gdp_transformation.py | 4 +- .../pynumero/interfaces/external_grey_box.py | 6 +- pyomo/contrib/solver/base.py | 14 ++-- pyomo/contrib/viewer/report.py | 2 +- pyomo/core/base/block.py | 65 ++++++++++--------- pyomo/core/base/piecewise.py | 6 +- .../plugins/transform/logical_to_linear.py | 4 +- pyomo/core/tests/unit/test_block.py | 14 ++-- pyomo/core/tests/unit/test_component.py | 8 +-- pyomo/core/tests/unit/test_indexed_slice.py | 4 +- pyomo/core/tests/unit/test_suffix.py | 4 +- pyomo/dae/flatten.py | 8 +-- pyomo/gdp/disjunct.py | 8 +-- pyomo/gdp/plugins/gdp_var_mover.py | 2 +- pyomo/gdp/tests/common_tests.py | 10 +-- pyomo/gdp/transformed_disjunct.py | 6 +- pyomo/gdp/util.py | 8 +-- pyomo/mpec/complementarity.py | 4 +- pyomo/opt/base/solvers.py | 8 +-- pyomo/repn/util.py | 4 +- .../solvers/direct_or_persistent_solver.py | 4 +- .../solvers/plugins/solvers/direct_solver.py | 8 +-- pyomo/solvers/plugins/solvers/mosek_direct.py | 2 +- .../plugins/solvers/persistent_solver.py | 8 +-- pyomo/util/report_scaling.py | 8 +-- pyomo/util/slices.py | 2 +- 42 files changed, 156 insertions(+), 153 deletions(-) diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index 201e5975ac9..b4ade16a597 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -25,7 +25,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, Block from pyomo.core.base.objective import _GeneralObjectiveData from pyomo.common.collections import ComponentMap from .utils.get_objective import get_objective @@ -621,13 +621,13 @@ def __str__(self): return self.name @abc.abstractmethod - def solve(self, model: _BlockData, timer: HierarchicalTimer = None) -> Results: + def solve(self, model: BlockData, timer: HierarchicalTimer = None) -> Results: """ Solve a Pyomo model. Parameters ---------- - model: _BlockData + model: BlockData The Pyomo model to be solved timer: HierarchicalTimer An option timer for reporting timing @@ -811,7 +811,7 @@ def add_constraints(self, cons: List[_GeneralConstraintData]): pass @abc.abstractmethod - def add_block(self, block: _BlockData): + def add_block(self, block: BlockData): pass @abc.abstractmethod @@ -827,7 +827,7 @@ def remove_constraints(self, cons: List[_GeneralConstraintData]): pass @abc.abstractmethod - def remove_block(self, block: _BlockData): + def remove_block(self, block: BlockData): pass @abc.abstractmethod @@ -1529,7 +1529,7 @@ def update(self, timer: HierarchicalTimer = None): class LegacySolverInterface(object): def solve( self, - model: _BlockData, + model: BlockData, tee: bool = False, load_solutions: bool = True, logfile: Optional[str] = None, diff --git a/pyomo/contrib/appsi/fbbt.py b/pyomo/contrib/appsi/fbbt.py index 8b6cc52d2aa..c6bbdb5bf3b 100644 --- a/pyomo/contrib/appsi/fbbt.py +++ b/pyomo/contrib/appsi/fbbt.py @@ -23,7 +23,7 @@ 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.block import _BlockData +from pyomo.core.base.block import BlockData from pyomo.core.base import SymbolMap, TextLabeler from pyomo.common.errors import InfeasibleConstraintException @@ -275,7 +275,7 @@ def _deactivate_satisfied_cons(self): c.deactivate() def perform_fbbt( - self, model: _BlockData, symbolic_solver_labels: Optional[bool] = None + self, model: BlockData, symbolic_solver_labels: Optional[bool] = None ): if model is not self._model: self.set_instance(model, symbolic_solver_labels=symbolic_solver_labels) @@ -304,7 +304,7 @@ def perform_fbbt( self._deactivate_satisfied_cons() return n_iter - def perform_fbbt_with_seed(self, model: _BlockData, seed_var: _GeneralVarData): + def perform_fbbt_with_seed(self, model: BlockData, seed_var: _GeneralVarData): if model is not self._model: self.set_instance(model) else: diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index 7f04ffbfce7..57bbf1b4c21 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -28,7 +28,7 @@ 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 +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 @@ -173,7 +173,7 @@ def add_params(self, params: List[_ParamData]): def add_constraints(self, cons: List[_GeneralConstraintData]): self._writer.add_constraints(cons) - def add_block(self, block: _BlockData): + def add_block(self, block: BlockData): self._writer.add_block(block) def remove_variables(self, variables: List[_GeneralVarData]): @@ -185,7 +185,7 @@ def remove_params(self, params: List[_ParamData]): def remove_constraints(self, cons: List[_GeneralConstraintData]): self._writer.remove_constraints(cons) - def remove_block(self, block: _BlockData): + def remove_block(self, block: BlockData): self._writer.remove_block(block) def set_objective(self, obj: _GeneralObjectiveData): diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index 1b7ab5000d2..2e04a979fda 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -24,7 +24,7 @@ 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.block import BlockData from pyomo.core.base.param import _ParamData from pyomo.core.base.objective import _GeneralObjectiveData from pyomo.common.timing import HierarchicalTimer @@ -188,7 +188,7 @@ def add_params(self, params: List[_ParamData]): def add_constraints(self, cons: List[_GeneralConstraintData]): self._writer.add_constraints(cons) - def add_block(self, block: _BlockData): + def add_block(self, block: BlockData): self._writer.add_block(block) def remove_variables(self, variables: List[_GeneralVarData]): @@ -200,7 +200,7 @@ def remove_params(self, params: List[_ParamData]): def remove_constraints(self, cons: List[_GeneralConstraintData]): self._writer.remove_constraints(cons) - def remove_block(self, block: _BlockData): + def remove_block(self, block: BlockData): self._writer.remove_block(block) def set_objective(self, obj: _GeneralObjectiveData): diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index 54e21d333e5..19ec5f8031c 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -30,7 +30,7 @@ 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 +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 @@ -237,7 +237,7 @@ def add_params(self, params: List[_ParamData]): def add_constraints(self, cons: List[_GeneralConstraintData]): self._writer.add_constraints(cons) - def add_block(self, block: _BlockData): + def add_block(self, block: BlockData): self._writer.add_block(block) def remove_variables(self, variables: List[_GeneralVarData]): @@ -249,7 +249,7 @@ def remove_params(self, params: List[_ParamData]): def remove_constraints(self, cons: List[_GeneralConstraintData]): self._writer.remove_constraints(cons) - def remove_block(self, block: _BlockData): + def remove_block(self, block: BlockData): self._writer.remove_block(block) def set_objective(self, obj: _GeneralObjectiveData): diff --git a/pyomo/contrib/appsi/solvers/wntr.py b/pyomo/contrib/appsi/solvers/wntr.py index 00c0598c687..928eda2b514 100644 --- a/pyomo/contrib/appsi/solvers/wntr.py +++ b/pyomo/contrib/appsi/solvers/wntr.py @@ -39,7 +39,7 @@ 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.block import BlockData from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.param import _ParamData from pyomo.core.base.constraint import _GeneralConstraintData @@ -178,7 +178,7 @@ def _solve(self, timer: HierarchicalTimer): ) return results - def solve(self, model: _BlockData, timer: HierarchicalTimer = None) -> 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() diff --git a/pyomo/contrib/appsi/writers/lp_writer.py b/pyomo/contrib/appsi/writers/lp_writer.py index 9984cb7465d..518be5fac99 100644 --- a/pyomo/contrib/appsi/writers/lp_writer.py +++ b/pyomo/contrib/appsi/writers/lp_writer.py @@ -15,7 +15,7 @@ from pyomo.core.base.constraint import _GeneralConstraintData from pyomo.core.base.objective import _GeneralObjectiveData from pyomo.core.base.sos import _SOSConstraintData -from pyomo.core.base.block import _BlockData +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 @@ -167,7 +167,7 @@ def _set_objective(self, obj: _GeneralObjectiveData): cobj.name = cname self._writer.objective = cobj - def write(self, model: _BlockData, filename: str, timer: HierarchicalTimer = None): + def write(self, model: BlockData, filename: str, timer: HierarchicalTimer = None): if timer is None: timer = HierarchicalTimer() if model is not self._model: diff --git a/pyomo/contrib/appsi/writers/nl_writer.py b/pyomo/contrib/appsi/writers/nl_writer.py index bd24a86216a..75b026ab521 100644 --- a/pyomo/contrib/appsi/writers/nl_writer.py +++ b/pyomo/contrib/appsi/writers/nl_writer.py @@ -15,7 +15,7 @@ from pyomo.core.base.constraint import _GeneralConstraintData from pyomo.core.base.objective import _GeneralObjectiveData from pyomo.core.base.sos import _SOSConstraintData -from pyomo.core.base.block import _BlockData +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 @@ -232,7 +232,7 @@ def _set_objective(self, obj: _GeneralObjectiveData): cobj.sense = sense self._writer.objective = cobj - def write(self, model: _BlockData, filename: str, timer: HierarchicalTimer = None): + def write(self, model: BlockData, filename: str, timer: HierarchicalTimer = None): if timer is None: timer = HierarchicalTimer() if model is not self._model: diff --git a/pyomo/contrib/benders/benders_cuts.py b/pyomo/contrib/benders/benders_cuts.py index cf96ba26164..0653be55986 100644 --- a/pyomo/contrib/benders/benders_cuts.py +++ b/pyomo/contrib/benders/benders_cuts.py @@ -9,7 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from pyomo.core.base.block import _BlockData, declare_custom_block +from pyomo.core.base.block import BlockData, declare_custom_block import pyomo.environ as pyo from pyomo.solvers.plugins.solvers.persistent_solver import PersistentSolver from pyomo.core.expr.visitor import identify_variables @@ -166,13 +166,13 @@ def _setup_subproblem(b, root_vars, relax_subproblem_cons): @declare_custom_block(name='BendersCutGenerator') -class BendersCutGeneratorData(_BlockData): +class BendersCutGeneratorData(BlockData): def __init__(self, component): if not mpi4py_available: raise ImportError('BendersCutGenerator requires mpi4py.') if not numpy_available: raise ImportError('BendersCutGenerator requires numpy.') - _BlockData.__init__(self, component) + BlockData.__init__(self, component) self.num_subproblems_by_rank = 0 # np.zeros(self.comm.Get_size()) self.subproblems = list() diff --git a/pyomo/contrib/cp/interval_var.py b/pyomo/contrib/cp/interval_var.py index 953b859ea20..ff11d6e3a9f 100644 --- a/pyomo/contrib/cp/interval_var.py +++ b/pyomo/contrib/cp/interval_var.py @@ -18,7 +18,7 @@ from pyomo.core import Integers, value from pyomo.core.base import Any, ScalarVar, ScalarBooleanVar -from pyomo.core.base.block import _BlockData, Block +from pyomo.core.base.block import BlockData, Block from pyomo.core.base.component import ModelComponentFactory from pyomo.core.base.global_set import UnindexedComponent_index from pyomo.core.base.indexed_component import IndexedComponent, UnindexedComponent_set @@ -87,14 +87,14 @@ def get_associated_interval_var(self): return self.parent_block() -class IntervalVarData(_BlockData): +class IntervalVarData(BlockData): """This class defines the abstract interface for a single interval variable.""" # We will put our four variables on this, and everything else is off limits. _Block_reserved_words = Any def __init__(self, component=None): - _BlockData.__init__(self, component) + BlockData.__init__(self, component) with self._declare_reserved_components(): self.is_present = IntervalVarPresence() diff --git a/pyomo/contrib/cp/transform/logical_to_disjunctive_program.py b/pyomo/contrib/cp/transform/logical_to_disjunctive_program.py index e318e621e88..c29bf3f2675 100644 --- a/pyomo/contrib/cp/transform/logical_to_disjunctive_program.py +++ b/pyomo/contrib/cp/transform/logical_to_disjunctive_program.py @@ -26,7 +26,7 @@ Transformation, NonNegativeIntegers, ) -from pyomo.core.base.block import _BlockData +from pyomo.core.base.block import BlockData from pyomo.core.base import SortComponents from pyomo.core.util import target_list from pyomo.gdp import Disjunct, Disjunction @@ -73,7 +73,7 @@ def _apply_to(self, model, **kwds): transBlocks = {} visitor = LogicalToDisjunctiveVisitor() for t in targets: - if t.ctype is Block or isinstance(t, _BlockData): + if t.ctype is Block or isinstance(t, BlockData): self._transform_block(t, model, visitor, transBlocks) elif t.ctype is LogicalConstraint: if t.is_indexed(): diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index 50cb84daaf5..b798dafced7 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -15,7 +15,7 @@ import enum import textwrap -from pyomo.core.base.block import _BlockData +from pyomo.core.base.block import BlockData from pyomo.core.base.var import Var from pyomo.core.base.constraint import Constraint from pyomo.core.base.objective import Objective @@ -279,7 +279,7 @@ def __init__(self, model=None, active=True, include_inequality=True, **kwds): self._incidence_graph = None self._variables = None self._constraints = None - elif isinstance(model, _BlockData): + elif isinstance(model, BlockData): self._constraints = [ con for con in model.component_data_objects(Constraint, active=active) @@ -348,7 +348,7 @@ def __init__(self, model=None, active=True, include_inequality=True, **kwds): else: raise TypeError( "Unsupported type for incidence graph. Expected PyomoNLP" - " or _BlockData but got %s." % type(model) + " or BlockData but got %s." % type(model) ) @property diff --git a/pyomo/contrib/incidence_analysis/scc_solver.py b/pyomo/contrib/incidence_analysis/scc_solver.py index 0c59fe8703e..378647c190c 100644 --- a/pyomo/contrib/incidence_analysis/scc_solver.py +++ b/pyomo/contrib/incidence_analysis/scc_solver.py @@ -27,7 +27,7 @@ def generate_strongly_connected_components( constraints, variables=None, include_fixed=False, igraph=None ): - """Yield in order ``_BlockData`` that each contain the variables and + """Yield in order ``BlockData`` that each contain the variables and constraints of a single diagonal block in a block lower triangularization of the incidence matrix of constraints and variables @@ -51,7 +51,7 @@ def generate_strongly_connected_components( Yields ------ - Tuple of ``_BlockData``, list-of-variables + Tuple of ``BlockData``, list-of-variables Blocks containing the variables and constraints of every strongly connected component, in a topological order. The variables are the "input variables" for that block. diff --git a/pyomo/contrib/incidence_analysis/tests/test_interface.py b/pyomo/contrib/incidence_analysis/tests/test_interface.py index 4b77d60d8ba..117e2e53b6d 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_interface.py +++ b/pyomo/contrib/incidence_analysis/tests/test_interface.py @@ -1888,7 +1888,7 @@ def test_block_data_obj(self): self.assertEqual(len(var_dmp.unmatched), 1) self.assertEqual(len(con_dmp.unmatched), 1) - msg = "Unsupported type.*_BlockData" + msg = "Unsupported type.*BlockData" with self.assertRaisesRegex(TypeError, msg): igraph = IncidenceGraphInterface(m.block) diff --git a/pyomo/contrib/latex_printer/latex_printer.py b/pyomo/contrib/latex_printer/latex_printer.py index 0a595dd8e1b..42fc9083953 100644 --- a/pyomo/contrib/latex_printer/latex_printer.py +++ b/pyomo/contrib/latex_printer/latex_printer.py @@ -64,7 +64,7 @@ from pyomo.core.base.external import _PythonCallbackFunctionID from pyomo.core.base.enums import SortComponents -from pyomo.core.base.block import _BlockData +from pyomo.core.base.block import BlockData from pyomo.repn.util import ExprType @@ -587,7 +587,7 @@ def latex_printer( Parameters ---------- - pyomo_component: _BlockData or Model or Objective or Constraint or Expression + 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 @@ -674,7 +674,7 @@ def latex_printer( use_equation_environment = True isSingle = True - elif isinstance(pyomo_component, _BlockData): + elif isinstance(pyomo_component, BlockData): objectives = [ obj for obj in pyomo_component.component_data_objects( diff --git a/pyomo/contrib/piecewise/piecewise_linear_function.py b/pyomo/contrib/piecewise/piecewise_linear_function.py index 66ca02ad125..e92edacc756 100644 --- a/pyomo/contrib/piecewise/piecewise_linear_function.py +++ b/pyomo/contrib/piecewise/piecewise_linear_function.py @@ -20,7 +20,7 @@ PiecewiseLinearExpression, ) from pyomo.core import Any, NonNegativeIntegers, value, Var -from pyomo.core.base.block import _BlockData, Block +from pyomo.core.base.block import BlockData, Block from pyomo.core.base.component import ModelComponentFactory from pyomo.core.base.expression import Expression from pyomo.core.base.global_set import UnindexedComponent_index @@ -36,11 +36,11 @@ logger = logging.getLogger(__name__) -class PiecewiseLinearFunctionData(_BlockData): +class PiecewiseLinearFunctionData(BlockData): _Block_reserved_words = Any def __init__(self, component=None): - _BlockData.__init__(self, component) + BlockData.__init__(self, component) with self._declare_reserved_components(): self._expressions = Expression(NonNegativeIntegers) diff --git a/pyomo/contrib/piecewise/transform/piecewise_to_gdp_transformation.py b/pyomo/contrib/piecewise/transform/piecewise_to_gdp_transformation.py index 2e056c47a15..779bb601c71 100644 --- a/pyomo/contrib/piecewise/transform/piecewise_to_gdp_transformation.py +++ b/pyomo/contrib/piecewise/transform/piecewise_to_gdp_transformation.py @@ -33,7 +33,7 @@ Any, ) from pyomo.core.base import Transformation -from pyomo.core.base.block import _BlockData, Block +from pyomo.core.base.block import BlockData, Block from pyomo.core.util import target_list from pyomo.gdp import Disjunct, Disjunction from pyomo.gdp.util import is_child_of @@ -147,7 +147,7 @@ def _apply_to_impl(self, instance, **kwds): self._transform_piecewise_linear_function( t, config.descend_into_expressions ) - elif t.ctype is Block or isinstance(t, _BlockData): + elif t.ctype is Block or isinstance(t, BlockData): self._transform_block(t, config.descend_into_expressions) elif t.ctype is Constraint: if not config.descend_into_expressions: diff --git a/pyomo/contrib/pynumero/interfaces/external_grey_box.py b/pyomo/contrib/pynumero/interfaces/external_grey_box.py index 7e42f161bee..68e652575cc 100644 --- a/pyomo/contrib/pynumero/interfaces/external_grey_box.py +++ b/pyomo/contrib/pynumero/interfaces/external_grey_box.py @@ -18,7 +18,7 @@ from pyomo.common.log import is_debug_set from pyomo.common.timing import ConstructionTimer from pyomo.core.base import Var, Set, Constraint, value -from pyomo.core.base.block import _BlockData, Block, declare_custom_block +from pyomo.core.base.block import BlockData, Block, declare_custom_block from pyomo.core.base.global_set import UnindexedComponent_index from pyomo.core.base.initializer import Initializer from pyomo.core.base.set import UnindexedComponent_set @@ -316,7 +316,7 @@ def evaluate_jacobian_outputs(self): # -class ExternalGreyBoxBlockData(_BlockData): +class ExternalGreyBoxBlockData(BlockData): def set_external_model(self, external_grey_box_model, inputs=None, outputs=None): """ Parameters @@ -424,7 +424,7 @@ class ScalarExternalGreyBoxBlock(ExternalGreyBoxBlockData, ExternalGreyBoxBlock) def __init__(self, *args, **kwds): ExternalGreyBoxBlockData.__init__(self, component=self) ExternalGreyBoxBlock.__init__(self, *args, **kwds) - # The above inherit from Block and _BlockData, so it's not until here + # The above inherit from Block and BlockData, so it's not until here # that we know it's scalar. So we set the index accordingly. self._index = UnindexedComponent_index diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 8840265763e..4b7da383a57 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -17,7 +17,7 @@ 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.block import BlockData from pyomo.core.base.objective import _GeneralObjectiveData from pyomo.common.config import document_kwargs_from_configdict, ConfigValue from pyomo.common.errors import ApplicationError @@ -108,13 +108,13 @@ def __str__(self): @document_kwargs_from_configdict(CONFIG) @abc.abstractmethod - def solve(self, model: _BlockData, **kwargs) -> Results: + def solve(self, model: BlockData, **kwargs) -> Results: """ Solve a Pyomo model. Parameters ---------- - model: _BlockData + model: BlockData The Pyomo model to be solved **kwargs Additional keyword arguments (including solver_options - passthrough @@ -182,7 +182,7 @@ class PersistentSolverBase(SolverBase): @document_kwargs_from_configdict(PersistentSolverConfig()) @abc.abstractmethod - def solve(self, model: _BlockData, **kwargs) -> Results: + def solve(self, model: BlockData, **kwargs) -> Results: super().solve(model, kwargs) def is_persistent(self): @@ -300,7 +300,7 @@ def add_constraints(self, cons: List[_GeneralConstraintData]): """ @abc.abstractmethod - def add_block(self, block: _BlockData): + def add_block(self, block: BlockData): """ Add a block to the model """ @@ -324,7 +324,7 @@ def remove_constraints(self, cons: List[_GeneralConstraintData]): """ @abc.abstractmethod - def remove_block(self, block: _BlockData): + def remove_block(self, block: BlockData): """ Remove a block from the model """ @@ -496,7 +496,7 @@ def _solution_handler( def solve( self, - model: _BlockData, + model: BlockData, tee: bool = False, load_solutions: bool = True, logfile: Optional[str] = None, diff --git a/pyomo/contrib/viewer/report.py b/pyomo/contrib/viewer/report.py index f83a53c608d..a1f893bba31 100644 --- a/pyomo/contrib/viewer/report.py +++ b/pyomo/contrib/viewer/report.py @@ -149,7 +149,7 @@ def degrees_of_freedom(blk): Return the degrees of freedom. Args: - blk (Block or _BlockData): Block to count degrees of freedom in + blk (Block or BlockData): Block to count degrees of freedom in Returns: (int): Number of degrees of freedom """ diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index 2918ef78b00..8f4e86fe697 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -254,7 +254,7 @@ class _BlockConstruction(object): class PseudoMap(AutoSlots.Mixin): """ This class presents a "mock" dict interface to the internal - _BlockData data structures. We return this object to the + BlockData data structures. We return this object to the user to preserve the historical "{ctype : {name : obj}}" interface without actually regenerating that dict-of-dicts data structure. @@ -487,7 +487,7 @@ def iteritems(self): return self.items() -class _BlockData(ActiveComponentData): +class BlockData(ActiveComponentData): """ This class holds the fundamental block data. """ @@ -537,9 +537,9 @@ def __init__(self, component): # _ctypes: { ctype -> [1st idx, last idx, count] } # _decl: { name -> idx } # _decl_order: list( tuples( obj, next_type_idx ) ) - super(_BlockData, self).__setattr__('_ctypes', {}) - super(_BlockData, self).__setattr__('_decl', {}) - super(_BlockData, self).__setattr__('_decl_order', []) + super(BlockData, self).__setattr__('_ctypes', {}) + super(BlockData, self).__setattr__('_decl', {}) + super(BlockData, self).__setattr__('_decl_order', []) self._private_data = None def __getattr__(self, val) -> Union[Component, IndexedComponent, Any]: @@ -574,7 +574,7 @@ def __setattr__(self, name: str, val: Union[Component, IndexedComponent, Any]): # Other Python objects are added with the standard __setattr__ # method. # - super(_BlockData, self).__setattr__(name, val) + super(BlockData, self).__setattr__(name, val) # # Case 2. The attribute exists and it is a component in the # list of declarations in this block. We will use the @@ -628,11 +628,11 @@ def __setattr__(self, name: str, val: Union[Component, IndexedComponent, Any]): # else: # - # NB: This is important: the _BlockData is either a scalar + # NB: This is important: the BlockData is either a scalar # Block (where _parent and _component are defined) or a # single block within an Indexed Block (where only # _component is defined). Regardless, the - # _BlockData.__init__() method declares these methods and + # BlockData.__init__() method declares these methods and # sets them either to None or a weakref. Thus, we will # never have a problem converting these objects from # weakrefs into Blocks and back (when pickling); the @@ -647,23 +647,23 @@ def __setattr__(self, name: str, val: Union[Component, IndexedComponent, Any]): # return True, this shouldn't be too inefficient. # if name == '_parent': - if val is not None and not isinstance(val(), _BlockData): + if val is not None and not isinstance(val(), BlockData): raise ValueError( "Cannot set the '_parent' attribute of Block '%s' " "to a non-Block object (with type=%s); Did you " "try to create a model component named '_parent'?" % (self.name, type(val)) ) - super(_BlockData, self).__setattr__(name, val) + super(BlockData, self).__setattr__(name, val) elif name == '_component': - if val is not None and not isinstance(val(), _BlockData): + if val is not None and not isinstance(val(), BlockData): raise ValueError( "Cannot set the '_component' attribute of Block '%s' " "to a non-Block object (with type=%s); Did you " "try to create a model component named '_component'?" % (self.name, type(val)) ) - super(_BlockData, self).__setattr__(name, val) + super(BlockData, self).__setattr__(name, val) # # At this point, we should only be seeing non-component data # the user is hanging on the blocks (uncommon) or the @@ -680,7 +680,7 @@ def __setattr__(self, name: str, val: Union[Component, IndexedComponent, Any]): delattr(self, name) self.add_component(name, val) else: - super(_BlockData, self).__setattr__(name, val) + super(BlockData, self).__setattr__(name, val) def __delattr__(self, name): """ @@ -703,7 +703,7 @@ def __delattr__(self, name): # Other Python objects are removed with the standard __detattr__ # method. # - super(_BlockData, self).__delattr__(name) + super(BlockData, self).__delattr__(name) def _compact_decl_storage(self): idxMap = {} @@ -775,11 +775,11 @@ def transfer_attributes_from(self, src): Parameters ---------- - src: _BlockData or dict + src: BlockData or dict The Block or mapping that contains the new attributes to assign to this block. """ - if isinstance(src, _BlockData): + if isinstance(src, BlockData): # There is a special case where assigning a parent block to # this block creates a circular hierarchy if src is self: @@ -788,7 +788,7 @@ def transfer_attributes_from(self, src): while p_block is not None: if p_block is src: raise ValueError( - "_BlockData.transfer_attributes_from(): Cannot set a " + "BlockData.transfer_attributes_from(): Cannot set a " "sub-block (%s) to a parent block (%s): creates a " "circular hierarchy" % (self, src) ) @@ -804,7 +804,7 @@ def transfer_attributes_from(self, src): del_src_comp = lambda x: None else: raise ValueError( - "_BlockData.transfer_attributes_from(): expected a " + "BlockData.transfer_attributes_from(): expected a " "Block or dict; received %s" % (type(src).__name__,) ) @@ -878,7 +878,7 @@ def collect_ctypes(self, active=None, descend_into=True): def model(self): # - # Special case: the "Model" is always the top-level _BlockData, + # Special case: the "Model" is always the top-level BlockData, # so if this is the top-level block, it must be the model # # Also note the interesting and intentional characteristic for @@ -1035,7 +1035,7 @@ def add_component(self, name, val): # is inappropriate here. The correct way to add the attribute # is to delegate the work to the next class up the MRO. # - super(_BlockData, self).__setattr__(name, val) + super(BlockData, self).__setattr__(name, val) # # Update the ctype linked lists # @@ -1106,7 +1106,7 @@ def add_component(self, name, val): # This is tricky: If we are in the middle of # constructing an indexed block, the block component # already has _constructed=True. Now, if the - # _BlockData.__init__() defines any local variables + # BlockData.__init__() defines any local variables # (like pyomo.gdp.Disjunct's indicator_var), name(True) # will fail: this block data exists and has a parent(), # but it has not yet been added to the parent's _data @@ -1194,7 +1194,7 @@ def del_component(self, name_or_object): # Note: 'del self.__dict__[name]' is inappropriate here. The # correct way to add the attribute is to delegate the work to # the next class up the MRO. - super(_BlockData, self).__delattr__(name) + super(BlockData, self).__delattr__(name) def reclassify_component_type( self, name_or_object, new_ctype, preserve_declaration_order=True @@ -1994,6 +1994,11 @@ def private_data(self, scope=None): return self._private_data[scope] +class _BlockData(metaclass=RenamedClass): + __renamed__new_class__ = BlockData + __renamed__version__ = '6.7.2.dev0' + + @ModelComponentFactory.register( "A component that contains one or more model components." ) @@ -2007,7 +2012,7 @@ class Block(ActiveIndexedComponent): is deferred. """ - _ComponentDataClass = _BlockData + _ComponentDataClass = BlockData _private_data_initializers = defaultdict(lambda: dict) @overload @@ -2100,7 +2105,7 @@ def _getitem_when_not_present(self, idx): # components declared by the rule have the opportunity # to be initialized with data from # _BlockConstruction.data as they are transferred over. - if obj is not _block and isinstance(obj, _BlockData): + if obj is not _block and isinstance(obj, BlockData): _block.transfer_attributes_from(obj) finally: if data is not None and _block is not self: @@ -2221,7 +2226,7 @@ def display(self, filename=None, ostream=None, prefix=""): ostream = sys.stdout for key in sorted(self): - _BlockData.display(self[key], filename, ostream, prefix) + BlockData.display(self[key], filename, ostream, prefix) @staticmethod def register_private_data_initializer(initializer, scope=None): @@ -2241,9 +2246,9 @@ def register_private_data_initializer(initializer, scope=None): Block._private_data_initializers[scope] = initializer -class ScalarBlock(_BlockData, Block): +class ScalarBlock(BlockData, Block): def __init__(self, *args, **kwds): - _BlockData.__init__(self, component=self) + BlockData.__init__(self, component=self) Block.__init__(self, *args, **kwds) # Initialize the data dict so that (abstract) attribute # assignment will work. Note that we do not trigger @@ -2266,7 +2271,7 @@ def __init__(self, *args, **kwds): Block.__init__(self, *args, **kwds) @overload - def __getitem__(self, index) -> _BlockData: ... + def __getitem__(self, index) -> BlockData: ... __getitem__ = IndexedComponent.__getitem__ # type: ignore @@ -2325,7 +2330,7 @@ def components_data(block, ctype, sort=None, sort_by_keys=False, sort_by_names=F # Create a Block and record all the default attributes, methods, etc. # These will be assumed to be the set of illegal component names. # -_BlockData._Block_reserved_words = set(dir(Block())) +BlockData._Block_reserved_words = set(dir(Block())) class _IndexedCustomBlockMeta(type): @@ -2376,7 +2381,7 @@ def declare_custom_block(name, new_ctype=None): """Decorator to declare components for a custom block data class >>> @declare_custom_block(name=FooBlock) - ... class FooBlockData(_BlockData): + ... class FooBlockData(BlockData): ... # custom block data class ... pass """ diff --git a/pyomo/core/base/piecewise.py b/pyomo/core/base/piecewise.py index 7817a61b2f2..b15def13ccb 100644 --- a/pyomo/core/base/piecewise.py +++ b/pyomo/core/base/piecewise.py @@ -43,7 +43,7 @@ from pyomo.common.deprecation import deprecation_warning from pyomo.common.numeric_types import value from pyomo.common.timing import ConstructionTimer -from pyomo.core.base.block import Block, _BlockData +from pyomo.core.base.block import Block, BlockData from pyomo.core.base.component import ModelComponentFactory from pyomo.core.base.constraint import Constraint, ConstraintList from pyomo.core.base.sos import SOSConstraint @@ -214,14 +214,14 @@ def _characterize_function(name, tol, f_rule, model, points, *index): return 0, values, False -class _PiecewiseData(_BlockData): +class _PiecewiseData(BlockData): """ This class defines the base class for all linearization and piecewise constraint generators.. """ def __init__(self, parent): - _BlockData.__init__(self, parent) + BlockData.__init__(self, parent) self._constructed = True self._bound_type = None self._domain_pts = None diff --git a/pyomo/core/plugins/transform/logical_to_linear.py b/pyomo/core/plugins/transform/logical_to_linear.py index 7aa541a5fdd..69328032004 100644 --- a/pyomo/core/plugins/transform/logical_to_linear.py +++ b/pyomo/core/plugins/transform/logical_to_linear.py @@ -29,7 +29,7 @@ BooleanVarList, SortComponents, ) -from pyomo.core.base.block import _BlockData +from pyomo.core.base.block import BlockData from pyomo.core.base.boolean_var import _DeprecatedImplicitAssociatedBinaryVariable from pyomo.core.expr.cnf_walker import to_cnf from pyomo.core.expr import ( @@ -100,7 +100,7 @@ def _apply_to(self, model, **kwds): # the GDP will be solved, and it would be wrong to assume that a GDP # will *necessarily* be solved as an algebraic model. The star # example of not doing so being GDPopt.) - if t.ctype is Block or isinstance(t, _BlockData): + if t.ctype is Block or isinstance(t, BlockData): self._transform_block(t, model, new_var_lists, transBlocks) elif t.ctype is LogicalConstraint: if t.is_indexed(): diff --git a/pyomo/core/tests/unit/test_block.py b/pyomo/core/tests/unit/test_block.py index 71e80d90a73..660f65f1944 100644 --- a/pyomo/core/tests/unit/test_block.py +++ b/pyomo/core/tests/unit/test_block.py @@ -54,7 +54,7 @@ from pyomo.core.base.block import ( ScalarBlock, SubclassOf, - _BlockData, + BlockData, declare_custom_block, ) import pyomo.core.expr as EXPR @@ -851,7 +851,7 @@ class DerivedBlock(ScalarBlock): _Block_reserved_words = None DerivedBlock._Block_reserved_words = ( - set(['a', 'b', 'c']) | _BlockData._Block_reserved_words + set(['a', 'b', 'c']) | BlockData._Block_reserved_words ) m = ConcreteModel() @@ -965,7 +965,7 @@ def __init__(self, *args, **kwds): b.c.d.e = Block() with self.assertRaisesRegex( ValueError, - r'_BlockData.transfer_attributes_from\(\): ' + r'BlockData.transfer_attributes_from\(\): ' r'Cannot set a sub-block \(c.d.e\) to a parent block \(c\):', ): b.c.d.e.transfer_attributes_from(b.c) @@ -974,7 +974,7 @@ def __init__(self, *args, **kwds): b = Block(concrete=True) with self.assertRaisesRegex( ValueError, - r'_BlockData.transfer_attributes_from\(\): expected a Block ' + r'BlockData.transfer_attributes_from\(\): expected a Block ' 'or dict; received str', ): b.transfer_attributes_from('foo') @@ -2977,7 +2977,7 @@ def test_write_exceptions(self): def test_override_pprint(self): @declare_custom_block('TempBlock') - class TempBlockData(_BlockData): + class TempBlockData(BlockData): def pprint(self, ostream=None, verbose=False, prefix=""): ostream.write('Testing pprint of a custom block.') @@ -3052,9 +3052,9 @@ def test_derived_block_construction(self): class ConcreteBlock(Block): pass - class ScalarConcreteBlock(_BlockData, ConcreteBlock): + class ScalarConcreteBlock(BlockData, ConcreteBlock): def __init__(self, *args, **kwds): - _BlockData.__init__(self, component=self) + BlockData.__init__(self, component=self) ConcreteBlock.__init__(self, *args, **kwds) _buf = [] diff --git a/pyomo/core/tests/unit/test_component.py b/pyomo/core/tests/unit/test_component.py index 175c4c47d46..b12db9af047 100644 --- a/pyomo/core/tests/unit/test_component.py +++ b/pyomo/core/tests/unit/test_component.py @@ -66,19 +66,17 @@ def test_getname(self): ) m.b[2]._component = None - self.assertEqual( - m.b[2].getname(fully_qualified=True), "[Unattached _BlockData]" - ) + self.assertEqual(m.b[2].getname(fully_qualified=True), "[Unattached BlockData]") # I think that getname() should do this: # self.assertEqual(m.b[2].c[2,4].getname(fully_qualified=True), - # "[Unattached _BlockData].c[2,4]") + # "[Unattached BlockData].c[2,4]") # but it doesn't match current behavior. I will file a PEP to # propose changing the behavior later and proceed to test # current behavior. self.assertEqual(m.b[2].c[2, 4].getname(fully_qualified=True), "c[2,4]") self.assertEqual( - m.b[2].getname(fully_qualified=False), "[Unattached _BlockData]" + m.b[2].getname(fully_qualified=False), "[Unattached BlockData]" ) self.assertEqual(m.b[2].c[2, 4].getname(fully_qualified=False), "c[2,4]") diff --git a/pyomo/core/tests/unit/test_indexed_slice.py b/pyomo/core/tests/unit/test_indexed_slice.py index babd3f3c46a..40aaad9fec9 100644 --- a/pyomo/core/tests/unit/test_indexed_slice.py +++ b/pyomo/core/tests/unit/test_indexed_slice.py @@ -17,7 +17,7 @@ import pyomo.common.unittest as unittest from pyomo.environ import Var, Block, ConcreteModel, RangeSet, Set, Any -from pyomo.core.base.block import _BlockData +from pyomo.core.base.block import BlockData from pyomo.core.base.indexed_component_slice import IndexedComponent_slice from pyomo.core.base.set import normalize_index @@ -64,7 +64,7 @@ def tearDown(self): self.m = None def test_simple_getitem(self): - self.assertIsInstance(self.m.b[1, 4], _BlockData) + self.assertIsInstance(self.m.b[1, 4], BlockData) def test_simple_getslice(self): _slicer = self.m.b[:, 4] diff --git a/pyomo/core/tests/unit/test_suffix.py b/pyomo/core/tests/unit/test_suffix.py index d2e861cceb5..9597bad7571 100644 --- a/pyomo/core/tests/unit/test_suffix.py +++ b/pyomo/core/tests/unit/test_suffix.py @@ -1603,7 +1603,7 @@ def test_clone_IndexedBlock(self): self.assertEqual(inst.junk.get(model.b[1]), None) self.assertEqual(inst.junk.get(inst.b[1]), 1.0) - def test_clone_BlockData(self): + def test_cloneBlockData(self): model = ConcreteModel() model.b = Block([1, 2, 3]) model.junk = Suffix() @@ -1761,7 +1761,7 @@ def test_pickle_IndexedBlock(self): self.assertEqual(inst.junk.get(model.b[1]), None) self.assertEqual(inst.junk.get(inst.b[1]), 1.0) - def test_pickle_BlockData(self): + def test_pickleBlockData(self): model = ConcreteModel() model.b = Block([1, 2, 3]) model.junk = Suffix() diff --git a/pyomo/dae/flatten.py b/pyomo/dae/flatten.py index febaf7c10c9..3d90cc443c1 100644 --- a/pyomo/dae/flatten.py +++ b/pyomo/dae/flatten.py @@ -259,7 +259,7 @@ def generate_sliced_components( Parameters ---------- - b: _BlockData + b: BlockData Block whose components will be sliced index_stack: list @@ -267,7 +267,7 @@ def generate_sliced_components( component, that have been sliced. This is necessary to return the sets that have been sliced. - slice_: IndexedComponent_slice or _BlockData + slice_: IndexedComponent_slice or BlockData Slice generated so far. This function will yield extensions to this slice at the current level of the block hierarchy. @@ -443,7 +443,7 @@ def flatten_components_along_sets(m, sets, ctype, indices=None, active=None): Parameters ---------- - m: _BlockData + m: BlockData Block whose components (and their sub-components) will be partitioned @@ -546,7 +546,7 @@ def flatten_dae_components(model, time, ctype, indices=None, active=None): Parameters ---------- - model: _BlockData + model: BlockData Block whose components are partitioned time: Set diff --git a/pyomo/gdp/disjunct.py b/pyomo/gdp/disjunct.py index d6e5fcfec57..e6d8d709425 100644 --- a/pyomo/gdp/disjunct.py +++ b/pyomo/gdp/disjunct.py @@ -41,7 +41,7 @@ ComponentData, ) from pyomo.core.base.global_set import UnindexedComponent_index -from pyomo.core.base.block import _BlockData +from pyomo.core.base.block import BlockData from pyomo.core.base.misc import apply_indexed_rule from pyomo.core.base.indexed_component import ActiveIndexedComponent from pyomo.core.expr.expr_common import ExpressionType @@ -412,7 +412,7 @@ def process(arg): return (_Initializer.deferred_value, arg) -class _DisjunctData(_BlockData): +class _DisjunctData(BlockData): __autoslot_mappers__ = {'_transformation_block': AutoSlots.weakref_mapper} _Block_reserved_words = set() @@ -424,7 +424,7 @@ def transformation_block(self): ) def __init__(self, component): - _BlockData.__init__(self, component) + BlockData.__init__(self, component) with self._declare_reserved_components(): self.indicator_var = AutoLinkedBooleanVar() self.binary_indicator_var = AutoLinkedBinaryVar(self.indicator_var) @@ -498,7 +498,7 @@ def _activate_without_unfixing_indicator(self): class ScalarDisjunct(_DisjunctData, Disjunct): def __init__(self, *args, **kwds): ## FIXME: This is a HACK to get around a chicken-and-egg issue - ## where _BlockData creates the indicator_var *before* + ## where BlockData creates the indicator_var *before* ## Block.__init__ declares the _defer_construction flag. self._defer_construction = True self._suppress_ctypes = set() diff --git a/pyomo/gdp/plugins/gdp_var_mover.py b/pyomo/gdp/plugins/gdp_var_mover.py index 5402b576368..7b1df0bb68f 100644 --- a/pyomo/gdp/plugins/gdp_var_mover.py +++ b/pyomo/gdp/plugins/gdp_var_mover.py @@ -115,7 +115,7 @@ def _apply_to(self, instance, **kwds): disjunct_component, Block ) # HACK: activate the block, but do not activate the - # _BlockData objects + # BlockData objects super(ActiveIndexedComponent, disjunct_component).activate() # Deactivate all constraints. Note that we only need to diff --git a/pyomo/gdp/tests/common_tests.py b/pyomo/gdp/tests/common_tests.py index 5d0d6f6c21b..e15a7c66d8a 100644 --- a/pyomo/gdp/tests/common_tests.py +++ b/pyomo/gdp/tests/common_tests.py @@ -30,7 +30,7 @@ from pyomo.gdp import Disjunct, Disjunction, GDP_Error from pyomo.core.expr.compare import assertExpressionsEqual from pyomo.core.base import constraint, ComponentUID -from pyomo.core.base.block import _BlockData +from pyomo.core.base.block import BlockData from pyomo.repn import generate_standard_repn import pyomo.core.expr as EXPR import pyomo.gdp.tests.models as models @@ -1704,10 +1704,10 @@ def check_all_components_transformed(self, m): # makeNestedDisjunctions_NestedDisjuncts model. self.assertIsInstance(m.disj.algebraic_constraint, Constraint) self.assertIsInstance(m.d1.disj2.algebraic_constraint, Constraint) - self.assertIsInstance(m.d1.transformation_block, _BlockData) - self.assertIsInstance(m.d2.transformation_block, _BlockData) - self.assertIsInstance(m.d1.d3.transformation_block, _BlockData) - self.assertIsInstance(m.d1.d4.transformation_block, _BlockData) + self.assertIsInstance(m.d1.transformation_block, BlockData) + self.assertIsInstance(m.d2.transformation_block, BlockData) + self.assertIsInstance(m.d1.d3.transformation_block, BlockData) + self.assertIsInstance(m.d1.d4.transformation_block, BlockData) def check_transformation_blocks_nestedDisjunctions(self, m, transformation): diff --git a/pyomo/gdp/transformed_disjunct.py b/pyomo/gdp/transformed_disjunct.py index 6cf60abf414..287d5ed1652 100644 --- a/pyomo/gdp/transformed_disjunct.py +++ b/pyomo/gdp/transformed_disjunct.py @@ -10,11 +10,11 @@ # ___________________________________________________________________________ from pyomo.common.autoslots import AutoSlots -from pyomo.core.base.block import _BlockData, IndexedBlock +from pyomo.core.base.block import BlockData, IndexedBlock from pyomo.core.base.global_set import UnindexedComponent_index, UnindexedComponent_set -class _TransformedDisjunctData(_BlockData): +class _TransformedDisjunctData(BlockData): __slots__ = ('_src_disjunct',) __autoslot_mappers__ = {'_src_disjunct': AutoSlots.weakref_mapper} @@ -23,7 +23,7 @@ def src_disjunct(self): return None if self._src_disjunct is None else self._src_disjunct() def __init__(self, component): - _BlockData.__init__(self, component) + BlockData.__init__(self, component) # pointer to the Disjunct whose transformation block this is. self._src_disjunct = None diff --git a/pyomo/gdp/util.py b/pyomo/gdp/util.py index fe11975954d..55d273938c5 100644 --- a/pyomo/gdp/util.py +++ b/pyomo/gdp/util.py @@ -22,7 +22,7 @@ LogicalConstraint, value, ) -from pyomo.core.base.block import _BlockData +from pyomo.core.base.block import BlockData from pyomo.common.collections import ComponentMap, ComponentSet, OrderedSet from pyomo.opt import TerminationCondition, SolverStatus @@ -330,7 +330,7 @@ def get_gdp_tree(targets, instance, knownBlocks=None): "Target '%s' is not a component on instance " "'%s'!" % (t.name, instance.name) ) - if t.ctype is Block or isinstance(t, _BlockData): + if t.ctype is Block or isinstance(t, BlockData): _blocks = t.values() if t.is_indexed() else (t,) for block in _blocks: if not block.active: @@ -387,7 +387,7 @@ def is_child_of(parent, child, knownBlocks=None): if knownBlocks is None: knownBlocks = {} tmp = set() - node = child if isinstance(child, (Block, _BlockData)) else child.parent_block() + node = child if isinstance(child, (Block, BlockData)) else child.parent_block() while True: known = knownBlocks.get(node) if known: @@ -452,7 +452,7 @@ def get_src_disjunct(transBlock): Parameters ---------- - transBlock: _BlockData which is in the relaxedDisjuncts IndexedBlock + transBlock: BlockData which is in the relaxedDisjuncts IndexedBlock on a transformation block. """ if ( diff --git a/pyomo/mpec/complementarity.py b/pyomo/mpec/complementarity.py index 79f76a9fc34..3982c7a87ba 100644 --- a/pyomo/mpec/complementarity.py +++ b/pyomo/mpec/complementarity.py @@ -19,7 +19,7 @@ from pyomo.core import Constraint, Var, Block, Set from pyomo.core.base.component import ModelComponentFactory from pyomo.core.base.global_set import UnindexedComponent_index -from pyomo.core.base.block import _BlockData +from pyomo.core.base.block import BlockData from pyomo.core.base.disable_methods import disable_methods from pyomo.core.base.initializer import ( Initializer, @@ -43,7 +43,7 @@ def complements(a, b): return ComplementarityTuple(a, b) -class _ComplementarityData(_BlockData): +class _ComplementarityData(BlockData): def _canonical_expression(self, e): # Note: as the complimentarity component maintains references to # the original expression (e), it is NOT safe or valid to bypass diff --git a/pyomo/opt/base/solvers.py b/pyomo/opt/base/solvers.py index f1f9d653a8a..c0698165603 100644 --- a/pyomo/opt/base/solvers.py +++ b/pyomo/opt/base/solvers.py @@ -536,15 +536,15 @@ def solve(self, *args, **kwds): # If the inputs are models, then validate that they have been # constructed! Collect suffix names to try and import from solution. # - from pyomo.core.base.block import _BlockData + from pyomo.core.base.block import BlockData import pyomo.core.base.suffix from pyomo.core.kernel.block import IBlock import pyomo.core.kernel.suffix _model = None for arg in args: - if isinstance(arg, (_BlockData, IBlock)): - if isinstance(arg, _BlockData): + if isinstance(arg, (BlockData, IBlock)): + if isinstance(arg, BlockData): if not arg.is_constructed(): raise RuntimeError( "Attempting to solve model=%s with unconstructed " @@ -553,7 +553,7 @@ def solve(self, *args, **kwds): _model = arg # import suffixes must be on the top-level model - if isinstance(arg, _BlockData): + if isinstance(arg, BlockData): model_suffixes = list( name for ( diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index 49cca32eaf9..b4a21a2108f 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -486,7 +486,7 @@ def categorize_valid_components( Parameters ---------- - model: _BlockData + model: BlockData The model tree to walk active: True or None @@ -507,7 +507,7 @@ def categorize_valid_components( Returns ------- - component_map: Dict[type, List[_BlockData]] + component_map: Dict[type, List[BlockData]] A dict mapping component type to a list of block data objects that contain declared component of that type. diff --git a/pyomo/solvers/plugins/solvers/direct_or_persistent_solver.py b/pyomo/solvers/plugins/solvers/direct_or_persistent_solver.py index c131b8ad10a..de38a0372d0 100644 --- a/pyomo/solvers/plugins/solvers/direct_or_persistent_solver.py +++ b/pyomo/solvers/plugins/solvers/direct_or_persistent_solver.py @@ -10,7 +10,7 @@ # ___________________________________________________________________________ from pyomo.core.base.PyomoModel import Model -from pyomo.core.base.block import Block, _BlockData +from pyomo.core.base.block import Block, BlockData from pyomo.core.kernel.block import IBlock from pyomo.opt.base.solvers import OptSolver from pyomo.core.base import SymbolMap, NumericLabeler, TextLabeler @@ -177,7 +177,7 @@ def _postsolve(self): """ This method should be implemented by subclasses.""" def _set_instance(self, model, kwds={}): - if not isinstance(model, (Model, IBlock, Block, _BlockData)): + if not isinstance(model, (Model, IBlock, Block, BlockData)): msg = ( "The problem instance supplied to the {0} plugin " "'_presolve' method must be a Model or a Block".format(type(self)) diff --git a/pyomo/solvers/plugins/solvers/direct_solver.py b/pyomo/solvers/plugins/solvers/direct_solver.py index 3eab658391c..609a81b2018 100644 --- a/pyomo/solvers/plugins/solvers/direct_solver.py +++ b/pyomo/solvers/plugins/solvers/direct_solver.py @@ -15,7 +15,7 @@ from pyomo.solvers.plugins.solvers.direct_or_persistent_solver import ( DirectOrPersistentSolver, ) -from pyomo.core.base.block import _BlockData +from pyomo.core.base.block import BlockData from pyomo.core.kernel.block import IBlock from pyomo.core.base.suffix import active_import_suffix_generator from pyomo.core.kernel.suffix import import_suffix_generator @@ -79,8 +79,8 @@ def solve(self, *args, **kwds): # _model = None for arg in args: - if isinstance(arg, (_BlockData, IBlock)): - if isinstance(arg, _BlockData): + if isinstance(arg, (BlockData, IBlock)): + if isinstance(arg, BlockData): if not arg.is_constructed(): raise RuntimeError( "Attempting to solve model=%s with unconstructed " @@ -89,7 +89,7 @@ def solve(self, *args, **kwds): _model = arg # import suffixes must be on the top-level model - if isinstance(arg, _BlockData): + if isinstance(arg, BlockData): model_suffixes = list( name for (name, comp) in active_import_suffix_generator(arg) ) diff --git a/pyomo/solvers/plugins/solvers/mosek_direct.py b/pyomo/solvers/plugins/solvers/mosek_direct.py index 5000a2f35c4..5c07c73b94e 100644 --- a/pyomo/solvers/plugins/solvers/mosek_direct.py +++ b/pyomo/solvers/plugins/solvers/mosek_direct.py @@ -558,7 +558,7 @@ def _add_block(self, block): Parameters ---------- - block: Block (scalar Block or single _BlockData) + block: Block (scalar Block or single BlockData) """ var_seq = tuple( block.component_data_objects( diff --git a/pyomo/solvers/plugins/solvers/persistent_solver.py b/pyomo/solvers/plugins/solvers/persistent_solver.py index 29aa3f2bbf5..d69c050291b 100644 --- a/pyomo/solvers/plugins/solvers/persistent_solver.py +++ b/pyomo/solvers/plugins/solvers/persistent_solver.py @@ -12,7 +12,7 @@ from pyomo.solvers.plugins.solvers.direct_or_persistent_solver import ( DirectOrPersistentSolver, ) -from pyomo.core.base.block import _BlockData +from pyomo.core.base.block import BlockData from pyomo.core.kernel.block import IBlock from pyomo.core.base.suffix import active_import_suffix_generator from pyomo.core.kernel.suffix import import_suffix_generator @@ -96,7 +96,7 @@ def add_block(self, block): Parameters ---------- - block: Block (scalar Block or single _BlockData) + block: Block (scalar Block or single BlockData) """ if self._pyomo_model is None: @@ -295,7 +295,7 @@ def remove_block(self, block): Parameters ---------- - block: Block (scalar Block or a single _BlockData) + block: Block (scalar Block or a single BlockData) """ # see PR #366 for discussion about handling indexed @@ -455,7 +455,7 @@ def solve(self, *args, **kwds): self.available(exception_flag=True) # Collect suffix names to try and import from solution. - if isinstance(self._pyomo_model, _BlockData): + if isinstance(self._pyomo_model, BlockData): model_suffixes = list( name for (name, comp) in active_import_suffix_generator(self._pyomo_model) diff --git a/pyomo/util/report_scaling.py b/pyomo/util/report_scaling.py index 201319ea92a..5ae28baa715 100644 --- a/pyomo/util/report_scaling.py +++ b/pyomo/util/report_scaling.py @@ -11,7 +11,7 @@ import pyomo.environ as pyo import math -from pyomo.core.base.block import _BlockData +from pyomo.core.base.block import BlockData from pyomo.common.collections import ComponentSet from pyomo.core.base.var import _GeneralVarData from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr @@ -42,7 +42,7 @@ def _print_var_set(var_set): return s -def _check_var_bounds(m: _BlockData, too_large: float): +def _check_var_bounds(m: BlockData, too_large: float): vars_without_bounds = ComponentSet() vars_with_large_bounds = ComponentSet() for v in m.component_data_objects(pyo.Var, descend_into=True): @@ -90,7 +90,7 @@ def _check_coefficients( def report_scaling( - m: _BlockData, too_large: float = 5e4, too_small: float = 1e-6 + m: BlockData, too_large: float = 5e4, too_small: float = 1e-6 ) -> bool: """ This function logs potentially poorly scaled parts of the model. @@ -107,7 +107,7 @@ def report_scaling( Parameters ---------- - m: _BlockData + m: BlockData The pyomo model or block too_large: float Values above too_large will generate a log entry diff --git a/pyomo/util/slices.py b/pyomo/util/slices.py index 53f6d364219..d85aa3fa926 100644 --- a/pyomo/util/slices.py +++ b/pyomo/util/slices.py @@ -98,7 +98,7 @@ def slice_component_along_sets(comp, sets, context=None): sets: `pyomo.common.collections.ComponentSet` Contains the sets to replace with slices context: `pyomo.core.base.block.Block` or - `pyomo.core.base.block._BlockData` + `pyomo.core.base.block.BlockData` Block below which to search for sets Returns: From c59f91bb1f0d6b94b9e9fb7bbce05ecb6c033c4c Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:32:28 -0600 Subject: [PATCH 1440/1797] Renamed _BooleanVarData -> BooleanVarData --- pyomo/core/base/__init__.py | 2 +- pyomo/core/base/boolean_var.py | 13 +++++++++---- pyomo/core/base/component.py | 2 +- pyomo/core/plugins/transform/logical_to_linear.py | 4 ++-- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/pyomo/core/base/__init__.py b/pyomo/core/base/__init__.py index 4bbd0c9dc44..98eceb45490 100644 --- a/pyomo/core/base/__init__.py +++ b/pyomo/core/base/__init__.py @@ -60,7 +60,7 @@ from pyomo.core.base.var import Var, _VarData, _GeneralVarData, ScalarVar, VarList from pyomo.core.base.boolean_var import ( BooleanVar, - _BooleanVarData, + BooleanVarData, _GeneralBooleanVarData, BooleanVarList, ScalarBooleanVar, diff --git a/pyomo/core/base/boolean_var.py b/pyomo/core/base/boolean_var.py index 246dcea6214..bf9d6159754 100644 --- a/pyomo/core/base/boolean_var.py +++ b/pyomo/core/base/boolean_var.py @@ -68,7 +68,7 @@ def __setstate__(self, state): self._boolvar = weakref_ref(state) -class _BooleanVarData(ComponentData, BooleanValue): +class BooleanVarData(ComponentData, BooleanValue): """ This class defines the data for a single variable. @@ -177,6 +177,11 @@ def free(self): return self.unfix() +class _BooleanVarData(metaclass=RenamedClass): + __renamed__new_class__ = BooleanVarData + __renamed__version__ = '6.7.2.dev0' + + def _associated_binary_mapper(encode, val): if val is None: return None @@ -189,7 +194,7 @@ def _associated_binary_mapper(encode, val): return val -class _GeneralBooleanVarData(_BooleanVarData): +class _GeneralBooleanVarData(BooleanVarData): """ This class defines the data for a single Boolean variable. @@ -222,7 +227,7 @@ def __init__(self, component=None): # # These lines represent in-lining of the # following constructors: - # - _BooleanVarData + # - BooleanVarData # - ComponentData # - BooleanValue self._component = weakref_ref(component) if (component is not None) else None @@ -390,7 +395,7 @@ def construct(self, data=None): _set.construct() # - # Construct _BooleanVarData objects for all index values + # Construct BooleanVarData objects for all index values # if not self.is_indexed(): self._data[None] = self diff --git a/pyomo/core/base/component.py b/pyomo/core/base/component.py index c91167379fd..0618d6d9d56 100644 --- a/pyomo/core/base/component.py +++ b/pyomo/core/base/component.py @@ -802,7 +802,7 @@ class ComponentData(_ComponentBase): __autoslot_mappers__ = {'_component': AutoSlots.weakref_mapper} # NOTE: This constructor is in-lined in the constructors for the following - # classes: _BooleanVarData, _ConnectorData, _ConstraintData, + # classes: BooleanVarData, _ConnectorData, _ConstraintData, # _GeneralExpressionData, _LogicalConstraintData, # _GeneralLogicalConstraintData, _GeneralObjectiveData, # _ParamData,_GeneralVarData, _GeneralBooleanVarData, _DisjunctionData, diff --git a/pyomo/core/plugins/transform/logical_to_linear.py b/pyomo/core/plugins/transform/logical_to_linear.py index 69328032004..da69ca113bd 100644 --- a/pyomo/core/plugins/transform/logical_to_linear.py +++ b/pyomo/core/plugins/transform/logical_to_linear.py @@ -285,7 +285,7 @@ class CnfToLinearVisitor(StreamBasedExpressionVisitor): """Convert CNF logical constraint to linear constraints. Expected expression node types: AndExpression, OrExpression, NotExpression, - AtLeastExpression, AtMostExpression, ExactlyExpression, _BooleanVarData + AtLeastExpression, AtMostExpression, ExactlyExpression, BooleanVarData """ @@ -372,7 +372,7 @@ def beforeChild(self, node, child, child_idx): if child.is_expression_type(): return True, None - # Only thing left should be _BooleanVarData + # Only thing left should be BooleanVarData # # TODO: After the expr_multiple_dispatch is merged, this should # be switched to using as_numeric. From 0c72d9faa267b4f02d3d8b11daad3581701fbb99 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:32:28 -0600 Subject: [PATCH 1441/1797] Renamed _ComplementarityData -> ComplementarityData --- pyomo/mpec/complementarity.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pyomo/mpec/complementarity.py b/pyomo/mpec/complementarity.py index 3982c7a87ba..aa8db922145 100644 --- a/pyomo/mpec/complementarity.py +++ b/pyomo/mpec/complementarity.py @@ -43,7 +43,7 @@ def complements(a, b): return ComplementarityTuple(a, b) -class _ComplementarityData(BlockData): +class ComplementarityData(BlockData): def _canonical_expression(self, e): # Note: as the complimentarity component maintains references to # the original expression (e), it is NOT safe or valid to bypass @@ -179,9 +179,14 @@ def set_value(self, cc): ) +class _ComplementarityData(metaclass=RenamedClass): + __renamed__new_class__ = ComplementarityData + __renamed__version__ = '6.7.2.dev0' + + @ModelComponentFactory.register("Complementarity conditions.") class Complementarity(Block): - _ComponentDataClass = _ComplementarityData + _ComponentDataClass = ComplementarityData def __new__(cls, *args, **kwds): if cls != Complementarity: @@ -298,9 +303,9 @@ def _conditional_block_printer(ostream, idx, data): ) -class ScalarComplementarity(_ComplementarityData, Complementarity): +class ScalarComplementarity(ComplementarityData, Complementarity): def __init__(self, *args, **kwds): - _ComplementarityData.__init__(self, self) + ComplementarityData.__init__(self, self) Complementarity.__init__(self, *args, **kwds) self._data[None] = self self._index = UnindexedComponent_index From e3fe3162f7b314ecdc84834b727fad6148bfa0ba Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:32:59 -0600 Subject: [PATCH 1442/1797] Renamed _ConnectorData -> ConnectorData --- pyomo/core/base/component.py | 2 +- pyomo/core/base/connector.py | 15 ++++++++++----- pyomo/core/plugins/transform/expand_connectors.py | 4 ++-- pyomo/repn/standard_repn.py | 4 ++-- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/pyomo/core/base/component.py b/pyomo/core/base/component.py index 0618d6d9d56..244d9b6a8f5 100644 --- a/pyomo/core/base/component.py +++ b/pyomo/core/base/component.py @@ -802,7 +802,7 @@ class ComponentData(_ComponentBase): __autoslot_mappers__ = {'_component': AutoSlots.weakref_mapper} # NOTE: This constructor is in-lined in the constructors for the following - # classes: BooleanVarData, _ConnectorData, _ConstraintData, + # classes: BooleanVarData, ConnectorData, _ConstraintData, # _GeneralExpressionData, _LogicalConstraintData, # _GeneralLogicalConstraintData, _GeneralObjectiveData, # _ParamData,_GeneralVarData, _GeneralBooleanVarData, _DisjunctionData, diff --git a/pyomo/core/base/connector.py b/pyomo/core/base/connector.py index 435a2c2fccb..e383b52fc11 100644 --- a/pyomo/core/base/connector.py +++ b/pyomo/core/base/connector.py @@ -28,7 +28,7 @@ logger = logging.getLogger('pyomo.core') -class _ConnectorData(ComponentData, NumericValue): +class ConnectorData(ComponentData, NumericValue): """Holds the actual connector information""" __slots__ = ('vars', 'aggregators') @@ -105,6 +105,11 @@ def _iter_vars(self): yield v +class _ConnectorData(metaclass=RenamedClass): + __renamed__new_class__ = ConnectorData + __renamed__version__ = '6.7.2.dev0' + + @ModelComponentFactory.register( "A bundle of variables that can be manipulated together." ) @@ -157,7 +162,7 @@ def __init__(self, *args, **kwd): # IndexedComponent # def _getitem_when_not_present(self, idx): - _conval = self._data[idx] = _ConnectorData(component=self) + _conval = self._data[idx] = ConnectorData(component=self) return _conval def construct(self, data=None): @@ -170,7 +175,7 @@ def construct(self, data=None): timer = ConstructionTimer(self) self._constructed = True # - # Construct _ConnectorData objects for all index values + # Construct ConnectorData objects for all index values # if self.is_indexed(): self._initialize_members(self._index_set) @@ -258,9 +263,9 @@ def _line_generator(k, v): ) -class ScalarConnector(Connector, _ConnectorData): +class ScalarConnector(Connector, ConnectorData): def __init__(self, *args, **kwd): - _ConnectorData.__init__(self, component=self) + ConnectorData.__init__(self, component=self) Connector.__init__(self, *args, **kwd) self._index = UnindexedComponent_index diff --git a/pyomo/core/plugins/transform/expand_connectors.py b/pyomo/core/plugins/transform/expand_connectors.py index 8c02f3e5698..82ec546e593 100644 --- a/pyomo/core/plugins/transform/expand_connectors.py +++ b/pyomo/core/plugins/transform/expand_connectors.py @@ -25,7 +25,7 @@ Var, SortComponents, ) -from pyomo.core.base.connector import _ConnectorData, ScalarConnector +from pyomo.core.base.connector import ConnectorData, ScalarConnector @TransformationFactory.register( @@ -69,7 +69,7 @@ def _apply_to(self, instance, **kwds): # The set of connectors found in the current constraint found = ComponentSet() - connector_types = set([ScalarConnector, _ConnectorData]) + connector_types = set([ScalarConnector, ConnectorData]) for constraint in instance.component_data_objects( Constraint, sort=SortComponents.deterministic ): diff --git a/pyomo/repn/standard_repn.py b/pyomo/repn/standard_repn.py index 8600a8a50f6..455e7bd9444 100644 --- a/pyomo/repn/standard_repn.py +++ b/pyomo/repn/standard_repn.py @@ -1136,7 +1136,7 @@ def _collect_external_fn(exp, multiplier, idMap, compute_values, verbose, quadra EXPR.RangedExpression: _collect_comparison, EXPR.EqualityExpression: _collect_comparison, EXPR.ExternalFunctionExpression: _collect_external_fn, - # _ConnectorData : _collect_linear_connector, + # ConnectorData : _collect_linear_connector, # ScalarConnector : _collect_linear_connector, _ParamData: _collect_const, ScalarParam: _collect_const, @@ -1536,7 +1536,7 @@ def _linear_collect_pow(exp, multiplier, idMap, compute_values, verbose, coef): #EXPR.EqualityExpression : _linear_collect_comparison, #EXPR.ExternalFunctionExpression : _linear_collect_external_fn, ##EXPR.LinearSumExpression : _collect_linear_sum, - ##_ConnectorData : _collect_linear_connector, + ##ConnectorData : _collect_linear_connector, ##ScalarConnector : _collect_linear_connector, ##param._ParamData : _collect_linear_const, ##param.ScalarParam : _collect_linear_const, From d4b72d2b56193ef68589913d9abd7135242609ba Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:36:53 -0600 Subject: [PATCH 1443/1797] Renamed _ConstraintData -> ConstraintData --- pyomo/contrib/viewer/report.py | 2 +- pyomo/core/base/__init__.py | 2 +- pyomo/core/base/component.py | 2 +- pyomo/core/base/constraint.py | 15 ++++++++++----- pyomo/core/base/logical_constraint.py | 2 +- pyomo/core/base/matrix_constraint.py | 8 ++++---- pyomo/core/beta/dict_objects.py | 4 ++-- pyomo/core/beta/list_objects.py | 4 ++-- pyomo/core/plugins/transform/add_slack_vars.py | 6 +++--- .../core/plugins/transform/equality_transform.py | 4 ++-- pyomo/core/plugins/transform/model.py | 4 ++-- pyomo/core/plugins/transform/scaling.py | 4 ++-- pyomo/core/tests/unit/test_con.py | 2 +- pyomo/gdp/disjunct.py | 2 +- pyomo/gdp/plugins/hull.py | 6 +++--- pyomo/gdp/tests/test_bigm.py | 8 ++++---- pyomo/gdp/util.py | 4 ++-- pyomo/repn/beta/matrix.py | 14 +++++++------- pyomo/repn/plugins/nl_writer.py | 6 +++--- pyomo/repn/plugins/standard_form.py | 4 ++-- pyomo/solvers/plugins/solvers/mosek_persistent.py | 8 ++++---- .../solvers/plugins/solvers/persistent_solver.py | 6 +++--- .../solvers/tests/checks/test_CPLEXPersistent.py | 2 +- .../tests/checks/test_gurobi_persistent.py | 2 +- .../tests/checks/test_xpress_persistent.py | 2 +- pyomo/util/calc_var_value.py | 6 +++--- 26 files changed, 67 insertions(+), 62 deletions(-) diff --git a/pyomo/contrib/viewer/report.py b/pyomo/contrib/viewer/report.py index a1f893bba31..a28e0082212 100644 --- a/pyomo/contrib/viewer/report.py +++ b/pyomo/contrib/viewer/report.py @@ -50,7 +50,7 @@ def get_residual(ui_data, c): values of the constraint body. This function uses the cached values and will not trigger recalculation. If variable values have changed, this may not yield accurate results. - c(_ConstraintData): a constraint or constraint data + c(ConstraintData): a constraint or constraint data Returns: (float) residual """ diff --git a/pyomo/core/base/__init__.py b/pyomo/core/base/__init__.py index 98eceb45490..9a5337ac2c8 100644 --- a/pyomo/core/base/__init__.py +++ b/pyomo/core/base/__init__.py @@ -70,7 +70,7 @@ simple_constraintlist_rule, ConstraintList, Constraint, - _ConstraintData, + ConstraintData, ) from pyomo.core.base.logical_constraint import ( LogicalConstraint, diff --git a/pyomo/core/base/component.py b/pyomo/core/base/component.py index 244d9b6a8f5..7d6fc903632 100644 --- a/pyomo/core/base/component.py +++ b/pyomo/core/base/component.py @@ -802,7 +802,7 @@ class ComponentData(_ComponentBase): __autoslot_mappers__ = {'_component': AutoSlots.weakref_mapper} # NOTE: This constructor is in-lined in the constructors for the following - # classes: BooleanVarData, ConnectorData, _ConstraintData, + # classes: BooleanVarData, ConnectorData, ConstraintData, # _GeneralExpressionData, _LogicalConstraintData, # _GeneralLogicalConstraintData, _GeneralObjectiveData, # _ParamData,_GeneralVarData, _GeneralBooleanVarData, _DisjunctionData, diff --git a/pyomo/core/base/constraint.py b/pyomo/core/base/constraint.py index fde1160e563..cbae828a459 100644 --- a/pyomo/core/base/constraint.py +++ b/pyomo/core/base/constraint.py @@ -130,7 +130,7 @@ def C_rule(model, i, j): # -class _ConstraintData(ActiveComponentData): +class ConstraintData(ActiveComponentData): """ This class defines the data for a single constraint. @@ -165,7 +165,7 @@ def __init__(self, component=None): # # These lines represent in-lining of the # following constructors: - # - _ConstraintData, + # - ConstraintData, # - ActiveComponentData # - ComponentData self._component = weakref_ref(component) if (component is not None) else None @@ -280,7 +280,12 @@ def get_value(self): raise NotImplementedError -class _GeneralConstraintData(_ConstraintData): +class _ConstraintData(metaclass=RenamedClass): + __renamed__new_class__ = ConstraintData + __renamed__version__ = '6.7.2.dev0' + + +class _GeneralConstraintData(ConstraintData): """ This class defines the data for a single general constraint. @@ -312,7 +317,7 @@ def __init__(self, expr=None, component=None): # # These lines represent in-lining of the # following constructors: - # - _ConstraintData, + # - ConstraintData, # - ActiveComponentData # - ComponentData self._component = weakref_ref(component) if (component is not None) else None @@ -897,7 +902,7 @@ def __init__(self, *args, **kwds): # currently in place). So during initialization only, we will # treat them as "indexed" objects where things like # Constraint.Skip are managed. But after that they will behave - # like _ConstraintData objects where set_value does not handle + # like ConstraintData objects where set_value does not handle # Constraint.Skip but expects a valid expression or None. # @property diff --git a/pyomo/core/base/logical_constraint.py b/pyomo/core/base/logical_constraint.py index f32d727931a..3a7bca75960 100644 --- a/pyomo/core/base/logical_constraint.py +++ b/pyomo/core/base/logical_constraint.py @@ -373,7 +373,7 @@ def display(self, prefix="", ostream=None): # # Checks flags like Constraint.Skip, etc. before actually creating a - # constraint object. Returns the _ConstraintData object when it should be + # constraint object. Returns the ConstraintData object when it should be # added to the _data dict; otherwise, None is returned or an exception # is raised. # diff --git a/pyomo/core/base/matrix_constraint.py b/pyomo/core/base/matrix_constraint.py index adc9742302e..8dac7c3d24b 100644 --- a/pyomo/core/base/matrix_constraint.py +++ b/pyomo/core/base/matrix_constraint.py @@ -19,7 +19,7 @@ from pyomo.core.expr.numvalue import value from pyomo.core.expr.numeric_expr import LinearExpression from pyomo.core.base.component import ModelComponentFactory -from pyomo.core.base.constraint import IndexedConstraint, _ConstraintData +from pyomo.core.base.constraint import IndexedConstraint, ConstraintData from pyomo.repn.standard_repn import StandardRepn from collections.abc import Mapping @@ -28,7 +28,7 @@ logger = logging.getLogger('pyomo.core') -class _MatrixConstraintData(_ConstraintData): +class _MatrixConstraintData(ConstraintData): """ This class defines the data for a single linear constraint derived from a canonical form Ax=b constraint. @@ -104,7 +104,7 @@ def __init__(self, index, component_ref): # # These lines represent in-lining of the # following constructors: - # - _ConstraintData, + # - ConstraintData, # - ActiveComponentData # - ComponentData self._component = component_ref @@ -209,7 +209,7 @@ def index(self): return self._index # - # Abstract Interface (_ConstraintData) + # Abstract Interface (ConstraintData) # @property diff --git a/pyomo/core/beta/dict_objects.py b/pyomo/core/beta/dict_objects.py index a8298b08e63..53d39939db2 100644 --- a/pyomo/core/beta/dict_objects.py +++ b/pyomo/core/beta/dict_objects.py @@ -15,7 +15,7 @@ from pyomo.common.log import is_debug_set from pyomo.core.base.set_types import Any from pyomo.core.base.var import IndexedVar, _VarData -from pyomo.core.base.constraint import IndexedConstraint, _ConstraintData +from pyomo.core.base.constraint import IndexedConstraint, ConstraintData from pyomo.core.base.objective import IndexedObjective, _ObjectiveData from pyomo.core.base.expression import IndexedExpression, _ExpressionData @@ -193,7 +193,7 @@ def __init__(self, *args, **kwds): # Constructor for ComponentDict needs to # go last in order to handle any initialization # iterable as an argument - ComponentDict.__init__(self, _ConstraintData, *args, **kwds) + ComponentDict.__init__(self, ConstraintData, *args, **kwds) class ObjectiveDict(ComponentDict, IndexedObjective): diff --git a/pyomo/core/beta/list_objects.py b/pyomo/core/beta/list_objects.py index f53997fed17..e8b40e6da53 100644 --- a/pyomo/core/beta/list_objects.py +++ b/pyomo/core/beta/list_objects.py @@ -15,7 +15,7 @@ from pyomo.common.log import is_debug_set from pyomo.core.base.set_types import Any from pyomo.core.base.var import IndexedVar, _VarData -from pyomo.core.base.constraint import IndexedConstraint, _ConstraintData +from pyomo.core.base.constraint import IndexedConstraint, ConstraintData from pyomo.core.base.objective import IndexedObjective, _ObjectiveData from pyomo.core.base.expression import IndexedExpression, _ExpressionData @@ -241,7 +241,7 @@ def __init__(self, *args, **kwds): # Constructor for ComponentList needs to # go last in order to handle any initialization # iterable as an argument - ComponentList.__init__(self, _ConstraintData, *args, **kwds) + ComponentList.__init__(self, ConstraintData, *args, **kwds) class XObjectiveList(ComponentList, IndexedObjective): diff --git a/pyomo/core/plugins/transform/add_slack_vars.py b/pyomo/core/plugins/transform/add_slack_vars.py index 6b5096d315c..0007f8de7ad 100644 --- a/pyomo/core/plugins/transform/add_slack_vars.py +++ b/pyomo/core/plugins/transform/add_slack_vars.py @@ -23,7 +23,7 @@ from pyomo.core.plugins.transform.hierarchy import NonIsomorphicTransformation from pyomo.common.config import ConfigBlock, ConfigValue from pyomo.core.base import ComponentUID -from pyomo.core.base.constraint import _ConstraintData +from pyomo.core.base.constraint import ConstraintData from pyomo.common.deprecation import deprecation_warning @@ -42,7 +42,7 @@ def target_list(x): # [ESJ 07/15/2020] We have to just pass it through because we need the # instance in order to be able to do anything about it... return [x] - elif isinstance(x, (Constraint, _ConstraintData)): + elif isinstance(x, (Constraint, ConstraintData)): return [x] elif hasattr(x, '__iter__'): ans = [] @@ -53,7 +53,7 @@ def target_list(x): deprecation_msg = None # same as above... ans.append(i) - elif isinstance(i, (Constraint, _ConstraintData)): + elif isinstance(i, (Constraint, ConstraintData)): ans.append(i) else: raise ValueError( diff --git a/pyomo/core/plugins/transform/equality_transform.py b/pyomo/core/plugins/transform/equality_transform.py index a1a1b72f146..99291c2227c 100644 --- a/pyomo/core/plugins/transform/equality_transform.py +++ b/pyomo/core/plugins/transform/equality_transform.py @@ -66,7 +66,7 @@ def _create_using(self, model, **kwds): con = equality.__getattribute__(con_name) # - # Get all _ConstraintData objects + # Get all ConstraintData objects # # We need to get the keys ahead of time because we are modifying # con._data on-the-fly. @@ -104,7 +104,7 @@ def _create_using(self, model, **kwds): con.add(ub_name, new_expr) # Since we explicitly `continue` for equality constraints, we - # can safely remove the old _ConstraintData object + # can safely remove the old ConstraintData object del con._data[ndx] return equality.create() diff --git a/pyomo/core/plugins/transform/model.py b/pyomo/core/plugins/transform/model.py index db8376afd29..7ee268a4292 100644 --- a/pyomo/core/plugins/transform/model.py +++ b/pyomo/core/plugins/transform/model.py @@ -55,8 +55,8 @@ def to_standard_form(self): # N.B. Structure hierarchy: # # active_components: {class: {attr_name: object}} - # object -> Constraint: ._data: {ndx: _ConstraintData} - # _ConstraintData: .lower, .body, .upper + # object -> Constraint: ._data: {ndx: ConstraintData} + # ConstraintData: .lower, .body, .upper # # So, altogether, we access a lower bound via # diff --git a/pyomo/core/plugins/transform/scaling.py b/pyomo/core/plugins/transform/scaling.py index ad894b31fde..6b83a2378d1 100644 --- a/pyomo/core/plugins/transform/scaling.py +++ b/pyomo/core/plugins/transform/scaling.py @@ -15,7 +15,7 @@ Var, Constraint, Objective, - _ConstraintData, + ConstraintData, _ObjectiveData, Suffix, value, @@ -197,7 +197,7 @@ def _apply_to(self, model, rename=True): already_scaled.add(id(c)) # perform the constraint/objective scaling and variable sub scaling_factor = component_scaling_factor_map[c] - if isinstance(c, _ConstraintData): + if isinstance(c, ConstraintData): body = scaling_factor * replace_expressions( expr=c.body, substitution_map=variable_substitution_dict, diff --git a/pyomo/core/tests/unit/test_con.py b/pyomo/core/tests/unit/test_con.py index 6ed19c1bcfd..2fa6c24de9c 100644 --- a/pyomo/core/tests/unit/test_con.py +++ b/pyomo/core/tests/unit/test_con.py @@ -1388,7 +1388,7 @@ def test_empty_singleton(self): # Even though we construct a ScalarConstraint, # if it is not initialized that means it is "empty" # and we should encounter errors when trying to access the - # _ConstraintData interface methods until we assign + # ConstraintData interface methods until we assign # something to the constraint. # self.assertEqual(a._constructed, True) diff --git a/pyomo/gdp/disjunct.py b/pyomo/gdp/disjunct.py index e6d8d709425..de021d37547 100644 --- a/pyomo/gdp/disjunct.py +++ b/pyomo/gdp/disjunct.py @@ -542,7 +542,7 @@ def __init__(self, component=None): # # These lines represent in-lining of the # following constructors: - # - _ConstraintData, + # - ConstraintData, # - ActiveComponentData # - ComponentData self._component = weakref_ref(component) if (component is not None) else None diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index 5b9d2ad08a9..134a3d16d66 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -750,20 +750,20 @@ def _transform_constraint( if obj.is_indexed(): newConstraint.add((name, i, 'eq'), newConsExpr) - # map the _ConstraintDatas (we mapped the container above) + # map the ConstraintDatas (we mapped the container above) 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) - # map to the _ConstraintData (And yes, for + # map to the ConstraintData (And yes, for # ScalarConstraints, this is overwriting the map to the # container we made above, and that is what I want to # happen. ScalarConstraints will map to lists. For # IndexedConstraints, we can map the container to the # container, but more importantly, we are mapping the - # _ConstraintDatas to each other above) + # ConstraintDatas to each other above) constraint_map.transformed_constraints[c].append( newConstraint[name, 'eq'] ) diff --git a/pyomo/gdp/tests/test_bigm.py b/pyomo/gdp/tests/test_bigm.py index c6ac49f6d36..bf0239d15e0 100644 --- a/pyomo/gdp/tests/test_bigm.py +++ b/pyomo/gdp/tests/test_bigm.py @@ -27,7 +27,7 @@ value, ) from pyomo.gdp import Disjunct, Disjunction, GDP_Error -from pyomo.core.base import constraint, _ConstraintData +from pyomo.core.base import constraint, ConstraintData from pyomo.core.expr.compare import ( assertExpressionsEqual, assertExpressionsStructurallyEqual, @@ -653,14 +653,14 @@ def test_disjunct_and_constraint_maps(self): if src[0]: # equality self.assertEqual(len(transformed), 2) - self.assertIsInstance(transformed[0], _ConstraintData) - self.assertIsInstance(transformed[1], _ConstraintData) + self.assertIsInstance(transformed[0], ConstraintData) + self.assertIsInstance(transformed[1], ConstraintData) self.assertIs(bigm.get_src_constraint(transformed[0]), srcDisjunct.c) self.assertIs(bigm.get_src_constraint(transformed[1]), srcDisjunct.c) else: # >= self.assertEqual(len(transformed), 1) - self.assertIsInstance(transformed[0], _ConstraintData) + self.assertIsInstance(transformed[0], ConstraintData) # check reverse map from the container self.assertIs(bigm.get_src_constraint(transformed[0]), srcDisjunct.c) diff --git a/pyomo/gdp/util.py b/pyomo/gdp/util.py index 55d273938c5..2164671ea16 100644 --- a/pyomo/gdp/util.py +++ b/pyomo/gdp/util.py @@ -525,13 +525,13 @@ def get_transformed_constraints(srcConstraint): Parameters ---------- - srcConstraint: ScalarConstraint or _ConstraintData, which must be in + srcConstraint: ScalarConstraint or ConstraintData, which must be in the subtree of a transformed Disjunct """ if srcConstraint.is_indexed(): raise GDP_Error( "Argument to get_transformed_constraint should be " - "a ScalarConstraint or _ConstraintData. (If you " + "a ScalarConstraint or ConstraintData. (If you " "want the container for all transformed constraints " "from an IndexedDisjunction, this is the parent " "component of a transformed constraint originating " diff --git a/pyomo/repn/beta/matrix.py b/pyomo/repn/beta/matrix.py index 916b0daf755..0201c46eb18 100644 --- a/pyomo/repn/beta/matrix.py +++ b/pyomo/repn/beta/matrix.py @@ -24,7 +24,7 @@ Constraint, IndexedConstraint, ScalarConstraint, - _ConstraintData, + ConstraintData, ) from pyomo.core.expr.numvalue import native_numeric_types from pyomo.repn import generate_standard_repn @@ -247,7 +247,7 @@ def _get_bound(exp): constraint_containers_removed += 1 for constraint, index in constraint_data_to_remove: # Note that this del is not needed: assigning Constraint.Skip - # above removes the _ConstraintData from the _data dict. + # above removes the ConstraintData from the _data dict. # del constraint[index] constraints_removed += 1 for block, constraint in constraint_containers_to_remove: @@ -348,12 +348,12 @@ def _get_bound(exp): ) -# class _LinearConstraintData(_ConstraintData,LinearCanonicalRepn): +# class _LinearConstraintData(ConstraintData,LinearCanonicalRepn): # # This change breaks this class, but it's unclear whether this # is being used... # -class _LinearConstraintData(_ConstraintData): +class _LinearConstraintData(ConstraintData): """ This class defines the data for a single linear constraint in canonical form. @@ -393,7 +393,7 @@ def __init__(self, index, component=None): # # These lines represent in-lining of the # following constructors: - # - _ConstraintData, + # - ConstraintData, # - ActiveComponentData # - ComponentData self._component = weakref_ref(component) if (component is not None) else None @@ -442,7 +442,7 @@ def __init__(self, index, component=None): # These lines represent in-lining of the # following constructors: # - _LinearConstraintData - # - _ConstraintData, + # - ConstraintData, # - ActiveComponentData # - ComponentData self._component = weakref_ref(component) if (component is not None) else None @@ -584,7 +584,7 @@ def constant(self): return sum(terms) # - # Abstract Interface (_ConstraintData) + # Abstract Interface (ConstraintData) # @property diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index ee5b65149ae..29d841248da 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -69,7 +69,7 @@ minimize, ) from pyomo.core.base.component import ActiveComponent -from pyomo.core.base.constraint import _ConstraintData +from pyomo.core.base.constraint import ConstraintData from pyomo.core.base.expression import ScalarExpression, _GeneralExpressionData from pyomo.core.base.objective import ( ScalarObjective, @@ -134,7 +134,7 @@ class NLWriterInfo(object): The list of (unfixed) Pyomo model variables in the order written to the NL file - constraints: List[_ConstraintData] + constraints: List[ConstraintData] The list of (active) Pyomo model constraints in the order written to the NL file @@ -466,7 +466,7 @@ def compile(self, column_order, row_order, obj_order, model_id): self.obj[obj_order[_id]] = val elif _id == model_id: self.prob[0] = val - elif isinstance(obj, (_VarData, _ConstraintData, _ObjectiveData)): + elif isinstance(obj, (_VarData, ConstraintData, _ObjectiveData)): missing_component_data.add(obj) elif isinstance(obj, (Var, Constraint, Objective)): # Expand this indexed component to store the diff --git a/pyomo/repn/plugins/standard_form.py b/pyomo/repn/plugins/standard_form.py index 239cd845930..e6dc217acc9 100644 --- a/pyomo/repn/plugins/standard_form.py +++ b/pyomo/repn/plugins/standard_form.py @@ -76,11 +76,11 @@ class LinearStandardFormInfo(object): The constraint right-hand sides. - rows : List[Tuple[_ConstraintData, int]] + 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 + (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). diff --git a/pyomo/solvers/plugins/solvers/mosek_persistent.py b/pyomo/solvers/plugins/solvers/mosek_persistent.py index 97f88e0cb9a..9e7f8de1b41 100644 --- a/pyomo/solvers/plugins/solvers/mosek_persistent.py +++ b/pyomo/solvers/plugins/solvers/mosek_persistent.py @@ -85,7 +85,7 @@ def add_constraints(self, con_seq): Parameters ---------- - con_seq: tuple/list of Constraint (scalar Constraint or single _ConstraintData) + con_seq: tuple/list of Constraint (scalar Constraint or single ConstraintData) """ self._add_constraints(con_seq) @@ -137,7 +137,7 @@ def remove_constraint(self, solver_con): To remove a conic-domain, you should use the remove_block method. Parameters ---------- - solver_con: Constraint (scalar Constraint or single _ConstraintData) + solver_con: Constraint (scalar Constraint or single ConstraintData) """ self.remove_constraints(solver_con) @@ -151,7 +151,7 @@ def remove_constraints(self, *solver_cons): Parameters ---------- - *solver_cons: Constraint (scalar Constraint or single _ConstraintData) + *solver_cons: Constraint (scalar Constraint or single ConstraintData) """ lq_cons = tuple( itertools.filterfalse(lambda x: isinstance(x, _ConicBase), solver_cons) @@ -205,7 +205,7 @@ def update_vars(self, *solver_vars): changing variable types and bounds. Parameters ---------- - *solver_var: Constraint (scalar Constraint or single _ConstraintData) + *solver_var: Constraint (scalar Constraint or single ConstraintData) """ try: var_ids = [] diff --git a/pyomo/solvers/plugins/solvers/persistent_solver.py b/pyomo/solvers/plugins/solvers/persistent_solver.py index d69c050291b..79cd669dd71 100644 --- a/pyomo/solvers/plugins/solvers/persistent_solver.py +++ b/pyomo/solvers/plugins/solvers/persistent_solver.py @@ -132,7 +132,7 @@ def add_constraint(self, con): Parameters ---------- - con: Constraint (scalar Constraint or single _ConstraintData) + con: Constraint (scalar Constraint or single ConstraintData) """ if self._pyomo_model is None: @@ -208,7 +208,7 @@ def add_column(self, model, var, obj_coef, constraints, coefficients): model: pyomo ConcreteModel to which the column will be added var: Var (scalar Var or single _VarData) obj_coef: float, pyo.Param - constraints: list of scalar Constraints of single _ConstraintDatas + constraints: list of scalar Constraints of single ConstraintDatas coefficients: list of the coefficient to put on var in the associated constraint """ @@ -328,7 +328,7 @@ def remove_constraint(self, con): Parameters ---------- - con: Constraint (scalar Constraint or single _ConstraintData) + con: Constraint (scalar Constraint or single ConstraintData) """ # see PR #366 for discussion about handling indexed diff --git a/pyomo/solvers/tests/checks/test_CPLEXPersistent.py b/pyomo/solvers/tests/checks/test_CPLEXPersistent.py index 91a60eee9dd..442212d4fbb 100644 --- a/pyomo/solvers/tests/checks/test_CPLEXPersistent.py +++ b/pyomo/solvers/tests/checks/test_CPLEXPersistent.py @@ -101,7 +101,7 @@ def test_add_column_exceptions(self): # add indexed constraint self.assertRaises(AttributeError, opt.add_column, m, m.y, -2, [m.ci], [1]) - # add something not a _ConstraintData + # add something not a ConstraintData self.assertRaises(AttributeError, opt.add_column, m, m.y, -2, [m.x], [1]) # constraint not on solver model diff --git a/pyomo/solvers/tests/checks/test_gurobi_persistent.py b/pyomo/solvers/tests/checks/test_gurobi_persistent.py index a2c089207e5..812390c23a4 100644 --- a/pyomo/solvers/tests/checks/test_gurobi_persistent.py +++ b/pyomo/solvers/tests/checks/test_gurobi_persistent.py @@ -382,7 +382,7 @@ def test_add_column_exceptions(self): # add indexed constraint self.assertRaises(AttributeError, opt.add_column, m, m.y, -2, [m.ci], [1]) - # add something not a _ConstraintData + # add something not a ConstraintData self.assertRaises(AttributeError, opt.add_column, m, m.y, -2, [m.x], [1]) # constraint not on solver model diff --git a/pyomo/solvers/tests/checks/test_xpress_persistent.py b/pyomo/solvers/tests/checks/test_xpress_persistent.py index ddae860cd92..dcd36780f62 100644 --- a/pyomo/solvers/tests/checks/test_xpress_persistent.py +++ b/pyomo/solvers/tests/checks/test_xpress_persistent.py @@ -262,7 +262,7 @@ def test_add_column_exceptions(self): # add indexed constraint self.assertRaises(AttributeError, opt.add_column, m, m.y, -2, [m.ci], [1]) - # add something not a _ConstraintData + # add something not a ConstraintData self.assertRaises(AttributeError, opt.add_column, m, m.y, -2, [m.x], [1]) # constraint not on solver model diff --git a/pyomo/util/calc_var_value.py b/pyomo/util/calc_var_value.py index b5e620fea07..d5bceb5c67b 100644 --- a/pyomo/util/calc_var_value.py +++ b/pyomo/util/calc_var_value.py @@ -12,7 +12,7 @@ from pyomo.common.errors import IterationLimitError 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 pyomo.core.base.constraint import Constraint, ConstraintData import logging @@ -55,7 +55,7 @@ def calculate_variable_from_constraint( ----------- variable: :py:class:`_VarData` The variable to solve for - constraint: :py:class:`_ConstraintData` or relational expression or `tuple` + constraint: :py:class:`ConstraintData` or relational expression or `tuple` The equality constraint to use to solve for the variable value. May be a `ConstraintData` object or any valid argument for ``Constraint(expr=<>)`` (i.e., a relational expression or 2- or @@ -81,7 +81,7 @@ def calculate_variable_from_constraint( """ # Leverage all the Constraint logic to process the incoming tuple/expression - if not isinstance(constraint, _ConstraintData): + if not isinstance(constraint, ConstraintData): constraint = Constraint(expr=constraint, name=type(constraint).__name__) constraint.construct() From 61c91d065eaf51cbcef2636a836566777239edc0 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:37:15 -0600 Subject: [PATCH 1444/1797] Renamed _DisjunctData -> DisjunctData --- pyomo/contrib/gdp_bounds/info.py | 4 ++-- pyomo/gdp/disjunct.py | 27 ++++++++++++++++----------- pyomo/gdp/plugins/hull.py | 2 +- pyomo/gdp/tests/test_bigm.py | 2 +- pyomo/gdp/util.py | 4 ++-- 5 files changed, 22 insertions(+), 17 deletions(-) diff --git a/pyomo/contrib/gdp_bounds/info.py b/pyomo/contrib/gdp_bounds/info.py index 6f39af5908d..db3f6d0846d 100644 --- a/pyomo/contrib/gdp_bounds/info.py +++ b/pyomo/contrib/gdp_bounds/info.py @@ -37,8 +37,8 @@ def disjunctive_bound(var, scope): Args: var (_VarData): Variable for which to compute bound scope (Component): The scope in which to compute the bound. If not a - _DisjunctData, it will walk up the tree and use the scope of the - most immediate enclosing _DisjunctData. + DisjunctData, it will walk up the tree and use the scope of the + most immediate enclosing DisjunctData. Returns: numeric: the tighter of either the disjunctive lower bound, the diff --git a/pyomo/gdp/disjunct.py b/pyomo/gdp/disjunct.py index de021d37547..dd9d2b4638c 100644 --- a/pyomo/gdp/disjunct.py +++ b/pyomo/gdp/disjunct.py @@ -412,7 +412,7 @@ def process(arg): return (_Initializer.deferred_value, arg) -class _DisjunctData(BlockData): +class DisjunctData(BlockData): __autoslot_mappers__ = {'_transformation_block': AutoSlots.weakref_mapper} _Block_reserved_words = set() @@ -434,23 +434,28 @@ def __init__(self, component): self._transformation_block = None def activate(self): - super(_DisjunctData, self).activate() + super(DisjunctData, self).activate() self.indicator_var.unfix() def deactivate(self): - super(_DisjunctData, self).deactivate() + super(DisjunctData, self).deactivate() self.indicator_var.fix(False) def _deactivate_without_fixing_indicator(self): - super(_DisjunctData, self).deactivate() + super(DisjunctData, self).deactivate() def _activate_without_unfixing_indicator(self): - super(_DisjunctData, self).activate() + super(DisjunctData, self).activate() + + +class _DisjunctData(metaclass=RenamedClass): + __renamed__new_class__ = DisjunctData + __renamed__version__ = '6.7.2.dev0' @ModelComponentFactory.register("Disjunctive blocks.") class Disjunct(Block): - _ComponentDataClass = _DisjunctData + _ComponentDataClass = DisjunctData def __new__(cls, *args, **kwds): if cls != Disjunct: @@ -475,7 +480,7 @@ def __init__(self, *args, **kwargs): # def _deactivate_without_fixing_indicator(self): # # Ideally, this would be a super call from this class. However, # # doing that would trigger a call to deactivate() on all the - # # _DisjunctData objects (exactly what we want to avoid!) + # # DisjunctData objects (exactly what we want to avoid!) # # # # For the time being, we will do something bad and directly call # # the base class method from where we would otherwise want to @@ -484,7 +489,7 @@ def __init__(self, *args, **kwargs): def _activate_without_unfixing_indicator(self): # Ideally, this would be a super call from this class. However, # doing that would trigger a call to deactivate() on all the - # _DisjunctData objects (exactly what we want to avoid!) + # DisjunctData objects (exactly what we want to avoid!) # # For the time being, we will do something bad and directly call # the base class method from where we would otherwise want to @@ -495,7 +500,7 @@ def _activate_without_unfixing_indicator(self): component_data._activate_without_unfixing_indicator() -class ScalarDisjunct(_DisjunctData, Disjunct): +class ScalarDisjunct(DisjunctData, Disjunct): def __init__(self, *args, **kwds): ## FIXME: This is a HACK to get around a chicken-and-egg issue ## where BlockData creates the indicator_var *before* @@ -503,7 +508,7 @@ def __init__(self, *args, **kwds): self._defer_construction = True self._suppress_ctypes = set() - _DisjunctData.__init__(self, self) + DisjunctData.__init__(self, self) Disjunct.__init__(self, *args, **kwds) self._data[None] = self self._index = UnindexedComponent_index @@ -524,7 +529,7 @@ def active(self): return any(d.active for d in self._data.values()) -_DisjunctData._Block_reserved_words = set(dir(Disjunct())) +DisjunctData._Block_reserved_words = set(dir(Disjunct())) class _DisjunctionData(ActiveComponentData): diff --git a/pyomo/gdp/plugins/hull.py b/pyomo/gdp/plugins/hull.py index 134a3d16d66..854366c0cf0 100644 --- a/pyomo/gdp/plugins/hull.py +++ b/pyomo/gdp/plugins/hull.py @@ -42,7 +42,7 @@ Binary, ) from pyomo.gdp import Disjunct, Disjunction, GDP_Error -from pyomo.gdp.disjunct import _DisjunctData +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 ( diff --git a/pyomo/gdp/tests/test_bigm.py b/pyomo/gdp/tests/test_bigm.py index bf0239d15e0..d5dcef3ba58 100644 --- a/pyomo/gdp/tests/test_bigm.py +++ b/pyomo/gdp/tests/test_bigm.py @@ -2196,7 +2196,7 @@ def test_do_not_assume_nested_indicators_local(self): class IndexedDisjunction(unittest.TestCase): # this tests that if the targets are a subset of the - # _DisjunctDatas in an IndexedDisjunction that the xor constraint + # DisjunctDatas in an IndexedDisjunction that the xor constraint # created on the parent block will still be indexed as expected. def test_xor_constraint(self): ct.check_indexed_xor_constraints_with_targets(self, 'bigm') diff --git a/pyomo/gdp/util.py b/pyomo/gdp/util.py index 2164671ea16..686253b0179 100644 --- a/pyomo/gdp/util.py +++ b/pyomo/gdp/util.py @@ -10,7 +10,7 @@ # ___________________________________________________________________________ from pyomo.gdp import GDP_Error, Disjunction -from pyomo.gdp.disjunct import _DisjunctData, Disjunct +from pyomo.gdp.disjunct import DisjunctData, Disjunct import pyomo.core.expr as EXPR from pyomo.core.base.component import _ComponentBase @@ -493,7 +493,7 @@ def get_src_constraint(transformedConstraint): def _find_parent_disjunct(constraint): # traverse up until we find the disjunct this constraint lives on parent_disjunct = constraint.parent_block() - while not isinstance(parent_disjunct, _DisjunctData): + while not isinstance(parent_disjunct, DisjunctData): if parent_disjunct is None: raise GDP_Error( "Constraint '%s' is not on a disjunct and so was not " From 47a7e26da00a1520e5903e16baeb6ce076b155c6 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:37:32 -0600 Subject: [PATCH 1445/1797] Renamed _DisjunctionData -> DisjunctionData --- pyomo/core/base/component.py | 2 +- pyomo/gdp/disjunct.py | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/pyomo/core/base/component.py b/pyomo/core/base/component.py index 7d6fc903632..dcaf976356b 100644 --- a/pyomo/core/base/component.py +++ b/pyomo/core/base/component.py @@ -805,7 +805,7 @@ class ComponentData(_ComponentBase): # classes: BooleanVarData, ConnectorData, ConstraintData, # _GeneralExpressionData, _LogicalConstraintData, # _GeneralLogicalConstraintData, _GeneralObjectiveData, - # _ParamData,_GeneralVarData, _GeneralBooleanVarData, _DisjunctionData, + # _ParamData,_GeneralVarData, _GeneralBooleanVarData, DisjunctionData, # ArcData, _PortData, _LinearConstraintData, and # _LinearMatrixConstraintData. Changes made here need to be made in those # constructors as well! diff --git a/pyomo/gdp/disjunct.py b/pyomo/gdp/disjunct.py index dd9d2b4638c..658ead27783 100644 --- a/pyomo/gdp/disjunct.py +++ b/pyomo/gdp/disjunct.py @@ -532,7 +532,7 @@ def active(self): DisjunctData._Block_reserved_words = set(dir(Disjunct())) -class _DisjunctionData(ActiveComponentData): +class DisjunctionData(ActiveComponentData): __slots__ = ('disjuncts', 'xor', '_algebraic_constraint', '_transformation_map') __autoslot_mappers__ = {'_algebraic_constraint': AutoSlots.weakref_mapper} _NoArgument = (0,) @@ -625,9 +625,14 @@ def set_value(self, expr): self.disjuncts.append(disjunct) +class _DisjunctionData(metaclass=RenamedClass): + __renamed__new_class__ = DisjunctionData + __renamed__version__ = '6.7.2.dev0' + + @ModelComponentFactory.register("Disjunction expressions.") class Disjunction(ActiveIndexedComponent): - _ComponentDataClass = _DisjunctionData + _ComponentDataClass = DisjunctionData def __new__(cls, *args, **kwds): if cls != Disjunction: @@ -768,9 +773,9 @@ def _pprint(self): ) -class ScalarDisjunction(_DisjunctionData, Disjunction): +class ScalarDisjunction(DisjunctionData, Disjunction): def __init__(self, *args, **kwds): - _DisjunctionData.__init__(self, component=self) + DisjunctionData.__init__(self, component=self) Disjunction.__init__(self, *args, **kwds) self._index = UnindexedComponent_index @@ -781,7 +786,7 @@ def __init__(self, *args, **kwds): # currently in place). So during initialization only, we will # treat them as "indexed" objects where things like # Constraint.Skip are managed. But after that they will behave - # like _DisjunctionData objects where set_value does not handle + # like DisjunctionData objects where set_value does not handle # Disjunction.Skip but expects a valid expression or None. # From 4320bc1c068ea2b812f22e149378fe9fc7770291 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:38:42 -0600 Subject: [PATCH 1446/1797] Renamed _ExpressionData -> ExpressionData --- pyomo/contrib/mcpp/pyomo_mcpp.py | 4 ++-- pyomo/core/base/__init__.py | 2 +- pyomo/core/base/expression.py | 11 ++++++++--- pyomo/core/base/objective.py | 4 ++-- pyomo/core/beta/dict_objects.py | 4 ++-- pyomo/core/beta/list_objects.py | 4 ++-- pyomo/gdp/tests/test_util.py | 6 +++--- pyomo/repn/plugins/ampl/ampl_.py | 4 ++-- pyomo/repn/standard_repn.py | 6 +++--- pyomo/repn/util.py | 4 ++-- 10 files changed, 27 insertions(+), 22 deletions(-) diff --git a/pyomo/contrib/mcpp/pyomo_mcpp.py b/pyomo/contrib/mcpp/pyomo_mcpp.py index 35e883f98da..1375ae61c50 100644 --- a/pyomo/contrib/mcpp/pyomo_mcpp.py +++ b/pyomo/contrib/mcpp/pyomo_mcpp.py @@ -20,7 +20,7 @@ from pyomo.common.fileutils import Library from pyomo.core import value, Expression from pyomo.core.base.block import SubclassOf -from pyomo.core.base.expression import _ExpressionData +from pyomo.core.base.expression import ExpressionData from pyomo.core.expr.numvalue import nonpyomo_leaf_types from pyomo.core.expr.numeric_expr import ( AbsExpression, @@ -307,7 +307,7 @@ def exitNode(self, node, data): ans = self.mcpp.newConstant(node) elif not node.is_expression_type(): ans = self.register_num(node) - elif type(node) in SubclassOf(Expression) or isinstance(node, _ExpressionData): + elif type(node) in SubclassOf(Expression) or isinstance(node, ExpressionData): ans = data[0] else: raise RuntimeError("Unhandled expression type: %s" % (type(node))) diff --git a/pyomo/core/base/__init__.py b/pyomo/core/base/__init__.py index 9a5337ac2c8..d875065d502 100644 --- a/pyomo/core/base/__init__.py +++ b/pyomo/core/base/__init__.py @@ -36,7 +36,7 @@ from pyomo.core.kernel.objective import minimize, maximize from pyomo.core.base.config import PyomoOptions -from pyomo.core.base.expression import Expression, _ExpressionData +from pyomo.core.base.expression import Expression, ExpressionData from pyomo.core.base.label import ( CuidLabeler, CounterLabeler, diff --git a/pyomo/core/base/expression.py b/pyomo/core/base/expression.py index 3ce998b62a4..e21613fcbb1 100644 --- a/pyomo/core/base/expression.py +++ b/pyomo/core/base/expression.py @@ -36,7 +36,7 @@ logger = logging.getLogger('pyomo.core') -class _ExpressionData(numeric_expr.NumericValue): +class ExpressionData(numeric_expr.NumericValue): """ An object that defines a named expression. @@ -137,13 +137,18 @@ def is_fixed(self): """A boolean indicating whether this expression is fixed.""" raise NotImplementedError - # _ExpressionData should never return False because + # ExpressionData should never return False because # they can store subexpressions that contain variables def is_potentially_variable(self): return True -class _GeneralExpressionDataImpl(_ExpressionData): +class _ExpressionData(metaclass=RenamedClass): + __renamed__new_class__ = ExpressionData + __renamed__version__ = '6.7.2.dev0' + + +class _GeneralExpressionDataImpl(ExpressionData): """ An object that defines an expression that is never cloned diff --git a/pyomo/core/base/objective.py b/pyomo/core/base/objective.py index fcc63755f2b..d259358dcd7 100644 --- a/pyomo/core/base/objective.py +++ b/pyomo/core/base/objective.py @@ -28,7 +28,7 @@ UnindexedComponent_set, rule_wrapper, ) -from pyomo.core.base.expression import _ExpressionData, _GeneralExpressionDataImpl +from pyomo.core.base.expression import ExpressionData, _GeneralExpressionDataImpl from pyomo.core.base.set import Set from pyomo.core.base.initializer import ( Initializer, @@ -86,7 +86,7 @@ def O_rule(model, i, j): # -class _ObjectiveData(_ExpressionData): +class _ObjectiveData(ExpressionData): """ This class defines the data for a single objective. diff --git a/pyomo/core/beta/dict_objects.py b/pyomo/core/beta/dict_objects.py index 53d39939db2..2b23d81e91a 100644 --- a/pyomo/core/beta/dict_objects.py +++ b/pyomo/core/beta/dict_objects.py @@ -17,7 +17,7 @@ from pyomo.core.base.var import IndexedVar, _VarData from pyomo.core.base.constraint import IndexedConstraint, ConstraintData from pyomo.core.base.objective import IndexedObjective, _ObjectiveData -from pyomo.core.base.expression import IndexedExpression, _ExpressionData +from pyomo.core.base.expression import IndexedExpression, ExpressionData from collections.abc import MutableMapping from collections.abc import Mapping @@ -211,4 +211,4 @@ def __init__(self, *args, **kwds): # Constructor for ComponentDict needs to # go last in order to handle any initialization # iterable as an argument - ComponentDict.__init__(self, _ExpressionData, *args, **kwds) + ComponentDict.__init__(self, ExpressionData, *args, **kwds) diff --git a/pyomo/core/beta/list_objects.py b/pyomo/core/beta/list_objects.py index e8b40e6da53..dd199eb70cd 100644 --- a/pyomo/core/beta/list_objects.py +++ b/pyomo/core/beta/list_objects.py @@ -17,7 +17,7 @@ from pyomo.core.base.var import IndexedVar, _VarData from pyomo.core.base.constraint import IndexedConstraint, ConstraintData from pyomo.core.base.objective import IndexedObjective, _ObjectiveData -from pyomo.core.base.expression import IndexedExpression, _ExpressionData +from pyomo.core.base.expression import IndexedExpression, ExpressionData from collections.abc import MutableSequence @@ -259,4 +259,4 @@ def __init__(self, *args, **kwds): # Constructor for ComponentList needs to # go last in order to handle any initialization # iterable as an argument - ComponentList.__init__(self, _ExpressionData, *args, **kwds) + ComponentList.__init__(self, ExpressionData, *args, **kwds) diff --git a/pyomo/gdp/tests/test_util.py b/pyomo/gdp/tests/test_util.py index fd555fc2f59..8ea72af37da 100644 --- a/pyomo/gdp/tests/test_util.py +++ b/pyomo/gdp/tests/test_util.py @@ -13,7 +13,7 @@ from pyomo.core import ConcreteModel, Var, Expression, Block, RangeSet, Any import pyomo.core.expr as EXPR -from pyomo.core.base.expression import _ExpressionData +from pyomo.core.base.expression import ExpressionData from pyomo.gdp.util import ( clone_without_expression_components, is_child_of, @@ -40,7 +40,7 @@ def test_clone_without_expression_components(self): test = clone_without_expression_components(base, {}) self.assertIsNot(base, test) self.assertEqual(base(), test()) - self.assertIsInstance(base, _ExpressionData) + self.assertIsInstance(base, ExpressionData) self.assertIsInstance(test, EXPR.SumExpression) test = clone_without_expression_components(base, {id(m.x): m.y}) self.assertEqual(3**2 + 3 - 1, test()) @@ -51,7 +51,7 @@ def test_clone_without_expression_components(self): self.assertEqual(base(), test()) self.assertIsInstance(base, EXPR.SumExpression) self.assertIsInstance(test, EXPR.SumExpression) - self.assertIsInstance(base.arg(0), _ExpressionData) + self.assertIsInstance(base.arg(0), ExpressionData) self.assertIsInstance(test.arg(0), EXPR.SumExpression) test = clone_without_expression_components(base, {id(m.x): m.y}) self.assertEqual(3**2 + 3 - 1 + 3, test()) diff --git a/pyomo/repn/plugins/ampl/ampl_.py b/pyomo/repn/plugins/ampl/ampl_.py index f422a085a3c..c6357cbecd9 100644 --- a/pyomo/repn/plugins/ampl/ampl_.py +++ b/pyomo/repn/plugins/ampl/ampl_.py @@ -33,7 +33,7 @@ from pyomo.core.base import ( SymbolMap, NameLabeler, - _ExpressionData, + ExpressionData, SortComponents, var, param, @@ -724,7 +724,7 @@ def _print_nonlinear_terms_NL(self, exp): self._print_nonlinear_terms_NL(exp.arg(0)) self._print_nonlinear_terms_NL(exp.arg(1)) - elif isinstance(exp, (_ExpressionData, IIdentityExpression)): + elif isinstance(exp, (ExpressionData, IIdentityExpression)): self._print_nonlinear_terms_NL(exp.expr) else: diff --git a/pyomo/repn/standard_repn.py b/pyomo/repn/standard_repn.py index 455e7bd9444..70368dd3d7e 100644 --- a/pyomo/repn/standard_repn.py +++ b/pyomo/repn/standard_repn.py @@ -20,7 +20,7 @@ import pyomo.core.expr as EXPR from pyomo.core.expr.numvalue import NumericConstant from pyomo.core.base.objective import _GeneralObjectiveData, ScalarObjective -from pyomo.core.base import _ExpressionData, Expression +from pyomo.core.base import ExpressionData, Expression from pyomo.core.base.expression import ScalarExpression, _GeneralExpressionData from pyomo.core.base.var import ScalarVar, Var, _GeneralVarData, value from pyomo.core.base.param import ScalarParam, _ParamData @@ -1152,7 +1152,7 @@ def _collect_external_fn(exp, multiplier, idMap, compute_values, verbose, quadra ScalarExpression: _collect_identity, expression: _collect_identity, noclone: _collect_identity, - _ExpressionData: _collect_identity, + ExpressionData: _collect_identity, Expression: _collect_identity, _GeneralObjectiveData: _collect_identity, ScalarObjective: _collect_identity, @@ -1551,7 +1551,7 @@ def _linear_collect_pow(exp, multiplier, idMap, compute_values, verbose, coef): ScalarExpression : _linear_collect_identity, expression : _linear_collect_identity, noclone : _linear_collect_identity, - _ExpressionData : _linear_collect_identity, + ExpressionData : _linear_collect_identity, Expression : _linear_collect_identity, _GeneralObjectiveData : _linear_collect_identity, ScalarObjective : _linear_collect_identity, diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index b4a21a2108f..7351ea51c58 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -40,7 +40,7 @@ SortComponents, ) from pyomo.core.base.component import ActiveComponent -from pyomo.core.base.expression import _ExpressionData +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 @@ -55,7 +55,7 @@ EXPR.NPV_SumExpression, } _named_subexpression_types = ( - _ExpressionData, + ExpressionData, kernel.expression.expression, kernel.objective.objective, ) From 86192304769e54cfcd3900e7878d71a767b5446a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:40:16 -0600 Subject: [PATCH 1447/1797] Renamed _FiniteRangeSetData -> FiniteRangeSetData --- pyomo/core/base/set.py | 17 +++++++++++------ pyomo/core/tests/unit/test_set.py | 6 +++--- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index b3277ab3260..319f3b0e5ae 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -2648,7 +2648,7 @@ def ranges(self): return iter(self._ranges) -class _FiniteRangeSetData( +class FiniteRangeSetData( _SortedSetMixin, _OrderedSetMixin, _FiniteSetMixin, _InfiniteRangeSetData ): __slots__ = () @@ -2672,7 +2672,7 @@ def _iter_impl(self): # iterate over it nIters = len(self._ranges) - 1 if not nIters: - yield from _FiniteRangeSetData._range_gen(self._ranges[0]) + yield from FiniteRangeSetData._range_gen(self._ranges[0]) return # The trick here is that we need to remove any duplicates from @@ -2683,7 +2683,7 @@ def _iter_impl(self): for r in self._ranges: # Note: there should always be at least 1 member in each # NumericRange - i = _FiniteRangeSetData._range_gen(r) + i = FiniteRangeSetData._range_gen(r) iters.append([next(i), i]) iters.sort(reverse=True, key=lambda x: x[0]) @@ -2756,6 +2756,11 @@ def ord(self, item): domain = _InfiniteRangeSetData.domain +class _FiniteRangeSetData(metaclass=RenamedClass): + __renamed__new_class__ = FiniteRangeSetData + __renamed__version__ = '6.7.2.dev0' + + @ModelComponentFactory.register( "A sequence of numeric values. RangeSet(start,end,step) is a sequence " "starting a value 'start', and increasing in values by 'step' until a " @@ -3120,7 +3125,7 @@ def construct(self, data=None): old_ranges.reverse() while old_ranges: r = old_ranges.pop() - for i, val in enumerate(_FiniteRangeSetData._range_gen(r)): + for i, val in enumerate(FiniteRangeSetData._range_gen(r)): if not _filter(_block, val): split_r = r.range_difference((NumericRange(val, val, 0),)) if len(split_r) == 2: @@ -3233,9 +3238,9 @@ class InfiniteSimpleRangeSet(metaclass=RenamedClass): __renamed__version__ = '6.0' -class FiniteScalarRangeSet(_ScalarOrderedSetMixin, _FiniteRangeSetData, RangeSet): +class FiniteScalarRangeSet(_ScalarOrderedSetMixin, FiniteRangeSetData, RangeSet): def __init__(self, *args, **kwds): - _FiniteRangeSetData.__init__(self, component=self) + FiniteRangeSetData.__init__(self, component=self) RangeSet.__init__(self, *args, **kwds) self._index = UnindexedComponent_index diff --git a/pyomo/core/tests/unit/test_set.py b/pyomo/core/tests/unit/test_set.py index 4bbac6ecaa0..3639aa82c73 100644 --- a/pyomo/core/tests/unit/test_set.py +++ b/pyomo/core/tests/unit/test_set.py @@ -60,7 +60,7 @@ FiniteSetOf, InfiniteSetOf, RangeSet, - _FiniteRangeSetData, + FiniteRangeSetData, _InfiniteRangeSetData, FiniteScalarRangeSet, InfiniteScalarRangeSet, @@ -1285,13 +1285,13 @@ def test_is_functions(self): self.assertTrue(i.isdiscrete()) self.assertTrue(i.isfinite()) self.assertTrue(i.isordered()) - self.assertIsInstance(i, _FiniteRangeSetData) + self.assertIsInstance(i, FiniteRangeSetData) i = RangeSet(1, 3) self.assertTrue(i.isdiscrete()) self.assertTrue(i.isfinite()) self.assertTrue(i.isordered()) - self.assertIsInstance(i, _FiniteRangeSetData) + self.assertIsInstance(i, FiniteRangeSetData) i = RangeSet(1, 3, 0) self.assertFalse(i.isdiscrete()) From 4ea182da5dab8f6994cbf7bc17a0cd1fc1b2f1ec Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:40:42 -0600 Subject: [PATCH 1448/1797] Renamed _FiniteSetData -> FiniteSetData --- pyomo/core/base/set.py | 17 +++++++++++------ pyomo/core/tests/unit/test_set.py | 8 ++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index 319f3b0e5ae..8db64620d5c 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -1294,7 +1294,7 @@ def ranges(self): yield NonNumericRange(i) -class _FiniteSetData(_FiniteSetMixin, _SetData): +class FiniteSetData(_FiniteSetMixin, _SetData): """A general unordered iterable Set""" __slots__ = ('_values', '_domain', '_validate', '_filter', '_dimen') @@ -1470,6 +1470,11 @@ def pop(self): return self._values.pop() +class _FiniteSetData(metaclass=RenamedClass): + __renamed__new_class__ = FiniteSetData + __renamed__version__ = '6.7.2.dev0' + + class _ScalarOrderedSetMixin(object): # This mixin is required because scalar ordered sets implement # __getitem__() as an alias of at() @@ -1630,7 +1635,7 @@ def _to_0_based_index(self, item): ) -class _OrderedSetData(_OrderedSetMixin, _FiniteSetData): +class _OrderedSetData(_OrderedSetMixin, FiniteSetData): """ This class defines the base class for an ordered set of concrete data. @@ -1652,7 +1657,7 @@ class _OrderedSetData(_OrderedSetMixin, _FiniteSetData): def __init__(self, component): self._values = {} self._ordered_values = [] - _FiniteSetData.__init__(self, component=component) + FiniteSetData.__init__(self, component=component) def _iter_impl(self): """ @@ -2034,7 +2039,7 @@ def __new__(cls, *args, **kwds): elif ordered is Set.SortedOrder: newObj._ComponentDataClass = _SortedSetData else: - newObj._ComponentDataClass = _FiniteSetData + newObj._ComponentDataClass = FiniteSetData return newObj @overload @@ -2388,9 +2393,9 @@ def __getitem__(self, index) -> _SetData: ... __getitem__ = IndexedComponent.__getitem__ # type: ignore -class FiniteScalarSet(_FiniteSetData, Set): +class FiniteScalarSet(FiniteSetData, Set): def __init__(self, **kwds): - _FiniteSetData.__init__(self, component=self) + FiniteSetData.__init__(self, component=self) Set.__init__(self, **kwds) self._index = UnindexedComponent_index diff --git a/pyomo/core/tests/unit/test_set.py b/pyomo/core/tests/unit/test_set.py index 3639aa82c73..a9b9fb9469b 100644 --- a/pyomo/core/tests/unit/test_set.py +++ b/pyomo/core/tests/unit/test_set.py @@ -82,7 +82,7 @@ SetProduct_FiniteSet, SetProduct_OrderedSet, _SetData, - _FiniteSetData, + FiniteSetData, _InsertionOrderSetData, _SortedSetData, _FiniteSetMixin, @@ -4137,9 +4137,9 @@ def test_indexed_set(self): self.assertFalse(m.I[1].isordered()) self.assertFalse(m.I[2].isordered()) self.assertFalse(m.I[3].isordered()) - self.assertIs(type(m.I[1]), _FiniteSetData) - self.assertIs(type(m.I[2]), _FiniteSetData) - self.assertIs(type(m.I[3]), _FiniteSetData) + self.assertIs(type(m.I[1]), FiniteSetData) + self.assertIs(type(m.I[2]), FiniteSetData) + self.assertIs(type(m.I[3]), FiniteSetData) self.assertEqual(m.I.data(), {1: (1,), 2: (2,), 3: (4,)}) # Explicit (constant) construction From 3a86baa8bcb6152edca7c4e687488e6c5de08137 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:41:02 -0600 Subject: [PATCH 1449/1797] Renamed _GeneralBooleanVarData -> GeneralBooleanVarData --- pyomo/contrib/cp/repn/docplex_writer.py | 4 +-- .../logical_to_disjunctive_walker.py | 2 +- pyomo/core/base/__init__.py | 2 +- pyomo/core/base/boolean_var.py | 27 +++++++++++-------- pyomo/core/base/component.py | 2 +- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index 8356a1e752f..510bbf4e398 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -60,7 +60,7 @@ ) from pyomo.core.base.boolean_var import ( ScalarBooleanVar, - _GeneralBooleanVarData, + GeneralBooleanVarData, IndexedBooleanVar, ) from pyomo.core.base.expression import ScalarExpression, _GeneralExpressionData @@ -964,7 +964,7 @@ class LogicalToDoCplex(StreamBasedExpressionVisitor): _GeneralVarData: _before_var, IndexedVar: _before_indexed_var, ScalarBooleanVar: _before_boolean_var, - _GeneralBooleanVarData: _before_boolean_var, + GeneralBooleanVarData: _before_boolean_var, IndexedBooleanVar: _before_indexed_boolean_var, _GeneralExpressionData: _before_named_expression, ScalarExpression: _before_named_expression, diff --git a/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py b/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py index d5f13e91535..548078f55f8 100644 --- a/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py +++ b/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py @@ -209,7 +209,7 @@ def _dispatch_atmost(visitor, node, *args): _before_child_dispatcher = {} _before_child_dispatcher[BV.ScalarBooleanVar] = _dispatch_boolean_var -_before_child_dispatcher[BV._GeneralBooleanVarData] = _dispatch_boolean_var +_before_child_dispatcher[BV.GeneralBooleanVarData] = _dispatch_boolean_var _before_child_dispatcher[AutoLinkedBooleanVar] = _dispatch_boolean_var _before_child_dispatcher[_ParamData] = _dispatch_param _before_child_dispatcher[ScalarParam] = _dispatch_param diff --git a/pyomo/core/base/__init__.py b/pyomo/core/base/__init__.py index d875065d502..bb62cb96782 100644 --- a/pyomo/core/base/__init__.py +++ b/pyomo/core/base/__init__.py @@ -61,7 +61,7 @@ from pyomo.core.base.boolean_var import ( BooleanVar, BooleanVarData, - _GeneralBooleanVarData, + GeneralBooleanVarData, BooleanVarList, ScalarBooleanVar, ) diff --git a/pyomo/core/base/boolean_var.py b/pyomo/core/base/boolean_var.py index bf9d6159754..287851a7f7e 100644 --- a/pyomo/core/base/boolean_var.py +++ b/pyomo/core/base/boolean_var.py @@ -194,7 +194,7 @@ def _associated_binary_mapper(encode, val): return val -class _GeneralBooleanVarData(BooleanVarData): +class GeneralBooleanVarData(BooleanVarData): """ This class defines the data for a single Boolean variable. @@ -271,13 +271,13 @@ def stale(self, val): def get_associated_binary(self): """Get the binary _VarData associated with this - _GeneralBooleanVarData""" + GeneralBooleanVarData""" return ( self._associated_binary() if self._associated_binary is not None else None ) def associate_binary_var(self, binary_var): - """Associate a binary _VarData to this _GeneralBooleanVarData""" + """Associate a binary _VarData to this GeneralBooleanVarData""" if ( self._associated_binary is not None and type(self._associated_binary) @@ -300,6 +300,11 @@ def associate_binary_var(self, binary_var): self._associated_binary = weakref_ref(binary_var) +class _GeneralBooleanVarData(metaclass=RenamedClass): + __renamed__new_class__ = GeneralBooleanVarData + __renamed__version__ = '6.7.2.dev0' + + @ModelComponentFactory.register("Logical decision variables.") class BooleanVar(IndexedComponent): """A logical variable, which may be defined over an index. @@ -314,7 +319,7 @@ class BooleanVar(IndexedComponent): to True. """ - _ComponentDataClass = _GeneralBooleanVarData + _ComponentDataClass = GeneralBooleanVarData def __new__(cls, *args, **kwds): if cls != BooleanVar: @@ -506,11 +511,11 @@ def _pprint(self): ) -class ScalarBooleanVar(_GeneralBooleanVarData, BooleanVar): +class ScalarBooleanVar(GeneralBooleanVarData, BooleanVar): """A single variable.""" def __init__(self, *args, **kwd): - _GeneralBooleanVarData.__init__(self, component=self) + GeneralBooleanVarData.__init__(self, component=self) BooleanVar.__init__(self, *args, **kwd) self._index = UnindexedComponent_index @@ -526,7 +531,7 @@ def __init__(self, *args, **kwd): def value(self): """Return the value for this variable.""" if self._constructed: - return _GeneralBooleanVarData.value.fget(self) + return GeneralBooleanVarData.value.fget(self) raise ValueError( "Accessing the value of variable '%s' " "before the Var has been constructed (there " @@ -537,7 +542,7 @@ def value(self): def value(self, val): """Set the value for this variable.""" if self._constructed: - return _GeneralBooleanVarData.value.fset(self, val) + return GeneralBooleanVarData.value.fset(self, val) raise ValueError( "Setting the value of variable '%s' " "before the Var has been constructed (there " @@ -546,7 +551,7 @@ def value(self, val): @property def domain(self): - return _GeneralBooleanVarData.domain.fget(self) + return GeneralBooleanVarData.domain.fget(self) def fix(self, value=NOTSET, skip_validation=False): """ @@ -554,7 +559,7 @@ def fix(self, value=NOTSET, skip_validation=False): indicating the variable should be fixed at its current value. """ if self._constructed: - return _GeneralBooleanVarData.fix(self, value, skip_validation) + return GeneralBooleanVarData.fix(self, value, skip_validation) raise ValueError( "Fixing variable '%s' " "before the Var has been constructed (there " @@ -564,7 +569,7 @@ def fix(self, value=NOTSET, skip_validation=False): def unfix(self): """Sets the fixed indicator to False.""" if self._constructed: - return _GeneralBooleanVarData.unfix(self) + return GeneralBooleanVarData.unfix(self) raise ValueError( "Freeing variable '%s' " "before the Var has been constructed (there " diff --git a/pyomo/core/base/component.py b/pyomo/core/base/component.py index dcaf976356b..1317c928686 100644 --- a/pyomo/core/base/component.py +++ b/pyomo/core/base/component.py @@ -805,7 +805,7 @@ class ComponentData(_ComponentBase): # classes: BooleanVarData, ConnectorData, ConstraintData, # _GeneralExpressionData, _LogicalConstraintData, # _GeneralLogicalConstraintData, _GeneralObjectiveData, - # _ParamData,_GeneralVarData, _GeneralBooleanVarData, DisjunctionData, + # _ParamData,_GeneralVarData, GeneralBooleanVarData, DisjunctionData, # ArcData, _PortData, _LinearConstraintData, and # _LinearMatrixConstraintData. Changes made here need to be made in those # constructors as well! From 80c064ac99ac5870cc1439f4cd3405f7565a3516 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:42:40 -0600 Subject: [PATCH 1450/1797] Renamed _GeneralConstraintData -> GeneralConstraintData --- pyomo/contrib/appsi/base.py | 48 +++++++++---------- pyomo/contrib/appsi/fbbt.py | 6 +-- pyomo/contrib/appsi/solvers/cbc.py | 6 +-- pyomo/contrib/appsi/solvers/cplex.py | 10 ++-- pyomo/contrib/appsi/solvers/gurobi.py | 16 +++---- pyomo/contrib/appsi/solvers/highs.py | 6 +-- pyomo/contrib/appsi/solvers/ipopt.py | 10 ++-- pyomo/contrib/appsi/solvers/wntr.py | 6 +-- pyomo/contrib/appsi/writers/lp_writer.py | 6 +-- pyomo/contrib/appsi/writers/nl_writer.py | 6 +-- pyomo/contrib/solver/base.py | 10 ++-- pyomo/contrib/solver/gurobi.py | 16 +++---- pyomo/contrib/solver/persistent.py | 12 ++--- pyomo/contrib/solver/solution.py | 14 +++--- .../contrib/solver/tests/unit/test_results.py | 6 +-- pyomo/core/base/constraint.py | 27 ++++++----- pyomo/core/tests/unit/test_con.py | 4 +- pyomo/core/tests/unit/test_dict_objects.py | 4 +- pyomo/core/tests/unit/test_list_objects.py | 4 +- pyomo/gdp/tests/common_tests.py | 4 +- pyomo/gdp/tests/test_bigm.py | 4 +- pyomo/gdp/tests/test_hull.py | 4 +- .../plugins/solvers/gurobi_persistent.py | 10 ++-- 23 files changed, 121 insertions(+), 118 deletions(-) diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index b4ade16a597..d5982fc72e6 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -21,7 +21,7 @@ Tuple, MutableMapping, ) -from pyomo.core.base.constraint import _GeneralConstraintData, Constraint +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 @@ -216,8 +216,8 @@ def get_primals( pass def get_duals( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ) -> Dict[_GeneralConstraintData, float]: + self, cons_to_load: Optional[Sequence[GeneralConstraintData]] = None + ) -> Dict[GeneralConstraintData, float]: """ Returns a dictionary mapping constraint to dual value. @@ -235,8 +235,8 @@ 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]: + self, cons_to_load: Optional[Sequence[GeneralConstraintData]] = None + ) -> Dict[GeneralConstraintData, float]: """ Returns a dictionary mapping constraint to slack. @@ -319,8 +319,8 @@ def get_primals( return primals def get_duals( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ) -> Dict[_GeneralConstraintData, float]: + 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 ' @@ -336,8 +336,8 @@ def get_duals( return duals def get_slacks( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ) -> Dict[_GeneralConstraintData, float]: + 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 ' @@ -731,8 +731,8 @@ def get_primals( pass def get_duals( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ) -> Dict[_GeneralConstraintData, float]: + self, cons_to_load: Optional[Sequence[GeneralConstraintData]] = None + ) -> Dict[GeneralConstraintData, float]: """ Declare sign convention in docstring here. @@ -752,8 +752,8 @@ def get_duals( ) def get_slacks( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ) -> Dict[_GeneralConstraintData, float]: + self, cons_to_load: Optional[Sequence[GeneralConstraintData]] = None + ) -> Dict[GeneralConstraintData, float]: """ Parameters ---------- @@ -807,7 +807,7 @@ def add_params(self, params: List[_ParamData]): pass @abc.abstractmethod - def add_constraints(self, cons: List[_GeneralConstraintData]): + def add_constraints(self, cons: List[GeneralConstraintData]): pass @abc.abstractmethod @@ -823,7 +823,7 @@ def remove_params(self, params: List[_ParamData]): pass @abc.abstractmethod - def remove_constraints(self, cons: List[_GeneralConstraintData]): + def remove_constraints(self, cons: List[GeneralConstraintData]): pass @abc.abstractmethod @@ -857,14 +857,14 @@ def get_primals(self, vars_to_load=None): 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, 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, 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) @@ -984,7 +984,7 @@ def add_params(self, params: List[_ParamData]): self._add_params(params) @abc.abstractmethod - def _add_constraints(self, cons: List[_GeneralConstraintData]): + def _add_constraints(self, cons: List[GeneralConstraintData]): pass def _check_for_new_vars(self, variables: List[_GeneralVarData]): @@ -1004,7 +1004,7 @@ def _check_to_remove_vars(self, variables: List[_GeneralVarData]): vars_to_remove[v_id] = v self.remove_variables(list(vars_to_remove.values())) - def add_constraints(self, cons: List[_GeneralConstraintData]): + def add_constraints(self, cons: List[GeneralConstraintData]): all_fixed_vars = dict() for con in cons: if con in self._named_expressions: @@ -1132,10 +1132,10 @@ def add_block(self, block): self.set_objective(obj) @abc.abstractmethod - def _remove_constraints(self, cons: List[_GeneralConstraintData]): + def _remove_constraints(self, cons: List[GeneralConstraintData]): pass - def remove_constraints(self, cons: List[_GeneralConstraintData]): + def remove_constraints(self, cons: List[GeneralConstraintData]): self._remove_constraints(cons) for con in cons: if con not in self._named_expressions: @@ -1334,7 +1334,7 @@ def update(self, timer: HierarchicalTimer = None): 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) + c.ctype is None and isinstance(c, GeneralConstraintData) ): old_cons.append(c) else: diff --git a/pyomo/contrib/appsi/fbbt.py b/pyomo/contrib/appsi/fbbt.py index c6bbdb5bf3b..121557414b3 100644 --- a/pyomo/contrib/appsi/fbbt.py +++ b/pyomo/contrib/appsi/fbbt.py @@ -20,7 +20,7 @@ from typing import List, Optional from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.param import _ParamData -from pyomo.core.base.constraint import _GeneralConstraintData +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.block import BlockData @@ -154,7 +154,7 @@ def _add_params(self, params: List[_ParamData]): cp = cparams[ndx] cp.name = self._symbol_map.getSymbol(p, self._param_labeler) - def _add_constraints(self, cons: List[_GeneralConstraintData]): + def _add_constraints(self, cons: List[GeneralConstraintData]): cmodel.process_fbbt_constraints( self._cmodel, self._pyomo_expr_types, @@ -175,7 +175,7 @@ def _add_sos_constraints(self, cons: List[_SOSConstraintData]): 'IntervalTightener does not support SOS constraints' ) - def _remove_constraints(self, cons: List[_GeneralConstraintData]): + def _remove_constraints(self, cons: List[GeneralConstraintData]): if self._symbolic_solver_labels: for c in cons: self._symbol_map.removeSymbol(c) diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index 57bbf1b4c21..cc7327df11c 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -27,7 +27,7 @@ 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.constraint import GeneralConstraintData from pyomo.core.base.block import BlockData from pyomo.core.base.param import _ParamData from pyomo.core.base.objective import _GeneralObjectiveData @@ -170,7 +170,7 @@ def add_variables(self, variables: List[_GeneralVarData]): def add_params(self, params: List[_ParamData]): self._writer.add_params(params) - def add_constraints(self, cons: List[_GeneralConstraintData]): + def add_constraints(self, cons: List[GeneralConstraintData]): self._writer.add_constraints(cons) def add_block(self, block: BlockData): @@ -182,7 +182,7 @@ def remove_variables(self, variables: List[_GeneralVarData]): def remove_params(self, params: List[_ParamData]): self._writer.remove_params(params) - def remove_constraints(self, cons: List[_GeneralConstraintData]): + def remove_constraints(self, cons: List[GeneralConstraintData]): self._writer.remove_constraints(cons) def remove_block(self, block: BlockData): diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index 2e04a979fda..222c466fb99 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -23,7 +23,7 @@ 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.constraint import GeneralConstraintData from pyomo.core.base.block import BlockData from pyomo.core.base.param import _ParamData from pyomo.core.base.objective import _GeneralObjectiveData @@ -185,7 +185,7 @@ def add_variables(self, variables: List[_GeneralVarData]): def add_params(self, params: List[_ParamData]): self._writer.add_params(params) - def add_constraints(self, cons: List[_GeneralConstraintData]): + def add_constraints(self, cons: List[GeneralConstraintData]): self._writer.add_constraints(cons) def add_block(self, block: BlockData): @@ -197,7 +197,7 @@ def remove_variables(self, variables: List[_GeneralVarData]): def remove_params(self, params: List[_ParamData]): self._writer.remove_params(params) - def remove_constraints(self, cons: List[_GeneralConstraintData]): + def remove_constraints(self, cons: List[GeneralConstraintData]): self._writer.remove_constraints(cons) def remove_block(self, block: BlockData): @@ -389,8 +389,8 @@ def get_primals( return res def get_duals( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ) -> Dict[_GeneralConstraintData, float]: + self, cons_to_load: Optional[Sequence[GeneralConstraintData]] = None + ) -> Dict[GeneralConstraintData, float]: if ( self._cplex_model.solution.get_solution_type() == self._cplex_model.solution.type.none diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index 1e18862e3bd..e20168034c6 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -24,7 +24,7 @@ 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.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 @@ -579,7 +579,7 @@ def _get_expr_from_pyomo_expr(self, expr): mutable_quadratic_coefficients, ) - def _add_constraints(self, cons: List[_GeneralConstraintData]): + def _add_constraints(self, cons: List[GeneralConstraintData]): for con in cons: conname = self._symbol_map.getSymbol(con, self._labeler) ( @@ -735,7 +735,7 @@ def _add_sos_constraints(self, cons: List[_SOSConstraintData]): self._constraints_added_since_update.update(cons) self._needs_updated = True - def _remove_constraints(self, cons: List[_GeneralConstraintData]): + def _remove_constraints(self, cons: List[GeneralConstraintData]): for con in cons: if con in self._constraints_added_since_update: self._update_gurobi_model() @@ -1195,7 +1195,7 @@ def set_linear_constraint_attr(self, con, attr, val): Parameters ---------- - con: pyomo.core.base.constraint._GeneralConstraintData + con: pyomo.core.base.constraint.GeneralConstraintData The pyomo constraint for which the corresponding gurobi constraint attribute should be modified. attr: str @@ -1272,7 +1272,7 @@ def get_linear_constraint_attr(self, con, attr): Parameters ---------- - con: pyomo.core.base.constraint._GeneralConstraintData + con: pyomo.core.base.constraint.GeneralConstraintData The pyomo constraint for which the corresponding gurobi constraint attribute should be retrieved. attr: str @@ -1304,7 +1304,7 @@ def get_quadratic_constraint_attr(self, con, attr): Parameters ---------- - con: pyomo.core.base.constraint._GeneralConstraintData + con: pyomo.core.base.constraint.GeneralConstraintData The pyomo constraint for which the corresponding gurobi constraint attribute should be retrieved. attr: str @@ -1425,7 +1425,7 @@ def cbCut(self, con): Parameters ---------- - con: pyomo.core.base.constraint._GeneralConstraintData + con: pyomo.core.base.constraint.GeneralConstraintData The cut to add """ if not con.active: @@ -1510,7 +1510,7 @@ def cbLazy(self, con): """ Parameters ---------- - con: pyomo.core.base.constraint._GeneralConstraintData + con: pyomo.core.base.constraint.GeneralConstraintData The lazy constraint to add """ if not con.active: diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index 3612b9d5014..7773d0624b2 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -21,7 +21,7 @@ from pyomo.core.kernel.objective import minimize, maximize from pyomo.core.base import SymbolMap from pyomo.core.base.var import _GeneralVarData -from pyomo.core.base.constraint import _GeneralConstraintData +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 @@ -376,7 +376,7 @@ def set_instance(self, model): if self._objective is None: self.set_objective(None) - def _add_constraints(self, cons: List[_GeneralConstraintData]): + def _add_constraints(self, cons: List[GeneralConstraintData]): self._sol = None if self._last_results_object is not None: self._last_results_object.solution_loader.invalidate() @@ -462,7 +462,7 @@ def _add_sos_constraints(self, cons: List[_SOSConstraintData]): 'Highs interface does not support SOS constraints' ) - def _remove_constraints(self, cons: List[_GeneralConstraintData]): + 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() diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index 19ec5f8031c..75ebb10f719 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -29,7 +29,7 @@ from pyomo.core.expr.visitor import replace_expressions 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.constraint import GeneralConstraintData from pyomo.core.base.block import BlockData from pyomo.core.base.param import _ParamData from pyomo.core.base.objective import _GeneralObjectiveData @@ -234,7 +234,7 @@ def add_variables(self, variables: List[_GeneralVarData]): def add_params(self, params: List[_ParamData]): self._writer.add_params(params) - def add_constraints(self, cons: List[_GeneralConstraintData]): + def add_constraints(self, cons: List[GeneralConstraintData]): self._writer.add_constraints(cons) def add_block(self, block: BlockData): @@ -246,7 +246,7 @@ def remove_variables(self, variables: List[_GeneralVarData]): def remove_params(self, params: List[_ParamData]): self._writer.remove_params(params) - def remove_constraints(self, cons: List[_GeneralConstraintData]): + def remove_constraints(self, cons: List[GeneralConstraintData]): self._writer.remove_constraints(cons) def remove_block(self, block: BlockData): @@ -534,9 +534,7 @@ def get_primals( res[v] = self._primal_sol[v] return res - def get_duals( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ): + def get_duals(self, cons_to_load: Optional[Sequence[GeneralConstraintData]] = None): if ( self._last_results_object is None or self._last_results_object.termination_condition diff --git a/pyomo/contrib/appsi/solvers/wntr.py b/pyomo/contrib/appsi/solvers/wntr.py index 928eda2b514..c11536e2e6f 100644 --- a/pyomo/contrib/appsi/solvers/wntr.py +++ b/pyomo/contrib/appsi/solvers/wntr.py @@ -42,7 +42,7 @@ 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.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 @@ -278,7 +278,7 @@ def _add_params(self, params: List[_ParamData]): 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]): + def _add_constraints(self, cons: List[GeneralConstraintData]): aml = wntr.sim.aml.aml for con in cons: if not con.equality: @@ -294,7 +294,7 @@ def _add_constraints(self, cons: List[_GeneralConstraintData]): self._pyomo_con_to_solver_con_map[con] = wntr_con self._needs_updated = True - def _remove_constraints(self, cons: List[_GeneralConstraintData]): + 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) diff --git a/pyomo/contrib/appsi/writers/lp_writer.py b/pyomo/contrib/appsi/writers/lp_writer.py index 518be5fac99..39298bd1a61 100644 --- a/pyomo/contrib/appsi/writers/lp_writer.py +++ b/pyomo/contrib/appsi/writers/lp_writer.py @@ -12,7 +12,7 @@ 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 +from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.objective import _GeneralObjectiveData from pyomo.core.base.sos import _SOSConstraintData from pyomo.core.base.block import BlockData @@ -99,14 +99,14 @@ def _add_params(self, params: List[_ParamData]): cp.value = p.value self._pyomo_param_to_solver_param_map[id(p)] = cp - def _add_constraints(self, cons: List[_GeneralConstraintData]): + def _add_constraints(self, cons: List[GeneralConstraintData]): cmodel.process_lp_constraints(cons, self) def _add_sos_constraints(self, cons: List[_SOSConstraintData]): if len(cons) != 0: raise NotImplementedError('LP writer does not yet support SOS constraints') - def _remove_constraints(self, cons: List[_GeneralConstraintData]): + def _remove_constraints(self, cons: List[GeneralConstraintData]): for c in cons: cc = self._pyomo_con_to_solver_con_map.pop(c) self._writer.remove_constraint(cc) diff --git a/pyomo/contrib/appsi/writers/nl_writer.py b/pyomo/contrib/appsi/writers/nl_writer.py index 75b026ab521..3e13ef4077a 100644 --- a/pyomo/contrib/appsi/writers/nl_writer.py +++ b/pyomo/contrib/appsi/writers/nl_writer.py @@ -12,7 +12,7 @@ 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 +from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.objective import _GeneralObjectiveData from pyomo.core.base.sos import _SOSConstraintData from pyomo.core.base.block import BlockData @@ -111,7 +111,7 @@ def _add_params(self, params: List[_ParamData]): cp = cparams[ndx] cp.name = self._symbol_map.getSymbol(p, self._param_labeler) - def _add_constraints(self, cons: List[_GeneralConstraintData]): + def _add_constraints(self, cons: List[GeneralConstraintData]): cmodel.process_nl_constraints( self._writer, self._expr_types, @@ -130,7 +130,7 @@ def _add_sos_constraints(self, cons: List[_SOSConstraintData]): if len(cons) != 0: raise NotImplementedError('NL writer does not support SOS constraints') - def _remove_constraints(self, cons: List[_GeneralConstraintData]): + def _remove_constraints(self, cons: List[GeneralConstraintData]): if self.config.symbolic_solver_labels: for c in cons: self._symbol_map.removeSymbol(c) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 4b7da383a57..4b7d8f35ddc 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -14,7 +14,7 @@ from typing import Sequence, Dict, Optional, Mapping, NoReturn, List, Tuple import os -from pyomo.core.base.constraint import _GeneralConstraintData +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 @@ -232,8 +232,8 @@ def _get_primals( ) def _get_duals( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ) -> Dict[_GeneralConstraintData, float]: + self, cons_to_load: Optional[Sequence[GeneralConstraintData]] = None + ) -> Dict[GeneralConstraintData, float]: """ Declare sign convention in docstring here. @@ -294,7 +294,7 @@ def add_parameters(self, params: List[_ParamData]): """ @abc.abstractmethod - def add_constraints(self, cons: List[_GeneralConstraintData]): + def add_constraints(self, cons: List[GeneralConstraintData]): """ Add constraints to the model """ @@ -318,7 +318,7 @@ def remove_parameters(self, params: List[_ParamData]): """ @abc.abstractmethod - def remove_constraints(self, cons: List[_GeneralConstraintData]): + def remove_constraints(self, cons: List[GeneralConstraintData]): """ Remove constraints from the model """ diff --git a/pyomo/contrib/solver/gurobi.py b/pyomo/contrib/solver/gurobi.py index d0ac0d80f45..cc95c0c5f0d 100644 --- a/pyomo/contrib/solver/gurobi.py +++ b/pyomo/contrib/solver/gurobi.py @@ -23,7 +23,7 @@ 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.constraint import _GeneralConstraintData +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 @@ -555,7 +555,7 @@ def _get_expr_from_pyomo_expr(self, expr): mutable_quadratic_coefficients, ) - def _add_constraints(self, cons: List[_GeneralConstraintData]): + def _add_constraints(self, cons: List[GeneralConstraintData]): for con in cons: conname = self._symbol_map.getSymbol(con, self._labeler) ( @@ -711,7 +711,7 @@ def _add_sos_constraints(self, cons: List[_SOSConstraintData]): self._constraints_added_since_update.update(cons) self._needs_updated = True - def _remove_constraints(self, cons: List[_GeneralConstraintData]): + def _remove_constraints(self, cons: List[GeneralConstraintData]): for con in cons: if con in self._constraints_added_since_update: self._update_gurobi_model() @@ -1125,7 +1125,7 @@ def set_linear_constraint_attr(self, con, attr, val): Parameters ---------- - con: pyomo.core.base.constraint._GeneralConstraintData + con: pyomo.core.base.constraint.GeneralConstraintData The pyomo constraint for which the corresponding gurobi constraint attribute should be modified. attr: str @@ -1202,7 +1202,7 @@ def get_linear_constraint_attr(self, con, attr): Parameters ---------- - con: pyomo.core.base.constraint._GeneralConstraintData + con: pyomo.core.base.constraint.GeneralConstraintData The pyomo constraint for which the corresponding gurobi constraint attribute should be retrieved. attr: str @@ -1234,7 +1234,7 @@ def get_quadratic_constraint_attr(self, con, attr): Parameters ---------- - con: pyomo.core.base.constraint._GeneralConstraintData + con: pyomo.core.base.constraint.GeneralConstraintData The pyomo constraint for which the corresponding gurobi constraint attribute should be retrieved. attr: str @@ -1355,7 +1355,7 @@ def cbCut(self, con): Parameters ---------- - con: pyomo.core.base.constraint._GeneralConstraintData + con: pyomo.core.base.constraint.GeneralConstraintData The cut to add """ if not con.active: @@ -1440,7 +1440,7 @@ def cbLazy(self, con): """ Parameters ---------- - con: pyomo.core.base.constraint._GeneralConstraintData + con: pyomo.core.base.constraint.GeneralConstraintData The lazy constraint to add """ if not con.active: diff --git a/pyomo/contrib/solver/persistent.py b/pyomo/contrib/solver/persistent.py index 4b1a7c58dcd..9b63e05ce46 100644 --- a/pyomo/contrib/solver/persistent.py +++ b/pyomo/contrib/solver/persistent.py @@ -12,7 +12,7 @@ import abc from typing import List -from pyomo.core.base.constraint import _GeneralConstraintData, Constraint +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 @@ -84,7 +84,7 @@ def add_parameters(self, params: List[_ParamData]): self._add_parameters(params) @abc.abstractmethod - def _add_constraints(self, cons: List[_GeneralConstraintData]): + def _add_constraints(self, cons: List[GeneralConstraintData]): pass def _check_for_new_vars(self, variables: List[_GeneralVarData]): @@ -104,7 +104,7 @@ def _check_to_remove_vars(self, variables: List[_GeneralVarData]): vars_to_remove[v_id] = v self.remove_variables(list(vars_to_remove.values())) - def add_constraints(self, cons: List[_GeneralConstraintData]): + def add_constraints(self, cons: List[GeneralConstraintData]): all_fixed_vars = {} for con in cons: if con in self._named_expressions: @@ -209,10 +209,10 @@ def add_block(self, block): self.set_objective(obj) @abc.abstractmethod - def _remove_constraints(self, cons: List[_GeneralConstraintData]): + def _remove_constraints(self, cons: List[GeneralConstraintData]): pass - def remove_constraints(self, cons: List[_GeneralConstraintData]): + def remove_constraints(self, cons: List[GeneralConstraintData]): self._remove_constraints(cons) for con in cons: if con not in self._named_expressions: @@ -384,7 +384,7 @@ def update(self, timer: HierarchicalTimer = None): 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) + c.ctype is None and isinstance(c, GeneralConstraintData) ): old_cons.append(c) else: diff --git a/pyomo/contrib/solver/solution.py b/pyomo/contrib/solver/solution.py index 32e84d2abca..e8c4631e7fd 100644 --- a/pyomo/contrib/solver/solution.py +++ b/pyomo/contrib/solver/solution.py @@ -12,7 +12,7 @@ import abc from typing import Sequence, Dict, Optional, Mapping, NoReturn -from pyomo.core.base.constraint import _GeneralConstraintData +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 @@ -67,8 +67,8 @@ def get_primals( """ def get_duals( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ) -> Dict[_GeneralConstraintData, float]: + self, cons_to_load: Optional[Sequence[GeneralConstraintData]] = None + ) -> Dict[GeneralConstraintData, float]: """ Returns a dictionary mapping constraint to dual value. @@ -121,8 +121,8 @@ def get_primals(self, vars_to_load=None): 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, 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) @@ -205,8 +205,8 @@ def get_primals( return res def get_duals( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ) -> Dict[_GeneralConstraintData, float]: + 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 ' diff --git a/pyomo/contrib/solver/tests/unit/test_results.py b/pyomo/contrib/solver/tests/unit/test_results.py index 74404aaba4c..38d6a540836 100644 --- a/pyomo/contrib/solver/tests/unit/test_results.py +++ b/pyomo/contrib/solver/tests/unit/test_results.py @@ -15,7 +15,7 @@ from pyomo.common import unittest from pyomo.common.config import ConfigDict -from pyomo.core.base.constraint import _GeneralConstraintData +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 @@ -67,8 +67,8 @@ def get_primals( return primals def get_duals( - self, cons_to_load: Optional[Sequence[_GeneralConstraintData]] = None - ) -> Dict[_GeneralConstraintData, float]: + 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 ' diff --git a/pyomo/core/base/constraint.py b/pyomo/core/base/constraint.py index cbae828a459..3455d2dde3c 100644 --- a/pyomo/core/base/constraint.py +++ b/pyomo/core/base/constraint.py @@ -285,7 +285,7 @@ class _ConstraintData(metaclass=RenamedClass): __renamed__version__ = '6.7.2.dev0' -class _GeneralConstraintData(ConstraintData): +class GeneralConstraintData(ConstraintData): """ This class defines the data for a single general constraint. @@ -684,6 +684,11 @@ def set_value(self, expr): ) +class _GeneralConstraintData(metaclass=RenamedClass): + __renamed__new_class__ = GeneralConstraintData + __renamed__version__ = '6.7.2.dev0' + + @ModelComponentFactory.register("General constraint expressions.") class Constraint(ActiveIndexedComponent): """ @@ -726,7 +731,7 @@ class Constraint(ActiveIndexedComponent): The class type for the derived subclass """ - _ComponentDataClass = _GeneralConstraintData + _ComponentDataClass = GeneralConstraintData class Infeasible(object): pass @@ -884,14 +889,14 @@ def display(self, prefix="", ostream=None): ) -class ScalarConstraint(_GeneralConstraintData, Constraint): +class ScalarConstraint(GeneralConstraintData, Constraint): """ ScalarConstraint is the implementation representing a single, non-indexed constraint. """ def __init__(self, *args, **kwds): - _GeneralConstraintData.__init__(self, component=self, expr=None) + GeneralConstraintData.__init__(self, component=self, expr=None) Constraint.__init__(self, *args, **kwds) self._index = UnindexedComponent_index @@ -915,7 +920,7 @@ def body(self): "an expression. There is currently " "nothing to access." % (self.name) ) - return _GeneralConstraintData.body.fget(self) + return GeneralConstraintData.body.fget(self) @property def lower(self): @@ -927,7 +932,7 @@ def lower(self): "an expression. There is currently " "nothing to access." % (self.name) ) - return _GeneralConstraintData.lower.fget(self) + return GeneralConstraintData.lower.fget(self) @property def upper(self): @@ -939,7 +944,7 @@ def upper(self): "an expression. There is currently " "nothing to access." % (self.name) ) - return _GeneralConstraintData.upper.fget(self) + return GeneralConstraintData.upper.fget(self) @property def equality(self): @@ -951,7 +956,7 @@ def equality(self): "an expression. There is currently " "nothing to access." % (self.name) ) - return _GeneralConstraintData.equality.fget(self) + return GeneralConstraintData.equality.fget(self) @property def strict_lower(self): @@ -963,7 +968,7 @@ def strict_lower(self): "an expression. There is currently " "nothing to access." % (self.name) ) - return _GeneralConstraintData.strict_lower.fget(self) + return GeneralConstraintData.strict_lower.fget(self) @property def strict_upper(self): @@ -975,7 +980,7 @@ def strict_upper(self): "an expression. There is currently " "nothing to access." % (self.name) ) - return _GeneralConstraintData.strict_upper.fget(self) + return GeneralConstraintData.strict_upper.fget(self) def clear(self): self._data = {} @@ -1040,7 +1045,7 @@ def add(self, index, expr): return self.__setitem__(index, expr) @overload - def __getitem__(self, index) -> _GeneralConstraintData: ... + def __getitem__(self, index) -> GeneralConstraintData: ... __getitem__ = IndexedComponent.__getitem__ # type: ignore diff --git a/pyomo/core/tests/unit/test_con.py b/pyomo/core/tests/unit/test_con.py index 2fa6c24de9c..26ccc7944a7 100644 --- a/pyomo/core/tests/unit/test_con.py +++ b/pyomo/core/tests/unit/test_con.py @@ -44,7 +44,7 @@ InequalityExpression, RangedExpression, ) -from pyomo.core.base.constraint import _GeneralConstraintData +from pyomo.core.base.constraint import GeneralConstraintData class TestConstraintCreation(unittest.TestCase): @@ -1074,7 +1074,7 @@ def test_setitem(self): m.c[2] = m.x**2 <= 4 self.assertEqual(len(m.c), 1) self.assertEqual(list(m.c.keys()), [2]) - self.assertIsInstance(m.c[2], _GeneralConstraintData) + self.assertIsInstance(m.c[2], GeneralConstraintData) self.assertEqual(m.c[2].upper, 4) m.c[3] = Constraint.Skip diff --git a/pyomo/core/tests/unit/test_dict_objects.py b/pyomo/core/tests/unit/test_dict_objects.py index 8260f1ae320..a13e0f25ac8 100644 --- a/pyomo/core/tests/unit/test_dict_objects.py +++ b/pyomo/core/tests/unit/test_dict_objects.py @@ -18,7 +18,7 @@ ExpressionDict, ) from pyomo.core.base.var import _GeneralVarData -from pyomo.core.base.constraint import _GeneralConstraintData +from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.objective import _GeneralObjectiveData from pyomo.core.base.expression import _GeneralExpressionData @@ -375,7 +375,7 @@ def setUp(self): class TestConstraintDict(_TestActiveComponentDictBase, unittest.TestCase): _ctype = ConstraintDict - _cdatatype = _GeneralConstraintData + _cdatatype = GeneralConstraintData def setUp(self): _TestComponentDictBase.setUp(self) diff --git a/pyomo/core/tests/unit/test_list_objects.py b/pyomo/core/tests/unit/test_list_objects.py index 3eb2e279964..e9c1cceb701 100644 --- a/pyomo/core/tests/unit/test_list_objects.py +++ b/pyomo/core/tests/unit/test_list_objects.py @@ -18,7 +18,7 @@ XExpressionList, ) from pyomo.core.base.var import _GeneralVarData -from pyomo.core.base.constraint import _GeneralConstraintData +from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.objective import _GeneralObjectiveData from pyomo.core.base.expression import _GeneralExpressionData @@ -392,7 +392,7 @@ def setUp(self): class TestConstraintList(_TestActiveComponentListBase, unittest.TestCase): _ctype = XConstraintList - _cdatatype = _GeneralConstraintData + _cdatatype = GeneralConstraintData def setUp(self): _TestComponentListBase.setUp(self) diff --git a/pyomo/gdp/tests/common_tests.py b/pyomo/gdp/tests/common_tests.py index e15a7c66d8a..233c3ca9c09 100644 --- a/pyomo/gdp/tests/common_tests.py +++ b/pyomo/gdp/tests/common_tests.py @@ -953,7 +953,7 @@ def check_disjunction_data_target(self, transformation): self.assertIsInstance(transBlock, Block) self.assertIsInstance(transBlock.component("disjunction_xor"), Constraint) self.assertIsInstance( - transBlock.disjunction_xor[2], constraint._GeneralConstraintData + transBlock.disjunction_xor[2], constraint.GeneralConstraintData ) self.assertIsInstance(transBlock.component("relaxedDisjuncts"), Block) self.assertEqual(len(transBlock.relaxedDisjuncts), 3) @@ -963,7 +963,7 @@ def check_disjunction_data_target(self, transformation): m, targets=[m.disjunction[1]] ) self.assertIsInstance( - m.disjunction[1].algebraic_constraint, constraint._GeneralConstraintData + m.disjunction[1].algebraic_constraint, constraint.GeneralConstraintData ) transBlock = m.component("_pyomo_gdp_%s_reformulation_4" % transformation) self.assertIsInstance(transBlock, Block) diff --git a/pyomo/gdp/tests/test_bigm.py b/pyomo/gdp/tests/test_bigm.py index d5dcef3ba58..efef4c5fb1f 100644 --- a/pyomo/gdp/tests/test_bigm.py +++ b/pyomo/gdp/tests/test_bigm.py @@ -1323,8 +1323,8 @@ def test_do_not_transform_deactivated_constraintDatas(self): self.assertEqual(len(cons_list), 2) lb = cons_list[0] ub = cons_list[1] - self.assertIsInstance(lb, constraint._GeneralConstraintData) - self.assertIsInstance(ub, constraint._GeneralConstraintData) + self.assertIsInstance(lb, constraint.GeneralConstraintData) + self.assertIsInstance(ub, constraint.GeneralConstraintData) def checkMs( self, m, disj1c1lb, disj1c1ub, disj1c2lb, disj1c2ub, disj2c1ub, disj2c2ub diff --git a/pyomo/gdp/tests/test_hull.py b/pyomo/gdp/tests/test_hull.py index 55edf244731..6093e01dc25 100644 --- a/pyomo/gdp/tests/test_hull.py +++ b/pyomo/gdp/tests/test_hull.py @@ -1253,11 +1253,11 @@ def check_second_iteration(self, model): orig = model.component("_pyomo_gdp_hull_reformulation") self.assertIsInstance( model.disjunctionList[1].algebraic_constraint, - constraint._GeneralConstraintData, + constraint.GeneralConstraintData, ) self.assertIsInstance( model.disjunctionList[0].algebraic_constraint, - constraint._GeneralConstraintData, + constraint.GeneralConstraintData, ) self.assertFalse(model.disjunctionList[1].active) self.assertFalse(model.disjunctionList[0].active) diff --git a/pyomo/solvers/plugins/solvers/gurobi_persistent.py b/pyomo/solvers/plugins/solvers/gurobi_persistent.py index 4522a2151c3..101a5340ea9 100644 --- a/pyomo/solvers/plugins/solvers/gurobi_persistent.py +++ b/pyomo/solvers/plugins/solvers/gurobi_persistent.py @@ -157,7 +157,7 @@ def set_linear_constraint_attr(self, con, attr, val): Parameters ---------- - con: pyomo.core.base.constraint._GeneralConstraintData + con: pyomo.core.base.constraint.GeneralConstraintData The pyomo constraint for which the corresponding gurobi constraint attribute should be modified. attr: str @@ -384,7 +384,7 @@ def get_linear_constraint_attr(self, con, attr): Parameters ---------- - con: pyomo.core.base.constraint._GeneralConstraintData + con: pyomo.core.base.constraint.GeneralConstraintData The pyomo constraint for which the corresponding gurobi constraint attribute should be retrieved. attr: str @@ -431,7 +431,7 @@ def get_quadratic_constraint_attr(self, con, attr): Parameters ---------- - con: pyomo.core.base.constraint._GeneralConstraintData + con: pyomo.core.base.constraint.GeneralConstraintData The pyomo constraint for which the corresponding gurobi constraint attribute should be retrieved. attr: str @@ -569,7 +569,7 @@ def cbCut(self, con): Parameters ---------- - con: pyomo.core.base.constraint._GeneralConstraintData + con: pyomo.core.base.constraint.GeneralConstraintData The cut to add """ if not con.active: @@ -647,7 +647,7 @@ def cbLazy(self, con): """ Parameters ---------- - con: pyomo.core.base.constraint._GeneralConstraintData + con: pyomo.core.base.constraint.GeneralConstraintData The lazy constraint to add """ if not con.active: From da96ac3f59b5336be1d805cd8ad60e8b9707b8cd Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:43:27 -0600 Subject: [PATCH 1451/1797] Renamed _GeneralExpressionData -> GeneralExpressionData --- pyomo/contrib/cp/repn/docplex_writer.py | 6 ++--- .../logical_to_disjunctive_walker.py | 4 +-- pyomo/contrib/fbbt/fbbt.py | 10 +++---- pyomo/contrib/latex_printer/latex_printer.py | 4 +-- pyomo/core/base/component.py | 2 +- pyomo/core/base/expression.py | 27 +++++++++++-------- pyomo/core/base/objective.py | 6 ++--- pyomo/core/tests/unit/test_dict_objects.py | 4 +-- pyomo/core/tests/unit/test_expression.py | 8 +++--- pyomo/core/tests/unit/test_list_objects.py | 4 +-- pyomo/dae/integral.py | 4 +-- pyomo/repn/plugins/nl_writer.py | 2 +- pyomo/repn/standard_repn.py | 6 ++--- 13 files changed, 46 insertions(+), 41 deletions(-) diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index 510bbf4e398..00b187a585e 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -63,7 +63,7 @@ GeneralBooleanVarData, IndexedBooleanVar, ) -from pyomo.core.base.expression import ScalarExpression, _GeneralExpressionData +from pyomo.core.base.expression import ScalarExpression, GeneralExpressionData from pyomo.core.base.param import IndexedParam, ScalarParam, _ParamData from pyomo.core.base.var import ScalarVar, _GeneralVarData, IndexedVar import pyomo.core.expr as EXPR @@ -949,7 +949,7 @@ class LogicalToDoCplex(StreamBasedExpressionVisitor): BeforeExpression: _handle_before_expression_node, AtExpression: _handle_at_expression_node, AlwaysIn: _handle_always_in_node, - _GeneralExpressionData: _handle_named_expression_node, + GeneralExpressionData: _handle_named_expression_node, ScalarExpression: _handle_named_expression_node, } _var_handles = { @@ -966,7 +966,7 @@ class LogicalToDoCplex(StreamBasedExpressionVisitor): ScalarBooleanVar: _before_boolean_var, GeneralBooleanVarData: _before_boolean_var, IndexedBooleanVar: _before_indexed_boolean_var, - _GeneralExpressionData: _before_named_expression, + GeneralExpressionData: _before_named_expression, ScalarExpression: _before_named_expression, IndexedParam: _before_indexed_param, # Because of indirection ScalarParam: _before_param, diff --git a/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py b/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py index 548078f55f8..b4fb5e26900 100644 --- a/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py +++ b/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py @@ -27,7 +27,7 @@ value, ) import pyomo.core.base.boolean_var as BV -from pyomo.core.base.expression import ScalarExpression, _GeneralExpressionData +from pyomo.core.base.expression import ScalarExpression, GeneralExpressionData from pyomo.core.base.param import ScalarParam, _ParamData from pyomo.core.base.var import ScalarVar, _GeneralVarData from pyomo.gdp.disjunct import AutoLinkedBooleanVar, Disjunct, Disjunction @@ -217,7 +217,7 @@ def _dispatch_atmost(visitor, node, *args): # don't handle them: _before_child_dispatcher[ScalarVar] = _dispatch_var _before_child_dispatcher[_GeneralVarData] = _dispatch_var -_before_child_dispatcher[_GeneralExpressionData] = _dispatch_expression +_before_child_dispatcher[GeneralExpressionData] = _dispatch_expression _before_child_dispatcher[ScalarExpression] = _dispatch_expression diff --git a/pyomo/contrib/fbbt/fbbt.py b/pyomo/contrib/fbbt/fbbt.py index bde33b3caa0..86f94506841 100644 --- a/pyomo/contrib/fbbt/fbbt.py +++ b/pyomo/contrib/fbbt/fbbt.py @@ -26,7 +26,7 @@ from pyomo.core.base.constraint import Constraint from pyomo.core.base.var import Var from pyomo.gdp import Disjunct -from pyomo.core.base.expression import _GeneralExpressionData, ScalarExpression +from pyomo.core.base.expression import GeneralExpressionData, ScalarExpression import logging from pyomo.common.errors import InfeasibleConstraintException, PyomoException from pyomo.common.config import ( @@ -340,7 +340,7 @@ def _prop_bnds_leaf_to_root_GeneralExpression(visitor, node, expr): Parameters ---------- visitor: _FBBTVisitorLeafToRoot - node: pyomo.core.base.expression._GeneralExpressionData + node: pyomo.core.base.expression.GeneralExpressionData expr: GeneralExpression arg """ bnds_dict = visitor.bnds_dict @@ -366,7 +366,7 @@ def _prop_bnds_leaf_to_root_GeneralExpression(visitor, node, expr): 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, + GeneralExpressionData: _prop_bnds_leaf_to_root_GeneralExpression, ScalarExpression: _prop_bnds_leaf_to_root_GeneralExpression, }, ) @@ -904,7 +904,7 @@ def _prop_bnds_root_to_leaf_GeneralExpression(node, bnds_dict, feasibility_tol): Parameters ---------- - node: pyomo.core.base.expression._GeneralExpressionData + 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 @@ -945,7 +945,7 @@ def _prop_bnds_root_to_leaf_GeneralExpression(node, bnds_dict, feasibility_tol): ) _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_map[GeneralExpressionData] = ( _prop_bnds_root_to_leaf_GeneralExpression ) _prop_bnds_root_to_leaf_map[ScalarExpression] = ( diff --git a/pyomo/contrib/latex_printer/latex_printer.py b/pyomo/contrib/latex_printer/latex_printer.py index 42fc9083953..0e9e379eb21 100644 --- a/pyomo/contrib/latex_printer/latex_printer.py +++ b/pyomo/contrib/latex_printer/latex_printer.py @@ -34,7 +34,7 @@ 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.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 ( @@ -399,7 +399,7 @@ def __init__(self): EqualityExpression: handle_equality_node, InequalityExpression: handle_inequality_node, RangedExpression: handle_ranged_inequality_node, - _GeneralExpressionData: handle_named_expression_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, diff --git a/pyomo/core/base/component.py b/pyomo/core/base/component.py index 1317c928686..6fd82f80ad4 100644 --- a/pyomo/core/base/component.py +++ b/pyomo/core/base/component.py @@ -803,7 +803,7 @@ class ComponentData(_ComponentBase): # NOTE: This constructor is in-lined in the constructors for the following # classes: BooleanVarData, ConnectorData, ConstraintData, - # _GeneralExpressionData, _LogicalConstraintData, + # GeneralExpressionData, _LogicalConstraintData, # _GeneralLogicalConstraintData, _GeneralObjectiveData, # _ParamData,_GeneralVarData, GeneralBooleanVarData, DisjunctionData, # ArcData, _PortData, _LinearConstraintData, and diff --git a/pyomo/core/base/expression.py b/pyomo/core/base/expression.py index e21613fcbb1..f5376381b2d 100644 --- a/pyomo/core/base/expression.py +++ b/pyomo/core/base/expression.py @@ -148,7 +148,7 @@ class _ExpressionData(metaclass=RenamedClass): __renamed__version__ = '6.7.2.dev0' -class _GeneralExpressionDataImpl(ExpressionData): +class GeneralExpressionDataImpl(ExpressionData): """ An object that defines an expression that is never cloned @@ -240,7 +240,7 @@ def __ipow__(self, other): return numeric_expr._pow_dispatcher[e.__class__, other.__class__](e, other) -class _GeneralExpressionData(_GeneralExpressionDataImpl, ComponentData): +class GeneralExpressionData(GeneralExpressionDataImpl, ComponentData): """ An object that defines an expression that is never cloned @@ -258,12 +258,17 @@ class _GeneralExpressionData(_GeneralExpressionDataImpl, ComponentData): __slots__ = ('_args_',) def __init__(self, expr=None, component=None): - _GeneralExpressionDataImpl.__init__(self, expr) + GeneralExpressionDataImpl.__init__(self, expr) # Inlining ComponentData.__init__ self._component = weakref_ref(component) if (component is not None) else None self._index = NOTSET +class _GeneralExpressionData(metaclass=RenamedClass): + __renamed__new_class__ = GeneralExpressionData + __renamed__version__ = '6.7.2.dev0' + + @ModelComponentFactory.register( "Named expressions that can be used in other expressions." ) @@ -280,7 +285,7 @@ class Expression(IndexedComponent): doc Text describing this component. """ - _ComponentDataClass = _GeneralExpressionData + _ComponentDataClass = GeneralExpressionData # This seems like a copy-paste error, and should be renamed/removed NoConstraint = IndexedComponent.Skip @@ -407,9 +412,9 @@ def construct(self, data=None): timer.report() -class ScalarExpression(_GeneralExpressionData, Expression): +class ScalarExpression(GeneralExpressionData, Expression): def __init__(self, *args, **kwds): - _GeneralExpressionData.__init__(self, expr=None, component=self) + GeneralExpressionData.__init__(self, expr=None, component=self) Expression.__init__(self, *args, **kwds) self._index = UnindexedComponent_index @@ -432,7 +437,7 @@ def __call__(self, exception=True): def expr(self): """Return expression on this expression.""" if self._constructed: - return _GeneralExpressionData.expr.fget(self) + return GeneralExpressionData.expr.fget(self) raise ValueError( "Accessing the expression of Expression '%s' " "before the Expression has been constructed (there " @@ -450,7 +455,7 @@ def clear(self): def set_value(self, expr): """Set the expression on this expression.""" if self._constructed: - return _GeneralExpressionData.set_value(self, expr) + return GeneralExpressionData.set_value(self, expr) raise ValueError( "Setting the expression of Expression '%s' " "before the Expression has been constructed (there " @@ -460,7 +465,7 @@ def set_value(self, expr): def is_constant(self): """A boolean indicating whether this expression is constant.""" if self._constructed: - return _GeneralExpressionData.is_constant(self) + return GeneralExpressionData.is_constant(self) raise ValueError( "Accessing the is_constant flag of Expression '%s' " "before the Expression has been constructed (there " @@ -470,7 +475,7 @@ def is_constant(self): def is_fixed(self): """A boolean indicating whether this expression is fixed.""" if self._constructed: - return _GeneralExpressionData.is_fixed(self) + return GeneralExpressionData.is_fixed(self) raise ValueError( "Accessing the is_fixed flag of Expression '%s' " "before the Expression has been constructed (there " @@ -514,6 +519,6 @@ def add(self, index, expr): """Add an expression with a given index.""" if (type(expr) is tuple) and (expr == Expression.Skip): return None - cdata = _GeneralExpressionData(expr, component=self) + cdata = GeneralExpressionData(expr, component=self) self._data[index] = cdata return cdata diff --git a/pyomo/core/base/objective.py b/pyomo/core/base/objective.py index d259358dcd7..5cd1a1f93eb 100644 --- a/pyomo/core/base/objective.py +++ b/pyomo/core/base/objective.py @@ -28,7 +28,7 @@ UnindexedComponent_set, rule_wrapper, ) -from pyomo.core.base.expression import ExpressionData, _GeneralExpressionDataImpl +from pyomo.core.base.expression import ExpressionData, GeneralExpressionDataImpl from pyomo.core.base.set import Set from pyomo.core.base.initializer import ( Initializer, @@ -120,7 +120,7 @@ def set_sense(self, sense): class _GeneralObjectiveData( - _GeneralExpressionDataImpl, _ObjectiveData, ActiveComponentData + GeneralExpressionDataImpl, _ObjectiveData, ActiveComponentData ): """ This class defines the data for a single objective. @@ -147,7 +147,7 @@ class _GeneralObjectiveData( __slots__ = ("_sense", "_args_") def __init__(self, expr=None, sense=minimize, component=None): - _GeneralExpressionDataImpl.__init__(self, expr) + GeneralExpressionDataImpl.__init__(self, expr) # Inlining ActiveComponentData.__init__ self._component = weakref_ref(component) if (component is not None) else None self._index = NOTSET diff --git a/pyomo/core/tests/unit/test_dict_objects.py b/pyomo/core/tests/unit/test_dict_objects.py index a13e0f25ac8..c82103cefb1 100644 --- a/pyomo/core/tests/unit/test_dict_objects.py +++ b/pyomo/core/tests/unit/test_dict_objects.py @@ -20,7 +20,7 @@ from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.objective import _GeneralObjectiveData -from pyomo.core.base.expression import _GeneralExpressionData +from pyomo.core.base.expression import GeneralExpressionData class _TestComponentDictBase(object): @@ -360,7 +360,7 @@ def setUp(self): class TestExpressionDict(_TestComponentDictBase, unittest.TestCase): _ctype = ExpressionDict - _cdatatype = _GeneralExpressionData + _cdatatype = GeneralExpressionData def setUp(self): _TestComponentDictBase.setUp(self) diff --git a/pyomo/core/tests/unit/test_expression.py b/pyomo/core/tests/unit/test_expression.py index 678df4c01a8..bf3ce0c2179 100644 --- a/pyomo/core/tests/unit/test_expression.py +++ b/pyomo/core/tests/unit/test_expression.py @@ -29,7 +29,7 @@ value, sum_product, ) -from pyomo.core.base.expression import _GeneralExpressionData +from pyomo.core.base.expression import GeneralExpressionData from pyomo.core.expr.compare import compare_expressions, assertExpressionsEqual from pyomo.common.tee import capture_output @@ -515,10 +515,10 @@ def test_implicit_definition(self): model.E = Expression(model.idx) self.assertEqual(len(model.E), 3) expr = model.E[1] - self.assertIs(type(expr), _GeneralExpressionData) + self.assertIs(type(expr), GeneralExpressionData) model.E[1] = None self.assertIs(expr, model.E[1]) - self.assertIs(type(expr), _GeneralExpressionData) + self.assertIs(type(expr), GeneralExpressionData) self.assertIs(expr.expr, None) model.E[1] = 5 self.assertIs(expr, model.E[1]) @@ -537,7 +537,7 @@ def test_explicit_skip_definition(self): model.E[1] = None expr = model.E[1] - self.assertIs(type(expr), _GeneralExpressionData) + self.assertIs(type(expr), GeneralExpressionData) self.assertIs(expr.expr, None) model.E[1] = 5 self.assertIs(expr, model.E[1]) diff --git a/pyomo/core/tests/unit/test_list_objects.py b/pyomo/core/tests/unit/test_list_objects.py index e9c1cceb701..b8e97b464fe 100644 --- a/pyomo/core/tests/unit/test_list_objects.py +++ b/pyomo/core/tests/unit/test_list_objects.py @@ -20,7 +20,7 @@ from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.objective import _GeneralObjectiveData -from pyomo.core.base.expression import _GeneralExpressionData +from pyomo.core.base.expression import GeneralExpressionData class _TestComponentListBase(object): @@ -377,7 +377,7 @@ def setUp(self): class TestExpressionList(_TestComponentListBase, unittest.TestCase): _ctype = XExpressionList - _cdatatype = _GeneralExpressionData + _cdatatype = GeneralExpressionData def setUp(self): _TestComponentListBase.setUp(self) diff --git a/pyomo/dae/integral.py b/pyomo/dae/integral.py index 41114296a93..f767e31f18c 100644 --- a/pyomo/dae/integral.py +++ b/pyomo/dae/integral.py @@ -14,7 +14,7 @@ from pyomo.core.base.indexed_component import rule_wrapper from pyomo.core.base.expression import ( Expression, - _GeneralExpressionData, + GeneralExpressionData, ScalarExpression, IndexedExpression, ) @@ -151,7 +151,7 @@ class ScalarIntegral(ScalarExpression, Integral): """ def __init__(self, *args, **kwds): - _GeneralExpressionData.__init__(self, None, component=self) + GeneralExpressionData.__init__(self, None, component=self) Integral.__init__(self, *args, **kwds) def clear(self): diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 29d841248da..a0dc09e2aa6 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -70,7 +70,7 @@ ) 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.expression import ScalarExpression, GeneralExpressionData from pyomo.core.base.objective import ( ScalarObjective, _GeneralObjectiveData, diff --git a/pyomo/repn/standard_repn.py b/pyomo/repn/standard_repn.py index 70368dd3d7e..cf2ba334d6c 100644 --- a/pyomo/repn/standard_repn.py +++ b/pyomo/repn/standard_repn.py @@ -21,7 +21,7 @@ from pyomo.core.expr.numvalue import NumericConstant from pyomo.core.base.objective import _GeneralObjectiveData, ScalarObjective from pyomo.core.base import ExpressionData, Expression -from pyomo.core.base.expression import ScalarExpression, _GeneralExpressionData +from pyomo.core.base.expression import ScalarExpression, GeneralExpressionData from pyomo.core.base.var import ScalarVar, Var, _GeneralVarData, value from pyomo.core.base.param import ScalarParam, _ParamData from pyomo.core.kernel.expression import expression, noclone @@ -1148,7 +1148,7 @@ def _collect_external_fn(exp, multiplier, idMap, compute_values, verbose, quadra Var: _collect_var, variable: _collect_var, IVariable: _collect_var, - _GeneralExpressionData: _collect_identity, + GeneralExpressionData: _collect_identity, ScalarExpression: _collect_identity, expression: _collect_identity, noclone: _collect_identity, @@ -1547,7 +1547,7 @@ def _linear_collect_pow(exp, multiplier, idMap, compute_values, verbose, coef): Var : _linear_collect_var, variable : _linear_collect_var, IVariable : _linear_collect_var, - _GeneralExpressionData : _linear_collect_identity, + GeneralExpressionData : _linear_collect_identity, ScalarExpression : _linear_collect_identity, expression : _linear_collect_identity, noclone : _linear_collect_identity, From cd5db6637bdd16a72d8260459c6b7757dd6c3cf0 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:43:42 -0600 Subject: [PATCH 1452/1797] Renamed _GeneralLogicalConstraintData -> GeneralLogicalConstraintData --- pyomo/core/base/component.py | 2 +- pyomo/core/base/logical_constraint.py | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/pyomo/core/base/component.py b/pyomo/core/base/component.py index 6fd82f80ad4..720373db809 100644 --- a/pyomo/core/base/component.py +++ b/pyomo/core/base/component.py @@ -804,7 +804,7 @@ class ComponentData(_ComponentBase): # NOTE: This constructor is in-lined in the constructors for the following # classes: BooleanVarData, ConnectorData, ConstraintData, # GeneralExpressionData, _LogicalConstraintData, - # _GeneralLogicalConstraintData, _GeneralObjectiveData, + # GeneralLogicalConstraintData, _GeneralObjectiveData, # _ParamData,_GeneralVarData, GeneralBooleanVarData, DisjunctionData, # ArcData, _PortData, _LinearConstraintData, and # _LinearMatrixConstraintData. Changes made here need to be made in those diff --git a/pyomo/core/base/logical_constraint.py b/pyomo/core/base/logical_constraint.py index 3a7bca75960..9af99c9ce5c 100644 --- a/pyomo/core/base/logical_constraint.py +++ b/pyomo/core/base/logical_constraint.py @@ -99,7 +99,7 @@ def get_value(self): raise NotImplementedError -class _GeneralLogicalConstraintData(_LogicalConstraintData): +class GeneralLogicalConstraintData(_LogicalConstraintData): """ This class defines the data for a single general logical constraint. @@ -173,6 +173,11 @@ def get_value(self): return self._expr +class _GeneralLogicalConstraintData(metaclass=RenamedClass): + __renamed__new_class__ = GeneralLogicalConstraintData + __renamed__version__ = '6.7.2.dev0' + + @ModelComponentFactory.register("General logical constraints.") class LogicalConstraint(ActiveIndexedComponent): """ @@ -215,7 +220,7 @@ class LogicalConstraint(ActiveIndexedComponent): The class type for the derived subclass """ - _ComponentDataClass = _GeneralLogicalConstraintData + _ComponentDataClass = GeneralLogicalConstraintData class Infeasible(object): pass @@ -409,14 +414,14 @@ def _check_skip_add(self, index, expr): return expr -class ScalarLogicalConstraint(_GeneralLogicalConstraintData, LogicalConstraint): +class ScalarLogicalConstraint(GeneralLogicalConstraintData, LogicalConstraint): """ ScalarLogicalConstraint is the implementation representing a single, non-indexed logical constraint. """ def __init__(self, *args, **kwds): - _GeneralLogicalConstraintData.__init__(self, component=self, expr=None) + GeneralLogicalConstraintData.__init__(self, component=self, expr=None) LogicalConstraint.__init__(self, *args, **kwds) self._index = UnindexedComponent_index @@ -436,7 +441,7 @@ def body(self): "an expression. There is currently " "nothing to access." % self.name ) - return _GeneralLogicalConstraintData.body.fget(self) + return GeneralLogicalConstraintData.body.fget(self) raise ValueError( "Accessing the body of logical constraint '%s' " "before the LogicalConstraint has been constructed (there " From 6e9d84cb43e1f327fc230c0711769cea598f3d03 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:45:29 -0600 Subject: [PATCH 1453/1797] Renamed _GeneralObjectiveData -> GeneralObjectiveData --- pyomo/contrib/appsi/base.py | 8 ++++---- pyomo/contrib/appsi/fbbt.py | 6 +++--- pyomo/contrib/appsi/solvers/cbc.py | 4 ++-- pyomo/contrib/appsi/solvers/cplex.py | 4 ++-- pyomo/contrib/appsi/solvers/ipopt.py | 4 ++-- pyomo/contrib/appsi/writers/lp_writer.py | 4 ++-- pyomo/contrib/appsi/writers/nl_writer.py | 4 ++-- .../contrib/community_detection/detection.py | 4 ++-- pyomo/contrib/latex_printer/latex_printer.py | 4 ++-- pyomo/contrib/solver/base.py | 4 ++-- pyomo/contrib/solver/persistent.py | 6 +++--- pyomo/core/base/component.py | 2 +- pyomo/core/base/objective.py | 19 ++++++++++++------- pyomo/core/tests/unit/test_dict_objects.py | 4 ++-- pyomo/core/tests/unit/test_list_objects.py | 4 ++-- pyomo/repn/plugins/nl_writer.py | 2 +- pyomo/repn/standard_repn.py | 6 +++--- 17 files changed, 47 insertions(+), 42 deletions(-) diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index d5982fc72e6..b1538ef1a35 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -26,7 +26,7 @@ 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.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 @@ -831,7 +831,7 @@ def remove_block(self, block: BlockData): pass @abc.abstractmethod - def set_objective(self, obj: _GeneralObjectiveData): + def set_objective(self, obj: GeneralObjectiveData): pass @abc.abstractmethod @@ -1054,10 +1054,10 @@ def add_sos_constraints(self, cons: List[_SOSConstraintData]): self._add_sos_constraints(cons) @abc.abstractmethod - def _set_objective(self, obj: _GeneralObjectiveData): + def _set_objective(self, obj: GeneralObjectiveData): pass - def set_objective(self, obj: _GeneralObjectiveData): + 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 diff --git a/pyomo/contrib/appsi/fbbt.py b/pyomo/contrib/appsi/fbbt.py index 121557414b3..ca178a49b00 100644 --- a/pyomo/contrib/appsi/fbbt.py +++ b/pyomo/contrib/appsi/fbbt.py @@ -22,7 +22,7 @@ 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, maximize from pyomo.core.base.block import BlockData from pyomo.core.base import SymbolMap, TextLabeler from pyomo.common.errors import InfeasibleConstraintException @@ -224,13 +224,13 @@ def update_params(self): cp = self._param_map[p_id] cp.value = p.value - def set_objective(self, obj: _GeneralObjectiveData): + def set_objective(self, obj: GeneralObjectiveData): if self._symbolic_solver_labels: if self._objective is not None: self._symbol_map.removeSymbol(self._objective) super().set_objective(obj) - def _set_objective(self, obj: _GeneralObjectiveData): + def _set_objective(self, obj: GeneralObjectiveData): if obj is None: ce = cmodel.Constant(0) sense = 0 diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index cc7327df11c..e73d080c02b 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -30,7 +30,7 @@ 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.core.base.objective import GeneralObjectiveData from pyomo.common.timing import HierarchicalTimer from pyomo.common.tee import TeeStream import sys @@ -188,7 +188,7 @@ def remove_constraints(self, cons: List[GeneralConstraintData]): def remove_block(self, block: BlockData): self._writer.remove_block(block) - def set_objective(self, obj: _GeneralObjectiveData): + def set_objective(self, obj: GeneralObjectiveData): self._writer.set_objective(obj) def update_variables(self, variables: List[_GeneralVarData]): diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index 222c466fb99..ffca656735e 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -26,7 +26,7 @@ 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.core.base.objective import GeneralObjectiveData from pyomo.common.timing import HierarchicalTimer import sys import time @@ -203,7 +203,7 @@ def remove_constraints(self, cons: List[GeneralConstraintData]): def remove_block(self, block: BlockData): self._writer.remove_block(block) - def set_objective(self, obj: _GeneralObjectiveData): + def set_objective(self, obj: GeneralObjectiveData): self._writer.set_objective(obj) def update_variables(self, variables: List[_GeneralVarData]): diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index 75ebb10f719..97d76a9ecb1 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -32,7 +32,7 @@ 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.core.base.objective import GeneralObjectiveData from pyomo.common.timing import HierarchicalTimer from pyomo.common.tee import TeeStream import sys @@ -252,7 +252,7 @@ def remove_constraints(self, cons: List[GeneralConstraintData]): def remove_block(self, block: BlockData): self._writer.remove_block(block) - def set_objective(self, obj: _GeneralObjectiveData): + def set_objective(self, obj: GeneralObjectiveData): self._writer.set_objective(obj) def update_variables(self, variables: List[_GeneralVarData]): diff --git a/pyomo/contrib/appsi/writers/lp_writer.py b/pyomo/contrib/appsi/writers/lp_writer.py index 39298bd1a61..696b1c16d61 100644 --- a/pyomo/contrib/appsi/writers/lp_writer.py +++ b/pyomo/contrib/appsi/writers/lp_writer.py @@ -13,7 +13,7 @@ from pyomo.core.base.param import _ParamData from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.constraint import GeneralConstraintData -from pyomo.core.base.objective import _GeneralObjectiveData +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 @@ -147,7 +147,7 @@ def update_params(self): cp = self._pyomo_param_to_solver_param_map[p_id] cp.value = p.value - def _set_objective(self, obj: _GeneralObjectiveData): + def _set_objective(self, obj: GeneralObjectiveData): cobj = cmodel.process_lp_objective( self._expr_types, obj, diff --git a/pyomo/contrib/appsi/writers/nl_writer.py b/pyomo/contrib/appsi/writers/nl_writer.py index 3e13ef4077a..33d7c59f08f 100644 --- a/pyomo/contrib/appsi/writers/nl_writer.py +++ b/pyomo/contrib/appsi/writers/nl_writer.py @@ -13,7 +13,7 @@ from pyomo.core.base.param import _ParamData from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.constraint import GeneralConstraintData -from pyomo.core.base.objective import _GeneralObjectiveData +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 @@ -180,7 +180,7 @@ def update_params(self): cp = self._pyomo_param_to_solver_param_map[p_id] cp.value = p.value - def _set_objective(self, obj: _GeneralObjectiveData): + def _set_objective(self, obj: GeneralObjectiveData): if obj is None: const = cmodel.Constant(0) lin_vars = list() diff --git a/pyomo/contrib/community_detection/detection.py b/pyomo/contrib/community_detection/detection.py index 5bf8187a243..af87fa5eb8b 100644 --- a/pyomo/contrib/community_detection/detection.py +++ b/pyomo/contrib/community_detection/detection.py @@ -31,7 +31,7 @@ Objective, ConstraintList, ) -from pyomo.core.base.objective import _GeneralObjectiveData +from pyomo.core.base.objective import GeneralObjectiveData from pyomo.core.expr.visitor import replace_expressions, identify_variables from pyomo.contrib.community_detection.community_graph import generate_model_graph from pyomo.common.dependencies import networkx as nx @@ -750,7 +750,7 @@ def generate_structured_model(self): # Check to see whether 'stored_constraint' is actually an objective (since constraints and objectives # grouped together) if self.with_objective and isinstance( - stored_constraint, (_GeneralObjectiveData, Objective) + stored_constraint, (GeneralObjectiveData, Objective) ): # If the constraint is actually an objective, we add it to the block as an objective new_objective = Objective( diff --git a/pyomo/contrib/latex_printer/latex_printer.py b/pyomo/contrib/latex_printer/latex_printer.py index 0e9e379eb21..5a2365f9544 100644 --- a/pyomo/contrib/latex_printer/latex_printer.py +++ b/pyomo/contrib/latex_printer/latex_printer.py @@ -35,7 +35,7 @@ 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 +from pyomo.core.base.objective import ScalarObjective, GeneralObjectiveData import pyomo.core.kernel as kernel from pyomo.core.expr.template_expr import ( GetItemExpression, @@ -403,7 +403,7 @@ def __init__(self): ScalarExpression: handle_named_expression_node, kernel.expression.expression: handle_named_expression_node, kernel.expression.noclone: handle_named_expression_node, - _GeneralObjectiveData: 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, diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 4b7d8f35ddc..0a12f572e5f 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -18,7 +18,7 @@ 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.core.base.objective import GeneralObjectiveData from pyomo.common.config import document_kwargs_from_configdict, ConfigValue from pyomo.common.errors import ApplicationError from pyomo.common.deprecation import deprecation_warning @@ -276,7 +276,7 @@ def set_instance(self, model): """ @abc.abstractmethod - def set_objective(self, obj: _GeneralObjectiveData): + def set_objective(self, obj: GeneralObjectiveData): """ Set current objective for the model """ diff --git a/pyomo/contrib/solver/persistent.py b/pyomo/contrib/solver/persistent.py index 9b63e05ce46..97a4067e78b 100644 --- a/pyomo/contrib/solver/persistent.py +++ b/pyomo/contrib/solver/persistent.py @@ -16,7 +16,7 @@ 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.core.base.objective import GeneralObjectiveData from pyomo.common.collections import ComponentMap from pyomo.common.timing import HierarchicalTimer from pyomo.core.expr.numvalue import NumericConstant @@ -149,10 +149,10 @@ def add_sos_constraints(self, cons: List[_SOSConstraintData]): self._add_sos_constraints(cons) @abc.abstractmethod - def _set_objective(self, obj: _GeneralObjectiveData): + def _set_objective(self, obj: GeneralObjectiveData): pass - def set_objective(self, obj: _GeneralObjectiveData): + 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 diff --git a/pyomo/core/base/component.py b/pyomo/core/base/component.py index 720373db809..1fd30f4212e 100644 --- a/pyomo/core/base/component.py +++ b/pyomo/core/base/component.py @@ -804,7 +804,7 @@ class ComponentData(_ComponentBase): # NOTE: This constructor is in-lined in the constructors for the following # classes: BooleanVarData, ConnectorData, ConstraintData, # GeneralExpressionData, _LogicalConstraintData, - # GeneralLogicalConstraintData, _GeneralObjectiveData, + # GeneralLogicalConstraintData, GeneralObjectiveData, # _ParamData,_GeneralVarData, GeneralBooleanVarData, DisjunctionData, # ArcData, _PortData, _LinearConstraintData, and # _LinearMatrixConstraintData. Changes made here need to be made in those diff --git a/pyomo/core/base/objective.py b/pyomo/core/base/objective.py index 5cd1a1f93eb..b89214377ab 100644 --- a/pyomo/core/base/objective.py +++ b/pyomo/core/base/objective.py @@ -119,7 +119,7 @@ def set_sense(self, sense): raise NotImplementedError -class _GeneralObjectiveData( +class GeneralObjectiveData( GeneralExpressionDataImpl, _ObjectiveData, ActiveComponentData ): """ @@ -192,6 +192,11 @@ def set_sense(self, sense): ) +class _GeneralObjectiveData(metaclass=RenamedClass): + __renamed__new_class__ = GeneralObjectiveData + __renamed__version__ = '6.7.2.dev0' + + @ModelComponentFactory.register("Expressions that are minimized or maximized.") class Objective(ActiveIndexedComponent): """ @@ -240,7 +245,7 @@ class Objective(ActiveIndexedComponent): The class type for the derived subclass """ - _ComponentDataClass = _GeneralObjectiveData + _ComponentDataClass = GeneralObjectiveData NoObjective = ActiveIndexedComponent.Skip def __new__(cls, *args, **kwds): @@ -389,14 +394,14 @@ def display(self, prefix="", ostream=None): ) -class ScalarObjective(_GeneralObjectiveData, Objective): +class ScalarObjective(GeneralObjectiveData, Objective): """ ScalarObjective is the implementation representing a single, non-indexed objective. """ def __init__(self, *args, **kwd): - _GeneralObjectiveData.__init__(self, expr=None, component=self) + GeneralObjectiveData.__init__(self, expr=None, component=self) Objective.__init__(self, *args, **kwd) self._index = UnindexedComponent_index @@ -432,7 +437,7 @@ def expr(self): "a sense or expression (there is currently " "no value to return)." % (self.name) ) - return _GeneralObjectiveData.expr.fget(self) + return GeneralObjectiveData.expr.fget(self) raise ValueError( "Accessing the expression of objective '%s' " "before the Objective has been constructed (there " @@ -455,7 +460,7 @@ def sense(self): "a sense or expression (there is currently " "no value to return)." % (self.name) ) - return _GeneralObjectiveData.sense.fget(self) + return GeneralObjectiveData.sense.fget(self) raise ValueError( "Accessing the sense of objective '%s' " "before the Objective has been constructed (there " @@ -498,7 +503,7 @@ def set_sense(self, sense): if self._constructed: if len(self._data) == 0: self._data[None] = self - return _GeneralObjectiveData.set_sense(self, sense) + return GeneralObjectiveData.set_sense(self, sense) raise ValueError( "Setting the sense of objective '%s' " "before the Objective has been constructed (there " diff --git a/pyomo/core/tests/unit/test_dict_objects.py b/pyomo/core/tests/unit/test_dict_objects.py index c82103cefb1..6dd8a21e2b4 100644 --- a/pyomo/core/tests/unit/test_dict_objects.py +++ b/pyomo/core/tests/unit/test_dict_objects.py @@ -19,7 +19,7 @@ ) from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.constraint import GeneralConstraintData -from pyomo.core.base.objective import _GeneralObjectiveData +from pyomo.core.base.objective import GeneralObjectiveData from pyomo.core.base.expression import GeneralExpressionData @@ -384,7 +384,7 @@ def setUp(self): class TestObjectiveDict(_TestActiveComponentDictBase, unittest.TestCase): _ctype = ObjectiveDict - _cdatatype = _GeneralObjectiveData + _cdatatype = GeneralObjectiveData def setUp(self): _TestComponentDictBase.setUp(self) diff --git a/pyomo/core/tests/unit/test_list_objects.py b/pyomo/core/tests/unit/test_list_objects.py index b8e97b464fe..1609f97af90 100644 --- a/pyomo/core/tests/unit/test_list_objects.py +++ b/pyomo/core/tests/unit/test_list_objects.py @@ -19,7 +19,7 @@ ) from pyomo.core.base.var import _GeneralVarData from pyomo.core.base.constraint import GeneralConstraintData -from pyomo.core.base.objective import _GeneralObjectiveData +from pyomo.core.base.objective import GeneralObjectiveData from pyomo.core.base.expression import GeneralExpressionData @@ -401,7 +401,7 @@ def setUp(self): class TestObjectiveList(_TestActiveComponentListBase, unittest.TestCase): _ctype = XObjectiveList - _cdatatype = _GeneralObjectiveData + _cdatatype = GeneralObjectiveData def setUp(self): _TestComponentListBase.setUp(self) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index a0dc09e2aa6..e1afb5720f3 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -73,7 +73,7 @@ from pyomo.core.base.expression import ScalarExpression, GeneralExpressionData from pyomo.core.base.objective import ( ScalarObjective, - _GeneralObjectiveData, + GeneralObjectiveData, _ObjectiveData, ) from pyomo.core.base.suffix import SuffixFinder diff --git a/pyomo/repn/standard_repn.py b/pyomo/repn/standard_repn.py index cf2ba334d6c..442e4677dbd 100644 --- a/pyomo/repn/standard_repn.py +++ b/pyomo/repn/standard_repn.py @@ -19,7 +19,7 @@ import pyomo.core.expr as EXPR from pyomo.core.expr.numvalue import NumericConstant -from pyomo.core.base.objective import _GeneralObjectiveData, ScalarObjective +from pyomo.core.base.objective import GeneralObjectiveData, ScalarObjective from pyomo.core.base import ExpressionData, Expression from pyomo.core.base.expression import ScalarExpression, GeneralExpressionData from pyomo.core.base.var import ScalarVar, Var, _GeneralVarData, value @@ -1154,7 +1154,7 @@ def _collect_external_fn(exp, multiplier, idMap, compute_values, verbose, quadra noclone: _collect_identity, ExpressionData: _collect_identity, Expression: _collect_identity, - _GeneralObjectiveData: _collect_identity, + GeneralObjectiveData: _collect_identity, ScalarObjective: _collect_identity, objective: _collect_identity, } @@ -1553,7 +1553,7 @@ def _linear_collect_pow(exp, multiplier, idMap, compute_values, verbose, coef): noclone : _linear_collect_identity, ExpressionData : _linear_collect_identity, Expression : _linear_collect_identity, - _GeneralObjectiveData : _linear_collect_identity, + GeneralObjectiveData : _linear_collect_identity, ScalarObjective : _linear_collect_identity, objective : _linear_collect_identity, } From aa79e1b4b0e517a28b8bf8360dea9b6a4b91bba3 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:48:14 -0600 Subject: [PATCH 1454/1797] Renamed _GeneralVarData -> GeneralVarData --- pyomo/contrib/appsi/base.py | 56 +++++++++---------- pyomo/contrib/appsi/fbbt.py | 10 ++-- pyomo/contrib/appsi/solvers/cbc.py | 16 +++--- pyomo/contrib/appsi/solvers/cplex.py | 16 +++--- pyomo/contrib/appsi/solvers/gurobi.py | 12 ++-- pyomo/contrib/appsi/solvers/highs.py | 8 +-- pyomo/contrib/appsi/solvers/ipopt.py | 16 +++--- pyomo/contrib/appsi/solvers/wntr.py | 8 +-- pyomo/contrib/appsi/writers/lp_writer.py | 8 +-- pyomo/contrib/appsi/writers/nl_writer.py | 8 +-- pyomo/contrib/cp/repn/docplex_writer.py | 4 +- .../logical_to_disjunctive_walker.py | 4 +- pyomo/contrib/latex_printer/latex_printer.py | 10 ++-- pyomo/contrib/parmest/utils/scenario_tree.py | 2 +- pyomo/contrib/solver/base.py | 22 ++++---- pyomo/contrib/solver/gurobi.py | 12 ++-- pyomo/contrib/solver/ipopt.py | 6 +- pyomo/contrib/solver/persistent.py | 18 +++--- pyomo/contrib/solver/solution.py | 22 ++++---- .../contrib/solver/tests/unit/test_results.py | 10 ++-- .../trustregion/tests/test_interface.py | 4 +- pyomo/core/base/__init__.py | 2 +- pyomo/core/base/component.py | 2 +- pyomo/core/base/var.py | 15 +++-- pyomo/core/expr/calculus/derivatives.py | 6 +- pyomo/core/tests/transform/test_add_slacks.py | 2 +- pyomo/core/tests/unit/test_dict_objects.py | 6 +- pyomo/core/tests/unit/test_list_objects.py | 6 +- pyomo/core/tests/unit/test_numeric_expr.py | 4 +- pyomo/core/tests/unit/test_reference.py | 12 ++-- pyomo/repn/standard_repn.py | 6 +- .../plugins/solvers/gurobi_persistent.py | 4 +- pyomo/util/report_scaling.py | 4 +- 33 files changed, 173 insertions(+), 168 deletions(-) diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index b1538ef1a35..1ce24220bfd 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -23,7 +23,7 @@ ) 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, Var from pyomo.core.base.param import _ParamData, Param from pyomo.core.base.block import BlockData, Block from pyomo.core.base.objective import GeneralObjectiveData @@ -180,7 +180,7 @@ def __init__( class SolutionLoaderBase(abc.ABC): def load_vars( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + self, vars_to_load: Optional[Sequence[GeneralVarData]] = None ) -> NoReturn: """ Load the solution of the primal variables into the value attribute of the variables. @@ -197,8 +197,8 @@ def load_vars( @abc.abstractmethod def get_primals( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[GeneralVarData]] = None + ) -> Mapping[GeneralVarData, float]: """ Returns a ComponentMap mapping variable to var value. @@ -256,8 +256,8 @@ def get_slacks( ) def get_reduced_costs( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[GeneralVarData]] = None + ) -> Mapping[GeneralVarData, float]: """ Returns a ComponentMap mapping variable to reduced cost. @@ -303,8 +303,8 @@ def __init__( self._reduced_costs = reduced_costs def get_primals( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: + 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 ' @@ -353,8 +353,8 @@ def get_slacks( return slacks def get_reduced_costs( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: + 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 ' @@ -709,7 +709,7 @@ def is_persistent(self): return True def load_vars( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + self, vars_to_load: Optional[Sequence[GeneralVarData]] = None ) -> NoReturn: """ Load the solution of the primal variables into the value attribute of the variables. @@ -726,8 +726,8 @@ def load_vars( @abc.abstractmethod def get_primals( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[GeneralVarData]] = None + ) -> Mapping[GeneralVarData, float]: pass def get_duals( @@ -771,8 +771,8 @@ def get_slacks( ) def get_reduced_costs( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[GeneralVarData]] = None + ) -> Mapping[GeneralVarData, float]: """ Parameters ---------- @@ -799,7 +799,7 @@ def set_instance(self, model): pass @abc.abstractmethod - def add_variables(self, variables: List[_GeneralVarData]): + def add_variables(self, variables: List[GeneralVarData]): pass @abc.abstractmethod @@ -815,7 +815,7 @@ def add_block(self, block: BlockData): pass @abc.abstractmethod - def remove_variables(self, variables: List[_GeneralVarData]): + def remove_variables(self, variables: List[GeneralVarData]): pass @abc.abstractmethod @@ -835,7 +835,7 @@ def set_objective(self, obj: GeneralObjectiveData): pass @abc.abstractmethod - def update_variables(self, variables: List[_GeneralVarData]): + def update_variables(self, variables: List[GeneralVarData]): pass @abc.abstractmethod @@ -869,8 +869,8 @@ def get_slacks( 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, 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) @@ -954,10 +954,10 @@ def set_instance(self, model): self.set_objective(None) @abc.abstractmethod - def _add_variables(self, variables: List[_GeneralVarData]): + def _add_variables(self, variables: List[GeneralVarData]): pass - def add_variables(self, variables: List[_GeneralVarData]): + def add_variables(self, variables: List[GeneralVarData]): for v in variables: if id(v) in self._referenced_variables: raise ValueError( @@ -987,7 +987,7 @@ def add_params(self, params: List[_ParamData]): def _add_constraints(self, cons: List[GeneralConstraintData]): pass - def _check_for_new_vars(self, variables: List[_GeneralVarData]): + def _check_for_new_vars(self, variables: List[GeneralVarData]): new_vars = dict() for v in variables: v_id = id(v) @@ -995,7 +995,7 @@ def _check_for_new_vars(self, variables: List[_GeneralVarData]): new_vars[v_id] = v self.add_variables(list(new_vars.values())) - def _check_to_remove_vars(self, variables: List[_GeneralVarData]): + def _check_to_remove_vars(self, variables: List[GeneralVarData]): vars_to_remove = dict() for v in variables: v_id = id(v) @@ -1174,10 +1174,10 @@ def remove_sos_constraints(self, cons: List[_SOSConstraintData]): del self._vars_referenced_by_con[con] @abc.abstractmethod - def _remove_variables(self, variables: List[_GeneralVarData]): + def _remove_variables(self, variables: List[GeneralVarData]): pass - def remove_variables(self, variables: List[_GeneralVarData]): + def remove_variables(self, variables: List[GeneralVarData]): self._remove_variables(variables) for v in variables: v_id = id(v) @@ -1246,10 +1246,10 @@ def remove_block(self, block): ) @abc.abstractmethod - def _update_variables(self, variables: List[_GeneralVarData]): + def _update_variables(self, variables: List[GeneralVarData]): pass - def update_variables(self, variables: List[_GeneralVarData]): + def update_variables(self, variables: List[GeneralVarData]): for v in variables: self._vars[id(v)] = ( v, diff --git a/pyomo/contrib/appsi/fbbt.py b/pyomo/contrib/appsi/fbbt.py index ca178a49b00..a360d2bce84 100644 --- a/pyomo/contrib/appsi/fbbt.py +++ b/pyomo/contrib/appsi/fbbt.py @@ -18,7 +18,7 @@ ) from .cmodel import cmodel, cmodel_available from typing import List, Optional -from pyomo.core.base.var import _GeneralVarData +from pyomo.core.base.var import GeneralVarData from pyomo.core.base.param import _ParamData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.sos import _SOSConstraintData @@ -121,7 +121,7 @@ def set_instance(self, model, symbolic_solver_labels: Optional[bool] = None): if self._objective is None: self.set_objective(None) - def _add_variables(self, variables: List[_GeneralVarData]): + def _add_variables(self, variables: List[GeneralVarData]): if self._symbolic_solver_labels: set_name = True symbol_map = self._symbol_map @@ -190,7 +190,7 @@ def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): 'IntervalTightener does not support SOS constraints' ) - def _remove_variables(self, variables: List[_GeneralVarData]): + def _remove_variables(self, variables: List[GeneralVarData]): if self._symbolic_solver_labels: for v in variables: self._symbol_map.removeSymbol(v) @@ -205,7 +205,7 @@ def _remove_params(self, params: List[_ParamData]): for p in params: del self._param_map[id(p)] - def _update_variables(self, variables: List[_GeneralVarData]): + def _update_variables(self, variables: List[GeneralVarData]): cmodel.process_pyomo_vars( self._pyomo_expr_types, variables, @@ -304,7 +304,7 @@ def perform_fbbt( self._deactivate_satisfied_cons() return n_iter - def perform_fbbt_with_seed(self, model: BlockData, seed_var: _GeneralVarData): + def perform_fbbt_with_seed(self, model: BlockData, seed_var: GeneralVarData): if model is not self._model: self.set_instance(model) else: diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index e73d080c02b..cd5158d905e 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -26,7 +26,7 @@ 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.var import GeneralVarData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.block import BlockData from pyomo.core.base.param import _ParamData @@ -164,7 +164,7 @@ def symbol_map(self): def set_instance(self, model): self._writer.set_instance(model) - def add_variables(self, variables: List[_GeneralVarData]): + def add_variables(self, variables: List[GeneralVarData]): self._writer.add_variables(variables) def add_params(self, params: List[_ParamData]): @@ -176,7 +176,7 @@ def add_constraints(self, cons: List[GeneralConstraintData]): def add_block(self, block: BlockData): self._writer.add_block(block) - def remove_variables(self, variables: List[_GeneralVarData]): + def remove_variables(self, variables: List[GeneralVarData]): self._writer.remove_variables(variables) def remove_params(self, params: List[_ParamData]): @@ -191,7 +191,7 @@ def remove_block(self, block: BlockData): def set_objective(self, obj: GeneralObjectiveData): self._writer.set_objective(obj) - def update_variables(self, variables: List[_GeneralVarData]): + def update_variables(self, variables: List[GeneralVarData]): self._writer.update_variables(variables) def update_params(self): @@ -440,8 +440,8 @@ def _check_and_escape_options(): return results def get_primals( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[GeneralVarData]] = None + ) -> Mapping[GeneralVarData, float]: if ( self._last_results_object is None or self._last_results_object.best_feasible_objective is None @@ -477,8 +477,8 @@ def get_duals(self, cons_to_load=None): return {c: self._dual_sol[c] for c in cons_to_load} def get_reduced_costs( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[GeneralVarData]] = None + ) -> Mapping[GeneralVarData, float]: if ( self._last_results_object is None or self._last_results_object.termination_condition diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index ffca656735e..cdd699105be 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -22,7 +22,7 @@ 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.var import GeneralVarData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.block import BlockData from pyomo.core.base.param import _ParamData @@ -179,7 +179,7 @@ def update_config(self): def set_instance(self, model): self._writer.set_instance(model) - def add_variables(self, variables: List[_GeneralVarData]): + def add_variables(self, variables: List[GeneralVarData]): self._writer.add_variables(variables) def add_params(self, params: List[_ParamData]): @@ -191,7 +191,7 @@ def add_constraints(self, cons: List[GeneralConstraintData]): def add_block(self, block: BlockData): self._writer.add_block(block) - def remove_variables(self, variables: List[_GeneralVarData]): + def remove_variables(self, variables: List[GeneralVarData]): self._writer.remove_variables(variables) def remove_params(self, params: List[_ParamData]): @@ -206,7 +206,7 @@ def remove_block(self, block: BlockData): def set_objective(self, obj: GeneralObjectiveData): self._writer.set_objective(obj) - def update_variables(self, variables: List[_GeneralVarData]): + def update_variables(self, variables: List[GeneralVarData]): self._writer.update_variables(variables) def update_params(self): @@ -362,8 +362,8 @@ def _postsolve(self, timer: HierarchicalTimer, solve_time): return results def get_primals( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[GeneralVarData]] = None + ) -> Mapping[GeneralVarData, float]: if ( self._cplex_model.solution.get_solution_type() == self._cplex_model.solution.type.none @@ -440,8 +440,8 @@ def get_duals( return res def get_reduced_costs( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[GeneralVarData]] = None + ) -> Mapping[GeneralVarData, float]: if ( self._cplex_model.solution.get_solution_type() == self._cplex_model.solution.type.none diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index e20168034c6..6da59042a80 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -23,7 +23,7 @@ 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 Var, GeneralVarData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.sos import _SOSConstraintData from pyomo.core.base.param import _ParamData @@ -458,7 +458,7 @@ def _process_domain_and_bounds( return lb, ub, vtype - def _add_variables(self, variables: List[_GeneralVarData]): + def _add_variables(self, variables: List[GeneralVarData]): var_names = list() vtypes = list() lbs = list() @@ -759,7 +759,7 @@ def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): del self._pyomo_sos_to_solver_sos_map[con] self._needs_updated = True - def _remove_variables(self, variables: List[_GeneralVarData]): + def _remove_variables(self, variables: List[GeneralVarData]): for var in variables: v_id = id(var) if var in self._vars_added_since_update: @@ -774,7 +774,7 @@ def _remove_variables(self, variables: List[_GeneralVarData]): def _remove_params(self, params: List[_ParamData]): pass - def _update_variables(self, variables: List[_GeneralVarData]): + 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: @@ -1221,7 +1221,7 @@ def set_var_attr(self, var, attr, val): Parameters ---------- - var: pyomo.core.base.var._GeneralVarData + var: pyomo.core.base.var.GeneralVarData The pyomo var for which the corresponding gurobi var attribute should be modified. attr: str @@ -1256,7 +1256,7 @@ def get_var_attr(self, var, attr): Parameters ---------- - var: pyomo.core.base.var._GeneralVarData + var: pyomo.core.base.var.GeneralVarData The pyomo var for which the corresponding gurobi var attribute should be retrieved. attr: str diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index 7773d0624b2..ded0092f38b 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -20,7 +20,7 @@ from pyomo.common.log import LogStream from pyomo.core.kernel.objective import minimize, maximize from pyomo.core.base import SymbolMap -from pyomo.core.base.var import _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 @@ -308,7 +308,7 @@ def _process_domain_and_bounds(self, var_id): return lb, ub, vtype - def _add_variables(self, variables: List[_GeneralVarData]): + 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() @@ -493,7 +493,7 @@ def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): 'Highs interface does not support SOS constraints' ) - def _remove_variables(self, variables: List[_GeneralVarData]): + 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() @@ -518,7 +518,7 @@ def _remove_variables(self, variables: List[_GeneralVarData]): def _remove_params(self, params: List[_ParamData]): pass - def _update_variables(self, variables: List[_GeneralVarData]): + 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() diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index 97d76a9ecb1..9ccb58095b1 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -28,7 +28,7 @@ from pyomo.core.expr.numvalue import value from pyomo.core.expr.visitor import replace_expressions from typing import Optional, Sequence, NoReturn, List, Mapping -from pyomo.core.base.var import _GeneralVarData +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 @@ -228,7 +228,7 @@ def set_instance(self, model): self._writer.config.symbolic_solver_labels = self.config.symbolic_solver_labels self._writer.set_instance(model) - def add_variables(self, variables: List[_GeneralVarData]): + def add_variables(self, variables: List[GeneralVarData]): self._writer.add_variables(variables) def add_params(self, params: List[_ParamData]): @@ -240,7 +240,7 @@ def add_constraints(self, cons: List[GeneralConstraintData]): def add_block(self, block: BlockData): self._writer.add_block(block) - def remove_variables(self, variables: List[_GeneralVarData]): + def remove_variables(self, variables: List[GeneralVarData]): self._writer.remove_variables(variables) def remove_params(self, params: List[_ParamData]): @@ -255,7 +255,7 @@ def remove_block(self, block: BlockData): def set_objective(self, obj: GeneralObjectiveData): self._writer.set_objective(obj) - def update_variables(self, variables: List[_GeneralVarData]): + def update_variables(self, variables: List[GeneralVarData]): self._writer.update_variables(variables) def update_params(self): @@ -514,8 +514,8 @@ def _apply_solver(self, timer: HierarchicalTimer): return results def get_primals( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[GeneralVarData]] = None + ) -> Mapping[GeneralVarData, float]: if ( self._last_results_object is None or self._last_results_object.best_feasible_objective is None @@ -551,8 +551,8 @@ def get_duals(self, cons_to_load: Optional[Sequence[GeneralConstraintData]] = No return {c: self._dual_sol[c] for c in cons_to_load} def get_reduced_costs( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[GeneralVarData]] = None + ) -> Mapping[GeneralVarData, float]: if ( self._last_results_object is None or self._last_results_object.termination_condition diff --git a/pyomo/contrib/appsi/solvers/wntr.py b/pyomo/contrib/appsi/solvers/wntr.py index c11536e2e6f..7f633161fe1 100644 --- a/pyomo/contrib/appsi/solvers/wntr.py +++ b/pyomo/contrib/appsi/solvers/wntr.py @@ -40,7 +40,7 @@ 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.var import GeneralVarData from pyomo.core.base.param import _ParamData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.common.timing import HierarchicalTimer @@ -239,7 +239,7 @@ def set_instance(self, model): self.add_block(model) - def _add_variables(self, variables: List[_GeneralVarData]): + def _add_variables(self, variables: List[GeneralVarData]): aml = wntr.sim.aml.aml for var in variables: varname = self._symbol_map.getSymbol(var, self._labeler) @@ -302,7 +302,7 @@ def _remove_constraints(self, cons: List[GeneralConstraintData]): del self._pyomo_con_to_solver_con_map[con] self._needs_updated = True - def _remove_variables(self, variables: List[_GeneralVarData]): + 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] @@ -322,7 +322,7 @@ def _remove_params(self, params: List[_ParamData]): self._symbol_map.removeSymbol(p) del self._pyomo_param_to_solver_param_map[p_id] - def _update_variables(self, variables: List[_GeneralVarData]): + def _update_variables(self, variables: List[GeneralVarData]): aml = wntr.sim.aml.aml for var in variables: v_id = id(var) diff --git a/pyomo/contrib/appsi/writers/lp_writer.py b/pyomo/contrib/appsi/writers/lp_writer.py index 696b1c16d61..94af5ba7e93 100644 --- a/pyomo/contrib/appsi/writers/lp_writer.py +++ b/pyomo/contrib/appsi/writers/lp_writer.py @@ -11,7 +11,7 @@ from typing import List from pyomo.core.base.param import _ParamData -from pyomo.core.base.var import _GeneralVarData +from pyomo.core.base.var import GeneralVarData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.objective import GeneralObjectiveData from pyomo.core.base.sos import _SOSConstraintData @@ -77,7 +77,7 @@ def set_instance(self, model): if self._objective is None: self.set_objective(None) - def _add_variables(self, variables: List[_GeneralVarData]): + def _add_variables(self, variables: List[GeneralVarData]): cmodel.process_pyomo_vars( self._expr_types, variables, @@ -117,7 +117,7 @@ def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): if len(cons) != 0: raise NotImplementedError('LP writer does not yet support SOS constraints') - def _remove_variables(self, variables: List[_GeneralVarData]): + def _remove_variables(self, variables: List[GeneralVarData]): for v in variables: cvar = self._pyomo_var_to_solver_var_map.pop(id(v)) del self._solver_var_to_pyomo_var_map[cvar] @@ -128,7 +128,7 @@ def _remove_params(self, params: List[_ParamData]): del self._pyomo_param_to_solver_param_map[id(p)] self._symbol_map.removeSymbol(p) - def _update_variables(self, variables: List[_GeneralVarData]): + def _update_variables(self, variables: List[GeneralVarData]): cmodel.process_pyomo_vars( self._expr_types, variables, diff --git a/pyomo/contrib/appsi/writers/nl_writer.py b/pyomo/contrib/appsi/writers/nl_writer.py index 33d7c59f08f..b7dab1d5a3e 100644 --- a/pyomo/contrib/appsi/writers/nl_writer.py +++ b/pyomo/contrib/appsi/writers/nl_writer.py @@ -11,7 +11,7 @@ from typing import List from pyomo.core.base.param import _ParamData -from pyomo.core.base.var import _GeneralVarData +from pyomo.core.base.var import GeneralVarData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.objective import GeneralObjectiveData from pyomo.core.base.sos import _SOSConstraintData @@ -78,7 +78,7 @@ def set_instance(self, model): self.set_objective(None) self._set_pyomo_amplfunc_env() - def _add_variables(self, variables: List[_GeneralVarData]): + def _add_variables(self, variables: List[GeneralVarData]): if self.config.symbolic_solver_labels: set_name = True symbol_map = self._symbol_map @@ -144,7 +144,7 @@ def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): if len(cons) != 0: raise NotImplementedError('NL writer does not support SOS constraints') - def _remove_variables(self, variables: List[_GeneralVarData]): + def _remove_variables(self, variables: List[GeneralVarData]): if self.config.symbolic_solver_labels: for v in variables: self._symbol_map.removeSymbol(v) @@ -161,7 +161,7 @@ def _remove_params(self, params: List[_ParamData]): for p in params: del self._pyomo_param_to_solver_param_map[id(p)] - def _update_variables(self, variables: List[_GeneralVarData]): + def _update_variables(self, variables: List[GeneralVarData]): cmodel.process_pyomo_vars( self._expr_types, variables, diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index 00b187a585e..eb50a543160 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -65,7 +65,7 @@ ) from pyomo.core.base.expression import ScalarExpression, GeneralExpressionData from pyomo.core.base.param import IndexedParam, ScalarParam, _ParamData -from pyomo.core.base.var import ScalarVar, _GeneralVarData, IndexedVar +from pyomo.core.base.var import ScalarVar, GeneralVarData, IndexedVar import pyomo.core.expr as EXPR from pyomo.core.expr.visitor import StreamBasedExpressionVisitor, identify_variables from pyomo.core.base import Set, RangeSet @@ -961,7 +961,7 @@ class LogicalToDoCplex(StreamBasedExpressionVisitor): IntervalVarData: _before_interval_var, IndexedIntervalVar: _before_indexed_interval_var, ScalarVar: _before_var, - _GeneralVarData: _before_var, + GeneralVarData: _before_var, IndexedVar: _before_indexed_var, ScalarBooleanVar: _before_boolean_var, GeneralBooleanVarData: _before_boolean_var, diff --git a/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py b/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py index b4fb5e26900..26b63d020a5 100644 --- a/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py +++ b/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py @@ -29,7 +29,7 @@ import pyomo.core.base.boolean_var as BV from pyomo.core.base.expression import ScalarExpression, GeneralExpressionData from pyomo.core.base.param import ScalarParam, _ParamData -from pyomo.core.base.var import ScalarVar, _GeneralVarData +from pyomo.core.base.var import ScalarVar, GeneralVarData from pyomo.gdp.disjunct import AutoLinkedBooleanVar, Disjunct, Disjunction @@ -216,7 +216,7 @@ def _dispatch_atmost(visitor, node, *args): # for the moment, these are all just so we can get good error messages when we # don't handle them: _before_child_dispatcher[ScalarVar] = _dispatch_var -_before_child_dispatcher[_GeneralVarData] = _dispatch_var +_before_child_dispatcher[GeneralVarData] = _dispatch_var _before_child_dispatcher[GeneralExpressionData] = _dispatch_expression _before_child_dispatcher[ScalarExpression] = _dispatch_expression diff --git a/pyomo/contrib/latex_printer/latex_printer.py b/pyomo/contrib/latex_printer/latex_printer.py index 5a2365f9544..efcd3016dbf 100644 --- a/pyomo/contrib/latex_printer/latex_printer.py +++ b/pyomo/contrib/latex_printer/latex_printer.py @@ -47,7 +47,7 @@ resolve_template, templatize_rule, ) -from pyomo.core.base.var import ScalarVar, _GeneralVarData, IndexedVar +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, SetOperator from pyomo.core.base.constraint import ScalarConstraint, IndexedConstraint @@ -404,7 +404,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_var_node, + GeneralVarData: handle_var_node, ScalarObjective: handle_named_expression_node, kernel.objective.objective: handle_named_expression_node, ExternalFunctionExpression: handle_external_function_node, @@ -706,9 +706,9 @@ def latex_printer( temp_comp, temp_indexes = templatize_fcn(pyomo_component) variableList = [] for v in identify_components( - temp_comp, [ScalarVar, _GeneralVarData, IndexedVar] + temp_comp, [ScalarVar, GeneralVarData, IndexedVar] ): - if isinstance(v, _GeneralVarData): + if isinstance(v, GeneralVarData): v_write = v.parent_component() if v_write not in ComponentSet(variableList): variableList.append(v_write) @@ -1275,7 +1275,7 @@ def get_index_names(st, lcm): rep_dict = {} for ky in reversed(list(latex_component_map)): - if isinstance(ky, (pyo.Var, _GeneralVarData)): + if isinstance(ky, (pyo.Var, GeneralVarData)): overwrite_value = latex_component_map[ky] if ky not in existing_components: overwrite_value = overwrite_value.replace('_', '\\_') diff --git a/pyomo/contrib/parmest/utils/scenario_tree.py b/pyomo/contrib/parmest/utils/scenario_tree.py index e71f51877b5..1062e4a2bf4 100644 --- a/pyomo/contrib/parmest/utils/scenario_tree.py +++ b/pyomo/contrib/parmest/utils/scenario_tree.py @@ -25,7 +25,7 @@ def build_vardatalist(self, model, varlist=None): """ - Convert a list of pyomo variables to a list of ScalarVar and _GeneralVarData. If varlist is none, builds a + Convert a list of pyomo variables to a list of ScalarVar and GeneralVarData. If varlist is none, builds a list of all variables in the model. The new list is stored in the vars_to_tighten attribute. By CD Laird Parameters diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 0a12f572e5f..a935a950819 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -15,7 +15,7 @@ import os from pyomo.core.base.constraint import GeneralConstraintData -from pyomo.core.base.var import _GeneralVarData +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 @@ -195,7 +195,7 @@ def is_persistent(self): return True def _load_vars( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + self, vars_to_load: Optional[Sequence[GeneralVarData]] = None ) -> NoReturn: """ Load the solution of the primal variables into the value attribute of the variables. @@ -212,19 +212,19 @@ def _load_vars( @abc.abstractmethod def _get_primals( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[GeneralVarData]] = None + ) -> Mapping[GeneralVarData, float]: """ Get mapping of variables to primals. Parameters ---------- - vars_to_load : Optional[Sequence[_GeneralVarData]], optional + vars_to_load : Optional[Sequence[GeneralVarData]], optional Which vars to be populated into the map. The default is None. Returns ------- - Mapping[_GeneralVarData, float] + Mapping[GeneralVarData, float] A map of variables to primals. """ raise NotImplementedError( @@ -251,8 +251,8 @@ def _get_duals( raise NotImplementedError(f'{type(self)} does not support the get_duals method') def _get_reduced_costs( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[GeneralVarData]] = None + ) -> Mapping[GeneralVarData, float]: """ Parameters ---------- @@ -282,7 +282,7 @@ def set_objective(self, obj: GeneralObjectiveData): """ @abc.abstractmethod - def add_variables(self, variables: List[_GeneralVarData]): + def add_variables(self, variables: List[GeneralVarData]): """ Add variables to the model """ @@ -306,7 +306,7 @@ def add_block(self, block: BlockData): """ @abc.abstractmethod - def remove_variables(self, variables: List[_GeneralVarData]): + def remove_variables(self, variables: List[GeneralVarData]): """ Remove variables from the model """ @@ -330,7 +330,7 @@ def remove_block(self, block: BlockData): """ @abc.abstractmethod - def update_variables(self, variables: List[_GeneralVarData]): + def update_variables(self, variables: List[GeneralVarData]): """ Update variables on the model """ diff --git a/pyomo/contrib/solver/gurobi.py b/pyomo/contrib/solver/gurobi.py index cc95c0c5f0d..353798133db 100644 --- a/pyomo/contrib/solver/gurobi.py +++ b/pyomo/contrib/solver/gurobi.py @@ -22,7 +22,7 @@ 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 _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 @@ -438,7 +438,7 @@ def _process_domain_and_bounds( return lb, ub, vtype - def _add_variables(self, variables: List[_GeneralVarData]): + def _add_variables(self, variables: List[GeneralVarData]): var_names = list() vtypes = list() lbs = list() @@ -735,7 +735,7 @@ def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): del self._pyomo_sos_to_solver_sos_map[con] self._needs_updated = True - def _remove_variables(self, variables: List[_GeneralVarData]): + def _remove_variables(self, variables: List[GeneralVarData]): for var in variables: v_id = id(var) if var in self._vars_added_since_update: @@ -750,7 +750,7 @@ def _remove_variables(self, variables: List[_GeneralVarData]): def _remove_parameters(self, params: List[_ParamData]): pass - def _update_variables(self, variables: List[_GeneralVarData]): + 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: @@ -1151,7 +1151,7 @@ def set_var_attr(self, var, attr, val): Parameters ---------- - var: pyomo.core.base.var._GeneralVarData + var: pyomo.core.base.var.GeneralVarData The pyomo var for which the corresponding gurobi var attribute should be modified. attr: str @@ -1186,7 +1186,7 @@ def get_var_attr(self, var, attr): Parameters ---------- - var: pyomo.core.base.var._GeneralVarData + var: pyomo.core.base.var.GeneralVarData The pyomo var for which the corresponding gurobi var attribute should be retrieved. attr: str diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 5f601b7a9f7..7111ec6e972 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -25,7 +25,7 @@ ) from pyomo.common.tempfiles import TempfileManager from pyomo.common.timing import HierarchicalTimer -from pyomo.core.base.var import _GeneralVarData +from pyomo.core.base.var import GeneralVarData from pyomo.core.staleflag import StaleFlagManager from pyomo.repn.plugins.nl_writer import NLWriter, NLWriterInfo from pyomo.contrib.solver.base import SolverBase @@ -80,8 +80,8 @@ def __init__( class IpoptSolutionLoader(SolSolutionLoader): def get_reduced_costs( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: + 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 ' diff --git a/pyomo/contrib/solver/persistent.py b/pyomo/contrib/solver/persistent.py index 97a4067e78b..aeacc9f87c4 100644 --- a/pyomo/contrib/solver/persistent.py +++ b/pyomo/contrib/solver/persistent.py @@ -14,7 +14,7 @@ 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.var import GeneralVarData from pyomo.core.base.param import _ParamData, Param from pyomo.core.base.objective import GeneralObjectiveData from pyomo.common.collections import ComponentMap @@ -54,10 +54,10 @@ def set_instance(self, model): self.set_objective(None) @abc.abstractmethod - def _add_variables(self, variables: List[_GeneralVarData]): + def _add_variables(self, variables: List[GeneralVarData]): pass - def add_variables(self, variables: List[_GeneralVarData]): + def add_variables(self, variables: List[GeneralVarData]): for v in variables: if id(v) in self._referenced_variables: raise ValueError( @@ -87,7 +87,7 @@ def add_parameters(self, params: List[_ParamData]): def _add_constraints(self, cons: List[GeneralConstraintData]): pass - def _check_for_new_vars(self, variables: List[_GeneralVarData]): + def _check_for_new_vars(self, variables: List[GeneralVarData]): new_vars = {} for v in variables: v_id = id(v) @@ -95,7 +95,7 @@ def _check_for_new_vars(self, variables: List[_GeneralVarData]): new_vars[v_id] = v self.add_variables(list(new_vars.values())) - def _check_to_remove_vars(self, variables: List[_GeneralVarData]): + def _check_to_remove_vars(self, variables: List[GeneralVarData]): vars_to_remove = {} for v in variables: v_id = id(v) @@ -250,10 +250,10 @@ def remove_sos_constraints(self, cons: List[_SOSConstraintData]): del self._vars_referenced_by_con[con] @abc.abstractmethod - def _remove_variables(self, variables: List[_GeneralVarData]): + def _remove_variables(self, variables: List[GeneralVarData]): pass - def remove_variables(self, variables: List[_GeneralVarData]): + def remove_variables(self, variables: List[GeneralVarData]): self._remove_variables(variables) for v in variables: v_id = id(v) @@ -309,10 +309,10 @@ def remove_block(self, block): ) @abc.abstractmethod - def _update_variables(self, variables: List[_GeneralVarData]): + def _update_variables(self, variables: List[GeneralVarData]): pass - def update_variables(self, variables: List[_GeneralVarData]): + def update_variables(self, variables: List[GeneralVarData]): for v in variables: self._vars[id(v)] = ( v, diff --git a/pyomo/contrib/solver/solution.py b/pyomo/contrib/solver/solution.py index e8c4631e7fd..3f327c1f280 100644 --- a/pyomo/contrib/solver/solution.py +++ b/pyomo/contrib/solver/solution.py @@ -13,7 +13,7 @@ from typing import Sequence, Dict, Optional, Mapping, NoReturn from pyomo.core.base.constraint import GeneralConstraintData -from pyomo.core.base.var import _GeneralVarData +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 @@ -31,7 +31,7 @@ class SolutionLoaderBase(abc.ABC): """ def load_vars( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + self, vars_to_load: Optional[Sequence[GeneralVarData]] = None ) -> NoReturn: """ Load the solution of the primal variables into the value attribute of the variables. @@ -49,8 +49,8 @@ def load_vars( @abc.abstractmethod def get_primals( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[GeneralVarData]] = None + ) -> Mapping[GeneralVarData, float]: """ Returns a ComponentMap mapping variable to var value. @@ -86,8 +86,8 @@ def get_duals( raise NotImplementedError(f'{type(self)} does not support the get_duals method') def get_reduced_costs( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[GeneralVarData]] = None + ) -> Mapping[GeneralVarData, float]: """ Returns a ComponentMap mapping variable to reduced cost. @@ -127,8 +127,8 @@ def get_duals( 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, 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) @@ -142,7 +142,7 @@ def __init__(self, sol_data: SolFileData, nl_info: NLWriterInfo) -> None: self._nl_info = nl_info def load_vars( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None + self, vars_to_load: Optional[Sequence[GeneralVarData]] = None ) -> NoReturn: if self._nl_info is None: raise RuntimeError( @@ -169,8 +169,8 @@ def load_vars( StaleFlagManager.mark_all_as_stale(delayed=True) def get_primals( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: + 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 ' diff --git a/pyomo/contrib/solver/tests/unit/test_results.py b/pyomo/contrib/solver/tests/unit/test_results.py index 38d6a540836..608af04a0ed 100644 --- a/pyomo/contrib/solver/tests/unit/test_results.py +++ b/pyomo/contrib/solver/tests/unit/test_results.py @@ -16,7 +16,7 @@ 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.core.base.var import GeneralVarData from pyomo.common.collections import ComponentMap from pyomo.contrib.solver import results from pyomo.contrib.solver import solution @@ -51,8 +51,8 @@ def __init__( self._reduced_costs = reduced_costs def get_primals( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: + 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 ' @@ -84,8 +84,8 @@ def get_duals( return duals def get_reduced_costs( - self, vars_to_load: Optional[Sequence[_GeneralVarData]] = None - ) -> Mapping[_GeneralVarData, float]: + 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 ' diff --git a/pyomo/contrib/trustregion/tests/test_interface.py b/pyomo/contrib/trustregion/tests/test_interface.py index 148caceddd1..64f76eb887d 100644 --- a/pyomo/contrib/trustregion/tests/test_interface.py +++ b/pyomo/contrib/trustregion/tests/test_interface.py @@ -33,7 +33,7 @@ cos, SolverFactory, ) -from pyomo.core.base.var import _GeneralVarData +from pyomo.core.base.var import GeneralVarData from pyomo.core.expr.numeric_expr import ExternalFunctionExpression from pyomo.core.expr.visitor import identify_variables from pyomo.contrib.trustregion.interface import TRFInterface @@ -158,7 +158,7 @@ def test_replaceExternalFunctionsWithVariables(self): self.assertIsInstance(k, ExternalFunctionExpression) self.assertIn(str(self.interface.model.x[0]), str(k)) self.assertIn(str(self.interface.model.x[1]), str(k)) - self.assertIsInstance(i, _GeneralVarData) + self.assertIsInstance(i, GeneralVarData) self.assertEqual(i, self.interface.data.ef_outputs[1]) for i, k in self.interface.data.basis_expressions.items(): self.assertEqual(k, 0) diff --git a/pyomo/core/base/__init__.py b/pyomo/core/base/__init__.py index bb62cb96782..7003cc3d720 100644 --- a/pyomo/core/base/__init__.py +++ b/pyomo/core/base/__init__.py @@ -57,7 +57,7 @@ from pyomo.core.base.check import BuildCheck from pyomo.core.base.set import Set, SetOf, simple_set_rule, RangeSet from pyomo.core.base.param import Param -from pyomo.core.base.var import Var, _VarData, _GeneralVarData, ScalarVar, VarList +from pyomo.core.base.var import Var, _VarData, GeneralVarData, ScalarVar, VarList from pyomo.core.base.boolean_var import ( BooleanVar, BooleanVarData, diff --git a/pyomo/core/base/component.py b/pyomo/core/base/component.py index 1fd30f4212e..faf6553be1b 100644 --- a/pyomo/core/base/component.py +++ b/pyomo/core/base/component.py @@ -805,7 +805,7 @@ class ComponentData(_ComponentBase): # classes: BooleanVarData, ConnectorData, ConstraintData, # GeneralExpressionData, _LogicalConstraintData, # GeneralLogicalConstraintData, GeneralObjectiveData, - # _ParamData,_GeneralVarData, GeneralBooleanVarData, DisjunctionData, + # _ParamData,GeneralVarData, GeneralBooleanVarData, DisjunctionData, # ArcData, _PortData, _LinearConstraintData, and # _LinearMatrixConstraintData. Changes made here need to be made in those # constructors as well! diff --git a/pyomo/core/base/var.py b/pyomo/core/base/var.py index 856a2dc0237..0e45ad44225 100644 --- a/pyomo/core/base/var.py +++ b/pyomo/core/base/var.py @@ -319,7 +319,7 @@ def free(self): return self.unfix() -class _GeneralVarData(_VarData): +class GeneralVarData(_VarData): """This class defines the data for a single variable.""" __slots__ = ('_value', '_lb', '_ub', '_domain', '_fixed', '_stale') @@ -643,6 +643,11 @@ def _process_bound(self, val, bound_type): return val +class _GeneralVarData(metaclass=RenamedClass): + __renamed__new_class__ = GeneralVarData + __renamed__version__ = '6.7.2.dev0' + + @ModelComponentFactory.register("Decision variables.") class Var(IndexedComponent, IndexedComponent_NDArrayMixin): """A numeric variable, which may be defined over an index. @@ -668,7 +673,7 @@ class Var(IndexedComponent, IndexedComponent_NDArrayMixin): doc (str, optional): Text describing this component. """ - _ComponentDataClass = _GeneralVarData + _ComponentDataClass = GeneralVarData @overload def __new__(cls: Type[Var], *args, **kwargs) -> Union[ScalarVar, IndexedVar]: ... @@ -952,11 +957,11 @@ def _pprint(self): ) -class ScalarVar(_GeneralVarData, Var): +class ScalarVar(GeneralVarData, Var): """A single variable.""" def __init__(self, *args, **kwd): - _GeneralVarData.__init__(self, component=self) + GeneralVarData.__init__(self, component=self) Var.__init__(self, *args, **kwd) self._index = UnindexedComponent_index @@ -1057,7 +1062,7 @@ def domain(self, domain): # between potentially variable GetItemExpression objects and # "constant" GetItemExpression objects. That will need to wait for # the expression rework [JDS; Nov 22]. - def __getitem__(self, args) -> _GeneralVarData: + def __getitem__(self, args) -> GeneralVarData: try: return super().__getitem__(args) except RuntimeError: diff --git a/pyomo/core/expr/calculus/derivatives.py b/pyomo/core/expr/calculus/derivatives.py index ecfdce02fd4..cd23cb16b2c 100644 --- a/pyomo/core/expr/calculus/derivatives.py +++ b/pyomo/core/expr/calculus/derivatives.py @@ -39,11 +39,11 @@ def differentiate(expr, wrt=None, wrt_list=None, mode=Modes.reverse_numeric): ---------- expr: pyomo.core.expr.numeric_expr.NumericExpression The expression to differentiate - wrt: pyomo.core.base.var._GeneralVarData + wrt: pyomo.core.base.var.GeneralVarData If specified, this function will return the derivative with - respect to wrt. wrt is normally a _GeneralVarData, but could + respect to wrt. wrt is normally a GeneralVarData, but could also be a _ParamData. wrt and wrt_list cannot both be specified. - wrt_list: list of pyomo.core.base.var._GeneralVarData + wrt_list: list of pyomo.core.base.var.GeneralVarData If specified, this function will return the derivative with respect to each element in wrt_list. A list will be returned where the values are the derivatives with respect to the diff --git a/pyomo/core/tests/transform/test_add_slacks.py b/pyomo/core/tests/transform/test_add_slacks.py index a74a9b75c4f..d66d6fba79e 100644 --- a/pyomo/core/tests/transform/test_add_slacks.py +++ b/pyomo/core/tests/transform/test_add_slacks.py @@ -330,7 +330,7 @@ def test_error_for_non_constraint_noniterable_target(self): self.assertRaisesRegex( ValueError, "Expected Constraint or list of Constraints.\n\tReceived " - "", + "", TransformationFactory('core.add_slack_variables').apply_to, m, targets=m.indexedVar[1], diff --git a/pyomo/core/tests/unit/test_dict_objects.py b/pyomo/core/tests/unit/test_dict_objects.py index 6dd8a21e2b4..f2c3cad8cc3 100644 --- a/pyomo/core/tests/unit/test_dict_objects.py +++ b/pyomo/core/tests/unit/test_dict_objects.py @@ -17,7 +17,7 @@ ObjectiveDict, ExpressionDict, ) -from pyomo.core.base.var import _GeneralVarData +from pyomo.core.base.var import GeneralVarData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.objective import GeneralObjectiveData from pyomo.core.base.expression import GeneralExpressionData @@ -348,10 +348,10 @@ def test_active(self): class TestVarDict(_TestComponentDictBase, unittest.TestCase): - # Note: the updated _GeneralVarData class only takes an optional + # Note: the updated GeneralVarData class only takes an optional # parent argument (you no longer pass the domain in) _ctype = VarDict - _cdatatype = lambda self, arg: _GeneralVarData() + _cdatatype = lambda self, arg: GeneralVarData() def setUp(self): _TestComponentDictBase.setUp(self) diff --git a/pyomo/core/tests/unit/test_list_objects.py b/pyomo/core/tests/unit/test_list_objects.py index 1609f97af90..fcc83a95a06 100644 --- a/pyomo/core/tests/unit/test_list_objects.py +++ b/pyomo/core/tests/unit/test_list_objects.py @@ -17,7 +17,7 @@ XObjectiveList, XExpressionList, ) -from pyomo.core.base.var import _GeneralVarData +from pyomo.core.base.var import GeneralVarData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.objective import GeneralObjectiveData from pyomo.core.base.expression import GeneralExpressionData @@ -365,10 +365,10 @@ def test_active(self): class TestVarList(_TestComponentListBase, unittest.TestCase): - # Note: the updated _GeneralVarData class only takes an optional + # Note: the updated GeneralVarData class only takes an optional # parent argument (you no longer pass the domain in) _ctype = XVarList - _cdatatype = lambda self, arg: _GeneralVarData() + _cdatatype = lambda self, arg: GeneralVarData() def setUp(self): _TestComponentListBase.setUp(self) diff --git a/pyomo/core/tests/unit/test_numeric_expr.py b/pyomo/core/tests/unit/test_numeric_expr.py index 968b3acb6a4..8e5e43eac9c 100644 --- a/pyomo/core/tests/unit/test_numeric_expr.py +++ b/pyomo/core/tests/unit/test_numeric_expr.py @@ -112,7 +112,7 @@ from pyomo.core.base.label import NumericLabeler from pyomo.core.expr.template_expr import IndexTemplate from pyomo.core.expr import expr_common -from pyomo.core.base.var import _GeneralVarData +from pyomo.core.base.var import GeneralVarData from pyomo.repn import generate_standard_repn from pyomo.core.expr.numvalue import NumericValue @@ -294,7 +294,7 @@ def value_check(self, exp, val): class TestExpression_EvaluateVarData(TestExpression_EvaluateNumericValue): def create(self, val, domain): - tmp = _GeneralVarData() + tmp = GeneralVarData() tmp.domain = domain tmp.value = val return tmp diff --git a/pyomo/core/tests/unit/test_reference.py b/pyomo/core/tests/unit/test_reference.py index cfd9b99f945..4fa2f4944e9 100644 --- a/pyomo/core/tests/unit/test_reference.py +++ b/pyomo/core/tests/unit/test_reference.py @@ -800,8 +800,8 @@ def test_reference_indexedcomponent_pprint(self): buf.getvalue(), """r : Size=2, Index={1, 2}, ReferenceTo=x Key : Object - 1 : - 2 : + 1 : + 2 : """, ) m.s = Reference(m.x[:, ...], ctype=IndexedComponent) @@ -811,8 +811,8 @@ def test_reference_indexedcomponent_pprint(self): buf.getvalue(), """s : Size=2, Index={1, 2}, ReferenceTo=x[:, ...] Key : Object - 1 : - 2 : + 1 : + 2 : """, ) @@ -1357,8 +1357,8 @@ def test_pprint_nonfinite_sets_ctypeNone(self): 1 IndexedComponent Declarations ref : Size=2, Index=NonNegativeIntegers, ReferenceTo=v Key : Object - 3 : - 5 : + 3 : + 5 : 2 Declarations: v ref """.strip(), diff --git a/pyomo/repn/standard_repn.py b/pyomo/repn/standard_repn.py index 442e4677dbd..907b4a2b115 100644 --- a/pyomo/repn/standard_repn.py +++ b/pyomo/repn/standard_repn.py @@ -22,7 +22,7 @@ from pyomo.core.base.objective import GeneralObjectiveData, ScalarObjective from pyomo.core.base import ExpressionData, Expression from pyomo.core.base.expression import ScalarExpression, GeneralExpressionData -from pyomo.core.base.var import ScalarVar, Var, _GeneralVarData, value +from pyomo.core.base.var import ScalarVar, Var, GeneralVarData, value from pyomo.core.base.param import ScalarParam, _ParamData from pyomo.core.kernel.expression import expression, noclone from pyomo.core.kernel.variable import IVariable, variable @@ -1143,7 +1143,7 @@ def _collect_external_fn(exp, multiplier, idMap, compute_values, verbose, quadra # param.Param : _collect_linear_const, # parameter : _collect_linear_const, NumericConstant: _collect_const, - _GeneralVarData: _collect_var, + GeneralVarData: _collect_var, ScalarVar: _collect_var, Var: _collect_var, variable: _collect_var, @@ -1542,7 +1542,7 @@ def _linear_collect_pow(exp, multiplier, idMap, compute_values, verbose, coef): ##param.ScalarParam : _collect_linear_const, ##param.Param : _collect_linear_const, ##parameter : _collect_linear_const, - _GeneralVarData : _linear_collect_var, + GeneralVarData : _linear_collect_var, ScalarVar : _linear_collect_var, Var : _linear_collect_var, variable : _linear_collect_var, diff --git a/pyomo/solvers/plugins/solvers/gurobi_persistent.py b/pyomo/solvers/plugins/solvers/gurobi_persistent.py index 101a5340ea9..8a81aad3d3e 100644 --- a/pyomo/solvers/plugins/solvers/gurobi_persistent.py +++ b/pyomo/solvers/plugins/solvers/gurobi_persistent.py @@ -192,7 +192,7 @@ def set_var_attr(self, var, attr, val): Parameters ---------- - con: pyomo.core.base.var._GeneralVarData + con: pyomo.core.base.var.GeneralVarData The pyomo var for which the corresponding gurobi var attribute should be modified. attr: str @@ -342,7 +342,7 @@ def get_var_attr(self, var, attr): Parameters ---------- - var: pyomo.core.base.var._GeneralVarData + var: pyomo.core.base.var.GeneralVarData The pyomo var for which the corresponding gurobi var attribute should be retrieved. attr: str diff --git a/pyomo/util/report_scaling.py b/pyomo/util/report_scaling.py index 5ae28baa715..265564bf12d 100644 --- a/pyomo/util/report_scaling.py +++ b/pyomo/util/report_scaling.py @@ -13,7 +13,7 @@ import math from pyomo.core.base.block import BlockData from pyomo.common.collections import ComponentSet -from pyomo.core.base.var import _GeneralVarData +from pyomo.core.base.var import GeneralVarData from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr from pyomo.core.expr.calculus.diff_with_pyomo import reverse_sd import logging @@ -73,7 +73,7 @@ def _check_coefficients( ): ders = reverse_sd(expr) for _v, _der in ders.items(): - if isinstance(_v, _GeneralVarData): + if isinstance(_v, GeneralVarData): if _v.is_fixed(): continue der_lb, der_ub = compute_bounds_on_expr(_der) From edd83c00ba974bf9d73b95b0e084ed0b63eee71a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:48:36 -0600 Subject: [PATCH 1455/1797] Renamed _InfiniteRangeSetData -> InfiniteRangeSetData --- pyomo/core/base/set.py | 23 ++++++++++++++--------- pyomo/core/tests/unit/test_set.py | 4 ++-- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index 8db64620d5c..f885dfeaa16 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -894,7 +894,7 @@ def _get_continuous_interval(self): @property @deprecated("The 'virtual' attribute is no longer supported", version='5.7') def virtual(self): - return isinstance(self, (_AnySet, SetOperator, _InfiniteRangeSetData)) + return isinstance(self, (_AnySet, SetOperator, InfiniteRangeSetData)) @virtual.setter def virtual(self, value): @@ -2608,7 +2608,7 @@ def ord(self, item): ############################################################################ -class _InfiniteRangeSetData(_SetData): +class InfiniteRangeSetData(_SetData): """Data class for a infinite set. This Set implements an interface to an *infinite set* defined by one @@ -2653,8 +2653,13 @@ def ranges(self): return iter(self._ranges) +class _InfiniteRangeSetData(metaclass=RenamedClass): + __renamed__new_class__ = InfiniteRangeSetData + __renamed__version__ = '6.7.2.dev0' + + class FiniteRangeSetData( - _SortedSetMixin, _OrderedSetMixin, _FiniteSetMixin, _InfiniteRangeSetData + _SortedSetMixin, _OrderedSetMixin, _FiniteSetMixin, InfiniteRangeSetData ): __slots__ = () @@ -2754,11 +2759,11 @@ def ord(self, item): ) # We must redefine ranges(), bounds(), and domain so that we get the - # _InfiniteRangeSetData version and not the one from + # InfiniteRangeSetData version and not the one from # _FiniteSetMixin. - bounds = _InfiniteRangeSetData.bounds - ranges = _InfiniteRangeSetData.ranges - domain = _InfiniteRangeSetData.domain + bounds = InfiniteRangeSetData.bounds + ranges = InfiniteRangeSetData.ranges + domain = InfiniteRangeSetData.domain class _FiniteRangeSetData(metaclass=RenamedClass): @@ -3228,9 +3233,9 @@ def _pprint(self): ) -class InfiniteScalarRangeSet(_InfiniteRangeSetData, RangeSet): +class InfiniteScalarRangeSet(InfiniteRangeSetData, RangeSet): def __init__(self, *args, **kwds): - _InfiniteRangeSetData.__init__(self, component=self) + InfiniteRangeSetData.__init__(self, component=self) RangeSet.__init__(self, *args, **kwds) self._index = UnindexedComponent_index diff --git a/pyomo/core/tests/unit/test_set.py b/pyomo/core/tests/unit/test_set.py index a9b9fb9469b..d669bb38f3b 100644 --- a/pyomo/core/tests/unit/test_set.py +++ b/pyomo/core/tests/unit/test_set.py @@ -61,7 +61,7 @@ InfiniteSetOf, RangeSet, FiniteRangeSetData, - _InfiniteRangeSetData, + InfiniteRangeSetData, FiniteScalarRangeSet, InfiniteScalarRangeSet, AbstractFiniteScalarRangeSet, @@ -1297,7 +1297,7 @@ def test_is_functions(self): self.assertFalse(i.isdiscrete()) self.assertFalse(i.isfinite()) self.assertFalse(i.isordered()) - self.assertIsInstance(i, _InfiniteRangeSetData) + self.assertIsInstance(i, InfiniteRangeSetData) def test_pprint(self): m = ConcreteModel() From b5c9dbaee26359c842dfeee3d8b962da81a7f513 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:48:49 -0600 Subject: [PATCH 1456/1797] Renamed _InsertionOrderSetData -> InsertionOrderSetData --- pyomo/core/base/set.py | 23 +++++++++++++-------- pyomo/core/tests/unit/test_set.py | 8 +++---- pyomo/core/tests/unit/test_template_expr.py | 2 +- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index f885dfeaa16..c0e9491ed1c 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -1642,9 +1642,9 @@ class _OrderedSetData(_OrderedSetMixin, FiniteSetData): In older Pyomo terms, this defines a "concrete" ordered set - that is, a set that "owns" the list of set members. While this class actually implements a set ordered by insertion order, we make the "official" - _InsertionOrderSetData an empty derivative class, so that + InsertionOrderSetData an empty derivative class, so that - issubclass(_SortedSetData, _InsertionOrderSetData) == False + issubclass(_SortedSetData, InsertionOrderSetData) == False Constructor Arguments: component The Set object that owns this data. @@ -1735,7 +1735,7 @@ def ord(self, item): raise ValueError("%s.ord(x): x not in %s" % (self.name, self.name)) -class _InsertionOrderSetData(_OrderedSetData): +class InsertionOrderSetData(_OrderedSetData): """ This class defines the data for a ordered set where the items are ordered in insertion order (similar to Python's OrderedSet. @@ -1756,7 +1756,7 @@ def set_value(self, val): "This WILL potentially lead to nondeterministic behavior " "in Pyomo" % (type(val).__name__,) ) - super(_InsertionOrderSetData, self).set_value(val) + super(InsertionOrderSetData, self).set_value(val) def update(self, values): if type(values) in Set._UnorderedInitializers: @@ -1766,7 +1766,12 @@ def update(self, values): "This WILL potentially lead to nondeterministic behavior " "in Pyomo" % (type(values).__name__,) ) - super(_InsertionOrderSetData, self).update(values) + super(InsertionOrderSetData, self).update(values) + + +class _InsertionOrderSetData(metaclass=RenamedClass): + __renamed__new_class__ = InsertionOrderSetData + __renamed__version__ = '6.7.2.dev0' class _SortedSetMixin(object): @@ -2035,7 +2040,7 @@ def __new__(cls, *args, **kwds): else: newObj = super(Set, cls).__new__(IndexedSet) if ordered is Set.InsertionOrder: - newObj._ComponentDataClass = _InsertionOrderSetData + newObj._ComponentDataClass = InsertionOrderSetData elif ordered is Set.SortedOrder: newObj._ComponentDataClass = _SortedSetData else: @@ -2363,7 +2368,7 @@ def _pprint(self): _ordered = "Sorted" else: _ordered = "{user}" - elif issubclass(_refClass, _InsertionOrderSetData): + elif issubclass(_refClass, InsertionOrderSetData): _ordered = "Insertion" return ( [ @@ -2405,13 +2410,13 @@ class FiniteSimpleSet(metaclass=RenamedClass): __renamed__version__ = '6.0' -class OrderedScalarSet(_ScalarOrderedSetMixin, _InsertionOrderSetData, Set): +class OrderedScalarSet(_ScalarOrderedSetMixin, InsertionOrderSetData, Set): def __init__(self, **kwds): # In case someone inherits from us, we will provide a rational # default for the "ordered" flag kwds.setdefault('ordered', Set.InsertionOrder) - _InsertionOrderSetData.__init__(self, component=self) + InsertionOrderSetData.__init__(self, component=self) Set.__init__(self, **kwds) diff --git a/pyomo/core/tests/unit/test_set.py b/pyomo/core/tests/unit/test_set.py index d669bb38f3b..38870d5213e 100644 --- a/pyomo/core/tests/unit/test_set.py +++ b/pyomo/core/tests/unit/test_set.py @@ -83,7 +83,7 @@ SetProduct_OrderedSet, _SetData, FiniteSetData, - _InsertionOrderSetData, + InsertionOrderSetData, _SortedSetData, _FiniteSetMixin, _OrderedSetMixin, @@ -4155,9 +4155,9 @@ def test_indexed_set(self): self.assertTrue(m.I[1].isordered()) self.assertTrue(m.I[2].isordered()) self.assertTrue(m.I[3].isordered()) - self.assertIs(type(m.I[1]), _InsertionOrderSetData) - self.assertIs(type(m.I[2]), _InsertionOrderSetData) - self.assertIs(type(m.I[3]), _InsertionOrderSetData) + self.assertIs(type(m.I[1]), InsertionOrderSetData) + self.assertIs(type(m.I[2]), InsertionOrderSetData) + self.assertIs(type(m.I[3]), InsertionOrderSetData) self.assertEqual(m.I.data(), {1: (4, 2, 5), 2: (4, 2, 5), 3: (4, 2, 5)}) # Explicit (constant) construction diff --git a/pyomo/core/tests/unit/test_template_expr.py b/pyomo/core/tests/unit/test_template_expr.py index 4f255e3567a..e6bd9d98a7d 100644 --- a/pyomo/core/tests/unit/test_template_expr.py +++ b/pyomo/core/tests/unit/test_template_expr.py @@ -127,7 +127,7 @@ def test_template_scalar_with_set(self): # Note that structural expressions do not implement polynomial_degree with self.assertRaisesRegex( AttributeError, - "'_InsertionOrderSetData' object has " "no attribute 'polynomial_degree'", + "'InsertionOrderSetData' object has " "no attribute 'polynomial_degree'", ): e.polynomial_degree() self.assertEqual(str(e), "s[{I}]") From d4d522a4e9b38b17f1944cdbab4292da2b11a220 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:49:48 -0600 Subject: [PATCH 1457/1797] Renamed _LogicalConstraintData -> LogicalConstraintData --- pyomo/core/base/__init__.py | 2 +- pyomo/core/base/component.py | 2 +- pyomo/core/base/logical_constraint.py | 13 +++++++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/pyomo/core/base/__init__.py b/pyomo/core/base/__init__.py index 7003cc3d720..7d1bd1401f7 100644 --- a/pyomo/core/base/__init__.py +++ b/pyomo/core/base/__init__.py @@ -75,7 +75,7 @@ from pyomo.core.base.logical_constraint import ( LogicalConstraint, LogicalConstraintList, - _LogicalConstraintData, + LogicalConstraintData, ) from pyomo.core.base.objective import ( simple_objective_rule, diff --git a/pyomo/core/base/component.py b/pyomo/core/base/component.py index faf6553be1b..341cd1506ff 100644 --- a/pyomo/core/base/component.py +++ b/pyomo/core/base/component.py @@ -803,7 +803,7 @@ class ComponentData(_ComponentBase): # NOTE: This constructor is in-lined in the constructors for the following # classes: BooleanVarData, ConnectorData, ConstraintData, - # GeneralExpressionData, _LogicalConstraintData, + # GeneralExpressionData, LogicalConstraintData, # GeneralLogicalConstraintData, GeneralObjectiveData, # _ParamData,GeneralVarData, GeneralBooleanVarData, DisjunctionData, # ArcData, _PortData, _LinearConstraintData, and diff --git a/pyomo/core/base/logical_constraint.py b/pyomo/core/base/logical_constraint.py index 9af99c9ce5c..23a422705df 100644 --- a/pyomo/core/base/logical_constraint.py +++ b/pyomo/core/base/logical_constraint.py @@ -42,7 +42,7 @@ """ -class _LogicalConstraintData(ActiveComponentData): +class LogicalConstraintData(ActiveComponentData): """ This class defines the data for a single logical constraint. @@ -99,7 +99,12 @@ def get_value(self): raise NotImplementedError -class GeneralLogicalConstraintData(_LogicalConstraintData): +class _LogicalConstraintData(metaclass=RenamedClass): + __renamed__new_class__ = LogicalConstraintData + __renamed__version__ = '6.7.2.dev0' + + +class GeneralLogicalConstraintData(LogicalConstraintData): """ This class defines the data for a single general logical constraint. @@ -123,7 +128,7 @@ def __init__(self, expr=None, component=None): # # These lines represent in-lining of the # following constructors: - # - _LogicalConstraintData, + # - LogicalConstraintData, # - ActiveComponentData # - ComponentData self._component = weakref_ref(component) if (component is not None) else None @@ -455,7 +460,7 @@ def body(self): # currently in place). So during initialization only, we will # treat them as "indexed" objects where things like # True are managed. But after that they will behave - # like _LogicalConstraintData objects where set_value expects + # like LogicalConstraintData objects where set_value expects # a valid expression or None. # From c37fc4fb13794b35e94e86b3a185494d2383f432 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:50:02 -0600 Subject: [PATCH 1458/1797] Renamed _ObjectiveData -> ObjectiveData --- pyomo/core/base/__init__.py | 2 +- pyomo/core/base/objective.py | 11 ++++++++--- pyomo/core/beta/dict_objects.py | 4 ++-- pyomo/core/beta/list_objects.py | 4 ++-- pyomo/core/plugins/transform/scaling.py | 4 ++-- pyomo/core/tests/unit/test_obj.py | 2 +- pyomo/core/tests/unit/test_suffix.py | 4 ++-- pyomo/repn/plugins/nl_writer.py | 6 +++--- 8 files changed, 21 insertions(+), 16 deletions(-) diff --git a/pyomo/core/base/__init__.py b/pyomo/core/base/__init__.py index 7d1bd1401f7..408cf16c00e 100644 --- a/pyomo/core/base/__init__.py +++ b/pyomo/core/base/__init__.py @@ -82,7 +82,7 @@ simple_objectivelist_rule, Objective, ObjectiveList, - _ObjectiveData, + ObjectiveData, ) from pyomo.core.base.connector import Connector from pyomo.core.base.sos import SOSConstraint diff --git a/pyomo/core/base/objective.py b/pyomo/core/base/objective.py index b89214377ab..58cb198e1ae 100644 --- a/pyomo/core/base/objective.py +++ b/pyomo/core/base/objective.py @@ -86,7 +86,7 @@ def O_rule(model, i, j): # -class _ObjectiveData(ExpressionData): +class ObjectiveData(ExpressionData): """ This class defines the data for a single objective. @@ -119,8 +119,13 @@ def set_sense(self, sense): raise NotImplementedError +class _ObjectiveData(metaclass=RenamedClass): + __renamed__new_class__ = ObjectiveData + __renamed__version__ = '6.7.2.dev0' + + class GeneralObjectiveData( - GeneralExpressionDataImpl, _ObjectiveData, ActiveComponentData + GeneralExpressionDataImpl, ObjectiveData, ActiveComponentData ): """ This class defines the data for a single objective. @@ -479,7 +484,7 @@ def sense(self, sense): # currently in place). So during initialization only, we will # treat them as "indexed" objects where things like # Objective.Skip are managed. But after that they will behave - # like _ObjectiveData objects where set_value does not handle + # like ObjectiveData objects where set_value does not handle # Objective.Skip but expects a valid expression or None # diff --git a/pyomo/core/beta/dict_objects.py b/pyomo/core/beta/dict_objects.py index 2b23d81e91a..7c44166f189 100644 --- a/pyomo/core/beta/dict_objects.py +++ b/pyomo/core/beta/dict_objects.py @@ -16,7 +16,7 @@ from pyomo.core.base.set_types import Any from pyomo.core.base.var import IndexedVar, _VarData from pyomo.core.base.constraint import IndexedConstraint, ConstraintData -from pyomo.core.base.objective import IndexedObjective, _ObjectiveData +from pyomo.core.base.objective import IndexedObjective, ObjectiveData from pyomo.core.base.expression import IndexedExpression, ExpressionData from collections.abc import MutableMapping @@ -202,7 +202,7 @@ def __init__(self, *args, **kwds): # Constructor for ComponentDict needs to # go last in order to handle any initialization # iterable as an argument - ComponentDict.__init__(self, _ObjectiveData, *args, **kwds) + ComponentDict.__init__(self, ObjectiveData, *args, **kwds) class ExpressionDict(ComponentDict, IndexedExpression): diff --git a/pyomo/core/beta/list_objects.py b/pyomo/core/beta/list_objects.py index dd199eb70cd..d10a30e18e2 100644 --- a/pyomo/core/beta/list_objects.py +++ b/pyomo/core/beta/list_objects.py @@ -16,7 +16,7 @@ from pyomo.core.base.set_types import Any from pyomo.core.base.var import IndexedVar, _VarData from pyomo.core.base.constraint import IndexedConstraint, ConstraintData -from pyomo.core.base.objective import IndexedObjective, _ObjectiveData +from pyomo.core.base.objective import IndexedObjective, ObjectiveData from pyomo.core.base.expression import IndexedExpression, ExpressionData from collections.abc import MutableSequence @@ -250,7 +250,7 @@ def __init__(self, *args, **kwds): # Constructor for ComponentList needs to # go last in order to handle any initialization # iterable as an argument - ComponentList.__init__(self, _ObjectiveData, *args, **kwds) + ComponentList.__init__(self, ObjectiveData, *args, **kwds) class XExpressionList(ComponentList, IndexedExpression): diff --git a/pyomo/core/plugins/transform/scaling.py b/pyomo/core/plugins/transform/scaling.py index 6b83a2378d1..ef418f094ae 100644 --- a/pyomo/core/plugins/transform/scaling.py +++ b/pyomo/core/plugins/transform/scaling.py @@ -16,7 +16,7 @@ Constraint, Objective, ConstraintData, - _ObjectiveData, + ObjectiveData, Suffix, value, ) @@ -226,7 +226,7 @@ def _apply_to(self, model, rename=True): else: c.set_value((lower, body, upper)) - elif isinstance(c, _ObjectiveData): + elif isinstance(c, ObjectiveData): c.expr = scaling_factor * replace_expressions( expr=c.expr, substitution_map=variable_substitution_dict, diff --git a/pyomo/core/tests/unit/test_obj.py b/pyomo/core/tests/unit/test_obj.py index 3c8a05f7058..dc2e320e63b 100644 --- a/pyomo/core/tests/unit/test_obj.py +++ b/pyomo/core/tests/unit/test_obj.py @@ -78,7 +78,7 @@ def test_empty_singleton(self): # Even though we construct a ScalarObjective, # if it is not initialized that means it is "empty" # and we should encounter errors when trying to access the - # _ObjectiveData interface methods until we assign + # ObjectiveData interface methods until we assign # something to the objective. # self.assertEqual(a._constructed, True) diff --git a/pyomo/core/tests/unit/test_suffix.py b/pyomo/core/tests/unit/test_suffix.py index 9597bad7571..70f028a3eff 100644 --- a/pyomo/core/tests/unit/test_suffix.py +++ b/pyomo/core/tests/unit/test_suffix.py @@ -1567,7 +1567,7 @@ def test_clone_ObjectiveArray(self): self.assertEqual(inst.junk.get(model.obj[1]), None) self.assertEqual(inst.junk.get(inst.obj[1]), 1.0) - def test_clone_ObjectiveData(self): + def test_cloneObjectiveData(self): model = ConcreteModel() model.x = Var([1, 2, 3], dense=True) model.obj = Objective([1, 2, 3], rule=lambda model, i: model.x[i]) @@ -1725,7 +1725,7 @@ def test_pickle_ObjectiveArray(self): self.assertEqual(inst.junk.get(model.obj[1]), None) self.assertEqual(inst.junk.get(inst.obj[1]), 1.0) - def test_pickle_ObjectiveData(self): + def test_pickleObjectiveData(self): model = ConcreteModel() model.x = Var([1, 2, 3], dense=True) model.obj = Objective([1, 2, 3], rule=simple_obj_rule) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index e1afb5720f3..c010cee5e54 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -74,7 +74,7 @@ from pyomo.core.base.objective import ( ScalarObjective, GeneralObjectiveData, - _ObjectiveData, + ObjectiveData, ) from pyomo.core.base.suffix import SuffixFinder from pyomo.core.base.var import _VarData @@ -139,7 +139,7 @@ class NLWriterInfo(object): The list of (active) Pyomo model constraints in the order written to the NL file - objectives: List[_ObjectiveData] + objectives: List[ObjectiveData] The list of (active) Pyomo model objectives in the order written to the NL file @@ -466,7 +466,7 @@ def compile(self, column_order, row_order, obj_order, model_id): self.obj[obj_order[_id]] = val elif _id == model_id: self.prob[0] = val - elif isinstance(obj, (_VarData, ConstraintData, _ObjectiveData)): + elif isinstance(obj, (_VarData, ConstraintData, ObjectiveData)): missing_component_data.add(obj) elif isinstance(obj, (Var, Constraint, Objective)): # Expand this indexed component to store the From 4127432baf064f55d2f09d7c264109734e70f4b7 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:50:05 -0600 Subject: [PATCH 1459/1797] Renamed _OrderedSetData -> OrderedSetData --- pyomo/core/base/set.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index c0e9491ed1c..c7a7edc8e4b 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -1301,7 +1301,7 @@ class FiniteSetData(_FiniteSetMixin, _SetData): def __init__(self, component): _SetData.__init__(self, component=component) - # Derived classes (like _OrderedSetData) may want to change the + # Derived classes (like OrderedSetData) may want to change the # storage if not hasattr(self, '_values'): self._values = set() @@ -1635,7 +1635,7 @@ def _to_0_based_index(self, item): ) -class _OrderedSetData(_OrderedSetMixin, FiniteSetData): +class OrderedSetData(_OrderedSetMixin, FiniteSetData): """ This class defines the base class for an ordered set of concrete data. @@ -1735,7 +1735,12 @@ def ord(self, item): raise ValueError("%s.ord(x): x not in %s" % (self.name, self.name)) -class InsertionOrderSetData(_OrderedSetData): +class _OrderedSetData(metaclass=RenamedClass): + __renamed__new_class__ = OrderedSetData + __renamed__version__ = '6.7.2.dev0' + + +class InsertionOrderSetData(OrderedSetData): """ This class defines the data for a ordered set where the items are ordered in insertion order (similar to Python's OrderedSet. @@ -1786,7 +1791,7 @@ def sorted_iter(self): return iter(self) -class _SortedSetData(_SortedSetMixin, _OrderedSetData): +class _SortedSetData(_SortedSetMixin, OrderedSetData): """ This class defines the data for a sorted set. @@ -1801,7 +1806,7 @@ class _SortedSetData(_SortedSetMixin, _OrderedSetData): def __init__(self, component): # An empty set is sorted... self._is_sorted = True - _OrderedSetData.__init__(self, component=component) + OrderedSetData.__init__(self, component=component) def _iter_impl(self): """ From ec3f121f81a4f52295caab029d5bfb5e826c569e Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:51:57 -0600 Subject: [PATCH 1460/1797] Renamed _ParamData -> ParamData --- pyomo/contrib/appsi/base.py | 14 ++++---- pyomo/contrib/appsi/fbbt.py | 6 ++-- pyomo/contrib/appsi/solvers/cbc.py | 6 ++-- pyomo/contrib/appsi/solvers/cplex.py | 6 ++-- pyomo/contrib/appsi/solvers/gurobi.py | 6 ++-- pyomo/contrib/appsi/solvers/highs.py | 6 ++-- pyomo/contrib/appsi/solvers/ipopt.py | 6 ++-- pyomo/contrib/appsi/solvers/wntr.py | 6 ++-- pyomo/contrib/appsi/writers/lp_writer.py | 6 ++-- pyomo/contrib/appsi/writers/nl_writer.py | 6 ++-- pyomo/contrib/cp/repn/docplex_writer.py | 4 +-- .../logical_to_disjunctive_walker.py | 4 +-- pyomo/contrib/latex_printer/latex_printer.py | 12 +++---- pyomo/contrib/pyros/config.py | 8 ++--- pyomo/contrib/pyros/tests/test_config.py | 8 ++--- pyomo/contrib/solver/base.py | 6 ++-- pyomo/contrib/solver/gurobi.py | 6 ++-- pyomo/contrib/solver/persistent.py | 10 +++--- pyomo/contrib/viewer/model_browser.py | 8 ++--- pyomo/core/base/component.py | 2 +- pyomo/core/base/param.py | 35 +++++++++++-------- pyomo/core/expr/calculus/derivatives.py | 2 +- pyomo/core/tests/unit/test_param.py | 10 +++--- pyomo/core/tests/unit/test_visitor.py | 4 +-- pyomo/repn/plugins/ampl/ampl_.py | 8 ++--- pyomo/repn/standard_repn.py | 6 ++-- 26 files changed, 102 insertions(+), 99 deletions(-) diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index 1ce24220bfd..e50d5201090 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -24,7 +24,7 @@ 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.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 @@ -803,7 +803,7 @@ def add_variables(self, variables: List[GeneralVarData]): pass @abc.abstractmethod - def add_params(self, params: List[_ParamData]): + def add_params(self, params: List[ParamData]): pass @abc.abstractmethod @@ -819,7 +819,7 @@ def remove_variables(self, variables: List[GeneralVarData]): pass @abc.abstractmethod - def remove_params(self, params: List[_ParamData]): + def remove_params(self, params: List[ParamData]): pass @abc.abstractmethod @@ -975,10 +975,10 @@ def add_variables(self, variables: List[GeneralVarData]): self._add_variables(variables) @abc.abstractmethod - def _add_params(self, params: List[_ParamData]): + def _add_params(self, params: List[ParamData]): pass - def add_params(self, params: List[_ParamData]): + def add_params(self, params: List[ParamData]): for p in params: self._params[id(p)] = p self._add_params(params) @@ -1198,10 +1198,10 @@ def remove_variables(self, variables: List[GeneralVarData]): del self._vars[v_id] @abc.abstractmethod - def _remove_params(self, params: List[_ParamData]): + def _remove_params(self, params: List[ParamData]): pass - def remove_params(self, params: List[_ParamData]): + def remove_params(self, params: List[ParamData]): self._remove_params(params) for p in params: del self._params[id(p)] diff --git a/pyomo/contrib/appsi/fbbt.py b/pyomo/contrib/appsi/fbbt.py index a360d2bce84..7735318f8ba 100644 --- a/pyomo/contrib/appsi/fbbt.py +++ b/pyomo/contrib/appsi/fbbt.py @@ -19,7 +19,7 @@ from .cmodel import cmodel, cmodel_available from typing import List, Optional from pyomo.core.base.var import GeneralVarData -from pyomo.core.base.param import _ParamData +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 @@ -143,7 +143,7 @@ def _add_variables(self, variables: List[GeneralVarData]): False, ) - def _add_params(self, params: List[_ParamData]): + def _add_params(self, params: List[ParamData]): cparams = cmodel.create_params(len(params)) for ndx, p in enumerate(params): cp = cparams[ndx] @@ -198,7 +198,7 @@ def _remove_variables(self, variables: List[GeneralVarData]): cvar = self._var_map.pop(id(v)) del self._rvar_map[cvar] - def _remove_params(self, params: List[_ParamData]): + def _remove_params(self, params: List[ParamData]): if self._symbolic_solver_labels: for p in params: self._symbol_map.removeSymbol(p) diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index cd5158d905e..d03e6e31c54 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -29,7 +29,7 @@ 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.param import ParamData from pyomo.core.base.objective import GeneralObjectiveData from pyomo.common.timing import HierarchicalTimer from pyomo.common.tee import TeeStream @@ -167,7 +167,7 @@ def set_instance(self, model): def add_variables(self, variables: List[GeneralVarData]): self._writer.add_variables(variables) - def add_params(self, params: List[_ParamData]): + def add_params(self, params: List[ParamData]): self._writer.add_params(params) def add_constraints(self, cons: List[GeneralConstraintData]): @@ -179,7 +179,7 @@ def add_block(self, block: BlockData): def remove_variables(self, variables: List[GeneralVarData]): self._writer.remove_variables(variables) - def remove_params(self, params: List[_ParamData]): + def remove_params(self, params: List[ParamData]): self._writer.remove_params(params) def remove_constraints(self, cons: List[GeneralConstraintData]): diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index cdd699105be..55259244d45 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -25,7 +25,7 @@ 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.param import ParamData from pyomo.core.base.objective import GeneralObjectiveData from pyomo.common.timing import HierarchicalTimer import sys @@ -182,7 +182,7 @@ def set_instance(self, model): def add_variables(self, variables: List[GeneralVarData]): self._writer.add_variables(variables) - def add_params(self, params: List[_ParamData]): + def add_params(self, params: List[ParamData]): self._writer.add_params(params) def add_constraints(self, cons: List[GeneralConstraintData]): @@ -194,7 +194,7 @@ def add_block(self, block: BlockData): def remove_variables(self, variables: List[GeneralVarData]): self._writer.remove_variables(variables) - def remove_params(self, params: List[_ParamData]): + def remove_params(self, params: List[ParamData]): self._writer.remove_params(params) def remove_constraints(self, cons: List[GeneralConstraintData]): diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index 6da59042a80..4392cdf0839 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -26,7 +26,7 @@ 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.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 @@ -489,7 +489,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_params(self, params: List[ParamData]): pass def _reinit(self): @@ -771,7 +771,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_params(self, params: List[ParamData]): pass def _update_variables(self, variables: List[GeneralVarData]): diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index ded0092f38b..a6b7c102c91 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -23,7 +23,7 @@ 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.base.param import ParamData 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 @@ -335,7 +335,7 @@ def _add_variables(self, variables: List[GeneralVarData]): len(vtypes), np.array(indices), np.array(vtypes) ) - def _add_params(self, params: List[_ParamData]): + def _add_params(self, params: List[ParamData]): pass def _reinit(self): @@ -515,7 +515,7 @@ def _remove_variables(self, variables: List[GeneralVarData]): self._pyomo_var_to_solver_var_map.clear() self._pyomo_var_to_solver_var_map.update(new_var_map) - def _remove_params(self, params: List[_ParamData]): + def _remove_params(self, params: List[ParamData]): pass def _update_variables(self, variables: List[GeneralVarData]): diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index 9ccb58095b1..ca75a1b02c8 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -31,7 +31,7 @@ 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.param import ParamData from pyomo.core.base.objective import GeneralObjectiveData from pyomo.common.timing import HierarchicalTimer from pyomo.common.tee import TeeStream @@ -231,7 +231,7 @@ def set_instance(self, model): def add_variables(self, variables: List[GeneralVarData]): self._writer.add_variables(variables) - def add_params(self, params: List[_ParamData]): + def add_params(self, params: List[ParamData]): self._writer.add_params(params) def add_constraints(self, cons: List[GeneralConstraintData]): @@ -243,7 +243,7 @@ def add_block(self, block: BlockData): def remove_variables(self, variables: List[GeneralVarData]): self._writer.remove_variables(variables) - def remove_params(self, params: List[_ParamData]): + def remove_params(self, params: List[ParamData]): self._writer.remove_params(params) def remove_constraints(self, cons: List[GeneralConstraintData]): diff --git a/pyomo/contrib/appsi/solvers/wntr.py b/pyomo/contrib/appsi/solvers/wntr.py index 7f633161fe1..8f2650dabb6 100644 --- a/pyomo/contrib/appsi/solvers/wntr.py +++ b/pyomo/contrib/appsi/solvers/wntr.py @@ -41,7 +41,7 @@ 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.param import ParamData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.common.timing import HierarchicalTimer from pyomo.core.base import SymbolMap, NumericLabeler, TextLabeler @@ -270,7 +270,7 @@ def _add_variables(self, variables: List[GeneralVarData]): ) self._needs_updated = True - def _add_params(self, params: List[_ParamData]): + def _add_params(self, params: List[ParamData]): aml = wntr.sim.aml.aml for p in params: pname = self._symbol_map.getSymbol(p, self._labeler) @@ -314,7 +314,7 @@ def _remove_variables(self, variables: List[GeneralVarData]): del self._solver_model._wntr_fixed_var_cons[v_id] self._needs_updated = True - def _remove_params(self, params: List[_ParamData]): + 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] diff --git a/pyomo/contrib/appsi/writers/lp_writer.py b/pyomo/contrib/appsi/writers/lp_writer.py index 94af5ba7e93..3a168cdcd91 100644 --- a/pyomo/contrib/appsi/writers/lp_writer.py +++ b/pyomo/contrib/appsi/writers/lp_writer.py @@ -10,7 +10,7 @@ # ___________________________________________________________________________ from typing import List -from pyomo.core.base.param import _ParamData +from pyomo.core.base.param import ParamData from pyomo.core.base.var import GeneralVarData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.objective import GeneralObjectiveData @@ -91,7 +91,7 @@ def _add_variables(self, variables: List[GeneralVarData]): False, ) - def _add_params(self, params: List[_ParamData]): + def _add_params(self, params: List[ParamData]): cparams = cmodel.create_params(len(params)) for ndx, p in enumerate(params): cp = cparams[ndx] @@ -123,7 +123,7 @@ def _remove_variables(self, variables: List[GeneralVarData]): del self._solver_var_to_pyomo_var_map[cvar] self._symbol_map.removeSymbol(v) - def _remove_params(self, params: List[_ParamData]): + def _remove_params(self, params: List[ParamData]): for p in params: del self._pyomo_param_to_solver_param_map[id(p)] self._symbol_map.removeSymbol(p) diff --git a/pyomo/contrib/appsi/writers/nl_writer.py b/pyomo/contrib/appsi/writers/nl_writer.py index b7dab1d5a3e..fced3c5ae10 100644 --- a/pyomo/contrib/appsi/writers/nl_writer.py +++ b/pyomo/contrib/appsi/writers/nl_writer.py @@ -10,7 +10,7 @@ # ___________________________________________________________________________ from typing import List -from pyomo.core.base.param import _ParamData +from pyomo.core.base.param import ParamData from pyomo.core.base.var import GeneralVarData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.objective import GeneralObjectiveData @@ -100,7 +100,7 @@ def _add_variables(self, variables: List[GeneralVarData]): False, ) - def _add_params(self, params: List[_ParamData]): + def _add_params(self, params: List[ParamData]): cparams = cmodel.create_params(len(params)) for ndx, p in enumerate(params): cp = cparams[ndx] @@ -153,7 +153,7 @@ def _remove_variables(self, variables: List[GeneralVarData]): cvar = self._pyomo_var_to_solver_var_map.pop(id(v)) del self._solver_var_to_pyomo_var_map[cvar] - def _remove_params(self, params: List[_ParamData]): + def _remove_params(self, params: List[ParamData]): if self.config.symbolic_solver_labels: for p in params: self._symbol_map.removeSymbol(p) diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index eb50a543160..75095755895 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, _ParamData +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 @@ -970,7 +970,7 @@ class LogicalToDoCplex(StreamBasedExpressionVisitor): ScalarExpression: _before_named_expression, IndexedParam: _before_indexed_param, # Because of indirection ScalarParam: _before_param, - _ParamData: _before_param, + ParamData: _before_param, } def __init__(self, cpx_model, symbolic_solver_labels=False): diff --git a/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py b/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py index 26b63d020a5..a228b1561dd 100644 --- a/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py +++ b/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py @@ -28,7 +28,7 @@ ) import pyomo.core.base.boolean_var as BV from pyomo.core.base.expression import ScalarExpression, GeneralExpressionData -from pyomo.core.base.param import ScalarParam, _ParamData +from pyomo.core.base.param import ScalarParam, ParamData from pyomo.core.base.var import ScalarVar, GeneralVarData from pyomo.gdp.disjunct import AutoLinkedBooleanVar, Disjunct, Disjunction @@ -211,7 +211,7 @@ def _dispatch_atmost(visitor, node, *args): _before_child_dispatcher[BV.ScalarBooleanVar] = _dispatch_boolean_var _before_child_dispatcher[BV.GeneralBooleanVarData] = _dispatch_boolean_var _before_child_dispatcher[AutoLinkedBooleanVar] = _dispatch_boolean_var -_before_child_dispatcher[_ParamData] = _dispatch_param +_before_child_dispatcher[ParamData] = _dispatch_param _before_child_dispatcher[ScalarParam] = _dispatch_param # for the moment, these are all just so we can get good error messages when we # don't handle them: diff --git a/pyomo/contrib/latex_printer/latex_printer.py b/pyomo/contrib/latex_printer/latex_printer.py index efcd3016dbf..dec058bb5ba 100644 --- a/pyomo/contrib/latex_printer/latex_printer.py +++ b/pyomo/contrib/latex_printer/latex_printer.py @@ -48,7 +48,7 @@ templatize_rule, ) from pyomo.core.base.var import ScalarVar, GeneralVarData, IndexedVar -from pyomo.core.base.param import _ParamData, ScalarParam, IndexedParam +from pyomo.core.base.param import ParamData, ScalarParam, IndexedParam from pyomo.core.base.set import _SetData, SetOperator from pyomo.core.base.constraint import ScalarConstraint, IndexedConstraint from pyomo.common.collections.component_map import ComponentMap @@ -417,7 +417,7 @@ def __init__(self): Numeric_GetItemExpression: handle_numericGetItemExpression_node, TemplateSumExpression: handle_templateSumExpression_node, ScalarParam: handle_param_node, - _ParamData: handle_param_node, + ParamData: handle_param_node, IndexedParam: handle_param_node, NPV_Numeric_GetItemExpression: handle_numericGetItemExpression_node, IndexedBlock: handle_indexedBlock_node, @@ -717,10 +717,8 @@ def latex_printer( variableList.append(v) parameterList = [] - for p in identify_components( - temp_comp, [ScalarParam, _ParamData, IndexedParam] - ): - if isinstance(p, _ParamData): + 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) @@ -1280,7 +1278,7 @@ def get_index_names(st, lcm): if ky not in existing_components: overwrite_value = overwrite_value.replace('_', '\\_') rep_dict[variableMap[ky]] = overwrite_value - elif isinstance(ky, (pyo.Param, _ParamData)): + elif isinstance(ky, (pyo.Param, ParamData)): overwrite_value = latex_component_map[ky] if ky not in existing_components: overwrite_value = overwrite_value.replace('_', '\\_') diff --git a/pyomo/contrib/pyros/config.py b/pyomo/contrib/pyros/config.py index bc2bfd591e6..e60b474d037 100644 --- a/pyomo/contrib/pyros/config.py +++ b/pyomo/contrib/pyros/config.py @@ -17,7 +17,7 @@ ) from pyomo.common.errors import ApplicationError, PyomoException from pyomo.core.base import Var, _VarData -from pyomo.core.base.param import Param, _ParamData +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 UncertaintySet @@ -62,7 +62,7 @@ def mutable_param_validator(param_obj): Parameters ---------- - param_obj : Param or _ParamData + param_obj : Param or ParamData Param-like object of interest. Raises @@ -98,7 +98,7 @@ class InputDataStandardizer(object): Pyomo component type, such as Component, Var or Param. cdatatype : type Corresponding Pyomo component data type, such as - _ComponentData, _VarData, or _ParamData. + _ComponentData, _VarData, or ParamData. ctype_validator : callable, optional Validator function for objects of type `ctype`. cdatatype_validator : callable, optional @@ -531,7 +531,7 @@ def pyros_config(): default=[], domain=InputDataStandardizer( ctype=Param, - cdatatype=_ParamData, + cdatatype=ParamData, ctype_validator=mutable_param_validator, allow_repeats=False, ), diff --git a/pyomo/contrib/pyros/tests/test_config.py b/pyomo/contrib/pyros/tests/test_config.py index 0f52d04135d..cd635e795fc 100644 --- a/pyomo/contrib/pyros/tests/test_config.py +++ b/pyomo/contrib/pyros/tests/test_config.py @@ -8,7 +8,7 @@ from pyomo.core.base import ConcreteModel, Var, _VarData from pyomo.common.log import LoggingIntercept from pyomo.common.errors import ApplicationError -from pyomo.core.base.param import Param, _ParamData +from pyomo.core.base.param import Param, ParamData from pyomo.contrib.pyros.config import ( InputDataStandardizer, mutable_param_validator, @@ -201,7 +201,7 @@ def test_standardizer_invalid_uninitialized_params(self): uninitialized entries passed. """ standardizer_func = InputDataStandardizer( - ctype=Param, cdatatype=_ParamData, ctype_validator=mutable_param_validator + ctype=Param, cdatatype=ParamData, ctype_validator=mutable_param_validator ) mdl = ConcreteModel() @@ -217,7 +217,7 @@ def test_standardizer_invalid_immutable_params(self): Param object(s) passed. """ standardizer_func = InputDataStandardizer( - ctype=Param, cdatatype=_ParamData, ctype_validator=mutable_param_validator + ctype=Param, cdatatype=ParamData, ctype_validator=mutable_param_validator ) mdl = ConcreteModel() @@ -237,7 +237,7 @@ def test_standardizer_valid_mutable_params(self): mdl.p2 = Param(["a", "b"], initialize=1, mutable=True) standardizer_func = InputDataStandardizer( - ctype=Param, cdatatype=_ParamData, ctype_validator=mutable_param_validator + ctype=Param, cdatatype=ParamData, ctype_validator=mutable_param_validator ) standardizer_input = [mdl.p1[0], mdl.p2] diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index a935a950819..1b22c17cf48 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -16,7 +16,7 @@ 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.param import ParamData from pyomo.core.base.block import BlockData from pyomo.core.base.objective import GeneralObjectiveData from pyomo.common.config import document_kwargs_from_configdict, ConfigValue @@ -288,7 +288,7 @@ def add_variables(self, variables: List[GeneralVarData]): """ @abc.abstractmethod - def add_parameters(self, params: List[_ParamData]): + def add_parameters(self, params: List[ParamData]): """ Add parameters to the model """ @@ -312,7 +312,7 @@ def remove_variables(self, variables: List[GeneralVarData]): """ @abc.abstractmethod - def remove_parameters(self, params: List[_ParamData]): + def remove_parameters(self, params: List[ParamData]): """ Remove parameters from the model """ diff --git a/pyomo/contrib/solver/gurobi.py b/pyomo/contrib/solver/gurobi.py index 353798133db..107de15e625 100644 --- a/pyomo/contrib/solver/gurobi.py +++ b/pyomo/contrib/solver/gurobi.py @@ -25,7 +25,7 @@ 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.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 @@ -469,7 +469,7 @@ def _add_variables(self, variables: List[GeneralVarData]): self._vars_added_since_update.update(variables) self._needs_updated = True - def _add_parameters(self, params: List[_ParamData]): + def _add_parameters(self, params: List[ParamData]): pass def _reinit(self): @@ -747,7 +747,7 @@ def _remove_variables(self, variables: List[GeneralVarData]): self._mutable_bounds.pop(v_id, None) self._needs_updated = True - def _remove_parameters(self, params: List[_ParamData]): + def _remove_parameters(self, params: List[ParamData]): pass def _update_variables(self, variables: List[GeneralVarData]): diff --git a/pyomo/contrib/solver/persistent.py b/pyomo/contrib/solver/persistent.py index aeacc9f87c4..558b8cbf314 100644 --- a/pyomo/contrib/solver/persistent.py +++ b/pyomo/contrib/solver/persistent.py @@ -15,7 +15,7 @@ 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.param import ParamData, Param from pyomo.core.base.objective import GeneralObjectiveData from pyomo.common.collections import ComponentMap from pyomo.common.timing import HierarchicalTimer @@ -75,10 +75,10 @@ def add_variables(self, variables: List[GeneralVarData]): self._add_variables(variables) @abc.abstractmethod - def _add_parameters(self, params: List[_ParamData]): + def _add_parameters(self, params: List[ParamData]): pass - def add_parameters(self, params: List[_ParamData]): + def add_parameters(self, params: List[ParamData]): for p in params: self._params[id(p)] = p self._add_parameters(params) @@ -274,10 +274,10 @@ def remove_variables(self, variables: List[GeneralVarData]): del self._vars[v_id] @abc.abstractmethod - def _remove_parameters(self, params: List[_ParamData]): + def _remove_parameters(self, params: List[ParamData]): pass - def remove_parameters(self, params: List[_ParamData]): + def remove_parameters(self, params: List[ParamData]): self._remove_parameters(params) for p in params: del self._params[id(p)] diff --git a/pyomo/contrib/viewer/model_browser.py b/pyomo/contrib/viewer/model_browser.py index 5887a577ba0..91dc946c55d 100644 --- a/pyomo/contrib/viewer/model_browser.py +++ b/pyomo/contrib/viewer/model_browser.py @@ -33,7 +33,7 @@ import pyomo.contrib.viewer.qt as myqt from pyomo.contrib.viewer.report import value_no_exception, get_residual -from pyomo.core.base.param import _ParamData +from pyomo.core.base.param import ParamData from pyomo.environ import ( Block, BooleanVar, @@ -243,7 +243,7 @@ def _get_expr_callback(self): return None def _get_value_callback(self): - if isinstance(self.data, _ParamData): + if isinstance(self.data, ParamData): v = value_no_exception(self.data, div0="divide_by_0") # Check the param value for numpy float and int, sometimes numpy # values can sneak in especially if you set parameters from data @@ -295,7 +295,7 @@ def _get_residual_callback(self): def _get_units_callback(self): if isinstance(self.data, (Var, Var._ComponentDataClass)): return str(units.get_units(self.data)) - if isinstance(self.data, (Param, _ParamData)): + if isinstance(self.data, (Param, ParamData)): return str(units.get_units(self.data)) return self._cache_units @@ -320,7 +320,7 @@ def _set_value_callback(self, val): o.value = val except: return - elif isinstance(self.data, _ParamData): + elif isinstance(self.data, ParamData): if not self.data.parent_component().mutable: return try: diff --git a/pyomo/core/base/component.py b/pyomo/core/base/component.py index 341cd1506ff..33b2f5c686c 100644 --- a/pyomo/core/base/component.py +++ b/pyomo/core/base/component.py @@ -805,7 +805,7 @@ class ComponentData(_ComponentBase): # classes: BooleanVarData, ConnectorData, ConstraintData, # GeneralExpressionData, LogicalConstraintData, # GeneralLogicalConstraintData, GeneralObjectiveData, - # _ParamData,GeneralVarData, GeneralBooleanVarData, DisjunctionData, + # ParamData,GeneralVarData, GeneralBooleanVarData, DisjunctionData, # ArcData, _PortData, _LinearConstraintData, and # _LinearMatrixConstraintData. Changes made here need to be made in those # constructors as well! diff --git a/pyomo/core/base/param.py b/pyomo/core/base/param.py index 5fcaf92b25a..9af6a37de45 100644 --- a/pyomo/core/base/param.py +++ b/pyomo/core/base/param.py @@ -118,7 +118,7 @@ def _parent(self, val): pass -class _ParamData(ComponentData, NumericValue): +class ParamData(ComponentData, NumericValue): """ This class defines the data for a mutable parameter. @@ -252,6 +252,11 @@ def _compute_polynomial_degree(self, result): return 0 +class _ParamData(metaclass=RenamedClass): + __renamed__new_class__ = ParamData + __renamed__version__ = '6.7.2.dev0' + + @ModelComponentFactory.register( "Parameter data that is used to define a model instance." ) @@ -285,7 +290,7 @@ class Param(IndexedComponent, IndexedComponent_NDArrayMixin): """ DefaultMutable = False - _ComponentDataClass = _ParamData + _ComponentDataClass = ParamData class NoValue(object): """A dummy type that is pickle-safe that we can use as the default @@ -523,14 +528,14 @@ def store_values(self, new_values, check=True): # instead of incurring the penalty of checking. for index, new_value in new_values.items(): if index not in self._data: - self._data[index] = _ParamData(self) + self._data[index] = ParamData(self) self._data[index]._value = new_value else: # For scalars, we will choose an approach based on # how "dense" the Param is if not self._data: # empty for index in self._index_set: - p = self._data[index] = _ParamData(self) + p = self._data[index] = ParamData(self) p._value = new_values elif len(self._data) == len(self._index_set): for index in self._index_set: @@ -538,7 +543,7 @@ def store_values(self, new_values, check=True): else: for index in self._index_set: if index not in self._data: - self._data[index] = _ParamData(self) + self._data[index] = ParamData(self) self._data[index]._value = new_values else: # @@ -601,9 +606,9 @@ def _getitem_when_not_present(self, index): # a default value, as long as *solving* a model without # reasonable values produces an informative error. if self._mutable: - # Note: _ParamData defaults to Param.NoValue + # Note: ParamData defaults to Param.NoValue if self.is_indexed(): - ans = self._data[index] = _ParamData(self) + ans = self._data[index] = ParamData(self) else: ans = self._data[index] = self ans._index = index @@ -698,8 +703,8 @@ def _setitem_impl(self, index, obj, value): return obj else: old_value, self._data[index] = self._data[index], value - # Because we do not have a _ParamData, we cannot rely on the - # validation that occurs in _ParamData.set_value() + # Because we do not have a ParamData, we cannot rely on the + # validation that occurs in ParamData.set_value() try: self._validate_value(index, value) return value @@ -736,14 +741,14 @@ def _setitem_when_not_present(self, index, value, _check_domain=True): self._index = UnindexedComponent_index return self elif self._mutable: - obj = self._data[index] = _ParamData(self) + obj = self._data[index] = ParamData(self) obj.set_value(value, index) obj._index = index return obj else: self._data[index] = value - # Because we do not have a _ParamData, we cannot rely on the - # validation that occurs in _ParamData.set_value() + # Because we do not have a ParamData, we cannot rely on the + # validation that occurs in ParamData.set_value() self._validate_value(index, value, _check_domain) return value except: @@ -901,9 +906,9 @@ def _pprint(self): return (headers, self.sparse_iteritems(), ("Value",), dataGen) -class ScalarParam(_ParamData, Param): +class ScalarParam(ParamData, Param): def __init__(self, *args, **kwds): - _ParamData.__init__(self, component=self) + ParamData.__init__(self, component=self) Param.__init__(self, *args, **kwds) self._index = UnindexedComponent_index @@ -996,7 +1001,7 @@ def _create_objects_for_deepcopy(self, memo, component_list): # between potentially variable GetItemExpression objects and # "constant" GetItemExpression objects. That will need to wait for # the expression rework [JDS; Nov 22]. - def __getitem__(self, args) -> _ParamData: + def __getitem__(self, args) -> ParamData: try: return super().__getitem__(args) except: diff --git a/pyomo/core/expr/calculus/derivatives.py b/pyomo/core/expr/calculus/derivatives.py index cd23cb16b2c..5df1fd3c65e 100644 --- a/pyomo/core/expr/calculus/derivatives.py +++ b/pyomo/core/expr/calculus/derivatives.py @@ -42,7 +42,7 @@ def differentiate(expr, wrt=None, wrt_list=None, mode=Modes.reverse_numeric): wrt: pyomo.core.base.var.GeneralVarData If specified, this function will return the derivative with respect to wrt. wrt is normally a GeneralVarData, but could - also be a _ParamData. wrt and wrt_list cannot both be specified. + also be a ParamData. wrt and wrt_list cannot both be specified. wrt_list: list of pyomo.core.base.var.GeneralVarData If specified, this function will return the derivative with respect to each element in wrt_list. A list will be returned diff --git a/pyomo/core/tests/unit/test_param.py b/pyomo/core/tests/unit/test_param.py index 9bc0c4b2ad2..b39272879f6 100644 --- a/pyomo/core/tests/unit/test_param.py +++ b/pyomo/core/tests/unit/test_param.py @@ -65,7 +65,7 @@ from pyomo.common.errors import PyomoException from pyomo.common.log import LoggingIntercept from pyomo.common.tempfiles import TempfileManager -from pyomo.core.base.param import _ParamData +from pyomo.core.base.param import ParamData from pyomo.core.base.set import _SetData from pyomo.core.base.units_container import units, pint_available, UnitsError @@ -181,7 +181,7 @@ def test_setitem_preexisting(self): idx = sorted(keys)[0] self.assertEqual(value(self.instance.A[idx]), self.data[idx]) if self.instance.A.mutable: - self.assertTrue(isinstance(self.instance.A[idx], _ParamData)) + self.assertTrue(isinstance(self.instance.A[idx], ParamData)) else: self.assertEqual(type(self.instance.A[idx]), float) @@ -190,7 +190,7 @@ def test_setitem_preexisting(self): if not self.instance.A.mutable: self.fail("Expected setitem[%s] to fail for immutable Params" % (idx,)) self.assertEqual(value(self.instance.A[idx]), 4.3) - self.assertTrue(isinstance(self.instance.A[idx], _ParamData)) + self.assertTrue(isinstance(self.instance.A[idx], ParamData)) except TypeError: # immutable Params should raise a TypeError exception if self.instance.A.mutable: @@ -249,7 +249,7 @@ def test_setitem_default_override(self): self.assertEqual(value(self.instance.A[idx]), self.instance.A._default_val) if self.instance.A.mutable: - self.assertIsInstance(self.instance.A[idx], _ParamData) + self.assertIsInstance(self.instance.A[idx], ParamData) else: self.assertEqual( type(self.instance.A[idx]), type(value(self.instance.A._default_val)) @@ -260,7 +260,7 @@ def test_setitem_default_override(self): if not self.instance.A.mutable: self.fail("Expected setitem[%s] to fail for immutable Params" % (idx,)) self.assertEqual(self.instance.A[idx].value, 4.3) - self.assertIsInstance(self.instance.A[idx], _ParamData) + self.assertIsInstance(self.instance.A[idx], ParamData) except TypeError: # immutable Params should raise a TypeError exception if self.instance.A.mutable: diff --git a/pyomo/core/tests/unit/test_visitor.py b/pyomo/core/tests/unit/test_visitor.py index 12fb98d1d19..ac61a3a24c7 100644 --- a/pyomo/core/tests/unit/test_visitor.py +++ b/pyomo/core/tests/unit/test_visitor.py @@ -72,7 +72,7 @@ RECURSION_LIMIT, get_stack_depth, ) -from pyomo.core.base.param import _ParamData, ScalarParam +from pyomo.core.base.param import ParamData, ScalarParam from pyomo.core.expr.template_expr import IndexTemplate from pyomo.common.collections import ComponentSet from pyomo.common.errors import TemplateExpressionError @@ -685,7 +685,7 @@ def __init__(self, model): self.model = model def visiting_potential_leaf(self, node): - if node.__class__ in (_ParamData, ScalarParam): + if node.__class__ in (ParamData, ScalarParam): if id(node) in self.substitute: return True, self.substitute[id(node)] self.substitute[id(node)] = 2 * self.model.w.add() diff --git a/pyomo/repn/plugins/ampl/ampl_.py b/pyomo/repn/plugins/ampl/ampl_.py index c6357cbecd9..840bee2166c 100644 --- a/pyomo/repn/plugins/ampl/ampl_.py +++ b/pyomo/repn/plugins/ampl/ampl_.py @@ -171,8 +171,8 @@ def _build_op_template(): _op_template[var._VarData] = "v%d{C}\n" _op_comment[var._VarData] = "\t#%s" - _op_template[param._ParamData] = "n%r{C}\n" - _op_comment[param._ParamData] = "" + _op_template[param.ParamData] = "n%r{C}\n" + _op_comment[param.ParamData] = "" _op_template[NumericConstant] = "n%r{C}\n" _op_comment[NumericConstant] = "" @@ -749,8 +749,8 @@ def _print_nonlinear_terms_NL(self, exp): ) ) - elif isinstance(exp, param._ParamData): - OUTPUT.write(self._op_string[param._ParamData] % (value(exp))) + elif isinstance(exp, param.ParamData): + OUTPUT.write(self._op_string[param.ParamData] % (value(exp))) elif isinstance(exp, NumericConstant) or exp.is_fixed(): OUTPUT.write(self._op_string[NumericConstant] % (value(exp))) diff --git a/pyomo/repn/standard_repn.py b/pyomo/repn/standard_repn.py index 907b4a2b115..5786d078385 100644 --- a/pyomo/repn/standard_repn.py +++ b/pyomo/repn/standard_repn.py @@ -23,7 +23,7 @@ from pyomo.core.base import ExpressionData, Expression from pyomo.core.base.expression import ScalarExpression, GeneralExpressionData from pyomo.core.base.var import ScalarVar, Var, GeneralVarData, value -from pyomo.core.base.param import ScalarParam, _ParamData +from pyomo.core.base.param import ScalarParam, ParamData from pyomo.core.kernel.expression import expression, noclone from pyomo.core.kernel.variable import IVariable, variable from pyomo.core.kernel.objective import objective @@ -1138,7 +1138,7 @@ def _collect_external_fn(exp, multiplier, idMap, compute_values, verbose, quadra EXPR.ExternalFunctionExpression: _collect_external_fn, # ConnectorData : _collect_linear_connector, # ScalarConnector : _collect_linear_connector, - _ParamData: _collect_const, + ParamData: _collect_const, ScalarParam: _collect_const, # param.Param : _collect_linear_const, # parameter : _collect_linear_const, @@ -1538,7 +1538,7 @@ def _linear_collect_pow(exp, multiplier, idMap, compute_values, verbose, coef): ##EXPR.LinearSumExpression : _collect_linear_sum, ##ConnectorData : _collect_linear_connector, ##ScalarConnector : _collect_linear_connector, - ##param._ParamData : _collect_linear_const, + ##param.ParamData : _collect_linear_const, ##param.ScalarParam : _collect_linear_const, ##param.Param : _collect_linear_const, ##parameter : _collect_linear_const, From 8c968e3e976bc8ea8c551a7c48d275d623e04559 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:51:57 -0600 Subject: [PATCH 1461/1797] Renamed _PiecewiseData -> PiecewiseData --- pyomo/core/base/piecewise.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pyomo/core/base/piecewise.py b/pyomo/core/base/piecewise.py index b15def13ccb..43f8ddbfef5 100644 --- a/pyomo/core/base/piecewise.py +++ b/pyomo/core/base/piecewise.py @@ -214,7 +214,7 @@ def _characterize_function(name, tol, f_rule, model, points, *index): return 0, values, False -class _PiecewiseData(BlockData): +class PiecewiseData(BlockData): """ This class defines the base class for all linearization and piecewise constraint generators.. @@ -272,6 +272,11 @@ def __call__(self, x): ) +class _PiecewiseData(metaclass=RenamedClass): + __renamed__new_class__ = PiecewiseData + __renamed__version__ = '6.7.2.dev0' + + class _SimpleSinglePiecewise(object): """ Called when the piecewise points list has only two points @@ -1125,7 +1130,7 @@ def f(model,j,x): not be modified. """ - _ComponentDataClass = _PiecewiseData + _ComponentDataClass = PiecewiseData def __new__(cls, *args, **kwds): if cls != Piecewise: @@ -1541,7 +1546,7 @@ def add(self, index, _is_indexed=None): raise ValueError(msg % (self.name, index, self._pw_rep)) if _is_indexed: - comp = _PiecewiseData(self) + comp = PiecewiseData(self) else: comp = self self._data[index] = comp @@ -1551,9 +1556,9 @@ def add(self, index, _is_indexed=None): comp.build_constraints(func, _self_xvar, _self_yvar) -class SimplePiecewise(_PiecewiseData, Piecewise): +class SimplePiecewise(PiecewiseData, Piecewise): def __init__(self, *args, **kwds): - _PiecewiseData.__init__(self, self) + PiecewiseData.__init__(self, self) Piecewise.__init__(self, *args, **kwds) From 590e21fad08496e01fc6611fe506867b2ae6217d Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:52:11 -0600 Subject: [PATCH 1462/1797] Renamed _PortData -> PortData --- pyomo/core/base/component.py | 2 +- pyomo/network/port.py | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/pyomo/core/base/component.py b/pyomo/core/base/component.py index 33b2f5c686c..7a4b7e40aab 100644 --- a/pyomo/core/base/component.py +++ b/pyomo/core/base/component.py @@ -806,7 +806,7 @@ class ComponentData(_ComponentBase): # GeneralExpressionData, LogicalConstraintData, # GeneralLogicalConstraintData, GeneralObjectiveData, # ParamData,GeneralVarData, GeneralBooleanVarData, DisjunctionData, - # ArcData, _PortData, _LinearConstraintData, and + # ArcData, PortData, _LinearConstraintData, and # _LinearMatrixConstraintData. Changes made here need to be made in those # constructors as well! def __init__(self, component): diff --git a/pyomo/network/port.py b/pyomo/network/port.py index 26822d4fee9..ee5c915d8db 100644 --- a/pyomo/network/port.py +++ b/pyomo/network/port.py @@ -36,7 +36,7 @@ logger = logging.getLogger('pyomo.network') -class _PortData(ComponentData): +class PortData(ComponentData): """ This class defines the data for a single Port @@ -285,6 +285,11 @@ def get_split_fraction(self, arc): return res +class _PortData(metaclass=RenamedClass): + __renamed__new_class__ = PortData + __renamed__version__ = '6.7.2.dev0' + + @ModelComponentFactory.register( "A bundle of variables that can be connected to other ports." ) @@ -339,7 +344,7 @@ def __init__(self, *args, **kwd): # IndexedComponent that support implicit definition def _getitem_when_not_present(self, idx): """Returns the default component data value.""" - tmp = self._data[idx] = _PortData(component=self) + tmp = self._data[idx] = PortData(component=self) tmp._index = idx return tmp @@ -357,7 +362,7 @@ def construct(self, data=None): for _set in self._anonymous_sets: _set.construct() - # Construct _PortData objects for all index values + # Construct PortData objects for all index values if self.is_indexed(): self._initialize_members(self._index_set) else: @@ -763,9 +768,9 @@ def _create_evar(member, name, eblock, index_set): return evar -class ScalarPort(Port, _PortData): +class ScalarPort(Port, PortData): def __init__(self, *args, **kwd): - _PortData.__init__(self, component=self) + PortData.__init__(self, component=self) Port.__init__(self, *args, **kwd) self._index = UnindexedComponent_index From 199ee006d445859f7d796f528a8b326cf883f3f6 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:55:57 -0600 Subject: [PATCH 1463/1797] Renamed _SetData -> SetData --- pyomo/contrib/latex_printer/latex_printer.py | 4 +- pyomo/core/base/set.py | 51 +++++++++++--------- pyomo/core/base/sets.py | 4 +- pyomo/core/tests/unit/test_param.py | 4 +- pyomo/core/tests/unit/test_set.py | 18 +++---- 5 files changed, 43 insertions(+), 38 deletions(-) diff --git a/pyomo/contrib/latex_printer/latex_printer.py b/pyomo/contrib/latex_printer/latex_printer.py index dec058bb5ba..28d1ca52943 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, SetOperator +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 @@ -1283,7 +1283,7 @@ def get_index_names(st, lcm): if ky not in existing_components: overwrite_value = overwrite_value.replace('_', '\\_') rep_dict[parameterMap[ky]] = overwrite_value - elif isinstance(ky, _SetData): + elif isinstance(ky, SetData): # already handled pass elif isinstance(ky, (float, int)): diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index c7a7edc8e4b..3280f512e83 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -87,7 +87,7 @@ 0. `class _SetDataBase(ComponentData)` *(pure virtual interface)* -1. `class _SetData(_SetDataBase)` +1. `class SetData(_SetDataBase)` *(base class for all AML Sets)* 2. `class _FiniteSetMixin(object)` @@ -102,7 +102,7 @@ bounded continuous ranges as well as unbounded discrete ranges). As there are an infinite number of values, iteration is *not* supported. The base class also implements all Python set operations. -Note that `_SetData` does *not* implement `len()`, as Python requires +Note that `SetData` does *not* implement `len()`, as Python requires `len()` to return a positive integer. Finite sets add iteration and support for `len()`. In addition, they @@ -520,7 +520,7 @@ class _SetDataBase(ComponentData): __slots__ = () -class _SetData(_SetDataBase): +class SetData(_SetDataBase): """The base for all Pyomo AML objects that can be used as a component indexing set. @@ -534,13 +534,13 @@ def __contains__(self, value): ans = self.get(value, _NotFound) except TypeError: # In Python 3.x, Sets are unhashable - if isinstance(value, _SetData): + if isinstance(value, SetData): ans = _NotFound else: raise if ans is _NotFound: - if isinstance(value, _SetData): + if isinstance(value, SetData): deprecation_warning( "Testing for set subsets with 'a in b' is deprecated. " "Use 'a.issubset(b)'.", @@ -1188,6 +1188,11 @@ def __gt__(self, other): return self >= other and not self == other +class _SetData(metaclass=RenamedClass): + __renamed__new_class__ = SetData + __renamed__version__ = '6.7.2.dev0' + + class _FiniteSetMixin(object): __slots__ = () @@ -1294,13 +1299,13 @@ def ranges(self): yield NonNumericRange(i) -class FiniteSetData(_FiniteSetMixin, _SetData): +class FiniteSetData(_FiniteSetMixin, SetData): """A general unordered iterable Set""" __slots__ = ('_values', '_domain', '_validate', '_filter', '_dimen') def __init__(self, component): - _SetData.__init__(self, component=component) + SetData.__init__(self, component=component) # Derived classes (like OrderedSetData) may want to change the # storage if not hasattr(self, '_values'): @@ -1986,7 +1991,7 @@ class SortedOrder(object): _UnorderedInitializers = {set} @overload - def __new__(cls: Type[Set], *args, **kwds) -> Union[_SetData, IndexedSet]: ... + def __new__(cls: Type[Set], *args, **kwds) -> Union[SetData, IndexedSet]: ... @overload def __new__(cls: Type[OrderedScalarSet], *args, **kwds) -> OrderedScalarSet: ... @@ -2193,7 +2198,7 @@ def _getitem_when_not_present(self, index): """Returns the default component data value.""" # Because we allow sets within an IndexedSet to have different # dimen, we have moved the tuplization logic from PyomoModel - # into Set (because we cannot know the dimen of a _SetData until + # into Set (because we cannot know the dimen of a SetData until # we are actually constructing that index). This also means # that we need to potentially communicate the dimen to the # (wrapped) value initializer. So, we will get the dimen first, @@ -2353,7 +2358,7 @@ def _pprint(self): # else: # return '{' + str(ans)[1:-1] + "}" - # TBD: In the current design, we force all _SetData within an + # TBD: In the current design, we force all SetData within an # indexed Set to have the same isordered value, so we will only # print it once in the header. Is this a good design? try: @@ -2398,7 +2403,7 @@ def data(self): return {k: v.data() for k, v in self.items()} @overload - def __getitem__(self, index) -> _SetData: ... + def __getitem__(self, index) -> SetData: ... __getitem__ = IndexedComponent.__getitem__ # type: ignore @@ -2479,14 +2484,14 @@ class AbstractSortedSimpleSet(metaclass=RenamedClass): ############################################################################ -class SetOf(_SetData, Component): +class SetOf(SetData, Component): """""" def __new__(cls, *args, **kwds): if cls is not SetOf: return super(SetOf, cls).__new__(cls) (reference,) = args - if isinstance(reference, (_SetData, GlobalSetBase)): + if isinstance(reference, (SetData, GlobalSetBase)): if reference.isfinite(): if reference.isordered(): return super(SetOf, cls).__new__(OrderedSetOf) @@ -2500,7 +2505,7 @@ def __new__(cls, *args, **kwds): return super(SetOf, cls).__new__(FiniteSetOf) def __init__(self, reference, **kwds): - _SetData.__init__(self, component=self) + SetData.__init__(self, component=self) kwds.setdefault('ctype', SetOf) Component.__init__(self, **kwds) self._ref = reference @@ -2523,7 +2528,7 @@ def construct(self, data=None): @property def dimen(self): - if isinstance(self._ref, _SetData): + if isinstance(self._ref, SetData): return self._ref.dimen _iter = iter(self) try: @@ -2618,7 +2623,7 @@ def ord(self, item): ############################################################################ -class InfiniteRangeSetData(_SetData): +class InfiniteRangeSetData(SetData): """Data class for a infinite set. This Set implements an interface to an *infinite set* defined by one @@ -2630,7 +2635,7 @@ class InfiniteRangeSetData(_SetData): __slots__ = ('_ranges',) def __init__(self, component): - _SetData.__init__(self, component=component) + SetData.__init__(self, component=component) self._ranges = None def get(self, value, default=None): @@ -3298,11 +3303,11 @@ class AbstractFiniteSimpleRangeSet(metaclass=RenamedClass): ############################################################################ -class SetOperator(_SetData, Set): +class SetOperator(SetData, Set): __slots__ = ('_sets',) def __init__(self, *args, **kwds): - _SetData.__init__(self, component=self) + SetData.__init__(self, component=self) Set.__init__(self, **kwds) self._sets, _anonymous = zip(*(process_setarg(_set) for _set in args)) _anonymous = tuple(filter(None, _anonymous)) @@ -4242,9 +4247,9 @@ def ord(self, item): ############################################################################ -class _AnySet(_SetData, Set): +class _AnySet(SetData, Set): def __init__(self, **kwds): - _SetData.__init__(self, component=self) + SetData.__init__(self, component=self) # There is a chicken-and-egg game here: the SetInitializer uses # Any as part of the processing of the domain/within/bounds # domain restrictions. However, Any has not been declared when @@ -4298,9 +4303,9 @@ def get(self, val, default=None): return super(_AnyWithNoneSet, self).get(val, default) -class _EmptySet(_FiniteSetMixin, _SetData, Set): +class _EmptySet(_FiniteSetMixin, SetData, Set): def __init__(self, **kwds): - _SetData.__init__(self, component=self) + SetData.__init__(self, component=self) Set.__init__(self, **kwds) self.construct() diff --git a/pyomo/core/base/sets.py b/pyomo/core/base/sets.py index ca693cf7d8b..3ebdc6875d1 100644 --- a/pyomo/core/base/sets.py +++ b/pyomo/core/base/sets.py @@ -17,8 +17,8 @@ process_setarg, set_options, simple_set_rule, - _SetDataBase, - _SetData, + SetDataBase, + SetData, Set, SetOf, IndexedSet, diff --git a/pyomo/core/tests/unit/test_param.py b/pyomo/core/tests/unit/test_param.py index b39272879f6..f22674b6bf7 100644 --- a/pyomo/core/tests/unit/test_param.py +++ b/pyomo/core/tests/unit/test_param.py @@ -66,7 +66,7 @@ from pyomo.common.log import LoggingIntercept from pyomo.common.tempfiles import TempfileManager from pyomo.core.base.param import ParamData -from pyomo.core.base.set import _SetData +from pyomo.core.base.set import SetData from pyomo.core.base.units_container import units, pint_available, UnitsError from io import StringIO @@ -1487,7 +1487,7 @@ def test_domain_set_initializer(self): m.I = Set(initialize=[1, 2, 3]) param_vals = {1: 1, 2: 1, 3: -1} m.p = Param(m.I, initialize=param_vals, domain={-1, 1}) - self.assertIsInstance(m.p.domain, _SetData) + self.assertIsInstance(m.p.domain, SetData) @unittest.skipUnless(pint_available, "units test requires pint module") def test_set_value_units(self): diff --git a/pyomo/core/tests/unit/test_set.py b/pyomo/core/tests/unit/test_set.py index 38870d5213e..abd5a03c755 100644 --- a/pyomo/core/tests/unit/test_set.py +++ b/pyomo/core/tests/unit/test_set.py @@ -81,7 +81,7 @@ SetProduct_InfiniteSet, SetProduct_FiniteSet, SetProduct_OrderedSet, - _SetData, + SetData, FiniteSetData, InsertionOrderSetData, _SortedSetData, @@ -4300,7 +4300,7 @@ def _l_tri(model, i, j): # This tests a filter that matches the dimentionality of the # component. construct() needs to recognize that the filter is # returning a constant in construct() and re-assign it to be the - # _filter for each _SetData + # _filter for each SetData def _lt_3(model, i): self.assertIs(model, m) return i < 3 @@ -5297,15 +5297,15 @@ def test_no_normalize_index(self): class TestAbstractSetAPI(unittest.TestCase): - def test_SetData(self): + def testSetData(self): # This tests an anstract non-finite set API m = ConcreteModel() m.I = Set(initialize=[1]) - s = _SetData(m.I) + s = SetData(m.I) # - # _SetData API + # SetData API # with self.assertRaises(DeveloperError): @@ -5395,7 +5395,7 @@ def test_SetData(self): def test_FiniteMixin(self): # This tests an anstract finite set API - class FiniteMixin(_FiniteSetMixin, _SetData): + class FiniteMixin(_FiniteSetMixin, SetData): pass m = ConcreteModel() @@ -5403,7 +5403,7 @@ class FiniteMixin(_FiniteSetMixin, _SetData): s = FiniteMixin(m.I) # - # _SetData API + # SetData API # with self.assertRaises(DeveloperError): @@ -5520,7 +5520,7 @@ class FiniteMixin(_FiniteSetMixin, _SetData): def test_OrderedMixin(self): # This tests an anstract ordered set API - class OrderedMixin(_OrderedSetMixin, _FiniteSetMixin, _SetData): + class OrderedMixin(_OrderedSetMixin, _FiniteSetMixin, SetData): pass m = ConcreteModel() @@ -5528,7 +5528,7 @@ class OrderedMixin(_OrderedSetMixin, _FiniteSetMixin, _SetData): s = OrderedMixin(m.I) # - # _SetData API + # SetData API # with self.assertRaises(DeveloperError): From 43c7c40b032a5d3ef8512d8a9d54969d52f9e45a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:56:14 -0600 Subject: [PATCH 1464/1797] Renamed _SortedSetData -> SortedSetData --- pyomo/core/base/set.py | 27 ++++++++++++++++----------- pyomo/core/tests/unit/test_set.py | 8 ++++---- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index 3280f512e83..d94cc86cf7c 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -1649,7 +1649,7 @@ class OrderedSetData(_OrderedSetMixin, FiniteSetData): implements a set ordered by insertion order, we make the "official" InsertionOrderSetData an empty derivative class, so that - issubclass(_SortedSetData, InsertionOrderSetData) == False + issubclass(SortedSetData, InsertionOrderSetData) == False Constructor Arguments: component The Set object that owns this data. @@ -1796,7 +1796,7 @@ def sorted_iter(self): return iter(self) -class _SortedSetData(_SortedSetMixin, OrderedSetData): +class SortedSetData(_SortedSetMixin, OrderedSetData): """ This class defines the data for a sorted set. @@ -1819,12 +1819,12 @@ def _iter_impl(self): """ if not self._is_sorted: self._sort() - return super(_SortedSetData, self)._iter_impl() + return super(SortedSetData, self)._iter_impl() def __reversed__(self): if not self._is_sorted: self._sort() - return super(_SortedSetData, self).__reversed__() + return super(SortedSetData, self).__reversed__() def _add_impl(self, value): # Note that the sorted status has no bearing on insertion, @@ -1838,7 +1838,7 @@ def _add_impl(self, value): # def discard(self, val): def clear(self): - super(_SortedSetData, self).clear() + super(SortedSetData, self).clear() self._is_sorted = True def at(self, index): @@ -1850,7 +1850,7 @@ def at(self, index): """ if not self._is_sorted: self._sort() - return super(_SortedSetData, self).at(index) + return super(SortedSetData, self).at(index) def ord(self, item): """ @@ -1862,7 +1862,7 @@ def ord(self, item): """ if not self._is_sorted: self._sort() - return super(_SortedSetData, self).ord(item) + return super(SortedSetData, self).ord(item) def sorted_data(self): return self.data() @@ -1875,6 +1875,11 @@ def _sort(self): self._is_sorted = True +class _SortedSetData(metaclass=RenamedClass): + __renamed__new_class__ = SortedSetData + __renamed__version__ = '6.7.2.dev0' + + ############################################################################ _SET_API = (('__contains__', 'test membership in'), 'get', 'ranges', 'bounds') @@ -2005,7 +2010,7 @@ def __new__(cls, *args, **kwds): # Many things are easier by forcing it to be consistent across # the set (namely, the _ComponentDataClass is constant). # However, it is a bit off that 'ordered' it the only arg NOT - # processed by Initializer. We can mock up a _SortedSetData + # processed by Initializer. We can mock up a SortedSetData # sort function that preserves Insertion Order (lambda x: x), but # the unsorted is harder (it would effectively be insertion # order, but ordered() may not be deterministic based on how the @@ -2052,7 +2057,7 @@ def __new__(cls, *args, **kwds): if ordered is Set.InsertionOrder: newObj._ComponentDataClass = InsertionOrderSetData elif ordered is Set.SortedOrder: - newObj._ComponentDataClass = _SortedSetData + newObj._ComponentDataClass = SortedSetData else: newObj._ComponentDataClass = FiniteSetData return newObj @@ -2435,13 +2440,13 @@ class OrderedSimpleSet(metaclass=RenamedClass): __renamed__version__ = '6.0' -class SortedScalarSet(_ScalarOrderedSetMixin, _SortedSetData, Set): +class SortedScalarSet(_ScalarOrderedSetMixin, SortedSetData, Set): def __init__(self, **kwds): # In case someone inherits from us, we will provide a rational # default for the "ordered" flag kwds.setdefault('ordered', Set.SortedOrder) - _SortedSetData.__init__(self, component=self) + SortedSetData.__init__(self, component=self) Set.__init__(self, **kwds) self._index = UnindexedComponent_index diff --git a/pyomo/core/tests/unit/test_set.py b/pyomo/core/tests/unit/test_set.py index abd5a03c755..f62589a6873 100644 --- a/pyomo/core/tests/unit/test_set.py +++ b/pyomo/core/tests/unit/test_set.py @@ -84,7 +84,7 @@ SetData, FiniteSetData, InsertionOrderSetData, - _SortedSetData, + SortedSetData, _FiniteSetMixin, _OrderedSetMixin, SetInitializer, @@ -4173,9 +4173,9 @@ def test_indexed_set(self): self.assertTrue(m.I[1].isordered()) self.assertTrue(m.I[2].isordered()) self.assertTrue(m.I[3].isordered()) - self.assertIs(type(m.I[1]), _SortedSetData) - self.assertIs(type(m.I[2]), _SortedSetData) - self.assertIs(type(m.I[3]), _SortedSetData) + self.assertIs(type(m.I[1]), SortedSetData) + self.assertIs(type(m.I[2]), SortedSetData) + self.assertIs(type(m.I[3]), SortedSetData) self.assertEqual(m.I.data(), {1: (2, 4, 5), 2: (2, 4, 5), 3: (2, 4, 5)}) # Explicit (procedural) construction From 63152a0be76006ff10e5f82f1b42de8af0ed6d56 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:57:39 -0600 Subject: [PATCH 1465/1797] Renamed _SOSConstraintData -> SOSConstraintData --- pyomo/contrib/appsi/base.py | 12 ++++++------ pyomo/contrib/appsi/fbbt.py | 6 +++--- pyomo/contrib/appsi/solvers/gurobi.py | 8 ++++---- pyomo/contrib/appsi/solvers/highs.py | 6 +++--- pyomo/contrib/appsi/writers/lp_writer.py | 6 +++--- pyomo/contrib/appsi/writers/nl_writer.py | 6 +++--- pyomo/contrib/solver/gurobi.py | 8 ++++---- pyomo/contrib/solver/persistent.py | 12 ++++++------ pyomo/core/base/sos.py | 15 ++++++++++----- pyomo/repn/plugins/cpxlp.py | 2 +- .../solvers/plugins/solvers/gurobi_persistent.py | 2 +- 11 files changed, 44 insertions(+), 39 deletions(-) diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index e50d5201090..409c8e2596c 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -22,7 +22,7 @@ MutableMapping, ) from pyomo.core.base.constraint import GeneralConstraintData, Constraint -from pyomo.core.base.sos import _SOSConstraintData, SOSConstraint +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 @@ -1034,10 +1034,10 @@ def add_constraints(self, cons: List[GeneralConstraintData]): v.fix() @abc.abstractmethod - def _add_sos_constraints(self, cons: List[_SOSConstraintData]): + def _add_sos_constraints(self, cons: List[SOSConstraintData]): pass - def add_sos_constraints(self, cons: List[_SOSConstraintData]): + def add_sos_constraints(self, cons: List[SOSConstraintData]): for con in cons: if con in self._vars_referenced_by_con: raise ValueError( @@ -1154,10 +1154,10 @@ def remove_constraints(self, cons: List[GeneralConstraintData]): del self._vars_referenced_by_con[con] @abc.abstractmethod - def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): + def _remove_sos_constraints(self, cons: List[SOSConstraintData]): pass - def remove_sos_constraints(self, cons: List[_SOSConstraintData]): + 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: @@ -1339,7 +1339,7 @@ def update(self, timer: HierarchicalTimer = None): old_cons.append(c) else: assert (c.ctype is SOSConstraint) or ( - c.ctype is None and isinstance(c, _SOSConstraintData) + c.ctype is None and isinstance(c, SOSConstraintData) ) old_sos.append(c) self.remove_constraints(old_cons) diff --git a/pyomo/contrib/appsi/fbbt.py b/pyomo/contrib/appsi/fbbt.py index 7735318f8ba..122ca5f7ffd 100644 --- a/pyomo/contrib/appsi/fbbt.py +++ b/pyomo/contrib/appsi/fbbt.py @@ -21,7 +21,7 @@ from pyomo.core.base.var import GeneralVarData 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.sos import SOSConstraintData from pyomo.core.base.objective import GeneralObjectiveData, minimize, maximize from pyomo.core.base.block import BlockData from pyomo.core.base import SymbolMap, TextLabeler @@ -169,7 +169,7 @@ def _add_constraints(self, cons: List[GeneralConstraintData]): for c, cc in self._con_map.items(): cc.name = self._symbol_map.getSymbol(c, self._con_labeler) - def _add_sos_constraints(self, cons: List[_SOSConstraintData]): + def _add_sos_constraints(self, cons: List[SOSConstraintData]): if len(cons) != 0: raise NotImplementedError( 'IntervalTightener does not support SOS constraints' @@ -184,7 +184,7 @@ def _remove_constraints(self, cons: List[GeneralConstraintData]): self._cmodel.remove_constraint(cc) del self._rcon_map[cc] - def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): + def _remove_sos_constraints(self, cons: List[SOSConstraintData]): if len(cons) != 0: raise NotImplementedError( 'IntervalTightener does not support SOS constraints' diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index 4392cdf0839..8606d44cd46 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -25,7 +25,7 @@ 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.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 @@ -709,7 +709,7 @@ def _add_constraints(self, cons: List[GeneralConstraintData]): self._constraints_added_since_update.update(cons) self._needs_updated = True - def _add_sos_constraints(self, cons: List[_SOSConstraintData]): + def _add_sos_constraints(self, cons: List[SOSConstraintData]): for con in cons: conname = self._symbol_map.getSymbol(con, self._labeler) level = con.level @@ -749,7 +749,7 @@ def _remove_constraints(self, cons: List[GeneralConstraintData]): self._mutable_quadratic_helpers.pop(con, None) self._needs_updated = True - def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): + def _remove_sos_constraints(self, cons: List[SOSConstraintData]): for con in cons: if con in self._constraints_added_since_update: self._update_gurobi_model() @@ -1288,7 +1288,7 @@ def get_sos_attr(self, con, attr): Parameters ---------- - con: pyomo.core.base.sos._SOSConstraintData + con: pyomo.core.base.sos.SOSConstraintData The pyomo SOS constraint for which the corresponding gurobi SOS constraint attribute should be retrieved. attr: str diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index a6b7c102c91..5af7b297684 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -22,7 +22,7 @@ from pyomo.core.base import SymbolMap 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.sos import SOSConstraintData from pyomo.core.base.param import ParamData from pyomo.core.expr.numvalue import value, is_constant from pyomo.repn import generate_standard_repn @@ -456,7 +456,7 @@ def _add_constraints(self, cons: List[GeneralConstraintData]): np.array(coef_values, dtype=np.double), ) - def _add_sos_constraints(self, cons: List[_SOSConstraintData]): + def _add_sos_constraints(self, cons: List[SOSConstraintData]): if cons: raise NotImplementedError( 'Highs interface does not support SOS constraints' @@ -487,7 +487,7 @@ def _remove_constraints(self, cons: List[GeneralConstraintData]): {v: k for k, v in self._pyomo_con_to_solver_con_map.items()} ) - def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): + def _remove_sos_constraints(self, cons: List[SOSConstraintData]): if cons: raise NotImplementedError( 'Highs interface does not support SOS constraints' diff --git a/pyomo/contrib/appsi/writers/lp_writer.py b/pyomo/contrib/appsi/writers/lp_writer.py index 3a168cdcd91..3a6682d5c00 100644 --- a/pyomo/contrib/appsi/writers/lp_writer.py +++ b/pyomo/contrib/appsi/writers/lp_writer.py @@ -14,7 +14,7 @@ from pyomo.core.base.var import GeneralVarData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.objective import GeneralObjectiveData -from pyomo.core.base.sos import _SOSConstraintData +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 @@ -102,7 +102,7 @@ def _add_params(self, params: List[ParamData]): def _add_constraints(self, cons: List[GeneralConstraintData]): cmodel.process_lp_constraints(cons, self) - def _add_sos_constraints(self, cons: List[_SOSConstraintData]): + def _add_sos_constraints(self, cons: List[SOSConstraintData]): if len(cons) != 0: raise NotImplementedError('LP writer does not yet support SOS constraints') @@ -113,7 +113,7 @@ def _remove_constraints(self, cons: List[GeneralConstraintData]): self._symbol_map.removeSymbol(c) del self._solver_con_to_pyomo_con_map[cc] - def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): + def _remove_sos_constraints(self, cons: List[SOSConstraintData]): if len(cons) != 0: raise NotImplementedError('LP writer does not yet support SOS constraints') diff --git a/pyomo/contrib/appsi/writers/nl_writer.py b/pyomo/contrib/appsi/writers/nl_writer.py index fced3c5ae10..c46cb1c0723 100644 --- a/pyomo/contrib/appsi/writers/nl_writer.py +++ b/pyomo/contrib/appsi/writers/nl_writer.py @@ -14,7 +14,7 @@ from pyomo.core.base.var import GeneralVarData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.objective import GeneralObjectiveData -from pyomo.core.base.sos import _SOSConstraintData +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 @@ -126,7 +126,7 @@ def _add_constraints(self, cons: List[GeneralConstraintData]): for c, cc in self._pyomo_con_to_solver_con_map.items(): cc.name = self._symbol_map.getSymbol(c, self._con_labeler) - def _add_sos_constraints(self, cons: List[_SOSConstraintData]): + def _add_sos_constraints(self, cons: List[SOSConstraintData]): if len(cons) != 0: raise NotImplementedError('NL writer does not support SOS constraints') @@ -140,7 +140,7 @@ def _remove_constraints(self, cons: List[GeneralConstraintData]): self._writer.remove_constraint(cc) del self._solver_con_to_pyomo_con_map[cc] - def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): + def _remove_sos_constraints(self, cons: List[SOSConstraintData]): if len(cons) != 0: raise NotImplementedError('NL writer does not support SOS constraints') diff --git a/pyomo/contrib/solver/gurobi.py b/pyomo/contrib/solver/gurobi.py index 107de15e625..c5a1c8c1cd8 100644 --- a/pyomo/contrib/solver/gurobi.py +++ b/pyomo/contrib/solver/gurobi.py @@ -24,7 +24,7 @@ from pyomo.core.base import SymbolMap, NumericLabeler, TextLabeler 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.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 @@ -685,7 +685,7 @@ def _add_constraints(self, cons: List[GeneralConstraintData]): self._constraints_added_since_update.update(cons) self._needs_updated = True - def _add_sos_constraints(self, cons: List[_SOSConstraintData]): + def _add_sos_constraints(self, cons: List[SOSConstraintData]): for con in cons: conname = self._symbol_map.getSymbol(con, self._labeler) level = con.level @@ -725,7 +725,7 @@ def _remove_constraints(self, cons: List[GeneralConstraintData]): self._mutable_quadratic_helpers.pop(con, None) self._needs_updated = True - def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): + def _remove_sos_constraints(self, cons: List[SOSConstraintData]): for con in cons: if con in self._constraints_added_since_update: self._update_gurobi_model() @@ -1218,7 +1218,7 @@ def get_sos_attr(self, con, attr): Parameters ---------- - con: pyomo.core.base.sos._SOSConstraintData + con: pyomo.core.base.sos.SOSConstraintData The pyomo SOS constraint for which the corresponding gurobi SOS constraint attribute should be retrieved. attr: str diff --git a/pyomo/contrib/solver/persistent.py b/pyomo/contrib/solver/persistent.py index 558b8cbf314..e98d76b4841 100644 --- a/pyomo/contrib/solver/persistent.py +++ b/pyomo/contrib/solver/persistent.py @@ -13,7 +13,7 @@ from typing import List from pyomo.core.base.constraint import GeneralConstraintData, Constraint -from pyomo.core.base.sos import _SOSConstraintData, SOSConstraint +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 @@ -130,10 +130,10 @@ def add_constraints(self, cons: List[GeneralConstraintData]): v.fix() @abc.abstractmethod - def _add_sos_constraints(self, cons: List[_SOSConstraintData]): + def _add_sos_constraints(self, cons: List[SOSConstraintData]): pass - def add_sos_constraints(self, cons: List[_SOSConstraintData]): + def add_sos_constraints(self, cons: List[SOSConstraintData]): for con in cons: if con in self._vars_referenced_by_con: raise ValueError( @@ -230,10 +230,10 @@ def remove_constraints(self, cons: List[GeneralConstraintData]): del self._vars_referenced_by_con[con] @abc.abstractmethod - def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): + def _remove_sos_constraints(self, cons: List[SOSConstraintData]): pass - def remove_sos_constraints(self, cons: List[_SOSConstraintData]): + 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: @@ -389,7 +389,7 @@ def update(self, timer: HierarchicalTimer = None): old_cons.append(c) else: assert (c.ctype is SOSConstraint) or ( - c.ctype is None and isinstance(c, _SOSConstraintData) + c.ctype is None and isinstance(c, SOSConstraintData) ) old_sos.append(c) self.remove_constraints(old_cons) diff --git a/pyomo/core/base/sos.py b/pyomo/core/base/sos.py index 6b8586c9b49..4a8afb05d71 100644 --- a/pyomo/core/base/sos.py +++ b/pyomo/core/base/sos.py @@ -28,7 +28,7 @@ logger = logging.getLogger('pyomo.core') -class _SOSConstraintData(ActiveComponentData): +class SOSConstraintData(ActiveComponentData): """ This class defines the data for a single special ordered set. @@ -101,6 +101,11 @@ def set_items(self, variables, weights): self._weights.append(w) +class _SOSConstraintData(metaclass=RenamedClass): + __renamed__new_class__ = SOSConstraintData + __renamed__version__ = '6.7.2.dev0' + + @ModelComponentFactory.register("SOS constraint expressions.") class SOSConstraint(ActiveIndexedComponent): """ @@ -512,10 +517,10 @@ def add(self, index, variables, weights=None): Add a component data for the specified index. """ if index is None: - # because ScalarSOSConstraint already makes an _SOSConstraintData instance + # because ScalarSOSConstraint already makes an SOSConstraintData instance soscondata = self else: - soscondata = _SOSConstraintData(self) + soscondata = SOSConstraintData(self) self._data[index] = soscondata soscondata._index = index @@ -549,9 +554,9 @@ def pprint(self, ostream=None, verbose=False, prefix=""): ostream.write("\t\t" + str(weight) + ' : ' + var.name + '\n') -class ScalarSOSConstraint(SOSConstraint, _SOSConstraintData): +class ScalarSOSConstraint(SOSConstraint, SOSConstraintData): def __init__(self, *args, **kwd): - _SOSConstraintData.__init__(self, self) + SOSConstraintData.__init__(self, self) SOSConstraint.__init__(self, *args, **kwd) self._index = UnindexedComponent_index diff --git a/pyomo/repn/plugins/cpxlp.py b/pyomo/repn/plugins/cpxlp.py index 46e6b6d5265..6228e7c7286 100644 --- a/pyomo/repn/plugins/cpxlp.py +++ b/pyomo/repn/plugins/cpxlp.py @@ -374,7 +374,7 @@ def _print_expr_canonical( def printSOS(self, symbol_map, labeler, variable_symbol_map, soscondata, output): """ - Prints the SOS constraint associated with the _SOSConstraintData object + Prints the SOS constraint associated with the SOSConstraintData object """ sos_template_string = self.sos_template_string diff --git a/pyomo/solvers/plugins/solvers/gurobi_persistent.py b/pyomo/solvers/plugins/solvers/gurobi_persistent.py index 8a81aad3d3e..585a78e3ef1 100644 --- a/pyomo/solvers/plugins/solvers/gurobi_persistent.py +++ b/pyomo/solvers/plugins/solvers/gurobi_persistent.py @@ -413,7 +413,7 @@ def get_sos_attr(self, con, attr): Parameters ---------- - con: pyomo.core.base.sos._SOSConstraintData + con: pyomo.core.base.sos.SOSConstraintData The pyomo SOS constraint for which the corresponding gurobi SOS constraint attribute should be retrieved. attr: str From 430f98207353b1e1e7383504aa0336bc852aeb9c Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 17:57:41 -0600 Subject: [PATCH 1466/1797] Renamed _SuffixData -> SuffixData --- pyomo/core/base/suffix.py | 4 ++-- pyomo/repn/plugins/nl_writer.py | 15 ++++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index be2f732650d..c4b37789773 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -129,7 +129,7 @@ class SuffixDirection(enum.IntEnum): IMPORT_EXPORT = 3 -_SuffixDataTypeDomain = In(SuffixDataType) +SuffixDataTypeDomain = In(SuffixDataType) _SuffixDirectionDomain = In(SuffixDirection) @@ -253,7 +253,7 @@ def datatype(self): def datatype(self, datatype): """Set the suffix datatype.""" if datatype is not None: - datatype = _SuffixDataTypeDomain(datatype) + datatype = SuffixDataTypeDomain(datatype) self._datatype = datatype @property diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index c010cee5e54..1a49238f35b 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -423,7 +423,7 @@ def _generate_symbol_map(self, info): return symbol_map -class _SuffixData(object): +class SuffixData(object): def __init__(self, name): self.name = name self.obj = {} @@ -505,6 +505,11 @@ def compile(self, column_order, row_order, obj_order, model_id): ) +class _SuffixData(metaclass=RenamedClass): + __renamed__new_class__ = SuffixData + __renamed__version__ = '6.7.2.dev0' + + class CachingNumericSuffixFinder(SuffixFinder): scale = True @@ -637,7 +642,7 @@ def write(self, model): continue name = suffix.local_name if name not in suffix_data: - suffix_data[name] = _SuffixData(name) + suffix_data[name] = SuffixData(name) suffix_data[name].update(suffix) # # Data structures to support variable/constraint scaling @@ -994,7 +999,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) + suffix_data[name] = SuffixData(name) suffix_data[name].datatype.add(Suffix.INT) sos_id = 0 sosno = suffix_data['sosno'] @@ -1344,7 +1349,7 @@ def write(self, model): if not _vals: continue ostream.write(f"S{_field|_float} {len(_vals)} {name}\n") - # Note: _SuffixData.compile() guarantees 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)) ) @@ -1454,7 +1459,7 @@ def write(self, model): logger.warning("ignoring 'dual' suffix for Model") if data.con: ostream.write(f"d{len(data.con)}\n") - # Note: _SuffixData.compile() guarantees 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)) ) From f810600f0520097052fb4bf483920b86d03c80fb Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 18:02:15 -0600 Subject: [PATCH 1467/1797] Renamed _VarData -> VarData --- pyomo/common/tests/test_timing.py | 4 +-- .../fme/fourier_motzkin_elimination.py | 4 +-- pyomo/contrib/gdp_bounds/info.py | 2 +- .../algorithms/solvers/pyomo_ext_cyipopt.py | 10 +++---- pyomo/contrib/pyros/config.py | 8 ++--- .../contrib/pyros/pyros_algorithm_methods.py | 2 +- pyomo/contrib/pyros/tests/test_config.py | 16 +++++----- pyomo/contrib/pyros/tests/test_grcs.py | 4 +-- pyomo/contrib/pyros/util.py | 6 ++-- pyomo/core/base/__init__.py | 2 +- pyomo/core/base/boolean_var.py | 4 +-- pyomo/core/base/piecewise.py | 16 +++++----- pyomo/core/base/var.py | 29 +++++++++++-------- pyomo/core/beta/dict_objects.py | 4 +-- pyomo/core/beta/list_objects.py | 4 +-- pyomo/core/expr/numeric_expr.py | 2 +- .../plugins/transform/eliminate_fixed_vars.py | 4 +-- .../plugins/transform/radix_linearization.py | 6 ++-- pyomo/core/tests/unit/test_piecewise.py | 2 +- pyomo/core/tests/unit/test_var_set_bounds.py | 2 +- pyomo/dae/misc.py | 2 +- pyomo/repn/plugins/ampl/ampl_.py | 10 +++---- pyomo/repn/plugins/cpxlp.py | 2 +- pyomo/repn/plugins/mps.py | 2 +- pyomo/repn/plugins/nl_writer.py | 10 +++---- pyomo/repn/plugins/standard_form.py | 6 ++-- .../plugins/solvers/cplex_persistent.py | 4 +-- .../plugins/solvers/gurobi_persistent.py | 4 +-- .../plugins/solvers/mosek_persistent.py | 4 +-- .../plugins/solvers/persistent_solver.py | 4 +-- .../plugins/solvers/xpress_persistent.py | 4 +-- pyomo/util/calc_var_value.py | 2 +- 32 files changed, 95 insertions(+), 90 deletions(-) diff --git a/pyomo/common/tests/test_timing.py b/pyomo/common/tests/test_timing.py index 48288746882..90f4cdcd034 100644 --- a/pyomo/common/tests/test_timing.py +++ b/pyomo/common/tests/test_timing.py @@ -35,7 +35,7 @@ Any, TransformationFactory, ) -from pyomo.core.base.var import _VarData +from pyomo.core.base.var import VarData class _pseudo_component(Var): @@ -62,7 +62,7 @@ def test_raw_construction_timer(self): ) v = Var() v.construct() - a = ConstructionTimer(_VarData(v)) + a = ConstructionTimer(VarData(v)) self.assertRegex( str(a), r"ConstructionTimer object for Var ScalarVar\[NOTSET\]; " diff --git a/pyomo/contrib/fme/fourier_motzkin_elimination.py b/pyomo/contrib/fme/fourier_motzkin_elimination.py index a1b5d744cf4..4636450c58e 100644 --- a/pyomo/contrib/fme/fourier_motzkin_elimination.py +++ b/pyomo/contrib/fme/fourier_motzkin_elimination.py @@ -23,7 +23,7 @@ value, ConstraintList, ) -from pyomo.core.base import TransformationFactory, _VarData +from pyomo.core.base import TransformationFactory, VarData from pyomo.core.plugins.transform.hierarchy import Transformation from pyomo.common.config import ConfigBlock, ConfigValue, NonNegativeFloat from pyomo.common.modeling import unique_component_name @@ -58,7 +58,7 @@ def _check_var_bounds_filter(constraint): def vars_to_eliminate_list(x): - if isinstance(x, (Var, _VarData)): + if isinstance(x, (Var, VarData)): if not x.is_indexed(): return ComponentSet([x]) ans = ComponentSet() diff --git a/pyomo/contrib/gdp_bounds/info.py b/pyomo/contrib/gdp_bounds/info.py index db3f6d0846d..e65df2bfab0 100644 --- a/pyomo/contrib/gdp_bounds/info.py +++ b/pyomo/contrib/gdp_bounds/info.py @@ -35,7 +35,7 @@ def disjunctive_bound(var, scope): """Compute the disjunctive bounds for a variable in a given scope. Args: - var (_VarData): Variable for which to compute bound + var (VarData): Variable for which to compute bound scope (Component): The scope in which to compute the bound. If not a DisjunctData, it will walk up the tree and use the scope of the most immediate enclosing DisjunctData. diff --git a/pyomo/contrib/pynumero/algorithms/solvers/pyomo_ext_cyipopt.py b/pyomo/contrib/pynumero/algorithms/solvers/pyomo_ext_cyipopt.py index 16c5a19a5c6..7f43f6ac7c0 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/pyomo_ext_cyipopt.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/pyomo_ext_cyipopt.py @@ -16,7 +16,7 @@ from pyomo.contrib.pynumero.interfaces.pyomo_nlp import PyomoNLP from pyomo.contrib.pynumero.sparse.block_vector import BlockVector from pyomo.environ import Var, Constraint, value -from pyomo.core.base.var import _VarData +from pyomo.core.base.var import VarData from pyomo.common.modeling import unique_component_name """ @@ -109,12 +109,12 @@ def __init__( An instance of a derived class (from ExternalInputOutputModel) that provides the methods to compute the outputs and the derivatives. - inputs : list of Pyomo variables (_VarData) + inputs : list of Pyomo variables (VarData) The Pyomo model needs to have variables to represent the inputs to the external model. This is the list of those input variables in the order that corresponds to the input_values vector provided in the set_inputs call. - outputs : list of Pyomo variables (_VarData) + outputs : list of Pyomo variables (VarData) The Pyomo model needs to have variables to represent the outputs from the external model. This is the list of those output variables in the order that corresponds to the numpy array returned from the evaluate_outputs call. @@ -130,7 +130,7 @@ def __init__( # verify that the inputs and outputs were passed correctly self._inputs = [v for v in inputs] for v in self._inputs: - if not isinstance(v, _VarData): + if not isinstance(v, VarData): raise RuntimeError( 'Argument inputs passed to PyomoExternalCyIpoptProblem must be' ' a list of VarData objects. Note: if you have an indexed variable, pass' @@ -139,7 +139,7 @@ def __init__( self._outputs = [v for v in outputs] for v in self._outputs: - if not isinstance(v, _VarData): + if not isinstance(v, VarData): raise RuntimeError( 'Argument outputs passed to PyomoExternalCyIpoptProblem must be' ' a list of VarData objects. Note: if you have an indexed variable, pass' diff --git a/pyomo/contrib/pyros/config.py b/pyomo/contrib/pyros/config.py index e60b474d037..59bf9a9ab37 100644 --- a/pyomo/contrib/pyros/config.py +++ b/pyomo/contrib/pyros/config.py @@ -16,7 +16,7 @@ Path, ) from pyomo.common.errors import ApplicationError, PyomoException -from pyomo.core.base import Var, _VarData +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 ObjectiveType, setup_pyros_logger @@ -98,7 +98,7 @@ class InputDataStandardizer(object): Pyomo component type, such as Component, Var or Param. cdatatype : type Corresponding Pyomo component data type, such as - _ComponentData, _VarData, or ParamData. + _ComponentData, VarData, or ParamData. ctype_validator : callable, optional Validator function for objects of type `ctype`. cdatatype_validator : callable, optional @@ -511,7 +511,7 @@ def pyros_config(): "first_stage_variables", ConfigValue( default=[], - domain=InputDataStandardizer(Var, _VarData, allow_repeats=False), + domain=InputDataStandardizer(Var, VarData, allow_repeats=False), description="First-stage (or design) variables.", visibility=1, ), @@ -520,7 +520,7 @@ def pyros_config(): "second_stage_variables", ConfigValue( default=[], - domain=InputDataStandardizer(Var, _VarData, allow_repeats=False), + domain=InputDataStandardizer(Var, VarData, allow_repeats=False), description="Second-stage (or control) variables.", visibility=1, ), diff --git a/pyomo/contrib/pyros/pyros_algorithm_methods.py b/pyomo/contrib/pyros/pyros_algorithm_methods.py index 5987db074e6..cfb57b08c7f 100644 --- a/pyomo/contrib/pyros/pyros_algorithm_methods.py +++ b/pyomo/contrib/pyros/pyros_algorithm_methods.py @@ -28,7 +28,7 @@ from pyomo.core.base import value from pyomo.core.expr import MonomialTermExpression from pyomo.common.collections import ComponentSet, ComponentMap -from pyomo.core.base.var import _VarData as VarData +from pyomo.core.base.var import VarData as VarData from itertools import chain from pyomo.common.dependencies import numpy as np diff --git a/pyomo/contrib/pyros/tests/test_config.py b/pyomo/contrib/pyros/tests/test_config.py index cd635e795fc..166fbada4ff 100644 --- a/pyomo/contrib/pyros/tests/test_config.py +++ b/pyomo/contrib/pyros/tests/test_config.py @@ -5,7 +5,7 @@ import logging import unittest -from pyomo.core.base import ConcreteModel, Var, _VarData +from pyomo.core.base import ConcreteModel, Var, VarData from pyomo.common.log import LoggingIntercept from pyomo.common.errors import ApplicationError from pyomo.core.base.param import Param, ParamData @@ -38,7 +38,7 @@ def test_single_component_data(self): mdl = ConcreteModel() mdl.v = Var([0, 1]) - standardizer_func = InputDataStandardizer(Var, _VarData) + standardizer_func = InputDataStandardizer(Var, VarData) standardizer_input = mdl.v[0] standardizer_output = standardizer_func(standardizer_input) @@ -74,7 +74,7 @@ def test_standardizer_indexed_component(self): mdl = ConcreteModel() mdl.v = Var([0, 1]) - standardizer_func = InputDataStandardizer(Var, _VarData) + standardizer_func = InputDataStandardizer(Var, VarData) standardizer_input = mdl.v standardizer_output = standardizer_func(standardizer_input) @@ -113,7 +113,7 @@ def test_standardizer_multiple_components(self): mdl.v = Var([0, 1]) mdl.x = Var(["a", "b"]) - standardizer_func = InputDataStandardizer(Var, _VarData) + standardizer_func = InputDataStandardizer(Var, VarData) standardizer_input = [mdl.v[0], mdl.x] standardizer_output = standardizer_func(standardizer_input) @@ -154,7 +154,7 @@ def test_standardizer_invalid_duplicates(self): mdl.v = Var([0, 1]) mdl.x = Var(["a", "b"]) - standardizer_func = InputDataStandardizer(Var, _VarData, allow_repeats=False) + standardizer_func = InputDataStandardizer(Var, VarData, allow_repeats=False) exc_str = r"Standardized.*list.*contains duplicate entries\." with self.assertRaisesRegex(ValueError, exc_str): @@ -165,7 +165,7 @@ def test_standardizer_invalid_type(self): Test standardizer raises exception as expected when input is of invalid type. """ - standardizer_func = InputDataStandardizer(Var, _VarData) + standardizer_func = InputDataStandardizer(Var, VarData) exc_str = r"Input object .*is not of valid component type.*" with self.assertRaisesRegex(TypeError, exc_str): @@ -178,7 +178,7 @@ def test_standardizer_iterable_with_invalid_type(self): """ mdl = ConcreteModel() mdl.v = Var([0, 1]) - standardizer_func = InputDataStandardizer(Var, _VarData) + 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): @@ -189,7 +189,7 @@ 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) + standardizer_func = InputDataStandardizer(Var, VarData) exc_str = r"Input object .*is not of valid component type.*" with self.assertRaisesRegex(TypeError, exc_str): diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index c308f0d6990..8093e93c8ef 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -19,7 +19,7 @@ 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.base.var import _VarData +from pyomo.core.base.var import VarData from pyomo.core.expr import ( identify_variables, identify_mutable_parameters, @@ -592,7 +592,7 @@ def test_dr_eqns_form_correct(self): param_product_multiplicand = term.args[0] dr_var_multiplicand = term.args[1] else: - self.assertIsInstance(term, _VarData) + self.assertIsInstance(term, VarData) param_product_multiplicand = 1 dr_var_multiplicand = term diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index a3ab3464aa8..65fb0a2c6aa 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -832,7 +832,7 @@ 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 + `VarData` objects participating in the active objective or constraints descended from `blk` which are not first-stage variables or second-stage variables. @@ -847,7 +847,7 @@ def get_state_vars(blk, first_stage_variables, second_stage_variables): Yields ------ - _VarData + VarData State variable. """ dof_var_set = ComponentSet(first_stage_variables) | ComponentSet( @@ -954,7 +954,7 @@ def validate_variable_partitioning(model, config): Returns ------- - list of _VarData + list of VarData State variables of the model. Raises diff --git a/pyomo/core/base/__init__.py b/pyomo/core/base/__init__.py index 408cf16c00e..0363380af1f 100644 --- a/pyomo/core/base/__init__.py +++ b/pyomo/core/base/__init__.py @@ -57,7 +57,7 @@ from pyomo.core.base.check import BuildCheck from pyomo.core.base.set import Set, SetOf, simple_set_rule, RangeSet from pyomo.core.base.param import Param -from pyomo.core.base.var import Var, _VarData, GeneralVarData, ScalarVar, VarList +from pyomo.core.base.var import Var, VarData, GeneralVarData, ScalarVar, VarList from pyomo.core.base.boolean_var import ( BooleanVar, BooleanVarData, diff --git a/pyomo/core/base/boolean_var.py b/pyomo/core/base/boolean_var.py index 287851a7f7e..925dca530a7 100644 --- a/pyomo/core/base/boolean_var.py +++ b/pyomo/core/base/boolean_var.py @@ -270,14 +270,14 @@ def stale(self, val): self._stale = StaleFlagManager.get_flag(0) def get_associated_binary(self): - """Get the binary _VarData associated with this + """Get the binary VarData associated with this GeneralBooleanVarData""" return ( self._associated_binary() if self._associated_binary is not None else None ) def associate_binary_var(self, binary_var): - """Associate a binary _VarData to this GeneralBooleanVarData""" + """Associate a binary VarData to this GeneralBooleanVarData""" if ( self._associated_binary is not None and type(self._associated_binary) diff --git a/pyomo/core/base/piecewise.py b/pyomo/core/base/piecewise.py index 43f8ddbfef5..f061ebfbdc8 100644 --- a/pyomo/core/base/piecewise.py +++ b/pyomo/core/base/piecewise.py @@ -47,7 +47,7 @@ from pyomo.core.base.component import ModelComponentFactory from pyomo.core.base.constraint import Constraint, ConstraintList from pyomo.core.base.sos import SOSConstraint -from pyomo.core.base.var import Var, _VarData, IndexedVar +from pyomo.core.base.var import Var, VarData, IndexedVar from pyomo.core.base.set_types import PositiveReals, NonNegativeReals, Binary from pyomo.core.base.util import flatten_tuple @@ -1240,7 +1240,7 @@ def __init__(self, *args, **kwds): # Check that the variables args are actually Pyomo Vars if not ( - isinstance(self._domain_var, _VarData) + isinstance(self._domain_var, VarData) or isinstance(self._domain_var, IndexedVar) ): msg = ( @@ -1249,7 +1249,7 @@ def __init__(self, *args, **kwds): ) raise TypeError(msg % (repr(self._domain_var),)) if not ( - isinstance(self._range_var, _VarData) + isinstance(self._range_var, VarData) or isinstance(self._range_var, IndexedVar) ): msg = ( @@ -1359,22 +1359,22 @@ def add(self, index, _is_indexed=None): _self_yvar = None _self_domain_pts_index = None if not _is_indexed: - # allows one to mix Var and _VarData as input to + # allows one to mix Var and VarData as input to # non-indexed Piecewise, index would be None in this case - # so for Var elements Var[None] is Var, but _VarData[None] would fail + # so for Var elements Var[None] is Var, but VarData[None] would fail _self_xvar = self._domain_var _self_yvar = self._range_var _self_domain_pts_index = self._domain_points[index] else: - # The following allows one to specify a Var or _VarData + # The following allows one to specify a Var or VarData # object even with an indexed Piecewise component. # The most common situation will most likely be a VarArray, # so we try this first. - if not isinstance(self._domain_var, _VarData): + if not isinstance(self._domain_var, VarData): _self_xvar = self._domain_var[index] else: _self_xvar = self._domain_var - if not isinstance(self._range_var, _VarData): + if not isinstance(self._range_var, VarData): _self_yvar = self._range_var[index] else: _self_yvar = self._range_var diff --git a/pyomo/core/base/var.py b/pyomo/core/base/var.py index 0e45ad44225..509238e4e6b 100644 --- a/pyomo/core/base/var.py +++ b/pyomo/core/base/var.py @@ -88,7 +88,7 @@ ) -class _VarData(ComponentData, NumericValue): +class VarData(ComponentData, NumericValue): """This class defines the abstract interface for a single variable. Note that this "abstract" class is not intended to be directly @@ -319,7 +319,12 @@ def free(self): return self.unfix() -class GeneralVarData(_VarData): +class _VarData(metaclass=RenamedClass): + __renamed__new_class__ = VarData + __renamed__version__ = '6.7.2.dev0' + + +class GeneralVarData(VarData): """This class defines the data for a single variable.""" __slots__ = ('_value', '_lb', '_ub', '_domain', '_fixed', '_stale') @@ -329,7 +334,7 @@ def __init__(self, component=None): # # These lines represent in-lining of the # following constructors: - # - _VarData + # - VarData # - ComponentData # - NumericValue self._component = weakref_ref(component) if (component is not None) else None @@ -448,9 +453,9 @@ def domain(self, domain): ) raise - @_VarData.bounds.getter + @VarData.bounds.getter def bounds(self): - # Custom implementation of _VarData.bounds to avoid unnecessary + # Custom implementation of VarData.bounds to avoid unnecessary # expression generation and duplicate calls to domain.bounds() domain_lb, domain_ub = self.domain.bounds() # lb is the tighter of the domain and bounds @@ -491,9 +496,9 @@ def bounds(self): ub = min(ub, domain_ub) return lb, ub - @_VarData.lb.getter + @VarData.lb.getter def lb(self): - # Custom implementation of _VarData.lb to avoid unnecessary + # Custom implementation of VarData.lb to avoid unnecessary # expression generation domain_lb, domain_ub = self.domain.bounds() # lb is the tighter of the domain and bounds @@ -516,9 +521,9 @@ def lb(self): lb = max(lb, domain_lb) return lb - @_VarData.ub.getter + @VarData.ub.getter def ub(self): - # Custom implementation of _VarData.ub to avoid unnecessary + # Custom implementation of VarData.ub to avoid unnecessary # expression generation domain_lb, domain_ub = self.domain.bounds() # ub is the tighter of the domain and bounds @@ -780,7 +785,7 @@ def add(self, index): def construct(self, data=None): """ - Construct the _VarData objects for this variable + Construct the VarData objects for this variable """ if self._constructed: return @@ -839,7 +844,7 @@ def construct(self, data=None): # initializers that are constant, we can avoid # re-calling (and re-validating) the inputs in certain # cases. To support this, we will create the first - # _VarData and then use it as a template to initialize + # VarData and then use it as a template to initialize # (constant portions of) every VarData so as to not # repeat all the domain/bounds validation. try: @@ -1008,7 +1013,7 @@ def fix(self, value=NOTSET, skip_validation=False): def unfix(self): """Unfix all variables in this :class:`IndexedVar` (treat as variable) - This sets the :attr:`_VarData.fixed` indicator to False for + This sets the :attr:`VarData.fixed` indicator to False for every variable in this :class:`IndexedVar`. """ diff --git a/pyomo/core/beta/dict_objects.py b/pyomo/core/beta/dict_objects.py index 7c44166f189..eedb3c45bf3 100644 --- a/pyomo/core/beta/dict_objects.py +++ b/pyomo/core/beta/dict_objects.py @@ -14,7 +14,7 @@ from pyomo.common.log import is_debug_set from pyomo.core.base.set_types import Any -from pyomo.core.base.var import IndexedVar, _VarData +from pyomo.core.base.var import IndexedVar, VarData from pyomo.core.base.constraint import IndexedConstraint, ConstraintData from pyomo.core.base.objective import IndexedObjective, ObjectiveData from pyomo.core.base.expression import IndexedExpression, ExpressionData @@ -184,7 +184,7 @@ def __init__(self, *args, **kwds): # Constructor for ComponentDict needs to # go last in order to handle any initialization # iterable as an argument - ComponentDict.__init__(self, _VarData, *args, **kwds) + ComponentDict.__init__(self, VarData, *args, **kwds) class ConstraintDict(ComponentDict, IndexedConstraint): diff --git a/pyomo/core/beta/list_objects.py b/pyomo/core/beta/list_objects.py index d10a30e18e2..005bfc38a1f 100644 --- a/pyomo/core/beta/list_objects.py +++ b/pyomo/core/beta/list_objects.py @@ -14,7 +14,7 @@ from pyomo.common.log import is_debug_set from pyomo.core.base.set_types import Any -from pyomo.core.base.var import IndexedVar, _VarData +from pyomo.core.base.var import IndexedVar, VarData from pyomo.core.base.constraint import IndexedConstraint, ConstraintData from pyomo.core.base.objective import IndexedObjective, ObjectiveData from pyomo.core.base.expression import IndexedExpression, ExpressionData @@ -232,7 +232,7 @@ def __init__(self, *args, **kwds): # Constructor for ComponentList needs to # go last in order to handle any initialization # iterable as an argument - ComponentList.__init__(self, _VarData, *args, **kwds) + ComponentList.__init__(self, VarData, *args, **kwds) class XConstraintList(ComponentList, IndexedConstraint): diff --git a/pyomo/core/expr/numeric_expr.py b/pyomo/core/expr/numeric_expr.py index 25d83ca20f4..50abaeedbba 100644 --- a/pyomo/core/expr/numeric_expr.py +++ b/pyomo/core/expr/numeric_expr.py @@ -1238,7 +1238,7 @@ class LinearExpression(SumExpression): - not potentially variable (e.g., native types, Params, or NPV expressions) - :py:class:`MonomialTermExpression` - - :py:class:`_VarData` + - :py:class:`VarData` Args: args (tuple): Children nodes diff --git a/pyomo/core/plugins/transform/eliminate_fixed_vars.py b/pyomo/core/plugins/transform/eliminate_fixed_vars.py index 9312035b8c8..934228afd7c 100644 --- a/pyomo/core/plugins/transform/eliminate_fixed_vars.py +++ b/pyomo/core/plugins/transform/eliminate_fixed_vars.py @@ -11,7 +11,7 @@ from pyomo.core.expr import ExpressionBase, as_numeric from pyomo.core import Constraint, Objective, TransformationFactory -from pyomo.core.base.var import Var, _VarData +from pyomo.core.base.var import Var, VarData from pyomo.core.util import sequence from pyomo.core.plugins.transform.hierarchy import IsomorphicTransformation @@ -77,7 +77,7 @@ def _fix_vars(self, expr, model): if isinstance(expr._args[i], ExpressionBase): _args.append(self._fix_vars(expr._args[i], model)) elif ( - isinstance(expr._args[i], Var) or isinstance(expr._args[i], _VarData) + isinstance(expr._args[i], Var) or isinstance(expr._args[i], VarData) ) and expr._args[i].fixed: if expr._args[i].value != 0.0: _args.append(as_numeric(expr._args[i].value)) diff --git a/pyomo/core/plugins/transform/radix_linearization.py b/pyomo/core/plugins/transform/radix_linearization.py index c67e556d60c..92270655f31 100644 --- a/pyomo/core/plugins/transform/radix_linearization.py +++ b/pyomo/core/plugins/transform/radix_linearization.py @@ -21,7 +21,7 @@ Block, RangeSet, ) -from pyomo.core.base.var import _VarData +from pyomo.core.base.var import VarData import logging @@ -268,8 +268,8 @@ def _collect_bilinear(self, expr, bilin, quad): self._collect_bilinear(e, bilin, quad) # No need to check denominator, as this is poly_degree==2 return - if not isinstance(expr._numerator[0], _VarData) or not isinstance( - expr._numerator[1], _VarData + if not isinstance(expr._numerator[0], VarData) or not isinstance( + expr._numerator[1], VarData ): raise RuntimeError("Cannot yet handle complex subexpressions") if expr._numerator[0] is expr._numerator[1]: diff --git a/pyomo/core/tests/unit/test_piecewise.py b/pyomo/core/tests/unit/test_piecewise.py index af82ef7c06d..7b8e01e6a45 100644 --- a/pyomo/core/tests/unit/test_piecewise.py +++ b/pyomo/core/tests/unit/test_piecewise.py @@ -104,7 +104,7 @@ def test_indexed_with_nonindexed_vars(self): model.con3 = Piecewise(*args, **keywords) # test that nonindexed Piecewise can handle - # _VarData (e.g model.x[1] + # VarData (e.g model.x[1] def test_nonindexed_with_indexed_vars(self): model = ConcreteModel() model.range = Var([1]) diff --git a/pyomo/core/tests/unit/test_var_set_bounds.py b/pyomo/core/tests/unit/test_var_set_bounds.py index bae89556ce3..1686ba4f1c6 100644 --- a/pyomo/core/tests/unit/test_var_set_bounds.py +++ b/pyomo/core/tests/unit/test_var_set_bounds.py @@ -36,7 +36,7 @@ # GAH: These tests been temporarily disabled. It is no longer the job of Var # to validate its domain at the time of construction. It only needs to # ensure that whatever object is passed as its domain is suitable for -# interacting with the _VarData interface (e.g., has a bounds method) +# interacting with the VarData interface (e.g., has a bounds method) # The plan is to start adding functionality to the solver interfaces # that will support custom domains. diff --git a/pyomo/dae/misc.py b/pyomo/dae/misc.py index 3e09a055577..dcb73f60c9e 100644 --- a/pyomo/dae/misc.py +++ b/pyomo/dae/misc.py @@ -263,7 +263,7 @@ def _update_var(v): # Note: This is not required it is handled by the _default method on # Var (which is now a IndexedComponent). However, it # would be much slower to rely on that method to generate new - # _VarData for a large number of new indices. + # VarData for a large number of new indices. new_indices = set(v.index_set()) - set(v._data.keys()) for index in new_indices: v.add(index) diff --git a/pyomo/repn/plugins/ampl/ampl_.py b/pyomo/repn/plugins/ampl/ampl_.py index 840bee2166c..1cff45b30c1 100644 --- a/pyomo/repn/plugins/ampl/ampl_.py +++ b/pyomo/repn/plugins/ampl/ampl_.py @@ -168,8 +168,8 @@ def _build_op_template(): _op_template[EXPR.EqualityExpression] = "o24{C}\n" _op_comment[EXPR.EqualityExpression] = "\t#eq" - _op_template[var._VarData] = "v%d{C}\n" - _op_comment[var._VarData] = "\t#%s" + _op_template[var.VarData] = "v%d{C}\n" + _op_comment[var.VarData] = "\t#%s" _op_template[param.ParamData] = "n%r{C}\n" _op_comment[param.ParamData] = "" @@ -733,16 +733,16 @@ def _print_nonlinear_terms_NL(self, exp): % (exp_type) ) - elif isinstance(exp, (var._VarData, IVariable)) and (not exp.is_fixed()): + elif isinstance(exp, (var.VarData, IVariable)) and (not exp.is_fixed()): # (self._output_fixed_variable_bounds or if not self._symbolic_solver_labels: OUTPUT.write( - self._op_string[var._VarData] + self._op_string[var.VarData] % (self.ampl_var_id[self._varID_map[id(exp)]]) ) else: OUTPUT.write( - self._op_string[var._VarData] + self._op_string[var.VarData] % ( self.ampl_var_id[self._varID_map[id(exp)]], self._name_labeler(exp), diff --git a/pyomo/repn/plugins/cpxlp.py b/pyomo/repn/plugins/cpxlp.py index 6228e7c7286..45f4279f8fe 100644 --- a/pyomo/repn/plugins/cpxlp.py +++ b/pyomo/repn/plugins/cpxlp.py @@ -60,7 +60,7 @@ def __init__(self): # The LP writer tracks which variables are # referenced in constraints, so that a user does not end up with a # zillion "unreferenced variables" warning messages. - # This dictionary maps id(_VarData) -> _VarData. + # This dictionary maps id(VarData) -> VarData. self._referenced_variable_ids = {} # Per ticket #4319, we are using %.17g, which mocks the diff --git a/pyomo/repn/plugins/mps.py b/pyomo/repn/plugins/mps.py index ba26783eea1..e1a0d2187fc 100644 --- a/pyomo/repn/plugins/mps.py +++ b/pyomo/repn/plugins/mps.py @@ -62,7 +62,7 @@ def __init__(self, int_marker=False): # referenced in constraints, so that one doesn't end up with a # zillion "unreferenced variables" warning messages. stored at # the object level to avoid additional method arguments. - # dictionary of id(_VarData)->_VarData. + # dictionary of id(VarData)->VarData. self._referenced_variable_ids = {} # Keven Hunter made a nice point about using %.16g in his attachment diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 1a49238f35b..e1691e75f2f 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -77,7 +77,7 @@ ObjectiveData, ) from pyomo.core.base.suffix import SuffixFinder -from pyomo.core.base.var import _VarData +from pyomo.core.base.var import VarData import pyomo.core.kernel as kernel from pyomo.core.pyomoobject import PyomoObject from pyomo.opt import WriterFactory @@ -129,7 +129,7 @@ class NLWriterInfo(object): Attributes ---------- - variables: List[_VarData] + variables: List[VarData] The list of (unfixed) Pyomo model variables in the order written to the NL file @@ -162,10 +162,10 @@ class NLWriterInfo(object): file in the same order as the :py:attr:`variables` and generated .col file. - eliminated_vars: List[Tuple[_VarData, NumericExpression]] + 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`, + 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 @@ -466,7 +466,7 @@ def compile(self, column_order, row_order, obj_order, model_id): self.obj[obj_order[_id]] = val elif _id == model_id: self.prob[0] = val - elif isinstance(obj, (_VarData, ConstraintData, ObjectiveData)): + elif isinstance(obj, (VarData, ConstraintData, ObjectiveData)): missing_component_data.add(obj) elif isinstance(obj, (Var, Constraint, Objective)): # Expand this indexed component to store the diff --git a/pyomo/repn/plugins/standard_form.py b/pyomo/repn/plugins/standard_form.py index e6dc217acc9..434a9b8f35a 100644 --- a/pyomo/repn/plugins/standard_form.py +++ b/pyomo/repn/plugins/standard_form.py @@ -84,17 +84,17 @@ class LinearStandardFormInfo(object): +/- 1 indicating if the row was multiplied by -1 (corresponding to a constraint lower bound) or +1 (upper bound). - columns : List[_VarData] + columns : List[VarData] The list of Pyomo variable objects corresponding to columns in the `A` and `c` matrices. - eliminated_vars: List[Tuple[_VarData, NumericExpression]] + 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`). + (: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. diff --git a/pyomo/solvers/plugins/solvers/cplex_persistent.py b/pyomo/solvers/plugins/solvers/cplex_persistent.py index fd396a8c87f..754dadc09e2 100644 --- a/pyomo/solvers/plugins/solvers/cplex_persistent.py +++ b/pyomo/solvers/plugins/solvers/cplex_persistent.py @@ -82,7 +82,7 @@ def update_var(self, var): Parameters ---------- - var: Var (scalar Var or single _VarData) + var: Var (scalar Var or single VarData) """ # see PR #366 for discussion about handling indexed @@ -130,7 +130,7 @@ def _add_column(self, var, obj_coef, constraints, coefficients): Parameters ---------- - var: Var (scalar Var or single _VarData) + var: Var (scalar Var or single VarData) obj_coef: float constraints: list of solver constraints coefficients: list of coefficients to put on var in the associated constraint diff --git a/pyomo/solvers/plugins/solvers/gurobi_persistent.py b/pyomo/solvers/plugins/solvers/gurobi_persistent.py index 585a78e3ef1..97a3533c3f9 100644 --- a/pyomo/solvers/plugins/solvers/gurobi_persistent.py +++ b/pyomo/solvers/plugins/solvers/gurobi_persistent.py @@ -111,7 +111,7 @@ def update_var(self, var): Parameters ---------- - var: Var (scalar Var or single _VarData) + var: Var (scalar Var or single VarData) """ # see PR #366 for discussion about handling indexed @@ -710,7 +710,7 @@ def _add_column(self, var, obj_coef, constraints, coefficients): Parameters ---------- - var: Var (scalar Var or single _VarData) + var: Var (scalar Var or single VarData) obj_coef: float constraints: list of solver constraints coefficients: list of coefficients to put on var in the associated constraint diff --git a/pyomo/solvers/plugins/solvers/mosek_persistent.py b/pyomo/solvers/plugins/solvers/mosek_persistent.py index 9e7f8de1b41..efcbb7dd9dd 100644 --- a/pyomo/solvers/plugins/solvers/mosek_persistent.py +++ b/pyomo/solvers/plugins/solvers/mosek_persistent.py @@ -95,7 +95,7 @@ def remove_var(self, solver_var): This will keep any other model components intact. Parameters ---------- - solver_var: Var (scalar Var or single _VarData) + solver_var: Var (scalar Var or single VarData) """ self.remove_vars(solver_var) @@ -106,7 +106,7 @@ def remove_vars(self, *solver_vars): This will keep any other model components intact. Parameters ---------- - *solver_var: Var (scalar Var or single _VarData) + *solver_var: Var (scalar Var or single VarData) """ try: var_ids = [] diff --git a/pyomo/solvers/plugins/solvers/persistent_solver.py b/pyomo/solvers/plugins/solvers/persistent_solver.py index 79cd669dd71..3c2a9e52eab 100644 --- a/pyomo/solvers/plugins/solvers/persistent_solver.py +++ b/pyomo/solvers/plugins/solvers/persistent_solver.py @@ -206,7 +206,7 @@ def add_column(self, model, var, obj_coef, constraints, coefficients): Parameters ---------- model: pyomo ConcreteModel to which the column will be added - var: Var (scalar Var or single _VarData) + var: Var (scalar Var or single VarData) obj_coef: float, pyo.Param constraints: list of scalar Constraints of single ConstraintDatas coefficients: list of the coefficient to put on var in the associated constraint @@ -380,7 +380,7 @@ def remove_var(self, var): Parameters ---------- - var: Var (scalar Var or single _VarData) + var: Var (scalar Var or single VarData) """ # see PR #366 for discussion about handling indexed diff --git a/pyomo/solvers/plugins/solvers/xpress_persistent.py b/pyomo/solvers/plugins/solvers/xpress_persistent.py index 513a7fbc257..fbdc2866dcf 100644 --- a/pyomo/solvers/plugins/solvers/xpress_persistent.py +++ b/pyomo/solvers/plugins/solvers/xpress_persistent.py @@ -90,7 +90,7 @@ def update_var(self, var): Parameters ---------- - var: Var (scalar Var or single _VarData) + var: Var (scalar Var or single VarData) """ # see PR #366 for discussion about handling indexed @@ -124,7 +124,7 @@ def _add_column(self, var, obj_coef, constraints, coefficients): Parameters ---------- - var: Var (scalar Var or single _VarData) + var: Var (scalar Var or single VarData) obj_coef: float constraints: list of solver constraints coefficients: list of coefficients to put on var in the associated constraint diff --git a/pyomo/util/calc_var_value.py b/pyomo/util/calc_var_value.py index d5bceb5c67b..254b82c59cd 100644 --- a/pyomo/util/calc_var_value.py +++ b/pyomo/util/calc_var_value.py @@ -53,7 +53,7 @@ def calculate_variable_from_constraint( Parameters: ----------- - variable: :py:class:`_VarData` + variable: :py:class:`VarData` The variable to solve for constraint: :py:class:`ConstraintData` or relational expression or `tuple` The equality constraint to use to solve for the variable value. From 0e994faacb398642756fdd12cf0802a49cf92dc4 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 18:06:48 -0600 Subject: [PATCH 1468/1797] Revert "Renamed _SuffixData -> SuffixData" This reverts commit 430f98207353b1e1e7383504aa0336bc852aeb9c. --- pyomo/core/base/suffix.py | 4 ++-- pyomo/repn/plugins/nl_writer.py | 15 +++++---------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/pyomo/core/base/suffix.py b/pyomo/core/base/suffix.py index c4b37789773..be2f732650d 100644 --- a/pyomo/core/base/suffix.py +++ b/pyomo/core/base/suffix.py @@ -129,7 +129,7 @@ class SuffixDirection(enum.IntEnum): IMPORT_EXPORT = 3 -SuffixDataTypeDomain = In(SuffixDataType) +_SuffixDataTypeDomain = In(SuffixDataType) _SuffixDirectionDomain = In(SuffixDirection) @@ -253,7 +253,7 @@ def datatype(self): def datatype(self, datatype): """Set the suffix datatype.""" if datatype is not None: - datatype = SuffixDataTypeDomain(datatype) + datatype = _SuffixDataTypeDomain(datatype) self._datatype = datatype @property diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index e1691e75f2f..23e14104b89 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -423,7 +423,7 @@ def _generate_symbol_map(self, info): return symbol_map -class SuffixData(object): +class _SuffixData(object): def __init__(self, name): self.name = name self.obj = {} @@ -505,11 +505,6 @@ def compile(self, column_order, row_order, obj_order, model_id): ) -class _SuffixData(metaclass=RenamedClass): - __renamed__new_class__ = SuffixData - __renamed__version__ = '6.7.2.dev0' - - class CachingNumericSuffixFinder(SuffixFinder): scale = True @@ -642,7 +637,7 @@ def write(self, model): continue name = suffix.local_name if name not in suffix_data: - suffix_data[name] = SuffixData(name) + suffix_data[name] = _SuffixData(name) suffix_data[name].update(suffix) # # Data structures to support variable/constraint scaling @@ -999,7 +994,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) + suffix_data[name] = _SuffixData(name) suffix_data[name].datatype.add(Suffix.INT) sos_id = 0 sosno = suffix_data['sosno'] @@ -1349,7 +1344,7 @@ def write(self, model): if not _vals: continue ostream.write(f"S{_field|_float} {len(_vals)} {name}\n") - # Note: SuffixData.compile() guarantees 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)) ) @@ -1459,7 +1454,7 @@ def write(self, model): logger.warning("ignoring 'dual' suffix for Model") if data.con: ostream.write(f"d{len(data.con)}\n") - # Note: SuffixData.compile() guarantees 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)) ) From b1a7b30cecf901ce0e1c4a9c146814b250e4cc75 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 19:44:54 -0600 Subject: [PATCH 1469/1797] Update ComponentData imports, provide deprecation paths --- pyomo/core/base/__init__.py | 110 +++++++++++++++++++++-------------- pyomo/core/base/piecewise.py | 2 +- pyomo/core/base/sets.py | 2 +- pyomo/gdp/__init__.py | 8 ++- 4 files changed, 75 insertions(+), 47 deletions(-) diff --git a/pyomo/core/base/__init__.py b/pyomo/core/base/__init__.py index 0363380af1f..9a06a3e02bb 100644 --- a/pyomo/core/base/__init__.py +++ b/pyomo/core/base/__init__.py @@ -33,10 +33,14 @@ BooleanValue, native_logical_values, ) + from pyomo.core.kernel.objective import minimize, maximize -from pyomo.core.base.config import PyomoOptions -from pyomo.core.base.expression import Expression, ExpressionData +from pyomo.core.base.component import name, Component, ModelComponentFactory +from pyomo.core.base.componentuid import ComponentUID +from pyomo.core.base.config import PyomoOptions +from pyomo.core.base.enums import SortComponents, TraversalStrategy +from pyomo.core.base.instance2dat import instance2dat from pyomo.core.base.label import ( CuidLabeler, CounterLabeler, @@ -47,17 +51,37 @@ NameLabeler, ShortNameLabeler, ) +from pyomo.core.base.misc import display +from pyomo.core.base.reference import Reference +from pyomo.core.base.symbol_map import symbol_map_from_instance +from pyomo.core.base.transformation import ( + Transformation, + TransformationFactory, + ReverseTransformationToken, +) + +from pyomo.core.base.PyomoModel import ( + global_option, + ModelSolution, + ModelSolutions, + Model, + ConcreteModel, + AbstractModel, +) # # Components # -from pyomo.core.base.component import name, Component, ModelComponentFactory -from pyomo.core.base.componentuid import ComponentUID from pyomo.core.base.action import BuildAction -from pyomo.core.base.check import BuildCheck -from pyomo.core.base.set import Set, SetOf, simple_set_rule, RangeSet -from pyomo.core.base.param import Param -from pyomo.core.base.var import Var, VarData, GeneralVarData, ScalarVar, VarList +from pyomo.core.base.block import ( + Block, + BlockData, + ScalarBlock, + active_components, + components, + active_components_data, + components_data, +) from pyomo.core.base.boolean_var import ( BooleanVar, BooleanVarData, @@ -65,6 +89,8 @@ BooleanVarList, ScalarBooleanVar, ) +from pyomo.core.base.check import BuildCheck +from pyomo.core.base.connector import Connector, ConnectorData from pyomo.core.base.constraint import ( simple_constraint_rule, simple_constraintlist_rule, @@ -72,6 +98,8 @@ Constraint, ConstraintData, ) +from pyomo.core.base.expression import Expression, ExpressionData +from pyomo.core.base.external import ExternalFunction from pyomo.core.base.logical_constraint import ( LogicalConstraint, LogicalConstraintList, @@ -84,19 +112,13 @@ ObjectiveList, ObjectiveData, ) -from pyomo.core.base.connector import Connector -from pyomo.core.base.sos import SOSConstraint -from pyomo.core.base.piecewise import Piecewise -from pyomo.core.base.suffix import ( - active_export_suffix_generator, - active_import_suffix_generator, - Suffix, -) -from pyomo.core.base.external import ExternalFunction -from pyomo.core.base.symbol_map import symbol_map_from_instance -from pyomo.core.base.reference import Reference - +from pyomo.core.base.param import Param, ParamData +from pyomo.core.base.piecewise import Piecewise, PiecewiseData from pyomo.core.base.set import ( + Set, + SetData, + SetOf, + RangeSet, Reals, PositiveReals, NonPositiveReals, @@ -116,34 +138,19 @@ PercentFraction, RealInterval, IntegerInterval, + simple_set_rule, ) -from pyomo.core.base.misc import display -from pyomo.core.base.block import ( - Block, - ScalarBlock, - active_components, - components, - active_components_data, - components_data, -) -from pyomo.core.base.enums import SortComponents, TraversalStrategy -from pyomo.core.base.PyomoModel import ( - global_option, - ModelSolution, - ModelSolutions, - Model, - ConcreteModel, - AbstractModel, -) -from pyomo.core.base.transformation import ( - Transformation, - TransformationFactory, - ReverseTransformationToken, +from pyomo.core.base.sos import SOSConstraint, SOSConstraintData +from pyomo.core.base.suffix import ( + active_export_suffix_generator, + active_import_suffix_generator, + Suffix, ) +from pyomo.core.base.var import Var, VarData, GeneralVarData, ScalarVar, VarList -from pyomo.core.base.instance2dat import instance2dat - +# # These APIs are deprecated and should be removed in the near future +# from pyomo.core.base.set import set_options, RealSet, IntegerSet, BooleanSet from pyomo.common.deprecation import relocated_module_attribute @@ -155,4 +162,19 @@ relocated_module_attribute( 'SimpleBooleanVar', 'pyomo.core.base.boolean_var.SimpleBooleanVar', version='6.0' ) +# Historically, only a subset of "private" component data classes were imported here +for _cdata in ( + 'ConstraintData', + 'LogicalConstraintData', + 'ExpressionData', + 'VarData', + 'GeneralVarData', + 'GeneralBooleanVarData', + 'BooleanVarData', + 'ObjectiveData', +): + relocated_module_attribute( + f'_{_cdata}', f'pyomo.core.base.{_cdata}', version='6.7.2.dev0' + ) +del _cdata del relocated_module_attribute diff --git a/pyomo/core/base/piecewise.py b/pyomo/core/base/piecewise.py index f061ebfbdc8..efe500dbfb1 100644 --- a/pyomo/core/base/piecewise.py +++ b/pyomo/core/base/piecewise.py @@ -40,7 +40,7 @@ import enum from pyomo.common.log import is_debug_set -from pyomo.common.deprecation import deprecation_warning +from pyomo.common.deprecation import RenamedClass, deprecation_warning from pyomo.common.numeric_types import value from pyomo.common.timing import ConstructionTimer from pyomo.core.base.block import Block, BlockData diff --git a/pyomo/core/base/sets.py b/pyomo/core/base/sets.py index 3ebdc6875d1..72d49479dd3 100644 --- a/pyomo/core/base/sets.py +++ b/pyomo/core/base/sets.py @@ -17,7 +17,7 @@ process_setarg, set_options, simple_set_rule, - SetDataBase, + _SetDataBase, SetData, Set, SetOf, diff --git a/pyomo/gdp/__init__.py b/pyomo/gdp/__init__.py index a18bc03084a..d204369cdba 100644 --- a/pyomo/gdp/__init__.py +++ b/pyomo/gdp/__init__.py @@ -9,7 +9,13 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from pyomo.gdp.disjunct import GDP_Error, Disjunct, Disjunction +from pyomo.gdp.disjunct import ( + GDP_Error, + Disjunct, + DisjunctData, + Disjunction, + DisjunctionData, +) # Do not import these files: importing them registers the transformation # plugins with the pyomo script so that they get automatically invoked. From 1e5b54e7a30ee6ad9de1a956bd1f975148c7efd9 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 20:17:28 -0600 Subject: [PATCH 1470/1797] Mrege GeneralVarData into VarData class --- pyomo/core/base/var.py | 439 +++++++++++++++++------------------------ 1 file changed, 183 insertions(+), 256 deletions(-) diff --git a/pyomo/core/base/var.py b/pyomo/core/base/var.py index 509238e4e6b..b1634b61c44 100644 --- a/pyomo/core/base/var.py +++ b/pyomo/core/base/var.py @@ -85,246 +85,11 @@ 'value', 'stale', 'fixed', + ('__call__', "access property 'value' on"), ) class VarData(ComponentData, NumericValue): - """This class defines the abstract interface for a single variable. - - Note that this "abstract" class is not intended to be directly - instantiated. - - """ - - __slots__ = () - - # - # Interface - # - - def has_lb(self): - """Returns :const:`False` when the lower bound is - :const:`None` or negative infinity""" - return self.lb is not None - - def has_ub(self): - """Returns :const:`False` when the upper bound is - :const:`None` or positive infinity""" - return self.ub is not None - - # TODO: deprecate this? Properties are generally preferred over "set*()" - def setlb(self, val): - """ - Set the lower bound for this variable after validating that - the value is fixed (or None). - """ - self.lower = val - - # TODO: deprecate this? Properties are generally preferred over "set*()" - def setub(self, val): - """ - Set the upper bound for this variable after validating that - the value is fixed (or None). - """ - self.upper = val - - @property - def bounds(self): - """Returns (or set) the tuple (lower bound, upper bound). - - This returns the current (numeric) values of the lower and upper - bounds as a tuple. If there is no bound, returns None (and not - +/-inf) - - """ - return self.lb, self.ub - - @bounds.setter - def bounds(self, val): - self.lower, self.upper = val - - @property - def lb(self): - """Return (or set) the numeric value of the variable lower bound.""" - lb = value(self.lower) - return None if lb == _ninf else lb - - @lb.setter - def lb(self, val): - self.lower = val - - @property - def ub(self): - """Return (or set) the numeric value of the variable upper bound.""" - ub = value(self.upper) - return None if ub == _inf else ub - - @ub.setter - def ub(self, val): - self.upper = val - - def is_integer(self): - """Returns True when the domain is a contiguous integer range.""" - _id = id(self.domain) - if _id in _known_global_real_domains: - return not _known_global_real_domains[_id] - _interval = self.domain.get_interval() - if _interval is None: - return False - # Note: it is not sufficient to just check the step: the - # starting / ending points must be integers (or not specified) - start, stop, step = _interval - return ( - step == 1 - and (start is None or int(start) == start) - and (stop is None or int(stop) == stop) - ) - - def is_binary(self): - """Returns True when the domain is restricted to Binary values.""" - domain = self.domain - if domain is Binary: - return True - if id(domain) in _known_global_real_domains: - return False - return domain.get_interval() == (0, 1, 1) - - def is_continuous(self): - """Returns True when the domain is a continuous real range""" - _id = id(self.domain) - if _id in _known_global_real_domains: - return _known_global_real_domains[_id] - _interval = self.domain.get_interval() - return _interval is not None and _interval[2] == 0 - - def is_fixed(self): - """Returns True if this variable is fixed, otherwise returns False.""" - return self.fixed - - def is_constant(self): - """Returns False because this is not a constant in an expression.""" - return False - - def is_variable_type(self): - """Returns True because this is a variable.""" - return True - - def is_potentially_variable(self): - """Returns True because this is a variable.""" - return True - - def _compute_polynomial_degree(self, result): - """ - If the variable is fixed, it represents a constant - is a polynomial with degree 0. Otherwise, it has - degree 1. This method is used in expressions to - compute polynomial degree. - """ - if self.fixed: - return 0 - return 1 - - def clear(self): - self.value = None - - def __call__(self, exception=True): - """Compute the value of this variable.""" - return self.value - - # - # Abstract Interface - # - - def set_value(self, val, skip_validation=False): - """Set the current variable value.""" - raise NotImplementedError - - @property - def value(self): - """Return (or set) the value for this variable.""" - raise NotImplementedError - - @property - def domain(self): - """Return (or set) the domain for this variable.""" - raise NotImplementedError - - @property - def lower(self): - """Return (or set) an expression for the variable lower bound.""" - raise NotImplementedError - - @property - def upper(self): - """Return (or set) an expression for the variable upper bound.""" - raise NotImplementedError - - @property - def fixed(self): - """Return (or set) the fixed indicator for this variable. - - Alias for :meth:`is_fixed` / :meth:`fix` / :meth:`unfix`. - - """ - raise NotImplementedError - - @property - def stale(self): - """The stale status for this variable. - - Variables are "stale" if their current value was not updated as - part of the most recent model update. A "model update" can be - one of several things: a solver invocation, loading a previous - solution, or manually updating a non-stale :class:`Var` value. - - Returns - ------- - bool - - Notes - ----- - Fixed :class:`Var` objects will be stale after invoking a solver - (as their value was not updated by the solver). - - Updating a stale :class:`Var` value will not cause other - variable values to be come stale. However, updating the first - non-stale :class:`Var` value after a solve or solution load - *will* cause all other variables to be marked as stale - - """ - raise NotImplementedError - - def fix(self, value=NOTSET, skip_validation=False): - """Fix the value of this variable (treat as nonvariable) - - This sets the :attr:`fixed` indicator to True. If ``value`` is - provided, the value (and the ``skip_validation`` flag) are first - passed to :meth:`set_value()`. - - """ - self.fixed = True - if value is not NOTSET: - self.set_value(value, skip_validation) - - def unfix(self): - """Unfix this variable (treat as variable in solver interfaces) - - This sets the :attr:`fixed` indicator to False. - - """ - self.fixed = False - - def free(self): - """Alias for :meth:`unfix`""" - return self.unfix() - - -class _VarData(metaclass=RenamedClass): - __renamed__new_class__ = VarData - __renamed__version__ = '6.7.2.dev0' - - -class GeneralVarData(VarData): """This class defines the data for a single variable.""" __slots__ = ('_value', '_lb', '_ub', '_domain', '_fixed', '_stale') @@ -365,10 +130,6 @@ def copy(cls, src): self._index = src._index return self - # - # Abstract Interface - # - def set_value(self, val, skip_validation=False): """Set the current variable value. @@ -429,14 +190,20 @@ def set_value(self, val, skip_validation=False): @property def value(self): + """Return (or set) the value for this variable.""" return self._value @value.setter def value(self, val): self.set_value(val) + def __call__(self, exception=True): + """Compute the value of this variable.""" + return self._value + @property def domain(self): + """Return (or set) the domain for this variable.""" return self._domain @domain.setter @@ -453,9 +220,42 @@ def domain(self, domain): ) raise - @VarData.bounds.getter + def has_lb(self): + """Returns :const:`False` when the lower bound is + :const:`None` or negative infinity""" + return self.lb is not None + + def has_ub(self): + """Returns :const:`False` when the upper bound is + :const:`None` or positive infinity""" + return self.ub is not None + + # TODO: deprecate this? Properties are generally preferred over "set*()" + def setlb(self, val): + """ + Set the lower bound for this variable after validating that + the value is fixed (or None). + """ + self.lower = val + + # TODO: deprecate this? Properties are generally preferred over "set*()" + def setub(self, val): + """ + Set the upper bound for this variable after validating that + the value is fixed (or None). + """ + self.upper = val + + @property def bounds(self): - # Custom implementation of VarData.bounds to avoid unnecessary + """Returns (or set) the tuple (lower bound, upper bound). + + This returns the current (numeric) values of the lower and upper + bounds as a tuple. If there is no bound, returns None (and not + +/-inf) + + """ + # Custom implementation of lb / ub to avoid unnecessary # expression generation and duplicate calls to domain.bounds() domain_lb, domain_ub = self.domain.bounds() # lb is the tighter of the domain and bounds @@ -496,10 +296,14 @@ def bounds(self): ub = min(ub, domain_ub) return lb, ub - @VarData.lb.getter + @bounds.setter + def bounds(self, val): + self.lower, self.upper = val + + @property def lb(self): - # Custom implementation of VarData.lb to avoid unnecessary - # expression generation + """Return (or set) the numeric value of the variable lower bound.""" + # Note: Implementation avoids unnecessary expression generation domain_lb, domain_ub = self.domain.bounds() # lb is the tighter of the domain and bounds lb = self._lb @@ -521,10 +325,14 @@ def lb(self): lb = max(lb, domain_lb) return lb - @VarData.ub.getter + @lb.setter + def lb(self, val): + self.lower = val + + @property def ub(self): - # Custom implementation of VarData.ub to avoid unnecessary - # expression generation + """Return (or set) the numeric value of the variable upper bound.""" + # Note: implementation avoids unnecessary expression generation domain_lb, domain_ub = self.domain.bounds() # ub is the tighter of the domain and bounds ub = self._ub @@ -546,6 +354,10 @@ def ub(self): ub = min(ub, domain_ub) return ub + @ub.setter + def ub(self, val): + self.upper = val + @property def lower(self): """Return (or set) an expression for the variable lower bound. @@ -602,8 +414,37 @@ def get_units(self): # component if not scalar return self.parent_component()._units + def fix(self, value=NOTSET, skip_validation=False): + """Fix the value of this variable (treat as nonvariable) + + This sets the :attr:`fixed` indicator to True. If ``value`` is + provided, the value (and the ``skip_validation`` flag) are first + passed to :meth:`set_value()`. + + """ + self.fixed = True + if value is not NOTSET: + self.set_value(value, skip_validation) + + def unfix(self): + """Unfix this variable (treat as variable in solver interfaces) + + This sets the :attr:`fixed` indicator to False. + + """ + self.fixed = False + + def free(self): + """Alias for :meth:`unfix`""" + return self.unfix() + @property def fixed(self): + """Return (or set) the fixed indicator for this variable. + + Alias for :meth:`is_fixed` / :meth:`fix` / :meth:`unfix`. + + """ return self._fixed @fixed.setter @@ -612,6 +453,28 @@ def fixed(self, val): @property def stale(self): + """The stale status for this variable. + + Variables are "stale" if their current value was not updated as + part of the most recent model update. A "model update" can be + one of several things: a solver invocation, loading a previous + solution, or manually updating a non-stale :class:`Var` value. + + Returns + ------- + bool + + Notes + ----- + Fixed :class:`Var` objects will be stale after invoking a solver + (as their value was not updated by the solver). + + Updating a stale :class:`Var` value will not cause other + variable values to be come stale. However, updating the first + non-stale :class:`Var` value after a solve or solution load + *will* cause all other variables to be marked as stale + + """ return StaleFlagManager.is_stale(self._stale) @stale.setter @@ -621,11 +484,70 @@ def stale(self, val): else: self._stale = StaleFlagManager.get_flag(0) - # Note: override the base class definition to avoid a call through a - # property + def is_integer(self): + """Returns True when the domain is a contiguous integer range.""" + _id = id(self.domain) + if _id in _known_global_real_domains: + return not _known_global_real_domains[_id] + _interval = self.domain.get_interval() + if _interval is None: + return False + # Note: it is not sufficient to just check the step: the + # starting / ending points must be integers (or not specified) + start, stop, step = _interval + return ( + step == 1 + and (start is None or int(start) == start) + and (stop is None or int(stop) == stop) + ) + + def is_binary(self): + """Returns True when the domain is restricted to Binary values.""" + domain = self.domain + if domain is Binary: + return True + if id(domain) in _known_global_real_domains: + return False + return domain.get_interval() == (0, 1, 1) + + def is_continuous(self): + """Returns True when the domain is a continuous real range""" + _id = id(self.domain) + if _id in _known_global_real_domains: + return _known_global_real_domains[_id] + _interval = self.domain.get_interval() + return _interval is not None and _interval[2] == 0 + def is_fixed(self): + """Returns True if this variable is fixed, otherwise returns False.""" return self._fixed + def is_constant(self): + """Returns False because this is not a constant in an expression.""" + return False + + def is_variable_type(self): + """Returns True because this is a variable.""" + return True + + def is_potentially_variable(self): + """Returns True because this is a variable.""" + return True + + def clear(self): + self.value = None + + def _compute_polynomial_degree(self, result): + """ + If the variable is fixed, it represents a constant + is a polynomial with degree 0. Otherwise, it has + degree 1. This method is used in expressions to + compute polynomial degree. + """ + if self._fixed: + return 0 + return 1 + def _process_bound(self, val, bound_type): if type(val) in native_numeric_types or val is None: # TODO: warn/error: check if this Var has units: assigning @@ -648,8 +570,13 @@ def _process_bound(self, val, bound_type): return val -class _GeneralVarData(metaclass=RenamedClass): - __renamed__new_class__ = GeneralVarData +class _VarData(metaclass=RenamedClass): + __renamed__new_class__ = VarData + __renamed__version__ = '6.7.2.dev0' + + +class _VarData(metaclass=RenamedClass): + __renamed__new_class__ = VarData __renamed__version__ = '6.7.2.dev0' @@ -678,7 +605,7 @@ class Var(IndexedComponent, IndexedComponent_NDArrayMixin): doc (str, optional): Text describing this component. """ - _ComponentDataClass = GeneralVarData + _ComponentDataClass = VarData @overload def __new__(cls: Type[Var], *args, **kwargs) -> Union[ScalarVar, IndexedVar]: ... @@ -962,11 +889,11 @@ def _pprint(self): ) -class ScalarVar(GeneralVarData, Var): +class ScalarVar(VarData, Var): """A single variable.""" def __init__(self, *args, **kwd): - GeneralVarData.__init__(self, component=self) + VarData.__init__(self, component=self) Var.__init__(self, *args, **kwd) self._index = UnindexedComponent_index @@ -1067,7 +994,7 @@ def domain(self, domain): # between potentially variable GetItemExpression objects and # "constant" GetItemExpression objects. That will need to wait for # the expression rework [JDS; Nov 22]. - def __getitem__(self, args) -> GeneralVarData: + def __getitem__(self, args) -> VarData: try: return super().__getitem__(args) except RuntimeError: From cf7ff538df92247af0cd7e7c3e34ed4f4549d8af Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 20:21:36 -0600 Subject: [PATCH 1471/1797] Update references from GeneralVarData to VarData --- pyomo/contrib/appsi/base.py | 60 +++++++++---------- pyomo/contrib/appsi/cmodel/src/expression.hpp | 6 +- pyomo/contrib/appsi/fbbt.py | 10 ++-- pyomo/contrib/appsi/solvers/cbc.py | 16 ++--- pyomo/contrib/appsi/solvers/cplex.py | 16 ++--- pyomo/contrib/appsi/solvers/gurobi.py | 12 ++-- pyomo/contrib/appsi/solvers/highs.py | 8 +-- pyomo/contrib/appsi/solvers/ipopt.py | 16 ++--- pyomo/contrib/appsi/solvers/wntr.py | 8 +-- pyomo/contrib/appsi/writers/lp_writer.py | 8 +-- pyomo/contrib/appsi/writers/nl_writer.py | 8 +-- pyomo/contrib/cp/repn/docplex_writer.py | 4 +- .../logical_to_disjunctive_walker.py | 4 +- pyomo/contrib/latex_printer/latex_printer.py | 12 ++-- pyomo/contrib/parmest/utils/scenario_tree.py | 2 +- pyomo/contrib/solver/base.py | 24 ++++---- pyomo/contrib/solver/gurobi.py | 12 ++-- pyomo/contrib/solver/ipopt.py | 6 +- pyomo/contrib/solver/persistent.py | 18 +++--- pyomo/contrib/solver/solution.py | 26 ++++---- .../contrib/solver/tests/unit/test_results.py | 10 ++-- .../trustregion/tests/test_interface.py | 4 +- pyomo/core/base/__init__.py | 9 ++- pyomo/core/base/component.py | 2 +- pyomo/core/expr/calculus/derivatives.py | 6 +- pyomo/core/tests/transform/test_add_slacks.py | 2 +- pyomo/core/tests/unit/test_dict_objects.py | 6 +- pyomo/core/tests/unit/test_list_objects.py | 6 +- pyomo/core/tests/unit/test_numeric_expr.py | 4 +- pyomo/core/tests/unit/test_reference.py | 12 ++-- pyomo/repn/standard_repn.py | 6 +- .../plugins/solvers/gurobi_persistent.py | 4 +- pyomo/util/report_scaling.py | 4 +- 33 files changed, 171 insertions(+), 180 deletions(-) diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index 409c8e2596c..930ff8393e9 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -23,7 +23,7 @@ ) 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 VarData, Var from pyomo.core.base.param import ParamData, Param from pyomo.core.base.block import BlockData, Block from pyomo.core.base.objective import GeneralObjectiveData @@ -179,9 +179,7 @@ def __init__( class SolutionLoaderBase(abc.ABC): - def load_vars( - self, vars_to_load: Optional[Sequence[GeneralVarData]] = None - ) -> NoReturn: + def load_vars(self, vars_to_load: Optional[Sequence[VarData]] = None) -> NoReturn: """ Load the solution of the primal variables into the value attribute of the variables. @@ -197,8 +195,8 @@ def load_vars( @abc.abstractmethod def get_primals( - self, vars_to_load: Optional[Sequence[GeneralVarData]] = None - ) -> Mapping[GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[VarData]] = None + ) -> Mapping[VarData, float]: """ Returns a ComponentMap mapping variable to var value. @@ -256,8 +254,8 @@ def get_slacks( ) def get_reduced_costs( - self, vars_to_load: Optional[Sequence[GeneralVarData]] = None - ) -> Mapping[GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[VarData]] = None + ) -> Mapping[VarData, float]: """ Returns a ComponentMap mapping variable to reduced cost. @@ -303,8 +301,8 @@ def __init__( self._reduced_costs = reduced_costs def get_primals( - self, vars_to_load: Optional[Sequence[GeneralVarData]] = None - ) -> Mapping[GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[VarData]] = None + ) -> Mapping[VarData, float]: if self._primals is None: raise RuntimeError( 'Solution loader does not currently have a valid solution. Please ' @@ -353,8 +351,8 @@ def get_slacks( return slacks def get_reduced_costs( - self, vars_to_load: Optional[Sequence[GeneralVarData]] = None - ) -> Mapping[GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[VarData]] = None + ) -> Mapping[VarData, float]: if self._reduced_costs is None: raise RuntimeError( 'Solution loader does not currently have valid reduced costs. Please ' @@ -708,9 +706,7 @@ class PersistentSolver(Solver): def is_persistent(self): return True - def load_vars( - self, vars_to_load: Optional[Sequence[GeneralVarData]] = None - ) -> NoReturn: + def load_vars(self, vars_to_load: Optional[Sequence[VarData]] = None) -> NoReturn: """ Load the solution of the primal variables into the value attribute of the variables. @@ -726,8 +722,8 @@ def load_vars( @abc.abstractmethod def get_primals( - self, vars_to_load: Optional[Sequence[GeneralVarData]] = None - ) -> Mapping[GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[VarData]] = None + ) -> Mapping[VarData, float]: pass def get_duals( @@ -771,8 +767,8 @@ def get_slacks( ) def get_reduced_costs( - self, vars_to_load: Optional[Sequence[GeneralVarData]] = None - ) -> Mapping[GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[VarData]] = None + ) -> Mapping[VarData, float]: """ Parameters ---------- @@ -799,7 +795,7 @@ def set_instance(self, model): pass @abc.abstractmethod - def add_variables(self, variables: List[GeneralVarData]): + def add_variables(self, variables: List[VarData]): pass @abc.abstractmethod @@ -815,7 +811,7 @@ def add_block(self, block: BlockData): pass @abc.abstractmethod - def remove_variables(self, variables: List[GeneralVarData]): + def remove_variables(self, variables: List[VarData]): pass @abc.abstractmethod @@ -835,7 +831,7 @@ def set_objective(self, obj: GeneralObjectiveData): pass @abc.abstractmethod - def update_variables(self, variables: List[GeneralVarData]): + def update_variables(self, variables: List[VarData]): pass @abc.abstractmethod @@ -869,8 +865,8 @@ def get_slacks( 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, vars_to_load: Optional[Sequence[VarData]] = None + ) -> Mapping[VarData, float]: self._assert_solution_still_valid() return self._solver.get_reduced_costs(vars_to_load=vars_to_load) @@ -954,10 +950,10 @@ def set_instance(self, model): self.set_objective(None) @abc.abstractmethod - def _add_variables(self, variables: List[GeneralVarData]): + def _add_variables(self, variables: List[VarData]): pass - def add_variables(self, variables: List[GeneralVarData]): + def add_variables(self, variables: List[VarData]): for v in variables: if id(v) in self._referenced_variables: raise ValueError( @@ -987,7 +983,7 @@ def add_params(self, params: List[ParamData]): def _add_constraints(self, cons: List[GeneralConstraintData]): pass - def _check_for_new_vars(self, variables: List[GeneralVarData]): + def _check_for_new_vars(self, variables: List[VarData]): new_vars = dict() for v in variables: v_id = id(v) @@ -995,7 +991,7 @@ def _check_for_new_vars(self, variables: List[GeneralVarData]): new_vars[v_id] = v self.add_variables(list(new_vars.values())) - def _check_to_remove_vars(self, variables: List[GeneralVarData]): + def _check_to_remove_vars(self, variables: List[VarData]): vars_to_remove = dict() for v in variables: v_id = id(v) @@ -1174,10 +1170,10 @@ def remove_sos_constraints(self, cons: List[SOSConstraintData]): del self._vars_referenced_by_con[con] @abc.abstractmethod - def _remove_variables(self, variables: List[GeneralVarData]): + def _remove_variables(self, variables: List[VarData]): pass - def remove_variables(self, variables: List[GeneralVarData]): + def remove_variables(self, variables: List[VarData]): self._remove_variables(variables) for v in variables: v_id = id(v) @@ -1246,10 +1242,10 @@ def remove_block(self, block): ) @abc.abstractmethod - def _update_variables(self, variables: List[GeneralVarData]): + def _update_variables(self, variables: List[VarData]): pass - def update_variables(self, variables: List[GeneralVarData]): + def update_variables(self, variables: List[VarData]): for v in variables: self._vars[id(v)] = ( v, diff --git a/pyomo/contrib/appsi/cmodel/src/expression.hpp b/pyomo/contrib/appsi/cmodel/src/expression.hpp index 0c0777ef468..803bb21b6e2 100644 --- a/pyomo/contrib/appsi/cmodel/src/expression.hpp +++ b/pyomo/contrib/appsi/cmodel/src/expression.hpp @@ -680,7 +680,7 @@ class PyomoExprTypes { 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[_VarData] = var; expr_type_map[AutoLinkedBinaryVar] = var; expr_type_map[ScalarParam] = param; expr_type_map[_ParamData] = param; @@ -732,8 +732,8 @@ class PyomoExprTypes { 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 _VarData = + py::module_::import("pyomo.core.base.var").attr("_VarData"); py::object AutoLinkedBinaryVar = py::module_::import("pyomo.gdp.disjunct").attr("AutoLinkedBinaryVar"); py::object numeric_expr = py::module_::import("pyomo.core.expr.numeric_expr"); diff --git a/pyomo/contrib/appsi/fbbt.py b/pyomo/contrib/appsi/fbbt.py index 122ca5f7ffd..1ebb3d40381 100644 --- a/pyomo/contrib/appsi/fbbt.py +++ b/pyomo/contrib/appsi/fbbt.py @@ -18,7 +18,7 @@ ) from .cmodel import cmodel, cmodel_available from typing import List, Optional -from pyomo.core.base.var import GeneralVarData +from pyomo.core.base.var import VarData from pyomo.core.base.param import ParamData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.sos import SOSConstraintData @@ -121,7 +121,7 @@ def set_instance(self, model, symbolic_solver_labels: Optional[bool] = None): if self._objective is None: self.set_objective(None) - def _add_variables(self, variables: List[GeneralVarData]): + def _add_variables(self, variables: List[VarData]): if self._symbolic_solver_labels: set_name = True symbol_map = self._symbol_map @@ -190,7 +190,7 @@ def _remove_sos_constraints(self, cons: List[SOSConstraintData]): 'IntervalTightener does not support SOS constraints' ) - def _remove_variables(self, variables: List[GeneralVarData]): + def _remove_variables(self, variables: List[VarData]): if self._symbolic_solver_labels: for v in variables: self._symbol_map.removeSymbol(v) @@ -205,7 +205,7 @@ def _remove_params(self, params: List[ParamData]): for p in params: del self._param_map[id(p)] - def _update_variables(self, variables: List[GeneralVarData]): + def _update_variables(self, variables: List[VarData]): cmodel.process_pyomo_vars( self._pyomo_expr_types, variables, @@ -304,7 +304,7 @@ def perform_fbbt( self._deactivate_satisfied_cons() return n_iter - def perform_fbbt_with_seed(self, model: BlockData, seed_var: GeneralVarData): + def perform_fbbt_with_seed(self, model: BlockData, seed_var: VarData): if model is not self._model: self.set_instance(model) else: diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index d03e6e31c54..7db9a32764e 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -26,7 +26,7 @@ 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.var import VarData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.block import BlockData from pyomo.core.base.param import ParamData @@ -164,7 +164,7 @@ def symbol_map(self): def set_instance(self, model): self._writer.set_instance(model) - def add_variables(self, variables: List[GeneralVarData]): + def add_variables(self, variables: List[VarData]): self._writer.add_variables(variables) def add_params(self, params: List[ParamData]): @@ -176,7 +176,7 @@ def add_constraints(self, cons: List[GeneralConstraintData]): def add_block(self, block: BlockData): self._writer.add_block(block) - def remove_variables(self, variables: List[GeneralVarData]): + def remove_variables(self, variables: List[VarData]): self._writer.remove_variables(variables) def remove_params(self, params: List[ParamData]): @@ -191,7 +191,7 @@ def remove_block(self, block: BlockData): def set_objective(self, obj: GeneralObjectiveData): self._writer.set_objective(obj) - def update_variables(self, variables: List[GeneralVarData]): + def update_variables(self, variables: List[VarData]): self._writer.update_variables(variables) def update_params(self): @@ -440,8 +440,8 @@ def _check_and_escape_options(): return results def get_primals( - self, vars_to_load: Optional[Sequence[GeneralVarData]] = None - ) -> Mapping[GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[VarData]] = None + ) -> Mapping[VarData, float]: if ( self._last_results_object is None or self._last_results_object.best_feasible_objective is None @@ -477,8 +477,8 @@ def get_duals(self, cons_to_load=None): return {c: self._dual_sol[c] for c in cons_to_load} def get_reduced_costs( - self, vars_to_load: Optional[Sequence[GeneralVarData]] = None - ) -> Mapping[GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[VarData]] = None + ) -> Mapping[VarData, float]: if ( self._last_results_object is None or self._last_results_object.termination_condition diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index 55259244d45..0ed3495ac1c 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -22,7 +22,7 @@ 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.var import VarData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.block import BlockData from pyomo.core.base.param import ParamData @@ -179,7 +179,7 @@ def update_config(self): def set_instance(self, model): self._writer.set_instance(model) - def add_variables(self, variables: List[GeneralVarData]): + def add_variables(self, variables: List[VarData]): self._writer.add_variables(variables) def add_params(self, params: List[ParamData]): @@ -191,7 +191,7 @@ def add_constraints(self, cons: List[GeneralConstraintData]): def add_block(self, block: BlockData): self._writer.add_block(block) - def remove_variables(self, variables: List[GeneralVarData]): + def remove_variables(self, variables: List[VarData]): self._writer.remove_variables(variables) def remove_params(self, params: List[ParamData]): @@ -206,7 +206,7 @@ def remove_block(self, block: BlockData): def set_objective(self, obj: GeneralObjectiveData): self._writer.set_objective(obj) - def update_variables(self, variables: List[GeneralVarData]): + def update_variables(self, variables: List[VarData]): self._writer.update_variables(variables) def update_params(self): @@ -362,8 +362,8 @@ def _postsolve(self, timer: HierarchicalTimer, solve_time): return results def get_primals( - self, vars_to_load: Optional[Sequence[GeneralVarData]] = None - ) -> Mapping[GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[VarData]] = None + ) -> Mapping[VarData, float]: if ( self._cplex_model.solution.get_solution_type() == self._cplex_model.solution.type.none @@ -440,8 +440,8 @@ def get_duals( return res def get_reduced_costs( - self, vars_to_load: Optional[Sequence[GeneralVarData]] = None - ) -> Mapping[GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[VarData]] = None + ) -> Mapping[VarData, float]: if ( self._cplex_model.solution.get_solution_type() == self._cplex_model.solution.type.none diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index 8606d44cd46..e2ecd9b69e7 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -23,7 +23,7 @@ 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 Var, VarData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.sos import SOSConstraintData from pyomo.core.base.param import ParamData @@ -458,7 +458,7 @@ def _process_domain_and_bounds( return lb, ub, vtype - def _add_variables(self, variables: List[GeneralVarData]): + def _add_variables(self, variables: List[VarData]): var_names = list() vtypes = list() lbs = list() @@ -759,7 +759,7 @@ def _remove_sos_constraints(self, cons: List[SOSConstraintData]): del self._pyomo_sos_to_solver_sos_map[con] self._needs_updated = True - def _remove_variables(self, variables: List[GeneralVarData]): + def _remove_variables(self, variables: List[VarData]): for var in variables: v_id = id(var) if var in self._vars_added_since_update: @@ -774,7 +774,7 @@ def _remove_variables(self, variables: List[GeneralVarData]): def _remove_params(self, params: List[ParamData]): pass - def _update_variables(self, variables: List[GeneralVarData]): + def _update_variables(self, variables: List[VarData]): for var in variables: var_id = id(var) if var_id not in self._pyomo_var_to_solver_var_map: @@ -1221,7 +1221,7 @@ def set_var_attr(self, var, attr, val): Parameters ---------- - var: pyomo.core.base.var.GeneralVarData + var: pyomo.core.base.var.VarData The pyomo var for which the corresponding gurobi var attribute should be modified. attr: str @@ -1256,7 +1256,7 @@ def get_var_attr(self, var, attr): Parameters ---------- - var: pyomo.core.base.var.GeneralVarData + var: pyomo.core.base.var.VarData The pyomo var for which the corresponding gurobi var attribute should be retrieved. attr: str diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index 5af7b297684..c3083ac78d3 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -20,7 +20,7 @@ from pyomo.common.log import LogStream from pyomo.core.kernel.objective import minimize, maximize from pyomo.core.base import SymbolMap -from pyomo.core.base.var import GeneralVarData +from pyomo.core.base.var import VarData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.sos import SOSConstraintData from pyomo.core.base.param import ParamData @@ -308,7 +308,7 @@ def _process_domain_and_bounds(self, var_id): return lb, ub, vtype - def _add_variables(self, variables: List[GeneralVarData]): + def _add_variables(self, variables: List[VarData]): self._sol = None if self._last_results_object is not None: self._last_results_object.solution_loader.invalidate() @@ -493,7 +493,7 @@ def _remove_sos_constraints(self, cons: List[SOSConstraintData]): 'Highs interface does not support SOS constraints' ) - def _remove_variables(self, variables: List[GeneralVarData]): + def _remove_variables(self, variables: List[VarData]): self._sol = None if self._last_results_object is not None: self._last_results_object.solution_loader.invalidate() @@ -518,7 +518,7 @@ def _remove_variables(self, variables: List[GeneralVarData]): def _remove_params(self, params: List[ParamData]): pass - def _update_variables(self, variables: List[GeneralVarData]): + def _update_variables(self, variables: List[VarData]): self._sol = None if self._last_results_object is not None: self._last_results_object.solution_loader.invalidate() diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index ca75a1b02c8..5cd9a51785d 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -28,7 +28,7 @@ from pyomo.core.expr.numvalue import value from pyomo.core.expr.visitor import replace_expressions from typing import Optional, Sequence, NoReturn, List, Mapping -from pyomo.core.base.var import GeneralVarData +from pyomo.core.base.var import VarData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.block import BlockData from pyomo.core.base.param import ParamData @@ -228,7 +228,7 @@ def set_instance(self, model): self._writer.config.symbolic_solver_labels = self.config.symbolic_solver_labels self._writer.set_instance(model) - def add_variables(self, variables: List[GeneralVarData]): + def add_variables(self, variables: List[VarData]): self._writer.add_variables(variables) def add_params(self, params: List[ParamData]): @@ -240,7 +240,7 @@ def add_constraints(self, cons: List[GeneralConstraintData]): def add_block(self, block: BlockData): self._writer.add_block(block) - def remove_variables(self, variables: List[GeneralVarData]): + def remove_variables(self, variables: List[VarData]): self._writer.remove_variables(variables) def remove_params(self, params: List[ParamData]): @@ -255,7 +255,7 @@ def remove_block(self, block: BlockData): def set_objective(self, obj: GeneralObjectiveData): self._writer.set_objective(obj) - def update_variables(self, variables: List[GeneralVarData]): + def update_variables(self, variables: List[VarData]): self._writer.update_variables(variables) def update_params(self): @@ -514,8 +514,8 @@ def _apply_solver(self, timer: HierarchicalTimer): return results def get_primals( - self, vars_to_load: Optional[Sequence[GeneralVarData]] = None - ) -> Mapping[GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[VarData]] = None + ) -> Mapping[VarData, float]: if ( self._last_results_object is None or self._last_results_object.best_feasible_objective is None @@ -551,8 +551,8 @@ def get_duals(self, cons_to_load: Optional[Sequence[GeneralConstraintData]] = No return {c: self._dual_sol[c] for c in cons_to_load} def get_reduced_costs( - self, vars_to_load: Optional[Sequence[GeneralVarData]] = None - ) -> Mapping[GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[VarData]] = None + ) -> Mapping[VarData, float]: if ( self._last_results_object is None or self._last_results_object.termination_condition diff --git a/pyomo/contrib/appsi/solvers/wntr.py b/pyomo/contrib/appsi/solvers/wntr.py index 8f2650dabb6..62c4b0ed358 100644 --- a/pyomo/contrib/appsi/solvers/wntr.py +++ b/pyomo/contrib/appsi/solvers/wntr.py @@ -40,7 +40,7 @@ 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.var import VarData from pyomo.core.base.param import ParamData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.common.timing import HierarchicalTimer @@ -239,7 +239,7 @@ def set_instance(self, model): self.add_block(model) - def _add_variables(self, variables: List[GeneralVarData]): + def _add_variables(self, variables: List[VarData]): aml = wntr.sim.aml.aml for var in variables: varname = self._symbol_map.getSymbol(var, self._labeler) @@ -302,7 +302,7 @@ def _remove_constraints(self, cons: List[GeneralConstraintData]): del self._pyomo_con_to_solver_con_map[con] self._needs_updated = True - def _remove_variables(self, variables: List[GeneralVarData]): + def _remove_variables(self, variables: List[VarData]): for var in variables: v_id = id(var) solver_var = self._pyomo_var_to_solver_var_map[v_id] @@ -322,7 +322,7 @@ def _remove_params(self, params: List[ParamData]): self._symbol_map.removeSymbol(p) del self._pyomo_param_to_solver_param_map[p_id] - def _update_variables(self, variables: List[GeneralVarData]): + def _update_variables(self, variables: List[VarData]): aml = wntr.sim.aml.aml for var in variables: v_id = id(var) diff --git a/pyomo/contrib/appsi/writers/lp_writer.py b/pyomo/contrib/appsi/writers/lp_writer.py index 3a6682d5c00..4be2b32d83d 100644 --- a/pyomo/contrib/appsi/writers/lp_writer.py +++ b/pyomo/contrib/appsi/writers/lp_writer.py @@ -11,7 +11,7 @@ from typing import List from pyomo.core.base.param import ParamData -from pyomo.core.base.var import GeneralVarData +from pyomo.core.base.var import VarData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.objective import GeneralObjectiveData from pyomo.core.base.sos import SOSConstraintData @@ -77,7 +77,7 @@ def set_instance(self, model): if self._objective is None: self.set_objective(None) - def _add_variables(self, variables: List[GeneralVarData]): + def _add_variables(self, variables: List[VarData]): cmodel.process_pyomo_vars( self._expr_types, variables, @@ -117,7 +117,7 @@ def _remove_sos_constraints(self, cons: List[SOSConstraintData]): if len(cons) != 0: raise NotImplementedError('LP writer does not yet support SOS constraints') - def _remove_variables(self, variables: List[GeneralVarData]): + def _remove_variables(self, variables: List[VarData]): for v in variables: cvar = self._pyomo_var_to_solver_var_map.pop(id(v)) del self._solver_var_to_pyomo_var_map[cvar] @@ -128,7 +128,7 @@ def _remove_params(self, params: List[ParamData]): del self._pyomo_param_to_solver_param_map[id(p)] self._symbol_map.removeSymbol(p) - def _update_variables(self, variables: List[GeneralVarData]): + def _update_variables(self, variables: List[VarData]): cmodel.process_pyomo_vars( self._expr_types, variables, diff --git a/pyomo/contrib/appsi/writers/nl_writer.py b/pyomo/contrib/appsi/writers/nl_writer.py index c46cb1c0723..70176146a1e 100644 --- a/pyomo/contrib/appsi/writers/nl_writer.py +++ b/pyomo/contrib/appsi/writers/nl_writer.py @@ -11,7 +11,7 @@ from typing import List from pyomo.core.base.param import ParamData -from pyomo.core.base.var import GeneralVarData +from pyomo.core.base.var import VarData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.objective import GeneralObjectiveData from pyomo.core.base.sos import SOSConstraintData @@ -78,7 +78,7 @@ def set_instance(self, model): self.set_objective(None) self._set_pyomo_amplfunc_env() - def _add_variables(self, variables: List[GeneralVarData]): + def _add_variables(self, variables: List[VarData]): if self.config.symbolic_solver_labels: set_name = True symbol_map = self._symbol_map @@ -144,7 +144,7 @@ def _remove_sos_constraints(self, cons: List[SOSConstraintData]): if len(cons) != 0: raise NotImplementedError('NL writer does not support SOS constraints') - def _remove_variables(self, variables: List[GeneralVarData]): + def _remove_variables(self, variables: List[VarData]): if self.config.symbolic_solver_labels: for v in variables: self._symbol_map.removeSymbol(v) @@ -161,7 +161,7 @@ def _remove_params(self, params: List[ParamData]): for p in params: del self._pyomo_param_to_solver_param_map[id(p)] - def _update_variables(self, variables: List[GeneralVarData]): + def _update_variables(self, variables: List[VarData]): cmodel.process_pyomo_vars( self._expr_types, variables, diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index 75095755895..221fd61af5b 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -65,7 +65,7 @@ ) from pyomo.core.base.expression import ScalarExpression, GeneralExpressionData from pyomo.core.base.param import IndexedParam, ScalarParam, ParamData -from pyomo.core.base.var import ScalarVar, GeneralVarData, IndexedVar +from pyomo.core.base.var import ScalarVar, VarData, IndexedVar import pyomo.core.expr as EXPR from pyomo.core.expr.visitor import StreamBasedExpressionVisitor, identify_variables from pyomo.core.base import Set, RangeSet @@ -961,7 +961,7 @@ class LogicalToDoCplex(StreamBasedExpressionVisitor): IntervalVarData: _before_interval_var, IndexedIntervalVar: _before_indexed_interval_var, ScalarVar: _before_var, - GeneralVarData: _before_var, + VarData: _before_var, IndexedVar: _before_indexed_var, ScalarBooleanVar: _before_boolean_var, GeneralBooleanVarData: _before_boolean_var, diff --git a/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py b/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py index a228b1561dd..d9483c0ed14 100644 --- a/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py +++ b/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py @@ -29,7 +29,7 @@ import pyomo.core.base.boolean_var as BV from pyomo.core.base.expression import ScalarExpression, GeneralExpressionData from pyomo.core.base.param import ScalarParam, ParamData -from pyomo.core.base.var import ScalarVar, GeneralVarData +from pyomo.core.base.var import ScalarVar, VarData from pyomo.gdp.disjunct import AutoLinkedBooleanVar, Disjunct, Disjunction @@ -216,7 +216,7 @@ def _dispatch_atmost(visitor, node, *args): # for the moment, these are all just so we can get good error messages when we # don't handle them: _before_child_dispatcher[ScalarVar] = _dispatch_var -_before_child_dispatcher[GeneralVarData] = _dispatch_var +_before_child_dispatcher[VarData] = _dispatch_var _before_child_dispatcher[GeneralExpressionData] = _dispatch_expression _before_child_dispatcher[ScalarExpression] = _dispatch_expression diff --git a/pyomo/contrib/latex_printer/latex_printer.py b/pyomo/contrib/latex_printer/latex_printer.py index 28d1ca52943..e11543cb375 100644 --- a/pyomo/contrib/latex_printer/latex_printer.py +++ b/pyomo/contrib/latex_printer/latex_printer.py @@ -47,7 +47,7 @@ resolve_template, templatize_rule, ) -from pyomo.core.base.var import ScalarVar, GeneralVarData, IndexedVar +from pyomo.core.base.var import ScalarVar, VarData, IndexedVar from pyomo.core.base.param import ParamData, ScalarParam, IndexedParam from pyomo.core.base.set import SetData, SetOperator from pyomo.core.base.constraint import ScalarConstraint, IndexedConstraint @@ -404,7 +404,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_var_node, + VarData: handle_var_node, ScalarObjective: handle_named_expression_node, kernel.objective.objective: handle_named_expression_node, ExternalFunctionExpression: handle_external_function_node, @@ -705,10 +705,8 @@ def latex_printer( 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): + for v in identify_components(temp_comp, [ScalarVar, VarData, IndexedVar]): + if isinstance(v, VarData): v_write = v.parent_component() if v_write not in ComponentSet(variableList): variableList.append(v_write) @@ -1273,7 +1271,7 @@ def get_index_names(st, lcm): rep_dict = {} for ky in reversed(list(latex_component_map)): - if isinstance(ky, (pyo.Var, GeneralVarData)): + if isinstance(ky, (pyo.Var, VarData)): overwrite_value = latex_component_map[ky] if ky not in existing_components: overwrite_value = overwrite_value.replace('_', '\\_') diff --git a/pyomo/contrib/parmest/utils/scenario_tree.py b/pyomo/contrib/parmest/utils/scenario_tree.py index 1062e4a2bf4..f245e053cad 100644 --- a/pyomo/contrib/parmest/utils/scenario_tree.py +++ b/pyomo/contrib/parmest/utils/scenario_tree.py @@ -25,7 +25,7 @@ def build_vardatalist(self, model, varlist=None): """ - Convert a list of pyomo variables to a list of ScalarVar and GeneralVarData. If varlist is none, builds a + Convert a list of pyomo variables to a list of ScalarVar and VarData. If varlist is none, builds a list of all variables in the model. The new list is stored in the vars_to_tighten attribute. By CD Laird Parameters diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 1b22c17cf48..fdc7361e6b8 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -15,7 +15,7 @@ import os from pyomo.core.base.constraint import GeneralConstraintData -from pyomo.core.base.var import GeneralVarData +from pyomo.core.base.var import VarData from pyomo.core.base.param import ParamData from pyomo.core.base.block import BlockData from pyomo.core.base.objective import GeneralObjectiveData @@ -194,9 +194,7 @@ def is_persistent(self): """ return True - def _load_vars( - self, vars_to_load: Optional[Sequence[GeneralVarData]] = None - ) -> NoReturn: + def _load_vars(self, vars_to_load: Optional[Sequence[VarData]] = None) -> NoReturn: """ Load the solution of the primal variables into the value attribute of the variables. @@ -212,19 +210,19 @@ def _load_vars( @abc.abstractmethod def _get_primals( - self, vars_to_load: Optional[Sequence[GeneralVarData]] = None - ) -> Mapping[GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[VarData]] = None + ) -> Mapping[VarData, float]: """ Get mapping of variables to primals. Parameters ---------- - vars_to_load : Optional[Sequence[GeneralVarData]], optional + vars_to_load : Optional[Sequence[VarData]], optional Which vars to be populated into the map. The default is None. Returns ------- - Mapping[GeneralVarData, float] + Mapping[VarData, float] A map of variables to primals. """ raise NotImplementedError( @@ -251,8 +249,8 @@ def _get_duals( raise NotImplementedError(f'{type(self)} does not support the get_duals method') def _get_reduced_costs( - self, vars_to_load: Optional[Sequence[GeneralVarData]] = None - ) -> Mapping[GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[VarData]] = None + ) -> Mapping[VarData, float]: """ Parameters ---------- @@ -282,7 +280,7 @@ def set_objective(self, obj: GeneralObjectiveData): """ @abc.abstractmethod - def add_variables(self, variables: List[GeneralVarData]): + def add_variables(self, variables: List[VarData]): """ Add variables to the model """ @@ -306,7 +304,7 @@ def add_block(self, block: BlockData): """ @abc.abstractmethod - def remove_variables(self, variables: List[GeneralVarData]): + def remove_variables(self, variables: List[VarData]): """ Remove variables from the model """ @@ -330,7 +328,7 @@ def remove_block(self, block: BlockData): """ @abc.abstractmethod - def update_variables(self, variables: List[GeneralVarData]): + def update_variables(self, variables: List[VarData]): """ Update variables on the model """ diff --git a/pyomo/contrib/solver/gurobi.py b/pyomo/contrib/solver/gurobi.py index c5a1c8c1cd8..ff4e93f7635 100644 --- a/pyomo/contrib/solver/gurobi.py +++ b/pyomo/contrib/solver/gurobi.py @@ -22,7 +22,7 @@ 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 GeneralVarData +from pyomo.core.base.var import VarData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.sos import SOSConstraintData from pyomo.core.base.param import ParamData @@ -438,7 +438,7 @@ def _process_domain_and_bounds( return lb, ub, vtype - def _add_variables(self, variables: List[GeneralVarData]): + def _add_variables(self, variables: List[VarData]): var_names = list() vtypes = list() lbs = list() @@ -735,7 +735,7 @@ def _remove_sos_constraints(self, cons: List[SOSConstraintData]): del self._pyomo_sos_to_solver_sos_map[con] self._needs_updated = True - def _remove_variables(self, variables: List[GeneralVarData]): + def _remove_variables(self, variables: List[VarData]): for var in variables: v_id = id(var) if var in self._vars_added_since_update: @@ -750,7 +750,7 @@ def _remove_variables(self, variables: List[GeneralVarData]): def _remove_parameters(self, params: List[ParamData]): pass - def _update_variables(self, variables: List[GeneralVarData]): + def _update_variables(self, variables: List[VarData]): for var in variables: var_id = id(var) if var_id not in self._pyomo_var_to_solver_var_map: @@ -1151,7 +1151,7 @@ def set_var_attr(self, var, attr, val): Parameters ---------- - var: pyomo.core.base.var.GeneralVarData + var: pyomo.core.base.var.VarData The pyomo var for which the corresponding gurobi var attribute should be modified. attr: str @@ -1186,7 +1186,7 @@ def get_var_attr(self, var, attr): Parameters ---------- - var: pyomo.core.base.var.GeneralVarData + var: pyomo.core.base.var.VarData The pyomo var for which the corresponding gurobi var attribute should be retrieved. attr: str diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 7111ec6e972..e4d25e4fea0 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -25,7 +25,7 @@ ) from pyomo.common.tempfiles import TempfileManager from pyomo.common.timing import HierarchicalTimer -from pyomo.core.base.var import GeneralVarData +from pyomo.core.base.var import VarData from pyomo.core.staleflag import StaleFlagManager from pyomo.repn.plugins.nl_writer import NLWriter, NLWriterInfo from pyomo.contrib.solver.base import SolverBase @@ -80,8 +80,8 @@ def __init__( class IpoptSolutionLoader(SolSolutionLoader): def get_reduced_costs( - self, vars_to_load: Optional[Sequence[GeneralVarData]] = None - ) -> Mapping[GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[VarData]] = None + ) -> Mapping[VarData, float]: if self._nl_info is None: raise RuntimeError( 'Solution loader does not currently have a valid solution. Please ' diff --git a/pyomo/contrib/solver/persistent.py b/pyomo/contrib/solver/persistent.py index e98d76b4841..103eb3c622f 100644 --- a/pyomo/contrib/solver/persistent.py +++ b/pyomo/contrib/solver/persistent.py @@ -14,7 +14,7 @@ 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.var import VarData from pyomo.core.base.param import ParamData, Param from pyomo.core.base.objective import GeneralObjectiveData from pyomo.common.collections import ComponentMap @@ -54,10 +54,10 @@ def set_instance(self, model): self.set_objective(None) @abc.abstractmethod - def _add_variables(self, variables: List[GeneralVarData]): + def _add_variables(self, variables: List[VarData]): pass - def add_variables(self, variables: List[GeneralVarData]): + def add_variables(self, variables: List[VarData]): for v in variables: if id(v) in self._referenced_variables: raise ValueError( @@ -87,7 +87,7 @@ def add_parameters(self, params: List[ParamData]): def _add_constraints(self, cons: List[GeneralConstraintData]): pass - def _check_for_new_vars(self, variables: List[GeneralVarData]): + def _check_for_new_vars(self, variables: List[VarData]): new_vars = {} for v in variables: v_id = id(v) @@ -95,7 +95,7 @@ def _check_for_new_vars(self, variables: List[GeneralVarData]): new_vars[v_id] = v self.add_variables(list(new_vars.values())) - def _check_to_remove_vars(self, variables: List[GeneralVarData]): + def _check_to_remove_vars(self, variables: List[VarData]): vars_to_remove = {} for v in variables: v_id = id(v) @@ -250,10 +250,10 @@ def remove_sos_constraints(self, cons: List[SOSConstraintData]): del self._vars_referenced_by_con[con] @abc.abstractmethod - def _remove_variables(self, variables: List[GeneralVarData]): + def _remove_variables(self, variables: List[VarData]): pass - def remove_variables(self, variables: List[GeneralVarData]): + def remove_variables(self, variables: List[VarData]): self._remove_variables(variables) for v in variables: v_id = id(v) @@ -309,10 +309,10 @@ def remove_block(self, block): ) @abc.abstractmethod - def _update_variables(self, variables: List[GeneralVarData]): + def _update_variables(self, variables: List[VarData]): pass - def update_variables(self, variables: List[GeneralVarData]): + def update_variables(self, variables: List[VarData]): for v in variables: self._vars[id(v)] = ( v, diff --git a/pyomo/contrib/solver/solution.py b/pyomo/contrib/solver/solution.py index 3f327c1f280..e089e621f1f 100644 --- a/pyomo/contrib/solver/solution.py +++ b/pyomo/contrib/solver/solution.py @@ -13,7 +13,7 @@ from typing import Sequence, Dict, Optional, Mapping, NoReturn from pyomo.core.base.constraint import GeneralConstraintData -from pyomo.core.base.var import GeneralVarData +from pyomo.core.base.var import VarData from pyomo.core.expr import value from pyomo.common.collections import ComponentMap from pyomo.common.errors import DeveloperError @@ -30,9 +30,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: + def load_vars(self, vars_to_load: Optional[Sequence[VarData]] = None) -> NoReturn: """ Load the solution of the primal variables into the value attribute of the variables. @@ -49,8 +47,8 @@ def load_vars( @abc.abstractmethod def get_primals( - self, vars_to_load: Optional[Sequence[GeneralVarData]] = None - ) -> Mapping[GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[VarData]] = None + ) -> Mapping[VarData, float]: """ Returns a ComponentMap mapping variable to var value. @@ -86,8 +84,8 @@ def get_duals( raise NotImplementedError(f'{type(self)} does not support the get_duals method') def get_reduced_costs( - self, vars_to_load: Optional[Sequence[GeneralVarData]] = None - ) -> Mapping[GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[VarData]] = None + ) -> Mapping[VarData, float]: """ Returns a ComponentMap mapping variable to reduced cost. @@ -127,8 +125,8 @@ def get_duals( 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, vars_to_load: Optional[Sequence[VarData]] = None + ) -> Mapping[VarData, float]: self._assert_solution_still_valid() return self._solver._get_reduced_costs(vars_to_load=vars_to_load) @@ -141,9 +139,7 @@ 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: + def load_vars(self, vars_to_load: Optional[Sequence[VarData]] = None) -> NoReturn: if self._nl_info is None: raise RuntimeError( 'Solution loader does not currently have a valid solution. Please ' @@ -169,8 +165,8 @@ def load_vars( StaleFlagManager.mark_all_as_stale(delayed=True) def get_primals( - self, vars_to_load: Optional[Sequence[GeneralVarData]] = None - ) -> Mapping[GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[VarData]] = None + ) -> Mapping[VarData, float]: if self._nl_info is None: raise RuntimeError( 'Solution loader does not currently have a valid solution. Please ' diff --git a/pyomo/contrib/solver/tests/unit/test_results.py b/pyomo/contrib/solver/tests/unit/test_results.py index 608af04a0ed..6c178d80298 100644 --- a/pyomo/contrib/solver/tests/unit/test_results.py +++ b/pyomo/contrib/solver/tests/unit/test_results.py @@ -16,7 +16,7 @@ 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.core.base.var import VarData from pyomo.common.collections import ComponentMap from pyomo.contrib.solver import results from pyomo.contrib.solver import solution @@ -51,8 +51,8 @@ def __init__( self._reduced_costs = reduced_costs def get_primals( - self, vars_to_load: Optional[Sequence[GeneralVarData]] = None - ) -> Mapping[GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[VarData]] = None + ) -> Mapping[VarData, float]: if self._primals is None: raise RuntimeError( 'Solution loader does not currently have a valid solution. Please ' @@ -84,8 +84,8 @@ def get_duals( return duals def get_reduced_costs( - self, vars_to_load: Optional[Sequence[GeneralVarData]] = None - ) -> Mapping[GeneralVarData, float]: + self, vars_to_load: Optional[Sequence[VarData]] = None + ) -> Mapping[VarData, float]: if self._reduced_costs is None: raise RuntimeError( 'Solution loader does not currently have valid reduced costs. Please ' diff --git a/pyomo/contrib/trustregion/tests/test_interface.py b/pyomo/contrib/trustregion/tests/test_interface.py index 64f76eb887d..0922ccf950b 100644 --- a/pyomo/contrib/trustregion/tests/test_interface.py +++ b/pyomo/contrib/trustregion/tests/test_interface.py @@ -33,7 +33,7 @@ cos, SolverFactory, ) -from pyomo.core.base.var import GeneralVarData +from pyomo.core.base.var import VarData from pyomo.core.expr.numeric_expr import ExternalFunctionExpression from pyomo.core.expr.visitor import identify_variables from pyomo.contrib.trustregion.interface import TRFInterface @@ -158,7 +158,7 @@ def test_replaceExternalFunctionsWithVariables(self): self.assertIsInstance(k, ExternalFunctionExpression) self.assertIn(str(self.interface.model.x[0]), str(k)) self.assertIn(str(self.interface.model.x[1]), str(k)) - self.assertIsInstance(i, GeneralVarData) + self.assertIsInstance(i, VarData) self.assertEqual(i, self.interface.data.ef_outputs[1]) for i, k in self.interface.data.basis_expressions.items(): self.assertEqual(k, 0) diff --git a/pyomo/core/base/__init__.py b/pyomo/core/base/__init__.py index 9a06a3e02bb..6851fe4cda0 100644 --- a/pyomo/core/base/__init__.py +++ b/pyomo/core/base/__init__.py @@ -40,7 +40,6 @@ from pyomo.core.base.componentuid import ComponentUID from pyomo.core.base.config import PyomoOptions from pyomo.core.base.enums import SortComponents, TraversalStrategy -from pyomo.core.base.instance2dat import instance2dat from pyomo.core.base.label import ( CuidLabeler, CounterLabeler, @@ -146,7 +145,9 @@ active_import_suffix_generator, Suffix, ) -from pyomo.core.base.var import Var, VarData, GeneralVarData, ScalarVar, VarList +from pyomo.core.base.var import Var, VarData, VarData, ScalarVar, VarList + +from pyomo.core.base.instance2dat import instance2dat # # These APIs are deprecated and should be removed in the near future @@ -163,12 +164,14 @@ 'SimpleBooleanVar', 'pyomo.core.base.boolean_var.SimpleBooleanVar', version='6.0' ) # Historically, only a subset of "private" component data classes were imported here +relocated_module_attribute( + f'_GeneralVarData', f'pyomo.core.base.VarData', version='6.7.2.dev0' +) for _cdata in ( 'ConstraintData', 'LogicalConstraintData', 'ExpressionData', 'VarData', - 'GeneralVarData', 'GeneralBooleanVarData', 'BooleanVarData', 'ObjectiveData', diff --git a/pyomo/core/base/component.py b/pyomo/core/base/component.py index 7a4b7e40aab..65844379eca 100644 --- a/pyomo/core/base/component.py +++ b/pyomo/core/base/component.py @@ -805,7 +805,7 @@ class ComponentData(_ComponentBase): # classes: BooleanVarData, ConnectorData, ConstraintData, # GeneralExpressionData, LogicalConstraintData, # GeneralLogicalConstraintData, GeneralObjectiveData, - # ParamData,GeneralVarData, GeneralBooleanVarData, DisjunctionData, + # ParamData,VarData, GeneralBooleanVarData, DisjunctionData, # ArcData, PortData, _LinearConstraintData, and # _LinearMatrixConstraintData. Changes made here need to be made in those # constructors as well! diff --git a/pyomo/core/expr/calculus/derivatives.py b/pyomo/core/expr/calculus/derivatives.py index 5df1fd3c65e..69fe4969938 100644 --- a/pyomo/core/expr/calculus/derivatives.py +++ b/pyomo/core/expr/calculus/derivatives.py @@ -39,11 +39,11 @@ def differentiate(expr, wrt=None, wrt_list=None, mode=Modes.reverse_numeric): ---------- expr: pyomo.core.expr.numeric_expr.NumericExpression The expression to differentiate - wrt: pyomo.core.base.var.GeneralVarData + wrt: pyomo.core.base.var.VarData If specified, this function will return the derivative with - respect to wrt. wrt is normally a GeneralVarData, but could + respect to wrt. wrt is normally a VarData, but could also be a ParamData. wrt and wrt_list cannot both be specified. - wrt_list: list of pyomo.core.base.var.GeneralVarData + wrt_list: list of pyomo.core.base.var.VarData If specified, this function will return the derivative with respect to each element in wrt_list. A list will be returned where the values are the derivatives with respect to the diff --git a/pyomo/core/tests/transform/test_add_slacks.py b/pyomo/core/tests/transform/test_add_slacks.py index d66d6fba79e..b395237b8e4 100644 --- a/pyomo/core/tests/transform/test_add_slacks.py +++ b/pyomo/core/tests/transform/test_add_slacks.py @@ -330,7 +330,7 @@ def test_error_for_non_constraint_noniterable_target(self): self.assertRaisesRegex( ValueError, "Expected Constraint or list of Constraints.\n\tReceived " - "", + "", TransformationFactory('core.add_slack_variables').apply_to, m, targets=m.indexedVar[1], diff --git a/pyomo/core/tests/unit/test_dict_objects.py b/pyomo/core/tests/unit/test_dict_objects.py index f2c3cad8cc3..0dc5cacd216 100644 --- a/pyomo/core/tests/unit/test_dict_objects.py +++ b/pyomo/core/tests/unit/test_dict_objects.py @@ -17,7 +17,7 @@ ObjectiveDict, ExpressionDict, ) -from pyomo.core.base.var import GeneralVarData +from pyomo.core.base.var import VarData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.objective import GeneralObjectiveData from pyomo.core.base.expression import GeneralExpressionData @@ -348,10 +348,10 @@ def test_active(self): class TestVarDict(_TestComponentDictBase, unittest.TestCase): - # Note: the updated GeneralVarData class only takes an optional + # Note: the updated VarData class only takes an optional # parent argument (you no longer pass the domain in) _ctype = VarDict - _cdatatype = lambda self, arg: GeneralVarData() + _cdatatype = lambda self, arg: VarData() def setUp(self): _TestComponentDictBase.setUp(self) diff --git a/pyomo/core/tests/unit/test_list_objects.py b/pyomo/core/tests/unit/test_list_objects.py index fcc83a95a06..f98b5279fc5 100644 --- a/pyomo/core/tests/unit/test_list_objects.py +++ b/pyomo/core/tests/unit/test_list_objects.py @@ -17,7 +17,7 @@ XObjectiveList, XExpressionList, ) -from pyomo.core.base.var import GeneralVarData +from pyomo.core.base.var import VarData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.objective import GeneralObjectiveData from pyomo.core.base.expression import GeneralExpressionData @@ -365,10 +365,10 @@ def test_active(self): class TestVarList(_TestComponentListBase, unittest.TestCase): - # Note: the updated GeneralVarData class only takes an optional + # Note: the updated VarData class only takes an optional # parent argument (you no longer pass the domain in) _ctype = XVarList - _cdatatype = lambda self, arg: GeneralVarData() + _cdatatype = lambda self, arg: VarData() def setUp(self): _TestComponentListBase.setUp(self) diff --git a/pyomo/core/tests/unit/test_numeric_expr.py b/pyomo/core/tests/unit/test_numeric_expr.py index 8e5e43eac9c..efb01e6d6ce 100644 --- a/pyomo/core/tests/unit/test_numeric_expr.py +++ b/pyomo/core/tests/unit/test_numeric_expr.py @@ -112,7 +112,7 @@ from pyomo.core.base.label import NumericLabeler from pyomo.core.expr.template_expr import IndexTemplate from pyomo.core.expr import expr_common -from pyomo.core.base.var import GeneralVarData +from pyomo.core.base.var import VarData from pyomo.repn import generate_standard_repn from pyomo.core.expr.numvalue import NumericValue @@ -294,7 +294,7 @@ def value_check(self, exp, val): class TestExpression_EvaluateVarData(TestExpression_EvaluateNumericValue): def create(self, val, domain): - tmp = GeneralVarData() + tmp = VarData() tmp.domain = domain tmp.value = val return tmp diff --git a/pyomo/core/tests/unit/test_reference.py b/pyomo/core/tests/unit/test_reference.py index 4fa2f4944e9..7370881612f 100644 --- a/pyomo/core/tests/unit/test_reference.py +++ b/pyomo/core/tests/unit/test_reference.py @@ -800,8 +800,8 @@ def test_reference_indexedcomponent_pprint(self): buf.getvalue(), """r : Size=2, Index={1, 2}, ReferenceTo=x Key : Object - 1 : - 2 : + 1 : + 2 : """, ) m.s = Reference(m.x[:, ...], ctype=IndexedComponent) @@ -811,8 +811,8 @@ def test_reference_indexedcomponent_pprint(self): buf.getvalue(), """s : Size=2, Index={1, 2}, ReferenceTo=x[:, ...] Key : Object - 1 : - 2 : + 1 : + 2 : """, ) @@ -1357,8 +1357,8 @@ def test_pprint_nonfinite_sets_ctypeNone(self): 1 IndexedComponent Declarations ref : Size=2, Index=NonNegativeIntegers, ReferenceTo=v Key : Object - 3 : - 5 : + 3 : + 5 : 2 Declarations: v ref """.strip(), diff --git a/pyomo/repn/standard_repn.py b/pyomo/repn/standard_repn.py index 5786d078385..2f5a413e963 100644 --- a/pyomo/repn/standard_repn.py +++ b/pyomo/repn/standard_repn.py @@ -22,7 +22,7 @@ from pyomo.core.base.objective import GeneralObjectiveData, ScalarObjective from pyomo.core.base import ExpressionData, Expression from pyomo.core.base.expression import ScalarExpression, GeneralExpressionData -from pyomo.core.base.var import ScalarVar, Var, GeneralVarData, value +from pyomo.core.base.var import ScalarVar, Var, VarData, value from pyomo.core.base.param import ScalarParam, ParamData from pyomo.core.kernel.expression import expression, noclone from pyomo.core.kernel.variable import IVariable, variable @@ -1143,7 +1143,7 @@ def _collect_external_fn(exp, multiplier, idMap, compute_values, verbose, quadra # param.Param : _collect_linear_const, # parameter : _collect_linear_const, NumericConstant: _collect_const, - GeneralVarData: _collect_var, + VarData: _collect_var, ScalarVar: _collect_var, Var: _collect_var, variable: _collect_var, @@ -1542,7 +1542,7 @@ def _linear_collect_pow(exp, multiplier, idMap, compute_values, verbose, coef): ##param.ScalarParam : _collect_linear_const, ##param.Param : _collect_linear_const, ##parameter : _collect_linear_const, - GeneralVarData : _linear_collect_var, + VarData : _linear_collect_var, ScalarVar : _linear_collect_var, Var : _linear_collect_var, variable : _linear_collect_var, diff --git a/pyomo/solvers/plugins/solvers/gurobi_persistent.py b/pyomo/solvers/plugins/solvers/gurobi_persistent.py index 97a3533c3f9..17ce33fd95f 100644 --- a/pyomo/solvers/plugins/solvers/gurobi_persistent.py +++ b/pyomo/solvers/plugins/solvers/gurobi_persistent.py @@ -192,7 +192,7 @@ def set_var_attr(self, var, attr, val): Parameters ---------- - con: pyomo.core.base.var.GeneralVarData + con: pyomo.core.base.var.VarData The pyomo var for which the corresponding gurobi var attribute should be modified. attr: str @@ -342,7 +342,7 @@ def get_var_attr(self, var, attr): Parameters ---------- - var: pyomo.core.base.var.GeneralVarData + var: pyomo.core.base.var.VarData The pyomo var for which the corresponding gurobi var attribute should be retrieved. attr: str diff --git a/pyomo/util/report_scaling.py b/pyomo/util/report_scaling.py index 265564bf12d..7619662c482 100644 --- a/pyomo/util/report_scaling.py +++ b/pyomo/util/report_scaling.py @@ -13,7 +13,7 @@ import math from pyomo.core.base.block import BlockData from pyomo.common.collections import ComponentSet -from pyomo.core.base.var import GeneralVarData +from pyomo.core.base.var import VarData from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr from pyomo.core.expr.calculus.diff_with_pyomo import reverse_sd import logging @@ -73,7 +73,7 @@ def _check_coefficients( ): ders = reverse_sd(expr) for _v, _der in ders.items(): - if isinstance(_v, GeneralVarData): + if isinstance(_v, VarData): if _v.is_fixed(): continue der_lb, der_ub = compute_bounds_on_expr(_der) From 458c84e5acabcb95186c780238ec7f951b4e54b4 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 20:29:24 -0600 Subject: [PATCH 1472/1797] Merge GeneralBooleanVarData into BooelanVarData class --- pyomo/core/base/boolean_var.py | 205 +++++++++++++-------------------- 1 file changed, 78 insertions(+), 127 deletions(-) diff --git a/pyomo/core/base/boolean_var.py b/pyomo/core/base/boolean_var.py index 925dca530a7..b6a25cd8f27 100644 --- a/pyomo/core/base/boolean_var.py +++ b/pyomo/core/base/boolean_var.py @@ -68,27 +68,61 @@ def __setstate__(self, state): self._boolvar = weakref_ref(state) +def _associated_binary_mapper(encode, val): + if val is None: + return None + if encode: + if val.__class__ is not _DeprecatedImplicitAssociatedBinaryVariable: + return val() + else: + if val.__class__ is not _DeprecatedImplicitAssociatedBinaryVariable: + return weakref_ref(val) + return val + + class BooleanVarData(ComponentData, BooleanValue): """ - This class defines the data for a single variable. + This class defines the data for a single Boolean variable. Constructor Arguments: component The BooleanVar object that owns this data. + Public Class Attributes: + domain The domain of this variable. fixed If True, then this variable is treated as a fixed constant in the model. stale A Boolean indicating whether the value of this variable is - legitimate. This value is true if the value should + legitimiate. This value is true if the value should be considered legitimate for purposes of reporting or other interrogation. value The numeric value of this variable. + + The domain attribute is a property because it is + too widely accessed directly to enforce explicit getter/setter + methods and we need to deter directly modifying or accessing + these attributes in certain cases. """ - __slots__ = () + __slots__ = ('_value', 'fixed', '_stale', '_associated_binary') + __autoslot_mappers__ = { + '_associated_binary': _associated_binary_mapper, + '_stale': StaleFlagManager.stale_mapper, + } def __init__(self, component=None): + # + # These lines represent in-lining of the + # following constructors: + # - BooleanVarData + # - ComponentData + # - BooleanValue self._component = weakref_ref(component) if (component is not None) else None self._index = NOTSET + self._value = None + self.fixed = False + self._stale = 0 # True + + self._associated_binary = None def is_fixed(self): """Returns True if this variable is fixed, otherwise returns False.""" @@ -132,118 +166,6 @@ def __call__(self, exception=True): """Compute the value of this variable.""" return self.value - @property - def value(self): - """Return the value for this variable.""" - raise NotImplementedError - - @property - def domain(self): - """Return the domain for this variable.""" - raise NotImplementedError - - @property - def fixed(self): - """Return the fixed indicator for this variable.""" - raise NotImplementedError - - @property - def stale(self): - """Return the stale indicator for this variable.""" - raise NotImplementedError - - def fix(self, value=NOTSET, skip_validation=False): - """Fix the value of this variable (treat as nonvariable) - - This sets the `fixed` indicator to True. If ``value`` is - provided, the value (and the ``skip_validation`` flag) are first - passed to :py:meth:`set_value()`. - - """ - self.fixed = True - if value is not NOTSET: - self.set_value(value, skip_validation) - - def unfix(self): - """Unfix this variable (treat as variable) - - This sets the `fixed` indicator to False. - - """ - self.fixed = False - - def free(self): - """Alias for :py:meth:`unfix`""" - return self.unfix() - - -class _BooleanVarData(metaclass=RenamedClass): - __renamed__new_class__ = BooleanVarData - __renamed__version__ = '6.7.2.dev0' - - -def _associated_binary_mapper(encode, val): - if val is None: - return None - if encode: - if val.__class__ is not _DeprecatedImplicitAssociatedBinaryVariable: - return val() - else: - if val.__class__ is not _DeprecatedImplicitAssociatedBinaryVariable: - return weakref_ref(val) - return val - - -class GeneralBooleanVarData(BooleanVarData): - """ - This class defines the data for a single Boolean variable. - - Constructor Arguments: - component The BooleanVar object that owns this data. - - Public Class Attributes: - domain The domain of this variable. - fixed If True, then this variable is treated as a - fixed constant in the model. - stale A Boolean indicating whether the value of this variable is - legitimiate. This value is true if the value should - be considered legitimate for purposes of reporting or - other interrogation. - value The numeric value of this variable. - - The domain attribute is a property because it is - too widely accessed directly to enforce explicit getter/setter - methods and we need to deter directly modifying or accessing - these attributes in certain cases. - """ - - __slots__ = ('_value', 'fixed', '_stale', '_associated_binary') - __autoslot_mappers__ = { - '_associated_binary': _associated_binary_mapper, - '_stale': StaleFlagManager.stale_mapper, - } - - def __init__(self, component=None): - # - # These lines represent in-lining of the - # following constructors: - # - BooleanVarData - # - ComponentData - # - BooleanValue - self._component = weakref_ref(component) if (component is not None) else None - self._index = NOTSET - self._value = None - self.fixed = False - self._stale = 0 # True - - self._associated_binary = None - - # - # Abstract Interface - # - - # value is an attribute - @property def value(self): """Return (or set) the value for this variable.""" @@ -271,13 +193,13 @@ def stale(self, val): def get_associated_binary(self): """Get the binary VarData associated with this - GeneralBooleanVarData""" + BooleanVarData""" return ( self._associated_binary() if self._associated_binary is not None else None ) def associate_binary_var(self, binary_var): - """Associate a binary VarData to this GeneralBooleanVarData""" + """Associate a binary VarData to this BooleanVarData""" if ( self._associated_binary is not None and type(self._associated_binary) @@ -299,9 +221,38 @@ def associate_binary_var(self, binary_var): if binary_var is not None: self._associated_binary = weakref_ref(binary_var) + def fix(self, value=NOTSET, skip_validation=False): + """Fix the value of this variable (treat as nonvariable) + + This sets the `fixed` indicator to True. If ``value`` is + provided, the value (and the ``skip_validation`` flag) are first + passed to :py:meth:`set_value()`. + + """ + self.fixed = True + if value is not NOTSET: + self.set_value(value, skip_validation) + + def unfix(self): + """Unfix this variable (treat as variable) + + This sets the `fixed` indicator to False. -class _GeneralBooleanVarData(metaclass=RenamedClass): - __renamed__new_class__ = GeneralBooleanVarData + """ + self.fixed = False + + def free(self): + """Alias for :py:meth:`unfix`""" + return self.unfix() + + +class _BooleanVarData(metaclass=RenamedClass): + __renamed__new_class__ = BooleanVarData + __renamed__version__ = '6.7.2.dev0' + + +class _BooleanVarData(metaclass=RenamedClass): + __renamed__new_class__ = BooleanVarData __renamed__version__ = '6.7.2.dev0' @@ -319,7 +270,7 @@ class BooleanVar(IndexedComponent): to True. """ - _ComponentDataClass = GeneralBooleanVarData + _ComponentDataClass = BooleanVarData def __new__(cls, *args, **kwds): if cls != BooleanVar: @@ -511,11 +462,11 @@ def _pprint(self): ) -class ScalarBooleanVar(GeneralBooleanVarData, BooleanVar): +class ScalarBooleanVar(BooleanVarData, BooleanVar): """A single variable.""" def __init__(self, *args, **kwd): - GeneralBooleanVarData.__init__(self, component=self) + BooleanVarData.__init__(self, component=self) BooleanVar.__init__(self, *args, **kwd) self._index = UnindexedComponent_index @@ -531,7 +482,7 @@ def __init__(self, *args, **kwd): def value(self): """Return the value for this variable.""" if self._constructed: - return GeneralBooleanVarData.value.fget(self) + return BooleanVarData.value.fget(self) raise ValueError( "Accessing the value of variable '%s' " "before the Var has been constructed (there " @@ -542,7 +493,7 @@ def value(self): def value(self, val): """Set the value for this variable.""" if self._constructed: - return GeneralBooleanVarData.value.fset(self, val) + return BooleanVarData.value.fset(self, val) raise ValueError( "Setting the value of variable '%s' " "before the Var has been constructed (there " @@ -551,7 +502,7 @@ def value(self, val): @property def domain(self): - return GeneralBooleanVarData.domain.fget(self) + return BooleanVarData.domain.fget(self) def fix(self, value=NOTSET, skip_validation=False): """ @@ -559,7 +510,7 @@ def fix(self, value=NOTSET, skip_validation=False): indicating the variable should be fixed at its current value. """ if self._constructed: - return GeneralBooleanVarData.fix(self, value, skip_validation) + return BooleanVarData.fix(self, value, skip_validation) raise ValueError( "Fixing variable '%s' " "before the Var has been constructed (there " @@ -569,7 +520,7 @@ def fix(self, value=NOTSET, skip_validation=False): def unfix(self): """Sets the fixed indicator to False.""" if self._constructed: - return GeneralBooleanVarData.unfix(self) + return BooleanVarData.unfix(self) raise ValueError( "Freeing variable '%s' " "before the Var has been constructed (there " From 015a4f859d63d9cdf615a488490301f88a2c96cc Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 20:30:30 -0600 Subject: [PATCH 1473/1797] Update references from GeneralBooleanVarData to BooelanVarData --- pyomo/contrib/cp/repn/docplex_writer.py | 4 ++-- pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py | 2 +- pyomo/core/base/__init__.py | 6 ++++-- pyomo/core/base/boolean_var.py | 2 +- pyomo/core/base/component.py | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index 221fd61af5b..1af153910c0 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -60,7 +60,7 @@ ) from pyomo.core.base.boolean_var import ( ScalarBooleanVar, - GeneralBooleanVarData, + BooleanVarData, IndexedBooleanVar, ) from pyomo.core.base.expression import ScalarExpression, GeneralExpressionData @@ -964,7 +964,7 @@ class LogicalToDoCplex(StreamBasedExpressionVisitor): VarData: _before_var, IndexedVar: _before_indexed_var, ScalarBooleanVar: _before_boolean_var, - GeneralBooleanVarData: _before_boolean_var, + BooleanVarData: _before_boolean_var, IndexedBooleanVar: _before_indexed_boolean_var, GeneralExpressionData: _before_named_expression, ScalarExpression: _before_named_expression, diff --git a/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py b/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py index d9483c0ed14..0c493b89321 100644 --- a/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py +++ b/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py @@ -209,7 +209,7 @@ def _dispatch_atmost(visitor, node, *args): _before_child_dispatcher = {} _before_child_dispatcher[BV.ScalarBooleanVar] = _dispatch_boolean_var -_before_child_dispatcher[BV.GeneralBooleanVarData] = _dispatch_boolean_var +_before_child_dispatcher[BV.BooleanVarData] = _dispatch_boolean_var _before_child_dispatcher[AutoLinkedBooleanVar] = _dispatch_boolean_var _before_child_dispatcher[ParamData] = _dispatch_param _before_child_dispatcher[ScalarParam] = _dispatch_param diff --git a/pyomo/core/base/__init__.py b/pyomo/core/base/__init__.py index 6851fe4cda0..bcc2a0e0e02 100644 --- a/pyomo/core/base/__init__.py +++ b/pyomo/core/base/__init__.py @@ -84,7 +84,7 @@ from pyomo.core.base.boolean_var import ( BooleanVar, BooleanVarData, - GeneralBooleanVarData, + BooleanVarData, BooleanVarList, ScalarBooleanVar, ) @@ -167,12 +167,14 @@ relocated_module_attribute( f'_GeneralVarData', f'pyomo.core.base.VarData', version='6.7.2.dev0' ) +relocated_module_attribute( + f'_GeneralBooleanVarData', f'pyomo.core.base.BooleanVarData', version='6.7.2.dev0' +) for _cdata in ( 'ConstraintData', 'LogicalConstraintData', 'ExpressionData', 'VarData', - 'GeneralBooleanVarData', 'BooleanVarData', 'ObjectiveData', ): diff --git a/pyomo/core/base/boolean_var.py b/pyomo/core/base/boolean_var.py index b6a25cd8f27..98761dee536 100644 --- a/pyomo/core/base/boolean_var.py +++ b/pyomo/core/base/boolean_var.py @@ -251,7 +251,7 @@ class _BooleanVarData(metaclass=RenamedClass): __renamed__version__ = '6.7.2.dev0' -class _BooleanVarData(metaclass=RenamedClass): +class _GeneralBooleanVarData(metaclass=RenamedClass): __renamed__new_class__ = BooleanVarData __renamed__version__ = '6.7.2.dev0' diff --git a/pyomo/core/base/component.py b/pyomo/core/base/component.py index 65844379eca..1c73809a25a 100644 --- a/pyomo/core/base/component.py +++ b/pyomo/core/base/component.py @@ -805,7 +805,7 @@ class ComponentData(_ComponentBase): # classes: BooleanVarData, ConnectorData, ConstraintData, # GeneralExpressionData, LogicalConstraintData, # GeneralLogicalConstraintData, GeneralObjectiveData, - # ParamData,VarData, GeneralBooleanVarData, DisjunctionData, + # ParamData,VarData, BooleanVarData, DisjunctionData, # ArcData, PortData, _LinearConstraintData, and # _LinearMatrixConstraintData. Changes made here need to be made in those # constructors as well! From 0cbfcb7418310b2dbb97aa6e915d4570e02c1d37 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 20:38:00 -0600 Subject: [PATCH 1474/1797] Restore deprecation path for GeneralVarData --- pyomo/core/base/var.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/core/base/var.py b/pyomo/core/base/var.py index b1634b61c44..8870fc5b09c 100644 --- a/pyomo/core/base/var.py +++ b/pyomo/core/base/var.py @@ -575,7 +575,7 @@ class _VarData(metaclass=RenamedClass): __renamed__version__ = '6.7.2.dev0' -class _VarData(metaclass=RenamedClass): +class _GeneralVarData(metaclass=RenamedClass): __renamed__new_class__ = VarData __renamed__version__ = '6.7.2.dev0' From adfe72b6ff4a6900f98d77dad7cac83da7e41250 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 20:38:35 -0600 Subject: [PATCH 1475/1797] Merge GeneralLogicalConstraintData into LogicalConstratintData --- pyomo/core/base/component.py | 2 +- pyomo/core/base/logical_constraint.py | 77 ++++----------------------- 2 files changed, 11 insertions(+), 68 deletions(-) diff --git a/pyomo/core/base/component.py b/pyomo/core/base/component.py index 1c73809a25a..3dd4d47046d 100644 --- a/pyomo/core/base/component.py +++ b/pyomo/core/base/component.py @@ -804,7 +804,7 @@ class ComponentData(_ComponentBase): # NOTE: This constructor is in-lined in the constructors for the following # classes: BooleanVarData, ConnectorData, ConstraintData, # GeneralExpressionData, LogicalConstraintData, - # GeneralLogicalConstraintData, GeneralObjectiveData, + # LogicalConstraintData, GeneralObjectiveData, # ParamData,VarData, BooleanVarData, DisjunctionData, # ArcData, PortData, _LinearConstraintData, and # _LinearMatrixConstraintData. Changes made here need to be made in those diff --git a/pyomo/core/base/logical_constraint.py b/pyomo/core/base/logical_constraint.py index 23a422705df..1daa5f83e90 100644 --- a/pyomo/core/base/logical_constraint.py +++ b/pyomo/core/base/logical_constraint.py @@ -43,68 +43,6 @@ class LogicalConstraintData(ActiveComponentData): - """ - This class defines the data for a single logical constraint. - - It functions as a pure interface. - - Constructor arguments: - component The LogicalConstraint object that owns this data. - - Public class attributes: - active A boolean that is true if this statement is - active in the model. - body The Pyomo logical expression for this statement - - Private class attributes: - _component The statement component. - _active A boolean that indicates whether this data is active - """ - - __slots__ = () - - def __init__(self, component=None): - # - # These lines represent in-lining of the - # following constructors: - # - ActiveComponentData - # - ComponentData - self._component = weakref_ref(component) if (component is not None) else None - self._index = NOTSET - self._active = True - - # - # Interface - # - def __call__(self, exception=True): - """Compute the value of the body of this logical constraint.""" - if self.body is None: - return None - return self.body(exception=exception) - - # - # Abstract Interface - # - @property - def expr(self): - """Get the expression on this logical constraint.""" - raise NotImplementedError - - def set_value(self, expr): - """Set the expression on this logical constraint.""" - raise NotImplementedError - - def get_value(self): - """Get the expression on this logical constraint.""" - raise NotImplementedError - - -class _LogicalConstraintData(metaclass=RenamedClass): - __renamed__new_class__ = LogicalConstraintData - __renamed__version__ = '6.7.2.dev0' - - -class GeneralLogicalConstraintData(LogicalConstraintData): """ This class defines the data for a single general logical constraint. @@ -178,8 +116,13 @@ def get_value(self): return self._expr +class _LogicalConstraintData(metaclass=RenamedClass): + __renamed__new_class__ = LogicalConstraintData + __renamed__version__ = '6.7.2.dev0' + + class _GeneralLogicalConstraintData(metaclass=RenamedClass): - __renamed__new_class__ = GeneralLogicalConstraintData + __renamed__new_class__ = LogicalConstraintData __renamed__version__ = '6.7.2.dev0' @@ -225,7 +168,7 @@ class LogicalConstraint(ActiveIndexedComponent): The class type for the derived subclass """ - _ComponentDataClass = GeneralLogicalConstraintData + _ComponentDataClass = LogicalConstraintData class Infeasible(object): pass @@ -419,14 +362,14 @@ def _check_skip_add(self, index, expr): return expr -class ScalarLogicalConstraint(GeneralLogicalConstraintData, LogicalConstraint): +class ScalarLogicalConstraint(LogicalConstraintData, LogicalConstraint): """ ScalarLogicalConstraint is the implementation representing a single, non-indexed logical constraint. """ def __init__(self, *args, **kwds): - GeneralLogicalConstraintData.__init__(self, component=self, expr=None) + LogicalConstraintData.__init__(self, component=self, expr=None) LogicalConstraint.__init__(self, *args, **kwds) self._index = UnindexedComponent_index @@ -446,7 +389,7 @@ def body(self): "an expression. There is currently " "nothing to access." % self.name ) - return GeneralLogicalConstraintData.body.fget(self) + return LogicalConstraintData.body.fget(self) raise ValueError( "Accessing the body of logical constraint '%s' " "before the LogicalConstraint has been constructed (there " From 2f1d4a0387995b741028f96e0e05aadc8c4a30a0 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 20:44:43 -0600 Subject: [PATCH 1476/1797] NFC: apply black --- pyomo/repn/tests/test_standard_form.py | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/pyomo/repn/tests/test_standard_form.py b/pyomo/repn/tests/test_standard_form.py index 9dee2b1d25d..591703e6ae8 100644 --- a/pyomo/repn/tests/test_standard_form.py +++ b/pyomo/repn/tests/test_standard_form.py @@ -245,33 +245,21 @@ def test_alternative_forms(self): m, mixed_form=True, column_order=col_order ) - self.assertEqual(repn.rows, [(m.c, -1), (m.d, 1), (m.e, 1), (m.e, -1), (m.f, 0)]) self.assertEqual( - list(map(str, repn.x)), - ['x', 'y[0]', 'y[1]', 'y[3]'], + repn.rows, [(m.c, -1), (m.d, 1), (m.e, 1), (m.e, -1), (m.f, 0)] ) + self.assertEqual(list(map(str, repn.x)), ['x', 'y[0]', 'y[1]', 'y[3]']) self.assertEqual( - list(v.bounds for v in repn.x), - [(None, None), (0, 10), (-5, 10), (-5, -2)], + list(v.bounds for v in repn.x), [(None, None), (0, 10), (-5, 10), (-5, -2)] ) ref = np.array( - [ - [1, 0, 2, 0], - [0, 0, 1, 4], - [0, 1, 6, 0], - [0, 1, 6, 0], - [1, 1, 0, 0], - ] + [[1, 0, 2, 0], [0, 0, 1, 4], [0, 1, 6, 0], [0, 1, 6, 0], [1, 1, 0, 0]] ) self.assertTrue(np.all(repn.A == ref)) print(repn) print(repn.b) self.assertTrue(np.all(repn.b == np.array([3, 5, 6, -3, 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]]))) # Note that the solution is a mix of inequality and equality constraints # self._verify_solution(soln, repn, False) From a27b8791f864546444824e4d803c86e48b6de606 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 21:33:32 -0600 Subject: [PATCH 1477/1797] Merge GeneralObjectiveData into ObjectiveData --- pyomo/core/base/objective.py | 58 ++++++++---------------------------- 1 file changed, 12 insertions(+), 46 deletions(-) diff --git a/pyomo/core/base/objective.py b/pyomo/core/base/objective.py index 58cb198e1ae..e4956748b6c 100644 --- a/pyomo/core/base/objective.py +++ b/pyomo/core/base/objective.py @@ -81,51 +81,8 @@ def O_rule(model, i, j): return rule_wrapper(rule, {None: ObjectiveList.End}) -# -# This class is a pure interface -# - - -class ObjectiveData(ExpressionData): - """ - This class defines the data for a single objective. - - Public class attributes: - expr The Pyomo expression for this objective - sense The direction for this objective. - """ - - __slots__ = () - - # - # Interface - # - - def is_minimizing(self): - """Return True if this is a minimization objective.""" - return self.sense == minimize - - # - # Abstract Interface - # - - @property - def sense(self): - """Access sense (direction) of this objective.""" - raise NotImplementedError - - def set_sense(self, sense): - """Set the sense (direction) of this objective.""" - raise NotImplementedError - - -class _ObjectiveData(metaclass=RenamedClass): - __renamed__new_class__ = ObjectiveData - __renamed__version__ = '6.7.2.dev0' - - -class GeneralObjectiveData( - GeneralExpressionDataImpl, ObjectiveData, ActiveComponentData +class ObjectiveData( + GeneralExpressionDataImpl, ActiveComponentData ): """ This class defines the data for a single objective. @@ -166,6 +123,10 @@ def __init__(self, expr=None, sense=minimize, component=None): "value: %s'" % (minimize, maximize, sense) ) + def is_minimizing(self): + """Return True if this is a minimization objective.""" + return self.sense == minimize + def set_value(self, expr): if expr is None: raise ValueError(_rule_returned_none_error % (self.name,)) @@ -197,8 +158,13 @@ def set_sense(self, sense): ) +class _ObjectiveData(metaclass=RenamedClass): + __renamed__new_class__ = ObjectiveData + __renamed__version__ = '6.7.2.dev0' + + class _GeneralObjectiveData(metaclass=RenamedClass): - __renamed__new_class__ = GeneralObjectiveData + __renamed__new_class__ = ObjectiveData __renamed__version__ = '6.7.2.dev0' From 13c11e58a8b49de727e3a9d0eeb630b55a19f1be Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 21:38:00 -0600 Subject: [PATCH 1478/1797] Update references from GeneralObjectiveData to ObjectiveData --- pyomo/contrib/appsi/base.py | 8 ++++---- pyomo/contrib/appsi/fbbt.py | 6 +++--- pyomo/contrib/appsi/solvers/cbc.py | 4 ++-- pyomo/contrib/appsi/solvers/cplex.py | 4 ++-- pyomo/contrib/appsi/solvers/ipopt.py | 4 ++-- pyomo/contrib/appsi/writers/lp_writer.py | 4 ++-- pyomo/contrib/appsi/writers/nl_writer.py | 4 ++-- pyomo/contrib/community_detection/detection.py | 4 ++-- pyomo/contrib/latex_printer/latex_printer.py | 4 ++-- pyomo/contrib/solver/base.py | 4 ++-- pyomo/contrib/solver/persistent.py | 6 +++--- pyomo/core/base/component.py | 2 +- pyomo/core/base/objective.py | 16 +++++++--------- pyomo/core/tests/unit/test_dict_objects.py | 4 ++-- pyomo/core/tests/unit/test_list_objects.py | 4 ++-- pyomo/repn/plugins/nl_writer.py | 6 +----- pyomo/repn/standard_repn.py | 6 +++--- 17 files changed, 42 insertions(+), 48 deletions(-) diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index 930ff8393e9..9d00a56e8b9 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -26,7 +26,7 @@ from pyomo.core.base.var import VarData, 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.core.base.objective import ObjectiveData 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 @@ -827,7 +827,7 @@ def remove_block(self, block: BlockData): pass @abc.abstractmethod - def set_objective(self, obj: GeneralObjectiveData): + def set_objective(self, obj: ObjectiveData): pass @abc.abstractmethod @@ -1050,10 +1050,10 @@ def add_sos_constraints(self, cons: List[SOSConstraintData]): self._add_sos_constraints(cons) @abc.abstractmethod - def _set_objective(self, obj: GeneralObjectiveData): + def _set_objective(self, obj: ObjectiveData): pass - def set_objective(self, obj: GeneralObjectiveData): + def set_objective(self, obj: ObjectiveData): if self._objective is not None: for v in self._vars_referenced_by_obj: self._referenced_variables[id(v)][2] = None diff --git a/pyomo/contrib/appsi/fbbt.py b/pyomo/contrib/appsi/fbbt.py index 1ebb3d40381..4b0d6d4876c 100644 --- a/pyomo/contrib/appsi/fbbt.py +++ b/pyomo/contrib/appsi/fbbt.py @@ -22,7 +22,7 @@ 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 ObjectiveData, minimize, maximize from pyomo.core.base.block import BlockData from pyomo.core.base import SymbolMap, TextLabeler from pyomo.common.errors import InfeasibleConstraintException @@ -224,13 +224,13 @@ def update_params(self): cp = self._param_map[p_id] cp.value = p.value - def set_objective(self, obj: GeneralObjectiveData): + def set_objective(self, obj: ObjectiveData): if self._symbolic_solver_labels: if self._objective is not None: self._symbol_map.removeSymbol(self._objective) super().set_objective(obj) - def _set_objective(self, obj: GeneralObjectiveData): + def _set_objective(self, obj: ObjectiveData): if obj is None: ce = cmodel.Constant(0) sense = 0 diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index 7db9a32764e..dffd479a5c7 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -30,7 +30,7 @@ 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.core.base.objective import ObjectiveData from pyomo.common.timing import HierarchicalTimer from pyomo.common.tee import TeeStream import sys @@ -188,7 +188,7 @@ def remove_constraints(self, cons: List[GeneralConstraintData]): def remove_block(self, block: BlockData): self._writer.remove_block(block) - def set_objective(self, obj: GeneralObjectiveData): + def set_objective(self, obj: ObjectiveData): self._writer.set_objective(obj) def update_variables(self, variables: List[VarData]): diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index 0ed3495ac1c..22c11bdfbe8 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -26,7 +26,7 @@ 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.core.base.objective import ObjectiveData from pyomo.common.timing import HierarchicalTimer import sys import time @@ -203,7 +203,7 @@ def remove_constraints(self, cons: List[GeneralConstraintData]): def remove_block(self, block: BlockData): self._writer.remove_block(block) - def set_objective(self, obj: GeneralObjectiveData): + def set_objective(self, obj: ObjectiveData): self._writer.set_objective(obj) def update_variables(self, variables: List[VarData]): diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index 5cd9a51785d..4144fbbecd9 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -32,7 +32,7 @@ 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.core.base.objective import ObjectiveData from pyomo.common.timing import HierarchicalTimer from pyomo.common.tee import TeeStream import sys @@ -252,7 +252,7 @@ def remove_constraints(self, cons: List[GeneralConstraintData]): def remove_block(self, block: BlockData): self._writer.remove_block(block) - def set_objective(self, obj: GeneralObjectiveData): + def set_objective(self, obj: ObjectiveData): self._writer.set_objective(obj) def update_variables(self, variables: List[VarData]): diff --git a/pyomo/contrib/appsi/writers/lp_writer.py b/pyomo/contrib/appsi/writers/lp_writer.py index 4be2b32d83d..3a6193bd314 100644 --- a/pyomo/contrib/appsi/writers/lp_writer.py +++ b/pyomo/contrib/appsi/writers/lp_writer.py @@ -13,7 +13,7 @@ from pyomo.core.base.param import ParamData from pyomo.core.base.var import VarData from pyomo.core.base.constraint import GeneralConstraintData -from pyomo.core.base.objective import GeneralObjectiveData +from pyomo.core.base.objective import ObjectiveData from pyomo.core.base.sos import SOSConstraintData from pyomo.core.base.block import BlockData from pyomo.repn.standard_repn import generate_standard_repn @@ -147,7 +147,7 @@ def update_params(self): cp = self._pyomo_param_to_solver_param_map[p_id] cp.value = p.value - def _set_objective(self, obj: GeneralObjectiveData): + def _set_objective(self, obj: ObjectiveData): cobj = cmodel.process_lp_objective( self._expr_types, obj, diff --git a/pyomo/contrib/appsi/writers/nl_writer.py b/pyomo/contrib/appsi/writers/nl_writer.py index 70176146a1e..754bd179497 100644 --- a/pyomo/contrib/appsi/writers/nl_writer.py +++ b/pyomo/contrib/appsi/writers/nl_writer.py @@ -13,7 +13,7 @@ from pyomo.core.base.param import ParamData from pyomo.core.base.var import VarData from pyomo.core.base.constraint import GeneralConstraintData -from pyomo.core.base.objective import GeneralObjectiveData +from pyomo.core.base.objective import ObjectiveData from pyomo.core.base.sos import SOSConstraintData from pyomo.core.base.block import BlockData from pyomo.repn.standard_repn import generate_standard_repn @@ -180,7 +180,7 @@ def update_params(self): cp = self._pyomo_param_to_solver_param_map[p_id] cp.value = p.value - def _set_objective(self, obj: GeneralObjectiveData): + def _set_objective(self, obj: ObjectiveData): if obj is None: const = cmodel.Constant(0) lin_vars = list() diff --git a/pyomo/contrib/community_detection/detection.py b/pyomo/contrib/community_detection/detection.py index af87fa5eb8b..0e2c3912e06 100644 --- a/pyomo/contrib/community_detection/detection.py +++ b/pyomo/contrib/community_detection/detection.py @@ -31,7 +31,7 @@ Objective, ConstraintList, ) -from pyomo.core.base.objective import GeneralObjectiveData +from pyomo.core.base.objective import ObjectiveData from pyomo.core.expr.visitor import replace_expressions, identify_variables from pyomo.contrib.community_detection.community_graph import generate_model_graph from pyomo.common.dependencies import networkx as nx @@ -750,7 +750,7 @@ def generate_structured_model(self): # Check to see whether 'stored_constraint' is actually an objective (since constraints and objectives # grouped together) if self.with_objective and isinstance( - stored_constraint, (GeneralObjectiveData, Objective) + stored_constraint, (ObjectiveData, Objective) ): # If the constraint is actually an objective, we add it to the block as an objective new_objective = Objective( diff --git a/pyomo/contrib/latex_printer/latex_printer.py b/pyomo/contrib/latex_printer/latex_printer.py index e11543cb375..13f30f899e4 100644 --- a/pyomo/contrib/latex_printer/latex_printer.py +++ b/pyomo/contrib/latex_printer/latex_printer.py @@ -35,7 +35,7 @@ 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 +from pyomo.core.base.objective import ScalarObjective, ObjectiveData import pyomo.core.kernel as kernel from pyomo.core.expr.template_expr import ( GetItemExpression, @@ -403,7 +403,7 @@ def __init__(self): ScalarExpression: handle_named_expression_node, kernel.expression.expression: handle_named_expression_node, kernel.expression.noclone: handle_named_expression_node, - GeneralObjectiveData: handle_named_expression_node, + ObjectiveData: handle_named_expression_node, VarData: handle_var_node, ScalarObjective: handle_named_expression_node, kernel.objective.objective: handle_named_expression_node, diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index fdc7361e6b8..c53f917bc2a 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -18,7 +18,7 @@ from pyomo.core.base.var import VarData from pyomo.core.base.param import ParamData from pyomo.core.base.block import BlockData -from pyomo.core.base.objective import GeneralObjectiveData +from pyomo.core.base.objective import ObjectiveData from pyomo.common.config import document_kwargs_from_configdict, ConfigValue from pyomo.common.errors import ApplicationError from pyomo.common.deprecation import deprecation_warning @@ -274,7 +274,7 @@ def set_instance(self, model): """ @abc.abstractmethod - def set_objective(self, obj: GeneralObjectiveData): + def set_objective(self, obj: ObjectiveData): """ Set current objective for the model """ diff --git a/pyomo/contrib/solver/persistent.py b/pyomo/contrib/solver/persistent.py index 103eb3c622f..81d0df1334f 100644 --- a/pyomo/contrib/solver/persistent.py +++ b/pyomo/contrib/solver/persistent.py @@ -16,7 +16,7 @@ from pyomo.core.base.sos import SOSConstraintData, SOSConstraint from pyomo.core.base.var import VarData from pyomo.core.base.param import ParamData, Param -from pyomo.core.base.objective import GeneralObjectiveData +from pyomo.core.base.objective import ObjectiveData from pyomo.common.collections import ComponentMap from pyomo.common.timing import HierarchicalTimer from pyomo.core.expr.numvalue import NumericConstant @@ -149,10 +149,10 @@ def add_sos_constraints(self, cons: List[SOSConstraintData]): self._add_sos_constraints(cons) @abc.abstractmethod - def _set_objective(self, obj: GeneralObjectiveData): + def _set_objective(self, obj: ObjectiveData): pass - def set_objective(self, obj: GeneralObjectiveData): + def set_objective(self, obj: ObjectiveData): if self._objective is not None: for v in self._vars_referenced_by_obj: self._referenced_variables[id(v)][2] = None diff --git a/pyomo/core/base/component.py b/pyomo/core/base/component.py index 3dd4d47046d..380aab23cbe 100644 --- a/pyomo/core/base/component.py +++ b/pyomo/core/base/component.py @@ -804,7 +804,7 @@ class ComponentData(_ComponentBase): # NOTE: This constructor is in-lined in the constructors for the following # classes: BooleanVarData, ConnectorData, ConstraintData, # GeneralExpressionData, LogicalConstraintData, - # LogicalConstraintData, GeneralObjectiveData, + # LogicalConstraintData, ObjectiveData, # ParamData,VarData, BooleanVarData, DisjunctionData, # ArcData, PortData, _LinearConstraintData, and # _LinearMatrixConstraintData. Changes made here need to be made in those diff --git a/pyomo/core/base/objective.py b/pyomo/core/base/objective.py index e4956748b6c..fea356229fb 100644 --- a/pyomo/core/base/objective.py +++ b/pyomo/core/base/objective.py @@ -81,9 +81,7 @@ def O_rule(model, i, j): return rule_wrapper(rule, {None: ObjectiveList.End}) -class ObjectiveData( - GeneralExpressionDataImpl, ActiveComponentData -): +class ObjectiveData(GeneralExpressionDataImpl, ActiveComponentData): """ This class defines the data for a single objective. @@ -216,7 +214,7 @@ class Objective(ActiveIndexedComponent): The class type for the derived subclass """ - _ComponentDataClass = GeneralObjectiveData + _ComponentDataClass = ObjectiveData NoObjective = ActiveIndexedComponent.Skip def __new__(cls, *args, **kwds): @@ -365,14 +363,14 @@ def display(self, prefix="", ostream=None): ) -class ScalarObjective(GeneralObjectiveData, Objective): +class ScalarObjective(ObjectiveData, Objective): """ ScalarObjective is the implementation representing a single, non-indexed objective. """ def __init__(self, *args, **kwd): - GeneralObjectiveData.__init__(self, expr=None, component=self) + ObjectiveData.__init__(self, expr=None, component=self) Objective.__init__(self, *args, **kwd) self._index = UnindexedComponent_index @@ -408,7 +406,7 @@ def expr(self): "a sense or expression (there is currently " "no value to return)." % (self.name) ) - return GeneralObjectiveData.expr.fget(self) + return ObjectiveData.expr.fget(self) raise ValueError( "Accessing the expression of objective '%s' " "before the Objective has been constructed (there " @@ -431,7 +429,7 @@ def sense(self): "a sense or expression (there is currently " "no value to return)." % (self.name) ) - return GeneralObjectiveData.sense.fget(self) + return ObjectiveData.sense.fget(self) raise ValueError( "Accessing the sense of objective '%s' " "before the Objective has been constructed (there " @@ -474,7 +472,7 @@ def set_sense(self, sense): if self._constructed: if len(self._data) == 0: self._data[None] = self - return GeneralObjectiveData.set_sense(self, sense) + return ObjectiveData.set_sense(self, sense) raise ValueError( "Setting the sense of objective '%s' " "before the Objective has been constructed (there " diff --git a/pyomo/core/tests/unit/test_dict_objects.py b/pyomo/core/tests/unit/test_dict_objects.py index 0dc5cacd216..fae1d21a87e 100644 --- a/pyomo/core/tests/unit/test_dict_objects.py +++ b/pyomo/core/tests/unit/test_dict_objects.py @@ -19,7 +19,7 @@ ) from pyomo.core.base.var import VarData from pyomo.core.base.constraint import GeneralConstraintData -from pyomo.core.base.objective import GeneralObjectiveData +from pyomo.core.base.objective import ObjectiveData from pyomo.core.base.expression import GeneralExpressionData @@ -384,7 +384,7 @@ def setUp(self): class TestObjectiveDict(_TestActiveComponentDictBase, unittest.TestCase): _ctype = ObjectiveDict - _cdatatype = GeneralObjectiveData + _cdatatype = ObjectiveData def setUp(self): _TestComponentDictBase.setUp(self) diff --git a/pyomo/core/tests/unit/test_list_objects.py b/pyomo/core/tests/unit/test_list_objects.py index f98b5279fc5..32f2fa328cf 100644 --- a/pyomo/core/tests/unit/test_list_objects.py +++ b/pyomo/core/tests/unit/test_list_objects.py @@ -19,7 +19,7 @@ ) from pyomo.core.base.var import VarData from pyomo.core.base.constraint import GeneralConstraintData -from pyomo.core.base.objective import GeneralObjectiveData +from pyomo.core.base.objective import ObjectiveData from pyomo.core.base.expression import GeneralExpressionData @@ -401,7 +401,7 @@ def setUp(self): class TestObjectiveList(_TestActiveComponentListBase, unittest.TestCase): _ctype = XObjectiveList - _cdatatype = GeneralObjectiveData + _cdatatype = ObjectiveData def setUp(self): _TestComponentListBase.setUp(self) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 23e14104b89..3c3c8539294 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -71,11 +71,7 @@ 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, - ObjectiveData, -) +from pyomo.core.base.objective import ScalarObjective, ObjectiveData from pyomo.core.base.suffix import SuffixFinder from pyomo.core.base.var import VarData import pyomo.core.kernel as kernel diff --git a/pyomo/repn/standard_repn.py b/pyomo/repn/standard_repn.py index 2f5a413e963..a23ebf6bb4f 100644 --- a/pyomo/repn/standard_repn.py +++ b/pyomo/repn/standard_repn.py @@ -19,7 +19,7 @@ import pyomo.core.expr as EXPR from pyomo.core.expr.numvalue import NumericConstant -from pyomo.core.base.objective import GeneralObjectiveData, ScalarObjective +from pyomo.core.base.objective import ObjectiveData, ScalarObjective from pyomo.core.base import ExpressionData, Expression from pyomo.core.base.expression import ScalarExpression, GeneralExpressionData from pyomo.core.base.var import ScalarVar, Var, VarData, value @@ -1154,7 +1154,7 @@ def _collect_external_fn(exp, multiplier, idMap, compute_values, verbose, quadra noclone: _collect_identity, ExpressionData: _collect_identity, Expression: _collect_identity, - GeneralObjectiveData: _collect_identity, + ObjectiveData: _collect_identity, ScalarObjective: _collect_identity, objective: _collect_identity, } @@ -1553,7 +1553,7 @@ def _linear_collect_pow(exp, multiplier, idMap, compute_values, verbose, coef): noclone : _linear_collect_identity, ExpressionData : _linear_collect_identity, Expression : _linear_collect_identity, - GeneralObjectiveData : _linear_collect_identity, + ObjectiveData : _linear_collect_identity, ScalarObjective : _linear_collect_identity, objective : _linear_collect_identity, } From 787eaf02a59cacd723c866c8f67740a82888c14b Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 22:38:41 -0600 Subject: [PATCH 1479/1797] Rename _ExpressionData, _GeneralExpressionDataImpl -> NamedExpressionData; _GeneralExpressionData -> ExpressionData --- pyomo/core/base/expression.py | 119 ++++++++++++---------------------- 1 file changed, 41 insertions(+), 78 deletions(-) diff --git a/pyomo/core/base/expression.py b/pyomo/core/base/expression.py index f5376381b2d..31bb5f835df 100644 --- a/pyomo/core/base/expression.py +++ b/pyomo/core/base/expression.py @@ -36,7 +36,7 @@ logger = logging.getLogger('pyomo.core') -class ExpressionData(numeric_expr.NumericValue): +class NamedExpressionData(numeric_expr.NumericValue): """ An object that defines a named expression. @@ -44,15 +44,14 @@ class ExpressionData(numeric_expr.NumericValue): expr The expression owned by this data. """ - __slots__ = () + __slots__ = ('_args_',) EXPRESSION_SYSTEM = EXPR.ExpressionType.NUMERIC PRECEDENCE = 0 ASSOCIATIVITY = EXPR.OperatorAssociativity.NON_ASSOCIATIVE - # - # Interface - # + def __init__(self, expr=None): + self._args_ = (expr,) def __call__(self, exception=True): """Compute the value of this expression.""" @@ -62,6 +61,18 @@ def __call__(self, exception=True): return arg return arg(exception=exception) + def create_node_with_local_data(self, values): + """ + Construct a simple expression after constructing the + contained expression. + + This class provides a consistent interface for constructing a + node, which is used in tree visitor scripts. + """ + obj = self.__class__() + obj._args_ = values + return obj + def is_named_expression_type(self): """A boolean indicating whether this in a named expression.""" return True @@ -110,9 +121,10 @@ def _compute_polynomial_degree(self, result): def _is_fixed(self, values): return values[0] - # - # Abstract Interface - # + # NamedExpressionData should never return False because + # they can store subexpressions that contain variables + def is_potentially_variable(self): + return True @property def expr(self): @@ -125,63 +137,6 @@ def expr(self): def expr(self, value): self.set_value(value) - def set_value(self, expr): - """Set the expression on this expression.""" - raise NotImplementedError - - def is_constant(self): - """A boolean indicating whether this expression is constant.""" - raise NotImplementedError - - def is_fixed(self): - """A boolean indicating whether this expression is fixed.""" - raise NotImplementedError - - # ExpressionData should never return False because - # they can store subexpressions that contain variables - def is_potentially_variable(self): - return True - - -class _ExpressionData(metaclass=RenamedClass): - __renamed__new_class__ = ExpressionData - __renamed__version__ = '6.7.2.dev0' - - -class GeneralExpressionDataImpl(ExpressionData): - """ - An object that defines an expression that is never cloned - - Constructor Arguments - expr The Pyomo expression stored in this expression. - component The Expression object that owns this data. - - Public Class Attributes - expr The expression owned by this data. - """ - - __slots__ = () - - def __init__(self, expr=None): - self._args_ = (expr,) - - def create_node_with_local_data(self, values): - """ - Construct a simple expression after constructing the - contained expression. - - This class provides a consistent interface for constructing a - node, which is used in tree visitor scripts. - """ - obj = ScalarExpression() - obj.construct() - obj._args_ = values - return obj - - # - # Abstract Interface - # - def set_value(self, expr): """Set the expression on this expression.""" if expr is None or expr.__class__ in native_numeric_types: @@ -240,7 +195,16 @@ def __ipow__(self, other): return numeric_expr._pow_dispatcher[e.__class__, other.__class__](e, other) -class GeneralExpressionData(GeneralExpressionDataImpl, ComponentData): +class _ExpressionData(metaclass=RenamedClass): + __renamed__new_class__ = NamedExpressionData + __renamed__version__ = '6.7.2.dev0' + +class _GeneralExpressionDataImpl(metaclass=RenamedClass): + __renamed__new_class__ = NamedExpressionData + __renamed__version__ = '6.7.2.dev0' + + +class ExpressionData(NamedExpressionData, ComponentData): """ An object that defines an expression that is never cloned @@ -255,17 +219,16 @@ class GeneralExpressionData(GeneralExpressionDataImpl, ComponentData): _component The expression component. """ - __slots__ = ('_args_',) + __slots__ = () def __init__(self, expr=None, component=None): - GeneralExpressionDataImpl.__init__(self, expr) - # Inlining ComponentData.__init__ + self._args_ = (expr,) self._component = weakref_ref(component) if (component is not None) else None self._index = NOTSET class _GeneralExpressionData(metaclass=RenamedClass): - __renamed__new_class__ = GeneralExpressionData + __renamed__new_class__ = ExpressionData __renamed__version__ = '6.7.2.dev0' @@ -285,7 +248,7 @@ class Expression(IndexedComponent): doc Text describing this component. """ - _ComponentDataClass = GeneralExpressionData + _ComponentDataClass = ExpressionData # This seems like a copy-paste error, and should be renamed/removed NoConstraint = IndexedComponent.Skip @@ -412,9 +375,9 @@ def construct(self, data=None): timer.report() -class ScalarExpression(GeneralExpressionData, Expression): +class ScalarExpression(ExpressionData, Expression): def __init__(self, *args, **kwds): - GeneralExpressionData.__init__(self, expr=None, component=self) + ExpressionData.__init__(self, expr=None, component=self) Expression.__init__(self, *args, **kwds) self._index = UnindexedComponent_index @@ -437,7 +400,7 @@ def __call__(self, exception=True): def expr(self): """Return expression on this expression.""" if self._constructed: - return GeneralExpressionData.expr.fget(self) + return ExpressionData.expr.fget(self) raise ValueError( "Accessing the expression of Expression '%s' " "before the Expression has been constructed (there " @@ -455,7 +418,7 @@ def clear(self): def set_value(self, expr): """Set the expression on this expression.""" if self._constructed: - return GeneralExpressionData.set_value(self, expr) + return ExpressionData.set_value(self, expr) raise ValueError( "Setting the expression of Expression '%s' " "before the Expression has been constructed (there " @@ -465,7 +428,7 @@ def set_value(self, expr): def is_constant(self): """A boolean indicating whether this expression is constant.""" if self._constructed: - return GeneralExpressionData.is_constant(self) + return ExpressionData.is_constant(self) raise ValueError( "Accessing the is_constant flag of Expression '%s' " "before the Expression has been constructed (there " @@ -475,7 +438,7 @@ def is_constant(self): def is_fixed(self): """A boolean indicating whether this expression is fixed.""" if self._constructed: - return GeneralExpressionData.is_fixed(self) + return ExpressionData.is_fixed(self) raise ValueError( "Accessing the is_fixed flag of Expression '%s' " "before the Expression has been constructed (there " @@ -519,6 +482,6 @@ def add(self, index, expr): """Add an expression with a given index.""" if (type(expr) is tuple) and (expr == Expression.Skip): return None - cdata = GeneralExpressionData(expr, component=self) + cdata = ExpressionData(expr, component=self) self._data[index] = cdata return cdata From 74b79181869619d68778eb9361d431dad362361c Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 22:40:12 -0600 Subject: [PATCH 1480/1797] Update *ExpressionData* referencces --- pyomo/contrib/appsi/cmodel/src/expression.hpp | 6 ++--- pyomo/contrib/cp/repn/docplex_writer.py | 6 ++--- .../logical_to_disjunctive_walker.py | 4 ++-- pyomo/contrib/fbbt/fbbt.py | 24 ++++++++----------- pyomo/contrib/latex_printer/latex_printer.py | 4 ++-- pyomo/contrib/mcpp/pyomo_mcpp.py | 6 +++-- pyomo/core/base/__init__.py | 6 +++-- pyomo/core/base/component.py | 2 +- pyomo/core/base/expression.py | 9 ++++--- pyomo/core/base/objective.py | 9 +++---- pyomo/core/expr/numeric_expr.py | 2 +- pyomo/core/tests/unit/test_dict_objects.py | 4 ++-- pyomo/core/tests/unit/test_expression.py | 8 +++---- pyomo/core/tests/unit/test_list_objects.py | 4 ++-- pyomo/dae/integral.py | 4 ++-- pyomo/gdp/tests/test_util.py | 6 ++--- pyomo/repn/plugins/ampl/ampl_.py | 4 ++-- pyomo/repn/plugins/nl_writer.py | 2 +- pyomo/repn/standard_repn.py | 16 ++++++++----- pyomo/repn/util.py | 4 ++-- 20 files changed, 67 insertions(+), 63 deletions(-) diff --git a/pyomo/contrib/appsi/cmodel/src/expression.hpp b/pyomo/contrib/appsi/cmodel/src/expression.hpp index 803bb21b6e2..ad1234b3863 100644 --- a/pyomo/contrib/appsi/cmodel/src/expression.hpp +++ b/pyomo/contrib/appsi/cmodel/src/expression.hpp @@ -700,7 +700,7 @@ class PyomoExprTypes { 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[_ExpressionData] = named_expr; expr_type_map[ScalarExpression] = named_expr; expr_type_map[Integral] = named_expr; expr_type_map[ScalarIntegral] = named_expr; @@ -765,8 +765,8 @@ class PyomoExprTypes { 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 _ExpressionData = + expr_module.attr("_ExpressionData"); py::object ScalarExpression = expr_module.attr("ScalarExpression"); py::object ScalarIntegral = py::module_::import("pyomo.dae.integral").attr("ScalarIntegral"); diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index 1af153910c0..37429d420d2 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -63,7 +63,7 @@ BooleanVarData, IndexedBooleanVar, ) -from pyomo.core.base.expression import ScalarExpression, GeneralExpressionData +from pyomo.core.base.expression import ScalarExpression, ExpressionData from pyomo.core.base.param import IndexedParam, ScalarParam, ParamData from pyomo.core.base.var import ScalarVar, VarData, IndexedVar import pyomo.core.expr as EXPR @@ -949,7 +949,7 @@ class LogicalToDoCplex(StreamBasedExpressionVisitor): BeforeExpression: _handle_before_expression_node, AtExpression: _handle_at_expression_node, AlwaysIn: _handle_always_in_node, - GeneralExpressionData: _handle_named_expression_node, + ExpressionData: _handle_named_expression_node, ScalarExpression: _handle_named_expression_node, } _var_handles = { @@ -966,7 +966,7 @@ class LogicalToDoCplex(StreamBasedExpressionVisitor): ScalarBooleanVar: _before_boolean_var, BooleanVarData: _before_boolean_var, IndexedBooleanVar: _before_indexed_boolean_var, - GeneralExpressionData: _before_named_expression, + ExpressionData: _before_named_expression, ScalarExpression: _before_named_expression, IndexedParam: _before_indexed_param, # Because of indirection ScalarParam: _before_param, diff --git a/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py b/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py index 0c493b89321..fdcfd5a8308 100644 --- a/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py +++ b/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py @@ -27,7 +27,7 @@ value, ) import pyomo.core.base.boolean_var as BV -from pyomo.core.base.expression import ScalarExpression, GeneralExpressionData +from pyomo.core.base.expression import ScalarExpression, ExpressionData from pyomo.core.base.param import ScalarParam, ParamData from pyomo.core.base.var import ScalarVar, VarData from pyomo.gdp.disjunct import AutoLinkedBooleanVar, Disjunct, Disjunction @@ -217,7 +217,7 @@ def _dispatch_atmost(visitor, node, *args): # don't handle them: _before_child_dispatcher[ScalarVar] = _dispatch_var _before_child_dispatcher[VarData] = _dispatch_var -_before_child_dispatcher[GeneralExpressionData] = _dispatch_expression +_before_child_dispatcher[ExpressionData] = _dispatch_expression _before_child_dispatcher[ScalarExpression] = _dispatch_expression diff --git a/pyomo/contrib/fbbt/fbbt.py b/pyomo/contrib/fbbt/fbbt.py index 86f94506841..eb7155313c4 100644 --- a/pyomo/contrib/fbbt/fbbt.py +++ b/pyomo/contrib/fbbt/fbbt.py @@ -26,7 +26,7 @@ from pyomo.core.base.constraint import Constraint from pyomo.core.base.var import Var from pyomo.gdp import Disjunct -from pyomo.core.base.expression import GeneralExpressionData, ScalarExpression +from pyomo.core.base.expression import ExpressionData, ScalarExpression import logging from pyomo.common.errors import InfeasibleConstraintException, PyomoException from pyomo.common.config import ( @@ -333,15 +333,15 @@ def _prop_bnds_leaf_to_root_UnaryFunctionExpression(visitor, node, arg): _unary_leaf_to_root_map[node.getname()](visitor, node, arg) -def _prop_bnds_leaf_to_root_GeneralExpression(visitor, node, expr): +def _prop_bnds_leaf_to_root_NamedExpression(visitor, node, expr): """ Propagate bounds from children to parent Parameters ---------- visitor: _FBBTVisitorLeafToRoot - node: pyomo.core.base.expression.GeneralExpressionData - expr: GeneralExpression arg + node: pyomo.core.base.expression.ExpressionData + expr: NamedExpressionData arg """ bnds_dict = visitor.bnds_dict if node in bnds_dict: @@ -366,8 +366,8 @@ def _prop_bnds_leaf_to_root_GeneralExpression(visitor, node, expr): 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, + ExpressionData: _prop_bnds_leaf_to_root_NamedExpression, + ScalarExpression: _prop_bnds_leaf_to_root_NamedExpression, }, ) @@ -898,13 +898,13 @@ def _prop_bnds_root_to_leaf_UnaryFunctionExpression(node, bnds_dict, feasibility ) -def _prop_bnds_root_to_leaf_GeneralExpression(node, bnds_dict, feasibility_tol): +def _prop_bnds_root_to_leaf_NamedExpression(node, bnds_dict, feasibility_tol): """ Propagate bounds from parent to children. Parameters ---------- - node: pyomo.core.base.expression.GeneralExpressionData + node: pyomo.core.base.expression.ExpressionData 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 @@ -945,12 +945,8 @@ def _prop_bnds_root_to_leaf_GeneralExpression(node, bnds_dict, feasibility_tol): ) _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[ExpressionData] = _prop_bnds_root_to_leaf_NamedExpression +_prop_bnds_root_to_leaf_map[ScalarExpression] = _prop_bnds_root_to_leaf_NamedExpression def _check_and_reset_bounds(var, lb, ub): diff --git a/pyomo/contrib/latex_printer/latex_printer.py b/pyomo/contrib/latex_printer/latex_printer.py index 13f30f899e4..cf286472a66 100644 --- a/pyomo/contrib/latex_printer/latex_printer.py +++ b/pyomo/contrib/latex_printer/latex_printer.py @@ -34,7 +34,7 @@ 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.expression import ScalarExpression, ExpressionData from pyomo.core.base.objective import ScalarObjective, ObjectiveData import pyomo.core.kernel as kernel from pyomo.core.expr.template_expr import ( @@ -399,7 +399,7 @@ def __init__(self): EqualityExpression: handle_equality_node, InequalityExpression: handle_inequality_node, RangedExpression: handle_ranged_inequality_node, - GeneralExpressionData: handle_named_expression_node, + ExpressionData: handle_named_expression_node, ScalarExpression: handle_named_expression_node, kernel.expression.expression: handle_named_expression_node, kernel.expression.noclone: handle_named_expression_node, diff --git a/pyomo/contrib/mcpp/pyomo_mcpp.py b/pyomo/contrib/mcpp/pyomo_mcpp.py index 1375ae61c50..0ef0237681b 100644 --- a/pyomo/contrib/mcpp/pyomo_mcpp.py +++ b/pyomo/contrib/mcpp/pyomo_mcpp.py @@ -20,7 +20,7 @@ from pyomo.common.fileutils import Library from pyomo.core import value, Expression from pyomo.core.base.block import SubclassOf -from pyomo.core.base.expression import ExpressionData +from pyomo.core.base.expression import NamedExpressionData from pyomo.core.expr.numvalue import nonpyomo_leaf_types from pyomo.core.expr.numeric_expr import ( AbsExpression, @@ -307,7 +307,9 @@ def exitNode(self, node, data): ans = self.mcpp.newConstant(node) elif not node.is_expression_type(): ans = self.register_num(node) - elif type(node) in SubclassOf(Expression) or isinstance(node, ExpressionData): + elif type(node) in SubclassOf(Expression) or isinstance( + node, NamedExpressionData + ): ans = data[0] else: raise RuntimeError("Unhandled expression type: %s" % (type(node))) diff --git a/pyomo/core/base/__init__.py b/pyomo/core/base/__init__.py index bcc2a0e0e02..3d1347659db 100644 --- a/pyomo/core/base/__init__.py +++ b/pyomo/core/base/__init__.py @@ -97,7 +97,7 @@ Constraint, ConstraintData, ) -from pyomo.core.base.expression import Expression, ExpressionData +from pyomo.core.base.expression import Expression, NamedExpressionData, ExpressionData from pyomo.core.base.external import ExternalFunction from pyomo.core.base.logical_constraint import ( LogicalConstraint, @@ -170,10 +170,12 @@ relocated_module_attribute( f'_GeneralBooleanVarData', f'pyomo.core.base.BooleanVarData', version='6.7.2.dev0' ) +relocated_module_attribute( + f'_ExpressionData', f'pyomo.core.base.NamedExpressionData', version='6.7.2.dev0' +) for _cdata in ( 'ConstraintData', 'LogicalConstraintData', - 'ExpressionData', 'VarData', 'BooleanVarData', 'ObjectiveData', diff --git a/pyomo/core/base/component.py b/pyomo/core/base/component.py index 380aab23cbe..50cf264c799 100644 --- a/pyomo/core/base/component.py +++ b/pyomo/core/base/component.py @@ -803,7 +803,7 @@ class ComponentData(_ComponentBase): # NOTE: This constructor is in-lined in the constructors for the following # classes: BooleanVarData, ConnectorData, ConstraintData, - # GeneralExpressionData, LogicalConstraintData, + # ExpressionData, LogicalConstraintData, # LogicalConstraintData, ObjectiveData, # ParamData,VarData, BooleanVarData, DisjunctionData, # ArcData, PortData, _LinearConstraintData, and diff --git a/pyomo/core/base/expression.py b/pyomo/core/base/expression.py index 31bb5f835df..10720366e28 100644 --- a/pyomo/core/base/expression.py +++ b/pyomo/core/base/expression.py @@ -44,15 +44,13 @@ class NamedExpressionData(numeric_expr.NumericValue): expr The expression owned by this data. """ - __slots__ = ('_args_',) + # Note: derived classes are expected to declare teh _args_ slot + __slots__ = () EXPRESSION_SYSTEM = EXPR.ExpressionType.NUMERIC PRECEDENCE = 0 ASSOCIATIVITY = EXPR.OperatorAssociativity.NON_ASSOCIATIVE - def __init__(self, expr=None): - self._args_ = (expr,) - def __call__(self, exception=True): """Compute the value of this expression.""" (arg,) = self._args_ @@ -199,6 +197,7 @@ class _ExpressionData(metaclass=RenamedClass): __renamed__new_class__ = NamedExpressionData __renamed__version__ = '6.7.2.dev0' + class _GeneralExpressionDataImpl(metaclass=RenamedClass): __renamed__new_class__ = NamedExpressionData __renamed__version__ = '6.7.2.dev0' @@ -219,7 +218,7 @@ class ExpressionData(NamedExpressionData, ComponentData): _component The expression component. """ - __slots__ = () + __slots__ = ('_args_',) def __init__(self, expr=None, component=None): self._args_ = (expr,) diff --git a/pyomo/core/base/objective.py b/pyomo/core/base/objective.py index fea356229fb..71cf5ba78f8 100644 --- a/pyomo/core/base/objective.py +++ b/pyomo/core/base/objective.py @@ -28,7 +28,7 @@ UnindexedComponent_set, rule_wrapper, ) -from pyomo.core.base.expression import ExpressionData, GeneralExpressionDataImpl +from pyomo.core.base.expression import NamedExpressionData from pyomo.core.base.set import Set from pyomo.core.base.initializer import ( Initializer, @@ -81,7 +81,7 @@ def O_rule(model, i, j): return rule_wrapper(rule, {None: ObjectiveList.End}) -class ObjectiveData(GeneralExpressionDataImpl, ActiveComponentData): +class ObjectiveData(NamedExpressionData, ActiveComponentData): """ This class defines the data for a single objective. @@ -104,10 +104,11 @@ class ObjectiveData(GeneralExpressionDataImpl, ActiveComponentData): _active A boolean that indicates whether this data is active """ - __slots__ = ("_sense", "_args_") + __slots__ = ("_args_", "_sense") def __init__(self, expr=None, sense=minimize, component=None): - GeneralExpressionDataImpl.__init__(self, expr) + # Inlining NamedExpressionData.__init__ + self._args_ = (expr,) # Inlining ActiveComponentData.__init__ self._component = weakref_ref(component) if (component is not None) else None self._index = NOTSET diff --git a/pyomo/core/expr/numeric_expr.py b/pyomo/core/expr/numeric_expr.py index 50abaeedbba..49bb2e0280f 100644 --- a/pyomo/core/expr/numeric_expr.py +++ b/pyomo/core/expr/numeric_expr.py @@ -722,7 +722,7 @@ def args(self): @deprecated( 'The implicit recasting of a "not potentially variable" ' 'expression node to a potentially variable one is no ' - 'longer supported (this violates that immutability ' + 'longer supported (this violates the immutability ' 'promise for Pyomo5 expression trees).', version='6.4.3', ) diff --git a/pyomo/core/tests/unit/test_dict_objects.py b/pyomo/core/tests/unit/test_dict_objects.py index fae1d21a87e..16b7e0bd2e0 100644 --- a/pyomo/core/tests/unit/test_dict_objects.py +++ b/pyomo/core/tests/unit/test_dict_objects.py @@ -20,7 +20,7 @@ from pyomo.core.base.var import VarData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.objective import ObjectiveData -from pyomo.core.base.expression import GeneralExpressionData +from pyomo.core.base.expression import ExpressionData class _TestComponentDictBase(object): @@ -360,7 +360,7 @@ def setUp(self): class TestExpressionDict(_TestComponentDictBase, unittest.TestCase): _ctype = ExpressionDict - _cdatatype = GeneralExpressionData + _cdatatype = ExpressionData def setUp(self): _TestComponentDictBase.setUp(self) diff --git a/pyomo/core/tests/unit/test_expression.py b/pyomo/core/tests/unit/test_expression.py index bf3ce0c2179..eb16f7c6142 100644 --- a/pyomo/core/tests/unit/test_expression.py +++ b/pyomo/core/tests/unit/test_expression.py @@ -29,7 +29,7 @@ value, sum_product, ) -from pyomo.core.base.expression import GeneralExpressionData +from pyomo.core.base.expression import ExpressionData from pyomo.core.expr.compare import compare_expressions, assertExpressionsEqual from pyomo.common.tee import capture_output @@ -515,10 +515,10 @@ def test_implicit_definition(self): model.E = Expression(model.idx) self.assertEqual(len(model.E), 3) expr = model.E[1] - self.assertIs(type(expr), GeneralExpressionData) + self.assertIs(type(expr), ExpressionData) model.E[1] = None self.assertIs(expr, model.E[1]) - self.assertIs(type(expr), GeneralExpressionData) + self.assertIs(type(expr), ExpressionData) self.assertIs(expr.expr, None) model.E[1] = 5 self.assertIs(expr, model.E[1]) @@ -537,7 +537,7 @@ def test_explicit_skip_definition(self): model.E[1] = None expr = model.E[1] - self.assertIs(type(expr), GeneralExpressionData) + self.assertIs(type(expr), ExpressionData) self.assertIs(expr.expr, None) model.E[1] = 5 self.assertIs(expr, model.E[1]) diff --git a/pyomo/core/tests/unit/test_list_objects.py b/pyomo/core/tests/unit/test_list_objects.py index 32f2fa328cf..94913bcbc02 100644 --- a/pyomo/core/tests/unit/test_list_objects.py +++ b/pyomo/core/tests/unit/test_list_objects.py @@ -20,7 +20,7 @@ from pyomo.core.base.var import VarData from pyomo.core.base.constraint import GeneralConstraintData from pyomo.core.base.objective import ObjectiveData -from pyomo.core.base.expression import GeneralExpressionData +from pyomo.core.base.expression import ExpressionData class _TestComponentListBase(object): @@ -377,7 +377,7 @@ def setUp(self): class TestExpressionList(_TestComponentListBase, unittest.TestCase): _ctype = XExpressionList - _cdatatype = GeneralExpressionData + _cdatatype = ExpressionData def setUp(self): _TestComponentListBase.setUp(self) diff --git a/pyomo/dae/integral.py b/pyomo/dae/integral.py index f767e31f18c..8c9512d98dd 100644 --- a/pyomo/dae/integral.py +++ b/pyomo/dae/integral.py @@ -14,7 +14,7 @@ from pyomo.core.base.indexed_component import rule_wrapper from pyomo.core.base.expression import ( Expression, - GeneralExpressionData, + ExpressionData, ScalarExpression, IndexedExpression, ) @@ -151,7 +151,7 @@ class ScalarIntegral(ScalarExpression, Integral): """ def __init__(self, *args, **kwds): - GeneralExpressionData.__init__(self, None, component=self) + ExpressionData.__init__(self, None, component=self) Integral.__init__(self, *args, **kwds) def clear(self): diff --git a/pyomo/gdp/tests/test_util.py b/pyomo/gdp/tests/test_util.py index 8ea72af37da..fa8e953f9f7 100644 --- a/pyomo/gdp/tests/test_util.py +++ b/pyomo/gdp/tests/test_util.py @@ -13,7 +13,7 @@ from pyomo.core import ConcreteModel, Var, Expression, Block, RangeSet, Any import pyomo.core.expr as EXPR -from pyomo.core.base.expression import ExpressionData +from pyomo.core.base.expression import NamedExpressionData from pyomo.gdp.util import ( clone_without_expression_components, is_child_of, @@ -40,7 +40,7 @@ def test_clone_without_expression_components(self): test = clone_without_expression_components(base, {}) self.assertIsNot(base, test) self.assertEqual(base(), test()) - self.assertIsInstance(base, ExpressionData) + self.assertIsInstance(base, NamedExpressionData) self.assertIsInstance(test, EXPR.SumExpression) test = clone_without_expression_components(base, {id(m.x): m.y}) self.assertEqual(3**2 + 3 - 1, test()) @@ -51,7 +51,7 @@ def test_clone_without_expression_components(self): self.assertEqual(base(), test()) self.assertIsInstance(base, EXPR.SumExpression) self.assertIsInstance(test, EXPR.SumExpression) - self.assertIsInstance(base.arg(0), ExpressionData) + self.assertIsInstance(base.arg(0), NamedExpressionData) self.assertIsInstance(test.arg(0), EXPR.SumExpression) test = clone_without_expression_components(base, {id(m.x): m.y}) self.assertEqual(3**2 + 3 - 1 + 3, test()) diff --git a/pyomo/repn/plugins/ampl/ampl_.py b/pyomo/repn/plugins/ampl/ampl_.py index 1cff45b30c1..cc99e9cfdae 100644 --- a/pyomo/repn/plugins/ampl/ampl_.py +++ b/pyomo/repn/plugins/ampl/ampl_.py @@ -33,7 +33,7 @@ from pyomo.core.base import ( SymbolMap, NameLabeler, - ExpressionData, + NamedExpressionData, SortComponents, var, param, @@ -724,7 +724,7 @@ def _print_nonlinear_terms_NL(self, exp): self._print_nonlinear_terms_NL(exp.arg(0)) self._print_nonlinear_terms_NL(exp.arg(1)) - elif isinstance(exp, (ExpressionData, IIdentityExpression)): + elif isinstance(exp, (NamedExpressionData, IIdentityExpression)): self._print_nonlinear_terms_NL(exp.expr) else: diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 3c3c8539294..8cc73b5e3fe 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -70,7 +70,7 @@ ) 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.expression import ScalarExpression, ExpressionData from pyomo.core.base.objective import ScalarObjective, ObjectiveData from pyomo.core.base.suffix import SuffixFinder from pyomo.core.base.var import VarData diff --git a/pyomo/repn/standard_repn.py b/pyomo/repn/standard_repn.py index a23ebf6bb4f..b767ab727af 100644 --- a/pyomo/repn/standard_repn.py +++ b/pyomo/repn/standard_repn.py @@ -20,8 +20,12 @@ import pyomo.core.expr as EXPR from pyomo.core.expr.numvalue import NumericConstant from pyomo.core.base.objective import ObjectiveData, ScalarObjective -from pyomo.core.base import ExpressionData, Expression -from pyomo.core.base.expression import ScalarExpression, GeneralExpressionData +from pyomo.core.base import Expression +from pyomo.core.base.expression import ( + ScalarExpression, + NamedExpressionData, + ExpressionData, +) from pyomo.core.base.var import ScalarVar, Var, VarData, value from pyomo.core.base.param import ScalarParam, ParamData from pyomo.core.kernel.expression import expression, noclone @@ -1148,11 +1152,11 @@ def _collect_external_fn(exp, multiplier, idMap, compute_values, verbose, quadra Var: _collect_var, variable: _collect_var, IVariable: _collect_var, - GeneralExpressionData: _collect_identity, + ExpressionData: _collect_identity, ScalarExpression: _collect_identity, expression: _collect_identity, noclone: _collect_identity, - ExpressionData: _collect_identity, + NamedExpressionData: _collect_identity, Expression: _collect_identity, ObjectiveData: _collect_identity, ScalarObjective: _collect_identity, @@ -1547,11 +1551,11 @@ def _linear_collect_pow(exp, multiplier, idMap, compute_values, verbose, coef): Var : _linear_collect_var, variable : _linear_collect_var, IVariable : _linear_collect_var, - GeneralExpressionData : _linear_collect_identity, + ExpressionData : _linear_collect_identity, ScalarExpression : _linear_collect_identity, expression : _linear_collect_identity, noclone : _linear_collect_identity, - ExpressionData : _linear_collect_identity, + NamedExpressionData : _linear_collect_identity, Expression : _linear_collect_identity, ObjectiveData : _linear_collect_identity, ScalarObjective : _linear_collect_identity, diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index 7351ea51c58..9a8713c6965 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -40,7 +40,7 @@ SortComponents, ) from pyomo.core.base.component import ActiveComponent -from pyomo.core.base.expression import ExpressionData +from pyomo.core.base.expression import NamedExpressionData from pyomo.core.expr.numvalue import is_fixed, value import pyomo.core.expr as EXPR import pyomo.core.kernel as kernel @@ -55,7 +55,7 @@ EXPR.NPV_SumExpression, } _named_subexpression_types = ( - ExpressionData, + NamedExpressionData, kernel.expression.expression, kernel.objective.objective, ) From 47bb04f00f591f175a7a65e656197a41fc0eae50 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 20 Mar 2024 23:29:02 -0600 Subject: [PATCH 1481/1797] Add missing method from GeneralLogicalConstraintData merge --- pyomo/core/base/logical_constraint.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyomo/core/base/logical_constraint.py b/pyomo/core/base/logical_constraint.py index 1daa5f83e90..9584078307d 100644 --- a/pyomo/core/base/logical_constraint.py +++ b/pyomo/core/base/logical_constraint.py @@ -77,6 +77,12 @@ def __init__(self, expr=None, component=None): if expr is not None: self.set_value(expr) + def __call__(self, exception=True): + """Compute the value of the body of this logical constraint.""" + if self.body is None: + return None + return self.body(exception=exception) + # # Abstract Interface # From d26a83ff243c7412bcb978f0f571b53ebc737f6c Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 21 Mar 2024 10:47:13 -0600 Subject: [PATCH 1482/1797] NFC: fix typo --- pyomo/core/base/expression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/core/base/expression.py b/pyomo/core/base/expression.py index 10720366e28..5638e48ea8b 100644 --- a/pyomo/core/base/expression.py +++ b/pyomo/core/base/expression.py @@ -44,7 +44,7 @@ class NamedExpressionData(numeric_expr.NumericValue): expr The expression owned by this data. """ - # Note: derived classes are expected to declare teh _args_ slot + # Note: derived classes are expected to declare the _args_ slot __slots__ = () EXPRESSION_SYSTEM = EXPR.ExpressionType.NUMERIC From db7b6b22ad1c2f3659fce2cb0cd96c69727ef6f3 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 21 Mar 2024 10:47:52 -0600 Subject: [PATCH 1483/1797] Update ComponentData references in APPSI --- pyomo/contrib/appsi/cmodel/src/expression.hpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pyomo/contrib/appsi/cmodel/src/expression.hpp b/pyomo/contrib/appsi/cmodel/src/expression.hpp index ad1234b3863..e91ca0af3b3 100644 --- a/pyomo/contrib/appsi/cmodel/src/expression.hpp +++ b/pyomo/contrib/appsi/cmodel/src/expression.hpp @@ -680,10 +680,10 @@ class PyomoExprTypes { expr_type_map[np_float32] = py_float; expr_type_map[np_float64] = py_float; expr_type_map[ScalarVar] = var; - expr_type_map[_VarData] = var; + expr_type_map[VarData] = var; expr_type_map[AutoLinkedBinaryVar] = var; expr_type_map[ScalarParam] = param; - expr_type_map[_ParamData] = param; + expr_type_map[ParamData] = param; expr_type_map[MonomialTermExpression] = product; expr_type_map[ProductExpression] = product; expr_type_map[NPV_ProductExpression] = product; @@ -700,7 +700,7 @@ class PyomoExprTypes { expr_type_map[UnaryFunctionExpression] = unary_func; expr_type_map[NPV_UnaryFunctionExpression] = unary_func; expr_type_map[LinearExpression] = linear; - expr_type_map[_ExpressionData] = named_expr; + expr_type_map[ExpressionData] = named_expr; expr_type_map[ScalarExpression] = named_expr; expr_type_map[Integral] = named_expr; expr_type_map[ScalarIntegral] = named_expr; @@ -728,12 +728,12 @@ class PyomoExprTypes { 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 ParamData = + py::module_::import("pyomo.core.base.param").attr("ParamData"); py::object ScalarVar = py::module_::import("pyomo.core.base.var").attr("ScalarVar"); - py::object _VarData = - py::module_::import("pyomo.core.base.var").attr("_VarData"); + py::object VarData = + py::module_::import("pyomo.core.base.var").attr("VarData"); py::object AutoLinkedBinaryVar = py::module_::import("pyomo.gdp.disjunct").attr("AutoLinkedBinaryVar"); py::object numeric_expr = py::module_::import("pyomo.core.expr.numeric_expr"); @@ -765,8 +765,8 @@ class PyomoExprTypes { 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 _ExpressionData = - expr_module.attr("_ExpressionData"); + py::object ExpressionData = + expr_module.attr("ExpressionData"); py::object ScalarExpression = expr_module.attr("ScalarExpression"); py::object ScalarIntegral = py::module_::import("pyomo.dae.integral").attr("ScalarIntegral"); From 5b48eb28989c29f23324d6d3e6cdf1ca452346fc Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 21 Mar 2024 12:18:53 -0600 Subject: [PATCH 1484/1797] Merge GeneralConstraintData into ConstraintData --- pyomo/core/base/constraint.py | 244 ++++++++++------------------------ 1 file changed, 70 insertions(+), 174 deletions(-) diff --git a/pyomo/core/base/constraint.py b/pyomo/core/base/constraint.py index 3455d2dde3c..08c97d7c8ae 100644 --- a/pyomo/core/base/constraint.py +++ b/pyomo/core/base/constraint.py @@ -125,17 +125,13 @@ def C_rule(model, i, j): return rule_wrapper(rule, result_map, map_types=map_types) -# -# This class is a pure interface -# - - -class ConstraintData(ActiveComponentData): +class ConstraintData(ConstraintData): """ - This class defines the data for a single constraint. + This class defines the data for a single general constraint. Constructor arguments: component The Constraint object that owns this data. + expr The Pyomo expression stored in this constraint. Public class attributes: active A boolean that is true if this constraint is @@ -155,164 +151,12 @@ class ConstraintData(ActiveComponentData): _active A boolean that indicates whether this data is active """ - __slots__ = () + __slots__ = ('_body', '_lower', '_upper', '_expr') # Set to true when a constraint class stores its expression # in linear canonical form _linear_canonical_form = False - def __init__(self, component=None): - # - # These lines represent in-lining of the - # following constructors: - # - ConstraintData, - # - ActiveComponentData - # - ComponentData - self._component = weakref_ref(component) if (component is not None) else None - self._index = NOTSET - self._active = True - - # - # Interface - # - - def __call__(self, exception=True): - """Compute the value of the body of this constraint.""" - return value(self.body, exception=exception) - - def has_lb(self): - """Returns :const:`False` when the lower bound is - :const:`None` or negative infinity""" - return self.lb is not None - - def has_ub(self): - """Returns :const:`False` when the upper bound is - :const:`None` or positive infinity""" - return self.ub is not None - - def lslack(self): - """ - Returns the value of f(x)-L for constraints of the form: - L <= f(x) (<= U) - (U >=) f(x) >= L - """ - lb = self.lb - if lb is None: - return _inf - else: - return value(self.body) - lb - - def uslack(self): - """ - Returns the value of U-f(x) for constraints of the form: - (L <=) f(x) <= U - U >= f(x) (>= L) - """ - ub = self.ub - if ub is None: - return _inf - else: - return ub - value(self.body) - - def slack(self): - """ - Returns the smaller of lslack and uslack values - """ - lb = self.lb - ub = self.ub - body = value(self.body) - if lb is None: - return ub - body - elif ub is None: - return body - lb - return min(ub - body, body - lb) - - # - # Abstract Interface - # - - @property - def body(self): - """Access the body of a constraint expression.""" - raise NotImplementedError - - @property - def lower(self): - """Access the lower bound of a constraint expression.""" - raise NotImplementedError - - @property - def upper(self): - """Access the upper bound of a constraint expression.""" - raise NotImplementedError - - @property - def lb(self): - """Access the value of the lower bound of a constraint expression.""" - raise NotImplementedError - - @property - def ub(self): - """Access the value of the upper bound of a constraint expression.""" - raise NotImplementedError - - @property - def equality(self): - """A boolean indicating whether this is an equality constraint.""" - raise NotImplementedError - - @property - def strict_lower(self): - """True if this constraint has a strict lower bound.""" - raise NotImplementedError - - @property - def strict_upper(self): - """True if this constraint has a strict upper bound.""" - raise NotImplementedError - - def set_value(self, expr): - """Set the expression on this constraint.""" - raise NotImplementedError - - def get_value(self): - """Get the expression on this constraint.""" - raise NotImplementedError - - -class _ConstraintData(metaclass=RenamedClass): - __renamed__new_class__ = ConstraintData - __renamed__version__ = '6.7.2.dev0' - - -class GeneralConstraintData(ConstraintData): - """ - This class defines the data for a single general constraint. - - Constructor arguments: - component The Constraint object that owns this data. - expr The Pyomo expression stored in this constraint. - - Public class attributes: - active A boolean that is true if this constraint is - active in the model. - body The Pyomo expression for this constraint - lower The Pyomo expression for the lower bound - upper The Pyomo expression for the upper bound - equality A boolean that indicates whether this is an - equality constraint - strict_lower A boolean that indicates whether this - constraint uses a strict lower bound - strict_upper A boolean that indicates whether this - constraint uses a strict upper bound - - Private class attributes: - _component The objective component. - _active A boolean that indicates whether this data is active - """ - - __slots__ = ('_body', '_lower', '_upper', '_expr') - def __init__(self, expr=None, component=None): # # These lines represent in-lining of the @@ -330,9 +174,9 @@ def __init__(self, expr=None, component=None): if expr is not None: self.set_value(expr) - # - # Abstract Interface - # + def __call__(self, exception=True): + """Compute the value of the body of this constraint.""" + return value(self.body, exception=exception) @property def body(self): @@ -456,6 +300,16 @@ def strict_upper(self): """True if this constraint has a strict upper bound.""" return False + def has_lb(self): + """Returns :const:`False` when the lower bound is + :const:`None` or negative infinity""" + return self.lb is not None + + def has_ub(self): + """Returns :const:`False` when the upper bound is + :const:`None` or positive infinity""" + return self.ub is not None + @property def expr(self): """Return the expression associated with this constraint.""" @@ -683,9 +537,51 @@ def set_value(self, expr): "upper bound (%s)." % (self.name, self._upper) ) + def lslack(self): + """ + Returns the value of f(x)-L for constraints of the form: + L <= f(x) (<= U) + (U >=) f(x) >= L + """ + lb = self.lb + if lb is None: + return _inf + else: + return value(self.body) - lb + + def uslack(self): + """ + Returns the value of U-f(x) for constraints of the form: + (L <=) f(x) <= U + U >= f(x) (>= L) + """ + ub = self.ub + if ub is None: + return _inf + else: + return ub - value(self.body) + + def slack(self): + """ + Returns the smaller of lslack and uslack values + """ + lb = self.lb + ub = self.ub + body = value(self.body) + if lb is None: + return ub - body + elif ub is None: + return body - lb + return min(ub - body, body - lb) + + +class _ConstraintData(metaclass=RenamedClass): + __renamed__new_class__ = ConstraintData + __renamed__version__ = '6.7.2.dev0' + class _GeneralConstraintData(metaclass=RenamedClass): - __renamed__new_class__ = GeneralConstraintData + __renamed__new_class__ = ConstraintData __renamed__version__ = '6.7.2.dev0' @@ -731,7 +627,7 @@ class Constraint(ActiveIndexedComponent): The class type for the derived subclass """ - _ComponentDataClass = GeneralConstraintData + _ComponentDataClass = ConstraintData class Infeasible(object): pass @@ -889,14 +785,14 @@ def display(self, prefix="", ostream=None): ) -class ScalarConstraint(GeneralConstraintData, Constraint): +class ScalarConstraint(ConstraintData, Constraint): """ ScalarConstraint is the implementation representing a single, non-indexed constraint. """ def __init__(self, *args, **kwds): - GeneralConstraintData.__init__(self, component=self, expr=None) + ConstraintData.__init__(self, component=self, expr=None) Constraint.__init__(self, *args, **kwds) self._index = UnindexedComponent_index @@ -920,7 +816,7 @@ def body(self): "an expression. There is currently " "nothing to access." % (self.name) ) - return GeneralConstraintData.body.fget(self) + return ConstraintData.body.fget(self) @property def lower(self): @@ -932,7 +828,7 @@ def lower(self): "an expression. There is currently " "nothing to access." % (self.name) ) - return GeneralConstraintData.lower.fget(self) + return ConstraintData.lower.fget(self) @property def upper(self): @@ -944,7 +840,7 @@ def upper(self): "an expression. There is currently " "nothing to access." % (self.name) ) - return GeneralConstraintData.upper.fget(self) + return ConstraintData.upper.fget(self) @property def equality(self): @@ -956,7 +852,7 @@ def equality(self): "an expression. There is currently " "nothing to access." % (self.name) ) - return GeneralConstraintData.equality.fget(self) + return ConstraintData.equality.fget(self) @property def strict_lower(self): @@ -968,7 +864,7 @@ def strict_lower(self): "an expression. There is currently " "nothing to access." % (self.name) ) - return GeneralConstraintData.strict_lower.fget(self) + return ConstraintData.strict_lower.fget(self) @property def strict_upper(self): @@ -980,7 +876,7 @@ def strict_upper(self): "an expression. There is currently " "nothing to access." % (self.name) ) - return GeneralConstraintData.strict_upper.fget(self) + return ConstraintData.strict_upper.fget(self) def clear(self): self._data = {} @@ -1045,7 +941,7 @@ def add(self, index, expr): return self.__setitem__(index, expr) @overload - def __getitem__(self, index) -> GeneralConstraintData: ... + def __getitem__(self, index) -> ConstraintData: ... __getitem__ = IndexedComponent.__getitem__ # type: ignore From 97ad3c0945ca1426729a107320a06c21d54653c0 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 21 Mar 2024 15:15:03 -0600 Subject: [PATCH 1485/1797] Update references from GeneralConstraintData to ConstraintData --- pyomo/contrib/appsi/base.py | 48 +++++++++---------- pyomo/contrib/appsi/fbbt.py | 6 +-- pyomo/contrib/appsi/solvers/cbc.py | 6 +-- pyomo/contrib/appsi/solvers/cplex.py | 10 ++-- pyomo/contrib/appsi/solvers/gurobi.py | 16 +++---- pyomo/contrib/appsi/solvers/highs.py | 6 +-- pyomo/contrib/appsi/solvers/ipopt.py | 8 ++-- pyomo/contrib/appsi/solvers/wntr.py | 6 +-- pyomo/contrib/appsi/writers/lp_writer.py | 6 +-- pyomo/contrib/appsi/writers/nl_writer.py | 6 +-- pyomo/contrib/solver/base.py | 10 ++-- pyomo/contrib/solver/gurobi.py | 16 +++---- pyomo/contrib/solver/persistent.py | 12 ++--- pyomo/contrib/solver/solution.py | 14 +++--- .../contrib/solver/tests/unit/test_results.py | 6 +-- pyomo/core/tests/unit/test_con.py | 4 +- pyomo/core/tests/unit/test_dict_objects.py | 4 +- pyomo/core/tests/unit/test_list_objects.py | 4 +- pyomo/gdp/tests/common_tests.py | 6 +-- pyomo/gdp/tests/test_bigm.py | 4 +- pyomo/gdp/tests/test_hull.py | 6 +-- .../plugins/solvers/gurobi_persistent.py | 10 ++-- 22 files changed, 105 insertions(+), 109 deletions(-) diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index 9d00a56e8b9..6655ec26524 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -21,7 +21,7 @@ Tuple, MutableMapping, ) -from pyomo.core.base.constraint import GeneralConstraintData, Constraint +from pyomo.core.base.constraint import ConstraintData, Constraint from pyomo.core.base.sos import SOSConstraintData, SOSConstraint from pyomo.core.base.var import VarData, Var from pyomo.core.base.param import ParamData, Param @@ -214,8 +214,8 @@ def get_primals( pass def get_duals( - self, cons_to_load: Optional[Sequence[GeneralConstraintData]] = None - ) -> Dict[GeneralConstraintData, float]: + self, cons_to_load: Optional[Sequence[ConstraintData]] = None + ) -> Dict[ConstraintData, float]: """ Returns a dictionary mapping constraint to dual value. @@ -233,8 +233,8 @@ 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]: + self, cons_to_load: Optional[Sequence[ConstraintData]] = None + ) -> Dict[ConstraintData, float]: """ Returns a dictionary mapping constraint to slack. @@ -317,8 +317,8 @@ def get_primals( return primals def get_duals( - self, cons_to_load: Optional[Sequence[GeneralConstraintData]] = None - ) -> Dict[GeneralConstraintData, float]: + self, cons_to_load: Optional[Sequence[ConstraintData]] = None + ) -> Dict[ConstraintData, float]: if self._duals is None: raise RuntimeError( 'Solution loader does not currently have valid duals. Please ' @@ -334,8 +334,8 @@ def get_duals( return duals def get_slacks( - self, cons_to_load: Optional[Sequence[GeneralConstraintData]] = None - ) -> Dict[GeneralConstraintData, float]: + self, cons_to_load: Optional[Sequence[ConstraintData]] = None + ) -> Dict[ConstraintData, float]: if self._slacks is None: raise RuntimeError( 'Solution loader does not currently have valid slacks. Please ' @@ -727,8 +727,8 @@ def get_primals( pass def get_duals( - self, cons_to_load: Optional[Sequence[GeneralConstraintData]] = None - ) -> Dict[GeneralConstraintData, float]: + self, cons_to_load: Optional[Sequence[ConstraintData]] = None + ) -> Dict[ConstraintData, float]: """ Declare sign convention in docstring here. @@ -748,8 +748,8 @@ def get_duals( ) def get_slacks( - self, cons_to_load: Optional[Sequence[GeneralConstraintData]] = None - ) -> Dict[GeneralConstraintData, float]: + self, cons_to_load: Optional[Sequence[ConstraintData]] = None + ) -> Dict[ConstraintData, float]: """ Parameters ---------- @@ -803,7 +803,7 @@ def add_params(self, params: List[ParamData]): pass @abc.abstractmethod - def add_constraints(self, cons: List[GeneralConstraintData]): + def add_constraints(self, cons: List[ConstraintData]): pass @abc.abstractmethod @@ -819,7 +819,7 @@ def remove_params(self, params: List[ParamData]): pass @abc.abstractmethod - def remove_constraints(self, cons: List[GeneralConstraintData]): + def remove_constraints(self, cons: List[ConstraintData]): pass @abc.abstractmethod @@ -853,14 +853,14 @@ def get_primals(self, vars_to_load=None): 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, cons_to_load: Optional[Sequence[ConstraintData]] = None + ) -> Dict[ConstraintData, 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, cons_to_load: Optional[Sequence[ConstraintData]] = None + ) -> Dict[ConstraintData, float]: self._assert_solution_still_valid() return self._solver.get_slacks(cons_to_load=cons_to_load) @@ -980,7 +980,7 @@ def add_params(self, params: List[ParamData]): self._add_params(params) @abc.abstractmethod - def _add_constraints(self, cons: List[GeneralConstraintData]): + def _add_constraints(self, cons: List[ConstraintData]): pass def _check_for_new_vars(self, variables: List[VarData]): @@ -1000,7 +1000,7 @@ def _check_to_remove_vars(self, variables: List[VarData]): vars_to_remove[v_id] = v self.remove_variables(list(vars_to_remove.values())) - def add_constraints(self, cons: List[GeneralConstraintData]): + def add_constraints(self, cons: List[ConstraintData]): all_fixed_vars = dict() for con in cons: if con in self._named_expressions: @@ -1128,10 +1128,10 @@ def add_block(self, block): self.set_objective(obj) @abc.abstractmethod - def _remove_constraints(self, cons: List[GeneralConstraintData]): + def _remove_constraints(self, cons: List[ConstraintData]): pass - def remove_constraints(self, cons: List[GeneralConstraintData]): + def remove_constraints(self, cons: List[ConstraintData]): self._remove_constraints(cons) for con in cons: if con not in self._named_expressions: @@ -1330,7 +1330,7 @@ def update(self, timer: HierarchicalTimer = None): 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) + c.ctype is None and isinstance(c, ConstraintData) ): old_cons.append(c) else: diff --git a/pyomo/contrib/appsi/fbbt.py b/pyomo/contrib/appsi/fbbt.py index 4b0d6d4876c..8e0c74b00e9 100644 --- a/pyomo/contrib/appsi/fbbt.py +++ b/pyomo/contrib/appsi/fbbt.py @@ -20,7 +20,7 @@ from typing import List, Optional from pyomo.core.base.var import VarData from pyomo.core.base.param import ParamData -from pyomo.core.base.constraint import GeneralConstraintData +from pyomo.core.base.constraint import ConstraintData from pyomo.core.base.sos import SOSConstraintData from pyomo.core.base.objective import ObjectiveData, minimize, maximize from pyomo.core.base.block import BlockData @@ -154,7 +154,7 @@ def _add_params(self, params: List[ParamData]): cp = cparams[ndx] cp.name = self._symbol_map.getSymbol(p, self._param_labeler) - def _add_constraints(self, cons: List[GeneralConstraintData]): + def _add_constraints(self, cons: List[ConstraintData]): cmodel.process_fbbt_constraints( self._cmodel, self._pyomo_expr_types, @@ -175,7 +175,7 @@ def _add_sos_constraints(self, cons: List[SOSConstraintData]): 'IntervalTightener does not support SOS constraints' ) - def _remove_constraints(self, cons: List[GeneralConstraintData]): + def _remove_constraints(self, cons: List[ConstraintData]): if self._symbolic_solver_labels: for c in cons: self._symbol_map.removeSymbol(c) diff --git a/pyomo/contrib/appsi/solvers/cbc.py b/pyomo/contrib/appsi/solvers/cbc.py index dffd479a5c7..08833e747e2 100644 --- a/pyomo/contrib/appsi/solvers/cbc.py +++ b/pyomo/contrib/appsi/solvers/cbc.py @@ -27,7 +27,7 @@ from pyomo.common.collections import ComponentMap from typing import Optional, Sequence, NoReturn, List, Mapping from pyomo.core.base.var import VarData -from pyomo.core.base.constraint import GeneralConstraintData +from pyomo.core.base.constraint import ConstraintData from pyomo.core.base.block import BlockData from pyomo.core.base.param import ParamData from pyomo.core.base.objective import ObjectiveData @@ -170,7 +170,7 @@ def add_variables(self, variables: List[VarData]): def add_params(self, params: List[ParamData]): self._writer.add_params(params) - def add_constraints(self, cons: List[GeneralConstraintData]): + def add_constraints(self, cons: List[ConstraintData]): self._writer.add_constraints(cons) def add_block(self, block: BlockData): @@ -182,7 +182,7 @@ def remove_variables(self, variables: List[VarData]): def remove_params(self, params: List[ParamData]): self._writer.remove_params(params) - def remove_constraints(self, cons: List[GeneralConstraintData]): + def remove_constraints(self, cons: List[ConstraintData]): self._writer.remove_constraints(cons) def remove_block(self, block: BlockData): diff --git a/pyomo/contrib/appsi/solvers/cplex.py b/pyomo/contrib/appsi/solvers/cplex.py index 22c11bdfbe8..10de981ce7d 100644 --- a/pyomo/contrib/appsi/solvers/cplex.py +++ b/pyomo/contrib/appsi/solvers/cplex.py @@ -23,7 +23,7 @@ from pyomo.common.collections import ComponentMap from typing import Optional, Sequence, NoReturn, List, Mapping, Dict from pyomo.core.base.var import VarData -from pyomo.core.base.constraint import GeneralConstraintData +from pyomo.core.base.constraint import ConstraintData from pyomo.core.base.block import BlockData from pyomo.core.base.param import ParamData from pyomo.core.base.objective import ObjectiveData @@ -185,7 +185,7 @@ def add_variables(self, variables: List[VarData]): def add_params(self, params: List[ParamData]): self._writer.add_params(params) - def add_constraints(self, cons: List[GeneralConstraintData]): + def add_constraints(self, cons: List[ConstraintData]): self._writer.add_constraints(cons) def add_block(self, block: BlockData): @@ -197,7 +197,7 @@ def remove_variables(self, variables: List[VarData]): def remove_params(self, params: List[ParamData]): self._writer.remove_params(params) - def remove_constraints(self, cons: List[GeneralConstraintData]): + def remove_constraints(self, cons: List[ConstraintData]): self._writer.remove_constraints(cons) def remove_block(self, block: BlockData): @@ -389,8 +389,8 @@ def get_primals( return res def get_duals( - self, cons_to_load: Optional[Sequence[GeneralConstraintData]] = None - ) -> Dict[GeneralConstraintData, float]: + self, cons_to_load: Optional[Sequence[ConstraintData]] = None + ) -> Dict[ConstraintData, float]: if ( self._cplex_model.solution.get_solution_type() == self._cplex_model.solution.type.none diff --git a/pyomo/contrib/appsi/solvers/gurobi.py b/pyomo/contrib/appsi/solvers/gurobi.py index e2ecd9b69e7..2719ecc2a00 100644 --- a/pyomo/contrib/appsi/solvers/gurobi.py +++ b/pyomo/contrib/appsi/solvers/gurobi.py @@ -24,7 +24,7 @@ from pyomo.core.kernel.objective import minimize, maximize from pyomo.core.base import SymbolMap, NumericLabeler, TextLabeler from pyomo.core.base.var import Var, VarData -from pyomo.core.base.constraint import GeneralConstraintData +from pyomo.core.base.constraint import ConstraintData 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 @@ -579,7 +579,7 @@ def _get_expr_from_pyomo_expr(self, expr): mutable_quadratic_coefficients, ) - def _add_constraints(self, cons: List[GeneralConstraintData]): + def _add_constraints(self, cons: List[ConstraintData]): for con in cons: conname = self._symbol_map.getSymbol(con, self._labeler) ( @@ -735,7 +735,7 @@ def _add_sos_constraints(self, cons: List[SOSConstraintData]): self._constraints_added_since_update.update(cons) self._needs_updated = True - def _remove_constraints(self, cons: List[GeneralConstraintData]): + def _remove_constraints(self, cons: List[ConstraintData]): for con in cons: if con in self._constraints_added_since_update: self._update_gurobi_model() @@ -1195,7 +1195,7 @@ def set_linear_constraint_attr(self, con, attr, val): Parameters ---------- - con: pyomo.core.base.constraint.GeneralConstraintData + con: pyomo.core.base.constraint.ConstraintData The pyomo constraint for which the corresponding gurobi constraint attribute should be modified. attr: str @@ -1272,7 +1272,7 @@ def get_linear_constraint_attr(self, con, attr): Parameters ---------- - con: pyomo.core.base.constraint.GeneralConstraintData + con: pyomo.core.base.constraint.ConstraintData The pyomo constraint for which the corresponding gurobi constraint attribute should be retrieved. attr: str @@ -1304,7 +1304,7 @@ def get_quadratic_constraint_attr(self, con, attr): Parameters ---------- - con: pyomo.core.base.constraint.GeneralConstraintData + con: pyomo.core.base.constraint.ConstraintData The pyomo constraint for which the corresponding gurobi constraint attribute should be retrieved. attr: str @@ -1425,7 +1425,7 @@ def cbCut(self, con): Parameters ---------- - con: pyomo.core.base.constraint.GeneralConstraintData + con: pyomo.core.base.constraint.ConstraintData The cut to add """ if not con.active: @@ -1510,7 +1510,7 @@ def cbLazy(self, con): """ Parameters ---------- - con: pyomo.core.base.constraint.GeneralConstraintData + con: pyomo.core.base.constraint.ConstraintData The lazy constraint to add """ if not con.active: diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index c3083ac78d3..6410700c569 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -21,7 +21,7 @@ from pyomo.core.kernel.objective import minimize, maximize from pyomo.core.base import SymbolMap from pyomo.core.base.var import VarData -from pyomo.core.base.constraint import GeneralConstraintData +from pyomo.core.base.constraint import ConstraintData from pyomo.core.base.sos import SOSConstraintData from pyomo.core.base.param import ParamData from pyomo.core.expr.numvalue import value, is_constant @@ -376,7 +376,7 @@ def set_instance(self, model): if self._objective is None: self.set_objective(None) - def _add_constraints(self, cons: List[GeneralConstraintData]): + def _add_constraints(self, cons: List[ConstraintData]): self._sol = None if self._last_results_object is not None: self._last_results_object.solution_loader.invalidate() @@ -462,7 +462,7 @@ def _add_sos_constraints(self, cons: List[SOSConstraintData]): 'Highs interface does not support SOS constraints' ) - def _remove_constraints(self, cons: List[GeneralConstraintData]): + def _remove_constraints(self, cons: List[ConstraintData]): self._sol = None if self._last_results_object is not None: self._last_results_object.solution_loader.invalidate() diff --git a/pyomo/contrib/appsi/solvers/ipopt.py b/pyomo/contrib/appsi/solvers/ipopt.py index 4144fbbecd9..76cd204e36d 100644 --- a/pyomo/contrib/appsi/solvers/ipopt.py +++ b/pyomo/contrib/appsi/solvers/ipopt.py @@ -29,7 +29,7 @@ from pyomo.core.expr.visitor import replace_expressions from typing import Optional, Sequence, NoReturn, List, Mapping from pyomo.core.base.var import VarData -from pyomo.core.base.constraint import GeneralConstraintData +from pyomo.core.base.constraint import ConstraintData from pyomo.core.base.block import BlockData from pyomo.core.base.param import ParamData from pyomo.core.base.objective import ObjectiveData @@ -234,7 +234,7 @@ def add_variables(self, variables: List[VarData]): def add_params(self, params: List[ParamData]): self._writer.add_params(params) - def add_constraints(self, cons: List[GeneralConstraintData]): + def add_constraints(self, cons: List[ConstraintData]): self._writer.add_constraints(cons) def add_block(self, block: BlockData): @@ -246,7 +246,7 @@ def remove_variables(self, variables: List[VarData]): def remove_params(self, params: List[ParamData]): self._writer.remove_params(params) - def remove_constraints(self, cons: List[GeneralConstraintData]): + def remove_constraints(self, cons: List[ConstraintData]): self._writer.remove_constraints(cons) def remove_block(self, block: BlockData): @@ -534,7 +534,7 @@ def get_primals( res[v] = self._primal_sol[v] return res - def get_duals(self, cons_to_load: Optional[Sequence[GeneralConstraintData]] = None): + def get_duals(self, cons_to_load: Optional[Sequence[ConstraintData]] = None): if ( self._last_results_object is None or self._last_results_object.termination_condition diff --git a/pyomo/contrib/appsi/solvers/wntr.py b/pyomo/contrib/appsi/solvers/wntr.py index 62c4b0ed358..0a66cc640e5 100644 --- a/pyomo/contrib/appsi/solvers/wntr.py +++ b/pyomo/contrib/appsi/solvers/wntr.py @@ -42,7 +42,7 @@ from pyomo.core.base.block import BlockData from pyomo.core.base.var import VarData from pyomo.core.base.param import ParamData -from pyomo.core.base.constraint import GeneralConstraintData +from pyomo.core.base.constraint import ConstraintData from pyomo.common.timing import HierarchicalTimer from pyomo.core.base import SymbolMap, NumericLabeler, TextLabeler from pyomo.common.dependencies import attempt_import @@ -278,7 +278,7 @@ def _add_params(self, params: List[ParamData]): 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]): + def _add_constraints(self, cons: List[ConstraintData]): aml = wntr.sim.aml.aml for con in cons: if not con.equality: @@ -294,7 +294,7 @@ def _add_constraints(self, cons: List[GeneralConstraintData]): self._pyomo_con_to_solver_con_map[con] = wntr_con self._needs_updated = True - def _remove_constraints(self, cons: List[GeneralConstraintData]): + def _remove_constraints(self, cons: List[ConstraintData]): for con in cons: solver_con = self._pyomo_con_to_solver_con_map[con] delattr(self._solver_model, solver_con.name) diff --git a/pyomo/contrib/appsi/writers/lp_writer.py b/pyomo/contrib/appsi/writers/lp_writer.py index 3a6193bd314..788dfde7892 100644 --- a/pyomo/contrib/appsi/writers/lp_writer.py +++ b/pyomo/contrib/appsi/writers/lp_writer.py @@ -12,7 +12,7 @@ from typing import List from pyomo.core.base.param import ParamData from pyomo.core.base.var import VarData -from pyomo.core.base.constraint import GeneralConstraintData +from pyomo.core.base.constraint import ConstraintData from pyomo.core.base.objective import ObjectiveData from pyomo.core.base.sos import SOSConstraintData from pyomo.core.base.block import BlockData @@ -99,14 +99,14 @@ def _add_params(self, params: List[ParamData]): cp.value = p.value self._pyomo_param_to_solver_param_map[id(p)] = cp - def _add_constraints(self, cons: List[GeneralConstraintData]): + def _add_constraints(self, cons: List[ConstraintData]): cmodel.process_lp_constraints(cons, self) def _add_sos_constraints(self, cons: List[SOSConstraintData]): if len(cons) != 0: raise NotImplementedError('LP writer does not yet support SOS constraints') - def _remove_constraints(self, cons: List[GeneralConstraintData]): + def _remove_constraints(self, cons: List[ConstraintData]): for c in cons: cc = self._pyomo_con_to_solver_con_map.pop(c) self._writer.remove_constraint(cc) diff --git a/pyomo/contrib/appsi/writers/nl_writer.py b/pyomo/contrib/appsi/writers/nl_writer.py index 754bd179497..27cdca004cb 100644 --- a/pyomo/contrib/appsi/writers/nl_writer.py +++ b/pyomo/contrib/appsi/writers/nl_writer.py @@ -12,7 +12,7 @@ from typing import List from pyomo.core.base.param import ParamData from pyomo.core.base.var import VarData -from pyomo.core.base.constraint import GeneralConstraintData +from pyomo.core.base.constraint import ConstraintData from pyomo.core.base.objective import ObjectiveData from pyomo.core.base.sos import SOSConstraintData from pyomo.core.base.block import BlockData @@ -111,7 +111,7 @@ def _add_params(self, params: List[ParamData]): cp = cparams[ndx] cp.name = self._symbol_map.getSymbol(p, self._param_labeler) - def _add_constraints(self, cons: List[GeneralConstraintData]): + def _add_constraints(self, cons: List[ConstraintData]): cmodel.process_nl_constraints( self._writer, self._expr_types, @@ -130,7 +130,7 @@ def _add_sos_constraints(self, cons: List[SOSConstraintData]): if len(cons) != 0: raise NotImplementedError('NL writer does not support SOS constraints') - def _remove_constraints(self, cons: List[GeneralConstraintData]): + def _remove_constraints(self, cons: List[ConstraintData]): if self.config.symbolic_solver_labels: for c in cons: self._symbol_map.removeSymbol(c) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index c53f917bc2a..45b5cca0179 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -14,7 +14,7 @@ from typing import Sequence, Dict, Optional, Mapping, NoReturn, List, Tuple import os -from pyomo.core.base.constraint import GeneralConstraintData +from pyomo.core.base.constraint import ConstraintData from pyomo.core.base.var import VarData from pyomo.core.base.param import ParamData from pyomo.core.base.block import BlockData @@ -230,8 +230,8 @@ def _get_primals( ) def _get_duals( - self, cons_to_load: Optional[Sequence[GeneralConstraintData]] = None - ) -> Dict[GeneralConstraintData, float]: + self, cons_to_load: Optional[Sequence[ConstraintData]] = None + ) -> Dict[ConstraintData, float]: """ Declare sign convention in docstring here. @@ -292,7 +292,7 @@ def add_parameters(self, params: List[ParamData]): """ @abc.abstractmethod - def add_constraints(self, cons: List[GeneralConstraintData]): + def add_constraints(self, cons: List[ConstraintData]): """ Add constraints to the model """ @@ -316,7 +316,7 @@ def remove_parameters(self, params: List[ParamData]): """ @abc.abstractmethod - def remove_constraints(self, cons: List[GeneralConstraintData]): + def remove_constraints(self, cons: List[ConstraintData]): """ Remove constraints from the model """ diff --git a/pyomo/contrib/solver/gurobi.py b/pyomo/contrib/solver/gurobi.py index ff4e93f7635..10d8120c8b3 100644 --- a/pyomo/contrib/solver/gurobi.py +++ b/pyomo/contrib/solver/gurobi.py @@ -23,7 +23,7 @@ from pyomo.core.kernel.objective import minimize, maximize from pyomo.core.base import SymbolMap, NumericLabeler, TextLabeler from pyomo.core.base.var import VarData -from pyomo.core.base.constraint import GeneralConstraintData +from pyomo.core.base.constraint import ConstraintData 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 @@ -555,7 +555,7 @@ def _get_expr_from_pyomo_expr(self, expr): mutable_quadratic_coefficients, ) - def _add_constraints(self, cons: List[GeneralConstraintData]): + def _add_constraints(self, cons: List[ConstraintData]): for con in cons: conname = self._symbol_map.getSymbol(con, self._labeler) ( @@ -711,7 +711,7 @@ def _add_sos_constraints(self, cons: List[SOSConstraintData]): self._constraints_added_since_update.update(cons) self._needs_updated = True - def _remove_constraints(self, cons: List[GeneralConstraintData]): + def _remove_constraints(self, cons: List[ConstraintData]): for con in cons: if con in self._constraints_added_since_update: self._update_gurobi_model() @@ -1125,7 +1125,7 @@ def set_linear_constraint_attr(self, con, attr, val): Parameters ---------- - con: pyomo.core.base.constraint.GeneralConstraintData + con: pyomo.core.base.constraint.ConstraintData The pyomo constraint for which the corresponding gurobi constraint attribute should be modified. attr: str @@ -1202,7 +1202,7 @@ def get_linear_constraint_attr(self, con, attr): Parameters ---------- - con: pyomo.core.base.constraint.GeneralConstraintData + con: pyomo.core.base.constraint.ConstraintData The pyomo constraint for which the corresponding gurobi constraint attribute should be retrieved. attr: str @@ -1234,7 +1234,7 @@ def get_quadratic_constraint_attr(self, con, attr): Parameters ---------- - con: pyomo.core.base.constraint.GeneralConstraintData + con: pyomo.core.base.constraint.ConstraintData The pyomo constraint for which the corresponding gurobi constraint attribute should be retrieved. attr: str @@ -1355,7 +1355,7 @@ def cbCut(self, con): Parameters ---------- - con: pyomo.core.base.constraint.GeneralConstraintData + con: pyomo.core.base.constraint.ConstraintData The cut to add """ if not con.active: @@ -1440,7 +1440,7 @@ def cbLazy(self, con): """ Parameters ---------- - con: pyomo.core.base.constraint.GeneralConstraintData + con: pyomo.core.base.constraint.ConstraintData The lazy constraint to add """ if not con.active: diff --git a/pyomo/contrib/solver/persistent.py b/pyomo/contrib/solver/persistent.py index 81d0df1334f..71322b7043e 100644 --- a/pyomo/contrib/solver/persistent.py +++ b/pyomo/contrib/solver/persistent.py @@ -12,7 +12,7 @@ import abc from typing import List -from pyomo.core.base.constraint import GeneralConstraintData, Constraint +from pyomo.core.base.constraint import ConstraintData, Constraint from pyomo.core.base.sos import SOSConstraintData, SOSConstraint from pyomo.core.base.var import VarData from pyomo.core.base.param import ParamData, Param @@ -84,7 +84,7 @@ def add_parameters(self, params: List[ParamData]): self._add_parameters(params) @abc.abstractmethod - def _add_constraints(self, cons: List[GeneralConstraintData]): + def _add_constraints(self, cons: List[ConstraintData]): pass def _check_for_new_vars(self, variables: List[VarData]): @@ -104,7 +104,7 @@ def _check_to_remove_vars(self, variables: List[VarData]): vars_to_remove[v_id] = v self.remove_variables(list(vars_to_remove.values())) - def add_constraints(self, cons: List[GeneralConstraintData]): + def add_constraints(self, cons: List[ConstraintData]): all_fixed_vars = {} for con in cons: if con in self._named_expressions: @@ -209,10 +209,10 @@ def add_block(self, block): self.set_objective(obj) @abc.abstractmethod - def _remove_constraints(self, cons: List[GeneralConstraintData]): + def _remove_constraints(self, cons: List[ConstraintData]): pass - def remove_constraints(self, cons: List[GeneralConstraintData]): + def remove_constraints(self, cons: List[ConstraintData]): self._remove_constraints(cons) for con in cons: if con not in self._named_expressions: @@ -384,7 +384,7 @@ def update(self, timer: HierarchicalTimer = None): 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) + c.ctype is None and isinstance(c, ConstraintData) ): old_cons.append(c) else: diff --git a/pyomo/contrib/solver/solution.py b/pyomo/contrib/solver/solution.py index e089e621f1f..a3e66475982 100644 --- a/pyomo/contrib/solver/solution.py +++ b/pyomo/contrib/solver/solution.py @@ -12,7 +12,7 @@ import abc from typing import Sequence, Dict, Optional, Mapping, NoReturn -from pyomo.core.base.constraint import GeneralConstraintData +from pyomo.core.base.constraint import ConstraintData from pyomo.core.base.var import VarData from pyomo.core.expr import value from pyomo.common.collections import ComponentMap @@ -65,8 +65,8 @@ def get_primals( """ def get_duals( - self, cons_to_load: Optional[Sequence[GeneralConstraintData]] = None - ) -> Dict[GeneralConstraintData, float]: + self, cons_to_load: Optional[Sequence[ConstraintData]] = None + ) -> Dict[ConstraintData, float]: """ Returns a dictionary mapping constraint to dual value. @@ -119,8 +119,8 @@ def get_primals(self, vars_to_load=None): 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, cons_to_load: Optional[Sequence[ConstraintData]] = None + ) -> Dict[ConstraintData, float]: self._assert_solution_still_valid() return self._solver._get_duals(cons_to_load=cons_to_load) @@ -201,8 +201,8 @@ def get_primals( return res def get_duals( - self, cons_to_load: Optional[Sequence[GeneralConstraintData]] = None - ) -> Dict[GeneralConstraintData, float]: + self, cons_to_load: Optional[Sequence[ConstraintData]] = None + ) -> Dict[ConstraintData, float]: if self._nl_info is None: raise RuntimeError( 'Solution loader does not currently have a valid solution. Please ' diff --git a/pyomo/contrib/solver/tests/unit/test_results.py b/pyomo/contrib/solver/tests/unit/test_results.py index 6c178d80298..a15c9b87253 100644 --- a/pyomo/contrib/solver/tests/unit/test_results.py +++ b/pyomo/contrib/solver/tests/unit/test_results.py @@ -15,7 +15,7 @@ from pyomo.common import unittest from pyomo.common.config import ConfigDict -from pyomo.core.base.constraint import GeneralConstraintData +from pyomo.core.base.constraint import ConstraintData from pyomo.core.base.var import VarData from pyomo.common.collections import ComponentMap from pyomo.contrib.solver import results @@ -67,8 +67,8 @@ def get_primals( return primals def get_duals( - self, cons_to_load: Optional[Sequence[GeneralConstraintData]] = None - ) -> Dict[GeneralConstraintData, float]: + self, cons_to_load: Optional[Sequence[ConstraintData]] = None + ) -> Dict[ConstraintData, float]: if self._duals is None: raise RuntimeError( 'Solution loader does not currently have valid duals. Please ' diff --git a/pyomo/core/tests/unit/test_con.py b/pyomo/core/tests/unit/test_con.py index 26ccc7944a7..15f190e281e 100644 --- a/pyomo/core/tests/unit/test_con.py +++ b/pyomo/core/tests/unit/test_con.py @@ -44,7 +44,7 @@ InequalityExpression, RangedExpression, ) -from pyomo.core.base.constraint import GeneralConstraintData +from pyomo.core.base.constraint import ConstraintData class TestConstraintCreation(unittest.TestCase): @@ -1074,7 +1074,7 @@ def test_setitem(self): m.c[2] = m.x**2 <= 4 self.assertEqual(len(m.c), 1) self.assertEqual(list(m.c.keys()), [2]) - self.assertIsInstance(m.c[2], GeneralConstraintData) + self.assertIsInstance(m.c[2], ConstraintData) self.assertEqual(m.c[2].upper, 4) m.c[3] = Constraint.Skip diff --git a/pyomo/core/tests/unit/test_dict_objects.py b/pyomo/core/tests/unit/test_dict_objects.py index 16b7e0bd2e0..ef9f330bfff 100644 --- a/pyomo/core/tests/unit/test_dict_objects.py +++ b/pyomo/core/tests/unit/test_dict_objects.py @@ -18,7 +18,7 @@ ExpressionDict, ) from pyomo.core.base.var import VarData -from pyomo.core.base.constraint import GeneralConstraintData +from pyomo.core.base.constraint import ConstraintData from pyomo.core.base.objective import ObjectiveData from pyomo.core.base.expression import ExpressionData @@ -375,7 +375,7 @@ def setUp(self): class TestConstraintDict(_TestActiveComponentDictBase, unittest.TestCase): _ctype = ConstraintDict - _cdatatype = GeneralConstraintData + _cdatatype = ConstraintData def setUp(self): _TestComponentDictBase.setUp(self) diff --git a/pyomo/core/tests/unit/test_list_objects.py b/pyomo/core/tests/unit/test_list_objects.py index 94913bcbc02..671a8429e06 100644 --- a/pyomo/core/tests/unit/test_list_objects.py +++ b/pyomo/core/tests/unit/test_list_objects.py @@ -18,7 +18,7 @@ XExpressionList, ) from pyomo.core.base.var import VarData -from pyomo.core.base.constraint import GeneralConstraintData +from pyomo.core.base.constraint import ConstraintData from pyomo.core.base.objective import ObjectiveData from pyomo.core.base.expression import ExpressionData @@ -392,7 +392,7 @@ def setUp(self): class TestConstraintList(_TestActiveComponentListBase, unittest.TestCase): _ctype = XConstraintList - _cdatatype = GeneralConstraintData + _cdatatype = ConstraintData def setUp(self): _TestComponentListBase.setUp(self) diff --git a/pyomo/gdp/tests/common_tests.py b/pyomo/gdp/tests/common_tests.py index 233c3ca9c09..50bc8b05f86 100644 --- a/pyomo/gdp/tests/common_tests.py +++ b/pyomo/gdp/tests/common_tests.py @@ -952,9 +952,7 @@ def check_disjunction_data_target(self, transformation): transBlock = m.component("_pyomo_gdp_%s_reformulation" % transformation) self.assertIsInstance(transBlock, Block) self.assertIsInstance(transBlock.component("disjunction_xor"), Constraint) - self.assertIsInstance( - transBlock.disjunction_xor[2], constraint.GeneralConstraintData - ) + self.assertIsInstance(transBlock.disjunction_xor[2], constraint.ConstraintData) self.assertIsInstance(transBlock.component("relaxedDisjuncts"), Block) self.assertEqual(len(transBlock.relaxedDisjuncts), 3) @@ -963,7 +961,7 @@ def check_disjunction_data_target(self, transformation): m, targets=[m.disjunction[1]] ) self.assertIsInstance( - m.disjunction[1].algebraic_constraint, constraint.GeneralConstraintData + m.disjunction[1].algebraic_constraint, constraint.ConstraintData ) transBlock = m.component("_pyomo_gdp_%s_reformulation_4" % transformation) self.assertIsInstance(transBlock, Block) diff --git a/pyomo/gdp/tests/test_bigm.py b/pyomo/gdp/tests/test_bigm.py index efef4c5fb1f..cf42eb260ff 100644 --- a/pyomo/gdp/tests/test_bigm.py +++ b/pyomo/gdp/tests/test_bigm.py @@ -1323,8 +1323,8 @@ def test_do_not_transform_deactivated_constraintDatas(self): self.assertEqual(len(cons_list), 2) lb = cons_list[0] ub = cons_list[1] - self.assertIsInstance(lb, constraint.GeneralConstraintData) - self.assertIsInstance(ub, constraint.GeneralConstraintData) + self.assertIsInstance(lb, constraint.ConstraintData) + self.assertIsInstance(ub, constraint.ConstraintData) def checkMs( self, m, disj1c1lb, disj1c1ub, disj1c2lb, disj1c2ub, disj2c1ub, disj2c2ub diff --git a/pyomo/gdp/tests/test_hull.py b/pyomo/gdp/tests/test_hull.py index 6093e01dc25..07876a9d213 100644 --- a/pyomo/gdp/tests/test_hull.py +++ b/pyomo/gdp/tests/test_hull.py @@ -1252,12 +1252,10 @@ def check_second_iteration(self, model): orig = model.component("_pyomo_gdp_hull_reformulation") self.assertIsInstance( - model.disjunctionList[1].algebraic_constraint, - constraint.GeneralConstraintData, + model.disjunctionList[1].algebraic_constraint, constraint.ConstraintData ) self.assertIsInstance( - model.disjunctionList[0].algebraic_constraint, - constraint.GeneralConstraintData, + model.disjunctionList[0].algebraic_constraint, constraint.ConstraintData ) self.assertFalse(model.disjunctionList[1].active) self.assertFalse(model.disjunctionList[0].active) diff --git a/pyomo/solvers/plugins/solvers/gurobi_persistent.py b/pyomo/solvers/plugins/solvers/gurobi_persistent.py index 17ce33fd95f..94a2ac6b734 100644 --- a/pyomo/solvers/plugins/solvers/gurobi_persistent.py +++ b/pyomo/solvers/plugins/solvers/gurobi_persistent.py @@ -157,7 +157,7 @@ def set_linear_constraint_attr(self, con, attr, val): Parameters ---------- - con: pyomo.core.base.constraint.GeneralConstraintData + con: pyomo.core.base.constraint.ConstraintData The pyomo constraint for which the corresponding gurobi constraint attribute should be modified. attr: str @@ -384,7 +384,7 @@ def get_linear_constraint_attr(self, con, attr): Parameters ---------- - con: pyomo.core.base.constraint.GeneralConstraintData + con: pyomo.core.base.constraint.ConstraintData The pyomo constraint for which the corresponding gurobi constraint attribute should be retrieved. attr: str @@ -431,7 +431,7 @@ def get_quadratic_constraint_attr(self, con, attr): Parameters ---------- - con: pyomo.core.base.constraint.GeneralConstraintData + con: pyomo.core.base.constraint.ConstraintData The pyomo constraint for which the corresponding gurobi constraint attribute should be retrieved. attr: str @@ -569,7 +569,7 @@ def cbCut(self, con): Parameters ---------- - con: pyomo.core.base.constraint.GeneralConstraintData + con: pyomo.core.base.constraint.ConstraintData The cut to add """ if not con.active: @@ -647,7 +647,7 @@ def cbLazy(self, con): """ Parameters ---------- - con: pyomo.core.base.constraint.GeneralConstraintData + con: pyomo.core.base.constraint.ConstraintData The lazy constraint to add """ if not con.active: From ef522d9338eb8722737d0522fe65bfd8e3c96ec9 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 21 Mar 2024 15:25:22 -0600 Subject: [PATCH 1486/1797] Fix base class --- pyomo/core/base/constraint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/core/base/constraint.py b/pyomo/core/base/constraint.py index 08c97d7c8ae..3a71758d55d 100644 --- a/pyomo/core/base/constraint.py +++ b/pyomo/core/base/constraint.py @@ -125,7 +125,7 @@ def C_rule(model, i, j): return rule_wrapper(rule, result_map, map_types=map_types) -class ConstraintData(ConstraintData): +class ConstraintData(ActiveComponentData): """ This class defines the data for a single general constraint. From e55007991d8ec0cadc84bc12bbddfcb01225bdcb Mon Sep 17 00:00:00 2001 From: Eslick Date: Fri, 22 Mar 2024 14:47:31 -0400 Subject: [PATCH 1487/1797] Fix for duplicate add --- pyomo/solvers/plugins/solvers/ASL.py | 4 +++- pyomo/solvers/plugins/solvers/IPOPT.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pyomo/solvers/plugins/solvers/ASL.py b/pyomo/solvers/plugins/solvers/ASL.py index ae7ad82c870..3ebe5c3b422 100644 --- a/pyomo/solvers/plugins/solvers/ASL.py +++ b/pyomo/solvers/plugins/solvers/ASL.py @@ -160,7 +160,9 @@ def create_command_line(self, executable, problem_files): # if 'PYOMO_AMPLFUNC' in env: if 'AMPLFUNC' in env: - env['AMPLFUNC'] += "\n" + env['PYOMO_AMPLFUNC'] + for line in env['PYOMO_AMPLFUNC'].split('\n'): + if line not in env['AMPLFUNC']: + env['AMPLFUNC'] += "\n" + line else: env['AMPLFUNC'] = env['PYOMO_AMPLFUNC'] diff --git a/pyomo/solvers/plugins/solvers/IPOPT.py b/pyomo/solvers/plugins/solvers/IPOPT.py index 4ebbbc07d3b..17b68da6364 100644 --- a/pyomo/solvers/plugins/solvers/IPOPT.py +++ b/pyomo/solvers/plugins/solvers/IPOPT.py @@ -121,7 +121,9 @@ def create_command_line(self, executable, problem_files): # if 'PYOMO_AMPLFUNC' in env: if 'AMPLFUNC' in env: - env['AMPLFUNC'] += "\n" + env['PYOMO_AMPLFUNC'] + for line in env['PYOMO_AMPLFUNC'].split('\n'): + if line not in env['AMPLFUNC']: + env['AMPLFUNC'] += "\n" + line else: env['AMPLFUNC'] = env['PYOMO_AMPLFUNC'] From 035cf9c6ff5e3482a283fb6ea21714db35a56709 Mon Sep 17 00:00:00 2001 From: Soren Davis Date: Fri, 22 Mar 2024 18:57:03 -0400 Subject: [PATCH 1488/1797] replace an error check that should never happen with a comment --- pyomo/contrib/piecewise/transform/disaggregated_logarithmic.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyomo/contrib/piecewise/transform/disaggregated_logarithmic.py b/pyomo/contrib/piecewise/transform/disaggregated_logarithmic.py index edb1a03afe6..d5de010f308 100644 --- a/pyomo/contrib/piecewise/transform/disaggregated_logarithmic.py +++ b/pyomo/contrib/piecewise/transform/disaggregated_logarithmic.py @@ -191,9 +191,8 @@ def x_constraint(b, i): # Not a Gray code, just a regular binary representation # TODO test the Gray codes too + # note: Must have num != 0 and ceil(log2(num)) > length to be valid def _get_binary_vector(self, num, length): - if num != 0 and ceil(log2(num)) > length: - raise DeveloperError("Invalid input in _get_binary_vector") # Use python's string formatting instead of bothering with modular # arithmetic. Hopefully not slow. return tuple(int(x) for x in format(num, f"0{length}b")) From 9f63effd966bbe232e4774d26c27cfb242391976 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Fri, 22 Mar 2024 21:16:49 -0600 Subject: [PATCH 1489/1797] initial implementation of function to perform the full (fine and coarse) dulmage-mendelsohn decomposition --- pyomo/contrib/incidence_analysis/config.py | 7 +++ pyomo/contrib/incidence_analysis/interface.py | 47 ++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/incidence_analysis/config.py b/pyomo/contrib/incidence_analysis/config.py index 128273b4dec..2a7734ba433 100644 --- a/pyomo/contrib/incidence_analysis/config.py +++ b/pyomo/contrib/incidence_analysis/config.py @@ -36,6 +36,13 @@ class IncidenceMethod(enum.Enum): """Use ``pyomo.repn.plugins.nl_writer.AMPLRepnVisitor``""" +class IncidenceOrder(enum.Enum): + + dulmage_mendelsohn_upper = 0 + + dulmage_mendelsohn_lower = 1 + + _include_fixed = ConfigValue( default=False, domain=bool, diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index 50cb84daaf5..2136a4ffc24 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 get_config_from_kwds +from pyomo.contrib.incidence_analysis.config import get_config_from_kwds, IncidenceOrder 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 ( @@ -995,3 +995,48 @@ def add_edge(self, variable, constraint): con_id = self._con_index_map[constraint] self._incidence_graph.add_edge(var_id, con_id) + + def partition_variables_and_constraints( + self, + variables=None, + constraints=None, + order=IncidenceOrder.dulmage_mendelsohn_upper, + ): + """Partition variables and constraints in an incidence graph + """ + variables, constraints = self._validate_input(variables, constraints) + vdmp, cdmp = self.dulmage_mendelsohn(variables=variables, constraints=constraints) + + ucv = vdmp.unmatched + vdmp.underconstrained + ucc = cdmp.underconstrained + + ocv = vdmp.overconstrained + occ = cdmp.overconstrained + cdmp.unmatched + + ucvblocks, uccblocks = self.get_connected_components( + variables=ucv, constraints=ucc + ) + ocvblocks, occblocks = self.get_connected_components( + variables=ocv, constraints=occ + ) + wcvblocks, wccblocks = self.block_triangularize( + variables=vdmp.square, constraints=cdmp.square + ) + # By default, we block-*lower* triangularize. By default, however, we want + # the Dulmage-Mendelsohn decomposition to be block-*upper* triangular. + wcvblocks.reverse() + wccblocks.reverse() + vpartition = [ucvblocks, wcvblocks, ocvblocks] + cpartition = [uccblocks, wccblocks, occblocks] + + if order == IncidenceOrder.dulmage_mendelsohn_lower: + # If a block-lower triangular matrix was requested, we need to reverse + # both the inner and outer partitions + vpartition.reverse() + cpartition.reverse() + for vb in vpartition: + vb.reverse() + for cb in cpartition: + cb.reverse() + + return vpartition, cpartition From 87517fae3ec0a41f6abd93c39c8af5fe93494f4d Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Fri, 22 Mar 2024 22:41:52 -0600 Subject: [PATCH 1490/1797] draft of function to plot incidence matrix in dulmage-mendelsohn order --- pyomo/contrib/incidence_analysis/interface.py | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index 2136a4ffc24..6e6dff7ba48 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -1040,3 +1040,84 @@ def partition_variables_and_constraints( cb.reverse() return vpartition, cpartition + + +import matplotlib.pyplot as plt +from matplotlib.patches import Rectangle + +def _get_rectangle_around_coords( + ij1, + ij2, + linewidth=2, +): + i1, j1 = ij1 + i2, j2 = ij2 + buffer = 0.5 + ll_corner = (min(i1, i2)-buffer, min(j1, j2)-buffer) + width = abs(i1 - i2) + 2*buffer + height = abs(j1 - j2) + 2*buffer + rect = Rectangle( + ll_corner, + width, + height, + clip_on=False, + fill=False, + edgecolor="orange", + linewidth=linewidth, + ) + return rect + + +def spy_dulmage_mendelsohn( + model, + order=IncidenceOrder.dulmage_mendelsohn_upper, + highlight_coarse=True, + highlight_fine=True, + ax=None, +): + igraph = IncidenceGraphInterface(model) + vpart, cpart = igraph.partition_variables_and_constraints(order=order) + vpart_fine = sum(vpart, []) + cpart_fine = sum(cpart, []) + vorder = sum(vpart_fine, []) + corder = sum(cpart_fine, []) + + imat = get_structural_incidence_matrix(vorder, corder) + + if ax is None: + fig, ax = plt.subplots() + else: + fig = None + + # TODO: Options to configure: + # - tick direction/presence + # - rectangle linewidth/linestyle + # - spy markersize + # markersize and linewidth should probably be set automatically + # based on size of problem + + ax.spy( + imat, + # TODO: pass keyword args + markersize=0.2, + ) + ax.tick_params(length=0) + if highlight_coarse: + start = (0, 0) + for vblocks, cblocks in zip(vpart, cpart): + # Get the total number of variables/constraints in this part + # of the coarse partition + nv = sum(len(vb) for vb in vblocks) + nc = sum(len(cb) for cb in cblocks) + stop = (start[0] + nv - 1, start[1] + nc - 1) + ax.add_patch(_get_rectangle_around_coords(start, stop)) + start = (stop[0] + 1, stop[1] + 1) + + if highlight_fine: + start = (0, 0) + for vb, cb in zip(vpart_fine, cpart_fine): + stop = (start[0] + len(vb) - 1, start[1] + len(cb) - 1) + ax.add_patch(_get_rectangle_around_coords(start, stop)) + start = (stop[0] + 1, stop[1] + 1) + + return fig, ax From a573244f008af69eceb39c0812cd3d30b09df268 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Mar 2024 06:32:30 -0600 Subject: [PATCH 1491/1797] Rename _ComponentBase -> ComponentBase --- pyomo/core/base/component.py | 12 +++++++++--- pyomo/core/base/set.py | 4 ++-- pyomo/core/util.py | 6 +++--- pyomo/gdp/util.py | 1 - 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/pyomo/core/base/component.py b/pyomo/core/base/component.py index 50cf264c799..7dea5b7dde5 100644 --- a/pyomo/core/base/component.py +++ b/pyomo/core/base/component.py @@ -20,6 +20,7 @@ from pyomo.common.autoslots import AutoSlots, fast_deepcopy from pyomo.common.collections import OrderedDict from pyomo.common.deprecation import ( + RenamedClass, deprecated, deprecation_warning, relocated_module_attribute, @@ -79,7 +80,7 @@ class CloneError(pyomo.common.errors.PyomoException): pass -class _ComponentBase(PyomoObject): +class ComponentBase(PyomoObject): """A base class for Component and ComponentData This class defines some fundamental methods and properties that are @@ -474,7 +475,12 @@ def _pprint_base_impl( ostream.write(_data) -class Component(_ComponentBase): +class _ComponentBase(metaclass=RenamedClass): + __renamed__new_class__ = ComponentBase + __renamed__version__ = '6.7.2.dev0' + + +class Component(ComponentBase): """ This is the base class for all Pyomo modeling components. @@ -779,7 +785,7 @@ def deactivate(self): self._active = False -class ComponentData(_ComponentBase): +class ComponentData(ComponentBase): """ This is the base class for the component data used in Pyomo modeling components. Subclasses of ComponentData are diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index d94cc86cf7c..fbf1ac60900 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -50,7 +50,7 @@ RangeDifferenceError, ) from pyomo.core.base.component import ( - _ComponentBase, + ComponentBase, Component, ComponentData, ModelComponentFactory, @@ -140,7 +140,7 @@ def process_setarg(arg): _anonymous.update(arg._anonymous_sets) return arg, _anonymous - elif isinstance(arg, _ComponentBase): + elif isinstance(arg, ComponentBase): if isinstance(arg, IndexedComponent) and arg.is_indexed(): raise TypeError( "Cannot apply a Set operator to an " diff --git a/pyomo/core/util.py b/pyomo/core/util.py index f337b487cef..4b6cc8f3320 100644 --- a/pyomo/core/util.py +++ b/pyomo/core/util.py @@ -18,7 +18,7 @@ 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 +from pyomo.core.base.component import ComponentBase import logging logger = logging.getLogger(__name__) @@ -238,12 +238,12 @@ def sequence(*args): def target_list(x): - if isinstance(x, _ComponentBase): + if isinstance(x, ComponentBase): return [x] elif hasattr(x, '__iter__'): ans = [] for i in x: - if isinstance(i, _ComponentBase): + if isinstance(i, ComponentBase): ans.append(i) else: raise ValueError( diff --git a/pyomo/gdp/util.py b/pyomo/gdp/util.py index 686253b0179..932a2ddf451 100644 --- a/pyomo/gdp/util.py +++ b/pyomo/gdp/util.py @@ -13,7 +13,6 @@ from pyomo.gdp.disjunct import DisjunctData, Disjunct import pyomo.core.expr as EXPR -from pyomo.core.base.component import _ComponentBase from pyomo.core import ( Block, Suffix, From b75a974378012d4319668961f99a91668aab6446 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sat, 23 Mar 2024 06:46:04 -0600 Subject: [PATCH 1492/1797] Remove _SetDataBase --- pyomo/core/base/reference.py | 6 +++--- pyomo/core/base/set.py | 26 ++++++++++---------------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/pyomo/core/base/reference.py b/pyomo/core/base/reference.py index 2279db067a6..fd6ba192c70 100644 --- a/pyomo/core/base/reference.py +++ b/pyomo/core/base/reference.py @@ -18,7 +18,7 @@ Sequence, ) from pyomo.common.modeling import NOTSET -from pyomo.core.base.set import DeclareGlobalSet, Set, SetOf, OrderedSetOf, _SetDataBase +from pyomo.core.base.set import DeclareGlobalSet, Set, SetOf, OrderedSetOf, SetData from pyomo.core.base.component import Component, ComponentData from pyomo.core.base.global_set import UnindexedComponent_set from pyomo.core.base.enums import SortComponents @@ -774,10 +774,10 @@ def Reference(reference, ctype=NOTSET): # is that within the subsets list, and set is a wildcard set. index = wildcards[0][1] # index is the first wildcard set. - if not isinstance(index, _SetDataBase): + if not isinstance(index, SetData): index = SetOf(index) for lvl, idx in wildcards[1:]: - if not isinstance(idx, _SetDataBase): + if not isinstance(idx, SetData): idx = SetOf(idx) index = index * idx # index is now either a single Set, or a SetProduct of the diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index fbf1ac60900..b9a2fe72e1d 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -84,10 +84,7 @@ All Sets implement one of the following APIs: -0. `class _SetDataBase(ComponentData)` - *(pure virtual interface)* - -1. `class SetData(_SetDataBase)` +1. `class SetData(ComponentData)` *(base class for all AML Sets)* 2. `class _FiniteSetMixin(object)` @@ -128,7 +125,7 @@ def process_setarg(arg): - if isinstance(arg, _SetDataBase): + if isinstance(arg, SetData): if ( getattr(arg, '_parent', None) is not None or getattr(arg, '_anonymous_sets', None) is GlobalSetBase @@ -512,16 +509,8 @@ class _NotFound(object): pass -# A trivial class that we can use to test if an object is a "legitimate" -# set (either ScalarSet, or a member of an IndexedSet) -class _SetDataBase(ComponentData): - """The base for all objects that can be used as a component indexing set.""" - - __slots__ = () - - -class SetData(_SetDataBase): - """The base for all Pyomo AML objects that can be used as a component +class SetData(ComponentData): + """The base for all Pyomo objects that can be used as a component indexing set. Derived versions of this class can be used as the Index for any @@ -1193,6 +1182,11 @@ class _SetData(metaclass=RenamedClass): __renamed__version__ = '6.7.2.dev0' +class _SetDataBase(metaclass=RenamedClass): + __renamed__new_class__ = SetData + __renamed__version__ = '6.7.2.dev0' + + class _FiniteSetMixin(object): __slots__ = () @@ -3496,7 +3490,7 @@ def _domain(self, val): def _checkArgs(*sets): ans = [] for s in sets: - if isinstance(s, _SetDataBase): + if isinstance(s, SetData): ans.append((s.isordered(), s.isfinite())) elif type(s) in {tuple, list}: ans.append((True, True)) From fb2212ad07ab7abbae86d4e1a0a7d98e894e07a1 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Sat, 23 Mar 2024 12:42:53 -0600 Subject: [PATCH 1493/1797] document plotting function and automatically calculate markersize if not provided --- pyomo/contrib/incidence_analysis/interface.py | 115 +++++++++++++++--- 1 file changed, 99 insertions(+), 16 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index 6e6dff7ba48..a4f08c737ec 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -1049,6 +1049,7 @@ def _get_rectangle_around_coords( ij1, ij2, linewidth=2, + linestyle="-", ): i1, j1 = ij1 i2, j2 = ij2 @@ -1064,18 +1065,82 @@ def _get_rectangle_around_coords( fill=False, edgecolor="orange", linewidth=linewidth, + linestyle=linestyle, ) return rect def spy_dulmage_mendelsohn( model, + *, + incidence_kwds=None, order=IncidenceOrder.dulmage_mendelsohn_upper, highlight_coarse=True, highlight_fine=True, + skip_wellconstrained=False, ax=None, + linewidth=2, + spy_kwds=None, ): - igraph = IncidenceGraphInterface(model) + """Plot sparsity structure in Dulmage-Mendelsohn order on Matplotlib + axes + + This is a wrapper around the Matplotlib ``Axes.spy`` method for plotting + an incidence matrix in Dulmage-Mendelsohn order, with coarse and/or fine + partitions highlighted. The coarse partition refers to the under-constrained, + over-constrained, and well-constrained subsystems, while the fine partition + refers to block diagonal or block triangular partitions of the former + subsystems. + + Parameters + ---------- + + model: ``ConcreteModel`` + Input model to plot sparsity structure of + + incidence_kwds: dict, optional + Config options for ``IncidenceGraphInterface`` + + order: ``IncidenceOrder``, optional + Order in which to plot sparsity structure + + highlight_coarse: bool, optional + Whether to draw a rectange around the coarse partition + + highlight_fine: bool, optional + Whether to draw a rectangle around the fine partition + + skip_wellconstrained: bool, optional + Whether to skip highlighting the well-constrained subsystem of the + coarse partition. Default False + + ax: ``matplotlib.pyplot.Axes``, optional + Axes object on which to plot. If not provided, new figure + and axes are created. + + linewidth: int, optional + Line width of for rectangle used to highlight. Default 2 + + spy_kwds: dict, optional + Keyword arguments for ``Axes.spy`` + + Returns + ------- + + fig: ``matplotlib.pyplot.Figure`` or ``None`` + Figure on which the sparsity structure is plotted. ``None`` if axes + are provided + + ax: ``matplotlib.pyplot.Axes`` + Axes on which the sparsity structure is plotted + + """ + if incidence_kwds is None: + incidence_kwds = {} + if spy_kwds is None: + spy_kwds = {} + + igraph = IncidenceGraphInterface(model, **incidence_kwds) vpart, cpart = igraph.partition_variables_and_constraints(order=order) vpart_fine = sum(vpart, []) cpart_fine = sum(cpart, []) @@ -1083,41 +1148,59 @@ def spy_dulmage_mendelsohn( corder = sum(cpart_fine, []) imat = get_structural_incidence_matrix(vorder, corder) + nvar = len(vorder) + ncon = len(corder) if ax is None: fig, ax = plt.subplots() else: fig = None - # TODO: Options to configure: - # - tick direction/presence - # - rectangle linewidth/linestyle - # - spy markersize - # markersize and linewidth should probably be set automatically - # based on size of problem - - ax.spy( - imat, - # TODO: pass keyword args - markersize=0.2, - ) + markersize = spy_kwds.pop("markersize", None) + if markersize is None: + # At 10000 vars/cons, we want markersize=0.2 + # At 20 vars/cons, we want markersize=10 + # We assume we want a linear relationship between 1/nvar + # and the markersize. + markersize = ( + (10.0 - 0.2) / (1/20 - 1/10000) * (1/max(nvar, ncon) - 1/10000) + + 0.2 + ) + + ax.spy(imat, markersize=markersize, **spy_kwds) ax.tick_params(length=0) if highlight_coarse: start = (0, 0) - for vblocks, cblocks in zip(vpart, cpart): + for i, (vblocks, cblocks) in enumerate(zip(vpart, cpart)): # Get the total number of variables/constraints in this part # of the coarse partition nv = sum(len(vb) for vb in vblocks) nc = sum(len(cb) for cb in cblocks) stop = (start[0] + nv - 1, start[1] + nc - 1) - ax.add_patch(_get_rectangle_around_coords(start, stop)) + if not (i == 1 and skip_wellconstrained): + # Regardless of whether we are plotting in upper or lower + # triangular order, the well-constrained subsystem is at + # position 1 + ax.add_patch( + _get_rectangle_around_coords(start, stop, linewidth=linewidth) + ) start = (stop[0] + 1, stop[1] + 1) if highlight_fine: + # Use dashed lines to distinguish inner from outer partitions + # if we are highlighting both + linestyle = "--" if highlight_coarse else "-" start = (0, 0) for vb, cb in zip(vpart_fine, cpart_fine): stop = (start[0] + len(vb) - 1, start[1] + len(cb) - 1) - ax.add_patch(_get_rectangle_around_coords(start, stop)) + ax.add_patch( + _get_rectangle_around_coords( + start, + stop, + linestyle=linestyle, + linewidth=linewidth, + ) + ) start = (stop[0] + 1, stop[1] + 1) return fig, ax From 6464db3587e4c3be7c311ca2cc18d907cd4e3cf0 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Sat, 23 Mar 2024 13:09:35 -0600 Subject: [PATCH 1494/1797] move spy_dulmage_mendelsohn to visualize module --- pyomo/contrib/incidence_analysis/interface.py | 211 +---------------- pyomo/contrib/incidence_analysis/visualize.py | 222 ++++++++++++++++++ 2 files changed, 223 insertions(+), 210 deletions(-) create mode 100644 pyomo/contrib/incidence_analysis/visualize.py diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index a4f08c737ec..50cb84daaf5 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 get_config_from_kwds, IncidenceOrder +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 ( @@ -995,212 +995,3 @@ def add_edge(self, variable, constraint): con_id = self._con_index_map[constraint] self._incidence_graph.add_edge(var_id, con_id) - - def partition_variables_and_constraints( - self, - variables=None, - constraints=None, - order=IncidenceOrder.dulmage_mendelsohn_upper, - ): - """Partition variables and constraints in an incidence graph - """ - variables, constraints = self._validate_input(variables, constraints) - vdmp, cdmp = self.dulmage_mendelsohn(variables=variables, constraints=constraints) - - ucv = vdmp.unmatched + vdmp.underconstrained - ucc = cdmp.underconstrained - - ocv = vdmp.overconstrained - occ = cdmp.overconstrained + cdmp.unmatched - - ucvblocks, uccblocks = self.get_connected_components( - variables=ucv, constraints=ucc - ) - ocvblocks, occblocks = self.get_connected_components( - variables=ocv, constraints=occ - ) - wcvblocks, wccblocks = self.block_triangularize( - variables=vdmp.square, constraints=cdmp.square - ) - # By default, we block-*lower* triangularize. By default, however, we want - # the Dulmage-Mendelsohn decomposition to be block-*upper* triangular. - wcvblocks.reverse() - wccblocks.reverse() - vpartition = [ucvblocks, wcvblocks, ocvblocks] - cpartition = [uccblocks, wccblocks, occblocks] - - if order == IncidenceOrder.dulmage_mendelsohn_lower: - # If a block-lower triangular matrix was requested, we need to reverse - # both the inner and outer partitions - vpartition.reverse() - cpartition.reverse() - for vb in vpartition: - vb.reverse() - for cb in cpartition: - cb.reverse() - - return vpartition, cpartition - - -import matplotlib.pyplot as plt -from matplotlib.patches import Rectangle - -def _get_rectangle_around_coords( - ij1, - ij2, - linewidth=2, - linestyle="-", -): - i1, j1 = ij1 - i2, j2 = ij2 - buffer = 0.5 - ll_corner = (min(i1, i2)-buffer, min(j1, j2)-buffer) - width = abs(i1 - i2) + 2*buffer - height = abs(j1 - j2) + 2*buffer - rect = Rectangle( - ll_corner, - width, - height, - clip_on=False, - fill=False, - edgecolor="orange", - linewidth=linewidth, - linestyle=linestyle, - ) - return rect - - -def spy_dulmage_mendelsohn( - model, - *, - incidence_kwds=None, - order=IncidenceOrder.dulmage_mendelsohn_upper, - highlight_coarse=True, - highlight_fine=True, - skip_wellconstrained=False, - ax=None, - linewidth=2, - spy_kwds=None, -): - """Plot sparsity structure in Dulmage-Mendelsohn order on Matplotlib - axes - - This is a wrapper around the Matplotlib ``Axes.spy`` method for plotting - an incidence matrix in Dulmage-Mendelsohn order, with coarse and/or fine - partitions highlighted. The coarse partition refers to the under-constrained, - over-constrained, and well-constrained subsystems, while the fine partition - refers to block diagonal or block triangular partitions of the former - subsystems. - - Parameters - ---------- - - model: ``ConcreteModel`` - Input model to plot sparsity structure of - - incidence_kwds: dict, optional - Config options for ``IncidenceGraphInterface`` - - order: ``IncidenceOrder``, optional - Order in which to plot sparsity structure - - highlight_coarse: bool, optional - Whether to draw a rectange around the coarse partition - - highlight_fine: bool, optional - Whether to draw a rectangle around the fine partition - - skip_wellconstrained: bool, optional - Whether to skip highlighting the well-constrained subsystem of the - coarse partition. Default False - - ax: ``matplotlib.pyplot.Axes``, optional - Axes object on which to plot. If not provided, new figure - and axes are created. - - linewidth: int, optional - Line width of for rectangle used to highlight. Default 2 - - spy_kwds: dict, optional - Keyword arguments for ``Axes.spy`` - - Returns - ------- - - fig: ``matplotlib.pyplot.Figure`` or ``None`` - Figure on which the sparsity structure is plotted. ``None`` if axes - are provided - - ax: ``matplotlib.pyplot.Axes`` - Axes on which the sparsity structure is plotted - - """ - if incidence_kwds is None: - incidence_kwds = {} - if spy_kwds is None: - spy_kwds = {} - - igraph = IncidenceGraphInterface(model, **incidence_kwds) - vpart, cpart = igraph.partition_variables_and_constraints(order=order) - vpart_fine = sum(vpart, []) - cpart_fine = sum(cpart, []) - vorder = sum(vpart_fine, []) - corder = sum(cpart_fine, []) - - imat = get_structural_incidence_matrix(vorder, corder) - nvar = len(vorder) - ncon = len(corder) - - if ax is None: - fig, ax = plt.subplots() - else: - fig = None - - markersize = spy_kwds.pop("markersize", None) - if markersize is None: - # At 10000 vars/cons, we want markersize=0.2 - # At 20 vars/cons, we want markersize=10 - # We assume we want a linear relationship between 1/nvar - # and the markersize. - markersize = ( - (10.0 - 0.2) / (1/20 - 1/10000) * (1/max(nvar, ncon) - 1/10000) - + 0.2 - ) - - ax.spy(imat, markersize=markersize, **spy_kwds) - ax.tick_params(length=0) - if highlight_coarse: - start = (0, 0) - for i, (vblocks, cblocks) in enumerate(zip(vpart, cpart)): - # Get the total number of variables/constraints in this part - # of the coarse partition - nv = sum(len(vb) for vb in vblocks) - nc = sum(len(cb) for cb in cblocks) - stop = (start[0] + nv - 1, start[1] + nc - 1) - if not (i == 1 and skip_wellconstrained): - # Regardless of whether we are plotting in upper or lower - # triangular order, the well-constrained subsystem is at - # position 1 - ax.add_patch( - _get_rectangle_around_coords(start, stop, linewidth=linewidth) - ) - start = (stop[0] + 1, stop[1] + 1) - - if highlight_fine: - # Use dashed lines to distinguish inner from outer partitions - # if we are highlighting both - linestyle = "--" if highlight_coarse else "-" - start = (0, 0) - for vb, cb in zip(vpart_fine, cpart_fine): - stop = (start[0] + len(vb) - 1, start[1] + len(cb) - 1) - ax.add_patch( - _get_rectangle_around_coords( - start, - stop, - linestyle=linestyle, - linewidth=linewidth, - ) - ) - start = (stop[0] + 1, stop[1] + 1) - - return fig, ax diff --git a/pyomo/contrib/incidence_analysis/visualize.py b/pyomo/contrib/incidence_analysis/visualize.py new file mode 100644 index 00000000000..929f8a77d4e --- /dev/null +++ b/pyomo/contrib/incidence_analysis/visualize.py @@ -0,0 +1,222 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ +"""Module for visualizing results of incidence graph or matrix analysis + +""" +from pyomo.contrib.incidence_analysis.config import IncidenceOrder +from pyomo.contrib.incidence_analysis.interface import ( + IncidenceGraphInterface, + get_structural_incidence_matrix, +) +from pyomo.common.dependencies import matplotlib + + +def _partition_variables_and_constraints( + model, order=IncidenceOrder.dulmage_mendelsohn_upper, **kwds +): + """Partition variables and constraints in an incidence graph + """ + igraph = IncidenceGraphInterface(model, **kwds) + vdmp, cdmp = igraph.dulmage_mendelsohn() + + ucv = vdmp.unmatched + vdmp.underconstrained + ucc = cdmp.underconstrained + + ocv = vdmp.overconstrained + occ = cdmp.overconstrained + cdmp.unmatched + + ucvblocks, uccblocks = igraph.get_connected_components( + variables=ucv, constraints=ucc + ) + ocvblocks, occblocks = igraph.get_connected_components( + variables=ocv, constraints=occ + ) + wcvblocks, wccblocks = igraph.block_triangularize( + variables=vdmp.square, constraints=cdmp.square + ) + # By default, we block-*lower* triangularize. By default, however, we want + # the Dulmage-Mendelsohn decomposition to be block-*upper* triangular. + wcvblocks.reverse() + wccblocks.reverse() + vpartition = [ucvblocks, wcvblocks, ocvblocks] + cpartition = [uccblocks, wccblocks, occblocks] + + if order == IncidenceOrder.dulmage_mendelsohn_lower: + # If a block-lower triangular matrix was requested, we need to reverse + # both the inner and outer partitions + vpartition.reverse() + cpartition.reverse() + for vb in vpartition: + vb.reverse() + for cb in cpartition: + cb.reverse() + + return vpartition, cpartition + + +def _get_rectangle_around_coords( + ij1, + ij2, + linewidth=2, + linestyle="-", +): + i1, j1 = ij1 + i2, j2 = ij2 + buffer = 0.5 + ll_corner = (min(i1, i2)-buffer, min(j1, j2)-buffer) + width = abs(i1 - i2) + 2*buffer + height = abs(j1 - j2) + 2*buffer + rect = matplotlib.patches.Rectangle( + ll_corner, + width, + height, + clip_on=False, + fill=False, + edgecolor="orange", + linewidth=linewidth, + linestyle=linestyle, + ) + return rect + + +def spy_dulmage_mendelsohn( + model, + *, + incidence_kwds=None, + order=IncidenceOrder.dulmage_mendelsohn_upper, + highlight_coarse=True, + highlight_fine=True, + skip_wellconstrained=False, + ax=None, + linewidth=2, + spy_kwds=None, +): + """Plot sparsity structure in Dulmage-Mendelsohn order on Matplotlib axes + + This is a wrapper around the Matplotlib ``Axes.spy`` method for plotting + an incidence matrix in Dulmage-Mendelsohn order, with coarse and/or fine + partitions highlighted. The coarse partition refers to the under-constrained, + over-constrained, and well-constrained subsystems, while the fine partition + refers to block diagonal or block triangular partitions of the former + subsystems. + + Parameters + ---------- + + model: ``ConcreteModel`` + Input model to plot sparsity structure of + + incidence_kwds: dict, optional + Config options for ``IncidenceGraphInterface`` + + order: ``IncidenceOrder``, optional + Order in which to plot sparsity structure + + highlight_coarse: bool, optional + Whether to draw a rectange around the coarse partition + + highlight_fine: bool, optional + Whether to draw a rectangle around the fine partition + + skip_wellconstrained: bool, optional + Whether to skip highlighting the well-constrained subsystem of the + coarse partition. Default False + + ax: ``matplotlib.pyplot.Axes``, optional + Axes object on which to plot. If not provided, new figure + and axes are created. + + linewidth: int, optional + Line width of for rectangle used to highlight. Default 2 + + spy_kwds: dict, optional + Keyword arguments for ``Axes.spy`` + + Returns + ------- + + fig: ``matplotlib.pyplot.Figure`` or ``None`` + Figure on which the sparsity structure is plotted. ``None`` if axes + are provided + + ax: ``matplotlib.pyplot.Axes`` + Axes on which the sparsity structure is plotted + + """ + plt = matplotlib.pyplot + if incidence_kwds is None: + incidence_kwds = {} + if spy_kwds is None: + spy_kwds = {} + + vpart, cpart = _partition_variables_and_constraints(model, order=order) + vpart_fine = sum(vpart, []) + cpart_fine = sum(cpart, []) + vorder = sum(vpart_fine, []) + corder = sum(cpart_fine, []) + + imat = get_structural_incidence_matrix(vorder, corder) + nvar = len(vorder) + ncon = len(corder) + + if ax is None: + fig, ax = plt.subplots() + else: + fig = None + + markersize = spy_kwds.pop("markersize", None) + if markersize is None: + # At 10000 vars/cons, we want markersize=0.2 + # At 20 vars/cons, we want markersize=10 + # We assume we want a linear relationship between 1/nvar + # and the markersize. + markersize = ( + (10.0 - 0.2) / (1/20 - 1/10000) * (1/max(nvar, ncon) - 1/10000) + + 0.2 + ) + + ax.spy(imat, markersize=markersize, **spy_kwds) + ax.tick_params(length=0) + if highlight_coarse: + start = (0, 0) + for i, (vblocks, cblocks) in enumerate(zip(vpart, cpart)): + # Get the total number of variables/constraints in this part + # of the coarse partition + nv = sum(len(vb) for vb in vblocks) + nc = sum(len(cb) for cb in cblocks) + stop = (start[0] + nv - 1, start[1] + nc - 1) + if not (i == 1 and skip_wellconstrained): + # Regardless of whether we are plotting in upper or lower + # triangular order, the well-constrained subsystem is at + # position 1 + ax.add_patch( + _get_rectangle_around_coords(start, stop, linewidth=linewidth) + ) + start = (stop[0] + 1, stop[1] + 1) + + if highlight_fine: + # Use dashed lines to distinguish inner from outer partitions + # if we are highlighting both + linestyle = "--" if highlight_coarse else "-" + start = (0, 0) + for vb, cb in zip(vpart_fine, cpart_fine): + stop = (start[0] + len(vb) - 1, start[1] + len(cb) - 1) + ax.add_patch( + _get_rectangle_around_coords( + start, + stop, + linestyle=linestyle, + linewidth=linewidth, + ) + ) + start = (stop[0] + 1, stop[1] + 1) + + return fig, ax From 4f78f7ac1587f717a3a39d7ef90ec58ef02f5c5e Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Sat, 23 Mar 2024 13:19:57 -0600 Subject: [PATCH 1495/1797] module to "test" visualization --- .../tests/test_visualize.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 pyomo/contrib/incidence_analysis/tests/test_visualize.py diff --git a/pyomo/contrib/incidence_analysis/tests/test_visualize.py b/pyomo/contrib/incidence_analysis/tests/test_visualize.py new file mode 100644 index 00000000000..ceb36c33e34 --- /dev/null +++ b/pyomo/contrib/incidence_analysis/tests/test_visualize.py @@ -0,0 +1,41 @@ +# ___________________________________________________________________________ +# +# 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.unittest as unittest +from pyomo.common.dependencies import matplotlib, matplotlib_available +from pyomo.contrib.incidence_analysis.visualize import spy_dulmage_mendelsohn +from pyomo.contrib.incidence_analysis.tests.models_for_testing import ( + make_gas_expansion_model, + make_dynamic_model, + make_degenerate_solid_phase_model, +) + + +@unittest.skipUnless(matplotlib_available, "Matplotlib is not available") +class TestSpy(unittest.TestCase): + + def test_spy_dulmage_mendelsohn(self): + models = [ + make_gas_expansion_model(), + make_dynamic_model(), + make_degenerate_solid_phase_model(), + ] + for m in models: + fig, ax = spy_dulmage_mendelsohn(m) + # Note that this is a weak test. We just test that we can call the + # plot method, it doesn't raise an error, and gives us back the + # types we expect. We don't attemt to validate the resulting plot. + self.assertTrue(isinstance(fig, matplotlib.pyplot.Figure)) + self.assertTrue(isinstance(ax, matplotlib.pyplot.Axes)) + + +if __name__ == "__main__": + unittest.main() From d10e549bbaa15a3a50a13f02c5d74f0c94321ea6 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Sat, 23 Mar 2024 13:40:49 -0600 Subject: [PATCH 1496/1797] apply black --- .../tests/test_visualize.py | 1 - pyomo/contrib/incidence_analysis/visualize.py | 30 +++++++------------ 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/tests/test_visualize.py b/pyomo/contrib/incidence_analysis/tests/test_visualize.py index ceb36c33e34..ea740e86c27 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_visualize.py +++ b/pyomo/contrib/incidence_analysis/tests/test_visualize.py @@ -21,7 +21,6 @@ @unittest.skipUnless(matplotlib_available, "Matplotlib is not available") class TestSpy(unittest.TestCase): - def test_spy_dulmage_mendelsohn(self): models = [ make_gas_expansion_model(), diff --git a/pyomo/contrib/incidence_analysis/visualize.py b/pyomo/contrib/incidence_analysis/visualize.py index 929f8a77d4e..e198d859db5 100644 --- a/pyomo/contrib/incidence_analysis/visualize.py +++ b/pyomo/contrib/incidence_analysis/visualize.py @@ -22,8 +22,7 @@ def _partition_variables_and_constraints( model, order=IncidenceOrder.dulmage_mendelsohn_upper, **kwds ): - """Partition variables and constraints in an incidence graph - """ + """Partition variables and constraints in an incidence graph""" igraph = IncidenceGraphInterface(model, **kwds) vdmp, cdmp = igraph.dulmage_mendelsohn() @@ -62,18 +61,13 @@ def _partition_variables_and_constraints( return vpartition, cpartition -def _get_rectangle_around_coords( - ij1, - ij2, - linewidth=2, - linestyle="-", -): +def _get_rectangle_around_coords(ij1, ij2, linewidth=2, linestyle="-"): i1, j1 = ij1 i2, j2 = ij2 buffer = 0.5 - ll_corner = (min(i1, i2)-buffer, min(j1, j2)-buffer) - width = abs(i1 - i2) + 2*buffer - height = abs(j1 - j2) + 2*buffer + ll_corner = (min(i1, i2) - buffer, min(j1, j2) - buffer) + width = abs(i1 - i2) + 2 * buffer + height = abs(j1 - j2) + 2 * buffer rect = matplotlib.patches.Rectangle( ll_corner, width, @@ -136,7 +130,7 @@ def spy_dulmage_mendelsohn( linewidth: int, optional Line width of for rectangle used to highlight. Default 2 - + spy_kwds: dict, optional Keyword arguments for ``Axes.spy`` @@ -178,10 +172,9 @@ def spy_dulmage_mendelsohn( # At 20 vars/cons, we want markersize=10 # We assume we want a linear relationship between 1/nvar # and the markersize. - markersize = ( - (10.0 - 0.2) / (1/20 - 1/10000) * (1/max(nvar, ncon) - 1/10000) - + 0.2 - ) + markersize = (10.0 - 0.2) / (1 / 20 - 1 / 10000) * ( + 1 / max(nvar, ncon) - 1 / 10000 + ) + 0.2 ax.spy(imat, markersize=markersize, **spy_kwds) ax.tick_params(length=0) @@ -211,10 +204,7 @@ def spy_dulmage_mendelsohn( stop = (start[0] + len(vb) - 1, start[1] + len(cb) - 1) ax.add_patch( _get_rectangle_around_coords( - start, - stop, - linestyle=linestyle, - linewidth=linewidth, + start, stop, linestyle=linestyle, linewidth=linewidth ) ) start = (stop[0] + 1, stop[1] + 1) From 3da70ddcab8322cde137cb782001cfc431af546d Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Sat, 23 Mar 2024 13:46:18 -0600 Subject: [PATCH 1497/1797] fix typos --- pyomo/contrib/incidence_analysis/tests/test_visualize.py | 2 +- pyomo/contrib/incidence_analysis/visualize.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/tests/test_visualize.py b/pyomo/contrib/incidence_analysis/tests/test_visualize.py index ea740e86c27..3a6a403810e 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_visualize.py +++ b/pyomo/contrib/incidence_analysis/tests/test_visualize.py @@ -31,7 +31,7 @@ def test_spy_dulmage_mendelsohn(self): fig, ax = spy_dulmage_mendelsohn(m) # Note that this is a weak test. We just test that we can call the # plot method, it doesn't raise an error, and gives us back the - # types we expect. We don't attemt to validate the resulting plot. + # types we expect. We don't attempt to validate the resulting plot. self.assertTrue(isinstance(fig, matplotlib.pyplot.Figure)) self.assertTrue(isinstance(ax, matplotlib.pyplot.Axes)) diff --git a/pyomo/contrib/incidence_analysis/visualize.py b/pyomo/contrib/incidence_analysis/visualize.py index e198d859db5..05c0661070a 100644 --- a/pyomo/contrib/incidence_analysis/visualize.py +++ b/pyomo/contrib/incidence_analysis/visualize.py @@ -115,7 +115,7 @@ def spy_dulmage_mendelsohn( Order in which to plot sparsity structure highlight_coarse: bool, optional - Whether to draw a rectange around the coarse partition + Whether to draw a rectangle around the coarse partition highlight_fine: bool, optional Whether to draw a rectangle around the fine partition From 86bdcab77b1f26df97d94c7616f95e6a38a33626 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Sat, 23 Mar 2024 22:24:04 -0600 Subject: [PATCH 1498/1797] skip test if scipy/networkx not available --- pyomo/contrib/incidence_analysis/tests/test_visualize.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/incidence_analysis/tests/test_visualize.py b/pyomo/contrib/incidence_analysis/tests/test_visualize.py index 3a6a403810e..7c5538b671f 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_visualize.py +++ b/pyomo/contrib/incidence_analysis/tests/test_visualize.py @@ -10,7 +10,12 @@ # ___________________________________________________________________________ import pyomo.common.unittest as unittest -from pyomo.common.dependencies import matplotlib, matplotlib_available +from pyomo.common.dependencies import ( + matplotlib, + matplotlib_available, + scipy_available, + networkx_available, +) from pyomo.contrib.incidence_analysis.visualize import spy_dulmage_mendelsohn from pyomo.contrib.incidence_analysis.tests.models_for_testing import ( make_gas_expansion_model, @@ -20,6 +25,8 @@ @unittest.skipUnless(matplotlib_available, "Matplotlib is not available") +@unittest.skipUnless(scipy_available, "SciPy is not available") +@unittest.skipUnless(networkx_available, "NetworkX is not available") class TestSpy(unittest.TestCase): def test_spy_dulmage_mendelsohn(self): models = [ From a89ef831321118862ad8082f5992886d906fc42b Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 25 Mar 2024 08:59:40 -0600 Subject: [PATCH 1499/1797] Bugfix: bound methods don't work on ScalarBlock --- pyomo/contrib/solver/base.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 8840265763e..12cb2e83918 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -14,11 +14,11 @@ 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.constraint import Constraint, _GeneralConstraintData +from pyomo.core.base.var import Var, _GeneralVarData from pyomo.core.base.param import _ParamData from pyomo.core.base.block import _BlockData -from pyomo.core.base.objective import _GeneralObjectiveData +from pyomo.core.base.objective import Objective, _GeneralObjectiveData from pyomo.common.config import document_kwargs_from_configdict, ConfigValue from pyomo.common.errors import ApplicationError from pyomo.common.deprecation import deprecation_warning @@ -435,9 +435,9 @@ def _map_results(self, model, results): ] legacy_soln.status = legacy_solution_status_map[results.solution_status] legacy_results.solver.termination_message = str(results.termination_condition) - legacy_results.problem.number_of_constraints = model.nconstraints() - legacy_results.problem.number_of_variables = model.nvariables() - number_of_objectives = model.nobjectives() + legacy_results.problem.number_of_constraints = len(list(model.component_map(ctype=Constraint))) + legacy_results.problem.number_of_variables = len(list(model.component_map(ctype=Var))) + number_of_objectives = len(list(model.component_map(ctype=Objective))) legacy_results.problem.number_of_objectives = number_of_objectives if number_of_objectives == 1: obj = get_objective(model) From 617cc59a141a551e1e5d8b65551d7e7c734f4ac8 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 25 Mar 2024 10:41:45 -0600 Subject: [PATCH 1500/1797] Apply black --- pyomo/contrib/solver/base.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 12cb2e83918..91398ba5970 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -435,8 +435,12 @@ def _map_results(self, model, results): ] legacy_soln.status = legacy_solution_status_map[results.solution_status] legacy_results.solver.termination_message = str(results.termination_condition) - legacy_results.problem.number_of_constraints = len(list(model.component_map(ctype=Constraint))) - legacy_results.problem.number_of_variables = len(list(model.component_map(ctype=Var))) + legacy_results.problem.number_of_constraints = len( + list(model.component_map(ctype=Constraint)) + ) + legacy_results.problem.number_of_variables = len( + list(model.component_map(ctype=Var)) + ) number_of_objectives = len(list(model.component_map(ctype=Objective))) legacy_results.problem.number_of_objectives = number_of_objectives if number_of_objectives == 1: From 8c2bb52b8142bc2aa824fe20c0336cc8a5c62176 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 25 Mar 2024 11:40:06 -0600 Subject: [PATCH 1501/1797] Update installation documentation to include Cython instructions --- doc/OnlineDocs/installation.rst | 35 ++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/doc/OnlineDocs/installation.rst b/doc/OnlineDocs/installation.rst index ecba05e13fb..2ed42f35f4e 100644 --- a/doc/OnlineDocs/installation.rst +++ b/doc/OnlineDocs/installation.rst @@ -12,7 +12,7 @@ version, Pyomo will remove testing for that Python version. Using CONDA ~~~~~~~~~~~ -We recommend installation with *conda*, which is included with the +We recommend installation with ``conda``, which is included with the Anaconda distribution of Python. You can install Pyomo in your system Python installation by executing the following in a shell: @@ -21,7 +21,7 @@ Python installation by executing the following in a shell: conda install -c conda-forge pyomo Optimization solvers are not installed with Pyomo, but some open source -optimization solvers can be installed with conda as well: +optimization solvers can be installed with ``conda`` as well: :: @@ -31,7 +31,7 @@ optimization solvers can be installed with conda as well: Using PIP ~~~~~~~~~ -The standard utility for installing Python packages is *pip*. You +The standard utility for installing Python packages is ``pip``. You can install Pyomo in your system Python installation by executing the following in a shell: @@ -43,14 +43,14 @@ the following in a shell: Conditional Dependencies ~~~~~~~~~~~~~~~~~~~~~~~~ -Extensions to Pyomo, and many of the contributions in `pyomo.contrib`, +Extensions to Pyomo, and many of the contributions in ``pyomo.contrib``, often have conditional dependencies on a variety of third-party Python packages including but not limited to: matplotlib, networkx, numpy, openpyxl, pandas, pint, pymysql, pyodbc, pyro4, scipy, sympy, and xlrd. A full list of conditional dependencies can be found in Pyomo's -`setup.py` and displayed using: +``setup.py`` and displayed using: :: @@ -72,3 +72,28 @@ with the standard Anaconda installation. You can check which Python packages you have installed using the command ``conda list`` or ``pip list``. Additional Python packages may be installed as needed. + + +Installation with Cython +~~~~~~~~~~~~~~~~~~~~~~~~ + +Users can opt to install Pyomo with +`cython `_ +initialized. + +.. note:: + This can only be done via ``pip`` or from source. + +Via ``pip``: + +:: + + pip install pyomo --global-option="--with-cython" + +From source: + +:: + + git clone https://github.com/Pyomo/pyomo.git + cd pyomo + python setup.py install --with-cython From e17f27559b01ddd07aed2011e68032289493d8fa Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 25 Mar 2024 12:58:02 -0600 Subject: [PATCH 1502/1797] Add links to Pyomo Book Springer page --- README.md | 1 + doc/OnlineDocs/bibliography.rst | 2 ++ doc/OnlineDocs/tutorial_examples.rst | 17 ++++++++++------- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 12c3ce8ed9a..707f1a06c5a 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ version, we will remove testing for that Python version. ### Tutorials and Examples +* [Pyomo — Optimization Modeling in Python](https://link.springer.com/book/10.1007/978-3-030-68928-5) * [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/) * The [companion notebooks](https://mobook.github.io/MO-book/intro.html) diff --git a/doc/OnlineDocs/bibliography.rst b/doc/OnlineDocs/bibliography.rst index 6cbb96d3bfb..c12d3f81d8c 100644 --- a/doc/OnlineDocs/bibliography.rst +++ b/doc/OnlineDocs/bibliography.rst @@ -39,6 +39,8 @@ Bibliography John D. Siirola, Jean-Paul Watson, and David L. Woodruff. Pyomo - Optimization Modeling in Python, 3rd Edition. Vol. 67. Springer, 2021. + doi: `10.1007/978-3-030-68928-5 + `_ .. [PyomoJournal] William E. Hart, Jean-Paul Watson, David L. Woodruff. "Pyomo: modeling and solving mathematical programs in diff --git a/doc/OnlineDocs/tutorial_examples.rst b/doc/OnlineDocs/tutorial_examples.rst index 6a40949ef90..a18f9d77d42 100644 --- a/doc/OnlineDocs/tutorial_examples.rst +++ b/doc/OnlineDocs/tutorial_examples.rst @@ -3,15 +3,18 @@ Pyomo Tutorial Examples Additional Pyomo tutorials and examples can be found at the following links: -`Pyomo Workshop Slides and Exercises -`_ +* `Pyomo — Optimization Modeling in Python + `_ ([PyomoBookIII]_) -`Prof. Jeffrey Kantor's Pyomo Cookbook -`_ +* `Pyomo Workshop Slides and Exercises + `_ -The `companion notebooks `_ -for *Hands-On Mathematical Optimization with Python* +* `Prof. Jeffrey Kantor's Pyomo Cookbook + `_ -`Pyomo Gallery `_ +* The `companion notebooks `_ + for *Hands-On Mathematical Optimization with Python* + +* `Pyomo Gallery `_ From 399cbf54cae48803933316e856374adf7ddec766 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 25 Mar 2024 13:04:51 -0600 Subject: [PATCH 1503/1797] Add recommendationg for advanced users --- 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 2ed42f35f4e..83cd08e7a4a 100644 --- a/doc/OnlineDocs/installation.rst +++ b/doc/OnlineDocs/installation.rst @@ -90,7 +90,7 @@ Via ``pip``: pip install pyomo --global-option="--with-cython" -From source: +From source (recommended for advanced users only): :: From 9b00edd4105fcfbba775a4b85f485aa0cca40d3a Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 25 Mar 2024 14:11:58 -0600 Subject: [PATCH 1504/1797] Hack for model.solutions --- pyomo/contrib/solver/base.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 91398ba5970..c193b2c0789 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -468,7 +468,15 @@ def _solution_handler( """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) + try: + model.solutions.add_symbol_map(symbol_map) + except AttributeError: + # Something wacky happens in IDAES due to the usage of ScalarBlock + # instead of PyomoModel. This is an attempt to fix that. + from pyomo.core.base.PyomoModel import ModelSolutions + + setattr(model.solutions, ModelSolutions()) + model.solutions.add_symbol_map(symbol_map) legacy_results._smap_id = id(symbol_map) delete_legacy_soln = True if load_solutions: From ac64a5a552a8feede8624ce0a7d952fdea52e2c2 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 25 Mar 2024 15:01:32 -0600 Subject: [PATCH 1505/1797] Typo: would be nice if setattr was used correctly --- 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 c193b2c0789..cdf58416659 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -475,7 +475,7 @@ def _solution_handler( # instead of PyomoModel. This is an attempt to fix that. from pyomo.core.base.PyomoModel import ModelSolutions - setattr(model.solutions, ModelSolutions()) + setattr(model, 'solutions', ModelSolutions()) model.solutions.add_symbol_map(symbol_map) legacy_results._smap_id = id(symbol_map) delete_legacy_soln = True From ca793605d99036c5513d58dad6d67a31ce2e2eba Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 25 Mar 2024 15:11:55 -0600 Subject: [PATCH 1506/1797] Need an instance in there --- 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 cdf58416659..07efbaed449 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -475,7 +475,7 @@ def _solution_handler( # instead of PyomoModel. This is an attempt to fix that. from pyomo.core.base.PyomoModel import ModelSolutions - setattr(model, 'solutions', ModelSolutions()) + setattr(model, 'solutions', ModelSolutions(model)) model.solutions.add_symbol_map(symbol_map) legacy_results._smap_id = id(symbol_map) delete_legacy_soln = True From f1177a287c2cf3dd0c2d9a79818209d49b1c533f Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Mon, 25 Mar 2024 16:12:52 -0600 Subject: [PATCH 1507/1797] dont draw box around DM subsystems if they are empty --- pyomo/contrib/incidence_analysis/visualize.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/incidence_analysis/visualize.py b/pyomo/contrib/incidence_analysis/visualize.py index 05c0661070a..8cefbcf61c9 100644 --- a/pyomo/contrib/incidence_analysis/visualize.py +++ b/pyomo/contrib/incidence_analysis/visualize.py @@ -186,10 +186,16 @@ def spy_dulmage_mendelsohn( nv = sum(len(vb) for vb in vblocks) nc = sum(len(cb) for cb in cblocks) stop = (start[0] + nv - 1, start[1] + nc - 1) - if not (i == 1 and skip_wellconstrained): + if ( + not (i == 1 and skip_wellconstrained) + and nv > 0 and nc > 0 + ): # Regardless of whether we are plotting in upper or lower # triangular order, the well-constrained subsystem is at # position 1 + # + # The get-rectangle function doesn't look good if we give it + # an "empty region" to box. ax.add_patch( _get_rectangle_around_coords(start, stop, linewidth=linewidth) ) @@ -202,6 +208,7 @@ def spy_dulmage_mendelsohn( start = (0, 0) for vb, cb in zip(vpart_fine, cpart_fine): stop = (start[0] + len(vb) - 1, start[1] + len(cb) - 1) + # Note that the subset's we're boxing here can't be empty. ax.add_patch( _get_rectangle_around_coords( start, stop, linestyle=linestyle, linewidth=linewidth From 6b94db6177424f0a33ebdf14514369ed02ece50e Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Mon, 25 Mar 2024 17:51:39 -0600 Subject: [PATCH 1508/1797] reformat --- pyomo/contrib/incidence_analysis/visualize.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/visualize.py b/pyomo/contrib/incidence_analysis/visualize.py index 8cefbcf61c9..9360d8ddfc6 100644 --- a/pyomo/contrib/incidence_analysis/visualize.py +++ b/pyomo/contrib/incidence_analysis/visualize.py @@ -186,10 +186,7 @@ def spy_dulmage_mendelsohn( nv = sum(len(vb) for vb in vblocks) nc = sum(len(cb) for cb in cblocks) stop = (start[0] + nv - 1, start[1] + nc - 1) - if ( - not (i == 1 and skip_wellconstrained) - and nv > 0 and nc > 0 - ): + if not (i == 1 and skip_wellconstrained) and nv > 0 and nc > 0: # Regardless of whether we are plotting in upper or lower # triangular order, the well-constrained subsystem is at # position 1 From 53635a9bbb1817ef2d3e4881779736cdabd87345 Mon Sep 17 00:00:00 2001 From: Eslick Date: Tue, 26 Mar 2024 10:56:25 -0400 Subject: [PATCH 1509/1797] Fix black and substring issue --- pyomo/solvers/plugins/solvers/ASL.py | 3 ++- pyomo/solvers/plugins/solvers/IPOPT.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pyomo/solvers/plugins/solvers/ASL.py b/pyomo/solvers/plugins/solvers/ASL.py index 3ebe5c3b422..38a9fc1df58 100644 --- a/pyomo/solvers/plugins/solvers/ASL.py +++ b/pyomo/solvers/plugins/solvers/ASL.py @@ -160,8 +160,9 @@ def create_command_line(self, executable, problem_files): # if 'PYOMO_AMPLFUNC' in env: if 'AMPLFUNC' in env: + existing = set(env['AMPLFUNC'].split("\n")) for line in env['PYOMO_AMPLFUNC'].split('\n'): - if line not in env['AMPLFUNC']: + if line not in existing: env['AMPLFUNC'] += "\n" + line else: env['AMPLFUNC'] = env['PYOMO_AMPLFUNC'] diff --git a/pyomo/solvers/plugins/solvers/IPOPT.py b/pyomo/solvers/plugins/solvers/IPOPT.py index 17b68da6364..8f5190a4a07 100644 --- a/pyomo/solvers/plugins/solvers/IPOPT.py +++ b/pyomo/solvers/plugins/solvers/IPOPT.py @@ -121,8 +121,9 @@ def create_command_line(self, executable, problem_files): # if 'PYOMO_AMPLFUNC' in env: if 'AMPLFUNC' in env: + existing = set(env['AMPLFUNC'].split("\n")) for line in env['PYOMO_AMPLFUNC'].split('\n'): - if line not in env['AMPLFUNC']: + if line not in existing: env['AMPLFUNC'] += "\n" + line else: env['AMPLFUNC'] = env['PYOMO_AMPLFUNC'] From dd3bb6a3adddec75c432505fac42b6e987bd4bd8 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 26 Mar 2024 14:07:05 -0600 Subject: [PATCH 1510/1797] Adding a test for the bug --- pyomo/gdp/tests/test_bigm.py | 40 ++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/pyomo/gdp/tests/test_bigm.py b/pyomo/gdp/tests/test_bigm.py index c6ac49f6d36..79ad24ae782 100644 --- a/pyomo/gdp/tests/test_bigm.py +++ b/pyomo/gdp/tests/test_bigm.py @@ -19,8 +19,11 @@ Set, Constraint, ComponentMap, + LogicalConstraint, + Objective, SolverFactory, Suffix, + TerminationCondition, ConcreteModel, Var, Any, @@ -2193,6 +2196,43 @@ def test_decl_order_opposite_instantiation_order(self): def test_do_not_assume_nested_indicators_local(self): ct.check_do_not_assume_nested_indicators_local(self, 'gdp.bigm') + @unittest.skipUnless(gurobi_available, "Gurobi is not available") + def test_constraints_not_enforced_when_an_ancestor_indicator_is_False(self): + m = ConcreteModel() + m.x = Var(bounds=(0, 30)) + + m.left = Disjunct() + m.left.left = Disjunct() + m.left.left.c = Constraint(expr=m.x >= 10) + m.left.right = Disjunct() + m.left.right.c = Constraint(expr=m.x >= 9) + m.left.disjunction = Disjunction(expr=[m.left.left, m.left.right]) + m.right = Disjunct() + m.right.left = Disjunct() + m.right.left.c = Constraint(expr=m.x >= 11) + m.right.right = Disjunct() + m.right.right.c = Constraint(expr=m.x >= 8) + m.right.disjunction = Disjunction(expr=[m.right.left, m.right.right]) + m.disjunction = Disjunction(expr=[m.left, m.right]) + + m.equiv_left = LogicalConstraint(expr=m.left.left.indicator_var.equivalent_to( + m.right.left.indicator_var)) + m.equiv_right = LogicalConstraint(expr=m.left.right.indicator_var.equivalent_to( + m.right.right.indicator_var)) + + m.obj = Objective(expr=m.x) + + TransformationFactory('gdp.bigm').apply_to(m) + results = SolverFactory('gurobi').solve(m) + self.assertEqual(results.solver.termination_condition, + TerminationCondition.optimal) + self.assertTrue(value(m.right.indicator_var)) + self.assertFalse(value(m.left.indicator_var)) + self.assertTrue(value(m.right.right.indicator_var)) + self.assertFalse(value(m.right.left.indicator_var)) + self.assertTrue(value(m.left.right.indicator_var)) + self.assertAlmostEqual(value(m.x), 8) + class IndexedDisjunction(unittest.TestCase): # this tests that if the targets are a subset of the From ff67f852c02068f01f4140305383867f8774f75d Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 26 Mar 2024 14:31:00 -0600 Subject: [PATCH 1511/1797] Generalizing how nested Constraints are relaxed in bigm so that they aren't enforced if any parent indicator_var is False --- pyomo/gdp/plugins/bigm.py | 28 ++++++++++++++++++---------- pyomo/gdp/plugins/bigm_mixin.py | 9 ++++++--- pyomo/gdp/util.py | 4 ++-- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/pyomo/gdp/plugins/bigm.py b/pyomo/gdp/plugins/bigm.py index 3f450dbbd4f..118fa6935d7 100644 --- a/pyomo/gdp/plugins/bigm.py +++ b/pyomo/gdp/plugins/bigm.py @@ -217,17 +217,16 @@ def _apply_to_impl(self, instance, **kwds): t, t.index(), bigM, - parent_disjunct=gdp_tree.parent(t), - root_disjunct=gdp_tree.root_disjunct(t), + gdp_tree, ) # issue warnings about anything that was in the bigM args dict that we # didn't use _warn_for_unused_bigM_args(bigM, self.used_args, logger) - def _transform_disjunctionData( - self, obj, index, bigM, parent_disjunct=None, root_disjunct=None - ): + def _transform_disjunctionData(self, obj, index, bigM, gdp_tree): + parent_disjunct = gdp_tree.parent(obj) + root_disjunct = gdp_tree.root_disjunct(obj) (transBlock, xorConstraint) = self._setup_transform_disjunctionData( obj, root_disjunct ) @@ -236,7 +235,7 @@ def _transform_disjunctionData( or_expr = 0 for disjunct in obj.disjuncts: or_expr += disjunct.binary_indicator_var - self._transform_disjunct(disjunct, bigM, transBlock) + self._transform_disjunct(disjunct, bigM, transBlock, gdp_tree) if obj.xor: xorConstraint[index] = or_expr == 1 @@ -249,7 +248,7 @@ def _transform_disjunctionData( # and deactivate for the writers obj.deactivate() - def _transform_disjunct(self, obj, bigM, transBlock): + def _transform_disjunct(self, obj, bigM, transBlock, gdp_tree): # 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. @@ -261,6 +260,12 @@ def _transform_disjunct(self, obj, bigM, transBlock): relaxationBlock = self._get_disjunct_transformation_block(obj, transBlock) + indicator_expression = 0 + node = obj + while node is not None: + indicator_expression += 1 - node.binary_indicator_var + node = gdp_tree.parent_disjunct(node) + # 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 / @@ -270,13 +275,15 @@ def _transform_disjunct(self, obj, bigM, transBlock): # comparing the two relaxations. # # Transform each component within this disjunct - self._transform_block_components(obj, obj, bigM, arg_list, suffix_list) + self._transform_block_components(obj, obj, bigM, arg_list, suffix_list, + indicator_expression) # deactivate disjunct to keep the writers happy obj._deactivate_without_fixing_indicator() def _transform_constraint( - self, obj, disjunct, bigMargs, arg_list, disjunct_suffix_list + self, obj, disjunct, bigMargs, arg_list, disjunct_suffix_list, + indicator_expression ): # add constraint to the transformation block, we'll transform it there. transBlock = disjunct._transformation_block() @@ -348,7 +355,8 @@ def _transform_constraint( bigm_src[c] = (lower, upper) self._add_constraint_expressions( - c, i, M, disjunct.binary_indicator_var, newConstraint, constraint_map + c, i, M, disjunct.binary_indicator_var, newConstraint, constraint_map, + indicator_expression=indicator_expression ) # deactivate because we relaxed diff --git a/pyomo/gdp/plugins/bigm_mixin.py b/pyomo/gdp/plugins/bigm_mixin.py index 510b36b5102..300509d81f8 100644 --- a/pyomo/gdp/plugins/bigm_mixin.py +++ b/pyomo/gdp/plugins/bigm_mixin.py @@ -232,7 +232,8 @@ def _estimate_M(self, expr, constraint): return tuple(M) def _add_constraint_expressions( - self, c, i, M, indicator_var, newConstraint, constraint_map + self, c, i, M, indicator_var, newConstraint, constraint_map, + indicator_expression=None ): # Since we are both combining components from multiple blocks and using # local names, we need to make sure that the first index for @@ -244,6 +245,8 @@ 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 + if indicator_expression is None: + indicator_expression = 1 - indicator_var if c.lower is not None: if M[0] is None: @@ -251,7 +254,7 @@ def _add_constraint_expressions( "Cannot relax disjunctive constraint '%s' " "because M is not defined." % name ) - M_expr = M[0] * (1 - indicator_var) + M_expr = M[0] * indicator_expression newConstraint.add((name, i, 'lb'), c.lower <= c.body - M_expr) constraint_map.transformed_constraints[c].append( newConstraint[name, i, 'lb'] @@ -263,7 +266,7 @@ def _add_constraint_expressions( "Cannot relax disjunctive constraint '%s' " "because M is not defined." % name ) - M_expr = M[1] * (1 - indicator_var) + M_expr = M[1] * indicator_expression newConstraint.add((name, i, 'ub'), c.body - M_expr <= c.upper) constraint_map.transformed_constraints[c].append( newConstraint[name, i, 'ub'] diff --git a/pyomo/gdp/util.py b/pyomo/gdp/util.py index fe11975954d..a8c6393f0b3 100644 --- a/pyomo/gdp/util.py +++ b/pyomo/gdp/util.py @@ -144,13 +144,13 @@ def parent(self, u): Arg: u : A node in the tree """ + if u in self._parent: + return self._parent[u] if u not in self._vertices: raise ValueError( "'%s' is not a vertex in the GDP tree. Cannot " "retrieve its parent." % u ) - if u in self._parent: - return self._parent[u] else: return None From e062625ba66012efcbd7ae6b0b4c849cb623456f Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 26 Mar 2024 14:53:33 -0600 Subject: [PATCH 1512/1797] Fixing the first couple tests I broke --- pyomo/gdp/tests/test_bigm.py | 66 ++++++++++++++---------------------- 1 file changed, 26 insertions(+), 40 deletions(-) diff --git a/pyomo/gdp/tests/test_bigm.py b/pyomo/gdp/tests/test_bigm.py index 79ad24ae782..8d0fa8bd633 100644 --- a/pyomo/gdp/tests/test_bigm.py +++ b/pyomo/gdp/tests/test_bigm.py @@ -2091,35 +2091,6 @@ def innerIndexed(d, i): m._pyomo_gdp_bigm_reformulation.relaxedDisjuncts, ) - def check_first_disjunct_constraint(self, disj1c, x, ind_var): - self.assertEqual(len(disj1c), 1) - cons = disj1c[0] - self.assertIsNone(cons.lower) - self.assertEqual(cons.upper, 1) - repn = generate_standard_repn(cons.body) - self.assertTrue(repn.is_quadratic()) - self.assertEqual(len(repn.linear_vars), 1) - self.assertEqual(len(repn.quadratic_vars), 4) - ct.check_linear_coef(self, repn, ind_var, 143) - self.assertEqual(repn.constant, -143) - for i in range(1, 5): - ct.check_squared_term_coef(self, repn, x[i], 1) - - def check_second_disjunct_constraint(self, disj2c, x, ind_var): - self.assertEqual(len(disj2c), 1) - cons = disj2c[0] - self.assertIsNone(cons.lower) - self.assertEqual(cons.upper, 1) - repn = generate_standard_repn(cons.body) - self.assertTrue(repn.is_quadratic()) - self.assertEqual(len(repn.linear_vars), 5) - self.assertEqual(len(repn.quadratic_vars), 4) - self.assertEqual(repn.constant, -63) # M = 99, so this is 36 - 99 - ct.check_linear_coef(self, repn, ind_var, 99) - for i in range(1, 5): - 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) @@ -2145,30 +2116,45 @@ def check_hierarchical_nested_model(self, m, bigm): # outer disjunction constraints disj1c = bigm.get_transformed_constraints(m.disj1.c) - self.check_first_disjunct_constraint(disj1c, m.x, m.disj1.binary_indicator_var) + self.assertEqual(len(disj1c), 1) + cons = disj1c[0] + assertExpressionsEqual( + self, + cons.expr, + m.x[1]**2 + m.x[2]**2 + m.x[3]**2 + m.x[4]**2 - 143.0*(1 - m.disj1.binary_indicator_var) <= 1.0 + ) disj2c = bigm.get_transformed_constraints(m.disjunct_block.disj2.c) - self.check_second_disjunct_constraint( - disj2c, m.x, m.disjunct_block.disj2.binary_indicator_var + self.assertEqual(len(disj2c), 1) + cons = disj2c[0] + cons.pprint() + assertExpressionsEqual( + self, + cons.expr, + (3 - m.x[1])**2 + (3 - m.x[2])**2 + (3 - m.x[3])**2 + (3 - m.x[4])**2 - 99.0*(1 - m.disjunct_block.disj2.binary_indicator_var) <= 1.0 ) # inner disjunction constraints innerd1c = bigm.get_transformed_constraints( m.disjunct_block.disj2.disjunction_disjuncts[0].constraint[1] ) - self.check_first_disjunct_constraint( - innerd1c, - m.x, - m.disjunct_block.disj2.disjunction_disjuncts[0].binary_indicator_var, + self.assertEqual(len(innerd1c), 1) + cons = innerd1c[0] + assertExpressionsEqual( + self, + cons.expr, + m.x[1]**2 + m.x[2]**2 + m.x[3]**2 + m.x[4]**2 - 143.0*(1 - m.disjunct_block.disj2.disjunction_disjuncts[0].binary_indicator_var + 1 - m.disjunct_block.disj2.binary_indicator_var) <= 1.0 ) innerd2c = bigm.get_transformed_constraints( m.disjunct_block.disj2.disjunction_disjuncts[1].constraint[1] ) - self.check_second_disjunct_constraint( - innerd2c, - m.x, - m.disjunct_block.disj2.disjunction_disjuncts[1].binary_indicator_var, + self.assertEqual(len(innerd2c), 1) + cons = innerd2c[0] + assertExpressionsEqual( + self, + cons.expr, + (3 - m.x[1])**2 + (3 - m.x[2])**2 + (3 - m.x[3])**2 + (3 - m.x[4])**2 - 99.0*(1 - m.disjunct_block.disj2.disjunction_disjuncts[1].binary_indicator_var + 1 - m.disjunct_block.disj2.binary_indicator_var) <= 1.0 ) def test_hierarchical_badly_ordered_targets(self): From 01f7ebe58af5ded0325b028c462906d8aa2c4179 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Tue, 26 Mar 2024 16:14:05 -0600 Subject: [PATCH 1513/1797] require variables and constraints to be specified separately in `remove_nodes`; update to raise error on invalid components --- pyomo/contrib/incidence_analysis/interface.py | 55 +++++++++++++++---- .../tests/test_interface.py | 33 +++++++---- 2 files changed, 65 insertions(+), 23 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index 50cb84daaf5..0ed9b34b0f8 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -453,11 +453,29 @@ def _validate_input(self, variables, constraints): raise ValueError("Neither variables nor a model have been provided.") else: variables = self.variables + elif self._incidence_graph is not None: + # If variables were provided and an incidence graph is cached, + # make sure the provided variables exist in the graph. + for var in variables: + if var not in self._var_index_map: + raise KeyError( + f"Variable {var} does not exist in the cached" + " incidence graph." + ) if constraints is None: if self._incidence_graph is None: raise ValueError("Neither constraints nor a model have been provided.") else: constraints = self.constraints + elif self._incidence_graph is not None: + # If constraints were provided and an incidence graph is cached, + # make sure the provided constraints exist in the graph. + for con in constraints: + if con not in self._con_index_map: + raise KeyError( + f"Constraint {con} does not exist in the cached" + " incidence graph." + ) _check_unindexed(variables + constraints) return variables, constraints @@ -854,7 +872,7 @@ def dulmage_mendelsohn(self, variables=None, constraints=None): # Hopefully this does not get too confusing... return var_partition, con_partition - def remove_nodes(self, nodes, constraints=None): + def remove_nodes(self, variables=None, constraints=None): """Removes the specified variables and constraints (columns and rows) from the cached incidence matrix. @@ -866,35 +884,48 @@ def remove_nodes(self, nodes, constraints=None): Parameters ---------- - nodes: list - VarData or ConData objects whose columns or rows will be - removed from the incidence matrix. + variables: list + VarData objects whose nodes will be removed from the incidence graph constraints: list - VarData or ConData objects whose columns or rows will be - removed from the incidence matrix. + ConData objects whose nodes will be removed from the incidence graph + + .. note:: + + **Breaking change in Pyomo vTBD** + + The pre-TBD implementation of ``remove_nodes`` allowed variables and + constraints to remove to be specified in a single list. This made + error checking difficult, and indeed, if invalid components were + provided, we carried on silently instead of throwing an error or + warning. As part of a fix to raise an error if an invalid component + (one that is not part of the incidence graph) is provided, we now require + variables and constraints to be specified separately. """ if constraints is None: constraints = [] + if variables is None: + variables = [] if self._incidence_graph is None: raise RuntimeError( "Attempting to remove variables and constraints from cached " "incidence matrix,\nbut no incidence matrix has been cached." ) - to_exclude = ComponentSet(nodes) - to_exclude.update(constraints) - vars_to_include = [v for v in self.variables if v not in to_exclude] - cons_to_include = [c for c in self.constraints if c not in to_exclude] + variables, constraints = self._validate_input(variables, constraints) + v_exclude = ComponentSet(variables) + c_exclude = ComponentSet(constraints) + vars_to_include = [v for v in self.variables if v not in v_exclude] + cons_to_include = [c for c in self.constraints if c not in c_exclude] incidence_graph = self._extract_subgraph(vars_to_include, cons_to_include) # update attributes self._variables = vars_to_include self._constraints = cons_to_include self._incidence_graph = incidence_graph self._var_index_map = ComponentMap( - (var, i) for i, var in enumerate(self.variables) + (var, i) for i, var in enumerate(vars_to_include) ) self._con_index_map = ComponentMap( - (con, i) for i, con in enumerate(self._constraints) + (con, i) for i, con in enumerate(cons_to_include) ) def plot(self, variables=None, constraints=None, title=None, show=True): diff --git a/pyomo/contrib/incidence_analysis/tests/test_interface.py b/pyomo/contrib/incidence_analysis/tests/test_interface.py index 4b77d60d8ba..3b2439ed2af 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_interface.py +++ b/pyomo/contrib/incidence_analysis/tests/test_interface.py @@ -634,17 +634,15 @@ def test_exception(self): nlp = PyomoNLP(model) igraph = IncidenceGraphInterface(nlp) - with self.assertRaises(RuntimeError) as exc: + with self.assertRaisesRegex(KeyError, "does not exist"): variables = [model.P] constraints = [model.ideal_gas] igraph.maximum_matching(variables, constraints) - self.assertIn("must be unindexed", str(exc.exception)) - with self.assertRaises(RuntimeError) as exc: + with self.assertRaisesRegex(KeyError, "does not exist"): variables = [model.P] constraints = [model.ideal_gas] igraph.block_triangularize(variables, constraints) - self.assertIn("must be unindexed", str(exc.exception)) @unittest.skipUnless(networkx_available, "networkx is not available.") @@ -885,17 +883,15 @@ def test_exception(self): model = make_gas_expansion_model() igraph = IncidenceGraphInterface(model) - with self.assertRaises(RuntimeError) as exc: + with self.assertRaisesRegex(KeyError, "does not exist"): variables = [model.P] constraints = [model.ideal_gas] igraph.maximum_matching(variables, constraints) - self.assertIn("must be unindexed", str(exc.exception)) - with self.assertRaises(RuntimeError) as exc: + with self.assertRaisesRegex(KeyError, "does not exist"): variables = [model.P] constraints = [model.ideal_gas] igraph.block_triangularize(variables, constraints) - self.assertIn("must be unindexed", str(exc.exception)) @unittest.skipUnless(scipy_available, "scipy is not available.") def test_remove(self): @@ -923,7 +919,7 @@ def test_remove(self): # Say we know that these variables and constraints should # be matched... vars_to_remove = [model.F[0], model.F[2]] - cons_to_remove = (model.mbal[1], model.mbal[2]) + cons_to_remove = [model.mbal[1], model.mbal[2]] igraph.remove_nodes(vars_to_remove, cons_to_remove) variable_set = ComponentSet(igraph.variables) self.assertNotIn(model.F[0], variable_set) @@ -1309,7 +1305,7 @@ def test_remove(self): # matrix. vars_to_remove = [m.flow_comp[1]] cons_to_remove = [m.flow_eqn[1]] - igraph.remove_nodes(vars_to_remove + cons_to_remove) + igraph.remove_nodes(vars_to_remove, cons_to_remove) var_dmp, con_dmp = igraph.dulmage_mendelsohn() var_con_set = ComponentSet(igraph.variables + igraph.constraints) underconstrained_set = ComponentSet( @@ -1460,6 +1456,21 @@ def test_remove_no_matrix(self): with self.assertRaisesRegex(RuntimeError, "no incidence matrix"): igraph.remove_nodes([m.v1]) + def test_remove_bad_node(self): + m = pyo.ConcreteModel() + m.x = pyo.Var([1, 2, 3]) + m.eq = pyo.Constraint(pyo.PositiveIntegers) + m.eq[1] = m.x[1] * m.x[2] == m.x[3] + m.eq[2] = m.x[1] + 2 * m.x[2] == 3 * m.x[3] + igraph = IncidenceGraphInterface(m) + with self.assertRaisesRegex(KeyError, "does not exist"): + # Suppose we think something like this should work. We should get + # an error, and not silently do nothing. + igraph.remove_nodes([m.x], [m.eq]) + + with self.assertRaisesRegex(KeyError, "does not exist"): + igraph.remove_nodes([[m.x[1], m.x[2]], [m.eq[1]]]) + @unittest.skipUnless(networkx_available, "networkx is not available.") @unittest.skipUnless(scipy_available, "scipy is not available.") @@ -1840,7 +1851,7 @@ def test_var_elim(self): 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]) + 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]) From 5ae3cf4a90dceb97cb1f707ccfa76754e783f9f8 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 26 Mar 2024 20:58:24 -0600 Subject: [PATCH 1514/1797] Fixing the last test that I broke --- pyomo/gdp/tests/test_bigm.py | 56 +++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/pyomo/gdp/tests/test_bigm.py b/pyomo/gdp/tests/test_bigm.py index 8d0fa8bd633..95c4652e387 100644 --- a/pyomo/gdp/tests/test_bigm.py +++ b/pyomo/gdp/tests/test_bigm.py @@ -1882,12 +1882,11 @@ def test_m_value_mappings(self): # many of the transformed constraints look like this, so can call this # function to test them. def check_bigM_constraint(self, cons, variable, M, indicator_var): - repn = generate_standard_repn(cons.body) - self.assertTrue(repn.is_linear()) - self.assertEqual(repn.constant, -M) - self.assertEqual(len(repn.linear_vars), 2) - ct.check_linear_coef(self, repn, variable, 1) - ct.check_linear_coef(self, repn, indicator_var, M) + assertExpressionsEqual( + self, + cons.body, + variable - float(M) * (1 - indicator_var.get_associated_binary()) + ) def check_inner_xor_constraint(self, inner_disjunction, outer_disjunct, bigm): inner_xor = inner_disjunction.algebraic_constraint @@ -1952,6 +1951,14 @@ def test_transformed_constraints(self): .binary_indicator_var, ) ), + 1, + EXPR.MonomialTermExpression( + ( + -1, + m.disjunct[1] + .binary_indicator_var, + ) + ), ] ), ) @@ -1961,37 +1968,41 @@ def test_transformed_constraints(self): ] ), ) - self.assertIsNone(cons1ub.lower) - self.assertEqual(cons1ub.upper, 0) - self.check_bigM_constraint( - cons1ub, m.z, 10, m.disjunct[1].innerdisjunct[0].indicator_var + assertExpressionsEqual( + self, + cons1ub.expr, + m.z - 10.0*(1 - m.disjunct[1].innerdisjunct[0].binary_indicator_var + + 1 - m.disjunct[1].binary_indicator_var) <= 0.0 ) cons2 = bigm.get_transformed_constraints(m.disjunct[1].innerdisjunct[1].c) self.assertEqual(len(cons2), 1) cons2lb = cons2[0] - self.assertEqual(cons2lb.lower, 5) - self.assertIsNone(cons2lb.upper) - self.check_bigM_constraint( - cons2lb, m.z, -5, m.disjunct[1].innerdisjunct[1].indicator_var + assertExpressionsEqual( + self, + cons2lb.expr, + 5.0 <= m.z - (-5.0)*(1 - m.disjunct[1].innerdisjunct[1].binary_indicator_var + + 1 - m.disjunct[1].binary_indicator_var) ) cons3 = bigm.get_transformed_constraints(m.simpledisjunct.innerdisjunct0.c) self.assertEqual(len(cons3), 1) cons3ub = cons3[0] - self.assertEqual(cons3ub.upper, 2) - self.assertIsNone(cons3ub.lower) - self.check_bigM_constraint( - cons3ub, m.x, 7, m.simpledisjunct.innerdisjunct0.indicator_var + assertExpressionsEqual( + self, + cons3ub.expr, + m.x - 7.0*(1 - m.simpledisjunct.innerdisjunct0.binary_indicator_var + 1 - + m.simpledisjunct.binary_indicator_var) <= 2.0 ) cons4 = bigm.get_transformed_constraints(m.simpledisjunct.innerdisjunct1.c) self.assertEqual(len(cons4), 1) cons4lb = cons4[0] - self.assertEqual(cons4lb.lower, 4) - self.assertIsNone(cons4lb.upper) - self.check_bigM_constraint( - cons4lb, m.x, -13, m.simpledisjunct.innerdisjunct1.indicator_var + assertExpressionsEqual( + self, + cons4lb.expr, + m.x - (-13.0)*(1 - m.simpledisjunct.innerdisjunct1.binary_indicator_var + + 1 - m.simpledisjunct.binary_indicator_var) >= 4.0 ) # Here we check that the xor constraint from @@ -2127,7 +2138,6 @@ def check_hierarchical_nested_model(self, m, bigm): disj2c = bigm.get_transformed_constraints(m.disjunct_block.disj2.c) self.assertEqual(len(disj2c), 1) cons = disj2c[0] - cons.pprint() assertExpressionsEqual( self, cons.expr, From 0c25598b30c3150851c3434d703aaa929c559911 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 26 Mar 2024 21:00:19 -0600 Subject: [PATCH 1515/1797] black --- pyomo/gdp/plugins/bigm.py | 30 +++++---- pyomo/gdp/plugins/bigm_mixin.py | 10 ++- pyomo/gdp/tests/test_bigm.py | 107 +++++++++++++++++++++++++------- 3 files changed, 109 insertions(+), 38 deletions(-) diff --git a/pyomo/gdp/plugins/bigm.py b/pyomo/gdp/plugins/bigm.py index 118fa6935d7..d715d913db8 100644 --- a/pyomo/gdp/plugins/bigm.py +++ b/pyomo/gdp/plugins/bigm.py @@ -213,12 +213,7 @@ def _apply_to_impl(self, instance, **kwds): bigM = self._config.bigM for t in preprocessed_targets: if t.ctype is Disjunction: - self._transform_disjunctionData( - t, - t.index(), - bigM, - gdp_tree, - ) + self._transform_disjunctionData(t, t.index(), bigM, gdp_tree) # issue warnings about anything that was in the bigM args dict that we # didn't use @@ -275,15 +270,21 @@ def _transform_disjunct(self, obj, bigM, transBlock, gdp_tree): # comparing the two relaxations. # # Transform each component within this disjunct - self._transform_block_components(obj, obj, bigM, arg_list, suffix_list, - indicator_expression) + self._transform_block_components( + obj, obj, bigM, arg_list, suffix_list, indicator_expression + ) # deactivate disjunct to keep the writers happy obj._deactivate_without_fixing_indicator() def _transform_constraint( - self, obj, disjunct, bigMargs, arg_list, disjunct_suffix_list, - indicator_expression + self, + obj, + disjunct, + bigMargs, + arg_list, + disjunct_suffix_list, + indicator_expression, ): # add constraint to the transformation block, we'll transform it there. transBlock = disjunct._transformation_block() @@ -355,8 +356,13 @@ def _transform_constraint( bigm_src[c] = (lower, upper) self._add_constraint_expressions( - c, i, M, disjunct.binary_indicator_var, newConstraint, constraint_map, - indicator_expression=indicator_expression + c, + i, + M, + disjunct.binary_indicator_var, + newConstraint, + constraint_map, + indicator_expression=indicator_expression, ) # deactivate because we relaxed diff --git a/pyomo/gdp/plugins/bigm_mixin.py b/pyomo/gdp/plugins/bigm_mixin.py index 300509d81f8..1c3fcb2c64a 100644 --- a/pyomo/gdp/plugins/bigm_mixin.py +++ b/pyomo/gdp/plugins/bigm_mixin.py @@ -232,8 +232,14 @@ def _estimate_M(self, expr, constraint): return tuple(M) def _add_constraint_expressions( - self, c, i, M, indicator_var, newConstraint, constraint_map, - indicator_expression=None + self, + c, + i, + M, + indicator_var, + newConstraint, + constraint_map, + indicator_expression=None, ): # Since we are both combining components from multiple blocks and using # local names, we need to make sure that the first index for diff --git a/pyomo/gdp/tests/test_bigm.py b/pyomo/gdp/tests/test_bigm.py index 95c4652e387..3174a95292e 100644 --- a/pyomo/gdp/tests/test_bigm.py +++ b/pyomo/gdp/tests/test_bigm.py @@ -1885,7 +1885,7 @@ def check_bigM_constraint(self, cons, variable, M, indicator_var): assertExpressionsEqual( self, cons.body, - variable - float(M) * (1 - indicator_var.get_associated_binary()) + variable - float(M) * (1 - indicator_var.get_associated_binary()), ) def check_inner_xor_constraint(self, inner_disjunction, outer_disjunct, bigm): @@ -1953,11 +1953,7 @@ def test_transformed_constraints(self): ), 1, EXPR.MonomialTermExpression( - ( - -1, - m.disjunct[1] - .binary_indicator_var, - ) + (-1, m.disjunct[1].binary_indicator_var) ), ] ), @@ -1971,8 +1967,15 @@ def test_transformed_constraints(self): assertExpressionsEqual( self, cons1ub.expr, - m.z - 10.0*(1 - m.disjunct[1].innerdisjunct[0].binary_indicator_var + - 1 - m.disjunct[1].binary_indicator_var) <= 0.0 + m.z + - 10.0 + * ( + 1 + - m.disjunct[1].innerdisjunct[0].binary_indicator_var + + 1 + - m.disjunct[1].binary_indicator_var + ) + <= 0.0, ) cons2 = bigm.get_transformed_constraints(m.disjunct[1].innerdisjunct[1].c) @@ -1981,8 +1984,15 @@ def test_transformed_constraints(self): assertExpressionsEqual( self, cons2lb.expr, - 5.0 <= m.z - (-5.0)*(1 - m.disjunct[1].innerdisjunct[1].binary_indicator_var - + 1 - m.disjunct[1].binary_indicator_var) + 5.0 + <= m.z + - (-5.0) + * ( + 1 + - m.disjunct[1].innerdisjunct[1].binary_indicator_var + + 1 + - m.disjunct[1].binary_indicator_var + ), ) cons3 = bigm.get_transformed_constraints(m.simpledisjunct.innerdisjunct0.c) @@ -1991,8 +2001,15 @@ def test_transformed_constraints(self): assertExpressionsEqual( self, cons3ub.expr, - m.x - 7.0*(1 - m.simpledisjunct.innerdisjunct0.binary_indicator_var + 1 - - m.simpledisjunct.binary_indicator_var) <= 2.0 + m.x + - 7.0 + * ( + 1 + - m.simpledisjunct.innerdisjunct0.binary_indicator_var + + 1 + - m.simpledisjunct.binary_indicator_var + ) + <= 2.0, ) cons4 = bigm.get_transformed_constraints(m.simpledisjunct.innerdisjunct1.c) @@ -2001,8 +2018,15 @@ def test_transformed_constraints(self): assertExpressionsEqual( self, cons4lb.expr, - m.x - (-13.0)*(1 - m.simpledisjunct.innerdisjunct1.binary_indicator_var - + 1 - m.simpledisjunct.binary_indicator_var) >= 4.0 + m.x + - (-13.0) + * ( + 1 + - m.simpledisjunct.innerdisjunct1.binary_indicator_var + + 1 + - m.simpledisjunct.binary_indicator_var + ) + >= 4.0, ) # Here we check that the xor constraint from @@ -2132,7 +2156,12 @@ def check_hierarchical_nested_model(self, m, bigm): assertExpressionsEqual( self, cons.expr, - m.x[1]**2 + m.x[2]**2 + m.x[3]**2 + m.x[4]**2 - 143.0*(1 - m.disj1.binary_indicator_var) <= 1.0 + m.x[1] ** 2 + + m.x[2] ** 2 + + m.x[3] ** 2 + + m.x[4] ** 2 + - 143.0 * (1 - m.disj1.binary_indicator_var) + <= 1.0, ) disj2c = bigm.get_transformed_constraints(m.disjunct_block.disj2.c) @@ -2141,7 +2170,12 @@ def check_hierarchical_nested_model(self, m, bigm): assertExpressionsEqual( self, cons.expr, - (3 - m.x[1])**2 + (3 - m.x[2])**2 + (3 - m.x[3])**2 + (3 - m.x[4])**2 - 99.0*(1 - m.disjunct_block.disj2.binary_indicator_var) <= 1.0 + (3 - m.x[1]) ** 2 + + (3 - m.x[2]) ** 2 + + (3 - m.x[3]) ** 2 + + (3 - m.x[4]) ** 2 + - 99.0 * (1 - m.disjunct_block.disj2.binary_indicator_var) + <= 1.0, ) # inner disjunction constraints @@ -2153,7 +2187,18 @@ def check_hierarchical_nested_model(self, m, bigm): assertExpressionsEqual( self, cons.expr, - m.x[1]**2 + m.x[2]**2 + m.x[3]**2 + m.x[4]**2 - 143.0*(1 - m.disjunct_block.disj2.disjunction_disjuncts[0].binary_indicator_var + 1 - m.disjunct_block.disj2.binary_indicator_var) <= 1.0 + m.x[1] ** 2 + + m.x[2] ** 2 + + m.x[3] ** 2 + + m.x[4] ** 2 + - 143.0 + * ( + 1 + - m.disjunct_block.disj2.disjunction_disjuncts[0].binary_indicator_var + + 1 + - m.disjunct_block.disj2.binary_indicator_var + ) + <= 1.0, ) innerd2c = bigm.get_transformed_constraints( @@ -2164,7 +2209,18 @@ def check_hierarchical_nested_model(self, m, bigm): assertExpressionsEqual( self, cons.expr, - (3 - m.x[1])**2 + (3 - m.x[2])**2 + (3 - m.x[3])**2 + (3 - m.x[4])**2 - 99.0*(1 - m.disjunct_block.disj2.disjunction_disjuncts[1].binary_indicator_var + 1 - m.disjunct_block.disj2.binary_indicator_var) <= 1.0 + (3 - m.x[1]) ** 2 + + (3 - m.x[2]) ** 2 + + (3 - m.x[3]) ** 2 + + (3 - m.x[4]) ** 2 + - 99.0 + * ( + 1 + - m.disjunct_block.disj2.disjunction_disjuncts[1].binary_indicator_var + + 1 + - m.disjunct_block.disj2.binary_indicator_var + ) + <= 1.0, ) def test_hierarchical_badly_ordered_targets(self): @@ -2211,17 +2267,20 @@ def test_constraints_not_enforced_when_an_ancestor_indicator_is_False(self): m.right.disjunction = Disjunction(expr=[m.right.left, m.right.right]) m.disjunction = Disjunction(expr=[m.left, m.right]) - m.equiv_left = LogicalConstraint(expr=m.left.left.indicator_var.equivalent_to( - m.right.left.indicator_var)) - m.equiv_right = LogicalConstraint(expr=m.left.right.indicator_var.equivalent_to( - m.right.right.indicator_var)) + m.equiv_left = LogicalConstraint( + expr=m.left.left.indicator_var.equivalent_to(m.right.left.indicator_var) + ) + m.equiv_right = LogicalConstraint( + expr=m.left.right.indicator_var.equivalent_to(m.right.right.indicator_var) + ) m.obj = Objective(expr=m.x) TransformationFactory('gdp.bigm').apply_to(m) results = SolverFactory('gurobi').solve(m) - self.assertEqual(results.solver.termination_condition, - TerminationCondition.optimal) + self.assertEqual( + results.solver.termination_condition, TerminationCondition.optimal + ) self.assertTrue(value(m.right.indicator_var)) self.assertFalse(value(m.left.indicator_var)) self.assertTrue(value(m.right.right.indicator_var)) From fe4a4e0815ac425bab389abd2d44fac5d91acf91 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 26 Mar 2024 21:33:16 -0600 Subject: [PATCH 1516/1797] Updating GDPopt call to _transform_constraint --- pyomo/contrib/gdpopt/util.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/gdpopt/util.py b/pyomo/contrib/gdpopt/util.py index 2cb70f0ea60..babe0245d57 100644 --- a/pyomo/contrib/gdpopt/util.py +++ b/pyomo/contrib/gdpopt/util.py @@ -553,6 +553,13 @@ def _add_bigm_constraint_to_transformed_model(m, constraint, block): # making a Reference to the ComponentData so that it will look like an # indexed component for now. If I redesign bigm at some point, then this # could be prettier. - bigm._transform_constraint(Reference(constraint), parent_disjunct, None, [], []) + bigm._transform_constraint( + Reference(constraint), + parent_disjunct, + None, + [], + [], + 1 - parent_disjunct.binary_indicator_var, + ) # Now get rid of it because this is a class attribute! del bigm._config From 6e1d351126e5e3f0b775d678b1778ceb38de5938 Mon Sep 17 00:00:00 2001 From: Eslick Date: Wed, 27 Mar 2024 08:18:07 -0400 Subject: [PATCH 1517/1797] Add Robbybp's patch --- pyomo/contrib/pynumero/interfaces/pyomo_nlp.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py index 51edd09311a..ce148f50ecf 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py @@ -92,15 +92,13 @@ def __init__(self, pyomo_model, nl_file_options=None): # The NL writer advertises the external function libraries # through the PYOMO_AMPLFUNC environment variable; merge it # with any preexisting AMPLFUNC definitions - amplfunc = "\n".join( - filter( - None, - ( - os.environ.get('AMPLFUNC', None), - os.environ.get('PYOMO_AMPLFUNC', None), - ), - ) - ) + amplfunc_lines = os.environ.get("AMPLFUNC", "").split("\n") + existing = set(amplfunc_lines) + for line in os.environ.get("PYOMO_AMPLFUNC", "").split("\n"): + # Skip (a) empty lines and (b) lines we already have + if line != "" and line not in existing: + amplfunc_lines.append(line) + amplfunc = "\n".join(amplfunc_lines) with CtypesEnviron(AMPLFUNC=amplfunc): super(PyomoNLP, self).__init__(nl_file) From 0e7fa12f6915e7eacfbaa24b1a6d36af8d46dc3c Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 27 Mar 2024 07:09:26 -0600 Subject: [PATCH 1518/1797] Adding some test skips that whatever partial environment I'm living in this morning caught --- pyomo/contrib/gdpopt/tests/test_LBB.py | 1 + pyomo/contrib/gdpopt/tests/test_gdpopt.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/gdpopt/tests/test_LBB.py b/pyomo/contrib/gdpopt/tests/test_LBB.py index 273327b02a4..8a553398fa6 100644 --- a/pyomo/contrib/gdpopt/tests/test_LBB.py +++ b/pyomo/contrib/gdpopt/tests/test_LBB.py @@ -59,6 +59,7 @@ def test_infeasible_GDP(self): self.assertIsNone(m.d.disjuncts[0].indicator_var.value) self.assertIsNone(m.d.disjuncts[1].indicator_var.value) + @unittest.skipUnless(z3_available, "Z3 SAT solver is not available") def test_infeasible_GDP_check_sat(self): """Test for infeasible GDP with check_sat option True.""" m = ConcreteModel() diff --git a/pyomo/contrib/gdpopt/tests/test_gdpopt.py b/pyomo/contrib/gdpopt/tests/test_gdpopt.py index 005df56ced5..98750f6e78a 100644 --- a/pyomo/contrib/gdpopt/tests/test_gdpopt.py +++ b/pyomo/contrib/gdpopt/tests/test_gdpopt.py @@ -1050,7 +1050,8 @@ def assert_correct_disjuncts_active( self.assertTrue(fabs(value(eight_process.profit.expr) - 68) <= 1e-2) - @unittest.skipUnless(Gurobi().available(), "APPSI Gurobi solver is not available") + @unittest.skipUnless(Gurobi().available() and Gurobi().license_is_valid(), + "APPSI Gurobi solver is not available") def test_auto_persistent_solver(self): exfile = import_file(join(exdir, 'eight_process', 'eight_proc_model.py')) m = exfile.build_eight_process_flowsheet() From 897704ded324a13bd946ce7191597c5d65d21f1d Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 27 Mar 2024 08:39:45 -0600 Subject: [PATCH 1519/1797] Fixing license check, though that's not the real issue --- pyomo/contrib/gdpopt/tests/test_gdpopt.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/gdpopt/tests/test_gdpopt.py b/pyomo/contrib/gdpopt/tests/test_gdpopt.py index 98750f6e78a..c33e0172def 100644 --- a/pyomo/contrib/gdpopt/tests/test_gdpopt.py +++ b/pyomo/contrib/gdpopt/tests/test_gdpopt.py @@ -22,6 +22,7 @@ from pyomo.common.collections import Bunch from pyomo.common.config import ConfigDict, ConfigValue from pyomo.common.fileutils import import_file, PYOMO_ROOT_DIR +from pyomo.contrib.appsi.base import Solver from pyomo.contrib.appsi.solvers.gurobi import Gurobi from pyomo.contrib.gdpopt.create_oa_subproblems import ( add_util_block, @@ -1050,8 +1051,9 @@ def assert_correct_disjuncts_active( self.assertTrue(fabs(value(eight_process.profit.expr) - 68) <= 1e-2) - @unittest.skipUnless(Gurobi().available() and Gurobi().license_is_valid(), - "APPSI Gurobi solver is not available") + @unittest.skipUnless(SolverFactory('appsi_gurobi').available( + exception_flag=False) and SolverFactory('appsi_gurobi').license_is_valid(), + "Legacy APPSI Gurobi solver is not available") def test_auto_persistent_solver(self): exfile = import_file(join(exdir, 'eight_process', 'eight_proc_model.py')) m = exfile.build_eight_process_flowsheet() From 60acf1c2f807f449ae822b43989884fdff41ba00 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 27 Mar 2024 08:40:30 -0600 Subject: [PATCH 1520/1797] black --- pyomo/contrib/gdpopt/tests/test_gdpopt.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/gdpopt/tests/test_gdpopt.py b/pyomo/contrib/gdpopt/tests/test_gdpopt.py index c33e0172def..bf295897ec0 100644 --- a/pyomo/contrib/gdpopt/tests/test_gdpopt.py +++ b/pyomo/contrib/gdpopt/tests/test_gdpopt.py @@ -1051,9 +1051,11 @@ def assert_correct_disjuncts_active( self.assertTrue(fabs(value(eight_process.profit.expr) - 68) <= 1e-2) - @unittest.skipUnless(SolverFactory('appsi_gurobi').available( - exception_flag=False) and SolverFactory('appsi_gurobi').license_is_valid(), - "Legacy APPSI Gurobi solver is not available") + @unittest.skipUnless( + SolverFactory('appsi_gurobi').available(exception_flag=False) + and SolverFactory('appsi_gurobi').license_is_valid(), + "Legacy APPSI Gurobi solver is not available", + ) def test_auto_persistent_solver(self): exfile = import_file(join(exdir, 'eight_process', 'eight_proc_model.py')) m = exfile.build_eight_process_flowsheet() From bb3fafb6231118f64b0e66a95ac74c5db6c11f9f Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 27 Mar 2024 09:07:40 -0600 Subject: [PATCH 1521/1797] Debugging GH Actions failures --- pyomo/contrib/gdpopt/solve_subproblem.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyomo/contrib/gdpopt/solve_subproblem.py b/pyomo/contrib/gdpopt/solve_subproblem.py index e3980c3c784..6ae3cc8e244 100644 --- a/pyomo/contrib/gdpopt/solve_subproblem.py +++ b/pyomo/contrib/gdpopt/solve_subproblem.py @@ -46,6 +46,8 @@ def configure_and_call_solver(model, solver, args, problem_type, timing, time_li solver_args.get('time_limit', float('inf')), remaining ) try: + ## DEBUG + solver_args['tee'] = True results = opt.solve(model, **solver_args) except ValueError as err: if 'Cannot load a SolverResults object with bad status: error' in str(err): From 53684194112384adbb524faa6a36f8077cbc8745 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 27 Mar 2024 11:29:10 -0600 Subject: [PATCH 1522/1797] Skipping 8PP logical problem tests when we don't have a baron license--the transformation of the logical stuff has nested structures and so grew to beyond demo size --- pyomo/contrib/gdpopt/solve_subproblem.py | 2 -- pyomo/contrib/gdpopt/tests/test_gdpopt.py | 9 +++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/gdpopt/solve_subproblem.py b/pyomo/contrib/gdpopt/solve_subproblem.py index 6ae3cc8e244..e3980c3c784 100644 --- a/pyomo/contrib/gdpopt/solve_subproblem.py +++ b/pyomo/contrib/gdpopt/solve_subproblem.py @@ -46,8 +46,6 @@ def configure_and_call_solver(model, solver, args, problem_type, timing, time_li solver_args.get('time_limit', float('inf')), remaining ) try: - ## DEBUG - solver_args['tee'] = True results = opt.solve(model, **solver_args) except ValueError as err: if 'Cannot load a SolverResults object with bad status: error' in str(err): diff --git a/pyomo/contrib/gdpopt/tests/test_gdpopt.py b/pyomo/contrib/gdpopt/tests/test_gdpopt.py index bf295897ec0..9fe8e450cba 100644 --- a/pyomo/contrib/gdpopt/tests/test_gdpopt.py +++ b/pyomo/contrib/gdpopt/tests/test_gdpopt.py @@ -768,6 +768,9 @@ def test_time_limit(self): results.solver.termination_condition, TerminationCondition.maxTimeLimit ) + @unittest.skipUnless( + license_is_valid, "No BARON license--8PP logical problem exceeds demo size" + ) def test_LOA_8PP_logical_default_init(self): """Test logic-based outer approximation with 8PP.""" exfile = import_file(join(exdir, 'eight_process', 'eight_proc_logical.py')) @@ -871,6 +874,9 @@ def test_LOA_8PP_maxBinary(self): ) ct.check_8PP_solution(self, eight_process, results) + @unittest.skipUnless( + license_is_valid, "No BARON license--8PP logical problem exceeds demo size" + ) def test_LOA_8PP_logical_maxBinary(self): """Test logic-based OA with max_binary initialization.""" exfile = import_file(join(exdir, 'eight_process', 'eight_proc_logical.py')) @@ -1131,6 +1137,9 @@ def test_RIC_8PP_default_init(self): ) ct.check_8PP_solution(self, eight_process, results) + @unittest.skipUnless( + license_is_valid, "No BARON license--8PP logical problem exceeds demo size" + ) def test_RIC_8PP_logical_default_init(self): """Test logic-based outer approximation with 8PP.""" exfile = import_file(join(exdir, 'eight_process', 'eight_proc_logical.py')) From 096bf542a903b8f232bea240be8028f2694d44de Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 27 Mar 2024 11:42:20 -0600 Subject: [PATCH 1523/1797] whoops, I can read and type and stuff --- pyomo/contrib/gdpopt/tests/test_gdpopt.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/gdpopt/tests/test_gdpopt.py b/pyomo/contrib/gdpopt/tests/test_gdpopt.py index 9fe8e450cba..3ac532116aa 100644 --- a/pyomo/contrib/gdpopt/tests/test_gdpopt.py +++ b/pyomo/contrib/gdpopt/tests/test_gdpopt.py @@ -769,7 +769,7 @@ def test_time_limit(self): ) @unittest.skipUnless( - license_is_valid, "No BARON license--8PP logical problem exceeds demo size" + license_available, "No BARON license--8PP logical problem exceeds demo size" ) def test_LOA_8PP_logical_default_init(self): """Test logic-based outer approximation with 8PP.""" @@ -875,7 +875,7 @@ def test_LOA_8PP_maxBinary(self): ct.check_8PP_solution(self, eight_process, results) @unittest.skipUnless( - license_is_valid, "No BARON license--8PP logical problem exceeds demo size" + license_available, "No BARON license--8PP logical problem exceeds demo size" ) def test_LOA_8PP_logical_maxBinary(self): """Test logic-based OA with max_binary initialization.""" @@ -1138,7 +1138,7 @@ def test_RIC_8PP_default_init(self): ct.check_8PP_solution(self, eight_process, results) @unittest.skipUnless( - license_is_valid, "No BARON license--8PP logical problem exceeds demo size" + license_available, "No BARON license--8PP logical problem exceeds demo size" ) def test_RIC_8PP_logical_default_init(self): """Test logic-based outer approximation with 8PP.""" From 065b422803f91a929e403df5357cd91d85c9e7c7 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Wed, 27 Mar 2024 17:48:28 -0600 Subject: [PATCH 1524/1797] update docstring --- pyomo/contrib/incidence_analysis/visualize.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/visualize.py b/pyomo/contrib/incidence_analysis/visualize.py index 9360d8ddfc6..af1bdbbb918 100644 --- a/pyomo/contrib/incidence_analysis/visualize.py +++ b/pyomo/contrib/incidence_analysis/visualize.py @@ -112,13 +112,16 @@ def spy_dulmage_mendelsohn( Config options for ``IncidenceGraphInterface`` order: ``IncidenceOrder``, optional - Order in which to plot sparsity structure + Order in which to plot sparsity structure. Default is + ``IncidenceOrder.dulmage_mendelsohn_upper`` for a block-upper triangular + matrix. Set to ``IncidenceOrder.dulmage_mendelsohn_lower`` for a + block-lower triangular matrix. highlight_coarse: bool, optional - Whether to draw a rectangle around the coarse partition + Whether to draw a rectangle around the coarse partition. Default True highlight_fine: bool, optional - Whether to draw a rectangle around the fine partition + Whether to draw a rectangle around the fine partition. Default True skip_wellconstrained: bool, optional Whether to skip highlighting the well-constrained subsystem of the From 81245460c156f187ead1baafaa58195c21389e60 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 27 Mar 2024 20:31:17 -0400 Subject: [PATCH 1525/1797] add highs version check and load_solutions attributes --- pyomo/contrib/mindtpy/algorithm_base_class.py | 48 +++++++++++-------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 8d25f3c1d3a..0394110675d 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -152,7 +152,9 @@ def __init__(self, **kwds): # 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 + self.mip_load_solutions = True + self.nlp_load_solutions = True + self.regularization_mip_load_solutions = True # Support use as a context manager under current solver API def __enter__(self): @@ -302,7 +304,7 @@ def model_is_valid(self): results = self.mip_opt.solve( self.original_model, tee=config.mip_solver_tee, - load_solutions=self.load_solutions, + load_solutions=self.mip_load_solutions, **config.mip_solver_args, ) if len(results.solution) > 0: @@ -846,7 +848,7 @@ def init_rNLP(self, add_oa_cuts=True): results = self.nlp_opt.solve( self.rnlp, tee=config.nlp_solver_tee, - load_solutions=self.load_solutions, + load_solutions=self.nlp_load_solutions, **nlp_args, ) if len(results.solution) > 0: @@ -868,7 +870,7 @@ def init_rNLP(self, add_oa_cuts=True): results = self.nlp_opt.solve( self.rnlp, tee=config.nlp_solver_tee, - load_solutions=self.load_solutions, + load_solutions=self.nlp_load_solutions, **nlp_args, ) if len(results.solution) > 0: @@ -999,7 +1001,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=self.load_solutions, **mip_args + m, + tee=config.mip_solver_tee, + load_solutions=self.mip_load_solutions, + **mip_args, ) if len(results.solution) > 0: m.solutions.load_from(results) @@ -1119,7 +1124,7 @@ def solve_subproblem(self): results = self.nlp_opt.solve( self.fixed_nlp, tee=config.nlp_solver_tee, - load_solutions=self.load_solutions, + load_solutions=self.nlp_load_solutions, **nlp_args, ) if len(results.solution) > 0: @@ -1586,7 +1591,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=self.load_solutions, + load_solutions=self.mip_load_solutions, **mip_args, ) if len(main_mip_results.solution) > 0: @@ -1674,7 +1679,7 @@ def solve_main(self): main_mip_results = self.mip_opt.solve( self.mip, tee=config.mip_solver_tee, - load_solutions=self.load_solutions, + load_solutions=self.mip_load_solutions, **mip_args, ) # update_attributes should be before load_from(main_mip_results), since load_from(main_mip_results) may fail. @@ -1735,7 +1740,7 @@ def solve_fp_main(self): main_mip_results = self.mip_opt.solve( self.mip, tee=config.mip_solver_tee, - load_solutions=self.load_solutions, + load_solutions=self.mip_load_solutions, **mip_args, ) # update_attributes should be before load_from(main_mip_results), since load_from(main_mip_results) may fail. @@ -1778,7 +1783,7 @@ def solve_regularization_main(self): main_mip_results = self.regularization_mip_opt.solve( self.mip, tee=config.mip_solver_tee, - load_solutions=self.load_solutions, + load_solutions=self.regularization_mip_load_solutions, **dict(config.mip_solver_args), ) if len(main_mip_results.solution) > 0: @@ -1994,7 +1999,7 @@ def handle_main_unbounded(self, main_mip): main_mip_results = self.mip_opt.solve( main_mip, tee=config.mip_solver_tee, - load_solutions=self.load_solutions, + load_solutions=self.mip_load_solutions, **config.mip_solver_args, ) if len(main_mip_results.solution) > 0: @@ -2277,6 +2282,11 @@ def check_subsolver_validity(self): raise ValueError(self.config.mip_solver + ' is not available.') if not self.mip_opt.license_is_valid(): raise ValueError(self.config.mip_solver + ' is not licensed.') + if self.config.mip_solver == "appsi_highs": + if self.mip_opt.version() < (1, 7, 0): + raise ValueError( + "MindtPy requires the use of HIGHS version 1.7.0 or higher for full compatibility." + ) if not self.nlp_opt.available(): raise ValueError(self.config.nlp_solver + ' is not available.') if not self.nlp_opt.license_is_valid(): @@ -2324,15 +2334,15 @@ def check_config(self): config.mip_solver = 'cplex_persistent' # related to https://github.com/Pyomo/pyomo/issues/2363 + if 'appsi' in config.mip_solver: + self.mip_load_solutions = False + if 'appsi' in config.nlp_solver: + self.nlp_load_solutions = False 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.mip_regularization_solver is not None + and 'appsi' in config.mip_regularization_solver ): - self.load_solutions = False + self.regularization_mip_load_solutions = False ################################################################################################################################ # Feasibility Pump @@ -2400,7 +2410,7 @@ def solve_fp_subproblem(self): results = self.nlp_opt.solve( fp_nlp, tee=config.nlp_solver_tee, - load_solutions=self.load_solutions, + load_solutions=self.nlp_load_solutions, **nlp_args, ) if len(results.solution) > 0: From 34c2c36c35260e8207a5850edcc5a985e8b3d55d Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 27 Mar 2024 20:39:26 -0400 Subject: [PATCH 1526/1797] add version check for highs in tests --- pyomo/contrib/mindtpy/tests/test_mindtpy.py | 7 ++++++- pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py | 8 +++++++- pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py | 8 +++++++- pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py | 9 ++++++++- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy.py b/pyomo/contrib/mindtpy/tests/test_mindtpy.py index 27c57370ba2..d0364378ed8 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy.py @@ -56,7 +56,12 @@ QCP_model._generate_model() extreme_model_list = [LP_model.model, QCP_model.model] -required_solvers = ('ipopt', 'appsi_highs') +if SolverFactory('appsi_highs').available(exception_flag=False) and SolverFactory( + 'appsi_highs' +).version() >= (1, 7, 0): + required_solvers = ('ipopt', 'appsi_highs') +else: + required_solvers = ('ipopt', 'glpk') if all(SolverFactory(s).available(exception_flag=False) for s in required_solvers): subsolvers_available = True else: diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py index fb78be6b2f1..dda0f74147e 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py @@ -23,7 +23,13 @@ from pyomo.environ import SolverFactory, value from pyomo.opt import TerminationCondition -required_solvers = ('ipopt', 'appsi_highs') +if SolverFactory('appsi_highs').available(exception_flag=False) and SolverFactory( + 'appsi_highs' +).version() >= (1, 7, 0): + required_solvers = ('ipopt', 'appsi_highs') +else: + required_solvers = ('ipopt', 'glpk') + if all(SolverFactory(s).available(exception_flag=False) for s in required_solvers): subsolvers_available = True else: diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py index b8f889e6920..0baa361910e 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py @@ -28,7 +28,13 @@ from pyomo.contrib.mindtpy.tests.feasibility_pump1 import FeasPump1 from pyomo.contrib.mindtpy.tests.feasibility_pump2 import FeasPump2 -required_solvers = ('ipopt', 'appsi_highs') +if SolverFactory('appsi_highs').available(exception_flag=False) and SolverFactory( + 'appsi_highs' +).version() >= (1, 7, 0): + required_solvers = ('ipopt', 'appsi_highs') +else: + required_solvers = ('ipopt', 'glpk') + if all(SolverFactory(s).available(exception_flag=False) for s in required_solvers): subsolvers_available = True else: diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py index d50a41ad000..e01558d48ef 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py @@ -18,7 +18,14 @@ from pyomo.contrib.mindtpy.tests.MINLP_simple import SimpleMINLP as SimpleMINLP model_list = [SimpleMINLP(grey_box=True)] -required_solvers = ('cyipopt', 'glpk') + +if SolverFactory('appsi_highs').available(exception_flag=False) and SolverFactory( + 'appsi_highs' +).version() >= (1, 7, 0): + required_solvers = ('cyipopt', 'appsi_highs') +else: + required_solvers = ('cyipopt', 'glpk') + if all(SolverFactory(s).available(exception_flag=False) for s in required_solvers): subsolvers_available = True else: From bd475d1a45ae34fff431694f74cb5f0d7b934535 Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 28 Mar 2024 11:34:26 -0400 Subject: [PATCH 1527/1797] Fix docstring typo --- 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 306141e9829..5d386240609 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -272,7 +272,7 @@ def adjust_solver_time_settings(timing_data_obj, solver, config): option. However, this may be overridden by any user specifications included in a GAMS optfile, which may be difficult to track down. - (3) To ensure the time limit is specified to a strictly + (4) To ensure the time limit is specified to a strictly positive value, the time limit is adjusted to a value of at least 1 second. """ From a99277df9afaad4fd2011376dfeb4a59acc50898 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 28 Mar 2024 10:59:48 -0600 Subject: [PATCH 1528/1797] Allow multiple definitions of solver options --- pyomo/contrib/solver/base.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 07efbaed449..7e93bacd54b 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -348,9 +348,9 @@ class LegacySolverWrapper: interface. Necessary for backwards compatibility. """ - def __init__(self, solver_io=None, **kwargs): - if solver_io is not None: - raise NotImplementedError('Still working on this') + def __init__(self, **kwargs): + if 'options' in kwargs: + self.options = kwargs.pop('options') super().__init__(**kwargs) # @@ -393,8 +393,14 @@ def _map_config( self.config.time_limit = timelimit if report_timing is not NOTSET: self.config.report_timing = report_timing + if hasattr(self, 'options'): + self.config.solver_options.set_value(self.options) if options is not NOTSET: + # This block is trying to mimic the existing logic in the legacy + # interface that allows users to pass initialized options to + # the solver object and override them in the solve call. 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" if raise_exception_on_nonoptimal_result is not NOTSET: From ef1464c5a3a02d032242ffe10df5ef3f7e753d22 Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 28 Mar 2024 13:17:55 -0400 Subject: [PATCH 1529/1797] Restore PyROS intro and disclaimer logging --- pyomo/contrib/pyros/pyros.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py index c3335588b7b..582233c4a56 100644 --- a/pyomo/contrib/pyros/pyros.py +++ b/pyomo/contrib/pyros/pyros.py @@ -12,7 +12,6 @@ # 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 from pyomo.core.base.block import Block from pyomo.core.expr import value from pyomo.core.base.var import Var @@ -20,7 +19,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, logger_domain from pyomo.contrib.pyros.util import ( recast_to_min_obj, add_decision_rule_constraints, @@ -347,6 +346,23 @@ def solve( global_solver=global_solver, ) ) + + # we want to log the intro and disclaimer in + # advance of assembling the config. + # this helps clarify to the user that any + # messages logged during assembly of the config + # were, in fact, logged after PyROS was initiated + progress_logger = logger_domain( + kwds.get( + "progress_logger", + kwds.get("options", dict()).get( + "progress_logger", default_pyros_solver_logger + ), + ) + ) + self._log_intro(logger=progress_logger, level=logging.INFO) + self._log_disclaimer(logger=progress_logger, level=logging.INFO) + config, state_vars = self._resolve_and_validate_pyros_args(model, **kwds) self._log_config( logger=config.progress_logger, From eb83c2e62485aefa0b0d92cd896b073f6a3ed010 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 28 Mar 2024 11:24:55 -0600 Subject: [PATCH 1530/1797] Add test for option setting behavior --- pyomo/contrib/solver/tests/unit/test_base.py | 35 ++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/pyomo/contrib/solver/tests/unit/test_base.py b/pyomo/contrib/solver/tests/unit/test_base.py index 179d9823679..ecf788b17d9 100644 --- a/pyomo/contrib/solver/tests/unit/test_base.py +++ b/pyomo/contrib/solver/tests/unit/test_base.py @@ -272,6 +272,41 @@ def test_map_config(self): with self.assertRaises(AttributeError): print(instance.config.keepfiles) + def test_solver_options_behavior(self): + # options can work in multiple ways (set from instantiation, set + # after instantiation, set during solve). + # Test case 1: Set at instantiation + solver = base.LegacySolverWrapper(options={'max_iter': 6}) + self.assertEqual(solver.options, {'max_iter': 6}) + + # Test case 2: Set later + solver = base.LegacySolverWrapper() + solver.options = {'max_iter': 4, 'foo': 'bar'} + self.assertEqual(solver.options, {'max_iter': 4, 'foo': 'bar'}) + + # Test case 3: pass some options to the mapping (aka, 'solve' command) + solver = base.LegacySolverWrapper() + config = ConfigDict(implicit=True) + config.declare( + 'solver_options', + ConfigDict(implicit=True, description="Options to pass to the solver."), + ) + solver.config = config + solver._map_config(options={'max_iter': 4}) + self.assertEqual(solver.config.solver_options, {'max_iter': 4}) + + # Test case 4: Set at instantiation and override during 'solve' call + solver = base.LegacySolverWrapper(options={'max_iter': 6}) + config = ConfigDict(implicit=True) + config.declare( + 'solver_options', + ConfigDict(implicit=True, description="Options to pass to the solver."), + ) + solver.config = config + solver._map_config(options={'max_iter': 4}) + self.assertEqual(solver.config.solver_options, {'max_iter': 4}) + self.assertEqual(solver.options, {'max_iter': 6}) + def test_map_results(self): # Unclear how to test this pass From d9ca9879032a9da21a41f0ece5bf623e4553398f Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 28 Mar 2024 13:32:37 -0400 Subject: [PATCH 1531/1797] Update PyROS solver logging docs example --- 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 76a751dd994..9faa6d1365f 100644 --- a/doc/OnlineDocs/contributed_packages/pyros.rst +++ b/doc/OnlineDocs/contributed_packages/pyros.rst @@ -903,10 +903,10 @@ Observe that the log contains the following information: :linenos: ============================================================================== - PyROS: The Pyomo Robust Optimization Solver, v1.2.9. - Pyomo version: 6.7.0 + PyROS: The Pyomo Robust Optimization Solver, v1.2.11. + Pyomo version: 6.7.2 Commit hash: unknown - Invoked at UTC 2023-12-16T00:00:00.000000 + Invoked at UTC 2024-03-28T00:00:00.000000 Developed by: Natalie M. Isenberg (1), Jason A. F. Sherman (1), John D. Siirola (2), Chrysanthos E. Gounaris (1) From b3024f5f74f903cd170c896de751966483695d39 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 28 Mar 2024 12:49:14 -0600 Subject: [PATCH 1532/1797] Make error message more clear --- pyomo/contrib/solver/base.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 7e93bacd54b..e5794a8088c 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -577,7 +577,10 @@ 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}).') + raise ApplicationError( + f'Solver "{self.name}" is not available. ' + f'The returned status is: {ans}.' + ) return bool(ans) def license_is_valid(self) -> bool: From 9eb3da0032f54851fd4743d1244ee88e7b3672c4 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 28 Mar 2024 13:32:01 -0600 Subject: [PATCH 1533/1797] Update options to allow both options and solver_options and writer_config --- pyomo/contrib/solver/base.py | 35 ++++++++++- pyomo/contrib/solver/tests/unit/test_base.py | 64 ++++++++++++++++++++ 2 files changed, 96 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index e5794a8088c..918f436a212 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -349,8 +349,15 @@ class LegacySolverWrapper: """ def __init__(self, **kwargs): - if 'options' in kwargs: + if 'options' in kwargs and 'solver_options' in kwargs: + raise ApplicationError( + "Both 'options' and 'solver_options' were requested. " + "Please use one or the other, not both." + ) + elif 'options' in kwargs: self.options = kwargs.pop('options') + elif 'solver_options' in kwargs: + self.solver_options = kwargs.pop('solver_options') super().__init__(**kwargs) # @@ -376,6 +383,8 @@ def _map_config( keepfiles=NOTSET, solnfile=NOTSET, options=NOTSET, + solver_options=NOTSET, + writer_config=NOTSET, ): """Map between legacy and new interface configuration options""" self.config = self.config() @@ -395,12 +404,27 @@ def _map_config( self.config.report_timing = report_timing if hasattr(self, 'options'): self.config.solver_options.set_value(self.options) - if options is not NOTSET: + if hasattr(self, 'solver_options'): + self.config.solver_options.set_value(self.solver_options) + if (options is not NOTSET) and (solver_options is not NOTSET): + # There is no reason for a user to be trying to mix both old + # and new options. That is silly. So we will yell at them. + # Example that would raise an error: + # solver.solve(model, options={'foo' : 'bar'}, solver_options={'foo' : 'not_bar'}) + raise ApplicationError( + "Both 'options' and 'solver_options' were declared " + "in the 'solve' call. Please use one or the other, " + "not both." + ) + elif options is not NOTSET: # This block is trying to mimic the existing logic in the legacy # interface that allows users to pass initialized options to # the solver object and override them in the solve call. self.config.solver_options.set_value(options) - + elif solver_options is not NOTSET: + self.config.solver_options.set_value(solver_options) + if writer_config is not NOTSET: + self.config.writer_config.set_value(writer_config) # This is a new flag in the interface. To preserve backwards compatibility, # its default is set to "False" if raise_exception_on_nonoptimal_result is not NOTSET: @@ -526,7 +550,10 @@ def solve( options: Optional[Dict] = None, keepfiles: bool = False, symbolic_solver_labels: bool = False, + # These are for forward-compatibility raise_exception_on_nonoptimal_result: bool = False, + solver_options: Optional[Dict] = None, + writer_config: Optional[Dict] = None, ): """ Solve method: maps new solve method style to backwards compatible version. @@ -552,6 +579,8 @@ def solve( 'keepfiles', 'solnfile', 'options', + 'solver_options', + 'writer_config', ) loc = locals() filtered_args = {k: loc[k] for k in map_args if loc.get(k, None) is not None} diff --git a/pyomo/contrib/solver/tests/unit/test_base.py b/pyomo/contrib/solver/tests/unit/test_base.py index ecf788b17d9..287116008ab 100644 --- a/pyomo/contrib/solver/tests/unit/test_base.py +++ b/pyomo/contrib/solver/tests/unit/test_base.py @@ -14,6 +14,7 @@ from pyomo.common import unittest from pyomo.common.config import ConfigDict from pyomo.contrib.solver import base +from pyomo.common.errors import ApplicationError class TestSolverBase(unittest.TestCase): @@ -307,6 +308,69 @@ def test_solver_options_behavior(self): self.assertEqual(solver.config.solver_options, {'max_iter': 4}) self.assertEqual(solver.options, {'max_iter': 6}) + # solver_options are also supported + # Test case 1: set at instantiation + solver = base.LegacySolverWrapper(solver_options={'max_iter': 6}) + self.assertEqual(solver.solver_options, {'max_iter': 6}) + + # Test case 2: Set later + solver = base.LegacySolverWrapper() + solver.solver_options = {'max_iter': 4, 'foo': 'bar'} + self.assertEqual(solver.solver_options, {'max_iter': 4, 'foo': 'bar'}) + + # Test case 3: pass some solver_options to the mapping (aka, 'solve' command) + solver = base.LegacySolverWrapper() + config = ConfigDict(implicit=True) + config.declare( + 'solver_options', + ConfigDict(implicit=True, description="Options to pass to the solver."), + ) + solver.config = config + solver._map_config(solver_options={'max_iter': 4}) + self.assertEqual(solver.config.solver_options, {'max_iter': 4}) + + # Test case 4: Set at instantiation and override during 'solve' call + solver = base.LegacySolverWrapper(solver_options={'max_iter': 6}) + config = ConfigDict(implicit=True) + config.declare( + 'solver_options', + ConfigDict(implicit=True, description="Options to pass to the solver."), + ) + solver.config = config + solver._map_config(solver_options={'max_iter': 4}) + self.assertEqual(solver.config.solver_options, {'max_iter': 4}) + self.assertEqual(solver.solver_options, {'max_iter': 6}) + + # users can mix... sort of + # Test case 1: Initialize with options, solve with solver_options + solver = base.LegacySolverWrapper(options={'max_iter': 6}) + config = ConfigDict(implicit=True) + config.declare( + 'solver_options', + ConfigDict(implicit=True, description="Options to pass to the solver."), + ) + solver.config = config + solver._map_config(solver_options={'max_iter': 4}) + self.assertEqual(solver.config.solver_options, {'max_iter': 4}) + + # users CANNOT initialize both values at the same time, because how + # do we know what to do with it then? + # Test case 1: Class instance + with self.assertRaises(ApplicationError): + solver = base.LegacySolverWrapper( + options={'max_iter': 6}, solver_options={'max_iter': 4} + ) + # Test case 2: Passing to `solve` + solver = base.LegacySolverWrapper() + config = ConfigDict(implicit=True) + config.declare( + 'solver_options', + ConfigDict(implicit=True, description="Options to pass to the solver."), + ) + solver.config = config + with self.assertRaises(ApplicationError): + solver._map_config(solver_options={'max_iter': 4}, options={'max_iter': 6}) + def test_map_results(self): # Unclear how to test this pass From a96cd1074d9bcdcb555b98278226b034462df04f Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 28 Mar 2024 14:42:11 -0600 Subject: [PATCH 1534/1797] Add a helpful comment --- pyomo/contrib/solver/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 918f436a212..756babc6b20 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -349,6 +349,8 @@ class LegacySolverWrapper: """ def __init__(self, **kwargs): + # There is no reason for a user to be trying to mix both old + # and new options. That is silly. So we will yell at them. if 'options' in kwargs and 'solver_options' in kwargs: raise ApplicationError( "Both 'options' and 'solver_options' were requested. " From 5cd0e653c2386e3e4436769c1464f98645091c6a Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 28 Mar 2024 14:48:57 -0600 Subject: [PATCH 1535/1797] Accidentally removed solver_io check --- pyomo/contrib/solver/base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 756babc6b20..064c411c74d 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -349,6 +349,8 @@ class LegacySolverWrapper: """ def __init__(self, **kwargs): + if 'solver_io' in kwargs: + raise NotImplementedError('Still working on this') # There is no reason for a user to be trying to mix both old # and new options. That is silly. So we will yell at them. if 'options' in kwargs and 'solver_options' in kwargs: From 71709fd7bc6ed63ae82dca1ce05cbdcb1a4fcc36 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 28 Mar 2024 15:04:05 -0600 Subject: [PATCH 1536/1797] Add information about options to docs --- .../developer_reference/solvers.rst | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/doc/OnlineDocs/developer_reference/solvers.rst b/doc/OnlineDocs/developer_reference/solvers.rst index 6168da3480e..94fb684236f 100644 --- a/doc/OnlineDocs/developer_reference/solvers.rst +++ b/doc/OnlineDocs/developer_reference/solvers.rst @@ -84,6 +84,37 @@ be used with other Pyomo tools / capabilities. ... 3 Declarations: x y obj +In keeping with our commitment to backwards compatibility, both the legacy and +future methods of specifying solver options are supported: + +.. testcode:: + :skipif: not ipopt_available + + import pyomo.environ as pyo + + 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) + + # Backwards compatible + status = pyo.SolverFactory('ipopt_v2').solve(model, options={'max_iter' : 6}) + # Forwards compatible + status = pyo.SolverFactory('ipopt_v2').solve(model, solver_options={'max_iter' : 6}) + model.pprint() + +.. testoutput:: + :skipif: not ipopt_available + :hide: + + 2 Var Declarations + ... + 3 Declarations: x y obj + Using the new interfaces directly ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From cd33b434126e77769462641f0cdc40cdfdab8211 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 28 Mar 2024 17:03:03 -0600 Subject: [PATCH 1537/1797] Consolidate into just self.options --- pyomo/contrib/solver/base.py | 9 +++------ pyomo/contrib/solver/tests/unit/test_base.py | 13 ++++--------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 064c411c74d..79e677b6226 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -361,7 +361,7 @@ def __init__(self, **kwargs): elif 'options' in kwargs: self.options = kwargs.pop('options') elif 'solver_options' in kwargs: - self.solver_options = kwargs.pop('solver_options') + self.options = kwargs.pop('solver_options') super().__init__(**kwargs) # @@ -408,17 +408,14 @@ def _map_config( self.config.report_timing = report_timing if hasattr(self, 'options'): self.config.solver_options.set_value(self.options) - if hasattr(self, 'solver_options'): - self.config.solver_options.set_value(self.solver_options) if (options is not NOTSET) and (solver_options is not NOTSET): # There is no reason for a user to be trying to mix both old # and new options. That is silly. So we will yell at them. # Example that would raise an error: # solver.solve(model, options={'foo' : 'bar'}, solver_options={'foo' : 'not_bar'}) raise ApplicationError( - "Both 'options' and 'solver_options' were declared " - "in the 'solve' call. Please use one or the other, " - "not both." + "Both 'options' and 'solver_options' were requested. " + "Please use one or the other, not both." ) elif options is not NOTSET: # This block is trying to mimic the existing logic in the legacy diff --git a/pyomo/contrib/solver/tests/unit/test_base.py b/pyomo/contrib/solver/tests/unit/test_base.py index 287116008ab..fb8020bedf6 100644 --- a/pyomo/contrib/solver/tests/unit/test_base.py +++ b/pyomo/contrib/solver/tests/unit/test_base.py @@ -311,14 +311,9 @@ def test_solver_options_behavior(self): # solver_options are also supported # Test case 1: set at instantiation solver = base.LegacySolverWrapper(solver_options={'max_iter': 6}) - self.assertEqual(solver.solver_options, {'max_iter': 6}) - - # Test case 2: Set later - solver = base.LegacySolverWrapper() - solver.solver_options = {'max_iter': 4, 'foo': 'bar'} - self.assertEqual(solver.solver_options, {'max_iter': 4, 'foo': 'bar'}) + self.assertEqual(solver.options, {'max_iter': 6}) - # Test case 3: pass some solver_options to the mapping (aka, 'solve' command) + # Test case 2: pass some solver_options to the mapping (aka, 'solve' command) solver = base.LegacySolverWrapper() config = ConfigDict(implicit=True) config.declare( @@ -329,7 +324,7 @@ def test_solver_options_behavior(self): solver._map_config(solver_options={'max_iter': 4}) self.assertEqual(solver.config.solver_options, {'max_iter': 4}) - # Test case 4: Set at instantiation and override during 'solve' call + # Test case 3: Set at instantiation and override during 'solve' call solver = base.LegacySolverWrapper(solver_options={'max_iter': 6}) config = ConfigDict(implicit=True) config.declare( @@ -339,7 +334,7 @@ def test_solver_options_behavior(self): solver.config = config solver._map_config(solver_options={'max_iter': 4}) self.assertEqual(solver.config.solver_options, {'max_iter': 4}) - self.assertEqual(solver.solver_options, {'max_iter': 6}) + self.assertEqual(solver.options, {'max_iter': 6}) # users can mix... sort of # Test case 1: Initialize with options, solve with solver_options From 4d8e1c3d7c6229dcfe712d7f71e125bc74f127cc Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 31 Mar 2024 13:23:48 -0600 Subject: [PATCH 1538/1797] Move from isinstance to ctype for verifying component type --- .../piecewise_to_gdp_transformation.py | 4 +-- .../core/plugins/transform/add_slack_vars.py | 5 ++-- pyomo/core/plugins/transform/scaling.py | 6 ++--- pyomo/repn/plugins/nl_writer.py | 25 +++++++++++-------- pyomo/util/calc_var_value.py | 4 +-- pyomo/util/report_scaling.py | 4 +-- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/pyomo/contrib/piecewise/transform/piecewise_to_gdp_transformation.py b/pyomo/contrib/piecewise/transform/piecewise_to_gdp_transformation.py index 779bb601c71..5417cbc17f4 100644 --- a/pyomo/contrib/piecewise/transform/piecewise_to_gdp_transformation.py +++ b/pyomo/contrib/piecewise/transform/piecewise_to_gdp_transformation.py @@ -33,7 +33,7 @@ Any, ) from pyomo.core.base import Transformation -from pyomo.core.base.block import BlockData, Block +from pyomo.core.base.block import Block from pyomo.core.util import target_list from pyomo.gdp import Disjunct, Disjunction from pyomo.gdp.util import is_child_of @@ -147,7 +147,7 @@ def _apply_to_impl(self, instance, **kwds): self._transform_piecewise_linear_function( t, config.descend_into_expressions ) - elif t.ctype is Block or isinstance(t, BlockData): + elif issubclass(t.ctype, Block): self._transform_block(t, config.descend_into_expressions) elif t.ctype is Constraint: if not config.descend_into_expressions: diff --git a/pyomo/core/plugins/transform/add_slack_vars.py b/pyomo/core/plugins/transform/add_slack_vars.py index 0007f8de7ad..39903384729 100644 --- a/pyomo/core/plugins/transform/add_slack_vars.py +++ b/pyomo/core/plugins/transform/add_slack_vars.py @@ -23,7 +23,6 @@ from pyomo.core.plugins.transform.hierarchy import NonIsomorphicTransformation from pyomo.common.config import ConfigBlock, ConfigValue from pyomo.core.base import ComponentUID -from pyomo.core.base.constraint import ConstraintData from pyomo.common.deprecation import deprecation_warning @@ -42,7 +41,7 @@ def target_list(x): # [ESJ 07/15/2020] We have to just pass it through because we need the # instance in order to be able to do anything about it... return [x] - elif isinstance(x, (Constraint, ConstraintData)): + elif getattr(x, 'ctype', None) is Constraint: return [x] elif hasattr(x, '__iter__'): ans = [] @@ -53,7 +52,7 @@ def target_list(x): deprecation_msg = None # same as above... ans.append(i) - elif isinstance(i, (Constraint, ConstraintData)): + elif getattr(i, 'ctype', None) is Constraint: ans.append(i) else: raise ValueError( diff --git a/pyomo/core/plugins/transform/scaling.py b/pyomo/core/plugins/transform/scaling.py index ef418f094ae..e962352668c 100644 --- a/pyomo/core/plugins/transform/scaling.py +++ b/pyomo/core/plugins/transform/scaling.py @@ -15,8 +15,6 @@ Var, Constraint, Objective, - ConstraintData, - ObjectiveData, Suffix, value, ) @@ -197,7 +195,7 @@ def _apply_to(self, model, rename=True): already_scaled.add(id(c)) # perform the constraint/objective scaling and variable sub scaling_factor = component_scaling_factor_map[c] - if isinstance(c, ConstraintData): + if c.ctype is Constraint: body = scaling_factor * replace_expressions( expr=c.body, substitution_map=variable_substitution_dict, @@ -226,7 +224,7 @@ def _apply_to(self, model, rename=True): else: c.set_value((lower, body, upper)) - elif isinstance(c, ObjectiveData): + elif c.ctype is Objective: c.expr = scaling_factor * replace_expressions( expr=c.expr, substitution_map=variable_substitution_dict, diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index 8cc73b5e3fe..76599f74228 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -437,6 +437,7 @@ def store(self, obj, val): self.values[obj] = val def compile(self, column_order, row_order, obj_order, model_id): + var_con_obj = {Var, Constraint, Objective} missing_component_data = ComponentSet() unknown_data = ComponentSet() queue = [self.values.items()] @@ -462,18 +463,20 @@ def compile(self, column_order, row_order, obj_order, model_id): 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( - product( - filterfalse(self.values.__contains__, obj.values()), (val,) + elif getattr(obj, 'ctype', None) in var_con_obj: + if obj.is_indexed(): + # 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( + product( + filterfalse(self.values.__contains__, obj.values()), + (val,), + ) ) - ) + else: + missing_component_data.add(obj) else: unknown_data.add(obj) if missing_component_data: diff --git a/pyomo/util/calc_var_value.py b/pyomo/util/calc_var_value.py index 254b82c59cd..156ad56dffb 100644 --- a/pyomo/util/calc_var_value.py +++ b/pyomo/util/calc_var_value.py @@ -12,7 +12,7 @@ from pyomo.common.errors import IterationLimitError 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 pyomo.core.base.constraint import Constraint import logging @@ -81,7 +81,7 @@ def calculate_variable_from_constraint( """ # Leverage all the Constraint logic to process the incoming tuple/expression - if not isinstance(constraint, ConstraintData): + if not getattr(constraint, 'ctype', None) is Constraint: constraint = Constraint(expr=constraint, name=type(constraint).__name__) constraint.construct() diff --git a/pyomo/util/report_scaling.py b/pyomo/util/report_scaling.py index 7619662c482..02b3710c334 100644 --- a/pyomo/util/report_scaling.py +++ b/pyomo/util/report_scaling.py @@ -13,7 +13,7 @@ import math from pyomo.core.base.block import BlockData from pyomo.common.collections import ComponentSet -from pyomo.core.base.var import VarData +from pyomo.core.base.var import Var from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr from pyomo.core.expr.calculus.diff_with_pyomo import reverse_sd import logging @@ -73,7 +73,7 @@ def _check_coefficients( ): ders = reverse_sd(expr) for _v, _der in ders.items(): - if isinstance(_v, VarData): + if getattr(_v, 'ctype', None) is Var: if _v.is_fixed(): continue der_lb, der_ub = compute_bounds_on_expr(_der) From 812a1b7fd80be976c5cba7ff93c5bc68d9f795da Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 31 Mar 2024 18:52:18 -0600 Subject: [PATCH 1539/1797] Remove giant status if tree --- pyomo/contrib/solver/gurobi_direct.py | 65 +++++++++++++-------------- 1 file changed, 30 insertions(+), 35 deletions(-) diff --git a/pyomo/contrib/solver/gurobi_direct.py b/pyomo/contrib/solver/gurobi_direct.py index 7b5ec6ed904..f5f1bca7184 100644 --- a/pyomo/contrib/solver/gurobi_direct.py +++ b/pyomo/contrib/solver/gurobi_direct.py @@ -88,6 +88,7 @@ class GurobiDirect(SolverBase): _available = None _num_instances = 0 + _tc_map = None def __init__(self, **kwds): super().__init__(**kwds) @@ -256,7 +257,6 @@ def _postsolve(self, timer: HierarchicalTimer, loader): config = self._config gprob = loader._grb_model - grb = gurobipy.GRB status = gprob.Status results = Results() @@ -264,45 +264,16 @@ def _postsolve(self, timer: HierarchicalTimer, loader): results.timing_info.gurobi_time = gprob.Runtime if gprob.SolCount > 0: - if status == grb.OPTIMAL: + if status == gurobipy.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 + results.termination_condition = self._get_tc_map().get( + status, TerminationCondition.unknown + ) if ( results.termination_condition @@ -310,7 +281,9 @@ def _postsolve(self, timer: HierarchicalTimer, loader): 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.' + 'Solver did not find the optimal solution. Set ' + 'opt.config.raise_exception_on_nonoptimal_result=False ' + 'to bypass this error.' ) results.incumbent_objective = None @@ -348,3 +321,25 @@ def _postsolve(self, timer: HierarchicalTimer, loader): timer.stop('load solution') return results + + def _get_tc_map(self): + if GurobiDirect._tc_map is None: + grb = gurobipy.GRB + tc = TerminationCondition + GurobiDirect._tc_map = { + grb.LOADED: tc.unknown, # problem is loaded, but no solution + grb.OPTIMAL: tc.convergenceCriteriaSatisfied, + grb.INFEASIBLE: tc.provenInfeasible, + grb.INF_OR_UNBD: tc.infeasibleOrUnbounded, + grb.UNBOUNDED: tc.unbounded, + grb.CUTOFF: tc.objectiveLimit, + grb.ITERATION_LIMIT: tc.iterationLimit, + grb.NODE_LIMIT: tc.iterationLimit, + grb.TIME_LIMIT: tc.maxTimeLimit, + grb.SOLUTION_LIMIT: tc.unknown, + grb.INTERRUPTED: tc.interrupted, + grb.NUMERIC: tc.unknown, + grb.SUBOPTIMAL: tc.unknown, + grb.USER_OBJ_LIMIT: tc.objectiveLimit, + } + return GurobiDirect._tc_map From 9d4b9131c76b337a41ff12cf4ea6f55062ae3115 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 31 Mar 2024 19:41:34 -0600 Subject: [PATCH 1540/1797] NFC: apply black --- pyomo/core/plugins/transform/scaling.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/pyomo/core/plugins/transform/scaling.py b/pyomo/core/plugins/transform/scaling.py index e962352668c..11d4ac8c493 100644 --- a/pyomo/core/plugins/transform/scaling.py +++ b/pyomo/core/plugins/transform/scaling.py @@ -10,14 +10,7 @@ # ___________________________________________________________________________ from pyomo.common.collections import ComponentMap -from pyomo.core.base import ( - Block, - Var, - Constraint, - Objective, - Suffix, - value, -) +from pyomo.core.base import Block, Var, Constraint, Objective, Suffix, value from pyomo.core.plugins.transform.hierarchy import Transformation from pyomo.core.base import TransformationFactory from pyomo.core.base.suffix import SuffixFinder From 711fafe517ce8f4d21a89c462e9083a40dfb55ca Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 31 Mar 2024 19:42:37 -0600 Subject: [PATCH 1541/1797] NFC: apply black --- pyomo/contrib/solver/gurobi_direct.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/solver/gurobi_direct.py b/pyomo/contrib/solver/gurobi_direct.py index f5f1bca7184..1164686f0f1 100644 --- a/pyomo/contrib/solver/gurobi_direct.py +++ b/pyomo/contrib/solver/gurobi_direct.py @@ -328,7 +328,7 @@ def _get_tc_map(self): tc = TerminationCondition GurobiDirect._tc_map = { grb.LOADED: tc.unknown, # problem is loaded, but no solution - grb.OPTIMAL: tc.convergenceCriteriaSatisfied, + grb.OPTIMAL: tc.convergenceCriteriaSatisfied, grb.INFEASIBLE: tc.provenInfeasible, grb.INF_OR_UNBD: tc.infeasibleOrUnbounded, grb.UNBOUNDED: tc.unbounded, From 8c4fb774a30a621e7890dd006cc9e83931c545e6 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 31 Mar 2024 20:17:52 -0600 Subject: [PATCH 1542/1797] Remove debugging; expand comment explaining difference in cut-and-paste tests --- pyomo/repn/tests/test_standard_form.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/repn/tests/test_standard_form.py b/pyomo/repn/tests/test_standard_form.py index 591703e6ae8..4c66ae87c41 100644 --- a/pyomo/repn/tests/test_standard_form.py +++ b/pyomo/repn/tests/test_standard_form.py @@ -256,11 +256,11 @@ def test_alternative_forms(self): [[1, 0, 2, 0], [0, 0, 1, 4], [0, 1, 6, 0], [0, 1, 6, 0], [1, 1, 0, 0]] ) self.assertTrue(np.all(repn.A == ref)) - print(repn) - print(repn.b) self.assertTrue(np.all(repn.b == np.array([3, 5, 6, -3, 8]))) self.assertTrue(np.all(repn.c == np.array([[-1, 0, -5, 0], [1, 0, 0, 15]]))) - # Note that the solution is a mix of inequality and equality constraints + # Note that the mixed_form solution is a mix of inequality and + # equality constraints, so we cannot (easily) reuse the + # _verify_solutions helper (as in the above cases): # self._verify_solution(soln, repn, False) repn = LinearStandardFormCompiler().write( From 7d9d490e05a2dca6a4b97f731c383295c8b40970 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 1 Apr 2024 13:04:36 -0600 Subject: [PATCH 1543/1797] Change check for options --- pyomo/contrib/solver/base.py | 19 +++++++++---------- pyomo/contrib/solver/tests/unit/test_base.py | 5 ++--- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 79e677b6226..9c19fccaa89 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -353,14 +353,13 @@ def __init__(self, **kwargs): raise NotImplementedError('Still working on this') # There is no reason for a user to be trying to mix both old # and new options. That is silly. So we will yell at them. - if 'options' in kwargs and 'solver_options' in kwargs: - raise ApplicationError( - "Both 'options' and 'solver_options' were requested. " - "Please use one or the other, not both." - ) - elif 'options' in kwargs: - self.options = kwargs.pop('options') - elif 'solver_options' in kwargs: + self.options = kwargs.pop('options', None) + if 'solver_options' in kwargs: + if self.options is not None: + raise ValueError( + "Both 'options' and 'solver_options' were requested. " + "Please use one or the other, not both." + ) self.options = kwargs.pop('solver_options') super().__init__(**kwargs) @@ -406,14 +405,14 @@ def _map_config( self.config.time_limit = timelimit if report_timing is not NOTSET: self.config.report_timing = report_timing - if hasattr(self, 'options'): + if self.options is not None: self.config.solver_options.set_value(self.options) if (options is not NOTSET) and (solver_options is not NOTSET): # There is no reason for a user to be trying to mix both old # and new options. That is silly. So we will yell at them. # Example that would raise an error: # solver.solve(model, options={'foo' : 'bar'}, solver_options={'foo' : 'not_bar'}) - raise ApplicationError( + raise ValueError( "Both 'options' and 'solver_options' were requested. " "Please use one or the other, not both." ) diff --git a/pyomo/contrib/solver/tests/unit/test_base.py b/pyomo/contrib/solver/tests/unit/test_base.py index fb8020bedf6..b52f96ba903 100644 --- a/pyomo/contrib/solver/tests/unit/test_base.py +++ b/pyomo/contrib/solver/tests/unit/test_base.py @@ -14,7 +14,6 @@ from pyomo.common import unittest from pyomo.common.config import ConfigDict from pyomo.contrib.solver import base -from pyomo.common.errors import ApplicationError class TestSolverBase(unittest.TestCase): @@ -351,7 +350,7 @@ def test_solver_options_behavior(self): # users CANNOT initialize both values at the same time, because how # do we know what to do with it then? # Test case 1: Class instance - with self.assertRaises(ApplicationError): + with self.assertRaises(ValueError): solver = base.LegacySolverWrapper( options={'max_iter': 6}, solver_options={'max_iter': 4} ) @@ -363,7 +362,7 @@ def test_solver_options_behavior(self): ConfigDict(implicit=True, description="Options to pass to the solver."), ) solver.config = config - with self.assertRaises(ApplicationError): + with self.assertRaises(ValueError): solver._map_config(solver_options={'max_iter': 4}, options={'max_iter': 6}) def test_map_results(self): From bfb9beb7488e722aa6b336b8a4c89ce39561a81c Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 1 Apr 2024 13:28:51 -0600 Subject: [PATCH 1544/1797] March 2024: Typos Update --- .github/workflows/typos.toml | 24 ++++++++++++++++++++++++ pyomo/contrib/latex_printer/__init__.py | 13 +------------ pyomo/core/base/component.py | 4 ++-- pyomo/core/base/indexed_component.py | 2 +- pyomo/core/base/reference.py | 2 +- pyomo/gdp/plugins/fix_disjuncts.py | 2 +- pyomo/gdp/tests/models.py | 2 +- pyomo/network/foqus_graph.py | 4 ++-- pyomo/solvers/plugins/solvers/GAMS.py | 20 ++++++++++---------- 9 files changed, 43 insertions(+), 30 deletions(-) diff --git a/.github/workflows/typos.toml b/.github/workflows/typos.toml index 23f94fc8afd..f98a6122ffd 100644 --- a/.github/workflows/typos.toml +++ b/.github/workflows/typos.toml @@ -40,4 +40,28 @@ WRONLY = "WRONLY" Hax = "Hax" # Big Sur Sur = "Sur" +# Ignore the shorthand ans for and +ans = "ans" +# Ignore the keyword arange +arange = "arange" +# Ignore IIS +IIS = "IIS" +iis = "iis" +# Ignore PN +PN = "PN" +# Ignore hd +hd = "hd" +# Ignore opf +opf = "opf" +# Ignore FRE +FRE = "FRE" +# Ignore MCH +MCH = "MCH" +# Ignore RO +ro = "ro" +RO = "RO" +# Ignore EOF - end of file +EOF = "EOF" +# Ignore lst as shorthand for list +lst = "lst" # AS NEEDED: Add More Words Below diff --git a/pyomo/contrib/latex_printer/__init__.py b/pyomo/contrib/latex_printer/__init__.py index c434b53dfe1..02eaa636a36 100644 --- a/pyomo/contrib/latex_printer/__init__.py +++ b/pyomo/contrib/latex_printer/__init__.py @@ -9,22 +9,11 @@ # 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. -# ___________________________________________________________________________ - # Recommended just to build all of the appropriate things import pyomo.environ # Remove one layer of .latex_printer -# import statemnt is now: +# import statement is now: # from pyomo.contrib.latex_printer import latex_printer try: from pyomo.contrib.latex_printer.latex_printer import latex_printer diff --git a/pyomo/core/base/component.py b/pyomo/core/base/component.py index 22c2bc4b804..9b1929daa06 100644 --- a/pyomo/core/base/component.py +++ b/pyomo/core/base/component.py @@ -368,7 +368,7 @@ def pprint(self, ostream=None, verbose=False, prefix=""): @property def name(self): - """Get the fully qualifed component name.""" + """Get the fully qualified component name.""" return self.getname(fully_qualified=True) # Adding a setter here to help users adapt to the new @@ -664,7 +664,7 @@ def getname(self, fully_qualified=False, name_buffer=None, relative_to=None): @property def name(self): - """Get the fully qualifed component name.""" + """Get the fully qualified component name.""" return self.getname(fully_qualified=True) # Allow setting a component's name if it is not owned by a parent diff --git a/pyomo/core/base/indexed_component.py b/pyomo/core/base/indexed_component.py index e1be613d666..37a62e5c4d7 100644 --- a/pyomo/core/base/indexed_component.py +++ b/pyomo/core/base/indexed_component.py @@ -731,7 +731,7 @@ def __delitem__(self, index): # this supports "del m.x[:,1]" through a simple recursive call if index.__class__ is IndexedComponent_slice: - # Assert that this slice ws just generated + # Assert that this slice was just generated assert len(index._call_stack) == 1 # Make a copy of the slicer items *before* we start # iterating over it (since we will be removing items!). diff --git a/pyomo/core/base/reference.py b/pyomo/core/base/reference.py index 2279db067a6..84cccec9749 100644 --- a/pyomo/core/base/reference.py +++ b/pyomo/core/base/reference.py @@ -579,7 +579,7 @@ def Reference(reference, ctype=NOTSET): :py:class:`IndexedComponent`. If the indices associated with wildcards in the component slice all - refer to the same :py:class:`Set` objects for all data identifed by + refer to the same :py:class:`Set` objects for all data identified by the slice, then the resulting indexed component will be indexed by the product of those sets. However, if all data do not share common set objects, or only a subset of indices in a multidimentional set diff --git a/pyomo/gdp/plugins/fix_disjuncts.py b/pyomo/gdp/plugins/fix_disjuncts.py index 44a9d91d513..172363caab7 100644 --- a/pyomo/gdp/plugins/fix_disjuncts.py +++ b/pyomo/gdp/plugins/fix_disjuncts.py @@ -52,7 +52,7 @@ class GDP_Disjunct_Fixer(Transformation): This reclassifies all disjuncts in the passed model instance as ctype Block and deactivates the constraints and disjunctions within inactive disjuncts. - In addition, it transforms relvant LogicalConstraints and BooleanVars so + In addition, it transforms relevant LogicalConstraints and BooleanVars so that the resulting model is a (MI)(N)LP (where it is only mixed-integer if the model contains integer-domain Vars or BooleanVars which were not indicator_vars of Disjuncs. diff --git a/pyomo/gdp/tests/models.py b/pyomo/gdp/tests/models.py index 0b84641899c..2995cacb450 100644 --- a/pyomo/gdp/tests/models.py +++ b/pyomo/gdp/tests/models.py @@ -840,7 +840,7 @@ def makeAnyIndexedDisjunctionOfDisjunctDatas(): build from DisjunctDatas. Identical mathematically to makeDisjunctionOfDisjunctDatas. - Used to test that the right things happen for a case where soemone + Used to test that the right things happen for a case where someone implements an algorithm which iteratively generates disjuncts and retransforms""" m = ConcreteModel() diff --git a/pyomo/network/foqus_graph.py b/pyomo/network/foqus_graph.py index e4cf3b92014..d904fa54008 100644 --- a/pyomo/network/foqus_graph.py +++ b/pyomo/network/foqus_graph.py @@ -358,9 +358,9 @@ def scc_calculation_order(self, sccNodes, ie, oe): done = False for i in range(len(sccNodes)): for j in range(len(sccNodes)): - for ine in ie[i]: + for in_e in ie[i]: for oute in oe[j]: - if ine == oute: + if in_e == oute: adj[j].append(i) adjR[i].append(j) done = True diff --git a/pyomo/solvers/plugins/solvers/GAMS.py b/pyomo/solvers/plugins/solvers/GAMS.py index be3499a2f6b..606098e5b7b 100644 --- a/pyomo/solvers/plugins/solvers/GAMS.py +++ b/pyomo/solvers/plugins/solvers/GAMS.py @@ -198,8 +198,8 @@ def _get_version(self): return _extract_version('') from gams import GamsWorkspace - ws = GamsWorkspace() - version = tuple(int(i) for i in ws._version.split('.')[:4]) + workspace = GamsWorkspace() + version = tuple(int(i) for i in workspace._version.split('.')[:4]) while len(version) < 4: version += (0,) return version @@ -209,8 +209,8 @@ def _run_simple_model(self, n): try: from gams import GamsWorkspace, DebugLevel - ws = GamsWorkspace(debug=DebugLevel.Off, working_directory=tmpdir) - t1 = ws.add_job_from_string(self._simple_model(n)) + workspace = GamsWorkspace(debug=DebugLevel.Off, working_directory=tmpdir) + t1 = workspace.add_job_from_string(self._simple_model(n)) t1.run() return True except: @@ -330,12 +330,12 @@ def solve(self, *args, **kwds): if tmpdir is not None and os.path.exists(tmpdir): newdir = False - ws = GamsWorkspace( + workspace = GamsWorkspace( debug=DebugLevel.KeepFiles if keepfiles else DebugLevel.Off, working_directory=tmpdir, ) - t1 = ws.add_job_from_string(output_file.getvalue()) + t1 = workspace.add_job_from_string(output_file.getvalue()) try: with OutputStream(tee=tee, logfile=logfile) as output_stream: @@ -349,7 +349,7 @@ def solve(self, *args, **kwds): # Always name working directory or delete files, # regardless of any errors. if keepfiles: - print("\nGAMS WORKING DIRECTORY: %s\n" % ws.working_directory) + print("\nGAMS WORKING DIRECTORY: %s\n" % workspace.working_directory) elif tmpdir is not None: # Garbage collect all references to t1.out_db # So that .gdx file can be deleted @@ -359,7 +359,7 @@ def solve(self, *args, **kwds): except: # Catch other errors and remove files first if keepfiles: - print("\nGAMS WORKING DIRECTORY: %s\n" % ws.working_directory) + print("\nGAMS WORKING DIRECTORY: %s\n" % workspace.working_directory) elif tmpdir is not None: # Garbage collect all references to t1.out_db # So that .gdx file can be deleted @@ -398,7 +398,7 @@ def solve(self, *args, **kwds): extract_rc = 'rc' in model_suffixes results = SolverResults() - results.problem.name = os.path.join(ws.working_directory, t1.name + '.gms') + results.problem.name = os.path.join(workspace.working_directory, t1.name + '.gms') results.problem.lower_bound = t1.out_db["OBJEST"].find_record().value results.problem.upper_bound = t1.out_db["OBJEST"].find_record().value results.problem.number_of_variables = t1.out_db["NUMVAR"].find_record().value @@ -587,7 +587,7 @@ def solve(self, *args, **kwds): results.solution.insert(soln) if keepfiles: - print("\nGAMS WORKING DIRECTORY: %s\n" % ws.working_directory) + print("\nGAMS WORKING DIRECTORY: %s\n" % workspace.working_directory) elif tmpdir is not None: # Garbage collect all references to t1.out_db # So that .gdx file can be deleted From ee92b7001c599a373c520be75097fdac05cfdd68 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 1 Apr 2024 13:30:52 -0600 Subject: [PATCH 1545/1797] Comment is misleading --- .github/workflows/typos.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/typos.toml b/.github/workflows/typos.toml index f98a6122ffd..4d69cde34e1 100644 --- a/.github/workflows/typos.toml +++ b/.github/workflows/typos.toml @@ -40,7 +40,7 @@ WRONLY = "WRONLY" Hax = "Hax" # Big Sur Sur = "Sur" -# Ignore the shorthand ans for and +# Ignore the shorthand ans for answer ans = "ans" # Ignore the keyword arange arange = "arange" From a69c1b8615fd58db538de28186f54fcd6b594cb9 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 1 Apr 2024 13:35:51 -0600 Subject: [PATCH 1546/1797] Address @jsiirola 's comments and apply black --- pyomo/network/foqus_graph.py | 4 ++-- pyomo/solvers/plugins/solvers/GAMS.py | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pyomo/network/foqus_graph.py b/pyomo/network/foqus_graph.py index d904fa54008..7c6c05256d9 100644 --- a/pyomo/network/foqus_graph.py +++ b/pyomo/network/foqus_graph.py @@ -359,8 +359,8 @@ def scc_calculation_order(self, sccNodes, ie, oe): for i in range(len(sccNodes)): for j in range(len(sccNodes)): for in_e in ie[i]: - for oute in oe[j]: - if in_e == oute: + for out_e in oe[j]: + if in_e == out_e: adj[j].append(i) adjR[i].append(j) done = True diff --git a/pyomo/solvers/plugins/solvers/GAMS.py b/pyomo/solvers/plugins/solvers/GAMS.py index 606098e5b7b..c0bab4dc23e 100644 --- a/pyomo/solvers/plugins/solvers/GAMS.py +++ b/pyomo/solvers/plugins/solvers/GAMS.py @@ -349,7 +349,9 @@ def solve(self, *args, **kwds): # Always name working directory or delete files, # regardless of any errors. if keepfiles: - print("\nGAMS WORKING DIRECTORY: %s\n" % workspace.working_directory) + print( + "\nGAMS WORKING DIRECTORY: %s\n" % workspace.working_directory + ) elif tmpdir is not None: # Garbage collect all references to t1.out_db # So that .gdx file can be deleted @@ -398,7 +400,9 @@ def solve(self, *args, **kwds): extract_rc = 'rc' in model_suffixes results = SolverResults() - results.problem.name = os.path.join(workspace.working_directory, t1.name + '.gms') + results.problem.name = os.path.join( + workspace.working_directory, t1.name + '.gms' + ) results.problem.lower_bound = t1.out_db["OBJEST"].find_record().value results.problem.upper_bound = t1.out_db["OBJEST"].find_record().value results.problem.number_of_variables = t1.out_db["NUMVAR"].find_record().value From 6e182bed965e98ead3578c1a2d79244c93760e00 Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Tue, 2 Apr 2024 11:29:38 +0200 Subject: [PATCH 1547/1797] Add MAiNGO to test_perisistent_solvers.py --- .../solvers/tests/test_persistent_solvers.py | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index ae189aca701..c063adc2bfe 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -17,7 +17,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, Cplex, Cbc, Highs, MAiNGO from typing import Type from pyomo.core.expr.numeric_expr import LinearExpression import os @@ -36,11 +36,23 @@ ('cplex', Cplex), ('cbc', Cbc), ('highs', Highs), + ('maingo', MAiNGO), ] -mip_solvers = [('gurobi', Gurobi), ('cplex', Cplex), ('cbc', Cbc), ('highs', Highs)] -nlp_solvers = [('ipopt', Ipopt)] -qcp_solvers = [('gurobi', Gurobi), ('ipopt', Ipopt), ('cplex', Cplex)] -miqcqp_solvers = [('gurobi', Gurobi), ('cplex', Cplex)] +mip_solvers = [ + ('gurobi', Gurobi), + ('cplex', Cplex), + ('cbc', Cbc), + ('highs', Highs), + ('maingo', MAiNGO), +] +nlp_solvers = [('ipopt', Ipopt), ('maingo', MAiNGO)] +qcp_solvers = [ + ('gurobi', Gurobi), + ('ipopt', Ipopt), + ('cplex', Cplex), + ('maingo', MAiNGO), +] +miqcqp_solvers = [('gurobi', Gurobi), ('cplex', Cplex), ('maingo', MAiNGO)] only_child_vars_options = [True, False] From fc4bf437bc6ba5919bebed55ef24f95a77a80aa2 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 2 Apr 2024 10:26:48 -0600 Subject: [PATCH 1548/1797] Skip test under CyIpopt 1.4.0 --- .../contrib/pynumero/examples/tests/test_cyipopt_examples.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py b/pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py index 408a0197382..bcd3b5d8bf5 100644 --- a/pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py +++ b/pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py @@ -266,6 +266,11 @@ def test_cyipopt_functor(self): s = df['ca_bal'] self.assertAlmostEqual(s.iloc[6], 0, places=3) + @unittest.skipIf( + cyipopt_core.__version__ == "1.4.0", + "Terminating Ipopt through a user callback is broken in CyIpopt 1.4.0 " + "(see mechmotum/cyipopt#249", + ) def test_cyipopt_callback_halt(self): ex = import_file( os.path.join(example_dir, 'callback', 'cyipopt_callback_halt.py') From 38d49d43293c45f50ccfbc9fbe7e3d57ed638a03 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 2 Apr 2024 10:27:20 -0600 Subject: [PATCH 1549/1797] Fix bug in retrieving cyipopt version --- .../contrib/pynumero/algorithms/solvers/cyipopt_solver.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/pynumero/algorithms/solvers/cyipopt_solver.py b/pyomo/contrib/pynumero/algorithms/solvers/cyipopt_solver.py index cdea542295b..53616298415 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/cyipopt_solver.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/cyipopt_solver.py @@ -319,7 +319,13 @@ def license_is_valid(self): return True def version(self): - return tuple(int(_) for _ in cyipopt.__version__.split(".")) + def _int(x): + try: + return int(x) + except: + return x + + return tuple(_int(_) for _ in cyipopt_interface.cyipopt.__version__.split(".")) def solve(self, model, **kwds): config = self.config(kwds, preserve_implicit=True) From c74427aeee68050d21c7d80929f9a1d440253c26 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 2 Apr 2024 10:27:43 -0600 Subject: [PATCH 1550/1797] Fix import from a deprecated location --- .../contrib/pynumero/examples/tests/test_cyipopt_examples.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py b/pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py index bcd3b5d8bf5..55dccd6a0ed 100644 --- a/pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py +++ b/pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py @@ -44,11 +44,13 @@ raise unittest.SkipTest("Pynumero needs the ASL extension to run CyIpopt tests") import pyomo.contrib.pynumero.algorithms.solvers.cyipopt_solver as cyipopt_solver +from pyomo.contrib.pynumero.interfaces.cyipopt_interface import cyipopt_available -if not cyipopt_solver.cyipopt_available: +if not cyipopt_available: raise unittest.SkipTest("PyNumero needs CyIpopt installed to run CyIpopt tests") import cyipopt as cyipopt_core + example_dir = os.path.join(this_file_dir(), '..') From 55c78059667d5412df28cab3dd0905e66aa197da Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 2 Apr 2024 10:35:02 -0600 Subject: [PATCH 1551/1797] NFC: fix a message typo --- pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py b/pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py index 55dccd6a0ed..a0e17df918a 100644 --- a/pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py +++ b/pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py @@ -271,7 +271,7 @@ def test_cyipopt_functor(self): @unittest.skipIf( cyipopt_core.__version__ == "1.4.0", "Terminating Ipopt through a user callback is broken in CyIpopt 1.4.0 " - "(see mechmotum/cyipopt#249", + "(see mechmotum/cyipopt#249)", ) def test_cyipopt_callback_halt(self): ex = import_file( From 50e2316be4d928694efb53fb11ea94546ee24e9f Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 2 Apr 2024 10:38:37 -0600 Subject: [PATCH 1552/1797] Use our version() method to test cyipopt version --- pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py b/pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py index a0e17df918a..2df43c1e797 100644 --- a/pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py +++ b/pyomo/contrib/pynumero/examples/tests/test_cyipopt_examples.py @@ -269,7 +269,7 @@ def test_cyipopt_functor(self): self.assertAlmostEqual(s.iloc[6], 0, places=3) @unittest.skipIf( - cyipopt_core.__version__ == "1.4.0", + cyipopt_solver.PyomoCyIpoptSolver().version() == (1, 4, 0), "Terminating Ipopt through a user callback is broken in CyIpopt 1.4.0 " "(see mechmotum/cyipopt#249)", ) From 643865c5f1828c581609f8920d3c0fe8b0c4da41 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 2 Apr 2024 11:27:10 -0600 Subject: [PATCH 1553/1797] Change to NaNs except for objectives --- pyomo/contrib/solver/base.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 9c19fccaa89..6359b49d945 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -465,13 +465,14 @@ def _map_results(self, model, results): ] legacy_soln.status = legacy_solution_status_map[results.solution_status] legacy_results.solver.termination_message = str(results.termination_condition) - legacy_results.problem.number_of_constraints = len( - list(model.component_map(ctype=Constraint)) - ) - legacy_results.problem.number_of_variables = len( - list(model.component_map(ctype=Var)) + legacy_results.problem.number_of_constraints = float('nan') + legacy_results.problem.number_of_variables = float('nan') + number_of_objectives = sum( + 1 + for _ in model.component_data_objects( + Objective, active=True, descend_into=True + ) ) - number_of_objectives = len(list(model.component_map(ctype=Objective))) legacy_results.problem.number_of_objectives = number_of_objectives if number_of_objectives == 1: obj = get_objective(model) From 1456e56857f4b45fbab945034f6916fff416799a Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 2 Apr 2024 11:31:37 -0600 Subject: [PATCH 1554/1797] Remove extra solutions logic, per commit to @andrewlee94's IDAES branch --- pyomo/contrib/solver/base.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 6359b49d945..cec392271f6 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -499,15 +499,7 @@ def _solution_handler( """Method to handle the preferred action for the solution""" symbol_map = SymbolMap() symbol_map.default_labeler = NumericLabeler('x') - try: - model.solutions.add_symbol_map(symbol_map) - except AttributeError: - # Something wacky happens in IDAES due to the usage of ScalarBlock - # instead of PyomoModel. This is an attempt to fix that. - from pyomo.core.base.PyomoModel import ModelSolutions - - setattr(model, 'solutions', ModelSolutions(model)) - model.solutions.add_symbol_map(symbol_map) + model.solutions.add_symbol_map(symbol_map) legacy_results._smap_id = id(symbol_map) delete_legacy_soln = True if load_solutions: From f2fd07c893fe5d13a6926407324fe4594a799f75 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 2 Apr 2024 13:49:44 -0600 Subject: [PATCH 1555/1797] Skip Tests on Draft and WIP Pull Requests --- .github/workflows/test_pr_and_main.yml | 9 ++++++++- doc/OnlineDocs/contribution_guide.rst | 13 ++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 76ec6de951a..72366eb1353 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -7,6 +7,11 @@ on: pull_request: branches: - main + types: + - opened + - reopened + - synchronize + - ready_for_review workflow_dispatch: inputs: git-ref: @@ -34,6 +39,8 @@ jobs: lint: name: lint/style-and-typos runs-on: ubuntu-latest + if: | + contains(github.event.pull_request.title, '[WIP]') != true && !github.event.pull_request.draft steps: - name: Checkout Pyomo source uses: actions/checkout@v4 @@ -733,7 +740,7 @@ jobs: cover: name: process-coverage-${{ matrix.TARGET }} needs: build - if: always() # run even if a build job fails + if: success() || failure() # run even if a build job fails, but not if cancelled runs-on: ${{ matrix.os }} timeout-minutes: 10 strategy: diff --git a/doc/OnlineDocs/contribution_guide.rst b/doc/OnlineDocs/contribution_guide.rst index 10670627546..285bb656406 100644 --- a/doc/OnlineDocs/contribution_guide.rst +++ b/doc/OnlineDocs/contribution_guide.rst @@ -71,6 +71,10 @@ at least 70% coverage of the lines modified in the PR and prefer coverage closer to 90%. We also require that all tests pass before a PR will be merged. +.. note:: + If you are having issues getting tests to pass on your Pull Request, + please tag any of the core developers to ask for help. + The Pyomo main branch provides a Github Actions workflow (configured in the ``.github/`` directory) that will test any changes pushed to a branch with a subset of the complete test harness that includes @@ -82,13 +86,16 @@ This will enable the tests to run automatically with each push to your fork. At any point in the development cycle, a "work in progress" pull request may be opened by including '[WIP]' at the beginning of the PR -title. This allows your code changes to be tested by the full suite of -Pyomo's automatic -testing infrastructure. Any pull requests marked '[WIP]' will not be +title. Any pull requests marked '[WIP]' or draft will not be reviewed or merged by the core development team. However, any '[WIP]' pull request left open for an extended period of time without active development may be marked 'stale' and closed. +.. note:: + Draft and WIP Pull Requests will **NOT** trigger tests. This is an effort to + keep our backlog as available as possible. Please liberally use the provided + branch testing for draft functionality. + Python Version Support ++++++++++++++++++++++ From b082419502a7c024990c28a54d91bb3069bea011 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 2 Apr 2024 13:57:37 -0600 Subject: [PATCH 1556/1797] Change language for branch test request --- doc/OnlineDocs/contribution_guide.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/OnlineDocs/contribution_guide.rst b/doc/OnlineDocs/contribution_guide.rst index 285bb656406..6054a8d2ba9 100644 --- a/doc/OnlineDocs/contribution_guide.rst +++ b/doc/OnlineDocs/contribution_guide.rst @@ -93,8 +93,8 @@ active development may be marked 'stale' and closed. .. note:: Draft and WIP Pull Requests will **NOT** trigger tests. This is an effort to - keep our backlog as available as possible. Please liberally use the provided - branch testing for draft functionality. + keep our backlog as available as possible. Please make use of the provided + branch test suite for evaluating / testing draft functionality. Python Version Support ++++++++++++++++++++++ From a3d6a430f21cd624aebf4559a7d60c8b5df12663 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 2 Apr 2024 15:29:18 -0600 Subject: [PATCH 1557/1797] Removing unused imports --- pyomo/contrib/gdpopt/tests/test_gdpopt.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyomo/contrib/gdpopt/tests/test_gdpopt.py b/pyomo/contrib/gdpopt/tests/test_gdpopt.py index 3ac532116aa..873bafabc76 100644 --- a/pyomo/contrib/gdpopt/tests/test_gdpopt.py +++ b/pyomo/contrib/gdpopt/tests/test_gdpopt.py @@ -22,8 +22,6 @@ from pyomo.common.collections import Bunch from pyomo.common.config import ConfigDict, ConfigValue from pyomo.common.fileutils import import_file, PYOMO_ROOT_DIR -from pyomo.contrib.appsi.base import Solver -from pyomo.contrib.appsi.solvers.gurobi import Gurobi from pyomo.contrib.gdpopt.create_oa_subproblems import ( add_util_block, add_disjunct_list, From 009f0d9af07c726977fa401502bc26fca52249c7 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 2 Apr 2024 15:37:44 -0600 Subject: [PATCH 1558/1797] Replace ProblemSense and the constants in kernel.objective with ObjectiveSense enum --- pyomo/common/enums.py | 38 ++++++++++++++++++++++++++++++++++ pyomo/core/kernel/objective.py | 5 +---- pyomo/opt/results/problem.py | 38 +++++++++++++++++++++++----------- 3 files changed, 65 insertions(+), 16 deletions(-) create mode 100644 pyomo/common/enums.py diff --git a/pyomo/common/enums.py b/pyomo/common/enums.py new file mode 100644 index 00000000000..c685843b41c --- /dev/null +++ b/pyomo/common/enums.py @@ -0,0 +1,38 @@ +# ___________________________________________________________________________ +# +# 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 enum + +from pyomo.common.deprecation import RenamedClass + +class ObjectiveSense(enum.IntEnum): + """Flag indicating if an objective is minimizing (1) or maximizing (-1). + + While the numeric values are arbitrary, there are parts of Pyomo + that rely on this particular choice of value. These values are also + consistent with some solvers (notably Gurobi). + + """ + minimize = 1 + maximize = -1 + + # Overloading __str__ is needed to match the behavior of the old + # pyutilib.enum class (removed June 2020). There are spots in the + # code base that expect the string representation for items in the + # enum to not include the class name. New uses of enum shouldn't + # need to do this. + def __str__(self): + return self.name + +minimize = ObjectiveSense.minimize +maximize = ObjectiveSense.maximize + + diff --git a/pyomo/core/kernel/objective.py b/pyomo/core/kernel/objective.py index 9aa8e3315ef..840c7cfd7e0 100644 --- a/pyomo/core/kernel/objective.py +++ b/pyomo/core/kernel/objective.py @@ -9,15 +9,12 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +from pyomo.common.enums import minimize, maximize from pyomo.core.expr.numvalue import as_numeric from pyomo.core.kernel.base import _abstract_readwrite_property from pyomo.core.kernel.container_utils import define_simple_containers from pyomo.core.kernel.expression import IExpression -# Constants used to define the optimization sense -minimize = 1 -maximize = -1 - class IObjective(IExpression): """ diff --git a/pyomo/opt/results/problem.py b/pyomo/opt/results/problem.py index 98f749f3aeb..e35bb155355 100644 --- a/pyomo/opt/results/problem.py +++ b/pyomo/opt/results/problem.py @@ -12,19 +12,33 @@ import enum from pyomo.opt.results.container import MapContainer +from pyomo.common.deprecation import deprecated, deprecation_warning +from pyomo.common.enums import ObjectiveSense -class ProblemSense(str, enum.Enum): - unknown = 'unknown' - minimize = 'minimize' - maximize = 'maximize' - # Overloading __str__ is needed to match the behavior of the old - # pyutilib.enum class (removed June 2020). There are spots in the - # code base that expect the string representation for items in the - # enum to not include the class name. New uses of enum shouldn't - # need to do this. - def __str__(self): - return self.value +class ProblemSenseType(type): + @deprecated( + "pyomo.opt.results.problem.ProblemSense has been replaced by " + "pyomo.common.enums.ObjectiveSense", + version="6.7.2.dev0", + ) + def __getattr__(cls, attr): + if attr == 'minimize': + return ObjectiveSense.minimize + if attr == 'maximize': + return ObjectiveSense.maximize + if attr == 'unknown': + deprecation_warning( + "ProblemSense.unknown is no longer an allowable option. " + "Mapping 'unknown' to 'minimize'", + version="6.7.2.dev0", + ) + return ObjectiveSense.minimize + raise AttributeError(attr) + + +class ProblemSense(metaclass=ProblemSenseType): + pass class ProblemInformation(MapContainer): @@ -40,4 +54,4 @@ def __init__(self): self.declare('number_of_integer_variables') self.declare('number_of_continuous_variables') self.declare('number_of_nonzeros') - self.declare('sense', value=ProblemSense.unknown, required=True) + self.declare('sense', value=ProblemSense.minimize, required=True) From 75f577fb65f1eb5febb1e18a716ad02816194fc5 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 2 Apr 2024 21:34:21 -0600 Subject: [PATCH 1559/1797] Removing a base class I didn't use --- .../cp/scheduling_expr/sequence_expressions.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/pyomo/contrib/cp/scheduling_expr/sequence_expressions.py b/pyomo/contrib/cp/scheduling_expr/sequence_expressions.py index d88504ac7e4..865a914b847 100644 --- a/pyomo/contrib/cp/scheduling_expr/sequence_expressions.py +++ b/pyomo/contrib/cp/scheduling_expr/sequence_expressions.py @@ -11,15 +11,8 @@ from pyomo.core.expr.logical_expr import BooleanExpression -# ESJ TODO: The naming in this file needs more thought, and it appears I do not -# need the base class. - -class SequenceVarExpression(BooleanExpression): - pass - - -class NoOverlapExpression(SequenceVarExpression): +class NoOverlapExpression(BooleanExpression): """ Expression representing that none of the IntervalVars in a SequenceVar overlap (if they are scheduled) @@ -35,7 +28,7 @@ def _to_string(self, values, verbose, smap): return "no_overlap(%s)" % values[0] -class FirstInSequenceExpression(SequenceVarExpression): +class FirstInSequenceExpression(BooleanExpression): """ Expression representing that the specified IntervalVar is the first in the sequence specified by SequenceVar (if it is scheduled) @@ -52,7 +45,7 @@ def _to_string(self, values, verbose, smap): return "first_in(%s, %s)" % (values[0], values[1]) -class LastInSequenceExpression(SequenceVarExpression): +class LastInSequenceExpression(BooleanExpression): """ Expression representing that the specified IntervalVar is the last in the sequence specified by SequenceVar (if it is scheduled) @@ -69,7 +62,7 @@ def _to_string(self, values, verbose, smap): return "last_in(%s, %s)" % (values[0], values[1]) -class BeforeInSequenceExpression(SequenceVarExpression): +class BeforeInSequenceExpression(BooleanExpression): """ Expression representing that one IntervalVar occurs before another in the sequence specified by the given SequenceVar (if both are scheduled) @@ -86,7 +79,7 @@ def _to_string(self, values, verbose, smap): return "before_in(%s, %s, %s)" % (values[0], values[1], values[2]) -class PredecessorToExpression(SequenceVarExpression): +class PredecessorToExpression(BooleanExpression): """ Expression representing that one IntervalVar is a direct predecessor to another in the sequence specified by the given SequenceVar (if both are scheduled) From 5bf1000b3b3be4e93da98f167d0921e3df9c7ae9 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 2 Apr 2024 21:37:28 -0600 Subject: [PATCH 1560/1797] Correcting copyright year --- pyomo/contrib/cp/debugging.py | 2 +- pyomo/contrib/cp/scheduling_expr/scheduling_logic.py | 2 +- pyomo/contrib/cp/scheduling_expr/sequence_expressions.py | 2 +- pyomo/contrib/cp/sequence_var.py | 2 +- pyomo/contrib/cp/tests/test_sequence_expressions.py | 2 +- pyomo/contrib/cp/tests/test_sequence_var.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyomo/contrib/cp/debugging.py b/pyomo/contrib/cp/debugging.py index 41c4d208de6..34fb105a571 100644 --- a/pyomo/contrib/cp/debugging.py +++ b/pyomo/contrib/cp/debugging.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# Copyright (c) 2008-2024 # National Technology and Engineering Solutions of Sandia, LLC # Under the terms 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/scheduling_logic.py b/pyomo/contrib/cp/scheduling_expr/scheduling_logic.py index fc9cefebf4d..b28d536b594 100644 --- a/pyomo/contrib/cp/scheduling_expr/scheduling_logic.py +++ b/pyomo/contrib/cp/scheduling_expr/scheduling_logic.py @@ -1,7 +1,7 @@ # ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2022 +# Copyright (c) 2008-2024 # National Technology and Engineering Solutions of Sandia, LLC # Under the terms 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/sequence_expressions.py b/pyomo/contrib/cp/scheduling_expr/sequence_expressions.py index 865a914b847..3ba799074de 100644 --- a/pyomo/contrib/cp/scheduling_expr/sequence_expressions.py +++ b/pyomo/contrib/cp/scheduling_expr/sequence_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/sequence_var.py b/pyomo/contrib/cp/sequence_var.py index b242b362f9d..486776f58da 100644 --- a/pyomo/contrib/cp/sequence_var.py +++ b/pyomo/contrib/cp/sequence_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_sequence_expressions.py b/pyomo/contrib/cp/tests/test_sequence_expressions.py index 218a4c0e1a0..62c868abfaf 100644 --- a/pyomo/contrib/cp/tests/test_sequence_expressions.py +++ b/pyomo/contrib/cp/tests/test_sequence_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/test_sequence_var.py b/pyomo/contrib/cp/tests/test_sequence_var.py index 404e21ca39c..ebff465a376 100644 --- a/pyomo/contrib/cp/tests/test_sequence_var.py +++ b/pyomo/contrib/cp/tests/test_sequence_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 From 1090b6447ff831f2b07dac332f5634ec75888cce Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 2 Apr 2024 21:41:18 -0600 Subject: [PATCH 1561/1797] Removing a stupid test --- pyomo/contrib/cp/tests/test_docplex_walker.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/pyomo/contrib/cp/tests/test_docplex_walker.py b/pyomo/contrib/cp/tests/test_docplex_walker.py index 2d08f6881dc..59d4f685c00 100644 --- a/pyomo/contrib/cp/tests/test_docplex_walker.py +++ b/pyomo/contrib/cp/tests/test_docplex_walker.py @@ -473,25 +473,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))) - - self.assertIs(visitor.pyomo_to_docplex[m.a[1]], a0) - self.assertTrue(b.equals(visitor.pyomo_to_docplex[m.b] == 1)) - def test_count_if_expression(self): m = self.get_model() m.a.domain = Integers From 2899c82ee2c43a0407cee8839da1f98341697f77 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 2 Apr 2024 22:14:48 -0600 Subject: [PATCH 1562/1797] Removing handling for IndexedSequenceVars because they can't appear in expressions right now --- pyomo/contrib/cp/repn/docplex_writer.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index 439530eaf04..93b9974434a 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -551,18 +551,6 @@ def _before_sequence_var(visitor, child): return False, (_GENERAL, visitor.var_map[_id]) -def _before_indexed_sequence_var(visitor, child): - # ESJ TODO: I'm not sure we can encounter an indexed sequence var in an - # expression right now? - cpx_vars = {} - for i, v in child.items(): - cpx_sequence_var = _get_docplex_sequence_var(visitor, v) - visitor.var_map[id(v)] = cpx_sequence_var - visitor.pyomo_to_docplex[v] = cpx_sequence_var - cpx_vars[i] = cpx_sequence_var - return False, (_GENERAL, cpx_vars) - - def _before_interval_var(visitor, child): _id = id(child) if _id not in visitor.var_map: @@ -1063,7 +1051,6 @@ class LogicalToDoCplex(StreamBasedExpressionVisitor): IndexedIntervalVar: _before_indexed_interval_var, ScalarSequenceVar: _before_sequence_var, _SequenceVarData: _before_sequence_var, - IndexedSequenceVar: _before_indexed_sequence_var, ScalarVar: _before_var, _GeneralVarData: _before_var, IndexedVar: _before_indexed_var, From f820efd887cf30884f22fd2403e9ef49f0b43b16 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 2 Apr 2024 22:15:02 -0600 Subject: [PATCH 1563/1797] Adding some monomial expression tests --- pyomo/contrib/cp/tests/test_docplex_walker.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/pyomo/contrib/cp/tests/test_docplex_walker.py b/pyomo/contrib/cp/tests/test_docplex_walker.py index 59d4f685c00..4ad07c10ee7 100644 --- a/pyomo/contrib/cp/tests/test_docplex_walker.py +++ b/pyomo/contrib/cp/tests/test_docplex_walker.py @@ -237,6 +237,35 @@ def test_expression_with_mutable_param(self): self.assertTrue(expr[1].equals(4 * x)) + def test_monomial_expressions(self): + m = ConcreteModel() + m.x = Var(domain=Integers, bounds=(1, 4)) + m.p = Param(initialize=4, mutable=True) + + visitor = self.get_visitor() + + const_expr = 3 * m.x + nested_expr = (1 / m.p) * m.x + pow_expr = (m.p ** (0.5)) * m.x + + e = m.x * 4 + expr = visitor.walk_expression((e, e, 0)) + self.assertIn(id(m.x), visitor.var_map) + x = visitor.var_map[id(m.x)] + self.assertTrue(expr[1].equals(4 * x)) + + e = 1.0 * m.x + expr = visitor.walk_expression((e, e, 0)) + self.assertTrue(expr[1].equals(x)) + + e = (1 / m.p) * m.x + expr = visitor.walk_expression((e, e, 0)) + self.assertTrue(expr[1].equals(0.25 * x)) + + e = (m.p ** (0.5)) * m.x + expr = visitor.walk_expression((e, e, 0)) + self.assertTrue(expr[1].equals(2 * x)) + @unittest.skipIf(not docplex_available, "docplex is not available") class TestCPExpressionWalker_LogicalExpressions(CommonTest): From ad69e15e9708279e757bbf53cb4e952886acf6b8 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 2 Apr 2024 22:15:24 -0600 Subject: [PATCH 1564/1797] Adding a test for my debugging utility, but I'm not sure how to not make it dumb yet --- pyomo/contrib/cp/tests/test_debugging.py | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 pyomo/contrib/cp/tests/test_debugging.py diff --git a/pyomo/contrib/cp/tests/test_debugging.py b/pyomo/contrib/cp/tests/test_debugging.py new file mode 100644 index 00000000000..4561b5e9f04 --- /dev/null +++ b/pyomo/contrib/cp/tests/test_debugging.py @@ -0,0 +1,28 @@ +# ___________________________________________________________________________ +# +# 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.unittest as unittest + +from pyomo.environ import ( + ConcreteModel, + Constraint, + Var, +) + +class TestCPDebugging(unittest.TestCase): + def test_debug_infeasibility(self): + m = ConcreteModel() + m.x = Var(domain=Integers, bounds=(2, 5)) + m.y = Var(domain=Integers, bounds=(7, 12)) + m.c = Constraint(expr=m.y <= m.x) + + # ESJ TODO: I don't know how to do this without a baseline, which we + # really don't want... From ea7c23704b7b97e08029e283891e8213f9e771bb Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 3 Apr 2024 11:12:23 -0600 Subject: [PATCH 1565/1797] Make ProblemSense an extension of the ObjectiveSense enum --- pyomo/common/enums.py | 102 +++++++++++++++++++++++++++++++++-- pyomo/opt/results/problem.py | 38 ++++--------- 2 files changed, 110 insertions(+), 30 deletions(-) diff --git a/pyomo/common/enums.py b/pyomo/common/enums.py index c685843b41c..7f00e87a85f 100644 --- a/pyomo/common/enums.py +++ b/pyomo/common/enums.py @@ -10,8 +10,97 @@ # ___________________________________________________________________________ import enum +import itertools + + +class ExtendedEnumType(enum.EnumType): + """Metaclass for creating an :py:class:`Enum` that extends another Enum + + In general, :py:class:`Enum` classes are not extensible: that is, + they are frozen when defined and cannot be the base class of another + Enum. This Metaclass provides a workaround for creating a new Enum + that extends an existing enum. Members in the base Enum are all + present as members on the extended enum. + + Example + ------- + + .. testcode:: + :hide: + + import enum + from pyomo.common.enums import ExtendedEnumType + + .. testcode:: + + class ObjectiveSense(enum.IntEnum): + minimize = 1 + maximize = -1 + + class ProblemSense(enum.IntEnum, metaclass=ExtendedEnumType): + __base_enum__ = ObjectiveSense + + unknown = 0 + + .. doctest:: + + >>> list(ProblemSense) + [, , ] + >>> ProblemSense.unknown + + >>> ProblemSense.maximize + + >>> ProblemSense(0) + + >>> ProblemSense(1) + + >>> ProblemSense('unknown') + + >>> ProblemSense('maximize') + + >>> hasattr(ProblemSense, 'minimize') + True + >>> hasattr(ProblemSense, 'unknown') + True + >>> ProblemSense.minimize is ObjectiveSense.minimize + True + + """ + + def __getattr__(cls, attr): + try: + return getattr(cls.__base_enum__, attr) + except: + return super().__getattr__(attr) + + def __iter__(cls): + # The members of this Enum are the base enum members joined with + # the local members + return itertools.chain(super().__iter__(), cls.__base_enum__.__iter__()) + + def __instancecheck__(cls, instance): + if cls.__subclasscheck__(type(instance)): + return True + # Also pretend that members of the extended enum are subclasses + # of the __base_enum__. This is needed to circumvent error + # checking in enum.__new__ (e.g., for `ProblemSense('minimize')`) + return cls.__base_enum__.__subclasscheck__(type(instance)) + + def _missing_(cls, value): + # Support attribute lookup by value or name + for attr in ('value', 'name'): + for member in cls: + if getattr(member, attr) == value: + return member + return None + + def __new__(metacls, cls, bases, classdict, **kwds): + # Support lookup by name - but only if the new Enum doesn't + # specify it's own implementation of _missing_ + if '_missing_' not in classdict: + classdict['_missing_'] = classmethod(ExtendedEnumType._missing_) + return super().__new__(metacls, cls, bases, classdict, **kwds) -from pyomo.common.deprecation import RenamedClass class ObjectiveSense(enum.IntEnum): """Flag indicating if an objective is minimizing (1) or maximizing (-1). @@ -21,6 +110,7 @@ class ObjectiveSense(enum.IntEnum): consistent with some solvers (notably Gurobi). """ + minimize = 1 maximize = -1 @@ -32,7 +122,13 @@ class ObjectiveSense(enum.IntEnum): def __str__(self): return self.name -minimize = ObjectiveSense.minimize -maximize = ObjectiveSense.maximize + @classmethod + def _missing_(cls, value): + for member in cls: + if member.name == value: + return member + return None +minimize = ObjectiveSense.minimize +maximize = ObjectiveSense.maximize diff --git a/pyomo/opt/results/problem.py b/pyomo/opt/results/problem.py index e35bb155355..34da8f91918 100644 --- a/pyomo/opt/results/problem.py +++ b/pyomo/opt/results/problem.py @@ -13,32 +13,16 @@ from pyomo.opt.results.container import MapContainer from pyomo.common.deprecation import deprecated, deprecation_warning -from pyomo.common.enums import ObjectiveSense - - -class ProblemSenseType(type): - @deprecated( - "pyomo.opt.results.problem.ProblemSense has been replaced by " - "pyomo.common.enums.ObjectiveSense", - version="6.7.2.dev0", - ) - def __getattr__(cls, attr): - if attr == 'minimize': - return ObjectiveSense.minimize - if attr == 'maximize': - return ObjectiveSense.maximize - if attr == 'unknown': - deprecation_warning( - "ProblemSense.unknown is no longer an allowable option. " - "Mapping 'unknown' to 'minimize'", - version="6.7.2.dev0", - ) - return ObjectiveSense.minimize - raise AttributeError(attr) - - -class ProblemSense(metaclass=ProblemSenseType): - pass +from pyomo.common.enums import ExtendedEnumType, ObjectiveSense + + +class ProblemSense(enum.IntEnum, metaclass=ExtendedEnumType): + __base_enum__ = ObjectiveSense + + unknown = 0 + + def __str__(self): + return self.name class ProblemInformation(MapContainer): @@ -54,4 +38,4 @@ def __init__(self): self.declare('number_of_integer_variables') self.declare('number_of_continuous_variables') self.declare('number_of_nonzeros') - self.declare('sense', value=ProblemSense.minimize, required=True) + self.declare('sense', value=ProblemSense.unknown, required=True) From 297ce1d1f5f0a2d64c61a121ae35d63fd3f1509e Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 3 Apr 2024 11:27:57 -0600 Subject: [PATCH 1566/1797] Switch Objective.sense to store ObjectiveSense enum values --- pyomo/core/__init__.py | 2 +- pyomo/core/base/__init__.py | 2 +- pyomo/core/base/objective.py | 26 ++++---------------------- pyomo/core/kernel/objective.py | 11 ++--------- 4 files changed, 8 insertions(+), 33 deletions(-) diff --git a/pyomo/core/__init__.py b/pyomo/core/__init__.py index bce79faacc5..f0d168d98f9 100644 --- a/pyomo/core/__init__.py +++ b/pyomo/core/__init__.py @@ -101,7 +101,7 @@ BooleanValue, native_logical_values, ) -from pyomo.core.kernel.objective import minimize, maximize +from pyomo.core.base import minimize, maximize from pyomo.core.base.config import PyomoOptions from pyomo.core.base.expression import Expression diff --git a/pyomo/core/base/__init__.py b/pyomo/core/base/__init__.py index 4bbd0c9dc44..df5ce743888 100644 --- a/pyomo/core/base/__init__.py +++ b/pyomo/core/base/__init__.py @@ -12,6 +12,7 @@ # TODO: this import is for historical backwards compatibility and should # probably be removed from pyomo.common.collections import ComponentMap +from pyomo.common.enums import minimize, maximize from pyomo.core.expr.symbol_map import SymbolMap from pyomo.core.expr.numvalue import ( @@ -33,7 +34,6 @@ BooleanValue, native_logical_values, ) -from pyomo.core.kernel.objective import minimize, maximize from pyomo.core.base.config import PyomoOptions from pyomo.core.base.expression import Expression, _ExpressionData diff --git a/pyomo/core/base/objective.py b/pyomo/core/base/objective.py index fcc63755f2b..10cc853dafb 100644 --- a/pyomo/core/base/objective.py +++ b/pyomo/core/base/objective.py @@ -15,6 +15,7 @@ from pyomo.common.pyomo_typing import overload from pyomo.common.deprecation import RenamedClass +from pyomo.common.enums import ObjectiveSense, minimize, maximize from pyomo.common.log import is_debug_set from pyomo.common.modeling import NOTSET from pyomo.common.formatting import tabular_writer @@ -35,7 +36,6 @@ IndexedCallInitializer, CountedCallInitializer, ) -from pyomo.core.base import minimize, maximize logger = logging.getLogger('pyomo.core') @@ -152,14 +152,7 @@ def __init__(self, expr=None, sense=minimize, component=None): self._component = weakref_ref(component) if (component is not None) else None self._index = NOTSET self._active = True - self._sense = sense - - if (self._sense != minimize) and (self._sense != maximize): - raise ValueError( - "Objective sense must be set to one of " - "'minimize' (%s) or 'maximize' (%s). Invalid " - "value: %s'" % (minimize, maximize, sense) - ) + self._sense = ObjectiveSense(sense) def set_value(self, expr): if expr is None: @@ -182,14 +175,7 @@ def sense(self, sense): def set_sense(self, sense): """Set the sense (direction) of this objective.""" - if sense in {minimize, maximize}: - self._sense = sense - else: - raise ValueError( - "Objective sense must be set to one of " - "'minimize' (%s) or 'maximize' (%s). Invalid " - "value: %s'" % (minimize, maximize, sense) - ) + self._sense = ObjectiveSense(sense) @ModelComponentFactory.register("Expressions that are minimized or maximized.") @@ -353,11 +339,7 @@ def _pprint(self): ], self._data.items(), ("Active", "Sense", "Expression"), - lambda k, v: [ - v.active, - ("minimize" if (v.sense == minimize) else "maximize"), - v.expr, - ], + lambda k, v: [v.active, v.sense, v.expr], ) def display(self, prefix="", ostream=None): diff --git a/pyomo/core/kernel/objective.py b/pyomo/core/kernel/objective.py index 840c7cfd7e0..ac6f22d07d3 100644 --- a/pyomo/core/kernel/objective.py +++ b/pyomo/core/kernel/objective.py @@ -9,7 +9,7 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from pyomo.common.enums import minimize, maximize +from pyomo.common.enums import ObjectiveSense, minimize, maximize from pyomo.core.expr.numvalue import as_numeric from pyomo.core.kernel.base import _abstract_readwrite_property from pyomo.core.kernel.container_utils import define_simple_containers @@ -81,14 +81,7 @@ def sense(self): @sense.setter def sense(self, sense): """Set the sense (direction) of this objective.""" - if (sense == minimize) or (sense == maximize): - self._sense = sense - else: - raise ValueError( - "Objective sense must be set to one of: " - "[minimize (%s), maximize (%s)]. Invalid " - "value: %s'" % (minimize, maximize, sense) - ) + self._sense = ObjectiveSense(sense) # inserts class definitions for simple _tuple, _list, and From ab59255c9a574d40a63c8bfdd827efe15b8f7639 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 3 Apr 2024 11:42:00 -0600 Subject: [PATCH 1567/1797] Remove reserences to ProblemSense --- pyomo/contrib/mindtpy/algorithm_base_class.py | 12 ++---------- pyomo/contrib/mindtpy/util.py | 1 - .../pynumero/algorithms/solvers/cyipopt_solver.py | 5 ++--- pyomo/solvers/plugins/solvers/CBCplugin.py | 12 ++++++------ pyomo/solvers/plugins/solvers/CPLEX.py | 14 +++++++------- pyomo/solvers/plugins/solvers/GAMS.py | 7 ++----- pyomo/solvers/plugins/solvers/GLPK.py | 8 +++----- pyomo/solvers/plugins/solvers/GUROBI.py | 8 ++++---- pyomo/solvers/plugins/solvers/SCIPAMPL.py | 9 ++------- pyomo/solvers/tests/checks/test_CBCplugin.py | 14 +++++++------- 10 files changed, 35 insertions(+), 55 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 785a89d8982..8c703f8d842 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -27,13 +27,7 @@ from operator import itemgetter from pyomo.common.errors import DeveloperError from pyomo.solvers.plugins.solvers.gurobi_direct import gurobipy -from pyomo.opt import ( - SolverFactory, - SolverResults, - ProblemSense, - SolutionStatus, - SolverStatus, -) +from pyomo.opt import SolverFactory, SolverResults, SolutionStatus, SolverStatus from pyomo.core import ( minimize, maximize, @@ -633,9 +627,7 @@ def process_objective(self, update_var_con_list=True): raise ValueError('Model has multiple active objectives.') else: main_obj = active_objectives[0] - self.results.problem.sense = ( - ProblemSense.minimize if main_obj.sense == 1 else ProblemSense.maximize - ) + self.results.problem.sense = main_obj.sense self.objective_sense = main_obj.sense # Move the objective to the constraints if it is nonlinear or move_objective is True. diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index 1543497838f..7345af8a3e2 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -29,7 +29,6 @@ from pyomo.contrib.mcpp.pyomo_mcpp import mcpp_available, McCormick from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr import pyomo.core.expr as EXPR -from pyomo.opt import ProblemSense from pyomo.contrib.gdpopt.util import get_main_elapsed_time, time_code from pyomo.util.model_size import build_model_size_report from pyomo.common.dependencies import attempt_import diff --git a/pyomo/contrib/pynumero/algorithms/solvers/cyipopt_solver.py b/pyomo/contrib/pynumero/algorithms/solvers/cyipopt_solver.py index 53616298415..0999550711c 100644 --- a/pyomo/contrib/pynumero/algorithms/solvers/cyipopt_solver.py +++ b/pyomo/contrib/pynumero/algorithms/solvers/cyipopt_solver.py @@ -65,7 +65,7 @@ from pyomo.common.config import ConfigBlock, ConfigValue from pyomo.common.timing import TicTocTimer from pyomo.core.base import Block, Objective, minimize -from pyomo.opt import SolverStatus, SolverResults, TerminationCondition, ProblemSense +from pyomo.opt import SolverStatus, SolverResults, TerminationCondition from pyomo.opt.results.solution import Solution logger = logging.getLogger(__name__) @@ -447,11 +447,10 @@ def solve(self, model, **kwds): results.problem.name = model.name obj = next(model.component_data_objects(Objective, active=True)) + results.problem.sense = obj.sense if obj.sense == minimize: - results.problem.sense = ProblemSense.minimize results.problem.upper_bound = info["obj_val"] else: - results.problem.sense = ProblemSense.maximize results.problem.lower_bound = info["obj_val"] results.problem.number_of_objectives = 1 results.problem.number_of_constraints = ng diff --git a/pyomo/solvers/plugins/solvers/CBCplugin.py b/pyomo/solvers/plugins/solvers/CBCplugin.py index eb6c2c2e1bd..f22fb117c8b 100644 --- a/pyomo/solvers/plugins/solvers/CBCplugin.py +++ b/pyomo/solvers/plugins/solvers/CBCplugin.py @@ -16,6 +16,7 @@ import subprocess from pyomo.common import Executable +from pyomo.common.enums import maximize, minimize from pyomo.common.errors import ApplicationError from pyomo.common.collections import Bunch from pyomo.common.tempfiles import TempfileManager @@ -29,7 +30,6 @@ SolverStatus, TerminationCondition, SolutionStatus, - ProblemSense, Solution, ) from pyomo.opt.solver import SystemCallSolver @@ -443,7 +443,7 @@ def process_logfile(self): # # Parse logfile lines # - results.problem.sense = ProblemSense.minimize + results.problem.sense = minimize results.problem.name = None optim_value = float('inf') lower_bound = None @@ -578,7 +578,7 @@ def process_logfile(self): 'CoinLpIO::readLp(): Maximization problem reformulated as minimization' in ' '.join(tokens) ): - results.problem.sense = ProblemSense.maximize + results.problem.sense = maximize # https://projects.coin-or.org/Cbc/browser/trunk/Cbc/src/CbcSolver.cpp?rev=2497#L3047 elif n_tokens > 3 and tokens[:2] == ('Result', '-'): if tokens[2:4] in [('Run', 'abandoned'), ('User', 'ctrl-c')]: @@ -752,9 +752,9 @@ def process_logfile(self): "maxIterations parameter." ) soln.gap = gap - if results.problem.sense == ProblemSense.minimize: + if results.problem.sense == minimize: upper_bound = optim_value - elif results.problem.sense == ProblemSense.maximize: + elif results.problem.sense == maximize: _ver = self.version() if _ver and _ver[:3] < (2, 10, 2): optim_value *= -1 @@ -824,7 +824,7 @@ def process_soln_file(self, results): INPUT = [] _ver = self.version() - invert_objective_sense = results.problem.sense == ProblemSense.maximize and ( + invert_objective_sense = results.problem.sense == maximize and ( _ver and _ver[:3] < (2, 10, 2) ) diff --git a/pyomo/solvers/plugins/solvers/CPLEX.py b/pyomo/solvers/plugins/solvers/CPLEX.py index 9f876b2d0f8..3a08257c87c 100644 --- a/pyomo/solvers/plugins/solvers/CPLEX.py +++ b/pyomo/solvers/plugins/solvers/CPLEX.py @@ -17,6 +17,7 @@ import subprocess from pyomo.common import Executable +from pyomo.common.enums import maximize, minimize from pyomo.common.errors import ApplicationError from pyomo.common.tempfiles import TempfileManager @@ -28,7 +29,6 @@ SolverStatus, TerminationCondition, SolutionStatus, - ProblemSense, Solution, ) from pyomo.opt.solver import ILMLicensedSystemCallSolver @@ -547,9 +547,9 @@ def process_logfile(self): ): # CPLEX 11.2 and subsequent has two Nonzeros sections. results.problem.number_of_nonzeros = int(tokens[2]) elif len(tokens) >= 5 and tokens[4] == "MINIMIZE": - results.problem.sense = ProblemSense.minimize + results.problem.sense = minimize elif len(tokens) >= 5 and tokens[4] == "MAXIMIZE": - results.problem.sense = ProblemSense.maximize + results.problem.sense = maximize elif ( len(tokens) >= 4 and tokens[0] == "Solution" @@ -859,9 +859,9 @@ def process_soln_file(self, results): else: sense = tokens[0].lower() if sense in ['max', 'maximize']: - results.problem.sense = ProblemSense.maximize + results.problem.sense = maximize if sense in ['min', 'minimize']: - results.problem.sense = ProblemSense.minimize + results.problem.sense = minimize break tINPUT.close() @@ -952,7 +952,7 @@ def process_soln_file(self, results): ) if primal_feasible == 1: soln.status = SolutionStatus.feasible - if results.problem.sense == ProblemSense.minimize: + if results.problem.sense == minimize: results.problem.upper_bound = soln.objective[ '__default_objective__' ]['Value'] @@ -964,7 +964,7 @@ def process_soln_file(self, results): soln.status = SolutionStatus.infeasible if self._best_bound is not None: - if results.problem.sense == ProblemSense.minimize: + if results.problem.sense == minimize: results.problem.lower_bound = self._best_bound else: results.problem.upper_bound = self._best_bound diff --git a/pyomo/solvers/plugins/solvers/GAMS.py b/pyomo/solvers/plugins/solvers/GAMS.py index c0bab4dc23e..035bd0b7603 100644 --- a/pyomo/solvers/plugins/solvers/GAMS.py +++ b/pyomo/solvers/plugins/solvers/GAMS.py @@ -36,7 +36,6 @@ Solution, SolutionStatus, TerminationCondition, - ProblemSense, ) from pyomo.common.dependencies import attempt_import @@ -422,11 +421,10 @@ def solve(self, *args, **kwds): assert len(obj) == 1, 'Only one objective is allowed.' obj = obj[0] objctvval = t1.out_db["OBJVAL"].find_record().value + results.problem.sense = obj.sense if obj.is_minimizing(): - results.problem.sense = ProblemSense.minimize results.problem.upper_bound = objctvval else: - results.problem.sense = ProblemSense.maximize results.problem.lower_bound = objctvval results.solver.name = "GAMS " + str(self.version()) @@ -984,11 +982,10 @@ def solve(self, *args, **kwds): assert len(obj) == 1, 'Only one objective is allowed.' obj = obj[0] objctvval = stat_vars["OBJVAL"] + results.problem.sense = obj.sense if obj.is_minimizing(): - results.problem.sense = ProblemSense.minimize results.problem.upper_bound = objctvval else: - results.problem.sense = ProblemSense.maximize results.problem.lower_bound = objctvval results.solver.name = "GAMS " + str(self.version()) diff --git a/pyomo/solvers/plugins/solvers/GLPK.py b/pyomo/solvers/plugins/solvers/GLPK.py index e6d8576489d..c8d5bc14237 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.enums import maximize, minimize from pyomo.common.errors import ApplicationError from pyomo.opt import ( SolverFactory, @@ -28,7 +29,6 @@ SolverResults, TerminationCondition, SolutionStatus, - ProblemSense, ) from pyomo.opt.base.solvers import _extract_version from pyomo.opt.solver import SystemCallSolver @@ -308,10 +308,8 @@ def process_soln_file(self, results): ): raise ValueError - self.is_integer = 'mip' == ptype and True or False - prob.sense = ( - 'min' == psense and ProblemSense.minimize or ProblemSense.maximize - ) + self.is_integer = 'mip' == ptype + prob.sense = minimize if 'min' == psense else maximize prob.number_of_constraints = prows prob.number_of_nonzeros = pnonz prob.number_of_variables = pcols diff --git a/pyomo/solvers/plugins/solvers/GUROBI.py b/pyomo/solvers/plugins/solvers/GUROBI.py index c8b0912970e..3a3a4d52322 100644 --- a/pyomo/solvers/plugins/solvers/GUROBI.py +++ b/pyomo/solvers/plugins/solvers/GUROBI.py @@ -18,6 +18,7 @@ from pyomo.common import Executable from pyomo.common.collections import Bunch +from pyomo.common.enums import maximize, minimize from pyomo.common.fileutils import this_file_dir from pyomo.common.tee import capture_output from pyomo.common.tempfiles import TempfileManager @@ -28,7 +29,6 @@ SolverStatus, TerminationCondition, SolutionStatus, - ProblemSense, Solution, ) from pyomo.opt.solver import ILMLicensedSystemCallSolver @@ -472,7 +472,7 @@ def process_soln_file(self, results): soln.objective['__default_objective__'] = { 'Value': float(tokens[1]) } - if results.problem.sense == ProblemSense.minimize: + if results.problem.sense == minimize: results.problem.upper_bound = float(tokens[1]) else: results.problem.lower_bound = float(tokens[1]) @@ -514,9 +514,9 @@ def process_soln_file(self, results): elif section == 1: if tokens[0] == 'sense': if tokens[1] == 'minimize': - results.problem.sense = ProblemSense.minimize + results.problem.sense = minimize elif tokens[1] == 'maximize': - results.problem.sense = ProblemSense.maximize + results.problem.sense = maximize else: try: val = eval(tokens[1]) diff --git a/pyomo/solvers/plugins/solvers/SCIPAMPL.py b/pyomo/solvers/plugins/solvers/SCIPAMPL.py index fd69954b428..6940ad7b5fe 100644 --- a/pyomo/solvers/plugins/solvers/SCIPAMPL.py +++ b/pyomo/solvers/plugins/solvers/SCIPAMPL.py @@ -20,12 +20,7 @@ from pyomo.opt.base import ProblemFormat, ResultsFormat from pyomo.opt.base.solvers import _extract_version, SolverFactory -from pyomo.opt.results import ( - SolverStatus, - TerminationCondition, - SolutionStatus, - ProblemSense, -) +from pyomo.opt.results import SolverStatus, TerminationCondition, SolutionStatus from pyomo.opt.solver import SystemCallSolver import logging @@ -374,7 +369,7 @@ def _postsolve(self): if len(results.solution) > 0: results.solution(0).status = SolutionStatus.optimal try: - if results.problem.sense == ProblemSense.minimize: + if results.solver.primal_bound < results.solver.dual_bound: results.problem.lower_bound = results.solver.primal_bound else: results.problem.upper_bound = results.solver.primal_bound diff --git a/pyomo/solvers/tests/checks/test_CBCplugin.py b/pyomo/solvers/tests/checks/test_CBCplugin.py index 2ea0e55c5f4..ad8846509ea 100644 --- a/pyomo/solvers/tests/checks/test_CBCplugin.py +++ b/pyomo/solvers/tests/checks/test_CBCplugin.py @@ -29,7 +29,7 @@ maximize, minimize, ) -from pyomo.opt import SolverFactory, ProblemSense, TerminationCondition, SolverStatus +from pyomo.opt import SolverFactory, TerminationCondition, SolverStatus from pyomo.solvers.plugins.solvers.CBCplugin import CBCSHELL cbc_available = SolverFactory('cbc', solver_io='lp').available(exception_flag=False) @@ -62,7 +62,7 @@ def test_infeasible_lp(self): results = self.opt.solve(self.model) - self.assertEqual(ProblemSense.minimize, results.problem.sense) + self.assertEqual(minimize, results.problem.sense) self.assertEqual( TerminationCondition.infeasible, results.solver.termination_condition ) @@ -81,7 +81,7 @@ def test_unbounded_lp(self): results = self.opt.solve(self.model) - self.assertEqual(ProblemSense.maximize, results.problem.sense) + self.assertEqual(maximize, results.problem.sense) self.assertEqual( TerminationCondition.unbounded, results.solver.termination_condition ) @@ -99,7 +99,7 @@ def test_optimal_lp(self): self.assertEqual(0.0, results.problem.lower_bound) self.assertEqual(0.0, results.problem.upper_bound) - self.assertEqual(ProblemSense.minimize, results.problem.sense) + self.assertEqual(minimize, results.problem.sense) self.assertEqual( TerminationCondition.optimal, results.solver.termination_condition ) @@ -118,7 +118,7 @@ def test_infeasible_mip(self): results = self.opt.solve(self.model) - self.assertEqual(ProblemSense.minimize, results.problem.sense) + self.assertEqual(minimize, results.problem.sense) self.assertEqual( TerminationCondition.infeasible, results.solver.termination_condition ) @@ -134,7 +134,7 @@ def test_unbounded_mip(self): results = self.opt.solve(self.model) - self.assertEqual(ProblemSense.minimize, results.problem.sense) + self.assertEqual(minimize, results.problem.sense) self.assertEqual( TerminationCondition.unbounded, results.solver.termination_condition ) @@ -159,7 +159,7 @@ def test_optimal_mip(self): self.assertEqual(1.0, results.problem.upper_bound) self.assertEqual(results.problem.number_of_binary_variables, 2) self.assertEqual(results.problem.number_of_integer_variables, 4) - self.assertEqual(ProblemSense.maximize, results.problem.sense) + self.assertEqual(maximize, results.problem.sense) self.assertEqual( TerminationCondition.optimal, results.solver.termination_condition ) From f80ff256c0da8a65f8be956e011246dc16f15583 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 3 Apr 2024 11:44:22 -0600 Subject: [PATCH 1568/1797] Report both lower and upper bounds from SCIP --- pyomo/solvers/plugins/solvers/SCIPAMPL.py | 2 ++ pyomo/solvers/tests/mip/test_scip_solve_from_instance.baseline | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pyomo/solvers/plugins/solvers/SCIPAMPL.py b/pyomo/solvers/plugins/solvers/SCIPAMPL.py index 6940ad7b5fe..c309ad29d96 100644 --- a/pyomo/solvers/plugins/solvers/SCIPAMPL.py +++ b/pyomo/solvers/plugins/solvers/SCIPAMPL.py @@ -371,7 +371,9 @@ def _postsolve(self): try: if results.solver.primal_bound < results.solver.dual_bound: results.problem.lower_bound = results.solver.primal_bound + results.problem.upper_bound = results.solver.dual_bound else: + results.problem.lower_bound = results.solver.dual_bound results.problem.upper_bound = results.solver.primal_bound except AttributeError: """ diff --git a/pyomo/solvers/tests/mip/test_scip_solve_from_instance.baseline b/pyomo/solvers/tests/mip/test_scip_solve_from_instance.baseline index a3eb9ffacec..976e4a1b82e 100644 --- a/pyomo/solvers/tests/mip/test_scip_solve_from_instance.baseline +++ b/pyomo/solvers/tests/mip/test_scip_solve_from_instance.baseline @@ -1,7 +1,7 @@ { "Problem": [ { - "Lower bound": -Infinity, + "Lower bound": 1.0, "Number of constraints": 0, "Number of objectives": 1, "Number of variables": 1, From 2f59e44f0eb0e1f360ee41fd3daebaba3404f7c3 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 3 Apr 2024 12:26:12 -0600 Subject: [PATCH 1569/1797] Updating baselines; ironically this makes the expected output actually match what is advertised in the Book --- examples/pyomobook/pyomo-components-ch/obj_declaration.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/pyomobook/pyomo-components-ch/obj_declaration.txt b/examples/pyomobook/pyomo-components-ch/obj_declaration.txt index 607586a1fb3..e4d4b02a252 100644 --- a/examples/pyomobook/pyomo-components-ch/obj_declaration.txt +++ b/examples/pyomobook/pyomo-components-ch/obj_declaration.txt @@ -55,7 +55,7 @@ Model unknown None value x[Q] + 2*x[R] -1 +minimize 6.5 Model unknown From e1ad8e55b1b8ce26a4695f275b371f67c18062a8 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 3 Apr 2024 13:04:43 -0600 Subject: [PATCH 1570/1797] Adding portability fixes for Python<3.11 --- pyomo/common/enums.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pyomo/common/enums.py b/pyomo/common/enums.py index 7f00e87a85f..ee934acd35f 100644 --- a/pyomo/common/enums.py +++ b/pyomo/common/enums.py @@ -11,9 +11,14 @@ import enum import itertools +import sys +if sys.version_info[:2] < (3, 11): + _EnumType = enum.EnumMeta +else: + _EnumType = enum.EnumType -class ExtendedEnumType(enum.EnumType): +class ExtendedEnumType(_EnumType): """Metaclass for creating an :py:class:`Enum` that extends another Enum In general, :py:class:`Enum` classes are not extensible: that is, From 4aa861cef0c91a5d335cb83d358a8ecf0074fb3c Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 3 Apr 2024 13:50:58 -0600 Subject: [PATCH 1571/1797] NFC: apply black --- pyomo/common/enums.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/common/enums.py b/pyomo/common/enums.py index ee934acd35f..7de8b13b81f 100644 --- a/pyomo/common/enums.py +++ b/pyomo/common/enums.py @@ -18,6 +18,7 @@ else: _EnumType = enum.EnumType + class ExtendedEnumType(_EnumType): """Metaclass for creating an :py:class:`Enum` that extends another Enum From d095e2409ded5cc027fa50e0b07b5bb17b77c06f Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 3 Apr 2024 15:14:10 -0600 Subject: [PATCH 1572/1797] Integrate common.enums into online docs --- .../library_reference/common/enums.rst | 7 +++++ .../library_reference/common/index.rst | 1 + pyomo/common/enums.py | 26 +++++++++++++++++-- 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 doc/OnlineDocs/library_reference/common/enums.rst diff --git a/doc/OnlineDocs/library_reference/common/enums.rst b/doc/OnlineDocs/library_reference/common/enums.rst new file mode 100644 index 00000000000..5ed2dbb1e80 --- /dev/null +++ b/doc/OnlineDocs/library_reference/common/enums.rst @@ -0,0 +1,7 @@ + +pyomo.common.enums +================== + +.. automodule:: pyomo.common.enums + :members: + :member-order: bysource diff --git a/doc/OnlineDocs/library_reference/common/index.rst b/doc/OnlineDocs/library_reference/common/index.rst index c9c99008250..c03436600f2 100644 --- a/doc/OnlineDocs/library_reference/common/index.rst +++ b/doc/OnlineDocs/library_reference/common/index.rst @@ -11,6 +11,7 @@ or rely on any other parts of Pyomo. config.rst dependencies.rst deprecation.rst + enums.rst errors.rst fileutils.rst formatting.rst diff --git a/pyomo/common/enums.py b/pyomo/common/enums.py index 7de8b13b81f..9988beedbff 100644 --- a/pyomo/common/enums.py +++ b/pyomo/common/enums.py @@ -9,6 +9,23 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +"""This module provides standard :py:class:`enum.Enum` definitions used in +Pyomo, along with additional utilities for working with custom Enums + +Utilities: + +.. autosummary:: + + ExtendedEnumType + +Standard Enums: + +.. autosummary:: + + ObjectiveSense + +""" + import enum import itertools import sys @@ -20,9 +37,9 @@ class ExtendedEnumType(_EnumType): - """Metaclass for creating an :py:class:`Enum` that extends another Enum + """Metaclass for creating an :py:class:`enum.Enum` that extends another Enum - In general, :py:class:`Enum` classes are not extensible: that is, + In general, :py:class:`enum.Enum` classes are not extensible: that is, they are frozen when defined and cannot be the base class of another Enum. This Metaclass provides a workaround for creating a new Enum that extends an existing enum. Members in the base Enum are all @@ -84,6 +101,11 @@ def __iter__(cls): # the local members return itertools.chain(super().__iter__(), cls.__base_enum__.__iter__()) + def __contains__(cls, member): + # This enum "containts" both it's local members and the members + # in the __base_enum__ (necessary for good auto-enum[sphinx] docs) + return super().__contains__(member) or member in cls.__base_enum__ + def __instancecheck__(cls, instance): if cls.__subclasscheck__(type(instance)): return True From 9a9c6843296b30bd45c0641c626736317cc785e2 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Wed, 3 Apr 2024 15:20:35 -0600 Subject: [PATCH 1573/1797] allow variables/constraints to be specified in same list in remove_nodes, but raise deprecation warning --- pyomo/contrib/incidence_analysis/interface.py | 28 +++++++++++++++++-- .../tests/test_interface.py | 18 ++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index 0ed9b34b0f8..f8d7ea855d4 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -28,7 +28,7 @@ scipy as sp, plotly, ) -from pyomo.common.deprecation import deprecated +from pyomo.common.deprecation import deprecated, deprecation_warning 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 @@ -911,7 +911,31 @@ def remove_nodes(self, variables=None, constraints=None): "Attempting to remove variables and constraints from cached " "incidence matrix,\nbut no incidence matrix has been cached." ) - variables, constraints = self._validate_input(variables, constraints) + + vars_to_validate = [] + cons_to_validate = [] + depr_msg = ( + "In IncidenceGraphInterface.remove_nodes, passing variables and" + " constraints in the same list is deprecated. Please separate your" + " variables and constraints and pass them in the order variables," + " constraints." + ) + for var in variables: + if var in self._con_index_map: + deprecation_warning(depr_msg, version="TBD") + cons_to_validate.append(var) + else: + vars_to_validate.append(var) + for con in constraints: + if con in self._var_index_map: + deprecation_warning(depr_msg, version="TBD") + vars_to_validate.append(con) + else: + cons_to_validate.append(con) + + variables, constraints = self._validate_input( + vars_to_validate, cons_to_validate + ) v_exclude = ComponentSet(variables) c_exclude = ComponentSet(constraints) vars_to_include = [v for v in self.variables if v not in v_exclude] diff --git a/pyomo/contrib/incidence_analysis/tests/test_interface.py b/pyomo/contrib/incidence_analysis/tests/test_interface.py index 3b2439ed2af..816c8cbe3d3 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_interface.py +++ b/pyomo/contrib/incidence_analysis/tests/test_interface.py @@ -1471,6 +1471,24 @@ def test_remove_bad_node(self): with self.assertRaisesRegex(KeyError, "does not exist"): igraph.remove_nodes([[m.x[1], m.x[2]], [m.eq[1]]]) + def test_remove_varcon_samelist_deprecated(self): + m = pyo.ConcreteModel() + m.x = pyo.Var([1, 2, 3]) + m.eq = pyo.Constraint(pyo.PositiveIntegers) + m.eq[1] = m.x[1] * m.x[2] == m.x[3] + m.eq[2] = m.x[1] + 2 * m.x[2] == 3 * m.x[3] + + igraph = IncidenceGraphInterface(m) + # This raises a deprecation warning. When the deprecated functionality + # is removed, this will fail, and this test should be updated accordingly. + igraph.remove_nodes([m.eq[1], m.x[1]]) + self.assertEqual(len(igraph.variables), 2) + self.assertEqual(len(igraph.constraints), 1) + + igraph.remove_nodes([], [m.eq[2], m.x[2]]) + self.assertEqual(len(igraph.variables), 1) + self.assertEqual(len(igraph.constraints), 0) + @unittest.skipUnless(networkx_available, "networkx is not available.") @unittest.skipUnless(scipy_available, "scipy is not available.") From 025e429ec66af22c0fa1fbaeae51f8d54a1d4421 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Wed, 3 Apr 2024 15:23:39 -0600 Subject: [PATCH 1574/1797] rephrase "breaking change" as "deprecation" --- 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 f8d7ea855d4..64551788a8b 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -891,7 +891,7 @@ def remove_nodes(self, variables=None, constraints=None): .. note:: - **Breaking change in Pyomo vTBD** + **Deprecation in Pyomo vTBD** The pre-TBD implementation of ``remove_nodes`` allowed variables and constraints to remove to be specified in a single list. This made From f6b4ea93e8c25cd6b535051b0177c4620162a499 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 3 Apr 2024 15:33:20 -0600 Subject: [PATCH 1575/1797] Add unit tests --- pyomo/common/enums.py | 4 +- pyomo/common/tests/test_enums.py | 99 ++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 pyomo/common/tests/test_enums.py diff --git a/pyomo/common/enums.py b/pyomo/common/enums.py index 9988beedbff..0dd65829026 100644 --- a/pyomo/common/enums.py +++ b/pyomo/common/enums.py @@ -83,10 +83,10 @@ class ProblemSense(enum.IntEnum, metaclass=ExtendedEnumType): >>> hasattr(ProblemSense, 'minimize') True - >>> hasattr(ProblemSense, 'unknown') - True >>> ProblemSense.minimize is ObjectiveSense.minimize True + >>> ProblemSense.minimize in ProblemSense + True """ diff --git a/pyomo/common/tests/test_enums.py b/pyomo/common/tests/test_enums.py new file mode 100644 index 00000000000..2d5ab01b6e3 --- /dev/null +++ b/pyomo/common/tests/test_enums.py @@ -0,0 +1,99 @@ +# ___________________________________________________________________________ +# +# 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 enum + +import pyomo.common.unittest as unittest + +from pyomo.common.enums import ExtendedEnumType, ObjectiveSense + + +class ProblemSense(enum.IntEnum, metaclass=ExtendedEnumType): + __base_enum__ = ObjectiveSense + + unknown = 0 + + +class TestExtendedEnumType(unittest.TestCase): + def test_members(self): + self.assertEqual( + list(ProblemSense), + [ProblemSense.unknown, ObjectiveSense.minimize, ObjectiveSense.maximize], + ) + + def test_isinstance(self): + self.assertIsInstance(ProblemSense.unknown, ProblemSense) + self.assertIsInstance(ProblemSense.minimize, ProblemSense) + self.assertIsInstance(ProblemSense.maximize, ProblemSense) + + self.assertTrue(ProblemSense.__instancecheck__(ProblemSense.unknown)) + self.assertTrue(ProblemSense.__instancecheck__(ProblemSense.minimize)) + self.assertTrue(ProblemSense.__instancecheck__(ProblemSense.maximize)) + + def test_getattr(self): + self.assertIs(ProblemSense.unknown, ProblemSense.unknown) + self.assertIs(ProblemSense.minimize, ObjectiveSense.minimize) + self.assertIs(ProblemSense.maximize, ObjectiveSense.maximize) + + def test_hasattr(self): + self.assertTrue(hasattr(ProblemSense, 'unknown')) + self.assertTrue(hasattr(ProblemSense, 'minimize')) + self.assertTrue(hasattr(ProblemSense, 'maximize')) + + def test_call(self): + self.assertIs(ProblemSense(0), ProblemSense.unknown) + self.assertIs(ProblemSense(1), ObjectiveSense.minimize) + self.assertIs(ProblemSense(-1), ObjectiveSense.maximize) + + self.assertIs(ProblemSense('unknown'), ProblemSense.unknown) + self.assertIs(ProblemSense('minimize'), ObjectiveSense.minimize) + self.assertIs(ProblemSense('maximize'), ObjectiveSense.maximize) + + with self.assertRaisesRegex( + ValueError, "'foo' is not a valid ProblemSense" + ): + ProblemSense('foo') + + def test_contains(self): + self.assertIn(ProblemSense.unknown, ProblemSense) + self.assertIn(ProblemSense.minimize, ProblemSense) + self.assertIn(ProblemSense.maximize, ProblemSense) + + self.assertNotIn(ProblemSense.unknown, ObjectiveSense) + self.assertIn(ProblemSense.minimize, ObjectiveSense) + self.assertIn(ProblemSense.maximize, ObjectiveSense) + +class TestObjectiveSense(unittest.TestCase): + def test_members(self): + self.assertEqual( + list(ObjectiveSense), + [ObjectiveSense.minimize, ObjectiveSense.maximize], + ) + + def test_hasattr(self): + self.assertTrue(hasattr(ProblemSense, 'minimize')) + self.assertTrue(hasattr(ProblemSense, 'maximize')) + + def test_call(self): + self.assertIs(ObjectiveSense(1), ObjectiveSense.minimize) + self.assertIs(ObjectiveSense(-1), ObjectiveSense.maximize) + + self.assertIs(ObjectiveSense('minimize'), ObjectiveSense.minimize) + self.assertIs(ObjectiveSense('maximize'), ObjectiveSense.maximize) + + with self.assertRaisesRegex( + ValueError, "'foo' is not a valid ObjectiveSense" + ): + ObjectiveSense('foo') + + def test_str(self): + self.assertEqual(str(ObjectiveSense.minimize), 'minimize') + self.assertEqual(str(ObjectiveSense.maximize), 'maximize') From 98c960e937cdd30b1f7be2d5d00387a90fbe99af Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 3 Apr 2024 15:39:27 -0600 Subject: [PATCH 1576/1797] NFC: fix typos --- pyomo/common/enums.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/common/enums.py b/pyomo/common/enums.py index 0dd65829026..4d969bf7a9e 100644 --- a/pyomo/common/enums.py +++ b/pyomo/common/enums.py @@ -102,8 +102,8 @@ def __iter__(cls): return itertools.chain(super().__iter__(), cls.__base_enum__.__iter__()) def __contains__(cls, member): - # This enum "containts" both it's local members and the members - # in the __base_enum__ (necessary for good auto-enum[sphinx] docs) + # This enum "contains" both its local members and the members in + # the __base_enum__ (necessary for good auto-enum[sphinx] docs) return super().__contains__(member) or member in cls.__base_enum__ def __instancecheck__(cls, instance): @@ -124,7 +124,7 @@ def _missing_(cls, value): def __new__(metacls, cls, bases, classdict, **kwds): # Support lookup by name - but only if the new Enum doesn't - # specify it's own implementation of _missing_ + # specify its own implementation of _missing_ if '_missing_' not in classdict: classdict['_missing_'] = classmethod(ExtendedEnumType._missing_) return super().__new__(metacls, cls, bases, classdict, **kwds) From 6c3245310aadd3211c2c7120d293adfb135517e2 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 3 Apr 2024 15:40:45 -0600 Subject: [PATCH 1577/1797] NFC: apply black --- pyomo/common/tests/test_enums.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/pyomo/common/tests/test_enums.py b/pyomo/common/tests/test_enums.py index 2d5ab01b6e3..52ee1c5abb3 100644 --- a/pyomo/common/tests/test_enums.py +++ b/pyomo/common/tests/test_enums.py @@ -57,9 +57,7 @@ def test_call(self): self.assertIs(ProblemSense('minimize'), ObjectiveSense.minimize) self.assertIs(ProblemSense('maximize'), ObjectiveSense.maximize) - with self.assertRaisesRegex( - ValueError, "'foo' is not a valid ProblemSense" - ): + with self.assertRaisesRegex(ValueError, "'foo' is not a valid ProblemSense"): ProblemSense('foo') def test_contains(self): @@ -71,11 +69,11 @@ def test_contains(self): self.assertIn(ProblemSense.minimize, ObjectiveSense) self.assertIn(ProblemSense.maximize, ObjectiveSense) + class TestObjectiveSense(unittest.TestCase): def test_members(self): self.assertEqual( - list(ObjectiveSense), - [ObjectiveSense.minimize, ObjectiveSense.maximize], + list(ObjectiveSense), [ObjectiveSense.minimize, ObjectiveSense.maximize] ) def test_hasattr(self): @@ -89,9 +87,7 @@ def test_call(self): self.assertIs(ObjectiveSense('minimize'), ObjectiveSense.minimize) self.assertIs(ObjectiveSense('maximize'), ObjectiveSense.maximize) - with self.assertRaisesRegex( - ValueError, "'foo' is not a valid ObjectiveSense" - ): + with self.assertRaisesRegex(ValueError, "'foo' is not a valid ObjectiveSense"): ObjectiveSense('foo') def test_str(self): From c7db40f70332d047fbb9197f8e2a9e694b4b34e6 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Wed, 3 Apr 2024 16:32:41 -0600 Subject: [PATCH 1578/1797] update remove_nodes tests for better coverage --- pyomo/contrib/incidence_analysis/tests/test_interface.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/incidence_analysis/tests/test_interface.py b/pyomo/contrib/incidence_analysis/tests/test_interface.py index 816c8cbe3d3..b0a9661aa54 100644 --- a/pyomo/contrib/incidence_analysis/tests/test_interface.py +++ b/pyomo/contrib/incidence_analysis/tests/test_interface.py @@ -1466,7 +1466,10 @@ def test_remove_bad_node(self): with self.assertRaisesRegex(KeyError, "does not exist"): # Suppose we think something like this should work. We should get # an error, and not silently do nothing. - igraph.remove_nodes([m.x], [m.eq]) + igraph.remove_nodes([m.x], [m.eq[1]]) + + with self.assertRaisesRegex(KeyError, "does not exist"): + igraph.remove_nodes(None, [m.eq]) with self.assertRaisesRegex(KeyError, "does not exist"): igraph.remove_nodes([[m.x[1], m.x[2]], [m.eq[1]]]) From d6af9b3b2d809ed45796fee016c2961acc9ecd89 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Wed, 3 Apr 2024 20:42:18 -0600 Subject: [PATCH 1579/1797] only log deprecation warning once --- pyomo/contrib/incidence_analysis/interface.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index 64551788a8b..ce6c3633e8d 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -920,15 +920,20 @@ def remove_nodes(self, variables=None, constraints=None): " variables and constraints and pass them in the order variables," " constraints." ) + if ( + any(var in self._con_index_map for var in variables) + or any(con in self._var_index_map for con in constraints) + ): + deprecation_warning(depr_msg, version="6.7.2.dev0") + # If we received variables/constraints in the same list, sort them. + # Any unrecognized objects will be caught by _validate_input. for var in variables: if var in self._con_index_map: - deprecation_warning(depr_msg, version="TBD") cons_to_validate.append(var) else: vars_to_validate.append(var) for con in constraints: if con in self._var_index_map: - deprecation_warning(depr_msg, version="TBD") vars_to_validate.append(con) else: cons_to_validate.append(con) From ac75f8380b59b9981e6853a37879d753a8676ef4 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Wed, 3 Apr 2024 20:50:18 -0600 Subject: [PATCH 1580/1797] reformat --- pyomo/contrib/incidence_analysis/interface.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index ce6c3633e8d..8dee0539cb3 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -920,9 +920,8 @@ def remove_nodes(self, variables=None, constraints=None): " variables and constraints and pass them in the order variables," " constraints." ) - if ( - any(var in self._con_index_map for var in variables) - or any(con in self._var_index_map for con in constraints) + if any(var in self._con_index_map for var in variables) or any( + con in self._var_index_map for con in constraints ): deprecation_warning(depr_msg, version="6.7.2.dev0") # If we received variables/constraints in the same list, sort them. From ce34115f48d6610cda97e2438f91aae10886209a Mon Sep 17 00:00:00 2001 From: Eslick Date: Thu, 4 Apr 2024 08:32:43 -0400 Subject: [PATCH 1581/1797] Centralize function to merge AMPLFUNC and PYOMO_AMPLFUNC --- .../contrib/pynumero/interfaces/pyomo_nlp.py | 16 +++++++------- pyomo/solvers/amplfunc_merge.py | 21 +++++++++++++++++++ pyomo/solvers/plugins/solvers/ASL.py | 6 ++---- pyomo/solvers/plugins/solvers/IPOPT.py | 7 +++---- 4 files changed, 35 insertions(+), 15 deletions(-) create mode 100644 pyomo/solvers/amplfunc_merge.py diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py index ce148f50ecf..bfd22ede86b 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py @@ -22,6 +22,7 @@ import pyomo.core.base as pyo from pyomo.common.collections import ComponentMap from pyomo.common.env import CtypesEnviron +from pyomo.solvers.amplfunc_merge import amplfunc_merge from ..sparse.block_matrix import BlockMatrix from pyomo.contrib.pynumero.interfaces.ampl_nlp import AslNLP from pyomo.contrib.pynumero.interfaces.nlp import NLP @@ -92,13 +93,14 @@ def __init__(self, pyomo_model, nl_file_options=None): # The NL writer advertises the external function libraries # through the PYOMO_AMPLFUNC environment variable; merge it # with any preexisting AMPLFUNC definitions - amplfunc_lines = os.environ.get("AMPLFUNC", "").split("\n") - existing = set(amplfunc_lines) - for line in os.environ.get("PYOMO_AMPLFUNC", "").split("\n"): - # Skip (a) empty lines and (b) lines we already have - if line != "" and line not in existing: - amplfunc_lines.append(line) - amplfunc = "\n".join(amplfunc_lines) + if 'PYOMO_AMPLFUNC' in os.environ: + if 'AMPLFUNC' in os.environ: + amplfunc = amplfunc_merge( + os.environ['AMPLFUNC'], os.environ['PYOMO_AMPLFUNC'] + ) + else: + amplfunc = os.environ['PYOMO_AMPLFUNC'] + with CtypesEnviron(AMPLFUNC=amplfunc): super(PyomoNLP, self).__init__(nl_file) diff --git a/pyomo/solvers/amplfunc_merge.py b/pyomo/solvers/amplfunc_merge.py new file mode 100644 index 00000000000..72c6587f552 --- /dev/null +++ b/pyomo/solvers/amplfunc_merge.py @@ -0,0 +1,21 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ + + +def amplfunc_merge(amplfunc, pyomo_amplfunc): + """Merge two AMPLFUNC variable strings eliminating duplicate lines""" + amplfunc_lines = amplfunc.split("\n") + existing = set(amplfunc_lines) + for line in pyomo_amplfunc.split("\n"): + # Skip lines we already have + if line not in existing: + amplfunc_lines.append(line) + return "\n".join(amplfunc_lines) diff --git a/pyomo/solvers/plugins/solvers/ASL.py b/pyomo/solvers/plugins/solvers/ASL.py index 38a9fc1df58..6d3e08af259 100644 --- a/pyomo/solvers/plugins/solvers/ASL.py +++ b/pyomo/solvers/plugins/solvers/ASL.py @@ -23,6 +23,7 @@ from pyomo.opt.solver import SystemCallSolver from pyomo.core.kernel.block import IBlock from pyomo.solvers.mockmip import MockMIP +from pyomo.solvers.amplfunc_merge import amplfunc_merge from pyomo.core import TransformationFactory import logging @@ -160,10 +161,7 @@ def create_command_line(self, executable, problem_files): # if 'PYOMO_AMPLFUNC' in env: if 'AMPLFUNC' in env: - existing = set(env['AMPLFUNC'].split("\n")) - for line in env['PYOMO_AMPLFUNC'].split('\n'): - if line not in existing: - env['AMPLFUNC'] += "\n" + line + env['AMPLFUNC'] = amplfunc_merge(env['AMPLFUNC'], env['PYOMO_AMPLFUNC']) else: env['AMPLFUNC'] = env['PYOMO_AMPLFUNC'] diff --git a/pyomo/solvers/plugins/solvers/IPOPT.py b/pyomo/solvers/plugins/solvers/IPOPT.py index 8f5190a4a07..a3c6b6beb28 100644 --- a/pyomo/solvers/plugins/solvers/IPOPT.py +++ b/pyomo/solvers/plugins/solvers/IPOPT.py @@ -21,6 +21,8 @@ from pyomo.opt.results import SolverStatus, SolverResults, TerminationCondition from pyomo.opt.solver import SystemCallSolver +from pyomo.solvers.amplfunc_merge import amplfunc_merge + import logging logger = logging.getLogger('pyomo.solvers') @@ -121,10 +123,7 @@ def create_command_line(self, executable, problem_files): # if 'PYOMO_AMPLFUNC' in env: if 'AMPLFUNC' in env: - existing = set(env['AMPLFUNC'].split("\n")) - for line in env['PYOMO_AMPLFUNC'].split('\n'): - if line not in existing: - env['AMPLFUNC'] += "\n" + line + env['AMPLFUNC'] = amplfunc_merge(env['AMPLFUNC'], env['PYOMO_AMPLFUNC']) else: env['AMPLFUNC'] = env['PYOMO_AMPLFUNC'] From 11d11e0672ef963631d9c75fb5946dc683e6592c Mon Sep 17 00:00:00 2001 From: Eslick Date: Thu, 4 Apr 2024 09:29:56 -0400 Subject: [PATCH 1582/1797] Add tests --- pyomo/solvers/amplfunc_merge.py | 6 ++ .../tests/checks/test_amplfunc_merge.py | 93 +++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 pyomo/solvers/tests/checks/test_amplfunc_merge.py diff --git a/pyomo/solvers/amplfunc_merge.py b/pyomo/solvers/amplfunc_merge.py index 72c6587f552..88babc2f43c 100644 --- a/pyomo/solvers/amplfunc_merge.py +++ b/pyomo/solvers/amplfunc_merge.py @@ -12,10 +12,16 @@ def amplfunc_merge(amplfunc, pyomo_amplfunc): """Merge two AMPLFUNC variable strings eliminating duplicate lines""" + # Assume that the strings amplfunc and pyomo_amplfunc don't contain duplicates + # Assume that the path separator is correct for the OS so we don't need to + # worry about comparing Unix and Windows paths. amplfunc_lines = amplfunc.split("\n") existing = set(amplfunc_lines) for line in pyomo_amplfunc.split("\n"): # Skip lines we already have if line not in existing: amplfunc_lines.append(line) + # Remove empty lines which could happen if one or both of the strings is + # empty or there are two new lines in a row for whatever reason. + amplfunc_lines = [s for s in amplfunc_lines if s != ""] return "\n".join(amplfunc_lines) diff --git a/pyomo/solvers/tests/checks/test_amplfunc_merge.py b/pyomo/solvers/tests/checks/test_amplfunc_merge.py new file mode 100644 index 00000000000..de31720010c --- /dev/null +++ b/pyomo/solvers/tests/checks/test_amplfunc_merge.py @@ -0,0 +1,93 @@ +# ___________________________________________________________________________ +# +# 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.unittest as unittest +from pyomo.solvers.amplfunc_merge import amplfunc_merge + + +class TestAMPLFUNCMerge(unittest.TestCase): + def test_merge_no_dup(self): + s1 = "my/place/l1.so\nanother/place/l1.so" + s2 = "my/place/l2.so" + sm = amplfunc_merge(s1, s2) + sm_list = sm.split("\n") + self.assertEqual(len(sm_list), 3) + # The order of lines should be maintained with the second string + # following the first + self.assertEqual(sm_list[0], "my/place/l1.so") + self.assertEqual(sm_list[1], "another/place/l1.so") + self.assertEqual(sm_list[2], "my/place/l2.so") + + def test_merge_empty1(self): + s1 = "" + s2 = "my/place/l2.so" + sm = amplfunc_merge(s1, s2) + sm_list = sm.split("\n") + self.assertEqual(len(sm_list), 1) + self.assertEqual(sm_list[0], "my/place/l2.so") + + def test_merge_empty2(self): + s1 = "my/place/l2.so" + s2 = "" + sm = amplfunc_merge(s1, s2) + sm_list = sm.split("\n") + self.assertEqual(len(sm_list), 1) + self.assertEqual(sm_list[0], "my/place/l2.so") + + def test_merge_empty_both(self): + s1 = "" + s2 = "" + sm = amplfunc_merge(s1, s2) + sm_list = sm.split("\n") + self.assertEqual(len(sm_list), 1) + self.assertEqual(sm_list[0], "") + + def test_merge_bad_type(self): + self.assertRaises(AttributeError, amplfunc_merge, "", 3) + self.assertRaises(AttributeError, amplfunc_merge, 3, "") + self.assertRaises(AttributeError, amplfunc_merge, 3, 3) + self.assertRaises(AttributeError, amplfunc_merge, None, "") + self.assertRaises(AttributeError, amplfunc_merge, "", None) + self.assertRaises(AttributeError, amplfunc_merge, 2.3, "") + self.assertRaises(AttributeError, amplfunc_merge, "", 2.3) + + def test_merge_duplicate1(self): + s1 = "my/place/l1.so\nanother/place/l1.so" + s2 = "my/place/l1.so\nanother/place/l1.so" + sm = amplfunc_merge(s1, s2) + sm_list = sm.split("\n") + self.assertEqual(len(sm_list), 2) + # The order of lines should be maintained with the second string + # following the first + self.assertEqual(sm_list[0], "my/place/l1.so") + self.assertEqual(sm_list[1], "another/place/l1.so") + + def test_merge_duplicate2(self): + s1 = "my/place/l1.so\nanother/place/l1.so" + s2 = "my/place/l1.so" + sm = amplfunc_merge(s1, s2) + sm_list = sm.split("\n") + self.assertEqual(len(sm_list), 2) + # The order of lines should be maintained with the second string + # following the first + self.assertEqual(sm_list[0], "my/place/l1.so") + self.assertEqual(sm_list[1], "another/place/l1.so") + + def test_merge_extra_linebreaks(self): + s1 = "\nmy/place/l1.so\nanother/place/l1.so\n" + s2 = "\nmy/place/l1.so\n\n" + sm = amplfunc_merge(s1, s2) + sm_list = sm.split("\n") + self.assertEqual(len(sm_list), 2) + # The order of lines should be maintained with the second string + # following the first + self.assertEqual(sm_list[0], "my/place/l1.so") + self.assertEqual(sm_list[1], "another/place/l1.so") From 30ffed7a4ee79435bd472f7846c0d95f67768712 Mon Sep 17 00:00:00 2001 From: Eslick Date: Thu, 4 Apr 2024 10:13:25 -0400 Subject: [PATCH 1583/1797] Fix error in pyomo_nlp missing undefined amplfunc --- .../contrib/pynumero/interfaces/pyomo_nlp.py | 8 +- pyomo/solvers/amplfunc_merge.py | 6 +- pyomo/solvers/plugins/solvers/ASL.py | 8 +- pyomo/solvers/plugins/solvers/IPOPT.py | 8 +- .../tests/checks/test_amplfunc_merge.py | 114 +++++++++++++++--- 5 files changed, 110 insertions(+), 34 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py index bfd22ede86b..e12d0cf568b 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_nlp.py @@ -93,13 +93,7 @@ def __init__(self, pyomo_model, nl_file_options=None): # The NL writer advertises the external function libraries # through the PYOMO_AMPLFUNC environment variable; merge it # with any preexisting AMPLFUNC definitions - if 'PYOMO_AMPLFUNC' in os.environ: - if 'AMPLFUNC' in os.environ: - amplfunc = amplfunc_merge( - os.environ['AMPLFUNC'], os.environ['PYOMO_AMPLFUNC'] - ) - else: - amplfunc = os.environ['PYOMO_AMPLFUNC'] + amplfunc = amplfunc_merge(os.environ) with CtypesEnviron(AMPLFUNC=amplfunc): super(PyomoNLP, self).__init__(nl_file) diff --git a/pyomo/solvers/amplfunc_merge.py b/pyomo/solvers/amplfunc_merge.py index 88babc2f43c..4c77f080ca1 100644 --- a/pyomo/solvers/amplfunc_merge.py +++ b/pyomo/solvers/amplfunc_merge.py @@ -10,7 +10,7 @@ # ___________________________________________________________________________ -def amplfunc_merge(amplfunc, pyomo_amplfunc): +def amplfunc_string_merge(amplfunc, pyomo_amplfunc): """Merge two AMPLFUNC variable strings eliminating duplicate lines""" # Assume that the strings amplfunc and pyomo_amplfunc don't contain duplicates # Assume that the path separator is correct for the OS so we don't need to @@ -25,3 +25,7 @@ def amplfunc_merge(amplfunc, pyomo_amplfunc): # empty or there are two new lines in a row for whatever reason. amplfunc_lines = [s for s in amplfunc_lines if s != ""] return "\n".join(amplfunc_lines) + +def amplfunc_merge(env): + """Merge AMPLFUNC and PYOMO_AMPLFuNC in an environment var dict""" + return amplfunc_string_merge(env.get("AMPLFUNC", ""), env.get("PYOMO_AMPLFUNC", "")) \ No newline at end of file diff --git a/pyomo/solvers/plugins/solvers/ASL.py b/pyomo/solvers/plugins/solvers/ASL.py index 6d3e08af259..bb8174a013e 100644 --- a/pyomo/solvers/plugins/solvers/ASL.py +++ b/pyomo/solvers/plugins/solvers/ASL.py @@ -159,11 +159,9 @@ def create_command_line(self, executable, problem_files): # Pyomo/Pyomo) with any user-specified external function # libraries # - if 'PYOMO_AMPLFUNC' in env: - if 'AMPLFUNC' in env: - env['AMPLFUNC'] = amplfunc_merge(env['AMPLFUNC'], env['PYOMO_AMPLFUNC']) - else: - env['AMPLFUNC'] = env['PYOMO_AMPLFUNC'] + amplfunc = amplfunc_merge(env) + if amplfunc: + env['AMPLFUNC'] = amplfunc cmd = [executable, problem_files[0], '-AMPL'] if self._timer: diff --git a/pyomo/solvers/plugins/solvers/IPOPT.py b/pyomo/solvers/plugins/solvers/IPOPT.py index a3c6b6beb28..21045cb7b4f 100644 --- a/pyomo/solvers/plugins/solvers/IPOPT.py +++ b/pyomo/solvers/plugins/solvers/IPOPT.py @@ -121,11 +121,9 @@ def create_command_line(self, executable, problem_files): # Pyomo/Pyomo) with any user-specified external function # libraries # - if 'PYOMO_AMPLFUNC' in env: - if 'AMPLFUNC' in env: - env['AMPLFUNC'] = amplfunc_merge(env['AMPLFUNC'], env['PYOMO_AMPLFUNC']) - else: - env['AMPLFUNC'] = env['PYOMO_AMPLFUNC'] + amplfunc = amplfunc_merge(env) + if amplfunc: + env['AMPLFUNC'] = amplfunc cmd = [executable, problem_files[0], '-AMPL'] if self._timer: diff --git a/pyomo/solvers/tests/checks/test_amplfunc_merge.py b/pyomo/solvers/tests/checks/test_amplfunc_merge.py index de31720010c..fb7701e9282 100644 --- a/pyomo/solvers/tests/checks/test_amplfunc_merge.py +++ b/pyomo/solvers/tests/checks/test_amplfunc_merge.py @@ -10,14 +10,14 @@ # ___________________________________________________________________________ import pyomo.common.unittest as unittest -from pyomo.solvers.amplfunc_merge import amplfunc_merge +from pyomo.solvers.amplfunc_merge import amplfunc_string_merge, amplfunc_merge -class TestAMPLFUNCMerge(unittest.TestCase): +class TestAMPLFUNCStringMerge(unittest.TestCase): def test_merge_no_dup(self): s1 = "my/place/l1.so\nanother/place/l1.so" s2 = "my/place/l2.so" - sm = amplfunc_merge(s1, s2) + sm = amplfunc_string_merge(s1, s2) sm_list = sm.split("\n") self.assertEqual(len(sm_list), 3) # The order of lines should be maintained with the second string @@ -29,7 +29,7 @@ def test_merge_no_dup(self): def test_merge_empty1(self): s1 = "" s2 = "my/place/l2.so" - sm = amplfunc_merge(s1, s2) + sm = amplfunc_string_merge(s1, s2) sm_list = sm.split("\n") self.assertEqual(len(sm_list), 1) self.assertEqual(sm_list[0], "my/place/l2.so") @@ -37,7 +37,7 @@ def test_merge_empty1(self): def test_merge_empty2(self): s1 = "my/place/l2.so" s2 = "" - sm = amplfunc_merge(s1, s2) + sm = amplfunc_string_merge(s1, s2) sm_list = sm.split("\n") self.assertEqual(len(sm_list), 1) self.assertEqual(sm_list[0], "my/place/l2.so") @@ -45,24 +45,24 @@ def test_merge_empty2(self): def test_merge_empty_both(self): s1 = "" s2 = "" - sm = amplfunc_merge(s1, s2) + sm = amplfunc_string_merge(s1, s2) sm_list = sm.split("\n") self.assertEqual(len(sm_list), 1) self.assertEqual(sm_list[0], "") def test_merge_bad_type(self): - self.assertRaises(AttributeError, amplfunc_merge, "", 3) - self.assertRaises(AttributeError, amplfunc_merge, 3, "") - self.assertRaises(AttributeError, amplfunc_merge, 3, 3) - self.assertRaises(AttributeError, amplfunc_merge, None, "") - self.assertRaises(AttributeError, amplfunc_merge, "", None) - self.assertRaises(AttributeError, amplfunc_merge, 2.3, "") - self.assertRaises(AttributeError, amplfunc_merge, "", 2.3) + self.assertRaises(AttributeError, amplfunc_string_merge, "", 3) + self.assertRaises(AttributeError, amplfunc_string_merge, 3, "") + self.assertRaises(AttributeError, amplfunc_string_merge, 3, 3) + self.assertRaises(AttributeError, amplfunc_string_merge, None, "") + self.assertRaises(AttributeError, amplfunc_string_merge, "", None) + self.assertRaises(AttributeError, amplfunc_string_merge, 2.3, "") + self.assertRaises(AttributeError, amplfunc_string_merge, "", 2.3) def test_merge_duplicate1(self): s1 = "my/place/l1.so\nanother/place/l1.so" s2 = "my/place/l1.so\nanother/place/l1.so" - sm = amplfunc_merge(s1, s2) + sm = amplfunc_string_merge(s1, s2) sm_list = sm.split("\n") self.assertEqual(len(sm_list), 2) # The order of lines should be maintained with the second string @@ -73,7 +73,7 @@ def test_merge_duplicate1(self): def test_merge_duplicate2(self): s1 = "my/place/l1.so\nanother/place/l1.so" s2 = "my/place/l1.so" - sm = amplfunc_merge(s1, s2) + sm = amplfunc_string_merge(s1, s2) sm_list = sm.split("\n") self.assertEqual(len(sm_list), 2) # The order of lines should be maintained with the second string @@ -84,10 +84,92 @@ def test_merge_duplicate2(self): def test_merge_extra_linebreaks(self): s1 = "\nmy/place/l1.so\nanother/place/l1.so\n" s2 = "\nmy/place/l1.so\n\n" - sm = amplfunc_merge(s1, s2) + sm = amplfunc_string_merge(s1, s2) sm_list = sm.split("\n") self.assertEqual(len(sm_list), 2) # The order of lines should be maintained with the second string # following the first self.assertEqual(sm_list[0], "my/place/l1.so") self.assertEqual(sm_list[1], "another/place/l1.so") + +class TestAMPLFUNCMerge(unittest.TestCase): + def test_merge_no_dup(self): + env = { + "AMPLFUNC": "my/place/l1.so\nanother/place/l1.so", + "PYOMO_AMPLFUNC": "my/place/l2.so", + } + sm = amplfunc_merge(env) + sm_list = sm.split("\n") + self.assertEqual(len(sm_list), 3) + self.assertEqual(sm_list[0], "my/place/l1.so") + self.assertEqual(sm_list[1], "another/place/l1.so") + self.assertEqual(sm_list[2], "my/place/l2.so") + + def test_merge_empty1(self): + env = { + "AMPLFUNC": "", + "PYOMO_AMPLFUNC": "my/place/l2.so", + } + sm = amplfunc_merge(env) + sm_list = sm.split("\n") + self.assertEqual(len(sm_list), 1) + self.assertEqual(sm_list[0], "my/place/l2.so") + + def test_merge_empty2(self): + env = { + "AMPLFUNC": "my/place/l2.so", + "PYOMO_AMPLFUNC": "", + } + sm = amplfunc_merge(env) + sm_list = sm.split("\n") + self.assertEqual(len(sm_list), 1) + self.assertEqual(sm_list[0], "my/place/l2.so") + + def test_merge_empty_both(self): + env = { + "AMPLFUNC": "", + "PYOMO_AMPLFUNC": "", + } + sm = amplfunc_merge(env) + sm_list = sm.split("\n") + self.assertEqual(len(sm_list), 1) + self.assertEqual(sm_list[0], "") + + def test_merge_duplicate1(self): + env = { + "AMPLFUNC": "my/place/l1.so\nanother/place/l1.so", + "PYOMO_AMPLFUNC": "my/place/l1.so\nanother/place/l1.so", + } + sm = amplfunc_merge(env) + sm_list = sm.split("\n") + self.assertEqual(len(sm_list), 2) + self.assertEqual(sm_list[0], "my/place/l1.so") + self.assertEqual(sm_list[1], "another/place/l1.so") + + def test_merge_no_pyomo(self): + env = { + "AMPLFUNC": "my/place/l1.so\nanother/place/l1.so", + } + sm = amplfunc_merge(env) + sm_list = sm.split("\n") + self.assertEqual(len(sm_list), 2) + self.assertEqual(sm_list[0], "my/place/l1.so") + self.assertEqual(sm_list[1], "another/place/l1.so") + + def test_merge_no_user(self): + env = { + "PYOMO_AMPLFUNC": "my/place/l1.so\nanother/place/l1.so", + } + sm = amplfunc_merge(env) + sm_list = sm.split("\n") + self.assertEqual(len(sm_list), 2) + self.assertEqual(sm_list[0], "my/place/l1.so") + self.assertEqual(sm_list[1], "another/place/l1.so") + + def test_merge_nothing(self): + env = {} + sm = amplfunc_merge(env) + sm_list = sm.split("\n") + self.assertEqual(len(sm_list), 1) + self.assertEqual(sm_list[0], "") + From d4c9ddf35c28dbd2ce20f81e8fff485d3fec13b5 Mon Sep 17 00:00:00 2001 From: Eslick Date: Thu, 4 Apr 2024 10:20:44 -0400 Subject: [PATCH 1584/1797] Run black --- pyomo/solvers/amplfunc_merge.py | 3 ++- pyomo/solvers/tests/checks/test_amplfunc_merge.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pyomo/solvers/amplfunc_merge.py b/pyomo/solvers/amplfunc_merge.py index 4c77f080ca1..9d127a94396 100644 --- a/pyomo/solvers/amplfunc_merge.py +++ b/pyomo/solvers/amplfunc_merge.py @@ -26,6 +26,7 @@ def amplfunc_string_merge(amplfunc, pyomo_amplfunc): amplfunc_lines = [s for s in amplfunc_lines if s != ""] return "\n".join(amplfunc_lines) + def amplfunc_merge(env): """Merge AMPLFUNC and PYOMO_AMPLFuNC in an environment var dict""" - return amplfunc_string_merge(env.get("AMPLFUNC", ""), env.get("PYOMO_AMPLFUNC", "")) \ No newline at end of file + return amplfunc_string_merge(env.get("AMPLFUNC", ""), env.get("PYOMO_AMPLFUNC", "")) diff --git a/pyomo/solvers/tests/checks/test_amplfunc_merge.py b/pyomo/solvers/tests/checks/test_amplfunc_merge.py index fb7701e9282..00885feb5a4 100644 --- a/pyomo/solvers/tests/checks/test_amplfunc_merge.py +++ b/pyomo/solvers/tests/checks/test_amplfunc_merge.py @@ -92,6 +92,7 @@ def test_merge_extra_linebreaks(self): self.assertEqual(sm_list[0], "my/place/l1.so") self.assertEqual(sm_list[1], "another/place/l1.so") + class TestAMPLFUNCMerge(unittest.TestCase): def test_merge_no_dup(self): env = { @@ -172,4 +173,3 @@ def test_merge_nothing(self): sm_list = sm.split("\n") self.assertEqual(len(sm_list), 1) self.assertEqual(sm_list[0], "") - From 4849ec515cee9d7ea1fd48747dc5f087a56752cb Mon Sep 17 00:00:00 2001 From: Eslick Date: Thu, 4 Apr 2024 10:30:58 -0400 Subject: [PATCH 1585/1797] Run black again --- .../tests/checks/test_amplfunc_merge.py | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/pyomo/solvers/tests/checks/test_amplfunc_merge.py b/pyomo/solvers/tests/checks/test_amplfunc_merge.py index 00885feb5a4..2c819404d2f 100644 --- a/pyomo/solvers/tests/checks/test_amplfunc_merge.py +++ b/pyomo/solvers/tests/checks/test_amplfunc_merge.py @@ -107,30 +107,21 @@ def test_merge_no_dup(self): self.assertEqual(sm_list[2], "my/place/l2.so") def test_merge_empty1(self): - env = { - "AMPLFUNC": "", - "PYOMO_AMPLFUNC": "my/place/l2.so", - } + env = {"AMPLFUNC": "", "PYOMO_AMPLFUNC": "my/place/l2.so"} sm = amplfunc_merge(env) sm_list = sm.split("\n") self.assertEqual(len(sm_list), 1) self.assertEqual(sm_list[0], "my/place/l2.so") def test_merge_empty2(self): - env = { - "AMPLFUNC": "my/place/l2.so", - "PYOMO_AMPLFUNC": "", - } + env = {"AMPLFUNC": "my/place/l2.so", "PYOMO_AMPLFUNC": ""} sm = amplfunc_merge(env) sm_list = sm.split("\n") self.assertEqual(len(sm_list), 1) self.assertEqual(sm_list[0], "my/place/l2.so") def test_merge_empty_both(self): - env = { - "AMPLFUNC": "", - "PYOMO_AMPLFUNC": "", - } + env = {"AMPLFUNC": "", "PYOMO_AMPLFUNC": ""} sm = amplfunc_merge(env) sm_list = sm.split("\n") self.assertEqual(len(sm_list), 1) @@ -148,9 +139,7 @@ def test_merge_duplicate1(self): self.assertEqual(sm_list[1], "another/place/l1.so") def test_merge_no_pyomo(self): - env = { - "AMPLFUNC": "my/place/l1.so\nanother/place/l1.so", - } + env = {"AMPLFUNC": "my/place/l1.so\nanother/place/l1.so"} sm = amplfunc_merge(env) sm_list = sm.split("\n") self.assertEqual(len(sm_list), 2) @@ -158,9 +147,7 @@ def test_merge_no_pyomo(self): self.assertEqual(sm_list[1], "another/place/l1.so") def test_merge_no_user(self): - env = { - "PYOMO_AMPLFUNC": "my/place/l1.so\nanother/place/l1.so", - } + env = {"PYOMO_AMPLFUNC": "my/place/l1.so\nanother/place/l1.so"} sm = amplfunc_merge(env) sm_list = sm.split("\n") self.assertEqual(len(sm_list), 2) From 24d3baf9b174beab9f34f39e02010fc87194a193 Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Thu, 4 Apr 2024 09:03:56 -0600 Subject: [PATCH 1586/1797] replace TBD with dev version in docstring --- 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 8dee0539cb3..0f47e03d0a2 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -891,9 +891,9 @@ def remove_nodes(self, variables=None, constraints=None): .. note:: - **Deprecation in Pyomo vTBD** + **Deprecation in Pyomo v6.7.2.dev0** - The pre-TBD implementation of ``remove_nodes`` allowed variables and + The pre-6.7.2.dev0 implementation of ``remove_nodes`` allowed variables and constraints to remove to be specified in a single list. This made error checking difficult, and indeed, if invalid components were provided, we carried on silently instead of throwing an error or From b830879053cdbed2a6746bf0bf7b4af2f15ac073 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 4 Apr 2024 10:22:04 -0600 Subject: [PATCH 1587/1797] adding 'synchronize' expression --- pyomo/contrib/cp/__init__.py | 6 +++++- pyomo/contrib/cp/repn/docplex_writer.py | 6 ++++++ .../cp/scheduling_expr/scheduling_logic.py | 15 +++++++++++++ pyomo/contrib/cp/tests/test_docplex_walker.py | 21 +++++++++++++++++++ .../cp/tests/test_sequence_expressions.py | 15 +++++++++++++ 5 files changed, 62 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/cp/__init__.py b/pyomo/contrib/cp/__init__.py index 96ef037853a..d206fe95251 100644 --- a/pyomo/contrib/cp/__init__.py +++ b/pyomo/contrib/cp/__init__.py @@ -25,7 +25,11 @@ before_in_sequence, predecessor_to, ) -from pyomo.contrib.cp.scheduling_expr.scheduling_logic import alternative, spans +from pyomo.contrib.cp.scheduling_expr.scheduling_logic import ( + alternative, + spans, + synchronize, +) from pyomo.contrib.cp.scheduling_expr.step_function_expressions import ( AlwaysIn, Step, diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index 93b9974434a..98d3e07e8ed 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -39,6 +39,7 @@ from pyomo.contrib.cp.scheduling_expr.scheduling_logic import ( AlternativeExpression, SpanExpression, + SynchronizeExpression, ) from pyomo.contrib.cp.scheduling_expr.precedence_expressions import ( BeforeExpression, @@ -992,6 +993,10 @@ def _handle_alternative_expression_node(visitor, node, *args): return _GENERAL, cp.alternative(args[0][1], [arg[1] for arg in args[1:]]) +def _handle_synchronize_expression_node(visitor, node, *args): + return _GENERAL, cp.synchronize(args[0][1], [arg[1] for arg in args[1:]]) + + class LogicalToDoCplex(StreamBasedExpressionVisitor): _operator_handles = { EXPR.GetItemExpression: _handle_getitem, @@ -1040,6 +1045,7 @@ class LogicalToDoCplex(StreamBasedExpressionVisitor): PredecessorToExpression: _handle_predecessor_to_expression_node, SpanExpression: _handle_span_expression_node, AlternativeExpression: _handle_alternative_expression_node, + SynchronizeExpression: _handle_synchronize_expression_node, } _var_handles = { IntervalVarStartTime: _before_interval_var_start_time, diff --git a/pyomo/contrib/cp/scheduling_expr/scheduling_logic.py b/pyomo/contrib/cp/scheduling_expr/scheduling_logic.py index b28d536b594..3556c3083fd 100644 --- a/pyomo/contrib/cp/scheduling_expr/scheduling_logic.py +++ b/pyomo/contrib/cp/scheduling_expr/scheduling_logic.py @@ -36,6 +36,15 @@ def _to_string(self, values, verbose, smap): return "alternative(%s, [%s])" % (values[0], ", ".join(values[1:])) +class SynchronizeExpression(NaryBooleanExpression): + """ + + """ + + def _to_string(self, values, verbose, smap): + return "synchronize(%s, [%s])" % (values[0], ", ".join(values[1:])) + + def spans(*args): """Creates a new SpanExpression""" @@ -46,3 +55,9 @@ def alternative(*args): """Creates a new AlternativeExpression""" return AlternativeExpression(list(_flattened(args))) + + +def synchronize(*args): + """Creates a new SynchronizeExpression""" + + return SynchronizeExpression(list(_flattened(args))) diff --git a/pyomo/contrib/cp/tests/test_docplex_walker.py b/pyomo/contrib/cp/tests/test_docplex_walker.py index 4ad07c10ee7..cce7306d3f9 100644 --- a/pyomo/contrib/cp/tests/test_docplex_walker.py +++ b/pyomo/contrib/cp/tests/test_docplex_walker.py @@ -20,6 +20,7 @@ before_in_sequence, predecessor_to, alternative, + synchronize, ) from pyomo.contrib.cp.scheduling_expr.step_function_expressions import ( AlwaysIn, @@ -1555,6 +1556,26 @@ def test_alternative(self): expr[1].equals(cp.alternative(whole_enchilada, [iv[i] for i in [1, 2, 3]])) ) + def test_synchronize(self): + m = self.get_model() + e = synchronize(m.whole_enchilada, [m.iv[i] for i in [1, 2, 3]]) + + visitor = self.get_visitor() + expr = visitor.walk_expression((e, e, 0)) + + self.assertIn(id(m.whole_enchilada), visitor.var_map) + whole_enchilada = visitor.var_map[id(m.whole_enchilada)] + self.assertIs(visitor.pyomo_to_docplex[m.whole_enchilada], whole_enchilada) + + iv = {} + for i in [1, 2, 3]: + self.assertIn(id(m.iv[i]), visitor.var_map) + iv[i] = visitor.var_map[id(m.iv[i])] + + self.assertTrue( + expr[1].equals(cp.synchronize(whole_enchilada, [iv[i] for i in [1, 2, 3]])) + ) + @unittest.skipIf(not docplex_available, "docplex is not available") class TestCPExpressionWalker_CumulFuncExpressions(CommonTest): diff --git a/pyomo/contrib/cp/tests/test_sequence_expressions.py b/pyomo/contrib/cp/tests/test_sequence_expressions.py index 62c868abfaf..b676881e379 100644 --- a/pyomo/contrib/cp/tests/test_sequence_expressions.py +++ b/pyomo/contrib/cp/tests/test_sequence_expressions.py @@ -15,8 +15,10 @@ from pyomo.contrib.cp.scheduling_expr.scheduling_logic import ( AlternativeExpression, SpanExpression, + SynchronizeExpression, alternative, spans, + synchronize, ) from pyomo.contrib.cp.scheduling_expr.sequence_expressions import ( NoOverlapExpression, @@ -159,3 +161,16 @@ def test_alternative(self): self.assertIs(e.args[i], m.iv[i]) self.assertEqual(str(e), "alternative(whole_enchilada, [iv[1], iv[2], iv[3]])") + + def test_synchronize(self): + m = self.make_model() + e = synchronize(m.whole_enchilada, [m.iv[i] for i in [1, 2, 3]]) + + self.assertIsInstance(e, SynchronizeExpression) + self.assertEqual(e.nargs(), 4) + self.assertEqual(len(e.args), 4) + self.assertIs(e.args[0], m.whole_enchilada) + for i in [1, 2, 3]: + self.assertIs(e.args[i], m.iv[i]) + + self.assertEqual(str(e), "synchronize(whole_enchilada, [iv[1], iv[2], iv[3]])") From 535dda6fbcf876b8ad31f226fd26748e6dc4e81f Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 4 Apr 2024 10:23:11 -0600 Subject: [PATCH 1588/1797] black --- pyomo/contrib/cp/scheduling_expr/scheduling_logic.py | 4 +--- pyomo/contrib/cp/tests/test_debugging.py | 7 ++----- pyomo/contrib/cp/tests/test_docplex_walker.py | 4 ++-- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/pyomo/contrib/cp/scheduling_expr/scheduling_logic.py b/pyomo/contrib/cp/scheduling_expr/scheduling_logic.py index 3556c3083fd..98e0c1ceabd 100644 --- a/pyomo/contrib/cp/scheduling_expr/scheduling_logic.py +++ b/pyomo/contrib/cp/scheduling_expr/scheduling_logic.py @@ -37,9 +37,7 @@ def _to_string(self, values, verbose, smap): class SynchronizeExpression(NaryBooleanExpression): - """ - - """ + """ """ def _to_string(self, values, verbose, smap): return "synchronize(%s, [%s])" % (values[0], ", ".join(values[1:])) diff --git a/pyomo/contrib/cp/tests/test_debugging.py b/pyomo/contrib/cp/tests/test_debugging.py index 4561b5e9f04..8e24e545724 100644 --- a/pyomo/contrib/cp/tests/test_debugging.py +++ b/pyomo/contrib/cp/tests/test_debugging.py @@ -11,11 +11,8 @@ import pyomo.common.unittest as unittest -from pyomo.environ import ( - ConcreteModel, - Constraint, - Var, -) +from pyomo.environ import ConcreteModel, Constraint, Var + class TestCPDebugging(unittest.TestCase): def test_debug_infeasibility(self): diff --git a/pyomo/contrib/cp/tests/test_docplex_walker.py b/pyomo/contrib/cp/tests/test_docplex_walker.py index cce7306d3f9..d14e0bc2d6f 100644 --- a/pyomo/contrib/cp/tests/test_docplex_walker.py +++ b/pyomo/contrib/cp/tests/test_docplex_walker.py @@ -248,7 +248,7 @@ def test_monomial_expressions(self): const_expr = 3 * m.x nested_expr = (1 / m.p) * m.x pow_expr = (m.p ** (0.5)) * m.x - + e = m.x * 4 expr = visitor.walk_expression((e, e, 0)) self.assertIn(id(m.x), visitor.var_map) @@ -1574,7 +1574,7 @@ def test_synchronize(self): self.assertTrue( expr[1].equals(cp.synchronize(whole_enchilada, [iv[i] for i in [1, 2, 3]])) - ) + ) @unittest.skipIf(not docplex_available, "docplex is not available") From 347b5950ba5e8515541783e6480e79c50d05ec88 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 4 Apr 2024 10:24:33 -0600 Subject: [PATCH 1589/1797] standard_form: return objective list, offsets --- pyomo/repn/plugins/standard_form.py | 39 ++++++++++++++++++----------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/pyomo/repn/plugins/standard_form.py b/pyomo/repn/plugins/standard_form.py index ea7b6a6a9e6..d09537e4eee 100644 --- a/pyomo/repn/plugins/standard_form.py +++ b/pyomo/repn/plugins/standard_form.py @@ -61,12 +61,16 @@ class LinearStandardFormInfo(object): Attributes ---------- - c : scipy.sparse.csr_array + c : scipy.sparse.csc_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" + c_offset : numpy.ndarray + + The list of objective constant offsets + A : scipy.sparse.csc_array The constraint coefficients. The constraint bodies may be @@ -89,6 +93,10 @@ class LinearStandardFormInfo(object): The list of Pyomo variable objects corresponding to columns in the `A` and `c` matrices. + objectives : List[_ObjectiveData] + + The list of Pyomo objective objects correcponding to the active objectives + eliminated_vars: List[Tuple[_VarData, NumericExpression]] The list of variables from the original model that do not appear @@ -101,12 +109,14 @@ class LinearStandardFormInfo(object): """ - def __init__(self, c, A, rhs, rows, columns, eliminated_vars): + def __init__(self, c, c_offset, A, rhs, rows, columns, objectives, eliminated_vars): self.c = c + self.c_offset = c_offset self.A = A self.rhs = rhs self.rows = rows self.columns = columns + self.objectives = objectives self.eliminated_vars = eliminated_vars @property @@ -305,21 +315,18 @@ def write(self, model): # # 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 - ) + objectives = [] + for blk in component_map[Objective]: + objectives.extend( + blk.component_data_objects( + Objective, active=True, descend_into=False, sort=sorter ) + ) + obj_offset = [] obj_data = [] obj_index = [] obj_index_ptr = [0] - for i, obj in enumerate(objectives): + for obj in objectives: repn = visitor.walk_expression(obj.expr) if repn.nonlinear is not None: raise ValueError( @@ -328,8 +335,10 @@ def write(self, model): ) N = len(repn.linear) obj_data.append(np.fromiter(repn.linear.values(), float, N)) + obj_offset.append(repn.constant) if obj.sense == maximize: obj_data[-1] *= -1 + obj_offset[-1] *= -1 obj_index.append( np.fromiter(map(var_order.__getitem__, repn.linear), float, N) ) @@ -495,7 +504,9 @@ def write(self, model): else: eliminated_vars = [] - info = LinearStandardFormInfo(c, A, rhs, rows, columns, eliminated_vars) + info = LinearStandardFormInfo( + c, np.array(obj_offset), A, rhs, rows, columns, objectives, eliminated_vars + ) timer.toc("Generated linear standard form representation", delta=False) return info From 89556c3da721c10ad7390cc2be0a0cc07e1124db Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 4 Apr 2024 10:25:19 -0600 Subject: [PATCH 1590/1797] standard_form: allow empty objectives, constraints --- pyomo/repn/plugins/standard_form.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pyomo/repn/plugins/standard_form.py b/pyomo/repn/plugins/standard_form.py index d09537e4eee..0211ba44387 100644 --- a/pyomo/repn/plugins/standard_form.py +++ b/pyomo/repn/plugins/standard_form.py @@ -465,13 +465,17 @@ def write(self, model): # Get the variable list columns = list(var_map.values()) # Convert the compiled data to scipy sparse matrices + if obj_data: + obj_data = np.concatenate(obj_data) + obj_index = np.concatenate(obj_index) c = scipy.sparse.csr_array( - (np.concatenate(obj_data), np.concatenate(obj_index), obj_index_ptr), - [len(obj_index_ptr) - 1, len(columns)], + (obj_data, obj_index, obj_index_ptr), [len(obj_index_ptr) - 1, len(columns)] ).tocsc() + if rows: + con_data = np.concatenate(con_data) + con_index = np.concatenate(con_index) A = scipy.sparse.csr_array( - (np.concatenate(con_data), np.concatenate(con_index), con_index_ptr), - [len(rows), len(columns)], + (con_data, con_index, con_index_ptr), [len(rows), len(columns)] ).tocsc() # Some variables in the var_map may not actually appear in the From e60c0dea7749100c94da6a697395d143e720f3c5 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 4 Apr 2024 10:31:24 -0600 Subject: [PATCH 1591/1797] gurobi_direct: support (partial) loading duals, reduced costs --- pyomo/contrib/solver/gurobi_direct.py | 77 ++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/solver/gurobi_direct.py b/pyomo/contrib/solver/gurobi_direct.py index 1164686f0f1..f10cc8f619f 100644 --- a/pyomo/contrib/solver/gurobi_direct.py +++ b/pyomo/contrib/solver/gurobi_direct.py @@ -15,6 +15,7 @@ import os from pyomo.common.config import ConfigValue +from pyomo.common.collections import ComponentMap, ComponentSet from pyomo.common.dependencies import attempt_import from pyomo.common.shutdown import python_is_shutting_down from pyomo.common.tee import capture_output, TeeStream @@ -59,10 +60,13 @@ def __init__( class GurobiDirectSolutionLoader(SolutionLoaderBase): - def __init__(self, grb_model, grb_vars, pyo_vars): + def __init__(self, grb_model, grb_cons, grb_vars, pyo_cons, pyo_vars, pyo_obj): self._grb_model = grb_model + self._grb_cons = grb_cons self._grb_vars = grb_vars + self._pyo_cons = pyo_cons self._pyo_vars = pyo_vars + self._pyo_obj = pyo_obj GurobiDirect._num_instances += 1 def __del__(self): @@ -72,15 +76,70 @@ def __del__(self): GurobiDirect.release_license() def load_vars(self, vars_to_load=None, solution_number=0): - assert vars_to_load is None assert solution_number == 0 - for p_var, g_var in zip(self._pyo_vars, self._grb_vars.x.tolist()): + if self._grb_model.SolCount == 0: + raise RuntimeError( + 'Solver does not currently have a valid solution. Please ' + 'check the termination condition.' + ) + + iterator = zip(self._pyo_vars, self._grb_vars.x.tolist()) + if vars_to_load: + vars_to_load = ComponentSet(vars_to_load) + iterator = filter(lambda var_val: var_val[0] in vars_to_load, iterator) + for p_var, g_var in iterator: p_var.set_value(g_var, skip_validation=True) + StaleFlagManager.mark_all_as_stale(delayed=True) - def get_primals(self, vars_to_load=None): - assert vars_to_load is None + def get_primals(self, vars_to_load=None, solution_number=0): assert solution_number == 0 - return ComponentMap(zip(self._pyo_vars, self._grb_vars.x.tolist())) + if self._grb_model.SolCount == 0: + raise RuntimeError( + 'Solver does not currently have a valid solution. Please ' + 'check the termination condition.' + ) + + iterator = zip(self._pyo_vars, self._grb_vars.x.tolist()) + if vars_to_load: + vars_to_load = ComponentSet(vars_to_load) + iterator = filter(lambda var_val: var_val[0] in vars_to_load, iterator) + return ComponentMap(iterator) + + def get_duals(self, cons_to_load=None): + if self._grb_model.Status != gurobipy.GRB.OPTIMAL: + raise RuntimeError( + 'Solver does not currently have valid duals. Please ' + 'check the termination condition.' + ) + + def dedup(_iter): + last = None + for con_info_dual in _iter: + if not con_info_dual[1] and con_info_dual[0][0] is last: + continue + last = con_info_dual[0][0] + yield con_info_dual + + iterator = dedup(zip(self._pyo_cons, self._grb_cons.getAttr('Pi').tolist())) + if cons_to_load: + cons_to_load = set(cons_to_load) + iterator = filter( + lambda con_info_dual: con_info_dual[0][0] in cons_to_load, iterator + ) + return {con_info[0]: dual for con_info, dual in iterator} + + def get_reduced_costs(self, vars_to_load=None): + if self._grb_model.Status != gurobipy.GRB.OPTIMAL: + raise RuntimeError( + 'Solver does not currently have valid reduced costs. Please ' + 'check the termination condition.' + ) + + iterator = zip(self._pyo_vars, self._grb_vars.getAttr('Rc').tolist()) + if vars_to_load: + vars_to_load = ComponentSet(vars_to_load) + iterator = filter(lambda var_rc: var_rc[0] in vars_to_load, iterator) + return ComponentMap(iterator) class GurobiDirect(SolverBase): @@ -240,7 +299,11 @@ def solve(self, model, **kwds) -> Results: os.chdir(orig_cwd) res = self._postsolve( - timer, GurobiDirectSolutionLoader(gurobi_model, x, repn.columns) + timer, + config, + GurobiDirectSolutionLoader( + gurobi_model, A, x, repn.rows, repn.columns, repn.objectives + ), ) res.solver_configuration = config res.solver_name = 'Gurobi' From dbefc5cab0a824283b8ac00d92950e569a10760f Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 4 Apr 2024 10:39:44 -0600 Subject: [PATCH 1592/1797] gurobi_direct: do not store ephemeral config on instance; rename gprob --- pyomo/contrib/solver/gurobi_direct.py | 38 +++++++++++++-------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/pyomo/contrib/solver/gurobi_direct.py b/pyomo/contrib/solver/gurobi_direct.py index f10cc8f619f..1d4b1871654 100644 --- a/pyomo/contrib/solver/gurobi_direct.py +++ b/pyomo/contrib/solver/gurobi_direct.py @@ -213,12 +213,13 @@ def version(self): 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() + config = self.config(value=kwds, preserve_implicit=True) if config.timer is None: config.timer = HierarchicalTimer() timer = config.timer + StaleFlagManager.mark_all_as_stale() + timer.start('compile_model') repn = LinearStandardFormCompiler().write(model, mixed_form=True) timer.stop('compile_model') @@ -256,8 +257,8 @@ def solve(self, model, **kwds) -> Results: try: orig_cwd = os.getcwd() - if self._config.working_dir: - os.chdir(self._config.working_dir) + if config.working_dir: + os.chdir(config.working_dir) with TeeStream(*ostreams) as t, capture_output(t.STDOUT, capture_fd=False): gurobi_model = gurobipy.Model() @@ -316,17 +317,15 @@ def solve(self, model, **kwds) -> Results: res.timing_info.timer = timer return res - def _postsolve(self, timer: HierarchicalTimer, loader): - config = self._config - - gprob = loader._grb_model - status = gprob.Status + def _postsolve(self, timer: HierarchicalTimer, config, loader): + grb_model = loader._grb_model + status = grb_model.Status results = Results() results.solution_loader = loader - results.timing_info.gurobi_time = gprob.Runtime + results.timing_info.gurobi_time = grb_model.Runtime - if gprob.SolCount > 0: + if grb_model.SolCount > 0: if status == gurobipy.GRB.OPTIMAL: results.solution_status = SolutionStatus.optimal else: @@ -349,30 +348,31 @@ def _postsolve(self, timer: HierarchicalTimer, loader): 'to bypass this error.' ) - results.incumbent_objective = None - results.objective_bound = None try: - results.incumbent_objective = gprob.ObjVal + if math.isfinite(grb_model.ObjVal): + results.incumbent_objective = grb_model.ObjVal + else: + results.incumbent_objective = None except (gurobipy.GurobiError, AttributeError): results.incumbent_objective = None try: - results.objective_bound = gprob.ObjBound + results.objective_bound = grb_model.ObjBound except (gurobipy.GurobiError, AttributeError): - if self._objective.sense == minimize: + if grb_model.ModelSense == OptimizationSense.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.objective_bound = None - results.iteration_count = gprob.getAttr('IterCount') + results.iteration_count = grb_model.getAttr('IterCount') timer.start('load solution') if config.load_solutions: - if gprob.SolCount > 0: + if grb_model.SolCount > 0: results.solution_loader.load_vars() else: raise RuntimeError( From e11fd66b7c8abeff4aa7c9b728de252ec20741d9 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 4 Apr 2024 10:41:10 -0600 Subject: [PATCH 1593/1797] gurobi_direct: make var processing more efficient --- pyomo/contrib/solver/gurobi_direct.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pyomo/contrib/solver/gurobi_direct.py b/pyomo/contrib/solver/gurobi_direct.py index 1d4b1871654..54ef2c5306e 100644 --- a/pyomo/contrib/solver/gurobi_direct.py +++ b/pyomo/contrib/solver/gurobi_direct.py @@ -237,20 +237,19 @@ def solve(self, model, **kwds) -> Results: _u = inf lb.append(_l) ub.append(_u) + CON = gurobipy.GRB.CONTINUOUS + BIN = gurobipy.GRB.BINARY + INT = gurobipy.GRB.INTEGER vtype = [ ( - gurobipy.GRB.CONTINUOUS + CON if v.is_continuous() - else ( - gurobipy.GRB.BINARY - if v.is_binary() - else gurobipy.GRB.INTEGER if v.is_integer() else '?' - ) + else (BIN if v.is_binary() else INT if v.is_integer() else '?') ) for v in repn.columns ] - sense_type = '>=<' - sense = [sense_type[r[1] + 1] for r in repn.rows] + sense_type = '=<>' # Note: ordering matches 0, 1, -1 + sense = [sense_type[r[1]] for r in repn.rows] timer.stop('prepare_matrices') ostreams = [io.StringIO()] + config.tee From 99a7efc2cbc93456c2a0e69850593444613b4a24 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 4 Apr 2024 10:41:57 -0600 Subject: [PATCH 1594/1797] gurobi_direct: support models with no objectives --- pyomo/contrib/solver/gurobi_direct.py | 36 ++++++++++++++------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/pyomo/contrib/solver/gurobi_direct.py b/pyomo/contrib/solver/gurobi_direct.py index 54ef2c5306e..82491e88a42 100644 --- a/pyomo/contrib/solver/gurobi_direct.py +++ b/pyomo/contrib/solver/gurobi_direct.py @@ -266,10 +266,13 @@ def solve(self, model, **kwds) -> Results: len(repn.columns), lb=lb, ub=ub, - obj=repn.c.todense()[0], + obj=repn.c.todense()[0] if repn.c.shape[0] else 0, vtype=vtype, ) A = gurobi_model.addMConstr(repn.A, x, sense, repn.rhs) + if repn.c.shape[0]: + gurobi_model.setAttr('ObjCon', repn.c_offset[0]) + gurobi_model.setAttr('ModelSense', int(repn.objectives[0].sense)) # gurobi_model.update() timer.stop('transfer_model') @@ -347,23 +350,22 @@ def _postsolve(self, timer: HierarchicalTimer, config, loader): 'to bypass this error.' ) - try: - if math.isfinite(grb_model.ObjVal): - results.incumbent_objective = grb_model.ObjVal - else: + if loader._pyo_obj: + try: + if math.isfinite(grb_model.ObjVal): + results.incumbent_objective = grb_model.ObjVal + else: + results.incumbent_objective = None + except (gurobipy.GurobiError, AttributeError): results.incumbent_objective = None - except (gurobipy.GurobiError, AttributeError): - results.incumbent_objective = None - try: - results.objective_bound = grb_model.ObjBound - except (gurobipy.GurobiError, AttributeError): - if grb_model.ModelSense == OptimizationSense.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 - ): + try: + results.objective_bound = grb_model.ObjBound + except (gurobipy.GurobiError, AttributeError): + if grb_model.ModelSense == OptimizationSense.minimize: + results.objective_bound = -math.inf + else: + results.objective_bound = math.inf + else: results.incumbent_objective = None results.objective_bound = None From 7e4938f8e4fe32eef50c1d4816c378d0e28b6413 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 4 Apr 2024 10:42:19 -0600 Subject: [PATCH 1595/1797] NFC: wrap long line --- pyomo/contrib/solver/gurobi_direct.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/solver/gurobi_direct.py b/pyomo/contrib/solver/gurobi_direct.py index 82491e88a42..ab5a07bc5f5 100644 --- a/pyomo/contrib/solver/gurobi_direct.py +++ b/pyomo/contrib/solver/gurobi_direct.py @@ -54,7 +54,8 @@ def __init__( ConfigValue( default=False, domain=bool, - description="If True, the values of the integer variables will be passed to Gurobi.", + description="If True, the current values of the integer variables " + "will be passed to Gurobi.", ), ) From 93590c6eabbb1c2f0b06592f6687d2feefb0243e Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 4 Apr 2024 10:42:45 -0600 Subject: [PATCH 1596/1797] gurobi_direct: add error checking for MO problems --- pyomo/contrib/solver/gurobi_direct.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyomo/contrib/solver/gurobi_direct.py b/pyomo/contrib/solver/gurobi_direct.py index ab5a07bc5f5..e0e0d7d32b0 100644 --- a/pyomo/contrib/solver/gurobi_direct.py +++ b/pyomo/contrib/solver/gurobi_direct.py @@ -225,6 +225,12 @@ def solve(self, model, **kwds) -> Results: repn = LinearStandardFormCompiler().write(model, mixed_form=True) timer.stop('compile_model') + if len(repn.objectives) > 1: + raise ValueError( + f"The {self.__class__.__name__} solver only supports models " + f"with zero or one objectives (received {len(repn.objectives)})." + ) + timer.start('prepare_matrices') inf = float('inf') ninf = -inf From 4ce69351e41e3dac0fa4a9f63f03c9e51805996a Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 4 Apr 2024 10:43:03 -0600 Subject: [PATCH 1597/1797] NFC: adding docstrings --- .../cp/scheduling_expr/scheduling_logic.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/cp/scheduling_expr/scheduling_logic.py b/pyomo/contrib/cp/scheduling_expr/scheduling_logic.py index 98e0c1ceabd..e5695b57c5c 100644 --- a/pyomo/contrib/cp/scheduling_expr/scheduling_logic.py +++ b/pyomo/contrib/cp/scheduling_expr/scheduling_logic.py @@ -29,15 +29,26 @@ def _to_string(self, values, verbose, smap): class AlternativeExpression(NaryBooleanExpression): """ - TODO/ + Expression over IntervalVars representing that if the first arg is present, + then exactly one of the following args must be present. The first arg is + absent if and only if all the others are absent. """ + # [ESJ 4/4/24]: docplex takes an optional 'cardinality' argument with this + # too--it generalized to "exactly n" of the intervals have to exist, + # basically. It would be nice to include this eventually, but this is + # probably fine for now. + def _to_string(self, values, verbose, smap): return "alternative(%s, [%s])" % (values[0], ", ".join(values[1:])) class SynchronizeExpression(NaryBooleanExpression): - """ """ + """ + Expression over IntervalVars synchronizing the first argument with all of the + following arguments. That is, if the first argument is present, the remaining + arguments start and end at the same time as it. + """ def _to_string(self, values, verbose, smap): return "synchronize(%s, [%s])" % (values[0], ", ".join(values[1:])) From 6fa6196a3e66c42b91a19e69ef9275c6289c493c Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 4 Apr 2024 10:48:34 -0600 Subject: [PATCH 1598/1797] standard_form: add option to control the final optimization sense --- pyomo/contrib/solver/gurobi_direct.py | 5 ++++- pyomo/repn/plugins/standard_form.py | 12 +++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/solver/gurobi_direct.py b/pyomo/contrib/solver/gurobi_direct.py index e0e0d7d32b0..36a783cba02 100644 --- a/pyomo/contrib/solver/gurobi_direct.py +++ b/pyomo/contrib/solver/gurobi_direct.py @@ -17,6 +17,7 @@ from pyomo.common.config import ConfigValue from pyomo.common.collections import ComponentMap, ComponentSet from pyomo.common.dependencies import attempt_import +from pyomo.common.enums import OptimizationSense from pyomo.common.shutdown import python_is_shutting_down from pyomo.common.tee import capture_output, TeeStream from pyomo.common.timing import HierarchicalTimer @@ -222,7 +223,9 @@ def solve(self, model, **kwds) -> Results: StaleFlagManager.mark_all_as_stale() timer.start('compile_model') - repn = LinearStandardFormCompiler().write(model, mixed_form=True) + repn = LinearStandardFormCompiler().write( + model, mixed_form=True, set_sense=None + ) timer.stop('compile_model') if len(repn.objectives) > 1: diff --git a/pyomo/repn/plugins/standard_form.py b/pyomo/repn/plugins/standard_form.py index 0211ba44387..566d0d8d932 100644 --- a/pyomo/repn/plugins/standard_form.py +++ b/pyomo/repn/plugins/standard_form.py @@ -20,6 +20,7 @@ document_kwargs_from_configdict, ) from pyomo.common.dependencies import scipy, numpy as np +from pyomo.common.enums import OptimizationSense from pyomo.common.gc_manager import PauseGC from pyomo.common.timing import TicTocTimer @@ -158,6 +159,14 @@ class LinearStandardFormCompiler(object): 'mix of <=, ==, and >=)', ), ) + CONFIG.declare( + 'set_sense', + ConfigValue( + default=OptimizationSense.minimize, + domain=InEnum(OptimizationSense), + description='If not None, map all objectives to the specified sense.', + ), + ) CONFIG.declare( 'show_section_timing', ConfigValue( @@ -315,6 +324,7 @@ def write(self, model): # # Process objective # + set_sense = self.config.set_sense objectives = [] for blk in component_map[Objective]: objectives.extend( @@ -336,7 +346,7 @@ def write(self, model): N = len(repn.linear) obj_data.append(np.fromiter(repn.linear.values(), float, N)) obj_offset.append(repn.constant) - if obj.sense == maximize: + if set_sense is not None and set_sense != obj.sense: obj_data[-1] *= -1 obj_offset[-1] *= -1 obj_index.append( From d546a248493a34623e61e46f002f3d85b8ef2b66 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 4 Apr 2024 10:49:00 -0600 Subject: [PATCH 1599/1797] Add gurobi_direcct to the solver test suite --- pyomo/contrib/solver/tests/solvers/test_solvers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/solver/tests/solvers/test_solvers.py b/pyomo/contrib/solver/tests/solvers/test_solvers.py index a4f4a3bc389..f91de2287b7 100644 --- a/pyomo/contrib/solver/tests/solvers/test_solvers.py +++ b/pyomo/contrib/solver/tests/solvers/test_solvers.py @@ -21,6 +21,7 @@ from pyomo.contrib.solver.base import SolverBase from pyomo.contrib.solver.ipopt import Ipopt from pyomo.contrib.solver.gurobi import Gurobi +from pyomo.contrib.solver.gurobi_direct import GurobiDirect from pyomo.core.expr.numeric_expr import LinearExpression @@ -32,8 +33,8 @@ if not param_available: raise unittest.SkipTest('Parameterized is not available.') -all_solvers = [('gurobi', Gurobi), ('ipopt', Ipopt)] -mip_solvers = [('gurobi', Gurobi)] +all_solvers = [('gurobi', Gurobi), ('gurobi_direct', GurobiDirect), ('ipopt', Ipopt)] +mip_solvers = [('gurobi', Gurobi), ('gurobi_direct', GurobiDirect)] nlp_solvers = [('ipopt', Ipopt)] qcp_solvers = [('gurobi', Gurobi), ('ipopt', Ipopt)] miqcqp_solvers = [('gurobi', Gurobi)] From a234fd42f44ef0acd4d789acfae05cbff8dcec70 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 4 Apr 2024 10:49:59 -0600 Subject: [PATCH 1600/1797] Taking the debugging util stuff out of this PR--I'll think about how to do it better later --- pyomo/contrib/cp/debugging.py | 29 ------------------------ pyomo/contrib/cp/tests/test_debugging.py | 25 -------------------- 2 files changed, 54 deletions(-) delete mode 100644 pyomo/contrib/cp/debugging.py delete mode 100644 pyomo/contrib/cp/tests/test_debugging.py diff --git a/pyomo/contrib/cp/debugging.py b/pyomo/contrib/cp/debugging.py deleted file mode 100644 index 34fb105a571..00000000000 --- a/pyomo/contrib/cp/debugging.py +++ /dev/null @@ -1,29 +0,0 @@ -# ___________________________________________________________________________ -# -# 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.opt import WriterFactory - - -def write_conflict_set(m, filename): - """ - For debugging infeasible CPs: writes the conflict set found by CP optimizer - to a file with the specified filename. - - Args: - m: Pyomo CP model - filename: string filename - """ - - cpx_mod, var_map = WriterFactory('docplex_model').write( - m, symbolic_solver_labels=True - ) - conflict = cpx_mod.refine_conflict() - conflict.write(filename) diff --git a/pyomo/contrib/cp/tests/test_debugging.py b/pyomo/contrib/cp/tests/test_debugging.py deleted file mode 100644 index 8e24e545724..00000000000 --- a/pyomo/contrib/cp/tests/test_debugging.py +++ /dev/null @@ -1,25 +0,0 @@ -# ___________________________________________________________________________ -# -# 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.unittest as unittest - -from pyomo.environ import ConcreteModel, Constraint, Var - - -class TestCPDebugging(unittest.TestCase): - def test_debug_infeasibility(self): - m = ConcreteModel() - m.x = Var(domain=Integers, bounds=(2, 5)) - m.y = Var(domain=Integers, bounds=(7, 12)) - m.c = Constraint(expr=m.y <= m.x) - - # ESJ TODO: I don't know how to do this without a baseline, which we - # really don't want... From 30490d24bcdf58763330f3216cbc30c93e559089 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 4 Apr 2024 10:58:14 -0600 Subject: [PATCH 1601/1797] Correct import of ObjectiveSense enum --- pyomo/contrib/solver/gurobi_direct.py | 4 ++-- pyomo/repn/plugins/standard_form.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/solver/gurobi_direct.py b/pyomo/contrib/solver/gurobi_direct.py index 36a783cba02..7b80651ccae 100644 --- a/pyomo/contrib/solver/gurobi_direct.py +++ b/pyomo/contrib/solver/gurobi_direct.py @@ -17,7 +17,7 @@ from pyomo.common.config import ConfigValue from pyomo.common.collections import ComponentMap, ComponentSet from pyomo.common.dependencies import attempt_import -from pyomo.common.enums import OptimizationSense +from pyomo.common.enums import ObjectiveSense from pyomo.common.shutdown import python_is_shutting_down from pyomo.common.tee import capture_output, TeeStream from pyomo.common.timing import HierarchicalTimer @@ -371,7 +371,7 @@ def _postsolve(self, timer: HierarchicalTimer, config, loader): try: results.objective_bound = grb_model.ObjBound except (gurobipy.GurobiError, AttributeError): - if grb_model.ModelSense == OptimizationSense.minimize: + if grb_model.ModelSense == ObjectiveSense.minimize: results.objective_bound = -math.inf else: results.objective_bound = math.inf diff --git a/pyomo/repn/plugins/standard_form.py b/pyomo/repn/plugins/standard_form.py index 566d0d8d932..a5aaece8531 100644 --- a/pyomo/repn/plugins/standard_form.py +++ b/pyomo/repn/plugins/standard_form.py @@ -20,7 +20,7 @@ document_kwargs_from_configdict, ) from pyomo.common.dependencies import scipy, numpy as np -from pyomo.common.enums import OptimizationSense +from pyomo.common.enums import ObjectiveSense from pyomo.common.gc_manager import PauseGC from pyomo.common.timing import TicTocTimer @@ -162,8 +162,8 @@ class LinearStandardFormCompiler(object): CONFIG.declare( 'set_sense', ConfigValue( - default=OptimizationSense.minimize, - domain=InEnum(OptimizationSense), + default=ObjectiveSense.minimize, + domain=InEnum(ObjectiveSense), description='If not None, map all objectives to the specified sense.', ), ) From b57adf1bc3aaa5c9c93e1988e31da94eb6ad81d7 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 4 Apr 2024 13:17:29 -0600 Subject: [PATCH 1602/1797] Add gurobi_direct to the docs --- doc/OnlineDocs/developer_reference/solvers.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/OnlineDocs/developer_reference/solvers.rst b/doc/OnlineDocs/developer_reference/solvers.rst index 94fb684236f..9e3281246f4 100644 --- a/doc/OnlineDocs/developer_reference/solvers.rst +++ b/doc/OnlineDocs/developer_reference/solvers.rst @@ -45,9 +45,12 @@ with existing interfaces). * - Ipopt - ``ipopt`` - ``ipopt_v2`` - * - Gurobi + * - Gurobi (persistent) - ``gurobi`` - ``gurobi_v2`` + * - Gurobi (direct) + - ``gurobi_direct`` + - ``gurobi_direct_v2`` Using the new interfaces through the legacy interface ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 942df952e5c79e9ddffe53a2fe98c36fce96132b Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Thu, 4 Apr 2024 13:40:45 -0600 Subject: [PATCH 1603/1797] NFC: Typo fix in amplfunc_merge.py --- pyomo/solvers/amplfunc_merge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/solvers/amplfunc_merge.py b/pyomo/solvers/amplfunc_merge.py index 9d127a94396..e49fd20e20f 100644 --- a/pyomo/solvers/amplfunc_merge.py +++ b/pyomo/solvers/amplfunc_merge.py @@ -28,5 +28,5 @@ def amplfunc_string_merge(amplfunc, pyomo_amplfunc): def amplfunc_merge(env): - """Merge AMPLFUNC and PYOMO_AMPLFuNC in an environment var dict""" + """Merge AMPLFUNC and PYOMO_AMPLFUNC in an environment var dict""" return amplfunc_string_merge(env.get("AMPLFUNC", ""), env.get("PYOMO_AMPLFUNC", "")) From 9790e080a7f3412841125e7c38ada2506e583ea2 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 4 Apr 2024 13:55:08 -0600 Subject: [PATCH 1604/1797] NFC: fix typo --- 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 a5aaece8531..110e95c3c6d 100644 --- a/pyomo/repn/plugins/standard_form.py +++ b/pyomo/repn/plugins/standard_form.py @@ -96,7 +96,7 @@ class LinearStandardFormInfo(object): objectives : List[_ObjectiveData] - The list of Pyomo objective objects correcponding to the active objectives + The list of Pyomo objective objects corresponding to the active objectives eliminated_vars: List[Tuple[_VarData, NumericExpression]] From 5dac102e6391311ff7a5615b870199dc3776842a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 4 Apr 2024 17:13:34 -0600 Subject: [PATCH 1605/1797] Adding test requested by PR review --- pyomo/common/tests/test_enums.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyomo/common/tests/test_enums.py b/pyomo/common/tests/test_enums.py index 52ee1c5abb3..80d081505e9 100644 --- a/pyomo/common/tests/test_enums.py +++ b/pyomo/common/tests/test_enums.py @@ -59,6 +59,8 @@ def test_call(self): with self.assertRaisesRegex(ValueError, "'foo' is not a valid ProblemSense"): ProblemSense('foo') + with self.assertRaisesRegex(ValueError, "2 is not a valid ProblemSense"): + ProblemSense(2) def test_contains(self): self.assertIn(ProblemSense.unknown, ProblemSense) From 7b46ea5f2c6fecd38a260ccd56968493acb70911 Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 5 Apr 2024 08:09:05 -0400 Subject: [PATCH 1606/1797] Make `symbolic_solver_labels` configurable --- pyomo/contrib/pyros/config.py | 15 +++++++++++++++ pyomo/contrib/pyros/tests/test_grcs.py | 2 ++ pyomo/contrib/pyros/util.py | 7 +++++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/pyros/config.py b/pyomo/contrib/pyros/config.py index bc2bfd591e6..8ab24939349 100644 --- a/pyomo/contrib/pyros/config.py +++ b/pyomo/contrib/pyros/config.py @@ -503,6 +503,21 @@ def pyros_config(): ), ), ) + CONFIG.declare( + 'symbolic_solver_labels', + ConfigValue( + default=False, + domain=bool, + description=( + """ + True to ensure the component names given to the + subordinate solvers for every subproblem reflect + the names of the corresponding Pyomo modeling components, + False otherwise. + """ + ), + ), + ) # ================================================ # === Required User Inputs diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py index 41223b30899..d49ed6b1002 100644 --- a/pyomo/contrib/pyros/tests/test_grcs.py +++ b/pyomo/contrib/pyros/tests/test_grcs.py @@ -3795,6 +3795,7 @@ def test_solve_master(self): config.declare( "progress_logger", ConfigValue(default=logging.getLogger(__name__)) ) + config.declare("symbolic_solver_labels", ConfigValue(default=False)) with time_code(master_data.timing, "main", is_main_timer=True): master_soln = solve_master(master_data, config) @@ -6171,6 +6172,7 @@ def test_log_config(self): " keepfiles=False\n" " tee=False\n" " load_solution=True\n" + " symbolic_solver_labels=False\n" " objective_focus=\n" " nominal_uncertain_param_vals=[0.5]\n" " decision_rule_order=0\n" diff --git a/pyomo/contrib/pyros/util.py b/pyomo/contrib/pyros/util.py index 5d386240609..23cde45d0cf 100644 --- a/pyomo/contrib/pyros/util.py +++ b/pyomo/contrib/pyros/util.py @@ -1799,7 +1799,7 @@ def call_solver(model, solver, config, timing_obj, timer_name, err_msg): If ApplicationError is raised by the solver. In this case, `err_msg` is logged through ``config.progress_logger.exception()`` before - the excception is raised. + the exception is raised. """ tt_timer = TicTocTimer() @@ -1811,7 +1811,10 @@ def call_solver(model, solver, config, timing_obj, timer_name, err_msg): try: results = solver.solve( - model, tee=config.tee, load_solutions=False, symbolic_solver_labels=True + model, + tee=config.tee, + load_solutions=False, + symbolic_solver_labels=config.symbolic_solver_labels, ) except ApplicationError: # account for possible external subsolver errors From f766b33b2f7cea168a1a1b1ba49d49b8b7e4f546 Mon Sep 17 00:00:00 2001 From: jasherma Date: Fri, 5 Apr 2024 08:10:55 -0400 Subject: [PATCH 1607/1797] Make log example reflective of `symbolic_solver_labels` --- doc/OnlineDocs/contributed_packages/pyros.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/OnlineDocs/contributed_packages/pyros.rst b/doc/OnlineDocs/contributed_packages/pyros.rst index 9faa6d1365f..95049eded8a 100644 --- a/doc/OnlineDocs/contributed_packages/pyros.rst +++ b/doc/OnlineDocs/contributed_packages/pyros.rst @@ -926,6 +926,7 @@ Observe that the log contains the following information: keepfiles=False tee=False load_solution=True + symbolic_solver_labels=False objective_focus= nominal_uncertain_param_vals=[0.13248000000000001, 4.97, 4.97, 1800] decision_rule_order=1 From b0217c86ec3baf36126cdcb8371b1e4dbf7e3874 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 5 Apr 2024 15:18:56 -0600 Subject: [PATCH 1608/1797] Moving exit node dispatcher onto base class from pyomo.repn.util and fixing the monomial expression tests --- pyomo/contrib/cp/repn/docplex_writer.py | 20 ++++++++++++++----- pyomo/contrib/cp/tests/test_docplex_walker.py | 4 ++-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index 98d3e07e8ed..5dd355dc70d 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -88,6 +88,7 @@ from pyomo.core.expr.visitor import StreamBasedExpressionVisitor, identify_variables from pyomo.core.base import Set, RangeSet from pyomo.core.base.set import SetProduct +from pyomo.repn.util import ExitNodeDispatcher from pyomo.opt import WriterFactory, SolverFactory, TerminationCondition, SolverResults ### FIXME: Remove the following as soon as non-active components no @@ -707,9 +708,11 @@ def _get_bool_valued_expr(arg): def _handle_monomial_expr(visitor, node, arg1, arg2): # Monomial terms show up a lot. This handles some common # simplifications (necessary in part for the unit tests) + print(arg1) + print(arg2) if arg2[1].__class__ in EXPR.native_types: return _GENERAL, arg1[1] * arg2[1] - elif arg1[1] == 1: + elif arg1[1].__class__ in EXPR.native_types and arg1[1] == 1: return arg2 return (_GENERAL, cp.times(_get_int_valued_expr(arg1), _get_int_valued_expr(arg2))) @@ -997,8 +1000,7 @@ def _handle_synchronize_expression_node(visitor, node, *args): return _GENERAL, cp.synchronize(args[0][1], [arg[1] for arg in args[1:]]) -class LogicalToDoCplex(StreamBasedExpressionVisitor): - _operator_handles = { +_operator_handles = { EXPR.GetItemExpression: _handle_getitem, EXPR.Structural_GetItemExpression: _handle_getitem, EXPR.Numeric_GetItemExpression: _handle_getitem, @@ -1047,6 +1049,14 @@ class LogicalToDoCplex(StreamBasedExpressionVisitor): AlternativeExpression: _handle_alternative_expression_node, SynchronizeExpression: _handle_synchronize_expression_node, } + + +class LogicalToDoCplex(StreamBasedExpressionVisitor): + exit_node_dispatcher = ExitNodeDispatcher( + _operator_handles + ) + # NOTE: Because of indirection, we can encounter indexed Params and Vars in + # expressions _var_handles = { IntervalVarStartTime: _before_interval_var_start_time, IntervalVarEndTime: _before_interval_var_end_time, @@ -1065,7 +1075,7 @@ class LogicalToDoCplex(StreamBasedExpressionVisitor): IndexedBooleanVar: _before_indexed_boolean_var, _GeneralExpressionData: _before_named_expression, ScalarExpression: _before_named_expression, - IndexedParam: _before_indexed_param, # Because of indirection + IndexedParam: _before_indexed_param, ScalarParam: _before_param, _ParamData: _before_param, } @@ -1101,7 +1111,7 @@ def beforeChild(self, node, child, child_idx): return True, None def exitNode(self, node, data): - return self._operator_handles[node.__class__](self, node, *data) + return self.exit_node_dispatcher[node.__class__](self, node, *data) finalizeResult = None diff --git a/pyomo/contrib/cp/tests/test_docplex_walker.py b/pyomo/contrib/cp/tests/test_docplex_walker.py index d14e0bc2d6f..a7e537ee15b 100644 --- a/pyomo/contrib/cp/tests/test_docplex_walker.py +++ b/pyomo/contrib/cp/tests/test_docplex_walker.py @@ -261,11 +261,11 @@ def test_monomial_expressions(self): e = (1 / m.p) * m.x expr = visitor.walk_expression((e, e, 0)) - self.assertTrue(expr[1].equals(0.25 * x)) + self.assertTrue(expr[1].equals(cp.float_div(1, 4) * x)) e = (m.p ** (0.5)) * m.x expr = visitor.walk_expression((e, e, 0)) - self.assertTrue(expr[1].equals(2 * x)) + self.assertTrue(expr[1].equals(cp.power(4, 0.5) * x)) @unittest.skipIf(not docplex_available, "docplex is not available") From 5424f604281bcd1f09ae16d710e55509a9de368e Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 5 Apr 2024 15:22:35 -0600 Subject: [PATCH 1609/1797] Removing some redundant operator handlers now that I have the subclass magic for automatic registration. --- pyomo/contrib/cp/repn/docplex_writer.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index 5dd355dc70d..50f5f9e4294 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -1002,13 +1002,7 @@ def _handle_synchronize_expression_node(visitor, node, *args): _operator_handles = { EXPR.GetItemExpression: _handle_getitem, - EXPR.Structural_GetItemExpression: _handle_getitem, - EXPR.Numeric_GetItemExpression: _handle_getitem, - EXPR.Boolean_GetItemExpression: _handle_getitem, EXPR.GetAttrExpression: _handle_getattr, - EXPR.Structural_GetAttrExpression: _handle_getattr, - EXPR.Numeric_GetAttrExpression: _handle_getattr, - EXPR.Boolean_GetAttrExpression: _handle_getattr, EXPR.CallExpression: _handle_call, EXPR.NegationExpression: _handle_negation_node, EXPR.ProductExpression: _handle_product_node, @@ -1017,7 +1011,6 @@ def _handle_synchronize_expression_node(visitor, node, *args): EXPR.AbsExpression: _handle_abs_node, EXPR.MonomialTermExpression: _handle_monomial_expr, EXPR.SumExpression: _handle_sum_node, - EXPR.LinearExpression: _handle_sum_node, EXPR.MinExpression: _handle_min_node, EXPR.MaxExpression: _handle_max_node, EXPR.NotExpression: _handle_not_node, From e6f0a10f64c802eb7f2a2fcb24d8bb9bf41a1f2d Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 5 Apr 2024 16:58:35 -0600 Subject: [PATCH 1610/1797] black --- pyomo/contrib/cp/repn/docplex_writer.py | 86 ++++++++++++------------- 1 file changed, 42 insertions(+), 44 deletions(-) diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index 50f5f9e4294..0263b6b82a1 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -1001,53 +1001,51 @@ def _handle_synchronize_expression_node(visitor, node, *args): _operator_handles = { - EXPR.GetItemExpression: _handle_getitem, - EXPR.GetAttrExpression: _handle_getattr, - EXPR.CallExpression: _handle_call, - EXPR.NegationExpression: _handle_negation_node, - EXPR.ProductExpression: _handle_product_node, - EXPR.DivisionExpression: _handle_division_node, - EXPR.PowExpression: _handle_pow_node, - EXPR.AbsExpression: _handle_abs_node, - EXPR.MonomialTermExpression: _handle_monomial_expr, - EXPR.SumExpression: _handle_sum_node, - EXPR.MinExpression: _handle_min_node, - EXPR.MaxExpression: _handle_max_node, - EXPR.NotExpression: _handle_not_node, - EXPR.EquivalenceExpression: _handle_equivalence_node, - EXPR.ImplicationExpression: _handle_implication_node, - EXPR.AndExpression: _handle_and_node, - EXPR.OrExpression: _handle_or_node, - EXPR.XorExpression: _handle_xor_node, - 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, - EXPR.RangedExpression: _handle_ranged_inequality_node, - BeforeExpression: _handle_before_expression_node, - AtExpression: _handle_at_expression_node, - AlwaysIn: _handle_always_in_node, - _GeneralExpressionData: _handle_named_expression_node, - ScalarExpression: _handle_named_expression_node, - NoOverlapExpression: _handle_no_overlap_expression_node, - FirstInSequenceExpression: _handle_first_in_sequence_expression_node, - LastInSequenceExpression: _handle_last_in_sequence_expression_node, - BeforeInSequenceExpression: _handle_before_in_sequence_expression_node, - PredecessorToExpression: _handle_predecessor_to_expression_node, - SpanExpression: _handle_span_expression_node, - AlternativeExpression: _handle_alternative_expression_node, - SynchronizeExpression: _handle_synchronize_expression_node, - } + EXPR.GetItemExpression: _handle_getitem, + EXPR.GetAttrExpression: _handle_getattr, + EXPR.CallExpression: _handle_call, + EXPR.NegationExpression: _handle_negation_node, + EXPR.ProductExpression: _handle_product_node, + EXPR.DivisionExpression: _handle_division_node, + EXPR.PowExpression: _handle_pow_node, + EXPR.AbsExpression: _handle_abs_node, + EXPR.MonomialTermExpression: _handle_monomial_expr, + EXPR.SumExpression: _handle_sum_node, + EXPR.MinExpression: _handle_min_node, + EXPR.MaxExpression: _handle_max_node, + EXPR.NotExpression: _handle_not_node, + EXPR.EquivalenceExpression: _handle_equivalence_node, + EXPR.ImplicationExpression: _handle_implication_node, + EXPR.AndExpression: _handle_and_node, + EXPR.OrExpression: _handle_or_node, + EXPR.XorExpression: _handle_xor_node, + 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, + EXPR.RangedExpression: _handle_ranged_inequality_node, + BeforeExpression: _handle_before_expression_node, + AtExpression: _handle_at_expression_node, + AlwaysIn: _handle_always_in_node, + _GeneralExpressionData: _handle_named_expression_node, + ScalarExpression: _handle_named_expression_node, + NoOverlapExpression: _handle_no_overlap_expression_node, + FirstInSequenceExpression: _handle_first_in_sequence_expression_node, + LastInSequenceExpression: _handle_last_in_sequence_expression_node, + BeforeInSequenceExpression: _handle_before_in_sequence_expression_node, + PredecessorToExpression: _handle_predecessor_to_expression_node, + SpanExpression: _handle_span_expression_node, + AlternativeExpression: _handle_alternative_expression_node, + SynchronizeExpression: _handle_synchronize_expression_node, +} class LogicalToDoCplex(StreamBasedExpressionVisitor): - exit_node_dispatcher = ExitNodeDispatcher( - _operator_handles - ) + exit_node_dispatcher = ExitNodeDispatcher(_operator_handles) # NOTE: Because of indirection, we can encounter indexed Params and Vars in # expressions _var_handles = { From 558df7097a4ef5ee757847b8099bd15b8db8ac5d Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 5 Apr 2024 17:17:34 -0600 Subject: [PATCH 1611/1797] Removing a lot of unused imports --- pyomo/contrib/cp/repn/docplex_writer.py | 3 +-- .../contrib/cp/scheduling_expr/step_function_expressions.py | 1 - pyomo/contrib/cp/tests/test_docplex_walker.py | 5 ----- pyomo/contrib/cp/tests/test_sequence_expressions.py | 5 ++--- pyomo/contrib/cp/tests/test_sequence_var.py | 2 +- pyomo/contrib/cp/transform/logical_to_disjunctive_program.py | 1 - pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py | 4 ---- 7 files changed, 4 insertions(+), 17 deletions(-) diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index 0263b6b82a1..c48d5858b0e 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -33,7 +33,6 @@ from pyomo.contrib.cp.sequence_var import ( SequenceVar, ScalarSequenceVar, - IndexedSequenceVar, _SequenceVarData, ) from pyomo.contrib.cp.scheduling_expr.scheduling_logic import ( @@ -81,7 +80,7 @@ _GeneralBooleanVarData, IndexedBooleanVar, ) -from pyomo.core.base.expression import ScalarExpression, _GeneralExpressionData +from pyomo.core.base.expression import _GeneralExpressionData, ScalarExpression from pyomo.core.base.param import IndexedParam, ScalarParam, _ParamData from pyomo.core.base.var import ScalarVar, _GeneralVarData, IndexedVar import pyomo.core.expr as EXPR diff --git a/pyomo/contrib/cp/scheduling_expr/step_function_expressions.py b/pyomo/contrib/cp/scheduling_expr/step_function_expressions.py index b75306f72c9..129dff66b48 100644 --- a/pyomo/contrib/cp/scheduling_expr/step_function_expressions.py +++ b/pyomo/contrib/cp/scheduling_expr/step_function_expressions.py @@ -15,7 +15,6 @@ IntervalVarStartTime, IntervalVarEndTime, ) -from pyomo.core.base.component import Component from pyomo.core.expr.base import ExpressionBase from pyomo.core.expr.logical_expr import BooleanExpression diff --git a/pyomo/contrib/cp/tests/test_docplex_walker.py b/pyomo/contrib/cp/tests/test_docplex_walker.py index a7e537ee15b..9aa91b5185f 100644 --- a/pyomo/contrib/cp/tests/test_docplex_walker.py +++ b/pyomo/contrib/cp/tests/test_docplex_walker.py @@ -17,13 +17,10 @@ no_overlap, first_in_sequence, last_in_sequence, - before_in_sequence, - predecessor_to, alternative, synchronize, ) from pyomo.contrib.cp.scheduling_expr.step_function_expressions import ( - AlwaysIn, Step, Pulse, ) @@ -56,8 +53,6 @@ Integers, inequality, Expression, - Reals, - Set, Param, ) diff --git a/pyomo/contrib/cp/tests/test_sequence_expressions.py b/pyomo/contrib/cp/tests/test_sequence_expressions.py index b676881e379..c7cf94f23d5 100644 --- a/pyomo/contrib/cp/tests/test_sequence_expressions.py +++ b/pyomo/contrib/cp/tests/test_sequence_expressions.py @@ -9,7 +9,6 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from io import StringIO import pyomo.common.unittest as unittest from pyomo.contrib.cp.interval_var import IntervalVar from pyomo.contrib.cp.scheduling_expr.scheduling_logic import ( @@ -32,8 +31,8 @@ first_in_sequence, last_in_sequence, ) -from pyomo.contrib.cp.sequence_var import SequenceVar, IndexedSequenceVar -from pyomo.environ import ConcreteModel, Integers, LogicalConstraint, Set, value, Var +from pyomo.contrib.cp.sequence_var import SequenceVar +from pyomo.environ import ConcreteModel, LogicalConstraint, Set class TestSequenceVarExpressions(unittest.TestCase): diff --git a/pyomo/contrib/cp/tests/test_sequence_var.py b/pyomo/contrib/cp/tests/test_sequence_var.py index ebff465a376..c1e205c6326 100644 --- a/pyomo/contrib/cp/tests/test_sequence_var.py +++ b/pyomo/contrib/cp/tests/test_sequence_var.py @@ -13,7 +13,7 @@ import pyomo.common.unittest as unittest from pyomo.contrib.cp.interval_var import IntervalVar from pyomo.contrib.cp.sequence_var import SequenceVar, IndexedSequenceVar -from pyomo.environ import ConcreteModel, Integers, Set, value, Var +from pyomo.environ import ConcreteModel, Set class TestScalarSequenceVar(unittest.TestCase): diff --git a/pyomo/contrib/cp/transform/logical_to_disjunctive_program.py b/pyomo/contrib/cp/transform/logical_to_disjunctive_program.py index e318e621e88..3c6960bc198 100644 --- a/pyomo/contrib/cp/transform/logical_to_disjunctive_program.py +++ b/pyomo/contrib/cp/transform/logical_to_disjunctive_program.py @@ -12,7 +12,6 @@ from pyomo.contrib.cp.transform.logical_to_disjunctive_walker import ( LogicalToDisjunctiveVisitor, ) -from pyomo.common.collections import ComponentMap from pyomo.common.modeling import unique_component_name from pyomo.common.config import ConfigDict, ConfigValue diff --git a/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py b/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py index d5f13e91535..09bba403850 100644 --- a/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py +++ b/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py @@ -9,14 +9,10 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -import collections - from pyomo.common.collections import ComponentMap from pyomo.common.errors import MouseTrap from pyomo.core.expr.expr_common import ExpressionType from pyomo.core.expr.visitor import StreamBasedExpressionVisitor -from pyomo.core.expr.numeric_expr import NumericExpression -from pyomo.core.expr.relational_expr import RelationalExpression import pyomo.core.expr as EXPR from pyomo.core.base import ( Binary, From a4a78ed5c629ab3f4b953f591537b10450b0a977 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 5 Apr 2024 17:17:59 -0600 Subject: [PATCH 1612/1797] black --- pyomo/contrib/cp/tests/test_docplex_walker.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pyomo/contrib/cp/tests/test_docplex_walker.py b/pyomo/contrib/cp/tests/test_docplex_walker.py index 9aa91b5185f..1173ae66eab 100644 --- a/pyomo/contrib/cp/tests/test_docplex_walker.py +++ b/pyomo/contrib/cp/tests/test_docplex_walker.py @@ -20,10 +20,7 @@ alternative, synchronize, ) -from pyomo.contrib.cp.scheduling_expr.step_function_expressions import ( - Step, - Pulse, -) +from pyomo.contrib.cp.scheduling_expr.step_function_expressions import Step, Pulse from pyomo.contrib.cp.repn.docplex_writer import docplex_available, LogicalToDoCplex from pyomo.core.base.range import NumericRange From 3e09bd2f5ca5bec451ba4e07d3886615173c1b9b Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Fri, 5 Apr 2024 17:19:16 -0600 Subject: [PATCH 1613/1797] removing debugging --- pyomo/contrib/cp/repn/docplex_writer.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index c48d5858b0e..c8a1143a7b5 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -707,8 +707,6 @@ def _get_bool_valued_expr(arg): def _handle_monomial_expr(visitor, node, arg1, arg2): # Monomial terms show up a lot. This handles some common # simplifications (necessary in part for the unit tests) - print(arg1) - print(arg2) if arg2[1].__class__ in EXPR.native_types: return _GENERAL, arg1[1] * arg2[1] elif arg1[1].__class__ in EXPR.native_types and arg1[1] == 1: From ad7011f12e352ca25212bd845893b3c2bb978318 Mon Sep 17 00:00:00 2001 From: Bernard Knueven Date: Mon, 8 Apr 2024 12:09:58 -0600 Subject: [PATCH 1614/1797] check _skip_trivial_costraints before the constraint body --- pyomo/solvers/plugins/solvers/gurobi_direct.py | 5 ++--- pyomo/solvers/plugins/solvers/xpress_direct.py | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/pyomo/solvers/plugins/solvers/gurobi_direct.py b/pyomo/solvers/plugins/solvers/gurobi_direct.py index 1d88eced629..ed66a4e0e7b 100644 --- a/pyomo/solvers/plugins/solvers/gurobi_direct.py +++ b/pyomo/solvers/plugins/solvers/gurobi_direct.py @@ -493,9 +493,8 @@ def _add_constraint(self, con): if not con.active: return None - if is_fixed(con.body): - if self._skip_trivial_constraints: - return None + if self._skip_trivial_constraints and is_fixed(con.body): + return None conname = self._symbol_map.getSymbol(con, self._labeler) diff --git a/pyomo/solvers/plugins/solvers/xpress_direct.py b/pyomo/solvers/plugins/solvers/xpress_direct.py index 75cf8f921df..c62f76d85ce 100644 --- a/pyomo/solvers/plugins/solvers/xpress_direct.py +++ b/pyomo/solvers/plugins/solvers/xpress_direct.py @@ -667,9 +667,8 @@ def _add_constraint(self, con): if not con.active: return None - if is_fixed(con.body): - if self._skip_trivial_constraints: - return None + if self._skip_trivial_constraints and is_fixed(con.body): + return None conname = self._symbol_map.getSymbol(con, self._labeler) From 9bf65310dc34fd555918dcc39a58b57349156a4d Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 9 Apr 2024 15:01:53 -0600 Subject: [PATCH 1615/1797] Bug fixes: name and solutions attributes --- pyomo/contrib/solver/base.py | 11 +++++++---- pyomo/contrib/solver/factory.py | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index cec392271f6..69f32b45075 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -61,10 +61,7 @@ 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.name = kwds.pop('name') self.config = self.CONFIG(value=kwds) # @@ -499,6 +496,12 @@ def _solution_handler( """Method to handle the preferred action for the solution""" symbol_map = SymbolMap() symbol_map.default_labeler = NumericLabeler('x') + if not hasattr(model, 'solutions'): + # This logic gets around Issue #2130 in which + # solutions is not an attribute on Blocks + from pyomo.core.base.PyomoModel import ModelSolutions + + setattr(model, 'solutions', ModelSolutions(model)) model.solutions.add_symbol_map(symbol_map) legacy_results._smap_id = id(symbol_map) delete_legacy_soln = True diff --git a/pyomo/contrib/solver/factory.py b/pyomo/contrib/solver/factory.py index 99fbcc3a6d0..71bf81ee15b 100644 --- a/pyomo/contrib/solver/factory.py +++ b/pyomo/contrib/solver/factory.py @@ -31,6 +31,7 @@ class LegacySolver(LegacySolverWrapper, cls): LegacySolver ) + cls.name = name return cls return decorator From 7268a954a83bfe83ee01620a9d16ddcfba9082ab Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 9 Apr 2024 15:05:51 -0600 Subject: [PATCH 1616/1797] Add back in logic for if there is no name attr --- pyomo/contrib/solver/base.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 69f32b45075..8d49344fbcf 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -14,8 +14,8 @@ from typing import Sequence, Dict, Optional, Mapping, NoReturn, List, Tuple import os -from pyomo.core.base.constraint import Constraint, _GeneralConstraintData -from pyomo.core.base.var import Var, _GeneralVarData +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 Objective, _GeneralObjectiveData @@ -62,6 +62,8 @@ def __init__(self, **kwds) -> None: # if they really desire. Otherwise it defaults to the class name (all lowercase) if "name" in kwds: self.name = kwds.pop('name') + elif not hasattr(self, 'name'): + self.name = type(self).__name__.lower() self.config = self.CONFIG(value=kwds) # From 9b4fd6f4f2a2a5f3b959cb9397d7923498280b1d Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 9 Apr 2024 15:12:27 -0600 Subject: [PATCH 1617/1797] Add relevant comments --- pyomo/contrib/solver/base.py | 5 ++++- pyomo/contrib/solver/factory.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/solver/base.py b/pyomo/contrib/solver/base.py index 8d49344fbcf..736bf8b5a7c 100644 --- a/pyomo/contrib/solver/base.py +++ b/pyomo/contrib/solver/base.py @@ -59,7 +59,10 @@ class SolverBase(abc.ABC): 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 they really desire. + # Otherwise it defaults to the name defined when the solver was registered + # in the SolverFactory or the class name (all lowercase), whichever is + # applicable if "name" in kwds: self.name = kwds.pop('name') elif not hasattr(self, 'name'): diff --git a/pyomo/contrib/solver/factory.py b/pyomo/contrib/solver/factory.py index 71bf81ee15b..d3ca1329af3 100644 --- a/pyomo/contrib/solver/factory.py +++ b/pyomo/contrib/solver/factory.py @@ -31,6 +31,7 @@ class LegacySolver(LegacySolverWrapper, cls): LegacySolver ) + # Preserve the preferred name, as registered in the Factory cls.name = name return cls From 88535d42141f0aa678ec88b8a86cf23684faf9aa Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 10 Apr 2024 09:24:12 -0600 Subject: [PATCH 1618/1797] Remove remaining references to '_.*Data' classes --- pyomo/contrib/pyros/config.py | 2 +- pyomo/core/base/block.py | 18 +++++++++--------- pyomo/gdp/util.py | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pyomo/contrib/pyros/config.py b/pyomo/contrib/pyros/config.py index 59bf9a9ab37..31e462223da 100644 --- a/pyomo/contrib/pyros/config.py +++ b/pyomo/contrib/pyros/config.py @@ -98,7 +98,7 @@ class InputDataStandardizer(object): Pyomo component type, such as Component, Var or Param. cdatatype : type Corresponding Pyomo component data type, such as - _ComponentData, VarData, or ParamData. + ComponentData, VarData, or ParamData. ctype_validator : callable, optional Validator function for objects of type `ctype`. cdatatype_validator : callable, optional diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index 8f4e86fe697..3eb18dde7a9 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -160,13 +160,13 @@ def __init__(self): self.seen_data = set() def unique(self, comp, items, are_values): - """Returns generator that filters duplicate _ComponentData objects from items + """Returns generator that filters duplicate ComponentData objects from items Parameters ---------- comp: ComponentBase The Component (indexed or scalar) that contains all - _ComponentData returned by the `items` generator. `comp` may + ComponentData returned by the `items` generator. `comp` may be an IndexedComponent generated by :py:func:`Reference` (and hence may not own the component datas in `items`) @@ -175,8 +175,8 @@ def unique(self, comp, items, are_values): `comp` Component. are_values: bool - If `True`, `items` yields _ComponentData objects, otherwise, - `items` yields `(index, _ComponentData)` tuples. + If `True`, `items` yields ComponentData objects, otherwise, + `items` yields `(index, ComponentData)` tuples. """ if comp.is_reference(): @@ -1399,7 +1399,7 @@ def _component_data_iteritems(self, ctype, active, sort, dedup): Generator that returns a nested 2-tuple of - ((component name, index value), _ComponentData) + ((component name, index value), ComponentData) for every component data in the block matching the specified ctype(s). @@ -1416,7 +1416,7 @@ def _component_data_iteritems(self, ctype, active, sort, dedup): Iterate over the components in a specified sorted order dedup: _DeduplicateInfo - Deduplicator to prevent returning the same _ComponentData twice + Deduplicator to prevent returning the same ComponentData twice """ for name, comp in PseudoMap(self, ctype, active, sort).items(): # NOTE: Suffix has a dict interface (something other derived @@ -1452,7 +1452,7 @@ def _component_data_iteritems(self, ctype, active, sort, dedup): yield from dedup.unique(comp, _items, False) def _component_data_itervalues(self, ctype, active, sort, dedup): - """Generator that returns the _ComponentData for every component data + """Generator that returns the ComponentData for every component data in the block. Parameters @@ -1467,7 +1467,7 @@ def _component_data_itervalues(self, ctype, active, sort, dedup): Iterate over the components in a specified sorted order dedup: _DeduplicateInfo - Deduplicator to prevent returning the same _ComponentData twice + Deduplicator to prevent returning the same ComponentData twice """ for comp in PseudoMap(self, ctype, active, sort).values(): # NOTE: Suffix has a dict interface (something other derived @@ -1573,7 +1573,7 @@ def component_data_iterindex( generator recursively descends into sub-blocks. The tuple is - ((component name, index value), _ComponentData) + ((component name, index value), ComponentData) """ dedup = _DeduplicateInfo() diff --git a/pyomo/gdp/util.py b/pyomo/gdp/util.py index ee905791c26..2fe8e9e1dee 100644 --- a/pyomo/gdp/util.py +++ b/pyomo/gdp/util.py @@ -534,7 +534,7 @@ def get_transformed_constraints(srcConstraint): "want the container for all transformed constraints " "from an IndexedDisjunction, this is the parent " "component of a transformed constraint originating " - "from any of its _ComponentDatas.)" + "from any of its ComponentDatas.)" ) transBlock = _get_constraint_transBlock(srcConstraint) transformed_constraints = transBlock.private_data( From b235295174500eacb3bc0c2171d298b3ce3b8d48 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 10 Apr 2024 09:31:02 -0600 Subject: [PATCH 1619/1797] calculate_variable_from_constraint: add check for indexed constraint --- pyomo/util/calc_var_value.py | 7 +++++++ pyomo/util/tests/test_calc_var_value.py | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/pyomo/util/calc_var_value.py b/pyomo/util/calc_var_value.py index 156ad56dffb..42ee3119361 100644 --- a/pyomo/util/calc_var_value.py +++ b/pyomo/util/calc_var_value.py @@ -85,6 +85,13 @@ def calculate_variable_from_constraint( constraint = Constraint(expr=constraint, name=type(constraint).__name__) constraint.construct() + if constraint.is_indexed(): + raise ValueError( + 'calculate_variable_from_constraint(): constraint must be a ' + 'scalar constraint or a single ConstraintData. Received ' + f'{constraint.__class__.__name__} ("{constraint.name}")' + ) + body = constraint.body lower = constraint.lb upper = constraint.ub diff --git a/pyomo/util/tests/test_calc_var_value.py b/pyomo/util/tests/test_calc_var_value.py index a02d7a7d838..4bed4d5c843 100644 --- a/pyomo/util/tests/test_calc_var_value.py +++ b/pyomo/util/tests/test_calc_var_value.py @@ -101,6 +101,15 @@ def test_initialize_value(self): ): calculate_variable_from_constraint(m.x, m.lt) + m.indexed = Constraint([1, 2], rule=lambda m, i: m.x <= i) + with self.assertRaisesRegex( + ValueError, + r"calculate_variable_from_constraint\(\): constraint must be a scalar " + r"constraint or a single ConstraintData. Received IndexedConstraint " + r'\("indexed"\)', + ): + calculate_variable_from_constraint(m.x, m.indexed) + def test_linear(self): m = ConcreteModel() m.x = Var() From 76cd7469b5faeb970ccf5b62e7f7e85e6c96cabd Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 10 Apr 2024 09:44:42 -0600 Subject: [PATCH 1620/1797] NFC: Update docstring to fix typo and copy/paste errors --- pyomo/core/base/boolean_var.py | 44 ++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/pyomo/core/base/boolean_var.py b/pyomo/core/base/boolean_var.py index 98761dee536..67c06bdacce 100644 --- a/pyomo/core/base/boolean_var.py +++ b/pyomo/core/base/boolean_var.py @@ -81,26 +81,30 @@ def _associated_binary_mapper(encode, val): class BooleanVarData(ComponentData, BooleanValue): - """ - This class defines the data for a single Boolean variable. - - Constructor Arguments: - component The BooleanVar object that owns this data. - - Public Class Attributes: - domain The domain of this variable. - fixed If True, then this variable is treated as a - fixed constant in the model. - stale A Boolean indicating whether the value of this variable is - legitimiate. This value is true if the value should - be considered legitimate for purposes of reporting or - other interrogation. - value The numeric value of this variable. - - The domain attribute is a property because it is - too widely accessed directly to enforce explicit getter/setter - methods and we need to deter directly modifying or accessing - these attributes in certain cases. + """This class defines the data for a single Boolean variable. + + Parameters + ---------- + component: Component + The BooleanVar object that owns this data. + + Attributes + ---------- + domain: SetData + The domain of this variable. + + fixed: bool + If True, then this variable is treated as a fixed constant in + the model. + + stale: bool + A Boolean indicating whether the value of this variable is + Consistent with the most recent solve. `True` indicates that + this variable's value was set prior to the most recent solve and + was not updated by the results returned by the solve. + + value: bool + The value of this variable. """ __slots__ = ('_value', 'fixed', '_stale', '_associated_binary') From d19c8c9b5d9c5aeb3e04ee55d543add5e75a7cf7 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 11 Apr 2024 08:30:41 -0600 Subject: [PATCH 1621/1797] Disable the use of universal newlines in the ipopt_v2 NL file --- pyomo/contrib/solver/ipopt.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/solver/ipopt.py b/pyomo/contrib/solver/ipopt.py index 5f601b7a9f7..588e06ad74c 100644 --- a/pyomo/contrib/solver/ipopt.py +++ b/pyomo/contrib/solver/ipopt.py @@ -307,7 +307,12 @@ 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( + # Note: the ASL has an issue where string constants written + # to the NL file (e.g. arguments in external functions) MUST + # be terminated with '\n' regardless of platform. We will + # disable universal newlines in the NL file to prevent + # Python from mapping those '\n' to '\r\n' on Windows. + with open(basename + '.nl', 'w', newline='\n') as nl_file, open( basename + '.row', 'w' ) as row_file, open(basename + '.col', 'w') as col_file: timer.start('write_nl_file') From b63358c5eb6578102917d07e00380fa3188973e3 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 16 Apr 2024 14:57:55 -0400 Subject: [PATCH 1622/1797] add highs version requirements for pr workflow --- .github/workflows/test_pr_and_main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index a2060240391..28c72541a13 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -606,7 +606,7 @@ jobs: shell: bash run: | echo "NOTE: temporarily pinning to highspy pre-release for testing" - $PYTHON_EXE -m pip install --cache-dir cache/pip highspy==1.7.1.dev1 \ + $PYTHON_EXE -m pip install --cache-dir cache/pip highspy>=1.7.1.dev1 \ || echo "WARNING: highspy is not available" - name: Set up coverage tracking From 747da89e8124d04955ebc161a74c227f658289e6 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 16 Apr 2024 14:43:14 -0600 Subject: [PATCH 1623/1797] TEMPORARY FIX: Pin to mpi4py 3.1.5 --- .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 55f903a37f9..a15240194f2 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 + PACKAGES: mpi4py==3.1.5 - 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 76ec6de951a..2615f6b838e 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 + PACKAGES: mpi4py==3.1.5 - os: ubuntu-latest python: '3.11' From 28f5080a4b3506200380d89bbe2cfedf9e23a282 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 16 Apr 2024 20:09:07 -0600 Subject: [PATCH 1624/1797] nlv2: fix error reporting number of nonlinear discrete variables --- 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 ee5b65149ae..d629da2ee87 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -1277,8 +1277,8 @@ def write(self, model): len(linear_binary_vars), len(linear_integer_vars), len(both_vars_nonlinear.intersection(discrete_vars)), - len(con_vars_nonlinear.intersection(discrete_vars)), - len(obj_vars_nonlinear.intersection(discrete_vars)), + len(con_only_nonlinear_vars.intersection(discrete_vars)), + len(obj_only_nonlinear_vars.intersection(discrete_vars)), ) ) # From be3ca3192d5b9a91a9834f001fa07b352b30b8aa Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 16 Apr 2024 20:09:50 -0600 Subject: [PATCH 1625/1797] nlv2: map integer variables over [0,1] t binary --- pyomo/repn/plugins/nl_writer.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index d629da2ee87..ab74f0ab44d 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -113,6 +113,7 @@ TOL = 1e-8 inf = float('inf') minus_inf = -inf +zero_one = {0, 1} _CONSTANT = ExprType.CONSTANT _MONOMIAL = ExprType.MONOMIAL @@ -882,7 +883,13 @@ def write(self, model): elif v.is_binary(): binary_vars.add(_id) elif v.is_integer(): - integer_vars.add(_id) + bnd = var_bounds[_id] + # Note: integer variables whose bounds are in {0, 1} + # should be classified as binary + if bnd[1] in zero_one and bnd[0] in zero_one: + binary_vars.add(_id) + else: + integer_vars.add(_id) else: raise ValueError( f"Variable '{v.name}' has a domain that is not Real, " From 2f585db8187952149c0974d14e6ce99ab20b4f31 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 16 Apr 2024 20:10:09 -0600 Subject: [PATCH 1626/1797] nlv2: add tests for variable categorization --- pyomo/repn/tests/ampl/test_nlv2.py | 134 ++++++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 1 deletion(-) diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index be72025edcd..0f2bacaea8b 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -42,6 +42,8 @@ Suffix, Constraint, Expression, + Binary, + Integers, ) import pyomo.environ as pyo @@ -1266,7 +1268,7 @@ def test_nonfloat_constants(self): 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 4 0 0 0 #discrete variables: binary, integer, nonlinear (b,c,o) + 4 0 0 0 0 #discrete variables: binary, integer, nonlinear (b,c,o) 4 4 #nonzeros in Jacobian, obj. gradient 6 4 #max name lengths: constraints, variables 0 0 0 0 0 #common exprs: b,c,o,c1,o1 @@ -2165,6 +2167,136 @@ def test_named_expressions(self): 0 0 1 0 2 0 +""", + OUT.getvalue(), + ) + ) + + def test_discrete_var_tabulation(self): + # This tests an error reported in #3235 + # + # Among other issues, this verifies that nonlinear discrete + # variables are tabulated correctly (header line 7), and that + # integer variables with bounds in {0, 1} are mapped to binary + # variables. + m = ConcreteModel() + m.p1 = Var(bounds=(0.85, 1.15)) + m.p2 = Var(bounds=(0.68, 0.92)) + m.c1 = Var(bounds=(-0.0, 0.7)) + m.c2 = Var(bounds=(-0.0, 0.7)) + m.t1 = Var(within=Binary, bounds=(0, 1)) + m.t2 = Var(within=Binary, bounds=(0, 1)) + m.t3 = Var(within=Binary, bounds=(0, 1)) + m.t4 = Var(within=Binary, bounds=(0, 1)) + m.t5 = Var(within=Integers, bounds=(0, None)) + m.t6 = Var(within=Integers, bounds=(0, None)) + m.x1 = Var(within=Binary) + m.x2 = Var(within=Integers, bounds=(0, 1)) + m.x3 = Var(within=Integers, bounds=(0, None)) + m.const = Constraint(expr=((0.7 - (m.c1*m.t1 + m.c2*m.t2)) <= (m.p1*m.t1 + m.p2*m.t2 + m.p1*m.t4 + m.t6*m.t5))) + m.OBJ = Objective(expr=(m.p1*m.t1 + m.p2*m.t2 + m.p2*m.t3 + m.x1 + m.x2 + m.x3)) + + OUT = io.StringIO() + nl_writer.NLWriter().write(m, OUT, symbolic_solver_labels=True) + + self.assertEqual( + *nl_diff( + """g3 1 1 0 # problem unknown + 13 1 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 + 9 10 4 #nonlinear vars in constraints, objectives, both + 0 0 0 1 #linear network variables; functions; arith, flags + 2 1 2 3 1 #discrete variables: binary, integer, nonlinear (b,c,o) + 9 8 #nonzeros in Jacobian, obj. gradient + 5 2 #max name lengths: constraints, variables + 0 0 0 0 0 #common exprs: b,c,o,c1,o1 +C0 #const +o0 #+ +o16 #- +o0 #+ +o2 #* +v4 #c1 +v2 #t1 +o2 #* +v5 #c2 +v3 #t2 +o16 #- +o54 #sumlist +4 #(n) +o2 #* +v0 #p1 +v2 #t1 +o2 #* +v1 #p2 +v3 #t2 +o2 #* +v0 #p1 +v6 #t4 +o2 #* +v7 #t6 +v8 #t5 +O0 0 #OBJ +o54 #sumlist +3 #(n) +o2 #* +v0 #p1 +v2 #t1 +o2 #* +v1 #p2 +v3 #t2 +o2 #* +v1 #p2 +v9 #t3 +x0 #initial guess +r #1 ranges (rhs's) +1 -0.7 #const +b #13 bounds (on variables) +0 0.85 1.15 #p1 +0 0.68 0.92 #p2 +0 0 1 #t1 +0 0 1 #t2 +0 -0.0 0.7 #c1 +0 -0.0 0.7 #c2 +0 0 1 #t4 +2 0 #t6 +2 0 #t5 +0 0 1 #t3 +0 0 1 #x1 +0 0 1 #x2 +2 0 #x3 +k12 #intermediate Jacobian column lengths +1 +2 +3 +4 +5 +6 +7 +8 +9 +9 +9 +9 +J0 9 #const +0 0 +1 0 +2 0 +3 0 +4 0 +5 0 +6 0 +7 0 +8 0 +G0 8 #OBJ +0 0 +1 0 +2 0 +3 0 +9 0 +10 1 +11 1 +12 1 """, OUT.getvalue(), ) From 25a7344df15fc60378a168a1adfb18496873f375 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 16 Apr 2024 20:25:10 -0600 Subject: [PATCH 1627/1797] NFC: apply black --- pyomo/repn/tests/ampl/test_nlv2.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pyomo/repn/tests/ampl/test_nlv2.py b/pyomo/repn/tests/ampl/test_nlv2.py index 0f2bacaea8b..27d129ca886 100644 --- a/pyomo/repn/tests/ampl/test_nlv2.py +++ b/pyomo/repn/tests/ampl/test_nlv2.py @@ -2193,8 +2193,15 @@ def test_discrete_var_tabulation(self): m.x1 = Var(within=Binary) m.x2 = Var(within=Integers, bounds=(0, 1)) m.x3 = Var(within=Integers, bounds=(0, None)) - m.const = Constraint(expr=((0.7 - (m.c1*m.t1 + m.c2*m.t2)) <= (m.p1*m.t1 + m.p2*m.t2 + m.p1*m.t4 + m.t6*m.t5))) - m.OBJ = Objective(expr=(m.p1*m.t1 + m.p2*m.t2 + m.p2*m.t3 + m.x1 + m.x2 + m.x3)) + m.const = Constraint( + expr=( + (0.7 - (m.c1 * m.t1 + m.c2 * m.t2)) + <= (m.p1 * m.t1 + m.p2 * m.t2 + m.p1 * m.t4 + m.t6 * m.t5) + ) + ) + m.OBJ = Objective( + expr=(m.p1 * m.t1 + m.p2 * m.t2 + m.p2 * m.t3 + m.x1 + m.x2 + m.x3) + ) OUT = io.StringIO() nl_writer.NLWriter().write(m, OUT, symbolic_solver_labels=True) From 6b1e2960e94172a10802326b6e7b848c4c7b4ab4 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 17 Apr 2024 00:36:04 -0400 Subject: [PATCH 1628/1797] fix test pr highs version bug --- .github/workflows/test_pr_and_main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 9eb6d362bb8..711e134e401 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -606,7 +606,7 @@ jobs: shell: bash run: | echo "NOTE: temporarily pinning to highspy pre-release for testing" - $PYTHON_EXE -m pip install --cache-dir cache/pip highspy>=1.7.1.dev1 \ + $PYTHON_EXE -m pip install --cache-dir cache/pip "highspy>=1.7.1.dev1" \ || echo "WARNING: highspy is not available" - name: Set up coverage tracking From 76ba0eb525b5a2037220b84e17edde93715cb599 Mon Sep 17 00:00:00 2001 From: MAiNGO-github <139969768+MAiNGO-github@users.noreply.github.com> Date: Wed, 17 Apr 2024 09:29:14 +0200 Subject: [PATCH 1629/1797] Update pyomo/contrib/appsi/solvers/maingo.py Co-authored-by: Miranda Mundt <55767766+mrmundt@users.noreply.github.com> --- pyomo/contrib/appsi/solvers/maingo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/solvers/maingo.py b/pyomo/contrib/appsi/solvers/maingo.py index 29464e6a876..017841d1b8c 100644 --- a/pyomo/contrib/appsi/solvers/maingo.py +++ b/pyomo/contrib/appsi/solvers/maingo.py @@ -384,7 +384,7 @@ def _postsolve(self, timer: HierarchicalTimer): results.termination_condition = TerminationCondition.optimal if status == maingopy.FEASIBLE_POINT: logger.warning( - "MAiNGO did only find a feasible solution but did not prove its global optimality." + "MAiNGO found a feasible solution but did not prove its global optimality." ) elif status == maingopy.INFEASIBLE: results.termination_condition = TerminationCondition.infeasible From f9ada2946d295da4d53e56c3a7cc2f78e5bbaa1e Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Wed, 17 Apr 2024 15:47:11 +0200 Subject: [PATCH 1630/1797] Restrict y to positive values (MAiNGO cannot handle 1/0) --- pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index c063adc2bfe..58806d1e86c 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -688,7 +688,7 @@ def test_fixed_vars_4( raise unittest.SkipTest m = pe.ConcreteModel() m.x = pe.Var() - m.y = pe.Var() + m.y = pe.Var(bounds=(1e-6, None)) 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) From 42fcd17429cc2b608cc26544dc19c8cd3074bbb9 Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Wed, 17 Apr 2024 15:48:15 +0200 Subject: [PATCH 1631/1797] Restrict y to positive values (MAiNGO cannot handle log(negative)) --- pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 58806d1e86c..5a46c1d3e5b 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -858,7 +858,7 @@ def test_log(self, name: str, opt_class: Type[PersistentSolver], only_child_vars if not opt.available(): raise unittest.SkipTest m = pe.ConcreteModel() - m.x = pe.Var(initialize=1) + m.x = pe.Var(initialize=1, bounds=(1e-6, None)) 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)) From bec9be7e9e3f4c5d221ddb076ddcf7697616182b Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Wed, 17 Apr 2024 15:49:48 +0200 Subject: [PATCH 1632/1797] Exluded MAiNGO for checking unbounded termination criterion --- .../solvers/tests/test_persistent_solvers.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 5a46c1d3e5b..660ba60f26f 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -1090,13 +1090,14 @@ def test_objective_changes( m.obj.sense = pe.maximize opt.config.load_solution = False res = opt.solve(m) - self.assertIn( - res.termination_condition, - { - TerminationCondition.unbounded, - TerminationCondition.infeasibleOrUnbounded, - }, - ) + if not isinstance(opt, MAiNGO): + self.assertIn( + res.termination_condition, + { + TerminationCondition.unbounded, + TerminationCondition.infeasibleOrUnbounded, + }, + ) m.obj.sense = pe.minimize opt.config.load_solution = True m.obj = pe.Objective(expr=m.x * m.y) From 6db9970a41760b64cf5e43ddc73eb3f8eb0aefc0 Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Wed, 17 Apr 2024 15:54:44 +0200 Subject: [PATCH 1633/1797] Create MAiNGOTest class with tighter tolerances to pass tests --- pyomo/contrib/appsi/solvers/maingo.py | 16 ++++++++++++++++ .../solvers/tests/test_persistent_solvers.py | 3 ++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/solvers/maingo.py b/pyomo/contrib/appsi/solvers/maingo.py index 29464e6a876..bff8d6f594e 100644 --- a/pyomo/contrib/appsi/solvers/maingo.py +++ b/pyomo/contrib/appsi/solvers/maingo.py @@ -201,6 +201,9 @@ def _solve(self, timer: HierarchicalTimer): self._mymaingo.set_option("loggingDestination", 2) self._mymaingo.set_log_file_name(config.logfile) + self._mymaingo.set_option("epsilonA", 1e-4) + self._mymaingo.set_option("epsilonR", 1e-4) + self._set_maingo_options() if config.time_limit is not None: self._mymaingo.set_option("maxTime", config.time_limit) @@ -480,3 +483,16 @@ def get_reduced_costs(self, vars_to_load=None): def get_duals(self, cons_to_load=None): raise ValueError("MAiNGO does not support returning Duals") + + + def _set_maingo_options(self): + pass + + +# Solver class with tighter tolerances for testing +class MAiNGOTest(MAiNGO): + def _set_maingo_options(self): + self._mymaingo.set_option("epsilonA", 1e-8) + self._mymaingo.set_option("epsilonR", 1e-8) + self._mymaingo.set_option("deltaIneq", 1e-9) + self._mymaingo.set_option("deltaEq", 1e-9) diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 660ba60f26f..23440065491 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -17,7 +17,8 @@ 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, MAiNGO +from pyomo.contrib.appsi.solvers import Gurobi, Ipopt, Cplex, Cbc, Highs +from pyomo.contrib.appsi.solvers import MAiNGOTest as MAiNGO from typing import Type from pyomo.core.expr.numeric_expr import LinearExpression import os From de7fc5c68360bb1c49d351eca21ffff3d7cf4d60 Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Wed, 17 Apr 2024 15:56:01 +0200 Subject: [PATCH 1634/1797] Exclude duals and RCs for tests with MAiNGO --- .../solvers/tests/test_persistent_solvers.py | 176 ++++++++++-------- 1 file changed, 99 insertions(+), 77 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 23440065491..f50461af373 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -185,14 +185,16 @@ def test_range_constraint( res = opt.solve(m) self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, -1) - duals = opt.get_duals() - self.assertAlmostEqual(duals[m.c], 1) + if not opt_class is MAiNGO: + 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.assertAlmostEqual(m.x.value, 1) - duals = opt.get_duals() - self.assertAlmostEqual(duals[m.c], 1) + if not opt_class is MAiNGO: + 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( @@ -209,9 +211,10 @@ def test_reduced_costs( self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, -1) self.assertAlmostEqual(m.y.value, -2) - rc = opt.get_reduced_costs() - self.assertAlmostEqual(rc[m.x], 3) - self.assertAlmostEqual(rc[m.y], 4) + if not opt_class is MAiNGO: + rc = opt.get_reduced_costs() + self.assertAlmostEqual(rc[m.x], 3) + self.assertAlmostEqual(rc[m.y], 4) @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) def test_reduced_costs2( @@ -226,14 +229,16 @@ def test_reduced_costs2( res = opt.solve(m) self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, -1) - rc = opt.get_reduced_costs() - self.assertAlmostEqual(rc[m.x], 1) + if not opt_class is MAiNGO: + 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.assertAlmostEqual(m.x.value, 1) - rc = opt.get_reduced_costs() - self.assertAlmostEqual(rc[m.x], 1) + if not opt_class is MAiNGO: + 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( @@ -265,9 +270,10 @@ def test_param_changes( 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) - duals = opt.get_duals() - self.assertAlmostEqual(duals[m.c1], (1 + a1 / (a2 - a1))) - self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) + if not opt_class is MAiNGO: + 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( @@ -303,9 +309,10 @@ def test_immutable_param( 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) - duals = opt.get_duals() - self.assertAlmostEqual(duals[m.c1], (1 + a1 / (a2 - a1))) - self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) + if not opt_class is MAiNGO: + 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( @@ -337,9 +344,10 @@ def test_equality( 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) - duals = opt.get_duals() - self.assertAlmostEqual(duals[m.c1], (1 + a1 / (a2 - a1))) - self.assertAlmostEqual(duals[m.c2], -a1 / (a2 - a1)) + if not opt_class is MAiNGO: + 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( @@ -407,9 +415,10 @@ def test_no_objective( 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) - duals = opt.get_duals() - self.assertAlmostEqual(duals[m.c1], 0) - self.assertAlmostEqual(duals[m.c2], 0) + if not opt_class is MAiNGO: + 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( @@ -436,9 +445,10 @@ def test_add_remove_cons( 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) - duals = opt.get_duals() - self.assertAlmostEqual(duals[m.c1], -(1 + a1 / (a2 - a1))) - self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) + if not opt_class is MAiNGO: + 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) @@ -447,10 +457,11 @@ def test_add_remove_cons( 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) - duals = opt.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)) + if not opt_class is MAiNGO: + duals = opt.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) @@ -459,9 +470,10 @@ def test_add_remove_cons( 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) - duals = opt.get_duals() - self.assertAlmostEqual(duals[m.c1], -(1 + a1 / (a2 - a1))) - self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) + if not opt_class is MAiNGO: + 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( @@ -500,14 +512,15 @@ def test_results_infeasible( 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() + if not opt_class is MAiNGO: + 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, only_child_vars_options)) def test_duals(self, name: str, opt_class: Type[PersistentSolver], only_child_vars): @@ -524,13 +537,14 @@ def test_duals(self, name: str, opt_class: Type[PersistentSolver], only_child_va res = opt.solve(m) self.assertAlmostEqual(m.x.value, 1) self.assertAlmostEqual(m.y.value, 1) - duals = opt.get_duals() - self.assertAlmostEqual(duals[m.c1], 0.5) - self.assertAlmostEqual(duals[m.c2], 0.5) + if not opt_class is MAiNGO: + duals = opt.get_duals() + self.assertAlmostEqual(duals[m.c1], 0.5) + self.assertAlmostEqual(duals[m.c2], 0.5) - duals = opt.get_duals(cons_to_load=[m.c1]) - self.assertAlmostEqual(duals[m.c1], 0.5) - self.assertNotIn(m.c2, duals) + duals = opt.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, only_child_vars_options)) def test_mutable_quadratic_coefficient( @@ -778,17 +792,19 @@ def test_mutable_param_with_range( 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) - duals = opt.get_duals() - self.assertAlmostEqual(duals[m.con1], (1 + a1 / (a2 - a1)), 6) - self.assertAlmostEqual(duals[m.con2], -a1 / (a2 - a1), 6) + if not opt_class is MAiNGO: + 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) - duals = opt.get_duals() - self.assertAlmostEqual(duals[m.con1], (1 + a1 / (a2 - a1)), 6) - self.assertAlmostEqual(duals[m.con2], -a1 / (a2 - a1), 6) + if not opt_class is MAiNGO: + 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( @@ -986,24 +1002,25 @@ def test_solution_loader( 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) + if not opt_class is MAiNGO: + 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, only_child_vars_options)) def test_time_limit( @@ -1373,7 +1390,8 @@ def test_param_updates(self, name: str, opt_class: Type[PersistentSolver]): 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) + if not opt_class is MAiNGO: + 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: @@ -1385,8 +1403,9 @@ def test_param_updates(self, name: str, opt_class: Type[PersistentSolver]): 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)) + if not opt_class is MAiNGO: + 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[PersistentSolver]): @@ -1397,11 +1416,14 @@ def test_load_solutions(self, name: str, opt_class: Type[PersistentSolver]): 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) + if not opt_class is MAiNGO: + 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) + if not opt_class is MAiNGO: + self.assertNotIn(m.c, m.dual) m.solutions.load_from(res) self.assertAlmostEqual(m.x.value, -1) - self.assertAlmostEqual(m.dual[m.c], 1) + if not opt_class is MAiNGO: + self.assertAlmostEqual(m.dual[m.c], 1) From 334b06762bbae40d954ae0ca54720f4f6c448893 Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Wed, 17 Apr 2024 15:56:31 +0200 Subject: [PATCH 1635/1797] MAiNGOTest class in __init__ --- pyomo/contrib/appsi/solvers/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/solvers/__init__.py b/pyomo/contrib/appsi/solvers/__init__.py index 352571b98f8..c1ebdf28780 100644 --- a/pyomo/contrib/appsi/solvers/__init__.py +++ b/pyomo/contrib/appsi/solvers/__init__.py @@ -15,4 +15,4 @@ from .cplex import Cplex from .highs import Highs from .wntr import Wntr, WntrResults -from .maingo import MAiNGO +from .maingo import MAiNGO, MAiNGOTest From a1e6b92c7518027f8234069acedc9dac86d8b04a Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Wed, 17 Apr 2024 15:57:07 +0200 Subject: [PATCH 1636/1797] Add copyright statement --- pyomo/contrib/appsi/solvers/maingo.py | 11 +++++++++++ pyomo/contrib/appsi/solvers/maingo_solvermodel.py | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/pyomo/contrib/appsi/solvers/maingo.py b/pyomo/contrib/appsi/solvers/maingo.py index bff8d6f594e..d48e9874712 100644 --- a/pyomo/contrib/appsi/solvers/maingo.py +++ b/pyomo/contrib/appsi/solvers/maingo.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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 collections import namedtuple import logging import math diff --git a/pyomo/contrib/appsi/solvers/maingo_solvermodel.py b/pyomo/contrib/appsi/solvers/maingo_solvermodel.py index 4abc53ae290..686d7c54657 100644 --- a/pyomo/contrib/appsi/solvers/maingo_solvermodel.py +++ b/pyomo/contrib/appsi/solvers/maingo_solvermodel.py @@ -1,3 +1,14 @@ +# ___________________________________________________________________________ +# +# 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.common.dependencies import attempt_import From 1fab90b84754bd652263a8eed467f081d0de603a Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Wed, 17 Apr 2024 15:58:03 +0200 Subject: [PATCH 1637/1797] Set default for ConfigValues --- pyomo/contrib/appsi/solvers/maingo.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/maingo.py b/pyomo/contrib/appsi/solvers/maingo.py index d48e9874712..ee85d4549a5 100644 --- a/pyomo/contrib/appsi/solvers/maingo.py +++ b/pyomo/contrib/appsi/solvers/maingo.py @@ -98,13 +98,11 @@ def __init__( visibility=visibility, ) - self.declare("logfile", ConfigValue(domain=str)) - self.declare("solver_output_logger", ConfigValue()) - self.declare("log_level", ConfigValue(domain=NonNegativeInt)) - - self.logfile = "" - self.solver_output_logger = logger - self.log_level = logging.INFO + self.declare("logfile", ConfigValue(domain=str, default="")) + self.declare("solver_output_logger", ConfigValue(default=logger)) + self.declare( + "log_level", ConfigValue(domain=NonNegativeInt, default=logging.INFO) + ) class MAiNGOSolutionLoader(PersistentSolutionLoader): From 5b70135f03575af6c1ef6ddce6bd98c12846194e Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Wed, 17 Apr 2024 15:58:30 +0200 Subject: [PATCH 1638/1797] Remove check for Python version --- pyomo/contrib/appsi/solvers/maingo.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/maingo.py b/pyomo/contrib/appsi/solvers/maingo.py index ee85d4549a5..0886ce21c75 100644 --- a/pyomo/contrib/appsi/solvers/maingo.py +++ b/pyomo/contrib/appsi/solvers/maingo.py @@ -151,15 +151,9 @@ def available(self): return self._available def version(self): - # Check if Python >= 3.8 - if sys.version_info.major >= 3 and sys.version_info.minor >= 8: - from importlib.metadata import version + import pkg_resources - version = version('maingopy') - else: - import pkg_resources - - version = pkg_resources.get_distribution('maingopy').version + version = pkg_resources.get_distribution('maingopy').version return tuple(int(k) for k in version.split('.')) From 55b7dff375cc9c82cd2d425698c521090711a816 Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Wed, 17 Apr 2024 16:00:41 +0200 Subject: [PATCH 1639/1797] Add missing functionalities and fix bugs --- pyomo/contrib/appsi/solvers/maingo.py | 81 ++++++++++++++----- .../appsi/solvers/maingo_solvermodel.py | 51 ++++++++---- 2 files changed, 97 insertions(+), 35 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/maingo.py b/pyomo/contrib/appsi/solvers/maingo.py index 0886ce21c75..eabfdd36267 100644 --- a/pyomo/contrib/appsi/solvers/maingo.py +++ b/pyomo/contrib/appsi/solvers/maingo.py @@ -120,6 +120,7 @@ def __init__(self, solver): super(MAiNGOResults, self).__init__() self.wallclock_time = None self.cpu_time = None + self.globally_optimal = None self.solution_loader = MAiNGOSolutionLoader(solver=solver) @@ -228,9 +229,14 @@ def solve(self, model, timer: HierarchicalTimer = None): self._last_results_object.solution_loader.invalidate() if timer is None: timer = HierarchicalTimer() - timer.start("set_instance") - self.set_instance(model) - timer.stop("set_instance") + 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: @@ -285,7 +291,7 @@ def _process_domain_and_bounds(self, var): return lb, ub, vtype def _add_variables(self, variables: List[_GeneralVarData]): - for ndx, var in enumerate(variables): + for var in variables: varname = self._symbol_map.getSymbol(var, self._labeler) lb, ub, vtype = self._process_domain_and_bounds(var) self._maingo_vars.append( @@ -331,10 +337,11 @@ def set_instance(self, model): con_list=self._cons, objective=self._objective, idmap=self._pyomo_var_to_solver_var_id_map, + logger=logger, ) def _add_constraints(self, cons: List[_GeneralConstraintData]): - self._cons = cons + self._cons += cons def _add_sos_constraints(self, cons: List[_SOSConstraintData]): if len(cons) >= 1: @@ -344,7 +351,8 @@ def _add_sos_constraints(self, cons: List[_SOSConstraintData]): pass def _remove_constraints(self, cons: List[_GeneralConstraintData]): - pass + for con in cons: + self._cons.remove(con) def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): if len(cons) >= 1: @@ -354,28 +362,48 @@ def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): pass def _remove_variables(self, variables: List[_GeneralVarData]): - pass + removed_maingo_vars = [] + for var in variables: + varname = self._symbol_map.getSymbol(var, self._labeler) + del self._maingo_vars[self._pyomo_var_to_solver_var_id_map[id(var)]] + removed_maingo_vars += [self._pyomo_var_to_solver_var_id_map[id(var)]] + del self._pyomo_var_to_solver_var_id_map[id(var)] + + for pyomo_var, maingo_var_id in self._pyomo_var_to_solver_var_id_map.items(): + # How many variables before current var where removed? + num_removed = 0 + for removed_var in removed_maingo_vars: + if removed_var <= maingo_var_id: + num_removed += 1 + self._pyomo_var_to_solver_var_id_map[pyomo_var] = ( + maingo_var_id - num_removed + ) def _remove_params(self, params: List[_ParamData]): pass def _update_variables(self, variables: List[_GeneralVarData]): - pass + for var in variables: + if id(var) not in self._pyomo_var_to_solver_var_id_map: + raise ValueError( + 'The Var provided to update_var needs to be added first: {0}'.format( + var + ) + ) + lb, ub, vtype = self._process_domain_and_bounds(var) + self._maingo_vars[self._pyomo_var_to_solver_var_id_map[id(var)]] = ( + MaingoVar(name=var.name, type=vtype, lb=lb, ub=ub, init=var.value) + ) def update_params(self): - pass + vars = [var[0] for var in self._vars.values()] + self._update_variables(vars) def _set_objective(self, obj): - if obj is None: - raise NotImplementedError( - "MAiNGO needs a objective. Please set a dummy objective." - ) - else: - if not obj.sense in {minimize, maximize}: - raise ValueError( - "Objective sense is not recognized: {0}".format(obj.sense) - ) - self._objective = obj + + if not obj.sense in {minimize, maximize}: + raise ValueError("Objective sense is not recognized: {0}".format(obj.sense)) + self._objective = obj def _postsolve(self, timer: HierarchicalTimer): config = self.config @@ -388,7 +416,9 @@ def _postsolve(self, timer: HierarchicalTimer): if status in {maingopy.GLOBALLY_OPTIMAL, maingopy.FEASIBLE_POINT}: results.termination_condition = TerminationCondition.optimal + results.globally_optimal = True if status == maingopy.FEASIBLE_POINT: + results.globally_optimal = False logger.warning( "MAiNGO did only find a feasible solution but did not prove its global optimality." ) @@ -425,8 +455,8 @@ def _postsolve(self, timer: HierarchicalTimer): timer.start("load solution") if config.load_solution: - if not results.best_feasible_objective is None: - if results.termination_condition != TerminationCondition.optimal: + if results.termination_condition is TerminationCondition.optimal: + if not results.globally_optimal: logger.warning( "Loading a feasible but suboptimal solution. " "Please set load_solution=False and check " @@ -487,6 +517,15 @@ def get_reduced_costs(self, vars_to_load=None): def get_duals(self, cons_to_load=None): raise ValueError("MAiNGO does not support returning Duals") + def update(self, timer: HierarchicalTimer = None): + super(MAiNGO, self).update(timer=timer) + self._solver_model = maingo_solvermodel.SolverModel( + var_list=self._maingo_vars, + con_list=self._cons, + objective=self._objective, + idmap=self._pyomo_var_to_solver_var_id_map, + logger=logger, + ) def _set_maingo_options(self): pass diff --git a/pyomo/contrib/appsi/solvers/maingo_solvermodel.py b/pyomo/contrib/appsi/solvers/maingo_solvermodel.py index 686d7c54657..ca746c4a9b7 100644 --- a/pyomo/contrib/appsi/solvers/maingo_solvermodel.py +++ b/pyomo/contrib/appsi/solvers/maingo_solvermodel.py @@ -13,6 +13,7 @@ from pyomo.common.dependencies import attempt_import from pyomo.core.base.var import ScalarVar +from pyomo.core.base.expression import ScalarExpression import pyomo.core.expr.expr_common as common import pyomo.core.expr as EXPR from pyomo.core.expr.numvalue import ( @@ -178,19 +179,14 @@ def visiting_potential_leaf(self, node): return True, maingo_var def _monomial_to_maingo(self, node): - if node.__class__ is ScalarVar: - var = node - const = 1 - else: - const, var = node.args - maingo_var_id = self.idmap[id(var)] - maingo_var = self.variables[maingo_var_id] + const, var = node.args if const.__class__ not in native_types: const = value(const) if var.is_fixed(): return const * var.value if not const: return 0 + maingo_var = self._var_to_maingo(var) if const in _plusMinusOne: if const < 0: return -maingo_var @@ -198,12 +194,25 @@ def _monomial_to_maingo(self, node): return maingo_var return const * maingo_var + def _var_to_maingo(self, var): + maingo_var_id = self.idmap[id(var)] + maingo_var = self.variables[maingo_var_id] + return maingo_var + def _linear_to_maingo(self, node): values = [ ( self._monomial_to_maingo(arg) - if (arg.__class__ in {EXPR.MonomialTermExpression, ScalarVar}) - else (value(arg)) + if (arg.__class__ is EXPR.MonomialTermExpression) + else ( + value(arg) + if arg.__class__ in native_numeric_types + else ( + self._var_to_maingo(arg) + if arg.is_variable_type() + else value(arg) + ) + ) ) for arg in node.args ] @@ -211,17 +220,25 @@ def _linear_to_maingo(self, node): class SolverModel(maingopy.MAiNGOmodel): - def __init__(self, var_list, objective, con_list, idmap): + def __init__(self, var_list, objective, con_list, idmap, logger): maingopy.MAiNGOmodel.__init__(self) self._var_list = var_list self._con_list = con_list self._objective = objective self._idmap = idmap + self._logger = logger + self._no_objective = False + + if self._objective is None: + self._logger.warning("No objective given, setting a dummy objective of 1.") + self._no_objective = True def build_maingo_objective(self, obj, visitor): + if self._no_objective: + return visitor.variables[-1] maingo_obj = visitor.dfs_postorder_stack(obj.expr) if obj.sense == maximize: - maingo_obj *= -1 + return -1 * maingo_obj return maingo_obj def build_maingo_constraints(self, cons, visitor): @@ -235,7 +252,7 @@ def build_maingo_constraints(self, cons, visitor): ineqs += [visitor.dfs_postorder_stack(con.lower - con.body)] elif con.has_ub(): ineqs += [visitor.dfs_postorder_stack(con.body - con.upper)] - elif con.has_ub(): + elif con.has_lb(): ineqs += [visitor.dfs_postorder_stack(con.lower - con.body)] else: raise ValueError( @@ -245,18 +262,24 @@ def build_maingo_constraints(self, cons, visitor): return eqs, ineqs def get_variables(self): - return [ + vars = [ maingopy.OptimizationVariable( maingopy.Bounds(var.lb, var.ub), var.type, var.name ) for var in self._var_list ] + if self._no_objective: + vars += [maingopy.OptimizationVariable(maingopy.Bounds(1, 1), "dummy_obj")] + return vars def get_initial_point(self): - return [ + initial = [ var.init if not var.init is None else (var.lb + var.ub) / 2.0 for var in self._var_list ] + if self._no_objective: + initial += [1] + return initial def evaluate(self, maingo_vars): visitor = ToMAiNGOVisitor(maingo_vars, self._idmap) From 380f32cb5b1a044002c7c93f5fac394c5ca9c3e2 Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Thu, 18 Apr 2024 09:28:54 +0200 Subject: [PATCH 1640/1797] Register MAiNGO in SolverFactory --- pyomo/contrib/appsi/plugins.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/plugins.py b/pyomo/contrib/appsi/plugins.py index fbe81484eba..3e1b639ce3b 100644 --- a/pyomo/contrib/appsi/plugins.py +++ b/pyomo/contrib/appsi/plugins.py @@ -11,7 +11,7 @@ from pyomo.common.extensions import ExtensionBuilderFactory from .base import SolverFactory -from .solvers import Gurobi, Ipopt, Cbc, Cplex, Highs +from .solvers import Gurobi, Ipopt, Cbc, Cplex, Highs, MAiNGO from .build import AppsiBuilder @@ -30,3 +30,6 @@ def load(): SolverFactory.register(name='highs', doc='Automated persistent interface to Highs')( Highs ) + SolverFactory.register( + name='maingo', doc='Automated persistent interface to MAiNGO' + )(MAiNGO) From cf560a626c56aac304c269c02c06d783c42957c0 Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Thu, 18 Apr 2024 09:31:50 +0200 Subject: [PATCH 1641/1797] Reformulate confusing comment --- pyomo/contrib/appsi/solvers/maingo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/solvers/maingo.py b/pyomo/contrib/appsi/solvers/maingo.py index d542542f543..f95a943bed3 100644 --- a/pyomo/contrib/appsi/solvers/maingo.py +++ b/pyomo/contrib/appsi/solvers/maingo.py @@ -369,8 +369,8 @@ def _remove_variables(self, variables: List[_GeneralVarData]): removed_maingo_vars += [self._pyomo_var_to_solver_var_id_map[id(var)]] del self._pyomo_var_to_solver_var_id_map[id(var)] + # Update _pyomo_var_to_solver_var_id_map to account for removed variables for pyomo_var, maingo_var_id in self._pyomo_var_to_solver_var_id_map.items(): - # How many variables before current var where removed? num_removed = 0 for removed_var in removed_maingo_vars: if removed_var <= maingo_var_id: From 3e477c929883d8a555fdde8afeb99b435f0be829 Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Thu, 18 Apr 2024 09:33:45 +0200 Subject: [PATCH 1642/1797] Skip tests with problematic log and 1/x --- .../appsi/solvers/tests/test_persistent_solvers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index f50461af373..b569b305a07 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -699,11 +699,11 @@ def test_fixed_vars_4( ): opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) opt.update_config.treat_fixed_vars_as_params = True - if not opt.available(): + if not opt.available() or opt_class in MAiNGO: raise unittest.SkipTest m = pe.ConcreteModel() m.x = pe.Var() - m.y = pe.Var(bounds=(1e-6, None)) + 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) @@ -872,10 +872,10 @@ def test_exp(self, name: str, opt_class: Type[PersistentSolver], only_child_vars @parameterized.expand(input=_load_tests(nlp_solvers, only_child_vars_options)) 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(): + if not opt.available() or opt_class in MAiNGO: raise unittest.SkipTest m = pe.ConcreteModel() - m.x = pe.Var(initialize=1, bounds=(1e-6, None)) + 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)) From d892473bca89ae62fd69b53eb0585b769cd91a60 Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Thu, 18 Apr 2024 09:42:04 +0200 Subject: [PATCH 1643/1797] Add maingo to options --- pyomo/contrib/appsi/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index 201e5975ac9..13a841437ac 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -1665,7 +1665,7 @@ def license_is_valid(self) -> bool: @property def options(self): - for solver_name in ['gurobi', 'ipopt', 'cplex', 'cbc', 'highs']: + for solver_name in ['gurobi', 'ipopt', 'cplex', 'cbc', 'highs', 'maingo']: if hasattr(self, solver_name + '_options'): return getattr(self, solver_name + '_options') raise NotImplementedError('Could not find the correct options') @@ -1673,7 +1673,7 @@ def options(self): @options.setter def options(self, val): found = False - for solver_name in ['gurobi', 'ipopt', 'cplex', 'cbc', 'highs']: + for solver_name in ['gurobi', 'ipopt', 'cplex', 'cbc', 'highs', 'maingo']: if hasattr(self, solver_name + '_options'): setattr(self, solver_name + '_options', val) found = True From d026ee135f72788536b0586e6815fcc561a0ba86 Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Thu, 18 Apr 2024 09:42:30 +0200 Subject: [PATCH 1644/1797] Rewrite check for MAiNGO --- .../solvers/tests/test_persistent_solvers.py | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index b569b305a07..2207ba70f4e 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -185,14 +185,14 @@ def test_range_constraint( res = opt.solve(m) self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, -1) - if not opt_class is MAiNGO: + if opt_class != MAiNGO: 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.assertAlmostEqual(m.x.value, 1) - if not opt_class is MAiNGO: + if opt_class != MAiNGO: duals = opt.get_duals() self.assertAlmostEqual(duals[m.c], 1) @@ -211,7 +211,7 @@ def test_reduced_costs( self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, -1) self.assertAlmostEqual(m.y.value, -2) - if not opt_class is MAiNGO: + if opt_class != MAiNGO: rc = opt.get_reduced_costs() self.assertAlmostEqual(rc[m.x], 3) self.assertAlmostEqual(rc[m.y], 4) @@ -229,14 +229,14 @@ def test_reduced_costs2( res = opt.solve(m) self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(m.x.value, -1) - if not opt_class is MAiNGO: + if opt_class != MAiNGO: 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.assertAlmostEqual(m.x.value, 1) - if not opt_class is MAiNGO: + if opt_class != MAiNGO: rc = opt.get_reduced_costs() self.assertAlmostEqual(rc[m.x], 1) @@ -270,7 +270,7 @@ def test_param_changes( 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) - if not opt_class is MAiNGO: + if opt_class != MAiNGO: duals = opt.get_duals() self.assertAlmostEqual(duals[m.c1], (1 + a1 / (a2 - a1))) self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) @@ -309,7 +309,7 @@ def test_immutable_param( 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) - if not opt_class is MAiNGO: + if opt_class != MAiNGO: duals = opt.get_duals() self.assertAlmostEqual(duals[m.c1], (1 + a1 / (a2 - a1))) self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) @@ -344,7 +344,7 @@ def test_equality( 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) - if not opt_class is MAiNGO: + if opt_class != MAiNGO: duals = opt.get_duals() self.assertAlmostEqual(duals[m.c1], (1 + a1 / (a2 - a1))) self.assertAlmostEqual(duals[m.c2], -a1 / (a2 - a1)) @@ -415,7 +415,7 @@ def test_no_objective( 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) - if not opt_class is MAiNGO: + if opt_class != MAiNGO: duals = opt.get_duals() self.assertAlmostEqual(duals[m.c1], 0) self.assertAlmostEqual(duals[m.c2], 0) @@ -445,7 +445,7 @@ def test_add_remove_cons( 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) - if not opt_class is MAiNGO: + if opt_class != MAiNGO: duals = opt.get_duals() self.assertAlmostEqual(duals[m.c1], -(1 + a1 / (a2 - a1))) self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) @@ -457,7 +457,7 @@ def test_add_remove_cons( 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) - if not opt_class is MAiNGO: + if opt_class != MAiNGO: duals = opt.get_duals() self.assertAlmostEqual(duals[m.c1], -(1 + a1 / (a3 - a1))) self.assertAlmostEqual(duals[m.c2], 0) @@ -470,7 +470,7 @@ def test_add_remove_cons( 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) - if not opt_class is MAiNGO: + if opt_class != MAiNGO: duals = opt.get_duals() self.assertAlmostEqual(duals[m.c1], -(1 + a1 / (a2 - a1))) self.assertAlmostEqual(duals[m.c2], a1 / (a2 - a1)) @@ -512,7 +512,7 @@ def test_results_infeasible( RuntimeError, '.*does not currently have a valid solution.*' ): res.solution_loader.load_vars() - if not opt_class is MAiNGO: + if opt_class != MAiNGO: with self.assertRaisesRegex( RuntimeError, '.*does not currently have valid duals.*' ): @@ -537,7 +537,7 @@ def test_duals(self, name: str, opt_class: Type[PersistentSolver], only_child_va res = opt.solve(m) self.assertAlmostEqual(m.x.value, 1) self.assertAlmostEqual(m.y.value, 1) - if not opt_class is MAiNGO: + if opt_class != MAiNGO: duals = opt.get_duals() self.assertAlmostEqual(duals[m.c1], 0.5) self.assertAlmostEqual(duals[m.c2], 0.5) @@ -699,7 +699,7 @@ def test_fixed_vars_4( ): opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) opt.update_config.treat_fixed_vars_as_params = True - if not opt.available() or opt_class in MAiNGO: + if not opt.available() or opt_class == MAiNGO: raise unittest.SkipTest m = pe.ConcreteModel() m.x = pe.Var() @@ -792,7 +792,7 @@ def test_mutable_param_with_range( 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) - if not opt_class is MAiNGO: + if opt_class != MAiNGO: duals = opt.get_duals() self.assertAlmostEqual(duals[m.con1], (1 + a1 / (a2 - a1)), 6) self.assertAlmostEqual(duals[m.con2], -a1 / (a2 - a1), 6) @@ -801,7 +801,7 @@ def test_mutable_param_with_range( 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) - if not opt_class is MAiNGO: + if opt_class != MAiNGO: duals = opt.get_duals() self.assertAlmostEqual(duals[m.con1], (1 + a1 / (a2 - a1)), 6) self.assertAlmostEqual(duals[m.con2], -a1 / (a2 - a1), 6) @@ -872,7 +872,7 @@ def test_exp(self, name: str, opt_class: Type[PersistentSolver], only_child_vars @parameterized.expand(input=_load_tests(nlp_solvers, only_child_vars_options)) 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() or opt_class in MAiNGO: + if not opt.available() or opt_class == MAiNGO: raise unittest.SkipTest m = pe.ConcreteModel() m.x = pe.Var(initialize=1) @@ -1002,7 +1002,7 @@ def test_solution_loader( self.assertNotIn(m.x, primals) self.assertIn(m.y, primals) self.assertAlmostEqual(primals[m.y], 1) - if not opt_class is MAiNGO: + if opt_class != MAiNGO: reduced_costs = res.solution_loader.get_reduced_costs() self.assertIn(m.x, reduced_costs) self.assertIn(m.y, reduced_costs) @@ -1108,7 +1108,7 @@ def test_objective_changes( m.obj.sense = pe.maximize opt.config.load_solution = False res = opt.solve(m) - if not isinstance(opt, MAiNGO): + if opt_class != MAiNGO: self.assertIn( res.termination_condition, { @@ -1390,7 +1390,7 @@ def test_param_updates(self, name: str, opt_class: Type[PersistentSolver]): 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)) - if not opt_class is MAiNGO: + if opt_class != MAiNGO: m.dual = pe.Suffix(direction=pe.Suffix.IMPORT) params_to_test = [(1, -1, 2, 1), (1, -2, 2, 1), (1, -1, 3, 1)] @@ -1403,7 +1403,7 @@ def test_param_updates(self, name: str, opt_class: Type[PersistentSolver]): 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) - if not opt_class is MAiNGO: + if opt_class != MAiNGO: self.assertAlmostEqual(m.dual[m.c1], (1 + a1 / (a2 - a1))) self.assertAlmostEqual(m.dual[m.c2], a1 / (a2 - a1)) @@ -1416,14 +1416,14 @@ def test_load_solutions(self, name: str, opt_class: Type[PersistentSolver]): m.x = pe.Var() m.obj = pe.Objective(expr=m.x) m.c = pe.Constraint(expr=(-1, m.x, 1)) - if not opt_class is MAiNGO: + if opt_class != MAiNGO: 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) - if not opt_class is MAiNGO: + if opt_class != MAiNGO: self.assertNotIn(m.c, m.dual) m.solutions.load_from(res) self.assertAlmostEqual(m.x.value, -1) - if not opt_class is MAiNGO: + if opt_class != MAiNGO: self.assertAlmostEqual(m.dual[m.c], 1) From cf3c9da2a17cff71953e2ced4f24f999835542ba Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Thu, 18 Apr 2024 12:26:32 +0200 Subject: [PATCH 1645/1797] Add ConfigDict for tolerances, change test tolerances --- pyomo/contrib/appsi/solvers/__init__.py | 2 +- pyomo/contrib/appsi/solvers/maingo.py | 60 ++++++++++++++----- .../solvers/tests/test_persistent_solvers.py | 27 ++++----- 3 files changed, 58 insertions(+), 31 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/__init__.py b/pyomo/contrib/appsi/solvers/__init__.py index c1ebdf28780..352571b98f8 100644 --- a/pyomo/contrib/appsi/solvers/__init__.py +++ b/pyomo/contrib/appsi/solvers/__init__.py @@ -15,4 +15,4 @@ from .cplex import Cplex from .highs import Highs from .wntr import Wntr, WntrResults -from .maingo import MAiNGO, MAiNGOTest +from .maingo import MAiNGO diff --git a/pyomo/contrib/appsi/solvers/maingo.py b/pyomo/contrib/appsi/solvers/maingo.py index f95a943bed3..944673be53d 100644 --- a/pyomo/contrib/appsi/solvers/maingo.py +++ b/pyomo/contrib/appsi/solvers/maingo.py @@ -25,7 +25,12 @@ ) from pyomo.contrib.appsi.cmodel import cmodel, cmodel_available from pyomo.common.collections import ComponentMap -from pyomo.common.config import ConfigValue, NonNegativeInt +from pyomo.common.config import ( + ConfigValue, + ConfigDict, + NonNegativeInt, + NonNegativeFloat, +) from pyomo.common.dependencies import attempt_import from pyomo.common.errors import PyomoException from pyomo.common.log import LogStream @@ -97,7 +102,41 @@ def __init__( implicit_domain=implicit_domain, visibility=visibility, ) + self.tolerances: ConfigDict = self.declare( + 'tolerances', ConfigDict(implicit=True) + ) + + self.tolerances.epsilonA: Optional[float] = self.tolerances.declare( + 'epsilonA', + ConfigValue( + domain=NonNegativeFloat, + default=1e-4, + description="Absolute optimality tolerance", + ), + ) + self.tolerances.epsilonR: Optional[float] = self.tolerances.declare( + 'epsilonR', + ConfigValue( + domain=NonNegativeFloat, + default=1e-4, + description="Relative optimality tolerance", + ), + ) + self.tolerances.deltaEq: Optional[float] = self.tolerances.declare( + 'deltaEq', + ConfigValue( + domain=NonNegativeFloat, default=1e-6, description="Equality tolerance" + ), + ) + self.tolerances.deltaIneq: Optional[float] = self.tolerances.declare( + 'deltaIneq', + ConfigValue( + domain=NonNegativeFloat, + default=1e-6, + description="Inequality tolerance", + ), + ) self.declare("logfile", ConfigValue(domain=str, default="")) self.declare("solver_output_logger", ConfigValue(default=logger)) self.declare( @@ -205,9 +244,10 @@ def _solve(self, timer: HierarchicalTimer): self._mymaingo.set_option("loggingDestination", 2) self._mymaingo.set_log_file_name(config.logfile) - self._mymaingo.set_option("epsilonA", 1e-4) - self._mymaingo.set_option("epsilonR", 1e-4) - self._set_maingo_options() + self._mymaingo.set_option("epsilonA", config.tolerances.epsilonA) + self._mymaingo.set_option("epsilonR", config.tolerances.epsilonR) + self._mymaingo.set_option("deltaEq", config.tolerances.deltaEq) + self._mymaingo.set_option("deltaIneq", config.tolerances.deltaIneq) if config.time_limit is not None: self._mymaingo.set_option("maxTime", config.time_limit) @@ -526,15 +566,3 @@ def update(self, timer: HierarchicalTimer = None): idmap=self._pyomo_var_to_solver_var_id_map, logger=logger, ) - - def _set_maingo_options(self): - pass - - -# Solver class with tighter tolerances for testing -class MAiNGOTest(MAiNGO): - def _set_maingo_options(self): - self._mymaingo.set_option("epsilonA", 1e-8) - self._mymaingo.set_option("epsilonR", 1e-8) - self._mymaingo.set_option("deltaIneq", 1e-9) - self._mymaingo.set_option("deltaEq", 1e-9) diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 2207ba70f4e..d6df1710a03 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -17,8 +17,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 MAiNGOTest as MAiNGO +from pyomo.contrib.appsi.solvers import Gurobi, Ipopt, Cplex, Cbc, Highs, MAiNGO from typing import Type from pyomo.core.expr.numeric_expr import LinearExpression import os @@ -866,8 +865,8 @@ def test_exp(self, name: str, opt_class: Type[PersistentSolver], only_child_vars 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) + self.assertAlmostEqual(m.x.value, -0.42630274815985264, 6) + self.assertAlmostEqual(m.y.value, 0.6529186341994245, 6) @parameterized.expand(input=_load_tests(nlp_solvers, only_child_vars_options)) def test_log(self, name: str, opt_class: Type[PersistentSolver], only_child_vars): @@ -1212,19 +1211,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.best_feasible_objective, 0, 6) m.x.fix(1) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 1) + self.assertAlmostEqual(res.best_feasible_objective, 1, 6) 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.best_feasible_objective, 0) + self.assertAlmostEqual(res.best_feasible_objective, 0, 6) m.x.fix(1) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 1) + self.assertAlmostEqual(res.best_feasible_objective, 1, 6) @parameterized.expand(input=_load_tests(mip_solvers, only_child_vars_options)) def test_with_gdp( @@ -1248,16 +1247,16 @@ def test_with_gdp( pe.TransformationFactory("gdp.bigm").apply_to(m) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 1) - self.assertAlmostEqual(m.x.value, 0) - self.assertAlmostEqual(m.y.value, 1) + self.assertAlmostEqual(res.best_feasible_objective, 1, 6) + self.assertAlmostEqual(m.x.value, 0, 6) + self.assertAlmostEqual(m.y.value, 1, 6) 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) + self.assertAlmostEqual(res.best_feasible_objective, 1, 6) + self.assertAlmostEqual(m.x.value, 0, 6) + self.assertAlmostEqual(m.y.value, 1, 6) @parameterized.expand(input=all_solvers) def test_variables_elsewhere(self, name: str, opt_class: Type[PersistentSolver]): From e0b6277f72fb00347d8ed482a534e5471ffd9688 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 18 Apr 2024 07:20:31 -0600 Subject: [PATCH 1646/1797] Attempt installing mpich first --- .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 a15240194f2..ce7d03f6898 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==3.1.5 + PACKAGES: mpich mpi4py - os: ubuntu-latest python: '3.10' From d4aced699a5ac0466ebcb375a05249bfabf25901 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 18 Apr 2024 07:51:59 -0600 Subject: [PATCH 1647/1797] Switch to openmpi --- .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 ce7d03f6898..1885f6a00e2 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: mpich mpi4py + PACKAGES: openmpi mpi4py - os: ubuntu-latest python: '3.10' From 13830955da64e801bd61a583582783e48243c742 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 18 Apr 2024 08:47:04 -0600 Subject: [PATCH 1648/1797] Remove unused import --- pyomo/opt/results/problem.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyomo/opt/results/problem.py b/pyomo/opt/results/problem.py index 34da8f91918..a8eca1e3b41 100644 --- a/pyomo/opt/results/problem.py +++ b/pyomo/opt/results/problem.py @@ -12,7 +12,6 @@ import enum from pyomo.opt.results.container import MapContainer -from pyomo.common.deprecation import deprecated, deprecation_warning from pyomo.common.enums import ExtendedEnumType, ObjectiveSense From 0e68f4bab1adc62e29652f2005d787fb31fc1ec7 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 18 Apr 2024 08:58:17 -0600 Subject: [PATCH 1649/1797] nlv2: simplify / improve performance of binary domain check --- pyomo/repn/plugins/nl_writer.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyomo/repn/plugins/nl_writer.py b/pyomo/repn/plugins/nl_writer.py index ab74f0ab44d..207846787fd 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -113,7 +113,7 @@ TOL = 1e-8 inf = float('inf') minus_inf = -inf -zero_one = {0, 1} +allowable_binary_var_bounds = {(0,0), (0,1), (1,1)} _CONSTANT = ExprType.CONSTANT _MONOMIAL = ExprType.MONOMIAL @@ -883,10 +883,9 @@ def write(self, model): elif v.is_binary(): binary_vars.add(_id) elif v.is_integer(): - bnd = var_bounds[_id] # Note: integer variables whose bounds are in {0, 1} # should be classified as binary - if bnd[1] in zero_one and bnd[0] in zero_one: + if var_bounds[_id] in allowable_binary_var_bounds: binary_vars.add(_id) else: integer_vars.add(_id) From ee05e18625ba7193e6cf739bccd66cb773fc7d2d Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 18 Apr 2024 09:05:46 -0600 Subject: [PATCH 1650/1797] NFC: apply black --- 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 207846787fd..86da2a3622b 100644 --- a/pyomo/repn/plugins/nl_writer.py +++ b/pyomo/repn/plugins/nl_writer.py @@ -113,7 +113,7 @@ TOL = 1e-8 inf = float('inf') minus_inf = -inf -allowable_binary_var_bounds = {(0,0), (0,1), (1,1)} +allowable_binary_var_bounds = {(0, 0), (0, 1), (1, 1)} _CONSTANT = ExprType.CONSTANT _MONOMIAL = ExprType.MONOMIAL From 232be203e8669d174b6aaa97ecfa0ddd1d639eeb Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 18 Apr 2024 09:52:27 -0600 Subject: [PATCH 1651/1797] Remove duplicate imports --- pyomo/core/base/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyomo/core/base/__init__.py b/pyomo/core/base/__init__.py index 3d1347659db..341af677b0e 100644 --- a/pyomo/core/base/__init__.py +++ b/pyomo/core/base/__init__.py @@ -84,7 +84,6 @@ from pyomo.core.base.boolean_var import ( BooleanVar, BooleanVarData, - BooleanVarData, BooleanVarList, ScalarBooleanVar, ) @@ -145,7 +144,7 @@ active_import_suffix_generator, Suffix, ) -from pyomo.core.base.var import Var, VarData, VarData, ScalarVar, VarList +from pyomo.core.base.var import Var, VarData, ScalarVar, VarList from pyomo.core.base.instance2dat import instance2dat From 354cadf178d6210ebe386687ff1af15d8481c253 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 18 Apr 2024 09:53:00 -0600 Subject: [PATCH 1652/1797] Revert accidental test name change --- pyomo/core/tests/unit/test_suffix.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyomo/core/tests/unit/test_suffix.py b/pyomo/core/tests/unit/test_suffix.py index 70f028a3eff..d2e861cceb5 100644 --- a/pyomo/core/tests/unit/test_suffix.py +++ b/pyomo/core/tests/unit/test_suffix.py @@ -1567,7 +1567,7 @@ def test_clone_ObjectiveArray(self): self.assertEqual(inst.junk.get(model.obj[1]), None) self.assertEqual(inst.junk.get(inst.obj[1]), 1.0) - def test_cloneObjectiveData(self): + def test_clone_ObjectiveData(self): model = ConcreteModel() model.x = Var([1, 2, 3], dense=True) model.obj = Objective([1, 2, 3], rule=lambda model, i: model.x[i]) @@ -1603,7 +1603,7 @@ def test_clone_IndexedBlock(self): self.assertEqual(inst.junk.get(model.b[1]), None) self.assertEqual(inst.junk.get(inst.b[1]), 1.0) - def test_cloneBlockData(self): + def test_clone_BlockData(self): model = ConcreteModel() model.b = Block([1, 2, 3]) model.junk = Suffix() @@ -1725,7 +1725,7 @@ def test_pickle_ObjectiveArray(self): self.assertEqual(inst.junk.get(model.obj[1]), None) self.assertEqual(inst.junk.get(inst.obj[1]), 1.0) - def test_pickleObjectiveData(self): + def test_pickle_ObjectiveData(self): model = ConcreteModel() model.x = Var([1, 2, 3], dense=True) model.obj = Objective([1, 2, 3], rule=simple_obj_rule) @@ -1761,7 +1761,7 @@ def test_pickle_IndexedBlock(self): self.assertEqual(inst.junk.get(model.b[1]), None) self.assertEqual(inst.junk.get(inst.b[1]), 1.0) - def test_pickleBlockData(self): + def test_pickle_BlockData(self): model = ConcreteModel() model.b = Block([1, 2, 3]) model.junk = Suffix() From 37915b4b409c3e468d70e0ed64faaee0b7c83ef5 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 18 Apr 2024 09:53:59 -0600 Subject: [PATCH 1653/1797] Fix docstring, add Objectives as known type to FBBT --- pyomo/contrib/fbbt/fbbt.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/fbbt/fbbt.py b/pyomo/contrib/fbbt/fbbt.py index eb7155313c4..1507c4a3cc5 100644 --- a/pyomo/contrib/fbbt/fbbt.py +++ b/pyomo/contrib/fbbt/fbbt.py @@ -24,9 +24,10 @@ import math from pyomo.core.base.block import Block from pyomo.core.base.constraint import Constraint +from pyomo.core.base.expression import ExpressionData, ScalarExpression +from pyomo.core.base.objective import ObjectiveData, ScalarObjective from pyomo.core.base.var import Var from pyomo.gdp import Disjunct -from pyomo.core.base.expression import ExpressionData, ScalarExpression import logging from pyomo.common.errors import InfeasibleConstraintException, PyomoException from pyomo.common.config import ( @@ -340,7 +341,7 @@ def _prop_bnds_leaf_to_root_NamedExpression(visitor, node, expr): Parameters ---------- visitor: _FBBTVisitorLeafToRoot - node: pyomo.core.base.expression.ExpressionData + node: pyomo.core.base.expression.NamedExpressionData expr: NamedExpressionData arg """ bnds_dict = visitor.bnds_dict @@ -368,6 +369,8 @@ def _prop_bnds_leaf_to_root_NamedExpression(visitor, node, expr): numeric_expr.AbsExpression: _prop_bnds_leaf_to_root_abs, ExpressionData: _prop_bnds_leaf_to_root_NamedExpression, ScalarExpression: _prop_bnds_leaf_to_root_NamedExpression, + ObjectiveData: _prop_bnds_leaf_to_root_NamedExpression, + ScalarObjective: _prop_bnds_leaf_to_root_NamedExpression, }, ) @@ -904,7 +907,7 @@ def _prop_bnds_root_to_leaf_NamedExpression(node, bnds_dict, feasibility_tol): Parameters ---------- - node: pyomo.core.base.expression.ExpressionData + node: pyomo.core.base.expression.NamedExpressionData 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 @@ -947,6 +950,8 @@ def _prop_bnds_root_to_leaf_NamedExpression(node, bnds_dict, feasibility_tol): _prop_bnds_root_to_leaf_map[ExpressionData] = _prop_bnds_root_to_leaf_NamedExpression _prop_bnds_root_to_leaf_map[ScalarExpression] = _prop_bnds_root_to_leaf_NamedExpression +_prop_bnds_root_to_leaf_map[ObjectiveData] = _prop_bnds_root_to_leaf_NamedExpression +_prop_bnds_root_to_leaf_map[ScalarObjective] = _prop_bnds_root_to_leaf_NamedExpression def _check_and_reset_bounds(var, lb, ub): From e35d537d30e6e28d2c54737408adcf24d7ae361d Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 18 Apr 2024 09:54:18 -0600 Subject: [PATCH 1654/1797] Fix docstring --- pyomo/core/base/constraint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/core/base/constraint.py b/pyomo/core/base/constraint.py index 3a71758d55d..eb4af76fdc1 100644 --- a/pyomo/core/base/constraint.py +++ b/pyomo/core/base/constraint.py @@ -127,7 +127,7 @@ def C_rule(model, i, j): class ConstraintData(ActiveComponentData): """ - This class defines the data for a single general constraint. + This class defines the data for a single algebraic constraint. Constructor arguments: component The Constraint object that owns this data. From 256d5bb8db73c11fb75679ed8b5770f99b6820d3 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 18 Apr 2024 11:30:24 -0600 Subject: [PATCH 1655/1797] Update PR test --- .github/workflows/test_pr_and_main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 2615f6b838e..619a5e695e2 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==3.1.5 + PACKAGES: openmpi mpi4py - os: ubuntu-latest python: '3.11' From 31474401ef9034b90946ca4a094f45b89b3f912a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrique=20J=C3=BAnior?= <16216517+henriquejsfj@users.noreply.github.com> Date: Sun, 14 Apr 2024 13:19:02 -0300 Subject: [PATCH 1656/1797] Fix: Get SCIP solving time considering float number with some text This fix does not change previous behavior and handles the case of a string with a float number plus some text. --- pyomo/solvers/plugins/solvers/SCIPAMPL.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/solvers/plugins/solvers/SCIPAMPL.py b/pyomo/solvers/plugins/solvers/SCIPAMPL.py index fd69954b428..966fb1e1a1d 100644 --- a/pyomo/solvers/plugins/solvers/SCIPAMPL.py +++ b/pyomo/solvers/plugins/solvers/SCIPAMPL.py @@ -455,7 +455,7 @@ def read_scip_log(filename: str): solver_status = scip_lines[0][colon_position + 2 : scip_lines[0].index('\n')] solving_time = float( - scip_lines[1][colon_position + 2 : scip_lines[1].index('\n')] + scip_lines[1][colon_position + 2 : scip_lines[1].index('\n')].split(' ')[0] ) try: From 3ce74167beac24f4ffd963fef8d0f57a0979ea69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrique=20J=C3=BAnior?= <16216517+henriquejsfj@users.noreply.github.com> Date: Thu, 18 Apr 2024 16:05:40 -0300 Subject: [PATCH 1657/1797] Test the Scip reoptimization option --- pyomo/solvers/tests/mip/test_scip.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyomo/solvers/tests/mip/test_scip.py b/pyomo/solvers/tests/mip/test_scip.py index 01de0d16826..ad54daeddc0 100644 --- a/pyomo/solvers/tests/mip/test_scip.py +++ b/pyomo/solvers/tests/mip/test_scip.py @@ -106,6 +106,12 @@ def test_scip_solve_from_instance_options(self): results.write(filename=_out, times=False, format='json') self.compare_json(_out, join(currdir, "test_scip_solve_from_instance.baseline")) + def test_scip_solve_from_instance_with_reoptimization(self): + # Test scip with re-optimization option enabled + # This case changes the Scip output results which may break the results parser + self.scip.options['reoptimization/enable'] = True + self.test_scip_solve_from_instance() + if __name__ == "__main__": deleteFiles = False From 7537bb517b2bc9b841ad368a433b63f0700e9d86 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 18 Apr 2024 13:54:42 -0600 Subject: [PATCH 1658/1797] Propogating domain to substitution var in the var aggregator --- pyomo/contrib/preprocessing/plugins/var_aggregator.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/preprocessing/plugins/var_aggregator.py b/pyomo/contrib/preprocessing/plugins/var_aggregator.py index d862f167fd7..5b714cf439d 100644 --- a/pyomo/contrib/preprocessing/plugins/var_aggregator.py +++ b/pyomo/contrib/preprocessing/plugins/var_aggregator.py @@ -13,7 +13,8 @@ from pyomo.common.collections import ComponentMap, ComponentSet -from pyomo.core.base import Block, Constraint, VarList, Objective, TransformationFactory +from pyomo.core.base import (Block, Constraint, VarList, Objective, Reals, + TransformationFactory) from pyomo.core.expr import ExpressionReplacementVisitor from pyomo.core.expr.numvalue import value from pyomo.core.plugins.transform.hierarchy import IsomorphicTransformation @@ -248,6 +249,12 @@ def _apply_to(self, model, detect_fixed_vars=True): # the variables in its equality set. z_agg.setlb(max_if_not_None(v.lb for v in eq_set if v.has_lb())) z_agg.setub(min_if_not_None(v.ub for v in eq_set if v.has_ub())) + # Set the domain of the aggregate variable to the intersection of + # the domains of the variables in its equality set + domain = Reals + for v in eq_set: + domain = domain & v.domain + z_agg.domain = domain # Set the fixed status of the aggregate var fixed_vars = [v for v in eq_set if v.fixed] From fe6ab256e9cc53c190161dea836b8b998716bb66 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 18 Apr 2024 13:54:59 -0600 Subject: [PATCH 1659/1797] Testing var aggregator with vars with different domains --- .../tests/test_var_aggregator.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/pyomo/contrib/preprocessing/tests/test_var_aggregator.py b/pyomo/contrib/preprocessing/tests/test_var_aggregator.py index 6f6d02f2180..ff1ea843d23 100644 --- a/pyomo/contrib/preprocessing/tests/test_var_aggregator.py +++ b/pyomo/contrib/preprocessing/tests/test_var_aggregator.py @@ -19,12 +19,16 @@ max_if_not_None, min_if_not_None, ) +from pyomo.core.expr.compare import assertExpressionsEqual from pyomo.environ import ( + Binary, ConcreteModel, Constraint, ConstraintList, + maximize, Objective, RangeSet, + Reals, SolverFactory, TransformationFactory, Var, @@ -210,6 +214,43 @@ def test_var_update(self): self.assertEqual(m.x.value, 0) self.assertEqual(m.y.value, 0) + def test_binary_inequality(self): + m = ConcreteModel() + m.x = Var(domain=Binary) + m.y = Var(domain=Binary) + m.c = Constraint(expr=m.x == m.y) + m.o = Objective(expr=0.5*m.x + m.y, sense=maximize) + TransformationFactory('contrib.aggregate_vars').apply_to(m) + var_to_z = m._var_aggregator_info.var_to_z + z = var_to_z[m.x] + self.assertIs(var_to_z[m.y], z) + self.assertEqual(z.domain, Binary) + self.assertEqual(z.lb, 0) + self.assertEqual(z.ub, 1) + assertExpressionsEqual( + self, + m.o.expr, + 0.5 * z + z + ) + + def test_equality_different_domains(self): + m = ConcreteModel() + m.x = Var(domain=Reals, bounds=(1, 2)) + m.y = Var(domain=Binary) + m.c = Constraint(expr=m.x == m.y) + m.o = Objective(expr=0.5*m.x + m.y, sense=maximize) + TransformationFactory('contrib.aggregate_vars').apply_to(m) + var_to_z = m._var_aggregator_info.var_to_z + z = var_to_z[m.x] + self.assertIs(var_to_z[m.y], z) + self.assertEqual(z.lb, 1) + self.assertEqual(z.ub, 1) + self.assertEqual(z.domain, Binary) + assertExpressionsEqual( + self, + m.o.expr, + 0.5 * z + z + ) if __name__ == '__main__': unittest.main() From 127f8c68f9a97da4a34a5a52f61d0d08c6acf0bb Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 18 Apr 2024 13:56:16 -0600 Subject: [PATCH 1660/1797] black --- .../preprocessing/plugins/var_aggregator.py | 10 ++++++++-- .../preprocessing/tests/test_var_aggregator.py | 17 +++++------------ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/pyomo/contrib/preprocessing/plugins/var_aggregator.py b/pyomo/contrib/preprocessing/plugins/var_aggregator.py index 5b714cf439d..3430d29de3a 100644 --- a/pyomo/contrib/preprocessing/plugins/var_aggregator.py +++ b/pyomo/contrib/preprocessing/plugins/var_aggregator.py @@ -13,8 +13,14 @@ from pyomo.common.collections import ComponentMap, ComponentSet -from pyomo.core.base import (Block, Constraint, VarList, Objective, Reals, - TransformationFactory) +from pyomo.core.base import ( + Block, + Constraint, + VarList, + Objective, + Reals, + TransformationFactory, +) from pyomo.core.expr import ExpressionReplacementVisitor from pyomo.core.expr.numvalue import value from pyomo.core.plugins.transform.hierarchy import IsomorphicTransformation diff --git a/pyomo/contrib/preprocessing/tests/test_var_aggregator.py b/pyomo/contrib/preprocessing/tests/test_var_aggregator.py index ff1ea843d23..b0b672b76b0 100644 --- a/pyomo/contrib/preprocessing/tests/test_var_aggregator.py +++ b/pyomo/contrib/preprocessing/tests/test_var_aggregator.py @@ -219,7 +219,7 @@ def test_binary_inequality(self): m.x = Var(domain=Binary) m.y = Var(domain=Binary) m.c = Constraint(expr=m.x == m.y) - m.o = Objective(expr=0.5*m.x + m.y, sense=maximize) + m.o = Objective(expr=0.5 * m.x + m.y, sense=maximize) TransformationFactory('contrib.aggregate_vars').apply_to(m) var_to_z = m._var_aggregator_info.var_to_z z = var_to_z[m.x] @@ -227,18 +227,14 @@ def test_binary_inequality(self): self.assertEqual(z.domain, Binary) self.assertEqual(z.lb, 0) self.assertEqual(z.ub, 1) - assertExpressionsEqual( - self, - m.o.expr, - 0.5 * z + z - ) + assertExpressionsEqual(self, m.o.expr, 0.5 * z + z) def test_equality_different_domains(self): m = ConcreteModel() m.x = Var(domain=Reals, bounds=(1, 2)) m.y = Var(domain=Binary) m.c = Constraint(expr=m.x == m.y) - m.o = Objective(expr=0.5*m.x + m.y, sense=maximize) + m.o = Objective(expr=0.5 * m.x + m.y, sense=maximize) TransformationFactory('contrib.aggregate_vars').apply_to(m) var_to_z = m._var_aggregator_info.var_to_z z = var_to_z[m.x] @@ -246,11 +242,8 @@ def test_equality_different_domains(self): self.assertEqual(z.lb, 1) self.assertEqual(z.ub, 1) self.assertEqual(z.domain, Binary) - assertExpressionsEqual( - self, - m.o.expr, - 0.5 * z + z - ) + assertExpressionsEqual(self, m.o.expr, 0.5 * z + z) + if __name__ == '__main__': unittest.main() From ebe0159071ef2a60a6e71dd3131913907e5aa64b Mon Sep 17 00:00:00 2001 From: robbybp Date: Fri, 19 Apr 2024 23:15:33 -0600 Subject: [PATCH 1661/1797] start adding an inventory of code to readme --- pyomo/contrib/pynumero/README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/pyomo/contrib/pynumero/README.md b/pyomo/contrib/pynumero/README.md index 0d165dbc39c..d4b6344ec76 100644 --- a/pyomo/contrib/pynumero/README.md +++ b/pyomo/contrib/pynumero/README.md @@ -71,3 +71,34 @@ Prerequisites - cmake - a C/C++ compiler - MA57 library or COIN-HSL Full + +Code organization +================= + +PyNumero was initially designed around three core components: linear solver +interfaces, an interface for function and derivative callbacks, and block +vector and matrix classes. Since then, it has incorporated additional +functionality in an ad-hoc manner. The following is a rough overview of +PyNumero, by directory: + +`linalg` +-------- + +Python interfaces to linear solvers. This is core functionality. + +`interfaces` +------------ + +- Classes that define and implement an API for function and derivative callbacks +required by nonlinear optimization solvers, e.g. `nlp.py` and `pyomo_nlp.py` +- Various wrappers around these NLP classes to support "hybrid" implementations, +e.g. `PyomoNLPWithGreyBoxBlocks` +- The `ExternalGreyBoxBlock` Pyomo modeling component and +`ExternalGreyBoxModel` API +- The `ExternalPyomoModel` implementation of `ExternalGreyBoxModel`, which allows +definition of an external grey box via an implicit function +- The `CyIpoptNLP` class, which wraps an object implementing the NLP API in +the interface required by CyIpopt + +`src` +----- From d4dcb0e81bbc20dc6597fc76b1400b81c02cd100 Mon Sep 17 00:00:00 2001 From: robbybp Date: Fri, 19 Apr 2024 23:16:14 -0600 Subject: [PATCH 1662/1797] add a note about backward compatibility to pynumero doc --- .../pynumero/backward_compatibility.rst | 14 ++++++++++++++ .../contributed_packages/pynumero/index.rst | 1 + 2 files changed, 15 insertions(+) create mode 100644 doc/OnlineDocs/contributed_packages/pynumero/backward_compatibility.rst diff --git a/doc/OnlineDocs/contributed_packages/pynumero/backward_compatibility.rst b/doc/OnlineDocs/contributed_packages/pynumero/backward_compatibility.rst new file mode 100644 index 00000000000..036a00bee62 --- /dev/null +++ b/doc/OnlineDocs/contributed_packages/pynumero/backward_compatibility.rst @@ -0,0 +1,14 @@ +Backward Compatibility +====================== + +While PyNumero is a third-party contribution to Pyomo, we intend to maintain +the stability of its core functionality. The core functionality of PyNumero +consists of: + +1. The ``NLP`` API and ``PyomoNLP`` implementation of this API +2. HSL and MUMPS linear solver interfaces +3. ``BlockVector`` and ``BlockMatrix`` classes +4. CyIpopt and SciPy solver interfaces + +Other parts of PyNumero, such as ``ExternalGreyBoxBlock`` and +``ImplicitFunctionSolver``, are experimental and subject to change without notice. diff --git a/doc/OnlineDocs/contributed_packages/pynumero/index.rst b/doc/OnlineDocs/contributed_packages/pynumero/index.rst index 6ff8b29f812..711bb83eb3b 100644 --- a/doc/OnlineDocs/contributed_packages/pynumero/index.rst +++ b/doc/OnlineDocs/contributed_packages/pynumero/index.rst @@ -13,6 +13,7 @@ PyNumero. For more details, see the API documentation (:ref:`pynumero_api`). installation.rst tutorial.rst api.rst + backward_compatibility.rst Developers From 102223f541f82b38085c15489d2884744dfbb8f8 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 22 Apr 2024 15:25:46 -0600 Subject: [PATCH 1663/1797] NFC: clarify NamedExpressionData docstring --- pyomo/core/base/expression.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyomo/core/base/expression.py b/pyomo/core/base/expression.py index 5638e48ea8b..013c388e6e5 100644 --- a/pyomo/core/base/expression.py +++ b/pyomo/core/base/expression.py @@ -37,11 +37,14 @@ class NamedExpressionData(numeric_expr.NumericValue): - """ - An object that defines a named expression. + """An object that defines a generic "named expression". + + This is the base class for both :py:class:`ExpressionData` and + :py:class:`ObjectiveData`. Public Class Attributes expr The expression owned by this data. + """ # Note: derived classes are expected to declare the _args_ slot From cc2803483b05f6c9348b303aac4105d181346e49 Mon Sep 17 00:00:00 2001 From: Alex Dowling Date: Tue, 23 Apr 2024 21:14:32 -0400 Subject: [PATCH 1664/1797] Fixed error about if values being ambiguous --- pyomo/contrib/doe/measurements.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pyomo/contrib/doe/measurements.py b/pyomo/contrib/doe/measurements.py index 5a3c44a76e4..11b84b4231b 100644 --- a/pyomo/contrib/doe/measurements.py +++ b/pyomo/contrib/doe/measurements.py @@ -93,17 +93,17 @@ def add_variables( upper_bounds, ) - if values: + if values is not None: # this dictionary keys are special set, values are its value self.variable_names_value.update(zip(added_names, values)) # if a scalar (int or float) is given, set it as the lower bound for all variables - if lower_bounds: + if lower_bounds is not None: if type(lower_bounds) in [int, float]: lower_bounds = [lower_bounds] * len(added_names) self.lower_bounds.update(zip(added_names, lower_bounds)) - if upper_bounds: + if upper_bounds is not None: if type(upper_bounds) in [int, float]: upper_bounds = [upper_bounds] * len(added_names) self.upper_bounds.update(zip(added_names, upper_bounds)) @@ -177,20 +177,20 @@ def _check_valid_input( raise ValueError("time index cannot be found in indices.") # if given a list, check if bounds have the same length with flattened variable - if values and len(values) != len_indices: + if values is not None and len(values) != len_indices: raise ValueError("Values is of different length with indices.") if ( - lower_bounds - and type(lower_bounds) == list - and len(lower_bounds) != len_indices + lower_bounds is not None # ensure not None + and type(lower_bounds) == list # ensure list + and len(lower_bounds) != len_indices # ensure same length ): raise ValueError("Lowerbounds is of different length with indices.") if ( - upper_bounds - and type(upper_bounds) == list - and len(upper_bounds) != len_indices + upper_bounds is not None # ensure None + and type(upper_bounds) == list # ensure list + and len(upper_bounds) != len_indices # ensure same length ): raise ValueError("Upperbounds is of different length with indices.") From 6d421a428c46a7621fb1f356406d392b3bc0e9e3 Mon Sep 17 00:00:00 2001 From: Alex Dowling Date: Tue, 23 Apr 2024 21:31:39 -0400 Subject: [PATCH 1665/1797] Added exception to prevent cryptic error messages later. --- pyomo/contrib/doe/doe.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyomo/contrib/doe/doe.py b/pyomo/contrib/doe/doe.py index d2ba2f277d6..eba22b954cf 100644 --- a/pyomo/contrib/doe/doe.py +++ b/pyomo/contrib/doe/doe.py @@ -101,6 +101,8 @@ def __init__( """ # parameters + if type(param_init) != dict: + raise ValueError("param_init should be a dictionary.") self.param = param_init # design variable name self.design_name = design_vars.variable_names From f1d480fe970472acc1919f4b32c8f9808bc2a179 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 24 Apr 2024 09:55:31 -0600 Subject: [PATCH 1666/1797] Fixing a bug where a division by 0 wasn't trapped and propogated as an invalid number --- pyomo/repn/linear.py | 2 +- pyomo/repn/tests/test_linear.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/pyomo/repn/linear.py b/pyomo/repn/linear.py index ba08c7ef245..6d084067511 100644 --- a/pyomo/repn/linear.py +++ b/pyomo/repn/linear.py @@ -292,7 +292,7 @@ def _handle_division_constant_constant(visitor, node, arg1, arg2): def _handle_division_ANY_constant(visitor, node, arg1, arg2): - arg1[1].multiplier /= arg2[1] + arg1[1].multiplier = apply_node_operation(node, (arg1[1].multiplier, arg2[1])) return arg1 diff --git a/pyomo/repn/tests/test_linear.py b/pyomo/repn/tests/test_linear.py index 0fd428fd8ee..861fecc7888 100644 --- a/pyomo/repn/tests/test_linear.py +++ b/pyomo/repn/tests/test_linear.py @@ -1436,6 +1436,22 @@ def test_errors_propagate_nan(self): m.z = Var() m.y.fix(1) + expr = (m.x + 1) / m.p + cfg = VisitorConfig() + with LoggingIntercept() as LOG: + repn = LinearRepnVisitor(*cfg).walk_expression(expr) + self.assertEqual( + LOG.getvalue(), + "Exception encountered evaluating expression 'div(1, 0)'\n" + "\tmessage: division by zero\n" + "\texpression: (x + 1)/p\n", + ) + self.assertEqual(repn.multiplier, 1) + self.assertEqual(str(repn.constant), 'InvalidNumber(nan)') + self.assertEqual(len(repn.linear), 1) + self.assertEqual(str(repn.linear[id(m.x)]), 'InvalidNumber(nan)') + self.assertEqual(repn.nonlinear, None) + expr = m.y + m.x + m.z + ((3 * m.x) / m.p) / m.y cfg = VisitorConfig() with LoggingIntercept() as LOG: From 910cf2d1247f0d75e80b6bd776236e8e05ae6beb Mon Sep 17 00:00:00 2001 From: Alex Dowling Date: Wed, 24 Apr 2024 21:04:11 -0400 Subject: [PATCH 1667/1797] Added units. --- pyomo/contrib/doe/doe.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/doe/doe.py b/pyomo/contrib/doe/doe.py index eba22b954cf..8c83e3145ce 100644 --- a/pyomo/contrib/doe/doe.py +++ b/pyomo/contrib/doe/doe.py @@ -240,12 +240,12 @@ def stochastic_program( if self.optimize: analysis_optimize = self._optimize_stochastic_program(m) dT = sp_timer.toc(msg=None) - self.logger.info("elapsed time: %0.1f" % dT) + self.logger.info("elapsed time: %0.1f seconds" % dT) return analysis_square, analysis_optimize else: dT = sp_timer.toc(msg=None) - self.logger.info("elapsed time: %0.1f" % dT) + self.logger.info("elapsed time: %0.1f seconds" % dT) return analysis_square def _compute_stochastic_program(self, m, optimize_option): @@ -389,7 +389,7 @@ def compute_FIM( FIM_analysis = self._direct_kaug() dT = square_timer.toc(msg=None) - self.logger.info("elapsed time: %0.1f" % dT) + self.logger.info("elapsed time: %0.1f seconds" % dT) return FIM_analysis From 5df54523e46be0e66aac827e21286a9977592011 Mon Sep 17 00:00:00 2001 From: Alex Dowling Date: Wed, 24 Apr 2024 21:52:51 -0400 Subject: [PATCH 1668/1797] Switched to isinstance --- pyomo/contrib/doe/doe.py | 6 +++--- pyomo/contrib/doe/measurements.py | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pyomo/contrib/doe/doe.py b/pyomo/contrib/doe/doe.py index 8c83e3145ce..b55f9aac0ac 100644 --- a/pyomo/contrib/doe/doe.py +++ b/pyomo/contrib/doe/doe.py @@ -38,7 +38,7 @@ from pyomo.contrib.sensitivity_toolbox.sens import get_dsdp from pyomo.contrib.doe.scenario import ScenarioGenerator, FiniteDifferenceStep from pyomo.contrib.doe.result import FisherResults, GridSearchResult - +import collections class CalculationMode(Enum): sequential_finite = "sequential_finite" @@ -101,7 +101,7 @@ def __init__( """ # parameters - if type(param_init) != dict: + if not isinstance(param_init, collections.Mapping): raise ValueError("param_init should be a dictionary.") self.param = param_init # design variable name @@ -777,7 +777,7 @@ def run_grid_search( # update the controlled value of certain time points for certain design variables for i, names in enumerate(design_dimension_names): # if the element is a list, all design variables in this list share the same values - if type(names) is list or type(names) is tuple: + if isinstance(names, collections.Sequence): for n in names: design_iter[n] = list(design_set_iter)[i] else: diff --git a/pyomo/contrib/doe/measurements.py b/pyomo/contrib/doe/measurements.py index 11b84b4231b..aa196ec9a49 100644 --- a/pyomo/contrib/doe/measurements.py +++ b/pyomo/contrib/doe/measurements.py @@ -26,6 +26,7 @@ # ___________________________________________________________________________ import itertools +import collections class VariablesWithIndices: @@ -171,7 +172,7 @@ def _check_valid_input( """ Check if the measurement information provided are valid to use. """ - assert type(var_name) is str, "var_name should be a string." + assert isinstance(var_name, str), "var_name should be a string." if time_index_position not in indices: raise ValueError("time index cannot be found in indices.") @@ -182,14 +183,14 @@ def _check_valid_input( if ( lower_bounds is not None # ensure not None - and type(lower_bounds) == list # ensure list + and isinstance(lower_bounds, collections.Sequence) # ensure list-like and len(lower_bounds) != len_indices # ensure same length ): raise ValueError("Lowerbounds is of different length with indices.") if ( upper_bounds is not None # ensure None - and type(upper_bounds) == list # ensure list + and isinstance(upper_bounds, collections.Sequence) # ensure list-like and len(upper_bounds) != len_indices # ensure same length ): raise ValueError("Upperbounds is of different length with indices.") From ea9d226f368959e3e028a4bd9a4a7bb77ea29938 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 24 Apr 2024 22:08:44 -0600 Subject: [PATCH 1669/1797] Skip black 24.4.1 due to a bug in the parser --- .github/workflows/test_branches.yml | 3 ++- .github/workflows/test_pr_and_main.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 1885f6a00e2..75db5d66431 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -40,7 +40,8 @@ jobs: python-version: '3.10' - name: Black Formatting Check run: | - pip install black + # Note v24.4.1 fails due to a bug in the parser + pip install 'black!=24.4.1' black . -S -C --check --diff --exclude examples/pyomobook/python-ch/BadIndent.py - name: Spell Check uses: crate-ci/typos@master diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 619a5e695e2..eb059a7ef82 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -43,7 +43,8 @@ jobs: python-version: '3.10' - name: Black Formatting Check run: | - pip install black + # Note v24.4.1 fails due to a bug in the parser + pip install 'black!=24.4.1' black . -S -C --check --diff --exclude examples/pyomobook/python-ch/BadIndent.py - name: Spell Check uses: crate-ci/typos@master From 98ee3ec8ad70b49f25a8aade1453c1d3be58ac8a Mon Sep 17 00:00:00 2001 From: robbybp Date: Wed, 24 Apr 2024 22:16:20 -0600 Subject: [PATCH 1670/1797] add more description to readme --- pyomo/contrib/pynumero/README.md | 43 ++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/pynumero/README.md b/pyomo/contrib/pynumero/README.md index d4b6344ec76..d68c055e97c 100644 --- a/pyomo/contrib/pynumero/README.md +++ b/pyomo/contrib/pynumero/README.md @@ -78,8 +78,13 @@ Code organization PyNumero was initially designed around three core components: linear solver interfaces, an interface for function and derivative callbacks, and block vector and matrix classes. Since then, it has incorporated additional -functionality in an ad-hoc manner. The following is a rough overview of -PyNumero, by directory: +functionality in an ad-hoc manner. The original "core functionality" of +PyNumero, as well as the solver interfaces accessible through +`SolverFactory`, should be considered stable and will only change after +appropriate deprecation warnings. Other functionality should be considered +experimental and subject to change without warning. + +The following is a rough overview of PyNumero, by directory: `linalg` -------- @@ -100,5 +105,39 @@ definition of an external grey box via an implicit function - The `CyIpoptNLP` class, which wraps an object implementing the NLP API in the interface required by CyIpopt +Of the above, only `PyomoNLP` and the `NLP` base class should be considered core +functionality. + `src` ----- + +C++ interfaces to ASL, MA27, and MA57. The ASL and MA27 interfaces are +core functionality. + +`sparse` +-------- + +Block vector and block matrix classes, including MPI variations. +These are core functionality. + +`algorithms` +------------ + +Originally intended to hold various useful algorithms implemented +on NLP objects rather than Pyomo models. Any files added here should +be considered experimental. + +`algorithms/solvers` +-------------------- + +Interfaces to Python solvers using the NLP API defined in `interfaces`. +Only the solvers accessible through `SolverFactory`, e.g. `PyomoCyIpoptSolver` +and `PyomoFsolveSolver`, should be considered core functionality. + +`examples` +---------- + +The examples demonstrated in `nlp_interface.py`, `nlp_interface_2.py1`, +`feasibility.py`, `mumps_example.py`, `sensitivity.py`, `sqp.py`, +`parallel_matvec.py`, and `parallel_vector_ops.py` are stable. All other +examples should be considered experimental. From 5689e7406574d5baa8d80c38f91264f7d9fc59ed Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Thu, 25 Apr 2024 09:33:06 -0600 Subject: [PATCH 1671/1797] add note that location of pyomo solvers is subject to change --- pyomo/contrib/pynumero/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyomo/contrib/pynumero/README.md b/pyomo/contrib/pynumero/README.md index d68c055e97c..f881e400d51 100644 --- a/pyomo/contrib/pynumero/README.md +++ b/pyomo/contrib/pynumero/README.md @@ -133,6 +133,8 @@ be considered experimental. Interfaces to Python solvers using the NLP API defined in `interfaces`. Only the solvers accessible through `SolverFactory`, e.g. `PyomoCyIpoptSolver` and `PyomoFsolveSolver`, should be considered core functionality. +The supported way to access these solvers is via `SolverFactory`. *The locations +of the underlying solver objects are subject to change without warning.* `examples` ---------- From a0bc0891f900993692c65c88c2c0e9aacc435d88 Mon Sep 17 00:00:00 2001 From: Miranda Mundt <55767766+mrmundt@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:30:23 -0600 Subject: [PATCH 1672/1797] Incorporate suggestion on wording Co-authored-by: Bethany Nicholson --- doc/OnlineDocs/contribution_guide.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/OnlineDocs/contribution_guide.rst b/doc/OnlineDocs/contribution_guide.rst index 6054a8d2ba9..b98dcc3d014 100644 --- a/doc/OnlineDocs/contribution_guide.rst +++ b/doc/OnlineDocs/contribution_guide.rst @@ -93,7 +93,7 @@ active development may be marked 'stale' and closed. .. note:: Draft and WIP Pull Requests will **NOT** trigger tests. This is an effort to - keep our backlog as available as possible. Please make use of the provided + reduce our CI backlog. Please make use of the provided branch test suite for evaluating / testing draft functionality. Python Version Support From 312f5722b1a703413133c887b415a0d249d1e032 Mon Sep 17 00:00:00 2001 From: Alex Dowling Date: Thu, 25 Apr 2024 21:46:59 -0400 Subject: [PATCH 1673/1797] Initialization improvements and degree of freedom fixes are important! --- pyomo/contrib/doe/doe.py | 116 +++++++++++++++++++++++++++--- pyomo/contrib/doe/measurements.py | 6 +- 2 files changed, 108 insertions(+), 14 deletions(-) diff --git a/pyomo/contrib/doe/doe.py b/pyomo/contrib/doe/doe.py index b55f9aac0ac..cac9bfd9271 100644 --- a/pyomo/contrib/doe/doe.py +++ b/pyomo/contrib/doe/doe.py @@ -38,7 +38,9 @@ from pyomo.contrib.sensitivity_toolbox.sens import get_dsdp from pyomo.contrib.doe.scenario import ScenarioGenerator, FiniteDifferenceStep from pyomo.contrib.doe.result import FisherResults, GridSearchResult -import collections +import collections.abc + +import inspect class CalculationMode(Enum): sequential_finite = "sequential_finite" @@ -101,7 +103,7 @@ def __init__( """ # parameters - if not isinstance(param_init, collections.Mapping): + if not isinstance(param_init, collections.abc.Mapping): raise ValueError("param_init should be a dictionary.") self.param = param_init # design variable name @@ -238,6 +240,10 @@ def stochastic_program( m, analysis_square = self._compute_stochastic_program(m, optimize_opt) if self.optimize: + # set max_iter to 0 to debug the initialization + # self.solver.options["max_iter"] = 0 + # self.solver.options["bound_push"] = 1e-10 + analysis_optimize = self._optimize_stochastic_program(m) dT = sp_timer.toc(msg=None) self.logger.info("elapsed time: %0.1f seconds" % dT) @@ -588,13 +594,34 @@ def _create_block(self): # Set for block/scenarios mod.scenario = pyo.Set(initialize=self.scenario_data.scenario_indices) + + # Determine if create_model takes theta as an optional input + # print(inspect.getfullargspec(self.create_model)) + pass_theta_to_initialize= ('theta' in inspect.getfullargspec(self.create_model).args) + #print("pass_theta_to_initialize =", pass_theta_to_initialize) # Allow user to self-define complex design variables self.create_model(mod=mod, model_option=ModelOptionLib.stage1) + # Fix parameter values in the copy of the stage1 model (if they exist) + for par in self.param: + cuid = pyo.ComponentUID(par) + var = cuid.find_component_on(mod) + if var is not None: + # Fix the parameter value + # Otherwise, the parameter does not exist on the stage 1 model + var.fix(self.param[par]) + def block_build(b, s): # create block scenarios - self.create_model(mod=b, model_option=ModelOptionLib.stage2) + # idea: check if create_model takes theta as an optional input, if so, pass parameter values to create_model + + if pass_theta_to_initialize: + theta_initialize = self.scenario_data.scenario[s] + #print("Initializing with theta=", theta_initialize) + self.create_model(mod=b, model_option=ModelOptionLib.stage2, theta=theta_initialize) + else: + self.create_model(mod=b, model_option=ModelOptionLib.stage2) # fix parameter values to perturbed values for par in self.param: @@ -605,7 +632,7 @@ def block_build(b, s): mod.block = pyo.Block(mod.scenario, rule=block_build) # discretize the model - if self.discretize_model: + if self.discretize_model is not None: mod = self.discretize_model(mod) # force design variables in blocks to be equal to global design values @@ -777,7 +804,7 @@ def run_grid_search( # update the controlled value of certain time points for certain design variables for i, names in enumerate(design_dimension_names): # if the element is a list, all design variables in this list share the same values - if isinstance(names, collections.Sequence): + if isinstance(names, collections.abc.Sequence): for n in names: design_iter[n] = list(design_set_iter)[i] else: @@ -881,20 +908,39 @@ def identity_matrix(m, i, j): else: return 0 + if self.jac_initial is not None: + dict_jac_initialize = {} + for i, bu in enumerate(model.regression_parameters): + for j, un in enumerate(model.measured_variables): + if isinstance(self.jac_initial, dict): + # Jacobian is a dictionary of arrays or lists where the key is the regression parameter name + dict_jac_initialize[(bu, un)] = self.jac_initial[bu][j] + elif isinstance(self.jac_initial, np.ndarray): + # Jacobian is a numpy array, rows are regression parameters, columns are measured variables + dict_jac_initialize[(bu, un)] = self.jac_initial[i][j] + + def initialize_jac(m, i, j): + if self.jac_initial is not None: + return dict_jac_initialize[(i, j)] + else: + return 0.1 + model.sensitivity_jacobian = pyo.Var( - model.regression_parameters, model.measured_variables, initialize=0.1 + model.regression_parameters, model.measured_variables, initialize=initialize_jac ) - if self.fim_initial: + if self.fim_initial is not None: dict_fim_initialize = {} for i, bu in enumerate(model.regression_parameters): for j, un in enumerate(model.regression_parameters): dict_fim_initialize[(bu, un)] = self.fim_initial[i][j] + + #print(dict_fim_initialize) def initialize_fim(m, j, d): return dict_fim_initialize[(j, d)] - if self.fim_initial: + if self.fim_initial is not None: model.fim = pyo.Var( model.regression_parameters, model.regression_parameters, @@ -1013,6 +1059,32 @@ def fim_rule(m, p, q): return model def _add_objective(self, m): + + ### Initialize the Cholesky decomposition matrix + if self.Cholesky_option: + + # Assemble the FIM matrix + fim = np.zeros((len(self.param), len(self.param))) + for i, bu in enumerate(m.regression_parameters): + for j, un in enumerate(m.regression_parameters): + fim[i][j] = m.fim[bu, un].value + + # Calculate the eigenvalues of the FIM matrix + eig = np.linalg.eigvals(fim) + + # If the smallest eigenvalue is (pratcially) negative, add a diagonal matrix to make it positive definite + small_number = 1E-10 + if min(eig) < small_number: + fim = fim + np.eye(len(self.param)) * (small_number - min(eig)) + + # Compute the Cholesky decomposition of the FIM matrix + L = np.linalg.cholesky(fim) + + # Initialize the Cholesky matrix + for i, c in enumerate(m.regression_parameters): + for j, d in enumerate(m.regression_parameters): + m.L_ele[c, d].value = L[i, j] + def cholesky_imp(m, c, d): """ Calculate Cholesky L matrix using algebraic constraints @@ -1103,14 +1175,20 @@ def _fix_design(self, m, design_val, fix_opt=True, optimize_option=None): m: model """ for name in self.design_name: + # Loop over design variables + # Get Pyomo variable object cuid = pyo.ComponentUID(name) var = cuid.find_component_on(m) if fix_opt: + # If fix_opt is True, fix the design variable var.fix(design_val[name]) else: + # Otherwise check optimize_option if optimize_option is None: + # If optimize_option is None, unfix all design variables var.unfix() else: + # Otherwise, unfix only the design variables listed in optimize_option with value True if optimize_option[name]: var.unfix() return m @@ -1126,7 +1204,7 @@ def _get_default_ipopt_solver(self): def _solve_doe(self, m, fix=False, opt_option=None): """Solve DOE model. If it's a square problem, fix design variable and solve. - Else, fix design variable and solve square problem firstly, then unfix them and solve the optimization problem + Else, fix design variable and solve square problem first, then unfix them and solve the optimization problem Parameters ---------- @@ -1140,14 +1218,30 @@ def _solve_doe(self, m, fix=False, opt_option=None): ------- solver_results: solver results """ - ### Solve square problem + # if fix = False, solve the optimization problem + # if fix = True, solve the square problem + + # either fix or unfix the design variables mod = self._fix_design( m, self.design_values, fix_opt=fix, optimize_option=opt_option ) + ''' + # This is for initialization diagnostics + # Remove before merging the PR + if not fix: + # halt at initial point + self.solver.options['max_iter'] = 0 + self.solver.options['bound_push'] = 1E-10 + else: + # resort to defaults + self.solver.options['max_iter'] = 3000 + self.solver.options['bound_push'] = 0.01 + ''' + # if user gives solver, use this solver. if not, use default IPOPT solver solver_result = self.solver.solve(mod, tee=self.tee_opt) - + return solver_result def _sgn(self, p): diff --git a/pyomo/contrib/doe/measurements.py b/pyomo/contrib/doe/measurements.py index aa196ec9a49..ae5b3519498 100644 --- a/pyomo/contrib/doe/measurements.py +++ b/pyomo/contrib/doe/measurements.py @@ -26,7 +26,7 @@ # ___________________________________________________________________________ import itertools -import collections +import collections.abc class VariablesWithIndices: @@ -183,14 +183,14 @@ def _check_valid_input( if ( lower_bounds is not None # ensure not None - and isinstance(lower_bounds, collections.Sequence) # ensure list-like + and isinstance(lower_bounds, collections.abc.Sequence) # ensure list-like and len(lower_bounds) != len_indices # ensure same length ): raise ValueError("Lowerbounds is of different length with indices.") if ( upper_bounds is not None # ensure None - and isinstance(upper_bounds, collections.Sequence) # ensure list-like + and isinstance(upper_bounds, collections.abc.Sequence) # ensure list-like and len(upper_bounds) != len_indices # ensure same length ): raise ValueError("Upperbounds is of different length with indices.") From 78c8558d3c0bc0beca6683ffe6b124223e501f6d Mon Sep 17 00:00:00 2001 From: Alex Dowling Date: Thu, 25 Apr 2024 21:53:56 -0400 Subject: [PATCH 1674/1797] Updated one type check and removed extra code from debugging. --- pyomo/contrib/doe/doe.py | 29 +++++++---------------------- pyomo/contrib/doe/measurements.py | 6 +++--- 2 files changed, 10 insertions(+), 25 deletions(-) diff --git a/pyomo/contrib/doe/doe.py b/pyomo/contrib/doe/doe.py index cac9bfd9271..d10fc1b7fcc 100644 --- a/pyomo/contrib/doe/doe.py +++ b/pyomo/contrib/doe/doe.py @@ -230,6 +230,7 @@ def stochastic_program( # FIM = Jacobian.T@Jacobian, the FIM is scaled by squared value the Jacobian is scaled self.fim_scale_constant_value = self.scale_constant_value**2 + # Start timer sp_timer = TicTocTimer() sp_timer.tic(msg=None) @@ -240,18 +241,17 @@ def stochastic_program( m, analysis_square = self._compute_stochastic_program(m, optimize_opt) if self.optimize: - # set max_iter to 0 to debug the initialization - # self.solver.options["max_iter"] = 0 - # self.solver.options["bound_push"] = 1e-10 - + # If set to optimize, solve the optimization problem (with degrees of freedom) analysis_optimize = self._optimize_stochastic_program(m) dT = sp_timer.toc(msg=None) self.logger.info("elapsed time: %0.1f seconds" % dT) + # Return both square problem and optimization problem results return analysis_square, analysis_optimize else: dT = sp_timer.toc(msg=None) self.logger.info("elapsed time: %0.1f seconds" % dT) + # Return only square problem results return analysis_square def _compute_stochastic_program(self, m, optimize_option): @@ -596,9 +596,7 @@ def _create_block(self): mod.scenario = pyo.Set(initialize=self.scenario_data.scenario_indices) # Determine if create_model takes theta as an optional input - # print(inspect.getfullargspec(self.create_model)) pass_theta_to_initialize= ('theta' in inspect.getfullargspec(self.create_model).args) - #print("pass_theta_to_initialize =", pass_theta_to_initialize) # Allow user to self-define complex design variables self.create_model(mod=mod, model_option=ModelOptionLib.stage1) @@ -617,10 +615,12 @@ def block_build(b, s): # idea: check if create_model takes theta as an optional input, if so, pass parameter values to create_model if pass_theta_to_initialize: + # Grab the values of theta for this scenario/block theta_initialize = self.scenario_data.scenario[s] - #print("Initializing with theta=", theta_initialize) + # Add model on block with theta values self.create_model(mod=b, model_option=ModelOptionLib.stage2, theta=theta_initialize) else: + # Otherwise add model on block without theta values self.create_model(mod=b, model_option=ModelOptionLib.stage2) # fix parameter values to perturbed values @@ -934,8 +934,6 @@ def initialize_jac(m, i, j): for i, bu in enumerate(model.regression_parameters): for j, un in enumerate(model.regression_parameters): dict_fim_initialize[(bu, un)] = self.fim_initial[i][j] - - #print(dict_fim_initialize) def initialize_fim(m, j, d): return dict_fim_initialize[(j, d)] @@ -1226,19 +1224,6 @@ def _solve_doe(self, m, fix=False, opt_option=None): m, self.design_values, fix_opt=fix, optimize_option=opt_option ) - ''' - # This is for initialization diagnostics - # Remove before merging the PR - if not fix: - # halt at initial point - self.solver.options['max_iter'] = 0 - self.solver.options['bound_push'] = 1E-10 - else: - # resort to defaults - self.solver.options['max_iter'] = 3000 - self.solver.options['bound_push'] = 0.01 - ''' - # if user gives solver, use this solver. if not, use default IPOPT solver solver_result = self.solver.solve(mod, tee=self.tee_opt) diff --git a/pyomo/contrib/doe/measurements.py b/pyomo/contrib/doe/measurements.py index ae5b3519498..dcaac1f14fd 100644 --- a/pyomo/contrib/doe/measurements.py +++ b/pyomo/contrib/doe/measurements.py @@ -27,7 +27,7 @@ import itertools import collections.abc - +from pyomo.common.numeric_types import native_numeric_types class VariablesWithIndices: def __init__(self): @@ -100,12 +100,12 @@ def add_variables( # if a scalar (int or float) is given, set it as the lower bound for all variables if lower_bounds is not None: - if type(lower_bounds) in [int, float]: + if type(lower_bounds) in native_numeric_types: lower_bounds = [lower_bounds] * len(added_names) self.lower_bounds.update(zip(added_names, lower_bounds)) if upper_bounds is not None: - if type(upper_bounds) in [int, float]: + if type(upper_bounds) in native_numeric_types: upper_bounds = [upper_bounds] * len(added_names) self.upper_bounds.update(zip(added_names, upper_bounds)) From 845fc3fdcae84180cd7f777887a771f55074e2bf Mon Sep 17 00:00:00 2001 From: Alex Dowling Date: Thu, 25 Apr 2024 21:58:33 -0400 Subject: [PATCH 1675/1797] Ran black --- pyomo/contrib/doe/doe.py | 27 +++++++++++++++++---------- pyomo/contrib/doe/measurements.py | 13 +++++++------ pyomo/contrib/doe/result.py | 2 +- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/pyomo/contrib/doe/doe.py b/pyomo/contrib/doe/doe.py index d10fc1b7fcc..ed5e81027dd 100644 --- a/pyomo/contrib/doe/doe.py +++ b/pyomo/contrib/doe/doe.py @@ -42,6 +42,7 @@ import inspect + class CalculationMode(Enum): sequential_finite = "sequential_finite" direct_kaug = "direct_kaug" @@ -228,7 +229,7 @@ def stochastic_program( # calculate how much the FIM element is scaled by a constant number # FIM = Jacobian.T@Jacobian, the FIM is scaled by squared value the Jacobian is scaled - self.fim_scale_constant_value = self.scale_constant_value**2 + self.fim_scale_constant_value = self.scale_constant_value ** 2 # Start timer sp_timer = TicTocTimer() @@ -241,7 +242,7 @@ def stochastic_program( m, analysis_square = self._compute_stochastic_program(m, optimize_opt) if self.optimize: - # If set to optimize, solve the optimization problem (with degrees of freedom) + # If set to optimize, solve the optimization problem (with degrees of freedom) analysis_optimize = self._optimize_stochastic_program(m) dT = sp_timer.toc(msg=None) self.logger.info("elapsed time: %0.1f seconds" % dT) @@ -382,7 +383,7 @@ def compute_FIM( # calculate how much the FIM element is scaled by a constant number # As FIM~Jacobian.T@Jacobian, FIM is scaled twice the number the Q is scaled - self.fim_scale_constant_value = self.scale_constant_value**2 + self.fim_scale_constant_value = self.scale_constant_value ** 2 square_timer = TicTocTimer() square_timer.tic(msg=None) @@ -594,9 +595,11 @@ def _create_block(self): # Set for block/scenarios mod.scenario = pyo.Set(initialize=self.scenario_data.scenario_indices) - + # Determine if create_model takes theta as an optional input - pass_theta_to_initialize= ('theta' in inspect.getfullargspec(self.create_model).args) + pass_theta_to_initialize = ( + 'theta' in inspect.getfullargspec(self.create_model).args + ) # Allow user to self-define complex design variables self.create_model(mod=mod, model_option=ModelOptionLib.stage1) @@ -618,7 +621,9 @@ def block_build(b, s): # Grab the values of theta for this scenario/block theta_initialize = self.scenario_data.scenario[s] # Add model on block with theta values - self.create_model(mod=b, model_option=ModelOptionLib.stage2, theta=theta_initialize) + self.create_model( + mod=b, model_option=ModelOptionLib.stage2, theta=theta_initialize + ) else: # Otherwise add model on block without theta values self.create_model(mod=b, model_option=ModelOptionLib.stage2) @@ -773,7 +778,7 @@ def run_grid_search( self.store_optimality_as_csv = store_optimality_as_csv # calculate how much the FIM element is scaled - self.fim_scale_constant_value = scale_constant_value**2 + self.fim_scale_constant_value = scale_constant_value ** 2 # to store all FIM results result_combine = {} @@ -926,7 +931,9 @@ def initialize_jac(m, i, j): return 0.1 model.sensitivity_jacobian = pyo.Var( - model.regression_parameters, model.measured_variables, initialize=initialize_jac + model.regression_parameters, + model.measured_variables, + initialize=initialize_jac, ) if self.fim_initial is not None: @@ -1071,7 +1078,7 @@ def _add_objective(self, m): eig = np.linalg.eigvals(fim) # If the smallest eigenvalue is (pratcially) negative, add a diagonal matrix to make it positive definite - small_number = 1E-10 + small_number = 1e-10 if min(eig) < small_number: fim = fim + np.eye(len(self.param)) * (small_number - min(eig)) @@ -1226,7 +1233,7 @@ def _solve_doe(self, m, fix=False, opt_option=None): # if user gives solver, use this solver. if not, use default IPOPT solver solver_result = self.solver.solve(mod, tee=self.tee_opt) - + return solver_result def _sgn(self, p): diff --git a/pyomo/contrib/doe/measurements.py b/pyomo/contrib/doe/measurements.py index dcaac1f14fd..fd3962f7888 100644 --- a/pyomo/contrib/doe/measurements.py +++ b/pyomo/contrib/doe/measurements.py @@ -29,6 +29,7 @@ import collections.abc from pyomo.common.numeric_types import native_numeric_types + class VariablesWithIndices: def __init__(self): """This class provides utility methods for DesignVariables and MeasurementVariables to create @@ -182,16 +183,16 @@ def _check_valid_input( raise ValueError("Values is of different length with indices.") if ( - lower_bounds is not None # ensure not None - and isinstance(lower_bounds, collections.abc.Sequence) # ensure list-like - and len(lower_bounds) != len_indices # ensure same length + lower_bounds is not None # ensure not None + and isinstance(lower_bounds, collections.abc.Sequence) # ensure list-like + and len(lower_bounds) != len_indices # ensure same length ): raise ValueError("Lowerbounds is of different length with indices.") if ( - upper_bounds is not None # ensure None - and isinstance(upper_bounds, collections.abc.Sequence) # ensure list-like - and len(upper_bounds) != len_indices # ensure same length + upper_bounds is not None # ensure None + and isinstance(upper_bounds, collections.abc.Sequence) # ensure list-like + and len(upper_bounds) != len_indices # ensure same length ): raise ValueError("Upperbounds is of different length with indices.") diff --git a/pyomo/contrib/doe/result.py b/pyomo/contrib/doe/result.py index 1593214c30a..8f98e74f159 100644 --- a/pyomo/contrib/doe/result.py +++ b/pyomo/contrib/doe/result.py @@ -81,7 +81,7 @@ def __init__( self.prior_FIM = prior_FIM self.store_FIM = store_FIM self.scale_constant_value = scale_constant_value - self.fim_scale_constant_value = scale_constant_value**2 + self.fim_scale_constant_value = scale_constant_value ** 2 self.max_condition_number = max_condition_number self.logger = logging.getLogger(__name__) self.logger.setLevel(level=logging.WARN) From 5c7cab5bb4629f2dc5ed6dde08d006c78b3735bf Mon Sep 17 00:00:00 2001 From: Alex Dowling Date: Thu, 25 Apr 2024 21:59:45 -0400 Subject: [PATCH 1676/1797] Reran black. --- pyomo/contrib/doe/doe.py | 6 +++--- pyomo/contrib/doe/result.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/doe/doe.py b/pyomo/contrib/doe/doe.py index ed5e81027dd..0d2c7c2f982 100644 --- a/pyomo/contrib/doe/doe.py +++ b/pyomo/contrib/doe/doe.py @@ -229,7 +229,7 @@ def stochastic_program( # calculate how much the FIM element is scaled by a constant number # FIM = Jacobian.T@Jacobian, the FIM is scaled by squared value the Jacobian is scaled - self.fim_scale_constant_value = self.scale_constant_value ** 2 + self.fim_scale_constant_value = self.scale_constant_value**2 # Start timer sp_timer = TicTocTimer() @@ -383,7 +383,7 @@ def compute_FIM( # calculate how much the FIM element is scaled by a constant number # As FIM~Jacobian.T@Jacobian, FIM is scaled twice the number the Q is scaled - self.fim_scale_constant_value = self.scale_constant_value ** 2 + self.fim_scale_constant_value = self.scale_constant_value**2 square_timer = TicTocTimer() square_timer.tic(msg=None) @@ -778,7 +778,7 @@ def run_grid_search( self.store_optimality_as_csv = store_optimality_as_csv # calculate how much the FIM element is scaled - self.fim_scale_constant_value = scale_constant_value ** 2 + self.fim_scale_constant_value = scale_constant_value**2 # to store all FIM results result_combine = {} diff --git a/pyomo/contrib/doe/result.py b/pyomo/contrib/doe/result.py index 8f98e74f159..1593214c30a 100644 --- a/pyomo/contrib/doe/result.py +++ b/pyomo/contrib/doe/result.py @@ -81,7 +81,7 @@ def __init__( self.prior_FIM = prior_FIM self.store_FIM = store_FIM self.scale_constant_value = scale_constant_value - self.fim_scale_constant_value = scale_constant_value ** 2 + self.fim_scale_constant_value = scale_constant_value**2 self.max_condition_number = max_condition_number self.logger = logging.getLogger(__name__) self.logger.setLevel(level=logging.WARN) From a8a5450fc5021b1c30daafcdb282f0e936a8d9ad Mon Sep 17 00:00:00 2001 From: Alex Dowling Date: Thu, 25 Apr 2024 22:06:38 -0400 Subject: [PATCH 1677/1797] Added more comments. --- pyomo/contrib/doe/doe.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyomo/contrib/doe/doe.py b/pyomo/contrib/doe/doe.py index 0d2c7c2f982..28dad1f20c7 100644 --- a/pyomo/contrib/doe/doe.py +++ b/pyomo/contrib/doe/doe.py @@ -913,6 +913,9 @@ def identity_matrix(m, i, j): else: return 0 + ### Initialize the Jacobian if provided by the user + + # If the user provides an initial Jacobian, convert it to a dictionary if self.jac_initial is not None: dict_jac_initialize = {} for i, bu in enumerate(model.regression_parameters): @@ -924,9 +927,12 @@ def identity_matrix(m, i, j): # Jacobian is a numpy array, rows are regression parameters, columns are measured variables dict_jac_initialize[(bu, un)] = self.jac_initial[i][j] + # Initialize the Jacobian matrix def initialize_jac(m, i, j): + # If provided by the user, use the values now stored in the dictionary if self.jac_initial is not None: return dict_jac_initialize[(i, j)] + # Otherwise initialize to 0.1 (which is an arbitrary non-zero value) else: return 0.1 From 1bac09e842eaaeeeaef8191e30e500f2e311fefb Mon Sep 17 00:00:00 2001 From: Alex Dowling Date: Thu, 25 Apr 2024 22:21:24 -0400 Subject: [PATCH 1678/1797] Reran black --- pyomo/contrib/doe/doe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/doe/doe.py b/pyomo/contrib/doe/doe.py index 28dad1f20c7..ab9a5ad9f85 100644 --- a/pyomo/contrib/doe/doe.py +++ b/pyomo/contrib/doe/doe.py @@ -914,7 +914,7 @@ def identity_matrix(m, i, j): return 0 ### Initialize the Jacobian if provided by the user - + # If the user provides an initial Jacobian, convert it to a dictionary if self.jac_initial is not None: dict_jac_initialize = {} From f4e989fc390cd08ec068dcdcb2b1826683bbb88d Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 29 Apr 2024 14:37:24 -0600 Subject: [PATCH 1679/1797] Add fileutils patch so find_library returns absolute path on Linux --- pyomo/common/fileutils.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/pyomo/common/fileutils.py b/pyomo/common/fileutils.py index 2cade36154d..80fd47a6377 100644 --- a/pyomo/common/fileutils.py +++ b/pyomo/common/fileutils.py @@ -38,6 +38,7 @@ import os import platform import importlib.util +import subprocess import sys from . import envvar @@ -375,9 +376,25 @@ def find_library(libname, cwd=True, include_PATH=True, pathlist=None): if libname_base.startswith('lib') and _system() != 'windows': libname_base = libname_base[3:] if ext.lower().startswith(('.so', '.dll', '.dylib')): - return ctypes.util.find_library(libname_base) + lib = ctypes.util.find_library(libname_base) else: - return ctypes.util.find_library(libname) + lib = ctypes.util.find_library(libname) + if lib and os.path.sep not in lib: + # work around https://github.com/python/cpython/issues/65241, + # where python does not return the absolute path on *nix + try: + libname = lib + ' ' + with subprocess.Popen(['/sbin/ldconfig', '-p'], + stdin=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + stdout=subprocess.PIPE, + env={'LC_ALL': 'C', 'LANG': 'C'}) as p: + for line in os.fsdecode(p.stdout.read()).splitlines(): + if line.lstrip().startswith(libname): + return os.path.realpath(line.split()[-1]) + except: + pass + return lib def find_executable(exename, cwd=True, include_PATH=True, pathlist=None): From ebb4d0768f30dbfaf09d56db55f7973ce6bb554a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 29 Apr 2024 14:38:21 -0600 Subject: [PATCH 1680/1797] bugfix: add missing import --- pyomo/contrib/simplification/build.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/contrib/simplification/build.py b/pyomo/contrib/simplification/build.py index 2c7b1830ff6..79bc9970241 100644 --- a/pyomo/contrib/simplification/build.py +++ b/pyomo/contrib/simplification/build.py @@ -17,6 +17,7 @@ from distutils.dist import Distribution from pybind11.setup_helpers import Pybind11Extension, build_ext +from pyomo.common.cmake_builder import handleReadonly from pyomo.common.envvar import PYOMO_CONFIG_DIR from pyomo.common.fileutils import find_library, this_file_dir From 16d49e13605cf1c8c4a3ba1a4079b5d751d55791 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 29 Apr 2024 14:38:54 -0600 Subject: [PATCH 1681/1797] NFC: clarify ginac builder exception message --- pyomo/contrib/simplification/build.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/simplification/build.py b/pyomo/contrib/simplification/build.py index 79bc9970241..508acb2d5a1 100644 --- a/pyomo/contrib/simplification/build.py +++ b/pyomo/contrib/simplification/build.py @@ -34,7 +34,9 @@ def build_ginac_interface(args=None): ginac_lib = find_library('ginac') if ginac_lib is None: raise RuntimeError( - 'could not find GiNaC library; please make sure it is in the LD_LIBRARY_PATH environment variable' + 'could not find the GiNaC library; please make sure either to install ' + 'the library and development headers system-wide, or include the ' + 'path tt the library in the LD_LIBRARY_PATH environment variable' ) ginac_lib_dir = os.path.dirname(ginac_lib) ginac_build_dir = os.path.dirname(ginac_lib_dir) @@ -45,7 +47,9 @@ def build_ginac_interface(args=None): cln_lib = find_library('cln') if cln_lib is None: raise RuntimeError( - 'could not find CLN library; please make sure it is in the LD_LIBRARY_PATH environment variable' + 'could not find the CLN library; please make sure either to install ' + 'the library and development headers system-wide, or include the ' + 'path tt the library in the LD_LIBRARY_PATH environment variable' ) cln_lib_dir = os.path.dirname(cln_lib) cln_build_dir = os.path.dirname(cln_lib_dir) From bd3299d4e50ac307947a055a2723ea85bdc6c534 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 29 Apr 2024 14:40:15 -0600 Subject: [PATCH 1682/1797] Register the GiNaC interface builder with the ExtensionBuilder --- pyomo/contrib/simplification/build.py | 8 ++++++++ pyomo/contrib/simplification/plugins.py | 17 +++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 pyomo/contrib/simplification/plugins.py diff --git a/pyomo/contrib/simplification/build.py b/pyomo/contrib/simplification/build.py index 508acb2d5a1..4952ac6dade 100644 --- a/pyomo/contrib/simplification/build.py +++ b/pyomo/contrib/simplification/build.py @@ -109,5 +109,13 @@ def run(self): dist.run_command('build_ext') +class GiNaCInterfaceBuilder(object): + def __call__(self, parallel): + return build_ginac_interface() + + def skip(self): + return not find_library('ginac') + + if __name__ == '__main__': build_ginac_interface(sys.argv[1:]) diff --git a/pyomo/contrib/simplification/plugins.py b/pyomo/contrib/simplification/plugins.py new file mode 100644 index 00000000000..6b08f7be4d7 --- /dev/null +++ b/pyomo/contrib/simplification/plugins.py @@ -0,0 +1,17 @@ +# ___________________________________________________________________________ +# +# 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.extensions import ExtensionBuilderFactory +from .build import GiNaCInterfaceBuilder + + +def load(): + ExtensionBuilderFactory.register('ginac')(GiNaCInterfaceBuilder) From 29b207246e4ca83f26c96fec1989b6cea24b79e9 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 29 Apr 2024 14:41:22 -0600 Subject: [PATCH 1683/1797] Rework GiNaC interface builder (in development - testing several things) - attempt to install from OS package repo - attempt local build ginac, add the built library to the download cache --- .github/workflows/test_branches.yml | 56 ++++++++++++++--------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 3bfb902b9e0..a23a603430c 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -157,24 +157,6 @@ jobs: # path: cache/os # key: pkg-${{env.CACHE_VER}}.0-${{runner.os}} - - name: install GiNaC - if: matrix.other == '/singletest' - run: | - cd .. - curl https://www.ginac.de/CLN/cln-1.3.7.tar.bz2 >cln-1.3.7.tar.bz2 - tar -xvf cln-1.3.7.tar.bz2 - cd cln-1.3.7 - ./configure - make -j 2 - sudo make install - cd .. - curl https://www.ginac.de/ginac-1.8.7.tar.bz2 >ginac-1.8.7.tar.bz2 - tar -xvf ginac-1.8.7.tar.bz2 - cd ginac-1.8.7 - ./configure - make -j 2 - sudo make install - - name: TPL package download cache uses: actions/cache@v4 if: ${{ ! matrix.slim }} @@ -206,7 +188,7 @@ jobs: # Notes: # - install glpk # - pyodbc needs: gcc pkg-config unixodbc freetds - for pkg in bash pkg-config unixodbc freetds glpk; do + for pkg in bash pkg-config unixodbc freetds glpk ginac; do brew list $pkg || brew install $pkg done @@ -218,7 +200,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 libginac-dev sudo chmod -R 777 ${GITHUB_WORKSPACE}/cache/os - name: Update Windows @@ -581,6 +563,32 @@ jobs: echo "$GJH_DIR" ls -l $GJH_DIR + - name: Install GiNaC + if: ${{ ! matrix.slim }} + run: | + if test ! -e "${DOWNLOAD_DIR}/ginac.tar.gz"; then + mkdir -p "${GITHUB_WORKSPACE}/cache/build/ginac" + cd "${GITHUB_WORKSPACE}/cache/build/ginac" + curl https://www.ginac.de/CLN/cln-1.3.7.tar.bz2 >cln-1.3.7.tar.bz2 + tar -xvf cln-1.3.7.tar.bz2 + cd cln-1.3.7 + ./configure --prefix "$TPL_DIR/ginac" --disable-static + make -j 4 + make install + cd "${GITHUB_WORKSPACE}/cache/build/ginac" + curl https://www.ginac.de/ginac-1.8.7.tar.bz2 >ginac-1.8.7.tar.bz2 + tar -xvf ginac-1.8.7.tar.bz2 + cd ginac-1.8.7 + ./configure --prefix "$TPL_DIR/ginac" --disable-static + make -j 4 + make install + cd "$TPL_DIR" + tar -czf "${DOWNLOAD_DIR}/ginac.tar.gz" ginac + else + cd "$TPL_DIR" + tar -xzf "${DOWNLOAD_DIR}/ginac.tar.gz" + fi + - name: Install Pyomo run: | echo "" @@ -635,14 +643,6 @@ jobs: echo "" pyomo build-extensions --parallel 2 - - name: Install GiNaC Interface - if: matrix.other == '/singletest' - run: | - export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH - echo "LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV - cd pyomo/contrib/simplification/ - $PYTHON_EXE build.py --inplace - - name: Report pyomo plugin information run: | echo "$PATH" From 1b1f944dbd00ca0e0b27d2fd2b70ee681b8e44ae Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Mon, 29 Apr 2024 16:58:04 -0600 Subject: [PATCH 1684/1797] bugfix --- pyomo/contrib/simplification/simplify.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/simplification/simplify.py b/pyomo/contrib/simplification/simplify.py index 5e251ca326a..27da5f5ca34 100644 --- a/pyomo/contrib/simplification/simplify.py +++ b/pyomo/contrib/simplification/simplify.py @@ -11,7 +11,7 @@ from pyomo.core.expr.sympy_tools import sympy2pyomo_expression, sympyify_expression from pyomo.core.expr.numeric_expr import NumericExpression -from pyomo.core.expr.numvalue import is_fixed, value +from pyomo.core.expr.numvalue import value, is_constant import logging import warnings @@ -28,15 +28,19 @@ def simplify_with_sympy(expr: NumericExpression): + if is_constant(expr): + return value(expr) om, se = sympyify_expression(expr) se = se.simplify() new_expr = sympy2pyomo_expression(se, om) - if is_fixed(new_expr): + if is_constant(new_expr): new_expr = value(new_expr) return new_expr def simplify_with_ginac(expr: NumericExpression, ginac_interface): + if is_constant(expr): + return value(expr) gi = ginac_interface ginac_expr = gi.to_ginac(expr) ginac_expr = ginac_expr.normal() From 74052eebd65a15cf03f36ade73846ec9fcf048e5 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 29 Apr 2024 21:58:32 -0600 Subject: [PATCH 1685/1797] Disable local build of GiNaC --- .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 5410c58495f..d1b6b96807b 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -565,7 +565,7 @@ jobs: ls -l $GJH_DIR - name: Install GiNaC - if: ${{ ! matrix.slim }} + if: ${{ 0 && ! matrix.slim }} run: | if test ! -e "${DOWNLOAD_DIR}/ginac.tar.gz"; then mkdir -p "${GITHUB_WORKSPACE}/cache/build/ginac" From 6bb0f7f375e7a10d4daa05841c4dd5544eff99ff Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 29 Apr 2024 21:59:34 -0600 Subject: [PATCH 1686/1797] NFC: apply black --- pyomo/common/fileutils.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pyomo/common/fileutils.py b/pyomo/common/fileutils.py index 80fd47a6377..7b6520327a0 100644 --- a/pyomo/common/fileutils.py +++ b/pyomo/common/fileutils.py @@ -384,11 +384,13 @@ def find_library(libname, cwd=True, include_PATH=True, pathlist=None): # where python does not return the absolute path on *nix try: libname = lib + ' ' - with subprocess.Popen(['/sbin/ldconfig', '-p'], - stdin=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - stdout=subprocess.PIPE, - env={'LC_ALL': 'C', 'LANG': 'C'}) as p: + with subprocess.Popen( + ['/sbin/ldconfig', '-p'], + stdin=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + stdout=subprocess.PIPE, + env={'LC_ALL': 'C', 'LANG': 'C'}, + ) as p: for line in os.fsdecode(p.stdout.read()).splitlines(): if line.lstrip().startswith(libname): return os.path.realpath(line.split()[-1]) From 39643b336ef3149fe7f57007ffaf31b684b6151d Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 30 Apr 2024 00:05:00 -0600 Subject: [PATCH 1687/1797] remove repeated code --- pyomo/contrib/appsi/build.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/pyomo/contrib/appsi/build.py b/pyomo/contrib/appsi/build.py index b3d78467f01..38f8cb713ca 100644 --- a/pyomo/contrib/appsi/build.py +++ b/pyomo/contrib/appsi/build.py @@ -16,15 +16,6 @@ import tempfile -def handleReadonly(function, path, excinfo): - excvalue = excinfo[1] - if excvalue.errno == errno.EACCES: - os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) # 0777 - function(path) - else: - raise - - def get_appsi_extension(in_setup=False, appsi_root=None): from pybind11.setup_helpers import Pybind11Extension @@ -66,6 +57,7 @@ def build_appsi(args=[]): from setuptools import Distribution from pybind11.setup_helpers import build_ext import pybind11.setup_helpers + from pyomo.common.cmake_builder import handleReadonly from pyomo.common.envvar import PYOMO_CONFIG_DIR from pyomo.common.fileutils import this_file_dir From 89ace9bed0c1a0366ac14dca0d627df0a89b1f01 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 30 Apr 2024 00:18:31 -0600 Subject: [PATCH 1688/1797] Add support for download tar archives to theFileDownloader --- pyomo/common/download.py | 48 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/pyomo/common/download.py b/pyomo/common/download.py index 5332287cfc7..95713e9ef76 100644 --- a/pyomo/common/download.py +++ b/pyomo/common/download.py @@ -29,6 +29,7 @@ urllib_error = attempt_import('urllib.error')[0] ssl = attempt_import('ssl')[0] zipfile = attempt_import('zipfile')[0] +tarfile = attempt_import('tarfile')[0] gzip = attempt_import('gzip')[0] distro, distro_available = attempt_import('distro') @@ -371,7 +372,7 @@ def get_zip_archive(self, url, dirOffset=0): # Simple sanity checks for info in zip_file.infolist(): f = info.filename - if f[0] in '\\/' or '..' in f: + if f[0] in '\\/' or '..' in f or os.path.isabs(f): logger.error( "malformed (potentially insecure) filename (%s) " "found in zip archive. Skipping file." % (f,) @@ -387,6 +388,51 @@ def get_zip_archive(self, url, dirOffset=0): info.filename = target[-1] + '/' if f[-1] == '/' else target[-1] zip_file.extract(f, os.path.join(self._fname, *tuple(target[dirOffset:-1]))) + def get_tar_archive(self, url, dirOffset=0): + if self._fname is None: + raise DeveloperError( + "target file name has not been initialized " + "with set_destination_filename" + ) + if os.path.exists(self._fname) and not os.path.isdir(self._fname): + raise RuntimeError( + "Target directory (%s) exists, but is not a directory" % (self._fname,) + ) + tar_file = tarfile.open(fileobj=io.BytesIO(self.retrieve_url(url))) + dest = os.path.realpath(self._fname) + + def filter_fcn(info): + # this mocks up the `tarfile` filter introduced in Python + # 3.12 and backported to later releases of Python (e.g., + # 3.8.17, 3.9.17, 3.10.12, and 3.11.4) + f = info.name + if os.path.isabs(f) or '..' in f or f.startswith(('/', os.sep)): + logger.error( + "malformed (potentially insecure) filename (%s) " + "found in tar archive. Skipping file." % (f,) + ) + return False + target = os.path.realpath(os.path.join(dest, f)) + if os.path.commonpath([target, dest]) != dest: + logger.error( + "malformed (potentially insecure) filename (%s) " + "found in zip archive. Skipping file." % (f,) + ) + return False + target = self._splitpath(f) + if len(target) <= dirOffset: + if not info.isdir(): + logger.warning( + "Skipping file (%s) in zip archive due to dirOffset" % (f,) + ) + return False + info.name = '/'.join(target[dirOffset:]) + # Strip high bits & group/other write bits + info.mode &= 0o755 + return True + + tar_file.extractall(dest, filter(filter_fcn, tar_file.getmembers())) + def get_gzipped_binary_file(self, url): if self._fname is None: raise DeveloperError( From bb274ff9d30960f72ffbbf001850d64a14caa6f6 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 30 Apr 2024 00:20:08 -0600 Subject: [PATCH 1689/1797] Switch GiNaC interface builder to use TempfileManager --- pyomo/contrib/simplification/build.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/pyomo/contrib/simplification/build.py b/pyomo/contrib/simplification/build.py index 4952ac6dade..d30f582bcea 100644 --- a/pyomo/contrib/simplification/build.py +++ b/pyomo/contrib/simplification/build.py @@ -71,13 +71,13 @@ def build_ginac_interface(args=None): class ginacBuildExt(build_ext): def run(self): basedir = os.path.abspath(os.path.curdir) - if self.inplace: - tmpdir = this_file_dir() - else: - tmpdir = os.path.abspath(tempfile.mkdtemp()) - print("Building in '%s'" % tmpdir) - os.chdir(tmpdir) - try: + with TempfileManager.new_context() as tempfile: + if self.inplace: + tmpdir = this_file_dir() + else: + tmpdir = os.path.abspath(tempfile.mkdtemp()) + print("Building in '%s'" % tmpdir) + os.chdir(tmpdir) super(ginacBuildExt, self).run() if not self.inplace: library = glob.glob("build/*/ginac_interface.*")[0] @@ -91,10 +91,6 @@ def run(self): if not os.path.exists(target): os.makedirs(target) shutil.copy(library, target) - finally: - os.chdir(basedir) - if not self.inplace: - shutil.rmtree(tmpdir, onerror=handleReadonly) package_config = { 'name': 'ginac_interface', From adaefbca72f94548b33328b7a28c2e03a4012a9d Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 30 Apr 2024 00:20:39 -0600 Subject: [PATCH 1690/1797] Add function for downloading and installing GiNaC and CLN --- pyomo/contrib/simplification/build.py | 83 ++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/simplification/build.py b/pyomo/contrib/simplification/build.py index d30f582bcea..3ea7748cdbf 100644 --- a/pyomo/contrib/simplification/build.py +++ b/pyomo/contrib/simplification/build.py @@ -10,19 +10,71 @@ # ___________________________________________________________________________ import glob +import logging import os import shutil import sys -import tempfile -from distutils.dist import Distribution +import subprocess -from pybind11.setup_helpers import Pybind11Extension, build_ext -from pyomo.common.cmake_builder import handleReadonly +from pyomo.common.download import FileDownloader from pyomo.common.envvar import PYOMO_CONFIG_DIR from pyomo.common.fileutils import find_library, this_file_dir +from pyomo.common.tempfiles import TempfileManager -def build_ginac_interface(args=None): +logger = logging.getLogger(__name__) + + +def build_ginac_library(parallel=None, argv=None): + print("\n**** Building GiNaC library ****") + + configure_cmd = ['configure', '--prefix=' + PYOMO_CONFIG_DIR, '--disable-static'] + make_cmd = ['make'] + if parallel: + make_cmd.append(f'-j{parallel}') + install_cmd = ['make', 'install'] + + with TempfileManager.new_context() as tempfile: + tmpdir = tempfile.mkdtemp() + + downloader = FileDownloader() + if argv: + downloader.parse_args(argv) + + url = 'https://www.ginac.de/CLN/cln-1.3.7.tar.bz2' + cln_dir = os.path.join(tmpdir, 'cln') + downloader.set_destination_filename(cln_dir) + logger.info( + "Fetching CLN from %s and installing it to %s" + % (url, downloader.destination()) + ) + downloader.get_tar_archive(url, dirOffset=1) + assert subprocess.run(configure_cmd, cwd=cln_dir).returncode == 0 + logger.info("\nBuilding CLN\n") + assert subprocess.run(make_cmd, cwd=cln_dir).returncode == 0 + assert subprocess.run(install_cmd, cwd=cln_dir).returncode == 0 + + url = 'https://www.ginac.de/ginac-1.8.7.tar.bz2' + ginac_dir = os.path.join(tmpdir, 'ginac') + downloader.set_destination_filename(ginac_dir) + logger.info( + "Fetching GiNaC from %s and installing it to %s" + % (url, downloader.destination()) + ) + downloader.get_tar_archive(url, dirOffset=1) + assert subprocess.run(configure_cmd, cwd=ginac_dir).returncode == 0 + logger.info("\nBuilding GiNaC\n") + assert subprocess.run(make_cmd, cwd=ginac_dir).returncode == 0 + assert subprocess.run(install_cmd, cwd=ginac_dir).returncode == 0 + + +def build_ginac_interface(parallel=None, args=None): + from distutils.dist import Distribution + from pybind11.setup_helpers import Pybind11Extension, build_ext + from pyomo.common.cmake_builder import handleReadonly + + print("\n**** Building GiNaC interface ****") + if args is None: args = list() dname = this_file_dir() @@ -107,11 +159,28 @@ def run(self): class GiNaCInterfaceBuilder(object): def __call__(self, parallel): - return build_ginac_interface() + return build_ginac_interface(parallel) def skip(self): return not find_library('ginac') if __name__ == '__main__': - build_ginac_interface(sys.argv[1:]) + logging.getLogger('pyomo').setLevel(logging.DEBUG) + parallel = None + for i, arg in enumerate(sys.argv): + if arg == '-j': + parallel = int(sys.argv.pop(i + 1)) + sys.argv.pop(i) + break + if arg.startswith('-j'): + if '=' in arg: + parallel = int(arg.split('=')[1]) + else: + parallel = int(arg[2:]) + sys.argv.pop(i) + break + if '--build-deps' in sys.argv: + sys.argv.remove('--build-deps') + build_ginac_library(parallel, []) + build_ginac_interface(parallel, sys.argv[1:]) From c7a8f8e243c2a51bf5e74803b1fc118fd4060816 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 30 Apr 2024 00:21:10 -0600 Subject: [PATCH 1691/1797] Hook GiNaC builder into pyomo command --- pyomo/environ/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/environ/__init__.py b/pyomo/environ/__init__.py index c1ceb8cb890..07b3dfad680 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.simplification', 'pyomo.contrib.solver', 'pyomo.contrib.trustregion', ] From a29bb3ed8149dc3b1221ce858cce5640716e5c14 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 30 Apr 2024 00:23:53 -0600 Subject: [PATCH 1692/1797] Remove simplification test marker --- .github/workflows/test_branches.yml | 5 ----- pyomo/contrib/simplification/tests/test_simplification.py | 1 - setup.cfg | 1 - 3 files changed, 7 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index d1b6b96807b..558d6dc2591 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -651,11 +651,6 @@ jobs: pyomo help --transformations || exit 1 pyomo help --writers || exit 1 - - name: Run Simplification Tests - if: matrix.other == '/singletest' - run: | - pytest -v -m 'simplification' pyomo/contrib/simplification/tests/test_simplification.py --junitxml="TEST-pyomo-simplify.xml" - - name: Run Pyomo tests if: matrix.mpi == 0 run: | diff --git a/pyomo/contrib/simplification/tests/test_simplification.py b/pyomo/contrib/simplification/tests/test_simplification.py index 1a5ae1e0036..be61631e9f3 100644 --- a/pyomo/contrib/simplification/tests/test_simplification.py +++ b/pyomo/contrib/simplification/tests/test_simplification.py @@ -106,7 +106,6 @@ class TestSimplificationSympy(TestCase, SimplificationMixin): @unittest.skipIf(not ginac_available, 'GiNaC is not available') -@unittest.pytest.mark.simplification class TestSimplificationGiNaC(TestCase, SimplificationMixin): def test_param(self): m = pe.ConcreteModel() diff --git a/setup.cfg b/setup.cfg index 855717490b3..b606138f38c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,4 +22,3 @@ markers = lp: marks lp tests gams: marks gams tests bar: marks bar tests - simplification: tests for expression simplification that have expensive (to install) dependencies From c475fe791ff6c60aa75ae8eb87a5cabb8d8786ab Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 30 Apr 2024 00:33:38 -0600 Subject: [PATCH 1693/1797] Switching output to sys.stdout, adding debugging --- pyomo/contrib/simplification/build.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/simplification/build.py b/pyomo/contrib/simplification/build.py index 3ea7748cdbf..5e613cf873f 100644 --- a/pyomo/contrib/simplification/build.py +++ b/pyomo/contrib/simplification/build.py @@ -26,7 +26,7 @@ def build_ginac_library(parallel=None, argv=None): - print("\n**** Building GiNaC library ****") + sys.stdout.write("\n**** Building GiNaC library ****") configure_cmd = ['configure', '--prefix=' + PYOMO_CONFIG_DIR, '--disable-static'] make_cmd = ['make'] @@ -73,7 +73,7 @@ def build_ginac_interface(parallel=None, args=None): from pybind11.setup_helpers import Pybind11Extension, build_ext from pyomo.common.cmake_builder import handleReadonly - print("\n**** Building GiNaC interface ****") + sys.stdout.write("\n**** Building GiNaC interface ****") if args is None: args = list() @@ -90,6 +90,7 @@ def build_ginac_interface(parallel=None, args=None): 'the library and development headers system-wide, or include the ' 'path tt the library in the LD_LIBRARY_PATH environment variable' ) + print("Found GiNaC library:", ginac_lib) ginac_lib_dir = os.path.dirname(ginac_lib) ginac_build_dir = os.path.dirname(ginac_lib_dir) ginac_include_dir = os.path.join(ginac_build_dir, 'include') @@ -103,6 +104,7 @@ def build_ginac_interface(parallel=None, args=None): 'the library and development headers system-wide, or include the ' 'path tt the library in the LD_LIBRARY_PATH environment variable' ) + print("Found CLN library:", cln_lib) cln_lib_dir = os.path.dirname(cln_lib) cln_build_dir = os.path.dirname(cln_lib_dir) cln_include_dir = os.path.join(cln_build_dir, 'include') @@ -128,7 +130,7 @@ def run(self): tmpdir = this_file_dir() else: tmpdir = os.path.abspath(tempfile.mkdtemp()) - print("Building in '%s'" % tmpdir) + sys.stdout.write("Building in '%s'" % tmpdir) os.chdir(tmpdir) super(ginacBuildExt, self).run() if not self.inplace: From 7053690e90b0a693ec8901d9b4c73279a85a41aa Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 30 Apr 2024 00:45:14 -0600 Subject: [PATCH 1694/1797] Support walking up the directory tree looking for ginac headers (this should better support debian system installations) --- pyomo/contrib/simplification/build.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/pyomo/contrib/simplification/build.py b/pyomo/contrib/simplification/build.py index 5e613cf873f..e6f300ae058 100644 --- a/pyomo/contrib/simplification/build.py +++ b/pyomo/contrib/simplification/build.py @@ -68,6 +68,16 @@ def build_ginac_library(parallel=None, argv=None): assert subprocess.run(install_cmd, cwd=ginac_dir).returncode == 0 +def _find_include(libdir, incpaths): + while 1: + basedir = os.path.dirname(libdir) + if not basedir or basedir == libdir: + return None + if os.path.exists(os.path.join(basedir, *incpaths)): + return os.path.join(basedir, *(incpaths[:-1]))): + libdir = basedir + + def build_ginac_interface(parallel=None, args=None): from distutils.dist import Distribution from pybind11.setup_helpers import Pybind11Extension, build_ext @@ -90,11 +100,9 @@ def build_ginac_interface(parallel=None, args=None): 'the library and development headers system-wide, or include the ' 'path tt the library in the LD_LIBRARY_PATH environment variable' ) - print("Found GiNaC library:", ginac_lib) ginac_lib_dir = os.path.dirname(ginac_lib) - ginac_build_dir = os.path.dirname(ginac_lib_dir) - ginac_include_dir = os.path.join(ginac_build_dir, 'include') - if not os.path.exists(os.path.join(ginac_include_dir, 'ginac', 'ginac.h')): + ginac_include_dir = _find_include(ginac_lib_dir, ('ginac', 'ginac.h')) + if not ginac_include_dir: raise RuntimeError('could not find GiNaC include directory') cln_lib = find_library('cln') @@ -104,11 +112,9 @@ def build_ginac_interface(parallel=None, args=None): 'the library and development headers system-wide, or include the ' 'path tt the library in the LD_LIBRARY_PATH environment variable' ) - print("Found CLN library:", cln_lib) cln_lib_dir = os.path.dirname(cln_lib) - cln_build_dir = os.path.dirname(cln_lib_dir) - cln_include_dir = os.path.join(cln_build_dir, 'include') - if not os.path.exists(os.path.join(cln_include_dir, 'cln', 'cln.h')): + cln_include_dir = _find_include(cln_lib_dir, ('cln', 'cln.h')) + if cln_include_dir: raise RuntimeError('could not find CLN include directory') extra_args = ['-std=c++11'] From 7126fb7a0258524d4a1233fd16a5931ef064fe08 Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Tue, 30 Apr 2024 10:57:09 +0200 Subject: [PATCH 1695/1797] Modified two tests --- .../appsi/solvers/tests/test_persistent_solvers.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index d6df1710a03..d38563844ff 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -1026,7 +1026,7 @@ def test_time_limit( self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) - if not opt.available(): + if not opt.available() or opt_class == MAiNGO: raise unittest.SkipTest from sys import platform @@ -1210,20 +1210,23 @@ def test_fixed_binaries( m.obj = pe.Objective(expr=m.y) m.c = pe.Constraint(expr=m.y >= m.x) m.x.fix(0) + + if type(opt) is MAiNGO: + opt.config.mip_gap = 1e-6 res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 0, 6) + self.assertAlmostEqual(res.best_feasible_objective, 0) m.x.fix(1) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 1, 6) + self.assertAlmostEqual(res.best_feasible_objective, 1) 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.best_feasible_objective, 0, 6) + self.assertAlmostEqual(res.best_feasible_objective, 0) m.x.fix(1) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 1, 6) + self.assertAlmostEqual(res.best_feasible_objective, 1) @parameterized.expand(input=_load_tests(mip_solvers, only_child_vars_options)) def test_with_gdp( From 673fd372e55c54496cd01446e248ba9ab3d96024 Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Tue, 30 Apr 2024 12:33:04 +0200 Subject: [PATCH 1696/1797] Modify test_persistent_solvers.py --- pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index d38563844ff..3db2ae3cba1 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -1211,7 +1211,7 @@ def test_fixed_binaries( m.c = pe.Constraint(expr=m.y >= m.x) m.x.fix(0) - if type(opt) is MAiNGO: + if opt_class == MAiNGO: opt.config.mip_gap = 1e-6 res = opt.solve(m) self.assertAlmostEqual(res.best_feasible_objective, 0) From 0127d29aeadf2034fde7b95b962d89c6c58488a6 Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Tue, 30 Apr 2024 13:21:54 +0200 Subject: [PATCH 1697/1797] Set default mipgap higher --- pyomo/contrib/appsi/solvers/maingo.py | 4 ++-- pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/maingo.py b/pyomo/contrib/appsi/solvers/maingo.py index 944673be53d..e52130061f7 100644 --- a/pyomo/contrib/appsi/solvers/maingo.py +++ b/pyomo/contrib/appsi/solvers/maingo.py @@ -110,7 +110,7 @@ def __init__( 'epsilonA', ConfigValue( domain=NonNegativeFloat, - default=1e-4, + default=1e-5, description="Absolute optimality tolerance", ), ) @@ -118,7 +118,7 @@ def __init__( 'epsilonR', ConfigValue( domain=NonNegativeFloat, - default=1e-4, + default=1e-5, description="Relative optimality tolerance", ), ) diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 3db2ae3cba1..6ab36ccc981 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -1211,8 +1211,6 @@ def test_fixed_binaries( m.c = pe.Constraint(expr=m.y >= m.x) m.x.fix(0) - if opt_class == MAiNGO: - opt.config.mip_gap = 1e-6 res = opt.solve(m) self.assertAlmostEqual(res.best_feasible_objective, 0) m.x.fix(1) From 389740c54cdae96ef27375fc2b03e59c9002d036 Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Tue, 30 Apr 2024 13:42:54 +0200 Subject: [PATCH 1698/1797] Modify test_fixed_binaries --- .../appsi/solvers/tests/test_persistent_solvers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 6ab36ccc981..7ff193b38e4 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -1212,19 +1212,19 @@ def test_fixed_binaries( m.x.fix(0) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 0) + self.assertAlmostEqual(res.best_feasible_objective, 0, 6) m.x.fix(1) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 1) + self.assertAlmostEqual(res.best_feasible_objective, 1, 6) 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.best_feasible_objective, 0) + self.assertAlmostEqual(res.best_feasible_objective, 0, 6) m.x.fix(1) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 1) + self.assertAlmostEqual(res.best_feasible_objective, 1, 6) @parameterized.expand(input=_load_tests(mip_solvers, only_child_vars_options)) def test_with_gdp( From be6922453be5ef7da898235b122d251d1222ad04 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 30 Apr 2024 07:31:44 -0600 Subject: [PATCH 1699/1797] Fix several typos / include search logic --- pyomo/contrib/simplification/build.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/pyomo/contrib/simplification/build.py b/pyomo/contrib/simplification/build.py index e6f300ae058..fb571c273eb 100644 --- a/pyomo/contrib/simplification/build.py +++ b/pyomo/contrib/simplification/build.py @@ -69,12 +69,13 @@ def build_ginac_library(parallel=None, argv=None): def _find_include(libdir, incpaths): + rel_path = ('include',) + incpaths while 1: basedir = os.path.dirname(libdir) if not basedir or basedir == libdir: return None - if os.path.exists(os.path.join(basedir, *incpaths)): - return os.path.join(basedir, *(incpaths[:-1]))): + if os.path.exists(os.path.join(basedir, *rel_path)): + return os.path.join(basedir, *(rel_path[:-len(incpaths)])) libdir = basedir @@ -86,15 +87,13 @@ def build_ginac_interface(parallel=None, args=None): sys.stdout.write("\n**** Building GiNaC interface ****") if args is None: - args = list() + args = [] dname = this_file_dir() _sources = ['ginac_interface.cpp'] - sources = list() - for fname in _sources: - sources.append(os.path.join(dname, fname)) + sources = [os.path.join(dname, fname) for fname in _sources] ginac_lib = find_library('ginac') - if ginac_lib is None: + if not ginac_lib: raise RuntimeError( 'could not find the GiNaC library; please make sure either to install ' 'the library and development headers system-wide, or include the ' @@ -106,7 +105,7 @@ def build_ginac_interface(parallel=None, args=None): raise RuntimeError('could not find GiNaC include directory') cln_lib = find_library('cln') - if cln_lib is None: + if not cln_lib: raise RuntimeError( 'could not find the CLN library; please make sure either to install ' 'the library and development headers system-wide, or include the ' @@ -114,7 +113,7 @@ def build_ginac_interface(parallel=None, args=None): ) cln_lib_dir = os.path.dirname(cln_lib) cln_include_dir = _find_include(cln_lib_dir, ('cln', 'cln.h')) - if cln_include_dir: + if not cln_include_dir: raise RuntimeError('could not find CLN include directory') extra_args = ['-std=c++11'] From 547b3015eed3157b3a4023a5e31ca53d1b598e57 Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Tue, 30 Apr 2024 15:32:58 +0200 Subject: [PATCH 1700/1797] Set epsilonA for one test --- pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 7ff193b38e4..7fa2a62a8be 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -1210,7 +1210,8 @@ def test_fixed_binaries( m.obj = pe.Objective(expr=m.y) m.c = pe.Constraint(expr=m.y >= m.x) m.x.fix(0) - + if type(opt) is MAiNGO: + opt.maingo_options["epsilonA"] = 1e-6 res = opt.solve(m) self.assertAlmostEqual(res.best_feasible_objective, 0, 6) m.x.fix(1) From 4ac04a6f3e8c4511f922d00c5eb7430bb8ee9b2a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 30 Apr 2024 07:33:23 -0600 Subject: [PATCH 1701/1797] NFC: apply black --- pyomo/contrib/simplification/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/simplification/build.py b/pyomo/contrib/simplification/build.py index fb571c273eb..a4094f993fa 100644 --- a/pyomo/contrib/simplification/build.py +++ b/pyomo/contrib/simplification/build.py @@ -75,7 +75,7 @@ def _find_include(libdir, incpaths): if not basedir or basedir == libdir: return None if os.path.exists(os.path.join(basedir, *rel_path)): - return os.path.join(basedir, *(rel_path[:-len(incpaths)])) + return os.path.join(basedir, *(rel_path[: -len(incpaths)])) libdir = basedir From abe5f8bb136bd1faadbcc4340deabfa42061d566 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 30 Apr 2024 07:47:07 -0600 Subject: [PATCH 1702/1797] Resync GHA workflows, remove ginac build code --- .github/workflows/test_branches.yml | 31 +++------------------ .github/workflows/test_pr_and_main.yml | 37 +++----------------------- 2 files changed, 7 insertions(+), 61 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 558d6dc2591..de40066b50f 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -201,7 +201,8 @@ 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 libginac-dev + install libopenblas-dev gfortran liblapack-dev glpk-utils \ + libginac-dev sudo chmod -R 777 ${GITHUB_WORKSPACE}/cache/os - name: Update Windows @@ -346,7 +347,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 -y $CONDA_DEPENDENCIES + 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" @@ -564,32 +565,6 @@ jobs: echo "$GJH_DIR" ls -l $GJH_DIR - - name: Install GiNaC - if: ${{ 0 && ! matrix.slim }} - run: | - if test ! -e "${DOWNLOAD_DIR}/ginac.tar.gz"; then - mkdir -p "${GITHUB_WORKSPACE}/cache/build/ginac" - cd "${GITHUB_WORKSPACE}/cache/build/ginac" - curl https://www.ginac.de/CLN/cln-1.3.7.tar.bz2 >cln-1.3.7.tar.bz2 - tar -xvf cln-1.3.7.tar.bz2 - cd cln-1.3.7 - ./configure --prefix "$TPL_DIR/ginac" --disable-static - make -j 4 - make install - cd "${GITHUB_WORKSPACE}/cache/build/ginac" - curl https://www.ginac.de/ginac-1.8.7.tar.bz2 >ginac-1.8.7.tar.bz2 - tar -xvf ginac-1.8.7.tar.bz2 - cd ginac-1.8.7 - ./configure --prefix "$TPL_DIR/ginac" --disable-static - make -j 4 - make install - cd "$TPL_DIR" - tar -czf "${DOWNLOAD_DIR}/ginac.tar.gz" ginac - else - cd "$TPL_DIR" - tar -xzf "${DOWNLOAD_DIR}/ginac.tar.gz" - fi - - name: Install Pyomo run: | echo "" diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 7c84ed14093..cdc42718cba 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -187,24 +187,6 @@ jobs: # path: cache/os # key: pkg-${{env.CACHE_VER}}.0-${{runner.os}} - - name: install GiNaC - if: matrix.other == '/singletest' - run: | - cd .. - curl https://www.ginac.de/CLN/cln-1.3.7.tar.bz2 >cln-1.3.7.tar.bz2 - tar -xvf cln-1.3.7.tar.bz2 - cd cln-1.3.7 - ./configure - make -j 2 - sudo make install - cd .. - curl https://www.ginac.de/ginac-1.8.7.tar.bz2 >ginac-1.8.7.tar.bz2 - tar -xvf ginac-1.8.7.tar.bz2 - cd ginac-1.8.7 - ./configure - make -j 2 - sudo make install - - name: TPL package download cache uses: actions/cache@v4 if: ${{ ! matrix.slim }} @@ -236,7 +218,7 @@ jobs: # Notes: # - install glpk # - pyodbc needs: gcc pkg-config unixodbc freetds - for pkg in bash pkg-config unixodbc freetds glpk; do + for pkg in bash pkg-config unixodbc freetds glpk ginac; do brew list $pkg || brew install $pkg done @@ -248,7 +230,8 @@ 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 \ + libginac-dev sudo chmod -R 777 ${GITHUB_WORKSPACE}/cache/os - name: Update Windows @@ -389,6 +372,7 @@ 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) @@ -664,14 +648,6 @@ jobs: echo "" pyomo build-extensions --parallel 2 - - name: Install GiNaC Interface - if: matrix.other == '/singletest' - run: | - export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH - echo "LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV - cd pyomo/contrib/simplification/ - $PYTHON_EXE build.py --inplace - - name: Report pyomo plugin information run: | echo "$PATH" @@ -679,11 +655,6 @@ jobs: pyomo help --transformations || exit 1 pyomo help --writers || exit 1 - - name: Run Simplification Tests - if: matrix.other == '/singletest' - run: | - pytest -v -m 'simplification' pyomo/contrib/simplification/tests/test_simplification.py --junitxml="TEST-pyomo-simplify.xml" - - name: Run Pyomo tests if: matrix.mpi == 0 run: | From 0c14380b675f55a49f238b675055a1557e191ce5 Mon Sep 17 00:00:00 2001 From: Clara Witte Date: Tue, 30 Apr 2024 16:02:17 +0200 Subject: [PATCH 1703/1797] Modify fixed_binary-test --- .../appsi/solvers/tests/test_persistent_solvers.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 7fa2a62a8be..67088297cf4 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -1210,22 +1210,20 @@ def test_fixed_binaries( m.obj = pe.Objective(expr=m.y) m.c = pe.Constraint(expr=m.y >= m.x) m.x.fix(0) - if type(opt) is MAiNGO: - opt.maingo_options["epsilonA"] = 1e-6 res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 0, 6) + self.assertAlmostEqual(res.best_feasible_objective, 0, 5) m.x.fix(1) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 1, 6) + self.assertAlmostEqual(res.best_feasible_objective, 1, 5) 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.best_feasible_objective, 0, 6) + self.assertAlmostEqual(res.best_feasible_objective, 0, 5) m.x.fix(1) res = opt.solve(m) - self.assertAlmostEqual(res.best_feasible_objective, 1, 6) + self.assertAlmostEqual(res.best_feasible_objective, 1, 5) @parameterized.expand(input=_load_tests(mip_solvers, only_child_vars_options)) def test_with_gdp( From 539d62dc2fc9dea7afe4c838dac5018921f67171 Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 30 Apr 2024 08:36:34 -0700 Subject: [PATCH 1704/1797] simplified use of suffix update and removed extra prints --- .../simple_reaction_parmest_example.py | 8 +++---- .../reactor_design/bootstrap_example.py | 2 +- .../confidence_region_example.py | 4 ++-- .../reactor_design/datarec_example.py | 24 +++++++++---------- .../reactor_design/leaveNout_example.py | 2 +- .../likelihood_ratio_example.py | 2 +- .../multisensor_data_example.py | 15 ++++++------ .../parameter_estimation_example.py | 2 +- .../examples/reactor_design/reactor_design.py | 8 +++---- .../reactor_design/timeseries_data_example.py | 2 +- .../rooney_biegler/bootstrap_example.py | 2 +- .../likelihood_ratio_example.py | 2 +- .../parameter_estimation_example.py | 2 +- .../examples/rooney_biegler/rooney_biegler.py | 4 ++-- .../rooney_biegler_with_constraint.py | 4 ++-- .../semibatch/parameter_estimation_example.py | 2 +- .../examples/semibatch/scenario_example.py | 2 +- pyomo/contrib/parmest/tests/test_parmest.py | 8 +++---- 18 files changed, 47 insertions(+), 48 deletions(-) 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 e5bfd99c84f..dcfca900f28 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 @@ -89,9 +89,9 @@ def label_model(self): m = self.model m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs.update([(m.x1, self.data['x1'])]) - m.experiment_outputs.update([(m.x2, self.data['x2'])]) - m.experiment_outputs.update([(m.y, self.data['y'])]) + m.experiment_outputs.update([(m.x1, self.data['x1']), + (m.x2, self.data['x2']), + (m.y, self.data['y'])]) return m @@ -156,7 +156,7 @@ def main(): # View one model # exp0_model = exp_list[0].get_labeled_model() - # print(exp0_model.pprint()) + # exp0_model.pprint() # ======================================================================= # Parameter estimation without covariance estimate diff --git a/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py b/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py index f845930ab79..598fef32b60 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.py @@ -31,7 +31,7 @@ def main(): # View one model # exp0_model = exp_list[0].get_labeled_model() - # print(exp0_model.pprint()) + # exp0_model.pprint() pest = parmest.Estimator(exp_list, obj_function='SSE') diff --git a/pyomo/contrib/parmest/examples/reactor_design/confidence_region_example.py b/pyomo/contrib/parmest/examples/reactor_design/confidence_region_example.py index 8aee6e9d67c..73129baf5cb 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/confidence_region_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/confidence_region_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 @@ -31,7 +31,7 @@ def main(): # View one model # exp0_model = exp_list[0].get_labeled_model() - # print(exp0_model.pprint()) + # exp0_model.pprint() pest = parmest.Estimator(exp_list, obj_function='SSE') diff --git a/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py b/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py index e05b69aa4cc..db03b268178 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py @@ -40,17 +40,17 @@ def label_model(self): # experiment outputs m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs.update([(m.ca, self.data_i['ca'])]) - m.experiment_outputs.update([(m.cb, self.data_i['cb'])]) - m.experiment_outputs.update([(m.cc, self.data_i['cc'])]) - m.experiment_outputs.update([(m.cd, self.data_i['cd'])]) + m.experiment_outputs.update([(m.ca, self.data_i['ca']), + (m.cb, self.data_i['cb']), + (m.cc, self.data_i['cc']), + (m.cd, self.data_i['cd'])]) # experiment standard deviations m.experiment_outputs_std = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs_std.update([(m.ca, self.data_std['ca'])]) - m.experiment_outputs_std.update([(m.cb, self.data_std['cb'])]) - m.experiment_outputs_std.update([(m.cc, self.data_std['cc'])]) - m.experiment_outputs_std.update([(m.cd, self.data_std['cd'])]) + m.experiment_outputs_std.update([(m.ca, self.data_std['ca']), + (m.cb, self.data_std['cb']), + (m.cc, self.data_std['cc']), + (m.cd, self.data_std['cd'])]) # no unknowns (theta names) m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) @@ -71,10 +71,10 @@ def label_model(self): # add experiment standard deviations m.experiment_outputs_std = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs_std.update([(m.ca, self.data_std['ca'])]) - m.experiment_outputs_std.update([(m.cb, self.data_std['cb'])]) - m.experiment_outputs_std.update([(m.cc, self.data_std['cc'])]) - m.experiment_outputs_std.update([(m.cd, self.data_std['cd'])]) + m.experiment_outputs_std.update([(m.ca, self.data_std['ca']), + (m.cb, self.data_std['cb']), + (m.cc, self.data_std['cc']), + (m.cd, self.data_std['cd'])]) return m diff --git a/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.py b/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.py index c735b191e0c..9560981ca5c 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.py @@ -38,7 +38,7 @@ def main(): # View one model # exp0_model = exp_list[0].get_labeled_model() - # print(exp0_model.pprint()) + # exp0_model.pprint() pest = parmest.Estimator(exp_list, obj_function='SSE') 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 45adaa27e7f..c2bff254077 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.py @@ -32,7 +32,7 @@ def main(): # View one model # exp0_model = exp_list[0].get_labeled_model() - # print(exp0_model.pprint()) + # exp0_model.pprint() pest = parmest.Estimator(exp_list, obj_function='SSE') 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 95bcf211207..d0136fa6f92 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py @@ -41,12 +41,11 @@ def label_model(self): m = self.model m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs.update( - [(m.ca, [self.data_i['ca1'], self.data_i['ca2'], self.data_i['ca3']])] - ) - m.experiment_outputs.update([(m.cb, [self.data_i['cb']])]) - m.experiment_outputs.update([(m.cc, [self.data_i['cc1'], self.data_i['cc2']])]) - m.experiment_outputs.update([(m.cd, [self.data_i['cd']])]) + m.experiment_outputs.update([ + (m.ca, [self.data_i['ca1'], self.data_i['ca2'], self.data_i['ca3']]), + (m.cb, [self.data_i['cb']]), + (m.cc, [self.data_i['cc1'], self.data_i['cc2']]), + (m.cd, [self.data_i['cd']])]) m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) m.unknown_parameters.update( @@ -80,8 +79,8 @@ def SSE_multisensor(model): # View one model # exp0_model = exp_list[0].get_labeled_model() - # print(exp0_model.pprint()) - # print(SSE_multisensor(exp0_model)) + # exp0_model.pprint() + # SSE_multisensor(exp0_model) pest = parmest.Estimator(exp_list, obj_function=SSE_multisensor) obj, theta = pest.theta_est() 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 d72b7aa9878..a84a3fde5e7 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.py @@ -31,7 +31,7 @@ def main(): # View one model # exp0_model = exp_list[0].get_labeled_model() - # print(exp0_model.pprint()) + # exp0_model.pprint() pest = parmest.Estimator(exp_list, obj_function='SSE') diff --git a/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py b/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py index a2025b8a324..7918d8a14cd 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py +++ b/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py @@ -107,10 +107,10 @@ def label_model(self): m = self.model m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs.update([(m.ca, self.data_i['ca'])]) - m.experiment_outputs.update([(m.cb, self.data_i['cb'])]) - m.experiment_outputs.update([(m.cc, self.data_i['cc'])]) - m.experiment_outputs.update([(m.cd, self.data_i['cd'])]) + m.experiment_outputs.update([(m.ca, self.data_i['ca']), + (m.cb, self.data_i['cb']), + (m.cc, self.data_i['cc']), + (m.cd, self.data_i['cd'])]) m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) m.unknown_parameters.update( 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 1e457bf1e89..04a64850f40 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/timeseries_data_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/timeseries_data_example.py @@ -68,7 +68,7 @@ def SSE_timeseries(model): # View one model & SSE # exp0_model = exp_list[0].get_labeled_model() - # print(exp0_model.pprint()) + # exp0_model.pprint() # print(SSE_timeseries(exp0_model)) pest = parmest.Estimator(exp_list, obj_function=SSE_timeseries) diff --git a/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.py b/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.py index 04917e1a817..944a01ac95e 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.py @@ -39,7 +39,7 @@ def SSE(model): # View one model # exp0_model = exp_list[0].get_labeled_model() - # print(exp0_model.pprint()) + # exp0_model.pprint() # Create an instance of the parmest estimator pest = parmest.Estimator(exp_list, obj_function=SSE) 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 d8b572890ba..54343993286 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/likelihood_ratio_example.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/likelihood_ratio_example.py @@ -40,7 +40,7 @@ def SSE(model): # View one model # exp0_model = exp_list[0].get_labeled_model() - # print(exp0_model.pprint()) + # exp0_model.pprint() # Create an instance of the parmest estimator pest = parmest.Estimator(exp_list, obj_function=SSE) 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 18c4904787b..3c9a93100bb 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/parameter_estimation_example.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/parameter_estimation_example.py @@ -39,7 +39,7 @@ def SSE(model): # View one model # exp0_model = exp_list[0].get_labeled_model() - # print(exp0_model.pprint()) + # exp0_model.pprint() # Create an instance of the parmest estimator pest = parmest.Estimator(exp_list, obj_function=SSE) diff --git a/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py b/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py index a9918cf2268..7b4dc289061 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py @@ -61,8 +61,8 @@ def label_model(self): m = self.model m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs.update([(m.hour, self.data['hour'])]) - m.experiment_outputs.update([(m.y, self.data['y'])]) + m.experiment_outputs.update([(m.hour, self.data['hour']), + (m.y, self.data['y'])]) m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) m.unknown_parameters.update( 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 259fa45785a..4a2a07a052d 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 @@ -65,8 +65,8 @@ def label_model(self): m = self.model m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs.update([(m.hour, self.data['hour'])]) - m.experiment_outputs.update([(m.y, self.data['y'])]) + m.experiment_outputs.update([(m.hour, self.data['hour']), + (m.y, self.data['y'])]) m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) m.unknown_parameters.update( diff --git a/pyomo/contrib/parmest/examples/semibatch/parameter_estimation_example.py b/pyomo/contrib/parmest/examples/semibatch/parameter_estimation_example.py index 0d5416c714a..7eafdd2b9c3 100644 --- a/pyomo/contrib/parmest/examples/semibatch/parameter_estimation_example.py +++ b/pyomo/contrib/parmest/examples/semibatch/parameter_estimation_example.py @@ -33,7 +33,7 @@ def main(): # View one model # exp0_model = exp_list[0].get_labeled_model() - # print(exp0_model.pprint()) + # exp0_model.pprint() # Note, the model already includes a 'SecondStageCost' expression # for sum of squared error that will be used in parameter estimation diff --git a/pyomo/contrib/parmest/examples/semibatch/scenario_example.py b/pyomo/contrib/parmest/examples/semibatch/scenario_example.py index b2e9f7bd7ee..697cb9ac7a5 100644 --- a/pyomo/contrib/parmest/examples/semibatch/scenario_example.py +++ b/pyomo/contrib/parmest/examples/semibatch/scenario_example.py @@ -34,7 +34,7 @@ def main(): # View one model # exp0_model = exp_list[0].get_labeled_model() - # print(exp0_model.pprint()) + # exp0_model.pprint() pest = parmest.Estimator(exp_list) diff --git a/pyomo/contrib/parmest/tests/test_parmest.py b/pyomo/contrib/parmest/tests/test_parmest.py index 9c65a31352f..e9cbfcebb7f 100644 --- a/pyomo/contrib/parmest/tests/test_parmest.py +++ b/pyomo/contrib/parmest/tests/test_parmest.py @@ -427,8 +427,8 @@ def label_model(self): m = self.model m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs.update([(m.hour, self.data["hour"])]) - m.experiment_outputs.update([(m.y, self.data["y"])]) + m.experiment_outputs.update([(m.hour, self.data["hour"]), + (m.y, self.data["y"])]) m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) m.unknown_parameters.update((k, pyo.ComponentUID(k)) for k in [m.theta]) @@ -506,8 +506,8 @@ def label_model(self): m = self.model m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs.update([(m.hour, self.data["hour"])]) - m.experiment_outputs.update([(m.y, self.data["y"])]) + m.experiment_outputs.update([(m.hour, self.data["hour"]), + (m.y, self.data["y"])]) m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) m.unknown_parameters.update((k, pyo.ComponentUID(k)) for k in [m.theta]) From b7dc0f1ac535f46ba7e1051211e6250bbb2824d6 Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 30 Apr 2024 08:50:23 -0700 Subject: [PATCH 1705/1797] minor update, added print --- .../parmest/examples/reactor_design/multisensor_data_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d0136fa6f92..f2820edf6a6 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py @@ -80,7 +80,7 @@ def SSE_multisensor(model): # View one model # exp0_model = exp_list[0].get_labeled_model() # exp0_model.pprint() - # SSE_multisensor(exp0_model) + # print(SSE_multisensor(exp0_model)) pest = parmest.Estimator(exp_list, obj_function=SSE_multisensor) obj, theta = pest.theta_est() From 032fd3102ec6e933eb51350ed1467d106cc0d986 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Tue, 30 Apr 2024 10:07:39 -0600 Subject: [PATCH 1706/1797] Update deprecation version in scenariocreator.py --- pyomo/contrib/parmest/scenariocreator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/parmest/scenariocreator.py b/pyomo/contrib/parmest/scenariocreator.py index f2798ad2e94..2208bde91a0 100644 --- a/pyomo/contrib/parmest/scenariocreator.py +++ b/pyomo/contrib/parmest/scenariocreator.py @@ -17,7 +17,7 @@ from pyomo.common.deprecation import deprecated from pyomo.common.deprecation import deprecation_warning -DEPRECATION_VERSION = '6.7.0' +DEPRECATION_VERSION = '6.7.2.dev0' import logging From 9c5f8df5266a7adaa271ab1904c1a0c6e3c1dd36 Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 30 Apr 2024 09:12:39 -0700 Subject: [PATCH 1707/1797] reformatted lists --- .../simple_reaction_parmest_example.py | 6 ++-- .../reactor_design/datarec_example.py | 36 ++++++++++++------- .../multisensor_data_example.py | 13 ++++--- .../examples/reactor_design/reactor_design.py | 12 ++++--- .../examples/rooney_biegler/rooney_biegler.py | 5 +-- .../rooney_biegler_with_constraint.py | 5 +-- pyomo/contrib/parmest/tests/test_parmest.py | 10 +++--- 7 files changed, 55 insertions(+), 32 deletions(-) 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 dcfca900f28..5c8a0219946 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 @@ -89,9 +89,9 @@ def label_model(self): m = self.model m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs.update([(m.x1, self.data['x1']), - (m.x2, self.data['x2']), - (m.y, self.data['y'])]) + m.experiment_outputs.update( + [(m.x1, self.data['x1']), (m.x2, self.data['x2']), (m.y, self.data['y'])] + ) return m diff --git a/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py b/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py index db03b268178..ba41bbfb7b8 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py @@ -40,17 +40,25 @@ def label_model(self): # experiment outputs m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs.update([(m.ca, self.data_i['ca']), - (m.cb, self.data_i['cb']), - (m.cc, self.data_i['cc']), - (m.cd, self.data_i['cd'])]) + m.experiment_outputs.update( + [ + (m.ca, self.data_i['ca']), + (m.cb, self.data_i['cb']), + (m.cc, self.data_i['cc']), + (m.cd, self.data_i['cd']) + ] + ) # experiment standard deviations m.experiment_outputs_std = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs_std.update([(m.ca, self.data_std['ca']), - (m.cb, self.data_std['cb']), - (m.cc, self.data_std['cc']), - (m.cd, self.data_std['cd'])]) + m.experiment_outputs_std.update( + [ + (m.ca, self.data_std['ca']), + (m.cb, self.data_std['cb']), + (m.cc, self.data_std['cc']), + (m.cd, self.data_std['cd']) + ] + ) # no unknowns (theta names) m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) @@ -71,10 +79,14 @@ def label_model(self): # add experiment standard deviations m.experiment_outputs_std = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs_std.update([(m.ca, self.data_std['ca']), - (m.cb, self.data_std['cb']), - (m.cc, self.data_std['cc']), - (m.cd, self.data_std['cd'])]) + m.experiment_outputs_std.update( + [ + (m.ca, self.data_std['ca']), + (m.cb, self.data_std['cb']), + (m.cc, self.data_std['cc']), + (m.cd, self.data_std['cd']) + ] + ) return m 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 f2820edf6a6..e7e4fb1b04d 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py @@ -41,11 +41,14 @@ def label_model(self): m = self.model m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs.update([ - (m.ca, [self.data_i['ca1'], self.data_i['ca2'], self.data_i['ca3']]), - (m.cb, [self.data_i['cb']]), - (m.cc, [self.data_i['cc1'], self.data_i['cc2']]), - (m.cd, [self.data_i['cd']])]) + m.experiment_outputs.update( + [ + (m.ca, [self.data_i['ca1'], self.data_i['ca2'], self.data_i['ca3']]), + (m.cb, [self.data_i['cb']]), + (m.cc, [self.data_i['cc1'], self.data_i['cc2']]), + (m.cd, [self.data_i['cd']]) + ] + ) m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) m.unknown_parameters.update( diff --git a/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py b/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py index 7918d8a14cd..7f6e46cc723 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py +++ b/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py @@ -107,10 +107,14 @@ def label_model(self): m = self.model m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs.update([(m.ca, self.data_i['ca']), - (m.cb, self.data_i['cb']), - (m.cc, self.data_i['cc']), - (m.cd, self.data_i['cd'])]) + m.experiment_outputs.update( + [ + (m.ca, self.data_i['ca']), + (m.cb, self.data_i['cb']), + (m.cc, self.data_i['cc']), + (m.cd, self.data_i['cd']) + ] + ) m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) m.unknown_parameters.update( diff --git a/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py b/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py index 7b4dc289061..9625ab32ea3 100644 --- a/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py +++ b/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.py @@ -61,8 +61,9 @@ def label_model(self): m = self.model m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs.update([(m.hour, self.data['hour']), - (m.y, self.data['y'])]) + m.experiment_outputs.update( + [(m.hour, self.data['hour']), (m.y, self.data['y'])] + ) m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) m.unknown_parameters.update( 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 4a2a07a052d..dd82b50cf7a 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 @@ -65,8 +65,9 @@ def label_model(self): m = self.model m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs.update([(m.hour, self.data['hour']), - (m.y, self.data['y'])]) + m.experiment_outputs.update( + [(m.hour, self.data['hour']), (m.y, self.data['y'])] + ) m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) m.unknown_parameters.update( diff --git a/pyomo/contrib/parmest/tests/test_parmest.py b/pyomo/contrib/parmest/tests/test_parmest.py index e9cbfcebb7f..e9a8e089335 100644 --- a/pyomo/contrib/parmest/tests/test_parmest.py +++ b/pyomo/contrib/parmest/tests/test_parmest.py @@ -427,8 +427,9 @@ def label_model(self): m = self.model m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs.update([(m.hour, self.data["hour"]), - (m.y, self.data["y"])]) + m.experiment_outputs.update( + [(m.hour, self.data["hour"]), (m.y, self.data["y"])] + ) m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) m.unknown_parameters.update((k, pyo.ComponentUID(k)) for k in [m.theta]) @@ -506,8 +507,9 @@ def label_model(self): m = self.model m.experiment_outputs = pyo.Suffix(direction=pyo.Suffix.LOCAL) - m.experiment_outputs.update([(m.hour, self.data["hour"]), - (m.y, self.data["y"])]) + m.experiment_outputs.update( + [(m.hour, self.data["hour"]), (m.y, self.data["y"])] + ) m.unknown_parameters = pyo.Suffix(direction=pyo.Suffix.LOCAL) m.unknown_parameters.update((k, pyo.ComponentUID(k)) for k in [m.theta]) From bf865f4383e93402548db9cd548ebbace383bb80 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Tue, 30 Apr 2024 10:14:20 -0600 Subject: [PATCH 1708/1797] Update deprecation version in parmest.py --- pyomo/contrib/parmest/parmest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index aecc9d5ebc2..a1200e2c3a5 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -78,7 +78,7 @@ from pyomo.common.deprecation import deprecated from pyomo.common.deprecation import deprecation_warning -DEPRECATION_VERSION = '6.7.0' +DEPRECATION_VERSION = '6.7.2.dev0' parmest_available = numpy_available & pandas_available & scipy_available From db48a25a987ad0f978e6ef3ebc873eabf33f1a92 Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 30 Apr 2024 09:29:22 -0700 Subject: [PATCH 1709/1797] added missing commas --- .../parmest/examples/reactor_design/datarec_example.py | 6 +++--- .../examples/reactor_design/multisensor_data_example.py | 2 +- .../parmest/examples/reactor_design/reactor_design.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py b/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py index ba41bbfb7b8..02aa13fceab 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py @@ -45,7 +45,7 @@ def label_model(self): (m.ca, self.data_i['ca']), (m.cb, self.data_i['cb']), (m.cc, self.data_i['cc']), - (m.cd, self.data_i['cd']) + (m.cd, self.data_i['cd']), ] ) @@ -56,7 +56,7 @@ def label_model(self): (m.ca, self.data_std['ca']), (m.cb, self.data_std['cb']), (m.cc, self.data_std['cc']), - (m.cd, self.data_std['cd']) + (m.cd, self.data_std['cd']), ] ) @@ -84,7 +84,7 @@ def label_model(self): (m.ca, self.data_std['ca']), (m.cb, self.data_std['cb']), (m.cc, self.data_std['cc']), - (m.cd, self.data_std['cd']) + (m.cd, self.data_std['cd']), ] ) 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 e7e4fb1b04d..48a7bca52ca 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py @@ -46,7 +46,7 @@ def label_model(self): (m.ca, [self.data_i['ca1'], self.data_i['ca2'], self.data_i['ca3']]), (m.cb, [self.data_i['cb']]), (m.cc, [self.data_i['cc1'], self.data_i['cc2']]), - (m.cd, [self.data_i['cd']]) + (m.cd, [self.data_i['cd']]), ] ) diff --git a/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py b/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py index 7f6e46cc723..a396c1ea721 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py +++ b/pyomo/contrib/parmest/examples/reactor_design/reactor_design.py @@ -112,7 +112,7 @@ def label_model(self): (m.ca, self.data_i['ca']), (m.cb, self.data_i['cb']), (m.cc, self.data_i['cc']), - (m.cd, self.data_i['cd']) + (m.cd, self.data_i['cd']), ] ) From e68c415176439e9299769e59ac4c25bdbf5f0fad Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 30 Apr 2024 09:31:22 -0700 Subject: [PATCH 1710/1797] removed _treemaker, not used --- pyomo/contrib/parmest/parmest.py | 37 -------------------------------- 1 file changed, 37 deletions(-) diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index a1200e2c3a5..c9826a57b1d 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -16,7 +16,6 @@ # TODO: move use_mpisppy to a Pyomo configuration option # Redesign TODOS -# TODO: _treemaker is not used in parmest, the code could be moved to scenario tree if needed # TODO: Create additional built in objective expressions in an Enum class which includes SSE (see SSE function below) # TODO: Clean up the use of theta_names through out the code. The Experiment returns the CUID of each theta and this can be used directly (instead of the name) # TODO: Clean up the use of updated_theta_names, model_theta_names, estimator_theta_names. Not sure if estimator_theta_names is the union or intersect of thetas in each model @@ -239,42 +238,6 @@ def _experiment_instance_creation_callback( return instance -# # ============================================= -# def _treemaker(scenlist): -# """ -# Makes a scenario tree (avoids dependence on daps) - -# Parameters -# ---------- -# scenlist (list of `int`): experiment (i.e. scenario) numbers - -# Returns -# ------- -# a `ConcreteModel` that is the scenario tree -# """ - -# num_scenarios = len(scenlist) -# m = scenario_tree.tree_structure_model.CreateAbstractScenarioTreeModel() -# m = m.create_instance() -# m.Stages.add('Stage1') -# m.Stages.add('Stage2') -# m.Nodes.add('RootNode') -# for i in scenlist: -# m.Nodes.add('LeafNode_Experiment' + str(i)) -# m.Scenarios.add('Experiment' + str(i)) -# m.NodeStage['RootNode'] = 'Stage1' -# m.ConditionalProbability['RootNode'] = 1.0 -# for node in m.Nodes: -# if node != 'RootNode': -# m.NodeStage[node] = 'Stage2' -# m.Children['RootNode'].add(node) -# m.Children[node].clear() -# m.ConditionalProbability[node] = 1.0 / num_scenarios -# m.ScenarioLeafNode[node.replace('LeafNode_', '')] = node - -# return m - - def SSE(model): """ Sum of squared error between `experiment_output` model and data values From 63634c99a913944864aa1450399a05e6c900ab53 Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 30 Apr 2024 09:40:05 -0700 Subject: [PATCH 1711/1797] removed _SecondStageCostExpr class, call objective function directly --- pyomo/contrib/parmest/parmest.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index c9826a57b1d..ac2c7fdb0aa 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -246,18 +246,6 @@ def SSE(model): return expr -class _SecondStageCostExpr(object): - """ - Class to pass objective expression into the Pyomo model - """ - - def __init__(self, ssc_function): - self._ssc_function = ssc_function - - def __call__(self, model): - return self._ssc_function(model) - - class Estimator(object): """ Parameter estimation class @@ -419,10 +407,10 @@ def _create_parmest_model(self, experiment_number): # TODO, this needs to be turned a enum class of options that still support custom functions if self.obj_function == 'SSE': - second_stage_rule = _SecondStageCostExpr(SSE) + second_stage_rule = SSE else: # A custom function uses model.experiment_outputs as data - second_stage_rule = _SecondStageCostExpr(self.obj_function) + second_stage_rule = self.obj_function model.FirstStageCost = pyo.Expression(expr=0) model.SecondStageCost = pyo.Expression(rule=second_stage_rule) From a92b4f58ed95a148aa2a9750018c81761f643794 Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 30 Apr 2024 09:53:35 -0700 Subject: [PATCH 1712/1797] changed yhat to y_hat --- .../parmest/examples/reactor_design/datarec_example.py | 4 ++-- .../examples/reactor_design/multisensor_data_example.py | 6 +++--- .../examples/reactor_design/timeseries_data_example.py | 6 +++--- pyomo/contrib/parmest/parmest.py | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py b/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py index 02aa13fceab..be08e727be9 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/datarec_example.py @@ -132,8 +132,8 @@ def main(): # Define sum of squared error objective function for data rec def SSE_with_std(model): expr = sum( - ((y - yhat) / model.experiment_outputs_std[y]) ** 2 - for y, yhat in model.experiment_outputs.items() + ((y - y_hat) / model.experiment_outputs_std[y]) ** 2 + for y, y_hat in model.experiment_outputs.items() ) 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 48a7bca52ca..208981a784a 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.py @@ -74,10 +74,10 @@ def main(): # Define sum of squared error def SSE_multisensor(model): expr = 0 - for y, yhat in model.experiment_outputs.items(): - num_outputs = len(yhat) + for y, y_hat in model.experiment_outputs.items(): + num_outputs = len(y_hat) for i in range(num_outputs): - expr += ((y - yhat[i]) ** 2) * (1 / num_outputs) + expr += ((y - y_hat[i]) ** 2) * (1 / num_outputs) return expr # View one model 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 04a64850f40..4eb191afd6d 100644 --- a/pyomo/contrib/parmest/examples/reactor_design/timeseries_data_example.py +++ b/pyomo/contrib/parmest/examples/reactor_design/timeseries_data_example.py @@ -59,10 +59,10 @@ def main(): def SSE_timeseries(model): expr = 0 - for y, yhat in model.experiment_outputs.items(): - num_time_points = len(yhat) + for y, y_hat in model.experiment_outputs.items(): + num_time_points = len(y_hat) for i in range(num_time_points): - expr += ((y - yhat[i]) ** 2) * (1 / num_time_points) + expr += ((y - y_hat[i]) ** 2) * (1 / num_time_points) return expr diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index ac2c7fdb0aa..6bc69c78bcd 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -242,7 +242,7 @@ def SSE(model): """ Sum of squared error between `experiment_output` model and data values """ - expr = sum((y - yhat) ** 2 for y, yhat in model.experiment_outputs.items()) + expr = sum((y - y_hat) ** 2 for y, y_hat in model.experiment_outputs.items()) return expr From 097849dbaaadbc1873b8b1dc3a83e24103a55aaf Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Tue, 30 Apr 2024 11:45:11 -0600 Subject: [PATCH 1713/1797] Update Estimator constructor in parmest to more robustly support both the new and deprecated APIs --- pyomo/contrib/parmest/parmest.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index 6bc69c78bcd..ae6a6ffe184 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -254,7 +254,7 @@ class Estimator(object): ---------- experiment_list: list of Experiments A list of experiment objects which creates one labeled model for - each expeirment + each experiment obj_function: string or function (optional) Built in objective (currently only "SSE") or custom function used to formulate parameter estimation objective. @@ -271,26 +271,25 @@ class Estimator(object): # backwards compatible constructor will accept the old deprecated inputs # as well as the new inputs using experiment lists - def __init__(self, *args, **kwargs): - - # check that we have at least one argument - assert len(args) > 0 + # TODO: when the deprecated Parmest API is removed, *args, can be removed from this constructor + def __init__(self, experiment_list, *args, obj_function=None, tee=False, diagnostic_mode=False, solver_options=None): # use deprecated interface self.pest_deprecated = None - if callable(args[0]): + if callable(experiment_list): deprecation_warning( - 'Using deprecated parmest inputs (model_function, ' - + 'data, theta_names), please use experiment lists instead.', + 'Using deprecated parmest interface (model_function, ' + 'data, theta_names). This interface will be removed in a future release, ' + 'please update to the new parmest interface using experiment lists.', version=DEPRECATION_VERSION, ) - self.pest_deprecated = _DeprecatedEstimator(*args, **kwargs) + self.pest_deprecated = _DeprecatedEstimator(experiment_list, *args, obj_function, tee, diagnostic_mode, solver_options) return # check that we have a (non-empty) list of experiments - assert isinstance(args[0], list) - assert len(args[0]) > 0 - self.exp_list = args[0] + assert isinstance(experiment_list, list) + assert len(args) == 0 + self.exp_list = experiment_list # check that an experiment has experiment_outputs and unknown_parameters model = self.exp_list[0].get_labeled_model() @@ -308,10 +307,10 @@ def __init__(self, *args, **kwargs): ) # populate keyword argument options - self.obj_function = kwargs.get('obj_function', None) - self.tee = kwargs.get('tee', False) - self.diagnostic_mode = kwargs.get('diagnostic_mode', False) - self.solver_options = kwargs.get('solver_options', None) + self.obj_function = obj_function + self.tee = tee + self.diagnostic_mode = diagnostic_mode + self.solver_options = solver_options # TODO This might not be needed here. # We could collect the union (or intersect?) of thetas when the models are built From 2a9c5337a52ebefadfc72e392d6b72299503d006 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 30 Apr 2024 13:59:21 -0600 Subject: [PATCH 1714/1797] Fixing a typo --- pyomo/contrib/cp/tests/test_docplex_walker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/cp/tests/test_docplex_walker.py b/pyomo/contrib/cp/tests/test_docplex_walker.py index 1173ae66eab..f7abb3d2b3c 100644 --- a/pyomo/contrib/cp/tests/test_docplex_walker.py +++ b/pyomo/contrib/cp/tests/test_docplex_walker.py @@ -1610,7 +1610,7 @@ def test_always_in(self): def test_always_in_single_pulse(self): # This is a bit silly as you can tell whether or not it is feasible - # structurally, but there's not reason it couldn't happen. + # structurally, but there's no reason it couldn't happen. m = self.get_model() f = Pulse((m.i, 3)) m.c = LogicalConstraint(expr=f.within((0, 3), (0, 10))) From efe4dabc5cc14aada83165f7abc3693f7e061bec Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Tue, 30 Apr 2024 16:22:21 -0600 Subject: [PATCH 1715/1797] Fixing formatting in parmest --- pyomo/contrib/parmest/parmest.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index ae6a6ffe184..c350f315fe4 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -272,7 +272,15 @@ class Estimator(object): # backwards compatible constructor will accept the old deprecated inputs # as well as the new inputs using experiment lists # TODO: when the deprecated Parmest API is removed, *args, can be removed from this constructor - def __init__(self, experiment_list, *args, obj_function=None, tee=False, diagnostic_mode=False, solver_options=None): + def __init__( + self, + experiment_list, + *args, + obj_function=None, + tee=False, + diagnostic_mode=False, + solver_options=None, + ): # use deprecated interface self.pest_deprecated = None @@ -283,7 +291,14 @@ def __init__(self, experiment_list, *args, obj_function=None, tee=False, diagnos 'please update to the new parmest interface using experiment lists.', version=DEPRECATION_VERSION, ) - self.pest_deprecated = _DeprecatedEstimator(experiment_list, *args, obj_function, tee, diagnostic_mode, solver_options) + self.pest_deprecated = _DeprecatedEstimator( + experiment_list, + *args, + obj_function, + tee, + diagnostic_mode, + solver_options, + ) return # check that we have a (non-empty) list of experiments @@ -309,7 +324,7 @@ def __init__(self, experiment_list, *args, obj_function=None, tee=False, diagnos # populate keyword argument options self.obj_function = obj_function self.tee = tee - self.diagnostic_mode = diagnostic_mode + self.diagnostic_mode = diagnostic_mode self.solver_options = solver_options # TODO This might not be needed here. From 8638268d73639866b5f8d885c69efd6db93b5a1d Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Tue, 30 Apr 2024 16:33:54 -0600 Subject: [PATCH 1716/1797] Fix formatting in parmest --- pyomo/contrib/parmest/parmest.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index c350f315fe4..ded40a87aff 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -262,7 +262,7 @@ class Estimator(object): "as is" and should be defined with a "FirstStageCost" and "SecondStageCost" expression that are used to build an objective. tee: bool, optional - Indicates that ef solver output should be teed + If True, print the solver output to the screen diagnostic_mode: bool, optional If True, print diagnostics from the solver solver_options: dict, optional @@ -273,12 +273,12 @@ class Estimator(object): # as well as the new inputs using experiment lists # TODO: when the deprecated Parmest API is removed, *args, can be removed from this constructor def __init__( - self, - experiment_list, - *args, - obj_function=None, - tee=False, - diagnostic_mode=False, + self, + experiment_list, + *args, + obj_function=None, + tee=False, + diagnostic_mode=False, solver_options=None, ): @@ -292,11 +292,11 @@ def __init__( version=DEPRECATION_VERSION, ) self.pest_deprecated = _DeprecatedEstimator( - experiment_list, - *args, - obj_function, - tee, - diagnostic_mode, + experiment_list, + *args, + obj_function, + tee, + diagnostic_mode, solver_options, ) return From eeec88ed9f8c550f4f91d9afea155fbf4c218d2b Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Tue, 30 Apr 2024 18:18:29 -0600 Subject: [PATCH 1717/1797] Reworking parmest Estimator constructor --- pyomo/contrib/parmest/parmest.py | 58 ++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index ded40a87aff..cdff785899e 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -53,7 +53,9 @@ import logging import types import json +from collections.abc import Callable from itertools import combinations +from functools import singledispatchmethod from pyomo.common.dependencies import ( attempt_import, @@ -271,39 +273,18 @@ class Estimator(object): # backwards compatible constructor will accept the old deprecated inputs # as well as the new inputs using experiment lists - # TODO: when the deprecated Parmest API is removed, *args, can be removed from this constructor + @singledispatchmethod def __init__( self, experiment_list, - *args, obj_function=None, tee=False, diagnostic_mode=False, solver_options=None, ): - # use deprecated interface - self.pest_deprecated = None - if callable(experiment_list): - deprecation_warning( - 'Using deprecated parmest interface (model_function, ' - 'data, theta_names). This interface will be removed in a future release, ' - 'please update to the new parmest interface using experiment lists.', - version=DEPRECATION_VERSION, - ) - self.pest_deprecated = _DeprecatedEstimator( - experiment_list, - *args, - obj_function, - tee, - diagnostic_mode, - solver_options, - ) - return - # check that we have a (non-empty) list of experiments assert isinstance(experiment_list, list) - assert len(args) == 0 self.exp_list = experiment_list # check that an experiment has experiment_outputs and unknown_parameters @@ -326,6 +307,9 @@ def __init__( self.tee = tee self.diagnostic_mode = diagnostic_mode self.solver_options = solver_options + self.pest_deprecated = ( + None # TODO: delete this when deprecated interface is removed + ) # TODO This might not be needed here. # We could collect the union (or intersect?) of thetas when the models are built @@ -339,6 +323,36 @@ def __init__( # boolean to indicate if model is initialized using a square solve self.model_initialized = False + # use deprecated interface + @__init__.register(Callable) + def _deprecated_init( + self, + model_function, + data, + theta_names, + obj_function=None, + tee=False, + diagnostic_mode=False, + solver_options=None, + ): + + deprecation_warning( + "You're using the deprecated parmest interface (model_function, " + "data, theta_names). This interface will be removed in a future release, " + "please update to the new parmest interface using experiment lists.", + version=DEPRECATION_VERSION, + ) + self.pest_deprecated = _DeprecatedEstimator( + model_function, + data, + theta_names, + obj_function, + tee, + diagnostic_mode, + solver_options, + ) + return + def _return_theta_names(self): """ Return list of fitted model parameter names From add489cb99a8e7ea7d2f65d4fb32662ecb07e57e Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 1 May 2024 09:42:45 -0400 Subject: [PATCH 1718/1797] update log of call_before_subproblem_solve --- pyomo/contrib/mindtpy/algorithm_base_class.py | 6 +++--- pyomo/contrib/mindtpy/config_options.py | 4 ++-- pyomo/contrib/mindtpy/single_tree.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 5547350ed44..bbcb8f5fc56 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -2950,7 +2950,7 @@ def MindtPy_iteration_loop(self): ) if self.curr_int_sol not in set(self.integer_list): # Call the NLP pre-solve callback - with time_code(self.timing, 'Call after subproblem solve'): + with time_code(self.timing, 'Call before subproblem solve'): config.call_before_subproblem_solve(self.fixed_nlp) fixed_nlp, fixed_nlp_result = self.solve_subproblem() @@ -2965,7 +2965,7 @@ def MindtPy_iteration_loop(self): # The constraint linearization happens in the handlers if not config.solution_pool: # Call the NLP pre-solve callback - with time_code(self.timing, 'Call after subproblem solve'): + with time_code(self.timing, 'Call before subproblem solve'): config.call_before_subproblem_solve(self.fixed_nlp) fixed_nlp, fixed_nlp_result = self.solve_subproblem() @@ -3002,7 +3002,7 @@ def MindtPy_iteration_loop(self): self.integer_list.append(self.curr_int_sol) # Call the NLP pre-solve callback - with time_code(self.timing, 'Call after subproblem solve'): + with time_code(self.timing, 'Call before subproblem solve'): config.call_before_subproblem_solve(self.fixed_nlp) fixed_nlp, fixed_nlp_result = self.solve_subproblem() diff --git a/pyomo/contrib/mindtpy/config_options.py b/pyomo/contrib/mindtpy/config_options.py index 019c6933d76..0d0b536525a 100644 --- a/pyomo/contrib/mindtpy/config_options.py +++ b/pyomo/contrib/mindtpy/config_options.py @@ -328,8 +328,8 @@ def _add_common_configs(CONFIG): ConfigValue( default=_DoNothing(), domain=None, - description='Function to be executed after every subproblem', - doc='Callback hook after a solution of the nonlinear subproblem.', + description='Function to be executed before every subproblem', + doc='Callback hook before a solution of the nonlinear subproblem.', ), ) CONFIG.declare( diff --git a/pyomo/contrib/mindtpy/single_tree.py b/pyomo/contrib/mindtpy/single_tree.py index bc0f5d3cf4f..6b501ef874d 100644 --- a/pyomo/contrib/mindtpy/single_tree.py +++ b/pyomo/contrib/mindtpy/single_tree.py @@ -774,7 +774,7 @@ def __call__(self): # solve subproblem # Call the NLP pre-solve callback - with time_code(mindtpy_solver.timing, 'Call after subproblem solve'): + with time_code(mindtpy_solver.timing, 'Call before subproblem solve'): config.call_before_subproblem_solve(mindtpy_solver.fixed_nlp) # The constraint linearization happens in the handlers fixed_nlp, fixed_nlp_result = mindtpy_solver.solve_subproblem() @@ -923,7 +923,7 @@ def LazyOACallback_gurobi(cb_m, cb_opt, cb_where, mindtpy_solver, config): # solve subproblem # Call the NLP pre-solve callback - with time_code(mindtpy_solver.timing, 'Call after subproblem solve'): + with time_code(mindtpy_solver.timing, 'Call before subproblem solve'): config.call_before_subproblem_solve(mindtpy_solver.fixed_nlp) # The constraint linearization happens in the handlers fixed_nlp, fixed_nlp_result = mindtpy_solver.solve_subproblem() From dcd5db165e64dcd9b4077401e184266a88719adc Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 1 May 2024 09:42:13 -0600 Subject: [PATCH 1719/1797] Adding comments to parmest Estimator constructor logic --- pyomo/contrib/parmest/parmest.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index cdff785899e..3516c52d19d 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -271,8 +271,11 @@ class Estimator(object): Provides options to the solver (also the name of an attribute) """ - # backwards compatible constructor will accept the old deprecated inputs - # as well as the new inputs using experiment lists + # The singledispatchmethod decorator is used here as a deprecation + # shim to be able to support the now deprecated Estimator interface + # which had a different number of arguments. When the deprecated API + # is removed this decorator and the _deprecated_init method below + # can be removed @singledispatchmethod def __init__( self, @@ -307,9 +310,9 @@ def __init__( self.tee = tee self.diagnostic_mode = diagnostic_mode self.solver_options = solver_options - self.pest_deprecated = ( - None # TODO: delete this when deprecated interface is removed - ) + + # TODO: delete this when the deprecated interface is removed + self.pest_deprecated = None # TODO This might not be needed here. # We could collect the union (or intersect?) of thetas when the models are built @@ -323,7 +326,11 @@ def __init__( # boolean to indicate if model is initialized using a square solve self.model_initialized = False - # use deprecated interface + # The deprecated Estimator constructor + # This works by checking the type of the first argument passed to + # the class constructor. If it matches the old interface (i.e. is + # callable) then this _deprecated_init method is called and the + # deprecation warning is displayed. @__init__.register(Callable) def _deprecated_init( self, From 927476913b1c0c6be56ac79c7095ad3414775d38 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 1 May 2024 09:48:10 -0600 Subject: [PATCH 1720/1797] Adding default values to the parmest Estimator docstring --- pyomo/contrib/parmest/parmest.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index 3516c52d19d..c63bb10b89e 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -263,12 +263,14 @@ class Estimator(object): If no function is specified, the model is used "as is" and should be defined with a "FirstStageCost" and "SecondStageCost" expression that are used to build an objective. + Default is None. tee: bool, optional - If True, print the solver output to the screen + If True, print the solver output to the screen. Default is False. diagnostic_mode: bool, optional - If True, print diagnostics from the solver + If True, print diagnostics from the solver. Default is False. solver_options: dict, optional - Provides options to the solver (also the name of an attribute) + Provides options to the solver (also the name of an attribute). + Default is None. """ # The singledispatchmethod decorator is used here as a deprecation From 48624f4f4198592b32e181c588ac508e7daa0923 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 1 May 2024 09:54:31 -0600 Subject: [PATCH 1721/1797] Removing list of parmest TODO items that was opened as a GitHub issue --- pyomo/contrib/parmest/parmest.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index c63bb10b89e..f3d35f41013 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -15,16 +15,6 @@ # TODO: move use_mpisppy to a Pyomo configuration option -# Redesign TODOS -# TODO: Create additional built in objective expressions in an Enum class which includes SSE (see SSE function below) -# TODO: Clean up the use of theta_names through out the code. The Experiment returns the CUID of each theta and this can be used directly (instead of the name) -# TODO: Clean up the use of updated_theta_names, model_theta_names, estimator_theta_names. Not sure if estimator_theta_names is the union or intersect of thetas in each model -# TODO: _return_theta_names should no longer be needed -# TODO: generally, theta ordering is not preserved by pyomo, so we should check that ordering -# matches values for each function, otherwise results will be wrong and/or inconsistent -# TODO: return model object (m.k1) and CUIDs in dataframes instead of names ("k1") - - # False implies always use the EF that is local to parmest use_mpisppy = True # Use it if we can but use local if not. if use_mpisppy: From e01830e117bf27f593fbc49689348c83ae5dc2ea Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 1 May 2024 11:35:33 -0600 Subject: [PATCH 1722/1797] Simplify checking for naming conflicts in parmest --- pyomo/contrib/parmest/parmest.py | 35 +++++++++++++++----------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index f3d35f41013..28506521524 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -350,14 +350,13 @@ def _deprecated_init( diagnostic_mode, solver_options, ) - return def _return_theta_names(self): """ Return list of fitted model parameter names """ # check for deprecated inputs - if self.pest_deprecated is not None: + if self.pest_deprecated: # if fitted model parameter names differ from theta_names # created when Estimator object is created @@ -365,9 +364,9 @@ def _return_theta_names(self): return self.pest_deprecated.theta_names_updated else: - return ( - self.pest_deprecated.theta_names - ) # default theta_names, created when Estimator object is created + + # default theta_names, created when Estimator object is created + return self.pest_deprecated.theta_names else: @@ -377,9 +376,9 @@ def _return_theta_names(self): return self.theta_names_updated else: - return ( - self.estimator_theta_names - ) # default theta_names, created when Estimator object is created + + # default theta_names, created when Estimator object is created + return self.estimator_theta_names def _expand_indexed_unknowns(self, model_temp): """ @@ -417,21 +416,19 @@ def _create_parmest_model(self, experiment_number): # Add objective function (optional) if self.obj_function: - for obj in model.component_objects(pyo.Objective): - if obj.name in ["Total_Cost_Objective"]: - raise RuntimeError( - "Parmest will not override the existing model Objective named " - + obj.name - ) - obj.deactivate() - for expr in model.component_data_objects(pyo.Expression): - if expr.name in ["FirstStageCost", "SecondStageCost"]: + # Check for component naming conflicts + reserved_names = ['Total_Cost_Objective', 'FirstStageCost', 'SecondStageCost'] + for n in reserved_names: + if model.component(n) or hasattr(model, n): raise RuntimeError( - "Parmest will not override the existing model Expression named " - + expr.name + f"Parmest will not override the existing model component named {n}" ) + # Deactivate any existing objective functions + for obj in model.component_objects(pyo.Objective): + obj.deactivate() + # TODO, this needs to be turned a enum class of options that still support custom functions if self.obj_function == 'SSE': second_stage_rule = SSE From d5a289b884738ee536fb7118517d1c7093dd3ec7 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 1 May 2024 12:15:29 -0600 Subject: [PATCH 1723/1797] Simplifying _expand_indexed_unknowns --- pyomo/contrib/parmest/parmest.py | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index 28506521524..1acd63c976d 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -384,22 +384,14 @@ def _expand_indexed_unknowns(self, model_temp): """ Expand indexed variables to get full list of thetas """ - model_theta_list = [k.name for k, v in model_temp.unknown_parameters.items()] - - # check for indexed theta items - indexed_theta_list = [] - for theta_i in model_theta_list: - var_cuid = ComponentUID(theta_i) - var_validate = var_cuid.find_component_on(model_temp) - for ind in var_validate.index_set(): - if ind is not None: - indexed_theta_list.append(theta_i + '[' + str(ind) + ']') - else: - indexed_theta_list.append(theta_i) - # if we found indexed thetas, use expanded list - if len(indexed_theta_list) > len(model_theta_list): - model_theta_list = indexed_theta_list + model_theta_list = [] + for c in model_temp.unknown_parameters.keys(): + if c.is_indexed(): + for _, ci in c.items(): + model_theta_list.append(ci.name) + else: + model_theta_list.append(c.name) return model_theta_list @@ -418,7 +410,11 @@ def _create_parmest_model(self, experiment_number): if self.obj_function: # Check for component naming conflicts - reserved_names = ['Total_Cost_Objective', 'FirstStageCost', 'SecondStageCost'] + reserved_names = [ + 'Total_Cost_Objective', + 'FirstStageCost', + 'SecondStageCost', + ] for n in reserved_names: if model.component(n) or hasattr(model, n): raise RuntimeError( From 27e84a8fd9d92b2e5e945650fcc8c1f6fb50570d Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 1 May 2024 16:04:55 -0600 Subject: [PATCH 1724/1797] Cleaning up logic in parmest --- pyomo/contrib/parmest/parmest.py | 34 +++++---------------- pyomo/contrib/parmest/tests/test_parmest.py | 2 +- 2 files changed, 9 insertions(+), 27 deletions(-) diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index 1acd63c976d..2d4c323b9b8 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -390,7 +390,7 @@ def _expand_indexed_unknowns(self, model_temp): if c.is_indexed(): for _, ci in c.items(): model_theta_list.append(ci.name) - else: + else: model_theta_list.append(c.name) return model_theta_list @@ -401,7 +401,6 @@ def _create_parmest_model(self, experiment_number): """ model = self.exp_list[experiment_number].get_labeled_model() - self.theta_names = [k.name for k, v in model.unknown_parameters.items()] if len(model.unknown_parameters) == 0: model.parmest_dummy_var = pyo.Var(initialize=1.0) @@ -443,29 +442,10 @@ def TotalCost_rule(model): ) # Convert theta Params to Vars, and unfix theta Vars - model = utils.convert_params_to_vars(model, self.theta_names) + theta_names = [k.name for k, v in model.unknown_parameters.items()] + parmest_model = utils.convert_params_to_vars(model, theta_names, fix_vars=False) - # Update theta names list to use CUID string representation - for i, theta in enumerate(self.theta_names): - var_cuid = ComponentUID(theta) - var_validate = var_cuid.find_component_on(model) - if var_validate is None: - logger.warning( - "theta_name[%s] (%s) was not found on the model", (i, theta) - ) - else: - try: - # If the component is not a variable, - # this will generate an exception (and the warning - # in the 'except') - var_validate.unfix() - self.theta_names[i] = repr(var_cuid) - except: - logger.warning(theta + ' is not a variable') - - self.parmest_model = model - - return model + return parmest_model def _instance_creation_callback(self, experiment_number=None, cb_data=None): model = self._create_parmest_model(experiment_number) @@ -1186,12 +1166,14 @@ def objective_at_theta(self, theta_values=None, initialize_parmest_model=False): # create a local instance of the pyomo model to access model variables and parameters model_temp = self._create_parmest_model(0) model_theta_list = self._expand_indexed_unknowns(model_temp) + # TODO: check if model_theta_list is correct if original unknown parameters + # are declared as params and transformed to vars during call to create_parmest_model - # if self.theta_names is not the same as temp model_theta_list, + # if self.estimator_theta_names is not the same as temp model_theta_list, # create self.theta_names_updated if set(self.estimator_theta_names) == set(model_theta_list) and len( self.estimator_theta_names - ) == set(model_theta_list): + ) == len(set(model_theta_list)): pass else: self.theta_names_updated = model_theta_list diff --git a/pyomo/contrib/parmest/tests/test_parmest.py b/pyomo/contrib/parmest/tests/test_parmest.py index e9a8e089335..0590f165da3 100644 --- a/pyomo/contrib/parmest/tests/test_parmest.py +++ b/pyomo/contrib/parmest/tests/test_parmest.py @@ -176,7 +176,7 @@ def test_diagnostic_mode(self): asym = np.arange(10, 30, 2) rate = np.arange(0, 1.5, 0.25) theta_vals = pd.DataFrame( - list(product(asym, rate)), columns=self.pest.theta_names + list(product(asym, rate)), columns=self.pest.estimator_theta_names ) obj_at_theta = self.pest.objective_at_theta(theta_vals) From ae2c5ab44f884dfe637321e4ab07849f5d9d1ce0 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Thu, 2 May 2024 17:46:02 -0600 Subject: [PATCH 1725/1797] More parmest cleanup --- pyomo/contrib/parmest/parmest.py | 2 +- pyomo/contrib/parmest/scenariocreator.py | 7 +--- pyomo/contrib/parmest/tests/test_examples.py | 5 ++- pyomo/contrib/parmest/tests/test_utils.py | 36 ++++++++------------ pyomo/contrib/parmest/utils/model_utils.py | 14 ++++++++ 5 files changed, 34 insertions(+), 30 deletions(-) diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index 2d4c323b9b8..105419dcb13 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -14,7 +14,6 @@ #### Redesign with Experiment class Dec 2023 # TODO: move use_mpisppy to a Pyomo configuration option - # False implies always use the EF that is local to parmest use_mpisppy = True # Use it if we can but use local if not. if use_mpisppy: @@ -1194,6 +1193,7 @@ def objective_at_theta(self, theta_values=None, initialize_parmest_model=False): ], "Theta name {} in 'theta_values' not in 'theta_names' {}".format( theta_temp, model_theta_list ) + assert len(list(theta_names)) == len(model_theta_list) all_thetas = theta_values.to_dict('records') diff --git a/pyomo/contrib/parmest/scenariocreator.py b/pyomo/contrib/parmest/scenariocreator.py index 2208bde91a0..7988cfa3f5f 100644 --- a/pyomo/contrib/parmest/scenariocreator.py +++ b/pyomo/contrib/parmest/scenariocreator.py @@ -169,12 +169,7 @@ def ScenariosFromExperiments(self, addtoSet): opt = pyo.SolverFactory(self.solvername) results = opt.solve(model) # solves and updates model ## pyo.check_termination_optimal(results) - ThetaVals = dict() - for theta in self.pest.theta_names: - tvar = eval('model.' + theta) - tval = pyo.value(tvar) - ##print(" theta, tval=", tvar, tval) - ThetaVals[theta] = tval + ThetaVals = {k.name: pyo.value(k) for k in model.unknown_parameters.keys()} addtoSet.addone(ParmestScen("ExpScen" + str(exp_num), ThetaVals, prob)) def ScenariosFromBootstrap(self, addtoSet, numtomake, seed=None): diff --git a/pyomo/contrib/parmest/tests/test_examples.py b/pyomo/contrib/parmest/tests/test_examples.py index 59a3e0adde2..dca05026e80 100644 --- a/pyomo/contrib/parmest/tests/test_examples.py +++ b/pyomo/contrib/parmest/tests/test_examples.py @@ -181,7 +181,10 @@ def test_multisensor_data_example(self): multisensor_data_example.main() - @unittest.skipUnless(matplotlib_available, "test requires matplotlib") + @unittest.skipUnless( + matplotlib_available and seaborn_available, + "test requires matplotlib and seaborn", + ) def test_datarec_example(self): from pyomo.contrib.parmest.examples.reactor_design import datarec_example diff --git a/pyomo/contrib/parmest/tests/test_utils.py b/pyomo/contrib/parmest/tests/test_utils.py index 611d67c1abb..d5e66ab58d5 100644 --- a/pyomo/contrib/parmest/tests/test_utils.py +++ b/pyomo/contrib/parmest/tests/test_utils.py @@ -25,18 +25,12 @@ ) @unittest.skipIf(not ipopt_available, "The 'ipopt' solver is not available") class TestUtils(unittest.TestCase): - @classmethod - def setUpClass(self): - pass - @classmethod - def tearDownClass(self): - pass - - @unittest.pytest.mark.expensive def test_convert_param_to_var(self): + # TODO: Check that this works for different structured models (indexed, blocks, etc) + from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( - reactor_design_model, + ReactorDesignExperiment, ) data = pd.DataFrame( @@ -49,24 +43,22 @@ def test_convert_param_to_var(self): ) # make model - instance = reactor_design_model() - - # add caf, sv - instance.caf = data.iloc[0]['caf'] - instance.sv = data.iloc[0]['sv'] - - solver = pyo.SolverFactory("ipopt") - solver.solve(instance) + exp = ReactorDesignExperiment(data, 0) + instance = exp.get_labeled_model() theta_names = ['k1', 'k2', 'k3'] - instance_vars = parmest.utils.convert_params_to_vars( + m_vars = parmest.utils.convert_params_to_vars( instance, theta_names, fix_vars=True ) - solver.solve(instance_vars) - assert instance.k1() == instance_vars.k1() - assert instance.k2() == instance_vars.k2() - assert instance.k3() == instance_vars.k3() + for v in theta_names: + self.assertTrue(hasattr(m_vars, v)) + c = m_vars.find_component(v) + self.assertIsInstance(c, pyo.Var) + self.assertTrue(c.fixed) + c_old = instance.find_component(v) + self.assertEqual(pyo.value(c), pyo.value(c_old)) + self.assertTrue(c in m_vars.unknown_parameters) if __name__ == "__main__": diff --git a/pyomo/contrib/parmest/utils/model_utils.py b/pyomo/contrib/parmest/utils/model_utils.py index 77491f74b02..7778ebcc9f1 100644 --- a/pyomo/contrib/parmest/utils/model_utils.py +++ b/pyomo/contrib/parmest/utils/model_utils.py @@ -15,6 +15,7 @@ from pyomo.core.expr import replace_expressions, identify_mutable_parameters from pyomo.core.base.var import IndexedVar from pyomo.core.base.param import IndexedParam +from pyomo.common.collections import ComponentMap from pyomo.environ import ComponentUID @@ -49,6 +50,7 @@ def convert_params_to_vars(model, param_names=None, fix_vars=False): # Convert Params to Vars, unfix Vars, and create a substitution map substitution_map = {} + comp_map = ComponentMap() for i, param_name in enumerate(param_names): # Leverage the parser in ComponentUID to locate the component. theta_cuid = ComponentUID(param_name) @@ -65,6 +67,7 @@ def convert_params_to_vars(model, param_names=None, fix_vars=False): theta_var_cuid = ComponentUID(theta_object.name) theta_var_object = theta_var_cuid.find_component_on(model) substitution_map[id(theta_object)] = theta_var_object + comp_map[theta_object] = theta_var_object # Indexed Param elif isinstance(theta_object, IndexedParam): @@ -90,6 +93,7 @@ def convert_params_to_vars(model, param_names=None, fix_vars=False): # Update substitution map (map each indexed param to indexed var) theta_var_cuid = ComponentUID(theta_object.name) theta_var_object = theta_var_cuid.find_component_on(model) + comp_map[theta_object] = theta_var_object var_theta_objects = [] for theta_obj in theta_var_object: theta_cuid = ComponentUID( @@ -101,6 +105,7 @@ def convert_params_to_vars(model, param_names=None, fix_vars=False): param_theta_objects, var_theta_objects ): substitution_map[id(param_theta_obj)] = var_theta_obj + comp_map[param_theta_obj] = var_theta_obj # Var or Indexed Var elif isinstance(theta_object, IndexedVar) or theta_object.is_variable_type(): @@ -182,6 +187,15 @@ def convert_params_to_vars(model, param_names=None, fix_vars=False): model.del_component(obj) model.add_component(obj.name, pyo.Objective(rule=expr, sense=obj.sense)) + # Convert Params to Vars in Suffixes + for s in model.component_objects(pyo.Suffix): + current_keys = list(s.keys()) + for c in current_keys: + if c in comp_map: + s[comp_map[c]] = s.pop(c) + + assert len(current_keys) == len(s.keys()) + # print('--- Updated Model ---') # model.pprint() # solver = pyo.SolverFactory('ipopt') From 88c12276c2d314eb01b3e0569ba5efbb29e41cf3 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Thu, 2 May 2024 18:09:32 -0600 Subject: [PATCH 1726/1797] Remove fixed TODO and fix typo in parmest test --- pyomo/contrib/parmest/parmest.py | 2 -- pyomo/contrib/parmest/tests/test_parmest.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index 105419dcb13..41e1724f94f 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -1165,8 +1165,6 @@ def objective_at_theta(self, theta_values=None, initialize_parmest_model=False): # create a local instance of the pyomo model to access model variables and parameters model_temp = self._create_parmest_model(0) model_theta_list = self._expand_indexed_unknowns(model_temp) - # TODO: check if model_theta_list is correct if original unknown parameters - # are declared as params and transformed to vars during call to create_parmest_model # if self.estimator_theta_names is not the same as temp model_theta_list, # create self.theta_names_updated diff --git a/pyomo/contrib/parmest/tests/test_parmest.py b/pyomo/contrib/parmest/tests/test_parmest.py index 0590f165da3..5f288154dcd 100644 --- a/pyomo/contrib/parmest/tests/test_parmest.py +++ b/pyomo/contrib/parmest/tests/test_parmest.py @@ -135,7 +135,7 @@ def test_likelihood_ratio(self): asym = np.arange(10, 30, 2) rate = np.arange(0, 1.5, 0.25) theta_vals = pd.DataFrame( - list(product(asym, rate)), columns=self.pest.theta_names + list(product(asym, rate)), columns=self.pest.estimator_theta_names ) obj_at_theta = self.pest.objective_at_theta(theta_vals) From 9cd9986227f23149dc814d731725da87cb6f958d Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 3 May 2024 11:51:52 -0600 Subject: [PATCH 1727/1797] Resolve (and test) error in RenamedClass when derived classes have multiple bases --- pyomo/common/deprecation.py | 2 +- pyomo/common/tests/test_deprecated.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pyomo/common/deprecation.py b/pyomo/common/deprecation.py index 5a6ca456079..c674dcddc78 100644 --- a/pyomo/common/deprecation.py +++ b/pyomo/common/deprecation.py @@ -542,7 +542,7 @@ def __renamed__warning__(msg): if new_class is None and '__renamed__new_class__' not in classdict: if not any( - hasattr(base, '__renamed__new_class__') + hasattr(mro, '__renamed__new_class__') for mro in itertools.chain.from_iterable( base.__mro__ for base in renamed_bases ) diff --git a/pyomo/common/tests/test_deprecated.py b/pyomo/common/tests/test_deprecated.py index 377e229c775..37e1ba81bb3 100644 --- a/pyomo/common/tests/test_deprecated.py +++ b/pyomo/common/tests/test_deprecated.py @@ -529,7 +529,10 @@ class DeprecatedClassSubclass(DeprecatedClass): out = StringIO() with LoggingIntercept(out): - class DeprecatedClassSubSubclass(DeprecatedClassSubclass): + class otherClass: + pass + + class DeprecatedClassSubSubclass(DeprecatedClassSubclass, otherClass): attr = 'DeprecatedClassSubSubclass' self.assertEqual(out.getvalue(), "") From 79e2e3650d49fb4d29bb65cd39a4edbe35b6835b Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 3 May 2024 11:53:34 -0600 Subject: [PATCH 1728/1797] Resolve backwards compatibility from renaming / removing pyomo_constant_types import --- pyomo/core/expr/numvalue.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pyomo/core/expr/numvalue.py b/pyomo/core/expr/numvalue.py index b656eea1bcd..3b335bd5fc4 100644 --- a/pyomo/core/expr/numvalue.py +++ b/pyomo/core/expr/numvalue.py @@ -44,6 +44,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.", ) +relocated_module_attribute( + 'pyomo_constant_types', + 'pyomo.common.numeric_types._pyomo_constant_types', + version='6.7.2.dev0', + f_globals=globals(), + 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.", +) relocated_module_attribute( 'RegisterNumericType', 'pyomo.common.numeric_types.RegisterNumericType', From 2d818adbfc0629d07a1111f66accd90d543eacdf Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 3 May 2024 11:54:15 -0600 Subject: [PATCH 1729/1797] Overhaul declare_custom_block to avoid using metaclasses --- pyomo/core/base/block.py | 87 ++++++++++++++--------------- pyomo/core/tests/unit/test_block.py | 31 ++++++++++ 2 files changed, 73 insertions(+), 45 deletions(-) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index 3eb18dde7a9..a27ca81bbfd 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -2333,48 +2333,42 @@ def components_data(block, ctype, sort=None, sort_by_keys=False, sort_by_names=F BlockData._Block_reserved_words = set(dir(Block())) -class _IndexedCustomBlockMeta(type): - """Metaclass for creating an indexed custom block.""" - - pass - - -class _ScalarCustomBlockMeta(type): - """Metaclass for creating a scalar custom block.""" - - def __new__(meta, name, bases, dct): - def __init__(self, *args, **kwargs): - # bases[0] is the custom block data object - bases[0].__init__(self, component=self) - # bases[1] is the custom block object that - # is used for declaration - bases[1].__init__(self, *args, **kwargs) - - dct["__init__"] = __init__ - return type.__new__(meta, name, bases, dct) +class ScalarCustomBlockMixin(object): + def __init__(self, *args, **kwargs): + # __bases__ for the ScalarCustomBlock is + # + # (ScalarCustomBlockMixin, {custom_data}, {custom_block}) + # + # Unfortunately, we cannot guarantee that this is being called + # from the ScalarCustomBlock (someone could have inherited from + # that class to make another scalar class). We will walk up the + # MRO to find the Scalar class (which should be the only class + # that has this Mixin as the first base class) + for cls in self.__class__.__mro__: + if cls.__bases__[0] is ScalarCustomBlockMixin: + _mixin, _data, _block = cls.__bases__ + _data.__init__(self, component=self) + _block.__init__(self, *args, **kwargs) + break class CustomBlock(Block): """The base class used by instances of custom block components""" - def __init__(self, *args, **kwds): + def __init__(self, *args, **kwargs): if self._default_ctype is not None: - kwds.setdefault('ctype', self._default_ctype) - Block.__init__(self, *args, **kwds) + kwargs.setdefault('ctype', self._default_ctype) + Block.__init__(self, *args, **kwargs) - def __new__(cls, *args, **kwds): - if cls.__name__.startswith('_Indexed') or cls.__name__.startswith('_Scalar'): + def __new__(cls, *args, **kwargs): + if cls.__bases__[0] is not CustomBlock: # we are entering here the second time (recursive) # therefore, we need to create what we have - return super(CustomBlock, cls).__new__(cls) + return super().__new__(cls, *args, **kwargs) if not args or (args[0] is UnindexedComponent_set and len(args) == 1): - n = _ScalarCustomBlockMeta( - "_Scalar%s" % (cls.__name__,), (cls._ComponentDataClass, cls), {} - ) - return n.__new__(n) + return super().__new__(cls._scalar_custom_block, *args, **kwargs) else: - n = _IndexedCustomBlockMeta("_Indexed%s" % (cls.__name__,), (cls,), {}) - return n.__new__(n) + return super().__new__(cls._indexed_custom_block, *args, **kwargs) def declare_custom_block(name, new_ctype=None): @@ -2386,9 +2380,9 @@ def declare_custom_block(name, new_ctype=None): ... pass """ - def proc_dec(cls): - # this is the decorator function that - # creates the block component class + def block_data_decorator(cls): + # this is the decorator function that creates the block + # component classes # Default (derived) Block attributes clsbody = { @@ -2399,7 +2393,7 @@ def proc_dec(cls): "_default_ctype": None, } - c = type( + c = type(CustomBlock)( name, # name of new class (CustomBlock,), # base classes clsbody, # class body definitions (will populate __dict__) @@ -2408,7 +2402,7 @@ def proc_dec(cls): if new_ctype is not None: if new_ctype is True: c._default_ctype = c - elif type(new_ctype) is type: + elif isinstance(new_ctype, type): c._default_ctype = new_ctype else: raise ValueError( @@ -2416,15 +2410,18 @@ def proc_dec(cls): "or 'True'; received: %s" % (new_ctype,) ) - # Register the new Block type in the same module as the BlockData - setattr(sys.modules[cls.__module__], name, c) - # TODO: can we also register concrete Indexed* and Scalar* - # classes into the original BlockData module (instead of relying - # on metaclasses)? + # Declare Indexed and Scalar versions of the custom blocks. We + # will register them both with the calling module scope, and + # with the CustomBlock (so that __new__ can route the object + # creation to the correct class) + c._indexed_custom_block = type(c)("Indexed" + name, (c,), {}) + c._scalar_custom_block = type(c)( + "Scalar" + name, (ScalarCustomBlockMixin, cls, c), {} + ) - # are these necessary? - setattr(cls, '_orig_name', name) - setattr(cls, '_orig_module', cls.__module__) + # Register the new Block types in the same module as the BlockData + for _cls in (c, c._indexed_custom_block, c._scalar_custom_block): + setattr(sys.modules[cls.__module__], _cls.__name__, _cls) return cls - return proc_dec + return block_data_decorator diff --git a/pyomo/core/tests/unit/test_block.py b/pyomo/core/tests/unit/test_block.py index 660f65f1944..33d6d2c8adc 100644 --- a/pyomo/core/tests/unit/test_block.py +++ b/pyomo/core/tests/unit/test_block.py @@ -13,6 +13,7 @@ # from io import StringIO +import logging import os import sys import types @@ -2975,6 +2976,36 @@ def test_write_exceptions(self): with self.assertRaisesRegex(ValueError, ".*Cannot write model in format"): m.write(format="bogus") + def test_custom_block(self): + @declare_custom_block('TestingBlock') + class TestingBlockData(BlockData): + def __init__(self, component): + BlockData.__init__(self, component) + logging.getLogger(__name__).warning("TestingBlockData.__init__") + + self.assertIn('TestingBlock', globals()) + self.assertIn('ScalarTestingBlock', globals()) + self.assertIn('IndexedTestingBlock', globals()) + + with LoggingIntercept() as LOG: + obj = TestingBlock() + self.assertIs(type(obj), ScalarTestingBlock) + self.assertEqual(LOG.getvalue().strip(), "TestingBlockData.__init__") + + with LoggingIntercept() as LOG: + obj = TestingBlock([1, 2]) + self.assertIs(type(obj), IndexedTestingBlock) + self.assertEqual(LOG.getvalue(), "") + + # Test that we can derive from a ScalarCustomBlock + class DerivedScalarTstingBlock(ScalarTestingBlock): + pass + + with LoggingIntercept() as LOG: + obj = DerivedScalarTstingBlock() + self.assertIs(type(obj), DerivedScalarTstingBlock) + self.assertEqual(LOG.getvalue().strip(), "TestingBlockData.__init__") + def test_override_pprint(self): @declare_custom_block('TempBlock') class TempBlockData(BlockData): From e4d26b3e37d2f074e58d5e272ea60a2c1604fada Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 3 May 2024 11:55:00 -0600 Subject: [PATCH 1730/1797] NFC: clarify comment --- pyomo/core/base/block.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index a27ca81bbfd..5513d405f4d 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -2412,8 +2412,8 @@ def block_data_decorator(cls): # Declare Indexed and Scalar versions of the custom blocks. We # will register them both with the calling module scope, and - # with the CustomBlock (so that __new__ can route the object - # creation to the correct class) + # with the CustomBlock (so that CustomBlock.__new__ can route + # the object creation to the correct class) c._indexed_custom_block = type(c)("Indexed" + name, (c,), {}) c._scalar_custom_block = type(c)( "Scalar" + name, (ScalarCustomBlockMixin, cls, c), {} From cff93a17664803915b0d0ea4d3bdf9f675a3a1d7 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Fri, 3 May 2024 14:11:47 -0600 Subject: [PATCH 1731/1797] Fixing non deterministic fragile test in parmest --- pyomo/contrib/parmest/tests/test_parmest.py | 23 ++++++++++----------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/pyomo/contrib/parmest/tests/test_parmest.py b/pyomo/contrib/parmest/tests/test_parmest.py index 5f288154dcd..69155dadb45 100644 --- a/pyomo/contrib/parmest/tests/test_parmest.py +++ b/pyomo/contrib/parmest/tests/test_parmest.py @@ -118,9 +118,9 @@ def test_bootstrap(self): 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) - self.assertTrue(CR[0.75].sum() == 7) - self.assertTrue(CR[1.0].sum() == 10) # all true + self.assertEqual(CR[0.5].sum(), 5) + self.assertEqual(CR[0.75].sum(), 7) + self.assertEqual(CR[1.0].sum(), 10) # all true graphics.pairwise_plot(theta_est) graphics.pairwise_plot(theta_est, thetavals) @@ -135,17 +135,16 @@ def test_likelihood_ratio(self): asym = np.arange(10, 30, 2) rate = np.arange(0, 1.5, 0.25) theta_vals = pd.DataFrame( - list(product(asym, rate)), columns=self.pest.estimator_theta_names + list(product(asym, rate)), columns=['asymptote', 'rate_constant'] ) - obj_at_theta = self.pest.objective_at_theta(theta_vals) LR = self.pest.likelihood_ratio_test(obj_at_theta, objval, [0.8, 0.9, 1.0]) self.assertTrue(set(LR.columns) >= set([0.8, 0.9, 1.0])) - self.assertTrue(LR[0.8].sum() == 6) - self.assertTrue(LR[0.9].sum() == 10) - self.assertTrue(LR[1.0].sum() == 60) # all true + self.assertEqual(LR[0.8].sum(), 6) + self.assertEqual(LR[0.9].sum(), 10) + self.assertEqual(LR[1.0].sum(), 60) # all true graphics.pairwise_plot(LR, thetavals, 0.8) @@ -164,9 +163,9 @@ def test_leaveNout(self): self.assertTrue(samples == [1]) # sample 1 was left out self.assertTrue(lno_theta.shape[0] == 1) # lno estimate for sample 1 self.assertTrue(set(lno_theta.columns) >= set([0.5, 1.0])) - self.assertTrue(lno_theta[1.0].sum() == 1) # all true - self.assertTrue(bootstrap_theta.shape[0] == 3) # bootstrap for sample 1 - self.assertTrue(bootstrap_theta[1.0].sum() == 3) # all true + self.assertEqual(lno_theta[1.0].sum(), 1) # all true + self.assertEqual(bootstrap_theta.shape[0], 3) # bootstrap for sample 1 + self.assertEqual(bootstrap_theta[1.0].sum(), 3) # all true def test_diagnostic_mode(self): self.pest.diagnostic_mode = True @@ -176,7 +175,7 @@ def test_diagnostic_mode(self): asym = np.arange(10, 30, 2) rate = np.arange(0, 1.5, 0.25) theta_vals = pd.DataFrame( - list(product(asym, rate)), columns=self.pest.estimator_theta_names + list(product(asym, rate)), columns=['asymptote', 'rate_constant'] ) obj_at_theta = self.pest.objective_at_theta(theta_vals) From cefb6d84a0a117ecb198c682b00465a924c00163 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 3 May 2024 14:30:18 -0600 Subject: [PATCH 1732/1797] Fixing typo in class name --- pyomo/core/tests/unit/test_block.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/core/tests/unit/test_block.py b/pyomo/core/tests/unit/test_block.py index 33d6d2c8adc..063db0e428e 100644 --- a/pyomo/core/tests/unit/test_block.py +++ b/pyomo/core/tests/unit/test_block.py @@ -2998,12 +2998,12 @@ def __init__(self, component): self.assertEqual(LOG.getvalue(), "") # Test that we can derive from a ScalarCustomBlock - class DerivedScalarTstingBlock(ScalarTestingBlock): + class DerivedScalarTestingBlock(ScalarTestingBlock): pass with LoggingIntercept() as LOG: - obj = DerivedScalarTstingBlock() - self.assertIs(type(obj), DerivedScalarTstingBlock) + obj = DerivedScalarTestingBlock() + self.assertIs(type(obj), DerivedScalarTestingBlock) self.assertEqual(LOG.getvalue().strip(), "TestingBlockData.__init__") def test_override_pprint(self): From e96b382392eef69a5feac0d85c6593486e7e2d42 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Fri, 3 May 2024 15:11:13 -0600 Subject: [PATCH 1733/1797] Try relaxing gurobipy version --- .github/workflows/test_branches.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 75db5d66431..3396eca0176 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -111,6 +111,14 @@ jobs: TARGET: win PYENV: pip + - os: ubuntu-latest + python: '3.11' + other: /singletest + category: "-m 'neos or importtest'" + skip_doctest: 1 + TARGET: linux + PYENV: pip + steps: - name: Checkout Pyomo source uses: actions/checkout@v4 @@ -265,7 +273,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==10.0.3 \ + -i https://pypi.gurobi.com gurobipy \ || echo "WARNING: Gurobi is not available" python -m pip install --cache-dir cache/pip xpress \ || echo "WARNING: Xpress Community Edition is not available" From 151c4aa10364be25deb2427950c0ead38cc549eb Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 3 May 2024 15:24:01 -0600 Subject: [PATCH 1734/1797] Ensure all custom block classes are assigned to the module scope --- pyomo/core/base/block.py | 22 ++++++++++++++++------ pyomo/core/tests/unit/test_block.py | 3 +++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index 5513d405f4d..376ed30e1dd 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -2380,13 +2380,13 @@ def declare_custom_block(name, new_ctype=None): ... pass """ - def block_data_decorator(cls): + def block_data_decorator(block_data): # this is the decorator function that creates the block # component classes # Default (derived) Block attributes clsbody = { - "__module__": cls.__module__, # magic to fix the module + "__module__": block_data.__module__, # magic to fix the module # Default IndexedComponent data object is the decorated class: "_ComponentDataClass": cls, # By default this new block does not declare a new ctype @@ -2414,14 +2414,24 @@ def block_data_decorator(cls): # will register them both with the calling module scope, and # with the CustomBlock (so that CustomBlock.__new__ can route # the object creation to the correct class) - c._indexed_custom_block = type(c)("Indexed" + name, (c,), {}) + c._indexed_custom_block = type(c)( + "Indexed" + name, + (c,), + { # ensure the created class is associated with the calling module + "__module__": block_data.__module__ + }, + ) c._scalar_custom_block = type(c)( - "Scalar" + name, (ScalarCustomBlockMixin, cls, c), {} + "Scalar" + name, + (ScalarCustomBlockMixin, block_data, c), + { # ensure the created class is associated with the calling module + "__module__": block_data.__module__ + }, ) # Register the new Block types in the same module as the BlockData for _cls in (c, c._indexed_custom_block, c._scalar_custom_block): - setattr(sys.modules[cls.__module__], _cls.__name__, _cls) - return cls + setattr(sys.modules[block_data.__module__], _cls.__name__, _cls) + return block_data return block_data_decorator diff --git a/pyomo/core/tests/unit/test_block.py b/pyomo/core/tests/unit/test_block.py index 063db0e428e..bf4a5d58636 100644 --- a/pyomo/core/tests/unit/test_block.py +++ b/pyomo/core/tests/unit/test_block.py @@ -2986,6 +2986,9 @@ def __init__(self, component): self.assertIn('TestingBlock', globals()) self.assertIn('ScalarTestingBlock', globals()) self.assertIn('IndexedTestingBlock', globals()) + self.assertIs(TestingBlock.__module__, __name__) + self.assertIs(ScalarTestingBlock.__module__, __name__) + self.assertIs(IndexedTestingBlock.__module__, __name__) with LoggingIntercept() as LOG: obj = TestingBlock() From 04a9708e169b6f417d6a470b14b6cb38b9d756b3 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 3 May 2024 15:24:26 -0600 Subject: [PATCH 1735/1797] Improve documentation --- pyomo/core/base/block.py | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index 376ed30e1dd..72d66c67c60 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -2362,9 +2362,16 @@ def __init__(self, *args, **kwargs): def __new__(cls, *args, **kwargs): if cls.__bases__[0] is not CustomBlock: - # we are entering here the second time (recursive) - # therefore, we need to create what we have + # we are creating a class other than the "generic" derived + # custom block class. We can assume that the routing of the + # generic block class to the specific Scalar or Indexed + # subclass has already occurred and we can pass control up + # to (toward) object.__new__() return super().__new__(cls, *args, **kwargs) + # If the first base class is this CustomBlock class, then the + # user is attempting to create the "generic" block class. + # Depending on the arguments, we need to map this to either the + # Scalar or Indexed block subclass. if not args or (args[0] is UnindexedComponent_set and len(args) == 1): return super().__new__(cls._scalar_custom_block, *args, **kwargs) else: @@ -2374,7 +2381,7 @@ def __new__(cls, *args, **kwargs): def declare_custom_block(name, new_ctype=None): """Decorator to declare components for a custom block data class - >>> @declare_custom_block(name=FooBlock) + >>> @declare_custom_block(name="FooBlock") ... class FooBlockData(BlockData): ... # custom block data class ... pass @@ -2384,19 +2391,24 @@ def block_data_decorator(block_data): # this is the decorator function that creates the block # component classes - # Default (derived) Block attributes - clsbody = { - "__module__": block_data.__module__, # magic to fix the module - # Default IndexedComponent data object is the decorated class: - "_ComponentDataClass": cls, - # By default this new block does not declare a new ctype - "_default_ctype": None, - } - + # Declare the new Block (derived from CustomBlock) corresponding + # to the BlockData that we are decorating + # + # Note the use of `type(CustomBlock)` to pick up the metaclass + # that was used to create the CustomBlock (in general, it should + # be `type`) c = type(CustomBlock)( name, # name of new class (CustomBlock,), # base classes - clsbody, # class body definitions (will populate __dict__) + # class body definitions (populate the new class' __dict__) + { + # ensure the created class is associated with the calling module + "__module__": block_data.__module__, + # Default IndexedComponent data object is the decorated class: + "_ComponentDataClass": block_data, + # By default this new block does not declare a new ctype + "_default_ctype": None, + }, ) if new_ctype is not None: @@ -2410,7 +2422,7 @@ def block_data_decorator(block_data): "or 'True'; received: %s" % (new_ctype,) ) - # Declare Indexed and Scalar versions of the custom blocks. We + # Declare Indexed and Scalar versions of the custom block. We # will register them both with the calling module scope, and # with the CustomBlock (so that CustomBlock.__new__ can route # the object creation to the correct class) From a934e9f86540058d9a81a0989432458cb702521f Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Fri, 3 May 2024 15:28:53 -0600 Subject: [PATCH 1736/1797] Try grabbing gurobipy straight from pypi --- .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 3396eca0176..dc0bdecff18 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -272,8 +272,7 @@ jobs: if test -z "${{matrix.slim}}"; then 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 \ + python -m pip install --cache-dir cache/pip gurobipy\ || echo "WARNING: Gurobi is not available" python -m pip install --cache-dir cache/pip xpress \ || echo "WARNING: Xpress Community Edition is not available" From fea6ca8f6cd62491acedb89c130e7749a26693b7 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 3 May 2024 15:34:55 -0600 Subject: [PATCH 1737/1797] Improve variable naming --- pyomo/core/base/block.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index 72d66c67c60..26f2d7071b1 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -2391,13 +2391,13 @@ def block_data_decorator(block_data): # this is the decorator function that creates the block # component classes - # Declare the new Block (derived from CustomBlock) corresponding - # to the BlockData that we are decorating + # Declare the new Block component (derived from CustomBlock) + # corresponding to the BlockData that we are decorating # # Note the use of `type(CustomBlock)` to pick up the metaclass # that was used to create the CustomBlock (in general, it should # be `type`) - c = type(CustomBlock)( + comp = type(CustomBlock)( name, # name of new class (CustomBlock,), # base classes # class body definitions (populate the new class' __dict__) @@ -2413,9 +2413,9 @@ def block_data_decorator(block_data): if new_ctype is not None: if new_ctype is True: - c._default_ctype = c + comp._default_ctype = comp elif isinstance(new_ctype, type): - c._default_ctype = new_ctype + comp._default_ctype = new_ctype else: raise ValueError( "Expected new_ctype to be either type " @@ -2426,23 +2426,23 @@ def block_data_decorator(block_data): # will register them both with the calling module scope, and # with the CustomBlock (so that CustomBlock.__new__ can route # the object creation to the correct class) - c._indexed_custom_block = type(c)( + comp._indexed_custom_block = type(comp)( "Indexed" + name, - (c,), + (comp,), { # ensure the created class is associated with the calling module "__module__": block_data.__module__ }, ) - c._scalar_custom_block = type(c)( + comp._scalar_custom_block = type(comp)( "Scalar" + name, - (ScalarCustomBlockMixin, block_data, c), + (ScalarCustomBlockMixin, block_data, comp), { # ensure the created class is associated with the calling module "__module__": block_data.__module__ }, ) # Register the new Block types in the same module as the BlockData - for _cls in (c, c._indexed_custom_block, c._scalar_custom_block): + for _cls in (comp, comp._indexed_custom_block, comp._scalar_custom_block): setattr(sys.modules[block_data.__module__], _cls.__name__, _cls) return block_data From 6d94224f965fa1415f8da78da974d50df9d58046 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Fri, 3 May 2024 15:56:03 -0600 Subject: [PATCH 1738/1797] Repinning gurobipy version --- .github/workflows/test_branches.yml | 10 +--------- .github/workflows/test_pr_and_main.yml | 3 +-- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index dc0bdecff18..611875fb456 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -111,14 +111,6 @@ jobs: TARGET: win PYENV: pip - - os: ubuntu-latest - python: '3.11' - other: /singletest - category: "-m 'neos or importtest'" - skip_doctest: 1 - TARGET: linux - PYENV: pip - steps: - name: Checkout Pyomo source uses: actions/checkout@v4 @@ -272,7 +264,7 @@ jobs: if test -z "${{matrix.slim}}"; then 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 gurobipy\ + python -m pip install --cache-dir cache/pip 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 5a484dccbc8..5b1bca70ede 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -301,8 +301,7 @@ jobs: if test -z "${{matrix.slim}}"; then 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==10.0.3 \ + python -m pip install --cache-dir cache/pip 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 97b3b0a9efda63e1021d038de1430cd021692bc7 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Fri, 3 May 2024 17:16:21 -0600 Subject: [PATCH 1739/1797] Minor edits to scenariocreator in parmest --- pyomo/contrib/parmest/scenariocreator.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/pyomo/contrib/parmest/scenariocreator.py b/pyomo/contrib/parmest/scenariocreator.py index 7988cfa3f5f..1729c6e6c72 100644 --- a/pyomo/contrib/parmest/scenariocreator.py +++ b/pyomo/contrib/parmest/scenariocreator.py @@ -14,11 +14,6 @@ import pyomo.environ as pyo -from pyomo.common.deprecation import deprecated -from pyomo.common.deprecation import deprecation_warning - -DEPRECATION_VERSION = '6.7.2.dev0' - import logging logger = logging.getLogger(__name__) @@ -129,14 +124,9 @@ class ScenarioCreator(object): def __init__(self, pest, solvername): - # is this a deprecated pest object? + # Check if we're using the deprecated parmest API self.scen_deprecated = None if pest.pest_deprecated is not None: - deprecation_warning( - "Using a deprecated parmest object for scenario " - + "creator, please recreate object using experiment lists.", - version=DEPRECATION_VERSION, - ) self.scen_deprecated = _ScenarioCreatorDeprecated( pest.pest_deprecated, solvername ) @@ -218,7 +208,7 @@ def ScenariosFromExperiments(self, addtoSet): a ScenarioSet """ - # assert isinstance(addtoSet, ScenarioSet) + assert isinstance(addtoSet, ScenarioSet) scenario_numbers = list(range(len(self.pest.callback_data))) @@ -247,7 +237,7 @@ def ScenariosFromBootstrap(self, addtoSet, numtomake, seed=None): numtomake (int) : number of scenarios to create """ - # assert isinstance(addtoSet, ScenarioSet) + assert isinstance(addtoSet, ScenarioSet) bootstrap_thetas = self.pest.theta_est_bootstrap(numtomake, seed=seed) addtoSet.append_bootstrap(bootstrap_thetas) From ff26f636f2d312505bd67822efbf7504d38b6445 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Fri, 3 May 2024 17:48:00 -0600 Subject: [PATCH 1740/1797] Simplify scenariocreator code in parmest to remove duplication --- pyomo/contrib/parmest/scenariocreator.py | 108 ++++++----------------- 1 file changed, 25 insertions(+), 83 deletions(-) diff --git a/pyomo/contrib/parmest/scenariocreator.py b/pyomo/contrib/parmest/scenariocreator.py index 1729c6e6c72..e887dd2e8be 100644 --- a/pyomo/contrib/parmest/scenariocreator.py +++ b/pyomo/contrib/parmest/scenariocreator.py @@ -124,78 +124,6 @@ class ScenarioCreator(object): def __init__(self, pest, solvername): - # Check if we're using the deprecated parmest API - self.scen_deprecated = None - if pest.pest_deprecated is not None: - self.scen_deprecated = _ScenarioCreatorDeprecated( - pest.pest_deprecated, solvername - ) - else: - self.pest = pest - self.solvername = solvername - - def ScenariosFromExperiments(self, addtoSet): - """Creates new self.Scenarios list using the experiments only. - - Args: - addtoSet (ScenarioSet): the scenarios will be added to this set - Returns: - a ScenarioSet - """ - - # check if using deprecated pest object - if self.scen_deprecated is not None: - self.scen_deprecated.ScenariosFromExperiments(addtoSet) - return - - assert isinstance(addtoSet, ScenarioSet) - - scenario_numbers = list(range(len(self.pest.exp_list))) - - prob = 1.0 / len(scenario_numbers) - for exp_num in scenario_numbers: - ##print("Experiment number=", exp_num) - model = self.pest._instance_creation_callback(exp_num) - opt = pyo.SolverFactory(self.solvername) - results = opt.solve(model) # solves and updates model - ## pyo.check_termination_optimal(results) - ThetaVals = {k.name: pyo.value(k) for k in model.unknown_parameters.keys()} - addtoSet.addone(ParmestScen("ExpScen" + str(exp_num), ThetaVals, prob)) - - def ScenariosFromBootstrap(self, addtoSet, numtomake, seed=None): - """Creates new self.Scenarios list using the experiments only. - - Args: - addtoSet (ScenarioSet): the scenarios will be added to this set - numtomake (int) : number of scenarios to create - """ - - # check if using deprecated pest object - if self.scen_deprecated is not None: - self.scen_deprecated.ScenariosFromBootstrap(addtoSet, numtomake, seed=seed) - return - - assert isinstance(addtoSet, ScenarioSet) - - bootstrap_thetas = self.pest.theta_est_bootstrap(numtomake, seed=seed) - addtoSet.append_bootstrap(bootstrap_thetas) - - -################################ -# deprecated functions/classes # -################################ - - -class _ScenarioCreatorDeprecated(object): - """Create scenarios from parmest. - - Args: - pest (Estimator): the parmest object - solvername (str): name of the solver (e.g. "ipopt") - - """ - - def __init__(self, pest, solvername): self.pest = pest self.solvername = solvername @@ -210,23 +138,32 @@ def ScenariosFromExperiments(self, addtoSet): assert isinstance(addtoSet, ScenarioSet) - scenario_numbers = list(range(len(self.pest.callback_data))) + if self.pest.pest_deprecated is not None: + scenario_numbers = list(range(len(self.pest.pest_deprecated.callback_data))) + else: + scenario_numbers = list(range(len(self.pest.exp_list))) prob = 1.0 / len(scenario_numbers) for exp_num in scenario_numbers: ##print("Experiment number=", exp_num) - model = self.pest._instance_creation_callback( - exp_num, self.pest.callback_data - ) + if self.pest.pest_deprecated is not None: + model = self.pest.pest_deprecated._instance_creation_callback( + exp_num, self.pest.pest_deprecated.callback_data + ) + else: + model = self.pest._instance_creation_callback(exp_num) opt = pyo.SolverFactory(self.solvername) results = opt.solve(model) # solves and updates model ## pyo.check_termination_optimal(results) - ThetaVals = dict() - for theta in self.pest.theta_names: - tvar = eval('model.' + theta) - tval = pyo.value(tvar) - ##print(" theta, tval=", tvar, tval) - ThetaVals[theta] = tval + if self.pest.pest_deprecated is not None: + ThetaVals = { + theta: pyo.value(model.find_component(theta)) + for theta in self.pest.pest_deprecated.theta_names + } + else: + ThetaVals = { + k.name: pyo.value(k) for k in model.unknown_parameters.keys() + } addtoSet.addone(ParmestScen("ExpScen" + str(exp_num), ThetaVals, prob)) def ScenariosFromBootstrap(self, addtoSet, numtomake, seed=None): @@ -239,5 +176,10 @@ def ScenariosFromBootstrap(self, addtoSet, numtomake, seed=None): assert isinstance(addtoSet, ScenarioSet) - bootstrap_thetas = self.pest.theta_est_bootstrap(numtomake, seed=seed) + if self.pest.pest_deprecated is not None: + bootstrap_thetas = self.pest.pest_deprecated.theta_est_bootstrap( + numtomake, seed=seed + ) + else: + bootstrap_thetas = self.pest.theta_est_bootstrap(numtomake, seed=seed) addtoSet.append_bootstrap(bootstrap_thetas) From e13edeea1401b4d105c795cff610557321291c95 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Fri, 3 May 2024 22:14:07 -0600 Subject: [PATCH 1741/1797] Test ctype management in declare_custom_block() --- pyomo/core/tests/unit/test_block.py | 30 ++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/pyomo/core/tests/unit/test_block.py b/pyomo/core/tests/unit/test_block.py index bf4a5d58636..3d578f7dc88 100644 --- a/pyomo/core/tests/unit/test_block.py +++ b/pyomo/core/tests/unit/test_block.py @@ -3009,7 +3009,35 @@ class DerivedScalarTestingBlock(ScalarTestingBlock): self.assertIs(type(obj), DerivedScalarTestingBlock) self.assertEqual(LOG.getvalue().strip(), "TestingBlockData.__init__") - def test_override_pprint(self): + def test_custom_block_ctypes(self): + @declare_custom_block('TestingBlock') + class TestingBlockData(BlockData): + pass + + self.assertIs(TestingBlock().ctype, Block) + + @declare_custom_block('TestingBlock', True) + class TestingBlockData(BlockData): + pass + + self.assertIs(TestingBlock().ctype, TestingBlock) + + @declare_custom_block('TestingBlock', Constraint) + class TestingBlockData(BlockData): + pass + + self.assertIs(TestingBlock().ctype, Constraint) + + with self.assertRaisesRegex( + ValueError, + r"Expected new_ctype to be either type or 'True'; received: \[\]", + ): + + @declare_custom_block('TestingBlock', []) + class TestingBlockData(BlockData): + pass + + def test_custom_block_override_pprint(self): @declare_custom_block('TempBlock') class TempBlockData(BlockData): def pprint(self, ostream=None, verbose=False, prefix=""): From 54bd3d393b73d1dcfa6bdec1854b69893eaec679 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 5 May 2024 10:08:04 -0600 Subject: [PATCH 1742/1797] Move the ginac interface sources to a subdirectory --- pyomo/contrib/simplification/build.py | 9 ++-- .../contrib/simplification/ginac/__init__.py | 52 +++++++++++++++++++ .../{ => ginac/src}/ginac_interface.cpp | 0 .../{ => ginac/src}/ginac_interface.hpp | 0 pyomo/contrib/simplification/simplify.py | 14 ++--- 5 files changed, 62 insertions(+), 13 deletions(-) create mode 100644 pyomo/contrib/simplification/ginac/__init__.py rename pyomo/contrib/simplification/{ => ginac/src}/ginac_interface.cpp (100%) rename pyomo/contrib/simplification/{ => ginac/src}/ginac_interface.hpp (100%) diff --git a/pyomo/contrib/simplification/build.py b/pyomo/contrib/simplification/build.py index a4094f993fa..0ae883bc55c 100644 --- a/pyomo/contrib/simplification/build.py +++ b/pyomo/contrib/simplification/build.py @@ -88,9 +88,10 @@ def build_ginac_interface(parallel=None, args=None): if args is None: args = [] - dname = this_file_dir() - _sources = ['ginac_interface.cpp'] - sources = [os.path.join(dname, fname) for fname in _sources] + sources = [ + os.path.join(this_file_dir(), 'ginac', 'src', fname) + for fname in ['ginac_interface.cpp'] + ] ginac_lib = find_library('ginac') if not ginac_lib: @@ -132,7 +133,7 @@ def run(self): basedir = os.path.abspath(os.path.curdir) with TempfileManager.new_context() as tempfile: if self.inplace: - tmpdir = this_file_dir() + tmpdir = os.path.join(this_file_dir(), 'ginac') else: tmpdir = os.path.abspath(tempfile.mkdtemp()) sys.stdout.write("Building in '%s'" % tmpdir) diff --git a/pyomo/contrib/simplification/ginac/__init__.py b/pyomo/contrib/simplification/ginac/__init__.py new file mode 100644 index 00000000000..af6511944de --- /dev/null +++ b/pyomo/contrib/simplification/ginac/__init__.py @@ -0,0 +1,52 @@ +# ___________________________________________________________________________ +# +# 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.dependencies import attempt_import as _attempt_import + + +def _importer(): + import os + import sys + from ctypes import cdll + from pyomo.common.envvar import PYOMO_CONFIG_DIR + from pyomo.common.fileutils import find_library + + try: + pyomo_config_dir = os.path.join( + PYOMO_CONFIG_DIR, + 'lib', + 'python%s.%s' % sys.version_info[:2], + 'site-packages', + ) + sys.path.insert(0, pyomo_config_dir) + # GiNaC needs 2 libraries that are generally dynamically linked + # to the interface library. If we built those ourselves, then + # the libraries will be PYOMO_CONFIG_DIR/lib ... but that + # directlor is very likely to NOT be on the library search path + # when the Python interpreter was started. We will manually + # look for those two libraries, and if we find them, load them + # into this process (so the interface can find them) + for lib in ('cln', 'ginac'): + fname = find_library(lib) + if fname is not None: + cdll.LoadLibrary(fname) + + import ginac_interface + except ImportError: + from . import ginac_interface + finally: + assert sys.path[0] == pyomo_config_dir + sys.path.pop(0) + + return ginac_interface + + +interface, interface_available = _attempt_import('ginac_interface', importer=_importer) diff --git a/pyomo/contrib/simplification/ginac_interface.cpp b/pyomo/contrib/simplification/ginac/src/ginac_interface.cpp similarity index 100% rename from pyomo/contrib/simplification/ginac_interface.cpp rename to pyomo/contrib/simplification/ginac/src/ginac_interface.cpp diff --git a/pyomo/contrib/simplification/ginac_interface.hpp b/pyomo/contrib/simplification/ginac/src/ginac_interface.hpp similarity index 100% rename from pyomo/contrib/simplification/ginac_interface.hpp rename to pyomo/contrib/simplification/ginac/src/ginac_interface.hpp diff --git a/pyomo/contrib/simplification/simplify.py b/pyomo/contrib/simplification/simplify.py index 27da5f5ca34..94f0ceaa33f 100644 --- a/pyomo/contrib/simplification/simplify.py +++ b/pyomo/contrib/simplification/simplify.py @@ -15,14 +15,10 @@ import logging import warnings -try: - from pyomo.contrib.simplification.ginac_interface import GinacInterface - - ginac_available = True -except: - GinacInterface = None - ginac_available = False - +from pyomo.contrib.simplification.ginac import ( + interface as ginac_interface, + interface_available as ginac_available, +) logger = logging.getLogger(__name__) @@ -51,7 +47,7 @@ def simplify_with_ginac(expr: NumericExpression, ginac_interface): class Simplifier(object): def __init__(self, suppress_no_ginac_warnings: bool = False) -> None: if ginac_available: - self.gi = GinacInterface(False) + self.gi = ginac_interface.GinacInterface(False) self.suppress_no_ginac_warnings = suppress_no_ginac_warnings def simplify(self, expr: NumericExpression): From 8a37c8b1e0a4a54702eb81996d5730a4e55c0da5 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 5 May 2024 10:08:50 -0600 Subject: [PATCH 1743/1797] Update builder to use argparse, clean up output --- pyomo/contrib/simplification/build.py | 73 +++++++++++++++------------ 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/pyomo/contrib/simplification/build.py b/pyomo/contrib/simplification/build.py index 0ae883bc55c..d540991b010 100644 --- a/pyomo/contrib/simplification/build.py +++ b/pyomo/contrib/simplification/build.py @@ -21,12 +21,11 @@ from pyomo.common.fileutils import find_library, this_file_dir from pyomo.common.tempfiles import TempfileManager +logger = logging.getLogger(__name__ if __name__ != '__main__' else 'pyomo') -logger = logging.getLogger(__name__) - -def build_ginac_library(parallel=None, argv=None): - sys.stdout.write("\n**** Building GiNaC library ****") +def build_ginac_library(parallel=None, argv=None, env=None): + sys.stdout.write("\n**** Building GiNaC library ****\n") configure_cmd = ['configure', '--prefix=' + PYOMO_CONFIG_DIR, '--disable-static'] make_cmd = ['make'] @@ -34,6 +33,12 @@ def build_ginac_library(parallel=None, argv=None): make_cmd.append(f'-j{parallel}') install_cmd = ['make', 'install'] + env = dict(os.environ) + pcdir = os.path.join(PYOMO_CONFIG_DIR, 'lib', 'pkgconfig') + if 'PKG_CONFIG_PATH' in env: + pcdir += os.pathsep + env['PKG_CONFIG_PATH'] + env['PKG_CONFIG_PATH'] = pcdir + with TempfileManager.new_context() as tempfile: tmpdir = tempfile.mkdtemp() @@ -49,10 +54,10 @@ def build_ginac_library(parallel=None, argv=None): % (url, downloader.destination()) ) downloader.get_tar_archive(url, dirOffset=1) - assert subprocess.run(configure_cmd, cwd=cln_dir).returncode == 0 + assert subprocess.run(configure_cmd, cwd=cln_dir, env=env).returncode == 0 logger.info("\nBuilding CLN\n") - assert subprocess.run(make_cmd, cwd=cln_dir).returncode == 0 - assert subprocess.run(install_cmd, cwd=cln_dir).returncode == 0 + assert subprocess.run(make_cmd, cwd=cln_dir, env=env).returncode == 0 + assert subprocess.run(install_cmd, cwd=cln_dir, env=env).returncode == 0 url = 'https://www.ginac.de/ginac-1.8.7.tar.bz2' ginac_dir = os.path.join(tmpdir, 'ginac') @@ -62,10 +67,10 @@ def build_ginac_library(parallel=None, argv=None): % (url, downloader.destination()) ) downloader.get_tar_archive(url, dirOffset=1) - assert subprocess.run(configure_cmd, cwd=ginac_dir).returncode == 0 + assert subprocess.run(configure_cmd, cwd=ginac_dir, env=env).returncode == 0 logger.info("\nBuilding GiNaC\n") - assert subprocess.run(make_cmd, cwd=ginac_dir).returncode == 0 - assert subprocess.run(install_cmd, cwd=ginac_dir).returncode == 0 + assert subprocess.run(make_cmd, cwd=ginac_dir, env=env).returncode == 0 + assert subprocess.run(install_cmd, cwd=ginac_dir, env=env).returncode == 0 def _find_include(libdir, incpaths): @@ -84,7 +89,7 @@ def build_ginac_interface(parallel=None, args=None): from pybind11.setup_helpers import Pybind11Extension, build_ext from pyomo.common.cmake_builder import handleReadonly - sys.stdout.write("\n**** Building GiNaC interface ****") + sys.stdout.write("\n**** Building GiNaC interface ****\n") if args is None: args = [] @@ -98,7 +103,7 @@ def build_ginac_interface(parallel=None, args=None): raise RuntimeError( 'could not find the GiNaC library; please make sure either to install ' 'the library and development headers system-wide, or include the ' - 'path tt the library in the LD_LIBRARY_PATH environment variable' + 'path to the library in the LD_LIBRARY_PATH environment variable' ) ginac_lib_dir = os.path.dirname(ginac_lib) ginac_include_dir = _find_include(ginac_lib_dir, ('ginac', 'ginac.h')) @@ -110,7 +115,7 @@ def build_ginac_interface(parallel=None, args=None): raise RuntimeError( 'could not find the CLN library; please make sure either to install ' 'the library and development headers system-wide, or include the ' - 'path tt the library in the LD_LIBRARY_PATH environment variable' + 'path to the library in the LD_LIBRARY_PATH environment variable' ) cln_lib_dir = os.path.dirname(cln_lib) cln_include_dir = _find_include(cln_lib_dir, ('cln', 'cln.h')) @@ -136,7 +141,7 @@ def run(self): tmpdir = os.path.join(this_file_dir(), 'ginac') else: tmpdir = os.path.abspath(tempfile.mkdtemp()) - sys.stdout.write("Building in '%s'" % tmpdir) + sys.stdout.write("Building in '%s'\n" % tmpdir) os.chdir(tmpdir) super(ginacBuildExt, self).run() if not self.inplace: @@ -174,21 +179,25 @@ def skip(self): if __name__ == '__main__': - logging.getLogger('pyomo').setLevel(logging.DEBUG) - parallel = None - for i, arg in enumerate(sys.argv): - if arg == '-j': - parallel = int(sys.argv.pop(i + 1)) - sys.argv.pop(i) - break - if arg.startswith('-j'): - if '=' in arg: - parallel = int(arg.split('=')[1]) - else: - parallel = int(arg[2:]) - sys.argv.pop(i) - break - if '--build-deps' in sys.argv: - sys.argv.remove('--build-deps') - build_ginac_library(parallel, []) - build_ginac_interface(parallel, sys.argv[1:]) + import argparse + + parser = argparse.ArgumentParser() + parser.add_argument( + "-j", + dest='parallel', + type=int, + default=None, + help="Enable parallel build with PARALLEL cores", + ) + parser.add_argument( + "--build-deps", + dest='build_deps', + action='store_true', + default=False, + help="Download and build the CLN/GiNaC libraries", + ) + options, argv = parser.parse_known_args(sys.argv) + logging.getLogger('pyomo').setLevel(logging.INFO) + if options.build_deps: + build_ginac_library(options.parallel, []) + build_ginac_interface(options.parallel, argv[1:]) From c2c63bc52acedace36de75d6ce09c0d062d1ef84 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 5 May 2024 10:09:09 -0600 Subject: [PATCH 1744/1797] Run sympy tests any time sympy is installed --- pyomo/contrib/simplification/tests/test_simplification.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/simplification/tests/test_simplification.py b/pyomo/contrib/simplification/tests/test_simplification.py index be61631e9f3..27208d42229 100644 --- a/pyomo/contrib/simplification/tests/test_simplification.py +++ b/pyomo/contrib/simplification/tests/test_simplification.py @@ -100,12 +100,12 @@ def test_unary(self): assertExpressionsEqual(self, e, e2) -@unittest.skipIf((not sympy_available) or (ginac_available), 'sympy is not available') +@unittest.skipUnless(sympy_available, 'sympy is not available') class TestSimplificationSympy(TestCase, SimplificationMixin): pass -@unittest.skipIf(not ginac_available, 'GiNaC is not available') +@unittest.skipUnless(ginac_available, 'GiNaC is not available') class TestSimplificationGiNaC(TestCase, SimplificationMixin): def test_param(self): m = pe.ConcreteModel() From b53bbfc67a5e2cdea65008de2b4651670a7df66a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 5 May 2024 12:00:31 -0600 Subject: [PATCH 1745/1797] Define NamedIntEnum --- pyomo/common/enums.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/pyomo/common/enums.py b/pyomo/common/enums.py index 4d969bf7a9e..121155d4ae8 100644 --- a/pyomo/common/enums.py +++ b/pyomo/common/enums.py @@ -17,6 +17,7 @@ .. autosummary:: ExtendedEnumType + NamedIntEnum Standard Enums: @@ -130,7 +131,21 @@ def __new__(metacls, cls, bases, classdict, **kwds): return super().__new__(metacls, cls, bases, classdict, **kwds) -class ObjectiveSense(enum.IntEnum): +class NamedIntEnum(enum.IntEnum): + """An extended version of :py:class:`enum.IntEnum` that supports + creating members by name as well as value. + + """ + + @classmethod + def _missing_(cls, value): + for member in cls: + if member.name == value: + return member + return None + + +class ObjectiveSense(NamedIntEnum): """Flag indicating if an objective is minimizing (1) or maximizing (-1). While the numeric values are arbitrary, there are parts of Pyomo @@ -150,13 +165,6 @@ class ObjectiveSense(enum.IntEnum): def __str__(self): return self.name - @classmethod - def _missing_(cls, value): - for member in cls: - if member.name == value: - return member - return None - minimize = ObjectiveSense.minimize maximize = ObjectiveSense.maximize From e7540f237b774a7297afed8fa62d6848aae2480a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 5 May 2024 12:02:12 -0600 Subject: [PATCH 1746/1797] Rework Simplifier so we can force the backend mode --- pyomo/contrib/simplification/simplify.py | 66 +++++++++++-------- .../tests/test_simplification.py | 43 +++++------- 2 files changed, 56 insertions(+), 53 deletions(-) diff --git a/pyomo/contrib/simplification/simplify.py b/pyomo/contrib/simplification/simplify.py index 94f0ceaa33f..840f3a1c1da 100644 --- a/pyomo/contrib/simplification/simplify.py +++ b/pyomo/contrib/simplification/simplify.py @@ -9,26 +9,25 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +import logging +import warnings + +from pyomo.common.enums import NamedIntEnum from pyomo.core.expr.sympy_tools import sympy2pyomo_expression, sympyify_expression from pyomo.core.expr.numeric_expr import NumericExpression from pyomo.core.expr.numvalue import value, is_constant -import logging -import warnings from pyomo.contrib.simplification.ginac import ( interface as ginac_interface, interface_available as ginac_available, ) -logger = logging.getLogger(__name__) - def simplify_with_sympy(expr: NumericExpression): if is_constant(expr): return value(expr) - om, se = sympyify_expression(expr) - se = se.simplify() - new_expr = sympy2pyomo_expression(se, om) + object_map, sympy_expr = sympyify_expression(expr) + new_expr = sympy2pyomo_expression(sympy_expr.simplify(), object_map) if is_constant(new_expr): new_expr = value(new_expr) return new_expr @@ -37,29 +36,40 @@ def simplify_with_sympy(expr: NumericExpression): def simplify_with_ginac(expr: NumericExpression, ginac_interface): if is_constant(expr): return value(expr) - gi = ginac_interface - ginac_expr = gi.to_ginac(expr) - ginac_expr = ginac_expr.normal() - new_expr = gi.from_ginac(ginac_expr) - return new_expr + ginac_expr = ginac_interface.to_ginac(expr) + return ginac_interface.from_ginac(ginac_expr.normal()) class Simplifier(object): - def __init__(self, suppress_no_ginac_warnings: bool = False) -> None: - if ginac_available: - self.gi = ginac_interface.GinacInterface(False) - self.suppress_no_ginac_warnings = suppress_no_ginac_warnings + class Mode(NamedIntEnum): + auto = 0 + sympy = 1 + ginac = 2 + + def __init__( + self, suppress_no_ginac_warnings: bool = False, mode: Mode = Mode.auto + ) -> None: + if mode == Simplifier.Mode.auto: + if ginac_available: + mode = Simplifier.Mode.ginac + else: + if not suppress_no_ginac_warnings: + msg = ( + "GiNaC does not seem to be available. Using SymPy. " + + "Note that the GiNaC interface is significantly faster." + ) + logging.getLogger(__name__).warning(msg) + warnings.warn(msg) + mode = Simplifier.Mode.sympy - def simplify(self, expr: NumericExpression): - if ginac_available: - return simplify_with_ginac(expr, self.gi) + if mode == Simplifier.Mode.ginac: + self.gi = ginac_interface.GinacInterface(False) + self.simplify = self._simplify_with_ginac else: - if not self.suppress_no_ginac_warnings: - msg = ( - "GiNaC does not seem to be available. Using SymPy. " - + "Note that the GiNac interface is significantly faster." - ) - logger.warning(msg) - warnings.warn(msg) - self.suppress_no_ginac_warnings = True - return simplify_with_sympy(expr) + self.simplify = self._simplify_with_sympy + + def _simplify_with_ginac(self, expr: NumericExpression): + return simplify_with_ginac(expr, self.gi) + + def _simplify_with_sympy(self, expr: NumericExpression): + return simplify_with_sympy(expr) diff --git a/pyomo/contrib/simplification/tests/test_simplification.py b/pyomo/contrib/simplification/tests/test_simplification.py index 27208d42229..efa9f903adc 100644 --- a/pyomo/contrib/simplification/tests/test_simplification.py +++ b/pyomo/contrib/simplification/tests/test_simplification.py @@ -9,17 +9,15 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from pyomo.common.unittest import TestCase from pyomo.common import unittest +from pyomo.common.fileutils import this_file_dir from pyomo.contrib.simplification import Simplifier from pyomo.contrib.simplification.simplify import ginac_available from pyomo.core.expr.compare import assertExpressionsEqual, compare_expressions -import pyomo.environ as pe from pyomo.core.expr.calculus.diff_with_pyomo import reverse_sd -from pyomo.common.dependencies import attempt_import - +from pyomo.core.expr.sympy_tools import sympy_available -sympy, sympy_available = attempt_import('sympy') +import pyomo.environ as pe class SimplificationMixin: @@ -37,8 +35,7 @@ def test_simplify(self): e = x * pe.log(x) der1 = reverse_sd(e)[x] der2 = reverse_sd(der1)[x] - simp = Simplifier() - der2_simp = simp.simplify(der2) + der2_simp = self.simp.simplify(der2) expected = x**-1.0 assertExpressionsEqual(self, expected, der2_simp) @@ -46,8 +43,7 @@ def test_mul(self): m = pe.ConcreteModel() x = m.x = pe.Var() e = 2 * x - simp = Simplifier() - e2 = simp.simplify(e) + e2 = self.simp.simplify(e) expected = 2.0 * x assertExpressionsEqual(self, expected, e2) @@ -55,16 +51,14 @@ def test_sum(self): m = pe.ConcreteModel() x = m.x = pe.Var() e = 2 + x - simp = Simplifier() - e2 = simp.simplify(e) + e2 = self.simp.simplify(e) self.compare_against_possible_results(e2, [2.0 + x, x + 2.0]) def test_neg(self): m = pe.ConcreteModel() x = m.x = pe.Var() e = -pe.log(x) - simp = Simplifier() - e2 = simp.simplify(e) + e2 = self.simp.simplify(e) self.compare_against_possible_results( e2, [(-1.0) * pe.log(x), pe.log(x) * (-1.0), -pe.log(x)] ) @@ -73,8 +67,7 @@ def test_pow(self): m = pe.ConcreteModel() x = m.x = pe.Var() e = x**2.0 - simp = Simplifier() - e2 = simp.simplify(e) + e2 = self.simp.simplify(e) assertExpressionsEqual(self, e, e2) def test_div(self): @@ -82,9 +75,7 @@ def test_div(self): x = m.x = pe.Var() y = m.y = pe.Var() e = x / y + y / x - x / y - simp = Simplifier() - e2 = simp.simplify(e) - print(e2) + e2 = self.simp.simplify(e) self.compare_against_possible_results( e2, [y / x, y * (1.0 / x), y * x**-1.0, x**-1.0 * y] ) @@ -95,25 +86,27 @@ def test_unary(self): func_list = [pe.log, pe.sin, pe.cos, pe.tan, pe.asin, pe.acos, pe.atan] for func in func_list: e = func(x) - simp = Simplifier() - e2 = simp.simplify(e) + e2 = self.simp.simplify(e) assertExpressionsEqual(self, e, e2) @unittest.skipUnless(sympy_available, 'sympy is not available') -class TestSimplificationSympy(TestCase, SimplificationMixin): - pass +class TestSimplificationSympy(unittest.TestCase, SimplificationMixin): + def setUp(self): + self.simp = Simplifier(mode=Simplifier.Mode.sympy) @unittest.skipUnless(ginac_available, 'GiNaC is not available') -class TestSimplificationGiNaC(TestCase, SimplificationMixin): +class TestSimplificationGiNaC(unittest.TestCase, SimplificationMixin): + def setUp(self): + self.simp = Simplifier(mode=Simplifier.Mode.ginac) + def test_param(self): m = pe.ConcreteModel() x = m.x = pe.Var() p = m.p = pe.Param(mutable=True) e1 = p * x**2 + p * x + p * x**2 - simp = Simplifier() - e2 = simp.simplify(e1) + e2 = self.simp.simplify(e1) self.compare_against_possible_results( e2, [ From 9c220b05e34bfff25e3fae4172b187ed169183c1 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 5 May 2024 12:56:34 -0600 Subject: [PATCH 1747/1797] Improve robustness of tar filter --- pyomo/common/download.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pyomo/common/download.py b/pyomo/common/download.py index 95713e9ef76..8361798817f 100644 --- a/pyomo/common/download.py +++ b/pyomo/common/download.py @@ -408,25 +408,25 @@ def filter_fcn(info): f = info.name if os.path.isabs(f) or '..' in f or f.startswith(('/', os.sep)): logger.error( - "malformed (potentially insecure) filename (%s) " - "found in tar archive. Skipping file." % (f,) - ) - return False - target = os.path.realpath(os.path.join(dest, f)) - if os.path.commonpath([target, dest]) != dest: - logger.error( - "malformed (potentially insecure) filename (%s) " - "found in zip archive. Skipping file." % (f,) + "malformed or potentially insecure filename (%s). " + "Skipping file." % (f,) ) return False target = self._splitpath(f) if len(target) <= dirOffset: if not info.isdir(): logger.warning( - "Skipping file (%s) in zip archive due to dirOffset" % (f,) + "Skipping file (%s) in tar archive due to dirOffset." % (f,) ) return False - info.name = '/'.join(target[dirOffset:]) + info.name = f = '/'.join(target[dirOffset:]) + target = os.path.realpath(os.path.join(dest, f)) + if os.path.commonpath([target, dest]) != dest: + logger.error( + "potentially insecure filename (%s) resolves outside target " + "directory. Skipping file." % (f,) + ) + return False # Strip high bits & group/other write bits info.mode &= 0o755 return True From e597bb5c390432555990394ab8fa474bfee8dbaa Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 5 May 2024 12:57:07 -0600 Subject: [PATCH 1748/1797] Ensure tar file is closed --- pyomo/common/download.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/common/download.py b/pyomo/common/download.py index 8361798817f..2a91553c728 100644 --- a/pyomo/common/download.py +++ b/pyomo/common/download.py @@ -398,8 +398,6 @@ def get_tar_archive(self, url, dirOffset=0): raise RuntimeError( "Target directory (%s) exists, but is not a directory" % (self._fname,) ) - tar_file = tarfile.open(fileobj=io.BytesIO(self.retrieve_url(url))) - dest = os.path.realpath(self._fname) def filter_fcn(info): # this mocks up the `tarfile` filter introduced in Python @@ -431,7 +429,9 @@ def filter_fcn(info): info.mode &= 0o755 return True - tar_file.extractall(dest, filter(filter_fcn, tar_file.getmembers())) + with tarfile.open(fileobj=io.BytesIO(self.retrieve_url(url))) as TAR: + dest = os.path.realpath(self._fname) + TAR.extractall(dest, filter(filter_fcn, TAR.getmembers())) def get_gzipped_binary_file(self, url): if self._fname is None: From 70166ffbeb3d39d5ed59cba6f479d041d3da0eb1 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Sun, 5 May 2024 12:58:19 -0600 Subject: [PATCH 1749/1797] test get_tar_archive() --- pyomo/common/tests/test_download.py | 70 ++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/pyomo/common/tests/test_download.py b/pyomo/common/tests/test_download.py index 87108be1c59..8fee0ba7e31 100644 --- a/pyomo/common/tests/test_download.py +++ b/pyomo/common/tests/test_download.py @@ -9,12 +9,14 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ +import io import os import platform import re import shutil -import tempfile import subprocess +import tarfile +import tempfile import pyomo.common.unittest as unittest import pyomo.common.envvar as envvar @@ -22,6 +24,7 @@ from pyomo.common import DeveloperError from pyomo.common.fileutils import this_file from pyomo.common.download import FileDownloader, distro_available +from pyomo.common.log import LoggingIntercept from pyomo.common.tee import capture_output @@ -242,7 +245,7 @@ def test_get_files_requires_set_destination(self): ): f.get_gzipped_binary_file('bogus') - def test_get_test_binary_file(self): + def test_get_text_binary_file(self): tmpdir = tempfile.mkdtemp() try: f = FileDownloader() @@ -263,3 +266,66 @@ def test_get_test_binary_file(self): self.assertEqual(os.path.getsize(target), len(os.linesep)) finally: shutil.rmtree(tmpdir) + + def test_get_tar_archive(self): + tmpdir = tempfile.mkdtemp() + try: + f = FileDownloader() + + # Mock retrieve_url so network connections are not necessary + buf = io.BytesIO() + with tarfile.open(mode="w:gz", fileobj=buf) as TAR: + info = tarfile.TarInfo('b/lnk') + info.size = 0 + info.type = tarfile.SYMTYPE + info.linkname = envvar.PYOMO_CONFIG_DIR + TAR.addfile(info) + for fname in ('a', 'b/c', 'b/d', '/root', 'b/lnk/test'): + info = tarfile.TarInfo(fname) + info.size = 0 + info.type = tarfile.REGTYPE + info.mode = 0o644 + info.mtime = info.uid = info.gid = 0 + info.uname = info.gname = 'root' + TAR.addfile(info) + f.retrieve_url = lambda url: buf.getvalue() + + with self.assertRaisesRegex( + DeveloperError, + r"(?s)target file name has not been initialized " + r"with set_destination_filename".replace(' ', r'\s+'), + ): + f.get_tar_archive(None, 1) + + _tmp = os.path.join(tmpdir, 'a_file') + with open(_tmp, 'w'): + pass + f.set_destination_filename(_tmp) + with self.assertRaisesRegex( + RuntimeError, + r"Target directory \(.*a_file\) exists, but is not a directory", + ): + f.get_tar_archive(None, 1) + + f.set_destination_filename(tmpdir) + with LoggingIntercept() as LOG: + f.get_tar_archive(None, 1) + + self.assertEqual( + LOG.getvalue().strip(), + """ +Skipping file (a) in tar archive due to dirOffset. +malformed or potentially insecure filename (/root). Skipping file. +potentially insecure filename (lnk/test) resolves outside target directory. Skipping file. +""".strip(), + ) + for f in ('c', 'd'): + fname = os.path.join(tmpdir, f) + self.assertTrue(os.path.exists(fname)) + self.assertTrue(os.path.isfile(fname)) + for f in ('lnk',): + fname = os.path.join(tmpdir, f) + self.assertTrue(os.path.exists(fname)) + self.assertTrue(os.path.islink(fname)) + finally: + shutil.rmtree(tmpdir) From e77be8c59c80fd6028c2bb0d2a456c485501b88b Mon Sep 17 00:00:00 2001 From: David L Woodruff Date: Sun, 5 May 2024 17:36:00 -0700 Subject: [PATCH 1750/1797] Code for infeasibility diagnostics called mis (#3172) * getting started moving mis code into Pyomo contrib * we have a test for mis, but it needs more coverage * now testing some exceptions * slight change to doc * black * fixing _get_constraint test * removing some spelling errors * more spelling errors removed * update typos.toml for mis * I forgot to push the __init__.py file in tests * a little documentation cleanup * moved mis to be part of iis * correct bad import in mis test * I didn't realize it would run every py file in the test directory * trying to get the Windows tests to pass by explicitly releasing the logger file handle * run black on test_mis.py * trying to manage the temp dir using the tempfilemanager as a context * catch the error that kills windows tests * run black again * windows started passing, but linux failing; one quick check to see if logging.info helps: * run black again * On windows we are just going to have to leave a log file from the test * add a test for a feasible model * Update pyomo/contrib/iis/mis.py Co-authored-by: Miranda Mundt <55767766+mrmundt@users.noreply.github.com> * Changes suggested by Miranda * run black again * simplifying the code * take care of Miranda's helpful comments * add sorely needed f to format error messages * added suggestions from R. Parker to the comments --------- Co-authored-by: Bernard Knueven Co-authored-by: Miranda Mundt <55767766+mrmundt@users.noreply.github.com> --- .github/workflows/typos.toml | 3 + doc/OnlineDocs/conf.py | 1 + doc/OnlineDocs/contributed_packages/iis.rst | 129 +++++++ pyomo/contrib/iis/__init__.py | 1 + pyomo/contrib/iis/mis.py | 377 ++++++++++++++++++++ pyomo/contrib/iis/tests/test_mis.py | 125 +++++++ pyomo/contrib/iis/tests/trivial_mis.py | 24 ++ 7 files changed, 660 insertions(+) create mode 100644 pyomo/contrib/iis/mis.py create mode 100644 pyomo/contrib/iis/tests/test_mis.py create mode 100644 pyomo/contrib/iis/tests/trivial_mis.py diff --git a/.github/workflows/typos.toml b/.github/workflows/typos.toml index 4d69cde34e1..7a38164898b 100644 --- a/.github/workflows/typos.toml +++ b/.github/workflows/typos.toml @@ -40,6 +40,9 @@ WRONLY = "WRONLY" Hax = "Hax" # Big Sur Sur = "Sur" +# contrib package named mis and the acronym whence the name comes +mis = "mis" +MIS = "MIS" # Ignore the shorthand ans for answer ans = "ans" # Ignore the keyword arange diff --git a/doc/OnlineDocs/conf.py b/doc/OnlineDocs/conf.py index 1aab4cd76c2..a06ccfbc9bd 100644 --- a/doc/OnlineDocs/conf.py +++ b/doc/OnlineDocs/conf.py @@ -84,6 +84,7 @@ 'sphinx.ext.todo', 'sphinx_copybutton', 'enum_tools.autoenum', + 'sphinx.ext.autosectionlabel', #'sphinx.ext.githubpages', ] diff --git a/doc/OnlineDocs/contributed_packages/iis.rst b/doc/OnlineDocs/contributed_packages/iis.rst index 98cb9e30771..fa97c2f8c61 100644 --- a/doc/OnlineDocs/contributed_packages/iis.rst +++ b/doc/OnlineDocs/contributed_packages/iis.rst @@ -1,6 +1,135 @@ +Infeasibility Diagnostics +!!!!!!!!!!!!!!!!!!!!!!!!! + +There are two closely related tools for infeasibility diagnosis: + + - :ref:`Infeasible Irreducible System (IIS) Tool` + - :ref:`Minimal Intractable System finder (MIS) Tool` + +The first simply provides a conduit for solvers that compute an +infeasible irreducible system (e.g., Cplex, Gurobi, or Xpress). The +second provides similar functionality, but uses the ``mis`` package +contributed to Pyomo. + + Infeasible Irreducible System (IIS) Tool ======================================== .. automodule:: pyomo.contrib.iis.iis .. autofunction:: pyomo.contrib.iis.write_iis + +Minimal Intractable System finder (MIS) Tool +============================================ + +The file ``mis.py`` finds sets of actions that each, independently, +would result in feasibility. The zero-tolerance is whatever the +solver uses, so users may want to post-process output if it is going +to be used for analysis. It also computes a minimal intractable system +(which is not guaranteed to be unique). It was written by Ben Knueven +as part of the watertap project (https://github.com/watertap-org/watertap) +and is therefore governed by a license shown +at the top of ``mis.py``. + +The algorithms come from John Chinneck's slides, see: https://www.sce.carleton.ca/faculty/chinneck/docs/CPAIOR07InfeasibilityTutorial.pdf + +Solver +------ + +At the time of this writing, you need to use IPopt even for LPs. + +Quick Start +----------- + +The file ``trivial_mis.py`` is a tiny example listed at the bottom of +this help file, which references a Pyomo model with the Python variable +`m` and has these lines: + +.. code-block:: python + + from pyomo.contrib.mis import compute_infeasibility_explanation + ipopt = pyo.SolverFactory("ipopt") + compute_infeasibility_explanation(m, solver=ipopt) + +.. Note:: + This is done instead of solving the problem. + +.. Note:: + IDAES users can pass ``get_solver()`` imported from ``ideas.core.solvers`` + as the solver. + +Interpreting the Output +----------------------- + +Assuming the dependencies are installed, running ``trivial_mis.py`` +(shown below) will +produce a lot of warnings from IPopt and then meaningful output (using a logger). + +Repair Options +^^^^^^^^^^^^^^ + +This output for the trivial example shows three independent ways that the model could be rendered feasible: + + +.. code-block:: text + + Model Trivial Quad may be infeasible. A feasible solution was found with only the following variable bounds relaxed: + ub of var x[1] by 4.464126126706818e-05 + lb of var x[2] by 0.9999553410114216 + Another feasible solution was found with only the following variable bounds relaxed: + lb of var x[1] by 0.7071067726864677 + ub of var x[2] by 0.41421355687130673 + ub of var y by 0.7071067651855212 + Another feasible solution was found with only the following inequality constraints, equality constraints, and/or variable bounds relaxed: + constraint: c by 0.9999999861866736 + + +Minimal Intractable System (MIS) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This output shows a minimal intractable system: + + +.. code-block:: text + + Computed Minimal Intractable System (MIS)! + Constraints / bounds in MIS: + lb of var x[2] + lb of var x[1] + constraint: c + +Constraints / bounds in guards for stability +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This part of the report is for nonlinear programs (NLPs). + +When we’re trying to reduce the constraint set, for an NLP there may be constraints that when missing cause the solver +to fail in some catastrophic fashion. In this implementation this is interpreted as failing to get a `results` +object back from the call to `solve`. In these cases we keep the constraint in the problem but it’s in the +set of “guard” constraints – we can’t really be sure they’re a source of infeasibility or not, +just that “bad things” happen when they’re not included. + +Perhaps ideally we would put a constraint in the “guard” set if IPopt failed to converge, and only put it in the +MIS if IPopt converged to a point of local infeasibility. However, right now the code generally makes the +assumption that if IPopt fails to converge the subproblem is infeasible, though obviously that is far from the truth. +Hence for difficult NLPs even the “Phase 1” may “fail” – in that when finished the subproblem containing just the +constraints in the elastic filter may be feasible -- because IPopt failed to converge and we assumed that meant the +subproblem was not feasible. + +Dealing with NLPs is far from clean, but that doesn’t mean the tool can’t return useful results even when its assumptions are not satisfied. + +trivial_mis.py +-------------- + +.. code-block:: python + + import pyomo.environ as pyo + m = pyo.ConcreteModel("Trivial Quad") + m.x = pyo.Var([1,2], bounds=(0,1)) + m.y = pyo.Var(bounds=(0, 1)) + m.c = pyo.Constraint(expr=m.x[1] * m.x[2] == -1) + m.d = pyo.Constraint(expr=m.x[1] + m.y >= 1) + + from pyomo.contrib.mis import compute_infeasibility_explanation + ipopt = pyo.SolverFactory("ipopt") + compute_infeasibility_explanation(m, solver=ipopt) diff --git a/pyomo/contrib/iis/__init__.py b/pyomo/contrib/iis/__init__.py index e8d6a7ac2c3..961ac576d42 100644 --- a/pyomo/contrib/iis/__init__.py +++ b/pyomo/contrib/iis/__init__.py @@ -10,3 +10,4 @@ # ___________________________________________________________________________ from pyomo.contrib.iis.iis import write_iis +from pyomo.contrib.iis.mis import compute_infeasibility_explanation diff --git a/pyomo/contrib/iis/mis.py b/pyomo/contrib/iis/mis.py new file mode 100644 index 00000000000..6b6cca8e29c --- /dev/null +++ b/pyomo/contrib/iis/mis.py @@ -0,0 +1,377 @@ +# ___________________________________________________________________________ +# +# 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. +# ___________________________________________________________________________ +""" +WaterTAP Copyright (c) 2020-2023, The Regents of the University of California, through Lawrence Berkeley National Laboratory, Oak Ridge National Laboratory, National Renewable Energy Laboratory, and National Energy Technology Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + + Neither the name of the University of California, Lawrence Berkeley National Laboratory, Oak Ridge National Laboratory, National Renewable Energy Laboratory, National Energy Technology Laboratory, U.S. Dept. of Energy nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +You are under no obligation whatsoever to provide any bug fixes, patches, or upgrades to the features, functionality or performance of the source code ("Enhancements") to anyone; however, if you choose to make your Enhancements available either publicly, or directly to Lawrence Berkeley National Laboratory, without imposing a separate written license agreement for such Enhancements, then you hereby grant the following license: a non-exclusive, royalty-free perpetual license to install, use, modify, prepare derivative works, incorporate into other computer software, distribute, and sublicense such enhancements or derivative works thereof, in binary and source code form. +""" +""" +Minimal Intractable System (MIS) finder +Originally written by Ben Knueven as part of the WaterTAP project: + https://github.com/watertap-org/watertap +That's why this file has the watertap copyright notice. + +copied by DLW 18Feb2024 and edited + +See: https://www.sce.carleton.ca/faculty/chinneck/docs/CPAIOR07InfeasibilityTutorial.pdf +""" + +import logging +import pyomo.environ as pyo + +from pyomo.core.plugins.transform.add_slack_vars import AddSlackVariables + +from pyomo.core.plugins.transform.hierarchy import IsomorphicTransformation + +from pyomo.common.modeling import unique_component_name +from pyomo.common.collections import ComponentMap, ComponentSet + +from pyomo.opt import WriterFactory + +logger = logging.getLogger("pyomo.contrib.iis") +logger.setLevel(logging.INFO) + + +class _VariableBoundsAsConstraints(IsomorphicTransformation): + """Replace all variables bounds and domain information with constraints. + + Leaves fixed Vars untouched (for now) + """ + + def _apply_to(self, instance, **kwds): + + bound_constr_block_name = unique_component_name(instance, "_variable_bounds") + instance.add_component(bound_constr_block_name, pyo.Block()) + bound_constr_block = instance.component(bound_constr_block_name) + + for v in instance.component_data_objects(pyo.Var, descend_into=True): + if v.fixed: + continue + lb, ub = v.bounds + if lb is None and ub is None: + continue + var_name = v.getname(fully_qualified=True) + if lb is not None: + con_name = "lb_for_" + var_name + con = pyo.Constraint(expr=(lb, v, None)) + bound_constr_block.add_component(con_name, con) + if ub is not None: + con_name = "ub_for_" + var_name + con = pyo.Constraint(expr=(None, v, ub)) + bound_constr_block.add_component(con_name, con) + + # now we deactivate the variable bounds / domain + v.domain = pyo.Reals + v.setlb(None) + v.setub(None) + + +def compute_infeasibility_explanation( + model, solver, tee=False, tolerance=1e-8, logger=logger +): + """ + This function attempts to determine why a given model is infeasible. It deploys + two main algorithms: + + 1. Successfully relaxes the constraints of the problem, and reports to the user + some sets of constraints and variable bounds, which when relaxed, creates a + feasible model. + 2. Uses the information collected from (1) to attempt to compute a Minimal + Infeasible System (MIS), which is a set of constraints and variable bounds + which appear to be in conflict with each other. It is minimal in the sense + that removing any single constraint or variable bound would result in a + feasible subsystem. + + Args + ---- + model: A pyomo block + solver: A pyomo solver object or a string for SolverFactory + tee (optional): Display intermediate solves conducted (False) + tolerance (optional): The feasibility tolerance to use when declaring a + constraint feasible (1e-08) + logger:logging.Logger + A logger for messages. Uses pyomo.contrib.mis logger by default. + + """ + # Suggested enhancement: It might be useful to return sets of names for each set of relaxed components, as well as the final minimal infeasible system + + # hold the original harmless + modified_model = model.clone() + + if solver is None: + raise ValueError("A solver must be supplied") + elif isinstance(solver, str): + solver = pyo.SolverFactory(solver) + else: + # assume we have a solver + assert solver.available() + + # first, cache the values we get + _value_cache = ComponentMap() + for v in model.component_data_objects(pyo.Var, descend_into=True): + _value_cache[v] = v.value + + # finding proper reference + if model.parent_block() is None: + common_name = "" + else: + common_name = model.name + "." + + _modified_model_var_to_original_model_var = ComponentMap() + _modified_model_value_cache = ComponentMap() + + for v in model.component_data_objects(pyo.Var, descend_into=True): + modified_model_var = modified_model.find_component(v.name[len(common_name) :]) + + _modified_model_var_to_original_model_var[modified_model_var] = v + _modified_model_value_cache[modified_model_var] = _value_cache[v] + modified_model_var.set_value(_value_cache[v], skip_validation=True) + + # TODO: For WT / IDAES models, we should probably be more + # selective in *what* we elasticize. E.g., it probably + # does not make sense to elasticize property calculations + # and maybe certain other equality constraints calculating + # values. Maybe we shouldn't elasticize *any* equality + # constraints. + # For example, elasticizing the calculation of mass fraction + # makes absolutely no sense and will just be noise for the + # modeler to sift through. We could try to sort the constraints + # such that we look for those with linear coefficients `1` on + # some term and leave those be. + # Alternatively, we could apply this tool to a version of the + # model that has as many as possible of these constraints + # "substituted out". + # move the variable bounds to the constraints + _VariableBoundsAsConstraints().apply_to(modified_model) + + AddSlackVariables().apply_to(modified_model) + slack_block = modified_model._core_add_slack_variables + + for v in slack_block.component_data_objects(pyo.Var): + v.fix(0) + # start with variable bounds -- these are the easiest to interpret + for c in modified_model._variable_bounds.component_data_objects( + pyo.Constraint, descend_into=True + ): + plus = slack_block.component(f"_slack_plus_{c.name}") + minus = slack_block.component(f"_slack_minus_{c.name}") + assert not (plus is None and minus is None) + if plus is not None: + plus.unfix() + if minus is not None: + minus.unfix() + + # TODO: Elasticizing too much at once seems to cause Ipopt trouble. + # After an initial sweep, we should just fix one elastic variable + # and put everything else on a stack of "constraints to elasticize". + # We elasticize one constraint at a time and fix one constraint at a time. + # After fixing an elastic variable, we elasticize a single constraint it + # appears in and put the remaining constraints on the stack. If the resulting problem + # is feasible, we keep going "down the tree". If the resulting problem is + # infeasible or cannot be solved, we elasticize a single constraint from + # the top of the stack. + # The algorithm stops when the stack is empty and the subproblem is infeasible. + # Along the way, any time the current problem is infeasible we can check to + # see if the current set of constraints in the filter is as a collection of + # infeasible constraints -- to terminate early. + # However, while more stable, this is much more computationally intensive. + # So, we leave the implementation simpler for now and consider this as + # a potential extension if this tool sometimes cannot report a good answer. + # Phase 1 -- build the initial set of constraints, or prove feasibility + msg = "" + fixed_slacks = ComponentSet() + elastic_filter = ComponentSet() + + def _constraint_loop(relaxed_things, msg): + if msg == "": + msg += f"Model {model.name} may be infeasible. A feasible solution was found with only the following {relaxed_things} relaxed:\n" + else: + msg += f"Another feasible solution was found with only the following {relaxed_things} relaxed:\n" + while True: + + def _constraint_generator(): + elastic_filter_size_initial = len(elastic_filter) + for v in slack_block.component_data_objects(pyo.Var): + if v.value > tolerance: + constr = _get_constraint(modified_model, v) + yield constr, v.value + v.fix(0) + fixed_slacks.add(v) + elastic_filter.add(constr) + if len(elastic_filter) == elastic_filter_size_initial: + raise Exception(f"Found model {model.name} to be feasible!") + + msg = _get_results_with_value(_constraint_generator(), msg) + for var, val in _modified_model_value_cache.items(): + var.set_value(val, skip_validation=True) + results = solver.solve(modified_model, tee=tee) + if pyo.check_optimal_termination(results): + msg += f"Another feasible solution was found with only the following {relaxed_things} relaxed:\n" + else: + break + return msg + + results = solver.solve(modified_model, tee=tee) + if pyo.check_optimal_termination(results): + msg = _constraint_loop("variable bounds", msg) + + # next, try relaxing the inequality constraints + for v in slack_block.component_data_objects(pyo.Var): + c = _get_constraint(modified_model, v) + if c.equality: + # equality constraint + continue + if v not in fixed_slacks: + v.unfix() + + results = solver.solve(modified_model, tee=tee) + if pyo.check_optimal_termination(results): + msg = _constraint_loop("inequality constraints and/or variable bounds", msg) + + for v in slack_block.component_data_objects(pyo.Var): + if v not in fixed_slacks: + v.unfix() + + results = solver.solve(modified_model, tee=tee) + if pyo.check_optimal_termination(results): + msg = _constraint_loop( + "inequality constraints, equality constraints, and/or variable bounds", msg + ) + + if len(elastic_filter) == 0: + # load the feasible solution into the original model + for modified_model_var, v in _modified_model_var_to_original_model_var.items(): + v.set_value(modified_model_var.value, skip_validation=True) + results = solver.solve(model, tee=tee) + if pyo.check_optimal_termination(results): + logger.info(f"A feasible solution was found!") + else: + logger.info( + f"Could not find a feasible solution with violated constraints or bounds. This model is likely unstable" + ) + + # Phase 2 -- deletion filter + # remove slacks by fixing them to 0 + for v in slack_block.component_data_objects(pyo.Var): + v.fix(0) + for o in modified_model.component_data_objects(pyo.Objective, descend_into=True): + o.deactivate() + + # mark all constraints not in the filter as inactive + for c in modified_model.component_data_objects(pyo.Constraint): + if c in elastic_filter: + continue + else: + c.deactivate() + + try: + results = solver.solve(modified_model, tee=tee) + except: + results = None + + if pyo.check_optimal_termination(results): + msg += "Could not determine Minimal Intractable System\n" + else: + deletion_filter = [] + guards = [] + for constr in elastic_filter: + constr.deactivate() + for var, val in _modified_model_value_cache.items(): + var.set_value(val, skip_validation=True) + math_failure = False + try: + results = solver.solve(modified_model, tee=tee) + except: + math_failure = True + + if math_failure: + constr.activate() + guards.append(constr) + elif pyo.check_optimal_termination(results): + constr.activate() + deletion_filter.append(constr) + else: # still infeasible without this constraint + pass + + msg += "Computed Minimal Intractable System (MIS)!\n" + msg += "Constraints / bounds in MIS:\n" + msg = _get_results(deletion_filter, msg) + msg += "Constraints / bounds in guards for stability:" + msg = _get_results(guards, msg) + + logger.info(msg) + + +def _get_results_with_value(constr_value_generator, msg=None): + # note that "lb_for_" and "ub_for_" are 7 characters long + if msg is None: + msg = "" + for c, value in constr_value_generator: + c_name = c.name + if "_variable_bounds" in c_name: + name = c.local_name + if "lb" in name: + msg += f"\tlb of var {name[7:]} by {value}\n" + elif "ub" in name: + msg += f"\tub of var {name[7:]} by {value}\n" + else: + raise RuntimeError("unrecognized var name") + else: + msg += f"\tconstraint: {c_name} by {value}\n" + return msg + + +def _get_results(constr_generator, msg=None): + # note that "lb_for_" and "ub_for_" are 7 characters long + if msg is None: + msg = "" + for c in constr_generator: + c_name = c.name + if "_variable_bounds" in c_name: + name = c.local_name + if "lb" in name: + msg += f"\tlb of var {name[7:]}\n" + elif "ub" in name: + msg += f"\tub of var {name[7:]}\n" + else: + raise RuntimeError("unrecognized var name") + else: + msg += f"\tconstraint: {c_name}\n" + return msg + + +def _get_constraint(modified_model, v): + if "_slack_plus_" in v.name: + constr = modified_model.find_component(v.local_name[len("_slack_plus_") :]) + if constr is None: + raise RuntimeError( + f"Bad constraint name {v.local_name[len('_slack_plus_'):]}" + ) + return constr + elif "_slack_minus_" in v.name: + constr = modified_model.find_component(v.local_name[len("_slack_minus_") :]) + if constr is None: + raise RuntimeError( + f"Bad constraint name {v.local_name[len('_slack_minus_'):]}" + ) + return constr + else: + raise RuntimeError(f"Bad var name {v.name}") diff --git a/pyomo/contrib/iis/tests/test_mis.py b/pyomo/contrib/iis/tests/test_mis.py new file mode 100644 index 00000000000..bbdb2367016 --- /dev/null +++ b/pyomo/contrib/iis/tests/test_mis.py @@ -0,0 +1,125 @@ +# ___________________________________________________________________________ +# +# 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.unittest as unittest +import pyomo.environ as pyo +import pyomo.contrib.iis.mis as mis +from pyomo.contrib.iis.mis import _get_constraint +from pyomo.common.tempfiles import TempfileManager + +import logging +import os + + +def _get_infeasible_model(): + m = pyo.ConcreteModel("trivial4test") + m.x = pyo.Var(within=pyo.Binary) + m.y = pyo.Var(within=pyo.NonNegativeReals) + + m.c1 = pyo.Constraint(expr=m.y <= 100.0 * m.x) + m.c2 = pyo.Constraint(expr=m.y <= -100.0 * m.x) + m.c3 = pyo.Constraint(expr=m.x >= 0.5) + + m.o = pyo.Objective(expr=-m.y) + + return m + + +def _get_feasible_model(): + m = pyo.ConcreteModel("Trivial Feasible Quad") + m.x = pyo.Var([1, 2], bounds=(0, 1)) + m.y = pyo.Var(bounds=(0, 1)) + m.c = pyo.Constraint(expr=m.x[1] * m.x[2] >= -1) + m.d = pyo.Constraint(expr=m.x[1] + m.y >= 1) + + return m + + +class TestMIS(unittest.TestCase): + @unittest.skipUnless( + pyo.SolverFactory("ipopt").available(exception_flag=False), + "ipopt not available", + ) + def test_write_mis_ipopt(self): + _test_mis("ipopt") + + def test__get_constraint_errors(self): + # A not-completely-cynical way to get the coverage up. + m = _get_infeasible_model() # not modified + fct = _get_constraint + + m.foo_slack_plus_ = pyo.Var() + self.assertRaises(RuntimeError, fct, m, m.foo_slack_plus_) + m.foo_slack_minus_ = pyo.Var() + self.assertRaises(RuntimeError, fct, m, m.foo_slack_minus_) + m.foo_bar = pyo.Var() + self.assertRaises(RuntimeError, fct, m, m.foo_bar) + + def test_feasible_model(self): + m = _get_feasible_model() + opt = pyo.SolverFactory("ipopt") + self.assertRaises(Exception, mis.compute_infeasibility_explanation, m, opt) + + +def _check_output(file_name): + # pretty simple check for now + with open(file_name, "r+") as file1: + lines = file1.readlines() + trigger = "Constraints / bounds in MIS:" + nugget = "lb of var y" + live = False # (long i) + found_nugget = False + for line in lines: + if trigger in line: + live = True + if live: + if nugget in line: + found_nugget = True + if not found_nugget: + raise RuntimeError(f"Did not find '{nugget}' after '{trigger}' in output") + else: + pass + + +def _test_mis(solver_name): + m = _get_infeasible_model() + opt = pyo.SolverFactory(solver_name) + + # This test seems to fail on Windows as it unlinks the tempfile, so live with it + # On a Windows machine, we will not use a temp dir and just try to delete the log file + if os.name == "nt": + file_name = f"_test_mis_{solver_name}.log" + logger = logging.getLogger(f"test_mis_{solver_name}") + logger.setLevel(logging.INFO) + fh = logging.FileHandler(file_name) + fh.setLevel(logging.DEBUG) + logger.addHandler(fh) + + mis.compute_infeasibility_explanation(m, opt, logger=logger) + _check_output(file_name) + # os.remove(file_name) cannot remove it on Windows. Still in use. + + else: # not windows + with TempfileManager.new_context() as tmpmgr: + tmp_path = tmpmgr.mkdtemp() + file_name = os.path.join(tmp_path, f"_test_mis_{solver_name}.log") + logger = logging.getLogger(f"test_mis_{solver_name}") + logger.setLevel(logging.INFO) + fh = logging.FileHandler(file_name) + fh.setLevel(logging.DEBUG) + logger.addHandler(fh) + + mis.compute_infeasibility_explanation(m, opt, logger=logger) + _check_output(file_name) + + +if __name__ == "__main__": + unittest.main() diff --git a/pyomo/contrib/iis/tests/trivial_mis.py b/pyomo/contrib/iis/tests/trivial_mis.py new file mode 100644 index 00000000000..4cf0dd7a357 --- /dev/null +++ b/pyomo/contrib/iis/tests/trivial_mis.py @@ -0,0 +1,24 @@ +# ___________________________________________________________________________ +# +# 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.environ as pyo + +m = pyo.ConcreteModel("Trivial Quad") +m.x = pyo.Var([1, 2], bounds=(0, 1)) +m.y = pyo.Var(bounds=(0, 1)) +m.c = pyo.Constraint(expr=m.x[1] * m.x[2] == -1) +m.d = pyo.Constraint(expr=m.x[1] + m.y >= 1) + +from pyomo.contrib.iis.mis import compute_infeasibility_explanation + +# Note: this particular little problem is quadratic +# As of 18Feb2024 DLW is not sure the explanation code works with solvers other than ipopt +ipopt = pyo.SolverFactory("ipopt") +compute_infeasibility_explanation(m, solver=ipopt) From de1c782ee1ffac58c31ea2f164c40d66abe93b6c Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Mon, 6 May 2024 13:52:35 -0600 Subject: [PATCH 1751/1797] Cleaning up a few docstrings in parmest --- pyomo/contrib/parmest/parmest.py | 34 +++++++++++++++++++------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index 41e1724f94f..70f9de8b84c 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -423,7 +423,8 @@ def _create_parmest_model(self, experiment_number): for obj in model.component_objects(pyo.Objective): obj.deactivate() - # TODO, this needs to be turned a enum class of options that still support custom functions + # TODO, this needs to be turned into an enum class of options that still support + # custom functions if self.obj_function == 'SSE': second_stage_rule = SSE else: @@ -635,7 +636,7 @@ def _Q_at_theta(self, thetavals, initialize_parmest_model=False): initialize_parmest_model: boolean If True: Solve square problem instance, build extensive form of the model for - parameter estimation, and set flag model_initialized to True + parameter estimation, and set flag model_initialized to True. Default is False. Returns ------- @@ -866,10 +867,11 @@ def theta_est( return_values: list, optional List of Variable names, used to return values from the model for data reconciliation calc_cov: boolean, optional - If True, calculate and return the covariance matrix (only for "ef_ipopt" solver) + If True, calculate and return the covariance matrix (only for "ef_ipopt" solver). + Default is False. cov_n: int, optional If calc_cov=True, then the user needs to supply the number of datapoints - that are used in the objective function + that are used in the objective function. Returns ------- @@ -902,9 +904,10 @@ def theta_est( for experiment in self.exp_list ] ) - assert isinstance( - cov_n, int - ), "The number of datapoints that are used in the objective function is required to calculate the covariance matrix" + assert isinstance(cov_n, int), ( + "The number of datapoints that are used in the objective function is " + "required to calculate the covariance matrix" + ) assert ( cov_n > num_unknowns ), "The number of datapoints must be greater than the number of parameters to estimate" @@ -936,11 +939,12 @@ def theta_est_bootstrap( Size of each bootstrap sample. If samplesize=None, samplesize will be set to the number of samples in the data replacement: bool, optional - Sample with or without replacement + Sample with or without replacement. Default is True. seed: int or None, optional Random seed return_samples: bool, optional - Return a list of sample numbers used in each bootstrap estimation + Return a list of sample numbers used in each bootstrap estimation. + Default is False. Returns ------- @@ -1006,7 +1010,7 @@ def theta_est_leaveNout( seed: int or None, optional Random seed return_samples: bool, optional - Return a list of sample numbers that were left out + Return a list of sample numbers that were left out. Default is False. Returns ------- @@ -1080,7 +1084,7 @@ def leaveNout_bootstrap_test( Random seed Returns - ---------- + ------- List of tuples with one entry per lNo_sample: * The first item in each tuple is the list of N samples that are left @@ -1141,8 +1145,9 @@ def objective_at_theta(self, theta_values=None, initialize_parmest_model=False): Values of theta used to compute the objective initialize_parmest_model: boolean - If True: Solve square problem instance, build extensive form of the model for - parameter estimation, and set flag model_initialized to True + If True: Solve square problem instance, build extensive form + of the model for parameter estimation, and set flag + model_initialized to True. Default is False. Returns @@ -1243,7 +1248,7 @@ def likelihood_ratio_test( alphas: list List of alpha values to use in the chi2 test return_thresholds: bool, optional - Return the threshold value for each alpha + Return the threshold value for each alpha. Default is False. Returns ------- @@ -1305,6 +1310,7 @@ def confidence_region_test( to determine if they are inside or outside. Returns + ------- training_results: pd.DataFrame Theta value used to generate the confidence region along with True (inside) or False (outside) for each alpha From 7f27d2f69b0be7027a2907c2de766ede4fab302d Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Mon, 6 May 2024 14:26:55 -0600 Subject: [PATCH 1752/1797] Cleaning up a few assert statements in test_parmest.py --- pyomo/contrib/parmest/tests/test_parmest.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/parmest/tests/test_parmest.py b/pyomo/contrib/parmest/tests/test_parmest.py index 69155dadb45..65e2e4a3b06 100644 --- a/pyomo/contrib/parmest/tests/test_parmest.py +++ b/pyomo/contrib/parmest/tests/test_parmest.py @@ -59,7 +59,8 @@ def setUp(self): RooneyBieglerExperiment, ) - # Note, the data used in this test has been corrected to use data.loc[5,'hour'] = 7 (instead of 6) + # 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"], @@ -109,7 +110,7 @@ def test_bootstrap(self): theta_est = self.pest.theta_est_bootstrap(num_bootstraps, return_samples=True) num_samples = theta_est["samples"].apply(len) - self.assertTrue(len(theta_est.index), 10) + self.assertEqual(len(theta_est.index), 10) self.assertTrue(num_samples.equals(pd.Series([6] * 10))) del theta_est["samples"] @@ -155,13 +156,13 @@ def test_leaveNout(self): results = self.pest.leaveNout_bootstrap_test( 1, None, 3, "Rect", [0.5, 1.0], seed=5436 ) - self.assertTrue(len(results) == 6) # 6 lNo samples + self.assertEqual(len(results), 6) # 6 lNo samples i = 1 samples = results[i][0] # list of N samples that are left out lno_theta = results[i][1] bootstrap_theta = results[i][2] self.assertTrue(samples == [1]) # sample 1 was left out - self.assertTrue(lno_theta.shape[0] == 1) # lno estimate for sample 1 + self.assertEqual(lno_theta.shape[0], 1) # lno estimate for sample 1 self.assertTrue(set(lno_theta.columns) >= set([0.5, 1.0])) self.assertEqual(lno_theta[1.0].sum(), 1) # all true self.assertEqual(bootstrap_theta.shape[0], 3) # bootstrap for sample 1 @@ -205,7 +206,7 @@ def test_parallel_parmest(self): retcode = ret.returncode else: retcode = subprocess.call(rlist) - assert retcode == 0 + self.assertEqual(retcode, 0) @unittest.skip("Most folks don't have k_aug installed") def test_theta_k_aug_for_Hessian(self): From 5439e9560b1d4b8602350d8de3c9006e1c5fd615 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Mon, 6 May 2024 15:34:15 -0600 Subject: [PATCH 1753/1797] Fix typo in doe.py --- pyomo/contrib/doe/doe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/doe/doe.py b/pyomo/contrib/doe/doe.py index ab9a5ad9f85..0fc3e8770fe 100644 --- a/pyomo/contrib/doe/doe.py +++ b/pyomo/contrib/doe/doe.py @@ -1083,7 +1083,7 @@ def _add_objective(self, m): # Calculate the eigenvalues of the FIM matrix eig = np.linalg.eigvals(fim) - # If the smallest eigenvalue is (pratcially) negative, add a diagonal matrix to make it positive definite + # If the smallest eigenvalue is (practically) negative, add a diagonal matrix to make it positive definite small_number = 1e-10 if min(eig) < small_number: fim = fim + np.eye(len(self.param)) * (small_number - min(eig)) From 3165c9d67b2b47a8d7ff9e601781ed4e2eecc8b2 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Mon, 6 May 2024 16:14:10 -0600 Subject: [PATCH 1754/1797] Minor edit to APPSI Highs version method to support older versions of Highs --- pyomo/contrib/appsi/solvers/highs.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index 3612b9d5014..29c3698b277 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -176,11 +176,23 @@ def available(self): return self.Availability.NotFound def version(self): - version = ( - highspy.HIGHS_VERSION_MAJOR, - highspy.HIGHS_VERSION_MINOR, - highspy.HIGHS_VERSION_PATCH, - ) + try: + version = ( + highspy.HIGHS_VERSION_MAJOR, + highspy.HIGHS_VERSION_MINOR, + highspy.HIGHS_VERSION_PATCH, + ) + except AttributeError: + # Older versions of Highs do not have the above attributes + # and the solver version can only be obtained by making + # an instance of the solver class. + tmp = highspy.Highs() + version = ( + tmp.versionMajor(), + tmp.versionMinor(), + tmp.versionPatch(), + ) + return version @property From b425c4db8e4b6edacf7352b5a0e85de0c69b1558 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Mon, 6 May 2024 16:21:03 -0600 Subject: [PATCH 1755/1797] Fixing black formatting in highs.py --- pyomo/contrib/appsi/solvers/highs.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py index 75975a4e0a8..87b9557269f 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -187,11 +187,7 @@ def version(self): # and the solver version can only be obtained by making # an instance of the solver class. tmp = highspy.Highs() - version = ( - tmp.versionMajor(), - tmp.versionMinor(), - tmp.versionPatch(), - ) + version = (tmp.versionMajor(), tmp.versionMinor(), tmp.versionPatch()) return version From c610a20cf594be2dcf34e847aaf63dfb08bfde2b Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Mon, 6 May 2024 16:22:49 -0600 Subject: [PATCH 1756/1797] Removing whitespace in highs.py --- 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 87b9557269f..c948444839d 100644 --- a/pyomo/contrib/appsi/solvers/highs.py +++ b/pyomo/contrib/appsi/solvers/highs.py @@ -176,7 +176,7 @@ def available(self): return self.Availability.NotFound def version(self): - try: + try: version = ( highspy.HIGHS_VERSION_MAJOR, highspy.HIGHS_VERSION_MINOR, From fc58199106b62604946d9c20dde95f2b0362e70f Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 7 May 2024 06:54:32 -0600 Subject: [PATCH 1757/1797] Clarify comment --- pyomo/contrib/solver/gurobi_direct.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/solver/gurobi_direct.py b/pyomo/contrib/solver/gurobi_direct.py index 7b80651ccae..edca7018f92 100644 --- a/pyomo/contrib/solver/gurobi_direct.py +++ b/pyomo/contrib/solver/gurobi_direct.py @@ -283,7 +283,8 @@ def solve(self, model, **kwds) -> Results: if repn.c.shape[0]: gurobi_model.setAttr('ObjCon', repn.c_offset[0]) gurobi_model.setAttr('ModelSense', int(repn.objectives[0].sense)) - # gurobi_model.update() + # Note: calling gurobi_model.update() here is not + # necessary (it will happen as part of optimize()) timer.stop('transfer_model') options = config.solver_options From 4446d364877ccbaa5faa33f9a100efecbd2975c9 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 7 May 2024 10:23:43 -0600 Subject: [PATCH 1758/1797] fix tests --- pyomo/environ/tests/test_package_layout.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/environ/tests/test_package_layout.py b/pyomo/environ/tests/test_package_layout.py index 4e1574ab158..47c6422a879 100644 --- a/pyomo/environ/tests/test_package_layout.py +++ b/pyomo/environ/tests/test_package_layout.py @@ -38,6 +38,7 @@ _NON_MODULE_DIRS = { join('contrib', 'ampl_function_demo', 'src'), join('contrib', 'appsi', 'cmodel', 'src'), + join('contrib', 'simplification', 'ginac', 'src'), join('contrib', 'pynumero', 'src'), join('core', 'tests', 'data', 'baselines'), join('core', 'tests', 'diet', 'baselines'), From 90b1783197210cdca5dfe3c0b45bb467e6d58148 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 7 May 2024 13:09:01 -0600 Subject: [PATCH 1759/1797] Update tar filter to handle ValueError from commonpath() --- pyomo/common/download.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pyomo/common/download.py b/pyomo/common/download.py index 2a91553c728..ad672e8c79b 100644 --- a/pyomo/common/download.py +++ b/pyomo/common/download.py @@ -419,11 +419,17 @@ def filter_fcn(info): return False info.name = f = '/'.join(target[dirOffset:]) target = os.path.realpath(os.path.join(dest, f)) - if os.path.commonpath([target, dest]) != dest: - logger.error( - "potentially insecure filename (%s) resolves outside target " - "directory. Skipping file." % (f,) - ) + try: + if os.path.commonpath([target, dest]) != dest: + logger.error( + "potentially insecure filename (%s) resolves outside target " + "directory. Skipping file." % (f,) + ) + return False + except ValueError: + # commonpath() will raise ValueError for paths that + # don't have anything in common (notably, when files are + # on different drives on Windows) return False # Strip high bits & group/other write bits info.mode &= 0o755 From 5aae45946ebd302cfe0e33d54c5927d2f193a362 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 7 May 2024 13:20:22 -0600 Subject: [PATCH 1760/1797] keep mutable parameters in sympy conversion --- pyomo/core/expr/sympy_tools.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pyomo/core/expr/sympy_tools.py b/pyomo/core/expr/sympy_tools.py index 48bd542be0f..05c9885cc8c 100644 --- a/pyomo/core/expr/sympy_tools.py +++ b/pyomo/core/expr/sympy_tools.py @@ -175,10 +175,11 @@ def sympyVars(self): class Pyomo2SympyVisitor(EXPR.StreamBasedExpressionVisitor): - def __init__(self, object_map): + def __init__(self, object_map, keep_mutable_parameters=True): sympy.Add # this ensures _configure_sympy gets run super(Pyomo2SympyVisitor, self).__init__() self.object_map = object_map + self.keep_mutable_parameters = keep_mutable_parameters def initializeWalker(self, expr): return self.beforeChild(None, expr, None) @@ -212,6 +213,8 @@ def beforeChild(self, node, child, child_idx): # # Everything else is a constant... # + if self.keep_mutable_parameters and child.is_parameter_type() and child.mutable: + return False, self.object_map.getSympySymbol(child) return False, value(child) @@ -245,13 +248,15 @@ def beforeChild(self, node, child, child_idx): return True, None -def sympyify_expression(expr): +def sympyify_expression(expr, keep_mutable_parameters=True): """Convert a Pyomo expression to a Sympy expression""" # # Create the visitor and call it. # object_map = PyomoSympyBimap() - visitor = Pyomo2SympyVisitor(object_map) + visitor = Pyomo2SympyVisitor( + object_map, keep_mutable_parameters=keep_mutable_parameters + ) return object_map, visitor.walk_expression(expr) From ae5ebd3ed492405ae488921ef3c6b36c886f098a Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 7 May 2024 13:26:35 -0600 Subject: [PATCH 1761/1797] update defaults for mutable parameters when using sympy --- pyomo/contrib/simplification/simplify.py | 2 +- pyomo/core/expr/sympy_tools.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/simplification/simplify.py b/pyomo/contrib/simplification/simplify.py index 840f3a1c1da..874b5b1e801 100644 --- a/pyomo/contrib/simplification/simplify.py +++ b/pyomo/contrib/simplification/simplify.py @@ -26,7 +26,7 @@ def simplify_with_sympy(expr: NumericExpression): if is_constant(expr): return value(expr) - object_map, sympy_expr = sympyify_expression(expr) + object_map, sympy_expr = sympyify_expression(expr, keep_mutable_parameters=True) new_expr = sympy2pyomo_expression(sympy_expr.simplify(), object_map) if is_constant(new_expr): new_expr = value(new_expr) diff --git a/pyomo/core/expr/sympy_tools.py b/pyomo/core/expr/sympy_tools.py index 05c9885cc8c..6c184f0e4c4 100644 --- a/pyomo/core/expr/sympy_tools.py +++ b/pyomo/core/expr/sympy_tools.py @@ -175,7 +175,7 @@ def sympyVars(self): class Pyomo2SympyVisitor(EXPR.StreamBasedExpressionVisitor): - def __init__(self, object_map, keep_mutable_parameters=True): + def __init__(self, object_map, keep_mutable_parameters=False): sympy.Add # this ensures _configure_sympy gets run super(Pyomo2SympyVisitor, self).__init__() self.object_map = object_map @@ -248,7 +248,7 @@ def beforeChild(self, node, child, child_idx): return True, None -def sympyify_expression(expr, keep_mutable_parameters=True): +def sympyify_expression(expr, keep_mutable_parameters=False): """Convert a Pyomo expression to a Sympy expression""" # # Create the visitor and call it. From e4920cbd229fc9712bf03e972c7788e9e0cb1eb6 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 7 May 2024 15:46:50 -0400 Subject: [PATCH 1762/1797] add test for call_before_subproblem_solve --- pyomo/contrib/mindtpy/tests/test_mindtpy.py | 24 +++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy.py b/pyomo/contrib/mindtpy/tests/test_mindtpy.py index 37969276d55..f54e766baa4 100644 --- a/pyomo/contrib/mindtpy/tests/test_mindtpy.py +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy.py @@ -101,6 +101,30 @@ def test_OA_rNLP(self): ) self.check_optimal_solution(model) + def test_OA_callback(self): + """Test the outer approximation decomposition algorithm.""" + with SolverFactory('mindtpy') as opt: + + def callback(model): + model.Y[1].value = 0 + model.Y[2].value = 0 + model.Y[3].value = 0 + + model = SimpleMINLP2() + # The callback function will make the OA method cycling. + results = opt.solve( + model, + strategy='OA', + init_strategy='rNLP', + mip_solver=required_solvers[1], + nlp_solver=required_solvers[0], + call_before_subproblem_solve=callback, + ) + self.assertIs( + results.solver.termination_condition, TerminationCondition.feasible + ) + self.assertAlmostEqual(value(results.problem.lower_bound), 5, places=1) + def test_OA_extreme_model(self): """Test the outer approximation decomposition algorithm.""" with SolverFactory('mindtpy') as opt: From 2b3bd4eea0d00e4e7e2dce3483aceaa04dcfb767 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Tue, 7 May 2024 14:38:14 -0600 Subject: [PATCH 1763/1797] update tests --- .../tests/test_simplification.py | 25 ++++++++++--------- setup.py | 1 + 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/pyomo/contrib/simplification/tests/test_simplification.py b/pyomo/contrib/simplification/tests/test_simplification.py index efa9f903adc..acef0af502e 100644 --- a/pyomo/contrib/simplification/tests/test_simplification.py +++ b/pyomo/contrib/simplification/tests/test_simplification.py @@ -89,18 +89,6 @@ def test_unary(self): e2 = self.simp.simplify(e) assertExpressionsEqual(self, e, e2) - -@unittest.skipUnless(sympy_available, 'sympy is not available') -class TestSimplificationSympy(unittest.TestCase, SimplificationMixin): - def setUp(self): - self.simp = Simplifier(mode=Simplifier.Mode.sympy) - - -@unittest.skipUnless(ginac_available, 'GiNaC is not available') -class TestSimplificationGiNaC(unittest.TestCase, SimplificationMixin): - def setUp(self): - self.simp = Simplifier(mode=Simplifier.Mode.ginac) - def test_param(self): m = pe.ConcreteModel() x = m.x = pe.Var() @@ -116,5 +104,18 @@ def test_param(self): p * x + 2.0 * p * x**2.0, x**2.0 * p * 2.0 + p * x, p * x + x**2.0 * p * 2.0, + p * x * (1 + 2 * x), ], ) + + +@unittest.skipUnless(sympy_available, 'sympy is not available') +class TestSimplificationSympy(unittest.TestCase, SimplificationMixin): + def setUp(self): + self.simp = Simplifier(mode=Simplifier.Mode.sympy) + + +@unittest.skipUnless(ginac_available, 'GiNaC is not available') +class TestSimplificationGiNaC(unittest.TestCase, SimplificationMixin): + def setUp(self): + self.simp = Simplifier(mode=Simplifier.Mode.ginac) diff --git a/setup.py b/setup.py index 70c1626a650..a125b02b2fe 100644 --- a/setup.py +++ b/setup.py @@ -306,6 +306,7 @@ def __ne__(self, other): "pyomo.contrib.mcpp": ["*.cpp"], "pyomo.contrib.pynumero": ['src/*', 'src/tests/*'], "pyomo.contrib.viewer": ["*.ui"], + "pyomo.contrib.simplification.ginac": ["src/*.cpp", "src/*.hpp"], }, ext_modules=ext_modules, entry_points=""" From 7c0741a1cebff1e5b17228543e55f77c6f1413de Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 8 May 2024 07:38:05 -0600 Subject: [PATCH 1764/1797] Make _SequenceVarData public to match recent change in pyomo/main --- pyomo/contrib/cp/repn/docplex_writer.py | 4 ++-- pyomo/contrib/cp/sequence_var.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/cp/repn/docplex_writer.py b/pyomo/contrib/cp/repn/docplex_writer.py index 53d14495c4b..6a0eb7749a8 100644 --- a/pyomo/contrib/cp/repn/docplex_writer.py +++ b/pyomo/contrib/cp/repn/docplex_writer.py @@ -33,7 +33,7 @@ from pyomo.contrib.cp.sequence_var import ( SequenceVar, ScalarSequenceVar, - _SequenceVarData, + SequenceVarData, ) from pyomo.contrib.cp.scheduling_expr.scheduling_logic import ( AlternativeExpression, @@ -1055,7 +1055,7 @@ class LogicalToDoCplex(StreamBasedExpressionVisitor): IntervalVarData: _before_interval_var, IndexedIntervalVar: _before_indexed_interval_var, ScalarSequenceVar: _before_sequence_var, - _SequenceVarData: _before_sequence_var, + SequenceVarData: _before_sequence_var, ScalarVar: _before_var, VarData: _before_var, IndexedVar: _before_indexed_var, diff --git a/pyomo/contrib/cp/sequence_var.py b/pyomo/contrib/cp/sequence_var.py index 486776f58da..cb42f445dc3 100644 --- a/pyomo/contrib/cp/sequence_var.py +++ b/pyomo/contrib/cp/sequence_var.py @@ -26,7 +26,7 @@ logger = logging.getLogger(__name__) -class _SequenceVarData(ActiveComponentData): +class SequenceVarData(ActiveComponentData): """This class defines the abstract interface for a single sequence variable.""" __slots__ = ('interval_vars',) @@ -62,7 +62,7 @@ def set_value(self, expr): @ModelComponentFactory.register("Sequences of IntervalVars") class SequenceVar(ActiveIndexedComponent): - _ComponentDataClass = _SequenceVarData + _ComponentDataClass = SequenceVarData def __new__(cls, *args, **kwds): if cls != SequenceVar: @@ -100,7 +100,7 @@ def _getitem_when_not_present(self, index): def construct(self, data=None): """ - Construct the _SequenceVarData objects for this SequenceVar + Construct the SequenceVarData objects for this SequenceVar """ if self._constructed: return @@ -140,9 +140,9 @@ def _pprint(self): ) -class ScalarSequenceVar(_SequenceVarData, SequenceVar): +class ScalarSequenceVar(SequenceVarData, SequenceVar): def __init__(self, *args, **kwds): - _SequenceVarData.__init__(self, component=self) + SequenceVarData.__init__(self, component=self) SequenceVar.__init__(self, *args, **kwds) self._index = UnindexedComponent_index From f76108584d5be2f12259055cfac0529a39c7e8c1 Mon Sep 17 00:00:00 2001 From: Miranda Mundt <55767766+mrmundt@users.noreply.github.com> Date: Wed, 8 May 2024 08:19:01 -0600 Subject: [PATCH 1765/1797] Create basic autodoc for MAiNGO --- .../appsi/appsi.solvers.maingo.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 doc/OnlineDocs/library_reference/appsi/appsi.solvers.maingo.rst diff --git a/doc/OnlineDocs/library_reference/appsi/appsi.solvers.maingo.rst b/doc/OnlineDocs/library_reference/appsi/appsi.solvers.maingo.rst new file mode 100644 index 00000000000..21e61c38d51 --- /dev/null +++ b/doc/OnlineDocs/library_reference/appsi/appsi.solvers.maingo.rst @@ -0,0 +1,14 @@ +MAiNGO +====== + +.. autoclass:: pyomo.contrib.appsi.solvers.maingo.MAiNGOConfig + :members: + :inherited-members: + :undoc-members: + :show-inheritance: + +.. autoclass:: pyomo.contrib.appsi.solvers.maingo.MAiNGO + :members: + :inherited-members: + :undoc-members: + :show-inheritance: From fbd9a0ab2d8a73babc3d67acfb6cc71d4d92677e Mon Sep 17 00:00:00 2001 From: Miranda Mundt <55767766+mrmundt@users.noreply.github.com> Date: Wed, 8 May 2024 08:22:32 -0600 Subject: [PATCH 1766/1797] Update APPSI TOC --- doc/OnlineDocs/library_reference/appsi/appsi.solvers.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/OnlineDocs/library_reference/appsi/appsi.solvers.rst b/doc/OnlineDocs/library_reference/appsi/appsi.solvers.rst index 1c598d95628..f4dcb81b4be 100644 --- a/doc/OnlineDocs/library_reference/appsi/appsi.solvers.rst +++ b/doc/OnlineDocs/library_reference/appsi/appsi.solvers.rst @@ -13,3 +13,4 @@ Solvers appsi.solvers.cplex appsi.solvers.cbc appsi.solvers.highs + appsi.solvers.maingo From 82dfda15e1ce4650ff3d7b7a2abfd2c90735a8bd Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 8 May 2024 08:28:12 -0600 Subject: [PATCH 1767/1797] Ensure the same output is logged on Windows and other platforms --- pyomo/common/download.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyomo/common/download.py b/pyomo/common/download.py index ad672e8c79b..ad3b64060e9 100644 --- a/pyomo/common/download.py +++ b/pyomo/common/download.py @@ -430,6 +430,10 @@ def filter_fcn(info): # commonpath() will raise ValueError for paths that # don't have anything in common (notably, when files are # on different drives on Windows) + logger.error( + "potentially insecure filename (%s) resolves outside target " + "directory. Skipping file." % (f,) + ) return False # Strip high bits & group/other write bits info.mode &= 0o755 From 8ebd61ccd5c8a3e8f5e62418024ea73130961c68 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 8 May 2024 09:54:39 -0600 Subject: [PATCH 1768/1797] Generate binary vectors without going through strings --- .../piecewise/transform/disaggregated_logarithmic.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/piecewise/transform/disaggregated_logarithmic.py b/pyomo/contrib/piecewise/transform/disaggregated_logarithmic.py index d5de010f308..d582cdcfff5 100644 --- a/pyomo/contrib/piecewise/transform/disaggregated_logarithmic.py +++ b/pyomo/contrib/piecewise/transform/disaggregated_logarithmic.py @@ -193,6 +193,10 @@ def x_constraint(b, i): # TODO test the Gray codes too # note: Must have num != 0 and ceil(log2(num)) > length to be valid def _get_binary_vector(self, num, length): - # Use python's string formatting instead of bothering with modular - # arithmetic. Hopefully not slow. - return tuple(int(x) for x in format(num, f"0{length}b")) + ans = [] + for i in range(length): + ans.append(num & 1) + num >>= 1 + assert not num + ans.reverse() + return tuple(ans) From 3572c445145b8e901d1de61cc842914c5ea60d8a Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Wed, 8 May 2024 10:37:39 -0600 Subject: [PATCH 1769/1797] ginac cleanup --- pyomo/contrib/simplification/build.py | 1 + pyomo/contrib/simplification/ginac/__init__.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/simplification/build.py b/pyomo/contrib/simplification/build.py index d540991b010..dfb9d2cf1c8 100644 --- a/pyomo/contrib/simplification/build.py +++ b/pyomo/contrib/simplification/build.py @@ -71,6 +71,7 @@ def build_ginac_library(parallel=None, argv=None, env=None): logger.info("\nBuilding GiNaC\n") assert subprocess.run(make_cmd, cwd=ginac_dir, env=env).returncode == 0 assert subprocess.run(install_cmd, cwd=ginac_dir, env=env).returncode == 0 + print("Installed GiNaC to %s" % (ginac_dir,)) def _find_include(libdir, incpaths): diff --git a/pyomo/contrib/simplification/ginac/__init__.py b/pyomo/contrib/simplification/ginac/__init__.py index af6511944de..6896bec12c4 100644 --- a/pyomo/contrib/simplification/ginac/__init__.py +++ b/pyomo/contrib/simplification/ginac/__init__.py @@ -30,7 +30,7 @@ def _importer(): # GiNaC needs 2 libraries that are generally dynamically linked # to the interface library. If we built those ourselves, then # the libraries will be PYOMO_CONFIG_DIR/lib ... but that - # directlor is very likely to NOT be on the library search path + # directory is very likely to NOT be on the library search path # when the Python interpreter was started. We will manually # look for those two libraries, and if we find them, load them # into this process (so the interface can find them) From a53c6f7ec4929a5821203ee0a87e8d31e88edb1d Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 8 May 2024 10:39:54 -0600 Subject: [PATCH 1770/1797] Add 'builders' marker for testing custom library builds (currently just ginac) --- .jenkins.sh | 7 +++++++ pyomo/contrib/simplification/tests/test_simplification.py | 2 ++ setup.cfg | 1 + 3 files changed, 10 insertions(+) diff --git a/.jenkins.sh b/.jenkins.sh index 37be6113ed9..37a9238f983 100644 --- a/.jenkins.sh +++ b/.jenkins.sh @@ -122,6 +122,13 @@ if test -z "$MODE" -o "$MODE" == setup; then echo "PYOMO_CONFIG_DIR=$PYOMO_CONFIG_DIR" echo "" + # Call Pyomo build scripts to build TPLs that would normally be + # skipped by the pyomo download-extensions / build-extensions + # actions below + if test [ " $CATEGORY " == *" builders "*; then + python pyomo/contrib/simplification/build.py --build-deps || exit 1 + fi + # Use Pyomo to download & compile binary extensions i=0 while /bin/true; do diff --git a/pyomo/contrib/simplification/tests/test_simplification.py b/pyomo/contrib/simplification/tests/test_simplification.py index acef0af502e..1ff9f5a3cc4 100644 --- a/pyomo/contrib/simplification/tests/test_simplification.py +++ b/pyomo/contrib/simplification/tests/test_simplification.py @@ -115,6 +115,8 @@ def setUp(self): self.simp = Simplifier(mode=Simplifier.Mode.sympy) +@unittest.pytest.mark.default +@unittest.pytest.mark.builders @unittest.skipUnless(ginac_available, 'GiNaC is not available') class TestSimplificationGiNaC(unittest.TestCase, SimplificationMixin): def setUp(self): diff --git a/setup.cfg b/setup.cfg index b606138f38c..d9ccbbb7c5e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,3 +22,4 @@ markers = lp: marks lp tests gams: marks gams tests bar: marks bar tests + builders: thests that should be run when testing custom (extension) builders \ No newline at end of file From 8f33eed1ed2a82c24be5d5b3dff77dcab472f67e Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 8 May 2024 10:40:36 -0600 Subject: [PATCH 1771/1797] Support multiple markers (categories) in jenkins driver --- .jenkins.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.jenkins.sh b/.jenkins.sh index 37a9238f983..8c72edf41c0 100644 --- a/.jenkins.sh +++ b/.jenkins.sh @@ -43,8 +43,8 @@ fi if test -z "$SLIM"; then export VENV_SYSTEM_PACKAGES='--system-site-packages' fi -if test ! -z "$CATEGORY"; then - export PY_CAT="-m $CATEGORY" +if test -n "$CATEGORY"; then + export PY_CAT="-m '"`echo "$CATEGORY" | sed -r "s/ +/ or /g"`"'" fi if test "$WORKSPACE" != "`pwd`"; then From 4dc4e893aa861bcbeaf777e971788f5e8ce39983 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 8 May 2024 10:41:01 -0600 Subject: [PATCH 1772/1797] Additional (debugging) output in ginac_interface builder --- pyomo/contrib/simplification/build.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/contrib/simplification/build.py b/pyomo/contrib/simplification/build.py index d540991b010..a1332490b1b 100644 --- a/pyomo/contrib/simplification/build.py +++ b/pyomo/contrib/simplification/build.py @@ -155,6 +155,7 @@ def run(self): ) if not os.path.exists(target): os.makedirs(target) + sys.stdout.write(f"Installing {library} in {target}\n") shutil.copy(library, target) package_config = { From 22ee3c76e9974960e156f567b332ddf6746c6021 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 8 May 2024 10:57:24 -0600 Subject: [PATCH 1773/1797] Updating CHANGELOG in preparation for the 6.7.2 release --- CHANGELOG.md | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c06e0f71378..11a9f4a3020 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,78 @@ Pyomo CHANGELOG =============== +------------------------------------------------------------------------------- +Pyomo 6.7.2 (9 May 2024) +------------------------------------------------------------------------------- + +- General + - Support config domains with either method or attribute domain_name (#3159) + - Update TPL package list due to contrib.solver (#3164) + - Automate TPL callback registrations (#3167) + - Fix type registrations for ExternalFunction arguments (#3168) + - Only modify module path and spec for deferred import modules (#3176) + - Add "mixed" standard form representation (#3201) + - Support "default" dispatchers in `ExitNodeDispatcher` (#3194) + - Redefine objective sense as a proper `IntEnum` (#3224) + - Fix division-by-0 bug in linear walker (#3246) +- Core + - Allow `Var` objects in `LinearExpression.args` (#3189) + - Add type hints to components (#3173) + - Simplify expressions generated by `TemplateSumExpression` (#3196) + - Make component data public classes (#3221, #3253) + - Exploit repeated named expressions in `identify_variables` (#3190) +- Documentation + - NFC: Add link to the HOMOWP companion notebooks (#3195) + - Update installation documentation to include Cython instructions (#3208) + - Add links to the Pyomo Book Springer page (#3211) +- Solver Interfaces + - Fix division by zero error in linear presolve (#3161) + - Subprocess timeout update (#3183) + - Solver Refactor - Allow no objective (#3181) + - NLv2: handle presolved independent linear subsystems (#3193) + - Update `LegacySolverWrapper` to be compatible with the `pyomo` script (#3202) + - Fix mosek_direct to use putqconk instead of putqcon (#3199) + - Solver Refactor - Bug fixes for IDAES Integration (#3214) + - Check _skip_trivial_constraints before the constraint body (#3226) + - Fix AMPL solver duplicate funcadd (#3206) + - Solver Refactor - Fix bugs in setting `name` and `solutions` attributes (#3228) + - Disable the use of universal newlines in the ipopt_v2 NL file (#3231) + - NLv2: fix reporting numbers of nonlinear discrete variables (#3238) + - Fix: Get SCIP solving time considering float number with some text (#3234) + - Solver Refactor - Add `gurobi_direct` implementation (#3225) +- Testing + - Set maxDiff=None on the base TestCase class (#3171) + - Testing infrastructure updates (#3175) + - Typos update for March 2024 (#3219) + - Add openmpi to testing environment to work around issue in mpi4py (#3236, #3239) + - Skip black 24.4.1 due to a bug in the parser (#3247) + - Skip tests on draft and WIP pull requests (#3223) + - Update GHA to grab gurobipy from PyPI (#3254) +- GDP + - Use private_data for all mappings between original and transformed components (#3166) + - Fix a bug in gdp.bigm transformation for nested GDPs (#3213) +- Contributed Packages + - APPSI: Allow cmodel to handle non-mutable params in var and constraint bounds (#3182) + - APPSI: Allow APPSI FBBT to handle nested named Expressions (#3185) + - APPSI: Add MAiNGO solver interface (#3165) + - DoE: Bug fixes (#3245) + - incidence_analysis: Improve performance of `solve_strongly_connected_components` for + models with named expressions (#3186) + - incidence_analysis: Add function to plot incidence graph in Dulmage-Mendelsohn order (#3207) + - incidence_analysis: Require variables and constraints to be specified separately in + `IncidenceGraphInterface.remove_nodes` (#3212) + - latex_printer: Resolve errors for set operations / multidimensional sets (#3177) + - MindtPy: Add Highs support (#2971) + - MindtPy: Add call_before_subproblem_solve callback (#3251) + - Parmest: New UI using experiment lists (#3160) + - preprocessing: Fix bug where variable aggregator did not intersect domains (#3241) + - PyNumero: Allow CyIpopt to solve problems without objectives (#3163) + - PyNumero: Work around bug in CyIpopt 1.4.0 (#3222) + - PyNumero: Include "inventory" in readme (#3248) + - PyROS: Simplify custom domain validators (#3169) + - PyROS: Fix iteration logging for edge case involving discrete sets (#3170) + - PyROS: Update solver timing system (#3198) + ------------------------------------------------------------------------------- Pyomo 6.7.1 (21 Feb 2024) ------------------------------------------------------------------------------- From 3bfa3bd1e8b1610217052fdf48cf4ff63a9b5f1f Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 8 May 2024 10:57:58 -0600 Subject: [PATCH 1774/1797] Pinning to numpy<2.0.0 for the release --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 70c1626a650..8817649ecc2 100644 --- a/setup.py +++ b/setup.py @@ -256,7 +256,7 @@ def __ne__(self, other): 'sphinx-toolbox>=2.16.0', 'sphinx-jinja2-compat>=0.1.1', 'enum_tools', - 'numpy', # Needed by autodoc for pynumero + 'numpy<2.0.0', # Needed by autodoc for pynumero 'scipy', # Needed by autodoc for pynumero ], 'optional': [ @@ -271,7 +271,7 @@ def __ne__(self, other): # installed on python 3.8 'networkx<3.2; python_version<"3.9"', 'networkx; python_version>="3.9"', - 'numpy', + 'numpy<2.0.0', 'openpyxl', # dataportals #'pathos', # requested for #963, but PR currently closed 'pint', # units From 1d1f131b940e00a351fae66f8ad9260f9029084c Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 8 May 2024 11:01:22 -0600 Subject: [PATCH 1775/1797] More updates to the CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11a9f4a3020..683551ba03a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ Pyomo 6.7.2 (9 May 2024) - APPSI: Allow cmodel to handle non-mutable params in var and constraint bounds (#3182) - APPSI: Allow APPSI FBBT to handle nested named Expressions (#3185) - APPSI: Add MAiNGO solver interface (#3165) + - CP: Add SequenceVar and other logical expressions for scheduling (#3227) - DoE: Bug fixes (#3245) - incidence_analysis: Improve performance of `solve_strongly_connected_components` for models with named expressions (#3186) From 38b966298ace3b1b06f73501a8e436f4031a51be Mon Sep 17 00:00:00 2001 From: Miranda Mundt <55767766+mrmundt@users.noreply.github.com> Date: Wed, 8 May 2024 11:13:38 -0600 Subject: [PATCH 1776/1797] Reorder and merge some items --- CHANGELOG.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 683551ba03a..d61758f7c1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,6 @@ Pyomo 6.7.2 (9 May 2024) - General - Support config domains with either method or attribute domain_name (#3159) - - Update TPL package list due to contrib.solver (#3164) - Automate TPL callback registrations (#3167) - Fix type registrations for ExternalFunction arguments (#3168) - Only modify module path and spec for deferred import modules (#3176) @@ -29,19 +28,18 @@ Pyomo 6.7.2 (9 May 2024) - Solver Interfaces - Fix division by zero error in linear presolve (#3161) - Subprocess timeout update (#3183) - - Solver Refactor - Allow no objective (#3181) + - Solver Refactor - Bug fixes for various components (#3181, #3214, #3228) - NLv2: handle presolved independent linear subsystems (#3193) - Update `LegacySolverWrapper` to be compatible with the `pyomo` script (#3202) - Fix mosek_direct to use putqconk instead of putqcon (#3199) - - Solver Refactor - Bug fixes for IDAES Integration (#3214) - Check _skip_trivial_constraints before the constraint body (#3226) - Fix AMPL solver duplicate funcadd (#3206) - - Solver Refactor - Fix bugs in setting `name` and `solutions` attributes (#3228) - Disable the use of universal newlines in the ipopt_v2 NL file (#3231) - NLv2: fix reporting numbers of nonlinear discrete variables (#3238) - Fix: Get SCIP solving time considering float number with some text (#3234) - Solver Refactor - Add `gurobi_direct` implementation (#3225) - Testing + - Update TPL package list due to `contrib.solver` (#3164) - Set maxDiff=None on the base TestCase class (#3171) - Testing infrastructure updates (#3175) - Typos update for March 2024 (#3219) @@ -64,7 +62,7 @@ Pyomo 6.7.2 (9 May 2024) - incidence_analysis: Require variables and constraints to be specified separately in `IncidenceGraphInterface.remove_nodes` (#3212) - latex_printer: Resolve errors for set operations / multidimensional sets (#3177) - - MindtPy: Add Highs support (#2971) + - MindtPy: Add HiGHS support (#2971) - MindtPy: Add call_before_subproblem_solve callback (#3251) - Parmest: New UI using experiment lists (#3160) - preprocessing: Fix bug where variable aggregator did not intersect domains (#3241) From e847f10f032fc2a04080af7784d671847a51adcc Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 8 May 2024 11:25:24 -0600 Subject: [PATCH 1777/1797] NFC: fix typo --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index d9ccbbb7c5e..f670cef8f68 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,4 +22,4 @@ markers = lp: marks lp tests gams: marks gams tests bar: marks bar tests - builders: thests that should be run when testing custom (extension) builders \ No newline at end of file + builders: tests that should be run when testing custom (extension) builders \ No newline at end of file From 8b11a1c789a73219054fec5d25b8db22f42e8da8 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 8 May 2024 11:27:51 -0600 Subject: [PATCH 1778/1797] NFC: resyncing test_branches and test_pr_and_main --- .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 df6455568b9..d9c36e78fc4 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -96,7 +96,7 @@ jobs: PACKAGES: openmpi mpi4py - os: ubuntu-latest - python: 3.11 + python: '3.11' other: /singletest category: "-m 'neos or importtest'" skip_doctest: 1 @@ -273,7 +273,7 @@ jobs: if test -z "${{matrix.slim}}"; then 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 gurobipy==10.0.3\ + python -m pip install --cache-dir cache/pip 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 3a33d89ff10707c707fc735285afcd8213868f94 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 8 May 2024 16:49:53 -0600 Subject: [PATCH 1779/1797] More edits to the CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d61758f7c1c..954231f9f2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ Pyomo 6.7.2 (9 May 2024) - MindtPy: Add HiGHS support (#2971) - MindtPy: Add call_before_subproblem_solve callback (#3251) - Parmest: New UI using experiment lists (#3160) + - piecewise: Add piecewise linear transformations (#3036) - preprocessing: Fix bug where variable aggregator did not intersect domains (#3241) - PyNumero: Allow CyIpopt to solve problems without objectives (#3163) - PyNumero: Work around bug in CyIpopt 1.4.0 (#3222) From fa2cc317eb90941c87997bb7b7935aca488cc1a6 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 8 May 2024 17:04:32 -0600 Subject: [PATCH 1780/1797] improve handling of category quotation --- .jenkins.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.jenkins.sh b/.jenkins.sh index 8c72edf41c0..a00b42eac4e 100644 --- a/.jenkins.sh +++ b/.jenkins.sh @@ -43,9 +43,6 @@ fi if test -z "$SLIM"; then export VENV_SYSTEM_PACKAGES='--system-site-packages' fi -if test -n "$CATEGORY"; then - export PY_CAT="-m '"`echo "$CATEGORY" | sed -r "s/ +/ or /g"`"'" -fi if test "$WORKSPACE" != "`pwd`"; then echo "ERROR: pwd is not WORKSPACE" @@ -185,7 +182,7 @@ if test -z "$MODE" -o "$MODE" == test; then python -m pytest -v \ -W ignore::Warning \ --junitxml="TEST-pyomo.xml" \ - $PY_CAT $TEST_SUITES $PYTEST_EXTRA_ARGS + -m "$CATEGORY" $TEST_SUITES $PYTEST_EXTRA_ARGS # Combine the coverage results and upload if test -z "$DISABLE_COVERAGE"; then From bd5f10cb7d0dbd96aae6114e9ee51407e9b4dd13 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 8 May 2024 17:07:16 -0600 Subject: [PATCH 1781/1797] Improve conftest.py efficiency --- conftest.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/conftest.py b/conftest.py index 7faad6fc89b..00abdecfa12 100644 --- a/conftest.py +++ b/conftest.py @@ -11,6 +11,8 @@ import pytest +_implicit_markers = {'default',} +_extended_implicit_markers = _implicit_markers.union({'solver',}) def pytest_runtest_setup(item): """ @@ -32,13 +34,10 @@ def pytest_runtest_setup(item): the default mode; but if solver tests are also marked with an explicit category (e.g., "expensive"), we will skip them. """ - marker = item.iter_markers() solvernames = [mark.args[0] for mark in item.iter_markers(name="solver")] solveroption = item.config.getoption("--solver") markeroption = item.config.getoption("-m") - implicit_markers = ['default'] - extended_implicit_markers = implicit_markers + ['solver'] - item_markers = set(mark.name for mark in marker) + item_markers = set(mark.name for mark in item.iter_markers()) if solveroption: if solveroption not in solvernames: pytest.skip("SKIPPED: Test not marked {!r}".format(solveroption)) @@ -46,9 +45,9 @@ def pytest_runtest_setup(item): elif markeroption: return elif item_markers: - if not set(implicit_markers).issubset( + if not _implicit_markers.issubset( item_markers - ) and not item_markers.issubset(set(extended_implicit_markers)): + ) and not item_markers.issubset(_extended_implicit_markers): pytest.skip('SKIPPED: Only running default, solver, and unmarked tests.') From 354feb2c9a81473bc9ca95138bcd8e4d4eadd85c Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 8 May 2024 17:07:46 -0600 Subject: [PATCH 1782/1797] Ensure that all unmarked tests are marked with the implicit markers --- conftest.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/conftest.py b/conftest.py index 00abdecfa12..6e8043c2c92 100644 --- a/conftest.py +++ b/conftest.py @@ -14,6 +14,18 @@ _implicit_markers = {'default',} _extended_implicit_markers = _implicit_markers.union({'solver',}) +def pytest_collection_modifyitems(items): + """ + This method will mark any unmarked tests with the implicit marker ('default') + + """ + for item in items: + try: + next(item.iter_markers()) + except StopIteration: + for marker in _implicit_markers: + item.add_marker(getattr(pytest.mark, marker)) + def pytest_runtest_setup(item): """ This method overrides pytest's default behavior for marked tests. From 92edcc5d7b486a767f0033514e13ce6549d8e81d Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 8 May 2024 17:08:29 -0600 Subject: [PATCH 1783/1797] NFC: apply black --- conftest.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/conftest.py b/conftest.py index 6e8043c2c92..34b366f9fd6 100644 --- a/conftest.py +++ b/conftest.py @@ -11,8 +11,9 @@ import pytest -_implicit_markers = {'default',} -_extended_implicit_markers = _implicit_markers.union({'solver',}) +_implicit_markers = {'default'} +_extended_implicit_markers = _implicit_markers.union({'solver'}) + def pytest_collection_modifyitems(items): """ @@ -26,6 +27,7 @@ def pytest_collection_modifyitems(items): for marker in _implicit_markers: item.add_marker(getattr(pytest.mark, marker)) + def pytest_runtest_setup(item): """ This method overrides pytest's default behavior for marked tests. @@ -57,9 +59,9 @@ def pytest_runtest_setup(item): elif markeroption: return elif item_markers: - if not _implicit_markers.issubset( - item_markers - ) and not item_markers.issubset(_extended_implicit_markers): + if not _implicit_markers.issubset(item_markers) and not item_markers.issubset( + _extended_implicit_markers + ): pytest.skip('SKIPPED: Only running default, solver, and unmarked tests.') From 7ae72756ad5e0019370ed3aaf9f327b86fd717d1 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 8 May 2024 17:47:16 -0600 Subject: [PATCH 1784/1797] Prevent APPSI / GiNaC interfaces from exposing module symbols globally --- pyomo/contrib/appsi/cmodel/src/cmodel_bindings.cpp | 5 +++-- pyomo/contrib/simplification/ginac/src/ginac_interface.cpp | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/appsi/cmodel/src/cmodel_bindings.cpp b/pyomo/contrib/appsi/cmodel/src/cmodel_bindings.cpp index 6acc1d79845..5a838ffd786 100644 --- a/pyomo/contrib/appsi/cmodel/src/cmodel_bindings.cpp +++ b/pyomo/contrib/appsi/cmodel/src/cmodel_bindings.cpp @@ -63,7 +63,8 @@ PYBIND11_MODULE(appsi_cmodel, m) { m.def("appsi_exprs_from_pyomo_exprs", &appsi_exprs_from_pyomo_exprs); m.def("appsi_expr_from_pyomo_expr", &appsi_expr_from_pyomo_expr); m.def("prep_for_repn", &prep_for_repn); - py::class_(m, "PyomoExprTypes").def(py::init<>()); + py::class_(m, "PyomoExprTypes", py::module_local()) + .def(py::init<>()); py::class_>(m, "Node") .def("is_variable_type", &Node::is_variable_type) .def("is_param_type", &Node::is_param_type) @@ -165,7 +166,7 @@ PYBIND11_MODULE(appsi_cmodel, m) { .def(py::init<>()) .def("write", &LPWriter::write) .def("get_solve_cons", &LPWriter::get_solve_cons); - py::enum_(m, "ExprType") + py::enum_(m, "ExprType", py::module_local()) .value("py_float", ExprType::py_float) .value("var", ExprType::var) .value("param", ExprType::param) diff --git a/pyomo/contrib/simplification/ginac/src/ginac_interface.cpp b/pyomo/contrib/simplification/ginac/src/ginac_interface.cpp index 1060f87161c..9b05baf71ca 100644 --- a/pyomo/contrib/simplification/ginac/src/ginac_interface.cpp +++ b/pyomo/contrib/simplification/ginac/src/ginac_interface.cpp @@ -298,7 +298,8 @@ py::object GinacInterface::from_ginac(ex &ge) { PYBIND11_MODULE(ginac_interface, m) { m.def("pyomo_to_ginac", &pyomo_to_ginac); - py::class_(m, "PyomoExprTypes").def(py::init<>()); + py::class_(m, "PyomoExprTypes", py::module_local()) + .def(py::init<>()); py::class_(m, "ginac_expression") .def("expand", [](ex &ge) { return ge.expand(); @@ -313,7 +314,7 @@ PYBIND11_MODULE(ginac_interface, m) { .def(py::init()) .def("to_ginac", &GinacInterface::to_ginac) .def("from_ginac", &GinacInterface::from_ginac); - py::enum_(m, "ExprType") + py::enum_(m, "ExprType", py::module_local()) .value("py_float", ExprType::py_float) .value("var", ExprType::var) .value("param", ExprType::param) From 1409aa2956159b8a062e12ed831db79b57dda189 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 8 May 2024 18:09:15 -0600 Subject: [PATCH 1785/1797] Fix bug in Jenkins driver --- .jenkins.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.jenkins.sh b/.jenkins.sh index a00b42eac4e..842733e471b 100644 --- a/.jenkins.sh +++ b/.jenkins.sh @@ -122,7 +122,7 @@ if test -z "$MODE" -o "$MODE" == setup; then # Call Pyomo build scripts to build TPLs that would normally be # skipped by the pyomo download-extensions / build-extensions # actions below - if test [ " $CATEGORY " == *" builders "*; then + if test [[ " $CATEGORY " == *" builders "* ]]; then python pyomo/contrib/simplification/build.py --build-deps || exit 1 fi From aa756017fc23091764a63721dc80c94181cbe01a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 8 May 2024 18:29:43 -0600 Subject: [PATCH 1786/1797] Fix typo in Jenkins driver --- .jenkins.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.jenkins.sh b/.jenkins.sh index 842733e471b..0f4e70d3cf1 100644 --- a/.jenkins.sh +++ b/.jenkins.sh @@ -122,7 +122,7 @@ if test -z "$MODE" -o "$MODE" == setup; then # Call Pyomo build scripts to build TPLs that would normally be # skipped by the pyomo download-extensions / build-extensions # actions below - if test [[ " $CATEGORY " == *" builders "* ]]; then + if [[ " $CATEGORY " == *" builders "* ]]; then python pyomo/contrib/simplification/build.py --build-deps || exit 1 fi From cdaff17f6a7e7c36428df887acdc7a7a59ec836a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 8 May 2024 18:53:56 -0600 Subject: [PATCH 1787/1797] Add info to the build log --- .jenkins.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.jenkins.sh b/.jenkins.sh index 0f4e70d3cf1..696847fd92c 100644 --- a/.jenkins.sh +++ b/.jenkins.sh @@ -123,13 +123,19 @@ if test -z "$MODE" -o "$MODE" == setup; then # skipped by the pyomo download-extensions / build-extensions # actions below if [[ " $CATEGORY " == *" builders "* ]]; then + echo "" + echo "Running local build scripts..." + echo "" + set -x python pyomo/contrib/simplification/build.py --build-deps || exit 1 + set +x fi # Use Pyomo to download & compile binary extensions i=0 while /bin/true; do i=$[$i+1] + echo "" echo "Downloading pyomo extensions (attempt $i)" pyomo download-extensions $PYOMO_DOWNLOAD_ARGS if test $? == 0; then From 76d53de4d383cd03eadaa4997f4deecebec6d4f1 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 8 May 2024 18:54:19 -0600 Subject: [PATCH 1788/1797] Explicitly call out CWD when running configure --- pyomo/contrib/simplification/build.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/simplification/build.py b/pyomo/contrib/simplification/build.py index 53133c2fb4e..b4bec63088a 100644 --- a/pyomo/contrib/simplification/build.py +++ b/pyomo/contrib/simplification/build.py @@ -27,7 +27,11 @@ def build_ginac_library(parallel=None, argv=None, env=None): sys.stdout.write("\n**** Building GiNaC library ****\n") - configure_cmd = ['configure', '--prefix=' + PYOMO_CONFIG_DIR, '--disable-static'] + configure_cmd = [ + os.path.join('.', 'configure'), + '--prefix=' + PYOMO_CONFIG_DIR, + '--disable-static', + ] make_cmd = ['make'] if parallel: make_cmd.append(f'-j{parallel}') From 35b71f87a1d070b10553119c86489f99875a6587 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 8 May 2024 20:17:31 -0600 Subject: [PATCH 1789/1797] Removing singletest from test_branches --- .github/workflows/test_branches.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index d9c36e78fc4..5063571c65f 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -95,14 +95,6 @@ jobs: PYENV: conda PACKAGES: openmpi mpi4py - - os: ubuntu-latest - python: '3.11' - other: /singletest - category: "-m 'neos or importtest'" - skip_doctest: 1 - TARGET: linux - PYENV: pip - - os: ubuntu-latest python: '3.10' other: /cython From f650f9645b27536e1ea45d36fc15af9b3fbc6f6e Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Thu, 9 May 2024 08:35:51 -0600 Subject: [PATCH 1790/1797] More edits to the CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 954231f9f2a..922e072250e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,7 @@ Pyomo 6.7.2 (9 May 2024) - PyROS: Simplify custom domain validators (#3169) - PyROS: Fix iteration logging for edge case involving discrete sets (#3170) - PyROS: Update solver timing system (#3198) + - simplification: New module for expression simplification using GiNaC or SymPy (#3088) ------------------------------------------------------------------------------- Pyomo 6.7.1 (21 Feb 2024) From 15b52dbabddcbc53a37ecc368d3a38d01e1482fa Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 9 May 2024 09:27:25 -0600 Subject: [PATCH 1791/1797] Setting final deprecation version strings --- pyomo/common/dependencies.py | 6 +++--- pyomo/common/numeric_types.py | 2 +- pyomo/contrib/incidence_analysis/interface.py | 6 +++--- pyomo/contrib/parmest/parmest.py | 6 ++---- pyomo/core/base/__init__.py | 8 ++++---- pyomo/core/base/block.py | 2 +- pyomo/core/base/boolean_var.py | 4 ++-- pyomo/core/base/component.py | 6 +++--- pyomo/core/base/connector.py | 2 +- pyomo/core/base/constraint.py | 4 ++-- pyomo/core/base/expression.py | 6 +++--- pyomo/core/base/logical_constraint.py | 4 ++-- pyomo/core/base/objective.py | 4 ++-- pyomo/core/base/param.py | 2 +- pyomo/core/base/piecewise.py | 2 +- pyomo/core/base/set.py | 16 ++++++++-------- pyomo/core/base/sos.py | 2 +- pyomo/core/base/var.py | 4 ++-- pyomo/core/expr/numvalue.py | 2 +- pyomo/gdp/disjunct.py | 4 ++-- pyomo/mpec/complementarity.py | 2 +- pyomo/network/arc.py | 2 +- pyomo/network/port.py | 2 +- 23 files changed, 48 insertions(+), 50 deletions(-) diff --git a/pyomo/common/dependencies.py b/pyomo/common/dependencies.py index ea9efe370f7..4c9e43002ef 100644 --- a/pyomo/common/dependencies.py +++ b/pyomo/common/dependencies.py @@ -611,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 in version 6.7.2.dev0) + DEPRECATED: renamed to ``defer_import`` (deprecated in version 6.7.2) defer_import: bool, optional If True, then the attempted import is deferred until the first @@ -674,7 +674,7 @@ def attempt_import( if defer_check is not None: deprecation_warning( 'defer_check=%s is deprecated. Please use defer_import' % (defer_check,), - version='6.7.2.dev0', + version='6.7.2', ) assert defer_import is None defer_import = defer_check @@ -787,7 +787,7 @@ def _perform_import( @deprecated( "``declare_deferred_modules_as_importable()`` is deprecated. " "Use the :py:class:`declare_modules_as_importable` context manager.", - version='6.7.2.dev0', + version='6.7.2', ) def declare_deferred_modules_as_importable(globals_dict): """Make all :py:class:`DeferredImportModules` in ``globals_dict`` importable diff --git a/pyomo/common/numeric_types.py b/pyomo/common/numeric_types.py index 8b48c77b5b2..2b63038e125 100644 --- a/pyomo/common/numeric_types.py +++ b/pyomo/common/numeric_types.py @@ -64,7 +64,7 @@ relocated_module_attribute( 'pyomo_constant_types', 'pyomo.common.numeric_types._pyomo_constant_types', - version='6.7.2.dev0', + version='6.7.2', 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 " diff --git a/pyomo/contrib/incidence_analysis/interface.py b/pyomo/contrib/incidence_analysis/interface.py index b73ec17f36c..73d9722eb7e 100644 --- a/pyomo/contrib/incidence_analysis/interface.py +++ b/pyomo/contrib/incidence_analysis/interface.py @@ -891,9 +891,9 @@ def remove_nodes(self, variables=None, constraints=None): .. note:: - **Deprecation in Pyomo v6.7.2.dev0** + **Deprecation in Pyomo v6.7.2** - The pre-6.7.2.dev0 implementation of ``remove_nodes`` allowed variables and + The pre-6.7.2 implementation of ``remove_nodes`` allowed variables and constraints to remove to be specified in a single list. This made error checking difficult, and indeed, if invalid components were provided, we carried on silently instead of throwing an error or @@ -923,7 +923,7 @@ def remove_nodes(self, variables=None, constraints=None): if any(var in self._con_index_map for var in variables) or any( con in self._var_index_map for con in constraints ): - deprecation_warning(depr_msg, version="6.7.2.dev0") + deprecation_warning(depr_msg, version="6.7.2") # If we received variables/constraints in the same list, sort them. # Any unrecognized objects will be caught by _validate_input. for var in variables: diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py index 70f9de8b84c..41e7792570b 100644 --- a/pyomo/contrib/parmest/parmest.py +++ b/pyomo/contrib/parmest/parmest.py @@ -68,8 +68,6 @@ from pyomo.common.deprecation import deprecated from pyomo.common.deprecation import deprecation_warning -DEPRECATION_VERSION = '6.7.2.dev0' - parmest_available = numpy_available & pandas_available & scipy_available inverse_reduced_hessian, inverse_reduced_hessian_available = attempt_import( @@ -338,7 +336,7 @@ def _deprecated_init( "You're using the deprecated parmest interface (model_function, " "data, theta_names). This interface will be removed in a future release, " "please update to the new parmest interface using experiment lists.", - version=DEPRECATION_VERSION, + version='6.7.2', ) self.pest_deprecated = _DeprecatedEstimator( model_function, @@ -1386,7 +1384,7 @@ def confidence_region_test( ################################ -@deprecated(version=DEPRECATION_VERSION) +@deprecated(version='6.7.2') def group_data(data, groupby_column_name, use_mean=None): """ Group data by scenario diff --git a/pyomo/core/base/__init__.py b/pyomo/core/base/__init__.py index 2b21725d82f..6b295196864 100644 --- a/pyomo/core/base/__init__.py +++ b/pyomo/core/base/__init__.py @@ -163,13 +163,13 @@ ) # Historically, only a subset of "private" component data classes were imported here relocated_module_attribute( - f'_GeneralVarData', f'pyomo.core.base.VarData', version='6.7.2.dev0' + f'_GeneralVarData', f'pyomo.core.base.VarData', version='6.7.2' ) relocated_module_attribute( - f'_GeneralBooleanVarData', f'pyomo.core.base.BooleanVarData', version='6.7.2.dev0' + f'_GeneralBooleanVarData', f'pyomo.core.base.BooleanVarData', version='6.7.2' ) relocated_module_attribute( - f'_ExpressionData', f'pyomo.core.base.NamedExpressionData', version='6.7.2.dev0' + f'_ExpressionData', f'pyomo.core.base.NamedExpressionData', version='6.7.2' ) for _cdata in ( 'ConstraintData', @@ -179,7 +179,7 @@ 'ObjectiveData', ): relocated_module_attribute( - f'_{_cdata}', f'pyomo.core.base.{_cdata}', version='6.7.2.dev0' + f'_{_cdata}', f'pyomo.core.base.{_cdata}', version='6.7.2' ) del _cdata del relocated_module_attribute diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index 2f5bdf85f6a..653809e0419 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -1983,7 +1983,7 @@ def private_data(self, scope=None): class _BlockData(metaclass=RenamedClass): __renamed__new_class__ = BlockData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' @ModelComponentFactory.register( diff --git a/pyomo/core/base/boolean_var.py b/pyomo/core/base/boolean_var.py index 67c06bdacce..db9a41fceda 100644 --- a/pyomo/core/base/boolean_var.py +++ b/pyomo/core/base/boolean_var.py @@ -252,12 +252,12 @@ def free(self): class _BooleanVarData(metaclass=RenamedClass): __renamed__new_class__ = BooleanVarData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' class _GeneralBooleanVarData(metaclass=RenamedClass): __renamed__new_class__ = BooleanVarData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' @ModelComponentFactory.register("Logical decision variables.") diff --git a/pyomo/core/base/component.py b/pyomo/core/base/component.py index d06b85dcdd4..966ce8c0737 100644 --- a/pyomo/core/base/component.py +++ b/pyomo/core/base/component.py @@ -477,7 +477,7 @@ def _pprint_base_impl( class _ComponentBase(metaclass=RenamedClass): __renamed__new_class__ = ComponentBase - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' class Component(ComponentBase): @@ -663,7 +663,7 @@ def getname(self, fully_qualified=False, name_buffer=None, relative_to=None): "use of this argument poses risks if the buffer contains " "names relative to different Blocks in the model hierarchy or " "a mixture of local and fully_qualified names.", - version='TODO', + version='6.4.1', ) name_buffer[id(self)] = ans return ans @@ -922,7 +922,7 @@ def getname(self, fully_qualified=False, name_buffer=None, relative_to=None): "use of this argument poses risks if the buffer contains " "names relative to different Blocks in the model hierarchy or " "a mixture of local and fully_qualified names.", - version='TODO', + version='6.4.1', ) if id(self) in name_buffer: # Return the name if it is in the buffer diff --git a/pyomo/core/base/connector.py b/pyomo/core/base/connector.py index e383b52fc11..1363f5abd65 100644 --- a/pyomo/core/base/connector.py +++ b/pyomo/core/base/connector.py @@ -107,7 +107,7 @@ def _iter_vars(self): class _ConnectorData(metaclass=RenamedClass): __renamed__new_class__ = ConnectorData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' @ModelComponentFactory.register( diff --git a/pyomo/core/base/constraint.py b/pyomo/core/base/constraint.py index eb4af76fdc1..e12860991c2 100644 --- a/pyomo/core/base/constraint.py +++ b/pyomo/core/base/constraint.py @@ -577,12 +577,12 @@ def slack(self): class _ConstraintData(metaclass=RenamedClass): __renamed__new_class__ = ConstraintData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' class _GeneralConstraintData(metaclass=RenamedClass): __renamed__new_class__ = ConstraintData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' @ModelComponentFactory.register("General constraint expressions.") diff --git a/pyomo/core/base/expression.py b/pyomo/core/base/expression.py index 013c388e6e5..a5120759236 100644 --- a/pyomo/core/base/expression.py +++ b/pyomo/core/base/expression.py @@ -198,12 +198,12 @@ def __ipow__(self, other): class _ExpressionData(metaclass=RenamedClass): __renamed__new_class__ = NamedExpressionData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' class _GeneralExpressionDataImpl(metaclass=RenamedClass): __renamed__new_class__ = NamedExpressionData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' class ExpressionData(NamedExpressionData, ComponentData): @@ -231,7 +231,7 @@ def __init__(self, expr=None, component=None): class _GeneralExpressionData(metaclass=RenamedClass): __renamed__new_class__ = ExpressionData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' @ModelComponentFactory.register( diff --git a/pyomo/core/base/logical_constraint.py b/pyomo/core/base/logical_constraint.py index 9584078307d..cc0780fd9bd 100644 --- a/pyomo/core/base/logical_constraint.py +++ b/pyomo/core/base/logical_constraint.py @@ -124,12 +124,12 @@ def get_value(self): class _LogicalConstraintData(metaclass=RenamedClass): __renamed__new_class__ = LogicalConstraintData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' class _GeneralLogicalConstraintData(metaclass=RenamedClass): __renamed__new_class__ = LogicalConstraintData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' @ModelComponentFactory.register("General logical constraints.") diff --git a/pyomo/core/base/objective.py b/pyomo/core/base/objective.py index e388d25aab4..f1204f2a09c 100644 --- a/pyomo/core/base/objective.py +++ b/pyomo/core/base/objective.py @@ -145,12 +145,12 @@ def set_sense(self, sense): class _ObjectiveData(metaclass=RenamedClass): __renamed__new_class__ = ObjectiveData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' class _GeneralObjectiveData(metaclass=RenamedClass): __renamed__new_class__ = ObjectiveData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' @ModelComponentFactory.register("Expressions that are minimized or maximized.") diff --git a/pyomo/core/base/param.py b/pyomo/core/base/param.py index 9af6a37de45..45de3286589 100644 --- a/pyomo/core/base/param.py +++ b/pyomo/core/base/param.py @@ -254,7 +254,7 @@ def _compute_polynomial_degree(self, result): class _ParamData(metaclass=RenamedClass): __renamed__new_class__ = ParamData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' @ModelComponentFactory.register( diff --git a/pyomo/core/base/piecewise.py b/pyomo/core/base/piecewise.py index efe500dbfb1..8c5f34d2b53 100644 --- a/pyomo/core/base/piecewise.py +++ b/pyomo/core/base/piecewise.py @@ -274,7 +274,7 @@ def __call__(self, x): class _PiecewiseData(metaclass=RenamedClass): __renamed__new_class__ = PiecewiseData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' class _SimpleSinglePiecewise(object): diff --git a/pyomo/core/base/set.py b/pyomo/core/base/set.py index b9a2fe72e1d..8b7c2a246d6 100644 --- a/pyomo/core/base/set.py +++ b/pyomo/core/base/set.py @@ -1179,12 +1179,12 @@ def __gt__(self, other): class _SetData(metaclass=RenamedClass): __renamed__new_class__ = SetData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' class _SetDataBase(metaclass=RenamedClass): __renamed__new_class__ = SetData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' class _FiniteSetMixin(object): @@ -1471,7 +1471,7 @@ def pop(self): class _FiniteSetData(metaclass=RenamedClass): __renamed__new_class__ = FiniteSetData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' class _ScalarOrderedSetMixin(object): @@ -1736,7 +1736,7 @@ def ord(self, item): class _OrderedSetData(metaclass=RenamedClass): __renamed__new_class__ = OrderedSetData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' class InsertionOrderSetData(OrderedSetData): @@ -1775,7 +1775,7 @@ def update(self, values): class _InsertionOrderSetData(metaclass=RenamedClass): __renamed__new_class__ = InsertionOrderSetData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' class _SortedSetMixin(object): @@ -1871,7 +1871,7 @@ def _sort(self): class _SortedSetData(metaclass=RenamedClass): __renamed__new_class__ = SortedSetData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' ############################################################################ @@ -2669,7 +2669,7 @@ def ranges(self): class _InfiniteRangeSetData(metaclass=RenamedClass): __renamed__new_class__ = InfiniteRangeSetData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' class FiniteRangeSetData( @@ -2782,7 +2782,7 @@ def ord(self, item): class _FiniteRangeSetData(metaclass=RenamedClass): __renamed__new_class__ = FiniteRangeSetData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' @ModelComponentFactory.register( diff --git a/pyomo/core/base/sos.py b/pyomo/core/base/sos.py index 4a8afb05d71..afd52c111bc 100644 --- a/pyomo/core/base/sos.py +++ b/pyomo/core/base/sos.py @@ -103,7 +103,7 @@ def set_items(self, variables, weights): class _SOSConstraintData(metaclass=RenamedClass): __renamed__new_class__ = SOSConstraintData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' @ModelComponentFactory.register("SOS constraint expressions.") diff --git a/pyomo/core/base/var.py b/pyomo/core/base/var.py index 8870fc5b09c..38d1d38a864 100644 --- a/pyomo/core/base/var.py +++ b/pyomo/core/base/var.py @@ -572,12 +572,12 @@ def _process_bound(self, val, bound_type): class _VarData(metaclass=RenamedClass): __renamed__new_class__ = VarData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' class _GeneralVarData(metaclass=RenamedClass): __renamed__new_class__ = VarData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' @ModelComponentFactory.register("Decision variables.") diff --git a/pyomo/core/expr/numvalue.py b/pyomo/core/expr/numvalue.py index 3b335bd5fc4..96e2f50b3f8 100644 --- a/pyomo/core/expr/numvalue.py +++ b/pyomo/core/expr/numvalue.py @@ -47,7 +47,7 @@ relocated_module_attribute( 'pyomo_constant_types', 'pyomo.common.numeric_types._pyomo_constant_types', - version='6.7.2.dev0', + version='6.7.2', f_globals=globals(), msg="The pyomo_constant_types set will be removed in the future: the set " "contained only NumericConstant and _PythonCallbackFunctionID, and provided " diff --git a/pyomo/gdp/disjunct.py b/pyomo/gdp/disjunct.py index 658ead27783..637f55cbed1 100644 --- a/pyomo/gdp/disjunct.py +++ b/pyomo/gdp/disjunct.py @@ -450,7 +450,7 @@ def _activate_without_unfixing_indicator(self): class _DisjunctData(metaclass=RenamedClass): __renamed__new_class__ = DisjunctData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' @ModelComponentFactory.register("Disjunctive blocks.") @@ -627,7 +627,7 @@ def set_value(self, expr): class _DisjunctionData(metaclass=RenamedClass): __renamed__new_class__ = DisjunctionData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' @ModelComponentFactory.register("Disjunction expressions.") diff --git a/pyomo/mpec/complementarity.py b/pyomo/mpec/complementarity.py index aa8db922145..26968ef9fca 100644 --- a/pyomo/mpec/complementarity.py +++ b/pyomo/mpec/complementarity.py @@ -181,7 +181,7 @@ def set_value(self, cc): class _ComplementarityData(metaclass=RenamedClass): __renamed__new_class__ = ComplementarityData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' @ModelComponentFactory.register("Complementarity conditions.") diff --git a/pyomo/network/arc.py b/pyomo/network/arc.py index 5e68f181a38..f2597b4c1bd 100644 --- a/pyomo/network/arc.py +++ b/pyomo/network/arc.py @@ -248,7 +248,7 @@ def _validate_ports(self, source, destination, ports): class _ArcData(metaclass=RenamedClass): __renamed__new_class__ = ArcData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' @ModelComponentFactory.register("Component used for connecting two Ports.") diff --git a/pyomo/network/port.py b/pyomo/network/port.py index ee5c915d8db..f6706dce644 100644 --- a/pyomo/network/port.py +++ b/pyomo/network/port.py @@ -287,7 +287,7 @@ def get_split_fraction(self, arc): class _PortData(metaclass=RenamedClass): __renamed__new_class__ = PortData - __renamed__version__ = '6.7.2.dev0' + __renamed__version__ = '6.7.2' @ModelComponentFactory.register( From 28c158c9dbfce3928d8d14afbc8fab2bd9017d4a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 9 May 2024 09:27:53 -0600 Subject: [PATCH 1792/1797] Finalizing release information --- .coin-or/projDesc.xml | 4 ++-- CHANGELOG.md | 26 ++++++++++++++------------ RELEASE.md | 9 +++++++-- pyomo/version/info.py | 4 ++-- 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/.coin-or/projDesc.xml b/.coin-or/projDesc.xml index da977677d1f..073efd968a7 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.1 - 6.7.1 + 6.7.2 + 6.7.2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 922e072250e..11b4ecbf785 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,7 @@ Pyomo 6.7.2 (9 May 2024) - Subprocess timeout update (#3183) - Solver Refactor - Bug fixes for various components (#3181, #3214, #3228) - NLv2: handle presolved independent linear subsystems (#3193) - - Update `LegacySolverWrapper` to be compatible with the `pyomo` script (#3202) + - Update `LegacySolverWrapper` compatibility with the `pyomo` script (#3202) - Fix mosek_direct to use putqconk instead of putqcon (#3199) - Check _skip_trivial_constraints before the constraint body (#3226) - Fix AMPL solver duplicate funcadd (#3206) @@ -43,37 +43,39 @@ Pyomo 6.7.2 (9 May 2024) - Set maxDiff=None on the base TestCase class (#3171) - Testing infrastructure updates (#3175) - Typos update for March 2024 (#3219) - - Add openmpi to testing environment to work around issue in mpi4py (#3236, #3239) + - Add openmpi to testing environment to resolve issue in mpi4py (#3236, #3239) - Skip black 24.4.1 due to a bug in the parser (#3247) - Skip tests on draft and WIP pull requests (#3223) - Update GHA to grab gurobipy from PyPI (#3254) - GDP - - Use private_data for all mappings between original and transformed components (#3166) + - Use private_data for all original / transformed component mappings (#3166) - Fix a bug in gdp.bigm transformation for nested GDPs (#3213) - Contributed Packages - - APPSI: Allow cmodel to handle non-mutable params in var and constraint bounds (#3182) + - APPSI: cmodel: handle non-mutable params in var / constraint bounds (#3182) - APPSI: Allow APPSI FBBT to handle nested named Expressions (#3185) - APPSI: Add MAiNGO solver interface (#3165) - CP: Add SequenceVar and other logical expressions for scheduling (#3227) - DoE: Bug fixes (#3245) - - incidence_analysis: Improve performance of `solve_strongly_connected_components` for - models with named expressions (#3186) - - incidence_analysis: Add function to plot incidence graph in Dulmage-Mendelsohn order (#3207) - - incidence_analysis: Require variables and constraints to be specified separately in - `IncidenceGraphInterface.remove_nodes` (#3212) - - latex_printer: Resolve errors for set operations / multidimensional sets (#3177) + - iis: Add minimal intractable system infeasibility diagnostics (#3172) + - incidence_analysis: Improve `solve_strongly_connected_components` + performance for models with named expressions (#3186) + - incidence_analysis: Add function to plot incidence graph in + Dulmage-Mendelsohn order (#3207) + - incidence_analysis: Require variables and constraints to be specified + separately in `IncidenceGraphInterface.remove_nodes` (#3212) + - latex_printer: bugfix for set operations / multidimensional sets (#3177) - MindtPy: Add HiGHS support (#2971) - MindtPy: Add call_before_subproblem_solve callback (#3251) - Parmest: New UI using experiment lists (#3160) - piecewise: Add piecewise linear transformations (#3036) - - preprocessing: Fix bug where variable aggregator did not intersect domains (#3241) + - preprocessing: bugfix: intersect domains in variable aggregator (#3241) - PyNumero: Allow CyIpopt to solve problems without objectives (#3163) - PyNumero: Work around bug in CyIpopt 1.4.0 (#3222) - PyNumero: Include "inventory" in readme (#3248) - PyROS: Simplify custom domain validators (#3169) - PyROS: Fix iteration logging for edge case involving discrete sets (#3170) - PyROS: Update solver timing system (#3198) - - simplification: New module for expression simplification using GiNaC or SymPy (#3088) + - simplification: expression simplification using GiNaC or SymPy (#3088) ------------------------------------------------------------------------------- Pyomo 6.7.1 (21 Feb 2024) diff --git a/RELEASE.md b/RELEASE.md index 9b101e0999a..b0228e53944 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,4 +1,4 @@ -We are pleased to announce the release of Pyomo 6.7.1. +We are pleased to announce the release of Pyomo 6.7.2. Pyomo is a collection of Python software packages that supports a diverse set of optimization capabilities for formulating and analyzing @@ -10,9 +10,14 @@ The following are highlights of the 6.7 release series: - Removed support for Python 3.7 - New writer for converting linear models to matrix form - Improved handling of nested GDPs + - Redesigned user API for parameter estimation - New packages: - - latex_printer (print Pyomo models to a LaTeX compatible format) + - iis: new capability for identifying minimal intractable systems + - latex_printer: print Pyomo models to a LaTeX compatible format - contrib.solver: preview of redesigned solver interfaces + - simplification: simplify Pyomo expressions + - New solver interfaces + - MAiNGO: Mixed-integer nonlinear global optimization - ...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 de2efe83fb6..b3538ad5868 100644 --- a/pyomo/version/info.py +++ b/pyomo/version/info.py @@ -27,8 +27,8 @@ major = 6 minor = 7 micro = 2 -releaselevel = 'invalid' -# releaselevel = 'final' +# releaselevel = 'invalid' +releaselevel = 'final' serial = 0 if releaselevel == 'final': From d5e5136b317603f0fe0e25b1d31457e25388212f Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Thu, 9 May 2024 11:05:29 -0600 Subject: [PATCH 1793/1797] Unpinning numpy version requirement --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 2e8cf1d5095..a125b02b2fe 100644 --- a/setup.py +++ b/setup.py @@ -256,7 +256,7 @@ def __ne__(self, other): 'sphinx-toolbox>=2.16.0', 'sphinx-jinja2-compat>=0.1.1', 'enum_tools', - 'numpy<2.0.0', # Needed by autodoc for pynumero + 'numpy', # Needed by autodoc for pynumero 'scipy', # Needed by autodoc for pynumero ], 'optional': [ @@ -271,7 +271,7 @@ def __ne__(self, other): # installed on python 3.8 'networkx<3.2; python_version<"3.9"', 'networkx; python_version>="3.9"', - 'numpy<2.0.0', + 'numpy', 'openpyxl', # dataportals #'pathos', # requested for #963, but PR currently closed 'pint', # units From 167f2b14bf4c9d6a719f08a343128880bb04342c Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Thu, 9 May 2024 11:06:37 -0600 Subject: [PATCH 1794/1797] Resetting main for development (6.7.3.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 b3538ad5868..2d50dfe7b5e 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 = 2 -# releaselevel = 'invalid' -releaselevel = 'final' +micro = 3 +releaselevel = 'invalid' +#releaselevel = 'final' serial = 0 if releaselevel == 'final': From 64a96147500cf0e71de8f955b3dfa92bf370799b Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Thu, 9 May 2024 11:10:11 -0600 Subject: [PATCH 1795/1797] Update info.py --- 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 2d50dfe7b5e..36945e8e011 100644 --- a/pyomo/version/info.py +++ b/pyomo/version/info.py @@ -28,7 +28,7 @@ minor = 7 micro = 3 releaselevel = 'invalid' -#releaselevel = 'final' +# releaselevel = 'final' serial = 0 if releaselevel == 'final': From 3f58bf632c22a139be074b4d98dbf0f4585cd115 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Sun, 12 May 2024 19:14:57 -0400 Subject: [PATCH 1796/1797] change constraint_violation_tolerance to config.constraint_tolerance --- pyomo/contrib/mindtpy/cut_generation.py | 4 ++-- pyomo/contrib/mindtpy/single_tree.py | 4 ++-- pyomo/contrib/mindtpy/util.py | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pyomo/contrib/mindtpy/cut_generation.py b/pyomo/contrib/mindtpy/cut_generation.py index e932755e9fd..aa7894eab18 100644 --- a/pyomo/contrib/mindtpy/cut_generation.py +++ b/pyomo/contrib/mindtpy/cut_generation.py @@ -109,7 +109,7 @@ def add_oa_cuts( constr.has_ub() and ( linearize_active - and abs(constr.uslack()) < config.zero_tolerance + and abs(constr.uslack()) < config.constraint_tolerance ) or (linearize_violated and constr.uslack() < 0) or (config.linearize_inactive and constr.uslack() > 0) @@ -147,7 +147,7 @@ def add_oa_cuts( constr.has_lb() and ( linearize_active - and abs(constr.lslack()) < config.zero_tolerance + and abs(constr.lslack()) < config.constraint_tolerance ) or (linearize_violated and constr.lslack() < 0) or (config.linearize_inactive and constr.lslack() > 0) diff --git a/pyomo/contrib/mindtpy/single_tree.py b/pyomo/contrib/mindtpy/single_tree.py index 6b501ef874d..719bd41b7b3 100644 --- a/pyomo/contrib/mindtpy/single_tree.py +++ b/pyomo/contrib/mindtpy/single_tree.py @@ -162,7 +162,7 @@ def add_lazy_oa_cuts( constr.has_ub() and ( linearize_active - and abs(constr.uslack()) < config.zero_tolerance + and abs(constr.uslack()) < config.constraint_tolerance ) or (linearize_violated and constr.uslack() < 0) or (config.linearize_inactive and constr.uslack() > 0) @@ -201,7 +201,7 @@ def add_lazy_oa_cuts( constr.has_lb() and ( linearize_active - and abs(constr.lslack()) < config.zero_tolerance + and abs(constr.lslack()) < config.constraint_tolerance ) or (linearize_violated and constr.lslack() < 0) or (config.linearize_inactive and constr.lslack() > 0) diff --git a/pyomo/contrib/mindtpy/util.py b/pyomo/contrib/mindtpy/util.py index 7345af8a3e2..225e2e24d42 100644 --- a/pyomo/contrib/mindtpy/util.py +++ b/pyomo/contrib/mindtpy/util.py @@ -580,11 +580,11 @@ def set_solver_constraint_violation_tolerance( The specific configurations for MindtPy. """ if solver_name == 'baron': - opt.options['AbsConFeasTol'] = config.zero_tolerance + opt.options['AbsConFeasTol'] = config.constraint_tolerance elif solver_name in {'ipopt', 'appsi_ipopt'}: - opt.options['constr_viol_tol'] = config.zero_tolerance + opt.options['constr_viol_tol'] = config.constraint_tolerance elif solver_name == 'cyipopt': - opt.config.options['constr_viol_tol'] = config.zero_tolerance + opt.config.options['constr_viol_tol'] = config.constraint_tolerance elif solver_name == 'gams': if config.nlp_solver_args['solver'] in { 'ipopt', @@ -599,7 +599,7 @@ def set_solver_constraint_violation_tolerance( ) if config.nlp_solver_args['solver'] in {'ipopt', 'ipopth'}: opt.options['add_options'].append( - 'constr_viol_tol ' + str(config.zero_tolerance) + 'constr_viol_tol ' + str(config.constraint_tolerance) ) if warm_start: # Ipopt warmstart options @@ -613,15 +613,15 @@ def set_solver_constraint_violation_tolerance( ) elif config.nlp_solver_args['solver'] == 'conopt': opt.options['add_options'].append( - 'RTNWMA ' + str(config.zero_tolerance) + 'RTNWMA ' + str(config.constraint_tolerance) ) elif config.nlp_solver_args['solver'] == 'msnlp': opt.options['add_options'].append( - 'feasibility_tolerance ' + str(config.zero_tolerance) + 'feasibility_tolerance ' + str(config.constraint_tolerance) ) elif config.nlp_solver_args['solver'] == 'baron': opt.options['add_options'].append( - 'AbsConFeasTol ' + str(config.zero_tolerance) + 'AbsConFeasTol ' + str(config.constraint_tolerance) ) opt.options['add_options'].append('$offecho') From e04a0e95e96989f5e2e34a31c59d52ab1f31f9ca Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Sun, 12 May 2024 19:20:45 -0400 Subject: [PATCH 1797/1797] add warm_start_fixed_nlp config --- pyomo/contrib/mindtpy/algorithm_base_class.py | 5 ++++- pyomo/contrib/mindtpy/config_options.py | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index e015fc89e09..2a6ba052694 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -2663,7 +2663,10 @@ def initialize_subsolvers(self): set_solver_mipgap(self.mip_opt, config.mip_solver, config) set_solver_constraint_violation_tolerance( - self.nlp_opt, config.nlp_solver, config + self.nlp_opt, + config.nlp_solver, + config, + warm_start=config.warm_start_fixed_nlp, ) set_solver_constraint_violation_tolerance( self.feasibility_nlp_opt, config.nlp_solver, config, warm_start=False diff --git a/pyomo/contrib/mindtpy/config_options.py b/pyomo/contrib/mindtpy/config_options.py index 5d265e72cf6..a5b3e54844d 100644 --- a/pyomo/contrib/mindtpy/config_options.py +++ b/pyomo/contrib/mindtpy/config_options.py @@ -647,6 +647,14 @@ def _add_subsolver_configs(CONFIG): doc='Which MIP subsolver is going to be used for solving the regularization problem.', ), ) + CONFIG.declare( + 'warm_start_fixed_nlp', + ConfigValue( + default=True, + description='whether to warm start the fixed NLP subproblem.', + domain=bool, + ), + ) def _add_tolerance_configs(CONFIG):