Skip to content
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

Update to MOI v1.0 #188

Merged
merged 9 commits into from
Mar 15, 2022
Merged
Show file tree
Hide file tree
Changes from 8 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
17 changes: 11 additions & 6 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,32 @@ name = "Alpine"
uuid = "07493b3f-dabb-5b16-a503-4139292d7dd4"
authors = ["Harsha Nagarajan, Site Wang, Kaarthik Sundar and contributors"]
repo = "https://github.com/lanl-ansi/Alpine.jl.git"
version = "0.2.7"
version = "0.2.8"

[deps]
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[compat]
JuMP = "^0.21.0"
MathOptInterface = "^0.9.1, 0.10"
julia = "^1"
Cbc = "0.8, 0.9, 1"
GLPK = "0.14, 0.15, 1"
Ipopt = "0.8, 0.9, 1"
JuMP = "0.22, 0.23"
Juniper = "0.8, 0.9"
MathOptInterface = "0.10, 1"
Pavito = "0.3.4"
julia = "1"

[extras]
Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76"
GLPK = "60bf3e95-4087-53dc-ae20-288a0d20c6a6"
Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9"
Juniper = "2ddba703-00a4-53a7-87a5-e8b9971dde84"
Pavito = "cd433a01-47d1-575d-afb7-6db927ee8d8f"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Cbc", "GLPK", "Ipopt", "Juniper", "Pavito", "Test"]
test = ["Cbc", "GLPK", "Ipopt", "Juniper", "Pavito", "Random", "Test"]
5 changes: 2 additions & 3 deletions src/Alpine.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ module Alpine

using JuMP

import LinearAlgebra as LA
import Random
import LinearAlgebra: dot, Diagonal
import Statistics

const ALPINE_DEBUG = false
Expand All @@ -29,7 +28,7 @@ include("amp.jl")
include("embedding.jl")
include("heuristics.jl")

# Convexification
# Convexification
include("multi.jl")
include("tmc.jl")

Expand Down
44 changes: 21 additions & 23 deletions src/algorithm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ function run_bounding_iteration(m::Optimizer)
return
end

