Decomposing a scenario-feasibility MILP with binary recourse in JuMP

I have a two-stage multi-scenario MILP in JuMP with an inventory/order-flow structure.

The first-stage variables are shared across all scenarios. Each scenario then has its own continuous flow variables and binary timing variables. The objective and epigraph constraints depend on the shared first-stage variables. For fixed first-stage variables, the scenario-specific part is essentially a feasibility recourse problem.

A runnable extensive-form model builder with example data is here:

By default, the script builds a small two-scenario instance using scenarios 0 and 1. The data directory also contains additional scenarios, but the larger extensive form is the scale at which solving directly becomes difficult.

It can be run with:

julia OptBMExtensive.jl

The script builds the extensive-form model and reports its size; it does not attempt to solve the large instance directly. I have not included my attempted Benders implementation because I am mainly trying to understand the correct formulation, decomposition, and cut structure, rather than debug a particular implementation.

Model structure

The sets are i \in I for initial items, t,k \in T for periods, and s \in S for scenarios.

The shared first-stage variables are x_i and \theta, with 0 \le x_i \le 1 for all i \in I, and \theta \ge 0.

The objective is \min \; \theta.

The scenario-dependent epigraph constraints are \theta \ge \sum_{i \in I} v^0_{s,i,1} x_i for all s \in S.

For each scenario, the remaining variables are scenario-specific. They include order quantities y_{s,t} \ge 0, usage variables 0 \le u^0_{s,i,t} \le 1 and u^1_{s,k,t} \ge 0, stock variables q^0_{s,i,t} \ge 0 and q^1_{s,k,t} \ge 0, and binary timing variables \delta_{s,t} \in \{0,1\} and \gamma_{s,t} \in \{0,1\}.

Here y is an order quantity, u is usage, q is remaining stock, \delta indicates whether a period is an order period, and \gamma indicates whether initial items may be used in that period.

The initial-item flow constraints are q^0_{s,i,1} + u^0_{s,i,1} = x_i for all s \in S and i \in I, and q^0_{s,i,t} + u^0_{s,i,t} = q^0_{s,i,t-1} for all s \in S, i \in I, and t > 1.

The ordered-item flow constraints are q^1_{s,k,k} = y_{s,k} for all s \in S and k \in T, and q^1_{s,k,t} + u^1_{s,k,t} = q^1_{s,k,t-1} for all s \in S, k \in T, and t > k.

For compactness, define the following period-level expressions:

A^0_{s,t} = \sum_{i \in I} r^0_{s,i,t} q^0_{s,i,t},

A^1_{s,t} = \sum_{k<t} r^1_{s,k,t} q^1_{s,k,t},

B^0_{s,t} = \sum_{i \in I} v^0_{s,i,t} u^0_{s,i,t},

B^1_{s,t} = \sum_{k<t} v^1_{s,k,t} u^1_{s,k,t}.

The per-period balance constraint is A^0_{s,t} + A^1_{s,t} + B^0_{s,t} + B^1_{s,t} - p_{s,t} y_{s,t} = d_{s,t} for all s \in S and t \in T.

The timing logic is represented with big-M constraints:

y_{s,t} \le M^y_{s,t}\delta_{s,t} for all s \in S and t \in T.

\sum_{i \in I} u^0_{s,i,t} + \sum_{k<t} u^1_{s,k,t} \le M^u_{s,t}(1-\delta_{s,t}) for all s \in S and t \in T.

\sum_{k \le t} q^1_{s,k,t} \le M^q_{s,t}(1-\gamma_{s,t}) for all s \in S and t \in T.

\sum_{i \in I} u^0_{s,i,t} \le M^0_{s,t}\gamma_{s,t} for all s \in S and t \in T.

The interpretation of these binary constraints is:

  • The binary variable \delta_{s,t} enforces order/use exclusivity in each scenario and period. If \delta_{s,t}=1, then y_{s,t} may be positive but all usage in that period is forced to zero. If \delta_{s,t}=0, then y_{s,t}=0 and usage may occur.

  • The binary variable \gamma_{s,t} enforces a priority rule between ordered stock and initial-item usage. If \gamma_{s,t}=1, then initial items may be used, but remaining ordered stock at the end of the period is forced to zero. If \gamma_{s,t}=0, then initial-item usage is forced to zero.

So the intended logic is: a period cannot both order and use inventory, and initial items can only be used once ordered stock has been exhausted by the end of that period.

Therefore, for fixed first-stage variables x, each scenario subproblem is a MILP feasibility problem: Q_s(x)=0 if scenario s is feasible for x, and Q_s(x)=+\infty otherwise.

Approximate JuMP structure

The JuMP structure is roughly:

model = Model()

@variable(model, 0 <= initial_qty[1:n_items] <= 1)
@variable(model, theta >= 0)

