Skip to content
9 changes: 9 additions & 0 deletions tutorials/linear-api/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[deps]
NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6"
NLPModelsTest = "7998695d-6960-4d3a-85c4-e1bceb8cd856"
LinearOperators = "5c8ed15e-5a4c-59e4-a42b-c7e8811fb125"

[compat]
Comment thread
arnavk23 marked this conversation as resolved.
NLPModels = "0.20"
NLPModelsTest = "0.9"
LinearOperators = "2.2"
73 changes: 73 additions & 0 deletions tutorials/linear-api/index.jmd
Original file line number Diff line number Diff line change
@@ -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]))()
Comment thread
arnavk23 marked this conversation as resolved.
Outdated
Comment thread
arnavk23 marked this conversation as resolved.
Outdated

# 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)
Comment thread
arnavk23 marked this conversation as resolved.
Outdated
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)
Comment thread
arnavk23 marked this conversation as resolved.
Outdated
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.