Skip to content

Commit c82359e

Browse files
committed
Improve codecov
1 parent c5e98cc commit c82359e

File tree

2 files changed

+54
-123
lines changed

2 files changed

+54
-123
lines changed

src/feasibility.jl

Lines changed: 2 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -915,13 +915,9 @@ function _analyze_objectives!(
915915
end
916916

917917
###
918+
919+
# unsafe as is its checked upstream
918920
function _last_primal_solution(model::JuMP.GenericModel)
919-
if !JuMP.has_values(model)
920-
error(
921-
"No primal solution is available. You must provide a point at " *
922-
"which to check feasibility.",
923-
)
924-
end
925921
return Dict(v => JuMP.value(v) for v in JuMP.all_variables(model))
926922
end
927923

@@ -1145,123 +1141,6 @@ function _add_with_resize!(vec, val, i)
11451141
return vec[i] = val
11461142
end
11471143

1148-
"""
1149-
dual_feasibility_report(
1150-
point::Function,
1151-
model::GenericModel{T};
1152-
atol::T = zero(T),
1153-
skip_missing::Bool = false,
1154-
) where {T}
1155-
1156-
A form of `dual_feasibility_report` where a function is passed as the first
1157-
argument instead of a dictionary as the second argument.
1158-
1159-
## Example
1160-
1161-
```jldoctest
1162-
julia> model = Model();
1163-
1164-
julia> @variable(model, 0.5 <= x <= 1, start = 1.3); TODO
1165-
1166-
julia> dual_feasibility_report(model) do v
1167-
return dual_start_value(v)
1168-
end
1169-
Dict{Any, Float64} with 1 entry:
1170-
x ≤ 1 => 0.3 TODO
1171-
```
1172-
"""
1173-
# probablye remove this method
1174-
function dual_feasibility_report(
1175-
point::Function,
1176-
model::JuMP.GenericModel{T};
1177-
atol::T = zero(T),
1178-
skip_missing::Bool = false,
1179-
) where {T}
1180-
if JuMP.num_nonlinear_constraints(model) > 0
1181-
error(
1182-
"Nonlinear constraints are not supported. " *
1183-
"Use `dual_feasibility_report` instead.",
1184-
)
1185-
end
1186-
if !skip_missing
1187-
constraint_list = JuMP.all_constraints(
1188-
model;
1189-
include_variable_in_set_constraints = false,
1190-
)
1191-
for c in constraint_list
1192-
if !haskey(point, c)
1193-
error(
1194-
"point does not contain a dual for constraint $c. Provide " *
1195-
"a dual, or pass `skip_missing = true`.",
1196-
)
1197-
end
1198-
end
1199-
end
1200-
dual_model = _dualize2(model)
1201-
map = dual_model.ext[:dualization_primal_dual_map].primal_con_dual_var
1202-
1203-
dual_var_primal_con = _reverse_primal_con_dual_var_map(map)
1204-
1205-
function dual_point(jump_dual_var::JuMP.GenericVariableRef{T})
1206-
# v is a variable in the dual jump model
1207-
# we need the associated cosntraint in the primal jump model
1208-
moi_dual_var = JuMP.index(jump_dual_var)
1209-
moi_primal_con, i = dual_var_primal_con[moi_dual_var]
1210-
jump_primal_con = JuMP.constraint_ref_with_index(model, moi_primal_con)
1211-
pre_point = point(jump_primal_con)
1212-
if ismissing(pre_point)
1213-
if !skip_missing
1214-
error(
1215-
"point does not contain a dual for constraint $jump_primal_con. Provide " *
1216-
"a dual, or pass `skip_missing = true`.",
1217-
)
1218-
else
1219-
return missing
1220-
end
1221-
end
1222-
return point(jump_primal_con)[i]
1223-
end
1224-
1225-
dual_con_to_violation = JuMP.primal_feasibility_report(
1226-
dual_point,
1227-
dual_model;
1228-
atol = atol,
1229-
skip_missing = skip_missing,
1230-
)
1231-
1232-
# some dual model constraints are associated with primal model variables (primal_con_dual_var)
1233-
# if variable is free
1234-
primal_var_dual_con =
1235-
dual_model.ext[:dualization_primal_dual_map].primal_var_dual_con
1236-
# if variable is bounded
1237-
primal_convar_dual_con =
1238-
dual_model.ext[:dualization_primal_dual_map].constrained_var_dual
1239-
# other dual model constraints (bounds) are associated with primal model constraints (non-bounds)
1240-
primal_con_dual_con =
1241-
dual_model.ext[:dualization_primal_dual_map].primal_con_dual_con
1242-
1243-
dual_con_primal_all = _build_dual_con_primal_all(
1244-
primal_var_dual_con,
1245-
primal_convar_dual_con,
1246-
primal_con_dual_con,
1247-
)
1248-
1249-
ret = _fix_ret(dual_con_to_violation, model, dual_con_primal_all)
1250-
1251-
return ret
1252-
end
1253-
1254-
function _reverse_primal_con_dual_var_map(primal_con_dual_var)
1255-
dual_var_primal_con =
1256-
Dict{MOI.VariableIndex,Tuple{MOI.ConstraintIndex,Int}}()
1257-
for (moi_con, vec_vars) in primal_con_dual_var
1258-
for (i, moi_var) in enumerate(vec_vars)
1259-
dual_var_primal_con[moi_var] = (moi_con, i)
1260-
end
1261-
end
1262-
return dual_var_primal_con
1263-
end
1264-
12651144
function _dualize2(
12661145
model::JuMP.GenericModel,
12671146
optimizer_constructor = nothing;

test/feasibility.jl

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ function test_no_solution()
3333
@test_throws ErrorException ModelAnalyzer.Feasibility.dual_feasibility_report(
3434
model,
3535
)
36+
# non linear not accepted
37+
@constraint(model, c, x^4 >= 0) # this will make the model non-linear
38+
@test_throws ErrorException ModelAnalyzer.Feasibility.dual_feasibility_report(
39+
model,
40+
)
3641
end
3742

3843
function test_only_bounds()
@@ -46,6 +51,11 @@ function test_only_bounds()
4651
Dict(LowerBoundRef(x) => 1.0),
4752
)
4853
@test isempty(report)
54+
55+
buf = IOBuffer()
56+
Base.show(buf, report)
57+
str = String(take!(buf))
58+
@test str == "Feasibility analysis found 0 issues"
4959
end
5060

5161
function test_no_lb()
@@ -196,9 +206,18 @@ function test_analyse_no_opt()
196206
@constraint(model, c, x >= 0)
197207
@objective(model, Min, x)
198208

209+
# test no primal point
210+
@test_throws ErrorException ModelAnalyzer.analyze(
211+
ModelAnalyzer.Feasibility.Analyzer(),
212+
model,
213+
)
214+
215+
# test no dual point
199216
@test_throws ErrorException ModelAnalyzer.analyze(
200217
ModelAnalyzer.Feasibility.Analyzer(),
201218
model,
219+
primal_point = Dict(x => 1.0),
220+
dual_check = true,
202221
)
203222

204223
data = ModelAnalyzer.analyze(
@@ -267,6 +286,39 @@ function test_analyse_no_opt()
267286
return
268287
end
269288

289+
# these tests are harder to permorm with a real solver as they tipically
290+
# return coherent objectives
291+
function test_lowlevel_mismatch()
292+
buf = IOBuffer()
293+
issues = []
294+
push!(issues, ModelAnalyzer.Feasibility.PrimalObjectiveMismatch(0.0, 1.0))
295+
push!(issues, ModelAnalyzer.Feasibility.DualObjectiveMismatch(0.0, 1.0))
296+
push!(issues, ModelAnalyzer.Feasibility.PrimalDualSolverMismatch(0.0, 1.0))
297+
for verbose in (true, false)
298+
ModelAnalyzer.summarize(
299+
buf,
300+
ModelAnalyzer.Feasibility.PrimalObjectiveMismatch,
301+
verbose = verbose,
302+
)
303+
ModelAnalyzer.summarize(
304+
buf,
305+
ModelAnalyzer.Feasibility.DualObjectiveMismatch,
306+
verbose = verbose,
307+
)
308+
ModelAnalyzer.summarize(
309+
buf,
310+
ModelAnalyzer.Feasibility.PrimalDualSolverMismatch,
311+
verbose = verbose,
312+
)
313+
for issue in issues
314+
# ensure we can summarize each issue type
315+
ModelAnalyzer.summarize(buf, issue, verbose = verbose)
316+
end
317+
end
318+
319+
return
320+
end
321+
270322
end # module
271323

272324
TestDualFeasibilityChecker.runtests()

0 commit comments

Comments
 (0)