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

Add support for ObjectiveFunction{VectorAffineFunction} #263

Merged
merged 4 commits into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
67 changes: 66 additions & 1 deletion src/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
is_feasibility::Bool
is_objective_function_set::Bool
is_objective_sense_set::Bool
multi_objective::Union{Nothing,MOI.VectorAffineFunction{Float64}}

# A flag to keep track of whether the objective is linear or quadratic.
hessian::Union{Nothing,SparseArrays.SparseMatrixCSC{Float64,HighsInt}}
Expand Down Expand Up @@ -305,6 +306,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
false,
false,
nothing,
nothing,
Set{_VariableInfo}(),
_variable_info_dict(),
_constraint_info_dict(),
Expand Down Expand Up @@ -366,6 +368,7 @@ function MOI.empty!(model::Optimizer)
model.is_feasibility = true
model.is_objective_function_set = false
model.is_objective_sense_set = false
model.multi_objective = nothing
model.hessian = nothing
empty!(model.binaries)
empty!(model.variable_info)
Expand Down Expand Up @@ -871,6 +874,10 @@ function MOI.delete(model::Optimizer, v::MOI.VariableIndex)
other_info.column -= 1
end
end
if model.multi_objective !== nothing
model.multi_objective =
MOI.Utilities.filter_variables(!=(v), model.multi_objective)
end
model.name_to_variable = nothing
model.name_to_constraint_index = nothing
return
Expand Down Expand Up @@ -1017,7 +1024,9 @@ function MOI.supports(
end

function MOI.get(model::Optimizer, ::MOI.ObjectiveFunctionType)
if model.hessian === nothing
if model.multi_objective !== nothing
return MOI.VectorAffineFunction{Float64}
elseif model.hessian === nothing
return MOI.ScalarAffineFunction{Float64}
else
return MOI.ScalarQuadraticFunction{Float64}
Expand All @@ -1029,6 +1038,11 @@ function MOI.set(
::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}},
f::MOI.ScalarAffineFunction{Float64},
)
if model.multi_objective !== nothing
ret = Highs_clearLinearObjectives(model)
_check_ret(ret)
model.multi_objective = nothing
end
num_vars = HighsInt(length(model.variable_info))
obj = zeros(Float64, num_vars)
for term in f.terms
Expand Down Expand Up @@ -1062,6 +1076,11 @@ function MOI.set(
::MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}},
f::MOI.ScalarQuadraticFunction{Float64},
)
if model.multi_objective !== nothing
ret = Highs_clearLinearObjectives(model)
_check_ret(ret)
model.multi_objective = nothing
end
numcol = length(model.variable_info)
obj = zeros(Float64, numcol)
for term in f.affine_terms
Expand Down Expand Up @@ -1228,6 +1247,52 @@ function MOI.modify(
return
end

function MOI.supports(
::Optimizer,
::MOI.ObjectiveFunction{MOI.VectorAffineFunction{Float64}},
)
return true
end

function MOI.get(
model::Optimizer,
::MOI.ObjectiveFunction{MOI.VectorAffineFunction{Float64}},
)
return model.multi_objective
end

function MOI.set(
model::Optimizer,
::MOI.ObjectiveFunction{MOI.VectorAffineFunction{Float64}},
f::MOI.VectorAffineFunction{Float64},
)
num_vars = HighsInt(length(model.variable_info))
O = MOI.output_dimension(f)
obj_coefs = zeros(Float64, O * num_vars)
for term in f.terms
col = column(model, term.scalar_term.variable) + 1
obj_coefs[O*(term.output_index-1)+col] += term.scalar_term.coefficient
end
# senseP will be 1 if MIN and -1 if MAX
senseP = Ref{HighsInt}()
ret = Highs_getObjectiveSense(model, senseP)
_check_ret(ret)
ret = Highs_passLinearObjectives(
model,
O, # num_linear_objective
fill(Float64(senseP[]), O), # weight: set to -1 if maximizing
f.constants, # offset
obj_coefs, # coefficients
zeros(Float64, O), # abs_tolerance
zeros(Float64, O), # rel_tolerance
ones(Cint, O), # priority
)
_check_ret(ret)
model.multi_objective = f
model.is_objective_function_set = true
return
end

###
### VariableIndex-in-Set constraints.
###
Expand Down
44 changes: 44 additions & 0 deletions test/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,50 @@ function test_dual_objective_value_infeasible()
return
end

function test_ObjectiveFunction_VectorAffineFunction_to_ScalarAffineFunction()
model = HiGHS.Optimizer()
MOI.set(model, MOI.Silent(), true)
x = MOI.add_variables(model, 2)
MOI.add_constraint(model, x[1], MOI.Interval(1.0, 2.0))
MOI.add_constraint(model, x[2], MOI.Interval(3.0, 4.0))
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
f = MOI.Utilities.operate(vcat, Float64, 1.0 * x[1], 1.0 * x[2])
MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f)
MOI.optimize!(model)
@test ≈(MOI.get(model, MOI.ObjectiveValue()), 4.0)
g = 1.0 * x[1] - 2.0 * x[2]
MOI.set(model, MOI.ObjectiveFunction{typeof(g)}(), g)
MOI.optimize!(model)
@test ≈(MOI.get(model, MOI.ObjectiveValue()), -7.0; atol = 1e-6)
@test ≈(MOI.get(model, MOI.VariablePrimal(), x), [1.0, 4.0]; atol = 1e-6)
@test MOI.get(model, MOI.ObjectiveFunctionType()) ==
MOI.ScalarAffineFunction{Float64}
@test model.multi_objective === nothing
return
end

function test_ObjectiveFunction_VectorAffineFunction_to_ScalarQuadraticFunction()
model = HiGHS.Optimizer()
MOI.set(model, MOI.Silent(), true)
x = MOI.add_variables(model, 2)
MOI.add_constraint(model, x[1], MOI.Interval(1.0, 2.0))
MOI.add_constraint(model, x[2], MOI.Interval(3.0, 4.0))
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
f = MOI.Utilities.operate(vcat, Float64, 1.0 * x[1], 1.0 * x[2])
MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f)
MOI.optimize!(model)
@test ≈(MOI.get(model, MOI.ObjectiveValue()), 4.0)
g = 1.0 * x[1] + (1.0 * x[2] * x[2] - 8.0 * x[2] + 16.0)
MOI.set(model, MOI.ObjectiveFunction{typeof(g)}(), g)
MOI.optimize!(model)
@test ≈(MOI.get(model, MOI.ObjectiveValue()), 1.0; atol = 1e-6)
@test ≈(MOI.get(model, MOI.VariablePrimal(), x), [1.0, 4.0]; atol = 1e-6)
@test MOI.get(model, MOI.ObjectiveFunctionType()) ==
MOI.ScalarQuadraticFunction{Float64}
@test model.multi_objective === nothing
return
end

end # module

TestMOIHighs.runtests()
Loading