From 3a40664bc73892c54955ce1f2c11e6596bdf07f9 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Fri, 7 Jul 2023 22:38:10 -0600 Subject: [PATCH 001/148] 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 002/148] 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 003/148] 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 004/148] 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 005/148] 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 006/148] 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 26375cb1e5363daddd4dc1d896ba6c7f54854912 Mon Sep 17 00:00:00 2001 From: Michael Bynum Date: Fri, 18 Aug 2023 08:33:56 -0600 Subject: [PATCH 007/148] update workflows --- .github/workflows/test_branches.yml | 2 +- .github/workflows/test_pr_and_main.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 69028e55a17..1c865462f60 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -280,7 +280,7 @@ jobs: || echo "WARNING: Gurobi is not available" python -m pip install --cache-dir cache/pip xpress \ || echo "WARNING: Xpress Community Edition is not available" - python -m pip install git+https://github.com/michaelbynum/wntr.git@working \ + python -m pip install git+https://github.com/usepa/wntr.git@main \ || echo "WARNING: WNTR is not available" fi python -c 'import sys; print("PYTHON_EXE=%s" \ diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 357a2fe866e..547e7972aa6 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -298,7 +298,7 @@ jobs: || echo "WARNING: Gurobi is not available" python -m pip install --cache-dir cache/pip xpress \ || echo "WARNING: Xpress Community Edition is not available" - python -m pip install git+https://github.com/michaelbynum/wntr.git@working \ + python -m pip install git+https://github.com/usepa/wntr.git@main \ || echo "WARNING: WNTR is not available" fi python -c 'import sys; print("PYTHON_EXE=%s" \ From bb03a18464b6f5f1f62dc696db2bed3686433d74 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Mon, 21 Aug 2023 16:54:28 -0400 Subject: [PATCH 008/148] fix load_state_into_pyomo bug --- pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py index 9a5dc50ef7b..fe3e62eb497 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py @@ -426,7 +426,7 @@ def load_state_into_pyomo(self, bound_multipliers=None): # not we are minimizing or maximizing - this is done in the ASL interface # for ipopt, but does not appear to be done in cyipopt. obj_sign = 1.0 - objs = list(m.component_data_objects(ctype=pyo.Objective, descend_into=True)) + objs = list(m.component_data_objects(ctype=pyo.Objective, active=True, descend_into=True)) assert len(objs) == 1 if objs[0].sense == pyo.maximize: obj_sign = -1.0 From 23cc0d69b6bce53f7f19984f56a3da03bd0cc4bd Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Tue, 29 Aug 2023 08:30:15 -0600 Subject: [PATCH 009/148] 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: Thu, 31 Aug 2023 13:39:38 -0600 Subject: [PATCH 010/148] 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 011/148] 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 a697ff14e00f6ca15c956e9ab9726f36fd8e2d08 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Thu, 31 Aug 2023 15:39:03 -0600 Subject: [PATCH 012/148] 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 013/148] 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: Fri, 1 Sep 2023 05:01:30 -0600 Subject: [PATCH 014/148] 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 015/148] 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 016/148] adding black --- pyomo/util/latex_printer.py | 16 ++++++++-------- pyomo/util/tests/test_latex_printer.py | 20 +++++++++++++------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index 765a4791ff2..f22c6d0d12f 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -74,9 +74,10 @@ def handle_negation_node(node, arg1): if childPrecedence[0] > precedence: arg1 = ' \\left( ' + arg1 + ' \\right) ' - + return '-' + arg1 + def handle_product_node(node, arg1, arg2): childPrecedence = [] for a in node.args: @@ -129,8 +130,8 @@ def handle_pow_node(node, arg1, arg2): arg1 = ' \\left( ' + arg1 + ' \\right) ' if childPrecedence[1] > precedence: - arg2 = ' \\left( ' + arg2 + ' \\right) ' - + arg2 = ' \\left( ' + arg2 + ' \\right) ' + return "%s^{%s}" % (arg1, arg2) @@ -166,7 +167,7 @@ def handle_var_node(node): prfx = '' psfx = '' - for i in range(0,len(splitName)): + for i in range(0, len(splitName)): se = splitName[i] if se != 0: if se == 'dot': @@ -183,12 +184,11 @@ def handle_var_node(node): else: filteredName.append(se) - - joinedName = prfx + filteredName[0] + psfx - for i in range(1,len(filteredName)): + joinedName = prfx + filteredName[0] + psfx + for i in range(1, len(filteredName)): joinedName += '_{' + filteredName[i] - joinedName += '}'*(len(filteredName)-1) + joinedName += '}' * (len(filteredName) - 1) return joinedName diff --git a/pyomo/util/tests/test_latex_printer.py b/pyomo/util/tests/test_latex_printer.py index 80bb0ee6b34..c2e2036ccf2 100644 --- a/pyomo/util/tests/test_latex_printer.py +++ b/pyomo/util/tests/test_latex_printer.py @@ -101,18 +101,25 @@ def ruleMaker(m): def generate_simple_model_2(): import pyomo.environ as pe - - m = pe.ConcreteModel(name = 'basicFormulation') + + m = pe.ConcreteModel(name='basicFormulation') m.x_dot = pe.Var() m.x_bar = pe.Var() m.x_star = pe.Var() m.x_hat = pe.Var() m.x_hat_1 = pe.Var() m.y_sub1_sub2_sub3 = pe.Var() - m.objective_1 = pe.Objective( expr = m.y_sub1_sub2_sub3 ) - m.constraint_1 = pe.Constraint(expr = (m.x_dot + m.x_bar + m.x_star + m.x_hat + m.x_hat_1)**2 <= m.y_sub1_sub2_sub3 ) - m.constraint_2 = pe.Constraint(expr = (m.x_dot + m.x_bar)**-(m.x_star + m.x_hat) <= m.y_sub1_sub2_sub3 ) - m.constraint_3 = pe.Constraint(expr = -(m.x_dot + m.x_bar)+ -(m.x_star + m.x_hat) <= m.y_sub1_sub2_sub3 ) + m.objective_1 = pe.Objective(expr=m.y_sub1_sub2_sub3) + m.constraint_1 = pe.Constraint( + expr=(m.x_dot + m.x_bar + m.x_star + m.x_hat + m.x_hat_1) ** 2 + <= m.y_sub1_sub2_sub3 + ) + m.constraint_2 = pe.Constraint( + expr=(m.x_dot + m.x_bar) ** -(m.x_star + m.x_hat) <= m.y_sub1_sub2_sub3 + ) + m.constraint_3 = pe.Constraint( + expr=-(m.x_dot + m.x_bar) + -(m.x_star + m.x_hat) <= m.y_sub1_sub2_sub3 + ) return m @@ -338,6 +345,5 @@ def test_latexPrinter_advancedVariables(self): self.assertEqual(pstr, bstr) - if __name__ == '__main__': unittest.main() From 6526d77ddcf380dc95700c17a1534c8dd0077c96 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Tue, 5 Sep 2023 21:48:26 -0600 Subject: [PATCH 017/148] 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 018/148] 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 019/148] 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 020/148] 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 51c8a71ea74de86abf6f031a3a678d82a654e8e4 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Thu, 7 Sep 2023 18:44:29 -0600 Subject: [PATCH 021/148] adding variable bound capability --- pyomo/util/latex_printer.py | 957 +++++++++++++++++++++--------------- 1 file changed, 570 insertions(+), 387 deletions(-) diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index dc73930cbeb..a23ae3fdfba 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -10,6 +10,8 @@ # ___________________________________________________________________________ import math +import copy +import re import pyomo.environ as pyo from pyomo.core.expr.visitor import StreamBasedExpressionVisitor from pyomo.core.expr import ( @@ -44,7 +46,10 @@ templatize_rule, ) from pyomo.core.base.var import ScalarVar, _GeneralVarData, IndexedVar +from pyomo.core.base.param import _ParamData +from pyomo.core.base.set import _SetData from pyomo.core.base.constraint import ScalarConstraint, IndexedConstraint +from pyomo.common.collections.component_map import ComponentMap from pyomo.core.base.external import _PythonCallbackFunctionID @@ -218,66 +223,68 @@ def handle_inequality_node(visitor, node, arg1, arg2): def handle_var_node(visitor, node): - overwrite_dict = visitor.overwrite_dict - # varList = visitor.variableList - - name = node.name - - declaredIndex = None - if '[' in name: - openBracketIndex = name.index('[') - closeBracketIndex = name.index(']') - if closeBracketIndex != len(name) - 1: - # I dont think this can happen, but possibly through a raw string and a user - # who is really hacking the variable name setter - raise ValueError( - 'Variable %s has a close brace not at the end of the string' % (name) - ) - declaredIndex = name[openBracketIndex + 1 : closeBracketIndex] - name = name[0:openBracketIndex] - - if name in overwrite_dict.keys(): - name = overwrite_dict[name] - - if visitor.use_smart_variables: - splitName = name.split('_') - if declaredIndex is not None: - splitName.append(declaredIndex) - - filteredName = [] - - prfx = '' - psfx = '' - for i in range(0, len(splitName)): - se = splitName[i] - if se != 0: - if se == 'dot': - prfx = '\\dot{' - psfx = '}' - elif se == 'hat': - prfx = '\\hat{' - psfx = '}' - elif se == 'bar': - prfx = '\\bar{' - psfx = '}' - else: - filteredName.append(se) - else: - filteredName.append(se) - - joinedName = prfx + filteredName[0] + psfx - for i in range(1, len(filteredName)): - joinedName += '_{' + filteredName[i] - - joinedName += '}' * (len(filteredName) - 1) - - else: - if declaredIndex is not None: - joinedName = name + '[' + declaredIndex + ']' - else: - joinedName = name - - return joinedName + return visitor.variableMap[node] + # return node.name + # overwrite_dict = visitor.overwrite_dict + # # varList = visitor.variableList + + # name = node.name + + # declaredIndex = None + # if '[' in name: + # openBracketIndex = name.index('[') + # closeBracketIndex = name.index(']') + # if closeBracketIndex != len(name) - 1: + # # I dont think this can happen, but possibly through a raw string and a user + # # who is really hacking the variable name setter + # raise ValueError( + # 'Variable %s has a close brace not at the end of the string' % (name) + # ) + # declaredIndex = name[openBracketIndex + 1 : closeBracketIndex] + # name = name[0:openBracketIndex] + + # if name in overwrite_dict.keys(): + # name = overwrite_dict[name] + + # if visitor.use_smart_variables: + # splitName = name.split('_') + # if declaredIndex is not None: + # splitName.append(declaredIndex) + + # filteredName = [] + + # prfx = '' + # psfx = '' + # for i in range(0, len(splitName)): + # se = splitName[i] + # if se != 0: + # if se == 'dot': + # prfx = '\\dot{' + # psfx = '}' + # elif se == 'hat': + # prfx = '\\hat{' + # psfx = '}' + # elif se == 'bar': + # prfx = '\\bar{' + # psfx = '}' + # else: + # filteredName.append(se) + # else: + # filteredName.append(se) + + # joinedName = prfx + filteredName[0] + psfx + # for i in range(1, len(filteredName)): + # joinedName += '_{' + filteredName[i] + + # joinedName += '}' * (len(filteredName) - 1) + + # else: + # if declaredIndex is not None: + # joinedName = name + '[' + declaredIndex + ']' + # else: + # joinedName = name + + # return joinedName def handle_num_node(visitor, node): @@ -355,16 +362,16 @@ def handle_indexTemplate_node(visitor, node, *args): def handle_numericGIE_node(visitor, node, *args): - addFinalBrace = False - if '_' in args[0]: - splitName = args[0].split('_') - joinedName = splitName[0] - for i in range(1, len(splitName)): - joinedName += '_{' + splitName[i] - joinedName += '}' * (len(splitName) - 2) - addFinalBrace = True - else: - joinedName = args[0] + # addFinalBrace = False + # if '_' in args[0]: + # splitName = args[0].split('_') + # joinedName = splitName[0] + # for i in range(1, len(splitName)): + # joinedName += '_{' + splitName[i] + # joinedName += '}' * (len(splitName) - 2) + # addFinalBrace = True + # else: + joinedName = args[0] pstr = '' pstr += joinedName + '_{' @@ -374,8 +381,8 @@ def handle_numericGIE_node(visitor, node, *args): pstr += ',' else: pstr += '}' - if addFinalBrace: - pstr += '}' + # if addFinalBrace: + # pstr += '}' return pstr @@ -392,9 +399,6 @@ def handle_templateSumExpression_node(visitor, node, *args): class _LatexVisitor(StreamBasedExpressionVisitor): def __init__(self): super().__init__() - self.use_smart_variables = False - self.x_only_mode = False - self.overwrite_dict = {} self._operator_handles = { ScalarVar: handle_var_node, @@ -433,47 +437,237 @@ def exitNode(self, node, data): return self._operator_handles[node.__class__](self, node, *data) -def number_to_letterStack(num): - alphabet = [ - 'a', - 'b', - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i', - 'j', - 'k', - 'l', - 'm', - 'n', - 'o', - 'p', - 'q', - 'r', - 's', - 't', - 'u', - 'v', - 'w', - 'x', - 'y', - 'z', - ] +def applySmartVariables(name): + splitName = name.split('_') + # print(splitName) + + filteredName = [] + + prfx = '' + psfx = '' + for i in range(0, len(splitName)): + se = splitName[i] + if se != 0: + if se == 'dot': + prfx = '\\dot{' + psfx = '}' + elif se == 'hat': + prfx = '\\hat{' + psfx = '}' + elif se == 'bar': + prfx = '\\bar{' + psfx = '}' + else: + filteredName.append(se) + else: + filteredName.append(se) + + joinedName = prfx + filteredName[0] + psfx + # print(joinedName) + # print(filteredName) + for i in range(1, len(filteredName)): + joinedName += '_{' + filteredName[i] + + joinedName += '}' * (len(filteredName) - 1) + # print(joinedName) + + return joinedName + +def analyze_variable(vr, visitor): + domainMap = { + 'Reals': '\\mathds{R}', + 'PositiveReals': '\\mathds{R}_{> 0}', + 'NonPositiveReals': '\\mathds{R}_{\\leq 0}', + 'NegativeReals': '\\mathds{R}_{< 0}', + 'NonNegativeReals': '\\mathds{R}_{\\geq 0}', + 'Integers': '\\mathds{Z}', + 'PositiveIntegers': '\\mathds{Z}_{> 0}', + 'NonPositiveIntegers': '\\mathds{Z}_{\\leq 0}', + 'NegativeIntegers': '\\mathds{Z}_{< 0}', + 'NonNegativeIntegers': '\\mathds{Z}_{\\geq 0}', + 'Boolean': '\\left\\{ 0 , 1 \\right \\}', + 'Binary': '\\left\\{ 0 , 1 \\right \\}', + # 'Any': None, + # 'AnyWithNone': None, + 'EmptySet': '\\varnothing', + 'UnitInterval': '\\mathds{R}', + 'PercentFraction': '\\mathds{R}', + # 'RealInterval' : None , + # 'IntegerInterval' : None , + } + + domainName = vr.domain.name + varBounds = vr.bounds + lowerBoundValue = varBounds[0] + upperBoundValue = varBounds[1] + + if domainName in ['Reals', 'Integers']: + if lowerBoundValue is not None: + lowerBound = str(lowerBoundValue) + ' \\leq ' + else: + lowerBound = '' + + if upperBoundValue is not None: + upperBound = ' \\leq ' + str(upperBoundValue) + else: + upperBound = '' + + elif domainName in ['PositiveReals', 'PositiveIntegers']: + if lowerBoundValue is not None: + if lowerBoundValue > 0: + lowerBound = str(lowerBoundValue) + ' \\leq ' + else: + lowerBound = ' 0 < ' + else: + lowerBound = ' 0 < ' + + if upperBoundValue is not None: + if upperBoundValue <= 0: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' + % (vr.name) + ) + else: + upperBound = ' \\leq ' + str(upperBoundValue) + else: + upperBound = '' + + elif domainName in ['NonPositiveReals', 'NonPositiveIntegers']: + if lowerBoundValue is not None: + if lowerBoundValue > 0: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' + % (vr.name) + ) + elif lowerBoundValue == 0: + lowerBound = ' 0 = ' + else: + lowerBound = str(upperBoundValue) + ' \\leq ' + else: + lowerBound = '' + + if upperBoundValue is not None: + if upperBoundValue >= 0: + upperBound = ' \\leq 0 ' + else: + upperBound = ' \\leq ' + str(upperBoundValue) + else: + upperBound = ' \\leq 0 ' + + elif domainName in ['NegativeReals', 'NegativeIntegers']: + if lowerBoundValue is not None: + if lowerBoundValue >= 0: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' + % (vr.name) + ) + else: + lowerBound = str(upperBoundValue) + ' \\leq ' + else: + lowerBound = '' + + if upperBoundValue is not None: + if upperBoundValue >= 0: + upperBound = ' < 0 ' + else: + upperBound = ' \\leq ' + str(upperBoundValue) + else: + upperBound = ' < 0 ' + + elif domainName in ['NonNegativeReals', 'NonNegativeIntegers']: + if lowerBoundValue is not None: + if lowerBoundValue > 0: + lowerBound = str(lowerBoundValue) + ' \\leq ' + else: + lowerBound = ' 0 \\leq ' + else: + lowerBound = ' 0 \\leq ' + + if upperBoundValue is not None: + if upperBoundValue < 0: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' + % (vr.name) + ) + elif upperBoundValue == 0: + upperBound = ' = 0 ' + else: + upperBound = ' \\leq ' + str(upperBoundValue) + else: + upperBound = '' + + elif domainName in [ + 'Boolean', + 'Binary', + 'Any', + 'AnyWithNone', + 'EmptySet', + ]: + lowerBound = '' + upperBound = '' + + elif domainName in ['UnitInterval', 'PercentFraction']: + if lowerBoundValue is not None: + if lowerBoundValue > 0: + lowerBound = str(lowerBoundValue) + ' \\leq ' + elif lowerBoundValue > 1: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' + % (vr.name) + ) + elif lowerBoundValue == 1: + lowerBound = ' = 1 ' + else: + lowerBound = ' 0 \\leq ' + else: + lowerBound = ' 0 \\leq ' + + if upperBoundValue is not None: + if upperBoundValue < 1: + upperBound = ' \\leq ' + str(upperBoundValue) + elif upperBoundValue < 0: + raise ValueError( + 'Formulation is infeasible due to bounds on variable %s' + % (vr.name) + ) + elif upperBoundValue == 0: + upperBound = ' = 0 ' + else: + upperBound = ' \\leq 1 ' + else: + upperBound = ' \\leq 1 ' + + else: + raise ValueError( + 'Domain %s not supported by the latex printer' % (domainName) + ) + + varBoundData = { + 'variable' : vr, + 'lowerBound' : lowerBound, + 'upperBound' : upperBound, + 'domainName' : domainName, + 'domainLatex' : domainMap[domainName], + } + + return varBoundData + + +def multiple_replace(pstr, rep_dict): + pattern = re.compile("|".join(rep_dict.keys()), flags=re.DOTALL) + return pattern.sub(lambda x: rep_dict[x.group(0)], pstr) def latex_printer( pyomo_component, filename=None, - use_align_environment=False, + use_equation_environment=False, split_continuous_sets=False, use_smart_variables=False, x_only_mode=0, use_short_descriptors=False, use_forall=False, - overwrite_dict={}, + overwrite_dict=None, ): """This function produces a string that can be rendered as LaTeX @@ -487,15 +681,15 @@ def latex_printer( filename: str An optional file to write the LaTeX to. Default of None produces no file - use_align_environment: bool + use_equation_environment: bool Default behavior uses equation/aligned and produces a single LaTeX Equation (ie, ==False). Setting this input to True will instead use the align environment, and produce equation numbers for each objective and constraint. Each objective and constraint will be labeled with its name in the pyomo model. This flag is only relevant for Models and Blocks. splitContinuous: bool - Default behavior has all sum indices be over "i \in I" or similar. Setting this flag to - True makes the sums go from: \sum_{i=1}^{5} if the set I is continuous and has 5 elements + Default behavior has all sum indices be over "i \\in I" or similar. Setting this flag to + True makes the sums go from: \\sum_{i=1}^{5} if the set I is continuous and has 5 elements Returns ------- @@ -509,6 +703,10 @@ def latex_printer( # is Single implies Objective, constraint, or expression # these objects require a slight modification of behavior # isSingle==False means a model or block + + if overwrite_dict is None: + overwrite_dict = ComponentMap() + isSingle = False if isinstance(pyomo_component, pyo.Objective): @@ -516,7 +714,7 @@ def latex_printer( constraints = [] expressions = [] templatize_fcn = templatize_constraint - use_align_environment = False + use_equation_environment = True isSingle = True elif isinstance(pyomo_component, pyo.Constraint): @@ -524,7 +722,7 @@ def latex_printer( constraints = [pyomo_component] expressions = [] templatize_fcn = templatize_constraint - use_align_environment = False + use_equation_environment = True isSingle = True elif isinstance(pyomo_component, pyo.Expression): @@ -532,7 +730,7 @@ def latex_printer( constraints = [] expressions = [pyomo_component] templatize_fcn = templatize_expression - use_align_environment = False + use_equation_environment = True isSingle = True elif isinstance(pyomo_component, (ExpressionBase, pyo.Var)): @@ -540,7 +738,7 @@ def latex_printer( constraints = [] expressions = [pyomo_component] templatize_fcn = templatize_passthrough - use_align_environment = False + use_equation_environment = True isSingle = True elif isinstance(pyomo_component, _BlockData): @@ -566,94 +764,56 @@ def latex_printer( ) if use_forall: - forallTag = ' \\forall' + forallTag = ' \\qquad \\forall' else: - forallTag = ', \\quad' + forallTag = ' \\quad' descriptorDict = {} if use_short_descriptors: descriptorDict['minimize'] = '\\min' descriptorDict['maximize'] = '\\max' descriptorDict['subject to'] = '\\text{s.t.}' + descriptorDict['with bounds'] = '\\text{w.b.}' else: descriptorDict['minimize'] = '\\text{minimize}' descriptorDict['maximize'] = '\\text{maximize}' descriptorDict['subject to'] = '\\text{subject to}' + descriptorDict['with bounds'] = '\\text{with bounds}' # In the case where just a single expression is passed, add this to the constraint list for printing constraints = constraints + expressions # Declare a visitor/walker visitor = _LatexVisitor() - visitor.use_smart_variables = use_smart_variables - # visitor.x_only_mode = x_only_mode - # # Only x modes - # # Mode 0 : dont use - # # Mode 1 : indexed variables become x_{_{ix}} - # # Mode 2 : uses standard alphabet [a,...,z,aa,...,az,...,aaa,...] with subscripts for indices, ex: abcd_{ix} - # # Mode 3 : unwrap everything into an x_{} list, WILL NOT WORK ON TEMPLATED CONSTRAINTS - nameReplacementDict = {} - if not isSingle: - # only works if you can get the variables from a block - variableList = [ - vr - for vr in pyomo_component.component_objects( - pyo.Var, descend_into=True, active=True - ) - ] - if x_only_mode == 1: - newVariableList = ['x' for i in range(0, len(variableList))] - for i in range(0, len(variableList)): - newVariableList[i] += '_' + str(i + 1) - overwrite_dict = dict(zip([v.name for v in variableList], newVariableList)) - elif x_only_mode == 2: - newVariableList = [ - alphabetStringGenerator(i) for i in range(0, len(variableList)) - ] - overwrite_dict = dict(zip([v.name for v in variableList], newVariableList)) - elif x_only_mode == 3: - newVariableList = ['x' for i in range(0, len(variableList))] - for i in range(0, len(variableList)): - newVariableList[i] += '_' + str(i + 1) - overwrite_dict = dict(zip([v.name for v in variableList], newVariableList)) - - unwrappedVarCounter = 0 - wrappedVarCounter = 0 - for v in variableList: - setData = v.index_set().data() - if setData[0] is None: - unwrappedVarCounter += 1 - wrappedVarCounter += 1 - nameReplacementDict['x_{' + str(wrappedVarCounter) + '}'] = ( - 'x_{' + str(unwrappedVarCounter) + '}' - ) - else: - wrappedVarCounter += 1 - for dta in setData: - dta_str = str(dta) - if '(' not in dta_str: - dta_str = '(' + dta_str + ')' - subsetString = dta_str.replace('(', '{') - subsetString = subsetString.replace(')', '}') - subsetString = subsetString.replace(' ', '') - unwrappedVarCounter += 1 - nameReplacementDict[ - 'x_{' + str(wrappedVarCounter) + '_' + subsetString + '}' - ] = ('x_{' + str(unwrappedVarCounter) + '}') - # for ky, vl in nameReplacementDict.items(): - # print(ky,vl) - # print(nameReplacementDict) + variableList = [ + vr + for vr in pyomo_component.component_objects( + pyo.Var, descend_into=True, active=True + ) + ] + + variableMap = ComponentMap() + vrIdx = 0 + for i in range(0,len(variableList)): + vr = variableList[i] + vrIdx += 1 + if isinstance(vr ,ScalarVar): + variableMap[vr] = 'x_' + str(vrIdx) + elif isinstance(vr, IndexedVar): + variableMap[vr] = 'x_' + str(vrIdx) + for sd in vr.index_set().data(): + vrIdx += 1 + variableMap[vr[sd]] = 'x_' + str(vrIdx) else: - # default to the standard mode where pyomo names are used - overwrite_dict = {} + raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers') - visitor.overwrite_dict = overwrite_dict + visitor.variableMap = variableMap # starts building the output string pstr = '' - if use_align_environment: + if not use_equation_environment: pstr += '\\begin{align} \n' tbSpc = 4 trailingAligner = '& ' @@ -664,7 +824,7 @@ def latex_printer( tbSpc = 8 else: tbSpc = 4 - trailingAligner = '' + trailingAligner = '&' # Iterate over the objectives and print for obj in objectives: @@ -678,7 +838,7 @@ def latex_printer( visitor.walk_expression(obj_template), trailingAligner, ) - if use_align_environment: + if not use_equation_environment: pstr += '\\label{obj:' + pyomo_component.name + '_' + obj.name + '} ' if not isSingle: pstr += '\\\\ \n' @@ -735,54 +895,26 @@ def latex_printer( if use_forall: conLine += '%s %s \\in %s ' % (forallTag, idxTag, setTag) else: - if trailingAligner == '': - conLine = ( - conLine - + '%s %s \\in %s ' % (forallTag, idxTag, setTag) - + trailingAligner - ) - else: - conLine = ( - conLine[0:-2] - + '%s %s \\in %s ' % (forallTag, idxTag, setTag) - + trailingAligner - ) + conLine = ( + conLine[0:-2] + + ' ' + trailingAligner + + '%s %s \\in %s ' % (forallTag, idxTag, setTag) + ) pstr += conLine # Add labels as needed - if use_align_environment: + if not use_equation_environment: pstr += '\\label{con:' + pyomo_component.name + '_' + con.name + '} ' # prevents an emptly blank line from being at the end of the latex output if i <= len(constraints) - 2: pstr += tail else: - pstr += '\n' + pstr += tail + # pstr += '\n' # Print bounds and sets if not isSingle: - domainMap = { - 'Reals': '\\mathcal{R}', - 'PositiveReals': '\\mathcal{R}_{> 0}', - 'NonPositiveReals': '\\mathcal{R}_{\\leq 0}', - 'NegativeReals': '\\mathcal{R}_{< 0}', - 'NonNegativeReals': '\\mathcal{R}_{\\geq 0}', - 'Integers': '\\mathcal{Z}', - 'PositiveIntegers': '\\mathcal{Z}_{> 0}', - 'NonPositiveIntegers': '\\mathcal{Z}_{\\leq 0}', - 'NegativeIntegers': '\\mathcal{Z}_{< 0}', - 'NonNegativeIntegers': '\\mathcal{Z}_{\\geq 0}', - 'Boolean': '\\left{ 0 , 1 \\right }', - 'Binary': '\\left{ 0 , 1 \\right }', - 'Any': None, - 'AnyWithNone': None, - 'EmptySet': '\\varnothing', - 'UnitInterval': '\\left[ 0 , 1 \\right ]', - 'PercentFraction': '\\left[ 0 , 1 \\right ]', - # 'RealInterval' : None , - # 'IntegerInterval' : None , - } - variableList = [ vr for vr in pyomo_component.component_objects( @@ -790,184 +922,81 @@ def latex_printer( ) ] - varBoundData = [[] for v in variableList] + varBoundData = [] for i in range(0, len(variableList)): vr = variableList[i] if isinstance(vr, ScalarVar): - domainName = vr.domain.name - varBounds = vr.bounds - lowerBoundValue = varBounds[0] - upperBoundValue = varBounds[1] - - varLatexName = visitor.walk_expression(vr) - if varLatexName in overwrite_dict.keys(): - varReplaceName = overwrite_dict[varLatexName] - else: - varReplaceName = varLatexName - - if domainName in ['Reals', 'Integers']: - if lowerBoundValue is not None: - lowerBound = str(lowerBoundValue) + ' \\leq ' - else: - lowerBound = '' - - if upperBoundValue is not None: - upperBound = ' \\leq ' + str(upperBoundValue) - else: - upperBound = '' - - elif domainName in ['PositiveReals', 'PositiveIntegers']: - if lowerBoundValue is not None: - if lowerBoundValue > 0: - lowerBound = str(lowerBoundValue) + ' \\leq ' - else: - lowerBound = ' 0 < ' - else: - lowerBound = ' 0 < ' - - if upperBoundValue is not None: - if upperBoundValue <= 0: - raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' - % (vr.name) - ) - else: - upperBound = ' \\leq ' + str(upperBoundValue) - else: - upperBound = '' - - elif domainName in ['NonPositiveReals', 'NonPositiveIntegers']: - if lowerBoundValue is not None: - if lowerBoundValue > 0: - raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' - % (vr.name) - ) - elif lowerBoundValue == 0: - lowerBound = ' 0 = ' - else: - lowerBound = str(upperBoundValue) + ' \\leq ' - else: - lowerBound = '' - - if upperBoundValue is not None: - if upperBoundValue >= 0: - upperBound = ' \\leq 0 ' - else: - upperBound = ' \\leq ' + str(upperBoundValue) - else: - upperBound = ' \\leq 0 ' - - elif domainName in ['NegativeReals', 'NegativeIntegers']: - if lowerBoundValue is not None: - if lowerBoundValue >= 0: - raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' - % (vr.name) - ) - else: - lowerBound = str(upperBoundValue) + ' \\leq ' - else: - lowerBound = '' - - if upperBoundValue is not None: - if upperBoundValue >= 0: - upperBound = ' < 0 ' - else: - upperBound = ' \\leq ' + str(upperBoundValue) - else: - upperBound = ' < 0 ' - - elif domainName in ['NonNegativeReals', 'NonNegativeIntegers']: - if lowerBoundValue is not None: - if lowerBoundValue > 0: - lowerBound = str(lowerBoundValue) + ' \\leq ' - else: - lowerBound = ' 0 \\leq ' - else: - lowerBound = ' 0 \\leq ' - - if upperBoundValue is not None: - if upperBoundValue < 0: - raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' - % (vr.name) - ) - elif upperBoundValue == 0: - upperBound = ' = 0 ' - else: - upperBound = ' \\leq ' + str(upperBoundValue) - else: - upperBound = '' - - elif domainName in [ - 'Boolean', - 'Binary', - 'Any', - 'AnyWithNone', - 'EmptySet', - ]: - lowerBound = '' - upperBound = '' - - elif domainName in ['UnitInterval', 'PercentFraction']: - if lowerBoundValue is not None: - if lowerBoundValue > 0: - upperBound = str(lowerBoundValue) + ' \\leq ' - elif lowerBoundValue > 1: - raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' - % (vr.name) - ) - elif lowerBoundValue == 1: - lowerBound = ' = 1 ' - else: - lowerBoundValue = ' 0 \\leq ' - else: - lowerBound = ' 0 \\leq ' - - if upperBoundValue is not None: - if upperBoundValue < 1: - upperBound = ' \\leq ' + str(upperBoundValue) - elif upperBoundValue < 0: - raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' - % (vr.name) - ) - elif upperBoundValue == 0: - upperBound = ' = 0 ' - else: - upperBound = ' \\leq 1 ' - else: - upperBound = ' \\leq 1 ' - - else: - raise ValueError( - 'Domain %s not supported by the latex printer' % (domainName) - ) - - varBoundData[i] = [ - vr, - varLatexName, - varReplaceName, - lowerBound, - upperBound, - domainName, - domainMap[domainName], - ] + varBoundDataEntry = analyze_variable(vr, visitor) + varBoundData.append( varBoundDataEntry ) elif isinstance(vr, IndexedVar): + varBoundData_indexedVar = [] # need to wrap in function and do individually # Check on the final variable after all the indices are processed - pass + setData = vr.index_set().data() + for sd in setData: + varBoundDataEntry = analyze_variable(vr[sd], visitor) + varBoundData_indexedVar.append(varBoundDataEntry) + globIndexedVariables = True + for j in range(0,len(varBoundData_indexedVar)-1): + chks = [] + chks.append(varBoundData_indexedVar[j]['lowerBound']==varBoundData_indexedVar[j+1]['lowerBound']) + chks.append(varBoundData_indexedVar[j]['upperBound']==varBoundData_indexedVar[j+1]['upperBound']) + chks.append(varBoundData_indexedVar[j]['domainName']==varBoundData_indexedVar[j+1]['domainName']) + if not all(chks): + globIndexedVariables = False + break + if globIndexedVariables: + varBoundData.append({'variable': vr, + 'lowerBound': varBoundData_indexedVar[0]['lowerBound'], + 'upperBound': varBoundData_indexedVar[0]['upperBound'], + 'domainName': varBoundData_indexedVar[0]['domainName'], + 'domainLatex': varBoundData_indexedVar[0]['domainLatex'], + }) + else: + varBoundData += varBoundData_indexedVar else: raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers' ) # print the accumulated data to the string + bstr = '' + appendBoundString = False + useThreeAlgn = False + for i in range(0,len(varBoundData)): + vbd = varBoundData[i] + if vbd['lowerBound'] == '' and vbd['upperBound'] == '' and vbd['domainName']=='Reals': + # unbounded all real, do not print + if i <= len(varBoundData)-2: + bstr = bstr[0:-2] + else: + if not useThreeAlgn: + algn = '& &' + useThreeAlgn = True + else: + algn = '&&&' + + if use_equation_environment: + conLabel = '' + else: + conLabel = ' \\label{con:' + pyomo_component.name + '_' + variableMap[vbd['variable']] + '_bound' + '} ' + + appendBoundString = True + coreString = vbd['lowerBound'] + variableMap[vbd['variable']] + vbd['upperBound'] + ' ' + trailingAligner + '\\qquad \\in ' + vbd['domainLatex'] + conLabel + bstr += ' ' * tbSpc + algn + ' %s' % (coreString) + if i <= len(varBoundData)-2: + bstr += '\\\\ \n' + else: + bstr += '\n' + + if appendBoundString: + pstr += ' ' * tbSpc + '& %s \n' % (descriptorDict['with bounds']) + pstr += bstr + else: + pstr = pstr[0:-4] + '\n' # close off the print string - if use_align_environment: + if not use_equation_environment: pstr += '\\end{align} \n' else: if not isSingle: @@ -977,6 +1006,13 @@ def latex_printer( # Handling the iterator indices + + # ==================== + # ==================== + # ==================== + # ==================== + # ==================== + # ==================== # preferential order for indices setPreferenceOrder = [ 'I', @@ -1002,10 +1038,6 @@ def latex_printer( ln = latexLines[jj] # only modify if there is a placeholder in the line if "PLACEHOLDER_8675309_GROUP_" in ln: - if x_only_mode == 2: - raise RuntimeError( - 'Unwrapping indexed variables when an indexed constraint is present yields incorrect results' - ) splitLatex = ln.split('__') # Find the unique combinations of group numbers and set names for word in splitLatex: @@ -1096,14 +1128,169 @@ def latex_printer( # Assign the newly modified line latexLines[jj] = ln + # ==================== + # ==================== + # ==================== + # ==================== + # ==================== + # ==================== + + # rejoin the corrected lines pstr = '\n'.join(latexLines) + if x_only_mode in [1,2,3]: + # Need to preserve only the non-variable elments in the overwrite_dict + new_overwrite_dict = {} + for ky, vl in overwrite_dict.items(): + if isinstance(ky,_GeneralVarData): + pass + elif isinstance(ky,_ParamData): + raise ValueEror('not implemented yet') + elif isinstance(ky,_SetData): + new_overwrite_dict[ky] = overwrite_dict[ky] + else: + raise ValueError('The overwrite_dict object has a key of invalid type: %s'%(str(ky))) + overwrite_dict = new_overwrite_dict + + # # Only x modes + # # Mode 0 : dont use + # # Mode 1 : indexed variables become x_{_{ix}} + # # Mode 2 : uses standard alphabet [a,...,z,aa,...,az,...,aaa,...] with subscripts for indices, ex: abcd_{ix} + # # Mode 3 : unwrap everything into an x_{} list, including the indexed vars themselves + + if x_only_mode == 1: + vrIdx = 0 + new_variableMap = ComponentMap() + for i in range(0,len(variableList)): + vr = variableList[i] + vrIdx += 1 + if isinstance(vr ,ScalarVar): + new_variableMap[vr] = 'x_{' + str(vrIdx) + '}' + elif isinstance(vr, IndexedVar): + new_variableMap[vr] = 'x_{' + str(vrIdx) + '}' + for sd in vr.index_set().data(): + # vrIdx += 1 + sdString = str(sd) + if sdString[0]=='(': + sdString = sdString[1:] + if sdString[-1]==')': + sdString = sdString[0:-1] + new_variableMap[vr[sd]] = 'x_{' + str(vrIdx) + '_{' + sdString + '}' + '}' + else: + raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers') + + new_overwrite_dict = ComponentMap() + for ky, vl in new_variableMap.items(): + new_overwrite_dict[ky] = vl + for ky, vl in overwrite_dict.items(): + new_overwrite_dict[ky] = vl + overwrite_dict = new_overwrite_dict + + elif x_only_mode == 2: + vrIdx = 0 + new_variableMap = ComponentMap() + for i in range(0,len(variableList)): + vr = variableList[i] + vrIdx += 1 + if isinstance(vr ,ScalarVar): + new_variableMap[vr] = alphabetStringGenerator(i) + elif isinstance(vr, IndexedVar): + new_variableMap[vr] = alphabetStringGenerator(i) + for sd in vr.index_set().data(): + # vrIdx += 1 + sdString = str(sd) + if sdString[0]=='(': + sdString = sdString[1:] + if sdString[-1]==')': + sdString = sdString[0:-1] + new_variableMap[vr[sd]] = alphabetStringGenerator(i) + '_{' + sdString + '}' + else: + raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers') + + new_overwrite_dict = ComponentMap() + for ky, vl in new_variableMap.items(): + new_overwrite_dict[ky] = vl + for ky, vl in overwrite_dict.items(): + new_overwrite_dict[ky] = vl + overwrite_dict = new_overwrite_dict + + elif x_only_mode == 3: + new_overwrite_dict = ComponentMap() + for ky, vl in variableMap.items(): + new_overwrite_dict[ky] = vl + for ky, vl in overwrite_dict.items(): + new_overwrite_dict[ky] = vl + overwrite_dict = new_overwrite_dict + + else: + vrIdx = 0 + new_variableMap = ComponentMap() + for i in range(0,len(variableList)): + vr = variableList[i] + vrIdx += 1 + if isinstance(vr ,ScalarVar): + new_variableMap[vr] = vr.name + elif isinstance(vr, IndexedVar): + new_variableMap[vr] = vr.name + for sd in vr.index_set().data(): + # vrIdx += 1 + sdString = str(sd) + if sdString[0]=='(': + sdString = sdString[1:] + if sdString[-1]==')': + sdString = sdString[0:-1] + if use_smart_variables: + new_variableMap[vr[sd]] = applySmartVariables(vr.name + '_{' + sdString + '}') + else: + new_variableMap[vr[sd]] = vr[sd].name + else: + raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers') + + for ky, vl in new_variableMap.items(): + if ky not in overwrite_dict.keys(): + overwrite_dict[ky] = vl + + rep_dict = {} + for ky in list(reversed(list(overwrite_dict.keys()))): + if isinstance(ky,(pyo.Var,_GeneralVarData)): + if use_smart_variables and x_only_mode not in [1]: + overwrite_value = applySmartVariables(overwrite_dict[ky]) + else: + overwrite_value = overwrite_dict[ky] + rep_dict[variableMap[ky]] = overwrite_value + elif isinstance(ky,_ParamData): + raise ValueEror('not implemented yet') + elif isinstance(ky,_SetData): + # already handled + pass + else: + raise ValueError('The overwrite_dict object has a key of invalid type: %s'%(str(ky))) + + pstr = multiple_replace(pstr,rep_dict) + + pattern = r'_([^{]*)_{([^{]*)}_bound' + replacement = r'_\1_\2_bound' + pstr = re.sub(pattern, replacement, pstr) + + pattern = r'_{([^{]*)}_{([^{]*)}' + replacement = r'_{\1_{\2}}' + pstr = re.sub(pattern, replacement, pstr) + + pattern = r'_(.)_{([^}]*)}' + replacement = r'_{\1_{\2}}' + pstr = re.sub(pattern, replacement, pstr) + + + # optional write to output file if filename is not None: fstr = '' fstr += '\\documentclass{article} \n' fstr += '\\usepackage{amsmath} \n' + fstr += '\\usepackage{amssymb} \n' + fstr += '\\usepackage{dsfont} \n' + fstr += '\\allowdisplaybreaks \n' fstr += '\\begin{document} \n' fstr += pstr fstr += '\\end{document} \n' @@ -1111,9 +1298,5 @@ def latex_printer( f.write(fstr) f.close() - # Catch up on only x mode 3 and replace - for ky, vl in nameReplacementDict.items(): - pstr = pstr.replace(ky, vl) - # return the latex string return pstr From ca54afc4c17ad23d3c417732a1570571dae957a1 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 7 Sep 2023 23:47:53 -0400 Subject: [PATCH 022/148] add the support of greybox in mindtpy --- pyomo/contrib/mindtpy/algorithm_base_class.py | 80 ++++++++++++++----- pyomo/contrib/mindtpy/config_options.py | 8 ++ pyomo/contrib/mindtpy/cut_generation.py | 47 +++++++++++ pyomo/contrib/mindtpy/feasibility_pump.py | 7 +- .../mindtpy/global_outer_approximation.py | 1 + pyomo/contrib/mindtpy/outer_approximation.py | 13 ++- 6 files changed, 134 insertions(+), 22 deletions(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index 7def1dcaab3..e1d6d3e98ba 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -34,6 +34,7 @@ SolutionStatus, SolverStatus, ) +from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxBlock from pyomo.core import ( minimize, maximize, @@ -289,7 +290,7 @@ def model_is_valid(self): results = self.mip_opt.solve( self.original_model, tee=config.mip_solver_tee, - load_solutions=False, + load_solutions=config.load_solutions, **config.mip_solver_args, ) if len(results.solution) > 0: @@ -323,6 +324,11 @@ def build_ordered_component_lists(self, model): ctype=Constraint, active=True, descend_into=(Block) ) ) + util_block.grey_box_list = list( + model.component_data_objects( + ctype=ExternalGreyBoxBlock, active=True, descend_into=(Block) + ) + ) util_block.linear_constraint_list = list( c for c in util_block.constraint_list @@ -352,7 +358,9 @@ def build_ordered_component_lists(self, model): # preserve a deterministic ordering. util_block.variable_list = list( v - for v in model.component_data_objects(ctype=Var, descend_into=(Block)) + for v in model.component_data_objects( + ctype=Var, descend_into=(Block, ExternalGreyBoxBlock) + ) if v in var_set ) util_block.discrete_variable_list = list( @@ -802,18 +810,22 @@ def init_rNLP(self, add_oa_cuts=True): MindtPy unable to handle the termination condition of the relaxed NLP. """ config = self.config - m = self.working_model.clone() + self.rnlp = self.working_model.clone() config.logger.debug('Relaxed NLP: Solve relaxed integrality') - MindtPy = m.MindtPy_utils - TransformationFactory('core.relax_integer_vars').apply_to(m) + MindtPy = self.rnlp.MindtPy_utils + TransformationFactory('core.relax_integer_vars').apply_to(self.rnlp) nlp_args = dict(config.nlp_solver_args) update_solver_timelimit(self.nlp_opt, config.nlp_solver, self.timing, config) with SuppressInfeasibleWarning(): + print('solving rnlp') results = self.nlp_opt.solve( - m, tee=config.nlp_solver_tee, load_solutions=False, **nlp_args + self.rnlp, + tee=config.nlp_solver_tee, + load_solutions=config.load_solutions, + **nlp_args, ) if len(results.solution) > 0: - m.solutions.load_from(results) + self.rnlp.solutions.load_from(results) subprob_terminate_cond = results.solver.termination_condition if subprob_terminate_cond in {tc.optimal, tc.feasible, tc.locallyOptimal}: main_objective = MindtPy.objective_list[-1] @@ -841,24 +853,24 @@ def init_rNLP(self, add_oa_cuts=True): ): # TODO: recover the opposite dual when cyipopt issue #2831 is solved. dual_values = ( - list(-1 * m.dual[c] for c in MindtPy.constraint_list) + list(-1 * self.rnlp.dual[c] for c in MindtPy.constraint_list) if config.calculate_dual_at_solution else None ) else: dual_values = ( - list(m.dual[c] for c in MindtPy.constraint_list) + list(self.rnlp.dual[c] for c in MindtPy.constraint_list) if config.calculate_dual_at_solution else None ) copy_var_list_values( - m.MindtPy_utils.variable_list, + self.rnlp.MindtPy_utils.variable_list, self.mip.MindtPy_utils.variable_list, config, ) if config.init_strategy == 'FP': copy_var_list_values( - m.MindtPy_utils.variable_list, + self.rnlp.MindtPy_utils.variable_list, self.working_model.MindtPy_utils.variable_list, config, ) @@ -867,6 +879,7 @@ def init_rNLP(self, add_oa_cuts=True): linearize_active=True, linearize_violated=True, cb_opt=None, + nlp=self.rnlp, ) for var in self.mip.MindtPy_utils.discrete_variable_list: # We don't want to trigger the reset of the global stale @@ -936,7 +949,10 @@ def init_max_binaries(self): mip_args = dict(config.mip_solver_args) update_solver_timelimit(self.mip_opt, config.mip_solver, self.timing, config) results = self.mip_opt.solve( - m, tee=config.mip_solver_tee, load_solutions=False, **mip_args + m, + tee=config.mip_solver_tee, + load_solutions=config.load_solutions, + **mip_args, ) if len(results.solution) > 0: m.solutions.load_from(results) @@ -1055,7 +1071,7 @@ def solve_subproblem(self): results = self.nlp_opt.solve( self.fixed_nlp, tee=config.nlp_solver_tee, - load_solutions=False, + load_solutions=config.load_solutions, **nlp_args, ) if len(results.solution) > 0: @@ -1153,6 +1169,7 @@ def handle_subproblem_optimal(self, fixed_nlp, cb_opt=None, fp=False): linearize_active=True, linearize_violated=True, cb_opt=cb_opt, + nlp=self.fixed_nlp, ) var_values = list(v.value for v in fixed_nlp.MindtPy_utils.variable_list) @@ -1229,6 +1246,7 @@ def handle_subproblem_infeasible(self, fixed_nlp, cb_opt=None): linearize_active=True, linearize_violated=True, cb_opt=cb_opt, + nlp=feas_subproblem, ) # Add a no-good cut to exclude this discrete option var_values = list(v.value for v in fixed_nlp.MindtPy_utils.variable_list) @@ -1311,6 +1329,12 @@ def solve_feasibility_subproblem(self): update_solver_timelimit( self.feasibility_nlp_opt, config.nlp_solver, self.timing, config ) + TransformationFactory('contrib.deactivate_trivial_constraints').apply_to( + feas_subproblem, + tmp=True, + ignore_infeasible=False, + tolerance=config.constraint_tolerance, + ) with SuppressInfeasibleWarning(): try: with time_code(self.timing, 'feasibility subproblem'): @@ -1346,6 +1370,9 @@ def solve_feasibility_subproblem(self): constr.activate() active_obj.activate() MindtPy.feas_obj.deactivate() + TransformationFactory('contrib.deactivate_trivial_constraints').revert( + feas_subproblem + ) return feas_subproblem, feas_soln def handle_feasibility_subproblem_tc(self, subprob_terminate_cond, MindtPy): @@ -1480,7 +1507,10 @@ def fix_dual_bound(self, last_iter_cuts): self.mip_opt, config.mip_solver, self.timing, config ) main_mip_results = self.mip_opt.solve( - self.mip, tee=config.mip_solver_tee, load_solutions=False, **mip_args + self.mip, + tee=config.mip_solver_tee, + load_solutions=config.load_solutions, + **mip_args, ) if len(main_mip_results.solution) > 0: self.mip.solutions.load_from(main_mip_results) @@ -1564,7 +1594,10 @@ def solve_main(self): try: main_mip_results = self.mip_opt.solve( - self.mip, tee=config.mip_solver_tee, load_solutions=False, **mip_args + self.mip, + tee=config.mip_solver_tee, + load_solutions=config.load_solutions, + **mip_args, ) # update_attributes should be before load_from(main_mip_results), since load_from(main_mip_results) may fail. if len(main_mip_results.solution) > 0: @@ -1617,7 +1650,10 @@ def solve_fp_main(self): mip_args = self.set_up_mip_solver() main_mip_results = self.mip_opt.solve( - self.mip, tee=config.mip_solver_tee, load_solutions=False, **mip_args + self.mip, + tee=config.mip_solver_tee, + load_solutions=config.load_solutions, + **mip_args, ) # update_attributes should be before load_from(main_mip_results), since load_from(main_mip_results) may fail. # if config.single_tree or config.use_tabu_list: @@ -1659,7 +1695,7 @@ def solve_regularization_main(self): main_mip_results = self.regularization_mip_opt.solve( self.mip, tee=config.mip_solver_tee, - load_solutions=False, + load_solutions=config.load_solutions, **dict(config.mip_solver_args), ) if len(main_mip_results.solution) > 0: @@ -1871,7 +1907,7 @@ def handle_main_unbounded(self, main_mip): main_mip_results = self.mip_opt.solve( main_mip, tee=config.mip_solver_tee, - load_solutions=False, + load_solutions=config.load_solutions, **config.mip_solver_args, ) if len(main_mip_results.solution) > 0: @@ -2263,7 +2299,10 @@ def solve_fp_subproblem(self): with SuppressInfeasibleWarning(): with time_code(self.timing, 'fp subproblem'): results = self.nlp_opt.solve( - fp_nlp, tee=config.nlp_solver_tee, load_solutions=False, **nlp_args + fp_nlp, + tee=config.nlp_solver_tee, + load_solutions=config.load_solutions, + **nlp_args, ) if len(results.solution) > 0: fp_nlp.solutions.load_from(results) @@ -2482,6 +2521,9 @@ def initialize_mip_problem(self): getattr(self.mip, 'ipopt_zU_out', _DoNothing()).deactivate() MindtPy = self.mip.MindtPy_utils + if len(MindtPy.grey_box_list) > 0: + for grey_box in MindtPy.grey_box_list: + grey_box.deactivate() if config.init_strategy == 'FP': MindtPy.cuts.fp_orthogonality_cuts = ConstraintList( diff --git a/pyomo/contrib/mindtpy/config_options.py b/pyomo/contrib/mindtpy/config_options.py index ed0c86baae9..507cbd995f8 100644 --- a/pyomo/contrib/mindtpy/config_options.py +++ b/pyomo/contrib/mindtpy/config_options.py @@ -494,6 +494,14 @@ def _add_common_configs(CONFIG): domain=bool, ), ) + CONFIG.declare( + 'load_solutions', + ConfigValue( + default=True, + description='Whether to load solutions in solve() function', + domain=bool, + ), + ) def _add_subsolver_configs(CONFIG): diff --git a/pyomo/contrib/mindtpy/cut_generation.py b/pyomo/contrib/mindtpy/cut_generation.py index c0449054baa..5613155ee7d 100644 --- a/pyomo/contrib/mindtpy/cut_generation.py +++ b/pyomo/contrib/mindtpy/cut_generation.py @@ -181,6 +181,53 @@ def add_oa_cuts( ) +def add_oa_cuts_for_grey_box( + target_model, jacobians_model, config, objective_sense, mip_iter, cb_opt=None +): + sign_adjust = -1 if objective_sense == minimize else 1 + if config.add_slack: + slack_var = target_model.MindtPy_utils.cuts.slack_vars.add() + for target_model_grey_box, jacobian_model_grey_box in zip( + target_model.MindtPy_utils.grey_box_list, + jacobians_model.MindtPy_utils.grey_box_list, + ): + jacobian_matrix = ( + jacobian_model_grey_box.get_external_model() + .evaluate_jacobian_outputs() + .toarray() + ) + for index, output in enumerate(target_model_grey_box.outputs.values()): + dual_value = jacobians_model.dual[jacobian_model_grey_box][ + output.name.replace("outputs", "output_constraints") + ] + target_model.MindtPy_utils.cuts.oa_cuts.add( + expr=copysign(1, sign_adjust * dual_value) + * ( + sum( + jacobian_matrix[index][var_index] * (var - value(var)) + for var_index, var in enumerate( + target_model_grey_box.inputs.values() + ) + ) + ) + - (output - value(output)) + - (slack_var if config.add_slack else 0) + <= 0 + ) + # TODO: gurobi_persistent currently does not support greybox model. + if ( + config.single_tree + and config.mip_solver == 'gurobi_persistent' + and mip_iter > 0 + and cb_opt is not None + ): + cb_opt.cbLazy( + target_model.MindtPy_utils.cuts.oa_cuts[ + len(target_model.MindtPy_utils.cuts.oa_cuts) + ] + ) + + def add_ecp_cuts( target_model, jacobians, diff --git a/pyomo/contrib/mindtpy/feasibility_pump.py b/pyomo/contrib/mindtpy/feasibility_pump.py index 5716400598a..990f56b8f93 100644 --- a/pyomo/contrib/mindtpy/feasibility_pump.py +++ b/pyomo/contrib/mindtpy/feasibility_pump.py @@ -52,7 +52,12 @@ def initialize_mip_problem(self): ) def add_cuts( - self, dual_values, linearize_active=True, linearize_violated=True, cb_opt=None + self, + dual_values, + linearize_active=True, + linearize_violated=True, + cb_opt=None, + nlp=None, ): add_oa_cuts( self.mip, diff --git a/pyomo/contrib/mindtpy/global_outer_approximation.py b/pyomo/contrib/mindtpy/global_outer_approximation.py index ee3ffb62f55..dfb7ef54630 100644 --- a/pyomo/contrib/mindtpy/global_outer_approximation.py +++ b/pyomo/contrib/mindtpy/global_outer_approximation.py @@ -95,6 +95,7 @@ def add_cuts( linearize_active=True, linearize_violated=True, cb_opt=None, + nlp=None, ): add_affine_cuts(self.mip, self.config, self.timing) diff --git a/pyomo/contrib/mindtpy/outer_approximation.py b/pyomo/contrib/mindtpy/outer_approximation.py index 99d9cea1bd4..6cf0b26cb37 100644 --- a/pyomo/contrib/mindtpy/outer_approximation.py +++ b/pyomo/contrib/mindtpy/outer_approximation.py @@ -16,7 +16,7 @@ from pyomo.opt import SolverFactory from pyomo.contrib.mindtpy.config_options import _get_MindtPy_OA_config from pyomo.contrib.mindtpy.algorithm_base_class import _MindtPyAlgorithm -from pyomo.contrib.mindtpy.cut_generation import add_oa_cuts +from pyomo.contrib.mindtpy.cut_generation import add_oa_cuts, add_oa_cuts_for_grey_box @SolverFactory.register( @@ -102,7 +102,12 @@ def initialize_mip_problem(self): ) def add_cuts( - self, dual_values, linearize_active=True, linearize_violated=True, cb_opt=None + self, + dual_values, + linearize_active=True, + linearize_violated=True, + cb_opt=None, + nlp=None, ): add_oa_cuts( self.mip, @@ -117,6 +122,10 @@ def add_cuts( linearize_active, linearize_violated, ) + if len(self.mip.MindtPy_utils.grey_box_list) > 0: + add_oa_cuts_for_grey_box( + self.mip, nlp, self.config, self.objective_sense, self.mip_iter, cb_opt + ) def deactivate_no_good_cuts_when_fixing_bound(self, no_good_cuts): # Only deactivate the last OA cuts may not be correct. From ec05a3d49f10b13c27ee73a87d392f3b6fd6e722 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Fri, 8 Sep 2023 00:05:51 -0400 Subject: [PATCH 023/148] add grey box test in mindtpy --- pyomo/contrib/mindtpy/tests/MINLP_simple.py | 42 +++++- .../mindtpy/tests/MINLP_simple_grey_box.py | 140 ++++++++++++++++++ .../mindtpy/tests/test_mindtpy_grey_box.py | 75 ++++++++++ 3 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py create mode 100644 pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple.py b/pyomo/contrib/mindtpy/tests/MINLP_simple.py index 5663c93af8b..6e1518e1b63 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple.py @@ -35,14 +35,23 @@ RangeSet, Var, minimize, + Block, ) from pyomo.common.collections import ComponentMap +from pyomo.contrib.mindtpy.tests.MINLP_simple_grey_box import GreyBoxModel +from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxBlock + + +def build_model_external(m): + ex_model = GreyBoxModel(initial={"X1": 0, "X2": 0, "Y1": 0, "Y2": 1, "Y3": 1}) + m.egb = ExternalGreyBoxBlock() + m.egb.set_external_model(ex_model) class SimpleMINLP(ConcreteModel): """Convex MINLP problem Assignment 6 APSE.""" - def __init__(self, *args, **kwargs): + def __init__(self, grey_box=False, *args, **kwargs): """Create the problem.""" kwargs.setdefault('name', 'SimpleMINLP') super(SimpleMINLP, self).__init__(*args, **kwargs) @@ -83,6 +92,37 @@ def __init__(self, *args, **kwargs): m.objective = Objective( expr=Y[1] + 1.5 * Y[2] + 0.5 * Y[3] + X[1] ** 2 + X[2] ** 2, sense=minimize ) + + if not grey_box: + m.objective = Objective( + expr=Y[1] + 1.5 * Y[2] + 0.5 * Y[3] + X[1] ** 2 + X[2] ** 2, + sense=minimize, + ) + else: + def _model_i(b): + build_model_external(b) + + m.my_block = Block(rule=_model_i) + + for i in m.I: + + def eq_inputX(m): + return m.X[i] == m.my_block.egb.inputs["X" + str(i)] + + con_name = "con_X_" + str(i) + m.add_component(con_name, Constraint(expr=eq_inputX)) + + for j in m.J: + + def eq_inputY(m): + return m.Y[j] == m.my_block.egb.inputs["Y" + str(j)] + + con_name = "con_Y_" + str(j) + m.add_component(con_name, Constraint(expr=eq_inputY)) + + # add objective + m.objective = Objective(expr=m.my_block.egb.outputs['z'], sense=minimize) + """Bound definitions""" # x (continuous) upper bounds x_ubs = {1: 4, 2: 4} diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py new file mode 100644 index 00000000000..069d2d894f4 --- /dev/null +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py @@ -0,0 +1,140 @@ +import numpy as np +import pyomo.environ as pyo +from scipy.sparse import coo_matrix +from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxModel +from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxBlock + + +class GreyBoxModel(ExternalGreyBoxModel): + """Greybox model to compute the example OF.""" + + def __init__(self, initial, use_exact_derivatives=True, verbose=True): + """ + Parameters + + use_exact_derivatives: bool + If True, the exact derivatives are used. If False, the finite difference + approximation is used. + verbose: bool + If True, print information about the model. + """ + self._use_exact_derivatives = use_exact_derivatives + self.verbose = verbose + self.initial = initial + + # For use with exact Hessian + self._output_con_mult_values = np.zeros(1) + + if not use_exact_derivatives: + raise NotImplementedError("use_exact_derivatives == False not supported") + + def input_names(self): + """Return the names of the inputs.""" + self.input_name_list = ["X1", "X2", "Y1", "Y2", "Y3"] + + return self.input_name_list + + def equality_constraint_names(self): + """Return the names of the equality constraints.""" + # no equality constraints + return [] + + def output_names(self): + """Return the names of the outputs.""" + return ['z'] + + def set_output_constraint_multipliers(self, output_con_multiplier_values): + """Set the values of the output constraint multipliers.""" + # because we only have one output constraint + assert len(output_con_multiplier_values) == 1 + np.copyto(self._output_con_mult_values, output_con_multiplier_values) + + def finalize_block_construction(self, pyomo_block): + """Finalize the construction of the ExternalGreyBoxBlock.""" + if self.initial is not None: + print("initialized") + pyomo_block.inputs["X1"].value = self.initial["X1"] + pyomo_block.inputs["X2"].value = self.initial["X2"] + pyomo_block.inputs["Y1"].value = self.initial["Y1"] + pyomo_block.inputs["Y2"].value = self.initial["Y2"] + pyomo_block.inputs["Y3"].value = self.initial["Y3"] + + else: + print("uninitialized") + for n in self.input_name_list: + pyomo_block.inputs[n].value = 1 + + pyomo_block.inputs["X1"].setub(4) + pyomo_block.inputs["X1"].setlb(0) + + pyomo_block.inputs["X2"].setub(4) + pyomo_block.inputs["X2"].setlb(0) + + pyomo_block.inputs["Y1"].setub(1) + pyomo_block.inputs["Y1"].setlb(0) + + pyomo_block.inputs["Y2"].setub(1) + pyomo_block.inputs["Y2"].setlb(0) + + pyomo_block.inputs["Y3"].setub(1) + pyomo_block.inputs["Y3"].setlb(0) + + def set_input_values(self, input_values): + """Set the values of the inputs.""" + self._input_values = list(input_values) + + def evaluate_equality_constraints(self): + """Evaluate the equality constraints.""" + # Not sure what this function should return with no equality constraints + return None + + def evaluate_outputs(self): + """Evaluate the output of the model.""" + # form matrix as a list of lists + # M = self._extract_and_assemble_fim() + x1 = self._input_values[0] + x2 = self._input_values[1] + y1 = self._input_values[2] + y2 = self._input_values[3] + y3 = self._input_values[4] + # z + z = x1**2 + x2**2 + y1 + 1.5 * y2 + 0.5 * y3 + + if self.verbose: + pass + # print("\n Consider inputs [x1,x2,y1,y2,y3] =\n",x1, x2, y1, y2, y3) + # print(" z = ",z,"\n") + + return np.asarray([z], dtype=np.float64) + + def evaluate_jacobian_equality_constraints(self): + """Evaluate the Jacobian of the equality constraints.""" + return None + + ''' + def _extract_and_assemble_fim(self): + M = np.zeros((self.n_parameters, self.n_parameters)) + for i in range(self.n_parameters): + for k in range(self.n_parameters): + M[i,k] = self._input_values[self.ele_to_order[(i,k)]] + + return M + ''' + + def evaluate_jacobian_outputs(self): + """Evaluate the Jacobian of the outputs.""" + if self._use_exact_derivatives: + + # compute gradient of log determinant + row = np.zeros(5) # to store row index + col = np.zeros(5) # to store column index + data = np.zeros(5) # to store data + + row[0], col[0], data[0] = (0, 0, 2 * self._input_values[0]) # x1 + row[0], col[1], data[1] = (0, 1, 2 * self._input_values[1]) # x2 + row[0], col[2], data[2] = (0, 2, 1) # y1 + row[0], col[3], data[3] = (0, 3, 1.5) # y2 + row[0], col[4], data[4] = (0, 4, 0.5) # y3 + + # sparse matrix + return coo_matrix((data, (row, col)), shape=(1, 5)) diff --git a/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py new file mode 100644 index 00000000000..d9ba683d198 --- /dev/null +++ b/pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py @@ -0,0 +1,75 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2022 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain +# rights in this software. +# This software is distributed under the 3-clause BSD License. +# ___________________________________________________________________________ + +"""Tests for the MindtPy solver.""" +from pyomo.core.expr.calculus.diff_with_sympy import differentiate_available +import pyomo.common.unittest as unittest +from pyomo.contrib.mindtpy.tests.MINLP_simple import SimpleMINLP as SimpleMINLP +from pyomo.environ import SolverFactory, value, maximize +from pyomo.opt import TerminationCondition + + +model_list = [SimpleMINLP(grey_box=True)] +required_solvers = ('cyipopt', 'glpk') +if all(SolverFactory(s).available(exception_flag=False) for s in required_solvers): + subsolvers_available = True +else: + subsolvers_available = False + + +@unittest.skipIf( + not subsolvers_available, + 'Required subsolvers %s are not available' % (required_solvers,), +) +@unittest.skipIf( + not differentiate_available, 'Symbolic differentiation is not available' +) +class TestMindtPy(unittest.TestCase): + """Tests for the MindtPy solver plugin.""" + + def check_optimal_solution(self, model, places=1): + for var in model.optimal_solution: + self.assertAlmostEqual( + var.value, model.optimal_solution[var], places=places + ) + + def test_OA_rNLP(self): + """Test the outer approximation decomposition algorithm.""" + with SolverFactory('mindtpy') as opt: + for model in model_list: + model = model.clone() + results = opt.solve( + model, + strategy='OA', + init_strategy='rNLP', + mip_solver=required_solvers[1], + nlp_solver=required_solvers[0], + calculate_dual_at_solution=True, + nlp_solver_args={ + 'options': { + 'hessian_approximation': 'limited-memory', + 'linear_solver': 'mumps', + } + }, + ) + + self.assertIn( + results.solver.termination_condition, + [TerminationCondition.optimal, TerminationCondition.feasible], + ) + self.assertAlmostEqual( + value(model.objective.expr), model.optimal_value, places=1 + ) + self.check_optimal_solution(model) + + +if __name__ == '__main__': + unittest.main() From 6cdff50a4626a3c101b2264ff1e8b44bed179ae3 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Fri, 8 Sep 2023 11:44:13 -0600 Subject: [PATCH 024/148] adding param support --- pyomo/util/latex_printer.py | 138 +++++++++++++++++++++++++++++++++--- 1 file changed, 128 insertions(+), 10 deletions(-) diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index a23ae3fdfba..5bbb4309c53 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -46,7 +46,7 @@ templatize_rule, ) from pyomo.core.base.var import ScalarVar, _GeneralVarData, IndexedVar -from pyomo.core.base.param import _ParamData +from pyomo.core.base.param import _ParamData, ScalarParam, IndexedParam from pyomo.core.base.set import _SetData from pyomo.core.base.constraint import ScalarConstraint, IndexedConstraint from pyomo.common.collections.component_map import ComponentMap @@ -395,6 +395,11 @@ def handle_templateSumExpression_node(visitor, node, *args): ) return pstr +def handle_param_node(visitor,node): + # return node.name + return visitor.parameterMap[node] + + class _LatexVisitor(StreamBasedExpressionVisitor): def __init__(self): @@ -431,6 +436,8 @@ def __init__(self): IndexTemplate: handle_indexTemplate_node, Numeric_GetItemExpression: handle_numericGIE_node, TemplateSumExpression: handle_templateSumExpression_node, + ScalarParam: handle_param_node, + _ParamData: handle_param_node, } def exitNode(self, node, data): @@ -811,6 +818,31 @@ def latex_printer( visitor.variableMap = variableMap + parameterList = [ + pm + for pm in pyomo_component.component_objects( + pyo.Param, descend_into=True, active=True + ) + ] + + parameterMap = ComponentMap() + pmIdx = 0 + for i in range(0,len(parameterList)): + vr = parameterList[i] + pmIdx += 1 + if isinstance(vr ,ScalarParam): + parameterMap[vr] = 'p_' + str(pmIdx) + elif isinstance(vr, IndexedParam): + parameterMap[vr] = 'p_' + str(pmIdx) + for sd in vr.index_set().data(): + pmIdx += 1 + parameterMap[vr[sd]] = 'p_' + str(pmIdx) + else: + raise DeveloperError( 'Parameter is not a parameter. Should not happen. Contact developers') + + visitor.parameterMap = parameterMap + + # starts building the output string pstr = '' if not use_equation_environment: @@ -991,7 +1023,7 @@ def latex_printer( if appendBoundString: pstr += ' ' * tbSpc + '& %s \n' % (descriptorDict['with bounds']) - pstr += bstr + pstr += bstr + '\n' else: pstr = pstr[0:-4] + '\n' @@ -1140,13 +1172,13 @@ def latex_printer( pstr = '\n'.join(latexLines) if x_only_mode in [1,2,3]: - # Need to preserve only the non-variable elments in the overwrite_dict + # Need to preserve only the set elments in the overwrite_dict new_overwrite_dict = {} for ky, vl in overwrite_dict.items(): if isinstance(ky,_GeneralVarData): pass elif isinstance(ky,_ParamData): - raise ValueEror('not implemented yet') + pass elif isinstance(ky,_SetData): new_overwrite_dict[ky] = overwrite_dict[ky] else: @@ -1180,9 +1212,30 @@ def latex_printer( else: raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers') + pmIdx = 0 + new_parameterMap = ComponentMap() + for i in range(0,len(parameterList)): + pm = parameterList[i] + pmIdx += 1 + if isinstance(pm ,ScalarParam): + new_parameterMap[pm] = 'p_{' + str(pmIdx) + '}' + elif isinstance(pm, IndexedParam): + new_parameterMap[pm] = 'p_{' + str(pmIdx) + '}' + for sd in pm.index_set().data(): + sdString = str(sd) + if sdString[0]=='(': + sdString = sdString[1:] + if sdString[-1]==')': + sdString = sdString[0:-1] + new_parameterMap[pm[sd]] = 'p_{' + str(pmIdx) + '_{' + sdString + '}' + '}' + else: + raise DeveloperError( 'Parameter is not a parameter. Should not happen. Contact developers') + new_overwrite_dict = ComponentMap() for ky, vl in new_variableMap.items(): new_overwrite_dict[ky] = vl + for ky, vl in new_parameterMap.items(): + new_overwrite_dict[ky] = vl for ky, vl in overwrite_dict.items(): new_overwrite_dict[ky] = vl overwrite_dict = new_overwrite_dict @@ -1208,9 +1261,30 @@ def latex_printer( else: raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers') + pmIdx = vrIdx-1 + new_parameterMap = ComponentMap() + for i in range(0,len(parameterList)): + pm = parameterList[i] + pmIdx += 1 + if isinstance(pm ,ScalarParam): + new_parameterMap[pm] = alphabetStringGenerator(pmIdx) + elif isinstance(pm, IndexedParam): + new_parameterMap[pm] = alphabetStringGenerator(pmIdx) + for sd in pm.index_set().data(): + sdString = str(sd) + if sdString[0]=='(': + sdString = sdString[1:] + if sdString[-1]==')': + sdString = sdString[0:-1] + new_parameterMap[pm[sd]] = alphabetStringGenerator(pmIdx) + '_{' + sdString + '}' + else: + raise DeveloperError( 'Parameter is not a parameter. Should not happen. Contact developers') + new_overwrite_dict = ComponentMap() for ky, vl in new_variableMap.items(): new_overwrite_dict[ky] = vl + for ky, vl in new_parameterMap.items(): + new_overwrite_dict[ky] = vl for ky, vl in overwrite_dict.items(): new_overwrite_dict[ky] = vl overwrite_dict = new_overwrite_dict @@ -1219,6 +1293,8 @@ def latex_printer( new_overwrite_dict = ComponentMap() for ky, vl in variableMap.items(): new_overwrite_dict[ky] = vl + for ky, vl in parameterMap.items(): + new_overwrite_dict[ky] = vl for ky, vl in overwrite_dict.items(): new_overwrite_dict[ky] = vl overwrite_dict = new_overwrite_dict @@ -1247,9 +1323,35 @@ def latex_printer( else: raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers') + pmIdx = 0 + new_parameterMap = ComponentMap() + for i in range(0,len(parameterList)): + pm = parameterList[i] + pmIdx += 1 + if isinstance(pm ,ScalarParam): + new_parameterMap[pm] = pm.name + elif isinstance(pm, IndexedParam): + new_parameterMap[pm] = pm.name + for sd in pm.index_set().data(): + # pmIdx += 1 + sdString = str(sd) + if sdString[0]=='(': + sdString = sdString[1:] + if sdString[-1]==')': + sdString = sdString[0:-1] + if use_smart_variables: + new_parameterMap[pm[sd]] = applySmartVariables(pm.name + '_{' + sdString + '}') + else: + new_parameterMap[pm[sd]] = pm[sd].name + else: + raise DeveloperError( 'Parameter is not a parameter. Should not happen. Contact developers') + for ky, vl in new_variableMap.items(): if ky not in overwrite_dict.keys(): overwrite_dict[ky] = vl + for ky, vl in new_parameterMap.items(): + if ky not in overwrite_dict.keys(): + overwrite_dict[ky] = vl rep_dict = {} for ky in list(reversed(list(overwrite_dict.keys()))): @@ -1259,19 +1361,35 @@ def latex_printer( else: overwrite_value = overwrite_dict[ky] rep_dict[variableMap[ky]] = overwrite_value - elif isinstance(ky,_ParamData): - raise ValueEror('not implemented yet') + elif isinstance(ky,(pyo.Param,_ParamData)): + if use_smart_variables and x_only_mode not in [1]: + overwrite_value = applySmartVariables(overwrite_dict[ky]) + else: + overwrite_value = overwrite_dict[ky] + rep_dict[parameterMap[ky]] = overwrite_value elif isinstance(ky,_SetData): # already handled pass else: raise ValueError('The overwrite_dict object has a key of invalid type: %s'%(str(ky))) - pstr = multiple_replace(pstr,rep_dict) + if not use_smart_variables: + for ky, vl in rep_dict.items(): + rep_dict[ky] = vl.replace('_','\\_') - pattern = r'_([^{]*)_{([^{]*)}_bound' - replacement = r'_\1_\2_bound' - pstr = re.sub(pattern, replacement, pstr) + label_rep_dict = copy.deepcopy(rep_dict) + for ky, vl in label_rep_dict.items(): + label_rep_dict[ky] = vl.replace('{','').replace('}','').replace('\\','') + + splitLines = pstr.split('\n') + for i in range(0,len(splitLines)): + if '\\label{' in splitLines[i]: + epr, lbl = splitLines[i].split('\\label{') + epr = multiple_replace(epr,rep_dict) + lbl = multiple_replace(lbl,label_rep_dict) + splitLines[i] = epr + '\\label{' + lbl + + pstr = '\n'.join(splitLines) pattern = r'_{([^{]*)}_{([^{]*)}' replacement = r'_{\1_{\2}}' From c2877a3fa0589d379aeb622ae889cac2672ef9be Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Fri, 8 Sep 2023 14:49:15 -0400 Subject: [PATCH 025/148] black format --- pyomo/contrib/mindtpy/tests/MINLP_simple.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple.py b/pyomo/contrib/mindtpy/tests/MINLP_simple.py index 6e1518e1b63..91976997c34 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple.py @@ -99,6 +99,7 @@ def __init__(self, grey_box=False, *args, **kwargs): sense=minimize, ) else: + def _model_i(b): build_model_external(b) From ccff4167e61363a84d2899016dd390eeb5c596c1 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Fri, 8 Sep 2023 14:52:42 -0400 Subject: [PATCH 026/148] black format --- pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py | 1 - pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py index 069d2d894f4..562e88ea667 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py @@ -124,7 +124,6 @@ def _extract_and_assemble_fim(self): def evaluate_jacobian_outputs(self): """Evaluate the Jacobian of the outputs.""" if self._use_exact_derivatives: - # compute gradient of log determinant row = np.zeros(5) # to store row index col = np.zeros(5) # to store column index diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py index a37507dee6b..0b6774a7d1a 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py @@ -426,7 +426,11 @@ def load_state_into_pyomo(self, bound_multipliers=None): # not we are minimizing or maximizing - this is done in the ASL interface # for ipopt, but does not appear to be done in cyipopt. obj_sign = 1.0 - objs = list(m.component_data_objects(ctype=pyo.Objective, active=True, descend_into=True)) + objs = list( + m.component_data_objects( + ctype=pyo.Objective, active=True, descend_into=True + ) + ) assert len(objs) == 1 if objs[0].sense == pyo.maximize: obj_sign = -1.0 From 3054a4431bfdde777b87d3c2d2ea75c51632ed97 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Fri, 8 Sep 2023 16:05:08 -0600 Subject: [PATCH 027/148] 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 e461e93b8253f4d00ae50b1dd9056ed8f09437b2 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Sun, 10 Sep 2023 21:49:08 -0400 Subject: [PATCH 028/148] add comment --- pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py index 0b6774a7d1a..d2a55c67f48 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py @@ -426,6 +426,8 @@ def load_state_into_pyomo(self, bound_multipliers=None): # not we are minimizing or maximizing - this is done in the ASL interface # for ipopt, but does not appear to be done in cyipopt. obj_sign = 1.0 + # since we will assert the number of objective functions, + # we only focus on active objective function. objs = list( m.component_data_objects( ctype=pyo.Objective, active=True, descend_into=True From 6c6053f4d312dca69a3352b3a3a84230b372baa9 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Mon, 11 Sep 2023 11:52:25 -0600 Subject: [PATCH 029/148] 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 69880ae77ce871eddb179340171c7bed567b2943 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Mon, 11 Sep 2023 13:03:28 -0600 Subject: [PATCH 030/148] 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 9a077d901f27c4c8db9c9e95bddda34b95996616 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Mon, 18 Sep 2023 18:19:02 -0600 Subject: [PATCH 031/148] making updates to the single constraint printer --- pyomo/util/latex_printer.py | 474 +++++++++++++++++++++++------------- 1 file changed, 302 insertions(+), 172 deletions(-) diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index aca6040e58f..4c1a4c31e07 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -31,6 +31,7 @@ ExternalFunctionExpression, ) +from pyomo.core.expr.visitor import identify_components from pyomo.core.expr.base import ExpressionBase from pyomo.core.base.expression import ScalarExpression, _GeneralExpressionData from pyomo.core.base.objective import ScalarObjective, _GeneralObjectiveData @@ -50,6 +51,7 @@ from pyomo.core.base.set import _SetData from pyomo.core.base.constraint import ScalarConstraint, IndexedConstraint from pyomo.common.collections.component_map import ComponentMap +from pyomo.common.collections.component_set import ComponentSet from pyomo.core.base.external import _PythonCallbackFunctionID @@ -102,7 +104,7 @@ def indexCorrector(ixs, base): return ixs -def alphabetStringGenerator(num,indexMode = False): +def alphabetStringGenerator(num, indexMode=False): if indexMode: alphabet = [ '.', @@ -164,9 +166,9 @@ def alphabetStringGenerator(num,indexMode = False): 'y', 'z', ] - ixs = decoder(num + 1, len(alphabet)-1) + ixs = decoder(num + 1, len(alphabet) - 1) pstr = '' - ixs = indexCorrector(ixs, len(alphabet)-1) + ixs = indexCorrector(ixs, len(alphabet) - 1) for i in range(0, len(ixs)): ix = ixs[i] pstr += alphabet[ix] @@ -257,6 +259,7 @@ def handle_inequality_node(visitor, node, arg1, arg2): def handle_var_node(visitor, node): return visitor.variableMap[node] + def handle_num_node(visitor, node): if isinstance(node, float): if node.is_integer(): @@ -328,7 +331,10 @@ def handle_functionID_node(visitor, node, *args): def handle_indexTemplate_node(visitor, node, *args): - return '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' % (node._group, visitor.setMap[node._set]) + return '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' % ( + node._group, + visitor.setMap[node._set], + ) def handle_numericGIE_node(visitor, node, *args): @@ -347,16 +353,19 @@ def handle_numericGIE_node(visitor, node, *args): def handle_templateSumExpression_node(visitor, node, *args): pstr = '' - for i in range(0,len(node._iters)): - pstr += '\\sum_{__S_PLACEHOLDER_8675309_GROUP_%s_%s__} ' %(node._iters[i][0]._group, visitor.setMap[node._iters[i][0]._set]) + for i in range(0, len(node._iters)): + pstr += '\\sum_{__S_PLACEHOLDER_8675309_GROUP_%s_%s__} ' % ( + node._iters[i][0]._group, + visitor.setMap[node._iters[i][0]._set], + ) pstr += args[0] return pstr -def handle_param_node(visitor,node): - return visitor.parameterMap[node] +def handle_param_node(visitor, node): + return visitor.parameterMap[node] class _LatexVisitor(StreamBasedExpressionVisitor): @@ -441,6 +450,7 @@ def applySmartVariables(name): return joinedName + def analyze_variable(vr, visitor): domainMap = { 'Reals': '\\mathds{R}', @@ -492,8 +502,7 @@ def analyze_variable(vr, visitor): if upperBoundValue is not None: if upperBoundValue <= 0: raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' - % (vr.name) + 'Formulation is infeasible due to bounds on variable %s' % (vr.name) ) else: upperBound = ' \\leq ' + str(upperBoundValue) @@ -504,8 +513,7 @@ def analyze_variable(vr, visitor): if lowerBoundValue is not None: if lowerBoundValue > 0: raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' - % (vr.name) + 'Formulation is infeasible due to bounds on variable %s' % (vr.name) ) elif lowerBoundValue == 0: lowerBound = ' 0 = ' @@ -526,8 +534,7 @@ def analyze_variable(vr, visitor): if lowerBoundValue is not None: if lowerBoundValue >= 0: raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' - % (vr.name) + 'Formulation is infeasible due to bounds on variable %s' % (vr.name) ) else: lowerBound = str(lowerBoundValue) + ' \\leq ' @@ -554,8 +561,7 @@ def analyze_variable(vr, visitor): if upperBoundValue is not None: if upperBoundValue < 0: raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' - % (vr.name) + 'Formulation is infeasible due to bounds on variable %s' % (vr.name) ) elif upperBoundValue == 0: upperBound = ' = 0 ' @@ -564,13 +570,7 @@ def analyze_variable(vr, visitor): else: upperBound = '' - elif domainName in [ - 'Boolean', - 'Binary', - 'Any', - 'AnyWithNone', - 'EmptySet', - ]: + elif domainName in ['Boolean', 'Binary', 'Any', 'AnyWithNone', 'EmptySet']: lowerBound = '' upperBound = '' @@ -580,8 +580,7 @@ def analyze_variable(vr, visitor): lowerBound = str(lowerBoundValue) + ' \\leq ' elif lowerBoundValue > 1: raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' - % (vr.name) + 'Formulation is infeasible due to bounds on variable %s' % (vr.name) ) elif lowerBoundValue == 1: lowerBound = ' = 1 ' @@ -595,8 +594,7 @@ def analyze_variable(vr, visitor): upperBound = ' \\leq ' + str(upperBoundValue) elif upperBoundValue < 0: raise ValueError( - 'Formulation is infeasible due to bounds on variable %s' - % (vr.name) + 'Formulation is infeasible due to bounds on variable %s' % (vr.name) ) elif upperBoundValue == 0: upperBound = ' = 0 ' @@ -606,16 +604,14 @@ def analyze_variable(vr, visitor): upperBound = ' \\leq 1 ' else: - raise ValueError( - 'Domain %s not supported by the latex printer' % (domainName) - ) + raise ValueError('Domain %s not supported by the latex printer' % (domainName)) varBoundData = { - 'variable' : vr, - 'lowerBound' : lowerBound, - 'upperBound' : upperBound, - 'domainName' : domainName, - 'domainLatex' : domainMap[domainName], + 'variable': vr, + 'lowerBound': lowerBound, + 'upperBound': upperBound, + 'domainName': domainName, + 'domainLatex': domainMap[domainName], } return varBoundData @@ -730,6 +726,57 @@ def latex_printer( % (str(type(pyomo_component))) ) + if isSingle: + temp_comp, temp_indexes = templatize_fcn(pyomo_component) + variableList = [] + for v in identify_components( + temp_comp, [ScalarVar, _GeneralVarData, IndexedVar] + ): + if isinstance(v, _GeneralVarData): + v_write = v.parent_component() + if v_write not in ComponentSet(variableList): + variableList.append(v_write) + else: + if v not in ComponentSet(variableList): + variableList.append(v) + + parameterList = [] + for p in identify_components( + temp_comp, [ScalarParam, _ParamData, IndexedParam] + ): + if isinstance(p, _ParamData): + p_write = p.parent_component() + if p_write not in ComponentSet(parameterList): + parameterList.append(p_write) + else: + if p not in ComponentSet(parameterList): + parameterList.append(p) + + # TODO: cannot extract this information, waiting on resolution of an issue + # setList = identify_components(pyomo_component.expr, pyo.Set) + + else: + variableList = [ + vr + for vr in pyomo_component.component_objects( + pyo.Var, descend_into=True, active=True + ) + ] + + parameterList = [ + pm + for pm in pyomo_component.component_objects( + pyo.Param, descend_into=True, active=True + ) + ] + + setList = [ + st + for st in pyomo_component.component_objects( + pyo.Set, descend_into=True, active=True + ) + ] + forallTag = ' \\qquad \\forall' descriptorDict = {} @@ -750,19 +797,12 @@ def latex_printer( # Declare a visitor/walker visitor = _LatexVisitor() - - variableList = [ - vr - for vr in pyomo_component.component_objects( - pyo.Var, descend_into=True, active=True - ) - ] variableMap = ComponentMap() vrIdx = 0 - for i in range(0,len(variableList)): + for i in range(0, len(variableList)): vr = variableList[i] vrIdx += 1 - if isinstance(vr ,ScalarVar): + if isinstance(vr, ScalarVar): variableMap[vr] = 'x_' + str(vrIdx) elif isinstance(vr, IndexedVar): variableMap[vr] = 'x_' + str(vrIdx) @@ -770,22 +810,17 @@ def latex_printer( vrIdx += 1 variableMap[vr[sd]] = 'x_' + str(vrIdx) else: - raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers') + raise DeveloperError( + 'Variable is not a variable. Should not happen. Contact developers' + ) visitor.variableMap = variableMap - parameterList = [ - pm - for pm in pyomo_component.component_objects( - pyo.Param, descend_into=True, active=True - ) - ] - parameterMap = ComponentMap() pmIdx = 0 - for i in range(0,len(parameterList)): + for i in range(0, len(parameterList)): vr = parameterList[i] pmIdx += 1 - if isinstance(vr ,ScalarParam): + if isinstance(vr, ScalarParam): parameterMap[vr] = 'p_' + str(pmIdx) elif isinstance(vr, IndexedParam): parameterMap[vr] = 'p_' + str(pmIdx) @@ -793,19 +828,15 @@ def latex_printer( pmIdx += 1 parameterMap[vr[sd]] = 'p_' + str(pmIdx) else: - raise DeveloperError( 'Parameter is not a parameter. Should not happen. Contact developers') + raise DeveloperError( + 'Parameter is not a parameter. Should not happen. Contact developers' + ) visitor.parameterMap = parameterMap - setList = [ - st - for st in pyomo_component.component_objects( - pyo.Set, descend_into=True, active=True - ) - ] setMap = ComponentMap() - for i in range(0,len(setList)): + for i in range(0, len(setList)): st = setList[i] - setMap[st] = 'SET' + str(i+1) + setMap[st] = 'SET' + str(i + 1) visitor.setMap = setMap # starts building the output string @@ -825,7 +856,13 @@ def latex_printer( # Iterate over the objectives and print for obj in objectives: - obj_template, obj_indices = templatize_fcn(obj) + try: + obj_template, obj_indices = templatize_fcn(obj) + except: + raise RuntimeError( + "An objective has been constructed that cannot be templatized" + ) + if obj.sense == 1: pstr += ' ' * tbSpc + '& %s \n' % (descriptorDict['minimize']) else: @@ -869,7 +906,12 @@ def latex_printer( # grab the constraint and templatize con = constraints[i] - con_template, indices = templatize_fcn(con) + try: + con_template, indices = templatize_fcn(con) + except: + raise RuntimeError( + "A constraint has been constructed that cannot be templatized" + ) # Walk the constraint conLine = ( @@ -917,7 +959,7 @@ def latex_printer( vr = variableList[i] if isinstance(vr, ScalarVar): varBoundDataEntry = analyze_variable(vr, visitor) - varBoundData.append( varBoundDataEntry ) + varBoundData.append(varBoundDataEntry) elif isinstance(vr, IndexedVar): varBoundData_indexedVar = [] # need to wrap in function and do individually @@ -927,21 +969,33 @@ def latex_printer( varBoundDataEntry = analyze_variable(vr[sd], visitor) varBoundData_indexedVar.append(varBoundDataEntry) globIndexedVariables = True - for j in range(0,len(varBoundData_indexedVar)-1): + for j in range(0, len(varBoundData_indexedVar) - 1): chks = [] - chks.append(varBoundData_indexedVar[j]['lowerBound']==varBoundData_indexedVar[j+1]['lowerBound']) - chks.append(varBoundData_indexedVar[j]['upperBound']==varBoundData_indexedVar[j+1]['upperBound']) - chks.append(varBoundData_indexedVar[j]['domainName']==varBoundData_indexedVar[j+1]['domainName']) + chks.append( + varBoundData_indexedVar[j]['lowerBound'] + == varBoundData_indexedVar[j + 1]['lowerBound'] + ) + chks.append( + varBoundData_indexedVar[j]['upperBound'] + == varBoundData_indexedVar[j + 1]['upperBound'] + ) + chks.append( + varBoundData_indexedVar[j]['domainName'] + == varBoundData_indexedVar[j + 1]['domainName'] + ) if not all(chks): globIndexedVariables = False break if globIndexedVariables: - varBoundData.append({'variable': vr, - 'lowerBound': varBoundData_indexedVar[0]['lowerBound'], - 'upperBound': varBoundData_indexedVar[0]['upperBound'], - 'domainName': varBoundData_indexedVar[0]['domainName'], - 'domainLatex': varBoundData_indexedVar[0]['domainLatex'], - }) + varBoundData.append( + { + 'variable': vr, + 'lowerBound': varBoundData_indexedVar[0]['lowerBound'], + 'upperBound': varBoundData_indexedVar[0]['upperBound'], + 'domainName': varBoundData_indexedVar[0]['domainName'], + 'domainLatex': varBoundData_indexedVar[0]['domainLatex'], + } + ) else: varBoundData += varBoundData_indexedVar else: @@ -953,11 +1007,15 @@ def latex_printer( bstr = '' appendBoundString = False useThreeAlgn = False - for i in range(0,len(varBoundData)): - vbd = varBoundData[i] - if vbd['lowerBound'] == '' and vbd['upperBound'] == '' and vbd['domainName']=='Reals': + for i in range(0, len(varBoundData)): + vbd = varBoundData[i] + if ( + vbd['lowerBound'] == '' + and vbd['upperBound'] == '' + and vbd['domainName'] == 'Reals' + ): # unbounded all real, do not print - if i <= len(varBoundData)-2: + if i <= len(varBoundData) - 2: bstr = bstr[0:-2] else: if not useThreeAlgn: @@ -969,12 +1027,28 @@ def latex_printer( if use_equation_environment: conLabel = '' else: - conLabel = ' \\label{con:' + pyomo_component.name + '_' + variableMap[vbd['variable']] + '_bound' + '} ' + conLabel = ( + ' \\label{con:' + + pyomo_component.name + + '_' + + variableMap[vbd['variable']] + + '_bound' + + '} ' + ) appendBoundString = True - coreString = vbd['lowerBound'] + variableMap[vbd['variable']] + vbd['upperBound'] + ' ' + trailingAligner + '\\qquad \\in ' + vbd['domainLatex'] + conLabel + coreString = ( + vbd['lowerBound'] + + variableMap[vbd['variable']] + + vbd['upperBound'] + + ' ' + + trailingAligner + + '\\qquad \\in ' + + vbd['domainLatex'] + + conLabel + ) bstr += ' ' * tbSpc + algn + ' %s' % (coreString) - if i <= len(varBoundData)-2: + if i <= len(varBoundData) - 2: bstr += '\\\\ \n' else: bstr += '\n' @@ -996,26 +1070,28 @@ def latex_printer( # Handling the iterator indices defaultSetLatexNames = ComponentMap() - for i in range(0,len(setList)): + for i in range(0, len(setList)): st = setList[i] if use_smart_variables: - chkName = setList[i].name - if len(chkName)==1 and chkName.upper() == chkName: + chkName = setList[i].name + if len(chkName) == 1 and chkName.upper() == chkName: chkName += '_mathcal' - defaultSetLatexNames[st] = applySmartVariables( chkName ) + defaultSetLatexNames[st] = applySmartVariables(chkName) else: - defaultSetLatexNames[st] = setList[i].name.replace('_','\\_') + defaultSetLatexNames[st] = setList[i].name.replace('_', '\\_') ## Could be used in the future if someone has a lot of sets # defaultSetLatexNames[st] = 'mathcal{' + alphabetStringGenerator(i).upper() + '}' if st in overwrite_dict.keys(): if use_smart_variables: - defaultSetLatexNames[st] = applySmartVariables( overwrite_dict[st][0] ) + defaultSetLatexNames[st] = applySmartVariables(overwrite_dict[st][0]) else: - defaultSetLatexNames[st] = overwrite_dict[st][0].replace('_','\\_') + defaultSetLatexNames[st] = overwrite_dict[st][0].replace('_', '\\_') - defaultSetLatexNames[st] = defaultSetLatexNames[st].replace('\\mathcal',r'\\mathcal') + defaultSetLatexNames[st] = defaultSetLatexNames[st].replace( + '\\mathcal', r'\\mathcal' + ) latexLines = pstr.split('\n') for jj in range(0, len(latexLines)): @@ -1037,17 +1113,23 @@ def latex_printer( # Determine if the set is continuous setInfo = dict( - zip(uniqueSets, [{'continuous':False} for i in range(0, len(uniqueSets))]) + zip( + uniqueSets, + [{'continuous': False} for i in range(0, len(uniqueSets))], + ) ) for ky, vl in setInfo.items(): - ix = int(ky[3:])-1 + ix = int(ky[3:]) - 1 setInfo[ky]['setObject'] = setList[ix] - setInfo[ky]['setRegEx'] = r'__S_PLACEHOLDER_8675309_GROUP_([0-9*])_%s__'%(ky) - setInfo[ky]['sumSetRegEx'] = r'sum_{__S_PLACEHOLDER_8675309_GROUP_([0-9*])_%s__}'%(ky) + setInfo[ky][ + 'setRegEx' + ] = r'__S_PLACEHOLDER_8675309_GROUP_([0-9*])_%s__' % (ky) + setInfo[ky][ + 'sumSetRegEx' + ] = r'sum_{__S_PLACEHOLDER_8675309_GROUP_([0-9*])_%s__}' % (ky) # setInfo[ky]['idxRegEx'] = r'__I_PLACEHOLDER_8675309_GROUP_[0-9*]_%s__'%(ky) - if split_continuous_sets: for ky, vl in setInfo.items(): st = vl['setObject'] @@ -1059,7 +1141,6 @@ def latex_printer( break setInfo[ky]['continuous'] = stCont - # replace the sets for ky, vl in setInfo.items(): # if the set is continuous and the flag has been set @@ -1069,45 +1150,66 @@ def latex_printer( bgn = stData[0] ed = stData[-1] - replacement = r'sum_{ __I_PLACEHOLDER_8675309_GROUP_\1_%s__ = %d }^{%d}'%(ky,bgn,ed) + replacement = ( + r'sum_{ __I_PLACEHOLDER_8675309_GROUP_\1_%s__ = %d }^{%d}' + % (ky, bgn, ed) + ) ln = re.sub(setInfo[ky]['sumSetRegEx'], replacement, ln) else: # if the set is not continuous or the flag has not been set - replacement = r'sum_{ __I_PLACEHOLDER_8675309_GROUP_\1_%s__ \\in __S_PLACEHOLDER_8675309_GROUP_\1_%s__ }'%(ky,ky) - ln = re.sub(setInfo[ky]['sumSetRegEx'], replacement, ln) + replacement = ( + r'sum_{ __I_PLACEHOLDER_8675309_GROUP_\1_%s__ \\in __S_PLACEHOLDER_8675309_GROUP_\1_%s__ }' + % (ky, ky) + ) + ln = re.sub(setInfo[ky]['sumSetRegEx'], replacement, ln) replacement = defaultSetLatexNames[setInfo[ky]['setObject']] - ln = re.sub(setInfo[ky]['setRegEx'],replacement,ln) + ln = re.sub(setInfo[ky]['setRegEx'], replacement, ln) # groupNumbers = re.findall(r'__I_PLACEHOLDER_8675309_GROUP_([0-9*])_SET[0-9]*__',ln) - setNumbers = re.findall(r'__I_PLACEHOLDER_8675309_GROUP_[0-9*]_SET([0-9]*)__',ln) - groupSetPairs = re.findall(r'__I_PLACEHOLDER_8675309_GROUP_([0-9*])_SET([0-9]*)__',ln) + setNumbers = re.findall( + r'__I_PLACEHOLDER_8675309_GROUP_[0-9*]_SET([0-9]*)__', ln + ) + groupSetPairs = re.findall( + r'__I_PLACEHOLDER_8675309_GROUP_([0-9*])_SET([0-9]*)__', ln + ) groupInfo = {} for vl in setNumbers: - groupInfo['SET'+vl] = {'setObject':setInfo['SET'+vl]['setObject'], 'indices':[]} + groupInfo['SET' + vl] = { + 'setObject': setInfo['SET' + vl]['setObject'], + 'indices': [], + } for gp in groupSetPairs: - if gp[0] not in groupInfo['SET'+gp[1]]['indices']: - groupInfo['SET'+gp[1]]['indices'].append(gp[0]) + if gp[0] not in groupInfo['SET' + gp[1]]['indices']: + groupInfo['SET' + gp[1]]['indices'].append(gp[0]) - indexCounter = 0 for ky, vl in groupInfo.items(): if vl['setObject'] in overwrite_dict.keys(): indexNames = overwrite_dict[vl['setObject']][1] if len(indexNames) < len(vl['indices']): - raise ValueError('Insufficient number of indices provided to the overwite dictionary for set %s'%(vl['setObject'].name)) - for i in range(0,len(indexNames)): - ln = ln.replace('__I_PLACEHOLDER_8675309_GROUP_%s_%s__'%(vl['indices'][i],ky),indexNames[i]) + raise ValueError( + 'Insufficient number of indices provided to the overwrite dictionary for set %s' + % (vl['setObject'].name) + ) + for i in range(0, len(indexNames)): + ln = ln.replace( + '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' + % (vl['indices'][i], ky), + indexNames[i], + ) else: - for i in range(0,len(vl['indices'])): - ln = ln.replace('__I_PLACEHOLDER_8675309_GROUP_%s_%s__'%(vl['indices'][i],ky),alphabetStringGenerator(indexCounter,True)) + for i in range(0, len(vl['indices'])): + ln = ln.replace( + '__I_PLACEHOLDER_8675309_GROUP_%s_%s__' + % (vl['indices'][i], ky), + alphabetStringGenerator(indexCounter, True), + ) indexCounter += 1 - # print('gn',groupInfo) - latexLines[jj] = ln @@ -1115,18 +1217,21 @@ def latex_printer( # pstr = pstr.replace('\\mathcal{', 'mathcal{') # pstr = pstr.replace('mathcal{', '\\mathcal{') - if x_only_mode in [1,2,3]: - # Need to preserve only the set elments in the overwrite_dict + if x_only_mode in [1, 2, 3]: + # Need to preserve only the set elements in the overwrite_dict new_overwrite_dict = {} for ky, vl in overwrite_dict.items(): - if isinstance(ky,_GeneralVarData): + if isinstance(ky, _GeneralVarData): pass - elif isinstance(ky,_ParamData): + elif isinstance(ky, _ParamData): pass - elif isinstance(ky,_SetData): + elif isinstance(ky, _SetData): new_overwrite_dict[ky] = overwrite_dict[ky] else: - raise ValueError('The overwrite_dict object has a key of invalid type: %s'%(str(ky))) + raise ValueError( + 'The overwrite_dict object has a key of invalid type: %s' + % (str(ky)) + ) overwrite_dict = new_overwrite_dict # # Only x modes @@ -1138,42 +1243,50 @@ def latex_printer( if x_only_mode == 1: vrIdx = 0 new_variableMap = ComponentMap() - for i in range(0,len(variableList)): + for i in range(0, len(variableList)): vr = variableList[i] vrIdx += 1 - if isinstance(vr ,ScalarVar): + if isinstance(vr, ScalarVar): new_variableMap[vr] = 'x_{' + str(vrIdx) + '}' elif isinstance(vr, IndexedVar): new_variableMap[vr] = 'x_{' + str(vrIdx) + '}' for sd in vr.index_set().data(): # vrIdx += 1 sdString = str(sd) - if sdString[0]=='(': + if sdString[0] == '(': sdString = sdString[1:] - if sdString[-1]==')': + if sdString[-1] == ')': sdString = sdString[0:-1] - new_variableMap[vr[sd]] = 'x_{' + str(vrIdx) + '_{' + sdString + '}' + '}' + new_variableMap[vr[sd]] = ( + 'x_{' + str(vrIdx) + '_{' + sdString + '}' + '}' + ) else: - raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers') + raise DeveloperError( + 'Variable is not a variable. Should not happen. Contact developers' + ) pmIdx = 0 new_parameterMap = ComponentMap() - for i in range(0,len(parameterList)): + for i in range(0, len(parameterList)): pm = parameterList[i] pmIdx += 1 - if isinstance(pm ,ScalarParam): + if isinstance(pm, ScalarParam): new_parameterMap[pm] = 'p_{' + str(pmIdx) + '}' elif isinstance(pm, IndexedParam): new_parameterMap[pm] = 'p_{' + str(pmIdx) + '}' for sd in pm.index_set().data(): sdString = str(sd) - if sdString[0]=='(': + if sdString[0] == '(': sdString = sdString[1:] - if sdString[-1]==')': + if sdString[-1] == ')': sdString = sdString[0:-1] - new_parameterMap[pm[sd]] = 'p_{' + str(pmIdx) + '_{' + sdString + '}' + '}' + new_parameterMap[pm[sd]] = ( + 'p_{' + str(pmIdx) + '_{' + sdString + '}' + '}' + ) else: - raise DeveloperError( 'Parameter is not a parameter. Should not happen. Contact developers') + raise DeveloperError( + 'Parameter is not a parameter. Should not happen. Contact developers' + ) new_overwrite_dict = ComponentMap() for ky, vl in new_variableMap.items(): @@ -1187,42 +1300,50 @@ def latex_printer( elif x_only_mode == 2: vrIdx = 0 new_variableMap = ComponentMap() - for i in range(0,len(variableList)): + for i in range(0, len(variableList)): vr = variableList[i] vrIdx += 1 - if isinstance(vr ,ScalarVar): + if isinstance(vr, ScalarVar): new_variableMap[vr] = alphabetStringGenerator(i) elif isinstance(vr, IndexedVar): new_variableMap[vr] = alphabetStringGenerator(i) for sd in vr.index_set().data(): # vrIdx += 1 sdString = str(sd) - if sdString[0]=='(': + if sdString[0] == '(': sdString = sdString[1:] - if sdString[-1]==')': + if sdString[-1] == ')': sdString = sdString[0:-1] - new_variableMap[vr[sd]] = alphabetStringGenerator(i) + '_{' + sdString + '}' + new_variableMap[vr[sd]] = ( + alphabetStringGenerator(i) + '_{' + sdString + '}' + ) else: - raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers') + raise DeveloperError( + 'Variable is not a variable. Should not happen. Contact developers' + ) - pmIdx = vrIdx-1 + pmIdx = vrIdx - 1 new_parameterMap = ComponentMap() - for i in range(0,len(parameterList)): + for i in range(0, len(parameterList)): pm = parameterList[i] pmIdx += 1 - if isinstance(pm ,ScalarParam): + if isinstance(pm, ScalarParam): new_parameterMap[pm] = alphabetStringGenerator(pmIdx) elif isinstance(pm, IndexedParam): new_parameterMap[pm] = alphabetStringGenerator(pmIdx) for sd in pm.index_set().data(): sdString = str(sd) - if sdString[0]=='(': + if sdString[0] == '(': sdString = sdString[1:] - if sdString[-1]==')': + if sdString[-1] == ')': sdString = sdString[0:-1] - new_parameterMap[pm[sd]] = alphabetStringGenerator(pmIdx) + '_{' + sdString + '}' + new_parameterMap[pm[sd]] = ( + alphabetStringGenerator(pmIdx) + '_{' + sdString + '}' + ) else: - raise DeveloperError( 'Parameter is not a parameter. Should not happen. Contact developers') + raise DeveloperError( + 'Parameter is not a parameter. Should not happen. Contact developers' + ) new_overwrite_dict = ComponentMap() for ky, vl in new_variableMap.items(): @@ -1243,52 +1364,60 @@ def latex_printer( new_overwrite_dict[ky] = vl overwrite_dict = new_overwrite_dict - else: + else: vrIdx = 0 new_variableMap = ComponentMap() - for i in range(0,len(variableList)): + for i in range(0, len(variableList)): vr = variableList[i] vrIdx += 1 - if isinstance(vr ,ScalarVar): + if isinstance(vr, ScalarVar): new_variableMap[vr] = vr.name elif isinstance(vr, IndexedVar): new_variableMap[vr] = vr.name for sd in vr.index_set().data(): # vrIdx += 1 sdString = str(sd) - if sdString[0]=='(': + if sdString[0] == '(': sdString = sdString[1:] - if sdString[-1]==')': + if sdString[-1] == ')': sdString = sdString[0:-1] if use_smart_variables: - new_variableMap[vr[sd]] = applySmartVariables(vr.name + '_' + sdString ) + new_variableMap[vr[sd]] = applySmartVariables( + vr.name + '_' + sdString + ) else: new_variableMap[vr[sd]] = vr[sd].name else: - raise DeveloperError( 'Variable is not a variable. Should not happen. Contact developers') + raise DeveloperError( + 'Variable is not a variable. Should not happen. Contact developers' + ) pmIdx = 0 new_parameterMap = ComponentMap() - for i in range(0,len(parameterList)): + for i in range(0, len(parameterList)): pm = parameterList[i] pmIdx += 1 - if isinstance(pm ,ScalarParam): + if isinstance(pm, ScalarParam): new_parameterMap[pm] = pm.name elif isinstance(pm, IndexedParam): new_parameterMap[pm] = pm.name for sd in pm.index_set().data(): # pmIdx += 1 sdString = str(sd) - if sdString[0]=='(': + if sdString[0] == '(': sdString = sdString[1:] - if sdString[-1]==')': + if sdString[-1] == ')': sdString = sdString[0:-1] if use_smart_variables: - new_parameterMap[pm[sd]] = applySmartVariables(pm.name + '_' + sdString ) + new_parameterMap[pm[sd]] = applySmartVariables( + pm.name + '_' + sdString + ) else: - new_parameterMap[pm[sd]] = str(pm[sd])#.name + new_parameterMap[pm[sd]] = str(pm[sd]) # .name else: - raise DeveloperError( 'Parameter is not a parameter. Should not happen. Contact developers') + raise DeveloperError( + 'Parameter is not a parameter. Should not happen. Contact developers' + ) for ky, vl in new_variableMap.items(): if ky not in overwrite_dict.keys(): @@ -1299,44 +1428,46 @@ def latex_printer( rep_dict = {} for ky in list(reversed(list(overwrite_dict.keys()))): - if isinstance(ky,(pyo.Var,pyo.Param)): - if use_smart_variables and x_only_mode in [0,3]: + if isinstance(ky, (pyo.Var, pyo.Param)): + if use_smart_variables and x_only_mode in [0, 3]: overwrite_value = applySmartVariables(overwrite_dict[ky]) else: overwrite_value = overwrite_dict[ky] rep_dict[variableMap[ky]] = overwrite_value - elif isinstance(ky,(_GeneralVarData,_ParamData)): + elif isinstance(ky, (_GeneralVarData, _ParamData)): if use_smart_variables and x_only_mode in [3]: overwrite_value = applySmartVariables(overwrite_dict[ky]) else: overwrite_value = overwrite_dict[ky] rep_dict[variableMap[ky]] = overwrite_value - elif isinstance(ky,_SetData): + elif isinstance(ky, _SetData): # already handled pass - elif isinstance(ky,(float,int)): + elif isinstance(ky, (float, int)): # happens when immutable parameters are used, do nothing pass else: - raise ValueError('The overwrite_dict object has a key of invalid type: %s'%(str(ky))) + raise ValueError( + 'The overwrite_dict object has a key of invalid type: %s' % (str(ky)) + ) if not use_smart_variables: for ky, vl in rep_dict.items(): - rep_dict[ky] = vl.replace('_','\\_') + rep_dict[ky] = vl.replace('_', '\\_') label_rep_dict = copy.deepcopy(rep_dict) for ky, vl in label_rep_dict.items(): - label_rep_dict[ky] = vl.replace('{','').replace('}','').replace('\\','') + label_rep_dict[ky] = vl.replace('{', '').replace('}', '').replace('\\', '') splitLines = pstr.split('\n') - for i in range(0,len(splitLines)): + for i in range(0, len(splitLines)): if use_equation_environment: - splitLines[i] = multiple_replace(splitLines[i],rep_dict) + splitLines[i] = multiple_replace(splitLines[i], rep_dict) else: if '\\label{' in splitLines[i]: epr, lbl = splitLines[i].split('\\label{') - epr = multiple_replace(epr,rep_dict) - lbl = multiple_replace(lbl,label_rep_dict) + epr = multiple_replace(epr, rep_dict) + lbl = multiple_replace(lbl, label_rep_dict) splitLines[i] = epr + '\\label{' + lbl pstr = '\n'.join(splitLines) @@ -1357,7 +1488,6 @@ def latex_printer( pstr = '\n'.join(finalLines) - # optional write to output file if filename is not None: fstr = '' From 13a72133f5be5b612a4585dd989d32f3fc7435f8 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 19 Sep 2023 19:33:25 -0400 Subject: [PATCH 032/148] remove the support of greybox in LP/NLP gurobi --- pyomo/contrib/mindtpy/cut_generation.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/pyomo/contrib/mindtpy/cut_generation.py b/pyomo/contrib/mindtpy/cut_generation.py index 5613155ee7d..dd60b004830 100644 --- a/pyomo/contrib/mindtpy/cut_generation.py +++ b/pyomo/contrib/mindtpy/cut_generation.py @@ -215,17 +215,18 @@ def add_oa_cuts_for_grey_box( <= 0 ) # TODO: gurobi_persistent currently does not support greybox model. - if ( - config.single_tree - and config.mip_solver == 'gurobi_persistent' - and mip_iter > 0 - and cb_opt is not None - ): - cb_opt.cbLazy( - target_model.MindtPy_utils.cuts.oa_cuts[ - len(target_model.MindtPy_utils.cuts.oa_cuts) - ] - ) + # https://github.com/Pyomo/pyomo/issues/3000 + # if ( + # config.single_tree + # and config.mip_solver == 'gurobi_persistent' + # and mip_iter > 0 + # and cb_opt is not None + # ): + # cb_opt.cbLazy( + # target_model.MindtPy_utils.cuts.oa_cuts[ + # len(target_model.MindtPy_utils.cuts.oa_cuts) + # ] + # ) def add_ecp_cuts( From 20ccbdc5663fef2280408cda46d68bc0c21eebae Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 19 Sep 2023 19:41:48 -0400 Subject: [PATCH 033/148] black format --- pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py index d2a55c67f48..945e9a05f51 100644 --- a/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py +++ b/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.py @@ -426,7 +426,7 @@ def load_state_into_pyomo(self, bound_multipliers=None): # not we are minimizing or maximizing - this is done in the ASL interface # for ipopt, but does not appear to be done in cyipopt. obj_sign = 1.0 - # since we will assert the number of objective functions, + # since we will assert the number of objective functions, # we only focus on active objective function. objs = list( m.component_data_objects( From 4288e338459a8d5b785403daf4a30de87ed1a9e9 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 20 Sep 2023 11:54:35 -0400 Subject: [PATCH 034/148] update import source --- pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py index 562e88ea667..00b78e7b89f 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py @@ -1,6 +1,5 @@ -import numpy as np -import pyomo.environ as pyo -from scipy.sparse import coo_matrix +from pyomo.common.dependencies import numpy as np +from pyomo.common.dependencies.scipy.sparse import coo_matrix from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxModel from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxBlock From 8d7f6c5915f881dbdf272cef2ccc83ead5772450 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 20 Sep 2023 12:06:49 -0400 Subject: [PATCH 035/148] remove redundant import --- pyomo/contrib/pynumero/interfaces/external_grey_box.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/external_grey_box.py b/pyomo/contrib/pynumero/interfaces/external_grey_box.py index 8fd728a7c9b..3684a6c3234 100644 --- a/pyomo/contrib/pynumero/interfaces/external_grey_box.py +++ b/pyomo/contrib/pynumero/interfaces/external_grey_box.py @@ -11,8 +11,6 @@ import abc import logging -import numpy as np -from scipy.sparse import coo_matrix from pyomo.common.deprecation import RenamedClass from pyomo.common.log import is_debug_set From 807e4f5fea8f91b09680ec4cea2a84b1a5fa45fd Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 20 Sep 2023 14:29:27 -0400 Subject: [PATCH 036/148] add config check for load_solutions --- pyomo/contrib/mindtpy/algorithm_base_class.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pyomo/contrib/mindtpy/algorithm_base_class.py b/pyomo/contrib/mindtpy/algorithm_base_class.py index e1d6d3e98ba..f3877304adb 100644 --- a/pyomo/contrib/mindtpy/algorithm_base_class.py +++ b/pyomo/contrib/mindtpy/algorithm_base_class.py @@ -817,7 +817,6 @@ def init_rNLP(self, add_oa_cuts=True): nlp_args = dict(config.nlp_solver_args) update_solver_timelimit(self.nlp_opt, config.nlp_solver, self.timing, config) with SuppressInfeasibleWarning(): - print('solving rnlp') results = self.nlp_opt.solve( self.rnlp, tee=config.nlp_solver_tee, @@ -2236,6 +2235,17 @@ def check_config(self): config.logger.info("Solution pool does not support APPSI solver.") config.mip_solver = 'cplex_persistent' + # related to https://github.com/Pyomo/pyomo/issues/2363 + if ( + 'appsi' in config.mip_solver + or 'appsi' in config.nlp_solver + or ( + config.mip_regularization_solver is not None + and 'appsi' in config.mip_regularization_solver + ) + ): + config.load_solutions = False + ################################################################################################################################ # Feasibility Pump From 30771b332f866bd861abf339c042114a88e7e0c1 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 20 Sep 2023 14:35:29 -0400 Subject: [PATCH 037/148] recover numpy and scipy import --- pyomo/contrib/pynumero/interfaces/external_grey_box.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyomo/contrib/pynumero/interfaces/external_grey_box.py b/pyomo/contrib/pynumero/interfaces/external_grey_box.py index 3684a6c3234..8fd728a7c9b 100644 --- a/pyomo/contrib/pynumero/interfaces/external_grey_box.py +++ b/pyomo/contrib/pynumero/interfaces/external_grey_box.py @@ -11,6 +11,8 @@ import abc import logging +import numpy as np +from scipy.sparse import coo_matrix from pyomo.common.deprecation import RenamedClass from pyomo.common.log import is_debug_set From 72b142baa8053644b2eca30e21db9f01d71fce68 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 20 Sep 2023 14:43:05 -0400 Subject: [PATCH 038/148] change numpy and scipy import --- pyomo/contrib/pynumero/interfaces/external_grey_box.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/external_grey_box.py b/pyomo/contrib/pynumero/interfaces/external_grey_box.py index 8fd728a7c9b..1594098069c 100644 --- a/pyomo/contrib/pynumero/interfaces/external_grey_box.py +++ b/pyomo/contrib/pynumero/interfaces/external_grey_box.py @@ -11,8 +11,8 @@ import abc import logging -import numpy as np -from scipy.sparse import coo_matrix +from pyomo.common.dependencies import numpy as np +from pyomo.common.dependencies.scipy.sparse import coo_matrix from pyomo.common.deprecation import RenamedClass from pyomo.common.log import is_debug_set From ff401df0a9e732f80635a1a7af5abc43c2176bff Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 20 Sep 2023 22:34:31 -0400 Subject: [PATCH 039/148] change scipy import --- pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py | 4 ++-- pyomo/contrib/pynumero/interfaces/external_grey_box.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py index 00b78e7b89f..186db3bb5a2 100644 --- a/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py +++ b/pyomo/contrib/mindtpy/tests/MINLP_simple_grey_box.py @@ -1,5 +1,5 @@ from pyomo.common.dependencies import numpy as np -from pyomo.common.dependencies.scipy.sparse import coo_matrix +import pyomo.common.dependencies.scipy.sparse as scipy_sparse from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxModel from pyomo.contrib.pynumero.interfaces.external_grey_box import ExternalGreyBoxBlock @@ -135,4 +135,4 @@ def evaluate_jacobian_outputs(self): row[0], col[4], data[4] = (0, 4, 0.5) # y3 # sparse matrix - return coo_matrix((data, (row, col)), shape=(1, 5)) + return scipy_sparse.coo_matrix((data, (row, col)), shape=(1, 5)) diff --git a/pyomo/contrib/pynumero/interfaces/external_grey_box.py b/pyomo/contrib/pynumero/interfaces/external_grey_box.py index 1594098069c..8fd728a7c9b 100644 --- a/pyomo/contrib/pynumero/interfaces/external_grey_box.py +++ b/pyomo/contrib/pynumero/interfaces/external_grey_box.py @@ -11,8 +11,8 @@ import abc import logging -from pyomo.common.dependencies import numpy as np -from pyomo.common.dependencies.scipy.sparse import coo_matrix +import numpy as np +from scipy.sparse import coo_matrix from pyomo.common.deprecation import RenamedClass from pyomo.common.log import is_debug_set From d82dcde63ebcb402d626b85608f02ae4325cdc4d Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Thu, 21 Sep 2023 17:59:11 -0400 Subject: [PATCH 040/148] 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 041/148] 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 042/148] 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 043/148] 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 e417da635959722de364047bd1c59dbee658e06a Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Fri, 22 Sep 2023 00:49:16 -0600 Subject: [PATCH 044/148] 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 045/148] 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 046/148] 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 047/148] 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 e9232d228767bd2cc823832d105ea1f3f71a2353 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Mon, 25 Sep 2023 20:27:28 -0600 Subject: [PATCH 048/148] 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 a523eea90f835b79cc62c1a63da1a2382733da7a Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 10 Oct 2023 12:08:13 -0600 Subject: [PATCH 049/148] 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 050/148] 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 051/148] 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 052/148] 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 6b4c1eac538077387423cde3e87bcc8b3a45cc6c Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 10 Oct 2023 14:46:53 -0600 Subject: [PATCH 053/148] 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 054/148] 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 055/148] 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 056/148] 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 057/148] 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 058/148] 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 059/148] 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 060/148] 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 061/148] 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 062/148] 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 063/148] 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 064/148] 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 065/148] 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 5e7c0b118906e42b30750fbea2236c4de0af5754 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Thu, 12 Oct 2023 12:07:59 -0600 Subject: [PATCH 066/148] 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 067/148] 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 46cf8a58174d866cf8c0db28527726d17717e08a Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 31 Oct 2023 14:25:22 -0400 Subject: [PATCH 068/148] 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 069/148] 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 8eacf0b73aada09f5d187dd34b47c7d0d622a634 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 31 Oct 2023 18:50:02 -0400 Subject: [PATCH 070/148] 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 071/148] 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 072/148] 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 073/148] 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 87c4742f57f6d51052e78bd705168394baa71220 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Thu, 2 Nov 2023 12:54:00 -0600 Subject: [PATCH 074/148] 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 93fdf50731f79408c703d9aad10397c7a3c1f011 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Thu, 2 Nov 2023 13:27:59 -0600 Subject: [PATCH 075/148] 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 076/148] 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 3981f2f0cfd1ce76061cc40ad7336e8f2eca3b95 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Tue, 7 Nov 2023 14:53:01 -0600 Subject: [PATCH 077/148] updating the default behavior of failed templatization --- .../model_debugging/latex_printing.rst | 2 + pyomo/util/latex_printer.py | 29 ++++++++----- pyomo/util/tests/test_latex_printer.py | 41 +++++++++++++++++++ 3 files changed, 62 insertions(+), 10 deletions(-) diff --git a/doc/OnlineDocs/model_debugging/latex_printing.rst b/doc/OnlineDocs/model_debugging/latex_printing.rst index 63ecd09f950..99e66b2688c 100644 --- a/doc/OnlineDocs/model_debugging/latex_printing.rst +++ b/doc/OnlineDocs/model_debugging/latex_printing.rst @@ -23,6 +23,8 @@ Pyomo models can be printed to a LaTeX compatible format using the ``pyomo.util. :type fontsize: str or int :param paper_dimensions: A dictionary that controls the paper margins and size. Keys are: [ 'height', 'width', 'margin_left', 'margin_right', 'margin_top', 'margin_bottom' ]. Default is standard 8.5x11 with one inch margins. Values are in inches :type paper_dimensions: dict + :param throw_templatization_error: Option to throw an error on templatization failure rather than printing each constraint individually, useful for very large models + :type throw_templatization_error: bool :return: A LaTeX style string that represents the passed in pyomoElement diff --git a/pyomo/util/latex_printer.py b/pyomo/util/latex_printer.py index 9dcbc9f912a..750caf36b60 100644 --- a/pyomo/util/latex_printer.py +++ b/pyomo/util/latex_printer.py @@ -644,6 +644,7 @@ def latex_printer( use_short_descriptors=False, fontsize=None, paper_dimensions=None, + throw_templatization_error=False, ): """This function produces a string that can be rendered as LaTeX @@ -689,6 +690,10 @@ def latex_printer( 'margin_bottom' ]. Default is standard 8.5x11 with one inch margins. Values are in inches + throw_templatization_error: bool + Option to throw an error on templatization failure rather than + printing each constraint individually, useful for very large models + Returns ------- @@ -968,10 +973,12 @@ def latex_printer( try: obj_template, obj_indices = templatize_fcn(obj) except: - obj_template = obj - # raise RuntimeError( - # "An objective has been constructed that cannot be templatized" - # ) + if throw_templatization_error: + raise RuntimeError( + "An objective has been constructed that cannot be templatized" + ) + else: + obj_template = obj if obj.sense == 1: pstr += ' ' * tbSpc + '& %s \n' % (descriptorDict['minimize']) @@ -1023,12 +1030,14 @@ def latex_printer( con_template, indices = templatize_fcn(con) con_template_list = [con_template] except: - # con_template = con[0] - con_template_list = [c.expr for c in con.values()] - indices = [] - # raise RuntimeError( - # "A constraint has been constructed that cannot be templatized" - # ) + if throw_templatization_error: + raise RuntimeError( + "A constraint has been constructed that cannot be templatized" + ) + else: + con_template_list = [c.expr for c in con.values()] + indices = [] + for con_template in con_template_list: # Walk the constraint conLine = ( diff --git a/pyomo/util/tests/test_latex_printer.py b/pyomo/util/tests/test_latex_printer.py index 32381dcf36d..685e7e2df38 100644 --- a/pyomo/util/tests/test_latex_printer.py +++ b/pyomo/util/tests/test_latex_printer.py @@ -977,6 +977,47 @@ def ruleMaker_2(m): self.assertEqual('\n' + pstr + '\n', bstr) + def test_latexPrinter_throwTemplatizeError(self): + m = pyo.ConcreteModel(name='basicFormulation') + m.I = pyo.Set(initialize=[1, 2, 3, 4, 5]) + m.x = pyo.Var(m.I, bounds=[-10, 10]) + m.c = pyo.Param(m.I, initialize=1.0, mutable=True) + + def ruleMaker_1(m): + return sum(m.c[i] * m.x[i] for i in m.I) + + def ruleMaker_2(m, i): + if i >= 2: + return m.x[i] <= 1 + else: + return pyo.Constraint.Skip + + m.objective = pyo.Objective(rule=ruleMaker_1) + m.constraint_1 = pyo.Constraint(m.I, rule=ruleMaker_2) + self.assertRaises( + RuntimeError, + latex_printer, + **{'pyomo_component': m, 'throw_templatization_error': True} + ) + pstr = latex_printer(m) + bstr = dedent( + r""" + \begin{align} + & \text{minimize} + & & \sum_{ i \in I } c_{i} x_{i} & \label{obj:basicFormulation_objective} \\ + & \text{subject to} + & & x[2] \leq 1 & \label{con:basicFormulation_constraint_1} \\ + & & x[3] \leq 1 & \label{con:basicFormulation_constraint_1} \\ + & & x[4] \leq 1 & \label{con:basicFormulation_constraint_1} \\ + & & x[5] \leq 1 & \label{con:basicFormulation_constraint_1} \\ + & \text{with bounds} + & & -10 \leq x \leq 10 & \qquad \in \mathds{R} \label{con:basicFormulation_x_bound} + \end{align} + """ + ) + + self.assertEqual('\n' + pstr + '\n', bstr) + if __name__ == '__main__': unittest.main() From b2a6a3e7b228637f0ea0e5d81d6804696b1ce5a3 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Wed, 8 Nov 2023 11:34:10 -0600 Subject: [PATCH 078/148] 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 cd58fe68d961b5d2e8382e28c3a650019fc81f38 Mon Sep 17 00:00:00 2001 From: jasherma Date: Mon, 13 Nov 2023 13:45:55 -0500 Subject: [PATCH 079/148] 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 080/148] 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 081/148] 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 082/148] 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 45ae79f9646ae75a505fbff82a8da1c422e3b891 Mon Sep 17 00:00:00 2001 From: jasherma Date: Mon, 13 Nov 2023 16:44:30 -0500 Subject: [PATCH 083/148] 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 73f2e16ce76bfb6e84bcb0183b523d2340ff477c Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Tue, 14 Nov 2023 15:23:58 -0500 Subject: [PATCH 084/148] 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 085/148] 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 ae8455821f6f36c56ddb23bcad357b23f661c230 Mon Sep 17 00:00:00 2001 From: ZedongPeng Date: Wed, 15 Nov 2023 13:11:37 -0500 Subject: [PATCH 086/148] 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 087/148] 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 088/148] 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 cf5b2ce9fb616e6cf7edf03f47da5440aca8d96b Mon Sep 17 00:00:00 2001 From: Robert Parker Date: Wed, 15 Nov 2023 16:11:55 -0700 Subject: [PATCH 089/148] 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 abfbaffe8d356fba0b16abac330ce5bce8fb059a Mon Sep 17 00:00:00 2001 From: jasherma Date: Thu, 16 Nov 2023 23:32:27 -0500 Subject: [PATCH 090/148] 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 091/148] 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 092/148] 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 093/148] 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 094/148] 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 095/148] 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 096/148] 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 097/148] 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 098/148] 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 099/148] 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 5ff4d1428e5a61dd5a6e1d992232b34436af5487 Mon Sep 17 00:00:00 2001 From: Cody Karcher Date: Wed, 22 Nov 2023 17:17:29 -0700 Subject: [PATCH 100/148] 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 101/148] 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 102/148] 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 103/148] 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 104/148] 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 105/148] 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 106/148] 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 107/148] 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 108/148] 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 109/148] 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 110/148] 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 111/148] 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 112/148] 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 113/148] 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 114/148] 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 115/148] 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 116/148] 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 117/148] 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 4a0a1dfcb9a9aa35ad6a66258a2d8f244d8e93ab Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 27 Nov 2023 13:05:07 -0700 Subject: [PATCH 118/148] 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 119/148] 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 0590ae632fb1ce7bf9c580655c47891ec0a0523d Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Mon, 27 Nov 2023 13:50:18 -0700 Subject: [PATCH 120/148] 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 121/148] 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 122/148] 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 123/148] 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 124/148] 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 a6662cd37d03f8776a4136c16840e42a9a89f1b0 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 27 Nov 2023 16:32:04 -0700 Subject: [PATCH 125/148] 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 126/148] 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 127/148] 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 b8e06b5ad81088956000397f4e116f598d3bebf8 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Tue, 28 Nov 2023 08:18:50 -0700 Subject: [PATCH 128/148] 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 129/148] 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 130/148] 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 131/148] 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 132/148] 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 0f604f6c3595628f912358ea7ca61870f7dd1d49 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 28 Nov 2023 09:01:48 -0700 Subject: [PATCH 133/148] 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 134/148] 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 135/148] 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 136/148] 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 137/148] 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 138/148] 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 139/148] 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 140/148] 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 141/148] 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 142/148] 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 4036dfa637104bb1a0cd627471df349fe99f7ef2 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 29 Nov 2023 13:27:41 -0700 Subject: [PATCH 143/148] 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 144/148] 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 145/148] 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 11cb6d9a32bc113a71bece46fb81d2f16631201d Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 29 Nov 2023 18:32:26 -0700 Subject: [PATCH 146/148] 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 147/148] 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 148/148] 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':