def _bound_dual(self, cur_lp: CyClpSimplex) -> CyClpSimplex:
        """ Place a bound on each index of the dual variable associated with the
        constraints of this node's LP relaxation and resolve. We do this by adding
        to each constraint i the slack variable 's_i' in the node's LP relaxation,
        and we give each new variable a large, positive coefficient in the objective.
        By duality, we get the desired dual LP constraints. Therefore, for nodes
        with infeasible primal LP relaxations and unbounded dual LP relaxations,
        resolving gives us a finite (albeit very large) dual solution, which can
        be used to parametrically lower bound the objective value of this node as
        we change its right hand side.

        :param cur_lp: the CyClpSimplex instance for which we want to bound its
        dual solution and resolve
        :return: A CyClpSimplex instance representing the same model as input,
        with the additions prescribed in the method description
        """

        # we add slack at end so user does not have to worry about adding to each node they create
        # and so we don't have to go back and update needlessly
        assert isinstance(cur_lp, CyClpSimplex), 'must give CyClpSimplex instance'
        for i, constr in enumerate(cur_lp.constraints):
            assert f's_{i}' not in [v.name for v in cur_lp.variables], \
                f"variable 's_{i}' is a reserved name. please name your variable something else"

        # cylp lacks (a documented) way to add a column, so rebuild the LP :[
        new_lp = CyClpSimplex()
        new_lp.logLevel = 0  # quiet output when resolving

        # recreate variables
        var_map = {v: new_lp.addVariable(v.name, v.dim) for v in cur_lp.variables}

        # bound them
        for orig_v, new_v in var_map.items():
            new_lp += CyLPArray(orig_v.lower) <= new_v <= CyLPArray(orig_v.upper)

        # re-add constraints, with slacks this time
        s = {}
        for i, constr in enumerate(cur_lp.constraints):
            s[i] = new_lp.addVariable(f's_{i}', constr.nRows)
            new_lp += s[i] >= CyLPArray(np.zeros(constr.nRows))
            new_lp += CyLPArray(constr.lower) <= \
                sum(constr.varCoefs[v] * var_map[v] for v in constr.variables) \
                + np.matrix(np.identity(constr.nRows))*s[i] <= CyLPArray(constr.upper)

        # set objective
        new_lp.objective = sum(
            CyLPArray(cur_lp.objectiveCoefficients[orig_v.indices]) * new_v for
            orig_v, new_v in var_map.items()
        ) + sum(self._M * v.sum() for v in s.values())

        # warm start
        orig_var_status, orig_slack_status = cur_lp.getBasisStatus()
        # each s_i at lower bound of 0 when added - status 3
        np.concatenate((orig_var_status, np.ones(sum(v.dim for v in s.values()))*3))
        new_lp.setBasisStatus(orig_var_status, orig_slack_status)

        # rerun and reassign
        new_lp.dual(startFinishOptions='x')
        return new_lp
Exemple #2
0
def solve_cylp(model, B_vectors, weights, ray, chunksize):
    """
    Worker process for LP_solver_cylp_mp.

    Parameters
    ----------
    model : CyClpModel
        Model of the LP Problem, see :py:func:`LP_solver_cylp_mp`
    B_vectors : matrix
        Matrix containing B vectors, see :py:func:`construct_B_vectors`
    weights : array
        Weights.
    ray : int
        Starting ray.
    chunksize : int
        Number of rays to process.

    Returns
    -------
    soln : array
        Solution to LP problem.

    See Also
    --------
    LP_solver_cylp_mp : Parent function.
    LP_solver_cylp : Single Process Solver.

    """
    from cylp.cy.CyClpSimplex import CyClpSimplex
    from cylp.py.modeling.CyLPModel import CyLPModel, CyLPArray

    n_gates = weights.shape[1] // 2
    n_rays = B_vectors.shape[0]
    soln = np.zeros([chunksize, n_gates])

    # import LP model in solver
    s = CyClpSimplex(model)

    # disable logging in multiprocessing anyway
    s.logLevel = 0

    i = 0
    for raynum in range(ray, ray + chunksize):
        # set new B_vector values for actual ray
        s.setRowLowerArray(np.squeeze(np.asarray(B_vectors[raynum])))
        # set new weights (objectives) for actual ray
        s.setObjectiveArray(np.squeeze(np.asarray(weights[raynum])))
        # solve with dual method, it is faster
        s.dual()
        # extract primal solution
        soln[i, :] = s.primalVariableSolution['x'][n_gates: 2 * n_gates]
        i = i + 1

    return soln
Exemple #3
0
def solve_cylp(model, B_vectors, weights, ray, chunksize):
    """
    Worker process for LP_solver_cylp_mp.

    Parameters
    ----------
    model : CyClpModel
        Model of the LP Problem, see :py:func:`LP_solver_cylp_mp`
    B_vectors : matrix
        Matrix containing B vectors, see :py:func:`construct_B_vectors`
    weights : array
        Weights.
    ray : int
        Starting ray.
    chunksize : int
        Number of rays to process.

    Returns
    -------
    soln : array
        Solution to LP problem.

    See Also
    --------
    LP_solver_cylp_mp : Parent function.
    LP_solver_cylp : Single Process Solver.

    """
    from cylp.cy.CyClpSimplex import CyClpSimplex
    from cylp.py.modeling.CyLPModel import CyLPModel, CyLPArray

    n_gates = weights.shape[1] // 2
    n_rays = B_vectors.shape[0]
    soln = np.zeros([chunksize, n_gates])

    # import LP model in solver
    s = CyClpSimplex(model)

    # disable logging in multiprocessing anyway
    s.logLevel = 0

    i = 0
    for raynum in range(ray, ray + chunksize):
        # set new B_vector values for actual ray
        s.setRowLowerArray(np.squeeze(np.asarray(B_vectors[raynum])))
        # set new weights (objectives) for actual ray
        s.setObjectiveArray(np.squeeze(np.asarray(weights[raynum])))
        # solve with dual method, it is faster
        s.dual()
        # extract primal solution
        soln[i, :] = s.primalVariableSolution['x'][n_gates:2 * n_gates]
        i = i + 1

    return soln