"""
"""
presolve(m::Optimizer)
"""
function presolve(m::Optimizer)
Expand All @@ -93,33 +93,33 @@ function presolve(m::Optimizer)
if m.status[:local_solve] in STATUS_OPT || m.status[:local_solve] in STATUS_LIMIT

Alp.get_option(m, :log_level) > 0 && println(" Local solver returns a feasible point with value $(round(m.best_obj, digits=4))")

bound_tightening(m, use_bound = true) # performs bound-tightening with the local solve objective value

Alp.get_option(m, :presolve_bt) && init_disc(m) # Re-initialize discretization dictionary on tight bounds

Alp.get_option(m, :disc_ratio_branch) && (Alp.set_option(m, :disc_ratio, update_disc_ratio(m, true)))

Alp.add_partition(m, use_solution=m.best_sol) # Setting up the initial discretization

elseif m.status[:local_solve] in STATUS_INF

(Alp.get_option(m, :log_level) > 0) && println(" Bound tightening without objective bounds (OBBT)")

bound_tightening(m, use_bound = false) # do bound tightening without objective value

(Alp.get_option(m, :disc_ratio_branch)) && (Alp.set_option(m, :disc_ratio, update_disc_ratio(m, true)))

Alp.get_option(m, :presolve_bt) && init_disc(m)

elseif m.status[:local_solve] == MOI.INVALID_MODEL

@warn " Warning: Presolve ends with local solver yielding $(m.status[:local_solve]). \n This may come from Ipopt's `:Not_Enough_Degrees_Of_Freedom`. \n Consider more replace equality constraints with >= and <= to resolve this."

else

@warn " Warning: Presolve ends with local solver yielding $(m.status[:local_solve])."

end

cputime_presolve = time() - start_presolve
Expand Down Expand Up @@ -185,8 +185,7 @@ function load_nonlinear_model(m::Optimizer, model::MOI.ModelLike, l_var, u_var)
for i in eachindex(x)
set = Alp._bound_set(l_var[i], u_var[i])
if set !== nothing
fx = MOI.SingleVariable(x[i])
MOI.add_constraint(model, fx, set)
MOI.add_constraint(model, x[i], set)
end
end
for (func, set) in m.lin_quad_constraints
Expand All @@ -203,11 +202,10 @@ end

function set_variable_type(model::MOI.ModelLike, xs, variable_types)
for (x, variable_type) in zip(xs, variable_types)
fx = MOI.SingleVariable(x)
if variable_type == :Int
MOI.add_constraint(model, fx, MOI.Integer())
MOI.add_constraint(model, x, MOI.Integer())
elseif variable_type == :Bin
MOI.add_constraint(model, fx, MOI.ZeroOne())
MOI.add_constraint(model, x, MOI.ZeroOne())
else
@assert variable_type == :Cont
end
Expand Down Expand Up @@ -347,10 +345,10 @@ function bounding_solve(m::Optimizer)
# ================= Solve End ================ #

if status in STATUS_OPT || status in STATUS_LIMIT

candidate_bound = (status == MOI.OPTIMAL) ? JuMP.objective_value(m.model_mip) : JuMP.objective_bound(m.model_mip)
candidate_bound_sol = [round.(JuMP.value(_index_to_variable_ref(m.model_mip, i)); digits=6) for i in 1:(m.num_var_orig+m.num_var_linear_mip+m.num_var_nonlinear_mip)]

# Experimental code
Alp.measure_relaxed_deviation(m, sol=candidate_bound_sol)
if Alp.get_option(m, :disc_consecutive_forbid) > 0
Expand All @@ -363,11 +361,11 @@ function bounding_solve(m::Optimizer)
m.status[:bounding_solve] = status
m.detected_bound = true
end

# collect_lb_pool(m) # Collect a pool of sub-optimal solutions - currently implemented for Gurobi only

elseif status in STATUS_INF || status == MOI.INFEASIBLE_OR_UNBOUNDED

push!(m.logs[:bound], "-")
m.status[:bounding_solve] = MOI.INFEASIBLE
@warn " Warning: Infeasibility detected in the MIP solver"
Expand All @@ -377,7 +375,7 @@ function bounding_solve(m::Optimizer)
end

elseif status == :Unbounded

m.status[:bounding_solve] = MOI.DUAL_INFEASIBLE
@warn " Warning: MIP solver returns unbounded"

Expand All @@ -403,7 +401,7 @@ For advanced usage, `Alp.get_option(m, :disc_var_pick)` allows `::Function` inpu

"""
function pick_disc_vars(m::Optimizer)

disc_var_pick = Alp.get_option(m, :disc_var_pick)

if isa(disc_var_pick, Function)
Expand Down
10 changes: 5 additions & 5 deletions src/moi_function2expr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ function _add_constant(expr::Expr, constant)
end

function _term_to_expr(t::MOI.ScalarAffineTerm)
return Expr(:call, :*, t.coefficient, _variable_index_to_expr(t.variable_index))
return Expr(:call, :*, t.coefficient, _variable_index_to_expr(t.variable))
end

function _term_to_expr(t::MOI.ScalarQuadraticTerm)
coef = t.coefficient
if t.variable_index_1 == t.variable_index_2
if t.variable_1 == t.variable_2
coef /= 2
end
return Expr(:call, :*, coef, _variable_index_to_expr(t.variable_index_1), _variable_index_to_expr(t.variable_index_2))
return Expr(:call, :*, coef, _variable_index_to_expr(t.variable_1), _variable_index_to_expr(t.variable_2))
end

function _add_terms(expr::Expr, terms::Vector)
Expand All @@ -47,8 +47,8 @@ function _moi_function_to_expr(t::MOI.ScalarQuadraticTerm)
return Expr(
:call, :*,
MOI.coefficient(t),
_variable_index_to_expr(t.variable_index_1),
_variable_index_to_expr(t.variable_index_2)
_variable_index_to_expr(t.variable_1),
_variable_index_to_expr(t.variable_2)
)
end

