Skip to content

Commit 1c4efbd

Browse files
authored
Merge pull request #678 from EdvinAblad/master
Add tests for get basis status for variables and constraints
2 parents 2402c51 + ab97226 commit 1c4efbd

File tree

8 files changed

+255
-30
lines changed

8 files changed

+255
-30
lines changed

src/Bridges/intervalbridge.jl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,30 @@ function MOI.get(model::MOI.ModelLike, a::MOI.ConstraintDual, c::SplitIntervalBr
4848
return lower_dual > -upper_dual ? lower_dual : upper_dual
4949
end
5050

51+
function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintBasisStatus, c::SplitIntervalBridge)
52+
lower_stat = MOI.get(model, MOI.ConstraintBasisStatus(), c.lower)
53+
upper_stat = MOI.get(model, MOI.ConstraintBasisStatus(), c.upper)
54+
if lower_stat == MOI.NONBASIC_AT_LOWER
55+
Compat.@warn("GreaterThan constraints should not have basis status:" *
56+
" NONBASIC_AT_LOWER, instead use NONBASIC.")
57+
end
58+
if upper_stat == MOI.NONBASIC_AT_UPPER
59+
Compat.@warn("LessThan constraints should not have basis status:" *
60+
" NONBASIC_AT_UPPER, instead use NONBASIC.")
61+
end
62+
if lower_stat == MOI.NONBASIC
63+
return MOI.NONBASIC_AT_LOWER
64+
end
65+
if upper_stat == MOI.NONBASIC
66+
return MOI.NONBASIC_AT_UPPER
67+
end
68+
if lower_stat != upper_stat
69+
Compat.@warn("Basis status of lower (`$lower_stat`) and upper (`$upper_stat`) constraint are inconsistent," *
70+
" both should be basic or super basic.")
71+
end
72+
return lower_stat
73+
end
74+
5175
# Constraints
5276
function MOI.modify(model::MOI.ModelLike, c::SplitIntervalBridge, change::MOI.AbstractFunctionModification)
5377
MOI.modify(model, c.lower, change)

src/Bridges/slackbridge.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ function MOI.get(model::MOI.ModelLike, a::MOI.ConstraintDual, c::ScalarSlackBrid
7171
# equal and we can return either one of them.
7272
return MOI.get(model, a, c.slack_in_set)
7373
end
74+
function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintBasisStatus, c::ScalarSlackBridge)
75+
MOI.get(model, MOI.ConstraintBasisStatus(), c.slack_in_set)
76+
end
7477

7578
# Constraints
7679
function MOI.modify(model::MOI.ModelLike, c::ScalarSlackBridge, change::MOI.AbstractFunctionModification)

src/Test/UnitTests/variables.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
### Functionality not yet tested
1414
- VariablePrimalStart
1515
- VariablePrimal
16-
- VariableBasisStatus
1716
- ListOfVariableIndices
1817
=#
1918

src/Test/config.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ struct TestConfig
99
# The expected "optimal" status returned by the solver. Either
1010
# MOI.OPTIMAL or MOI.LOCALLY_SOLVED.
1111
optimal_status::MOI.TerminationStatusCode
12+
basis::Bool # can get variable and constraint basis status
1213
function TestConfig(;atol::Float64 = 1e-8, rtol::Float64 = 1e-8, solve::Bool = true, query::Bool = true,
1314
modify_lhs::Bool = true, duals::Bool = true, infeas_certificates::Bool = true,
14-
optimal_status = MOI.OPTIMAL)
15+
optimal_status = MOI.OPTIMAL, basis::Bool = false)
1516
new(
1617
atol,
1718
rtol,
@@ -20,7 +21,8 @@ struct TestConfig
2021
modify_lhs,
2122
duals,
2223
infeas_certificates,
23-
optimal_status
24+
optimal_status,
25+
basis
2426
)
2527
end
2628
end

src/Test/contlinear.jl

Lines changed: 126 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,12 @@ function linear2test(model::MOI.ModelLike, config::TestConfig)
411411
@test MOI.get(model, MOI.ConstraintDual(), vc1) 0 atol=atol rtol=rtol
412412
@test MOI.get(model, MOI.ConstraintDual(), vc2) 1 atol=atol rtol=rtol
413413
end
414+
415+
if config.basis
416+
@test MOI.get(model, MOI.ConstraintBasisStatus(), vc1) == MOI.BASIC
417+
@test MOI.get(model, MOI.ConstraintBasisStatus(), vc2) == MOI.NONBASIC
418+
@test MOI.get(model, MOI.ConstraintBasisStatus(), c) == MOI.NONBASIC
419+
end
414420
end
415421
end
416422