for s in scenarios
    @variable(model, order_qty[1:n_periods] >= 0)

    @variable(model, 0 <= use_initial[1:n_items, 1:n_periods] <= 1)
    @variable(model, use_order[1:n_periods, 1:n_periods] >= 0)

    @variable(model, initial_stock[1:n_items, 1:n_periods] >= 0)
    @variable(model, order_stock[k = 1:n_periods, t = k:n_periods] >= 0)

    @variable(model, order_period[1:n_periods], Bin)
    @variable(model, initial_use_period[1:n_periods], Bin)

    # Coupling to the first-stage variables
    @constraint(model, [i = 1:n_items],
        initial_stock[i, 1] + use_initial[i, 1] == initial_qty[i]
    )

    @constraint(model, [i = 1:n_items, t = 2:n_periods],
        initial_stock[i, t] + use_initial[i, t] == initial_stock[i, t - 1]
    )

    # Ordered-item flow
    @constraint(model, [k = 1:n_periods],
        order_stock[k, k] == order_qty[k]
    )

    @constraint(model, [k = 1:n_periods, t = k+1:n_periods],
        order_stock[k, t] + use_order[k, t] == order_stock[k, t - 1]
    )

    # Scenario balance constraints and big-M timing constraints follow here.

    @constraint(model,
        theta >= sum(initial_value[s, i, 1] * initial_qty[i] for i in 1:n_items)
    )
end

@objective(model, Min, theta)

Formulation/decomposition question

I am unsure what the right decomposition should be for this structure.

One possible decomposition is:

  • master problem: shared variables x and \theta;
  • scenario subproblem: all scenario-specific variables for fixed x.

However, after fixing x, each scenario subproblem still contains the binary variables \delta and \gamma. Therefore, the scenario subproblem is a MILP feasibility problem, not a continuous LP feasibility problem. Classical LP-dual Benders feasibility cuts do not seem directly applicable.

Another possibility is to move the timing binaries \delta and \gamma into the master. Then the scenario subproblem becomes continuous, so Farkas/dual feasibility cuts should in principle be available. However, this makes the master much larger and more combinatorial, and on larger instances this has not worked well.

So my main question is:

What master/subproblem split would you recommend for this model, and what valid feasibility cuts would that split imply?

More specifically:

  1. Should the master contain only the shared first-stage variables x and \theta, leaving the binary timing variables in the scenario subproblems? If so, what kind of logic-based Benders or feasibility cuts would be valid for this structure?

  2. Or should the master include the timing binaries \delta and \gamma so that each scenario subproblem is continuous? If so, would the right cuts be LP Farkas feasibility cuts, and what would those cuts look like structurally?

  3. Are there stronger cuts or reformulations that exploit the flow/timing structure, rather than just excluding one infeasible binary pattern with a no-good cut?

  4. Once the formulation is chosen, would you recommend implementing it in JuMP as an outer-loop decomposition, or as branch-and-Benders using lazy constraints?

I would especially appreciate advice on what should be in the master, what should be in the subproblem, and what the valid feasibility cuts would look like.

Hi @decomp-lab, welcome to the forum :smile:

This is a rather open-ended question where the answer is “it depends”.

  1. For binary second-stage, you should really look to the literature. It depends if you want a local or global solution. Some things for you to search: “Lagrangian dual” + “Benders”
  2. Bringing second-stage variables back to the master is one possibility. They become “normal” state variables, so you just need regular optimality cuts
  3. I don’t know that we can help with this. We try to focus on Julia/JuMP related code questions. The MIP literature is vast.
  4. You could do either. See Benders decomposition · JuMP. This also explains how to compute feasibility cuts.

Selfish plug: you might also consider trying GitHub - odow/SDDP.jl: A JuMP extension for Stochastic Dual Dynamic Programming · GitHub.

Thanks, that’s helpful.

I think my original question was probably too broad.

For context, I had tried one Benders variant before posting. I put `x`, `theta`, `delta`, and `gamma` in the master, so that once those were fixed each scenario subproblem was continuous. I then used LP/Farkas feasibility cuts from the scenario subproblems.

That worked on a small test instance, but on the larger instance it did not find a feasible incumbent in a reasonable time. My suspicion is that the cuts are weak, the big-M formulation is making the master too hard, or both.

For now, I am not really looking for a global optimality proof. A good feasible solution/policy would already be useful.

I’ll take your suggestion and try formulating it in SDDP.jl. My rough plan is to treat the shared first-stage decision `x` as the state passed into the recourse stage, with the scenario-specific order/use/stock variables and the timing binaries in the second-stage model. Since the second stage still has binaries, I’ll look at the duality handlers you linked to, probably starting with `LagrangianDuality` or `BanditDuality`.

