struct binned1dDensity
    grid::Array{Float64,1}
    cumarr::Array{Float64}
    density
end

function getbinned1dDensity(g, lim, Nbins)
    grid = [σi for σi=LinRange(lim[1], lim[2], Nbins)]
    #
    weights = [g(v...) for v in (grid[2:end] .+ grid[1:end-1]) ./ 2]
    weights ./= sum(weights)
    cumarr = [0;cumsum(vcat(weights...), dims=1)]
    return binned1dDensity(grid,cumarr,g)
end

import Base:rand
function rand(bD::binned1dDensity)
    # get
    binind = findfirst(bD.cumarr .> rand())-1
    σl, σr = bD.grid[binind], bD.grid[binind+1]
    σ = σl+rand()*(σr-σl)
    bD.density(σ) == 0.0 && return rand(bD)
    return σ
end

#################

struct binned2dDensity
    grid::Matrix{Array{Float64,1}}
    cumarr::Array{Float64}
    density
end

arg1_lims(binned2dDensity) = (binned2dDensity.grid[1,1][1],binned2dDensity.grid[end,end][1])
arg2_lims(binned2dDensity) = (binned2dDensity.grid[1,1][2],binned2dDensity.grid[end,end][2])

function getbinned2dDensity(g, arg1_lims, arg2_lims,  N1, N2)
    grid = hcat([[[x1,x2]
        for x2=range(arg2_lims[1], arg2_lims[2], length=N1)]
        for x1=range(arg1_lims[1], arg1_lims[2], length=N2)]...)
    #
    weights = [g(v...) for v in (grid[2:end,2:end] .+ grid[1:end-1,1:end-1]) ./ 2]
    # normalize by the cell size
    for i=1:size(grid,1)-1, j=1:size(grid,2)-1
        weights[i,j] *= (grid[i+1,j][2] - grid[i,j][2])*(grid[i,j+1][1] - grid[i,j][1])
    end
    weights ./= sum(weights)
    cumarr = [0;cumsum(vcat(weights...), dims=1)]
    return binned2dDensity(grid,cumarr, g)
end

import Base:rand
function rand(bD::binned2dDensity)
    # get
    binind = findfirst(bD.cumarr .> rand())-2
    # pars back
    Nrows, Ncols = size(bD.grid)
    indCol = div(binind, Nrows-1) + 1
    indRow = mod(binind, Nrows-1) + 1
    indRow+1 > Nrows && error("indRow+1 > Nrows: binind = $binind")
    indCol+1 > Ncols && error("indCol+1 > Ncols: binind = $binind")
    s1, σ1 = bD.grid[indRow,indCol]
    s2, σ2 = bD.grid[indRow+1,indCol+1]
#         s1 != s2 && error("Something is wrong!")
    s = s1+rand()*(s2-s1)
    σ = σ1+rand()*(σ2-σ1)
    bD.density(s,σ) == 0.0 && return rand(bD)
    return [s, σ]
end

function gridded_density_function(x, bD::binned2dDensity)
    (s,σ) = x
    (bD.grid[1,1][1] > s || s > bD.grid[1,end][1]) && return 0.0
    (bD.grid[1,1][2] > σ || σ > bD.grid[end,1][2]) && return 0.0
    #
    mp_s = map(x->x[1], bD.grid[1,:])
    i0 = findfirst(mp_s .> s)
    s0 = (mp_s[i0]+mp_s[i0-1])/2
    #
    mp_σ = map(x->x[2], bD.grid[:,1])
    j0 = findfirst(mp_σ .> σ)
    σ0 = (mp_σ[j0]+mp_σ[j0-1])/2
    #
    return bD.density(s0,σ0)!=0.0 ?  bD.density(s0,σ0) : 0.0;
end


module Bias

using LinearAlgebra
using Statistics
using Random
using SparseArrays

include("word_sets.jl")

export WEAT_WORD_SETS

function get_weat_idx_set(word_set::NamedTuple, vocab::Dict)
    words2indices(words) = [vocab[w].index for w in words if w in keys(vocab)]
    return (S=words2indices(word_set.S), T=words2indices(word_set.T),
            A=words2indices(word_set.A), B=words2indices(word_set.B))
end

function normalize_rows(X::AbstractArray)
    return mapslices(normalize, X, dims=2)
end

function normalize_rows(X::SparseMatrixCSC)
    return mapslices(normalize, X, dims=1)'
end

