From ebf337dc1a73015c6d7c180c5b338e52b2a5a873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 13 Aug 2026 08:53:58 +0000 Subject: [PATCH 1/5] Use MOI.Nonlinear.QPBlockData for the quadratic constraints Replace the per-constraint QuadraticConstraint storage (COO Hessian, sparse gradient, gradient-sparsity dictionary) and its bespoke evaluation loops with a MOI.Nonlinear.QPBlockData holding the quadratic constraints, in both MathOptNLPModel and MathOptNLSModel. QuadraticConstraints keeps its nquad/nnzj/nnzh fields and now also stores the precomputed Jacobian and Hessian structures of the block (with per-constraint Hessian offsets for jth_hess_coord!). The block-shaped NLPModels methods (cons, jac, jprod, jtprod, hess, hprod) call the corresponding MOI evaluator methods of the block; the per-constraint methods (jth_hess_coord!, jth_hprod!, ghjvprod!) use MOI.Nonlinear's per-function helpers. LinearConstraints and Objective are deliberately kept: their materialized sparse structures are performance fast paths that QPBlockData would not preserve. Behavioral notes: - The Jacobian structure of a quadratic constraint may now contain duplicate column entries (one per term) instead of the previous deduplicated gradient sparsity, so nnzj counts can differ; NLPModels COO conventions allow duplicates. - VectorQuadraticFunction constants are now handled with MOI.Utilities.shift_constant, fixing the previous handling that produced lcon = +constant for Nonnegatives and an infeasible lcon/ucon pair for Zeros with nonzero constants. Co-Authored-By: Claude Fable 5 --- src/moi_nlp_model.jl | 202 +++++++++++++++++----------------------- src/moi_nls_model.jl | 161 ++++++++++++-------------------- src/utils.jl | 216 +++++++++++++++---------------------------- 3 files changed, 217 insertions(+), 362 deletions(-) diff --git a/src/moi_nlp_model.jl b/src/moi_nlp_model.jl index a4975aa3..8f33ea11 100644 --- a/src/moi_nlp_model.jl +++ b/src/moi_nlp_model.jl @@ -132,10 +132,7 @@ function NLPModels.cons_nln!(nlp::MathOptNLPModel, x::AbstractVector, c::Abstrac offset = 0 if nlp.quadcon.nquad > 0 offset += nlp.quadcon.nquad - for i = 1:(nlp.quadcon.nquad) - qcon = nlp.quadcon.constraints[i] - c[i] = 0.5 * coo_sym_dot(qcon.A.rows, qcon.A.cols, qcon.A.vals, x, x) + dot(qcon.b, x) - end + MOI.eval_constraint(nlp.quadcon.block, view(c, 1:(nlp.quadcon.nquad)), x) end if nlp.nlcon.nnln > 0 offset += nlp.nlcon.nnln @@ -166,11 +163,8 @@ function NLPModels.cons!(nlp::MathOptNLPModel, x::AbstractVector, c::AbstractVec end if nlp.quadcon.nquad > 0 offset += nlp.quadcon.nquad - for i = 1:(nlp.quadcon.nquad) - qcon = nlp.quadcon.constraints[i] - c[nlp.meta.nlin + i] = - 0.5 * coo_sym_dot(qcon.A.rows, qcon.A.cols, qcon.A.vals, x, x) + dot(qcon.b, x) - end + ind_quad = (nlp.meta.nlin + 1):(nlp.meta.nlin + nlp.quadcon.nquad) + MOI.eval_constraint(nlp.quadcon.block, view(c, ind_quad), x) end if nlp.nlcon.nnln > 0 offset += nlp.nlcon.nnln @@ -210,14 +204,10 @@ function NLPModels.jac_nln_structure!( ) offset = 0 if nlp.quadcon.nquad > 0 - for i = 1:(nlp.quadcon.nquad) - # qcon.g is the sparsity pattern of the gradient of the quadratic constraint qcon - qcon = nlp.quadcon.constraints[i] - ind_quad = (offset + 1):(offset + qcon.nnzg) - view(rows, ind_quad) .= i - view(cols, ind_quad) .= qcon.g - offset += qcon.nnzg - end + ind_quad = 1:(nlp.quadcon.nnzj) + view(rows, ind_quad) .= nlp.quadcon.jac_rows + view(cols, ind_quad) .= nlp.quadcon.jac_cols + offset += nlp.quadcon.nnzj end @assert offset == nlp.quadcon.nnzj if nlp.nlcon.nnln > 0 @@ -257,14 +247,10 @@ function NLPModels.jac_structure!( offset += nlp.lincon.nnzj end if nlp.quadcon.nquad > 0 - for i = 1:(nlp.quadcon.nquad) - # qcon.g is the sparsity pattern of the gradient of the quadratic constraint qcon - qcon = nlp.quadcon.constraints[i] - ind_quad = (offset + 1):(offset + qcon.nnzg) - view(rows, ind_quad) .= nlp.meta.nlin .+ i - view(cols, ind_quad) .= qcon.g - offset += qcon.nnzg - end + ind_quad = (offset + 1):(offset + nlp.quadcon.nnzj) + view(rows, ind_quad) .= nlp.meta.nlin .+ nlp.quadcon.jac_rows + view(cols, ind_quad) .= nlp.quadcon.jac_cols + offset += nlp.quadcon.nnzj end @assert offset == nlp.lincon.nnzj + nlp.quadcon.nnzj if nlp.nlcon.nnln > 0 @@ -304,26 +290,8 @@ function NLPModels.jac_nln_coord!(nlp::MathOptNLPModel, x::AbstractVector, vals: offset = 0 if nlp.quadcon.nquad > 0 ind_quad = 1:(nlp.quadcon.nnzj) - view(vals, ind_quad) .= 0.0 - for i = 1:(nlp.quadcon.nquad) - qcon = nlp.quadcon.constraints[i] - for (j, ind) in enumerate(qcon.b.nzind) - k = qcon.dg[ind] - vals[offset + k] += qcon.b.nzval[j] - end - for j = 1:(qcon.nnzh) - row = qcon.A.rows[j] - col = qcon.A.cols[j] - val = qcon.A.vals[j] - k1 = qcon.dg[row] - vals[offset + k1] += val * x[col] - if row != col - k2 = qcon.dg[col] - vals[offset + k2] += val * x[row] - end - end - offset += qcon.nnzg - end + MOI.eval_constraint_jacobian(nlp.quadcon.block, view(vals, ind_quad), x) + offset += nlp.quadcon.nnzj end if nlp.nlcon.nnln > 0 ind_nnln = (offset + 1):(offset + nlp.nlcon.nnzj) @@ -355,26 +323,8 @@ function NLPModels.jac_coord!(nlp::MathOptNLPModel, x::AbstractVector, vals::Abs end if nlp.quadcon.nquad > 0 ind_quad = (nlp.lincon.nnzj + 1):(nlp.lincon.nnzj + nlp.quadcon.nnzj) - view(vals, ind_quad) .= 0.0 - for i = 1:(nlp.quadcon.nquad) - qcon = nlp.quadcon.constraints[i] - for (j, ind) in enumerate(qcon.b.nzind) - k = qcon.dg[ind] - vals[offset + k] += qcon.b.nzval[j] - end - for j = 1:(qcon.nnzh) - row = qcon.A.rows[j] - col = qcon.A.cols[j] - val = qcon.A.vals[j] - k1 = qcon.dg[row] - vals[offset + k1] += val * x[col] - if row != col - k2 = qcon.dg[col] - vals[offset + k2] += val * x[row] - end - end - offset += qcon.nnzg - end + MOI.eval_constraint_jacobian(nlp.quadcon.block, view(vals, ind_quad), x) + offset += nlp.quadcon.nnzj end if nlp.nlcon.nnln > 0 ind_nnln = (offset + 1):(offset + nlp.nlcon.nnzj) @@ -422,11 +372,9 @@ function NLPModels.jprod_nln!( ) increment!(nlp, :neval_jprod_nln) if nlp.quadcon.nquad > 0 - for i = 1:(nlp.quadcon.nquad) - # Jv[i] += (Aᵢ * x + bᵢ)ᵀ * v - qcon = nlp.quadcon.constraints[i] - Jv[i] = coo_sym_dot(qcon.A.rows, qcon.A.cols, qcon.A.vals, x, v) + dot(qcon.b, v) - end + ind_quad = 1:(nlp.quadcon.nquad) + view(Jv, ind_quad) .= 0.0 + MOI.eval_constraint_jacobian_product(nlp.quadcon.block, view(Jv, ind_quad), x, v) end if nlp.nlcon.nnln > 0 ind_nnln = (nlp.quadcon.nquad + 1):(nlp.quadcon.nquad + nlp.nlcon.nnln) @@ -478,12 +426,9 @@ function NLPModels.jprod!( ) end if nlp.quadcon.nquad > 0 - for i = 1:(nlp.quadcon.nquad) - # Jv[i] = (Aᵢ * x + bᵢ)ᵀ * v - qcon = nlp.quadcon.constraints[i] - Jv[nlp.meta.nlin + i] = - coo_sym_dot(qcon.A.rows, qcon.A.cols, qcon.A.vals, x, v) + dot(qcon.b, v) - end + ind_quad = (nlp.meta.nlin + 1):(nlp.meta.nlin + nlp.quadcon.nquad) + view(Jv, ind_quad) .= 0.0 + MOI.eval_constraint_jacobian_product(nlp.quadcon.block, view(Jv, ind_quad), x, v) end if nlp.nlcon.nnln > 0 ind_nnln = @@ -544,12 +489,13 @@ function NLPModels.jtprod_nln!( end (nlp.nlcon.nnln == 0) && (Jtv .= 0.0) if nlp.quadcon.nquad > 0 - for i = 1:(nlp.quadcon.nquad) - # Jtv += v[i] * (Aᵢ * x + bᵢ) - qcon = nlp.quadcon.constraints[i] - coo_sym_add_mul!(qcon.A.rows, qcon.A.cols, qcon.A.vals, x, Jtv, v[i]) - Jtv .+= v[i] .* qcon.b - end + # Jtv += Jᵀ * v[1:nquad], where J is the Jacobian of the quadratic block + MOI.eval_constraint_jacobian_transpose_product( + nlp.quadcon.block, + Jtv, + x, + view(v, 1:(nlp.quadcon.nquad)), + ) end if nlp.oracles.ncon > 0 row_offset = nlp.quadcon.nquad + nlp.nlcon.nnln @@ -597,11 +543,13 @@ function NLPModels.jtprod!( ) end if nlp.quadcon.nquad > 0 - for i = 1:(nlp.quadcon.nquad) - qcon = nlp.quadcon.constraints[i] - coo_sym_add_mul!(qcon.A.rows, qcon.A.cols, qcon.A.vals, x, Jtv, v[nlp.meta.nlin + i]) - Jtv .+= v[nlp.meta.nlin + i] .* qcon.b - end + ind_quad = (nlp.meta.nlin + 1):(nlp.meta.nlin + nlp.quadcon.nquad) + MOI.eval_constraint_jacobian_transpose_product( + nlp.quadcon.block, + Jtv, + x, + view(v, ind_quad), + ) end if nlp.oracles.ncon > 0 row_offset = nlp.meta.nlin + nlp.quadcon.nquad + nlp.nlcon.nnln @@ -636,12 +584,9 @@ function NLPModels.hess_structure!( end index = nlp.obj.nnzh if nlp.quadcon.nquad > 0 - for i = 1:(nlp.quadcon.nquad) - qcon = nlp.quadcon.constraints[i] - view(rows, (index + 1):(index + qcon.nnzh)) .= qcon.A.rows - view(cols, (index + 1):(index + qcon.nnzh)) .= qcon.A.cols - index += qcon.nnzh - end + view(rows, (index + 1):(index + nlp.quadcon.nnzh)) .= nlp.quadcon.hess_rows + view(cols, (index + 1):(index + nlp.quadcon.nnzh)) .= nlp.quadcon.hess_cols + index += nlp.quadcon.nnzh end if (nlp.obj.type == "NONLINEAR") || (nlp.nlcon.nnln > 0) view(rows, (index + 1):(index + nlp.nlcon.nnzh)) .= nlp.nlcon.hess_rows @@ -687,15 +632,18 @@ function NLPModels.hess_coord!( MOI.eval_hessian_lagrangian(nlp.eval, view(vals, ind_nnzh), x, obj_weight, λ_nnln) end - # 3. Quadratic constraint Hessian blocks + # 3. Quadratic constraint Hessian blocks. The objective of the block is + # zero, so the objective weight is irrelevant. if nlp.quadcon.nquad > 0 - index = nlp.obj.nnzh - for i = 1:(nlp.quadcon.nquad) - qcon = nlp.quadcon.constraints[i] - ind = (index + 1):(index + qcon.nnzh) - view(vals, ind) .= y[nlp.meta.nlin + i] .* qcon.A.vals - index += qcon.nnzh - end + ind = (nlp.obj.nnzh + 1):(nlp.obj.nnzh + nlp.quadcon.nnzh) + ind_quad = (nlp.meta.nlin + 1):(nlp.meta.nlin + nlp.quadcon.nquad) + MOI.eval_hessian_lagrangian( + nlp.quadcon.block, + view(vals, ind), + x, + 0.0, + view(y, ind_quad), + ) end # 4. Oracle Hessian blocks are appended at the very end @@ -767,14 +715,15 @@ function NLPModels.jth_hess_coord!( # Quadratic constraints if nlp.meta.nlin + 1 ≤ j ≤ nlp.meta.nlin + nlp.quadcon.nquad - index = nlp.obj.nnzh - for i = 1:(nlp.quadcon.nquad) - qcon = nlp.quadcon.constraints[i] - if j == nlp.meta.nlin + i - view(vals, (index + 1):(index + qcon.nnzh)) .= qcon.A.vals - end - index += qcon.nnzh - end + i = j - nlp.meta.nlin + index = nlp.obj.nnzh + nlp.quadcon.hess_offset[i] + nnzh_i = nlp.quadcon.hess_offset[i + 1] - nlp.quadcon.hess_offset[i] + MOI.Nonlinear._eval_sparse_hessian( + view(vals, (index + 1):(index + nnzh_i)), + nlp.quadcon.block.constraints[i], + 1.0, + nlp.quadcon.block.parameters, + ) end # Non-oracle nonlinear constraints @@ -847,10 +796,15 @@ function NLPModels.hprod!( end if nlp.quadcon.nquad > 0 (nlp.obj.type == "LINEAR") && (nlp.nlcon.nnln == 0) && (hv .= 0.0) - for i = 1:(nlp.quadcon.nquad) - qcon = nlp.quadcon.constraints[i] - coo_sym_add_mul!(qcon.A.rows, qcon.A.cols, qcon.A.vals, v, hv, y[nlp.meta.nlin + i]) - end + ind_quad = (nlp.meta.nlin + 1):(nlp.meta.nlin + nlp.quadcon.nquad) + MOI.eval_hessian_lagrangian_product( + nlp.quadcon.block, + hv, + x, + v, + 0.0, + view(y, ind_quad), + ) end if nlp.oracles.ncon > 0 (nlp.obj.type == "LINEAR") && (nlp.meta.nnln == nlp.oracles.ncon) && (hv .= 0.0) @@ -920,8 +874,14 @@ function NLPModels.jth_hprod!( @rangecheck 1 nlp.meta.ncon j hv .= 0.0 if nlp.meta.nlin + 1 ≤ j ≤ nlp.meta.nlin + nlp.quadcon.nquad - qcon = nlp.quadcon.constraints[j - nlp.meta.nlin] - coo_sym_add_mul!(qcon.A.rows, qcon.A.cols, qcon.A.vals, v, hv, 1.0) + MOI.Nonlinear._eval_Hv_product( + nlp.quadcon.block.constraints[j - nlp.meta.nlin], + hv, + x, + v, + 1.0, + nlp.quadcon.block.parameters, + ) elseif nlp.meta.nlin + nlp.quadcon.nquad + 1 ≤ j ≤ nlp.meta.nlin + nlp.quadcon.nquad + nlp.nlcon.nnln @@ -974,8 +934,16 @@ function NLPModels.ghjvprod!( increment!(nlp, :neval_hprod) ghv .= 0.0 for i = (nlp.meta.nlin + 1):(nlp.meta.nlin + nlp.quadcon.nquad) - qcon = nlp.quadcon.constraints[i - nlp.meta.nlin] - ghv[i] = coo_sym_dot(qcon.A.rows, qcon.A.cols, qcon.A.vals, g, v) + fill!(nlp.hv, 0.0) + MOI.Nonlinear._eval_Hv_product( + nlp.quadcon.block.constraints[i - nlp.meta.nlin], + nlp.hv, + x, + v, + 1.0, + nlp.quadcon.block.parameters, + ) + ghv[i] = dot(g, nlp.hv) end for i = (nlp.meta.nlin + nlp.quadcon.nquad + 1):(nlp.meta.ncon) jth_hprod!(nlp, x, v, i, nlp.hv) diff --git a/src/moi_nls_model.jl b/src/moi_nls_model.jl index eb11cb60..bedb234b 100644 --- a/src/moi_nls_model.jl +++ b/src/moi_nls_model.jl @@ -270,10 +270,7 @@ function NLPModels.cons_nln!(nls::MathOptNLSModel, x::AbstractVector, c::Abstrac offset = 0 if nls.quadcon.nquad > 0 offset += nls.quadcon.nquad - for i = 1:(nls.quadcon.nquad) - qcon = nls.quadcon.constraints[i] - c[i] = 0.5 * coo_sym_dot(qcon.A.rows, qcon.A.cols, qcon.A.vals, x, x) + dot(qcon.b, x) - end + MOI.eval_constraint(nls.quadcon.block, view(c, 1:(nls.quadcon.nquad)), x) end if nls.nlcon.nnln > 0 offset += nls.nlcon.nnln @@ -303,11 +300,8 @@ function NLPModels.cons!(nls::MathOptNLSModel, x::AbstractVector, c::AbstractVec end if nls.quadcon.nquad > 0 offset += nls.quadcon.nquad - for i = 1:(nls.quadcon.nquad) - qcon = nls.quadcon.constraints[i] - c[nls.meta.nlin + i] = - 0.5 * coo_sym_dot(qcon.A.rows, qcon.A.cols, qcon.A.vals, x, x) + dot(qcon.b, x) - end + ind_quad = (nls.meta.nlin + 1):(nls.meta.nlin + nls.quadcon.nquad) + MOI.eval_constraint(nls.quadcon.block, view(c, ind_quad), x) end if nls.nlcon.nnln > 0 offset += nls.nlcon.nnln @@ -346,14 +340,10 @@ function NLPModels.jac_nln_structure!( ) offset = 0 if nls.quadcon.nquad > 0 - for i = 1:(nls.quadcon.nquad) - # qcon.g is the sparsity pattern of the gradient of the quadratic constraint qcon - qcon = nls.quadcon.constraints[i] - ind_quad = (offset + 1):(offset + qcon.nnzg) - view(rows, ind_quad) .= i - view(cols, ind_quad) .= qcon.g - offset += qcon.nnzg - end + ind_quad = 1:(nls.quadcon.nnzj) + view(rows, ind_quad) .= nls.quadcon.jac_rows + view(cols, ind_quad) .= nls.quadcon.jac_cols + offset += nls.quadcon.nnzj end if nls.nlcon.nnln > 0 # non-oracle nonlinear constraints @@ -390,14 +380,10 @@ function NLPModels.jac_structure!( offset += nls.lincon.nnzj end if nls.quadcon.nquad > 0 - for i = 1:(nls.quadcon.nquad) - # qcon.g is the sparsity pattern of the gradient of the quadratic constraint qcon - qcon = nls.quadcon.constraints[i] - ind_quad = (offset + 1):(offset + qcon.nnzg) - view(rows, ind_quad) .= nls.meta.nlin .+ i - view(cols, ind_quad) .= qcon.g - offset += qcon.nnzg - end + ind_quad = (offset + 1):(offset + nls.quadcon.nnzj) + view(rows, ind_quad) .= nls.meta.nlin .+ nls.quadcon.jac_rows + view(cols, ind_quad) .= nls.quadcon.jac_cols + offset += nls.quadcon.nnzj end if nls.nlcon.nnln > 0 # non-oracle nonlinear constraints @@ -434,26 +420,8 @@ function NLPModels.jac_nln_coord!(nls::MathOptNLSModel, x::AbstractVector, vals: offset = 0 if nls.quadcon.nquad > 0 ind_quad = 1:(nls.quadcon.nnzj) - view(vals, ind_quad) .= 0.0 - for i = 1:(nls.quadcon.nquad) - qcon = nls.quadcon.constraints[i] - for (j, ind) in enumerate(qcon.b.nzind) - k = qcon.dg[ind] - vals[offset + k] += qcon.b.nzval[j] - end - for j = 1:(qcon.nnzh) - row = qcon.A.rows[j] - col = qcon.A.cols[j] - val = qcon.A.vals[j] - k1 = qcon.dg[row] - vals[offset + k1] += val * x[col] - if row != col - k2 = qcon.dg[col] - vals[offset + k2] += val * x[row] - end - end - offset += qcon.nnzg - end + MOI.eval_constraint_jacobian(nls.quadcon.block, view(vals, ind_quad), x) + offset += nls.quadcon.nnzj end if nls.nlcon.nnln > 0 ind_nnln = (offset + 1):(offset + nls.nlcon.nnzj) @@ -484,26 +452,8 @@ function NLPModels.jac_coord!(nls::MathOptNLSModel, x::AbstractVector, vals::Abs end if nls.quadcon.nquad > 0 ind_quad = (nls.lincon.nnzj + 1):(nls.lincon.nnzj + nls.quadcon.nnzj) - view(vals, ind_quad) .= 0.0 - for i = 1:(nls.quadcon.nquad) - qcon = nls.quadcon.constraints[i] - for (j, ind) in enumerate(qcon.b.nzind) - k = qcon.dg[ind] - vals[offset + k] += qcon.b.nzval[j] - end - for j = 1:(qcon.nnzh) - row = qcon.A.rows[j] - col = qcon.A.cols[j] - val = qcon.A.vals[j] - k1 = qcon.dg[row] - vals[offset + k1] += val * x[col] - if row != col - k2 = qcon.dg[col] - vals[offset + k2] += val * x[row] - end - end - offset += qcon.nnzg - end + MOI.eval_constraint_jacobian(nls.quadcon.block, view(vals, ind_quad), x) + offset += nls.quadcon.nnzj end if nls.nlcon.nnln > 0 ind_nnln = (offset + 1):(offset + nls.nlcon.nnzj) @@ -550,11 +500,9 @@ function NLPModels.jprod_nln!( ) increment!(nls, :neval_jprod_nln) if nls.quadcon.nquad > 0 - for i = 1:(nls.quadcon.nquad) - # Jv[i] += (Aᵢ * x + bᵢ)ᵀ * v - qcon = nls.quadcon.constraints[i] - Jv[i] = coo_sym_dot(qcon.A.rows, qcon.A.cols, qcon.A.vals, x, v) + dot(qcon.b, v) - end + ind_quad = 1:(nls.quadcon.nquad) + view(Jv, ind_quad) .= 0.0 + MOI.eval_constraint_jacobian_product(nls.quadcon.block, view(Jv, ind_quad), x, v) end if nls.nlcon.nnln > 0 ind_nnln = (nls.quadcon.nquad + 1):(nls.quadcon.nquad + nls.nlcon.nnln) @@ -606,12 +554,9 @@ function NLPModels.jprod!( ) end if nls.quadcon.nquad > 0 - for i = 1:(nls.quadcon.nquad) - # Jv[i] = (Aᵢ * x + bᵢ)ᵀ * v - qcon = nls.quadcon.constraints[i] - Jv[nls.meta.nlin + i] = - coo_sym_dot(qcon.A.rows, qcon.A.cols, qcon.A.vals, x, v) + dot(qcon.b, v) - end + ind_quad = (nls.meta.nlin + 1):(nls.meta.nlin + nls.quadcon.nquad) + view(Jv, ind_quad) .= 0.0 + MOI.eval_constraint_jacobian_product(nls.quadcon.block, view(Jv, ind_quad), x, v) end if nls.nlcon.nnln > 0 ind_nnln = @@ -672,12 +617,13 @@ function NLPModels.jtprod_nln!( end (nls.nlcon.nnln == 0) && (Jtv .= 0.0) if nls.quadcon.nquad > 0 - for i = 1:(nls.quadcon.nquad) - # Jtv += v[i] * (Aᵢ * x + bᵢ) - qcon = nls.quadcon.constraints[i] - coo_sym_add_mul!(qcon.A.rows, qcon.A.cols, qcon.A.vals, x, Jtv, v[i]) - Jtv .+= v[i] .* qcon.b - end + # Jtv += Jᵀ * v[1:nquad], where J is the Jacobian of the quadratic block + MOI.eval_constraint_jacobian_transpose_product( + nls.quadcon.block, + Jtv, + x, + view(v, 1:(nls.quadcon.nquad)), + ) end if nls.oracles.ncon > 0 row_offset = nls.quadcon.nquad + nls.nlcon.nnln @@ -725,11 +671,13 @@ function NLPModels.jtprod!( ) end if nls.quadcon.nquad > 0 - for i = 1:(nls.quadcon.nquad) - qcon = nls.quadcon.constraints[i] - coo_sym_add_mul!(qcon.A.rows, qcon.A.cols, qcon.A.vals, x, Jtv, v[nls.meta.nlin + i]) - Jtv .+= v[nls.meta.nlin + i] .* qcon.b - end + ind_quad = (nls.meta.nlin + 1):(nls.meta.nlin + nls.quadcon.nquad) + MOI.eval_constraint_jacobian_transpose_product( + nls.quadcon.block, + Jtv, + x, + view(v, ind_quad), + ) end if nls.oracles.ncon > 0 row_offset = nls.meta.nlin + nls.quadcon.nquad + nls.nlcon.nnln @@ -765,12 +713,9 @@ function NLPModels.hess_structure!( index = nls.lls.nnzh if nls.quadcon.nquad > 0 index = nls.lls.nnzh - for i = 1:(nls.quadcon.nquad) - qcon = nls.quadcon.constraints[i] - view(rows, (index + 1):(index + qcon.nnzh)) .= qcon.A.rows - view(cols, (index + 1):(index + qcon.nnzh)) .= qcon.A.cols - index += qcon.nnzh - end + view(rows, (index + 1):(index + nls.quadcon.nnzh)) .= nls.quadcon.hess_rows + view(cols, (index + 1):(index + nls.quadcon.nnzh)) .= nls.quadcon.hess_cols + index += nls.quadcon.nnzh end if (nls.nls_meta.nnln > 0) || (nls.nlcon.nnln > 0) view(rows, (index + 1):(index + nls.nlcon.nnzh)) .= nls.nlcon.hess_rows @@ -803,12 +748,17 @@ function NLPModels.hess_coord!( view(vals, 1:(nls.lls.nnzh)) .= obj_weight .* nls.lls.hessian.vals end if nls.quadcon.nquad > 0 - index = nls.lls.nnzh - for i = 1:(nls.quadcon.nquad) - qcon = nls.quadcon.constraints[i] - view(vals, (index + 1):(index + qcon.nnzh)) .= y[nls.meta.nlin + i] .* qcon.A.vals - index += qcon.nnzh - end + # The objective of the block is zero, so the objective weight is + # irrelevant. + ind = (nls.lls.nnzh + 1):(nls.lls.nnzh + nls.quadcon.nnzh) + ind_quad = (nls.meta.nlin + 1):(nls.meta.nlin + nls.quadcon.nquad) + MOI.eval_hessian_lagrangian( + nls.quadcon.block, + view(vals, ind), + x, + 0.0, + view(y, ind_quad), + ) end if (nls.nls_meta.nnln > 0) || (nls.nlcon.nnln > 0) λ = view( @@ -904,10 +854,15 @@ function NLPModels.hprod!( ) end if nls.quadcon.nquad > 0 - for i = 1:(nls.quadcon.nquad) - qcon = nls.quadcon.constraints[i] - coo_sym_add_mul!(qcon.A.rows, qcon.A.cols, qcon.A.vals, v, hv, y[nls.meta.nlin + i]) - end + ind_quad = (nls.meta.nlin + 1):(nls.meta.nlin + nls.quadcon.nquad) + MOI.eval_hessian_lagrangian_product( + nls.quadcon.block, + hv, + x, + v, + 0.0, + view(y, ind_quad), + ) end if nls.oracles.ncon > 0 offset_y = nls.meta.nlin + nls.quadcon.nquad + nls.nlcon.nnln diff --git a/src/utils.jl b/src/utils.jl index 9013716c..edf9447d 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -74,23 +74,58 @@ mutable struct LinearConstraints nnzj::Int end -# xᵀAx + bᵀx -mutable struct QuadraticConstraint - A::COO - b::SparseVector{Float64} - g::Vector{Int} - dg::Dict{Int, Int} - nnzg::Int - nnzh::Int -end +""" + QuadraticConstraints +The quadratic constraints, stored in a `MOI.Nonlinear.QPBlockData` whose rows +are the constraints in the order they were parsed. The Jacobian and Hessian +structures of the block are precomputed; `hess_offset[i]` is the number of +Hessian entries before those of constraint `i`, so that the entries of +constraint `i` are `hess_offset[i]+1:hess_offset[i+1]`. +""" mutable struct QuadraticConstraints nquad::Int - constraints::Vector{QuadraticConstraint} + block::MOI.Nonlinear.QPBlockData{Float64} + jac_rows::Vector{Int} + jac_cols::Vector{Int} nnzj::Int + hess_rows::Vector{Int} + hess_cols::Vector{Int} + hess_offset::Vector{Int} nnzh::Int end +function QuadraticConstraints(block::MOI.Nonlinear.QPBlockData{Float64}) + nquad = length(block) + jac_structure = MOI.jacobian_structure(block) + jac_rows = [r for (r, _) in jac_structure] + jac_cols = [c for (_, c) in jac_structure] + hess_structure = Tuple{Int, Int}[] + hess_offset = zeros(Int, nquad + 1) + for i = 1:nquad + MOI.Nonlinear._append_sparse_hessian_structure!( + block.constraints[i], + hess_structure, + block.parameters, + ) + hess_offset[i + 1] = length(hess_structure) + end + # NLPModels expects the lower triangle + hess_rows = [max(r, c) for (r, c) in hess_structure] + hess_cols = [min(r, c) for (r, c) in hess_structure] + return QuadraticConstraints( + nquad, + block, + jac_rows, + jac_cols, + length(jac_structure), + hess_rows, + hess_cols, + hess_offset, + length(hess_structure), + ) +end + """ NonLinearStructure @@ -319,127 +354,30 @@ end Parse a `ScalarQuadraticFunction` fun with its associated set. `qcons`, `quad_lcon`, `quad_ucon` are updated. """ -function parser_SQF(fun, set, nvar, qcons, quad_lcon, quad_ucon, index_map) - _index(v::MOI.VariableIndex) = index_map[v].value - - b = spzeros(Float64, nvar) - rows = Int[] - cols = Int[] - vals = Float64[] - - # Parse a ScalarAffineTerm{Float64}(coefficient, variable_index) - for term in fun.affine_terms - b[_index(term.variable)] = term.coefficient - end - - # Parse a ScalarQuadraticTerm{Float64}(coefficient, variable_index_1, variable_index_2) - for term in fun.quadratic_terms - i = _index(term.variable_1) - j = _index(term.variable_2) - if i ≥ j - push!(rows, i) - push!(cols, j) - else - push!(rows, j) - push!(cols, i) - end - push!(vals, term.coefficient) - end - - if typeof(set) in (MOI.Interval{Float64}, MOI.GreaterThan{Float64}) - push!(quad_lcon, -fun.constant + set.lower) - elseif typeof(set) == MOI.EqualTo{Float64} - push!(quad_lcon, -fun.constant + set.value) - else - push!(quad_lcon, -Inf) - end - - if typeof(set) in (MOI.Interval{Float64}, MOI.LessThan{Float64}) - push!(quad_ucon, -fun.constant + set.upper) - elseif typeof(set) == MOI.EqualTo{Float64} - push!(quad_ucon, -fun.constant + set.value) - else - push!(quad_ucon, Inf) - end - - A = COO(rows, cols, vals) - g = unique(vcat(rows, cols, b.nzind)) # sparsity pattern of Ax + b - nnzg = length(g) - # dg is a dictionary where: - # - The key `r` specifies a row index in the vector Ax + b. - # - The value `dg[r]` is a position in the vector (of length nnzg) - # where the non-zero entries of the Jacobian for row `r` are stored. - dg = Dict{Int, Int}(g[p] => p for p = 1:nnzg) - nnzh = length(vals) - qcon = QuadraticConstraint(A, b, g, dg, nnzg, nnzh) - push!(qcons, qcon) -end - -""" - parser_VQF(fun, set, nvar, qcons, quad_lcon, quad_ucon, index_map) - -Parse a `VectorQuadraticFunction` fun with its associated set. -`qcons`, `quad_lcon`, `quad_ucon` are updated. -""" -function parser_VQF(fun, set, nvar, qcons, quad_lcon, quad_ucon, index_map) - _index(v::MOI.VariableIndex) = index_map[v].value - - ncon = length(fun.constants) - for k = 1:ncon - b = spzeros(Float64, nvar) - rows = Int[] - cols = Int[] - vals = Float64[] - - # Parse a VectorAffineTerm{Float64}(output_index, scalar_term) - for affine_term in fun.affine_terms - if affine_term.output_index == k - b[_index(affine_term.scalar_term.variable)] = affine_term.scalar_term.coefficient - end - end - - # Parse a VectorQuadraticTerm{Float64}(output_index, scalar_term) - for quadratic_term in fun.quadratic_terms - if quadratic_term.output_index == k - i = _index(quadratic_term.scalar_term.variable_1) - j = _index(quadratic_term.scalar_term.variable_2) - if i ≥ j - push!(rows, i) - push!(cols, j) - else - push!(rows, j) - push!(cols, i) - end - push!(vals, quadratic_term.scalar_term.coefficient) - end - end - - constant = fun.constants[k] - - if typeof(set) in (MOI.Nonnegatives, MOI.Zeros) - append!(quad_lcon, constant) - else - append!(quad_lcon, -Inf) - end - - if typeof(set) in (MOI.Nonpositives, MOI.Zeros) - append!(quad_ucon, -constant) - else - append!(quad_ucon, Inf) - end - - A = COO(rows, cols, vals) - g = unique(vcat(rows, cols, b.nzind)) # sparsity pattern of Ax + b - nnzg = length(g) - # dg is a dictionary where: - # - The key `r` specifies a row index in the vector Ax + b. - # - The value `dg[r]` is a position in the vector (of length nnzg) - # where the non-zero entries of the Jacobian for row `r` are stored. - dg = Dict{Int, Int}(g[p] => p for p = 1:nnzg) - nnzh = length(vals) - qcon = QuadraticConstraint(A, b, g, dg, nnzg, nnzh) - push!(qcons, qcon) +function parser_SQF(fun, set, block, index_map) + f = MOI.Utilities.map_indices(index_map, fun) + # The constant is moved into the set, as MOI requires for + # scalar-function-in-set constraints. + g = SQF(f.quadratic_terms, f.affine_terms, 0.0) + MOI.add_constraint(block, g, MOI.Utilities.shift_constant(set, -f.constant)) + return +end + +_scalar_set(::MOI.Nonnegatives) = MOI.GreaterThan(0.0) +_scalar_set(::MOI.Nonpositives) = MOI.LessThan(0.0) +_scalar_set(::MOI.Zeros) = MOI.EqualTo(0.0) + +function parser_VQF(fun, set, block, index_map) + f = MOI.Utilities.map_indices(index_map, fun) + for fi in MOI.Utilities.scalarize(f) + g = SQF(fi.quadratic_terms, fi.affine_terms, 0.0) + MOI.add_constraint( + block, + g, + MOI.Utilities.shift_constant(_scalar_set(set), -fi.constant), + ) end + return end """ @@ -459,9 +397,7 @@ function parser_MOI(moimodel, index_map, nvar) # Variables associated to quadratic constraints nquad = 0 - qcons = QuadraticConstraint[] - quad_lcon = Float64[] - quad_ucon = Float64[] + quad_block = MOI.Nonlinear.QPBlockData{Float64}() contypes = MOI.get(moimodel, MOI.ListOfConstraintTypesPresent()) for (F, S) in contypes @@ -494,11 +430,11 @@ function parser_MOI(moimodel, index_map, nvar) nlin += set.dimension end if typeof(fun) <: SQF - parser_SQF(fun, set, nvar, qcons, quad_lcon, quad_ucon, index_map) + parser_SQF(fun, set, quad_block, index_map) nquad += 1 end if typeof(fun) <: VQF - parser_VQF(fun, set, nvar, qcons, quad_lcon, quad_ucon, index_map) + parser_VQF(fun, set, quad_block, index_map) nquad += set.dimension end end @@ -506,13 +442,9 @@ function parser_MOI(moimodel, index_map, nvar) coo = COO(linrows, lincols, linvals) lin_nnzj = length(linvals) lincon = LinearConstraints(coo, lin_nnzj) - quad_nnzj = 0 - quad_nnzh = 0 - for i = 1:nquad - quad_nnzj += qcons[i].nnzg - quad_nnzh += qcons[i].nnzh - end - quadcon = QuadraticConstraints(nquad, qcons, quad_nnzj, quad_nnzh) + quadcon = QuadraticConstraints(quad_block) + quad_lcon = copy(quad_block.g_L) + quad_ucon = copy(quad_block.g_U) return nlin, lincon, lin_lcon, lin_ucon, quadcon, quad_lcon, quad_ucon end From bd9e6f828fdf8cfed8783e4e00f36977549caa86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 13 Aug 2026 12:28:49 +0200 Subject: [PATCH 2/5] MOI --- .github/workflows/ci.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b1d59ca..630355b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,6 +54,13 @@ jobs: ${{ runner.os }}-test-${{ env.cache-name }}- ${{ runner.os }}-test- ${{ runner.os }}- + - name: MOI + shell: julia --project=@. {0} + run: | + using Pkg + Pkg.add([ + PackageSpec(name="MathOptInterface", rev="bl/qp_block_data"), + ]) - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 From c0f364af48606350d90d7f3eb92748146fab53fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 13 Aug 2026 12:44:17 +0000 Subject: [PATCH 3/5] Use the add_... product functions of QPBlockData Follow-up to the rename in MathOptInterface: the accumulating product functions of QPBlockData are no longer methods of the MOI.eval_... generic functions. The calls on nls.Feval are unchanged: that is an MOI.Nonlinear.Evaluator, for which the MOI generic functions apply. --- src/moi_nlp_model.jl | 14 +++++++------- src/moi_nls_model.jl | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/moi_nlp_model.jl b/src/moi_nlp_model.jl index 8f33ea11..0b3b9297 100644 --- a/src/moi_nlp_model.jl +++ b/src/moi_nlp_model.jl @@ -374,7 +374,7 @@ function NLPModels.jprod_nln!( if nlp.quadcon.nquad > 0 ind_quad = 1:(nlp.quadcon.nquad) view(Jv, ind_quad) .= 0.0 - MOI.eval_constraint_jacobian_product(nlp.quadcon.block, view(Jv, ind_quad), x, v) + MOI.Nonlinear.add_constraint_jacobian_product(nlp.quadcon.block, view(Jv, ind_quad), x, v) end if nlp.nlcon.nnln > 0 ind_nnln = (nlp.quadcon.nquad + 1):(nlp.quadcon.nquad + nlp.nlcon.nnln) @@ -428,7 +428,7 @@ function NLPModels.jprod!( if nlp.quadcon.nquad > 0 ind_quad = (nlp.meta.nlin + 1):(nlp.meta.nlin + nlp.quadcon.nquad) view(Jv, ind_quad) .= 0.0 - MOI.eval_constraint_jacobian_product(nlp.quadcon.block, view(Jv, ind_quad), x, v) + MOI.Nonlinear.add_constraint_jacobian_product(nlp.quadcon.block, view(Jv, ind_quad), x, v) end if nlp.nlcon.nnln > 0 ind_nnln = @@ -490,7 +490,7 @@ function NLPModels.jtprod_nln!( (nlp.nlcon.nnln == 0) && (Jtv .= 0.0) if nlp.quadcon.nquad > 0 # Jtv += Jᵀ * v[1:nquad], where J is the Jacobian of the quadratic block - MOI.eval_constraint_jacobian_transpose_product( + MOI.Nonlinear.add_constraint_jacobian_transpose_product( nlp.quadcon.block, Jtv, x, @@ -544,7 +544,7 @@ function NLPModels.jtprod!( end if nlp.quadcon.nquad > 0 ind_quad = (nlp.meta.nlin + 1):(nlp.meta.nlin + nlp.quadcon.nquad) - MOI.eval_constraint_jacobian_transpose_product( + MOI.Nonlinear.add_constraint_jacobian_transpose_product( nlp.quadcon.block, Jtv, x, @@ -797,7 +797,7 @@ function NLPModels.hprod!( if nlp.quadcon.nquad > 0 (nlp.obj.type == "LINEAR") && (nlp.nlcon.nnln == 0) && (hv .= 0.0) ind_quad = (nlp.meta.nlin + 1):(nlp.meta.nlin + nlp.quadcon.nquad) - MOI.eval_hessian_lagrangian_product( + MOI.Nonlinear.add_hessian_lagrangian_product( nlp.quadcon.block, hv, x, @@ -874,7 +874,7 @@ function NLPModels.jth_hprod!( @rangecheck 1 nlp.meta.ncon j hv .= 0.0 if nlp.meta.nlin + 1 ≤ j ≤ nlp.meta.nlin + nlp.quadcon.nquad - MOI.Nonlinear._eval_Hv_product( + MOI.Nonlinear._add_Hv_product( nlp.quadcon.block.constraints[j - nlp.meta.nlin], hv, x, @@ -935,7 +935,7 @@ function NLPModels.ghjvprod!( ghv .= 0.0 for i = (nlp.meta.nlin + 1):(nlp.meta.nlin + nlp.quadcon.nquad) fill!(nlp.hv, 0.0) - MOI.Nonlinear._eval_Hv_product( + MOI.Nonlinear._add_Hv_product( nlp.quadcon.block.constraints[i - nlp.meta.nlin], nlp.hv, x, diff --git a/src/moi_nls_model.jl b/src/moi_nls_model.jl index bedb234b..25892eb1 100644 --- a/src/moi_nls_model.jl +++ b/src/moi_nls_model.jl @@ -502,7 +502,7 @@ function NLPModels.jprod_nln!( if nls.quadcon.nquad > 0 ind_quad = 1:(nls.quadcon.nquad) view(Jv, ind_quad) .= 0.0 - MOI.eval_constraint_jacobian_product(nls.quadcon.block, view(Jv, ind_quad), x, v) + MOI.Nonlinear.add_constraint_jacobian_product(nls.quadcon.block, view(Jv, ind_quad), x, v) end if nls.nlcon.nnln > 0 ind_nnln = (nls.quadcon.nquad + 1):(nls.quadcon.nquad + nls.nlcon.nnln) @@ -556,7 +556,7 @@ function NLPModels.jprod!( if nls.quadcon.nquad > 0 ind_quad = (nls.meta.nlin + 1):(nls.meta.nlin + nls.quadcon.nquad) view(Jv, ind_quad) .= 0.0 - MOI.eval_constraint_jacobian_product(nls.quadcon.block, view(Jv, ind_quad), x, v) + MOI.Nonlinear.add_constraint_jacobian_product(nls.quadcon.block, view(Jv, ind_quad), x, v) end if nls.nlcon.nnln > 0 ind_nnln = @@ -618,7 +618,7 @@ function NLPModels.jtprod_nln!( (nls.nlcon.nnln == 0) && (Jtv .= 0.0) if nls.quadcon.nquad > 0 # Jtv += Jᵀ * v[1:nquad], where J is the Jacobian of the quadratic block - MOI.eval_constraint_jacobian_transpose_product( + MOI.Nonlinear.add_constraint_jacobian_transpose_product( nls.quadcon.block, Jtv, x, @@ -672,7 +672,7 @@ function NLPModels.jtprod!( end if nls.quadcon.nquad > 0 ind_quad = (nls.meta.nlin + 1):(nls.meta.nlin + nls.quadcon.nquad) - MOI.eval_constraint_jacobian_transpose_product( + MOI.Nonlinear.add_constraint_jacobian_transpose_product( nls.quadcon.block, Jtv, x, @@ -855,7 +855,7 @@ function NLPModels.hprod!( end if nls.quadcon.nquad > 0 ind_quad = (nls.meta.nlin + 1):(nls.meta.nlin + nls.quadcon.nquad) - MOI.eval_hessian_lagrangian_product( + MOI.Nonlinear.add_hessian_lagrangian_product( nls.quadcon.block, hv, x, From 28ebe16232018f0a5f20d9a7ca8fcab5529beb3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Fri, 21 Aug 2026 07:19:36 +0200 Subject: [PATCH 4/5] Use MOI.Nonlinear.ModelWithQuad instead of QPBlockData (#238) * Use MOI.Nonlinear.ModelWithQuad instead of QPBlockData Store the parsed quadratic constraints in a MOI.Nonlinear.ModelWithQuad and evaluate them through the corresponding MOI.Nonlinear.EvaluatorWithQuad, instead of using the QPBlockData and its private helpers directly. The per-constraint methods (jth_hess_coord!, jth_hprod! and ghjvprod!) evaluate the Hessian of the Lagrangian of the block with a basis multiplier vector, which removes the per-constraint Hessian offsets. The products of the evaluator overwrite their output, so the Jacobian-transpose and Hessian products go through a new hv workspace before being accumulated. * MOI * Add the variables to the ModelWithQuad MOI.Nonlinear.ModelWithQuad now owns the variables of the model, so the parser adds them and the evaluator no longer takes ordered_variables. * MOI --- .github/workflows/Aqua.yml | 7 +++ .github/workflows/Documentation.yml | 7 +++ .github/workflows/ci.yml | 2 +- src/moi_nlp_model.jl | 91 +++++++++++------------------ src/moi_nls_model.jl | 54 ++++++++--------- src/utils.jl | 66 ++++++++++----------- 6 files changed, 102 insertions(+), 125 deletions(-) diff --git a/.github/workflows/Aqua.yml b/.github/workflows/Aqua.yml index 73e12cb6..0bb7b427 100644 --- a/.github/workflows/Aqua.yml +++ b/.github/workflows/Aqua.yml @@ -13,6 +13,13 @@ jobs: - uses: julia-actions/setup-julia@latest with: version: '1' + - name: MOI + shell: julia --project=@. {0} + run: | + using Pkg + Pkg.add([ + PackageSpec(name="MathOptInterface", rev="bl/model_with_quad"), + ]) - name: Aqua.jl run: | PKG_SRC_PATH=`pwd` diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml index be0b8658..2d1faff1 100644 --- a/.github/workflows/Documentation.yml +++ b/.github/workflows/Documentation.yml @@ -14,6 +14,13 @@ jobs: - uses: julia-actions/setup-julia@latest with: version: '1' + - name: MOI + shell: julia --project=@. {0} + run: | + using Pkg + Pkg.add([ + PackageSpec(name="MathOptInterface", rev="bl/model_with_quad"), + ]) - name: Install dependencies run: julia --project=docs -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' - name: Build and deploy diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 630355b4..46612c2f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,7 +59,7 @@ jobs: run: | using Pkg Pkg.add([ - PackageSpec(name="MathOptInterface", rev="bl/qp_block_data"), + PackageSpec(name="MathOptInterface", rev="bl/model_with_quad"), ]) - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 diff --git a/src/moi_nlp_model.jl b/src/moi_nlp_model.jl index 0b3b9297..6bfd41e5 100644 --- a/src/moi_nlp_model.jl +++ b/src/moi_nlp_model.jl @@ -132,7 +132,7 @@ function NLPModels.cons_nln!(nlp::MathOptNLPModel, x::AbstractVector, c::Abstrac offset = 0 if nlp.quadcon.nquad > 0 offset += nlp.quadcon.nquad - MOI.eval_constraint(nlp.quadcon.block, view(c, 1:(nlp.quadcon.nquad)), x) + MOI.eval_constraint(nlp.quadcon.evaluator, view(c, 1:(nlp.quadcon.nquad)), x) end if nlp.nlcon.nnln > 0 offset += nlp.nlcon.nnln @@ -164,7 +164,7 @@ function NLPModels.cons!(nlp::MathOptNLPModel, x::AbstractVector, c::AbstractVec if nlp.quadcon.nquad > 0 offset += nlp.quadcon.nquad ind_quad = (nlp.meta.nlin + 1):(nlp.meta.nlin + nlp.quadcon.nquad) - MOI.eval_constraint(nlp.quadcon.block, view(c, ind_quad), x) + MOI.eval_constraint(nlp.quadcon.evaluator, view(c, ind_quad), x) end if nlp.nlcon.nnln > 0 offset += nlp.nlcon.nnln @@ -290,7 +290,7 @@ function NLPModels.jac_nln_coord!(nlp::MathOptNLPModel, x::AbstractVector, vals: offset = 0 if nlp.quadcon.nquad > 0 ind_quad = 1:(nlp.quadcon.nnzj) - MOI.eval_constraint_jacobian(nlp.quadcon.block, view(vals, ind_quad), x) + MOI.eval_constraint_jacobian(nlp.quadcon.evaluator, view(vals, ind_quad), x) offset += nlp.quadcon.nnzj end if nlp.nlcon.nnln > 0 @@ -323,7 +323,7 @@ function NLPModels.jac_coord!(nlp::MathOptNLPModel, x::AbstractVector, vals::Abs end if nlp.quadcon.nquad > 0 ind_quad = (nlp.lincon.nnzj + 1):(nlp.lincon.nnzj + nlp.quadcon.nnzj) - MOI.eval_constraint_jacobian(nlp.quadcon.block, view(vals, ind_quad), x) + MOI.eval_constraint_jacobian(nlp.quadcon.evaluator, view(vals, ind_quad), x) offset += nlp.quadcon.nnzj end if nlp.nlcon.nnln > 0 @@ -373,8 +373,7 @@ function NLPModels.jprod_nln!( increment!(nlp, :neval_jprod_nln) if nlp.quadcon.nquad > 0 ind_quad = 1:(nlp.quadcon.nquad) - view(Jv, ind_quad) .= 0.0 - MOI.Nonlinear.add_constraint_jacobian_product(nlp.quadcon.block, view(Jv, ind_quad), x, v) + MOI.eval_constraint_jacobian_product(nlp.quadcon.evaluator, view(Jv, ind_quad), x, v) end if nlp.nlcon.nnln > 0 ind_nnln = (nlp.quadcon.nquad + 1):(nlp.quadcon.nquad + nlp.nlcon.nnln) @@ -382,7 +381,7 @@ function NLPModels.jprod_nln!( end if nlp.oracles.ncon > 0 for i = - (nlp.quadcon.nquad + nlp.nlcon.nnln + 1):(nlp.quadcon.nquad + nlp.nlcon.nnln + nlp.oracles.ncon) + (nlp.quadcon.nquad + nlp.nlcon.nnln + 1):(nlp.quadcon.nquad + nlp.nlcon.nnln + nlp.oracles.ncon) Jv[i] = 0 end @@ -427,8 +426,7 @@ function NLPModels.jprod!( end if nlp.quadcon.nquad > 0 ind_quad = (nlp.meta.nlin + 1):(nlp.meta.nlin + nlp.quadcon.nquad) - view(Jv, ind_quad) .= 0.0 - MOI.Nonlinear.add_constraint_jacobian_product(nlp.quadcon.block, view(Jv, ind_quad), x, v) + MOI.eval_constraint_jacobian_product(nlp.quadcon.evaluator, view(Jv, ind_quad), x, v) end if nlp.nlcon.nnln > 0 ind_nnln = @@ -489,13 +487,16 @@ function NLPModels.jtprod_nln!( end (nlp.nlcon.nnln == 0) && (Jtv .= 0.0) if nlp.quadcon.nquad > 0 - # Jtv += Jᵀ * v[1:nquad], where J is the Jacobian of the quadratic block - MOI.Nonlinear.add_constraint_jacobian_transpose_product( - nlp.quadcon.block, - Jtv, + # Jtv += Jᵀ * v[1:nquad], where J is the Jacobian of the quadratic block. + # The product of the evaluator overwrites its output, so it goes through + # the workspace `nlp.hv`. + MOI.eval_constraint_jacobian_transpose_product( + nlp.quadcon.evaluator, + nlp.hv, x, view(v, 1:(nlp.quadcon.nquad)), ) + Jtv .+= nlp.hv end if nlp.oracles.ncon > 0 row_offset = nlp.quadcon.nquad + nlp.nlcon.nnln @@ -544,12 +545,13 @@ function NLPModels.jtprod!( end if nlp.quadcon.nquad > 0 ind_quad = (nlp.meta.nlin + 1):(nlp.meta.nlin + nlp.quadcon.nquad) - MOI.Nonlinear.add_constraint_jacobian_transpose_product( - nlp.quadcon.block, - Jtv, + MOI.eval_constraint_jacobian_transpose_product( + nlp.quadcon.evaluator, + nlp.hv, x, view(v, ind_quad), ) + Jtv .+= nlp.hv end if nlp.oracles.ncon > 0 row_offset = nlp.meta.nlin + nlp.quadcon.nquad + nlp.nlcon.nnln @@ -637,13 +639,7 @@ function NLPModels.hess_coord!( if nlp.quadcon.nquad > 0 ind = (nlp.obj.nnzh + 1):(nlp.obj.nnzh + nlp.quadcon.nnzh) ind_quad = (nlp.meta.nlin + 1):(nlp.meta.nlin + nlp.quadcon.nquad) - MOI.eval_hessian_lagrangian( - nlp.quadcon.block, - view(vals, ind), - x, - 0.0, - view(y, ind_quad), - ) + MOI.eval_hessian_lagrangian(nlp.quadcon.evaluator, view(vals, ind), x, 0.0, view(y, ind_quad)) end # 4. Oracle Hessian blocks are appended at the very end @@ -715,15 +711,11 @@ function NLPModels.jth_hess_coord!( # Quadratic constraints if nlp.meta.nlin + 1 ≤ j ≤ nlp.meta.nlin + nlp.quadcon.nquad - i = j - nlp.meta.nlin - index = nlp.obj.nnzh + nlp.quadcon.hess_offset[i] - nnzh_i = nlp.quadcon.hess_offset[i + 1] - nlp.quadcon.hess_offset[i] - MOI.Nonlinear._eval_sparse_hessian( - view(vals, (index + 1):(index + nnzh_i)), - nlp.quadcon.block.constraints[i], - 1.0, - nlp.quadcon.block.parameters, - ) + μ = nlp.quadcon.y_scratch + μ[j - nlp.meta.nlin] = 1.0 + ind = (nlp.obj.nnzh + 1):(nlp.obj.nnzh + nlp.quadcon.nnzh) + MOI.eval_hessian_lagrangian(nlp.quadcon.evaluator, view(vals, ind), x, 0.0, μ) + μ[j - nlp.meta.nlin] = 0.0 end # Non-oracle nonlinear constraints @@ -797,14 +789,8 @@ function NLPModels.hprod!( if nlp.quadcon.nquad > 0 (nlp.obj.type == "LINEAR") && (nlp.nlcon.nnln == 0) && (hv .= 0.0) ind_quad = (nlp.meta.nlin + 1):(nlp.meta.nlin + nlp.quadcon.nquad) - MOI.Nonlinear.add_hessian_lagrangian_product( - nlp.quadcon.block, - hv, - x, - v, - 0.0, - view(y, ind_quad), - ) + MOI.eval_hessian_lagrangian_product(nlp.quadcon.evaluator, nlp.hv, x, v, 0.0, view(y, ind_quad)) + hv .+= nlp.hv end if nlp.oracles.ncon > 0 (nlp.obj.type == "LINEAR") && (nlp.meta.nnln == nlp.oracles.ncon) && (hv .= 0.0) @@ -874,14 +860,10 @@ function NLPModels.jth_hprod!( @rangecheck 1 nlp.meta.ncon j hv .= 0.0 if nlp.meta.nlin + 1 ≤ j ≤ nlp.meta.nlin + nlp.quadcon.nquad - MOI.Nonlinear._add_Hv_product( - nlp.quadcon.block.constraints[j - nlp.meta.nlin], - hv, - x, - v, - 1.0, - nlp.quadcon.block.parameters, - ) + μ = nlp.quadcon.y_scratch + μ[j - nlp.meta.nlin] = 1.0 + MOI.eval_hessian_lagrangian_product(nlp.quadcon.evaluator, hv, x, v, 0.0, μ) + μ[j - nlp.meta.nlin] = 0.0 elseif nlp.meta.nlin + nlp.quadcon.nquad + 1 ≤ j ≤ nlp.meta.nlin + nlp.quadcon.nquad + nlp.nlcon.nnln @@ -933,16 +915,11 @@ function NLPModels.ghjvprod!( error("The function ghjvprod! is not supported by this MathOptNLPModel.") increment!(nlp, :neval_hprod) ghv .= 0.0 + μ = nlp.quadcon.y_scratch for i = (nlp.meta.nlin + 1):(nlp.meta.nlin + nlp.quadcon.nquad) - fill!(nlp.hv, 0.0) - MOI.Nonlinear._add_Hv_product( - nlp.quadcon.block.constraints[i - nlp.meta.nlin], - nlp.hv, - x, - v, - 1.0, - nlp.quadcon.block.parameters, - ) + μ[i - nlp.meta.nlin] = 1.0 + MOI.eval_hessian_lagrangian_product(nlp.quadcon.evaluator, nlp.hv, x, v, 0.0, μ) + μ[i - nlp.meta.nlin] = 0.0 ghv[i] = dot(g, nlp.hv) end for i = (nlp.meta.nlin + nlp.quadcon.nquad + 1):(nlp.meta.ncon) diff --git a/src/moi_nls_model.jl b/src/moi_nls_model.jl index 25892eb1..7b3cef0e 100644 --- a/src/moi_nls_model.jl +++ b/src/moi_nls_model.jl @@ -13,6 +13,7 @@ mutable struct MathOptNLSModel <: AbstractNLSModel{Float64, Vector{Float64}} nlcon::NonLinearStructure oracles::Oracles λ::Vector{Float64} + hv::Vector{Float64} counters::NLSCounters end @@ -88,6 +89,7 @@ function MathOptNLSModel(cmodel::JuMP.Model, F; hessian::Bool = true, name::Stri nlcon, oracles, λ, + zeros(nvar), nls_counters, ) end @@ -270,7 +272,7 @@ function NLPModels.cons_nln!(nls::MathOptNLSModel, x::AbstractVector, c::Abstrac offset = 0 if nls.quadcon.nquad > 0 offset += nls.quadcon.nquad - MOI.eval_constraint(nls.quadcon.block, view(c, 1:(nls.quadcon.nquad)), x) + MOI.eval_constraint(nls.quadcon.evaluator, view(c, 1:(nls.quadcon.nquad)), x) end if nls.nlcon.nnln > 0 offset += nls.nlcon.nnln @@ -301,7 +303,7 @@ function NLPModels.cons!(nls::MathOptNLSModel, x::AbstractVector, c::AbstractVec if nls.quadcon.nquad > 0 offset += nls.quadcon.nquad ind_quad = (nls.meta.nlin + 1):(nls.meta.nlin + nls.quadcon.nquad) - MOI.eval_constraint(nls.quadcon.block, view(c, ind_quad), x) + MOI.eval_constraint(nls.quadcon.evaluator, view(c, ind_quad), x) end if nls.nlcon.nnln > 0 offset += nls.nlcon.nnln @@ -420,7 +422,7 @@ function NLPModels.jac_nln_coord!(nls::MathOptNLSModel, x::AbstractVector, vals: offset = 0 if nls.quadcon.nquad > 0 ind_quad = 1:(nls.quadcon.nnzj) - MOI.eval_constraint_jacobian(nls.quadcon.block, view(vals, ind_quad), x) + MOI.eval_constraint_jacobian(nls.quadcon.evaluator, view(vals, ind_quad), x) offset += nls.quadcon.nnzj end if nls.nlcon.nnln > 0 @@ -452,7 +454,7 @@ function NLPModels.jac_coord!(nls::MathOptNLSModel, x::AbstractVector, vals::Abs end if nls.quadcon.nquad > 0 ind_quad = (nls.lincon.nnzj + 1):(nls.lincon.nnzj + nls.quadcon.nnzj) - MOI.eval_constraint_jacobian(nls.quadcon.block, view(vals, ind_quad), x) + MOI.eval_constraint_jacobian(nls.quadcon.evaluator, view(vals, ind_quad), x) offset += nls.quadcon.nnzj end if nls.nlcon.nnln > 0 @@ -501,8 +503,7 @@ function NLPModels.jprod_nln!( increment!(nls, :neval_jprod_nln) if nls.quadcon.nquad > 0 ind_quad = 1:(nls.quadcon.nquad) - view(Jv, ind_quad) .= 0.0 - MOI.Nonlinear.add_constraint_jacobian_product(nls.quadcon.block, view(Jv, ind_quad), x, v) + MOI.eval_constraint_jacobian_product(nls.quadcon.evaluator, view(Jv, ind_quad), x, v) end if nls.nlcon.nnln > 0 ind_nnln = (nls.quadcon.nquad + 1):(nls.quadcon.nquad + nls.nlcon.nnln) @@ -510,7 +511,7 @@ function NLPModels.jprod_nln!( end if nls.oracles.ncon > 0 for i = - (nls.quadcon.nquad + nls.nlcon.nnln + 1):(nls.quadcon.nquad + nls.nlcon.nnln + nls.oracles.ncon) + (nls.quadcon.nquad + nls.nlcon.nnln + 1):(nls.quadcon.nquad + nls.nlcon.nnln + nls.oracles.ncon) Jv[i] = 0 end @@ -555,8 +556,7 @@ function NLPModels.jprod!( end if nls.quadcon.nquad > 0 ind_quad = (nls.meta.nlin + 1):(nls.meta.nlin + nls.quadcon.nquad) - view(Jv, ind_quad) .= 0.0 - MOI.Nonlinear.add_constraint_jacobian_product(nls.quadcon.block, view(Jv, ind_quad), x, v) + MOI.eval_constraint_jacobian_product(nls.quadcon.evaluator, view(Jv, ind_quad), x, v) end if nls.nlcon.nnln > 0 ind_nnln = @@ -617,13 +617,16 @@ function NLPModels.jtprod_nln!( end (nls.nlcon.nnln == 0) && (Jtv .= 0.0) if nls.quadcon.nquad > 0 - # Jtv += Jᵀ * v[1:nquad], where J is the Jacobian of the quadratic block - MOI.Nonlinear.add_constraint_jacobian_transpose_product( - nls.quadcon.block, - Jtv, + # Jtv += Jᵀ * v[1:nquad], where J is the Jacobian of the quadratic block. + # The product of the evaluator overwrites its output, so it goes through + # the workspace `nls.hv`. + MOI.eval_constraint_jacobian_transpose_product( + nls.quadcon.evaluator, + nls.hv, x, view(v, 1:(nls.quadcon.nquad)), ) + Jtv .+= nls.hv end if nls.oracles.ncon > 0 row_offset = nls.quadcon.nquad + nls.nlcon.nnln @@ -672,12 +675,13 @@ function NLPModels.jtprod!( end if nls.quadcon.nquad > 0 ind_quad = (nls.meta.nlin + 1):(nls.meta.nlin + nls.quadcon.nquad) - MOI.Nonlinear.add_constraint_jacobian_transpose_product( - nls.quadcon.block, - Jtv, + MOI.eval_constraint_jacobian_transpose_product( + nls.quadcon.evaluator, + nls.hv, x, view(v, ind_quad), ) + Jtv .+= nls.hv end if nls.oracles.ncon > 0 row_offset = nls.meta.nlin + nls.quadcon.nquad + nls.nlcon.nnln @@ -752,13 +756,7 @@ function NLPModels.hess_coord!( # irrelevant. ind = (nls.lls.nnzh + 1):(nls.lls.nnzh + nls.quadcon.nnzh) ind_quad = (nls.meta.nlin + 1):(nls.meta.nlin + nls.quadcon.nquad) - MOI.eval_hessian_lagrangian( - nls.quadcon.block, - view(vals, ind), - x, - 0.0, - view(y, ind_quad), - ) + MOI.eval_hessian_lagrangian(nls.quadcon.evaluator, view(vals, ind), x, 0.0, view(y, ind_quad)) end if (nls.nls_meta.nnln > 0) || (nls.nlcon.nnln > 0) λ = view( @@ -855,14 +853,8 @@ function NLPModels.hprod!( end if nls.quadcon.nquad > 0 ind_quad = (nls.meta.nlin + 1):(nls.meta.nlin + nls.quadcon.nquad) - MOI.Nonlinear.add_hessian_lagrangian_product( - nls.quadcon.block, - hv, - x, - v, - 0.0, - view(y, ind_quad), - ) + MOI.eval_hessian_lagrangian_product(nls.quadcon.evaluator, nls.hv, x, v, 0.0, view(y, ind_quad)) + hv .+= nls.hv end if nls.oracles.ncon > 0 offset_y = nls.meta.nlin + nls.quadcon.nquad + nls.nlcon.nnln diff --git a/src/utils.jl b/src/utils.jl index edf9447d..68626549 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -77,52 +77,46 @@ end """ QuadraticConstraints -The quadratic constraints, stored in a `MOI.Nonlinear.QPBlockData` whose rows -are the constraints in the order they were parsed. The Jacobian and Hessian -structures of the block are precomputed; `hess_offset[i]` is the number of -Hessian entries before those of constraint `i`, so that the entries of -constraint `i` are `hess_offset[i]+1:hess_offset[i+1]`. +The quadratic constraints, stored in a `MOI.Nonlinear.ModelWithQuad` whose +rows are the constraints in the order they were parsed, and evaluated through +the corresponding `MOI.Nonlinear.EvaluatorWithQuad`. The Jacobian and Hessian +structures of the block are precomputed. `y_scratch` holds basis multiplier +vectors for the per-constraint methods (`jth_hess_coord!`, `jth_hprod!` and +`ghjvprod!`). """ mutable struct QuadraticConstraints nquad::Int - block::MOI.Nonlinear.QPBlockData{Float64} + evaluator::MOI.Nonlinear.EvaluatorWithQuad jac_rows::Vector{Int} jac_cols::Vector{Int} nnzj::Int hess_rows::Vector{Int} hess_cols::Vector{Int} - hess_offset::Vector{Int} nnzh::Int + y_scratch::Vector{Float64} end -function QuadraticConstraints(block::MOI.Nonlinear.QPBlockData{Float64}) - nquad = length(block) - jac_structure = MOI.jacobian_structure(block) +function QuadraticConstraints(quad_model::MOI.Nonlinear.ModelWithQuad) + nquad = length(quad_model) + evaluator = MOI.Nonlinear.Evaluator(quad_model, MOI.Nonlinear.SparseReverseMode()) + MOI.initialize(evaluator, [:Grad, :Jac, :JacVec, :Hess, :HessVec]) + jac_structure = MOI.jacobian_structure(evaluator) jac_rows = [r for (r, _) in jac_structure] jac_cols = [c for (_, c) in jac_structure] - hess_structure = Tuple{Int, Int}[] - hess_offset = zeros(Int, nquad + 1) - for i = 1:nquad - MOI.Nonlinear._append_sparse_hessian_structure!( - block.constraints[i], - hess_structure, - block.parameters, - ) - hess_offset[i + 1] = length(hess_structure) - end + hess_structure = MOI.hessian_lagrangian_structure(evaluator) # NLPModels expects the lower triangle hess_rows = [max(r, c) for (r, c) in hess_structure] hess_cols = [min(r, c) for (r, c) in hess_structure] return QuadraticConstraints( nquad, - block, + evaluator, jac_rows, jac_cols, length(jac_structure), hess_rows, hess_cols, - hess_offset, length(hess_structure), + zeros(nquad), ) end @@ -354,12 +348,12 @@ end Parse a `ScalarQuadraticFunction` fun with its associated set. `qcons`, `quad_lcon`, `quad_ucon` are updated. """ -function parser_SQF(fun, set, block, index_map) +function parser_SQF(fun, set, quad_model, index_map) f = MOI.Utilities.map_indices(index_map, fun) # The constant is moved into the set, as MOI requires for # scalar-function-in-set constraints. g = SQF(f.quadratic_terms, f.affine_terms, 0.0) - MOI.add_constraint(block, g, MOI.Utilities.shift_constant(set, -f.constant)) + MOI.add_constraint(quad_model, g, MOI.Utilities.shift_constant(set, -f.constant)) return end @@ -367,15 +361,11 @@ _scalar_set(::MOI.Nonnegatives) = MOI.GreaterThan(0.0) _scalar_set(::MOI.Nonpositives) = MOI.LessThan(0.0) _scalar_set(::MOI.Zeros) = MOI.EqualTo(0.0) -function parser_VQF(fun, set, block, index_map) +function parser_VQF(fun, set, quad_model, index_map) f = MOI.Utilities.map_indices(index_map, fun) for fi in MOI.Utilities.scalarize(f) g = SQF(fi.quadratic_terms, fi.affine_terms, 0.0) - MOI.add_constraint( - block, - g, - MOI.Utilities.shift_constant(_scalar_set(set), -fi.constant), - ) + MOI.add_constraint(quad_model, g, MOI.Utilities.shift_constant(_scalar_set(set), -fi.constant)) end return end @@ -397,7 +387,10 @@ function parser_MOI(moimodel, index_map, nvar) # Variables associated to quadratic constraints nquad = 0 - quad_block = MOI.Nonlinear.QPBlockData{Float64}() + quad_model = MOI.Nonlinear.ModelWithQuad(MOI.Nonlinear.Model()) + for _ = 1:nvar + MOI.add_variable(quad_model) + end contypes = MOI.get(moimodel, MOI.ListOfConstraintTypesPresent()) for (F, S) in contypes @@ -430,11 +423,11 @@ function parser_MOI(moimodel, index_map, nvar) nlin += set.dimension end if typeof(fun) <: SQF - parser_SQF(fun, set, quad_block, index_map) + parser_SQF(fun, set, quad_model, index_map) nquad += 1 end if typeof(fun) <: VQF - parser_VQF(fun, set, quad_block, index_map) + parser_VQF(fun, set, quad_model, index_map) nquad += set.dimension end end @@ -442,9 +435,10 @@ function parser_MOI(moimodel, index_map, nvar) coo = COO(linrows, lincols, linvals) lin_nnzj = length(linvals) lincon = LinearConstraints(coo, lin_nnzj) - quadcon = QuadraticConstraints(quad_block) - quad_lcon = copy(quad_block.g_L) - quad_ucon = copy(quad_block.g_U) + quadcon = QuadraticConstraints(quad_model) + quad_bounds = MOI.NLPBlockData(quadcon.evaluator).constraint_bounds + quad_lcon = [b.lower for b in quad_bounds] + quad_ucon = [b.upper for b in quad_bounds] return nlin, lincon, lin_lcon, lin_ucon, quadcon, quad_lcon, quad_ucon end From 6bf5247b0c270d760b9e8390c6640bb7d4897125 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Fri, 21 Aug 2026 07:36:21 +0200 Subject: [PATCH 5/5] MOI --- .github/workflows/Aqua.yml | 2 +- .github/workflows/Documentation.yml | 2 +- .github/workflows/ci.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/Aqua.yml b/.github/workflows/Aqua.yml index 0bb7b427..1c73f8f5 100644 --- a/.github/workflows/Aqua.yml +++ b/.github/workflows/Aqua.yml @@ -18,7 +18,7 @@ jobs: run: | using Pkg Pkg.add([ - PackageSpec(name="MathOptInterface", rev="bl/model_with_quad"), + PackageSpec(name="MathOptInterface", rev="bl/qp_block_data"), ]) - name: Aqua.jl run: | diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml index 2d1faff1..7505fff9 100644 --- a/.github/workflows/Documentation.yml +++ b/.github/workflows/Documentation.yml @@ -19,7 +19,7 @@ jobs: run: | using Pkg Pkg.add([ - PackageSpec(name="MathOptInterface", rev="bl/model_with_quad"), + PackageSpec(name="MathOptInterface", rev="bl/qp_block_data"), ]) - name: Install dependencies run: julia --project=docs -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46612c2f..630355b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,7 +59,7 @@ jobs: run: | using Pkg Pkg.add([ - PackageSpec(name="MathOptInterface", rev="bl/model_with_quad"), + PackageSpec(name="MathOptInterface", rev="bl/qp_block_data"), ]) - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1