def test_bound_dual(self): bb = BranchAndBound(infeasible2) bb.root_node.lp += np.matrix( [[0, -1, -1]]) * bb.root_node.lp.getVarByName('x') >= CyLPArray( [-2.5]) bb.solve() terminal_nodes = bb.tree.get_leaves(0) infeasible_nodes = [ n for n in terminal_nodes if n.lp_feasible is False ] n = infeasible_nodes[0] lp = bb._bound_dual(n.lp) # test that we get a CyClpSimplex object back self.assertTrue(isinstance(lp, CyClpSimplex), 'should return CyClpSimplex instance') # same variables plus extra 's' self.assertTrue( {v.name for v in lp.variables} == {'x', 's_0', 's_1'}, 'x should already exist and s_1 and s_2 should be added') old_x = n.lp.getVarByName('x') new_x, s_0, s_1 = lp.getVarByName('x'), lp.getVarByName( 's_0'), lp.getVarByName('s_1') # same variable bounds, plus s >= 0 self.assertTrue( all(new_x.lower == old_x.lower) and all(new_x.upper == old_x.upper), 'x should have the same bounds') self.assertTrue( all(s_0.lower == [0, 0]) and all(s_0.upper > [1e300, 1e300]), 's_0 >= 0') self.assertTrue( all(s_1.lower == [0]) and all(s_1.upper > 1e300), 's_1 >= 0') # same constraints, plus slack s self.assertTrue(lp.nConstraints == 3, 'should have same number of constraints') self.assertTrue( (lp.constraints[0].varCoefs[new_x] == np.array([[-1, -1, 0], [0, 0, -1]])).all(), 'x coefs should stay same') self.assertTrue((lp.constraints[0].varCoefs[s_0] == np.matrix( np.identity(2))).all(), 's_0 should have coef of 2-D identity') self.assertTrue( all(lp.constraints[1].varCoefs[new_x] == np.array([0, -1, -1])), 'x coefs should stay same') self.assertTrue( lp.constraints[1].varCoefs[s_1] == np.matrix(np.identity(1)), 's_0 should have coef of 1-D identity') self.assertTrue( all(lp.constraints[0].lower == np.array([1, -1])) and all(lp.constraints[0].upper >= np.array([1e300])), 'constraint bounds should remain same') self.assertTrue( lp.constraints[1].lower == np.array([-2.5]) and lp.constraints[1].upper >= np.array([1e300]), 'constraint bounds should remain same') # same objective, plus large s coefficient self.assertTrue( all(lp.objective == np.array([-1, -1, 0, bb._M, bb._M, bb._M]))) # problem is now feasible self.assertTrue(lp.getStatusCode() == 0, 'lp should now be optimal')
def test_dual_bound(self): # Ensure that BranchAndBound.dual_bound generates the dual function # that we saw in ISE 418 HW 3 problem 1 bb = BranchAndBound(h3p1) bb.solve() bound = bb.dual_bound(CyLPArray([3.5, -3.5])) self.assertTrue(bb.objective_value == bound, 'dual should be strong at original rhs') prob = { 0: h3p1_0, 1: h3p1_1, 2: h3p1_2, 3: h3p1_3, 4: h3p1_4, 5: h3p1_5 } sol_new = {0: 0, 1: 1, 2: 1, 3: 2, 4: 2, 5: 3} sol_bound = {0: 0, 1: .5, 2: 1, 3: 2, 4: 2, 5: 2.5} for beta in range(6): new_bb = BranchAndBound(prob[beta]) new_bb.solve() bound = bb.dual_bound(CyLPArray(np.array([beta, -beta]))) self.assertTrue( isclose(sol_new[beta], new_bb.objective_value, abs_tol=.01), 'new branch and bound objective should match expected') self.assertTrue(isclose(sol_bound[beta], bound), 'new dual bound value should match expected') self.assertTrue( bound <= new_bb.objective_value + .01, 'dual bound value should be at most the value function for this rhs' ) bb = BranchAndBound(small_branch) bb.solve() bound = bb.dual_bound(CyLPArray([2.5, 4.5])) # just make sure the dual bound works here too self.assertTrue( bound <= -5.99, 'dual bound value should be at most the value function for this rhs' ) # check function calls bb = BranchAndBound(small_branch) bb.solve() bound_duals = [ bb._bound_dual(n.lp) for n in bb.tree.get_node_instances([6, 12, 10, 8, 2]) ] with patch.object(bb, '_bound_dual') as bd: bd.side_effect = bound_duals bound = bb.dual_bound(CyLPArray([3, 3])) self.assertTrue(bd.call_count == 5) bb = BranchAndBound(small_branch) bb.solve() bound = bb.dual_bound(CyLPArray([3, 3])) with patch.object(bb, '_bound_dual') as bd: bound = bb.dual_bound(CyLPArray([1, 1])) self.assertFalse(bd.called)