MBM: optional GP-based M sampling on InfiniteGDP models - #139
MBM: optional GP-based M sampling on InfiniteGDP models #139dnguyen227 wants to merge 10 commits into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 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. 🚀 New features to boost your workflow:
|
|
@pulsipher ready for review |
pulsipher
left a comment
There was a problem hiding this comment.
This is better, but I think it can be made less complicated.
| """ | ||
| struct GPSampler | ||
| # Typed Any so base needs no AbstractGPs dependency | ||
| kernel::Any |
There was a problem hiding this comment.
should use parametric type
| optimizer::O | ||
| default_M::T | ||
|
|
||
| sampler::Any |
There was a problem hiding this comment.
should use parametric type
| optimizer::O | ||
| M::Dict{LogicalVariableRef{M}, Any} | ||
| default_M::T | ||
| sampler::Any |
There was a problem hiding this comment.
should be parametric type
| """ | ||
| 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 " * |
There was a problem hiding this comment.
Instead of nothing let's have a type. We should have an abstract type of AbstractMBMSampler with a default of ExhaustiveSampler() instead of nothing.
| # 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 | ||
|
|
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Does this make raw_M recursive?
| objectives::AbstractArray, | ||
| sub::DP.GDPSubmodel, | ||
| method::DP._MBM, | ||
| support_grids::Function |
There was a problem hiding this comment.
I don't see why this argument is a function or why it needs to be an argument
| julia> method = MBM(HiGHS.Optimizer, sampler = GPSampler(kappa = 4.0)) | ||
| ``` | ||
| """ | ||
| struct GPSampler |
There was a problem hiding this comment.
I don't think this makes sense to have in datatypes
Implementation of MBM
Not sure how to work around needing to make a new extension.