From d3331e554d0de12b8465316b3aa1eebf7a559d2b Mon Sep 17 00:00:00 2001 From: MaxenceGollier Date: Mon, 2 Mar 2026 10:46:58 -0500 Subject: [PATCH 01/12] add NullRegularizer --- src/ShiftedProximalOperators.jl | 1 + src/null.jl | 54 +++++++++++++++++++++++++++++++++ test/runtests.jl | 34 +++++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 src/null.jl diff --git a/src/ShiftedProximalOperators.jl b/src/ShiftedProximalOperators.jl index b21055a..e25b21b 100644 --- a/src/ShiftedProximalOperators.jl +++ b/src/ShiftedProximalOperators.jl @@ -30,6 +30,7 @@ include("groupNormL2.jl") include("Rank.jl") include("cappedl1.jl") include("Nuclearnorm.jl") +include("null.jl") include("shiftedCompositeNormL2.jl") include("shiftedNormL0.jl") diff --git a/src/null.jl b/src/null.jl new file mode 100644 index 0000000..204f5ea --- /dev/null +++ b/src/null.jl @@ -0,0 +1,54 @@ +# Null regularizer +export NullRegularizer + +@doc raw""" + NullRegularizer(::Type{T}) where {T <: Real} + NullRegularizer(lambda::T) where {T <: Real} + + +Returns the null regularizer, i.e., the function that is identically zero. +```math +h(x) = 0 +``` + +### Arguments +- `T`: The type of zero that is expected to be returned by the regularizer. + +In the second constructor, the type of lambda is used to infer the type of zero that is expected to be returned by the regularizer. The value of lambda is ignored. +""" +struct NullRegularizer{T <: Real} <: ShiftedProximableFunction end + +NullRegularizer(lambda::T) where {T <: Real} = NullRegularizer{T}() +NullRegularizer(::Type{T}) where {T <: Real} = NullRegularizer{T}() + +shifted(h::NullRegularizer{T}, xk::AbstractVector{T}) where {T <: Real} = + NullRegularizer(T) + +function shift!(h::NullRegularizer{T}, xk::AbstractVector{T}) where {T <: Real} + return h +end + +function (h::NullRegularizer{T})(y) where {T <: Real} + return zero(T) +end + +fun_name(ψ::NullRegularizer{T}) where {T <: Real} = "null regularizer" +fun_expr(ψ::NullRegularizer{T}) where {T <: Real} = "t ↦ 0" +fun_params(ψ::NullRegularizer{T}) where {T <: Real} = "" + +function Base.show(io::IO, ψ::NullRegularizer{T}) where {T <: Real} + println(io, "description : ", fun_name(ψ)) + println(io, "expression : ", fun_expr(ψ)) + println(io, "parameters : ", fun_params(ψ)) +end + +function prox!( + y::AbstractVector{T}, + ψ::NullRegularizer{T}, + q::AbstractVector{T}, + ν::T +) where {T <: Real} + @assert ν > zero(T) + y .= q + return y +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 4855827..d0c9a71 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -7,6 +7,40 @@ using Test include("test_psvd.jl") +@testset "NullRegularizer" begin + h = NullRegularizer(Float64) + @test h(1.0) == 0.0 + y = similar([1.0, 2.0]) + prox!(y, h, [3.0, 4.0], 1.0) + @test all(y .== [3.0, 4.0]) + + h_shifted = shifted(h, [1.0, 2.0]) + @test h_shifted(1.0) == 0.0 + prox!(y, h_shifted, [3.0, 4.0], 1.0) + @test all(y .== [3.0, 4.0]) + + shift!(h_shifted, [5.0, 6.0]) + @test h_shifted(1.0) == 0.0 + prox!(y, h_shifted, [3.0, 4.0], 1.0) + @test all(y .== [3.0, 4.0]) + + h = NullRegularizer(Float32) + @test h(1.0f0) == 0.0f0 + y = similar([1.0f0, 2.0f0]) + prox!(y, h, [3.0f0, 4.0f0], 1.0f0) + @test all(y .== [3.0f0, 4.0f0]) + + h_shifted = shifted(h, [1.0f0, 2.0f0]) + @test h_shifted(1.0f0) == 0.0f0 + prox!(y, h_shifted, [3.0f0, 4.0f0], 1.0f0) + @test all(y .== [3.0f0, 4.0f0]) + + shift!(h_shifted, [5.0f0, 6.0f0]) + @test h_shifted(1.0f0) == 0.0f0 + prox!(y, h_shifted, [3.0f0, 4.0f0], 1.0f0) + @test all(y .== [3.0f0, 4.0f0]) +end + for (op, composite_op, shifted_op) ∈ zip((:NormL2,), (:CompositeNormL2,), (:ShiftedCompositeNormL2,)) @testset "$shifted_op" begin From e5de4b67a730c2e1142f2d5d63775b04aa6f949d Mon Sep 17 00:00:00 2001 From: MaxenceGollier Date: Mon, 2 Mar 2026 11:14:12 -0500 Subject: [PATCH 02/12] add iprox for null regularizer --- src/null.jl | 12 ++++++++++++ test/runtests.jl | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/null.jl b/src/null.jl index 204f5ea..99e83b0 100644 --- a/src/null.jl +++ b/src/null.jl @@ -51,4 +51,16 @@ function prox!( @assert ν > zero(T) y .= q return y +end + +function iprox!( + y::AbstractVector{T}, + ψ::NullRegularizer{T}, + g::AbstractVector{T}, + d::AbstractVector{T}, +) where {T <: Real} + @inbounds for i ∈ eachindex(y) + @assert d[i] > 0 + y[i] = g[i] / d[i] + end end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index d0c9a71..86c2d30 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -13,32 +13,44 @@ include("test_psvd.jl") y = similar([1.0, 2.0]) prox!(y, h, [3.0, 4.0], 1.0) @test all(y .== [3.0, 4.0]) + iprox!(y, h, [3.0, 4.0], [2.0, 4.0]) + @test all(y .== [1.5, 1.0]) h_shifted = shifted(h, [1.0, 2.0]) @test h_shifted(1.0) == 0.0 prox!(y, h_shifted, [3.0, 4.0], 1.0) @test all(y .== [3.0, 4.0]) + iprox!(y, h_shifted, [3.0, 4.0], [2.0, 4.0]) + @test all(y .== [1.5, 1.0]) shift!(h_shifted, [5.0, 6.0]) @test h_shifted(1.0) == 0.0 prox!(y, h_shifted, [3.0, 4.0], 1.0) @test all(y .== [3.0, 4.0]) + iprox!(y, h_shifted, [3.0, 4.0], [2.0, 4.0]) + @test all(y .== [1.5, 1.0]) h = NullRegularizer(Float32) @test h(1.0f0) == 0.0f0 y = similar([1.0f0, 2.0f0]) prox!(y, h, [3.0f0, 4.0f0], 1.0f0) @test all(y .== [3.0f0, 4.0f0]) + iprox!(y, h, [3.0f0, 4.0f0], [2.0f0, 4.0f0]) + @test all(y .== [1.5f0, 1.0f0]) h_shifted = shifted(h, [1.0f0, 2.0f0]) @test h_shifted(1.0f0) == 0.0f0 prox!(y, h_shifted, [3.0f0, 4.0f0], 1.0f0) @test all(y .== [3.0f0, 4.0f0]) + iprox!(y, h_shifted, [3.0f0, 4.0f0], [2.0f0, 4.0f0]) + @test all(y .== [1.5f0, 1.0f0]) shift!(h_shifted, [5.0f0, 6.0f0]) @test h_shifted(1.0f0) == 0.0f0 prox!(y, h_shifted, [3.0f0, 4.0f0], 1.0f0) @test all(y .== [3.0f0, 4.0f0]) + iprox!(y, h_shifted, [3.0f0, 4.0f0], [2.0f0, 4.0f0]) + @test all(y .== [1.5f0, 1.0f0]) end for (op, composite_op, shifted_op) ∈ From da702d09a72a9d8c7a41ec0d1dc16845c3b370db Mon Sep 17 00:00:00 2001 From: MaxenceGollier Date: Mon, 2 Mar 2026 11:27:06 -0500 Subject: [PATCH 03/12] rename variables --- src/null.jl | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/null.jl b/src/null.jl index 99e83b0..ef57a0a 100644 --- a/src/null.jl +++ b/src/null.jl @@ -32,19 +32,19 @@ function (h::NullRegularizer{T})(y) where {T <: Real} return zero(T) end -fun_name(ψ::NullRegularizer{T}) where {T <: Real} = "null regularizer" -fun_expr(ψ::NullRegularizer{T}) where {T <: Real} = "t ↦ 0" -fun_params(ψ::NullRegularizer{T}) where {T <: Real} = "" +fun_name(h::NullRegularizer{T}) where {T <: Real} = "null regularizer" +fun_expr(h::NullRegularizer{T}) where {T <: Real} = "t ↦ 0" +fun_params(h::NullRegularizer{T}) where {T <: Real} = "" -function Base.show(io::IO, ψ::NullRegularizer{T}) where {T <: Real} - println(io, "description : ", fun_name(ψ)) - println(io, "expression : ", fun_expr(ψ)) - println(io, "parameters : ", fun_params(ψ)) +function Base.show(io::IO, h::NullRegularizer{T}) where {T <: Real} + println(io, "description : ", fun_name(h)) + println(io, "expression : ", fun_expr(h)) + println(io, "parameters : ", fun_params(h)) end function prox!( y::AbstractVector{T}, - ψ::NullRegularizer{T}, + h::NullRegularizer{T}, q::AbstractVector{T}, ν::T ) where {T <: Real} @@ -55,7 +55,7 @@ end function iprox!( y::AbstractVector{T}, - ψ::NullRegularizer{T}, + h::NullRegularizer{T}, g::AbstractVector{T}, d::AbstractVector{T}, ) where {T <: Real} From dd8843c1c12f1c39ff3ccb2b602ce3a5888810f5 Mon Sep 17 00:00:00 2001 From: MaxenceGollier Date: Mon, 2 Mar 2026 11:40:43 -0500 Subject: [PATCH 04/12] add null regularizer box --- src/ShiftedProximalOperators.jl | 1 + src/nullBox.jl | 74 +++++++++++++++++++++++++++++++++ test/runtests.jl | 44 +++++++++++++++++--- 3 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 src/nullBox.jl diff --git a/src/ShiftedProximalOperators.jl b/src/ShiftedProximalOperators.jl index e25b21b..c8fb09a 100644 --- a/src/ShiftedProximalOperators.jl +++ b/src/ShiftedProximalOperators.jl @@ -31,6 +31,7 @@ include("Rank.jl") include("cappedl1.jl") include("Nuclearnorm.jl") include("null.jl") +include("nullBox.jl") include("shiftedCompositeNormL2.jl") include("shiftedNormL0.jl") diff --git a/src/nullBox.jl b/src/nullBox.jl new file mode 100644 index 0000000..df68e02 --- /dev/null +++ b/src/nullBox.jl @@ -0,0 +1,74 @@ +# Box null regularizer +export NullRegularizerBox + +@doc raw""" + NullRegularizerBox(l, u) + NullRegularizerBox(::Type{T}, n) + + +Returns the box null regularizer, i.e., the function that is identically zero on a box and +∞ outside of it. +```math +h(x) = \chi(x | [l, u]) +``` + +### Arguments +- `l`: The lower bound of the box. +- `u`: The upper bound of the box. + +In the second constructor, the bounds are vectors of type T and size n set to -Inf and +Inf, respectively. +""" +mutable struct NullRegularizerBox{T <: Real, V <: AbstractVector{T}} <: ShiftedProximableFunction + l::V + u::V + Δ::T +end + +NullRegularizerBox(l::V, u::V) where {T <: Real, V <: AbstractVector{T}} = NullRegularizerBox(l, u, zero(T)) +NullRegularizerBox(::Type{T}, n::Integer) where {T <: Real} = NullRegularizerBox(fill(T(-Inf), n), fill(T(Inf), n)) + +function (h::NullRegularizerBox{T})(y) where {T <: Real} + ϵ = √eps(eltype(y)) + @inbounds for i in eachindex(y) + if !(h.l[i] - ϵ ≤ y[i] ≤ h.u[i] + ϵ) + return T(Inf) + end + end + return zero(T) +end + +fun_name(ψ::NullRegularizerBox{T}) where {T <: Real} = "box null regularizer" +fun_expr(ψ::NullRegularizerBox{T}) where {T <: Real} = "t ↦ χ(t | [l, u])" +fun_params(ψ::NullRegularizerBox{T}) where {T <: Real} = + "l = $(ψ.l)\n" * " "^14 * "u = $(ψ.u)" + +function Base.show(io::IO, ψ::NullRegularizerBox{T}) where {T <: Real} + println(io, "description : ", fun_name(ψ)) + println(io, "expression : ", fun_expr(ψ)) + println(io, "parameters : ", fun_params(ψ)) +end + +function prox!( + y::AbstractVector{T}, + h::NullRegularizerBox{T}, + q::AbstractVector{T}, + ν::T +) where {T <: Real} + @assert ν > zero(T) + @inbounds for i in eachindex(y) + y[i] = clamp(q[i], h.l[i], h.u[i]) + end + return y +end + +function iprox!( + y::AbstractVector{T}, + h::NullRegularizerBox{T}, + g::AbstractVector{T}, + d::AbstractVector{T}, +) where {T <: Real} + @inbounds for i in eachindex(y) + @assert d[i] > 0 + y[i] = clamp(g[i] / d[i], h.l[i], h.u[i]) + end + return y +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 86c2d30..3e5b6c2 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -9,7 +9,7 @@ include("test_psvd.jl") @testset "NullRegularizer" begin h = NullRegularizer(Float64) - @test h(1.0) == 0.0 + @test h([1.0, 1.0]) == 0.0 y = similar([1.0, 2.0]) prox!(y, h, [3.0, 4.0], 1.0) @test all(y .== [3.0, 4.0]) @@ -17,21 +17,21 @@ include("test_psvd.jl") @test all(y .== [1.5, 1.0]) h_shifted = shifted(h, [1.0, 2.0]) - @test h_shifted(1.0) == 0.0 + @test h_shifted([1.0, 1.0]) == 0.0 prox!(y, h_shifted, [3.0, 4.0], 1.0) @test all(y .== [3.0, 4.0]) iprox!(y, h_shifted, [3.0, 4.0], [2.0, 4.0]) @test all(y .== [1.5, 1.0]) shift!(h_shifted, [5.0, 6.0]) - @test h_shifted(1.0) == 0.0 + @test h_shifted([1.0, 1.0]) == 0.0 prox!(y, h_shifted, [3.0, 4.0], 1.0) @test all(y .== [3.0, 4.0]) iprox!(y, h_shifted, [3.0, 4.0], [2.0, 4.0]) @test all(y .== [1.5, 1.0]) h = NullRegularizer(Float32) - @test h(1.0f0) == 0.0f0 + @test h([1.0f0, 1.0f0]) == 0.0f0 y = similar([1.0f0, 2.0f0]) prox!(y, h, [3.0f0, 4.0f0], 1.0f0) @test all(y .== [3.0f0, 4.0f0]) @@ -39,20 +39,52 @@ include("test_psvd.jl") @test all(y .== [1.5f0, 1.0f0]) h_shifted = shifted(h, [1.0f0, 2.0f0]) - @test h_shifted(1.0f0) == 0.0f0 + @test h_shifted([1.0f0, 1.0f0]) == 0.0f0 prox!(y, h_shifted, [3.0f0, 4.0f0], 1.0f0) @test all(y .== [3.0f0, 4.0f0]) iprox!(y, h_shifted, [3.0f0, 4.0f0], [2.0f0, 4.0f0]) @test all(y .== [1.5f0, 1.0f0]) shift!(h_shifted, [5.0f0, 6.0f0]) - @test h_shifted(1.0f0) == 0.0f0 + @test h_shifted([1.0f0, 1.0f0]) == 0.0f0 prox!(y, h_shifted, [3.0f0, 4.0f0], 1.0f0) @test all(y .== [3.0f0, 4.0f0]) iprox!(y, h_shifted, [3.0f0, 4.0f0], [2.0f0, 4.0f0]) @test all(y .== [1.5f0, 1.0f0]) end +@testset "NullRegularizerBox" begin + l = [-1.0, -2.0] + u = [1.0, 2.0] + h = NullRegularizerBox(l, u) + @test h([0.0, 0.0]) == 0.0 + @test h([-1.0, -2.0]) == 0.0 + @test h([1.0, 2.0]) == 0.0 + @test h([-1.01, -2.0]) == Inf + @test h([1.01, 2.0]) == Inf + + y = similar([3.0, 4.0]) + prox!(y, h, [3.0, 4.0], 1.0) + @test all(y .== [1.0, 2.0]) + iprox!(y, h, [3.0, 4.0], [2.0, 4.0]) + @test all(y .== [1.0, 1.0]) + + l = [-1.0f0, -2.0f0] + u = [1.0f0, 2.0f0] + h = NullRegularizerBox(l, u) + @test h([0.0f0, 0.0f0]) == 0.0f0 + @test h([-1.0f0, -2.0f0]) == 0.0f0 + @test h([1.0f0, 2.0f0]) == 0.0f0 + @test h([-1.01f0, -2.0f0]) == Inf + @test h([1.01f0, 2.0f0]) == Inf + + y = similar([3.0f0, 4.0f0]) + prox!(y, h, [3.0f0, 4.0f0], 1.0f0) + @test all(y .== [1.0f0, 2.0f0]) + iprox!(y, h, [3.0f0, 4.0f0], [2.0f0, 4.0f0]) + @test all(y .== [1.0f0, 1.0f0]) +end + for (op, composite_op, shifted_op) ∈ zip((:NormL2,), (:CompositeNormL2,), (:ShiftedCompositeNormL2,)) @testset "$shifted_op" begin From 0e3e4654e3c45abab94c37da49e866cf730323b3 Mon Sep 17 00:00:00 2001 From: MaxenceGollier Date: Mon, 2 Mar 2026 12:15:31 -0500 Subject: [PATCH 05/12] use the prox_zero and iprox_zero functions --- src/nullBox.jl | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/nullBox.jl b/src/nullBox.jl index df68e02..241223b 100644 --- a/src/nullBox.jl +++ b/src/nullBox.jl @@ -17,13 +17,12 @@ h(x) = \chi(x | [l, u]) In the second constructor, the bounds are vectors of type T and size n set to -Inf and +Inf, respectively. """ -mutable struct NullRegularizerBox{T <: Real, V <: AbstractVector{T}} <: ShiftedProximableFunction +mutable struct NullRegularizerBox{T <: Real, V <: AbstractVector{T}} l::V u::V - Δ::T end -NullRegularizerBox(l::V, u::V) where {T <: Real, V <: AbstractVector{T}} = NullRegularizerBox(l, u, zero(T)) +NullRegularizerBox(l::V, u::V) where {T <: Real, V <: AbstractVector{T}} = NullRegularizerBox(l, u) NullRegularizerBox(::Type{T}, n::Integer) where {T <: Real} = NullRegularizerBox(fill(T(-Inf), n), fill(T(Inf), n)) function (h::NullRegularizerBox{T})(y) where {T <: Real} @@ -55,7 +54,7 @@ function prox!( ) where {T <: Real} @assert ν > zero(T) @inbounds for i in eachindex(y) - y[i] = clamp(q[i], h.l[i], h.u[i]) + y[i] = prox_zero(q[i], h.l[i], h.u[i]) end return y end @@ -67,8 +66,7 @@ function iprox!( d::AbstractVector{T}, ) where {T <: Real} @inbounds for i in eachindex(y) - @assert d[i] > 0 - y[i] = clamp(g[i] / d[i], h.l[i], h.u[i]) + y[i] = iprox_zero(d[i], g[i], h.l[i], h.u[i]) end return y end \ No newline at end of file From 6489190a6890144416f78609ea519662debad2be Mon Sep 17 00:00:00 2001 From: MaxenceGollier Date: Mon, 2 Mar 2026 12:18:39 -0500 Subject: [PATCH 06/12] update iprox! --- src/null.jl | 2 +- test/runtests.jl | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/null.jl b/src/null.jl index ef57a0a..3f95dfa 100644 --- a/src/null.jl +++ b/src/null.jl @@ -61,6 +61,6 @@ function iprox!( ) where {T <: Real} @inbounds for i ∈ eachindex(y) @assert d[i] > 0 - y[i] = g[i] / d[i] + y[i] = - g[i] / d[i] end end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 3e5b6c2..6c43aca 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -14,21 +14,21 @@ include("test_psvd.jl") prox!(y, h, [3.0, 4.0], 1.0) @test all(y .== [3.0, 4.0]) iprox!(y, h, [3.0, 4.0], [2.0, 4.0]) - @test all(y .== [1.5, 1.0]) + @test all(y .== -[1.5, 1.0]) h_shifted = shifted(h, [1.0, 2.0]) @test h_shifted([1.0, 1.0]) == 0.0 prox!(y, h_shifted, [3.0, 4.0], 1.0) @test all(y .== [3.0, 4.0]) iprox!(y, h_shifted, [3.0, 4.0], [2.0, 4.0]) - @test all(y .== [1.5, 1.0]) + @test all(y .== -[1.5, 1.0]) shift!(h_shifted, [5.0, 6.0]) @test h_shifted([1.0, 1.0]) == 0.0 prox!(y, h_shifted, [3.0, 4.0], 1.0) @test all(y .== [3.0, 4.0]) iprox!(y, h_shifted, [3.0, 4.0], [2.0, 4.0]) - @test all(y .== [1.5, 1.0]) + @test all(y .== -[1.5, 1.0]) h = NullRegularizer(Float32) @test h([1.0f0, 1.0f0]) == 0.0f0 @@ -36,21 +36,21 @@ include("test_psvd.jl") prox!(y, h, [3.0f0, 4.0f0], 1.0f0) @test all(y .== [3.0f0, 4.0f0]) iprox!(y, h, [3.0f0, 4.0f0], [2.0f0, 4.0f0]) - @test all(y .== [1.5f0, 1.0f0]) + @test all(y .== -[1.5f0, 1.0f0]) h_shifted = shifted(h, [1.0f0, 2.0f0]) @test h_shifted([1.0f0, 1.0f0]) == 0.0f0 prox!(y, h_shifted, [3.0f0, 4.0f0], 1.0f0) @test all(y .== [3.0f0, 4.0f0]) iprox!(y, h_shifted, [3.0f0, 4.0f0], [2.0f0, 4.0f0]) - @test all(y .== [1.5f0, 1.0f0]) + @test all(y .== -[1.5f0, 1.0f0]) shift!(h_shifted, [5.0f0, 6.0f0]) @test h_shifted([1.0f0, 1.0f0]) == 0.0f0 prox!(y, h_shifted, [3.0f0, 4.0f0], 1.0f0) @test all(y .== [3.0f0, 4.0f0]) iprox!(y, h_shifted, [3.0f0, 4.0f0], [2.0f0, 4.0f0]) - @test all(y .== [1.5f0, 1.0f0]) + @test all(y .== -[1.5f0, 1.0f0]) end @testset "NullRegularizerBox" begin @@ -67,7 +67,7 @@ end prox!(y, h, [3.0, 4.0], 1.0) @test all(y .== [1.0, 2.0]) iprox!(y, h, [3.0, 4.0], [2.0, 4.0]) - @test all(y .== [1.0, 1.0]) + @test all(y .== -[1.0, 1.0]) l = [-1.0f0, -2.0f0] u = [1.0f0, 2.0f0] @@ -82,7 +82,7 @@ end prox!(y, h, [3.0f0, 4.0f0], 1.0f0) @test all(y .== [1.0f0, 2.0f0]) iprox!(y, h, [3.0f0, 4.0f0], [2.0f0, 4.0f0]) - @test all(y .== [1.0f0, 1.0f0]) + @test all(y .== -[1.0f0, 1.0f0]) end for (op, composite_op, shifted_op) ∈ From b8c08251f618de39bc16bdc740bd40b6209f7d59 Mon Sep 17 00:00:00 2001 From: MaxenceGollier Date: Mon, 2 Mar 2026 12:23:08 -0500 Subject: [PATCH 07/12] remove constructor --- src/nullBox.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/nullBox.jl b/src/nullBox.jl index 241223b..76d68b2 100644 --- a/src/nullBox.jl +++ b/src/nullBox.jl @@ -22,7 +22,6 @@ mutable struct NullRegularizerBox{T <: Real, V <: AbstractVector{T}} u::V end -NullRegularizerBox(l::V, u::V) where {T <: Real, V <: AbstractVector{T}} = NullRegularizerBox(l, u) NullRegularizerBox(::Type{T}, n::Integer) where {T <: Real} = NullRegularizerBox(fill(T(-Inf), n), fill(T(Inf), n)) function (h::NullRegularizerBox{T})(y) where {T <: Real} From 565914b1e45d18b162acfe7e67e6c8d3aa88356c Mon Sep 17 00:00:00 2001 From: MaxenceGollier Date: Mon, 2 Mar 2026 13:30:35 -0500 Subject: [PATCH 08/12] add shiftedNullBox regularizer --- src/ShiftedProximalOperators.jl | 4 +- src/nullBox.jl | 71 ----------------- src/shiftedNullBox.jl | 99 ++++++++++++++++++++++++ test/runtests.jl | 131 ++++++++++++++------------------ 4 files changed, 158 insertions(+), 147 deletions(-) delete mode 100644 src/nullBox.jl create mode 100644 src/shiftedNullBox.jl diff --git a/src/ShiftedProximalOperators.jl b/src/ShiftedProximalOperators.jl index c8fb09a..d047b86 100644 --- a/src/ShiftedProximalOperators.jl +++ b/src/ShiftedProximalOperators.jl @@ -30,8 +30,9 @@ include("groupNormL2.jl") include("Rank.jl") include("cappedl1.jl") include("Nuclearnorm.jl") + include("null.jl") -include("nullBox.jl") +include("shiftedNullBox.jl") include("shiftedCompositeNormL2.jl") include("shiftedNormL0.jl") @@ -100,6 +101,7 @@ end set_radius!(ψ::ShiftedNormL0Box, Δ::R) where {R <: Real} = set_bounds!(ψ, -Δ, Δ) set_radius!(ψ::ShiftedNormL1Box, Δ::R) where {R <: Real} = set_bounds!(ψ, -Δ, Δ) set_radius!(ψ::ShiftedRootNormLhalfBox, Δ::R) where {R <: Real} = set_bounds!(ψ, -Δ, Δ) +set_radius!(ψ::ShiftedNullRegularizerBox, Δ::R) where {R <: Real} = set_bounds!(ψ, -Δ, Δ) """ set_bounds!(ψ, l, u) diff --git a/src/nullBox.jl b/src/nullBox.jl deleted file mode 100644 index 76d68b2..0000000 --- a/src/nullBox.jl +++ /dev/null @@ -1,71 +0,0 @@ -# Box null regularizer -export NullRegularizerBox - -@doc raw""" - NullRegularizerBox(l, u) - NullRegularizerBox(::Type{T}, n) - - -Returns the box null regularizer, i.e., the function that is identically zero on a box and +∞ outside of it. -```math -h(x) = \chi(x | [l, u]) -``` - -### Arguments -- `l`: The lower bound of the box. -- `u`: The upper bound of the box. - -In the second constructor, the bounds are vectors of type T and size n set to -Inf and +Inf, respectively. -""" -mutable struct NullRegularizerBox{T <: Real, V <: AbstractVector{T}} - l::V - u::V -end - -NullRegularizerBox(::Type{T}, n::Integer) where {T <: Real} = NullRegularizerBox(fill(T(-Inf), n), fill(T(Inf), n)) - -function (h::NullRegularizerBox{T})(y) where {T <: Real} - ϵ = √eps(eltype(y)) - @inbounds for i in eachindex(y) - if !(h.l[i] - ϵ ≤ y[i] ≤ h.u[i] + ϵ) - return T(Inf) - end - end - return zero(T) -end - -fun_name(ψ::NullRegularizerBox{T}) where {T <: Real} = "box null regularizer" -fun_expr(ψ::NullRegularizerBox{T}) where {T <: Real} = "t ↦ χ(t | [l, u])" -fun_params(ψ::NullRegularizerBox{T}) where {T <: Real} = - "l = $(ψ.l)\n" * " "^14 * "u = $(ψ.u)" - -function Base.show(io::IO, ψ::NullRegularizerBox{T}) where {T <: Real} - println(io, "description : ", fun_name(ψ)) - println(io, "expression : ", fun_expr(ψ)) - println(io, "parameters : ", fun_params(ψ)) -end - -function prox!( - y::AbstractVector{T}, - h::NullRegularizerBox{T}, - q::AbstractVector{T}, - ν::T -) where {T <: Real} - @assert ν > zero(T) - @inbounds for i in eachindex(y) - y[i] = prox_zero(q[i], h.l[i], h.u[i]) - end - return y -end - -function iprox!( - y::AbstractVector{T}, - h::NullRegularizerBox{T}, - g::AbstractVector{T}, - d::AbstractVector{T}, -) where {T <: Real} - @inbounds for i in eachindex(y) - y[i] = iprox_zero(d[i], g[i], h.l[i], h.u[i]) - end - return y -end \ No newline at end of file diff --git a/src/shiftedNullBox.jl b/src/shiftedNullBox.jl new file mode 100644 index 0000000..dbff35e --- /dev/null +++ b/src/shiftedNullBox.jl @@ -0,0 +1,99 @@ +# Null box regularizer +export ShiftedNullRegularizerBox + +@doc raw""" + ShiftedNullRegularizerBox(h, sj, shifted_twice, l, u) + +Returns the shifted box null regularizer, i.e., the function that is identically zero on a box and +∞ outside of it. +```math +ψ(x) = h(xk + sj + x) + \chi(sj + x | [l, u]) +``` +where `h` is identically zero, `xk`represents a shift, `sj` is an additional shift that is applied to the indicator +function as well and `[l, u]` is the box that defines the domain of the function. + +### Arguments +- `h`: The unshifted null regularizer (see `NullRegularizer`). +- `sj`: The shift of the indicator function. +- `shifted_twice`: A boolean indicating whether `sj` is updated or not on shifts, true means that `sj` is updated, false means that `xk` is updated. +- `l`: The lower bound of the box. +- `u`: The upper bound of the box. +""" +mutable struct ShiftedNullRegularizerBox{T <: Real, V <: AbstractVector{T}} <: ShiftedProximableFunction + h::NullRegularizer{T} + sj::V + shifted_twice::Bool + l::V + u::V + + function ShiftedNullRegularizerBox( + h::NullRegularizer{T}, + sj::V, + shifted_twice::Bool, + l::V, + u::V, + ) where {T <: Real, V <: AbstractVector{T}} + new{T, V}(h, sj, shifted_twice, l, u) + end +end + +shifted( + h::NullRegularizer{T}, + xk::AbstractVector{T}, + l::AbstractVector{T}, + u::AbstractVector{T}, +) where {T <: Real} = ShiftedNullRegularizerBox(h, zero(xk), false, l, u) +shifted( + h::NullRegularizer{T}, + xk::AbstractVector{T}, + Δ::T, + χ::Conjugate{IndBallL1{T}}, +) where {T <: Real, V <: AbstractVector{T}} = ShiftedNullRegularizerBox(h, zero(xk), false, fill(-Δ, length(xk)), fill(Δ, length(xk))) +shifted( + ψ::ShiftedNullRegularizerBox{T, V}, + sj::AbstractVector{T}, +) where {T <: Real, V <: AbstractVector{T}} = + ShiftedNullRegularizerBox(ψ.h, sj, true, ψ.l, ψ.u) + +function shift!(ψ::ShiftedNullRegularizerBox{T, V}, shift::AbstractVector{T}) where {T <: Real, V <: AbstractVector{T}} + ψ.shifted_twice && (ψ.sj .= shift) +end + +function (ψ::ShiftedNullRegularizerBox{T, V})(y) where {T <: Real, V <: AbstractVector{T}} + ϵ = √eps(eltype(y)) + @inbounds for i in eachindex(y) + if !(ψ.l[i] - ϵ ≤ ψ.sj[i] + y[i] ≤ ψ.u[i] + ϵ) + return T(Inf) + end + end + return zero(T) +end + +fun_name(ψ::ShiftedNullRegularizerBox{T, V}) where {T <: Real, V <: AbstractVector{T}} = "shifted null regularizer with box indicator" +fun_expr(ψ::ShiftedNullRegularizerBox{T, V}) where {T <: Real, V <: AbstractVector{T}} = "t ↦ χ({sj + t .∈ [l,u]})" +fun_params(ψ::ShiftedNullRegularizerBox{T, V}) where {T <: Real, V <: AbstractVector{T}} = + "sj = $(ψ.sj)\n" * " "^14 * "l = $(ψ.l)\n" * " "^14 * "u = $(ψ.u)" + +function prox!( + y::AbstractVector{T}, + ψ::ShiftedNullRegularizerBox{T, V}, + q::AbstractVector{T}, + σ::T, +) where {T <: Real, V <: AbstractVector{T}} + @assert σ > zero(T) + @inbounds for i ∈ eachindex(y) + y[i] = prox_zero(q[i], ψ.l[i] - ψ.sj[i], ψ.u[i] - ψ.sj[i]) + end + return y +end + +function iprox!( + y::AbstractVector{T}, + ψ::ShiftedNullRegularizerBox{T, V}, + g::AbstractVector{T}, + d::AbstractVector{T}, +) where {T <: Real, V <: AbstractVector{T}} + @inbounds for i ∈ eachindex(y) + y[i] = iprox_zero(d[i], g[i], ψ.l[i] - ψ.sj[i], ψ.u[i] - ψ.sj[i]) + end + return y +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 6c43aca..86fc5cd 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -8,81 +8,62 @@ using Test include("test_psvd.jl") @testset "NullRegularizer" begin - h = NullRegularizer(Float64) - @test h([1.0, 1.0]) == 0.0 - y = similar([1.0, 2.0]) - prox!(y, h, [3.0, 4.0], 1.0) - @test all(y .== [3.0, 4.0]) - iprox!(y, h, [3.0, 4.0], [2.0, 4.0]) - @test all(y .== -[1.5, 1.0]) - - h_shifted = shifted(h, [1.0, 2.0]) - @test h_shifted([1.0, 1.0]) == 0.0 - prox!(y, h_shifted, [3.0, 4.0], 1.0) - @test all(y .== [3.0, 4.0]) - iprox!(y, h_shifted, [3.0, 4.0], [2.0, 4.0]) - @test all(y .== -[1.5, 1.0]) - - shift!(h_shifted, [5.0, 6.0]) - @test h_shifted([1.0, 1.0]) == 0.0 - prox!(y, h_shifted, [3.0, 4.0], 1.0) - @test all(y .== [3.0, 4.0]) - iprox!(y, h_shifted, [3.0, 4.0], [2.0, 4.0]) - @test all(y .== -[1.5, 1.0]) - - h = NullRegularizer(Float32) - @test h([1.0f0, 1.0f0]) == 0.0f0 - y = similar([1.0f0, 2.0f0]) - prox!(y, h, [3.0f0, 4.0f0], 1.0f0) - @test all(y .== [3.0f0, 4.0f0]) - iprox!(y, h, [3.0f0, 4.0f0], [2.0f0, 4.0f0]) - @test all(y .== -[1.5f0, 1.0f0]) - - h_shifted = shifted(h, [1.0f0, 2.0f0]) - @test h_shifted([1.0f0, 1.0f0]) == 0.0f0 - prox!(y, h_shifted, [3.0f0, 4.0f0], 1.0f0) - @test all(y .== [3.0f0, 4.0f0]) - iprox!(y, h_shifted, [3.0f0, 4.0f0], [2.0f0, 4.0f0]) - @test all(y .== -[1.5f0, 1.0f0]) - - shift!(h_shifted, [5.0f0, 6.0f0]) - @test h_shifted([1.0f0, 1.0f0]) == 0.0f0 - prox!(y, h_shifted, [3.0f0, 4.0f0], 1.0f0) - @test all(y .== [3.0f0, 4.0f0]) - iprox!(y, h_shifted, [3.0f0, 4.0f0], [2.0f0, 4.0f0]) - @test all(y .== -[1.5f0, 1.0f0]) -end - -@testset "NullRegularizerBox" begin - l = [-1.0, -2.0] - u = [1.0, 2.0] - h = NullRegularizerBox(l, u) - @test h([0.0, 0.0]) == 0.0 - @test h([-1.0, -2.0]) == 0.0 - @test h([1.0, 2.0]) == 0.0 - @test h([-1.01, -2.0]) == Inf - @test h([1.01, 2.0]) == Inf - - y = similar([3.0, 4.0]) - prox!(y, h, [3.0, 4.0], 1.0) - @test all(y .== [1.0, 2.0]) - iprox!(y, h, [3.0, 4.0], [2.0, 4.0]) - @test all(y .== -[1.0, 1.0]) - - l = [-1.0f0, -2.0f0] - u = [1.0f0, 2.0f0] - h = NullRegularizerBox(l, u) - @test h([0.0f0, 0.0f0]) == 0.0f0 - @test h([-1.0f0, -2.0f0]) == 0.0f0 - @test h([1.0f0, 2.0f0]) == 0.0f0 - @test h([-1.01f0, -2.0f0]) == Inf - @test h([1.01f0, 2.0f0]) == Inf - - y = similar([3.0f0, 4.0f0]) - prox!(y, h, [3.0f0, 4.0f0], 1.0f0) - @test all(y .== [1.0f0, 2.0f0]) - iprox!(y, h, [3.0f0, 4.0f0], [2.0f0, 4.0f0]) - @test all(y .== -[1.0f0, 1.0f0]) + for T in (Float64, Float32) + h = NullRegularizer(T) + @test h(T.([1.0, 1.0])) == T(0.0) + y = similar(T.([1.0, 2.0])) + prox!(y, h, T.([3.0, 4.0]), T(1.0)) + @test all(y .== T.([3.0, 4.0])) + iprox!(y, h, T.([3.0, 4.0]), T.([2.0, 4.0])) + @test all(y .== -T.([1.5, 1.0])) + + h_shifted = shifted(h, T.([1.0, 2.0])) + @test h_shifted(T.([1.0, 1.0])) == T(0.0) + prox!(y, h_shifted, T.([3.0, 4.0]), T(1.0)) + @test all(y .== T.([3.0, 4.0])) + iprox!(y, h_shifted, T.([3.0, 4.0]), T.([2.0, 4.0])) + @test all(y .== -T.([1.5, 1.0])) + + shift!(h_shifted, T.([5.0, 6.0])) + @test h_shifted(T.([1.0, 1.0])) == T(0.0) + prox!(y, h_shifted, T.([3.0, 4.0]), T(1.0)) + @test all(y .== T.([3.0, 4.0])) + iprox!(y, h_shifted, T.([3.0, 4.0]), T.([2.0, 4.0])) + @test all(y .== -T.([1.5, 1.0])) + + h_shifted_box = shifted(h, T.([1.0, 2.0]), T.([0.0, 0.0]), T.([2.0, 3.0])) + @test h_shifted_box.shifted_twice == false + @test h_shifted_box(T.([0.0, 0.0])) == T(0.0) + @test h_shifted_box(T.([-1.0, 0.0])) == T(Inf) + @test h_shifted_box(T.([2.0, 3.0])) == T(0.0) + @test h_shifted_box(T.([1.0, 1.5])) == T(0.0) + prox!(y, h_shifted_box, T.([3.0, 4.0]), T(1.0)) + @test all(y .== T.([2.0, 3.0])) + iprox!(y, h_shifted_box, T.([3.0, 4.0]), T.([2.0, 4.0])) + @test all(y .== T.([0.0, 0.0])) + + shift!(h_shifted_box, T.([5.0, 6.0])) + + @test h_shifted_box(T.([0.0, 0.0])) == T(0.0) + @test h_shifted_box(T.([-1.0, 0.0])) == T(Inf) + @test h_shifted_box(T.([2.0, 3.0])) == T(0.0) + @test h_shifted_box(T.([1.0, 1.5])) == T(0.0) + prox!(y, h_shifted_box, T.([3.0, 4.0]), T(1.0)) + @test all(y .== T.([2.0, 3.0])) + iprox!(y, h_shifted_box, T.([3.0, 4.0]), T.([2.0, 4.0])) + @test all(y .== T.([0.0, 0.0])) + + h_shifted_twice_box = shifted(h_shifted_box, T.([1.0, 2.0])) + @test h_shifted_twice_box.shifted_twice == true + @test h_shifted_twice_box(T.([0.0, 0.0])) == h_shifted_box(T.([1.0, 2.0])) + @test h_shifted_twice_box(T.([-1.0, 0.0])) == h_shifted_box(T.([0.0, 2.0])) + @test h_shifted_twice_box(T.([2.0, 3.0])) == h_shifted_box(T.([3.0, 5.0])) + @test h_shifted_twice_box(T.([1.0, 1.5])) == h_shifted_box(T.([2.0, 3.5])) + prox!(y, h_shifted_twice_box, T.([3.0, 4.0]), T(1.0)) + @test all(y .== T.([1.0, 1.0])) + iprox!(y, h_shifted_twice_box, T.([3.0, 4.0]), T.([2.0, 4.0])) + @test all(y .== T.([-1.0, -1.0])) + end end for (op, composite_op, shifted_op) ∈ From cbb4315a4d2f59f5d9a81961aeb38ec7035ab670 Mon Sep 17 00:00:00 2001 From: MaxenceGollier Date: Mon, 2 Mar 2026 13:40:37 -0500 Subject: [PATCH 09/12] add allocation tests --- test/test_allocs.jl | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/test_allocs.jl b/test/test_allocs.jl index d2824b3..9af83c5 100644 --- a/test/test_allocs.jl +++ b/test/test_allocs.jl @@ -37,6 +37,13 @@ macro wrappedallocs(expr) end @testset "allocs" begin + + for op ∈ (:ShiftedNullRegularizerBox,) + h_shifted_box = shifted(NullRegularizer(1.0), [1.0, 2.0], [0.0, 0.0], [2.0, 3.0]) + @test @wrappedallocs(prox!(y, h_shifted_box, [3.0, 4.0], T(1.0))) == 0 + @test @wrappedallocs(iprox!(y, h_shifted_box, [3.0, 4.0], [2.0, 4.0])) == 0 + end + for (op, composite_op) ∈ ((:NormL2, :CompositeNormL2),) CompositeOp = eval(composite_op) @@ -66,7 +73,7 @@ end ν = 0.1056 @test @wrappedallocs(prox!(y, ϕ, x, ν)) == 0 end - for op ∈ (:NormL0, :NormL1, :RootNormLhalf) + for op ∈ (:NormL0, :NormL1, :RootNormLhalf, :NullRegularizer) h = eval(op)(1.0) n = 1000 xk = rand(n) @@ -99,7 +106,7 @@ end @test allocs == 0 end - for op ∈ (:NormL0, :NormL1) + for op ∈ (:NormL0, :NormL1, :NullRegularizer) h = eval(op)(1.0) n = 1000 xk = rand(n) From 55e05e769ff26c94ac1296ee68e5a7f319d853ca Mon Sep 17 00:00:00 2001 From: MaxenceGollier Date: Mon, 2 Mar 2026 13:53:30 -0500 Subject: [PATCH 10/12] fix allocation tests --- test/test_allocs.jl | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test/test_allocs.jl b/test/test_allocs.jl index 9af83c5..9b90d39 100644 --- a/test/test_allocs.jl +++ b/test/test_allocs.jl @@ -38,9 +38,13 @@ end @testset "allocs" begin - for op ∈ (:ShiftedNullRegularizerBox,) - h_shifted_box = shifted(NullRegularizer(1.0), [1.0, 2.0], [0.0, 0.0], [2.0, 3.0]) - @test @wrappedallocs(prox!(y, h_shifted_box, [3.0, 4.0], T(1.0))) == 0 + for op ∈ (:NullRegularizer,) + h = eval(op)(Float64) + @test @wrappedallocs(h([0.0, 0.0])) == 0 + h_shifted_box = shifted(h, [1.0, 2.0], [0.0, 0.0], [2.0, 3.0]) + @test @wrappedallocs(h_shifted_box([0.0, 0.0])) == 0 + y = zeros(Float64, 2) + @test @wrappedallocs(prox!(y, h_shifted_box, [3.0, 4.0], 1.0)) == 0 @test @wrappedallocs(iprox!(y, h_shifted_box, [3.0, 4.0], [2.0, 4.0])) == 0 end @@ -73,7 +77,7 @@ end ν = 0.1056 @test @wrappedallocs(prox!(y, ϕ, x, ν)) == 0 end - for op ∈ (:NormL0, :NormL1, :RootNormLhalf, :NullRegularizer) + for op ∈ (:NormL0, :NormL1, :RootNormLhalf,) h = eval(op)(1.0) n = 1000 xk = rand(n) @@ -106,7 +110,7 @@ end @test allocs == 0 end - for op ∈ (:NormL0, :NormL1, :NullRegularizer) + for op ∈ (:NormL0, :NormL1,) h = eval(op)(1.0) n = 1000 xk = rand(n) From 650b0a68adf7db556e0159068af3072a596790ad Mon Sep 17 00:00:00 2001 From: MaxenceGollier Date: Mon, 2 Mar 2026 15:46:55 -0500 Subject: [PATCH 11/12] add parametric type --- src/shiftedNullBox.jl | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/src/shiftedNullBox.jl b/src/shiftedNullBox.jl index dbff35e..1ccad1c 100644 --- a/src/shiftedNullBox.jl +++ b/src/shiftedNullBox.jl @@ -18,50 +18,51 @@ function as well and `[l, u]` is the box that defines the domain of the function - `l`: The lower bound of the box. - `u`: The upper bound of the box. """ -mutable struct ShiftedNullRegularizerBox{T <: Real, V <: AbstractVector{T}} <: ShiftedProximableFunction +mutable struct ShiftedNullRegularizerBox{T <: Real, V <: AbstractVector{T}, VT <: Union{AbstractVector{T}, T}} <: ShiftedProximableFunction h::NullRegularizer{T} sj::V shifted_twice::Bool - l::V - u::V + l::VT + u::VT function ShiftedNullRegularizerBox( h::NullRegularizer{T}, sj::V, shifted_twice::Bool, - l::V, - u::V, - ) where {T <: Real, V <: AbstractVector{T}} - new{T, V}(h, sj, shifted_twice, l, u) + l::VT, + u::VT, + ) where {T <: Real, V <: AbstractVector{T}, VT <: Union{AbstractVector{T}, T}} + new{T, V, VT}(h, sj, shifted_twice, l, u) end end shifted( h::NullRegularizer{T}, xk::AbstractVector{T}, - l::AbstractVector{T}, - u::AbstractVector{T}, -) where {T <: Real} = ShiftedNullRegularizerBox(h, zero(xk), false, l, u) + l::VT, + u::VT, +) where {T <: Real, VT} = ShiftedNullRegularizerBox(h, zero(xk), false, l, u) shifted( h::NullRegularizer{T}, xk::AbstractVector{T}, Δ::T, χ::Conjugate{IndBallL1{T}}, -) where {T <: Real, V <: AbstractVector{T}} = ShiftedNullRegularizerBox(h, zero(xk), false, fill(-Δ, length(xk)), fill(Δ, length(xk))) +) where {T <: Real} = ShiftedNullRegularizerBox(h, zero(xk), false, -Δ, Δ) shifted( - ψ::ShiftedNullRegularizerBox{T, V}, + ψ::ShiftedNullRegularizerBox{T, V, VT}, sj::AbstractVector{T}, -) where {T <: Real, V <: AbstractVector{T}} = - ShiftedNullRegularizerBox(ψ.h, sj, true, ψ.l, ψ.u) +) where {T <: Real, V <: AbstractVector{T}, VT} = ShiftedNullRegularizerBox(ψ.h, sj, true, ψ.l, ψ.u) -function shift!(ψ::ShiftedNullRegularizerBox{T, V}, shift::AbstractVector{T}) where {T <: Real, V <: AbstractVector{T}} +function shift!(ψ::ShiftedNullRegularizerBox{T, V, VT}, shift::AbstractVector{T}) where {T <: Real, V <: AbstractVector{T}, VT} ψ.shifted_twice && (ψ.sj .= shift) end function (ψ::ShiftedNullRegularizerBox{T, V})(y) where {T <: Real, V <: AbstractVector{T}} ϵ = √eps(eltype(y)) @inbounds for i in eachindex(y) - if !(ψ.l[i] - ϵ ≤ ψ.sj[i] + y[i] ≤ ψ.u[i] + ϵ) + l = ψ.l isa AbstractVector ? ψ.l[i] : ψ.l + u = ψ.u isa AbstractVector ? ψ.u[i] : ψ.u + if !(l - ϵ ≤ ψ.sj[i] + y[i] ≤ u + ϵ) return T(Inf) end end @@ -81,7 +82,9 @@ function prox!( ) where {T <: Real, V <: AbstractVector{T}} @assert σ > zero(T) @inbounds for i ∈ eachindex(y) - y[i] = prox_zero(q[i], ψ.l[i] - ψ.sj[i], ψ.u[i] - ψ.sj[i]) + l = ψ.l isa AbstractVector ? ψ.l[i] : ψ.l + u = ψ.u isa AbstractVector ? ψ.u[i] : ψ.u + y[i] = prox_zero(q[i], l - ψ.sj[i], u - ψ.sj[i]) end return y end @@ -93,7 +96,9 @@ function iprox!( d::AbstractVector{T}, ) where {T <: Real, V <: AbstractVector{T}} @inbounds for i ∈ eachindex(y) - y[i] = iprox_zero(d[i], g[i], ψ.l[i] - ψ.sj[i], ψ.u[i] - ψ.sj[i]) + l = ψ.l isa AbstractVector ? ψ.l[i] : ψ.l + u = ψ.u isa AbstractVector ? ψ.u[i] : ψ.u + y[i] = iprox_zero(d[i], g[i], l - ψ.sj[i], u - ψ.sj[i]) end return y end \ No newline at end of file From 6b45e4534e1747091d71e54bc2174cbb1028f30b Mon Sep 17 00:00:00 2001 From: MaxenceGollier Date: Mon, 2 Mar 2026 16:33:12 -0500 Subject: [PATCH 12/12] add shifted constructor with selected argument --- src/shiftedNullBox.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/shiftedNullBox.jl b/src/shiftedNullBox.jl index 1ccad1c..83d757f 100644 --- a/src/shiftedNullBox.jl +++ b/src/shiftedNullBox.jl @@ -36,6 +36,13 @@ mutable struct ShiftedNullRegularizerBox{T <: Real, V <: AbstractVector{T}, VT < end end +shifted( + h::NullRegularizer{T}, + xk::AbstractVector{T}, + l, + u, + selected::AbstractArray{I} = 1:length(xk), +) where {T <: Real, I <: Integer} = shifted(h, xk, l, u) shifted( h::NullRegularizer{T}, xk::AbstractVector{T},