function effect_size(S::AbstractArray, T::AbstractArray, A::AbstractArray,
        B::AbstractArray)
    Ŝ = normalize_rows(S)
    T̂ = normalize_rows(T)
    Â = normalize_rows(A)
    B̂ = normalize_rows(B)

    μSA = mean(Ŝ * Â', dims=2)
    μSB = mean(Ŝ * B̂', dims=2)
    μTA = mean(T̂ * Â', dims=2)
    μTB = mean(T̂ * B̂', dims=2)

    dS = μSA - μSB
    dT = μTA - μTB
    return (mean(dS) - mean(dT)) / std(vcat(dS, dT))
end

# Helper to grab word vecs for you
function effect_size(W, weat_idx_set::NamedTuple)
    S = W[weat_idx_set.S, :]
    T = W[weat_idx_set.T, :]
    A = W[weat_idx_set.A, :]
    B = W[weat_idx_set.B, :]
    return effect_size(S, T, A, B)
end

function effect_size(X::SparseMatrixCSC, weat_idx_set::NamedTuple)
    S = X[:, weat_idx_set.S]
    T = X[:, weat_idx_set.T]
    A = X[:, weat_idx_set.A]
    B = X[:, weat_idx_set.B]
    return effect_size(S, T, A, B)
end

# Helper to compute effect size after changes to the embedding
function effect_size(W, weat_idx_set::NamedTuple, deltas::Dict)
    weat_vec_set = []
    delta_indices = keys(deltas) # the indices that have changes
    for indices in weat_idx_set
        vecs = W[indices, :]
        for (idx, pos) = zip(delta_indices, indexin(delta_indices, indices))
            # idx: word index of changed vectors
            # pos: relative position of that index in the "vecs" matrix
            if pos != nothing
                vecs[pos, :] += deltas[idx]
            end
        end
        push!(weat_vec_set, vecs)
    end
    return effect_size(weat_vec_set...)
end

# Compute p-value of the bias
function p_value(W, word_indices, N=10_000)
    es = effect_size(W, word_indices)
    ST = vcat(word_indices.S, word_indices.T)
    boundary = length(word_indices.S)
    trials = zeros(N)
    for i in 1:N
        perm = randperm(length(ST))
        S = ST[perm[1:boundary]]
        T = ST[perm[boundary+1:end]]
        trials[i] = effect_size(W, (S=S, T=T, A=word_indices.A, B=word_indices.B))
    end
    return mean(trials .> es)
end

end


struct SGD
    lr;
    momentum;
    weight_decay;
    v;
end

function (c::SGD)(model, loss)
    i = 1
    for p in params(model)
        c.v[i] = c.v[i] .* c.momentum + (1-c.momentum) .* (grad(loss, p) + 2 * c.weight_decay .* p)
        p = p .- c.lr * c.v[i]
        i = i + 1
    end
end

function SGD(lr::Int, momentum::Float, weight_decay::Float, model)
    v = []
    for p in params(model)
        push!(v, zeros(size(p)...))
    end
    SGD(lr, momentum, weight_decay, v)
end


struct DomTreeNode
    level::Int
    children::Vector{Int}
end
DomTreeNode() = DomTreeNode(1, Vector{Int}())

struct DomTree
    idoms::Vector{Int}
    nodes::Vector{DomTreeNode}
end

"""
    Checks if bb1 dominates bb2
"""
function dominates(domtree, bb1, bb2)
    bb1 == bb2 && return true
    target_level = domtree.nodes[bb1].level
    source_level = domtree.nodes[bb2].level
    source_level < target_level && return false
    for _ in (source_level-1):-1:target_level
        bb2 = domtree.idoms[bb2]
    end
    return bb1 == bb2
end

function update_level!(domtree, node, level)
    domtree[node] = DomTreeNode(level, domtree[node].children)
    foreach(domtree[node].children) do child
        update_level!(domtree, child, level+1)
    end
end

struct DominatedBlocks
    domtree::DomTree
    worklist::Vector{Int}
end

function dominated(domtree::DomTree, root::Int)
    doms = DominatedBlocks(domtree, Vector{Int}())
    push!(doms.worklist, root)
    doms
end

function start(doms::DominatedBlocks)
    nothing
end

function next(doms::DominatedBlocks, state::Nothing)
    bb = pop!(doms.worklist)
    for dominated in doms.domtree.nodes[bb].children
        push!(doms.worklist, dominated)
    end
    (bb, nothing)
end

function done(doms::DominatedBlocks, state::Nothing)
    isempty(doms.worklist)
end

# Construct Dom Tree
# Simple algorithm - TODO: Switch to the fast version (e.g. https://tanujkhattar.wordpress.com/2016/01/11/dominator-tree-of-a-directed-graph/)
function construct_domtree(cfg)
    dominators = IdSet{Int}[n == 1 ? IdSet{Int}(n) : IdSet{Int}(1:length(cfg.blocks)) for n = 1:length(cfg.blocks)]
    changed = true
    while changed
        changed = false
        for n = 2:length(cfg.blocks)
            isempty(cfg.blocks[n].preds) && continue
            firstp, rest = Iterators.peel(cfg.blocks[n].preds)
            new_doms = copy(dominators[firstp])
            for p in rest
                intersect!(new_doms, dominators[p])
            end
            push!(new_doms, n)
            changed |= (new_doms != dominators[n])
            dominators[n] = new_doms
        end
    end
    # Compute idoms
    idoms = fill(0, length(cfg.blocks))
    for i = 2:length(cfg.blocks)
        for dom in dominators[i]
            i == dom && continue
            any(p->p !== i && p !== dom && dom in dominators[p], dominators[i]) && continue
            idoms[i] = dom
        end
    end
    # Compute children
    domtree = DomTreeNode[DomTreeNode() for _ = 1:length(cfg.blocks)]
    for (idx, idom) in Iterators.enumerate(idoms)
        (idx == 1 || idom == 0) && continue
        push!(domtree[idom].children, idx)
    end
    # Recursively set level
    update_level!(domtree, 1, 1)
    DomTree(idoms, domtree)
end


# Copyright (c) 2021 California Institute of Technology (“Caltech”).
# U.S. Government sponsorship acknowledged.
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# - Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
# - Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# - Neither the name of Caltech nor its operating division, the Jet Propulsion
#   Laboratory, nor the names of its contributors may be used to endorse or
#   promote products derived from this software without specific prior written
#   permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

######################### Radial Functions #########################3

function radial_greens_function(L, r1, r2)
    if r1 <= r2
        return r2 / (2*L+1) * (r1/r2)^L
    else
        return r2 / (2*L+1) * (r2/r1)^(L+1)
    end
end

function get_radial_greens_matrix(L, ri, Δr)
    G = fill(NaN, length(ri), length(ri))
    for j=1:length(ri), i=j:length(ri)
        G[i,j] = G[j,i] = (ri[i] / ri[j]) * radial_greens_function(L, ri[i], ri[j])
    end
    G .*= Δr
    return Hermitian(G)
end

function get_radial_cryofunks(L, ri, Δr)
    B = diagm(@. √Δr * ri)
    G = get_radial_greens_matrix(L, ri, Δr)
    e = eigen(G)
    k = @. 1 / √e.values
    p = sortperm(k)
    k = k[p]
    Z = e.vectors[:,p]
    g = inv(B) * Z
    ginv = Z' * B
    return k, g, ginv
end

# vim: set sw=4 et sts=4 :


@symbol_func function n_rho(cur_reactor::AbstractReactor, cur_rho::AbstractSymbol)
  cur_n_bar = cur_reactor.n_bar

  cur_nu_n = cur_reactor.nu_n

  cur_n_rho = 1 - cur_rho ^ 2

  cur_n_rho ^= cur_nu_n

  cur_n_rho *= cur_n_bar

  cur_n_rho *= 1 + cur_nu_n

  cur_n_rho
end


heightmap = collect.(readlines("day9/heightmap.txt"))
heightmap = parse.(Int, permutedims(hcat(heightmap...)))
heightmap = pairs(heightmap)

function find_neighbors(index)
    CartesianIndex.(Tuple(index) .+ dir for dir in [(-1, 0), (1, 0), (0, -1), (0, 1)])
end

function find_lows(heightmap)
    lows = []
    for (index, value) in heightmap
        neighbor_values = [get(heightmap, i, 9) for i in find_neighbors(index)]
        if all(value .< neighbor_values)
            push!(lows, index)
        end
    end
    lows
end

p1 = sum(heightmap[i] + 1 for i in find_lows(heightmap))

@show p1

function find_basins(heightmap)
    lows = find_lows(heightmap)
    neighbors = Dict((index, find_neighbors(index)) for index in keys(heightmap))
    basins = []
    for low in lows
        indices = Set([low])
        basin = empty(indices)
        while !isempty(indices)
            index = pop!(indices)
            new_indices = filter(
                i -> get(heightmap, i, 9) < 9 && !(i in basin),
                neighbors[index]
            )
            union!(indices, new_indices)
            push!(basin, index)
        end
        push!(basins, basin)
    end
    basins
end

basins = find_basins(heightmap)
p2 = prod(sort(length.(basins), rev = true)[1:3])

@show p2


# eql_root.jl

using MIRT: eql_root

using Test: @test, @test_throws, @inferred

tests = [
1 1 -1
0 1 2
4 5 0
1 0 9 # 3 both times
# 1 0 -9; # -3 both times. omitted because out of prog scope
4 4 5
] # (2x+5)(2x-1), so 1/2.

predicted = [-1, 1, 0, 3, 1/2]
results = eql_root(tests[:,1], tests[:,2], tests[:,3])

@test results == predicted
@test eql_root(1, 1, -1) == -1


ishorizontal(s::Segment) = s.p1[2]==s.p2[2]
isvertical(s::Segment) = s.p1[1]==s.p2[1]

function draw_segment!(field, s::Segment)
    start, goal = s.p1, s.p2
    field[start[2], start[1]] += 1
    dif = sign.(goal - start)
    current = start
    while current != goal
        current += dif
        field[current[2], current[1]] += 1
    end
end

function day05a(segments; include_diagonals=false)
    field = zeros(Int8, 1000, 1000)
    for s in segments
        if include_diagonals || ishorizontal(s) || isvertical(s)
            draw_segment!(field, s)
        end
    end
    return length(findall(field .> 1))
end

function day05b(segments)
    return day05a(segments, include_diagonals=true)
end

function day05(io)
    segments = parse_segments(io)
    return (day05a(segments), day05b(segments))
end


module LinearWrapper

using JavaCall

const BaseMultivariateOptimizer = @jimport org.hipparchus.optim.BaseMultivariateOptimizer
const BaseOptimizer = @jimport org.hipparchus.optim.BaseOptimizer
const Class = @jimport java.lang.Class
const Collection = @jimport java.util.Collection
const ConvergenceChecker = @jimport org.hipparchus.optim.ConvergenceChecker
const Enum = @jimport java.lang.Enum
const GoalType = @jimport org.hipparchus.optim.nonlinear.scalar.GoalType
const JString = @jimport java.lang.String
const LinearConstraint = @jimport org.hipparchus.optim.linear.LinearConstraint
const LinearConstraintSet = @jimport org.hipparchus.optim.linear.LinearConstraintSet
const LinearObjectiveFunction = @jimport org.hipparchus.optim.linear.LinearObjectiveFunction
const LinearOptimizer = @jimport org.hipparchus.optim.linear.LinearOptimizer
const List = @jimport java.util.List
const MultivariateOptimizer = @jimport org.hipparchus.optim.nonlinear.scalar.MultivariateOptimizer
const NonNegativeConstraint = @jimport org.hipparchus.optim.linear.NonNegativeConstraint
const Object = @jimport java.lang.Object
const OptimizationData = @jimport org.hipparchus.optim.OptimizationData
const Optional = @jimport java.util.Optional
const PivotSelectionRule = @jimport org.hipparchus.optim.linear.PivotSelectionRule
const PointValuePair = @jimport org.hipparchus.optim.PointValuePair
const RealVector = @jimport org.hipparchus.linear.RealVector
const Relationship = @jimport org.hipparchus.optim.linear.Relationship
const SimplexSolver = @jimport org.hipparchus.optim.linear.SimplexSolver
const SimplexTableau = @jimport org.hipparchus.optim.linear.SimplexTableau
const SolutionCallback = @jimport org.hipparchus.optim.linear.SolutionCallback

export LinearConstraint
export LinearConstraintSet
export LinearObjectiveFunction
export LinearOptimizer
export NonNegativeConstraint
export PivotSelectionRule
export Relationship
export SimplexSolver
export SimplexTableau
export SolutionCallback
export compare_to
export compute_objective_value
export describe_constable
export do_optimize
export equals
export get_class
export get_coefficients
export get_constant_term
export get_constraints
export get_convergence_checker
export get_declaring_class
export get_evaluations
export get_goal_type
export get_iterations
export get_lower_bound
export get_max_evaluations
export get_max_iterations
export get_relationship
export get_solution
export get_start_point
export get_upper_bound
export get_value
export hash_code
export is_restricted_to_non_negative
export is_solution_optimal
export name
export normalize_constraints
export notify
export notify_all
export opposite_relationship
export optimize
export ordinal
export to_string
export value
export value_of
export values
export wait

include("linear_constraint.jl")
include("linear_constraint_set.jl")
include("linear_objective_function.jl")
include("linear_optimizer.jl")
include("non_negative_constraint.jl")
include("pivot_selection_rule.jl")
include("relationship.jl")
include("simplex_solver.jl")
include("simplex_tableau.jl")
include("solution_callback.jl")

end


export Gaussian,
        Disk,
        MRing,
        Crescent,
        ConcordanceCrescent,
        ExtendedRing,
        Ring,
        ParabolicSegment,
        Wisp

"""
$(TYPEDEF)
A type that defines it is a geometric model. These are usually
primitive models, and are usually analytic in Fourier and the image domain.
"""
abstract type GeometricModel <: AbstractModel end
@inline flux(::GeometricModel) = 1.0

@inline isprimitive(::Type{<:GeometricModel}) = IsPrimitive()

@inline visanalytic(::Type{<:GeometricModel}) = IsAnalytic()
@inline imanalytic(::Type{<:GeometricModel}) = IsAnalytic()

"""
    $(TYPEDEF)
Gaussian geometrical model.
This is a Gaussian with unit flux and standard deviation.

## Notes
To change the Gaussian flux, and shape please use the modifier functions
"""
struct Gaussian{T} <: GeometricModel end
Gaussian() = Gaussian{Float64}()
radialextent(::Gaussian) = 5.0

@inline function intensity_point(::Gaussian, x,y)
    return exp(-(x^2+y^2)/2)/2π
end

@inline function visibility_point(::Gaussian{T}, u, v, args...) where {T}
    return exp(-2π^2*(u^2 + v^2)) + zero(T)im
end

raw"""
    $(TYPEDEF)
Tophat disk geometrical model. The model is given by
```math
    I(x,y) = \begin{cases} \pi^{-1} & x^2+y^2 < 1 \\ 0 & x^2+y^2 \geq 0 \end{cases}
```
"""
struct Disk{T} <: GeometricModel end
Disk() = Disk{Float64}()

@inline function intensity_point(::Disk{T}, x, y, args...) where {T}
    r = x^2 + y^2
    return r < 1 ?  one(T)/(π) : zero(T)
end

@inline function visibility_point(::Disk{T}, x, y, args...) where {T}
    ur = 2π*(hypot(x,y) + eps(T))
    return 2*besselj1(ur)/(ur) + zero(T)im
end

radialextent(::Disk) = 3.0

"""
    $(TYPEDEF)
m-ring geometric model. This corresponds to a delta ring with a fourier expansion
in θ. The m in m-ring refers to the order of the Fourier expansion. The radius is unity.
"""
struct Ring{T} <: GeometricModel end
Ring() = Ring{Float64}()
radialextent(::Ring) = 1.5

@inline function intensity_point(::Ring{T}, x::Number, y::Number) where {T}
    r = hypot(x,y)
    θ = atan(x,y)
    dr = 1e-2
    if (abs(r-1) < dr/2)
        acc = one(T)
        return acc/(2π*dr)
    else
        return zero(T)
    end
end

@inline function visibility_point(::Ring{T}, u, v, args...) where {T}
    k = 2π*sqrt(u^2 + v^2) + eps(T)
    vis = besselj0(k) + zero(T)*im
    return vis
end

"""
    $(TYPEDEF)
m-ring geometric model. This corresponds to a delta ring with a fourier expansion
in θ. The m in m-ring refers to the order of the Fourier expansion. The radius is unity.
"""
struct MRing{T,N} <: GeometricModel
    """
    Real Fourier mode coefficients
    """
    α::NTuple{N,T}
    """
    Imaginary Fourier mode coefficients
    """
    β::NTuple{N,T}
end

function MRing{N}(α::T, β::T) where {N,T<:AbstractVector}
    (N != length(α)) && throw("N must be the length of the vector")
    S = promote_type(eltype(α), eltype(β))
    return MRing{S, N}(NTuple{N,S}(α), NTuple{N,S}(β))
end

function MRing(c::NTuple{N, <:Complex}) where {N}
    α = real.(c)
    β = imag.(c)
    return MRing(α, β)
end

radialextent(::MRing) = 1.5

@inline function intensity_point(m::MRing{T,N}, x::Number, y::Number) where {T,N}
    r = hypot(x,y)
    θ = atan(x,y)
    dr = 0.025
    if (abs(r-1) < dr/2)
        acc = one(T)
        for n in 1:N
            s,c = sincos(n*θ)
            acc += m.α[n]*c - m.β[n]*s
        end
        return acc/(2π*dr)
    else
        return zero(T)
    end
end

@inline function visibility_point(m::MRing{T,N}, u, v, args...) where {T,N}
    k = 2π*sqrt(u^2 + v^2) + eps(T)
    vis = besselj0(k) + zero(T)*im
    θ = atan(u, v)
    @inbounds for n in 1:N
        s,c = sincos(n*θ)
        vis += 2*(m.α[n]*c - m.β[n]*s)*(1im)^n*besselj(n, k)
    end
    return vis
end

"""
    $(TYPEDEF)
Creates a [Kamruddin and Dexter](https://academic.oup.com/mnras/article/434/1/765/1005984)
crescent model. This works by composing two disk models together.

# Arguments
- router: The radius of the outer disk
- rinner: The radius of the inner disk
- shift: How much the inner disk radius is shifted (positive is to the right)
- floor: The floor of the inner disk 0 means the inner intensity is zero and 1 means it is a large disk.
"""
function Crescent(router, rinner, shift, floor)
    m = stretched(Disk(), router, router)*(π*router^2) - shifted(stretched(Disk(), rinner, rinner)*((1-floor)*π*rinner^2), shift, zero(typeof(shift)))
    return m/flux(m)
end

"""
    $(TYPEDEF)
Creates the ConcordanceCrescent model, i.e. a flat-top crescent
with a displacment and a slash. Note this creates a crescent with
unit flux. If you want a different flux please use the `renomed`
modifier.

## Fields
## Notes
Unlike the Gaussian and Disk models this does not create the
unit version. In fact, this model could have been created using
the `Disk` and primitives by using Comrade.jl's model composition
functionality.
"""
struct ConcordanceCrescent{T} <: GeometricModel
    """
    Outer radius of the crescent
    """
    router::T
    """
    Inner radius of the crescent
    (i.e. inside this radius there is a hole)
    """
    rinner::T
    """
    Displacment of the inner disk radius
    """
    shift::T
    """
    Strength of the linear slash. Note that
    s∈[0.0,1.0] to ensure positivity in the image.
    """
    slash::T
end

radialextent(m::ConcordanceCrescent) = m.router*1.5

# Crescent normalization to ensure the
function _crescentnorm(m::ConcordanceCrescent)
    f = (1+m.slash)*(m.router^2 - m.rinner^2) -
        (1-m.slash)*m.shift*m.rinner*m.rinner/m.router
    return 2/π/f
end

function intensity_point(m::ConcordanceCrescent{T}, x, y, args...) where {T}
    r2 = x^2 + y ^2
    norm = _crescentnorm(m)
    if (r2 < m.router^2 && (x-m.shift)^2 + y^2 > m.rinner^2 )
        return norm/2*((1+x/m.router) + m.slash*(1-x/m.router))
    else
        return zero(T)
    end
end

function visibility_point(m::ConcordanceCrescent{T}, u, v, args...) where {T}
    k = 2π*sqrt(u^2 + v^2) + eps(T)
    norm = π*_crescentnorm(m)/k
    phaseshift = cispi(2*m.shift*u)
    b0outer,b0inner = besselj0(k*m.router), besselj0(k*m.rinner)
    b1outer,b1inner = besselj1(k*m.router), besselj1(k*m.rinner)
    b2outer,b2inner = besselj(2,k*m.router), besselj(2, k*m.rinner)

    v1 = (1+m.slash)*m.router*b1outer
    v2 = ((1+m.slash) + (1-m.slash)*m.shift/m.router)*
            phaseshift*m.rinner*b1inner
    v3 = -2im*π*u*(1-m.slash)*(m.router*b0outer -
                           m.router*b2outer -
                           2*b1outer/k
                          )/(2*k)
    v4 = -2im*π*u*(1-m.slash)*(m.rinner*b0inner -
                          m.rinner*b2inner -
                          2*b1inner/k
                         )/(2*k)*(m.rinner/m.router)*phaseshift
    return norm*(v1-v2+v3-v4)
end

"""
    $(TYPEDEF)
A symmetric extended ring whose radial profile follows an inverse
gamma distributions.

# Note
We mainly use this as an example of a non-analytic Fourier transform
(although it has a complicated expression)

# Fields

$(FIELDS)

"""
struct ExtendedRing{F} <: GeometricModel
    """shape of the radial distribution"""
    shape::F
end
visanalytic(::Type{<:ExtendedRing}) = NotAnalytic()

radialextent(m::ExtendedRing) = 6.0

function intensity_point(m::ExtendedRing, x, y)
    r = hypot(x, y) + eps()
    β = (m.shape + 1)
    α = m.shape
    β^α*r^(-α-2)*exp(-β/r)/gamma(α)/(2*π)
end

"""
    $(TYPEDEF)
A delta parabolic segment in the image domain.
The segment is centered at zero, with roots ±1 and a yintercept of 1.
"""
struct ParabolicSegment{F} <: GeometricModel end
ParabolicSegment() = ParabolicSegment{Float64}()
radialextent(::ParabolicSegment{T}) where {T} = one(T)*sqrt(2)

"""
    ParabolicSegment(a, h)
A parabolic segment with x-intercepts `±a`` and a yintercept of `h``.

# Note
This is just a convenience function for `stretched(ParabolicSegment(), a, h)`
"""
@inline function ParabolicSegment(a, h)
    # Define stretched model from unital model
    stretched(ParabolicSegment(), a, h)
end

function intensity_point(::ParabolicSegment{T}, x, y) where {T}
    #length = (√5 + asinh(2)/2)
    yw = (1-x^2)
    if abs(y - yw) < 0.01/2 && abs(x) < 1
        return 1/(2*0.01)
    else
        return zero(T)
    end
end

function visibility_point(::ParabolicSegment{T}, u, v) where {T}
    ϵ = sqrt(eps(T))
    vϵ = v + ϵ + 0im
    phase = cispi(3/4 + 2*vϵ + u^2/(2vϵ))
    #length = (√5 + asinh(2)/2)
    Δ1 = erf(√(π/(2vϵ))*cispi(1/4)*(u-2vϵ))
    Δ2 = erf(√(π/(2vϵ))*cispi(1/4)*(u+2vϵ))
    return phase/(√(2vϵ))*(Δ1-Δ2)/4
end


# ------------------------------------------------------------------------------------------
# ## Create a caesar cipher
#
# ### Caesar ciphers
#
# A caesar cipher is an encryption scheme that shifts all letters in the alphabet by some
# specified offset to other letters in the alphabet.
#
# For example, a shift of 1 would turn the letter "A" into the letter "B" and the letter "M"
# to the letter "N".
#
# ### Goal
#
# We want to add a method to the `+` operator such that we can add together a string and an
# integer shift to encrypt a message. For example,
#
# ```julia
# 4 + "hello" == "lipps"
# ```
#
# We'll walk you through the steps to do this!
#
# ### Test it out
#
# Once you think you have it working, try to decrypt the following string by adding a shift
# of -7.
# ```julia
# "Kv'uv{'tlkksl'pu'{ol'hmmhpyz'vm'kyhnvuz'mvy'\u80v|'hyl'jy|ujo\u80'huk'{hz{l'nvvk'~p{o'rl{
# jo|w5"
# ```
#
# ### Let's get started!
#
# First, we want a way to convert between characters and integers. Actually, under the hood,
# all of our characters are being represented as numbers via their *ASCII representation*.
#
# You can start to get a feel for how this works by running the following lines of code.
#
# ```julia
# convert(Int, 'a')
# convert(Int, 'b')
# convert(Char, 97)
# convert(Char, 98)
# ```
# ------------------------------------------------------------------------------------------

convert(Int, 'a')

convert(Int, 'b')

convert(Char, 97)

convert(Char, 98)

# ------------------------------------------------------------------------------------------
# What happens when you try to add an integer to a character? (Note that the difference
# between `Char`s and `String`s is important here!)
# ------------------------------------------------------------------------------------------

'a' + 1

'm' + 1

# Basically our caesar cipher already works for characters.
# We still need to add a method to `+` that makes it work for strings!

# ------------------------------------------------------------------------------------------
# When we treat a string elementwise, what is the type of (`typeof`) each element?
# ------------------------------------------------------------------------------------------

[typeof(x) for x in "Mew"]

map(x -> typeof(x), collect("Mew"))

# ------------------------------------------------------------------------------------------
# Try to write a function called `caesar(shift, stringin)` that encodes its input string,
# `stringin`, by shifting all letters in the alphabet by `shift`.
#
# One way to do this is to use the `map` or `broadcast` function!
# ------------------------------------------------------------------------------------------

caesar(shift, stringin) = map(x -> x + shift, stringin)

# ------------------------------------------------------------------------------------------
# If you think you have this working, try out
# ```julia
# caesar(-4, "lipps")
# ```
# ------------------------------------------------------------------------------------------

caesar(-4, "lipps")

# ------------------------------------------------------------------------------------------
# Last, we want to extend the `+` operator to include a way to apply this cipher.
#
# The `+` operator lives in a place called "Base". Everything that lives in Base is
# accessible to us as users by default, but we need a special incantation to modify the
# things that live in Base. If we want to modify `+`, our incantation is
#
# ```julia
# import Base: +
# ```
# ------------------------------------------------------------------------------------------

import Base: +

# ------------------------------------------------------------------------------------------
# After you have imported `+` from Base, we are ready to modify it.
#
# If you're not sure how to add a method for `+`, let's go through an example first.
#
# Let's say we want to add a method for `+` that allows us to concatenate strings so that we
# can say
# ```julia
# "hello " + "world"
# ```
# and receive the output string ```hello world```.
#
# To do this, we can write a method for `+` like this
# ------------------------------------------------------------------------------------------

+(x::String, y::String) = string(x, y)

# ------------------------------------------------------------------------------------------
# Note that I've added type information about `x` and `y` with a double colon and the word
# `String` to say that `x` and `y` are both strings.
#
# To test that this works, try out
# ```julia
# "hello " + "world"
# ```
# ------------------------------------------------------------------------------------------

"hello " + "world"

# ------------------------------------------------------------------------------------------
# And now that you've extended `+` once, let's add another method for `+` that calls the
# `caesar` function we've written.
# ------------------------------------------------------------------------------------------

+(shift::Int, stringin::String) = caesar(shift, stringin)

# ------------------------------------------------------------------------------------------
# Now try it out via
#
# ```julia
# -7 + "Kv'uv{'tlkksl'pu'{ol'hmmhpyz'vm'kyhnvuz'mvy'\u80v|'hyl'jy|ujo\u80'huk'{hz{l'nvvk'~p{
# o'rl{jo|w5"
# ```
# ------------------------------------------------------------------------------------------

-7 + "Kv'uv{'tlkksl'pu'{ol'hmmhpyz'vm'kyhnvuz'mvy'\u80v|'hyl'jy|ujo\u80'huk'{hz{l'nvvk'~p{o'rl{jo|w5"


mutable struct JacobiPreconditioner{Tv, Ti} <: AbstractPreconditioner{Tv,Ti}
    A::ExtendableSparseMatrix{Tv,Ti}
    invdiag::Array{Tv,1}
    function JacobiPreconditioner{Tv,Ti}() where {Tv,Ti}
        p=new()
        p.invdiag=zeros(Tv,0)
        p
    end
end

"""
```
JacobiPreconditioner(;valuetype=Float64, indextype=Int64)
JacobiPreconditioner(matrix)
```

Jacobi preconditioner
"""
JacobiPreconditioner(;valuetype::Type=Float64, indextype::Type=Int64)=JacobiPreconditioner{valuetype,indextype}()

function update!(precon::JacobiPreconditioner{Tv,Ti}) where {Tv,Ti}
    flush!(precon.A)
    cscmatrix=precon.A.cscmatrix
    if length(precon.invdiag)==0
        precon.invdiag=Array{Tv,1}(undef,cscmatrix.n)
    end
    invdiag=precon.invdiag
    n=cscmatrix.n
    @inbounds for i=1:n
        invdiag[i]=1.0/cscmatrix[i,i]
    end
    precon
end

function  LinearAlgebra.ldiv!(u::AbstractArray{T,1} where T, precon::JacobiPreconditioner, v::AbstractArray{T,1} where T)
    invdiag=precon.invdiag
    n=length(invdiag)
    @inbounds @simd for i=1:n
        u[i]=invdiag[i]*v[i]
    end
end

function LinearAlgebra.ldiv!(precon::JacobiPreconditioner, v::AbstractArray{T,1} where T)
    ldiv!(v, precon, v)
end


#
# SpherePlots.jl –
#
# import RecipesBase: plot
# import Plots: scatter3d, scatter3d!, surface!, surface, current
# export plot
# """
#     plot(M::Sphere, signals) – plot signals on the two sphere
# """
# function plot(M::Sphere,signals::Array{Array{SnPoint,1},1})
#     if manifoldDimension(M) > 2
#         error("Plot only works for functions on S2")
#     end
#     plotlyjs()
#     n = 180
#     u = linspace(0,2*π,n);
#     v = linspace(0,π,n);
#     x = cos.(u) * sin.(v)';
#     y = sin.(u) * sin.(v)';
#     z = ones(n) * cos.(v)';
#     surface(x,y,z,opacity=.95,
#     f = :white,
#     axis=nothing,colorbar=nothing,legend=nothing,border=:none)
#     for signal in signals
#         if any(manifoldDimension.(signal) .> 2)
#             error("This plot method only works for points on S2.");
#         end
#         xS = [ getValue(p)[1] for p in signal]
#         yS = [ getValue(p)[2] for p in signal]
#         zS = [ getValue(p)[3] for p in signal]
#         scatter3d!(xS,yS,zS)
#     end
#     return current()
# end
# plot(M::Sphere, signal::Array{SnPoint,1}) = plot(M,[signal])


sumsqr(x, y) = x^2 + y^2


module Day8

export solvePart1, solvePart2

import DelimitedFiles: readdlm
import DataStructures: deque, popfirst!

function getInput()
    open("day8input.txt") do f
        return readdlm(f, Int64)[:]
    end
end

struct Node
    children
    metadata
end

function extractTreeRecursive(q)
    numChildren = popfirst!(q)
    numMetadata = popfirst!(q)
    children = []
    for i in 1:numChildren
        push!(children, extractTreeRecursive(q))
    end
    metadata = []
    for i in 1:numMetadata
        push!(metadata, popfirst!(q))
    end
    return Node(children, metadata)
end

function extractTree(numbers)
    q = deque(Int64)
    push!(q, numbers...)
    tree = extractTreeRecursive(q)
    return tree
end

function sumMetadata(tree::Node)
    summ = 0
    for child in tree.children
        summ += sumMetadata(child)
    end
    summ += sum(tree.metadata)
    return summ
end

function solvePart1()
    numbers = getInput()
    tree = extractTree(numbers)
    return sumMetadata(tree)
end

function getValue(node::Node)
    n = length(node.children)
    if n == 0
        return sum(node.metadata)
    end
    value = 0
    for entry in node.metadata
        if entry <= n
            value += getValue(node.children[entry])
        end
    end
    return value
end

function solvePart2()
    numbers = getInput()
    tree = extractTree(numbers)
    return getValue(tree)
end

end


##############################################################################
#
# Test problem
#
#############################################################################

function rosenbrock(x)
  n = 6

  # Initializations
  f = 0

  evenIdx = 2:2:n
  oddIdx = 1:2:(n - 1)

  f1 = x[evenIdx] .- x[oddIdx] .^ 2
  f2 = 1 .- x[oddIdx]

  # Function
  f = sum(f1 .^ 2 .+ f2 .^ 2)

  return f
end


"""
    cembed(ts::Vector{Vector{T}}, in_which_pos::Vector{Int},
                at_what_lags::Vector{Int}) where T <: Number ->
                Embedding

Perform a state space embedding of the vectors in `ts`.

## Arguments
1. `which_ts::Vector{Vector{T}} where T <: Number`. Contains the the time
    series to embed.
2. `in_which_pos::Vector{Int}``. The length of in_which_pos gives the dimension
    of the embedding. The value of the ith element of in_which_pos indicates
    which time series in the ith column of the embedding.
    - **Example 1**: if `which_ts = [ts1, ts2]`, then we index ts1 as 1 and
        ts2 as 2. Setting `in_which_pos = [2, 2, 1]` will result in a
        3-dimensional embedding where `ts2` will appear in columns 1 and 2,
        while `ts1` will appear in column 3.
    - **Example 2**: If `which_ts = [ts1, ts2, ts3]`, then
        `in_which_pos = [2, 1, 2, 3, 3]` results in a 5-dimensional embedding
        where `ts1`appears in column 2, `ts2` appears in columns 1 and 3, while
        `ts3`appears in columns 4 and 5.
3. `at_what_lags::Vector{Int}` sets the lag in each column. Must be the same
    length as `which_ts`.
    - **Example**: if `in_which_pos = [2, 2, 1]`, then
        `at_what_lags = [1, 0, -1]` means that the lag in column 1 is 1, the
        lag in the second column is 0 and the lag in the third column is -1.
"""
function cembed(ts::Vector{Vector{T}},
               in_which_pos,
               at_what_lags;
                 labels::Vector{String} = ["" for x in 1:length(ts)]) where {T<:Number}
    dim = length(in_which_pos)
    minlag, maxlag = minimum(at_what_lags), maximum(at_what_lags)
    npts = length(ts[1]) - (maxlag + abs(minlag))
    E = zeros(T, dim, npts)

    for i in 1:length(in_which_pos)
        ts_ind = in_which_pos[i]
        TS = ts[ts_ind]
        lag = at_what_lags[i]

        if lag > 0
            E[i, :] = TS[((1 + abs(minlag)) + lag):(end - maxlag) + lag]
        elseif lag < 0
            E[i, :] = TS[((1 + abs(minlag)) - abs(lag)):(end - maxlag - abs(lag))]
        elseif lag == 0
            E[i, :] = TS[(1 + abs(minlag)):(end - maxlag)]
        end
    end

    embeddingdata = EmbeddingData{dim, T}(
        ts,  # the dataseries
        ["" for i = 1:length(ts)], # empty labels by default
        ["" for i = 1:length(ts)], # empty descriptions by default
        EmbeddingPositions(in_which_pos), # the positions in which the dataseries appear
        EmbeddingLags(at_what_lags) # the embedding lag for each column
        )
    Embedding(E, embeddingdata)
end

"""
    embed(v::Vector{Vector{T}})

Returns an embedding of a vector of vectors, treating each
vector as a dynamical variable. Zero lag is used for all the
columns.
"""
function cembed(v::Vector{Vector{T}}) where {T}
    D = length(v)
    cembed(v, [i for i = 1:D], [0 for i in 1:D])
end

"""
    embed(A::AbstractArray{T, 2}) where T

Returns an embedding of an array, treating each
column as a dynamical variable. Zero lag is used
for all the columns.
"""
function cembed(data::AbstractArray{T, 2}) where T

	if size(data, 1) > size(data, 2)
        #info("Treating each row of data as a point")
        dim = size(data, 2)
        which_pos = [i for i = 1:dim]
        which_lags = [0 for i in 1:dim]
        return cembed([data[:, i] for i = 1:dim], which_pos, which_lags)
    else
        #info("Treating each column of data as a point")
        dim = size(data, 1)
        which_pos = [i for i = 1:dim]
        which_lags = [0 for i in 1:dim]
        return cembed([data[i, :] for i = 1:dim], which_pos, which_lags)
	end
end

"""
    embed(data::AbstractArray{T, 2},
        in_which_pos::Vector{Int},
        at_what_lags::Vector{Int}) where T

Embedding of data represented by an array. Each
column of the array must correspond to one data series.
"""
function cembed(data::AbstractArray{T, 2},
                in_which_pos,
                at_what_lags) where T
    if size(data, 1) > size(data, 2)
        #info("Treating each row as a point")
        dim = size(data, 2)
        cembed([data[:, i] for i = 1:dim], in_which_pos, at_what_lags)
    else
        #info("Treating each column of data as a point")
        dim = size(data, 1)
        cembed([data[i, :] for i = 1:dim], in_which_pos, at_what_lags)
    end
end

export cembed


using Extremes, DataFrames, Distributions, Gadfly, Dates. Mamba

data = load("wooster")

x = collect(Date(1983,1,1):Day(1):Date(1987,12,31))

data[!,:Date] = x
select!(data, [:Date, :Temperature])

data

plot(data, x=:Date, y=:Temperature, Geom.point)

df = copy(data)

df[!,:Temperature] = -data[:,:Temperature]

filter!(row -> month(row.Date) ∈ (1,2,11,12), df)

plot(df, x=:Date, y=:Temperature, Geom.point)

y = df[:,:Temperature]

threshold =  -10

cluster = getcluster(y, threshold, runlength=4)

z = maximum.(cluster) .- threshold

fm = gpfit(z)

r = returnlevel(fm, threshold, size(df,1), 120, 100, .95)

r.value[]
r.cint[]

data = load("dowjones")

X = data[:,:Index]

X̃ = 100*(log.(X[2:end]) - log.(X[1:end-1]))

df = DataFrame(Date = data[2:end, :Date], Variation = X̃)

plot(df, x=:Date, y=:Variation, Geom.line)

threshold = 2.0

cluster = getcluster(df[:,:Variation], threshold, runlength=4)

z = maximum.(cluster) .- threshold

fm = gpfit(z)

data = load("fremantle")

fm = gevfitbayes(data, :SeaLevel, locationcovid = [:Year, :SOI])

p = plot(fm.sim[:,1,1])

p[2]

p = plot(fm.sim)

hstack([p[1], p[2]])

# Lis all files in a directory
filenames = String[]

for (root, dirs, files) in walkdir("src/validationplots/")
    for file in files
        push!(filenames, joinpath(root, file))
        # println(joinpath(root, file)) # path to files
    end
end


"""
meshgrid(x)
meshgrid(x, y)
meshgrid(x, y, z)

Copy of Matlab's meshgrid function.
Returns 2D grid coordinates based on x and y.

# Examples
```jldoctest
julia> R = meshgrid(1:3, 1:3)
([1 2 3; 1 2 3; 1 2 3], [1 1 1; 2 2 2; 3 3 3])
```

In 3D, meshgrid returns vectors rather than tensors.
```jldoctest
julia> meshgrid(1:3)
([1 2 3; 1 2 3; 1 2 3], [1 1 1; 2 2 2; 3 3 3])
```
"""

function meshgrid(x,y)
        X = repeat(transpose(x[:]), size(y[:], 1), 1)
        Y = repeat(y, 1, size(x, 1))
        return X, Y
end

function meshgrid(x)
        return meshgrid(x,x)
end

function meshgrid(x1D,y1D,z1D)
        Np = length(x1D)*length(y1D)*length(z1D)
        x = zeros(Np)
        y = zeros(Np)
        z = zeros(Np)
        sk = 1
        for k = 1:length(z1D)
                for j = 1:length(y1D)
                        for i = 1:length(x1D)
                                x[sk] = x1D[i]
                                y[sk] = y1D[j]
                                z[sk] = z1D[k]
                                sk += 1
                        end
                end
        end
        return x,y,z
end


import FractionalCalculus.FracDiffAlg

"""
Riemann-Liouville sense fractional derivative algorithms, please refer to [Riemann-Liouville derivative](https://en.wikipedia.org/wiki/Fractional_calculus#Riemann%E2%80%93Liouville_fractional_derivative)
"""
abstract type RLDiff <: FracDiffAlg end

#RLDiff_LinearInterp maybe??
"""
# Riemann Liouville sense derivative approximation

    fracdiff(f, α, end_point, h, RLDiff_Approx())

Using Linear interpolation to approximate fractional derivative in Riemann Liouville  fractional derivative sense.

### Example

```julia-repl
julia> fracdiff(x->x^5, 0.5, 2.5, 0.0001, RLDiff_Approx())
```

!!! warning
    The RLDiff_Approx algorithm only support for 0 < α < 1.
"""
struct RLDiff_Approx <: RLDiff end

"""
# Riemann Liouville sense derivative using Triangular Strip Matrix to discrete and compute.

    fracdiff(f, α, end_point, h, RLDiff_Matrix())

Using [Triangular Strip Matrix](https://en.wikipedia.org/wiki/Triangle_strip) to approximate fractional derivative.

### Example

```julia-repl
julia> fracdiff(x->x^5, 0.5, 2.5, 0.0001, RLInt_Matrix())
```

!!! info
    Triangular Strip Matrix method returns the derivative in the interval ``[0, T]`` in ```Vector```

!!! tip
    With the advancing Triangular Strip Matrix method, you can not only compute fractional derivatives, integer order, higher order derivative is also supported!!
Try to set α as an integer, arbitrary integer of course! I promise you would enjoy it😏

```tex
@article{2009,
title={Matrix approach to discrete fractional calculus II: Partial fractional differential equations},
DOI={10.1016/j.jcp.2009.01.014},
author={Podlubny, Igor and Chechkin, Aleksei and Skovranek, Tomas and Chen, YangQuan and Vinagre Jara, Blas M.},
}
```
"""
struct RLDiff_Matrix <: RLDiff end

"""
Using linear spline interpolation method to approximate the Riemann Liouville fractional derivative.
"""
struct RL_Linear_Spline_Interp <: RLDiff end

################################################################
###                    Type defination done                  ###
################################################################

function fracdiff(f::Union{Number, Function}, α, end_point, h, ::RLDiff_Approx)
    #checks(f, α, 0, end_point)

    summation = 0
    n = Int64(floor(end_point/h))

    @fastmath @inbounds @simd for i ∈ 0:n-1
        summation += (f(end_point-i*h) - f(end_point-(i+1)*h))*((i+1)^(1-α) - i^(1-α))
    end

    result = ((1-α)*f(0)/n^α+summation)*end_point^(-α)*n^α/gamma(2-α)
    return result
end

function fracdiff(f::Union{Number, Function}, α::Float64, end_point::AbstractArray, h, ::RLDiff_Approx)::Vector
    result = map(x->fracdiff(f, α, x, h, RLDiff_Approx()), end_point)
    return result
end

function fracdiff(f, α, end_point, h::Float64, ::RLDiff_Matrix)
    N = Int64(end_point/h+1)
    @views tspan = collect(0:h:end_point)
    return B(N, α, h)*f.(tspan)
end

#Compute the eliminator matrix Sₖ by omiting n-th row
function eliminator(n, row)
    temp = zeros(n, n) + I
    return @views temp[Not(row), :]
end

#Generate elements in Matrix.
function omega(n, p)
    omega = zeros(n+1)

    omega[1]=1
    @fastmath @inbounds @simd for i ∈ 1:n
        omega[i+1]=(1-(p+1)/i)*omega[i]
    end

    return omega
end
function B(N, p, h::Float64)
    result = zeros(N, N)
    temp = omega(N, p)

    @inbounds @simd for i ∈ 1:N
        @views result[i, 1:i]=reverse(temp[1:i])
    end

    return h^(-p)*result
end
# Multiple dispatch for not assigning step size *h*.
function B(N, p)
    result=zeros(N, N)
    temp=omega(N, p)

    @inbounds @simd for i ∈ 1:N
        @views result[i, 1:i]=reverse(temp[1:i])
    end

    return result
end

#=
Numerical methods for fractional calculus by Li, Changpin
Page 57

Linear Spline Interpolation
=#
function fracdiff(f, α, x, h, ::RL_Linear_Spline_Interp)
    N = Int64(floor(x/h))

    result = 0

    for k = 0:(N+1)
        result += z̄ₘₖ(N, k, α)*f(k*h)
    end

    return 1/(gamma(4-α)h^α)*result

end

function z̄ₘₖ(m, k, α)
    if k ≤ m-1
        return c̄ⱼₖ(m-1, k, α)-2*c̄ⱼₖ(m, k, α)+c̄ⱼₖ(m+1, k, α)
    elseif k == m
        return -2*c̄ⱼₖ(m, k, α)+c̄ⱼₖ(m+1, k, α)
    elseif k == m+1
        return c̄ⱼₖ(m+1, k, α)
    elseif k > m+1
        return 0
    end
end

function c̄ⱼₖ(j, k, α)
    if k==0
        return (j-1)^(3-α)-j^(2-α)*(j-3+α)
    elseif 1 ≤ k ≤ j-1
        return (j-k+1)^(3-α)-2*(j-k)^(3-α)+(j-k-1)^(3-α)
    elseif k == j
        return 1
    end
end

function fracdiff(f::Union{Number, Function}, α::Float64, end_point::AbstractArray, h, ::RL_Linear_Spline_Interp)::Vector
    result = map(x->fracdiff(f, α, x, h, RL_Linear_Spline_Interp()), end_point)
    return result
end


@testset "Brent's Method" begin
    for (name, prob) in OptimTestProblems.UnivariateProblems.examples
        for T in (Float64, BigFloat)
            results = optimize(prob.f, convert(Array{T}, prob.bounds)..., method = Brent())

            @test Optim.converged(results)
            @test norm(Optim.minimizer(results) .- prob.minimizers) < 1e-7
        end
    end

    ## corner cases - empty and zero-width brackets
    result = optimize(sqrt, 4.0, 4.0, method = Brent())
    @test Optim.converged(result)
    @test Optim.minimizer(result) == 4.0
    @test Optim.minimum(result) == 2.0
    @test_throws ErrorException optimize(identity, 2.0, 1.0, Brent())
    @test summary(result) == "Brent's Method"

    ## corner cases - largely flat functions
    result = optimize(x->sign(x), -2, 2)
    @test Optim.converged(result)
    @test Optim.minimum(result) == -1.0
    result = optimize(x->sign(x), -1, 2)
    @test Optim.converged(result)
    @test Optim.minimum(result) == -1.0
    result = optimize(x->sign(x), -2, 1)
    @test Optim.converged(result)
    @test Optim.minimum(result) == -1.0

    result = Optim.optimize(x->sin(x), 0, 2π, Optim.Brent(); abs_tol=1e-4, store_trace=false, show_trace=true, iterations=2)
end


using Knet

"""
Smooth L1 Loss.
- As stated in the Fast R-CNN paper, it is calculated as:

    for |x| < beta --> 0.5 * x^2
    otherwise      --> |x| - 0.5

    where x = gt - pred
"""

function smooth_l1(x; beta=1)
    low_idx = findall(Array(value(x)) .< beta)
    high_idx = findall(Array(value(x)) .>= beta)
    loss_sum = sum(x[high_idx] .- 0.5)
    loss_sum += sum(0.5 .* (x[low_idx].^2))
    return loss_sum
end


"""
$(TYPEDEF)

two sample test for mean

Constructors
------------
* `TwoSampleMean(mu1::Real, mu2::Real, k::Real, stdunknown::Bool)`

Arguments
---------
* `mu1`: Mean of group 1

* `mu2`: Mean of group 2

* `k`: Allocation ratio of the groups, k = n(group1) / n(group 2)

* `stdunknown`: If the population standard deviation is known, default = false,
when the population parameter is known, the power / sample size are calculated based
on a standard normal distribution, otherwise a t distribution will be used

Fields
------
$(FIELDS)
"""
type TwoSampleMean <: TrialTest
    mu1::Real
    mu2::Real
    k::Real
    stdunknown::Bool

    # Validator
    function TwoSampleMean(mu1, mu2, k, stdunknown = false)

        if !((-Inf < mu1 < Inf) & (-Inf < mu2 < Inf))

            error("Mean values must be in (-Inf, Inf)")

        end # end if

        if !(0 < k < Inf)

            error("Allocation ratio must be in (0, Inf)")

        end # end if
        new(mu1, mu2, k, stdunknown)

    end # end function

end # end type

# Two-sample test for mean, if the standard devaition is the same for the two groups
function hypotheses{T <: TwoSampleMean}(test::T, n::Real, std::Real, alpha::Real, side::String)

    diff = test.mu1 - test.mu2
    se = sqrt(1 / n + 1 / (test.k * n)) * std
    ts = diff / se
    if test.stdunknown == false

        if side == "two"

            p =  cdf(ZDIST, ts - quantile(ZDIST, 1 - alpha / 2)) + cdf(ZDIST, -ts - quantile(ZDIST, 1 - alpha / 2))

        else

            p = cdf(ZDIST, abs(ts) - quantile(ZDIST, 1 - alpha))

        end # end if

    else

        if side == "two"

            p =  cdf(TDist(n + test.k * n - 2), ts - quantile(TDist(n + test.k * n - 2), 1 - alpha / 2)) +
                 cdf(TDist(n + test.k * n - 2), -ts - quantile(TDist(n + test.k * n - 2), 1 - alpha / 2))

        else

            p = cdf(TDist(n + test.k * n - 2), abs(ts) - quantile(TDist(n + test.k * n - 2), 1 - alpha))

        end # end if

    end # end if
    # power_result(test, p)
    return p

end # end function

# Two-sample test for mean, if the standard devaition is not the same
function hypotheses{T <: TwoSampleMean}(test::T, n::Real, std::Tuple{Real, Real}, alpha::Real, side::String)

    diff = test.mu1 - test.mu2
    se = sqrt(std[1] ^ 2 / (test.k * n) + std[2] ^ 2 / n)
    ts = diff / se
    if test.stdunknown == false

        if side == "two"

            p =  cdf(ZDIST, ts - quantile(ZDIST, 1 - alpha / 2)) + cdf(ZDIST, -ts - quantile(ZDIST, 1 - alpha / 2))

        else

            p = cdf(ZDIST, abs(ts) - quantile(ZDIST, 1 - alpha))

        end # end if

    else

        if side == "two"

            p =  cdf(TDist(n + test.k * n - 2), ts - quantile(TDist(n + test.k * n - 2), 1 - alpha / 2)) +
                 cdf(TDist(n + test.k * n - 2), -ts - quantile(TDist(n + test.k * n - 2), 1 - alpha / 2))

        else

            p = cdf(TDist(n + test.k * n - 2), abs(ts) - quantile(TDist(n + test.k * n - 2), 1 - alpha))

        end # end if

    end # end if
    # power_result(test, p)
    return p

end # end function


struct PSRate{P} <: PopulationState{P}
  n::Int64 # population size
  neurontype::P
  state_now::Vector{Float64}
  alloc_du::Vector{Float64}
  alloc_r::Vector{Float64}
  input::Vector{Float64}
end
function PSRate(p::NeuronType,n::Integer)
  PSRate(n,p,[zeros(Float64,n) for _ in (1:4) ]... )
end

iofunction(x,ps::PSRate) = iofunction(x,ps.neurontype)
ioinv(x,ps::PSRate) =ioinv(x,ps.neurontype)

function local_update!(t::Real,dt::Float64,ps::PSRate)
  copy!(ps.alloc_du,ps.state_now)  # u
  ps.alloc_du .-= ps.input  # (u-I)
  lmul!(-dt/ps.neurontype.τ,ps.alloc_du) # du =  dt/τ (-u+I)
  ps.state_now .+= ps.alloc_du # u_t+1 = u_t + du
  return nothing
end

# threshold-linear input-output function
struct NTReLU <: NeuronType
  τ::Float64 # time constant
  α::Float64 # gain modulation
end
@inline function iofunction(x::Float64,p::NTReLU)
  if x<=0.0
    return 0.0
  else
    return x*p.α
  end
end
@inline function ioinv(y::Float64,p::NTReLU)
  y<=0.0 && return 0.0
  return y/p.α
end

# threshold-quadratic input-output function
struct NTQuadratic <: NeuronType
  τ::Float64 # time constant
  α::Float64 # gain modulation
end
@inline function iofunction(x::Float64,p::NTQuadratic)
  if x<=0.0
    return 0.0
  else
    return x*x*p.α
  end
end
@inline function ioinv(y::Float64,p::NTQuadratic)
  y<=0.0 && return 0.0
  return sqrt(y/p.α)
end

struct NTStep <: NeuronType
  θ::Float64 # step boundary
end
@inline function iofunction(x::Float64,p::NTStep)
  return (x<p.θ ? zero(Float64) : one(Float64))
end

"""
    send_signal(conn::ConnectionStateRate)

Computes the input to postsynaptic population, given the current state of presynaptic population.
For a rate model, it applies the iofunction to the neuron potentials, gets the rate values
then multiplies rates by weights, adding the result to the input of the postsynaptic population.
"""
function forward_signal!(t::Real,dt::Real,pspost::PSRate,conn::BaseConnection,pspre::PSRate)
  # convert a state to rates r = iofun(u)
  r = pspre.alloc_r
  broadcast!(x->iofunction(x,pspre.neurontype),r,pspre.state_now)
  # multiply by weights, add to input of b .  input_b += W * r
  mul!(pspost.input,conn.weights,r,1,1)
  return nothing
end


push!(LOAD_PATH,"../src/") # load module from local directory
using PolaronMobility

const T=300

#Ts,Kμs, Hμs, FHIPμs, ks, Ms, As, Bs, Cs, Fs, Taus
#effectivemass=0.12 # the bare-electron band effective-mass.
# --> 0.12 for electrons and 0.15 for holes, in MAPI. See 2014 PRB.
# MAPI  4.5, 24.1, 2.25THz - 75 cm^-1 ; α=
println("Calculating polaron for T=$T for MAPIe params...")
MAPIe=polaronmobility(T, 4.5, 24.1, 2.25E12, 0.12)
#MAPIh=polaronmobility(T, 4.5, 24.1, 2.25E12, 0.15)

#function ImX(nurange,v,w,βred,α,ω,mb)

# Yup, this is a bit horrid.
v=MAPIe.v[1]
w=MAPIe.w[1]
βred=MAPIe.βred[1]
α=MAPIe.α[1]
ω=MAPIe.ω[1]
mb=MAPIe.mb[1]

nu=0:0.1:40
println("Integrating ImX for nu=$nu range...")
s=PolaronMobility.ImX(nu, v, w, βred,α,ω,mb)

println("Loading Plots for plotting...")
using Plots

plot( s.nu,s.ImX,label="ImX",
         markersize=3,marker=:downtriangle, xlab="nu (units Omega)",ylab="ImX")
xaxis!(:log10)
yaxis!(:log10)
savefig("MAPIe-ImX.png")
plot( s.nu,s.μ,label="mu",
         markersize=3,marker=:uptriangle, xlab="nu (units Omega)",ylab="Mob")
xaxis!(:log10)
yaxis!(:log10)

savefig("MAPIe-mu.png")

println("That's me!")


module WienerHopf
using ApproxFun, SingularIntegralEquations, RiemannHilbert, ForwardDiff

import ApproxFunBase: SegmentDomain, fromcanonical, tocanonical, angle,
                        reverseorientation, isambiguous, arclength,
                        leftendpoint, rightendpoint, complexlength,
                        AnyDomain, tocanonicalD, fromcanonicalD,
                        setcanonicaldomain, mappoint
import SingularIntegralEquations: stieltjes, stieltjesmoment!, undirected
import Base: convert, isempty, +, -, *, ==
import ForwardDiff: derivative
import RiemannHilbert: RiemannDual, finitepart,
            fpleftstieltjesmoment!, fprightstieltjesmoment!,
            collocationpoints, fpcauchymatrix, evaluationmatrix

export SqrtLine, collocationpoints, fpcauchymatrix, evaluationmatrix

"""
    SqrtLine{a}(c)

represents the line at angle `a` in the complex plane, centred at `c`.
This is encoded by a map from -1..1 that allows rapid expansion of
functions with sqrt-decay.
"""
struct SqrtLine{angle,T<:Number} <: SegmentDomain{T}
    center::T

    #TODO get this inner constructor working again.
    SqrtLine{angle,T}(c) where {angle,T} = new{angle,T}(c)
    SqrtLine{angle,T}() where {angle,T} = new{angle,T}(zero(T))
end

SqrtLine{a}(c::Number) where {a} = SqrtLine{a,typeof(c)}(c)
SqrtLine{a}() where {a} = SqrtLine{a,Float64}()
SqrtLine() = SqrtLine{false}()

angle(d::SqrtLine{a}) where {a} = a*π

reverseorientation(d::SqrtLine{true}) = SqrtLine{false}(d.center)
reverseorientation(d::SqrtLine{false}) = SqrtLine{true}(d.center)
reverseorientation(d::SqrtLine{a}) where {a} = SqrtLine{a-1}(d.center)

isambiguous(d::SqrtLine) = isnan(d.center)
convert(::Type{Domain{T}},d::SqrtLine{a}) where {a,T<:Number} = SqrtLine{a,T}(d.center)
convert(::Type{SqrtLine{a,T}},::AnyDomain) where {a,T<:Number} = SqrtLine{a,T}(NaN)
convert(::Type{IT},::AnyDomain) where {IT<:SqrtLine}=SqrtLine(NaN,NaN)

## Map interval

##TODO non-1 alpha,beta

isempty(::SqrtLine) = false

fourtoone(x) = (x+x^3)/(1-x^2)^2
ifourtoone(α) = if abs(α) ≤ 0.001
        α - 3α^3 + 22α^5 # Taylor series
    else
        (1 + sqrt(1 + 16α^2) - sqrt(2)sqrt(1 + sqrt(1 + 16α^2)))/(4α)
    end

function ifourtoone(k, α)
    x = ifourtoone(α)
    k == 1 && return x
    k == 2 && return inv(x)
    c = x + inv(x) - inv(α)
    k == 3 && return (-c+sqrt(c^2-4))/2
    k == 4 && return (-c-sqrt(c^2-4))/2
    throw(ArgumentError("$k"))
end

sqrtline_tocanonical(x) = ifourtoone(x)
sqrtline_fromcanonical(x) = fourtoone(x)

sqrtline_tocanonicalD(x) = derivative(ifourtoone,x)
sqrtline_fromcanonicalD(x) = derivative(fourtoone,x)

tocanonical(d::SqrtLine,x) = sqrtline_tocanonical(cis(-angle(d)).*(x-d.center))
tocanonical(d::SqrtLine{false},x) = sqrtline_tocanonical(x-d.center)
tocanonical(d::SqrtLine{true},x) = sqrtline_tocanonical(d.center-x)

tocanonicalD(d::SqrtLine,x) = cis(-angle(d)).*sqrtline_tocanonicalD(cis(-angle(d)).*(x-d.center))
tocanonicalD(d::SqrtLine{false},x) = sqrtline_tocanonicalD(x-d.center)
tocanonicalD(d::SqrtLine{true},x) = -sqrtline_tocanonicalD(d.center-x)

fromcanonical(d::SqrtLine,x) = cis(angle(d))*sqrtline_fromcanonical(x)+d.center
fromcanonical(d::SqrtLine{false},x) = sqrtline_fromcanonical(x)+d.center
fromcanonical(d::SqrtLine{true},x) = -sqrtline_fromcanonical(x)+d.center

fromcanonicalD(d::SqrtLine,x) = cis(angle(d))*sqrtline_fromcanonicalD(x)
fromcanonicalD(d::SqrtLine{false},x) = sqrtline_fromcanonicalD(x)
fromcanonicalD(d::SqrtLine{true},x) = -sqrtline_fromcanonicalD(x)

invfromcanonicalD(d::SqrtLine,x) = cis(-angle(d))*sqrtline_invfromcanonicalD(x)
invfromcanonicalD(d::SqrtLine{false},x) = sqrtline_invfromcanonicalD(x)
invfromcanonicalD(d::SqrtLine{true},x) = -sqrtline_invfromcanonicalD(x)

==(d::SqrtLine{a},m::SqrtLine{a}) where {a} = d.center == m.center

# algebra
*(c::Real,d::SqrtLine{false}) = SqrtLine{sign(c)>0 ? false : true}(isapprox(d.center,0) ? d.center : c*d.center,d.α,d.β)
*(c::Real,d::SqrtLine{true}) = SqrtLine{sign(c)>0 ? true : false}(isapprox(d.center,0) ? d.center : c*d.center,d.α,d.β)
*(c::Number,d::SqrtLine) = SqrtLine(isapprox(d.center,0) ? d.center : c*d.center,angle(d)+angle(c),d.α,d.β)
*(d::SqrtLine,c::Number) = c*d
for OP in (:+,:-)
    @eval begin
        $OP(c::Number,d::SqrtLine{a}) where {a} = SqrtLine{a}($OP(c,d.center),d.α,d.β)
        $OP(d::SqrtLine{a},c::Number) where {a} = SqrtLine{a}($OP(d.center,c),d.α,d.β)
    end
end

# algebra
arclength(d::SqrtLine) = Inf
leftendpoint(d::SqrtLine) = -Inf
rightendpoint(d::SqrtLine) = Inf
complexlength(d::SqrtLine) =Inf

# Sum over all inverses of fromcanonical, see [Olver,2014]

fpstieltjes(s,f,z) = finitepart(stieltjes(s,f,RiemannDual(z,1)))

function stieltjes(S::Space{<:SqrtLine},f,z)
    if domain(S) == SqrtLine()
        # TODO: rename tocanonical
        s = setcanonicaldomain(S)
        u = undirected(z)
        stieltjes(s,f,ifourtoone(1,z))+stieltjes(s,f,ifourtoone(2,u))+
            stieltjes(s,f,ifourtoone(3,u))+stieltjes(s,f,ifourtoone(4,u))-
            2fpstieltjes(s,f,1)-2fpstieltjes(s,f,-1)
    else
        stieltjes(setdomain(S,SqrtLine()),f,mappoint(domain(S),SqrtLine(),z))
    end
end

function stieltjesmoment!(ret,S::PolynomialSpace{<:SqrtLine},z,filter=identity)
    if domain(S) == SqrtLine()
        s = setcanonicaldomain(S)
        u = complex(undirected(z))
        tmp = similar(ret)
        stieltjesmoment!(ret, s, ifourtoone(1,z),filter)
        stieltjesmoment!(tmp, s, ifourtoone(2,u),filter); ret .+= tmp
        stieltjesmoment!(tmp, s, ifourtoone(3,u),filter); ret .+= tmp
        stieltjesmoment!(tmp, s, ifourtoone(4,u),filter); ret .+= tmp
        fpleftstieltjesmoment!(tmp, s); ret .-= 2 .* tmp
        fprightstieltjesmoment!(tmp, s); ret .-= 2 .* tmp
        ret
    else
        stieltjesmoment!(ret,setdomain(S,SqrtLine()),mappoint(domain(S),SqrtLine(),z),filter)
    end
end

fprightstieltjesmoment!(V, sp::PolynomialSpace{<:SqrtLine}) = fill!(V,0)
fpleftstieltjesmoment!(V, sp::PolynomialSpace{<:SqrtLine}) = fill!(V,0)

end # module


@testset "smoothing" begin

# Need to test non-box object, such as a sphere and see if subpixel smoothing generates
# the expected smoothed material parameters (instead of simply using kottke_input_simple or
# amean_param or hmean_param.)

@testset "smoothing, box with odd number of voxels" begin
    # Create a grid.
    isbloch = [true, true, true]
    # isbloch = [true, false, false]
    lprim = ([-1.5, -0.5, 0.5, 1.5], [-1.5, -0.5, 0.5, 1.5], [-1.5, -0.5, 0.5, 1.5])
    g3 = Grid(lprim, isbloch)
    N = g3.N

    # Create materials.
    εvac = 1.0
    vac = Material("Vacuum", ε=εvac)

    εdiel = 2.0
    diel = Material("Dielectric", ε=εdiel)

    # Create objects.
    dom_vac = Object(Cuboid(g3.bounds), vac)
    obj_diel = Object(Cuboid([0,0,0], [1,1,1]), diel)
    # obj_diel = Object(Ball([0,0,0], 1), diel)

    # Add objects.
    oind2shp = Shape3[]
    oind2εind = ParamInd[]
    oind2μind = ParamInd[]
    εind2ε = S²ComplexF3[]
    μind2μ = S²ComplexF3[]

    add_obj!(oind2shp, (oind2εind,oind2μind), (εind2ε,μind2μ), dom_vac, obj_diel)

    # Construct arguments and call assign_param!.
    N = g3.N
    ε3d = create_param_array(N)
    εxx_oind3d = create_oind_array(N)
    εyy_oind3d = create_oind_array(N)
    εzz_oind3d = create_oind_array(N)
    εoo_oind3d = create_oind_array(N)

    μ3d = create_param_array(N)
    μxx_oind3d = create_oind_array(N)
    μyy_oind3d = create_oind_array(N)
    μzz_oind3d = create_oind_array(N)
    μoo_oind3d = create_oind_array(N)

    boundft = SVec(EE,EE,EE)
    assign_param!(ε3d, (μxx_oind3d,μyy_oind3d,μzz_oind3d), oind2shp, oind2εind, εind2ε, ft2gt.(EE,boundft), g3.ghosted.τl, g3.isbloch)
    assign_param!(ε3d, tuple(μoo_oind3d), oind2shp, oind2εind, εind2ε, ft2gt.(EE,boundft), g3.ghosted.τl, g3.isbloch)
    assign_param!(μ3d, (εxx_oind3d,εyy_oind3d,εzz_oind3d), oind2shp, oind2μind, μind2μ, ft2gt.(HH,boundft), g3.ghosted.τl, g3.isbloch)
    assign_param!(μ3d, tuple(εoo_oind3d), oind2shp, oind2μind, μind2μ, ft2gt.(HH,boundft), g3.ghosted.τl, g3.isbloch)

    # To-do: test the sanity of the assigned param3d here.  It is relatively easy, and it was very helpful.

    # Perform smoothing.
    smooth_param!(ε3d, (εxx_oind3d,εyy_oind3d,εzz_oind3d), oind2shp, oind2εind, εind2ε, ft2gt.(EE,boundft), g3.l, g3.ghosted.l, g3.σ, g3.ghosted.∆τ)
    smooth_param!(ε3d, tuple(εoo_oind3d), oind2shp, oind2εind, εind2ε, ft2gt.(EE,boundft), g3.l, g3.ghosted.l, g3.σ, g3.ghosted.∆τ)

    ε3dred = view(ε3d, 1:N[1], 1:N[2], 1:N[3], 1:3, 1:3)

    # Construct an expected ε3d.
    ε3dexp = Array{ComplexF64}(undef,3,3,3,3,3)
    rvol = 0.5  # all nonzero rvol used in this test is 0.5
    εh = 1 / (rvol/εdiel + (1-rvol)/εvac)  # harmonic average
    εa = rvol*εdiel + (1-rvol)*εvac  # arithmetic average

    # Initialize ε3dexp.
    for k = 1:N[3], j = 1:N[2], i = 1:N[1]
        ε3dexp[i,j,k,:,:] = εvac * Matrix(I,3,3)
    end

    # Yee's cell at (2,2,2)
    nout = normalize([-1,-1,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,2,2,:,:] = εsm  # corner of (2,2,2) cell
    nout = normalize([0,-1,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,2,2,1,1] = εsm[1,1]  # x-edge of (2,2,2) cell
    nout = normalize([-1,0,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,2,2,2,2] = εsm[2,2]  # y-edge of (2,2,2) cell
    nout = normalize([-1,-1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,2,2,3,3] = εsm[3,3]  # z-edge of (2,2,2) cell

    # Yee's cell at (3,2,2)
    nout = normalize([1,-1,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,2,2,:,:] = εsm
    ε3dexp[3,2,2,1,1] = εvac
    nout = normalize([1,0,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,2,2,2,2] = εsm[2,2]
    nout = normalize([1,-1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,2,2,3,3] = εsm[3,3]

    # Yee's cell at (2,3,2)
    nout = normalize([-1,1,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,3,2,:,:] = εsm
    nout = normalize([0,1,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,3,2,1,1] = εsm[1,1]
    ε3dexp[2,3,2,2,2] = εvac
    nout = normalize([-1,1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,3,2,3,3] = εsm[3,3]

    # Yee's cell at (3,3,2)
    nout = normalize([1,1,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,3,2,:,:] = εsm
    ε3dexp[3,3,2,1,1] = εvac
    ε3dexp[3,3,2,2,2] = εvac
    nout = normalize([1,1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,3,2,3,3] = εsm[3,3]

    # Yee's cell at (2,2,3)
    nout = normalize([-1,-1,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,2,3,:,:] = εsm
    nout = normalize([0,-1,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,2,3,1,1] = εsm[1,1]
    nout = normalize([-1,0,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,2,3,2,2] = εsm[2,2]
    ε3dexp[2,2,3,3,3] = εvac

    # Yee's cell at (3,2,3)
    nout = normalize([1,-1,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,2,3,:,:] = εsm
    ε3dexp[3,2,3,1,1] = εvac
    nout = normalize([-1,0,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,2,3,2,2] = εsm[2,2]
    ε3dexp[3,2,3,3,3] = εvac

    # Yee's cell at (2,3,3)
    nout = normalize([-1,1,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,3,3,:,:] = εsm
    nout = normalize([0,1,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,3,3,1,1] = εsm[1,1]
    ε3dexp[2,3,3,2,2] = εvac
    ε3dexp[2,3,3,3,3] = εvac

    # Yee's cell at (3,3,3)
    nout = normalize([1,1,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,3,3,:,:] = εsm
    ε3dexp[3,3,3,1,1] = εvac
    ε3dexp[3,3,3,2,2] = εvac
    ε3dexp[3,3,3,3,3] = εvac

    for k = 1:N[3], j = 1:N[2], i = 1:N[1]
        # @info "(i,j,k) = $((i,j,k))"  # uncomment this to know where test fails
        @test @view(ε3dred[i,j,k,:,:]) ≈ @view(ε3dexp[i,j,k,:,:])
        # @info "$(@view(ε3dred[i,j,k,:,:]))"
        # @test issymmetric(@view(ε3dred[i,j,k,:,:]))
        A = @view(ε3dred[i,j,k,:,:])
        @test A ≈ transpose(A)
    end
end  # @testset "smoothing, box with odd number of voxels"

@testset "smoothing, box with even number of voxels" begin
    # Create a grid.
    lprim = ([-2,-1,0,1,2], [-2,-1,0,1,2], [-2,-1,0,1,2])
    isbloch = [true, true, true]
    g3 = Grid(lprim, isbloch)
    N = g3.N

    # Create materials.
    εvac = 1.0
    vac = Material("Vacuum", ε=εvac)

    εdiel = 2.0
    diel = Material("Dielectric", ε=εdiel)

    # Create objects.
    dom_vac = Object(Cuboid(g3.bounds), vac)
    obj_diel = Object(Cuboid([0,0,0], [2,2,2]), diel)
    # obj_diel = Object(Ball([0,0,0], 1), diel)

    # Add objects.
    oind2shp = Shape3[]
    oind2εind = ParamInd[]
    oind2μind = ParamInd[]
    εind2ε = S²ComplexF3[]
    μind2μ = S²ComplexF3[]

    add_obj!(oind2shp, (oind2εind,oind2μind), (εind2ε,μind2μ), dom_vac, obj_diel)

    # Construct arguments and call assign_param!.
    N = g3.N
    ε3d = create_param_array(N)
    εxx_oind3d = create_oind_array(N)
    εyy_oind3d = create_oind_array(N)
    εzz_oind3d = create_oind_array(N)
    εoo_oind3d = create_oind_array(N)

    μ3d = create_param_array(N)
    μxx_oind3d = create_oind_array(N)
    μyy_oind3d = create_oind_array(N)
    μzz_oind3d = create_oind_array(N)
    μoo_oind3d = create_oind_array(N)

    boundft = SVec(EE,EE,EE)
    assign_param!(ε3d, (μxx_oind3d,μyy_oind3d,μzz_oind3d), oind2shp, oind2εind, εind2ε, ft2gt.(EE,boundft), g3.ghosted.τl, g3.isbloch)
    assign_param!(ε3d, tuple(μoo_oind3d), oind2shp, oind2εind, εind2ε, ft2gt.(EE,boundft), g3.ghosted.τl, g3.isbloch)
    assign_param!(μ3d, (εxx_oind3d,εyy_oind3d,εzz_oind3d), oind2shp, oind2μind, μind2μ, ft2gt.(HH,boundft), g3.ghosted.τl, g3.isbloch)
    assign_param!(μ3d, tuple(εoo_oind3d), oind2shp, oind2μind, μind2μ, ft2gt.(HH,boundft), g3.ghosted.τl, g3.isbloch)

    # To-do: test the sanity of the assigned param3d here.  It is relatively easy, and it was very helpful.

    # Perform smoothing.
    smooth_param!(ε3d, (εxx_oind3d,εyy_oind3d,εzz_oind3d), oind2shp, oind2εind, εind2ε, ft2gt.(EE,boundft), g3.l, g3.ghosted.l, g3.σ, g3.ghosted.∆τ)
    smooth_param!(ε3d, tuple(εoo_oind3d), oind2shp, oind2εind, εind2ε, ft2gt.(EE,boundft), g3.l, g3.ghosted.l, g3.σ, g3.ghosted.∆τ)

    ε3dred = view(ε3d, 1:N[1], 1:N[2], 1:N[3], 1:3, 1:3)

    # Construct an expected ε3d.
    ε3dexp = Array{ComplexF64}(undef,4,4,4,3,3)
    rvol = 0.5  # all nonzero rvol used in this test is 0.5
    εh = 1 / (rvol/εdiel + (1-rvol)/εvac)  # harmonic average
    εa = rvol*εdiel + (1-rvol)*εvac  # arithmetic average

    # Initialize ε3dexp.
    for k = 1:N[3], j = 1:N[2], i = 1:N[1]
        ε3dexp[i,j,k,:,:] = εvac * Matrix(I,3,3)
    end

    ## k = 2
    # Yee's cell at (2,2,2)
    nout = normalize([-1,-1,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,2,2,:,:] = εsm  # corner of (2,2,2) cell
    nout = normalize([0,-1,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,2,2,1,1] = εsm[1,1]  # x-edge of (2,2,2) cell
    nout = normalize([-1,0,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,2,2,2,2] = εsm[2,2]  # y-edge of (2,2,2) cell
    nout = normalize([-1,-1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,2,2,3,3] = εsm[3,3]  # z-edge of (2,2,2) cell

    # Yee's cell at (3,2,2)
    nout = normalize([0,-1,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,2,2,:,:] = εsm
    nout = normalize([0,-1,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,2,2,1,1] = εsm[1,1]
    nout = normalize([0,0,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,2,2,2,2] = εsm[2,2]
    nout = normalize([0,-1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,2,2,3,3] = εsm[3,3]

    # Yee's cell at (4,2,2)
    nout = normalize([1,-1,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[4,2,2,:,:] = εsm
    ε3dexp[4,2,2,1,1] = εvac
    nout = normalize([1,0,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[4,2,2,2,2] = εsm[2,2]
    nout = normalize([1,-1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[4,2,2,3,3] = εsm[3,3]

    # Yee's cell at (2,3,2)
    nout = normalize([-1,0,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,3,2,:,:] = εsm
    nout = normalize([0,0,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,3,2,1,1] = εsm[1,1]
    nout = normalize([-1,0,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,3,2,2,2] = εsm[2,2]
    nout = normalize([-1,0,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,3,2,3,3] = εsm[3,3]

    # Yee's cell at (3,3,2)
    nout = normalize([0,0,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,3,2,:,:] = εsm
    nout = normalize([0,0,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,3,2,1,1] = εsm[1,1]
    nout = normalize([0,0,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,3,2,2,2] = εsm[2,2]
    ε3dexp[3,3,2,3,3] = εdiel

    # Yee's cell at (4,3,2)
    nout = normalize([1,0,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[4,3,2,:,:] = εsm
    ε3dexp[4,3,2,1,1] = εvac
    nout = normalize([1,0,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[4,3,2,2,2] = εsm[2,2]
    nout = normalize([1,0,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[4,3,2,3,3] = εsm[3,3]

    # Yee's cell at (2,4,2)
    nout = normalize([-1,1,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,4,2,:,:] = εsm
    nout = normalize([0,1,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,4,2,1,1] = εsm[1,1]
    ε3dexp[2,4,2,2,2] = εvac
    nout = normalize([-1,1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,4,2,3,3] = εsm[3,3]

    # Yee's cell at (3,4,2)
    nout = normalize([0,1,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,4,2,:,:] = εsm
    nout = normalize([0,1,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,4,2,1,1] = εsm[1,1]
    ε3dexp[3,4,2,2,2] = εvac
    nout = normalize([0,1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,4,2,3,3] = εsm[3,3]

    # Yee's cell at (4,4,2)
    nout = normalize([1,1,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[4,4,2,:,:] = εsm
    ε3dexp[4,4,2,1,1] = εvac
    ε3dexp[4,4,2,2,2] = εvac
    nout = normalize([1,1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[4,4,2,3,3] = εsm[3,3]

    ## k = 3
    # Yee's cell at (2,2,3)
    nout = normalize([-1,-1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,2,3,:,:] = εsm
    nout = normalize([0,-1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,2,3,1,1] = εsm[1,1]
    nout = normalize([-1,0,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,2,3,2,2] = εsm[2,2]
    nout = normalize([-1,-1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,2,3,3,3] = εsm[3,3]

    # Yee's cell at (3,2,3)
    nout = normalize([0,-1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,2,3,:,:] = εsm
    nout = normalize([0,-1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,2,3,1,1] = εsm[1,1]
    ε3dexp[3,2,3,2,2] = εdiel
    nout = normalize([0,-1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,2,3,3,3] = εsm[3,3]

    # Yee's cell at (4,2,3)
    nout = normalize([1,-1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[4,2,3,:,:] = εsm
    ε3dexp[4,2,3,1,1] = εvac
    nout = normalize([1,0,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[4,2,3,2,2] = εsm[2,2]
    nout = normalize([1,-1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[4,2,3,3,3] = εsm[3,3]

    # Yee's cell at (2,3,3)
    nout = normalize([-1,0,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,3,3,:,:] = εsm
    ε3dexp[2,3,3,1,1] = εdiel
    nout = normalize([-1,0,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,3,3,2,2] = εsm[2,2]
    nout = normalize([-1,0,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,3,3,3,3] = εsm[3,3]

    # Yee's cell at (3,3,3)
    ε3dexp[3,3,3,1,1] = εdiel
    ε3dexp[3,3,3,2,2] = εdiel
    ε3dexp[3,3,3,3,3] = εdiel

    # Yee's cell at (4,3,3)
    nout = normalize([1,0,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[4,3,3,:,:] = εsm
    ε3dexp[4,3,3,1,1] = εvac
    nout = normalize([1,0,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[4,3,3,2,2] = εsm[2,2]
    nout = normalize([1,0,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[4,3,3,3,3] = εsm[3,3]

    # Yee's cell at (2,4,3)
    nout = normalize([-1,1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,4,3,:,:] = εsm
    nout = normalize([0,1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,4,3,1,1] = εsm[1,1]
    ε3dexp[2,4,3,2,2] = εvac
    nout = normalize([-1,1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,4,3,3,3] = εsm[3,3]

    # Yee's cell at (3,4,3)
    nout = normalize([0,1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,4,3,:,:] = εsm
    nout = normalize([0,1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,4,3,1,1] = εsm[1,1]
    ε3dexp[3,4,3,2,2] = εvac
    nout = normalize([0,1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,4,3,3,3] = εsm[3,3]

    # Yee's cell at (4,4,3)
    nout = normalize([1,1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[4,4,3,:,:] = εsm
    ε3dexp[4,4,3,1,1] = εvac
    ε3dexp[4,4,3,2,2] = εvac
    nout = normalize([1,1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[4,4,3,3,3] = εsm[3,3]

    ## k = 4
    # Yee's cell at (2,2,4)
    nout = normalize([-1,-1,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,2,4,:,:] = εsm
    nout = normalize([0,-1,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,2,4,1,1] = εsm[1,1]
    nout = normalize([-1,0,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,2,4,2,2] = εsm[2,2]
    ε3dexp[2,2,4,3,3] = εvac

    # Yee's cell at (3,2,4)
    nout = normalize([0,-1,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,2,4,:,:] = εsm
    nout = normalize([0,-1,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,2,4,1,1] = εsm[1,1]
    nout = normalize([0,0,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,2,4,2,2] = εsm[2,2]
    ε3dexp[3,2,4,3,3] = εvac

    # Yee's cell at (4,2,4)
    nout = normalize([1,-1,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[4,2,4,:,:] = εsm
    ε3dexp[4,2,4,1,1] = εvac
    nout = normalize([-1,0,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[4,2,4,2,2] = εsm[2,2]
    ε3dexp[4,2,4,3,3] = εvac

    # Yee's cell at (2,3,4)
    nout = normalize([-1,0,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,3,4,:,:] = εsm
    nout = normalize([0,0,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,3,4,1,1] = εsm[1,1]
    nout = normalize([-1,0,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,3,4,2,2] = εsm[2,2]
    ε3dexp[2,3,4,3,3] = εvac

    # Yee's cell at (3,3,4)
    nout = normalize([0,0,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,3,4,:,:] = εsm
    nout = normalize([0,0,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,3,4,1,1] = εsm[1,1]
    nout = normalize([0,0,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,3,4,2,2] = εsm[2,2]
    ε3dexp[3,3,4,3,3] = εvac

    # Yee's cell at (4,3,4)
    nout = normalize([1,0,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[4,3,4,:,:] = εsm
    ε3dexp[4,3,4,1,1] = εvac
    nout = normalize([1,0,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[4,3,4,2,2] = εsm[2,2]
    ε3dexp[4,3,4,3,3] = εvac

    # Yee's cell at (2,4,4)
    nout = normalize([-1,1,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,4,4,:,:] = εsm
    nout = normalize([0,1,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,4,4,1,1] = εsm[1,1]
    ε3dexp[2,4,4,2,2] = εvac
    ε3dexp[2,4,4,3,3] = εvac

    # Yee's cell at (3,4,4)
    nout = normalize([0,1,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,4,4,:,:] = εsm
    nout = normalize([0,1,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,4,4,1,1] = εsm[1,1]
    ε3dexp[3,4,4,2,2] = εvac
    ε3dexp[3,4,4,3,3] = εvac

    # Yee's cell at (4,4,4)
    nout = normalize([1,1,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[4,4,4,:,:] = εsm
    ε3dexp[4,4,4,1,1] = εvac
    ε3dexp[4,4,4,2,2] = εvac
    ε3dexp[4,4,4,3,3] = εvac

    for k = 1:N[3], j = 1:N[2], i = 1:N[1]
        # @info "(i,j,k) = $((i,j,k))"  # uncomment this to know where test fails
        @test @view(ε3dred[i,j,k,:,:]) ≈ @view(ε3dexp[i,j,k,:,:])
        # @test issymmetric(@view(ε3dred[i,j,k,:,:]))
        A = @view(ε3dred[i,j,k,:,:])
        @test A ≈ transpose(A)
    end
end  # @testset "smoothing, box with even number of voxels"

# Not sure if this test was completed.  It was commented out.  When uncommented, the test failed.
# @testset "smoothing, inspired from SALT3D/test/usage2d_phcwg_test_sym" begin
#     # Create a grid.
#     lprim = ([-0.5,0.5], [-2,-1,0,1,2], [-0.5,0.5])
#     isbloch = [true, true, true]
#     g3 = Grid(lprim, isbloch)
#     N = g3.N
#
#     # Create materials.
#     εvac = 1.0
#     vac = EncodedMaterial(PRIM, Material("Vacuum", ε=εvac))
#
#     εdiel = 2.0
#     diel = EncodedMaterial(PRIM, Material("Dielectric", ε=εdiel))
#
#     # Create objects.
#     dom_vac = Object(Cuboid(g3.bounds), vac)
#     obj_diel = Object(Cuboid([0,0,0], [1,2,1]), diel)
#     # obj_diel = Object(Ball([0,0,0], 1), diel)
#
#     # Add objects.
#     ovec = Object{3}[]
#     paramset = (S²ComplexF3[], S²ComplexF3[])
#     add_obj!(ovec, paramset, dom_vac, obj_diel)
#
#     # Construct arguments and call assign_param!.
#     param3d = create_param_array(N)
#     obj3d = create_p_storage(Object{3}, N)
#     pind3d = create_p_storage(ParamInd, N)
#     oind3d = create_p_storage(ObjInd, N)
#
#     assign_param!(param3d, obj3d, pind3d, oind3d, ovec, g3.ghosted.τl, g3.isbloch)
#     smooth_param!(param3d, obj3d, pind3d, oind3d, g3.l, g3.ghosted.l, g3.σ, g3.ghosted.∆τ)
#
#     ε3d = view(param3d[nPR], 1:N[nX], 1:N[nY], 1:N[nZ], 1:3, 1:3)
#
#     # Construct an expected ε3d.
#     ε3dexp = Array{ComplexF64}(undef,4,4,4,3,3)
#     rvol = 0.5  # all nonzero rvol used in this test is 0.5
#     εh = 1 / (rvol/εdiel + (1-rvol)/εvac)  # harmonic average
#     εa = rvol*εdiel + (1-rvol)*εvac  # arithmetic average
#
#     # Initialize ε3dexp.
#     for k = 1:4, j = 1:4, i = 1:4
#         ε3dexp[i,j,k,:,:] = εvac * Matrix(I,3,3)
#     end
#
#     # Yee's cell at (1,2,1)
#     nout = normalize([-1,-1,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[1,2,1,:,:] = εsm  # corner of (2,2,2) cell
#     nout = normalize([0,-1,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[1,2,1,1,1] = εsm[1,1]  # x-edge of (2,2,2) cell
#     nout = normalize([-1,0,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[1,2,1,2,2] = εsm[2,2]  # y-edge of (2,2,2) cell
#     nout = normalize([-1,-1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[1,2,1,3,3] = εsm[3,3]  # z-edge of (2,2,2) cell
#
#     # Yee's cell at (3,2,2)
#     nout = normalize([1,-1,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,2,2,:,:] = εsm
#     ε3dexp[3,2,2,1,1] = εvac
#     nout = normalize([1,0,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,2,2,2,2] = εsm[2,2]
#     nout = normalize([1,-1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,2,2,3,3] = εsm[3,3]
#
#     # Yee's cell at (2,3,2)
#     nout = normalize([-1,1,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,3,2,:,:] = εsm
#     nout = normalize([0,1,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,3,2,1,1] = εsm[1,1]
#     ε3dexp[2,3,2,2,2] = εvac
#     nout = normalize([-1,1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,3,2,3,3] = εsm[3,3]
#
#     # Yee's cell at (3,3,2)
#     nout = normalize([1,1,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,3,2,:,:] = εsm
#     ε3dexp[3,3,2,1,1] = εvac
#     ε3dexp[3,3,2,2,2] = εvac
#     nout = normalize([1,1,0]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,3,2,3,3] = εsm[3,3]
#
#     # Yee's cell at (2,2,3)
#     nout = normalize([-1,-1,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,2,3,:,:] = εsm
#     nout = normalize([0,-1,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,2,3,1,1] = εsm[1,1]
#     nout = normalize([-1,0,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,2,3,2,2] = εsm[2,2]
#     ε3dexp[2,2,3,3,3] = εvac
#
#     # Yee's cell at (3,2,3)
#     nout = normalize([1,-1,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,2,3,:,:] = εsm
#     ε3dexp[3,2,3,1,1] = εvac
#     nout = normalize([-1,0,-1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,2,3,2,2] = εsm[2,2]
#     ε3dexp[3,2,3,3,3] = εvac
#
#     # Yee's cell at (2,3,3)
#     nout = normalize([-1,1,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,3,3,:,:] = εsm
#     nout = normalize([0,1,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[2,3,3,1,1] = εsm[1,1]
#     ε3dexp[2,3,3,2,2] = εvac
#     ε3dexp[2,3,3,3,3] = εvac
#
#     # Yee's cell at (3,3,3)
#     nout = normalize([1,1,1]); P = nout*nout'; εsm = εh * P + εa * (I-P); ε3dexp[3,3,3,:,:] = εsm
#     ε3dexp[3,3,3,1,1] = εvac
#     ε3dexp[3,3,3,2,2] = εvac
#     ε3dexp[3,3,3,3,3] = εvac
#
#     for k = 1:3, j = 1:3, i = 1:3
#         # @info "(i,j,k) = $((i,j,k))"  # uncomment this to know where test fails
#         @test ε3d[i,j,k,:,:] ≈ ε3dexp[i,j,k,:,:]
#     end
# end

# Need to include a test with three objects with mat1, mat2, mat1.  In the wrong code I
# assumed that I could find the foreground object by sorting voxel corners in terms of pind,
# but this test would show that that is not the case.

end  # @testset "smoothing"


# Test to see if the rejection algorithm is working !

push!(LOAD_PATH, "../models")
push!(LOAD_PATH, "../")
using SparseInverseProblems
using SuperResModels
using TestCases
using Utils

using PyCall
@pyimport numpy as np
@pyimport matplotlib.pyplot as plt

### Static parameters and container for use.
f_c = 20
x_max = 1.0
num_x = 10*f_c
filter_x =  ones(2*f_c+1);
model_static = SuperResModels.Fourier1d(f_c, x_max, filter_x, num_x)
### Dynamic parameters and container for use.
K = 2
tau = 1.0/(K*1.0)
# the maximum velocity on the domain is given by x_max/2 + K tau v_max = 1
v_max = 0.5
num_v = 10
model_dynamic = SuperResModels.DynamicFourier1d(model_static, v_max, tau, K, num_v*K)

# How particles are generated
test_case = () -> TestCases.cloud_1d_full(x_max, v_max, 1, 1, K, tau, rand(4:10))

### We generate particles and we evaluate their separation
N = 10000
separations = zeros(N)
dataFolder = "../data"
for i = 1:N
    (thetas, weights) = Utils.Rejection_sampling(test_case,dataFolder, K,tau,x_max)
    separations[i] = Utils.separation_val(thetas,K,tau,x_max)
end

plt.figure()
plt.hist(separations,100)
plt.show()


#=
 Algorithms for triple-word arithmetic
 Nicolas Fabiano, Jean-Michel Muller, Joris Picot
=#

function vec_sum(x0::T, x1::T, x2::T) where {T}
    s1, e2 = two_sum(x1, x2)
    s0, e1 = two_sum(x0, s1)
    return s0,e1,e2
end

function vecsum(x0::T,x1::T,x2::T) where {T}
   x0 === 0.0 && return (two_sum(x1, x2)..., 0.0)
   s1, e2 = two_sum(x1, x2)
   e0, e1 = two_sum(x0, s1)
   return (e0, e1, e2)
end

function vseb(a0::T, a1::T, a2::T, a3::T, a4::T, a5::T) where {T}
    a2 === 0.0 && return (a0, a1, a2)
	r0, e1 = two_sum(a0, a1)
	if e1 != 0.0
	  y0 = r0
	else
	  e1 = r0
	end
	y1, y2  = two_sum(e1, a2)
	return (y0, y1, y2)
end

function vseb(a0::T, a1::T, a2::T) where {T}
    a2 === 0.0 && return (a0, a1, a2)
	r0, e1 = two_sum(a0, a1)
	if e1 != 0.0
	  y0 = r0
	else
	  e1 = r0
	end
	y1, y2  = two_sum(e1, a2)
	return (y0, y1, y2)
end

function vecseb(x0::T, x1::T, x2::T) where {T}
   a0, a1, a2 = vecsum(x0, x1, x2)
   return vseb(a0, a1, a2)
end

function tripleword(a::T, b::T, c::T) where {T}
    d0, d1 = two_sum(a,b)
    # e0, e1, e2 = vecsum(d0, d1, c)
    s1, e2 = two_sum(d1, c)
    e0, e1 = two_sum(d0, s1)
    # s0, s1, s2 = vseb(e0, e1, e2)
    r0, e1 = two_sum(e0, e1)
    if e1 != 0.0
	s0 = r0
    else
	e1 = r0
    end
    s1, s2  = two_sum(e1, e2)
    return (s0, s1, s2)
end

using SetRounding

function Float64(x0::Float64, x1::Float64, x2::Float64)
    s, e = two_hilo_sum(x0, x1+x1)
    if e !== 0.0
        y = x0 + x1
    elseif x2 > 0.0
        setrounding(Float64, RoundUp)
        y = x0 + x1
        setrounding(Float64, RoundNearest)
    elseif x2 < 0.0
        setrounding(Float64, RoundDown)
        y = x0 + x1
        setrounding(Float64, RoundNearest)
    else
        y = x0 + x1
    end
    return y
end

@inline max_min(x,y) = ifelse(abs(y) < abs(x), (x,y) , (y,x))

function maxtomin(a::T, b::T, c::T) where {T}
    b, c = max_min(b, c)
    a, c = max_min(a, c)
    a, b = max_min(a, b)
    return a, b, c
end

function vec_sum(x1::T, x2::T, x3::T) where {T}
    s3 = x3
    s2, e3 = two_sum(x2, s3)
    s1, e2 = two_sum(x1, s2)
    return s1,e2,e3
end

function vecsum_errbranch(x::NTuple{3,T}) where {T}
    y = r = e = zeros(T, 3)
    j = 1
    e[1] = x[1]
    for i = 1:1
        r[i], t = two_sum(e[i], x[i+1])
        if t !== zero(T)
            y[j] = r[i]
            e[i+1] = t
            j += 1
        else
            e[i+1] = r[i]
        end
    end
    y[j], y[j+1] = two_sum(e[3], x[4])
    return y
end

function fast_vecsum_errbranch(x1::T,x2::T,x3::T) where {T}
    y = zeros(T, 3)
    j = 1
    # e[1] = x1
    # i = 1
    r, t = two_sum(x1, x2)
    if t !== zero(T)
       y[j] = r
       e = t
       j += 1
    else
       e = r
    end

    y[j], y[j+1] = two_sum(e, x3)
    return y
end

function tripleword(x1::T, x2::T, x3::T) where {T}
    a1, a2 = two_sum(x1, x2)
    c1, c2 = two_sum(a1, x3)
    e1to3 = vec_sum(c1,c2,a2)
    y = vecsum_errbranch(e1to3)
    return (y...,)
end

@inline function fast_tripleword(x1::T, x2::T, x3::T) where {T}
    a,b,c,d = maxtomin(x1,x2,x3)
    return fast_vecsum_errbranch(a,b,c)
end


push!(LOAD_PATH,pwd()*"/src") #using local modules
using Revise #enables to change the source code and not having to reload all modules or kill the REPL
using Plots
using SparseArrays

using QuadratureRules
#using GreenDeltaStar
using MeshFactory
using Assembly

#ellipse
x0=2.0; y0=0.0; a=0.1; b=0.5
n=30
BEMesh=ellipseBE(x0,y0,a,b,n)

#p=plot(BEMesh.coordinates[:,1],BEMesh.coordinates[:,2], aspect_ratio=:equal)
#display(p)

#quadrature rules
quadRuleA=gauleg(-1,1,3)
#quadRuleA=gauleg(-1,1,5)
quadRuleB=gauleg(-1,1,5)

#assemble BEM matrices
#@time K,V=assemPotentialKandV(BEMesh,quadRuleA,quadRuleB)
@time K,V=assemPotentialKandVwithDuffy(BEMesh,quadRuleA,quadRuleB)
#@show K
#@show V

#sparse matrix
Kspmat=sparse(K.I,K.J,K.E,n,n)
#@show Kspmat

Vspmat=sparse(V.I,V.J,V.E) #no need to set the dimension of the matrix, sparse seeks the largest i and j
#@show Vspmat

#coils
quadRule_r=gauleg(-1,1,4)
quadRule_z=gauleg(-1,1,4)
quadRule=gauleg(-1,1,2)

r0=[3,3,0.5]; z0=[3,-3,0]; dr=[0.25,0.25,0.2]; dz=[0.25,0.25,2]
Ic=[5.0e5, 5.0e5,-5.0e6]
#Ic=zeros(3)

Jcrhs=zeros(n)

for i=1:size(Ic,1)
	rhs=assemblyRhsRectangleCoil(r0[i],z0[i],dr[i],dz[i],Ic[i],quadRule_r, quadRule_z, BEMesh, quadRule)
	global Jcrhs=Jcrhs+rhs
end

@show Jcrhs

##boundary integral equation K.f-V.q = Jc with f P1 Dirichlet and q P0 Neumann
f=2.0*ones(n) # Dirichlet given
@time q=Vspmat\(Kspmat*f - Jcrhs) #compute Neumann
#boundary integral equation K.f-V.q=0 with f P1 Dirichlet and q P0 Neumann
#q=ones(n)*1.0e6 # Neumann given
#f=Kspmat\(Vspmat*q) #compute Dirichlet

@show f
@show q

##
##
##xr=[2,2.02,1.92]; xz=[0,0,0]
###psi=evalPsiFromBEM(BEMesh,quadRule,f,q,xr,xz)
##psi=@.evalPsiFromBEM((BEMesh,),(quadRule,),(f,),(q,),xr,xz)
###@show psi

ln=50
xr=range(0.1,6,length=ln)
#xr=range(1.5,2.5,length=ln)
xr_rep=repeat(xr,ln)

xz=range(-5,5,length=ln)
#xz=range(-1.5,1.5,length=ln)
xz_rep=reshape(repeat(xz,1,ln)',ln*ln)

@time evalBEM=@.evalPsiFromBEM((BEMesh,),(quadRule,),(f,),(q,),xr_rep,xz_rep)
psiBEM=@.getindex(evalBEM,:psi)
#@show psiBEM

# points inside ellipse are included in plot but psi is wrong of course
#pBEM=contourf(xr,xz,psiBEM)
#display(pBEM)

## coils
#quadRule_r=gauleg(-1,1,4)
#quadRule_z=gauleg(-1,1,4)
#
#r0=[3,3,0.5]; z0=[3,-3,0]; dr=[0.25,0.25,0.2]; dz=[0.25,0.25,2]
#Ic=[5.0e5, 5.0e5,-5.0e6]

#xr=4; xz=4;
#psiC=evalPsiFromRectangleCoil(r0,z0,dr,dz,Ic,quadRule_r,quadRule_z,xr,xz)

psiC=zeros(size(xr_rep,1))

for i=1:size(Ic,1)
	local evalC=@.evalPsiFromRectangleCoil((r0[i],),(z0[i],),(dr[i],),(dz[i],),(Ic[i],),(quadRule_r,),(quadRule_z,),xr_rep,xz_rep)
	global psiC=psiC+@.getindex(evalC,:psi)
end
#@show psiC

#pC=contourf(xr,xz,psiC)
#display(pC)

psi=psiBEM+psiC
@time p=contourf(xr,xz,psi)
#p=contour(xr,xz,psi)
display(p)

dl=0.01
xr=[x0-a-dl,x0+a+dl,x0,x0]; xz=[y0,y0,y0+b+dl,y0-b-dl]
evalBEM=@.evalPsiFromBEM((BEMesh,),(quadRule,),(f,),(q,),xr,xz,derivative=0,) #trick to broadcast, "fixed" arguments are seen as tuples so almost a scalar
psiBEM=@.getindex(evalBEM,:psi)

psiC=zeros(size(xr,1))
for i=1:size(Ic,1)
	local evalC=@.evalPsiFromRectangleCoil((r0[i],),(z0[i],),(dr[i],),(dz[i],),(Ic[i],),(quadRule_r,),(quadRule_z,),xr,xz)
	global psiC=psiC+@.getindex(evalC,:psi)
end
psi=psiBEM+psiC
@show psi

println("This is the end")
readline()


using ValueInferenceTools
using ValueInferenceTools: to_sign
using Test
using Random

@testset "inferenceaccumulator" begin
    prob = TaskStats(0.9, 0.3)
    rews = [true, true, false, false, true]
    side = [true, true ,true, false, false]

    ratios = zeros(length(rews))
    for i in 2:length(rews)
        term = (ratios[i-1]+prob.dpl)/(1-prob.dpl)
        side[i] == side[i-1] || (term = 1/term)
        ratios[i] = rews[i] ? 0.0 : term/(1-prob.rwd)
    end
    vals = @. $(prob.rwd) * (1 - 2 / (ratios ^ (-to_sign(side)) + 1))

    @test vals ≈ accumulatefromdata(InferenceAccumulator(prob), rews, side)

    Random.seed!(4)
    rews = rand(Bool, 100)
    side = rand(Bool, 100)

    ratios = zeros(length(rews))
    for i in 2:length(rews)
        term = (ratios[i-1]+prob.dpl)/(1-prob.dpl)
        side[i] == side[i-1] || (term = 1/term)
        ratios[i] = rews[i] ? 0.0 : term/(1-prob.rwd)
    end
    vals = @. $(prob.rwd) * (1 - 2 / (ratios ^ (-to_sign(side)) + 1))
    @test vals ≈ accumulatefromdata(InferenceAccumulator(prob), rews, side)
end


import Base.norm

function perfTest(N = 100000)
    start = time()
    x = Variable(N)
    c = ones(N, 1)
    p = Problem(:minimize, dot(c, x), [c <= x, x <= 2 * c])
    println(time() - start)
    solve!(p)
    return println(time() - start)
end

function test()
    start = time()
    N = 200
    x = Variable(1)
    y = Variable(N, N)
    c = ones(N, 1)
    p = Problem(:minimize, c' * (y + x) * c, [x >= 3, 2 * y >= 0, y <= x])
    println(time() - start)
    solve!(p)
    println(p.optval)
    return println(time() - start)
end

# Assuming speed of calling randn, sprandn, etc is negligible for now...
# This is untrue. We'll soon generate data files.
# Also we'll need lucky data for this to be feasible.
# test2(10, 15, 10); sometimes gets feasible, bounded problems
function test2(m = 1000, n = 1000, q = 1000)
    x = Variable(n)
    A = randn(m, n)
    b = randn(m)
    C = randn(q, n)
    d = C * randn(n)
    c = randn(n)
    p = Problem(:minimize, c' * x, [A * x <= b, C * x == d])
    solve!(p)
    println(p.optval)
    return p
end

# test2sparse(10,10,5,0.4); sometimes gets bounded, feasible problems
function test2sparse(m = 1000, n = 1000, q = 1000, p = 0.1)
    x = Variable(n)
    A = sprandn(m, n, p)
    b = randn(m)
    C = sprandn(q, n, p)
    d = C * randn(n)
    c = randn(n)
    p = Problem(:minimize, c' * x, [A * x <= b, C * x == d])
    solve!(p)
    println(p.optval)
    return p
end

# http://see.stanford.edu/materials/lsocoee364a/hw4sol.pdf
# close to optimal with 10, 10
function test3(m = 1000, n = 1000)
    t = Variable(1)
    A = randn(m, n)
    x = randn(n) # TODO: The more interesting problem is to make x also a variable
    # but then we don't have the solution analytically
    # we can compare it to minimize norm(A*x-b, 'inf') in Matlab with variable x
    b = randn(m)
    p = Problem(
        :minimize,
        t,
        [A * x - b <= t * ones(m, 1), -t * ones(m, 1) <= A * x - b],
    )
    solve!(p)
    println(p.optval)
    soln = Base.norm(A * x - b, Inf)
    println("Solution and deviation from solution")
    println(soln)
    println(p.optval - soln)
    return p
end

# http://see.stanford.edu/materials/lsocoee364a/hw4sol.pdf
# we get close to optimal with 10, 10
function test4(m = 1000, n = 1000)
    s = Variable(m)
    x = randn(n) # TODO: This is also more interesting with x = Variable(n)
    # We can compare it to minimize norm(A*x-b, 1) in Matlab with variable x
    A = randn(m, n)
    b = randn(m)
    p = Problem(:minimize, sum(s), [A * x - b <= s, A * x - b >= -s])
    solve!(p)
    return p
end