"""
    NovDist

Novel distributions, copula, sampling, bootstrapping and other contents.
including:
1. Sampling
    1. LatinHyperCube(): latin hypercube sampling
2. Distributions
3. Generic methods
    1. invcdf(): inverse function of CDF, in fact, it is another name of Distributions.quantile()
		1. invstdnorm(), invnorm(), invexpo()
	2. rrank(): r-style ranking API, using ranking methosd in StatsBase module

Depends on:
1. Distributions: std lib
2. Random: std lib
3. LinearAlgebra: std lib
4. StatsBase: std lib

"""
module NovDist
    import Distributions  # std lib of distributions
	import Random  # std lib
	import LinearAlgebra  # std lib
	import StatsBase  # std lib
	# -----------
	# generic functions
	export LatinHyperCube, rrank
	# quick functions
	export invcdf, invstdnorm, invnorm, invexpo

# ----------------------- convenient methods naming
"""
	rrank( x::AbstractArray ; rev::Bool = false, ties_method::String = "ordinal" )

R-language style ranking, returns the ranks of an array's elements;
using StatsBase (std lib);
but different ties methods from R-lang;

receives:
1. x::AbstractArray, the array to get ranks
2. rev::Bool, reverse ranks? false for ordinary order, true for reverse order
3. ties_method::String, methods of ties, one of ["ordinal","dense","compete","average"]
returns:
1. Res::AbstractArray

ties methods & examples & equivalent std function:
0. example vector to rank: x = [3,4,4,99]
1. "ordinal" ==> 1,2,3,4 ==> StatsBase.ordinalrank(x) ==> "ordinal ranking"
2. "dense" ==> 1,2,2,3 ==> StatsBase.denserank(x) ==> "dense ranking"
3. "compete" ==> 1,2,2,4 ==> StatsBase.competerank(x) ==> "standard competition ranking"
4. "average" ==> 1,2.5,2.5,4 ==> StatsBase.tiedrank(x) ==> "fractional ranking"

reference:
1. http://en.wikipedia.org/wiki/Ranking#Fractional_ranking_.28.221_2.5_2.5_4.22_ranking.29
"""
function rrank( x::AbstractArray ; rev::Bool = false, ties_method::String = "ordinal" )
	if ties_method == "ordinal"
		return StatsBase.ordinalrank( x, rev = rev )::AbstractArray
	elseif ties_method == "dense"
		return StatsBase.denserank( x, rev = rev )::AbstractArray
	elseif ties_method == "compete"
		return StatsBase.competerank( x, rev = rev )::AbstractArray
	elseif ties_method == "average"
		return StatsBase.tiedrank( x, rev = rev )::AbstractArray
	else
		throw(AssertionError("invalid ties_method string"))
	end
end # end function rrank
# -----------------------

# ----------------------- Abbreviation Types
# univariate continuous distribution types
UniVarContinuousDistribution = Distributions.Distribution{Distributions.Univariate,Distributions.Continuous}

# ----------------------- Inverse CDF (Univariate distributions)
invcdf( d::Distributions.UnivariateDistribution, q::Real ) = Distributions.quantile( d, q )
# ----------------------- Quick Functions
invstdnorm( p::Real ) = invcdf( Distributions.Normal(0.0, 1.0), p )
invnorm( p::Real, μ::Real, σ::Real ) = invcdf( Distributions.Normal(μ, σ), p )
invexpo( p::Real, θ::Real ) = invcdf( Distributions.Exponential(θ), p )

# -----------------------
"""
    LatinHyperCube( D::Distributions.UnivariateDistribution, N::Int, P::Int )

Latin hypercube sampling for Univariate Distributions, e.g. Normal, Beta, Expoenential;
requires:
1. D::Distributions.UnivariateDistribution, a distribution instance
2. N::Int, a positive integer which indicates how many parts to equally divide in [0,1], the prob domain
3. P::Int, a positive integer which indicates how many samples to generate in EACH part
returns:
1. Ret::Vector{Float64}, a sampling vector with N * P sample points/elements
depends on:
1. invcdf(), of course, equivalent to Distributions.quantile()
2. Random.shuffle(), shuffles samples
timing:
1. benchmark: Intel Core i7 6700 3.40GHz, SATA3.0, no SSD, 16G DDR3
2. code: @time eval(:( D = Distributions.Normal(0.0,1.0); NovDist.LatinHyperCube(D,100,100); ) );
3. about: 0.000943 seconds (76 allocations: 160.938 KiB)
4. about: sample mu = -0.0005; sigma = 0.99803
"""
function LatinHyperCube( D::Distributions.UnivariateDistribution, N::Int, P::Int )
    # validation
    @assert( (N > 0) & (P > 0), "requries N,P > 0 but received: $(N) and $(P)" )
    # sampling in N divided Uniform(0,1)
    # NOTE: sampling N*P points in ONE Uniform(0,1) then scale them to each Uniform
    local Res::Vector{Float64} = rand( Float64, N * P )
    # loop to re-scale & inverse
    for x in 0:N-1
        for y in 1:P
			# re-scale to a new Uniform & inverse prob to a point
            Res[ x * P + y ] = Distributions.quantile( D, (Res[ x * P + y ] + x) / N )
        end
    end
	# return sampling points which are shuffled by Random.shuffle
    return Random.shuffle(Res)::Vector{Float64}
end # end function LatinHyperCube
# -----------------------
"""
	LatinHyperCube( D::Distributions.AbstractMvNormal, N::Int, P::Int )

Latin hypercube sampling for Multivariate Normal Distributions
requires:
1. D::Distributions.AbstractMvNormal, a distribution instance
2. N::Int, a positive integer which indicates how many parts to equally divide in [0,1], the prob domain, on each dimension
3. P::Int, a positive integer which indicates how many samples to generate in EACH part
returns:
1. Ret::Matrix{Float64}, a sampling vector with N * P rows and Distributions.dim(D) columns; each row is a sample point
depends on:
2. LinearAlgebra.cholesky(), Cholesky decomposition
3. rrank == StatsBase.ordinalrank(), ranks of a vector
4. LatinHyperCube( D::Distributions.UnivariateDistribution, N::Int, P::Int )
timing:
1. benchmark: Intel Core i7 6700 3.40GHz, SATA3.0, no SSD, 16G DDR3
2. code: @time eval(:( D = Distributions.MvNormal(rand(5),rand(5)); NovDist.LatinHyperCube(D,100,100); ) );
3. 17.526934 seconds (949.95 M allocations: 17.898 GiB, 6.20% gc time)
4. maximum absolute error between sampled correlation matrix and target matrix: 0.00754
reference:
1. Iman, R. L., and W. J. Conover. 1982. A Distribution-free Approach to Inducing Rank Correlation Among Input Variables. Communications in Statistics B 11:311-334
2. Zhang, Y. , & Pinder, G. . (2003). Latin hypercube lattice sample selection strategy for correlated random hydraulic conductivity fields. Water Resources Research, 39(8).
3. http://freesourcecode.net/matlabprojects/71269/latin-hypercube-sampling-in-matlab#.XLdVUuriuUk
"""
function LatinHyperCube( D::Distributions.AbstractMvNormal, N::Int, P::Int )
    # validation
	@assert( (N > 0) & (P > 0), "requries N,P > 0 but received: $(N) and $(P)" )

	# sampling in N divided Uniform(0,1)
	# NOTE: sampling N*P points in ONE Uniform(0,1) on EACH dimension
	local Ddim::Int = Distributions.dim( D.Σ )  # dim of D
	local Res::Matrix{Float64} = rand( Float64, N * P, Ddim )
	# NOTE: using Int for less memoery cost & easy indexing
	# NOTE: in practice, we do not need to declare a W0
	local Rnk1::Matrix{Int} = zeros( Int, N * P, Ddim )  # (row) ranks of each column for Res with correlation
	local EachDimStd = sqrt.( LinearAlgebra.diag( D.Σ ) )  # std of each marginal distribution

	# loop to re-scale & inverse to a MvNormal without correlation (each dim is independent)
	# NOTE: just use our LatinHyperCube() for univariate normal distribution which auto shuffles the results
	for z in 1:Ddim
		Res[:,z] = LatinHyperCube( Distributions.Normal( D.μ[z], EachDimStd[z] ), N, P )
	end

	# compute R (Ddim * Ddim size), the correlation matrix (not cov) of the Res without correlation
	# NOTE: when N*P is large, it is a good idea to use a eye(Ddim) to approximate R
	local Rmat::Matrix{Float64} = (N * P) > 10000 ?  LinearAlgebra.diagm( 0 => ones(Ddim) )  :  StatsBase.cor(Res)
	# get a new sample matrix Res1
	# NOTE: we do not specially declare Q,P but integrate them to the computing of X1
	# 		the complier is smart enough to optimize it for least memeory & time costs
	local Res1 = Res * transpose( LinearAlgebra.cholesky( Distributions.cor( D ) ).L * inv(LinearAlgebra.cholesky( Rmat ).L) )

	# record ranks of each column of Res1, then rearrange Res's columns (one by one) according to Res1's col-ranks
	for z in 1:Ddim
		# first, record X1's rank values
		Rnk1[:,z] = StatsBase.ordinalrank(Res1[:,z])
		# then, sort X0's column, the index is the rank value
		Res[:,z] = sort( Res[:,z] )
		# finally, rearrange indices according to Rnk1
		Res[:,z] = Res[Rnk1[:,z], z]
	end

	# return (we have already shuffled columns)
	return Res::Matrix{Float64}
