# Efficiently Retrieving Variable Values after Gurobi optimization with JuMP

**URL:** <https://discourse.julialang.org/t/efficiently-retrieving-variable-values-after-gurobi-optimization-with-jump/127049>\
**Category:** Optimization (Mathematical)\
**Tags:** gurobi\
**Created:** [March 17, 2025, 10:51am UTC](https://discourse.julialang.org/t/efficiently-retrieving-variable-values-after-gurobi-optimization-with-jump/127049 "2025-03-17T10:51:06Z")\
**Posts on this page:** 10\
**Page:** 2

<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:** [July 9, 2025, 11:05pm UTC](https://discourse.julialang.org/t/efficiently-retrieving-variable-values-after-gurobi-optimization-with-jump/127049/21 "2025-07-09T23:05:40Z")

</div>

Can you close that issue and open it in SDDP.jl instead? [GitHub · Where software is built](https://github.com/odow/SDDP.jl/issues)

---

<div class="post-metadata">

**Author:** ![Mathis\_3](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mathis_3/32/215843_2.png) [@Mathis\_3](https://discourse.julialang.org/u/Mathis_3)\
**Post date:** [July 9, 2025, 11:13pm UTC](https://discourse.julialang.org/t/efficiently-retrieving-variable-values-after-gurobi-optimization-with-jump/127049/22 "2025-07-09T23:13:30Z")

</div>

Sorry, it’s done: [Performance issue with value in JuMP when using SDDP · Issue #869 · odow/SDDP.jl · GitHub](https://github.com/odow/SDDP.jl/issues/869)

---

<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:** [September 23, 2025, 6:32am UTC](https://discourse.julialang.org/t/efficiently-retrieving-variable-values-after-gurobi-optimization-with-jump/127049/23 "2025-09-23T06:32:56Z")

</div>

I just took a slight look at the procedure of `JuMP.value`. To be honest, it’s highly complex—no wonder it’s slow.

~~I would like to ask~~ if JuMP could provide a pass-by-address method, besides the existing pass-by-value method?

I wrote one

```julia
import JuMP, Gurobi
const GRB_ENV = Gurobi.Env()

function unsafe_array_value!(X, x)
    o = JuMP.unsafe_backend(JuMP.owner_model(x[1]))
    for j = eachindex(X)
        Gurobi.GRBgetdblattrelement(o, "X", 
        Gurobi.c_column(o, JuMP.index(x[j])),
        view(X, j))
    end
end

function unsafe_test()
    m = JuMP.Model(() -> Gurobi.Optimizer(GRB_ENV))
    JuMP.@variable(m, x[i = 1:3, j = 1:4] >= i + j)
    JuMP.@objective(m, Min, sum(x))
    JuMP.optimize!(m)
    X = similar(x, Float64)
    unsafe_array_value!(X, x)
    X
end

unsafe_test()

```

For direct\_model, do this

```julia-auto
function scalar!(X, j, m, x)
    o = m.moi_backend
    Gurobi.GRBgetdblattrelement(o, "X", Gurobi.c_column(o, JuMP.index(x[j])), view(X, j))
end;
value!(X, m, x) = foreach(j -> scalar!(X, j, m, x), eachindex(X));
function solve_mst_and_up_value!(model, s, θ, β)
    JuMP.optimize!(model)
    JuMP.termination_status(model) == JuMP.OPTIMAL || error()
    s.ub.x = JuMP.objective_bound(model)
    value!(s.β, model, β)
    value!(s.θ, model, θ)
end;
function shot!(ref; model = model, θ = θ, β = β, rEF = rEF)
    s = rEF.x
    @lock mst_lock solve_mst_and_up_value!(model, s, θ, β) # `s` gets updated/mutated here
    s_tmp = ref.x
    setfield!(ref, :x, s) # the ref gets updated here
    setfield!(rEF, :x, s_tmp)
end;

```

---

<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:** [September 23, 2025, 9:58am UTC](https://discourse.julialang.org/t/efficiently-retrieving-variable-values-after-gurobi-optimization-with-jump/127049/24 "2025-09-23T09:58:14Z")

</div>

> [@WalterMadelim](#):
>
> `Gurobi.c_column(o, JuMP.index(x[j]))`

This part is not correct.

You cannot connect the JuMP.index to an unsafe\_backend.

Please read the docstring of `unsafe_backend` very carefully:

> **[JuMP · JuMP](https://jump.dev/JuMP.jl/stable/api/JuMP/#unsafe_backend)**
>
> Documentation for JuMP.

I have no plans to change how value works. I’m yet to see any evidence that it is a performance bottleneck in any real problem.

---

<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:** [September 24, 2025, 2:43am UTC](https://discourse.julialang.org/t/efficiently-retrieving-variable-values-after-gurobi-optimization-with-jump/127049/25 "2025-09-24T02:43:30Z")

</div>

What is this API for? (JuMP → src → variables.jl)

```julia
function value(var_value::Function, v::GenericVariableRef)
    return var_value(v)
end

```

It’s weird:

```julia
julia> x
_[1]

julia> JuMP.value(x)
0.0

julia> JuMP.value(-, x) # What is the purpose of this??
-_[1]

```

Why not write `-x` directly?

---

<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:** [September 24, 2025, 2:48am UTC](https://discourse.julialang.org/t/efficiently-retrieving-variable-values-after-gurobi-optimization-with-jump/127049/26 "2025-09-24T02:48:27Z")

</div>

For stuff like `value(start_value, x)`, or for querying `value(var_value, expr)` where `var_value` is not `value`. `vaule(-, x)` is not the intended use-case. The weak assumption is really `var_value(::AbstractVariableRef)::Real`.

Originally added in [Add `value` for constraints with a custom `var_value` function by dourouc05 · Pull Request #2317 · jump-dev/JuMP.jl · GitHub](https://github.com/jump-dev/JuMP.jl/pull/2317)

---

<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:** [October 5, 2025, 3:50am UTC](https://discourse.julialang.org/t/efficiently-retrieving-variable-values-after-gurobi-optimization-with-jump/127049/27 "2025-10-05T03:50:08Z")

</div>

I was a bit surprised that your `Ref` method has zero allocate. Why?

But my `value!` method is still 2x faster than (MOI’s method + `@.`) (see the Comments of the last 2 lines):

```julia
import MathOptInterface as MOI
import Gurobi
using BenchmarkTools

# Retrieve by MOI
const VP = MOI.VariablePrimal();
pass_by_value_scalar(o, x) = MOI.get(o, VP, x);

# Retrieve by my method
function scalar!(X, j, o, x)
    Gurobi.GRBgetdblattrelement(o, "X",
        Gurobi.c_column(o, x[j]),
        view(X, j)
    )
end;
value!(X, o, x) = foreach(j -> scalar!(X, j, o, x), eachindex(X));

# Test begins
o = Gurobi.Optimizer();
N = 9999;
x = MOI.add_variables(o, N); X = similar(x, Float64);
MOI.optimize!(o)

@btime value!($X, $o, $x) # 406.750 μs (0 allocations: 0 bytes)
@btime @. $X = pass_by_value_scalar($o, $x); # 870.432 μs (0 allocations: 0 bytes)

```

This is the best solution that I can come up with. I don’t know if anyone can figure out a faster one.

* * *

Another question, @odow:  
what is the purpose of this wrapper?

```julia
# !!! danger
# We want to avoid being too specific in the type arguments to avoid method
# ambiguity. For example,
# get(::ModelLike, ::AbstractVariableAttribute, ::Vector{VariableIndex})
# would not allow us to define get(::SomeModel, ::AnyAttribute, ::Vector).
function get(model::ModelLike, attr::AnyAttribute, idxs::Vector)
    if isempty(idxs)
        return Vector{attribute_value_type(attr)}()
    end
    return get.(model, attr, idxs)
end

```

Why not let users themselves write `get.`?

---

<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:** [October 5, 2025, 8:25pm UTC](https://discourse.julialang.org/t/efficiently-retrieving-variable-values-after-gurobi-optimization-with-jump/127049/28 "2025-10-05T20:25:56Z")

</div>

The original intent was to allow solvers to provide an efficient way of querying multiple values in the same API call.We didn’t make much progress on this and most people use `get.` or `value.` because _querying the value is essentially never a bottleneck in any real-world application_.

Write nice, simple, high-level algorithms. Don’t worry about the low-level details and optimisations. You’ll find that most opportunities for improvements come from higher-level agorithmic choices. If your code is spending all its time querying a result value, you’re doing something wrong.

---

<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:** [October 23, 2025, 4:17am UTC](https://discourse.julialang.org/t/efficiently-retrieving-variable-values-after-gurobi-optimization-with-jump/127049/29 "2025-10-23T04:17:50Z")

</div>

> [@odow](#):
>
> Write nice, simple, high-level algorithms. Don’t worry about the low-level details and optimisations.

After gaining some experiences, I have to admit you’re right. So I won’t go back to use MOI because it will cost me lots of effort I can ill afford.

* * *

And I come up with this solution, where the master model’s solution is exposed to the outside world via `solution_ref.x` only. The master needs to keeps two initially allocated solution containers (=tuple).

Do you think this idea is advisable?

```julia
function retrieve!(solution_mutated, model) # this is allocate free (I hope so)
    x = model[:x]
    y = model[:y]
    @. solution_mutated.x = value(x)
    @. solution_mutated.y = value(y)
    nothing
end

# build the master model
model = JuMP.Model()
@variable(model, x[1:3])
@variable(model, y[1:4])
@expression(model, e, sum(x) + sum(y))
@constraint(model, e >= 0)
@objective(model, Min, e)

# define these 3 containers
solution_exposed = (x = fill(0., 3), y = fill(0., 4))
solution_mutated = (x = fill(2., 3), y = fill(2., 4))
solution_ref = Ref(solution_exposed)

# do some iterations
@constraint(model, e >= 1) # add a new cut
optimize!(model)
retrieve!(solution_mutated, model) # get the updated solution _without_ vector allocating
solution_exposed, solution_mutated = solution_mutated, solution_exposed # __swap__
setfield!(solution_ref, :x, solution_exposed) # update the referrence (in an instant)

@constraint(model, e >= 2) # add a new cut
optimize!(model)
retrieve!(solution_mutated, model)

```

If you think this idea is correct, then probably this can serve as an answer to this topic.

---

<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:** [October 23, 2025, 4:45am UTC](https://discourse.julialang.org/t/efficiently-retrieving-variable-values-after-gurobi-optimization-with-jump/127049/30 "2025-10-23T04:45:19Z")

</div>

Why so complicated? Until you benchmark that the allocations are a problem, _go for the simplest possible thing_.

```Julia
get_solution(model) = (; x = value(model[:x]), y = value(model[:y]))

model = JuMP.Model()
@variable(model, x[1:3])
@variable(model, y[1:4])
@expression(model, e, sum(x) + sum(y))
@constraint(model, e >= 0)
@objective(model, Min, e)
solution_ref = Ref{Any}()

optimize!(model)
solution_ref[] = get_solution(model)

@constraint(model, e >= 1)
optimize!(model)
solution_ref[] = get_solution(model)

@constraint(model, e >= 2)
optimize!(model)
solution_ref[] = get_solution(model)

```

[Previous page](https://discourse.julialang.org/t/efficiently-retrieving-variable-values-after-gurobi-optimization-with-jump/127049.md?page=1)
