Skip to content

Commit 698f055

Browse files
committed
define RegularizedOptimizationModel type
A type representing the problem as a whole is necessary in order to run benchmarks with SolverBenchmark.jl.
1 parent 85de184 commit 698f055

12 files changed

+258
-52
lines changed

Project.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@ MLDatasets = "^0.7.4"
1919
NLPModels = "0.16, 0.17, 0.18, 0.19, 0.20"
2020
Noise = "0.2"
2121
Requires = "1"
22-
julia = "^1.3.0"
22+
julia = "^1.6.0"
2323

2424
[extras]
2525
ADNLPModels = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a"
2626
DifferentialEquations = "0c46a032-eb83-5123-abaf-570d42b7fbaa"
2727
MLDatasets = "eb30cadb-4394-5ae3-aed4-317e484a6458"
28+
ProximalOperators = "a725b495-10eb-56fe-b38b-717eba820537"
2829
QuadraticModels = "f468eda6-eac5-11e8-05a5-ff9e497bcd19"
2930
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
3031

3132
[targets]
32-
test = ["ADNLPModels", "DifferentialEquations", "MLDatasets", "QuadraticModels", "Test"]
33+
test = ["ADNLPModels", "DifferentialEquations", "MLDatasets", "ProximalOperators", "QuadraticModels", "Test"]

src/RegularizedProblems.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ include("group_lasso_model.jl")
1414
include("nnmf.jl")
1515

1616
function __init__()
17+
@require ProximalOperators = "a725b495-10eb-56fe-b38b-717eba820537" begin
18+
include("testset_bpdn.jl")
19+
include("testset_lrcomp.jl")
20+
include("testset_matrand.jl")
21+
include("testset_group_lasso.jl")
22+
end
1723
@require ADNLPModels = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a" begin
1824
@require DifferentialEquations = "0c46a032-eb83-5123-abaf-570d42b7fbaa" begin
1925
include("fh_model.jl")

src/group_lasso_model.jl

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
11
export group_lasso_model
22

