From 3ea29b47f1ce673ae4df1ec4e664bddc004876dc Mon Sep 17 00:00:00 2001
From: Stephan Hilb <stephan@ecshi.net>
Date: Thu, 7 May 2020 14:40:31 +0200
Subject: [PATCH] code: start anew

---
 DualTVDD/Manifest.toml     | 257 -------------------
 DualTVDD/Project.toml      |  17 --
 DualTVDD/src/DualTVDD.jl   | 166 -------------
 DualTVDD/src/fd.jl         | 450 ---------------------------------
 DualTVDD/src/filtering.jl  | 121 ---------
 DualTVDD/test/runtests.jl  |  31 ---
 Manifest.toml              | 230 -----------------
 Project.toml               |  18 +-
 chambollefp.jl             |  99 --------
 chambollefp_dd.jl          | 114 ---------
 fd.jl                      | 491 -------------------------------------
 filtering.jl               | 110 ---------
 src/DualTVDD.jl            |   5 +
 test.py                    | 134 ----------
 {DualTVDD => test}/repl.jl |   5 +-
 15 files changed, 21 insertions(+), 2227 deletions(-)
 delete mode 100644 DualTVDD/Manifest.toml
 delete mode 100644 DualTVDD/Project.toml
 delete mode 100644 DualTVDD/src/DualTVDD.jl
 delete mode 100644 DualTVDD/src/fd.jl
 delete mode 100644 DualTVDD/src/filtering.jl
 delete mode 100644 DualTVDD/test/runtests.jl
 delete mode 100644 Manifest.toml
 delete mode 100644 chambollefp.jl
 delete mode 100644 chambollefp_dd.jl
 delete mode 100644 fd.jl
 delete mode 100644 filtering.jl
 create mode 100644 src/DualTVDD.jl
 delete mode 100644 test.py
 rename {DualTVDD => test}/repl.jl (98%)

