Skip to content

Support Multiple Big-M reformulation. #120

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: master
Choose a base branch
from

Conversation

dnguyen227
Copy link

This PR adds the Multiple Big-M reformation technique. The technique can be called similar to BigM and Hull:

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

In src/mbm.jl are extensions for reformulate_disjunction, _reformulate_disjunct, and reformulate_disjunct_constraint functions that dispatch over the new MBM datatype.

Documentation to follow in another PR.

@dnguyen227 dnguyen227 marked this pull request as ready for review June 25, 2025 16:12
Copy link
Collaborator

@pulsipher pulsipher left a comment

Choose a reason for hiding this comment

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

Overall pretty good, please generalize to work with the InfiniteOpt extension and add this to the docs.

src/mbm.jl Outdated
method::MBM
) where {T,S <: Union{_MOI.LessThan, _MOI.GreaterThan}}
sub_model = JuMP.Model()
new_vars = Dict{JuMP.VariableRef, JuMP.VariableRef}()
Copy link
Author

Choose a reason for hiding this comment

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

JuMP.Variable_ref_type ->get the type -> use that type

Comment on lines +28 to +48
function _copy_variable(
target_model::JuMP.AbstractModel,
original_var::JuMP.AbstractVariableRef,
)
# Create new variable
new_var = JuMP.@variable(target_model, base_name = JuMP.name(original_var))

# Copy all properties from original variable
JuMP.has_lower_bound(original_var) && JuMP.set_lower_bound(new_var, JuMP.lower_bound(original_var))
JuMP.has_upper_bound(original_var) && JuMP.set_upper_bound(new_var, JuMP.upper_bound(original_var))
JuMP.has_start_value(original_var) && JuMP.set_start_value(new_var, JuMP.start_value(original_var))
JuMP.is_integer(original_var) && JuMP.set_integer(new_var)
JuMP.is_binary(original_var) && JuMP.set_binary(new_var)

# Handle fixed values with force=true (as in original MBM code)
if JuMP.is_fixed(original_var)
JuMP.fix(new_var, JuMP.fix_value(original_var); force=true)
end

return new_var
end
Copy link
Author

Choose a reason for hiding this comment

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

This new function differs from make_disaggregated_variable(). The main difference is that instead of passing the variable attributes, I instead pass the variable itself and check for the attributes within this new function.

Why?
The alternative to this would be to have all attributes as possible inputs and default them to nothing, but then the function would still need to check if the value is 'nothing'.
For example:

function _copy_variable(
    target_model::JuMP.AbstractModel,
    original_var::JuMP.AbstractVariableRef;
    base_name::Union{String, Nothing} = nothing,
    lower_bound::Union{Float64, Nothing} = nothing,
    upper_bound::Union{Float64, Nothing} = nothing,
    start_value::Union{Float64, Nothing} = nothing,
    is_integer::Union{Bool, Nothing} = nothing,
    is_binary::Union{Bool, Nothing} = nothing,
    fixed_value::Union{Float64, Nothing} = nothing
    )
    
    # Create new variable with base name
    var_name = base_name !== nothing ? base_name : JuMP.name(original_var)
    new_var = JuMP.@variable(target_model, base_name = var_name)

    # Set attributes if provided
    lower_bound !== nothing && JuMP.set_lower_bound(new_var, lower_bound)
    upper_bound !== nothing && JuMP.set_upper_bound(new_var, upper_bound)
    start_value !== nothing && JuMP.set_start_value(new_var, start_value)
    is_integer !== nothing && is_integer && JuMP.set_integer(new_var)
    is_binary !== nothing && is_binary && JuMP.set_binary(new_var)
    
    # Handle fixed value with force=true
    if fixed_value !== nothing
        JuMP.fix(new_var, fixed_value; force=true)
    end

    return new_var
end

This involved two checks, once before passing to the function, then one inside the function. So I thought just passing the variable to limit it to one check was better.

Let me know if there is a better way to do this, especially if I can make more akin to make_disaggregated_variable.

VariableRef changed to AbstractVariableRef.
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