Skip to content

Commit

Permalink
Add batched modification methods (#3716)
Browse files Browse the repository at this point in the history
  • Loading branch information
joaquimg authored Mar 28, 2024
1 parent c3cefe0 commit c0dca14
Show file tree
Hide file tree
Showing 5 changed files with 585 additions and 8 deletions.
58 changes: 56 additions & 2 deletions src/constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand All @@ -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}},
Expand All @@ -773,6 +773,60 @@ 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)
con1 : 2 x ≤ 1
julia> @constraint(model, con2, 3x + 2 <= 4)
con2 : 3 x ≤ 2
julia> set_normalized_rhs([con1, con2], [4, 5])
julia> con1
con1 : 2 x ≤ 4
julia> con2
con2 : 3 x ≤ 5
```
"""
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(
backend(owner_model(first(constraints))),
MOI.ConstraintSet(),
index.(constraints),
S.(convert.(T, values)),
)
return
end

"""
normalized_rhs(constraint::ConstraintRef)
Expand Down
186 changes: 183 additions & 3 deletions src/objective.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -491,11 +494,92 @@ function _set_objective_coefficient(
return
end

"""
set_objective_coefficient(
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_coefficient(model, [x, y], [5, 4])
julia> objective_function(model)
5 x + 4 y + 1
```
"""
function set_objective_coefficient(
model::GenericModel{T},
variables::AbstractVector{<:GenericVariableRef{T}},
coeffs::AbstractVector{<:Real},
) where {T}
if _nlp_objective_function(model) !== nothing
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)
_set_objective_coefficient(model, variables, convert.(T, coeffs), F)
model.is_model_dirty = true
return
end

function _set_objective_coefficient(
model::GenericModel{T},
variables::AbstractVector{<:GenericVariableRef{T}},
coeffs::AbstractVector{<:T},
::Type{GenericVariableRef{T}},
) where {T}
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)
end
set_objective_function(model, new_objective)
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}
Expand Down Expand Up @@ -529,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))
Expand Down Expand Up @@ -572,3 +659,96 @@ function _set_objective_coefficient(
)
return
end

"""
set_objective_coefficient(
model::GenericModel{T},
variables_1::AbstractVector{<:GenericVariableRef{T}},
variables_2::AbstractVector{<:GenericVariableRef{T}},
coefficients::AbstractVector{<: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]
```
"""
function set_objective_coefficient(
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 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)
F = moi_function_type(objective_function_type(model))
_set_objective_coefficient(model, variables_1, variables_2, coeffs_t, F)
model.is_model_dirty = true
return
end

function _set_objective_coefficient(
model::GenericModel{T},
variables_1::AbstractVector{<:V},
variables_2::AbstractVector{<:V},
coeffs::AbstractVector{<:T},
::Type{F},
) 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

function _set_objective_coefficient(
model::GenericModel{T},
variables_1::AbstractVector{<:GenericVariableRef{T}},
variables_2::AbstractVector{<:GenericVariableRef{T}},
coeffs::AbstractVector{<:T},
::Type{MOI.ScalarQuadraticFunction{T}},
) where {T}
for (i, x, y) in zip(eachindex(coeffs), variables_1, variables_2)
if x == y
coeffs[i] *= T(2)
end
end
MOI.modify(
backend(model),
MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{T}}(),
MOI.ScalarQuadraticCoefficientChange.(
index.(variables_1),
index.(variables_2),
coeffs,
),
)
return
end
Loading

0 comments on commit c0dca14

Please sign in to comment.