One thing I’ll need to handle is that the second stage is mostly a feasibility problem rather than a natural recourse-cost minimisation problem, so I may need to add penalised slack variables to get a useful value function.

I’ll try this route first. If I run into problems with the SDDP.jl formulation, I’ll post a smaller follow-up example.

Thanks again for the pointers.

I think I can have some comments on the topic but since I’m now having some ailments on my eye​:smiling_face_with_tear:, I’m now resting. I’ll see if I can share some insights after recovery.

Master side can indeed be the bottleneck of 2SSP-BD, especially if the num of scenarios is large.

I’ve taken a look at your actual code. SDDP is probably the wrong tool for the job.

How many scenarios do you want to scale to?

For now, I am not really looking for a global optimality proof. A good feasible solution/policy would already be useful.

The easiest way to start is just to write Benders. But when you want to compute the dual to bring a cut back, solve the LP relaxation instead of the binary second-stage.

Thanks. There are nine scenarios in the current data set, numbered 0 to 8. I have been using 0:1 as a quick small test case, but the case I am trying to get working is the full 0:8 instance. I do not currently need to scale to hundreds of scenarios; the difficulty already shows up on the nine-scenario case.

Following your suggestion, I tried a Benders implementation where the cuts are generated from the LP relaxation of each scenario subproblem. I have put the current code and small example data here:

The Benders file is benders_elastic_lp.jl. It can be run on two scenarios with:

julia benders_elastic_lp.jl 0:1 100 300 ./inventory_example_data true 1.0

and on all nine scenarios with:

julia benders_elastic_lp.jl 0:8 100 300 ./inventory_example_data true 1.0

One implementation detail: I used an elastic Phase-I LP relaxation for the scenario subproblems rather than direct Farkas rays. The timing binaries are relaxed to [0,1], nonnegative artificial slacks are added to the scenario constraints, and the subproblem minimizes total scaled violation.

The master contains the shared x, theta, and one violation estimator eta_s per scenario. If the elastic LP value at the current x is positive, I add a cut of the form

phi_s(x_k) + g_s' * (x - x_k) <= eta_s

where phi_s(x_k) is the elastic LP violation and g_s comes from the duals of the constraints linking the local scenario copy of x to the master x.

This is not literally a Farkas-ray cut from the original infeasible LP. I used an elastic Phase-I variant instead, where the LP-relaxed scenario subproblem minimises scaled artificial violation and the resulting duals generate feasibility cuts. I did this because extracting Farkas rays with Xpress appeared to require disabling presolve, which made the early Benders iterations very slow. My understanding is that this should still be a valid way to enforce feasibility of the LP-relaxed recourse problem as eta_s is driven to zero.

For the nine-scenario case 0:8, the LP-relaxation Benders loop converges and the plain LP recourse check passes for all scenarios. However, the final binary-recourse verification still reports positive scaled violations.

The results from one run are:

Metric Result
Status LP-recourse feasible, binary recourse not verified feasible
LP lower bound theta 4.71093e8
Cuts 135
Plain LP verification Passed for all 9 scenarios
Binary recourse check Positive scaled violations for all scenarios at 1e-4 tol

The minimum scaled violations from the binary-recourse checks are:

s0=9.3e-3, s1=2.2e-3, s2=2.4e-3, s3=5.8e-2, s4=6.4e-3,
s5=9.2e-4, s6=5.3e-3, s7=1.4e-3, s8=1.5e-3

So the LP-relaxation Benders loop seems to give an LP-recourse-feasible x and a lower bound, but the final x is not binary-recourse feasible.

Is this the expected limitation of using LP-relaxation Benders cuts for a binary-recourse problem? If so, what is the recommended way to enforce or recover binary-recourse feasibility when the master variable x is continuous?

Thanks Walter, hope your eye gets better soon.

In my case the instance is still small in scenario count, only nine scenarios, but the extensive form already does not solve reliably for that case, which is why I am looking at decomposition. The main issue I am seeing now is that the Benders LP-relaxation gives an LP-recourse-feasible x, but the same x fails binary-recourse verification.

I would be interested in any thoughts when you are feeling better.

What is the number of `n_items` in practice? is it over 1000? It affects the size of the master problem.

The most typical application of Benders decomposition in 2SSP context is a problem where the number of scenarios is large. In your case the num of scenarios is only 9, which might not motivate a decomposition method strongly, especially concerning your mixed-integer recourse setting.

Some general mentalities:

Decomposition methods (or block decomposition methods, equivalently) are well-suited for those problems where the coupling among blocks are weak, and the resulting intra-block subproblems are easy to solve (e.g. small-scaled MILPs). If either condition is unmet, then probably it would still be difficult even after decomposition.

