"""
    Xtal.lattice_sanity_check(M::AbstractMatrix)

Runs checks on a matrix intended to represent basis vectors of a crystal unit cell. Returns
nothing, but warns if the cell vectors form a left-handed coordinate system, and throws an
`AssertionError` if the cell vectors are not linearly independent.
"""
@inline function lattice_sanity_check(M::AbstractMatrix{<:Real})
    # Skip this check for lattices that are zero (meaning unspecified basis)
    if iszero(M)
        return nothing
    end
    @assert det(M) != 0 string(
        "cell vectors are not linearly independent.\n",
        "Matrix contents: ", M
    )
    det(M) < 0  && @warn "cell vectors form a left-handed coordinate system."
    return nothing
end

# TODO: can we use views on vectors of vectors to get a matrix?
function lattice_sanity_check(vs::AbstractVector{<:AbstractVector{<:Real}})
    return lattice_sanity_check(hcat(vs...))
end

"""
    BasisVectors{D}

Collection of `D` basis vectors spanning `D`-dimensional space.

Internally, a `BasisVectors{D}` is represented as an `SVector{D,SVector{D,Float64}}`. This avoids a
length declaration that would be required by an `SMatrix`.

`BasisVectors` can be indexed similarly to a matrix. However, there is one major difference:
indexing with a single value returns an `SVector{D,Float64}`, not a value as would happen with most
subtypes of `AbstractMatrix`.
"""
struct BasisVectors{D}
    vs::SVector{D,SVector{D,Float64}}
    # TODO: there's probably a nicer way to handle this, but whatever
    # Inner constructor for all matrices
    function BasisVectors{D}(M::AbstractMatrix{<:Real}) where D
        lattice_sanity_check(M)
        return new{D}([M[:,n] for n in 1:size(M,2)])
    end
    # And for all vectors of vectors
    function BasisVectors{D}(M::AbstractVector{AbstractVector{<:Real}}) where D
        lattice_sanity_check(M)
        return new{D}([M[:,n] for n in 1:size(M,2)])
    end
    # If SVectors are used directly, this one can be called
    function BasisVectors(vs::SVector{D,<:SVector{D,<:Real}}) where D
        lattice_sanity_check(vs)
        return new{D}(vs)
    end
    # Same for SMatrices
    function BasisVectors(M::SMatrix{D,D,<:Real}) where D
        lattice_sanity_check(M)
        return new{D}(SVector{D,SVector{D,Float64}}(M[:,n] for n in 1:D))
    end
end

BasisVectors(vs::Vararg{AbstractVector{<:Real}, D}) where D = BasisVectors{D}(hcat(vs...))
BasisVectors{D}(vs::AbstractVector{<:AbstractVector}) where D = BasisVectors{D}(hcat(vs...))

# TODO: might this be better off as an inner constructor?
function BasisVectors(M::AbstractMatrix{<:Real})
    return BasisVectors(SVector{D,SVector{D,Float64}}(M[:,n] for n in 1:D))
end

# This should get a vector
Base.getindex(b::BasisVectors, ind) = b.vs[ind]
# This should treat BasisVectors like matrices
Base.getindex(b::BasisVectors, i1, i2) = b[i2][i1]

vectors(b::BasisVectors) = b.vs
matrix(b::BasisVectors{D}) where D = SMatrix{D,D,Float64}(b[m,n] for m in 1:D, n in 1:D)

# This is needed for broadcasting
Base.length(b::BasisVectors) = length(b.vs)
Base.iterate(b::BasisVectors, state) = iterate(b.vs, state)
Base.iterate(b::BasisVectors) = iterate(b, 1)

# TODO: does this make sense?
Base.convert(::Type{SMatrix{D,D,Float64}}, b::BasisVectors{D}) where D = matrix(b)
# TODO: do we need other kinds of conversions?

Base.zero(::Type{BasisVectors{D}}) where D = BasisVectors(zeros(SVector{D,SVector{D,Float64}}))
Base.zero(::BasisVectors{D}) where D = BasisVectors(zeros(SVector{D,SVector{D,Float64}}))
Base.zeros(::Type{BasisVectors{D}}) where D = BasisVectors(zeros(SVector{D,SVector{D,Float64}}))

# Definitions for multiplication/division by a scalar
# TODO: there's probably a more efficient way to do this
Base.:*(s::Number, b::BasisVectors) = BasisVectors(matrix(b) * s)
Base.:*(b::BasisVectors, s::Number) = s * b
Base.:/(b::BasisVectors, s::Number) = BasisVectors(matrix(b) / s)

# And multiplication/division by vectors
Base.:*(b::BasisVectors, v::AbstractVecOrMat) = matrix(b) * v
Base.:*(v::AbstractVecOrMat, b::BasisVectors) = v * matrix(b)
Base.:\(b::BasisVectors, v::AbstractVecOrMat) = matrix(b) \ v

"""
    dual(b::BasisVectors)

Generates the dual lattice defined by a set of basis vectors.

"Dual" refers to the basis generated with the inverse transpose. This does not include factors of τ
that are used crystallographically (τ = 2π).
"""
dual(b::BasisVectors) = BasisVectors(inv(transpose(matrix(b))))

"""
    RealLattice{D}

Describes a real space crystal lattice with primitive and conventional basis vectors.
"""
struct RealLattice{D} <: AbstractLattice{D}
    prim::BasisVectors{D}
    conv::BasisVectors{D}
    # Inner constructor should take any AbstractMatrix{<:Real} as input
    function RealLattice{D}(
        prim::BasisVectors{D},
        conv::BasisVectors{D}
    ) where D
        # Perform checks on the lattice pairs
        @assert all(x -> x - round(Int, x) < TOL_DEF, matrix(prim)\matrix(conv)) string(
            "The larger set of basis vectors is not expressed in terms of integer multiples of",
            "the smaller set of basis vectors."
        )
        new(prim, conv)
    end
end

"""
    ReciprocalLattice{D}

Describes a reciprocal space crystal lattice with primitive and conventional basis vectors.
"""
struct ReciprocalLattice{D} <: AbstractLattice{D}
    prim::BasisVectors{D}
    conv::BasisVectors{D}
    # Inner constructor should take any AbstractMatrix{<:Real} as input
    function ReciprocalLattice{D}(
        prim::BasisVectors{D},
        conv::BasisVectors{D}
    ) where D
        # Perform checks on the lattice pairs
        @assert all(x -> x - round(Int, x) < TOL_DEF, matrix(conv)\matrix(prim)) string(
            "The smaller set of basis vectors is not expressed in terms of integer reciprocals of",
            "the larger set of basis vectors."
        )
        return new(prim, conv)
    end
end

# Get primitive and conventional lattices
# This is the preferred way of doing so
"""
    prim(l::AbstractLattice{D}) -> BasisVectors{D}

Returns the primitive lattice in a `RealLattice` or a `ReciprocalLattice`.
"""
prim(l::AbstractLattice) = l.prim

"""
    conv(l::AbstractLattice{D}) -> BasisVectors{D}

Returns the conventional lattice in a `RealLattice` or a `ReciprocalLattice`.
"""
conv(l::AbstractLattice) = l.conv

# Define the constructor for vectors of vectors
function (Type{AbstractLattice{D}})(
    prim::AbstractVector{<:AbstractVector{<:Real}},
    conv::AbstractVector{<:AbstractVector{<:Real}},
) where D
    return RealLattice{D}(BasisVectors{D}(prim), BasisVectors{D}(conv))
end

# Conversions between real and reciprocal lattices
# It's critical that the `RealLattice` and `ReciprocalLattice` constructors are idempotent
# so that conversion can be completely seamless
RealLattice(latt::RealLattice) = latt
ReciprocalLattice(latt::ReciprocalLattice) = latt

"""
    ReciprocalLattice(latt::RealLattice{D})

Converts a real lattice to its corresponding reciprocal lattice.
"""
function ReciprocalLattice(latt::RealLattice{D}) where D
    return ReciprocalLattice{D}(dual(prim(latt))*2π, dual(conv(latt)*2π))
end

"""
    RealLattice(latt::ReciprocalLattice{D})

Converts a reciprocal lattice to its corresponding real lattice.
"""
function RealLattice(latt::ReciprocalLattice{D}) where D
    return RealLattice{D}(dual(prim(latt))/2π, dual(conv(latt)/2π))
end

Base.convert(::Type{RealLattice}, latt::AbstractLattice) = RealLattice(latt)
Base.convert(::Type{ReciprocalLattice}, latt::AbstractLattice) = ReciprocalLattice(latt)
#=
function convert(::Type{RealLattice{D}}, latt::AbstractLattice{D}) where D
    return RealLattice(latt)
end

function convert(::Type{ReciprocalLattice{D}}, latt::AbstractLattice{D}) where D
    return ReciprocalLattice(latt)
end
=#

"""
    Xtal.lattice_pair_generator_3D(M::AbstractMatrix; prim=false, ctr=:P)

Generates a pair of 3D lattices that are related by common crystallographic transformations.

Returns an `NTuple{2, BasisVectors{3}` with the first matrix containing the primitive basis and the
second containing the conventional basis.
"""
function lattice_pair_generator_3D(M::AbstractMatrix; prim=false, ctr=:P)
    # Without special centering just return the pair of basis vectors
    ctr == :P && return BasisVectors{3}.((M,M))
    # Figure out which type of cell the input matrix defines
    # TODO: Try to ensure this operations favors a c axis pointing along the z direction
    if prim
        Mp = M
        Mc = inv(REDUCTION_MATRIX_3D[ctr]) * M
    else
        Mc = M
        Mp = REDUCTION_MATRIX_3D[ctr] * M
    end
    return BasisVectors{3}.((Mp,Mc))
end

# It appears these next two docstrings are broken!
#=
"""
    RealLattice{3}(M::AbstractMatrix{<:Real}; prim=false, ctr=:P)

Generates a 3-dimensional real space lattice given a set of conventional or primitive basis
vectors and centering information.

By default, inputs are assumed to describe a conventional cell.
"""
=#
function RealLattice{3}(M::AbstractMatrix{<:Real}; prim=false, ctr=:P)
    return RealLattice{3}(lattice_pair_generator_3D(M, prim=prim, ctr=ctr)...)
end

"""
    cell_lengths(M::AbstractMatrix) -> Vector{Float64}

Returns the lengths of the constituent vectors in a matrix representing cell vectors.
"""
cell_lengths(M::AbstractMatrix) = [norm(M[:,n]) for n = 1:size(M,2)]
# TODO: perhaps this is not necessary anymore
# But removing it might break the API
cell_lengths(b::BasisVectors) = cell_lengths(matrix(b))

"""
    lengths(b::BasisVectors) -> Vector{Float64}

Returns the lengths of the constituent vectors in a matrix representing cell vectors.
"""
lengths(b::BasisVectors) = cell_lengths(matrix(b))

"""
    cell_volume(M::AbstractMatrix) -> Float64

Returns the volume of a unit cell defined by a matrix. This volume does not carry the sign
(negative for cells that do not follow the right hand rule).
"""
cell_volume(M::AbstractMatrix) = abs(det(M))
# TODO: perhaps this is not necessary anymore
# But removing it might break the API
cell_volume(b::BasisVectors) = cell_volume(matrix(b))

"""
    volume(b::BasisVectors) -> Float64

Returns the volume of a unit cell defined by a matrix. This volume does not carry the sign
(negative for cells that do not follow the right hand rule).
"""
volume(b::BasisVectors) = cell_volume(matrix(b))

"""
    volume(l::AbstractLattice; primitive=true) -> Float64

Returns the volume of a lattice. By default, the primitive cell volume is used, but the
conventional cell volume may be calculated with `primitive=false`.
"""
volume(l::AbstractLattice; primitive::Bool=true) = volume(primitive ? prim(l) : conv(l))

"""
    Xtal.generate_pairs(D::Integer) -> Vector{NTuple{2,Int}}

Generate pairs of integers up to `D` in ascending order.
"""
function generate_pairs(D::Integer)
    out = Vector{NTuple{2,Int}}(undef, Int(D*(D-1)/2))
    c = 0
    for a = 1:D
        for b = (a+1):D
            c += 1
            out[c] = (a,b)
        end
    end
    return out
end

"""
    Xtal.generate_pairs(::Type{Val{D}}) -> SVector{D*(D-1)/2, NTuple{2,Int}}

Generate pairs of integers up to `D` in ascending order in an `SVector`.
"""
function generate_pairs(::Type{Val{D}}) where D
    N = Int(D*(D-1)/2)
    return SVector{N,NTuple{2,Int}}(generate_pairs(D))
end

"""
    cell_angle_cos(M::AbstractMatrix)

Generates the cosines of the unit cell angles.

The angles are generated in the correct order [α, β, γ] for 3-dimensional cells. This is achieved
by reversing the output of `generate_pairs()`. For crystals with more spatial dimensions, this
may lead to unexpected results.
"""
function cell_angle_cos(M::AbstractMatrix)
    dimpairs = reverse(generate_pairs(size(M,1)))
    return [dot(M[:,a], M[:,b])/(norm(M[:,a])*norm(M[:,b])) for (a,b) in dimpairs]
end

"""
    cell_angle_rad(M::AbstractMatrix)

Returns the angles (in radians) between each pair of basis vectors.
"""
cell_angle_rad(M::AbstractMatrix) = acos.(cell_angle_cos(M))
cell_angle_deg(M::AbstractMatrix) = acosd.(cell_angle_cos(M))

"""
    lengths(L::AbstractLattice{D}; prim=false) -> SVector{D,Float64}

Returns the cell vector lengths. By default, returns the lengths of the conventional cell vectors.
"""
function lengths(L::AbstractLattice{D}; prim=false) where D
    prim ? M = prim(L) : M = conv(L)
    return SVector{D,Float64}(cell_lengths(M))
end

"""
    angles_deg(L::AbstractLattice{D}; prim=false) -> SVector{D,Float64}

Returns the cell vector angles in degrees.
"""
function angles_deg(L::AbstractLattice{D}; prim=false) where D
    prim ? M = prim(L) : M = conv(L)
    return SVector{D,Float64}(cell_angles_deg(M))
end

"""
   angles_deg(L::AbstractLattice{D}; prim=false) -> SVector{D,Float64}

Returns the cell vector angles in radians.
"""
function angles_rad(L::AbstractLattice{D}; prim=false) where D
    prim ? M = prim(L) : M = conv(L)
    return SVector{D,Float64}(cell_angles_rad(M))
end

"""
    lattice2D(a::Real, b::Real, γ::Real) -> BasisVectors{2}

Constructs a set of basis vectors in an `SMatrix` that correspond to a unit cell in 2D with the
same length and angle parameters (in degrees).

By default, the b-vector is oriented along y. This selection corresponds to the default orientation
chosen by `lattice3D()`.
"""
function lattice2D(a::Real, b::Real, γ::Real)
    return BasisVectors(SMatrix{2,2,Float64}(a*sind(γ), a*cosd(γ), 0, b))
end

"""
    lattice3D(a::Real, b::Real, c::Real, α::Real, β::Real, γ::Real) -> BasisVectors{3}

Constructs a set of basis vectors in an `SMatrix` that correspond to a unit cell in 3D with the
same length and angle parameters (in degrees).

By default, the b-vector is oriented along y, and the a-vector is chosen to be perpendicular to
z, leaving the c-vector to freely vary. This selection allows for the most convenient orientation
of symmetry operations.
"""
function lattice3D(a::Real, b::Real, c::Real, α::Real, β::Real, γ::Real)
    c1 = c*(cosd(β) - cosd(γ)*cosd(α))/sind(γ)
    c2 = c*cosd(α)
    M = SMatrix{3,3,Float64}(a*sind(γ), a*cosd(γ), 0,
                                     0,        b,  0,
                                c1, c2, sqrt(c^2 - (c1^2 + c2^2)))
    return BasisVectors(M)
end

# TODO: Update and document this function a bit more.
# It works, but that really isn't enough for me.
# I think this was pulled directly from the WaveTrans source code
function maxHKLindex(M::AbstractMatrix{<:Real}, ecut::Real; c = CVASP)
    # I think the parts below convert a set of basis vectors into their reciprocals
    # But we already have ways to do that with `dual(::BasisVectors)`
    #--------------------------------------------------------------------------#
    cosines = cell_angle_cos(M)
    crosses = [cross(M[:,a], M[:,b]) for (a,b) in zip([2,3,1], [3,1,2])]
    triples = [dot(M[:,n], crosses[n])/(norm(crosses[n])*norm(M[:,n])) for n in 1:3]
    sines = hcat([[sqrt(1-c^2), sqrt(1-c^2) ,t] for (c,t) in zip(cosines, triples)]...)
    #--------------------------------------------------------------------------#
    nbmax = sqrt(c*ecut) ./ [norm(M[:,a])*sines[a,b] for a in 1:3, b in 1:3] .+ 1
    return floor.(Int, vec(maximum(nbmax, dims=1)))
end

# Assume that the basis vectors are defined in reciprocal space (??)
maxHKLindex(b::BasisVectors{3}, ecut::Real; c = CVASP) = maxHKLindex(matrix(b), ecut, c = c)

"""
    Xtal.maxHKLindex(L::AbstractLattice, ecut::Real; prim=true, c = CVASP)

Determines the maximum integer values of the reciprocal lattice vectors needed to store data out
to a specific energy cutoff for a 3D lattice.

By default, the energy cutoff is assumed to be in units of eV, the reciprocal lattice vector
lengths are assumed to be in angstroms, and the value of c (2m/ħ^2) is taken from VASP's default
value (which is incorrect!). Different values of c may be used for different units.

The functionality implemented here was taken from WaveTrans:
https://www.andrew.cmu.edu/user/feenstra/wavetrans/
"""
function maxHKLindex(L::AbstractLattice{3}, ecut::Real, prim=true, c = CVASP)
    M = ReciprocalLattice{3}(prim ? prim(L) : conv(L))
    return maxHKLindex(M, ecut, c=c)
end


## basic copyto function, sets F up on cg pid
copyto(F::Any, pids::Vector{Int}) = F

## computes y = (F'F + lambda \eye) x
function AtA_mul_B!(y::AbstractVector{Float64}, F, x::AbstractVector{Float64}, lambda::Float64)
  length(y) == length(x) || throw(DimensionMismatch("length(y)=$(length(y)) must equal length(x)=$(length(x))"))
  At_mul_B!(y, F, F*x)
  for i = 1:length(y)
    y[i] += lambda*x[i]
  end
  return nothing
end

## for standard objects nonshared does not do anything
nonshared(A) = A

######### cg code ##########
function normsq{T}(x::Vector{T})
  s = zero(T)
  @inbounds @simd for i=1:length(x)
    s += x[i]*x[i]
  end
  s
end

norm2{T}(x::Vector{T}) = sqrt(normsq(x))

function prod_add!(p, mult, r)
  @inbounds @simd for i=1:length(p)
    p[i] = mult*p[i] + r[i]
  end
end

function add_prod!(x, mult, v)
  @inbounds @simd for i=1:length(x)
    x[i] += mult*v[i]
  end
end

function sub_prod!(x, mult, v)
  @inbounds @simd for i=1:length(x)
    x[i] -= mult*v[i]
  end
end

## function for calling with remote
cg_AtA_ref(Aref::RemoteRef, b::AbstractVector{Float64}, lambda::Float64, tol::Float64, maxiter::Int=length(b)) = cg_AtA(fetch(Aref), b, lambda, tol=tol, maxiter=maxiter)

## p and z are parallelized (SharedArray)
function cg_AtA(A::ParallelSBM, b::AbstractVector{Float64}, lambda::Float64;
         tol::Float64=size(A,2)*eps(), maxiter::Int=size(A,2))
    return cg_AtA(A, b, lambda, A.sh1, A.sh2, tol=tol, maxiter=maxiter)
end

## non-parallel version
function cg_AtA(A, b::AbstractVector{Float64}, lambda::Float64;
         tol::Float64=size(A,2)*eps(), maxiter::Int=size(A,2))
    return cg_AtA(A, b, lambda, zeros(length(b)), zeros(length(b)), tol=tol, maxiter=maxiter)
end

function cg_AtA(A, b::AbstractVector{Float64}, lambda::Float64, p::AbstractVector{Float64}, z::AbstractVector{Float64};
         tol::Float64=size(A,2)*eps(), maxiter::Int=size(A,2))
    tol = tol * norm(b)
    ## x is set initially to 0
    x = zeros(Float64, length(b))
    #r = b - A * x
    r = zeros(length(x)) + b
    Base.copy!(p, r)
    bkden = zero(eltype(x))

    for iter = 1:maxiter
        bknum = normsq(r)::Float64
        err   = sqrt(bknum)

        err < tol && return x#, err, iter

        if iter > 1
            bk = bknum / bkden
            prod_add!(p, bk, r)
        end
        bkden = bknum

        ## z = (A'A + lambda*\eye) * p
        AtA_mul_B!(z, A, p, lambda)

        ak = bknum / dot(z, p)

        add_prod!(x, ak, p)
        sub_prod!(r, ak, z)
    end
    x#, sqrt(normsq(r)), maxiter
end


"""
    diffci(x1, n1, x2, n2; level = 0.95, method = :default)

Proportion difference (x1 / n1 - x2 / n2) confidence interval.

* 'method'

- `:mn` | `:default`- Miettinen & Nurminen;  Miettinen, O. and Nurminen, M. (1985), Comparative analysis of two rates. Statist. Med., 4: 213-226. doi:10.1002/sim.4780040211;
- `:wald` - Wald CI without CC;
- `:waldcc` - Wald CI with CC;
- `:nhs`  - Newcombes Hybrid (wilson) Score interval; Newcombe RG (1998), Interval Estimation for the Difference Between Independent Proportions: Comparison of Eleven Methods. Statistics in Medicine 17, 873-890;
- `:nhscc` - Newcombes Hybrid Score CC; Newcombe (1998);
- `:ac` -  Agresti-Caffo interval; Agresti A, Caffo B., “Simple and effective confidence intervals for proportions and differences of proportions result from adding two successes and two failures”, American Statistician 54: 280–288 (2000);
- `:ha` - Hauck-Andersen; Hauck, W. W., & Anderson, S. (1986). A Comparison of Large-Sample Confidence Interval Methods for the Difference of Two Binomial Probabilities. The American Statistician, 40(4), 318–322. doi:10.1080/00031305.1986.10475426 ;
- `:mnmee` - Mee maximum likelihood method; Mee RW (1984) Confidence bounds for the difference between two probabilities,Biometrics40:1175-1176
- `:mover` - Method of variance estimates recovery;
- `:jeffrey` - Brown, Li's Jeffreys.

# Reference:

* Brown, L.D., Cai, T.T., and DasGupta, A. Interval estimation for a binomial proportion. Statistical Science, 16(2):101–117, 2001.
* Farrington, C. P. and Manning, G. (1990), “Test Statistics and Sample Size Formulae for Comparative Binomial Trials with Null Hypothesis of Non-zero Risk Difference or Non-unity Relative Risk,” Statistics in Medicine, 9, 1447–1454
* Li HQ, Tang ML, Wong WK. Confidence intervals for ratio of two Poisson rates using the methodof variance estimates recovery. Computational Statistics 2014; 29(3-4):869-889
* Brown, L., Cai, T., & DasGupta, A. (2003). INTERVAL ESTIMATION IN EXPONENTIAL FAMILIES. Statistica Sinica, 13(1), 19-49.
"""
function diffci(x1, n1, x2, n2; level = 0.95, method = :default)

    alpha    = 1 - level
    if method == :mn || method == :default
        ci_diff_mn(x1, n1, x2, n2, alpha)
    elseif method == :wald
        ci_diff_wald(x1, n1, x2, n2, alpha)
    elseif method == :waldcc
        ci_diff_wald_cc(x1, n1, x2, n2, alpha)
    elseif method == :nhs
        ci_diff_nhs(x1, n1, x2, n2, alpha)
    elseif method == :nhscc
        ci_diff_nhs_cc(x1, n1, x2, n2, alpha)
    elseif method == :ac
        ci_diff_ac(x1, n1, x2, n2, alpha)
    elseif method == :ha
        ci_diff_ha(x1, n1, x2, n2, alpha)
    elseif method == :mnmee
        ci_diff_mnmee(x1, n1, x2, n2, alpha)
    elseif method == :mover
        ci_diff_mover(x1, n1, x2, n2, alpha)
    elseif method == :jeffrey
        ci_diff_jeffrey(x1, n1, x2, n2, alpha)
    else
        throw(ArgumentError("unknown ci method=$(method)"))
    end
