Skip to content

Commit 05cde2a

Browse files
authored
Improve VectorQuadraticFunctions (#235)
* Improve VectorQuadraticFunctions * format
1 parent 75a453b commit 05cde2a

File tree

3 files changed

+171
-8
lines changed

3 files changed

+171
-8
lines changed

src/MOI_wrapper.jl

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,6 +1072,7 @@ function _add_constraint_with_parameters_on_function(
10721072
# Create parametric vector quadratic function
10731073
pf = ParametricVectorQuadraticFunction(f)
10741074
_cache_multiplicative_params!(model, pf)
1075+
# _cache_set_constant!(pf, set) # there is no constant in vector sets
10751076
_update_cache!(pf, model)
10761077

10771078
# Get the current function after parameter substitution
@@ -1768,6 +1769,10 @@ function MOI.get(
17681769
DoubleDicts.nonempty_outer_keys(model.quadratic_constraint_cache)
17691770
push!(output, (F, S, ParametricQuadraticFunction{T}))
17701771
end
1772+
for (F, S) in
1773+
DoubleDicts.nonempty_outer_keys(model.vector_quadratic_constraint_cache)
1774+
push!(output, (F, S, ParametricVectorQuadraticFunction{T}))
1775+
end
17711776
return collect(output)
17721777
end
17731778

@@ -1990,6 +1995,41 @@ function MOI.compute_conflict!(model::Optimizer)
19901995
end
19911996
end
19921997
end
1998+
for (F, S) in keys(model.vector_quadratic_constraint_cache.dict)
1999+
vector_quadratic_constraint_cache_inner =
2000+
model.vector_quadratic_constraint_cache[F, S]
2001+
for (inner_ci, pf) in vector_quadratic_constraint_cache_inner
2002+
if MOI.get(
2003+
model.optimizer,
2004+
MOI.ConstraintConflictStatus(),
2005+
inner_ci,
2006+
) == MOI.NOT_IN_CONFLICT
2007+
continue
2008+
end
2009+
for term in vector_affine_parameter_terms(pf)
2010+
push!(
2011+
model.parameters_in_conflict,
2012+
term.scalar_term.variable,
2013+
)
2014+
end
2015+
for term in vector_quadratic_parameter_parameter_terms(pf)
2016+
push!(
2017+
model.parameters_in_conflict,
2018+
term.scalar_term.variable_1,
2019+
)
2020+
push!(
2021+
model.parameters_in_conflict,
2022+
term.scalar_term.variable_2,
2023+
)
2024+
end
2025+
for term in vector_quadratic_parameter_variable_terms(pf)
2026+
push!(
2027+
model.parameters_in_conflict,
2028+
term.scalar_term.variable_1,
2029+
)
2030+
end
2031+
end
2032+
end
19932033
end
19942034
return
19952035
end

src/update_parameters.jl

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -152,19 +152,17 @@ end
152152

153153
function _affine_build_change_and_up_param_func(
154154
pf::ParametricVectorQuadraticFunction{T},
155-
delta_terms,
155+
delta_terms::Dict,
156156
) where {T}
157+
new_terms = Dict{MOI.VariableIndex,Vector{Tuple{Int64,T}}}()
157158
for ((var, output_idx), coef) in delta_terms
158159
base_coef = pf.current_terms_with_p[(var, output_idx)]
159160
new_coef = base_coef + coef
160161
pf.current_terms_with_p[(var, output_idx)] = new_coef
161-
end
162-
new_terms = Dict{MOI.VariableIndex,Vector{Tuple{Int64,T}}}()
163-
for ((var, output_idx), coef) in pf.current_terms_with_p
164-
if !iszero(coef)
165-
base = get!(new_terms, var, Tuple{Int64,T}[])
166-
push!(base, (output_idx, coef))
167-
end
162+
base = get!(new_terms, var, Tuple{Int64,T}[])
163+
# we can rely on push because delta_terms if a Dict so coef are
164+
# unique per (var, output_idx)
165+
push!(base, (output_idx, new_coef))
168166
end
169167
changes = Vector{MOI.MultirowChange}(undef, length(new_terms))
170168
for (i, (var, tuples)) in enumerate(new_terms)

test/test_MathOptInterface.jl

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2380,6 +2380,131 @@ function test_constraint_primal_start_get_for_parameter()
23802380
return
23812381
end
23822382

2383+
function test_vector_quadratic_parameter_update_round_trip()
2384+
# Maximize x s.t. x >= 0, p*x <= 1.
2385+
# Initial p=2 → x=0.5; update p=4 → x=0.25.
2386+
model = POI.Optimizer(SCS.Optimizer)
2387+
MOI.set(model, MOI.Silent(), true)
2388+
x = MOI.add_variable(model)
2389+
p, cp = MOI.add_constrained_variable(model, MOI.Parameter(2.0))
2390+
MOI.add_constraint(model, x, MOI.GreaterThan(0.0))
2391+
# VectorQuadratic: [1.0 - p*x] ∈ Nonnegatives(1) → p*x ≤ 1
2392+
f = MOI.VectorQuadraticFunction(
2393+
[MOI.VectorQuadraticTerm(1, MOI.ScalarQuadraticTerm(-1.0, p, x))],
2394+
MOI.VectorAffineTerm{Float64}[],
2395+
[1.0],
2396+
)
2397+
MOI.add_constraint(model, f, MOI.Nonnegatives(1))
2398+
# Minimize -x (maximize x)
2399+
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
2400+
MOI.set(
2401+
model,
2402+
MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(),
2403+
MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(-1.0, x)], 0.0),
2404+
)
2405+
MOI.optimize!(model)
2406+
@test MOI.get(model, MOI.VariablePrimal(), x) 0.5 atol = ATOL
2407+
# Update p = 4 → now x ≤ 0.25
2408+
MOI.set(model, MOI.ConstraintSet(), cp, MOI.Parameter(4.0))
2409+
MOI.optimize!(model)
2410+
@test MOI.get(model, MOI.VariablePrimal(), x) 0.25 atol = ATOL
2411+
return
2412+
end
2413+
2414+
function test_list_of_parametric_constraint_types_with_vector_quadratic()
2415+
T = Float64
2416+
model = POI.Optimizer(MOI.Utilities.Model{T}())
2417+
x = MOI.add_variable(model)
2418+
p, _cp = MOI.add_constrained_variable(model, MOI.Parameter(2.0))
2419+
# VectorQuadratic constraint with a pv term
2420+
f = MOI.VectorQuadraticFunction(
2421+
[MOI.VectorQuadraticTerm(1, MOI.ScalarQuadraticTerm(-1.0, p, x))],
2422+
MOI.VectorAffineTerm{T}[],
2423+
[1.0],
2424+
)
2425+
MOI.add_constraint(model, f, MOI.Nonnegatives(1))
2426+
types = MOI.get(model, POI.ListOfParametricConstraintTypesPresent())
2427+
@test any(types) do (_, _, P)
2428+
return P == POI.ParametricVectorQuadraticFunction{T}
2429+
end
2430+
return
2431+
end
2432+
2433+
function test_compute_conflict_vector_quadratic()
2434+
T = Float64
2435+
mock = MOI.Utilities.MockOptimizer(MOI.Utilities.Model{T}())
2436+
MOI.set(mock, MOI.ConflictStatus(), MOI.COMPUTE_CONFLICT_NOT_CALLED)
2437+
model = POI.Optimizer(
2438+
MOI.Utilities.CachingOptimizer(MOI.Utilities.Model{T}(), mock),
2439+
)
2440+
x = MOI.add_variable(model)
2441+
p, p_ci = MOI.add_constrained_variable(model, MOI.Parameter(2.0))
2442+
p2, p2_ci = MOI.add_constrained_variable(model, MOI.Parameter(3.0))
2443+
p3, p3_ci = MOI.add_constrained_variable(model, MOI.Parameter(4.0))
2444+
p4, p4_ci = MOI.add_constrained_variable(model, MOI.Parameter(5.0))
2445+
# f1: pv term (IN_CONFLICT) — covers the pv push branch
2446+
f1 = MOI.VectorQuadraticFunction(
2447+
[MOI.VectorQuadraticTerm(1, MOI.ScalarQuadraticTerm(-1.0, p, x))],
2448+
MOI.VectorAffineTerm{T}[],
2449+
[1.0],
2450+
)
2451+
# f2: affine parameter term only (IN_CONFLICT) — covers the affine-p push branch
2452+
f2 = MOI.VectorQuadraticFunction(
2453+
MOI.VectorQuadraticTerm{T}[],
2454+
[MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, p2))],
2455+
[0.0],
2456+
)
2457+
# f3: pp term (IN_CONFLICT) — covers the pp push branch
2458+
f3 = MOI.VectorQuadraticFunction(
2459+
[MOI.VectorQuadraticTerm(1, MOI.ScalarQuadraticTerm(1.0, p3, p3))],
2460+
MOI.VectorAffineTerm{T}[],
2461+
[0.0],
2462+
)
2463+
# f4: pv term (NOT_IN_CONFLICT) — covers the continue branch
2464+
f4 = MOI.VectorQuadraticFunction(
2465+
[MOI.VectorQuadraticTerm(1, MOI.ScalarQuadraticTerm(-1.0, p4, x))],
2466+
MOI.VectorAffineTerm{T}[],
2467+
[1.0],
2468+
)
2469+
MOI.add_constraint(model, f1, MOI.Nonnegatives(1))
2470+
MOI.add_constraint(model, f2, MOI.Nonnegatives(1))
2471+
MOI.add_constraint(model, f3, MOI.Nonnegatives(1))
2472+
MOI.add_constraint(model, f4, MOI.Nonnegatives(1))
2473+
MOI.Utilities.set_mock_optimize!(
2474+
mock,
2475+
mock::MOI.Utilities.MockOptimizer -> begin
2476+
MOI.Utilities.mock_optimize!(
2477+
mock,
2478+
MOI.INFEASIBLE,
2479+
MOI.NO_SOLUTION,
2480+
MOI.NO_SOLUTION;
2481+
constraint_conflict_status = [
2482+
(MOI.VectorAffineFunction{T}, MOI.Nonnegatives) => [
2483+
MOI.IN_CONFLICT, # f1 (pv)
2484+
MOI.IN_CONFLICT, # f2 (affine-p)
2485+
MOI.IN_CONFLICT, # f3 (pp)
2486+
MOI.NOT_IN_CONFLICT, # f4 (pv, not conflicting)
2487+
],
2488+
],
2489+
)
2490+
MOI.set(mock, MOI.ConflictStatus(), MOI.CONFLICT_FOUND)
2491+
end,
2492+
)
2493+
MOI.optimize!(model)
2494+
@test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE
2495+
MOI.compute_conflict!(model)
2496+
@test MOI.get(model, MOI.ConflictStatus()) == MOI.CONFLICT_FOUND
2497+
@test MOI.get(model, MOI.ConstraintConflictStatus(), p_ci) ==
2498+
MOI.MAYBE_IN_CONFLICT # p in f1 (IN_CONFLICT, pv)
2499+
@test MOI.get(model, MOI.ConstraintConflictStatus(), p2_ci) ==
2500+
MOI.MAYBE_IN_CONFLICT # p2 in f2 (IN_CONFLICT, affine-p)
2501+
@test MOI.get(model, MOI.ConstraintConflictStatus(), p3_ci) ==
2502+
MOI.MAYBE_IN_CONFLICT # p3 in f3 (IN_CONFLICT, pp)
2503+
@test MOI.get(model, MOI.ConstraintConflictStatus(), p4_ci) ==
2504+
MOI.NOT_IN_CONFLICT # p4 in f4 (NOT_IN_CONFLICT)
2505+
return
2506+
end
2507+
23832508
function test_vector_quadratic_no_parameters_affine_get_constraint_function()
23842509
# A VectorQuadraticFunction with no parameters and no quadratic terms
23852510
# (empty quadratic_terms) should use the affine fast path. The outer

0 commit comments

Comments
 (0)