From b95806be00a2c368576f8601af75ff21f89c7477 Mon Sep 17 00:00:00 2001 From: arnavk23 Date: Thu, 21 May 2026 15:22:05 +0530 Subject: [PATCH 1/7] changing example due to failure --- .../introduction-to-solverbenchmark/index.jmd | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/tutorials/introduction-to-solverbenchmark/index.jmd b/tutorials/introduction-to-solverbenchmark/index.jmd index 09ef6b5..c51433f 100644 --- a/tutorials/introduction-to-solverbenchmark/index.jmd +++ b/tutorials/introduction-to-solverbenchmark/index.jmd @@ -228,19 +228,12 @@ If a solver's `GenericExecutionStats` contains a `solver_specific` dictionary, t Here is an example showing how to set a solver-specific flag so that it appears as a column in the resulting stats table and can be used for tabulation: ```julia -using NLPModelsTest, DataFrames, SolverCore, SolverBenchmark +using ADNLPModels, SolverCore -function newton(nlp) - stats = GenericExecutionStats(nlp) - set_solver_specific!(stats, :isConvex, true) - return stats -end - -solvers = Dict(:newton => newton) -problems = [NLPModelsTest.BROWNDEN()] -stats = bmark_solvers(solvers, problems) +nlp = ADNLPModel(x -> sum(x .^ 2), [1.0, 1.0]) +stats = GenericExecutionStats(nlp) +set_solver_specific!(stats, :isConvex, true) +set_solver_specific!(stats, :inner_iterations, 7) -# Access the solver-specific column `:isConvex` for the `:newton` solver -df_newton = stats[:newton] -df_newton.isConvex +stats.solver_specific ``` From 8a7891c17aa893946f6471dc5454741e01be2846 Mon Sep 17 00:00:00 2001 From: arnavk23 Date: Thu, 21 May 2026 15:38:01 +0530 Subject: [PATCH 2/7] adding copilot suggestions --- .../introduction-to-solverbenchmark/index.jmd | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/tutorials/introduction-to-solverbenchmark/index.jmd b/tutorials/introduction-to-solverbenchmark/index.jmd index c51433f..4ad5567 100644 --- a/tutorials/introduction-to-solverbenchmark/index.jmd +++ b/tutorials/introduction-to-solverbenchmark/index.jmd @@ -224,16 +224,22 @@ The tutorial covers how to use the problems from `OptimizationProblems` to run a ### Handling `solver_specific` in `stats` -If a solver's `GenericExecutionStats` contains a `solver_specific` dictionary, then when `bmark_solvers` processes the results it creates a column in the per-solver `DataFrame` for each key in that dictionary. These columns can then be analyzed and compared alongside the standard metrics such as `status` and `elapsed_time`. +If a solver's `GenericExecutionStats` contains a `solver_specific` dictionary, those values can be tabulated alongside the standard metrics such as `status` and `elapsed_time`. -Here is an example showing how to set a solver-specific flag so that it appears as a column in the resulting stats table and can be used for tabulation: +Here is an example showing how to populate `solver_specific` and place those fields in a stats table: ```julia -using ADNLPModels, SolverCore +using ADNLPModels, DataFrames, SolverCore nlp = ADNLPModel(x -> sum(x .^ 2), [1.0, 1.0]) -stats = GenericExecutionStats(nlp) -set_solver_specific!(stats, :isConvex, true) -set_solver_specific!(stats, :inner_iterations, 7) - -stats.solver_specific +stats = SolverCore.GenericExecutionStats(nlp) +SolverCore.set_solver_specific!(stats, :isConvex, true) +SolverCore.set_solver_specific!(stats, :inner_iterations, 7) + +df = DataFrame( + solver = ["newton"], + status = [stats.status], + isConvex = [stats.solver_specific[:isConvex]], + inner_iterations = [stats.solver_specific[:inner_iterations]], +) +pretty_stats(stdout, df) ``` From 41937e04154618a4b871e5502ffd61b6eaf1bbf1 Mon Sep 17 00:00:00 2001 From: arnavk23 Date: Sat, 23 May 2026 13:55:44 +0530 Subject: [PATCH 3/7] adding failing reset! --- .../Project.toml | 2 ++ .../introduction-to-solverbenchmark/index.jmd | 30 ++++++++++--------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/tutorials/introduction-to-solverbenchmark/Project.toml b/tutorials/introduction-to-solverbenchmark/Project.toml index 04e073c..9e3441e 100644 --- a/tutorials/introduction-to-solverbenchmark/Project.toml +++ b/tutorials/introduction-to-solverbenchmark/Project.toml @@ -1,6 +1,7 @@ [deps] DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" NLPModelsTest = "7998695d-6960-4d3a-85c4-e1bceb8cd856" +NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" PyPlot = "d330b81b-6aea-500a-939a-2ce795aea3ee" @@ -12,6 +13,7 @@ SolverCore = "ff4d7338-4cf1-434d-91df-b86cb86fb843" DataFrames = "1.3.4" NLPModelsTest = "0.9" +NLPModels = "0.20" Plots = "1.31.7" PyPlot = "2.10.0" SolverBenchmark = "0.5.3" diff --git a/tutorials/introduction-to-solverbenchmark/index.jmd b/tutorials/introduction-to-solverbenchmark/index.jmd index 4ad5567..d673c83 100644 --- a/tutorials/introduction-to-solverbenchmark/index.jmd +++ b/tutorials/introduction-to-solverbenchmark/index.jmd @@ -228,18 +228,20 @@ If a solver's `GenericExecutionStats` contains a `solver_specific` dictionary, t Here is an example showing how to populate `solver_specific` and place those fields in a stats table: ```julia -using ADNLPModels, DataFrames, SolverCore - -nlp = ADNLPModel(x -> sum(x .^ 2), [1.0, 1.0]) -stats = SolverCore.GenericExecutionStats(nlp) -SolverCore.set_solver_specific!(stats, :isConvex, true) -SolverCore.set_solver_specific!(stats, :inner_iterations, 7) - -df = DataFrame( - solver = ["newton"], - status = [stats.status], - isConvex = [stats.solver_specific[:isConvex]], - inner_iterations = [stats.solver_specific[:inner_iterations]], -) -pretty_stats(stdout, df) +import NLPModels: reset! +using NLPModelsTest, DataFrames, SolverCore, SolverBenchmark + +function newton(nlp) + stats = GenericExecutionStats(nlp) + set_solver_specific!(stats, :isConvex, true) + return stats +end + +solvers = Dict(:newton => newton) +problems = [NLPModelsTest.BROWNDEN()] +stats = bmark_solvers(solvers, problems) + +# Access the solver-specific column `:isConvex` for the `:newton` solver +df_newton = stats[:newton] +df_newton.isConvex ``` From 6136c0541dc74b5bf84cbf9e6f90364b4cf7bf9b Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Sat, 23 May 2026 19:29:12 +0530 Subject: [PATCH 4/7] Update index.jmd --- tutorials/introduction-to-solverbenchmark/index.jmd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorials/introduction-to-solverbenchmark/index.jmd b/tutorials/introduction-to-solverbenchmark/index.jmd index d673c83..ee0dfe2 100644 --- a/tutorials/introduction-to-solverbenchmark/index.jmd +++ b/tutorials/introduction-to-solverbenchmark/index.jmd @@ -224,9 +224,9 @@ The tutorial covers how to use the problems from `OptimizationProblems` to run a ### Handling `solver_specific` in `stats` -If a solver's `GenericExecutionStats` contains a `solver_specific` dictionary, those values can be tabulated alongside the standard metrics such as `status` and `elapsed_time`. +If a solver's `GenericExecutionStats` contains a `solver_specific` dictionary, then when `bmark_solvers` processes the results it creates a column in the per-solver `DataFrame` for each key in that dictionary. These columns can then be analyzed and compared alongside the standard metrics such as `status` and `elapsed_time`. -Here is an example showing how to populate `solver_specific` and place those fields in a stats table: +Here is an example showing how to set a solver-specific flag so that it appears as a column in the resulting stats table and can be used for tabulation: ```julia import NLPModels: reset! using NLPModelsTest, DataFrames, SolverCore, SolverBenchmark From fb520c008ace0ce87837180023af3abfd82b1f4b Mon Sep 17 00:00:00 2001 From: arnavk23 Date: Tue, 26 May 2026 14:13:37 +0530 Subject: [PATCH 5/7] Fix tests to accept index.* filenames; add linear-api tutorial (closes #135) --- tutorials/linear-api/Project.toml | 9 ++++ tutorials/linear-api/index.jmd | 73 +++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 tutorials/linear-api/Project.toml create mode 100644 tutorials/linear-api/index.jmd diff --git a/tutorials/linear-api/Project.toml b/tutorials/linear-api/Project.toml new file mode 100644 index 0000000..c2732ef --- /dev/null +++ b/tutorials/linear-api/Project.toml @@ -0,0 +1,9 @@ +[deps] +NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6" +NLPModelsTest = "7998695d-6960-4d3a-85c4-e1bceb8cd856" +LinearOperators = "5c8ed15e-5a4c-59e4-a42b-c7e8811fb125" + +[compat] +NLPModels = "0.20" +NLPModelsTest = "0.9" +LinearOperators = "2.2" diff --git a/tutorials/linear-api/index.jmd b/tutorials/linear-api/index.jmd new file mode 100644 index 0000000..2719ed2 --- /dev/null +++ b/tutorials/linear-api/index.jmd @@ -0,0 +1,73 @@ +--- +title: "Accessing Linear and Nonlinear Constraints (Linear API)" +tags: ["models", "linear", "constraints", "linear_api"] +author: "arnavk23" +--- + +# Accessing Linear and Nonlinear Constraints (Linear API) + +This short tutorial illustrates how to inspect and operate on the constraint Jacobian using the "linear API" utilities available in the NLPModels ecosystem. The linear API is useful when a problem contains both linear and nonlinear constraints and you want to test or operate on the Jacobian and related linear operators without materializing dense matrices. + +We demonstrate how to: + +- evaluate the constraint vector; +- obtain the Jacobian sparsity and coordinates; +- build the `LinearOperator` representation of the Jacobian and use `mul!` to compute Jacobian-vector products; and +- compare direct `jprod!`/`jtprod!` evaluations with the operator-based `mul!`. + +## Example using a test problem + +We use a problem from `NLPModelsTest` which may contain both linear and nonlinear constraints. The `NLPModelsTest` package exposes a collection of example problems useful for experimenting with the APIs. + +```julia +using NLPModelsTest, NLPModels, LinearOperators, LinearAlgebra + +# Load a test problem (choose one that has constraints) +list = NLPModelsTest.nlp_problems +nlp = eval(Symbol(list[7]))() + +# Point at which to evaluate +x = nlp.meta.x0 + +# Evaluate constraints +c = zeros(nlp.meta.ncon) +cons!(nlp, x, c) +println("c = ", c) + +# Get Jacobian sparsity and values +rows = zeros(Int, nlp.meta.nnzj) +cols = zeros(Int, nlp.meta.nnzj) +jac_structure!(nlp, rows, cols) +vals = zeros(Float64, nlp.meta.nnzj) +jac_coord!(nlp, x, vals) + +println("Jacobian nonzero pattern (first 10):") +println(hcat(rows[1:min(end,10)], cols[1:min(end,10)], vals[1:min(end,10)])) + +# Build a LinearOperator for the Jacobian (operator acts on variable-space vectors) +Jv = zeros(nlp.meta.ncon) +Jtv = zeros(nlp.meta.nvar) +J = jac_op!(nlp, x, Jv, Jtv) # returns a LinearOperator representing the Jacobian + +# Compare operator-based J*v with jprod! +v = randn(nlp.meta.nvar) +Jv_op = similar(Jv) +mul!(Jv_op, J, v) # operator-based product +Jv_direct = zeros(nlp.meta.ncon) +jprod!(nlp, x, v, Jv_direct) # direct Jacobian-vector product API + +println("||J*v (op) - J*v (jprod!)|| = ", norm(Jv_op - Jv_direct)) + +# And similarly for J'*w +w = randn(nlp.meta.ncon) +Jt_w_op = zeros(nlp.meta.nvar) +mul!(Jt_w_op, adjoint(J), w) +Jt_w_direct = zeros(nlp.meta.nvar) +jtprod!(nlp, x, w, Jt_w_direct) +println("||J' * w (op) - J' * w (jtprod!)|| = ", norm(Jt_w_op - Jt_w_direct)) +``` + +## Notes + +- The `jac_op!`/`hess_op!` helpers produce `LinearOperator` objects (see `LinearOperators.jl`), which allow efficient `mul!` operations without assembling dense matrices. +- The testing utilities in this repository expose a `linear_api` option (for example in `test_allocs_nlpmodels`) that enables checking the linear-specific functions; see the `Test allocations of NLPModels` tutorial for details. \ No newline at end of file From 11346c4ca06a7389157edc53046725a99cbe099a Mon Sep 17 00:00:00 2001 From: arnavk23 Date: Tue, 26 May 2026 14:35:44 +0530 Subject: [PATCH 6/7] Implemented exactly as suggested: the tutorial now uses deterministic vectors instead of randomness, so CI/doctest outputs are reproducible. --- tutorials/linear-api/index.jmd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorials/linear-api/index.jmd b/tutorials/linear-api/index.jmd index 2719ed2..e50507f 100644 --- a/tutorials/linear-api/index.jmd +++ b/tutorials/linear-api/index.jmd @@ -50,7 +50,7 @@ Jtv = zeros(nlp.meta.nvar) J = jac_op!(nlp, x, Jv, Jtv) # returns a LinearOperator representing the Jacobian # Compare operator-based J*v with jprod! -v = randn(nlp.meta.nvar) +v = ones(nlp.meta.nvar) Jv_op = similar(Jv) mul!(Jv_op, J, v) # operator-based product Jv_direct = zeros(nlp.meta.ncon) @@ -59,7 +59,7 @@ jprod!(nlp, x, v, Jv_direct) # direct Jacobian-vector product API println("||J*v (op) - J*v (jprod!)|| = ", norm(Jv_op - Jv_direct)) # And similarly for J'*w -w = randn(nlp.meta.ncon) +w = ones(nlp.meta.ncon) Jt_w_op = zeros(nlp.meta.nvar) mul!(Jt_w_op, adjoint(J), w) Jt_w_direct = zeros(nlp.meta.nvar) From 0ec91db844ca39bde740b0179a96f2eec036cc41 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Tue, 26 May 2026 19:58:21 +0530 Subject: [PATCH 7/7] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tutorials/linear-api/Project.toml | 1 + tutorials/linear-api/index.jmd | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/tutorials/linear-api/Project.toml b/tutorials/linear-api/Project.toml index c2732ef..b7d8d8a 100644 --- a/tutorials/linear-api/Project.toml +++ b/tutorials/linear-api/Project.toml @@ -4,6 +4,7 @@ NLPModelsTest = "7998695d-6960-4d3a-85c4-e1bceb8cd856" LinearOperators = "5c8ed15e-5a4c-59e4-a42b-c7e8811fb125" [compat] +julia = "1" NLPModels = "0.20" NLPModelsTest = "0.9" LinearOperators = "2.2" diff --git a/tutorials/linear-api/index.jmd b/tutorials/linear-api/index.jmd index 2719ed2..ca3a77f 100644 --- a/tutorials/linear-api/index.jmd +++ b/tutorials/linear-api/index.jmd @@ -17,14 +17,16 @@ We demonstrate how to: ## Example using a test problem -We use a problem from `NLPModelsTest` which may contain both linear and nonlinear constraints. The `NLPModelsTest` package exposes a collection of example problems useful for experimenting with the APIs. +We use the `HS21` problem from `NLPModelsTest`, a standard constrained Hock--Schittkowski test problem. Choosing it by name keeps this tutorial stable across package versions and ensures the example exercises the constraint/Jacobian APIs that the linear API is designed to inspect. ```julia using NLPModelsTest, NLPModels, LinearOperators, LinearAlgebra -# Load a test problem (choose one that has constraints) -list = NLPModelsTest.nlp_problems -nlp = eval(Symbol(list[7]))() +# Load a specific constrained test problem by name instead of relying on +# the ordering of `nlp_problems`, which can change across package versions. +problem_name = "HS21" +@assert problem_name in NLPModelsTest.nlp_problems "$(problem_name) is not available in NLPModelsTest.nlp_problems" +nlp = getfield(NLPModelsTest, Symbol(problem_name))() # Point at which to evaluate x = nlp.meta.x0