|
|
@ -83,56 +83,81 @@ function _update_solution!(data::JuMPSolverData)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function are_constraints_satisfied(
|
|
|
|
function add_constraints(
|
|
|
|
data::JuMPSolverData;
|
|
|
|
data::JuMPSolverData;
|
|
|
|
lhs::Vector{Vector{Tuple{String, Float64}}},
|
|
|
|
lhs::Vector{Vector{Tuple{String, Float64}}},
|
|
|
|
rhs::Vector{Float64},
|
|
|
|
rhs::Vector{Float64},
|
|
|
|
senses::Vector{String},
|
|
|
|
senses::Vector{String},
|
|
|
|
tol::Float64=1e-5,
|
|
|
|
names::Vector{String},
|
|
|
|
)::Vector{Bool}
|
|
|
|
)::Nothing
|
|
|
|
result = []
|
|
|
|
|
|
|
|
for (i, sense) in enumerate(senses)
|
|
|
|
for (i, sense) in enumerate(senses)
|
|
|
|
lhs_value = 0.0
|
|
|
|
lhs_expr = AffExpr(0.0)
|
|
|
|
for (varname, coeff) in lhs[i]
|
|
|
|
for (varname, coeff) in lhs[i]
|
|
|
|
var = data.varname_to_var[varname]
|
|
|
|
var = data.varname_to_var[varname]
|
|
|
|
lhs_value += data.solution[var] * coeff
|
|
|
|
add_to_expression!(lhs_expr, var, coeff)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if sense == "<"
|
|
|
|
if sense == "<"
|
|
|
|
push!(result, lhs_value <= rhs[i] + tol)
|
|
|
|
constr = @constraint(data.model, lhs_expr <= rhs[i])
|
|
|
|
elseif sense == ">"
|
|
|
|
elseif sense == ">"
|
|
|
|
push!(result, lhs_value >= rhs[i] - tol)
|
|
|
|
constr = @constraint(data.model, lhs_expr >= rhs[i])
|
|
|
|
else
|
|
|
|
else
|
|
|
|
push!(result, abs(lhs_value - rhs[i]) <= tol)
|
|
|
|
constr = @constraint(data.model, lhs_expr == rhs[i])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
set_name(constr, names[i])
|
|
|
|
|
|
|
|
data.cname_to_constr[names[i]] = constr
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return result
|
|
|
|
return
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function add_constraints(
|
|
|
|
function are_constraints_satisfied(
|
|
|
|
data::JuMPSolverData;
|
|
|
|
data::JuMPSolverData;
|
|
|
|
lhs::Vector{Vector{Tuple{String, Float64}}},
|
|
|
|
lhs::Vector{Vector{Tuple{String, Float64}}},
|
|
|
|
rhs::Vector{Float64},
|
|
|
|
rhs::Vector{Float64},
|
|
|
|
senses::Vector{String},
|
|
|
|
senses::Vector{String},
|
|
|
|
names::Vector{String},
|
|
|
|
tol::Float64=1e-5,
|
|
|
|
)::Nothing
|
|
|
|
)::Vector{Bool}
|
|
|
|
|
|
|
|
result = []
|
|
|
|
for (i, sense) in enumerate(senses)
|
|
|
|
for (i, sense) in enumerate(senses)
|
|
|
|
lhs_expr = AffExpr(0.0)
|
|
|
|
lhs_value = 0.0
|
|
|
|
for (varname, coeff) in lhs[i]
|
|
|
|
for (varname, coeff) in lhs[i]
|
|
|
|
var = data.varname_to_var[varname]
|
|
|
|
var = data.varname_to_var[varname]
|
|
|
|
add_to_expression!(lhs_expr, var, coeff)
|
|
|
|
lhs_value += data.solution[var] * coeff
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if sense == "<"
|
|
|
|
if sense == "<"
|
|
|
|
constr = @constraint(data.model, lhs_expr <= rhs[i])
|
|
|
|
push!(result, lhs_value <= rhs[i] + tol)
|
|
|
|
elseif sense == ">"
|
|
|
|
elseif sense == ">"
|
|
|
|
constr = @constraint(data.model, lhs_expr >= rhs[i])
|
|
|
|
push!(result, lhs_value >= rhs[i] - tol)
|
|
|
|
else
|
|
|
|
else
|
|
|
|
constr = @constraint(data.model, lhs_expr == rhs[i])
|
|
|
|
push!(result, abs(lhs_value - rhs[i]) <= tol)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
set_name(constr, names[i])
|
|
|
|
|
|
|
|
data.cname_to_constr[names[i]] = constr
|
|
|
|
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return
|
|
|
|
return result
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function build_test_instance_knapsack()
|
|
|
|
|
|
|
|
weights = [23.0, 26.0, 20.0, 18.0]
|
|
|
|
|
|
|
|
prices = [505.0, 352.0, 458.0, 220.0]
|
|
|
|
|
|
|
|
capacity = 67.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
model = Model()
|
|
|
|
|
|
|
|
n = length(weights)
|
|
|
|
|
|
|
|
@variable(model, x[0:n-1], Bin)
|
|
|
|
|
|
|
|
@variable(model, z, lower_bound=0.0, upper_bound=capacity)
|
|
|
|
|
|
|
|
@objective(model, Max, sum(x[i-1] * prices[i] for i in 1:n))
|
|
|
|
|
|
|
|
@constraint(model, eq_capacity, sum(x[i-1] * weights[i] for i in 1:n) - z == 0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return JuMPInstance(model)
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function build_test_instance_infeasible()
|
|
|
|
|
|
|
|
model = Model()
|
|
|
|
|
|
|
|
@variable(model, x, Bin)
|
|
|
|
|
|
|
|
@objective(model, Max, x)
|
|
|
|
|
|
|
|
@constraint(model, x >= 2)
|
|
|
|
|
|
|
|
return JuMPInstance(model)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -166,9 +191,15 @@ function solve(
|
|
|
|
break
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
_update_solution!(data)
|
|
|
|
if is_infeasible(data)
|
|
|
|
primal_bound = JuMP.objective_value(model)
|
|
|
|
data.solution = Dict()
|
|
|
|
dual_bound = JuMP.objective_bound(model)
|
|
|
|
primal_bound = nothing
|
|
|
|
|
|
|
|
dual_bound = nothing
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
_update_solution!(data)
|
|
|
|
|
|
|
|
primal_bound = JuMP.objective_value(model)
|
|
|
|
|
|
|
|
dual_bound = JuMP.objective_bound(model)
|
|
|
|
|
|
|
|
end
|
|
|
|
if JuMP.objective_sense(model) == MOI.MIN_SENSE
|
|
|
|
if JuMP.objective_sense(model) == MOI.MIN_SENSE
|
|
|
|
sense = "min"
|
|
|
|
sense = "min"
|
|
|
|
lower_bound = dual_bound
|
|
|
|
lower_bound = dual_bound
|
|
|
@ -200,8 +231,13 @@ function solve_lp(data::JuMPSolverData; tee::Bool=false)
|
|
|
|
wallclock_time = @elapsed begin
|
|
|
|
wallclock_time = @elapsed begin
|
|
|
|
log = _optimize_and_capture_output!(model, tee=tee)
|
|
|
|
log = _optimize_and_capture_output!(model, tee=tee)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
_update_solution!(data)
|
|
|
|
if is_infeasible(data)
|
|
|
|
obj_value = JuMP.objective_value(model)
|
|
|
|
data.solution = Dict()
|
|
|
|
|
|
|
|
obj_value = nothing
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
_update_solution!(data)
|
|
|
|
|
|
|
|
obj_value = JuMP.objective_value(model)
|
|
|
|
|
|
|
|
end
|
|
|
|
for var in bin_vars
|
|
|
|
for var in bin_vars
|
|
|
|
JuMP.set_binary(var)
|
|
|
|
JuMP.set_binary(var)
|
|
|
|
end
|
|
|
|
end
|
|
|
@ -213,17 +249,24 @@ function solve_lp(data::JuMPSolverData; tee::Bool=false)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function set_instance!(data::JuMPSolverData, instance, model::JuMP.Model)
|
|
|
|
function set_instance!(
|
|
|
|
|
|
|
|
data::JuMPSolverData,
|
|
|
|
|
|
|
|
instance;
|
|
|
|
|
|
|
|
model::Union{Nothing,JuMP.Model},
|
|
|
|
|
|
|
|
)::Nothing
|
|
|
|
data.instance = instance
|
|
|
|
data.instance = instance
|
|
|
|
|
|
|
|
if model === nothing
|
|
|
|
|
|
|
|
model = instance.to_model()
|
|
|
|
|
|
|
|
end
|
|
|
|
data.model = model
|
|
|
|
data.model = model
|
|
|
|
data.bin_vars = [
|
|
|
|
data.bin_vars = [
|
|
|
|
var
|
|
|
|
var
|
|
|
|
for var in JuMP.all_variables(data.model)
|
|
|
|
for var in JuMP.all_variables(model)
|
|
|
|
if JuMP.is_binary(var)
|
|
|
|
if JuMP.is_binary(var)
|
|
|
|
]
|
|
|
|
]
|
|
|
|
data.varname_to_var = Dict(
|
|
|
|
data.varname_to_var = Dict(
|
|
|
|
JuMP.name(var) => var
|
|
|
|
JuMP.name(var) => var
|
|
|
|
for var in JuMP.all_variables(data.model)
|
|
|
|
for var in JuMP.all_variables(model)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
JuMP.set_optimizer(model, data.optimizer_factory)
|
|
|
|
JuMP.set_optimizer(model, data.optimizer_factory)
|
|
|
|
data.cname_to_constr = Dict()
|
|
|
|
data.cname_to_constr = Dict()
|
|
|
@ -234,6 +277,7 @@ function set_instance!(data::JuMPSolverData, instance, model::JuMP.Model)
|
|
|
|
data.cname_to_constr[name] = constr
|
|
|
|
data.cname_to_constr[name] = constr
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -256,7 +300,10 @@ end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function is_infeasible(data::JuMPSolverData)
|
|
|
|
function is_infeasible(data::JuMPSolverData)
|
|
|
|
return JuMP.termination_status(data.model) == MOI.INFEASIBLE
|
|
|
|
return JuMP.termination_status(data.model) in [
|
|
|
|
|
|
|
|
MOI.INFEASIBLE,
|
|
|
|
|
|
|
|
MOI.INFEASIBLE_OR_UNBOUNDED,
|
|
|
|
|
|
|
|
]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -409,22 +456,6 @@ function get_constraints(
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function build_test_instance_knapsack()
|
|
|
|
|
|
|
|
weights = [23.0, 26.0, 20.0, 18.0]
|
|
|
|
|
|
|
|
prices = [505.0, 352.0, 458.0, 220.0]
|
|
|
|
|
|
|
|
capacity = 67.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
model = Model()
|
|
|
|
|
|
|
|
n = length(weights)
|
|
|
|
|
|
|
|
@variable(model, x[0:n-1], Bin)
|
|
|
|
|
|
|
|
@variable(model, z, lower_bound=0.0, upper_bound=capacity)
|
|
|
|
|
|
|
|
@objective(model, Max, sum(x[i-1] * prices[i] for i in 1:n))
|
|
|
|
|
|
|
|
@constraint(model, eq_capacity, sum(x[i-1] * weights[i] for i in 1:n) - z == 0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return JuMPInstance(model)
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pydef mutable struct JuMPSolver <: miplearn.solvers.internal.InternalSolver
|
|
|
|
@pydef mutable struct JuMPSolver <: miplearn.solvers.internal.InternalSolver
|
|
|
|
function __init__(self, optimizer_factory)
|
|
|
|
function __init__(self, optimizer_factory)
|
|
|
|
self.data = JuMPSolverData(
|
|
|
|
self.data = JuMPSolverData(
|
|
|
@ -459,19 +490,18 @@ end
|
|
|
|
)...)
|
|
|
|
)...)
|
|
|
|
|
|
|
|
|
|
|
|
build_test_instance_infeasible(self) =
|
|
|
|
build_test_instance_infeasible(self) =
|
|
|
|
error("not implemented")
|
|
|
|
build_test_instance_infeasible()
|
|
|
|
|
|
|
|
|
|
|
|
build_test_instance_knapsack(self) =
|
|
|
|
build_test_instance_knapsack(self) =
|
|
|
|
build_test_instance_knapsack()
|
|
|
|
build_test_instance_knapsack()
|
|
|
|
|
|
|
|
|
|
|
|
# FIXME: Actually clone instead of returning self
|
|
|
|
clone(self) = JuMPSolver(self.data.optimizer_factory)
|
|
|
|
clone(self) = self
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fix(self, solution) =
|
|
|
|
fix(self, solution) =
|
|
|
|
fix!(self.data, solution)
|
|
|
|
fix!(self.data, solution)
|
|
|
|
|
|
|
|
|
|
|
|
get_solution(self) =
|
|
|
|
get_solution(self) =
|
|
|
|
self.data.solution
|
|
|
|
isempty(self.data.solution) ? nothing : self.data.solution
|
|
|
|
|
|
|
|
|
|
|
|
get_constraints(
|
|
|
|
get_constraints(
|
|
|
|
self;
|
|
|
|
self;
|
|
|
@ -529,8 +559,8 @@ end
|
|
|
|
[n for n in names],
|
|
|
|
[n for n in names],
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
set_instance(self, instance, model) =
|
|
|
|
set_instance(self, instance, model=nothing) =
|
|
|
|
set_instance!(self.data, instance, model)
|
|
|
|
set_instance!(self.data, instance, model=model)
|
|
|
|
|
|
|
|
|
|
|
|
set_warm_start(self, solution) =
|
|
|
|
set_warm_start(self, solution) =
|
|
|
|
set_warm_start!(self.data, solution)
|
|
|
|
set_warm_start!(self.data, solution)
|
|
|
|