Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding the LP sensitivity report print function for solution_summary #2794

Closed
wants to merge 3 commits into from

Conversation

AtsushiSakai
Copy link
Contributor

I added the LP sensitivity report print function for solution_summary to fix #2662.

Currently, just a printing function is added, not using Table.jl interfaces.
This is a print example:

* Solver : Mock
* Status
  Termination status : OPTIMAL
  Primal status      : FEASIBLE_POINT
  Dual status        : FEASIBLE_POINT
  Message from the solver:
  "solver specific string"
* Candidate solution
  Objective value      : 2.1
  Dual objective value : 2.1
* Work counters
  Solve time (sec)   : 5.00000
* LP sensitivity summary
- Variables sensitivity
  Variable name : x
    Objective value coefficient : 1.1
    Reduced cost : 0.1
    Lower bound : -1.0
    Upper bound : 1.0
    Optimal value : 1.0
    Allowable increase in objective coefficient : Inf
    Allowable decrease in objective coefficient : 0.1
  Variable name : y
    Objective value coefficient : 1.0
    Reduced cost : -0.0
    Lower bound : 0.0
    Upper bound : Inf
    Optimal value : 1.0
    Allowable increase in objective coefficient : 0.1
    Allowable decrease in objective coefficient : 1.0
- Constraints sensitivity
  Constraint name : c1
    Right hand side (RHS) coefficient : 1.0
    Shadow price : -0.0
    RHS optimal value : 1.0
    Allowable increase in RHS coefficient  : Inf
    Allowable decrease in RHS coefficient  : Inf
  Constraint name : c2
    Right hand side (RHS) coefficient : 2.0
    Shadow price : 1.0
    RHS optimal value : 2.0
    Allowable increase in RHS coefficient  : Inf
    Allowable decrease in RHS coefficient  : 1.0

I'm not sure how to get slack or surplus value for each constraint.
If someone can advise me how to do it, I'd like to add it.

@codecov
Copy link

codecov bot commented Nov 6, 2021

Codecov Report

Merging #2794 (dc251a7) into master (37eac58) will increase coverage by 0.10%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2794      +/-   ##
==========================================
+ Coverage   94.26%   94.36%   +0.10%     
==========================================
  Files          43       43              
  Lines        5524     5576      +52     
==========================================
+ Hits         5207     5262      +55     
+ Misses        317      314       -3     
Impacted Files Coverage Δ
src/print.jl 94.50% <100.00%> (+1.24%) ⬆️
src/variables.jl 98.63% <0.00%> (+<0.01%) ⬆️
src/constraints.jl 95.58% <0.00%> (+0.02%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 37eac58...dc251a7. Read the comment docs.

@odow
Copy link
Member

odow commented Nov 6, 2021

Have you ever used Excel?

The reason for a table is that Excel has a sensitivity report function that is often used to teach students in a LP-101 class.

I expect this functionality to be used very rarely in JuMP outside the classroom, so we should put it in a separate function and reproduce exactly what teachers expect: https://www.solver.com/how-correctly-interpret-sensitivity-reports-premium-solver

image

It's also going to be slow, so we don't want to run this for large LPs, and it won't work for any other type of problem, which are other reasons for putting this in a separate function.

@AtsushiSakai
Copy link
Contributor Author

AtsushiSakai commented Nov 11, 2021

Have you ever used Excel?

Unfortunately, I have not used Excel sensitivity report.

so we should put it in a separate function and reproduce exactly what teachers expect:

OK. Do you mean a separate function means a separate print function?

@odow
Copy link
Member

odow commented Nov 11, 2021

We already have

"""
SensitivityReport
See [`lp_sensitivity_report`](@ref).
"""
struct SensitivityReport
rhs::Dict{ConstraintRef,Tuple{Float64,Float64}}
objective::Dict{VariableRef,Tuple{Float64,Float64}}
end
Base.getindex(s::SensitivityReport, c::ConstraintRef) = s.rhs[c]
Base.getindex(s::SensitivityReport, x::VariableRef) = s.objective[x]
"""
lp_sensitivity_report(model::Model; atol::Float64 = 1e-8)::SensitivityReport
Given a linear program `model` with a current optimal basis, return a
[`SensitivityReport`](@ref) object, which maps:
- Every variable reference to a tuple `(d_lo, d_hi)::Tuple{Float64,Float64}`,
explaining how much the objective coefficient of the corresponding variable
can change by, such that the original basis remains optimal.
- Every constraint reference to a tuple `(d_lo, d_hi)::Tuple{Float64,Float64}`,
explaining how much the right-hand side of the corresponding constraint can
change by, such that the basis remains optimal.
Both tuples are relative, rather than absolute. So given a objective coefficient
of `1.0` and a tuple `(-0.5, 0.5)`, the objective coefficient can range
between `1.0 - 0.5` an `1.0 + 0.5`.
`atol` is the primal/dual optimality tolerance, and should match the tolerance
of the solver used to compute the basis.
Note: interval constraints are NOT supported.
# Example
model = Model(GLPK.Optimizer)
@variable(model, -1 <= x <= 2)
@objective(model, Min, x)
optimize!(model)
report = lp_sensitivity_report(model; atol = 1e-7)
dx_lo, dx_hi = report[x]
println(
"The objective coefficient of `x` can decrease by \$dx_lo or " *
"increase by \$dx_hi."
)
c = LowerBoundRef(x)
dRHS_lo, dRHS_hi = report[c]
println(
"The lower bound of `x` can decrease by \$dRHS_lo or increase " *
"by \$dRHS_hi."
)
"""
function lp_sensitivity_report(model::Model; atol::Float64 = 1e-8)

So perhaps we should implement

Base.show(io::IO, report::SensitivityReport)

or

Base.print(io::IO, report::SenstivityReport)

I'm not sure whether we should print it to the screen, or print it as a CSV for people to open in Excel.

I've used this before: https://github.com/ronisbr/PrettyTables.jl. Not sure if we want it as a JuMP dependency though.

end

"""
solution_summary(model::Model; verbose::Bool = false)
solution_summary(model::Model; verbose::Bool = false, sensitivity = false)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
solution_summary(model::Model; verbose::Bool = false, sensitivity = false)
solution_summary(model::Model; verbose::Bool = false, lp_sensitivity = false)

since its only for LP

@odow
Copy link
Member

odow commented Feb 22, 2022

I'm going to close this as stale.

@AtsushiSakai thanks for taking a stab. It's always useful to see different design ideas, even if they don't end up being integrated. We're still interested in something like this, but it should be a separate print-out using the SensitivityReport, and be in the form of a table, not part of solution_summary.

@odow odow closed this Feb 22, 2022
@odow odow mentioned this pull request Feb 22, 2022
@odow
Copy link
Member

odow commented Feb 22, 2022

Ah, @AtsushiSakai I see you were following my suggestion in #2662. I guess that was bad advice. Apologies. 😄

@AtsushiSakai
Copy link
Contributor Author

I'm sorry I left this PR. It is OK to close this PR. I will try this again when I have time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

LP sensitivity reports
3 participants