# Code used to generate gradient waveforms that are moment-compensated
# Sequence optimization for diffusion motion-compensation

using MRIsim, JuMP, Ipopt #, Gtk
using MRIsim: get_Bmatrix, get_SRmatrix, get_Mmatrix,
              dur, get_bvalue, write_diff_fwf, delay
## Parameters
dwell_time = 6.4e-6
Gmax =  21e-3 #21e-3 45e-3 T/m
Smax = 50 #52.5 mT/m/ms
plots = true
N1 = 250 #floor(Int64, τ * 1e3 * 15625 / 100) + 2 # Δt = 6.4e-6 #dwell-time
#Path were to write the waveforms
#path = "/media/ccp/Samsung_T5/"
path_file = "/home/ccp/Documents/MRIsim.jl/"
k = 2
sym = false
maxwell = true
moment_nulled, bvalue0 = false, 50
seq_name = maxwell ? "MX_MC$(k)_" : "MC$(k)_"
seq_name = sym ? seq_name*"sym_" : seq_name
seq_name = seq_name * "b" * string(bvalue0)
# Read files
f = readlines(path_file*"qte_timings.txt")
timings = tryparse.(Float64, split(f[2]," "))
δ1, rf180, δ2 = timings[timings .≠ nothing]*1e-3
if sym
    rf180 += δ1 - δ2
    δ1 = δ2
end
#Grads# - Pre-defined RF waveforms.
τ = (δ1 + rf180 + δ2) # τ/Nt = Δt => Nt = τ/Δt
N2 = floor(Int, N1 * δ2 / δ1)
DIF = Sequence([Grad(x -> 1, δ1 - dwell_time, N1) delay(rf180) Grad(x -> 1, δ2 - dwell_time, N2)])
idx180 = N1 + 1
_, N = size(DIF.GR)
#Opt matrices
B =  get_Bmatrix(DIF)  #B-value
SR = get_SRmatrix(DIF) #Slew-rate
M =  get_Mmatrix(DIF; order=0); #Moments
M0v, M1v, M2v = M[1,:], M[2,:], M[3,:]
t = DIF.GR.T
T = [sum(t[1:i])  for i=1:N] #Acumulated time
# Optimazation
Mm = M[2:k+1,:] ./ 10 .^(2:k+1)
println("###")
model = Model(); set_optimizer(model, Ipopt.Optimizer); set_silent(model)
@variable(model, -Gmax <= x[1:N] <= Gmax, start=1, start=Gmax); #max-grads

