Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions docs/src/guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
These are guidelines for the creation of models using NLPModels to help keeping the models uniform, and for future reference in the creation of solvers.

Table of contents:

- [Bare minimum](@ref bare-minimum)
- [Expected behaviour](@ref expected-behaviour)
- [Advanced counters](@ref advanced-counters)
Expand All @@ -14,23 +15,28 @@ Your model should derive from `AbstractNLPModel` or some other abstract class de
It is mandatory that it have a `meta :: NLPModelMeta` field, storing all the relevant problem information.
The model also needs to provide `Counters` information. The easiest way is to define `counters :: Counters`.
For instance:

```julia
mutable struct MyModel{T, S} <: AbstractNLPModel{T, S}
meta :: NLPModelMeta{T, S}
counters :: Counters
end
```

For alternatives to storing `Counters` in the model, check [advanced counters](@ref advanced-counters).
The minimum information that should be set for your model through `NLPModelMeta` is `nvar`, the number of variables.
The following is a valid constructor for `MyModel`:

```julia
function MyModel()
return MyModel(NLPModelMeta(5), Counters())
end
```

More information can be passed to `NLPModelMeta`.
See the full list [here](https://github.com/JuliaSmoothOptimizers/NLPModels.jl/blob/main/src/nlp/meta.jl#L32).
The essential fields are

- `x0`: Starting point (defaults to `zeros`)
- `lvar`, `uvar`: Bounds on the variables (default to `(-∞,∞)`)
- `ncon`: Number of constraints (defaults to `0`)
Expand All @@ -43,6 +49,7 @@ Luckily, many have a default implementation.
We collect here the list of functions that should be implemented for a complete API.

Here, the following notation applies:

- `nlp` is your instance of `MyModel <: AbstractNLPModel`
- `x` is the point where the function is evaluated
- `y` is the vector of Lagrange multipliers (for constrained problems only)
Expand Down Expand Up @@ -112,25 +119,32 @@ The following is a non-exhaustive list of expected behaviour for methods.

To further specialize your model, you can also define `show_header` and possibly `show`.
The default `show_header` simply prints the `typeof` the NLPModel, so it should be specialized with the specific information that you prefer. For instance, `SlackModel` defines

```julia
show_header(io :: IO, nlp :: SlackModel) = println(io, "SlackModel - Model with slack variables")
```
Furthermore, we define a general `show` that calls `show_header` and specific `show` functions for the `meta` and the `counters`. If your model does not have `counters` in the default location, you must define `show` for them as well. Alternatively, you may desire to change the behaviour of show. Here is an example, again from `SlackModel`:

Furthermore, we define a general `show` that calls `show_header` and specific `show` functions for the `meta` and the `counters`. If your model does not have `counters` in the default location, you must define `get_counters` for them as well. Alternatively, you may desire to change the behaviour of show. The default behaviour is

```julia
function show(io :: IO, nlp :: SlackModel)
function show(io :: IO, nlp :: AbstractNLPModel)
show_header(io, nlp)
show(io, nlp.meta)
show(io, nlp.model.counters)
show(io, get_counters(nlp.model))
end
```

## [Advanced counters](@id advanced-counters)

If a model does not implement `counters`, then it needs to define

- `get_counters(nlp)` - get the `Counters` object associated with `nlp`
- `neval_xxx(nlp)` - get field `xxx` of `Counters`
- `reset!(nlp)` - resetting all counters
- `increment!(nlp, s)` - increment counter `s`
Comment on lines 139 to 144

Copilot AI Mar 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "Advanced counters" guidance is now misleading: a model can store counters under a different field name and just implement get_counters(nlp) to reuse the existing neval_*, increment!, reset!, etc. implementations. Consider updating this section to recommend overriding get_counters first, and only implementing all neval_xxx/reset!/increment! manually when the counters are not representable as a Counters object.

Copilot uses AI. Check for mistakes.

For instance

```julia
for counter in fieldnames(Counters)
@eval begin
Expand All @@ -144,19 +158,23 @@ function increment!(nlp :: MyModel, s :: Symbol)
INCREMENT COUNTER s
end
```

One example of such model is the `SlackModel`, which stores an internal `model :: AbstractNLPModel`, thus defining

```julia
$counter(nlp :: SlackModel) = $counter(nlp.model)
reset!(nlp :: SlackModel) = reset!(nlp.model)
increment!(nlp :: SlackModel, s :: Symbol) = increment!(nlp.model, s)
```

This construction can be replicated calling the macro `@default_counters Model inner`.
In the case of SlackModel, the equivalent call is

```julia
@default_counters SlackModel model
```

Furthermore, the `show` method has to be updated with the correct direction of `counter`. See [show](@ref show) for more information.
Furthermore, the `show` method may have to be updated with the correct direction of `counter`. See [show](@ref show) for more information.

## [Advanced tests](@id advanced-tests)

Expand Down
25 changes: 19 additions & 6 deletions src/nlp/counters.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export Counters, sum_counters, increment!, decrement!, reset!
export Counters, get_counters, sum_counters, increment!, decrement!, reset!

"""
Counters
Expand Down Expand Up @@ -39,14 +39,26 @@ mutable struct Counters
end

# simple default API for retrieving counters

# For backward compatibility.
# In the future, forcing this method to raise an error if not implemented
# would be more robust.
"""
get_counters(nlp)

Return the `Counters` struct associated with `nlp`.
By default, this is `nlp.counters`, but it can be overridden in concrete models.
"""
get_counters(nlp::AbstractNLPModel) = nlp.counters
Comment thread
dpo marked this conversation as resolved.

for counter in fieldnames(Counters)
@eval begin
"""
$($counter)(nlp)

Get the number of `$(split("$($counter)", "_")[2])` evaluations.
"""
$counter(nlp::AbstractNLPModel) = nlp.counters.$counter
$counter(nlp::AbstractNLPModel) = get_counters(nlp).$counter
export $counter
end
end
Expand All @@ -61,7 +73,7 @@ Increment counter `s` of problem `nlp`.
end

for fun in fieldnames(Counters)
@eval increment!(nlp::AbstractNLPModel, ::Val{$(Meta.quot(fun))}) = nlp.counters.$fun += 1
@eval increment!(nlp::AbstractNLPModel, ::Val{$(Meta.quot(fun))}) = get_counters(nlp).$fun += 1
end

"""
Expand All @@ -70,7 +82,8 @@ end
Decrement counter `s` of problem `nlp`.
"""
function decrement!(nlp::AbstractNLPModel, s::Symbol)
setproperty!(nlp.counters, s, getproperty(nlp.counters, s) - 1)
counters = get_counters(nlp)
setproperty!(counters, s, getproperty(counters, s) - 1)
end

"""
Expand All @@ -92,7 +105,7 @@ end

Sum all counters of problem `nlp` except `cons`, `jac`, `jprod` and `jtprod`.
"""
sum_counters(nlp::AbstractNLPModel) = sum_counters(nlp.counters)
sum_counters(nlp::AbstractNLPModel) = sum_counters(get_counters(nlp))

"""
reset!(counters)
Expand All @@ -112,7 +125,7 @@ end
Reset evaluation count and model data (if appropriate) in `nlp`.
"""
function LinearOperators.reset!(nlp::AbstractNLPModel)
reset!(nlp.counters)
reset!(get_counters(nlp))
reset_data!(nlp)
return nlp
end
2 changes: 1 addition & 1 deletion src/nlp/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ show_header(io::IO, nlp::AbstractNLPModel) = println(io, typeof(nlp))
function Base.show(io::IO, nlp::AbstractNLPModel)
show_header(io, nlp)
show(io, nlp.meta)
show(io, nlp.counters)
show(io, get_counters(nlp))
end

"""
Expand Down
2 changes: 1 addition & 1 deletion src/nlp/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ macro default_counters(Model, inner, excluded = :())
ex.args,
:(
Base.getproperty(nlp::$(esc(Model)), s::Symbol) =
(s == :counters ? nlp.$inner.counters : getfield(nlp, s))
(s == :counters ? get_counters(nlp.$inner) : getfield(nlp, s))
),
)
ex
Expand Down
12 changes: 6 additions & 6 deletions src/nls/counters.jl
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ function sum_counters(c::NLSCounters)
end
return s
end
sum_counters(nls::AbstractNLSModel) = sum_counters(nls.counters)
sum_counters(nls::AbstractNLSModel) = sum_counters(get_counters(nls))

