Skip to content
Snippets Groups Projects
Commit 8321793f authored by Stephan Hilb's avatar Stephan Hilb
Browse files

update codebase

parent c8c50744
No related branches found
No related tags found
No related merge requests found
...@@ -2,7 +2,8 @@ module DualTVDD ...@@ -2,7 +2,8 @@ module DualTVDD
include("types.jl") include("common.jl")
include("models.jl")
include("chambolle.jl") include("chambolle.jl")
include("dualtvdd.jl") include("dualtvdd.jl")
include("projgrad.jl") include("projgrad.jl")
...@@ -24,7 +25,7 @@ function run() ...@@ -24,7 +25,7 @@ function run()
display(norm(B)) display(norm(B))
md = DualTVDD.OpROFModel(g, B, α) md = DualTVDD.DualTVDDModel(g, B, α)
ctx = DualTVDD.init(md, DualTVDD.ChambolleAlgorithm()) ctx = DualTVDD.init(md, DualTVDD.ChambolleAlgorithm())
ctx2 = DualTVDD.init(md, DualTVDD.ProjGradAlgorithm(λ=1/sqrt(8)/norm(B))) ctx2 = DualTVDD.init(md, DualTVDD.ProjGradAlgorithm(λ=1/sqrt(8)/norm(B)))
...@@ -81,10 +82,10 @@ function rundd() ...@@ -81,10 +82,10 @@ function rundd()
α = 1/4 α = 1/4
md = DualTVDD.DualTVDDModel(f, A, α, 0., 0.) md = DualTVDD.DualTVDDModel(f, A, α, 0., 0.)
alg = DualTVDD.DualTVDDAlgorithm(M=(2,2), overlap=(2,2), σ=1) alg = DualTVDD.DualTVDDAlgorithm(M=(2,2), overlap=(2,2), σ=0.25)
ctx = DualTVDD.init(md, alg) ctx = DualTVDD.init(md, alg)
md2 = DualTVDD.OpROFModel(g, B, α) md2 = DualTVDD.DualTVDDModel(g, B, α)
alg2 = DualTVDD.ChambolleAlgorithm() alg2 = DualTVDD.ChambolleAlgorithm()
ctx2 = DualTVDD.init(md2, alg2) ctx2 = DualTVDD.init(md2, alg2)
...@@ -141,7 +142,7 @@ function run3() ...@@ -141,7 +142,7 @@ function run3()
α = 0.1 α = 0.1
# Chambolle # Chambolle
md = DualTVDD.OpROFModel(g, B, α) md = DualTVDD.DualTVDDModel(g, B, α)
alg = DualTVDD.ChambolleAlgorithm() alg = DualTVDD.ChambolleAlgorithm()
ctx = DualTVDD.init(md, alg) ctx = DualTVDD.init(md, alg)
......
...@@ -4,8 +4,8 @@ ...@@ -4,8 +4,8 @@
# https://doi.org/10.1023/B:JMIV.0000011325.36760.1e # https://doi.org/10.1023/B:JMIV.0000011325.36760.1e
# #
# Implementation Notes: # Implementation Notes:
# - λ is not a scalar but a scalar field # - λ is called α and is not a scalar but a scalar field
# - pointwise feasibility constraint |p|<=λ instead of |p|<=1 # - pointwise feasibility constraint |p|<=α instead of |p|<=1
# - B is introduced, the original Chambolle algorithm has B=Id # - B is introduced, the original Chambolle algorithm has B=Id
using Base: @_inline_meta using Base: @_inline_meta
...@@ -21,35 +21,32 @@ struct ChambolleAlgorithm <: Algorithm ...@@ -21,35 +21,32 @@ struct ChambolleAlgorithm <: Algorithm
end end
end end
struct ChambolleContext{M,A,G,Λ,T,R,S,Sv,K1,K2} <: Context struct ChambolleContext{M,A,T,R,S,Sv,K1,K2} <: Context
"model data" "model data"
model::M model::M
"algorithm data" "algorithm data"
algorithm::A algorithm::A
"matrix view on model.f" "matrix view on model.f"
g::G
"matrix view on model.λ"
λ::Λ
"matrix view on pv"
p::T p::T
"matrix view on rv" "matrix view on rv"
r::R r::R
"matrix view on sv" "matrix view on sv"
s::S s::S
"dual variable as vector" "dual variable as vector"
rv::Sv rv::Sv
"scalar temporary variable" "scalar temporary variable"
sv::Sv sv::Sv
"div(p) + g kernel" "div(p) + g kernel"
k1::K1 k1::K1
"(p + (τ/normB)*grad(q))/(1 + (τ/normB)/λ|grad(q)|) kernel" "(p + (τ/normB)*grad(q))/(1 + (τ/normB)/λ|grad(q)|) kernel"
k2::K2 k2::K2
end end
function init(md::OpROFModel, alg::ChambolleAlgorithm) function init(md::DualTVDDModel, alg::ChambolleAlgorithm)
d = ndims(md.g) d = ndims(md.g)
g = extend(md.g, StaticKernels.ExtensionNothing())
λ = extend(md.λ, StaticKernels.ExtensionNothing())
pv = zeros(d * length(md.g)) pv = zeros(d * length(md.g))
rv = zeros(length(md.g)) rv = zeros(length(md.g))
sv = zero(rv) sv = zero(rv)
...@@ -57,69 +54,38 @@ function init(md::OpROFModel, alg::ChambolleAlgorithm) ...@@ -57,69 +54,38 @@ function init(md::OpROFModel, alg::ChambolleAlgorithm)
r = reshape(reinterpret(Float64, rv), size(md.g)) r = reshape(reinterpret(Float64, rv), size(md.g))
s = extend(reshape(reinterpret(Float64, sv), size(md.g)), StaticKernels.ExtensionReplicate()) s = extend(reshape(reinterpret(Float64, sv), size(md.g)), StaticKernels.ExtensionReplicate())
z = zero(CartesianIndex{d}) @inline kf1(pw) = @inbounds divergence(pw) + md.g[pw.position]
@inline kf1(pw, gw) = @inbounds divergence(pw) + gw[z]
k1 = Kernel{ntuple(_->-1:1, d)}(kf1) k1 = Kernel{ntuple(_->-1:1, d)}(kf1)
normB = norm(md.B) my_norm(B::UniformScaling{Bool}) = B.λ * sqrt(length(md.g))
my_norm(B) = norm(B)
@inline function kf2(pw, sw) normB = my_norm(md.B)
@inbounds iszero(λ[pw.position]) && return zero(pw[z])
@inline function kf2(sw)
@inbounds iszero(md.α[sw.position]) && return zero(p[sw.position])
sgrad = (alg.τ / normB) * gradient(sw) sgrad = (alg.τ / normB) * gradient(sw)
return @inbounds (pw[z] + sgrad) / (1 + norm(sgrad) / λ[pw.position]) return @inbounds (p[sw.position] + sgrad) / (1 + norm(sgrad) / md.α[sw.position])
end end
k2 = Kernel{ntuple(_->0:1, d)}(kf2) k2 = Kernel{ntuple(_->0:1, d)}(kf2)
return ChambolleContext(md, alg, g, λ, p, r, s, rv, sv, k1, k2) return ChambolleContext(md, alg, p, r, s, rv, sv, k1, k2)
end end
function reset!(ctx::ChambolleContext) function reset!(ctx::ChambolleContext)
fill!(ctx.p, zero(eltype(ctx.p))) fill!(ctx.p, zero(eltype(ctx.p)))
end end
@generated function gradient(w::StaticKernels.Window{<:Any,N}) where N
i0 = ntuple(_->0, N)
i1(k) = ntuple(i->Int(k==i), N)
wi = (:(w[$(i1(k)...)] - w[$(i0...)]) for k in 1:N)
return quote
Base.@_inline_meta
return @inbounds SVector($(wi...))
end
end
@generated function divergence(w::StaticKernels.Window{SVector{N,T},N}) where {N,T}
i0 = ntuple(_->0, N)
i1(k) = ntuple(i->Int(k==i), N)
wi = (:((isnothing(w[$(i1(k)...)]) ? zero(T) : w[$(i0...)][$k]) -
(isnothing(w[$((.-i1(k))...)]) ? zero(T) : w[$((.-i1(k))...)][$k])) for k in 1:N)
return quote
Base.@_inline_meta
return @inbounds +($(wi...))
end
end
# x = (A'*A + β*I) \ (FD.divergence(grid, p) .+ A'*f)
# q = FD.gradient(grid, x)
function step!(ctx::ChambolleContext) function step!(ctx::ChambolleContext)
# r = div(p) + g # r = div(p) + g
map!(ctx.k1, ctx.r, ctx.p, ctx.g) map!(ctx.k1, ctx.r, ctx.p)
# s = B * r # s = B * r
mul!(ctx.sv, ctx.model.B, ctx.rv) mul!(ctx.sv, ctx.model.B, ctx.rv)
# p = (p + τ*grad(s)) / (1 + τ/λ|grad(s)|) # p = (p + τ*grad(s)) / (1 + τ/λ|grad(s)|)
map!(ctx.k2, ctx.p, ctx.p, ctx.s) map!(ctx.k2, ctx.p, ctx.s)
end end
function recover_u!(ctx) recover_u(ctx::ChambolleContext) = recover_u(ctx.p, ctx.model)
# r = div(p) + g
map!(ctx.k1, ctx.r, ctx.p, ctx.g)
# s = B * r
mul!(ctx.sv, ctx.model.B, ctx.rv)
return ctx.s
end
# #
#function solve(md::ROFModel, alg::Chambolle; #function solve(md::ROFModel, alg::Chambolle;
......
using StaticArrays
using StaticKernels
# Solver Interface Notes: # Solver Interface Notes:
# #
# - a concrete subtype `<:Model` describes one model type and each instance # - a concrete subtype `<:Model` describes one model type and each instance
# represents a partical solvable problem, including model parameters and data # represents a fully specified problem, i.e. including data and model parameters
# - a concrete subtype `<:Algorithm` describes an algorithm type, maybe # - a concrete subtype `<:Algorithm` describes an algorithm type, maybe
# applicable to different models with special implementations # applicable to different models with special implementations
# - abstract subtypes `<:Model`, `<:Algorithm` may exist # - abstract subtypes `<:Model`, `<:Algorithm` may exist
# - `init(::Model, ::Algorithm)::Context` initializes a runnable algorithm context
# - `step!(::Context)::Context` performs one non-allocating step
# - `solve(::Model, ::Algorithm)` must lead to the same deterministic # - `solve(::Model, ::Algorithm)` must lead to the same deterministic
# outcome depending only on the arguments given. # outcome depending only on the arguments given.
# - `init(::Model, ::Algorithm)::Context` initializes a runnable algorithm context
# - `run(::Context)` must continue on from the given context # - `run(::Context)` must continue on from the given context
# - `(<:Algorithm)(...)` constructors must accept keyword arguments only # - `(<:Algorithm)(...)` constructors must accept keyword arguments only
...@@ -15,28 +19,47 @@ abstract type Model end ...@@ -15,28 +19,47 @@ abstract type Model end
abstract type Algorithm end abstract type Algorithm end
abstract type Context end abstract type Context end
"min_u 1/2 * |Au-f|_2^2 + α*TV(u) + β/2 |u|_2 + γ |u|_1"
struct DualTVDDModel{U,VV} <: Model "helper struct to prevent allocations"
"given data" struct AppliedLinearKernel{K,A}
f::U kernel::K
"forward operator" data::A
A::VV
"total variation parameter"
α::Float64
"L2 regularization parameter"
β::Float64
"L1 regularization parameter"
γ::Float64
end end
"min_p 1/2 * |-div(p) - g|_B^2 + χ_{|p|<=λ}" compute!(dst, op::AppliedLinearKernel) = map!(dst, op.kernel, op.a)
struct OpROFModel{U,VV,Λ} <: Model
"given data"
g::U @generated function gradient(w::StaticKernels.Window{<:Any,N}) where N
"B norm operator" i0 = ntuple(_->0, N)
B::VV i1(k) = ntuple(i->Int(k==i), N)
"total variation parameter"
λ::Λ wi = (:(w[$(i1(k)...)] - w[$(i0...)]) for k in 1:N)
return quote
Base.@_inline_meta
return @inbounds SVector($(wi...))
end
end end
OpROFModel(g, B, λ::Real) = OpROFModel(g, B, fill!(similar(g, typeof(λ)), λ)) @generated function divergence(w::StaticKernels.Window{SVector{N,T},N}) where {N,T}
i0 = ntuple(_->0, N)
i1(k) = ntuple(i->Int(k==i), N)
wi = (:((isnothing(w[$(i1(k)...)]) ? zero(T) : w[$(i0...)][$k]) -
(isnothing(w[$((.-i1(k))...)]) ? zero(T) : w[$((.-i1(k))...)][$k])) for k in 1:N)
return quote
Base.@_inline_meta
return @inbounds +($(wi...))
end
end
function div_op(a::AbstractArray{<:StaticVector{N},N}) where N
k = Kernel{ntuple(_->-1:1, ndims(a))}(k)
ae = extend(a, StaticKernels.ExtensionNothing())
return AppliedLinearKernel(k, ae)
end
function grad_op(a::AbstractArray)
k = Kernel{ntuple(_->0:1, ndims(a))}(k)
ae = extend(a, StaticKernels.ExtensionReplicate())
return AppliedLinearKernel(k, ae)
end
...@@ -49,18 +49,20 @@ function init(md::DualTVDDModel, alg::DualTVDDAlgorithm) ...@@ -49,18 +49,20 @@ function init(md::DualTVDDModel, alg::DualTVDDAlgorithm)
ptmp = extend(zeros(SVector{d,Float64}, size(md.f)), StaticKernels.ExtensionNothing()) ptmp = extend(zeros(SVector{d,Float64}, size(md.f)), StaticKernels.ExtensionNothing())
# precomputed global B # precomputed global B
#B = inv(md.A' * md.A + md.β * I) B = inv(md.A' * md.A + md.β * I)
B = diagm(ones(length(md.f))) + md.β * I #B = diagm(ones(length(md.f))) + md.β * I
# create subproblem contexts # create subproblem contexts
li = LinearIndices(size(md.f)) li = LinearIndices(size(md.f))
submds = [OpROFModel(subg[i], B, subα[i]) submds = [DualTVDDModel(subg[i], B, subα[i])
for i in CartesianIndices(subax)] for i in CartesianIndices(subax)]
subalg = ChambolleAlgorithm()
subalg = ProjGradAlgorithm(λ=1/sqrt(8)/norm(B))
#subalg = ChambolleAlgorithm()
subctx = [init(submds[i], subalg) for i in CartesianIndices(subax)] subctx = [init(submds[i], subalg) for i in CartesianIndices(subax)]
# subcontext B is identity # subcontext B is identity
B = inv(md.A' * md.A + md.β * I) #B = inv(md.A' * md.A + md.β * I)
return DualTVDDContext(md, alg, g, p, ptmp, B, subax, subg, subctx) return DualTVDDContext(md, alg, g, p, ptmp, B, subax, subg, subctx)
end end
...@@ -80,8 +82,8 @@ function step!(ctx::DualTVDDContext) ...@@ -80,8 +82,8 @@ function step!(ctx::DualTVDDContext)
# TODO: make p computation local! # TODO: make p computation local!
ctx.ptmp .= (1 .- theta.(Ref(ax), Ref(sax), Ref(overlap), CartesianIndices(ctx.p))) .* ctx.p ctx.ptmp .= (1 .- theta.(Ref(ax), Ref(sax), Ref(overlap), CartesianIndices(ctx.p))) .* ctx.p
map!(, sg, ctx.ptmp) #map!(kΛ, sg, ctx.ptmp)
sg .+= ctx.g #sg .+= ctx.g
for j in 1:1000 for j in 1:1000
step!(ctx.subctx[i]) step!(ctx.subctx[i])
......
"min_p 1/2 * |-div(p)-g|_B^2 + χ_{|p|_2<α}"
struct DualROFOpModel{Tg,TB,} <: Model
"dual data"
g::Tg
"B operator"
B::TB
"total variation parameter" 
α::
end
DualTVDDModel(g, B, α) = DualTVDDModel(g, B, α, nothing)
DualTVDDModel(g, B, α::Real) = DualTVDDModel(g, B, fill!(similar(g), α))
function recover_u(p, md::DualTVDDModel)
d = ndims(md.g)
u = similar(md.g)
v = similar(md.g)
@inline kfΛ(w) = @inbounds divergence(w)
= Kernel{ntuple(_->-1:1, d)}(kfΛ)
# v = div(p) + A'*f
map!(, v, p) # extension: nothing
v .+= md.g
# u = B * v
mul!(vec(u), md.B, vec(v))
return u
end
struct ProjGradAlgorithm <: Algorithm struct ProjGradAlgorithm <: Algorithm
"gradient step size" "gradient step size"
λ::Float64 τ::Float64
function ProjGradAlgorithm(; λ) function ProjGradAlgorithm(; τ)
return new(λ) return new(τ)
end end
end end
struct ProjGradContext{M,A,V,W,Wv,WvWv,R,S,K1,K2} struct ProjGradContext{M,A,Tp,Wv,R,S,K1,K2}
model::M model::M
algorithm::A algorithm::A
"dual optimization variable" "dual optimization variable"
p::V p::Tp
"precomputed A'f"
g::W
"extended model.λ"
λ::W
"scalar temporary 1" "scalar temporary 1"
rv::Wv rv::Wv
"scalar temporary 2" "scalar temporary 2"
sv::Wv sv::Wv
"precomputed (A'A + βI)^(-1)"
B::WvWv
"matrix view on rv" "matrix view on rv"
r::R r::R
...@@ -32,13 +26,12 @@ struct ProjGradContext{M,A,V,W,Wv,WvWv,R,S,K1,K2} ...@@ -32,13 +26,12 @@ struct ProjGradContext{M,A,V,W,Wv,WvWv,R,S,K1,K2}
k2::K2 k2::K2
end end
function init(md::OpROFModel, alg::ProjGradAlgorithm) function init(md::DualTVDDModel, alg::ProjGradAlgorithm)
d = ndims(md.g) d = ndims(md.g)
ax = axes(md.g) ax = axes(md.g)
p = extend(zeros(SVector{d,Float64}, ax), StaticKernels.ExtensionNothing()) p = extend(zeros(SVector{d,Float64}, ax), StaticKernels.ExtensionNothing())
g = extend(md.g, StaticKernels.ExtensionNothing()) g = extend(md.g, StaticKernels.ExtensionNothing())
λ = extend(md.λ, StaticKernels.ExtensionNothing())
rv = zeros(length(md.g)) rv = zeros(length(md.g))
sv = zeros(length(md.g)) sv = zeros(length(md.g))
...@@ -47,40 +40,26 @@ function init(md::OpROFModel, alg::ProjGradAlgorithm) ...@@ -47,40 +40,26 @@ function init(md::OpROFModel, alg::ProjGradAlgorithm)
s = extend(reshape(sv, ax), StaticKernels.ExtensionReplicate()) s = extend(reshape(sv, ax), StaticKernels.ExtensionReplicate())
z = zero(CartesianIndex{d}) z = zero(CartesianIndex{d})
@inline kf1(pw, gw) = @inbounds -divergence(pw) - gw[z]
@inline kf1(pw) = @inbounds -divergence(pw) - md.g[pw.position]
k1 = Kernel{ntuple(_->-1:1, d)}(kf1) k1 = Kernel{ntuple(_->-1:1, d)}(kf1)
@inline function kf2(pw, sw) @inline function kf2(pw, sw)
Base.@_inline_meta @inbounds q = pw[z] - alg.τ * gradient(sw)
q = pw[z] - alg.λ * gradient(sw) return @inbounds q / max(norm(q) / md.α[sw.position], 1)
return q / max(norm(q) / md.λ[sw.position], 1)
end end
k2 = Kernel{ntuple(_->0:1, d)}(kf2) k2 = Kernel{ntuple(_->0:1, d)}(kf2)
return ProjGradContext(md, alg, p, g, λ, rv, sv, md.B, r, s, k1, k2) return ProjGradContext(md, alg, p, rv, sv, r, s, k1, k2)
end end
function step!(ctx::ProjGradContext) function step!(ctx::ProjGradContext)
# r = Λ*p - g # r = Λ*p - g
map!(ctx.k1, ctx.r, ctx.p, ctx.g) map!(ctx.k1, ctx.r, ctx.p)
# s = B * r # s = B * r
mul!(ctx.sv, ctx.B, ctx.rv) mul!(ctx.sv, ctx.model.B, ctx.rv)
# p = proj(p - λΛ's) # p = proj(p - λΛ's)
map!(ctx.k2, ctx.p, ctx.p, ctx.s) map!(ctx.k2, ctx.p, ctx.p, ctx.s)
end end
function recover_u!(ctx::ProjGradContext) recover_u(ctx::ProjGradContext) = recover_u(ctx.p, ctx.model)
d = ndims(ctx.g)
u = similar(ctx.g)
v = similar(ctx.g)
@inline kfΛ(w) = @inbounds divergence(w)
= Kernel{ntuple(_->-1:1, d)}(kfΛ)
# v = div(p) + A'*f
map!(, v, ctx.p) # extension: nothing
v .+= ctx.g
# u = B * v
mul!(vec(u), ctx.B, vec(v))
return u
end
using Test
using LinearAlgebra
using DualTVDD:
DualTVDDModel, ProjGradAlgorithm, ChambolleAlgorithm,
init, step!, recover_u
g = Float64[0 2; 1 0]
md = DualTVDDModel(g, I, 1e-10)
for alg in (ProjGradAlgorithm(τ=1/8), ChambolleAlgorithm())
ctx = init(md, alg)
for i in 1:100
step!(ctx)
end
@test recover_u(ctx) g
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment