@@ -59,6 +59,10 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
5959 vector_nonlinear_oracle_constraints:: Vector {
6060 Tuple{MOI. VectorOfVariables,_VectorNonlinearOracleCache},
6161 }
62+ jacobian_sparsity:: Vector{Tuple{Int,Int}}
63+ hessian_sparsity:: Union{Nothing,Vector{Tuple{Int,Int}}}
64+ needs_new_inner:: Bool
65+ has_only_linear_constraints:: Bool
6266
6367 function Optimizer ()
6468 return new (
@@ -84,6 +88,10 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
8488 0 ,
8589 MOI. Nonlinear. SparseReverseMode (),
8690 Tuple{MOI. VectorOfVariables,_VectorNonlinearOracleCache}[],
91+ Tuple{Int,Int}[],
92+ nothing ,
93+ true ,
94+ false ,
8795 )
8896 end
8997end
@@ -132,6 +140,10 @@ function MOI.empty!(model::Optimizer)
132140 model. barrier_iterations = 0
133141 # SKIP: model.ad_backend
134142 empty! (model. vector_nonlinear_oracle_constraints)
143+ empty! (model. jacobian_sparsity)
144+ model. hessian_sparsity = nothing
145+ model. needs_new_inner = true
146+ model. has_only_linear_constraints = true
135147 return
136148end
137149
@@ -405,7 +417,7 @@ function MOI.set(
405417 set:: S ,
406418) where {S<: _SETS }
407419 MOI. set (model. variables, MOI. ConstraintSet (), ci, set)
408- model. inner = nothing
420+ model. needs_new_inner = true
409421 return
410422end
411423
@@ -484,7 +496,7 @@ function MOI.set(
484496 S<: _SETS ,
485497}
486498 MOI. set (model. qp_data, MOI. ConstraintSet (), ci, set)
487- model. inner = nothing
499+ model. needs_new_inner = true
488500 return
489501end
490502
@@ -625,7 +637,7 @@ function MOI.set(
625637 index = MOI. Nonlinear. ConstraintIndex (ci. value)
626638 func = model. nlp_model[index]. expression
627639 model. nlp_model. constraints[index] = MOI. Nonlinear. Constraint (func, set)
628- model. inner = nothing
640+ model. needs_new_inner = true
629641 return
630642end
631643
@@ -950,7 +962,7 @@ function MOI.set(
950962 sense:: MOI.OptimizationSense ,
951963)
952964 model. sense = sense
953- model. inner = nothing
965+ model. needs_new_inner = true
954966 return
955967end
956968
@@ -1201,66 +1213,31 @@ end
12011213
12021214# ## MOI.optimize!
12031215
1204- function _setup_model (model:: Optimizer )
1205- vars = MOI. get (model. variables, MOI. ListOfVariableIndices ())
1206- if isempty (vars)
1207- # Don't attempt to create a problem because Ipopt will error.
1208- model. invalid_model = true
1209- return
1210- end
1211- if model. nlp_model != = nothing
1212- model. nlp_data = MOI. NLPBlockData (
1213- MOI. Nonlinear. Evaluator (model. nlp_model, model. ad_backend, vars),
1214- )
1215- end
1216- has_quadratic_constraints =
1217- any (isequal (_kFunctionTypeScalarQuadratic), model. qp_data. function_type)
1218- has_nlp_constraints =
1219- ! isempty (model. nlp_data. constraint_bounds) ||
1220- ! isempty (model. vector_nonlinear_oracle_constraints)
1221- has_hessian = :Hess in MOI. features_available (model. nlp_data. evaluator)
1222- for (_, s) in model. vector_nonlinear_oracle_constraints
1223- if s. set. eval_hessian_lagrangian === nothing
1224- has_hessian = false
1225- break
1216+ function _eval_jac_g_cb (model, x, rows, cols, values)
1217+ if values === nothing
1218+ for i in 1 : length (model. jacobian_sparsity)
1219+ rows[i], cols[i] = model. jacobian_sparsity[i]
12261220 end
1227- end
1228- init_feat = [:Grad ]
1229- if has_hessian
1230- push! (init_feat, :Hess )
1231- end
1232- if has_nlp_constraints
1233- push! (init_feat, :Jac )
1234- end
1235- MOI. initialize (model. nlp_data. evaluator, init_feat)
1236- jacobian_sparsity = MOI. jacobian_structure (model)
1237- hessian_sparsity = if has_hessian
1238- MOI. hessian_lagrangian_structure (model)
12391221 else
1240- Tuple{Int,Int}[]
1241- end
1242- eval_f_cb (x) = MOI. eval_objective (model, x)
1243- eval_grad_f_cb (x, grad_f) = MOI. eval_objective_gradient (model, grad_f, x)
1244- eval_g_cb (x, g) = MOI. eval_constraint (model, g, x)
1245- function eval_jac_g_cb (x, rows, cols, values)
1246- if values === nothing
1247- for i in 1 : length (jacobian_sparsity)
1248- rows[i], cols[i] = jacobian_sparsity[i]
1249- end
1250- else
1251- MOI. eval_constraint_jacobian (model, values, x)
1252- end
1253- return
1222+ MOI. eval_constraint_jacobian (model, values, x)
12541223 end
1255- function eval_h_cb (x, rows, cols, obj_factor, lambda, values)
1256- if values === nothing
1257- for i in 1 : length (hessian_sparsity)
1258- rows[i] , cols[i] = hessian_sparsity[i]
1259- end
1260- else
1261- MOI . eval_hessian_lagrangian (model, values, x, obj_factor, lambda)
1224+ return
1225+ end
1226+
1227+ function _eval_h_cb (model, x, rows, cols, obj_factor, lambda, values)
1228+ if values === nothing
1229+ for (i, v) in enumerate (model . hessian_sparsity :: Vector{Tuple{Int,Int}} )
1230+ rows[i], cols[i] = v
12621231 end
1263- return
1232+ else
1233+ MOI. eval_hessian_lagrangian (model, values, x, obj_factor, lambda)
1234+ end
1235+ return
1236+ end
1237+
1238+ function _setup_inner (model:: Optimizer ):: Ipopt.IpoptProblem
1239+ if ! model. needs_new_inner
1240+ return model. inner
12641241 end
12651242 g_L, g_U = copy (model. qp_data. g_L), copy (model. qp_data. g_U)
12661243 for (_, s) in model. vector_nonlinear_oracle_constraints
@@ -1271,61 +1248,105 @@ function _setup_model(model::Optimizer)
12711248 push! (g_L, bound. lower)
12721249 push! (g_U, bound. upper)
12731250 end
1251+ function eval_h_cb (x, rows, cols, obj_factor, lambda, values)
1252+ return _eval_h_cb (model, x, rows, cols, obj_factor, lambda, values)
1253+ end
1254+ has_hessian = model. hessian_sparsity != = nothing
12741255 model. inner = Ipopt. CreateIpoptProblem (
1275- length (vars ),
1256+ length (model . variables . lower ),
12761257 model. variables. lower,
12771258 model. variables. upper,
12781259 length (g_L),
12791260 g_L,
12801261 g_U,
1281- length (jacobian_sparsity),
1282- length (hessian_sparsity),
1283- eval_f_cb,
1284- eval_g_cb,
1285- eval_grad_f_cb,
1286- eval_jac_g_cb,
1262+ length (model. jacobian_sparsity),
1263+ has_hessian ? length (model. hessian_sparsity) : 0 ,
1264+ (x) -> MOI. eval_objective (model, x),
1265+ (x, g) -> MOI. eval_constraint (model, g, x),
1266+ (x, grad_f) -> MOI. eval_objective_gradient (model, grad_f, x),
1267+ (x, rows, cols, values) ->
1268+ _eval_jac_g_cb (model, x, rows, cols, values),
12871269 has_hessian ? eval_h_cb : nothing ,
12881270 )
1271+ inner = model. inner:: Ipopt.IpoptProblem
12891272 if model. sense == MOI. MIN_SENSE
1290- Ipopt. AddIpoptNumOption (model . inner, " obj_scaling_factor" , 1.0 )
1273+ Ipopt. AddIpoptNumOption (inner, " obj_scaling_factor" , 1.0 )
12911274 elseif model. sense == MOI. MAX_SENSE
1292- Ipopt. AddIpoptNumOption (model . inner, " obj_scaling_factor" , - 1.0 )
1275+ Ipopt. AddIpoptNumOption (inner, " obj_scaling_factor" , - 1.0 )
12931276 end
12941277 # Ipopt crashes by default if NaN/Inf values are returned from the
12951278 # evaluation callbacks. This option tells Ipopt to explicitly check for them
12961279 # and return Invalid_Number_Detected instead. This setting may result in a
12971280 # minor performance loss and can be overwritten by specifying
12981281 # check_derivatives_for_naninf="no".
1299- Ipopt. AddIpoptStrOption (model . inner, " check_derivatives_for_naninf" , " yes" )
1282+ Ipopt. AddIpoptStrOption (inner, " check_derivatives_for_naninf" , " yes" )
13001283 if ! has_hessian
13011284 Ipopt. AddIpoptStrOption (
1302- model . inner,
1285+ inner,
13031286 " hessian_approximation" ,
13041287 " limited-memory" ,
13051288 )
13061289 end
1307- if ! has_nlp_constraints && ! has_quadratic_constraints
1308- Ipopt. AddIpoptStrOption (model . inner, " jac_c_constant" , " yes" )
1309- Ipopt. AddIpoptStrOption (model . inner, " jac_d_constant" , " yes" )
1290+ if model . has_only_linear_constraints
1291+ Ipopt. AddIpoptStrOption (inner, " jac_c_constant" , " yes" )
1292+ Ipopt. AddIpoptStrOption (inner, " jac_d_constant" , " yes" )
13101293 if ! model. nlp_data. has_objective
1311- # We turn on this option if all constraints are linear and the
1312- # objective is linear or quadratic. From the documentation, it's
1313- # unclear if it may also apply if the constraints are at most
1314- # quadratic.
1315- Ipopt. AddIpoptStrOption (model. inner, " hessian_constant" , " yes" )
1294+ Ipopt. AddIpoptStrOption (inner, " hessian_constant" , " yes" )
13161295 end
13171296 end
1318- return
1297+ function _moi_callback (args... )
1298+ # iter_count is args[2]
1299+ model. barrier_iterations = args[2 ]
1300+ if model. callback != = nothing
1301+ return model. callback (args... )
1302+ end
1303+ return true
1304+ end
1305+ Ipopt. SetIntermediateCallback (inner, _moi_callback)
1306+ model. needs_new_inner = false
1307+ return model. inner
13191308end
13201309
1321- function copy_parameters (model:: Optimizer )
1322- if model. nlp_model === nothing
1310+ function _setup_model (model:: Optimizer )
1311+ if MOI. get (model, MOI. NumberOfVariables ()) == 0
1312+ # Don't attempt to create a problem because Ipopt will error.
1313+ model. invalid_model = true
13231314 return
13241315 end
1325- empty! (model. qp_data. parameters)
1326- for (p, index) in model. parameters
1327- model. qp_data. parameters[p. value] = model. nlp_model[index]
1316+ if model. nlp_model != = nothing
1317+ vars = MOI. get (model. variables, MOI. ListOfVariableIndices ())
1318+ model. nlp_data = MOI. NLPBlockData (
1319+ MOI. Nonlinear. Evaluator (model. nlp_model, model. ad_backend, vars),
1320+ )
1321+ end
1322+ has_quadratic_constraints =
1323+ any (isequal (_kFunctionTypeScalarQuadratic), model. qp_data. function_type)
1324+ has_nlp_constraints =
1325+ ! isempty (model. nlp_data. constraint_bounds) ||
1326+ ! isempty (model. vector_nonlinear_oracle_constraints)
1327+ has_hessian = :Hess in MOI. features_available (model. nlp_data. evaluator)
1328+ for (_, s) in model. vector_nonlinear_oracle_constraints
1329+ if s. set. eval_hessian_lagrangian === nothing
1330+ has_hessian = false
1331+ break
1332+ end
1333+ end
1334+ init_feat = [:Grad ]
1335+ if has_hessian
1336+ push! (init_feat, :Hess )
1337+ end
1338+ if has_nlp_constraints
1339+ push! (init_feat, :Jac )
1340+ end
1341+ MOI. initialize (model. nlp_data. evaluator, init_feat)
1342+ model. jacobian_sparsity = MOI. jacobian_structure (model)
1343+ model. hessian_sparsity = nothing
1344+ if has_hessian
1345+ model. hessian_sparsity = MOI. hessian_lagrangian_structure (model)
13281346 end
1347+ model. has_only_linear_constraints =
1348+ ! has_nlp_constraints && ! has_quadratic_constraints
1349+ model. needs_new_inner = true
13291350 return
13301351end
13311352
@@ -1337,8 +1358,13 @@ function MOI.optimize!(model::Optimizer)
13371358 if model. invalid_model
13381359 return
13391360 end
1340- copy_parameters (model)
1341- inner = model. inner:: Ipopt.IpoptProblem
1361+ inner = _setup_inner (model)
1362+ if model. nlp_model != = nothing
1363+ empty! (model. qp_data. parameters)
1364+ for (p, index) in model. parameters
1365+ model. qp_data. parameters[p. value] = model. nlp_model[index]
1366+ end
1367+ end
13421368 # The default print level is `5`
13431369 Ipopt. AddIpoptIntOption (inner, " print_level" , model. silent ? 0 : 5 )
13441370 # Other misc options that over-ride the ones set above.
@@ -1358,13 +1384,12 @@ function MOI.optimize!(model::Optimizer)
13581384 end
13591385 end
13601386 # Initialize the starting point, projecting variables from 0 onto their
1361- # bounds if VariablePrimalStart is not provided.
1387+ # bounds if VariablePrimalStart is not provided.
13621388 for i in 1 : length (model. variable_primal_start)
1363- inner. x[i] = if model. variable_primal_start[i] != = nothing
1364- model. variable_primal_start[i]
1365- else
1366- clamp (0.0 , model. variables. lower[i], model. variables. upper[i])
1367- end
1389+ inner. x[i] = something (
1390+ model. variable_primal_start[i],
1391+ clamp (0.0 , model. variables. lower[i], model. variables. upper[i]),
1392+ )
13681393 end
13691394 for (i, start) in enumerate (model. qp_data. mult_g)
13701395 inner. mult_g[i] = _dual_start (model, start, - 1 )
@@ -1384,22 +1409,13 @@ function MOI.optimize!(model::Optimizer)
13841409 inner. mult_x_L[i] = _dual_start (model, model. mult_x_L[i])
13851410 inner. mult_x_U[i] = _dual_start (model, model. mult_x_U[i], - 1 )
13861411 end
1412+ # Reset timers
13871413 model. barrier_iterations = 0
1388- function _moi_callback (args... )
1389- # iter_count is args[2]
1390- model. barrier_iterations = args[2 ]
1391- if model. callback != = nothing
1392- return model. callback (args... )
1393- end
1394- return true
1395- end
1396- # Clear timers
13971414 for (_, s) in model. vector_nonlinear_oracle_constraints
13981415 s. eval_f_timer = 0.0
13991416 s. eval_jacobian_timer = 0.0
14001417 s. eval_hessian_lagrangian_timer = 0.0
14011418 end
1402- Ipopt. SetIntermediateCallback (inner, _moi_callback)
14031419 Ipopt. IpoptSolve (inner)
14041420 model. solve_time = time () - start_time
14051421 return
0 commit comments