end
"""
    diffci(contab::ConTab; level = 0.95, method = :default)

ConTab 2X2:

A | B
--|--
C | D

Difference: A / (A + B) - C / (C + D)
"""
function diffci(contab::ConTab; level = 0.95, method = :default)
    if !(size(contab.tab, 1) == size(contab.tab, 2) == 2) throw(ArgumentError("CI only for 2 X 2 tables.")) end
    alpha    = 1 - level
    x1 = contab.tab[1,1]
    n1 = x1 + contab.tab[1,2]
    x2 = contab.tab[2,1]
    n2 = x2 + contab.tab[2,2]
    diffci(x1, n1, x2, n2; level = level, method = method)
end
"""
    orci(contab::ConTab; level = 0.95, method = :default)

- `:mn`
- `:woolf`
- `:awoolf`
- `:mover`
"""
function orci(contab::ConTab; level = 0.95, method = :default)
    if !(size(contab.tab, 1) == size(contab.tab, 2) == 2) throw(ArgumentError("CI only for 2 X 2 tables.")) end
    alpha    = 1 - level
    x1 = contab.tab[1,1]
    n1 = x1 + contab.tab[1,2]
    x2 = contab.tab[2,1]
    n2 = x2 + contab.tab[2,2]
    if method == :mn || method == :default
        ci_or_mn(x1, n1, x2, n2, alpha)
    elseif method == :woolf
        ci_or_woolf(x1, n1, x2, n2, alpha)
    elseif method == :awoolf
        ci_or_awoolf(x1, n1, x2, n2, alpha)
    elseif method == :mover
        ci_or_mover(x1, n1, x2, n2, alpha)
    else
        throw(ArgumentError("unknown ci method=$(method)"))
    end
end
"""
    rrci(contab::ConTab; level = 0.95, method = :default)

- `:mn`
- `:cli`
- `:li`
- `:mover`
"""
function rrci(contab::ConTab; level = 0.95, method = :default)
    if !(size(contab.tab, 1) == size(contab.tab, 2) == 2) throw(ArgumentError("CI only for 2 X 2 tables.")) end
    alpha    = 1 - level
    x1 = contab.tab[1,1]
    n1 = x1 + contab.tab[1,2]
    x2 = contab.tab[2,1]
    n2 = x2 + contab.tab[2,2]
    if method == :mn || method == :default
        ci_rr_mn(x1, n1, x2, n2, alpha)
    elseif method == :cli
        ci_rr_cli(x1, n1, x2, n2, alpha)
    elseif method == :li
        ci_rr_li(x1, n1, x2, n2, alpha)
    elseif method == :mover
        ci_rr_mover(x1, n1, x2, n2, alpha)
    else
        throw(ArgumentError("unknown ci method=$(method)"))
    end
end

"""
    propci(x::Int, n::Int; level = 0.95, method = :default)

`method`:

- `:wilson` | `:default` - Wilson's confidence interval (CI) for a single proportion (wilson score);
- `:wilsoncc` - Wilson's CI with continuity correction (CC);
- `:cp` - Clopper-Pearson exact CI;
- `:blaker` - Blaker exact CI for discrete distributions;
- `:soc` - SOC: Second-Order corrected CI;
- `:arc` - Arcsine CI;
- `:wald` - Wald CI without CC;
- `:waldcc` - Wald CI with CC;
- `:ac`
- `:jeffrey`
"""
function propci(x::Int, n::Int; level = 0.95, method = :default)
    if  x > n throw(ArgumentError("x > n")) end
    alpha    = 1 - level
    if method == :wilson || method == :default
        fx = ci_prop_wilson
    elseif method==:wilsoncc
        fx = ci_prop_wilson_cc
    elseif method==:cp
        fx = ci_prop_cp
    elseif method==:blaker
        fx = ci_prop_blaker
    elseif method==:soc
        fx = ci_prop_soc
    elseif method==:arc
        fx = ci_prop_arc
    elseif method==:wald
        fx = ci_prop_wald
    elseif method==:waldcc
        fx = ci_prop_wald_cc
    elseif method==:ac
        fx = ci_prop_ac
    elseif method==:jeffrey
        fx = ci_prop_jeffrey
    else
        throw(ArgumentError("unknown method!"))
    end
    fx(x, n, alpha)
end
"""
    propci(contab::ConTab; level = 0.95, method = :default)
"""
function propci(contab::ConTab; level = 0.95, method = :default)
    alpha    = 1 - level
    if  size(contab.tab, 2) != 2 throw(ArgumentError("CI only for N X 2 tables.")) end
    if size(contab, 1) > 1
        v = Vector{Tuple{Float64, Float64}}(undef, size(contab, 1))
        for i = 1:size(contab, 1)
            x = contab.tab[i,1]
            n = x + contab.tab[i,2]
            println(x, " : ", n)
            v[i] = propci(x, n; level = level, method = method)
        end
        return Tuple(v)
    else
        x = contab.tab[1,1]
        n = x + contab.tab[1,2]
        return propci(x, n; level = level, method = method)
    end
end

"""
    propci(prop::Proportion; level = 0.95, method = :default)
"""
function propci(prop::Proportion; level = 0.95, method = :default)
    propci(prop.x, prop.n; level = level, method = method)
end

"""
    mpropci(contab::ConTab; level = 0.95, method = :default)

Multinomial proportions confidence interval.

`method`:

- `goodman` | `default` Goodman, L.A. (1965). On Simultaneous Confidence Intervals for Multinomial Proportions. Technometrics 7: 247-254.

"""
function mpropci(contab::ConTab; level = 0.95, method = :default)
    alpha    = 1 - level
    if  size(contab.tab, 2) <= 2 throw(ArgumentError("CI only for N X M tables where M > 2")) end

    if method == :goodman || method == :default
        fx = ci_prop_goodman
    else
        throw(ArgumentError("unknown method!"))
    end
    if size(contab, 1) > 1
        v = Vector{Vector{Tuple{Float64, Float64}}}(undef, size(contab, 1))
        for i = 1:size(contab, 1)
            v[i] = fx(view(contab.tab, i, :), alpha)
        end
        return v
    else
        return fx(view(contab.tab, 1, :), alpha)
    end
end

################################################################################
# Proportion difference CI
################################################################################
# Method of Mee 1984 with Miettinen and Nurminen modification n / (n - 1) Newcombe 1998
# Score intervals for the difference of two binomial proportions
@inline function mle_diff(p1, n1, p2, n2, δ)
    if p1 - p2 - δ == 0 return 0.0 end
    θ = n2 / n1
    a = 1 + θ
    b = -(1 + θ + p1 + θ * p2 + δ * (θ + 2))
    c = δ^2 + δ * (2 * p1 + θ + 1) + p1 + θ * p2
    d = -p1 * δ * (1 + δ)
    v = (b / a / 3)^3 - b * c/(6 * a * a) + d / 2 / a
    u = sign(v) * sqrt((b / 3 / a)^2 - c / 3 / a)
    w = (pi + acos(v / u^3)) / 3
    p1n = 2 * u * cos(w) - b / 3 /a
    p2n = p1n - δ
    return p1n, p2n
end
@inline function mn_diff_z_val(p1, n1, p2, n2, est, δ)
    p1n, p2n = mle_diff(p1, n1, p2, n2, δ)
    return (est - δ)^2 / ((n1 + n2) / (n1 + n2 - 1) * (p1n * (1 - p1n) / n1 + p2n * (1 - p2n) / n2))
end
function ci_diff_mn(x1, n1, x2, n2, alpha; atol::Float64 = 1E-8)
    lcis, ucis = ci_diff_nhs_cc(x1, n1, x2, n2, alpha)
    p1       = x1 / n1
    p2       = x2 / n2
    est      = p1 - p2
    z        = quantile(Chisq(1), 1 - alpha)
    fmnd(x)  = mn_diff_z_val(p1, n1, p2, n2, est, x) - z
    if fmnd(lcis) * fmnd(est - eps()) < 0.0
        ll = lcis
        lu = est - eps()
    else
        ll = -1.0 + eps()
        lu = lcis
    end
    if fmnd(ucis) * fmnd(est + eps()) < 0.0
        ul = est + eps()
        uu = ucis
    else
        ul = ucis
        uu = 1.0 - eps()
    end
    lci = find_zero(fmnd, (ll, lu), atol = atol)
    uci = find_zero(fmnd, (ul, uu), atol = atol)
    return lci, uci
end
# Wald
function ci_diff_wald(x1, n1, x2, n2, alpha)
    p1       = x1 / n1
    p2       = x2 / n2
    est      = p1 - p2
    z        = quantile(Normal(), 1 - alpha / 2)
    se       = sqrt(p1 * (1 - p1) / n1 + p2 * (1 - p2) / n2)
    return est - z * se, est + z * se
end
# Wald continuity correction
function ci_diff_wald_cc(x1, n1, x2, n2, alpha)
    p1       = x1 / n1
    p2       = x2 / n2
    est      = p1 - p2
    z        = quantile(Normal(), 1 - alpha / 2)
    se       = sqrt(p1 * (1 - p1) / n1 + p2 * (1 - p2) / n2)
    cc       = (1 / n1 + 1 / n2) / 2
    return est - z * se - cc, est + z * se + cc
end
# Newcombes Hybrid (wilson) Score interval for the difference of proportions
# Newcombe 1998
function ci_diff_nhs(x1, n1, x2, n2, alpha)
    p1       = x1 / n1
    p2       = x2 / n2
    est      = p1 - p2
    z        = quantile(Normal(), 1 - alpha / 2)
    lci1, uci1 = ci_prop_wilson(x1, n1, alpha)
    lci2, uci2 = ci_prop_wilson(x2, n2, alpha)
    return  est - z * sqrt(lci1 * (1 - lci1)/n1 + uci2 * (1 - uci2) / n2), est + z * sqrt(uci1 * (1 - uci1) / n1 + lci2 * (1 - lci2) / n2)
