Ejemplo n.º 1
0
def BranchAndBoundCylp(T, CONSTRAINTS, VARIABLES, OBJ, MAT, RHS,
                   branch_strategy=MOST_FRACTIONAL,
                   search_strategy=DEPTH_FIRST,
                   complete_enumeration=False,
                   display_interval=None,
                   binary_vars=True):
    if T.get_layout() == 'dot2tex':
        cluster_attrs = {'name':'Key', 'label':r'\text{Key}', 'fontsize':'12'}
        T.add_node('C', label = r'\text{Candidate}', style = 'filled',
                      color = 'yellow', fillcolor = 'yellow')
        T.add_node('I', label = r'\text{Infeasible}', style = 'filled',
                      color = 'orange', fillcolor = 'orange')
        T.add_node('S', label = r'\text{Solution}', style = 'filled',
                      color = 'lightblue', fillcolor = 'lightblue')
        T.add_node('P', label = r'\text{Pruned}', style = 'filled',
                      color = 'red', fillcolor = 'red')
        T.add_node('PC', label = r'\text{Pruned}$\\ $\text{Candidate}', style = 'filled',
                      color = 'red', fillcolor = 'yellow')
    else:
        cluster_attrs = {'name':'Key', 'label':'Key', 'fontsize':'12'}
        T.add_node('C', label = 'Candidate', style = 'filled',
                      color = 'yellow', fillcolor = 'yellow')
        T.add_node('I', label = 'Infeasible', style = 'filled',
                      color = 'orange', fillcolor = 'orange')
        T.add_node('S', label = 'Solution', style = 'filled',
                      color = 'lightblue', fillcolor = 'lightblue')
        T.add_node('P', label = 'Pruned', style = 'filled',
                      color = 'red', fillcolor = 'red')
        T.add_node('PC', label = 'Pruned \n Candidate', style = 'filled',
                      color = 'red', fillcolor = 'yellow')
    T.add_edge('C', 'I', style = 'invisible', arrowhead = 'none')
    T.add_edge('I', 'S', style = 'invisible', arrowhead = 'none')
    T.add_edge('S', 'P', style = 'invisible', arrowhead = 'none')
    T.add_edge('P', 'PC', style = 'invisible', arrowhead = 'none')
    T.create_cluster(['C', 'I', 'S', 'P', 'PC'], cluster_attrs)

    # Change to CyLP format
    cyOBJ = CyLPArray([-val for val in OBJ.values()]) # CyLP takes min
    cyMAT = np.matrix([MAT[v] for v in VARIABLES]).T
    cyRHS = CyLPArray(RHS)


    # The initial lower bound
    LB = -INFINITY
    # The number of LP's solved, and the number of nodes solved
    node_count = 1
    iter_count = 0
    lp_count = 0

    numCons = len(CONSTRAINTS)
    numVars = len(VARIABLES)
    # List of incumbent solution variable values
    opt = dict([(i, 0) for i in VARIABLES])
    pseudo_u = dict((i, (OBJ[i], 0)) for i in VARIABLES)
    pseudo_d = dict((i, (OBJ[i], 0)) for i in VARIABLES)
    print("===========================================")
    print("Starting Branch and Bound")
    if branch_strategy == MOST_FRACTIONAL:
        print("Most fractional variable")
    elif branch_strategy == FIXED_BRANCHING:
        print("Fixed order")
    elif branch_strategy == PSEUDOCOST_BRANCHING:
        print("Pseudocost brancing")
    else:
        print("Unknown branching strategy %s" %branch_strategy)
    if search_strategy == DEPTH_FIRST:
        print("Depth first search strategy")
    elif search_strategy == BEST_FIRST:
        print("Best first search strategy")
    else:
        print("Unknown search strategy %s" %search_strategy)
    print("===========================================")
    # List of candidate nodes
    Q = PriorityQueue()
    # The current tree depth
    cur_depth = 0
    cur_index = 0
    # Timer
    timer = time.time()
    Q.push(0, -INFINITY, (0, None, None, None, None, None, None))
    # Branch and Bound Loop
    while not Q.isEmpty():
        infeasible = False
        integer_solution = False
        (cur_index, parent, relax, branch_var, branch_var_value, sense,
        rhs) = Q.pop()
        if cur_index is not 0:
            cur_depth = T.get_node_attr(parent, 'level') + 1
        else:
            cur_depth = 0
        print("")
        print("----------------------------------------------------")
        print("")
        if LB > -INFINITY:
            print("Node: %s, Depth: %s, LB: %s" %(cur_index,cur_depth,LB))
        else:
            print("Node: %s, Depth: %s, LB: %s" %(cur_index,cur_depth,"None"))
        if relax is not None and relax <= LB:
            print("Node pruned immediately by bound")
            T.set_node_attr(parent, 'color', 'red')
            continue
        #====================================
        #    LP Relaxation
        #====================================
        # Compute lower bound by LP relaxation
        prob = CyLPModel()

        # CyLP do not allow change variable object without model
        if binary_vars:
            var = prob.addVariable('x', dim=len(VARIABLES))
            prob += 0 <= var <= 1
        else:
            var = prob.addVariable('x', dim=len(VARIABLES))
            prob += 0 <= var # To avoid random generated problems be unbounded


        prob += cyMAT * var <= RHS
        prob.objective = cyOBJ * var
        s = CyClpSimplex(prob)

        # Fix all prescribed variables
        branch_vars = []
        if cur_index is not 0:
            sys.stdout.write("Branching variables: ")
            branch_vars.append(branch_var)
            if sense == '>=':
                prob += var[int(branch_var[1:])] >= rhs # slice the name for index
            else:
                prob += var[int(branch_var[1:])] <= rhs # slice the name for index
            print(branch_var, end=' ')
            pred = parent
            while not str(pred) == '0':
                pred_branch_var = T.get_node_attr(pred, 'branch_var')
                pred_rhs = T.get_node_attr(pred, 'rhs')
                pred_sense = T.get_node_attr(pred, 'sense')
                if pred_sense == '<=':
                    prob += var[int(pred_branch_var[1:])] <= pred_rhs
                else:
                    prob += var[int(pred_branch_var[1:])] >= pred_rhs
                print(pred_branch_var, end=' ')
                branch_vars.append(pred_branch_var)
                pred = T.get_node_attr(pred, 'parent')
            print()
        # Solve the LP relaxation
        s = CyClpSimplex(prob)
        s.initialSolve()
        lp_count = lp_count +1

        if (s.getStatusCode() < 0):
            print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%')
            print(lp_count)
            print(s.getStatusCode())
            print(s.primal())
            print(s.objectiveValue)
            print(s.primalVariableSolution)
            print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%')

        # Check infeasibility
        #-1 - unknown e.g. before solve or if postSolve says not optimal
        #0 - optimal
        #1 - primal infeasible
        #2 - dual infeasible
        #3 - stopped on iterations or time
        #4 - stopped due to errors
        #5 - stopped by event handler (virtual int ClpEventHandler::event())
        infeasible = (s.getStatusCode() in [-1,1,3,4,5])
        # Print status
        if infeasible:
            print("LP Solved, status: Infeasible")
        else:
            print("LP Solved, status: %s, obj: %s" %(s.getStatusString(),-s.objectiveValue))

        if(s.getStatusCode() == 0):
            relax = -s.objectiveValue
            # Update pseudocost
            if branch_var != None:
                if sense == '<=':
                    pseudo_d[branch_var] = (
                    old_div((pseudo_d[branch_var][0]*pseudo_d[branch_var][1] +
                    old_div((T.get_node_attr(parent, 'obj') - relax),
                    (branch_var_value - rhs))),(pseudo_d[branch_var][1]+1)),
                    pseudo_d[branch_var][1]+1)
                else:
                    pseudo_u[branch_var] = (
                    old_div((pseudo_u[branch_var][0]*pseudo_d[branch_var][1] +
                     old_div((T.get_node_attr(parent, 'obj') - relax),
                     (rhs - branch_var_value))),(pseudo_u[branch_var][1]+1)),
                    pseudo_u[branch_var][1]+1)
            var_values = dict([(i, s.primalVariableSolution['x'][int(i[1:])]) for i in VARIABLES])
            integer_solution = 1
            for i in VARIABLES:
                if (abs(round(var_values[i]) - var_values[i]) > .001):
                    integer_solution = 0
                    break
            # Determine integer_infeasibility_count and
            # Integer_infeasibility_sum for scatterplot and such
            integer_infeasibility_count = 0
            integer_infeasibility_sum = 0.0
            for i in VARIABLES:
                if (var_values[i] not in set([0,1])):
                    integer_infeasibility_count += 1
                    integer_infeasibility_sum += min([var_values[i],
                                                      1.0-var_values[i]])
            if (integer_solution and relax>LB):
                LB = relax
                for i in VARIABLES:
                    # These two have different data structures first one
                    #list, second one dictionary
                    opt[i] = var_values[i]
                print("New best solution found, objective: %s" %relax)
                for i in VARIABLES:
                    if var_values[i] > 0:
                        print("%s = %s" %(i, var_values[i]))
            elif (integer_solution and relax<=LB):
                print("New integer solution found, objective: %s" %relax)
                for i in VARIABLES:
                    if var_values[i] > 0:
                        print("%s = %s" %(i, var_values[i]))
            else:
                print("Fractional solution:")
                for i in VARIABLES:
                    if var_values[i] > 0:
                        print("%s = %s" %(i, var_values[i]))
            #For complete enumeration
            if complete_enumeration:
                relax = LB - 1
        else:
            relax = INFINITY
        if integer_solution:
            print("Integer solution")
            BBstatus = 'S'
            status = 'integer'
            color = 'lightblue'
        elif infeasible:
            print("Infeasible node")
            BBstatus = 'I'
            status = 'infeasible'
            color = 'orange'
        elif not complete_enumeration and relax <= LB:
            print("Node pruned by bound (obj: %s, UB: %s)" %(relax,LB))
            BBstatus = 'P'
            status = 'fathomed'
            color = 'red'
        elif cur_depth >= numVars :
            print("Reached a leaf")
            BBstatus = 'fathomed'
            status = 'L'
        else:
            BBstatus = 'C'
            status = 'candidate'
            color = 'yellow'
        if BBstatus is 'I':
            if T.get_layout() == 'dot2tex':
                label = '\text{I}'
            else:
                label = 'I'
        else:
            label = "%.1f"%relax
        if iter_count == 0:
            if status is not 'candidate':
                integer_infeasibility_count = None
                integer_infeasibility_sum = None
            if status is 'fathomed':
                if T._incumbent_value is None:
                    print('WARNING: Encountered "fathom" line before '+\
                        'first incumbent.')
            T.AddOrUpdateNode(0, None, None, 'candidate', relax,
                             integer_infeasibility_count,
                             integer_infeasibility_sum,
                             label = label,
                             obj = relax, color = color,
                             style = 'filled', fillcolor = color)
            if status is 'integer':
                T._previous_incumbent_value = T._incumbent_value
                T._incumbent_value = relax
                T._incumbent_parent = -1
                T._new_integer_solution = True
    #           #Currently broken
    #           if ETREE_INSTALLED and T.attr['display'] == 'svg':
    #               T.write_as_svg(filename = "node%d" % iter_count,
    #                                 nextfile = "node%d" % (iter_count + 1),
    #                                 highlight = cur_index)
        else:
            _direction = {'<=':'L', '>=':'R'}
            if status is 'infeasible':
                integer_infeasibility_count = T.get_node_attr(parent,
                                     'integer_infeasibility_count')
                integer_infeasibility_sum = T.get_node_attr(parent,
                                     'integer_infeasibility_sum')
                relax = T.get_node_attr(parent, 'lp_bound')
            elif status is 'fathomed':
                if T._incumbent_value is None:
                    print('WARNING: Encountered "fathom" line before'+\
                        ' first incumbent.')
                    print('  This may indicate an error in the input file.')
            elif status is 'integer':
                integer_infeasibility_count = None
                integer_infeasibility_sum = None
            T.AddOrUpdateNode(cur_index, parent, _direction[sense],\
                                 status, relax,\
                                 integer_infeasibility_count,\
                                 integer_infeasibility_sum,\
                                 branch_var = branch_var,\
                                 branch_var_value = var_values[branch_var],\
                                 sense = sense, rhs = rhs, obj = relax,\
                                 color = color, style = 'filled',\
                                 label = label, fillcolor = color)
            if status is 'integer':
                T._previous_incumbent_value = T._incumbent_value
                T._incumbent_value = relax
                T._incumbent_parent = parent
                T._new_integer_solution = True
            # Currently Broken
    #           if ETREE_INSTALLED and T.attr['display'] == 'svg':
    #               T.write_as_svg(filename = "node%d" % iter_count,
    #                                 prevfile = "node%d" % (iter_count - 1),
    #                                 nextfile = "node%d" % (iter_count + 1),
    #                                 highlight = cur_index)
            if T.get_layout() == 'dot2tex':
                _dot2tex_label = {'>=':' \geq ', '<=':' \leq '}
                T.set_edge_attr(parent, cur_index, 'label',\
                                   str(branch_var) + _dot2tex_label[sense] +\
                                   str(rhs))
            else:
                T.set_edge_attr(parent, cur_index, 'label',\
                                   str(branch_var) + sense + str(rhs))
        iter_count += 1
        if BBstatus == 'C':
            # Branching:
            # Choose a variable for branching
            branching_var = None
            if branch_strategy == FIXED_BRANCHING:
                #fixed order
                for i in VARIABLES:
                    frac = min(s.primalVariableSolution['x'][int(i[1:])]-math.floor(s.primalVariableSolution['x'][int(i[1:])]),\
                               math.ceil(s.primalVariableSolution['x'][int(i[1:])]) - s.primalVariableSolution['x'][int(i[1:])])
                    if (frac > 0):
                        min_frac = frac
                        branching_var = i
                        # TODO(aykut): understand this break
                        break
            elif branch_strategy == MOST_FRACTIONAL:
                #most fractional variable
                min_frac = -1
                for i in VARIABLES:
                    frac = min(s.primalVariableSolution['x'][int(i[1:])]-math.floor(s.primalVariableSolution['x'][int(i[1:])]),\
                               math.ceil(s.primalVariableSolution['x'][int(i[1:])])- s.primalVariableSolution['x'][int(i[1:])])
                    if (frac> min_frac):
                        min_frac = frac
                        branching_var = i
            elif branch_strategy == PSEUDOCOST_BRANCHING:
                scores = {}
                for i in VARIABLES:
                    # find the fractional solutions
                    if (s.primalVariableSolution['x'][int(i[1:])] - math.floor(s.primalVariableSolution['x'][int(i[1:])])) != 0:
                        scores[i] = min(pseudo_u[i][0]*(1-s.primalVariableSolution['x'][int(i[1:])]),\
                                        pseudo_d[i][0]*s.primalVariableSolution['x'][int(i[1:])])
                    # sort the dictionary by value
                branching_var = sorted(list(scores.items()),
                                       key=lambda x : x[1])[-1][0]
            else:
                print("Unknown branching strategy %s" %branch_strategy)
                exit()
            if branching_var is not None:
                print("Branching on variable %s" %branching_var)
            #Create new nodes
            if search_strategy == DEPTH_FIRST:
                priority = (-cur_depth - 1, -cur_depth - 1)
            elif search_strategy == BEST_FIRST:
                priority = (-relax, -relax)
            elif search_strategy == BEST_ESTIMATE:
                priority = (-relax - pseudo_d[branching_var][0]*\
                                 (math.floor(s.primalVariableSolution['x'][int(branching_var[1:])]) -\
                                      s.primalVariableSolution['x'][int(branching_var[1:])]),\
                            -relax + pseudo_u[branching_var][0]*\
                                 (math.ceil(s.primalVariableSolution['x'][int(branching_var[1:])]) -\
                                      s.primalVariableSolution['x'][int(branching_var[1:])]))
            node_count += 1
            Q.push(node_count, priority[0], (node_count, cur_index, relax, branching_var,
                    var_values[branching_var],
                    '<=', math.floor(s.primalVariableSolution['x'][int(branching_var[1:])])))
            node_count += 1
            Q.push(node_count, priority[1], (node_count, cur_index, relax, branching_var,
                    var_values[branching_var],
                    '>=', math.ceil(s.primalVariableSolution['x'][int(branching_var[1:])])))
            T.set_node_attr(cur_index, color, 'green')
        if T.root is not None and display_interval is not None and\
                iter_count%display_interval == 0:
            T.display(count=iter_count)

    timer = int(math.ceil((time.time()-timer)*1000))
    print("")
    print("===========================================")
    print("Branch and bound completed in %sms" %timer)
    print("Strategy: %s" %branch_strategy)
    if complete_enumeration:
        print("Complete enumeration")
    print("%s nodes visited " %node_count)
    print("%s LP's solved" %lp_count)
    print("===========================================")
    print("Optimal solution")
    #print optimal solution
    for i in sorted(VARIABLES):
        if opt[i] > 0:
            print("%s = %s" %(i, opt[i]))
    print("Objective function value")
    print(LB)
    print("===========================================")
    if T.attr['display'] is not 'off':
        T.display(count=iter_count)
    T._lp_count = lp_count
    return LB,opt
