Skip to content

Commit 808b347

Browse files
committed
Introduce get_counters accessor
An accessor is more robust than accessing to an attribute explicitly, and allows concrete models to have Counters attributes that are not named `counters`. For backward compatibility, get_counters(model) = model.counters, though that may change in the future.
1 parent b057f72 commit 808b347

7 files changed

Lines changed: 51 additions & 20 deletions

File tree

docs/src/guidelines.md

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
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.
44

55
Table of contents:
6+
67
- [Bare minimum](@ref bare-minimum)
78
- [Expected behaviour](@ref expected-behaviour)
89
- [Advanced counters](@ref advanced-counters)
@@ -14,23 +15,28 @@ Your model should derive from `AbstractNLPModel` or some other abstract class de
1415
It is mandatory that it have a `meta :: NLPModelMeta` field, storing all the relevant problem information.
1516
The model also needs to provide `Counters` information. The easiest way is to define `counters :: Counters`.
1617
For instance:
18+
1719
```julia
1820
mutable struct MyModel{T, S} <: AbstractNLPModel{T, S}
1921
meta :: NLPModelMeta{T, S}
2022
counters :: Counters
2123
end
2224
```
25+
2326
For alternatives to storing `Counters` in the model, check [advanced counters](@ref advanced-counters).
2427
The minimum information that should be set for your model through `NLPModelMeta` is `nvar`, the number of variables.
2528
The following is a valid constructor for `MyModel`:
29+
2630
```julia
2731
function MyModel()
2832
return MyModel(NLPModelMeta(5), Counters())
2933
end
3034
```
35+
3136
More information can be passed to `NLPModelMeta`.
3237
See the full list [here](https://github.com/JuliaSmoothOptimizers/NLPModels.jl/blob/main/src/nlp/meta.jl#L32).
3338
The essential fields are
39+
3440
- `x0`: Starting point (defaults to `zeros`)
3541
- `lvar`, `uvar`: Bounds on the variables (default to `(-∞,∞)`)
3642
- `ncon`: Number of constraints (defaults to `0`)
@@ -43,6 +49,7 @@ Luckily, many have a default implementation.
4349
We collect here the list of functions that should be implemented for a complete API.
4450

4551
Here, the following notation applies:
52+
4653
- `nlp` is your instance of `MyModel <: AbstractNLPModel`
4754
- `x` is the point where the function is evaluated
4855
- `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.
112119

113120
To further specialize your model, you can also define `show_header` and possibly `show`.
114121
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
122+
115123
```julia
116124
show_header(io :: IO, nlp :: SlackModel) = println(io, "SlackModel - Model with slack variables")
117125
```
118-
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`:
126+
127+
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
128+
119129
```julia
120-
function show(io :: IO, nlp :: SlackModel)
130+
function show(io :: IO, nlp :: AbstractNLPModel)
121131
show_header(io, nlp)
122132
show(io, nlp.meta)
123-
show(io, nlp.model.counters)
133+
show(io, get_counters(nlp.model))
124134
end
125135
```
126136

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

129139
If a model does not implement `counters`, then it needs to define
140+
141+
- `get_counters(nlp)` - get the `Counters` object associated with `nlp`
130142
- `neval_xxx(nlp)` - get field `xxx` of `Counters`
131143
- `reset!(nlp)` - resetting all counters
132144
- `increment!(nlp, s)` - increment counter `s`
145+
133146
For instance
147+
134148
```julia
135149
for counter in fieldnames(Counters)
136150
@eval begin
@@ -144,19 +158,23 @@ function increment!(nlp :: MyModel, s :: Symbol)
144158
INCREMENT COUNTER s
145159
end
146160
```
161+
147162
One example of such model is the `SlackModel`, which stores an internal `model :: AbstractNLPModel`, thus defining
163+
148164
```julia
149165
$counter(nlp :: SlackModel) = $counter(nlp.model)
150166
reset!(nlp :: SlackModel) = reset!(nlp.model)
151167
increment!(nlp :: SlackModel, s :: Symbol) = increment!(nlp.model, s)
152168
```
169+
153170
This construction can be replicated calling the macro `@default_counters Model inner`.
154171
In the case of SlackModel, the equivalent call is
172+
155173
```julia
156174
@default_counters SlackModel model
157175
```
158176

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

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

src/nlp/counters.jl

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export Counters, sum_counters, increment!, decrement!, reset!
1+
export Counters, get_counters, sum_counters, increment!, decrement!, reset!
22

33
"""
44
Counters
@@ -39,14 +39,26 @@ mutable struct Counters
3939
end
4040

4141
# simple default API for retrieving counters
42+
43+
# For backward compatibility.
44+
# In the future, forcing this method to raise an error if not implemented
45+
# would be more robust.
46+
"""
47+
get_counters(nlp)
48+
49+
Return the `Counters` struct associated with `nlp`.
50+
By default, this is `nlp.counters`, but it can be overridden in concrete models.
51+
"""
52+
get_counters(nlp::AbstractNLPModel) = nlp.counters
53+
4254
for counter in fieldnames(Counters)
4355
@eval begin
4456
"""
4557
$($counter)(nlp)
4658
4759
Get the number of `$(split("$($counter)", "_")[2])` evaluations.
4860
"""
49-
$counter(nlp::AbstractNLPModel) = nlp.counters.$counter
61+
$counter(nlp::AbstractNLPModel) = get_counters(nlp).$counter
5062
export $counter
5163
end
5264
end
@@ -61,7 +73,7 @@ Increment counter `s` of problem `nlp`.
6173
end
6274

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

6779
"""
@@ -70,7 +82,8 @@ end
7082
Decrement counter `s` of problem `nlp`.
7183
"""
7284
function decrement!(nlp::AbstractNLPModel, s::Symbol)
73-
setproperty!(nlp.counters, s, getproperty(nlp.counters, s) - 1)
85+
counters = get_counters(nlp)
86+
setproperty!(counters, s, getproperty(counters, s) - 1)
7487
end
7588

7689
"""
@@ -92,7 +105,7 @@ end
92105
93106
Sum all counters of problem `nlp` except `cons`, `jac`, `jprod` and `jtprod`.
94107
"""
95-
sum_counters(nlp::AbstractNLPModel) = sum_counters(nlp.counters)
108+
sum_counters(nlp::AbstractNLPModel) = sum_counters(get_counters(nlp))
96109

97110
"""
98111
reset!(counters)
@@ -112,7 +125,7 @@ end
112125
Reset evaluation count and model data (if appropriate) in `nlp`.
113126
"""
114127
function LinearOperators.reset!(nlp::AbstractNLPModel)
115-
reset!(nlp.counters)
128+
reset!(get_counters(nlp))
116129
reset_data!(nlp)
117130
return nlp
118131
end

src/nlp/show.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ show_header(io::IO, nlp::AbstractNLPModel) = println(io, typeof(nlp))
1111
function Base.show(io::IO, nlp::AbstractNLPModel)
1212
show_header(io, nlp)
1313
show(io, nlp.meta)
14-
show(io, nlp.counters)
14+
show(io, get_counters(nlp))
1515
end
1616

1717
"""

src/nlp/utils.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ macro default_counters(Model, inner, excluded = :())
162162
ex.args,
163163
:(
164164
Base.getproperty(nlp::$(esc(Model)), s::Symbol) =
165-
(s == :counters ? nlp.$inner.counters : getfield(nlp, s))
165+
(s == :counters ? get_counters(nlp.$inner) : getfield(nlp, s))
166166
),
167167
)
168168
ex