end # end function LatinHyperCube
# -----------------------

end # end module NovDist


module PartialOrder
export subDAGof, ordered_subDAGof, copyDAG, raw_filter!

type Node{T}
    value::T
    gt::Set{Node{T}}

    Node(value) = new(value, Set{Node{T}}())
    Node(value, gt) = new(value, gt)
end
Node{T}(value::T) = Node{T}(value)

copyDAG{T}(top::Node{T}) = copyDAG(Dict{Node{T},Node{T}}(), top)
function copyDAG{T}(subs::Dict{Node{T},Node{T}}, node::Node{T})
    if haskey(subs, node); return subs[node] end

    gt = Set{Node{T}}([copyDAG(subs, child) for child in node.gt])
    subs[node] = Node{T}(node.value, gt)
end

raw_filter!{T}(n::Node{T}, keep::Set{T}) = raw_filter!(Set{Node{T}}(), n, keep)
function raw_filter!{T}(seen::Set{Node{T}}, node::Node{T}, keep::Set{T})
    if node in seen; return end
    push!(seen, node)
    node.gt = Set{Node{T}}(filter(node->(node.value in keep), node.gt))
    for child in node.gt;  raw_filter!(child, keep)  end
end

subDAGof{T}(node::Node{T}) = (sub = Set{Node{T}}(); addsubDAG!(sub, node); sub)
function addsubDAG!{T}(seen::Set{Node{T}}, node::Node{T})
    if node in seen; return; end
    push!(seen, node)
    for below in node.gt; addsubDAG!(seen, below); end
end

function ordered_subDAGof{T}(node::Node{T})
    seen, order = Set{Node{T}}(), Node{T}[]
    addsubDAG!(seen, order, node)
    order
end
function addsubDAG!{T}(seen::Set{Node{T}},order::Vector{Node{T}},node::Node{T})
    if node in seen; return; end
    push!(seen, node); push!(order, node)
    for below in node.gt; addsubDAG!(seen, order, below); end
end

insert!{T}(at::Node{T}, node::Node{T}) = insert!(Dict{Node{T},Bool}(), at, node)
function insert!{T}(seen::Dict{Node{T},Bool}, at::Node{T}, node::Node{T})
    if haskey(seen, at); return seen[at] end
    if node.value >= at.value
        if at.value >= node.value
            at.value = node.value  # at == node
            return seen[at] = true
        end
        # node > at
        push!(node.gt, at)
        at_above_node = false
    else
        at_above_node = any(Bool[insert!(seen, below, node) for below in at.gt])
    end
    if !at_above_node
        if at.value >= node.value
            setdiff!(at.gt, intersect(at.gt, node.gt))
            push!(at.gt, node)
            at_above_node = true
        end
    end
    seen[at] = at_above_node
end

end # module


#=
Taken from https://github.com/JuliaPlots/StatsMakie.jl/blob/master/src/typerecipes/distribution.jl
The StatMakie.jl package is licensed under the MIT "Expat" License:
    Copyright (c) 2018: Pietro Vertechi. =#

# pick a nice default x range given a distribution
function default_range(dist::Distribution, alpha=0.0001)
    minval = isfinite(minimum(dist)) ? minimum(dist) : quantile(dist, alpha)
    maxval = isfinite(maximum(dist)) ? maximum(dist) : quantile(dist, 1 - alpha)
    minval..maxval
end

isdiscrete(::Distribution) = false
isdiscrete(::Distribution{<:VariateForm,<:Discrete}) = true

support(dist::Distribution) = default_range(dist)
support(dist::Distribution{<:VariateForm,<:Discrete}) = UnitRange(endpoints(default_range(dist))...)

convert_arguments(P::PlotFunc, dist::Distribution) = convert_arguments(P, support(dist), dist)

function convert_arguments(P::PlotFunc, x::Union{Interval,AbstractVector}, dist::Distribution)
    default_ptype = isdiscrete(dist) ? ScatterLines : Lines
    ptype = plottype(P, default_ptype)
    to_plotspec(ptype, convert_arguments(ptype, x, x -> pdf(dist, x)))
end
# -----------------------------------------------------------------------------
# qqplots (M. K. Borregaard implementation from StatPlots)

"""
    qqplot(x, y; kwargs...)
Draw a Q-Q plot, comparing quantiles of two distributions. `y` must be a list of
samples, i.e., `AbstractVector{<:Real}`, whereas `x` can be
- a list of samples,
- an abstract distribution, e.g. `Normal(0, 1)`,
- a distribution type, e.g. `Normal`.
In the last case, the distribution type is fitted to the data `y`.

The attribute `qqline` (defaults to `:none`) determines how to compute a fit line for the Q-Q plot.
Possible values are the following.
- `:identity` draws the identity line.
- `:fit` computes a least squares line fit of the quantile pairs.
- `:fitrobust` computes the line that passes through the first and third quartiles of the distributions.
- `:none` omits drawing the line.
Broadly speaking, `qqline = :identity` is useful to see if `x` and `y` follow the same distribution,
whereas `qqline = :fit` and `qqline = :fitrobust` are useful to see if the distribution of `y` can be
obtained from the distribution of `x` via an affine transformation.

Graphical attributes are
- `color` to control color of both line and markers (if `markercolor` is not specified)
- `linestyle`
- `linewidth`
- `markercolor`
- `strokecolor`
- `strokewidth`
- `marker`
- `markersize`
"""
@recipe(QQPlot) do scene
    s_theme = default_theme(scene, Scatter)
    l_theme = default_theme(scene, Lines)
    Attributes(
        color = l_theme.color,
        linestyle = l_theme.linestyle,
        linewidth = l_theme.linewidth,
        markercolor = automatic,
        markersize = s_theme.markersize,
        strokecolor = s_theme.strokecolor,
        strokewidth = s_theme.strokewidth,
        marker = s_theme.marker,
        inspectable = theme(scene, :inspectable),
        cycle = [:color],
    )
end

"""
    qqnorm(y; kwargs...)

Shorthand for `qqplot(Normal(0,1), y)`, i.e., draw a Q-Q plot of `y` against the
standard normal distribution. See `qqplot` for more details.
"""
@recipe(QQNorm) do scene
    default_theme(scene, QQPlot)
end

# Compute points and line for the qqplot
function fit_qqplot(x, y; qqline = :none)
    if !(qqline in (:identity, :fit, :fitrobust, :none))
        msg = "valid values for qqline are :identity, :fit, :fitrobust or :none, " *
            "encountered " * repr(qqline)
        throw(ArgumentError(msg))
    end
    h = qqbuild(x, y)
    points = Point2f.(h.qx, h.qy)
    qqline == :none && return points, Point2f[]
    xs = collect(extrema(h.qx))
    if qqline == :identity
        ys = xs
    elseif qqline == :fit
        itc, slp = hcat(fill!(similar(h.qx), 1), h.qx) \ h.qy
        ys = @. slp * xs + itc
    else # if qqline == :fitrobust
        quantx, quanty = quantile(x, [0.25, 0.75]), quantile(y, [0.25, 0.75])
        slp = (quanty[2] - quanty[1]) / (quantx[2] - quantx[1])
        ys = @. quanty + slp * (xs - quantx)
    end
    return points, Point2f.(xs, ys)
end

# Fit distribution type, otherwise leave first argument unchanged
maybefit(D::Type{<:Distribution}, y) = Distributions.fit(D, y)
maybefit(x, _) = x

function convert_arguments(::Type{<:QQPlot}, x′, y; qqline = :none)
    x = maybefit(x′, y)
    points, line = fit_qqplot(x, y; qqline = qqline)
    return PlotSpec{QQPlot}(points, line)
end

convert_arguments(::Type{<:QQNorm}, y; qqline = :none) =
    convert_arguments(QQPlot, Distributions.Normal(0, 1), y; qqline = qqline)

used_attributes(::Type{<:QQNorm}, y) = (:qqline,)
used_attributes(::Type{<:QQPlot}, x, y) = (:qqline,)

function Makie.plot!(p::QQPlot)

    points, line = p[1], p[2]
    real_markercolor = Observable{RGBColors}()

    map!(real_markercolor, p.color, p.markercolor) do color, markercolor
        return to_color(markercolor === automatic ? color : markercolor)
    end

    scatter!(p, points;
        color = real_markercolor,
        strokecolor = p.strokecolor,
        strokewidth = p.strokewidth,
        marker = p.marker,
        markersize = p.markersize,
        inspectable = p.inspectable
    )
    linesegments!(p, line;
        color = p.color,
        linestyle = p.linestyle,
        linewidth = p.linewidth,
        inspectable = p.inspectable
    )
end


using Test
using StaticArrays
using RootedTrees

trees_array = (rootedtree([1,2,3]),
               rootedtree([1,2,3]),
               rootedtree([1,2,2]),
               rootedtree([1,2,3,3]))

