Exemplo n.º 1
0
def _relax_customer_constraints_with_feasRelax(m, customer_cover_constraints,
                                               active_ptls, relaxed_ptls):
    # relax the model and relax minimal number of customer serving constraints
    pens = [1.0] * len(customer_cover_constraints)
    m.feasRelax(2, True, None, None, None, customer_cover_constraints, pens)
    # TODO: not sure if feasRelax can change Status, test it someday
    if m.Status == GRB.INTERRUPTED:
        raise KeyboardInterrupt()
    m.optimize()

    # restore SIGINT callback handler which is changed by gurobipy
    signal(SIGINT, default_int_handler)

    status = m.Status
    if __debug__:
        log(DEBUG - 2, "Relaxed problem Gurobi runtime = %.2f" % m.Runtime)
        if m.Status == GRB.OPTIMAL:
            log(
                DEBUG - 3, "Relaxed problem Gurobi objective = %.2f" %
                m.getObjective().getValue())

    if status == GRB.OPTIMAL:
        return _decision_variables_to_petals(m.X, active_ptls,
                                             relaxed_ptls), False
    elif status == GRB.TIME_LIMIT:
        raise GurobiError(
            10023,
            "Gurobi timeout reached when attempting to solve relaxed SCPCVRP")
    elif m.Status == GRB.INTERRUPTED:
        raise KeyboardInterrupt()
    return None, False
Exemplo n.º 2
0
def _mmp_solve(w1_ij, x_ij_keys, n, w2_ij=None):
    """A helper function that solves a weighted maximum matching problem.
    """

    m = Model("MBSA")

    if __debug__:
        log(DEBUG, "")
        log(
            DEBUG, "Solving a weighted maximum matching problem with " +
            "%d savings weights." % len(w1_ij))

    # build model
    x_ij = m.addVars(x_ij_keys, obj=w1_ij, vtype=GRB.BINARY, name='x')
    _mmp_add_cnts_sum(m, x_ij, x_ij_keys, w1_ij, n)
    m._vars = x_ij
    m.modelSense = GRB.MAXIMIZE
    m.update()

    # disable output
    m.setParam('OutputFlag', 0)
    m.setParam('TimeLimit', MAX_MIP_SOLVER_RUNTIME)
    m.setParam('Threads', MIP_SOLVER_THREADS)
    #m.write("out.lp")
    m.optimize()

    # restore SIGINT callback handler which is changed by gurobipy
    signal(SIGINT, default_int_handler)

    if __debug__:
        log(DEBUG - 1, "Gurobi runtime = %.2f" % m.Runtime)

    if m.Status == GRB.OPTIMAL:
        if w2_ij == None:
            max_wt, max_merge = max(
                (w1_ij[k], x_ij_keys[k]) for k, v in enumerate(m.X) if v)
        else:
            max_wt, _, max_merge = max((w1_ij[k], w2_ij[k], x_ij_keys[k])
                                       for k, v in enumerate(m.X) if v)

        return max_wt, max_merge[0], max_merge[1]
    elif m.Status == GRB.TIME_LIMIT:
        raise GurobiError(
            10023, "Gurobi timeout reached when attempting to solve GAP")
    elif m.Status == GRB.INTERRUPTED:
        raise KeyboardInterrupt()
    return None
