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
23 changes: 21 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,25 @@ function set_normalized_rhs(
return
end

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
126 changes: 125 additions & 1 deletion src/variables.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -2566,6 +2566,61 @@ function set_normalized_coefficient(
return
end

"""
set_normalized_coefficient(
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_coefficient(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_coefficient([con, con], [x, y], [6, 7])

julia> con
con : 6 x + 7 y ≤ 2
```
"""
function set_normalized_coefficient(
constraints::AbstractVector{
<:ConstraintRef{<:AbstractModel,<:MOI.ConstraintIndex{F}},
},
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"
throw(DimensionMismatch(msg))
end
model = owner_model(first(constraints))
MOI.modify(
backend(model),
index.(constraints),
MOI.ScalarCoefficientChange.(index.(variables), convert.(T, coeffs)),
)
model.is_model_dirty = true
return
end

"""
set_normalized_coefficients(
con_ref::ConstraintRef,
Expand Down Expand Up @@ -2669,6 +2724,75 @@ function set_normalized_coefficient(
return
end

"""
set_normalized_coefficient(
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_coefficient(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, 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
```
"""
function set_normalized_coefficient(
constraints::AbstractVector{
<:ConstraintRef{<:AbstractModel,<:MOI.ConstraintIndex{F}},
},
variables_1::AbstractVector{<:AbstractVariableRef},
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"
throw(DimensionMismatch(msg))
end
new_coeffs = convert.(T, coeffs)
for (i, x, y) in zip(eachindex(new_coeffs), variables_1, variables_2)
if x == y
new_coeffs[i] *= T(2)
end
end
model = owner_model(first(constraints))
MOI.modify(
backend(model),
index.(constraints),
MOI.ScalarQuadraticCoefficientChange.(
index.(variables_1),
index.(variables_2),
new_coeffs,
),
)
model.is_model_dirty = true
return
end

"""
normalized_coefficient(
constraint::ConstraintRef,
Expand Down
Loading
Loading