end
# Newcombes Hybrid Score continuity correction
function ci_diff_nhs_cc(x1, n1, x2, n2, alpha)
    p1       = x1 / n1
    p2       = x2 / n2
    est      = p1 - p2
    z        = quantile(Normal(), 1 - alpha / 2)
    lci1, uci1 = ci_prop_wilson_cc(x1, n1, alpha)
    lci2, uci2 = ci_prop_wilson_cc(x2, n2, alpha)
    return  est - sqrt((p1 - lci1)^2 + (uci2 - p2)^2), est + sqrt((uci1 - p1)^2 + (p2 - lci2)^2)
end
# Agresti-Caffo interval for the difference of proportions
# Agresti A, Caffo B., “Simple and effective confidence intervals for proportions and differences of proportions result from adding two successes and two failures”, American Statistician 54: 280–288 (2000)
function ci_diff_ac(x1, n1, x2, n2, alpha)
    z        = quantile(Normal(), 1 - alpha / 2)
    p1I      = (x1 + 1) / (n1 + 2)
    p2I      = (x2 + 1) / (n2 + 2)
    n1I      = n1 + 2
    n2I      = n2 + 2
    est      = p1I - p2I
    se       = sqrt(p1I * (1 - p1I) / n1I + p2I * (1 - p2I) / n2I)
    return  est - z * se, est + z * se
end
# Hauck-Andersen
# Hauck, W. W., & Anderson, S. (1986). A Comparison of Large-Sample Confidence Interval Methods for the Difference of Two Binomial Probabilities. The American Statistician, 40(4), 318–322. doi:10.1080/00031305.1986.10475426

function ci_diff_ha(x1, n1, x2, n2, alpha)
    p1       = x1 / n1
    p2       = x2 / n2
    est      = p1 - p2
    z        = quantile(Normal(), 1 - alpha / 2)
    se       = sqrt(p1 * (1 - p1) / (n1 - 1) + p2 * (1 - p2) / (n2 - 1))
    cc       = 1 / min(n1, n2)
    return est - z * se - cc, est + z * se + cc
end
# Mee RW (1984) Confidence bounds for the difference between two probabilities,Biometrics40:1175-1176
# MN - no correction
@inline function mnmee_z_val(p1, n1, p2, n2, est, δ)
    p1n, p2n = mle_diff(p1, n1, p2, n2, δ)
    return (est - δ)^2 / (p1n * (1 - p1n) / n1 + p2n * (1 - p2n) / n2)
end
function ci_diff_mnmee(x1, n1, x2, n2, alpha; atol::Float64 = 1E-8)
    lcis, ucis = ci_diff_nhs_cc(x1, n1, x2, n2, alpha)
    p1       = x1 / n1
    p2       = x2 / n2
    est      = p1 - p2
    z        = quantile(Chisq(1), 1 - alpha)
    fmnd(x)  = mnmee_z_val(p1, n1, p2, n2, est, x) - z
    if fmnd(lcis) * fmnd(est - eps()) < 0.0
        ll = lcis
        lu = est - eps()
    else
        ll = -1.0 + eps()
        lu = lcis
    end
    if fmnd(ucis) * fmnd(est + eps()) < 0.0
        ul = est + eps()
        uu = ucis
    else
        ul = ucis
        uu = 1.0 - eps()
    end
    lci = find_zero(fmnd, (ll, lu), atol = atol)
    uci = find_zero(fmnd, (ul, uu), atol = atol)
    return  lci, uci
end
# Brown, Li's Jeffreys
function ci_diff_jeffreys(x1, n1, x2, n2, alpha)
    p1   = (x1 +  1/2) / (n1 + 1)
    p2   = (x2 +  1/2) / (n2 + 1)
    se   = sqrt(p1*(1 - p1) / n1 + p2 * (1 - p2) / n2)
    z    = quantile(Normal(), 1 - alpha / 2)
    est  = p1 - p2
    return  max(-1.0, est - z * se), min(1.0, est + z * se)
end
# Method of variance estimates recovery
function ci_diff_mover(x1, n1, x2, n2, alpha)
    p1       = x1 / n1
    p2       = x2 / n2
    est      = p1 - p2
    Z        = quantile(Normal(), 1 - alpha / 2)
    lci1, uci1   = ci_prop_wilson(x1, n1, alpha)
    lci2, uci2   = ci_prop_wilson(x2, n2, alpha)
    lci      = est - sqrt((p1 - lci1)^2 + (uci2 - p2)^2)
    uci      = est + sqrt((uci1 - p1)^2 + (p2 - lci2)^2)
    return lci, uci
end
################################################################################
# Odd ratio CI
################################################################################
@inline function mle_or(φ, x1, n1, x2, n2)
    a  = n2 * (φ-1)
    b  = φ * n1 + n2 - (x1 + x2) * (φ - 1)
    c  = -(x1 + x2)
    p2 = (-b + sqrt(b * b - 4 * a * c)) / a / 2
    p1 = p2 * φ/(1 + p2 * (φ - 1))
    return p1, p2
end
@inline function mle_or_z_val(φ, x1, n1, x2, n2)
    p1 = x1 / n1
    pmle1, pmle2 = mle_or(φ, x1, n1, x2, n2)
    return (n1 * (p1 - pmle1))^2 * (1 / (n1 * pmle1 * (1 - pmle1)) + 1/(n2 * pmle2 * (1 - pmle2))) / ((n1 + n2)/(n1 + n2 - 1))
end
################################################################################
# Miettinen O. S., Nurminen M. (1985) Comparative analysis of two rates.Statistics in Medicine4,213–226
# MN Score
function ci_or_mn(x1, n1, x2, n2, alpha; atol::Float64 = 1E-8)
    z        = quantile(Chisq(1), 1 - alpha)
    fmnor(x) = mle_or_z_val(x, x1, n1, x2, n2) - z
    if (x1== 0 && x2 == 0) || (x1 == n1 && x2 == n2)
        return 0.0, Inf
    elseif x1==0 || x2 == n2
        return 0.0, find_zero(fmnor, atol, atol = atol)
    elseif x1 == n1 || x2 == 0
        return find_zero(fmnor, atol, atol = atol), Inf
    else
        est  = (x1 / (n1 - x1)) / (x2 / (n2 - x2))
        return find_zero(fmnor, atol, atol = atol), find_zero(fmnor, est + atol, atol = atol)
    end
end
# Woolf logit
# Woolf, B. (1955). On estimating the relation between blood group and disease. Annals of human genetics, 19(4):251-253.
function ci_or_woolf(x1, n1, x2, n2, alpha)
    xa        = x1
    xb        = n1 - x1
    xc        = x2
    xd        = n2 - x2
    est       = xa*xd/xc/xb
    estI      = log(est)
    se        = sqrt(1/xa + 1/xb + 1/xc + 1/xd)
    z         = quantile(Normal(), 1 - alpha / 2)
    return exp(estI - z * se), exp(estI + z * se)
end
# Adjusted Woolf interval (Gart adjusted logit) Lawson, R (2005):Smallsample confidence intervals for the odds ratio.  Communication in Statistics Simulation andComputation, 33, 1095-1113.
# Gart, J. J. (1966). Alternative analyses of contingency tables. Journal of the Royal Statistical Society. Series B (Methodological), 28:164-179.
function ci_or_awoolf(x1, n1, x2, n2, alpha)
    xa        = x1 +  1/2
    xb        = n1 - x1 +  1/2
    xc        = x2 + 1/2
    xd        = n2 - x2 +  1/2
    est       = xa*xd/xc/xb
    estI      = log(est)
    se        = sqrt(1/xa + 1/xb + 1/xc + 1/xd)
    z         = quantile(Normal(), 1 - alpha/2)
    return exp(estI - z * se), exp(estI + z * se)
end
# Method of variance estimates recovery
# Donner, A. and Zou, G. (2012). Closed-form confidence intervals for functions of the normal mean and standard deviation. Statistical Methods in Medical Research, 21(4):347-359.
function ci_or_mover(x1, n1, x2, n2, alpha)
    p1       = (x1/(n1-x1))
    p2       = (x2/(n2-x2))
    est      = p1/p2
    z        = quantile(Normal(), 1 - alpha / 2)
    lci1, uci1   = ci_prop_wilson(x1, n1, alpha)
    lci2, uci2   = ci_prop_wilson(x2, n2, alpha)
    vl1      = lci1/(1 - lci1)
    vu1      = uci1/(1 - uci1)
    vl2      = lci2/(1 - lci2)
    vu2      = uci2/(1 - uci2)
    lci      = (p1*p2-sqrt((p1*p2)^2 - vl1*vu2*(2*p1-vl1)*(2*p2-vu2)))/(vu2*(2*p2 - vu2))
    uci      = (p1*p2+sqrt((p1*p2)^2 - vu1*vl2*(2*p1-vu1)*(2*p2-vl2)))/(vl2*(2*p2 - vl2))
    return lci, uci
end
################################################################################
# Risk ratio CI
################################################################################
@inline function mle_rr(φ, x1, n1, x2, n2)
    a = (n1 + n2) * φ
    b = -(φ * (x1 + n2) + x2 + n1)
    c = x1 + x2
    p2 = (-b - sqrt(b * b - 4 * a * c)) / 2 / a
    p1 = p2 * φ
    return p1, p2
end
@inline function mle_rr_z_val(φ, x1, n1, x2, n2)
    p1 = x1 / n1
    p2 = x2 / n2
    pmle1, pmle2 = mle_rr(φ, x1, n1, x2, n2)
    return (p1 - φ * p2)^2 / ((pmle1 * (1 - pmle1) / n1 + φ * φ * pmle2 * (1 - pmle2) / n2) * ((n1 + n2 - 1) / (n1 + n2)))
end
################################################################################
# Miettinen-Nurminen Score interval
# Miettinen, O. and Nurminen, M. (1985), Comparative analysis of two rates. Statist. Med., 4: 213-226. doi:10.1002/sim.4780040211
function ci_rr_mn(x1, n1, x2, n2, alpha; atol::Float64 = 1E-8)
    z        = quantile(Chisq(1), 1 - alpha)
    fmnrr(x) = mle_rr_z_val(x, x1, n1, x2, n2) - z
    if (x1==0 && x2==0) || (x1==n1 && x2==n2)
        return  0.0, Inf
    elseif x1==0 || x2==n2
        return 0.0, find_zero(fmnrr, atol, atol=atol)
    elseif x1==n1 || x2 == 0
        return find_zero(fmnrr, atol, atol=atol), Inf
    else
        est = (x1 / n1) / (x2 / n2)
        return find_zero(fmnrr, atol, atol=atol), find_zero(fmnrr, est + atol, atol=atol)
    end
end
# Crude log interval
# Gart, JJand Nam, J (1988): Approximate interval estimation of the ratio of binomial parameters: Areview and corrections for skewness. Biometrics 44, 323-338.
function ci_rr_cli(x1, n1, x2, n2, alpha)
    x1I       = x1 + 1/2
    x2I       = x2 + 1/2
    n1I       = n1 + 1/2
    n2I       = n2 + 1/2
    estI      = log((x1I / n1I) / (x2I / n2I))
    se        = sqrt(1 / x2I + 1 / x1I - 1 / n2I - 1 / n1I)
    est       = (x1 / n1) / (x2 / n2)
    z         =  quantile(Normal(), 1 - alpha / 2)
    return exp(estI - z * se), exp(estI + z * se)
end
# Katz D, Baptista J, Azen SP and Pike MC. Obtaining confidence intervals for the risk ratio in cohort studies. Biometrics 1978; 34: 469–474
function ci_rr_li(x1, n1, x2, n2, alpha)
    est       = (x1 / n1) / (x2 / n2)
    estI      = log(est)
    se        = sqrt(1 / x2 + 1 / x1 - 1 / n2 - 1 / n1)
    z         = quantile(Normal(), 1 - alpha / 2)
    return exp(estI - z * se), exp(estI + z * se)
