diff --git a/docs/src/guidelines.md b/docs/src/guidelines.md index 8562e197..4239168c 100644 --- a/docs/src/guidelines.md +++ b/docs/src/guidelines.md @@ -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) @@ -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`) @@ -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) @@ -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` + For instance + ```julia for counter in fieldnames(Counters) @eval begin @@ -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) diff --git a/src/nlp/counters.jl b/src/nlp/counters.jl index e99df3e2..6f1832eb 100644 --- a/src/nlp/counters.jl +++ b/src/nlp/counters.jl @@ -1,4 +1,4 @@ -export Counters, sum_counters, increment!, decrement!, reset! +export Counters, get_counters, sum_counters, increment!, decrement!, reset! """ Counters @@ -39,6 +39,18 @@ 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 + for counter in fieldnames(Counters) @eval begin """ @@ -46,7 +58,7 @@ for counter in fieldnames(Counters) 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 @@ -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 """ @@ -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 """ @@ -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) @@ -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 diff --git a/src/nlp/show.jl b/src/nlp/show.jl index 63e145cc..6de39f43 100644 --- a/src/nlp/show.jl +++ b/src/nlp/show.jl @@ -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 """ diff --git a/src/nlp/utils.jl b/src/nlp/utils.jl index 1f576f63..e5f2d4a1 100644 --- a/src/nlp/utils.jl +++ b/src/nlp/utils.jl @@ -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 diff --git a/src/nls/counters.jl b/src/nls/counters.jl index f6be41de..2807e002 100644 --- a/src/nls/counters.jl +++ b/src/nls/counters.jl @@ -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 @@ -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 @@ -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 diff --git a/src/nls/show.jl b/src/nls/show.jl index 1b1bb96a..0765567f 100644 --- a/src/nls/show.jl +++ b/src/nls/show.jl @@ -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 """ diff --git a/test/nlp/utils.jl b/test/nlp/utils.jl index 36e54776..b76b98a8 100644 --- a/test/nlp/utils.jl +++ b/test/nlp/utils.jl @@ -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