Ejemplo n.º 2
0
def BranchAndBound(T, CONSTRAINTS, VARIABLES, OBJ, MAT, RHS,
                   branch_strategy=MOST_FRACTIONAL,
                   search_strategy=DEPTH_FIRST,
                   complete_enumeration=False,
                   display_interval=None,
                   binary_vars=True,
                   solver='dynamic',
                   rel_param=(4, 3, 1 / 6, 5),
                   more_return=False
                   ):
    """
        solver: 
            dynamic       - initialSolve
            primalSimplex - initialPrimalSolve 
            dualSimplex   - initialDualSolve

        Parameter Tuple for Reliability Branching
        rel_param = (eta_rel,gamma,mu,lambda):
            eta_rel = 0       - psesudocost branching
            eta_rel = 1       - psesudocost branching with strong branching initialization
            eta_rel = 4,8     - best performing reliability branching rules
            eta_rel = inifity - strong branching
            gamma             - max number of simplex iterations in score calculation
            mu                - score factor, a number between 0 and 1. Paper uses 1/6
            lambda            - if max score is not updated for lambda 
                                consecutive iterations, stop.
        more_return:
            False - return maximizer and max 
            True  - also return a dict of stats(time, tree size, LP solved)
    """
    ACTUAL_BRANCH_STRATEGY = branch_strategy
    # reliability branching parameters
    eta_rel, gamma, mu, lam = rel_param
    # hybrid branching parameters
    total_num_pivot = average_num_pivot = 0
    # translate problems into cylp format
    cyOBJ = CyLPArray([-val for val in OBJ.values()])
    cyMAT = np.matrix([MAT[v] for v in VARIABLES]).T
    cyRHS = CyLPArray(RHS)
    OBJ = cyOBJ
    MAT = cyMAT
    RHS = cyRHS
    if T.get_layout() == 'dot2tex':
        cluster_attrs = {'name': 'Key', 'label': r'\text{Key}', 'fontsize': '12'}
        T.add_node('C', label=r'\text{Candidate}', style='filled',
                   color='yellow', fillcolor='yellow')
        T.add_node('I', label=r'\text{Infeasible}', style='filled',
                   color='orange', fillcolor='orange')
        T.add_node('S', label=r'\text{Solution}', style='filled',
                   color='lightblue', fillcolor='lightblue')
        T.add_node('P', label=r'\text{Pruned}', style='filled',
                   color='red', fillcolor='red')
        T.add_node('PC', label=r'\text{Pruned}$\\ $\text{Candidate}', style='filled',
                   color='red', fillcolor='yellow')
    else:
        cluster_attrs = {'name': 'Key', 'label': 'Key', 'fontsize': '12'}
        T.add_node('C', label='Candidate', style='filled',
                   color='yellow', fillcolor='yellow')
        T.add_node('I', label='Infeasible', style='filled',
                   color='orange', fillcolor='orange')
        T.add_node('S', label='Solution', style='filled',
                   color='lightblue', fillcolor='lightblue')
        T.add_node('P', label='Pruned', style='filled',
                   color='red', fillcolor='red')
        T.add_node('PC', label='Pruned \n Candidate', style='filled',
                   color='red', fillcolor='yellow')
    T.add_edge('C', 'I', style='invisible', arrowhead='none')
    T.add_edge('I', 'S', style='invisible', arrowhead='none')
    T.add_edge('S', 'P', style='invisible', arrowhead='none')
    T.add_edge('P', 'PC', style='invisible', arrowhead='none')
    T.create_cluster(['C', 'I', 'S', 'P', 'PC'], cluster_attrs)
    # The initial lower bound
    LB = -INFINITY
    # The number of LP's solved, and the number of nodes solved
    node_count = 1
    iter_count = 0
    lp_count = 0  # The problems that are fully solved during
    # Reliability and Hybrid branching is also couned here
    # For reliability branching
    half_solved = 0  # record number problems been halfly solved when calculate scores
    full_solved = 0  # record number problems been fully solved when calculate scores

    numVars = len(VARIABLES)
    # List of incumbent solution variable values
    opt = dict([(i, 0) for i in range(len(VARIABLES))])
    pseudo_u = dict((i, (-OBJ[i], 0)) for i in range(len(VARIABLES)))
    pseudo_d = dict((i, (-OBJ[i], 0)) for i in range(len(VARIABLES)))

    print("===========================================")
    print("Starting Branch and Bound")
    if branch_strategy == MOST_FRACTIONAL:
        print("Most fractional variable")
    elif branch_strategy == FIXED_BRANCHING:
        print("Fixed order")
    elif branch_strategy == PSEUDOCOST_BRANCHING:
        print("Pseudocost brancing")
    elif branch_strategy == RELIABILITY_BRANCHING:
        print("Reliability branching")
    elif branch_strategy == HYBRID:
        print('Hybrid strong/pseduocost branching')
    else:
        print("Unknown branching strategy %s" % branch_strategy)
    if search_strategy == DEPTH_FIRST:
        print("Depth first search strategy")
    elif search_strategy == BEST_FIRST:
        print("Best first search strategy")
    elif search_strategy == BEST_ESTIMATE:
        print("Best estimate search strategy")
    else:
        print("Unknown search strategy %s" % search_strategy)
    print("===========================================")
    # List of candidate nodes
    Q = PriorityQueue()
    # The current tree depth
    cur_depth = 0
    cur_index = 0
    # Timer
    timer = time.time()
    Q.push(0, -INFINITY, (0, None, None, None, None, None, None))
    # Branch and Bound Loop
    while not Q.isEmpty():
        # maximum allowed strong branch performed
        if branch_strategy == HYBRID and cur_depth > max(int(len(VARIABLES) * 0.2), 5):
            branch_strategy = PSEUDOCOST_BRANCHING
            print("Switch from strong branch to psedocost branch")
        infeasible = False
        integer_solution = False
        (cur_index, parent, relax, branch_var, branch_var_value, sense,
         rhs) = Q.pop()
        if cur_index is not 0:
            cur_depth = T.get_node_attr(parent, 'level') + 1
        else:
            cur_depth = 0
        print("")
        print("----------------------------------------------------")
        print("")
        if LB > -INFINITY:
            print("Node: %s, Depth: %s, LB: %s" % (cur_index, cur_depth, LB))
        else:
            print("Node: %s, Depth: %s, LB: %s" % (cur_index, cur_depth, "None"))
        if relax is not None and relax <= LB:
            print("Node pruned immediately by bound")
            T.set_node_attr(parent, 'color', 'red')
            continue
        # ====================================
        #    LP Relaxation
        # ====================================
        # Compute lower bound by LP relaxation
        prob = CyLPModel()
        if binary_vars:
            x = prob.addVariable('x', dim=len(VARIABLES))
            prob += 0 <= x <= 1
        else:
            x = prob.addVariable('x', dim=len(VARIABLES))
        prob.objective = OBJ * x
        prob += MAT * x <= RHS
        # Fix all prescribed variables
        branch_vars = []
        if cur_index is not 0:
            sys.stdout.write("Branching variables: ")
            branch_vars.append(branch_var)
            if sense == '>=':
                prob += x[branch_var] >= rhs
            else:
                prob += x[branch_var] <= rhs
            print('x_{}'.format(branch_var), end=' ')
            pred = parent
            while not str(pred) == '0':
                pred_branch_var = T.get_node_attr(pred, 'branch_var')
                pred_rhs = T.get_node_attr(pred, 'rhs')
                pred_sense = T.get_node_attr(pred, 'sense')
                if pred_sense == '<=':
                    prob += x[pred_branch_var] <= pred_rhs
                else:
                    prob += x[pred_branch_var] >= pred_rhs
                print(pred_branch_var, end=' ')
                branch_vars.append(pred_branch_var)
                pred = T.get_node_attr(pred, 'parent')
            print()
        # Solve the LP relaxation
        s = CyClpSimplex(prob)
        if solver == 'primalSimplex':
            s.initialPrimalSolve()
        elif solver == 'dualSimplex':
            s.initialDualSolve()
        else:
            s.initialSolve()
        lp_count = lp_count + 1
        total_num_pivot += s.iteration
        average_num_pivot = total_num_pivot / lp_count
        # Check infeasibility
        # -1 - unknown e.g. before solve or if postSolve says not optimal
        # 0 - optimal
        # 1 - primal infeasible
        # 2 - dual infeasible
        # 3 - stopped on iterations or time
        # 4 - stopped due to errors
        # 5 - stopped by event handler (virtual int ClpEventHandler::event())
        infeasible = (s.getStatusCode() in [1, 2])
        # Print status
        if infeasible:
            print("LP Solved, status: Infeasible")
        else:
            print("LP Solved, status: %s, obj: %s" % (s.getStatusString(),
                                                      s.objectiveValue))
        if(s.getStatusCode() == 0):
            relax = -round(s.objectiveValue,7)
            # Update pseudocost
            if branch_var != None:
                if sense == '<=':
                    pseudo_d[branch_var] = (
                        old_div((pseudo_d[branch_var][0] * pseudo_d[branch_var][1] +
                                 old_div((T.get_node_attr(parent, 'obj') - relax),
                                         (branch_var_value - rhs))),
                                (pseudo_d[branch_var][1] + 1)),
                        pseudo_d[branch_var][1] + 1)
                else:
                    pseudo_u[branch_var] = (
                        old_div((pseudo_u[branch_var][0] * pseudo_d[branch_var][1] +
                                 old_div((T.get_node_attr(parent, 'obj') - relax),
                                         (rhs - branch_var_value))),
                                (pseudo_u[branch_var][1] + 1)),
                        pseudo_u[branch_var][1] + 1)
            var_values = dict([(i, round(s.primalVariableSolution['x'][i],7))
                               for i in range(len(VARIABLES))])
            integer_solution = 1
            for i in range(len(VARIABLES)):
                if (abs(round(var_values[i]) - var_values[i]) > .001):
                    integer_solution = 0
                    break
            # Determine integer_infeasibility_count and
            # Integer_infeasibility_sum for scatterplot and such
            integer_infeasibility_count = 0
            integer_infeasibility_sum = 0.0
            for i in range(len(VARIABLES)):
                if (var_values[i] not in set([0, 1])):
                    integer_infeasibility_count += 1
                    integer_infeasibility_sum += min([var_values[i],
                                                      1.0 - var_values[i]])
            if (integer_solution and relax > LB):
                LB = relax
                for i in range(len(VARIABLES)):
                    # These two have different data structures first one
                    # list, second one dictionary
                    opt[i] = var_values[i]
                print("New best solution found, objective: %s" % relax)
                for i in range(len(VARIABLES)):
                    if var_values[i] > 0:
                        print("%s = %s" % (i, var_values[i]))
            elif (integer_solution and relax <= LB):
                print("New integer solution found, objective: %s" % relax)
                for i in range(len(VARIABLES)):
                    if var_values[i] > 0:
                        print("%s = %s" % (i, var_values[i]))
            else:
                print("Fractional solution:")
                for i in range(len(VARIABLES)):
                    if var_values[i] > 0:
                        print("x%s = %s" % (i, var_values[i]))
            # For complete enumeration
            if complete_enumeration:
                relax = LB - 1
        else:
            relax = INFINITY
        if integer_solution:
            print("Integer solution")
            BBstatus = 'S'
            status = 'integer'
            color = 'lightblue'
        elif infeasible:
            print("Infeasible node")
            BBstatus = 'I'
            status = 'infeasible'
            color = 'orange'
        elif not complete_enumeration and relax <= LB:
            print("Node pruned by bound (obj: %s, UB: %s)" % (relax, LB))
            BBstatus = 'P'
            status = 'fathomed'
            color = 'red'
        elif cur_depth >= numVars:
            print("Reached a leaf")
            BBstatus = 'fathomed'
            status = 'L'
        else:
            BBstatus = 'C'
            status = 'candidate'
            color = 'yellow'
        if BBstatus is 'I':
            if T.get_layout() == 'dot2tex':
                label = '\text{I}'
            else:
                label = 'I'
        else:
            label = "%.1f" % relax
        if iter_count == 0:
            if status is not 'candidate':
                integer_infeasibility_count = None
                integer_infeasibility_sum = None
            if status is 'fathomed':
                if T._incumbent_value is None:
                    print('WARNING: Encountered "fathom" line before ' +
                          'first incumbent.')
            T.AddOrUpdateNode(0, None, None, 'candidate', relax,
                              integer_infeasibility_count,
                              integer_infeasibility_sum,
                              label=label,
                              obj=relax, color=color,
                              style='filled', fillcolor=color)
            if status is 'integer':
                T._previous_incumbent_value = T._incumbent_value
                T._incumbent_value = relax
                T._incumbent_parent = -1
                T._new_integer_solution = True
    #           #Currently broken
    #           if ETREE_INSTALLED and T.attr['display'] == 'svg':
    #               T.write_as_svg(filename = "node%d" % iter_count,
    #                                 nextfile = "node%d" % (iter_count + 1),
    #                                 highlight = cur_index)
        else:
            _direction = {'<=': 'L', '>=': 'R'}
            if status is 'infeasible':
                integer_infeasibility_count = T.get_node_attr(parent,
                                                              'integer_infeasibility_count')
                integer_infeasibility_sum = T.get_node_attr(parent,
                                                            'integer_infeasibility_sum')
                relax = T.get_node_attr(parent, 'lp_bound')
            elif status is 'fathomed':
                if T._incumbent_value is None:
                    print('WARNING: Encountered "fathom" line before' +
                          ' first incumbent.')
                    print('  This may indicate an error in the input file.')
            elif status is 'integer':
                integer_infeasibility_count = None
                integer_infeasibility_sum = None
            T.AddOrUpdateNode(cur_index, parent, _direction[sense],
                              status, relax,
                              integer_infeasibility_count,
                              integer_infeasibility_sum,
                              branch_var=branch_var,
                              branch_var_value=var_values[branch_var],
                              sense=sense, rhs=rhs, obj=relax,
                              color=color, style='filled',
                              label=label, fillcolor=color)
            if status is 'integer':
                T._previous_incumbent_value = T._incumbent_value
                T._incumbent_value = relax
                T._incumbent_parent = parent
                T._new_integer_solution = True
            # Currently Broken
    #           if ETREE_INSTALLED and T.attr['display'] == 'svg':
    #               T.write_as_svg(filename = "node%d" % iter_count,
    #                                 prevfile = "node%d" % (iter_count - 1),
    #                                 nextfile = "node%d" % (iter_count + 1),
    #                                 highlight = cur_index)
            if T.get_layout() == 'dot2tex':
                _dot2tex_label = {'>=': ' \geq ', '<=': ' \leq '}
                T.set_edge_attr(parent, cur_index, 'label',
                                str(branch_var) + _dot2tex_label[sense] +
                                str(rhs))
            else:
                T.set_edge_attr(parent, cur_index, 'label',
                                str(branch_var) + sense + str(rhs))
        iter_count += 1
        if BBstatus == 'C':
            # Branching:
            # Choose a variable for branching
            branching_var = None
            if branch_strategy == FIXED_BRANCHING:
                # fixed order
                for i in range(len(VARIABLES)):
                    frac = min(var_values[i] - math.floor(var_values[i]),
                               math.ceil(var_values[i]) - var_values[i])
                    if (frac > 0):
                        min_frac = frac
                        branching_var = i
                        # TODO(aykut): understand this break
                        break
            elif branch_strategy == MOST_FRACTIONAL:
                # most fractional variable
                min_frac = -1
                for i in range(len(VARIABLES)):
                    frac = min(var_values[i] - math.floor(var_values[i]),
                               math.ceil(var_values[i]) - var_values[i])
                    if (frac > min_frac):
                        min_frac = frac
                        branching_var = i
            elif branch_strategy == PSEUDOCOST_BRANCHING:
                scores = {}
                for i in range(len(VARIABLES)):
                    # find the fractional solutions
                    if abs(var_values[i] - math.floor(var_values[i])) > 1e-8:
                        scores[i] = min(pseudo_u[i][0] * (math.ceil(var_values[i])
                                                          - var_values[i]),
                                        pseudo_d[i][0] * (var_values[i]
                                                          - math.floor(var_values[i])))
                # sort the dictionary by value
                branching_var = sorted(list(scores.items()), key=lambda x: x[1])[-1][0]

            elif branch_strategy == RELIABILITY_BRANCHING:
                # Calculating Scores
                # The algorithm in paper is different from the one in Grumpy
                # I will try to use paper notations
                scores = {}
                for i in range(len(VARIABLES)):
                    # find the fractional solutions
                    if abs(var_values[i] - math.floor(var_values[i])) > 1e-8:
                        qp = pseudo_u[i][0] * (math.ceil(var_values[i])
                                               - var_values[i])  # q^+
                        qm = pseudo_d[i][0] * (var_values[i]
                                               - math.floor(var_values[i]))  # q^^-
                        scores[i] = (1 - mu) * min(qm, qp) + mu * max(qm, qp)

                # sort the dictionary by value
                candidate_vars = [en[0] for en in sorted(list(scores.items()), key=lambda x: x[1], reverse=True)]

                no_change = 0  # number of iterations that maximum of scores is not changed
                smax = scores[candidate_vars[0]]  # current maximum of scores
                for i in candidate_vars:
                    if min(pseudo_d[i][1], pseudo_u[i][1]) < eta_rel:
                        qp = pseudo_u[i][0] * (math.ceil(var_values[i]) - var_values[i])  # q^+
                        qm = pseudo_d[i][0] * (var_values[i] - math.floor(var_values[i]))  # q^^-

                        # left subproblem/down direction
                        s_left = CyClpSimplex(prob)
                        s_left += x[i] <= math.floor(var_values[i])
                        s_left.maxNumIteration = gamma  # solve for fixed number of iterations
                        s_left.dual()
                        if s_left.getStatusCode() == 0:
                            qm = relax + s_left.objectiveValue  # use a more reliable source to update q^-
                            full_solved = full_solved + 1
                            lp_count = lp_count + 1  # If the LP is fully solved, counter plus one
                        elif s_left.getStatusCode() == 3:
                            qm = relax + s_left.objectiveValue  # use a more reliable source to update q^-
                            half_solved = half_solved + 1

                        # right subproblem/up direction
                        s_right = CyClpSimplex(prob)
                        s_right += x[i] >= math.ceil(var_values[i])
                        s_right.maxNumIteration = gamma  # solve for fixed number of iterations
                        s_right.dual()
                        if s_right.getStatusCode() == 0:
                            qp = relax + s_right.objectiveValue   # use a more reliable source to update q^+
                            full_solved = full_solved + 1
                            lp_count = lp_count + 1  # If the LP is fully solved, counter plus one
                        elif s_right.getStatusCode() == 3:
                            qp = relax + s_right.objectiveValue  # use a more reliable source to update q^+
                            half_solved = half_solved + 1

                        scores[i] = (1 - mu) * min(qm, qp) + mu * max(qm, qp)
                        if (smax == scores[i]):
                            no_change += 1
                        elif (smax <= scores[i]):
                            no_change = 0
                            smax = scores[i]
                        else:
                            no_change = 0

                    if no_change >= lam:
                        break
                branching_var = sorted(list(scores.items()), key=lambda x: x[1])[-1][0]

            elif branch_strategy == HYBRID:
                scores = {}
                for i in range(len(VARIABLES)):
                    # find the fractional solutions
                    if abs(var_values[i] - math.floor(var_values[i])) > 1e-8:
                        scores[i] = min(pseudo_u[i][0] * (math.ceil(var_values[i])
                                                          - var_values[i]),
                                        pseudo_d[i][0] * (var_values[i]
                                                          - math.floor(var_values[i])))
                candidate_vars = [en[0] for en in sorted(list(scores.items()), key=lambda x: x[1], reverse=True)]
                restricted_candidate_vars = candidate_vars[:max(1, int(0.5 * len(candidate_vars)))]
                # print("Subset of candidate variables:")
                # print(restricted_candidate_vars)
                best_progress = 0
                branch_candidate = None
                for i in restricted_candidate_vars:
                    s = CyClpSimplex(prob)
                    s += math.floor(var_values[i]) <= x[i] <= math.ceil(var_values[i])
                    s.maxNumIteration = average_num_pivot * 2
                    s.dual()
                    if s.getStatusCode() == 0:
                        lp_count += 1
                    progress = relax - (-s.objectiveValue)
                    if (progress - best_progress) > 1e-8:
                        branch_candidate = i
                        best_progress = progress
                if branch_candidate is None:
                    branch_candidate = restricted_candidate_vars[0]
                branching_var = branch_candidate

            else:
                print("Unknown branching strategy %s" % branch_strategy)
                exit()
            if branching_var is not None:
                print("Branching on variable %s" % branching_var)
            # Create new nodes
            if search_strategy == DEPTH_FIRST:
                priority = (-cur_depth - 1, -cur_depth - 1)
            elif search_strategy == BEST_FIRST:
                priority = (-relax, -relax)
            elif search_strategy == BEST_ESTIMATE:
                priority = (-relax - pseudo_d[branching_var][0] *
                            (math.floor(var_values[branching_var]) - var_values[branching_var]),
                            -relax + pseudo_u[branching_var][0] *
                            (math.ceil(var_values[branching_var]) - var_values[branching_var]))
            node_count += 1
            Q.push(node_count, priority[0], (node_count, cur_index, relax, branching_var,
                                             var_values[branching_var],
                                             '<=', math.floor(var_values[branching_var])))
            node_count += 1
            Q.push(node_count, priority[1], (node_count, cur_index, relax, branching_var,
                                             var_values[branching_var],
                                             '>=', math.ceil(var_values[branching_var])))
            T.set_node_attr(cur_index, color, 'green')
        if T.root is not None and display_interval is not None and\
                iter_count % display_interval == 0:
            T.display(count=iter_count)

    timer = int(math.ceil((time.time() - timer) * 1000))
    print("")
    print("===========================================")
    print("Branch and bound completed in %sms" % timer)
    print("Strategy: %s" % ACTUAL_BRANCH_STRATEGY)
    if complete_enumeration:
        print("Complete enumeration")
    print("%s nodes visited " % node_count)
    print("%s LP's solved" % lp_count)
    print("===========================================")
    print("Optimal solution")
    # print optimal solution
    for i in range(len(VARIABLES)):
        if opt[i] > 0:
            print("x%s = %s" % (i, opt[i]))
    print("Objective function value")
    print(LB)
    print("===========================================")
    if T.attr['display'] is not 'off':
        T.display(count=iter_count)
    T._lp_count = lp_count

    if more_return:
        stat = {'Time': timer, 'Size': node_count, 'LP Solved': lp_count}
        if branch_strategy == RELIABILITY_BRANCHING:
            stat['LP Solved for Bounds'] = lp_count - full_solved
            stat['Halfly Solved'] = half_solved
            stat['Fully Solved'] = full_solved
        return opt, LB, stat
    
    return opt, LB