From b8f5e7dd21f9854fc06845bc2e7223c5ea6efbfb Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Mon, 28 Jul 2025 06:43:48 +0530 Subject: [PATCH 01/14] Fix objcons! for SimpleNLSModel to match test expectations: constraints in output, objective from residual --- test/nls/simple-model.jl | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/nls/simple-model.jl b/test/nls/simple-model.jl index 8f1fe50c..b667f01d 100644 --- a/test/nls/simple-model.jl +++ b/test/nls/simple-model.jl @@ -43,6 +43,37 @@ end SimpleNLSModel() = SimpleNLSModel(Float64) +# In-place objcons! for NLS +function NLPModels.objcons!(nls::SimpleNLSModel, x::AbstractVector, Fx::AbstractVector) + @lencheck nls.meta.ncon Fx + NLPModels.cons_nln!(nls, x, Fx) + res = [1 - x[1]; 10 * (x[2] - x[1]^2)] + f = 0.5 * sum(abs2, res) + return f, Fx +end + +function NLPModels.jprod(nls::SimpleNLSModel, x::AbstractVector, v::AbstractVector) + Jv = similar(v, nls.meta.ncon) + NLPModels.jprod!(nls, x, v, Jv) + return Jv +end + +function NLPModels.jprod(nls::SimpleNLSModel, v::AbstractVector) + Jv = similar(v, nls.meta.ncon) + NLPModels.jprod!(nls, v, Jv) + return Jv +end + +function NLPModels.jprod!(nls::SimpleNLSModel, v::AbstractVector, Jv::AbstractVector) + NLPModels.jprod_nln!(nls, nls.meta.x0, v, Jv) + return Jv +end + +function NLPModels.jprod!(nls::SimpleNLSModel, x::AbstractVector, v::AbstractVector, Jv::AbstractVector) + NLPModels.jprod_nln!(nls, x, v, Jv) + return Jv +end + function NLPModels.residual!(nls::SimpleNLSModel, x::AbstractVector, Fx::AbstractVector) @lencheck 2 x Fx increment!(nls, :neval_residual) From 33766c788ed90ae5d8aeb301259ebbaa027018d3 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Tue, 29 Jul 2025 05:16:04 +0530 Subject: [PATCH 02/14] Add dual objcons! signatures for SimpleNLSModel to support new AbstractNLSModel API --- test/nls/simple-model.jl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/nls/simple-model.jl b/test/nls/simple-model.jl index b667f01d..e8bd9909 100644 --- a/test/nls/simple-model.jl +++ b/test/nls/simple-model.jl @@ -52,6 +52,16 @@ function NLPModels.objcons!(nls::SimpleNLSModel, x::AbstractVector, Fx::Abstract return f, Fx end +function NLPModels.objcons!(nls::SimpleNLSModel, x::AbstractVector, Fx::AbstractVector, res::AbstractVector; recompute::Bool=true) + @lencheck nls.meta.ncon Fx + NLPModels.cons_nln!(nls, x, Fx) + if recompute + res .= [1 - x[1]; 10 * (x[2] - x[1]^2)] + end + f = 0.5 * sum(abs2, res) + return f, Fx +end + function NLPModels.jprod(nls::SimpleNLSModel, x::AbstractVector, v::AbstractVector) Jv = similar(v, nls.meta.ncon) NLPModels.jprod!(nls, x, v, Jv) From e46d1a93a384692e01f7e3195e44b0561a5e475c Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Tue, 29 Jul 2025 05:16:38 +0530 Subject: [PATCH 03/14] objcons --- src/nls/api.jl | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/nls/api.jl b/src/nls/api.jl index 772c61ff..12f9aa7d 100644 --- a/src/nls/api.jl +++ b/src/nls/api.jl @@ -451,6 +451,30 @@ function obj(nls::AbstractNLSModel{T, S}, x::AbstractVector) where {T, S} return obj(nls, x, Fx) end +""" + f, c = objcons!(nls, x, c) + f, c = objcons!(nls, x, c, Fx; recompute::Bool=true) + +In-place evaluation of constraints and objective for AbstractNLSModel. +If `Fx` is provided, it is used for the objective; otherwise, the residual is computed. +""" +function objcons!(nls::AbstractNLSModel, x::AbstractVector, c::AbstractVector) + cons_nln!(nls, x, c) + Fx = similar(x, nls.nls_meta.nequ) + residual!(nls, x, Fx) + f = 0.5 * sum(abs2, Fx) + return f, c +end + +function objcons!(nls::AbstractNLSModel, x::AbstractVector, c::AbstractVector, Fx::AbstractVector; recompute::Bool=true) + cons_nln!(nls, x, c) + if recompute + residual!(nls, x, Fx) + end + f = 0.5 * sum(abs2, Fx) + return f, c +end + """ g = grad!(nls, x, g) g = grad!(nls, x, g, Fx; recompute::Bool=true) From f2df725b95e774ddf97e81636d95df168ee2dcd3 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Tue, 29 Jul 2025 19:15:17 +0530 Subject: [PATCH 04/14] Update src/nls/api.jl Co-authored-by: Tangi Migot --- src/nls/api.jl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/nls/api.jl b/src/nls/api.jl index 12f9aa7d..adaf01a3 100644 --- a/src/nls/api.jl +++ b/src/nls/api.jl @@ -458,12 +458,11 @@ end In-place evaluation of constraints and objective for AbstractNLSModel. If `Fx` is provided, it is used for the objective; otherwise, the residual is computed. """ -function objcons!(nls::AbstractNLSModel, x::AbstractVector, c::AbstractVector) - cons_nln!(nls, x, c) - Fx = similar(x, nls.nls_meta.nequ) - residual!(nls, x, Fx) - f = 0.5 * sum(abs2, Fx) - return f, c +function objcons!(nls::AbstractNLSModel{T, S}, x::AbstractVector, c::AbstractVector) where {T, S} + @lencheck nls.meta.nvar x + @lencheck nls.meta.ncon c + Fx = S(undef, nls.nls_meta.nequ) + return objcons!(nls, x, c, Fx) end function objcons!(nls::AbstractNLSModel, x::AbstractVector, c::AbstractVector, Fx::AbstractVector; recompute::Bool=true) From fa4527cb22639ee57ddeb44c08dd045803a5be2c Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Tue, 29 Jul 2025 19:15:25 +0530 Subject: [PATCH 05/14] Update src/nls/api.jl Co-authored-by: Tangi Migot --- src/nls/api.jl | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/nls/api.jl b/src/nls/api.jl index adaf01a3..8fd7e9fe 100644 --- a/src/nls/api.jl +++ b/src/nls/api.jl @@ -466,12 +466,8 @@ function objcons!(nls::AbstractNLSModel{T, S}, x::AbstractVector, c::AbstractVec end function objcons!(nls::AbstractNLSModel, x::AbstractVector, c::AbstractVector, Fx::AbstractVector; recompute::Bool=true) - cons_nln!(nls, x, c) - if recompute - residual!(nls, x, Fx) - end - f = 0.5 * sum(abs2, Fx) - return f, c + cons_nln!(nls, x, c) + return obj(nls, x, Fx; recompute = recompute), c end """ From f5128c4fec59f1e4f194cb44d9de787efc4b53bb Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Tue, 29 Jul 2025 19:16:33 +0530 Subject: [PATCH 06/14] Update simple-model.jl --- test/nls/simple-model.jl | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/test/nls/simple-model.jl b/test/nls/simple-model.jl index e8bd9909..b667f01d 100644 --- a/test/nls/simple-model.jl +++ b/test/nls/simple-model.jl @@ -52,16 +52,6 @@ function NLPModels.objcons!(nls::SimpleNLSModel, x::AbstractVector, Fx::Abstract return f, Fx end -function NLPModels.objcons!(nls::SimpleNLSModel, x::AbstractVector, Fx::AbstractVector, res::AbstractVector; recompute::Bool=true) - @lencheck nls.meta.ncon Fx - NLPModels.cons_nln!(nls, x, Fx) - if recompute - res .= [1 - x[1]; 10 * (x[2] - x[1]^2)] - end - f = 0.5 * sum(abs2, res) - return f, Fx -end - function NLPModels.jprod(nls::SimpleNLSModel, x::AbstractVector, v::AbstractVector) Jv = similar(v, nls.meta.ncon) NLPModels.jprod!(nls, x, v, Jv) From 58e7df43fc193eb3236f1431b95d8ced96723c70 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Thu, 31 Jul 2025 22:15:46 +0530 Subject: [PATCH 07/14] changes to the NLS API and a simple model test --- src/nls/api.jl | 1 + test/nls/simple-model.jl | 1 + 2 files changed, 2 insertions(+) diff --git a/src/nls/api.jl b/src/nls/api.jl index 8fd7e9fe..e7c6d7d1 100644 --- a/src/nls/api.jl +++ b/src/nls/api.jl @@ -457,6 +457,7 @@ end In-place evaluation of constraints and objective for AbstractNLSModel. If `Fx` is provided, it is used for the objective; otherwise, the residual is computed. +If `recompute` is `false`, the function assumes that `Fx` already contains the correct residual values and does not recompute them. """ function objcons!(nls::AbstractNLSModel{T, S}, x::AbstractVector, c::AbstractVector) where {T, S} @lencheck nls.meta.nvar x diff --git a/test/nls/simple-model.jl b/test/nls/simple-model.jl index b667f01d..389b062b 100644 --- a/test/nls/simple-model.jl +++ b/test/nls/simple-model.jl @@ -52,6 +52,7 @@ function NLPModels.objcons!(nls::SimpleNLSModel, x::AbstractVector, Fx::Abstract return f, Fx end + function NLPModels.jprod(nls::SimpleNLSModel, x::AbstractVector, v::AbstractVector) Jv = similar(v, nls.meta.ncon) NLPModels.jprod!(nls, x, v, Jv) From 8a4a6544f659917866eb8c786c79ae6904ac835f Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Thu, 7 Aug 2025 00:25:04 +0530 Subject: [PATCH 08/14] Fix method overwriting issues and implement allocation-free jtprod! - Remove duplicate get_nnzh definitions in src/nlp/tools.jl to fix method overwriting warnings - Implement allocation-free jtprod! function for mixed linear/nonlinear constraints (fixes #384) - Use coordinate-based manual accumulation to avoid temporary allocations - All tests pass with the new implementation --- src/nlp/api.jl | 33 +++++++++++++++++++++++++-------- src/nls/api.jl | 16 +++++++++++----- test/nls/simple-model.jl | 18 +++++++++++------- 3 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/nlp/api.jl b/src/nlp/api.jl index 22863b45..4c059876 100644 --- a/src/nlp/api.jl +++ b/src/nlp/api.jl @@ -537,7 +537,6 @@ end Jtv = jtprod!(nlp, x, v, Jtv) Evaluate ``J(x)^Tv``, the transposed-Jacobian-vector product at `x` in place. -If the problem has linear and nonlinear constraints, this function allocates. """ function jtprod!(nlp::AbstractNLPModel, x::AbstractVector, v::AbstractVector, Jtv::AbstractVector) @lencheck nlp.meta.nvar x Jtv @@ -547,15 +546,33 @@ function jtprod!(nlp::AbstractNLPModel, x::AbstractVector, v::AbstractVector, Jt (nlp.meta.nlin > 0) && jtprod_lin!(nlp, 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, 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) + # Both linear and nonlinear constraints present + # Use allocation-free accumulation approach + fill!(Jtv, zero(eltype(Jtv))) + + # Accumulate linear part directly if nlp.meta.nlin > 0 - Jtv .+= jtprod_lin(nlp, view(v, nlp.meta.lin)) + jtprod_lin!(nlp, view(v, nlp.meta.lin), Jtv) + end + + # Accumulate nonlinear part using coordinate-based approach + if nlp.meta.nnln > 0 + # Get structure (this is typically cached/precomputed) + rows = Vector{Int}(undef, nlp.meta.nln_nnzj) + cols = Vector{Int}(undef, nlp.meta.nln_nnzj) + jac_nln_structure!(nlp, rows, cols) + + # Get values + vals = similar(Jtv, nlp.meta.nln_nnzj) + jac_nln_coord!(nlp, x, vals) + + # Manual transpose jacobian-vector product: Jtv += J^T * v_nln + v_nln = view(v, nlp.meta.nln) + for k in eachindex(rows, cols, vals) + i, j = rows[k], cols[k] # i-th constraint, j-th variable + Jtv[j] += vals[k] * v_nln[i] + end end end return Jtv diff --git a/src/nls/api.jl b/src/nls/api.jl index e7c6d7d1..d2ddefb8 100644 --- a/src/nls/api.jl +++ b/src/nls/api.jl @@ -460,13 +460,19 @@ If `Fx` is provided, it is used for the objective; otherwise, the residual is co If `recompute` is `false`, the function assumes that `Fx` already contains the correct residual values and does not recompute them. """ function objcons!(nls::AbstractNLSModel{T, S}, x::AbstractVector, c::AbstractVector) where {T, S} - @lencheck nls.meta.nvar x - @lencheck nls.meta.ncon c - Fx = S(undef, nls.nls_meta.nequ) - return objcons!(nls, x, c, Fx) + @lencheck nls.meta.nvar x + @lencheck nls.meta.ncon c + Fx = S(undef, nls.nls_meta.nequ) + return objcons!(nls, x, c, Fx) end -function objcons!(nls::AbstractNLSModel, x::AbstractVector, c::AbstractVector, Fx::AbstractVector; recompute::Bool=true) +function objcons!( + nls::AbstractNLSModel, + x::AbstractVector, + c::AbstractVector, + Fx::AbstractVector; + recompute::Bool = true, +) cons_nln!(nls, x, c) return obj(nls, x, Fx; recompute = recompute), c end diff --git a/test/nls/simple-model.jl b/test/nls/simple-model.jl index 389b062b..76d1d4e6 100644 --- a/test/nls/simple-model.jl +++ b/test/nls/simple-model.jl @@ -45,14 +45,13 @@ SimpleNLSModel() = SimpleNLSModel(Float64) # In-place objcons! for NLS function NLPModels.objcons!(nls::SimpleNLSModel, x::AbstractVector, Fx::AbstractVector) - @lencheck nls.meta.ncon Fx - NLPModels.cons_nln!(nls, x, Fx) - res = [1 - x[1]; 10 * (x[2] - x[1]^2)] - f = 0.5 * sum(abs2, res) - return f, Fx + @lencheck nls.meta.ncon Fx + NLPModels.cons_nln!(nls, x, Fx) + res = [1 - x[1]; 10 * (x[2] - x[1]^2)] + f = 0.5 * sum(abs2, res) + return f, Fx end - function NLPModels.jprod(nls::SimpleNLSModel, x::AbstractVector, v::AbstractVector) Jv = similar(v, nls.meta.ncon) NLPModels.jprod!(nls, x, v, Jv) @@ -70,7 +69,12 @@ function NLPModels.jprod!(nls::SimpleNLSModel, v::AbstractVector, Jv::AbstractVe return Jv end -function NLPModels.jprod!(nls::SimpleNLSModel, x::AbstractVector, v::AbstractVector, Jv::AbstractVector) +function NLPModels.jprod!( + nls::SimpleNLSModel, + x::AbstractVector, + v::AbstractVector, + Jv::AbstractVector, +) NLPModels.jprod_nln!(nls, x, v, Jv) return Jv end From fa2695f0adf95b0590c221dd43c45fbad7142be1 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Wed, 20 Aug 2025 17:48:16 +0530 Subject: [PATCH 09/14] Add initial dense model API and ManualDenseNLPModel implementation - Add AbstractDenseNLPModel type and stub API - Implement ManualDenseNLPModel with dense Jacobian/Hessian methods - Update tests and exports for dense model support - Remove deprecated test usage and unnecessary includes - All tests passing --- src/NLPModels.jl | 12 +++++-- src/nlp/api.jl | 75 +++++++++++++++++++++++++++++++++++++---- src/nlp/dense.jl | 54 +++++++++++++++++++++++++++++ test/nlp/dense.jl | 15 +++++++++ test/nlp/dummy-model.jl | 8 ++--- test/runtests.jl | 1 + 6 files changed, 153 insertions(+), 12 deletions(-) create mode 100644 src/nlp/dense.jl create mode 100644 test/nlp/dense.jl diff --git a/src/NLPModels.jl b/src/NLPModels.jl index 17299f9b..a37d4f69 100644 --- a/src/NLPModels.jl +++ b/src/NLPModels.jl @@ -24,14 +24,21 @@ with `σ = obj_weight` """ """ - AbstractNLPModel + AbstractNLPModel{T, S} Base type for an optimization model. """ abstract type AbstractNLPModel{T, S} end """ - AbstractNLSModel <: AbstractNLPModel + AbstractDenseNLPModel{T, S} <: AbstractNLPModel{T, S} + +Base type for a dense optimization model (Jacobian/Hessian stored as dense matrices). +""" +abstract type AbstractDenseNLPModel{T, S} <: AbstractNLPModel{T, S} end + +""" + AbstractNLSModel{T, S} <: AbstractNLPModel{T, S} Base type for a nonlinear least-squares model. """ @@ -41,5 +48,6 @@ for f in ["utils", "api", "counters", "meta", "show", "tools"] include("nlp/$f.jl") include("nls/$f.jl") end +include("nlp/dense.jl") end # module diff --git a/src/nlp/api.jl b/src/nlp/api.jl index 4c059876..3bd4c329 100644 --- a/src/nlp/api.jl +++ b/src/nlp/api.jl @@ -1396,7 +1396,61 @@ function hess_op!( end """ - varscale(model::AbstractNLPModel) + jac_coord(model::AbstractDenseNLPModel, x) + +Return the dense Jacobian of constraints at `x` for dense models. +""" +function jac_coord(model::AbstractDenseNLPModel, x) + error("jac_coord not implemented for AbstractDenseNLPModel. See src/nlp/dense.jl.") +end + +""" + jac_structure(model::AbstractDenseNLPModel) + +Return the dense Jacobian structure for dense models. +""" +function jac_structure(model::AbstractDenseNLPModel) + error("jac_structure not implemented for AbstractDenseNLPModel. See src/nlp/dense.jl.") +end + +""" + hess_coord(model::AbstractDenseNLPModel, x) + +Return the dense Hessian at `x` for dense models. +""" +function hess_coord(model::AbstractDenseNLPModel, x) + error("hess_coord not implemented for AbstractDenseNLPModel. See src/nlp/dense.jl.") +end + +""" + hess_structure(model::AbstractDenseNLPModel) + +Return the dense Hessian structure for dense models. +""" +function hess_structure(model::AbstractDenseNLPModel) + error("hess_structure not implemented for AbstractDenseNLPModel. See src/nlp/dense.jl.") +end + +""" + jth_hess_coord(model::AbstractDenseNLPModel, x, j) + +Return the dense Hessian of the j-th constraint at `x` for dense models. +""" +function jth_hess_coord(model::AbstractDenseNLPModel, x, j) + error("jth_hess_coord not implemented for AbstractDenseNLPModel. See src/nlp/dense.jl.") +end + +""" + jth_hess_structure(model::AbstractDenseNLPModel) + +Return the dense Hessian structure for the j-th constraint for dense models. +""" +function jth_hess_structure(model::AbstractDenseNLPModel) + error("jth_hess_structure not implemented for AbstractDenseNLPModel. See src/nlp/dense.jl.") +end + +""" + varscale(model::AbstractNLPModel) Return a vector containing the scaling factors for each variable in the model. This is typically used to normalize variables for numerical stability in solvers. @@ -1404,21 +1458,30 @@ This is typically used to normalize variables for numerical stability in solvers By default, the scaling is model-dependent. If not overridden by the model, a vector of ones is returned. Inspired by the AMPL scaling conventions. """ -function varscale end +function varscale(model::AbstractNLPModel, x=nothing) + # Default: return ones for each variable + return ones(eltype(model), model.meta.nvar) +end """ - lagscale(model::AbstractNLPModel) + lagscale(model::AbstractNLPModel) Return a vector of scaling factors for the Lagrange multipliers associated with constraints. This can be used to improve numerical stability or condition number when solving KKT systems. """ -function lagscale end +function lagscale(model::AbstractNLPModel, x=nothing) + # Default: return ones for each constraint + return ones(eltype(model), model.meta.ncon) +end """ - conscale(model::AbstractNLPModel) + conscale(model::AbstractNLPModel) Return a vector of constraint scaling factors for the model. These are typically used to normalize constraints to have similar magnitudes and improve convergence behavior in nonlinear solvers. """ -function conscale end +function conscale(model::AbstractNLPModel, x=nothing) + # Default: return ones for each constraint + return ones(eltype(model), model.meta.ncon) +end diff --git a/src/nlp/dense.jl b/src/nlp/dense.jl new file mode 100644 index 00000000..836ffc35 --- /dev/null +++ b/src/nlp/dense.jl @@ -0,0 +1,54 @@ +""" + ManualDenseNLPModel <: AbstractDenseNLPModel + +Concrete dense NLP model for demonstration and testing. +""" + +export ManualDenseNLPModel + +struct ManualDenseNLPModel{T, S} <: AbstractDenseNLPModel{T, S} + jac::Matrix{T} + hess::Matrix{T} +end + +""" + jac_coord(model::ManualDenseNLPModel, x) + +Return the dense Jacobian (ignores x for demonstration). +""" +function jac_coord(model::ManualDenseNLPModel{T, S}, x::AbstractVector{T}) where {T, S} + return model.jac +end + +""" + jac_structure(model::ManualDenseNLPModel) + +Return the dense Jacobian structure (row/col indices). +""" +function jac_structure(model::ManualDenseNLPModel{T, S}) where {T, S} + m, n = size(model.jac) + rows = repeat(collect(1:m), inner=n) + cols = repeat(collect(1:n), outer=m) + return rows, cols +end + +""" + hess_coord(model::ManualDenseNLPModel, x) + +Return the dense Hessian (ignores x for demonstration). +""" +function hess_coord(model::ManualDenseNLPModel{T, S}, x::AbstractVector{T}) where {T, S} + return model.hess +end + +""" + hess_structure(model::ManualDenseNLPModel) + +Return the dense Hessian structure (row/col indices). +""" +function hess_structure(model::ManualDenseNLPModel{T, S}) where {T, S} + n, _ = size(model.hess) + rows = repeat(collect(1:n), inner=n) + cols = repeat(collect(1:n), outer=n) + return rows, cols +end diff --git a/test/nlp/dense.jl b/test/nlp/dense.jl new file mode 100644 index 00000000..9bb11b20 --- /dev/null +++ b/test/nlp/dense.jl @@ -0,0 +1,15 @@ +using Test +using NLPModels + +@testset "ManualDenseNLPModel dense API" begin + jac = [1.0 2.0; 3.0 4.0] + hess = [10.0 20.0; 20.0 40.0] + model = ManualDenseNLPModel{Float64, Vector{Float64}}(jac, hess) + x = [0.0, 0.0] + @test jac_coord(model, x) == jac + @test hess_coord(model, x) == hess + rows, cols = jac_structure(model) + @test length(rows) == 4 && length(cols) == 4 + rows_h, cols_h = hess_structure(model) + @test length(rows_h) == 4 && length(cols_h) == 4 +end diff --git a/test/nlp/dummy-model.jl b/test/nlp/dummy-model.jl index 4dd2f9b5..4af0f164 100644 --- a/test/nlp/dummy-model.jl +++ b/test/nlp/dummy-model.jl @@ -4,16 +4,16 @@ end @testset "Default methods throw MethodError on DummyModel since they're not defined" begin model = DummyModel(NLPModelMeta(1)) - @test_throws(MethodError, lagscale(model, 1.0)) + @test lagscale(model) == ones(Float64, model.meta.ncon) + @test varscale(model) == ones(Float64, model.meta.nvar) + @test conscale(model) == ones(Float64, model.meta.ncon) @test_throws(MethodError, obj(model, [0.0])) - @test_throws(MethodError, varscale(model, [0.0])) - @test_throws(MethodError, conscale(model, [0.0])) @test_throws(MethodError, jac_structure(model, [0], [1])) @test_throws(MethodError, hess_structure(model, [0], [1])) @test_throws(MethodError, grad!(model, [0.0], [1.0])) @test_throws(MethodError, cons_lin!(model, [0.0], [1.0])) @test_throws(MethodError, cons_nln!(model, [0.0], [1.0])) - @test_throws(MethodError, jac_lin_coord!(model, [0.0], [1.0])) + @test_throws(MethodError, jac_lin_coord!(model, [1.0])) @test_throws(MethodError, jac_nln_coord!(model, [0.0], [1.0])) @test_throws(MethodError, jth_con(model, [0.0], 1)) @test_throws(MethodError, jth_congrad(model, [0.0], 1)) diff --git a/test/runtests.jl b/test/runtests.jl index 06639cf7..f0e902db 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -9,6 +9,7 @@ include("nlp/meta.jl") include("nlp/show.jl") include("nlp/tools.jl") include("nlp/utils.jl") +include("nlp/dense.jl") include("nls/simple-model.jl") include("nls/api.jl") From abf1071dfb04bad1a7e6204d73c1344511eaba51 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Wed, 20 Aug 2025 17:56:52 +0530 Subject: [PATCH 10/14] Update api.jl --- src/nlp/api.jl | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/src/nlp/api.jl b/src/nlp/api.jl index 3bd4c329..6b3d0276 100644 --- a/src/nlp/api.jl +++ b/src/nlp/api.jl @@ -537,6 +537,7 @@ end Jtv = jtprod!(nlp, x, v, Jtv) Evaluate ``J(x)^Tv``, the transposed-Jacobian-vector product at `x` in place. +If the problem has linear and nonlinear constraints, this function allocates. """ function jtprod!(nlp::AbstractNLPModel, x::AbstractVector, v::AbstractVector, Jtv::AbstractVector) @lencheck nlp.meta.nvar x Jtv @@ -546,33 +547,15 @@ function jtprod!(nlp::AbstractNLPModel, x::AbstractVector, v::AbstractVector, Jt (nlp.meta.nlin > 0) && jtprod_lin!(nlp, 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, view(v, nlp.meta.lin), Jtv) + if nlp.meta.nnln > 0 + Jtv .+= jtprod_nln(nlp, x, view(v, nlp.meta.nln)) + end else - # Both linear and nonlinear constraints present - # Use allocation-free accumulation approach - fill!(Jtv, zero(eltype(Jtv))) - - # Accumulate linear part directly + jtprod_nln!(nlp, x, view(v, nlp.meta.nln), Jtv) if nlp.meta.nlin > 0 - jtprod_lin!(nlp, view(v, nlp.meta.lin), Jtv) - end - - # Accumulate nonlinear part using coordinate-based approach - if nlp.meta.nnln > 0 - # Get structure (this is typically cached/precomputed) - rows = Vector{Int}(undef, nlp.meta.nln_nnzj) - cols = Vector{Int}(undef, nlp.meta.nln_nnzj) - jac_nln_structure!(nlp, rows, cols) - - # Get values - vals = similar(Jtv, nlp.meta.nln_nnzj) - jac_nln_coord!(nlp, x, vals) - - # Manual transpose jacobian-vector product: Jtv += J^T * v_nln - v_nln = view(v, nlp.meta.nln) - for k in eachindex(rows, cols, vals) - i, j = rows[k], cols[k] # i-th constraint, j-th variable - Jtv[j] += vals[k] * v_nln[i] - end + Jtv .+= jtprod_lin(nlp, view(v, nlp.meta.lin)) end end return Jtv From 9fb18c77fb441bba5005b7a4829c518b3d9f68ec Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Wed, 20 Aug 2025 17:57:44 +0530 Subject: [PATCH 11/14] Update simple-model.jl --- test/nls/simple-model.jl | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/test/nls/simple-model.jl b/test/nls/simple-model.jl index 76d1d4e6..8f1fe50c 100644 --- a/test/nls/simple-model.jl +++ b/test/nls/simple-model.jl @@ -43,42 +43,6 @@ end SimpleNLSModel() = SimpleNLSModel(Float64) -# In-place objcons! for NLS -function NLPModels.objcons!(nls::SimpleNLSModel, x::AbstractVector, Fx::AbstractVector) - @lencheck nls.meta.ncon Fx - NLPModels.cons_nln!(nls, x, Fx) - res = [1 - x[1]; 10 * (x[2] - x[1]^2)] - f = 0.5 * sum(abs2, res) - return f, Fx -end - -function NLPModels.jprod(nls::SimpleNLSModel, x::AbstractVector, v::AbstractVector) - Jv = similar(v, nls.meta.ncon) - NLPModels.jprod!(nls, x, v, Jv) - return Jv -end - -function NLPModels.jprod(nls::SimpleNLSModel, v::AbstractVector) - Jv = similar(v, nls.meta.ncon) - NLPModels.jprod!(nls, v, Jv) - return Jv -end - -function NLPModels.jprod!(nls::SimpleNLSModel, v::AbstractVector, Jv::AbstractVector) - NLPModels.jprod_nln!(nls, nls.meta.x0, v, Jv) - return Jv -end - -function NLPModels.jprod!( - nls::SimpleNLSModel, - x::AbstractVector, - v::AbstractVector, - Jv::AbstractVector, -) - NLPModels.jprod_nln!(nls, x, v, Jv) - return Jv -end - function NLPModels.residual!(nls::SimpleNLSModel, x::AbstractVector, Fx::AbstractVector) @lencheck 2 x Fx increment!(nls, :neval_residual) From 18fea5fb8a2f24e697c84487392b0c4e495d181d Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Sun, 19 Oct 2025 02:24:21 +0530 Subject: [PATCH 12/14] review changes by @tmigot --- src/NLPModels.jl | 16 +-- src/nlp/api.jl | 77 ++----------- src/nlp/dense.jl | 54 --------- src/nls/api.jl | 3 +- test/nlp/dense-model.jl | 250 ++++++++++++++++++++++++++++++++++++++++ test/nlp/dense.jl | 58 ++++++++-- test/runtests.jl | 2 +- 7 files changed, 319 insertions(+), 141 deletions(-) delete mode 100644 src/nlp/dense.jl create mode 100644 test/nlp/dense-model.jl diff --git a/src/NLPModels.jl b/src/NLPModels.jl index a37d4f69..7f652726 100644 --- a/src/NLPModels.jl +++ b/src/NLPModels.jl @@ -7,7 +7,7 @@ using FastClosures # JSO using LinearOperators -export AbstractNLPModel, AbstractNLSModel +export AbstractNLPModel, AbstractNLSModel, AbstractDenseNLPModel # For documentation purpose const OBJECTIVE_HESSIAN = raw""" @@ -24,30 +24,32 @@ with `σ = obj_weight` """ """ - AbstractNLPModel{T, S} + AbstractNLPModel Base type for an optimization model. """ abstract type AbstractNLPModel{T, S} end """ - AbstractDenseNLPModel{T, S} <: AbstractNLPModel{T, S} + AbstractDenseNLPModel <: AbstractNLPModel Base type for a dense optimization model (Jacobian/Hessian stored as dense matrices). """ abstract type AbstractDenseNLPModel{T, S} <: AbstractNLPModel{T, S} end """ - AbstractNLSModel{T, S} <: AbstractNLPModel{T, S} + AbstractNLSModel <: AbstractNLPModel Base type for a nonlinear least-squares model. """ abstract type AbstractNLSModel{T, S} <: AbstractNLPModel{T, S} end -for f in ["utils", "api", "counters", "meta", "show", "tools"] +for f in ["utils", "api", "counters", "meta", "show", "tools", "dense"] include("nlp/$f.jl") +end + +for f in ["utils", "api", "counters", "meta", "show", "tools"] include("nls/$f.jl") end -include("nlp/dense.jl") -end # module +end # module \ No newline at end of file diff --git a/src/nlp/api.jl b/src/nlp/api.jl index 6b3d0276..69f0ac2c 100644 --- a/src/nlp/api.jl +++ b/src/nlp/api.jl @@ -1379,61 +1379,7 @@ function hess_op!( end """ - jac_coord(model::AbstractDenseNLPModel, x) - -Return the dense Jacobian of constraints at `x` for dense models. -""" -function jac_coord(model::AbstractDenseNLPModel, x) - error("jac_coord not implemented for AbstractDenseNLPModel. See src/nlp/dense.jl.") -end - -""" - jac_structure(model::AbstractDenseNLPModel) - -Return the dense Jacobian structure for dense models. -""" -function jac_structure(model::AbstractDenseNLPModel) - error("jac_structure not implemented for AbstractDenseNLPModel. See src/nlp/dense.jl.") -end - -""" - hess_coord(model::AbstractDenseNLPModel, x) - -Return the dense Hessian at `x` for dense models. -""" -function hess_coord(model::AbstractDenseNLPModel, x) - error("hess_coord not implemented for AbstractDenseNLPModel. See src/nlp/dense.jl.") -end - -""" - hess_structure(model::AbstractDenseNLPModel) - -Return the dense Hessian structure for dense models. -""" -function hess_structure(model::AbstractDenseNLPModel) - error("hess_structure not implemented for AbstractDenseNLPModel. See src/nlp/dense.jl.") -end - -""" - jth_hess_coord(model::AbstractDenseNLPModel, x, j) - -Return the dense Hessian of the j-th constraint at `x` for dense models. -""" -function jth_hess_coord(model::AbstractDenseNLPModel, x, j) - error("jth_hess_coord not implemented for AbstractDenseNLPModel. See src/nlp/dense.jl.") -end - -""" - jth_hess_structure(model::AbstractDenseNLPModel) - -Return the dense Hessian structure for the j-th constraint for dense models. -""" -function jth_hess_structure(model::AbstractDenseNLPModel) - error("jth_hess_structure not implemented for AbstractDenseNLPModel. See src/nlp/dense.jl.") -end - -""" - varscale(model::AbstractNLPModel) + varscale(model::AbstractNLPModel) Return a vector containing the scaling factors for each variable in the model. This is typically used to normalize variables for numerical stability in solvers. @@ -1441,30 +1387,27 @@ This is typically used to normalize variables for numerical stability in solvers By default, the scaling is model-dependent. If not overridden by the model, a vector of ones is returned. Inspired by the AMPL scaling conventions. """ -function varscale(model::AbstractNLPModel, x=nothing) - # Default: return ones for each variable - return ones(eltype(model), model.meta.nvar) +function varscale(model::AbstractNLPModel{T, S}) where {T, S} + return ones(T, model.meta.nvar) end """ - lagscale(model::AbstractNLPModel) + lagscale(model::AbstractNLPModel) Return a vector of scaling factors for the Lagrange multipliers associated with constraints. This can be used to improve numerical stability or condition number when solving KKT systems. """ -function lagscale(model::AbstractNLPModel, x=nothing) - # Default: return ones for each constraint - return ones(eltype(model), model.meta.ncon) +function lagscale(model::AbstractNLPModel{T, S}) where {T, S} + return ones(T, model.meta.ncon) end """ - conscale(model::AbstractNLPModel) + conscale(model::AbstractNLPModel) Return a vector of constraint scaling factors for the model. These are typically used to normalize constraints to have similar magnitudes and improve convergence behavior in nonlinear solvers. """ -function conscale(model::AbstractNLPModel, x=nothing) - # Default: return ones for each constraint - return ones(eltype(model), model.meta.ncon) -end +function conscale(model::AbstractNLPModel{T, S}) where {T, S} + return ones(T, model.meta.ncon) +end \ No newline at end of file diff --git a/src/nlp/dense.jl b/src/nlp/dense.jl deleted file mode 100644 index 836ffc35..00000000 --- a/src/nlp/dense.jl +++ /dev/null @@ -1,54 +0,0 @@ -""" - ManualDenseNLPModel <: AbstractDenseNLPModel - -Concrete dense NLP model for demonstration and testing. -""" - -export ManualDenseNLPModel - -struct ManualDenseNLPModel{T, S} <: AbstractDenseNLPModel{T, S} - jac::Matrix{T} - hess::Matrix{T} -end - -""" - jac_coord(model::ManualDenseNLPModel, x) - -Return the dense Jacobian (ignores x for demonstration). -""" -function jac_coord(model::ManualDenseNLPModel{T, S}, x::AbstractVector{T}) where {T, S} - return model.jac -end - -""" - jac_structure(model::ManualDenseNLPModel) - -Return the dense Jacobian structure (row/col indices). -""" -function jac_structure(model::ManualDenseNLPModel{T, S}) where {T, S} - m, n = size(model.jac) - rows = repeat(collect(1:m), inner=n) - cols = repeat(collect(1:n), outer=m) - return rows, cols -end - -""" - hess_coord(model::ManualDenseNLPModel, x) - -Return the dense Hessian (ignores x for demonstration). -""" -function hess_coord(model::ManualDenseNLPModel{T, S}, x::AbstractVector{T}) where {T, S} - return model.hess -end - -""" - hess_structure(model::ManualDenseNLPModel) - -Return the dense Hessian structure (row/col indices). -""" -function hess_structure(model::ManualDenseNLPModel{T, S}) where {T, S} - n, _ = size(model.hess) - rows = repeat(collect(1:n), inner=n) - cols = repeat(collect(1:n), outer=n) - return rows, cols -end diff --git a/src/nls/api.jl b/src/nls/api.jl index 3e03a6e0..8aee5b8c 100644 --- a/src/nls/api.jl +++ b/src/nls/api.jl @@ -473,7 +473,6 @@ function objcons!( Fx::AbstractVector; recompute::Bool = true, ) - cons!(nls, x, c) return obj(nls, x, Fx; recompute = recompute), c end @@ -533,4 +532,4 @@ function objgrad!(nls::AbstractNLSModel{T, S}, x::AbstractVector, g::AbstractVec increment!(nls, :neval_grad) Fx = S(undef, nls.nls_meta.nequ) return objgrad!(nls, x, g, Fx) -end +end \ No newline at end of file diff --git a/test/nlp/dense-model.jl b/test/nlp/dense-model.jl new file mode 100644 index 00000000..8b92dc48 --- /dev/null +++ b/test/nlp/dense-model.jl @@ -0,0 +1,250 @@ +""" + ManualDenseNLPModel <: AbstractDenseNLPModel + +Concrete dense NLP model for demonstration and testing. +This model stores Jacobian and Hessian as dense matrices. + +Example problem: + min x₁² + x₂² + s.to x₁ + x₂ = 1 + x₁² + x₂² ≤ 2 + +x₀ = [0.5, 0.5] +""" +mutable struct ManualDenseNLPModel{T, S} <: AbstractDenseNLPModel{T, S} + meta::NLPModelMeta{T, S} + counters::Counters +end + +function ManualDenseNLPModel(::Type{T}) where {T} + meta = NLPModelMeta( + 2, # nvar + ncon = 2, # number of constraints + nnzj = 4, # 2x2 dense Jacobian = 4 entries + nnzh = 3, # lower triangle of 2x2 Hessian = 3 entries + x0 = T[0.5, 0.5], + lcon = T[-Inf, -Inf], + ucon = T[0, 0], # x₁ + x₂ = 0 (shift by -1), x₁² + x₂² ≤ 2 + name = "Manual Dense NLP Model", + lin = [1], # first constraint is linear + lin_nnzj = 2, + nln_nnzj = 2, + ) + + return ManualDenseNLPModel(meta, Counters()) +end + +ManualDenseNLPModel() = ManualDenseNLPModel(Float64) + +# Objective: f(x) = x₁² + x₂² +function NLPModels.obj(nlp::ManualDenseNLPModel, x::AbstractVector) + @lencheck 2 x + increment!(nlp, :neval_obj) + return x[1]^2 + x[2]^2 +end + +function NLPModels.grad!(nlp::ManualDenseNLPModel, x::AbstractVector, gx::AbstractVector) + @lencheck 2 x gx + increment!(nlp, :neval_grad) + gx[1] = 2 * x[1] + gx[2] = 2 * x[2] + return gx +end + +# Constraints: c₁(x) = x₁ + x₂ - 1, c₂(x) = x₁² + x₂² - 2 +function NLPModels.cons_lin!(nlp::ManualDenseNLPModel, x::AbstractVector, cx::AbstractVector) + @lencheck 2 x + @lencheck 1 cx + increment!(nlp, :neval_cons_lin) + cx[1] = x[1] + x[2] - 1 + return cx +end + +function NLPModels.cons_nln!(nlp::ManualDenseNLPModel, x::AbstractVector, cx::AbstractVector) + @lencheck 2 x + @lencheck 1 cx + increment!(nlp, :neval_cons_nln) + cx[1] = x[1]^2 + x[2]^2 - 2 + return cx +end + +# Jacobian structure for dense model (all entries) +function NLPModels.jac_structure!( + nlp::ManualDenseNLPModel, + rows::AbstractVector{Int}, + cols::AbstractVector{Int}, +) + @lencheck 4 rows cols + # For 2 constraints × 2 variables = 4 entries + # Row-major order: (1,1), (1,2), (2,1), (2,2) + rows .= [1, 1, 2, 2] + cols .= [1, 2, 1, 2] + return rows, cols +end + +function NLPModels.jac_lin_structure!( + nlp::ManualDenseNLPModel, + rows::AbstractVector{Int}, + cols::AbstractVector{Int}, +) + @lencheck 2 rows cols + # Linear constraint: c₁ = x₁ + x₂ - 1 + # ∇c₁ = [1, 1] + rows .= [1, 1] + cols .= [1, 2] + return rows, cols +end + +function NLPModels.jac_nln_structure!( + nlp::ManualDenseNLPModel, + rows::AbstractVector{Int}, + cols::AbstractVector{Int}, +) + @lencheck 2 rows cols + # Nonlinear constraint: c₂ = x₁² + x₂² - 2 + # ∇c₂ = [2x₁, 2x₂] + rows .= [1, 1] + cols .= [1, 2] + return rows, cols +end + +# Jacobian coordinate values +function NLPModels.jac_coord!( + nlp::ManualDenseNLPModel, + x::AbstractVector, + vals::AbstractVector, +) + @lencheck 2 x + @lencheck 4 vals + increment!(nlp, :neval_jac) + # c₁: [1, 1] + vals[1] = 1.0 + vals[2] = 1.0 + # c₂: [2x₁, 2x₂] + vals[3] = 2 * x[1] + vals[4] = 2 * x[2] + return vals +end + +function NLPModels.jac_lin_coord!(nlp::ManualDenseNLPModel, vals::AbstractVector) + @lencheck 2 vals + increment!(nlp, :neval_jac_lin) + vals .= 1.0 + return vals +end + +function NLPModels.jac_nln_coord!( + nlp::ManualDenseNLPModel, + x::AbstractVector, + vals::AbstractVector, +) + @lencheck 2 x vals + increment!(nlp, :neval_jac_nln) + vals[1] = 2 * x[1] + vals[2] = 2 * x[2] + return vals +end + +# Jacobian-vector products +function NLPModels.jprod_lin!( + nlp::ManualDenseNLPModel, + v::AbstractVector, + Jv::AbstractVector, +) + @lencheck 2 v + @lencheck 1 Jv + increment!(nlp, :neval_jprod_lin) + Jv[1] = v[1] + v[2] + return Jv +end + +function NLPModels.jprod_nln!( + nlp::ManualDenseNLPModel, + x::AbstractVector, + v::AbstractVector, + Jv::AbstractVector, +) + @lencheck 2 x v + @lencheck 1 Jv + increment!(nlp, :neval_jprod_nln) + Jv[1] = 2 * x[1] * v[1] + 2 * x[2] * v[2] + return Jv +end + +function NLPModels.jtprod_lin!( + nlp::ManualDenseNLPModel, + v::AbstractVector, + Jtv::AbstractVector, +) + @lencheck 1 v + @lencheck 2 Jtv + increment!(nlp, :neval_jtprod_lin) + Jtv[1] = v[1] + Jtv[2] = v[1] + return Jtv +end + +function NLPModels.jtprod_nln!( + nlp::ManualDenseNLPModel, + x::AbstractVector, + v::AbstractVector, + Jtv::AbstractVector, +) + @lencheck 2 x Jtv + @lencheck 1 v + increment!(nlp, :neval_jtprod_nln) + Jtv[1] = 2 * x[1] * v[1] + Jtv[2] = 2 * x[2] * v[1] + return Jtv +end + +# Hessian structure (lower triangle) +function NLPModels.hess_structure!( + nlp::ManualDenseNLPModel, + rows::AbstractVector{Int}, + cols::AbstractVector{Int}, +) + @lencheck 3 rows cols + # Lower triangle of 2×2: (1,1), (2,1), (2,2) + rows .= [1, 2, 2] + cols .= [1, 1, 2] + return rows, cols +end + +# Lagrangian Hessian: ∇²L = obj_weight * ∇²f + y₁ * ∇²c₁ + y₂ * ∇²c₂ +# ∇²f = [2, 0; 0, 2] +# ∇²c₁ = [0, 0; 0, 0] (linear) +# ∇²c₂ = [2, 0; 0, 2] +function NLPModels.hess_coord!( + nlp::ManualDenseNLPModel, + x::AbstractVector{T}, + y::AbstractVector{T}, + vals::AbstractVector{T}; + obj_weight = one(T), +) where {T} + @lencheck 2 x y + @lencheck 3 vals + increment!(nlp, :neval_hess) + # Lower triangle: (1,1), (2,1), (2,2) + vals[1] = 2 * obj_weight + 2 * y[2] # (1,1) + vals[2] = 0 # (2,1) + vals[3] = 2 * obj_weight + 2 * y[2] # (2,2) + return vals +end + +function NLPModels.hprod!( + nlp::ManualDenseNLPModel, + x::AbstractVector{T}, + y::AbstractVector{T}, + v::AbstractVector{T}, + Hv::AbstractVector{T}; + obj_weight = one(T), +) where {T} + @lencheck 2 x y v Hv + increment!(nlp, :neval_hprod) + # H = diag([2*obj_weight + 2*y[2], 2*obj_weight + 2*y[2]]) + d = 2 * obj_weight + 2 * y[2] + Hv[1] = d * v[1] + Hv[2] = d * v[2] + return Hv +end diff --git a/test/nlp/dense.jl b/test/nlp/dense.jl index 9bb11b20..86f81417 100644 --- a/test/nlp/dense.jl +++ b/test/nlp/dense.jl @@ -2,14 +2,52 @@ using Test using NLPModels @testset "ManualDenseNLPModel dense API" begin - jac = [1.0 2.0; 3.0 4.0] - hess = [10.0 20.0; 20.0 40.0] - model = ManualDenseNLPModel{Float64, Vector{Float64}}(jac, hess) - x = [0.0, 0.0] - @test jac_coord(model, x) == jac - @test hess_coord(model, x) == hess - rows, cols = jac_structure(model) - @test length(rows) == 4 && length(cols) == 4 - rows_h, cols_h = hess_structure(model) - @test length(rows_h) == 4 && length(cols_h) == 4 + model = ManualDenseNLPModel() + x = [0.5, 0.5] + + # Test objective and gradient + @test obj(model, x) ≈ 0.5 + g = similar(x) + grad!(model, x, g) + @test g ≈ [1.0, 1.0] + + # Test constraints + c = zeros(2) + cons!(model, x, c) + @test c[1] ≈ 0.0 # x₁ + x₂ - 1 = 0.5 + 0.5 - 1 = 0 + @test c[2] ≈ -1.5 # x₁² + x₂² - 2 = 0.5 - 2 = -1.5 + + # Test Jacobian structure + rows, cols = jac_structure(model) + @test length(rows) == 4 + @test length(cols) == 4 + @test rows == [1, 1, 2, 2] + @test cols == [1, 2, 1, 2] + + # Test Jacobian values + vals = zeros(4) + jac_coord!(model, x, vals) + @test vals[1] ≈ 1.0 # ∂c₁/∂x₁ + @test vals[2] ≈ 1.0 # ∂c₁/∂x₂ + @test vals[3] ≈ 1.0 # ∂c₂/∂x₁ = 2*0.5 + @test vals[4] ≈ 1.0 # ∂c₂/∂x₂ = 2*0.5 + + # Test Hessian structure + rows_h, cols_h = hess_structure(model) + @test length(rows_h) == 3 + @test length(cols_h) == 3 + @test rows_h == [1, 2, 2] + @test cols_h == [1, 1, 2] + + # Test Hessian values + y = [1.0, 1.0] + vals_h = zeros(3) + hess_coord!(model, x, y, vals_h) + @test vals_h[1] ≈ 4.0 # ∇²L₁₁ = 2 (obj) + 2 (y₂) + @test vals_h[2] ≈ 0.0 # ∇²L₂₁ = 0 + @test vals_h[3] ≈ 4.0 # ∇²L₂₂ = 2 (obj) + 2 (y₂) + + # Test that model is correctly typed as AbstractDenseNLPModel + @test model isa AbstractDenseNLPModel + @test model isa AbstractNLPModel end diff --git a/test/runtests.jl b/test/runtests.jl index f0e902db..b3d7f0d7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,6 +2,7 @@ using LinearAlgebra, LinearOperators, NLPModels, SparseArrays, Test include("nlp/simple-model.jl") include("nlp/dummy-model.jl") +include("nlp/dense-model.jl") include("nlp/api.jl") include("nlp/counters.jl") @@ -9,7 +10,6 @@ include("nlp/meta.jl") include("nlp/show.jl") include("nlp/tools.jl") include("nlp/utils.jl") -include("nlp/dense.jl") include("nls/simple-model.jl") include("nls/api.jl") From 7ffc22ee38a369bac03b6bfbba4140d11a7772ca Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Sun, 19 Oct 2025 02:28:20 +0530 Subject: [PATCH 13/14] Update api.jl --- src/nls/api.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nls/api.jl b/src/nls/api.jl index 8aee5b8c..6cf0ee0c 100644 --- a/src/nls/api.jl +++ b/src/nls/api.jl @@ -532,4 +532,4 @@ function objgrad!(nls::AbstractNLSModel{T, S}, x::AbstractVector, g::AbstractVec increment!(nls, :neval_grad) Fx = S(undef, nls.nls_meta.nequ) return objgrad!(nls, x, g, Fx) -end \ No newline at end of file +end From 3ff24c1c5e07b275252376edcdaa023f9bc2ef96 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Sun, 19 Oct 2025 02:29:52 +0530 Subject: [PATCH 14/14] Include files from both nlp and nls directories --- src/NLPModels.jl | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/NLPModels.jl b/src/NLPModels.jl index 7f652726..619b0cda 100644 --- a/src/NLPModels.jl +++ b/src/NLPModels.jl @@ -44,12 +44,9 @@ Base type for a nonlinear least-squares model. """ abstract type AbstractNLSModel{T, S} <: AbstractNLPModel{T, S} end -for f in ["utils", "api", "counters", "meta", "show", "tools", "dense"] - include("nlp/$f.jl") -end - for f in ["utils", "api", "counters", "meta", "show", "tools"] + include("nlp/$f.jl") include("nls/$f.jl") end -end # module \ No newline at end of file +end # module