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
166 changes: 165 additions & 1 deletion src/objective.jl
Original file line number Diff line number Diff line change
Expand Up @@ -491,11 +491,87 @@ 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 is already set in the model")
odow marked this conversation as resolved.
Show resolved Hide resolved
elseif length(variables) != length(coeffs)
msg = "The number of variables and coefficients must match"
odow marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -572,3 +648,91 @@ 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 is already set in the model")
elseif !(length(variables_1) == length(variables_2) == length(coeffs))
msg = "The number of variables and coefficients must match"
odow marked this conversation as resolved.
Show resolved Hide resolved
throw(DimensionMismatch(msg))
end
coeffs_t = convert.(T, coeffs)::AbstractVector{<:T}
odow marked this conversation as resolved.
Show resolved Hide resolved
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