for (t1,t2,t3,t4) in (trees_array,)
  @test t1 == t1
  @test t1 == t2
  @test !(t1 == t3)
  @test !(t1 == t4)

  @test t3 < t2    && t2 > t3
  @test !(t2 < t3) && !(t3 > t2)
  @test t1 < t4    && t4 > t1
  @test !(t4 < t1) && !(t1 > t4)
  @test t1 <= t2   && t2 >= t1
  @test t2 <= t2   && t2 >= t2

  println(devnull, t1)
  println(devnull, t2)
  println(devnull, t3)
  println(devnull, t4)
end

t = rootedtree([1])
@test order(t) == 1
@test σ(t) == 1
@test γ(t) == 1
@test α(t) == 1
@test β(t) == α(t)*γ(t)

t = rootedtree([1, 2])
@test order(t) == 2
@test σ(t) == 1
@test γ(t) == 2
@test α(t) == 1
@test β(t) == α(t)*γ(t)

t = rootedtree([1, 2, 2])
@test order(t) == 3
@test σ(t) == 2
@test γ(t) == 3
@test α(t) == 1
@test β(t) == α(t)*γ(t)

t = rootedtree([1, 2, 3])
@test order(t) == 3
@test σ(t) == 1
@test γ(t) == 6
@test α(t) == 1
@test β(t) == α(t)*γ(t)

t = rootedtree([1, 2, 2, 2])
@test order(t) == 4
@test σ(t) == 6
@test γ(t) == 4
@test α(t) == 1
@test β(t) == α(t)*γ(t)

t = rootedtree([1, 2, 2, 3])
@inferred RootedTrees.subtrees(t)
@test order(t) == 4
@test σ(t) == 1
@test γ(t) == 8
@test α(t) == 3
@test β(t) == α(t)*γ(t)

t = rootedtree([1, 2, 3, 3])
@test order(t) == 4
@test σ(t) == 2
@test γ(t) == 12
@test β(t) == α(t)*γ(t)

t = rootedtree([1, 2, 3, 4])
@test order(t) == 4
@test σ(t) == 1
@test γ(t) == 24
@test α(t) == 1

t = rootedtree([1, 2, 2, 2, 2])
@test order(t) == 5
@test σ(t) == 24
@test γ(t) == 5
@test α(t) == 1
@test β(t) == α(t)*γ(t)

t = rootedtree([1, 2, 2, 2, 3])
@test order(t) == 5
@test σ(t) == 2
@test γ(t) == 10
@test α(t) == 6
@test β(t) == α(t)*γ(t)

t = rootedtree([1, 2, 2, 3, 3])
@test order(t) == 5
@test σ(t) == 2
@test γ(t) == 15
@test α(t) == 4

t = rootedtree([1, 2, 2, 3, 4])
@test order(t) == 5
@test σ(t) == 1
@test γ(t) == 30
@test α(t) == 4
@test β(t) == α(t)*γ(t)

t = rootedtree([1, 2, 3, 2, 3])
@test order(t) == 5
@test σ(t) == 2
@test γ(t) == 20
@test α(t) == 3
@test β(t) == α(t)*γ(t)

t = rootedtree([1, 2, 3, 3, 3])
@test order(t) == 5
@test σ(t) == 6
@test γ(t) == 20
@test α(t) == 1
@test β(t) == α(t)*γ(t)

t = rootedtree([1, 2, 3, 3, 4])
@test order(t) == 5
@test σ(t) == 1
@test γ(t) == 40
@test α(t) == 3
@test β(t) == α(t)*γ(t)

t = rootedtree([1, 2, 3, 4, 4])
@test order(t) == 5
@test σ(t) == 2
@test γ(t) == 60
@test α(t) == 1
@test β(t) == α(t)*γ(t)

t = rootedtree([1, 2, 3, 4, 5])
@test order(t) == 5
@test σ(t) == 1
@test γ(t) == 120
@test α(t) == 1
@test β(t) == α(t)*γ(t)

# see butcher2008numerical, Table 302(I)
number_of_rooted_trees = [1, 1, 2, 4, 9, 20, 48, 115, 286, 719]
for order in 1:10
  num = 0
  for t in RootedTreeIterator(order)
    num += 1
  end
  @test num == number_of_rooted_trees[order] == count_trees(order)
end

# Runge-Kutta method SSPRK33 of order 3
A = [0 0 0; 1 0 0; 1/4 1/4 0]
b = [1/6, 1/6, 2/3]
c = A * fill(1, length(b))
for order in 1:3
  for t in RootedTreeIterator(order)
    @test residual_order_condition(t, A, b, c) ≈ 0 atol=eps()
  end
end
let order=4
  res = 0.
  for t in RootedTreeIterator(order)
    res += abs(residual_order_condition(t, A, b, c))
  end
  @test res > 10*eps()
end

A = @SArray [0 0 0; 1 0 0; 1/4 1/4 0]
b = @SArray [1/6, 1/6, 2/3]
c = A * SVector(1, 1, 1)
for order in 1:3
  for t in RootedTreeIterator(order)
    @test residual_order_condition(t, A, b, c) ≈ 0 atol=eps()
  end
end
let order=4
  res = 0.
  for t in RootedTreeIterator(order)
    res += abs(residual_order_condition(t, A, b, c))
  end
  @test res > 10*eps()
end


# ---
# title: 1438. Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit
# id: problem1438
# author: Qling
# date: 2021-02-21
# difficulty: Medium
# categories: Array, Sliding Window
# link: <https://leetcode.com/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/description/>
# hidden: true
# ---
#
# Given an array of integers `nums` and an integer `limit`, return the size of
# the longest **non-empty** subarray such that the absolute difference between
# any two elements of this subarray is less than or equal to `limit` _._
#
#
#
# **Example 1:**
#
#
#
#     Input: nums = [8,2,4,7], limit = 4
#     Output: 2
#     Explanation: All subarrays are:
#     [8] with maximum absolute diff |8-8| = 0 <= 4.
#     [8,2] with maximum absolute diff |8-2| = 6 > 4.
#     [8,2,4] with maximum absolute diff |8-2| = 6 > 4.
#     [8,2,4,7] with maximum absolute diff |8-2| = 6 > 4.
#     [2] with maximum absolute diff |2-2| = 0 <= 4.
#     [2,4] with maximum absolute diff |2-4| = 2 <= 4.
#     [2,4,7] with maximum absolute diff |2-7| = 5 > 4.
#     [4] with maximum absolute diff |4-4| = 0 <= 4.
#     [4,7] with maximum absolute diff |4-7| = 3 <= 4.
#     [7] with maximum absolute diff |7-7| = 0 <= 4.
#     Therefore, the size of the longest subarray is 2.
#
#
# **Example 2:**
#
#
#
#     Input: nums = [10,1,2,4,7,2], limit = 5
#     Output: 4
#     Explanation: The subarray [2,4,7,2] is the longest since the maximum absolute diff is |2-7| = 5 <= 5.
#
#
# **Example 3:**
#
#
#
#     Input: nums = [4,2,2,2,4,4,2,2], limit = 0
#     Output: 3
#
#
#
#
# **Constraints:**
#
#   * `1 <= nums.length <= 10^5`
#   * `1 <= nums[i] <= 10^9`
#   * `0 <= limit <= 10^9`
#
#
## @lc code=start

using LeetCode

function longest_subarray(nums::Vector{Int}, limit::Int)
    queMin, queMax = Deque{Int}(), Deque{Int}()
    n = length(nums)
    left = right = 1
    res = 0

    while right <= n
        while !isempty(queMin) && !isempty(queMax) && last(queMax) < nums[right]
            pop!(queMax)
        end
        while !isempty(queMin) && !isempty(queMax) && last(queMin) > nums[right]
            pop!(queMin)
        end
        push!(queMax, nums[right])
        push!(queMin, nums[right])

        while !isempty(queMin) && !isempty(queMax) && first(queMax) - first(queMin) > limit
            if nums[left] == first(queMin)
                popfirst!(queMin)
            end
            if nums[left] == first(queMax)
                popfirst!(queMax)
            end
            left += 1
        end

        res = max(res, right - left + 1)
        right += 1

    end

    return res
end
## @lc code=end


using Printf

function bench_time(fctn, times, name)
    real_time = @elapsed for i = 1 : times
	fctn()
    end
    return name, real_time, real_time, 0, 0
end

function run_(name, times, fctn)
    name, rt, ut, sys, consed = bench_time(fctn, times, name)
    @printf ";;; running #<benchmark %s for %s runs>\n" name times
    @printf "(\"%s\" %s %s %d %d)\n" name rt ut sys consed
end

const fib_iter = 25

function fact(n)
    if 0==n
        1
    else
        n * fact(n - 1)
    end
end

function run_factorial()
    fact(big(500))
end

function fib(n)
    if n < 2
        1
    else
        fib(n-1)+fib(n-2)
    end
end

function run_fib()
    fib(fib_iter)
end

function ackermann(m, n)
    if 0==m
        n+1
    elseif 0==n
        ackermann(m-1, 1)
    else
        ackermann(m-1, ackermann(m, n-1))
    end
