Coluna.jl iteratively generate columns infos

Dear Coluna.jl developers,

I am using Coluna.jl (https://atoptima.github.io/Coluna.jl/dev/) for a simple branch-and-price approach to the VRP. Is there a way to use Coluna.jl without having all the column information at the start of the branch-and-price process? I generate an initial subset of columns (vehicle routes) and then generate the other columns and their costs during the branch-and-price process since the number of possible vehicle route is exponential. In the examples, it appears that all costs should be initialized at the beginning.

Thanks very much.
Best regards,
Luca

could you share a link?

https://atoptima.github.io/Coluna.jl/stable/start/start/#Classic-model-solved-with-MIP-solver

The subsection you shared is the problem definition, which is a large-scale MIP.

all costs should be initialized at the beginning

I didn’t get your point.
The doc there was giving the data of the problem, which is surely required.

The Coluna can help you decompose then.

(But from my perspective, I would like to write algorithm using JuMP directly, which should not be too strenuous.)

You can share some code, so that others can help you better.

Indeed I was wondering if one can start with a small set of columns whose costs are known, and generate the other column with the pricing phase (the cost of the new column is the value of the objective function of the pricing problem and they are not known in advance).

I understand what branch-and-price is and what a pricing subproblem is.
But not sure we are talking in the same context.
Could you share a description of the algorithm you want to realize?
Or give a description of the problem formulation you are dealing with, and your decomposition scheme?

It’s the classical VRP with time windows (https://convegni.unica.it/odysseus2018/files/2018/06/bpc_vr_desaulniers.pdf). The master is just the minimization of the cost of the routes with the constraint that each customer must be visit once, the pricing is the constrained resources shortest path.

Is this the original deterministic formulation?

If this is the case, then this formulation corresponds to the coluna doc you’ve just shared. And its data must be specified.

I use the path-formulation where the variables represent the vehicle routes. I cannot generate all the routes at the beginning since their number is exponential…

I understand what you are talking about. But what is the definition of \Omega?

It needs to be specified.

The number of vertices of a polytope can be exponential since they come from combinatorial numbers. Therefore we won’t include all of them in the master problem ab initio. But you still have to specify the polytope, e.g. with a number of linear constraints.

1 Like

Hi @Luca,

The Coluna developers don’t check this forum very often. @guimarqu is the one who can point you in the right direction. Otherwise, consider opening an issue at GitHub · Where software is built

Are you looking for the pricing callback? Pricing callback · Coluna.jl

If you’re looking for a pure-JuMP column generation (no branching) tutorial where the columns are generated during the solve, see Column generation · JuMP

Hi,

You need to write the original formulation with JuMP and annotate variables and constraints using BlockDecomposition. Coluna uses these annotations to generate the extended formulation (master + pricing subproblems). These formulations are internally managed by Coluna.

If you want to start with a set of columns, you can use the initial columns callback : Initial columns callback · Coluna.jl

Concerning the subproblems, do you generate them using (i) MILP solver or (ii) a resource constrained shortest path algorithm (this is the most efficient) ?

(i) nothing to do in this case. Coluna will compute the reduced cost of pricing variables at each iteration, update the subproblems, optimize, generate the columns and put them into the master.

(ii) you can use a pricing callback. Coluna will send you the reduced cost of pricing variables and you’ll send the columns at the end of the callback (see Pricing callback · Coluna.jl)

If your fleet is homogeneous of size K, pricing subproblems are identical and therefore all generate the same column. You can solve using “multiplicity” to tell Coluna the number of subproblems is the size of the fleet (Identical subproblems · Coluna.jl). Doing this, Coluna will solve the subproblem once and the usual convex combination equality on the column weights into the master will be replaced by a constraint that allow to choose between 0 and K columns.

3 Likes

Using the economic interpretation

To be honest, I didn’t understand its reasoning. But I can give a proof of its logic from a mathematical perspective, in which Lagrangian dual problem is involved.
This problem comes in a different flavor of the one I studied (This problem appears to have no large-scale deterministic MIP formulation). No wonder I was a bit confused at a earlier time.
But things are clear to me now. My code for that cutting roll problem is simply

import JuMP, Gurobi; GRB_ENV = Gurobi.Env();
l = [2, 3]; # length (in meters)
d = [4, 5]; # demand (in quantities)
W = 7; # length of the raw roll
P = [3 0; 0 2];
I, J = size(P) # J indicates the number of existing columns
@assert length(l) == length(d) == I
COT = 1e-6 # cut-off tolerance
glb = Inf # global lower bound

function outer_mip(P) # employed to find feasible/suboptimal solutions
    J = size(P, 2)
    outer = JuMP.Model(() -> Gurobi.Optimizer(GRB_ENV)); JuMP.set_silent(outer)
    JuMP.@variable(outer, n[1:J] ≥ 0, Int)
    JuMP.@constraint(outer, [i = 1:I], view(P, i, :)'n ≥ d[i]) # [π ≥ 0]
    JuMP.@objective(outer, Min, sum(n))
    JuMP.optimize!(outer); JuMP.assert_is_solved_and_feasible(outer; allow_local = false)
    return ub, n = JuMP.objective_value(outer), JuMP.value.(n)
end;

subproblem = JuMP.Model(() -> Gurobi.Optimizer(GRB_ENV)); JuMP.set_silent(subproblem)
JuMP.@variable(subproblem, p[1:I] ≥ 0, Int); JuMP.@constraint(subproblem, l'p ≤ W) # a feasible pattern

outerDual = JuMP.Model(() -> Gurobi.Optimizer(GRB_ENV)); JuMP.set_silent(outerDual)
JuMP.@variable(outerDual, π[1:I] ≥ 0); JuMP.@objective(outerDual, Max, d'π)
J = size(P, 2)
JuMP.@constraint(outerDual, [j = 1:J], 1 ≥ view(P, :, j)'π) # [⇐ n ≥ 0]

while true # the aim of this main loop is to recruit as many advantageous columns as possible,
    # in order to lower the feasible objective value of the `outer_mip`.
    # This is in turn achieved by attempting to lower the DualBound of `outer_mip`.
    # Hence, a dual cutting plane method is employed
    Π, lb = let
        JuMP.optimize!(outerDual); JuMP.assert_is_solved_and_feasible(outerDual; allow_local = false)
        JuMP.value.(π), JuMP.objective_value(outerDual)
    end;
    p_new, vio_level = let
        JuMP.@objective(subproblem, Max, Π'p)
        JuMP.optimize!(subproblem); JuMP.assert_is_solved_and_feasible(subproblem; allow_local = false)
        JuMP.value.(p), JuMP.objective_value(subproblem) - 1
    end;
    if vio_level > COT
        @info "At J = $J, (invalid) lb = $lb"
        JuMP.@constraint(outerDual, 1 ≥ p_new'π) # add a new cut to lower the lb of the outer_mip
        P = [P p_new] # recruit a new advantageous column
        J = size(P, 2) # update J
    else
        global glb = lb
        @info "At J = $J, lb = $lb; no more advantageous columns can be found, thus break"
        break
    end
end

# After the main loop, we generate a feasible solution to the original problem.
# This feasible solution cannot assure global optimality because we have no idea
# whether there is still an advantageous column unrecruited. And I'm unaware of
# an efficient algorithm to identify it.
ub, n = outer_mip(P)
@info "We find a feasible solution attaining the ObjVal = $ub, Lagrangian ObjBound = $glb, the solution is" n

@WalterMadelim if you have suggestions for improvements to the JuMP documentation, please open a pull request. But let’s please not derail someone else’s question with unrelated topics.

1 Like