@@ -436,9 +442,9 @@ function linear3test(model::MOI.ModelLike, config::TestConfig)
436442
x = MOI.add_variable(model)
437443
@test MOI.get(model, MOI.NumberOfVariables()) == 1
438444

439-
MOI.add_constraint(model, MOI.SingleVariable(x), MOI.GreaterThan(0.0))
445+
vc = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.GreaterThan(0.0))
440446
cf = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0)
441-
MOI.add_constraint(model, cf, MOI.GreaterThan(3.0))
447+
c = MOI.add_constraint(model, cf, MOI.GreaterThan(3.0))
442448

443449
@test MOI.get(model, MOI.NumberOfConstraints{MOI.SingleVariable,MOI.GreaterThan{Float64}}()) == 1
444450
@test MOI.get(model, MOI.NumberOfConstraints{MOI.ScalarAffineFunction{Float64},MOI.GreaterThan{Float64}}()) == 1
@@ -459,6 +465,11 @@ function linear3test(model::MOI.ModelLike, config::TestConfig)
459465
@test MOI.get(model, MOI.ObjectiveValue()) 3 atol=atol rtol=rtol
460466

461467
@test MOI.get(model, MOI.VariablePrimal(), x) 3 atol=atol rtol=rtol
468+
469+
if config.basis
470+
@test MOI.get(model, MOI.ConstraintBasisStatus(), vc) == MOI.BASIC
471+
@test MOI.get(model, MOI.ConstraintBasisStatus(), c) == MOI.NONBASIC
472+
end
462473
end
463474

464475
# max x
@@ -471,9 +482,9 @@ function linear3test(model::MOI.ModelLike, config::TestConfig)
471482
x = MOI.add_variable(model)
472483
@test MOI.get(model, MOI.NumberOfVariables()) == 1
473484

474-
MOI.add_constraint(model, MOI.SingleVariable(x), MOI.LessThan(0.0))
485+
vc = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.LessThan(0.0))
475486
cf = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0)
476-
MOI.add_constraint(model, cf, MOI.LessThan(3.0))
487+
c = MOI.add_constraint(model, cf, MOI.LessThan(3.0))
477488

478489
@test MOI.get(model, MOI.NumberOfConstraints{MOI.SingleVariable,MOI.LessThan{Float64}}()) == 1
479490
@test MOI.get(model, MOI.NumberOfConstraints{MOI.ScalarAffineFunction{Float64},MOI.LessThan{Float64}}()) == 1
@@ -496,6 +507,11 @@ function linear3test(model::MOI.ModelLike, config::TestConfig)
496507
@test MOI.get(model, MOI.ObjectiveValue()) 0 atol=atol rtol=rtol
497508

498509
@test MOI.get(model, MOI.VariablePrimal(), x) 0 atol=atol rtol=rtol
510+
511+
if config.basis
512+
@test MOI.get(model, MOI.ConstraintBasisStatus(), vc) == MOI.NONBASIC
513+
@test MOI.get(model, MOI.ConstraintBasisStatus(), c) == MOI.BASIC
514+
end
499515
end
500516
end
501517

@@ -1050,17 +1066,17 @@ function linear9test(model::MOI.ModelLike, config::TestConfig)
10501066
x = MOI.add_variable(model)
10511067
y = MOI.add_variable(model)
10521068

1053-
MOI.add_constraints(model,
1069+
vc12 = MOI.add_constraints(model,
10541070
[MOI.SingleVariable(x), MOI.SingleVariable(y)],
10551071
[MOI.GreaterThan(30.0), MOI.GreaterThan(0.0)]
10561072
)
10571073

1058-
MOI.add_constraints(model,
1074+
c1 = MOI.add_constraints(model,
10591075
[MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, -1.5], [x, y]), 0.0)],
10601076
[MOI.GreaterThan(0.0)]
10611077
)
10621078

