Skip to content

Commit 491ef42

Browse files
authored
Add ScalarQuadraticCoefficientChange (#2296)
1 parent 7914147 commit 491ef42

File tree

8 files changed

+211
-2
lines changed

8 files changed

+211
-2
lines changed

docs/src/manual/modification.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,44 @@ true
208208
[`ScalarCoefficientChange`](@ref) can also be used to modify the objective
209209
function by passing an instance of [`ObjectiveFunction`](@ref).
210210

211+
## Modify quadratic coefficients in a scalar function
212+
213+
Use [`modify`](@ref) and [`ScalarQuadraticCoefficientChange`](@ref) to modify
214+
the quadratic coefficient of a [`ScalarQuadraticFunction`](@ref).
215+
216+
```jldoctest
217+
julia> model = MOI.Utilities.Model{Float64}();
218+
219+
julia> x = MOI.add_variables(model, 2);
220+
221+
julia> c = MOI.add_constraint(
222+
model,
223+
1.0 * x[1] * x[1] + 2.0 * x[1] * x[2],
224+
MOI.EqualTo(1.0),
225+
)
226+
MathOptInterface.ConstraintIndex{MathOptInterface.ScalarQuadraticFunction{Float64}, MathOptInterface.EqualTo{Float64}}(1)
227+
228+
julia> MOI.modify(
229+
model,
230+
c,
231+
MOI.ScalarQuadraticCoefficientChange(x[1], x[1], 3.0),
232+
);
233+
234+
julia> MOI.modify(
235+
model,
236+
c,
237+
MOI.ScalarQuadraticCoefficientChange(x[1], x[2], 4.0),
238+
);
239+
240+
julia> new_f = 1.5 * x[1] * x[1] + 4.0 * x[1] * x[2];
241+
242+
julia> MOI.get(model, MOI.ConstraintFunction(), c) ≈ new_f
243+
true
244+
```
245+
246+
[`ScalarQuadraticCoefficientChange`](@ref) can also be used to modify the
247+
objective function by passing an instance of [`ObjectiveFunction`](@ref).
248+
211249
## Modify affine coefficients in a vector function
212250

213251
Use [`modify`](@ref) and [`MultirowChange`](@ref) to modify a vector of affine

docs/src/reference/modification.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ AbstractFunctionModification
1414
ScalarConstantChange
1515
VectorConstantChange
1616
ScalarCoefficientChange
17+
ScalarQuadraticCoefficientChange
1718
MultirowChange
1819
```

docs/src/tutorials/implementing.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,7 @@ the following `AbstractModification`s:
499499

500500
* [`ScalarConstantChange`](@ref)
501501
* [`ScalarCoefficientChange`](@ref)
502+
* [`ScalarQuadraticCoefficientChange`](@ref)
502503
* [`VectorConstantChange`](@ref)
503504
* [`MultirowChange`](@ref)
504505

src/Bridges/bridge_optimizer.jl

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,7 +1160,11 @@ function _modify_bridged_function(
11601160
change::MOI.AbstractFunctionModification,
11611161
)
11621162
if is_bridged(b, ci_or_obj)
1163-
MOI.modify(recursive_model(b), bridge(b, ci_or_obj), change)
1163+
try
1164+
MOI.modify(recursive_model(b), bridge(b, ci_or_obj), change)
1165+
catch
1166+
MOI.throw_modify_not_allowed(ci_or_obj, change)
1167+
end
11641168
else
11651169
MOI.modify(b.model, ci_or_obj, change)
11661170
end
@@ -1801,6 +1805,13 @@ function is_bridged(
18011805
return is_bridged(b, change.variable)
18021806
end
18031807

1808+
function is_bridged(
1809+
b::AbstractBridgeOptimizer,
1810+
change::MOI.ScalarQuadraticCoefficientChange,
1811+
)
1812+
return is_bridged(b, change.variable_1) || is_bridged(b, change.variable_2)
1813+
end
1814+
18041815
function modify_bridged_change(
18051816
b::AbstractBridgeOptimizer,
18061817
ci,
@@ -1870,6 +1881,18 @@ function modify_bridged_change(
18701881
return
18711882
end
18721883

1884+
function modify_bridged_change(
1885+
b::AbstractBridgeOptimizer,
1886+
ci_or_obj,
1887+
change::MOI.ScalarQuadraticCoefficientChange,
1888+
)
1889+
return MOI.throw_modify_not_allowed(
1890+
ci_or_obj,
1891+
change,
1892+
"Cannot bridge `ScalarQuadraticCoefficientChange`.",
1893+
)
1894+
end
1895+
18731896
function MOI.modify(
18741897
b::AbstractBridgeOptimizer,
18751898
ci::MOI.ConstraintIndex,
@@ -1879,7 +1902,11 @@ function MOI.modify(
18791902
modify_bridged_change(b, ci, change)
18801903
else
18811904
if is_bridged(b, ci)
1882-
call_in_context(MOI.modify, b, ci, change)
1905+
try
1906+
call_in_context(MOI.modify, b, ci, change)
1907+
catch
1908+
MOI.throw_modify_not_allowed(ci, change)
1909+
end
18831910
else
18841911
MOI.modify(b.model, ci, change)
18851912
end

src/Test/test_modification.jl

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,3 +1015,44 @@ function test_modification_incorrect_VariableIndex(
10151015
)
10161016
return
10171017
end
1018+
1019+
function test_modification_objective_scalarquadraticcoefficientchange(
1020+
model::MOI.ModelLike,
1021+
config::Config{T},
1022+
) where {T}
1023+
attr = MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{T}}()
1024+
@requires MOI.supports(model, attr)
1025+
@requires _supports(config, MOI.modify)
1026+
@requires _supports(config, MOI.ScalarQuadraticCoefficientChange)
1027+
x = MOI.add_variable(model)
1028+
MOI.set(model, attr, T(1) * x * x + T(2) * x + T(3))
1029+
@test MOI.get(model, attr) T(1) * x * x + T(2) * x + T(3)
1030+
MOI.modify(model, attr, MOI.ScalarQuadraticCoefficientChange(x, x, T(4)))
1031+
@test MOI.get(model, attr) T(2) * x * x + T(2) * x + T(3)
1032+
y = MOI.add_variable(model)
1033+
MOI.set(model, attr, T(1) * x * x + T(2) * x * y)
1034+
@test MOI.get(model, attr) T(1) * x * x + T(2) * x * y
1035+
MOI.modify(model, attr, MOI.ScalarQuadraticCoefficientChange(x, y, T(4)))
1036+
@test MOI.get(model, attr) T(1) * x * x + T(4) * x * y
1037+
return
1038+
end
1039+
1040+
function test_modification_constraint_scalarquadraticcoefficientchange(
1041+
model::MOI.ModelLike,
1042+
config::Config{T},
1043+
) where {T}
1044+
F, S = MOI.ScalarQuadraticFunction{T}, MOI.LessThan{T}
1045+
@requires MOI.supports_constraint(model, F, S)
1046+
@requires _supports(config, MOI.modify)
1047+
@requires _supports(config, MOI.ScalarQuadraticCoefficientChange)
1048+
x = MOI.add_variable(model)
1049+
y = MOI.add_variable(model)
1050+
f = T(1) * x * x + T(1) * x * x - T(1) * x * y + T(2) * y * y - T(1) * x * y
1051+
c = MOI.add_constraint(model, f, MOI.LessThan(T(1)))
1052+
@test MOI.get(model, MOI.ConstraintFunction(), c) f
1053+
g = T(1) * x * x + T(-3) * x * y + T(2) * y * y
1054+
MOI.modify(model, c, MOI.ScalarQuadraticCoefficientChange(x, x, T(2)))
1055+
MOI.modify(model, c, MOI.ScalarQuadraticCoefficientChange(x, y, -T(3)))
1056+
@test MOI.get(model, MOI.ConstraintFunction(), c) g
1057+
return
1058+
end

src/Utilities/functions.jl

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,17 @@ function map_indices(
396396
)
397397
end
398398

399+
function map_indices(
400+
index_map::F,
401+
change::MOI.ScalarQuadraticCoefficientChange,
402+
) where {F<:Function}
403+
return MOI.ScalarQuadraticCoefficientChange(
404+
index_map(change.variable_1),
405+
index_map(change.variable_2),
406+
change.new_coefficient,
407+
)
408+
end
409+
399410
function map_indices(
400411
index_map::F,
401412
change::MOI.MultirowChange,
@@ -1386,6 +1397,26 @@ function modify_function!(
13861397
return f
13871398
end
13881399

1400+
function modify_function!(
1401+
f::MOI.ScalarQuadraticFunction{T},
1402+
change::MOI.ScalarQuadraticCoefficientChange{T},
1403+
) where {T}
1404+
indices = findall(f.quadratic_terms) do term
1405+
return term.variable_1 == change.variable_1 &&
1406+
term.variable_2 == change.variable_2
1407+
end
1408+
for j in reverse(indices)
1409+
deleteat!(f.quadratic_terms, j)
1410+
end
1411+
term = MOI.ScalarQuadraticTerm(
1412+
change.new_coefficient,
1413+
change.variable_1,
1414+
change.variable_2,
1415+
)
1416+
push!(f.quadratic_terms, term)
1417+
return f
1418+
end
1419+
13891420
function _modify_coefficients(
13901421
terms::Vector{MOI.VectorAffineTerm{T}},
13911422
variable::MOI.VariableIndex,

src/functions.jl

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,39 @@ struct ScalarCoefficientChange{T} <: AbstractFunctionModification
782782
new_coefficient::T
783783
end
784784

785+
"""
786+
ScalarQuadraticCoefficientChange{T}(
787+
variable_1::VariableIndex,
788+
variable_2::VariableIndex,
789+
new_coefficient::T,
790+
)
791+
792+
A struct used to request a change in the quadratic coefficient of a
793+
[`ScalarQuadraticFunction`](@ref).
794+
795+
## Scaling factors
796+
797+
A [`ScalarQuadraticFunction`](@ref) has an implicit `0.5` scaling factor in
798+
front of the `Q` matrix. This modification applies to terms in the `Q` matrix.
799+
800+
If `variable_1 == variable_2`, this modification sets the corresponding diagonal
801+
element of the `Q` matrix to `new_coefficient`.
802+
803+
If `variable_1 != variable_2`, this modification is equivalent to setting both
804+
the corresponding upper- and lower-triangular elements of the `Q` matrix to
805+
`new_coefficient`.
806+
807+
As a consequence:
808+
809+
* to modify the term `x^2` to become `2x^2`, `new_coefficient` must be `4`
810+
* to modify the term `xy` to become `2xy`, `new_coefficient` must be `2`
811+
"""
812+
struct ScalarQuadraticCoefficientChange{T} <: AbstractFunctionModification
813+
variable_1::VariableIndex
814+
variable_2::VariableIndex
815+
new_coefficient::T
816+
end
817+
785818
# !!! developer note
786819
# MultiRowChange is mutable because its `variable` field of an immutable
787820
# type, while `new_coefficients` is of a mutable type, meaning that creating

test/Bridges/bridge_optimizer.jl

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,6 +1038,43 @@ function test_deleting_all_variables_in_bridged_functionize_objective()
10381038
return
10391039
end
10401040

1041+
function test_modify_objective_scalar_quadratic_coefficient_change()
1042+
T = Float64
1043+
inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{T}())
1044+
model = MOI.Bridges.Objective.Slack{T}(inner)
1045+
x = MOI.add_variable(model)
1046+
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
1047+
attr = MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{T}}()
1048+
MOI.set(model, attr, T(1) * x * x + T(2) * x + T(3))
1049+
change = MOI.ScalarQuadraticCoefficientChange(x, x, T(4))
1050+
@test_throws MOI.ModifyObjectiveNotAllowed MOI.modify(model, attr, change)
1051+
return
1052+
end
1053+
1054+
function test_modify_variable_scalar_quadratic_coefficient_change()
1055+
T = Float64
1056+
inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{T}())
1057+
model = MOI.Bridges.Variable.Free{T}(inner)
1058+
x = MOI.add_variable(model)
1059+
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
1060+
attr = MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{T}}()
1061+
MOI.set(model, attr, T(1) * x * x + T(2) * x + T(3))
1062+
change = MOI.ScalarQuadraticCoefficientChange(x, x, T(4))
1063+
@test_throws MOI.ModifyObjectiveNotAllowed MOI.modify(model, attr, change)
1064+
return
1065+
end
1066+
1067+
function test_modify_constraint_scalar_quadratic_coefficient_change()
1068+
T = Float64
1069+
inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{T}())
1070+
model = MOI.Bridges.Constraint.QuadtoSOC{T}(inner)
1071+
x = MOI.add_variable(model)
1072+
c = MOI.add_constraint(model, T(1) * x * x + T(2) * x, MOI.LessThan(T(1)))
1073+
change = MOI.ScalarQuadraticCoefficientChange(x, x, T(4))
1074+
@test_throws MOI.ModifyConstraintNotAllowed MOI.modify(model, c, change)
1075+
return
1076+
end
1077+
10411078
end # module
10421079

10431080
TestBridgeOptimizer.runtests()

0 commit comments

Comments
 (0)