There are 2 categories: dual decomposition (developed around complicating constraints) and primal decomposition (developed around complicating variables). Benders decomposition (BD) belongs to the latter. Although BD is closerly related to 2SSP, dual decomposition is still a via solution approach, particularly when the number of scenarios is small.

Compared with BD, dual decomposition is cleaner to implement, especially in your mixed-integer recourse context.

I’ve both implemented dual decomposition and BD successfully with JuMP, where neither (the user’s manual implementation of) branch-and-bound or lazy cuts are necessary.


My general impression is that the original formulation of your physical problem itself is less than ideal, For example, you’re ending up with pure feasibility cuts, but generally inventory problems should be cast as min-cost problems so that feasibility cuts shouldn’t occur altogether.

Thanks, this is very helpful.

On n_items: in the current full instance it is about 1,546. So although the scenario count is only 9, the shared first-stage vector is already fairly large, and each scenario brings a sizeable flow/timing recourse block.

I agree that 9 scenarios would not normally be a strong motivation for Benders in a standard two-stage stochastic program. In this case, my motivation is more pragmatic: the 9-scenario extensive form is not solving reliably/tractably for me, so I am trying decomposition as a way to get a feasible/certified solution for this instance rather than because I have hundreds of scenarios. For reference, the extensive-form builder is in OptBMExtensive.jl in the GitHub repo linked earlier.

Since posting, I have also experimented with a hybrid Benders approach. In outline:

  1. Start with a small master containing only the shared x, theta, and one eta_s per scenario.
  2. For each scenario, solve an elastic LP relaxation of the recourse problem at the current x.
  3. Use the duals from the constraints linking the scenario copy of x to the master x to add cuts of the form
    phi_s(x_k) + g_s' * (x - x_k) <= eta_s.
  4. Once the LP-relaxation Benders loop gives an LP-recourse-feasible point, check the true binary recourse scenario by scenario.
  5. If binary recourse fails for some scenarios, embed only the worst failing binary-recourse block directly into the master and continue using LP-relaxation cuts for the remaining scenarios.

So the method is not pure classical Benders; it is more like a partial/hybrid decomposition. The idea is to avoid putting all second-stage timing binaries into the master from the start, because that seems to collapse back toward the extensive form. Unfortunately, this is still taking around 7 hours on the full instance, which is too long for my use case.

Your comment about dual decomposition is interesting. I am not very familiar with it in this setting. My concern was that, for a mixed-integer recourse/feasibility problem, a Lagrangian or dual-decomposition approach might give a useful bound but then still require a nontrivial primal recovery heuristic to get a feasible x and scenario recourse decisions. Is that a fair concern, or is there a cleaner way to structure dual decomposition here?

If you have any pointers on how you would formulate the dual decomposition for this particular structure in JuMP, even just which constraints you would relax and what the resulting scenario subproblems/master coordination step would look like, that would be very useful.

Are you aware that this is termed robust optimization? Are you familiar with the usage of Benders decomposition in a robust optimization context?

My major concern is still about your feasibility subproblems. Maybe it should be avoided. It’s not pleasant to write julia program on generating feasibility cuts, according to my own experience. Do some relaxation/penalty so that you only need to generate optimality cuts. And the quality of the solution can be assessed via lower and upper bounds (i.e. optimality gaps).

Dual decomposition can be used in standard 2SSP context (see Section 2.2 of https://doi.org/10.1287/ijoc.2022.1185), but here you’re doing robust optimization. I’m not sure if it works well.


In the Bender decomposition framework, there are a few sorts of strengthened cuts can be added. My experience is that: if your num of scenario is large enough, then you basically only need strengthened benders cuts—the resulting optimality gap should be satisfyingly small. But here you have only 9 scenarios, so I’m not sure if your final optimality gap can be small. If you attempt to add other cuts e.g. Lagrangian cuts, that will incur significantly more MILP computations.

7 hours are indeed too long. So you probably want to revise your original physical model before focusing on decomposition techniques, e.g. use more concrete constraints rather than vague “big-M”'s. And introduce proper slack variables so that subproblem infeasibilities can be eradicated.

Thanks. I see the robust-optimization analogy, but I am not sure it is the most natural framing here.

There are only nine fixed scenarios, not a structured uncertainty set over which I am doing adversarial separation or robust-counterpart generation. There are also no probabilities or expected-value terms, so it is not really a standard stochastic-programming setting either.

The model is closer to a finite-scenario feasibility/recourse MILP: choose one shared first-stage x, and require that each of the nine scenario-specific mixed-integer recourse blocks is feasible, with theta representing the worst case over the scenario-dependent first-stage value expressions.

So my motivation for decomposition is mainly computational- the finite extensive form is already too hard to solve directly, even with only nine scenarios. The Benders/hybrid approach is just a way of delaying or partially enforcing the scenario recourse blocks, rather than a robust-optimization algorithm in the usual sense.