src/nls/counters.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ function sum_counters(c::NLSCounters)
5151
end
5252
return s
5353
end
54-
sum_counters(nls::AbstractNLSModel) = sum_counters(nls.counters)
54+
sum_counters(nls::AbstractNLSModel) = sum_counters(get_counters(nls))
5555

5656
for counter in fieldnames(NLSCounters)
5757
counter == :counters && continue
@@ -61,14 +61,14 @@ for counter in fieldnames(NLSCounters)
6161
6262
Get the number of `$(split("$($counter)", "_")[2])` evaluations.
6363
"""
64-
$counter(nls::AbstractNLSModel) = nls.counters.$counter
64+
$counter(nls::AbstractNLSModel) = get_counters(nls).$counter
6565
export $counter
6666
end
6767
end
6868

6969
for counter in fieldnames(Counters)
7070
@eval begin
71-
$counter(nls::AbstractNLSModel) = nls.counters.counters.$counter
71+
$counter(nls::AbstractNLSModel) = get_counters(nls).counters.$counter
7272
export $counter
7373
end
7474
end
@@ -84,16 +84,16 @@ end
8484

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

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

9595
function LinearOperators.reset!(nls::AbstractNLSModel)
96-
reset!(nls.counters)
96+
reset!(get_counters(nls))
9797
return nls
9898
end
9999

src/nls/show.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
function Base.show(io::IO, nls::AbstractNLSModel)
22
show_header(io, nls)
33
show(io, nls.meta, nls.nls_meta)
4-
show(io, nls.counters)
4+
show(io, get_counters(nls))
55
end
66

77
"""

test/nlp/utils.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ end
2222
nlp = SuperNLPModel{Float64, Vector{Float64}}(SimpleNLPModel())
2323
increment!(nlp, :neval_obj)
2424
@test neval_obj(nlp.model) == 1
25-
@test nlp.counters == nlp.model.counters
25+
@test get_counters(nlp) == get_counters(nlp.model)
2626
@test neval_hprod(nlp) == 0 # because counters are forwarded, even though neval_hprod has not been forwarded
2727
end

0 commit comments

Comments
 (0)