1063-
MOI.add_constraints(model,
1079+
c23 = MOI.add_constraints(model,
10641080
[
10651081
MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([12.0, 8.0], [x, y]), 0.0),
10661082
MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1_000.0, 300.0], [x, y]), 0.0)
@@ -1086,6 +1102,14 @@ function linear9test(model::MOI.ModelLike, config::TestConfig)
10861102
@test MOI.get(model, MOI.ObjectiveValue()) 79e4/11 atol=atol rtol=rtol
10871103
@test MOI.get(model, MOI.VariablePrimal(), x) 650/11 atol=atol rtol=rtol
10881104
@test MOI.get(model, MOI.VariablePrimal(), y) 400/11 atol=atol rtol=rtol
1105+
1106+
if config.basis
1107+
@test MOI.get(model, MOI.ConstraintBasisStatus(), vc12[1]) == MOI.BASIC
1108+
@test MOI.get(model, MOI.ConstraintBasisStatus(), vc12[2]) == MOI.BASIC
1109+
@test MOI.get(model, MOI.ConstraintBasisStatus(), c1[1]) == MOI.BASIC
1110+
@test MOI.get(model, MOI.ConstraintBasisStatus(), c23[1]) == MOI.NONBASIC
1111+
@test MOI.get(model, MOI.ConstraintBasisStatus(), c23[2]) == MOI.NONBASIC
1112+
end
10891113
end
10901114
end
10911115

@@ -1110,7 +1134,7 @@ function linear10test(model::MOI.ModelLike, config::TestConfig)
11101134
x = MOI.add_variable(model)
11111135
y = MOI.add_variable(model)
11121136

1113-
MOI.add_constraints(model,
1137+
vc = MOI.add_constraints(model,
11141138
[MOI.SingleVariable(x), MOI.SingleVariable(y)],
11151139
[MOI.GreaterThan(0.0), MOI.GreaterThan(0.0)]
11161140
)
@@ -1135,6 +1159,13 @@ function linear10test(model::MOI.ModelLike, config::TestConfig)
11351159
@test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
11361160
@test MOI.get(model, MOI.ConstraintDual(), c) -1 atol=atol rtol=rtol
11371161
end
1162+
1163+
if config.basis
1164+
# There are multiple optimal bases. Either x or y can be in the optimal basis.
1165+
@test (MOI.get(model, MOI.ConstraintBasisStatus(), vc[1]) == MOI.BASIC ||
1166+
MOI.get(model, MOI.ConstraintBasisStatus(), vc[2])== MOI.BASIC)
1167+
@test MOI.get(model, MOI.ConstraintBasisStatus(), c) == MOI.NONBASIC_AT_UPPER
1168+
end
11381169
end
11391170

11401171
MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, 1.0], [x, y]), 0.0))
@@ -1153,6 +1184,13 @@ function linear10test(model::MOI.ModelLike, config::TestConfig)
11531184
@test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
11541185
@test MOI.get(model, MOI.ConstraintDual(), c) 1 atol=atol rtol=rtol
11551186
end
1187+
1188+
if config.basis
1189+
# There are multiple optimal bases. Either x or y can be in the optimal basis."
1190+
@test (MOI.get(model, MOI.ConstraintBasisStatus(), vc[1]) == MOI.BASIC ||
1191+
MOI.get(model, MOI.ConstraintBasisStatus(), vc[2])== MOI.BASIC)
1192+
@test MOI.get(model, MOI.ConstraintBasisStatus(), c) == MOI.NONBASIC_AT_LOWER
1193+
end
11561194
end
11571195

11581196
MOI.set(model, MOI.ConstraintSet(), c, MOI.Interval(2.0, 12.0))
@@ -1168,6 +1206,13 @@ function linear10test(model::MOI.ModelLike, config::TestConfig)
11681206
@test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
11691207
@test MOI.get(model, MOI.ObjectiveValue()) 2.0 atol=atol rtol=rtol
11701208
@test MOI.get(model, MOI.ConstraintPrimal(), c) 2 atol=atol rtol=rtol
1209+
1210+
if config.basis
1211+
# There are multiple optimal bases. Either x or y can be in the optimal basis.
1212+
@test (MOI.get(model, MOI.ConstraintBasisStatus(), vc[1]) == MOI.BASIC ||
1213+
MOI.get(model, MOI.ConstraintBasisStatus(), vc[2])== MOI.BASIC)
1214+
@test MOI.get(model, MOI.ConstraintBasisStatus(), c) == MOI.NONBASIC_AT_LOWER
1215+
end
11711216
end
11721217