Expand Down
59 changes: 36 additions & 23 deletions src/solver.jl
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ mutable struct OptimizerOptions
presolve_track_time::Bool # Account presolve time for total time usage
presolve_bt::Bool # Perform bound tightening procedure before the main algorithm (default: true)
presolve_time_limit::Float64 # Time limit for presolving (seconds)
presolve_bt_max_iter::Int # Maximum iterations allowed to perform presolve
presolve_bt_max_iter::Int # Maximum iterations allowed to perform presolve
presolve_bt_width_tol::Float64 # Width tolerance for bound-tightening
presolve_bt_output_tol::Float64 # Variable bounds truncation tol (change to precision)
presolve_bt_algo::Any # Method used for bound tightening procedures, can either be an index of default methods or functional inputs
Expand All @@ -67,7 +67,7 @@ mutable struct OptimizerOptions
# Features for Integer Problems (NOTE: no support for int-lin problems)
int_enable::Bool # Convert integer problem into binary problem
int_cumulative_disc::Bool # Cumulatively involve integer variables for discretization

end

function default_options()
Expand Down Expand Up @@ -172,9 +172,9 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
has_nl_objective::Bool
objective_function::Union{Nothing, MOI.ScalarAffineFunction{Float64}, MOI.ScalarQuadraticFunction{Float64}}

# Additional initial data
# Additional initial data
is_obj_linear_orig::Bool # Boolean parameter for type of objective

# (un-populated options for later use)
# A_orig::Any # Linear constraint matrix
# A_l_orig::Vector{Float64} # Linear constraint matrix LHS
Expand Down Expand Up @@ -259,9 +259,23 @@ MOI.is_set_by_optimize(::NumberOfPresolveIterations) = true
MOI.get(m::Optimizer, ::NumberOfPresolveIterations) = m.logs[:bt_iter]

MOI.get(m::Optimizer, ::MOI.TerminationStatus) = m.alpine_status

function MOI.get(m::Optimizer, attr::MOI.PrimalStatus)
if attr.result_index != 1
return MOI.NO_SOLUTION
elseif m.alpine_status == MOI.OPTIMAL
return MOI.FEASIBLE_POINT
elseif m.alpine_status == MOI.LOCALLY_SOLVED
return MOI.FEASIBLE_POINT
end
return MOI.NO_SOLUTION
end

MOI.get(::Optimizer, ::MOI.PrimalStatus) = MOI.NO_SOLUTION

MOI.get(m::Optimizer, ::MOI.ObjectiveValue) = m.best_obj
MOI.get(m::Optimizer, ::MOI.ObjectiveBound) = m.best_bound
MOI.get(m::Optimizer, ::MOI.SolveTime) = m.logs[:total_time]
MOI.get(m::Optimizer, ::MOI.SolveTimeSec) = m.logs[:total_time]

function get_option(m::Optimizer, s::Symbol)
getproperty(m.options, s)
Expand Down Expand Up @@ -338,18 +352,18 @@ function MOI.empty!(m::Optimizer)
create_logs!(m)
end

MOIU.supports_default_copy_to(model::Optimizer, copy_names::Bool) = !copy_names
MOI.supports_incremental_interface(::Optimizer) = true

function MOI.copy_to(model::Optimizer, src::MOI.ModelLike; copy_names = false)
return MOIU.default_copy_to(model, src, copy_names)
function MOI.copy_to(model::Optimizer, src::MOI.ModelLike)
return MOIU.default_copy_to(model, src)
end

MOI.get(::Optimizer, ::MOI.SolverName) = "Alpine"

function MOI.set(model::Optimizer, param::MOI.RawParameter, value)
function MOI.set(model::Optimizer, param::MOI.RawOptimizerAttribute, value)
Alp.set_option(model, Symbol(param.name), value)
end
function MOI.get(model::Optimizer, param::MOI.RawParameter)
function MOI.get(model::Optimizer, param::MOI.RawOptimizerAttribute)
Alp.get_option(model, Symbol(param.name))
end

Expand Down Expand Up @@ -378,7 +392,7 @@ end

const SCALAR_SET = Union{MOI.EqualTo{Float64}, MOI.LessThan{Float64}, MOI.GreaterThan{Float64}, MOI.Interval{Float64}}

MOI.supports_constraint(::Optimizer, ::Type{MOI.SingleVariable}, ::Type{<:SCALAR_SET}) = true
MOI.supports_constraint(::Optimizer, ::Type{MOI.VariableIndex}, ::Type{<:SCALAR_SET}) = true

_lower(set::MOI.EqualTo) = set.value
_upper(set::MOI.EqualTo) = set.value
Expand All @@ -389,8 +403,7 @@ _upper(set::MOI.GreaterThan) = nothing
_lower(set::MOI.Interval) = set.lower
_upper(set::MOI.Interval) = set.upper