end

function run_ackermann()
    ackermann(3, 11)
end

function tak(x, y, z)
    if !(y < x)
        z
    else
        tak(tak(x-1, y, z),
            tak(y-1, z, x),
            tak(z-1, x, y))
    end
end

function run_tak()
    tak(18, 12, 6)
end

function bench_1d_arrays(size, runs)
    ones = fill(UInt16(1), size)
    twos = fill(UInt16(2), size)
    threes = Vector{UInt16}(undef, size)
    for run = 1 : runs
	for pos = 1 : size
	    threes[pos] = ones[pos] + twos[pos]
	end
	for elt = threes
	    @assert elt == 3 @sprintf "The assertion %s == 3 failed." elt
        end
    end
end

function run_bench_1d_arrays()
    bench_1d_arrays(100000, 10)
end

function bench_2d_arrays(size, runs)
    ones = fill(UInt16(1), (size, size))
    twos = fill(UInt16(2), (size, size))
    threes = Array{UInt16, 2}(undef, (size, size))
    for run = 1 : runs
        for i = 1 : size
            for j = 1 : size
                threes[i, j] = ones[i, j] + twos[i, j]
            end
        end
        for i = 1 : size
            for j = 1 : size
                elt = threes[i, j]
                @assert elt == 3 @sprintf "The assertion %s == 3 failed." elt
            end
        end
    end
end

function run_bench_2d_arrays()
    bench_2d_arrays(2000, 10)
end

function fib_single_float(n)
    if n < Float32(2)
        Float32(1)
    else
	fib_single_float(n-Float32(1)) + fib_single_float(n-Float32(2))
    end
end

function run_fib_single_float()
    fib_single_float(Float32(fib_iter))
end

function fib_double_float(n)
    if n < Float64(2)
	Float64(1)
    else
        fib_double_float(n-Float64(1)) + fib_double_float(n-Float64(2))
    end
end

function run_fib_double_float()
    fib_double_float(Float64(fib_iter))
endn