11731218
MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, 1.0], [x, y]), 0.0))
@@ -1180,6 +1225,70 @@ function linear10test(model::MOI.ModelLike, config::TestConfig)
11801225
@test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
11811226
@test MOI.get(model, MOI.ObjectiveValue()) 12.0 atol=atol rtol=rtol
11821227
@test MOI.get(model, MOI.ConstraintPrimal(), c) 12 atol=atol rtol=rtol
1228+
1229+
if config.basis
1230+
# There are multiple optimal bases. Either x or y can be in the optimal basis.
1231+
@test (MOI.get(model, MOI.ConstraintBasisStatus(), vc[1]) == MOI.BASIC ||
1232+
MOI.get(model, MOI.ConstraintBasisStatus(), vc[2])== MOI.BASIC)
1233+
@test MOI.get(model, MOI.ConstraintBasisStatus(), c) == MOI.NONBASIC_AT_UPPER
1234+
end
1235+
end
1236+
end
1237+
1238+
# inactive ranged constraints
1239+
function linear10btest(model::MOI.ModelLike, config::TestConfig)
1240+
atol = config.atol
1241+
rtol = config.rtol
1242+
# minimize x + y
1243+
#
1244+
# s.t. -1 <= x + y <= 10
1245+
# x, y >= 0
1246+
1247+
@test MOIU.supports_default_copy_to(model, #=copy_names=# false)
1248+
@test MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}())
1249+
@test MOI.supports(model, MOI.ObjectiveSense())
1250+
@test MOI.supports_constraint(model, MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64})
1251+
@test MOI.supports_constraint(model, MOI.SingleVariable, MOI.GreaterThan{Float64})
1252+
1253+
MOI.empty!(model)
1254+
@test MOI.is_empty(model)
1255+
1256+
x = MOI.add_variable(model)
1257+
y = MOI.add_variable(model)
1258+
1259+
vc = MOI.add_constraints(model,
1260+
[MOI.SingleVariable(x), MOI.SingleVariable(y)],
1261+
[MOI.GreaterThan(0.0), MOI.GreaterThan(0.0)]
1262+
)
1263+
1264+
c = MOI.add_constraint(model, MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, 1.0], [x,y]), 0.0), MOI.Interval(-1.0, 10.0))
1265+
1266+
MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1.0, 1.0], [x, y]), 0.0))
1267+
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
1268+
1269+
if config.solve
1270+
@test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED
1271+
1272+
MOI.optimize!(model)
1273+
1274+
@test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status
1275+
@test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
1276+
@test MOI.get(model, MOI.ObjectiveValue()) 0.0 atol=atol rtol=rtol
1277+
@test MOI.get(model, MOI.ConstraintPrimal(), c) 0.0 atol=atol rtol=rtol
1278+
1279+
if config.duals
1280+
@test MOI.get(model, MOI.ResultCount()) >= 1
1281+
@test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT
1282+
@test MOI.get(model, MOI.ConstraintDual(), c) 0.0 atol=atol rtol=rtol
1283+
@test MOI.get(model, MOI.ConstraintDual(), vc[1]) 1.0 atol=atol rtol=rtol
1284+
@test MOI.get(model, MOI.ConstraintDual(), vc[2]) 1.0 atol=atol rtol=rtol
1285+
end
1286+
1287+
if config.basis
1288+
@test (MOI.get(model, MOI.ConstraintBasisStatus(), vc[1]) == MOI.NONBASIC)
1289+
@test (MOI.get(model, MOI.ConstraintBasisStatus(), vc[1]) == MOI.NONBASIC)
1290+
@test MOI.get(model, MOI.ConstraintBasisStatus(), c) == MOI.BASIC
1291+
end
11831292
end
11841293
end
11851294

@@ -1427,6 +1536,14 @@ function linear14test(model::MOI.ModelLike, config::TestConfig)
14271536
@test MOI.get(model, MOI.ConstraintDual(), clby) 0 atol=atol rtol=rtol
14281537
@test MOI.get(model, MOI.ConstraintDual(), clbz) 0 atol=atol rtol=rtol
14291538
@test MOI.get(model, MOI.ConstraintDual(), cubz) -2 atol=atol rtol=rtol
1539+
1540+
if config.basis
1541+
@test MOI.get(model, MOI.ConstraintBasisStatus(), clbx) == MOI.NONBASIC
1542+
@test MOI.get(model, MOI.ConstraintBasisStatus(), clby) == MOI.BASIC
1543+
@test MOI.get(model, MOI.ConstraintBasisStatus(), clbz) == MOI.BASIC
1544+
@test MOI.get(model, MOI.ConstraintBasisStatus(), cubz) == MOI.NONBASIC
1545+
@test MOI.get(model, MOI.ConstraintBasisStatus(), c) == MOI.NONBASIC
1546+
end
14301547
end
14311548
end
14321549

@@ -1551,6 +1668,7 @@ const contlineartests = Dict("linear1" => linear1test,
15511668
"linear8c" => linear8ctest,
15521669
"linear9" => linear9test,
15531670
"linear10" => linear10test,
1671+
"linear10b" => linear10btest,
15541672
"linear11" => linear11test,
15551673
"linear12" => linear12test,
15561674
"linear13" => linear13test,

0 commit comments

Comments
 (0)