Skip to content

Commit

Permalink
Add ScalarQuadraticCoefficientChange (#2296)
Browse files Browse the repository at this point in the history
  • Loading branch information
odow committed Oct 9, 2023
1 parent 7914147 commit 491ef42
Show file tree
Hide file tree
Showing 8 changed files with 211 additions and 2 deletions.
38 changes: 38 additions & 0 deletions docs/src/manual/modification.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,44 @@ true
[`ScalarCoefficientChange`](@ref) can also be used to modify the objective
function by passing an instance of [`ObjectiveFunction`](@ref).

## Modify quadratic coefficients in a scalar function

Use [`modify`](@ref) and [`ScalarQuadraticCoefficientChange`](@ref) to modify
the quadratic coefficient of a [`ScalarQuadraticFunction`](@ref).

```jldoctest
julia> model = MOI.Utilities.Model{Float64}();
julia> x = MOI.add_variables(model, 2);
julia> c = MOI.add_constraint(
model,
1.0 * x[1] * x[1] + 2.0 * x[1] * x[2],
MOI.EqualTo(1.0),
)
MathOptInterface.ConstraintIndex{MathOptInterface.ScalarQuadraticFunction{Float64}, MathOptInterface.EqualTo{Float64}}(1)
julia> MOI.modify(
model,
c,
MOI.ScalarQuadraticCoefficientChange(x[1], x[1], 3.0),
);
julia> MOI.modify(
model,
c,
MOI.ScalarQuadraticCoefficientChange(x[1], x[2], 4.0),
);
julia> new_f = 1.5 * x[1] * x[1] + 4.0 * x[1] * x[2];
julia> MOI.get(model, MOI.ConstraintFunction(), c) ≈ new_f
true
```

[`ScalarQuadraticCoefficientChange`](@ref) can also be used to modify the
objective function by passing an instance of [`ObjectiveFunction`](@ref).

## Modify affine coefficients in a vector function

Use [`modify`](@ref) and [`MultirowChange`](@ref) to modify a vector of affine
Expand Down
1 change: 1 addition & 0 deletions docs/src/reference/modification.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ AbstractFunctionModification
ScalarConstantChange
VectorConstantChange
ScalarCoefficientChange
ScalarQuadraticCoefficientChange
MultirowChange
```
1 change: 1 addition & 0 deletions docs/src/tutorials/implementing.md
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,7 @@ the following `AbstractModification`s:

* [`ScalarConstantChange`](@ref)
* [`ScalarCoefficientChange`](@ref)
* [`ScalarQuadraticCoefficientChange`](@ref)
* [`VectorConstantChange`](@ref)
* [`MultirowChange`](@ref)

Expand Down
31 changes: 29 additions & 2 deletions src/Bridges/bridge_optimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1160,7 +1160,11 @@ function _modify_bridged_function(
change::MOI.AbstractFunctionModification,
)
if is_bridged(b, ci_or_obj)
MOI.modify(recursive_model(b), bridge(b, ci_or_obj), change)
try
MOI.modify(recursive_model(b), bridge(b, ci_or_obj), change)
catch
MOI.throw_modify_not_allowed(ci_or_obj, change)
end
else
MOI.modify(b.model, ci_or_obj, change)
end
Expand Down Expand Up @@ -1801,6 +1805,13 @@ function is_bridged(
return is_bridged(b, change.variable)
end

function is_bridged(
b::AbstractBridgeOptimizer,
change::MOI.ScalarQuadraticCoefficientChange,
)
return is_bridged(b, change.variable_1) || is_bridged(b, change.variable_2)
end

function modify_bridged_change(
b::AbstractBridgeOptimizer,
ci,
Expand Down Expand Up @@ -1870,6 +1881,18 @@ function modify_bridged_change(
return
end

function modify_bridged_change(
b::AbstractBridgeOptimizer,
ci_or_obj,
change::MOI.ScalarQuadraticCoefficientChange,
)
return MOI.throw_modify_not_allowed(
ci_or_obj,
change,
"Cannot bridge `ScalarQuadraticCoefficientChange`.",
)
end

function MOI.modify(
b::AbstractBridgeOptimizer,
ci::MOI.ConstraintIndex,
Expand All @@ -1879,7 +1902,11 @@ function MOI.modify(
modify_bridged_change(b, ci, change)
else
if is_bridged(b, ci)
call_in_context(MOI.modify, b, ci, change)
try
call_in_context(MOI.modify, b, ci, change)
catch
MOI.throw_modify_not_allowed(ci, change)
end
else
MOI.modify(b.model, ci, change)
end
Expand Down
41 changes: 41 additions & 0 deletions src/Test/test_modification.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1015,3 +1015,44 @@ function test_modification_incorrect_VariableIndex(
)
return
end

function test_modification_objective_scalarquadraticcoefficientchange(
model::MOI.ModelLike,
config::Config{T},
) where {T}
attr = MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{T}}()
@requires MOI.supports(model, attr)
@requires _supports(config, MOI.modify)
@requires _supports(config, MOI.ScalarQuadraticCoefficientChange)
x = MOI.add_variable(model)
MOI.set(model, attr, T(1) * x * x + T(2) * x + T(3))
@test MOI.get(model, attr) T(1) * x * x + T(2) * x + T(3)
MOI.modify(model, attr, MOI.ScalarQuadraticCoefficientChange(x, x, T(4)))
@test MOI.get(model, attr) T(2) * x * x + T(2) * x + T(3)
y = MOI.add_variable(model)
MOI.set(model, attr, T(1) * x * x + T(2) * x * y)
@test MOI.get(model, attr) T(1) * x * x + T(2) * x * y
MOI.modify(model, attr, MOI.ScalarQuadraticCoefficientChange(x, y, T(4)))
@test MOI.get(model, attr) T(1) * x * x + T(4) * x * y
return
end

function test_modification_constraint_scalarquadraticcoefficientchange(
model::MOI.ModelLike,
config::Config{T},
) where {T}
F, S = MOI.ScalarQuadraticFunction{T}, MOI.LessThan{T}
@requires MOI.supports_constraint(model, F, S)
@requires _supports(config, MOI.modify)
@requires _supports(config, MOI.ScalarQuadraticCoefficientChange)
x = MOI.add_variable(model)
y = MOI.add_variable(model)
f = T(1) * x * x + T(1) * x * x - T(1) * x * y + T(2) * y * y - T(1) * x * y
c = MOI.add_constraint(model, f, MOI.LessThan(T(1)))
@test MOI.get(model, MOI.ConstraintFunction(), c) f
g = T(1) * x * x + T(-3) * x * y + T(2) * y * y
MOI.modify(model, c, MOI.ScalarQuadraticCoefficientChange(x, x, T(2)))
MOI.modify(model, c, MOI.ScalarQuadraticCoefficientChange(x, y, -T(3)))
@test MOI.get(model, MOI.ConstraintFunction(), c) g
return
end
31 changes: 31 additions & 0 deletions src/Utilities/functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,17 @@ function map_indices(
)
end

function map_indices(
index_map::F,
change::MOI.ScalarQuadraticCoefficientChange,
) where {F<:Function}
return MOI.ScalarQuadraticCoefficientChange(
index_map(change.variable_1),
index_map(change.variable_2),
change.new_coefficient,
)
end

function map_indices(
index_map::F,
change::MOI.MultirowChange,
Expand Down Expand Up @@ -1386,6 +1397,26 @@ function modify_function!(
return f
end

function modify_function!(
f::MOI.ScalarQuadraticFunction{T},
change::MOI.ScalarQuadraticCoefficientChange{T},
) where {T}
indices = findall(f.quadratic_terms) do term
return term.variable_1 == change.variable_1 &&
term.variable_2 == change.variable_2
end
for j in reverse(indices)
deleteat!(f.quadratic_terms, j)
end
term = MOI.ScalarQuadraticTerm(
change.new_coefficient,
change.variable_1,
change.variable_2,
)
push!(f.quadratic_terms, term)
return f
end

function _modify_coefficients(
terms::Vector{MOI.VectorAffineTerm{T}},
variable::MOI.VariableIndex,
Expand Down
33 changes: 33 additions & 0 deletions src/functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,39 @@ struct ScalarCoefficientChange{T} <: AbstractFunctionModification
new_coefficient::T
end

"""
ScalarQuadraticCoefficientChange{T}(
variable_1::VariableIndex,
variable_2::VariableIndex,
new_coefficient::T,
)
A struct used to request a change in the quadratic coefficient of a
[`ScalarQuadraticFunction`](@ref).
## Scaling factors
A [`ScalarQuadraticFunction`](@ref) has an implicit `0.5` scaling factor in
front of the `Q` matrix. This modification applies to terms in the `Q` matrix.
If `variable_1 == variable_2`, this modification sets the corresponding diagonal
element of the `Q` matrix to `new_coefficient`.
If `variable_1 != variable_2`, this modification is equivalent to setting both
the corresponding upper- and lower-triangular elements of the `Q` matrix to
`new_coefficient`.
As a consequence:
* to modify the term `x^2` to become `2x^2`, `new_coefficient` must be `4`
* to modify the term `xy` to become `2xy`, `new_coefficient` must be `2`
"""
struct ScalarQuadraticCoefficientChange{T} <: AbstractFunctionModification
variable_1::VariableIndex
variable_2::VariableIndex
new_coefficient::T
end

# !!! developer note
# MultiRowChange is mutable because its `variable` field of an immutable
# type, while `new_coefficients` is of a mutable type, meaning that creating
Expand Down
37 changes: 37 additions & 0 deletions test/Bridges/bridge_optimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1038,6 +1038,43 @@ function test_deleting_all_variables_in_bridged_functionize_objective()
return
end

function test_modify_objective_scalar_quadratic_coefficient_change()
T = Float64
inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{T}())
model = MOI.Bridges.Objective.Slack{T}(inner)
x = MOI.add_variable(model)
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
attr = MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{T}}()
MOI.set(model, attr, T(1) * x * x + T(2) * x + T(3))
change = MOI.ScalarQuadraticCoefficientChange(x, x, T(4))
@test_throws MOI.ModifyObjectiveNotAllowed MOI.modify(model, attr, change)
return
end

function test_modify_variable_scalar_quadratic_coefficient_change()
T = Float64
inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{T}())
model = MOI.Bridges.Variable.Free{T}(inner)
x = MOI.add_variable(model)
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
attr = MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{T}}()
MOI.set(model, attr, T(1) * x * x + T(2) * x + T(3))
change = MOI.ScalarQuadraticCoefficientChange(x, x, T(4))
@test_throws MOI.ModifyObjectiveNotAllowed MOI.modify(model, attr, change)
return
end

function test_modify_constraint_scalar_quadratic_coefficient_change()
T = Float64
inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{T}())
model = MOI.Bridges.Constraint.QuadtoSOC{T}(inner)
x = MOI.add_variable(model)
c = MOI.add_constraint(model, T(1) * x * x + T(2) * x, MOI.LessThan(T(1)))
change = MOI.ScalarQuadraticCoefficientChange(x, x, T(4))
@test_throws MOI.ModifyConstraintNotAllowed MOI.modify(model, c, change)
return
end

end # module

TestBridgeOptimizer.runtests()

0 comments on commit 491ef42

Please sign in to comment.