if moment_nulled
    @objective(model, Min, -x'*B*x); #b-value
    @constraint(model, moments, Mm*x .== 0); #moments
else
    @variable(model, t1)
    # @variable(model, t2)
    @objective(model, Min, t1);
    @constraint(model, bvalue, x'*B*x == bvalue0); #b-value
    @constraint(model, moments, [t1; Mm*x] in MOI.NormOneCone(k+1))
    # @constraint(model, [t2; 1e-7*SR*x] in MOI.NormOneCone(N+2))
end
@constraint(model, M0, M0v'*x == 0) #diffusion condition
@constraint(model, start_seq,  x[1] .== 0); #seq
@constraint(model, end_seq,  x[end] .== 0); #seq
@constraint(model, start_RF180,  x[idx180] .== 0); #rf
@constraint(model, end_RF180,  x[idx180 + 1] .== 0); #rf
@constraint(model, slewrate, -Smax .<= SR*x .<= Smax); #slew rate
if maxwell
    @constraint(model, concomitant, sum(x[1:idx180-1].^2) == sum(x[idx180+1:end].^2)); #concomitant
end
optimize!(model);
gx = value.(x) #retrieving solution
#Seq object
setproperty!.(DIF.GR, :A, [gx'; 0*gx'; 0*gx'])
bmax = gx'*B*gx
@info "λ0 = $(abs(round(M0v'*gx/Gmax,digits=1))), λ1 = $(abs(round(M1v'*gx/Gmax,digits=1))), λ2 = $(abs(round(M2v'*gx/Gmax,digits=1)))"
@info "b-value: $(round(bmax, digits=2)) s/mm2"
@info round(δ1*1e3,digits=4), round(rf180*1e3,digits=4), round(δ2*1e3,digits=4)
@info seq_name
if termination_status(model) == MOI.LOCALLY_SOLVED
    @info "Solved!"
else
    @info "NOT Solved :("
end
#TO SCANNER
write_diff_fwf(DIF,idx180,Gmax,floor(Int64,bmax); filename=path_file*"QTE_Waveforms/$(seq_name).txt", name=seq_name)
write_diff_fwf(DIF,idx180,Gmax,floor(Int64,bmax); filename=path_file*"QTE_Waveforms/qte_vectors_input.txt", name=seq_name)
# Plots
# if plots
MRIsim.plot_grads_moments(DIF,title="ODTI, b=$(round(get_bvalue(DIF)*1e-6, digits=2)) s/mm2, λ0 = $(abs(round(1e3*M0v'*gx,digits=1))), λ1 = $(abs(round(1e3*M1v'*gx,digits=1))), λ2 = $(abs(round(1e3*M2v'*gx,digits=1)))")
# end
# end


using MLStyle
using Pipe
using Statistics

const data = readlines()

const char_pair = Dict([
    '(' => ')',
    '[' => ']',
    '{' => '}',
    '<' => '>',
])

const char_score = Dict([
    ')' => 3,
    ']' => 57,
    '}' => 1197,
    '>' => 25137,
    '(' => 1,
    '[' => 2,
    '{' => 3,
    '<' => 4,
])

function first_err_char(l)
    stack = []
    for c in l
        @match c begin
            cc && if cc ∈ keys(char_pair) end => push!(stack, cc)
            cc && if char_pair[stack[end]] == cc end => pop!(stack)
            cc => return cc, nothing
        end
    end
    return nothing, reverse(stack)
end

# Part One
@pipe data .|> first_err_char .|> get(char_score, _[1], 0) |> sum |> println

compute_score_completion(::Nothing) = 0
compute_score_completion(chars) = mapreduce((acc, x) -> 5acc + x,
                                            chars;
                                            init=0) do c
    get(char_score, c, nothing)
end

# Part Two
@pipe data .|>
      first_err_char .|>
      compute_score_completion(_[2]) |>
      filter(>(0), _) |>
      median |>
      convert(Int, _) |>
      println


"""
    mix_trans(gas::A, P, T, X, mean_MW) where {A <: Arrhenius.Solution}

Compute the tranposrt properties of a mixture using mixture average formula

> Equations Ref. https://personal.ems.psu.edu/~radovic/ChemKin_Theory_PaSR.pdf
> Equations. 5-50/51/52 for viscosity and thermal conductivity

Pure species viscosities [Pa-s]

Thermal conductivity. [W/m/K].

> Equation 5-46 for diffusion

Mixture-averaged diffusion coefficients [m^2/s] relating the mass-averaged diffusive fluxes
(with respect to the mass averaged velocity) to gradients in the species mole fractions.

Test this module in _transport_test.jl

See also implementations in ReacTorch
"""
function mix_trans(gas::A, P, T, X, mean_MW) where {A <: Arrhenius.Solution}

    logT = log(T)
    trans_T = [logT^6, logT^5, logT^4, logT^3, logT^2, logT, 1.0]

    ## species_viscosities_poly

    η = trans_T' * gas.trans.species_viscosities_poly

    Wk_over_Wj = gas.MW * (1.0 ./ gas.MW')

    Wj_over_Wk = 1 ./ Wk_over_Wj

    ηk_over_ηj = η' * (1.0 ./ η)

    Φ = @. 1.0 / sqrt(8.0) / sqrt(1.0 + Wk_over_Wj) *
        (1.0 + sqrt(ηk_over_ηj) * (Wj_over_Wk)^(0.25))^2

    η_mix = sum((X .* η') ./ (Φ * X))

    ## thermal_conductivity_func
    λ = trans_T' * gas.trans.thermal_conductivity_poly

    λ_mix = (sum(X .* λ') + 1.0 / sum(X ./ λ')) / 2.0

    ## binary_diff_coeffs_func

    X_eps = clamp.(X, 1.e-12, Inf)
    D = reshape(trans_T' * gas.trans.binary_diff_coeffs_poly,
                gas.n_species, gas.n_species)

    XjWj = X_eps' * gas.MW .- X_eps .* gas.MW

    XjDjk = sum(X_eps .* (1.0 ./ D'), dims=1) .- (X_eps ./ diag(D))'

    Dkm = XjWj ./ XjDjk' ./ mean_MW / P * one_atm

    return η_mix, λ_mix, Dkm
end
export mix_trans


module MCluster
using DataFrames

    mutable struct MC
        label
        n::Int64
        ls::Array{Float64}
        ss::Array{Float64}
        centroid::Array{Float64}

        function MC()
            x = new()
        end

        function MC(sample::Array{T, 1}, label) where {T<:Number}
            v_size = length(sample)
            x = new()
            x.n = 1
            x.label = label
            x.ls = zeros(T, v_size)
            x.ss = zeros(T, v_size)
            for i = 1:v_size
                x.ls[i] = x.ls[i] + sample[i]
                x.ss[i] = x.ss[i] + sample[i] ^ 2
            end
            x.centroid = copy(sample)
            return x
        end
    end

    function mc_set_centroid(mc::MC)
        mc.centroid = mc.ls / mc.n
    end

    function predict_r(mc::MC, sample::Array{T}) where {T<:Number}
        ss = mc.ss + sample .^2
        ls = mc.ls + sample
        n = mc.n + 1
        return abs(sum((ss / n) - (ls / n).^ 2)) ^ (1/2)
    end

    function append!(mc::MC, sample::Array{T}) where {T<:Number}
        mc.n = mc.n + 1
        mc.ls = mc.ls + sample
        mc.ss = mc.ss + sample .^ 2
        mc_set_centroid(mc)
    end

    function merge(mc_a::MC, mc_b::MC)
        new_mc = MC()
        new_mc.n = mc_a.n + mc_b.n
        new_mc.ls = mc_a.ls + mc_b.ls
        new_mc.ss = mc_a.ss + mc_b.ss
        mc_set_centroid(new_mc)
        return new_mc

    end
end #module


"""
    SSIM([kernel], [(α, β, γ)]) <: FullReferenceIQI
    assess(iqi::SSIM, img, ref)
    ssim(img, ref)

Structural similarity (SSIM) index is an image quality assessment method based
on degradation of structural information.

The SSIM index is composed of three components: luminance, contrast, and
structure; `ssim = 𝐿ᵅ * 𝐶ᵝ * 𝑆ᵞ`, where `W := (α, β, γ)` controls relative
importance of each components. By default `W = (1.0, 1.0, 1.0)`.

In practice, a mean version SSIM is used. At each pixel, SSIM is calculated
locally with neighborhoods weighted by `kernel`, returning a ssim map;
`ssim` is actaully `mean(ssim_map)`.
By default `kernel = KernelFactors.gaussian(1.5, 11)`.

!!! info

    SSIM is defined only for gray images. RGB images are treated as 3d Gray
    images. General `Color3` images are converted to RGB images first, in which
    case, you could manually expand them using `channelview` if you don't want
    them converted to RGB first.

# Example

`ssim(img, ref)` should be sufficient to get a benchmark for algorithms. One
could also instantiate a customed SSIM, then pass it to `assess` or use it as a
function. For example:

```julia
iqi = SSIM(KernelFactors.gaussian(2.5, 17), (1.0, 1.0, 2.0))
assess(iqi, img, ref)
iqi(img, ref)
```

# Reference

[1] Wang, Z., Bovik, A. C., Sheikh, H. R., & Simoncelli, E. P. (2004). Image quality assessment: from error visibility to structural similarity. _IEEE transactions on image processing_, 13(4), 600-612.

[2] Wang, Z., Bovik, A. C., Sheikh, H. R., & Simoncelli, E. P. (2003). The SSIM Index for Image Quality Assessment. Retrived May 30, 2019, from http://www.cns.nyu.edu/~lcv/ssim/
"""
struct SSIM <: FullReferenceIQI
    kernel::AbstractArray{<:Real}
    W::NTuple{3}
    function SSIM(kernel, W)
        ndims(kernel) == 1 || throw(ArgumentError("only 1-d kernel is valid"))
        issymetric(kernel) || @warn "SSIM kernel is assumed to be symmetric"
        all(W .>= 0) || throw(ArgumentError("(α, β, γ) should be non-negative, instead it's $(W)"))
        new(kernel, W)
    end
end

# default values from [1]
const SSIM_KERNEL = KernelFactors.gaussian(1.5, 11) # kernel
const SSIM_W = (1.0, 1.0, 1.0) # (α, β, γ)
SSIM(kernel=SSIM_KERNEL) = SSIM(kernel, SSIM_W)

# api
# By default we don't crop the padding boundary to meet the ssim result from
# MATLAB Image Processing Toolbox, which is used more broadly than the original
# implementaion [2] (written in MATLAB as well).
# TODO: add keyword argument "crop=false" for compatibility
# -- Johnny Chen <johnnychen94@hotmail.com>
(iqi::SSIM)(x, ref) = mean(_ssim_map(iqi, x, ref))

@doc (@doc SSIM)
ssim(x, ref) = SSIM()(x, ref)

# Parameters `(K₁, K₂)` are used to avoid instability when denominator is very
# close to zero. Different from origianl implementation [2], we don't make it
# public since the ssim result is insensitive to these parameters according to
# [1].
# -- Johnny Chen <johnnychen94@hotmail.com>
const SSIM_K = (0.01, 0.03)

# SSIM is defined only for gray images,
# RGB images are treated as 3d gray images,
# other Color3 images are converted to RGB first.
function _ssim_map(iqi::SSIM, x::GenericGrayImage, ref::GenericGrayImage, peakval = 1.0, K = SSIM_K)
    if size(x) ≠ size(ref)
        err = ArgumentError("images should be the same size, instead they're $(size(x))-$(size(ref))")
        throw(err)
    end
    α, β, γ = iqi.W
    C₁, C₂ = @. (peakval * K)^2
    C₃ = C₂/2

    T = promote_type(float(eltype(ref)), float(eltype(x)))
    x = of_eltype(T, x)
    ref = of_eltype(T, ref)

    # calculate ssim in the neighborhood of each pixel, weighted by window
    window = kernelfactors(Tuple(repeated(iqi.kernel, ndims(ref))))

    μx = imfilter(x, window)   # equation (14) in [1]
    μy = imfilter(ref, window) # equation (14) in [1]
    μx² = μx .* μx
    μy² = μy .* μy
    μxy = μx .* μy
    σx² = imfilter(x.^2, window) .- μx²     # equation (15) in [1]
    σy² = imfilter(ref.^2, window) .- μy²   # equation (15) in [1]
    σxy = imfilter(x .* ref, window) .- μxy # equation (16) in [1]

    if [α, β, γ] ≈ [1.0, 1.0, 1.0]
        # equation (13) in [1]
        ssim_map = @. ((2μxy + C₁)*(2σxy + C₂))/((μx²+μy²+C₁)*(σx² + σy² + C₂))
    else
        σx_σy = @. sqrt(σx²*σy²)
        l = @. (2μxy + C₁)/(μx² + μy²) # equation (6) in [1]
        c = @. (2σx_σy + C₂)/(σx² + σy² + C₂) # equation (9) in [1]
        s = @. (σxy + C₃)/(σx_σy + C₃) # equation (10) in [1]

        ssim_map = @. l^α * c^β * s^γ # equation (12) in [1]
    end
    return ssim_map
end

_ssim_map(iqi::SSIM,
          x::AbstractArray{<:AbstractRGB},
          ref::AbstractArray{<:AbstractRGB},
          peakval = 1.0, K = SSIM_K) =
    _ssim_map(iqi, channelview(x), channelview(ref), peakval, K)

_ssim_map(iqi::SSIM,
          x::AbstractArray{<:Color3},
          ref::AbstractArray{<:Color3},
          peakval = 1.0, K = SSIM_K) =
    _ssim_map(iqi, of_eltype(RGB, x), of_eltype(RGB, ref), peakval, K)

# helpers
function issymetric(kernel)
    origin = first(axes(kernel, 1))
    center = (length(kernel)-1) ÷ 2 + origin
    kernel[origin:center] ≈ kernel[end:-1:center]
end


function vertical_cut(A::FuzzyNumber; α::Real=0.2)
    B = copy(A)
    vertical_cut!(B, α=α)
    B
end

function vertical_cut!(A::FuzzyNumber; α::Real=0.2)
	_lvl = Int(floor(length(A.levels) / (1 / α)))
	for lvl = 1:_lvl
		A.grades[lvl] = A.grades[_lvl + 1]
	end
end

function vertical_cut(A::FuzzyNumber, left::Real, right::Real)
    B = copy(A)
	for lvl = 1:length(B.levels)
        _left = B.grades[lvl].left
        _right = B.grades[lvl].right
        if (!isnothing(left) && _left < left) _left = left end
        if (!isnothing(right) && _right < right) _right = right end
		B.grades[lvl] = Interval(_left, _right)
	end
	B
end

function linearly_dampen(A::FuzzyNumber; multiplier::Real=0.5)
    if isSingleton(A)
        return A
    end
    B = copy(A)
    FuzzySets.linearly_dampen!(B, multiplier=multiplier)
    B
end

function linearly_dampen!(A::FuzzyNumber; multiplier::Real=0.5)
    if isSingleton(A)
        return
    end
    x3 = support(A).right
    x2 = peak(A)
    x1 = support(A).left
    y3 = A(x3)
    y2 = A(x2)
    y1 = A(x1)
    bound_0 = (x2 - x1 == 0) ? true : false
    bound_1 = (x3 - x2 == 0) ? true : false
    left_slope = (y2 - y1) / (x2 - x1)
    right_slope = (y3 - y2) / (x3 - x2)
    left_slope /= multiplier
    right_slope /= multiplier
    left_intercept = y2 - (left_slope * x2)
    right_intercept = y2 - (right_slope * x2)
    for (lvl, α) in enumerate(A.levels)
        # x = (y - b) / m
        left = (α - left_intercept) / left_slope
        right = (α - right_intercept) / right_slope
        left = bound_0 ? 0 : left
        right = bound_1 ? 1 : right
        left = min(left, x2)
        right = max(x2, right)
		A.grades[lvl] = Interval(left, right)
	end
end

function reflection_dampen(A::FuzzyNumber)
    B = copy(A)
    reflection_dampen!(B)
    B
end

function reflection_dampen!(A::FuzzyNumber)
    if isSingleton(A)
        return
    end
    x3 = support(A).right
    x2 = peak(A)
    x1 = support(A).left

    bound_0 = (x2 - x1 == 0) ? true : false
    bound_1 = (x3 - x2 == 0) ? true : false

    is_left_narrower =  x2 - x1 < x3 - x2
    for lvl = 1:length(A.levels)
        if is_left_narrower
            left = A.grades[lvl].left
            right = x2 + (x2 - left)
        else
            right = A.grades[lvl].right
            left = x2 - (right - x2)
        end
        # left = bound_0 ? 0 : left
        # right = bound_1 ? 1 : right
        left = min(left, x2)
        right = max(x2, right)
		A.grades[lvl] = Interval(left, right)
	end
end

function midpoint_dampen(A::FuzzyNumber)
    B = copy(A)
    midpoint_dampen!(B)
    B
end

function midpoint_dampen!(A::FuzzyNumber; δ::Float64=0.5)
    if isSingleton(A)
        return
    end

    peak_mid = peak(A)
    for lvl = 1:length(A.levels)
        cut = A.grades[lvl]
        midpoint = mid(cut)
        # is_left_narrower =  midpoint - cut.left < cut.right - midpoint
        # if is_left_narrower
        #     midpoint =
        # else
        # end
        Δleft = midpoint - cut.left
        Δright = cut.right - midpoint
        left = peak_mid - (Δleft * δ)
        right = peak_mid + (Δright * δ)
        left = max(min(left, peak_mid), 0)
        right = min(max(peak_mid, right), 1)
		A.grades[lvl] = Interval(left, right)
	end
end


using Plots,ApproxFun,SingularIntegralEquations;  gadfly()

##
#  Ideal fluid flow consists of level sets of the imagainary part of a function
# that is asymptotic to c*z and whose imaginary part vanishes on Γ
#
#
# On the unit interval, -2*hilbert(ui) gives the imaginary part of cauchy(ui,z)
#  So if we want to find ui defined on Γ so that hilbert(ui) = imag(c*z)
#  then c*z + 2cauchy(u,z) vanishes on Γ
##

u(x,y)=α*(x+im*y)+2cauchy(ui,x+im*y)

m=80;x = linspace(-2.,2.,m);y = linspace(-1.,1.,m+1)
    xx,yy = x.+0.*y',0.*x.+y'

c

ui|>space
Number(c)+hilbert(ui)(0.25+0.125im)

imag(α*(0.25+0.125im))

Hilbert()*ui
x=Fun()
f=Fun(exp)*sqrt(1-x^2)
cauchy(f,.1+.000001im)
2cauchy(f,.1,+)
f(.1)-im*hilbert(f,.1)

d=Circle()
z=Fun(d)
f=exp(real(z))
f(exp(im*0.1))

2imag(cauchy(f,exp(im*0.1),+))+real(hilbert(f,exp(im*0.1)))

im*sum(f/z)
linesum(f)

∪

k=50
    Γ=Interval(0.,1+0.5im)
    z=Fun(Γ)
    α=exp(-π*k/50im)
    c,ui=[1 Hilbert()]\imag(α*z)
    plot(Γ)
    contour!(x,y,imag(u(xx,yy))).o

##
# On an arc, the Hilbert transform no longer gives the imaginary part
#  However, pseudohilbert gives the imaginary part of pseudocauchy
#  So if we want to find ui defined on Γ so that pseudohilbert(ui) = imag(c*z)
#  then c*z + 2pseudocauchy(u,z) vanishes on Γ
##

u(x,y)=α*(x+im*y)+2pseudocauchy(ui,x+im*y)

m=80;x = linspace(-2.,2.,m);y = linspace(-2.,2.,m+1)
    xx,yy = x.+0.*y',0.*x.+y'

k=227;
    Γ=0.5+exp(im*Interval(0.1,-42))
    z=Fun(Γ)
    α=exp(-k/50im)
    c,ui=[1 PseudoHilbert()]\imag(α*z)
    plot(Γ)
    contour!(x,y,imag(u(xx,yy))).o

##
#  Circle
##

Γ=Circle()
z=Fun(Fourier(Γ))

u(x,y)=α*(x+im*y)+2cauchy(ui,x+im*y)

m=80;x = linspace(-2.,2.,m);y = linspace(-2.,2.,m+1)
    xx,yy = x.+0.*y',0.*x.+y'

k=239;
    α=exp(-k/45im)
    c,ui=[0 BasisFunctional(1);
          1 real(Hilbert())]\[0.,imag(α*z)]

    plot(Γ)
    contour!(x,y,imag(u(xx,yy))).o

##
# On a curve, the Hilbert transform may be complex, so we
# take the real part
##

u(x,y)=α*(x+im*y)+2cauchy(ui,x+im*y)

m=80;x = linspace(-2.,2.,m);y = linspace(-2.,2.,m+1)
    xx,yy = x.+0.*y',0.*x.+y'

Γ=Curve(Fun(x->exp(0.8im)*(x+x^2-1+im*(x-4x^3+x^4)/6)))
    z=Fun(Γ)
    α=im
    c,ui=[1 real(Hilbert())]\imag(α*z)
    plot(Γ)
    contour!(x,y,imag(u(xx,yy))).o

##
#  Two intervals requires explicitely stating the space (for now)
##

Γ=Interval(-1.,-0.5)∪Interval(-0.3,1.)
z=Fun(Γ)

ds=PiecewiseSpace(map(d->JacobiWeight(0.5,0.5,Ultraspherical(1,d)),Γ.domains))

u(x,y)=α*(x+im*y)+2cauchy(ui,x+im*y)

m=80;x = linspace(-2.,2.,m);y = linspace(-1.,1.,m+1)
    xx,yy = x.+0.*y',0.*x.+y'

k=114;
    α=exp(k/50*im)
    a,b,ui=[ones(Γ[1]) ones(Γ[2]) Hilbert(ds)]\imag(α*z)
    plot(Γ)
    contour!(x,y,imag(u(xx,yy))).o

Γ=Interval(-1.,0.)∪Interval(0.5im,1.)
z=Fun(Γ)
ds=PiecewiseSpace(map(d->JacobiWeight(0.5,0.5,Ultraspherical(1,d)),Γ.domains))

u(x,y)=α*(x+im*y)+2cauchy(ui,x+im*y)

m=80;x = linspace(-2.,2.,m);y = linspace(-1.,1.,m+1)
    xx,yy = x.+0.*y',0.*x.+y'

k=114;
    α=exp(k/50*im)
    a,b,ui=[ones(Γ[1]) ones(Γ[2]) real(Hilbert(ds))]\imag(α*z)
    plot(Γ)
    contour!(x,y,imag(u(xx,yy)))

Γ=Interval(-im,1.0-im)∪Curve(Fun(x->exp(0.8im)*(x+x^2-1+im*(x-4x^3+x^4)/6)))
z=Fun(Γ)

ds=PiecewiseSpace([JacobiWeight(0.5,0.5,Ultraspherical(1,Γ[1])),
                       MappedSpace(Γ[2],JacobiWeight(0.5,0.5,Ultraspherical(1)))])

m=80;x = linspace(-2.,2.,m);y = linspace(-3.,2.,m+1)
    xx,yy = x.+0.*y',0.*x.+y'

k=114;
    α=exp(k/50*im)
    a,b,ui=[ones(Γ[1]) ones(Γ[2]) real(Hilbert(ds))]\imag(α*z)
Gadfly.plot(ApproxFun.layer(Γ),
                layer(x=x,y=y,z=imag(u(xx,yy)),Main.Gadfly.Geom.contour(levels=[-3.:.05:4.])))

## Channel

# TODO: rename
using ApproxFun,SingularIntegralEquations,SO,Plots
    import ApproxFun:RealUnivariateSpace,ComplexBasis,canonicalspace,bandinds,addentries!,conversion_rule,
            spacescompatible,toeplitz_addentries!,domainscompatible,rangespace, ConcreteConversion,
            coefficients
    import SingularIntegralEquations:cauchy,ConcreteHilbert, stieltjes
immutable FourierDirichlet{DD} <: RealUnivariateSpace{DD}
    domain::DD
end

FourierDirichlet()=FourierDirichlet(PeriodicInterval())

    spacescompatible(a::FourierDirichlet,b::FourierDirichlet)=domainscompatible(a,b)

    canonicalspace(S::FourierDirichlet)=Fourier(domain(S))

    bandinds{DD}(::ConcreteConversion{FourierDirichlet{DD},Fourier{DD}})=-1,1
    function addentries!{DD}(C::ConcreteConversion{FourierDirichlet{DD},Fourier{DD}},A,kr::Range,::Colon)
        toeplitz_addentries!([1.],[0.,1.],A,kr)
    end

    conversion_rule{DD}(b::FourierDirichlet{DD},a::Fourier{DD})=b

    setdomain(F::FourierDirichlet,d::Domain)=FourierDirichlet(d)

    function coefficients(v::Vector,a::FourierDirichlet,b::Fourier)
        ret=zeros(typeof(v),length(v)+1)
        ret[2]=v[1]
        for k=2:length(v)
            ret[k-1]+=v[k]
            ret[k+1]+=v[k]
        end
        ret
    end

bandinds{L<:PeriodicLine}(H::ConcreteHilbert{FourierDirichlet{L}})=-2,2
    rangespace{L<:PeriodicLine}(H::ConcreteHilbert{FourierDirichlet{L}})=Fourier(domain(H))

    function addentries!{L<:PeriodicLine}(H::ConcreteHilbert{FourierDirichlet{L}},A,kr::Range,::Colon)
        if 1 in kr
            A[1,1]+=1
        end
        addentries!(real(Hilbert(FourierDirichlet(Circle()))),A,kr,:)
        A
    end

stieltjes{DD}(sp::FourierDirichlet{DD},cfs,z)=stieltjes(Fun(Fun(f,sp),Fourier),z)
    stieltjes{PL}(f::Fun{Fourier{PL}},z::Vector)=Complex128[stieltjes(f,zk) for zk in z]
    stieltjes{PL}(f::Fun{Fourier{PL}},z::Matrix)=reshape(stieltjes(f,vec(z)),size(z,1),size(z,2))::Matrix{Complex128}
    stieltjes{PL}(f::Fun{FourierDirichlet{PL}},z)=stieltjes(Fun(f,Fourier),z)

S=FourierDirichlet(PeriodicLine()-1.0im)
PS=PiecewiseSpace([S,JacobiWeight(0.5,0.5,Ultraspherical(1,Interval(-0.5im,1.+1.1im))),
                    JacobiWeight(0.5,0.5,Ultraspherical(1,Interval(-0.5,-0.5+.5im)))])

H=Hilbert(PS)

f=Fun(z->imag(z+1.0im),rangespace(H))
Γ=domain(PS)

a,b,ui=linsolve([ones(Γ[2]) ones(Γ[3]) real(H)],f;tolerance=1E-10)
m=80;x = linspace(-2.,3.,m);y = linspace(-1.,3.,m+1)
    xx,yy = x.+0.*y',0.*x.+y'

u(x,y)=(x+im*y)+2cauchy(ui,x+im*y)
    vals=imag(u(xx,yy))

pyplot()
plot(Γ[2];color=:Blue,legend=false)
    plot!(Γ[3];color=:Blue)
    plot!(Interval(-2.-im,3.0-im);color=:Blue)
    contour!(x,y,vals;ylims=(-2.,3.),levels=-1.:.1:3.)

Main.PyPlot.savefig("halfg.png";dpi=200,bbox_inches="tight")

png("halfplane")

gadfly()
pdf("halfplane")

Gadfly.plot(ApproxFun.layer(Γ[2]),

            ApproxFun.layer(Interval(-2.-im,3.0-im)),
                layer(x=x,y=y,z=imag(u(xx,yy)),Main.Gadfly.Geom.contour(levels=[-1.:0.05:2.])))

pieces(ui)[2]|>ApproxFun.plot
g=pieces(ui)[1]
    g=Fun(g.coefficients,space(g).space)
    g=Fun(g,Fourier)

g|>ApproxFun.plot

S1=MappedSpace(PeriodicLine()-1.0im,FourierDirichlet())
S2=MappedSpace(PeriodicLine()+1.0im,FourierDirichlet())

PS=PiecewiseSpace([S1,S2])
H=Hilbert(PS)

using SO

ds=PS[1]
rs=MappedSpace(domain(PS[2]),Fourier())
b=Fun([zeros(18);1.],ds);
    Fun(x->-stieltjes(b,x)/π,rs).coefficients|>chopm

g=Fun(b,MappedSpace(domain(PS[1]),Fourier()))
@which cauchy(b,5.0-1.0im)
cauchy(g,5.0-1.0im)
Fun(x->-stieltjes(b,x)/π,rs,20)

v1=chop(Fun(x->-stieltjes(b,x)/π,rs).coefficients,tol)

f=Fun(z->imag(z+1.0im),rangespace(H))
Γ=domain(PS)

a,ui=linsolve([ones(Γ[2]) real(H)],f;tolerance=1E-5)
m=80;x = linspace(-2.,3.,m);y = linspace(-1.5,2.,m+1)
    xx,yy = x.+0.*y',0.*x.+y'

u(x,y)=(x+im*y)+2cauchy(ui,x+im*y)
Gadfly.plot(ApproxFun.layer(Γ[2]),
            ApproxFun.layer(Interval(-2.-im,3.0-im)),
                layer(x=x,y=y,z=imag(u(xx,yy)),Main.Gadfly.Geom.contour(levels=[-1.:0.05:2.])))

d=Circle()
f=Fun(exp,d)
g=Fun(cos,d)

s=z->imag(conj(z)*cauchy(f,z)+cauchy(g,z))
pyplot()
x=y=linspace(-1.1,1.1,40)
z=Float64[s(x+im*y) for x in x, y in y]

s(0.999999999exp(im*1.))-imag(exp(-im)*f(exp(im))+g(exp(im)))
s(0.999999999exp(im*1.))
exp(dual(0.99999999999exp(im*1.),exp(im*1.)))
z=dual(0.99999999999exp(im*1.),exp(im*1.))
conj(z(&exp(

exp(im*1.)*exp(exp(im*1.))
z=Fun(d)
Multiplication(Fun([0.,0.,1.],Fourier(d)),Fourier(d))
Multiplication(conj(z),Laurent(d))
imag(f(z) +  conj(z)*g(z))

g(z)==r(z)/2
f(z)==q(z)-z/2*r(z)

imag(q(z) +  (z+conj(z))/2*r(z))

imag(exp(z))
imag(conj(z)(1/abs(z)*f(z)      +z*f'(z)))+z*g(z)) == 0

realpart(imag(z*exp(z))           )
realpart(imag(conj(z)(1/abs(z)*f(z)      +z*f'(z)))+z*g(z))        )
dualpart(imag(conj(z)*exp(z))          )-(realpart(imag(conj(z)/abs(z)*exp(z)           + conj(z)*z*exp(z))))

conj(z)

s(dual(0.99999999999exp(im*1.),exp(im*1.)))
dualpart(cos((10E5)*dual(1,2)))+2*10E5*sin(10E5)
2exp(1.)
expm([1 1 1/2;
      0 1 1;
      0 0 1])
cauchy(f,exp(im),+)
cauchy(g,exp(im),+)
gui()
surface(x,y,z)
s(.1+.2im)

real(Hilbert())  # Im g(z)


"""
     ps2ls(psys::PeriodicStateSpace[, kstart]; ss = false, cyclic = false) -> sys::DescriptorStateSpace

Build the discrete-time lifted LTI system equivalent to a discrete-time periodic system.

For a discrete-time periodic system `psys = (A(t),B(t),C(t),D(t))` with period `T` and sample time `Ts`,
the equivalent stacked (see [1]) LTI descriptor state-space representation
`sys = (A-λE,B,C,D)` is built, with the input, state and output vectors defined over time intervals of length `T` (instead `Ts`).
The optional argument `kstart` specifies a desired time to start the sequence of periodic matrices (default: `kstart = 1`).

If `ss = true` (default: `ss = false`), then all non-dynamic modes are elliminated and
a standard state-space realization (with `E = I`) is determined, which corresponds to the lifting techniques of [2], where
only the input and output vectors are defined over time intervals of length `T`.
The determination of the standard lifted representation involves forming matrix products
(e.g., by explicitly forming the monodromy matrix) and therefore is potentially less suited for numerical computations.

If cyclic = true, the cyclic reformulation of [3] is used to build a lifted standard system with
the input, state and output vectors defined over time intervals of length `T`.

_References_

[1] O. M. Grasselli and S. Longhi. Finite zero structure of linear periodic discrete-time systems.
    Int. J. Systems Sci., 22:1785–1806,  1991.

[2] R. A. Meyer and C. S. Burrus. A unified analysis of multirate and periodically time-varying
    digital filters”, IEEE Transactions on Circuits and Systems, 22:162–167, 1975.

[3] D. S. Flamm. A new shift-invariant representation for periodic systems,
    Systems and Control Letters, 17:9–14, 1991.
"""
function ps2ls(psys::PeriodicStateSpace{<:AbstractPeriodicArray{:d,T}}, kstart::Int = 1; ss::Bool = false, cyclic::Bool = false) where {T}
    (pa, pb, pc, pd) = (psys.A.dperiod, psys.B.dperiod, psys.C.dperiod, psys.D.dperiod)
    (na, nb, nc, nd) = (psys.A.nperiod, psys.B.nperiod, psys.C.nperiod, psys.D.nperiod)
    ndx, nx = size(psys.A)
    p, m = size(psys)
    patype = length(nx) == 1
    Ts = psys.A.Ts
    K = pa*na  # number of blocks
    N = patype ? nx*pa*na : sum(nx)*na  # dimension of lifted state vector
    M = K*m
    P = K*p
    B = zeros(T, N, M)
    # n = patype ? ndx[1] : ndx[mod(kstart+pb-2,pb)+1]
    # copyto!(view(B,1:n,M-m+1:M),getpm(psys.B,kstart+pb-1,pb))
    n = patype ? ndx[1] : ndx[mod(kstart-2,pb)+1]
    copyto!(view(B,1:n,M-m+1:M),getpm(psys.B,kstart-1,pb))
    i1 = n+1
    j1 = 1
    pbc = pb
    for i = 1:nb
        k = kstart
        i == nb && (pbc = pb-1)
        for j = 1:pbc
            jj = patype ? 1 : mod(k-1,pb)+1
            i2 = i1+ndx[jj]-1
            j2 = j1+m-1
            copyto!(view(B,i1:i2,j1:j2),getpm(psys.B,k,pb))
            k += 1
            i1 = i2+1
            j1 = j2+1
        end
    end
    C = zeros(T, P, N)
    i1 = 1
    j1 = 1
    for i = 1:nc
        k = kstart
        for j = 1:pc
            jj = patype ? 1 : mod(k-1,pc)+1
            i2 = i1+p-1
            j2 = j1+nx[jj]-1
            copyto!(view(C,i1:i2,j1:j2),getpm(psys.C,k,pc))
            k += 1
            i1 = i2+1
            j1 = j2+1
        end
    end
    D = zeros(T, P, M)
    i1 = 1
    j1 = 1
    for i = 1:nd
        k = kstart
        for j = 1:pd
            i2 = i1+p-1
            j2 = j1+m-1
            copyto!(view(D,i1:i2,j1:j2),getpm(psys.D,k,pd))
            k += 1
            i1 = i2+1
            j1 = j2+1
        end
    end
    A = zeros(T, N, N)
    nf = patype ? nx[1] : nx[mod(kstart-2,pa)+1]
    copyto!(view(A,1:n,N-nf+1:N),getpm(psys.A,kstart-1,pa))
    i1 = n+1
    j1 = 1
    pac = pa
    U = -I
    for i = 1:na
        k = kstart
        i == na && (pac = pa-1)
        for j = 1:pac
            jj = patype ? 1 : mod(k-1,pa)+1
            jj1 = patype ? 1 : mod(k,pa)+1
            i2 = i1+ndx[jj]-1
            j2 = j1+nx[jj]-1
            copyto!(view(A,i1:i2,j1:j2),getpm(psys.A,k,pa))
            cyclic || copyto!(view(A,i1:i2,j2+1:j2+nx[jj1]),U)
            k += 1
            i1 = i2+1
            j1 = j2+1
        end
    end
    cyclic && (return dss(A, B, C, D; Ts))
    if ss
       # generate the standard lifted system using residualization formulas for E = [I 0; 0 0]
       i1 = 1:n; i2 = n+1:N
       A11 = view(A,i1,i1)
       A12 = view(A,i1,i2)
       A21 = view(A,i2,i1)
       A22 = LowerTriangular(view(A,i2,i2))
       B1 = view(B,i1,:)
       B2 = view(B,i2,:)
       C1 = view(C,:,i1)
       C2 = view(C,:,i2)
       # make A22 = I
       ldiv!(A22,A21)
       ldiv!(A22,B2)
       # apply simplified residualization formulas
       mul!(D, C2, B2, -1, 1)
       mul!(B1, A12, B2, -1, 1)
       mul!(C1, C2, A21, -1, 1)
       mul!(A11, A12, A21, -1, 1)
       return dss(A11, B1, C1, D; Ts)
    else
       # generate the stacked lifted descriptor system of (Grasselli and Longhi, 1991)
       E = zeros(T, N, N)
       copyto!(view(E,1:n,1:n),I)
       return dss(A, E, B, C, D; Ts)
    end
end
function ps2ls1(psys::PeriodicStateSpace{PeriodicMatrix{:d,T}}, kstart::Int = 1; ss::Bool = false, cyclic::Bool = false) where {T}
    pa = psys.A.dperiod
    na = psys.A.nperiod
    ndx, nx = size(psys.A)
    Ts = psys.A.Ts
    K = pa*na  # number of blocks
    N = sum(nx)*na  # dimension of lifted state vector
    pb = psys.B.dperiod
    nb = psys.B.nperiod
    pc = psys.C.dperiod
    nc = psys.C.nperiod
    pd = psys.D.dperiod
    nd = psys.D.nperiod
    m = psys.nu[1]
    M = K*m
    p = psys.ny[1]
    P = K*p
    B = zeros(T, N, M)
    jfin = mod(kstart+pb-2,pb)+1
    n = ndx[jfin]
    copyto!(view(B,1:n,M-m+1:M),psys.B.M[jfin])
    i1 = n+1
    j1 = 1
    pbc = pb
    for i = 1:nb
        k = kstart
        i == nb && (pbc = pb-1)
        for j = 1:pbc
            jj = mod(k-1,pb)+1
            i2 = i1+ndx[jj]-1
            j2 = j1+m-1
            copyto!(view(B,i1:i2,j1:j2),psys.B.M[jj])
            k += 1
            i1 = i2+1
            j1 = j2+1
        end
    end
    C = zeros(T, P, N)
    i1 = 1
    j1 = 1
    for i = 1:nc
        k = kstart
        for j = 1:pc
            jj = mod(k-1,pc)+1
            i2 = i1+p-1
            j2 = j1+nx[jj]-1
            copyto!(view(C,i1:i2,j1:j2),psys.C.M[jj])
            k += 1
            i1 = i2+1
            j1 = j2+1
        end
    end
    D = zeros(T, P, M)
    i1 = 1
    j1 = 1
    for i = 1:nd
        k = kstart
        for j = 1:pd
            i2 = i1+p-1
            j2 = j1+m-1
            copyto!(view(D,i1:i2,j1:j2),psys.D.M[mod(k-1,pd)+1])
            k += 1
            i1 = i2+1
            j1 = j2+1
        end
    end
    A = zeros(T, N, N)
    jafin = mod(kstart+pa-2,pa)+1
    nf = nx[jafin]
    copyto!(view(A,1:n,N-nf+1:N),psys.A.M[jafin])
    i1 = n+1
    j1 = 1
    pac = pa
    U = cyclic ? 0I : -I
    for i = 1:na
        k = kstart
        i == na && (pac = pa-1)
        for j = 1:pac
            jj = mod(k-1,pa)+1
            jj1 = mod(k,pa)+1
            i2 = i1+ndx[jj]-1
            j2 = j1+nx[jj]-1
            copyto!(view(A,i1:i2,j1:j2),psys.A.M[jj])
            copyto!(view(A,i1:i2,j2+1:j2+nx[jj1]),U)
            k += 1
            i1 = i2+1
            j1 = j2+1
        end
    end
    cyclic && (return dss(A, B, C, D; Ts))
    if ss
       # generate the standard lifted system using residualization formulas for E = [I 0; 0 0]
       i1 = 1:n; i2 = n+1:N
       A11 = view(A,i1,i1)
       A12 = view(A,i1,i2)
       A21 = view(A,i2,i1)
       A22 = LowerTriangular(view(A,i2,i2))
       B1 = view(B,i1,:)
       B2 = view(B,i2,:)
       C1 = view(C,:,i1)
       C2 = view(C,:,i2)
       # make A22 = I
       ldiv!(A22,A21)
       ldiv!(A22,B2)
       # apply simplified residualization formulas
       mul!(D, C2, B2, -1, 1)
       mul!(B1, A12, B2, -1, 1)
       mul!(C1, C2, A21, -1, 1)
       mul!(A11, A12, A21, -1, 1)
       return dss(A11, B1, C1, D; Ts)
    else
       # generate the stacked lifted descriptor system of (Grasselli and Longhi, 1991)
       E = zeros(T, N, N)
       copyto!(view(E,1:n,1:n),I)
       return dss(A, E, B, C, D; Ts)
    end
end

ps2ls1(psys::PeriodicStateSpace{PeriodicArray{:d,T}}, k::Int = 1; kwargs...) where {T} =
      ps2ls1(convert(PeriodicStateSpace{PeriodicMatrix},psys), k; kwargs...)

"""
     ps2frls(psysc::PeriodicStateSpace, N) -> sys::DescriptorStateSpace

Build the real frequency-lifted representation of a continuous-time periodic system.

For a continuos-time periodic system `psysc = (A(t),B(t),C(t),D(t))`, the real
LTI state-space representation `sys = (At-Nt,Bt,Ct,Dt)` is built, where `At`, `Bt`, `Ct` and `Dt`
are truncated block Toeplitz matrices and `Nt` is a block diagonal matrix.
`N` is the number of selected harmonic components in the Fourier series of system matrices.

_Note:_ This is an experimental implementation based on the operator representation of periodic matrices
in the [ApproxFun.jl](https://github.com/JuliaApproximation/ApproxFun.jl) package.
"""
function ps2frls(psysc::PeriodicStateSpace{PM}, N::Int; P::Int= 1) where {T,PM <: AbstractPeriodicArray{:c,T}}
    psyscfr = typeof(psysc) <: PeriodicStateSpace{FourierFunctionMatrix} ? psysc :
              convert(PeriodicStateSpace{FourierFunctionMatrix},psysc)
    N >= 0 || error("number of selected harmonics must be nonnegative, got $N")
    (Af, Bf, Cf, Df) = P == 1 ? (psyscfr.A, psyscfr.B, psyscfr.C, psyscfr.D) :
                                (FourierFunctionMatrix(Fun(t -> psyscfr.A.M(t),Fourier(0..P*psyscfr.A.period))),
                                 FourierFunctionMatrix(Fun(t -> psyscfr.B.M(t),Fourier(0..P*psyscfr.B.period))),
                                 FourierFunctionMatrix(Fun(t -> psyscfr.C.M(t),Fourier(0..P*psyscfr.C.period))),
                                 FourierFunctionMatrix(Fun(t -> psyscfr.D.M(t),Fourier(0..P*psyscfr.D.period))))

    n, m = size(Bf); p = size(Cf,1);
    D = Derivative(domain(Af.M))
    ND = DiagDerOp(D,n)
    Aop = Af.M - ND
    Cop = Multiplication(Cf.M,domainspace(ND))
    sdu = domainspace(DiagDerOp(0*D,m))
    Bop = Multiplication(Bf.M,sdu)
    Dop = Multiplication(Df.M,sdu)
    Ntx = 2*n*(2*N+1)
    Ntu = m*(2*N+1)
    Nty = p*(2*N+1)
    sys = dss(Matrix(Aop[1:Ntx,1:Ntx]), Matrix(Bop[1:Ntx,1:Ntu]), Matrix(Cop[1:Nty,1:Ntx]), Matrix(Dop[1:Nty,1:Ntu]))
    return sys
end
"""
     ps2fls(psysc::PeriodicStateSpace, N; P) -> sys::DescriptorStateSpace

Build the frequency-lifted representation of a continuous-time periodic system.

For a continuos-time periodic system `psysc = (A(t),B(t),C(t),D(t))`, the (complex)
LTI state-space representation `sys = (At-Nt,Bt,Ct,Dt)` is built, where `At`, `Bt`, `Ct` and `Dt`
are truncated block Toeplitz matrices and `Nt` is a block diagonal matrix (see [1] or [2]).
`N` is the number of selected harmonic components in the Fourier series of system matrices
and the keyword parameter `P` is the number of full periods to be considered (default: `P = 1`).

_References_

[1] N. M. Wereley. Analysis and control of linear periodically time varying systems.
    Ph.D. thesis, Department of Aeronautics and Astronautics, MIT, 1990.

[2] S. Bittanti and P. Colaneri. Periodic Systems : Filtering and Fontrol.
    Springer-Verlag London, 2009.
"""
function ps2fls(psysc::PeriodicStateSpace{PM}, N::Int; P::Int= 1) where {T,PM <: AbstractPeriodicArray{:c,T}}
    psyshr = typeof(psysc) <: PeriodicStateSpace{HarmonicArray} ? psysc :
             convert(PeriodicStateSpace{HarmonicArray},psysc)
    N >= 0 || error("number of selected harmonics must be nonnegative, got $N")
    P >= 1 || error("period multiplicator must be at least 1, got $P")
    (PA, PB, PC, PD ) = (psyshr.A.nperiod, psyshr.B.nperiod, psyshr.C.nperiod, psyshr.D.nperiod)
    K = lcm(PA,PB,PC,PD)
    #println("K = $K PA = $PA PB = $PB PC = $PC PD = $PD")
     # sys = dss(hr2btupd(psyshr.A, N*div(K,PA); P = P*PA), hr2bt(psyshr.B,N*div(K,PB); P = P*PB),
    #           hr2bt(psyshr.C,N*div(K,PC); P = P*PC),hr2bt(psyshr.D,N*div(K,PD); P = P*PD))
    # sys = dss(hr2btupd(psyshr.A, N; P = P*PA, nperiod = div(K,PA)), hr2bt(psyshr.B, N; P = P*PB, nperiod = div(K,PB)),
    #           hr2bt(psyshr.C, N; P = P*PC, nperiod = div(K,PC)),hr2bt(psyshr.D, N; P = P*PD, nperiod = div(K,PD)))
    sys = dss(hr2btupd(psyshr.A, N; P, nperiod = K), hr2bt(psyshr.B, N; P, nperiod = K),
              hr2bt(psyshr.C, N; P, nperiod = K), hr2bt(psyshr.D, N; P, nperiod = K))
    return sys
end
"""
     hr2bt(Ahr::HarmonicArray, N; P, nperiod]) -> Abt::Matrix

Build the block Toeplitz matrix of a harmonic (Fourier) array representation of a periodic matrix.

The harmonic representation object `Ahr` of period `T` of a periodic matrix `Ahr(t)`
of subperiod `T′ = T/k` is in the form

                     p
     Ahr(t) = A_0 +  ∑ ( Ac_i*cos(i*t*2*π/T′)+As_i*sin(i*2*π*t/T′) ) ,
                    i=1

where `k ≥ 1` is the number of subperiods. `Ahr(t)` can be equivalently expressed in the Fourier series
representation form

                p
     Ahr(t) =   ∑ A_i*exp(im*i*ωh*t) ,
               i=-p

where `ωh = 2π/T′`, `A_i = (Ac_i-im*As_i)/2` and  `A_{-i} = (Ac_i+im*As_i)/2`.
`N` is the number of selected harmonic components (or Fourier modes) used for approximation.
The keyword parameter `P` is the number of full periods to be considered (default: `P = 1`) and `nperiod` is the
number of subperiods to be considered, such that `1 ≤ nperiod ≤ k` (default: `nperiod = k`).

For a given number `N ≥ p`, if the number of period is `P = 1` and the number of subperiods is `nperiod = 1`,
then the _banded_ block Toeplitz matrix `Abt` with `(2N+1)×(2N+1)` blocks is constructed

           ( A_0  A_{-1} …  A_{-p}        0    )
           ( A_1   A_0             ⋱           )
           (  ⋮         ⋱            ⋱         )
     Abt = ( A_p             ⋱          A_{-p} )
           (        ⋱           ⋱         ⋮    )
           (  0        A_p      …         A_0  )

If `N < p`, then a truncated _full_ block Toeplitz matrix is built using the first `N` harmonic components.

Generally, for given `P ≥ 1` and  `nperiod ≥ 1`, the block Toeplitz matrix `Abt` is constructed with `(2N*np+1)×(2N*np+1)` blocks,
with `np = P*nperiod`, such that each `A_i` is preceeded in its column by `np-1` zero blocks,
each `A_{-i}` is preceeded in its row by `np-1` zero blocks and all diagonal blocks are equal to`A_0`.
"""
function hr2bt(A::HarmonicArray, N::Int; P::Int = 1, nperiod::Int = A.nperiod)
    N > 0 || error("the number of harmonic components must be nonnegative, got $N")
    #(nperiod < 1 || nperiod > A.nperiod) && error("number of subperiods must be between 1 and $(A.nperiod), got $nperiod")
    nperiod < 1  && error("number of subperiods must be between 1 and $(A.nperiod), got $nperiod")
    P < 1 && error("number of period must be at least 1, got $P")
    T = promote_type(Float64,eltype(A))
    p = size(A.values,3)-1
    p < 0 && (return zeros(complex(T),0,0))
    m, n = size(A)
    np = P*nperiod
    nb = 2*N*np+1
    BT = zeros(complex(T),nb*m,nb*n)
    i1, i2, j1, j2 = 1, m, 1, n
    BT[i1:i2,j1:j2] = A.values[:,:,1] # the diagonal A0
    ki1 = m*np+1
    ki2 = ki1+m-1
    kj1 = n*np+1
    kj2 = kj1+n-1
    minpN = min(p,N)
    for k = 1:minpN
        BT[i1:i2,kj1:kj2] = A.values[:,:,k+1]/2           # this is A_{-k} := (Ac_k+im*As_k)/2
        BT[ki1:ki2,j1:j2] = conj(view(BT,i1:i2,kj1:kj2))  # this is A_k    := (Ac_k-im*As_k)/2
        ki1 += m*np
        ki2 += m*np
        kj1 += n*np
        kj2 += n*np
    end
    i1 = 1
    j1 = n+1
    ik1 = m+1
    ik2 = 2m
    jk1 = n+1
    jk2 = 2n
    for i = 1:nb-1
        i1 += m
        i2 = min(i1+m*(minpN*np+1)-1,nb*m)
        i1 <= i2 || continue
        j1 += n
        j2 = min(j1+n*np-1,nb*n)
        BT[i1:i2,jk1:jk2] = BT[1:min(i2-i1+1,nb*m),1:n]
        BT[ik1:ik2,j1:j2] = BT[1:m,n+1:n+min(j2-j1+1,nb*n)]
        ik1 += m
        ik2 += m
        jk1 += n
        jk2 += n
    end
    return BT
end
"""
     hr2btupd(Ahr::HarmonicArray, N; P, nperiod, shift]) -> Ab::Matrix

Build the updated block Toeplitz matrix of a harmonic (Fourier) array representation of a periodic matrix.

If `BT` is the block Toeplitz matrix of the harmonic array representation of the `n × n` periodic matrix `Ahr` of subperiod `T′`
(see [`HarmonicArray`](@ref)) as constructed with [`hr2bt`](@ref), then the updated matrix Ab = BT-NT is constructed,
with `NT` a block-diagonal matrix with `n × n` diagonal blocks.
The `k`-th diagonal block of `NT` is the diagonal matrix `im*(μ + k*ωh)*I`, where `μ` is a shift specified via
the keyword parameter `shift = μ` (default: `μ = 0`)  and `ωh` is the base frequency computed as `ωh = 2π*nperiod/(P*T′)`.
The value of shift must satisfy `0 ≤ μ ≤ ωh/2`.
"""
function hr2btupd(A::HarmonicArray, N::Int; P::Int = 1, nperiod::Int = A.nperiod, shift::Real = 0)
    n = size(A,1)
    n == size(A,2) || error("the periodic matrix must be square")
    BT = hr2bt(A, N; P, nperiod)
    np = P*nperiod
    nb = 2*N*np+1
    ωh = 2*pi/P/A.period/A.nperiod*nperiod
    (shift >= 0 && shift <= ωh/2)
    #Ej0 = fill(eltype(BT)(shift + im*ωh),n)
    Ej0 = similar(Vector{eltype(BT)},n)
    k = -N*np
    for i = 1:nb
        ind = (i-1)*n+1:i*n
        BT0 = view(BT,ind,ind)
        #BT0[diagind(BT0)] = diag(BT0)-k*Ej0;
        BT0[diagind(BT0)] = diag(BT0)-fill!(Ej0,(im*(shift + k*ωh)))
        k += 1
    end
    return BT
end

# function phasemat(A::HarmonicArray{:c,T}, N::Int; P::Int = 1, nperiod::Int = A.nperiod, shift::Real = 0) where T
#     (nperiod < 1 || nperiod > A.nperiod) && error("number of subperiods must be between 1 and $(A.period), got $nperiod")
#     P < 1 && error("number of period must be at least 1, got $P")
#     T1 = promote_type(Float64,T)
#     n, m = size(A)
#     n == m || error("the periodic matrix must be square")
#     #nperiod = A.nperiod
#     np = P*nperiod
#     nb = 2*N*np+1
#     #ωh = 2*pi/A.period
#     ωh = 2*pi/A.period/nperiod
#     ωh = 2*pi/P/A.period/A.nperiod*nperiod
#     E = Diagonal((shift + im*ωh)*Matrix{complex(T)}(I,nb*n,nb*n))
#     k = -N*np
#     for i = 1:nb
#         ind = (i-1)*n+1:i*n
#         rmul!(view(E,ind,ind),k)
#         #k += nperiod
#         k += 1
#     end
#     return E
# end
# DiagDerOp(D::ApproxFunBase.DerivativeWrapper, n::Int) =
#    eval(Meta.parse(string("[",join([string(join(["0I " for j in 1:i-1]),"D ",join(["0I " for j in i+1:n]),";") for i = 1:n]),"]")))
# DiagDerOp(n::Int) =
#    string("[",join([string(join(["0I " for j in 1:i-1]),"D ",join(["0I " for j in i+1:n]),";") for i = 1:n]),"]")

function DiagDerOp(D::Union{ApproxFunBase.DerivativeWrapper,ApproxFunBase.ConstantTimesOperator}, n::Int)
    Z = tuple(D,ntuple(n->0I,n-1)...)
    for i = 2:n
        Z1 = tuple(ntuple(n->0I,i-1)...,D,ntuple(n->0I,n-i)...)
        Z = tuple(Z...,Z1...)
    end
    return hvcat(n,Z...)
end


using MultilevelEstimators
using DifferentialEquations
using Reporter
using FileIO
using DelimitedFiles

DIR=pwd()*"/MultilevelEstimators/data"

# DifferentialEquations SDE example partly taken from https://diffeq.sciml.ai/stable/tutorials/sde_example/

# define the problem
α=0.05
β=0.2
u₀=100.
f(u,p,t) = α*u
g(u,p,t) = β*u
dt = 1/2^(4)
T = 1.
tspan = (0.0, T)
P_func = (x) -> exp(-α*T)*maximum([0, x-100.])
prob = SDEProblem(f,g,u₀,(0.0,1.0), dt=dt)

sol = solve(prob,EM())
using Plots; plotly() # Using the Plotly backend
p1 = plot(sol)

MEAN = 0
P = 10^4
for p=1:P
    sol = solve(prob,EM())
    MEAN += sol[end]
end
MEAN /= P

# The mean of the solution at the end, should equal ???

# Multilevelusing FileIOEstimators.jl setup, adapted from documentation

LL = 20
SDE_probs = zeros(LL)
for i in 1:LL
    dt = 1//2^(i+1)
    SDE_probs[i] = dt
end

function sample_SDE(level, ω; TEST=false)
    # solve on finest grid
    SDE_prob_coarse = SDEProblem(f, g, u₀, (0.0, 1.0), dt=SDE_probs[level + one(level)])
    sol1 = solve(SDE_prob_coarse, EM(), save_noise = true) # TO DO, dt needs to be varied
    Qf = P_func(sol1.u[end])

    # compute difference when not on coarsest grid
    dQ = copy(Qf)
    if level != Level(0)
        SDE_prob_fine = SDEProblem(f, g, u₀, (0.0, 1.0), dt=SDE_probs[level], noise=NoiseWrapper(sol1.W))
        sol2 = solve(SDE_prob_fine, EM())
        Qc = P_func(sol2.u[end])
        dQ -= Qc
    end

    if TEST
        return dQ, Qf, Qc
    else
        return dQ, Qf
    end
end

distributions = [Normal()]
estimator = Estimator(ML(), MC(), sample_SDE, distributions, folder=DIR)

# run Estimator
h = run(estimator, 5e-2)

S = MultilevelEstimators.samples_diff(estimator)
name = estimator[:name]
sf_dir= string(name[1:end-5])

isdir(joinpath(DIR,sf_dir)) || mkdir(joinpath(DIR,sf_dir))
isdir(joinpath(DIR,sf_dir,"Samples")) || mkdir(joinpath(DIR,sf_dir,"Samples"))

for idx in CartesianIndices(S)
    idx_dir = joinpath(DIR,sf_dir,"Samples", join(idx.I,"_"))
    isdir(idx_dir) || mkdir(idx_dir)
    for k in keys(S[idx])
        writedlm(joinpath(idx_dir,string("samples_level_",k[1]-1,".txt")),S[idx][k])
    end
end

full_history = History(h)

filePath = DIR*"/UntitledEstimator.jld2"
history = load(filePath,"history")
report(history, folder=DIR*"/testReport", include_preamble=true)


using StateSpaceModels, Plots

y = collect(1:0.25:20) + 0.5*randn(77)
y[10:20] .= NaN
model = linear_trend(y)
ss = statespace(model)

plot([y y], legend = :topleft, label = "observations", lw = 2, layout = (2, 1), xticks = 0:10:77, color = "black", grid = false)
plot!([ss.filter.a[:, 1] ss.smoother.alpha[:, 1]], label = ["filtered state" "smoothed state"],
        color = ["indianred" "green"], lw = 2)


input_string = open(f->read(f, String), "inputs/04.input")
input_lines = split(input_string, '\n', keepempty=false)
numbers = map(x -> parse(Int64, x), split(input_lines[1], ','))

function build_boards(n)
  ans = Vector{Matrix}()
  curr = Vector{Vector{Int64}}()
  curr_idx = 0
  for line in 2:length(input_lines)
    row = map(x -> parse(Int64, x), split(input_lines[line], r"\s+", keepempty=false))
    push!(curr, row)
    curr_idx = curr_idx + 1
    if curr_idx == n
      push!(ans, hcat(curr...))
      curr = Vector{Vector{Int64}}()
      curr_idx = 0
    end
  end
  ans
end

boards = build_boards(5)

function draw(board, number)
  map(row -> map(num -> if num == number 0 else num end, row), board)
end

function wins(board)
  row_sums = sum(board, dims=1)
  col_sums = sum(board, dims=2)
  length(filter(x -> x == 0, col_sums)) > 0 || length(filter(x -> x == 0, row_sums)) > 0
end

function sum_unmarked(board)
  sum(map(row -> sum(row), board))
end

function go()
  draw_idx = 1
  total_won = 0
  n_boards = length(boards)
  won = zeros(n_boards)
  won_first = 0
  won_last = 0
  while true
    draw_number = numbers[draw_idx]
    for i in 1:n_boards
      if won[i] == 1
        continue
      end
      boards[i] = draw(boards[i], draw_number)
      if wins(boards[i])
        won[i] = 1
        total_won = total_won + 1
        if total_won == 1
          won_first = draw_number * sum_unmarked(boards[i])
        elseif total_won == n_boards
          won_last = draw_number * sum_unmarked(boards[i])
          return (won_first, won_last)
        end
      end
    end
    draw_idx = draw_idx + 1
  end
end

(part1, part2) = go()

println("Part 1: " * string(part1))
println("Part 2: " * string(part2))


"""
    WaldA(;ν, k, A, θ)

Constructor for Wald distribution

# Fields

- `ν`: drift rate
- `k`: k = b - A where b is the decision threshold, and A is the maximum starting point
- `A`: the maximum starting point diffusion process, sampled from Uniform distribution
- `θ`: a encoding-motor time offset

## Usage

```julia
using SequentialSamplingModels
dist = WaldA(ν=.5, σ=1.0, ϕ=.3)
data = rand(dist, 10)
like = pdf.(dist, data)
loglike = logpdf.(dist, data)
```
# References

Tillman, G., Van Zandt, T., & Logan, G. D. (2020). Sequential sampling models without random between-trial variability:
The racing diffusion model of speeded decision making. Psychonomic Bulletin & Review, 27, 911-936.
"""
struct WaldA{T1,T2,T3,T4} <: ContinuousUnivariateDistribution
    ν::T1
    k::T2
    A::T3
    θ::T4
end

Broadcast.broadcastable(x::WaldA) = Ref(x)

WaldA(;ν, k, A, θ) = WaldA(ν, k, A, θ)

Φ(x) = cdf(Normal(0, 1), x)
ϕ(x) = pdf(Normal(0, 1), x)

function pdf(d::WaldA, rt::Float64)
    @unpack ν, k, A, θ = d
    t = rt - θ
    α = (k - t * ν) / √(t)
    β = (A + k - t * ν) / √(t)
    return (-ν * Φ(α) + 1 / √(t) * ϕ(α) +
        ν * Φ(β) - 1 / √(t) * ϕ(β)) / A
end

logpdf(d::WaldA, rt::Float64) = log(pdf(d, rt))

function cdf(d::WaldA, rt::Float64)
    @unpack ν, k, A, θ = d
    t = rt - θ
    b = A + k
    α1 = √(2) * ((ν * t - b)/√(2 * t))
    α2 = √(2) * ((ν * t - k)/√(2 * t))
    β1 = √(2) * ((-ν * t - b)/√(2 * t))
    β2 = √(2) * ((-ν * t - k)/√(2 * t))
    v1 = (1 / (2 * ν * A)) * (Φ(α2) - Φ(α1))
    v2 = (√(t) / A) * (α2 * Φ(α2) - α1 * Φ(α1))
    v3 = -(1 / (2 * ν * A)) * (exp(2 * ν * k) * Φ(β2) - exp(2 * ν * b) * Φ(β1))
    v4 = (√(t) / A) * (ϕ(α2) - ϕ(α1))
    return v1 + v2 + v3 + v4
end

logccdf(d::WaldA, rt::Float64) = log(1 - cdf(d, rt))

# function rand(d::WaldA, α)
#     @unpack ν, θ = d
#     return rand(InverseGaussian(α / ν, α^2)) + θ
# end

function rand(d::WaldA)
    @unpack ν, k, A, θ = d
    z = rand(Uniform(0, A))
    α = k + A - z
    return rand(InverseGaussian(α / ν, α^2)) + θ
end

"""
    DiffusionRace(;ν, k, A, θ)

An object for the racing diffusion model.

# Fields

- `ν`: a vector of drift rates
- `k`: k = b - A where b is the decision threshold, and A is the maximum starting point
- `A`: the maximum starting point diffusion process, sampled from Uniform distribution
- `θ`: a encoding-motor time offset

# Example

```julia
using SequentialSamplingModels
dist = DiffusionRace(;ν=[1,2], k=.3, A=.7, θ=.2)
data = rand(dist, 10)
like = pdf.(dist, data)
loglike = logpdf.(dist, data)
```
# References

Tillman, G., Van Zandt, T., & Logan, G. D. (2020). Sequential sampling models without random between-trial variability:
The racing diffusion model of speeded decision making. Psychonomic Bulletin & Review, 27, 911-936.
"""
struct DiffusionRace{T1,T2,T3,T4} <: SequentialSamplingModel
    ν::T1
    k::T2
    A::T3
    θ::T4
end

Broadcast.broadcastable(x::DiffusionRace) = Ref(x)

DiffusionRace(;ν, k, A, θ) = DiffusionRace(ν, k, A, θ)

function rand(dist::DiffusionRace)
    @unpack ν, A, k, θ = dist
    z = rand(Uniform(0, A))
    α = k + A - z
    x = @. rand(WaldA(ν, k, A, θ))
    rt,resp = findmin(x)
    return resp,rt
end

rand(dist::DiffusionRace, N::Int) = [rand(dist) for i in 1:N]

function logpdf(d::DiffusionRace, r::Int, rt::Float64)
    @unpack ν, k, A, θ = d
    LL = 0.0
    for (i,m) in enumerate(ν)
        if i == r
            LL += logpdf(WaldA(m, k, A, θ), rt)
        else
            LL += logccdf(WaldA(m, k, A, θ), rt)
        end
    end
    return LL
end

logpdf(d::DiffusionRace, data::Tuple) = logpdf(d, data...)

pdf(d::DiffusionRace, data::Tuple) = pdf(d, data...)

function pdf(d::DiffusionRace, r::Int, rt::Float64)
    @unpack ν, k, A, θ = d
    like = 1.0
    for (i,m) in enumerate(ν)
        if i == r
            like *= pdf(WaldA(m, k, A, θ), rt)
        else
            like *= (1 - cdf(WaldA(m, k, A, θ), rt))
        end
    end
    return like
end

# using Distributions, Parameters
# import Base.rand
# import Distributions: pdf, logpdf, cdf
# struct Race{T1,T2,T3} <: ContinuousUnivariateDistribution
#     ν::T1
#     α::T2
#     θ::T3
# end

# Broadcast.broadcastable(x::Race) = Ref(x)

# Race(;ν, α, θ) = Race(ν, α, θ)

# function rand(dist::Race)
#     @unpack ν, α, θ = dist
#     x = @. rand(Wald(ν, α, θ))
#     rt,resp = findmin(x)
#     return resp,rt
# end

# rand(dist::Race, N::Int) = [rand(dist) for i in 1:N]

# function logpdf(d::Race, r::Int, rt::Float64)
#     @unpack ν, α, θ = d
#     LL = 0.0
#     for (i,m) in enumerate(ν)
#         if i == r
#             LL += logpdf(Wald(;ν, α, θ), rt)
#         else
#             LL += logccdf(Wald(;ν, α, θ), rt)
#         end
#     end
#     return LL
# end

# logpdf(d::Race, data::Tuple) = logpdf(d, data...)

# pdf(d::Race, data::Tuple) = pdf(d, data...)

# function pdf(d::Race, r::Int, rt::Float64)
#     @unpack ν, α, θ = d
#     like = 1.0
#     for (i,m) in enumerate(ν)
#         if i == r
#             like *= pdf(Wald(;ν=m, α, θ), rt)
#         else
#             like *= (1 - cdf(Wald(;ν=m, α, θ), rt))
#         end
#     end
#     return like
# end


using Memoize

# Return the number of possibilities having at least 1 block
@memoize function solve(size)
  result = 0
  for head = 0:size-3
    for len = 3:size-head
      result += 1 + solve(size-head-len-1)
    end
  end

  return result
end

# Return the number of possibilities having at least 1 block + the case of no
# no block at all
function euler0114(size)
  1+solve(size)
end

println(string("7:  ", euler0114(7)))
println(string("50: ", euler0114(50)))


"""
    binary_search(sorted::AbstractVector{T}, value::T)::Int where {T<:Real}

Perfom a binary search to return the index i of an sorted array
such that sorted[i-1] < value <= sorted[i]

# Examples
```jldoctest
julia> MonteCarloX.binary_search([1.,2.,3.,4.],2.5)
3
```
```jldoctest
julia> MonteCarloX.binary_search([1,2,3,4],2)
2
```
"""
function binary_search(sorted::AbstractVector{T}, value::T)::Int where {T <: Real}
    # catch lower-bound case that cannot be reached by binary search
    id = 1
    if value > sorted[1]
        index_l = 1
        index_r = length(sorted)
        while index_l < index_r - 1
            # this should be fine because build for integers!
            index_m = fld(index_l + index_r, 2)
            if sorted[index_m] < value
                index_l = index_m
            else
                index_r = index_m
            end
        end
        id = index_r
    end
    return id
end

"""
    log_sum(a::T,b::T)

Return result of logarithmic sum ``c = \\ln(A+B) = a + \\ln(1+e^{|b-a|})`` where ``C =
e^c = A+B = e^a + e^b``.

This is useful for sums that involve elements that span multiple orders of
magnitude, e.g., the partition sum that is required as normalization factor
during reweighting.

# Examples
```jldoctest
julia> exp(MonteCarloX.log_sum(log(2.), log(3.)))
5.000000000000001

```

"""
function log_sum(a::T, b::T)::T where T <: AbstractFloat
    if b < a
        return a + log(1 + exp(b - a))
    else
        return b + log(1 + exp(a - b))
    end
end

"""
    random_element(list_probabilities::Vector{T},rng::AbstractRNG)::Int where T<:AbstractFloat

Pick an index with probability defined by `list_probability` (which needs to be normalized).

#Remark
Deprecated for use of StatsBase.sample

# Examples
```jldoctest
julia> using Random

julia> rng = MersenneTwister(1000);

julia> MonteCarloX.random_element([0.1,0.2,0.3,0.4],rng)
4
julia> MonteCarloX.random_element([0.1,0.2,0.3,0.4],rng)
4
julia> MonteCarloX.random_element([0.1,0.2,0.3,0.4],rng)
3
julia> MonteCarloX.random_element([0.1,0.2,0.3,0.4],rng)
4
```
"""
function random_element(list_probabilities::Vector{Float64}, rng::AbstractRNG)::Int
    theta = rand(rng) * sum(list_probabilities)

    id = 1
    cumulated_prob = list_probabilities[id]
    while cumulated_prob < theta
        id += 1
        @inbounds cumulated_prob += list_probabilities[id]
    end

    return id
end

# TODO: iterate over n-dimensional StatsBase.Histogram
# for start, CartesianIndices(hist.weights) can give you a way to iterate over
# weights, and the cartesian index is your bin, if you want to convert the bin
# number back to edge value, you just need to access the corresponding element
# in hist.edges

# does this need performance boost? - maybe this is faseter when explicit for 1D
Base.getindex(h::AbstractHistogram{T,1}, x::Real) where {T} = getindex(h, (x,))

function Base.getindex(h::Histogram{T,N}, xs::NTuple{N,Real}) where {T,N}
    idx = StatsBase.binindex(h, xs)
    if checkbounds(Bool, h.weights, idx...)
        return @inbounds h.weights[idx...]
    else
        return missing
    end
end

Base.setindex!(h::AbstractHistogram{T,1}, value::Real, x::Real) where {T} = setindex!(h, value, (x,))

function Base.setindex!(h::Histogram{T,N}, value::Real, xs::NTuple{N,Real}) where {T,N}
    h.isdensity && error("Density histogram must have float-type weights")
    idx = StatsBase.binindex(h, xs)
    if checkbounds(Bool, h.weights, idx...)
        @inbounds h.weights[idx...] = value
    else
        return missing
    end
end


module Day17

export get_inputs, get_solution1, get_solution2

using OffsetArrays

## Input getting
function get_inputs()
    test_input1 = test_input2 = read_input(IOBuffer(
        """
        .#.
        ..#
        ###
        """
    ))
    test_output1 = 112
    test_output2 = 848
    data = read_input(joinpath(@__DIR__, "input.txt"))
    return (; test_input1, test_input2, test_output1, test_output2, data)
end

function read_input(io)
    char_matrix = permutedims(reduce(hcat, collect.(readlines(io))), (2,1))
    active_matrix = char_matrix .== '#'
    return active_matrix
end

## Solution functions
# Allocate an OffsetArray for the space the active cells will grow into
function make_space(active_matrix, dims=3, steps=6)
    init_dim_size = (axes(active_matrix)..., ntuple(d->0, dims-2)...)
    dim_size = grow(init_dim_size, steps+1)

    # This will make an OffsetArray with indices that reach back into the negatives
    space = zeros(Bool, dim_size...)
    for ij in CartesianIndices(active_matrix)
        space[Tuple(ij)..., ntuple(d->0, dims-2)...] = active_matrix[ij]
    end
    return space
end

# Adjacent indices of (0,0,0,...)
adjacent_indices(dims) = (
    Tuple(ijk)
    for ijk in CartesianIndices(ntuple(i->-1:1, dims))
    if !all(Tuple(ijk) .== 0)
)
# Get the adjacent cells
get_adjacent(data, idx, adj_inds) = (data[(adj_idx .+ idx)...] for adj_idx in adj_inds)
get_adjacent(data, idx::CartesianIndex, adj_inds) = get_adjacent(data, idx.I, adj_inds)

# Step the simulation by iterating through the bounding_box axes
function step!(space, temp, bounding_box)
    adj_inds = adjacent_indices(ndims(space))
    for idx in CartesianIndices(bounding_box)
        count_adj = count(get_adjacent(space, idx, adj_inds))
        temp[idx] = if space[idx] == true
            count_adj==2 || count_adj==3
        else
            count_adj==3
        end
    end
    space .= temp
end

# Grow a bounding box of array axes
grow(box, n=1) = map(range -> first(range)-n:last(range)+n, box)

# Part 1
function get_solution1(data, dims=3, steps=6)
    space = make_space(data, dims, steps)
    temp = copy(space)
    bounding_box = grow((axes(data)..., ntuple(d->0, dims-2)...))
    for _ in 1:6
        step!(space, temp, bounding_box)
        bounding_box = grow(bounding_box)
    end
    return count(space)
end

# Part 2
get_solution2(data) = get_solution1(data, 4)

end


module FiniteStrains

using ..EquationsOfStateOfSolids: _⅔, _⅓, _1½

abstract type FiniteStrain end
struct EulerianStrain <: FiniteStrain end
struct LagrangianStrain <: FiniteStrain end
struct NaturalStrain <: FiniteStrain end
struct InfinitesimalStrain <: FiniteStrain end

"""
    ToEulerianStrain(v0)(v)
    ToLagrangianStrain(v0)(v)
    ToNaturalStrain(v0)(v)
    ToInfinitesimalStrain(v0)(v)

Calculate the finite strain of `v` based on the reference volume `v0`.

!!! info
    See the formulae on the [`Gibbs2` paper](https://www.sciencedirect.com/science/article/pii/S0010465511001470) Table 3.
"""
struct ToStrain{S<:FiniteStrain,T}
    v0::T
end
ToStrain{S}(v0::T) where {S,T} = ToStrain{S,T}(v0)
const ToEulerianStrain = ToStrain{EulerianStrain}
const ToLagrangianStrain = ToStrain{LagrangianStrain}
const ToNaturalStrain = ToStrain{NaturalStrain}
const ToInfinitesimalStrain = ToStrain{InfinitesimalStrain}
(x::ToEulerianStrain)(v) = ((x.v0 / v)^_⅔ - 1) / 2
(x::ToLagrangianStrain)(v) = ((v / x.v0)^_⅔ - 1) / 2
(x::ToNaturalStrain)(v) = log(v / x.v0) / 3
(x::ToInfinitesimalStrain)(v) = 1 - (x.v0 / v)^_⅓

"""
    FromEulerianStrain(v0)(f)
    FromLagrangianStrain(v0)(f)
    FromNaturalStrain(v0)(f)
    FromInfinitesimalStrain(v0)(f)

Calculate the original volume `v` from the finite strain `f` based on the reference volume `v0`.

!!! info
    See the formulae on the [`Gibbs2` paper](https://www.sciencedirect.com/science/article/pii/S0010465511001470) Table 3.
"""
struct FromStrain{S<:FiniteStrain,T}
    v0::T
end
FromStrain{S}(v0::T) where {S,T} = FromStrain{S,T}(v0)
const FromEulerianStrain = FromStrain{EulerianStrain}
const FromLagrangianStrain = FromStrain{LagrangianStrain}
const FromNaturalStrain = FromStrain{NaturalStrain}
const FromInfinitesimalStrain = FromStrain{InfinitesimalStrain}
# Eulerian strain
function (x::FromEulerianStrain)(f)
    v = x.v0 / (2f + 1)^_1½
    return isreal(v) ? real(v) : v
end
# Lagrangian strain
function (x::FromLagrangianStrain)(f)
    v = x.v0 * (2f + 1)^_1½
    return isreal(v) ? real(v) : v
end
# Natural strain
function (x::FromNaturalStrain)(f)
    v = x.v0 * exp(3f)
    return isreal(v) ? real(v) : v
end
# Infinitesimal strain
function (x::FromInfinitesimalStrain)(f)
    v = x.v0 / (1 - f)^3
    return isreal(v) ? real(v) : v
end

"""
    Dⁿᵥf(s::EulerianStrain, deg, v0)
    Dⁿᵥf(s::LagrangianStrain, deg, v0)
    Dⁿᵥf(s::NaturalStrain, deg, v0)
    Dⁿᵥf(s::InfinitesimalStrain, deg, v0)

Return a function of `v` that calculates the `deg`th order derivative of strain wrt volume from `v0`.

!!! info
    See the formulae on Ref. 1 Table 3.
"""
function Dⁿᵥf(s::EulerianStrain, deg, v0)
    function (v)
        if isone(deg)  # Stop recursion
            return -(v0 / v)^_⅔ / 3 / v
        else  # Recursion
            return -(3deg - 1) / 3 / v * Dⁿᵥf(s, deg - 1, v0)(v)
        end
    end
end
function Dⁿᵥf(s::LagrangianStrain, deg, v0)
    function (v)
        if isone(deg)  # Stop recursion
            return -(v / v0)^_⅔ / 3 / v
        else  # Recursion
            return -(3deg - 5) / 3 / v * Dⁿᵥf(s, deg - 1, v0)(v)
        end
    end
end
function Dⁿᵥf(s::NaturalStrain, deg, v0)
    function (v)
        if isone(deg)  # Stop recursion
            return 1 / 3 / v
        else  # Recursion
            return -(deg - 1) / v * Dⁿᵥf(s, deg - 1, v0)(v)
        end
    end
end
function Dⁿᵥf(s::InfinitesimalStrain, deg, v0)
    function (v)
        if isone(deg)  # Stop recursion
            return (1 - ToInfinitesimalStrain(v0)(v))^4 / 3 / v0
        else  # Recursion
            return -(3deg - 2) / 3 / v * Dⁿᵥf(s, deg - 1, v0)(v)
        end
    end
end

function straintype end

end


"
    Iterating over an AbstractGroup is the same as iterating over the set.
"
abstract type AbstractGroup end

# TODO: convert `Set` to `AbstractSet` where possible to support OrderedSets et al
"""
    Stucture consisting of a set and a binary operation. No constraints are put on either expression.
"""
struct Groupoid{T} <: AbstractGroup
    set::Set{T}
    operation::Function
end

"""
    Group.
    The following axioms must hold. Assuming a group G = (S, ∘), then
      - Closure: the group must be closed under the binary operation.
        ∀x,y ∈ S: x ∘ y ∈ S
      - Associativity:
        ∀x,y,z ∈ S: x ∘ (y ∘ z) =  (x ∘ y) ∘ z
      - Identity:
        ∃e ∈ S: ∀x ∈ S, x ∘ e = x = e ∘ x
      - Inverses:
        ∀x ∈ S, ∃ x⁻¹ ∈ S : x ∘ x⁻¹ = e = x⁻¹ ∘ x
"""
struct Group{T} <: AbstractGroup where {T}
    set::Set{T}
    operation::Function
    Group{T}(set, operation) where T = if _validate_group(Groupoid(set, operation)) new(set, operation) end
end
Group(set::Set{T}, operation::Function) where {T} = Group{T}(set, operation)

"""
    Subgroup of a group.
    Create using SubGroup(group, subset).
    Must be closed under the group operation, include the identity element and include inverses for each element.
"""
struct SubGroup{T} <: AbstractGroup where {T}
    group::Group{T}
    set::Set{T}
    operation::Function
end

function SubGroup(group::Group{T}, subset::Set{<:T})::SubGroup{T} where {T}
    @assert subset ⊆ group.set
    g = Groupoid(subset, group.operation)
    assert_closure(g)
    _assert_inverses(get_identity_element(g), g.set, g.operation)
    return SubGroup(group, subset, group.operation)
end

abstract type AbstractGroupAction end

struct _GroupActionLike{T} <: AbstractGroupAction where {T}
    group::Group{<:T}
    set::Set{<:T}
    action::Function
end

struct GroupAction{T} <: AbstractGroupAction where {T}
    group::Group{<:T}
    set::Set{<:T}
    action::Function
    #GroupAction{T}
end

# TODO: fix convert below, then simplify code as necessary
# import Base: convert
# convert(T::Set{S}, x::Array{S, 1}) where {S} = T(_ for _ in x)
# convert(Set, Array{Int, 1}([1]))
function coset(left::T, set::Set{T}, operation::Function)::Set{T} where {T}
    #Set(x for x in operation.(left, set))
    Set(operation(left, right) for right in set)
end

function coset(set::Set{T}, right::T, operation::Function)::Set{T} where {T}
#    Set(x for x in operation.(set, right))
    Set(operation(left, right) for left in set)

end

function coset(left, group::AbstractGroup)
    coset(left, group.set, group.operation)
end

function coset(group::AbstractGroup, right)
    coset(group.set, right, group.operation)
end

function set_composition(left_set::Set{<:T}, right_set::Set{<:T}, operation::Function)::Set  where {T}
    # `map` is not defined on sets
    # reduce(union, coset(left, right_set, operation) for left in left_set)
    Set(operation(left, right) for left in left_set for right in right_set)
end

function assert_closure(set::Set, closed_in_set::Set, operation::Function)::Nothing
    # We only check left closure.
    @assert set_composition(set, closed_in_set, operation) ⊆ closed_in_set
end

function assert_closure(group::AbstractGroup)::Nothing
    assert_closure(group.set, group.set, group.operation)
end

function assert_closure(ga::AbstractGroupAction)::Nothing
    assert_closure(ga.group.set, ga.set, ga.action)
end

function assert_associativity(set::Set, operation::Function)::Nothing
    for x in set
        for y in set
            for z in set
                a = operation(x, operation(y, z))
                b = operation(operation(x, y), z)
                @assert a == b "$x ∘ ($y ∘ $z) = $a ≠ $b = ($x ∘ $y) ∘ $z"
            end
        end
    end
end

function assert_associativity(group::AbstractGroup)::Nothing
    assert_associativity(group.set, group.operation)
end
function _assert_identity_element(element::T, set::Set{T}, operation::Function)::Nothing where {T}
    # we assume broadcasting works on the operation
    # @assert operation.(element, set) == set  # should be using ordered sets or vectors here
    for x in set
         @assert x == operation(element, x) == operation(x, element)
    end
end

function get_identity_element(group::AbstractGroup) # TODO: return type
    for x in group.set
        try
            _assert_identity_element(x, group.set, group.operation)
            return x
        catch AssertionError
            continue
        end
    end
    throw(AssertionError("No identity element found"))
end

function _assert_inverses(identity_element::T, set::Set{T}, operation::Function)::Nothing where {T}
    for item in set
        @assert identity_element in coset(item, set, operation)
        @assert identity_element in coset(set, item, operation)
    end
end

function _validate_group(group::AbstractGroup)::Bool
    # Validation
    assert_closure(group)
    assert_associativity(group)
    e = get_identity_element(group)  # This raises on failure
    _assert_inverses(e, group.set, group.operation)
    return true
end

function naive_isequal(x::T, y::T) where T
    for f in fieldnames(T)
        if getfield(x, f) != getfield(y, f)
            return false
        end
    end
    return true
end

import Base: ==
==(x::T, y::T) where T<:AbstractGroup = naive_isequal(x, y)
# ==(x::Group, y::Group) = naive_isequal(x, y)
# ==(x::SubGroup, y::SubGroup) = naive_isequal(x, y)
function ==(x::AbstractGroup, y::SubGroup)::Bool
    if y.set == x.set && x.operation == y.operation
        return true
    end
    return false
end
==(x::SubGroup, y::AbstractGroup) = ==(y, x)
# Prevent `MethodError: ==(::SubGroup{Int64}, ::SubGroup{Int64}) is ambiguous`
==(x::SubGroup, y::SubGroup) = naive_isequal(x, y)

import Base: iterate
iterate(group::AbstractGroup) = iterate(group.set)
iterate(group::AbstractGroup, t::T) where {T} = iterate(group.set, t)
import Base: length
length(x::AbstractGroup) = length(x.set)

"""
    N is a _normal_ subgroup of G if ∀g ∈ G, gN = Ng
    N is a normal subgroup of G iff N is a subgroup of G and N is a union of conjugacy classes of G
"""
function isnormal(subgroup::SubGroup)
    for g in subgroup.group
        left_coset = coset(g, subgroup)
        right_coset = coset(subgroup, g)
        if coset(g, subgroup) != coset(subgroup, g)
            return false
        end
    end
    return true
end

function iscyclic(group::AbstractGroup)
    if length(find_generators(group)) > 0
        return true
    end
    return false
end

function quotient_group(subgroup::SubGroup)
    @assert isnormal(subgroup) "Subgroup must be normal"
    # The set here is equal to the "partition" of the group into cosets created by group elements with the subgroup
    f(x::Set, y::Set) = set_composition(x, y, subgroup.operation)
    return Group(Set(coset(g, subgroup) for g in subgroup.group), f)
end

function generate_subgroup(group::AbstractGroup, generator)::SubGroup
    e = get_identity_element(group)
    generated_set = Set([e generator])
    p = group.operation(generator, generator)
    while p != e
        push!(generated_set, p)
        p = group.operation(p, generator)
    end
    SubGroup(group, generated_set, group.operation)
end

function find_generators(group::AbstractGroup)::Set
    filter(g->generate_subgroup(group, g) == group, group.set)
end

function _cayley_table(group::AbstractGroup) # TODO: move to utils
    elements = [x for x in group.set]
    inside = map(x->
            map(y->group.operation(x, y),
            elements
            ), elements)

    return elements, inside
end
# _cayley_table((generate_subgroup(G, 6) |> quotient_group))[2]

"""

Conjugate x by g, i.e. perform gxg⁻¹

"""
function conjugate(group::AbstractGroup, x::T, g::T) where {T}
    group.operation(group.operation(g, x), inv(group, g))
end

"""
Return { gxg⁻¹: ∀g ∈ G}
"""
function conjugacy_class(group::AbstractGroup, x)::Set
    Set(conjugate(group, x, g) for g in group)
end

"""
Return the distinct conjugacy classes in `group`.
The set of distinct conjugacy classes forms a partition of the group.
"""
function conjugacy_classes(group::AbstractGroup)::Set
    Set(conjugacy_class(group, x) for x in group)
end

"""
Obtain the inverse of `x` in `group`.
"""
function inv(group::AbstractGroup, x)
    e = get_identity_element(group)
    for h in group
        if group.operation(x, h) == group.operation(h, x) == e
            return h
        end
    end
    return missing  # TODO: missing or nothing?
end

abstract type AbstractHomomorphism end
"""
An isomorphism ϕ: (G, ∘) → (H, ⋆) is a mapping which satisfies the following properties
- ϕ is one-to-one and onto

See also: Homomorphism
"""
struct Isomorphism <: AbstractHomomorphism
    from_group::AbstractGroup
    to_group::AbstractGroup
    mapping::Dict
end

"""
An isomorphism ϕ: (G, ∘) → (H, ⋆) is a mapping which satisfies the following property:
    ∀x,y ∈ G, ϕ(x ∘ y) = ϕ(x) ⋆ ϕ(y)   (i.e. it preserves composites)
"""
struct Homomorphism <: AbstractHomomorphism
    from_group::AbstractGroup
    to_group::AbstractGroup
    mapping::Dict
end

function order(group::AbstractGroup)
    return length(group.set)
end

function order(group::AbstractGroup, x)
    return length(generate_subgroup(group, x))
end

function get_isomorphism(a::AbstractGroup, b::AbstractGroup)
    if a == b  # Automorphism
         return Dict(k=>k for k in a)
    end
    if iscyclic(a) && iscyclic(b) && order(a) == order(b)  # Cyclic subgroups of the same order
        gen_a, gen_b = find_generators(a) |> first, find_generators(b) |> first
        mapping = Dict(gen_a => gen_b)
        p_a = gen_a
        p_b = gen_b
        for i in 1:order(a)
            p_a = a.operation(p_a, gen_a)
            p_b = b.operation(p_b, gen_b)
            mapping[p_a] = p_b
        end
        return mapping
    end
    throw("Not implemented")
end

"""
Assert the following property, where mapping=ϕ, from=(G, ∘), to=(H, ⋆)
    ∀ x,y ∈ G; ϕ(x, y) = ϕ(x) ⋆ ϕ(y)
"""
function _assert_homomorphism_property(mapping::Dict, from::AbstractGroup, to::AbstractGroup)
   for x in from
        for y in from
            @assert mapping[from.operation(x, y)] == to.operation(mapping[x], mapping[y])
        end
    end
end

"""
Let (G, ∘) be a group, X be a set and ^ be a group action.
    ∀ g,h ∈ G; ∀x in X; g ^ (h ^ x) = (g ∘ h) ^ x
"""
function _assert_homomorphism_property(ga::AbstractGroupAction)
    for g in ga.group
        for h in ga.group
            for x in ga.set
                @assert ga.action(g, ga.action(h, x)) == ga.action(ga.group.operation(g, h), x)
            end
        end
    end
end

function _transform(ϕ::Function, arr::Union{Vector, AbstractSet})::Union{Vector, AbstractSet}
    return ϕ.(arr)
end

function _transform(ϕ::Dict, arr::Union{Vector, AbstractSet})::Union{Vector, AbstractSet}
    return _transform(x-> ϕ[x], arr)
end

"""
Let G be a group and ϕ: G → H be a homomorphism. Then,
    Ker ϕ = {g ∈ G: ϕ(g) = e_H}

Let V, W be vector subspaces and t: V → W be a linear transfomation. Then,
    Ker t = {v⃗ ∈ V: ϕ(v⃗) = 0⃗}
"""
function kernel(transformation::Union{Function, Dict}, from::Union{AbstractSet, AbstractGroup}, identity)
    v = [g for g in from]
    t = _transform(transformation, v)
    mask = t .== identity
    return Set(v[mask])
end

function image(transformation, from::AbstractSet)::Set
   _transform(transformation, from) |> Set
end
function image(transformation, from::AbstractGroup)::Set
   _transform(transformation, from.set) |> Set
end

function GroupAction(group::Group{<:T}, set::Set{<:T}, action::Function)::GroupAction{T} where {T}
    ga = _GroupActionLike(group, set, action)
    assert_closure(ga)

    e = get_identity_element(ga.group)
    for x in ga.set
        @assert ga.action(e, x) == x
    end

    _assert_homomorphism_property(ga)

    return GroupAction{T}(group, set, action)
end

"""
    Orb x = {∀g ∈ G, g ^ x}
"""
function orbit(ga::AbstractGroupAction, x)::Set  # TODO: define type
    @assert x in ga.set
    return ga.action.(ga.group, x) |> Set
end

"""
    Get the set of all orbits for the group action
"""
function orbits(ga::AbstractGroupAction)::Set
    return Set(orbit(ga, x) for x in ga.set)
end

"""
    Stab x = {g ∈ G: g^x = x}
"""
function stabilizer(ga::AbstractGroupAction, x)
    @assert x in ga.set
    v = [g for g in ga.group.set]
    t = ga.action.(v, x)
    stable_mask = t .== x
    return Set(v[stable_mask])
end

# function stabilizers(ga::AbstractGroupAction)
#     return Set(stabilizer(ga, x) for x in ga.set)
# end

"""
    Fix g = {x ∈ X: g^x = x}
"""
function fixed_set(ga::AbstractGroupAction, g)
    @assert g in ga.group.set
    v = [x for x in ga.set]
    t = ga.action.(g, v)
    return Set(v[t .== v])
end


rg(M) = M.*G./C./C

function dudθ(θ, tgα, R)
    cθ = cos(θ)
    sθ = sin(θ)
    (cθ+sθ.*tgα)./R./(sθ-cθ.*tgα)
end

function dduddθ(θ, tgα, R)
    cθ = cos(θ)
    sθ = sin(θ)

#        V                 W
#    ------------      -----------
#    (cθ+sθ.*tgα)./R./(sθ-cθ.*tgα)

    v = cθ+sθ.*tgα
    w = sθ-cθ.*tgα

    dv = -sθ+cθ.*tgα
    dw =  cθ-sθ.*tgα

#   1/R*{  V*W^(-1) }'
    1/R*( dv/w-v/w/w*dw)
end

function ray_angle(θ, r, dudθ)
    cθ = cos(θ)
    sθ = sin(θ)
    -atan((-r.*dudθ.*sθ+cθ)./(-r.*dudθ.*cθ-sθ))
end

function mylinspace(i,f,N)
    l = linspace(0,1,div(N,2)).^0.001
    ll = π/2-i
    lr = f-π/2
    [(1-reverse(l)).*ll+i; l.*lr+π/2]
end

function extrapolate_to_meeting_same_side(x1,y1,x2,y2)
    s1 = Spline1D(x1,y1, k=1, bc="error")
    s2 = Spline1D(x2,y2, k=1, bc="error")
    X = fzero(x->evaluate(s1,x)-evaluate(s2,x), 0.0, min(x1[end],x2[end]))
    Y = evaluate(s1,X)

    xx1 = x1[x1.<X]
    yy1 = y1[x1.<X]
    xx2 = x2[x2.<X]
    yy2 = y2[x2.<X]
    push!(xx1, X)
    push!(xx2, X)

    push!(yy1, Y)
    push!(yy2, Y)

    xx1,yy1, xx2,yy2
end

function extrapolate_to_x2!(x1,y1,x2)
    ixs = find(x1.<x2)
    x1 = x1[ixs]
    y1 = y1[ixs]

    dx1 = x1[end]-x1[end-10]
    dy1 = y1[end]-y1[end-10]
    tg1 = dy1/dx1

    DX1 = x2-x1[end]
    DY1 = DX1*tg1

    X = x2
    Y = y1[end] + DY1

    push!(x1, X)
    push!(y1, Y)

    x1, y1
end

function to_small(v)
    Float64[float(vi) for vi in v]
end


"""
f(x,y) = x+y
"""
function model_1(n_train = 100, n_test = 30)
    n_train = 100
    xtrain=rand(2,n_train)
    ytrain=sum(xtrain, dims=1)

    xtest=10rand(2,n_test)
    ytest=sum(xtest, dims=1)

    return (xtrain=xtrain, ytrain=ytrain, xtest=xtest, ytest=ytest)
end


using SparseArrays
using LinearAlgebra

@testset "matrixnetwork" begin
    A = load_matrix_network("dfs_example")
    M = MatrixNetwork(A)
    B = sparse(M)
    @test A == B
    C = sparse_transpose(M)
    @test A' == C
    @test size(M) == size(B)
    @test_throws DomainError size(M, 0)
    @test size(M, 4) == 1
    @test ndims(M) == 2

    load_matrix_network_all("minnesota")
    load_matrix_network_all("U3A")

    #@show issym(sparse([0 1; 0 0]'))
    @test is_undirected(MatrixNetwork(sparse([0 1; 1 0]))) == true
    @test is_undirected(MatrixNetwork(sparse([0 1; 0 1]))) == false
    @test is_undirected(MatrixNetwork(load_matrix_network("dfs_example"))) == false
    @test is_undirected(sparse([0 1; 1 0])) == true

    @test is_connected(havel_hakimi_graph([1,1,1,1])) == false
    @test is_connected(havel_hakimi_graph([2,2,2])) == true
    @test is_connected(sparse([0 1 0; 0 0 1; 1 0 0])) == true
    @test is_connected(spzeros(1,1)) == true
    @test is_connected(empty_graph(0)) == false
    @test is_connected(empty_graph(1)) == true
    @test is_connected(empty_graph(2)) == false

    @test is_empty(empty_graph(0)) == true
    @test is_empty(MatrixNetwork(Int[],Int[],0)) == true
    @test is_empty(MatrixNetwork(Int[1],Int[1])) == false
    @test is_empty(erdos_renyi_undirected(0,0)) == true
    @test is_empty(erdos_renyi_undirected(1,0)) == false

    @testset "matvec" begin
        G = erdos_renyi_directed(5,2)
        y = collect(1:5)
        A = sparse(G)
        @test norm(A*y - G*y,2) <= 10*eps(1.0)
        @test norm(G'*y - A'*y,2) <= 10*eps(1.0)
    end
end


using Base.Test
using AGRN

config = AGRN.Config()

@testset "Evolution functions" begin
    @testset "tournament" begin
        @test AGRN.tournament([rand()]) == 1
        for i in 2:10
            fits = rand(i)
            winner = AGRN.tournament(fits)
            @test winner >= 1
            @test winner <= i
            @test fits[winner] > minimum(fits)
        end
    end
    @testset "speciation" begin
        distance(a::Float64, b::Float64, config::AGRN.Config) = abs(a-b)
        population = rand(rand(5:15))
        reprs = rand(rand(2:5))
        species = AGRN.speciation(population, reprs, config; dfunc=distance)
        @test length(species) == length(population)
        @test all(species .<= length(reprs))
    end
    @testset "species sizes" begin
        fits = rand(config.ga_population)
        species = rand(1:5, config.ga_population)
        spec_sizes = AGRN.species_sizes(fits, species, config)
        @test length(spec_sizes) == 5
        @test sum(spec_sizes) == config.ga_population
    end
end

@testset "Evolutionary runs" begin
    @testset "Length fit" begin
        function length_fit(ind::GRN)
            length(ind)
        end
        fitness = Fitness(1, 1, length_fit)
        config = AGRN.Config()
        max_fit, best = evolve(fitness, config)
        @test max_fit > (1.0 + 1.0 + 1.0)
        @test length(best) == max_fit
    end
    @testset "sin fit" begin
        function sin_fit(ind::GRN)
            inputs = (sin.(0.0:0.1:5.0) + 1.0) / 2.0
            outs = zeros(length(inputs))
            for i in eachindex(inputs)
                outs[i] = step!(ind, [inputs[i]])[1]
            end
            -sqrt(sum((inputs - outs).^2)/length(inputs))
        end
        fitness = Fitness(1, 1, sin_fit)
        config = AGRN.Config()
        max_fit, best = evolve(fitness, config)
        @test max_fit < 0.0
        rand_grn = GRN(1, 1, 1, config)
        @test sin_fit(best) >= sin_fit(rand_grn)
    end
end


include("../src/autodiff_module.jl")

module dtest
using ..autodiff
using PyPlot
PyPlot.pygui(true)

fcn0_1(x) = 5 * x + 10.0

fcn1(x) = begin
    c1 = x^3
    c2 = x^2
    c3 = x
    t1 = 1/3 * c1
    return t1 + 1/2 * c2 + 5 * c3 + 10.0
end
fcn2(x) = sin(x) * exp(x) + x^5
fcn3(x) = sin(x)

mode = autodiff.ReverseMode()

# x0 = 2.1 # 5.0 #-1.5 # 4.6 # 3.2
# der_f = autodiff.derivative(autodiff.ForwardMode(), fcn1, x0)
# der_r = autodiff.derivative(autodiff.ReverseMode(), fcn1, x0)

x0 = collect(-3.0:0.1:5)
L = length(x0)
der_f = Vector{Float64}(undef, L)
der_r = Vector{Float64}(undef, L)
delta_der = Vector{Float64}(undef, L)

# der_f = zeros(Float64, L)
# der_r = zeros(Float64, L)
# delta_der = zeros(Float64, L)

for ii = 1:L
    der_r[ii] = autodiff.derivative(autodiff.ReverseMode(), fcn3, x0[ii])
end

for ii = 1:L
    der_f[ii] = autodiff.derivative(autodiff.ForwardMode(), fcn3, x0[ii])
end

delta_der = der_f - der_r

PyPlot.figure()
PyPlot.plot(x0, der_f, marker = :+)
PyPlot.plot(x0, der_r, marker = :o)

PyPlot.figure()
PyPlot.plot(x0, delta_der)

end


using ApproxFun, Test
import ApproxFun: expα, expβ, expγ

#
# This tests the numerical stability of the formulæ appearing in Eq. (2.5) of:
#
# A.-K. Kassam and L. N. Trefethen, Fourth-order time-stepping for stiff PDEs, SIAM J. Sci. Comput., 26:1214--1233, 2005.
#

@testset "ETDRK4" begin
    x = 10 .^ range(-15, stop=2, length=18)

    @test norm(expα.(x)./expα.(big.(x)).-1,Inf) < eps()
    @test norm(expβ.(x)./expβ.(big.(x)).-1,Inf) < eps()
    @test norm(expγ.(x)./expγ.(big.(x)).-1,Inf) < eps()
end


using Yeppp
using Test
import LinearAlgebra

for T in (Float32, Float64)
    x::Vector{T} = rand(1000)
    y::Vector{T} = rand(1000)
    z::Vector{T} = randn(1000)
    res::Vector{T} = rand(1000)

    @test Yeppp.add!(res, x, y) == x .+ y
    @test Yeppp.multiply!(res, x, y) == x .* y
    @test Yeppp.subtract!(res, x, y) == x .- y
    @test Yeppp.multiply(x, y) == x .* y
    @test Yeppp.subtract(x, y) == x .- y
    @test Yeppp.min!(res, x, y) == min.(x, y)
    @test Yeppp.max!(res, x, y) == max.(x, y)
    @test Yeppp.min(x, y) == min.(x, y)
    @test Yeppp.max(x, y) == max.(x, y)

    @test Yeppp.sum(x) ≈ sum(x)
    @test Yeppp.sumabs2(x) ≈ sum(abs2, x)
    @test Yeppp.dot(x, y) ≈ LinearAlgebra.dot(x, y)

end

x = rand(1000)
y = rand(1000)
z = randn(1000)
res = rand(1000)

@test Yeppp.exp!(res, x) ≈ exp.(x)
@test Yeppp.log!(res, x) ≈ log.(x)
@test Yeppp.sin!(res, x) ≈ sin.(x)
@test Yeppp.cos!(res, x) ≈ cos.(x)
@test Yeppp.tan!(res, x) ≈ tan.(x)

@test Yeppp.sum(x) ≈ sum(x)
@test Yeppp.sumabs(x) ≈ sum(abs, x)
@test Yeppp.sumabs2(x) ≈ sum(abs2, x)


Base.@kwdef struct LucasKanade
    iterations::Int64 = 30
    window_size::Int64 = 9
    pyramid_levels::Int64 = 3
    eigenvalue_threshold::Float64 = 1e-4
    ϵ::Float64 = 1e-2
end

function optflow!(
    displacement, first_pyramid, second_pyramid, points, algorithm::LucasKanade,
)
    has_enough_layers =
        length(first_pyramid.layers) > algorithm.pyramid_levels &&
        length(second_pyramid.layers) > algorithm.pyramid_levels
    has_enough_layers || throw("Not enough layers in pyramids.")

    n_points = length(points)
    status = trues(n_points)
    n_good = n_points

    window = algorithm.window_size
    mode = BSpline(Linear())

    for level in (algorithm.pyramid_levels + 1):-1:1
        level_resolution = axes(first_pyramid.layers[level])
        # Interpolate layer to get sub-pixel precision.
        # We never go out-of-bound, so there is no need to extrapolate.
        # We can safely use `interpolate!` function (thus saving some memory), because
        # when doing linear interpolation, there is no modification to the input array.
        # See: https://github.com/JuliaMath/Interpolations.jl/blob/master/src/b-splines/prefiltering.jl#L33
        interploated_layer = interpolate!(@inbounds(second_pyramid.layers[level]), mode)

        Threads.@threads for n in 1:n_points
            @inbounds status[n] || continue

            point = get_pyramid_coordinate(@inbounds(points[n]), level)
            offsets = get_offsets(point, point, window, level_resolution)
            grid = get_grid(point, offsets)

            G_inv, min_eigenvalue = compute_spatial_gradient(first_pyramid, grid, level)
            if min_eigenvalue < algorithm.eigenvalue_threshold
                @inbounds status[n] = false
                n_good -= 1
                continue
            end

            pyramid_contribution = SVector{2, Float64}(0.0, 0.0)
            for _ in 1:algorithm.iterations
                putative_flow = @inbounds displacement[n] + pyramid_contribution
                putative_correspondence = point + putative_flow
                if !lies_in(level_resolution, putative_correspondence)
                    @inbounds status[n] = false
                    n_good -= 1
                    break
                end

                new_offsets = get_offsets(
                    point, putative_correspondence, window, level_resolution)
                # Recalculate gradient only if the offset changes.
                if new_offsets != offsets
                    offsets = new_offsets
                    grid = get_grid(point, offsets)
                    G_inv, min_eigenvalue = compute_spatial_gradient(
                        first_pyramid, grid, level)
                    if min_eigenvalue < algorithm.eigenvalue_threshold
                        @inbounds status[n] = false
                        n_good -= 1
                        break
                    end
                else
                    offsets = new_offsets
                    grid = get_grid(point, offsets)
                end

                estimated_flow = compute_flow_vector(
                    putative_correspondence,
                    first_pyramid, interploated_layer, level,
                    grid, offsets, G_inv)

                # Epsilon termination criteria.
                abs(estimated_flow[1]) < algorithm.ϵ &&
                    abs(estimated_flow[2]) < algorithm.ϵ && break

                pyramid_contribution += estimated_flow
                in_bounds = lies_in(
                    level_resolution, putative_correspondence + estimated_flow)
                if !in_bounds
                    @inbounds status[n] = false
                    n_good -= 1
                    break
                end
            end
            @inbounds status[n] || continue
            @inbounds displacement[n] += pyramid_contribution
            level > 1 && (@inbounds displacement[n] *= 2.0;)
        end
    end

    displacement, status, n_good
end

function compute_partial_derivatives(Iy, Ix; kwargs...)
    Iyy = typeof(Iy)(undef, size(Iy))
    Ixx = typeof(Iy)(undef, size(Iy))
    Iyx = typeof(Iy)(undef, size(Iy))
    compute_partial_derivatives!(Iyy, Ixx, Iyx, Iy, Ix; kwargs...)
end

function compute_partial_derivatives!(
    Iyy, Ixx, Iyx, Iy, Ix;
    squared = typeof(Iy)(undef, size(Iy)),
    filtered = typeof(Iy)(undef, size(Iy)), σ = 4.0,
)
    kernel_factors = get_kernel(σ, 2)

    squared .= Iy .* Iy
    imfilter!(filtered, squared, kernel_factors)
    integral_image!(Iyy, filtered)

    squared .= Ix .* Ix
    imfilter!(filtered, squared, kernel_factors)
    integral_image!(Ixx, filtered)

    squared .= Iy .* Ix
    imfilter!(filtered, squared, kernel_factors)
    integral_image!(Iyx, filtered)

    Iyy, Ixx, Iyx
end

function integral_image!(integral, img)
    sd = coords_spatial(img)
    cumsum!(integral, img; dims=sd[1])
    for i ∈ 2:length(sd)
        cumsum!(integral, integral; dims=sd[i])
    end
    integral
end

function _compute_spatial_gradient(
    grid, Iyy_integral, Iyx_integral, Ixx_integral,
)
    sum_Iyy = boxdiff(Iyy_integral, grid[1], grid[2])
    sum_Ixx = boxdiff(Ixx_integral, grid[1], grid[2])
    sum_Iyx = boxdiff(Iyx_integral, grid[1], grid[2])
    SMatrix{2, 2, Float64}(sum_Iyy, sum_Iyx, sum_Iyx, sum_Ixx)
end

function compute_spatial_gradient(pyramid::LKPyramid, grid, level)
    G = _compute_spatial_gradient(
        grid, pyramid.Iyy[level], pyramid.Iyx[level], pyramid.Ixx[level])
    U, S, V = svd2x2(G)
    G_inv = pinv2x2(U, S, V)
    min_eigenvalue = min(S[1, 1], S[2, 2]) / prod(length.(grid))

    G_inv, min_eigenvalue
end

function prepare_linear_system(corresponding_point, A, Iy, Ix, offsets, B)
    P, Q = size(A)
    by, bx = 0.0, 0.0

    @inbounds for q in 1:Q, p in 1:P
        r = corresponding_point[1] + offsets[1][p]
        c = corresponding_point[2] + offsets[2][q]

        δI = A[p, q] - B(r, c)
        by += δI * Iy[p, q]
        bx += δI * Ix[p, q]
    end

    SVector{2, Float64}(by, bx)
end

function compute_flow_vector(
    corresponding_point,
    first_pyramid::LKPyramid, etp, level,
    grid, offsets, G_inv,
)
    b = prepare_linear_system(
        corresponding_point,
        view(first_pyramid.layers[level], grid[1], grid[2]),
        view(first_pyramid.Iy[level], grid[1], grid[2]),
        view(first_pyramid.Ix[level], grid[1], grid[2]),
        offsets, etp)
    G_inv * b
end

@inline lies_in(area, point) = @inbounds(
    first(area[1]) ≤ point[1] ≤ last(area[1]) &&
    first(area[2]) ≤ point[2] ≤ last(area[2]))

"""
# Arguments
- `level`: Level of the pyramid in `[1, levels]` range.
"""
@inline get_pyramid_coordinate(point, level) = floor.(Int, point ./ 2 ^ (level - 1))

function get_offsets(point, new_point, window, image_axes)
    rows, cols = image_axes

    up = floor(Int, min(window, min(point[1], new_point[1]) - first(rows)))
    down = floor(Int, min(window, last(rows) - max(point[1], new_point[1])))
    left = floor(Int, min(window, min(point[2], new_point[2]) - first(cols)))
    right = floor(Int, min(window, last(cols) - max(point[2], new_point[2])))

    (-up:down, -left:right)
end

@inline get_grid(point, offsets) = @inbounds (
    (point[1] + offsets[1][begin]):(point[1] + offsets[1][end]),
    (point[2] + offsets[2][begin]):(point[2] + offsets[2][end]))


using OpenQuantumBase, Test
import SparseArrays:spzeros

A = (s)->(1 - s)
B = (s)->s
u = [1.0 + 0.0im, 1] / sqrt(2)
ρ = u * u'

H_sparse = SparseHamiltonian([A, B], [spσx, spσz])

H_real = convert(Real, H_sparse)
@test eltype(H_real) <: Real
@test H_sparse(0.0) ≈ H_real(0.0)

@test size(H_sparse) == (2,2)
@test issparse(H_sparse)
@test H_sparse(0) ≈ 2π * spσx
@test evaluate(H_sparse, 0) == spσx
@test H_sparse(0.5) ≈ π * (spσx + spσz)
@test evaluate(H_sparse, 0.5) == (spσx + spσz)/2
@test get_cache(H_sparse) ≈ π*(spσx + spσz)

du = [1.0 + 0.0im 0; 0 0]
H_sparse(du, ρ, 1.0, 0.5)
@test du ≈ -1.0im * π * ((σx + σz) * ρ - ρ * (σx + σz))

# update_cache method
C = similar(spσz)
update_cache!(C, H_sparse, 10, 0.5)
@test C == -1im * π * (spσx + spσz)

# update_vectorized_cache method
C = C⊗C
update_vectorized_cache!(C, H_sparse, 10, 0.5)
temp = -1im * π * (spσx + spσz)
@test C == spσi⊗temp - transpose(temp)⊗spσi

H_sparse = SparseHamiltonian([A, B], real.([spσx ⊗ spσi + spσi ⊗ spσx, 0.1spσz ⊗ spσi - spσz ⊗ spσz]))
w, v = eigen_decomp(H_sparse, 1.0)
vf = [0, 0, 0, 1.0]

@test w ≈ [-1.1, -0.9]
@test abs(v[end, 1]) ≈ 1
@test abs(v[1, 2]) ≈ 1


export Soln, SolvData, ExData

##
# Types for Examples
##

type ExData
  a::Function
  b::Function
  c::Function

  f::Function
  chi::Function
  g::Function
  phi::Function

  s::Function

  u::Function
  ux::Function
  ut::Function
  sp::Function

  γ::Function

  function ExData(a::Function, b::Function, c::Function, s::Function, u::Function, ux::Function, ut::Function, sp::Function, γf::Function)

    aux{T<:Real}(x::T, t::T)=a(x,t)*ux(x,t)
    auxx{T<:Real}(x::T, t::T)=(aux(x+gPDEDiffEst,t)-aux(x,t))/gPDEDiffEst

    f{T<:Real}(x::T, t::T)=-(auxx(x,t)+b(x,t)*ux(x,t)+c(x,t)*u(x,t)-ut(x,t))

    g{T<:Real}(t::T)=aux(zero(T), t)

    phi{T<:Real}(x::T)=u(x, zero(T))

    chi{T<:Real}(x::T, t::T)=a(x, t)*ux(x, t)+γf(x, t)*sp(t)

    return new(a,b,c,f,chi,g,phi,s,u,ux,ut,sp,γf)
  end
end

##
# Types for PDE Solver Routine
##
@lintpragma("Info me PDEtypes: Consider removing dx information from SolnA.")
type SolnA{T<:Real, T1<:Real}
  xgrid::Vector{T}
  tgrid::Vector{T}
  dx::Vector{T} # TODO: Consider removing dx information
  tau::T
  xbdy::Vector{T}
  soln::Matrix{T1}
end

SolnA(nSlice::Int, s::Function)=SolnA(linspace(0,1), linspace(0,1), linspace(0,1), 0.1, linspace(0,1), [one(Float) for x in linspace(0,1), t in linspace(0,1)])
typealias Soln SolnA

type SolvData
  verbose::Bool
  a::Function
  b::Function
  c::Function
  f::Function
  rhs::Function
  g::Function
  phi::Function
  s::Function
  grid::SolnA
  SolvData(verbose::Bool, a::Function, b::Function, c::Function, f::Function, rhs::Function, g::Function, phi::Function, s::Function, nSlice::Int) = new(verbose, a, b, c, f, rhs, g, phi, s, SolnA(nSlice, s))
end
SolvData(a::Function, b::Function, c::Function, f::Function, rhs::Function, g::Function, phi::Function, s::Function, nSlice::Int) = SolvData(gSetVerbose, a, b, c, f, rhs, g, phi, s, nSlice)

function SolvData(exin::ExData, nSlice::Int)
  rhs{T<:Real}(t::T) = exin.chi(exin.s(t), t) - exin.γ(exin.s(t), t) * exin.sp(t)
  return SolvData(gSetVerbose, exin.a, exin.b, exin.c, exin.f, rhs, exin.g, exin.phi, exin.s, nSlice)
end

# include("PDESolvDataHelpers.jl")