"""
Lightweight 3D attitude representation module.

Defines the abstract type `Abstract3DRotation`, and implements four concrete subtypes:
- Unit quaternion (`RQuat`)
- Rotation matrix (`RMatrix`)
- Axis-angle (`RAxAng`)
- Euler angles (`REuler`)

All of them support the essential attitude operations used in this package:
- Equality (`==`) and approximate equality (`≈`)
- Inversion (`r'`)
- Composition (`r1 ∘ r2`)
- Vector coordinate transformation (`r * v == r(v)`)
- Conversion to and from other representations (`r1 = R1(r2)`)

`RQuat` operates as a primary representation. It implements all of the
operations natively, and therefore it can be used for promotion whenever a
specific operation cannot be directly performed on its original operand(s).

This allows for a common interface; any `Abstract3DRotation` subtype need only implement
conversions to and from `RQuat` to be fully compatible with all other subtypes
and operations. However, for efficiency reasons, some direct methods are
provided for the implemented subtypes.
"""
module Attitude

using StaticArrays
using StructArrays
using LinearAlgebra
using Flight.Quaternions
using Flight.Plotting
#using ..Quaternions: UnitQuat, Quat #also works (but relies on folder hierarchy)

export Abstract3DRotation, RQuat, RAxAng, REuler, RMatrix, Rx, Ry, Rz, dt

const half_π = π/2

"""
Compute the skew-symmetric matrix corresponding to a 3-element vector.
"""
function skew(v::AbstractVector)

    @SMatrix [0 -v[3] v[2];
            v[3] 0 -v[1];
            -v[2] v[1] 0]

end

######################### Abstract3DRotation ###########################

"""
Generic 3D rotation descriptor
"""
abstract type Abstract3DRotation end

"Function call notation for coordinate transformations"
(r::Abstract3DRotation)(v::AbstractVector{<:Real}) = r * v

############################# RQuat ###############################

"Unit quaternion representation"
struct RQuat <: Abstract3DRotation
    _u::UnitQuat #no normalization is performed for an UnitQuat input
end

#generic AbstractVectors are normalized by default
function RQuat(v::AbstractVector{<:Real}; normalization::Bool = true)
    RQuat(UnitQuat(SVector{4,Float64}(v); normalization))
end
RQuat() = RQuat(UnitQuat(1.0))
RQuat(r::Abstract3DRotation) = convert(RQuat, r)

Base.getindex(r::RQuat, i) = getindex(r._u, i)
Base.length(r::RQuat) = length(r._u)
Base.size(r::RQuat) = size(r._u)
# Base.iterate(r::RQuat, state = 1) = iterate(getfield(getfield(r._u, :_q), :_sv)[:], state)

# direct iteration unsupported because it allocates. doing x.=r[:] instead of
#x.=r gets the underlying SVector first by forwarding getindex, then iterates on
#the StateVector itself. this avoids allocations.

Base.show(io::IO, r::RQuat) = print(io, "$(typeof(r))($(r[:]))")

