1# ___________________________________________________________________________ 2# 3# Pyomo: Python Optimization Modeling Objects 4# Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC 5# Under the terms of Contract DE-NA0003525 with National Technology and 6# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain 7# rights in this software. 8# This software is distributed under the 3-clause BSD License. 9# ___________________________________________________________________________ 10 11from pyomo.common.collections import ComponentMap, ComponentSet 12import pyomo.core.expr.numeric_expr as numeric_expr 13from pyomo.core.expr.visitor import ExpressionValueVisitor, identify_variables 14from pyomo.core.expr.numvalue import nonpyomo_leaf_types, value 15from pyomo.core.expr.numvalue import is_fixed 16import pyomo.contrib.fbbt.interval as interval 17import math 18from pyomo.core.base.block import Block 19from pyomo.core.base.constraint import Constraint 20from pyomo.core.base.var import Var 21from pyomo.gdp import Disjunct 22from pyomo.core.base.expression import _GeneralExpressionData, ScalarExpression 23import logging 24from pyomo.common.errors import InfeasibleConstraintException, PyomoException 25from pyomo.common.config import ConfigBlock, ConfigValue, In, NonNegativeFloat, NonNegativeInt 26 27logger = logging.getLogger(__name__) 28 29 30""" 31The purpose of this file is to perform feasibility based bounds 32tightening. This is a very basic implementation, but it is done 33directly with pyomo expressions. The only functions that are meant to 34be used by users are fbbt and compute_bounds_on_expr. The first set of 35functions in this file (those with names starting with 36_prop_bnds_leaf_to_root) are used for propagating bounds from the 37variables to each node in the expression tree (all the way to the 38root node). The second set of functions (those with names starting 39with _prop_bnds_root_to_leaf) are used to propagate bounds from the 40constraint back to the variables. For example, consider the constraint 41x*y + z == 1 with -1 <= x <= 1 and -2 <= y <= 2. When propagating 42bounds from the variables to the root (the root is x*y + z), we find 43that -2 <= x*y <= 2, and that -inf <= x*y + z <= inf. However, 44from the constraint, we know that 1 <= x*y + z <= 1, so we may 45propagate bounds back to the variables. Since we know that 461 <= x*y + z <= 1 and -2 <= x*y <= 2, then we must have -1 <= z <= 3. 47However, bounds cannot be improved on x*y, so bounds cannot be 48improved on either x or y. 49 50>>> import pyomo.environ as pe 51>>> m = pe.ConcreteModel() 52>>> m.x = pe.Var(bounds=(-1,1)) 53>>> m.y = pe.Var(bounds=(-2,2)) 54>>> m.z = pe.Var() 55>>> from pyomo.contrib.fbbt.fbbt import fbbt 56>>> m.c = pe.Constraint(expr=m.x*m.y + m.z == 1) 57>>> fbbt(m) 58>>> print(m.z.lb, m.z.ub) 59-1.0 3.0 60 61""" 62 63 64class FBBTException(PyomoException): 65 pass 66 67 68def _prop_bnds_leaf_to_root_ProductExpression(node, bnds_dict, feasibility_tol): 69 """ 70 71 Parameters 72 ---------- 73 node: pyomo.core.expr.numeric_expr.ProductExpression 74 bnds_dict: ComponentMap 75 feasibility_tol: float 76 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 77 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 78 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 79 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 80 is more conservative). 81 """ 82 assert len(node.args) == 2 83 arg1, arg2 = node.args 84 lb1, ub1 = bnds_dict[arg1] 85 lb2, ub2 = bnds_dict[arg2] 86 if arg1 is arg2: 87 bnds_dict[node] = interval.power(lb1, ub1, 2, 2, feasibility_tol) 88 else: 89 bnds_dict[node] = interval.mul(lb1, ub1, lb2, ub2) 90 91 92def _prop_bnds_leaf_to_root_SumExpression(node, bnds_dict, feasibility_tol): 93 """ 94 95 Parameters 96 ---------- 97 node: pyomo.core.expr.numeric_expr.SumExpression 98 bnds_dict: ComponentMap 99 feasibility_tol: float 100 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 101 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 102 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 103 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 104 is more conservative). 105 """ 106 arg0 = node.arg(0) 107 lb, ub = bnds_dict[arg0] 108 for i in range(1, node.nargs()): 109 arg = node.arg(i) 110 lb2, ub2 = bnds_dict[arg] 111 lb, ub = interval.add(lb, ub, lb2, ub2) 112 bnds_dict[node] = (lb, ub) 113 114 115def _prop_bnds_leaf_to_root_LinearExpression(node: numeric_expr.LinearExpression, bnds_dict, feasibility_tol): 116 """ 117 This is very similar to sum expression 118 """ 119 lb, ub = bnds_dict[node.constant] 120 for coef, v in zip(node.linear_coefs, node.linear_vars): 121 coef_bnds = bnds_dict[coef] 122 v_bnds = bnds_dict[v] 123 term_bounds = interval.mul(*coef_bnds, *v_bnds) 124 lb, ub = interval.add(lb, ub, *term_bounds) 125 bnds_dict[node] = (lb, ub) 126 127 128def _prop_bnds_leaf_to_root_DivisionExpression(node, bnds_dict, feasibility_tol): 129 """ 130 131 Parameters 132 ---------- 133 node: pyomo.core.expr.numeric_expr.DivisionExpression 134 bnds_dict: ComponentMap 135 feasibility_tol: float 136 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 137 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 138 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 139 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 140 is more conservative). 141 """ 142 assert len(node.args) == 2 143 arg1, arg2 = node.args 144 lb1, ub1 = bnds_dict[arg1] 145 lb2, ub2 = bnds_dict[arg2] 146 bnds_dict[node] = interval.div(lb1, ub1, lb2, ub2, feasibility_tol=feasibility_tol) 147 148 149def _prop_bnds_leaf_to_root_PowExpression(node, bnds_dict, feasibility_tol): 150 """ 151 152 Parameters 153 ---------- 154 node: pyomo.core.expr.numeric_expr.PowExpression 155 bnds_dict: ComponentMap 156 feasibility_tol: float 157 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 158 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 159 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 160 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 161 is more conservative). 162 """ 163 assert len(node.args) == 2 164 arg1, arg2 = node.args 165 lb1, ub1 = bnds_dict[arg1] 166 lb2, ub2 = bnds_dict[arg2] 167 bnds_dict[node] = interval.power(lb1, ub1, lb2, ub2, feasibility_tol=feasibility_tol) 168 169 170def _prop_bnds_leaf_to_root_ReciprocalExpression(node, bnds_dict, feasibility_tol): 171 """ 172 173 Parameters 174 ---------- 175 node: pyomo.core.expr.numeric_expr.ReciprocalExpression 176 bnds_dict: ComponentMap 177 feasibility_tol: float 178 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 179 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 180 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 181 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 182 is more conservative). 183 """ 184 assert len(node.args) == 1 185 arg = node.args[0] 186 lb1, ub1 = bnds_dict[arg] 187 bnds_dict[node] = interval.inv(lb1, ub1, feasibility_tol) 188 189 190def _prop_bnds_leaf_to_root_NegationExpression(node, bnds_dict, feasibility_tol): 191 """ 192 193 Parameters 194 ---------- 195 node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression 196 bnds_dict: ComponentMap 197 feasibility_tol: float 198 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 199 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 200 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 201 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 202 is more conservative). 203 """ 204 assert len(node.args) == 1 205 arg = node.args[0] 206 lb1, ub1 = bnds_dict[arg] 207 bnds_dict[node] = interval.sub(0, 0, lb1, ub1) 208 209 210def _prop_bnds_leaf_to_root_exp(node, bnds_dict, feasibility_tol): 211 """ 212 213 Parameters 214 ---------- 215 node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression 216 bnds_dict: ComponentMap 217 feasibility_tol: float 218 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 219 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 220 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 221 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 222 is more conservative). 223 """ 224 assert len(node.args) == 1 225 arg = node.args[0] 226 lb1, ub1 = bnds_dict[arg] 227 bnds_dict[node] = interval.exp(lb1, ub1) 228 229 230def _prop_bnds_leaf_to_root_log(node, bnds_dict, feasibility_tol): 231 """ 232 233 Parameters 234 ---------- 235 node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression 236 bnds_dict: ComponentMap 237 feasibility_tol: float 238 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 239 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 240 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 241 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 242 is more conservative). 243 """ 244 assert len(node.args) == 1 245 arg = node.args[0] 246 lb1, ub1 = bnds_dict[arg] 247 bnds_dict[node] = interval.log(lb1, ub1) 248 249 250def _prop_bnds_leaf_to_root_log10(node, bnds_dict, feasibility_tol): 251 """ 252 253 Parameters 254 ---------- 255 node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression 256 bnds_dict: ComponentMap 257 feasibility_tol: float 258 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 259 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 260 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 261 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 262 is more conservative). 263 """ 264 assert len(node.args) == 1 265 arg = node.args[0] 266 lb1, ub1 = bnds_dict[arg] 267 bnds_dict[node] = interval.log10(lb1, ub1) 268 269 270def _prop_bnds_leaf_to_root_sin(node, bnds_dict, feasibility_tol): 271 """ 272 273 Parameters 274 ---------- 275 node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression 276 bnds_dict: ComponentMap 277 feasibility_tol: float 278 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 279 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 280 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 281 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 282 is more conservative). 283 """ 284 assert len(node.args) == 1 285 arg = node.args[0] 286 lb1, ub1 = bnds_dict[arg] 287 bnds_dict[node] = interval.sin(lb1, ub1) 288 289 290def _prop_bnds_leaf_to_root_cos(node, bnds_dict, feasibility_tol): 291 """ 292 293 Parameters 294 ---------- 295 node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression 296 bnds_dict: ComponentMap 297 feasibility_tol: float 298 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 299 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 300 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 301 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 302 is more conservative). 303 """ 304 assert len(node.args) == 1 305 arg = node.args[0] 306 lb1, ub1 = bnds_dict[arg] 307 bnds_dict[node] = interval.cos(lb1, ub1) 308 309 310def _prop_bnds_leaf_to_root_tan(node, bnds_dict, feasibility_tol): 311 """ 312 313 Parameters 314 ---------- 315 node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression 316 bnds_dict: ComponentMap 317 feasibility_tol: float 318 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 319 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 320 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 321 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 322 is more conservative). 323 """ 324 assert len(node.args) == 1 325 arg = node.args[0] 326 lb1, ub1 = bnds_dict[arg] 327 bnds_dict[node] = interval.tan(lb1, ub1) 328 329 330def _prop_bnds_leaf_to_root_asin(node, bnds_dict, feasibility_tol): 331 """ 332 333 Parameters 334 ---------- 335 node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression 336 bnds_dict: ComponentMap 337 feasibility_tol: float 338 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 339 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 340 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 341 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 342 is more conservative). 343 """ 344 assert len(node.args) == 1 345 arg = node.args[0] 346 lb1, ub1 = bnds_dict[arg] 347 bnds_dict[node] = interval.asin(lb1, ub1, -interval.inf, interval.inf, feasibility_tol) 348 349 350def _prop_bnds_leaf_to_root_acos(node, bnds_dict, feasibility_tol): 351 """ 352 353 Parameters 354 ---------- 355 node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression 356 bnds_dict: ComponentMap 357 feasibility_tol: float 358 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 359 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 360 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 361 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 362 is more conservative). 363 """ 364 assert len(node.args) == 1 365 arg = node.args[0] 366 lb1, ub1 = bnds_dict[arg] 367 bnds_dict[node] = interval.acos(lb1, ub1, -interval.inf, interval.inf, feasibility_tol) 368 369 370def _prop_bnds_leaf_to_root_atan(node, bnds_dict, feasibility_tol): 371 """ 372 373 Parameters 374 ---------- 375 node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression 376 bnds_dict: ComponentMap 377 feasibility_tol: float 378 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 379 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 380 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 381 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 382 is more conservative). 383 """ 384 assert len(node.args) == 1 385 arg = node.args[0] 386 lb1, ub1 = bnds_dict[arg] 387 bnds_dict[node] = interval.atan(lb1, ub1, -interval.inf, interval.inf) 388 389 390def _prop_bnds_leaf_to_root_sqrt(node, bnds_dict, feasibility_tol): 391 """ 392 393 Parameters 394 ---------- 395 node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression 396 bnds_dict: ComponentMap 397 feasibility_tol: float 398 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 399 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 400 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 401 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 402 is more conservative). 403 """ 404 assert len(node.args) == 1 405 arg = node.args[0] 406 lb1, ub1 = bnds_dict[arg] 407 bnds_dict[node] = interval.power(lb1, ub1, 0.5, 0.5, feasibility_tol=feasibility_tol) 408 409 410_unary_leaf_to_root_map = dict() 411_unary_leaf_to_root_map['exp'] = _prop_bnds_leaf_to_root_exp 412_unary_leaf_to_root_map['log'] = _prop_bnds_leaf_to_root_log 413_unary_leaf_to_root_map['log10'] = _prop_bnds_leaf_to_root_log10 414_unary_leaf_to_root_map['sin'] = _prop_bnds_leaf_to_root_sin 415_unary_leaf_to_root_map['cos'] = _prop_bnds_leaf_to_root_cos 416_unary_leaf_to_root_map['tan'] = _prop_bnds_leaf_to_root_tan 417_unary_leaf_to_root_map['asin'] = _prop_bnds_leaf_to_root_asin 418_unary_leaf_to_root_map['acos'] = _prop_bnds_leaf_to_root_acos 419_unary_leaf_to_root_map['atan'] = _prop_bnds_leaf_to_root_atan 420_unary_leaf_to_root_map['sqrt'] = _prop_bnds_leaf_to_root_sqrt 421 422 423def _prop_bnds_leaf_to_root_UnaryFunctionExpression(node, bnds_dict, feasibility_tol): 424 """ 425 426 Parameters 427 ---------- 428 node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression 429 bnds_dict: ComponentMap 430 feasibility_tol: float 431 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 432 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 433 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 434 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 435 is more conservative). 436 """ 437 if node.getname() in _unary_leaf_to_root_map: 438 _unary_leaf_to_root_map[node.getname()](node, bnds_dict, feasibility_tol) 439 else: 440 bnds_dict[node] = (-interval.inf, interval.inf) 441 442 443def _prop_bnds_leaf_to_root_GeneralExpression(node, bnds_dict, feasibility_tol): 444 """ 445 Propagate bounds from children to parent 446 447 Parameters 448 ---------- 449 node: pyomo.core.base.expression._GeneralExpressionData 450 bnds_dict: ComponentMap 451 feasibility_tol: float 452 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 453 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 454 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 455 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 456 is more conservative). 457 """ 458 expr_lb, expr_ub = bnds_dict[node.expr] 459 bnds_dict[node] = (expr_lb, expr_ub) 460 461 462_prop_bnds_leaf_to_root_map = dict() 463_prop_bnds_leaf_to_root_map[numeric_expr.ProductExpression] = _prop_bnds_leaf_to_root_ProductExpression 464_prop_bnds_leaf_to_root_map[numeric_expr.DivisionExpression] = _prop_bnds_leaf_to_root_DivisionExpression 465_prop_bnds_leaf_to_root_map[numeric_expr.ReciprocalExpression] = _prop_bnds_leaf_to_root_ReciprocalExpression 466_prop_bnds_leaf_to_root_map[numeric_expr.PowExpression] = _prop_bnds_leaf_to_root_PowExpression 467_prop_bnds_leaf_to_root_map[numeric_expr.SumExpression] = _prop_bnds_leaf_to_root_SumExpression 468_prop_bnds_leaf_to_root_map[numeric_expr.MonomialTermExpression] = _prop_bnds_leaf_to_root_ProductExpression 469_prop_bnds_leaf_to_root_map[numeric_expr.NegationExpression] = _prop_bnds_leaf_to_root_NegationExpression 470_prop_bnds_leaf_to_root_map[numeric_expr.UnaryFunctionExpression] = _prop_bnds_leaf_to_root_UnaryFunctionExpression 471_prop_bnds_leaf_to_root_map[numeric_expr.LinearExpression] = _prop_bnds_leaf_to_root_LinearExpression 472 473_prop_bnds_leaf_to_root_map[numeric_expr.NPV_ProductExpression] = _prop_bnds_leaf_to_root_ProductExpression 474_prop_bnds_leaf_to_root_map[numeric_expr.NPV_DivisionExpression] = _prop_bnds_leaf_to_root_DivisionExpression 475_prop_bnds_leaf_to_root_map[numeric_expr.NPV_ReciprocalExpression] = _prop_bnds_leaf_to_root_ReciprocalExpression 476_prop_bnds_leaf_to_root_map[numeric_expr.NPV_PowExpression] = _prop_bnds_leaf_to_root_PowExpression 477_prop_bnds_leaf_to_root_map[numeric_expr.NPV_SumExpression] = _prop_bnds_leaf_to_root_SumExpression 478_prop_bnds_leaf_to_root_map[numeric_expr.NPV_NegationExpression] = _prop_bnds_leaf_to_root_NegationExpression 479_prop_bnds_leaf_to_root_map[numeric_expr.NPV_UnaryFunctionExpression] = _prop_bnds_leaf_to_root_UnaryFunctionExpression 480 481_prop_bnds_leaf_to_root_map[_GeneralExpressionData] = _prop_bnds_leaf_to_root_GeneralExpression 482_prop_bnds_leaf_to_root_map[ScalarExpression] = _prop_bnds_leaf_to_root_GeneralExpression 483 484 485def _prop_bnds_root_to_leaf_ProductExpression(node, bnds_dict, feasibility_tol): 486 """ 487 488 Parameters 489 ---------- 490 node: pyomo.core.expr.numeric_expr.ProductExpression 491 bnds_dict: ComponentMap 492 feasibility_tol: float 493 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 494 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 495 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 496 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 497 is more conservative). 498 """ 499 assert len(node.args) == 2 500 arg1, arg2 = node.args 501 lb0, ub0 = bnds_dict[node] 502 lb1, ub1 = bnds_dict[arg1] 503 lb2, ub2 = bnds_dict[arg2] 504 if arg1 is arg2: 505 _lb1, _ub1 = interval._inverse_power1(lb0, ub0, 2, 2, orig_xl=lb1, orig_xu=ub1, feasibility_tol=feasibility_tol) 506 _lb2, _ub2 = _lb1, _ub1 507 else: 508 _lb1, _ub1 = interval.div(lb0, ub0, lb2, ub2, feasibility_tol) 509 _lb2, _ub2 = interval.div(lb0, ub0, lb1, ub1, feasibility_tol) 510 if _lb1 > lb1: 511 lb1 = _lb1 512 if _ub1 < ub1: 513 ub1 = _ub1 514 if _lb2 > lb2: 515 lb2 = _lb2 516 if _ub2 < ub2: 517 ub2 = _ub2 518 bnds_dict[arg1] = (lb1, ub1) 519 bnds_dict[arg2] = (lb2, ub2) 520 521 522def _prop_bnds_root_to_leaf_SumExpression(node, bnds_dict, feasibility_tol): 523 """ 524 This function is a bit complicated. A simpler implementation 525 would loop through each argument in the sum and do the following: 526 527 bounds_on_arg_i = bounds_on_entire_sum - bounds_on_sum_of_args_excluding_arg_i 528 529 and the bounds_on_sum_of_args_excluding_arg_i could be computed 530 for each argument. However, the computational expense would grow 531 approximately quadratically with the length of the sum. Thus, 532 we do the following. Consider the expression 533 534 y = x1 + x2 + x3 + x4 535 536 and suppose we have bounds on y. We first accumulate bounds to 537 obtain a list like the following 538 539 [(x1)_bounds, (x1+x2)_bounds, (x1+x2+x3)_bounds, (x1+x2+x3+x4)_bounds] 540 541 Then we can propagate bounds back to x1, x2, x3, and x4 with the 542 following 543 544 (x4)_bounds = (x1+x2+x3+x4)_bounds - (x1+x2+x3)_bounds 545 (x3)_bounds = (x1+x2+x3)_bounds - (x1+x2)_bounds 546 (x2)_bounds = (x1+x2)_bounds - (x1)_bounds 547 548 Parameters 549 ---------- 550 node: pyomo.core.expr.numeric_expr.ProductExpression 551 bnds_dict: ComponentMap 552 feasibility_tol: float 553 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 554 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 555 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 556 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 557 is more conservative). 558 """ 559 # first accumulate bounds 560 accumulated_bounds = list() 561 accumulated_bounds.append(bnds_dict[node.arg(0)]) 562 lb0, ub0 = bnds_dict[node] 563 for i in range(1, node.nargs()): 564 _lb0, _ub0 = accumulated_bounds[i-1] 565 _lb1, _ub1 = bnds_dict[node.arg(i)] 566 accumulated_bounds.append(interval.add(_lb0, _ub0, _lb1, _ub1)) 567 if lb0 > accumulated_bounds[node.nargs() - 1][0]: 568 accumulated_bounds[node.nargs() - 1] = (lb0, accumulated_bounds[node.nargs()-1][1]) 569 if ub0 < accumulated_bounds[node.nargs() - 1][1]: 570 accumulated_bounds[node.nargs() - 1] = (accumulated_bounds[node.nargs()-1][0], ub0) 571 572 for i in reversed(range(1, node.nargs())): 573 lb0, ub0 = accumulated_bounds[i] 574 lb1, ub1 = accumulated_bounds[i-1] 575 lb2, ub2 = bnds_dict[node.arg(i)] 576 _lb1, _ub1 = interval.sub(lb0, ub0, lb2, ub2) 577 _lb2, _ub2 = interval.sub(lb0, ub0, lb1, ub1) 578 if _lb1 > lb1: 579 lb1 = _lb1 580 if _ub1 < ub1: 581 ub1 = _ub1 582 if _lb2 > lb2: 583 lb2 = _lb2 584 if _ub2 < ub2: 585 ub2 = _ub2 586 accumulated_bounds[i-1] = (lb1, ub1) 587 bnds_dict[node.arg(i)] = (lb2, ub2) 588 lb, ub = bnds_dict[node.arg(0)] 589 _lb, _ub = accumulated_bounds[0] 590 if _lb > lb: 591 lb = _lb 592 if _ub < ub: 593 ub = _ub 594 bnds_dict[node.arg(0)] = (lb, ub) 595 596 597def _prop_bnds_root_to_leaf_LinearExpression(node: numeric_expr.LinearExpression, 598 bnds_dict: ComponentMap, 599 feasibility_tol: float): 600 """ 601 This is very similar to SumExpression. 602 """ 603 # first accumulate bounds 604 accumulated_bounds = list() 605 accumulated_bounds.append(bnds_dict[node.constant]) 606 lb0, ub0 = bnds_dict[node] 607 for coef, v in zip(node.linear_coefs, node.linear_vars): 608 _lb0, _ub0 = accumulated_bounds[-1] 609 _lb_coef, _ub_coef = bnds_dict[coef] 610 _lb_v, _ub_v = bnds_dict[v] 611 _lb_term, _ub_term = interval.mul(_lb_coef, _ub_coef, _lb_v, _ub_v) 612 accumulated_bounds.append(interval.add(_lb0, _ub0, _lb_term, _ub_term)) 613 if lb0 > accumulated_bounds[-1][0]: 614 accumulated_bounds[-1] = (lb0, accumulated_bounds[-1][1]) 615 if ub0 < accumulated_bounds[-1][1]: 616 accumulated_bounds[-1] = (accumulated_bounds[-1][0], ub0) 617 618 for i in reversed(range(len(node.linear_coefs))): 619 lb0, ub0 = accumulated_bounds[i + 1] 620 lb1, ub1 = accumulated_bounds[i] 621 coef = node.linear_coefs[i] 622 v = node.linear_vars[i] 623 coef_bnds = bnds_dict[coef] 624 v_bnds = bnds_dict[v] 625 lb2, ub2 = interval.mul(*coef_bnds, *v_bnds) 626 _lb1, _ub1 = interval.sub(lb0, ub0, lb2, ub2) 627 _lb2, _ub2 = interval.sub(lb0, ub0, lb1, ub1) 628 if _lb1 > lb1: 629 lb1 = _lb1 630 if _ub1 < ub1: 631 ub1 = _ub1 632 if _lb2 > lb2: 633 lb2 = _lb2 634 if _ub2 < ub2: 635 ub2 = _ub2 636 accumulated_bounds[i] = (lb1, ub1) 637 bnds_dict[v] = interval.div(lb2, ub2, *coef_bnds, feasibility_tol=feasibility_tol) 638 639 640def _prop_bnds_root_to_leaf_DivisionExpression(node, bnds_dict, feasibility_tol): 641 """ 642 643 Parameters 644 ---------- 645 node: pyomo.core.expr.numeric_expr.DivisionExpression 646 bnds_dict: ComponentMap 647 feasibility_tol: float 648 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 649 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 650 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 651 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 652 is more conservative). 653 """ 654 assert len(node.args) == 2 655 arg1, arg2 = node.args 656 lb0, ub0 = bnds_dict[node] 657 lb1, ub1 = bnds_dict[arg1] 658 lb2, ub2 = bnds_dict[arg2] 659 _lb1, _ub1 = interval.mul(lb0, ub0, lb2, ub2) 660 _lb2, _ub2 = interval.div(lb1, ub1, lb0, ub0, feasibility_tol=feasibility_tol) 661 if _lb1 > lb1: 662 lb1 = _lb1 663 if _ub1 < ub1: 664 ub1 = _ub1 665 if _lb2 > lb2: 666 lb2 = _lb2 667 if _ub2 < ub2: 668 ub2 = _ub2 669 bnds_dict[arg1] = (lb1, ub1) 670 bnds_dict[arg2] = (lb2, ub2) 671 672 673def _prop_bnds_root_to_leaf_PowExpression(node, bnds_dict, feasibility_tol): 674 """ 675 676 Parameters 677 ---------- 678 node: pyomo.core.expr.numeric_expr.PowExpression 679 bnds_dict: ComponentMap 680 feasibility_tol: float 681 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 682 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 683 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 684 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 685 is more conservative). 686 """ 687 assert len(node.args) == 2 688 arg1, arg2 = node.args 689 lb0, ub0 = bnds_dict[node] 690 lb1, ub1 = bnds_dict[arg1] 691 lb2, ub2 = bnds_dict[arg2] 692 _lb1, _ub1 = interval._inverse_power1(lb0, ub0, lb2, ub2, orig_xl=lb1, orig_xu=ub1, feasibility_tol=feasibility_tol) 693 if _lb1 > lb1: 694 lb1 = _lb1 695 if _ub1 < ub1: 696 ub1 = _ub1 697 bnds_dict[arg1] = (lb1, ub1) 698 699 if is_fixed(arg2) and lb2 == ub2: # No need to tighten the bounds on arg2 if arg2 is fixed 700 pass 701 else: 702 _lb2, _ub2 = interval._inverse_power2(lb0, ub0, lb1, ub1, feasiblity_tol=feasibility_tol) 703 if _lb2 > lb2: 704 lb2 = _lb2 705 if _ub2 < ub2: 706 ub2 = _ub2 707 bnds_dict[arg2] = (lb2, ub2) 708 709 710def _prop_bnds_root_to_leaf_sqrt(node, bnds_dict, feasibility_tol): 711 """ 712 713 Parameters 714 ---------- 715 node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression 716 bnds_dict: ComponentMap 717 feasibility_tol: float 718 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 719 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 720 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 721 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 722 is more conservative). 723 """ 724 assert len(node.args) == 1 725 arg1 = node.args[0] 726 lb0, ub0 = bnds_dict[node] 727 lb1, ub1 = bnds_dict[arg1] 728 lb2, ub2 = (0.5, 0.5) 729 _lb1, _ub1 = interval._inverse_power1(lb0, ub0, lb2, ub2, orig_xl=lb1, orig_xu=ub1, feasibility_tol=feasibility_tol) 730 if _lb1 > lb1: 731 lb1 = _lb1 732 if _ub1 < ub1: 733 ub1 = _ub1 734 bnds_dict[arg1] = (lb1, ub1) 735 736 737def _prop_bnds_root_to_leaf_ReciprocalExpression(node, bnds_dict, feasibility_tol): 738 """ 739 740 Parameters 741 ---------- 742 node: pyomo.core.expr.numeric_expr.ReciprocalExpression 743 bnds_dict: ComponentMap 744 feasibility_tol: float 745 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 746 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 747 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 748 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 749 is more conservative). 750 """ 751 assert len(node.args) == 1 752 arg = node.args[0] 753 lb0, ub0 = bnds_dict[node] 754 lb1, ub1 = bnds_dict[arg] 755 _lb1, _ub1 = interval.inv(lb0, ub0, feasibility_tol) 756 if _lb1 > lb1: 757 lb1 = _lb1 758 if _ub1 < ub1: 759 ub1 = _ub1 760 bnds_dict[arg] = (lb1, ub1) 761 762 763def _prop_bnds_root_to_leaf_NegationExpression(node, bnds_dict, feasibility_tol): 764 """ 765 766 Parameters 767 ---------- 768 node: pyomo.core.expr.numeric_expr.NegationExpression 769 bnds_dict: ComponentMap 770 feasibility_tol: float 771 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 772 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 773 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 774 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 775 is more conservative). 776 """ 777 assert len(node.args) == 1 778 arg = node.args[0] 779 lb0, ub0 = bnds_dict[node] 780 lb1, ub1 = bnds_dict[arg] 781 _lb1, _ub1 = interval.sub(0, 0, lb0, ub0) 782 if _lb1 > lb1: 783 lb1 = _lb1 784 if _ub1 < ub1: 785 ub1 = _ub1 786 bnds_dict[arg] = (lb1, ub1) 787 788 789def _prop_bnds_root_to_leaf_exp(node, bnds_dict, feasibility_tol): 790 """ 791 792 Parameters 793 ---------- 794 node: pyomo.core.expr.numeric_expr.ProductExpression 795 bnds_dict: ComponentMap 796 feasibility_tol: float 797 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 798 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 799 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 800 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 801 is more conservative). 802 """ 803 assert len(node.args) == 1 804 arg = node.args[0] 805 lb0, ub0 = bnds_dict[node] 806 lb1, ub1 = bnds_dict[arg] 807 _lb1, _ub1 = interval.log(lb0, ub0) 808 if _lb1 > lb1: 809 lb1 = _lb1 810 if _ub1 < ub1: 811 ub1 = _ub1 812 bnds_dict[arg] = (lb1, ub1) 813 814 815def _prop_bnds_root_to_leaf_log(node, bnds_dict, feasibility_tol): 816 """ 817 818 Parameters 819 ---------- 820 node: pyomo.core.expr.numeric_expr.ProductExpression 821 bnds_dict: ComponentMap 822 feasibility_tol: float 823 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 824 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 825 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 826 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 827 is more conservative). 828 """ 829 assert len(node.args) == 1 830 arg = node.args[0] 831 lb0, ub0 = bnds_dict[node] 832 lb1, ub1 = bnds_dict[arg] 833 _lb1, _ub1 = interval.exp(lb0, ub0) 834 if _lb1 > lb1: 835 lb1 = _lb1 836 if _ub1 < ub1: 837 ub1 = _ub1 838 bnds_dict[arg] = (lb1, ub1) 839 840 841def _prop_bnds_root_to_leaf_log10(node, bnds_dict, feasibility_tol): 842 """ 843 844 Parameters 845 ---------- 846 node: pyomo.core.expr.numeric_expr.ProductExpression 847 bnds_dict: ComponentMap 848 feasibility_tol: float 849 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 850 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 851 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 852 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 853 is more conservative). 854 """ 855 assert len(node.args) == 1 856 arg = node.args[0] 857 lb0, ub0 = bnds_dict[node] 858 lb1, ub1 = bnds_dict[arg] 859 _lb1, _ub1 = interval.power(10, 10, lb0, ub0, feasibility_tol=feasibility_tol) 860 if _lb1 > lb1: 861 lb1 = _lb1 862 if _ub1 < ub1: 863 ub1 = _ub1 864 bnds_dict[arg] = (lb1, ub1) 865 866 867def _prop_bnds_root_to_leaf_sin(node, bnds_dict, feasibility_tol): 868 """ 869 870 Parameters 871 ---------- 872 node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression 873 bnds_dict: ComponentMap 874 feasibility_tol: float 875 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 876 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 877 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 878 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 879 is more conservative). 880 """ 881 assert len(node.args) == 1 882 arg = node.args[0] 883 lb0, ub0 = bnds_dict[node] 884 lb1, ub1 = bnds_dict[arg] 885 _lb1, _ub1 = interval.asin(lb0, ub0, lb1, ub1, feasibility_tol) 886 if _lb1 > lb1: 887 lb1 = _lb1 888 if _ub1 < ub1: 889 ub1 = _ub1 890 bnds_dict[arg] = (lb1, ub1) 891 892 893def _prop_bnds_root_to_leaf_cos(node, bnds_dict, feasibility_tol): 894 """ 895 896 Parameters 897 ---------- 898 node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression 899 bnds_dict: ComponentMap 900 feasibility_tol: float 901 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 902 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 903 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 904 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 905 is more conservative). 906 """ 907 assert len(node.args) == 1 908 arg = node.args[0] 909 lb0, ub0 = bnds_dict[node] 910 lb1, ub1 = bnds_dict[arg] 911 _lb1, _ub1 = interval.acos(lb0, ub0, lb1, ub1, feasibility_tol) 912 if _lb1 > lb1: 913 lb1 = _lb1 914 if _ub1 < ub1: 915 ub1 = _ub1 916 bnds_dict[arg] = (lb1, ub1) 917 918 919def _prop_bnds_root_to_leaf_tan(node, bnds_dict, feasibility_tol): 920 """ 921 922 Parameters 923 ---------- 924 node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression 925 bnds_dict: ComponentMap 926 feasibility_tol: float 927 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 928 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 929 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 930 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 931 is more conservative). 932 """ 933 assert len(node.args) == 1 934 arg = node.args[0] 935 lb0, ub0 = bnds_dict[node] 936 lb1, ub1 = bnds_dict[arg] 937 _lb1, _ub1 = interval.atan(lb0, ub0, lb1, ub1) 938 if _lb1 > lb1: 939 lb1 = _lb1 940 if _ub1 < ub1: 941 ub1 = _ub1 942 bnds_dict[arg] = (lb1, ub1) 943 944 945def _prop_bnds_root_to_leaf_asin(node, bnds_dict, feasibility_tol): 946 """ 947 948 Parameters 949 ---------- 950 node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression 951 bnds_dict: ComponentMap 952 feasibility_tol: float 953 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 954 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 955 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 956 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 957 is more conservative). 958 """ 959 assert len(node.args) == 1 960 arg = node.args[0] 961 lb0, ub0 = bnds_dict[node] 962 lb1, ub1 = bnds_dict[arg] 963 _lb1, _ub1 = interval.sin(lb0, ub0) 964 if _lb1 > lb1: 965 lb1 = _lb1 966 if _ub1 < ub1: 967 ub1 = _ub1 968 bnds_dict[arg] = (lb1, ub1) 969 970 971def _prop_bnds_root_to_leaf_acos(node, bnds_dict, feasibility_tol): 972 """ 973 974 Parameters 975 ---------- 976 node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression 977 bnds_dict: ComponentMap 978 feasibility_tol: float 979 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 980 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 981 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 982 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 983 is more conservative). 984 """ 985 assert len(node.args) == 1 986 arg = node.args[0] 987 lb0, ub0 = bnds_dict[node] 988 lb1, ub1 = bnds_dict[arg] 989 _lb1, _ub1 = interval.cos(lb0, ub0) 990 if _lb1 > lb1: 991 lb1 = _lb1 992 if _ub1 < ub1: 993 ub1 = _ub1 994 bnds_dict[arg] = (lb1, ub1) 995 996 997def _prop_bnds_root_to_leaf_atan(node, bnds_dict, feasibility_tol): 998 """ 999 1000 Parameters 1001 ---------- 1002 node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression 1003 bnds_dict: ComponentMap 1004 feasibility_tol: float 1005 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 1006 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 1007 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 1008 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 1009 is more conservative). 1010 """ 1011 assert len(node.args) == 1 1012 arg = node.args[0] 1013 lb0, ub0 = bnds_dict[node] 1014 lb1, ub1 = bnds_dict[arg] 1015 _lb1, _ub1 = interval.tan(lb0, ub0) 1016 if _lb1 > lb1: 1017 lb1 = _lb1 1018 if _ub1 < ub1: 1019 ub1 = _ub1 1020 bnds_dict[arg] = (lb1, ub1) 1021 1022 1023_unary_root_to_leaf_map = dict() 1024_unary_root_to_leaf_map['exp'] = _prop_bnds_root_to_leaf_exp 1025_unary_root_to_leaf_map['log'] = _prop_bnds_root_to_leaf_log 1026_unary_root_to_leaf_map['log10'] = _prop_bnds_root_to_leaf_log10 1027_unary_root_to_leaf_map['sin'] = _prop_bnds_root_to_leaf_sin 1028_unary_root_to_leaf_map['cos'] = _prop_bnds_root_to_leaf_cos 1029_unary_root_to_leaf_map['tan'] = _prop_bnds_root_to_leaf_tan 1030_unary_root_to_leaf_map['asin'] = _prop_bnds_root_to_leaf_asin 1031_unary_root_to_leaf_map['acos'] = _prop_bnds_root_to_leaf_acos 1032_unary_root_to_leaf_map['atan'] = _prop_bnds_root_to_leaf_atan 1033_unary_root_to_leaf_map['sqrt'] = _prop_bnds_root_to_leaf_sqrt 1034 1035 1036def _prop_bnds_root_to_leaf_UnaryFunctionExpression(node, bnds_dict, feasibility_tol): 1037 """ 1038 1039 Parameters 1040 ---------- 1041 node: pyomo.core.expr.numeric_expr.UnaryFunctionExpression 1042 bnds_dict: ComponentMap 1043 feasibility_tol: float 1044 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 1045 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 1046 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 1047 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 1048 is more conservative). 1049 """ 1050 if node.getname() in _unary_root_to_leaf_map: 1051 _unary_root_to_leaf_map[node.getname()](node, bnds_dict, feasibility_tol) 1052 else: 1053 logger.warning('Unsupported expression type for FBBT: {0}. Bounds will not be improved in this part of ' 1054 'the tree.' 1055 ''.format(node.getname())) 1056 1057 1058def _prop_bnds_root_to_leaf_GeneralExpression(node, bnds_dict, feasibility_tol): 1059 """ 1060 Propagate bounds from parent to children. 1061 1062 Parameters 1063 ---------- 1064 node: pyomo.core.base.expression._GeneralExpressionData 1065 bnds_dict: ComponentMap 1066 feasibility_tol: float 1067 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 1068 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 1069 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 1070 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 1071 is more conservative). 1072 """ 1073 expr_lb, expr_ub = bnds_dict[node] 1074 bnds_dict[node.expr] = (expr_lb, expr_ub) 1075 1076 1077_prop_bnds_root_to_leaf_map = dict() 1078_prop_bnds_root_to_leaf_map[numeric_expr.ProductExpression] = _prop_bnds_root_to_leaf_ProductExpression 1079_prop_bnds_root_to_leaf_map[numeric_expr.DivisionExpression] = _prop_bnds_root_to_leaf_DivisionExpression 1080_prop_bnds_root_to_leaf_map[numeric_expr.ReciprocalExpression] = _prop_bnds_root_to_leaf_ReciprocalExpression 1081_prop_bnds_root_to_leaf_map[numeric_expr.PowExpression] = _prop_bnds_root_to_leaf_PowExpression 1082_prop_bnds_root_to_leaf_map[numeric_expr.SumExpression] = _prop_bnds_root_to_leaf_SumExpression 1083_prop_bnds_root_to_leaf_map[numeric_expr.MonomialTermExpression] = _prop_bnds_root_to_leaf_ProductExpression 1084_prop_bnds_root_to_leaf_map[numeric_expr.NegationExpression] = _prop_bnds_root_to_leaf_NegationExpression 1085_prop_bnds_root_to_leaf_map[numeric_expr.UnaryFunctionExpression] = _prop_bnds_root_to_leaf_UnaryFunctionExpression 1086_prop_bnds_root_to_leaf_map[numeric_expr.LinearExpression] = _prop_bnds_root_to_leaf_LinearExpression 1087 1088_prop_bnds_root_to_leaf_map[numeric_expr.NPV_ProductExpression] = _prop_bnds_root_to_leaf_ProductExpression 1089_prop_bnds_root_to_leaf_map[numeric_expr.NPV_DivisionExpression] = _prop_bnds_root_to_leaf_DivisionExpression 1090_prop_bnds_root_to_leaf_map[numeric_expr.NPV_ReciprocalExpression] = _prop_bnds_root_to_leaf_ReciprocalExpression 1091_prop_bnds_root_to_leaf_map[numeric_expr.NPV_PowExpression] = _prop_bnds_root_to_leaf_PowExpression 1092_prop_bnds_root_to_leaf_map[numeric_expr.NPV_SumExpression] = _prop_bnds_root_to_leaf_SumExpression 1093_prop_bnds_root_to_leaf_map[numeric_expr.NPV_NegationExpression] = _prop_bnds_root_to_leaf_NegationExpression 1094_prop_bnds_root_to_leaf_map[numeric_expr.NPV_UnaryFunctionExpression] = _prop_bnds_root_to_leaf_UnaryFunctionExpression 1095 1096_prop_bnds_root_to_leaf_map[_GeneralExpressionData] = _prop_bnds_root_to_leaf_GeneralExpression 1097_prop_bnds_root_to_leaf_map[ScalarExpression] = _prop_bnds_root_to_leaf_GeneralExpression 1098 1099 1100def _check_and_reset_bounds(var, lb, ub): 1101 """ 1102 This function ensures that lb is not less than var.lb and that ub is not greater than var.ub. 1103 """ 1104 orig_lb = value(var.lb) 1105 orig_ub = value(var.ub) 1106 if orig_lb is None: 1107 orig_lb = -interval.inf 1108 if orig_ub is None: 1109 orig_ub = interval.inf 1110 if lb < orig_lb: 1111 lb = orig_lb 1112 if ub > orig_ub: 1113 ub = orig_ub 1114 return lb, ub 1115 1116 1117class _FBBTVisitorLeafToRoot(ExpressionValueVisitor): 1118 """ 1119 This walker propagates bounds from the variables to each node in 1120 the expression tree (all the way to the root node). 1121 """ 1122 def __init__(self, bnds_dict, integer_tol=1e-4, feasibility_tol=1e-8): 1123 """ 1124 Parameters 1125 ---------- 1126 bnds_dict: ComponentMap 1127 integer_tol: float 1128 feasibility_tol: float 1129 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 1130 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 1131 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 1132 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 1133 is more conservative). 1134 """ 1135 self.bnds_dict = bnds_dict 1136 self.integer_tol = integer_tol 1137 self.feasibility_tol = feasibility_tol 1138 1139 def visit(self, node, values): 1140 if node.__class__ in _prop_bnds_leaf_to_root_map: 1141 _prop_bnds_leaf_to_root_map[node.__class__](node, self.bnds_dict, self.feasibility_tol) 1142 else: 1143 self.bnds_dict[node] = (-interval.inf, interval.inf) 1144 return None 1145 1146 def visiting_potential_leaf(self, node): 1147 if node.__class__ in nonpyomo_leaf_types: 1148 self.bnds_dict[node] = (node, node) 1149 return True, None 1150 1151 if node.is_variable_type(): 1152 if node in self.bnds_dict: 1153 return True, None 1154 if node.is_fixed(): 1155 lb = value(node.value) 1156 ub = lb 1157 else: 1158 lb = value(node.lb) 1159 ub = value(node.ub) 1160 if lb is None: 1161 lb = -interval.inf 1162 if ub is None: 1163 ub = interval.inf 1164 if lb - self.feasibility_tol > ub: 1165 raise InfeasibleConstraintException('Variable has a lower bound which is larger than its upper bound: {0}'.format(str(node))) 1166 self.bnds_dict[node] = (lb, ub) 1167 return True, None 1168 1169 if node.__class__ is numeric_expr.LinearExpression: 1170 const_val = value(node.constant) 1171 self.bnds_dict[node.constant] = (const_val, const_val) 1172 for coef in node.linear_coefs: 1173 coef_val = value(coef) 1174 self.bnds_dict[coef] = (coef_val, coef_val) 1175 for v in node.linear_vars: 1176 self.visiting_potential_leaf(v) 1177 _prop_bnds_leaf_to_root_LinearExpression(node, self.bnds_dict, self.feasibility_tol) 1178 return True, None 1179 1180 if not node.is_expression_type(): 1181 assert is_fixed(node) 1182 val = value(node) 1183 self.bnds_dict[node] = (val, val) 1184 return True, None 1185 1186 return False, None 1187 1188 1189class _FBBTVisitorRootToLeaf(ExpressionValueVisitor): 1190 """ 1191 This walker propagates bounds from the constraint back to the 1192 variables. Note that the bounds on every node in the tree must 1193 first be computed with _FBBTVisitorLeafToRoot. 1194 """ 1195 def __init__(self, bnds_dict, integer_tol=1e-4, feasibility_tol=1e-8): 1196 """ 1197 Parameters 1198 ---------- 1199 bnds_dict: ComponentMap 1200 integer_tol: float 1201 feasibility_tol: float 1202 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 1203 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 1204 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 1205 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 1206 is more conservative). 1207 """ 1208 self.bnds_dict = bnds_dict 1209 self.integer_tol = integer_tol 1210 self.feasibility_tol = feasibility_tol 1211 1212 def visit(self, node, values): 1213 pass 1214 1215 def visiting_potential_leaf(self, node): 1216 if node.__class__ in nonpyomo_leaf_types: 1217 lb, ub = self.bnds_dict[node] 1218 if abs(lb - value(node)) > self.feasibility_tol: 1219 raise InfeasibleConstraintException('Detected an infeasible constraint.') 1220 if abs(ub - value(node)) > self.feasibility_tol: 1221 raise InfeasibleConstraintException('Detected an infeasible constraint.') 1222 return True, None 1223 1224 if node.is_variable_type(): 1225 lb, ub = self.bnds_dict[node] 1226 1227 lb, ub = self.bnds_dict[node] 1228 if lb > ub: 1229 if lb - self.feasibility_tol > ub: 1230 raise InfeasibleConstraintException('Lower bound ({1}) computed for variable {0} is larger than the computed upper bound ({2}).'.format(node, lb, ub)) 1231 else: 1232 """ 1233 If we reach this code, then lb > ub, but not by more than feasibility_tol. 1234 Now we want to decrease lb slightly and increase ub slightly so that lb <= ub. 1235 However, we also have to make sure we do not make lb lower than the original lower bound 1236 and make sure we do not make ub larger than the original upper bound. This is what 1237 _check_and_reset_bounds is for. 1238 """ 1239 lb -= self.feasibility_tol 1240 ub += self.feasibility_tol 1241 lb, ub = _check_and_reset_bounds(node, lb, ub) 1242 self.bnds_dict[node] = (lb, ub) 1243 if lb == interval.inf: 1244 raise InfeasibleConstraintException('Computed a lower bound of +inf for variable {0}'.format(node)) 1245 if ub == -interval.inf: 1246 raise InfeasibleConstraintException('Computed an upper bound of -inf for variable {0}'.format(node)) 1247 1248 if node.is_binary() or node.is_integer(): 1249 """ 1250 This bit of code has two purposes: 1251 1) Improve the bounds on binary and integer variables with the fact that they are integer. 1252 2) Account for roundoff error. If the lower bound of a binary variable comes back as 1253 1e-16, the lower bound may actually be 0. This could potentially cause problems when 1254 handing the problem to a MIP solver. Some solvers are robust to this, but some may not be 1255 and may give the wrong solution. Even if the correct solution is found, this could 1256 introduce numerical problems. 1257 """ 1258 if lb > -interval.inf: 1259 lb = max(math.floor(lb), math.ceil(lb - self.integer_tol)) 1260 if ub < interval.inf: 1261 ub = min(math.ceil(ub), math.floor(ub + self.integer_tol)) 1262 """ 1263 We have to make sure we do not make lb lower than the original lower bound 1264 and make sure we do not make ub larger than the original upper bound. This is what 1265 _check_and_reset_bounds is for. 1266 """ 1267 lb, ub = _check_and_reset_bounds(node, lb, ub) 1268 self.bnds_dict[node] = (lb, ub) 1269 1270 if lb != -interval.inf: 1271 node.setlb(lb) 1272 if ub != interval.inf: 1273 node.setub(ub) 1274 return True, None 1275 1276 if node.__class__ is numeric_expr.LinearExpression: 1277 _prop_bnds_root_to_leaf_LinearExpression(node, self.bnds_dict, self.feasibility_tol) 1278 for v in node.linear_vars: 1279 self.visiting_potential_leaf(v) 1280 return True, None 1281 1282 if not node.is_expression_type(): 1283 lb, ub = self.bnds_dict[node] 1284 if abs(lb - value(node)) > self.feasibility_tol: 1285 raise InfeasibleConstraintException('Detected an infeasible constraint.') 1286 if abs(ub - value(node)) > self.feasibility_tol: 1287 raise InfeasibleConstraintException('Detected an infeasible constraint.') 1288 return True, None 1289 1290 if node.__class__ in _prop_bnds_root_to_leaf_map: 1291 _prop_bnds_root_to_leaf_map[node.__class__](node, self.bnds_dict, self.feasibility_tol) 1292 else: 1293 logger.warning('Unsupported expression type for FBBT: {0}. Bounds will not be improved in this part of ' 1294 'the tree.' 1295 ''.format(str(type(node)))) 1296 1297 return False, None 1298 1299 1300def _fbbt_con(con, config): 1301 """ 1302 Feasibility based bounds tightening for a constraint. This function attempts to improve the bounds of each variable 1303 in the constraint based on the bounds of the constraint and the bounds of the other variables in the constraint. 1304 For example: 1305 1306 >>> import pyomo.environ as pe 1307 >>> from pyomo.contrib.fbbt.fbbt import fbbt 1308 >>> m = pe.ConcreteModel() 1309 >>> m.x = pe.Var(bounds=(-1,1)) 1310 >>> m.y = pe.Var(bounds=(-2,2)) 1311 >>> m.z = pe.Var() 1312 >>> m.c = pe.Constraint(expr=m.x*m.y + m.z == 1) 1313 >>> fbbt(m.c) 1314 >>> print(m.z.lb, m.z.ub) 1315 -1.0 3.0 1316 1317 Parameters 1318 ---------- 1319 con: pyomo.core.base.constraint.Constraint 1320 constraint on which to perform fbbt 1321 config: ConfigBlock 1322 see documentation for fbbt 1323 1324 Returns 1325 ------- 1326 new_var_bounds: ComponentMap 1327 A ComponentMap mapping from variables a tuple containing the lower and upper bounds, respectively, computed 1328 from FBBT. 1329 """ 1330 if not con.active: 1331 return ComponentMap() 1332 1333 bnds_dict = ComponentMap() # a dictionary to store the bounds of every node in the tree 1334 1335 # a walker to propagate bounds from the variables to the root 1336 visitorA = _FBBTVisitorLeafToRoot(bnds_dict, feasibility_tol=config.feasibility_tol) 1337 visitorA.dfs_postorder_stack(con.body) 1338 1339 # Now we need to replace the bounds in bnds_dict for the root 1340 # node with the bounds on the constraint (if those bounds are 1341 # better). 1342 _lb = value(con.lower) 1343 _ub = value(con.upper) 1344 if _lb is None: 1345 _lb = -interval.inf 1346 if _ub is None: 1347 _ub = interval.inf 1348 1349 lb, ub = bnds_dict[con.body] 1350 1351 # check if the constraint is infeasible 1352 if lb > _ub + config.feasibility_tol or ub < _lb - config.feasibility_tol: 1353 raise InfeasibleConstraintException('Detected an infeasible constraint during FBBT: {0}'.format(str(con))) 1354 1355 # check if the constraint is always satisfied 1356 if config.deactivate_satisfied_constraints: 1357 if lb >= _lb - config.feasibility_tol and ub <= _ub + config.feasibility_tol: 1358 con.deactivate() 1359 1360 if _lb > lb: 1361 lb = _lb 1362 if _ub < ub: 1363 ub = _ub 1364 bnds_dict[con.body] = (lb, ub) 1365 1366 # Now, propagate bounds back from the root to the variables 1367 visitorB = _FBBTVisitorRootToLeaf(bnds_dict, integer_tol=config.integer_tol, feasibility_tol=config.feasibility_tol) 1368 visitorB.dfs_postorder_stack(con.body) 1369 1370 new_var_bounds = ComponentMap() 1371 for _node, _bnds in bnds_dict.items(): 1372 if _node.__class__ in nonpyomo_leaf_types: 1373 continue 1374 if _node.is_variable_type(): 1375 lb, ub = bnds_dict[_node] 1376 if lb == -interval.inf: 1377 lb = None 1378 if ub == interval.inf: 1379 ub = None 1380 new_var_bounds[_node] = (lb, ub) 1381 return new_var_bounds 1382 1383 1384def _fbbt_block(m, config): 1385 """ 1386 Feasibility based bounds tightening (FBBT) for a block or model. This 1387 loops through all of the constraints in the block and performs 1388 FBBT on each constraint (see the docstring for _fbbt_con()). 1389 Through this processes, any variables whose bounds improve 1390 by more than tol are collected, and FBBT is 1391 performed again on all constraints involving those variables. 1392 This process is continued until no variable bounds are improved 1393 by more than tol. 1394 1395 Parameters 1396 ---------- 1397 m: pyomo.core.base.block.Block or pyomo.core.base.PyomoModel.ConcreteModel 1398 config: ConfigBlock 1399 See the docs for fbbt 1400 1401 Returns 1402 ------- 1403 new_var_bounds: ComponentMap 1404 A ComponentMap mapping from variables a tuple containing the lower and upper bounds, respectively, computed 1405 from FBBT. 1406 """ 1407 new_var_bounds = ComponentMap() 1408 var_to_con_map = ComponentMap() 1409 var_lbs = ComponentMap() 1410 var_ubs = ComponentMap() 1411 n_cons = 0 1412 for c in m.component_data_objects(ctype=Constraint, active=True, 1413 descend_into=config.descend_into, sort=True): 1414 for v in identify_variables(c.body): 1415 if v not in var_to_con_map: 1416 var_to_con_map[v] = list() 1417 if v.lb is None: 1418 var_lbs[v] = -interval.inf 1419 else: 1420 var_lbs[v] = value(v.lb) 1421 if v.ub is None: 1422 var_ubs[v] = interval.inf 1423 else: 1424 var_ubs[v] = value(v.ub) 1425 var_to_con_map[v].append(c) 1426 n_cons += 1 1427 1428 for _v in m.component_data_objects(ctype=Var, active=True, descend_into=True, sort=True): 1429 if _v.is_fixed(): 1430 _v.setlb(_v.value) 1431 _v.setub(_v.value) 1432 new_var_bounds[_v] = (_v.value, _v.value) 1433 1434 n_fbbt = 0 1435 1436 improved_vars = ComponentSet() 1437 for c in m.component_data_objects(ctype=Constraint, active=True, 1438 descend_into=config.descend_into, sort=True): 1439 _new_var_bounds = _fbbt_con(c, config) 1440 n_fbbt += 1 1441 new_var_bounds.update(_new_var_bounds) 1442 for v, bnds in _new_var_bounds.items(): 1443 vlb, vub = bnds 1444 if vlb is not None: 1445 if vlb > var_lbs[v] + config.improvement_tol: 1446 improved_vars.add(v) 1447 var_lbs[v] = vlb 1448 if vub is not None: 1449 if vub < var_ubs[v] - config.improvement_tol: 1450 improved_vars.add(v) 1451 var_ubs[v] = vub 1452 1453 while len(improved_vars) > 0: 1454 if n_fbbt >= n_cons * config.max_iter: 1455 break 1456 v = improved_vars.pop() 1457 for c in var_to_con_map[v]: 1458 _new_var_bounds = _fbbt_con(c, config) 1459 n_fbbt += 1 1460 new_var_bounds.update(_new_var_bounds) 1461 for _v, bnds in _new_var_bounds.items(): 1462 _vlb, _vub = bnds 1463 if _vlb is not None: 1464 if _vlb > var_lbs[_v] + config.improvement_tol: 1465 improved_vars.add(_v) 1466 var_lbs[_v] = _vlb 1467 if _vub is not None: 1468 if _vub < var_ubs[_v] - config.improvement_tol: 1469 improved_vars.add(_v) 1470 var_ubs[_v] = _vub 1471 1472 return new_var_bounds 1473 1474 1475def fbbt(comp, deactivate_satisfied_constraints=False, integer_tol=1e-5, feasibility_tol=1e-8, max_iter=10, 1476 improvement_tol=1e-4, descend_into=True): 1477 """ 1478 Perform FBBT on a constraint, block, or model. For more control, 1479 use _fbbt_con and _fbbt_block. For detailed documentation, see 1480 the docstrings for _fbbt_con and _fbbt_block. 1481 1482 Parameters 1483 ---------- 1484 comp: pyomo.core.base.constraint.Constraint or pyomo.core.base.block.Block or pyomo.core.base.PyomoModel.ConcreteModel 1485 deactivate_satisfied_constraints: bool 1486 If deactivate_satisfied_constraints is True and a constraint is always satisfied, then the constranit 1487 will be deactivated 1488 integer_tol: float 1489 If the lower bound computed on a binary variable is less than or equal to integer_tol, then the 1490 lower bound is left at 0. Otherwise, the lower bound is increased to 1. If the upper bound computed 1491 on a binary variable is greater than or equal to 1-integer_tol, then the upper bound is left at 1. 1492 Otherwise the upper bound is decreased to 0. 1493 feasibility_tol: float 1494 If the bounds computed on the body of a constraint violate the bounds of the constraint by more than 1495 feasibility_tol, then the constraint is considered infeasible and an exception is raised. This tolerance 1496 is also used when performing certain interval arithmetic operations to ensure that none of the feasible 1497 region is removed due to floating point arithmetic and to prevent math domain errors (a larger value 1498 is more conservative). 1499 max_iter: int 1500 Used for Blocks only (i.e., comp.ctype == Block). When performing FBBT on a Block, we first perform FBBT on 1501 every constraint in the Block. We then attempt to identify which constraints to repeat FBBT on based on the 1502 improvement in variable bounds. If the bounds on a variable improve by more than improvement_tol, then FBBT 1503 is performed on the constraints using that Var. However, this algorithm is not guaranteed to converge, so 1504 max_iter limits the total number of times FBBT is performed to max_iter times the number of constraints 1505 in the Block. 1506 improvement_tol: float 1507 Used for Blocks only (i.e., comp.ctype == Block). When performing FBBT on a Block, we first perform FBBT on 1508 every constraint in the Block. We then attempt to identify which constraints to repeat FBBT on based on the 1509 improvement in variable bounds. If the bounds on a variable improve by more than improvement_tol, then FBBT 1510 is performed on the constraints using that Var. 1511 1512 Returns 1513 ------- 1514 new_var_bounds: ComponentMap 1515 A ComponentMap mapping from variables a tuple containing the lower and upper bounds, respectively, computed 1516 from FBBT. 1517 """ 1518 config = ConfigBlock() 1519 dsc_config = ConfigValue(default=deactivate_satisfied_constraints, domain=In({True, False})) 1520 integer_tol_config = ConfigValue(default=integer_tol, domain=NonNegativeFloat) 1521 ft_config = ConfigValue(default=feasibility_tol, domain=NonNegativeFloat) 1522 mi_config = ConfigValue(default=max_iter, domain=NonNegativeInt) 1523 improvement_tol_config = ConfigValue(default=improvement_tol, domain=NonNegativeFloat) 1524 descend_into_config = ConfigValue(default=descend_into) 1525 config.declare('deactivate_satisfied_constraints', dsc_config) 1526 config.declare('integer_tol', integer_tol_config) 1527 config.declare('feasibility_tol', ft_config) 1528 config.declare('max_iter', mi_config) 1529 config.declare('improvement_tol', improvement_tol_config) 1530 config.declare('descend_into', descend_into_config) 1531 1532 new_var_bounds = ComponentMap() 1533 if comp.ctype == Constraint: 1534 if comp.is_indexed(): 1535 for _c in comp.values(): 1536 _new_var_bounds = _fbbt_con(comp, config) 1537 new_var_bounds.update(_new_var_bounds) 1538 else: 1539 _new_var_bounds = _fbbt_con(comp, config) 1540 new_var_bounds.update(_new_var_bounds) 1541 elif comp.ctype in {Block, Disjunct}: 1542 _new_var_bounds = _fbbt_block(comp, config) 1543 new_var_bounds.update(_new_var_bounds) 1544 else: 1545 raise FBBTException('Cannot perform FBBT on objects of type {0}'.format(type(comp))) 1546 1547 return new_var_bounds 1548 1549 1550def compute_bounds_on_expr(expr): 1551 """ 1552 Compute bounds on an expression based on the bounds on the variables in the expression. 1553 1554 Parameters 1555 ---------- 1556 expr: pyomo.core.expr.numeric_expr.ExpressionBase 1557 1558 Returns 1559 ------- 1560 lb: float 1561 ub: float 1562 """ 1563 bnds_dict = ComponentMap() 1564 visitor = _FBBTVisitorLeafToRoot(bnds_dict) 1565 visitor.dfs_postorder_stack(expr) 1566 lb, ub = bnds_dict[expr] 1567 if lb == -interval.inf: 1568 lb = None 1569 if ub == interval.inf: 1570 ub = None 1571 1572 return lb, ub 1573 1574 1575class BoundsManager(object): 1576 def __init__(self, comp): 1577 self._vars = ComponentSet() 1578 self._saved_bounds = list() 1579 1580 if comp.ctype == Constraint: 1581 if comp.is_indexed(): 1582 for c in comp.values(): 1583 self._vars.update(identify_variables(c.body)) 1584 else: 1585 self._vars.update(identify_variables(comp.body)) 1586 else: 1587 for c in comp.component_data_objects(Constraint, descend_into=True, active=True, sort=True): 1588 self._vars.update(identify_variables(c.body)) 1589 1590 def save_bounds(self): 1591 bnds = ComponentMap() 1592 for v in self._vars: 1593 bnds[v] = (v.lb, v.ub) 1594 self._saved_bounds.append(bnds) 1595 1596 def pop_bounds(self, ndx=-1): 1597 bnds = self._saved_bounds.pop(ndx) 1598 for v, _bnds in bnds.items(): 1599 lb, ub = _bnds 1600 v.setlb(lb) 1601 v.setub(ub) 1602 1603 def load_bounds(self, bnds, save_current_bounds=True): 1604 if save_current_bounds: 1605 self.save_bounds() 1606 for v, _bnds in bnds.items(): 1607 if v in self._vars: 1608 lb, ub = _bnds 1609 v.setlb(lb) 1610 v.setub(ub) 1611