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 batched modification methods #3716

Merged
merged 22 commits into from
Mar 28, 2024
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(
odow marked this conversation as resolved.
Show resolved Hide resolved
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)
odow marked this conversation as resolved.
Show resolved Hide resolved
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
Loading