function fib_ratio(n)
    function fr(n)
        if n == 1
            1
        else
            1 + Rational{BigInt}(1//fr(n-1))
        end
    end
    numerator(fr(n))
end

function run_fib_ratio()
    fib_ratio(150)
end

function listn(n)
    fill(0, n)
end

function shorterp(x, y)
    0<length(y) && (0==length(x) || shorterp(x[2:end], y[2:end]))
end

function mas(x, y, z)
    if !shorterp(y, x)
        z
    else
        mas(mas(x[2:end], y, z),
            mas(y[2:end], z, x),
            mas(z[2:end], x, y))
    end
end

function run_takl()
    l18 = listn(18)
    l12 = listn(12)
    l6 = listn(6)
    mas(l18, l12, l6)
end

function bench_string_concat(size, runs)
    for _ = 1 : runs
        buf = IOBuffer()
        for _ = 1 : size
            write(buf, "hi there!")
        end
        buflen = length(String(take!(buf)))
        # buflen = length(repeat("hi there!", size)) # much faster
        @assert buflen == length("hi there!")*size
                @sprintf "buflen:%s == size*runs:%s" buflen length("hi there!")*size
    end
end

function crc_division_step(bit, rmdr, poly, msb_mask)
    # Shift in the bit into the LSB of the register (rmdr)
    new_rmdr = Int64(bit | (rmdr * 2))
    # Divide by the polynomial, and return the new remainder
    if (0 == (msb_mask & new_rmdr))
        new_rmdr
    else
        xor(new_rmdr, poly)
    end
end

function integer_length(n)
    if n < 0
        n = -n
    else
        n += 1
    end
    ans = log2(n)
    Int64(ceil(ans))
end

function compute_adjustment(poly,n)
    # Precompute X^(n-1) mod poly
    poly_len_mask = 1 << (integer_length(poly) - 1)
    rmdr = crc_division_step(1, 0, poly, poly_len_mask)
    for k = 1 : n-1
        rmdr = crc_division_step(0, rmdr, poly, poly_len_mask)
    end
    rmdr
end

function calculate_crc40(iterations)
    crc_poly = 1099587256329
    len = 3014633
    answer = 0
    for k = 1 : iterations
        answer = compute_adjustment(crc_poly, len)
    end
  return answer;
end

function run_crc40()
    calculate_crc40(10) #549793628164
end

function main()
    @printf "\n(\"Julia %s\"\n" VERSION
    run_("CRC40", 2, run_crc40)
    run_("1D-ARRAYS", 1, run_bench_1d_arrays)
    run_("2D-ARRAYS", 1, run_bench_2d_arrays)
    run_("FIB", 50, run_fib)
    run_("FIB-RATIO", 500, run_fib_ratio)
    run_("FIB-SINGLE-FLOAT", 50, run_fib_single_float)
    run_("FIB-DOUBLE-FLOAT", 50, run_fib_double_float)
    run_("ACKERMANN", 1, run_ackermann)
    run_("TAK", 1000, run_tak)
    run_("TAKL", 150, run_takl)
    run_("FACTORIAL", 1000, run_factorial)
    run_("STRING-CONCAT", 1, (() -> bench_string_concat(1000000, 100)))
    @printf ")\n"
end

#main()


"""
    binary_tournament(population)
Apply binary tournament to obtain a solution from from population.
"""
function binary_tournament(population)
    a, b = rand(1:length(population), 2)

    return is_better(population[a], population[b]) ? population[a] : population[b]
end

function binary_tournament(population, fitness::Vector{T})  where T <: Real
    a, b = rand(1:length(population), 2)

    return fitness[a] < fitness[b] ? population[a] : population[b]
end


using ModelingToolkit
using Graphs
using DiffEqBase
using Test
using UnPack

# Define some variables
@parameters t L g
@variables x(t) y(t) w(t) z(t) T(t) xˍt(t) yˍt(t) xˍˍt(t) yˍˍt(t)
D = Differential(t)

eqs2 = [D(D(x)) ~ T * x,
    D(D(y)) ~ T * y - g,
    0 ~ x^2 + y^2 - L^2]
pendulum2 = ODESystem(eqs2, t, [x, y, T], [L, g], name = :pendulum)
lowered_sys = ModelingToolkit.ode_order_lowering(pendulum2)

lowered_eqs = [D(xˍt) ~ T * x,
    D(yˍt) ~ T * y - g,
    D(x) ~ xˍt,
    D(y) ~ yˍt,
    0 ~ x^2 + y^2 - L^2]
@test ODESystem(lowered_eqs, t, [xˍt, yˍt, x, y, T], [L, g], name = :pendulum) ==
      lowered_sys
@test isequal(equations(lowered_sys), lowered_eqs)

# Simple pendulum in cartesian coordinates
eqs = [D(x) ~ w,
    D(y) ~ z,
    D(w) ~ T * x,
    D(z) ~ T * y - g,
    0 ~ x^2 + y^2 - L^2]
pendulum = ODESystem(eqs, t, [x, y, w, z, T], [L, g], name = :pendulum)

state = TearingState(pendulum)
@unpack graph, var_to_diff = state.structure
@test StructuralTransformations.maximal_matching(graph, eq -> true,
                                                 v -> var_to_diff[v] === nothing) ==
      map(x -> x == 0 ? StructuralTransformations.unassigned : x,
          [1, 2, 3, 4, 0, 0, 0, 0, 0])

using ModelingToolkit
@parameters t L g
@variables x(t) y(t) w(t) z(t) T(t) xˍt(t) yˍt(t)
D = Differential(t)
idx1_pendulum = [D(x) ~ w,
    D(y) ~ z,
    #0 ~ x^2 + y^2 - L^2,
    D(w) ~ T * x,
    D(z) ~ T * y - g,
    # intermediate 1: 0 ~ 2x*D(x) + 2y*D(y) - 0,
    # intermediate 2(a): 0 ~ 2x*w + 2y*z - 0, (substitute D(x) and D(y))
    #0 ~ 2x*w + 2y*z,
    # D(D(x)) ~ D(w) and substitute the rhs
    D(xˍt) ~ T * x,
    # D(D(y)) ~ D(z) and substitute the rhs
    D(yˍt) ~ T * y - g,
    # 2x*D(D(x)) + 2*D(x)*D(x) + 2y*D(D(y)) + 2*D(y)*D(y) and
    # substitute the rhs
    0 ~ 2x * (T * x) + 2 * xˍt * xˍt + 2y * (T * y - g) + 2 * yˍt * yˍt]
@named idx1_pendulum = ODESystem(idx1_pendulum, t, [x, y, w, z, xˍt, yˍt, T], [L, g])
first_order_idx1_pendulum = ode_order_lowering(idx1_pendulum)

using OrdinaryDiffEq
using LinearAlgebra
prob = ODEProblem(ODEFunction(first_order_idx1_pendulum),
                  #  [x, y, w, z, xˍt, yˍt, T]
                  [1, 0, 0, 0, 0, 0, 0.0],# 0, 0, 0, 0],
                  (0, 10.0),
                  [1, 9.8],
                  mass_matrix = calculate_massmatrix(first_order_idx1_pendulum))
sol = solve(prob, Rodas5());
#plot(sol, vars=(1, 2))

new_sys = dae_index_lowering(ModelingToolkit.ode_order_lowering(pendulum2))

prob_auto = ODEProblem(new_sys,
                       [D(x) => 0,
                           D(y) => 0,
                           x => 1,
                           y => 0,
                           T => 0.0],
                       (0, 100.0),
                       [1, 9.8])
sol = solve(prob_auto, Rodas5());
#plot(sol, vars=(x, y))

# Define some variables
@parameters t L g
@variables x(t) y(t) T(t)
D = Differential(t)

eqs2 = [D(D(x)) ~ T * x,
    D(D(y)) ~ T * y - g,
    0 ~ x^2 + y^2 - L^2]
pendulum2 = ODESystem(eqs2, t, [x, y, T], [L, g], name = :pendulum)

# Turn into a first order differential equation system
first_order_sys = ModelingToolkit.ode_order_lowering(pendulum2)

# Perform index reduction to get an Index 1 DAE
new_sys = dae_index_lowering(first_order_sys)

u0 = [
    D(x) => 0.0,
    D(y) => 0.0,
    x => 1.0,
    y => 0.0,
    T => 0.0,
]

p = [
    L => 1.0,
    g => 9.8,
]

prob_auto = ODEProblem(new_sys, u0, (0.0, 10.0), p)
sol = solve(prob_auto, Rodas5());
#plot(sol, vars=(D(x), y))

let pss_pendulum2 = partial_state_selection(pendulum2)
    # This currently selects `T` rather than `x` at top level. Needs tearing priorities to fix.
    @test_broken length(equations(pss_pendulum2)) == 3
    @test_broken length(equations(ModelingToolkit.ode_order_lowering(pss_pendulum2))) == 4
end

eqs = [D(x) ~ w,
    D(y) ~ z,
    D(w) ~ T * x,
    D(z) ~ T * y - g,
    0 ~ x^2 + y^2 - L^2]
pendulum = ODESystem(eqs, t, [x, y, w, z, T], [L, g], name = :pendulum)

let pss_pendulum = partial_state_selection(pendulum)
    # This currently selects `T` rather than `x` at top level. Needs tearing priorities to fix.
    @test_broken length(equations(pss_pendulum)) == 3
end

sys = structural_simplify(pendulum2)
@test length(equations(sys)) == 5
@test length(states(sys)) == 5

u0 = [
    D(x) => 0.0,
    D(y) => 0.0,
    x => sqrt(2) / 2,
    y => sqrt(2) / 2,
    T => 0.0,
]
p = [
    L => 1.0,
    g => 9.8,
]

prob_auto = DAEProblem(sys, zeros(length(u0)), u0, (0.0, 0.2), p)
sol = solve(prob_auto, DFBDF())
@test norm(sol[x] .^ 2 + sol[y] .^ 2 .- 1) < 1e-2


module Judycon

"""
    QuickFind
"""
struct QuickFind
    id :: Vector{Int}
end

function QuickFind(n::Int)
    id = collect(1:n)
    return QuickFind(id)
end

function find(qf::QuickFind, p::Int)
    id = qf.id
    return id[p]
end

function isconnected(qf::QuickFind, p::Int, q::Int)
    i = find(qf, p)
    j = find(qf, q)
    return isequal(i, j)
end

function connect!(qf::QuickFind, p::Int, q::Int)
    id = qf.id
    pid = id[p]
    qid = id[q]
    n = length(id)
    for i=1:n
        if isequal(getindex(id, i), pid)
            setindex!(id, qid, i)
        end
    end
    return
end

"""
    QuickUnion
"""
struct QuickUnion
    id :: Vector{Int}
    sz :: Vector{Int}
    weighted :: Bool
    compress :: Bool
end

function QuickUnion(n::Int, weighted=true, compress=true)
    id = collect(1:n)
    sz = zeros(Int, n)
    return QuickUnion(id, sz, weighted, compress)
end

function find(qu::QuickUnion, p::Int)
    id = qu.id
    while p != id[p]
        if qu.compress
            id[p] = id[id[p]]
        end
        p = id[p]
    end
    return p
end

function isconnected(qu::QuickUnion, p::Int, q::Int)
    i = find(qu, p)
    j = find(qu, q)
    return isequal(i, j)
end

"""
    connect(G, p, q)

Connect p and q in G.

If weighting is used, connect such a way that the depth of the tree is minimized.
"""
function connect!(qu::QuickUnion, p::Int, q::Int)
    id = qu.id
    i = find(qu, p)
    j = find(qu, q)
    if qu.weighted
        sz = qu.sz
        if (sz[i] < sz[j])
            id[i] = j
            sz[j] += sz[i]
        else
            id[j] = i
            sz[i] += sz[j]
        end
    else
        id[i] = j
    end
end

export QuickFind, QuickUnion, connect!, isconnected, find

end


# ---
# title: 423. Reconstruct Original Digits from English
# id: problem423
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Math
# link: <https://leetcode.com/problems/reconstruct-original-digits-from-english/description/>
# hidden: true
# ---
#
# Given a **non-empty** string containing an out-of-order English representation
# of digits `0-9`, output the digits in ascending order.
#
# **Note:**
#
#   1. Input contains only lowercase English letters.
#   2. Input is guaranteed to be valid and can be transformed to its original digits. That means invalid inputs such as "abc" or "zerone" are not permitted.
#   3. Input length is less than 50,000.
#
# **Example 1:**
#
#
#
#     Input: "owoztneoer"
#
#     Output: "012"
#
#
# **Example 2:**
#
#
#
#     Input: "fviefuro"
#
#     Output: "45"
#
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


#test fast transform via mapped Chebyschev
using ApproxFun, FastTransforms, BenchmarkTools

import FastTransforms: Butterfly

M=128

# Get the actual matrix from the plan in ApproxFun.plan_transform(::Hermite(),rand(256)), say.

PlanHerm = ApproxFun.plan_transform(Hermite(),rand(M));
x,w = PlanHerm.plan;

V=ApproxFun.hermitep(0:M-1,x)';
nrm=(V.^2)*w;
P= Diagonal(inv.(nrm))*V*Diagonal(w)

#P=Q[1:N,:]
BF = Butterfly(P, floor(Int, log2(size(P, 1))-6); sketch = :none, atol = 1e-14);

r = FastTransforms.allranks(BF) # just diagnostic info.

mean(r), std(r)
y = zeros(M)+im*zeros(M)
x = randn(M)+im*randn(M);
zr = zeros(M);
zi = zeros(M)
z = zeros(x)
@time A_mul_B!(y, P, x)

@time A_mul_B!(zr, BF, real(x)); A_mul_B!(zi, BF, imag(x));z=zr+im*zi

norm(y-z) # Did it work?

#test for transform to quadrature grid
N=256
x,wx,Tx = nfieldtrans("Hermite",N,2)

BFT = Butterfly(Tx, floor(Int, log2(size(Tx, 1))-6); sketch = :none, atol = 1e-32)

r = FastTransforms.allranks(BFT) # just diagnostic info.

mean(r), std(r)

M=N
x0 = randn(N)
x = randn(N)+im*randn(N)
x1 = randn(M)+im*randn(M)
y = randn(M)
zr = randn(N)
zi = randn(N)
z = randn(N)+im*randn(N)

@time A_mul_B!(x1, Tx, x)

@time A_mul_B!(zr, BFT, real(x)); A_mul_B!(zi, BFT, imag(x));z=zr+im*zi

norm(x-z) # Did it work?


import ApproxFun: dotu

# This solves via forward substitution
function forwardsubstitution!(ret,B,μ1,μ2,filter=identity)
    n = length(ret)
    if n≥1
        ret[1] = filter(μ1)
    end
    if n≥2
        ret[2] = filter(μ2)
    end
    if n≥3
        @inbounds for k=2:n-1
            ret[k+1] = filter(-(B[k,k-1]*ret[k-1]+B[k,k]*ret[k])/B[k,k+1])
        end
    end
    ret
end

forwardsubstitution(R,n,μ1,μ2) =
    forwardsubstitution!(Array{promote_type(eltype(R),typeof(μ1),typeof(μ2))}(undef,n),R,μ1,μ2)

# This solves as a boundary value provblem
stieltjesbackward(S::Space,z::Number) = JacobiZ(S,z)\[stieltjesmoment(S,0,z)]

stieltjesbackward!(ret,S::Space,z::Number) = ret[:] = stieltjesbackward(S,z)[1:length(ret)]

stieltjesforward(sp::Space,n,z) = forwardsubstitution(JacobiZ(sp,undirected(z)),n,
                                                      stieltjesmoment(sp,0,z),
                                                      stieltjesmoment(sp,1,z))

stieltjesforward!(ret,sp::Space,z,filter=identity) = forwardsubstitution!(ret,JacobiZ(sp,undirected(z)),
                                                    stieltjesmoment(sp,0,z),
                                                    stieltjesmoment(sp,1,z),filter)

hilbertforward(sp::Space,n,z) = forwardsubstitution(JacobiZ(sp,z),n,
                                                    hilbertmoment(sp,0,z),
                                                    hilbertmoment(sp,1,z))

function stieltjesmoment!(ret,S::PolynomialSpace{<:IntervalOrSegment},z,filter=identity)
    if domain(S) == ChebyshevInterval()
        n = length(ret)
        tol = 1/floor(Int,sqrt(n))
        if (abs(real(z)) ≤ 1+tol) && (abs(imag(z)) ≤ tol)
            cfs = stieltjesforward!(ret,S,z,filter)
        else
            cfs = stieltjesbackward!(ret,S,z)
        end

        ret
    else
        stieltjesmoment!(ret,setdomain(S,ChebyshevInterval()),mobius(S,z),filter)
    end
end

function stieltjesintervalrecurrence(S,f::AbstractVector,z)
    tol=1/floor(Int,sqrt(length(f)))
    if isinf(z)
        zero(promote_type(typeof(z), eltype(f)))
    elseif (abs(real(z)) ≤ 1+tol) && (abs(imag(z)) ≤ tol)
        cfs = stieltjesforward(S,length(f),z)
        dotu(cfs,f)
    else
        cfs = stieltjesbackward(S,undirected(z))
        dotu(cfs,f)
    end
end

stieltjesintervalrecurrence(S,f::AbstractVector,z::AbstractArray) =
    reshape(promote_type(eltype(f),eltype(z))[ stieltjesintervalrecurrence(S,f,z[i]) for i in eachindex(z) ], size(z))

function stieltjes(S::PolynomialSpace{<:IntervalOrSegment},f,z::Number)
    if domain(S)==ChebyshevInterval()
        #TODO: check tolerance
        stieltjesintervalrecurrence(Legendre(),coefficients(f,S,Legendre()),z)
    else
        stieltjes(setdomain(S,ChebyshevInterval()),f,mobius(S,z))
    end
end

function hilbert(S::PolynomialSpace{<:IntervalOrSegment},f,z::Number)
    if domain(S) == ChebyshevInterval()
        L = Legendre(domain(S))
        cfs = hilbertforward(L,length(f),z)
        dotu(cfs,coefficients(f, S, L))
    else
        hilbert(setdomain(S,ChebyshevInterval()),f,mobius(S,z))
    end
end

# Sum over all inverses of fromcanonical, see [Olver,2014]
function stieltjes(S::Space{<:Line},f,z)
    if domain(S)==Line()
        # TODO: rename tocanonical
        stieltjes(setcanonicaldomain(S),f,tocanonical(S,z)) +
            stieltjes(setcanonicaldomain(S),f,(-1-sqrt(1+4z^2))/(2z))
    else
        stieltjes(setdomain(S,Line()),f,mappoint(domain(S),Line(),z))
    end
end

## log kernel

function logkernel(S::PolynomialSpace{<:IntervalOrSegment},v,z::Number)
    if domain(S) == ChebyshevInterval()
        DS=JacobiWeight(1,1,Jacobi(1,1))
        D=Derivative(DS)[2:end,:]

        f=Fun(Fun(S,v),Legendre())  # convert to Legendre expansion
        u=D\(f|(2:∞))   # find integral, dropping first coefficient of f

        (f.coefficients[1]*logabslegendremoment(z) +
            real(stieltjes(Fun(u,Legendre()),z+0im)))/π
    else
        Mp=abs(fromcanonicalD(S,0))
        Mp*logkernel(setcanonicaldomain(S),v,mobius(S,z)) +
            linesum(Fun(S,v))*log(Mp)/π
    end
end


@testset "Square root Augmented Unscented Kalman filter" begin
    @testset "time update" begin
        x = randn(6)
        A = randn(6,6)
        P = A'A
        P_chol = cholesky(P)
        B = randn(6,6)
        Q = B'B
        Q_chol = cholesky(Q)
        F = randn(6,6)
        f(x) = F * x
        f(x, noise) = F * x .+ noise

        tu = time_update(x, P, F, Q)
        tu_chol = time_update(x, P_chol, f, Augment(Q_chol))
        @test get_covariance(tu_chol) ≈ get_covariance(tu)
        @test get_state(tu_chol) ≈ get_state(tu)

        f!(y, x) = mul!(y, F, x)
        f!(y, x, noise) = y.= @~ F * x .+ noise
        tu_inter = SRAUKFTUIntermediate(6)
        tu_aug_inplace = time_update!(tu_inter, x, P_chol, f!, Augment(Q_chol))
        @test get_covariance(tu_aug_inplace) ≈ get_covariance(tu)
        @test get_state(tu_aug_inplace) ≈ get_state(tu)
    end

    @testset "measurement update" begin
        x = randn(6)
        A = randn(6,6)
        P = A'A
        P_chol = cholesky(P)
        B = randn(3,3)
        R = B'B
        y = randn(3)
        R_chol = cholesky(R)
        H = randn(3,6)
        h(x) = H * x
        h(x, noise) = H * x .+ noise

        mu = measurement_update(x, P, y, H, R)
        mu_chol = @inferred measurement_update(x, P_chol, y, h, Augment(R_chol))
        @test get_covariance(mu) ≈ get_covariance(mu_chol)
        @test get_state(mu) ≈ get_state(mu_chol)

        h!(y, x) = mul!(y, H, x)
        h!(y, x, noise) = y .= @~ H * x .+ noise
        mu_inter = @inferred SRAUKFMUIntermediate(6, 3)
        mu_ukf_inplace = measurement_update!(mu_inter, x, P_chol, y, h!, Augment(R_chol))
        @test get_covariance(mu_ukf_inplace) ≈ get_covariance(mu)
        @test get_state(mu_ukf_inplace) ≈ get_state(mu)
    end
end


################################################################################
#
#  Imports
#
################################################################################

import AbstractAlgebra

################################################################################
#
#  Factorization Methods
#
################################################################################

# Square-free Factorization
function factor_sff(f::AbstractAlgebra.Generic.Poly{AbstractAlgebra.gfelem{Int32}})

    # Is monic?

    R = 1
    C = AbstractAlgebra.gcd(f, f')
    w = div(f, C)
end

# Distinct Degree Factorization
function factor_ddf(f::AbstractAlgebra.Generic.Poly{AbstractAlgebra.gfelem{Int32}})

    # Is square-free?

    i = 1
    k = AbstractAlgebra.base_ring(f)

    S, x = AbstractAlgebra.PolynomialRing(k, "x", cached = false)

    fstar = f

    while AbstractAlgebra.degree(fstar) >= 2*i
        r = x^(AbstractAlgebra.characteristic(k))^i - x
        g = AbstractAlgebra.gcd(fstar, r)
        if g != 1
            S = push!(S, (g, i))
            fstar = AbstractAlgebra.divexact(fstar, g)
        end
    end

    if fstar != 1
        S = push!(S, (fstar, AbstractAlgebra.degree(fstar)))
    elseif S == []
        return [(f, 1)]
    else
        return S
    end
end


@export function E_Gd_L1L2(Gd_spins, L1, L2, J1, J2)
    return   Gd_spins[1].v ⋅ ( J2 * L1 + J1 * L2) +
             Gd_spins[2].v ⋅ ( J1 * L1 + J2 * L2) +
             Gd_spins[3].v ⋅ (-J1 * L1 + J2 * L2) +
             Gd_spins[4].v ⋅ (-J2 * L1 + J1 * L2) +
             Gd_spins[5].v ⋅ (-J2 * L1 - J1 * L2) +
             Gd_spins[6].v ⋅ (-J1 * L1 - J2 * L2) +
             Gd_spins[7].v ⋅ ( J1 * L1 - J2 * L2) +
             Gd_spins[8].v ⋅ ( J2 * L1 - J1 * L2)
end

#Gd under magnetic field
@export E_Gd_H(Gd_spins, H, χ) = χ*mapreduce(x ->  x.v⋅H, -, Gd_spins, init=0.0)

#L1 & L2 under magnetic field
@export E_L_H(L, H, χ) = 0.5χ * (L⋅H)^2

#L1 L2 interaction
@export E_L1L2(L1, L2, γ) = 0.5γ * (L1⋅L2)^2

#L1 & L2 interaction with their easy axis
@export E_L_easy(L, easy, K_L) = - 0.5K_L * (L⋅easy)^2

#Gd interaction with their easy axis
@export function E_Gd_easy(Gd_spins, easy, K_Gd )
    out = 0.0
    for i in eachindex(Gd_spins)
        out -= (Gd_spins[i] ⋅ easy[i])^2
    end
    return K_Gd * out
end

@export Landau_F(ϕ, H; α=10, β=1, Hc=1) = α*(H-Hc) * cos(ϕ-π/4)^2 + sin(2ϕ)^2 + β*(H-Hc)*sin(ϕ)^2


module StateVectorTests

using Test
using PLext

@testset "StateVector Tests" begin
    @testset "Initialize test" begin
        sv = PLext.StateVector(4)
        @test length(sv.data) == 16
        PLext.init_state!(sv)
        @test sv.data[1] == 1+0im
        @test sv.data[2:end] == zeros(15).+0im
    end

    @testset "PauliX test" begin
        sv = PLext.StateVector(1)
        PLext.init_state!(sv)
        PLext.applyPauliX!(sv,1, false)
        @test sv[2] == 1 + 0im
        PLext.applyPauliX!(sv,1)
        @test sv[1] == 1 + 0im
        @test sv[2] == 0 + 0im
    end
    @testset "Hadamard test" begin
        sv = PLext.StateVector(2)
        PLext.init_state!(sv)
        PLext.applyHadamard!(sv,1)
        @test isapprox(view(sv.data, [1,3]), repeat([1/sqrt(2)], 2))
        @test isapprox(view(sv.data, [2,4]), repeat([0], 2))

        PLext.applyHadamard!(sv,2)
        @test isapprox(sv.data, repeat([1/2], 4))
        PLext.applyHadamard!(sv,1)

        @test isapprox(view(sv.data, [1,2]), repeat([1/sqrt(2)], 2))
        @test isapprox(view(sv.data, [3,4]), repeat([0], 2))

        PLext.applyHadamard!(sv,2)
        @test isapprox(sv.data[1], 1+0im)

        @test isapprox(sv.data[1], 1)
        @test isapprox(view(sv.data, 2:4), repeat([0], 3))
    end

end

end


# numerically integrate Mₙ(k²)/(4br)ᵐ
function Mn_num(k2::T, m::Int64) where T
    f(x) = sqrt(k2 - sin(x)^2)^m
    if k2 < 1
        kap2 = T(asin(sqrt(big(k2))))
    else
        kap2 = 0.5 * π
    end
    Mn, err = quadgk(f, -kap2, kap2, rtol=1e-15)
    return Mn
end

function test_Mn(r::T, b::T) where T
    n_max = 30
    u = ones(n_max + 2)
    prec_frac = zeros(n_max + 3)
    prec_abs = zeros(n_max + 3)

    ld = PolynomialLimbDark(u)
    ld_b = PolynomialLimbDark(big.(u))

    t = ld(b, r)
    t_b = ld_b(big(b), big(r))

    onembmr2 = (r + 1 - b) * (1 - r + b)
    k2 = max(0, onembmr2 / (4 * b * r))

    sqbr = sqrt(b * r)

    color = ones(Int, n_max + 3)
    for m in 0:ld.n_max
        Mnn = Mn_num(k2, m) * (2 * sqbr)^m
        Mn = ld.Mn[m + 1]
        Mn_b = Float64(ld_b.Mn[m + 1])
        t1 = isapprox(Mnn, Mn; atol=1e-15, rtol=1e-6)
        t2 = isapprox(Mn, Mn_b; atol=1e-15, rtol=1e-6)
        if !t1
            diff = Mnn - Mn
            @warn "Mn discrepancy" b r k2 Mnn Mn diff
        end
        if !t2
            diff = Mn - Mn_b
            @warn "Mn discrepancy" b r k2 Mn Mn_b diff
        end
        @test t1
        @test t2
        t1 || t2 && (color[begin + m] = 2)
        prec_frac[begin + m] = abs(Mn / Mn_b - 1)
        prec_abs[begin + m] = abs(asinh(Mn) - asinh(Mn_b))
    end
    PLOT && mnerror(prec_frac, prec_abs, c=color, title="r=$r, b=$b")
    PLOT && savefig(plotsdir("Mn_error_r=$(r)_b=$b.png"))
    return nothing
end

@testset "Mn compute" begin
    r = 100.0
    ϵ = 1e-8
    bs = [r - 1 + ϵ, r, r + 1 - ϵ]

    test_Mn.(r, bs)

    r = 0.01
    # points close to boundary
    bs = [ϵ, r, 1 - r - ϵ, 1 - r + ϵ, 1, r + 1 - ϵ]
    test_Mn.(r, bs)

    # random points
    ntest = 10
    for i in 1:ntest
        r = 2.0
        b = 0.25
        while r > 1 + b || r < b - 1
            r = 2 * rand(rng)
            b = 2 * rand(rng)
        end
        test_Mn(r, b)
    end
end

@testset "Mn raise" begin
    nothing
end


module SpecialFunctions

using OpenSpecFun_jll

export
    airyai,
    airyaiprime,
    airybi,
    airybiprime,
    airyaix,
    airyaiprimex,
    airybix,
    airybiprimex,
    besselh,
    besselhx,
    besseli,
    besselix,
    besselj,
    sphericalbesselj,
    besselj0,
    besselj1,
    besseljx,
    besselk,
    besselkx,
    bessely,
    sphericalbessely,
    bessely0,
    bessely1,
    besselyx,
    dawson,
    ellipk,
    ellipe,
    erf,
    erfc,
    erfcinv,
    erfcx,
    erfi,
    erfinv,
    logerfc,
    logerfcx,
    eta,
    digamma,
    invdigamma,
    polygamma,
    trigamma,
    gamma_inc,
    beta_inc,
    beta_inc_inv,
    gamma_inc_inv,
    ncbeta,
    ncF,
    hankelh1,
    hankelh1x,
    hankelh2,
    hankelh2x,
    zeta,
    sinint,
    cosint,
    lbinomial

include("bessel.jl")
include("erf.jl")
include("ellip.jl")
include("sincosint.jl")
include("gamma.jl")
include("gamma_inc.jl")
include("betanc.jl")
include("beta_inc.jl")
include("deprecated.jl")

for f in (:digamma, :erf, :erfc, :erfcinv, :erfcx, :erfi, :erfinv, :logerfc, :logerfcx,
          :eta, :gamma, :invdigamma, :logfactorial, :lgamma, :trigamma, :ellipk, :ellipe)
    @eval $(f)(::Missing) = missing
end
for f in (:beta, :lbeta)
    @eval $(f)(::Number, ::Missing) = missing
    @eval $(f)(::Missing, ::Number) = missing
    @eval $(f)(::Missing, ::Missing) = missing
end
polygamma(m::Integer, x::Missing) = missing

end # module


struct SOL

    inner_boundary :: Vector{Complex{Float64}}
    outer_boundary :: Vector{Complex{Float64}}
    psi :: Function
    iota :: Function
    bvec :: Function

end

function SOL(Bpol, Bz, Nc, r0, Nr, Nt, Nz, ds, divs::Matrix{Float64})

    # compute inner and outer boundaries
    outer_chain = Complex{Float64}[]
    inner_chain = Vector{Complex{Float64}}(undef, Nc)
    thetas = LinRange(0, 2*pi, Nc+1)[1:Nc]
    for i=1:Nc
        if !any([divs[1,j] < thetas[i] < divs[2,j] for j=1:size(divs)[2]])
            outer_chain = [outer_chain; [0.95*exp(im*thetas[i])]]
        end
        inner_chain[i] = r0*exp(im*thetas[i])
    end

    # compute boundary value for psi
    psib = Vector{Float64}(undef, 1001)
    ts = LinRange(0, 2*pi, 1002)[1:1001]
    for i=1:1001
        for j=1:size(divs)[2]
            if divs[1,j] < ts[i] < divs[2,j]
                u = abs(2*(ts[i] - (divs[1,j] + divs[2,j])/2) / (divs[2,j] - divs[1,j]))
                psib[i] = divs[3,j]*(1 - u)
            else
                psib[i] = 0.0
            end
        end
    end

    # find fourier coefficients for flux function
    C = fft(psib)
    c0 = real(C[1]) / 1001
    a = 2*real.(C[2:501]) / 1001
    b = -2*imag.(C[2:501]) / 1001

    # define flux function
    psi(z) = begin
        r = abs(z)
        t = angle(z)

        if r < 0.9
            kmax = minimum([500, abs(Int64(ceil(8 / log10(r/0.9))))])
            c0 + Bpol*log(r/0.9) + sum([(r/0.9)^k * (a[k]*cos(k*t) + b[k]*sin(k*t)) for k=1:kmax])
        else
            c0 + Bpol*log(r/0.9) + sum([(r/0.9)^(-k) * (a[k]*cos(k*t) + b[k]*sin(k*t)) for k=1:500])
        end
    end

    # define poloidal and total magnetic field
    gradPsi(r) = ForwardDiff.gradient(r -> psi(r[1] + im*r[2]), r)
    Bp(r) = begin dpsi = gradPsi(r); [-dpsi[2], dpsi[1]] end
    B(r) = [Bp(r)..., Bz]
    bvec(r) = begin Bvec = B(r); Bvec / norm(Bvec) end

    # define 1/q
    iota(z) = 2*pi*norm(Bp([real(z), imag(z)])) / Bz

    return SOL(inner_chain, outer_chain, psi, iota, bvec)
end

function fluxmap(z::ComplexF64, dPhi::Float64, ds::Float64, sol::SOL)

    points, dS = trace_fieldline([real(z), imag(z), 0.0], sol.bvec, r -> sol.psi(r[1] + im*r[2]), ds) do r
        abs(r[3]) > dPhi
    end
    z_final = Complex(points[1:2,end]...)

    return z_final, dS
end


# https://arxiv.org/abs/1511.05286
struct SegmentedLSE{T, U} <: AggregationFunction
    ρ::T
    C::U
end

Flux.@functor SegmentedLSE

SegmentedLSE(d::Int) = SegmentedLSE(randn(Float32, d), zeros(Float32, d))

r_map(ρ) = softplus.(ρ)
inv_r_map = (r) -> max.(r, 0) .+ log1p.(-exp.(-abs.(r)))

(m::SegmentedLSE)(x::MaybeMatrix, bags::AbstractBags, w=nothing) = segmented_lse_forw(x, m.C, r_map(m.ρ), bags)
function (m::SegmentedLSE)(x::AbstractMatrix, bags::AbstractBags, w::AggregationWeights, mask::AbstractVector)
    segmented_lse_forw(x .+ typemin(T) * mask', m.C, r_map(m.ρ), bags)
end

function _lse_precomp(x::AbstractMatrix, r, bags)
    M = zeros(eltype(x), length(r), length(bags))
    @inbounds for (bi, b) in enumerate(bags)
        if !isempty(b)
            for i in eachindex(r)
                M[i, bi] = r[i] * x[i, first(b)]
            end
            for j in b[2:end]
                for i in eachindex(r)
                    M[i, bi] = max(M[i, bi], r[i] * x[i, j])
                end
            end
        end
    end
    M
end

function _segmented_lse_norm(x::AbstractMatrix, C, r, bags::AbstractBags, M)
    y = zeros(eltype(x), length(r), length(bags))
    @inbounds for (bi, b) in enumerate(bags)
        if isempty(b)
            for i in eachindex(C)
                y[i, bi] = C[i]
            end
        else
            for j in b
                for i in eachindex(r)
                    y[i, bi] += exp.(r[i] * x[i, j] - M[i, bi])
                end
            end
            for i in eachindex(r)
                y[i, bi] = (log(y[i, bi]) - log(length(b)) + M[i, bi]) / r[i]
            end
        end
    end
    y
end

segmented_lse_forw(::Missing, C::AbstractVector, r, bags::AbstractBags) = repeat(C, 1, length(bags))
function segmented_lse_forw(x::AbstractMatrix, C, r, bags::AbstractBags)
    M = _lse_precomp(x, r, bags)
    _segmented_lse_norm(x, C, r, bags, M)
end

function segmented_lse_back(Δ, y, x, C, r, bags, M)
    dx = zero(x)
    dC = zero(C)
    dr = zero(r)
    s1 = zeros(eltype(x), length(r))
    s2 = zeros(eltype(x), length(r))

    @inbounds for (bi, b) in enumerate(bags)
        if isempty(b)
            for i in eachindex(C)
                dC[i] += Δ[i, bi]
            end
        else
            for i in eachindex(r)
                s1[i] = s2[i] = 0
            end
            for j in b
                for i in eachindex(r)
                    e = exp(r[i] * x[i, j] - M[i, bi])
                    s1[i] += e
                    s2[i] += x[i, j] * e
                end
            end
            for j in b
                for i in eachindex(r)
                    dx[i, j] = Δ[i, bi] * exp(r[i] * x[i, j] - M[i, bi]) / s1[i]
                end
            end
            for i in eachindex(r)
                dr[i] += Δ[i, bi] * (s2[i]/s1[i] - y[i, bi]) / r[i]
            end
        end
    end
    dx, dC, dr, nothing, nothing
end

function segmented_lse_back(Δ, ::Missing, C, bags)
    dC = zero(C)
    @inbounds for (bi, b) in enumerate(bags)
        for i in eachindex(C)
            dC[i] += Δ[i, bi]
        end
    end
    nothing, dC, nothing, nothing, nothing
end

@adjoint function segmented_lse_forw(x::AbstractMatrix, C::AbstractVector, r::AbstractVector, bags::AbstractBags)
    M = _lse_precomp(x, r, bags)
    y = _segmented_lse_norm(x, C, r, bags, M)
    grad = Δ -> segmented_lse_back(Δ, y, x, C, r, bags, M)
    y, grad
end

@adjoint function segmented_lse_forw(x::Missing, C::AbstractVector, r::AbstractVector, bags::AbstractBags)
    y = segmented_lse_forw(x, C, r, bags)
    grad = Δ -> segmented_lse_back(Δ, x, C, bags)
    y, grad
end


function soi_distance(t, orb::Orbit, bd::CelestialBody)
    if orb.primary != bd.orbit.primary
        throw(ArgumentError("orbit and body do not share a primary body"))
    end
    return norm(time_orbital_position(t, orb) - time_orbital_position(t, bd.orbit)) - bd.SoI
end

"""
    nextorb = propagate_across_soi(t, orb)

Searches for SoI changes starting at time `t`, and returns the orbit after the SoI
patch if one occurs.

For periodic (elliptical) orbits, the search begins at time `t` and ends at a full period
beyond time `t`. If no SoI change occurs, the original orbit is returned.

For hyperbolic orbits, the search begins at time `t` and ends at the time of SoI ejection.
If the orbit's primary body has no SoI (i.e. it is a Star), the search ends at the time when
the orbit is gauranteed to have passed beyond the outermost satellite of the star.
"""
function propagate_across_soi(t, orb::Orbit)
    if orb.e < 1
        endtime = orb.period
    else
        if typeof(orb.primary) == Star
            f_sat = max(orb.primary.satellite_bodies)
            endtime = true_to_time(orbital_angle(f_sat.a*(1+f_sat.e)+f_sat.SoI, orb), orb)
        else
            endtime = ejection_time(orb)
        end
    end

    intersects = Tuple{CelestialBody, Float64}[]
    for bd in orb.primary.satellite_bodies
        #TO DO: ensure that earliest intersection is found, if multiple exist
        obj_fun(t) = soi_distance(t, orb, bd)
        res = optimize(obj_fun, t, endtime, Brent())
        if Optim.minimum(res) < 0
            push!(intersects, (bd, Optim.minimizer(res)))
            endtime = Optim.minimizer(res)
        end
    end

    if !isempty(intersects)
        intsct_bd, mindist_time = intersects[end]
        obj_fun2(t) = abs(soi_distance(t, orb, intsct_bd))
        res2 = optimize(obj_fun2, t, mindist_time, Brent())
        intsct_time = Optim.minimizer(res2)
        r̄ = time_orbital_position(intsct_time, orb) - time_orbital_position(intsct_time, intsct_bd.orbit)
        v̄ = time_orbital_velocity(intsct_time, orb) - time_orbital_velocity(intsct_time, intsct_bd.orbit)
        @show r̄, v̄, intsct_time
    elseif orb.e > 1
        intsct_bd = orb.primary.orbit.primary
        intsct_time = endtime
        r̄ = time_orbital_position(intsct_time, orb) + time_orbital_position(intsct_time, orb.primary.orbit)
        v̄ = time_orbital_velocity(intsct_time, orb) + time_orbital_velocity(intsct_time, orb.primary.orbit)
    else
        return orb
    end
    return Orbit(intsct_time, r̄, v̄, intsct_bd)
end


using GLM, CSV, DataFrames, PyCall, PyPlot
PyDict(matplotlib."rcParams")["axes.formatter.use_locale"] = true
ihd17r = CSV.File("data/IHME-GBD_2017_DATA-f3f91654-1.csv") |> DataFrame
ihd17paf = CSV.File("data/IHME-GBD_2017_DATA-cba77f73-1.csv") |> DataFrame
gbd17iso3166_ctries = CSV.File("data/gbd17iso3166_ctries.csv") |> DataFrame
ihd17r[:lrate] = log.(ihd17r[:val] ./ 10^5)
ihd17paf[:lnegpaf] = log.(1 .- ihd17paf[:val])
sexes = ["män", "kvinnor"]

function ihd17sex(s)
	r_sex = ihd17r[ihd17r[:sex].==s, :]
	paf_sex = ihd17paf[ihd17paf[:sex].==s, :]
	r_sex_sub = DataFrame(location = r_sex[:location], lrate = r_sex[:lrate])
	paf_sex_sub = DataFrame(location = paf_sex[:location], lnegpaf = paf_sex[:lnegpaf])
    rpaf = join(r_sex_sub, paf_sex_sub, on = :location)
	Dict(:sex => s, :df => join(rpaf, gbd17iso3166_ctries, on = :location))
end

function pafrate_ols(sdict)
	ols = lm(@formula(lrate~lnegpaf), sdict[:df])
	Dict(:ols => ols, :sdict => sdict)
end

function poplot(podict)
	close("all")
	df = podict[:sdict][:df]
	ols = podict[:ols]
	co1lab = round(coef(ols)[1], digits=3)
	co2lab = round(coef(ols)[2]*-1, digits=3)
	reglab = latexstring(replace("$co1lab-$co2lab x", "."=>"{,}"))
	plot(df[:lnegpaf], df[:lrate], ".")
	plot(df[:lnegpaf], predict(ols), "-", label = reglab)
	for(i, iso3166) in enumerate(df[:iso3166])
		annotate(iso3166, (df[:lnegpaf][i], df[:lrate][i]))
	end
	grid(1)
	legend(loc=3)
	title("Ischemisk hjärtsjukdom 2017 50\u201369 $(sexes[podict[:sdict][:sex]])")
	xlabel("log(1\u2212PAF)")
	ylabel("log(dödstal)")
end