end
# Method of variance estimates recovery (Donner, Zou, 2012)
function ci_rr_mover(x1, n1, x2, n2, alpha)
    p1       = x1 / n1
    p2       = x2 / n2
    est      = p1 / p2
    Z        = quantile(Normal(), 1 - alpha / 2)
    lci1, uci1   = ci_prop_wilson(x1, n1, alpha)
    lci2, uci2   = ci_prop_wilson(x2, n2, alpha)
    lci      = (p1 * p2 - sqrt((p1 * p2)^2 - lci1 * uci2 * (2 * p1 - lci1) * (2 * p2 - uci2))) / (uci2 * (2 * p2 - uci2))
    uci      = (p1 * p2 + sqrt((p1 * p2)^2 - uci1 * lci2 * (2 * p1 - uci1) * (2 * p2 - lci2))) / (lci2 * (2 * p2 - lci2))
    return  lci, uci
end
################################################################################
# Prorotion CI
################################################################################
# Wilson’s confidence interval for a single proportion, wilson score
# Wilson, E.B. (1927) Probable inference, the law of succession, and statistical inferenceJ. Amer.Stat. Assoc22, 209–212
function ci_prop_wilson(x, n, alpha)
    z   = abs(quantile(Normal(), 1 - alpha / 2))
    p   = x / n
    d   = 1 + (z^2) / n
    se  = sqrt((p * (1 - p) + (z^2) / (4 * n)) / n) / d
    est = (p + (z^2) / (2 * n)) / d
    return est - z * se, est + z * se
end
# Wilson CC
# Newcombe, R. G. (1998). "Two-sided confidence intervals for the single proportion: comparison of seven methods". Statistics in Medicine. 17 (8): 857–872. doi:10.1002/(SICI)1097-0258(19980430)17:8<857::AID-SIM777>3.0.CO;2-E. PMID 959561
function ci_prop_wilson_cc(x, n, alpha)
    z = abs(quantile(Normal(), 1 - alpha / 2))
    p = x / n
    l = (2*n*p+z*z-1-z*sqrt(z*z-2-1/n+4*p*(n*(1-p)+1)))/2/(n+z*z)
    u = (2*n*p+z*z+1+z*sqrt(z*z+2-1/n+4*p*(n*(1-p)-1)))/2/(n+z*z)
    return min(p, l), max(p, u)
end
#Clopper-Pearson exatct CI
#Clopper, C. and Pearson, E.S. (1934) The use of confidence or fiducial limits illustrated in the caseof the binomial.Biometrika26, 404–413.
function ci_prop_cp(x, n, alpha)
    if x == 0
        ll = 0.0
        ul = 1 - (alpha / 2)^(1 / n)
    elseif x == n
        ul = 1.0
        ll = (alpha / 2)^(1 / n)
    else
        ll = 1/(1+(n-x+1)/(x*quantile(FDist(2*x, 2*(n-x+1)), alpha/2)))
        ul = 1/(1+(n-x) / ((x+1)*quantile(FDist(2*(x+1), 2*(n-x)), 1-alpha/2)))
    end
    return ll, ul
end
# Blaker CI
# Blaker, H. (2000). Confidence curves and improved exact confidence intervals for discrete distributions,Canadian Journal of Statistics28 (4), 783–798
function ci_prop_blaker(x, n, alpha; atol::Float64 = 1E-8)
    lower = 0; upper = 1;
    fx(p) =  acceptbin(x, n, p) - alpha
    if n != 0
        lower = quantile(Beta(x, n-x+1), alpha/2)
        lower = find_zero(fx, lower, atol=atol)
    end
    if x != n
        upper = quantile(Beta(x+1, n-x), 1-alpha/2)
        upper = find_zero(fx, upper, atol=atol)
    end
    return lower,upper
end
@inline function acceptbin(x, n, p)
    BIN = Binomial(n,p)
    p1 = 1-cdf(BIN,x-1)
    p2 =   cdf(BIN,x)
    a1 = p1 + cdf(BIN, quantile(BIN,p1)-1)
    a2 = p2+1-cdf(BIN, quantile(BIN,1-p2))
    return min(a1,a2)
end
# SOC  Second-Order corrected
# T. Tony Cai One-sided confdence intervals in discrete distributions doi:10.1016/j.jspi.2004.01.00
function ci_prop_soc(x, n, alpha)
    p  = x/n
    k  = quantile(Normal(), 1-alpha/2)
    k2 = k^2
    η  = k2/3+1/6
    γ1 = -(k2*13/18+17/18)
    γ2 = k2/18+7/36
    m  = (x+η)/(n+2*η)
    b  = k*sqrt(p*(1-p)+(γ1*p*(1-p)+γ2)/n)/sqrt(n)
    return m-b, m+b
end
# Arcsine
function ci_prop_arc(x, n, alpha)
    q = quantile(Normal(), 1-alpha/2)
    p = x/n
    z = q/(2*sqrt(n))
    return sin(asin(sqrt(p))-z)^2, sin(asin(sqrt(p))+z)^2
end
# Wald CI
function ci_prop_wald(x, n, alpha)
    p=x/n
    b = quantile(Normal(), 1-alpha/2)*sqrt(p*(1-p)/n)
    return p-b, p+b
end
# Wald CI CC
function ci_prop_wald_cc(x::Int, n::Int, alpha::Real)
    p=x/n
    b = quantile(Normal(), 1-alpha/2)*sqrt(p*(1-p)/n)
    cc = 0.5 / n
    return p-b-cc, p+b+cc
end
# Agresti-Coull
function ci_prop_ac(x, n, alpha)
    z = quantile(Normal(), 1 - alpha / 2)
    n = n + z^2
    est = (x + z ^ 2 / 2) / n
    se = sqrt(est * (1 - est) / n)
    return (est-z*se, est+z*se)
end

# Jeffreys interval
function ci_prop_jeffrey(x, n, alpha)
    (quantile(Beta(x + 1/2, n - x + 1/2), alpha/2), quantile(Beta(x + 1/2, n - x + 1/2), 1-alpha/2))
end

################################################################################
# Goodman CI
################################################################################
# Goodman, L.A. (1965). On Simultaneous Confidence Intervals for Multinomial Proportions. Technometrics 7: 247-254.
# https://blogs.sas.com/content/iml/2017/02/15/confidence-intervals-multinomial-proportions.html
# https://rdrr.io/cran/CoinMinD/man/GM.html
function ci_prop_goodman(v, alpha::T2) where T2
    k   = length(v)
    s   = sum(v)
    p   = v ./ s
    chi = quantile(Chisq(one(Int)), one(T2) - alpha/k)
    ci  = Vector{Tuple{Float64, Float64}}(undef, k)
    d   = 2 * (chi + s)
     @inbounds @simd for i = 1:k
        ci[i] = ( (chi + 2v[i] - sqrt(chi*chi + 4v[i]*chi*(1.0 - v[i]/s))) / d , (chi + 2v[i] + sqrt(chi*chi + 4v[i]*chi*(1.0 - v[i]/s))) / d )
    end
    ci
end


"""
    box_counting_dim(xg, yg, basin; kwargs...)
This function compute the box-counting dimension of the boundary. It is related to the uncertainty dimension.

[C. Grebogi, S. W. McDonald, E. Ott, J. A. Yorke, Final state sensitivity: An obstruction to predictability, Physics Letters A, 99, 9, 1983]

## Arguments
* `basin` : the matrix containing the information of the basin.
* `xg`, `yg` : 1-dim range vector that defines the grid of the initial conditions to test.

## Keyword arguments
* `kwargs...` these arguments are passed to `generalized_dim` see `ChaosTools.jl` for the docs.

"""
function box_counting_dim(xg, yg, basin; kwargs...)
    # get points coordinates in the boudary
    bnd = get_boundary_filt(basin)
    I1 = findall(bnd .== 1);
    v = Dataset{2,eltype(xg)}()
    for  ind in I1; push!(v, [xg[ind[1]] , yg[ind[2]]]); end;
    return generalized_dim(v; q=0, kwargs...)
end

function box_counting_dim(xg, yg, bsn::BasinInfo; kwargs...)
    ind  = findall(iseven.(bsn.basin) .== true)
    # Not sure it is necessary to deepcopy
    basin_test = deepcopy(bsn.basin)
    # Remove attractors from the picture
    for k in ind; basin_test[k] = basin_test[k]+1; end
    return box_counting_dim(xg, yg, basin_test; kwargs...)
end

"""
    uncertainty_exponent(bsn::BasinInfo, integ; precision=1e-3) -> α,e,ε,f_ε
This function estimates the uncertainty exponent of the boundary. It is related to the uncertainty dimension.

[C. Grebogi, S. W. McDonald, E. Ott, J. A. Yorke, Final state sensitivity: An obstruction to predictability, Physics Letters A, 99, 9, 1983]

## Arguments
* `bsn` : structure that holds the information of the basin.
* `integ` : handle to the iterator of the dynamical system.

## Keyword arguments
* `precision` variance of the estimator of the uncertainty function.

"""
function uncertainty_exponent(bsn::BasinInfo, integ; precision=1e-3)
    xg = bsn.xg; yg = bsn.yg;
    nx=length(xg)
    ny=length(yg)
    xi = xg[1]; xf = xg[end];
    grid_res_x=xg[2]-xg[1]

    num_step=10
    N_u = zeros(1,num_step) # number of uncertain box
    N = zeros(1,num_step) # number of boxes
    ε = zeros(1,num_step) # resolution

    # First we compute a crude estimate to have an idea at which resolution
    D,e,f = static_estimate(xg,yg,bsn);
    println("First estimate: α=",D)

    # resolution in log scale
    # estimate the minimum resolution to estimate a f_ε ∼ 10^-3
    min_ε = -3/D[1]
    @show min_ε = max(min_ε,-7) # limit resolution to 10^-7 to avoid numerical inconsistencies.
    max_ε = log10(grid_res_x*5) # max radius of the ball is roughly 5 pixels.

    r_ε = 10 .^ range(min_ε,max_ε,length=num_step)

    for (k,eps) in enumerate(r_ε)
        Nb=0; Nu=0; μ=0; σ²=0; M₂=0;
        completed = 0;
        # Find uncertain boxes
        while completed == 0
            k1 = rand(1:nx)
            k2 = rand(1:ny)

            c1 = bsn.basin[k1,k2]
            c2 = get_color_point!(bsn, integ, [xg[k1]+eps,yg[k2]])
            c3 = get_color_point!(bsn, integ, [xg[k1]-eps,yg[k2]])

            if length(unique([c1,Int(c2),Int(c3)]))>1
                Nu = Nu + 1
            end
            Nb += 1

            # Welford's online average estimation and variance of the estimator
            M₂ = wel_var(M₂, μ, Nu/Nb, Nb)
            μ = wel_mean(μ, Nu/Nb, Nb)
            σ² = M₂/Nb
            # If the process is binomial, the variance of the estimator is Nu/Nb·(1-Nu/Nb)/Nb (p·q/N)
            # simulations matches
            #push!(sig,Nu/Nb*(1-Nu/Nb)/Nb)

            # Stopping criterion: variance of the estimator of the mean bellow 1e-3
            if Nu > 50 && σ² < precision
                completed = 1
                @show Nu,Nb,σ²
            end

            if Nu < 10 && Nb>10000
                # skip this value, we don't find the boundary
                # corresponds to roughly f_ε ∼ 10^-4
                @warn "Resolution may be too small for this basin, skip value"
                completed = 1
                Nu = 0
            end

        end
        N_u[k]=Nu
        N[k]=Nb
        ε[k]=eps
    end

    # uncertain function
    f_ε = N_u./N

    # remove zeros in case there are:
    ind = f_ε .> 0
    f_ε =  f_ε[ind]
    ε = ε[ind]

    # @show i1,D=linear_region(vec(log10.(ε)), vec(log10.(f_ε)))
    # Dϵ=0.
    # @show i1
    # get exponent from a linear region
    @. model(x, p) = p[1]*x+p[2]
    fit = curve_fit(model, vec(log10.(ε)), vec(log10.(f_ε)), [2., 2.])
    D = coef(fit)
    Dϵ = estimate_errors(fit)
    return D[1], Dϵ[1], vec(log10.(ε)),vec(log10.(f_ε))
