def METHOD_NAME(prob, i, booleans, depth, epsilon):
    if i > depth: return None
    # branch
    branch_var = branch(booleans)
    # try true branch
    branch_var.set(True)
    true_branch = bound(prob, booleans)
    # try false branch
    branch_var.set(False)
    false_branch = bound(prob, booleans)
    # keep track of best objective so far
    if true_branch['obj'] < false_branch['obj']:
        solution = true_branch
    else:
        solution = false_branch
    # update the bound
    solution['lb'] = min(true_branch['lb'],false_branch['lb'])
    solution['ub'] = min(true_branch['ub'],false_branch['ub'])
    # check if gap is small enough
    solution['gap'] = solution['ub'] - solution['lb']
    if solution['gap'] < epsilon:
        branch_var.unset()
        return solution
    # if the gap isn't small enough, we will choose a branch to go down
    def take_branch(true_or_false):
        branch_var.set(true_or_false)
        if true_or_false is True: branch_bools = true_branch['sol']
        else: branch_bools = false_branch['sol']
        # restore the values into the set of booleans
        for b, value in zip(booleans,branch_bools):
            b.save_value(value)
        return METHOD_NAME(prob, i+1, booleans, depth, epsilon)
    # partition based on lower bounds
    if true_branch['lb'] < false_branch['lb']:
        true_subtree = take_branch(True)
        false_subtree = take_branch(False)
    else:
        false_subtree = take_branch(False)
        true_subtree = take_branch(True)
    # propagate best solution up the tree
    if true_subtree and false_subtree:
        if true_subtree['obj'] < false_subtree['obj']:
            return true_subtree
        return false_subtree
    if not false_subtree and true_subtree: return true_subtree
    if not true_subtree and false_subtree: return false_subtree
    # return best guess so far
    return solution