def test_acos(self): m = pe.Block(concrete=True) m.x = pe.Var() m.c = pe.Constraint(expr=pe.inequality(body=pe.acos(m.x), lower=1, upper=2)) fbbt(m) self.assertAlmostEqual(pe.value(m.x.lb), math.cos(2)) self.assertAlmostEqual(pe.value(m.x.ub), math.cos(1))
def test_atan(self): m = pe.Block(concrete=True) m.x = pe.Var() m.c = pe.Constraint(expr=pe.inequality(body=pe.atan(m.x), lower=-0.5, upper=0.5)) fbbt(m) self.assertAlmostEqual(pe.value(m.x.lb), math.tan(-0.5)) self.assertAlmostEqual(pe.value(m.x.ub), math.tan(0.5))
def test_always_feasible(self): m = pe.ConcreteModel() m.x = pe.Var(bounds=(1,2)) m.y = pe.Var(bounds=(1,2)) m.c = pe.Constraint(expr=m.x + m.y >= 0) fbbt(m) self.assertTrue(m.c.active) fbbt(m, deactivate_satisfied_constraints=True) self.assertFalse(m.c.active)
def test_pow5(self): m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var(bounds=(0.5, 1)) m.c = pe.Constraint(expr=2**m.x == m.y) fbbt(m) self.assertAlmostEqual(m.x.lb, -1) self.assertAlmostEqual(m.x.ub, 0)
def test_cos(self): m = pe.Block(concrete=True) m.x = pe.Var(bounds=(0, math.pi)) m.c = pe.Constraint(expr=pe.inequality(body=pe.cos(m.x), lower=-0.5, upper=0.5)) fbbt(m) self.assertAlmostEqual(pe.value(m.x.lb), math.acos(0.5)) self.assertAlmostEqual(pe.value(m.x.ub), math.acos(-0.5)) m = pe.Block(concrete=True) m.x = pe.Var() m.c = pe.Constraint(expr=pe.inequality(body=pe.cos(m.x), lower=-0.5, upper=0.5)) fbbt(m) self.assertEqual(m.x.lb, None) self.assertEqual(m.x.ub, None)
def test_add(self): x_bounds = [(-2.5, 2.8), (-2.5, -0.5), (0.5, 2.8), (-2.5, 0), (0, 2.8), (-2.5, -1), (1, 2.8), (-1, -0.5), (0.5, 1)] c_bounds = [(-2.5, 2.8), (-2.5, -0.5), (0.5, 2.8), (-2.5, 0), (0, 2.8), (-2.5, -1), (1, 2.8), (-1, -0.5), (0.5, 1)] for xl, xu in x_bounds: for cl, cu in c_bounds: m = pe.Block(concrete=True) m.x = pe.Var(bounds=(xl, xu)) m.y = pe.Var() m.p = pe.Param(mutable=True) m.p.value = 1 m.c = pe.Constraint(expr=pe.inequality(body=m.x+m.y+(m.p+1), lower=cl, upper=cu)) new_bounds = fbbt(m) self.assertEqual(new_bounds[m.x], (pe.value(m.x.lb), pe.value(m.x.ub))) self.assertEqual(new_bounds[m.y], (pe.value(m.y.lb), pe.value(m.y.ub))) x = np.linspace(pe.value(m.x.lb), pe.value(m.x.ub), 100) z = np.linspace(pe.value(m.c.lower), pe.value(m.c.upper), 100) if m.y.lb is None: yl = -np.inf else: yl = m.y.lb if m.y.ub is None: yu = np.inf else: yu = m.y.ub for _x in x: _y = z - _x - m.p.value - 1 self.assertTrue(np.all(yl <= _y)) self.assertTrue(np.all(yu >= _y))
def test_multiple_constraints2(self): m = pe.ConcreteModel() m.x = pe.Var(bounds=(-3, 3)) m.y = pe.Var(bounds=(None, 0)) m.z = pe.Var() m.c = pe.ConstraintList() m.c.add(-m.x - m.y >= -1) m.c.add(-m.x - m.y <= -1) m.c.add(-m.y - m.x*m.z >= -2) m.c.add(-m.y - m.x*m.z <= 2) m.c.add(-m.x - m.z == 1) fbbt(m) self.assertAlmostEqual(pe.value(m.x.lb), 1, 8) self.assertAlmostEqual(pe.value(m.x.ub), 1, 8) self.assertAlmostEqual(pe.value(m.y.lb), 0, 8) self.assertAlmostEqual(pe.value(m.y.ub), 0, 8) self.assertAlmostEqual(pe.value(m.z.lb), -2, 8) self.assertAlmostEqual(pe.value(m.z.ub), -2, 8)
def test_log(self): c_bounds = [(-2.5, 2.8), (-2.5, -0.5), (0.5, 2.8), (-2.5, 0), (0, 2.8), (-2.5, -1), (1, 2.8), (-1, -0.5), (0.5, 1)] for cl, cu in c_bounds: m = pe.Block(concrete=True) m.x = pe.Var() m.c = pe.Constraint(expr=pe.inequality(body=pe.log(m.x), lower=cl, upper=cu)) fbbt(m) z = np.linspace(pe.value(m.c.lower), pe.value(m.c.upper), 100) if m.x.lb is None: xl = -np.inf else: xl = pe.value(m.x.lb) if m.x.ub is None: xu = np.inf else: xu = pe.value(m.x.ub) x = np.exp(z) self.assertTrue(np.all(xl <= x)) self.assertTrue(np.all(xu >= x))
def test_x_cubed(self): m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() m.c = pe.Constraint(expr=m.x**3 == m.y) fbbt(m) self.assertEqual(m.x.lb, None) self.assertEqual(m.x.ub, None) self.assertEqual(m.y.lb, None) self.assertEqual(m.y.ub, None) m.x.setlb(None) m.x.setub(None) m.y.setlb(1) m.y.setub(8) fbbt(m) self.assertAlmostEqual(m.x.lb, 1) self.assertAlmostEqual(m.x.ub, 2) m.x.setlb(None) m.x.setub(None) m.y.setlb(-8) m.y.setub(8) fbbt(m) self.assertAlmostEqual(m.x.lb, -2) self.assertAlmostEqual(m.x.ub, 2) m.x.setlb(None) m.x.setub(None) m.y.setlb(-5) m.y.setub(8) fbbt(m) self.assertAlmostEqual(m.x.lb, -5.0**(1.0/3.0)) self.assertAlmostEqual(m.x.ub, 2) m.x.setlb(None) m.x.setub(None) m.y.setlb(-8) m.y.setub(-1) fbbt(m) self.assertAlmostEqual(m.x.lb, -2) self.assertAlmostEqual(m.x.ub, -1)
def test_sqrt(self): m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() m.c = pe.Constraint(expr=pe.sqrt(m.x) == m.y) fbbt(m) self.assertEqual(m.x.lb, 0) self.assertEqual(m.x.ub, None) self.assertEqual(m.y.lb, 0) self.assertEqual(m.y.ub, None) m.x.setlb(None) m.x.setub(None) m.y.setlb(-5) m.y.setub(-1) with self.assertRaises(InfeasibleConstraintException): fbbt(m) m.x.setlb(None) m.x.setub(None) m.y.setub(0) m.y.setlb(None) fbbt(m) self.assertAlmostEqual(m.x.lb, 0) self.assertAlmostEqual(m.x.ub, 0) m.x.setlb(None) m.x.setub(None) m.y.setub(2) m.y.setlb(1) fbbt(m) self.assertAlmostEqual(m.x.lb, 1) self.assertAlmostEqual(m.x.ub, 4) m.x.setlb(None) m.x.setub(0) m.y.setlb(None) m.y.setub(None) fbbt(m) self.assertAlmostEqual(m.y.lb, 0) self.assertAlmostEqual(m.y.ub, 0)
def test_binary(self): m = pe.ConcreteModel() m.x = pe.Var(domain=pe.Binary) m.y = pe.Var(domain=pe.Binary) m.c = pe.Constraint(expr=m.x + m.y >= 1.5) fbbt(m) self.assertEqual(pe.value(m.x.lb), 1) self.assertEqual(pe.value(m.x.ub), 1) self.assertEqual(pe.value(m.y.lb), 1) self.assertEqual(pe.value(m.y.ub), 1) m = pe.ConcreteModel() m.x = pe.Var(domain=pe.Binary) m.y = pe.Var(domain=pe.Binary) m.c = pe.Constraint(expr=m.x + m.y <= 0.5) fbbt(m) self.assertEqual(pe.value(m.x.lb), 0) self.assertEqual(pe.value(m.x.ub), 0) self.assertEqual(pe.value(m.y.lb), 0) self.assertEqual(pe.value(m.y.ub), 0)
def test_pow4(self): y_bounds = [(0.5, 2.8), (0, 2.8), (1, 2.8), (0.5, 1), (0, 0.5)] exp_vals = [-3, -2.5, -2, -1.5, -1, -0.5, 0.5, 1, 1.5, 2, 2.5, 3] for yl, yu in y_bounds: for _exp_val in exp_vals: m = pe.Block(concrete=True) m.x = pe.Var() m.y = pe.Var(bounds=(yl, yu)) m.c = pe.Constraint(expr=m.x**_exp_val == m.y) fbbt(m) y = np.linspace(pe.value(m.y.lb) + 1e-6, pe.value(m.y.ub), 100, endpoint=True) if m.x.lb is None: xl = -np.inf else: xl = m.x.lb if m.x.ub is None: xu = np.inf else: xu = m.x.ub _x = np.exp(np.log(y) / _exp_val) self.assertTrue(np.all(xl <= _x)) self.assertTrue(np.all(xu >= _x))
def test_x_pow_minus_3(self): m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() m.c = pe.Constraint(expr=m.x**(-3) == m.y) fbbt(m) self.assertEqual(m.x.lb, None) self.assertEqual(m.x.ub, None) self.assertEqual(m.y.lb, None) self.assertEqual(m.y.ub, None) m.y.setlb(-1) m.y.setub(-0.125) fbbt(m) self.assertAlmostEqual(m.x.lb, -2) self.assertAlmostEqual(m.x.ub, -1) m.x.setlb(None) m.x.setub(None) m.y.setub(0) fbbt(m) self.assertEqual(m.x.lb, None) self.assertAlmostEqual(m.x.ub, -1) m.x.setlb(None) m.x.setub(None) m.y.setub(-1) m.y.setlb(1) fbbt(m) self.assertEqual(m.x.lb, None) self.assertEqual(m.x.ub, None) m.y.setlb(0.125) m.y.setub(1) fbbt(m) self.assertAlmostEqual(m.x.lb, 1) self.assertAlmostEqual(m.x.ub, 2)
def test_x_pow_minus_3(self): m = pyo.ConcreteModel() m.x = pyo.Var() m.y = pyo.Var() m.c = pyo.Constraint(expr=m.x**(-3) == m.y) fbbt(m) self.assertEqual(m.x.lb, None) self.assertEqual(m.x.ub, None) self.assertEqual(m.y.lb, None) self.assertEqual(m.y.ub, None) m.y.setlb(-1) m.y.setub(-0.125) fbbt(m) self.assertAlmostEqual(m.x.lb, -2) self.assertAlmostEqual(m.x.ub, -1) m.x.setlb(None) m.x.setub(None) m.y.setub(0) fbbt(m) self.assertEqual(m.x.lb, None) self.assertAlmostEqual(m.x.ub, -1) m.x.setlb(None) m.x.setub(None) m.y.setlb(-1) m.y.setub(1) fbbt(m) self.assertEqual(m.x.lb, None) self.assertEqual(m.x.ub, None) m.y.setlb(0.125) m.y.setub(1) fbbt(m) self.assertAlmostEqual(m.x.lb, 1) self.assertAlmostEqual(m.x.ub, 2)
def test_log10(self): c_bounds = [(-2.5, 2.8), (-2.5, -0.5), (0.5, 2.8), (-2.5, 0), (0, 2.8), (-2.5, -1), (1, 2.8), (-1, -0.5), (0.5, 1)] for cl, cu in c_bounds: m = pyo.Block(concrete=True) m.x = pyo.Var() m.c = pyo.Constraint( expr=pyo.inequality(body=pyo.log10(m.x), lower=cl, upper=cu)) fbbt(m) z = np.linspace(pyo.value(m.c.lower), pyo.value(m.c.upper), 100) if m.x.lb is None: xl = -np.inf else: xl = pyo.value(m.x.lb) if m.x.ub is None: xu = np.inf else: xu = pyo.value(m.x.ub) x = 10**z print(xl, xu, cl, cu) print(x) self.assertTrue(np.all(xl <= x)) self.assertTrue(np.all(xu >= x))
def test_no_update_bounds(self): m = pe.ConcreteModel() m.x = pe.Var(bounds=(0, 1)) m.y = pe.Var(bounds=(1, 1)) m.c = pe.Constraint(expr=m.x + m.y == 1) new_bounds = fbbt(m, update_variable_bounds=False) self.assertEqual(pe.value(m.x.lb), 0) self.assertEqual(pe.value(m.x.ub), 1) self.assertEqual(pe.value(m.y.lb), 1) self.assertEqual(pe.value(m.y.ub), 1) self.assertAlmostEqual(new_bounds[m.x][0], 0, 12) self.assertAlmostEqual(new_bounds[m.x][1], 0, 12) self.assertAlmostEqual(new_bounds[m.y][0], 1, 12) self.assertAlmostEqual(new_bounds[m.y][1], 1, 12)
def test_exp(self): c_bounds = [(-2.5, 2.8), (0.5, 2.8), (0, 2.8), (1, 2.8), (0.5, 1)] for cl, cu in c_bounds: m = pe.Block(concrete=True) m.x = pe.Var() m.c = pe.Constraint(expr=pe.inequality(body=pe.exp(m.x), lower=cl, upper=cu)) fbbt(m) if pe.value(m.c.lower) <= 0: _cl = 1e-6 else: _cl = pe.value(m.c.lower) z = np.linspace(_cl, pe.value(m.c.upper), 100) if m.x.lb is None: xl = -np.inf else: xl = pe.value(m.x.lb) if m.x.ub is None: xu = np.inf else: xu = pe.value(m.x.ub) x = np.log(z) self.assertTrue(np.all(xl <= x)) self.assertTrue(np.all(xu >= x))
def test_pow1(self): x_bounds = [(0, 2.8), (0.5, 2.8), (1, 2.8), (0.5, 1)] c_bounds = [(-2.5, 2.8), (0.5, 2.8), (-2.5, 0), (0, 2.8), (1, 2.8), (0.5, 1)] for xl, xu in x_bounds: for cl, cu in c_bounds: m = pe.Block(concrete=True) m.x = pe.Var(bounds=(xl, xu)) m.y = pe.Var() m.c = pe.Constraint(expr=pe.inequality(body=m.x**m.y, lower=cl, upper=cu)) fbbt(m) x = np.linspace(pe.value(m.x.lb) + 1e-6, pe.value(m.x.ub), 100, endpoint=False) z = np.linspace(pe.value(m.c.lower) + 1e-6, pe.value(m.c.upper), 100, endpoint=False) if m.y.lb is None: yl = -np.inf else: yl = m.y.lb if m.y.ub is None: yu = np.inf else: yu = m.y.ub for _x in x: _y = np.log(abs(z)) / np.log(abs(_x)) self.assertTrue(np.all(yl <= _y)) self.assertTrue(np.all(yu >= _y))
def test_sub1(self): x_bounds = [(-2.5, 2.8), (-2.5, -0.5), (0.5, 2.8), (-2.5, 0), (0, 2.8), (-2.5, -1), (1, 2.8), (-1, -0.5), (0.5, 1)] c_bounds = [(-2.5, 2.8), (-2.5, -0.5), (0.5, 2.8), (-2.5, 0), (0, 2.8), (-2.5, -1), (1, 2.8), (-1, -0.5), (0.5, 1)] for xl, xu in x_bounds: for cl, cu in c_bounds: m = pe.Block(concrete=True) m.x = pe.Var(bounds=(xl, xu)) m.y = pe.Var() m.c = pe.Constraint(expr=pe.inequality(body=m.x-m.y, lower=cl, upper=cu)) fbbt(m) x = np.linspace(pe.value(m.x.lb), pe.value(m.x.ub), 100) z = np.linspace(pe.value(m.c.lower), pe.value(m.c.upper), 100) if m.y.lb is None: yl = -np.inf else: yl = m.y.lb if m.y.ub is None: yu = np.inf else: yu = m.y.ub for _x in x: _y = _x - z self.assertTrue(np.all(yl <= _y)) self.assertTrue(np.all(yu >= _y))
def test_pow2(self): x_bounds = [(-2.5, 2.8), (-2.5, -0.5), (0.5, 2.8), (-2.5, 0), (0, 2.8), (-2.5, -1), (1, 2.8), (-1, -0.5), (0.5, 1)] c_bounds = [(-2.5, 2.8), (-2.5, -0.5), (0.5, 2.8), (-2.5, 0), (0, 2.8), (-2.5, -1), (1, 2.8), (-1, -0.5), (0.5, 1)] for xl, xu in x_bounds: for cl, cu in c_bounds: m = pe.Block(concrete=True) m.x = pe.Var(bounds=(xl, xu)) m.y = pe.Var() m.c = pe.Constraint( expr=pe.inequality(body=m.y**m.x, lower=cl, upper=cu)) fbbt(m) x = np.linspace(pe.value(m.x.lb) + 1e-6, pe.value(m.x.ub), 100, endpoint=False) z = np.linspace(pe.value(m.c.lower) + 1e-6, pe.value(m.c.upper), 100, endpoint=False) if m.y.lb is None: yl = -np.inf else: yl = m.y.lb if m.y.ub is None: yu = np.inf else: yu = m.y.ub for _x in x: _y = np.exp(np.log(abs(z)) / _x) self.assertTrue(np.all(yl <= _y)) self.assertTrue(np.all(yu >= _y)) _y = -_y self.assertTrue(np.all(yl <= _y)) self.assertTrue(np.all(yu >= _y))
def test_sub2(self): x_bounds = [(-2.5, 2.8), (-2.5, -0.5), (0.5, 2.8), (-2.5, 0), (0, 2.8), (-2.5, -1), (1, 2.8), (-1, -0.5), (0.5, 1)] c_bounds = [(-2.5, 2.8), (-2.5, -0.5), (0.5, 2.8), (-2.5, 0), (0, 2.8), (-2.5, -1), (1, 2.8), (-1, -0.5), (0.5, 1)] for xl, xu in x_bounds: for cl, cu in c_bounds: m = pe.Block(concrete=True) m.x = pe.Var(bounds=(xl, xu)) m.y = pe.Var() m.c = pe.Constraint(expr=pe.inequality(body=m.y-m.x, lower=cl, upper=cu)) fbbt(m) x = np.linspace(pe.value(m.x.lb), pe.value(m.x.ub), 100) z = np.linspace(pe.value(m.c.lower), pe.value(m.c.upper), 100) if m.y.lb is None: yl = -np.inf else: yl = m.y.lb if m.y.ub is None: yu = np.inf else: yu = m.y.ub for _x in x: _y = z + _x self.assertTrue(np.all(yl <= _y)) self.assertTrue(np.all(yu >= _y))
def test_pow1(self): x_bounds = [(0, 2.8), (0.5, 2.8), (1, 2.8), (0.5, 1)] c_bounds = [(-2.5, 2.8), (0.5, 2.8), (-2.5, 0), (0, 2.8), (1, 2.8), (0.5, 1)] for xl, xu in x_bounds: for cl, cu in c_bounds: m = pyo.Block(concrete=True) m.x = pyo.Var(bounds=(xl, xu)) m.y = pyo.Var() m.c = pyo.Constraint( expr=pyo.inequality(body=m.x**m.y, lower=cl, upper=cu)) if xl > 0 and cu <= 0: with self.assertRaises(InfeasibleConstraintException): fbbt(m) else: fbbt(m) x = np.linspace(pyo.value(m.x.lb) + 1e-6, pyo.value(m.x.ub), 100, endpoint=False) z = np.linspace(pyo.value(m.c.lower) + 1e-6, pyo.value(m.c.upper), 100, endpoint=False) if m.y.lb is None: yl = -np.inf else: yl = m.y.lb if m.y.ub is None: yu = np.inf else: yu = m.y.ub for _x in x: _y = np.log(abs(z)) / np.log(abs(_x)) self.assertTrue(np.all(yl <= _y)) self.assertTrue(np.all(yu >= _y))
def test_skip_unknown_expression1(self): m = pyo.ConcreteModel() m.x = pyo.Var(bounds=(1, 1)) m.y = pyo.Var() expr = DummyExpr([m.x, m.y]) m.c = pyo.Constraint(expr=expr == 1) OUT = StringIO() with LoggingIntercept(OUT, 'pyomo.contrib.fbbt.fbbt'): new_bounds = fbbt(m) self.assertEqual(pyo.value(m.x.lb), 1) self.assertEqual(pyo.value(m.x.ub), 1) self.assertEqual(pyo.value(m.y.lb), None) self.assertEqual(pyo.value(m.y.ub), None) self.assertIn("Unsupported expression type for FBBT", OUT.getvalue())
def test_skip_unknown_expression2(self): def dummy_unary_expr(x): return 0.5 * x m = pyo.ConcreteModel() m.x = pyo.Var(bounds=(0, 4)) expr = UnaryFunctionExpression((m.x, ), name='dummy_unary_expr', fcn=dummy_unary_expr) m.c = pyo.Constraint(expr=expr == 1) OUT = StringIO() with LoggingIntercept(OUT, 'pyomo.contrib.fbbt.fbbt'): new_bounds = fbbt(m) self.assertEqual(pyo.value(m.x.lb), 0) self.assertEqual(pyo.value(m.x.ub), 4) self.assertIn("Unsupported expression type for FBBT", OUT.getvalue())
def test_skip_unknown_expression2(self): def dummy_unary_expr(x): return 0.5*x m = pe.ConcreteModel() m.x = pe.Var(bounds=(0,4)) expr = UnaryFunctionExpression((m.x,), name='dummy_unary_expr', fcn=dummy_unary_expr) m.c = pe.Constraint(expr=expr == 1) logging_io = io.StringIO() handler = logging.StreamHandler(stream=logging_io) handler.setLevel(logging.WARNING) logger = logging.getLogger('pyomo.contrib.fbbt.fbbt') logger.addHandler(handler) new_bounds = fbbt(m) handler.flush() self.assertEqual(pe.value(m.x.lb), 0) self.assertEqual(pe.value(m.x.ub), 4) a = "Unsupported expression type for FBBT" b = logging_io.getvalue() a = a.strip() b = b.strip() self.assertTrue(b.startswith(a)) logger.removeHandler(handler)
def test_skip_unknown_expression1(self): m = pe.ConcreteModel() m.x = pe.Var(bounds=(1,1)) m.y = pe.Var() expr = DummyExpr([m.x, m.y]) m.c = pe.Constraint(expr=expr == 1) logging_io = io.StringIO() handler = logging.StreamHandler(stream=logging_io) handler.setLevel(logging.WARNING) logger = logging.getLogger('pyomo.contrib.fbbt.fbbt') logger.addHandler(handler) new_bounds = fbbt(m) handler.flush() self.assertEqual(pe.value(m.x.lb), 1) self.assertEqual(pe.value(m.x.ub), 1) self.assertEqual(pe.value(m.y.lb), None) self.assertEqual(pe.value(m.y.ub), None) a = "Unsupported expression type for FBBT" b = logging_io.getvalue() a = a.strip() b = b.strip() self.assertTrue(b.startswith(a)) logger.removeHandler(handler)
def test_skip_unknown_expression1(self): m = pe.ConcreteModel() m.x = pe.Var(bounds=(1, 1)) m.y = pe.Var() expr = DummyExpr([m.x, m.y]) m.c = pe.Constraint(expr=expr == 1) logging_io = io.StringIO() handler = logging.StreamHandler(stream=logging_io) handler.setLevel(logging.WARNING) logger = logging.getLogger('pyomo.contrib.fbbt.fbbt') logger.addHandler(handler) new_bounds = fbbt(m) handler.flush() self.assertEqual(pe.value(m.x.lb), 1) self.assertEqual(pe.value(m.x.ub), 1) self.assertEqual(pe.value(m.y.lb), None) self.assertEqual(pe.value(m.y.ub), None) a = "Unsupported expression type for FBBT" b = logging_io.getvalue() a = a.strip() b = b.strip() self.assertTrue(b.startswith(a)) logger.removeHandler(handler)
def solve(self, model, **kwds): """Solve the model. Warning: this solver is still in beta. Keyword arguments subject to change. Undocumented keyword arguments definitely subject to change. Args: model (Block): a Pyomo model or block to be solved """ config = self.CONFIG(kwds.pop('options', {})) config.set_value(kwds) solve_data = MindtPySolveData() solve_data.results = SolverResults() solve_data.timing = Bunch() solve_data.curr_int_sol = [] solve_data.should_terminate = False solve_data.integer_list = [] check_config(config) # if the objective function is a constant, dual bound constraint is not added. obj = next(model.component_data_objects(ctype=Objective, active=True)) if obj.expr.polynomial_degree() == 0: config.use_dual_bound = False if config.use_fbbt: fbbt(model) # TODO: logging_level is not logging.INFO here config.logger.info( 'Use the fbbt to tighten the bounds of variables') solve_data.original_model = model solve_data.working_model = model.clone() if config.integer_to_binary: TransformationFactory('contrib.integer_to_binary'). \ apply_to(solve_data.working_model) new_logging_level = logging.INFO if config.tee else None with time_code(solve_data.timing, 'total', is_main_timer=True), \ lower_logger_level_to(config.logger, new_logging_level), \ create_utility_block(solve_data.working_model, 'MindtPy_utils', solve_data): config.logger.info('---Starting MindtPy---') MindtPy = solve_data.working_model.MindtPy_utils setup_results_object(solve_data, config) process_objective( solve_data, config, move_linear_objective=(config.init_strategy == 'FP' or config.add_regularization is not None), use_mcpp=config.use_mcpp, updata_var_con_list=config.add_regularization is None) # The epigraph constraint is very "flat" for branching rules, # we want to use to original model for the main mip. if MindtPy.objective_list[0].expr.polynomial_degree() in { 1, 0 } and config.add_regularization is not None: MindtPy.objective_list[0].activate() MindtPy.objective_constr.deactivate() MindtPy.objective.deactivate() # Save model initial values. solve_data.initial_var_values = list( v.value for v in MindtPy.variable_list) # Store the initial model state as the best solution found. If we # find no better solution, then we will restore from this copy. solve_data.best_solution_found = None solve_data.best_solution_found_time = None # Record solver name solve_data.results.solver.name = 'MindtPy' + str(config.strategy) # Validate the model to ensure that MindtPy is able to solve it. if not model_is_valid(solve_data, config): return # Create a model block in which to store the generated feasibility # slack constraints. Do not leave the constraints on by default. feas = MindtPy.feas_opt = Block() feas.deactivate() feas.feas_constraints = ConstraintList( doc='Feasibility Problem Constraints') # Create a model block in which to store the generated linear # constraints. Do not leave the constraints on by default. lin = MindtPy.cuts = Block() lin.deactivate() # no-good cuts exclude particular discrete decisions lin.no_good_cuts = ConstraintList(doc='no-good cuts') # Feasible no-good cuts exclude discrete realizations that have # been explored via an NLP subproblem. Depending on model # characteristics, the user may wish to revisit NLP subproblems # (with a different initialization, for example). Therefore, these # cuts are not enabled by default. # # Note: these cuts will only exclude integer realizations that are # not already in the primary no_good_cuts ConstraintList. lin.feasible_no_good_cuts = ConstraintList( doc='explored no-good cuts') lin.feasible_no_good_cuts.deactivate() # Set up iteration counters solve_data.nlp_iter = 0 solve_data.mip_iter = 0 solve_data.mip_subiter = 0 solve_data.nlp_infeasible_counter = 0 if config.init_strategy == 'FP': solve_data.fp_iter = 1 # set up bounds solve_data.LB = float('-inf') solve_data.UB = float('inf') solve_data.LB_progress = [solve_data.LB] solve_data.UB_progress = [solve_data.UB] if config.single_tree and (config.add_no_good_cuts or config.use_tabu_list): solve_data.stored_bound = {} if config.strategy == 'GOA' and (config.add_no_good_cuts or config.use_tabu_list): solve_data.num_no_good_cuts_added = {} # Set of NLP iterations for which cuts were generated lin.nlp_iters = Set(dimen=1) # Set of MIP iterations for which cuts were generated in ECP lin.mip_iters = Set(dimen=1) if config.feasibility_norm == 'L1' or config.feasibility_norm == 'L2': feas.nl_constraint_set = RangeSet( len(MindtPy.nonlinear_constraint_list), doc='Integer index set over the nonlinear constraints.') # Create slack variables for feasibility problem feas.slack_var = Var(feas.nl_constraint_set, domain=NonNegativeReals, initialize=1) else: feas.slack_var = Var(domain=NonNegativeReals, initialize=1) # Create slack variables for OA cuts if config.add_slack: lin.slack_vars = VarList(bounds=(0, config.max_slack), initialize=0, domain=NonNegativeReals) # Flag indicating whether the solution improved in the past # iteration or not solve_data.solution_improved = False solve_data.bound_improved = False if config.nlp_solver == 'ipopt': if not hasattr(solve_data.working_model, 'ipopt_zL_out'): solve_data.working_model.ipopt_zL_out = Suffix( direction=Suffix.IMPORT) if not hasattr(solve_data.working_model, 'ipopt_zU_out'): solve_data.working_model.ipopt_zU_out = Suffix( direction=Suffix.IMPORT) # Initialize the main problem with time_code(solve_data.timing, 'initialization'): MindtPy_initialize_main(solve_data, config) # Algorithm main loop with time_code(solve_data.timing, 'main loop'): MindtPy_iteration_loop(solve_data, config) if solve_data.best_solution_found is not None: # Update values in original model copy_var_list_values(from_list=solve_data.best_solution_found. MindtPy_utils.variable_list, to_list=MindtPy.variable_list, config=config) copy_var_list_values(MindtPy.variable_list, [ i for i in solve_data.original_model.component_data_objects( Var) if not i.fixed ], config) # exclude fixed variables here. This is consistent with the definition of variable_list in GDPopt.util solve_data.results.problem.lower_bound = solve_data.LB solve_data.results.problem.upper_bound = solve_data.UB solve_data.results.solver.timing = solve_data.timing solve_data.results.solver.user_time = solve_data.timing.total solve_data.results.solver.wallclock_time = solve_data.timing.total solve_data.results.solver.iterations = solve_data.mip_iter solve_data.results.solver.num_infeasible_nlp_subproblem = solve_data.nlp_infeasible_counter solve_data.results.solver.best_solution_found_time = solve_data.best_solution_found_time if config.single_tree: solve_data.results.solver.num_nodes = solve_data.nlp_iter - \ (1 if config.init_strategy == 'rNLP' else 0) return solve_data.results
def test_x_pow_minus_2(self): m = pyo.ConcreteModel() m.x = pyo.Var() m.y = pyo.Var() m.c = pyo.Constraint(expr=m.x**(-2) == m.y) fbbt(m) self.assertEqual(m.x.lb, None) self.assertEqual(m.x.ub, None) self.assertEqual(m.y.lb, 0) self.assertEqual(m.y.ub, None) m.y.setlb(-5) m.y.setub(-1) with self.assertRaises(InfeasibleConstraintException): fbbt(m) m.x.setlb(None) m.x.setub(None) m.y.setub(0) with self.assertRaises(InfeasibleConstraintException): fbbt(m) m.x.setlb(None) m.x.setub(None) m.y.setub(1) m.y.setlb(0.25) fbbt(m) self.assertAlmostEqual(m.x.lb, -2) self.assertAlmostEqual(m.x.ub, 2) m.x.setlb(0) fbbt(m) self.assertAlmostEqual(m.x.lb, 1) self.assertAlmostEqual(m.x.ub, 2) m.x.setlb(None) m.x.setub(0) fbbt(m) self.assertAlmostEqual(m.x.lb, -2) self.assertAlmostEqual(m.x.ub, -1)
def test_x_pow_minus_2(self): m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() m.c = pe.Constraint(expr=m.x**(-2) == m.y) fbbt(m) self.assertEqual(m.x.lb, None) self.assertEqual(m.x.ub, None) self.assertEqual(m.y.lb, 0) self.assertEqual(m.y.ub, None) m.y.setlb(-5) m.y.setub(-1) with self.assertRaises(InfeasibleConstraintException): fbbt(m) m.x.setlb(None) m.x.setub(None) m.y.setub(0) with self.assertRaises(InfeasibleConstraintException): fbbt(m) m.x.setlb(None) m.x.setub(None) m.y.setub(1) m.y.setlb(0.25) fbbt(m) self.assertAlmostEqual(m.x.lb, -2) self.assertAlmostEqual(m.x.ub, 2) m.x.setlb(0) fbbt(m) self.assertAlmostEqual(m.x.lb, 1) self.assertAlmostEqual(m.x.ub, 2) m.x.setlb(None) m.x.setub(0) fbbt(m) self.assertAlmostEqual(m.x.lb, -2) self.assertAlmostEqual(m.x.ub, -1)
def solve(self, model, **kwds): """Solve the model. Warning: this solver is still in beta. Keyword arguments subject to change. Undocumented keyword arguments definitely subject to change. Warning: at this point in time, if you try to use PSC or GBD with anything other than IPOPT as the NLP solver, bad things will happen. This is because the suffixes are not in place to extract dual values from the variable bounds for any other solver. TODO: fix needed with the GBD implementation. Args: model (Block): a Pyomo model or block to be solved """ config = self.CONFIG(kwds.pop('options', {})) config.set_value(kwds) # configuration confirmation if config.single_tree: config.iteration_limit = 1 config.add_slack = False config.add_nogood_cuts = False config.mip_solver = 'cplex_persistent' config.logger.info( "Single tree implementation is activated. The defalt MIP solver is 'cplex_persistent'" ) # if the slacks fix to zero, just don't add them if config.max_slack == 0.0: config.add_slack = False if config.strategy == "GOA": config.add_nogood_cuts = True config.add_slack = True config.use_mcpp = True config.integer_to_binary = True config.use_dual = False config.use_fbbt = True if config.nlp_solver == "baron": config.use_dual = False # if ecp tolerance is not provided use bound tolerance if config.ecp_tolerance is None: config.ecp_tolerance = config.bound_tolerance # if the objective function is a constant, dual bound constraint is not added. obj = next(model.component_data_objects(ctype=Objective, active=True)) if obj.expr.polynomial_degree() == 0: config.use_dual_bound = False solve_data = MindtPySolveData() solve_data.results = SolverResults() solve_data.timing = Container() solve_data.curr_int_sol = [] solve_data.prev_int_sol = [] if config.use_fbbt: fbbt(model) config.logger.info( "Use the fbbt to tighten the bounds of variables") solve_data.original_model = model solve_data.working_model = model.clone() if config.integer_to_binary: TransformationFactory('contrib.integer_to_binary'). \ apply_to(solve_data.working_model) new_logging_level = logging.INFO if config.tee else None with time_code(solve_data.timing, 'total', is_main_timer=True), \ lower_logger_level_to(config.logger, new_logging_level), \ create_utility_block(solve_data.working_model, 'MindtPy_utils', solve_data): config.logger.info("---Starting MindtPy---") MindtPy = solve_data.working_model.MindtPy_utils setup_results_object(solve_data, config) process_objective(solve_data, config, use_mcpp=config.use_mcpp) # Save model initial values. solve_data.initial_var_values = list( v.value for v in MindtPy.variable_list) # Store the initial model state as the best solution found. If we # find no better solution, then we will restore from this copy. solve_data.best_solution_found = None solve_data.best_solution_found_time = None # Record solver name solve_data.results.solver.name = 'MindtPy' + str(config.strategy) # Validate the model to ensure that MindtPy is able to solve it. if not model_is_valid(solve_data, config): return # Create a model block in which to store the generated feasibility # slack constraints. Do not leave the constraints on by default. feas = MindtPy.MindtPy_feas = Block() feas.deactivate() feas.feas_constraints = ConstraintList( doc='Feasibility Problem Constraints') # Create a model block in which to store the generated linear # constraints. Do not leave the constraints on by default. lin = MindtPy.MindtPy_linear_cuts = Block() lin.deactivate() # Integer cuts exclude particular discrete decisions lin.integer_cuts = ConstraintList(doc='integer cuts') # Feasible integer cuts exclude discrete realizations that have # been explored via an NLP subproblem. Depending on model # characteristics, the user may wish to revisit NLP subproblems # (with a different initialization, for example). Therefore, these # cuts are not enabled by default. # # Note: these cuts will only exclude integer realizations that are # not already in the primary integer_cuts ConstraintList. lin.feasible_integer_cuts = ConstraintList( doc='explored integer cuts') lin.feasible_integer_cuts.deactivate() # Set up iteration counters solve_data.nlp_iter = 0 solve_data.mip_iter = 0 solve_data.mip_subiter = 0 # set up bounds solve_data.LB = float('-inf') solve_data.UB = float('inf') solve_data.LB_progress = [solve_data.LB] solve_data.UB_progress = [solve_data.UB] if config.single_tree and config.add_nogood_cuts: solve_data.stored_bound = {} if config.strategy == 'GOA' and config.add_nogood_cuts: solve_data.num_no_good_cuts_added = {} # Set of NLP iterations for which cuts were generated lin.nlp_iters = Set(dimen=1) # Set of MIP iterations for which cuts were generated in ECP lin.mip_iters = Set(dimen=1) if config.feasibility_norm == 'L1' or config.feasibility_norm == 'L2': feas.nl_constraint_set = Set( initialize=[ i for i, constr in enumerate(MindtPy.constraint_list, 1) if constr.body.polynomial_degree() not in (1, 0) ], doc="Integer index set over the nonlinear constraints." "The set corresponds to the index of nonlinear constraint in constraint_set" ) # Create slack variables for feasibility problem feas.slack_var = Var(feas.nl_constraint_set, domain=NonNegativeReals, initialize=1) else: feas.slack_var = Var(domain=NonNegativeReals, initialize=1) # Create slack variables for OA cuts if config.add_slack: lin.slack_vars = VarList(bounds=(0, config.max_slack), initialize=0, domain=NonNegativeReals) # Flag indicating whether the solution improved in the past # iteration or not solve_data.solution_improved = False if config.nlp_solver == 'ipopt': if not hasattr(solve_data.working_model, 'ipopt_zL_out'): solve_data.working_model.ipopt_zL_out = Suffix( direction=Suffix.IMPORT) if not hasattr(solve_data.working_model, 'ipopt_zU_out'): solve_data.working_model.ipopt_zU_out = Suffix( direction=Suffix.IMPORT) # Initialize the master problem with time_code(solve_data.timing, 'initialization'): MindtPy_initialize_master(solve_data, config) # Algorithm main loop with time_code(solve_data.timing, 'main loop'): MindtPy_iteration_loop(solve_data, config) if solve_data.best_solution_found is not None: # Update values in original model copy_var_list_values(from_list=solve_data.best_solution_found. MindtPy_utils.variable_list, to_list=MindtPy.variable_list, config=config) # MindtPy.objective_value.set_value( # value(solve_data.working_objective_expr, exception=False)) copy_var_list_values( MindtPy.variable_list, solve_data.original_model.component_data_objects(Var), config) solve_data.results.problem.lower_bound = solve_data.LB solve_data.results.problem.upper_bound = solve_data.UB solve_data.results.solver.timing = solve_data.timing solve_data.results.solver.user_time = solve_data.timing.total solve_data.results.solver.wallclock_time = solve_data.timing.total solve_data.results.solver.iterations = solve_data.mip_iter solve_data.results.solver.best_solution_found_time = solve_data.best_solution_found_time if config.single_tree: solve_data.results.solver.num_nodes = solve_data.nlp_iter - \ (1 if config.init_strategy == 'rNLP' else 0) return solve_data.results
def relax(model, descend_into=None, in_place=False, use_fbbt=True): if not in_place: m = model.clone() else: m = model if descend_into is None: descend_into = (pe.Block, Disjunct) aux_var_map = dict() counter_dict = dict() degree_map = ComponentMap() for c in m.component_data_objects(ctype=Constraint, active=True, descend_into=descend_into, sort=True): body_degree = polynomial_degree(c.body) if body_degree is not None: if body_degree <= 1: continue if c.lower is not None and c.upper is not None: relaxation_side = RelaxationSide.BOTH elif c.lower is not None: relaxation_side = RelaxationSide.OVER elif c.upper is not None: relaxation_side = RelaxationSide.UNDER else: raise ValueError('Encountered a constraint without a lower or an upper bound: ' + str(c)) parent_block = c.parent_block() relaxation_side_map = ComponentMap() relaxation_side_map[c.body] = relaxation_side if parent_block in counter_dict: counter = counter_dict[parent_block] else: parent_block.relaxations = pe.Block() parent_block.aux_vars = pe.VarList() parent_block.aux_cons = pe.ConstraintList() counter = RelaxationCounter() counter_dict[parent_block] = counter new_body = _relax_expr(expr=c.body, aux_var_map=aux_var_map, parent_block=parent_block, relaxation_side_map=relaxation_side_map, counter=counter, degree_map=degree_map) lb = c.lower ub = c.upper parent_block.aux_cons.add(pe.inequality(lb, new_body, ub)) parent_component = c.parent_component() if parent_component.is_indexed(): del parent_component[c.index()] else: parent_block.del_component(c) for c in m.component_data_objects(ctype=pe.Objective, active=True, descend_into=descend_into, sort=True): degree = polynomial_degree(c.expr) if degree is not None: if degree <= 1: continue if c.sense == pe.minimize: relaxation_side = RelaxationSide.UNDER elif c.sense == pe.maximize: relaxation_side = RelaxationSide.OVER else: raise ValueError('Encountered an objective with an unrecognized sense: ' + str(c)) parent_block = c.parent_block() relaxation_side_map = ComponentMap() relaxation_side_map[c.expr] = relaxation_side if parent_block in counter_dict: counter = counter_dict[parent_block] else: parent_block.relaxations = pe.Block() parent_block.aux_vars = pe.VarList() parent_block.aux_cons = pe.ConstraintList() counter = RelaxationCounter() counter_dict[parent_block] = counter if not hasattr(parent_block, 'aux_objectives'): parent_block.aux_objectives = pe.ObjectiveList() new_body = _relax_expr(expr=c.expr, aux_var_map=aux_var_map, parent_block=parent_block, relaxation_side_map=relaxation_side_map, counter=counter, degree_map=degree_map) sense = c.sense parent_block.aux_objectives.add(new_body, sense=sense) parent_component = c.parent_component() if parent_component.is_indexed(): del parent_component[c.index()] else: parent_block.del_component(c) if use_fbbt: for _aux_var, relaxation in aux_var_map.values(): relaxation.rebuild(build_nonlinear_constraint=True) fbbt(m, deactivate_satisfied_constraints=True) for _aux_var, relaxation in aux_var_map.values(): relaxation.use_linear_relaxation = True relaxation.rebuild() else: for _aux_var, relaxation in aux_var_map.values(): relaxation.use_linear_relaxation = True relaxation.rebuild() return m
def solve_linear_GDP(linear_GDP_model, solve_data, config): """Solves the linear GDP model and attempts to resolve solution issues.""" m = linear_GDP_model GDPopt = m.GDPopt_utils # Transform disjunctions _bigm = TransformationFactory('gdp.bigm') _bigm.handlers[Port] = False _bigm.apply_to(m) preprocessing_transformations = [ # # Propagate variable bounds # 'contrib.propagate_eq_var_bounds', # # Detect fixed variables # 'contrib.detect_fixed_vars', # # Propagate fixed variables # 'contrib.propagate_fixed_vars', # # Remove zero terms in linear expressions # 'contrib.remove_zero_terms', # # Remove terms in equal to zero summations # 'contrib.propagate_zero_sum', # # Transform bound constraints # 'contrib.constraints_to_var_bounds', # # Detect fixed variables # 'contrib.detect_fixed_vars', # # Remove terms in equal to zero summations # 'contrib.propagate_zero_sum', # Remove trivial constraints 'contrib.deactivate_trivial_constraints', ] if config.mip_presolve: try: fbbt(m, integer_tol=config.integer_tolerance) for xfrm in preprocessing_transformations: TransformationFactory(xfrm).apply_to(m) except InfeasibleConstraintException: config.logger.debug("MIP preprocessing detected infeasibility.") mip_result = MasterProblemResult() mip_result.feasible = False mip_result.var_values = list(v.value for v in GDPopt.variable_list) mip_result.pyomo_results = SolverResults() mip_result.pyomo_results.solver.termination_condition = tc.error mip_result.disjunct_values = list(disj.indicator_var.value for disj in GDPopt.disjunct_list) return mip_result # Deactivate extraneous IMPORT/EXPORT suffixes getattr(m, 'ipopt_zL_out', _DoNothing()).deactivate() getattr(m, 'ipopt_zU_out', _DoNothing()).deactivate() # Create solver, check availability if not SolverFactory(config.mip_solver).available(): raise RuntimeError("MIP solver %s is not available." % config.mip_solver) # Callback immediately before solving MIP master problem config.call_before_master_solve(m, solve_data) try: with SuppressInfeasibleWarning(): mip_args = dict(config.mip_solver_args) elapsed = get_main_elapsed_time(solve_data.timing) remaining = max(config.time_limit - elapsed, 1) if config.mip_solver == 'gams': mip_args['add_options'] = mip_args.get('add_options', []) mip_args['add_options'].append('option reslim=%s;' % remaining) elif config.mip_solver == 'multisolve': mip_args['time_limit'] = min( mip_args.get('time_limit', float('inf')), remaining) results = SolverFactory(config.mip_solver).solve(m, **mip_args) except RuntimeError as e: if 'GAMS encountered an error during solve.' in str(e): config.logger.warning( "GAMS encountered an error in solve. Treating as infeasible.") mip_result = MasterProblemResult() mip_result.feasible = False mip_result.var_values = list(v.value for v in GDPopt.variable_list) mip_result.pyomo_results = SolverResults() mip_result.pyomo_results.solver.termination_condition = tc.error mip_result.disjunct_values = list(disj.indicator_var.value for disj in GDPopt.disjunct_list) return mip_result else: raise terminate_cond = results.solver.termination_condition if terminate_cond is tc.infeasibleOrUnbounded: # Linear solvers will sometimes tell me that it's infeasible or # unbounded during presolve, but fails to distinguish. We need to # resolve with a solver option flag on. results, terminate_cond = distinguish_mip_infeasible_or_unbounded( m, config) if terminate_cond is tc.unbounded: # Solution is unbounded. Add an arbitrary bound to the objective and resolve. # This occurs when the objective is nonlinear. The nonlinear objective is moved # to the constraints, and deactivated for the linear master problem. obj_bound = 1E15 config.logger.warning( 'Linear GDP was unbounded. ' 'Resolving with arbitrary bound values of (-{0:.10g}, {0:.10g}) on the objective. ' 'Check your initialization routine.'.format(obj_bound)) main_objective = next(m.component_data_objects(Objective, active=True)) GDPopt.objective_bound = Constraint(expr=(-obj_bound, main_objective.expr, obj_bound)) with SuppressInfeasibleWarning(): results = SolverFactory(config.mip_solver).solve( m, **config.mip_solver_args) terminate_cond = results.solver.termination_condition # Build and return results object mip_result = MasterProblemResult() mip_result.feasible = True mip_result.var_values = list(v.value for v in GDPopt.variable_list) mip_result.pyomo_results = results mip_result.disjunct_values = list(disj.indicator_var.value for disj in GDPopt.disjunct_list) if terminate_cond in {tc.optimal, tc.locallyOptimal, tc.feasible}: pass elif terminate_cond is tc.infeasible: config.logger.info( 'Linear GDP is now infeasible. ' 'GDPopt has finished exploring feasible discrete configurations.') mip_result.feasible = False elif terminate_cond is tc.maxTimeLimit: # TODO check that status is actually ok and everything is feasible config.logger.info( 'Unable to optimize linear GDP problem within time limit. ' 'Using current solver feasible solution.') elif (terminate_cond is tc.other and results.solution.status is SolutionStatus.feasible): # load the solution and suppress the warning message by setting # solver status to ok. config.logger.info('Linear GDP solver reported feasible solution, ' 'but not guaranteed to be optimal.') else: raise ValueError('GDPopt unable to handle linear GDP ' 'termination condition ' 'of %s. Solver message: %s' % (terminate_cond, results.solver.message)) return mip_result
def relax(model, descend_into=None, in_place=False, use_fbbt=True, fbbt_options=None): """ Create a convex relaxation of the model. Parameters ---------- model: pyomo.core.base.block._BlockData or pyomo.core.base.PyomoModel.ConcreteModel The model or block to be relaxed descend_into: type or tuple of type, optional The types of pyomo components that should be checked for constraints to be relaxed. The default is (Block, Disjunct). in_place: bool, optional If False (default=False), model will be cloned, and the clone will be relaxed. If True, then model will be modified in place. use_fbbt: bool, optional If True (default=True), then FBBT will be used to tighten variable bounds. If False, FBBT will not be used. fbbt_options: dict, optional The options to pass to the call to fbbt. See pyomo.contrib.fbbt.fbbt.fbbt for details. Returns ------- m: pyomo.core.base.block._BlockData or pyomo.core.base.PyomoModel.ConcreteModel The relaxed model """ """ For now, we will use FBBT both before relaxing the model and after relaxing the model. The reason we need to do it before relaxing the model is that the variable bounds will affect the structure of the relaxation. For example, if we need to relax x**3 and x >= 0, then we know x**3 is convex, and we can relax it as a convex, univariate function. However, if x can be positive or negative, then x**3 is neither convex nor concave. In this case, we relax it by reformulating it as x * x**2. The hope is that performing FBBT before relaxing the model will help identify things like x >= 0 and therefore x**3 is convex. The correct way to do this is to update the relaxation classes so that the original expression is known, and the best relaxation can be used anytime the variable bounds are updated. For example, suppose the model is relaxed and, only after OBBT is performed, we find out x >= 0. We should be able to easily update the relaxation so that x**3 is then relaxed as a convex univariate function. The reason FBBT needs to be performed after relaxing the model is that we want to make sure that all of the auxilliary variables introduced get tightened bounds. The correct way to handle this is to perform FBBT with the original model with suspect, which forms a DAG. Each auxilliary variable introduced in the relaxed model corresponds to a node in the DAG. If we use suspect, then we can easily update the bounds of the auxilliary variables without performing FBBT a second time. """ if not in_place: m = model.clone() else: m = model if fbbt_options is None: fbbt_options = dict() if use_fbbt: fbbt(m, **fbbt_options) if descend_into is None: descend_into = (pe.Block, Disjunct) aux_var_map = dict() counter_dict = dict() degree_map = ComponentMap() for c in nonrelaxation_component_data_objects(m, ctype=Constraint, active=True, descend_into=descend_into, sort=True): body_degree = polynomial_degree(c.body) if body_degree is not None: if body_degree <= 1: continue if c.lower is not None and c.upper is not None: relaxation_side = RelaxationSide.BOTH elif c.lower is not None: relaxation_side = RelaxationSide.OVER elif c.upper is not None: relaxation_side = RelaxationSide.UNDER else: raise ValueError('Encountered a constraint without a lower or an upper bound: ' + str(c)) parent_block = c.parent_block() relaxation_side_map = ComponentMap() relaxation_side_map[c.body] = relaxation_side if parent_block in counter_dict: counter = counter_dict[parent_block] else: parent_block.relaxations = pe.Block() parent_block.aux_vars = pe.VarList() parent_block.aux_cons = pe.ConstraintList() counter = RelaxationCounter() counter_dict[parent_block] = counter new_body = _relax_expr(expr=c.body, aux_var_map=aux_var_map, parent_block=parent_block, relaxation_side_map=relaxation_side_map, counter=counter, degree_map=degree_map) lb = c.lower ub = c.upper parent_block.aux_cons.add(pe.inequality(lb, new_body, ub)) parent_component = c.parent_component() if parent_component.is_indexed(): del parent_component[c.index()] else: parent_block.del_component(c) for c in nonrelaxation_component_data_objects(m, ctype=pe.Objective, active=True, descend_into=descend_into, sort=True): degree = polynomial_degree(c.expr) if degree is not None: if degree <= 1: continue if c.sense == pe.minimize: relaxation_side = RelaxationSide.UNDER elif c.sense == pe.maximize: relaxation_side = RelaxationSide.OVER else: raise ValueError('Encountered an objective with an unrecognized sense: ' + str(c)) parent_block = c.parent_block() relaxation_side_map = ComponentMap() relaxation_side_map[c.expr] = relaxation_side if parent_block in counter_dict: counter = counter_dict[parent_block] else: parent_block.relaxations = pe.Block() parent_block.aux_vars = pe.VarList() parent_block.aux_cons = pe.ConstraintList() counter = RelaxationCounter() counter_dict[parent_block] = counter if not hasattr(parent_block, 'aux_objectives'): parent_block.aux_objectives = pe.ObjectiveList() new_body = _relax_expr(expr=c.expr, aux_var_map=aux_var_map, parent_block=parent_block, relaxation_side_map=relaxation_side_map, counter=counter, degree_map=degree_map) sense = c.sense parent_block.aux_objectives.add(new_body, sense=sense) parent_component = c.parent_component() if parent_component.is_indexed(): del parent_component[c.index()] else: parent_block.del_component(c) if use_fbbt: for _aux_var, relaxation in aux_var_map.values(): relaxation.rebuild(build_nonlinear_constraint=True) tmp_fbbt_options = dict(fbbt_options) tmp_fbbt_options['deactivate_satisfied_constraints'] = False fbbt(m, **tmp_fbbt_options) for _aux_var, relaxation in aux_var_map.values(): relaxation.use_linear_relaxation = True relaxation.rebuild() else: for _aux_var, relaxation in aux_var_map.values(): relaxation.use_linear_relaxation = True relaxation.rebuild() return m
def plot_feasible_region_2d(relaxation, show_plot=True, num_pts=30, tol=1e-4): if not isinstance(relaxation, (PWXSquaredRelaxation, PWUnivariateRelaxation, PWCosRelaxation, PWSinRelaxation, PWArctanRelaxation)): raise NotImplementedError('Plotting is not supported for {0}.'.format( type(relaxation))) x = relaxation.get_rhs_vars()[0] w = relaxation.get_aux_var() rel_name = relaxation.local_name x_name = x.local_name w_name = w.local_name rel_parent = relaxation.parent_block() x_parent = x.parent_block() w_parent = w.parent_block() rel_parent.del_component(relaxation) x_parent.del_component(x) w_parent.del_component(w) orig_xlb = x.lb orig_xub = x.ub orig_wlb = w.lb orig_wub = w.ub fbbt(relaxation) xlb = pe.value(x.lb) xub = pe.value(x.ub) wlb = pe.value(w.lb) wub = pe.value(w.ub) x_list = np.linspace(xlb, xub, num_pts) w_list = np.linspace(wlb, wub, num_pts) w_true = list() rhs_expr = relaxation.get_rhs_expr() for _x in x_list: x.value = float(_x) w_true.append(pe.value(rhs_expr)) plt.plot(x_list, w_true) x_list = [float(i) for i in x_list] w_list = [float(i) for i in w_list] m = pe.ConcreteModel() m.x = x m.w = w m.b = relaxation opt = pe.SolverFactory('gurobi_persistent') opt.options['BarQCPConvTol'] = 1e-8 opt.options['FeasibilityTol'] = 1e-8 opt.set_instance(m) feasible_x = list() feasible_w = list() for _xval in x_list: for _wval in w_list: x.fix(_xval) if hasattr(m, 'obj'): del m.obj m.obj = pe.Objective(expr=(w - _wval)**2) opt.update_var(x) opt.set_objective(m.obj) res = opt.solve(load_solutions=False, save_results=False) if res.solver.termination_condition != pe.TerminationCondition.optimal: raise RuntimeError( 'Could not produce plot because a feasiblity test failed.') opt.load_vars([w]) if abs(pe.value(w.value) - _wval) <= tol: feasible_x.append(_xval) feasible_w.append(_wval) plt.scatter(feasible_x, feasible_w, 5.0) plt.xlabel('x') plt.ylabel('y') if show_plot: plt.show() m.del_component(relaxation) m.del_component(x) m.del_component(w) del m rel_parent.add_component(rel_name, relaxation) x_parent.add_component(x_name, x) w_parent.add_component(w_name, w) x.unfix() x.setlb(orig_xlb) x.setub(orig_xub) w.setlb(orig_wlb) w.setub(orig_wub)
def enumerate_solutions(): import time feed_choices = ['cheap', 'expensive'] feed_compressor_choices = ['single_stage', 'two_stage'] reactor_choices = ['cheap', 'expensive'] recycle_compressor_choices = ['single_stage', 'two_stage'] print('{0:<20}{1:<20}{2:<20}{3:<20}{4:<20}{5:<20}'.format( 'feed choice', 'feed compressor', 'reactor choice', 'recycle compressor', 'termination cond', 'profit')) since = time.time() for feed_choice in feed_choices: for feed_compressor_choice in feed_compressor_choices: for reactor_choice in reactor_choices: for recycle_compressor_choice in recycle_compressor_choices: m = MethanolModel() m = m.model for _d in m.component_data_objects(gdp.Disjunct, descend_into=True, active=True, sort=True): _d.BigM = pe.Suffix() for _c in _d.component_data_objects(pe.Constraint, descend_into=True, active=True, sort=True): lb, ub = compute_bounds_on_expr(_c.body) _d.BigM[_c] = max(abs(lb), abs(ub)) if feed_choice == 'cheap': m.cheap_feed_disjunct.indicator_var.fix(1) m.expensive_feed_disjunct.indicator_var.fix(0) else: m.cheap_feed_disjunct.indicator_var.fix(0) m.expensive_feed_disjunct.indicator_var.fix(1) if feed_compressor_choice == 'single_stage': m.single_stage_feed_compressor_disjunct.indicator_var.fix( 1) m.two_stage_feed_compressor_disjunct.indicator_var.fix( 0) else: m.single_stage_feed_compressor_disjunct.indicator_var.fix( 0) m.two_stage_feed_compressor_disjunct.indicator_var.fix( 1) if reactor_choice == 'cheap': m.cheap_reactor.indicator_var.fix(1) m.expensive_reactor.indicator_var.fix(0) else: m.cheap_reactor.indicator_var.fix(0) m.expensive_reactor.indicator_var.fix(1) if recycle_compressor_choice == 'single_stage': m.single_stage_recycle_compressor_disjunct.indicator_var.fix( 1) m.two_stage_recycle_compressor_disjunct.indicator_var.fix( 0) else: m.single_stage_recycle_compressor_disjunct.indicator_var.fix( 0) m.two_stage_recycle_compressor_disjunct.indicator_var.fix( 1) pe.TransformationFactory('gdp.fix_disjuncts').apply_to(m) fbbt(m, deactivate_satisfied_constraints=True) fix_vars_with_equal_bounds(m) for v in m.component_data_objects(pe.Var, descend_into=True): if not v.is_fixed(): if v.has_lb() and v.has_ub(): v.value = 0.5 * (v.lb + v.ub) else: v.value = 1.0 opt = pe.SolverFactory('ipopt') res = opt.solve(m, tee=False) print('{0:<20}{1:<20}{2:<20}{3:<20}{4:<20}{5:<20}'.format( feed_choice, feed_compressor_choice, reactor_choice, recycle_compressor_choice, str(res.solver.termination_condition), str(-pe.value(m.objective)))) time_elapsed = time.time() - since print('The code run {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60)) return m
def set_up_solve_data(model, config): """Set up the solve data. Parameters ---------- model : Pyomo model The original model to be solved in MindtPy. config : ConfigBlock The specific configurations for MindtPy. Returns ------- solve_data : MindtPySolveData Data container that holds solve-instance data. """ solve_data = MindtPySolveData() solve_data.results = SolverResults() solve_data.timing = Bunch() solve_data.curr_int_sol = [] solve_data.should_terminate = False solve_data.integer_list = [] # if the objective function is a constant, dual bound constraint is not added. obj = next(model.component_data_objects(ctype=Objective, active=True)) if obj.expr.polynomial_degree() == 0: config.use_dual_bound = False if config.use_fbbt: fbbt(model) # TODO: logging_level is not logging.INFO here config.logger.info('Use the fbbt to tighten the bounds of variables') solve_data.original_model = model solve_data.working_model = model.clone() # Set up iteration counters solve_data.nlp_iter = 0 solve_data.mip_iter = 0 solve_data.mip_subiter = 0 solve_data.nlp_infeasible_counter = 0 if config.init_strategy == 'FP': solve_data.fp_iter = 1 # set up bounds if obj.sense == minimize: solve_data.primal_bound = float('inf') solve_data.dual_bound = float('-inf') else: solve_data.primal_bound = float('-inf') solve_data.dual_bound = float('inf') solve_data.primal_bound_progress = [solve_data.primal_bound] solve_data.dual_bound_progress = [solve_data.dual_bound] solve_data.primal_bound_progress_time = [0] solve_data.dual_bound_progress_time = [0] solve_data.abs_gap = float('inf') solve_data.rel_gap = float('inf') solve_data.log_formatter = ' {:>9} {:>15} {:>15g} {:>12g} {:>12g} {:>7.2%} {:>7.2f}' solve_data.fixed_nlp_log_formatter = '{:1}{:>9} {:>15} {:>15g} {:>12g} {:>12g} {:>7.2%} {:>7.2f}' solve_data.log_note_formatter = ' {:>9} {:>15} {:>15}' if config.add_regularization is not None: if config.add_regularization in { 'level_L1', 'level_L_infinity', 'grad_lag' }: solve_data.regularization_mip_type = 'MILP' elif config.add_regularization in { 'level_L2', 'hess_lag', 'hess_only_lag', 'sqp_lag' }: solve_data.regularization_mip_type = 'MIQP' if config.single_tree and (config.add_no_good_cuts or config.use_tabu_list): solve_data.stored_bound = {} if config.strategy == 'GOA' and (config.add_no_good_cuts or config.use_tabu_list): solve_data.num_no_good_cuts_added = {} # Flag indicating whether the solution improved in the past # iteration or not solve_data.primal_bound_improved = False solve_data.dual_bound_improved = False if config.nlp_solver == 'ipopt': if not hasattr(solve_data.working_model, 'ipopt_zL_out'): solve_data.working_model.ipopt_zL_out = Suffix( direction=Suffix.IMPORT) if not hasattr(solve_data.working_model, 'ipopt_zU_out'): solve_data.working_model.ipopt_zU_out = Suffix( direction=Suffix.IMPORT) if config.quadratic_strategy == 0: solve_data.mip_objective_polynomial_degree = {0, 1} solve_data.mip_constraint_polynomial_degree = {0, 1} elif config.quadratic_strategy == 1: solve_data.mip_objective_polynomial_degree = {0, 1, 2} solve_data.mip_constraint_polynomial_degree = {0, 1} elif config.quadratic_strategy == 2: solve_data.mip_objective_polynomial_degree = {0, 1, 2} solve_data.mip_constraint_polynomial_degree = {0, 1, 2} return solve_data
def _solve_rnGDP_subproblem(model, solve_data): config = solve_data.config subproblem = TransformationFactory('gdp.bigm').create_using(model) obj_sense_correction = solve_data.objective_sense != minimize try: with SuppressInfeasibleWarning(): try: fbbt(subproblem, integer_tol=config.integer_tolerance) except InfeasibleConstraintException: copy_var_list_values( # copy variable values, even if errored from_list=subproblem.GDPopt_utils.variable_list, to_list=model.GDPopt_utils.variable_list, config=config, ignore_integrality=True) return float('inf'), float('inf') minlp_args = dict(config.minlp_solver_args) if config.minlp_solver == 'gams': elapsed = get_main_elapsed_time(solve_data.timing) remaining = max(config.time_limit - elapsed, 1) minlp_args['add_options'] = minlp_args.get('add_options', []) minlp_args['add_options'].append('option reslim=%s;' % remaining) result = SolverFactory(config.minlp_solver).solve( subproblem, **minlp_args) except RuntimeError as e: config.logger.warning( "Solver encountered RuntimeError. Treating as infeasible. " "Msg: %s\n%s" % (str(e), traceback.format_exc())) copy_var_list_values( # copy variable values, even if errored from_list=subproblem.GDPopt_utils.variable_list, to_list=model.GDPopt_utils.variable_list, config=config, ignore_integrality=True) return float('inf'), float('inf') term_cond = result.solver.termination_condition if term_cond == tc.optimal: assert result.solver.status is SolverStatus.ok lb = result.problem.lower_bound if not obj_sense_correction else -result.problem.upper_bound ub = result.problem.upper_bound if not obj_sense_correction else -result.problem.lower_bound copy_var_list_values( from_list=subproblem.GDPopt_utils.variable_list, to_list=model.GDPopt_utils.variable_list, config=config, ) return lb, ub elif term_cond == tc.locallyOptimal or term_cond == tc.feasible: assert result.solver.status is SolverStatus.ok lb = result.problem.lower_bound if not obj_sense_correction else -result.problem.upper_bound ub = result.problem.upper_bound if not obj_sense_correction else -result.problem.lower_bound # TODO handle LB absent copy_var_list_values( from_list=subproblem.GDPopt_utils.variable_list, to_list=model.GDPopt_utils.variable_list, config=config, ) return lb, ub elif term_cond == tc.unbounded: copy_var_list_values(from_list=subproblem.GDPopt_utils.variable_list, to_list=model.GDPopt_utils.variable_list, config=config, ignore_integrality=True) return float('-inf'), float('-inf') elif term_cond == tc.infeasible: copy_var_list_values(from_list=subproblem.GDPopt_utils.variable_list, to_list=model.GDPopt_utils.variable_list, config=config, ignore_integrality=True) return float('inf'), float('inf') else: config.logger.warning( "Unknown termination condition of %s. Treating as infeasible." % term_cond) copy_var_list_values(from_list=subproblem.GDPopt_utils.variable_list, to_list=model.GDPopt_utils.variable_list, config=config, ignore_integrality=True) return float('inf'), float('inf')
def create_relaxation_of_polar_acopf(model_data, include_soc=True, use_linear_relaxation=False): if not coramin_available: raise ImportError( 'Cannot create polar relaxation unless coramin is available.') model, md = _create_base_relaxation(model_data) branches = dict(md.elements(element_type='branch')) bus_attrs = md.attributes(element_type='bus') branch_attrs = md.attributes(element_type='branch') bus_pairs = zip_items(branch_attrs['from_bus'], branch_attrs['to_bus']) unique_bus_pairs = list( OrderedDict((val, None) for idx, val in bus_pairs.items()).keys()) # declare the polar voltages libbus.declare_var_vm(model, bus_attrs['names'], initialize=bus_attrs['vm'], bounds=zip_items(bus_attrs['v_min'], bus_attrs['v_max'])) va_bounds = {k: (-pi, pi) for k in bus_attrs['va']} libbus.declare_var_va(model, bus_attrs['names'], initialize=bus_attrs['va'], bounds=va_bounds) # fix the reference bus ref_bus = md.data['system']['reference_bus'] ref_angle = md.data['system']['reference_bus_angle'] model.va[ref_bus].fix(radians(ref_angle)) # relate c, s, and vmsq to vm and va libbus.declare_eq_vmsq(model=model, index_set=bus_attrs['names'], coordinate_type=CoordinateType.POLAR) libbranch.declare_eq_c(model=model, index_set=unique_bus_pairs, coordinate_type=CoordinateType.POLAR) libbranch.declare_eq_s(model=model, index_set=unique_bus_pairs, coordinate_type=CoordinateType.POLAR) # declare angle difference limits on interconnected buses libbranch.declare_ineq_angle_diff_branch_lbub( model=model, index_set=branch_attrs['names'], branches=branches, coordinate_type=CoordinateType.POLAR) fbbt(model, deactivate_satisfied_constraints=False) model = coramin.relaxations.relax(model, in_place=True, use_fbbt=False) if not use_linear_relaxation: for b in model.component_data_objects(pe.Block, descend_into=True, active=True, sort=True): if isinstance(b, (coramin.relaxations.BaseRelaxation, coramin.relaxations.BaseRelaxationData)): if polynomial_degree(b.get_rhs_expr()) == 2: if not isinstance( b, (coramin.relaxations.PWMcCormickRelaxation, coramin.relaxations.PWMcCormickRelaxationData)): b.use_linear_relaxation = False b.rebuild() if include_soc: libbranch.declare_ineq_soc( model=model, index_set=unique_bus_pairs, use_outer_approximation=use_linear_relaxation) return model, md
def test_x_sq(self): m = pe.ConcreteModel() m.x = pe.Var() m.y = pe.Var() m.c = pe.Constraint(expr=m.x**2 == m.y) fbbt(m) self.assertEqual(m.x.lb, None) self.assertEqual(m.x.ub, None) self.assertEqual(m.y.lb, 0) self.assertEqual(m.y.ub, None) m.x.setlb(None) m.x.setub(None) m.y.setlb(1) m.y.setub(4) fbbt(m) self.assertAlmostEqual(m.x.lb, -2) self.assertAlmostEqual(m.x.ub, 2) m.x.setlb(0) fbbt(m) self.assertAlmostEqual(m.x.lb, 1) self.assertAlmostEqual(m.x.ub, 2) m.x.setlb(-0.5) fbbt(m) self.assertAlmostEqual(m.x.lb, 1) self.assertAlmostEqual(m.x.ub, 2) m.x.setlb(-1) fbbt(m) self.assertAlmostEqual(m.x.lb, -1) self.assertAlmostEqual(m.x.ub, 2) m.x.setlb(None) m.x.setub(0) fbbt(m) self.assertAlmostEqual(m.x.lb, -2) self.assertAlmostEqual(m.x.ub, -1) m.x.setlb(None) m.x.setub(None) m.y.setlb(-5) m.y.setub(-1) with self.assertRaises(ValueError): fbbt(m) m.y.setub(0) fbbt(m) self.assertEqual(m.x.lb, 0) self.assertEqual(m.x.ub, 0)