end

function wel_var(M₂, μ, xₙ, n)

    μ₂ = μ + (xₙ - μ)/n
    M₂ = M₂ + (xₙ - μ)*(xₙ - μ₂)
    return M₂
end

function wel_mean(μ, xₙ, n)
    return μ + (xₙ - μ)/n
end

function static_estimate(xg,yg,bsn; precision=1e-4)

    xg = bsn.xg; yg = bsn.yg;
    y_grid_res=yg[2]-yg[1]
    nx=length(xg)
    ny=length(yg)

    num_step=10
    N_u = zeros(1,num_step) # number of uncertain box
    N = zeros(1,num_step) # number of boxes
    ε = zeros(1,num_step) # resolution

    # resolution in pixels
    min_ε = 1;
    max_ε = 10;

    r_ε = min_ε:max_ε
    #@show r_ε = 10 .^ range(log10(min_ε),log10(max_ε),length=num_step)
    for (k,eps) in enumerate(r_ε)
        Nb=0; Nu=0; μ=0; σ²=0; M₂=0;
        completed = 0;
        # Find uncertain boxes
        while completed == 0
            kx = rand(1:nx)
            ky = rand(ceil(Int64,eps+1):floor(Int64,ny-eps))

            indy = range(ky-eps,ky+eps,step=1)
            c = [bsn.basin[kx,ky] for ky in indy]

            if length(unique(c))>1
                Nu = Nu + 1
            end
            Nb += 1

            # Welford's online average estimation and variance of the estimator
            M₂ = wel_var(M₂, μ, Nu/Nb, Nb)
            μ = wel_mean(μ, Nu/Nb, Nb)
            σ² = M₂/Nb

            # Stopping criterion: variance of the estimator of the mean bellow 1e-3
            if Nu > 50 && σ² < precision
                completed = 1
                #@show Nu,Nb,σ²
            end

        end
        N_u[k]=Nu
        N[k]=Nb
        ε[k]=eps*y_grid_res
    end

    # uncertain function
    f_ε = N_u./N

    # remove zeros in case there are:
    ind = f_ε .> 0.
    f_ε =  f_ε[ind]
    ε = ε[ind]
    # get exponent
    @. model(x, p) = p[1]*x+p[2]
    fit = curve_fit(model, vec(log10.(ε)), vec(log10.(f_ε)), [2., 2.])
    D = coef(fit)
    @show estimate_errors(fit)
    #D = linear_region(vec(log10.(ε)), vec(log10.(f_ε)))
    return D[1],vec(log10.(ε)), vec(log10.(f_ε))

end

function ball_estimate(xg,yg,bsn; precision = 1e-4)

    xg = bsn.xg; yg = bsn.yg;
    xf=xg[end];xi=xg[1];yf=yg[end];yi=yg[1];
    y_grid_res=yg[2]-yg[1]
    nx=length(xg)
    ny=length(yg)

    num_step=10
    N_u = zeros(1,num_step) # number of uncertain box
    N = zeros(1,num_step) # number of boxes
    ε = zeros(1,num_step) # resolution

    # resolution in pixels
    min_ε = 1;
    max_ε = 10;
    @show r_ε = 10 .^ range(log10(min_ε),log10(max_ε),length=num_step)
    #r_ε = (min_ε:max_ε)*y_grid_res

    for (k,eps) in enumerate(r_ε)
        Nb=0; Nu=0; μ=0; σ²=0; M₂=0;
        completed = 0;
        c_ind = get_ind_in_circle(eps)
        # Find uncertain boxes
        while completed == 0

            kx = rand()*((nx-eps)-(1+eps))+1+eps
            ky = rand()*((ny-eps)-(1+eps))+1+eps

            # I = get_indices_in_range(kx, ky, eps, c_ind)
            # c = [ bsn.basin[n[1],n[2]] for n in eachrow(I)]

            Ix,Iy = get_indices_in_range(kx, ky, eps, c_ind)
            c = [ bsn.basin[n,m] for n in Ix, m in Iy]

            if length(unique(c))>1
                Nu = Nu + 1
            end
            Nb += 1

            # Welford's online average estimation and variance of the estimator
            M₂ = wel_var(M₂, μ, Nu/Nb, Nb)
            μ = wel_mean(μ, Nu/Nb, Nb)
            σ² = M₂/Nb

            # Stopping criterion: variance of the estimator of the mean bellow 1e-3
            if Nu > 50 && σ² < precision
                completed = 1
                @show Nu,Nb,σ²
            end

        end
        N_u[k]=Nu
        N[k]=Nb
        ε[k]=eps*y_grid_res
    end

    # uncertain function
    f_ε = N_u./N

    # remove zeros in case there are:
    ind = f_ε .> 0.
    f_ε =  f_ε[ind]
    ε = ε[ind]
    # get exponent
    @. model(x, p) = p[1]*x+p[2]
    fit = curve_fit(model, vec(log10.(ε)), vec(log10.(f_ε)), [2., 2.])
    D = coef(fit)
    @show estimate_errors(fit)
    return D[1],vec(log10.(ε)), vec(log10.(f_ε))

end

function get_indices_in_range(kx, ky, eps,c_ind)

    # center and scale

    #indx = ceil.(Int64,(kx-eps):1.:(kx+eps))
    #indy = ceil.(Int64,(ky-eps):1.:(ky+eps))
    indx = range(ceil.(Int64,kx-eps),ceil(Int64,kx+eps)-1,step=1)
    indy = range(ceil.(Int64,ky-eps),ceil(Int64,ky+eps)-1,step=1)

    #I = deepcopy(c_ind)

    #Find indices in a circle of radius eps
    #I[:,1] .= I[:,1] .+ ceil(Int64,kx)
    #I[:,2] .= I[:,2] .+ ceil(Int64,ky)
    #@show [[n[1],n[2]] for n in I]

    return indx,indy
    #return I
end

function get_ind_in_circle(r)
    #Find indices in a circle of radius eps
    I = Array{Int64,1}[]
    r_l = ceil(Int16,r)
    for n in -r_l:r_l, m in -r_l:r_l
        if n^2+m^2 ≤ r^2
            push!(I, [n; m])
        end
    end
    v= hcat(I...)'
    return v
end


export ReflectGate, reflect

"""
    ReflectGate{N, T, Tr} <: PrimitiveBlock{N}

Reflection operator to target state `psi`.

# Definition

```math
|ψ⟩ → 2 |s⟩⟨s| - 1
```
"""
struct ReflectGate{N, T, Tr <: ArrayReg{1, T}} <: PrimitiveBlock{N}
    psi::Tr
end

"""
    ReflectGate(r::ArrayReg{1})

Create a [`ReflectGate`](@ref) with a quantum register `r`.
"""
ReflectGate(r::ArrayReg{1, T}) where T = ReflectGate{nqubits(r), T, typeof(r)}(r)

"""
    ReflectGate(r::AbstractVector)

Create a [`ReflectGate`](@ref) with a quantum state vector `v`.
"""
ReflectGate(v::AbstractVector{<:Complex}) = ReflectGate(ArrayReg(v))

"""
    reflect(r::ArrayReg)

Create a [`ReflectGate`](@ref) with an [`ArrayReg`](@ref).
"""
reflect(r::ArrayReg) = reflect(statevec(r))

"""
    reflect(v::AbstractVector{<:Complex})

Create a [`ReflectGate`](@ref) with an quantum state vector `v`.

# Example

```jldoctest; setup=:(using YaoBlocks; using YaoArrayRegister)
julia> reflect(rand_state(3))
┌ Warning: `ReflectGate` will be moved to `YaoExtensions.jl` in the next release.
└ @ YaoBlocks ~/.julia/packages/YaoBlocks/XXC9Y/src/primitive/reflect_gate.jl:19
reflect(ArrayReg{1, Complex{Float64}, Array...})
```
"""
reflect(v::AbstractVector{<:Complex}) = ReflectGate(v)

