I am seeking methods to identify the basis and non-basis matrices in HiGHS, or determine the status of variables and columns within the basis and non-basis. Additionally, I am curious about accessing the final simplex tableau of a model that I have read through an MPS file.
Hi @mohammad_amini, welcome to the forum.
This is something that we’re currently missing documentation for in JuMP (it’s a fairly rare request). I’ve posted an issue to improve: Suggestions for documentation improvements · Issue #2348 · jump-dev/JuMP.jl · GitHub
For now, you can do something like:
julia> using JuMP, HiGHS
julia> model = direct_model(HiGHS.Optimizer())
A JuMP Model
Feasibility problem with:
Variables: 0
Model mode: DIRECT
Solver name: HiGHS
julia> @variable(model, 0 <= x[1:2] <= 1)
Running HiGHS 1.6.0: Copyright (c) 2023 HiGHS under MIT licence terms
2-element Vector{VariableRef}:
x[1]
x[2]
julia> @constraint(model, c, sum(x) == 1)
c : x[1] + x[2] = 1
julia> @objective(model, Max, sum(x))
x[1] + x[2]
julia> optimize!(model)
Presolving model
0 rows, 0 cols, 0 nonzeros
0 rows, 0 cols, 0 nonzeros
Presolve : Reductions: rows 0(-1); columns 0(-2); elements 0(-2) - Reduced to empty
Solving the original LP from the solution after postsolve
Model status : Optimal
Objective value : 1.0000000000e+00
HiGHS run time : 0.00
julia> v_stat = Dict(
xi => MOI.get(model, MOI.VariableBasisStatus(), xi)
for xi in all_variables(model)
)
Dict{VariableRef, MathOptInterface.BasisStatusCode} with 2 entries:
x[2] => BASIC
x[1] => NONBASIC_AT_LOWER
julia> c_stat = Dict(
ci => MOI.get(model, MOI.ConstraintBasisStatus(), ci)
for ci in all_constraints(model; include_variable_in_set_constraints = false)
)
Dict{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.EqualTo{Float64}}, ScalarShape}, MathOptInterface.BasisStatusCode} with 1 entry:
c : x[1] + x[2] = 1 => NONBASIC
julia> v_stat[x[1]]
NONBASIC_AT_LOWER::BasisStatusCode = 2
julia> v_stat[x[2]]
BASIC::BasisStatusCode = 0
julia> c_stat[c]
NONBASIC::BasisStatusCode = 1
julia> basic_vars = [xi for (xi, stat) in v_stat if stat == MOI.BASIC]
1-element Vector{VariableRef}:
x[2]
I started to add a tutorial for this: [docs] add tutorial on querying the basis status by odow · Pull Request #3675 · jump-dev/JuMP.jl · GitHub. Not really sure what topics to cover/what would be useful, etc, so feedback appreciated.
Thank you so much.
No problem. What, exactly, do you want to accomplish with the basis? What documentation would be useful?
The issue I’m encountering pertains to identifying basis and non-basis variables, along with their corresponding matrices, within the final tableau. The challenge arises specifically in the context of degenerate problems. Essentially, it seems this function only account for non-zero variables whose values do not match their lower and upper bounds, But I need to find some variables that their value is zero, or match with their lower or upper bound but we have them in the basis matrix.
Essentially, it seems this function
Which function?
A degenerate variable that has value 0
and is in the basis will still have a MOI.BASIC
status code.
As one example:
julia> using JuMP, HiGHS
julia> function is_degenerate(x)
if get_attribute(x, MOI.VariableBasisStatus()) == MOI.BASIC
v = value(x)
return (has_lower_bound(x) && ≈(v, lower_bound(x))) ||
(has_upper_bound(x) && ≈(v, upper_bound(x)))
end
return false
end
is_degenerate (generic function with 1 method)
julia> A, b, c = [1 1; 0 1], [1, 1], [1, 1]
([1 1; 0 1], [1, 1], [1, 1])
julia> model = Model(HiGHS.Optimizer);
julia> set_silent(model)
julia> @variable(model, x[1:2] >= 0)
2-element Vector{VariableRef}:
x[1]
x[2]
julia> @objective(model, Min, c' * x)
x[1] + x[2]
julia> @constraint(model, A * x == b)
[x[1] + x[2] - 1, x[2] - 1] ∈ MathOptInterface.Zeros(2)
julia> optimize!(model)
julia> degenerate_variables = filter(is_degenerate, all_variables(model))
1-element Vector{VariableRef}:
x[1]
Edit: I added a section to the docs: [docs] add tutorial on querying the basis status by odow · Pull Request #3675 · jump-dev/JuMP.jl · GitHub
With this code also I can not find degenerate basic variables for the below model (obviously this model is degenerate in 0,1):
NAME example
ROWS
N OBJ
L R114
L R115
L R116
COLUMNS
MARKER ‘MARKER’ ‘INTORG’
C157 OBJ 3
C157 R114 1
C157 R115 -2
C157 R116 -1
C158 OBJ 2
C158 R114 -2
C158 R115 1
C158 R116 -1
MARKER ‘MARKER’ ‘INTEND’
RHS
RHS1 OBJ 0
RHS1 R114 1
RHS1 R115 1
RHS1 R116 -1
BOUNDS
ENDATA
To clarify, this is your problem?
julia> using JuMP, HiGHS
julia> A, b, c = [1 -2; -2 1; -1 -1], [1, 1, -1], [3, 2]
([1 -2; -2 1; -1 -1], [1, 1, -1], [3, 2])
julia> model = Model(HiGHS.Optimizer)
A JuMP Model
Feasibility problem with:
Variables: 0
Model mode: AUTOMATIC
CachingOptimizer state: EMPTY_OPTIMIZER
Solver name: HiGHS
julia> @variable(model, x[1:2] >= 0, Int)
2-element Vector{VariableRef}:
x[1]
x[2]
julia> cons = @constraint(model, A * x .<= b)
3-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
x[1] - 2 x[2] ≤ 1
-2 x[1] + x[2] ≤ 1
-x[1] - x[2] ≤ -1
julia> optimize!(model)
Running HiGHS 1.6.0: Copyright (c) 2023 HiGHS under MIT licence terms
Presolving model
3 rows, 2 cols, 6 nonzeros
3 rows, 2 cols, 6 nonzeros
Objective function is integral with scale 1
Solving MIP model with:
3 rows
2 cols (0 binary, 2 integer, 0 implied int., 0 continuous)
6 nonzeros
Nodes | B&B Tree | Objective Bounds | Dynamic Constraints | Work
Proc. InQueue | Leaves Expl. | BestBound BestSol Gap | Cuts InLp Confl. | LpIters Time
0 0 0 0.00% 0 inf inf 0 0 0 0 0.0s
Solving report
Status Optimal
Primal bound 0
Dual bound 0
Gap 0% (tolerance: 0.01%)
Solution status feasible
0 (objective)
0 (bound viol.)
0 (int. viol.)
0 (row viol.)
Timing 0.00 (total)
0.00 (presolve)
0.00 (postsolve)
Nodes 1
LP iterations 1 (total)
0 (strong br.)
0 (separation)
0 (heuristics)
julia> get_attribute.(x, MOI.VariableBasisStatus())
2-element Vector{MathOptInterface.BasisStatusCode}:
NONBASIC_AT_LOWER::BasisStatusCode = 2
NONBASIC_AT_LOWER::BasisStatusCode = 2
julia> get_attribute.(cons, MOI.ConstraintBasisStatus())
3-element Vector{MathOptInterface.BasisStatusCode}:
BASIC::BasisStatusCode = 0
BASIC::BasisStatusCode = 0
BASIC::BasisStatusCode = 0
I assume the problem is because of the Int
variables. I don’t know what basis HiGHS returns if it solves an integer problem.
If you drop the integrality, you get:
julia> get_attribute.(x, MOI.VariableBasisStatus())
2-element Vector{MathOptInterface.BasisStatusCode}:
NONBASIC_AT_LOWER::BasisStatusCode = 2
BASIC::BasisStatusCode = 0
julia> get_attribute.(cons, MOI.ConstraintBasisStatus())
3-element Vector{MathOptInterface.BasisStatusCode}:
BASIC::BasisStatusCode = 0
BASIC::BasisStatusCode = 0
NONBASIC::BasisStatusCode = 1
Thank you. The output is different. After running your code and dropping integrality, output of get_attribute.(x, MOI.VariableBasisStatus()) is:
NONBASIC_AT_LOWER::BasisStatusCode = 2
NONBASIC_AT_LOWER::BasisStatusCode = 2
And output for get_attribute.(cons, MOI.ConstraintBasisStatus()) is:
3-element Vector{MathOptInterface.BasisStatusCode}:
BASIC::BasisStatusCode = 0
BASIC::BasisStatusCode = 0
BASIC::BasisStatusCode = 0
What is the output of versioninfo()
and import Pkg; Pkg.status()
?
Do you have the most recent version of HiGHS? Could you post the log of the solver?
I think I resolved it. It was because of the HiGHS version. Is there any function to find slacks on the basis?
Julia version is 1.9.4
There is no explicit function for the slacks.
See this tutorial for some examples: Sensitivity analysis of a linear program · JuMP