Skip to content

Commit

Permalink
Track nonlinear resultant vars outside of MOI (#590)
Browse files Browse the repository at this point in the history
  • Loading branch information
simonbowly authored Nov 28, 2024
1 parent 9720c80 commit 1c31d31
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 19 deletions.
37 changes: 26 additions & 11 deletions src/MOI_wrapper/MOI_nonlinear.jl
Original file line number Diff line number Diff line change
Expand Up @@ -324,15 +324,19 @@ function MOI.add_constraint(
opcode = Cint[]
data = Cdouble[]
parent = Cint[]
sense, rhs = _sense_and_rhs(s)
_process_nonlinear(model, f, opcode, data, parent)
# Add resultant variable
vi, ci = MOI.add_constrained_variable(model, s)
resvar_index = c_column(model, vi)
# Add resultant variable. We don't use MOI.add_constrained_variable because
# we don't want it to show up in the bound constraints, etc.
column = _get_next_column(model)
lb, ub = _bounds(s)
lb = something(lb, -Inf)
ub = something(ub, Inf)
ret = GRBaddvar(model, 0, C_NULL, C_NULL, 0.0, lb, ub, GRB_CONTINUOUS, "")
_check_ret(model, ret)
ret = GRBaddgenconstrNL(
model,
C_NULL,
resvar_index,
column - 1,
length(opcode),
opcode,
data,
Expand All @@ -342,7 +346,7 @@ function MOI.add_constraint(
_require_update(model, model_change = true)
model.last_constraint_index += 1
model.nl_constraint_info[model.last_constraint_index] =
_NLConstraintInfo(length(model.nl_constraint_info) + 1, s, vi)
_NLConstraintInfo(length(model.nl_constraint_info) + 1, s, column)
return MOI.ConstraintIndex{MOI.ScalarNonlinearFunction,typeof(s)}(
model.last_constraint_index,
)
Expand Down Expand Up @@ -371,8 +375,13 @@ function MOI.delete(
end
delete!(model.nl_constraint_info, c.value)
model.name_to_constraint_index = nothing
# Remove resultant variable
MOI.delete(model, info.resvar)
# Delete resultant variable from the Gurobi model. These are not tracked in
# model.variable_info but they do need to be accounted for in index
# adjustment.
del_cols = [Cint(info.resvar_index - 1)]
ret = GRBdelvars(model, length(del_cols), del_cols)
_check_ret(model, ret)
append!(model.columns_deleted_since_last_update, del_cols .+ 1)
_require_update(model, model_change = true)
return
end
Expand All @@ -389,9 +398,15 @@ function MOI.delete(
info.row -= searchsortedlast(rows_to_delete, info.row - 1)
end
model.name_to_constraint_index = nothing
# Delete resultant variables
resvars = [_info(model, c).resvar for c in cs]
MOI.delete(model, resvars)
# Delete resultant variables from the Gurobi model for all removed
# constraints. These are not tracked in model.variable_info but they do
# need to be accounted for in index adjustment.
del_cols = [Cint(_info(model, c).resvar_index - 1) for c in cs]
ret = GRBdelvars(model, length(del_cols), del_cols)
_check_ret(model, ret)
append!(model.columns_deleted_since_last_update, del_cols .+ 1)
_require_update(model, model_change = true)
# Remove entries from nl constraint tracking
cs_values = sort!(getfield.(cs, :value))
filter!(model.nl_constraint_info) do pair
return isempty(searchsorted(cs_values, pair.first))
Expand Down
12 changes: 9 additions & 3 deletions src/MOI_wrapper/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ mutable struct _NLConstraintInfo
# Storage for constraint names. Where possible, these are also stored in
# the Gurobi model.
name::String
resvar::MOI.VariableIndex
function _NLConstraintInfo(row::Int, set, resvar::MOI.VariableIndex)
return new(row, set, "", resvar)
resvar_index::Int
function _NLConstraintInfo(row::Int, set, resvar_index::Int)
return new(row, set, "", resvar_index)
end
end

Expand Down Expand Up @@ -566,6 +566,12 @@ function _update_if_necessary(
var_info.column,
)
end
for nl_info in values(model.nl_constraint_info)
nl_info.resvar_index -= searchsortedlast(
model.columns_deleted_since_last_update,
nl_info.resvar_index,
)
end
model.next_column -= length(model.columns_deleted_since_last_update)
empty!(model.columns_deleted_since_last_update)
ret = GRBupdatemodel(model)
Expand Down
103 changes: 98 additions & 5 deletions test/MOI/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,14 @@ function test_runtests()
"_RotatedSecondOrderCone_",
"_GeometricMeanCone_",
# Shaky tests
"vector_nonlinear",
"VectorNonlinearFunction",
# Tests should be skipped due to RequirementsUnmet, but aren't
r"^test_nonlinear_expression_hs071$",
r"^test_nonlinear_expression_hs071_epigraph$",
"_multiobjective_vector_nonlinear",
# Timeouts
r"^test_nonlinear_expression_hs109$",
r"^test_nonlinear_expression_hs110$",
# MOI.get(MOI.ObjectiveValue()) fails for NL objectives
r"^test_nonlinear_expression_quartic$",
r"^test_nonlinear_expression_overrides_objective$",
# Nonlinear duals not computed
r"^test_nonlinear_duals$",
],
)
Expand Down Expand Up @@ -590,6 +589,72 @@ function test_add_constrained_variables()
return
end

function test_add_constrained_variable_greaterthan()
model = Gurobi.Optimizer(GRB_ENV)
MOI.set(model, MOI.Silent(), true)
set = MOI.GreaterThan{Float64}(1.2)
vi, ci = MOI.add_constrained_variable(model, set)
@test MOI.get(model, MOI.NumberOfVariables()) == 1
@test MOI.get(model, MOI.ListOfConstraintTypesPresent()) ==
[(MOI.VariableIndex, MOI.GreaterThan{Float64})]
@test MOI.get(model, MOI.ConstraintFunction(), ci) == vi
@test MOI.get(model, MOI.ConstraintSet(), ci) == set
# Force update and check correct bounds on the Gurobi model
MOI.optimize!(model)
valueP = Ref{Cdouble}()
ret = Gurobi.GRBgetdblattrelement(model, "LB", 0, valueP)
@test ret == 0
@test valueP[] == 1.2
ret = Gurobi.GRBgetdblattrelement(model, "UB", 0, valueP)
@test ret == 0
@test valueP[] >= -1e30
return
end

function test_add_constrained_variable_lessthan()
model = Gurobi.Optimizer(GRB_ENV)
MOI.set(model, MOI.Silent(), true)
set = MOI.LessThan{Float64}(3.4)
vi, ci = MOI.add_constrained_variable(model, set)
@test MOI.get(model, MOI.NumberOfVariables()) == 1
@test MOI.get(model, MOI.ListOfConstraintTypesPresent()) ==
[(MOI.VariableIndex, MOI.LessThan{Float64})]
@test MOI.get(model, MOI.ConstraintFunction(), ci) == vi
@test MOI.get(model, MOI.ConstraintSet(), ci) == set
# Force update and check correct bounds on the Gurobi model
MOI.optimize!(model)
valueP = Ref{Cdouble}()
ret = Gurobi.GRBgetdblattrelement(model, "LB", 0, valueP)
@test ret == 0
@test valueP[] <= -1e30
ret = Gurobi.GRBgetdblattrelement(model, "UB", 0, valueP)
@test ret == 0
@test valueP[] == 3.4
return
end

function test_add_constrained_variable_equalto()
model = Gurobi.Optimizer(GRB_ENV)
MOI.set(model, MOI.Silent(), true)
set = MOI.EqualTo{Float64}(5.2)
vi, ci = MOI.add_constrained_variable(model, set)
@test MOI.get(model, MOI.NumberOfVariables()) == 1
@test MOI.get(model, MOI.ListOfConstraintTypesPresent()) ==
[(MOI.VariableIndex, MOI.EqualTo{Float64})]
@test MOI.get(model, MOI.ConstraintFunction(), ci) == vi
@test MOI.get(model, MOI.ConstraintSet(), ci) == set
# Force update and check correct bounds on the Gurobi model
MOI.optimize!(model)
valueP = Ref{Cdouble}()
ret = Gurobi.GRBgetdblattrelement(model, "LB", 0, valueP)
@test ret == 0
@test valueP[] == 5.2
ret = Gurobi.GRBgetdblattrelement(model, "UB", 0, valueP)
@test ret == 0
@test valueP[] == 5.2
return
end

function _is_binary(x; atol = 1e-6)
return isapprox(x, 0; atol = atol) || isapprox(x, 1; atol = atol)
end
Expand Down Expand Up @@ -1363,6 +1428,34 @@ function test_ConstrName_too_long()
return
end

function test_delete_nonlinear_index()
if !Gurobi._supports_nonlinear()
return
end
model = Gurobi.Optimizer(GRB_ENV)
x1 = MOI.add_variable(model)
x2 = MOI.add_variable(model)
MOI.add_constraint(model, x1, MOI.GreaterThan(-1.0))
MOI.add_constraint(model, x1, MOI.LessThan(1.0))
MOI.add_constraint(model, x2, MOI.GreaterThan(-1.0))
MOI.add_constraint(model, x2, MOI.LessThan(1.0))
g1 = MOI.ScalarNonlinearFunction(
:+,
Any[MOI.ScalarNonlinearFunction(:sin, Any[2.5*x1]), 1.0*x2],
)
g2 = MOI.ScalarNonlinearFunction(
:+,
Any[MOI.ScalarNonlinearFunction(:cos, Any[2.5*x1]), 1.0*x2],
)
c1 = MOI.add_constraint(model, g1, MOI.EqualTo(0.0))
c2 = MOI.add_constraint(model, g2, MOI.EqualTo(0.0))
# Delete in order: tests that the resvar index of the first constraint
# is correctly adjusted.
MOI.delete(model, c1)
MOI.delete(model, c2)
return
end

end # TestMOIWrapper

TestMOIWrapper.runtests()

0 comments on commit 1c31d31

Please sign in to comment.