3-
function group_lasso_data(m::Int, n::Int, g::Int, ag::Int, noise::Float64 = 0.01)
4-
(m n) || error("number of rows ($m) should be ≤ number of columns ($n)")
5-
(mod(n, g) == 0) || error("number of groups ($g) must divide evenly into number of rows ($n)")
6-
(ag g) || error("number of active groups ($ag) must be smaller than the number of groups ($g)")
7-
3+
function group_lasso_data(;
4+
m::Int = 200,
5+
n::Int = 512,
6+
g::Int = 16,
7+
ag::Int = 5,
8+
noise::Float64 = 0.01,
9+
compound::Int = 1,
10+
)
11+
m n || error("number of rows ($m) should be ≤ number of columns ($n)")
12+
mod(n, g) == 0 || error("number of groups ($g) must divide evenly into number of rows ($n)")
13+
ag g || error("number of active groups ($ag) must be smaller than the number of groups ($g)")
14+
compound > 0 || error("compound factor must be positive")
15+
16+
m = compound * m
17+
n = compound * n
18+
g = compound * g
19+
ag = compound * ag
820
x0 = zeros(n)
921
active_groups = sort(randperm(g)[1:ag]) # pick out active groups
1022
group_eles = Int(n / g) # get number of elements in a group
@@ -25,12 +37,8 @@ function group_lasso_data(m::Int, n::Int, g::Int, ag::Int, noise::Float64 = 0.01
2537
A, b, b0, x0, g, active_groups, indset
2638
end
2739

28-
group_lasso_data(compound::Int = 1, args...) =
29-
group_lasso_data(200 * compound, 512 * compound, 16 * compound, 5 * compound, args...)
30-
3140
"""
32-
model, nls_model, sol = group_lasso_model(args...)
33-
model, nls_model, sol = group_lasso_model(compound = 1, args...)
41+
model, nls_model, sol = group_lasso_model(; kwargs...)
3442
3543
Return an instance of an `NLPModel` and `NLSModel` representing the group-lasso
3644
problem, i.e., the under-determined linear least-squares objective
@@ -42,28 +50,23 @@ vector following a normal distribution with mean zero and standard deviation σ.
4250
Note that with this format, all groups have a the same number of elements and the number of
4351
groups divides evenly into the total number of elements.
4452
45-
## Arguments
46-
47-
* `m :: Int`: the number of rows of A
48-
* `n :: Int`: the number of columns of A (with `n` ≥ `m`)
49-
* `g :: Int : the number of groups`
50-
* `ag :: Array{Int}`: group-index denoting which groups are active (with `max(ag) ≤ g`), i.e. `[1, 4, 5]` when there are 7 groups
51-
* `noise :: Float64`: noise amount ϵ (default: 0.01).
52-
53-
The second form calls the first form with arguments
53+
## Keyword Arguments
5454
55-
m = 200 * compound
56-
n = 512 * compound
57-
k = 10 * compound
55+
* `m :: Int`: the number of rows of A (default: 200)
56+
* `n :: Int`: the number of columns of A, with `n` ≥ `m` (default: 512)
57+
* `g :: Int`: the number of groups (default: 16)
58+
* `ag :: Int`: the number of active groups (default: 5)
59+
* `noise :: Float64`: noise amount (default: 0.01)
60+
* `compound :: Int`: multiplier for `m`, `n`, `g`, and `ag` (default: 1).
5861
5962
## Return Value
6063
6164
An instance of a `FirstOrderModel` that represents the group-lasso problem.
6265
An instance of a `FirstOrderNLSModel` that represents the group-lasso problem.
63-
Also returns true x, number of groups g, group-index denoting which groups are active, and a Matrix where rows are group indices of x
66+
Also returns true x, number of groups g, group-index denoting which groups are active, and a Matrix where rows are group indices of x.
6467
"""
65-
function group_lasso_model(args...)
66-
A, b, b0, x0, g, active_groups, indset = group_lasso_data(args...)
68+
function group_lasso_model(args...; kwargs...)
69+
A, b, b0, x0, g, active_groups, indset = group_lasso_data(args...; kwargs...)
6770
r = similar(b)
6871

6972
function resid!(r, x)

src/lrcomp_model.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ function lrcomp_data(m::Int, n::Int; T::DataType = Float64)
55
A
66
end
77

8-
function lrcomp_model(m::Int, n::Int; T::DataType = Float64)
8+
function lrcomp_model(; m::Int = 100, n::Int = 100, T::DataType = Float64)
99
A = lrcomp_data(m, n, T = T)
1010
r = vec(similar(A))
1111

src/matrand_model.jl

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ function mat_rand(m::Int, n::Int, r::Int, sr::Float64, va::Float64, vb::Float64,
77
Ω = findall(<(sr), rand(m, n))
88
B = xs[Ω]
99
B = (1 - c) * add_gauss(B, va, 0; clip = true) + c * add_gauss(B, vb, 0; clip = true)
10-
ω = zeros(Int64, size(Ω, 1)) # Vectorize Omega
10+
ω = zeros(Int64, size(Ω, 1)) # Vectorize Omega
1111
for i = 1:size(Ω, 1)
1212
ω[i] = Ω[i][1] + size(Ω, 2) * (Ω[i][2] - 1)
1313
end
@@ -44,7 +44,7 @@ function matrix_completion_model(xs, B, ω)
4444
end
4545

4646
"""
47-
model, nls_model, sol = random_matrix_completion_model(args...)
47+
model, nls_model, sol = random_matrix_completion_model(; kwargs...)
4848
4949
Return an instance of an `NLPModel` and an instance of an `NLSModel` representing
5050
the same matrix completion problem, i.e., the square linear least-squares objective
@@ -55,38 +55,38 @@ in the Frobenius norm, where X is the unknown image represented as an m x n matr
5555
A is a fixed image, and the operator P only retains a certain subset of pixels of
5656
X and A.
5757
58-
## Arguments
58+
## Keyword Arguments
5959
60-
* `m :: Int`: the number of rows of X and A
61-
* `n :: Int`: the number of columns of X and A
62-
* `r :: Int`: the desired rank of A
60+
* `m :: Int`: the number of rows of X and A (default: 100)
61+
* `n :: Int`: the number of columns of X and A (default: 100)
62+
* `r :: Int`: the desired rank of A (default: 5)
6363
* `sr :: AbstractFloat`: a threshold between 0 and 1 used to determine the set of pixels
64-
retained by the operator P
65-
* `va :: AbstractFloat`: the variance of a first Gaussian perturbation to be applied to A
66-
* `vb :: AbstractFloat`: the variance of a second Gaussian perturbation to be applied to A
67-
* `c :: AbstractFloat`: the coefficient of the convex combination of the two Gaussian perturbations.
64+
retained by the operator P (default: 0.8)
65+
* `va :: AbstractFloat`: the variance of a first Gaussian perturbation to be applied to A (default: 1.0e-4)
66+
* `vb :: AbstractFloat`: the variance of a second Gaussian perturbation to be applied to A (default: 1.0e-2)
67+
* `c :: AbstractFloat`: the coefficient of the convex combination of the two Gaussian perturbations (default: 0.2).
6868
6969
## Return Value
7070
7171
An instance of a `FirstOrderModel` and of a `FirstOrderNLSModel` that represent the same
7272
matrix completion problem, and the exact solution.
7373
"""
74-
function random_matrix_completion_model(
75-
m::Int,
76-
n::Int,
77-
r::Int,
78-
sr::R,
79-
va::R,
80-
vb::R,
81-
c::R,
82-
) where {R <: AbstractFloat}
74+
function random_matrix_completion_model(;
75+
m::Int = 100,
76+
n::Int = 100,
77+
r::Int = 5,
78+
sr::Float64 = 0.8,
79+
va::Float64 = 1.0e-4,
80+
vb::Float64 = 1.0e-2,
81+
c::Float64 = 0.2,
82+
)
8383
xs, B, ω = mat_rand(m, n, r, sr, va, vb, c)
8484
matrix_completion_model(xs, B, ω)
8585
end
8686

8787
function perturb(I, c = 0.8, p = 0.8)
8888
Ω = findall(<(p), rand(256, 256))
89-
ω = zeros(Int, size(Ω, 1)) # Vectorize Omega
89+
ω = zeros(Int, size(Ω, 1)) # Vectorize Omega
9090
for i = 1:size(Ω, 1)
9191
ω[i] = Ω[i][1] + 256 * (Ω[i][2] - 1)
9292
end
@@ -98,7 +98,7 @@ function perturb(I, c = 0.8, p = 0.8)
9898
end
9999

100100
"""
101-
model, nls_model, sol = MIT_matrix_completion_model(args...)
101+
model, nls_model, sol = MIT_matrix_completion_model()
102102
103103
A special case of matrix completion problem in which the exact image is a noisy
104104
MIT logo.

src/testset_bpdn.jl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Predefine a set of common problem instances.
2+
export setup_bpdn_l0, setup_bpdn_l1, setup_bpdn_B0
3+
4+
function setup_bpdn_l0(args...; kwargs...)
5+
model, nls_model, _ = bpdn_model(args...)
6+
λ = norm(grad(model, zeros(model.meta.nvar)), Inf) / 10
7+
h = NormL0(λ)
8+
return RegularizedNLPModel(model, h), RegularizedNLSModel(nls_model, h)
9+
end
10+
11+
function setup_bpdn_l1(args...; kwargs...)
12+
model, nls_model, _ = bpdn_model(args...)
13+
λ = norm(grad(model, zeros(model.meta.nvar)), Inf) / 10
14+
h = NormL1(λ)
15+
return RegularizedNLPModel(model, h), RegularizedNLSModel(nls_model, h)
16+
end
17+
18+
function setup_bpdn_B0(compound = 1, args...; kwargs...)
19+
model, nls_model, _ = bpdn_model(compound, args...)
20+
h = IndBallL0(10 * compound)
21+
return RegularizedNLPModel(model, h), RegularizedNLSModel(nls_model, h)
22+
end

src/testset_group_lasso.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Predefine a set of common problem instances.
2+
export setup_group_lasso_l12
3+
4+
function setup_group_lasso_l12(args...; kwargs...)
5+
model, nls_model, ng, _, idx = group_lasso_model(; kwargs...)
6+
idx = [idx[i, :] for i = 1:ng]
7+
λ = 0.2 * ones(ng)
8+
h = GroupNormL2(λ, idx)
9+
return RegularizedNLPModel(model, h), RegularizedNLSModel(nls_model, h)
10+
end
11+

src/testset_lrcomp.jl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Predefine a set of common problem instances.
2+
export setup_lrcomp_rank, setup_lrcomp_nuclear
3+
4+
function setup_lrcomp_rank(args...; kwargs...)
5+
model, nls_model, _ = lrcomp_model(args...; kwargs...)
6+
λ = 0.1
7+
h = Rank(λ)
8+
return RegularizedNLPModel(model, h), RegularizedNLSModel(nls_model, h)
9+
end
10+
11+
function setup_lrcomp_nuclear(args...; kwargs...)
12+
model, nls_model, _ = lrcomp_model(args...; kwargs...)
13+
λ = 0.1
14+
h = NuclearNorm(λ)
15+
return RegularizedNLPModel(model, h), RegularizedNLSModel(nls_model, h)
16+
end

src/testset_matrand.jl

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Predefine a set of common problem instances.
2+
export setup_random_completion_rank, setup_random_completion_nuclear
3+
export setup_mit_completion_rank, setup_mit_completion_nuclear
4+
5+
function setup_random_completion_rank(args...; kwargs...)
6+
model, nls_model, _ = random_matrix_completion_model(; kwargs...)
7+
λ = 0.1
8+
h = Rank(λ)
9+
return RegularizedNLPModel(model, h), RegularizedNLSModel(nls_model, h)
10+
end
11+
12+
function setup_random_completion_nuclear(args...; kwargs...)
13+
model, nls_model, _ = random_matrix_completion_model(; kwargs...)
14+
λ = 0.1
15+
h = NuclearNorm(λ)
16+
return RegularizedNLPModel(model, h), RegularizedNLSModel(nls_model, h)
17+
end
18+
19+
function setup_mit_completion_rank(args...; kwargs...)
20+
model, nls_model, _ = MIT_matrix_completion_model()
21+
λ = 0.1
22+
h = Rank(λ)
23+
return RegularizedNLPModel(model, h), RegularizedNLSModel(nls_model, h)
24+
end
25+
26+
function setup_mit_completion_nuclear(args...; kwargs...)
27+
model, nls_model, _ = MIT_matrix_completion_model()
28+
λ = 0.1
29+
h = NuclearNorm(λ)
30+
return RegularizedNLPModel(model, h), RegularizedNLSModel(nls_model, h)
31+
end

src/types.jl

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
export FirstOrderModel, FirstOrderNLSModel
1+
export FirstOrderModel,
2+
FirstOrderNLSModel, AbstractRegularizedNLPModel, RegularizedNLPModel, RegularizedNLSModel
23

34
"""
45
model = FirstOrderModel(f, ∇f!; name = "first-order model")
@@ -131,3 +132,98 @@ function NLPModels.jtprod_residual!(
131132
nls.jtprod_resid!(Jtv, x, v)
132133
Jtv
133134
end
135+
136+
abstract type AbstractRegularizedNLPModel{T, S} <: AbstractNLPModel{T, S} end
137+
138+
"""
139+
rmodel = RegularizedNLPModel(model, regularizer)
140+
rmodel = RegularizedNLSModel(model, regularizer)
141+
142+
An aggregate type to represent a regularized optimization model, .i.e.,
143+
of the form
144+
145+
minimize f(x) + h(x),
146+
147+
where f is smooth (and is usually assumed to have Lipschitz-continuous gradient),
148+
and h is lower semi-continuous (and may have to be prox-bounded).
149+
150+
The regularized model is made of
151+
152+
- `model <: AbstractNLPModel`: the smooth part of the model, for example a `FirstOrderModel`
153+
- `h`: the nonsmooth part of the model; typically a regularizer defined in `ProximalOperators.jl`
154+
- `selected`: the subset of variables to which the regularizer h should be applied (default: all).
155+
156+
This aggregate type can be used to call solvers with a single object representing the
157+
model, but is especially useful for use with SolverBenchmark.jl, which expects problems
158+
to be defined by a single object.
159+
"""
160+
mutable struct RegularizedNLPModel{T, S, M <: AbstractNLPModel{T, S}, H, I} <:
161+
AbstractRegularizedNLPModel{T, S}
162+
model::M # smooth model
163+
h::H # regularizer
164+
selected::I # set of variables to which the regularizer should be applied
165+
end
166+
167+
function RegularizedNLPModel(model::AbstractNLPModel{T, S}, h::H) where {T, S, H}
168+
selected = 1:get_nvar(model)
169+
RegularizedNLPModel{T, S, typeof(model), typeof(h), typeof(selected)}(model, h, selected)
170+
end
171+
172+
mutable struct RegularizedNLSModel{T, S, M <: AbstractNLSModel{T, S}, H, I} <:
173+
AbstractRegularizedNLPModel{T, S}
174+
model::M # smooth model
175+
h::H # regularizer
176+
selected::I # set of variables to which the regularizer should be applied
177+
end
178+
179+
function RegularizedNLSModel(model::AbstractNLSModel{T, S}, h::H) where {T, S, H}
180+
selected = 1:get_nvar(model)
181+
RegularizedNLSModel{T, S, typeof(model), typeof(h), typeof(selected)}(model, h, selected)
182+
end
183+
184+
function NLPModels.obj(rnlp::AbstractRegularizedNLPModel, x::AbstractVector)
185+
# The size check on x will be performed when evaluating the smooth model.
186+
# We intentionally do not increment an objective evaluation counter here
187+
# because the relevant counters are inside the smooth term.
188+
obj(rnlp.model, x) + rnlp.h(x)
189+
end
190+
191+
# Forward meta getters so they grab info from the smooth model
192+
for field fieldnames(NLPModels.NLPModelMeta)
193+
meth = Symbol("get_", field)
194+
if field == :name
195+
@eval NLPModels.$meth(rnlp::RegularizedNLPModel) =
196+
NLPModels.$meth(rnlp.model) * "/" * string(typeof(rnlp.h).name.wrapper)
197+
@eval NLPModels.$meth(rnls::RegularizedNLSModel) =
198+
NLPModels.$meth(rnls.model) * "/" * string(typeof(rnls.h).name.wrapper)
199+
else
200+
@eval NLPModels.$meth(rnlp::RegularizedNLPModel) = NLPModels.$meth(rnlp.model)
201+
end
202+
end
203+
204+
for field in fieldnames(NLPModels.NLSMeta)
205+
meth = Symbol("get_", field)
206+
@eval NLPModels.$meth(rnls::RegularizedNLSModel) = NLPModels.$meth(rnls.model)
207+
end
208+
209+
# Forward counter getters so they grab info from the smooth model
210+
for model_type (RegularizedNLPModel, RegularizedNLSModel)
211+
for counter in fieldnames(Counters)
212+
@eval NLPModels.$counter(rnlp::$model_type) = NLPModels.$counter(rnlp.model)
213+
end
214+
end
215+
216+
for counter in fieldnames(NLSCounters)
217+
counter == :counters && continue
218+
@eval NLPModels.$counter(rnls::RegularizedNLSModel) = NLPModels.$counter(rnls.model)
219+
end
220+
221+
# simple show method for now
222+
function Base.show(io::IO, rnlp::AbstractRegularizedNLPModel)
223+
print(io, "Smooth model: ")
224+
show(io, rnlp.model)
225+
print(io, "\nRegularizer: ")
226+
show(io, rnlp.h)
227+
print(io, "\n\nSelected variables: ")
228+
show(io, rnlp.selected)
229+
end

0 commit comments

Comments
 (0)