# \`dual\_objective\_value\` in JuMP.solution\_summary

**URL:** <https://discourse.julialang.org/t/dual-objective-value-in-jump-solution-summary/134556>\
**Category:** Optimization (Mathematical)\
**Tags:** jump, gurobi\
**Created:** [December 15, 2025, 2:27am UTC](https://discourse.julialang.org/t/dual-objective-value-in-jump-solution-summary/134556 "2025-12-15T02:27:51Z")\
**Posts on this page:** 13\
**Page:** 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:** [December 15, 2025, 2:27am UTC](https://discourse.julialang.org/t/dual-objective-value-in-jump-solution-summary/134556/1 "2025-12-15T02:27:51Z")

</div>

I have a question: what is the `dual_objective_value` below

```julia-auto
julia> JuMP.solution_summary(model)
solution_summary(; result = 1, verbose = false)
├ solver_name : Gurobi
├ Termination
│ ├ termination_status : OPTIMAL
│ ├ result_count : 1
│ ├ raw_status : Model was solved to optimality (subject to tolerances), and an optimal solution is available.
│ └ objective_bound : 3.14159e+00
├ Solution (result = 1)
│ ├ primal_status : FEASIBLE_POINT
│ ├ dual_status : FEASIBLE_POINT
│ ├ objective_value : 3.14159e+00
│ └ dual_objective_value : 3.14159e+00
└ Work counters
  ├ solve_time (sec) : 2.53916e-04
  ├ simplex_iterations : 0
  ├ barrier_iterations : 0
  └ node_count : 0

```

Which Gurobi’s attribute did it refer to? (I fail to identify)

---

<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:** [December 15, 2025, 2:47am UTC](https://discourse.julialang.org/t/dual-objective-value-in-jump-solution-summary/134556/2 "2025-12-15T02:47:34Z")

</div>

It doesn’t correspond to any Gurobi attribute.

`dual_objective_value(model)` is equivalent to querying the `MOI.DualObjectiveValue` attribute. This attribute returns the objective value of the dual problem.

Here’s the code:

> <https://github.com/jump-dev/Gurobi.jl/blob/c8ee27f79b884955c4450fa3e6e61af321ea1809/src/MOI_wrapper/MOI_wrapper.jl#L3374-L3378>

Because Gurobi doesn’t provide access to the dual objective value, we manually compute it based on the constraint duals.

Gurobi does have `ObjBound`, but I think we had cases in the past where this was not always equivalent to the dual objective for LPs: [Model Attributes - Gurobi Optimizer Reference Manual](https://docs.gurobi.com/projects/optimizer/en/current/reference/attributes/model.html#objbound)

---

<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:** [December 15, 2025, 3:56am UTC](https://discourse.julialang.org/t/dual-objective-value-in-jump-solution-summary/134556/3 "2025-12-15T03:56:41Z")

</div>

Inspired by a recent [post](https://discourse.julialang.org/t/retrieve-current-solution-when-termination-status-is-time-limit/134464/7) where an LP is solved to suboptimality, I think it makes some sense to have relative gap/feasible solution/objective bound for LPs also. But it seems that Gurobi only has `MIPGap`, not applied to LPs. (no more comments unless I find that more needs are required in the future.)

I believe we can make the information of `solution_summary` richer.

---

<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 20, 2026, 7:26am UTC](https://discourse.julialang.org/t/dual-objective-value-in-jump-solution-summary/134556/4 "2026-03-20T07:26:23Z")

</div>

I wonder what is the fastest way to generate a Benders’ feasibility cut from the 2nd-stage subproblem given an improper 1st-stage trial solution, with the Gurobi solver?

I already has a conventional method myself, which manually slack the 2nd-stage subproblem so it can be solved to OPTIMAL, and then I can generate a feasibility cut using reduced cost info and objective\_value. But with my method I need to switch the status of my subproblem (so I might sometimes need to solve twice—calling optimize! twice). I wonder if retrieving information directly under INFEASIBLE status would be better. But I’m yet unclear how to do

```julia-auto
Gurobi Optimizer version 13.0.1 build v13.0.1rc0 (linux64 - "Ubuntu 24.04.4 LTS")

CPU model: AMD EPYC 7763 64-Core Processor, instruction set [SSE2|AVX|AVX2]
Thread count: 128 physical cores, 256 logical processors, using up to 1 threads

Non-default parameters:
InfUnbdInfo 1
Threads 1

Optimize a model with 7178 rows, 11042 columns and 26012 nonzeros (Min)
Model fingerprint: 0x3bc01260
Model has 2 linear objective coefficients
Coefficient statistics:
  Matrix range [2e-01, 2e+00]
  Objective range [1e+00, 1e+00]
  Bounds range [1e-02, 1e+02]
  RHS range [1e-01, 2e+02]

Presolve removed 7156 rows and 9054 columns
Presolve time: 0.00s
Iteration Objective Primal Inf. Dual Inf. Time
       0 handle free variables 0s

Solved in 2278 iterations and 0.05 seconds (0.08 work units)
Infeasible model
solution_summary(; result = 1, verbose = false)
├ solver_name : Gurobi
├ Termination
│ ├ termination_status : INFEASIBLE
│ ├ result_count : 0
│ ├ raw_status : Model was proven to be infeasible.
│ └ objective_bound : 7.52770e+00
├ Solution (result = 1)
│ ├ primal_status : NO_SOLUTION
│ └ dual_status : INFEASIBILITY_CERTIFICATE
└ Work counters
  ├ solve_time (sec) : 4.97351e-02
  ├ simplex_iterations : 2278
  ├ barrier_iterations : 0
  └ node_count : 0
ERROR: Result index of attribute MathOptInterface.DualObjectiveValue(1) out of bounds. There are currently 0 solution(s) in the model.
Stacktrace:
 [1] check_result_index_bounds
   @ ~/.julia/packages/MathOptInterface/Q3V1z/src/attributes.jl:238 [inlined]
 [2] get
   @ ~/.julia/packages/Gurobi/K2XSK/src/MOI_wrapper/MOI_wrapper.jl:3376 [inlined]
 [3] _moi_get_result(model::Gurobi.Optimizer, args::MathOptInterface.DualObjectiveValue)
   @ JuMP ~/.julia/packages/JuMP/0tD10/src/optimizer_interface.jl:1191
 [4] get(model::JuMP.Model, attr::MathOptInterface.DualObjectiveValue)
   @ JuMP ~/.julia/packages/JuMP/0tD10/src/optimizer_interface.jl:1220
 [5] dual_objective_value(model::JuMP.Model; result::Int64)
   @ JuMP ~/.julia/packages/JuMP/0tD10/src/objective.jl:167

```

---

<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 20, 2026, 8:21am UTC](https://discourse.julialang.org/t/dual-objective-value-in-jump-solution-summary/134556/5 "2026-03-20T08:21:28Z")

</div>

That doesn’t look right. Can you open an issue in Gurobi.jl with a reproducible example? The smaller the better.

---

<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 20, 2026, 8:30am UTC](https://discourse.julialang.org/t/dual-objective-value-in-jump-solution-summary/134556/6 "2026-03-20T08:30:22Z")

</div>

> [@odow](#):
>
> That doesn’t look right.

Why?

* * *

The bigger question is

- How to generate a feasibility cut with the Gurobi Solver, directly under `INFEASIBLE` status? use `FarkasDual` for _every_ constraint? _Should_ we follow this routine?

Currently I have a method myself that works good (add artificial slack variable in the 2nd-stage problem so it is always feasible). My methods has pros and cons:

- pros: only need the primal formulation, only need to watch the linking constraint, only need the `RC` variable attribute to get the slope of the feasibility cut and need the `ObjVal` model attribute to determine the intercept of the feasibility cut.
- cons: I need to switch the status during performing the Benders decomposition algorithm: to “turn on” and later on “turn off” the artificial variables.

I wonder what is the pros and cons of the “standard” method (as written in the JuMP’s doc, with HiGHS solver)

---

<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 20, 2026, 8:38am UTC](https://discourse.julialang.org/t/dual-objective-value-in-jump-solution-summary/134556/7 "2026-03-20T08:38:12Z")

</div>

I tend to believe that results are better off retrieved directly from the solver (e.g. they use highly-optimized low-level code, so they should be the fastest).

For example, we can use `JuMP.value` to retrieve the numeric value of a `JuMP.AffExpr`, but that entails JuMP calculation, which might be slow. And It might be seen as a performance trap.

A better method should be

```julia-auto
JuMP.@variable(m, expr)
JuMP.@constraint(m, expr == #= The long expression =#)
optimize!(m)
JuMP.value(expr) # This directly retrieve a Float64 without further calculation in julia

```

I think the same issue lies with `JuMP.dual_objective_value`, if it performs additional calculation in julia. (am I right?)

---

<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 20, 2026, 8:45am UTC](https://discourse.julialang.org/t/dual-objective-value-in-jump-solution-summary/134556/8 "2026-03-20T08:45:39Z")

</div>

> That doesn’t look right.
> 
> Why?

Because JuMP threw an error that there were no solutions, but there is an infeasibility certificate.

The result count should be `1` if there is a certificate:

> <https://github.com/jump-dev/Gurobi.jl/blob/2b36c30c75a20c8b46f68e77894bcda2f4973da6/src/MOI_wrapper/MOI_wrapper.jl#L3380-L3383>

> How to generate a feasibility cut with the Gurobi Solver

> **[Feasibility cuts - Benders decomposition · JuMP](https://jump.dev/JuMP.jl/stable/tutorials/algorithms/benders_decomposition/#Feasibility-cuts)**
>
> This tutorial was generated using Literate.jl. Download the source as a .jl file. | Documentation for JuMP.

If you’re doing Benders, I strongly encourage adding variables to represent the incoming state variables. It means that you need to care only about the Farkas dual of the FixRef constraints for the incoming state variables. Basically: what the tutorial does.

> I think the same issue lies with `JuMP.dual_objective_value`, if it performs additional calculation in julia. (am I right?)

Yes, currently we haven’t implemented a method in Gurobi.jl to compute the dual objective value, so it uses a relatively expensive fallback.

---

<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 20, 2026, 8:57am UTC](https://discourse.julialang.org/t/dual-objective-value-in-jump-solution-summary/134556/9 "2026-03-20T08:57:13Z")

</div>

Can we exchange our method on generating feasibility cut? Using raw info retrieved from Gurobi model/variable attributes, (I have no concrete idea how you’d like to implement `dual_objective_value` and `reduced_cost` under `INFEASIBLE` status). (It’s perhaps not correct to retrieve the `RC` variable attribute from Gurobi under a `INFEASIBLE` status.)

My current guess is that you need to retrieve the constraint attribute `FarkasDual` of _all_ constraints (may be much more than the linking ones) in the 2nd-stage subproblem. And then calculate the intercept of the feasibility cut.

---

<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 20, 2026, 9:08am UTC](https://discourse.julialang.org/t/dual-objective-value-in-jump-solution-summary/134556/10 "2026-03-20T09:08:30Z")

</div>

> [@odow](#):
>
> That doesn’t look right. Can you open an issue in [Gurobi.jl](https://juliaregistries.github.io/General/packages/redirect_to_repo/Gurobi) with a reproducible example?

I’m ~~not~~ sure ~~if~~ it’s my reason: for performance reasons, I used many direct Gurobi C-API, e.g. I used `Gurobi.GRBoptimize` instead of `JuMP.optimize!`.

Now I’m used to build the physical model with JuMP, but use Gurobi C-API in the performance critical part.

---

<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 20, 2026, 9:37am UTC](https://discourse.julialang.org/t/dual-objective-value-in-jump-solution-summary/134556/11 "2026-03-20T09:37:04Z")

</div>

I guess the related code are at

```julia-auto
function _dual_objective_value(
    model::MOI.ModelLike,
    ::Type{F},
    ::Type{S},
    ::Type{T},
    result_index::Integer,
)::T where {T,F<:MOI.AbstractFunction,S<:MOI.AbstractSet}
    value = zero(T)
    if F == variable_function_type(S) && !_variable_set_in_dual_objective(S)
        # Early return. This is a constraint like x in R_+, so no contribution
        # appears in the dual objective.
        return value
    end
    for ci in MOI.get(model, MOI.ListOfConstraintIndices{F,S}())
        constant = MOI.constant(MOI.get(model, MOI.ConstraintFunction(), ci), T)
        set = MOI.get(model, MOI.ConstraintSet(), ci)
        dual = MOI.get(model, MOI.ConstraintDual(result_index), ci)
        value += _dual_objective_dot(constant, dual, set)
    end
    return value
end

```

and

```julia-auto
function MOI.get(
    model::Optimizer,
    attr::MOI.ConstraintDual,
    c::MOI.ConstraintIndex{MOI.ScalarAffineFunction{Float64},<:Any},
)
    _throw_if_optimize_in_progress(model, attr)
    MOI.check_result_index_bounds(model, attr)
    valueP = Ref{Cdouble}()
    row = Cint(_info(model, c).row - 1)
    if model.has_infeasibility_cert
        ret = GRBgetdblattrelement(model, "FarkasDual", row, valueP)
        _check_ret(model, ret)
        return -valueP[]
    end
    ret = GRBgetdblattrelement(model, "Pi", row, valueP)
    _check_ret(model, ret)
    return _dual_multiplier(model) * valueP[]
end

```

and (This is in `reduced_cost`)

```julia-auto
function _farkas_variable_dual(model::Optimizer, col::Cint)
    numnzP = Ref{Cint}()
    ret = GRBgetvars(model, numnzP, C_NULL, C_NULL, C_NULL, col, 1)
    _check_ret(model, ret)
    vbeg = Vector{Cint}(undef, 2)
    vind = Vector{Cint}(undef, numnzP[])
    vval = Vector{Cdouble}(undef, numnzP[])
    ret = GRBgetvars(model, numnzP, vbeg, vind, vval, col, 1)
    _check_ret(model, ret)
    λ = Vector{Cdouble}(undef, numnzP[])
    ret = GRBgetdblattrlist(model, "FarkasDual", length(vind), vind, λ)
    _check_ret(model, ret)
    return λ' * vval
end

```

. So I’d like to ask:

- Do you loop over _all_ constraints in the 2nd-stage subproblem and query the `FarkasDual` constraint attribute element-wise, in order to calculate the Benders’ feasibility cut?

If so, I think it’s a bit of involved.

---

<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 22, 2026, 9:56pm UTC](https://discourse.julialang.org/t/dual-objective-value-in-jump-solution-summary/134556/12 "2026-03-22T21:56:01Z")

</div>

> Do you loop over _all_ constraints in the 2nd-stage subproblem and query the `FarkasDual`constraint attribute element-wise, in order to calculate the Benders’ feasibility cut?

You need to compute the associated `dual_objective_value(model)`, which is the offset for the feasibility cut. If that’s not available directly from the solver, then yes.

- [Benders decomposition · JuMP](https://jump.dev/JuMP.jl/stable/tutorials/algorithms/benders_decomposition/#Feasibility-cuts)
- [Infeasibility certificates · JuMP](https://jump.dev/JuMP.jl/stable/moi/background/infeasibility_certificates/#Infeasible-problems)

Have you benchmarked to confirm that computing `dual_objective_value` is a bottleneck? Even if it is, you might find that simpler code is worth the trade-off.

---

<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 23, 2026, 1:06am UTC](https://discourse.julialang.org/t/dual-objective-value-in-jump-solution-summary/134556/13 "2026-03-23T01:06:09Z")

</div>

I have a trick myself that completely obviate the need for looking at `FarkasDual` of Gurobi.

 ![image](https://global.discourse-cdn.com/julialang/original/3X/c/a/ca1a65422ba195e4e8704256dd2f908fdb54ddf6.jpeg)

Suppose the `Q(x)` is your original 2nd-stage value function, where `x` is the linking variable from the 1st-stage, `z` is the copy variable in the subproblem, and `y` is the primal 2nd-stage variable. `π` is the dual variable associated with the copy constraint.

For some improper trial of `x`, you may find that the constraint `dz + ey == b` causes `INFEASIBLE`.

My solution is to build an augmented model as shown in `\bar_Q(x)`, where nonnegative variable `a^+` and `a^-` are introduced, and their summation is priced onto the objective with coefficient `1`.

Now the augmented model is always feasible for any trial input of `x`, so it can be used to generate feasibility cut under `OPTIMAL` termination.

To work in “slack” mode so you generate feasibility cut, you only need to turn off the objective coefficient of `y` (i.e. set them to zero). and delete the upper bound of `a`.

To revert to “non-slack” mode so you generate a normal optimality cut, you just need to recover the objective coefficient of `y` and set the upper bound of `a` to zero (so that all slack variables are essentially zero, `\bar_Q` reverts to `Q`).

Therefore, I only need to query `π` and `JuMP.objective_value` under `OPTIMAL` termination, in both situations.

* * *

Edit:

It clicked for me today that feasibility cuts may not be the exact tool for 2-Stage Stochastic Programming.

Suppose the 1st-stage decision is `x` and the uncertainty (happening after `x`’s decision) is `D`. The constraint is `x-1 ≤ D ≤ x+1`. (You can think of `x` as the Generator’s base dispatch, `D` is the realized power load, so this constraint stems from the ramping constraint of a generator). Now assume we have 2 scenarios

- `D == 2` with probability 0.5
- `D == 5` with probability 0.5

It’s easy to see that the `x` can be prepared so that each scenario be feasible individually, but not simultaneously (in which case `x` has to satisfy `4 ≤ x ≤ 3`).
