Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ext/AbstractGPsDisjunctiveProgramming.jl
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ function DP.sample_M_values(
n = length(indices)
solved = Dict{Int, Float64}()
solve_at(index::Int) = begin
M_val = DP.raw_M(sub, objectives[indices[index]], method)
M_val = DP.compute_M(sub, objectives[indices[index]], method)
M_val === nothing && return false
solved[index] = M_val
return true
Expand Down
4 changes: 2 additions & 2 deletions ext/InfiniteDisjunctiveProgramming.jl
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ function DP.sample_M_values(
)
M_vals = Array{Float64}(undef, size(objectives))
for I in eachindex(objectives)
m = DP.raw_M(sub, objectives[I], method)
m = DP.compute_M(sub, objectives[I], method)
m === nothing && return nothing
M_vals[I] = m
end
Expand All @@ -294,7 +294,7 @@ end
# Transcribe mini_expr, compute the per-support M values with the
# method's sampler, and aggregate to a scalar if uniform, else to a
# parameter function on main.
function DP.raw_M(
function DP.compute_M(
sub::DP.GDPSubmodel{<:InfiniteOpt.InfiniteModel},
mini_expr::JuMP.AbstractJuMPScalar,
method::DP._MBM
Expand Down
50 changes: 27 additions & 23 deletions src/mbm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ end
prepare_max_M_objective(model, obj::ScalarConstraint, sub::GDPSubmodel)

Convert a constraint into an objective expression for M-value
maximization. Returns a single JuMP expression to pass to `raw_M`.
maximization. Returns a single JuMP expression to pass to `compute_M`.
"""
function prepare_max_M_objective(
::JuMP.AbstractModel,
Expand All @@ -267,14 +267,14 @@ function prepare_max_M_objective(
end

"""
raw_M(sub::GDPSubmodel, objective, method::_MBM)
compute_M(sub::GDPSubmodel, objective, method::_MBM)

Maximize `objective` over `sub` to obtain one raw M value for MBM.
Maximize `objective` over `sub` to obtain one M value for MBM.
Returns `max(obj_value, 0)` on optimal, `nothing` on infeasible
(signals the constraint is redundant in the combined region), or
`method.default_M` otherwise (unbounded, numerical failure, etc).
"""
function raw_M(
function compute_M(
sub::GDPSubmodel{<:JuMP.AbstractModel},
objective::JuMP.AbstractJuMPScalar,
method::_MBM
Expand All @@ -299,7 +299,7 @@ function _maximize_M(
method::_MBM
) where {T, S <: Union{_MOI.LessThan, _MOI.GreaterThan}}
sub = _get_submodel(model, constraints, method)
return raw_M(sub,
return compute_M(sub,
prepare_max_M_objective(model, objective, sub), method)
end

Expand Down Expand Up @@ -329,11 +329,13 @@ function _maximize_M(
set_value = objective.set.value
ge_obj = JuMP.ScalarConstraint(objective.func, MOI.GreaterThan(set_value))
le_obj = JuMP.ScalarConstraint(objective.func, MOI.LessThan(set_value))
raw_lower = raw_M(sub, prepare_max_M_objective(model, ge_obj, sub), method)
raw_upper = raw_M(sub, prepare_max_M_objective(model, le_obj, sub), method)
(raw_lower === nothing || raw_upper === nothing) &&
lower_M = compute_M(sub,
prepare_max_M_objective(model, ge_obj, sub), method)
upper_M = compute_M(sub,
prepare_max_M_objective(model, le_obj, sub), method)
(lower_M === nothing || upper_M === nothing) &&
return nothing
return [raw_lower, raw_upper]
return [lower_M, upper_M]
end

# Interval: solve both lower and upper bound directions.
Expand All @@ -349,11 +351,13 @@ function _maximize_M(
MOI.GreaterThan(set_values[1]))
le_obj = JuMP.ScalarConstraint(objective.func,
MOI.LessThan(set_values[2]))
raw_lower = raw_M(sub, prepare_max_M_objective(model, ge_obj, sub), method)
raw_upper = raw_M(sub, prepare_max_M_objective(model, le_obj, sub), method)
(raw_lower === nothing || raw_upper === nothing) &&
lower_M = compute_M(sub,
prepare_max_M_objective(model, ge_obj, sub), method)
upper_M = compute_M(sub,
prepare_max_M_objective(model, le_obj, sub), method)
(lower_M === nothing || upper_M === nothing) &&
return nothing
return [raw_lower, raw_upper]
return [lower_M, upper_M]
end

# Nonpositives: per-row LessThan solves.
Expand All @@ -369,11 +373,11 @@ function _maximize_M(
for i in 1:objective.set.dimension
le_obj = JuMP.ScalarConstraint(
objective.func[i], MOI.LessThan(zero(val_type)))
raw = raw_M(sub,
M_value = compute_M(sub,
prepare_max_M_objective(model, le_obj, sub),
method)
raw === nothing && return nothing
push!(results, raw)
M_value === nothing && return nothing
push!(results, M_value)
end
return results
end
Expand All @@ -392,11 +396,11 @@ function _maximize_M(
ge_obj = JuMP.ScalarConstraint(
objective.func[i],
MOI.GreaterThan(zero(val_type)))
raw = raw_M(sub,
M_value = compute_M(sub,
prepare_max_M_objective(model, ge_obj, sub),
method)
raw === nothing && return nothing
push!(results, raw)
M_value === nothing && return nothing
push!(results, M_value)
end
return results
end
Expand All @@ -418,15 +422,15 @@ function _maximize_M(
le_obj = JuMP.ScalarConstraint(
objective.func[i],
MOI.LessThan(zero(val_type)))
raw_ge = raw_M(sub,
ge_M = compute_M(sub,
prepare_max_M_objective(model, ge_obj, sub),
method)
raw_le = raw_M(sub,
le_M = compute_M(sub,
prepare_max_M_objective(model, le_obj, sub),
method)
(raw_ge === nothing || raw_le === nothing) &&
(ge_M === nothing || le_M === nothing) &&
return nothing
push!(results, max(raw_ge, raw_le))
push!(results, max(ge_M, le_M))
end
return results
end
Expand Down
18 changes: 9 additions & 9 deletions test/constraints/mbm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ function test_prepare_max_M_objective()
1 - new_vars[x[2]][1])
end

function test_raw_M()
function test_compute_M()
model = GDPModel()
@variable(model, 0 <= x, start = 1)
@variable(model, 0 <= y)
Expand All @@ -134,19 +134,19 @@ function test_raw_M()
DisjunctConstraintRef[con2], mbm)
obj = DP.prepare_max_M_objective(model,
constraint_object(con), sub)
@test DP.raw_M(sub, obj, mbm) == 0.0
@test DP.compute_M(sub, obj, mbm) == 0.0
set_upper_bound(x, 1)
sub2 = DP.copy_model_with_constraints(model,
DisjunctConstraintRef[con], mbm)
obj2 = DP.prepare_max_M_objective(model,
constraint_object(con2), sub2)
@test DP.raw_M(sub2, obj2, mbm) == 15
@test DP.compute_M(sub2, obj2, mbm) == 15
set_integer(y)
@constraint(model, con3, y*x == 15,
Disjunct(Y[1]))
obj3 = DP.prepare_max_M_objective(model,
constraint_object(con2), sub2)
@test DP.raw_M(sub2, obj3, mbm) == 15
@test DP.compute_M(sub2, obj3, mbm) == 15
# Fresh _MBM after changing bounds
JuMP.fix(y, 5; force=true)
mbm2 = DP._MBM(
Expand All @@ -155,7 +155,7 @@ function test_raw_M()
DisjunctConstraintRef[con], mbm2)
obj4 = DP.prepare_max_M_objective(model,
constraint_object(con2), sub3)
@test DP.raw_M(sub3, obj4, mbm2) == 10
@test DP.compute_M(sub3, obj4, mbm2) == 10
# Infeasible region → nothing
delete_lower_bound(x)
mbm3 = DP._MBM(
Expand All @@ -164,7 +164,7 @@ function test_raw_M()
DisjunctConstraintRef[con2], mbm3)
obj5 = DP.prepare_max_M_objective(model,
constraint_object(con2), sub4)
@test DP.raw_M(sub4, obj5, mbm3) == nothing
@test DP.compute_M(sub4, obj5, mbm3) == nothing

# infeasible (x >= 100 but x <= 1)
set_upper_bound(x, 1)
Expand All @@ -175,7 +175,7 @@ function test_raw_M()
mbm4)
obj6 = DP.prepare_max_M_objective(model,
constraint_object(con), sub5)
@test DP.raw_M(sub5, obj6, mbm4) == nothing
@test DP.compute_M(sub5, obj6, mbm4) == nothing

# Unbounded subproblem → default_M fallback.
# No lower bound on x means max(5 - x) s.t. x <= 3
Expand All @@ -191,7 +191,7 @@ function test_raw_M()
DisjunctConstraintRef[ub_con1], mbm_ub)
obj_ub = DP.prepare_max_M_objective(model_ub,
constraint_object(ub_con2), sub_ub)
@test DP.raw_M(sub_ub, obj_ub, mbm_ub) == mbm_ub.default_M
@test DP.compute_M(sub_ub, obj_ub, mbm_ub) == mbm_ub.default_M
end

function test_maximize_M()
Expand Down Expand Up @@ -811,7 +811,7 @@ end
test__replace_variables_quad_numeric_map()
test_replace_variables_in_constraint()
test_prepare_max_M_objective()
test_raw_M()
test_compute_M()
test_maximize_M()
test_reformulate_disjunct_constraint()
test_reformulate_disjunct()
Expand Down
40 changes: 20 additions & 20 deletions test/extensions/AbstractGPsDisjunctiveProgramming.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ function test_gp_sampler_kwargs()
@test_throws ErrorException GPSampler(initial_supports = Float64[])
end

# Mirror of test_raw_M_infinite_scalar: uniform seed M values collapse
# Mirror of test_compute_M_infinite_scalar: uniform seed M values collapse
# to the exactly-solved scalar under the GP sampler
function test_gp_raw_M_scalar()
function test_gp_compute_M_scalar()
model = InfiniteGDPModel()
@infinite_parameter(model, t ∈ [0, 1], num_supports = 5)
@variable(model, 0 <= x <= 10, Infinite(t))
Expand All @@ -50,12 +50,12 @@ function test_gp_raw_M_scalar()
model, DP.DisjunctConstraintRef[con2], mbm)
obj = DP.prepare_max_M_objective(
model, JuMP.constraint_object(con), sub)
@test DP.raw_M(sub, obj, mbm) == 5.0
@test DP.compute_M(sub, obj, mbm) == 5.0
end

# With frac_supports = 1.0 every support is solved exactly, so the GP
# sampler must reproduce the exact grid parameter function
function test_gp_raw_M_matches_exact()
function test_gp_compute_M_matches_exact()
function pfunc_values(sampler, supports)
model = InfiniteGDPModel()
@infinite_parameter(model, t ∈ [0, 1], supports = supports)
Expand All @@ -71,7 +71,7 @@ function test_gp_raw_M_matches_exact()
model, DP.DisjunctConstraintRef[con2], mbm)
obj = DP.prepare_max_M_objective(
model, JuMP.constraint_object(con), sub)
M = DP.raw_M(sub, obj, mbm)
M = DP.compute_M(sub, obj, mbm)
@test M isa InfiniteOpt.GeneralVariableRef
return [InfiniteOpt.raw_function(M)(t_val) for t_val in supports]
end
Expand All @@ -92,8 +92,8 @@ end
# Two independent parameters: the GP path builds 2-D coordinates and
# fits a multivariate GP; with frac_supports = 1.0 every support is solved
# exactly, so the parameter function matches the exhaustive one. Setup
# as in test_raw_M_infinite_two_params: M(t, s) = 10 - t - s.
function test_gp_raw_M_two_params()
# as in test_compute_M_infinite_two_params: M(t, s) = 10 - t - s.
function test_gp_compute_M_two_params()
function pfunc_values(sampler)
model = InfiniteGDPModel()
@infinite_parameter(model, t ∈ [0, 1], supports = [0.0, 0.5, 1.0])
Expand All @@ -109,7 +109,7 @@ function test_gp_raw_M_two_params()
model, DP.DisjunctConstraintRef[con2], mbm)
obj = DP.prepare_max_M_objective(
model, JuMP.constraint_object(con), sub)
M = DP.raw_M(sub, obj, mbm)
M = DP.compute_M(sub, obj, mbm)
@test M isa InfiniteOpt.GeneralVariableRef
raw_fn = InfiniteOpt.raw_function(M)
return [raw_fn(t_val, s_val)
Expand All @@ -122,8 +122,8 @@ end
# Dependent parameters: the joint supports become the GP coordinates
# directly; with frac_supports = 1.0 every support is solved exactly, so the
# M values match the exhaustive ones. Setup as in
# test_raw_M_infinite_dependent_varying: M(ξ) = 10 - ξ[1] - ξ[2].
function test_gp_raw_M_dependent()
# test_compute_M_infinite_dependent_varying: M(ξ) = 10 - ξ[1] - ξ[2].
function test_gp_compute_M_dependent()
model = InfiniteGDPModel()
@infinite_parameter(model, ξ[1:2] ∈ [0, 1], num_supports = 6)
@variable(model, 0 <= x <= 10, Infinite(ξ))
Expand All @@ -137,7 +137,7 @@ function test_gp_raw_M_dependent()
model, DP.DisjunctConstraintRef[con2], mbm)
obj = DP.prepare_max_M_objective(
model, JuMP.constraint_object(con), sub)
M = DP.raw_M(sub, obj, mbm)
M = DP.compute_M(sub, obj, mbm)
@test M isa InfiniteOpt.GeneralVariableRef
raw_fn = InfiniteOpt.raw_function(M)
S = InfiniteOpt.supports(ξ)
Expand Down Expand Up @@ -233,7 +233,7 @@ end
# GP is fit and the unsolved supports keep their std_dev_margin * sd cushion,
# which must sit above the M that detection would have returned.
function test_gp_detect_uniform_M_off()
function raw_M_with(detect)
function compute_M_with(detect)
model = InfiniteGDPModel()
@infinite_parameter(model, t ∈ [0, 1], num_supports = 20)
@variable(model, 0 <= x <= 10, Infinite(t))
Expand All @@ -247,10 +247,10 @@ function test_gp_detect_uniform_M_off()
model, DP.DisjunctConstraintRef[con2], mbm)
obj = DP.prepare_max_M_objective(
model, JuMP.constraint_object(con), sub)
return DP.raw_M(sub, obj, mbm)
return DP.compute_M(sub, obj, mbm)
end
@test raw_M_with(true) == 5.0
M = raw_M_with(false)
@test compute_M_with(true) == 5.0
M = compute_M_with(false)
@test M isa InfiniteOpt.GeneralVariableRef
raw_fn = InfiniteOpt.raw_function(M)
vals = [raw_fn(t) for t in range(0, 1, length = 20)]
Expand All @@ -276,7 +276,7 @@ function test_gp_detect_uniform_M_off_dependent()
model, DP.DisjunctConstraintRef[con2], mbm)
obj = DP.prepare_max_M_objective(
model, JuMP.constraint_object(con), sub)
@test DP.raw_M(sub, obj, mbm) == 5.0
@test DP.compute_M(sub, obj, mbm) == 5.0
end

function test_gp_unknown_sampler_error()
Expand All @@ -299,10 +299,10 @@ end

@testset "AbstractGPsDisjunctiveProgramming" begin
test_gp_sampler_kwargs()
test_gp_raw_M_scalar()
test_gp_raw_M_matches_exact()
test_gp_raw_M_two_params()
test_gp_raw_M_dependent()
test_gp_compute_M_scalar()
test_gp_compute_M_matches_exact()
test_gp_compute_M_two_params()
test_gp_compute_M_dependent()
test_gp_mbm_solve_equivalence()
test_gp_periodic_M_seeds()
test_gp_detect_uniform_M_off()
Expand Down
Loading
Loading