diff --git a/Project.toml b/Project.toml index c185bb9..0e48b5e 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "HiGHS" uuid = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" -version = "1.7.5" +version = "1.8.0" [deps] HiGHS_jll = "8fd58aa0-07eb-5a78-9b36-339c94fd15ea" @@ -10,7 +10,7 @@ SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" [compat] HiGHS_jll = "=1.5.1, =1.5.3, =1.6.0" -MathOptInterface = "1.20" +MathOptInterface = "1.21" PrecompileTools = "1" SparseArrays = "1.6" Test = "1.6" diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 06e16ba..c4ad69b 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -1055,7 +1055,12 @@ end function MOI.modify( model::Optimizer, - ::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}, + ::MOI.ObjectiveFunction{ + <:Union{ + MOI.ScalarAffineFunction{Float64}, + MOI.ScalarQuadraticFunction{Float64}, + }, + }, chg::MOI.ScalarConstantChange{Float64}, ) ret = Highs_changeObjectiveOffset(model, chg.new_constant) @@ -1066,7 +1071,12 @@ end function MOI.modify( model::Optimizer, - ::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}, + ::MOI.ObjectiveFunction{ + <:Union{ + MOI.ScalarAffineFunction{Float64}, + MOI.ScalarQuadraticFunction{Float64}, + }, + }, chg::MOI.ScalarCoefficientChange{Float64}, ) ret = Highs_changeColCost( @@ -1079,6 +1089,44 @@ function MOI.modify( return end +function MOI.modify( + model::Optimizer, + attr::MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}, + chg::MOI.ScalarQuadraticCoefficientChange{Float64}, +) + if model.hessian === nothing + f = MOI.get( + model, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + ) + term = MOI.ScalarQuadraticTerm( + chg.new_coefficient, + chg.variable_1, + chg.variable_2, + ) + new_f = MOI.ScalarQuadraticFunction([term], f.terms, f.constant) + MOI.set(model, attr, new_f) + return + end + i, j = column(model, chg.variable_1), column(model, chg.variable_2) + if i < j + j, i = i, j + end + model.hessian[i+1, j+1] = chg.new_coefficient + ret = Highs_passHessian( + model, + size(model.hessian, 1), + length(model.hessian.nzval), + kHighsHessianFormatTriangular, + model.hessian.colptr .- HighsInt(1), + model.hessian.rowval .- HighsInt(1), + model.hessian.nzval, + ) + _check_ret(ret) + model.is_objective_function_set = true + return +end + ### ### VariableIndex-in-Set constraints. ### diff --git a/test/MOI_wrapper.jl b/test/MOI_wrapper.jl index 650af7c..3b1b786 100644 --- a/test/MOI_wrapper.jl +++ b/test/MOI_wrapper.jl @@ -476,6 +476,73 @@ function test_relax_integrality_after_solve() return end +function test_quadratic_modification_from_affine() + model = HiGHS.Optimizer() + MOI.set(model, MOI.Silent(), true) + x = MOI.add_variable(model) + MOI.add_constraint(model, x, MOI.GreaterThan(2.0)) + f = 2.0 * x + 1.0 + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) + attr = MOI.ObjectiveFunction{typeof(f)}() + MOI.set(model, attr, f) + MOI.optimize!(model) + @test isapprox(MOI.get(model, MOI.ObjectiveValue()), 5, atol = 1e-5) + F = MOI.ScalarQuadraticFunction{Float64} + attr = MOI.ObjectiveFunction{F}() + MOI.modify(model, attr, MOI.ScalarQuadraticCoefficientChange(x, x, 3.0)) + MOI.optimize!(model) + @test isapprox(MOI.get(model, MOI.ObjectiveValue()), 11, atol = 1e-5) + return +end + +function test_quadratic_off_diagonal_modification() + model = HiGHS.Optimizer() + MOI.set(model, MOI.Silent(), true) + x = MOI.add_variables(model, 2) + MOI.add_constraint.(model, x, MOI.GreaterThan.([2.0, 3.0])) + f = 4.0 * x[1] * x[1] + 2.0 * x[1] * x[2] + 2.0 * x[2] * x[2] + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) + attr = MOI.ObjectiveFunction{typeof(f)}() + MOI.set(model, attr, f) + MOI.optimize!(model) + a = MOI.get(model, MOI.VariablePrimal(), x) + y = 0.5 * a' * [8 2; 2 4] * a + @test isapprox(MOI.get(model, MOI.ObjectiveValue()), y, atol = 1e-5) + MOI.modify( + model, + attr, + MOI.ScalarQuadraticCoefficientChange(x[1], x[2], -1.0), + ) + MOI.optimize!(model) + a = MOI.get(model, MOI.VariablePrimal(), x) + y = 0.5 * a' * [8 -1; -1 4] * a + @test isapprox(MOI.get(model, MOI.ObjectiveValue()), y, atol = 1e-5) + return +end + +function test_quadratic_diagonal_modification() + model = HiGHS.Optimizer() + MOI.set(model, MOI.Silent(), true) + x = MOI.add_variable(model) + MOI.add_constraint(model, x, MOI.GreaterThan(2.0)) + f = 3.0 * x * x + 2.0 * x + 1.0 + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) + attr = MOI.ObjectiveFunction{typeof(f)}() + MOI.set(model, attr, f) + MOI.optimize!(model) + @test isapprox(MOI.get(model, MOI.ObjectiveValue()), 17, atol = 1e-5) + MOI.modify(model, attr, MOI.ScalarConstantChange(2.0)) + MOI.optimize!(model) + @test isapprox(MOI.get(model, MOI.ObjectiveValue()), 18, atol = 1e-5) + MOI.modify(model, attr, MOI.ScalarCoefficientChange(x, 3.0)) + MOI.optimize!(model) + @test isapprox(MOI.get(model, MOI.ObjectiveValue()), 20, atol = 1e-5) + MOI.modify(model, attr, MOI.ScalarQuadraticCoefficientChange(x, x, 8.0)) + MOI.optimize!(model) + @test isapprox(MOI.get(model, MOI.ObjectiveValue()), 24, atol = 1e-5) + return +end + end TestMOIHighs.runtests()