Relaxation of MIP in order to use getdual

Hey everyone,

I have a modular optimisation problem that can be LP or MIP depending on the setting. To get the shadow price I query via JuMP.has_duals(model) whether I can receive the shadow price with the corresponding JuMP (JuMP.shadow_price) function.

If this is not the case, I have found the possibility to relax the problem via various forum entries.
Unfortunately, I get this error with the following shadow price query:

However, it appears to me that there is a dual result…

I have already tried various solutions, including using relax_integrality(). Nevertheless, the error message remains the same. I’m using Gurobi as a Solver for both problems.

Here is the corresponding source code to relax the MIP:

"""
        get_fixed_model(model::Model)
takes the MIP Problem and fixes all binary and integer variables to the solutions and returns a copy of the model.
"""
function get_fixed_model(model::Model)
    solution = Dict(
        v => value(v) for v in all_variables(model)
    )
    for v in all_variables(model)
        if is_binary(v)
            unset_binary(v)
            fix(v, solution[v]; force = true)
        elseif is_integer(v)
            unset_integer(v)
            fix(v, solution[v]; force = true)
        end
    end
    new_model = copy(model)
    set_optimizer(new_model, Gurobi.Optimizer)
    return new_model
end

"""
        get_shadow_price_mip(cep::OptModel, config::OptConfig, scale::Dict)
creates a fixed copy of the model, runs an optimization and calculates the shadow prices from this.
"""
function get_shadow_price_mip(model::Model)
    fixed_model = get_fixed_model(model)
    #undo = relax_integrality(fixed_model);
    JuMP.optimize!(fixed_model)
    return get_shadow_price(fixed_model) 
end

Please provide a reproducible example, including the definition of the model.

Here is a minimal example which shows the described behaviour. I hope this will help!
Thanks in advance :slight_smile:

using JuMP
using Gurobi

# create dummy model
model = Model(Gurobi.Optimizer)
# setup variables, constraints and objective
@variable(model, x)
@variable(model, a, Bin)
@constraint(model, reference, a * 2 * x >= 1)
@objective(model, Min, x)
# optimize
JuMP.optimize!(model)
JuMP.has_duals(model) # should return false
# new fixed model
#### test with fixed binaries
fixed_model = get_fixed_model(model)
#### alternative test with relax_integrality
#=
fixed_model = copy(model)
set_optimizer(fixed_model, Gurobi.Optimizer)
undo = relax_integrality(fixed_model);
set_optimizer_attributes(fixed_model, "NonConvex" => 2)
=#
JuMP.optimize!(fixed_model)
JuMP.has_duals(fixed_model) # returns false but expected to return true
JuMP.shadow_price(reference) # throws an error


function get_fixed_model(model::Model)
    solution = Dict(
        v => value(v) for v in all_variables(model)
    )
    for v in all_variables(model)
        if is_binary(v)
            unset_binary(v)
            fix(v, solution[v]; force = true)
        elseif is_integer(v)
            unset_integer(v)
            fix(v, solution[v]; force = true)
        end
    end
    new_model = copy(model)
    set_optimizer(new_model, Gurobi.Optimizer)
    return new_model
end

reference belongs to your original model, not the copied one.

Use fixed_model[:reference], or modify the original model in place rather than creating a copy.

Unfortunately, the error still occurs even if I use the ‘fixed_model[:reference]’.
The same behaviour remains even if I adjust the model itself.

Ah. Your constraint has a * 2 * x. When you fix a variable it does not replace it with a constant.

This is covered in the docs, albeit in a slightly weird place:

You need to set Gurobi’s QCPDual parameter:

fixed_model = get_fixed_model(model)
set_optimizer_attribute(fixed_model, "QCPDual", 1)
optimize!(fixed_model)

julia> has_duals(fixed_model)
true

julia> shadow_price(fixed_model[:reference])
-0.4999999999696272

Thanks a lot! Now it works. :slight_smile:
Have a nice weekend!

1 Like