diff --git a/examples/pyomo/core/t1.py b/examples/pyomo/core/t1.py deleted file mode 100644 index 784b816a89b..00000000000 --- a/examples/pyomo/core/t1.py +++ /dev/null @@ -1,27 +0,0 @@ -# ____________________________________________________________________________________ -# -# Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2026 National Technology and Engineering Solutions of Sandia, LLC -# Under the terms of Contract DE-NA0003525 with National Technology and Engineering -# Solutions of Sandia, LLC, the U.S. Government retains certain rights in this -# software. This software is distributed under the 3-clause BSD License. -# ____________________________________________________________________________________ - -import pyomo.environ as pyo - - -def pyomo_create_model(options, model_options): - model = pyo.ConcreteModel() - model.x1 = pyo.Var(within=pyo.NonNegativeReals) - model.x2 = pyo.Var(within=pyo.NonNegativeReals) - model.x3 = pyo.Var(within=pyo.NonNegativeReals) - - model.o = pyo.Objective( - expr=6 * model.x1 + 4 * model.x2 + 2 * model.x3, sense=pyo.minimize - ) - - model.c1 = pyo.Constraint(expr=4 * model.x1 + 2 * model.x2 + model.x3 >= 5) - model.c2 = pyo.Constraint(expr=model.x1 + model.x2 >= 3) - model.c3 = pyo.Constraint(expr=model.x2 + model.x3 >= 4) - - return model diff --git a/examples/pyomo/core/t2.py b/examples/pyomo/core/t2.py deleted file mode 100644 index 475a8f85665..00000000000 --- a/examples/pyomo/core/t2.py +++ /dev/null @@ -1,27 +0,0 @@ -# ____________________________________________________________________________________ -# -# Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2026 National Technology and Engineering Solutions of Sandia, LLC -# Under the terms of Contract DE-NA0003525 with National Technology and Engineering -# Solutions of Sandia, LLC, the U.S. Government retains certain rights in this -# software. This software is distributed under the 3-clause BSD License. -# ____________________________________________________________________________________ - -import pyomo.environ as pyo - - -def pyomo_create_model(options, model_options): - model = pyo.ConcreteModel() - model.x1 = pyo.Var(within=pyo.NonNegativeReals) - model.x2 = pyo.Var(within=pyo.NonPositiveReals) - model.x3 = pyo.Var(within=pyo.Reals) - - model.o = pyo.Objective( - expr=model.x1 + 2 * model.x2 + 3 * model.x3, sense=pyo.maximize - ) - - model.c1 = pyo.Constraint(expr=-model.x1 + 3 * model.x2 == 5) - model.c2 = pyo.Constraint(expr=2 * model.x1 - model.x2 + 3 * model.x3 >= 6) - model.c3 = pyo.Constraint(expr=model.x3 <= 4) - - return model diff --git a/examples/pyomo/core/t5.py b/examples/pyomo/core/t5.py deleted file mode 100644 index c3eeeaba210..00000000000 --- a/examples/pyomo/core/t5.py +++ /dev/null @@ -1,28 +0,0 @@ -# ____________________________________________________________________________________ -# -# Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2026 National Technology and Engineering Solutions of Sandia, LLC -# Under the terms of Contract DE-NA0003525 with National Technology and Engineering -# Solutions of Sandia, LLC, the U.S. Government retains certain rights in this -# software. This software is distributed under the 3-clause BSD License. -# ____________________________________________________________________________________ - -# -# A duality example adapted from -# http://www.stanford.edu/~ashishg/msande111/notes/chapter4.pdf -# -import pyomo.environ as pyo - - -def pyomo_create_model(options, model_options): - model = pyo.ConcreteModel() - model.x1 = pyo.Var(within=pyo.NonNegativeReals) - model.x2 = pyo.Var(within=pyo.NonNegativeReals) - model.o = pyo.Objective(expr=3 * model.x1 + 2.5 * model.x2, sense=pyo.maximize) - - model.c1 = pyo.Constraint(expr=4.44 * model.x1 <= 100) - model.c2 = pyo.Constraint(expr=6.67 * model.x2 <= 100) - model.c3 = pyo.Constraint(expr=4 * model.x1 + 2.86 * model.x2 <= 100) - model.c4 = pyo.Constraint(expr=3 * model.x1 + 6 * model.x2 <= 100) - - return model diff --git a/pyomo/core/plugins/transform/model.py b/pyomo/core/plugins/transform/model.py deleted file mode 100644 index 0cfbc2e69ef..00000000000 --- a/pyomo/core/plugins/transform/model.py +++ /dev/null @@ -1,463 +0,0 @@ -# ____________________________________________________________________________________ -# -# Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2026 National Technology and Engineering Solutions of Sandia, LLC -# Under the terms of Contract DE-NA0003525 with National Technology and Engineering -# Solutions of Sandia, LLC, the U.S. Government retains certain rights in this -# software. This software is distributed under the 3-clause BSD License. -# ____________________________________________________________________________________ - -# -# NOTE: deprecated code -# -# This code is here for historical purposes, and -# because we may support an explicit matrix representation for models. -# - -from pyomo.common.deprecation import deprecated -from pyomo.core.base import Objective, Constraint -import array - - -@deprecated( - "to_standard_form() is deprecated. " - "Please use WriterFactory('compile_standard_form')", - version='6.7.3', - remove_in='6.8.0', -) -def to_standard_form(self): - r""" - Produces a standard-form representation of the model. Returns - the coefficient matrix (A), the cost vector (c), and the - constraint vector (b), where the 'standard form' problem is - - .. math:: - - \min/\max\ & c'x \\ - s.t.\ & Ax = b \\ - & x >= 0 - - All three returned values are instances of the array.array - class, and store Python floats (C doubles). - """ - - from pyomo.repn import generate_standard_repn - - # We first need to create an map of all variables to their column - # number - colID = {} - ID2name = {} - id = 0 - tmp = self.variables().keys() - tmp.sort() - - for v in tmp: - colID[v] = id - ID2name[id] = v - id += 1 - - # First we go through the constraints and introduce slack and excess - # variables to eliminate inequality constraints - # - # N.B. Structure hierarchy: - # - # active_components: {class: {attr_name: object}} - # object -> Constraint: ._data: {ndx: ConstraintData} - # ConstraintData: .lower, .body, .upper - # - # So, altogether, we access a lower bound via - # - # model.component_map(active=True)[Constraint]['con_name']['index'].lower - # - # {le,ge,eq}Constraints are - # {constraint_name: {index: {variable_or_none: coefficient}} objects - # that represent each constraint. None in the innermost dictionary - # represents the constant term. - # - # i.e. - # - # min x1 + 2*x2 + x4 - # s.t. x1 = 1 - # x2 + 3*x3 <= -1 - # x1 + x4 >= 3 - # x1 + 2*x2 + + 3*x4 >= 0 - # - # - # would be represented as (modulo the names of the variables, - # constraints, and indices) - # - # eqConstraints = {'c1': {None: {'x1':1, None:-1}}} - # leConstraints = {'c2': {None: {'x2':1, 'x3':3, None:1}}} - # geConstraints = {'c3': {None: {'x1':1, 'x4':1, None:-3}}, - # 'c4': {None: {'x1':1, 'x2':2, 'x4':1, None:0}}} - # - # Note the we have the luxury of dealing only with linear terms. - var_id_map = {} - leConstraints = {} - geConstraints = {} - eqConstraints = {} - objectives = {} - # For each registered component - for c in self.component_map(active=True): - # Get all subclasses of Constraint - if issubclass(c, Constraint): - cons = self.component_map(c, active=True) - - # Get the name of the constraint, and the constraint set itself - for con_set_name in cons: - con_set = cons[con_set_name] - - # For each indexed constraint in the constraint set - for ndx in con_set._data: - con = con_set._data[ndx] - - # Process the body - terms = self._process_canonical_repn( - generate_standard_repn(con.body, var_id_map) - ) - - # Process the bounds of the constraint - if con.equality: - # Equality constraint, only check lower bound - lb = self._process_canonical_repn( - generate_standard_repn(con.lower, var_id_map) - ) - - # Update terms - for k in lb: - v = lb[k] - if k in terms: - terms[k] -= v - else: - terms[k] = -v - - # Add constraint to equality constraints - eqConstraints[(con_set_name, ndx)] = terms - else: - # Process upper bounds (<= constraints) - if con.upper is not None: - # Less than or equal to constraint - tmp = dict(terms) - - ub = self._process_canonical_repn( - generate_standard_repn(con.upper, var_id_map) - ) - - # Update terms - for k in ub: - if k in terms: - tmp[k] -= ub[k] - else: - tmp[k] = -ub[k] - - # Add constraint to less than or equal to - # constraints - leConstraints[(con_set_name, ndx)] = tmp - - # Process lower bounds (>= constraints) - if con.lower is not None: - # Less than or equal to constraint - tmp = dict(terms) - - lb = self._process_canonical_repn( - generate_standard_repn(con.lower, var_id_map) - ) - - # Update terms - for k in lb: - if k in terms: - tmp[k] -= lb[k] - else: - tmp[k] = -lb[k] - - # Add constraint to less than or equal to - # constraints - geConstraints[(con_set_name, ndx)] = tmp - elif issubclass(c, Objective): - # Process objectives - objs = self.component_map(c, active=True) - - # Get the name of the objective, and the objective set itself - for obj_set_name in objs: - obj_set = objs[obj_set_name] - - # For each indexed objective in the objective set - for ndx in obj_set._data: - obj = obj_set._data[ndx] - # Process the objective - terms = self._process_canonical_repn( - generate_standard_repn(obj.expr, var_id_map) - ) - - objectives[(obj_set_name, ndx)] = terms - - # We now have all the constraints. Add a slack variable for every - # <= constraint and an excess variable for every >= constraint. - nSlack = len(leConstraints) - nExcess = len(geConstraints) - - nConstraints = len(leConstraints) + len(geConstraints) + len(eqConstraints) - nVariables = len(colID) + nSlack + nExcess - nRegVariables = len(colID) - - # Make the arrays - coefficients = array.array("d", [0] * nConstraints * nVariables) - constraints = array.array("d", [0] * nConstraints) - costs = array.array("d", [0] * nVariables) - - # Populate the coefficient matrix - constraintID = 0 - - # Add less than or equal to constraints - for ndx in leConstraints: - con = leConstraints[ndx] - for termKey in con: - coef = con[termKey] - - if termKey is None: - # Constraint coefficient - constraints[constraintID] = -coef - else: - # Variable coefficient - col = colID[termKey] - coefficients[constraintID * nVariables + col] = coef - - # Add the slack - coefficients[constraintID * nVariables + nRegVariables + constraintID] = 1 - constraintID += 1 - - # Add greater than or equal to constraints - for ndx in geConstraints: - con = geConstraints[ndx] - for termKey in con: - coef = con[termKey] - - if termKey is None: - # Constraint coefficient - constraints[constraintID] = -coef - else: - # Variable coefficient - col = colID[termKey] - coefficients[constraintID * nVariables + col] = coef - - # Add the slack - coefficients[constraintID * nVariables + nRegVariables + constraintID] = -1 - constraintID += 1 - - # Add equality constraints - for ndx in eqConstraints: - con = eqConstraints[ndx] - for termKey in con: - coef = con[termKey] - - if termKey is None: - # Constraint coefficient - constraints[constraintID] = -coef - else: - # Variable coefficient - col = colID[termKey] - coefficients[constraintID * nVariables + col] = coef - - constraintID += 1 - - # Determine cost coefficients - for obj_name in objectives: - obj = objectives[obj_name]() - for var in obj: - costs[colID[var]] = obj[var] - - # Print the model - # - # The goal is to print - # - # var1 var2 var3 ... - # +-- --+ - # | cost1 cost2 cost3 ...| - # +-- --+ - # +-- --+ +-- --+ - # con1 | coef11 coef12 coef13 ...| | eq1 | - # con2 | coef21 coef22 coef23 ...| | eq2 | - # con2 | coef31 coef32 coef33 ...| | eq3 | - # . | . . . . | | . | - # . | . . . . | | . | - # . | . . . . | | . | - - constraintPadding = 2 - numFmt = "% 1.4f" - altFmt = "% 1.1g" - maxColWidth = max(len(numFmt % 0.0), len(altFmt % 0.0)) - maxConstraintColWidth = max(len(numFmt % 0.0), len(altFmt % 0.0)) - - # Generate constraint names - maxConNameLen = 0 - conNames = [] - for name in leConstraints: - strName = str(name) - if len(strName) > maxConNameLen: - maxConNameLen = len(strName) - conNames.append(strName) - for name in geConstraints: - strName = str(name) - if len(strName) > maxConNameLen: - maxConNameLen = len(strName) - conNames.append(strName) - for name in eqConstraints: - strName = str(name) - if len(strName) > maxConNameLen: - maxConNameLen = len(strName) - conNames.append(strName) - - # Generate the variable names - varNames = [None] * len(colID) - for name in colID: - tmp_name = ' ' + name - if len(tmp_name) > maxColWidth: - maxColWidth = len(tmp_name) - varNames[colID[name]] = tmp_name - for i in range(0, nSlack): - tmp_name = " _slack_%i" % i - if len(tmp_name) > maxColWidth: - maxColWidth = len(tmp_name) - varNames.append(tmp_name) - for i in range(0, nExcess): - tmp_name = " _excess_%i" % i - if len(tmp_name) > maxColWidth: - maxColWidth = len(tmp_name) - varNames.append(tmp_name) - - # Variable names - line = ' ' * maxConNameLen + (' ' * constraintPadding) + ' ' - for col in range(0, nVariables): - # Format entry - token = varNames[col] - - # Pad with trailing whitespace - token += ' ' * (maxColWidth - len(token)) - - # Add to line - line += ' ' + token + ' ' - print(line + '\n') - - # Cost vector - print( - ' ' * maxConNameLen - + (' ' * constraintPadding) - + "+--" - + ' ' * ((maxColWidth + 2) * nVariables - 4) - + "--+" - + '\n' - ) - line = ' ' * maxConNameLen + (' ' * constraintPadding) + "|" - for col in range(0, nVariables): - # Format entry - token = numFmt % costs[col] - if len(token) > maxColWidth: - token = altFmt % costs[col] - - # Pad with trailing whitespace - token += ' ' * (maxColWidth - len(token)) - - # Add to line - line += ' ' + token + ' ' - line += "|" - print(line + '\n') - print( - ' ' * maxConNameLen - + (' ' * constraintPadding) - + "+--" - + ' ' * ((maxColWidth + 2) * nVariables - 4) - + "--+" - + '\n' - ) - - # Constraints - print( - ' ' * maxConNameLen - + (' ' * constraintPadding) - + "+--" - + ' ' * ((maxColWidth + 2) * nVariables - 4) - + "--+" - + (' ' * constraintPadding) - + "+--" - + (' ' * (maxConstraintColWidth - 1)) - + "--+" - + '\n' - ) - for row in range(0, nConstraints): - # Print constraint name - line = ( - conNames[row] - + (' ' * constraintPadding) - + (' ' * (maxConNameLen - len(conNames[row]))) - + "|" - ) - - # Print each coefficient - for col in range(0, nVariables): - # Format entry - token = numFmt % coefficients[nVariables * row + col] - if len(token) > maxColWidth: - token = altFmt % coefficients[nVariables * row + col] - - # Pad with trailing whitespace - token += ' ' * (maxColWidth - len(token)) - - # Add to line - line += ' ' + token + ' ' - - line += "|" + (' ' * constraintPadding) + "|" - - # Add constraint vector - token = numFmt % constraints[row] - if len(token) > maxConstraintColWidth: - token = altFmt % constraints[row] - - # Pad with trailing whitespace - token += ' ' * (maxConstraintColWidth - len(token)) - - line += ' ' + token + " |" - print(line + '\n') - print( - ' ' * maxConNameLen - + (' ' * constraintPadding) - + "+--" - + ' ' * ((maxColWidth + 2) * nVariables - 4) - + "--+" - + (' ' * constraintPadding) - + "+--" - + (' ' * (maxConstraintColWidth - 1)) - + "--+" - + '\n' - ) - - return (coefficients, costs, constraints) - - -def _process_canonical_repn(self, expr): - """ - Returns a dictionary of {var_name_or_None: coef} values - """ - - terms = {} - - # Get the variables from the canonical representation - vars = expr.pop(-1, {}) - - # Find the linear terms - linear = expr.pop(1, {}) - for k in linear: - # FrozeDicts don't support (k, v)-style iteration - v = linear[k] - - # There's exactly 1 variable in each term - terms[vars[k.keys()[0]].label] = v - - # Get the constant term, if present - const = expr.pop(0, {}) - if None in const: - terms[None] = const[None] - - if len(expr) != 0: - raise TypeError("Nonlinear terms in expression") - - return terms diff --git a/pyomo/duality/__init__.py b/pyomo/duality/__init__.py deleted file mode 100644 index d928abdec30..00000000000 --- a/pyomo/duality/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# ____________________________________________________________________________________ -# -# Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2026 National Technology and Engineering Solutions of Sandia, LLC -# Under the terms of Contract DE-NA0003525 with National Technology and Engineering -# Solutions of Sandia, LLC, the U.S. Government retains certain rights in this -# software. This software is distributed under the 3-clause BSD License. -# ____________________________________________________________________________________ - -from pyomo.duality import collect diff --git a/pyomo/duality/collect.py b/pyomo/duality/collect.py deleted file mode 100644 index 0f65893a0d9..00000000000 --- a/pyomo/duality/collect.py +++ /dev/null @@ -1,250 +0,0 @@ -# ____________________________________________________________________________________ -# -# Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2026 National Technology and Engineering Solutions of Sandia, LLC -# Under the terms of Contract DE-NA0003525 with National Technology and Engineering -# Solutions of Sandia, LLC, the U.S. Government retains certain rights in this -# software. This software is distributed under the 3-clause BSD License. -# ____________________________________________________________________________________ - -# Routines to collect data in a structured format - -from pyomo.common.collections import Bunch -from pyomo.core.base import Var, Constraint, Objective, maximize, minimize -from pyomo.repn.standard_repn import generate_standard_repn - - -def collect_linear_terms(block, unfixed): - # - # Variables are constraints of block - # Constraints are unfixed variables of block and the parent model. - # - vnames = set() - for obj in block.component_objects(Constraint, active=True): - vnames.add( - (obj.getname(fully_qualified=True, relative_to=block), obj.is_indexed()) - ) - cnames = set(unfixed) - for obj in block.component_objects(Var, active=True): - cnames.add( - (obj.getname(fully_qualified=True, relative_to=block), obj.is_indexed()) - ) - # - A = {} - b_coef = {} - c_rhs = {} - c_sense = {} - d_sense = None - v_domain = {} - # - # Collect objective - # - for odata in block.component_objects(Objective, active=True): - for ndx in odata: - if odata[ndx].sense == maximize: - o_terms = generate_standard_repn( - -1 * odata[ndx].expr, compute_values=False - ) - d_sense = minimize - else: - o_terms = generate_standard_repn(odata[ndx].expr, compute_values=False) - d_sense = maximize - for var, coef in zip(o_terms.linear_vars, o_terms.linear_coefs): - c_rhs[var.parent_component().local_name, var.index()] = coef - # Stop after the first objective - break - # - # Collect constraints - # - for data in block.component_objects(Constraint, active=True): - name = data.getname(relative_to=block) - for ndx in data: - con = data[ndx] - body_terms = generate_standard_repn(con.body, compute_values=False) - if body_terms.is_fixed(): - # - # If a constraint has a fixed body, then don't collect it. - # - continue - lower_terms = ( - generate_standard_repn(con.lower, compute_values=False) - if not con.lower is None - else None - ) - upper_terms = ( - generate_standard_repn(con.upper, compute_values=False) - if not con.upper is None - else None - ) - # - if not lower_terms is None and not lower_terms.is_constant(): - raise ( - RuntimeError, - "Error during dualization: Constraint '%s' has a lower bound that is non-constant", - ) - if not upper_terms is None and not upper_terms.is_constant(): - raise ( - RuntimeError, - "Error during dualization: Constraint '%s' has an upper bound that is non-constant", - ) - # - for var, coef in zip(body_terms.linear_vars, body_terms.linear_coefs): - try: - # The variable is in the subproblem - varname = var.parent_component().getname( - fully_qualified=True, relative_to=block - ) - except: - # The variable is somewhere else in the model - varname = var.parent_component().getname( - fully_qualified=True, relative_to=block.model() - ) - varndx = var.index() - A.setdefault(varname, {}).setdefault(varndx, []).append( - Bunch(coef=coef, var=name, ndx=ndx) - ) - # - if not con.equality: - # - # Inequality constraint - # - if lower_terms is None: - # - # body <= upper - # - v_domain[name, ndx] = -1 - b_coef[name, ndx] = upper_terms.constant - body_terms.constant - elif upper_terms is None: - # - # lower <= body - # - v_domain[name, ndx] = 1 - b_coef[name, ndx] = lower_terms.constant - body_terms.constant - else: - # - # lower <= body <= upper - # - # Dual for lower bound - # - ndx_ = tuple(list(ndx).append('lb')) - v_domain[name, ndx_] = 1 - b_coef[name, ndx] = lower_terms.constant - body_terms.constant - # - # Dual for upper bound - # - ndx_ = tuple(list(ndx).append('ub')) - v_domain[name, ndx_] = -1 - b_coef[name, ndx] = upper_terms.constant - body_terms.constant - else: - # - # Equality constraint - # - v_domain[name, ndx] = 0 - b_coef[name, ndx] = lower_terms.constant - body_terms.constant - - # - # Collect bound constraints - # - def all_vars(b): - """ - This conditionally chains together the active variables in the current block with - the active variables in all of the parent blocks (if any exist). - """ - for obj in b.component_objects(Var, active=True, descend_into=True): - name = obj.parent_component().getname(fully_qualified=True, relative_to=b) - yield (name, obj) - # - # Look through parent blocks - # - b = b.parent_block() - while not b is None: - for obj in b.component_objects(Var, active=True, descend_into=False): - name = obj.parent_component().name - yield (name, obj) - b = b.parent_block() - - for name, data in all_vars(block): - # - # Skip fixed variables (in the parent) - # - if not (name, data.is_indexed()) in cnames: - continue - # - # Iterate over all variable indices - # - for ndx in data: - var = data[ndx] - bounds = var.bounds - if bounds[0] is None and bounds[1] is None: - c_sense[name, ndx] = 'e' - elif bounds[0] is None: - if bounds[1] == 0.0: - c_sense[name, ndx] = 'g' - else: - c_sense[name, ndx] = 'e' - # - # Add constraint that defines the upper bound - # - name_ = name + "_upper_" - varname = data.parent_component().getname( - fully_qualified=True, relative_to=block - ) - varndx = data[ndx].index() - A.setdefault(varname, {}).setdefault(varndx, []).append( - Bunch(coef=1.0, var=name_, ndx=ndx) - ) - # - v_domain[name_, ndx] = -1 - b_coef[name_, ndx] = bounds[1] - elif bounds[1] is None: - if bounds[0] == 0.0: - c_sense[name, ndx] = 'l' - else: - c_sense[name, ndx] = 'e' - # - # Add constraint that defines the lower bound - # - name_ = name + "_lower_" - varname = data.parent_component().getname( - fully_qualified=True, relative_to=block - ) - varndx = data[ndx].index() - A.setdefault(varname, {}).setdefault(varndx, []).append( - Bunch(coef=1.0, var=name_, ndx=ndx) - ) - # - v_domain[name_, ndx] = 1 - b_coef[name_, ndx] = bounds[0] - else: - # Bounded above and below - c_sense[name, ndx] = 'e' - # - # Add constraint that defines the upper bound - # - name_ = name + "_upper_" - varname = data.parent_component().getname( - fully_qualified=True, relative_to=block - ) - varndx = data[ndx].index() - A.setdefault(varname, {}).setdefault(varndx, []).append( - Bunch(coef=1.0, var=name_, ndx=ndx) - ) - # - v_domain[name_, ndx] = -1 - b_coef[name_, ndx] = bounds[1] - # - # Add constraint that defines the lower bound - # - name_ = name + "_lower_" - varname = data.parent_component().getname( - fully_qualified=True, relative_to=block - ) - varndx = data[ndx].index() - A.setdefault(varname, {}).setdefault(varndx, []).append( - Bunch(coef=1.0, var=name_, ndx=ndx) - ) - # - v_domain[name_, ndx] = 1 - b_coef[name_, ndx] = bounds[0] - # - return (A, b_coef, c_rhs, c_sense, d_sense, vnames, cnames, v_domain) diff --git a/pyomo/duality/lagrangian_dual.py b/pyomo/duality/lagrangian_dual.py deleted file mode 100644 index 72d7dd60a52..00000000000 --- a/pyomo/duality/lagrangian_dual.py +++ /dev/null @@ -1,213 +0,0 @@ -# ____________________________________________________________________________________ -# -# Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2026 National Technology and Engineering Solutions of Sandia, LLC -# Under the terms of Contract DE-NA0003525 with National Technology and Engineering -# Solutions of Sandia, LLC, the U.S. Government retains certain rights in this -# software. This software is distributed under the 3-clause BSD License. -# ____________________________________________________________________________________ - -# -# NOTE: deprecated code -# -from pyomo.common.deprecation import deprecated -from pyomo.core import ( - TransformationFactory, - Constraint, - Set, - Var, - Objective, - AbstractModel, - maximize, -) -from pyomo.repn import generate_standard_repn -from pyomo.core.plugins.transform.hierarchy import IsomorphicTransformation -from pyomo.core.plugins.transform.standard_form import StandardForm -from pyomo.core.plugins.transform.util import partial, process_canonical_repn - - -@TransformationFactory.register("core.lagrangian_dual", doc="Create the LP dual model.") -class DualTransformation(IsomorphicTransformation): - """Creates a standard form Pyomo model that is equivalent to another - model - - Options - dual_constraint_suffix Defaults to ``_constraint`` - dual_variable_prefix Defaults to ``p_`` - slack_names Defaults to ``auxiliary_slack`` - excess_names Defaults to ``auxiliary_excess`` - lb_names Defaults to ``_lower_bound`` - ub_names Defaults to ``_upper_bound`` - pos_suffix Defaults to ``_plus`` - neg_suffix Defaults to ``_minus`` - - """ - - @deprecated( - "Use of the pyomo.duality package is deprecated. There are known bugs " - "in pyomo.duality, and we do not recommend the use of this code. " - "Development of dualization capabilities has been shifted to " - "the Pyomo Adversarial Optimization (PAO) library. Please contact " - "William Hart for further details (wehart@sandia.gov).", - version='5.6.2', - ) - def __init__(self, **kwds): - kwds['name'] = "linear_dual" - super(DualTransformation, self).__init__(**kwds) - - def _create_using(self, model, **kwds): - """ - Transform a model to its Lagrangian dual. - """ - - # Optional naming schemes for dual variables and constraints - constraint_suffix = kwds.pop("dual_constraint_suffix", "_constraint") - variable_prefix = kwds.pop("dual_variable_prefix", "p_") - - # Optional naming schemes to pass to StandardForm - sf_kwds = {} - sf_kwds["slack_names"] = kwds.pop("slack_names", "auxiliary_slack") - sf_kwds["excess_names"] = kwds.pop("excess_names", "auxiliary_excess") - sf_kwds["lb_names"] = kwds.pop("lb_names", "_lower_bound") - sf_kwds["ub_names"] = kwds.pop("ub_names", "_upper_bound") - sf_kwds["pos_suffix"] = kwds.pop("pos_suffix", "_plus") - sf_kwds["neg_suffix"] = kwds.pop("neg_suffix", "_minus") - - # Get the standard form model - sf_transform = StandardForm() - sf = sf_transform(model, **sf_kwds) - - # Roughly, parse the objectives and constraints to form A, b, and c of - # - # min c'x - # s.t. Ax = b - # x >= 0 - # - # and create a new model from them. - - # We use sparse matrix representations - - # {constraint_name: {variable_name: coefficient}} - A = _sparse(lambda: _sparse(0)) - - # {constraint_name: coefficient} - b = _sparse(0) - - # {variable_name: coefficient} - c = _sparse(0) - - # Walk constraints - for con_name, con_array in sf.component_map(Constraint, active=True).items(): - for con in (con_array[ndx] for ndx in con_array.index_set()): - # The qualified constraint name - cname = "%s%s" % (variable_prefix, con.local_name) - - # Process the body of the constraint - body_terms = process_canonical_repn(generate_standard_repn(con.body)) - - # Add a numeric constant to the 'b' vector, if present - b[cname] -= body_terms.pop(None, 0) - - # Add variable coefficients to the 'A' matrix - row = _sparse(0) - for vname, coef in body_terms.items(): - row["%s%s" % (vname, constraint_suffix)] += coef - - # Process the upper bound of the constraint. We rely on - # StandardForm to produce equality constraints, thus - # requiring us only to check the lower bounds. - lower_terms = process_canonical_repn(generate_standard_repn(con.lower)) - - # Add a numeric constant to the 'b' matrix, if present - b[cname] += lower_terms.pop(None, 0) - - # Add any variables to the 'A' matrix, if present - for vname, coef in lower_terms.items(): - row["%s%s" % (vname, constraint_suffix)] -= coef - - A[cname] = row - - # Walk objectives. Multiply all coefficients by the objective's 'sense' - # to convert maximizing objectives to minimizing ones. - for obj_name, obj_array in sf.component_map(Objective, active=True).items(): - for obj in (obj_array[ndx] for ndx in obj_array.index_set()): - # The qualified objective name - - # Process the objective - terms = process_canonical_repn(generate_standard_repn(obj.expr)) - - # Add coefficients - for name, coef in terms.items(): - c["%s%s" % (name, constraint_suffix)] += coef * obj_array.sense - - # Form the dual - dual = AbstractModel() - - # Make constraint index set - constraint_set_init = [] - for var_name, var_array in sf.component_map(Var, active=True).items(): - for var in (var_array[ndx] for ndx in var_array.index_set()): - constraint_set_init.append("%s%s" % (var.local_name, constraint_suffix)) - - # Make variable index set - variable_set_init = [] - dual_variable_roots = [] - for con_name, con_array in sf.component_map(Constraint, active=True).items(): - for con in (con_array[ndx] for ndx in con_array.index_set()): - dual_variable_roots.append(con.local_name) - variable_set_init.append("%s%s" % (variable_prefix, con.local_name)) - - # Create the dual Set and Var objects - dual.var_set = Set(initialize=variable_set_init) - dual.con_set = Set(initialize=constraint_set_init) - dual.vars = Var(dual.var_set) - - # Make the dual constraints - def constraintRule(A, c, ndx, model): - return sum(A[v][ndx] * model.vars[v] for v in model.var_set) <= c[ndx] - - dual.cons = Constraint(dual.con_set, rule=partial(constraintRule, A, c)) - - # Make the dual objective (maximizing) - def objectiveRule(b, model): - return sum(b[v] * model.vars[v] for v in model.var_set) - - dual.obj = Objective(rule=partial(objectiveRule, b), sense=maximize) - - return dual.create() - - -class _sparse(dict): - """ - Represents a sparse map. Uses a user-provided value to initialize - entries. If the default value is a callable object, it is called - with no arguments. - - Examples - - # Sparse vector - v = _sparse(0) - - # 2-dimensional sparse matrix - A = _sparse(lambda: _sparse(0)) - - """ - - def __init__(self, default, *args, **kwds): - dict.__init__(self, *args, **kwds) - - if hasattr(default, "__call__"): - self._default_value = None - self._default_func = default - else: - self._default_value = default - self._default_func = None - - def __getitem__(self, ndx): - if ndx in self: - return dict.__getitem__(self, ndx) - else: - if self._default_func is not None: - return self._default_func() - else: - return self._default_value diff --git a/pyomo/duality/plugins.py b/pyomo/duality/plugins.py deleted file mode 100644 index 86309f1baf6..00000000000 --- a/pyomo/duality/plugins.py +++ /dev/null @@ -1,174 +0,0 @@ -# ____________________________________________________________________________________ -# -# Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2026 National Technology and Engineering Solutions of Sandia, LLC -# Under the terms of Contract DE-NA0003525 with National Technology and Engineering -# Solutions of Sandia, LLC, the U.S. Government retains certain rights in this -# software. This software is distributed under the 3-clause BSD License. -# ____________________________________________________________________________________ - -import logging - -from pyomo.common.deprecation import deprecated -from pyomo.core.base import ( - Transformation, - TransformationFactory, - Var, - Constraint, - Objective, - minimize, - NonNegativeReals, - NonPositiveReals, - Reals, - Block, - Model, - ConcreteModel, -) -from pyomo.duality.collect import collect_linear_terms - - -def load(): - pass - - -logger = logging.getLogger('pyomo.core') - - -# -# This transformation creates a new block that -# is the dual of the specified block. If no block is -# specified, then the entire model is dualized. -# This returns a new Block object. -# -@TransformationFactory.register( - 'duality.linear_dual', doc="[DEPRECATED] Dualize a linear model" -) -@deprecated( - "Use of the pyomo.duality package is deprecated. There are known bugs " - "in pyomo.duality, and we do not recommend the use of this code. " - "Development of dualization capabilities has been shifted to " - "the Pyomo Adversarial Optimization (PAO) library. Please contact " - "William Hart for further details (wehart@sandia.gov).", - version='5.6.2', -) -class LinearDual_PyomoTransformation(Transformation): - def __init__(self): - super(LinearDual_PyomoTransformation, self).__init__() - - def _create_using(self, instance, **kwds): - options = kwds.pop('options', {}) - bname = options.get('block', None) - # - # Iterate over the model collecting variable data, - # until the block is found. - # - block = None - if block is None: - block = instance - else: - for name, data in instance.component_map(Block, active=True).items(): - if name == bname: - block = instance - if block is None: - raise RuntimeError("Missing block: " + bname) - # - # Generate the dual - # - instance_ = self._dualize(block) - - return instance_ - - def _dualize(self, block, unfixed=[]): - """ - Generate the dual of a block - """ - # - # Collect linear terms from the block - # - A, b_coef, c_rhs, c_sense, d_sense, vnames, cnames, v_domain = ( - collect_linear_terms(block, unfixed) - ) - ##print(A) - ##print(vnames) - ##print(cnames) - ##print(list(A.keys())) - ##print("---") - ##print(A.keys()) - ##print(c_sense) - ##print(c_rhs) - # - # Construct the block - # - if isinstance(block, Model): - dual = ConcreteModel() - else: - dual = Block() - dual.construct() - _vars = {} - - def getvar(name, ndx=None): - v = _vars.get((name, ndx), None) - if v is None: - v = Var() - if ndx is None: - v_name = name - elif type(ndx) is tuple: - v_name = "%s[%s]" % (name, ','.join(map(str, ndx))) - else: - v_name = "%s[%s]" % (name, str(ndx)) - setattr(dual, v_name, v) - _vars[name, ndx] = v - return v - - # - # Construct the objective - # - if d_sense == minimize: - dual.o = Objective( - expr=sum( - -b_coef[name, ndx] * getvar(name, ndx) for name, ndx in b_coef - ), - sense=d_sense, - ) - else: - dual.o = Objective( - expr=sum(b_coef[name, ndx] * getvar(name, ndx) for name, ndx in b_coef), - sense=d_sense, - ) - # - # Construct the constraints - # - for cname in A: - for ndx, terms in A[cname].items(): - expr = 0 - for term in terms: - expr += term.coef * getvar(term.var, term.ndx) - if not (cname, ndx) in c_rhs: - c_rhs[cname, ndx] = 0.0 - if c_sense[cname, ndx] == 'e': - e = expr - c_rhs[cname, ndx] == 0 - elif c_sense[cname, ndx] == 'l': - e = expr - c_rhs[cname, ndx] <= 0 - else: - e = expr - c_rhs[cname, ndx] >= 0 - c = Constraint(expr=e) - if ndx is None: - c_name = cname - elif type(ndx) is tuple: - c_name = "%s[%s]" % (cname, ','.join(map(str, ndx))) - else: - c_name = "%s[%s]" % (cname, str(ndx)) - setattr(dual, c_name, c) - # - for (name, ndx), domain in v_domain.items(): - v = getvar(name, ndx) - flag = type(ndx) is tuple and (ndx[-1] == 'lb' or ndx[-1] == 'ub') - if domain == 1: - v.domain = NonNegativeReals - elif domain == -1: - v.domain = NonPositiveReals - else: - # TODO: verify that this case is possible - v.domain = Reals - - return dual diff --git a/pyomo/duality/tests/__init__.py b/pyomo/duality/tests/__init__.py deleted file mode 100644 index 14f31814302..00000000000 --- a/pyomo/duality/tests/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# ____________________________________________________________________________________ -# -# Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2026 National Technology and Engineering Solutions of Sandia, LLC -# Under the terms of Contract DE-NA0003525 with National Technology and Engineering -# Solutions of Sandia, LLC, the U.S. Government retains certain rights in this -# software. This software is distributed under the 3-clause BSD License. -# ____________________________________________________________________________________ -# -# Pyomo tests -# diff --git a/pyomo/duality/tests/t1.txt b/pyomo/duality/tests/t1.txt deleted file mode 100644 index 81e6fa3d326..00000000000 --- a/pyomo/duality/tests/t1.txt +++ /dev/null @@ -1,51 +0,0 @@ -{ - "Problem": [ - { - "Lower bound": 14.0, - "Name": "unknown", - "Number of constraints": 4, - "Number of nonzeros": 8, - "Number of objectives": 1, - "Number of variables": 4, - "Sense": "minimize", - "Upper bound": 14.0 - } - ], - "Solution": [ - { - "number of solutions": 1, - "number of solutions displayed": 1 - }, - { - "Constraint": "No nonzero values", - "Gap": 0.0, - "Objective": { - "o": { - "Value": 14.0 - } - }, - "Status": "feasible", - "Variable": { - "x2": { - "Value": 3.0 - }, - "x3": { - "Value": 1.0 - } - } - } - ], - "Solver": [ - { - "Error rc": 0, - "Statistics": { - "Branch and bound": { - "Number of bounded subproblems": 0, - "Number of created subproblems": 0 - } - }, - "Status": "ok", - "Termination condition": "optimal" - } - ] -} diff --git a/pyomo/duality/tests/t1_linear_dual.lp b/pyomo/duality/tests/t1_linear_dual.lp deleted file mode 100644 index f5d9dc6aa73..00000000000 --- a/pyomo/duality/tests/t1_linear_dual.lp +++ /dev/null @@ -1,31 +0,0 @@ -\* Source Pyomo model name=unknown *\ - -max -o: -+5.0 c1 -+3.0 c2 -+4.0 c3 - -s.t. - -c_u_x1_: -+4 c1 -+1 c2 -<= 6 - -c_u_x2_: -+2 c1 -+1 c2 -+1 c3 -<= 4 - -c_u_x3_: -+1 c1 -+1 c3 -<= 2 - -bounds - 0 <= c1 <= +inf - 0 <= c2 <= +inf - 0 <= c3 <= +inf -end diff --git a/pyomo/duality/tests/t5.txt b/pyomo/duality/tests/t5.txt deleted file mode 100644 index 8af009381fc..00000000000 --- a/pyomo/duality/tests/t5.txt +++ /dev/null @@ -1,51 +0,0 @@ -{ - "Problem": [ - { - "Lower bound": 77.3022049286641, - "Name": "unknown", - "Number of constraints": 5, - "Number of nonzeros": 7, - "Number of objectives": 1, - "Number of variables": 3, - "Sense": "maximize", - "Upper bound": 77.3022049286641 - } - ], - "Solution": [ - { - "number of solutions": 1, - "number of solutions displayed": 1 - }, - { - "Constraint": "No nonzero values", - "Gap": 0.0, - "Objective": { - "o": { - "Value": 77.3022049286641 - } - }, - "Status": "feasible", - "Variable": { - "x1": { - "Value": 20.3631647211414 - }, - "x2": { - "Value": 6.48508430609598 - } - } - } - ], - "Solver": [ - { - "Error rc": 0, - "Statistics": { - "Branch and bound": { - "Number of bounded subproblems": 0, - "Number of created subproblems": 0 - } - }, - "Status": "ok", - "Termination condition": "optimal" - } - ] -} diff --git a/pyomo/duality/tests/t5_linear_dual.lp b/pyomo/duality/tests/t5_linear_dual.lp deleted file mode 100644 index 9e3f6d0b3bf..00000000000 --- a/pyomo/duality/tests/t5_linear_dual.lp +++ /dev/null @@ -1,29 +0,0 @@ -\* Source Pyomo model name=unknown *\ - -min -o: --100.0 c1 --100.0 c2 --100.0 c3 --100.0 c4 - -s.t. - -c_u_x1_: -+4.44 c1 -+4 c3 -+3 c4 -<= -3 - -c_u_x2_: -+6.67 c2 -+2.86 c3 -+6 c4 -<= -2.5 - -bounds - -inf <= c1 <= 0 - -inf <= c2 <= 0 - -inf <= c3 <= 0 - -inf <= c4 <= 0 -end diff --git a/pyomo/duality/tests/test_linear_dual.py b/pyomo/duality/tests/test_linear_dual.py deleted file mode 100644 index 4f7d44ed95f..00000000000 --- a/pyomo/duality/tests/test_linear_dual.py +++ /dev/null @@ -1,189 +0,0 @@ -# ____________________________________________________________________________________ -# -# Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2008-2026 National Technology and Engineering Solutions of Sandia, LLC -# Under the terms of Contract DE-NA0003525 with National Technology and Engineering -# Solutions of Sandia, LLC, the U.S. Government retains certain rights in this -# software. This software is distributed under the 3-clause BSD License. -# ____________________________________________________________________________________ -# -# Test transformations for linear duality -# - -import os -from os.path import abspath, dirname, normpath, join - -currdir = dirname(abspath(__file__)) -exdir = normpath(join(currdir, '..', '..', '..', 'examples', 'pyomo', 'core')) - -import pyomo.common.unittest as unittest - -from pyomo.common.dependencies import yaml, yaml_available, yaml_load_args -from pyomo.repn.tests.lp_diff import load_and_compare_lp_baseline -from pyomo.scripting.util import cleanup -import pyomo.scripting.pyomo_main as main - -solver = None - - -class CommonTests: - solve = True - - def run_bilevel(self, *_args, **kwds): - if self.solve: - args = ['solve'] - _solver = kwds.get('solver', 'glpk') - args.append('--solver=%s' % _solver) - args.append('--save-results=result.yml') - args.append('--results-format=json') - else: - args = ['convert'] - if 'transform' in kwds: - args.append('--transform=%s' % kwds['transform']) - args.append('-c') - args.append('--symbolic-solver-labels') - args.append('--file-determinism=2') - - if False: - args.append('--stream-solver') - args.append('--tempdir=' + currdir) - args.append('--keepfiles') - args.append('--debug') - args.append('--logging=verbose') - - args = args + list(_args) - os.chdir(currdir) - - print('***') - # print(' '.join(args)) - output = main.main(args) - try: - output = main.main(args) - except: - output = None - cleanup() - print('***') - return output - - def check(self, problem, solver): - pass - - def referenceFile(self, problem, solver): - return join(currdir, problem + '.txt') - - def getObjective(self, fname): - FILE = open(fname) - data = yaml.load(FILE, **yaml_load_args) - FILE.close() - solutions = data.get('Solution', []) - ans = [] - for x in solutions: - ans.append(x.get('Objective', {})) - return ans - - def updateDocStrings(self): - for key in dir(self): - if key.startswith('test'): - getattr(self, key).__doc__ = " (%s)" % getattr(self, key).__name__ - - def test_t5(self): - self.problem = 'test_t5' - self.run_bilevel(join(exdir, 't5.py')) - self.check('t5', 'linear_dual') - - def test_t1(self): - self.problem = 'test_t1' - self.run_bilevel(join(exdir, 't1.py')) - self.check('t1', 'linear_dual') - - -class Reformulate(unittest.TestCase, CommonTests): - solve = False - - def tearDown(self): - if os.path.exists(os.path.join(currdir, 'result.yml')): - os.remove(os.path.join(currdir, 'result.yml')) - - @classmethod - def setUpClass(cls): - import pyomo.environ - - def run_bilevel(self, *args, **kwds): - args = list(args) - args.append('--output=' + self.problem + '_result.lp') - kwds['transform'] = 'duality.linear_dual' - CommonTests.run_bilevel(self, *args, **kwds) - - def referenceFile(self, problem, solver): - return join(currdir, problem + "_" + solver + '.lp') - - def check(self, problem, solver): - self.assertEqual( - *load_and_compare_lp_baseline( - self.referenceFile(problem, solver), - join(currdir, self.problem + '_result.lp'), - ) - ) - - -class Solver(unittest.TestCase): - @classmethod - def setUpClass(cls): - import pyomo.environ - - def tearDown(self): - if os.path.exists(os.path.join(currdir, 'result.yml')): - os.remove(os.path.join(currdir, 'result.yml')) - - def check(self, problem, solver): - refObj = self.getObjective(self.referenceFile(problem, solver)) - ansObj = self.getObjective(join(currdir, 'result.yml')) - self.assertEqual(len(refObj), len(ansObj)) - for i in range(len(refObj)): - self.assertEqual(len(refObj[i]), len(ansObj[i])) - for key, val in refObj[i].items(): - self.assertAlmostEqual( - val['Value'], ansObj[i].get(key, None)['Value'], places=3 - ) - - -class Solve_GLPK(Solver, CommonTests): - @classmethod - def setUpClass(cls): - global solvers - import pyomo.environ - - solvers = pyomo.opt.check_available_solvers('glpk') - - def setUp(self): - if (not yaml_available) or (not 'glpk' in solvers): - self.skipTest( - "YAML is not available or the 'glpk' executable is not available" - ) - - def run_bilevel(self, *args, **kwds): - kwds['solver'] = 'glpk' - CommonTests.run_bilevel(self, *args, **kwds) - - -class Solve_CPLEX(Solver, CommonTests): - @classmethod - def setUpClass(cls): - global solvers - import pyomo.environ - - solvers = pyomo.opt.check_available_solvers('cplex') - - def setUp(self): - if (not yaml_available) or (not 'cplex' in solvers): - self.skipTest( - "YAML is not available or the 'cplex' executable is not available" - ) - - def run_bilevel(self, *args, **kwds): - kwds['solver'] = 'cplex' - CommonTests.run_bilevel(self, *args, **kwds) - - -if __name__ == "__main__": - unittest.main() diff --git a/pyomo/environ/__init__.py b/pyomo/environ/__init__.py index 43276c6ca4d..580839859e0 100644 --- a/pyomo/environ/__init__.py +++ b/pyomo/environ/__init__.py @@ -24,7 +24,6 @@ def _do_import(pkg_name): 'pyomo.core', 'pyomo.opt', 'pyomo.dataportal', - 'pyomo.duality', 'pyomo.repn', 'pyomo.neos', 'pyomo.solvers', diff --git a/pyomo/scripting/tests/test_cmds.py b/pyomo/scripting/tests/test_cmds.py index df8e16b277b..2ae2bcd33ec 100644 --- a/pyomo/scripting/tests/test_cmds.py +++ b/pyomo/scripting/tests/test_cmds.py @@ -150,7 +150,7 @@ def test_help_transformations(self): self.assertTrue(re.search('Pyomo Model Transformations', OUT)) self.assertTrue(re.search('core.relax_integer_vars', OUT)) # test a transformation that we know is deprecated - self.assertTrue(re.search(r'duality.linear_dual\s+\[DEPRECATED\]', OUT)) + self.assertTrue(re.search(r'gdp.chull\s+\[DEPRECATED\]', OUT)) def test_downloader(self): _orig = DownloadFactory._cls