"""
Computes first and second moments of `` \\exp[Jx_{i}x_{j} + h_{i}x_{i} +h_{j}x_{j} -\\frac12 x^t \\cdot S \\cdot x + x^t \\cdot y] ``

Input:
* `F::PairIsing <:Factor`: Factor node ``\\psi_{ij}``
* `y::Vector{Float64}`: Array of cavity fields ``\\in \\mathbb{R}^{ 2 }``
* `S::Vector{Float64}`: Cavity coupling matrix ``\\in \\mathbb{R}^{  2 \\times 2 }``
"""
function moments(F::IsingPair, y::Vector{Float64}, S::Array{Float64,2})
    mi = tanh(F.hi + y[1] + atanh(tanh(F.J - S[1,2]) * tanh(F.hj + y[2])))
    mj = tanh(F.hj + y[2] + atanh(tanh(F.J - S[1,2]) * tanh(F.hi + y[1])))
    cij = tanh(F.J - S[1,2] + atanh(tanh(F.hj + y[2]) * tanh(F.hi + y[1]))) - mi*mj
    return [mi;mj],[1-mi^2 cij; cij 1-mj^2]
end

"""
Computes first and second moments of `` \\exp[-\\psi.E(x) -\\frac12 x^t \\cdot S \\cdot x + x^t \\cdot y] ``

Input:
* `F::EnergyFun <:Factor`: Factor node ``\\psi_{a}``
* `y::Vector{Float64}`: Array of cavity fields ``\\in \\mathbb{R}^{ |\\partial a| }``
* `S::Vector{Float64}`: Cavity coupling matrix ``\\in \\mathbb{R}^{  |\\partial a| \\times |\\partial a| }``

Output: first and second moments under tilted "a" distribution -> `av, cov`
where `av` is a vector of size ``|\\partial a|``, `cov`  is a ``|\\partial a| \\times |\\partial a|`` matrix }
"""
function moments(F::EnergyFun, y::Vector{Float64}, S::Array{Float64,2})
    n = length(F.idx)
    N = 1<<n
    av = zeros(n)
    cov = zeros(n,n)
    z = 0
    X = [ [2*((i >> k)%2)-1 for k in 0:(n-1)] for i=0:(N-1) ]
    Y = [ -F.E(X[i]) + X[i]'*(y - 0.5*S*X[i]) for i=1:N ]
    m = maximum(Y)
    @inbounds for i=1:N
        px = exp(Y[i]-m)
        @assert(px>=0)
        z += px
        av .+= X[i] * px
        cov .+= (X[i]*X[i]') * px
    end
    av ./= z
    cov ./= z
    cov .-= av*av'
    return av, cov
end

pearsonmap(x) = sqrt(1-x)/(atanh(sqrt(1-x))*x)

"""
Update the parameters of the gaussian factor "a" according to closure.

Input:

* `F::EnergyFun <:Factor`: Factor node ``\\psi_{a}``
* `y::Vector{Float64}`: Array of cavity fields of size ``|\\partial a|``
* `S::Vector{Float64}`: Cavity coupling matrix of size ``|\\partial a| \\times |\\partial a|``
* `μta::Vector{Float64}`: tilted magnetizations, size ``|\\partial a|`` -> gets updated as ref.
* `Σta::Matrix{Float64}`: Correlation matrix , size ``|\\partial a| \\times |\\partial a|`` -> gets updated as ref.
* `closure::Symbol`: closure protocol
* `η::Float64`: interpolation parameter (valid on :DC closure) -> ``η=0`` gives BP fixed points
* `epsclamp::Float64` clamp tilted moments in [-1+epsclamp, 1-epsclamp]
* `epsconv::Float64`: precision convergence
* `epsmom::Float64`: error over moments at iteration t

Output:

* `ynew::Vector{Float64}`: fields of gauss. factor a @iter t+1, size ``|\\partial a|``
* `Snew::Vector{Float64}`: coupling matrix of gauss. factor a @iter t+1, ``|\\partial a| \\times |\\partial a|``
* `epsmom::Float64`: error over moments at iteration t+1

"""
function setclosure!(F::Factor, y::Vector{Float64},
                        S::Array{Float64,2},
                        µta::Vector{Float64},
                        Σta::Array{Float64,2},
                        closure::Symbol,
                        η::Float64,
                        λ::Float64,
                        epsclamp::Float64,
                        epsmom::Float64)

  av,cov = moments(F, y, S)
  epsmom = max(epsmom , update!(μta,av,0.0), update!(Σta,cov,0.0))
    if closure == :DC
        var = sqrt.(pearsonmap.(clamp.(diag(cov), epsclamp, 1-epsclamp)))
        cov .*= var * var'
        cov = η*cov+(1-η)*Diagonal(cov)
        icov = inv(cov + λ * I)
        Snew = icov-S
        ynew = icov * av - y
    elseif closure == :DC2
        cov = η*cov + (1-η) * Diagonal(cov)
        icov = inv(cov + λ * I)
        means = diag(cov).*atanh.(clamp.(av, -1+epsclamp,1-epsclamp))
        Snew = icov - S
        ynew = icov * means - y
    elseif closure == :EP
        av, cov = moments(F,y,S)
        icov = inv(cov + λ*I)
        Snew = icov - S;
        ynew = icov * av - y
    else
        throw("Select a valid closure")
    end
    return ynew,Snew,epsmom
end


module Hidden
    export is_sum_of_cubes

    function is_cubic(n::Int)::Bool
        n == mapreduce(x -> x^3, +, digits(n))
    end

    function format_return(cubics::Vector{Int})::String
        if length(cubics) > 0
            string( join(map(x->string(x," "),cubics)), sum(cubics), " Lucky" )
        else
            "Unlucky"
        end
    end

    function is_sum_of_cubes(s::AbstractString)::String
        cubics::Vector{Int} = []
        index = 1
        while index <= length(s)
            m = match(r"\d{1,3}", s, index)
            if m !== nothing
                number = parse(Int,m.match)
                if is_cubic(number)
                    push!(cubics,number)
                end
                index = m.offset + length(m.match)
            else
                index = length(s)+1
            end
        end
        format_return(cubics)
    end

end


using Distributions, Plots
gr(fontfamily = "Palatino", legend = false)
scatter([Tuple(c) for c in eachcol(rand(MvNormal([30.0,100], [15.0 12; 12 13]), 100))],xlabel = "ad spending", ylabel = "revenue")

savefig("ad-spending.jl")


x = [
    0.0 0.3 0.6
    0.0 0.0 0.0
]
rs = 0.5:0.5:1.

dist = PeriodicEuclidean(ones(2))

@test K(rs, x) ≈ [2 / 3, 1.]
@test K(rs, x; dist) ≈ ones(2)

@test D(rs, x, 2) ≈ [1. 1/3; 1. 1.]
@test D(rs, x, 2; dist) ≈ ones(2, 2)

@test T(rs, x) ≈ [0., 1.]
@test T(rs, x; dist) ≈ [1., 1.]

@test H(rs, x, 0.5) ≈ [4 / 9, 7 / 9]
@test H(rs, x, 0.5; dist) ≈ [8 / 9, 1.]


#!/home/jburdick/bin/julia
# Writes out the coefficients of the bound matrix, both
# using exact binomials, and using various approximations
# (and log-transformed numbers), as a check of the math.

push!(LOAD_PATH, ".")

using CountingBound, LogCountingBound

"""
  Writes counts for some values of k, r, and n.
  k: the value of k
  maxN: the maximum value of n (this will use
		powers of two	<= maxN)
	logADropOff: this will start r at k, and continue as the log bound
		increases, until the bound drops below (max - logADropOff)
  outputDir: directory in which to write output files
  Side effects: writes files A and b.
    (The choices of r are a bit arbitrary).
"""
function writeCounts(k, maxN, logADropOff, outputDir)
  # values of n to use, from k to maxN
  # (for now, spaced at powers of 2)
  nList = [BigInt(2^i) for i in 1:trunc(log(2,maxN))]
  # print(nList)
  # print("\n")
  # print(typeof(nList[1]))
  # print("\n")
  # ??? for some reason, this was giving a syntax error
  # nList = [n for n in nList if n>=k && n<=maxN]
  # therefore, using filter()...
  nList = filter(n -> n>=k && n<=maxN, nList)

  # FIXME should create output directory
  # first, write the bound b
  of = open(outputDir * "/b_k=" * string(k) * "_maxN=" * string(maxN) * ".csv", "w")
  write(of, "k,n,logBound\n")
  for n in nList
    bound = countingBound(binomial(n, 2), binomial(n, k))
		logBound = NaN
		if bound > 0
			logBound = log(bound)
		end
    write(of, string(k) * "," * string(n) * ","
      * string(logBound) * "\n")
  end
  close(of)

  # then, write the coefficients
  of = open(outputDir * "/A_k=" * string(k) * "_maxN=" * string(maxN) * ".csv", "w")
  write(of, "k,r,n,logA\n")
  for n = nList
		# tracks the maximum (initially undefined)
		maxLogA = NaN
		# loop through possible values of r (this will usually stop early)
    for r = k:n
      print("k=" * string(k) * " r=" * string(r) * " n=" * string(n) * "\n")
			# compute log of number of cliques, and print it
      logA = logNumMaximalCliques(k, r, n)
      write(of, string(k) * "," * string(r) * "," * string(n)
        * "," * string(logA) * "\n")
			# keep track of maximum
			if isnan(maxLogA) || logA > maxLogA
				maxLogA = logA
			end
			# if we've fallen far enough below the maximum, stop the loop
			if logA <= maxLogA - logADropOff
				break
			end
    end
  end
  close(of)

end

outputDir = "logCoef1"
mkpath(outputDir)

for k in [6,8,10,12,14,16,18,20,50,100,200,500,1000]
	writeCounts(k, 1e60, 100 * log(10), outputDir)
end


# testing discretization procedures
# example is based on Walter Gautschi "Orthogonal Polynomials: Computation and Approximation"
# examples 2.36

using PolyChaos, Test
import LinearAlgebra: norm

nodes = [40, 80, 160, 320]
tol = 1e-14

@time @testset "Stieltjes procedure" begin
    for n in nodes
        myfile = open("fej$n.txt")
        βref = parse.(Float64,readlines(myfile))
        βcom = stieltjes(n,fejer(n)...)[2]
        @test isapprox(norm(βref-βcom,Inf),0.;atol=tol)
        close(myfile)
    end
end

@time @testset "Lanczos procedure" begin
    for n in nodes
        myfile = open("fej$n.txt")
        βref = parse.(Float64,readlines(myfile))
        βcom = lanczos(n,fejer(n)...)[2]
        @test isapprox(norm(βref-βcom,Inf),0.;atol=tol)
        close(myfile)
    end
end


### Compute empirical covariance matrix recursively

function covariance!(
  C::RealMatrix,
  lastC::RealMatrix,
  k::Integer,
  x::RealVector,
  lastmean::RealVector,
  secondlastmean::RealVector
)
  C[:, :] = (k-1)*lastC
  BLAS.ger!(1.0, x, x, C)
  BLAS.ger!(1.0, -(k+1)*lastmean, lastmean, C)
  BLAS.ger!(1.0, k*secondlastmean, secondlastmean, C)
  C[:, :] /= k
end


module MultivariateMixtures

using LinearAlgebra
using Statistics: mean, var, cov, covm
using StatsBase: fit, ConvergenceException
using MultivariateStats: PPCA, FactorAnalysis, outdim
using Distributions
using Distributions: log2π
using Clustering: kmeans, assignments

import Base: show, length, convert
import Distributions: probs, ncomponents, components, component, logpdf, logpdf!
import Statistics: mean

export fit_mm, FinateMixtureModel

include("types.jl")
include("utils.jl")
include("gmm.jl")
include("mfa.jl")

# move to MultivariateStats
length(fa::FactorAnalysis) = outdim(fa)

# auxiliary functions
convert(::Type{MvNormal}, fa::FactorAnalysis) = MvNormal(mean(fa), cov(fa))
logpdf(fa::FactorAnalysis{T}, x::AbstractVector{T}) where {T<:Real} =
    logpdf(convert(MvNormal, fa), x)
logpdf(fa::FactorAnalysis{T}, X::AbstractMatrix{T}) where {T<:Real} =
    logpdf!(Vector{T}(undef, size(X,2)), fa, X)
logpdf!(r::AbstractArray{T}, fa::FactorAnalysis{T}, X::AbstractMatrix{T}) where {T<:Real} =
    logpdf!(r, convert(MvNormal, fa), X)
predict(classifier::Union{MixtureModel,FinateMixtureModel}, data::AbstractVector) =
    findmax(componentwise_logpdf(classifier, data))[end]
predict(classifier::Union{MixtureModel,FinateMixtureModel}, data::AbstractMatrix; dims=2) =
    map(ci->ci[2], findmax(componentwise_logpdf(classifier, (dims == 1 ? data' : data)), dims=2)[end])[:]

end


module FrameFunRBF

using DomainSets, BasisFunctions, FrameFun
using StaticArrays, LinearAlgebra
using Printf
using CompactTranslatesDict

BF = BasisFunctions

import Base:
    length,
    size

import BasisFunctions:
    support,
    name,
    unsafe_eval_element,
    plotgrid

import FrameFun:
    dictionary,
    SamplingStyle,
    SolverStyle

export RBF, SmoothRBF, PiecewiseSmoothRBF
export Multiquadric,
    InverseMultiquadric,
    InverseQuadratic,
    Gaussian,
    PolyharmonicSpline,
    ThinPlateSpline

export RBFPlatform,
    RBFParLinear,
    RBFParConstant,
    RBFParSquareRoot,
    rbfselect

export RBFDictionary,
    PeriodicRBFDictionary

include("gridgenerator.jl")
include("rbf.jl")
include("rbf_dict.jl")
include("approximation.jl")
include("platform.jl")

end


struct NACA4{T<:AbstractFloat, SYM, STE} <: NACABase{T}
    m::T
    p::T
    t::T
end

function NACA4(::Type{T}, num::AbstractString, sharp_TE::Bool) where {T<:AbstractFloat}
    if length(num) != 4
        throw(ArgumentError("invalid 4-dig NACA airfoil: NACA$num"))
    end
    t = parse(T, num[3:4]) / 100
    m = parse(T, num[1:1]) / 100
    p = parse(T, num[2:2]) / 10
    NACA4{T, m==0, sharp_TE}(m,p,t)
end

function y_t(a::NACA4{T, SYM, false}, x::T)::T where {T<:AbstractFloat,SYM}
    5*a.t*(.2969*√x-.1260*x-.3516*x^2+.2843*x^3-.1015*x^4)
end
function y_t(a::NACA4{T, SYM, true}, x::T)::T where {T<:AbstractFloat,SYM}
    5*a.t*(.2969*√x-.1260*x-.3516*x^2+.2843*x^3-.1036*x^4)
end

function dy_t(a::NACA4{T, SYM, false}, x::T)::T where {T<:AbstractFloat,SYM}
    5*a.t*(.2969/(2*√x)-.1260-.3516*2*x+.2843*3*x^2-.1015*4*x^3)
end

function dy_t(a::NACA4{T, SYM, true}, x::T)::T where {T<:AbstractFloat,SYM}
    5*a.t*(.2969/(2*√x)-.1260-.3516*2*x+.2843*3*x^2-.1036*4*x^3)
end

function y_c(a::NACA4{T}, x::T)::T where {T<:AbstractFloat}
    if x < a.p
        a.m/a.p^2 * (2*a.p*x - x^2)
    else
        a.m/(1-a.p)^2 * (1 - 2*a.p + 2*a.p*x -x^2)
    end
end

function dy_c(a::NACA4{T}, x::T)::T where {T<:AbstractFloat}
    if x < a.p
        2 * a.m/a.p^2 * (a.p - x)
    else
        2 * a.m/(1-a.p)^2 * (a.p*x -x)
    end
end

function y_upper(a::NACA4{T,true}, x::T)::T where {T<:AbstractFloat}
    y_t(a, x)
end

function y_lower(a::NACA4{T,true}, x::T)::T where {T<:AbstractFloat}
    -y_t(a, x)
end

function dy_upper(a::NACA4{T,true}, x::T)::T where {T<:AbstractFloat}
    dy_t(a, x)
end

function dy_lower(a::NACA4{T,true}, x::T)::T where {T<:AbstractFloat}
    -dy_t(a, x)
end

function fy_upper(a::NACA4{T,true}, x::T)::T where {T<:AbstractFloat}
    y_t(a, x), dy_t(a, x)
end

function fy_lower(a::NACA4{T,true}, x::T)::T where {T<:AbstractFloat}
    -y_t(a, x), -dy_t(a, x)
end

function y_upper(a::NACA4{T,false}, x::T)::T where {T<:AbstractFloat}
    y_c(a,x) + y_t(a, x) * cos(atan(dy_c(a,x)))
end

function y_lower(a::NACA4{T,false}, x::T)::T where {T<:AbstractFloat}
    y_c(a,x) - y_t(a, x) * cos(atan(dy_c(a,x)))
end

function x_upper(a::NACA4{T,false}, x::T)::T where {T<:AbstractFloat}
    x - y_t(a, x) * sin(atan(dy_c(a,x)))
end

function x_lower(a::NACA4{T,false}, x::T)::T where {T<:AbstractFloat}
    x + y_t(a, x) * sin(atan(dy_c(a,x)))
end


using DifferenceEquations
using Distributions
using Optim
using Random
using LinearAlgebra
using Test

# function ar2_transition(u, p, t) # f
#     A = [p[1] p[2]; 1 0]
#     return A * u
# end

# function ar2_noise(u, p, t) # g
#     return [p[3], 0.0]
# end

# function ar2_observation(u, p, t) # h
#     return [1 0] * u
# end

# Random.seed!(1)

# p = [0.5, -0.25]
# tspan = (1, 100)

# phi1 = 0.5
# phi2 = -0.25
# sigma = 1
# true_theta = [phi1, phi2, sigma]

# Y = zeros(tspan[2])
# n = randn(tspan[2])
# Y += sigma .* n

# for t in 3:length(Y)
#     Y[t] += phi1 * Y[t-1] + phi2 * Y[t-2]
# end

# u0 = [Y[2]; Y[1]]

# @testset "AR2" begin
#     @testset "Provided observations" begin
#         prob = StateSpaceProblem(
#             ar2_transition, # f
#             ar2_noise, # g
#             ar2_observation, # h
#             n,
#             [0.0, 0.0], # u0
#             tspan, # timespan
#             nothing, # observation noise
#             Y # observables
#         )

#         trans_x(x) = [x[1], x[2], exp(x[3])]
#         target(x) = -DifferenceEquations._solve(prob, ConditionalGaussian(), trans_x(x)).likelihood
#         res = optimize(target, [0.0, 0.0, 1.0], Optim.Options(iterations=1000))
#         display(res)
#         println(-res.minimum)
#         println(trans_x(res.minimizer))

#         # Calculate whether we inferred phi1 and phi2
#         @test isapprox(res.minimizer[1], phi1, atol=0.01)
#         @test isapprox(res.minimizer[2], phi2, atol=0.01)
#         @test isapprox(exp(res.minimizer[3]), sigma, atol=0.01)
#     end

#     @testset "No observables" begin
#         prob = StateSpaceProblem(
#             ar2_transition, # f
#             ar2_noise, # g
#             ar2_observation, # h
#             n,
#             [0.0, 0.0], # u0
#             tspan, # timespan
#             nothing,#n, # observation noise
#             nothing#Y # observables
#         )

#         sol1 = DifferenceEquations._solve(prob, ConditionalGaussian(), true_theta)
#         sol2 = DifferenceEquations._solve(prob, ConditionalGaussian(), [0.0, 0.0, 1.0])
#         @test sol1.likelihood > sol2.likelihood
#     end
# end

@testset "Linear model" begin

    A = [0.8 0.0; 0.1 0.7]
    B = Diagonal([0.1, 0.5])
    C = [0.5 0.5] # one observable
    R = [0.01]

    # Simulate data
    T = 10
    u0 =[0.0, 0.1]
    tspan = (1, T)

    prob1 = LinearStateSpaceProblem(A, B, C, u0, tspan, R=R)
    sol1 = solve(prob1, LinearGaussian())

    prob2 = LinearStateSpaceProblem(A, B, C, u0, tspan, R=R, observables=sol1.z)
    sol2 = solve(prob2, LinearGaussian())

    prob3 = LinearStateSpaceProblem(A, B, C, u0, tspan, R=R, observables=sol2.z, noise=DefinedNoise(sol2.n))
    sol3 = solve(prob3, LinearGaussian())

    @test sol2.n == sol3.n
end


using LowRank, Test
# using Plots
# using JLD
# using Random, Distributions, LinearAlgebra

N = 30
d = 2
n = 10
k = 1
err = 0.0
indices = 1:n

A = zeros(d, N)
U = rand(d, k)
V = rand(k, n)
A[:, 1:n] = U*V
A[:, n+1:N] = rand(d, N-n)
data_set = collect(eachcol(A))

# file = load(joinpath(@__DIR__, "..","datasets", "N_30_d_2.jld"))
# data_set, (err, indices) = file["data"], file["n_10_k_1"]
# n = 10
# k = 1

# @time @test find_low_rank_subset_checkall(data_set,n,k) == (err, indices)
@test find_low_rank_subset_sample_rep(data_set,n,k)[1] <= 4*err + 0.0001
@test FastLowRank(data_set, n, k)[1] <= err + 0.0001

# PLotting
# p = scatter([Tuple(x) for x in V], label = "all points")
# scatter!(p, [Tuple(V[i]) for i in indices], label = "optimal points")
# plot!(p, [(0., 0.), -1 .* Tuple(truncated_svd(hcat(V[indices]...), 1).U[:,1])], label = "optimal line" )


function hargreaves(
    forcings::DataFrame;
    tminCol::Symbol=:tmin,
    tmaxCol::Symbol=:tmax,
    dateCol::Symbol=:datetime
)

    dts = forcings[!,dateCol]
    tmin = forcings[!,tminCol]
    tmax = forcings[!,tmaxCol]
    len = length(tmax)
    Gsc = 367
    lhov = 2.257

    doy = map(dayofyear, dts)

    tavg = map(mean,zip(tmin,tmax))

    eto = zeros(len)

    for (i,t) in enumerate(doy)
        b = 2 * pi * (Int16(t)/365)
        Rav = 1.00011 + 0.034221*cos(b) + 0.00128*sin(b) + 0.000719*cos(2*b) + 0.000077*sin(2*b)
        Ho = ((Gsc * Rav) * 86400)/1e6

        eto[i] = (0.0023 * Ho * (tmax[i]-tmin[i])^0.5 * (tavg[i]+17.8))
    end

    return eto
end

function hamon(forcings::DataFrame, par::Float64, lat::Float64; tavgCol::Symbol=:tavg, dateCol::Symbol=:dataetime)

    dts = forcings[!,dateCol]
    tavg = forcings[!,tavgCol]
    len = length(tavg)

    doy = map(dayofyear, dts)

    θ = @. 0.2163108 + 2 * atan(0.9671396 * tan(0.0086 * (doy - 186)))
	pi_v = @. asin(0.39795 * cos(θ))
    daylighthr = @. 24 - 24/pi * acos((sin(0.8333 * pi/180) + sin(lat * pi/180) * sin(pi_v))/(cos(lat * pi/180) * cos(pi_v)))

    esat = @. 0.611 * exp(17.27 * tavg/(237.3 + tavg))

    eto = @. par * 29.8 * daylighthr * (esat/(tavg + 273.2))

  return eto
end


using Statistics, Memoization

struct PeriodicFiniteDifferenceOperator{T} <: AbstractMatrix{T}
  N::Int
  difforder::Int
  accuracyorder::Int
  stencil::Vector{T}
end
function PeriodicFiniteDifferenceOperator(N::Int, L::T, difforder::Int, accuracyorder::Int=2) where {T}
  @assert iseven(accuracyorder)
  Δ = L / N
  b = zeros(accuracyorder + 1)
  b[difforder + 1] = factorial(difforder)
  x = (collect(1:accuracyorder + 1) .- (accuracy÷2 + 1)) .* Δ
  stencil = hcat([x[i+1].^(0:accuracyorder) for i in 0:accuracyorder]...) \ b ./ Δ^difforder
  return PeriodicFiniteDifferenceOperator{T}(N, difforder, accuracyorder, stencil)
end
Base.eltype(p::PeriodicFiniteDifferenceOperator{T}) where T = T
Base.size(p::PeriodicFiniteDifferenceOperator{T}) where {T} = (p.N, p.N)
function Base.getindex(p::PeriodicFiniteDifferenceOperator{T},
                       I::Vararg{Int, 2}) where {T}
  i, j = I
  A = p.order
  for jj ∈ (-A÷2:A÷2)
    j == mod1(jj + i, p.N) && return p.stencil[jj + A÷2 + 1]
  end
  return zero(T)
end

struct PeriodicFiniteIntegratorOperator{A,T} <: Function
  N::Int
  Δ::T
  function PeriodicFiniteIntegratorOperator(N::Int, L::T, order::Int=2) where {T}
    Δ = L / N
    return new{order,T}(N, Δ)
  end
end
@memoize polymatrix(N) = lu(hcat([(0:N-1).^i for i in 0:N-1]...))
function integratepoly(y, a, b) # TODO calculate coefficients and not do this
  N = length(y)
  N > 8 && throw(error("length of y is too large, gives badly conditioned matrix"))
  coeffs = zeros(N+1)
  coeffs[2:end] = (polymatrix(N) \ y) ./ (1:N)
  return sum(coeffs .* b.^(0:N)) - sum(coeffs .* a.^(0:N))
end
function (p::PeriodicFiniteIntegratorOperator{1})(z, y)
  z[1] = y[1]/2 * p.Δ
  for i ∈ 2:length(z)
    z[i] = z[i-1] + y[i] * p.Δ
  end
  return z
end
function (p::PeriodicFiniteIntegratorOperator{2})(z, y)
  z[1] = (y[end]/4 + 3y[1]/4) * p.Δ
  for i in 2:length(z)
    z[i] = z[i-1] + (y[i-1] + y[i])/2 * p.Δ
  end
  return z
end
# this operator is 4th order accurate despite using a 3 point stencil
function (p::PeriodicFiniteIntegratorOperator{3})(z, y)
  fill!(z, 0)
  z[1] = integratepoly([y[end], y[1], y[2]], 0.5, 1) * p.Δ
  z[2] = integratepoly([y[end], y[1], y[2]], 1, 1.5) * p.Δ
  for i in 2:length(z)-1
    z[i] += z[i-1] + integratepoly(y[i-1:i+1], 0.5, 1) * p.Δ
    z[i+1] += integratepoly(y[i-1:i+1], 1, 1.5) * p.Δ
  end
  z[end] += z[end-1] + integratepoly([y[end-1], y[end], y[1]], 0.5, 1) * p.Δ
  return z
end
function (p::PeriodicFiniteIntegratorOperator{4})(z, y)
  z[1] = integratepoly([y[end-1], y[end], y[1], y[2]], 1.5, 2) * p.Δ
  z[2] = z[1] + integratepoly([y[end], y[1], y[2], y[3]], 1, 2) * p.Δ
  for i in 3:length(z)-1
    z[i] = z[i-1] + integratepoly(y[i-2:i+1], 1, 2) * p.Δ
  end
  z[end] = z[end-1] + integratepoly([y[end-2], y[end-1], y[end], y[1]], 1, 2) * p.Δ
  return z
end
(p::PeriodicFiniteIntegratorOperator{A,T})(y) where {A,T} = p(deepcopy(y), y)

struct FiniteDifferenceField{BC<:PeriodicGridBC, T, A} <: AbstractField{BC}
  chargedensity::EquispacedValueGrid{BC,T}
  electricfield::EquispacedValueGrid{BC,T}
  integrator::PeriodicFiniteIntegratorOperator{A,T}
  function FiniteDifferenceField(chargedensity::EquispacedValueGrid{BC,T},
                                 electricfield::EquispacedValueGrid{BC,T},
                                 integrator::PeriodicFiniteIntegratorOperator{A,T}) where {BC,T,A}

    zero!(electricfield)
    finitediff = new{BC,T,A}(chargedensity, electricfield, integrator)
    solve!(finitediff)
    return finitediff
  end
end
function FiniteDifferenceField(chargedensity::EquispacedValueGrid{PeriodicGridBC};
                               order::Int=2)
  electricfield = deepcopy(chargedensity)
  zero!(electricfield)
  N = first(size(chargedensity))
  integrator = PeriodicFiniteIntegratorOperator(N, chargedensity.L, order)
  return FiniteDifferenceField(chargedensity, electricfield, integrator)
end
function FiniteDifferenceField(N::Int, L::Real, ::Type{BC}=PeriodicGridBC; order::Int=2) where {BC}
  return FiniteDifferenceField(EquispacedValueGrid(N, L, BC), order=order)
end

presolve(g::EquispacedValueGrid{PeriodicGridBC}) = g .- mean(g)
presolve(g::EquispacedValueGrid{<:AbstractBC}) = g
function solve!(f::FiniteDifferenceField{PeriodicGridBC})
  ρ = presolve(f.chargedensity)
  f.electricfield .= demean!(f.integrator(ρ))
  return f
end

zero!(f::FiniteDifferenceField) = (zero!(f.chargedensity); zero!(f.electricfield))


function near02()
#
#  test polynomial suggested by Z. Zeng
#
    e = 0.01;
    p = poly([(1-e)*ones(20);ones(20);-0.5*[1;1;1;1;1]]);
    z = [1-e 20; 1 20; -0.5 5];
    p, PolyZeros(z)
end


function distmesh2d(
    fd::Function,
    fh::Function,
    bbox,
    h0::Float64,
    pfix = [],
    ttol = 0.1,
    geps = 0.001 * h0,
    Fscale = 1.2,
    dptol = 0.001,
    deltat = 0.2,
    deps = sqrt(eps(Float64)) * h0,
)::Tuple{Array{Float64, 1}, Array{Float64, 1}}
    pold = Inf
    scaler = Scaler(bbox)
    v1, v2 = extractbox(bbox, h0, calculateh1(h0))
    x, y = meshgrid(v1, v2)
    shiftevenrows!(x, h0)
    p = buildinitialpoints(x,y,fd,scaler,geps)
    pfix = [vcat(scaledpoint(row, scaler)) for row in eachrow(pfix)]
    pfix = transpose(reshape(vcat(pfix...), 2, length(pfix)))
    while true
        del, vor, summ = deldir(p[:, 1], p[:, 2])
        trigs = triangles(del, summ)
        line_edges = validedges(trigs, del, scaler, fd, geps)
        pointstofvces = pointstoforces(line_edges, scaler, Fscale, pfix, fh)
        finalp, moveindex =
            finalpositions(pointstofvces, scaler, deltat, fd, geps, deps, h0)
        if moveindex < dptol
            x, y = plotedges(line_edges)
            break
        else
            p = finalp
        end
    end
    return [unscalex(x, scaler) for x in x], [unscaley(y, scaler) for y in y]
end

function buildinitialpoints(
    x::Array{Float64, 2},
    y::Array{Float64, 2},
    fd::Function,
    scaler::Scaler,
    geps::Float64
)::Array{Float64, 2}
    p = [x[:] y[:]]
    p = [vcat(row) for row in eachrow(p) if fd(row) < -geps]
    r0 = [1 ./ fh(row...) .^ 2 for row in p]
    r0max = maximum(r0 ./ maximum(r0))
    p = [vcat(scaledpoint(row, scaler)) for row in p if (rand(Float64, size(p)))[1] < r0max]
    p = transpose(reshape(vcat(p...), 2, length(p)))
    return p
end

function calculateh1(h0::Float64)::Float64
    return h0 * (sqrt(3) / 2)
end

function extractbox(
    bbox::Array{Float64,2},
    h0::Real,
    h1::Real
)::Tuple{Array{Float64, 1}, Array{Float64, 1}}
    v1 = bbox[1, 1]:h0:bbox[2, 1]
    v2 = bbox[1, 2]:h1:bbox[2, 2]
    return v1, v2
end

function shiftevenrows!(x::Array{Float64, 2}, h0::Float64)
    x[2:2:end, :] = x[2:2:end, :] .+ h0 / 2
end


#import GeometryBasics: Point, Point3, Triangle
import GeometryBasics: Point, Point3, Point2, Vec, Vec2, Vec3, TriangleFace, Rectangle
using LinearAlgebra
import StaticArrays
import GeneralPolygonClipper as gpc
using VoronoiCells

struct SProject2d end

function boundingbox2(pts)
    i = firstindex(pts)
    xmin = pts[i][1]
    ymin = pts[i][2]

    xmax = xmin
    ymax = ymin

    for p in pts
        x,y = p
        if x < xmin
            xmin = x
        elseif x > xmax
            xmax = x
        end
        if y < ymin
            ymin = y
        elseif y > ymax
            ymax = y
        end
    end
    return (xmin, ymin), (xmax, ymax)

end

function boundingbox3(pts)

    i = firstindex(pts)
    xmin = pts[i][1]
    ymin = pts[i][2]
    zmin = pts[i][3]

    xmax = xmin
    ymax = ymin
    zmax = zmin

    for p in pts
        x,y,z = p
        if x < xmin
            xmin = x
        elseif x > xmax
            xmax = x
        end
        if y < ymin
            ymin = y
        elseif y > ymax
            ymax = y
        end
        if z < zmin
            zmin = z
        elseif z > zmax
            zmax = z
        end

    end
    return (xmin, ymin, zmin), (xmax, ymax, zmax)

end

function get_basis(n::Vec{3,T}) where {T}
    # We want to project the polygons on a surface plane
    # but we still need two two independent vector to form
    # an orthonormal basis. It would be better if the
    # axis are partially aligned with the original
    # 3D reference frame.

    ex = Vec{3,T}(1, 0, 0)
    ey = Vec{3,T}(0, 1, 0)
    ez = Vec{3,T}(0, 0, 1)

    e = (ex, ey, ez)
    dx = ex ⋅ n
    dy = ey ⋅ n
    dz = ez ⋅ n

    # If the surface is almost horizontal idxmax=3
    idxmax = argmax( abs.((dx, dy, dz)) )
    if idxmax==3
        idx = 2 # Y axis will be vertical
    else
        # Figure out which axis is more perpendicular to the normal:
        idx = argmin( abs.((dx, dy, dz)) )
    end
    # idx corresponds to the axis more nearly perpendicular.
    # Since in most buildings most faces are vertical or
    # approximately so, the upward direction will be the y axis
    # for the projection.

    vy = e[idx] - (e[idx] ⋅ n) * n  # Vector normal to n and e[idx]
    vx = vy × n

    # return the new *orthonormal* basis
    return vx ./ hypot(vx...), vy ./ hypot(vy...), n ./ hypot(n...)

end

"""
`mean_mesh_normal(msh)`

Compute the mean normal of a mesh
"""
function mean_mesh_normal(msh)

    np = length(msh) # Number of polygons (or triangles...)

    nrm <- sum(normal(p) for p in msh)
    n⃗ = nrm ./ hypot(nrm...) # Unit vector

    return get_basis(n⃗)

end

normal1(x) = (x[2]-x[1]) × (x[3]-x[1]) / 2

"""
`discrsurface(type, faces, pext, fext, pint, fint)`

Building surface discretization.

This function discretizes a face of a building around
 internal and external pressure taps. A face is made up of subfaces
which are made up of triangles.

 * `type` A type specifying the algorithm used
 * `faces` a collection of faces
 * `pext` External pressure taps
 * `fext` Index of faces where the pressure taps are located
 * `pint` Internal pressure taps
 * `fint` Index of faces where internal taps are located. Should be a subset of `fext`
"""
function discrsurface(::Type{SProject2d}, faces, pext, fext,  pint, fint)
    # Check if `fint` is an subset of `fext`
    for f in fint
        if f ∉ fext
            error("External surfaces should include every internal surface!")
        end
    end

    # Calculate the mean normal of all external faces
    nrm = Vec3{Float64}(0,0,0)
    for f in faces[fext]
        for tri in f
            n = normal1(tri)
            nrm += n
        end
    end

    nrm = nrm / norm(n)

    # Project points and faces on a plane normal to `nrm`
    e = get_basis(nrm) # Basis

    P₀ = Point3(0,0,0)

    # Discretize external surface:
    tri_ext = partition_faces(pext, faces, fext, e, P₀)

    # Discretize internal surfaces, if there are any
    if length(pint) > 0
        # Get the mesh
        tri_int = partition_faces(pint, faces, fint, e, P₀)

        # Now we should intersec
    end

end

"""
`partition_faces(pts, facelist, fidx, e, p0)`

Partition a collection of faces in regions corresponding to a collection of points.

The faces are collections of triangles and they are projected into a plane defined
by point `p0` and normal `e[3]`. This plane has two direction vectors:

 * `e[1]` that correspnds to the 2d axis direction vector î
 * `e[2]` that correspnds to the 2d axis direction vector ĵ

Once the points and faces are projected into the plane, a Voronoi tesselation
is carried out with respect to the points and each Voronoi cell is intersected
with each triangle that compose the faces. This intersection is represented
as a set of triangles.

## Arguments

 * `pts` Vector of points
 * `facelist` A vector containing every face in the model
 * `fidx` Vector with indices of faces that should be processed
 * `e` Basis defining the projection plane
 * `p0` Origin of the 2d coordinate system on the projection plane

## Return value

This function returns a vector with a tuple containing a triangle, the index of
the point, the index of the face that this triangle belongs to and the index of the
model triangle to which the triangle belongs.

"""
function partition_faces(pts, facelist, fidx, e, p0)

    # Let's work with the necessary faces only!
    faces = facelist[fidx]

    # A plane is specified by a point and a normal. Just use the origin as the point
    P₀ = p0
    # Project the triangles
    faces2d = [[project_triangle(e, P₀, tri3) for tri3 in ff] for ff in faces]

    # Project the points on thge same plane
    pts2 = [Point2( (p - P₀) ⋅ e[1], (p-P₀)⋅e[2] ) for p in pts]

    # Get a bounding box of all the projected surfaces to create the Voronoi tesselation
    xmin, ymin = faces2d[1][1][1]
    xmax, ymax = xmin, ymin
    for f in faces2d
        for tri in f
            for i in 1:3
                x,y = tri[i]
                if x < xmin
                    xmin = x
                elseif x > xmax
                    xmax = x
                end
                if y < ymin
                    ymin = y
                elseif y > ymax
                    ymax = y
                end
            end
        end
    end
    Δx = xmax - xmin; Δy = ymax - ymin

    # Bounding box. Let's make it bigger to ensure everything fits...
    rect = Rectangle(Point2(xmin-Δx, ymin-Δy), Point2(xmax+Δx, ymax+Δy))

    # Voronoi tesselation of external points
    tessex = voronoicells(pts2, rect)

    # Array to store the 2d triangles, the face index and triangle index.
    tri2d = Tuple{TriangleFace{Point2{Float64}}, Int, Int, Int}[]

    # Let's go through every Voronoi cell and intersect it with every triangle
    # in the faces
    for i in eachindex(pts)
        vpts = tessex.Cells[i] # Voronoi cell
        (xmin1,ymin1), (xmax1,ymax1) = boundingbox2(vpts)
        # Create a GenericPolygonClipper.jl polygon
        pgpc = gpc.GPCPolygon([false], [[gpc.Vertex(v[1], v[2]) for v in vpts]])
        for (k, face)   in enumerate(faces2d)
            for (j, tri) in enumerate(face)
                (xmin2, ymin2), (xmax2, ymax2) = boundingbox2(tri)
                if !(xmax2 < xmin1 || xmax1 < xmin2 ||
                    ymax2 < ymin1 || ymax1 < ymin2)
                    # There *might* be an intersection!
                    # If there is an intersection, we want it decomposed in triangle
                    # strips.
                    trigpc = gpc.GPCPolygon([false],
                                            [[gpc.Vertex(tri[i][1], tri[i][2]) for i in 1:3]])
                    # Now we carry out the intersection between the Voronoi cell
                    # and the face triangle
                    trilst = gpc.intersect_strip(pgpc, trigpc)
                    for s in trilst
                        for l in 1:length(s)
                            vv = s[l]
                            tri_new = TriangleFace(Point2{Float64}(vv[1].x, vv[1].y),
                                                   Point2{Float64}(vv[2].x, vv[2].y),
                                                   Point2{Float64}(vv[3].x, vv[3].y))
                            push!(tri2d, (tri_new, i, fidx[k], j))
                        end
                    end

                end

            end
        end
    end
    # Project back to 3d.
    # And remember that the orientation might have been inverted
    return [ (project_back(e, P₀, x[1], facelist[x[3]][x[4]]),
              x[2], x[3], x[4]) for x in tri2d ]

end

"""
`project_triangle(e, p, tri3)`

Project a 3d triangle into a plane defined by basis `e` with origin `p`.

## Arguments

 * `e` Tuple containing the basis. `e[1]` and `e[2]` are the vectors definind the plane and `e[3]` is the vector normal to the plane
 * `p` 3d point which is used as origin in the plane
 * `tri3` 3d Triangle that should be projected.

## Return value

A `2d TriangleFace` object containing the projected triangle.
"""
function project_triangle(e, p, tri3)
    v1 = Point2( (tri3[1]-p) ⋅ e[1], (tri3[1]-p) ⋅ e[2])
    v2 = Point2( (tri3[2]-p) ⋅ e[1], (tri3[2]-p) ⋅ e[2])
    v3 = Point2( (tri3[3]-p) ⋅ e[1], (tri3[3]-p) ⋅ e[2])
    return TriangleFace(v1, v2, v3)

end

"""
`project_back(e, p, tri2d, tri0)`

Project back a 2d triangle into the 3d surface of the model.

This function projects a 2d triangle back into 3d space where the triangle
belongs to the surface defined by 3d triangle `tri0`.

The 2d plane where `tri2d` is defined has as origin the point `p` (3d) and
unit direction vectors `î = e[1]` and `ĵ = e[2]`.

The function will reorder the output vertices so that it has the same orientation
as the 3d triangle `tri0`.

## Arguments
 * `e` Tuple with direction vectors defining the projection plane
 * `p` 3d point which is used as origin in the 2d projection plane
 * `tri2d` The 2d triangle that should be projected back
 * `tri0` triangle that defines the 3d plane where the output triangle should be located.

## Return value

A `TriangleFace` corresponding to `tri2d` in the plane defined by `tri0`.
"""
function project_back(e, p, tri2d, tri0)
    # Any point P in the same plane of a triangle
    # can be computed as P - P₁ = α⋅u + β⋅v
    # Where:
    #  * P₁ is the first vertex of the triangle
    #  * u is the vector from P₁ to P₂
    #  * v is the vector from P₁ to P₃
    # In the projected plane (basis `e`), this results
    # in a system of equations M = [ux vx; uy vy]
    # M⋅[α; β] = [x-x₁, y-y₁]
    # Inverting M we can calculate α and β and now
    # we can use the original definintion above to compute P.

    # Project first vertex on the plane
    vx₁ = (tri0[1]-p) ⋅ e[1]
    vy₁ = (tri0[1]-p) ⋅ e[2]

    u = tri0[2] - tri0[1]
    v = tri0[3] - tri0[1]
    ux = u ⋅ e[1]
    uy = u ⋅ e[2]

    vx = v ⋅ e[1]
    vy = v ⋅ e[2]

    Δ = ux * vy - uy * vx

    A11 =  vy / Δ; A12 = -vx / Δ
    A21 = -uy / Δ; A22 =  ux / Δ

    δx = tri2d[1][1] - vx₁
    δy = tri2d[1][2] - vy₁
    α = A11*δx + A12*δy
    β = A21*δx + A22*δy
    p1 = tri0[1] + α * u + β * v

    δx = tri2d[2][1] - vx₁
    δy = tri2d[2][2] - vy₁
    α = A11*δx + A12*δy
    β = A21*δx + A22*δy
    p2 = tri0[1] + α * u + β * v

    δx = tri2d[3][1] - vx₁
    δy = tri2d[3][2] - vy₁
    α = A11*δx + A12*δy
    β = A21*δx + A22*δy
    p3 = tri0[1] + α * u + β * v

    # Check orientation of the nodes.
    # If the normals are in the same direction, keep the order p1, p2, p3.
    # Otherwise invert: p1, p3, p2.
    if (u × v) ⋅ ( (p2-p1)×(p3-p1) ) > 0
        return TriangleFace(p1, p2, p3)
    else
        return TriangleFace(p1, p3, p2)
    end

end


function recsolve!(x, y, lu, rd, memo)
    any((x, y) .> size(memo[])) && return 0
    ismissing(memo[][x, y]) || return memo[][x, y]

    if x == size(memo[], 1)
        memo[][x, y] = recsolve!(x, y + 1, lu, rd, memo)
    elseif y == size(memo[], 2)
        memo[][x, y] = recsolve!(x + 1, y, lu, rd, memo)
    else
        rs = recsolve!(x, y + 1, lu, rd, memo) + recsolve!(x + 1, y, lu, rd, memo)
        memo[][x, y] = rs / 2
    end

    memo[][x, y]
end

function solve(case, wh, lu, rd)
    memo = Array{Union{Missing,Rational}}(missing, wh)
    memo[lu[1]:rd[1], lu[2]:rd[2]] .= Rational(0)
    memo[wh...] = Rational(1)

    p = recsolve!(1, 1, lu, rd, Ref(memo)) |> Float64
    println("Case #$(case): $p")
end

function main()
    T = parse(Int, readline(stdin))
    for case in 1:T
        W, H, L, U, R, D = parse.(Int, split(readline(stdin)))
        solve(case, (W, H), (L, U), (R, D))
    end
end

isinteractive() || main()


module PowerLogistic

using Distributions, Roots, Plots

export powerLogistic, mdpr, mdor, plotmdor

# power for simple logistic regression based on
# http://personal.health.usf.edu/ywu/logistic.pdf
# Hsieh et al. STATISTICS IN MEDICINE
# Statist. Med. 17, 1623-1634 (1998)
#

# created on Sept. 24, 2012

# OR = exp(β) odds ratio
# p1 = Pr(Y = 1)
function SSizeLogisticCon(p1, OR; alpha=0.05, power=0.8)
   β = log(OR)
   za = quantile(Normal(), 1 - alpha/2)
   zb = quantile(Normal(), power)
   return ceil(Int64,(za+zb)^2 / (p1*(1-p1)*β^2))
end

# OR = exp(β) odds ratio
# p1 = Pr(Y = 1)
function powerLogisticCon(n, p1, OR; alpha=0.05)
   β = log(OR)
   za = quantile(Normal(),1-alpha/2)
   return ccdf(Normal(),za-sqrt(n*β^2*p1*(1-p1)))
end

###########################
# logistic regression logit(p) = beta0+ beta1*X
# B=pr(X=1)
# p1 = Pr(D|X=0) # event rate at X=0
# p2 = Pr(D|X=1) # event rate at X=1
# alpha - type I error rate
function SSizeLogisticBin(p1, p2, B; alpha=0.05, power=0.8)
   za = quantile(Normal(),1-alpha/2)
   zb = quantile(Normal(),power)

   p=(1-B)*p1+B*p2
   part1 = za*sqrt(p*(1-p)/B)
   part2 = zb*sqrt( p1*(1-p1)+p2*(1-p2)*(1-B)/B)
   part3 = (p1-p2)^2*(1-B)
   return ceil(Int64,(part1+part2)^2/part3)
end

###########################
# logistic regression logit(p) = beta0+ beta1*X
# B=pr(X=1)
# p1 = Pr(D|X=0) # event rate at X=0
# p2 = Pr(D|X=1) # event rate at X=1
# alpha - type I error rate
function powerLogisticBin(n, p1, p2, B; alpha=0.05)
   za = quantile(Normal(),1-alpha/2)

   p=(1-B)*p1+B*p2
   a = za*sqrt(p*(1-p)/B)
   b = sqrt( p1*(1-p1)+p2*(1-p2)*(1-B)/B)
   myc = (p1-p2)^2*(1-B)
   return cdf(Normal(), (sqrt(n*myc) - a)/b )
end

#############################################################
# public functions
#############################################################

"""
   powerLogistic(n = 0, p1 = .0, p2 = .0, B = .0, OR = .0, alpha = 0.05, power = 0.8)

Estimates power or sample size at type I error = `alpha` and power ≥ `power`.
All options are keyword options. If `n` is not specified (i.e., n = 0), it produces sample size.
Otherwise, it produces power of the sample. Other options are:

- `p1` = Pr(Y = 1) for a continous variable. `p1` = Pr(D|X=0), event rate at X = 0.
- `p2` = Pr(D|X=1), event rate at X = 1.
- `B` = Pr(X=1)
- `alpha`: Type I error (default = 0.05)
- `power`: Power (default = 0.8)

### Example
```jldoctest
julia> powerLogistic(p1=.5,OR=exp(0.405),power=0.95)
317

julia> powerLogistic(n = 317, p1=.5,OR=exp(0.405))
0.950...

julia> powerLogistic(p1 = .4, p2 = .5, B = .5, OR=exp(0.405), alpha = 0.05, power = 0.95)
1281

julia> powerLogistic(n = 1281, p1 = .4, p2 = .5, B = .5, OR=exp(0.405), alpha = 0.05)
0.950...
```
"""
function powerLogistic(;
   n = 0,
   p1 = 0.0,
   p2 = 0.0,
   B = 0.0,
   OR = 0.0,
   alpha = 0.05,
   power = 0.8)

   # return sample size
   if n == 0
      if B == 0.0 # continuous
         if p1 == 0.0 || OR == 0.0
            error("`p1` and `OR` must be greater than zero.")
         end
         return SSizeLogisticCon(p1,OR,alpha = alpha, power = power)
      else
         if p1 == p2 && p1 > 0.0 && p2 > 0.0
            error("`p1` and `p2` must be different and must be greater than zero.")
         end
         return SSizeLogisticBin(p1,p2,B,alpha = alpha, power = power)
      end
   # return power
   else
      if B == 0.0 # continuous
         if p1 == 0.0 || OR == 0.0
            error("`p1` and `OR` must be greater than zero.")
         end
         return powerLogisticCon(n, p1, OR,alpha = alpha)
      else
         if p1 == p2 && p1 > 0.0 && p2 > 0.0
            error("`p1` and `p2` must be different and must be greater than zero.")
         end
         return powerLogisticBin(n, p1, p2, B,alpha = alpha)
      end
   end
end

function mdpr(;n = 0, p1 = 0.0, B = 0.0, alpha = 0.05, power = 0.8)

   if n == 0
      error("`n` is required. `n` is the total sample size.")
   end

   if p1 == 0.0
      error("`p1` is required. `p1` is the event rate at X = 0.")
   end

   if B == 0.0
      error("`B` is required. `B` is the proportion of the sample with X = 1.")
   end

   # find p2 that achieves power greater than the power specified
   return fzero(x->powerLogisticBin(n,p1,x,B,alpha=alpha) - power,p1+1e-10,1.0-1e-10)
end

function mdor(;n = 0, p1 = 0.0, B = 0.0, alpha = 0.05, power = 0.8)

   if n == 0
      error("`n` is required. `n` is the total sample size.")
   end

   if p1 == 0.0
      error("`p1` is required. `p1` is the event rate at X = 0.")
   end

   if B == 0.0
      error("`B` is required. `B` is the proportion of the sample with X = 1.")
   end

   # find p2 that achieves power greater than the power specified
   p2 = fzero(x->powerLogisticBin(n,p1,x,B,alpha=alpha) - power,p1 + 1e-9,1.0-1e-9)
   return oddsratio(p2,p1)
end

function oddsratio(p1,p2)
   return (p1/(1-p1))/(p2/(1-p2))
end

function plotmdor(; nvec::Vector = [], p1::Union{Float64,Vector} = 0.0, B::Union{Float64,Vector} = 0.0, alpha = 0.05, power = 0.8)

   len = length(nvec)
   if len < 2
      error("`nvec` must have 2 or more values for sample size")
   end

   orvec = Array{Float64,2}(length(p1),len)

   for (i,p) in enumerate(p1)
      orvec[i,:] = [mdor(n = x, p1 = p, B = B, alpha = alpha, power = power) for x in nvec]
   end

   # plotly
   plotly()

   xlim_lower = nvec[len] / 40
   orvec = orvec'

   # plot
   nlines = length(p1)

   # plot
   if nlines == 1
      plot(nvec,orvec[:,1],
         marker=(2,.5,:circle,:blue),
         left_margin = 8mm,
         title = "Minimum Detectable Odds Ratio",
         ylabel = "Odds Ratio",
         xlabel = "Sample Size",
         ylims=(1-0.1,maximum(orvec)+0.1),
         xlims=(-xlim_lower,nvec[len]+xlim_lower),
         label=false,
         legend = false
      )
   else
      plot(nvec,orvec[:,1],label = string("p1 = ",p1[1]),marker=(2,.5,:circle))
      for i=2:(nlines-1)
         plot!(nvec,orvec[:,i],label = string("p1 = ",p1[i]),marker=(2,.5,:circle))
      end
      plot!(nvec,orvec[:,nlines],
            label = string("p1 = ",p1[nlines]),
            marker=(2,.5,:circle),
            left_margin = 8mm,
            title = "Minimum Detectable Odds Ratio",
            ylabel = "Odds Ratio",
            xlabel = "Sample Size",
            ylims=(1-0.1,maximum(orvec)+0.1),
            xlims=(-xlim_lower,nvec[len]+xlim_lower)
         )
    end
end

end


using SolverPeeker
using Test
using JuMP
using MathOptInterface
const MOI = MathOptInterface
using GLPK

# Start writing tests here, then divide into seperate files

@testset "main" begin

    @testset "lprep.jl" begin

        """ Test not able to conbvert to lp from non-linear constraint. """
        model0 = Model()
        @variable(model0, x >= 0)
        @variable(model0, y >= 0)
        @objective(model0, Max, 3x+5y+2)
        @constraint(model0, x^2+2y*x-y <= 180)

        @test_throws MOI.UnsupportedConstraint get_lpmodel(model0)

        """ Test getting correct A, b and c matrix up to set equality. """
        model1 = Model()
        @variable(model1, x >= 5, Int)
        @variable(model1, y <= 3)
        @variable(model1, 0 <= z <= 50)
        @objective(model1, Max, 3x+5y-2z+2)
        @constraint(model1, x+2y <= 45)
        @constraint(model1, 3z - y >= 4)
        @constraint(model1, 5x+y+2z <= 55)

        lp = get_lpmodel(model1)
        A, b = get_constraint_matrices(lp)
        c = get_objective_vector(lp)
        # From model1, the A matrix, and b vector, when constraints are made less than,
        # sould be equal, up to reshuffeling of elements, to the following:
        mat = [1 2 0; 0 1 -3; 5 1 2]
        vec = [45, -4, 55]
        # Objective only gives variable indices
        obj = [3 5 -2]

        @test issetequal(A, mat)
        @test issetequal(b, vec)
        @test issetequal(c, obj)
        @test lp.objective.constant == 2

        # Add a constraint:
        @constraint(model1, 9x-7y+4-x == 3z)
        lp = get_lpmodel(model1)
        A, b = get_constraint_matrices(lp)
        # The constraint will be split in two, one less than or equal, one greater than or equal,
        # and then the latter will be converted to a less then.
        #All this after summing and moving all the variables to 1 side.
        mat = [mat; 8 -7 -3; -8 7 3]
        vec = [b; -4; 4]

        @test issetequal(A, mat)
        @test issetequal(b, vec)
        # objective should remain the same.
        @test issetequal(c, obj)
        @test lp.objective.constant == 2

        """ Test getting of variables, with correct variable indices. """
        model2 = Model()
        @variable(model2, x >= 5, Int)
        @variable(model2, y <= 3)
        @variable(model2, 0 <= z <= 50)
        @variable(model2, a[1:2], Bin)
        @objective(model2, Max, 3x+5y-2z+sum(a))

        lp2 = get_lpmodel(model2)

        # How the arrays should look like up to set equality
        #greater_than = [5, NaN, 0, NaN, NaN]
        #less_than = [NaN, 3, 50, NaN, NaN]
        #integer = [1, NaN, NaN, NaN, NaN]
        #zero_one = [NaN, NaN, NaN, 1, 1]

        greater_than = get_greater_than_variables(lp2)
        less_than = get_less_than_variables(lp2)
        integer = get_integer_variables(lp2)
        zero_one = get_zero_one_variables(lp2)

        for (index, name) in lp2.var_to_name
            if (name == "x")
                @test greater_than[index.value] == 5
                @test isnan(less_than[index.value])
                @test integer[index.value] == 1
                @test isnan(zero_one[index.value])
            elseif (name == "y")
                @test isnan(greater_than[index.value])
                @test less_than[index.value] == 3
                @test isnan(integer[index.value])
                @test isnan(zero_one[index.value])
            elseif (name == "z")
                @test greater_than[index.value] == 0
                @test less_than[index.value] == 50
                @test isnan(integer[index.value])
                @test isnan(zero_one[index.value])
            elseif (name == "a[1]")
                @test isnan(greater_than[index.value])
                @test isnan(less_than[index.value])
                @test isnan(integer[index.value])
                @test zero_one[index.value] == 1
            elseif (name == "a[2]")
                @test isnan(greater_than[index.value])
                @test isnan(less_than[index.value])
                @test isnan(integer[index.value])
                @test zero_one[index.value] == 1
            else
                # No other names should exist in the model
                @test false
            end
        end

    end

    @testset "recognize.jl" begin

        """ Test difference constraints """

        # Test that only difference constraints return true
        model1 = Model()
        @variable(model1, x>=0, Int)
        @variable(model1, y, Int)
        @variable(model1, z, Int)
        @objective(model1, Min, x-2y-z)
        @constraint(model1, x-y <= 3)
        @constraint(model1, -z+y <= -5)
        @constraint(model1, x-y >= 7)
        @constraint(model1, z-x <= 2)
        @constraint(model1, x-z <= -3)

        lp1 = get_lpmodel(model1)
        @test recognize(lp1, DifferenceConstraints()) == true

        # Add a non-difference constraint, and get false
        @constraint(model1, x+z <= -3)

        lp1 = get_lpmodel(model1)
        @test recognize(lp1, DifferenceConstraints()) == false

        # Test that model with only one variable will return false
        model2 = Model()
        @variable(model2, x)
        @objective(model2, Max, x+2)
        @constraint(model2, x-4 <= 9)

        lp2 = get_lpmodel(model2)
        @test recognize(lp2, DifferenceConstraints()) == false

        # Test if no constraints return false
        model3 = Model()
        @variable(model3, x, Bin)
        @variable(model3, y, Int)
        @variable(model3, z, Int)
        @objective(model3, Max, x+y+z)

        lp3 = get_lpmodel(model3)
        @test recognize(lp3, DifferenceConstraints()) == false

        """ Test AllIntegerVariables """

        @test recognize(lp1, AllIntegerVariables()) == true
        @test recognize(lp2, AllIntegerVariables()) == false
        @test recognize(lp3, AllIntegerVariables()) == false

        """ Test AllIntegerConstraintBounds """

        @test recognize(lp1, AllIntegerConstraintBounds()) == true
        @test recognize(lp2, AllIntegerConstraintBounds()) == true

        model4 = Model()
        @variable(model4, x)
        @variable(model4, y, Int)
        @variable(model4, z, Int)
        @objective(model4, Max, x+y+z)
        @constraint(model4, x-z <= 4.6)

        lp4 = get_lpmodel(model4)
        @test recognize(lp4, AllIntegerConstraintBounds()) == false

    end

    @testset "solve.jl" begin

        model1 = Model()
        @variable(model1, x1, Int)
        @variable(model1, x2, Int)
        @variable(model1, x3, Int)
        @variable(model1, x4, Int)
        @variable(model1, x5, Int)
        @objective(model1, Min, x1+x2-5x3+2x4+6x5)
        @constraint(model1, x1-x2 <= 0)
        @constraint(model1, x1-x5 <= -1)
        @constraint(model1, x2-x5 <= 1)
        @constraint(model1, x3-x1 <= 5)
        @constraint(model1, x4-x1 <= 4)
        @constraint(model1, x4-x3 <= -1)
        @constraint(model1, x5-x3 <= -3)
        @constraint(model1, x5-x4 <= -3)

        lp1 = get_lpmodel(model1)

        # TODO: Test return status
        solve!(lp1, model1, ShortestPath())

    end

    @testset "optimize.jl" begin
        # TODO: Attatch status return and test correct
        # Test feasible unbounded
        model1 = Model()
        @variable(model1, x1, Int)
        @variable(model1, x2, Int)
        @variable(model1, x3, Int)
        @variable(model1, x4, Int)
        @variable(model1, x5, Int)
        @objective(model1, Min, x1+x2-5x3+2x4+6x5)
        @constraint(model1, x1-x2 <= 0)
        @constraint(model1, x1-x5 <= -1)
        @constraint(model1, x2-x5 <= 1)
        @constraint(model1, x3-x1 <= 5)
        @constraint(model1, x4-x1 <= 4)
        @constraint(model1, x4-x3 <= -1)
        @constraint(model1, x5-x3 <= -3)
        @constraint(model1, x5-x4 <= -3)

        @test SolverPeeker.optimize!(model1) == true

        # Test infeasible
        model2 = Model()
        @variable(model2, x>=0, Int)
        @variable(model2, y, Int)
        @variable(model2, z, Int)
        @objective(model2, Min, x-2y-z)
        @constraint(model2, x-y <= 3)
        @constraint(model2, -z+y <= -5)
        @constraint(model2, x-y >= 7)
        @constraint(model2, z-x <= 2)
        @constraint(model2, x-z <= -3)

        @test SolverPeeker.optimize!(model2) == true

        # TODO: Test not recognizeable, no solver attatched
        model3 = Model()
        @variable(model3, x>=0)
        @variable(model3, y)
        @variable(model3, z)
        @objective(model3, Min, x-2y-z)
        @constraint(model3, x-y <= 3)
        @constraint(model3, -z+y <= -5)
        @constraint(model3, x-y >= 7)
        @constraint(model3, z-x <= 2)
        @constraint(model3, 2x-z <= -3)

        @test SolverPeeker.optimize!(model3) == false

        # TODO: Test optimizer is used
        set_optimizer(model3, GLPK.Optimizer)

        @test SolverPeeker.optimize!(model3) == false

    end

end

#include("lprep.jl")
#include("recognize.jl")


"""Initialises the breaks array based on various methods:

    maxentropy: quantile / maximum entropy.
    rand: random breaks without empty classes."""
function BreaksInit(n::Int,X::Array{T,1},method::String="maxentropy") where {T<:AbstractFloat}

    if method == "maxentropy"

        np = length(X) ÷ n       # number of data points per chunk

        # breaks are determind by the lower bounds index of the data array X
        breaks = Array{Int,1}(undef,n+1)

        # the lower bounds index of the first interval is always 1
        # quantile-approach / maximum entropy method
        breaks[1:end-1] = collect(1:np:np*n)

        # this is redundant but makes class collection easier
        breaks[end] = length(X)+1

    elseif method == "rand"     # random initialisation

        # breaks are determind by the lower bounds index of the data array X
        breaks = Array{Int,1}(undef,n+1)
        breaks[1] = 1
        breaks[end] = length(X)+1

        breaks[2:end-1] = randintNDNC(3,length(X)-1,n-1)

    else
        throw(error("Method '$method' not defined."))
    end

    return breaks
end

"""randint with no duplicates and no consecutive numbers"""
function randintNDNC(N0::Int,N1::Int,n::Int)

    ratio = n/(N1-N0)

    if ratio > 0.33
        throw(error("Subset-set size ratio is $ratio, too likely that no solution can be found. Increase n."))
    elseif ratio > 0.2 && n > 10000
        @warn "Finding $n random numbers for a subset-set size ratio of $ratio may take a long time."
    end

    v = Array{Int,1}(undef,n)
    v[1] = rand(N0:N1)

    for i in 2:n
        r = rand(N0:N1)     # pick a random number
        while any(abs.(r .- v[1:i-1]) .<= 1)    # check that it's not duplicate or a consecutive
            r = rand(N0:N1) # if that's the case try another one.
        end
        v[i] = r
    end

    sort!(v)
    return v
end


#=
nufft.jl
Non-uniform FFT (NUFFT), currently a wrapper around NFFT.jl
todo: open issues: small N, odd N, nufft!, adjoint!
2019-06-06, Jeff Fessler, University of Michigan
=#

export nufft_init, nufft_plots, nufft

#using MIRT: dtft_init, map_many
using NFFT
using Plots
using Random: seed!
using LinearAlgebra: norm

"""
`nufft_eltype(w::AbstractArray{<:Real})`
ensure NFFTPlan is Float32 or Float64
"""
function nufft_eltype(w::AbstractArray{<:Number})
	T = eltype(w)
	if T <: Integer || T == Float16
		return Float32 # require at least Float32
	elseif T ∉ (Float32,Float64)
		throw("unknown type $T")
	end
	return T
end

# the following convenience routine ensures correct type passed to nfft()
# see https://github.com/tknopp/NFFT.jl/pull/33
# todo: may be unnecessary with future version of nfft()
"""
`nufft_typer(T::DataType, x::AbstractArray{<:Real}; warn::Bool=true)`
type conversion wrapper for `nfft()`
"""
function nufft_typer(T::DataType, x::AbstractArray{<:Number}; warn::Bool=true)
	if eltype(x) == T
		return x
	end
	@warn("converting $(eltype(x)) to $T")
	return T.(x)
end

"""
`p = nufft_init(w, N; nfft_m=4, nfft_sigma=2.0, pi_error=true, n_shift=0)`

Setup 1D NUFFT,
for computing fast ``O(N \\log N)`` approximation to

``X[m] = sum_{n=0}^{N-1} x[n] exp(-i w[m] (n - n_shift)), m=1,…,M``

in
* `w::AbstractArray{<:Real}` `[M]` frequency locations (units radians/sample)
	+ `eltype(w)` determines the NFFTPlan type; so to save memory use Float32!
* `N::Int` signal length

option
* `nfft_m::Int` 		see NFFT.jl documentation; default 4
* `nfft_sigma::Real`	"", default 2.0
* `n_shift::Real`		often is N/2; default 0
* `pi_error::Bool`		throw error if ``|w| > π``, default `true`
   + Set to `false` only if you are very sure of what you are doing!
* `do_many::Bool`	support extended inputs via `map_many`? default `true`

out
* `p NamedTuple` with fields
	`nufft = x -> nufft(x), adjoint = y -> nufft_adj(y), A=LinearMap`

The default settings are such that for a 1D signal of length N=512,
the worst-case error is below 1e-5 which is probably adequate
for typical medical imaging applications.
To verify this statement, run `nufft_plot1()` and see plot.
"""
function nufft_init(w::AbstractArray{<:Real}, N::Int;
		n_shift::Real = 0,
		nfft_m::Int = 4,
		nfft_sigma::Real = 2.0,
		pi_error::Bool = true,
		do_many::Bool = true,
	)

	N < 6 && throw("NFFT may be erroneous for small N")
	isodd(N) && throw("NFFT erroneous for odd N")
	pi_error && any(abs.(w) .> π) &&
		throw(ArgumentError("|w| > π is likely an error"))

	T = nufft_eltype(w)
	CT = Complex{T}
	f = convert.(T, w/(2π)) # note: NFFTPlan must have correct type
	p = NFFTPlan(f, N, nfft_m, nfft_sigma) # create plan
	M = length(w)
	# extra phase here because NFFT always starts from -N/2
	phasor = CT.(cis.(-w * (N/2 - n_shift)))
	phasor_conj = conj.(phasor)
	forw1 = x -> nfft(p, nufft_typer(CT, x)) .* phasor
#	forw! = x,y -> nfft!(p, nufft_typer(CT, x)) .* phasor # todo
	back1 = y -> nfft_adjoint(p, nufft_typer(CT, y .* phasor_conj))

	A = LinearMap{CT}(x -> forw1(x), y -> back1(y), M, N) # no "many" here!

	if do_many
		forw = x -> map_many(forw1, x, (N,))
		back = y -> map_many(back1, y, (M,))
	else
		forw = forw1
		back = back1
	end

	return (nufft=forw, adjoint=back, A=A)
end

"""
`p = nufft_init(w, N; nfft_m=4, nfft_sigma=2.0, pi_error=true, n_shift=?)`

Setup multi-dimensional NUFFT,
for computing fast ``O(N \\log N)`` approximation to

``X[m] = sum_{n=0}^{N-1} x[n] exp(-i w[m,:] (n - n_shift)), m=1,…,M``

in
* `w::AbstractMatrix{<:Real}` `[M,D]` frequency locations (units radians/sample)
	+ `eltype(w)` determines the NFFTPlan type; so to save memory use Float32!
* `N::Dims` `[D]` signal dimensions

option
* `nfft_m::Int` 		see NFFT.jl documentation; default 4
* `nfft_sigma::Real`	"", default 2.0
* `n_shift::AbstractVector{<:Real}`	`[D]`	often is N/2; default zeros(D)
* `pi_error::Bool`		throw error if ``|w| > π``, default `true`
   + Set to `false` only if you are very sure of what you are doing!
* `do_many::Bool`	support extended inputs via `map_many`? default `true`

The default `do_many` option is designed for parallel MRI where the k-space
sampling pattern applies to every coil.
It may also be useful for dynamic MRI with repeated sampling patterns.
The coil and/or time dimensions must come after the spatial dimensions.

out
* `p NamedTuple` with fields
	`nufft = x -> nufft(x), adjoint = y -> nufft_adj(y), A=LinearMap`
	(The LinearMap does not support `do_many`.)
"""
function nufft_init(w::AbstractMatrix{<:Real},
		N::Dims;
		n_shift::AbstractVector{<:Real} = zeros(Int, length(N)),
		nfft_m::Int = 4,
		nfft_sigma::Real = 2.0,
		pi_error::Bool = true,
		do_many::Bool = true,
	)

	any(N .< 6) && throw("NFFT may be erroneous for small N")
	any(isodd.(N)) && throw("NFFT erroneous for odd N")
	pi_error && any(abs.(w) .> π) &&
		throw(ArgumentError("|w| > π is likely an error"))

	M,D = size(w)
	length(N) != D && throw(DimensionMismatch("length(N) vs D=$D"))
	length(n_shift) != D && throw(DimensionMismatch("length(n_shift) vs D=$D"))

	T = nufft_eltype(w)
	CT = Complex{T}
	f = convert.(T, w/(2π)) # note: NFFTPlan must have correct type
	p = NFFTPlan(f', N, nfft_m, nfft_sigma) # create plan

	# extra phase here because NFFT always starts from -N/2
	phasor = CT.(cis.(-w * (collect(N)/2. - n_shift)))
	phasor_conj = conj.(phasor)
	forw1 = x -> nfft(p, nufft_typer(CT, x)) .* phasor
	back1 = y -> nfft_adjoint(p, nufft_typer(CT, y .* phasor_conj))

	# no "many" for LinearMap:
	A = LinearMap{CT}(x -> forw1(reshape(x,N)), y -> back1(y)[:], M, prod(N))

	if do_many
		forw = x -> map_many(forw1, x, N)
		back = y -> map_many(back1, y, (M,))
	else
		forw = forw1
		back = back1
	end

	return (nufft=forw, adjoint=back, A=A)
end

"""
`nufft_test1(; M=30, N=20, n_shift=1.7, T=?, tol=?)`
simple 1D tests
"""
function nufft_test1( ;
		M::Int = 30, N::Int = 20, n_shift::Real = 1.7,
		T::DataType = Float64, tol::Real = 1e-6)
	seed!(0)
	w = (rand(M) .- 0.5) * 2 * pi
	w = T.(w)
	x = complex.(randn(T, N), randn(T, N))
	sd = dtft_init(w, N; n_shift=n_shift)
	sn = nufft_init(w, N, n_shift=n_shift)
	o0 = sd.dtft(x)
	y = Complex{T}.(o0 / norm(o0))
	a0 = sd.adjoint(y)
	o1 = sn.nufft(x)
	o2 = sn.A * x
	a1 = sn.adjoint(y)
	a2 = sn.A' * y
	@test norm(o1 - o0, Inf) / norm(o0, Inf) < tol
	@test isequal(o1, o2)
	@test norm(a1 - a0, Inf) / norm(a0, Inf) < tol
	@test isequal(a1, a2)
	@test isapprox(Matrix(sn.A)', Matrix(sn.A')) # 1D adjoint test

	sn = nufft_init(w, N, n_shift=n_shift, do_many=false)
	o3 = sn.nufft(x)
	@test norm(o3 - o0, Inf) / norm(o0, Inf) < tol

	sn.nufft(ones(Int,N)) # should produce a warning
	true
end

"""
`nufft_test2(; M=?, N=?, n_shift=?, T=?, tol=?)`
simple 2D test
"""
function nufft_test2(;
		M::Int = 31,
		N::Dims = (10,8),
		n_shift::AbstractVector{<:Real} = [4,3],
		T::DataType = Float64, tol::Real = 2e-6)

	seed!(0)
	w = []

#=
	# fft sampling
	M = prod(N)
	w1 = (2*pi) * (0:N[1]-1) / N[1]
    w2 = (2*pi) * (0:N[2]-1) / N[2]
    w1 = repeat(w1, 1, N[2])
    w2 = repeat(w2', N[1], 1)
    w = [w1[:] w2[:]]
	n_shift = [0,0]
=#

	w = (rand(M,2) .- 0.5) * 2 * pi

	w = T.(w)
	sd = dtft_init(w, N; n_shift=n_shift)
	sn = nufft_init(w, N, n_shift=n_shift, pi_error=false)

	x = complex.(randn(T, N), randn(T, N))
	o0 = sd.dtft(x)
	o1 = sn.nufft(x)
	@test norm(o1 - o0, Inf) / norm(o0, Inf) < tol

#=
	# fft test only
	o2 = fft(x)
	o0 = reshape(o0, N)
	o1 = reshape(o1, N)
	@show norm(o2 - o0, Inf) / norm(o0, Inf)
	@show norm(o1 - o0, Inf) / norm(o0, Inf)
	@show norm(o2 - o1, Inf) / norm(o0, Inf)
=#

	y = Complex{T}.(o0 / norm(o0))
	a0 = sd.adjoint(y)
	a1 = sn.adjoint(y)
	@test norm(a1 - a0, Inf) / norm(a0, Inf) < tol
	o2 = sn.A * x[:]
	@test isequal(o1, o2)
	a2 = sn.A' * y
	a2 = reshape(a2, N)
	@test isequal(a1, a2)
	@test isapprox(Matrix(sn.A)', Matrix(sn.A')) # 2D adjoint test

	@test isequal(sn.nufft(cat(dims=4, x, 2x)),
		cat(dims=3, sn.nufft(x), sn.nufft(2x)))
	@test isequal(sn.adjoint(cat(dims=3, y, 2y)),
			cat(dims=4, sn.adjoint(y), sn.adjoint(2y)))

	sn = nufft_init(w, N, n_shift=n_shift, pi_error=false, do_many=false)
	o3 = sn.nufft(x)
	@test norm(o3 - o0, Inf) / norm(o0, Inf) < tol
	true
end

"""
`w, errs = nufft_errors(; M=?, w=?, N=?, n_shift=?, ...)`

Compute worst-case errors for NUFFT (for signal of length N of unit norm)
"""
function nufft_errors(;
	M::Int = 401,
	N::Int = 512,
	w::AbstractArray{<:Real} = LinRange(0, 2π/N, M),
	n_shift::Real = 0,
	kwargs...)

	sd = dtft_init(w, N; n_shift=n_shift)
	sn = nufft_init(w, N; n_shift=n_shift, kwargs...)
	E = Matrix(sn.A - sd.A)
	return w, mapslices(norm, E, dims=2)[:] # [M]
end

"""
`nufft_plot1()`

Plot worst-case error over all frequencies w between 0 and 2pi/N for various N.
"""
function nufft_plot1()
	Nlist = 2 .^ (4:9)
	elist = zeros(length(Nlist))
	for ii=1:length(Nlist)
		w, errs = nufft_errors(; N = Nlist[ii])
	#	plot(w*N/2/π, tmp)
		elist[ii] = maximum(errs)
	end
	scatter(Nlist, elist, xtick=Nlist, label="", xlabel="N", ylabel="error")
end

"""
`nufft_plot_error_m(; mlist=?)`

plot error vs NFFT sigma
"""
function nufft_plot_error_m(;
	mlist::AbstractArray{<:Int} = 3:7)
	worst = zeros(length(mlist))
	for jm = 1:length(mlist)
		_, errs = nufft_errors(; nfft_m=mlist[jm])
		worst[jm] = maximum(errs)
	end
	scatter(mlist, worst, xlabel="m", ylabel="error", label="")
end

"""
`nufft_plot_error_s(; slist=?)`

plot error vs NFFT sigma
"""
function nufft_plot_error_s(;
	slist::AbstractArray{<:Real} = [1.5; 2:6])
	worst = zeros(length(slist))
	for is = 1:length(slist)
		_, errs = nufft_errors(; nfft_sigma=slist[is])
		worst[is] = maximum(errs)
	end
	scatter(slist, worst, xlabel="σ", ylabel="error", label="")
end

"""
`nufft_plots()`
various NUFFT error plots
"""
function nufft_plots()
	p1 = nufft_plot_error_s()
	p2 = nufft_plot_error_m()
	p3 = nufft_plot1()
	plot(p1, p2, p3)
end

"""
`nufft(:test)`
self tests
"""
function nufft(test::Symbol)
	test != :test && throw("bad symbol $test")
	@test_throws String nufft_eltype(ones(BigFloat,3))
	@test_throws String nufft_init([0], 2)
	@test_throws String nufft_init([0], 7)
	@test_throws ArgumentError nufft_init([2π], 8)
	@test nufft_plots() isa Plots.Plot
	@test nufft_test1()
	@test nufft_test1(; T=Float32)
	@test nufft_test2()
	@test nufft_test2(; T=Float32)
	true
end

#=
	# todo: 1d vs 2d
	M = 4
	N = (M,1)
	seed!(0)
	w = (0:(M-1))/M * 2 * pi
	w = [w zeros(M)]
	sd = dtft_init(w, N)
	Ad = Matrix(sd.A)
	sn1 = nufft_init(w[:,1], N[1]; pi_error=false)
	An1 = Matrix(sn1.A)
	@show maximum(abs.(An1 - Ad))
	sn2 = nufft_init(w, N; pi_error=false)
	An2 = Matrix(sn2.A)
#	@show o0 = sd.dtft(x)
#	@show o1 = sn.nufft(x)
#	@show o1-o0
#	@test maximum(abs.(o1 - o0)) < tol
=#

#=
	# todo MWE for 1D vs 2D
	M = 6
	x1 = collect((-Int(M/2)):(Int(M/2)-1))/M
	N1 = M
	p1 = NFFTPlan(x1, N1)
#	p1 = NFFTPlan(x1', (N1,)) # this works too
#	f1 = ones(N1)
	f1 = [1.,2,3,4,0,0]
	o1 = nfft(p1, complex(f1)) # slightly annoying to have to use complex() here

	N2 = (M,4) # works fine with "4" instead of "1" here and separable signal
	x2 = [x1 zeros(M)]
	p2 = NFFTPlan(x2', N2)
	f2 = f1 * [0,0,1,0]'
	o2 = nfft(p2, complex(f2)) # ditto

	display(round.(o1, digits=7))
	display(round.(o2, digits=7))

#	tmp = nufft_init(x1*2*pi, N1)
=#

#nufft(:test)
#nufft_test1(;N=41) # todo: inaccurate


#= ===================
   LOGISTIC CLASSIFIER
   =================== =#

"""
Logistic Classifier (typically called "Logistic Regression"). This model is
a standard classifier for both binary and multiclass classification.
In the binary case it corresponds to the LogisticLoss, in the multiclass to the
Multinomial (softmax) loss. An elastic net penalty can be applied with
overall objective function

``L(y, Xθ) + n⋅λ|θ|₂²/2 + n⋅γ|θ|₁``

where ``L`` is either the logistic or multinomial loss and ``λ`` and ``γ`` indicate
the strength of the L2 (resp. L1) regularisation components and
``n`` is the number of samples `size(X, 1)`.
With `scale_penalty_with_samples = false` the objective function is
``L(y, Xθ) + λ|θ|₂²/2 + γ|θ|₁``

## Parameters

$TYPEDFIELDS

$(example_docstring("LogisticClassifier", nclasses = 2))
"""
@with_kw_noshow mutable struct LogisticClassifier <: MMI.Probabilistic
    "strength of the regulariser if `penalty` is `:l2` or `:l1`.
    Strength of the L2 regulariser if `penalty` is `:en`."
    lambda::Real             = 1.0
    "strength of the L1 regulariser if `penalty` is `:en`."
    gamma::Real              = 0.0
    "the penalty to use, either `:l2`, `:l1`, `:en` (elastic net) or `:none`."
    penalty::SymStr          = :l2
    "whether to fit the intercept or not."
    fit_intercept::Bool      = true
    "whether to penalize the intercept."
    penalize_intercept::Bool = false
    "whether to scale the penalty with the number of samples."
    scale_penalty_with_samples::Bool = true
    "type of solver to use, default if `nothing`."
    solver::Option{Solver}   = nothing
end

glr(m::LogisticClassifier, nclasses::Integer) =
    LogisticRegression(m.lambda, m.gamma;
                       penalty=Symbol(m.penalty),
                       multi_class=(nclasses > 2),
                       fit_intercept=m.fit_intercept,
                       penalize_intercept=m.penalize_intercept,
                       scale_penalty_with_samples=m.scale_penalty_with_samples,
                       nclasses=nclasses)

descr(::Type{LogisticClassifier}) = "Classifier corresponding to the loss function ``L(y, Xθ) + λ|θ|₂²/2 + γ|θ|₁`` where `L` is the logistic loss."

#= ======================
   MULTINOMIAL CLASSIFIER
   ====================== =#

"""
See `LogisticClassifier`, it's the same except that multiple classes are assumed
by default. The other parameters are the same.

## Parameters

$TYPEDFIELDS

$(example_docstring("LogisticClassifier", nclasses = 3))
"""
@with_kw_noshow mutable struct MultinomialClassifier <: MMI.Probabilistic
    "strength of the regulariser if `penalty` is `:l2` or `:l1`.
    Strength of the L2 regulariser if `penalty` is `:en`."
    lambda::Real             = 1.0
    "strength of the L1 regulariser if `penalty` is `:en`."
    gamma::Real              = 0.0
    "the penalty to use, either `:l2`, `:l1`, `:en` (elastic net) or `:none`."
    penalty::SymStr          = :l2
    "whether to fit the intercept or not."
    fit_intercept::Bool      = true
    "whether to penalize the intercept."
    penalize_intercept::Bool = false
    "whether to scale the penalty with the number of samples."
    scale_penalty_with_samples::Bool = true
    "type of solver to use, default if `nothing`."
    solver::Option{Solver}   = nothing
end

glr(m::MultinomialClassifier, nclasses::Integer) =
    MultinomialRegression(m.lambda, m.gamma;
                          penalty=Symbol(m.penalty),
                          fit_intercept=m.fit_intercept,
                          penalize_intercept=m.penalize_intercept,
                          scale_penalty_with_samples=m.scale_penalty_with_samples,
                          nclasses=nclasses)

descr(::Type{MultinomialClassifier}) =
    "Classifier corresponding to the loss function " *
    "``L(y, Xθ) + λ|θ|₂²/2 + γ|θ|₁`` where `L` is the multinomial loss."


abstract type AbstractLayer{N} end

Base.ndims(::AbstractLayer{N}) where {N} = N
Base.size(layer::AbstractLayer, d::Int) = size(layer)[d]

"""
    flatten(layer, x)

Returns a vectorized version of `x`.
"""
function flatten(layer::AbstractLayer, x::AbstractArray)
    @assert size(layer) == size(x)[1:ndims(layer)]
    if ndims(layer) == ndims(x)
        return vec(x)
    else
        return reshape(x, length(layer), prod(size(x, d) for d in (ndims(layer) + 1):ndims(x)))
    end
end

"""
    energy(layer, x)

Layer energy, reduced over layer dimensions.
"""
function energy(layer::AbstractLayer, x::AbstractArray)
    @assert size(layer) == size(x)[1:ndims(layer)]
    Es = energies(layer, x)
    if ndims(layer) == ndims(x)
        return sum(Es)
    else
        E = sum(Es; dims = 1:ndims(layer))
        return reshape(E, size(x)[(ndims(layer) + 1):end])
    end
end

"""
    free_energy(layer, inputs = 0)

Cumulant generating function of layer, reduced over layer dimensions.
"""
function free_energy(layer::AbstractLayer, inputs::Union{Real,AbstractArray} = 0)
    F = free_energies(layer, inputs)
    if inputs isa Real || ndims(layer) == ndims(inputs)
        return sum(F)
    else
        f = sum(F; dims = 1:ndims(layer))
        return reshape(f, size(inputs)[(ndims(layer) + 1):end])
    end
end

"""
    suffstats(layer, data; [wts])

Computes the sufficient statistics of `layer` extracted from `data`.
"""
function suffstats end

"""
    batchdims(layer, x)

Indices of batch dimensions in `x`, with respect to `layer`.
"""
function batchdims(layer::AbstractLayer, x::AbstractArray)
    @assert size(layer) == size(x)[1:ndims(layer)]
    return (ndims(layer) + 1):ndims(x)
end

"""
    batch_size(layer, x)

Batch sizes of `x`, with respect to `layer`.
"""
function batch_size(layer::AbstractLayer, x::AbstractArray)
    @assert size(layer) == size(x)[1:ndims(layer)]
    return size(x)[batchdims(layer, x)]
end

"""
    batchmean(layer, x; wts = nothing)

Mean of `x` over batch dimensions, weigthed by `wts`.
"""
function batchmean(layer::AbstractLayer, x::AbstractArray; wts = nothing)
    @assert size(layer) == size(x)[1:ndims(layer)]
    if ndims(layer) == ndims(x)
        wts::Nothing
        return x
    else
        μ = wmean(x; wts, dims = batchdims(layer, x))
        return reshape(μ, size(layer))
    end
end

"""
    batchvar(layer, x; wts = nothing, [mean])

Variance of `x` over batch dimensions, weigthed by `wts`.
"""
function batchvar(
    layer::AbstractLayer, x::AbstractArray; wts = nothing, mean = batchmean(layer, x; wts)
)
    @assert size(layer) == size(x)[1:ndims(layer)] == size(mean)
    return batchmean(layer, (x .- mean).^2; wts)
end

"""
    batchstd(layer, x; wts = nothing, [mean])

Standard deviation of `x` over batch dimensions, weigthed by `wts`.
"""
function batchstd(
    layer::AbstractLayer, x::AbstractArray; wts = nothing, mean = batchmean(layer, x; wts)
)
    return sqrt.(batchvar(layer, x; wts, mean))
end

"""
    batchcov(layer, x; wts = nothing, [mean])

Covariance of `x` over batch dimensions, weigthed by `wts`.
"""
function batchcov(
    layer::AbstractLayer, x::AbstractArray; wts = nothing, mean = batchmean(layer, x; wts)
)
    @assert size(layer) == size(x)[1:ndims(layer)] == size(mean)
    ξ = flatten(layer, x .- mean)
    if isnothing(wts)
        C = ξ * ξ' / size(ξ, 2)
    else
        @assert size(wts) == batch_size(layer, x)
        w = Diagonal(vec(wts))
        C = ξ * w * ξ' / sum(w)
    end
    return reshape(C, size(layer)..., size(layer)...)
end

"""
    total_mean_from_inputs(layer, inputs; wts = nothing)

Total mean of unit activations from inputs.
"""
function total_mean_from_inputs(
    layer::AbstractLayer, inputs::Union{Real,AbstractArray}; wts = nothing
)
    h_ave = mean_from_inputs(layer, inputs)
    return batchmean(layer, h_ave; wts)
end

"""
    total_var_from_inputs(layer, inputs; wts = nothing)

Total variance of unit activations from inputs.
"""
function total_var_from_inputs(
    layer::AbstractLayer, inputs::Union{Real,AbstractArray}; wts = nothing
)
    h_ave, h_var = meanvar_from_inputs(layer, inputs)
    ν_int = batchmean(layer, h_var; wts) # intrinsic noise
    ν_ext = batchvar(layer, h_ave; wts) # extrinsic noise
    return ν_int + ν_ext # law of total variance
end

"""
    total_meanvar_from_inputs(layer, inputs; wts = nothing)

Total mean and total variance of unit activations from inputs.
"""
function total_meanvar_from_inputs(
    layer::AbstractLayer, inputs::Union{Real,AbstractArray}; wts = nothing
)
    h_ave, h_var = meanvar_from_inputs(layer, inputs)
    μ = batchmean(layer, h_ave; wts)
    ν_int = batchmean(layer, h_var; wts) # intrinsic noise
    ν_ext = batchvar(layer, h_ave; wts, mean = μ) # extrinsic noise
    ν = ν_int + ν_ext # law of total variance
    return (μ = μ, ν = ν)
end

"""
    ∂energy(layer, data; wts = nothing)
    ∂energy(layer, stats)

Derivative of average energy of `data` with respect to `layer` parameters.
In the second form, `stats` are the pre-computed sufficient statistics
of the layer. See [`suffstats`](@ref).
"""
function ∂energy(layer::AbstractLayer, data::AbstractArray; wts=nothing)
    stats = suffstats(layer, data; wts)
    return ∂energy(layer, stats)
end

"""
    ∂free_energy(layer, inputs = 0; wts = 1)

Unit activation moments, conjugate to layer parameters.
These are obtained by differentiating `free_energies` with respect to the layer parameters.
Averages over configurations (weigthed by `wts`).
"""
function ∂free_energy(layer::AbstractLayer, inputs::Union{Real,AbstractArray} = 0; wts = nothing)
    ∂F = ∂free_energies(layer, inputs)
    return map(∂F) do ∂f
        batchmean(layer, ∂f; wts)
    end
end


"""
Calculate both the spatial and the operational entanglement entropies of a
region A, using the SVD. The latter is the "entanglement of particles"
introduced by Wiseman and Vaccaro in 2003.
"""
function operational_entanglement(L::Int, N::Int, l::Int, precision::Int, alpha::Float64, nu::Vector{Float64}, eigenvalues::Bool, probabilities::Bool)

    setprecision(precision)
#set_bigfloat_precision(precision)
# =======================================================
#Calculating the probability distribution (PN) of the number of particles in the targeted partition.
    scaling_factor= BigFloat
    scaling_factor=10.0^300
    normsDim=l+1
    #P1 = zeros(BigFloat, 2)
    PN_old = zeros(BigFloat, normsDim)
    PN_new = zeros(BigFloat, normsDim)
    if nu[1]>=0 && nu[1]<=1
       nuVal=nu[1]
    elseif nu[1]>1
       nuVal=1.0
    else
       nuVal=0.0
    end
    PN_old[1]=scaling_factor*(1-nuVal)
    PN_old[2]=scaling_factor*nuVal
    NN=2
    for i= 2:normsDim-1
       if nu[i]>=0 && nu[i]<=1
          nuVal=nu[i]
       elseif nu[i]>1
          nuVal=1.0
       else
          nuVal=0.0
       end
       PN_new[1]=(1-nuVal)*PN_old[1]
       PN_new[NN+1]=nuVal*PN_old[NN]
       for k= 1:NN-1
          PN_new[k+1]=(1-nuVal)*PN_old[k+1]+nuVal*PN_old[k]
       end
       NN=NN+1
       for k=1: NN
          PN_old[k]=PN_new[k]
       end
    end
    if l==1
       for k=1: normsDim
          PN_new[k]=PN_old[k]
       end
    end
    PN_new/= scaling_factor
    err_PN = abs(sum(PN_new) - 1.0)
    if err_PN > 1e-12
        @warn("probability distribution error: $(err_PN)")

    end
# =======================================================
# =======================================================
#Calculating the second Renyi entanglement entropy.
    Salpha = zeros(BigFloat, 3)
    Salpha_old = zeros(BigFloat, normsDim)
    Salpha_new = zeros(BigFloat, normsDim)
    #alpha=2.0
    if nu[1]>=0 && nu[1]<=1
       nuVal=nu[1]
    elseif nu[1]>1
       nuVal=1.0
    else
       nuVal=0.0
    end
    Salpha_old[1]=scaling_factor*(1-nuVal)^alpha
    Salpha_old[2]=scaling_factor*nuVal^alpha
    NN=2
    for i= 2:normsDim-1
       if nu[i]>=0 && nu[i]<=1
          nuVal=nu[i]
       elseif nu[i]>1
          nuVal=1.0
       else
          nuVal=0.0
       end
       Salpha_new[1]=(1-nuVal)^alpha*Salpha_old[1]
       Salpha_new[NN+1]=nuVal^alpha*Salpha_old[NN]
       for k= 1:NN-1
          Salpha_new[k+1]=(1-nuVal)^alpha*Salpha_old[k+1]+nuVal^alpha*Salpha_old[k]
       end
       NN=NN+1
       for k=1: NN
          Salpha_old[k]=Salpha_new[k]
       end
    end

    if l==1
       for k=1: normsDim
          Salpha_new[k]=Salpha_old[k]
       end
    end
    Salpha_new/= scaling_factor
# =======================================================
    LogSumPNalpha= BigFloat
    SalphalogSalpha= BigFloat
    PnlogPN= BigFloat
    sigma2_n= BigFloat
    sigma2_n_PN= BigFloat
    n= BigFloat
    n2= BigFloat

    H4= BigFloat
    H5= BigFloat
    H6= BigFloat

    S1_sp= BigFloat
    Salpha_sp= BigFloat
    S1_op= BigFloat
    Salpha_op= BigFloat
# =======================================================

    S1_sp=0.0
    Salpha_sp=0.0
    for i= 1:normsDim-1
       if nu[i]>0 && nu[i]<1
             S1_sp-=(nu[i]*log(nu[i])+(1-nu[i])*log((1-nu[i])))
             Salpha_sp-=log(nu[i]^alpha+(1-nu[i])^alpha)
       end
    end
    err_Salpha = abs(sum(Salpha_new) - exp(-1.0*Salpha_sp))/exp(-1.0*Salpha_sp)
    if err_Salpha > 1e-12
        @warn("Salpha_op error: $(err_Salpha)")

    end
    Salpha_sp/=(alpha-1)
# =======================================================

    sigma2_n=0.0
    for i= 1:normsDim-1
       if nu[i]>0 && nu[i]<1
          sigma2_n+=(nu[i]*(1-nu[i]))
       end
    end

    sigma2_n_PN=0.0
    n=0
    n2=0
    for i= 1: normsDim
       if PN_new[i]>0
          n+=PN_new[i]*(i-1)
          n2+=PN_new[i]*(i-1)^2
       end
    end
    sigma2_n_PN=n2-n^2

    err_sigma2 = abs(sigma2_n_PN - sigma2_n)/sigma2_n
    if err_sigma2 > 1e-12
        @warn("sigma2_n error: $(err_sigma2)")

    end
# =======================================================

    PnlogPN=0.0
    for i= 1: normsDim
       if PN_new[i]>0
          PnlogPN+=PN_new[i]*log(PN_new[i])
       end
    end
# =======================================================

    S1_op=0.0
    S1_op=S1_sp+PnlogPN

    PNlogSalpha=0.0
    for i= 1: normsDim
       if Salpha_new[i]>0
          PNlogSalpha-=PN_new[i]*log(Salpha_new[i])
       end
    end

    Salpha_op= PNlogSalpha+alpha*PnlogPN
    Salpha_op/=(alpha-1)

    LogSumPNalpha=sum(PN_new.^alpha)
    LogSumPNalpha=-log(LogSumPNalpha)/(alpha-1)

# =======================================================

    H4=0.0
    H5=0.0
    H6=0.0
    H = zeros(BigFloat, normsDim)
    for i= 1: normsDim
       if PN_new[i]>0
          H[i]+=Salpha_new[i]/PN_new[i]^alpha
          H4+=PN_new[i]*H[i]
          H5+=PN_new[i]*H[i]^(1/(alpha-1))
          H6+=Salpha_new[i]^(1/alpha)
       end

    end

    H4=log(H4)/(1-alpha)
    H5=-log(H5)
    H6=-log(H6)*(alpha/(alpha-1))

# =======================New=============================
# =======================================================

     if l==100
        if probabilities
           Output="L$(L)N$(N)l$(l)alpha$(alpha)Pn.dat"
           open(Output, "w") do fp
              write(fp, "# L=$(L), N=$(N), l=$(l), alpha=$(alpha) \n")
              write(fp, "# ==============================================================================\n")
              write(fp, @sprintf "#%7s%24s%24s%24s\n" "n"  "P(n)" "P(n)^alpha"  "P(n)_alpha" )

              sumPnalpha=0.0
              sumPn_alpha=0.0
              sumSalpha_new =sum(Salpha_new)
              sumPN_newalpha =sum(PN_new.^alpha)
              for i=1: normsDim
                 write(fp, @sprintf "%8s%24.12E%24.12E%24.12E\n" "$(i-1)"  PN_new[i]  PN_new[i]^alpha/sumPN_newalpha Salpha_new[i]/sumSalpha_new)
                 sumPnalpha= sumPnalpha+PN_new[i]^alpha/sumPN_newalpha
                 sumPn_alpha= sumPn_alpha+Salpha_new[i]/sumSalpha_new
              end
               write(fp, "#$(sumPnalpha)  \n")
               write(fp, "#$(sumPn_alpha)  \n")
               flush(fp)
           end
        end

#___________________________________________eigvals_______________________________________________
        if eigenvalues
           Outputn="L$(L)N$(N)l$(l)alpha$(alpha)eigvals.dat"
           open(Outputn, "w") do fn
              write(fn, "# L=$(L), N=$(N), l=$(l) \n")
              write(fn, "# ===============================\n")
              write(fn, @sprintf "#%7s%24s\n" "n"  "nu" )
              for i=1: normsDim-1
                 if nu[i]>=0 && nu[i]<=1
                    nuVal=nu[i]
                 elseif nu[i]>1
                    nuVal=1.0
                 else
                    nuVal=0.0
                 end
                 write(fn, @sprintf "%8s%24.12E\n" "$(i)"  nuVal)
              end
              flush(fn)
           end
        end
#__________________________________________________________________________________________

     end

    S1_sp, S1_op, Salpha_sp, Salpha_op, sigma2_n,LogSumPNalpha,H4,H5,H6
end


## Run HMC using a Bayesian probit model with a Normal prior N(0, priorVar*I)
## on the Swiss banknote data

using Distributions, GeometricMCMC

include("vaso.jl")
include("probitNormalPrior.jl")

# Create Model instance
model =
  Model(nPars, data, logPrior, logLikelihood, gradLogPosterior, randPrior);

# Create setDriftStep function for adjusting the leap step of HMC
leapSteps = [0.4, 1e-3/2, 1e-3, 1e-2, 1e-1, 0.15, 0.2, 0.25, 0.3, 0.35]

setLeapStep(i::Int, acceptanceRatio::Float64, nMcmc::Int,
  nBurnin::Int, currentStep::Float64) =
  setHmcLeapStep(i, acceptanceRatio, nMcmc, nBurnin, currentStep, leapSteps)

# Create instance malaOpts of HMC options
hmcOpts = HmcOpts(55000, 5000, 10, setLeapStep, eye(nPars));

# Run HMC
mcmc = Array(Float64, nPars, nPars)
z = Array(Float64, nPars, nPars)
linearZvMcmc = Array(Float64, nPars, nPars)
linearCoef = Array(Float64, nPars, nPars)
quadraticZvMcmc = Array(Float64, nPars, nPars)
quadraticCoef = Array(Float64, convert(Int, nPars*(nPars+3)/2), nPars)

try
  mcmc, z = hmc(model, hmcOpts);

  # Compute ZV-HMC mean estimators based on linear polynomial
  linearZvMcmc, linearCoef = linearZv(mcmc, z);

  # Compute ZV-HMC mean estimators based on quadratic polynomial
  quadraticZvMcmc, quadraticCoef = quadraticZv(mcmc, z);
catch msg
  println(msg)
end


using Plots

t = range(0, 4π, length = 100)
r = range(1, 0, length = 100)

x = cos.(t) .* r
y = sin.(t) .* r

@gif for i in eachindex(x)
    scatter((x[i], y[i]), lims = (-1, 1), label = "")
end

@gif for i in eachindex(x)
    plot(x[1:i], y[1:i], lims = (-1, 1), label = "")
    scatter!((x[i], y[i]), color = 1, label = "")
end

@gif for i in eachindex(x)
    plot(x[1:i], y[1:i], alpha = max.((1:i) .+ 10 .- i, 0) / 10, lims = (-1, 1), label = "")
    scatter!((x[i], y[i]), color = 1, label = "")
end


# ## Non-zero inflated, continuous. e.g. LogNormal
# function expert_ll_pos(d::e, tl::Real, yl::Real, yu::Real, tu::Real) where {e<:NonZIContinuousExpert}

#     expert_ll = (yl == yu) ? logpdf.(d, yl) : logcdf.(d, yu) + log1mexp.(logcdf.(d, yl) - logcdf.(d, yu))
#     expert_ll = (tu == 0.) ? -Inf : expert_ll

#     return expert_ll
# end

# ## Zero inflated, continuous. e.g. ZILogNormal
# function expert_ll_pos(d::e, tl::Real, yl::Real, yu::Real, tu::Real) where {e<:ZIContinuousExpert}

#     expert_ll = (yl == yu) ? logpdf.(d, yl) : logcdf.(d, yu) + log1mexp.(logcdf.(d, yl) - logcdf.(d, yu))
#     expert_ll = (tu == 0.) ? -Inf : expert_ll

#     return expert_ll
# end

# ## Non-zero inflated, discrete. e.g. Poisson
# function expert_ll_pos(d::e, tl::Real, yl::Real, yu::Real, tu::Real) where {e<:NonZIDiscreteExpert}

#     expert_ll = (yl == yu) ? logpdf.(d, yl) : logcdf.(d, yu) + log1mexp.(logcdf.(d, ceil.(yl) .- 1) - logcdf.(d, yu))

#     return expert_ll
# end

# ## Zero inflated, discrete. e.g. ZIPoisson
# function expert_ll_pos(d::e, tl::Real, yl::Real, yu::Real, tu::Real) where {e<:ZIDiscreteExpert}

#     expert_ll = (yl == yu) ? logpdf.(d, yl) : logcdf.(d, yu) + log1mexp.(logcdf.(d, ceil.(yl) .- 1) - logcdf.(d, yu))

#     return expert_ll
# end


ipa = Dict()
ipb = Dict()

# Input membership functions
ipa["small"] = TriangularMF(1, 2, 3)
ipa["large"] = TriangularMF(5, 6, 7)
ipb["small"] = TriangularMF(1, 2, 3)
ips = [ipa, ipb]

# Rules
rule1 = Rule(["small", "small"], [1.0, 1.0, 1.0])
rule2 = Rule(["large", "small"], [0, 0, 17.0])
rules = [rule1, rule2]

fis = FISSugeno(ips, rules)

# Input values
ival1 = [2.3, 1.2] # Runs completely on rule 1
ival2 = [6.4, 2.1] # Runs completely on rule 2

@assert eval_fis(fis, ival1) == 4.5
@assert eval_fis(fis, ival2) == 17.0