# ____________________________________________________________________________________
#
# 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
[docs]
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)