function MOI.add_constraint(model::Optimizer, f::MOI.SingleVariable, set::SCALAR_SET)
vi = f.variable
function MOI.add_constraint(model::Optimizer, vi::MOI.VariableIndex, set::SCALAR_SET)
l = _lower(set)
if l !== nothing
model.l_var_orig[vi.value] = l
Expand All @@ -399,20 +412,20 @@ function MOI.add_constraint(model::Optimizer, f::MOI.SingleVariable, set::SCALAR
if u !== nothing
model.u_var_orig[vi.value] = u
end
return MOI.ConstraintIndex{typeof(f), typeof(set)}(vi.value)
return MOI.ConstraintIndex{typeof(vi),typeof(set)}(vi.value)
end

MOI.supports_constraint(::Optimizer, ::Type{MOI.SingleVariable}, ::Type{MOI.Integer}) = true
MOI.supports_constraint(::Optimizer, ::Type{MOI.VariableIndex}, ::Type{MOI.Integer}) = true

function MOI.add_constraint(model::Optimizer, f::MOI.SingleVariable, set::MOI.Integer)
model.var_type_orig[f.variable.value] = :Int
return MOI.ConstraintIndex{typeof(f), typeof(set)}(f.variable.value)
function MOI.add_constraint(model::Optimizer, f::MOI.VariableIndex, set::MOI.Integer)
model.var_type_orig[f.value] = :Int
return MOI.ConstraintIndex{typeof(f), typeof(set)}(f.value)
end
MOI.supports_constraint(::Optimizer, ::Type{MOI.SingleVariable}, ::Type{MOI.ZeroOne}) = true
MOI.supports_constraint(::Optimizer, ::Type{MOI.VariableIndex}, ::Type{MOI.ZeroOne}) = true

function MOI.add_constraint(model::Optimizer, f::MOI.SingleVariable, set::MOI.ZeroOne)
model.var_type_orig[f.variable.value] = :Bin
return MOI.ConstraintIndex{typeof(f), typeof(set)}(f.variable.value)
function MOI.add_constraint(model::Optimizer, f::MOI.VariableIndex, set::MOI.ZeroOne)
model.var_type_orig[f.value] = :Bin
return MOI.ConstraintIndex{typeof(f), typeof(set)}(f.value)
end

MOI.supports_constraint(model::Optimizer, ::Type{<:Union{MOI.ScalarAffineFunction{Float64}, MOI.ScalarQuadraticFunction{Float64}}}, ::Type{<:SCALAR_SET}) = true
Expand Down Expand Up @@ -587,7 +600,7 @@ function MOI.get(model::Optimizer, attr::MOI.VariablePrimal, vi::MOI.VariableInd
MOI.check_result_index_bounds(model, attr)
MOI.throw_if_not_valid(model, vi)
return model.best_sol[vi.value]

end

MOI.get(model::Optimizer, ::MOI.ResultCount) = model.alpine_status == MOI.OPTIMIZE_NOT_CALLED ? 0 : 1
Expand Down
4 changes: 1 addition & 3 deletions src/tmc.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import LinearAlgebra: dot, Diagonal

function amp_post_mccormick(m::Optimizer; kwargs...)

options = Dict(kwargs)
Expand Down Expand Up @@ -311,4 +309,4 @@ function tightmccormick_monomial(m,x_p,x,xz,lb_x,ub_x,z,p,lazy,quad) # if p=2, t

return
end
=#
=#
12 changes: 7 additions & 5 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using JuMP, MathOptInterface
using LinearAlgebra
using Ipopt
using Cbc
using Cbc
using Juniper
import Pavito
using GLPK
Expand Down Expand Up @@ -33,7 +33,9 @@ function _build(model::JuMP.Model)
end

# Perform Tests
include("$(alpine_dir)/test/test_solver.jl")
include("$(alpine_dir)/test/test_expression.jl")
include("$(alpine_dir)/test/test_algorithm.jl")
include("$(alpine_dir)/test/test_utility.jl")
@testset "Alpine tests" begin
include(joinpath(@__DIR__, "test_solver.jl"))
include(joinpath(@__DIR__, "test_expression.jl"))
include(joinpath(@__DIR__, "test_algorithm.jl"))
include(joinpath(@__DIR__, "test_utility.jl"))
end
Loading