Skip to content

MBM: optional GP-based M sampling on InfiniteGDP models - #139

Open
dnguyen227 wants to merge 10 commits into
infiniteopt:masterfrom
dnguyen227:mbm_gp
Open

MBM: optional GP-based M sampling on InfiniteGDP models #139
dnguyen227 wants to merge 10 commits into
infiniteopt:masterfrom
dnguyen227:mbm_gp

Conversation

@dnguyen227

Copy link
Copy Markdown
Contributor

Implementation of MBM

using AbstractGPs 

optimize!(model, gdp_method = MBM(HiGHS.Optimizer)) 

MBM(HiGHS.Optimizer, M_sampler = :exact)                        # every support, as before
MBM(HiGHS.Optimizer, M_sampler = GPSampler(budget = 0.1))       # solve 10% of supports
MBM(HiGHS.Optimizer, M_sampler = GPSampler(kappa = 4.0))        # more conservative M

Not sure how to work around needing to make a new extension.

@codecov

codecov Bot commented Aug 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.54%. Comparing base (a4ebe8a) to head (081e722).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #139      +/-   ##
==========================================
+ Coverage   99.51%   99.54%   +0.02%     
==========================================
  Files          17       19       +2     
  Lines        2061     2178     +117     
==========================================
+ Hits         2051     2168     +117     
  Misses         10       10              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@dnguyen227

Copy link
Copy Markdown
Contributor Author

@pulsipher ready for review

@pulsipher pulsipher left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is better, but I think it can be made less complicated.

Comment thread src/datatypes.jl
"""
struct GPSampler
# Typed Any so base needs no AbstractGPs dependency
kernel::Any

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should use parametric type

Comment thread src/datatypes.jl
optimizer::O
default_M::T

sampler::Any

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should use parametric type

Comment thread src/datatypes.jl
optimizer::O
M::Dict{LogicalVariableRef{M}, Any}
default_M::T
sampler::Any

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be parametric type

Comment thread src/extension_api.jl
"""
function sample_M_values(sampler, objectives, sub, method, support_grids)
error("Unrecognized `sampler` value `$(repr(sampler))` for MBM " *
"on an infinite model. Use `nothing` to solve an M " *

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of nothing let's have a type. We should have an abstract type of AbstractMBMSampler with a default of ExhaustiveSampler() instead of nothing.

Comment on lines +41 to +101
# Bound info for parameter refs in disjunct constraints: parameter
# functions report their support extrema, finite parameters a point,
# other parameters (-Inf, Inf). Returns nothing for variables.
function _parameter_bound_info(
vref::InfiniteOpt.GeneralVariableRef
)::Union{Nothing, Tuple{Float64, Float64}}
_is_parameter(vref) || return nothing
dvref = InfiniteOpt.dispatch_variable_ref(vref)
if dvref isa InfiniteOpt.ParameterFunctionRef
prefs = InfiniteOpt.parameter_list(dvref)
length(prefs) == 1 || return (-Inf, Inf)
supports = InfiniteOpt.supports(first(prefs))
isempty(supports) && return (-Inf, Inf)
func = InfiniteOpt.raw_function(dvref)
func_values = [func(s) for s in supports]
return (minimum(func_values), maximum(func_values))
elseif dvref isa InfiniteOpt.FiniteParameterRef
value = InfiniteOpt.parameter_value(dvref)
return (value, value)
end
return (-Inf, Inf)
end

function DP.set_variable_bound_info(
vref::InfiniteOpt.GeneralVariableRef, ::DP.BigM)
info = _parameter_bound_info(vref)
info === nothing || return info
lb = JuMP.has_lower_bound(vref) ? JuMP.lower_bound(vref) : -Inf
ub = JuMP.has_upper_bound(vref) ? JuMP.upper_bound(vref) : Inf
return lb, ub
end

# Hull and PSplit require finite bounds that include 0
function _zero_inclusive_bounds(
vref::InfiniteOpt.GeneralVariableRef,
info::Union{Nothing, Tuple{Float64, Float64}},
method_name::String
)::Tuple{Float64, Float64}
info === nothing || return (min(0, info[1]), max(0, info[2]))
if !JuMP.has_lower_bound(vref) || !JuMP.has_upper_bound(vref)
error("Variable $vref must have both lower and upper " *
"bounds defined when using the $method_name " *
"reformulation.")
end
return (min(0, JuMP.lower_bound(vref)),
max(0, JuMP.upper_bound(vref)))
end

function DP.set_variable_bound_info(
vref::InfiniteOpt.GeneralVariableRef, ::DP.Hull)
return _zero_inclusive_bounds(vref,
_parameter_bound_info(vref), "Hull")
end

function DP.set_variable_bound_info(
vref::InfiniteOpt.GeneralVariableRef,
::Union{DP.PSplit, DP._PSplit})
return _zero_inclusive_bounds(vref,
_parameter_bound_info(vref), "PSplit")
end

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this specific to this PR on MBM?

# the supports, so it is deferred until sample_M_values needs it.
function _support_grids(
sub::DP.GDPSubmodel, mini_expr::JuMP.AbstractJuMPScalar)
reverse_map = Dict(ws[1] => v for (v, ws) in sub.fwd_map)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are simpler ways to check, like if the model even has any dependent parameters

# 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(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we call this raw_M?

)
M_vals = Array{Float64}(undef, size(objectives))
for I in eachindex(objectives)
m = DP.raw_M(sub, objectives[I], method)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this make raw_M recursive?

objectives::AbstractArray,
sub::DP.GDPSubmodel,
method::DP._MBM,
support_grids::Function

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why this argument is a function or why it needs to be an argument

Comment thread src/datatypes.jl
julia> method = MBM(HiGHS.Optimizer, sampler = GPSampler(kappa = 4.0))
```
"""
struct GPSampler

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this makes sense to have in datatypes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants