module Backward

export value_and_gradient, gradient

import Base: ==, !=, <, <=, >, >=, isless, isequal
import Base: +, *, -, /, exp, sqrt, log, log1p
import Base: sin, cos, tan, sec, csc, cot
import Base: atan
import Base: sum
import Base: convert, promote_rule, zero, one

import AutoDiff.StatsFunctions: logsumexp

mutable struct BADNode{T<:Number} <: Number
    tape::Array{BADNode{T},1}
    value::T
    adj::T
    backprop!
end

function zero(::Type{BADNode{T}}) where {T <: Number}
    BADNode(BADNode{T}[], zero(T), zero(T), (n)->())
end

function zero(x::BADNode{T}) where {T <: Number}
    BADNode(BADNode{T}[], zero(T), zero(T), (n)->())
end

function one(::Type{BADNode{T}}) where {T <: Number}
    BADNode(BADNode{T}[], one(T), zero(T), (n)->())
end

function one(x::BADNode{T}) where {T <: Number}
    BADNode(BADNode{T}[], one(T), zero(T), (n)->())
end

function convert(::Type{BADNode{T}}, x::T) where {T <: Number}
    BADNode(BADNode{T}[], x, zero(T), (n)->())
end

function promote_rule(::Type{BADNode{T}}, ::Type{T}) where {T <: Number}
    BADNode{T}
end

for fn in [Symbol("=="), Symbol("!="), Symbol("<"), Symbol("<="), Symbol(">"), Symbol(">="), :isless, :isequal]
    @eval begin
        $fn(a::BADNode{T}, b::BADNode{T}) where {T <: Number} = $fn(a.value, b.value)
    end
end

function +(x::BADNode{T}, y::BADNode{T}) where {T <: Number}
    t = (length(x.tape) == 0 ? y.tape : x.tape)
    function bp!(n::BADNode{T})
        x.adj += n.adj
        y.adj += n.adj
    end
    n = BADNode(t, x.value + y.value, zero(T), bp!)
    push!(t, n)
    n
end

function -(x::BADNode{T}, y::BADNode{T}) where {T <: Number}
    t = (length(x.tape) == 0 ? y.tape : x.tape)
    function bp!(n::BADNode{T})
        x.adj += n.adj
        y.adj -= n.adj
    end
    n = BADNode(t, x.value - y.value, zero(T), bp!)
    push!(t, n)
    n
end

function -(x::BADNode{T}) where {T <: Number}
    t = x.tape
    function bp!(n::BADNode{T})
        x.adj -= n.adj
    end
    n = BADNode(t, -x.value, zero(T), bp!)
    push!(t, n)
    n
end

function *(x::BADNode{T}, y::BADNode{T}) where {T <: Number}
    t = (length(x.tape) == 0 ? y.tape : x.tape)
    function bp!(n::BADNode{T})
        x.adj += y.value*n.adj
        y.adj += x.value*n.adj
    end
    n = BADNode(t, x.value*y.value, zero(T), bp!)
    push!(t, n)
    n
end

function /(x::BADNode{T}, y::BADNode{T}) where {T <: Number}
    t = (length(x.tape) == 0 ? y.tape : x.tape)
    r = x.value / y.value
    function bp!(n::BADNode{T})
        x.adj += n.adj / y.value
        y.adj -= r / y.value * n.adj
    end
    n = BADNode(t, r, zero(T), bp!)
    push!(t, n)
    n
end

function exp(x::BADNode{T}) where {T <: Number}
    t = x.tape
    r = exp(x.value)
    function bp!(n::BADNode{T})
        x.adj += r*n.adj
    end
    n = BADNode(t, r, zero(T), bp!)
    push!(t, n)
    n
end

function sqrt(x::BADNode{T}) where {T <: Number}
    t = x.tape
    r = sqrt(x.value)
    function bp!(n::BADNode{T})
        x.adj += n.adj / (convert(T, 2)*r)
    end
    n = BADNode(t, r, zero(T), bp!)
    push!(t, n)
    n
end

function sin_deriv(x,y)
    cos(x)
end

function cos_deriv(x,y)
    -sin(x)
end

function tan_deriv(x, y)
    z = sec(x)
    z*z
end

function sec_deriv(x, y)
    y*tan(x)
end

function csc_deriv(x, y)
    -y*cot(x)
end

function cot_deriv(x, y)
    z = csc(x)
    -z*z
end

function atan_deriv(x, y)
    one(x) / (one(x) + x*x)
end

for (f, df) in zip([:sin, :cos, :tan, :sec, :csc, :cot, :atan], [:sin_deriv, :cos_deriv, :tan_deriv, :sec_deriv, :csc_deriv, :cot_deriv, :atan_deriv])
    fstr = uppercasefirst(string(f))
    nodename = Symbol("BADNode$(fstr)")
    @eval begin
        function $f(x::BADNode{T}) where {T <: Number}
            t = x.tape
            r = $f(x.value)
            function bp!(n::BADNode{T})
                x.adj += $df(x.value, r)*n.adj
            end
            n = BADNode(t, r, zero(T), bp!)
            push!(t, n)
            n
        end
    end
end

function atan(y::BADNode{T}, x::BADNode{T}) where {T <: Number}
    t = (length(x.tape) == 0 ? y.tape : x.tape)
    function bp!(n::BADNode{T})
        denom = x.value*x.value + y.value*y.value
        y.adj += n.adj * x.value / denom
        x.adj -= n.adj * y.value / denom
    end
    n = BADNode(t, atan(y.value, x.value), zero(T), bp!)
    push!(t, n)
    n
end

function sum(x::Array{BADNode{T}, N}) where {T <: Number, N}
    t = BADNode{T}[]
    for i in eachindex(x)
        if length(x[i].tape) > 0
            t = x[i].tape
            break
        end
    end

    s = zero(T)
    for i in eachindex(x)
        s += x[i].value
    end

    function bp!(n::BADNode{T})
        for i in eachindex(x)
            x[i].adj += n.adj
        end
    end

    n = BADNode(t, s, zero(T), bp!)
    push!(t, n)
    n
end

function log(x::BADNode{T}) where {T <: Number}
    t = x.tape
    function bp!(n::BADNode{T})
        x.adj += n.adj / x.value
    end
    n = BADNode(t, log(x.value), zero(T), bp!)
    push!(t, n)
    n
end

function log1p(x::BADNode{T}) where {T <: Number}
    function bp!(n::BADNode{T})
        x.adj += n.adj / (one(T) + x.value)
    end
    n = BADNode(x.tape, log1p(x.value), zero(T), bp!)
    push!(x.tape, n)
    n
end

function logsumexp(x::BADNode{T}, y::BADNode{T}) where {T <: Number}
    t = (length(x.tape) == 0 ? y.tape : x.tape)
    r = logsumexp(x.value, y.value)
    function bp!(n::BADNode{T})
        x.adj += n.adj * exp(x.value - r)
        y.adj += n.adj * exp(y.value - r)
    end
    n = BADNode(t, r, zero(T), bp!)
    push!(t, n)
    n
end

function logsumexp(x::Array{BADNode{T}, N}) where {T <: Number, N}
    t = BADNode{T}[]
    for i in eachindex(x)
        if length(x[i].tape) > 0
            t = x[i].tape
            break
        end
    end

    xx = Array{T, N}(undef, size(x))
    for i in eachindex(x)
        xx[i] = x[i].value
    end
    r = logsumexp(xx)

    function bp!(n::BADNode{T})
        for i in eachindex(x)
            x[i].adj += n.adj*exp(x[i].value - r)
        end
    end

    n = BADNode(t, r, zero(T), bp!)
    push!(t, n)
    n
end

function value_and_gradient(f)
    function vgf(x::T) where T<:Number
        t = BADNode{T}[]
        y = BADNode(t, x, zero(T), (n)->())
        push!(t, y)
        r = f(y)
        r.adj = one(T)
        while length(t) > 0
            n = pop!(t)
            n.backprop!(n)
        end
        (r.value, y.adj)
    end

    function vgf(xs::Array{T, N}) where {T <: Number, N}
        t = BADNode{T}[]
        ys = Array{BADNode{T}, N}(undef, size(xs))
        for i in eachindex(ys)
            ys[i] = BADNode(t, xs[i], zero(T), (n)->())
            push!(t, ys[i])
        end
        r = f(ys)
        r.adj = one(T)

        while length(t) > 0
            n = pop!(t)
            n.backprop!(n)
        end

        zs = Array{T, N}(undef, size(xs))
        for i in eachindex(xs)
            zs[i] = ys[i].adj
        end
        (r.value, zs)
    end

    function vgf(x::Vararg{T,N}) where {T<:Number, N}
        t = BADNode{T}[]

        y = Array{BADNode{T}, 1}(undef, length(x))
        for i in eachindex(x)
            y[i] = BADNode(t, x[i], zero(T), (n)->())
            push!(t, y[i])
        end

        r = f(y...)
        r.adj = one(T)

        while length(t) > 0
            n = pop!(t)
            n.backprop!(n)
        end

        g = T[n.adj for n in y]
        (r.value, g)
    end

    vgf
end

function gradient(f)
    vgf = value_and_gradient(f)
    function g(x...)
        vgf(x...)[2]
    end

    g
end

end


function find_edges(A)
    "
    This function finds the edges by finding the distance to the nearest background (0)
    pixel and keeping only the pixels that are adjacent to the nearest background pixel
    "

    # Set every pixel that is currently background (0) equal to 1, as these are the pixels we are interested in
    A = A .== 0

    # Find the indeces of these background pixels (now set equal to 1)
    A = feature_transform(A)

    # Find the distance away from the nearest background pixel (now set to 1)
    A = distance_transform(A)

    # Return the indeces where the distance is equal to 1 as this means this pixel is adjacent to a background pixel
    return Tuple.(findall(x->x==1, A))
end


# Some Boiler-plate code for preconditioning
#
# Meaning of P:
#    P ≈ ∇²E, so the preconditioned gradient is P^{-1} ∇E
#    P can be an arbitrary type but at a minimum it MUST provide:
#        A_ldiv_B!(x, P, b) ->  x = P \ b
#        dot(x, P, b)  ->  x' P b
#
#    If `dot` is not provided, then Optim.jl will try to define it via
#        dot(x, P, b) = vecdot(x, A_mul_B!(similar(x), P, y))
#
#    finally the preconditioner can be updated after each x-update using
#        precondprep!
#    but this is passed as an argument at the moment!
#

#####################################################
#  [0] Defaults and aliases for easier reading of the code
#      these can also be over-written if necessary.

# an inner product w.r.t. a metric P (=preconditioner)
Base.dot(x, P, y) = vecdot(x, A_mul_B!(similar(x), P, y))

# default preconditioner update
precondprep!(P, x) = nothing

#####################################################
#  [1] Empty preconditioner = Identity
#

# out =  P^{-1} * A
Base.A_ldiv_B!(out, ::Void, A) = copy!(out, A)
# A' * P B
Base.dot(A, ::Void, B) = vecdot(A, B)

#####################################################
#  [2] Diagonal preconditioner
#      P = Diag(d)
#      unfortunately, Base does not implement
#      A_ldiv_B!(a, P, b) or A_mul_B! for this type, so we do it by hand
#      TODO: maybe implement this in Base
Base.A_ldiv_B!(out::Array, P::Diagonal, A::Array) = copy!(out, A ./ P.diag)
Base.dot(A::Array, P::Diagonal, B::Array) = vecdot(A, P.diag .* B)

#####################################################
#  [3] Inverse Diagonal preconditioner
#      here, P is stored by the entries of its inverse
#      TODO: maybe implement this in Base?
type InverseDiagonal
   diag
end
Base.A_ldiv_B!(out::Array, P::InverseDiagonal, A::Array) = copy!(out, A .* P.diag)
Base.dot(A::Array, P::InverseDiagonal, B::Vector) = vecdot(A, B ./ P.diag)

#####################################################
#  [4] Matrix Preconditioner
#  the assumption here is that P is given by its inverse, which is typical
#     > A_ldiv_B! is about to be moved to Base, so we need a temporary hack
#     > A_mul_B! is already in Base, which defines `dot`
#  nothing to do!
Base.A_ldiv_B!(x, P::AbstractMatrix, b) = copy!(x, P \ b)


#=
disk-phantom.jl
generate random disk phantoms
2019-07-03, Jeff Fessler, University of Michigan
=#

export disk_phantom_params

using Random: seed!

