From 611c0cd5d147a8916cb0a715511e3f657c8537ee Mon Sep 17 00:00:00 2001 From: Joaquim Garcia Date: Fri, 22 Mar 2024 11:52:33 -0300 Subject: [PATCH 01/22] Add plural objective changes --- src/objective.jl | 181 ++++++++++++++++++++++++++++++++++++++++- test/test_objective.jl | 89 ++++++++++++++++++++ 2 files changed, 269 insertions(+), 1 deletion(-) diff --git a/src/objective.jl b/src/objective.jl index 2992557d81f..f87586222ae 100644 --- a/src/objective.jl +++ b/src/objective.jl @@ -443,6 +443,8 @@ julia> set_objective_coefficient(model, x, 3) julia> objective_function(model) 3 x + 1 ``` + +See also [`set_objective_coefficients`](@ref). """ function set_objective_coefficient( model::GenericModel{T}, @@ -491,11 +493,98 @@ function _set_objective_coefficient( return end +""" + set_objective_coefficients( + model::GenericModel, + variables::Vector{<:GenericVariableRef}, + coefficients::Vector{<:Real}, + ) + +Set multiple linear objective coefficients associated with `variables` to `coefficients`, in a single call. + +Note: this function will throw an error if a nonlinear objective is set. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x); + +julia> @variable(model, y); + +julia> @objective(model, Min, 3x + 2y + 1) +3 x + 2 y + 1 + +julia> set_objective_coefficients(model, [x, y], [5, 4]) + +julia> objective_function(model) +5 x + 4 y + 1 +``` + +See also [`set_objective_coefficient`](@ref). +""" +function set_objective_coefficients( + model::GenericModel{T}, + variables::AbstractVector{<:GenericVariableRef{T}}, + coeffs::AbstractVector{<:Real}, +) where {T} + if _nlp_objective_function(model) !== nothing + error("A nonlinear objective is already set in the model") + end + coeffs_t = convert.(T, coeffs)::AbstractVector{T} + F = objective_function_type(model) + _set_objective_coefficients(model, variables, coeffs_t, F) + model.is_model_dirty = true + return +end + +function _set_objective_coefficients( + model::GenericModel{T}, + variables::AbstractVector{<:GenericVariableRef{T}}, + coeffs::AbstractVector{<:T}, + ::Type{GenericVariableRef{T}}, +) where {T} + current_obj = objective_function(model) + current_obj_index = index(current_obj) + if length(variables) == 1 && current_obj_index == index(variables[]) + set_objective_function(model, coeffs[] * variables[]) + else + position = findfirst(x -> index(x) == current_obj_index, variables) + if positions === nothing + set_objective_function( + model, + add_to_expression!( + LinearAlgebra.dot(coeffs, variables), + current_obj, + ), + ) + else + set_objective_function(model, LinearAlgebra.dot(coeffs, variables)) + end + end + return +end + +function _set_objective_coefficient( + model::GenericModel{T}, + variables::AbstractVector{<:GenericVariableRef{T}}, + coeffs::AbstractVector{<:T}, + ::Type{F}, +) where {T,F} + MOI.modify( + backend(model), + MOI.ObjectiveFunction{moi_function_type(F)}(), + MOI.ScalarCoefficientChange.(index.(variables), coeffs), + ) + return +end + """ set_objective_coefficient( model::GenericModel{T}, variable_1::GenericVariableRef{T}, - variable_1::GenericVariableRef{T}, + variable_2::GenericVariableRef{T}, coefficient::Real, ) where {T} @@ -520,6 +609,8 @@ julia> set_objective_coefficient(model, x[1], x[2], 3) julia> objective_function(model) 2 x[1]² + 3 x[1]*x[2] + +See also [`set_objective_coefficients`](@ref). ``` """ function set_objective_coefficient( @@ -572,3 +663,91 @@ function _set_objective_coefficient( ) return end + +""" + set_objective_coefficients( + model::GenericModel{T}, + variables_1::GenericVariableRef{T}, + variables_2::GenericVariableRef{T}, + coefficients::Real, + ) where {T} + +Set multiple quadratic objective coefficients associated with `variables_1` and +`variables_2` to `coefficients`, in a single call. + +Note: this function will throw an error if a nonlinear objective is set. + +## Example + +```jldoctest +julia> model = Model(); + +julia> @variable(model, x[1:2]); + +julia> @objective(model, Min, x[1]^2 + x[1] * x[2]) +x[1]² + x[1]*x[2] + +julia> set_objective_coefficient(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) + +julia> objective_function(model) +2 x[1]² + 3 x[1]*x[2] + +See also [`set_objective_coefficient`](@ref). +``` +""" +function set_objective_coefficients( + model::GenericModel{T}, + variables_1::AbstractVector{<:GenericVariableRef{T}}, + variables_2::AbstractVector{<:GenericVariableRef{T}}, + coeffs::AbstractVector{<:Real}, +) where {T} + if _nlp_objective_function(model) !== nothing + error("A nonlinear objective is already set in the model") + end + coeffs_t = convert.(T, coeffs)::AbstractVector{<:T} + F = moi_function_type(objective_function_type(model)) + _set_objective_coefficients(model, variables_1, variables_2, coeffs_t, F) + model.is_model_dirty = true + return +end + +function _set_objective_coefficients( + model::GenericModel{T}, + variables_1::AbstractVector{<:GenericVariableRef{T}}, + variables_2::AbstractVector{<:GenericVariableRef{T}}, + coeffs::AbstractVector{<:T}, + ::Type{F}, +) where {T,F} + current_obj = objective_function(model) + new_obj = add_to_expression!( + sum( + coeffs[i] * variables_1[i] * variables_2[i] for + i in eachindex(coeffs) + ), + current_obj, + ) + set_objective_function(model, new_obj) + return +end + +function _set_objective_coefficients( + model::GenericModel{T}, + variables_1::AbstractVector{<:GenericVariableRef{T}}, + variables_2::AbstractVector{<:GenericVariableRef{T}}, + coeffs::AbstractVector{<:T}, + ::Type{MOI.ScalarQuadraticFunction{T}}, +) where {T} + if variable_1 == variable_2 + coeff *= T(2) + end + MOI.modify( + backend(model), + MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{T}}(), + MOI.ScalarQuadraticCoefficientChange.( + index.(variables_1), + index.(variables_2), + coeffs, + ), + ) + return +end diff --git a/test/test_objective.jl b/test/test_objective.jl index 3bf23fa2622..0a467063b07 100644 --- a/test/test_objective.jl +++ b/test/test_objective.jl @@ -50,6 +50,22 @@ function test_objective_coef_update_linear_objective_changes() return end +function test_objective_coef_batch_update_linear_objective_changes() + model = Model() + @variable(model, x) + @variable(model, y) + @objective(model, Max, x) + set_objective_coefficient(model, [x, y], [4.0, 5.0]) + @test isequal_canonical(objective_function(model), 4x + 5y) + @objective(model, Max, x + y) + set_objective_coefficient(model, [x, y], [4.0, 5.0]) + @test isequal_canonical(objective_function(model), 4x + 5y) + @objective(model, Min, x) + set_objective_coefficient(model, [y], [2.0]) + @test isequal_canonical(objective_function(model), x + 2.0 * y) + return +end + function test_objective_coef_update_quadratic_objective_changes() model = Model() @variable(model, x) @@ -59,6 +75,16 @@ function test_objective_coef_update_quadratic_objective_changes() return end +function test_objective_coef_update_quadratic_objective_batch_changes() + model = Model() + @variable(model, x) + @variable(model, y) + @objective(model, Max, x^2 + x + y) + set_objective_coefficient(model, [x, y], [4.0, 5.0]) + @test isequal_canonical(objective_function(model), x^2 + 4x + 5y) + return +end + function test_extension_objective_sense_get_set( ModelType = Model, VariableType = VariableRef, @@ -235,6 +261,23 @@ function test_set_objective_coefficient_quadratic() return end +function test_set_objective_coefficient_quadratic_batch() + model = Model() + @variable(model, x[1:2]) + @objective(model, Min, x[1]^2 + x[1] * x[2] + x[1] + 2) + set_objective_coefficient(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) + @test isequal_canonical( + objective_function(model), + 2 * x[1]^2 + 3 * x[1] * x[2] + x[1] + 2, + ) + set_objective_coefficient(model, [x[2]], [x[1]], [4]) + @test isequal_canonical( + objective_function(model), + 2 * x[1]^2 + 4 * x[1] * x[2] + x[1] + 2, + ) + return +end + function test_set_objective_coefficient_quadratic_error() model = Model() @variable(model, x[1:2]) @@ -246,6 +289,17 @@ function test_set_objective_coefficient_quadratic_error() return end +function test_set_objective_coefficient_quadratic_batch_error() + model = Model() + @variable(model, x[1:2]) + @NLobjective(model, Min, x[1] * x[2]) + @test_throws( + ErrorException("A nonlinear objective is already set in the model"), + set_objective_coefficient(model, [x[1]], [x[1]], [2]), + ) + return +end + function test_set_objective_coefficient_quadratic_affine_original() model = Model() @variable(model, x[1:2]) @@ -259,6 +313,18 @@ function test_set_objective_coefficient_quadratic_affine_original() return end +function test_set_objective_coefficient_quadratic_batch_affine_original() + model = Model() + @variable(model, x[1:2]) + @objective(model, Min, x[1] + 2) + set_objective_coefficient(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) + @test isequal_canonical( + objective_function(model), + 2 * x[1]^2 + 3 * x[1] * x[2] + x[1] + 2, + ) + return +end + function test_set_objective_coefficient_quadratic_variable_original() model = Model() @variable(model, x[1:2]) @@ -272,6 +338,18 @@ function test_set_objective_coefficient_quadratic_variable_original() return end +function test_set_objective_coefficient_quadratic_batch_variable_original() + model = Model() + @variable(model, x[1:2]) + @objective(model, Min, x[1]) + set_objective_coefficient(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) + @test isequal_canonical( + objective_function(model), + 2 * x[1]^2 + 3 * x[1] * x[2] + x[1], + ) + return +end + function test_set_objective_coefficient_quadratic_nothing_set() model = Model() @variable(model, x[1:2]) @@ -284,4 +362,15 @@ function test_set_objective_coefficient_quadratic_nothing_set() return end +function test_set_objective_coefficient_quadratic_batch_nothing_set() + model = Model() + @variable(model, x[1:2]) + set_objective_coefficient(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) + @test isequal_canonical( + objective_function(model), + 2 * x[1]^2 + 3 * x[1] * x[2], + ) + return +end + end # module From 79abaa17cb61934cc1af59491d87e17ffa58afb0 Mon Sep 17 00:00:00 2001 From: Joaquim Garcia Date: Fri, 22 Mar 2024 11:56:34 -0300 Subject: [PATCH 02/22] fix docs --- src/objective.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/objective.jl b/src/objective.jl index f87586222ae..c3e44c4e4dd 100644 --- a/src/objective.jl +++ b/src/objective.jl @@ -609,9 +609,9 @@ julia> set_objective_coefficient(model, x[1], x[2], 3) julia> objective_function(model) 2 x[1]² + 3 x[1]*x[2] +``` See also [`set_objective_coefficients`](@ref). -``` """ function set_objective_coefficient( model::GenericModel{T}, @@ -691,9 +691,9 @@ julia> set_objective_coefficient(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) julia> objective_function(model) 2 x[1]² + 3 x[1]*x[2] +``` See also [`set_objective_coefficient`](@ref). -``` """ function set_objective_coefficients( model::GenericModel{T}, From 452b3587171359c86a07eca1f5990a7d021d4485 Mon Sep 17 00:00:00 2001 From: Joaquim Garcia Date: Fri, 22 Mar 2024 12:26:06 -0300 Subject: [PATCH 03/22] fix docs --- src/objective.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/objective.jl b/src/objective.jl index c3e44c4e4dd..2caac7eab34 100644 --- a/src/objective.jl +++ b/src/objective.jl @@ -667,9 +667,9 @@ end """ set_objective_coefficients( model::GenericModel{T}, - variables_1::GenericVariableRef{T}, - variables_2::GenericVariableRef{T}, - coefficients::Real, + variables_1::AbstractVector{ Date: Fri, 22 Mar 2024 12:26:31 -0300 Subject: [PATCH 04/22] add constraint modification batch methods --- src/variables.jl | 121 +++++++++++++++++++++++++++++++++++++++- test/test_constraint.jl | 36 +++++++++++- 2 files changed, 155 insertions(+), 2 deletions(-) diff --git a/src/variables.jl b/src/variables.jl index 64e96907e8a..a087e282bcc 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -2525,7 +2525,7 @@ end set_normalized_coefficient( constraint::ConstraintRef, variable::GenericVariableRef, - value, + value::Number, ) Set the coefficient of `variable` in the constraint `constraint` to `value`. @@ -2550,6 +2550,8 @@ julia> set_normalized_coefficient(con, x, 4) julia> con con : 4 x ≤ 2 ``` + +See also [`set_normalized_coefficients`](@ref). """ function set_normalized_coefficient( con_ref::ConstraintRef{<:AbstractModel,<:MOI.ConstraintIndex{F}}, @@ -2566,6 +2568,58 @@ function set_normalized_coefficient( return end +""" + set_normalized_coefficients( + constraints::AbstractVector{<:ConstraintRef}, + variables::AbstractVector{<:GenericVariableRef}, + values::AbstractVector{<:Number}, + ) + +Set multiple coefficient of `variables` in the constraints `constraints` to `values`. + +Note that prior to this step, JuMP will aggregate multiple terms containing the +same variable. For example, given a constraint `2x + 3x <= 2`, +`set_normalized_coefficients(con, [x], [4])` will create the constraint `4x <= 2`. + +## Example + +```jldoctest; filter=r"≤|<=" +julia> model = Model(); + +julia> @variable(model, x) +x + +julia> @variable(model, y) +y + +julia> @constraint(model, con, 2x + 3x + 4y <= 2) +con : 5 x + 4 y ≤ 2 + +julia> set_normalized_coefficients(con, [x, y], [6, 7]) + +julia> con +con : 6 x + 7 y ≤ 2 +``` + +See also [`set_normalized_coefficients`](@ref). +""" +function set_normalized_coefficients( + con_ref::AbstractVector{ + <:ConstraintRef{<:AbstractModel,<:MOI.ConstraintIndex{F}}, + }, + variables::AbstractVector{<:AbstractVariableRef}, + values::AbstractVector{<:Number}, +) where {T,F<:Union{MOI.ScalarAffineFunction{T},MOI.ScalarQuadraticFunction{T}}} + model = owner_model(first(con_ref)) + MOI.modify( + backend(model), + index.(con_ref), + MOI.ScalarCoefficientChange.(index.(variables), convert.(T, values)), + ) + model.is_model_dirty = true + return +end + """ set_normalized_coefficients( con_ref::ConstraintRef, @@ -2596,6 +2650,8 @@ julia> set_normalized_coefficients(con, x, [(1, 2.0), (2, 5.0)]) julia> con con : [2 x, 5 x] ∈ MathOptInterface.Nonnegatives(2) ``` + +See also [`set_normalized_coefficient`](@ref). """ function set_normalized_coefficients( constraint::ConstraintRef{<:AbstractModel,<:MOI.ConstraintIndex{F}}, @@ -2644,6 +2700,8 @@ julia> set_normalized_coefficient(con, x[1], x[2], 5) julia> con con : 4 x[1]² + 5 x[1]*x[2] + x[2] ≤ 2 ``` + +See also [`set_normalized_coefficients`](@ref). """ function set_normalized_coefficient( constraint::ConstraintRef{<:AbstractModel,<:MOI.ConstraintIndex{F}}, @@ -2669,6 +2727,67 @@ function set_normalized_coefficient( return end +""" + set_normalized_coefficients( + constraints::AbstractVector{<:ConstraintRef}, + variables_1:AbstractVector{<:GenericVariableRef}, + variables_2:AbstractVector{<:GenericVariableRef}, + values::AbstractVector{<:Number}, + ) + +Set multiple quadratic coefficients associated with `variables_1` and `variables_2` in +the constraints `constraints` to `values`. + +Note that prior to this step, JuMP will aggregate multiple terms containing the +same variable. For example, given a constraint `2x^2 + 3x^2 <= 2`, +`set_normalized_coefficients(con, [x], [x], [4])` will create the constraint `4x^2 <= 2`. + +## Example + +```jldoctest; filter=r"≤|<=" +julia> model = Model(); + +julia> @variable(model, x[1:2]); + +julia> @constraint(model, con, 2x[1]^2 + 3 * x[1] * x[2] + x[2] <= 2) +con : 2 x[1]² + 3 x[1]*x[2] + x[2] ≤ 2 + +julia> set_normalized_coefficient(con, [x[1], x[1]], [x[1], x[2]], [4, 5]) + +julia> con +con : 4 x[1]² + 5 x[1]*x[2] + x[2] ≤ 2 +``` + +See also [`set_normalized_coefficients`](@ref). +""" +function set_normalized_coefficients( + constraints::AbstractVector{ + <:ConstraintRef{<:AbstractModel,<:MOI.ConstraintIndex{F}}, + }, + variables_1::AbstractVector{<:AbstractVariableRef}, + variables_2::AbstractVector{<:AbstractVariableRef}, + values::AbstractVector{<:Number}, +) where {T,F<:MOI.ScalarQuadraticFunction{T}} + new_values = convert.(T, values) + for i in eachindex(variables_1) + if variables_1[i] == variables_2[i] + new_values[i] *= T(2) + end + end + model = owner_model(first(constraints)) + MOI.modify( + backend(model), + index.(constraint), + MOI.ScalarQuadraticCoefficientChanges.( + index.(variables_1), + index.(variables_2), + new_values, + ), + ) + model.is_model_dirty = true + return +end + """ normalized_coefficient( constraint::ConstraintRef, diff --git a/test/test_constraint.jl b/test/test_constraint.jl index 36246e52d95..c082d120368 100644 --- a/test/test_constraint.jl +++ b/test/test_constraint.jl @@ -980,7 +980,29 @@ function test_change_coefficient() return end -function test_change_coefficients() +function test_change_coefficient_batch() + model = Model() + x = @variable(model) + y = @variable(model) + con_ref = @constraint(model, 2 * x + 3 * y == -1) + @test normalized_coefficient(con_ref, x) == 2.0 + @test normalized_coefficient(con_ref, y) == 3.0 + set_normalized_coefficient(con_ref, [x, y], [1.0, 4.0]) + @test normalized_coefficient(con_ref, x) == 1.0 + @test normalized_coefficient(con_ref, y) == 4.0 + set_normalized_coefficient(con_ref, [x, y], [3, 4]) # Check type promotion. + @test normalized_coefficient(con_ref, x) == 3.0 + @test normalized_coefficient(con_ref, y) == 4.0 + quad_con = @constraint(model, x^2 == 0) + @test normalized_coefficient(quad_con, x) == 0.0 + set_normalized_coefficient(quad_con, [x, y], [2, 7]) + @test normalized_coefficient(quad_con, x) == 2.0 + @test normalized_coefficient(quad_con, y) == 7.0 + @test isequal_canonical(constraint_object(quad_con).func, x^2 + 2x + 7y) + return +end + +function test_change_coefficients_vector_function() model = Model() @variable(model, x) @constraint(model, con, [2x + 3x, 4x] in MOI.Nonnegatives(2)) @@ -1806,4 +1828,16 @@ function test_set_normalized_coefficient_quadratic() return end +function test_set_normalized_coefficient_quadratic_batch() + model = Model() + @variable(model, x[1:2]) + @constraint(model, con, 2x[1]^2 + 3 * x[1] * x[2] + x[2] <= 2) + @test normalized_coefficient(con, x[1], x[1]) == 2.0 + @test normalized_coefficient(con, x[1], x[2]) == 3.0 + set_normalized_coefficient(con, [x[1], x[1]], [x[1], x[2]], [4, 5]) + @test normalized_coefficient(con, x[1], x[1]) == 4.0 + @test normalized_coefficient(con, x[1], x[2]) == 5.0 + return +end + end # module From 93b20c78a5e7e494f12e57730e553a6af4f14837 Mon Sep 17 00:00:00 2001 From: Joaquim Garcia Date: Fri, 22 Mar 2024 12:40:52 -0300 Subject: [PATCH 05/22] add rhs batch modifications --- src/constraints.jl | 23 +++++++++++++++++++++-- test/test_constraint.jl | 23 +++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/constraints.jl b/src/constraints.jl index b5176857c5e..c37b986cde6 100644 --- a/src/constraints.jl +++ b/src/constraints.jl @@ -731,7 +731,7 @@ function add_constraint( end """ - set_normalized_rhs(constraint::ConstraintRef, value) + set_normalized_rhs(constraint::ConstraintRef, value::Number) Set the right-hand side term of `constraint` to `value`. @@ -758,7 +758,7 @@ con : 2 x ≤ 4 """ function set_normalized_rhs( con_ref::ConstraintRef{<:AbstractModel,MOI.ConstraintIndex{F,S}}, - value, + value::Number, ) where { T, S<:Union{MOI.LessThan{T},MOI.GreaterThan{T},MOI.EqualTo{T}}, @@ -773,6 +773,25 @@ function set_normalized_rhs( return end +function set_normalized_rhs( + constraints::AbstractVector{ + <:ConstraintRef{<:AbstractModel,MOI.ConstraintIndex{F,S}}, + }, + values::AbstractVector{<:Number}, +) where { + T, + S<:Union{MOI.LessThan{T},MOI.GreaterThan{T},MOI.EqualTo{T}}, + F<:Union{MOI.ScalarAffineFunction{T},MOI.ScalarQuadraticFunction{T}}, +} + MOI.set( + owner_model(first(constraints)), + MOI.ConstraintSet(), + constraints, + S.(convert.(T, values)), + ) + return +end + """ normalized_rhs(constraint::ConstraintRef) diff --git a/test/test_constraint.jl b/test/test_constraint.jl index c082d120368..23b23055f3b 100644 --- a/test/test_constraint.jl +++ b/test/test_constraint.jl @@ -1030,6 +1030,29 @@ function test_change_rhs() return end +function test_change_rhs_batch() + model = Model() + x = @variable(model) + con_ref1 = @constraint(model, 2 * x <= 1) + con_ref2 = @constraint(model, 3 * x <= 2) + @test normalized_rhs(con_ref1) == 1.0 + @test normalized_rhs(con_ref2) == 2.0 + set_normalized_rhs([con_ref1, con_ref2], [3.0, 4.0]) + @test normalized_rhs(con_ref1) == 3.0 + @test normalized_rhs(con_ref2) == 4.0 + con_ref1 = @constraint(model, 2 * x - 1 == 1) + con_ref2 = @constraint(model, 2 * x - 1 == 2) + @test normalized_rhs(con_ref1) == 2.0 + @test normalized_rhs(con_ref2) == 3.0 + set_normalized_rhs([con_ref1, con_ref2], [3.0, 4.0]) + @test normalized_rhs(con_ref1) == 3.0 + @test normalized_rhs(con_ref2) == 4.0 + con_ref1 = @constraint(model, 0 <= 2 * x) + con_ref2 = @constraint(model, 2 * x <= 1) + @test_throws MethodError set_normalized_rhs([con_ref1, con_ref2], [3, 3]) + return +end + function test_add_to_function_constant_scalar() model = Model() x = @variable(model) From e654c74c21279f92d95f7b714a5e827ce069f6c1 Mon Sep 17 00:00:00 2001 From: Joaquim Garcia Date: Fri, 22 Mar 2024 13:36:31 -0300 Subject: [PATCH 06/22] fix tests --- src/constraints.jl | 4 ++-- test/test_constraint.jl | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/constraints.jl b/src/constraints.jl index c37b986cde6..e35b43dbf46 100644 --- a/src/constraints.jl +++ b/src/constraints.jl @@ -784,9 +784,9 @@ function set_normalized_rhs( F<:Union{MOI.ScalarAffineFunction{T},MOI.ScalarQuadraticFunction{T}}, } MOI.set( - owner_model(first(constraints)), + backend(owner_model(first(constraints))), MOI.ConstraintSet(), - constraints, + index.(constraints), S.(convert.(T, values)), ) return diff --git a/test/test_constraint.jl b/test/test_constraint.jl index 23b23055f3b..82166792768 100644 --- a/test/test_constraint.jl +++ b/test/test_constraint.jl @@ -987,15 +987,15 @@ function test_change_coefficient_batch() con_ref = @constraint(model, 2 * x + 3 * y == -1) @test normalized_coefficient(con_ref, x) == 2.0 @test normalized_coefficient(con_ref, y) == 3.0 - set_normalized_coefficient(con_ref, [x, y], [1.0, 4.0]) + set_normalized_coefficients(con_ref, [x, y], [1.0, 4.0]) @test normalized_coefficient(con_ref, x) == 1.0 @test normalized_coefficient(con_ref, y) == 4.0 - set_normalized_coefficient(con_ref, [x, y], [3, 4]) # Check type promotion. + set_normalized_coefficients(con_ref, [x, y], [3, 4]) # Check type promotion. @test normalized_coefficient(con_ref, x) == 3.0 @test normalized_coefficient(con_ref, y) == 4.0 quad_con = @constraint(model, x^2 == 0) @test normalized_coefficient(quad_con, x) == 0.0 - set_normalized_coefficient(quad_con, [x, y], [2, 7]) + set_normalized_coefficients(quad_con, [x, y], [2, 7]) @test normalized_coefficient(quad_con, x) == 2.0 @test normalized_coefficient(quad_con, y) == 7.0 @test isequal_canonical(constraint_object(quad_con).func, x^2 + 2x + 7y) From 113d992c7d16680757f45c6330c92c818d9a165f Mon Sep 17 00:00:00 2001 From: Joaquim Garcia Date: Fri, 22 Mar 2024 17:45:05 -0300 Subject: [PATCH 07/22] fix tests --- src/objective.jl | 10 ++++++---- src/variables.jl | 4 ++-- test/test_constraint.jl | 10 +++++----- test/test_objective.jl | 20 ++++++++++---------- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/objective.jl b/src/objective.jl index 2caac7eab34..d5d07489469 100644 --- a/src/objective.jl +++ b/src/objective.jl @@ -551,7 +551,7 @@ function _set_objective_coefficients( set_objective_function(model, coeffs[] * variables[]) else position = findfirst(x -> index(x) == current_obj_index, variables) - if positions === nothing + if position === nothing set_objective_function( model, add_to_expression!( @@ -566,7 +566,7 @@ function _set_objective_coefficients( return end -function _set_objective_coefficient( +function _set_objective_coefficients( model::GenericModel{T}, variables::AbstractVector{<:GenericVariableRef{T}}, coeffs::AbstractVector{<:T}, @@ -737,8 +737,10 @@ function _set_objective_coefficients( coeffs::AbstractVector{<:T}, ::Type{MOI.ScalarQuadraticFunction{T}}, ) where {T} - if variable_1 == variable_2 - coeff *= T(2) + for i in eachindex(variables_1) + if variables_1[i] == variables_2[i] + coeffs[i] *= T(2) + end end MOI.modify( backend(model), diff --git a/src/variables.jl b/src/variables.jl index a087e282bcc..76c6eac65b5 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -2777,8 +2777,8 @@ function set_normalized_coefficients( model = owner_model(first(constraints)) MOI.modify( backend(model), - index.(constraint), - MOI.ScalarQuadraticCoefficientChanges.( + index.(constraints), + MOI.ScalarQuadraticCoefficientChange.( index.(variables_1), index.(variables_2), new_values, diff --git a/test/test_constraint.jl b/test/test_constraint.jl index 82166792768..e702ca2bbd3 100644 --- a/test/test_constraint.jl +++ b/test/test_constraint.jl @@ -987,15 +987,15 @@ function test_change_coefficient_batch() con_ref = @constraint(model, 2 * x + 3 * y == -1) @test normalized_coefficient(con_ref, x) == 2.0 @test normalized_coefficient(con_ref, y) == 3.0 - set_normalized_coefficients(con_ref, [x, y], [1.0, 4.0]) + set_normalized_coefficients([con_ref, con_ref], [x, y], [1.0, 4.0]) @test normalized_coefficient(con_ref, x) == 1.0 @test normalized_coefficient(con_ref, y) == 4.0 - set_normalized_coefficients(con_ref, [x, y], [3, 4]) # Check type promotion. + set_normalized_coefficients([con_ref, con_ref], [x, y], [3, 4]) # Check type promotion. @test normalized_coefficient(con_ref, x) == 3.0 @test normalized_coefficient(con_ref, y) == 4.0 quad_con = @constraint(model, x^2 == 0) @test normalized_coefficient(quad_con, x) == 0.0 - set_normalized_coefficients(quad_con, [x, y], [2, 7]) + set_normalized_coefficients([quad_con, quad_con], [x, y], [2, 7]) @test normalized_coefficient(quad_con, x) == 2.0 @test normalized_coefficient(quad_con, y) == 7.0 @test isequal_canonical(constraint_object(quad_con).func, x^2 + 2x + 7y) @@ -1047,7 +1047,7 @@ function test_change_rhs_batch() set_normalized_rhs([con_ref1, con_ref2], [3.0, 4.0]) @test normalized_rhs(con_ref1) == 3.0 @test normalized_rhs(con_ref2) == 4.0 - con_ref1 = @constraint(model, 0 <= 2 * x) + con_ref1 = @constraint(model, 0 >= 2 * x) con_ref2 = @constraint(model, 2 * x <= 1) @test_throws MethodError set_normalized_rhs([con_ref1, con_ref2], [3, 3]) return @@ -1857,7 +1857,7 @@ function test_set_normalized_coefficient_quadratic_batch() @constraint(model, con, 2x[1]^2 + 3 * x[1] * x[2] + x[2] <= 2) @test normalized_coefficient(con, x[1], x[1]) == 2.0 @test normalized_coefficient(con, x[1], x[2]) == 3.0 - set_normalized_coefficient(con, [x[1], x[1]], [x[1], x[2]], [4, 5]) + set_normalized_coefficients([con, con], [x[1], x[1]], [x[1], x[2]], [4, 5]) @test normalized_coefficient(con, x[1], x[1]) == 4.0 @test normalized_coefficient(con, x[1], x[2]) == 5.0 return diff --git a/test/test_objective.jl b/test/test_objective.jl index 0a467063b07..6a1673df086 100644 --- a/test/test_objective.jl +++ b/test/test_objective.jl @@ -55,13 +55,13 @@ function test_objective_coef_batch_update_linear_objective_changes() @variable(model, x) @variable(model, y) @objective(model, Max, x) - set_objective_coefficient(model, [x, y], [4.0, 5.0]) + set_objective_coefficients(model, [x, y], [4.0, 5.0]) @test isequal_canonical(objective_function(model), 4x + 5y) @objective(model, Max, x + y) - set_objective_coefficient(model, [x, y], [4.0, 5.0]) + set_objective_coefficients(model, [x, y], [4.0, 5.0]) @test isequal_canonical(objective_function(model), 4x + 5y) @objective(model, Min, x) - set_objective_coefficient(model, [y], [2.0]) + set_objective_coefficients(model, [y], [2.0]) @test isequal_canonical(objective_function(model), x + 2.0 * y) return end @@ -80,7 +80,7 @@ function test_objective_coef_update_quadratic_objective_batch_changes() @variable(model, x) @variable(model, y) @objective(model, Max, x^2 + x + y) - set_objective_coefficient(model, [x, y], [4.0, 5.0]) + set_objective_coefficients(model, [x, y], [4.0, 5.0]) @test isequal_canonical(objective_function(model), x^2 + 4x + 5y) return end @@ -265,12 +265,12 @@ function test_set_objective_coefficient_quadratic_batch() model = Model() @variable(model, x[1:2]) @objective(model, Min, x[1]^2 + x[1] * x[2] + x[1] + 2) - set_objective_coefficient(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) + set_objective_coefficients(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) @test isequal_canonical( objective_function(model), 2 * x[1]^2 + 3 * x[1] * x[2] + x[1] + 2, ) - set_objective_coefficient(model, [x[2]], [x[1]], [4]) + set_objective_coefficients(model, [x[2]], [x[1]], [4]) @test isequal_canonical( objective_function(model), 2 * x[1]^2 + 4 * x[1] * x[2] + x[1] + 2, @@ -295,7 +295,7 @@ function test_set_objective_coefficient_quadratic_batch_error() @NLobjective(model, Min, x[1] * x[2]) @test_throws( ErrorException("A nonlinear objective is already set in the model"), - set_objective_coefficient(model, [x[1]], [x[1]], [2]), + set_objective_coefficients(model, [x[1]], [x[1]], [2]), ) return end @@ -317,7 +317,7 @@ function test_set_objective_coefficient_quadratic_batch_affine_original() model = Model() @variable(model, x[1:2]) @objective(model, Min, x[1] + 2) - set_objective_coefficient(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) + set_objective_coefficients(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) @test isequal_canonical( objective_function(model), 2 * x[1]^2 + 3 * x[1] * x[2] + x[1] + 2, @@ -342,7 +342,7 @@ function test_set_objective_coefficient_quadratic_batch_variable_original() model = Model() @variable(model, x[1:2]) @objective(model, Min, x[1]) - set_objective_coefficient(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) + set_objective_coefficients(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) @test isequal_canonical( objective_function(model), 2 * x[1]^2 + 3 * x[1] * x[2] + x[1], @@ -365,7 +365,7 @@ end function test_set_objective_coefficient_quadratic_batch_nothing_set() model = Model() @variable(model, x[1:2]) - set_objective_coefficient(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) + set_objective_coefficients(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) @test isequal_canonical( objective_function(model), 2 * x[1]^2 + 3 * x[1] * x[2], From 9399959a1197fb2e73dbd10dba9e2aee739bf009 Mon Sep 17 00:00:00 2001 From: Joaquim Garcia Date: Fri, 22 Mar 2024 19:35:32 -0300 Subject: [PATCH 08/22] fix cov --- src/objective.jl | 4 +--- test/test_objective.jl | 27 ++++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/objective.jl b/src/objective.jl index d5d07489469..9623d48a742 100644 --- a/src/objective.jl +++ b/src/objective.jl @@ -547,9 +547,7 @@ function _set_objective_coefficients( ) where {T} current_obj = objective_function(model) current_obj_index = index(current_obj) - if length(variables) == 1 && current_obj_index == index(variables[]) - set_objective_function(model, coeffs[] * variables[]) - else + if length(variables) > 0 position = findfirst(x -> index(x) == current_obj_index, variables) if position === nothing set_objective_function( diff --git a/test/test_objective.jl b/test/test_objective.jl index 6a1673df086..c66c917f2c2 100644 --- a/test/test_objective.jl +++ b/test/test_objective.jl @@ -50,6 +50,28 @@ function test_objective_coef_update_linear_objective_changes() return end +function test_objective_coef_update_linear_objective_error() + model = Model() + @variable(model, x[1:2]) + @NLobjective(model, Min, x[1] * x[2]) + @test_throws( + ErrorException("A nonlinear objective is already set in the model"), + set_objective_coefficient(model, x[1], 2), + ) + return +end + +function test_objective_coef_update_linear_objective_batch_error() + model = Model() + @variable(model, x[1:2]) + @NLobjective(model, Min, x[1] * x[2]) + @test_throws( + ErrorException("A nonlinear objective is already set in the model"), + set_objective_coefficients(model, [x[1], x[2]], [2, 3]), + ) + return +end + function test_objective_coef_batch_update_linear_objective_changes() model = Model() @variable(model, x) @@ -61,8 +83,11 @@ function test_objective_coef_batch_update_linear_objective_changes() set_objective_coefficients(model, [x, y], [4.0, 5.0]) @test isequal_canonical(objective_function(model), 4x + 5y) @objective(model, Min, x) + set_objective_coefficients(model, [x], [2.0]) + @test isequal_canonical(objective_function(model), 2x) + @objective(model, Min, x) set_objective_coefficients(model, [y], [2.0]) - @test isequal_canonical(objective_function(model), x + 2.0 * y) + @test isequal_canonical(objective_function(model), x + 2y) return end From 7be10ccb4d6bee3c10807b54c1b7ac71da9dbacb Mon Sep 17 00:00:00 2001 From: Joaquim Garcia Date: Fri, 22 Mar 2024 19:41:54 -0300 Subject: [PATCH 09/22] fix docs --- src/objective.jl | 2 +- src/variables.jl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/objective.jl b/src/objective.jl index 9623d48a742..d29ad28c11d 100644 --- a/src/objective.jl +++ b/src/objective.jl @@ -685,7 +685,7 @@ julia> @variable(model, x[1:2]); julia> @objective(model, Min, x[1]^2 + x[1] * x[2]) x[1]² + x[1]*x[2] -julia> set_objective_coefficient(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) +julia> set_objective_coefficients(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) julia> objective_function(model) 2 x[1]² + 3 x[1]*x[2] diff --git a/src/variables.jl b/src/variables.jl index 76c6eac65b5..01ee8d09898 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -2595,7 +2595,7 @@ y julia> @constraint(model, con, 2x + 3x + 4y <= 2) con : 5 x + 4 y ≤ 2 -julia> set_normalized_coefficients(con, [x, y], [6, 7]) +julia> set_normalized_coefficients([con, con], [x, y], [6, 7]) julia> con con : 6 x + 7 y ≤ 2 @@ -2752,7 +2752,7 @@ julia> @variable(model, x[1:2]); julia> @constraint(model, con, 2x[1]^2 + 3 * x[1] * x[2] + x[2] <= 2) con : 2 x[1]² + 3 x[1]*x[2] + x[2] ≤ 2 -julia> set_normalized_coefficient(con, [x[1], x[1]], [x[1], x[2]], [4, 5]) +julia> set_normalized_coefficient([con, con], [x[1], x[1]], [x[1], x[2]], [4, 5]) julia> con con : 4 x[1]² + 5 x[1]*x[2] + x[2] ≤ 2 From eda33ac5cfc4c8c584403a71675972523e00b6e6 Mon Sep 17 00:00:00 2001 From: Joaquim Garcia Date: Fri, 22 Mar 2024 19:49:06 -0300 Subject: [PATCH 10/22] add dimension check --- src/objective.jl | 14 ++++++++++++++ test/test_objective.jl | 26 ++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/objective.jl b/src/objective.jl index d29ad28c11d..1206828dde0 100644 --- a/src/objective.jl +++ b/src/objective.jl @@ -532,6 +532,13 @@ function set_objective_coefficients( if _nlp_objective_function(model) !== nothing error("A nonlinear objective is already set in the model") end + if length(variables) != length(coeffs) + throw( + DimensionMismatch( + "The number of variables and coefficients must match", + ), + ) + end coeffs_t = convert.(T, coeffs)::AbstractVector{T} F = objective_function_type(model) _set_objective_coefficients(model, variables, coeffs_t, F) @@ -702,6 +709,13 @@ function set_objective_coefficients( if _nlp_objective_function(model) !== nothing error("A nonlinear objective is already set in the model") end + if !(length(variables_1) == length(variables_2) == length(coeffs)) + throw( + DimensionMismatch( + "The number of variables and coefficients must match", + ), + ) + end coeffs_t = convert.(T, coeffs)::AbstractVector{<:T} F = moi_function_type(objective_function_type(model)) _set_objective_coefficients(model, variables_1, variables_2, coeffs_t, F) diff --git a/test/test_objective.jl b/test/test_objective.jl index c66c917f2c2..73f35961498 100644 --- a/test/test_objective.jl +++ b/test/test_objective.jl @@ -72,6 +72,19 @@ function test_objective_coef_update_linear_objective_batch_error() return end +function test_objective_coef_update_linear_objective_batch_dimension_error() + model = Model() + @variable(model, x[1:2]) + @objective(model, Min, x[1] * x[2]) + @test_throws( + DimensionMismatch( + "The number of variables and coefficients must match", + ), + set_objective_coefficients(model, [x[1], x[2]], [2]), + ) + return +end + function test_objective_coef_batch_update_linear_objective_changes() model = Model() @variable(model, x) @@ -325,6 +338,19 @@ function test_set_objective_coefficient_quadratic_batch_error() return end +function test_set_objective_coefficient_quadratic_batch_dimension_error() + model = Model() + @variable(model, x[1:2]) + @objective(model, Min, x[1] * x[2]) + @test_throws( + DimensionMismatch( + "The number of variables and coefficients must match", + ), + set_objective_coefficients(model, [x[1]], [x[1]], [2, 3]), + ) + return +end + function test_set_objective_coefficient_quadratic_affine_original() model = Model() @variable(model, x[1:2]) From 504c40494447357d9efaac4e07be94eb8d852a37 Mon Sep 17 00:00:00 2001 From: Joaquim Garcia Date: Fri, 22 Mar 2024 20:03:27 -0300 Subject: [PATCH 11/22] more dimension checks --- src/variables.jl | 37 ++++++++++++++++++++++++++++--------- test/test_constraint.jl | 12 ++++++++++++ 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/variables.jl b/src/variables.jl index 01ee8d09898..5b1995dd004 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -2604,17 +2604,24 @@ con : 6 x + 7 y ≤ 2 See also [`set_normalized_coefficients`](@ref). """ function set_normalized_coefficients( - con_ref::AbstractVector{ + constraints::AbstractVector{ <:ConstraintRef{<:AbstractModel,<:MOI.ConstraintIndex{F}}, }, variables::AbstractVector{<:AbstractVariableRef}, - values::AbstractVector{<:Number}, + coeffs::AbstractVector{<:Number}, ) where {T,F<:Union{MOI.ScalarAffineFunction{T},MOI.ScalarQuadraticFunction{T}}} - model = owner_model(first(con_ref)) + if !(length(constraints) == length(variables) == length(coeffs)) + throw( + DimensionMismatch( + "The number of constraints, variables and coefficients must match", + ), + ) + end + model = owner_model(first(constraints)) MOI.modify( backend(model), - index.(con_ref), - MOI.ScalarCoefficientChange.(index.(variables), convert.(T, values)), + index.(constraints), + MOI.ScalarCoefficientChange.(index.(variables), convert.(T, coeffs)), ) model.is_model_dirty = true return @@ -2766,12 +2773,24 @@ function set_normalized_coefficients( }, variables_1::AbstractVector{<:AbstractVariableRef}, variables_2::AbstractVector{<:AbstractVariableRef}, - values::AbstractVector{<:Number}, + coeffs::AbstractVector{<:Number}, ) where {T,F<:MOI.ScalarQuadraticFunction{T}} - new_values = convert.(T, values) + if !( + length(constraints) == + length(variables_1) == + length(variables_2) == + length(coeffs) + ) + throw( + DimensionMismatch( + "The number of constraints, variables and coefficients must match", + ), + ) + end + new_coeffs = convert.(T, coeffs) for i in eachindex(variables_1) if variables_1[i] == variables_2[i] - new_values[i] *= T(2) + new_coeffs[i] *= T(2) end end model = owner_model(first(constraints)) @@ -2781,7 +2800,7 @@ function set_normalized_coefficients( MOI.ScalarQuadraticCoefficientChange.( index.(variables_1), index.(variables_2), - new_values, + new_coeffs, ), ) model.is_model_dirty = true diff --git a/test/test_constraint.jl b/test/test_constraint.jl index e702ca2bbd3..e8d445ea8ca 100644 --- a/test/test_constraint.jl +++ b/test/test_constraint.jl @@ -999,6 +999,12 @@ function test_change_coefficient_batch() @test normalized_coefficient(quad_con, x) == 2.0 @test normalized_coefficient(quad_con, y) == 7.0 @test isequal_canonical(constraint_object(quad_con).func, x^2 + 2x + 7y) + @test_throws( + DimensionMismatch( + "The number of constraints, variables and coefficients must match", + ), + set_normalized_coefficients([con_ref], [x, y], [4, 5]), + ) return end @@ -1860,6 +1866,12 @@ function test_set_normalized_coefficient_quadratic_batch() set_normalized_coefficients([con, con], [x[1], x[1]], [x[1], x[2]], [4, 5]) @test normalized_coefficient(con, x[1], x[1]) == 4.0 @test normalized_coefficient(con, x[1], x[2]) == 5.0 + @test_throws( + DimensionMismatch( + "The number of constraints, variables and coefficients must match", + ), + set_normalized_coefficients([con], [x[1], x[1]], [x[1], x[2]], [4, 5]), + ) return end From 9ef84d533fe25c12fc3d74d713296d32d96730e8 Mon Sep 17 00:00:00 2001 From: Joaquim Garcia Date: Fri, 22 Mar 2024 20:13:41 -0300 Subject: [PATCH 12/22] fix docs --- src/variables.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/variables.jl b/src/variables.jl index 5b1995dd004..fbdbea6e5be 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -2759,7 +2759,7 @@ julia> @variable(model, x[1:2]); julia> @constraint(model, con, 2x[1]^2 + 3 * x[1] * x[2] + x[2] <= 2) con : 2 x[1]² + 3 x[1]*x[2] + x[2] ≤ 2 -julia> set_normalized_coefficient([con, con], [x[1], x[1]], [x[1], x[2]], [4, 5]) +julia> set_normalized_coefficients([con, con], [x[1], x[1]], [x[1], x[2]], [4, 5]) julia> con con : 4 x[1]² + 5 x[1]*x[2] + x[2] ≤ 2 From e6772fdffdc28131fd350dad101be5db7e4dda87 Mon Sep 17 00:00:00 2001 From: Joaquim Garcia Date: Fri, 22 Mar 2024 23:24:35 -0300 Subject: [PATCH 13/22] rename to unify methods --- src/objective.jl | 32 ++++++++++++-------------------- src/variables.jl | 24 ++++++++---------------- test/test_constraint.jl | 12 ++++++------ test/test_objective.jl | 28 ++++++++++++++-------------- 4 files changed, 40 insertions(+), 56 deletions(-) diff --git a/src/objective.jl b/src/objective.jl index 1206828dde0..779b1925515 100644 --- a/src/objective.jl +++ b/src/objective.jl @@ -443,8 +443,6 @@ julia> set_objective_coefficient(model, x, 3) julia> objective_function(model) 3 x + 1 ``` - -See also [`set_objective_coefficients`](@ref). """ function set_objective_coefficient( model::GenericModel{T}, @@ -494,7 +492,7 @@ function _set_objective_coefficient( end """ - set_objective_coefficients( + set_objective_coefficient( model::GenericModel, variables::Vector{<:GenericVariableRef}, coefficients::Vector{<:Real}, @@ -516,15 +514,13 @@ julia> @variable(model, y); julia> @objective(model, Min, 3x + 2y + 1) 3 x + 2 y + 1 -julia> set_objective_coefficients(model, [x, y], [5, 4]) +julia> set_objective_coefficient(model, [x, y], [5, 4]) julia> objective_function(model) 5 x + 4 y + 1 ``` - -See also [`set_objective_coefficient`](@ref). """ -function set_objective_coefficients( +function set_objective_coefficient( model::GenericModel{T}, variables::AbstractVector{<:GenericVariableRef{T}}, coeffs::AbstractVector{<:Real}, @@ -541,12 +537,12 @@ function set_objective_coefficients( end coeffs_t = convert.(T, coeffs)::AbstractVector{T} F = objective_function_type(model) - _set_objective_coefficients(model, variables, coeffs_t, F) + _set_objective_coefficient(model, variables, coeffs_t, F) model.is_model_dirty = true return end -function _set_objective_coefficients( +function _set_objective_coefficient( model::GenericModel{T}, variables::AbstractVector{<:GenericVariableRef{T}}, coeffs::AbstractVector{<:T}, @@ -571,7 +567,7 @@ function _set_objective_coefficients( return end -function _set_objective_coefficients( +function _set_objective_coefficient( model::GenericModel{T}, variables::AbstractVector{<:GenericVariableRef{T}}, coeffs::AbstractVector{<:T}, @@ -615,8 +611,6 @@ julia> set_objective_coefficient(model, x[1], x[2], 3) julia> objective_function(model) 2 x[1]² + 3 x[1]*x[2] ``` - -See also [`set_objective_coefficients`](@ref). """ function set_objective_coefficient( model::GenericModel{T}, @@ -670,7 +664,7 @@ function _set_objective_coefficient( end """ - set_objective_coefficients( + set_objective_coefficient( model::GenericModel{T}, variables_1::AbstractVector{ @variable(model, x[1:2]); julia> @objective(model, Min, x[1]^2 + x[1] * x[2]) x[1]² + x[1]*x[2] -julia> set_objective_coefficients(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) +julia> set_objective_coefficient(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) julia> objective_function(model) 2 x[1]² + 3 x[1]*x[2] ``` - -See also [`set_objective_coefficient`](@ref). """ -function set_objective_coefficients( +function set_objective_coefficient( model::GenericModel{T}, variables_1::AbstractVector{<:GenericVariableRef{T}}, variables_2::AbstractVector{<:GenericVariableRef{T}}, @@ -718,12 +710,12 @@ function set_objective_coefficients( end coeffs_t = convert.(T, coeffs)::AbstractVector{<:T} F = moi_function_type(objective_function_type(model)) - _set_objective_coefficients(model, variables_1, variables_2, coeffs_t, F) + _set_objective_coefficient(model, variables_1, variables_2, coeffs_t, F) model.is_model_dirty = true return end -function _set_objective_coefficients( +function _set_objective_coefficient( model::GenericModel{T}, variables_1::AbstractVector{<:GenericVariableRef{T}}, variables_2::AbstractVector{<:GenericVariableRef{T}}, @@ -742,7 +734,7 @@ function _set_objective_coefficients( return end -function _set_objective_coefficients( +function _set_objective_coefficient( model::GenericModel{T}, variables_1::AbstractVector{<:GenericVariableRef{T}}, variables_2::AbstractVector{<:GenericVariableRef{T}}, diff --git a/src/variables.jl b/src/variables.jl index fbdbea6e5be..a8ad86f8d65 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -2550,8 +2550,6 @@ julia> set_normalized_coefficient(con, x, 4) julia> con con : 4 x ≤ 2 ``` - -See also [`set_normalized_coefficients`](@ref). """ function set_normalized_coefficient( con_ref::ConstraintRef{<:AbstractModel,<:MOI.ConstraintIndex{F}}, @@ -2569,7 +2567,7 @@ function set_normalized_coefficient( end """ - set_normalized_coefficients( + set_normalized_coefficient( constraints::AbstractVector{<:ConstraintRef}, variables::AbstractVector{<:GenericVariableRef}, values::AbstractVector{<:Number}, @@ -2579,7 +2577,7 @@ Set multiple coefficient of `variables` in the constraints `constraints` to `val Note that prior to this step, JuMP will aggregate multiple terms containing the same variable. For example, given a constraint `2x + 3x <= 2`, -`set_normalized_coefficients(con, [x], [4])` will create the constraint `4x <= 2`. +`set_normalized_coefficient(con, [x], [4])` will create the constraint `4x <= 2`. ## Example @@ -2595,15 +2593,13 @@ y julia> @constraint(model, con, 2x + 3x + 4y <= 2) con : 5 x + 4 y ≤ 2 -julia> set_normalized_coefficients([con, con], [x, y], [6, 7]) +julia> set_normalized_coefficient([con, con], [x, y], [6, 7]) julia> con con : 6 x + 7 y ≤ 2 ``` - -See also [`set_normalized_coefficients`](@ref). """ -function set_normalized_coefficients( +function set_normalized_coefficient( constraints::AbstractVector{ <:ConstraintRef{<:AbstractModel,<:MOI.ConstraintIndex{F}}, }, @@ -2707,8 +2703,6 @@ julia> set_normalized_coefficient(con, x[1], x[2], 5) julia> con con : 4 x[1]² + 5 x[1]*x[2] + x[2] ≤ 2 ``` - -See also [`set_normalized_coefficients`](@ref). """ function set_normalized_coefficient( constraint::ConstraintRef{<:AbstractModel,<:MOI.ConstraintIndex{F}}, @@ -2735,7 +2729,7 @@ function set_normalized_coefficient( end """ - set_normalized_coefficients( + set_normalized_coefficient( constraints::AbstractVector{<:ConstraintRef}, variables_1:AbstractVector{<:GenericVariableRef}, variables_2:AbstractVector{<:GenericVariableRef}, @@ -2747,7 +2741,7 @@ the constraints `constraints` to `values`. Note that prior to this step, JuMP will aggregate multiple terms containing the same variable. For example, given a constraint `2x^2 + 3x^2 <= 2`, -`set_normalized_coefficients(con, [x], [x], [4])` will create the constraint `4x^2 <= 2`. +`set_normalized_coefficient(con, [x], [x], [4])` will create the constraint `4x^2 <= 2`. ## Example @@ -2759,15 +2753,13 @@ julia> @variable(model, x[1:2]); julia> @constraint(model, con, 2x[1]^2 + 3 * x[1] * x[2] + x[2] <= 2) con : 2 x[1]² + 3 x[1]*x[2] + x[2] ≤ 2 -julia> set_normalized_coefficients([con, con], [x[1], x[1]], [x[1], x[2]], [4, 5]) +julia> set_normalized_coefficient([con, con], [x[1], x[1]], [x[1], x[2]], [4, 5]) julia> con con : 4 x[1]² + 5 x[1]*x[2] + x[2] ≤ 2 ``` - -See also [`set_normalized_coefficients`](@ref). """ -function set_normalized_coefficients( +function set_normalized_coefficient( constraints::AbstractVector{ <:ConstraintRef{<:AbstractModel,<:MOI.ConstraintIndex{F}}, }, diff --git a/test/test_constraint.jl b/test/test_constraint.jl index e8d445ea8ca..52f7ad31874 100644 --- a/test/test_constraint.jl +++ b/test/test_constraint.jl @@ -987,15 +987,15 @@ function test_change_coefficient_batch() con_ref = @constraint(model, 2 * x + 3 * y == -1) @test normalized_coefficient(con_ref, x) == 2.0 @test normalized_coefficient(con_ref, y) == 3.0 - set_normalized_coefficients([con_ref, con_ref], [x, y], [1.0, 4.0]) + set_normalized_coefficient([con_ref, con_ref], [x, y], [1.0, 4.0]) @test normalized_coefficient(con_ref, x) == 1.0 @test normalized_coefficient(con_ref, y) == 4.0 - set_normalized_coefficients([con_ref, con_ref], [x, y], [3, 4]) # Check type promotion. + set_normalized_coefficient([con_ref, con_ref], [x, y], [3, 4]) # Check type promotion. @test normalized_coefficient(con_ref, x) == 3.0 @test normalized_coefficient(con_ref, y) == 4.0 quad_con = @constraint(model, x^2 == 0) @test normalized_coefficient(quad_con, x) == 0.0 - set_normalized_coefficients([quad_con, quad_con], [x, y], [2, 7]) + set_normalized_coefficient([quad_con, quad_con], [x, y], [2, 7]) @test normalized_coefficient(quad_con, x) == 2.0 @test normalized_coefficient(quad_con, y) == 7.0 @test isequal_canonical(constraint_object(quad_con).func, x^2 + 2x + 7y) @@ -1003,7 +1003,7 @@ function test_change_coefficient_batch() DimensionMismatch( "The number of constraints, variables and coefficients must match", ), - set_normalized_coefficients([con_ref], [x, y], [4, 5]), + set_normalized_coefficient([con_ref], [x, y], [4, 5]), ) return end @@ -1863,14 +1863,14 @@ function test_set_normalized_coefficient_quadratic_batch() @constraint(model, con, 2x[1]^2 + 3 * x[1] * x[2] + x[2] <= 2) @test normalized_coefficient(con, x[1], x[1]) == 2.0 @test normalized_coefficient(con, x[1], x[2]) == 3.0 - set_normalized_coefficients([con, con], [x[1], x[1]], [x[1], x[2]], [4, 5]) + set_normalized_coefficient([con, con], [x[1], x[1]], [x[1], x[2]], [4, 5]) @test normalized_coefficient(con, x[1], x[1]) == 4.0 @test normalized_coefficient(con, x[1], x[2]) == 5.0 @test_throws( DimensionMismatch( "The number of constraints, variables and coefficients must match", ), - set_normalized_coefficients([con], [x[1], x[1]], [x[1], x[2]], [4, 5]), + set_normalized_coefficient([con], [x[1], x[1]], [x[1], x[2]], [4, 5]), ) return end diff --git a/test/test_objective.jl b/test/test_objective.jl index 73f35961498..bbd8500e9a8 100644 --- a/test/test_objective.jl +++ b/test/test_objective.jl @@ -67,7 +67,7 @@ function test_objective_coef_update_linear_objective_batch_error() @NLobjective(model, Min, x[1] * x[2]) @test_throws( ErrorException("A nonlinear objective is already set in the model"), - set_objective_coefficients(model, [x[1], x[2]], [2, 3]), + set_objective_coefficient(model, [x[1], x[2]], [2, 3]), ) return end @@ -80,7 +80,7 @@ function test_objective_coef_update_linear_objective_batch_dimension_error() DimensionMismatch( "The number of variables and coefficients must match", ), - set_objective_coefficients(model, [x[1], x[2]], [2]), + set_objective_coefficient(model, [x[1], x[2]], [2]), ) return end @@ -90,16 +90,16 @@ function test_objective_coef_batch_update_linear_objective_changes() @variable(model, x) @variable(model, y) @objective(model, Max, x) - set_objective_coefficients(model, [x, y], [4.0, 5.0]) + set_objective_coefficient(model, [x, y], [4.0, 5.0]) @test isequal_canonical(objective_function(model), 4x + 5y) @objective(model, Max, x + y) - set_objective_coefficients(model, [x, y], [4.0, 5.0]) + set_objective_coefficient(model, [x, y], [4.0, 5.0]) @test isequal_canonical(objective_function(model), 4x + 5y) @objective(model, Min, x) - set_objective_coefficients(model, [x], [2.0]) + set_objective_coefficient(model, [x], [2.0]) @test isequal_canonical(objective_function(model), 2x) @objective(model, Min, x) - set_objective_coefficients(model, [y], [2.0]) + set_objective_coefficient(model, [y], [2.0]) @test isequal_canonical(objective_function(model), x + 2y) return end @@ -118,7 +118,7 @@ function test_objective_coef_update_quadratic_objective_batch_changes() @variable(model, x) @variable(model, y) @objective(model, Max, x^2 + x + y) - set_objective_coefficients(model, [x, y], [4.0, 5.0]) + set_objective_coefficient(model, [x, y], [4.0, 5.0]) @test isequal_canonical(objective_function(model), x^2 + 4x + 5y) return end @@ -303,12 +303,12 @@ function test_set_objective_coefficient_quadratic_batch() model = Model() @variable(model, x[1:2]) @objective(model, Min, x[1]^2 + x[1] * x[2] + x[1] + 2) - set_objective_coefficients(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) + set_objective_coefficient(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) @test isequal_canonical( objective_function(model), 2 * x[1]^2 + 3 * x[1] * x[2] + x[1] + 2, ) - set_objective_coefficients(model, [x[2]], [x[1]], [4]) + set_objective_coefficient(model, [x[2]], [x[1]], [4]) @test isequal_canonical( objective_function(model), 2 * x[1]^2 + 4 * x[1] * x[2] + x[1] + 2, @@ -333,7 +333,7 @@ function test_set_objective_coefficient_quadratic_batch_error() @NLobjective(model, Min, x[1] * x[2]) @test_throws( ErrorException("A nonlinear objective is already set in the model"), - set_objective_coefficients(model, [x[1]], [x[1]], [2]), + set_objective_coefficient(model, [x[1]], [x[1]], [2]), ) return end @@ -346,7 +346,7 @@ function test_set_objective_coefficient_quadratic_batch_dimension_error() DimensionMismatch( "The number of variables and coefficients must match", ), - set_objective_coefficients(model, [x[1]], [x[1]], [2, 3]), + set_objective_coefficient(model, [x[1]], [x[1]], [2, 3]), ) return end @@ -368,7 +368,7 @@ function test_set_objective_coefficient_quadratic_batch_affine_original() model = Model() @variable(model, x[1:2]) @objective(model, Min, x[1] + 2) - set_objective_coefficients(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) + set_objective_coefficient(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) @test isequal_canonical( objective_function(model), 2 * x[1]^2 + 3 * x[1] * x[2] + x[1] + 2, @@ -393,7 +393,7 @@ function test_set_objective_coefficient_quadratic_batch_variable_original() model = Model() @variable(model, x[1:2]) @objective(model, Min, x[1]) - set_objective_coefficients(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) + set_objective_coefficient(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) @test isequal_canonical( objective_function(model), 2 * x[1]^2 + 3 * x[1] * x[2] + x[1], @@ -416,7 +416,7 @@ end function test_set_objective_coefficient_quadratic_batch_nothing_set() model = Model() @variable(model, x[1:2]) - set_objective_coefficients(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) + set_objective_coefficient(model, [x[1], x[1]], [x[1], x[2]], [2, 3]) @test isequal_canonical( objective_function(model), 2 * x[1]^2 + 3 * x[1] * x[2], From 96bd014bfc13e4e881e0f1b103028c9511ace699 Mon Sep 17 00:00:00 2001 From: Joaquim Dias Garcia Date: Sat, 23 Mar 2024 19:03:48 -0300 Subject: [PATCH 14/22] Update src/variables.jl --- src/variables.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/variables.jl b/src/variables.jl index a8ad86f8d65..ae152d2c9e7 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -2653,8 +2653,6 @@ julia> set_normalized_coefficients(con, x, [(1, 2.0), (2, 5.0)]) julia> con con : [2 x, 5 x] ∈ MathOptInterface.Nonnegatives(2) ``` - -See also [`set_normalized_coefficient`](@ref). """ function set_normalized_coefficients( constraint::ConstraintRef{<:AbstractModel,<:MOI.ConstraintIndex{F}}, From e1211401fdcfd60efc59d6012c162d890191a878 Mon Sep 17 00:00:00 2001 From: odow Date: Mon, 25 Mar 2024 11:32:56 +1300 Subject: [PATCH 15/22] Minor tweaks --- src/objective.jl | 63 ++++++++++++++++-------------------------------- src/variables.jl | 32 +++++++++++------------- 2 files changed, 35 insertions(+), 60 deletions(-) diff --git a/src/objective.jl b/src/objective.jl index 779b1925515..05e7cb7c646 100644 --- a/src/objective.jl +++ b/src/objective.jl @@ -498,7 +498,8 @@ end coefficients::Vector{<:Real}, ) -Set multiple linear objective coefficients associated with `variables` to `coefficients`, in a single call. +Set multiple linear objective coefficients associated with `variables` to +`coefficients`, in a single call. Note: this function will throw an error if a nonlinear objective is set. @@ -527,17 +528,12 @@ function set_objective_coefficient( ) where {T} if _nlp_objective_function(model) !== nothing error("A nonlinear objective is already set in the model") + elseif length(variables) != length(coeffs) + msg = "The number of variables and coefficients must match" + throw(DimensionMismatch(msg)) end - if length(variables) != length(coeffs) - throw( - DimensionMismatch( - "The number of variables and coefficients must match", - ), - ) - end - coeffs_t = convert.(T, coeffs)::AbstractVector{T} F = objective_function_type(model) - _set_objective_coefficient(model, variables, coeffs_t, F) + _set_objective_coefficient(model, variables, convert.(T, coeffs), F) model.is_model_dirty = true return end @@ -548,22 +544,15 @@ function _set_objective_coefficient( coeffs::AbstractVector{<:T}, ::Type{GenericVariableRef{T}}, ) where {T} - current_obj = objective_function(model) - current_obj_index = index(current_obj) - if length(variables) > 0 - position = findfirst(x -> index(x) == current_obj_index, variables) - if position === nothing - set_objective_function( - model, - add_to_expression!( - LinearAlgebra.dot(coeffs, variables), - current_obj, - ), - ) - else - set_objective_function(model, LinearAlgebra.dot(coeffs, variables)) - end + if length(variables) == 0 + return + end + new_objective = coeffs' * variables + current_obj = objective_function(model)::GenericVariableRef{T} + if !(current_obj in variables) + add_to_expression!(new_objective, current_obj) end + set_objective_function(model, new_objective) return end @@ -700,13 +689,9 @@ function set_objective_coefficient( ) where {T} if _nlp_objective_function(model) !== nothing error("A nonlinear objective is already set in the model") - end - if !(length(variables_1) == length(variables_2) == length(coeffs)) - throw( - DimensionMismatch( - "The number of variables and coefficients must match", - ), - ) + elseif !(length(variables_1) == length(variables_2) == length(coeffs)) + msg = "The number of variables and coefficients must match" + throw(DimensionMismatch(msg)) end coeffs_t = convert.(T, coeffs)::AbstractVector{<:T} F = moi_function_type(objective_function_type(model)) @@ -722,14 +707,8 @@ function _set_objective_coefficient( coeffs::AbstractVector{<:T}, ::Type{F}, ) where {T,F} - current_obj = objective_function(model) - new_obj = add_to_expression!( - sum( - coeffs[i] * variables_1[i] * variables_2[i] for - i in eachindex(coeffs) - ), - current_obj, - ) + new_obj = @expression(model, sum(coeffs .* variables_1 .* variables_2)) + add_to_expression!(new_obj, objective_function(model)) set_objective_function(model, new_obj) return end @@ -741,8 +720,8 @@ function _set_objective_coefficient( coeffs::AbstractVector{<:T}, ::Type{MOI.ScalarQuadraticFunction{T}}, ) where {T} - for i in eachindex(variables_1) - if variables_1[i] == variables_2[i] + for (i, x, y) in zip(eachindex(coeffs), variables_1, variables_2) + if x == y coeffs[i] *= T(2) end end diff --git a/src/variables.jl b/src/variables.jl index ae152d2c9e7..d8ec818cb0c 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -2573,7 +2573,8 @@ end values::AbstractVector{<:Number}, ) -Set multiple coefficient of `variables` in the constraints `constraints` to `values`. +Set multiple coefficient of `variables` in the constraints `constraints` to +`values`. Note that prior to this step, JuMP will aggregate multiple terms containing the same variable. For example, given a constraint `2x + 3x <= 2`, @@ -2607,11 +2608,8 @@ function set_normalized_coefficient( coeffs::AbstractVector{<:Number}, ) where {T,F<:Union{MOI.ScalarAffineFunction{T},MOI.ScalarQuadraticFunction{T}}} if !(length(constraints) == length(variables) == length(coeffs)) - throw( - DimensionMismatch( - "The number of constraints, variables and coefficients must match", - ), - ) + msg = "The number of constraints, variables and coefficients must match" + throw(DimensionMismatch(msg)) end model = owner_model(first(constraints)) MOI.modify( @@ -2734,12 +2732,13 @@ end values::AbstractVector{<:Number}, ) -Set multiple quadratic coefficients associated with `variables_1` and `variables_2` in -the constraints `constraints` to `values`. +Set multiple quadratic coefficients associated with `variables_1` and +`variables_2` in the constraints `constraints` to `values`. Note that prior to this step, JuMP will aggregate multiple terms containing the same variable. For example, given a constraint `2x^2 + 3x^2 <= 2`, -`set_normalized_coefficient(con, [x], [x], [4])` will create the constraint `4x^2 <= 2`. +`set_normalized_coefficient(con, [x], [x], [4])` will create the constraint +`4x^2 <= 2`. ## Example @@ -2765,21 +2764,18 @@ function set_normalized_coefficient( variables_2::AbstractVector{<:AbstractVariableRef}, coeffs::AbstractVector{<:Number}, ) where {T,F<:MOI.ScalarQuadraticFunction{T}} - if !( + dimension_match = length(constraints) == length(variables_1) == length(variables_2) == length(coeffs) - ) - throw( - DimensionMismatch( - "The number of constraints, variables and coefficients must match", - ), - ) + if !dimension_match + msg = "The number of constraints, variables and coefficients must match" + throw(DimensionMismatch(msg)) end new_coeffs = convert.(T, coeffs) - for i in eachindex(variables_1) - if variables_1[i] == variables_2[i] + for (i, x, y) in zip(eachindex(new_coeffs), variables_1, variables_2) + if x == y new_coeffs[i] *= T(2) end end From 94d5c56a9f83775a810ae3f026a625f5b85e4194 Mon Sep 17 00:00:00 2001 From: odow Date: Mon, 25 Mar 2024 11:41:03 +1300 Subject: [PATCH 16/22] Update --- src/objective.jl | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/objective.jl b/src/objective.jl index 05e7cb7c646..ec624427049 100644 --- a/src/objective.jl +++ b/src/objective.jl @@ -702,13 +702,16 @@ end function _set_objective_coefficient( model::GenericModel{T}, - variables_1::AbstractVector{<:GenericVariableRef{T}}, - variables_2::AbstractVector{<:GenericVariableRef{T}}, + variables_1::AbstractVector{<:V}, + variables_2::AbstractVector{<:V}, coeffs::AbstractVector{<:T}, ::Type{F}, -) where {T,F} - new_obj = @expression(model, sum(coeffs .* variables_1 .* variables_2)) +) where {T,F,V<:GenericVariableRef{T}} + new_obj = GenericQuadExpr{T,V}() add_to_expression!(new_obj, objective_function(model)) + for (c, x, y) in zip(coeffs, variables_1, variables_2) + add_to_expression!(new_obj, c, x, y) + end set_objective_function(model, new_obj) return end From 2b4174a87b6770d7611b4bc03cbfefa0cdb301ec Mon Sep 17 00:00:00 2001 From: odow Date: Mon, 25 Mar 2024 12:06:14 +1300 Subject: [PATCH 17/22] Update --- src/objective.jl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/objective.jl b/src/objective.jl index ec624427049..a6ba5f1c1b9 100644 --- a/src/objective.jl +++ b/src/objective.jl @@ -544,10 +544,7 @@ function _set_objective_coefficient( coeffs::AbstractVector{<:T}, ::Type{GenericVariableRef{T}}, ) where {T} - if length(variables) == 0 - return - end - new_objective = coeffs' * variables + new_objective = LinearAlgebra.dot(coeffs, variables) current_obj = objective_function(model)::GenericVariableRef{T} if !(current_obj in variables) add_to_expression!(new_objective, current_obj) From 5da61b4b27fde069d1ea002eb8b39d27ede4fb9d Mon Sep 17 00:00:00 2001 From: Joaquim Garcia Date: Wed, 27 Mar 2024 01:28:30 -0300 Subject: [PATCH 18/22] [no ci] fix docs --- src/objective.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/objective.jl b/src/objective.jl index a6ba5f1c1b9..e4db2c0e490 100644 --- a/src/objective.jl +++ b/src/objective.jl @@ -652,9 +652,9 @@ end """ set_objective_coefficient( model::GenericModel{T}, - variables_1::AbstractVector{ Date: Wed, 27 Mar 2024 01:28:44 -0300 Subject: [PATCH 19/22] add docs for batch rhs change --- src/constraints.jl | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/constraints.jl b/src/constraints.jl index e35b43dbf46..397cc826b4d 100644 --- a/src/constraints.jl +++ b/src/constraints.jl @@ -773,6 +773,41 @@ function set_normalized_rhs( return end +""" + set_normalized_rhs( + constraints::AbstractVector{<:ConstraintRef}, + values::AbstractVector{<:Number} + ) + +Set the right-hand side terms of all `constraints` to `values`. + +Note that prior to this step, JuMP will aggregate all constant terms onto the +right-hand side of the constraint. For example, given a constraint `2x + 1 <= +2`, `set_normalized_rhs([con], [4])` will create the constraint `2x <= 4`, not `2x + +1 <= 4`. + +## Example + +```jldoctest; filter=r"≤|<=" +julia> model = Model(); + +julia> @variable(model, x); + +julia> @constraint(model, con1, 2x + 1 <= 2) +con : 2 x ≤ 1 + +julia> @constraint(model, con2, 3x + 2 <= 4) +con : 3 x ≤ 2 + +julia> set_normalized_rhs([con1, con2], [4, 5]) + +julia> con1 +con1 : 2 x ≤ 4 + +julia> con2 +con1 : 3 x ≤ 5 +``` +""" function set_normalized_rhs( constraints::AbstractVector{ <:ConstraintRef{<:AbstractModel,MOI.ConstraintIndex{F,S}}, From a7ca2d6df250deff133c217567033709080dc8b1 Mon Sep 17 00:00:00 2001 From: Joaquim Garcia Date: Wed, 27 Mar 2024 01:29:10 -0300 Subject: [PATCH 20/22] fix docs --- src/constraints.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/constraints.jl b/src/constraints.jl index 397cc826b4d..a2bf644afa6 100644 --- a/src/constraints.jl +++ b/src/constraints.jl @@ -794,10 +794,10 @@ julia> model = Model(); julia> @variable(model, x); julia> @constraint(model, con1, 2x + 1 <= 2) -con : 2 x ≤ 1 +con1 : 2 x ≤ 1 julia> @constraint(model, con2, 3x + 2 <= 4) -con : 3 x ≤ 2 +con2 : 3 x ≤ 2 julia> set_normalized_rhs([con1, con2], [4, 5]) From 3d8ad8db22616e05eacea593c7d54adfc3d7706b Mon Sep 17 00:00:00 2001 From: Joaquim Garcia Date: Wed, 27 Mar 2024 02:01:56 -0300 Subject: [PATCH 21/22] fix docs --- src/constraints.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constraints.jl b/src/constraints.jl index a2bf644afa6..110fb200788 100644 --- a/src/constraints.jl +++ b/src/constraints.jl @@ -805,7 +805,7 @@ julia> con1 con1 : 2 x ≤ 4 julia> con2 -con1 : 3 x ≤ 5 +con2 : 3 x ≤ 5 ``` """ function set_normalized_rhs( From 48012e9ab9514ea39d0c3922e95890961817586c Mon Sep 17 00:00:00 2001 From: odow Date: Fri, 29 Mar 2024 10:48:49 +1300 Subject: [PATCH 22/22] Update --- src/objective.jl | 34 +++++++++++++++++++++++++--------- src/variables.jl | 16 +++++++--------- test/test_constraint.jl | 4 ++-- test/test_objective.jl | 24 ++++++++++++++++++------ 4 files changed, 52 insertions(+), 26 deletions(-) diff --git a/src/objective.jl b/src/objective.jl index e4db2c0e490..f75b645a66b 100644 --- a/src/objective.jl +++ b/src/objective.jl @@ -450,7 +450,10 @@ function set_objective_coefficient( coeff::Real, ) where {T} if _nlp_objective_function(model) !== nothing - error("A nonlinear objective is already set in the model") + error( + "A nonlinear objective created by the legacy `@NLobjective` is " * + "set in the model. This does not support modification.", + ) end coeff_t = convert(T, coeff)::T F = objective_function_type(model) @@ -527,9 +530,14 @@ function set_objective_coefficient( coeffs::AbstractVector{<:Real}, ) where {T} if _nlp_objective_function(model) !== nothing - error("A nonlinear objective is already set in the model") - elseif length(variables) != length(coeffs) - msg = "The number of variables and coefficients must match" + error( + "A nonlinear objective created by the legacy `@NLobjective` is " * + "set in the model. This does not support modification.", + ) + end + n, m = length(variables), length(coeffs) + if !(n == m) + msg = "The number of variables ($n) and coefficients ($m) must match" throw(DimensionMismatch(msg)) end F = objective_function_type(model) @@ -605,7 +613,10 @@ function set_objective_coefficient( coeff::Real, ) where {T} if _nlp_objective_function(model) !== nothing - error("A nonlinear objective is already set in the model") + error( + "A nonlinear objective created by the legacy `@NLobjective` is " * + "set in the model. This does not support modification.", + ) end coeff_t = convert(T, coeff)::T F = moi_function_type(objective_function_type(model)) @@ -685,12 +696,17 @@ function set_objective_coefficient( coeffs::AbstractVector{<:Real}, ) where {T} if _nlp_objective_function(model) !== nothing - error("A nonlinear objective is already set in the model") - elseif !(length(variables_1) == length(variables_2) == length(coeffs)) - msg = "The number of variables and coefficients must match" + error( + "A nonlinear objective created by the legacy `@NLobjective` is " * + "set in the model. This does not support modification.", + ) + end + n1, n2, m = length(variables_1), length(variables_2), length(coeffs) + if !(n1 == n2 == m) + msg = "The number of variables ($n1, $n2) and coefficients ($m) must match" throw(DimensionMismatch(msg)) end - coeffs_t = convert.(T, coeffs)::AbstractVector{<:T} + coeffs_t = convert.(T, coeffs) F = moi_function_type(objective_function_type(model)) _set_objective_coefficient(model, variables_1, variables_2, coeffs_t, F) model.is_model_dirty = true diff --git a/src/variables.jl b/src/variables.jl index d8ec818cb0c..daa7003ff89 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -2607,8 +2607,9 @@ function set_normalized_coefficient( variables::AbstractVector{<:AbstractVariableRef}, coeffs::AbstractVector{<:Number}, ) where {T,F<:Union{MOI.ScalarAffineFunction{T},MOI.ScalarQuadraticFunction{T}}} - if !(length(constraints) == length(variables) == length(coeffs)) - msg = "The number of constraints, variables and coefficients must match" + c, n, m = length(constraints), length(variables), length(coeffs) + if !(c == n == m) + msg = "The number of constraints ($c), variables ($n) and coefficients ($m) must match" throw(DimensionMismatch(msg)) end model = owner_model(first(constraints)) @@ -2764,13 +2765,10 @@ function set_normalized_coefficient( variables_2::AbstractVector{<:AbstractVariableRef}, coeffs::AbstractVector{<:Number}, ) where {T,F<:MOI.ScalarQuadraticFunction{T}} - dimension_match = - length(constraints) == - length(variables_1) == - length(variables_2) == - length(coeffs) - if !dimension_match - msg = "The number of constraints, variables and coefficients must match" + c, m = length(constraints), length(coeffs) + n1, n2 = length(variables_1), length(variables_1) + if !(c == n1 == n2 == m) + msg = "The number of constraints ($c), variables ($n1, $n2) and coefficients ($m) must match" throw(DimensionMismatch(msg)) end new_coeffs = convert.(T, coeffs) diff --git a/test/test_constraint.jl b/test/test_constraint.jl index 52f7ad31874..08c89342fbc 100644 --- a/test/test_constraint.jl +++ b/test/test_constraint.jl @@ -1001,7 +1001,7 @@ function test_change_coefficient_batch() @test isequal_canonical(constraint_object(quad_con).func, x^2 + 2x + 7y) @test_throws( DimensionMismatch( - "The number of constraints, variables and coefficients must match", + "The number of constraints (1), variables (2) and coefficients (2) must match", ), set_normalized_coefficient([con_ref], [x, y], [4, 5]), ) @@ -1868,7 +1868,7 @@ function test_set_normalized_coefficient_quadratic_batch() @test normalized_coefficient(con, x[1], x[2]) == 5.0 @test_throws( DimensionMismatch( - "The number of constraints, variables and coefficients must match", + "The number of constraints (1), variables (2, 2) and coefficients (2) must match", ), set_normalized_coefficient([con], [x[1], x[1]], [x[1], x[2]], [4, 5]), ) diff --git a/test/test_objective.jl b/test/test_objective.jl index bbd8500e9a8..553b0865243 100644 --- a/test/test_objective.jl +++ b/test/test_objective.jl @@ -55,7 +55,10 @@ function test_objective_coef_update_linear_objective_error() @variable(model, x[1:2]) @NLobjective(model, Min, x[1] * x[2]) @test_throws( - ErrorException("A nonlinear objective is already set in the model"), + ErrorException( + "A nonlinear objective created by the legacy `@NLobjective` is " * + "set in the model. This does not support modification.", + ), set_objective_coefficient(model, x[1], 2), ) return @@ -66,7 +69,10 @@ function test_objective_coef_update_linear_objective_batch_error() @variable(model, x[1:2]) @NLobjective(model, Min, x[1] * x[2]) @test_throws( - ErrorException("A nonlinear objective is already set in the model"), + ErrorException( + "A nonlinear objective created by the legacy `@NLobjective` is " * + "set in the model. This does not support modification.", + ), set_objective_coefficient(model, [x[1], x[2]], [2, 3]), ) return @@ -78,7 +84,7 @@ function test_objective_coef_update_linear_objective_batch_dimension_error() @objective(model, Min, x[1] * x[2]) @test_throws( DimensionMismatch( - "The number of variables and coefficients must match", + "The number of variables (2) and coefficients (1) must match", ), set_objective_coefficient(model, [x[1], x[2]], [2]), ) @@ -321,7 +327,10 @@ function test_set_objective_coefficient_quadratic_error() @variable(model, x[1:2]) @NLobjective(model, Min, x[1] * x[2]) @test_throws( - ErrorException("A nonlinear objective is already set in the model"), + ErrorException( + "A nonlinear objective created by the legacy `@NLobjective` is " * + "set in the model. This does not support modification.", + ), set_objective_coefficient(model, x[1], x[1], 2), ) return @@ -332,7 +341,10 @@ function test_set_objective_coefficient_quadratic_batch_error() @variable(model, x[1:2]) @NLobjective(model, Min, x[1] * x[2]) @test_throws( - ErrorException("A nonlinear objective is already set in the model"), + ErrorException( + "A nonlinear objective created by the legacy `@NLobjective` is " * + "set in the model. This does not support modification.", + ), set_objective_coefficient(model, [x[1]], [x[1]], [2]), ) return @@ -344,7 +356,7 @@ function test_set_objective_coefficient_quadratic_batch_dimension_error() @objective(model, Min, x[1] * x[2]) @test_throws( DimensionMismatch( - "The number of variables and coefficients must match", + "The number of variables (1, 1) and coefficients (2) must match", ), set_objective_coefficient(model, [x[1]], [x[1]], [2, 3]), )