From 5f59ca4ed92514214088ce7722f9dea7eb4e5175 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 3 Feb 2026 00:30:16 -0600 Subject: [PATCH 01/10] Support dense Jacobians and Hessians --- README.md | 16 +++++----- docs/src/guidelines.md | 3 ++ docs/src/index.md | 14 +++++---- src/nlp/api.jl | 71 +++++++++++++++++++++++++----------------- src/nlp/meta.jl | 32 ++++++++++++------- 5 files changed, 83 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 3557944b..a6781c55 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ If Jacobian-vector products can be computed more efficiently than by evaluating The following method is defined if second-order derivatives are available: -* `hess(model, x, y)`: evaluate *∇²L(x,y)*, the Hessian of the Lagrangian at `x` and `y` +* `hess(model, x, y)`: evaluate *∇²L(x,y)*, the Hessian of the Lagrangian at `x` and `y` as a sparse matrix If Hessian-vector products can be computed more efficiently than by evaluating the Hessian explicitly, the following method may be implemented: @@ -111,16 +111,18 @@ Attribute | Type | Notes `jfree` | `Vector{Int}` | indices of "free" constraints (there shouldn't be any) `jinf` | `Vector{Int}` | indices of the visibly infeasible constraints `nnzo` | `Int` | number of nonzeros in the gradient -`nnzj` | `Int` | number of nonzeros in the sparse Jacobian -`lin_nnzj` | `Int` | number of nonzeros in the sparse linear constraints Jacobian -`nln_nnzj` | `Int` | number of nonzeros in the sparse nonlinear constraints Jacobian -`nnzh` | `Int` | number of nonzeros in the lower triangular part of the sparse Hessian of the Lagrangian +`nnzj` | `Int` | number of nonzeros in the Jacobian +`lin_nnzj` | `Int` | number of nonzeros in the linear constraints Jacobian +`nln_nnzj` | `Int` | number of nonzeros in the nonlinear constraints Jacobian +`nnzh` | `Int` | number of nonzeros in the lower triangular part of the Hessian of the Lagrangian `minimize` | `Bool` | true if `optimize == minimize` `islp` | `Bool` | true if the problem is a linear program `name` | `String` | problem name +`sparse_jacobian` | `Bool` | true if the Jacobian of the constraints is sparse +`sparse_hessian` | `Bool` | true if the Hessian of the Lagrangian is sparse `grad_available` | `Bool` | true if the gradient of the objective is available -`jac_available` | `Bool` | true if the sparse Jacobian of the constraints is available -`hess_available` | `Bool` | true if the sparse Hessian of the Lagrangian is available +`jac_available` | `Bool` | true if the Jacobian of the constraints is available +`hess_available` | `Bool` | true if the Hessian of the Lagrangian is available `jprod_available` | `Bool` | true if the Jacobian-vector product `J * v` is available `jtprod_available` | `Bool` | true if the transpose Jacobian-vector product `J' * v` is available `hprod_available` | `Bool` | true if the Hessian-vector product of the Lagrangian `H * v` is available diff --git a/docs/src/guidelines.md b/docs/src/guidelines.md index b5e42634..f11e107b 100644 --- a/docs/src/guidelines.md +++ b/docs/src/guidelines.md @@ -82,6 +82,9 @@ The indices of linear and nonlinear constraints are respectively available in `n If your model uses only linear (resp. nonlinear) constraints, then it suffices to implement the `*_lin` (resp. `*_nln`) functions. Alternatively, one could implement only the functions without the suffixes `_nln!` (e.g., only `cons!`), but this might run into errors with tools differentiating linear and nonlinear constraints. +If the Jacobian or the Hessian of the Lagrangian is dense, there is no need to implement the corresponding `*_structure!` methods. +This is specified at the initialization of [`NLPModelMeta`](@ref) through the keyword arguments `sparse_jacobian` and `sparse_hessian`. + ## [Availability of the API](@id availability-api) If only a subset of the functions listed above is implemented, you can indicate which ones are not available when creating the [`NLPModelMeta`](@ref), using the keyword arguments diff --git a/docs/src/index.md b/docs/src/index.md index c5c4f123..3a8758cc 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -106,16 +106,18 @@ Attribute | Type | Notes `jfree` | `Vector{Int}` | indices of "free" constraints (there shouldn't be any) `jinf` | `Vector{Int}` | indices of the visibly infeasible constraints `nnzo` | `Int` | number of nonzeros in the gradient -`nnzj` | `Int` | number of nonzeros in the sparse Jacobian -`lin_nnzj` | `Int` | number of nonzeros in the sparse linear constraints Jacobian -`nln_nnzj` | `Int` | number of nonzeros in the sparse nonlinear constraints Jacobian -`nnzh` | `Int` | number of nonzeros in the lower triangular part of the sparse Hessian of the Lagrangian +`nnzj` | `Int` | number of nonzeros in the Jacobian +`lin_nnzj` | `Int` | number of nonzeros in the linear constraints Jacobian +`nln_nnzj` | `Int` | number of nonzeros in the nonlinear constraints Jacobian +`nnzh` | `Int` | number of nonzeros in the lower triangular part of the Hessian of the Lagrangian `minimize` | `Bool` | true if `optimize == minimize` `islp` | `Bool` | true if the problem is a linear program `name` | `String` | problem name +`sparse_jacobian` | `Bool` | true if the Jacobian of the constraints is sparse +`sparse_hessian` | `Bool` | true if the Hessian of the Lagrangian is sparse `grad_available` | `Bool` | true if the gradient of the objective is available -`jac_available` | `Bool` | true if the sparse Jacobian of the constraints is available -`hess_available` | `Bool` | true if the sparse Hessian of the Lagrangian is available +`jac_available` | `Bool` | true if the Jacobian of the constraints is available +`hess_available` | `Bool` | true if the Hessian of the Lagrangian is available `jprod_available` | `Bool` | true if the Jacobian-vector product `J * v` is available `jtprod_available` | `Bool` | true if the transpose Jacobian-vector product `J' * v` is available `hprod_available` | `Bool` | true if the Hessian-vector product of the Lagrangian `H * v` is available diff --git a/src/nlp/api.jl b/src/nlp/api.jl index cd1efeb9..c4f8fa2f 100644 --- a/src/nlp/api.jl +++ b/src/nlp/api.jl @@ -178,7 +178,7 @@ end (rows,cols) = jac_structure(nlp) Return the structure of the constraints Jacobian in sparse coordinate format. -This function is only available if `nlp.meta.jac_available` is set to `true`. +This function is only available when both `nlp.meta.jac_available` and `nlp.meta.sparse_jacobian` are set to `true`. """ function jac_structure(nlp::AbstractNLPModel) rows = Vector{Int}(undef, nlp.meta.nnzj) @@ -190,7 +190,7 @@ end jac_structure!(nlp, rows, cols) Return the structure of the constraints Jacobian in sparse coordinate format in place. -This function is only available if `nlp.meta.jac_available` is set to `true`. +This function is only available when both `nlp.meta.jac_available` and `nlp.meta.sparse_jacobian` are set to `true`. """ function jac_structure!( nlp::AbstractNLPModel, @@ -227,7 +227,7 @@ end (rows,cols) = jac_lin_structure(nlp) Return the structure of the linear constraints Jacobian in sparse coordinate format. -This function is only available if `nlp.meta.jac_available` is set to `true`. +This function is only available when both `nlp.meta.jac_available` and `nlp.meta.sparse_jacobian` are set to `true`. """ function jac_lin_structure(nlp::AbstractNLPModel) rows = Vector{Int}(undef, nlp.meta.lin_nnzj) @@ -239,7 +239,7 @@ end jac_lin_structure!(nlp, rows, cols) Return the structure of the linear constraints Jacobian in sparse coordinate format in place. -This function is only available if `nlp.meta.jac_available` is set to `true`. +This function is only available when both `nlp.meta.jac_available` and `nlp.meta.sparse_jacobian` are set to `true`. """ function jac_lin_structure! end @@ -247,7 +247,7 @@ function jac_lin_structure! end (rows,cols) = jac_nln_structure(nlp) Return the structure of the nonlinear constraints Jacobian in sparse coordinate format. -This function is only available if `nlp.meta.jac_available` is set to `true`. +This function is only available when both `nlp.meta.jac_available` and `nlp.meta.sparse_jacobian` are set to `true`. """ function jac_nln_structure(nlp::AbstractNLPModel) rows = Vector{Int}(undef, nlp.meta.nln_nnzj) @@ -259,14 +259,16 @@ end jac_nln_structure!(nlp, rows, cols) Return the structure of the nonlinear constraints Jacobian in sparse coordinate format in place. -This function is only available if `nlp.meta.jac_available` is set to `true`. +This function is only available when both `nlp.meta.jac_available` and `nlp.meta.sparse_jacobian` are set to `true`. """ function jac_nln_structure! end """ vals = jac_coord!(nlp, x, vals) -Evaluate ``J(x)``, the constraints Jacobian at `x` in sparse coordinate format, rewriting `vals`. +Evaluate ``J(x)``, the constraints Jacobian at `x`, overwriting `vals`. +It uses a sparse coordinate format when `nlp.meta.sparse_jacobian` is set to `true`. +Otherwise, `vals` is expected to be a dense matrix. This function is only available if `nlp.meta.jac_available` is set to `true`. """ function jac_coord!(nlp::AbstractNLPModel, x::AbstractVector, vals::AbstractVector) @@ -296,7 +298,7 @@ end vals = jac_coord(nlp, x) Evaluate ``J(x)``, the constraints Jacobian at `x` in sparse coordinate format. -This function is only available if `nlp.meta.jac_available` is set to `true`. +This function is only available when both `nlp.meta.jac_available` and `nlp.meta.sparse_jacobian` are set to `true`. """ function jac_coord(nlp::AbstractNLPModel{T, S}, x::AbstractVector) where {T, S} @lencheck nlp.meta.nvar x @@ -308,7 +310,7 @@ end Jx = jac(nlp, x) Evaluate ``J(x)``, the constraints Jacobian at `x` as a sparse matrix. -This function is only available if `nlp.meta.jac_available` is set to `true`. +This function is only available when both `nlp.meta.jac_available` and `nlp.meta.sparse_jacobian` are set to `true`. """ function jac(nlp::AbstractNLPModel, x::AbstractVector) @lencheck nlp.meta.nvar x @@ -320,7 +322,9 @@ end """ vals = jac_lin_coord!(nlp, x, vals) -Evaluate ``J(x)``, the linear constraints Jacobian at `x` in sparse coordinate format, overwriting `vals`. +Evaluate ``J(x)``, the linear constraints Jacobian at `x`, overwriting `vals`. +It uses a sparse coordinate format when `nlp.meta.sparse_jacobian` is set to `true`. +Otherwise, `vals` is expected to be a dense matrix. This function is only available if `nlp.meta.jac_available` is set to `true`. """ function jac_lin_coord! end @@ -329,7 +333,7 @@ function jac_lin_coord! end vals = jac_lin_coord(nlp, x) Evaluate ``J(x)``, the linear constraints Jacobian at `x` in sparse coordinate format. -This function is only available if `nlp.meta.jac_available` is set to `true`. +This function is only available when both `nlp.meta.jac_available` and `nlp.meta.sparse_jacobian` are set to `true`. """ function jac_lin_coord(nlp::AbstractNLPModel{T, S}, x::AbstractVector) where {T, S} @lencheck nlp.meta.nvar x @@ -341,7 +345,7 @@ end Jx = jac_lin(nlp, x) Evaluate ``J(x)``, the linear constraints Jacobian at `x` as a sparse matrix. -This function is only available if `nlp.meta.jac_available` is set to `true`. +This function is only available when both `nlp.meta.jac_available` and `nlp.meta.sparse_jacobian` are set to `true`. """ function jac_lin(nlp::AbstractNLPModel, x::AbstractVector) @lencheck nlp.meta.nvar x @@ -353,7 +357,9 @@ end """ vals = jac_nln_coord!(nlp, x, vals) -Evaluate ``J(x)``, the nonlinear constraints Jacobian at `x` in sparse coordinate format, overwriting `vals`. +Evaluate ``J(x)``, the nonlinear constraints Jacobian at `x`, overwriting `vals`. +It uses a sparse coordinate format when `nlp.meta.sparse_jacobian` is set to `true`. +Otherwise, `vals` is expected to be a dense matrix. This function is only available if `nlp.meta.jac_available` is set to `true`. """ function jac_nln_coord! end @@ -362,7 +368,7 @@ function jac_nln_coord! end vals = jac_nln_coord(nlp, x) Evaluate ``J(x)``, the nonlinear constraints Jacobian at `x` in sparse coordinate format. -This function is only available if `nlp.meta.jac_available` is set to `true`. +This function is only available when both `nlp.meta.jac_available` and `nlp.meta.sparse_jacobian` are set to `true`. """ function jac_nln_coord(nlp::AbstractNLPModel{T, S}, x::AbstractVector) where {T, S} @lencheck nlp.meta.nvar x @@ -374,7 +380,7 @@ end Jx = jac_nln(nlp, x) Evaluate ``J(x)``, the nonlinear constraints Jacobian at `x` as a sparse matrix. -This function is only available if `nlp.meta.jac_available` is set to `true`. +TThis function is only available when both `nlp.meta.jac_available` and `nlp.meta.sparse_jacobian` are set to `true`. """ function jac_nln(nlp::AbstractNLPModel, x::AbstractVector) @lencheck nlp.meta.nvar x @@ -953,6 +959,7 @@ end Evaluate the Hessian of j-th constraint at `x` in sparse coordinate format. Only the lower triangle is returned. +This function is only available when `nlp.meta.sparse_hessian` is set to `true`. """ function jth_hess_coord(nlp::AbstractNLPModel{T, S}, x::AbstractVector, j::Integer) where {T, S} @lencheck nlp.meta.nvar x @@ -964,8 +971,11 @@ end """ vals = jth_hess_coord!(nlp, x, j, vals) -Evaluate the Hessian of j-th constraint at `x` in sparse coordinate format, with `vals` of -length `nlp.meta.nnzh`, in place. Only the lower triangle is returned. +Evaluate the Hessian of j-th constraint at `x`, overwriting `vals`. +Only the lower triangle is returned. +It uses a sparse coordinate format, with `vals` of length `nlp.meta.nnzh`, +when `nlp.meta.sparse_hessian` is set to `true`. +Otherwise, `vals` is expected to be a dense matrix. """ function jth_hess_coord! end @@ -975,6 +985,7 @@ function jth_hess_coord! end Evaluate the Hessian of j-th constraint at `x` as a sparse matrix with the same sparsity pattern as the Lagrangian Hessian. A `Symmetric` object wrapping the lower triangle is returned. +This function is only available when `nlp.meta.sparse_hessian` is set to `true`. """ function jth_hess(nlp::AbstractNLPModel, x::AbstractVector, j::Integer) @lencheck nlp.meta.nvar x @@ -1036,7 +1047,7 @@ function ghjvprod! end (rows,cols) = hess_structure(nlp) Return the structure of the Lagrangian Hessian in sparse coordinate format. -This function is only available if `nlp.meta.hess_available` is set to `true`. +This function is only available when both `nlp.meta.hess_available` and `nlp.meta.sparse_hessian` are set to `true`. """ function hess_structure(nlp::AbstractNLPModel) rows = Vector{Int}(undef, nlp.meta.nnzh) @@ -1048,17 +1059,18 @@ end hess_structure!(nlp, rows, cols) Return the structure of the Lagrangian Hessian in sparse coordinate format in place. -This function is only available if `nlp.meta.hess_available` is set to `true`. +This function is only available when both `nlp.meta.hess_available` and `nlp.meta.sparse_hessian` are set to `true`. """ function hess_structure! end """ vals = hess_coord!(nlp, x, vals; obj_weight=1.0) -Evaluate the objective Hessian at `x` in sparse coordinate format, -with objective function scaled by `obj_weight`, i.e., -$(OBJECTIVE_HESSIAN), overwriting `vals`. +Evaluate the objective Hessian at `x` with objective function scaled by +`obj_weight`, i.e., $(OBJECTIVE_HESSIAN), overwriting `vals`. Only the lower triangle is returned. +It uses a sparse coordinate format when `nlp.meta.sparse_hessian` is set to `true`. +Otherwise, `vals` is expected to be a dense matrix. This function is only available if `nlp.meta.hess_available` is set to `true`. """ function hess_coord!( @@ -1076,10 +1088,11 @@ end """ vals = hess_coord!(nlp, x, y, vals; obj_weight=1.0) -Evaluate the Lagrangian Hessian at `(x,y)` in sparse coordinate format, -with objective function scaled by `obj_weight`, i.e., -$(LAGRANGIAN_HESSIAN), overwriting `vals`. +Evaluate the Lagrangian Hessian at `(x,y)` with objective function scaled by +`obj_weight`, i.e., $(LAGRANGIAN_HESSIAN), overwriting `vals`. Only the lower triangle is returned. +It uses a sparse coordinate format when `nlp.meta.sparse_hessian` is set to `true`. +Otherwise, `vals` is expected to be a dense matrix. This function is only available if `nlp.meta.hess_available` is set to `true`. """ function hess_coord! end @@ -1091,7 +1104,7 @@ Evaluate the objective Hessian at `x` in sparse coordinate format, with objective function scaled by `obj_weight`, i.e., $(OBJECTIVE_HESSIAN). Only the lower triangle is returned. -This function is only available if `nlp.meta.hess_available` is set to `true`. +This function is only available when both `nlp.meta.hess_available` and `nlp.meta.sparse_hessian` are set to `true`. """ function hess_coord( nlp::AbstractNLPModel{T, S}, @@ -1110,7 +1123,7 @@ Evaluate the Lagrangian Hessian at `(x,y)` in sparse coordinate format, with objective function scaled by `obj_weight`, i.e., $(LAGRANGIAN_HESSIAN). Only the lower triangle is returned. -This function is only available if `nlp.meta.hess_available` is set to `true`. +This function is only available when both `nlp.meta.hess_available` and `nlp.meta.sparse_hessian` are set to `true`. """ function hess_coord( nlp::AbstractNLPModel{T, S}, @@ -1131,7 +1144,7 @@ Evaluate the objective Hessian at `x` as a sparse matrix, with objective function scaled by `obj_weight`, i.e., $(OBJECTIVE_HESSIAN). A `Symmetric` object wrapping the lower triangle is returned. -This function is only available if `nlp.meta.hess_available` is set to `true`. +This function is only available when both `nlp.meta.hess_available` and `nlp.meta.sparse_hessian` are set to `true`. """ function hess( nlp::AbstractNLPModel{T, S}, @@ -1151,7 +1164,7 @@ Evaluate the Lagrangian Hessian at `(x,y)` as a sparse matrix, with objective function scaled by `obj_weight`, i.e., $(LAGRANGIAN_HESSIAN). A `Symmetric` object wrapping the lower triangle is returned. -This function is only available if `nlp.meta.hess_available` is set to `true`. +This function is only available when both `nlp.meta.hess_available` and `nlp.meta.sparse_hessian` are set to `true`. """ function hess( nlp::AbstractNLPModel{T, S}, diff --git a/src/nlp/meta.jl b/src/nlp/meta.jl index 884d0922..21c5d7fa 100644 --- a/src/nlp/meta.jl +++ b/src/nlp/meta.jl @@ -43,17 +43,19 @@ The following keyword arguments are accepted: - `lcon`: vector of constraint lower bounds - `ucon`: vector of constraint upper bounds - `nnzo`: number of nonzeros in the gradient -- `nnzj`: number of elements needed to store the nonzeros in the sparse Jacobian -- `lin_nnzj`: number of elements needed to store the nonzeros in the sparse Jacobian of linear constraints -- `nln_nnzj`: number of elements needed to store the nonzeros in the sparse Jacobian of nonlinear constraints -- `nnzh`: number of elements needed to store the nonzeros in the sparse Hessian +- `nnzj`: number of elements needed to store the nonzeros in the Jacobian +- `lin_nnzj`: number of elements needed to store the nonzeros in the Jacobian of linear constraints +- `nln_nnzj`: number of elements needed to store the nonzeros in the Jacobian of nonlinear constraints +- `nnzh`: number of elements needed to store the nonzeros in the Hessian of the Lagragian - `lin`: indices of linear constraints - `minimize`: true if optimize == minimize - `islp`: true if the problem is a linear program - `name`: problem name +- `sparse_jacobian`: indicates whether the Jacobian of the constraints is sparse +- `sparse_hessian`: indicates whether the Hessian of the Lagrangian is sparse - `grad_available`: indicates whether the gradient of the objective is available -- `jac_available`: indicates whether the sparse Jacobian of the constraints is available -- `hess_available`: indicates whether the sparse Hessian of the Lagrangian is available +- `jac_available`: indicates whether the Jacobian of the constraints is available +- `hess_available`: indicates whether the Hessian of the Lagrangian is available - `jprod_available`: indicates whether the Jacobian-vector product `J * v` is available - `jtprod_available`: indicates whether the transpose Jacobian-vector product `J' * v` is available - `hprod_available`: indicates whether the Hessian-vector product of the Lagrangian `H * v` is available @@ -121,6 +123,9 @@ struct NLPModelMeta{T, S} <: AbstractNLPModelMeta{T, S} islp::Bool name::String + sparse_jacobian::Bool + sparse_hessian::Bool + grad_available::Bool jac_available::Bool hess_available::Bool @@ -145,11 +150,13 @@ function NLPModelMeta{T, S}( nnzj = nvar * ncon, lin_nnzj = 0, nln_nnzj = nnzj - lin_nnzj, - nnzh = nvar * (nvar + 1) / 2, + nnzh = nvar * (nvar + 1) ÷ 2, lin = Int[], minimize::Bool = true, islp::Bool = false, name = "Generic", + sparse_jacobian::Bool = true, + sparse_hessian::Bool = true, grad_available::Bool = true, jac_available::Bool = (ncon > 0), hess_available::Bool = true, @@ -157,7 +164,7 @@ function NLPModelMeta{T, S}( jtprod_available::Bool = (ncon > 0), hprod_available::Bool = true, ) where {T, S} - if (nvar < 1) || (ncon < 0) + if (nvar < 1) || (ncon < 0) || (nnzj < 0) || (nnzh < 0) error("Nonsensical dimensions") end @@ -189,9 +196,6 @@ function NLPModelMeta{T, S}( jinf = Int[] end - nnzj = max(0, nnzj) - nnzh = max(0, nnzh) - nln = setdiff(1:ncon, lin) nlin = length(lin) nnln = length(nln) @@ -232,6 +236,8 @@ function NLPModelMeta{T, S}( minimize, islp, name, + sparse_jacobian, + sparse_hessian, grad_available, jac_available, hess_available, @@ -266,6 +272,8 @@ function NLPModelMeta( minimize::Bool = meta.minimize, islp::Bool = meta.islp, name = meta.name, + sparse_jacobian::Bool = true, + sparse_hessian::Bool = true, grad_available::Bool = meta.grad_available, jac_available::Bool = meta.jac_available, hess_available::Bool = meta.hess_available, @@ -294,6 +302,8 @@ function NLPModelMeta( minimize = minimize, islp = islp, name = name, + sparse_jacobian = sparse_jacobian, + sparse_hessian = sparse_hessian, grad_available = grad_available, jac_available = jac_available, hess_available = hess_available, From 3a3b2b78eaaeac6a04267f2b732ba18e4df46619 Mon Sep 17 00:00:00 2001 From: Alexis Montoison <35051714+amontoison@users.noreply.github.com> Date: Tue, 3 Feb 2026 00:33:21 -0600 Subject: [PATCH 02/10] Apply suggestion from @amontoison --- src/nlp/meta.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nlp/meta.jl b/src/nlp/meta.jl index 21c5d7fa..491c49df 100644 --- a/src/nlp/meta.jl +++ b/src/nlp/meta.jl @@ -46,7 +46,7 @@ The following keyword arguments are accepted: - `nnzj`: number of elements needed to store the nonzeros in the Jacobian - `lin_nnzj`: number of elements needed to store the nonzeros in the Jacobian of linear constraints - `nln_nnzj`: number of elements needed to store the nonzeros in the Jacobian of nonlinear constraints -- `nnzh`: number of elements needed to store the nonzeros in the Hessian of the Lagragian +- `nnzh`: number of elements needed to store the nonzeros in the Hessian of the Lagrangian - `lin`: indices of linear constraints - `minimize`: true if optimize == minimize - `islp`: true if the problem is a linear program From 36abb564a58b69083ba9ec93d2726d4221b8c30a Mon Sep 17 00:00:00 2001 From: Alexis Montoison <35051714+amontoison@users.noreply.github.com> Date: Tue, 3 Feb 2026 01:29:48 -0600 Subject: [PATCH 03/10] Apply suggestion from @amontoison --- src/nlp/api.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nlp/api.jl b/src/nlp/api.jl index c4f8fa2f..b402adba 100644 --- a/src/nlp/api.jl +++ b/src/nlp/api.jl @@ -380,7 +380,7 @@ end Jx = jac_nln(nlp, x) Evaluate ``J(x)``, the nonlinear constraints Jacobian at `x` as a sparse matrix. -TThis function is only available when both `nlp.meta.jac_available` and `nlp.meta.sparse_jacobian` are set to `true`. +This function is only available when both `nlp.meta.jac_available` and `nlp.meta.sparse_jacobian` are set to `true`. """ function jac_nln(nlp::AbstractNLPModel, x::AbstractVector) @lencheck nlp.meta.nvar x From a9ad33bbe1cde4c45781b89991577a1bd8185bb3 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 3 Feb 2026 18:32:57 -0600 Subject: [PATCH 04/10] =?UTF-8?q?Do=20another=20pass=20after=20the=20comme?= =?UTF-8?q?nts=20of=20Fran=C3=A7ois?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/src/api.md | 7 ++++--- docs/src/guidelines.md | 20 +++++++++++-------- src/nlp/api.jl | 44 ++++++++++++++++++++++++++++-------------- src/nlp/meta.jl | 2 +- 4 files changed, 47 insertions(+), 26 deletions(-) diff --git a/docs/src/api.md b/docs/src/api.md index c7424fb8..d310341b 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -40,13 +40,14 @@ NLPModels instances. |-------------------|-------------------------------------------| | ``f(x)`` | [`obj`](@ref), [`objgrad`](@ref), [`objgrad!`](@ref), [`objcons`](@ref), [`objcons!`](@ref) | | ``\nabla f(x)`` | [`grad`](@ref), [`grad!`](@ref), [`objgrad`](@ref), [`objgrad!`](@ref) | -| ``\nabla^2 f(x)`` | [`hess`](@ref), [`hess_op`](@ref), [`hess_op!`](@ref), [`hess_coord`](@ref), [`hess_coord`](@ref), [`hess_structure`](@ref), [`hess_structure!`](@ref), [`hprod`](@ref), [`hprod!`](@ref) | +| ``\nabla^2 f(x)`` | [`hess`](@ref), [`hess_op`](@ref), [`hess_op!`](@ref), [`hess_coord`](@ref), [`hess_coord!`](@ref), [`hess_dense!`](@ref), [`hess_structure`](@ref), [`hess_structure!`](@ref), [`hprod`](@ref), [`hprod!`](@ref) | | ``c(x)`` | [`cons_lin`](@ref), [`cons_lin!`](@ref), [`cons_nln`](@ref), [`cons_nln!`](@ref), [`cons`](@ref), [`cons!`](@ref), [`objcons`](@ref), [`objcons!`](@ref) | -| ``J(x)`` | [`jac_lin`](@ref), [`jac_nln`](@ref), [`jac`](@ref), [`jac_lin_op`](@ref), [`jac_lin_op!`](@ref), [`jac_nln_op`](@ref), [`jac_nln_op!`](@ref),[`jac_op`](@ref), [`jac_op!`](@ref), [`jac_lin_coord`](@ref), [`jac_lin_coord!`](@ref), [`jac_nln_coord`](@ref), [`jac_nln_coord!`](@ref), [`jac_coord`](@ref), [`jac_coord!`](@ref), [`jac_lin_structure`](@ref), [`jac_lin_structure!`](@ref), [`jac_nln_structure`](@ref), [`jac_nln_structure!`](@ref), [`jac_structure`](@ref), [`jprod_lin`](@ref), [`jprod_lin!`](@ref), [`jprod_nln`](@ref), [`jprod_nln!`](@ref), [`jprod`](@ref), [`jprod!`](@ref), [`jtprod_lin`](@ref), [`jtprod_lin!`](@ref), [`jtprod_nln`](@ref), [`jtprod_nln!`](@ref), [`jtprod`](@ref), [`jtprod!`](@ref) | -| ``\nabla^2 L(x,y)`` | [`hess`](@ref), [`hess_op`](@ref), [`hess_coord`](@ref), [`hess_coord!`](@ref), [`hess_structure`](@ref), [`hess_structure!`](@ref), [`hprod`](@ref), [`hprod!`](@ref), [`jth_hprod`](@ref), [`jth_hprod!`](@ref), [`jth_hess`](@ref), [`jth_hess_coord`](@ref), [`jth_hess_coord!`](@ref), [`ghjvprod`](@ref), [`ghjvprod!`](@ref) | +| ``J(x)`` | [`jac_lin`](@ref), [`jac_nln`](@ref), [`jac`](@ref), [`jac_lin_op`](@ref), [`jac_lin_op!`](@ref), [`jac_nln_op`](@ref), [`jac_nln_op!`](@ref),[`jac_op`](@ref), [`jac_op!`](@ref), [`jac_lin_coord`](@ref), [`jac_lin_coord!`](@ref), [`jac_nln_coord`](@ref), [`jac_nln_coord!`](@ref), [`jac_coord`](@ref), [`jac_coord!`](@ref), [`jac_dense!`](@ref), [`jac_lin_structure`](@ref), [`jac_lin_structure!`](@ref), [`jac_nln_structure`](@ref), [`jac_nln_structure!`](@ref), [`jac_structure`](@ref), [`jprod_lin`](@ref), [`jprod_lin!`](@ref), [`jprod_nln`](@ref), [`jprod_nln!`](@ref), [`jprod`](@ref), [`jprod!`](@ref), [`jtprod_lin`](@ref), [`jtprod_lin!`](@ref), [`jtprod_nln`](@ref), [`jtprod_nln!`](@ref), [`jtprod`](@ref), [`jtprod!`](@ref) | +| ``\nabla^2 L(x,y)`` | [`hess`](@ref), [`hess_op`](@ref), [`hess_coord`](@ref), [`hess_coord!`](@ref), [`hess_dense!`](@ref), [`hess_structure`](@ref), [`hess_structure!`](@ref), [`hprod`](@ref), [`hprod!`](@ref), [`jth_hprod`](@ref), [`jth_hprod!`](@ref), [`jth_hess`](@ref), [`jth_hess_coord`](@ref), [`jth_hess_coord!`](@ref), [`ghjvprod`](@ref), [`ghjvprod!`](@ref) | If only a subset of the functions listed above is implemented, you can indicate which ones are not available when creating the [`NLPModelMeta`](@ref), using the keyword arguments `grad_available`, `jac_available`, `hess_available`, `jprod_available`, `jtprod_available`, and `hprod_available`. +You can also specify whether the Jacobian of the constraints and the Hessian of the objective or Lagrangian are sparse using the keyword arguments `sparse_jacobian` and `sparse_hessian`. ## [API for NLSModels](@id nls-api) diff --git a/docs/src/guidelines.md b/docs/src/guidelines.md index f11e107b..8b301fcb 100644 --- a/docs/src/guidelines.md +++ b/docs/src/guidelines.md @@ -60,21 +60,24 @@ The following functions should be defined: - Objective (unconstrained models only need to worry about these) - `obj(nlp, x)` - `grad!(nlp, x, g)` - - `hess_structure!(nlp, hrows, hcols)` - - `hess_coord!(nlp, x, hvals; obj_weight=1)` + - `hess_structure!(nlp, hrows, hcols)` (sparse Hessian) + - `hess_coord!(nlp, x, hvals; obj_weight=1)` (sparse Hessian) + - `hess_dense!(nlp, x, Hx; obj_weight=1)` (dense Hessian) - `hprod!(nlp, x, v, Hv; obj_weight=1)` (actually defaults to calling the constrained case) - Constraints (constrained models need to worry about these and the ones above) - `cons_lin!(nlp, x, c)` - `cons_nln!(nlp, x, c)` - - `jac_lin_structure!(nlp, jrows, jcols)` - - `jac_nln_structure!(nlp, jrows, jcols)` - - `jac_lin_coord!(nlp, x, jvals)` - - `jac_nln_coord!(nlp, x, jvals)` + - `jac_lin_structure!(nlp, jrows, jcols)` (sparse Jacobian) + - `jac_nln_structure!(nlp, jrows, jcols)` (sparse Jacobian) + - `jac_lin_coord!(nlp, x, jvals)` (sparse Jacobian) + - `jac_nln_coord!(nlp, x, jvals)` (sparse Jacobian) + - `jac_dense!(nlp, x, Jx)` (dense Jacobian) - `jprod_lin!(nlp, x, v, Jv)` - `jprod_nln!(nlp, x, v, Jv)` - `jtprod_lin!(nlp, x, v, Jtv)` - `jtprod_nln!(nlp, x, v, Jtv)` - - `hess_coord!(nlp, x, y, hvals; obj_weight=1)` + - `hess_coord!(nlp, x, y, hvals; obj_weight=1)` (sparse Hessian) + - `hess_dense!(nlp, x, y, Hx; obj_weight=1)` (dense Hessian) - `hprod!(nlp, x, y, v, Hv; obj_weight=1)` The linear constraints are specified at the initialization of the `NLPModelMeta` using the keyword arguement `lin`. @@ -82,7 +85,8 @@ The indices of linear and nonlinear constraints are respectively available in `n If your model uses only linear (resp. nonlinear) constraints, then it suffices to implement the `*_lin` (resp. `*_nln`) functions. Alternatively, one could implement only the functions without the suffixes `_nln!` (e.g., only `cons!`), but this might run into errors with tools differentiating linear and nonlinear constraints. -If the Jacobian or the Hessian of the Lagrangian is dense, there is no need to implement the corresponding `*_structure!` methods. +If the Jacobian or the Hessian of the Lagrangian is dense, there is no need to implement the corresponding `*_structure!` and `*_coord!` methods. +Only the corresponding `*_dense!` methods need to be implemented. This is specified at the initialization of [`NLPModelMeta`](@ref) through the keyword arguments `sparse_jacobian` and `sparse_hessian`. ## [Availability of the API](@id availability-api) diff --git a/src/nlp/api.jl b/src/nlp/api.jl index b402adba..37b75caf 100644 --- a/src/nlp/api.jl +++ b/src/nlp/api.jl @@ -12,6 +12,7 @@ export jth_hprod, jth_hprod!, ghjvprod, ghjvprod! export hess_structure!, hess_structure, hess_coord!, hess_coord export hess, hprod, hprod!, hess_op, hess_op! export varscale, lagscale, conscale +export jac_dense!, hess_dense! """ f = obj(nlp, x) @@ -266,10 +267,8 @@ function jac_nln_structure! end """ vals = jac_coord!(nlp, x, vals) -Evaluate ``J(x)``, the constraints Jacobian at `x`, overwriting `vals`. -It uses a sparse coordinate format when `nlp.meta.sparse_jacobian` is set to `true`. -Otherwise, `vals` is expected to be a dense matrix. -This function is only available if `nlp.meta.jac_available` is set to `true`. +Evaluate ``J(x)``, the constraints Jacobian at `x` in sparse coordinate format, overwriting `vals`. +This function is only available when both `nlp.meta.jac_available` and `nlp.meta.sparse_jacobian` are set to `true`. """ function jac_coord!(nlp::AbstractNLPModel, x::AbstractVector, vals::AbstractVector) @lencheck nlp.meta.nvar x @@ -306,6 +305,14 @@ function jac_coord(nlp::AbstractNLPModel{T, S}, x::AbstractVector) where {T, S} return jac_coord!(nlp, x, vals) end +""" + Jx = jac_dense!(nlp, x, Jx) + +Evaluate ``J(x)``, the constraints Jacobian at `x` in dense format, overwriting `Jx`. +This function is only available when `nlp.meta.jac_available` is set to `true` and `nlp.meta.sparse_jacobian` is set to `false`. +""" +function jac_dense! end + """ Jx = jac(nlp, x) @@ -1066,12 +1073,11 @@ function hess_structure! end """ vals = hess_coord!(nlp, x, vals; obj_weight=1.0) -Evaluate the objective Hessian at `x` with objective function scaled by -`obj_weight`, i.e., $(OBJECTIVE_HESSIAN), overwriting `vals`. +Evaluate the objective Hessian at `x` in sparse coordinate format, +with objective function scaled by `obj_weight`, i.e., +$(OBJECTIVE_HESSIAN), overwriting `vals`. Only the lower triangle is returned. -It uses a sparse coordinate format when `nlp.meta.sparse_hessian` is set to `true`. -Otherwise, `vals` is expected to be a dense matrix. -This function is only available if `nlp.meta.hess_available` is set to `true`. +This function is only available when both `nlp.meta.hess_available` and `nlp.meta.sparse_hessian` are set to `true`. """ function hess_coord!( nlp::AbstractNLPModel{T, S}, @@ -1088,12 +1094,11 @@ end """ vals = hess_coord!(nlp, x, y, vals; obj_weight=1.0) -Evaluate the Lagrangian Hessian at `(x,y)` with objective function scaled by -`obj_weight`, i.e., $(LAGRANGIAN_HESSIAN), overwriting `vals`. +Evaluate the Lagrangian Hessian at `(x,y)` in sparse coordinate format, +with objective function scaled by `obj_weight`, i.e., +$(LAGRANGIAN_HESSIAN), overwriting `vals`. Only the lower triangle is returned. -It uses a sparse coordinate format when `nlp.meta.sparse_hessian` is set to `true`. -Otherwise, `vals` is expected to be a dense matrix. -This function is only available if `nlp.meta.hess_available` is set to `true`. +This function is only available when both `nlp.meta.hess_available` and `nlp.meta.sparse_hessian` are set to `true`. """ function hess_coord! end @@ -1157,6 +1162,17 @@ function hess( Symmetric(sparse(rows, cols, vals, nlp.meta.nvar, nlp.meta.nvar), :L) end +""" + Hx = hess_dense!(nlp, x, Hx; obj_weight=1.0) + Hx = hess_dense!(nlp, x, y, Hx; obj_weight=1.0) + +The first method evaluates ``H(x)``, the Hessian of the objective at `x` in dense format, overwriting `Hx`. +The second method evaluates ``H(x,y)``, the Hessian of the Lagrangian at `(x,y)` in dense format, overwriting `Hx`. +Only the lower triangular part of `Hx` needs to be filled. +This function is only available when `nlp.meta.hess_available` is set to `true` and `nlp.meta.sparse_hessian` is set to `false`. +""" +function hess_dense! end + """ Hx = hess(nlp, x, y; obj_weight=1.0) diff --git a/src/nlp/meta.jl b/src/nlp/meta.jl index 491c49df..509781d4 100644 --- a/src/nlp/meta.jl +++ b/src/nlp/meta.jl @@ -150,7 +150,7 @@ function NLPModelMeta{T, S}( nnzj = nvar * ncon, lin_nnzj = 0, nln_nnzj = nnzj - lin_nnzj, - nnzh = nvar * (nvar + 1) ÷ 2, + nnzh = sparse_hessian ? nvar * (nvar + 1) ÷ 2 : nvar * nvar, lin = Int[], minimize::Bool = true, islp::Bool = false, From 6e450651ce312780b3c286be88e9defcc7dbd4a6 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 3 Feb 2026 18:35:12 -0600 Subject: [PATCH 05/10] Keep nnzh to be the number of nonzeros in the lower triangular part even in the dense case --- src/nlp/meta.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nlp/meta.jl b/src/nlp/meta.jl index 509781d4..491c49df 100644 --- a/src/nlp/meta.jl +++ b/src/nlp/meta.jl @@ -150,7 +150,7 @@ function NLPModelMeta{T, S}( nnzj = nvar * ncon, lin_nnzj = 0, nln_nnzj = nnzj - lin_nnzj, - nnzh = sparse_hessian ? nvar * (nvar + 1) ÷ 2 : nvar * nvar, + nnzh = nvar * (nvar + 1) ÷ 2, lin = Int[], minimize::Bool = true, islp::Bool = false, From 147a4aa9f5a6b262a8fe7e1ef3eb28566fb92e8e Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 3 Feb 2026 18:59:02 -0600 Subject: [PATCH 06/10] Fix docstring --- src/nlp/api.jl | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/nlp/api.jl b/src/nlp/api.jl index 37b75caf..2501dd08 100644 --- a/src/nlp/api.jl +++ b/src/nlp/api.jl @@ -329,10 +329,8 @@ end """ vals = jac_lin_coord!(nlp, x, vals) -Evaluate ``J(x)``, the linear constraints Jacobian at `x`, overwriting `vals`. -It uses a sparse coordinate format when `nlp.meta.sparse_jacobian` is set to `true`. -Otherwise, `vals` is expected to be a dense matrix. -This function is only available if `nlp.meta.jac_available` is set to `true`. +Evaluate ``J(x)``, the linear constraints Jacobian at `x` in sparse coordinate format, overwriting `vals`. +This function is only available when both `nlp.meta.jac_available` and `nlp.meta.sparse_jacobian` are set to `true`. """ function jac_lin_coord! end @@ -364,10 +362,8 @@ end """ vals = jac_nln_coord!(nlp, x, vals) -Evaluate ``J(x)``, the nonlinear constraints Jacobian at `x`, overwriting `vals`. -It uses a sparse coordinate format when `nlp.meta.sparse_jacobian` is set to `true`. -Otherwise, `vals` is expected to be a dense matrix. -This function is only available if `nlp.meta.jac_available` is set to `true`. +Evaluate ``J(x)``, the nonlinear constraints Jacobian at `x` in sparse coordinate format, overwriting `vals`. +This function is only available when both `nlp.meta.jac_available` and `nlp.meta.sparse_jacobian` are set to `true`. """ function jac_nln_coord! end From b31fdfecedc51b5cada7313f1c40d10116d8134e Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 3 Feb 2026 19:02:33 -0600 Subject: [PATCH 07/10] Fix docstring --- src/nlp/api.jl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/nlp/api.jl b/src/nlp/api.jl index 2501dd08..ca15e375 100644 --- a/src/nlp/api.jl +++ b/src/nlp/api.jl @@ -974,11 +974,9 @@ end """ vals = jth_hess_coord!(nlp, x, j, vals) -Evaluate the Hessian of j-th constraint at `x`, overwriting `vals`. +Evaluate the Hessian of j-th constraint at `x` in sparse coordinate format, with `vals` of length `nlp.meta.nnzh`, in place. Only the lower triangle is returned. -It uses a sparse coordinate format, with `vals` of length `nlp.meta.nnzh`, -when `nlp.meta.sparse_hessian` is set to `true`. -Otherwise, `vals` is expected to be a dense matrix. +This function is only available when `nlp.meta.sparse_hessian` is set to `true`. """ function jth_hess_coord! end From e2b203f50187cb2596fff7a0667ee82e58137838 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 3 Feb 2026 19:09:34 -0600 Subject: [PATCH 08/10] Updatde api.md --- docs/src/api.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/api.md b/docs/src/api.md index d310341b..cd91de80 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -29,6 +29,7 @@ If not, click on the link and go to the description. - `!` means inplace; - `_coord` means coordinate format; +- `_dense` means dense format; - `prod` means matrix-vector product; - `_op` means operator (as in [LinearOperators.jl](https://github.com/JuliaSmoothOptimizers/LinearOperators.jl)); - `_lin` and `_nln` respectively refer to linear and nonlinear constraints. From e63b22af4923bb1f7d12cad555ea893ef95af5aa Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 3 Feb 2026 19:55:40 -0600 Subject: [PATCH 09/10] Remark about the in-place API for dense Jacobians and Hessians --- docs/src/guidelines.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/guidelines.md b/docs/src/guidelines.md index 8b301fcb..0f4a14ef 100644 --- a/docs/src/guidelines.md +++ b/docs/src/guidelines.md @@ -87,6 +87,7 @@ Alternatively, one could implement only the functions without the suffixes `_nln If the Jacobian or the Hessian of the Lagrangian is dense, there is no need to implement the corresponding `*_structure!` and `*_coord!` methods. Only the corresponding `*_dense!` methods need to be implemented. +Note that only the in-place API is provided for dense Jacobians and Hessians. This is specified at the initialization of [`NLPModelMeta`](@ref) through the keyword arguments `sparse_jacobian` and `sparse_hessian`. ## [Availability of the API](@id availability-api) From 391d8cb2ea45fb0d97b864cccb36ef5626540592 Mon Sep 17 00:00:00 2001 From: Alexis Montoison Date: Tue, 3 Feb 2026 20:30:32 -0600 Subject: [PATCH 10/10] Try to fix the performances of the split API --- docs/src/guidelines.md | 31 ++++++---- docs/src/reference.md | 12 ++-- src/nlp/api.jl | 119 ++------------------------------------- test/nlp/simple-model.jl | 82 ++++++++++++++++++++++++--- test/nls/simple-model.jl | 53 +++++++++++++++++ 5 files changed, 156 insertions(+), 141 deletions(-) diff --git a/docs/src/guidelines.md b/docs/src/guidelines.md index 0f4a14ef..92b9c8d0 100644 --- a/docs/src/guidelines.md +++ b/docs/src/guidelines.md @@ -64,26 +64,33 @@ The following functions should be defined: - `hess_coord!(nlp, x, hvals; obj_weight=1)` (sparse Hessian) - `hess_dense!(nlp, x, Hx; obj_weight=1)` (dense Hessian) - `hprod!(nlp, x, v, Hv; obj_weight=1)` (actually defaults to calling the constrained case) + - Constraints (constrained models need to worry about these and the ones above) - - `cons_lin!(nlp, x, c)` - - `cons_nln!(nlp, x, c)` - - `jac_lin_structure!(nlp, jrows, jcols)` (sparse Jacobian) - - `jac_nln_structure!(nlp, jrows, jcols)` (sparse Jacobian) - - `jac_lin_coord!(nlp, x, jvals)` (sparse Jacobian) - - `jac_nln_coord!(nlp, x, jvals)` (sparse Jacobian) + - `cons!(nlp, x, c)` + - `jac_structure!(nlp, jrows, jcols)` (sparse Jacobian) + - `jac_coord!(nlp, x, jvals)` (sparse Jacobian) - `jac_dense!(nlp, x, Jx)` (dense Jacobian) - - `jprod_lin!(nlp, x, v, Jv)` - - `jprod_nln!(nlp, x, v, Jv)` - - `jtprod_lin!(nlp, x, v, Jtv)` - - `jtprod_nln!(nlp, x, v, Jtv)` + - `jprod!(nlp, x, v, Jv)` + - `jtprod!(nlp, x, v, Jtv)` - `hess_coord!(nlp, x, y, hvals; obj_weight=1)` (sparse Hessian) - `hess_dense!(nlp, x, y, Hx; obj_weight=1)` (dense Hessian) - `hprod!(nlp, x, y, v, Hv; obj_weight=1)` +When the split API is enabled (`nlp.meta.split_api = true`), the treatment of linear and nonlinear constraints and their associated Jacobians is separated, and the following additional functions must be provided: + +- `cons_lin!(nlp, x, c)` +- `cons_nln!(nlp, x, c)` +- `jac_lin_structure!(nlp, jrows, jcols)` +- `jac_nln_structure!(nlp, jrows, jcols)` +- `jac_lin_coord!(nlp, x, jvals)` +- `jac_nln_coord!(nlp, x, jvals)` +- `jprod_lin!(nlp, x, v, Jv)` +- `jprod_nln!(nlp, x, v, Jv)` +- `jtprod_lin!(nlp, x, v, Jtv)` +- `jtprod_nln!(nlp, x, v, Jtv)` + The linear constraints are specified at the initialization of the `NLPModelMeta` using the keyword arguement `lin`. The indices of linear and nonlinear constraints are respectively available in `nlp.meta.lin` and `nlp.meta.nln`. -If your model uses only linear (resp. nonlinear) constraints, then it suffices to implement the `*_lin` (resp. `*_nln`) functions. -Alternatively, one could implement only the functions without the suffixes `_nln!` (e.g., only `cons!`), but this might run into errors with tools differentiating linear and nonlinear constraints. If the Jacobian or the Hessian of the Lagrangian is dense, there is no need to implement the corresponding `*_structure!` and `*_coord!` methods. Only the corresponding `*_dense!` methods need to be implemented. diff --git a/docs/src/reference.md b/docs/src/reference.md index 45ffa759..ef9a50ca 100644 --- a/docs/src/reference.md +++ b/docs/src/reference.md @@ -1,17 +1,17 @@ # Reference -​ + ## Contents -​ + ```@contents Pages = ["reference.md"] ``` -​ + ## Index -​ + ```@index Pages = ["reference.md"] ``` -​ + ```@autodocs Modules = [NLPModels] -``` \ No newline at end of file +``` diff --git a/src/nlp/api.jl b/src/nlp/api.jl index ca15e375..3b993331 100644 --- a/src/nlp/api.jl +++ b/src/nlp/api.jl @@ -57,26 +57,7 @@ end Evaluate ``c(x)``, the constraints at `x` in place. """ -function cons!(nlp::AbstractNLPModel, x::AbstractVector, cx::AbstractVector) - @lencheck nlp.meta.nvar x - @lencheck nlp.meta.ncon cx - increment!(nlp, :neval_cons) - if nlp.meta.nlin > 0 - if nlp.meta.nnln == 0 - cons_lin!(nlp, x, cx) - else - cons_lin!(nlp, x, view(cx, nlp.meta.lin)) - end - end - if nlp.meta.nnln > 0 - if nlp.meta.nlin == 0 - cons_nln!(nlp, x, cx) - else - cons_nln!(nlp, x, view(cx, nlp.meta.nln)) - end - end - return cx -end +function cons! end """ c = cons_lin(nlp, x) @@ -193,36 +174,7 @@ end Return the structure of the constraints Jacobian in sparse coordinate format in place. This function is only available when both `nlp.meta.jac_available` and `nlp.meta.sparse_jacobian` are set to `true`. """ -function jac_structure!( - nlp::AbstractNLPModel, - rows::AbstractVector{T}, - cols::AbstractVector{T}, -) where {T} - @lencheck nlp.meta.nnzj rows cols - lin_ind = 1:(nlp.meta.lin_nnzj) - if nlp.meta.nlin > 0 - if nlp.meta.nnln == 0 - jac_lin_structure!(nlp, rows, cols) - else - jac_lin_structure!(nlp, view(rows, lin_ind), view(cols, lin_ind)) - for i in lin_ind - rows[i] += count(x < nlp.meta.lin[rows[i]] for x in nlp.meta.nln) - end - end - end - if nlp.meta.nnln > 0 - if nlp.meta.nlin == 0 - jac_nln_structure!(nlp, rows, cols) - else - nln_ind = (nlp.meta.lin_nnzj + 1):(nlp.meta.lin_nnzj + nlp.meta.nln_nnzj) - jac_nln_structure!(nlp, view(rows, nln_ind), view(cols, nln_ind)) - for i in nln_ind - rows[i] += count(x < nlp.meta.nln[rows[i]] for x in nlp.meta.lin) - end - end - end - return rows, cols -end +function jac_structure! end """ (rows,cols) = jac_lin_structure(nlp) @@ -270,29 +222,7 @@ function jac_nln_structure! end Evaluate ``J(x)``, the constraints Jacobian at `x` in sparse coordinate format, overwriting `vals`. This function is only available when both `nlp.meta.jac_available` and `nlp.meta.sparse_jacobian` are set to `true`. """ -function jac_coord!(nlp::AbstractNLPModel, x::AbstractVector, vals::AbstractVector) - @lencheck nlp.meta.nvar x - @lencheck nlp.meta.nnzj vals - increment!(nlp, :neval_jac) - if nlp.meta.nlin > 0 - if nlp.meta.nnln == 0 - jac_lin_coord!(nlp, x, vals) - else - lin_ind = 1:(nlp.meta.lin_nnzj) - jac_lin_coord!(nlp, x, view(vals, lin_ind)) - end - end - if nlp.meta.nnln > 0 - if nlp.meta.nlin == 0 - jac_nln_coord!(nlp, x, vals) - else - nln_ind = (nlp.meta.lin_nnzj + 1):(nlp.meta.lin_nnzj + nlp.meta.nln_nnzj) - jac_nln_coord!(nlp, x, view(vals, nln_ind)) - end - end - return vals -end - +function jac_coord! end """ vals = jac_coord(nlp, x) @@ -410,26 +340,7 @@ end Evaluate ``J(x)v``, the Jacobian-vector product at `x` in place. This function is only available if `nlp.meta.jprod_available` is set to `true`. """ -function jprod!(nlp::AbstractNLPModel, x::AbstractVector, v::AbstractVector, Jv::AbstractVector) - @lencheck nlp.meta.nvar x v - @lencheck nlp.meta.ncon Jv - increment!(nlp, :neval_jprod) - if nlp.meta.nlin > 0 - if nlp.meta.nnln == 0 - jprod_lin!(nlp, x, v, Jv) - else - jprod_lin!(nlp, x, v, view(Jv, nlp.meta.lin)) - end - end - if nlp.meta.nnln > 0 - if nlp.meta.nlin == 0 - jprod_nln!(nlp, x, v, Jv) - else - jprod_nln!(nlp, x, v, view(Jv, nlp.meta.nln)) - end - end - return Jv -end +function jprod! end """ Jv = jprod!(nlp, rows, cols, vals, v, Jv) @@ -554,27 +465,7 @@ Evaluate ``J(x)^Tv``, the transposed-Jacobian-vector product at `x` in place. If the problem has linear and nonlinear constraints, this function allocates. This function is only available if `nlp.meta.jtprod_available` is set to `true`. """ -function jtprod!(nlp::AbstractNLPModel, x::AbstractVector, v::AbstractVector, Jtv::AbstractVector) - @lencheck nlp.meta.nvar x Jtv - @lencheck nlp.meta.ncon v - increment!(nlp, :neval_jtprod) - if nlp.meta.nnln == 0 - (nlp.meta.nlin > 0) && jtprod_lin!(nlp, x, v, Jtv) - elseif nlp.meta.nlin == 0 - (nlp.meta.nnln > 0) && jtprod_nln!(nlp, x, v, Jtv) - elseif nlp.meta.nlin >= nlp.meta.nnln - jtprod_lin!(nlp, x, view(v, nlp.meta.lin), Jtv) - if nlp.meta.nnln > 0 - Jtv .+= jtprod_nln(nlp, x, view(v, nlp.meta.nln)) - end - else - jtprod_nln!(nlp, x, view(v, nlp.meta.nln), Jtv) - if nlp.meta.nlin > 0 - Jtv .+= jtprod_lin(nlp, x, view(v, nlp.meta.lin)) - end - end - return Jtv -end +function jtprod! end """ Jtv = jtprod!(nlp, rows, cols, vals, v, Jtv) diff --git a/test/nlp/simple-model.jl b/test/nlp/simple-model.jl index 7f61c626..745bd6c7 100644 --- a/test/nlp/simple-model.jl +++ b/test/nlp/simple-model.jl @@ -94,11 +94,20 @@ function NLPModels.hprod!( return Hv end +function NLPModels.cons!(nlp::SimpleNLPModel, x::AbstractVector, cx::AbstractVector) + @lencheck 2 x + @lencheck 2 cx + increment!(nlp, :neval_cons) + cx[1] = x[1] - 2 * x[2] + 1 + cx[2] = -x[1]^2 / 4 - x[2]^2 + 1 + return cx +end + function NLPModels.cons_nln!(nlp::SimpleNLPModel, x::AbstractVector, cx::AbstractVector) @lencheck 2 x @lencheck 1 cx increment!(nlp, :neval_cons_nln) - cx .= [-x[1]^2 / 4 - x[2]^2 + 1] + cx[1] = -x[1]^2 / 4 - x[2]^2 + 1 return cx end @@ -106,10 +115,21 @@ function NLPModels.cons_lin!(nlp::SimpleNLPModel, x::AbstractVector, cx::Abstrac @lencheck 2 x @lencheck 1 cx increment!(nlp, :neval_cons_lin) - cx .= [x[1] - 2 * x[2] + 1] + cx[1] = x[1] - 2 * x[2] + 1 return cx end +function NLPModels.jac_structure!( + nlp::SimpleNLPModel, + rows::AbstractVector{Int}, + cols::AbstractVector{Int}, +) + @lencheck 4 rows cols + rows .= [1, 1, 2, 2] + cols .= [1, 2, 1, 2] + return rows, cols +end + function NLPModels.jac_nln_structure!( nlp::SimpleNLPModel, rows::AbstractVector{Int}, @@ -132,20 +152,48 @@ function NLPModels.jac_lin_structure!( return rows, cols end +function NLPModels.jac_coord!(nlp::SimpleNLPModel, x::AbstractVector, vals::AbstractVector) + @lencheck 2 x + @lencheck 4 vals + increment!(nlp, :neval_jac) + vals[1] = 1 + vals[2] = -2 + vals[3] = -x[1] / 2 + vals[4] = -2 * x[2] + return vals +end + function NLPModels.jac_nln_coord!(nlp::SimpleNLPModel, x::AbstractVector, vals::AbstractVector) - @lencheck 2 x vals + @lencheck 2 x + @lencheck 2 vals increment!(nlp, :neval_jac_nln) - vals .= [-x[1] / 2, -2 * x[2]] + vals[1] = -x[1] / 2 + vals[2] = -2 * x[2] return vals end function NLPModels.jac_lin_coord!(nlp::SimpleNLPModel, x::AbstractVector, vals::AbstractVector) @lencheck 2 x vals increment!(nlp, :neval_jac_lin) - vals .= [1, -2] + vals[1] = 1 + vals[2] = -2 return vals end +function NLPModels.jprod!( + nlp::SimpleNLPModel, + x::AbstractVector, + v::AbstractVector, + Jv::AbstractVector, +) + @lencheck 2 x v + @lencheck 2 Jv + increment!(nlp, :neval_jprod) + Jv[1] = v[1] - 2 * v[2] + Jv[2] = -x[1] * v[1] / 2 - 2 * x[2] * v[2] + return Jv +end + function NLPModels.jprod_nln!( nlp::SimpleNLPModel, x::AbstractVector, @@ -155,7 +203,7 @@ function NLPModels.jprod_nln!( @lencheck 2 x v @lencheck 1 Jv increment!(nlp, :neval_jprod_nln) - Jv .= [-x[1] * v[1] / 2 - 2 * x[2] * v[2]] + Jv[1] = -x[1] * v[1] / 2 - 2 * x[2] * v[2] return Jv end @@ -168,10 +216,24 @@ function NLPModels.jprod_lin!( @lencheck 2 x v @lencheck 1 Jv increment!(nlp, :neval_jprod_lin) - Jv .= [v[1] - 2 * v[2]] + Jv[1] = v[1] - 2 * v[2] return Jv end +function NLPModels.jtprod!( + nlp::SimpleNLPModel, + x::AbstractVector, + v::AbstractVector, + Jtv::AbstractVector, +) + @lencheck 2 x Jtv + @lencheck 2 v + increment!(nlp, :neval_jtprod) + Jtv[1] = v[1] - x[1] * v[2] / 2 + Jtv[2] = -2 * v[1] -2 * x[2] * v[2] + return Jtv +end + function NLPModels.jtprod_nln!( nlp::SimpleNLPModel, x::AbstractVector, @@ -181,7 +243,8 @@ function NLPModels.jtprod_nln!( @lencheck 2 x Jtv @lencheck 1 v increment!(nlp, :neval_jtprod_nln) - Jtv .= [-x[1] * v[1] / 2; -2 * x[2] * v[1]] + Jtv[1] = -x[1] * v[1] / 2 + Jtv[2] = -2 * x[2] * v[1] return Jtv end @@ -194,7 +257,8 @@ function NLPModels.jtprod_lin!( @lencheck 2 x Jtv @lencheck 1 v increment!(nlp, :neval_jtprod_lin) - Jtv .= [v[1]; -2 * v[1]] + Jtv[1] = v[1] + Jtv[2] = -2 * v[1] return Jtv end diff --git a/test/nls/simple-model.jl b/test/nls/simple-model.jl index 8f1fe50c..56d9672b 100644 --- a/test/nls/simple-model.jl +++ b/test/nls/simple-model.jl @@ -135,6 +135,14 @@ function NLPModels.hprod_residual!( return Hiv end +function NLPModels.cons!(nls::SimpleNLSModel, x::AbstractVector, cx::AbstractVector) + @lencheck 2 x + @lencheck 3 cx + increment!(nls, :neval_cons) + cx .= [x[1] + x[2]^2; x[1]^2 + x[2]; x[1]^2 + x[2]^2 - 1] + return cx +end + function NLPModels.cons_nln!(nls::SimpleNLSModel, x::AbstractVector, cx::AbstractVector) @lencheck 2 x @lencheck 3 cx @@ -143,6 +151,17 @@ function NLPModels.cons_nln!(nls::SimpleNLSModel, x::AbstractVector, cx::Abstrac return cx end +function NLPModels.jac_structure!( + nls::SimpleNLSModel, + rows::AbstractVector{<:Integer}, + cols::AbstractVector{<:Integer}, +) + @lencheck 6 rows cols + rows .= [1, 1, 2, 2, 3, 3] + cols .= [1, 2, 1, 2, 1, 2] + return rows, cols +end + function NLPModels.jac_nln_structure!( nls::SimpleNLSModel, rows::AbstractVector{<:Integer}, @@ -154,6 +173,14 @@ function NLPModels.jac_nln_structure!( return rows, cols end +function NLPModels.jac_coord!(nls::SimpleNLSModel, x::AbstractVector, vals::AbstractVector) + @lencheck 2 x + @lencheck 6 vals + increment!(nls, :neval_jac) + vals .= [1, 2x[2], 2x[1], 1, 2x[1], 2x[2]] + return vals +end + function NLPModels.jac_nln_coord!(nls::SimpleNLSModel, x::AbstractVector, vals::AbstractVector) @lencheck 2 x @lencheck 6 vals @@ -162,6 +189,19 @@ function NLPModels.jac_nln_coord!(nls::SimpleNLSModel, x::AbstractVector, vals:: return vals end +function NLPModels.jprod!( + nls::SimpleNLSModel, + x::AbstractVector, + v::AbstractVector, + Jv::AbstractVector, +) + @lencheck 2 x v + @lencheck 3 Jv + increment!(nls, :neval_jprod) + Jv .= [v[1] + 2x[2] * v[2]; 2x[1] * v[1] + v[2]; 2x[1] * v[1] + 2x[2] * v[2]] + return Jv +end + function NLPModels.jprod_nln!( nls::SimpleNLSModel, x::AbstractVector, @@ -175,6 +215,19 @@ function NLPModels.jprod_nln!( return Jv end +function NLPModels.jtprod!( + nls::SimpleNLSModel, + x::AbstractVector, + v::AbstractVector, + Jtv::AbstractVector, +) + @lencheck 2 x Jtv + @lencheck 3 v + increment!(nls, :neval_jtprod) + Jtv .= [v[1] + 2x[1] * (v[2] + v[3]); v[2] + 2x[2] * (v[1] + v[3])] + return Jtv +end + function NLPModels.jtprod_nln!( nls::SimpleNLSModel, x::AbstractVector,