"""
    params = disk_phantom_params( ; ...)

Generate `ndisk × 6` ellipse phantom parameters
for a head-sized disk plus many disks within it,
designed so that the disks have some minimum separation `minsep`
to avoid overlap and to simplify patch-based model fitting.

# Options
- `fov::Real = 240` image field of view in mm
- `rhead::Function = () -> 100` background radius for "head" [mm]
- `muhead::Function = () -> 1000` "μ" (intensity) value for background head disk
- `rdisk::Function = () -> 10 + 10 * rand()` random disk radii [10,20]
- `mudisk::Function = () -> 100 + 200 * rand()` "μ" values for disks [100,300]
- `ndisk::Function = () -> 10` # of random disks
- `minsep::Real = 8` minimum disk separation in mm
- `maxtry::Int = 500` give up on adding more disks if this is reached
- `warn::Bool = false` warn if maxtry reached?
- `seed::Int = 0` if nonzero then use this seed

The function options can be replaced with rand() for other Distributions.
"""
function disk_phantom_params( ;
    fov::RealU = 240,
#   rhead::RealU = 100, # background radius for "head" [mm]
    rhead::Function = () -> 100, # background radius for "head" [mm]
    muhead::Function = () -> 1000, # "μ" value for background head
#   rmin::RealU = 10, # min radius for disks
#   rmax::RealU = 20, # max radius for disks
    rdisk::Function = () -> 10 + 10 * rand(), # disk radius [10,20]
#   mumin::RealU = 100, # range of "μ" values for disks
#   mumax::RealU = 300,
    mudisk::Function = () -> 100 + 200 * rand(), # "μ" values for disks [100,300]
    ndisk::Function = () -> rand(10:10), # of disks
    minsep::RealU = 8, # minimum separation in mm
    # radial position of disk centers, uses rhead and rmin:
    cdisk::Function = () -> sqrt(rand()) * (100 - 10 - minsep),
    maxtry::Int = 500, # give up on adding more disks if this is reached
    warn::Bool = false, # warn if maxtry reached?
    seed::Int = 0,
)

    ndisk = ndisk()
    rhead = rhead()
    μhead = muhead()

    params = zeros(Float32, ndisk+1, 6)
    params[end,:] = [0, 0, rhead, rhead, 0, μhead]

#   randu = (a, b ; f = x->x) -> a + f(rand()) * (b - a)
    seper = (a,b,r1,x,y,r2) -> sqrt(abs2(x - a) + abs2(y - b)) - r1 - r2

    (seed != 0) && seed!(seed)

    for id=1:ndisk
        trial = 0
        for ii=1:maxtry
#           rc = randu(0, rhead - rmin - minsep, f = x->x^0.5)
            rc = cdisk()
            phi = rand() * 2π
            rad = rdisk()
            val = mudisk()
            trial = [rc*cos(phi), rc*sin(phi), rad, rad, 0, val]

            id == 1 && break # first one is always fine

            # see if trial is too close to another one
            c = 1:(id-1) # check these
            sep = seper.(params[c,1], params[c,2], params[c,3],
                trial[1], trial[2], trial[3])

            (minimum(sep) > minsep) && (rhead - (rc + rad) > minsep) && break
            ii == maxtry && warn && @warn("need more tries")
            ii == maxtry && return params[[1:id; end],:]
        end

#=
        if id > 1
            c = 1:(id-1) # check these
            sep = seper(params[c,1], params[c,2], params[c,3],
                trial[1], trial[2], trial[3])
            @show minimum(sep)
        end
        @show id, trial
=#

        params[id,:] = trial
    end

    return params
end


# This file contains some code that is chared across several tests

using StaticArrays, Test, ForwardDiff
using NeighbourLists: SMat, SVec
using Test, StaticArrays, ForwardDiff

# ------ generate random configurations -------