for counter in fieldnames(NLSCounters)
counter == :counters && continue
Expand All @@ -61,14 +61,14 @@ for counter in fieldnames(NLSCounters)

Get the number of `$(split("$($counter)", "_")[2])` evaluations.
"""
$counter(nls::AbstractNLSModel) = nls.counters.$counter
$counter(nls::AbstractNLSModel) = get_counters(nls).$counter
export $counter
end
end

for counter in fieldnames(Counters)
@eval begin
$counter(nls::AbstractNLSModel) = nls.counters.counters.$counter
$counter(nls::AbstractNLSModel) = get_counters(nls).counters.$counter
export $counter
end
end
Expand All @@ -84,16 +84,16 @@ end

for fun in fieldnames(NLSCounters)
fun == :counters && continue
@eval increment!(nls::AbstractNLSModel, ::Val{$(Meta.quot(fun))}) = nls.counters.$fun += 1
@eval increment!(nls::AbstractNLSModel, ::Val{$(Meta.quot(fun))}) = get_counters(nls).$fun += 1
end

for fun in fieldnames(Counters)
@eval $NLPModels.increment!(nls::AbstractNLSModel, ::Val{$(Meta.quot(fun))}) =
nls.counters.counters.$fun += 1
get_counters(nls).counters.$fun += 1
end

function LinearOperators.reset!(nls::AbstractNLSModel)
reset!(nls.counters)
reset!(get_counters(nls))
return nls
end

Expand Down
2 changes: 1 addition & 1 deletion src/nls/show.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
function Base.show(io::IO, nls::AbstractNLSModel)
show_header(io, nls)
show(io, nls.meta, nls.nls_meta)
show(io, nls.counters)
show(io, get_counters(nls))
end

"""
Expand Down
2 changes: 1 addition & 1 deletion test/nlp/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ end
nlp = SuperNLPModel{Float64, Vector{Float64}}(SimpleNLPModel())
increment!(nlp, :neval_obj)
@test neval_obj(nlp.model) == 1
@test nlp.counters == nlp.model.counters
@test get_counters(nlp) == get_counters(nlp.model)
@test neval_hprod(nlp) == 0 # because counters are forwarded, even though neval_hprod has not been forwarded
end
Loading