#Strict equality (accounts for the unit quaternions' double cover of SO(3))
Base.:(==)(r1::RQuat, r2::RQuat) = (r1._u == r2._u || r1._u == -r2._u)
# Approximate equality (accounts for the unit quaternions' double cover of SO(3))
Base.:(≈)(r1::RQuat, r2::RQuat) = (r1._u ≈ r2._u || r1._u ≈ -r2._u)
# Composition
Base.:∘(r1::RQuat, r2::RQuat) = RQuat(r1._u * r2._u)
# Inversion
Base.adjoint(r::RQuat) = RQuat(r._u', normalization = false)

# Coordinate transformation (function call notation also supported)
function Base.:*(r_ab::RQuat, v_b_in::AbstractVector{<:Real})::SVector{3,Float64}
    v_b = SVector{3, Float64}(v_b_in) #casting to SVector yields a 3x speed gain
    q = r_ab._u; q_re = q.real; q_im = q.imag
    v_a = v_b + 2q_im × (q_re * v_b + q_im × v_b)
    return v_a
end

LinearAlgebra.norm(r::RQuat) = norm(r._u)
LinearAlgebra.normalize(r::RQuat) = RQuat(normalize(r._u))

"""
    dt(r_ab::RQuat, ω_ab_b::AbstractVector{<:Real})

Unit quaternion time derivative

If `r_ab` represents the rotation from axes εa to axes εb, and `ω_ab_b` is the
angular velocity of εb with respect to εa projected in εb, then `ṙ_ab ==
dt(r_ab, ω_ab_b)`
"""
dt(r_ab::RQuat, ω_ab_b::AbstractVector{<:Real}) = 0.5 * (r_ab._u * FreeQuat(imag=ω_ab_b))

##### RQuat fallbacks #####

#require each Abstract3DRotation subtype to implement conversions to and from RQuat
Base.convert(::Type{RQuat}, r::R) where {R<:Abstract3DRotation} = error("Implement $R to RQuat conversion")
Base.convert(::Type{R}, r::RQuat) where {R<:Abstract3DRotation} = error("Implement RQuat to $R conversion")

#this enables a two-step conversion via RQuat, but requires defining trivial
#conversions to avoid infinite recursion
Base.convert(::Type{R}, r::Abstract3DRotation) where {R<:Abstract3DRotation} = convert(R, RQuat(r))
Base.convert(::Type{R}, r::R) where {R<:Abstract3DRotation} = r
Base.convert(::Type{RQuat}, r::RQuat) = r

#unless the representation defines its own methods, fall back to RQuat;
#promote rules don't work here, because promoting two inputs of the same subtype
#leaves them unchanged, and here we need them converted to RQuat
Base.adjoint(r::R) where {R<:Abstract3DRotation} = convert(R, RQuat(r)')
Base.:*(r::Abstract3DRotation, v::AbstractVector{<:Real}) = RQuat(r) * v
Base.:(≈)(r1::Abstract3DRotation, r2::Abstract3DRotation) = RQuat(r1) ≈ RQuat(r2)
Base.:∘(r1::Abstract3DRotation, r2::Abstract3DRotation) = RQuat(r1) ∘ RQuat(r2)

#absolute equality is not defined in general, because comparing two different
#subtypes requires promotion to RQuat, whose outcome is affected by floating
#point precision

######################### RMatrix #######################

"Rotation matrix representation"
struct RMatrix <: Abstract3DRotation
    _mat::SMatrix{3, 3, Float64, 9}
    function RMatrix(input::AbstractArray{<:Real, 2}; normalization::Bool = true)
        return normalization ? new(qr(input).Q) : new(input)
    end
end

#normalize by default
RMatrix() = RMatrix(SMatrix{3,3,Float64}(I), normalization = false)
RMatrix(r::Abstract3DRotation) = convert(RMatrix, r)

Base.size(r::RMatrix) = size(getfield(r,:_mat))
Base.getindex(r::RMatrix, i...) = getindex(getfield(r,:_mat), i...)

Base.convert(::Type{RMatrix}, r::RMatrix) = r

function Base.convert(::Type{RMatrix}, r::RQuat)

    q = normalize(r._u) #cheap
    q_sq = q[:] .* q[:]
    dq12 = 2*q[1]*q[2]; dq13 = 2*q[1]*q[3]; dq14 = 2*q[1]*q[4];
    dq23 = 2*q[2]*q[3]; dq24 = 2*q[2]*q[4]; dq34 = 2*q[3]*q[4];

    M = @SMatrix [
    1 - 2*(q_sq[3]+q_sq[4])  dq23 - dq14                dq24 + dq13;
    dq23 + dq14              1 - 2*(q_sq[2]+q_sq[4])    dq34 - dq12;
    dq24 - dq13              dq34 + dq12                1 - 2*(q_sq[2]+q_sq[3])
    ]

    RMatrix(M, normalization = false)

end

function Base.convert(::Type{RQuat}, r::RMatrix)
    R = r._mat
    tr_R = tr(R)
    i_max = findmax(vcat(tr_R, diag(R)))[2]

    #these 4-element vectors are all proportional to the the desired unit
    #quaternion's components. to minimize numerical errors, we choose the one
    #with the largest norm (indicated by i_max), and construct the quaternion by
    #normalizing it
    if i_max == 1
        v = SVector{4,Float64}(
            1 + tr_R,
            R[3,2] - R[2,3],
            R[1,3] - R[3,1],
            R[2,1] - R[1,2]
        )
    elseif i_max == 2
        v = SVector{4,Float64}(
            R[3,2] - R[2,3],
            1 + 2R[1,1] - tr_R,
            R[1,2] + R[2,1],
            R[3,1] + R[1,3]
        )
    elseif i_max == 3
        v = SVector{4,Float64}(
            R[1,3] - R[3,1],
            R[1,2] + R[2,1],
            1 + 2R[2,2] - tr_R,
            R[2,3] + R[3,2]
        )
    else #i_max == 4
        v = SVector{4, Float64}(
            R[2,1] - R[1,2],
            R[3,1] + R[1,3],
            R[2,3] + R[3,2],
            1 + 2R[3,3] - tr_R
        )
    end

    return RQuat(v, normalization = true)

end

#RMatrix also implements all operators natively
Base.:(==)(r1::RMatrix, r2::RMatrix) = (r1._mat == r2._mat)
Base.:(≈)(r1::RMatrix, r2::RMatrix) = r1._mat ≈ r2._mat

# Coordinate transformation
Base.:*(r::RMatrix, v::AbstractVector{<:Real}) = r._mat * v

# Composition
Base.:∘(r1::RMatrix, r2::RMatrix) = RMatrix(r1._mat * r2._mat, normalization = false)
Base.:*(r1::RMatrix, r2::RMatrix) = r1 ∘ r2 #allow overload of * for composition

# Inversion
Base.adjoint(r::RMatrix) = RMatrix(r._mat', normalization = false)

#let the constructor handle it
LinearAlgebra.normalize(r::RMatrix) = RMatrix(r._mat, normalization = true)

LinearAlgebra.det(r::RMatrix) = det(r._mat)

"""
    dt(r_ab::RMatrix, ω_ab_b::AbstractVector{<:Real})

Rotation matrix time derivative

If `R_ab` represents the rotation from axes εa to axes εb, and `ω_ab_b` is the
angular velocity of εb with respect to εa projected in εb, then `Ṙ_ab ==
dt(R_ab, ω_ab_b)`
"""
function dt(R_ab::RMatrix, ω_ab_b::AbstractVector{<:Real})
    Ω_ab_b = skew(ω_ab_b)
    return R_ab._mat * Ω_ab_b
end

############################# RAxAng ###############################

"Axis-angle representation"
struct RAxAng <: Abstract3DRotation
    axis::SVector{3, Float64}
    angle::Float64
    function RAxAng(axis::AbstractVector{<:Real}, angle::Real; normalization::Bool = true)
        axis = SVector{3,Float64}(axis) #normalization will be faster for an SVector
        return normalization ? new(normalize(axis), angle) : new(axis, angle)
    end
end

RAxAng(r::Abstract3DRotation) = convert(RAxAng, r)
RAxAng(::Nothing, ::Real; normalization::Bool = false) = RAxAng()
RAxAng(input::Tuple{Union{Nothing, AbstractVector{<:Real}}, Real}) = RAxAng(input...)

#shorthand constructors for rotations around X, Y, Z
Rz(ψ::Real = 0.0) = RAxAng(SVector{3}(0.0, 0.0, 1.0), ψ, normalization = false)
Ry(θ::Real = 0.0) = RAxAng(SVector{3}(0.0, 1.0, 0.0), θ, normalization = false)
Rx(φ::Real = 0.0) = RAxAng(SVector{3}(1.0, 0.0, 0.0), φ, normalization = false)

RAxAng() = Rx(0)

#conversions to and from RQuat
function Base.convert(::Type{RAxAng}, r::RQuat)
    q_re = r._u.real
    q_im = r._u.imag
    norm_im = norm(q_im)
    μ = 2atan(norm_im, q_re)
    u = (norm_im > 0 ? q_im / norm_im : nothing)
    return RAxAng(u, μ, normalization = false)
end

function Base.convert(::Type{RQuat}, r::RAxAng)
    u = r.axis
    μ = r.angle
    RQuat(UnitQuat(real = cos(0.5μ), imag = u*sin(0.5μ), normalization = false))
end

# Inversion (the only native operation we can easily define for axis-angle)
Base.adjoint(r::RAxAng) = RAxAng(r.axis, -r.angle)

############################# REuler #############################

"Euler angle representation (convention is ZYX)"
struct REuler <: Abstract3DRotation
    ψ::Float64 #heading
    θ::Float64 #inclination
    φ::Float64 #bank
    function REuler(ψ, θ, φ)
        @assert abs(ψ<=π) "Heading must be within [-π, π]"
        @assert abs(θ<=half_π) "Inclination must be within [-π/2, π/2]"
        @assert abs(φ<=π) "Bank must be within [-π, π]"
        new(ψ, θ, φ)
    end
end

REuler(r::Abstract3DRotation) = convert(REuler, r)
REuler(input::Tuple{Real, Real, Real}) = REuler(input...)
REuler(; ψ = 0, θ = 0, φ = 0) = REuler(ψ, θ, φ)

#conversions to and from RQuat
function Base.convert(::Type{REuler}, r::RQuat)
        q = r._u
        q_sq = q[:] .* q[:]

        ψ = atan( 2*(q[1]*q[4] + q[2]*q[3]), 1 - 2*(q_sq[3] + q_sq[4]))
        θ = asin( clamp(2*(q[1]*q[3] - q[2]*q[4]), -1, 1) )
        φ = atan( 2*(q[1]*q[2] + q[3]*q[4]), 1 - 2*(q_sq[2] + q_sq[3]))

        return REuler(ψ, θ, φ)
end

function Base.convert(::Type{RQuat}, r::REuler)
    Rz(r.ψ) ∘ Ry(r.θ) ∘ Rx(r.φ)
end

########################## Plotting #################################

#unless a more specialized method is defined, a TimeHistory{Rotations} is
#converted to REuler for plotting
@recipe function plot_rotation(th::TimeHistory{<:AbstractVector{<:Abstract3DRotation}};
                                rot_ref = "", rot_target = "")

    v_euler = Vector{REuler}(undef, length(th.data))
    for i in 1:length(v_euler)
        v_euler[i] = REuler(th.data[i])
    end
    sa = StructArray(v_euler)
    data = hcat(sa.ψ, sa.θ, sa.φ)./π #plot as π factors

    label --> ["Heading" "Inclination" "Bank"]
    yguide --> hcat(L"$\psi_{%$rot_ref %$rot_target} \ (\pi \ rad)$",
                    L"$\theta_{%$rot_ref %$rot_target} \ (\pi \ rad)$",
                    L"$\phi_{%$rot_ref %$rot_target} \ (\pi \ rad)$")
    th_split --> :h #custom TimeHistory attribute

    return TimeHistory(th.t, data)

end

end


"""
Package: Forecast

    loess(xv, yv;
          d = 0,
          q = 3*sum((!ismissing).(yv))÷4,
          rho = fill(1.0,length(yv)),
          exact = [], extra = [])

    loess(yv;
          d = 0,
          q = 3*sum((!ismissing).(yv))÷4,
          rho = fill(1.0,length(yv)),
          exact = [], extra = [])

Return a funcion to smooth a vector of observations using locally weighted regressions.

Although loess can be used to smooth observations for any given number of independent variables, this implementation is univariate. The speed of loess can be greatly increased by using fast aproximations for the linear fitting calculations, however this implementation calculates allows as well for exact results.

The loess functionality and nomenclature follows the descriptions in:

"STL: A Seasonal, Trend Decomposition Procedure Based on Loess"
Robert B. Cleveland, William S. Cleveland, Jean E. McRae, and Irma Terpenning.
Journal of Official Statistics Vol. 6. No. 1, 1990, pp. 3-73 (c) Statistics Sweden.

# Arguments
- `xv`: Observations' support, if not provided 1:length(yv) is used instead.
- `yv`: Observation values.
- `d`: Degree of the linear fit, it accepts values 0, 1 or 2, if 0 an estimation of `d` is calculated.
- `q`: As q increases loess becomes smoother, when q tends to infinity loess tends to an ordinary least square poynomial fit of degree `d`. It defaults to the rounding of 3/4 of xv's non-missing values length.
- `rho`: Weights expressing the reliability of the observations (e.g. if yi had variances σ^2*ki where ki where known, the rhoi could be 1/ki). It defaults to 1.
- `exact`: Vector containing support values where exact loess will be calculated, the least values the faster will be loess. It defaults to an emtpy array. For series of 10 values or less exact loess is guaranteed.
- `extra`: Vector containing support values where, besideds those values chosen by the heuristic, exact loess will be calculated.

# Returns
A function returning the exact loess for the values contained in `exact` and either exact loess or fast approximations for the rest.

# Examples
```julia-repl
n = 1000
x = sort(rand(n)*2*pi);
y = sin.(x) + randn(n);
f = Smoothers.loess(x,y)
isapprox(f(pi),0;atol=0.1) #true
[...]
```
"""
loess

function loess(yv::AbstractVector{<:Union{Missing,T}};
               d::Integer=0, # 0 -> auto
               q::Integer=3*length(yv)÷4,
               rho::AbstractVector{<:Union{Missing,T}}=fill(T(1),length(yv)),
               exact::AbstractVector{T}=T[],
               extra::AbstractVector{T}=T[]) where {T<:Real,S<:Real}

    loess(T(1):length(yv),yv;d,q,rho,exact,extra)
end

function loess(xv::AbstractVector{R},
               yv::AbstractVector{<:Union{Missing,T}};
               d::Integer=0,
               q::Integer=3*length(yv)÷4,
               rho::AbstractVector{<:Union{Missing,T}}=fill(T(1.0),length(xv)),
               exact::AbstractVector{R}=R[],
               extra::AbstractVector{R}=R[]) where {R<:Real,T<:Real}

    @assert issorted(xv) "xv values must be sorted"
    @assert d in 0:2 "Linear Regression must be of degree 1 or 2 or 0 (auto)"
    @assert length(xv) == length(yv) "xv and yv must have the same length"

    # Removing missing values
    myi = findall(x -> !ismissing(x),yv)
    @assert length(myi) > 0 "at least one yv value must be non missing"
    q = Int(floor(q*length(myi)/length(yv)))
    xv = xv[myi]
    yv = Vector{T}(yv[myi])
    rho = rho[myi]

    length(xv) == 1 && return x -> repeat([yv[1]],length(x))
    length(xv) == 2 && begin d=(yv[2]-yv[1])/(xv[2]-xv[1]); return x -> @. d*x+(yv[1]-d*xv[1]) end

    # Promote to same Float type
    xv,yv,rho,exact,extra,_ = Base.promote(xv,yv,rho,collect(exact),collect(extra),
                                           [Base.promote_op(/,T,T)(1.0)])

    ## Ax = b prediction
    P = eltype(xv)
    A = hcat(xv,fill(P(1.0),length(xv)))
    b = yv

    ## Order Estimation
    d = (d == 0) ? min(khat(yv),2) : d  # loess accepts 1 or 2
    d == 2 && (A = hcat(xv .^ 2, A))

    # Select collection
    predictx = exact
    if isempty(exact)
        N = length(xv)
        n = 2*sr(N)*N÷q
        m,M = extrema(xv)
        gap = P((M-m)/n)
        #exact = vcat(exact, xv[Int64.(round.(LinRange(1,length(xv),20)))])
        predict = sort(unique(vcat(collect(m:gap:M),M,extra)))
        predictx = vcat(m-P(10.0)*gap,m-gap,
                        predict,
                        M+gap,M+P(10.0)*gap)
    else
        @assert length(exact) > d "length(exact) should be greater than d"
    end

    # Predict collection
    res = similar(predictx)
    for (i,xi) in enumerate(predictx)
        res[i] = ghat(xi;A,b,d,q,rho)
    end

    Spline1D(predictx, res; k=d, bc="extrapolate",s=0)

end

# loess estimation
function ghat(x::T;
              A::AbstractMatrix{T},
              b::AbstractVector{T},
              d::Integer,
              q::Integer,
              rho::AbstractVector{T}) where T<:Real

    xv = A[:,d]
    yv = b

    ## λ_q
    n = length(xv)
    xvx = @. abs(xv-x)
    qidx = sortperm(xvx)[1:min(q,n)]
    qdist = abs(xv[last(qidx)]-x)*max(T(1),q/n)

    ## upsilon
    w = zeros(n)
    for wi in qidx
        aq = abs(xv[wi]-x)/qdist
        w[wi] = max((1-aq^3)^3,T(0))
    end

    A = @. A*(w*rho)
    b = @. b*(w*rho)

    lsq_x = A\b

    d == 1 ? sum([x,T(1)].*lsq_x) : sum([x^2,x,T(1)].*lsq_x)

end

#Sturges' Rule
sr(n) = Int(round(1+3.322*log(n)))

# absolute standard deviation
function astd(x::AbstractVector{<:Union{Missing,Real}})
    astd(collect(skipmissing(x)))
end

function astd(x::AbstractVector{<:Real})
    n = length(x)
    sum(abs.(x.-sum(x)/n))/n
end

# k order estimation for Splines
function khat(y::AbstractVector{<:Union{Missing,Real}})
    khat(collect(skipmissing(y)))
end

function khat(y::AbstractVector{<:Real})
    dy = diff(y); ady = astd(dy)
    ady < 1e-10 && return 1
    mi = 1
    for i in 1:3
        dy = diff(dy)
        nady = astd(dy); nady < 1e-10 && break
        mi += (ady > nady) ? 1 : break
        ady = nady
    end
    mi+1
end


"""
    function getS(m::Model; zeros=spzeros)::Tuple{Vector{String},Vector{String},AbstractMatrix{Float64}}

Extract the vector of species (aka metabolite) identifiers, vector of reaction
identifiers, and the (dense) stoichiometry matrix from an existing `Model`.
Returns a tuple with these values.

The matrix is sparse by default (initially constructed by
`SparseArrays.spzeros`). You can fill in a custom empty matrix constructed to
argument `zeros`; e.g. running with `zeros=zeros` will produce a dense matrix.
"""
function getS(
    m::Model;
    zeros = spzeros,
)::Tuple{Vector{String},Vector{String},AbstractMatrix{Float64}}
    rows = [k for k in keys(m.species)]
    cols = [k for k in keys(m.reactions)]
    rowsd = Dict(k => i for (i, k) in enumerate(rows))
    S = zeros(Float64, length(rows), length(cols))
    for col = 1:length(cols)
        stoi = m.reactions[cols[col]].stoichiometry
        S[getindex.(Ref(rowsd), keys(stoi)), col] .= values(stoi)
    end
    return rows, cols, S
end

"""
    function getLBs(m::Model)::Vector{Tuple{Float64,String}}

Extract a vector of lower bounds of reaction rates from the model. All bounds
are accompanied with the unit of the corresponding value (this behavior is
based on SBML specification).
"""
function getLBs(m::Model)::Vector{Tuple{Float64,String}}
    return broadcast(x -> x.lb, values(m.reactions))
end

"""
    function getUBs(m::Model)::Vector{Tuple{Float64,String}}

Likewise to `getLBs`, extract the upper bounds.
"""
function getUBs(m::Model)::Vector{Tuple{Float64,String}}
    return broadcast(x -> x.ub, values(m.reactions))
end

"""
    function getOCs(m::Model)::Vector{Float64}

Extract the vector of objective coefficients of each reaction.
"""
function getOCs(m::Model)::Vector{Float64}
    return broadcast(x -> x.oc, values(m.reactions))
end


# ---
# title: 123. Best Time to Buy and Sell Stock III
# id: problem123
# author: Tian Jun
# date: 2020-10-31
# difficulty: Hard
# categories: Array, Dynamic Programming
# link: <https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/description/>
# hidden: true
# ---
#
# Say you have an array for which the _i_ th element is the price of a given
# stock on day _i_.
#
# Design an algorithm to find the maximum profit. You may complete at most _two_
# transactions.
#
# **Note:  **You may not engage in multiple transactions at the same time (i.e.,
# you must sell the stock before you buy again).
#
#
#
# **Example 1:**
#
#
#
#     Input: prices = [3,3,5,0,0,3,1,4]
#     Output: 6
#     Explanation: Buy on day 4 (price = 0) and sell on day 6 (price = 3), profit = 3-0 = 3.
#     Then buy on day 7 (price = 1) and sell on day 8 (price = 4), profit = 4-1 = 3.
#
# **Example 2:**
#
#
#
#     Input: prices = [1,2,3,4,5]
#     Output: 4
#     Explanation: Buy on day 1 (price = 1) and sell on day 5 (price = 5), profit = 5-1 = 4.
#     Note that you cannot buy on day 1, buy on day 2 and sell them later, as you are engaging multiple transactions at the same time. You must sell before buying again.
#
#
# **Example 3:**
#
#
#
#     Input: prices = [7,6,4,3,1]
#     Output: 0
#     Explanation: In this case, no transaction is done, i.e. max profit = 0.
#
#
# **Example 4:**
#
#
#
#     Input: prices = [1]
#     Output: 0
#
#
#
#
# **Constraints:**
#
#   * `1 <= prices.length <= 105`
#   * `0 <= prices[i] <= 105`
#
#
## @lc code=start
using LeetCode

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


export  AbstractMeasure,
        AbstractCanonicalMeasure,
        AbstractQuad,
        AbstractOrthoPoly,
        AbstractCanonicalOrthoPoly,
        AbstractTensor

abstract type AbstractQuad end

abstract type AbstractMeasure end
abstract type AbstractCanonicalMeasure <: AbstractMeasure end

abstract type AbstractOrthoPoly end
abstract type AbstractCanonicalOrthoPoly <: AbstractOrthoPoly end

abstract type AbstractTensor end


#using FixedSizeArrays
CD = CollisionDetection

@test CD.fitsinbox([1,1,1],1.2,[0,0,0],1,2.21) == true
@test CD.fitsinbox([1,1,1],1.2,[0,0,0],1,2.19) == false
@test CD.fitsinbox([-1,-1],1.2,[0,0],1,2.21) == true

d, n = 2, 9
data = SVector{d,Float64}[2*rand(SVector{d,Float64}) for i in 1:n]  # create the list of points for the tree test
push!(data,SVector(2.0,2.0))        # The is the the top right corner
push!(data,SVector(0.0,0.0))        # This is the lower left corner
push!(data, SVector(0.90,0.5))      # This point is at the edgre of sector(0) (-,-)
radii = abs.(zeros(n+2))           # We set raduis for all points to zero to test if they fill in sectors as well
push!(radii,0.2)                  # Now only the test point has a raduis
tree = CD.Octree(data, radii)     # Create an Octree with normal (ratio=1) so that means the point is unmovable to child sector
@test tree.rootbox.data[1]==n+3   # Now we test if the test point is the only unmovable (it should be)
tree = CD.Octree(data, radii,1.2) # We create a new tree but with wider boxes to include points at sectors edge
@test in(n+3,tree.rootbox.children[1].data)==true # now the point should be located at sector zero (+1) of the direct child
push!(data, SVector(0.90,0.5))      # now we add another point but with slightly fatter raduis than the ratio 1.2
push!(radii,0.26)
tree = CD.Octree(data, radii,1.2)
@test in(n+4,tree.rootbox.children[1].data)==false # Now it should go to the child sector
@test tree.rootbox.data[1]==n+4                    # but will be definatly the only one in top level,


function ranks(v::AbstractArray)
  n     = length(v)
  place = sortperm(v)
  ord   = Array(Int64, n)

  for(i in 1:n)
    ord[place[i]] = i
  end

  ord
end

function factorial(n::Integer)
  prod(1:n)
end

function combinations(k::Integer, n::Integer)
  factorial(n) / (factorial(k) * factorial(n-k))
end


export lsqr, lsqr!

"""
    lsqr(A, b; kwrags...) -> x, [history]

Same as [`lsqr!`](@ref), but allocates a solution vector `x` initialized with zeros.
"""
lsqr(A, b; kwargs...) = lsqr!(zerox(A, b), A, b; kwargs...)

"""
    lsqr!(x, A, b; kwargs...) -> x, [history]

Minimizes ``\\|Ax - b\\|^2 + \\|damp*x\\|^2`` in the Euclidean norm. If multiple solutions
exists returns the minimal norm solution.

The method is based on the Golub-Kahan bidiagonalization process.
It is algebraically equivalent to applying CG to the normal equations
``(A^*A + λ^2I)x = A^*b`` but has better numerical properties,
especially if A is ill-conditioned.

# Arguments
- `x`: Initial guess, will be updated in-place;
- `A`: linear operator;
- `b`: right-hand side.

## Keywords

- `damp::Number = 0`: damping parameter.
- `atol::Number = 1e-6`, `btol::Number = 1e-6`: stopping tolerances. If both are
  1.0e-9 (say), the final residual norm should be accurate to about 9 digits.
  (The final `x` will usually have fewer correct digits,
  depending on `cond(A)` and the size of damp).
- `conlim::Number = 1e8`: stopping tolerance.  `lsmr` terminates if an estimate
  of `cond(A)` exceeds conlim.  For compatible systems Ax = b,
  conlim could be as large as 1.0e+12 (say).  For least-squares
  problems, conlim should be less than 1.0e+8.
  Maximum precision can be obtained by setting
  `atol` = `btol` = `conlim` = zero, but the number of iterations
  may then be excessive.
- `maxiter::Int = maximum(size(A))`: maximum number of iterations.
- `verbose::Bool = false`: print method information.
- `log::Bool = false`: output an extra element of type `ConvergenceHistory`
  containing extra information of the method execution.

# Return values

**if `log` is `false`**

- `x`: approximated solution.

**if `log` is `true`**

- `x`: approximated solution.
- `ch`: convergence history.

**ConvergenceHistory keys**

- `:atol` => `::Real`: atol stopping tolerance.
- `:btol` => `::Real`: btol stopping tolerance.
- `:ctol` => `::Real`: ctol stopping tolerance.
- `:anorm` => `::Real`: anorm.
- `:rnorm` => `::Real`: rnorm.
- `:cnorm` => `::Real`: cnorm.
- `:resnom` => `::Vector`: residual norm at each iteration.
"""
function lsqr!(x, A, b;
    maxiter::Int=maximum(size(A)), log::Bool=false,
    kwargs...
    )
    T = Adivtype(A, b)
    z = zero(T)
    history = ConvergenceHistory(partial=!log)
    reserve!(history,[:resnorm,:anorm,:rnorm,:cnorm],maxiter)
    lsqr_method!(history, x, A, b; maxiter=maxiter, kwargs...)
    log && shrink!(history)
    log ? (x, history) : x
end

#########################
# Method Implementation #
#########################

# Michael Saunders, Systems Optimization Laboratory,
# Dept of MS&E, Stanford University.
#
# Adapted for Julia by Timothy E. Holy with the following changes:
#    - Allow an initial guess for x
#    - Eliminate printing
#----------------------------------------------------------------------
function lsqr_method!(log::ConvergenceHistory, x, A, b;
    damp=0, atol=sqrt(eps(real(Adivtype(A,b)))), btol=sqrt(eps(real(Adivtype(A,b)))),
    conlim=real(one(Adivtype(A,b)))/sqrt(eps(real(Adivtype(A,b)))),
    maxiter::Int=maximum(size(A)), verbose::Bool=false,
    )
    verbose && @printf("=== lsqr ===\n%4s\t%7s\t\t%7s\t\t%7s\t\t%7s\n","iter","resnorm","anorm","cnorm","rnorm")
    # Sanity-checking
    m = size(A,1)
    n = size(A,2)
    length(x) == n || error("x should be of length ", n)
    length(b) == m || error("b should be of length ", m)
    for i = 1:n
        isfinite(x[i]) || error("Initial guess for x must be finite")
    end

    # Initialize
    T = Adivtype(A, b)
    Tr = real(T)
    itn = istop = 0
    ctol = conlim > 0 ? convert(Tr, 1/conlim) : zero(Tr)
    Anorm = Acond = ddnorm = res2 = xnorm = xxnorm = z = sn2 = zero(Tr)
    cs2 = -one(Tr)
    dampsq = abs2(damp)
    tmpm = similar(b, T, m)
    tmpn = similar(x, T, n)

    log[:atol] = atol
    log[:btol] = btol
    log[:ctol] = ctol

    # Set up the first vectors u and v for the bidiagonalization.
    # These satisfy  beta*u = b-A*x,  alpha*v = A'u.
    u = b - A*x
    v = copy(x)
    beta = norm(u)
    alpha = zero(Tr)
    if beta > 0
        log.mtvps=1
        u .*= inv(beta)
        Ac_mul_B!(v,A,u)
        alpha = norm(v)
    end
    if alpha > 0
        v .*= inv(alpha)
    end
    w = copy(v)
    wrho = similar(w)

    Arnorm = alpha*beta
    if Arnorm == 0
        return
    end

    rhobar = alpha
    phibar = bnorm = rnorm = r1norm = r2norm = beta

    #------------------------------------------------------------------
    #     Main iteration loop.
    #------------------------------------------------------------------
    while (itn < maxiter) & !log.isconverged
        nextiter!(log,mvps=1)
        itn += 1

        # Perform the next step of the bidiagonalization to obtain the
        # next beta, u, alpha, v.  These satisfy the relations
        #      beta*u  =  A*v  - alpha*u,
        #      alpha*v  =  A'*u - beta*v.

        # Note that the following three lines are a band aid for a GEMM: X: C := αAB + βC.
        # This is already supported in A_mul_B! for sparse and distributed matrices, but not yet dense
        A_mul_B!(tmpm, A, v)
        u .= -alpha .* u .+ tmpm
        beta = norm(u)
        if beta > 0
            log.mtvps+=1
            u .*= inv(beta)
            Anorm = sqrt(abs2(Anorm) + abs2(alpha) + abs2(beta) + dampsq)
            # Note that the following three lines are a band aid for a GEMM: X: C := αA'B + βC.
            # This is already supported in Ac_mul_B! for sparse and distributed matrices, but not yet dense
            Ac_mul_B!(tmpn, A, u)
            v .= -beta .* v .+ tmpn
            alpha  = norm(v)
            if alpha > 0
                v .*= inv(alpha)
            end
        end

        # Use a plane rotation to eliminate the damping parameter.
        # This alters the diagonal (rhobar) of the lower-bidiagonal matrix.
        rhobar1 = sqrt(abs2(rhobar) + dampsq)
        cs1     = rhobar/rhobar1
        sn1     = damp  /rhobar1
        psi     = sn1*phibar
        phibar  = cs1*phibar

        # Use a plane rotation to eliminate the subdiagonal element (beta)
        # of the lower-bidiagonal matrix, giving an upper-bidiagonal matrix.
        rho     =   sqrt(abs2(rhobar1) + abs2(beta))
        cs      =   rhobar1/rho
        sn      =   beta   /rho
        theta   =   sn*alpha
        rhobar  = - cs*alpha
        phi     =   cs*phibar
        phibar  =   sn*phibar
        tau     =   sn*phi

        # Update x and w
        t1      =   phi  /rho
        t2      = - theta/rho

        x .+= t1*w
        w = t2 .* w .+ v
        wrho .= w .* inv(rho)
        ddnorm += norm(wrho)

        # Use a plane rotation on the right to eliminate the
        # super-diagonal element (theta) of the upper-bidiagonal matrix.
        # Then use the result to estimate  norm(x).
        delta   =   sn2*rho
        gambar  =  -cs2*rho
        rhs     =   phi - delta*z
        zbar    =   rhs/gambar
        xnorm   =   sqrt(xxnorm + abs2(zbar))
        gamma   =   sqrt(abs2(gambar) + abs2(theta))
        cs2     =   gambar/gamma
        sn2     =   theta /gamma
        z       =   rhs   /gamma
        xxnorm +=   abs2(z)

        # Test for convergence.
        # First, estimate the condition of the matrix  Abar,
        # and the norms of  rbar  and  Abar'rbar.
        Acond   =   Anorm*sqrt(ddnorm)
        res1    =   abs2(phibar)
        res2    =   res2 + abs2(psi)
        rnorm   =   sqrt(res1 + res2)
        Arnorm  =   alpha*abs(tau)

        # 07 Aug 2002:
        # Distinguish between
        #    r1norm = ||b - Ax|| and
        #    r2norm = rnorm in current code
        #           = sqrt(r1norm^2 + damp^2*||x||^2).
        #    Estimate r1norm from
        #    r1norm = sqrt(r2norm^2 - damp^2*||x||^2).
        # Although there is cancellation, it might be accurate enough.
        r1sq    =   abs2(rnorm) - dampsq*xxnorm
        r1norm  =   sqrt(abs(r1sq));   if r1sq < 0 r1norm = - r1norm; end
        r2norm  =   rnorm
        push!(log, :resnorm, r1norm)

        # Now use these norms to estimate certain other quantities,
        # some of which will be small near a solution.
        test1   =   rnorm /bnorm
        test2   =   Arnorm/(Anorm*rnorm)
        test3   =   inv(Acond)
        t1      =   test1/(1 + Anorm*xnorm/bnorm)
        rtol    =   btol + atol*Anorm*xnorm/bnorm
        push!(log, :cnorm, test3)
        push!(log, :anorm, test2)
        push!(log, :rnorm, test1)
        verbose && @printf("%3d\t%1.2e\t%1.2e\t%1.2e\t%1.2e\n",itn,r1norm,test2,test3,test1)

        # The following tests guard against extremely small values of
        # atol, btol  or  ctol.  (The user may have set any or all of
        # the parameters  atol, btol, conlim  to 0.)
        # The effect is equivalent to the normal tests using
        # atol = eps,  btol = eps,  conlim = 1/eps.
        if itn >= maxiter  istop = 7; end
        if 1 + test3  <= 1 istop = 6; end
        if 1 + test2  <= 1 istop = 5; end
        if 1 + t1     <= 1 istop = 4; end

        # Allow for tolerances set by the user
        if  test3 <= ctol  istop = 3; end
        if  test2 <= atol  istop = 2; end
        if  test1 <= rtol  istop = 1; end

        setconv(log, istop > 0)
    end
    verbose && @printf("\n")
    x
end


## using TimeData

## include("/home/chris/research/julia/TimeData/src/TimeData.jl")
include("/home/chris/research/julia/Econometrics/src/Econometrics.jl")
using TimeData
using Winston
using Distributions

tm = readTimedata("/home/chris/research/asset_mgmt/data/datesLogRet.csv")

## get 5 lowest minima stocks
mapFunc(x) = minimum(x)
critFunc(x) = islowest(x, 5)
mostNegativeReturns = TimeData.getVars(tm, mapFunc, critFunc)

#########################
## get moving averages ##
#########################

import Winston.plot
function plot(tm::Timematr)
    plot(core(tm))
end

movAvgs = movAvg(tm, 150)
plot(movAvgs)

marketMeans = rowmeans(tm)
plot(marketMeans)
var(core(marketMeans))

marketMovAvg = movAvg(marketMeans, 400)
plot(marketMovAvg)
Winston.ylim(-0.6, 0.6)

######################################
## mean-shifting induced dependency ##
######################################

marketMovAvg = movAvg(marketMeans, 20)
plot(marketMovAvg)
Winston.ylim(-0.6, 0.6)

## for each day, simulate independent normally distributed values
nDays = size(marketMovAvg, 1)

using Distributions
d = Normal(0, 1.6)
simInnov = rand(d, nDays, 2)
simVals = simInnov + repmat(core(marketMovAvg), 1, 2)
cor(simVals[:, 1], simVals[:,2])

## plot autocor
p = []
for ii=1:20
    if(ii==1)
        p = autocorr(core(tm[1])[:])
    else
        p = autocorr!(core(tm[ii])[:])
    end
end
p
autocorr_red!(simVals[:, 1])

## look at ranks!!

######################################
## correlations for all frequencies ##
######################################

maxAggregation = 50

## include("/home/chris/research/julia/TimeData/src/TimeData.jl")
## tm = readTimedata("/home/chris/research/asset_mgmt/data/datesLogRet.csv")
nObs = size(tm, 2)

frequ = 50
for ii=1:20
    eqVal = true
    x = 0
    y = 0
    while(eqVal)
        (x, y) = [rand(1:nObs) rand(1:nObs)]
        if x !== y
            eqVal = false
        end
    end
    println("first: ", x, " , second: ", y)
    a = Econometrics.frequCorrPlot(tm, x, y, frequ)
    plot(a[:, 1], a[:, 2], "+b")
    ylim(-0.2, 1)
    ## p = FramedPlot(
    ##                xrange=(0,frequ),
    ##                yrange=(-0.3,1))
    ## ## pts = Points(a[:, 1], a[:, 2], kind="filled circle")
    ## pts = Points(a[:, 1], a[:, 2], kind="plus")
    ## add(p, pts)
    fname = string("./pics/corrFrequ", ii, ".png")
    file(fname)
end

    ## create simulated data with no autocorrelation

nSim = 15000
using Distributions
## create two-dimensional normal distribution
rho = 0.5
sigma1 = 1.4
sigma2 = 1.4
covMatr = [sigma1^2 sigma1*sigma2*rho; sigma1*sigma2*rho sigma2^2]
d = MvNormal(covMatr)

simInnov = rand(d, nSim)
tmSim = Timematr(simInnov')
a = TimeData.frequCorrPlot(tmSim, 1, 2, frequ)
plot(a[:, 1], a[:, 2], "+b")
ylim(-0.2, 1)

###########
## GARCH ##
###########

testData = tm[1]

## load econometrics library
include("/home/chris/research/julia/Econometrics/src/Econometrics.jl")

(sigmaHat, paramsHat) = Econometrics.garchFit(testData)

plot(sigmaHat)

sigma = sqrt(var(core(testData)))
initVal = [0, sigma^2, 0, 0, sigma]

params1 =  [0.0338456,
            0.463194,
            0.616413,
            0.193743,
            1.39494]

params2 = [ 0.056642,
           0.0467392,
           0.932111,
           0.0485675,
           1.01403]

Econometrics.garchLLH(paramsHat, core(testData))
Econometrics.garchLLH(params2, core(testData))

d = Normal(0, sigma)
nllh2 = -sum(log(pdf(d, core(testData))))

kk = x -> Econometrics.garchLLH(x, core(testData))
kk(initVal)

garchObj = garchFit(core(tm[1])[:])
sigs = garchObj.sigma
plot(sigs)


#!/usr/bin/env julia

# Calculate Mandelbrot set using OpenCL

if VERSION < v"0.4-"
  typealias UInt16 Uint16
end

import OpenCL
const cl = OpenCL

import GR

const mandel_kernel = "
#pragma OPENCL EXTENSION cl_khr_byte_addressable_store : enable
__kernel void mandelbrot(__global double2 *q, __global ushort *output,
                         double const min_x, double const max_x,
                         double const min_y, double const max_y,
                         ushort const width, ushort const height,
                         ushort const iters)
{
    int ci = 0, inc = 1;
    int gid = get_global_id(0);
    double nreal, real = 0;
    double imag = 0;

    q[gid].x = min_x + (gid % width) * (max_x - min_x) / width;
    q[gid].y = min_y + (gid / width) * (max_y - min_y) / height;

    output[gid] = iters;

    for (int curiter = 0; curiter < iters; curiter++) {
        nreal = real * real - imag * imag + q[gid].x;
        imag = 2 * real * imag + q[gid].y;
        real = nreal;

        if (real * real + imag * imag >= 4) {
            output[gid] = ci;
            return;
        }
        ci += inc;
        if (ci == 0 || ci == 255)
            inc = -inc;
    }
}"

for device in reverse(cl.available_devices(cl.platforms()[1]))
    global ctx, queue, prg

    ctx = cl.Context(device)
    queue = cl.CmdQueue(ctx)
    try
        prg = cl.Program(ctx, source = mandel_kernel) |> cl.build!
        println(device[:name])
        break
    end
end

function calc_fractal(q, min_x, max_x, min_y, max_y, width, height, iters)
    global ctx, queue, prg

    output = Array(UInt16, size(q))

    q_opencl = cl.Buffer(Complex128, ctx, (:r, :copy), hostbuf=q)
    output_opencl = cl.Buffer(UInt16, ctx, :w, length(output))

    k = cl.Kernel(prg, "mandelbrot")
    cl.call(queue, k, length(q), nothing, q_opencl, output_opencl,
            min_x, max_x, min_y, max_y, UInt16(width), UInt16(height), UInt16(iters))
    cl.copy!(queue, output, output_opencl)

    return output
end

function create_fractal(min_x, max_x, min_y, max_y, width, height, iters)
    q = zeros(Complex128, (width, height))

    output = calc_fractal(q, min_x, max_x, min_y, max_y, width, height, iters)

    return output
end

x = -0.9223327810370947027656057193752719757635
y = 0.3102598350874576432708737495917724836010

f = 0.5
for i in 1:200
    tic()
    image = create_fractal(x-f, x+f, y-f, y+f, 500, 500, 400)
    dt = toq()

    @printf("Mandelbrot created in %f s\n", dt)

    GR.clearws()
    GR.setviewport(0, 1, 0, 1)
    GR.setcolormap(13)
    GR.cellarray(0, 1, 0, 1, 500, 500, image + 1000)
    GR.updatews()

    f *= 0.9
end


@testset "Poly" begin

  K = PadicField(2, 100)
  Kx, x = PolynomialRing(K, "x")
  L, gL = eisenstein_extension(x^2+2, "a")

  @testset "Fun Factor" for F in [K, L]
    Fx, x = PolynomialRing(F, "x")
    f = x^5
    for i = 0:4
      c = K(rand(FlintZZ, 1:100))
      f += c*x^i
    end
    u = 1
    for i = 1:5
      c = K(rand(FlintZZ, 1:100))
      u += 2*c*x^i
    end

    g = f*u
    u1, f1 = @inferred Hecke.fun_factor(g)
    @test u == u1
    @test f1 == f
  end

  @testset "Gcd" for F in [K, L]
    Fx, x = PolynomialRing(F, "x")
    f = (2*x+1)*(x+1)
    g = x^3+1
    gg = @inferred gcd(f, g)
    @test gg == x+1

    f = (2*x+1)*(x+1)
    g = (2*x+1)*(x+2)
    @test gcd(f, g) == 2*x+1

    f = (x + 1//K(2)) * (2*x^2+x+1)
    g = 2*x+1
    @test gcd(f, g) == g
  end

  @testset "Gcdx" for F in [K, L]
    Fx, x = PolynomialRing(F, "x")
    f = (2*x+1)*(x+1)
    g = x^3+1
    d, u, v = gcdx(f, g)
    @test d == gcd(f, g)
    @test u*f + v*g == d

    f = (2*x+1)*(x+1)
    g = (2*x+1)*(x+2)
    d, u, v = @inferred gcdx(f, g)
    @test gcd(f, g) == d
    @test d == u*f + v*g

    f = (x + 1//K(2)) * (2*x^2+x+1)
    g = 2*x+1
    d, u, v = gcdx(f, g)
    @test g == d
    @test u*f + v*g == d
  end

  @testset "Hensel" for F in [K, L]
    Fx, x = PolynomialRing(F, "x")
    f = (x+1)^3
    g = (x^2+x+1)
    h = x^2 +2*x + 8
    ff = f*g*h
    lf = @inferred Hecke.Hensel_factorization(ff)
    @test prod(values(lf)) == ff
  end

  @testset "Slope Factorization" for F in [K, L]
    Fx, x = PolynomialRing(F, "x")
    f = prod(x-2^i for i = 1:5)
    lf = @inferred Hecke.slope_factorization(f)
    @test prod(keys(lf)) == f
    @test all(x -> isone(degree(x)), keys(lf))
    @test length(Hecke.slope_factorization(2*x+1)) == 1
  end

  @testset "Resultant" begin
    R, x = PolynomialRing(PadicField(853, 2), "x")
    a = 4*x^5 + x^4 + 256*x^3 + 192*x^2 + 48*x + 4
    b = derivative(a)
    rab = @inferred resultant(a, b)
    @test rab == det(sylvester_matrix(a, b))
  end
end


using DSP, Distributions

t = linspace(0,5pi,512);
x1 = sin(t);
x2 = 0.4*cos(4t);
x3 = 0.2*cos(8t);
x = x1 + x2 + x3;

responsetype = Lowpass(0.2)
prototype = Elliptic(4, 0.5, 30)
tf = convert(TFFilter, digitalfilter(responsetype, prototype))
numerator_coefs   = coefb(tf)
denominator_coefs = coefa(tf)

responsetype = Bandpass(10, 40; fs=1000)
prototype = Butterworth(4)
xb = filt(digitalfilter(responsetype, prototype), x);

using Winston
plot(xb)


# We calculate the expected total number of collisions,
# from legitimate submissions: n - D + D((D-1)/D)^n
# n = # values, D = size of the output space
# source: http://matt.might.net/articles/counting-hash-collisions/
# To run this script install julia and run "julia collisions.jl".

bits = big"128"  # length of hash
ppl = 8000000000 # world population estimate
cens = 4*24*14   # CENs per person for two weeks, assuming rotation frequency of 15 minutes
n = ppl*cens     # total number of CENs for two weeks
H = 2^bits       # size of hash output space, uniform distribution is assumed

print(n - H + H*((H-1)/H)^n)


@testset "MumfordShah" begin
    p, q = synthetic_gradient(SynthSphere(15), img_size = 50)
    z = zeros(Float64, size(p))
    Z = MumfordShah(z=z)(p, q)
    @test Z[25,25] ≈ 10.598521326817886
    @test Z[1,1] ≈ -2.2208982959969585
    @test Z[38,38] ≈ -2.1579445391253027
end


@testset "unbiased.jl" begin
    @testset "Unbiased: Two-dimensional example" begin
        # categorical distributions
        skce = UnbiasedSKCE(SqExponentialKernel() ⊗ WhiteKernel())
        for predictions in ([[1, 0], [0, 1]], ColVecs([1 0; 0 1]), RowVecs([1 0; 0 1]))
            @test iszero(@inferred(skce(predictions, [1, 2])))
            @test iszero(@inferred(skce(predictions, [1, 1])))
            @test @inferred(skce(predictions, [2, 1])) ≈ -2 * exp(-1)
            @test iszero(@inferred(skce(predictions, [2, 2])))
        end

        # probabilities
        skce = UnbiasedSKCE(
            (SqExponentialKernel() ∘ ScaleTransform(sqrt(2))) ⊗ WhiteKernel()
        )
        @test iszero(@inferred(skce([1, 0], [true, false])))
        @test iszero(@inferred(skce([1, 0], [true, true])))
        @test @inferred(skce([1, 0], [false, true])) ≈ -2 * exp(-1)
        @test iszero(@inferred(skce([1, 0], [false, false])))
    end

    @testset "Unbiased: Basic properties" begin
        skce = UnbiasedSKCE((ExponentialKernel() ∘ ScaleTransform(0.1)) ⊗ WhiteKernel())
        estimates = Vector{Float64}(undef, 1_000)

        # categorical distributions
        for nclasses in (2, 10, 100)
            dist = Dirichlet(nclasses, 1.0)

            predictions = [Vector{Float64}(undef, nclasses) for _ in 1:20]
            targets = Vector{Int}(undef, 20)

            for i in 1:length(estimates)
                rand!.(Ref(dist), predictions)
                targets .= rand.(Categorical.(predictions))

                estimates[i] = skce(predictions, targets)
            end

            @test any(x -> x > zero(x), estimates)
            @test any(x -> x < zero(x), estimates)
            @test mean(estimates) ≈ 0 atol = 1e-3
        end

        # probabilities
        predictions = Vector{Float64}(undef, 20)
        targets = Vector{Bool}(undef, 20)
        for i in 1:length(estimates)
            rand!(predictions)
            map!(targets, predictions) do p
                rand() < p
            end
            estimates[i] = skce(predictions, targets)
        end

        @test any(x -> x > zero(x), estimates)
        @test any(x -> x < zero(x), estimates)
        @test mean(estimates) ≈ 0 atol = 1e-3
    end

    @testset "Block: Two-dimensional example" begin
        # categorical distributions
        skce = BlockUnbiasedSKCE(SqExponentialKernel() ⊗ WhiteKernel())
        for predictions in ([[1, 0], [0, 1]], ColVecs([1 0; 0 1]), RowVecs([1 0; 0 1]))
            @test iszero(@inferred(skce(predictions, [1, 2])))
            @test iszero(@inferred(skce(predictions, [1, 1])))
            @test @inferred(skce(predictions, [2, 1])) ≈ -2 * exp(-1)
            @test iszero(@inferred(skce(predictions, [2, 2])))
        end

        # two predictions, ten times replicated
        for predictions in (
            repeat([[1, 0], [0, 1]], 10),
            ColVecs(repeat([1 0; 0 1], 1, 10)),
            RowVecs(repeat([1 0; 0 1], 10, 1)),
        )
            @test iszero(@inferred(skce(predictions, repeat([1, 2], 10))))
            @test iszero(@inferred(skce(predictions, repeat([1, 1], 10))))
            @test @inferred(skce(predictions, repeat([2, 1], 10))) ≈ -2 * exp(-1)
            @test iszero(@inferred(skce(predictions, repeat([2, 2], 10))))
        end

        # probabilities
        skce = BlockUnbiasedSKCE(
            (SqExponentialKernel() ∘ ScaleTransform(sqrt(2))) ⊗ WhiteKernel()
        )
        @test iszero(@inferred(skce([1, 0], [true, false])))
        @test iszero(@inferred(skce([1, 0], [true, true])))
        @test @inferred(skce([1, 0], [false, true])) ≈ -2 * exp(-1)
        @test iszero(@inferred(skce([1, 0], [false, false])))

        # two predictions, ten times replicated
        @test iszero(@inferred(skce(repeat([1, 0], 10), repeat([true, false], 10))))
        @test iszero(@inferred(skce(repeat([1, 0], 10), repeat([true, true], 10))))
        @test @inferred(skce(repeat([1, 0], 10), repeat([false, true], 10))) ≈ -2 * exp(-1)
        @test iszero(@inferred(skce(repeat([1, 0], 10), repeat([false, false], 10))))
    end

    @testset "Block: Basic properties" begin
        nsamples = 20
        kernel = (ExponentialKernel() ∘ ScaleTransform(0.1)) ⊗ WhiteKernel()
        skce = UnbiasedSKCE(kernel)
        blockskce = BlockUnbiasedSKCE(kernel)
        blockskce_all = BlockUnbiasedSKCE(kernel, nsamples)
        estimates = Vector{Float64}(undef, 1_000)

        # categorical distributions
        for nclasses in (2, 10, 100)
            dist = Dirichlet(nclasses, 1.0)

            predictions = [Vector{Float64}(undef, nclasses) for _ in 1:nsamples]
            targets = Vector{Int}(undef, nsamples)

            for i in 1:length(estimates)
                rand!.(Ref(dist), predictions)
                targets .= rand.(Categorical.(predictions))

                estimates[i] = blockskce(predictions, targets)

                # consistency checks
                @test estimates[i] ≈ mean(
                    skce(predictions[(2 * i - 1):(2 * i)], targets[(2 * i - 1):(2 * i)]) for
                    i in 1:(nsamples ÷ 2)
                )
                @test skce(predictions, targets) == blockskce_all(predictions, targets)
            end

            @test any(x -> x > zero(x), estimates)
            @test any(x -> x < zero(x), estimates)
            @test mean(estimates) ≈ 0 atol = 5e-3
        end

        # probabilities
        predictions = Vector{Float64}(undef, nsamples)
        targets = Vector{Bool}(undef, nsamples)

        for i in 1:length(estimates)
            rand!(predictions)
            map!(targets, predictions) do p
                return rand() < p
            end
            estimates[i] = blockskce(predictions, targets)

            # consistency checks
            @test estimates[i] ≈ mean(
                skce(predictions[(2 * i - 1):(2 * i)], targets[(2 * i - 1):(2 * i)]) for
                i in 1:(nsamples ÷ 2)
            )
            @test skce(predictions, targets) == blockskce_all(predictions, targets)
        end

        @test any(x -> x > zero(x), estimates)
        @test any(x -> x < zero(x), estimates)
        @test mean(estimates) ≈ 0 atol = 5e-3
    end
end


using Tensors
using TreeTensors
using Base.Test

n = ModeTree!
mtree = link!(
    n(
        n([Mode(:a,2)]),
        n([Mode(:b,2)])
    ),
    n([Mode(:c,2)],
        n([Mode(:d,2)])
    )
)
randranks(mtree) = Dict(e => rand(1:5) for e in edges(mtree, root_to_leaves))
maxranks(mtree) = Dict(e => sqrt(msize(mode(mtree))) for e in edges(mtree, root_to_leaves))

x = rand(Complex{Float64},mode(mtree))
y = contract!(decompose(x, mtree, maxrank()))
@test_approx_eq_eps(norm(x-y), 0, 1e-14)

x = rand(Complex{Float64}, mtree, randranks(mtree))
y = rand(Complex{Float64}, mtree, randranks(mtree))
@test_approx_eq_eps(norm(contract(x) + contract(y) - contract!(x+y)), 0, 1e-10)

x = rand(Complex{Float64}, square(mtree), randranks(mtree))
y = rand(Complex{Float64}, square(mtree), randranks(mtree))
@test_approx_eq_eps(norm(contract(x)*contract(y) - contract!(x*y)), 0, 1e-10)

x = rand(Complex{Float64}, mtree, randranks(mtree))
@test_approx_eq_eps(norm(2*x - truncate!(x+x, adaptive(1e-12))), 0, 1e-10)

x = rand(Complex{Float64}, mtree, randranks(mtree))
y = rand(Complex{Float64}, mtree, randranks(mtree))
@test_approx_eq_eps(dot(x,y), dot(contract(x), contract(y)), 1e-10)

A = rand(Complex{Float64}, square(mtree), randranks(mtree))
b = rand(Complex{Float64}, mtree, randranks(mtree))
x = rand(Complex{Float64}, mtree, maxranks(mtree))
x,hist = als_solve!(x,A,b, DenseSolver())
@test_approx_eq_eps(norm(contract(A)*contract(x) - contract(b)), 0, 1e-10)

# GMRES doesn't work because IterativeSolvers.gmres! does
# not do restart = min(restart, size(x))
#x = rand(Complex{Float64}, mtree, maxranks(mtree))
#x,hist = als_solve!(x,A,b, GMRES(tol=1e-15, restart = 1000))
#@test_approx_eq_eps(norm(contract(A)*contract(x) - contract(b)), 0, 1e-12)

x = rand(Complex{Float64}, mtree, randranks(mtree))
y = rand(Complex{Float64}, mtree, randranks(mtree))
z = rand(Complex{Float64}, mtree, maxranks(mtree))
z,hist = als_sum!(z,[x,y])
@test_approx_eq_eps(norm(contract(x) + contract(y) - contract(z)), 0, 1e-10)

A = rand(Complex{Float64}, square(mtree), randranks(mtree))
x = rand(Complex{Float64}, mtree, randranks(mtree))
y = rand(Complex{Float64}, mtree, randranks(mtree))
z = rand(Complex{Float64}, mtree, maxranks(mtree))
z,hist = als_axpy!(z,A,x,y)
@test_approx_eq_eps(norm(contract(A)*contract(x) + contract(y) - contract(z)), 0, 1e-10)


using NonstationaryProcesses
using DynamicalSystems
using Distributions
using StatsPlots

sim = lorenzSim(
    X0 = [0.0, -0.01, 9.0],
    parameter_profile = (constantParameter, constantParameter, constantParameter),
    parameter_profile_parameters = (10.0, 28.0, 8/3), # Sprott's recomendation
    transient_t0 = -100.0,
    t0 = 0.0,
    dt = 0.001,
    savedt = 0.05,
    tmax = 50.0,
    alg = AutoVern7(Rodas5()),
    solver_opts = Dict(:adaptive => true, :reltol => 1e-15))

# High dim.
N = 2000
𝐃 = [Uniform(9.0, 14.0), Uniform(26.0, 56.0), Uniform(1.0, 2.8)];
ps = [rand.(𝐃) for i in 1:N];
𝒮 = [sim(ps = ps[i]) for i ∈ 1:N];

𝝀 = lyapunov.(𝒮)
density(𝝀, color=cornflowerblue, linewidth=2.5, xguide="λ", yguide="Density", label=nothing, dpi=600, bg=nothing, bginside=nothing)


"""
Original publication:
N. Görnitz, M. Kloft, K. Rieck, and U. Brefeld.
Toward supervised anomaly detection. Journal of
Artificial Intelligence Research (JAIR), pages
235–262, Jan. 2013.
"""
struct NeighborhoodBasedPQs <: HybridPQs
    occ::SVDD.OCClassifier
    knn::Array{Int,2}
    η::Float64
    k::Int
    NeighborhoodBasedPQs(occ::SVDD.OCClassifier, x::Array{T, 2}; η=0.5, k=5) where T <: Real = new(occ, knn_indices(x; k=k), η, k)
end

function qs_score(qs::NeighborhoodBasedPQs, x::Array{T, 2}, labels::Dict{Symbol, Array{Int, 1}})::Array{Float64,1} where T <: Real
    @assert size(qs.knn, 2) == size(x, 2)
    Lin_indices = SVDD.merge_pools(labels, :Lin)
    τ_nb = -(0.5 .+ 1 / (2 * qs.k) * [length(qs.knn[:, i] ∩ Lin_indices) for i in 1:size(x, 2)])
    return qs.η * -abs.(SVDD.predict(qs.occ, x)) + (1 - qs.η) * τ_nb
end


const hrep_doc = """
Return the equivalent halfspace representation of the convex hull, i.e.
matrix ``A`` and vector ``b`` such that the set of points inside the hull
is

```math
\\left\\{ x \\mid A x \\le b \\right\\}
```
"""

"""
$(SIGNATURES)

$hrep_doc

If `hull` is backed by a statically sized vector of vertices, the output `(A, b)`
will be statically sized as well. If the vector of vertices is additionally
immutable (e.g., a `StaticArrays.SVector`), then `hrep` will not perform any
dynamic memory allocation.
"""
hrep(hull::ConvexHull) = _hrep(hull, Length(vertices(hull)))

function _hrep(hull::ConvexHull, ::Length{N}) where N
    T = eltype(hull)
    R = arithmetic_closure(T)
    vertices = hull.vertices
    if N === StaticArrays.Dynamic()
        n = length(vertices)
        A = similar(vertices, R, n, 2)
        b = similar(vertices, R, n)
        hrep!(A, b, hull)
        return A, b
    else
        Amut = similar(vertices, R, Size(N, 2))
        bmut = similar(vertices, R, Size(N))
        hrep!(Amut, bmut, hull)
        A = convert(similar_type(vertices, R, Size(Amut)), Amut)
        b = convert(similar_type(vertices, R, Size(bmut)), bmut)
        return A, b
    end
end

"""
$(SIGNATURES)

$hrep_doc

This function stores its output in the (mutable) matrix `A` and vector `b`.
"""
@inline function hrep!(A::AbstractMatrix, b::AbstractVector, hull::ConvexHull)
    signop = edge_normal_sign_operator(hull)
    vertices = hull.vertices
    n = length(vertices)
    @boundscheck begin
        size(A, 1) >= n || throw(DimensionMismatch())
        size(A, 2) == 2 || throw(DimensionMismatch())
        size(A, 1) == length(b) || throw(DimensionMismatch)
    end
    if length(b) != n
        @inbounds A .= 0
        @inbounds b .= 0
    end
    @inbounds @simd for i = Base.OneTo(n)
        v1 = vertices[i]
        v2 = vertices[ifelse(i == n, 1, i + 1)]
        δ = v2 - v1
        δx, δy = unpack(δ)
        outward_normal = signop(SVector(δy, -δx))
        Ai = transpose(outward_normal)
        bi = Ai * v1
        A[i, 1] = Ai[1]
        A[i, 2] = Ai[2]
        b[i] = bi
    end
    nothing
end


import AbstractPlotting: plot, plot!, scatter, scatter!

# some plotting utilities
export plotif, trimplot, signchart
export arrow!, vectorfieldplot!
export newton_vis

###

#  plot "recipe" for functions

function AbstractPlotting.plot(f::Function, a::Number, b::Number, args...; kwargs...)
    xs = range(a, b, length=251)
    AbstractPlotting.plot(xs, f.(xs), args...; kwargs...)
end

function  AbstractPlotting.plot!(scene::AbstractPlotting.Scene, f::Function, args...; kwargs...)
    rect = scene.data_limits[]
    a, b = rect.origin[1],  rect.origin[1] + rect.widths[1]
    xs = range(a, b, length=500)
    AbstractPlotting.plot!(scene, a..b, f, args...;  kwargs...)
end

AbstractPlotting.plot!(f::Function, args...; kwargs...) = AbstractPlotting.plot!(AbstractPlotting.current_scene(), f, args...; kwargs...)

#  plot "recipe" for 2D parametric functions
function AbstractPlotting.plot(f::Function, g::Function, a::Number, b::Number, args...; kwargs...)
    xs = range(float(a), float(b), length=500)
    AbstractPlotting.lines(f.(xs), g.(xs), args...; kwargs...)
end

function  AbstractPlotting.plot!(scene::AbstractPlotting.Scene, f::Function, g::Function, a::Number, b::Number, args...; kwargs...)
    xs = range(a, stop=b, length=500)
    AbstractPlotting.plot!(scene, f.(xs), g.(xs), args...;  kwargs...)
end
AbstractPlotting.plot!(f::Function, g::Function, a::Number, b::Number, args...; kwargs...) =
    AbstractPlotting.plot!(AbstractPlotting.current_scene(), f, g, a, b, args...; kwargs...)

#  plot reciple  for SymPy objects
AbstractPlotting.plot(ex::Sym, a::Number, b::Number; kwargs...) = AbstractPlotting.plot(lambdify(ex), a, b; kwargs...)
AbstractPlotting.plot!(ex::Sym; kwargs...) = AbstractPlotting.plot!(lambdify(ex); kwargs...)

AbstractPlotting.plot(ex1::Sym, ex2::Sym,  a::Number, b::Number; kwargs...) =
    AbstractPlotting.plot(lambdify(ex1), lambdify(ex2), a, b; kwargs...)
AbstractPlotting.plot!(ex1::Sym, ex2::Sym,  a::Number, b::Number; kwargs...) =
    AbstractPlotting.plot!(lambdify(ex1), lambdify(ex2), a,b; kwargs...)

# Plot of tuple
# d= 1
# d>1 parametric
function AbstractPlotting.plot(fs::Tuple, a::Number, b::Number; kwargs...)
    xs = range(a, stop=b, length=500)
    ys = [ [f(x) for x in xs] for f in fs]
    if length(ys) == 1
        AbstractPlotting.lines(xs, ys[1]; kwargs...)
    else
        AbstractPlotting.lines(ys...; kwargs...)
    end
end

function AbstractPlotting.plot!(fs::Tuple, args...; kwargs...)
    xs = range(args[1], stop=args[2], length=500)
    ys = [ [f(x) for x in xs] for f in fs]
    AbstractPlotting.plot!(ys...; kwargs...)
end

## Parametric surface plot
using GeometryBasics
function make_vertices(r, xs, ys)
    [Point3f0(r(u,v))  for v in ys for u in xs]
end

# single row of faces
function make_facesⱼ(xs, ys, j)
    nx, ny = length(xs), length(ys)
    geti = (i,j) -> (j-1)*nx + (i-1) + 1
    faces = TriangleFace{Int}[]
    for i in 1:nx-1
        f1 = TriangleFace(geti(i,j), geti(i,j+1), geti(i+1,j+1))
        f2 = TriangleFace(geti(i+1,j+1), geti(i,j), geti(i+1,j))
        append!(faces, (f1, f2))
    end

    faces
end

parametric_surface_plot(r, xs, ys; kwargs...) = parametric_surface_plot!(Scene(), r, xs, ys; kwargs...)
function parametric_surface_plot!(scene, r, xs, ys; kwargs...)
    nx, ny = length(xs), length(ys)

    vertices = make_vertices(r, xs, ys)

    for j in 1:ny-1
        facesⱼ =  make_facesⱼ(xs, ys, j)
        wireframe!(scene, GeometryBasics.Mesh(vertices, facesⱼ); kwargs...)
    end

    scene
end
export parametric_surface_plot, parametric_surface_plot!

##
## --------------------------------------------------
##

"""
   trimplot(f, a, b, c=20; kwargs...)

Plot f over [a,b] but break graph if it exceeds c in absolute value.
"""
function trimplot(f, a, b, c=20; kwargs...)
    plot(x -> abs(f(x)) <= c ? f(x) : NaN, a, b; kwargs...)
end

"""
    plotif(f, g, a, b)

Plot f colored depending on g >= 0 or not.
"""
function plotif(f, g, a, b)
    xs = range(a, b, length=251)
    cols = identify_colors(g, xs)
    cols = push!(cols, cols[end])
    lines(xs, f, color=cols, linewidth=10)
end

"""
   signchart(f, a, b)

Plot f over a,b with different color when negative.
"""
function signchart(f, a, b)
    p = plotif(f, f, a, b)
    plot!(p, zero)
    p
end

"""
   `arrow!(p, v)`

Add the vector `v` to the plot anchored at `p`.

This would just be a call to `quiver`, but there is no 3-D version of that. As well, the syntax for quiver is a bit awkward for plotting just a single arrow. (Though efficient if plotting many).

```
using AbstractPlotting
r(t) = [sin(t), cos(t), t]
rp(t) = [cos(t), -sin(t), 1]
lines(unzip(r, 0, 2pi)...)
t0 = 1
arrow!(r(t0), r'(t0))
```
"""
function arrow!(scene::AbstractPlotting.Scene, p, v; kwargs...)
    P = length(p) == 3 ? Point3f0 : Point2f0
    AbstractPlotting.arrows!(P.([p]), P.([v]); kwargs...)
end
arrow!(p,v;kwargs...) = arrow!(AbstractPlotting.current_scene(), p, v; kwargs...)

"""

    vectorfieldplot(V; xlim=(-5,5), ylim=(-5,5), n=10; kwargs...)

V is a function that takes a point and returns a vector (2D dimensions), such as `V(x) = x[1]^2 + x[2]^2`.

The grid `xlim × ylim` is paritioned into (n+1) × (n+1) points. At each point, `pt`, a vector proportional to `V(pt)` is drawn.

This is written to add to an existing plot.

```
plot()  # make a plot
V(x,y) = [x, y-x]
vectorfield_plot!(p, V)
p
```
"""
function vectorfieldplot( V; xlim=(-5,5), ylim=(-5,5), n=10, kwargs...)

    V′(x) = Point2f0(V(x...)...)
    AbstractPlotting.streamplot(V′, xlim[1]..xlim[2], ylim[1]..ylim[2]; kwargs...)
end
function vectorfieldplot!(scene::AbstractPlotting.Scene, V; xlim=(-5,5), ylim=(-5,5), n=10, kwargs...)

    V′(x) = Point2f0(V(x...)...)
    AbstractPlotting.streamplot!(scene, V′, xlim[1]..xlim[2], ylim[1]..ylim[2]; kwargs...)
end
vectorfieldplot!(V; kwargs...) =
    vectorfieldplot!(AbstractPlotting.current_scene(), V; kwargs...)

## --------------------------------------------------

##
# visualize newtons method
function newton_vis(f, x0, a=Inf,b=-Inf; steps=5, kwargs...)
    xs = Float64[x0]
    for i in 1:steps
        xᵢ = xs[end]
        xᵢ₊₁ = xᵢ - f(xᵢ)/f'(xᵢ)
        push!(xs, xᵢ₊₁)
    end

    m,M = extrema(xs)
    m = min(m, a)
    M = max(M, b)

    p = plot(f, m, M; linewidth=3,  kwargs...)
    plot!([m,M], [0,0])
    plot!(p, zero)
    for i in 1:steps
        plot!(p, [xs[i],xs[i],xs[i+1]], [0,f(xs[i]), 0])
        scatter!(p, xs[i:i],[0], markersize=1)
    end
    scatter!(p, [xs[steps+1]], [0], markersize=1)
    p
end


import BlockArrays: BlockSizes, BlockIndex, globalrange, nblocks, global2blockindex, blockindex2global

#=
[1,1  1,2] | [1,3  1,4  1,5]
--------------------------
[2,1  2,2] | [2,3  2,4  2,5]
[3,1  3,2] | [3,3  3,4  3,5]
----------------------------
[4,1  4,2] | [4,3  4,4  4,5]
[5,1  5,2] | [5,3  5,4  5,5]
[6,1  6,2] | [6,3  6,4  6,5]
=#

block_size = BlockSizes([1,2,3], [2, 3])

@test nblocks(block_size) == (3,2)
@test nblocks(block_size, 1) == 3
@test nblocks(block_size, 2) == 2

@inferred globalrange(block_size, (1,1)) == (1:1, 1:2)
@inferred globalrange(block_size, (1,2)) == (1:1, 3:5)
@inferred globalrange(block_size, (2,1)) == (2:3, 1:2)
@inferred globalrange(block_size, (2,2)) == (2:3, 3:5)

@inferred global2blockindex(block_size, (3, 1)) == BlockIndex((2,1), (2,1))
@inferred global2blockindex(block_size, (1, 4)) == BlockIndex((1,2), (1,2))
@inferred global2blockindex(block_size, (4, 5)) == BlockIndex((3,2), (1,3))

@inferred blockindex2global(block_size, BlockIndex((2,1), (2,1))) == (3, 1)
@inferred blockindex2global(block_size, BlockIndex((1,2), (1,2))) == (1, 4)
@inferred blockindex2global(block_size, BlockIndex((3,2), (1,3))) == (4, 5)

buf = IOBuffer()
print(buf, block_size)
@test takebuf_string(buf) == "[1,2,3]×[2,3]"


module MomentWrapper

using JavaCall

const AbstractStorelessUnivariateStatistic = @jimport org.hipparchus.stat.descriptive.AbstractStorelessUnivariateStatistic
const AbstractUnivariateStatistic = @jimport org.hipparchus.stat.descriptive.AbstractUnivariateStatistic
const AggregatableStatistic = @jimport org.hipparchus.stat.descriptive.AggregatableStatistic
const Class = @jimport java.lang.Class
const DoubleConsumer = @jimport java.util.function.DoubleConsumer
const FirstMoment = @jimport org.hipparchus.stat.descriptive.moment.FirstMoment
const FourthMoment = @jimport org.hipparchus.stat.descriptive.moment.FourthMoment
const GeometricMean = @jimport org.hipparchus.stat.descriptive.moment.GeometricMean
const Iterable = @jimport java.lang.Iterable
const JString = @jimport java.lang.String
const Kurtosis = @jimport org.hipparchus.stat.descriptive.moment.Kurtosis
const Mean = @jimport org.hipparchus.stat.descriptive.moment.Mean
const Object = @jimport java.lang.Object
const SecondMoment = @jimport org.hipparchus.stat.descriptive.moment.SecondMoment
const SemiVariance = @jimport org.hipparchus.stat.descriptive.moment.SemiVariance
const SemiVariance_Direction = @jimport org.hipparchus.stat.descriptive.moment.SemiVariance$Direction
const Skewness = @jimport org.hipparchus.stat.descriptive.moment.Skewness
const StandardDeviation = @jimport org.hipparchus.stat.descriptive.moment.StandardDeviation
const StorelessUnivariateStatistic = @jimport org.hipparchus.stat.descriptive.StorelessUnivariateStatistic
const SumOfLogs = @jimport org.hipparchus.stat.descriptive.summary.SumOfLogs
const ThirdMoment = @jimport org.hipparchus.stat.descriptive.moment.ThirdMoment
const UnivariateStatistic = @jimport org.hipparchus.stat.descriptive.UnivariateStatistic
const Variance = @jimport org.hipparchus.stat.descriptive.moment.Variance
const WeightedEvaluation = @jimport org.hipparchus.stat.descriptive.WeightedEvaluation

export FirstMoment
export FourthMoment
export GeometricMean
export Kurtosis
export Mean
export SecondMoment
export SemiVariance
export Skewness
export StandardDeviation
export ThirdMoment
export Variance
export accept
export aggregate
export and_then
export clear
export copy
export equals
export evaluate
export get_class
export get_data
export get_n
export get_result
export get_variance_direction
export hash_code
export increment
export increment_all
export is_bias_corrected
export notify
export notify_all
export set_data
export to_string
export wait
export with_bias_corrected
export with_bias_correction
export with_variance_direction

include("first_moment.jl")
include("fourth_moment.jl")
include("geometric_mean.jl")
include("kurtosis.jl")
include("mean.jl")
include("second_moment.jl")
include("semi_variance.jl")
include("skewness.jl")
include("standard_deviation.jl")
include("third_moment.jl")
include("variance.jl")

end


using Optim, HTTP, GLM, LinearAlgebra, Random, Statistics, DataFrames, DataFramesMeta, CSV

#Worked with Ahmed (our class), Aleeze, and Waleed.
function ps6()
include("create_grids.jl")

#Question 1
url = "https://raw.githubusercontent.com/OU-PhD-Econometrics/fall-2020/master/ProblemSets/PS5-ddc/busdata.csv"
df = CSV.read(HTTP.get(url).body,DataFrame)

# create bus id variable
df = @transform(df, bus_id = 1:size(df,1))

#---------------------------------------------------
# reshape from wide to long (must do this twice be-
# cause DataFrames.stack() requires doing it one
# variable at a time)
#---------------------------------------------------
# first reshape the decision variable
dfy = @select(df, :bus_id,:Y1,:Y2,:Y3,:Y4,:Y5,:Y6,:Y7,:Y8,:Y9,:Y10,:Y11,:Y12,:Y13,:Y14,:Y15,:Y16,:Y17,:Y18,:Y19,:Y20,:RouteUsage,:Branded)
dfy_long = DataFrames.stack(dfy, Not([:bus_id,:RouteUsage,:Branded]))
rename!(dfy_long, :value => :Y)
dfy_long = @transform(dfy_long, time = kron(collect([1:20]...),ones(size(df,1))))
select!(dfy_long, Not(:variable))

# next reshape the odometer variable
dfx = @select(df, :bus_id,:Odo1,:Odo2,:Odo3,:Odo4,:Odo5,:Odo6,:Odo7,:Odo8,:Odo9,:Odo10,:Odo11,:Odo12,:Odo13,:Odo14,:Odo15,:Odo16,:Odo17,:Odo18,:Odo19,:Odo20)
dfx_long = DataFrames.stack(dfx, Not([:bus_id]))
rename!(dfx_long, :value => :Odometer)
dfx_long = @transform(dfx_long, time = kron(collect([1:20]...),ones(size(df,1))))
select!(dfx_long, Not(:variable))

# join reshaped df's back together
df_long = leftjoin(dfy_long, dfx_long, on = [:bus_id,:time])
sort!(df_long,[:bus_id,:time])

#2
flx = glm(@formula(Y ~ Odometer*(Odometer)^2*RouteUsage*(RouteUsage)^2*Branded*time*(time)^2), df_long, Binomial(), LogitLink())
println(flx)

#3A
url = "https://raw.githubusercontent.com/OU-PhD-Econometrics/fall-2020/master/ProblemSets/PS5-ddc/busdata.csv"
df2 = CSV.read(HTTP.get(url).body, DataFrame)
Y = Matrix(df[:,[:Y1,:Y2,:Y3,:Y4,:Y5,:Y6,:Y7,:Y8,:Y9,:Y10,:Y11,:Y12,:Y13,:Y14,:Y15,:Y16,:Y17,:Y18,:Y19,:Y20]])
X = Matrix(df[:,[:Odo1,:Odo2,:Odo3,:Odo4,:Odo5,:Odo6,:Odo7,:Odo8,:Odo9,:Odo10,:Odo11,:Odo12,:Odo13,:Odo14,:Odo15,:Odo16,:Odo17,:Odo18,:Odo19,:Odo20]])
Z = Vector(df[:,:RouteUsage])
B = Vector(df[:,:Branded])
N = size(Y,1)
T = size(Y,2)
Xstate = Matrix(df[:,[:Xst1,:Xst2,:Xst3,:Xst4,:Xst5,:Xst6,:Xst7,:Xst8,:Xst9,:Xst10,:Xst11,:Xst12,:Xst13,:Xst14,:Xst15,:Xst16,:Xst17,:Xst18,:Xst19,:Xst20]])
Zstate = Vector(df[:,:Zst])

zval,zbin,xval,xbin,xtran = create_grids()

D = DataFrame()
                D.Odometer = kron(ones(zbin), xval)
                D.RouteUsage = kron(zval, ones(xbin))
                D.time= zeros(size(xtran,1))
                D.Branded = zeros(size(xtran,1))

function values(din,Zstate,Xstate,xtran,zbin,xbin,xval)

    FVARRY=zeros(size(xtran,1),2,T+1)
    for t=2:T
        for b=0:1
            din.time[t]=t
            din.Branded[t]=b
            p_0 = predict(θ, din)

            FVARRY[:, b+1, t] = - .9 .*log.(p_0)
        end
    end
    FTV1 = zeros(size(df,1), T)
    for i=1:size(df,1)
        row0 = (Zstate[i]-1)*xbin+1
        for t=1:T
            row1  = Xstate[i,t] + (Zstate[i]-1)*xbin
            FTV1[i,t] = (xtran[row1,:].-xtran[row0,:])'*FV[row0:row0+xbin-1,df.Branded[i]+1,t+1]
        end
    end

    return FVT1'[:]
end

ftv1 = futval(flexlog, Zstate,Xstate,xtran,zbin,xbin,E)
df_long = @transform(df_long, fv=ftv1)

theta_hat_ccp_glm = glm(@formula(Y ~ Odometer + Branded),
                    df_long, Binomial(), LogitLink(),
                    offset=df_long.fv)
println(theta_ccp)
end

@time ps6()


using GLPKMathProgInterface, GLPK
using MathProgBase
using Compat.Test

include("params.jl")
include("setbounds.jl")
include("mathprog.jl")


using Plots

function cplx_heatmap(M)
    plot(
        heatmap(abs.(M)),
        heatmap(real.(M)),
        heatmap(imag.(M)),
        layout = (1,3),
        size = (1600,400)
    )
end

function interp_nd(G, P, X, Y)
iv = zeros(size(P)[1], length(X))
for i ∈ 1:length(X)
    index_x = argmin(abs.(G.-X[i]))
    index_y = argmin(abs.(G.-Y[i]))

    iv[:, i] = P[:, index_y, index_x]
end
return iv
end

function interp(G, P, X, Y)
iv = zeros(length(X))
for i ∈ 1:length(X)
    index_x = argmin(abs.(G.-X[i]))
    index_y = argmin(abs.(G.-Y[i]))

    iv[i] = P[index_y, index_x]
end
return iv
end


@non_differentiable id(V::VectorSpace)
@non_differentiable isomorphism(cod::VectorSpace, dom::VectorSpace)
@non_differentiable domain(W::TensorKit.HomSpace)
@non_differentiable domain(t::AbstractTensorMap)
@non_differentiable domain(t::AbstractTensorMap, i)
@non_differentiable codomain(W::TensorKit.HomSpace)
@non_differentiable codomain(t::AbstractTensorMap)
@non_differentiable codomain(t::AbstractTensorMap, i)

convert_to_array(X::AbstractTensorMap) = convert(Array, X)

function rrule(::typeof(convert_to_array), X::AbstractTensorMap)

    fwd = convert_to_array(X)
    space_X = space(X)

    function convert_to_array_pushback(f̄wd)
        return NoTangent(), TensorMap(f̄wd, space_X)
    end

    return fwd, convert_to_array_pushback
end

"""
    rrule(::typeof(TensorMap), data::A, codom::ProductSpace, dom::ProductSpace) where A <: DenseMatrix

    rrule for TensorMap construction. only did the simplest case.
    FIXME. seems buggy
"""
function rrule(::typeof(TensorMap), data::A, codom::ProductSpace, dom::ProductSpace) where A <: DenseMatrix
    fwd = TensorMap(data, codom, dom)
    function TensorMap_pushback(f̄wd)
        return NoTangent(), f̄wd.data, NoTangent(), NoTangent()
    end
    return fwd, TensorMap_pushback
end

"""
    rrule(::typeof(tr), A::TensorMap)

    rrule for trace operation.
"""
function rrule(::typeof(tr), A::TensorMap)
    fwd = tr(A)
    function tr_pushback(f̄wd)
        Ā = f̄wd * id(domain(A))
        return NoTangent(), Ā
    end
    return fwd, tr_pushback
end

"""
    elem_mult(a::AbstractTensorMap,b::AbstractTensorMap)

    Element-wise multiplication between TensorMaps. Copied from TensorKitAD.jl.
"""
function elem_mult(a::AbstractTensorMap,b::AbstractTensorMap)
    dst = similar(a);
    for (k,block) in blocks(dst)
        copyto!(block,blocks(a)[k].*blocks(b)[k]);
    end
    dst
end

"""
    rrule(::typeof(elem_mult), a::AbstractTensorMap, b::AbstractTensorMap)
"""
function rrule(::typeof(elem_mult), a::AbstractTensorMap, b::AbstractTensorMap)
    fwd = similar(a);
    for (k,block) in blocks(fwd)
        copyto!(block,blocks(a)[k].*blocks(b)[k]);
    end

    function elem_mult_pushback(f̄wd)
        ā, b̄ = similar(a), similar(b)
        for (k, block) in blocks(f̄wd)
            copyto!(blocks(ā)[k], block .* conj.(blocks(b)[k]))
            copyto!(blocks(b̄)[k], block .* conj.(blocks(a)[k]))
        end

        return NoTangent(), ā, b̄
    end
    return fwd, elem_mult_pushback
end


# -*- coding: utf-8 -*-
# ---
# jupyter:
#   jupytext:
#     formats: ipynb,jl:hydrogen
#     text_representation:
#       extension: .jl
#       format_name: hydrogen
#       format_version: '1.3'
#       jupytext_version: 1.11.2
#   kernelspec:
#     display_name: Julia 1.6.2
#     language: julia
#     name: julia-1.6
# ---

# %%
using Plots

f(t, x) = exp(-x^2/(2t))/√(2π*t)
u(t, x) = f(t+0.1, x+2) + 2f(t+0.2, x-2)
t = range(0, 3; length=101)
x = range(-5, 5; length=101)
T = u.(t', x)

surface(t, x, T; colorbar=false,
    xlabel="t", ylabel="x", zlabel="T",
    camera=(30, 70), color=reverse(cgrad(:RdYlBu_11)))

# %%
#using LinearAlgebra, SparseArrays, Plots, DifferentialEquations
#using Surrogates
using Plots
#gr()
L=20
N=100
k=50
x=range(0, stop=100, length=100);
dx=x[2]-x[1];
dt=0.5*dx^2/(2*k);
t=0:dt:10
# print("the vlues of t",t)
#T=zeros(length(t),length(x))
T=zeros(length(x),length(t))
s=size(T)
print("size of matrix",s)
#T[1,2]=T[1,1]=100 #complete 1st row
#T[end,end]=150  #last row of a matrix
@. T[:, 1] = exp(-(x-50)^2/(2*5^2))
for j in 1:1960
    for i=2:99
        T[i,j+1]=T[i,j]+k*dt/(dx^2)*(T[i+1,j]-2*T[i,j] + T[i-1,j])
    end
end
surface(t, x, T; color=:gist_earth)

# %%
#using LinearAlgebra, SparseArrays, Plots, DifferentialEquations
#using Surrogates
using Plots
#gr()
L=20
N=100
k=50
x=range(0, stop=100, length=100);
dx=x[2]-x[1];
dt=0.5*dx^2/(2*k);
t=0:dt:10
# print("the vlues of t",t)
#T=zeros(length(t),length(x))
T=zeros(length(x),length(t)) # t <--> x
s=size(T)
print("size of matrix",s)
#T[1,2]=T[1,1]=100 #complete 1st row
#T[end,end]=150  #last row of a matrix
@. T[:, 1] = exp(-(x-50)^2/(2*5^2)) # initial values
#for i in 2:1960
for j in 1:1960 # i -> j
#    for j=1:99
    for i=2:99 # j -> i, 1:99 -> 2:99
        T[i,j+1]=T[i,j]+k*dt/(dx^2)*(T[i+1,j]-2*T[i,j] + T[i-1,j])
    end
end
#surface(x,t,T)
surface(t,x,T) # x <--> t

# %%
T

# %%
using Plots

k = 50
x = range(0, 100; length=100);
dx = step(x)
dt = 0.5*dx^2/(2k)
t = 0:dt:10
T = zeros(length(x), length(t))
U = zeros(length(x), length(t))

@. T[:, 1] = exp(-(x-50)^2/(2*5^2))

for i in eachindex(x)
    U[i, 1] = exp(-(x[i]-50)^2/(2*5^2))
end

@show T ≈ U

plot(x, T[:, 1]; label="T[:,1]", lw=2)
plot!(x, U[:, 1]; label="U[:,1]", lw=2, ls=:dash)

# %%
@show T[:, 1];

# %%
plot(T[:, 1])

# %%
?@.

# %%
@show VERSION
using Pkg
Pkg.status("Plots")
Pkg.status("PlotUtils"; mode=PKGMODE_MANIFEST)
Pkg.status("GR"; mode=PKGMODE_MANIFEST)

# %%

0015/Surrogates/3D plot example - heat eq.jl


consts=@equs(c=3e8, G=6.67e-11, massSun=2e30)
E=@equ m*v^2/2=G*M*m/R
R=E*:R/:m/:v^2*2
R_BH=R&@equs(v=c, M=massSun)&consts
@test floor(R_BH.rhs)==2964
R=U(6.67e-11,:m^3/:s^2/:kg)*U(2e30,:kg)/U(3e8,:m/:s)/U(3e8,:m/:s)*2
@test R.units==:m

include("../examples/LQG.jl")


using Random: rand, RandomDevice
using Primes: nextprime
"""
    symmetric_key(bytes::Int64)

Generates a symmetric key with the number of bytes given by `bytes`.
"""
symmetric_key(bytes::Int64) = rand(RandomDevice(), UInt8, bytes)

function asymmetric_key(bits=20, e=2^16+1)
    lower = big(1) << (bits - 1)
    upper = (big(1) << bits) - 1

    p = nextprime(rand(RandomDevice(), lower:upper))
    q = nextprime(rand(RandomDevice(), lower:upper))

    n = p * q
    # lambda = lcm(p-1, q-1)
    d = invmod(e, (p - 1) * (q - 1))

    public_key = (n, e)
    private_key = (n, d)

    return (public_key, private_key)
end

sbox(bytes) = map(byte -> [
    0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
    0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
    0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
    0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
    0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
    0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
    0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
    0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
    0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
    0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
    0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
    0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
    0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
    0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
    0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
    0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16,
][byte+1], bytes)  # Julia is 1-indexed

invsbox(bytes) = map(byte -> [
    0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
    0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
    0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
    0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
    0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
    0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
    0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
    0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
    0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
    0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
    0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
    0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
    0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
    0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
    0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
    0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d,
][byte+1], bytes)

"""
Generate the subkeys for the AES encryption standard given a key.
    https://embeddedsw.net/Cipher_Reference_Home.html
    https://en.wikipedia.org/wiki/AES_key_schedule
    https://cryptography.fandom.com/wiki/Rijndael_key_schedule
"""
function rijndael_key_schedule(key::Vector{UInt8}, r)
    rot_word(bytes::Vector{UInt8}) = UInt8[bytes[2], bytes[3], bytes[4], bytes[1]]

    function genrcon(n::Integer)  # no longer needed
        result = UInt8[0x01]
        for i in 2:n
            rc_i_minus_one = result[end]
            rc_i = 0x02*rc_i_minus_one
            if rc_i_minus_one < 0x80
                push!(result, rc_i)
            else
                push!(result, xor(rc_i, 0x1b))
            end
        end
        return result
    end

    function nextrcon(rc_i_minus_one)
        rc_i = 0x02*rc_i_minus_one
        if rc_i_minus_one < 0x80
            return rc_i
        else
            return xor(rc_i, 0x1b)
        end
    end

    w = [key[i:(i+3)] for i in 1:4:length(key)]
    n = length(w)
    #rcon = genrcon(4*(r+1) % 256)  # rcon_i = x^(254+i) mod x^8 + x^4 + x^3 + x + 1
    rcon = 0x01

    for i in (n+1):(4*(r+1))
        if (i-1) % n == 0
            push!(w, xor.(w[i-n], sbox(rot_word(w[i-1])), [rcon, 0x00, 0x00, 0x00]))
            rcon = nextrcon(rcon)
        elseif (i-1)% n == 4 && n > 6
            push!(w, xor.(w[i-n], sbox(w[i-1])))
        else
            push!(w, xor.(w[i-n], w[i-1]))
        end
    end

    return w
end


using AdvancedHMC, Distributions, ForwardDiff

# Choose parameter dimensionality and initial parameter value
D = 10; initial_θ = rand(D)

dist = Normal() ^ D

# Define the target distribution
ℓπ(θ) = logdensity(dist, θ)

# Set the number of samples to draw and warmup iterations
n_samples, n_adapts = 2_000, 1_000

# Define a Hamiltonian system
metric = DenseEuclideanMetric(D)
hamiltonian = Hamiltonian(metric, ℓπ, ForwardDiff)

# Define a leapfrog solver, with initial step size chosen heuristically
initial_ϵ = find_good_stepsize(hamiltonian, initial_θ)
integrator = Leapfrog(initial_ϵ)

# Define an HMC sampler, with the following components
#   - multinomial sampling scheme,
#   - generalised No-U-Turn criteria, and
#   - windowed adaption for step-size and diagonal mass matrix
proposal = NUTS{MultinomialTS, GeneralisedNoUTurn}(integrator)
adaptor = StanHMCAdaptor(MassMatrixAdaptor(metric), StepSizeAdaptor(0.8, integrator))

# Number of chains to sample
nchains = 4

# Cache to store the chains
chains = Vector{Any}(undef, nchains)

# The `samples` from each parallel chain is stored in the `chains` vector
# Adjust the `verbose` flag as per need
Threads.@threads for i in 1:nchains
  samples, stats = sample(hamiltonian, proposal, initial_θ, n_samples, adaptor, n_adapts; verbose=true)
  chains[i] = samples
end


using Primes

@testset "Chineese remainder theorem" begin

    modular_images(a, ms) = map(m -> mod(a, m), ms)

    Ms = [
        [2, 3],
        [3, 7],
        [2^31 - 1, Primes.nextprime(2^31)],
        [Primes.nextprime(BigInt(2)^100), Primes.nextprime(BigInt(2)^31)],
        [Primes.nextprime(BigInt(2)^10000), Primes.nextprime(BigInt(2)^31)]
    ]
    buf, buf1, buf2, buf3, n1, n2 = [BigInt(0) for _ in 1:6]

    for ms in Ms
        M = prod(ms)
        nums = [rand(0:M-1) for _ in 1:1000]

        msinv = [invmod(ms[1], ms[2]), invmod(ms[2], ms[1])]
        for a in nums
            rs = modular_images(a, ms)

            r1, r2 = BigInt.(rs)
            m1, m2 = BigInt.(ms)
            minv1, minv2 = BigInt.(msinv)
            Groebner.CRT!(M, buf, n1, n2, r1, minv1, UInt64(r2), minv2, m1, m2)
            @test buf == a
        end

    end

end


using Revise
using AdFem
using PyCall
using LinearAlgebra
using ADCME
using MAT
using PyPlot
np = pyimport("numpy")

# Domain information
NT = 10
Δt = 1/NT
n = 20
m = n
h = 1.0/n
bdnode = Int64[]
for i = 1:m+1
    for j = 1:n+1
        if i==1 || i==m+1 || j==1|| j==n+1
            push!(bdnode, (j-1)*(m+1)+i)
        end
    end
end

is_training = false
b = 1.0
# Physical parameters
if !is_training
    E = 1.0
    ν = 0.35
    global H = E/(1-ν^2)*[1.0 ν 0
        ν 1 0
        0 0 1-ν]|>constant
else
    H = diagm(0=>ones(3))
    global H = Variable(H)
end
Q = SparseTensor(compute_fvm_tpfa_matrix(m, n, h))
K = compute_fem_stiffness_matrix(H, m, n, h)
L = SparseTensor(compute_interaction_matrix(m, n, h))
M = SparseTensor(compute_fvm_mass_matrix(m, n, h))
A = [K -b*L'
b*L/Δt 1/Δt*M-Q]
A, Abd = fem_impose_coupled_Dirichlet_boundary_condition(A, bdnode, m, n, h)
# error()
U = zeros(m*n+2(m+1)*(n+1), NT+1)
x = Float64[]; y = Float64[]
for j = 1:n+1
    for i = 1:m+1
        push!(x, (i-1)*h)
        push!(y, (j-1)*h)
    end
end

injection = (div(n,2)-1)*m + 3
production = (div(n,2)-1)*m + m-3

function condition(i, ta_u)
    i<=NT
end

function body(i, ta_u)
    u = read(ta_u, i)
    bdval = zeros(2*length(bdnode))
    rhs1 = vector([bdnode; bdnode.+ (m+1)*(n+1)], bdval, 2(m+1)*(n+1))
    rhs2 = zeros(m*n)
    rhs2[injection] += 1.0
    rhs2[production] -= 1.0
    rhs2 += b*L*u[1:2(m+1)*(n+1)]/Δt +
            M * u[2(m+1)*(n+1)+1:end]/Δt
    rhs = [rhs1;rhs2]
    rhs -= Abd * bdval
    o = A\rhs
    ta_u = write(ta_u, i+1, o)
    i+1, ta_u
end

i = constant(1, dtype=Int32)
ta_u = TensorArray(NT+1)
ta_u = write(ta_u, 1, constant(zeros(2(m+1)*(n+1)+m*n)))
_, u_out = while_loop(condition, body, [i, ta_u]; parallel_iterations=1)
u_out = stack(u_out)
u_out.set_shape((NT+1, size(u_out,2)))

upper_idx = Int64[]
for i = 1:m+1
    push!(upper_idx, (div(n,3)-1)*(m+1)+i)
    push!(upper_idx, (div(n,3)-1)*(m+1)+i + (m+1)*(n+1))
end
for i = 1:m
    push!(upper_idx, (div(n,3)-1)*m+i+2(m+1)*(n+1))
end

if is_training
    Ue = matread("U.mat")["U"]
    loss = sum((u_out[:, upper_idx] - Ue)^2)
    sess = Session(); init(sess)
    loss_ = BFGS!(sess, loss, 500)

    close("all")
    semilogy(loss_)
    xlabel("Iterations")
    ylabel("Loss")
    savefig("loss.png")
else
    sess = Session(); init(sess)
    U = run(sess, u_out)
    matwrite("U.mat", Dict("U"=> U[:, upper_idx]))
    visualize_displacement(U'|>Array, m, n, h, name="_tf")
    visualize_pressure(U'|>Array, m, n, h, name="_tf")

end


@testset "Pseudodivision" begin
    R, (x, y, z) = Nemo.PolynomialRing(Nemo.QQ, ["x", "y", "z"])

    @test iszero(pseudodivision(x^2 - y^2, x - y, x))
    @test pseudodivision(y * x^4 + x^3 + x, z * x^2, x) == x
end


# Trotter factorized solution for systems with few modes.

"""
Trotter factorized solution for a small system.
"""
struct Trotter <: Solution
    "Partition function."
    Z::Float64
    "Energy."
    E::Float64
    "Heat capacity."
    Cv::Float64
end

"""
    Trotter(sys::System, beta::Float64, basis_size::Int, P::Int)

Calculate the solution for `sys` at `beta` with `basis_size` basis functions
and `P` links.
"""
function Trotter(sys::System, beta::Float64, basis_size::Int, P::Int)
    basis = Basis(sys, basis_size)
    h0, V = operators(basis, sys)

    # Single Trotter product.
    tau = beta / P
    rho = exp(-tau * h0) * exp(-tau * V)

    # Full path.
    path = rho^P

    Z = tr(path)
    E = tr(path * (h0 + V)) / Z
    Cv = (tr(path * (h0 + V)^2) / Z - E^2) * beta^2

    Trotter(Z, E, Cv)
end


"""
$(SIGNATURES)

Find the closest point to `p` within `hull`. If `p` is inside `hull`,
`p` itself is returned.
"""
function closest_point(p::PointLike, hull::ConvexHull)
    op = orientation_comparator(hull)
    vertices = hull.vertices
    n = length(vertices)
    @inbounds begin
        if n === 0
            throw(ArgumentError())
        elseif n === 1
            return vertices[1]
        else
            closest = p
            i = 1
            v1 = vertices[n]
            v2 = vertices[1]
            while true
                δ = v2 - v1
                p′ = p - v1
                strictly_outside_edge = op(cross2(p′, δ), 0)
                if strictly_outside_edge
                    # Find closest point to current edge.
                    λ = clamp(p′ ⋅ δ / (δ ⋅ δ), false, true)
                    closest_to_edge = v1 + λ * δ

                    # Accept if previous candidate was p itself or if it's closer.
                    if closest == p || normsquared(closest_to_edge - p) < normsquared(closest - p)
                        closest = closest_to_edge
                    end
                end
                i == n && break
                v1 = vertices[i]
                v2 = vertices[i + 1]
                i += 1
            end
            return closest
        end
    end
end


using Test, StaticArrays, Random
using LogisticRegression: optimize!

Random.seed!(0)
X = [rand(2) for _ in 1:10]
Y = [x[1] + x[2] > 1.0 ? 1.0 : 0.0 for x in X]
J = [0.0]
w = [1.0 -1.0]
b = [0.0]
optimize!(J, w, b, X, Y, num_iterations=50, learning_rate=3.0)
@test isapprox(J, [0.15912527602988893])
@test isapprox(w, [4.9046541360009215 4.340606046548434])
@test isapprox(b, [-3.867136029168926])


# Just intonation definitions for note names in extended pythagorean tuning.
# Using A = 440 tuning standard, then all other notes are tuned by 5ths to that A
# All notes are turned to their 4th octave frequencies (i.e. C is middle C)
# Other octaves can be reached by multiplying or dividing by 2
A = 440
# Down circle of 5ths
D = 2//3*A
G = 4//3*D
C = 2//3*G
F = 4//3*C
Bb = 4//3*F
Eb = 2//3*Bb
Ab = 4//3*Eb
Db = 2//3*Ab
Gb = 4//3*Db
Cb = 2//3*Gb
Fb = 4//3*Cb

# Up circle of 5ths
E = 330  # 3/4*A
B = 495  # 3/2*E
Fs = 3//4*B
Cs = 3//4*Fs
Gs = 3//2*Cs
Ds = 3//4*Gs
As = 3//2*Ds
Es = 3//4*As
Bs = 3//2*Es


using Catalyst
rn = @reaction_network begin
    α, S + I --> 2I
    β, I --> R
    S^2, R --> 0
end α β

# check can make a graph
gr = Graph(rn)

# check can save a graph
fname = Base.Filesystem.tempname()
savegraph(gr, fname, "png")


"""
    LanczosBasis(n) -> LanczosBasis(n,identity,one,L2(n))
    LanczosBasis(n,a,b,p::InnerProduct)

Orthogonal basis for `span{b(x), a(x)*b(x), ..., a(x)^(n-1)*b(x)}`.
"""
struct LanczosBasis{T,A,B,P} <: AbstractBasis
    length::Int
    a_fun::A
    b_fun::B
    inner_product::P

    # Recursion coefficients
    invnorms::Vector{T}
    dots1::Vector{T}
    dots2::Vector{T}
end

LanczosBasis(n) = LanczosBasis(n,identity,one,L2(n))
function LanczosBasis(n,a_fun,b_fun,p::InnerProduct)
    a = evaluate(a_fun,p)
    b = evaluate(b_fun,p)

    T = typeof(dot(a,b,p))
    invnorms = Vector{T}(undef,n)
    dots1 = Vector{T}(undef,max(0,n-1))
    dots2 = Vector{T}(undef,max(0,n-2))

    n == 0 && return LanczosBasis(n,a_fun,b_fun,p, invnorms,dots1,dots2)
    invnorms[1] = inv(norm(b,p))
    n == 1 && return LanczosBasis(n,a_fun,b_fun,p, invnorms,dots1,dots2)
    q1 = invnorms[1].*b

    q̃2 = a.*q1
    dots1[1] = dot(q̃2,q1,p)
    q̃2 = q̃2 .- dots1[1].*q1
    dots2_end = norm(q̃2,p)
    invnorms[2] = inv(dots2_end)
    q2 = invnorms[2].*q̃2

    for i = 3:n
        dots2[i-2] = dots2_end
        q̃3 = a.*q2
        dots1[i-1] = dot(q̃3,q2,p)
        q̃3 = q̃3 .- dots1[i-1].*q2 .- dots2[i-2].*q1
        dots2_end = norm(q̃3,p)
        invnorms[i] = inv(dots2_end)
        q3 = invnorms[i].*q̃3
        q1,q2 = q2,q3
    end
    return LanczosBasis(n,a_fun,b_fun,p, invnorms,dots1,dots2)
end

Base.length(B::LanczosBasis) = B.length

approxpoints(B::LanczosBasis) = evalpoints(B.inner_product)
approxtransform(B::LanczosBasis,f) = dot(B,f,w)

function iterate_basis(B::LanczosBasis, x)
    length(B) == 0 && return nothing
    q1 = B.invnorms[1]*B.b_fun(x)
    return q1,(2,B.a_fun(x),q1,q1)
end

function iterate_basis(B::LanczosBasis, x, (i,a,q2,q1))
    i > length(B) && return nothing
    if i == 2
        q2 = B.invnorms[i]*(a*q1 - B.dots1[i-1]*q1)
    else
        q1,q2 = q2, B.invnorms[i]*(a*q2 - B.dots1[i-1]*q2 - B.dots2[i-2]*q1)
    end
    return q2,(i+1,a,q2,q1)
end


"""
$(TYPEDEF)

Base for all types which explicitly represent a surreal number.
"""
abstract type Surreal <: Number end

Surreal(x::Surreal) = x
Surreal(x) = SurrealForm(x)

"""
	left(x)::SurrealSet

Get the left subset of the canonical surreal number form of `x`
"""
function left end

"""
	right(x)::SurrealSet

Get the right subset of the canonical surreal number form of `x`.
"""
function right end

"""
	$(FUNCTIONNAME)(x)

Equivalent to `(left(x), right(x))`.
"""
leftright(x) = (left(x), right(x))

"""
	issurreal(x::Number)::Bool

Whether the number `x` is a surreal number.
"""
issurreal(::Surreal) = true
issurreal(::Real) = true
issurreal(x::Complex) = isreal(x)

"""
	isordinal(x::Number)::Bool

Whether the number `x` is an ordinal number.
"""
isordinal(x::Real) = isinteger(x) && x > 0

"""
	generation(x::Number)

Get the generation of the surreal number `x`.
"""
function generation end


using NonlinearEigenproblemsTest
using NonlinearEigenproblems
using Test
using IterativeSolvers
using LinearAlgebra
using Random
using SparseArrays

@testset "ILAN" begin

    @testset "Compute as many eigenpairs as possible (neigs=Inf)" begin
        n=100
        Random.seed!(1) # reset the random seed
        K = [1:n;2:n;1:n-1]; J=[1:n;1:n-1;2:n]
        A1 = sparse(K, J, rand(3*n-2)); A1 = A1+A1';
        A2 = sparse(K, J, rand(3*n-2)); A2 = A2+A2';
        A3 = sparse(K, J, rand(3*n-2)); A3 = A3+A3';
        nep=DEP([A1,A2,A3],[0,1,0.8])
        v0=rand(n)
        λ,W=ilan(nep,σ=0,γ=1;neigs=Inf,logger=0,maxit=30,tol=eps()*100,check_error_every=Inf,v=v0,errmeasure=ResidualErrmeasure(nep),inner_solver_method=NEPSolver.IARInnerSolver(tol=1e2,maxit=50))
        verify_lambdas(3, nep, λ, W, eps()*100)
    end

    @testset "Compute eigenpairs via Ritz extraction" begin
        nep=nep_gallery("dep_symm_double",10);
        v0=ones(size(nep,1));
        λ,W=ilan(nep;v=v0,tol=1e-5,neigs=3);
        verify_lambdas(3, nep, λ, W, 1e-5)
    end

    @testset "Errors thrown" begin
        n=100
        Random.seed!(1) # reset the random seed
        K = [1:n;2:n;1:n-1]; J=[1:n;1:n-1;2:n]
        A1 = sparse(K, J, rand(3*n-2)); A1 = A1+A1';
        A2 = sparse(K, J, rand(3*n-2)); A2 = A2+A2';
        A3 = sparse(K, J, rand(3*n-2)); A3 = A3+A3';
        nep=DEP([A1,A2,A3],[0,1,0.8])
        v0=rand(n)
        @test_throws NEPCore.NoConvergenceException λ,W,V2,H2,ω2,HH2=ilan(nep,σ=0,γ=1;neigs=2,logger=0,maxit=3,tol=eps()*100,check_error_every=Inf,v=v0,errmeasure=ResidualErrmeasure(nep))
    end

    @testset "Different format" begin
        n=100
        Random.seed!(1) # reset the random seed
        K = [1:n;2:n;1:n-1]; J=[1:n;1:n-1;2:n]
        A1 = sparse(K, J, rand(3*n-2)); A1 = A1+A1';
        A2 = sparse(K, J, rand(3*n-2)); A2 = A2+A2';
        nep1=DEP([A1,A2],[0,1])
        nep2=SPMF_NEP([one(A1),A1,A2],[S -> -S,S -> one(S),S -> exp(-S)]);

        v0=rand(n)
        λ,W,err,V,H,ω,HH=ilan(nep1,σ=0,γ=1;neigs=Inf,logger=0,maxit=10,tol=eps()*100,check_error_every=Inf,v=v0)

        λ2,W2,err2,V2,H2,ω2,HH2,=ilan(nep2,σ=0,γ=1;neigs=Inf,logger=0,maxit=10,tol=eps()*100,check_error_every=Inf,v=v0)
        #@test opnorm(W - W2) < 1e-6
        @test norm(λ - λ)<1e-6
        @test norm(W - W2)<1e-6
        @test norm(V - V2)<1e-6
        @test norm(H - H2)<1e-6
        @test norm(ω - ω2)<1e-6
        @test norm(HH - HH2)<1e-6
        @test opnorm(V'*V - I) < 1e-6
        @test opnorm(V2'*V2 - I) < 1e-6

    end
end


using CategoricalArrays, DataFrames, GLM, CSV, Statistics

function recode_raw_survey_data!(df)
    recode!(df.Q3, 1=> 10.0, 2=> 12.0, 3=> 13.0, 4=> 14.0, 5=> 16.0, 6=> 18.0, 7=> 20.0)
    recode!(df.perturb1, 2=> 1.0, 1=> -1.0)
end

function confusion(var1, var2)
    println("11: ", sum(var1.*var2)/length(var1))
    println("10: ", sum(var1.*(1 .-var2)/length(var1)))
    println("01: ", sum((1 .-var1).*var2/length(var1)))
    println("00: ", sum((1 .-var1).*(1 .-var2)/length(var1)))
end

function clean_data(datapath)
    data = DataFrame!(CSV.File(datapath))
    recode_raw_survey_data!(data)
    recode!(data.Q11, missing => -100)
    data.correct = (data.Q11 .≈ 3) .+ (data.Q11 .≈ -0.5)
    println(nrow(data))
    return head(data, 316)
end

function summary_stats(datapath)
    data = clean_data(datapath)
    println("Conditional prob I like math: ", mean((data.Q10 .>3).*data.correct)/mean(data.correct))
    println("Cond prob incorrect: ", mean((data.Q10 .>3).*(1 .- data.correct))/mean(1 .- data.correct))
    for var in [data.Q10, data.Q12, data.Q3, data.correct]
        println(mean(var), " (", std(var), ") ")
    end
    print(lm(@formula(Q10 ~ correct ), data))
    #println("Education and Skill: ", cor(data.Q3, data.Q12))
    #println("Correct and Outcome: ", cor(data.correct, data.Q10))
end

function linReg(y, x)
    x = [ones(length(y)) x ]
    β = inv(x'*x)*x'*y
    return β
end

function calculate_gradients(datapath, β, psize)
    data = clean_data(datapath)
    yhat = β[1] .+ (data.perturb1.*psize .+ β[2]).*data.correct
    π = -1 .*(yhat .- data.Q10).^2
    gradient = linReg(π, data.perturb1 .* psize)[2]
    println("Gradient: ", gradient)
    return gradient
end

function get_b0_gradient(datapath, β, psize)
    data = clean_data(datapath)
    net = mean(data.Q10) - β[1] - mean(data.correct.*β[2])
    println(net)
    return net
end

function get_intercept(datapath, β)
    data = clean_data(datapath)
    β0  = mean(data.Q10) - mean(β.*data.correct)
    return β0
end

function o_mse_calc(files, beta)
    mses = zeros(length(files))
    n  = zeros(length(files))
    for (i, f) in zip(1:length(files), files)
        data = clean_data(f)
        x = Matrix([ones(size(data)[1]) data[!, ["correct"]]])
        y = data.Q10
        yhat = x*beta
        mses[i] =  1 - mean((yhat .- y).^2)/var(y)
        println("MSE: ", mean((yhat .- y).^2))
        n[i] = length(yhat)
    end
    println(mses)
    return sum(mses.*n)/sum(n)
end


#=

  Euler #49 in Julia.

  Problem 49:
  """
  The arithmetic sequence, 1487, 4817, 8147, in which each of the terms
  increases by 3330, is unusual in two ways: (i) each of the three terms are
  prime, and, (ii) each of the 4-digit numbers are permutations of one another.

  There are no arithmetic sequences made up of three 1-, 2-, or 3-digit primes,
  exhibiting this property, but there is one other 4-digit increasing sequence.

  What 12-digit number do you form by concatenating the three terms
  in this sequence?
  """

  This Julia program was created by Hakan Kjellerstrand, hakank@gmail.com
  See also my Julia page: http://www.hakank.org/julia/

=#

include("Euler.jl")

function check_perms(n, diff)
    allperms = all_permutations2(digits(n))
    if length(allperms) > 0
        p1 = get_element(n, allperms, diff)
        if p1 !== nothing
            p2 = get_element(p1, allperms, diff)
            if p2 !== nothing
                return [n, p1, p2]
            end
        end
    end

    return nothing
end

function get_element(n, ll, diff)
    for p in ll
        pp = parse(Int,join(p,""))
        # if isPrime(pp) && pp > n && pp-n == diff
        if pp > n && pp-n == diff && isPrime(pp)
            return pp
        end
    end
    return nothing
end

# 0.10001170s
function euler49a()
    diff = 3330
    res = 0
    for n in 1001:2:9999
        if n != 1487 && isPrime(n)
            c = check_perms(n, diff)
            if c !== nothing
                res = c
                break
            end
        end
    end

    return parse(Int,join(res,""))

end

run_euler(euler49a)


module KernelsTests

using Test
using InplaceArrays.Arrays

test_kernel(+,(3,2),5)

@test kernel_return_types((+,/),1,1) == (Int,Float64)

f = bcast(+)
a = rand(3,2)
b = 3
c = a .+ b
test_kernel(f,(a,b),c)

#test_kernel(1,(),1)
#test_kernel(1,(1,),1)
#
#test_kernel([1,2],(),[1,2])
#test_kernel([1,2],(1,),[1,2])

k = elem(-)
test_kernel(k,(1,),-1)
test_kernel(k,([1,2],),[-1,-2])
test_kernel(k,(1,2),-1)
test_kernel(k,(1.0,2),-1.0)
test_kernel(k,(1,2.0),-1.0)
test_kernel(k,([1,2],2),[-1,0])
test_kernel(k,(2,[1,2]),[1,0])
test_kernel(k,([3,4],[1,2]),[2,2])

k = contract(-)
test_kernel(k,([3,4],[1,2]),3-1+4-2)

end # module