function rand_config(N)
   C = SMat( diagm(0 => 2.0 .+ 0.2 * rand(3)) * N )
   X = [ C' * rand(SVec)   for i = 1:ceil(Int, abs(det(C))) ÷ 4 + 2 ]
   pbc = SVec(rand(Bool, 3))
   return X, C, pbc
end

# --------- MANY BODY CODE THAT IS SHARED ACROSS TESTS ------------

sqrt1p(x) = expm1( 0.5 * log1p( x ) )   # sqrt(1 + x) - 1
sqrt1p_d(x) = 0.5 / sqrt(1+x)

function fnbody(r, r0, rcut)
   E = sum( exp.(1.0 .- r./r0) )
   F = prod( (r./rcut .- 1.0) .* (r .< rcut) )
   return sqrt1p(E) * F^2
end

function fnbody_d(r, r0, rcut)
   E = sum( exp.(1.0 .- r./r0) )
   F = prod( (r./rcut .- 1.0) .* (r .< rcut) )
   ∇f = (- sqrt1p_d(E) * F^2 / r0) .* (exp.(1.0 .- r./r0))
   ∇f += (sqrt1p(E) * 2.0 / rcut * F^2) .* (r./rcut .- 1.0 .- 1e-14).^(-1)
   return ∇f
end

# Generate a MODEL N-Body function
function gen_fnbody(rcut, r0=1.0)
   return r->fnbody(r, r0, rcut), r -> fnbody_d(r, r0, rcut)
end

println("Checking that `fnbody` is correct...")
fnbody_ad(rs, r0, rcut) = ForwardDiff.gradient( t -> fnbody(t, r0, rcut), rs )
for n = 1:5
   r = rand(SVector{3, Float64})
   @test fnbody_d(r, 1.0, 2.0) ≈ fnbody_ad(r, 1.0, 2.0)
end

# global assembly of n-body energies and forces

n_body(X, f, M, rcut, C, pbc = (false, false, false),
            nlist = PairList(X, rcut, C, pbc, sorted = true)) =
   n_body(f, M, nlist)

n_body(f, M, nlist::PairList) =
   NeighbourLists.maptosites!(f, zeros(nsites(nlist)),
                                 NeighbourLists.nbodies(M, nlist)) |> sum

grad_n_body(X, df, M, rcut, C, pbc = (false, false, false),
            nlist = PairList(X, rcut, C, pbc, sorted = true)) =
   grad_n_body(df, M, nlist)

grad_n_body(df, M, nlist::PairList) =
   NeighbourLists.maptosites_d!(df, zeros(SVec{Float64}, nsites(nlist)),
                                  NeighbourLists.nbodies(M, nlist))


@testset "agreement/confusion matrix tests" begin
    hard_label_pairs = zip([1, 1, 3, 1, 3, 1, 2, 1], [2, 2, 1, 1, 3, 2, 3, 1])
    c = confusion_matrix(3, hard_label_pairs)
    @test c == [
        2 3 0
        0 0 1
        1 0 1
    ]
    kappa, percent_agreement = cohens_kappa(3, hard_label_pairs)
    chance = Lighthouse._probability_of_chance_agreement(3, hard_label_pairs)
    @test chance == (5 * 3 + 1 * 3 + 2 * 2) / 8^2
    @test accuracy(c) == percent_agreement == 3 / 8
    @test kappa == (3 / 8 - chance) / (1 - chance)
    stats = binary_statistics(c, 3)
    @test stats.predicted_positives == 2
    @test stats.predicted_negatives == 6
    @test stats.actual_positives == 2
    @test stats.actual_negatives == 6
    @test stats.true_positives == 1
    @test stats.true_negatives == 2
    @test stats.false_positives == 1
    @test stats.false_negatives == 1
    @test stats.true_positive_rate == 0.5
    @test stats.true_negative_rate == 1 / 3
    @test stats.false_positive_rate == 1 / 6
    @test stats.false_negative_rate == 0.5
    @test stats.precision == 0.5

    labels = rand(StableRNG(42), 1:3, 100)
    hard_label_pairs = zip(labels, labels)
    c = confusion_matrix(3, hard_label_pairs)
    @test tr(c) == length(labels) == sum(c)
    kappa, percent_agreement = cohens_kappa(3, hard_label_pairs)
    @test accuracy(c) == percent_agreement == 1
    @test kappa == 1
    for i in 1:3
        stats = binary_statistics(c, i)
        @test stats.predicted_positives == stats.true_positives == stats.actual_positives
        @test stats.predicted_negatives == stats.true_negatives == stats.actual_negatives
        @test stats.false_positives == stats.false_negatives == 0
        @test stats.true_positive_rate == stats.true_negative_rate == 1
        @test stats.false_positive_rate == stats.false_negative_rate == 0
        @test stats.precision == 1
    end

    n, k = 1_000_000, 2
    rng = StableRNG(42)
    hard_label_pairs = zip(rand(rng, 1:k, n), rand(rng, 1:k, n))
    c = confusion_matrix(k, hard_label_pairs)
    kappa, percent_agreement = cohens_kappa(3, hard_label_pairs)
    @test percent_agreement == accuracy(c)
    @test isapprox(percent_agreement, 0.5; atol=0.02)
    @test isapprox(kappa, 0.0; atol=0.02)
    stats = binary_statistics(c, 1)
    @test isapprox(stats.predicted_positives, 500_000; atol=2000)
    @test isapprox(stats.predicted_negatives, 500_000; atol=2000)
    @test isapprox(stats.actual_positives, 500_000; atol=2000)
    @test isapprox(stats.actual_negatives, 500_000; atol=2000)
    @test isapprox(stats.true_positives, 250_000; atol=2000)
    @test isapprox(stats.true_negatives, 250_000; atol=2000)
    @test isapprox(stats.false_positives, 250_000; atol=2000)
    @test isapprox(stats.false_negatives, 250_000; atol=2000)
    @test isapprox(stats.true_positive_rate, 0.5; atol=0.02)
    @test isapprox(stats.true_negative_rate, 0.5; atol=0.02)
    @test isapprox(stats.false_positive_rate, 0.5; atol=0.02)
    @test isapprox(stats.false_negative_rate, 0.5; atol=0.02)
    @test isapprox(stats.precision, 0.5; atol=0.02)

    @test confusion_matrix(10, ()) == zeros(10, 10)
    @test all(ismissing, cohens_kappa(10, ()))
    @test ismissing(accuracy(zeros(10, 10)))
    stats = binary_statistics(zeros(10, 10), 1)
    @test stats.predicted_positives == 0
    @test stats.predicted_negatives == 0
    @test stats.actual_positives == 0
    @test stats.actual_negatives == 0
    @test stats.true_positives == 0
    @test stats.true_negatives == 0
    @test stats.false_positives == 0
    @test stats.false_negatives == 0
    @test stats.true_positive_rate == 1
    @test stats.true_negative_rate == 1
    @test stats.false_positive_rate == 0
    @test stats.false_negative_rate == 0
    @test ismissing(stats.precision)

    for p in 0:0.1:1
        @test Lighthouse._cohens_kappa(p, p) == 0
        if p > 0
            @test Lighthouse._cohens_kappa(p / 2, p) < 0
            @test Lighthouse._cohens_kappa(p, p / 2) > 0
        end
    end
end

@testset "`calibration_curve`" begin
    rng = StableRNG(42)
    probs = rand(rng, 1_000_000)
    bitmask = rand(rng, Bool, 1_000_000)
    bin_count = 12
    bins, fractions, totals, mean_squared_error = calibration_curve(probs, bitmask;
                                                                    bin_count=bin_count)
    @test bin_count == length(bins)
    @test first(first(bins)) == 0.0 && last(last(bins)) == 1.0
    @test all(!ismissing, fractions)
    @test all(!iszero, totals)
    @test all(isapprox.(fractions, 0.5; atol=0.02))
    @test all(isapprox.(totals, length(probs) / bin_count; atol=1000))
    @test sum(totals) == length(probs)
    @test isapprox(ceil(mean(fractions) * length(bitmask)), count(bitmask); atol=1)
    @test isapprox(mean_squared_error, inv(bin_count); atol=0.002)

    rng = StableRNG(42)
    probs = range(0.0, 1.0; length=1_000_000)
    bitmask = [rand(rng) <= p for p in probs]
    bin_count = 10
    bins, fractions, totals, mean_squared_error = calibration_curve(probs, bitmask;
                                                                    bin_count=bin_count)
    ideal = range(mean(first(bins)), mean(last(bins)); length=bin_count)
    @test bin_count == length(bins)
    @test first(first(bins)) == 0.0 && last(last(bins)) == 1.0
    @test all(!ismissing, fractions)
    @test all(!iszero, totals)
    @test all(isapprox.(fractions, ideal; atol=0.01))
    @test all(totals .== 1_000_000 / bin_count)
    @test isapprox(ceil(mean(fractions) * length(bitmask)), count(bitmask); atol=1)
    @test isapprox(mean_squared_error, 0.0; atol=0.00001)

    bitmask = reverse(bitmask)
    bins, fractions, totals, mean_squared_error = calibration_curve(probs, bitmask;
                                                                    bin_count=bin_count)
    @test bin_count == length(bins)
    @test first(first(bins)) == 0.0 && last(last(bins)) == 1.0
    @test all(!ismissing, fractions)
    @test all(!iszero, totals)
    @test all(isapprox.(fractions, reverse(ideal); atol=0.01))
    @test all(totals .== 1_000_000 / bin_count)
    @test isapprox(ceil(mean(fractions) * length(bitmask)), count(bitmask); atol=1)
    @test isapprox(mean_squared_error, 1 / 3; atol=0.01)
end


#!/usr/bin/env python
# vim: set fileencoding=utf-8
""" This file is a Julia translation of the MATLAB file prony.m

 Julia version by Adrien Pajon 28 Sept 2020
 Copyright notice from prony.m:
 copyright 1996, by M.H. Hayes.  For use with the book
 "Statistical Digital Signal Processing and Modeling"
 (John Wiley & Sons, 1996).
"""

include("convm.jl")

function prony(x, p, q)
    # Model a signal using Prony's method
    #
    # Usage: [b,a,err] = prony(x,p,q)
    #
    # The input sequence x is modeled as the unit sample response of
    # a filter having a system function of the form
    #     H(z) = B(z)/A(z)
    # The polynomials B(z) and A(z) are formed from the vectors
    #     b=[b(0), b(1), ... b(q)]
    #     a=[1   , a(1), ... a(p)]
    # The input q defines the number of zeros in the model
    # and p defines the number of poles. The modeling error is
    # returned in err.
    #
    # This comes from Hayes, p. 149, 153, etc

    # x = x[:]
    # N = length(x)
    # if p + q >= length(x)
    #     print("ERROR: model order too large")
    #     print(
    #         "p q length(x) " +
    #         convert(String, p) +
    #         " " +
    #         convert(String, q) +
    #         " " +
    #         convert(String, length(x)),
    #     )
    #     return false
    # end
    #
    # # This formulation uses eq. 4.50, p. 153
    # # Set up the convolution matrices
    # X = convm(x, p + 1)
    # Xq = X[q+1:N+p-1, 1:p]
    # xq1 = -X[q+2:N+p, 1]
    # # Solve for denominator coefficients
    # a = []
    # if p > 0
    #     a = (Xq \ xq1)
    #     a = [1; a] # a(1) is 1
    # end
    #
    # # Solve for the model error
    # err = Array(conj(x[q+2:N])') * X[q+2:N, 1:p+1]
    # err = err * a
    #
    # # Solve for numerator coefficients
    # if q > 0
    #     # (This is the same as for Pad?)
    #     b = X[1:q+1, 1:p+1] * a
    # else
    #     # all-pole model
    #     # b(0) is x(0), but a better solution is to match energy
    #     b = sqrt(err)
    # end

    x   = x[:];

    N   = length(x);

    if p+q>=length(x)
        error("Model order too large")
        print(
            "p q length(x) " +
            convert(String, p) +
            " " +
            convert(String, q) +
            " " +
            convert(String, length(x))
        )
    end

    X   = convm(x,p+1);

    Xq  = X[q+1:N+p-1,1:p];

    a   = [1;-Xq\X[q+2:N+p,1]];

    b   = X[1:q+1,1:p+1]*a;

    err = x[q+2:N]'*X[q+2:N,1:p+1]*a;

    return (b, a, err)
end

####################################################################
# Example of code to use prony()

# function main()
#     # Test driver
#     # From pp. 149-150
#     x = ones(21)
#     p = q = 1
#     b, a, err = prony(x, p, q)
#     print("a: ")
#     println(a)
#     print("b: ")
#     println(b)
#     print("err: ")
#     println(err)
#
#     # From pp. 152-153
#     # Note that these results don't match the book, but they do match the
#     # MATLAB version. So I'm either setting things up wrong or this is an
#     # errata in the book.
#     p = q = 5
#     nd = 5
#     n = 0:11
#     i = sinc.((n .- nd) ./ 2) ./ 2
#     b, a, err = prony(i, p, q)
#     print("a: ")
#     println(a)
#     print("b: ")
#     println(b)
#     print("err: ")
#     println(err)
# end
# main()


using LinearAlgebra
using TensorOperations
using QuantumOptics
import QuantumOptics:identityoperator
include("../lib/tensor_utils.jl")

function identityoperator(Nfocks::Vector{Int})
    return identityoperator(tensor(FockBasis.(Nfocks)...))
end

function a_vectors(Nfocks::Vector{Int})
    N = length(Nfocks)
    bs = FockBasis.(Nfocks)
    as = Array{Any}(undef, N)
    for (i,b) in enumerate(bs)
        a = identityoperator.(bs)
        a[i] = destroy(b)
        as[i] = reduce(tensor,a)
    end
    return as
end

function ground_state(Nfocks)
    return basisstate(tensor(FockBasis.(Nfocks)...), 1);
end

import Base:*
function *(S::Matrix{<:Number}, z::Vector{SparseOperator})
    return [sum(S[i,:].*z) for i in 1:length(z)]
end

function a2z_mat(N::Int)
    Imat = Matrix{Float64}(I, N, N)
    return [Imat im*Imat ; Imat -im*Imat]/√2
end

function converta2z(Xa::Array{<:Number})
    L = a2z_mat(Int(size(Xa)[1]/2))
    return change_coordinate(Xa,L)
end

#########################################################################
# get μ of arbitrary rank
# ϕ: Ket vector
# z:  a vector of operators (basis)
# rank: the rank of μ tensor we want to get
function get_μ(ϕ::Ket, z::Array{SparseOperator,1}, rank::Int64)
    len = length(z)
    indeces_set = Base.product(ntuple(i->1:len,rank)...)
    size = Base.product(ntuple(i->len,rank)...)
    μ = Array{Float64}(undef,size...)
    for inds in indeces_set
        μ[inds...] = real(expect(reduce(*,(i->z[i]).(collect(inds))),ϕ))
    end
    return symmetrize_tensor(μ)
end

#########################################################################
# get an operator/ operators from a tensor/tensors with a basis z
# X: Tensor
# z: a vector of operators (basis)
function get_operator(X::Array{<:Number},z::Array{SparseOperator,1})
    return sum(X[inds...] * reduce(*,(i->z[i]).(collect(inds))) for inds in Base.product(ntuple(i->1:length(z),ndims(X))...))
end

function get_operator(Xs::Array{Array{Type_Element},1} where Type_Element<:Number,z::Array{SparseOperator,1})
    return sum( get_operator(X,z) for X in Xs)
end

function get_operator(set_of_Xs::Array{Array{Type_Element},2} where Type_Element<:Number,z::Array{SparseOperator,1})
    return [ get_operator(set_of_Xs[i,:],z) for i in 1:size(set_of_Xs)[1]]
end


using Statistics
using LinearAlgebra
import Random

@testset "KPLS Pediction Tests (in sample)" begin

    @testset "Test KPLS Single Non Linear Target" begin

        Random.seed!(1)

        z(x)     = 4.26 * (exp.(-x) - 4 * exp.(-2.0*x) + 3 * exp.(-3.0*x))
        x_values = Array(range(0.0,step=3.5,length=100))
        z_pure   = z(x_values)
        noise    = Random.randn(100)
        z_noisy  = z_pure + noise
        X        = collect(x_values)[:,:]
        Y        = z_noisy[:,:] #z_pure
        model    = PLSRegressor.fit(X,Y,nfactors=1,kernel="rbf",width=0.01)
    	Y_pred   = PLSRegressor.predict(model,X)
        @test mean(abs.(Y .- Y_pred)) < 1e-2

    end

    @testset "Test KPLS Single Target (Linear Target)" begin

        X        = [1 2; 2 4; 4.0 6][:,:]
        Y        = [-2; -4; -6.0][:,:]
        model    = PLSRegressor.fit(X,Y,nfactors=1,kernel="rbf",width=0.01)
    	Y_pred     = PLSRegressor.predict(model,X)
        @test mean(abs.(Y .- Y_pred)) < 1e-6

        X        = [1 2; 2 4; 4.0 6][:,:]
        Y        = [2; 4; 6.0][:,:]
        model    = PLSRegressor.fit(X,Y,nfactors=1,kernel="rbf",width=0.01)
    	Y_pred     = PLSRegressor.predict(model,X)
        @test mean(abs.(Y .- Y_pred)) < 1e-6

    end

    @testset "Test KPLS Multiple Target (Linear Target)" begin

        X        = [1; 2; 3.0][:,:]
        Y        = [1 1; 2 2; 3 3.0][:,:]
        model    = PLSRegressor.fit(X,Y,nfactors=1,kernel="rbf",width=0.01)
    	Y_pred     = PLSRegressor.predict(model,X)
        @test mean(abs.(Y .- Y_pred)) < 1e-6

        X        = [1; 2; 3.0][:,:]
        Y        = [1 -1; 2 -2; 3 -3.0][:,:]
        model    = PLSRegressor.fit(X,Y,nfactors=1,kernel="rbf",width=0.01)
    	Y_pred     = PLSRegressor.predict(model,X)
        @test mean(abs.(Y .- Y_pred)) < 1e-6

        @testset "Linear Prediction Tests " begin

        X        = [1 2; 2 4; 4 6.0][:,:]
        Y        = [4 2;6 4;8 6.0][:,:]
        model    = PLSRegressor.fit(X,Y,nfactors=1,kernel="rbf",width=0.01)
    	Y_pred     = PLSRegressor.predict(model,X)
        @test mean(abs.(Y .- Y_pred)) < 1e-6

        X           = [1 -2; 2 -4; 4 -6.0][:,:]
        Y           = [-4 -2;-6 -4;-8 -6.0][:,:]
        model    = PLSRegressor.fit(X,Y,nfactors=1,kernel="rbf",width=0.01)
    	Y_pred     = PLSRegressor.predict(model,X)
        @test mean(abs.(Y .- Y_pred)) < 1e-6

        end

    end

end;

### not saving yeat.

@testset "Test Saving and Loading KPLS Models" begin

	Xtr        = [1 -2; 2 -4; 4.0 -6]
	Ytr        = [-2; -4; -6.0][:,:]
	Xt         = [6 -8; 8 -10; 10.0 -12]
	model1    = PLSRegressor.fit(Xtr,Ytr,nfactors=1,kernel="rbf",width=0.01)
	pred1     = PLSRegressor.predict(model1,Xt)

	PLSRegressor.save(model1)
	model2    = PLSRegressor.load()

	pred2     = PLSRegressor.predict(model2,Xt)
    rm(PLSRegressor.MODEL_FILENAME)
	@test all(pred1 .== pred2)

end;


# Multiples of 3 and 5

LIMIT = 1000

function solve()
    return sum((i % 3 == 0 || i % 5 == 0) ? i : 0 for i in 1:(LIMIT - 1))
end

print(Int(solve()))


include("./Functions.jl")

using Primes
using Functions

answer = 0
for j = 1:9
  for i = 1:(factorial(j))
    k = kthpermutation(i,j)
    if isprime(k)
      if k > answer
        answer = k
      end
    end
  end
end

println(answer)


### A Pluto.jl notebook ###
# v0.15.1

using Markdown
using InteractiveUtils

# ╔═╡ 60ccea97-08b4-417e-81e0-5110ce532d51
using PlutoUI

# ╔═╡ c9d6131c-d577-4523-9f1e-6f4543dd5f44
using Measures

# ╔═╡ 5115e0e4-0e37-4787-9b41-2a18605dc457
using LaTeXStrings

# ╔═╡ 504ac24f-5e3b-48fc-9ea7-b684274022f8
md"""
# Basic Julia types for data visualization
"""

# ╔═╡ 934c4b0c-913b-44f0-a880-50141b853165
md"""
## Functions
"""

# ╔═╡ b5c95cbd-1fe8-4796-92d0-3b4ae309c8d9
function addition(x, y)
    x + y
end

# ╔═╡ 4df5192a-c576-480f-ab1f-bbb73273a8cf
subtraction(x, y) = x - y

# ╔═╡ c27f7c14-c6bb-43f6-b231-37d66fd9dd81
md"""
### Higher-order and anonymous functions
"""

# ╔═╡ 2ed06105-6ca5-4cdb-b176-b4eedf24db43
sum(abs, [-1, 1])

# ╔═╡ 48dafa99-d4ff-4ec3-b58d-5a06ae49db26
sum(x -> abs(x), [-1, 1])

# ╔═╡ 4e4d3536-2360-4790-9962-d761ea565af6
sum([-1, 1]) do x
    abs(x)
end

# ╔═╡ ef345764-ffb7-4e59-a99a-30cbe3f383d6
md"""
## Julia types
"""

# ╔═╡ fa6157ea-49e0-4b48-b83c-fcfa334cc3a6
typeof("Hello World")

# ╔═╡ 4b5de8d6-c778-42e3-9a91-e63ff235913a
typeof(2)

# ╔═╡ c1ee4f85-b9f0-4ab3-acbc-50573c02a459
numbers = 1:5

# ╔═╡ dabf264b-93d0-492f-be5a-47cc3efb9b0a
Dump(numbers)

# ╔═╡ 8fdef782-3d8f-4560-9054-371bf49a705d
numbers.start

# ╔═╡ b99c8424-1da3-4fde-8d9a-f698c1511d03
numbers.stop

# ╔═╡ 49d1b1c3-45e4-41ef-91ed-562ee220ac5f
md"""
### Multiple dispatch
"""

# ╔═╡ 9699f41d-6f92-4528-9e38-a94c8ee72f25
concatenate(a::String, b::String) = a * b

# ╔═╡ 47b84567-81ad-4ddd-aebb-5cbaab2a1543
concatenate(a::Int, b::Int) = parse(Int, string(a) * string(b))

# ╔═╡ 67f9a128-d276-4455-827f-66cd58030517
concatenate("Hello", "World")

# ╔═╡ 0f77e7ed-1b7e-4004-b8cc-2061292ada5e
concatenate(1, 2)

# ╔═╡ a4b1caa7-bb57-49bc-bec0-6600a6d70951
methods(concatenate)

# ╔═╡ a1ac508b-9503-479f-8b16-3039c0d16c9b
concatenate(string(1), string(2))

# ╔═╡ 3a0be158-bd80-4837-a040-dc11470e3c58
convert(Float64, 1)

# ╔═╡ 9f915932-d7f3-47a9-a85c-2182531e8c46
Float64(1)

# ╔═╡ 0abeb1d5-1413-4dd4-8f20-9d95b866fa63
string(1)

# ╔═╡ 5634427a-e14d-4c37-86a4-6da6ca656c12
parse(Int, "1")

# ╔═╡ 72fc678c-06c7-43d7-9716-b91a1425aa89
md"""
### Representing numerical values
"""

# ╔═╡ 57757638-993e-4fbe-b0fc-faf0cc34e995
md"""
`Int`
"""

# ╔═╡ ec4f8362-39ca-4cf6-af8b-857e152d82b7
-1

# ╔═╡ 082a675a-d7c5-4c33-a5d7-1e83570f16fd
5

# ╔═╡ 6947633b-6896-4bc6-929c-50a68002a94d
10_000

# ╔═╡ 61371d87-acb9-448a-a875-f5745224c5df
md"`Float64`"

# ╔═╡ b5b0311e-9c16-4b9a-8091-43838b7feb8f
1.0

# ╔═╡ 588fd62f-509c-4b99-8e23-9b60e14d5602
-2e3

# ╔═╡ c2d8adc5-1ef2-4c46-bfb2-9ea30ca12e89
13.5E10

# ╔═╡ c54a18cd-eddc-4e20-bb7e-1b33e832b422
.1

# ╔═╡ c03b8c69-64e3-4223-8765-2818ea52f5a2
1.

# ╔═╡ 38e70ac5-fa8d-4ca8-9c71-c104a48b1e87
NaN

# ╔═╡ 05a088e1-60b6-47df-bf61-fddf548563e0
md"Units"

# ╔═╡ 1bcbfd07-375a-4fcb-913b-cd159fce54e7
10.5cm

# ╔═╡ 9f9573af-b65a-4246-aefa-64cc48ea9dcd
md"""
### Representing text
"""

# ╔═╡ ac0abe2d-6fe0-4a11-b212-0ca1ecc68ae2
"Single line String"

# ╔═╡ cc3a3304-c9b5-45e5-8209-9c94bfc611dd
"""
Multiline
String
"""

# ╔═╡ 0cc52da8-303d-45fd-9390-7b203cd15773
'a'

# ╔═╡ dfd31ce8-4c00-4ebe-a5f9-4d50b4c2e3cd
md"**Markdown** string"

# ╔═╡ 4f79eb5e-3cd6-42a0-b557-5a5821a9428b
html"HTML string"

# ╔═╡ c91a721d-1cb5-4839-8566-842c996e61a1
L"LaTeX string"

# ╔═╡ daf19907-5675-4430-886d-a69f3b71aed9
md"`Symbol`"

# ╔═╡ c71d8f39-4911-450d-bb97-8b93a61693a7
:var1

# ╔═╡ 3e72cf06-2247-4a71-bbaa-c1d420ed83e4
Symbol("1var")

# ╔═╡ ca7472e6-b08b-410b-a921-cccdd5fbfc31
md"""
### Collections
"""

# ╔═╡ 1d1b47c9-429c-41ec-9e09-38bddd2e3f7e
md"`Tuple`"

# ╔═╡ 8c9c54d7-403c-48a7-b63e-12c7fdd5659c
("x", 0)

# ╔═╡ 69631bb0-f279-4bf2-86d0-0f21bc6d3314
(1,)

# ╔═╡ 7e07074d-4aa3-459c-9c90-24718815d865
md"`Array`"

# ╔═╡ 86fab7a8-6d48-42dc-b963-df227162fb39
[1]

# ╔═╡ ff173169-77aa-4e6b-988d-0d1f7bd15a02
[1, 2]

# ╔═╡ 96e44dec-8ed7-4b87-b0de-c069d22e54f7
[1 2; 3 4]

# ╔═╡ 7bcf656c-e397-4544-a2c1-7c5a082a42d6
[ 1 2
  3 4 ]

# ╔═╡ bcd44701-7992-45fc-be4d-e4a13cfbcc63
zeros(4, 2)

# ╔═╡ 5a93658c-5807-46ef-8faf-108347a38bb4
zeros(10)

# ╔═╡ d66723b9-7db7-4943-9a34-b55a930fa207
collection = collect(1:5)

# ╔═╡ db71a959-3ef0-4da5-bd27-deaa4c47837b
md"Indexing"

# ╔═╡ 666df65b-3679-42ec-877e-4947b35f97a5
collection[1]

# ╔═╡ 3348ed56-0d26-407e-90b1-ba1a82e9f3d3
md"Array comprehension"

# ╔═╡ bff3d2f1-8255-4fc6-ac6f-43285d5d6989
[sqrt(x) for x in 1:5]

# ╔═╡ 5ea04cfd-7d8c-450e-9335-65f9a13887ae
[sqrt(x) for x in 1:10 if x % 2 != 0]

# ╔═╡ d8f5d3ae-5175-43e4-8279-77024d69856f
md"Broadcasting"

# ╔═╡ e60f8bdc-b825-4a69-aec0-89ea01e590fe
sqrt.(collection) .+ 1

# ╔═╡ 86deb0c1-f291-4bd9-861d-4355fe2dff81
md"""
---
"""

# ╔═╡ 29a2068e-47f7-4327-b52c-457f521a970f
PlutoUI.TableOfContents()

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
Measures = "442fdcdd-2543-5da2-b0f3-8c86c306513e"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"

[compat]
LaTeXStrings = "~1.2.1"
Measures = "~0.3.1"
PlutoUI = "~0.7.9"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

[[Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "8076680b162ada2a031f707ac7b4953e30667a37"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.2"

[[LaTeXStrings]]
git-tree-sha1 = "c7f1c695e06c01b95a67f0cd1d34994f3e7db104"
uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
version = "1.2.1"

[[Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[Measures]]
git-tree-sha1 = "e498ddeee6f9fdb4551ce855a46f54dbd900245f"
uuid = "442fdcdd-2543-5da2-b0f3-8c86c306513e"
version = "0.3.1"

[[Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[Parsers]]
deps = ["Dates"]
git-tree-sha1 = "438d35d2d95ae2c5e8780b330592b6de8494e779"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.0.3"

[[PlutoUI]]
deps = ["Base64", "Dates", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "Suppressor"]
git-tree-sha1 = "44e225d5837e2a2345e69a1d1e01ac2443ff9fcb"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.9"

[[Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[Random]]
deps = ["Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"

[[Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[Suppressor]]
git-tree-sha1 = "a819d77f31f83e5792a76081eee1ea6342ab8787"
uuid = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
version = "0.2.0"

[[Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
"""

# ╔═╡ Cell order:
# ╟─504ac24f-5e3b-48fc-9ea7-b684274022f8
# ╟─934c4b0c-913b-44f0-a880-50141b853165
# ╠═b5c95cbd-1fe8-4796-92d0-3b4ae309c8d9
# ╠═4df5192a-c576-480f-ab1f-bbb73273a8cf
# ╟─c27f7c14-c6bb-43f6-b231-37d66fd9dd81
# ╠═2ed06105-6ca5-4cdb-b176-b4eedf24db43
# ╠═48dafa99-d4ff-4ec3-b58d-5a06ae49db26
# ╠═4e4d3536-2360-4790-9962-d761ea565af6
# ╟─ef345764-ffb7-4e59-a99a-30cbe3f383d6
# ╠═fa6157ea-49e0-4b48-b83c-fcfa334cc3a6
# ╠═4b5de8d6-c778-42e3-9a91-e63ff235913a
# ╠═60ccea97-08b4-417e-81e0-5110ce532d51
# ╠═c1ee4f85-b9f0-4ab3-acbc-50573c02a459
# ╠═dabf264b-93d0-492f-be5a-47cc3efb9b0a
# ╠═8fdef782-3d8f-4560-9054-371bf49a705d
# ╠═b99c8424-1da3-4fde-8d9a-f698c1511d03
# ╟─49d1b1c3-45e4-41ef-91ed-562ee220ac5f
# ╠═9699f41d-6f92-4528-9e38-a94c8ee72f25
# ╠═67f9a128-d276-4455-827f-66cd58030517
# ╠═47b84567-81ad-4ddd-aebb-5cbaab2a1543
# ╠═0f77e7ed-1b7e-4004-b8cc-2061292ada5e
# ╠═a4b1caa7-bb57-49bc-bec0-6600a6d70951
# ╠═a1ac508b-9503-479f-8b16-3039c0d16c9b
# ╠═3a0be158-bd80-4837-a040-dc11470e3c58
# ╠═9f915932-d7f3-47a9-a85c-2182531e8c46
# ╠═0abeb1d5-1413-4dd4-8f20-9d95b866fa63
# ╠═5634427a-e14d-4c37-86a4-6da6ca656c12
# ╟─72fc678c-06c7-43d7-9716-b91a1425aa89
# ╟─57757638-993e-4fbe-b0fc-faf0cc34e995
# ╠═ec4f8362-39ca-4cf6-af8b-857e152d82b7
# ╠═082a675a-d7c5-4c33-a5d7-1e83570f16fd
# ╠═6947633b-6896-4bc6-929c-50a68002a94d
# ╟─61371d87-acb9-448a-a875-f5745224c5df
# ╠═b5b0311e-9c16-4b9a-8091-43838b7feb8f
# ╠═588fd62f-509c-4b99-8e23-9b60e14d5602
# ╠═c2d8adc5-1ef2-4c46-bfb2-9ea30ca12e89
# ╠═c54a18cd-eddc-4e20-bb7e-1b33e832b422
# ╠═c03b8c69-64e3-4223-8765-2818ea52f5a2
# ╠═38e70ac5-fa8d-4ca8-9c71-c104a48b1e87
# ╟─05a088e1-60b6-47df-bf61-fddf548563e0
# ╠═c9d6131c-d577-4523-9f1e-6f4543dd5f44
# ╠═1bcbfd07-375a-4fcb-913b-cd159fce54e7
# ╟─9f9573af-b65a-4246-aefa-64cc48ea9dcd
# ╠═ac0abe2d-6fe0-4a11-b212-0ca1ecc68ae2
# ╠═cc3a3304-c9b5-45e5-8209-9c94bfc611dd
# ╠═0cc52da8-303d-45fd-9390-7b203cd15773
# ╠═5115e0e4-0e37-4787-9b41-2a18605dc457
# ╠═dfd31ce8-4c00-4ebe-a5f9-4d50b4c2e3cd
# ╠═4f79eb5e-3cd6-42a0-b557-5a5821a9428b
# ╠═c91a721d-1cb5-4839-8566-842c996e61a1
# ╟─daf19907-5675-4430-886d-a69f3b71aed9
# ╠═c71d8f39-4911-450d-bb97-8b93a61693a7
# ╠═3e72cf06-2247-4a71-bbaa-c1d420ed83e4
# ╟─ca7472e6-b08b-410b-a921-cccdd5fbfc31
# ╟─1d1b47c9-429c-41ec-9e09-38bddd2e3f7e
# ╠═8c9c54d7-403c-48a7-b63e-12c7fdd5659c
# ╠═69631bb0-f279-4bf2-86d0-0f21bc6d3314
# ╟─7e07074d-4aa3-459c-9c90-24718815d865
# ╠═86fab7a8-6d48-42dc-b963-df227162fb39
# ╠═ff173169-77aa-4e6b-988d-0d1f7bd15a02
# ╠═96e44dec-8ed7-4b87-b0de-c069d22e54f7
# ╠═7bcf656c-e397-4544-a2c1-7c5a082a42d6
# ╠═bcd44701-7992-45fc-be4d-e4a13cfbcc63
# ╠═5a93658c-5807-46ef-8faf-108347a38bb4
# ╠═d66723b9-7db7-4943-9a34-b55a930fa207
# ╟─db71a959-3ef0-4da5-bd27-deaa4c47837b
# ╠═666df65b-3679-42ec-877e-4947b35f97a5
# ╟─3348ed56-0d26-407e-90b1-ba1a82e9f3d3
# ╠═bff3d2f1-8255-4fc6-ac6f-43285d5d6989
# ╠═5ea04cfd-7d8c-450e-9335-65f9a13887ae
# ╟─d8f5d3ae-5175-43e4-8279-77024d69856f
# ╠═e60f8bdc-b825-4a69-aec0-89ea01e590fe
# ╟─86deb0c1-f291-4bd9-861d-4355fe2dff81
# ╠═29a2068e-47f7-4327-b52c-457f521a970f
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


# Copyright (c) 2019 Daniel Moura
# https://towardsdatascience.com/freeing-the-data-scientist-mind-from-the-curse-of-vectorization-11634c370107?source=friends_link&sk=e7b0086f3678261cb64b6dbb43744613
# linkedin.com/in/dmoura
# twitter.com/daniel_c_moura

# running this file:
# julia --optimize=3 --check-bounds=no distances_among_pairs.jl

using CPUTime
using LinearAlgebra

### functions under evaluation ###

function distances_among_pairs_loop(v)
    nin = length(v)
    nout = div(nin * nin - nin, 2) #length of the output vector
    dists = Vector{eltype(v)}(undef,nout) #preallocating output vector
    k = 1 #current position in the output vector
    @inbounds for i in 1:(nin-1)
        a = v[i]
        @inbounds for j in (i+1):nin
            b = v[j]
            dists[k] = abs(a-b)
            k += 1
        end
    end
    dists
end

distances_among_pairs_comp(v) =
    [abs(v[i]-v[j]) for i in eachindex(v), j in eachindex(v) if i<j]

function distances_among_pairs_outer(v)
    dists = v .- v'
    abs.(dists[tril!(trues(size(dists)), -1)])
end

### aux functions ###

function bench(label, f, v)
    GC.gc() #cleaning the garbage before each measurement
    print("$label:\n  ")
    @time @CPUtime f(v)
    println()
end

function readvec(run)
    open("vec$run.csv", "r") do f
        map(line -> parse(Float64, line), eachline(f))
    end
end

### main ###

function main()
    println("Warming up...")
    v = fill(1.0, 3)
    distances_among_pairs_loop(v)
    distances_among_pairs_comp(v)
    distances_among_pairs_outer(v)

    for r in 1:3
        println("\n----- RUN $r -----")
        v = readvec(r)
        bench("LOOP",           distances_among_pairs_loop,  v)
        bench("COMPREHENSION",  distances_among_pairs_comp,  v)
        bench("OUTER",          distances_among_pairs_outer, v)
    end
end

main()


### A Pluto.jl notebook ###
# v0.12.11

using Markdown
using InteractiveUtils

# ╔═╡ 3c57ceb0-f934-11ea-1bef-c16b272d0c6b
using Pkg, DrWatson

# ╔═╡ 3c580a7e-f934-11ea-3b0c-4d5d7d5a7382
begin
	@quickactivate "StatisticalRethinkingTuring"
	using Turing
	using StatisticalRethinking
end

# ╔═╡ 83ad95c0-f933-11ea-1552-23ac7681803c
md"## Pluto-helpers-namedtuple.jl"

# ╔═╡ 3c9b1db4-f934-11ea-3d97-f51c2acc9037
md"## snippet 2.6"

# ╔═╡ 3ca3ac90-f934-11ea-1a8e-edfa2c664f1a
@model function ppl2_0(W, L)
    p ~ Uniform(0, 1)
    W ~ Binomial(W + L, p)
end

# ╔═╡ 3caf45dc-f934-11ea-2d1d-796707d89526
m2_0t = ppl2_0(6, 3);

# ╔═╡ 3cb18732-f934-11ea-3ebf-991c7b602d12
q2_0t = quap(m2_0t)

# ╔═╡ 3cbf6b30-f934-11ea-3b10-433589e36648
begin
	quap2_0t_df = DataFrame(:μ => rand(q2_0t.distr, 4000))
	Text(precis(quap2_0t_df; io=String))
end

# ╔═╡ d1670910-2c0d-11eb-12d0-616c518e22da
begin
	post2_0t = sample(m2_0t, NUTS(), 5000)
	Text(sprint(show, "text/plain", post2_0t))
end

# ╔═╡ 0604d19a-2cd4-11eb-3797-975be539c612
QM(q2_0t)

# ╔═╡ 57af25e0-2d97-11eb-0c3e-bb88e690e5ae
q2_0t

# ╔═╡ 3ce617ae-f934-11ea-0e4a-ff1ec9ee2610
md"## End pluto_helpers-namedtuple.jl"

# ╔═╡ Cell order:
# ╟─83ad95c0-f933-11ea-1552-23ac7681803c
# ╠═3c57ceb0-f934-11ea-1bef-c16b272d0c6b
# ╠═3c580a7e-f934-11ea-3b0c-4d5d7d5a7382
# ╟─3c9b1db4-f934-11ea-3d97-f51c2acc9037
# ╠═3ca3ac90-f934-11ea-1a8e-edfa2c664f1a
# ╠═3caf45dc-f934-11ea-2d1d-796707d89526
# ╠═3cb18732-f934-11ea-3ebf-991c7b602d12
# ╠═3cbf6b30-f934-11ea-3b10-433589e36648
# ╠═d1670910-2c0d-11eb-12d0-616c518e22da
# ╠═0604d19a-2cd4-11eb-3797-975be539c612
# ╠═57af25e0-2d97-11eb-0c3e-bb88e690e5ae
# ╟─3ce617ae-f934-11ea-0e4a-ff1ec9ee2610


### A Pluto.jl notebook ###
# v0.14.4

using Markdown
using InteractiveUtils

# ╔═╡ 7681f7f4-5830-4860-a67d-c2acd1ed8218
using Plots

# ╔═╡ b79ef21d-e232-4c10-a0ba-6b4c54ad71aa
begin
	using PlutoUI
	almost(text) = Markdown.MD(Markdown.Admonition("warning", "Almost there!", [text]))
	keep_working(text=md"The answer is not quite right.") = Markdown.MD(Markdown.Admonition("danger", "Keep working on it!", [text]));
	alert(text=md"Alert!") = Markdown.MD(Markdown.Admonition("danger", "Alert!", [text]));
	exercise(text=md"The answer is not quite right.") = Markdown.MD(Markdown.Admonition("correct", "Exercise", [text]));
	warning(text=md"Warning.") = Markdown.MD(Markdown.Admonition("warning", "Warning!", [text]));
	correct(text=md"Great! You got the right answer! Let's move on to the next section.") = Markdown.MD(Markdown.Admonition("correct", "Got it!", [text]));
	hint(text) = Markdown.MD(Markdown.Admonition("hint", "Hint", [text]));
end;

# ╔═╡ 69b42df0-8ae2-11eb-0bba-1983308b31c6
md"""
# Preliminary Weight, CG and Static Margin Estimation

"""

# ╔═╡ e76379e2-4d75-4b74-800d-8672941dc9a5
plotlyjs()

# ╔═╡ 8da00018-3cac-40d0-8d49-14a147467387
begin
	S_W=27#wing reference area

	#canard
	AR_H=0.9*4.5
	lambda_H=0#LE sweep
	S_H=8.26#reference area
	#l_H=5.265967537251188 #cg to local  cg

	#vertical stabilizer_wingtip
	#AR_V1=1.38
	#lambda_V1=36
	#S_V1=1.38
	#l_V1=7.755

	#Vertical stabilizer_fin
	#AR_V2=0.58
	#lambda_V2=45
	#S_V2=0.58
	#l_V2=7.2

	#other
	Mach=0.235
	Eta=0.97
	Eta_H=0.9

	#Fuselage
	L_F=9
	w_F=1.8
	S_F=33.9
end;

# ╔═╡ 0b5a24d0-8b00-11eb-224a-8b6fb1aff81a
md"""
## Estimating Mean Aerodynamic Chord Location
![](https://godot-bloggy.xyz/post/diagrams/WingParams.svg)

From the trapezoidal geometry:
```math
x_{40\%~\text{MAC}} = x_{\text{LE},\ \text{MAC}} + 0.4\bar{c}, \quad \text{where} \quad x_{\text{LE},\ \text{MAC}} = x_{\text{LE},\ \text{root}} + \bar Y\tan\Lambda_{\text{LE}}
```

"""

# ╔═╡ e8dad1e6-7477-42cc-b8e3-c518af7d9b52
begin
	# Vertical stabilizer
	S_v = 604
	lambda_v = deg2rad(44.4)
end;

# ╔═╡ a3b5d8b0-89e2-11eb-1040-031e0142ca7c
struct LiftingSurface
	c_root 		:: Float64
	c_tip 		:: Float64
    x_RLE 		:: Float64
    b 			:: Float64
    sweep_LE 	:: Float64
    taper 		:: Float64
end

# ╔═╡ 780dc91e-e6cf-4013-a04a-b35fa0fa9755
struct VaryingTaperLiftingSurface
	yB:: Float64
	x_rootLE :: Float64
	c_root:: Float64
	c_B :: Float64
	b :: Float64
	c_tip :: Float64
	taper_inner :: Float64
	taper :: Float64
	sweep_LEinner :: Float64
	sweep_LEouter :: Float64
	sweep_TE :: Float64
end

# ╔═╡ c82ef9d0-89e5-11eb-02d1-db611f26e0b6
md"""
Define a function for MAC with linear taper
```math
\bar{c}=\frac{2}{3}\left(\frac{1+\lambda+\lambda^2}{1+\lambda}\right)c_\text{root},~ \bar{Y}=\frac{b}{6}\left(\frac{1+2\lambda}{1+\lambda}\right)
```
"""

# ╔═╡ 3d325ff0-2a5e-4267-86f1-41c2cc25d7b1
md"""
Defined function for MAC for wings with cranked leading and trailing edges
```math
c_{\text{MAC}}=\frac{2}{3} c_{r} \frac{(\lambda_{i}^2 + \lambda_{i}\lambda+\lambda^2) + \left [ \frac{y_{B}}{(b/2 - y_{B})} \right ] (1+\lambda_{i}+\lambda_{i}^2) }{(\lambda_{i}^2+\lambda) + \left [ \frac{y_{B}}{(b/2 - y_{B})} \right ](1+\lambda_{i}^2)}
```
```math
y_{\text{MAC}}=\frac{1}{3} \left ( \frac{b}{2} -y_{B}\right ) \frac{\lambda_{i} \left (1 + 2 \lambda_{i} \lambda \right) + \left [ \frac{y_{B}}{(b/2) -y_{B}} \right ] (2+\lambda_{i}\lambda ) + \left [ \frac{y_{B}}{(b/2) -y_{B}} \right ]^2 (1+2\lambda_{i})}{(\lambda_{i}+\lambda)+\left [ \frac{y_{B}}{(b/2) -y_{B}} \right ](1+\lambda_{i})}
```
The above equation is taken from https://www.sciencedirect.com/topics/engineering/mean-aerodynamic-chord

"""

# ╔═╡ 137918a0-8b06-11eb-0f23-2fd22e086911
md"The function defined for MAC along the chord for linear tapered wing"

# ╔═╡ 3053c570-89e3-11eb-1f51-8fb42324f651
mean_aerodynamic_chord(surf :: LiftingSurface) = 2/3 * (1 + surf.taper + surf.taper^2)/(1 + surf.taper) * surf.c_root

# ╔═╡ 11ddb3ae-db3a-41b0-a77d-387f99b0e9a0
md"The function defined for MAC along the chord for cranked LE & TE wing"

# ╔═╡ 09f81461-7713-41fc-a3b9-cc106018ccf5
c_MAC(surf :: VaryingTaperLiftingSurface)=(2/3)*surf.c_root*(((surf.taper_inner^2)+(surf.taper_inner*surf.taper)+(surf.taper^2)+((surf.yB/((surf.b/2)-surf.yB))*(1+surf.taper_inner+(surf.taper_inner^2))))/(surf.taper+(surf.taper_inner^2)+((surf.yB/((surf.b/2)-surf.yB))*(1+surf.taper_inner^2))))

# ╔═╡ 3a8b8e80-89e6-11eb-255d-df692399569f
md"Define a function for MAC for linear-tapered wing along the span-wise direction"

# ╔═╡ 68795e80-89e6-11eb-04da-c9e66888d483
Y_mac(surf :: LiftingSurface) = surf.b * 1/6 * (1 + 2 * surf.taper) / (1 + surf.taper)

# ╔═╡ 3a4723e4-ec74-4d59-b1cd-4d1e99f2434e
md"Define a function for MAC for cranked LE & TE wing along the span-wise direction"

# ╔═╡ 28ad10c1-678a-45e5-85b7-772a99108f0c
yMAC(surf :: VaryingTaperLiftingSurface)=(1/3)*((surf.b/2)-surf.yB)*(((surf.taper_inner*(1+(2*surf.taper_inner*surf.taper)))+((surf.yB/((surf.b/2)-surf.yB))*(2+(surf.taper_inner*surf.taper)))+((surf.yB/((surf.b/2)-surf.yB))^2)*(1+(2*surf.taper_inner)))/(surf.taper_inner+surf.taper+((surf.yB/((surf.b/2)-surf.yB))*(1+surf.taper_inner))))

# ╔═╡ 11a520d0-89e6-11eb-2337-a5c4040b8013
md"Define the values of the wing for its struct."

# ╔═╡ be1b261a-be10-4c60-adad-36614e7b5827
begin
	#Main wing with LERX
	yB = 2.24#y loc at where it changes sweep
	x_rootLE= 4.252#distance of wing to nose at root
	c_root = 4#root chord
	c_B = 2.1#chord at where it changes sweep
	b_W = 12.6#span of wing
	c_tip = 0.96#chord at tip
	taper_inner = 0.525
	taper_W = 0.24#overall taper
	sweep_LEinner = 56
	sweep_LEouter = 26
	sweep_TE = 12
end;

# ╔═╡ c3824140-28d3-4f0d-8d56-3dbf42dca2cf
function getTaperSweepofMAC(ymac)
	if ymac<yB
		relSweep=sweep_LEinner
	elseif ymac>yB
		relSweep=sweep_LEouter
	elseif ymac>b_W
		relSweep=0
	end
	relSweep
end

# ╔═╡ 12d99410-89e4-11eb-1bbb-6b20dcf1678a
begin

	#canards
	croot_h = 0.9
	ctip_h = 0.9
	xRLE_h = 0.675#root LE longitudinal distance from nose
	b_h = 5
	sweepLE_h = 0
	taper_h = ctip_h/croot_h
	S_c=croot_h*b_h

	#vertical stabilizer at wingtip #concerning wingtip and span
	croot_V1 = 1.65
	ctip_V1 = 0.45
	xRLE_V1 = x_rootLE+(yB*tan(sweep_LEinner))+((b_W-yB)*tan(sweep_LEouter))
	b_V1 = 2
	sweepLE_V1 = 36
	taper_V1 = ctip_V1/croot_V1
	S_wingtip=(((croot_V1+ctip_V1)*b_V1)/2)*2

	#vertical stabilizer at fin
	croot_V2=0.58
	ctip_V2=0.45
	xRLE_V2=7.2
	b_V2=0.42
	sweepLE_V2=45
	taper_V2=ctip_V2/croot_V2
	S_aftFin=((croot_V2+ctip_V2)*b_V2)/2
end;

# ╔═╡ 608bc750-8b06-11eb-2123-fb1b2a57a252
md"Structs of type ```LiftingSurface``` are created by applying the ```LiftingSurface``` type as a **function** with inputs for its fields:"

# ╔═╡ 30fe0cc4-7e57-49bc-89b2-ce3b00cc2a65
canard=LiftingSurface(croot_h,ctip_h,xRLE_h,b_h,sweepLE_h,taper_h);

# ╔═╡ 9e1c4b9c-4352-47e0-be98-65cc599474ac
vStab=LiftingSurface(croot_V1,ctip_V1,xRLE_V1,b_V1,sweepLE_V1,taper_V1);

# ╔═╡ cb11e4d7-233f-4779-b9ad-7059bff013c9
finStab=LiftingSurface(croot_V2,ctip_V2,xRLE_V2,b_V2,sweepLE_V2,taper_V2);

# ╔═╡ b356c994-2314-491d-a93d-b5625be04036
Mainwing = VaryingTaperLiftingSurface(yB, x_rootLE, c_root,c_B,b_W,c_tip,taper_inner,taper_W,sweep_LEinner,sweep_LEouter,sweep_TE);

# ╔═╡ 2a7232ae-70a5-4bee-9b63-6d9903701a40
mainWingMAC_c = c_MAC(Mainwing) #Main wing chord MAC

# ╔═╡ f90ac96f-200c-4c00-bb15-f24074865da4
canardMAC_c=mean_aerodynamic_chord(canard)#canard MAC

# ╔═╡ 463f1e3b-365a-4311-a4db-bdbefa66564d
vStabMAC_c=mean_aerodynamic_chord(vStab)#wingtip v. stabilizer MAC

# ╔═╡ 5072dbe8-a88e-4891-8cb6-6a765d055dae
finStabMAC_c=mean_aerodynamic_chord(finStab)#aft fin MAC

# ╔═╡ d76e65a8-9e8f-4665-8fa8-52382d8dfbb2
mainWingMAC_Y=yMAC(Mainwing) #Main wing MAC y.loc, within inboard wing

# ╔═╡ 45d4c80a-5798-4451-a591-c1811ba15fed
canardMAC_Y=Y_mac(canard)#canard MAC y.loc

# ╔═╡ fc3217ef-7fc1-433a-a9b2-84cfda8f04d8
vStabMAC_Y=Y_mac(vStab)+(b_W/2)#vertical stabilizer MAC y.loc

# ╔═╡ 2d4a5450-8b07-11eb-3b69-3dc27e505c50
md"Now you can obtain the CG location of the wing with respect to the reference mean aerodynamic chord location."

# ╔═╡ afaaa1f0-8a0f-11eb-00dc-47d7ecc444a7
wing_mac_origin(surf :: LiftingSurface, percent) = surf.x_RLE + Y_mac(surf) * tan(surf.sweep_LE) + percent / 100 * mean_aerodynamic_chord(surf)

# ╔═╡ 142c524a-3917-430d-aab8-1469dc8effa8
wingMAC_loc_origin(wing :: VaryingTaperLiftingSurface, perc) = wing.x_rootLE + yMAC(wing)*tand(wing.sweep_LEinner) + perc/100*c_MAC(wing)

# ╔═╡ aee47142-8ce8-4189-9bfd-3ee51fbb4cb1
Mainwing_cg40 = wingMAC_loc_origin(Mainwing,40)

# ╔═╡ 197916ee-fe63-4df2-869b-81c440d5ff84
Mainwing_cg25 = wingMAC_loc_origin(Mainwing,25)

# ╔═╡ 3e05470c-1161-4adc-9607-3638368a14d6
Canard_cg40=wing_mac_origin(canard,40)

# ╔═╡ 10ca0d20-b99e-4b6e-a6b3-ecb95d1bf1b0
Canard_cg25=wing_mac_origin(canard,25)

# ╔═╡ 2c683bb7-83d6-409d-850b-9f622cfeefe9
vStab_cg40=wing_mac_origin(vStab,40)

# ╔═╡ f1ab1278-4ab5-423c-a773-5d2e874d06f7
vStab_cg25=wing_mac_origin(vStab,25)

# ╔═╡ b9c58e91-11e8-47db-a331-14691993f212
finStab_cg40=wing_mac_origin(finStab,40)

# ╔═╡ 7a95efd5-9794-43cb-b6cc-970c0899f746
finStab_cg25=wing_mac_origin(finStab,25)

# ╔═╡ 9c13d810-8a13-11eb-07a5-3bcedbd77eac
md"""
## Center of Gravity Estimation

The position of the aircraft’s center of gravity (CG) is given by:
```math
\mathbf{x}_\text{cg} = \frac{\sum_i W_i \ (\mathbf{x}_{\text{cg}})_i}{\sum_i W_i}, \quad \mathbf{x} = \begin{bmatrix}
  x \\ y \\ z
\end{bmatrix}
```

where $W_i$ represents the weight for each component and $\mathbf x_{\text{cg}_i}$ is the longitudinal distance between the origin and the CG of the $i$th component. The product in the form of $W_i \mathbf x_{\text{cg}_i}$ is also referred to as the moment of the $i$th component.

Considering a takeoff gross weight of 2655 kg and using Raymer's reference data, the specific weight and position parameters of the structural components are shown in the following table:

Components | Weight Ratio | Reference Weight (kg) | Approximate Location
:-------- | :-----: | :----------:|----------:
Nose landing gear | 0.057 * 15% - W * 0.014 | 2655 | Centroid
Main landing gear | 0.057 * 85% - W * 0.014 | 2655 | Centroid
Installed engine     | 1.4 | 208.7     | Centroid
“All-else empty”    | 0.1  | 2655     | 40-50% Length

Note: We consider the **nose** as the reference point and **clockwise moments** as positive!
"""

# ╔═╡ 9b2da92a-d97c-485b-ab36-bc775a65a159
begin
	# Filled in quantities
	TOGW = 2858 	 # kg
	w_engine = 363*1.4 # kg
	reserveFuel=0.06
	FuelWF=0.31135388287326593
	TrappedFuel=TOGW*FuelWF*reserveFuel#kg
	UsableFuel=TOGW*FuelWF/(reserveFuel+1)#kg

	#weight of Components
	w_MainWing=278.55
	w_fuselage=275.8
	w_canard=82.7475#67.0255
	w_VtailTip=43.99
	w_VtailFin=13.529
	w_MainLG=(0.057*TOGW-(TOGW*0.014))*0.85
	w_NoseLG=(0.057*TOGW-(TOGW*0.014))*0.15
	w_SeatRow1=30*2
	w_SeatRow2=30*3
	w_Crew=90
	w_passenger1=90*1
	w_passenger2=90*3
	w_BRS=40

	#cg length
	cg_fuselage=4.05
	cg_MainLG=5.625
	cg_NoseLG=0.825
	cg_SeatRow1=3.825
	cg_SeatRow2=5.325
	cg_engine=8.040
	cg_fuel=5.79
	cg_BRS=6

end;

# ╔═╡ 7a540330-8a13-11eb-3e55-c15452e5004c
md"It's convenient to use a function for the moment calculations."

# ╔═╡ ea34f410-8a14-11eb-1556-3b8c3ea0307d
moment(dist, weight) = dist * weight

# ╔═╡ 00d09cb2-8a15-11eb-0638-dffe4c995856
md"For the previously generated wing, the total moment is:"

# ╔═╡ a9ab5c00-8a18-11eb-1992-93bb7c57c029
md"The weight and CG position of each component can hence be computed and included in a dictionary for convenience in calculations."

# ╔═╡ 89bf9247-00c9-4b30-b328-6b1777d474a7
weight_pos = Dict(	"MainWing"   => (w_MainWing,Mainwing_cg40),
						"fuselage"  => (w_fuselage,cg_fuselage),
        				"canard"  => (w_canard,Canard_cg40),
        				"vTailTip"   => (w_VtailTip,vStab_cg40),
        				"vTailFin" => (w_VtailFin,finStab_cg40),
        				"mainLG" => (w_MainLG,cg_MainLG),
        				"noseLG" => (w_NoseLG,cg_NoseLG),
						"SeatRow_1" =>(w_SeatRow1,cg_SeatRow1),
						"SeatRow_2" => (w_SeatRow2,cg_SeatRow2),
						"engine"=> (w_engine,cg_engine),
						"trappedFuel" =>(TrappedFuel,cg_fuel),
						"Crew" => (w_Crew,cg_SeatRow1),
						"usableFuel"=> (UsableFuel,cg_fuel),
						"Passenger1" => (w_passenger1,cg_SeatRow1),
						"Passenger2" => (w_passenger2,cg_SeatRow2),
						"BRS" => (w_BRS,cg_BRS),
						);

# ╔═╡ 4175e400-8a1a-11eb-305d-bf6a7ad893ed
md"Now we can calculate the total moments generated from all the component, i.e., $\sum_i W_i \mathbf x_{\text{cg}_i}$"

# ╔═╡ 5b9711fa-4950-4512-9bec-14d08f594324
moment_list = [ moment(weight, pos_x) for (weight, pos_x) in values(weight_pos) ];

# ╔═╡ b63575a7-d350-496e-a0a7-9653413bfb62
momentSum = sum(moment_list)

# ╔═╡ 84906350-8a1a-11eb-26b6-2324a98f390b
md"The same logic also applies to the total weight, i.e., $\sum_i W_i$"

# ╔═╡ 934c246e-b0b7-4421-8545-a9e7b3d16bd3
tot_weight = sum(weight for (weight, pos_x) in values(weight_pos))

# ╔═╡ a8e2beb4-817f-4de7-a883-f6777c2ab73c
xCG = momentSum/tot_weight

# ╔═╡ 3a64faf2-56cd-438a-a187-a213bcdfcd14
md"""## CG excursion calculation"""

# ╔═╡ edf063b2-54ea-46f5-9d13-854e5014657d
weight_Empty = Dict(	"MainWing"   => (w_MainWing,Mainwing_cg40),
						"fuselage"  => (w_fuselage,cg_fuselage),
        				"canard"  => (w_canard,Canard_cg40),
        				"vTailTip"   => (w_VtailTip,vStab_cg40),
        				"vTailFin" => (w_VtailFin,finStab_cg40),
        				"mainLG" => (w_MainLG,cg_MainLG),
        				"noseLG" => (w_NoseLG,cg_NoseLG),
						"SeatRow_1" =>(w_SeatRow1,cg_SeatRow1),
						"SeatRow_2" => (w_SeatRow2,cg_SeatRow2),
						"engine"=> (w_engine,cg_engine),
						);

# ╔═╡ 34c83825-cca7-430c-a70c-09cd85d99852
WeightComponent_fuel=Dict("usableFuel"=> (UsableFuel,cg_fuel));

# ╔═╡ e01584c3-1a50-4b0e-8f6c-008312e6e945
weightComponent_OPEmpty=Dict("trappedFuel" =>(TrappedFuel,cg_fuel),
						"Crew" => (w_Crew,cg_SeatRow1));

# ╔═╡ a5ba9d65-2ba9-4328-ad81-ac426f91320c
WeightComponent_payload=Dict("Passenger1" => (w_passenger1,cg_SeatRow1),
						"Passenger2" => (w_passenger2,cg_SeatRow2),
						"BRS" => (w_BRS,cg_BRS));

# ╔═╡ 1dbe47ad-952c-4074-aac3-80e643099531
emptyW_component = sum(weight for (weight, pos_x) in values(weight_Empty))

# ╔═╡ bf9d769c-1918-46a2-904c-199da2adb044
OpEmpt_component=sum(weight for (weight, pos_x) in values(weightComponent_OPEmpty))

# ╔═╡ d9cad966-9d5f-4c1f-a75a-56a66cfb4c98
fuel_component = UsableFuel

# ╔═╡ af74f497-4752-42fd-9a85-9618b787df96
payload_component = sum(weight for (weight, pos_x) in values(WeightComponent_payload))

# ╔═╡ 6df33c5b-fe54-480c-9d57-a2e806be631e
sum_moments_emptyComp = sum(moment(weight, pos_x) for (weight, pos_x) in values(weight_Empty));

# ╔═╡ e241a14b-8340-45dd-9bc7-04d2b426198c
sum_moments_OpEmpt_Comp = sum(moment(weight, pos_x) for (weight, pos_x) in values(weightComponent_OPEmpty))

# ╔═╡ df0597a3-492b-4735-9497-90179942a882
sum_moments_fuel_Comp = sum(moment(fuel_component, 4.8));

# ╔═╡ 7bab3e29-2558-4a20-b092-6e8921e427d5
sum_moments_payload = sum(moment(weight, pos_x) for (weight, pos_x) in values(WeightComponent_payload));

# ╔═╡ 3d3f2ec4-299e-4936-9fec-18d0542e273d
x_cg_Empt_Comp = sum_moments_emptyComp/emptyW_component

# ╔═╡ 6a8e4835-5643-4b3c-b9b5-28e596cd1170
x_cg_OpEmpt_Comp = sum_moments_OpEmpt_Comp/OpEmpt_component

# ╔═╡ e64398d7-6aeb-40b5-b774-e9a7ef50ab79
x_cg_fuel_Comp = sum_moments_fuel_Comp/fuel_component

# ╔═╡ 6df55772-ea54-48f0-a577-bb0a372dcffa
x_cg_Passenger = sum_moments_payload/payload_component

# ╔═╡ 33a141a5-3e02-419a-be64-5d654578982d
begin
	moment1=sum_moments_emptyComp
	moment2=sum_moments_OpEmpt_Comp
	moment3=sum_moments_fuel_Comp
	moment4=sum_moments_payload

	x_cg1=x_cg_Empt_Comp
	x_cg2=x_cg_OpEmpt_Comp
	x_cg3=x_cg_fuel_Comp
	x_cg4=x_cg_Passenger
end;

# ╔═╡ 61d42a8a-cb21-4f10-a310-66a06d3b2565
function excursion(m1,m2,m3,m4,x1,x2,x3,x4)
	excursion_xcg=[m1/(m1/x1),(m2+m1)/((m2/x2)+(m1/x1)),(m3+m2+m1)/((m3/x3)+(m2/x2)+(m1/x1)),(m4+m3+m2+m1)/((m4/x4)+(m3/x3)+(m2/x2)+(m1/x1))]
	excursion_weight=[m1/x1,(m2/x2)+(m1/x1),(m3/x3)+(m2/x2)+(m1/x1),(m4/x4)+(m3/x3)+(m2/x2)+(m1/x1)]
	excursion_xcg,excursion_weight
end

# ╔═╡ 30b2ad6c-88bc-48f0-a436-4e155cff23f7
#Case 1 - 1234
case1cg,case1weight=excursion(moment1,moment2,moment3,moment4,x_cg1,x_cg2,x_cg3,x_cg4);

# ╔═╡ 0ed8b8b0-0674-4eff-bd68-d05f9b8c2a33
#Case 2 - 1243
case2cg,case2weight=excursion(moment1,moment2,moment4,moment3,x_cg1,x_cg2,x_cg4,x_cg3);

# ╔═╡ f589ca20-8af5-4164-8fba-1124ff131d35
#Case 3 - 1324
case3cg,case3weight=excursion(moment1,moment3,moment2,moment4,x_cg1,x_cg3,x_cg2,x_cg4);

# ╔═╡ f000648b-a70e-4600-b2f7-0af98922da15
#Case 4 - 1342
case4cg,case4weight=excursion(moment1,moment3,moment4,moment2,x_cg1,x_cg3,x_cg4,x_cg2);

# ╔═╡ c4748573-4944-4000-a21e-e4254dac6f07
#Case 5 - 1423
case5cg,case5weight=excursion(moment1,moment4,moment2,moment3,x_cg1,x_cg4,x_cg2,x_cg3);

# ╔═╡ 872a4079-7ef5-4501-8326-7f542fa671ed
#Case 6 - 1432
case6cg,case6weight=excursion(moment1,moment4,moment3,moment2,x_cg1,x_cg4,x_cg3,x_cg2);

# ╔═╡ 1f3a5f6a-d99f-48b3-8d62-ef2b4cfb4cfb
begin
	cg_excursionPlot = plot(ylabel = "Weight, kg", xlabel = "Longitudinal distance from the tip of the nose, m",
		 			title = "CG Excursion Plot")
	plot!(case1cg, case1weight, label = "1234")
	plot!(case2cg, case2weight, label = "1243")
	plot!(case3cg, case3weight, label = "1324")
	plot!(case4cg, case4weight, label = "1342")
	plot!(case5cg, case5weight, label = "1423")
	plot!(case6cg,case6weight, label="1432")
end

# ╔═╡ 7eae14e0-8a1b-11eb-15f0-372c8c42804b
md"""## Static Margin Estimation

The forces depicted are the largest contributors to the longitudinal stability characteristics.

The static margin equation in a slightly modified form:
```math
\text{Static Margin} = \frac{l_h S_h}{\bar{c}S_w} \frac{C_{L_{\alpha_h}}}{C_{L_{\alpha_w}}} - \frac{\partial C_{m_{fus}}}{\partial C_L} - \frac{(x_{cg} - x_{25\%\ \text{MAC}})}{\bar{c}}
```
4 parameters are unknown after the sizing procedure:
1. The lift curve slope for the wing $C_{L_{\alpha_w}}$
2. The lift curve slope of the horizontal stabilizer $C_{L_{\alpha_h}}$
3. Derivative of pitching moment of fuselage (including other components) with respect to $C_L$ $\frac{\partial C_{m_{fus}}}{\partial C_L}$
4. Distance between wing aerodynamic center and the CG  $x_{cg} - x_{25\%\ \text{MAC}}$.

"""

# ╔═╡ d7449777-49c3-4acc-af6a-523fc6e67231
Canard_staticMargin(l_h,S_c,CLa_c,cbar,S_w,CLA_w,CmCL,x_cg,x_ac)=((l_h*S_c*CLa_c)/(cbar*S_w*CLA_w))+(CmCL)-((x_cg-x_ac)/cbar)

# ╔═╡ b89f03d2-0b78-45a9-9644-d5c1eb16f29d
md""" #### Downwash effect
"""

# ╔═╡ c77332d9-98eb-4cc5-a1c7-7fa60dd14dc3
begin
	downwashWing=0.41
	corrFactorWingDownwash=0.129
	CLwingIsolate=0.0688
	CLcanardIsolate=0.0693
	upwashCanard=0.05
end;

# ╔═╡ b620d985-d81b-466e-a489-6d6bf505f0c8
CLwing=CLwingIsolate*(1-(downwashWing*corrFactorWingDownwash))

# ╔═╡ 38fc9c2a-c84f-430e-aa2e-2b14e5cbe112
ClCanard_upwash=CLcanardIsolate*(1+upwashCanard)*S_c/S_W

# ╔═╡ 11dd1622-50c4-404e-88a2-c29c02760ca9
CLCanardWing=ClCanard_upwash+CLwing

# ╔═╡ e258ad9e-a39b-45c8-997b-d785977116fd
md"""### Fuselage Pitching Moment (Newton's Divided Differences Interpolation Polynomial) """

# ╔═╡ 55a66ce9-2c77-42dd-8aac-446cddb1274d
FusePitch(K_fus,w_fus,L_fus,S_w,cbar,CLA_w)=(K_fus*L_fus*(w_fus)^2)/(S_w*cbar*CLA_w)

# ╔═╡ fc926801-2060-4c9f-b103-466c30cb899a
getFuse(wingloc)=0.115+(0.57*(wingloc-0.1))+(5.75*(wingloc-0.1)*(wingloc-0.2))+((-24)*(wingloc-0.1)*(wingloc-0.2)*(wingloc-0.3))+(96.25*(wingloc-0.1)*(wingloc-0.2)*(wingloc-0.3)*(wingloc-0.4))+((-314.167)*(wingloc-0.1)*(wingloc-0.2)*(wingloc-0.3)*(wingloc-0.4)*(wingloc-0.5))+(890.2778*(wingloc-0.1)*(wingloc-0.2)*(wingloc-0.3)*(wingloc-0.4)*(wingloc-0.5)*(wingloc-0.6))

# ╔═╡ 5a22ad68-2241-4c8e-bfc5-bbaf9bf786e2
md"""### Tail volume parameters"""

# ╔═╡ cccce8d6-afa5-484f-8871-9106e71da354
#define moment arms
begin
	l_canard=abs(Mainwing_cg25-Canard_cg25)
	l_wingtip=abs(vStab_cg25-Mainwing_cg25)
	l_aftFin=abs(finStab_cg25-Mainwing_cg25)
end;

# ╔═╡ 84aedc8c-5a59-4e7a-8136-d209b16984bd
C_VT(L_vt,S_vt,b_w,S_w)=(L_vt*S_vt)/(b_w*S_w)

# ╔═╡ 87ad6166-a5f7-4fdd-9ca3-85ec1ac62fff
C_HT(L_ht,S_ht,cbar_w,S_w)=(L_ht*S_ht)/(cbar_w*S_w)

# ╔═╡ 217f7fb1-0cc4-4ead-8998-2fc5c13571a9
Volume_wingtip=C_VT(l_canard,S_wingtip,b_W,S_W)

# ╔═╡ 97c7bd4b-f860-4e66-bd5a-a901f9c014ee
Volume_aftFin=C_VT(l_aftFin,S_aftFin,b_W,S_W)

# ╔═╡ 8164635b-1ad6-4b74-8926-b90d6182cc69
Volume_Canard=C_HT(l_canard,S_c,mainWingMAC_c,S_W)

# ╔═╡ 995fb655-6e01-452b-91fd-93b5cb89a17e
md"""#### Value Checks"""

# ╔═╡ 67fc7cd4-afd1-4812-9e34-41527ca8e68a
testCmCL=FusePitch(getFuse(Mainwing_cg25/L_F),w_F,L_F,S_W,mainWingMAC_c,CLwing)

# ╔═╡ 8944cab7-3a9e-4573-93cc-3f605fcb545a
SM1=Canard_staticMargin(case1cg[1]-Canard_cg40,S_c,ClCanard_upwash,mainWingMAC_c,S_W,CLwing,testCmCL,case1cg[1],Mainwing_cg40)

# ╔═╡ d6562dfe-6656-494b-aae7-9807aec745f9
neutralPoint=(SM1/100)*mainWingMAC_c+case1cg[1]

# ╔═╡ Cell order:
# ╟─69b42df0-8ae2-11eb-0bba-1983308b31c6
# ╠═e76379e2-4d75-4b74-800d-8672941dc9a5
# ╠═7681f7f4-5830-4860-a67d-c2acd1ed8218
# ╠═8da00018-3cac-40d0-8d49-14a147467387
# ╟─0b5a24d0-8b00-11eb-224a-8b6fb1aff81a
# ╟─e8dad1e6-7477-42cc-b8e3-c518af7d9b52
# ╠═a3b5d8b0-89e2-11eb-1040-031e0142ca7c
# ╠═780dc91e-e6cf-4013-a04a-b35fa0fa9755
# ╠═c3824140-28d3-4f0d-8d56-3dbf42dca2cf
# ╟─c82ef9d0-89e5-11eb-02d1-db611f26e0b6
# ╟─3d325ff0-2a5e-4267-86f1-41c2cc25d7b1
# ╟─137918a0-8b06-11eb-0f23-2fd22e086911
# ╠═3053c570-89e3-11eb-1f51-8fb42324f651
# ╟─11ddb3ae-db3a-41b0-a77d-387f99b0e9a0
# ╠═09f81461-7713-41fc-a3b9-cc106018ccf5
# ╟─3a8b8e80-89e6-11eb-255d-df692399569f
# ╠═68795e80-89e6-11eb-04da-c9e66888d483
# ╟─3a4723e4-ec74-4d59-b1cd-4d1e99f2434e
# ╠═28ad10c1-678a-45e5-85b7-772a99108f0c
# ╟─11a520d0-89e6-11eb-2337-a5c4040b8013
# ╠═12d99410-89e4-11eb-1bbb-6b20dcf1678a
# ╠═be1b261a-be10-4c60-adad-36614e7b5827
# ╟─608bc750-8b06-11eb-2123-fb1b2a57a252
# ╠═30fe0cc4-7e57-49bc-89b2-ce3b00cc2a65
# ╠═9e1c4b9c-4352-47e0-be98-65cc599474ac
# ╠═cb11e4d7-233f-4779-b9ad-7059bff013c9
# ╠═b356c994-2314-491d-a93d-b5625be04036
# ╠═2a7232ae-70a5-4bee-9b63-6d9903701a40
# ╠═f90ac96f-200c-4c00-bb15-f24074865da4
# ╠═463f1e3b-365a-4311-a4db-bdbefa66564d
# ╠═5072dbe8-a88e-4891-8cb6-6a765d055dae
# ╠═d76e65a8-9e8f-4665-8fa8-52382d8dfbb2
# ╠═45d4c80a-5798-4451-a591-c1811ba15fed
# ╠═fc3217ef-7fc1-433a-a9b2-84cfda8f04d8
# ╟─2d4a5450-8b07-11eb-3b69-3dc27e505c50
# ╠═afaaa1f0-8a0f-11eb-00dc-47d7ecc444a7
# ╠═142c524a-3917-430d-aab8-1469dc8effa8
# ╠═aee47142-8ce8-4189-9bfd-3ee51fbb4cb1
# ╠═197916ee-fe63-4df2-869b-81c440d5ff84
# ╠═3e05470c-1161-4adc-9607-3638368a14d6
# ╠═10ca0d20-b99e-4b6e-a6b3-ecb95d1bf1b0
# ╠═2c683bb7-83d6-409d-850b-9f622cfeefe9
# ╠═f1ab1278-4ab5-423c-a773-5d2e874d06f7
# ╠═b9c58e91-11e8-47db-a331-14691993f212
# ╠═7a95efd5-9794-43cb-b6cc-970c0899f746
# ╟─9c13d810-8a13-11eb-07a5-3bcedbd77eac
# ╠═9b2da92a-d97c-485b-ab36-bc775a65a159
# ╟─7a540330-8a13-11eb-3e55-c15452e5004c
# ╠═ea34f410-8a14-11eb-1556-3b8c3ea0307d
# ╟─00d09cb2-8a15-11eb-0638-dffe4c995856
# ╟─a9ab5c00-8a18-11eb-1992-93bb7c57c029
# ╠═89bf9247-00c9-4b30-b328-6b1777d474a7
# ╟─4175e400-8a1a-11eb-305d-bf6a7ad893ed
# ╠═5b9711fa-4950-4512-9bec-14d08f594324
# ╠═b63575a7-d350-496e-a0a7-9653413bfb62
# ╟─84906350-8a1a-11eb-26b6-2324a98f390b
# ╠═934c246e-b0b7-4421-8545-a9e7b3d16bd3
# ╠═a8e2beb4-817f-4de7-a883-f6777c2ab73c
# ╟─3a64faf2-56cd-438a-a187-a213bcdfcd14
# ╠═edf063b2-54ea-46f5-9d13-854e5014657d
# ╠═34c83825-cca7-430c-a70c-09cd85d99852
# ╠═e01584c3-1a50-4b0e-8f6c-008312e6e945
# ╠═a5ba9d65-2ba9-4328-ad81-ac426f91320c
# ╠═1dbe47ad-952c-4074-aac3-80e643099531
# ╠═bf9d769c-1918-46a2-904c-199da2adb044
# ╠═d9cad966-9d5f-4c1f-a75a-56a66cfb4c98
# ╠═af74f497-4752-42fd-9a85-9618b787df96
# ╠═6df33c5b-fe54-480c-9d57-a2e806be631e
# ╠═e241a14b-8340-45dd-9bc7-04d2b426198c
# ╠═df0597a3-492b-4735-9497-90179942a882
# ╠═7bab3e29-2558-4a20-b092-6e8921e427d5
# ╠═3d3f2ec4-299e-4936-9fec-18d0542e273d
# ╠═6a8e4835-5643-4b3c-b9b5-28e596cd1170
# ╠═e64398d7-6aeb-40b5-b774-e9a7ef50ab79
# ╠═6df55772-ea54-48f0-a577-bb0a372dcffa
# ╠═33a141a5-3e02-419a-be64-5d654578982d
# ╠═61d42a8a-cb21-4f10-a310-66a06d3b2565
# ╠═30b2ad6c-88bc-48f0-a436-4e155cff23f7
# ╠═0ed8b8b0-0674-4eff-bd68-d05f9b8c2a33
# ╠═f589ca20-8af5-4164-8fba-1124ff131d35
# ╠═f000648b-a70e-4600-b2f7-0af98922da15
# ╠═c4748573-4944-4000-a21e-e4254dac6f07
# ╠═872a4079-7ef5-4501-8326-7f542fa671ed
# ╠═1f3a5f6a-d99f-48b3-8d62-ef2b4cfb4cfb
# ╟─7eae14e0-8a1b-11eb-15f0-372c8c42804b
# ╠═d7449777-49c3-4acc-af6a-523fc6e67231
# ╟─b89f03d2-0b78-45a9-9644-d5c1eb16f29d
# ╠═c77332d9-98eb-4cc5-a1c7-7fa60dd14dc3
# ╠═b620d985-d81b-466e-a489-6d6bf505f0c8
# ╠═38fc9c2a-c84f-430e-aa2e-2b14e5cbe112
# ╠═11dd1622-50c4-404e-88a2-c29c02760ca9
# ╟─e258ad9e-a39b-45c8-997b-d785977116fd
# ╠═55a66ce9-2c77-42dd-8aac-446cddb1274d
# ╠═fc926801-2060-4c9f-b103-466c30cb899a
# ╟─5a22ad68-2241-4c8e-bfc5-bbaf9bf786e2
# ╠═cccce8d6-afa5-484f-8871-9106e71da354
# ╠═84aedc8c-5a59-4e7a-8136-d209b16984bd
# ╠═87ad6166-a5f7-4fdd-9ca3-85ec1ac62fff
# ╠═217f7fb1-0cc4-4ead-8998-2fc5c13571a9
# ╠═97c7bd4b-f860-4e66-bd5a-a901f9c014ee
# ╠═8164635b-1ad6-4b74-8926-b90d6182cc69
# ╟─995fb655-6e01-452b-91fd-93b5cb89a17e
# ╠═67fc7cd4-afd1-4812-9e34-41527ca8e68a
# ╠═8944cab7-3a9e-4573-93cc-3f605fcb545a
# ╠═d6562dfe-6656-494b-aae7-9807aec745f9
# ╟─b79ef21d-e232-4c10-a0ba-6b4c54ad71aa


using Test
using CSV
include("../src/FindKm.jl")

################################################################################
# Tests for functions that normalise data like M3Drop expects
################################################################################

#Check can accurately find no. detected genes per cell
df = DataFrame(Column1 = ["I1", "I2", "I3", "I4"], Cell1 = Float64[0, 0, 0, 0],
Cell2 = Float64[0,1,1,1])
expected_output_true = DataFrame(Cells = [Symbol("Cell1"), Symbol("Cell2")],
DetectedGenes = Float64[0,3])
@test FindNumDetectedGenesPerCell(df, true) == expected_output_true

#Check can accurately find no. undetected genes per cell
expected_output_false = DataFrame(Cells = [Symbol("Cell1"), Symbol("Cell2")],
DetectedGenes = Float64[4,1])
@test FindNumDetectedGenesPerCell(df, false) == expected_output_false

#Check can find mean DetectedGenes
@test FindMean(expected_output_true) == 1.5
@test FindMean(expected_output_false) == 2.5

#Check can find sd DetectedGenes
@test FindStd(expected_output_true) ≈ 2.12 atol=0.01
@test FindStd(expected_output_false) ≈ 2.12 atol=0.01

#Check removing low quality cells works
expected_output = deepcopy(expected_output_false)
@test RemoveLowQuality(expected_output_false, 2.5, 2.12) == expected_output
df = DataFrame(Cells = [Symbol("Cell1")], DetectedGenes = Float64[8025])
@test RemoveLowQuality(df, 0, 1 ) == DataFrame(Cells = Symbol[],
DetectedGenes = Float64[])

#Check can remove cells correctly
orig_df = DataFrame(Isoforms = ["I1", "I2", "I3", "I4"], Cell1 = Float64[0, 0, 0, 0],
Cell2 = Float64[0,1,1,1])
@test KeepFiltered(df, orig_df) == DataFrame(Isoforms = ["I1", "I2", "I3", "I4"],
 Cell1 = Float64[0, 0, 0, 0])
@test KeepFiltered(DataFrame(Cells = []), orig_df) ==
DataFrame(Isoforms = ["I1", "I2", "I3", "I4"])

#Check can correctly remove undetected genes
test_data = DataFrame(Column1 = ["I1", "I2", "I3"], Cell1 = Float64[0, 0, 1],
Cell2 = Float64[0,1,1], Cell3 = Float64[1,1,1], Cell4 = [1,1,1])
expected_output = DataFrame(Column1 = ["I3"], Cell1 = Float64[1],
Cell2 = Float64[1], Cell3 = Float64[1], Cell4 = Float64[1])
@test RemoveUndetectedGenes(test_data) == expected_output

#Check cpm calculation is correct
input = DataFrame(Isoforms = ["I1", "I2", "I3", "I4"], Cell1 = Float64[0, 0, 0, 1],
Cell2 = Float64[0,0,1,1])
expected_output = DataFrame(Isoforms = ["I1", "I2", "I3", "I4"], Cell1 = Float64[0, 0, 0, 1000000],
Cell2 = Float64[0,0,500000,500000])
@test ConvertToCpm(input) == expected_output

#Check can correctly remove lowly expressed genes
expected_output = DataFrame(Isoforms = ["I3", "I4"], Cell1 = Float64[ 0, 1],
Cell2 = Float64[1,1])
@test RemoveLowlyExpressedGenes(input) == expected_output

################################################################################
# Tests for functions that calculate Km
################################################################################

input = DataFrame(Isoforms = ["I1", "I2"], Cell1 = [0, 1], Cell2 = [1, 1])
expected_output = DataFrame(Isoforms = ["I1", "I2"], p = [0.5, 0])
@test Findp(input) == expected_output

s_out = deepcopy(expected_output)
s_out[:s] = [0.5, 1]
@test Finds(expected_output, input) == s_out

#Check LogLikelihood function gives correct output
LogLikelihoodFn = LogLikelihood(Float64[1,2,3], Float64[1,2,3])
@test LogLikelihoodFn([1.0,1.0]) ≈ 8.05 atol=0.01

#Check maximum likelihood estimation is working
df = CSV.read("test/data/p.txt", header = true, delim = ',')
p=df.x
df = CSV.read("test/data/s.txt", header = true, delim = ',')
s=df.x
parameters = MaxLikelihoodEst(p, s)
@test parameters[1] ≈ 8.5 atol=0.1
@test parameters[2] ≈ 0.1 atol=0.1


function mergesort(v)
    if length(v) <= 1
        return v
    end
    return merge(mergesort(v[1:end÷2]), mergesort(v[end÷2+1:end]))
end

function merge(v1, v2)
    if isempty(v1)
        return v2

    elseif isempty(v2)
        return v1

    elseif v2[1] < v1[1]
        return [v2[1]; merge(v1, v2[2:end])]

    else # v1[1] <= v2[1]
        return [v1[1]; merge(v1[2:end], v2)]
    end
end

v = [144, 89, 4, 9, 95, 12, 86, 25]
println("unsorted array = ", v)

sorted_v = mergesort(v)

println("merge sorted array = ", sorted_v)