# Update optimization model by changing variables to coefficients

**URL:** <https://discourse.julialang.org/t/update-optimization-model-by-changing-variables-to-coefficients/124642>\
**Category:** Optimization (Mathematical)\
**Tags:** jump\
**Created:** [January 10, 2025, 1:48pm UTC](https://discourse.julialang.org/t/update-optimization-model-by-changing-variables-to-coefficients/124642 "2025-01-10T13:48:54Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![max](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/max/32/31135_2.png) [@max](https://discourse.julialang.org/u/max)\
**Post date:** [January 10, 2025, 1:48pm UTC](https://discourse.julialang.org/t/update-optimization-model-by-changing-variables-to-coefficients/124642/1 "2025-01-10T13:48:54Z")

</div>

Hi there, 🙂  
I am trying to extract shadow prices out of a MIQP model. In order to do this I want to “fix” the mixed integer variables after a first solving and then extract the duals.

Here’s a minimal working example:

```julia
using JuMP
using Gurobi

model = Model(Gurobi.Optimizer)

@variable(model, x, Bin)
@variable(model, y)

@constraint(model, x * y >= 1)
@constraint(model, c1, x + y <= 6)
@objective(model, Max, y)

optimize!(model)

solution = Dict(v => value(v) for v in all_variables(model))
for v in all_variables(model)
    is_binary(v) || continue
    unset_binary(v)
    fix(v, solution[v]; force=true)
end
new_model = copy(model)
set_optimizer(new_model, Gurobi.Optimizer)
set_optimizer_attribute(new_model, "QCPDual", 1)
optimize!(new_model)

JuMP.shadow_price(new_model[:c1])

```

However, for larger and more difficult models, QCPDual is not guaranteed to compute duals according to [this post](https://support.gurobi.com/hc/en-us/community/posts/9695099791249-Computing-dual-for-QCP-in-Gurobi-Python).

I was thinking if there is an option to get fully rid of the binary/integer variables and replace those by the result values in order to end up with a full LP. Is there a way to e.g. iterate through my constraints by using e.g. `all_constraints(model; include_variable_in_set_constraints=true)` replace the variables and afterwards delete the variables from the model?  
In the end I would hope to get then a model in this case which looks like this:

```julia
max(y)
s.t.
1 * y >= 1
1 + y <= 6

```

So far I was not able to figure this out…

---

<div class="post-metadata">

**Author:** ![odow](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/odow/32/28685_2.png) [@odow](https://discourse.julialang.org/u/odow)\
**Post date:** [January 10, 2025, 7:35pm UTC](https://discourse.julialang.org/t/update-optimization-model-by-changing-variables-to-coefficients/124642/2 "2025-01-10T19:35:14Z")

</div>

This is an interesting question. We don’t have a good way to solve this, unfortunately, and I can’t even recommend a simple work-around.

The fixed variable approach should be sufficient. Do you have an example where the QCP dual failed?

Also note that you can fix the variables with:

```julia
undo = fix_discrete_variables(model);

```

See [Computing the duals of a mixed-integer program · JuMP](https://jump.dev/JuMP.jl/stable/tutorials/linear/mip_duality/#Use-fix_discrete_variables)

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [January 11, 2025, 1:11am UTC](https://discourse.julialang.org/t/update-optimization-model-by-changing-variables-to-coefficients/124642/3 "2025-01-11T01:11:10Z")

</div>

I don’t think this problem is well posed.

@max Dual variable (shadow prices) exists in convex conic programs, not in QP.  
Because QP may be convex or nonconvex. Therefore, if you want a dual variable, you should first formulate your problem as a convex quadratic conic program, not only a QP.

If your intention is to get an LP with adjustable parameters, then just natively write a function in which you build this LP, and the function arguments being those parameters.

---

<div class="post-metadata">

**Author:** ![odow](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/odow/32/28685_2.png) [@odow](https://discourse.julialang.org/u/odow)\
**Post date:** [January 11, 2025, 1:17am UTC](https://discourse.julialang.org/t/update-optimization-model-by-changing-variables-to-coefficients/124642/4 "2025-01-11T01:17:03Z")

</div>

The problem is well posed, and Gurobi can compute duals for QPs (and QCPs, if you set `QCPDual` to `1`).

Here’s how I would write the code in JuMP:

```julia
julia> using JuMP, Gurobi

julia> begin
           model = Model(Gurobi.Optimizer)
           set_silent(model)
           @variable(model, x, Bin)
           @variable(model, y)
           @constraint(model, x * y >= 1)
           @constraint(model, c1, x + y <= 6)
           @objective(model, Max, y)
           optimize!(model)
           undo = fix_discrete_variables(model)
           set_attribute(model, "QCPDual", 1)
           optimize!(model)
           @assert is_solved_and_feasible(model)
           shadow_price(c1)
       end
1.0

```

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [January 11, 2025, 2:25am UTC](https://discourse.julialang.org/t/update-optimization-model-by-changing-variables-to-coefficients/124642/5 "2025-01-11T02:25:39Z")

</div>

I don’t think in a realistic operations research problem we will make use of the dual variable of a general QP (or QCQP).

We tend to write dual programs for convex conic programs (including convex SOCP). (Do we also write dual programs for QP? seems exists but I don’t encounter it often).

The Gurobi solver is usable but some times may have problems like [Gurobi Error 10005: Unable to retrieve attribute 'Pi' - #7 by WalterMadelim](https://discourse.julialang.org/t/gurobi-error-10005-unable-to-retrieve-attribute-pi/53691/7)

I have a bit of experience about the Gurobi solver. Sometimes I encounter strange behaviors. Perhaps tuning those parameters could help make it more stable, like `Presolve`, `DualReductions`

---

<div class="post-metadata">

**Author:** ![odow](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/odow/32/28685_2.png) [@odow](https://discourse.julialang.org/u/odow)\
**Post date:** [January 11, 2025, 3:35am UTC](https://discourse.julialang.org/t/update-optimization-model-by-changing-variables-to-coefficients/124642/6 "2025-01-11T03:35:17Z")

</div>

> Do we also write dual programs for QP

See [Duality · MathOptInterface](https://jump.dev/MathOptInterface.jl/stable/background/duality/#Dual-for-problems-with-quadratic-functions)

---

<div class="post-metadata">

**Author:** ![max](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/max/32/31135_2.png) [@max](https://discourse.julialang.org/u/max)\
**Post date:** [January 13, 2025, 11:33am UTC](https://discourse.julialang.org/t/update-optimization-model-by-changing-variables-to-coefficients/124642/7 "2025-01-13T11:33:40Z")

</div>

Thanks for your quick answers! Actually, I cannot provide an easy example where I encounter the failed computation of the Duals by `QCPDual`…

However, I thought it could be an “easy” way of getting rid of the QP formulations (at least in my usecase). I only have QP formulations in my model where I multiply binaries with linear continuous variables. That’s why I thought I could remove those formulations.

Maybe I need to consider reformulating my Model

---

<div class="post-metadata">

**Author:** ![odow](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/odow/32/28685_2.png) [@odow](https://discourse.julialang.org/u/odow)\
**Post date:** [January 13, 2025, 8:25pm UTC](https://discourse.julialang.org/t/update-optimization-model-by-changing-variables-to-coefficients/124642/8 "2025-01-13T20:25:02Z")

</div>

You can reformulate `x * y` to:

```julia
using JuMP
function bilinear(model, x, y)
    @assert is_binary(x)
    @assert has_lower_bound(y)
    @assert has_upper_bound(y)
    y_L, y_U = lower_bound(y), upper_bound(y)
    xy = @variable(model)
    @constraint(model, xy <= y_U * x)
    @constraint(model, xy >= y_U * x)
    @constraint(model, xy <= y - y_L * (1 - x))
    @constraint(model, xy >= y - y_U * (1 - x))
    return xy
end

model = Model()
@variable(model, x, Bin)
@variable(model, -100 <= y <= 100)
xy = bilinear(model, x, y)
@constraint(model, xy >= 1)

```

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [March 24, 2025, 3:01am UTC](https://discourse.julialang.org/t/update-optimization-model-by-changing-variables-to-coefficients/124642/9 "2025-03-24T03:01:06Z")

</div>

In your example `x * y >= 1` happens to be a convex constraint.  
If I change the sign, then it ends up with Error, like this

```julia
julia> using JuMP, Gurobi

julia> begin
           model = Model(Gurobi.Optimizer)
               @variable(model, 0 <= x <= 1)
           @variable(model, y)
           @constraint(model, x * y <= 1)
           @constraint(model, c1, x + y <= 6)
           @objective(model, Max, y)
           set_attribute(model, "QCPDual", 1)
       end
Set parameter Username
Set parameter LicenseID to value 2602363
Academic license - for non-commercial use only - expires 2025-12-20
Set parameter QCPDual to value 1

julia> optimize!(model)
Set parameter QCPDual to value 1
Gurobi Optimizer version 12.0.1 build v12.0.1rc0 (win64 - Windows 11.0 (26100.2))

CPU model: 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz, instruction set [SSE2|AVX|AVX2|AVX512]
Thread count: 4 physical cores, 8 logical processors, using up to 8 threads

Non-default parameters:
QCPDual 1

Optimize a model with 1 rows, 2 columns and 2 nonzeros
Model fingerprint: 0x38be9e11
Model has 1 quadratic constraint
Coefficient statistics:
  Matrix range [1e+00, 1e+00]
  QMatrix range [1e+00, 1e+00]
  Objective range [1e+00, 1e+00]
  Bounds range [1e+00, 1e+00]
  RHS range [6e+00, 6e+00]
  QRHS range [1e+00, 1e+00]

Error: Continuous model is non-convex but QCP duals are requested.
       Either set QCPDuals to 0, or set NonConvex to 2 to solve model as a MIP.

User-callback calls 31, time in user-callback 0.00 sec
ERROR: Gurobi Error 10020: Constraint Q not PSD (diagonal adjustment of 1.0e+00 would be required). Set NonConvex parameter to -1 or 2 to solve model.
Stacktrace:
 [1] _check_ret
   @ K:\judepot1114\packages\Gurobi\QK0tn\src\MOI_wrapper\MOI_wrapper.jl:417 [inlined]
 [2] _check_ret_GRBoptimize(model::Gurobi.Optimizer)
   @ Gurobi K:\judepot1114\packages\Gurobi\QK0tn\src\MOI_wrapper\MOI_wrapper.jl:434
 [3] optimize!(model::Gurobi.Optimizer)
   @ Gurobi K:\judepot1114\packages\Gurobi\QK0tn\src\MOI_wrapper\MOI_wrapper.jl:2799
 [4] optimize!
   @ K:\judepot1114\packages\MathOptInterface\TYq6d\src\Bridges\bridge_optimizer.jl:367 [inlined]
 [5] optimize!
   @ K:\judepot1114\packages\MathOptInterface\TYq6d\src\MathOptInterface.jl:122 [inlined]
 [6] optimize!(m::MathOptInterface.Utilities.CachingOptimizer{…})
   @ MathOptInterface.Utilities K:\judepot1114\packages\MathOptInterface\TYq6d\src\Utilities\cachingoptimizer.jl:327
 [7] optimize!(model::Model; ignore_optimize_hook::Bool, _differentiation_backend::MathOptInterface.Nonlinear.SparseReverseMode, kwargs::@Kwargs{})
   @ JuMP K:\judepot1114\packages\JuMP\xlp0s\src\optimizer_interface.jl:595
 [8] optimize!(model::Model)
   @ JuMP K:\judepot1114\packages\JuMP\xlp0s\src\optimizer_interface.jl:546
 [9] top-level scope
   @ REPL[3]:1
Some type information was truncated. Use `show(err)` to see complete types.

```

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [March 24, 2025, 3:07am UTC](https://discourse.julialang.org/t/update-optimization-model-by-changing-variables-to-coefficients/124642/10 "2025-03-24T03:07:29Z")

</div>

If we follow the Error message, then

```julia
set_attribute(model, "NonConvex", 2)
optimize!(model)
JuMP.dual_status(model) # NO_SOLUTION::ResultStatusCode = 0
JuMP.shadow_price(c1)
ERROR: The shadow price is not available because no dual result is available.

```

---

<div class="post-metadata">

**Author:** ![odow](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/odow/32/28685_2.png) [@odow](https://discourse.julialang.org/u/odow)\
**Post date:** [March 24, 2025, 3:11am UTC](https://discourse.julialang.org/t/update-optimization-model-by-changing-variables-to-coefficients/124642/11 "2025-03-24T03:11:19Z")

</div>

Note that in the original example `x` is binary. It is then fixed to a value. So your posts and errors are because you have relaxed the bounds on `x`, not because of the sign of `x * y <= 1` (which is always non-convex, regardless of `>=` or `<=`).

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [March 24, 2025, 4:52am UTC](https://discourse.julialang.org/t/update-optimization-model-by-changing-variables-to-coefficients/124642/12 "2025-03-24T04:52:45Z")

</div>

I took a closer look. Here is your original code (which works)

```julia
using JuMP, Gurobi
begin
    model = Model(Gurobi.Optimizer)
    set_silent(model)
    @variable(model, x, Bin)
    @variable(model, y)
    @constraint(model, x * y >= 1)
    @constraint(model, c1, x + y <= 6)
    @objective(model, Max, y)
    optimize!(model) # 🍅 This is an MIQCP
    undo = fix_discrete_variables(model)
    # 🍅 At this line, `model` is continuous, convex and quadratic 
    # Therefore, we can proceed with
    set_attribute(model, "QCPDual", 1)
    optimize!(model)
    assert_is_solved_and_feasible(model; dual = true)
    shadow_price(c1)
end

```

> [@odow](#):
>
> you have relaxed the bounds on `x`

Actually I retained the bounds via `0 <= x <= 1`. I relaxed the integerality constraint of `x` to let it become a continuous QCP, which is easier than it was.

And about the bilinear constraint `x * y >= 1`, it is a bit subtle.

1. This constraint is itself nonconvex
2. However, if we restrict `x >= 0` additionally (which is then equivalent to `x > 0`), then Gurobi can identify it as convex.

Here are 2 related examples who are both convex (and Gurobi can identify)  
The first is

```julia
begin
    model = Model(Gurobi.Optimizer)
    @variable(model, x >= 0)
    @variable(model, y)
    @constraint(model, x * y >= 1)
    @constraint(model, c1, x + y <= 6)
    @objective(model, Max, y)
    set_attribute(model, "QCPDual", 1)
end
optimize!(model) # convex
assert_is_solved_and_feasible(model; dual = true)

```

The second is

```julia
begin
    model = Model(Gurobi.Optimizer)
    @variable(model, x <= 0)
    @variable(model, y)
    @constraint(model, x * y >= 1)
    @constraint(model, c1, x + y <= 6)
    @objective(model, Max, y)
    set_attribute(model, "QCPDual", 1)
end
optimize!(model) # convex
assert_is_solved_and_feasible(model; dual = true)

```

An exceptional point for me is that `Gurobi` can Identify this program as convex quadratic

```julia
    model = Model(Gurobi.Optimizer)
    @variable(model, y)
    @variable(model, x)
    set_lower_bound(x, 1)
    set_upper_bound(x, 1)
    @constraint(model, x * y <= 1)
    @objective(model, Max, y)
    optimize!(model)
    JuMP.solution_summary(model; verbose = true)

```

Although it still fails to directly identify it as a simpler linear program

```julia
    @variable(model, y)
    @constraint(model, y <= 1)
    @objective(model, Max, y)

```

Another exceptional point for me is that `Gurobi` can even provide the **dual variable of non-affine constraint** like `x * y >= 1`, although I’m unsure if it has a usage in reality.
