Skip to content

Commit 864d1fb

Browse files
authored
Add timers to _VectorNonlinearOracle (#502)
1 parent 886bd53 commit 864d1fb

File tree

2 files changed

+70
-34
lines changed

2 files changed

+70
-34
lines changed

ext/IpoptMathOptInterfaceExt/MOI_wrapper.jl

Lines changed: 51 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,6 @@ struct _VectorNonlinearOracle <: MOI.AbstractVectorSet
129129
eval_jacobian::Function
130130
hessian_lagrangian_structure::Vector{Tuple{Int,Int}}
131131
eval_hessian_lagrangian::Union{Nothing,Function}
132-
# Temporary storage
133-
x::Vector{Float64}
134132

135133
function _VectorNonlinearOracle(;
136134
dimension::Int,
@@ -154,8 +152,6 @@ struct _VectorNonlinearOracle <: MOI.AbstractVectorSet
154152
eval_jacobian,
155153
hessian_lagrangian_structure,
156154
eval_hessian_lagrangian,
157-
# Temporary storage
158-
zeros(dimension),
159155
)
160156
end
161157
end
@@ -174,6 +170,18 @@ function Base.show(io::IO, s::_VectorNonlinearOracle)
174170
return
175171
end
176172

173+
mutable struct _VectorNonlinearOracleCache
174+
set::_VectorNonlinearOracle
175+
x::Vector{Float64}
176+
eval_f_timer::Float64
177+
eval_jacobian_timer::Float64
178+
eval_hessian_lagrangian_timer::Float64
179+
180+
function _VectorNonlinearOracleCache(set::_VectorNonlinearOracle)
181+
return new(set, zeros(set.input_dimension), 0.0, 0.0, 0.0)
182+
end
183+
end
184+
177185
"""
178186
Optimizer()
179187
@@ -202,7 +210,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
202210
barrier_iterations::Int
203211
ad_backend::MOI.Nonlinear.AbstractAutomaticDifferentiation
204212
vector_nonlinear_oracle_constraints::Vector{
205-
Tuple{MOI.VectorOfVariables,_VectorNonlinearOracle},
213+
Tuple{MOI.VectorOfVariables,_VectorNonlinearOracleCache},
206214
}
207215

208216
function Optimizer()
@@ -228,7 +236,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
228236
nothing,
229237
0,
230238
MOI.Nonlinear.SparseReverseMode(),
231-
Tuple{MOI.VectorOfVariables,_VectorNonlinearOracle}[],
239+
Tuple{MOI.VectorOfVariables,_VectorNonlinearOracleCache}[],
232240
)
233241
end
234242
end
@@ -847,7 +855,8 @@ function MOI.add_constraint(
847855
s::S,
848856
) where {F<:MOI.VectorOfVariables,S<:_VectorNonlinearOracle}
849857
model.inner = nothing
850-
push!(model.vector_nonlinear_oracle_constraints, (f, s))
858+
cache = _VectorNonlinearOracleCache(s)
859+
push!(model.vector_nonlinear_oracle_constraints, (f, cache))
851860
n = length(model.vector_nonlinear_oracle_constraints)
852861
return MOI.ConstraintIndex{F,S}(n)
853862
end
@@ -859,10 +868,10 @@ function row(
859868
offset = length(model.qp_data)
860869
for i in 1:(ci.value-1)
861870
_, s = model.vector_nonlinear_oracle_constraints[i]
862-
offset += s.output_dimension
871+
offset += s.set.output_dimension
863872
end
864873
_, s = model.vector_nonlinear_oracle_constraints[ci.value]
865-
return offset .+ (1:s.output_dimension)
874+
return offset .+ (1:s.set.output_dimension)
866875
end
867876

868877
function MOI.get(
@@ -890,7 +899,7 @@ function MOI.get(
890899
_jacobian_structure(J, 0, f, s)
891900
J_val = zeros(length(J))
892901
_eval_constraint_jacobian(J_val, 0, model.inner.x, f, s)
893-
dual = zeros(MOI.dimension(s))
902+
dual = zeros(MOI.dimension(s.set))
894903
# dual = λ' * J(x)
895904
col_to_index = Dict(x.value => j for (j, x) in enumerate(f.variables))
896905
for ((row, col), J_rc) in zip(J, J_val)
@@ -1184,14 +1193,14 @@ function _eval_constraint(
11841193
offset::Int,
11851194
x::AbstractVector,
11861195
f::MOI.VectorOfVariables,
1187-
s::_VectorNonlinearOracle,
1196+
s::_VectorNonlinearOracleCache,
11881197
)
1189-
for i in 1:s.input_dimension
1198+
for i in 1:s.set.input_dimension
11901199
s.x[i] = x[f.variables[i].value]
11911200
end
1192-
ret = view(g, offset .+ (1:s.output_dimension))
1193-
s.eval_f(ret, s.x)
1194-
return offset + s.output_dimension
1201+
ret = view(g, offset .+ (1:s.set.output_dimension))
1202+
s.eval_f_timer += @elapsed s.set.eval_f(ret, s.x)
1203+
return offset + s.set.output_dimension
11951204
end
11961205

11971206
function MOI.eval_constraint(model::Optimizer, g, x)
@@ -1211,12 +1220,12 @@ function _jacobian_structure(
12111220
ret::AbstractVector,
12121221
row_offset::Int,
12131222
f::MOI.VectorOfVariables,
1214-
s::_VectorNonlinearOracle,
1223+
s::_VectorNonlinearOracleCache,
12151224
)
1216-
for (i, j) in s.jacobian_structure
1225+
for (i, j) in s.set.jacobian_structure
12171226
push!(ret, (row_offset + i, f.variables[j].value))
12181227
end
1219-
return row_offset + s.output_dimension
1228+
return row_offset + s.set.output_dimension
12201229
end
12211230

12221231
function MOI.jacobian_structure(model::Optimizer)
@@ -1241,13 +1250,14 @@ function _eval_constraint_jacobian(
12411250
offset::Int,
12421251
x::AbstractVector,
12431252
f::MOI.VectorOfVariables,
1244-
s::_VectorNonlinearOracle,
1253+
s::_VectorNonlinearOracleCache,
12451254
)
1246-
for i in 1:s.input_dimension
1255+
for i in 1:s.set.input_dimension
12471256
s.x[i] = x[f.variables[i].value]
12481257
end
1249-
nnz = length(s.jacobian_structure)
1250-
s.eval_jacobian(view(values, offset .+ (1:nnz)), s.x)
1258+
nnz = length(s.set.jacobian_structure)
1259+
s.eval_jacobian_timer +=
1260+
@elapsed s.set.eval_jacobian(view(values, offset .+ (1:nnz)), s.x)
12511261
return offset + nnz
12521262
end
12531263

@@ -1267,9 +1277,9 @@ end
12671277
function _hessian_lagrangian_structure(
12681278
ret::AbstractVector,
12691279
f::MOI.VectorOfVariables,
1270-
s::_VectorNonlinearOracle,
1280+
s::_VectorNonlinearOracleCache,
12711281
)
1272-
for (i, j) in s.hessian_lagrangian_structure
1282+
for (i, j) in s.set.hessian_lagrangian_structure
12731283
push!(ret, (f.variables[i].value, f.variables[j].value))
12741284
end
12751285
return
@@ -1291,16 +1301,17 @@ function _eval_hessian_lagrangian(
12911301
μ::AbstractVector,
12921302
μ_offset::Int,
12931303
f::MOI.VectorOfVariables,
1294-
s::_VectorNonlinearOracle,
1304+
s::_VectorNonlinearOracleCache,
12951305
)
1296-
for i in 1:s.input_dimension
1306+
for i in 1:s.set.input_dimension
12971307
s.x[i] = x[f.variables[i].value]
12981308
end
1299-
H_nnz = length(s.hessian_lagrangian_structure)
1309+
H_nnz = length(s.set.hessian_lagrangian_structure)
13001310
H_view = view(H, H_offset .+ (1:H_nnz))
1301-
μ_view = view(μ, μ_offset .+ (1:s.output_dimension))
1302-
s.eval_hessian_lagrangian(H_view, s.x, μ_view)
1303-
return H_offset + H_nnz, μ_offset + s.output_dimension
1311+
μ_view = view(μ, μ_offset .+ (1:s.set.output_dimension))
1312+
s.eval_hessian_lagrangian_timer +=
1313+
@elapsed s.set.eval_hessian_lagrangian(H_view, s.x, μ_view)
1314+
return H_offset + H_nnz, μ_offset + s.set.output_dimension
13041315
end
13051316

13061317
function MOI.eval_hessian_lagrangian(model::Optimizer, H, x, σ, μ)
@@ -1359,7 +1370,7 @@ function _setup_model(model::Optimizer)
13591370
!isempty(model.vector_nonlinear_oracle_constraints)
13601371
has_hessian = :Hess in MOI.features_available(model.nlp_data.evaluator)
13611372
for (_, s) in model.vector_nonlinear_oracle_constraints
1362-
if s.eval_hessian_lagrangian === nothing
1373+
if s.set.eval_hessian_lagrangian === nothing
13631374
has_hessian = false
13641375
break
13651376
end
@@ -1403,8 +1414,8 @@ function _setup_model(model::Optimizer)
14031414
end
14041415
g_L, g_U = copy(model.qp_data.g_L), copy(model.qp_data.g_U)
14051416
for (_, s) in model.vector_nonlinear_oracle_constraints
1406-
append!(g_L, s.l)
1407-
append!(g_U, s.u)
1417+
append!(g_L, s.set.l)
1418+
append!(g_U, s.set.u)
14081419
end
14091420
for bound in model.nlp_data.constraint_bounds
14101421
push!(g_L, bound.lower)
@@ -1532,6 +1543,12 @@ function MOI.optimize!(model::Optimizer)
15321543
end
15331544
return true
15341545
end
1546+
# Clear timers
1547+
for (_, s) in model.vector_nonlinear_oracle_constraints
1548+
s.eval_f_timer = 0.0
1549+
s.eval_jacobian_timer = 0.0
1550+
s.eval_hessian_lagrangian_timer = 0.0
1551+
end
15351552
Ipopt.SetIntermediateCallback(inner, _moi_callback)
15361553
Ipopt.IpoptSolve(inner)
15371554
model.solve_time = time() - start_time
@@ -1696,7 +1713,7 @@ function row(
16961713
)
16971714
offset = length(model.qp_data)
16981715
for (_, s) in model.vector_nonlinear_oracle_constraints
1699-
offset += s.output_dimension
1716+
offset += s.set.output_dimension
17001717
end
17011718
return offset + ci.value
17021719
end

test/MOI_wrapper.jl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,7 @@ function test_vector_nonlinear_oracle()
858858
l = [0.0, 0.0],
859859
u = [0.0, 0.0],
860860
eval_f = (ret, x) -> begin
861+
sleep(0.5) # to make timing test more robust
861862
@test length(ret) == 2
862863
@test length(x) == 5
863864
ret[1] = x[1]^2 - x[4]
@@ -866,6 +867,7 @@ function test_vector_nonlinear_oracle()
866867
end,
867868
jacobian_structure = [(1, 1), (2, 2), (2, 3), (1, 4), (2, 5)],
868869
eval_jacobian = (ret, x) -> begin
870+
sleep(0.5) # to make timing test more robust
869871
@test length(ret) == 5
870872
@test length(x) == 5
871873
ret[1] = 2 * x[1]
@@ -877,6 +879,7 @@ function test_vector_nonlinear_oracle()
877879
end,
878880
hessian_lagrangian_structure = [(1, 1), (2, 2), (3, 3)],
879881
eval_hessian_lagrangian = (ret, x, u) -> begin
882+
sleep(1.0) # to make timing test more robust
880883
@test length(ret) == 3
881884
@test length(x) == 5
882885
@test length(u) == 2
@@ -916,6 +919,22 @@ function test_vector_nonlinear_oracle()
916919
@test isapprox(y_v, [x_v[1]^2, x_v[2]^2 + x_v[3]^3], atol = 1e-5)
917920
@test MOI.get(model, MOI.ConstraintPrimal(), c) [x_v; y_v]
918921
@test MOI.get(model, MOI.ConstraintDual(), c) zeros(5)
922+
# Test timers with plenty of buffer to avoid a flakey test
923+
for (_, cache) in model.vector_nonlinear_oracle_constraints
924+
@show cache.eval_f_timer
925+
@test 0.9 < cache.eval_f_timer < 2
926+
@test 0.9 < cache.eval_jacobian_timer < 4
927+
@test 0.9 < cache.eval_hessian_lagrangian_timer < 2
928+
end
929+
# Test that optimize! resets the timers. Upper bounds are chosen such that
930+
# they're violated if times from both solves were added together.
931+
MOI.optimize!(model)
932+
for (_, cache) in model.vector_nonlinear_oracle_constraints
933+
@show cache.eval_f_timer
934+
@test 0.9 < cache.eval_f_timer < 2
935+
@test 0.9 < cache.eval_jacobian_timer < 4
936+
@test 0.9 < cache.eval_hessian_lagrangian_timer < 2
937+
end
919938
return
920939
end
921940

0 commit comments

Comments
 (0)