diff --git a/DualTVDD/Manifest.toml b/DualTVDD/Manifest.toml
deleted file mode 100644
index d95fb7f..0000000
--- a/DualTVDD/Manifest.toml
+++ /dev/null
@@ -1,257 +0,0 @@
-# This file is machine-generated - editing it directly is not advised
-
-[[Base64]]
-uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
-
-[[BinaryProvider]]
-deps = ["Libdl", "Logging", "SHA"]
-git-tree-sha1 = "c7361ce8a2129f20b0e05a89f7070820cfed6648"
-uuid = "b99e7846-7c00-51b0-8f62-c81ae34c0232"
-version = "0.5.6"
-
-[[ColorTypes]]
-deps = ["FixedPointNumbers", "Random"]
-git-tree-sha1 = "10050a24b09e8e41b951e9976b109871ce98d965"
-uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
-version = "0.8.0"
-
-[[Colors]]
-deps = ["ColorTypes", "FixedPointNumbers", "InteractiveUtils", "Printf", "Reexport"]
-git-tree-sha1 = "c9c1845d6bf22e34738bee65c357a69f416ed5d1"
-uuid = "5ae59095-9a9b-59fe-a467-6f913c188581"
-version = "0.9.6"
-
-[[Compat]]
-deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
-git-tree-sha1 = "84aa74986c5b9b898b0d1acaf3258741ee64754f"
-uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
-version = "2.1.0"
-
-[[Contour]]
-deps = ["LinearAlgebra", "StaticArrays", "Test"]
-git-tree-sha1 = "b974e164358fea753ef853ce7bad97afec15bb80"
-uuid = "d38c429a-6771-53c6-b99e-75d170b6e991"
-version = "0.5.1"
-
-[[DataAPI]]
-git-tree-sha1 = "8903f0219d3472543fc4b2f5ebaf675a07f817c0"
-uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
-version = "1.0.1"
-
-[[DataStructures]]
-deps = ["InteractiveUtils", "OrderedCollections"]
-git-tree-sha1 = "0809951a1774dc724da22d26e4289bbaab77809a"
-uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
-version = "0.17.0"
-
-[[Dates]]
-deps = ["Printf"]
-uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
-
-[[DelimitedFiles]]
-deps = ["Mmap"]
-uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"
-
-[[Distributed]]
-deps = ["Random", "Serialization", "Sockets"]
-uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"
-
-[[FFMPEG]]
-deps = ["BinaryProvider", "Libdl"]
-git-tree-sha1 = "1dd2128ff10894081f30931b355dc892d1352de9"
-uuid = "c87230d0-a227-11e9-1b43-d7ebe4e7570a"
-version = "0.2.2"
-
-[[FixedPointNumbers]]
-git-tree-sha1 = "d14a6fa5890ea3a7e5dcab6811114f132fec2b4b"
-uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
-version = "0.6.1"
-
-[[GR]]
-deps = ["Base64", "DelimitedFiles", "LinearAlgebra", "Pkg", "Printf", "Random", "Serialization", "Sockets", "Test"]
-git-tree-sha1 = "b4c31b6377b6d51b6c69a3a9737d10c34d43974e"
-uuid = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71"
-version = "0.41.0"
-
-[[GeometryTypes]]
-deps = ["ColorTypes", "FixedPointNumbers", "IterTools", "LinearAlgebra", "StaticArrays"]
-git-tree-sha1 = "4bf5706f3b9a2c5adbbc473c8c91582c1fa816a3"
-uuid = "4d00f742-c7ba-57c2-abde-4428a4b178cb"
-version = "0.7.6"
-
-[[InteractiveUtils]]
-deps = ["Markdown"]
-uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
-
-[[IterTools]]
-git-tree-sha1 = "2ebe60d7343962966d1779a74a760f13217a6901"
-uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
-version = "1.2.0"
-
-[[JSON]]
-deps = ["Dates", "Mmap", "Parsers", "Unicode"]
-git-tree-sha1 = "b34d7cef7b337321e97d22242c3c2b91f476748e"
-uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
-version = "0.21.0"
-
-[[LibGit2]]
-uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"
-
-[[Libdl]]
-uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
-
-[[LinearAlgebra]]
-deps = ["Libdl"]
-uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
-
-[[Logging]]
-uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
-
-[[Markdown]]
-deps = ["Base64"]
-uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
-
-[[Measures]]
-deps = ["Test"]
-git-tree-sha1 = "ddfd6d13e330beacdde2c80de27c1c671945e7d9"
-uuid = "442fdcdd-2543-5da2-b0f3-8c86c306513e"
-version = "0.3.0"
-
-[[Missings]]
-deps = ["SparseArrays", "Test"]
-git-tree-sha1 = "f0719736664b4358aa9ec173077d4285775f8007"
-uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
-version = "0.4.1"
-
-[[Mmap]]
-uuid = "a63ad114-7e13-5084-954f-fe012c677804"
-
-[[NaNMath]]
-deps = ["Compat"]
-git-tree-sha1 = "ce3b85e484a5d4c71dd5316215069311135fa9f2"
-uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
-version = "0.3.2"
-
-[[OffsetArrays]]
-git-tree-sha1 = "1af2f79c7eaac3e019a0de41ef63335ff26a0a57"
-uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
-version = "0.11.1"
-
-[[OrderedCollections]]
-deps = ["Random", "Serialization", "Test"]
-git-tree-sha1 = "c4c13474d23c60d20a67b217f1d7f22a40edf8f1"
-uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
-version = "1.1.0"
-
-[[Parsers]]
-deps = ["Dates", "Test"]
-git-tree-sha1 = "ef0af6c8601db18c282d092ccbd2f01f3f0cd70b"
-uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
-version = "0.3.7"
-
-[[Pkg]]
-deps = ["Dates", "LibGit2", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"]
-uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
-
-[[PlotThemes]]
-deps = ["PlotUtils", "Requires", "Test"]
-git-tree-sha1 = "f3afd2d58e1f6ac9be2cea46e4a9083ccc1d990b"
-uuid = "ccf2f8ad-2431-5c83-bf29-c5338b663b6a"
-version = "0.3.0"
-
-[[PlotUtils]]
-deps = ["Colors", "Dates", "Printf", "Random", "Reexport", "Test"]
-git-tree-sha1 = "8e87bbb778c26f575fbe47fd7a49c7b5ca37c0c6"
-uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043"
-version = "0.5.8"
-
-[[Plots]]
-deps = ["Base64", "Contour", "Dates", "FFMPEG", "FixedPointNumbers", "GR", "GeometryTypes", "JSON", "LinearAlgebra", "Measures", "NaNMath", "Pkg", "PlotThemes", "PlotUtils", "Printf", "REPL", "Random", "RecipesBase", "Reexport", "Requires", "Showoff", "SparseArrays", "Statistics", "StatsBase", "UUIDs"]
-git-tree-sha1 = "f2aa8a7b5bc0ccec57a1237a97b6f59fc8d9ef57"
-uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
-version = "0.26.2"
-
-[[Printf]]
-deps = ["Unicode"]
-uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
-
-[[REPL]]
-deps = ["InteractiveUtils", "Markdown", "Sockets"]
-uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
-
-[[Random]]
-deps = ["Serialization"]
-uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
-
-[[RecipesBase]]
-git-tree-sha1 = "7bdce29bc9b2f5660a6e5e64d64d91ec941f6aa2"
-uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
-version = "0.7.0"
-
-[[Reexport]]
-deps = ["Pkg"]
-git-tree-sha1 = "7b1d07f411bc8ddb7977ec7f377b97b158514fe0"
-uuid = "189a3867-3050-52da-a836-e630ba90ab69"
-version = "0.2.0"
-
-[[Requires]]
-deps = ["Test"]
-git-tree-sha1 = "f6fbf4ba64d295e146e49e021207993b6b48c7d1"
-uuid = "ae029012-a4dd-5104-9daa-d747884805df"
-version = "0.5.2"
-
-[[SHA]]
-uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
-
-[[Serialization]]
-uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
-
-[[SharedArrays]]
-deps = ["Distributed", "Mmap", "Random", "Serialization"]
-uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"
-
-[[Showoff]]
-deps = ["Dates"]
-git-tree-sha1 = "e032c9df551fb23c9f98ae1064de074111b7bc39"
-uuid = "992d4aef-0814-514b-bc4d-f2e9a6c4116f"
-version = "0.3.1"
-
-[[Sockets]]
-uuid = "6462fe0b-24de-5631-8697-dd941f90decc"
-
-[[SortingAlgorithms]]
-deps = ["DataStructures", "Random", "Test"]
-git-tree-sha1 = "03f5898c9959f8115e30bc7226ada7d0df554ddd"
-uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c"
-version = "0.3.1"
-
-[[SparseArrays]]
-deps = ["LinearAlgebra", "Random"]
-uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
-
-[[StaticArrays]]
-deps = ["LinearAlgebra", "Random", "Statistics"]
-git-tree-sha1 = "db23bbf50064c582b6f2b9b043c8e7e98ea8c0c6"
-uuid = "90137ffa-7385-5640-81b9-e52037218182"
-version = "0.11.0"
-
-[[Statistics]]
-deps = ["LinearAlgebra", "SparseArrays"]
-uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
-
-[[StatsBase]]
-deps = ["DataAPI", "DataStructures", "LinearAlgebra", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics"]
-git-tree-sha1 = "c53e809e63fe5cf5de13632090bc3520649c9950"
-uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
-version = "0.32.0"
-
-[[Test]]
-deps = ["Distributed", "InteractiveUtils", "Logging", "Random"]
-uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
-
-[[UUIDs]]
-deps = ["Random", "SHA"]
-uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
-
-[[Unicode]]
-uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
diff --git a/DualTVDD/Project.toml b/DualTVDD/Project.toml
deleted file mode 100644
index dfc958c..0000000
--- a/DualTVDD/Project.toml
+++ /dev/null
@@ -1,17 +0,0 @@
-name = "DualTVDD"
-uuid = "e723064d-6d9e-46ff-9d98-de992daae271"
-authors = ["Stephan Hilb <stephan.hilb@mathematik.uni-stuttgart.de>"]
-version = "0.1.0"
-
-[deps]
-Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
-LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
-OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
-Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
-StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
-
-[extras]
-Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
-
-[targets]
-test = ["Test"]
diff --git a/DualTVDD/src/DualTVDD.jl b/DualTVDD/src/DualTVDD.jl
deleted file mode 100644
index b28348c..0000000
--- a/DualTVDD/src/DualTVDD.jl
+++ /dev/null
@@ -1,166 +0,0 @@
-module DualTVDD
-
-include("filtering.jl")
-include("fd.jl")
-
-# import
-
-using LinearAlgebra: I
-using .FD
-using .FD: parts
-
-# export
-
-export MetaGrid, GridFunction
-export dditer
-
-# algorithm
-
-
-"""
-    partition_function(metagrid, idx...)
-
-Create (global) partition of unity grid function θ.
-"""
-function partition_function(metagrid, idx...)
-    function h(r, rp, i)
-        if i <= first(r) + metagrid.overlap
-            return 1.0
-        elseif i >= last(r) - metagrid.overlap
-            return 1.0
-        else
-            return (min(i - first(rp), last(rp) - i, metagrid.overlap) + 1) / (metagrid.overlap + 1)
-        end
-    end
-
-    vidx = FD.indices(metagrid, idx...)
-    ctheta = map(x -> [h(x[1], x[2], i) for i in x[2]], zip(axes(metagrid), vidx))
-
-    dst = zeros(size(metagrid))
-    for (I,J) in zip(CartesianIndices(vidx), CartesianIndices(length.(vidx)))
-        dst[I] = prod(getindex.(ctheta, Tuple(J)))
-    end
-    FD.GridFunction(metagrid, dst)
-end
-
-
-"""
-    p2u
-
-Compute primal solution from dual solution according to the optimality conditions.
-"""
-function p2u(grid, p, A, f; α, β)
-    FD.GridFunction(grid, (A'*A + β*I) \ (FD.divergence_z(p).data[:] .+ A'*f.data[:]))
-end
-
-
-"""
-    fpiter(grid, p, wg, A, α, β, τ)
-
-Fixed point iteration (Chambolle).
-"""
-# TODO: investigate matrix multiplication performance with GridFunctions
-function fpiter(grid, p, wg, A, α, β, τ)
-    # applying B is a global operation
-    x = FD.GridFunction(FD.parent(grid),
-                        (A'*A + β*I) \ (FD.divergence_z(FD.zero_continuation(p)) + wg).data[:])
-
-    q = FD.GridFunction(grid, view(FD.gradient(x).data, grid.indices..., :))
-    ## this misses out on the right boundary
-    #q = FD.gradient(FD.GridFunction(grid, view(x.data, grid.indices..., :)))
-
-    p_new = FD.GridFunction(grid, FD.pwmul(FD.GridFunction(grid, α ./ (α .+ τ .* FD.pw_norm(q))),
-                                           FD.GridFunction(grid, p .+ τ .* q)))
-
-    reserr = maximum(sum((p_new .- p).^2, dims=ndims(p)))
-    (p_new, reserr)
-end
-
-"""
-fpalg(grid, w, A, p = 0; α, β, τ = 1/8, ϵ = 1e-4)
-
-Fixed point algorithm (Chambolle), calling `fpiter`.
-"""
-function fpalg(grid, w, A, p = FD.GridFunction(x -> 0, ndims(grid), grid);
-               α, β, τ = 1/8, ϵ = 1e-4)
-    reserr = 1
-    k = 0
-    while reserr > ϵ && k < 10e1
-        k += 1
-        (p, reserr) = fpiter(grid, p, w, A, α, β, τ)
-    end
-    return p
-end
-
-
-"""
-    dditer(metagrid, p, A, f; α, β, τ, ϵ, ρ)
-
-Domain decomposition algorithm, calling `fpalg` for each part.
-"""
-function dditer(metagrid, p, A, f; α, β, τ, ϵ, ρ)
-    # contribution from previous step
-    p̂ = (1-ρ) * p
-
-    for part in parts(metagrid)
-        grid = view(metagrid, Tuple(part)...)
-
-        fv = view(f, Tuple(part)...)
-
-        p̂v = view(p̂, Tuple(part)...)
-        pv = view(p, Tuple(part)...)
-
-        θ = partition_function(metagrid, Tuple(part)...)
-        θv = view(θ, Tuple(part)...)
-
-        w = FD.GridFunction(metagrid, A' * f.data[:]) +
-            FD.divergence(FD.pwmul(FD.GridFunction(metagrid, 1 .- θ), p))
-        wv = view(w, Tuple(part)...)
-
-        p̂v .+= ρ .* fpalg(grid, w, A, α=α*θv, β=β, τ=τ, ϵ=ϵ)
-    end
-    p .= p̂
-
-    # reconstruct primal solution and plot (FIXME: hack)
-    u = p2u(metagrid, p, A, f, α=α, β=β)
-    FD.my_plot(u)
-    #FD.my_plot(FD.pw_norm(p))
-end
-
-using LinearAlgebra: norm
-using SparseArrays: spdiagm
-"""
-    ddalg()
-
-temporary run function
-"""
-function ddalg()
-    grid = MetaGrid((0:0.01:1, 0:0.01:1), (1, 1), 5)
-
-    f = GridFunction(x -> float(norm(x .- [0.5, 0.5]) < 0.4), 1, grid)
-    g = GridFunction(x -> norm(x), 2, grid)
-
-    A = I
-    #A = spdiagm(0 => ones(length(grid)), 1 => 0.3 * ones(length(grid)-1))
-    #f = GridFunction(grid, A * f.data[:])
-
-    "TV parameter"
-    α=1
-    "L2 parameter"
-    β=0
-    "chambolle weighing"
-    τ=1/8
-    "residual error tolerance for local problem"
-    ϵ=1e-9
-    "???"
-    ρ=0.5
-
-    p = GridFunction(x -> 0, 2, grid)
-
-    for n = 1:10
-      println("iteration $n")
-      dditer(grid, p, A, f, α=α, β=β, τ=τ, ϵ=ϵ, ρ=ρ)
-    end
-end
-
-end # module
diff --git a/DualTVDD/src/fd.jl b/DualTVDD/src/fd.jl
deleted file mode 100644
index 6829c9e..0000000
--- a/DualTVDD/src/fd.jl
+++ /dev/null
@@ -1,450 +0,0 @@
-module FD
-
-# local modules
-"Finite Differences"
-module FDiff
-
-using OffsetArrays: OffsetVector
-
-const forward = OffsetVector([-1, 1], -1)
-const backward = OffsetVector([-1, 1], -2)
-const central = OffsetVector([-1, 0, 1], -2)
-
-end # module FDiff
-
-
-# imports
-using StaticArrays: SVector
-using OffsetArrays
-import Colors: Gray
-import Plots: plot
-# local imports
-using ..Filtering
-using .FDiff
-
-# export
-export Grid, MetaGrid, GridView, GridFunction
-export grid
-export ⊙
-
-
-# abstract types
-"""
-    AbstractGrid{d}
-
-Abstract rectangular grid type, allowing positional indexing of coordinates.
-"""
-abstract type AbstractGrid{d} <: AbstractArray{SVector{d,Float64}, d} end
-
-Base.IndexStyle(::Type{<:AbstractGrid}) = IndexCartesian()
-
-Base.parent(grid::AbstractGrid) = grid
-
-
-"""
-    _fdkern(fd, ndims, dir)
-
-Create from `fd` a finite difference kernel usable in `ndims` dimension
-oriented in dimension `dir`.
-"""
-# TODO: make it typestable on ndims
-function _fdkern(fd::OffsetArray{T,1}, ndims::Integer, dir::Integer) where T
-    1 <= dir <= ndims || throw(ArgumentError("dimesion $(dir) out of range 1:$(ndims)"))
-
-    offsets = ntuple(i -> i == dir ? axes(fd, 1) : (0:0), ndims)
-    out = OffsetArray{T}(undef, offsets)
-    # copyto! from OffsetArray fails, broadcasting for differing axes fails too
-    # workaround: copyto! from parent
-    copyto!(out, fd.parent)
-end
-
-
-# Grid
-
-
-# aka `StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}`
-const FDStepRange = typeof(0:0.1:1)
-
-struct Grid{d} <: AbstractGrid{d}
-    domain::NTuple{d, FDStepRange}
-end
-Grid(domain::FDStepRange...) = Grid(domain)
-
-# TODO: somehow display(grid) is incorrect for d = 1 ?
-Base.show(io::IO, grid::Grid) =
-    print(io, "Grid from $(first.(grid.domain)) to $(last.(grid.domain)) ",
-              "with spacing $(step.(grid.domain))")
-
-Base.size(grid::Grid) = ntuple(i -> grid.domain[i].len, ndims(grid))
-
-Base.iterate(grid::Grid) = iterate(Iterators.product(grid.domain...))
-Base.iterate(grid::Grid, state) = iterate(Iterators.product(grid.domain...), state)
-
-Base.getindex(grid::Grid, idx...) = SVector(getindex.(grid.domain, idx))
-Base.getindex(grid::Grid, I::CartesianIndex) = SVector(getindex.(grid.domain, Tuple(I)))
-
-
-# GridView
-
-struct GridView{d} <: AbstractGrid{d}
-    grid::AbstractGrid{d}
-    indices::NTuple{d, UnitRange{Int}}
-end
-GridView(grid, indices::UnitRange{Int}...) = GridView(grid, indices)
-
-Base.size(grid::GridView) = length.(grid.indices)
-
-# FIXME: this is terrible!
-Base.iterate(grid::GridView) = iterate(grid.grid[I] for I in CartesianIndices(grid.indices))
-Base.iterate(grid::GridView, state) = iterate([grid.grid[I] for I in CartesianIndices(grid.indices)], state)
-
-Base.getindex(grid::GridView, idx...) = getindex(grid.grid, (first.(grid.indices) .- 1 .+ Base.to_indices(grid, idx))...)
-
-Base.parent(grid::GridView) = grid.grid
-
-
-# MetaGrid (partitioned Grid)
-
-
-struct MetaGrid{d} <: AbstractGrid{d}
-    grid::Grid{d}
-    indices::Array{NTuple{d, UnitRange{Int}}, d}
-    overlap::Int
-end
-
-MetaGrid(domain::NTuple{d, FDStepRange}, pnum, overlap) where d = MetaGrid(Grid(domain), pnum, overlap)
-
-"""
-    MetaGrid(domain::NTuple{d, FDStepRange}, pnum::NTuple{d, Int}, overlap::Int)
-    MetaGrid(grid::Grid{d}, pnum::NTuple{d, Int}, overlap::Int)
-
-Create a grid on `domain`, partitioned along dimensions according to `pnum`
-respecting `overlap`.
-"""
-function MetaGrid(grid::Grid{d}, pnum::NTuple{d, Int}, overlap::Int) where d
-    tsize = size(grid) .+ (pnum .- 1) .* overlap
-
-    psize = tsize .÷ pnum
-    osize = tsize .- pnum .* psize
-
-    overhang(I, j) = I[j] == pnum[j] ? osize[j] : 0
-
-    indices = Array{NTuple{d, UnitRange{Int}}, d}(undef, pnum)
-    for I in CartesianIndices(pnum)
-        indices[I] = ntuple(j -> ((I[j] - 1) * psize[j] - (I[j] - 1) * overlap + 1) :
-                            (      I[j] * psize[j] - (I[j] - 1) * overlap + overhang(I, j)), d)
-    end
-    MetaGrid{d}(grid, indices, overlap)
-end
-
-Base.size(grid::MetaGrid) = size(grid.grid)
-
-Base.iterate(grid::MetaGrid) = iterate(grid.grid)
-Base.iterate(grid::MetaGrid, state) = iterate(grid.grid, state)
-
-Base.getindex(grid::MetaGrid, idx...) = getindex(grid.grid, idx...)
-
-indices(grid::MetaGrid, idx...) = getindex(grid.indices, idx...)
-
-Base.view(grid::MetaGrid, idx...) = GridView(grid, indices(grid, idx...))
-
-parts(grid::MetaGrid) = CartesianIndices(grid.indices)
-
-
-# GridFunction
-
-
-abstract type AbstractGridFunction{G,T,d,n} <: AbstractArray{T,1} end
-
-rangedim(f::AbstractGridFunction{G,T,d,n}) where {G,T,d,n} = n
-domaindim(f::AbstractGridFunction{G,T,d}) where {G,T,d} = d
-
-Base.similar(f::F, ::Type{S}, dims::Int...) where {F<:AbstractGridFunction, S} =
-    GridFunction(f.grid, Array{S}(undef, length(f)))
-Base.parent(f::AbstractGridFunction) = f
-
-
-"""
-    GridFunction{G<:AbstractGrid,T,d,n,D}
-
-`n`-dimensional function values of type `T` on a Grid `G` of dimension `d`.
-
-`D == d + 1`
-"""
-struct GridFunction{G<:AbstractGrid,T,d,n,A,D} <: AbstractGridFunction{G,T,d,n}
-    grid::G
-    data::A
-    function GridFunction{G,T,d,n,A,D}(grid::G, data::A) where {G,T,d,n,D,A<:AbstractArray{T,D}}
-        D == d + 1 || throw(ArgumentError)
-        d == ndims(grid) || throw(ArgumentError)
-        (size(grid)..., n) == size(data) || throw(ArgumentError("data does not match given size"))
-        new(grid, data)
-    end
-end
-# TODO: check size(data)
-#GridFunction(grid::G, data::AbstractArray{T,D}) where {d,G<:AbstractGrid{d},T,D} =
-#    GridFunction{G,T,d,size(data, d+1),typeof(data),d+1}(grid, data)
-function GridFunction(grid::G, data::AbstractArray, ::Val{n}) where {d,G<:AbstractGrid{d}, n}
-    if length(data) == length(grid) && n > 1
-        data = repeat(data, inner=ntuple(i -> i == d+1 ? n : 1, d+1))
-    elseif size(data) != (size(grid)..., n)
-        data = reshape(data, size(grid)..., n)
-    end
-    length(data) ÷ length(grid) == n || throw(ArgumentError("dimensions don't match"))
-    GridFunction{G,eltype(data),d,n,typeof(data),d+1}(grid, data)
-end
-function GridFunction(grid::G, data::AbstractArray) where {d,G<:AbstractGrid{d}}
-    GridFunction(grid, data, Val(length(data) ÷ length(grid)))
-end
-function GridFunction(f::Function, n::Int, grid::G) where {G<:AbstractGrid}
-    data = Array{Float64,ndims(grid)+1}(undef, size(grid)..., n)
-    for I in CartesianIndices(grid)
-        data[Tuple(I)..., :] .= f(grid[I])
-    end
-    GridFunction(grid, data)
-end
-function GridFunction(grid::AbstractGrid, ::UndefInitializer, ::Val{n} = Val(1)) where n
-    data = Array{Float64}(undef, size(grid)..., 1)
-    GridFunction{typeof(grid), eltype(data), ndims(grid), n, typeof(data), ndims(grid) + 1}(grid, data)
-end
-
-Base.show(io::IO, f::GridFunction) =
-    print(io, "$(rangedim(f))d-GridFunction on $(f.grid), $(length(f)) dofs")
-
-Base.size(f::GridFunction) = (length(f.data),)
-#Base.axes(f::GridFunction) = (Base.OneTo(length(f.data)),)
-Base.getindex(f::GridFunction, idx::Int) = getindex(f.data, idx)
-Base.setindex!(f::GridFunction, v, idx::Int) = setindex!(f.data, v, idx)
-
-## TODO: using this prints #undef for display(f)
-#Base.getindex(f::GridFunction, idx...) = getindex(f.data, idx...)
-#Base.setindex!(f::GridFunction, v, idx...) = setindex!(f.data, v, idx...)
-
-#Base.IndexStyle(::Type{<:GridFunction}) = IndexLinear()
-
-#component(f::GridFunction, k::Integer) = view(reshape(f.data,
-grid(f::GridFunction) = f.grid
-
-# TODO: call is different, so maybe make thin not part of Base
-Base.view(f::AbstractGridFunction{G}, idx...) where G<:MetaGrid =
-GridFunction(GridView(f.grid, indices(f.grid, idx...)), view(f.data, indices(f.grid, idx...)..., :))
-
-function zero_continuation(f::GridFunction{<:GridView})
-    dst = GridFunction(parent(grid(f)), zeros(size(parent(grid(f)))..., rangedim(f)), Val(rangedim(f)))
-    dst.data[grid(f).indices..., :] .= f.data
-    dst
-end
-
-Base.parent(f::GridFunction{<:GridView,<:Any,<:Any,<:Any,<:SubArray}) =
-GridFunction(parent(grid(f)), parent(f.data))
-
-
-
-
-
-function _check_shape(a::GridFunction, b::GridFunction)
-    #TODO: why does fv.grid == gv.grid fail?
-    a.grid === b.grid || throw(ArgumentError("grids need to match"))
-    rangedim(a) == rangedim(b) || throw(ArgumentError("range dimensions need to match"))
-end
-
-Base.:*(a::Number, b::GridFunction) = GridFunction(b.grid, a * b.data, Val(rangedim(b)))
-Base.:-(a::GridFunction) = GridFunction(a.grid, -a.data, Val(rangedim(a)))
-function Base.:+(a::GridFunction, b::GridFunction)
-    _check_shape(a, b)
-    GridFunction(a.grid, a.data .+ b.data, Val(rangedim(a)))
-end
-function Base.:-(a::GridFunction, b::GridFunction)
-    _check_shape(a, b)
-    GridFunction(a.grid, a.data .- b.data, Val(rangedim(a)))
-end
-
-
-function Base.:+(a::GridFunction, b::GridFunction{<:GridView})
-    a.grid == parent(b.grid) || throw(ArgumentError("grids need to match"))
-    data = copy(a.data)
-    datav = view(data, b.grid.indices..., :)
-    datav .+= b.data
-    GridFunction(a.grid, data, Val(rangedim(a)))
-end
-Base.:+(a::GridFunction{<:GridView}, b::GridFunction) = b + a
-
-# TODO: cleanup! use metaprogramming
-function Base.:+(a::GridFunction{<:GridView}, b::GridFunction{<:GridView})
-    _check_shape(a, b)
-    GridFunction(a.grid, a.data .+ b.data, Val(rangedim(a)))
-end
-# TODO: cleanup! use metaprogramming
-function Base.:-(a::GridFunction{<:GridView}, b::GridFunction{<:GridView})
-    _check_shape(a, b)
-    GridFunction(a.grid, a.data .- b.data, Val(rangedim(a)))
-end
-
-function Base.:-(a::GridFunction, b::GridFunction{<:GridView})
-    a.grid == parent(b.grid) || throw(ArgumentError("grids need to match"))
-    data = copy(a.data)
-    datav = view(data, b.grid.indices..., :)
-    datav .-= b.data
-    GridFunction(a.grid, data, Val(rangedim(a)))
-end
-Base.:-(a::GridFunction{<:GridView}, b::GridFunction) = b - a
-
-
-
-function my_plot(img::FD.GridFunction{G,T,d,1}) where {G,T,d}
-    print("Plotting ... ")
-
-    data = reshape(copy(img.data), size(img.grid))
-
-    min = minimum(data)
-    max = maximum(data)
-    @show min, max
-
-    #@. data = (data - min) / (max - min)
-    @. data = clamp(data, 0, 1)
-
-    plot(Gray.(data), show=true)
-end
-
-
-## Operations
-
-function fdfilter(f::GridFunction{G,T,d,n}, kern) where {G,T,d,n}
-    A = zeros(axes(f.data))
-    for k = 1:n
-        vidx = ntuple(i -> i <= d ? Colon() : k, d + 1)
-        imfilter!(view(A, vidx...), f.data[vidx...], kern)
-    end
-    GridFunction(f.grid, A)
-end
-
-derivate(f::GridFunction, dir::Integer; fd = FDiff.forward) = fdfilter(f, _fdkern(fd, ndims(f.grid), dir))
-
-
-# currently gradient and divergence are specific implementations (as needed for
-# chambolles algorithm), other choices may be reasonable
-
-function gradient(f::GridFunction{G,T,d,1}; fd=FDiff.forward) where {G,T,d}
-    #A = Array{T,d+1}(undef, (size(f.grid)..., d))
-    A = zeros(size(f.grid)..., d)
-    for k = 1:d
-        vidx = ntuple(i -> i <= d ? Colon() : k, d + 1)
-        vidx2 = ntuple(i -> i <= d ? Colon() : 1, d + 1)
-        imfilter!(view(A, vidx...), f.data[vidx2...], bidx -> trim_kernel(_fdkern(fd, ndims(f.grid), k), bidx))
-        # FIXME: hackish, only for forward fd
-        selectdim(selectdim(A, d+1, k), k, size(f.grid, k)) .= 0
-    end
-    GridFunction(f.grid, A)
-end
-
-function divergence(f::GridFunction{G,T,d}; fd=FDiff.backward) where {d,G<:AbstractGrid{d},T}
-    A = zeros(size(f.grid)..., 1)
-    for k = 1:d
-        vidx = ntuple(i -> i <= d ? Colon() : 1, d + 1)
-        vidx2 = ntuple(i -> i <= d ? Colon() : k, d + 1)
-        imfilter!(view(A, vidx...), f.data[vidx2...], bidx -> trim_kernel(_fdkern(fd, ndims(f.grid), k), bidx))
-    end
-    GridFunction(f.grid, A)
-end
-
-function _kernel_zero_right_bnd!(kernel, bidx, k)
-    if bidx[k] == 1
-        kernel[ntuple(i -> 0, ndims(kernel))...] = 0
-    end
-    kernel
-end
-
-function gradient_c(f::GridFunction{G,T,d,1}; fd=FDiff.forward) where {G,T,d}
-    #A = Array{T,d+1}(undef, (size(f.grid)..., d))
-    A = zeros(size(f.grid)..., d)
-    for k = 1:d
-        vidx = ntuple(i -> i <= d ? Colon() : k, d + 1)
-        vidx2 = ntuple(i -> i <= d ? Colon() : 1, d + 1)
-        imfilter!(view(A, vidx...), f.data[vidx2...], bidx -> trim_kernel(_fdkern(fd, ndims(f.grid), k), bidx))
-    end
-    GridFunction(f.grid, A)
-end
-
-function divergence_z(f::GridFunction{G,T,d}; fd=FDiff.backward) where {d,G<:AbstractGrid{d},T}
-    A = zeros(size(f.grid)..., 1)
-    for k = 1:d
-        vidx = ntuple(i -> i <= d ? Colon() : 1, d + 1)
-        vidx2 = ntuple(i -> i <= d ? Colon() : k, d + 1)
-        imfilter!(view(A, vidx...), f.data[vidx2...], bidx -> _kernel_zero_right_bnd!(trim_kernel(_fdkern(fd, ndims(f.grid), k), bidx), bidx, k))
-    end
-    GridFunction(f.grid, A)
-end
-
-function pw_norm(f::GridFunction)
-    d = domaindim(f)
-    GridFunction(f.grid, sqrt.(sum(f.data.^2, dims=d+1)), Val(1))
-end
-
-function my_norm(f::GridFunction)
-    d = domaindim(f)
-    GridFunction(f.grid, sqrt.(sum(f.data.^2, dims=d+1)), Val(rangedim(f)))
-end
-
-"""
-    f ⊙ g
-
-Pointwise multiplication of two gridfunctions.
-"""
-⊙(f, g) = pwmul(f, g)
-pwmul(f::GridFunction{G,T,d,n}, g::GridFunction{G,T,d,n}) where {G,T,d,n} = f .* g
-function pwmul(f::GridFunction{G,T,d,1}, g::GridFunction{G,T,d}) where {G,T,d}
-    # TODO: maybe comprehension? Or make broadcast work
-    data = similar(g.data)
-    for I in CartesianIndices(f.grid)
-        data[Tuple(I)..., :] = f.data[Tuple(I)..., 1] * g.data[Tuple(I)..., :]
-    end
-    GridFunction(f.grid, data, Val(rangedim(g)))
-end
-
-"""
-    neuman_kernel(kernel, bidx)
-
-Modify `kernel` to be usable on a boundary `bixd` in a neumannn boundary setting.
-
-Ghost nodes are eliminated through central finite differences. Currently only
-the basic 5-star Laplace is supported.
-"""
-function neumann_kernel(kernel, bidx)
-    out = copy(kernel)
-    for (i, bi) in enumerate(bidx)
-        colrange = (i == j ? Colon() : 0 for j in 1:ndims(kernel))
-        out[colrange...] .+= bi * FDiff.forward
-    end
-    out /= 2 ^ count(!iszero, bidx)
-    trim_kernel(out, bidx)
-end
-
-function my_norm(grid::Grid, img::AbstractArray)
-    img = reshape(img, (size(grid)..., ndims(grid)))
-    d = ndims(grid)
-
-    out = sqrt.(sum(img.^2, dims=d+1))
-    out = repeat(out, inner=ntuple(i -> i == d+1 ? size(img, d+1) : 1, d+1))
-
-    out[:]
-end
-
-
-function apply(kern, flatimg, size)
-    img = reshape(flatimg, size)
-    out = imfilter(kern, img)
-    reshape(out, prod(size))
-end
-
-function solve(pde, shape::NTuple{d, Integer}) where d
-    ndims(pde.stencil) == d || throw("dimension mismatch")
-    length(pde.f) == prod(shape) || throw("bounds mismatch")
-
-    A = LinearMap{Float64}(u -> apply(pde.stencil, u, shape), prod(shape), issymmetric=true, isposdef=true)
-
-    u = zeros(prod(shape))
-    cg!(u, A, pde.f)
-    return u
-end
-
-end
diff --git a/DualTVDD/src/filtering.jl b/DualTVDD/src/filtering.jl
deleted file mode 100644
index e9adaeb..0000000
--- a/DualTVDD/src/filtering.jl
+++ /dev/null
@@ -1,121 +0,0 @@
-module Filtering
-
-import OffsetArrays
-
-export imfilter, imfilter!, trim_kernel
-
-const Kernel{T, N} = OffsetArrays.OffsetArray{T, N}
-
-@inline function _shift_range(range, shift::Integer)
-    Base.UnitRange(first(range)+shift : last(range)+shift)
-end
-
-## TODO: allow border to be tuple
-#function _innershift_indices(a::Array{T, d}, shift::NTuple{d, Integer}, border = abs(maximum(shift))) where {T, d}
-#  abs(maximum(shift)) <= border || throw("cannot shift by $shift with border $border")
-#  range(i) = (1 + shift[i] + border):(lastindex(a, i) + shift[i] - border)
-#  ntuple(range, Val(ndims(a)))
-#end
-#
-#function innershift_view(a::Array{T, d}, shift::NTuple{d, Integer} = ntuple(i -> 0, d), border = abs(maximum(shift))) where {T, d}
-#  view(a, _innershift_indices(a, shift, border)...)
-#end
-
-@inline function _bidx_to_range(range::AbstractUnitRange, bidx::Integer)
-    if bidx > 0
-        return last(range):last(range)
-    elseif bidx < 0
-        return first(range):first(range)
-    else
-        return first(range)+1:last(range)-1
-    end
-end
-
-
-function _trim_kernel_axes(kaxes, bidx)
-    ntuple(Val(length(kaxes))) do i
-        if bidx[i] < 0
-            return 0:last(kaxes[i])
-        elseif bidx[i] > 0
-            return first(kaxes[i]):0
-        elseif kaxes[i] isa Integer # FIXME: dirty!
-            return UnitRange(kaxes[i], kaxes[i])
-        else
-            return UnitRange(kaxes[i])
-        end
-    end
-end
-
-"""
-    trim_kernel(kernel, bidx)
-
-Trim `kernel` so that the resulting kernel is usable on the boundary indicated
-by `bidx`, i.e. replace evaluations that would occur outside the boundary with
-zero.
-"""
-function trim_kernel(kernel, bidx)
-    dst_axes = _trim_kernel_axes(axes(kernel), bidx)
-    out = similar(kernel, dst_axes)
-    range = CartesianIndices(out)
-    copyto!(out, range, kernel, range)
-end
-
-
-"""
-    imfilter!(dst, img, kern, slice=:inner)
-
-Filters `img` applying `kern` and write to `dst`. `dst` is added to, so you
-should initialize with zeros.
-Only `dst[slice]` is written to.
-"""
-# TODO: weirdly Base.Slice instead of Base.UnitRange leads to axes that are not
-# compatible when broadcasting, figure out why!
-function imfilter!(dst, img, kern, slice=_bidx_to_range.(axes(img), ntuple(i->0, ndims(img))))
-    ndims(dst) == ndims(img) == ndims(kern) == length(slice) ||
-        throw(ArgumentError("dst, image, kernel or slice dimensions disagree " *
-                            "($(ndims(dst)) vs $(ndims(img)) vs $(ndims(kern)) vs $(length(slice)))"))
-    axes(dst) == axes(img) ||
-        throw(ArgumentError("output and input image axes disagree"))
-    all(size(kern) .<= size(img)) ||
-        throw(ArgumentError("kernel bigger than image"))
-    last.(slice) .+ last.(axes(kern)) <= last.(axes(img)) ||
-    first.(slice) .+ first.(axes(kern)) >= first.(axes(img)) ||
-        throw(ArgumentError("kernel and/or slice out of bounds for image"))
-
-    dstslice = view(dst, UnitRange.(slice)...)
-    for i in CartesianIndices(kern)
-        iszero(kern[i]) && continue
-        dstslice .+= kern[i] .* view(img, (_shift_range.(slice, Tuple(i)))...)
-    end
-    dst
-end
-
-function imfilter!(dst, img, kernelf::Function)
-    for bidx in Iterators.product(ntuple(i -> -1:1, ndims(img))...)
-        range = _bidx_to_range.(axes(img), bidx)
-        imfilter!(dst, img, kernelf(bidx), range)
-    end
-    dst
-end
-
-"""
-    imfilter(img, kernelf)
-
-Filter `img` using a kernel function `kernelf(bidx) = kernel` specifying a
-kernel for each type of node.
-"""
-# need zeros here, because `imfilter!` acts additively
-imfilter(img, kernelf::Function) = imfilter!(zeros(size(img)), img, kernelf)
-
-
-"""
-    imfilter(img, kern)
-
-Filter `img` by `kern` using correlation.
-Boundary is zeroed out.
-"""
-imfilter(img, kern) = imfilter(img, bidx -> trim_kernel(kern, bidx))
-# TODO: use keyword arguments
-imfilter_inner(img, kern) = imfilter(img, bidx -> all(bidx .== 0) ? kern : zeros(ntuple(i -> 0, ndims(img))))
-
-end
diff --git a/DualTVDD/test/runtests.jl b/DualTVDD/test/runtests.jl
deleted file mode 100644
index 74a4996..0000000
--- a/DualTVDD/test/runtests.jl
+++ /dev/null
@@ -1,31 +0,0 @@
-using Test
-
-using DualTVDD
-using LinearAlgebra
-
-grid = MetaGrid((0:0.01:1, 0:0.01:1), (2, 2), 5)
-
-A = I
-#A = spdiagm(0 => ones(length(grid)), 1 => ones(length(grid)-1) )
-#f = A * f
-
-f = GridFunction(x -> float(norm(x .- [0.5, 0.5]) < 0.4), 1, grid)
-g = GridFunction(x -> norm(x), 2, grid)
-
-"TV parameter"
-α=100000
-"L2 parameter"
-β=0
-"???"
-τ=1/8
-"residual error tolerance for local problem"
-ϵ=1e-9
-"???"
-ρ=0.5
-
-p = GridFunction(x -> 0, 2, grid)
-
-for n = 1:10
-  println("iteration $n")
-  dditer(grid, p, A, f, α=α, β=β, τ=τ, ϵ=ϵ, ρ=ρ)
-end
diff --git a/Manifest.toml b/Manifest.toml
deleted file mode 100644
index a9e8b92..0000000
--- a/Manifest.toml
+++ /dev/null
@@ -1,230 +0,0 @@
-[[Base64]]
-uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
-
-[[ColorTypes]]
-deps = ["FixedPointNumbers", "Random", "Test"]
-git-tree-sha1 = "f73b0e10f2a5756de7019818a41654686da06b09"
-uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
-version = "0.7.5"
-
-[[Colors]]
-deps = ["ColorTypes", "FixedPointNumbers", "InteractiveUtils", "Pkg", "Printf", "Reexport", "Test"]
-git-tree-sha1 = "8c89e0a9a583954eae3efcf6a531e51c02b38cee"
-uuid = "5ae59095-9a9b-59fe-a467-6f913c188581"
-version = "0.9.4"
-
-[[Compat]]
-deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
-git-tree-sha1 = "ff2595695fc4f14427358ce2593f867085c45dcb"
-uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
-version = "1.2.0"
-
-[[Contour]]
-deps = ["LinearAlgebra", "StaticArrays", "Test"]
-git-tree-sha1 = "b974e164358fea753ef853ce7bad97afec15bb80"
-uuid = "d38c429a-6771-53c6-b99e-75d170b6e991"
-version = "0.5.1"
-
-[[DataStructures]]
-deps = ["InteractiveUtils", "OrderedCollections", "REPL", "Random", "Serialization", "Test"]
-git-tree-sha1 = "8fc6e166e24fda04b2b648d4260cdad241788c54"
-uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
-version = "0.14.0"
-
-[[Dates]]
-deps = ["Printf"]
-uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
-
-[[DelimitedFiles]]
-deps = ["Mmap"]
-uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"
-
-[[Distributed]]
-deps = ["LinearAlgebra", "Random", "Serialization", "Sockets"]
-uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"
-
-[[FD]]
-deps = ["OffsetArrays", "StaticArrays"]
-path = "FD"
-uuid = "edd53c04-b66a-11e8-3f8d-297d8a5291a5"
-version = "0.1.0"
-
-[[FixedPointNumbers]]
-deps = ["Pkg", "Test"]
-git-tree-sha1 = "b8045033701c3b10bf2324d7203404be7aef88ba"
-uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
-version = "0.5.3"
-
-[[GR]]
-deps = ["Base64", "DelimitedFiles", "Pkg", "Random", "Serialization", "Sockets", "Test"]
-git-tree-sha1 = "0437921cbf8ee555ab7a2a3115c1d0907289e816"
-uuid = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71"
-version = "0.34.1"
-
-[[InteractiveUtils]]
-deps = ["LinearAlgebra", "Markdown"]
-uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
-
-[[JSON]]
-deps = ["Dates", "Distributed", "Mmap", "Pkg", "Sockets", "Test", "Unicode"]
-git-tree-sha1 = "fec8e4d433072731466d37ed0061b3ba7f70eeb9"
-uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
-version = "0.19.0"
-
-[[LibGit2]]
-uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"
-
-[[Libdl]]
-uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
-
-[[LinearAlgebra]]
-deps = ["Libdl"]
-uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
-
-[[Logging]]
-uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
-
-[[Markdown]]
-deps = ["Base64"]
-uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
-
-[[Measures]]
-deps = ["Pkg", "Test"]
-git-tree-sha1 = "ddfd6d13e330beacdde2c80de27c1c671945e7d9"
-uuid = "442fdcdd-2543-5da2-b0f3-8c86c306513e"
-version = "0.3.0"
-
-[[Missings]]
-deps = ["Dates", "InteractiveUtils", "SparseArrays", "Test"]
-git-tree-sha1 = "adc26d2ee85a49c413464110d922cf21efc9d233"
-uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
-version = "0.3.1"
-
-[[Mmap]]
-uuid = "a63ad114-7e13-5084-954f-fe012c677804"
-
-[[NaNMath]]
-deps = ["Compat"]
-git-tree-sha1 = "ce3b85e484a5d4c71dd5316215069311135fa9f2"
-uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
-version = "0.3.2"
-
-[[OffsetArrays]]
-deps = ["DelimitedFiles", "Test"]
-git-tree-sha1 = "f8cd140824f74f2948ff8660bc2292e16d29c1b7"
-uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
-version = "0.8.1"
-
-[[OrderedCollections]]
-deps = ["Pkg", "Random", "Serialization", "Test"]
-git-tree-sha1 = "85619a3f3e17bb4761fe1b1fd47f0e979f964d5b"
-uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
-version = "1.0.2"
-
-[[Pkg]]
-deps = ["Dates", "LibGit2", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"]
-uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
-
-[[PlotThemes]]
-deps = ["PlotUtils", "Requires", "Test"]
-git-tree-sha1 = "f3afd2d58e1f6ac9be2cea46e4a9083ccc1d990b"
-uuid = "ccf2f8ad-2431-5c83-bf29-c5338b663b6a"
-version = "0.3.0"
-
-[[PlotUtils]]
-deps = ["Colors", "Dates", "Printf", "Random", "Reexport", "Test"]
-git-tree-sha1 = "78553a920c4869d20742ba66193e3692369086ec"
-uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043"
-version = "0.5.4"
-
-[[Plots]]
-deps = ["Base64", "Contour", "Dates", "FixedPointNumbers", "GR", "JSON", "LinearAlgebra", "Measures", "NaNMath", "Pkg", "PlotThemes", "PlotUtils", "Printf", "Random", "RecipesBase", "Reexport", "Requires", "Showoff", "SparseArrays", "StaticArrays", "Statistics", "StatsBase", "Test", "UUIDs"]
-git-tree-sha1 = "437b77558cc7f24883bd4c4fe25a240304402ffd"
-uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
-version = "0.20.5"
-
-[[Printf]]
-deps = ["Unicode"]
-uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
-
-[[REPL]]
-deps = ["InteractiveUtils", "Markdown", "Sockets"]
-uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
-
-[[Random]]
-deps = ["Serialization"]
-uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
-
-[[RecipesBase]]
-deps = ["Pkg", "Random", "Test"]
-git-tree-sha1 = "0b3cb370ee4dc00f47f1193101600949f3dcf884"
-uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
-version = "0.6.0"
-
-[[Reexport]]
-deps = ["Pkg"]
-git-tree-sha1 = "7b1d07f411bc8ddb7977ec7f377b97b158514fe0"
-uuid = "189a3867-3050-52da-a836-e630ba90ab69"
-version = "0.2.0"
-
-[[Requires]]
-deps = ["Test"]
-git-tree-sha1 = "f6fbf4ba64d295e146e49e021207993b6b48c7d1"
-uuid = "ae029012-a4dd-5104-9daa-d747884805df"
-version = "0.5.2"
-
-[[SHA]]
-uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
-
-[[Serialization]]
-uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
-
-[[SharedArrays]]
-deps = ["Distributed", "Mmap", "Random", "Serialization"]
-uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"
-
-[[Showoff]]
-deps = ["Compat", "Pkg"]
-git-tree-sha1 = "276b24f3ace98bec911be7ff2928d497dc759085"
-uuid = "992d4aef-0814-514b-bc4d-f2e9a6c4116f"
-version = "0.2.1"
-
-[[Sockets]]
-uuid = "6462fe0b-24de-5631-8697-dd941f90decc"
-
-[[SortingAlgorithms]]
-deps = ["DataStructures", "Random", "Test"]
-git-tree-sha1 = "03f5898c9959f8115e30bc7226ada7d0df554ddd"
-uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c"
-version = "0.3.1"
-
-[[SparseArrays]]
-deps = ["LinearAlgebra", "Random"]
-uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
-
-[[StaticArrays]]
-deps = ["InteractiveUtils", "LinearAlgebra", "Random", "Statistics", "Test"]
-git-tree-sha1 = "d432c79bef174a830304f8601427a4357dfdbfb7"
-uuid = "90137ffa-7385-5640-81b9-e52037218182"
-version = "0.8.3"
-
-[[Statistics]]
-deps = ["LinearAlgebra", "SparseArrays"]
-uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
-
-[[StatsBase]]
-deps = ["DataStructures", "LinearAlgebra", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "Test"]
-git-tree-sha1 = "723193a13e8078cec6dcd0b8fe245c8bfd81690e"
-uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
-version = "0.25.0"
-
-[[Test]]
-deps = ["Distributed", "InteractiveUtils", "Logging", "Random"]
-uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
-
-[[UUIDs]]
-deps = ["Random"]
-uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
-
-[[Unicode]]
-uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
diff --git a/Project.toml b/Project.toml
index 9add2e9..b60374f 100644
--- a/Project.toml
+++ b/Project.toml
@@ -1,4 +1,14 @@
-[deps]
-Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
-FD = "edd53c04-b66a-11e8-3f8d-297d8a5291a5"
-Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
+name = "DualTVDD"
+uuid = "93adc0ee-851f-4b8b-8bf8-c8a87ded093b"
+authors = ["Stephan Hilb <stephan@ecshi.net>"]
+version = "0.1.0"
+
+[compat]
+julia = "1.4.0"
+
+[extras]
+BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
+Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
+
+[targets]
+test = ["Test", "BenchmarkTools"]
diff --git a/chambollefp.jl b/chambollefp.jl
deleted file mode 100644
index 56f3495..0000000
--- a/chambollefp.jl
+++ /dev/null
@@ -1,99 +0,0 @@
-using StaticArrays
-using SparseArrays
-using OffsetArrays
-using LinearAlgebra
-using Colors
-using Plots
-
-using FD
-
-
-imf(f, grid) = [f(x) for x in grid]
-
-imshow(grid, img) = FD.my_plot(FD.GridFunction(grid, img))
-#plot(Gray.((reshape(float.(img), size(grid)) .- minimum(float.(img))) / (maximum(float.(img)) - minimum(float.(img))) ), show=true)
-
-function _fpiter(grid, p, f, A, α, β, τ)
-  x = (A'*A + β*I) \ (FD.divergence(grid, p) .+ A'*f)
-  q = FD.gradient(grid, x)
-
-  p_new = (p .+ τ .* q) ./ (1 .+ (τ/α) .* FD.my_norm(grid, q))
-
-  reserr = maximum(sum((p_new .- p).^2, dims=ndims(p)))
-  (p_new, reserr)
-end
-
-function fpalg(grid, f, A; α, β, τ = 1/8, ϵ = 1e-4)
-  p = zeros(length(grid) * ndims(grid))
-
-  reserr = 1
-  k = 0
-  while reserr > ϵ && k < 300e1
-    k += 1
-    #sleep(0.1 / k)
-    (p, reserr) = _fpiter(grid, p, f, A, α, β, τ)
-
-    uimg = (A'*A + β*I) \ (FD.divergence(grid, p) .+ A'*f)
-
-    imshow(grid, uimg)
-    println("[$(lpad(k, 4))] res = $(round(reserr, digits=5))")
-    println("min/max: $(minimum(FD.my_norm(FD.GridFunction(grid,p)))) / $(maximum(FD.my_norm(FD.GridFunction(grid,p))))")
-  end
-end
-
-#st = OffsetArray([0 -1 0; -1 4 -1; 0 -1 0], (-2, -2))
-
-grid = FD.Grid(0:0.01:1, 0:0.01:1)
-#grid = FD.Grid(0:0.5:1, 0:0.5:1)
-
-n = 1
-#A = spdiagm(collect(k => fill(1/(2n+1), length(grid)-abs(k)) for k in -n:n)...)
-
-# circle, scalar
-f = reshape([float(norm(x .- [0.5, 0.5]) < 0.4) for x in grid], length(grid))
-#f += clamp.(0.2 * rand(eltype(f), size(f)), 0, 1)
-
-
-A = I
-#A = spdiagm(0 => ones(length(grid)), 1 => ones(length(grid)-1) )
-
-f = A * f
-
-#imshow(grid, f)
-#sleep(5)
-
-img = fpalg(grid, f, A, α=10000, β=0, τ=1/8, ϵ=1e-7)
-
-
-#A = Array{Float64}(undef, (length(grid), length(grid)))
-#for i in 1:length(grid)
-#  ei = float.(1:length(grid) .== i)
-#  #A[:, i] = FD.divergence(grid, FD.gradient(grid, ei))
-#  #A[:, i] = FD.divergence(grid, append!(ei, zeros(length(grid))))
-#  A[:, i] = reshape(reshape(FD.gradient(grid, ei), size(grid)..., 2)[:,:,2], length(grid))
-#end
-#A
-
-#display(A)
-
-
-#display(f2)
-#imshow(grid, f2)
-
-
-
-#@benchmark solve((stencil=st, f=zeros(10000)), (100, 100))
-
-#u = zeros(100, 100)
-
-#A = LinearMap{Float64}(u -> apply(st, u, size(uinner)), length(uinner), issymmetric=true, isposdef=true)
-
-#cg!(uflat, A, zeros(length(uinner)))
-
-
-#A = sparse(A)
-
-#@benchmark cg($A, zeros(10000))
-
-# TODO: A* as sparse matrix?
-
diff --git a/chambollefp_dd.jl b/chambollefp_dd.jl
deleted file mode 100644
index b622ff9..0000000
--- a/chambollefp_dd.jl
+++ /dev/null
@@ -1,114 +0,0 @@
-#include("fd.jl")
-
-using LinearAlgebra
-using SparseArrays
-import FD
-
-
-import Colors: Gray
-import Plots: plot
-
-
-
-"partition of unity function"
-function theta(grid, idx...)
-  function h(r, rp, i)
-    i <= first(r) + grid.overlap && return 1.0
-    i >= last(r) - grid.overlap && return 1.0
-    (min(i - first(rp), last(rp) - i, grid.overlap) + 1) / (grid.overlap + 1)
-  end
-
-  vidx = FD.indices(grid, idx...)
-  ctheta = map(x -> [h(x[1], x[2], i) for i in x[2]], zip(axes(grid), vidx))
-
-  dst = zeros(size(grid))
-  for (I,J) in zip(CartesianIndices(vidx), CartesianIndices(length.(vidx)))
-    dst[I] = prod(getindex.(ctheta, Tuple(J)))
-  end
-  FD.GridFunction(grid, dst)
-end
-
-
-function p2u(grid, p, A, f; α, β)
-  FD.GridFunction(grid, (A'*A + β*I) \ (FD.divergence_z(p).data[:] .+ A'*f.data[:]))
-end
-
-
-# TODO: investigate matrix multiplication performance with GridFunctions
-function fpiter(grid, p, wg, A, α, β, τ)
-  x = FD.GridFunction(FD.parent(grid), (A'*A + β*I) \ (FD.divergence_z(FD.zero_continuation(p)) + wg).data[:])
-  q = FD.GridFunction(grid, view(FD.gradient(x).data, grid.indices..., :))
-  ## this misses out on the right boundary
-  #q = FD.gradient(FD.GridFunction(grid, view(x.data, grid.indices..., :)))
-
-  p_new = FD.GridFunction(grid, FD.pwmul(FD.GridFunction(grid, α ./ (α .+ τ .* FD.pw_norm(q))),
-                                         FD.GridFunction(grid, p .+ τ .* q)))
-
-  reserr = maximum(sum((p_new .- p).^2, dims=ndims(p)))
-  (p_new, reserr)
-end
-
-
-function fpalg(grid, w, A, p = FD.GridFunction(x -> 0, ndims(grid), grid);
-               α, β, τ = 1/8, ϵ = 1e-4)
-  reserr = 1
-  k = 0
-  while reserr > ϵ && k < 10e1
-    k += 1
-    (p, reserr) = fpiter(grid, p, w, A, α, β, τ)
-  end
-  return p
-end
-
-
-function dditer(grid, p, A, f; α, β, τ, ϵ, ρ)
-  p̂ = (1-ρ) * p
-
-  i = 0
-  for part in FD.parts(grid)
-    gridv = view(grid, Tuple(part)...)
-
-    fv = view(f, Tuple(part)...)
-
-    p̂v = view(p̂, Tuple(part)...)
-    pv = view(p, Tuple(part)...)
-
-    θ = theta(grid, Tuple(part)...)
-    θv = view(θ, Tuple(part)...)
-
-    w = FD.GridFunction(grid, A' * f.data[:]) + FD.divergence(FD.pwmul(FD.GridFunction(grid, 1 .- θ), p))
-    wv = view(w, Tuple(part)...)
-
-    p̂v .+= ρ .* fpalg(gridv, w, A, α=α*θv, β=β, τ=τ, ϵ=ϵ)
-
-    i += 1
-  end
-  p .= p̂
-
-  u = p2u(grid, p, A, f, α=α, β=β)
-  FD.my_plot(u)
-  #FD.my_plot(FD.pw_norm(p))
-end
-
-
-grid = FD.MetaGrid((0:0.01:1, 0:0.01:1), (2, 2), 5)
-
-A = I
-#A = spdiagm(0 => ones(length(grid)), 1 => ones(length(grid)-1) )
-#f = A * f
-
-f = FD.GridFunction(x -> float(norm(x .- [0.5, 0.5]) < 0.4), 1, grid)
-g = FD.GridFunction(x -> norm(x), 2, grid)
-
-α=100000
-β=0
-τ=1/8
-ϵ=1e-9
-ρ=0.5
-
-p = FD.GridFunction(x -> 0, 2, grid)
-
-for n = 1:10
-  println("iteration $n")
-  dditer(grid, p, A, f, α=α, β=β, τ=τ, ϵ=ϵ, ρ=ρ)
-end
diff --git a/fd.jl b/fd.jl
deleted file mode 100644
index 19d3c07..0000000
--- a/fd.jl
+++ /dev/null
@@ -1,491 +0,0 @@
-module FDM
-
-using StaticArrays
-using SparseArrays
-using OffsetArrays
-using LinearMaps
-using IterativeSolvers
-
-include("filtering.jl")
-
-"Finite Differences"
-module FD
-
-using OffsetArrays
-
-const forward = OffsetVector([-1, 1], -1)
-const backward = OffsetVector([-1, 1], -2)
-const central = OffsetVector([-1, 0, 1], -2)
-function dirfd(fd, ndims, dir)
-  dir <= ndims || throw(ArgumentError("need dir <= ndims!"))
-  dfd = OffsetArray(zeros(ntuple(i -> i == dir ? length(fd) : 1, ndims)), ntuple(i -> i == dir ? fd.offsets[1] : -1, ndims))
-  # Workaround: LinearIndices(::OffsetVector) are not 1-based, so copyto! chockes
-  copyto!(dfd, fd.parent)
-end
-
-end
-
-
-using .Filtering
-using .FD
-
-"""
-    _fdkern(fd, ndims, dir)
-
-Create from `fd` a finite difference kernel usable in `ndims` dimension
-oriented in dimension `dir`.
-"""
-# TODO: make it typestable on ndims
-function _fdkern(fd::OffsetArray{T,1,Array{T,1}}, ndims::Integer, dir::Integer) where T
-  1 <= dir <= ndims || throw(ArgumentError("dimesion $(dir) out of range 1:$(ndims)"))
-  dfd = OffsetArray{T}(undef, Tuple(i == dir ? axes(fd, 1) : (0:0) for i in 1:ndims))
-  # copyto! from OffsetArray fails, broadcasting for differing axes fails too
-  # workaround: copyto! from parent
-  copyto!(dfd, fd.parent)
-end
-
-
-# Grid
-
-
-abstract type AbstractGrid{d} <: AbstractArray{Float64, d} end
-
-Base.ndims(grid::AbstractGrid{d}) where d = d
-Base.axes(grid::AbstractGrid) = Base.OneTo.(size(grid))
-Base.length(grid::AbstractGrid) = prod(size(grid))
-Base.IndexStyle(::Type{<:AbstractGrid}) = IndexCartesian()
-
-# aka `StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}`
-const FDStepRange = typeof(0:0.1:1)
-
-struct Grid{d} <: AbstractGrid{d}
-  domain::NTuple{d, FDStepRange}
-end
-Grid(domain::FDStepRange...) = Grid(domain)
-Base.show(io::IO, grid::Grid) =
-    print("Grid from $(first.(grid.domain)) to $(last.(grid.domain)) ",
-          "with spacing $(step.(grid.domain))")
-
-Base.size(grid::Grid) = ntuple(i -> grid.domain[i].len, ndims(grid))
-
-Base.iterate(grid::Grid) = iterate(Iterators.product(grid.domain...))
-Base.iterate(grid::Grid, state) = iterate(Iterators.product(grid.domain...), state)
-
-Base.getindex(grid::Grid, idx...) = SVector(getindex.(grid.domain, idx))
-Base.getindex(grid::Grid, I::CartesianIndex) = SVector(getindex.(grid.domain, Tuple(I)))
-
-
-# GridView
-
-struct GridView{d} <: AbstractGrid{d}
-  grid::AbstractGrid{d}
-  indices::NTuple{d, UnitRange{Int}}
-end
-GridView(grid, indices::UnitRange{Int}...) = GridView(grid, indices)
-
-Base.size(grid::GridView) = length.(grid.indices)
-
-# FIXME: this is terrible!
-Base.iterate(grid::GridView) = iterate(grid.grid[I] for I in CartesianIndices(grid.indices))
-Base.iterate(grid::GridView, state) = iterate([grid.grid[I] for I in CartesianIndices(grid.indices)], state)
-
-Base.getindex(grid::GridView, idx...) = getindex(grid.grid, (first.(grid.indices) .- 1 .+ Base.to_indices(grid, idx))...)
-
-# MetaGrid
-
-
-struct MetaGrid{d} <: AbstractGrid{d}
-  grid::Grid{d}
-  indices::Array{NTuple{d, UnitRange{Int}}, d}
-  overlap::Int
-end
-
-"""
-    MetaGrid(domain::NTuple{d, FDStepRange}, pnum::NTuple{d, Int}, overlap::Int)
-
-Create a grid on `domain`, partitioned along dimensions according to `pnum`
-respecting `overlap`.
-"""
-function MetaGrid(domain::NTuple{d, FDStepRange}, pnum::NTuple{d, Int}, overlap::Int) where d
-  grid = Grid(domain)
-  tsize = size(grid) .+ (pnum .- 1) .* overlap
-
-  psize = tsize .÷ pnum
-  osize = tsize .- pnum .* psize
-
-  overhang(I, j) = I[j] == pnum[j] ? osize[j] : 0
-
-  indices = Array{NTuple{d, UnitRange{Int}}, d}(undef, pnum)
-  for I in CartesianIndices(pnum)
-    indices[I] = ntuple(j -> ((I[j] - 1) * psize[j] - (I[j] - 1) * overlap + 1) :
-                             (      I[j] * psize[j] - (I[j] - 1) * overlap + overhang(I, j)), d)
-  end
-  MetaGrid{d}(grid, indices, overlap)
-end
-
-Base.size(grid::MetaGrid) = size(grid.grid)
-
-Base.iterate(grid::MetaGrid) = iterate(grid.grid)
-Base.iterate(grid::MetaGrid, state) = iterate(grid.grid, state)
-
-Base.getindex(grid::MetaGrid, idx...) = getindex(grid.grid, idx...)
-
-indices(grid::MetaGrid, idx...) = getindex(grid.indices, idx...)
-
-Base.view(grid::MetaGrid, idx...) = GridView(grid, indices(grid, idx...))
-
-parts(grid::MetaGrid) = CartesianIndices(grid.indices)
-
-
-## GridFunction
-
-abstract type AbstractGridFunction{G,T,d,n} <: AbstractArray{T,1} end
-
-rangedim(f::AbstractGridFunction{G,T,d,n}) where {G,T,d,n} = n
-domaindim(f::AbstractGridFunction{G,T,d}) where {G,T,d} = d
-
-Base.similar(f::F, ::Type{S}, dims::Int...) where {F<:AbstractGridFunction, S} =
-    GridFunction(f.grid, Array{S}(undef, length(f)))
-
-
-"""
-    GridFunction{G<:AbstractGrid,T,d,n,D}
-
-`n`-dimensional function values of type `T` on a Grid `G` of dimension `d`.
-
-`D == d + 1`
-"""
-struct GridFunction{G<:AbstractGrid,T,d,n,A,D} <: AbstractGridFunction{G,T,d,n}
-  grid::G
-  data::A
-  function GridFunction{G,T,d,n,A,D}(grid::G, data::A) where {G,T,d,n,D,A<:AbstractArray{T,D}}
-    D == d + 1 || throw(ArgumentError)
-    d == ndims(grid) || throw(ArgumentError)
-    (size(grid)..., n) == size(data) || throw(ArgumentError("data does not match given size"))
-    new(grid, data)
-  end
-end
-# TODO: check size(data)
-#GridFunction(grid::G, data::AbstractArray{T,D}) where {d,G<:AbstractGrid{d},T,D} =
-#    GridFunction{G,T,d,size(data, d+1),typeof(data),d+1}(grid, data)
-function GridFunction(grid::G, data::AbstractArray, ::Val{n}) where {d,G<:AbstractGrid{d}, n}
-  if length(data) ÷ length(grid) == 1 && n > 1
-    data = repeat(data, inner=ntuple(i -> i == d+1 ? n : 1, d+1))
-  else
-    # TODO: check size(data)
-    data = reshape(data, size(grid)..., n)
-  end
-  length(data) ÷ length(grid) == n || throw(ArgumentError("dimensions don't match"))
-  GridFunction{G,eltype(data),d,n,typeof(data),d+1}(grid, data)
-end
-function GridFunction(grid::G, data::AbstractArray) where {d,G<:AbstractGrid{d}}
-  GridFunction(grid, data, Val(length(data) ÷ length(grid)))
-end
-function GridFunction(f::Function, n::Int, grid::G) where {G<:AbstractGrid}
-  data = Array{Float64,ndims(grid)+1}(undef, size(grid)..., n)
-  for I in CartesianIndices(grid)
-    data[Tuple(I)..., :] .= f(grid[I])
-  end
-  GridFunction(grid, data)
-end
-function GridFunction(grid::AbstractGrid, ::UndefInitializer, ::Val{n} = Val(1)) where n
-  data = Array{Float64}(undef, size(grid)..., 1)
-  GridFunction{typeof(grid), eltype(data), ndims(grid), n, typeof(data), ndims(grid) + 1}(grid, data)
-end
-
-#Base.show(io::IO, grid::GridFunction) =
-#    print("GridFunction on $(length(grid)) grid points")
-
-Base.size(f::GridFunction) = (length(f.data),)
-#Base.axes(f::GridFunction) = (Base.OneTo(length(f.data)),)
-Base.getindex(f::GridFunction, idx::Int) = getindex(f.data, idx)
-Base.setindex!(f::GridFunction, v, idx::Int) = setindex!(f.data, v, idx)
-
-## TODO: using this prints #undef for display(f)
-#Base.getindex(f::GridFunction, idx...) = getindex(f.data, idx...)
-#Base.setindex!(f::GridFunction, v, idx...) = setindex!(f.data, v, idx...)
-
-#Base.IndexStyle(::Type{<:GridFunction}) = IndexLinear()
-
-#component(f::GridFunction, k::Integer) = view(reshape(f.data,
-
-Base.view(f::AbstractGridFunction{G}, idx...) where G<:MetaGrid =
-    GridFunction(GridView(f.grid, indices(f.grid, idx...)), view(f.data, indices(f.grid, idx...)..., :))
-
-grid(f::GridFunction) = f.grid
-
-
-Base.:*(a::Number, b::GridFunction) = GridFunction(b.grid, a * b.data, Val(rangedim(b)))
-
-
-
-import Colors: Gray
-import Plots: plot
-
-# TODO: add clamp option
-my_plot(img::AbstractGridFunction{G,T,d,1}) where {G,T,d} =
-    plot(reshape(Gray.((img .- minimum(img)) ./ (maximum(img) - minimum(img))), size(img.grid)), show=true)
-
-## Operations
-
-function fdfilter(f::GridFunction{G,T,d,n}, kern) where {G,T,d,n}
-  A = zeros(axes(f.data))
-  for k = 1:n
-    vidx = ntuple(i -> i <= d ? Colon() : k, d + 1)
-    imfilter!(view(A, vidx...), f.data[vidx...], kern)
-  end
-  GridFunction(f.grid, A)
-end
-
-derivate(f::GridFunction, dir::Integer; fd = FD.forward) = fdfilter(f, _fdkern(fd, ndims(f.grid), dir))
-
-
-# currently gradient and divergence are specific implementations (as needed for
-# chambolles algorithm), other choices may be reasonable
-
-function gradient(f::GridFunction{G,T,d,1}; fd=FD.forward) where {G,T,d}
-  #A = Array{T,d+1}(undef, (size(f.grid)..., d))
-  A = zeros(size(f.grid)..., d)
-  for k = 1:d
-    vidx = ntuple(i -> i <= d ? Colon() : k, d + 1)
-    vidx2 = ntuple(i -> i <= d ? Colon() : 1, d + 1)
-    imfilter!(view(A, vidx...), f.data[vidx2...], bidx -> trim_kernel(_fdkern(fd, ndims(f.grid), k), bidx))
-    # FIXME: hackish, only for forward fd
-    selectdim(selectdim(A, d+1, k), k, size(f.grid, k)) .= 0
-  end
-  GridFunction(f.grid, A)
-end
-
-function divergence(f::GridFunction{G,T,d}; fd=FD.backward) where {d,G<:AbstractGrid{d},T}
-  A = zeros(size(f.grid)..., 1)
-  for k = 1:d
-    vidx = ntuple(i -> i <= d ? Colon() : 1, d + 1)
-    vidx2 = ntuple(i -> i <= d ? Colon() : k, d + 1)
-    imfilter!(view(A, vidx...), f.data[vidx2...], bidx -> trim_kernel(_fdkern(fd, ndims(f.grid), k), bidx))
-  end
-  GridFunction(f.grid, A)
-end
-
-function pw_norm(f::GridFunction)
-  d = domaindim(f)
-  GridFunction(f.grid, sqrt.(sum(f.data.^2, dims=d+1)), Val(1))
-end
-
-function my_norm(f::GridFunction)
-  d = domaindim(f)
-  GridFunction(f.grid, sqrt.(sum(f.data.^2, dims=d+1)), Val(rangedim(f)))
-end
-
-function pwmul(f::GridFunction{G,T,d,1}, g::GridFunction{G,T,d}) where {G,T,d}
-  # TODO: maybe comprehension?
-  data = similar(g.data)
-  for I in CartesianIndices(f.grid)
-    data[Tuple(I)..., :] = f.data[Tuple(I)..., 1] * g.data[Tuple(I)..., :]
-  end
-  GridFunction(f.grid, data, Val(rangedim(g)))
-end
-
-
-
-
-"""
-    neuman_kernel(kernel, bidx)
-
-Modify `kernel` to be usable on a boundary `bixd` in a neumannn boundary setting.
-
-Ghost nodes are eliminated through central finite differences. Currently only
-the basic 5-star Laplace is supported.
-"""
-function neumann_kernel(kernel, bidx)
-  out = copy(kernel)
-  for (i, bi) in enumerate(bidx)
-    colrange = (i == j ? Colon() : 0 for j in 1:ndims(kernel))
-    out[colrange...] .+= bi * FD.forward
-  end
-  out /= 2 ^ count(!iszero, bidx)
-  trim_kernel(out, bidx)
-end
-
-function divergence(grid::Grid{d}, field) where d
-  field = reshape(field, (size(grid)..., ndims(grid)))
-  size(field, d+1) == d || throw(ArgumentError("need a d-vectorfield on a d-grid"))
-  dst = zeros(size(field))
-  for k in 1:d
-    kern = FD.dirfd(FD.backward, d, k)
-    imfilter!(selectdim(dst, d+1, k), selectdim(field, d+1, k), bidx -> trim_kernel(kern, bidx))
-  end
-  out = dropdims(sum(dst, dims=d+1), dims=d+1)
-  out[:]
-end
-
-function gradient(grid::Grid, img::AbstractArray)
-  img = reshape(img, size(grid))
-  d = ndims(img)
-  dst = zeros(size(img)..., d)
-  for k in 1:d
-    kern = FD.dirfd(FD.forward, d, k)
-    imfilter!(selectdim(dst, d+1, k), img, bidx -> trim_kernel(kern, bidx))
-    # FIXME: hack
-    selectdim(selectdim(dst, d+1, k), k, size(grid, k)) .= 0
-  end
-  reshape(dst, length(grid) * ndims(grid))
-end
-
-function my_norm(grid::Grid, img::AbstractArray)
-  img = reshape(img, (size(grid)..., ndims(grid)))
-  d = ndims(grid)
-
-  out = sqrt.(sum(img.^2, dims=d+1))
-  out = repeat(out, inner=ntuple(i -> i == d+1 ? size(img, d+1) : 1, d+1))
-
-  out[:]
-end
-
-
-function apply(kern, flatimg, size)
-  img = reshape(flatimg, size)
-  out = imfilter(kern, img)
-  reshape(out, prod(size))
-end
-
-function solve(pde, shape::NTuple{d, Integer}) where d
-  ndims(pde.stencil) == d || throw("dimension mismatch")
-  length(pde.f) == prod(shape) || throw("bounds mismatch")
-
-  A = LinearMap{Float64}(u -> apply(pde.stencil, u, shape), prod(shape), issymmetric=true, isposdef=true)
-
-  u = zeros(prod(shape))
-  cg!(u, A, pde.f)
-  return u
-end
-
-end
-
-
-#module FD
-#
-#using OffsetArrays
-#include("filtering.jl")
-#
-#
-#"FD-Kernels"
-#module Kernels
-#
-#using OffsetArrays
-#
-#const forward = OffsetVector([-1, 1], 0:1)
-#const backward = OffsetVector([-1, 1], -1:0)
-#const central = OffsetVector([-1, 0, 1], -1:1)
-#
-#end
-#
-#using .Filtering
-#using .Kernels
-#
-#
-#
-### Implementation
-#
-#
-### Grid
-#
-## aka `StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}`
-#const FDStepRange = typeof(0:0.1:1)
-#
-#struct Grid{d}
-#  domain::NTuple{d, FDStepRange}
-#end
-#
-#Grid(domain::FDStepRange...) = Grid(domain)
-#Grid(ndims::Integer, range::FDStepRange) = Grid(ntuple(i -> range, ndims))
-#
-#Base.show(io::IO, grid::Grid) =
-#    print("FDGrid from $(first.(grid.domain)) to $(last.(grid.domain)) ",
-#          "with spacing $(step.(grid.domain))")
-#
-## this is not type-stable?
-##Base.size(grid::Grid) = getproperty.(grid.domain, :len)
-#Base.size(grid::Grid) = ntuple(i -> grid.domain[i].len, ndims(grid))
-#Base.getindex(grid::Grid, idx...) = SVector(getindex.(grid.domain, idx))
-#
-#Base.ndims(grid::Grid{d}) where d = d
-#Base.axes(grid::Grid) = Base.OneTo.(size(grid))
-#Base.length(grid::Grid) = prod(size(grid))
-#
-## TODO: there might be a better way to wrap that product iterator
-#Base.iterate(grid::Grid) = iterate(Iterators.product(grid.domain...))
-#Base.iterate(grid::Grid, state) = iterate(Iterators.product(grid.domain...), state)
-#
-#
-### GridFunction
-#
-#struct GridFunction{T,d} <: AbstractArray{T,1}
-#  grid::Grid{d}
-#  data::Array{T,1}
-#end
-#
-#Base.show(io::IO, grid::GridFunction) =
-#    print("GridFunction on $(length(grid)) grid points")
-#
-#Base.size(f::GridFunction) = size(f.data)
-#Base.getindex(f::GridFunction, idx...) = getindex(f.data, idx...)
-#Base.setindex!(f::GridFunction, v, idx...) = setindex!(f.data, v, idx...)
-#
-#Base.IndexStyle(::Type{<:GridFunction}) = IndexLinear()
-#Base.similar(f::GridFunction, ::Type{S}, size::Int...) where S =
-#    GridFunction(f.grid, Array{S}(undef, size))
-#
-### Operations
-#
-#function fdfilter(f::GridFunction, kern)
-#  A = imfilter(reshape(f.data, size(f.grid)), kern)
-#  GridFunction(f.grid, A[:])
-#end
-#
-#derivate(f::GridFunction, dir::Integer; fd = FD.forward) = fdfilter(f, _fdkern(fd, ndims(f.grid), dir))
-#
-##gradient(f::GridFunction) = GridFunction(f.grid,
-#
-#
-#"""
-#    neuman_kernel(kernel, bidx)
-#
-#Modify `kernel` to be usable on a boundary `bixd` in a neumannn boundary setting.
-#
-#Ghost nodes are eliminated through central finite differences. Currently only
-#the basic 5-star Laplace is supported.
-#"""
-#function neumann_kernel(kernel, bidx)
-#  out = copy(kernel)
-#  for (i, bi) in enumerate(bidx)
-#    colrange = (i == j ? Colon() : 0 for j in 1:ndims(kernel))
-#    out[colrange...] .+= bi * FD.forward
-#  end
-#  out /= 2 ^ count(!iszero, bidx)
-#  trim_kernel(out, bidx)
-#end
-#
-#function divergence(grid::Grid{d}, field) where d
-#  field = reshape(field, (size(grid)..., ndims(grid)))
-#  size(field, d+1) == d || throw(ArgumentError("need a d-vectorfield on a d-grid"))
-#  dst = zeros(size(field))
-#  for k in 1:d
-#    kern = FD.dirfd(FD.backward, d, k)
-#    imfilter!(selectdim(dst, d+1, k), selectdim(field, d+1, k), kern)
-#  end
-#  out = dropdims(sum(dst, dims=d+1), dims=d+1)
-#  reshape(out, length(grid))
-#end
-#
-#function gradient(grid::Grid, img::AbstractArray)
-#  img = reshape(img, size(grid))
-#  d = ndims(img)
-#  dst = zeros(size(img)..., d)
-#  for k in 1:d
-#    kern = FD.dirfd(FD.forward, d, k)
-#    imfilter!(selectdim(dst, d+1, k), img, kern)
-#  end
-#  reshape(dst, length(grid) * ndims(grid))
-#end
-#
-#end
diff --git a/filtering.jl b/filtering.jl
deleted file mode 100644
index 11fa7b2..0000000
--- a/filtering.jl
+++ /dev/null
@@ -1,110 +0,0 @@
-module Filtering
-
-export imfilter, imfilter!, trim_kernel
-
-@inline function _shift_range(range, shift::Integer)
-  Base.UnitRange(first(range)+shift : last(range)+shift)
-end
-
-## TODO: allow border to be tuple
-#function _innershift_indices(a::Array{T, d}, shift::NTuple{d, Integer}, border = abs(maximum(shift))) where {T, d}
-#  abs(maximum(shift)) <= border || throw("cannot shift by $shift with border $border")
-#  range(i) = (1 + shift[i] + border):(lastindex(a, i) + shift[i] - border)
-#  ntuple(range, Val(ndims(a)))
-#end
-#
-#function innershift_view(a::Array{T, d}, shift::NTuple{d, Integer} = ntuple(i -> 0, d), border = abs(maximum(shift))) where {T, d}
-#  view(a, _innershift_indices(a, shift, border)...)
-#end
-
-@inline function _bidx_to_range(range::AbstractUnitRange, bidx::Integer)
-  if bidx > 0
-    return last(range):last(range)
-  elseif bidx < 0
-    return first(range):first(range)
-  else
-    return first(range)+1:last(range)-1
-  end
-end
-
-
-# we return Base.Slice since that is what `OffsetArray` uses for `axes()`
-function _trim_kernel_axes(kaxes, bidx)
-  ntuple(Val(length(kaxes))) do i
-    if bidx[i] < 0
-      return Base.Slice(0:last(kaxes[i]))
-    elseif bidx[i] > 0
-      return Base.Slice(first(kaxes[i]):0)
-    else
-      return Base.Slice(kaxes[i])
-    end
-  end
-end
-
-"""
-    trim_kernel(kernel, bidx)
-
-Trim `kernel` so that the resulting kernel is usable on the boundary
-indicated by `bidx`
-"""
-function trim_kernel(kernel, bidx)
-  dst_axes = _trim_kernel_axes(axes(kernel), bidx)
-  out = similar(kernel, dst_axes)
-  range = CartesianIndices(out)
-  copyto!(out, range, kernel, range)
-end
-
-
-"""
-    imfilter!(dst, img, kern, slice=:inner)
-
-Filters `img` applying `kern` and write to `dst`. `dst` is added to, so you
-should initialize with zeros.
-"""
-# TODO: weirdly Base.Slice instead of Base.UnitRange leads to axes that are not
-# compatible when broadcasting, figure out why!
-function imfilter!(dst, img, kern, slice=_bidx_to_range.(axes(img), ntuple(i->0, ndims(img))))
-  ndims(dst) == ndims(img) == ndims(kern) == length(slice) ||
-      throw(ArgumentError("dst, image, kernel or slice dimensions disagree ($(ndims(dst)) vs $(ndims(img)) vs $(ndims(kern)) vs $(length(slice)))"))
-  axes(dst) == axes(img) || throw(ArgumentError("output and input image axes disagree"))
-  all(size(kern) .<= size(img)) || throw(ArgumentError("kernel bigger than image"))
-  last.(slice) .+ last.(axes(kern)) <= last.(axes(img)) || first.(slice) .+ first.(axes(kern)) >= first.(axes(img)) ||
-      throw(ArgumentError("kern and/or slice out of bounds for image"))
-
-  dstslice = view(dst, UnitRange.(slice)...)
-  for i in CartesianIndices(kern)
-    iszero(kern[i]) && continue
-    dstslice .+= kern[i] .* view(img, (_shift_range.(slice, Tuple(i)))...)
-  end
-  dst
-end
-
-function imfilter!(dst, img, kernelf::Function)
-  for bidx in Iterators.product(ntuple(i -> -1:1, ndims(img))...)
-    range = _bidx_to_range.(axes(img), bidx)
-    imfilter!(dst, img, kernelf(bidx), range)
-  end
-  dst
-end
-
-"""
-    imfilter(img, kernelf)
-
-Filter `img` using a kernel function `kernelf(bidx) = kernel` specifying a
-kernel for each type of node.
-"""
-# need zeros here, because `imfilter!` acts additively
-imfilter(img, kernelf::Function) = imfilter!(zeros(size(img)), img, kernelf)
-
-
-"""
-    imfilter(img, kern)
-
-Filter `img` by `kern` using correlation.
-Boundary is zeroed out.
-"""
-imfilter(img, kern) = imfilter(img, bidx -> trim_kernel(kern, bidx))
-# TODO: use keyword arguments
-imfilter_inner(img, kern) = imfilter(img, bidx -> all(bidx .== 0) ? kern : zeros(ntuple(i -> 0, ndims(img))))
-
-end
diff --git a/src/DualTVDD.jl b/src/DualTVDD.jl
new file mode 100644
index 0000000..d1742f1
--- /dev/null
+++ b/src/DualTVDD.jl
@@ -0,0 +1,5 @@
+module DualTVDD
+
+greet() = print("Hello World!")
+
+end # module
diff --git a/test.py b/test.py
deleted file mode 100644
index 67db6cd..0000000
--- a/test.py
+++ /dev/null
@@ -1,134 +0,0 @@
-import numpy as np
-from scipy.sparse import diags, coo_matrix
-from scipy.sparse.linalg import spsolve, cg
-from mpl_toolkits.mplot3d import Axes3D
-import matplotlib.pyplot as plt
-
-# TODO: use np.indices, np.mgrid, np.meshgrid, etc.
-
-
-domain = np.array([100, 100, 100])
-d = domain.size
-N = np.prod(domain)
-
-
-# laplace stencil in d dimensions
-def laplace(d):
-    st1 = np.array([1, -2, 1])
-    s = st1.size
-
-    st = np.zeros(np.full(d, s))
-
-    idx = np.empty(d, dtype=slice)
-    idx.fill(st1.size // 2)
-    idx[0] = slice(None)
-
-    for k in range(d):
-        st[tuple(idx)] += st1
-        idx = np.roll(idx, 1)
-
-    return st
-
-stencil = laplace(d)
-
-print(stencil)
-
-
-#stencil = np.array([[0, -1, 0], [-1, 4, -1], [0, -1, 0]])
-
-
-u = np.zeros(N)
-f = 1 * np.ones(N)
-
-# boundary nodes (first and last planes are special)
-bnd = np.full(N, False)
-for nd in np.cumprod(np.flip(domain[1:], axis=0)):
-    print(f"nd = {nd}")
-    bnd = bnd | (np.arange(0, N) % nd == 0)
-    bnd = bnd | (np.arange(0, N) % -nd == -1)
-bnd = bnd | (np.arange(0, N) // domain[-1] == 0)
-bnd = bnd | (np.arange(0, N) // domain[-1] == N // domain[-1] - 1)
-
-bnd_cnt = np.count_nonzero(bnd)
-free_cnt = np.prod(domain) - bnd_cnt
-
-
-# TODO: set u boundary data
-#u[bnd] = np.arange(0, N)[bnd]
-
-print(f"boundary nodes: {np.count_nonzero(bnd)}")
-print(f"free nodes: {np.count_nonzero(~bnd)}")
-
-elstrides = np.flip(np.cumprod(np.flip(np.append(domain[1:], [1]), 0)), 0)
-diagonals = []
-offsets = []
-
-
-center = (np.array(stencil.shape) - 1) // 2
-for index, value in np.ndenumerate(stencil):
-    if value == 0:
-        continue
-    offset = np.array(index) - center
-    offsets.append(np.dot(elstrides, offset))
-    diagonals.append(value)
-
-print(diagonals, offsets)
-A = diags(diagonals, offsets, np.full(2, np.prod(domain))).tocoo()
-
-f = f - A @ u
-
-def filter_free(bnd, A):
-    assert A.shape[0] == A.shape[1]
-    print("filtering ...", end=" ", flush=True)
-
-    # filter out boundary nodes
-    spfree = ~bnd[A.row] & ~bnd[A.col]
-    rows, cols, data = A.row[spfree], A.col[spfree], A.data[spfree]
-
-    # correct indices
-    offset = -np.cumsum(bnd)
-    rows = rows + offset[rows]
-    cols = cols + offset[cols]
-
-    nf = A.shape[0] - np.count_nonzero(bnd)
-    Af = coo_matrix((data, (rows, cols)), shape=(nf, nf))
-    print("finished!")
-    return Af
-
-Af = filter_free(bnd, A).tocsr()
-
-#Af = A[np.ix_(~bnd, ~bnd)]
-ff = f[~bnd]
-
-print("solving ...", end=" ", flush=True)
-u[~bnd] = spsolve(Af, ff)
-#u[~bnd] = cg(Af, ff)
-print("finished!")
-
-
-x, y = np.meshgrid(np.arange(0, domain[1]), np.arange(0, domain[0]))
-
-print(x.shape, y.shape)
-z = u.reshape(domain)
-
-fig = plt.figure()
-ax = fig.add_subplot(111, projection='3d')
-ax.plot_surface(x, y, z)
-plt.show()
-#
-#
-#print(u.reshape(domain))
-#
-#print(np.diagflat(np.full((n), value)))
-
-
-#np.roll(p, -1) + 2*p + np.roll(
-
-
-
-
-#s = np.sin(2 * np.pi * t)
-#
-#plt.plot(t, s)
-#plt.show()
-
diff --git a/DualTVDD/repl.jl b/test/repl.jl
similarity index 98%
rename from DualTVDD/repl.jl
rename to test/repl.jl
index aa11454..bbeea48 100644
--- a/DualTVDD/repl.jl
+++ b/test/repl.jl
@@ -1,7 +1,6 @@
-using Pkg
-Pkg.activate(".")
-
 using BenchmarkTools
 using Revise
+using Pkg
+Pkg.activate(".")
 
 using DualTVDD
-- 
GitLab