Exemplo n.º 3
0
def _solve_gap(N, D_s, d, C, K, L=None, L_ctr_multipiler=1.0):
    """A helper function that Solves VRP as a Generalized Assignment Problem
    to assign customers to vehicles with a objective function that the delivery
    cost as described in (Fisher & Jaikumar 1981).
    
    D_s is the distance matrix complemented with distances to K seed points.
    That is:
          [D_0]
    D_s = [S  ], where D_0 is the first row of the full distance matrix and
                         S is the distances from seed points to node points
    d is the list of customer demands with d[0]=0 being the depot node
    C is the capacity of the K identical trucks
    
    also, additional (and optional) constraints can be given:
    
    L is the maximum tour cost/duration/length
    L_ctr_multipiler allows iteratively adjusting the max route cost
     approximation constraint in order to avoid producing assignments that are
     ruled infeasible by the feasibility checker. 
    --
    Fisher, M. L. and Jaikumar, R. (1981), A generalized assignment 
    heuristic for vehicle routing. Networks, 11: 109-124.
    """

    ## build the cost approximation matrix "insertion_cost"

    # it is ~ a insertion cost matrix, where each coefficient is the cost
    #  of inserting customer i to the route consisting visit to seed k.
    #
    # we assume that distances are symmetric, but if asymmetric
    #  distances are to be used, take min
    # d_{ik} = min(c_{0i}+c_{i{i_k}}+c_{i{i_k}},
    #              c_{0{i_k}}+c_[{i_k}i}+c_{i0})
    #              -(c_{0{i_k}}+c_{{i_k}0})

    m = Model("GAPCVRP")

    # the order of the keys is important when we interpret the results
    Y_ik_keys = [(i, k) for k in range(K) for i in range(1, N)]

    # delivery cost approximation coefficients for the objective function
    insertion_cost = {(i,k): D_s[0,i]+D_s[k,i]-D_s[k,0] \
                              for i,k in Y_ik_keys}

    # variables and the objective
    Y_ik = m.addVars(Y_ik_keys, obj=insertion_cost, vtype=GRB.BINARY, name='y')

    ## constraints

    # c1, the capacity constraint and optional tour cost constraint cl
    approx_route_cost_constraints = []
    if C: c1_coeffs = d[1:]
    for k in range(K):
        ck_vars = [Y_ik[i, k] for i in range(1, N)]
        if C:
            c1_lhs = LinExpr(c1_coeffs, ck_vars)
            #c1_lhs = Y_ik.prod(c1_coeffs, '*', k)
            m.addConstr(c1_lhs <= C, "c1_k%d" % k)

        # ct = optional tour cost constraints
        #  it is a bit hidden, but the additional side constraint can be found
        #  from Fisher & Jaikumar (1981) p121, 2. paragraph.
        # However, for whatever reason, this does not seem to produce the
        #  same results as reported in their paper as the constraint easily
        #  starts to make the problem infeasible and the exact mechanism to
        #  recover that is not specified in the paper.
        if L:
            ct_coeffs = [
                insertion_cost[(i, k)] * L_ctr_multipiler for i in range(1, N)
            ]
            ct_lhs = LinExpr(ct_coeffs, ck_vars)
            #ct_lhs = Y_ik.prod(ct_coeffs, '*', k)
            constr_l = m.addConstr(ct_lhs <= L, "cl_k%d" % k)
            approx_route_cost_constraints.append(constr_l)

    # c2, the assignment constraints
    for i in range(1, N):
        # c2_1..N every node assigned only to 1 route
        m.addConstr(Y_ik.sum(i, '*') == 1, "c1_i%d" % i)

    ## update the model and solve
    m._vars = Y_ik
    m.modelSense = GRB.MINIMIZE
    m.update()
    #m.write("gapvrp_model.lp")
    # disable output
    m.setParam('OutputFlag', 0)
    m.setParam('Threads', MIP_SOLVER_THREADS)
    # REMOVEME
    m.setParam('MIPFocus', 3)
    m.setParam('TimeLimit', MAX_MIP_SOLVER_RUNTIME)

    m.optimize()

    # restore SIGINT callback handler which is changed by gurobipy
    signal(SIGINT, default_int_handler)

    if __debug__:
        log(DEBUG - 1, "Gurobi runtime = %.2f" % m.Runtime)

    if m.Status == GRB.OPTIMAL:
        return _decision_variables_to_assignments(m, Y_ik, N, K)
    elif m.Status == GRB.INFEASIBLE and L:
        # relax the model and allow violating minimal number of the approximate
        #  route length constraints
        pens = [1.0] * len(approx_route_cost_constraints)
        m.feasRelax(1, True, None, None, None, approx_route_cost_constraints,
                    pens)
        # TODO: not sure if feasRelax can change Status, test it someday
        if m.Status == GRB.INTERRUPTED:
            raise KeyboardInterrupt()  # pass it on
        m.optimize()

        # restore SIGINT callback handler which is changed by gurobipy
        signal(SIGINT, default_int_handler)

        status = m.Status
        if __debug__:
            log(DEBUG - 1, "Relaxed problem Gurobi runtime = %.2f" % m.Runtime)
        if status == GRB.OPTIMAL:
            return _decision_variables_to_assignments(m, Y_ik, N, K)
        elif status == GRB.TIME_LIMIT:
            raise GurobiError(
                10023,
                "Gurobi timeout reached when attempting to solve relaxed SCPCVRP"
            )
        elif m.Status == GRB.INTERRUPTED:
            raise KeyboardInterrupt()  # pass it on
        return None
    elif m.Status == GRB.TIME_LIMIT:
        raise GurobiError(
            10023, "Gurobi timeout reached when attempting to solve GAP")
    elif m.Status == GRB.INTERRUPTED:
        raise KeyboardInterrupt()  # pass it on
    return None
Exemplo n.º 4
0
def _solve_set_covering(N,
                        active_ptls,
                        relaxed_ptls,
                        forbidden_combos,
                        allow_infeasible=True,
                        D=None,
                        K=None):
    """A helper function that solves a set covering problem. The inputs are
    a list of node sets and corresponding costs for the set.
    --
    Fisher, M. L. and Jaikumar, R. (1981), A generalized assignment 
    heuristic for vehicle routing. Networks, 11: 109-124.
    """

    m = Model("SCPCVRP")
    nactive = len(active_ptls.routes)
    nrelaxed = len(relaxed_ptls.routes)
    nforbidden = len(forbidden_combos)

    # the order of the keys is important when we interpret the results
    X_j_keys = range(nactive + nrelaxed)
    # variables and the objective
    X_j_costs = active_ptls.costs + relaxed_ptls.costs
    X_j_node_sets = active_ptls.nodes + relaxed_ptls.nodes
    #update forbidden indices to match the current active petal set
    X_j_forbidden_combos = []
    for fc in forbidden_combos:
        if all(i < nactive for i in fc):
            X_j_forbidden_combos.append(
                [i if i >= 0 else -i - 1 + nactive for i in fc])

    #print("REMOVEME: Solving with K=%d, %d node sets, and %d solutions forbidden" % (K, nactive+nrelaxed,nforbidden))

    if __debug__:
        log(
            DEBUG, "Solving over-constrained VRP as a set covering problem " +
            "with %d petals, where %d of the possible configurations are forbidden."
            % (nactive + nrelaxed, nforbidden))
        if nforbidden > 0:
            log(DEBUG - 2, " and with following solutions forbidden:")
            log(DEBUG - 3, "(petal indices = %s)" % str(X_j_forbidden_combos))
            for fc in X_j_forbidden_combos:
                fc_sol = [0]
                for i in fc:
                    if i < nactive:
                        fc_sol.extend(active_ptls.routes[i][1:])
                    else:
                        fc_sol.extend(relaxed_ptls.routes[i - nactive][1:])
                fc_sol = without_empty_routes(fc_sol)
                log(DEBUG - 2, "%s (%.2f)" % (fc_sol, objf(fc_sol, D)))

    X_j = m.addVars(X_j_keys, obj=X_j_costs, vtype=GRB.BINARY, name='x')

    ## constraints
    c1_constrs, c2_constrs, c3_constrs = _add_set_covering_constraints(
        m, N, K, X_j, X_j_keys, X_j_node_sets, X_j_forbidden_combos)

    ## update the model and solve
    m._vars = X_j
    m.modelSense = GRB.MINIMIZE
    m.update()
    # disable output
    m.setParam('OutputFlag', 0)
    m.setParam('TimeLimit', MAX_MIP_SOLVER_RUNTIME)
    m.setParam('Threads', MIP_SOLVER_THREADS)
    #m.write("petalout.lp")
    m.optimize()

    # restore SIGINT callback handler which is changed by gurobipy
    signal(SIGINT, default_int_handler)

    if __debug__:
        log(DEBUG - 2, "Gurobi runtime = %.2f" % m.Runtime)
        if m.Status == GRB.OPTIMAL:
            log(DEBUG - 3,
                "Gurobi objective = %.2f" % m.getObjective().getValue())

    if m.Status == GRB.OPTIMAL:
        return _decision_variables_to_petals(m.X, active_ptls,
                                             relaxed_ptls), True
    elif m.Status == GRB.TIME_LIMIT:
        raise GurobiError(
            10023, "Gurobi timeout reached when attempting to solve SCPCVRP")
    elif m.Status == GRB.INTERRUPTED:
        raise KeyboardInterrupt()
    # Sometimes the solution is infeasible, try to relax it a little.
    elif m.Status == GRB.INFEASIBLE and allow_infeasible:
        return _relax_customer_constraints_with_feasRelax(
            m, c1_constrs, active_ptls, relaxed_ptls)
    return None, False
Exemplo n.º 5
0
def solve_tsp_gurobi(D, selected_idxs):
    #print
    #print "============== GUROBI ============="

    n = len(selected_idxs)
    if selected_idxs[0] == selected_idxs[-1]:
        n = n - 1

    # no need to invoke Gurobi for tiny TSP cases
    if n <= 3:
        sol = None
        obj_f = 0.0
        if n > 1:
            sol = list(selected_idxs)
            if sol[0] != sol[-1]:
                sol.append(sol[0])
            obj_f = sum(D[sol[i - 1], sol[i]] for i in range(1, len(sol)))
        return sol, obj_f

    m = Model("TSP")
    m.params.OutputFlag = 0

    # Create variables
    edgevars = {}

    for i in range(n):
        for j in range(i + 1):
            from_node = selected_idxs[i]
            to_node = selected_idxs[j]
            edgevars[i, j] = m.addVar(obj=D[from_node, to_node],
                                      vtype=GRB.BINARY,
                                      name='e' + str(i) + '_' + str(j))
            edgevars[j, i] = edgevars[i, j]
        m.update()

    # Add degree-2 constraint, and forbid loops
    for i in range(n):
        m.addConstr(quicksum(edgevars[i, j] for j in range(n)) == 2)
        edgevars[i, i].ub = 0
    m.update()

    # Optimize model
    m._vars = edgevars
    m.params.LazyConstraints = 1
    m.setParam('TimeLimit', MAX_MIP_SOLVER_RUNTIME)
    m.setParam('Threads', MIP_SOLVER_THREADS)

    m.optimize(_subtourelim)

    # restore SIGINT callback handler which is changed by gurobipy
    signal(SIGINT, default_int_handler)

    status = m.Status
    if status == GRB.TIME_LIMIT:
        raise GurobiError(
            10023, "Gurobi timeout reached when attempting to solve TSP")
    elif m.Status == GRB.INTERRUPTED:
        raise KeyboardInterrupt()

    solution = m.getAttr('x', edgevars)
    selected = [(i, j) for i in range(n) for j in range(n)
                if solution[i, j] > 0.5]
    cycles = _subtour(selected, n)
    assert len(cycles) == n

    # make the route always start from the 1st index
    sol = [selected_idxs[i] for i in cycles] + [selected_idxs[0]]
    obj_f = m.objVal
    return sol, obj_f