Source code for pyomo.util.report_scaling

# ____________________________________________________________________________________
#
# 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
import math
from pyomo.core.base.block import BlockData
from pyomo.common.collections import ComponentSet
from pyomo.core.base.var import Var
from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr
from pyomo.core.expr.calculus.diff_with_pyomo import reverse_sd
import logging

logger = logging.getLogger(__name__)


def _bounds_to_float(lb, ub):
    if lb is None:
        lb = -math.inf
    if ub is None:
        ub = math.inf
    return lb, ub


def _print_var_set(var_set):
    s = f'{"LB":>12}{"UB":>12}    Var\n'

    for v in var_set:
        v_lb, v_ub = _bounds_to_float(*v.bounds)
        s += f'{v_lb:>12.2e}{v_ub:>12.2e}    {str(v)}\n'

    s += '\n'

    return s


def _check_var_bounds(m: BlockData, too_large: float):
    vars_without_bounds = ComponentSet()
    vars_with_large_bounds = ComponentSet()
    for v in m.component_data_objects(pyo.Var, descend_into=True):
        if v.is_fixed():
            continue
        if v.lb is None or v.ub is None:
            vars_without_bounds.add(v)
        elif v.lb <= -too_large or v.ub >= too_large:
            vars_with_large_bounds.add(v)

    return vars_without_bounds, vars_with_large_bounds


def _print_coefficients(comp_map):
    s = ''
    for c, der_bounds in comp_map.items():
        s += str(c)
        s += '\n'
        s += f'    {"Coef LB":>12}{"Coef UB":>12}    Var\n'
        for v, der_lb, der_ub in der_bounds:
            s += f'    {der_lb:>12.2e}{der_ub:>12.2e}    {str(v)}\n'
        s += '\n'
    return s


def _check_coefficients(
    comp, expr, too_large, too_small, largs_coef_map, small_coef_map
):
    ders = reverse_sd(expr)
    for _v, _der in ders.items():
        if getattr(_v, 'ctype', None) is Var:
            if _v.is_fixed():
                continue
            der_lb, der_ub = compute_bounds_on_expr(_der)
            der_lb, der_ub = _bounds_to_float(der_lb, der_ub)
            if der_lb <= -too_large or der_ub >= too_large:
                if comp not in largs_coef_map:
                    largs_coef_map[comp] = list()
                largs_coef_map[comp].append((_v, der_lb, der_ub))
            if abs(der_lb) <= too_small and abs(der_ub) < too_small:
                if der_lb != 0 or der_ub != 0:
                    if comp not in small_coef_map:
                        small_coef_map[comp] = list()
                    small_coef_map[comp].append((_v, der_lb, der_ub))


[docs] def report_scaling( m: BlockData, too_large: float = 5e4, too_small: float = 1e-6 ) -> bool: """ This function logs potentially poorly scaled parts of the model. It requires that all variables be bounded. It is important to note that this check is neither necessary nor sufficient to ensure a well-scaled model. However, it is a useful tool to help identify problematic parts of a model. This function uses symbolic differentiation and interval arithmetic to compute bounds on each entry in the jacobian of the constraints. Note that logging has to be turned on to get the output Parameters ---------- m: BlockData The pyomo model or block too_large: float Values above too_large will generate a log entry too_small: float Coefficients below too_small will generate a log entry Returns ------- success: bool Returns False if any potentially poorly scaled components were found """ vars_without_bounds, vars_with_large_bounds = _check_var_bounds(m, too_large) cons_with_large_bounds = dict() cons_with_large_coefficients = dict() cons_with_small_coefficients = dict() objs_with_large_coefficients = pyo.ComponentMap() objs_with_small_coefficients = pyo.ComponentMap() for c in m.component_data_objects(pyo.Constraint, active=True, descend_into=True): _check_coefficients( c, c.body, too_large, too_small, cons_with_large_coefficients, cons_with_small_coefficients, ) for c in m.component_data_objects(pyo.Constraint, active=True, descend_into=True): c_lb, c_ub = compute_bounds_on_expr(c.body) c_lb, c_ub = _bounds_to_float(c_lb, c_ub) if c_lb <= -too_large or c_ub >= too_large: cons_with_large_bounds[c] = (c_lb, c_ub) for c in m.component_data_objects(pyo.Objective, active=True, descend_into=True): _check_coefficients( c, c.expr, too_large, too_small, objs_with_large_coefficients, objs_with_small_coefficients, ) s = '\n\n' if len(vars_without_bounds) > 0: s += 'The following variables are not bounded. Please add bounds.\n' s += _print_var_set(vars_without_bounds) if len(vars_with_large_bounds) > 0: s += 'The following variables have large bounds. Please scale them.\n' s += _print_var_set(vars_with_large_bounds) if len(objs_with_large_coefficients) > 0: s += 'The following objectives have potentially large coefficients. Please scale them.\n' s += _print_coefficients(objs_with_large_coefficients) if len(objs_with_small_coefficients) > 0: s += 'The following objectives have small coefficients.\n' s += _print_coefficients(objs_with_small_coefficients) if len(cons_with_large_coefficients) > 0: s += 'The following constraints have potentially large coefficients. Please scale them.\n' s += _print_coefficients(cons_with_large_coefficients) if len(cons_with_small_coefficients) > 0: s += 'The following constraints have small coefficients.\n' s += _print_coefficients(cons_with_small_coefficients) if len(cons_with_large_bounds) > 0: s += 'The following constraints have bodies with large bounds. Please scale them.\n' s += f'{"LB":>12}{"UB":>12} Constraint\n' for c, (c_lb, c_ub) in cons_with_large_bounds.items(): s += f'{c_lb:>12.2e}{c_ub:>12.2e} {str(c)}\n' if ( len(vars_without_bounds) > 0 or len(vars_with_large_bounds) > 0 or len(cons_with_large_coefficients) > 0 or len(cons_with_small_coefficients) > 0 or len(objs_with_small_coefficients) > 0 or len(objs_with_large_coefficients) > 0 or len(cons_with_large_bounds) > 0 ): logger.info(s) return False return True