function apply!(r::ArrayReg, g::ReflectGate{N, T, <:ArrayReg}) where {N, T}
    v = state(g.psi)
    r.state .= 2 .* (v' * r.state) .* v - r.state
    return r
end

# target type is the same with block's
function mat(::Type{T}, r::ReflectGate{N, T}) where {N, T}
    v = statevec(r.psi)
    return 2 * v * v' - IMatrix(length(v))
end

# different
function mat(::Type{T1}, r::ReflectGate{N, T2}) where {N, T1, T2}
    M = mat(T2, r)
    return copyto!(similar(M, T1), M)
end

Base.:(==)(A::ReflectGate, B::ReflectGate) = A.psi == B.psi
Base.copy(r::ReflectGate) = ReflectGate(r.psi)

Yao.isreflexive(::ReflectGate) = true
Yao.ishermitian(::ReflectGate) = true
Yao.isunitary(::ReflectGate) = true


@test isadmtup([0,2,4]) == false
@test isadmtup([0,2,6,8,12]) == true
@test isadmtup([3, 5, 11, 17, 29]) == false
@test isadmtup([5, 7, 13, 19, 31]) == false
@test isadmtup([5,7,11,13,17]) == true


using StructuralCausalModels, Test

ProjDir = @__DIR__
cd(ProjDir) #do

fname = scm_path("..", "examples", "Shipley", "fig2.6", "fig2.6.dot")
#Sys.isapple() && run(`open -a GraphViz.app $(fname)`)

# Read `=>` as `~` in regression models, or `<-` in causal models.
d = OrderedDict(
  :u => [:x, :v],
  :s1 => [:u],
  :w => [:v, :y],
  :s2 => [:w]
);

dag = DAG("fig2.6", d);

@testset "m_separation_02" begin

  @test to_ggm(dag) == "DAG(u ~ x + v, s1 ~ u, w ~ v + y, s2 ~ w)"
  @test m_separation(dag, :x, :v; debug=false) == true
  @test m_separation(dag, :x, [:v]; c=:u, debug=false) == false
  @test m_separation(dag, [:x], :v; c=:s1, debug=false) == false
  @test m_separation(dag, [:u], [:w]; debug=false) == false
  @test m_separation(dag, :u, [:w]; c=:v, debug=false) == true

  @test m_separation(dag, [:x], :y) == true
  @test m_separation(dag, :x, :y; c=[:u, :w], debug=false) == false
  @test m_separation(dag, :x, :y; c=[:s1, :s2], debug=false) == false
  @test m_separation(dag, :x, :y; c=[:u, :v, :w], debug=false) == true
  @test m_separation(dag, :x, [:v]; c=:u, debug=false) == false
  @test m_separation(dag, [:x], :v; c=:s1, debug=false) == false

end


function k_statistics(a, k::Int)
    if length(a) < 2
        return a[1]
    end
    a, i, j = partsort!(a, a[(1+length(a)) ÷ 2])
    if k <= i
        return k_statistics(a[1:i], k)
    elseif i < k < j+1
        return a[k]
    else
        return k_statistics(a[j+1:end], k - j)
    end
end

function median(a)
    if length(a) % 2 == 0
        return k_statistics(a, length(a) ÷ 2)
    else
        return k_statistics(a, (length(a) ÷ 2 + 1))
    end
end


# calculateposterior.jl

"""
    calculatedependentfaberschauderfunctions(higestlevel::Int64)

Internal function, not exported.

Calculates which Faber-Schauder functions (ψ_i,ψ_j) have essential overlapping
support, i<j. All other combinations (ψ_i, ψ_j), i<j, have essential
nonoverlapping support and hence int_0^T ψ_i(X_t)ψ_j(X_t)dt=0, which we use in
calculategirsanovmatrix.

Returns a triple (lengthvectors, rowindices, columnindices) where
```julia
lengthvectors == length(rowindices) == length(columnindices),
```
and for i in 1:lengthvectors (ψ_rowindices[i],ψ_columnindices[i]) have
essentially overlapping support. For all i, rowindices[i] < columnindices[i].
"""
function calculatedependentfaberschauderfunctions(higestlevel::Int64)
    lengthvectors = higestlevel*2^(higestlevel+1)+1
    rowindices = Vector{Int}(lengthvectors)
    columnindices = Vector{Int}(lengthvectors)
    index = 1
    # column value need to be strictly greater than row value.
    # psi_1 is dependent with every Faber-Schauder function.
    for i in 2:2^(higestlevel+1)
        rowindices[index] = 1
        columnindices[index] = i
        index += 1
    end
    # psi_{j,k}=psi_{2^j+k} is dependent with
    # psi_{j+d,(k-1)2^d+1},...,psi_{j+d, k2^d}, d\ge1.
    for jone in 0:higestlevel-1
        twotothepowerjone = 2^jone
        for kone in 1:2^jone
            ione = twotothepowerjone+kone
            for  jtwo in jone+1:higestlevel
                d = jtwo - jone
                twotothepowerjtwo = 2^jtwo
                for ktwo in (kone-1)*2^d+1:kone*2^d
                    rowindices[index] = ione
                    columnindices[index] = twotothepowerjtwo+ktwo
                    index += 1
                end
            end
        end
    end
    return (lengthvectors, rowindices, columnindices)
end
#test written

# calculategirsanovmatrixelement methods:

"""
    calculategirsanovmatrixelement(ψ1Xt, ψ2Xt, σ, Δt)
    calculategirsanovmatrixelement(samplevalueindices::BitArray{1},
        ψ1Xt, ψ2Xt, σ, Δt)

Internal function, not exported!

Where ψ1Xt, ψ2Xt are AbstractArray{Float64}, and σ and Δt are either an
AbstractArray{Float64} or a Float64.

Let ψ_a and ψ_b denote two basis elements.
calculategirsanovmatrixelement calculates the (a,b) element of the Girsanov
matrix defined by int_0^T ψ_a(X_t)ψ_b(X_t)/(σ^2(X_t)) dt, where ψ_a is given
by ψ1Xt (already evaluated in X) and ψ_b by ψ2Xt (already evaluated in X).
"""
function calculategirsanovmatrixelement(
        ψ1Xt::S,
        ψ2Xt::T,
        σ::Float64,
        Δt::Float64) where {S<:AbstractArray{Float64},
            T<:AbstractArray{Float64}}
    return Δt * sum(ψ1Xt .* ψ2Xt) / (σ * σ)
end
#test written

function calculategirsanovmatrixelement(
        ψ1Xt::R,
        ψ2Xt::S,
        σ::Float64,
        Δt::T) where {R<:AbstractArray{Float64}, S<:AbstractArray{Float64},
            T<:AbstractArray{Float64}}
    return sum(Δt .* ψ1Xt .* ψ2Xt) / (σ * σ)
end
#test written

function calculategirsanovmatrixelement(
        ψ1Xt::R,
        ψ2Xt::S,
        σXt::T,
        Δt::Float64) where {R<:AbstractArray{Float64},
            S<:AbstractArray{Float64}, T<:AbstractArray{Float64}}
    return Δt * sum(ψ1Xt .* ψ2Xt ./ (σXt .* σXt))
end
#test written

function calculategirsanovmatrixelement(
        ψ1Xt::Q,
        ψ2Xt::R,
        σXt::S,
        Δt::T) where {Q<:AbstractArray{Float64}, R<:AbstractArray{Float64},
            S<:AbstractArray{Float64}, T<:AbstractArray{Float64}}
    return sum(Δt .* ψ1Xt .* ψ2Xt ./ (σXt .* σXt))
end
#test written

function calculategirsanovmatrixelement(
        samplevalueindices::BitArray{1},
        ψ1Xt::S,
        ψ2Xt::T,
        σ::Float64,
        Δt::Float64) where {S<:AbstractArray{Float64}, T<:AbstractArray{Float64}}
    filteredψ1Xt = ψ1Xt[samplevalueindices]
    filteredψ2Xt = ψ2Xt[samplevalueindices]
    return Δt * sum(filteredψ1Xt .* filteredψ2Xt) / (σ * σ)
end
#test written

function calculategirsanovmatrixelement(
        samplevalueindices::BitArray{1},
        ψ1Xt::R,
        ψ2Xt::S,
        σ::Float64,
        Δt::T) where {R<:AbstractArray{Float64}, S<:AbstractArray{Float64},
            T<:AbstractArray{Float64}}
    filteredΔt = Δt[samplevalueindices]
    filteredψ1Xt = ψ1Xt[samplevalueindices]
    filteredψ2Xt = ψ2Xt[samplevalueindices]
    return sum(filteredΔt .* filteredψ1Xt .* filteredψ2Xt) / (σ * σ)
end
#test written

function calculategirsanovmatrixelement(
        samplevalueindices::BitArray{1},
        ψ1Xt::R,
        ψ2Xt::S,
        σXt::T,
        Δt::Float64) where {R<:AbstractArray{Float64}, S<:AbstractArray{Float64},
            T<:AbstractArray{Float64}}
    filteredψ1Xt = ψ1Xt[samplevalueindices]
    filteredψ2Xt = ψ2Xt[samplevalueindices]
    filteredσXt = σXt[samplevalueindices]
    return Δt * sum(filteredψ1Xt .* filteredψ2Xt ./
        (filteredσXt .* filteredσXt))
end
#test written

function calculategirsanovmatrixelement(
        samplevalueindices::BitArray{1},
        ψ1Xt::Q,
        ψ2Xt::R,
        σXt::S,
        Δt::T) where {Q<:AbstractArray{Float64}, R<:AbstractArray{Float64},
            S<:AbstractArray{Float64}, T<:AbstractArray{Float64}}
    filteredψ1Xt = ψ1Xt[samplevalueindices]
    filteredψ2Xt = ψ2Xt[samplevalueindices]
    filteredσXt = σXt[samplevalueindices]
    filteredΔt = Δt[samplevalueindices]
    return sum(filteredΔt .* filteredψ1Xt .* filteredψ2Xt ./ (
        filteredσXt .* filteredσXt))
end
#test written

"""
    calculateΔt(timeinterval::Range{Float64}) = step(timeinterval)
    calculateΔt(timeinterval::AbstractArray{Float64}) = timeinterval[2:end] -
        timeinterval[1:end-1]

Internal function, not exported!

Calculated Δt, returns a Float64, when timeinterval is a range object, and
wise the increments of the timeinterval vector.
"""
calculateΔt(timeinterval::Range{Float64}) = step(timeinterval)
calculateΔt(timeinterval::AbstractArray{Float64}) = timeinterval[2:end] -
    timeinterval[1:end-1]
# Test written.

# calculategirsanovmatrix methods:

"""
    calculategirsanovmatrix(
        Π::FaberSchauderExpansionWithGaussianCoefficients,
        samplevalueindices::Vector{BitArray{1}},
        X::SamplePath,
        ψXt::Vector{Array{Float64,1}},
        σXt::T) where {T<:Union{S, Float64} where {S<:AbstractArray{Float64}}}
    calculategirsanovmatrix(
        Π::AbstractGaussianProcess,
        X::SamplePath,
        ψXt::S,
        σXt::T) where {S<:AbstractArray{U} where {U<:AbstractArray{Float64}},
            T<:Union{Float64, V} where {V<:AbstractArray{Float64}}}

Internal function, not exported!

Calculates the Girsanov matrix. In case of a
FaberSchauderExpansionWithGaussianCoefficients-object it will make use of the
sparsity structure of the Faber-Schauder basis.
"""
function calculategirsanovmatrix(
        sizesquarematrix::Int,
        timeinterval::R,
        ψXt::S,
        σXt::T) where {R<:AbstractArray{Float64}, S<:AbstractArray{U} where U<:AbstractArray{Float64}, T<:Union{Float64, V} where V<:AbstractArray{Float64}}
    girsanovmatrix = Array{Float64}(sizesquarematrix, sizesquarematrix)
    Δt = calculateΔt(timeinterval)
    for k in 1:sizesquarematrix
        for l in k:sizesquarematrix
            girsanovmatrix[k,l] = calculategirsanovmatrixelement(
                ψXt[k], ψXt[l], σXt, Δt)
        end
    end
    return Symmetric(girsanovmatrix)
end

function calculategirsanovmatrix(
        Π::FaberSchauderExpansionWithGaussianCoefficients,
        samplevalueindices::Vector{BitArray{1}},
        timeinterval::S,
        ψXt::Vector{Array{Float64,1}},
        σXt::T) where {S<:AbstractArray{Float64},
            T<:Union{U, Float64} where {U<:AbstractArray{Float64}}}
    lengthvectors, rowindices, columnindices =
        calculatedependentfaberschauderfunctions(Π.higestlevel)
    d = length(Π)
    numberofnonzeroelements = (2*Π.higestlevel+1)*d+2
    V = Vector{Float64}(numberofnonzeroelements)
    Δt = calculateΔt(timeinterval)
    for i in 1:d
        V[i] = calculategirsanovmatrixelement(samplevalueindices[i],
            ψXt[i], ψXt[i], σXt, Δt)
    end
    rowindices, columnindices = vcat(1:d, rowindices, columnindices),
        vcat(1:d, columnindices, rowindices)
    for i in d+1:d+lengthvectors
        V[i+lengthvectors] = V[i] = calculategirsanovmatrixelement(
            samplevalueindices[columnindices[i]], ψXt[rowindices[i]],
            ψXt[columnindices[i]], σXt, Δt)
    end
    return sparse(rowindices, columnindices, V, d, d)
end

"""
    calculategirsanovvectorelement(
        samplevalueindices::BitArray{1},
        ΔXt::R,
        ψXt::S,
        σXt::T) where {R<:AbstractArray{Float64}, S<:AbstractArray{Float64},
            T<:AbstractArray{Float64}}
    calculategirsanovvectorelement(
        samplevalueindices::BitArray{1},
        ΔXt::S,
        ψXt::T,
        σ::Float64) where {S<:AbstractArray{Float64}, T<:AbstractArray{Float64}}
    calculategirsanovvectorelement(
        ΔXt::R,
        ψXt::S,
        σXt::T) where {R<:AbstractArray{Float64}, S<:AbstractArray{Float64},
            T<:AbstractArray{Float64}}
    calculategirsanovvectorelement(
        ΔXt::S,
        ψXt::T,
        σ::Float64) where {S<:AbstractArray{Float64}, T<:AbstractArray{Float64}}

Internal function, used by calculategirsanovvector, not exported!

Calculates the kth Girsanov vector element int_0^T ψ_k(X_t)/(σ^2(X_t)) dX_t,
where ψ_k is the kth basis function and T is the end time.
"""
function calculategirsanovvectorelement(
    ΔXt::R,
    ψXt::S,
    σXt::T) where {R<:AbstractArray{Float64}, S<:AbstractArray{Float64},
        T<:AbstractArray{Float64}}
    return sum(ΔXt .* ψXt ./ (σXt .* σXt))
end
#Test written.

function calculategirsanovvectorelement(
    ΔXt::S,
    ψXt::T,
    σ::Float64) where {S<:AbstractArray{Float64}, T<:AbstractArray{Float64}}
    return sum(ΔXt .* ψXt) / (σ * σ)
end
#Test written.

function calculategirsanovvectorelement(
        samplevalueindices::BitArray{1},
        ΔXt::S,
        ψXt::T,
        σ::Float64) where {S<:AbstractArray{Float64}, T<:AbstractArray{Float64}}
    filteredΔXt = ΔXt[samplevalueindices]
    filteredψXt = ψXt[samplevalueindices]
    return sum(filteredΔXt .* filteredψXt) / (σ*σ)
end
#Test written.

function calculategirsanovvectorelement(
        samplevalueindices::BitArray{1},
        ΔXt::R,
        ψXt::S,
        σXt::T) where {R<:AbstractArray{Float64}, S<:AbstractArray{Float64},
            T<:AbstractArray{Float64}}
    filteredΔXt = ΔXt[samplevalueindices]
    filterψXt = ψXt[samplevalueindices]
    filteredσXt = σXt[samplevalueindices]
    return sum(filteredΔXt .* filterψXt ./ (filteredσXt
        .* filteredσXt))
end
#Test written.

"""
    calculategirsanovvector(
        lengthvector::Int,
        samplevalueindices::Vector{BitArray{1}},
        samplevalues::S,
        ψXt::Vector{Array{Float64,1}},
        σXt::T) where {S<:AbstractArray{Float64}, T<:Union{Float64, U} where
            U<:AbstractArray{Float64}}
    calculategirsanovvector(
        lengthvector::Int,
        samplevalues::R,
        ψXt::S,
        σXt::T) where {R<:AbstractArray{Float64}, S<:AbstractArray{U} where
            U<:AbstractArray{Float64}, T<:Union{Float64, V} where
            V<:AbstractArray{Float64}}

Internal function, not exported!

Calculates the Girsanov vector.
"""
function calculategirsanovvector(
        lengthvector::Int,
        samplevalues::R,
        ψXt::S,
        σXt::T) where {R<:AbstractArray{Float64}, S<:AbstractArray{U} where
            U<:AbstractArray{Float64}, T<:Union{Float64, V} where
            V<:AbstractArray{Float64}}
    ΔXt = samplevalues[2:end] - samplevalues[1:end-1]
    return [calculategirsanovvectorelement(ΔXt, ψXt[k], σXt) for k in
        1:lengthvector]
end
# Test written.

function calculategirsanovvector(
        lengthvector::Int,
        samplevalueindices::Vector{BitArray{1}},
        samplevalues::S,
        ψXt::Vector{Array{Float64,1}},
        σXt::T) where {S<:AbstractArray{Float64}, T<:Union{Float64, U} where
            U<:AbstractArray{Float64}}
    ΔXt = samplevalues[2:end] - samplevalues[1:end-1]
    return [calculategirsanovvectorelement(samplevalueindices[k], ΔXt, ψXt[k],
        σXt) for k in 1:lengthvector]
end
# Test written.

"""
    calculateσXt(σ::Float64, v::AbstractArray{Float64}) = σ
    calculateσXt(σ::Function, v::AbstractArray{Float64}) = σ.(v)

Internal function, not exported!
"""
calculateσXt(σ::Float64, v::AbstractArray{Float64}) = σ
calculateσXt(σ::Function, v::AbstractArray{Float64}) = σ.(v)
#Tests written.

"""
    calculateposterior(Π::FaberSchauderExpansionWithGaussianCoefficients,
        X::SamplePath, model::SDEModel)
    calculateposterior(Π::AbstractGaussianProcess,
        X::SamplePath, model::SDEModel)::GaussianProcess

Calculates the posterior distribution Π(⋅∣X), and returns it as a
GaussianProcess object. Uses model to determine the right likelihood.

# Examples

##Example Faber-Schauder expansion
```julia
model = SDEModel(1.0, 0.0, 10000.0, 0.1)
sde = SDE(x->sinpi(2*x), model)
X = rand(sde)
Π = FaberSchauderExpansionWithGaussianCoefficients([2.0^(-j) for j in 0:5])
postΠ = calculateposterior(Π, X, model)
```
##Example Fourier expansion
```julia
model = SDEModel(1.0, 0.0, 10000.0, 0.1)
sde = SDE(x->sinpi(2*x), model)
X = rand(sde)
distribution = GaussianVector(diagm([k^(-1.0) for k in 1.0:50.0]))
Π = GaussianProcess([fourier(k) for k in 1:50], distribution)
postΠ = calculateposterior(Π, X, model)
```
"""
function calculateposterior(Π::AbstractGaussianProcess,
        X::SamplePath, model::SDEModel)::GaussianProcess
    σXt = calculateσXt(model.σ, X.samplevalues[1:end-1])
    ψXt = [f.(X.samplevalues[1:end-1]) for f in Π.basis]
    lengthΠ = length(Π)
    girsanovvector = calculategirsanovvector(lengthΠ, X.samplevalues, ψXt, σXt)
    girsanovmatrix = calculategirsanovmatrix(lengthΠ, X.timeinterval, ψXt, σXt)
    precisionmatrixposterior = full(girsanovmatrix) + inv(full(Π.distribution.var))
    potentialposterior = girsanovvector + full(Π.distribution.mean)
    meanposterior = precisionmatrixposterior \ potentialposterior
    posteriordistribution = GaussianVector(meanposterior,
        precisionmatrixposterior^(-0.5))
    return GaussianProcess(Π.basis, posteriordistribution)
end

function calculateposterior(Π::FaberSchauderExpansionWithGaussianCoefficients,
        X::SamplePath, model::SDEModel)
    samplevaluesmod1 = mod.(X.samplevalues[1:end-1], 1.0)
    σXt = calculateσXt(model.σ, X.samplevalues[1:end-1])
    ψXt = [f.(X.samplevalues[1:end-1]) for f in Π.basis]
    samplevalueindices = [Π.leftboundssupport[i] .≤
        samplevaluesmod1 .≤ Π.rightboundssupport[i] for i in 1:length(Π)]
    girsanovvector = calculategirsanovvector(length(Π), samplevalueindices,
        X.samplevalues, ψXt, σXt)
    girsanovmatrix = calculategirsanovmatrix(Π, samplevalueindices,
        X.timeinterval, ψXt, σXt)
    precisionmatrixposterior = full(girsanovmatrix) + inv(full(Π.distribution.var))
    potentialposterior = girsanovvector + mean(Π.distribution)
    meanposterior = precisionmatrixposterior \ potentialposterior
    posteriordistribution = GaussianVector(meanposterior,
        precisionmatrixposterior^(-0.5))
    return FaberSchauderExpansionWithGaussianCoefficients(Π.higestlevel, posteriordistribution)
end


"""
    KernelProduct(kernels::Array{Kernel})

Create a product of kernels.
One can also use the operator `*` :
```
    k1 = SqExponentialKernel()
    k2 = LinearKernel()
    k = KernelProduct([k1, k2]) == k1 * k2
    kernelmatrix(k, X) == kernelmatrix(k1, X) .* kernelmatrix(k2, X)
    kernelmatrix(k, X) == kernelmatrix(k1 * k2, X)
```
"""
struct KernelProduct <: Kernel
    kernels::Vector{Kernel}
end

Base.:*(k1::Kernel,k2::Kernel) = KernelProduct([k1,k2])
Base.:*(k1::KernelProduct,k2::KernelProduct) = KernelProduct(vcat(k1.kernels,k2.kernels)) #TODO Add test
Base.:*(k::Kernel,kp::KernelProduct) = KernelProduct(vcat(k,kp.kernels))
Base.:*(kp::KernelProduct,k::Kernel) = KernelProduct(vcat(kp.kernels,k))

Base.length(k::KernelProduct) = length(k.kernels)

(κ::KernelProduct)(x, y) = prod(k(x, y) for k in κ.kernels)

function kernelmatrix(κ::KernelProduct, x::AbstractVector)
    return reduce(hadamard, kernelmatrix(κ.kernels[i], x) for i in 1:length(κ))
end

function kernelmatrix(κ::KernelProduct, x::AbstractVector, y::AbstractVector)
    return reduce(hadamard, kernelmatrix(κ.kernels[i], x, y) for i in 1:length(κ))
end

function kerneldiagmatrix(κ::KernelProduct, x::AbstractVector)
    return reduce(hadamard, kerneldiagmatrix(κ.kernels[i], x) for i in 1:length(κ))
end

function Base.show(io::IO, κ::KernelProduct)
    printshifted(io, κ, 0)
end

function printshifted(io::IO, κ::KernelProduct, shift::Int)
    print(io, "Product of $(length(κ)) kernels:")
    for i in 1:length(κ)
        print(io, "\n" * ("\t" ^ (shift + 1))* "- ")
        printshifted(io, κ.kernels[i], shift + 2)
    end
end


struct Blade{T}<:GeometricAlgebraType
    coef::T
    index::Int
end

scalar(coef) = Blade(coef, 1)

is_scalar(b::Blade) = b.index == 1

eltype(::Type{<:Blade{T}}) where {T} = T
eltype(b::Blade) = eltype(typeof(b))

grade(::Number) = 0

is_homogeneous(::Blade) = true

(==)(x::Blade, y::Blade) = x.index == y.index && x.coef == y.coef
(≈)(x::Blade, y::Blade; kwargs...) = x.index == y.index && isapprox(x.coef, y.coef; kwargs...)

Base.iszero(b::Blade) = b.coef ≈ 0

struct KVector{K,V<:MVector}<:GeometricAlgebraType
    coefs::V
    start::Int
end

KVector{K}(coefs::AbstractVector) where {K} = KVector{K}(MVector{length(coefs)}(coefs))

const Bivector = KVector{2}
const Trivector = KVector{3}
const Quadvector = KVector{4}

vectype(::Type{<:KVector{<:Any,V}}) where {V} = V

eltype(T::Type{<:KVector}) = eltype(vectype(T))

length(T::Type{<:KVector}) = length(vectype(T))

grade(::Type{<:KVector{K}}) where {K} = K
grades(T::Type{<:KVector}) = [grade(T)]

# define methods for instances
for f ∈ [:eltype, :length, :grade, :grades]
    @eval begin
        $f(kvec::KVector) = $f(typeof(kvec))
    end
end

setindex!(kvec::KVector, args...) = setindex!(kvec.coefs, args...)

store!(kvec::KVector, b::Blade) = setindex!(kvec, b.coef, value(kvec, b))

function add!(kvec::KVector, b::Blade)
    i = index(kvec, b)
    setindex!(kvec, kvec[i] + b.coef, i)
end

getindex(kvec::KVector, args...) = getindex(kvec.coefs, args...)

index(kvec::KVector, i) = i - (kvec.start - 1)
index(kvec::KVector, b::Blade) = b.index - (kvec.start - 1)

value(x, i) = x[index(x, i)]

blades(x::GeometricAlgebraType) = (Blade(c, i) for (c, i) ∈ zip(x.coefs, indices(x)))

nonzero_blades(x::GeometricAlgebraType) = (b for b ∈ blades(x) if !iszero(b))

is_homogeneous(::KVector) = true

indices(kvec::KVector) = (kvec.start):(kvec.start + length(kvec) - 1)

function show(io::IO, @nospecialize kvec::KVector)
    if kvec == zero(typeof(kvec), eltype(kvec))
        print(io, string(scalar(zero(eltype(kvec)))))
    else
        print(io, join(string.(blades(kvec)), " + "))
    end
end