Exemple #4
0
def LP_solver_cylp(A_Matrix, B_vectors, weights, really_verbose=False):
    """
    Solve the Linear Programming problem given in Giangrande et al, 2012 using
    the CyLP module.

    Parameters
    ----------
    A_Matrix : matrix
        Row augmented A matrix, see :py:func:`construct_A_matrix`
    B_vectors : matrix
        Matrix containing B vectors, see :py:func:`construct_B_vectors`
    weights : array
        Weights.
    really_verbose : bool
        True to print CLP messaging. False to suppress.

    Returns
    -------
    soln : array
        Solution to LP problem.

    See Also
    --------
    LP_solver_cvxopt : Solve LP problem using the CVXOPT module.
    LP_solver_pyglpk : Solve LP problem using the PyGLPK module.

    """
    from cylp.cy.CyClpSimplex import CyClpSimplex
    from cylp.py.modeling.CyLPModel import CyLPModel, CyLPArray

    n_gates = weights.shape[1] // 2
    n_rays = B_vectors.shape[0]
    soln = np.zeros([n_rays, n_gates])

    # Create CyLPModel and initialize it
    model = CyLPModel()
    G = np.matrix(A_Matrix)
    h = CyLPArray(np.empty(B_vectors.shape[1]))
    x = model.addVariable('x', G.shape[1])
    model.addConstraint(G * x >= h)
    #c = CyLPArray(np.empty(weights.shape[1]))
    c = CyLPArray(np.squeeze(weights[0]))
    model.objective = c * x

    # import model in solver
    s = CyClpSimplex(model)
    # disable logging
    if not really_verbose:
            s.logLevel = 0

    for raynum in range(n_rays):

        # set new B_vector values for actual ray
        s.setRowLowerArray(np.squeeze(np.asarray(B_vectors[raynum])))
        # set new weights (objectives) for actual ray
        #s.setObjectiveArray(np.squeeze(np.asarray(weights[raynum])))
        # solve with dual method, it is faster
        s.dual()
        # extract primal solution
        soln[raynum, :] = s.primalVariableSolution['x'][n_gates: 2 * n_gates]

    # apply smoothing filter on a per scan basis
    soln = smooth_and_trim_scan(soln, window_len=5, window='sg_smooth')
    return soln
Exemple #5
0
def LP_solver_cylp(A_Matrix, B_vectors, weights, really_verbose=False):
    """
    Solve the Linear Programming problem given in Giangrande et al, 2012 using
    the CyLP module.

    Parameters
    ----------
    A_Matrix : matrix
        Row augmented A matrix, see :py:func:`construct_A_matrix`
    B_vectors : matrix
        Matrix containing B vectors, see :py:func:`construct_B_vectors`
    weights : array
        Weights.
    really_verbose : bool
        True to print CLP messaging. False to suppress.

    Returns
    -------
    soln : array
        Solution to LP problem.

    See Also
    --------
    LP_solver_cvxopt : Solve LP problem using the CVXOPT module.
    LP_solver_pyglpk : Solve LP problem using the PyGLPK module.

    """
    from cylp.cy.CyClpSimplex import CyClpSimplex
    from cylp.py.modeling.CyLPModel import CyLPModel, CyLPArray

    n_gates = weights.shape[1] // 2
    n_rays = B_vectors.shape[0]
    soln = np.zeros([n_rays, n_gates])

    # Create CyLPModel and initialize it
    model = CyLPModel()
    G = np.matrix(A_Matrix)
    h = CyLPArray(np.empty(B_vectors.shape[1]))
    x = model.addVariable('x', G.shape[1])
    model.addConstraint(G * x >= h)
    #c = CyLPArray(np.empty(weights.shape[1]))
    c = CyLPArray(np.squeeze(weights[0]))
    model.objective = c * x

    # import model in solver
    s = CyClpSimplex(model)
    # disable logging
    if not really_verbose:
        s.logLevel = 0

    for raynum in range(n_rays):

        # set new B_vector values for actual ray
        s.setRowLowerArray(np.squeeze(np.asarray(B_vectors[raynum])))
        # set new weights (objectives) for actual ray
        #s.setObjectiveArray(np.squeeze(np.asarray(weights[raynum])))
        # solve with dual method, it is faster
        s.dual()
        # extract primal solution
        soln[raynum, :] = s.primalVariableSolution['x'][n_gates:2 * n_gates]

    # apply smoothing filter on a per scan basis
    soln = smooth_and_trim_scan(soln, window_len=5, window='sg_smooth')
    return soln
Exemple #6
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