From a2b5acb47e9cf711908525d98154fcb96f686bad Mon Sep 17 00:00:00 2001 From: d227nguyen Date: Wed, 23 Sep 2026 15:01:13 -0400 Subject: [PATCH] _raw_M -> compute_M --- ext/AbstractGPsDisjunctiveProgramming.jl | 2 +- ext/InfiniteDisjunctiveProgramming.jl | 4 +- src/mbm.jl | 50 ++++++++++--------- test/constraints/mbm.jl | 18 +++---- .../AbstractGPsDisjunctiveProgramming.jl | 40 +++++++-------- .../InfiniteDisjunctiveProgramming.jl | 40 +++++++-------- 6 files changed, 79 insertions(+), 75 deletions(-) diff --git a/ext/AbstractGPsDisjunctiveProgramming.jl b/ext/AbstractGPsDisjunctiveProgramming.jl index be83162..a819036 100644 --- a/ext/AbstractGPsDisjunctiveProgramming.jl +++ b/ext/AbstractGPsDisjunctiveProgramming.jl @@ -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 diff --git a/ext/InfiniteDisjunctiveProgramming.jl b/ext/InfiniteDisjunctiveProgramming.jl index 230b13b..2b9863c 100644 --- a/ext/InfiniteDisjunctiveProgramming.jl +++ b/ext/InfiniteDisjunctiveProgramming.jl @@ -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 @@ -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 diff --git a/src/mbm.jl b/src/mbm.jl index 8f74269..ce597e3 100644 --- a/src/mbm.jl +++ b/src/mbm.jl @@ -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, @@ -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 @@ -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 @@ -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. @@ -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. @@ -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 @@ -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 @@ -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 diff --git a/test/constraints/mbm.jl b/test/constraints/mbm.jl index 11f164d..ca20136 100644 --- a/test/constraints/mbm.jl +++ b/test/constraints/mbm.jl @@ -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) @@ -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( @@ -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( @@ -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) @@ -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 @@ -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() @@ -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() diff --git a/test/extensions/AbstractGPsDisjunctiveProgramming.jl b/test/extensions/AbstractGPsDisjunctiveProgramming.jl index a950262..a53912d 100644 --- a/test/extensions/AbstractGPsDisjunctiveProgramming.jl +++ b/test/extensions/AbstractGPsDisjunctiveProgramming.jl @@ -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)) @@ -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) @@ -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 @@ -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]) @@ -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) @@ -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(ξ)) @@ -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(ξ) @@ -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)) @@ -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)] @@ -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() @@ -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() diff --git a/test/extensions/InfiniteDisjunctiveProgramming.jl b/test/extensions/InfiniteDisjunctiveProgramming.jl index 82193c8..36ddefe 100644 --- a/test/extensions/InfiniteDisjunctiveProgramming.jl +++ b/test/extensions/InfiniteDisjunctiveProgramming.jl @@ -364,11 +364,11 @@ function test_logical_value() @test eltype(val) == Bool end -# raw_M against an InfiniteModel where M is constant across supports. +# compute_M against an InfiniteModel where M is constant across supports. # Setup: x(t) ∈ [0, 10], disj1: x ≥ 5, disj2: x ≤ 3. # For disj1 slack r(x) = 5 - x maximized over disj2's region x ∈ [0, 3]: # max(5 - x) = 5 at x = 0. Same at every support ⇒ scalar M = 5. -function test_raw_M_infinite_scalar() +function test_compute_M_infinite_scalar() model = InfiniteGDPModel() @infinite_parameter(model, t ∈ [0, 1], num_supports = 5) @variable(model, 0 <= x <= 10, Infinite(t)) @@ -382,14 +382,14 @@ function test_raw_M_infinite_scalar() obj = DP.prepare_max_M_objective( model, JuMP.constraint_object(con), sub) @test length(InfiniteOpt.parameter_refs(obj)) == 1 - @test DP.raw_M(sub, obj, mbm) == 5.0 + @test DP.compute_M(sub, obj, mbm) == 5.0 end -# raw_M with a support-varying M. Setup: x(t) ∈ [0, 10], disj1: x ≤ 2t, +# compute_M with a support-varying M. Setup: x(t) ∈ [0, 10], disj1: x ≤ 2t, # disj2: x ≥ 0.5. Slack r(x) = x - 2t maximized over x ∈ [0.5, 10]: -# max(x - 2t) = 10 - 2t. Varies with t ⇒ raw_M returns a pfunc whose +# max(x - 2t) = 10 - 2t. Varies with t ⇒ compute_M returns a pfunc whose # raw values at supports are max-of-cell upper bounds for 10 - 2t. -function test_raw_M_infinite_param_function() +function test_compute_M_infinite_param_function() model = InfiniteGDPModel() supports = [0.0, 0.25, 0.5, 0.75, 1.0] @infinite_parameter(model, t ∈ [0, 1], supports = supports) @@ -404,7 +404,7 @@ function test_raw_M_infinite_param_function() 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) # max-of-corners is conservative: raw_fn(t) ≥ 10 - 2t at supports. @@ -413,13 +413,13 @@ function test_raw_M_infinite_param_function() end end -# raw_M over two infinite parameters with different support counts. +# compute_M over two infinite parameters with different support counts. # Transcription orders the objective dimensions by parameter group, # which need not be the ascending order of the grids, so the M values # must be permuted to line up. Setup: x(t, s) in [0, 10], # disj1: x <= t + s, disj2: x >= 0.5. Slack r(x) = x - t - s # maximized over x in [0.5, 10]: 10 - t - s. -function test_raw_M_infinite_two_params() +function test_compute_M_infinite_two_params() model = InfiniteGDPModel() @infinite_parameter(model, t ∈ [0, 1], supports = [0.0, 0.5, 1.0]) @infinite_parameter(model, s ∈ [0, 1], supports = [0.0, 1.0]) @@ -433,7 +433,7 @@ function test_raw_M_infinite_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) for t_val in [0.0, 0.5, 1.0], s_val in [0.0, 1.0] @@ -443,8 +443,8 @@ end # Dependent parameters with a uniform M short-circuit to a scalar # before any support grid is needed. Setup as in -# test_raw_M_infinite_scalar, over a dependent parameter array. -function test_raw_M_infinite_dependent_params() +# test_compute_M_infinite_scalar, over a dependent parameter array. +function test_compute_M_infinite_dependent_params() model = InfiniteGDPModel() @infinite_parameter(model, ξ[1:2] ∈ [0, 1], num_supports = 4) @variable(model, 0 <= x <= 10, Infinite(ξ)) @@ -457,14 +457,14 @@ function test_raw_M_infinite_dependent_params() 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 # Dependent parameters with M varying over the joint supports: the # parameter function looks M up at each joint support and falls back # to the conservative max off-support. Setup: x(ξ) in [0, 10], # disj1: x <= ξ[1] + ξ[2], disj2: x >= 0.5, so M(ξ) = 10 - ξ1 - ξ2. -function test_raw_M_infinite_dependent_varying() +function test_compute_M_infinite_dependent_varying() model = InfiniteGDPModel() @infinite_parameter(model, ξ[1:2] ∈ [0, 1], num_supports = 4) @variable(model, 0 <= x <= 10, Infinite(ξ)) @@ -477,7 +477,7 @@ function test_raw_M_infinite_dependent_varying() 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(ξ) @@ -1188,11 +1188,11 @@ end @testset "MBM" begin test_interpolate() - test_raw_M_infinite_scalar() - test_raw_M_infinite_param_function() - test_raw_M_infinite_two_params() - test_raw_M_infinite_dependent_params() - test_raw_M_infinite_dependent_varying() + test_compute_M_infinite_scalar() + test_compute_M_infinite_param_function() + test_compute_M_infinite_two_params() + test_compute_M_infinite_dependent_params() + test_compute_M_infinite_dependent_varying() test_mbm_finite_and_integer_var() test_mbm_infinite_simple() test_mbm_infinite_param_dependent()