# -*- coding: utf-8 -*-
# ____________________________________________________________________________________
#
# 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.
# ____________________________________________________________________________________
"""Module with diagnostic utilities for infeasible models."""
from pyomo.core import Constraint, Var, value
from math import fabs
import logging
from pyomo.common import deprecated
from pyomo.core.expr.visitor import identify_variables
from pyomo.util.blockutil import log_model_constraints
logger = logging.getLogger(__name__)
def _check_infeasible(obj, val, tol):
if val is None:
# Undefined value due to missing variable value or evaluation error
return 4
# Check for infeasibilities
infeasible = 0
if obj.has_lb():
lb = value(obj.lower, exception=False)
if lb is None:
infeasible |= 4 | 1
elif lb - val > tol:
infeasible |= 1
if obj.has_ub():
ub = value(obj.upper, exception=False)
if ub is None:
infeasible |= 4 | 2
elif val - ub > tol:
infeasible |= 2
return infeasible
[docs]
def find_infeasible_constraints(m, tol=1e-6):
"""Find the infeasible constraints in the model.
Uses the current model state.
Parameters
----------
m: Block
Pyomo block or model to check
tol: float
absolute feasibility tolerance
Yields
------
constr: ConstraintData
The infeasible constraint object
body_value: float or None
The numeric value of the constraint body (or None if there was an
error evaluating the expression)
infeasible: int
A bitmask indicating which bound was infeasible (1 for the lower
bound, 2 for the upper bound, or 4 if the body or bound was
undefined)
"""
# Iterate through all active constraints on the model
for constr in m.component_data_objects(
ctype=Constraint, active=True, descend_into=True
):
body_value = value(constr.body, exception=False)
infeasible = _check_infeasible(constr, body_value, tol)
if infeasible:
yield constr, body_value, infeasible
[docs]
def log_infeasible_constraints(
m, tol=1e-6, logger=logger, log_expression=False, log_variables=False
):
"""Logs the infeasible constraints in the model.
Uses the current model state. Messages are logged at the INFO level.
Parameters
----------
m: Block
Pyomo block or model to check
tol: float
absolute feasibility tolerance
logger: logging.Logger
Logger to output to; defaults to `pyomo.util.infeasible`.
log_expression: bool
If true, prints the constraint expression
log_variables: bool
If true, prints the constraint variable names and values
"""
if logger.getEffectiveLevel() > logging.INFO:
logger.warning(
'log_infeasible_constraints() called with a logger whose '
'effective level is higher than logging.INFO: no output '
'will be logged regardless of constraint feasibility'
)
for constr, body, infeas in find_infeasible_constraints(m, tol):
if constr.equality:
lb = lb_expr = lb_op = ""
ub_expr = constr.upper
ub = value(ub_expr, exception=False)
if body is None:
ub_op = " =?= "
else:
ub_op = " =/= "
else:
if constr.has_lb():
lb_expr = constr.lower
lb = value(lb_expr, exception=False)
if body is None:
lb_op = " <?= "
elif infeas & 1:
lb_op = " </= "
else:
lb_op = " <= "
else:
lb = lb_expr = lb_op = ""
if constr.has_ub():
ub_expr = constr.upper
ub = value(ub_expr, exception=False)
if body is None:
ub_op = " <?= "
elif infeas & 2:
ub_op = " </= "
else:
ub_op = " <= "
else:
ub = ub_expr = ub_op = ""
if body is None:
body = "evaluation error"
if lb is None:
lb = "evaluation error"
if ub is None:
ub = "evaluation error"
line = f"CONSTR {constr.name}: {lb}{lb_op}{body}{ub_op}{ub}"
if log_expression:
line += f"\n - EXPR: {lb_expr}{lb_op}{constr.body}{ub_op}{ub_expr}"
if log_variables:
line += ''.join(
f"\n - VAR {v.name}: {v.value}"
for v in identify_variables(constr.expr, include_fixed=True)
)
logger.info(line)
[docs]
def find_infeasible_bounds(m, tol=1e-6):
"""Find variables whose values are outside their bounds
Uses the current model state. Variables with no values are returned
as if they were infeasible.
Parameters
----------
m: Block
Pyomo block or model to check
tol: float
absolute feasibility tolerance
Yields
------
var: VarData
The variable that is outside its bounds
infeasible: int
A bitmask indicating which bound was infeasible (1 for the lower
bound or 2 for the upper bound; 4 indicates the variable had no
value or a bound was undefined)
"""
for var in m.component_data_objects(ctype=Var, descend_into=True):
val = var.value
infeasible = _check_infeasible(var, val, tol)
if infeasible:
yield var, infeasible
_evaluation_errors = {
4: 'no assigned value',
5: 'error evaluating lower bound',
6: 'error evaluating upper bound',
7: 'error evaluating lower or upper bounds',
}
[docs]
def log_infeasible_bounds(m, tol=1e-6, logger=logger):
"""Logs the infeasible variable bounds in the model.
Parameters
----------
m: Block
Pyomo block or model to check
tol: float
absolute feasibility tolerance
logger: logging.Logger
Logger to output to; defaults to `pyomo.util.infeasible`.
"""
if logger.getEffectiveLevel() > logging.INFO:
logger.warning(
'log_infeasible_bounds() called with a logger whose '
'effective level is higher than logging.INFO: no output '
'will be logged regardless of bound feasibility'
)
for var, infeas in find_infeasible_bounds(m, tol):
if infeas & 4:
logger.info(f"VAR {var.name}: {_evaluation_errors[infeas]}.")
continue
if infeas & 1:
logger.info(f'VAR {var.name}: LB {var.lb} </= {var.value}')
if infeas & 2:
logger.info(f'VAR {var.name}: {var.value} </= UB {var.ub}')
def _check_close(obj, val, tol):
if val is None:
return 4
close = 0
if obj.has_lb():
lb = value(obj.lower, exception=False)
if lb is None:
close |= 4 | 1
if fabs(lb - val) <= tol:
close |= 1
if obj.has_ub():
ub = value(obj.upper, exception=False)
if ub is None:
close |= 4 | 2
if fabs(val - ub) <= tol:
close |= 2
return close
[docs]
def find_close_to_bounds(m, tol=1e-6):
"""Find variables and constraints whose values are close to their bounds.
Uses the current model state. Variables with no values and
constraints with evaluation errors are returned as if they were
close to their bounds.
Note
----
This will omit variables and constraints in several situations:
- Equality constraints are omitted (as they should always
be close to their bounds!).
- Range constraints where both the upper and lower bounds are
close are omitted (these are basically equality constraints).
- Fixed variables are omitted (this is analogous to an equality
constraint).
- Variables where both the upper and lower bounds are close are
omitted (these are basically fixed variables).
Parameters
----------
m: Block
Pyomo block or model to check
tol: float
absolute feasibility tolerance: values within tol of the bound
will be returned.
Yields
------
var: ComponentData
The variable or constraint that is close to its bounds
val: float
The value of the variable or constraint body
close: int
A bitmask indicating which bound(s) the value was close to (1
for the lower bound or 2 for the upper bound; 0 indicates the
variable or constraint had no value or evaluating the constraint
generated an error)
"""
for var in m.component_data_objects(ctype=Var, descend_into=True):
if var.fixed:
continue
val = var.value
close = _check_close(var, val, tol)
if not close or close & 4:
# interior value or evaluation error
continue
if close == 3:
# The bounds are too close: skip this Var (it is
# effectively fixed)
continue
yield var, val, close
for con in m.component_data_objects(
ctype=Constraint, active=True, descend_into=True
):
if con.equality:
continue
val = value(con.body, exception=False)
close = _check_close(con, val, tol)
if not close or close & 4:
# interior value or evaluation error
continue
if close == 3:
# The bounds are too close: skip this Constraint (it is
# effectively an equality)
continue
yield con, val, close
[docs]
def log_close_to_bounds(m, tol=1e-6, logger=logger):
"""Print the variables and constraints that are near their bounds.
See :py:func:`find_close_to_bounds()` for a description of the
variables and constraints that are returned (and which are omitted).
Parameters
----------
m: Block
Pyomo block or model to check
tol: float
absolute feasibility tolerance
logger: logging.Logger
Logger to output to; defaults to `pyomo.util.infeasible`.
"""
if logger.getEffectiveLevel() > logging.INFO:
logger.warning(
'log_close_to_bounds() called with a logger whose '
'effective level is higher than logging.INFO: no output '
'will be logged regardless of bound status'
)
for obj, val, close in find_close_to_bounds(m, tol):
if not close:
if obj.ctype is Var:
logger.debug(f"Skipping VAR {obj.name} with no assigned value.")
elif obj.ctype is Constraint:
logger.info(f"Skipping CONSTR {obj.name}: evaluation error.")
else:
logger.error(f"Object {obj.name} was neither a Var nor Constraint")
continue
if close & 1:
logger.info(f'{obj.name} near LB of {obj.lb}')
if close & 2:
logger.info(f'{obj.name} near UB of {obj.ub}')
return
[docs]
@deprecated(
"log_active_constraints is deprecated. "
"Please use pyomo.util.blockutil.log_model_constraints()",
version="5.7.3",
)
def log_active_constraints(m, logger=logger):
log_model_constraints(m, logger)