#=
This file contains a few algorithms that are used to compute convex
hulls in two dimensions.

Currently included are:

* Graham Scan Algorithm
* Monotone Chain Algorithm

Algorithms that should be added are:

* Quickhull Algorithm
* Chan Algorithm
* ?
=#
module CHull2d

# Load dependencies
using FixedSizeArrays

# Base methods
import Base: *, in, intersect, isfinite, isless, isequal, length

# Include file of all types
include("CHullTypes.jl")
include("utils.jl")

#
# Include all algorithms
#
include("intersect.jl")
include("algorithms.jl")

const ALGDICT = Dict{Symbol, Function}()
ALGDICT[:MonotoneChain] = _monotonechain
ALGDICT[:GrahamScan] = _grahamscan
# ALGDICT[:QuickHull] = _quickhull

"""
Creates the convex hull of a set of points

* points : The set of points that you would like to take the convex hull of
* algorithm : Which convex hull algorithm to use. Currently implemented are
              [:GrahamScan, :MonotoneChain]
* _at : Whether or not to use the Akl Toussaint pruning
"""
function convexhull{T}(points::Vector{Point{2, T}}; algorithm::Symbol=:MonotoneChain, _at::Bool=true)
    # TODO: Should make sure that there are at least 3 not co-linear points
    # First prune points
    points = _at ? _akltoussaint(points) : points

    # Apply algorithm
    ep = ALGDICT[algorithm](points)

    return ConvexHull(ep)
end

export ConvexHull, LineSegment, Quadrant, Point, # Types
       convexhull, wrappoints, pt_out_tri,
       evaluatex, evaluatey, ccw, cw, orientation

end


using StatsBase, Plots; pyplot()

faces, N = 1:6, 10^6
mcEstimate = counts(rand(faces,N), faces)/N

plot(faces, mcEstimate,
	line=:stem, marker=:circle,
	c=:blue, ms=10, msw=0, lw=4, label="MC estimate")
plot!([i for i in faces], [1/6 for _ in faces],
	line=:stem, marker=:xcross, c=:red,
	ms=6, msw=0, lw=2, label="PMF",
	xlabel="Face number", ylabel="Probability", ylims=(0,0.22))


using MultivariateOrthogonalPolynomials, FastTransforms, BlockBandedMatrices, Plots
plotly()

Z = Zernike(1)
W = Weighted(Z)
xy = axes(Z,1); x,y = first.(xy),last.(xy)
Δ = Z \ (Laplacian(xy) * W)
S = Z \ W

k = 2
f = @.(cos(x*exp(y)))
F = factorize(Δ + k^2 * S)
c = Z \ f
F \ c

u = W * ((Δ + k^2 * S) \ (Z \ f))


# https://optimization.mccormick.northwestern.edu/index.php/Disjunctive_inequalities
using JuMP
using DisjunctiveProgramming

m = Model()
@variable(m, 0<=x[1:2]<=10)

@constraint(m, con1[i=1:2], x[i]<=[3,4][i])
@constraint(m, con2, 0<=x[1])
@constraint(m, con3, 0<=x[2])
@constraint(m, con4[i=1:2], [5,4][i]<=x[i])
@constraint(m, con5, x[1] <= 9)
@constraint(m, con6, x[2] <= 6)

@disjunction(m,(con1,con2,con3),(con4,con5,con6),reformulation=:BMR)


### A Pluto.jl notebook ###
# v0.12.20

using Markdown
using InteractiveUtils

# ╔═╡ a10408d0-6b1a-11eb-26d1-ade2b8bc5570
using GMT

# ╔═╡ 21c0d760-6b1a-11eb-1dbe-557799ae1164
md"# Ploting functions

There are a couple of predefined functions that can plotted for demonstration purposes.
For example the [_ackley_](https://en.wikipedia.org/wiki/Ackley_function) function
"

# ╔═╡ a5c49e20-6b1a-11eb-09e6-ab92f5f2cbc6
imshow("ackley", view=(159,30), shade=true)

# ╔═╡ f4134f40-6b1a-11eb-2e01-93c3ba5e52a6
md" Or the *rosenbrock* that looks like a manta ray (other options are *parabola, eggbox,  sombrero*)"

# ╔═╡ 0e50fdd0-6b1b-11eb-1971-4f1f5d7064b6
imshow("rosenbrock", view=(159,30), shade=true)

# ╔═╡ c5b1442e-6b1b-11eb-3292-e3ae7c257923
md"But besides these predefined functions one can any function that defines a surface.
For example a parabola can be plotted with the code bellow. First argument can be an anonymous function (like the example) of a function. Second and third args contain the plotting domain and step used to evaluate the function."

# ╔═╡ 1bde9e10-6b1d-11eb-0356-65dfa0dbe4c3
imshow((x,y) -> sqrt(x^2 + y^2), -5:0.05:5, -5:0.05:5, view=(159,30), shade=true, frame=:autoXYZg)

# ╔═╡ 437448ce-6b1d-11eb-0350-85821c4d8826
md"And we can plot 3D lines too. Same thing, give a parametric equation and"

# ╔═╡ 657c4a40-6b1d-11eb-25aa-49118e1f7460
plot3d(x -> sin(x)*cos(10x), y -> sin(y)*sin(10y), z -> cos(z), 0:pi/200:pi, lt=2, lc=:brown, frame=:autoXYZg, show=true)

# ╔═╡ 78903ba0-6b31-11eb-1ff4-e9cf2d6f2978
md"And a 2D example"

# ╔═╡ 89a23290-6b31-11eb-2e75-2db7eb10552a
lines(x -> cos(x) * x, y -> sin(y) * y, linspace(0,2.1pi,100), region=(-4,7,-5.5,2.5),
	lw=2, lc=:sienna, decorated=(quoted=true, const_label=" I am the center of the Universe", font=(34,"Times-Italic"), curved=true), aspect=:equal, show=true)

# ╔═╡ Cell order:
# ╟─21c0d760-6b1a-11eb-1dbe-557799ae1164
# ╠═a10408d0-6b1a-11eb-26d1-ade2b8bc5570
# ╠═a5c49e20-6b1a-11eb-09e6-ab92f5f2cbc6
# ╟─f4134f40-6b1a-11eb-2e01-93c3ba5e52a6
# ╠═0e50fdd0-6b1b-11eb-1971-4f1f5d7064b6
# ╟─c5b1442e-6b1b-11eb-3292-e3ae7c257923
# ╠═1bde9e10-6b1d-11eb-0356-65dfa0dbe4c3
# ╟─437448ce-6b1d-11eb-0350-85821c4d8826
# ╠═657c4a40-6b1d-11eb-25aa-49118e1f7460
# ╟─78903ba0-6b31-11eb-1ff4-e9cf2d6f2978
# ╠═89a23290-6b31-11eb-2e75-2db7eb10552a


# ---
# title: 98. Validate Binary Search Tree
# id: problem98
# author: Indigo
# date: 2021-06-02
# difficulty: Medium
# categories: Tree, Depth-first Search, Recursion
# link: <https://leetcode.com/problems/validate-binary-search-tree/description/>
# hidden: true
# ---
#
# Given the `root` of a binary tree, _determine if it is a valid binary search
# tree (BST)_.
#
# A **valid BST** is defined as follows:
#
#   * The left subtree of a node contains only nodes with keys **less than** the node's key.
#   * The right subtree of a node contains only nodes with keys **greater than** the node's key.
#   * Both the left and right subtrees must also be binary search trees.
#
#
#
# **Example 1:**
#
# ![](https://assets.leetcode.com/uploads/2020/12/01/tree1.jpg)
#
#
#
#     Input: root = [2,1,3]
#     Output: true
#
#
# **Example 2:**
#
# ![](https://assets.leetcode.com/uploads/2020/12/01/tree2.jpg)
#
#
#
#     Input: root = [5,1,4,null,null,3,6]
#     Output: false
#     Explanation: The root node's value is 5 but its right child's value is 4.
#
#
#
#
# **Constraints:**
#
#   * The number of nodes in the tree is in the range `[1, 104]`.
#   * `-231 <= Node.val <= 231 - 1`
#
#
## @lc code=start
using LeetCode

function is_valid_BST(root::TreeNode)::Bool
    stack, val = TreeNode{Int}[], -Inf
    while !isempty(stack) || !isnothing(root)
        while !isnothing(root)
            push!(stack, root)
            root = root.left
        end
        ## inorder traversal: put codes here
        root.val <= val && return false ## strictly increasing
        val, root = root.val, root.right
    end
    true
end

## @lc code=end

## @lc test=start
@testset "98.validate-binary-search-tree.jl" begin
    @test is_valid_BST(TreeNode{Int}([5, 1, 4, nothing, nothing, 3, 6])) == false
    @test is_valid_BST(TreeNode{Int}([5, 1, 6])) == true
    @test is_valid_BST(TreeNode{Int}([5,4,6,nothing,nothing, 3,7])) == false
    @test is_valid_BST(TreeNode{Int}([2,2,2])) == false
end
## @lc test=end

## @lc info=start
# link: [solution to 98](https://leetcode-cn.com/problems/validate-binary-search-tree/solution/98-yan-zheng-er-cha-sou-suo-shu-di-gui-d-1w6m/)
## @lc info=end


# returns the variable (or matrix), lagged p times,
# with the first p rows filled with ones (to avoid divide errors)
# remember to drop those rows before doing analysis
function lag(x,p)
	n = size(x,1)
    k = size(x,2)
	lagged_x = [ones(p,k); x[1:n-p,:]]
end


# right hand side function in our system
function f!(du, u, p, t)
    du[1] = u[1] * (1.0-u[2]);
    du[2] = u[2] * (u[1]-1.0);
    du
end

# set up ODE
function solve_ode(u0, Tmax)
    # define the ODE problem
    prob = ODEProblem(f!, u0, (0., Tmax))
    # solve the problem
    sol = solve(prob)

    return sol
end


using Pkg;
using DelimitedFiles, DataFrames
using LinearAlgebra, Statistics, JuMP, Ipopt, Juniper, Optim
using Gadfly, Cairo, Compose;
cd(@__DIR__) #src

include("func_synth.jl")
dat, ~ = readdlm("MLAB_data.txt", header = true);

# Index to group the data: Control vs. treated ============================
idx_control = 1:38;
idx_treat   = 39;

# Group data ==============================================================
# Endogenous time-invariant predictor -------------------------------------
idx_predictor = 1:7
xₒ = dat[idx_predictor, idx_control];         # Control group
x₁ = dat[idx_predictor, idx_treat];           # Treatment group

# Rescale predictors
global xₒ, x₁ = normalz(xₒ, x₁, idx_control, idx_treat);

# Outcome data to evaluate ------------------------------------------------
idx_outcome = 8:38;
yₒ = dat[idx_outcome, idx_control];           # Control group
y₁ = dat[idx_outcome, idx_treat];             # Treatment group

idx_pretreat = 1:19;                          # Rows of pre-treat period
global yₒpre = yₒ[idx_pretreat, :];
global y₁pre = y₁[idx_pretreat, :];

# Optimization
s = std(hcat(xₒ, x₁)', dims = 1)';
s2 = s[2:end];
s1 = s[1];
ν₀ = ((s1./s2).^2);

# Optimization to estimate weights ========================================
#https://julianlsolvers.github.io/Optim.jl/stable/#user/minimization/
#https://julianlsolvers.github.io/Optim.jl/stable/#algo/samin/
q = (length(ν₀))
opt = optimize(loss_ssr, fill(0.0, q), fill(5e8, q), ν₀,
                Fminbox(BFGS()), Optim.Options(iterations=100) # or 100_000
            );
# NOTE: Requires further improvement, as the optimization takes a long time.
# Can try with different optimization methods
opt = optimize(loss_ssr, fill(0.0, q), fill(5e8, q), ν₀,
                  SAMIN(), Optim.Options(iterations=1_000_000) # or 100_000
            );

# Save results
ν = vcat(1, Optim.minimizer(opt));

#MATLAB optimal solutions - to check the accuracy of solveQadProg(H, f)
#=
ν = [1,
      780.438610055690,
      24909.6348724115,
      37806.0921140682,
      124.042538397502,
      60413.0041399961,
      63630.9996910117];
=#
# Recover weights: w-weights
D = (eye(length(ν)) .* ν);

H = xₒ' * D * xₒ;
f = -1 .* (x₁' * D * xₒ);

ω = abs.(solveQadProg(H, f)); # we know this works now.

# Build counterfactual output.
ŷₒ = yₒ * ω;
ȳₒ = mean(yₒ, dims = 2); # Res of US, for plotting purpose

# plot ====================================================================
yr = 1970:1:2000;
plt_yavg = layer(x = yr, y = y₁, Geom.line,
                  Theme(default_color=colorant"deepskyblue",
                        line_style = [:solid]),
                  xintercept = [1988],
                        Geom.vline(color = "black", style=[[0.5mm,0.5mm]])
            );
plt_y    = layer(x = yr, y = ȳₒ, Geom.line,
                  Theme(default_color=colorant"red", line_style = [:solid]));
plt_yhat = layer(x = yr, y = ŷₒ, Geom.line, Theme(line_style = [:dash]));
plt_fin = plot(plt_yavg, plt_y, plt_yhat,
            Guide.xlabel("Year"),
            Guide.ylabel("Somking per capita (in packs)"),
            Guide.annotation(compose(context(),
                        Compose.text(1988.5, 25, "← Proposition 99"))),
            Guide.annotation(compose(context(),
                        Compose.text(1993, 110, "Rest of U.S."))),
            Guide.annotation(compose(context(),
                        Compose.text(1971, 95, "Solid: California (actual) \n Dash: California (synthetic)")))
      )
pngout = SVG("plt_fin.svg", 5inch, 4inch)
draw(pngout, plt_fin)


using SpecialFunctions: erfcinv

@doc raw"""
The Morlet wavelet.
```math
 g(t) = \sqrt{\frac{\delta}{2\pi}}\exp(2\pi\,i\,t-t^2\,\delta)\,,
```
where δ specifies the time-frequency resolution of the wavelet.
"""
struct MorletWavelet <: GenericContinuousWavelet
    δ::Float64
    norm::Float64
    σ::Float64

    @doc raw"""
        MorletWavelet(δ::Real; ϵ::Real=1e-3)

    Constructs a new Morlet wavelet, whith the given `dff` parameter specifying the time-frequency
    resolution. The optional keyword arguent ϵ specifies the cutoff at which the kernel evaluation
    gets truncated. It is defined as the fraction of total power loss of the mother wavelet. Smaller
    values of ϵ will increase the precision of the wavelet transform on the cost of longer kernels
    leading to slower convolutions.
    """
    function MorletWavelet(δ::Real; ϵ::Real=1e-3)
        new(δ, δ/2/π, sqrt(2)*erfcinv(2*ϵ));
    end
end

function eval_analysis(wav::MorletWavelet, t::Float64)
    exp(-t^2 * wav.δ/2 + 2π*t*1im) * wav.norm;
end

eval_synthesis(wav::MorletWavelet, t::Float64) = eval_analysis(wav, t);

function eval_repkern(wav::MorletWavelet, a::Float64, b::Float64)
    a2p1 = (a^2 + 1); am1 = (a - 1);
    d = wav.δ; d2 = d*d;
    c = d2/sqrt(2*π*d*a2p1);
    re = -(d2*b^2 + 4*π^2*am1^2)/(2*d*a2p1);
    im = 2*π*b*(1 + am1/a2p1)/a;
    c * exp(re + im*1im);
end

function cutoff_time(wav::MorletWavelet)
    # 99.9% power at scale 1
    wav.σ/sqrt(wav.δ);
end;

function cutoff_freq(wav::MorletWavelet)
    # 99.9% power at scale 1
    1 + wav.σ*sqrt(wav.δ);
end;


### Beta -- Bernoulli or Binomial

posterior(prior::Beta, ss::BernoulliStats) = Beta(prior.alpha + ss.cnt1, prior.beta + ss.cnt0)

posterior(prior::Beta, ss::BinomialStats) = Beta(prior.alpha + ss.ns, prior.beta + (ss.ne * ss.n - ss.ns))

function posterior{T<:Real}(prior::Beta, ::Type{Binomial}, n::Integer, x::Array{T})
	posterior(prior, suffstats(Binomial, n, x))
end

function posterior{T<:Real}(prior::Beta, ::Type{Binomial}, n::Integer, x::Array{T}, w::Array{Float64})
	posterior(prior, suffstats(Binomial, n, x, w))
end

### Dirichlet -- Categorical or Multinomial

function posterior{T<:Real}(prior::Dirichlet, ::Type{Categorical}, x::Array{T})
	α = copy(prior.alpha)
	for i in 1:length(x)
		@inbounds xi = x[i]
		α[xi] += 1.0   # cannot put @inbounds here, as no guarantee xi is inbound
	end
	return Dirichlet(α)
end

function posterior{T<:Real}(prior::Dirichlet, ::Type{Categorical}, x::Array{T}, w::Array{Float64})
	if length(x) != length(w)
		throw(ArgumentError("Inconsistent argument dimensions."))
	end
	α = copy(prior.alpha)
	for i in 1:length(x)
		@inbounds xi = x[i]
		@inbounds wi = w[i]
		α[xi] += wi
	end
	return Dirichlet(α)
end

function posterior{T<:Real}(prior::Dirichlet, ::Type{Multinomial}, x::Vector{T})
	return Dirichlet(prior.alpha + x)
end

# each column are counts for one experiment
function posterior{T<:Real}(prior::Dirichlet, ::Type{Multinomial}, X::Matrix{T})
	d::Int = dim(prior)
	if d != size(X, 1)
		throw(ArgumentError("Inconsistent argument dimensions."))
	end
	n = size(X, 2)
	α = copy(prior.alpha)
	o = 0
	for j in 1:n
		for i in 1:d
			@inbounds α[i] += X[o+i]
		end
		o += d
	end
	return Dirichlet(α)
end

function posterior{T<:Real}(prior::Dirichlet, ::Type{Multinomial}, X::Matrix{T}, w::Array{Float64})
	d::Int = dim(prior)
	if d != size(X, 1)
		throw(ArgumentError("Inconsistent argument dimensions."))
	end
	n = size(X, 2)
	if n != length(w)
		throw(ArgumentError("Inconsistent argument dimensions."))
	end
	α = copy(prior.alpha)
	o = 0
	for j in 1:n
		@inbounds wj = w[j]
		for i in 1:d
			@inbounds α[i] += X[o+i] * wj
		end
		o += d
	end
	return Dirichlet(α)
end

### Gamma -- Exponential

function posterior(prior::Gamma, ss::ExponentialStats)
	return Gamma(prior.shape + ss.sw, 1.0 / (rate(prior) + ss.sx))
end

### For Normal distributions

function posterior(prior::Normal, ss::NormalKnownSigmaStats)
	μ0 = prior.μ
	c0 = 1.0 / abs2(prior.σ)
	c1 = 1.0 / abs2(ss.σ)

	τ = c0 + ss.tw * c1
	μ1 = (μ0 * c0 + ss.s * c1) / τ
	σ1 = sqrt(1 / τ)
	return Normal(μ1, σ1)
end

function posterior(prior::InvertedGamma, ss::NormalKnownMuStats)
	α1 = prior.shape + ss.tw / 2
	β1 = rate(prior) + ss.s2 / 2
	return InvertedGamma(α1, 1.0 / β1)
end

function posterior{T<:Real}(prior::(Normal, Float64), ::Type{Normal}, x::Array{T})
	pri_μ::Normal = prior[1]
	σ::Float64 = prior[2]
	posterior(pri_μ, suffstats(NormalKnownSigma(σ), x))
end

function posterior{T<:Real}(prior::(Normal, Float64), ::Type{Normal}, x::Array{T}, w::Array{Float64})
	pri_μ::Normal = prior[1]
	σ::Float64 = prior[2]
	posterior(pri_μ, suffstats(NormalKnownSigma(σ), x, w))
end

function posterior{T<:Real}(prior::(Float64, InvertedGamma), ::Type{Normal}, x::Array{T})
	μ::Float64 = prior[1]
	pri_σ::InvertedGamma = prior[2]
	posterior(pri_σ, suffstats(NormalKnownMu(μ), x))
end

function posterior{T<:Real}(prior::(Float64, InvertedGamma), ::Type{Normal}, x::Array{T}, w::Array{Float64})
	μ::Float64 = prior[1]
	pri_σ::InvertedGamma = prior[2]
	posterior(pri_σ, suffstats(NormalKnownMu(μ), x, w))
end


# double run_P(int N1,int N2,double dt,double *spike_times_1,double *spike_times_2){
function run_P(spike_times_1::Vector{<:Real},
                spike_times_2::Vector{<:Real},
                dt::Real,
            )::Float64
    Nab = 0
    N1 = length(spike_times_1)
    N2 = length(spike_times_2)

    spike_index2 = 1
    for spike_index1 = 1:N1
        while (spike_index2 < N2)
            # check every spike in train 1 to see if there's a spike in train 2 within dt  (don't count spike pairs)
            # don't need to search all spike_index2 each iteration
            if abs(spike_times_1[spike_index1] - spike_times_2[spike_index2]) <=
               dt
                Nab = Nab + 1
                break
            elseif spike_times_2[spike_index2] > spike_times_1[spike_index1]
                break
            else
                spike_index2 = spike_index2 + 1
            end # if
        end # while
    end # for

    return Nab
end

# double run_T(int N1v,double dtv,double startv, double endv, double *spike_times_1)
"""
dt is the window size used for searching for spike cooccurance

TODO: what is missing now is the how the vectors should be defined, because this would remove ambiguity about the time resolutions
"""
function run_T(spike_times_1::Vector{<:Real},
                dt::Real,
                start_time::Real,
                stop_time::Real,
            )::Float64
    N1 = length(spike_times_1)
    vec_index = 1

    # maximum
    time_A = 2 * N1 * dt

    #  if just one spike in train
    if N1 == 1
        first_spike_time = spike_times_1[0]
        if first_spike_time - start_time < dt
            time_A = time_A - start_time + first_spike_time - dt
        elseif first_spike_time + dt > stop_time
            time_A = time_A - first_spike_time - dt + stop_time
        end
    else # if more than one spike in train
        while vec_index < N1
            dif_index = vec_index + 1
            diff = spike_times_1[dif_index] - spike_times_1[vec_index]

            if diff < 2 * dt # subtract overlap
                time_A = time_A - 2 * dt + diff
            end
            vec_index += 1
        end

        # check if spikes are within dt of the start_time and/or end, if so just need to subract
        # overlap of first and/or last spike as all within-train overlaps have been accounted for
        if spike_times_1[1] - start_time < dt
            time_A = time_A - start_time + spike_times_1[1] - dt
        end

        if stop_time - spike_times_1[N1-1] < dt
            time_A = time_A - spike_times_1[N1] - dt + stop_time
        end
    end

    return time_A
end

# void run_sttc(int *N1v,int *N2v,double *dtv,double *Time,double *index,double *spike_times_1,double *spike_times_2){
function tailing_coefficient(spike_times_1::Vector{<:Real},
                                spike_times_2::Vector{<:Real},
                                dt::Real,
                                start_time::Real,
                                stop_time::Real,
                            )

    N1 = length(spike_times_1)
    N2 = length(spike_times_2)

    if (N1 == 0 || N2 == 0)
        index = NaN
    else
        T = stop_time - start_time
        # TA=run_T(N1,dt,Time[0],Time[1], spike_times_1) / T
        # TB=run_T(N2,dt,Time[0],Time[1], spike_times_2) / T
        # PA=run_P(N1,N2,dt, spike_times_1, spike_times_2) / N1
        # PB=run_P(N2,N1,dt, spike_times_2, spike_times_1) / N2

        TA = run_T(spike_times_1, dt, start_time, stop_time) / T
        TB = run_T(spike_times_2, dt, start_time, stop_time) / T
        PA = run_P(spike_times_1, spike_times_2, dt) / N1
        PB = run_P(spike_times_2, spike_times_1, dt) / N2

        index =
            0.5 * (PA - TB) / (1 - TB * PA) + 0.5 * (PB - TA) / (1 - TA * PB)
    end

    return index
end


function petk01()
#
# M. Petkovic testing polynomials (p. 109)
#
    p1 = poly([-1.0; -1; 3; 3; 3; -im*[1;1;1;1]]);
    p1 = reverse(p1.a)
    p2 = [1;-2;5];
    p2 = conv(p2,p2);
    p = conv(p1,p2);
    z = [-1 2; 3  3; -im  4; 1+2*im  2; 1-2*im  2];
    p, PolyZeros(z)
end


"""
    statistical_moments(results, n; field_apply=real)::Vector{Matrix}

Calculate moments up to `n` of results at each position and wavenumber/time, after applying `field_apply`.
"""
function statistical_moments(results::AbstractVector{SimRes}, num_moments::Int; field_apply=real) where {T,SimRes<:SimulationResult{T}}

    # Number of positions and wavenumbers/time points sampled
    X, K = size(results[1])

    # Number of realisations
    R = length(results)

    moments = [Matrix{T}(undef,X,K) for i=1:num_moments]

    # For each wavenumber or timestep, calculate each moment up to num_moments
    for i = 1:X
        for j = 1:K
            responses = [field_apply(field(results[r],i,j)) for r=1:R]
            μ = mean(responses)
            moments[1][i,j] = μ
            for m=2:num_moments
                moment = sum((responses .- μ).^m)/(R-1)
                moments[m][i,j] = sign(moment) * abs(moment)^(1.0/m)
            end
        end
    end
    return moments
end


using MultivariateStats
using LinearAlgebra
using Test
using StableRNGs
import Statistics: mean, cov, var
import SparseArrays
import StatsBase

@testset "Probabilistic PCA" begin

    rng = StableRNG(34568)

    ## PCA with zero mean

    X = randn(rng, 5, 10)
    Y = randn(rng, 3, 10)

    W = qr(randn(rng, 5, 5)).Q[:, 1:3]
    σ² = 0.1
    M = PPCA(Float64[], W, σ²)

    @test size(M) == (5,3)
    @test mean(M) == zeros(5)
    @test loadings(M) == W
    @test var(M) == σ²

    T = inv(W'*W .+ σ²*Matrix(I, 3, 3))*W'
    @test predict(M, X[:,1]) ≈ T * X[:,1]
    @test predict(M, X) ≈ T * X

    R = W*inv(W'W)*(W'W .+ σ²*Matrix(I, 3, 3))
    @test reconstruct(M, Y[:,1]) ≈ R * Y[:,1]
    @test reconstruct(M, Y) ≈ R * Y

    ## PCA with non-zero mean

    mval = rand(rng, 5)
    M = PPCA(mval, W, σ²)

    @test size(M) == (5,3)
    @test mean(M) == mval
    @test loadings(M) == W
    @test var(M) == σ²

    @test predict(M, X[:,1]) ≈ T * (X[:,1] .- mval)
    @test predict(M, X) ≈ T * (X .- mval)

    @test reconstruct(M, Y[:,1]) ≈ R * Y[:,1] .+ mval
    @test reconstruct(M, Y) ≈ R * Y .+ mval

    ## prepare training data

    d = 5
    n = 1000

    R = collect(qr(randn(rng, d, d)).Q)
    @test R'R ≈ Matrix(I, 5, 5)
    rmul!(R, Diagonal(sqrt.([0.5, 0.3, 0.1, 0.05, 0.05])))

    X = R'randn(rng, 5, n) .+ randn(rng, 5)
    mval = vec(mean(X, dims=2))
    Z = X .- mval

    M0 = fit(PCA, X; mean=mval, maxoutdim = 4)

    ## ppcaml (default)

    M = fit(PPCA, X)
    P = projection(M)
    W = loadings(M)

    @test size(M) == (5,4)
    @test mean(M) == mval
    @test P'P ≈ Matrix(I, 4, 4)
    @test reconstruct(M, predict(M, X)) ≈ reconstruct(M0, predict(M0, X))

    M = fit(PPCA, X; mean=mval)
    @test loadings(M) ≈ W

    M = fit(PPCA, Z; mean=0)
    @test loadings(M) ≈ W

    M = fit(PPCA, X; maxoutdim=3)
    P = projection(M)
    W = loadings(M)

    @test size(M) == (5,3)
    @test P'P ≈ Matrix(I, 3, 3)

    # ppcaem

    M = fit(PPCA, X; method=:em)
    P = projection(M)
    W = loadings(M)

    @test size(M) == (5,4)
    @test mean(M) == mval
    @test P'P ≈ Matrix(I, 4, 4)
    @test all(isapprox.(reconstruct(M, predict(M, X)), reconstruct(M0, predict(M0, X)), atol=1e-2))

    M = fit(PPCA, X; method=:em, mean=mval)
    @test loadings(M) ≈ W

    M = fit(PPCA, Z; method=:em, mean=0)
    @test loadings(M) ≈ W

    M = fit(PPCA, X; method=:em, maxoutdim=3)
    P = projection(M)

    @test size(M) == (5,3)
    @test P'P ≈ Matrix(I, 3, 3)

    @test_throws StatsBase.ConvergenceException fit(PPCA, X; method=:em, maxiter=1)

    # bayespca
    M0 = fit(PCA, X; mean=mval, maxoutdim = 3)

    M = fit(PPCA, X; method=:bayes)
    P = projection(M)
    W = loadings(M)

    @test size(M) == (5,3)
    @test mean(M) == mval
    @test P'P ≈ Matrix(I, 3, 3)
    @test reconstruct(M, predict(M, X)) ≈ reconstruct(M0, predict(M0, X))

    M = fit(PPCA, X; method=:bayes, mean=mval)
    @test loadings(M) ≈ W

    M = fit(PPCA, Z; method=:bayes, mean=0)
    @test loadings(M) ≈ W

    M = fit(PPCA, X; method=:em, maxoutdim=2)
    P = projection(M)

    @test size(M) == (5,2)
    @test P'P ≈ Matrix(I, 2, 2)

    @test_throws StatsBase.ConvergenceException fit(PPCA, X; method=:bayes, maxiter=1)

    # Different data types
    # --------------------
    X = randn(rng, Float64, 5, 10)
    XX = convert.(Float32, X)

    Y = randn(rng, Float64, 1, 10)
    YY = convert.(Float32, Y)

    for method in (:bayes, :em)
        M = fit(PPCA, X ; maxoutdim=1, method=method)
        MM = fit(PPCA, XX ; maxoutdim=1, method=method)

        # mix types
        predict(M, XX)
        predict(MM, X)
        reconstruct(M, YY)
        reconstruct(MM, Y)

        # type consistency
        for func in (mean, projection, var, loadings)
            @test eltype(func(M)) == Float64
            @test eltype(func(MM)) == Float32
        end
    end

    # views
    X = randn(rng, 5, 200)
    M = fit(PPCA, view(X, :, 1:100), maxoutdim=3)
    M = fit(PPCA, view(X, :, 1:100), maxoutdim=3, method=:em)
    M = fit(PPCA, view(X, :, 1:100), maxoutdim=3, method=:bayes)
    # sparse
    @test_throws AssertionError fit(PPCA, SparseArrays.sprandn(rng, 100d, n, 0.6))

end


using NumericalMethodsforEngineers
using Test

@sym begin
  ClearAll(x, y, sol)
  sol1 = DSolve(D(y(t),t) - y(t) + Sin(t), y(t))
  Print("DSolve(D(y(t),t) - y(t) + Sin(t), y(t)) = $(sol1)\n")
  #sol2 = Roots(D(x^4+1,x,2),x)
  #Println(sol2)
end


"""
    InverseWishart(ν, Ψ)
```julia
ν::Real           degrees of freedom (greater than p - 1)
Ψ::AbstractPDMat  p x p scale matrix
```
The [inverse Wishart distribution](http://en.wikipedia.org/wiki/Inverse-Wishart_distribution)
generalizes the inverse gamma distribution to ``p\\times p`` real, positive definite
matrices ``\\boldsymbol{\\Sigma}``. If ``\\boldsymbol{\\Sigma}\\sim IW_p(\\nu,\\boldsymbol{\\Psi})``,
then its probability density function is

```math
f(\\boldsymbol{\\Sigma}; \\nu,\\boldsymbol{\\Psi}) =
\\frac{\\left|\\boldsymbol{\\Psi}\\right|^{\\nu/2}}{2^{\\nu p/2}\\Gamma_p(\\frac{\\nu}{2})} \\left|\\boldsymbol{\\Sigma}\\right|^{-(\\nu+p+1)/2} e^{-\\frac{1}{2}\\operatorname{tr}(\\boldsymbol{\\Psi}\\boldsymbol{\\Sigma}^{-1})}.
```

``\\mathbf{H}\\sim W_p(\\nu, \\mathbf{S})`` if and only if ``\\mathbf{H}^{-1}\\sim IW_p(\\nu, \\mathbf{S}^{-1})``.
"""
struct InverseWishart{T<:Real, ST<:AbstractPDMat} <: ContinuousMatrixDistribution
    df::T     # degree of freedom
    Ψ::ST     # scale matrix
    c0::T     # log of normalizing constant
end

#### Constructors

function InverseWishart(df::T, Ψ::AbstractPDMat{T}) where T<:Real
    p = dim(Ψ)
    df > p - 1 || error("df should be greater than dim - 1.")
    c0 = _invwishart_c0(df, Ψ)
    R = Base.promote_eltype(T, c0)
    prom_Ψ = convert(AbstractArray{R}, Ψ)
    InverseWishart{R, typeof(prom_Ψ)}(R(df), prom_Ψ, R(c0))
end

function InverseWishart(df::Real, Ψ::AbstractPDMat)
    T = Base.promote_eltype(df, Ψ)
    InverseWishart(T(df), convert(AbstractArray{T}, Ψ))
end

InverseWishart(df::Real, Ψ::Matrix) = InverseWishart(df, PDMat(Ψ))

InverseWishart(df::Real, Ψ::Cholesky) = InverseWishart(df, PDMat(Ψ))

function _invwishart_c0(df::Real, Ψ::AbstractPDMat)
    h_df = df / 2
    p = dim(Ψ)
    h_df * (p * typeof(df)(logtwo) - logdet(Ψ)) + logmvgamma(p, h_df)
end

#### Properties

insupport(::Type{InverseWishart}, X::Matrix) = isposdef(X)
insupport(d::InverseWishart, X::Matrix) = size(X) == size(d) && isposdef(X)

dim(d::InverseWishart) = dim(d.Ψ)
size(d::InverseWishart) = (p = dim(d); (p, p))
size(d::InverseWishart, i) = size(d)[i]
rank(d::InverseWishart) = dim(d)
params(d::InverseWishart) = (d.df, d.Ψ, d.c0)
@inline partype(d::InverseWishart{T}) where {T<:Real} = T

### Conversion
function convert(::Type{InverseWishart{T}}, d::InverseWishart) where T<:Real
    P = convert(AbstractArray{T}, d.Ψ)
    InverseWishart{T, typeof(P)}(T(d.df), P, T(d.c0))
end
function convert(::Type{InverseWishart{T}}, df, Ψ::AbstractPDMat, c0) where T<:Real
    P = convert(AbstractArray{T}, Ψ)
    InverseWishart{T, typeof(P)}(T(df), P, T(c0))
end

#### Show

show(io::IO, d::InverseWishart) = show_multline(io, d, [(:df, d.df), (:Ψ, Matrix(d.Ψ))])

#### Statistics

function mean(d::InverseWishart)
    df = d.df
    p = dim(d)
    r = df - (p + 1)
    if r > 0.0
        return Matrix(d.Ψ) * (1.0 / r)
    else
        error("mean only defined for df > p + 1")
    end
end

mode(d::InverseWishart) = d.Ψ * inv(d.df + dim(d) + 1.0)

#  https://en.wikipedia.org/wiki/Inverse-Wishart_distribution#Moments
function cov(d::InverseWishart, i::Integer, j::Integer, k::Integer, l::Integer)
    p, ν, Ψ = (dim(d), d.df, Matrix(d.Ψ))
    ν > p + 3 || throw(ArgumentError("cov only defined for df > dim + 3"))
    inv((ν - p)*(ν - p - 3)*(ν - p - 1)^2)*(2Ψ[i,j]*Ψ[k,l] + (ν-p-1)*(Ψ[i,k]*Ψ[j,l] + Ψ[i,l]*Ψ[k,j]))
end

function var(d::InverseWishart, i::Integer, j::Integer)
    p, ν, Ψ = (dim(d), d.df, Matrix(d.Ψ))
    ν > p + 3 || throw(ArgumentError("var only defined for df > dim + 3"))
    inv((ν - p)*(ν - p - 3)*(ν - p - 1)^2)*(ν - p + 1)*Ψ[i,j]^2 + (ν - p - 1)*Ψ[i,i]*Ψ[j,j]
end

#### Evaluation

function _logpdf(d::InverseWishart, X::AbstractMatrix)
    p = dim(d)
    df = d.df
    Xcf = cholesky(X)
    # we use the fact: tr(Ψ * inv(X)) = tr(inv(X) * Ψ) = tr(X \ Ψ)
    Ψ = Matrix(d.Ψ)
    -0.5 * ((df + p + 1) * logdet(Xcf) + tr(Xcf \ Ψ)) - d.c0
end

#### Sampling

_rand!(rng::AbstractRNG, d::InverseWishart, A::AbstractMatrix) =
    (A .= inv(cholesky!(_rand!(rng, Wishart(d.df, inv(d.Ψ)), A))))


import MCLightTransport.scatterHG
using Plots

## Plotting Henyey-Greenstein scattering function
rnd = 0.0:0.001:1
plot( rnd, scatterHG.(rnd, 0.99), label="g = 0.99")
plot!(rnd, scatterHG.(rnd, 0.90), label="g = 0.90")
plot!(rnd, scatterHG.(rnd, 0.80), label="g = 0.80")
plot!(rnd, scatterHG.(rnd, 0.50), label="g = 0.50")
plot!(rnd, scatterHG.(rnd, 0.10), label="g = 0.10")
plot!(rnd, scatterHG.(rnd, 0.00), label="g = 0.00")
plot!(legend=:bottomright)

##
plot( rnd[1:end-1], log10.(diff(scatterHG.(rnd, 0.99))), label="g = 0.99")
plot!(rnd[1:end-1], log10.(diff(scatterHG.(rnd, 0.90))), label="g = 0.90")
plot!(rnd[1:end-1], log10.(diff(scatterHG.(rnd, 0.80))), label="g = 0.80")
plot!(rnd[1:end-1], log10.(diff(scatterHG.(rnd, 0.50))), label="g = 0.50")
plot!(rnd[1:end-1], log10.(diff(scatterHG.(rnd, 0.10))), label="g = 0.10")
plot!(rnd[1:end-1], log10.(diff(scatterHG.(rnd, 0.00))), label="g = 0.00")
plot!(legend=:bottomright)

##
henyey_greenstein(cosθ, g) = 0.5*(1 - g^2)/(1 + g^2 - 2*g*cosθ)^(3/2)


"""
    li4(z::ComplexF64)::ComplexF64

Returns the complex 4th order polylogarithm of a complex number `z` of type `ComplexF64`.

Author: Alexander Voigt

License: MIT

# Example
```julia
li4(1.0 + 1.0im)
```
"""
function li4(z::ComplexF64)::ComplexF64
    function clog(z)
        az::Float64 = angle(z)
        return 0.5*log(abs2(z)) + (imag(z) == 0.0 && az < 0.0 ? -az : az)*1.0im
    end

    z4::Float64 = 1.0823232337111382
    bf = (
        1.0                   , -7.0/16.0              ,
        1.1651234567901235e-01, -1.9820601851851852e-02,
        1.9279320987654321e-03, -3.1057098765432099e-05,
       -1.5624009114857835e-05,  8.4851235467732066e-07,
        2.2909616603189711e-07, -2.1832614218526917e-08,
       -3.8828248791720156e-09,  5.4462921032203321e-10,
        6.9608052106827254e-11, -1.3375737686445215e-11,
       -1.2784852685266572e-12,  3.2605628580248922e-13,
        2.3647571168618257e-14, -7.9231351220311617e-15
    )

    if imag(z) == 0.0
        if real(z) == 0.0
            return 0.0 + 0.0im
        end
        if real(z) == 1.0
            return z4 + 0.0im
        end
        if real(z) == -1.0
            return -7.0/8.0*z4 + 0.0im
        end
    end

    nz::Float64  = abs2(z)
    pz::Float64  = angle(z)
    lnz::Float64 = 0.5*log(nz)

    if lnz*lnz + pz*pz < 1.0 # |log(z)| < 1
        v::ComplexF64  = lnz + pz*im
        v2::ComplexF64 = v*v
        v4::ComplexF64 = v2*v2
        v8::ComplexF64 = v4*v4
        c1::Float64 = 1.2020569031595943 # zeta(3)
        c2::Float64 = 0.82246703342411322
        c3::ComplexF64 = (11.0/6.0 - clog(-v))/6.0
        c4::Float64 = -1.0/48.0

        cs = (
            -6.9444444444444444e-04, 1.6534391534391534e-06,
            -1.0935444136502338e-08, 1.0438378493934049e-10,
            -1.2165942300622435e-12, 1.6130006528350101e-14,
            -2.3428810452879340e-16
        )

        return z4 + v2 * (c2 + v2 * c4) +
            v * (
                c1 +
                c3*v2 +
                v4*(cs[1] + v2*cs[2]) +
                v8*(cs[3] + v2*cs[4] + v4*(cs[5] + v2*cs[6])) +
                v8*v8*cs[7]
            )
    end

    (u::ComplexF64, rest::ComplexF64, sgn::Float64) = if nz <= 1.0
        (-clog(1.0 - z), 0.0 + 0.0im, 1.0)
    else # nz > 1.0
        arg::Float64 = pz > 0.0 ? pz - pi : pz + pi
        lmz::ComplexF64 = lnz + arg*im # clog(z)
        lmz2::ComplexF64 = lmz*lmz
        (-clog(1.0 - 1.0/z), 1.0/360.0*(-7.0*pi^4 + lmz2*(-30.0*pi^2 - 15.0*lmz2)), -1.0)
    end

    u2::ComplexF64 = u*u
    u4::ComplexF64 = u2*u2
    u8::ComplexF64 = u4*u4

    rest + sgn * (
       u*bf[1] +
       u2*(bf[2] + u*bf[3]) +
       u4*(bf[4] + u*bf[5] + u2*(bf[6] + u*bf[7])) +
       u8*(bf[8] + u*bf[9] + u2*(bf[10] + u*bf[11]) +
           u4*(bf[12] + u*bf[13] + u2*(bf[14] + u*bf[15]))) +
       u8*u8*(bf[16] + u*bf[17] + u2*bf[18])
    )
end


"""Loads an edge list file as an adjacency matrix."""
function load_adjacency_matrix(path::String)
    edge_list = load_edge_list(path, UInt16)
    one_indexing!(edge_list)
    adj_matrix = load_adjacency_matrix(edge_list)
    return adj_matrix
end

function load_adjacency_matrix(edge_list::Array)
    n_nodes = size(edge_list, 1)
    adjacency_matrix = zeros(Bool, (n_nodes, n_nodes))
    for (n_s, n_t) in eachrow(edge_list)
        adjacency_matrix[n_s, n_t] = true
    end
    return adjacency_matrix
end

function load_adjacency_matrix(io::IO, n_nodes::Int)
    adjacency_matrix = zeros(Bool, (n_nodes, n_nodes))
    for line in eachline(io)
        n_s, n_t = process_line(line, Int)
        adjacency_matrix[n_s + 1, n_t + 1] = true
    end
    return adjacency_matrix
end


"""
Compute partition numbers https://en.wikipedia.org/wiki/Partition_(number_theory)
This example is not performant because for initial value `N` every step invokes
recursion on `N` subproblems but for most steps this creates many subproblems
with negative values, which are simply discarded.
"""

include("../ragamuffin.jl")

function partition(N, floor=1)
    if N == 0
        return 1
    end
    out = 0
    for i in floor:N
        out += partition(N - i, i)
    end
    return out
end

function expand_helper(x :: Cell{T}, n) :: Cell{T} where T
    if n >= x.value[2]
        return Cell{T}((x.value[1] - n, n), x.depth, x.output_index)
    else
        return Cell{T}((-1, -1) , -1, -1)
    end
end

function ragamuffin_partition(min_N, max_N)

    T = Tuple{Int32, Int32}
    program = initialize(collect((Int32(i), Int32(1)) for i in min_N:max_N), (-Int32(1), -Int32(1)), CuFrame, 1024, Int)
    add!(program, :root, (:test_base_case, :end), value_function = x -> x.value[1] >= 0 )
    add!(program, :test_base_case, (:increment, :expand), value_function = x -> x.value[1] == 0)
    add!(program, :increment, :end, dest_function = x -> 1)
    add!(program, :expand, :root, value_function = (expand_helper, collect(1:max_N)))

    @time run(program)
    return program.dest
end
max_N = 10
@time partition(max_N)
expected = collect(partition(i) for i in 1:max_N)
batched = ragamuffin_partition(1, max_N)
@info batched
@assert expected == batched


using Cairo
using LSystems

c = CairoRGBSurface(964, 964);
cr = CairoContext(c);

save(cr);

set_source_rgb(cr, 1.0, 1.0, 1.0);
rectangle(cr, 0.0, 0.0, 964.0, 964.0);
fill(cr);

restore(cr);
save(cr);

x, y, a, d = 10., 10., 180, 15
set_line_width(cr, 1);
move_to(cr, x, y);

hilbert_start = "A"
hilbert_trans = Dict([
('A', "-BF+AFA+FB-"),
('B', "+AF-BFB-FA+")
])

function determine_new_position(x, y, a, d)
    x -= sind(a) * d
    y -= cosd(a) * d
    return x, y
end

level = 6
drawing_instructions = @task dol_system(hilbert_start, hilbert_trans, level)

for v in drawing_instructions
    if v == 'F'
        x, y = determine_new_position(x, y, a, d)
        line_to(cr, x, y)
    elseif v == '+'
        a -= 90
    elseif v == '-'
        a += 90
    elseif v == '['
        push!(s, [x, y, a])
    elseif v == ']'
        x, y, a = pop!(s)
        move_to(cr, x, y)
    end
end

stroke(cr);
write_to_png(c, "hilbert_$level.png");


using Statistics

Statistics.varm(A::CuArray{<:Real},m::AbstractArray{<:Real}; dims, corrected::Bool=true) =
    sum((A .- m).^2, dims=dims)/(prod(size(A)[[dims...]])::Int-corrected)

Statistics.stdm(A::CuArray{<:Real},m::AbstractArray{<:Real}, dim::Int; corrected::Bool=true) =
    sqrt.(varm(A,m;dims=dim,corrected=corrected))

Statistics._std(A::CuArray, corrected::Bool, mean, dims) =
    Base.sqrt.(Statistics.var(A; corrected=corrected, mean=mean, dims=dims))

Statistics._std(A::CuArray, corrected::Bool, mean, ::Colon) =
    Base.sqrt.(Statistics.var(A; corrected=corrected, mean=mean, dims=:))

# Revert https://github.com/JuliaLang/Statistics.jl/pull/25
Statistics._mean(A::CuArray, ::Colon)    = sum(A) / length(A)
Statistics._mean(f, A::CuArray, ::Colon) = sum(f, A) / length(A)
Statistics._mean(A::CuArray, dims)    = mean!(Base.reducedim_init(t -> t/2, +, A, dims), A)
Statistics._mean(f, A::CuArray, dims) = sum(f, A, dims=dims) / mapreduce(i -> size(A, i), *, unique(dims); init=1)


##Testing the Generation of Kronecker Graph
include("KronGraph500NoPerm.jl")

SCALE = 12
EdgesPerVertex = 16
Nmax = 2.^SCALE
M = EdgesPerVertex .* Nmax

v1,v2 = KronGraph500NoPerm(SCALE,EdgesPerVertex)
A = sparse(v1,v2,1)

#There is a spy in PyPlot
#=
using PyPlot

figure
spy(A)
xlabel("end vertex")
ylabel("start vertex")

figure
loglog(full(OutDegree(A)),'o')
xlabel("degree")
ylabel("count")

spy(A)

=#


@doc raw"""
    Game(s, t)

A two-player, two-strategy, symmetric game. The games are characterized by the diagonal elements of
a payoff matrix

```math
\begin{bmatrix}
    1 & S \\
    T & 0
\end{bmatrix}
```

with ``-0.5 \leq S \leq 0.5`` and ``0.5 \leq T \leq 1.5``,
"""
struct Game
    payoffs::SMatrix{2, 2, Float64}
    function Game(s::Number, t::Number)
        if -0.5 ≤ s ≤ 0.5
            if 0.5 ≤ t ≤ 1.5
                return new([1.0 s; t 0.0])
            end
            throw(DomainError(t, "expected 0.5 ≤ t ≤ 1.5"))
        end
        throw(DomainError(s, "expected -0.5 ≤ s ≤ 0.5"))
    end
end

"""
    sparam(game)

Get the S-parameter of the game.
"""
sparam(g::Game) = g.payoffs[1,2]

"""
    tparam(game)

Get the T-parameter of the game.
"""
tparam(g::Game) = g.payoffs[2,1]

"""
    quadrant(game)

Get the quadrant of S-T space in which the game resides.
"""
function quadrant(g::Game)
    S, T = sparam(g), tparam(g)
    if one(T) ≤ T
        zero(S) ≤ S ? 1 : 4
    else
        zero(S) ≤ S ? 2 : 3
    end
end

"""
    name(quad::Int)

Get the classical name of the games in a quadrant, e.g. Hawk-Dove, Harmony, Stag Hunt or Prisoner's
Dilemma.
"""
function name(quad::Int)
    if quad === 1
        "Hawk-Dove"
    elseif quad === 2
        "Harmony"
    elseif quad === 3
        "Stag Hunt"
    elseif quad === 4
        "Prisoner's Dilemma"
    else
        error("invalid quadrant: $q")
    end
end

"""
    name(game)

Get the classical name of the game, e.g. Hawk-Dove, Harmony, Stag Hunt or Prisoner's Dilemma.
"""
name(g::Game) = name(quadrant(g))

"""
    play(g::Game, a, b)

Play the game with the strategies `a` and `b`, returning the payoff recieved by agent playing `a`.
"""
play(g::Game, a::Int, b::Int) = g.payoffs[a, b]

Base.show(io::IO, g::Game) = print(io, "Game($(sparam(g)), $(tparam(g)))")

const GamesIterator = let ParameterRange = typeof(-0.5:0.1:0.5)
    Iterators.ProductIterator{Tuple{ParameterRange, ParameterRange}}
end

"""
    Games(sstep, tstep)

An iterator over the games with ``S`` and ``T`` parameters varied over their ranges with the
provided step sizes.
"""
struct Games
    iterator::GamesIterator
    Games(sstep, tstep) = let ss = -0.5:sstep:0.5, ts = 0.5:tstep:1.5
        new(product(ss, ts))
    end
end

Base.iterate(g::Games) = let iteree = iterate(g.iterator)
    if iteree === nothing
        iteree
    else
        element, state = iteree
        Game(element...), state
    end
end

Base.iterate(g::Games, state) = let iteree = iterate(g.iterator, state)
    if iteree === nothing
        iteree
    else
        element, state = iteree
        Game(element...), state
    end
end

Base.length(g::Games) = length(g.iterator)

@doc raw"""
    Sigmoid(β)

A rule specifying that the agent will switch its strategy with probability

```math
P(dp) = \frac{1}{1 + e^{-\beta dp}}
```

where ``dp`` is the difference in the two payoffs.
"""
struct Sigmoid <: AbstractRule
    β::Float64
    function Sigmoid(β::Float64 = 1.0)
        if β < zero(β)
            throw(DomainError(β, "must be non-negative"))
        end
        new(β)
    end
end

apply(s::Sigmoid, dp::Float64) = 1.0 / (1.0 + exp(-s.β * dp))

@doc raw"""
    Heaviside(ϵ)

A rule specifying that the agent will switch its strategy with probability

```math
P(dp) = \begin{cases}
    0.0, & dp \leq -\epsilon \\
    0.5, & -\epsilon < dp < \epsilon \\
    1.0, & \epsilon < dp
\end{cases}
```

where ``dp`` is the difference in the two payoffs.
"""
struct Heaviside <: AbstractRule
    ϵ::Float64
    function Heaviside(ϵ::Float64 = 1e-3)
        if ϵ < zero(ϵ)
            throw(DomainError(ϵ, "must be non-negative"))
        end
        new(ϵ)
    end
end

apply(h::Heaviside, dp::Float64) = (abs(dp) < h.ϵ) ? 0.5 : float(dp > 0)

lattice_graph(m::Int, n::Int) = if m < 1
    throw(DomainError(m, "lattice must have at least 1 row"))
elseif n < 1
    throw(DomainError(m, "lattice must have at least 1 column"))
else
    crosspath(m, path_graph(n))
end

"""
    RandomScheme(p=0.5)

The "random" update scheme. Under this scheme, the agent will randomly choose strategy 2 with
probability `p`. This is primarily for validation purposes.
"""
struct RandomScheme <: AbstractScheme
    p::Float64
end
RandomScheme() = RandomScheme(0.5)

function decide(r::RandomScheme,
                arena::AbstractArena{G, RandomScheme},
                ss::AbstractVector{Int},
                ps::AbstractArray{Float64,2}) where {G <: SimpleGraph}
    Int.(rand(length(ss)) .> r.p) .+ 1
end

function decide(::RandomScheme, ::AbstractArena, ::AbstractVector{Int}, ::AbstractVector{Float64})
    error("this method is ill-defined")
end
decide(::RandomScheme, ::AbstractArena, ::Int, ::Float64) = error("this method is ill-defined")

"""
    CounterFactural([rule = Sigmoid()])

The "counter-factual" update scheme. Under this scheme, the agent will switch their strategy
according to some probability. The probability the agent will switch is determined by applying the
scheme's rule to the payoff difference.
"""
struct CounterFactual{Rule <: AbstractRule} <: AbstractScheme
    rule::Rule
end
CounterFactual() = CounterFactual{Sigmoid}(Sigmoid())

function decide(cf::S,
                arena::AbstractArena{G, S},
                s::Int,
                dp::Float64) where {G, S <: CounterFactual}
    (rand() <= apply(cf.rule, dp)) ? (3 - s) : s
end

"""
    MimicRandom()

The "mimic random" update scheme. Under this scheme, the agent will switch their strategy at
uniformly at random to the strategy of one of their neighbors or their own.
"""
struct MimicRandom <: AbstractScheme end

function decide(m::MimicRandom,
                arena::AbstractArena{G, MimicRandom},
                ss::AbstractVector{Int},
                dps::AbstractArray{Float64,2}) where {G <: SimpleGraph}
    map(i -> ss[rand([i; neighbors(arena, i)])], 1:length(ss))
end

function decide(::MimicRandom, ::AbstractArena, ::AbstractVector{Int}, ::AbstractVector{Float64})
    error("this method is ill-defined")
end
decide(::MimicRandom, ::AbstractArena, ::Int, ::Float64) = error("this method is ill-defined")

"""
    MimicBest()

The "mimic best" update scheme. Under this scheme, the agent will switch their strategy to that
of their neighbors who recieved the greatest payout, possibly retaining their own.
"""
struct MimicBest <: AbstractScheme end

function decide(m::MimicBest,
                arena::AbstractArena{G, MimicBest},
                ss::AbstractVector{Int},
                ps::AbstractArray{Float64,2}) where {G <: SimpleGraph}
    map(1:length(ss)) do i
        ns = neighbors(arena, i)
        m, j = findmax(ps[ns])
        m > ps[i] ? ss[ns[j]] : ss[i]
    end
end

function decide(::MimicBest, ::AbstractArena, ::AbstractVector{Int}, ::AbstractVector{Float64})
    error("this method is ill-defined")
end
decide(::MimicBest, ::AbstractArena, ::Int, ::Float64) = error("this method is ill-defined")

"""
    MimicBiased()

The "mimic biased" update scheme. Under this scheme, the agent will switch their strategy
randomly to that of their neighbors with a log-probability proportional to their payoffs.
"""
struct MimicBiased <: AbstractScheme end

function decide(m::MimicBiased,
                arena::AbstractArena{G, MimicBiased},
                ss::AbstractVector{Int},
                ps::AbstractArray{Float64,2}) where {G <: SimpleGraph}
    map(1:length(ss)) do i
        idx = [i; neighbors(arena, i)]
        p = exp.(ps[idx])

        perm = sortperm(p)
        p = p[perm]
        idx = idx[perm]

        j = findfirst(cumsum(p) .>= sum(p)*rand())
        ss[idx[j]]
    end
end

function decide(::MimicBiased, ::AbstractArena, ::AbstractVector{Int}, ::AbstractVector{Float64})
    error("this method is ill-defined")
end
decide(::MimicBiased, ::AbstractArena, ::Int, ::Float64) = error("this method is ill-defined")

"""
    Arena(game, graph, scheme) <: AbstractArena

An arena in which the agents, situated on the vertices of a graph, play a game with their neighbors.
Every pair of agents plays the same game. With each round of play, the agents use the scheme to
decide what their next strategy will be.

In this arena, the agents accumulate payoff by playing with every neighbor. The decision of what
strategy to use in the next time step is based on the difference between the total payoffs for each
of the agents' strategies.
"""
struct Arena{Graph, Scheme} <: AbstractArena{Graph, Scheme}
    game::Game
    graph::Graph
    scheme::Scheme
    function Arena(ga::Game, gr::Graph, s::Scheme) where {Graph, Scheme}
        if nv(gr) < 2
            throw(DomainError(gr, "graph cannot be empty"))
        elseif has_self_loops(gr)
            throw(DomainError(gr, "graph cannot have self-loops"))
        elseif any(map(c -> length(c) < 2, connected_components(gr)))
            throw(DomainError(gr, "each component of the graph must have at least 2 elements"))
        end
        new{Graph, Scheme}(ga, gr, s)
    end
end


using ECOS
using JuMP
using StructJuMP
# I have started a package for SDDP
# The solution of problem 5.2 and 5.3 can be seen as a particular case of this work
using StructDualDynProg

# Telling ECOS solver to shut up :)
solver = ECOS.ECOSSolver(verbose=false)
# You can replace it with another solver if you wish

include("data.jl")

# Modelisation with the package StructJuMP
numScen = 2
M1 = StructuredModel(num_scenarios=numScen)

@variable M1 x1[1:n] >= 0
# change the false to true to see the effect of adding the variable v1
if false
    @variable M1 v1[1:n] >= 0
    @constraints M1 begin
        x1 .== v1
    end
    @objective(M1, Min, dot(ic, v1))
else
    @objective(M1, Min, dot(ic, x1))
end

for s in 1:numScen
    M2 = StructuredModel(parent=M1, prob=p2[s])
    @variable(M2, y2[1:n, 1:m] >= 0)
    @constraints M2 begin
        demand[j=1:m], sum(y2[:,j]) == D2[j,s]
        ylim[i=1:n], sum(y2[i,:]) <= x1[i]
    end
    @objective(M2, Min, dot(C, y2 * T))
end

# Solving with my package SDDP

for cutmode in [:AveragedCut, :MultiCut]
    status, objval, sol = SDDP(M1, 2, solver, cutmode)

    v11value = sol[1:4]
    @show status
    @show objval
    @show v11value
    SDDPclear(M1)
end


"""
    precessing_nutating_example([Ωₒ], [Ωₚ], [α], [α̇], [ν], [R₀])

Return an exact quaternionic function of time representing nutating
precessional orbital motion, as described in Sec. 6.2 of [this
paper](https://arxiv.org/abs/1604.08139).  This example is useful because it
provides physically realistic but *very* complicated motion, while still being
simple to code up and differentiate analytically.

The output represents the rotation of the line joining the orbiting bodies and
their angular velocity.  The following are the input parameters, along with
their default values and physical interpretations:

  * `Ωₒ=2π/1_000`: The orbital frequency
  * `Ωₚ=2π/10_000`: The precessional frequency
  * `α=π/8`: The opening angle of the precession cone at ``t=0``
  * `α̇=2α/100_000`: The rate of opening of the precession cone
  * `ν=π/80`: The angle of nutation
  * `R₀=exp(-3α*imx/10)`: Overal rotation of the system

The default values are chosen to be typical of a (potentially) real precessing
binary black hole system shortly before merger.

The returned objects are three functions of time: `R`, `ω⃗`, and `Ṙ`, which
return the orientation as a `Rotor`, followed by the angular velocity as a
`QuatVec`, and the time-derivative of `R` as a `Quaternion`.

# Example
```jldoctest
julia> R, ω⃗, Ṙ = precessing_nutating_example();

julia> R(12.34)
0.9944579779058746 + 0.09804177421238346𝐢 - 0.00084850453525312𝐣 + 0.03795287510453948𝐤
julia> ω⃗(345.67)
0.0 + 0.00046343007342867023𝐢 - 0.0007032818419003181𝐣 + 0.006214814810035088𝐤
julia> ϵ = 1e-6; (R(ϵ) - R(-ϵ)) / 2ϵ  # Approximate derivative at t=0
-3.8491432263754177e-7 + (3.9080960689830135e-6)𝐢 - (6.861695854245619e-5)𝐣 + 0.003076329202503836𝐤
julia> Ṙ(0)
-3.8491240998153413e-7 + (3.9080812828476746e-6)𝐢 - (6.861695854245637e-5)𝐣 + 0.0030763292025038354𝐤
```

"""
function precessing_nutating_example(Ωₒ=2π/1_000, Ωₚ=2π/10_000, α=π/8, α̇=2α/100_000, ν=π/80, R₀=exp(-3α*imx/10))
    R₁(t) = exp(Ωₒ*t*imz/2)
    R₂(t) = exp((α + α̇*t)*imx/2)
    R₃(t) = exp(Ωₚ*t*imz/2)
    R₄(t) = exp(ν*imx/2)
    R(t) = R₀ * R₁(t) * R₄(t) * inv(R₁(t)) * R₃(t) * R₂(t) * inv(R₃(t)) * R₁(t)
    Ṙ₀ = 0imz
    Ṙ₁(t) = R₁(t) * (Ωₒ*imz/2)
    Ṙ₂(t) = R₂(t) * (α̇*imx/2)
    Ṙ₃(t) = R₃(t) * (Ωₚ*imz/2)
    Ṙ₄(t) = 0imz
    Ṙ(t) = (
        Ṙ₀ * R₁(t) * R₄(t) * inv(R₁(t)) * R₃(t) * R₂(t) * inv(R₃(t)) * R₁(t) +
        R₀ * Ṙ₁(t) * R₄(t) * inv(R₁(t)) * R₃(t) * R₂(t) * inv(R₃(t)) * R₁(t) +
        R₀ * R₁(t) * Ṙ₄(t) * inv(R₁(t)) * R₃(t) * R₂(t) * inv(R₃(t)) * R₁(t) +
        -R₀ * R₁(t) * R₄(t) * inv(R₁(t)) * Ṙ₁(t) * inv(R₁(t)) * R₃(t) * R₂(t) * inv(R₃(t)) * R₁(t) +
        R₀ * R₁(t) * R₄(t) * inv(R₁(t)) * Ṙ₃(t) * R₂(t) * inv(R₃(t)) * R₁(t) +
        R₀ * R₁(t) * R₄(t) * inv(R₁(t)) * R₃(t) * Ṙ₂(t) * inv(R₃(t)) * R₁(t) +
        -R₀ * R₁(t) * R₄(t) * inv(R₁(t)) * R₃(t) * R₂(t) * inv(R₃(t)) * Ṙ₃(t) * inv(R₃(t)) * R₁(t) +
        R₀ * R₁(t) * R₄(t) * inv(R₁(t)) * R₃(t) * R₂(t) * inv(R₃(t)) * Ṙ₁(t)
    )
    ω⃗(t) = 2 * QuatVec(Ṙ(t) * inv(R(t)))
    (R, ω⃗, Ṙ)
end


using DataFrames
using LinearAlgebra

export Mack, claims2cum, cum2claims, cum2futureclaims, replacena!
"""
A reserve triangle with chain ladder information
"""
mutable struct Mack
  "Length of history"
  I::Int
  "Cummulative claims (upper triangle)"
  cum::Matrix{Float64}
  "Time vector of development factors"
  f::Vector{Float64}
  "Time vector of future claims payments"
  futureclaims::Vector{Float64}
  "Vector of reseves"
  res::Vector{Float64}
  """
  Total reserves: ``\\mathit{tot\\_res} =\\sum_{i=1}^I \\mathit{res}_i``
  """
  tot_res::Float64
  "Vector of mean square errors"
  mse::Vector{Float64}
  "Total mean square error"
  tot_mse::Float64
end

# function Mack(df_triang::DataFrame; cum::Bool = false)
# Mack(convert(DataArray{Real, 2}, df_triang); cum = cum)
# end
#
# function Mack(da_triang::DataArray{Real, 2}; cum::Bool = false)
#   Mack(convert(Array{Real,2}, da_triang, 0); cum = cum)
# end

function Mack(triang::Matrix{Float64}; cum::Bool = false)
  if cum
    C = deepcopy(triang)
  else
    C = claims2cum(triang)
  end
  # Number of accident / development years
  I = size(C)[1]
  # Development factors
  f = [sum(C[1:(I-𝑘),𝑘+1])/sum(C[1:(I-𝑘),𝑘]) for 𝑘 ∈ 1:(I-1)]
  # Ultimate claim amounts
  for 𝑘  ∈ 2:I
    for 𝑙 ∈ (I+1-𝑘+1):I
      C[𝑘,𝑙] = C[𝑘,I+1-𝑘] * prod(f[(I+1-𝑘):( 𝑙 -1)])
    end
  end
  futclaims = cum2futureclaims(C)
  # Reserves
  R = [C[𝑘,I]-C[𝑘,I+1-𝑘] for 𝑘 ∈ 1:I]
  σ² =
    [1/(I-𝑘-1) *
      C[1:(I-𝑘),𝑘] ⋅ (C[1:(I-𝑘),𝑘+1] ./ C[1:(I-𝑘),𝑘] .- f[𝑘]).^2
    for 𝑘 ∈ 1:(I-1)]
  σ²[I-1] = min(σ²[I-2]^2 / σ²[I-3], min(σ²[I-3],σ²[I-2]))
  # mean square error
  mse = zeros(Float64, I)
  for 𝑖 ∈ 2:I
    mse[𝑖] = 0
    for 𝑘 ∈ (I+1-𝑖):(I-1)
      mse[𝑖] += σ²[𝑘]/f[𝑘]^2 * (1/C[𝑖,𝑘] + 1/ sum(C[1:I-𝑘,𝑘]))
    end
    mse[𝑖] *= C[𝑖,I]^2
  end
  # total mean square error
  mse_total = 0.0
  for 𝑖 ∈ 2:I
    tmp = 0.0
    for 𝑘 ∈ collect(I+1-𝑖 : I-1)
      tmp += (2σ²[𝑘] / f[𝑘]^2) / sum(C[1 : I-𝑘, 𝑘])
    end
    tmp *= C[𝑖,I] * sum(C[𝑖+1 : I, I])
    tmp += mse[𝑖]
    mse_total += tmp
  end
  return Mack(I, C, f, futclaims, R, sum(R), mse, mse_total)
end

"""
replacena!(df::DataFrame, replacement::Any)

Replaces all 'NA'-values with 'replacement'
"""
function replacena!(df::DataFrame, replacement::Any)
  nrows, ncols = size(df)
  for 𝑗 ∈ 1:ncols; for 𝑖 ∈ 1:nrows
    if ismissing(df[𝑖,𝑗]); df[𝑖,𝑗] = replacement; end
    end
  end
end

"""
upperleft(quadrat_mat::Array{Real,2})

Sets all values in the strict lower right triangle to 0.
"""
function upperleft(quadrat_mat::Matrix{T}) where T <: Real
  c = deepcopy(quadrat_mat)
  n = size(c,1)
  if n ≠ size(c,2)
    error("known: Matrix is not quadratic")
  end
  for 𝑗 ∈ 1:n; for 𝑖 ∈ 1:n
    if 𝑖 > n-𝑗+1; c[𝑖,𝑗] = 0 end
  end; end
  c
end

"""
lowerright(quadrat_mat::Array{Real,2})

Sets all values in the upperleft triangle (incl. diagonal) to 0.
"""
function lowerright(quadrat_mat::Matrix{T}) where T <: Real
  quadrat_mat - upperleft(quadrat_mat)
end

"""
    claims2cum(c::Matrix)

    Convert a quadratic upper left triangular reserve matrix to
    the correponding cumulative triangular reserve matrix.
    The (strictly) lower triangular part is set to 0
"""
function claims2cum(c::Matrix{T}) where T <: Real
  if size(c,1) ≠ size(c,2)
    error("claims2cum: Matrix is not quadratic")
  end
 # cum = zeros(Real,size(c))
  #for i in 1:size(c, 1)
    #cum[i,1:size(c, 1)-i+1] = cumsum(c[i,1:size(c, 1)-i+1], 2)
  #end
  upperleft(cumsum(c, dims=2))
end

"""
    cum2claims(c::Matrix)

    Convert a quadratic cumulative claims matrix into a
    claims matrix.
"""
function cum2claims(c::Matrix{T}) where T <: Real
  I = size(c,2)
  if size(c,1) ≠ I
    error("cum2claims: Matrix is not quadratic")
  end
  claims = zero(c)
  for 𝑖 ∈ 1:I
    claims[𝑖,1] = c[𝑖,1]
    for 𝑘 ∈ 2:I
     claims[𝑖,𝑘] = c[𝑖,𝑘]-c[𝑖,𝑘-1]
    end
  end
  claims
end

"""
    cum2futclaims(c::Array{Real,2})

    Calculate the future claims vector from the quadratic,
    (strictly) lower triangle of a projected cumulative claims
    matrix
"""
function cum2futureclaims(c::Matrix{T}) where T <: Real
  I = size(c,2)
  if size(c,1) ≠ I
    error("cumclaims: Matrix is not quadratic")
  end
  claims = cum2claims(c)
  futureclaims = zero(claims[1:(I-1), 1])
   for 𝑘 ∈ 2:I
    futureclaims[𝑘-1] = sum(claims[I-𝑘+2:I,𝑘])
  end
  futureclaims
end


# Compute first num_components basis vectors for PCA, after subtracting projection onto fixed_comp
function fit_gen_pca_rv{T}(X::Array{T,2}, fixed_comp::Array{T,1}; quad_term::Array{T,1} = zeros(T,0), num_components::Integer=4, tol::Float64=1e-12, max_it::Int64 = 10)
  num_lambda = size(X,1)
  num_spectra = size(X,2)
  M = rand(T, (num_lambda,num_components) )  # random initialization is part of algorithm (i.e., not zeros)
  s = zeros(T,num_lambda)                    # pre-allocated memory for compute_pca_component
  scores = zeros(num_spectra,num_components)
  mu = vec(mean(X,2))
  Xtmp = X.-mu                               # perform PCA after subtracting off mean
  totalvar = sum(abs2,Xtmp)
  fracvar = zeros(num_components)
  M[:,1] = fixed_comp                        # Force fixed (i.e., Doppler) component to replace first PCA component
  fixed_comp_norm = 1/sum(abs2,fixed_comp)
  for i in 1:num_spectra
    scores[i,1] = z = (dot(view(Xtmp,:,i),fixed_comp)*fixed_comp_norm)  # Normalize differently, so scores are z (i.e., doppler shift)
	  Xtmp[:,i] -= z*fixed_comp
    if length(quad_term) == length(fixed_comp)  # Optionally subtract off quadratic term from Doppler shift
      Xtmp[:,i] -= 0.5*z*z*quad_term   # subtract off projection onto fixed component and quadratic term separately
    end
  end
  fracvar[1] = sum(abs2,Xtmp)/totalvar
  #println("# sum(abs2,Xtmp) = ", sum(abs2,Xtmp) )
  #println("# j = ", 1, " sum(abs2,Xtmp) = ", sum(abs2,Xtmp), " frac_var_remain= ", fracvar[1] )
  for j in 2:num_components
    compute_pca_component!(Xtmp,view(M,:,j),s,tol=tol,max_it=max_it)
    for i in 1:num_spectra
       scores[i,j] = dot(view(Xtmp,:,i),view(M,:,j)) #/sum(abs2,view(M,:,j-1))
       Xtmp[:,i] .-= scores[i,j]*view(M,:,j)
	  end
    fracvar[j] = sum(abs2,Xtmp)/totalvar
    println("# j = ", j, " sum(abs2,Xtmp) = ", sum(abs2,Xtmp), " frac_var_remain= ", fracvar[j] )
  end
  return (mu,M,scores)
end

function est_rvs_from_pc{T}(X::Array{T,2}, mu::Vector{T}, doppler_comp::Vector{T})
  num_lambda = size(X,1)
  num_spectra = size(X,2)
  speed_of_light = 3.0e8
  rv = zeros(num_spectra)
  Xtmp = X.-mu
  fixed_comp_norm = 1/sum(abs2,doppler_comp)
  for i in 1:num_spectra
      rv[i] = dot(Xtmp[:,i],doppler_comp)*fixed_comp_norm*speed_of_light
  end
  rv
end


export LSRK144, LSRK54, solve!, dostep!

function solve!(q, timeend, solver;
                after_step::Function = (x...) -> nothing,
                after_stage::Function = (x...) -> nothing)
  finalstep = false
  step = 0
  while true
    step += 1
    time = solver.time
    if time + solver.dt >= timeend
      solver.dt = timeend - time
      finalstep = true
    end
    dostep!(q, solver, after_stage)
    after_step(step, time, q)
    finalstep && break
  end
end

mutable struct LSRK{FT, AT, NS, RHS}
  dt::FT
  time::FT
  rhs!::RHS
  dq::AT
  rka::NTuple{NS, FT}
  rkb::NTuple{NS, FT}
  rkc::NTuple{NS, FT}

  function LSRK(rhs!, rka, rkb, rkc, q, dt, t0)
      FT = eltype(eltype(q))
      dq = similar(q)
      fill!(dq, zero(eltype(q)))
      AT = typeof(q)
      RHS = typeof(rhs!)
      new{FT, AT, length(rka), RHS}(FT(dt), FT(t0), rhs!, dq, rka, rkb, rkc)
  end
end

function dostep!(q, lsrk::LSRK, after_stage)
  @unpack rhs!, dq, rka, rkb, rkc, dt, time = lsrk
  for stage = 1:length(rka)
    stagetime = time + rkc[stage] * dt
    dq .*= rka[stage]
    rhs!(dq, q, stagetime)
    @. q += rkb[stage] * dt * dq
    after_stage(stagetime, q)
  end
  lsrk.time += dt
end

function LSRK144(rhs!, q, dt; t0 = 0)
  rka = (
     0,
    -0.7188012108672410,
    -0.7785331173421570,
    -0.0053282796654044,
    -0.8552979934029281,
    -3.9564138245774565,
    -1.5780575380587385,
    -2.0837094552574054,
    -0.7483334182761610,
    -0.7032861106563359,
     0.0013917096117681,
    -0.0932075369637460,
    -0.9514200470875948,
    -7.1151571693922548,
  )

  rkb = (
    0.0367762454319673,
    0.3136296607553959,
    0.1531848691869027,
    0.0030097086818182,
    0.3326293790646110,
    0.2440251405350864,
    0.3718879239592277,
    0.6204126221582444,
    0.1524043173028741,
    0.0760894927419266,
    0.0077604214040978,
    0.0024647284755382,
    0.0780348340049386,
    5.5059777270269628,
  )

  rkc = (
    0,
    0.0367762454319673,
    0.1249685262725025,
    0.2446177702277698,
    0.2476149531070420,
    0.2969311120382472,
    0.3978149645802642,
    0.5270854589440328,
    0.6981269994175695,
    0.8190890835352128,
    0.8527059887098624,
    0.8604711817462826,
    0.8627060376969976,
    0.8734213127600976,
  )
  LSRK(rhs!, rka, rkb, rkc, q, dt, t0)
end

function LSRK54(rhs!, q, dt; t0 = 0)
  rka = (
    (0),
    (-567301805773 // 1357537059087),
    (-2404267990393 // 2016746695238),
    (-3550918686646 // 2091501179385),
    (-1275806237668 // 842570457699),
  )

  rkb = (
    (1432997174477 // 9575080441755),
    (5161836677717 // 13612068292357),
    (1720146321549 // 2090206949498),
    (3134564353537 // 4481467310338),
    (2277821191437 // 14882151754819),
  )

  rkc = (
    (0),
    (1432997174477 // 9575080441755),
    (2526269341429 // 6820363962896),
    (2006345519317 // 3224310063776),
    (2802321613138 // 2924317926251),
  )
  LSRK(rhs!, rka, rkb, rkc, q, dt, t0)
end


using Test
using CVChannel

"""
This script verifies that the channels formed from axisymmetric states are multiplicative
for qubits and qutrits.
"""

println("Verifying qubit multiplicativity of axisymmetric channels")
@testset "qubit multiplicativity of axisymmetric channels" begin
    y_step = 0.1
    x_step = 0.1

    d = 2
    y_bounds = CVChannel._axisymmetric_y_bounds(d)

    it = 0
    y_range = y_bounds[1]:y_step:y_bounds[2]
    for y in y_range
        it = it + 1
        println("verifying y-slice $it of $(length(y_range))")
        x_constraints = CVChannel._axisymmetric_x_constraints(d,y)
        for x in x_constraints[1]:x_step:x_constraints[2]
            @testset "(x,y) = ($x,$y)" begin
                ρ_axi = axisymmetricState(d,x,y)

                J_N = d*ρ_axi
                (opt_cv_N, opt_σAB_N) = pptCVDual(J_N, d, d)

                J_NN = permuteSubsystems(kron(J_N,J_N), [1,3,2,4], [d,d,d,d])
                (opt_cv_NN, opt_σAB_NN) = pptCVPrimal(J_NN, d^2, d^2)

                multiplicativity = opt_cv_NN - opt_cv_N^2

                @test multiplicativity ≈ 0 atol=2e-5
            end
        end
    end
end

println("Verifying qutrit multiplicativity of axisymmetric channels")
@time @testset "qutrit multiplicativity of axisymmetric channels" begin
    y_step = 0.1
    x_step = 0.1

    d = 3

    y_bounds = CVChannel._axisymmetric_y_bounds(d)

    y_range = y_bounds[1]:y_step:y_bounds[2]
    it = 0
    for y in y_range
        it = it + 1
        println("verifying y-slice $it of $(length(y_range))")
        x_constraints = CVChannel._axisymmetric_x_constraints(d,y)
        for x in x_constraints[1]:x_step:x_constraints[2]
            @testset "(x,y) = ($x,$y)" begin
                ρ_axi = axisymmetricState(d,x,y)

                J_N = d*ρ_axi
                (opt_cv_N, opt_σAB_N) = pptCVDual(J_N, d, d)

                J_NN = permuteSubsystems(kron(J_N,J_N), [1,3,2,4], [d,d,d,d])
                (opt_cv_NN, opt_σAB_NN) = pptCVPrimal(J_NN, d^2, d^2)

                multiplicativity = opt_cv_NN - opt_cv_N^2

                @test multiplicativity ≈ 0 atol=2e-5
            end
        end
    end
end


### A Pluto.jl notebook ###
# v0.14.5

using Markdown
using InteractiveUtils

# ╔═╡ 8bc61658-b880-11eb-0b6c-a5b7ff4dcf3e
using SampledSignals

# ╔═╡ 821a6565-7046-46ff-b874-521fd846d703
using WAV

# ╔═╡ 06853df6-44a0-42cb-8e5a-95bbc8962b81
using Base, UnicodePlots

# ╔═╡ aa8e6100-5919-461a-8394-dea283f15b00
using Unitful

# ╔═╡ bc1888ec-9c33-45e7-91cb-4a7024f1acd7
begin

# some examples using signal operators
using SignalOperators

# a pure tone 20 dB below a power 1 signal, with on and off ramps (for
# a smooth onset/offset)
sound1 = Signal(sin,ω=1kHz) |> Until(5s) |> Ramp |> Normpower |> Amplify(-20dB)

# a sound defined by a file, matching the overall power to that of sound1
sound2 = "example.wav" |> Normpower |> Amplify(-20dB)

# a 1kHz sawtooth wave
sound3 = Signal(ϕ -> ϕ-π,ω=1kHz) |> Ramp |> Normpower |> Amplify(-20dB)

# a 5 Hz amplitude modulated noise
sound4 = randn |>
    Amplify(Signal(ϕ -> 0.5sin(ϕ) + 0.5,ω=5Hz)) |>
    Until(5s) |> Normpower |> Amplify(-20dB)

# a 1kHz tone surrounded by a notch noise
SNR = 5dB
x = Signal(sin,ω=1kHz) |> Until(1s) |> Ramp |> Normpower |> Amplify(-20dB + SNR)
z = Signal(randn) |> Until(1s) |> Filt(Bandstop,0.5kHz,2kHz) |> Normpower |> Amplify(-20dB)
scene = Mix(x,z)

# # write all of the signals to a single file, at 44.1 kHz
# Append(sound1,sound2,sound3,sound4,scene) |> ToFramerate(44.1kHz) |> sink("examples.wav")
end

# ╔═╡ 770f4a6b-2fe3-4493-a324-08a54131ea88
md"""
# Oscillators
It is the most important component of a synthesizer, it is a component that
that generates a sequence of numbers that repeats after a certain interval.

The most simplest oscillator is one that produces which is generated by
unsurprisingly yet delightfully, the sine function.

https://www.reddit.com/r/Python/comments/lw50ne/making_a_synthesizer_using_python/
https://python.plainenglish.io/making-a-synth-with-python-oscillators-2cb8e68e9c3b
"""

# ╔═╡ fe3abf37-bcad-4717-9ebd-702838c5ccbf
begin    # Example audio
    audio_one_channel = SampleBuf(rand(100000), 10000)
    audio_two_channel = SampleBuf(rand(100000,2), 10000)
    audio_multi_channel = SampleBuf(rand(100000,2), 10000)
end

# ╔═╡ 961a4dfa-0cbb-46aa-8af1-a056ade120ad
function get_sin_oscillator(freq=1, sample_rate=512)
	increment = ( 2 * pi * freq) / sample_rate
	return(sin(v) for v in Iterators.countfrom(0, increment))
end

# ╔═╡ dfe31fdd-6be0-41cf-8fae-77c0e8692627
begin
osc =  get_sin_oscillator(1, 512)
samples = [osc for i in 1:512]
# samples = [next(osc,3) for i in 1:512]
end

# ╔═╡ d1890135-cbcf-4b23-a32c-418be77debc0
samples

# ╔═╡ ea2c8ea0-7f9d-4687-b657-21e4d2a0c253
sizeof(osc)

# ╔═╡ 764cb127-bcec-450f-8ed5-933e3bc1db6c
begin

# some examples from WaveForm.jl
squarewave(x::Real) = ifelse(mod2pi(x) < π, 1.0, -1.0)

md"""
    squarewave(x, θ)
Compute ``2\pi``-periodic square wave of `x` with a duty cycle `θ`
and a peak amplitude ``1``.
"""
function squarewave(x::Real, θ::Real)
    0 ≤ θ ≤ 1 || throw(DomainError(θ, "squwarewave(x, θ) is only defined for 0 ≤ θ ≤ 1."))
    ifelse(mod2pi(x) < 2π * θ, 1.0, -1.0)
end

md"""
    squarewave1(x)
Compute ``1``-periodic square wave of `x` with a peak amplitude ``1``.
"""
squarewave1(x::Real) = ifelse(mod(x, 1) < 1/2, 1.0, -1.0)

md"""
    squarewave1(x, θ)
Compute ``1``-periodic square wave of `x` with a duty cycle `θ`
and a peak amplitude ``1``.
"""
function squarewave1(x::Real, θ::Real)
    0 ≤ θ ≤ 1 || throw(DomainError(θ, "squwarewave1(x, θ) is only defined for 0 ≤ θ ≤ 1."))
    ifelse(mod(x, 1) < θ, 1.0, -1.0)
end

md"""
    trianglewave(x)
Compute ``2\pi``-periodic triangle wave of `x` with a peak amplitude ``1``.
"""
function trianglewave(x::Real)
    modx = mod2pi(x + π/2)
    ifelse(modx < π, 2modx/π - 1, -2modx/π + 3)
end

md"""
    trianglewave1(x)
Compute ``1``-periodic triangle wave of `x` with a peak amplitude ``1``.
"""
function trianglewave1(x::Real)
    modx = mod(x + 1/4, 1.0)
    ifelse(modx < 1/2, 4modx - 1, -4modx + 3)
end

md"""
    sawtoothwave(x)
Compute ``2\pi``-periodic sawtooth wave of `x` with a peak amplitude ``1``.
"""
sawtoothwave(x::Real) = rem2pi(x, RoundNearest) / π

md"""
    sawtoothwave1(x)
Compute ``1``-periodic sawtooth wave of `x` with a peak amplitude ``1``.
"""
sawtoothwave1(x::Real) = rem(x, 1.0, RoundNearest) * 2

end

# ╔═╡ 29ef8525-5c0e-4dfd-8313-c3232ae362ab
squarewave(1)

# ╔═╡ a98ee93f-aac1-4b80-b337-f96f17744561
lineplot([squarewave], -π/2, 2π)

# ╔═╡ 466a36e8-c298-4ff9-ae0a-87c92905cf2a
lineplot([sawtoothwave], -π/2, 2π)

# ╔═╡ 3122c57a-f46f-49d7-9770-b51b1347b8f9
lineplot([trianglewave], -π/2, 2π)

# ╔═╡ 11b6cb26-da27-4222-94fe-40ef41f1c954
  audio_multi1_channel = SampleBuf(rand(100000,2), 10000)

# ╔═╡ acde3a7e-b314-4e3d-96d7-81b63821e060
typeof(SampleBuf(rand(100000,2), 10000))

# ╔═╡ 1dd3020a-9d13-4ab5-9967-14644d72f6df
audio_multi1_channel.data

# ╔═╡ 7f0b5cd9-24c7-4c40-a62c-0d2e909c1ae9
# Using Dan Casmiro WAV.jl
begin
fs = 8e3
t = 0.0:1/fs:prevfloat(1.0)
f = 1e3
y = sin.(2pi * f * t) * 0.1
wavwrite(y, "example.wav", Fs=fs)

y, fs = wavread("example.wav")
y = sin.(2pi * 2f * t) * 0.1
wavappend(y, "example.wav")

y, fs = wavread("example.wav")
wavplay(y, fs)
end

# ╔═╡ e7a0111f-1df0-4a36-8a24-d68c059b8cad
begin
nsamples = []
for i in 0:1/10000:10
	push!(nsamples, sinpi(f * i))
end
end

# ╔═╡ 3b19dcd5-37ef-4676-924b-e0b0778b1dd5
nsamples1 = hcat(nsamples, nsamples)

# ╔═╡ 4ff67c7e-73c8-4cfa-91a0-392ab3af2318
# using sampledsignals
audio_samples = SampleBuf(nsamples1, 10000)

# ╔═╡ 9b3bbcb7-544c-43b6-957a-495291525736
# using wavplay
wavplay(audio_samples.data,10000)

# ╔═╡ b92e9a63-f08e-47fe-baab-f5cc309bcdbc
# using SignalOperators.Units # allows the use of dB, Hz, s etc... as unitful values

# ╔═╡ b271de0b-3566-4712-bcdc-06a66f6f0b86
sound1

# ╔═╡ Cell order:
# ╠═8bc61658-b880-11eb-0b6c-a5b7ff4dcf3e
# ╠═821a6565-7046-46ff-b874-521fd846d703
# ╠═06853df6-44a0-42cb-8e5a-95bbc8962b81
# ╠═770f4a6b-2fe3-4493-a324-08a54131ea88
# ╠═fe3abf37-bcad-4717-9ebd-702838c5ccbf
# ╠═961a4dfa-0cbb-46aa-8af1-a056ade120ad
# ╠═dfe31fdd-6be0-41cf-8fae-77c0e8692627
# ╠═d1890135-cbcf-4b23-a32c-418be77debc0
# ╠═ea2c8ea0-7f9d-4687-b657-21e4d2a0c253
# ╠═764cb127-bcec-450f-8ed5-933e3bc1db6c
# ╠═29ef8525-5c0e-4dfd-8313-c3232ae362ab
# ╠═a98ee93f-aac1-4b80-b337-f96f17744561
# ╠═466a36e8-c298-4ff9-ae0a-87c92905cf2a
# ╠═3122c57a-f46f-49d7-9770-b51b1347b8f9
# ╠═e7a0111f-1df0-4a36-8a24-d68c059b8cad
# ╠═3b19dcd5-37ef-4676-924b-e0b0778b1dd5
# ╠═9b3bbcb7-544c-43b6-957a-495291525736
# ╠═4ff67c7e-73c8-4cfa-91a0-392ab3af2318
# ╠═11b6cb26-da27-4222-94fe-40ef41f1c954
# ╠═acde3a7e-b314-4e3d-96d7-81b63821e060
# ╠═1dd3020a-9d13-4ab5-9967-14644d72f6df
# ╠═7f0b5cd9-24c7-4c40-a62c-0d2e909c1ae9
# ╠═b92e9a63-f08e-47fe-baab-f5cc309bcdbc
# ╠═aa8e6100-5919-461a-8394-dea283f15b00
# ╠═bc1888ec-9c33-45e7-91cb-4a7024f1acd7
# ╠═b271de0b-3566-4712-bcdc-06a66f6f0b86


# ## Box plot
# ### Box plot (1)

#md # ![FILE_NAME.png](images/FILE_NAME.png)

using StatsPlots, Statistics, Random
Random.seed!(2018)

n = 30
science = rand(1:10, n)
@show median(science)
boxplot(science, label=permutedims(["science"]))


using FileIO;
using DelimitedFiles;
using Statistics: mean, std;
using Random: randperm;

# =============================================================================
# datasets.jl -> Funciones útiles aplicables a un dataset:
#   - oneHotEncoding
#   - normalizar:
#       -calculateMinMaxNormalizationParameters
#       -normalizeMinMax!
#       -calculateZeroMeanNormalizationParameters
#       -normalizeZeroMean!
#   - holdOut (sobrecargada 2 y 3 params)
#   - validacion cruzada(añadir en un futuro) -> FALTAS TU
#   - classifyOutputs
# =============================================================================

#
# Esta función sirve para normalizar las salidas deseadas del dataset para un
#   problema de clasificacion, tanto como si es binario o multiclase.
#
# @arguments
#   feature: Array con los outputs deseados
#
# @return: outputs en one-hot-encoding
#
function oneHotEncoding(feature::Array{Any,1}, classes::Array{Any,1})
    # Primero se comprueba que todos los elementos del vector esten en el vector de clases (linea adaptada del final de la practica 4)
    @assert(all([in(value, classes) for value in feature]));
    numClasses = length(classes);

    @assert(numClasses>1)
    if (numClasses==2)
        # Si solo hay dos clases, se devuelve una matriz con una columna
        oneHot = Array{Bool,2}(undef, size(feature,1), 1);
        oneHot[:,1] .= (feature.==classes[1]);
    else
        # Si hay mas de dos clases se devuelve una matriz con una columna por clase
        oneHot = Array{Bool,2}(undef, size(feature,1), numClasses);
        for numClass = 1:numClasses
            oneHot[:,numClass] .= (feature.==classes[numClass]);
        end;
    end;
    return oneHot;
end;
# Extraemos las clases distintas con unique
oneHotEncoding(feature::Array{Any,1}) = oneHotEncoding(feature::Array{Any,1}, unique(feature));
#  En este caso, el propio vector ya está codificado
oneHotEncoding(feature::Array{Bool,1}) = feature;

# =============================================================================
# Funciones útiles para la normalización de un dataset
# =============================================================================

#
# Función auxiliar para calcular el máximo y mínimo de cada atributo.
#
# @arguments
#   dataset: los inputs.
#   dataInRows: si esta traspuesta o no.
#
# @return: array con el máximo y mínimo de cada atributo.
#
calculateMinMaxNormalizationParameters(dataset::Array{Float64,2}; dataInRows=true) =
    ( minimum(dataset, dims=(dataInRows ? 1 : 2)), maximum(dataset, dims=(dataInRows ? 1 : 2)) );

#
# Función AUXILIAR para normalizar los inputs entre máximo y mínimo.
#
# @arguments
#   dataset: los inputs.
#   normalizationParameters: array con el máximo y mínimo de cada atributo.
#   dataInRows: si esta traspuesta o no.
#
function normalizeMinMax!(dataset::Array{Float64,2}, normalizationParameters::Tuple{Array{Float64,2},Array{Float64,2}}; dataInRows=true)
    min = normalizationParameters[1];
    max = normalizationParameters[2];
    dataset .-= min;
    dataset ./= (max .- min);
    # Si hay algun atributo en el que todos los valores son iguales, se pone a 0
    if (dataInRows)
        dataset[:, vec(min.==max)] .= 0;
    else
        dataset[vec(min.==max), :] .= 0;
    end;
end;

#
# Función que normaliza los inputs entre máximo y mínimo (es a la que llamamos).
#
# @arguments
#   dataset: los inputs.
#   dataInRows: si la matriz está traspuesta o no.
#
normalizeMinMax!(dataset::Array{Float64,2}; dataInRows=true) = normalizeMinMax!(dataset, calculateMinMaxNormalizationParameters(dataset; dataInRows=dataInRows); dataInRows=dataInRows);

#
# Función auxiliar para calcular la media y la desviacion típica de cada atributo.
#
# @arguments
#   dataset: los inputs.
#   dataInRows: si esta traspuesta o no.
#
# @return: array con la media y la desviacion típica de cada atributo.
#
calculateZeroMeanNormalizationParameters(dataset::Array{Float64,2}; dataInRows=true) =
( mean(dataset, dims=(dataInRows ? 1 : 2)), std(dataset, dims=(dataInRows ? 1 : 2)) );

#
# Función AUXILIAR para normalizar los inputs con media cero.
#
# @arguments
#   dataset: los inputs.
#   normalizationParameters: array con la media y la desviacion típica de cada atributo.
#   dataInRows: si esta traspuesta o no.
#
function normalizeZeroMean!(dataset::Array{Float64,2}, normalizationParameters::Tuple{Array{Float64,2},Array{Float64,2}}; dataInRows=true)
    avg  = normalizationParameters[1];
    stnd = normalizationParameters[2];
    dataset .-= avg;
    dataset ./= stnd;
    # Si hay algun atributo en el que todos los valores son iguales, se pone a 0
    if (dataInRows)
        dataset[:, vec(stnd.==0)] .= 0;
    else
        dataset[vec(stnd.==0), :] .= 0;
    end
end;

#
# Función que normaliza los inputs con media 0 (es a la que llamamos).
#
# @arguments
#   dataset: los inputs.
#   dataInRows: si la matriz está traspuesta o no.
#
normalizeZeroMean!(dataset::Array{Float64,2}; dataInRows=true) = normalizeZeroMean!(dataset, calculateZeroMeanNormalizationParameters(dataset; dataInRows=dataInRows); dataInRows=dataInRows);

# =============================================================================
# Obtener los conjuntos discuntos adecuados (holdOut)
# =============================================================================

#
# Función que divide el conjunto de entrenamiento en entrenamiento y test.
#
# @arguments
#   N: número de patrones.
#   P: porcentaje de división test/entrenamiento.
#
# @return nuevos conjuntos de entrenamiento y test
#
function holdOut(N::Int, P::Float64)
    @assert ((P>=0.) & (P<=1.));
    indices = randperm(N);
    numTrainingInstances = Int(round(N*(1-P)));
    return (indices[1:numTrainingInstances], indices[numTrainingInstances+1:end]);
end

#
# Función que divide el conjunto de entrenamiento en entrenamiento, test y validación.
#
# @arguments
#   N: número de patrones.
#   Ptest: porcentaje de división test/entrenamiento.
#   Pval: porcentaje de división validación/entrenamiento.
#
# @return nuevos conjuntos de entrenamiento, test y validacion.
#
function holdOut(N::Int, Ptest::Float64, Pval::Float64)
    @assert ((Pval>=0.) & (Pval<=1.));
    @assert ((Ptest>=0.) & (Ptest<=1.));
    @assert ((Pval+Ptest)<=1.);
    # Primero separamos en entrenamiento+validation y test
    (trainingValidationIndices, testIndices) = holdOut(N, Ptest);
    # Después separamos el conjunto de entrenamiento+validation
    (trainingIndices, validationIndices) = holdOut(length(trainingValidationIndices), Pval*N/length(trainingValidationIndices))
    return (trainingValidationIndices[trainingIndices], trainingValidationIndices[validationIndices], testIndices);
end;

# =============================================================================
# classifyOutputs -> Pasar de una salida en float64 a bool
# =============================================================================

function classifyOutputs(outputs::Array{Float64,2}; dataInRows::Bool=true, threshold::Float64=0.5)
    numOutputs = size(outputs, dataInRows ? 2 : 1);
    @assert(numOutputs!=2)
    if numOutputs==1
        return convert(Array{Bool,2}, outputs.>=threshold);
    else
        # Miramos donde esta el valor mayor de cada instancia con la funcion findmax
        (_,indicesMaxEachInstance) = findmax(outputs, dims= dataInRows ? 2 : 1);
        # Creamos la matriz de valores booleanos con valores inicialmente a false y asignamos esos indices a true
        outputsBoolean = Array{Bool,2}(falses(size(outputs)));
        outputsBoolean[indicesMaxEachInstance] .= true;
        # Comprobamos que efectivamente cada patron solo este clasificado en una clase
        @assert(all(sum(outputsBoolean, dims=dataInRows ? 2 : 1).==1));
        return outputsBoolean;
    end;
end;


using MultivariateStats
using LinearAlgebra
using Test
import Statistics: mean, cov
import Random

#Test equivalence of eigenvectors/singular vectors taking into account possible
#phase (sign) differences
#This may end up in Base.Test; see JuliaLang/julia#10651
function test_approx_eq_vecs(a::StridedVecOrMat{S},
    b::StridedVecOrMat{T}, error=nothing) where {S<:Real, T<:Real}
    n = size(a, 2)
    @test n==size(b, 2) && size(a, 1)==size(b, 1)
    error==nothing && (error=n^3*(eps(S)+eps(T)))
    for i=1:n
        ev1, ev2 = a[:, i], b[:, i]
        deviation = min(abs(norm(ev1-ev2)), abs(norm(ev1+ev2)))
        if !isnan(deviation)
            @test isapprox.(deviation, 0.0, atol=error)
        end
    end
end

Random.seed!(34568)

## prepare data
let
d = 5
ns = [10, 15, 20]
nc = length(ns)
n = sum(ns)
Xs = Matrix{Float64}[]
ys = Vector{Int}[]
Ss = Matrix{Float64}[]
cmeans = zeros(d, nc)

for k = 1:nc
    R = qr(randn(d, d)).Q
    nk = ns[k]

    Xk = R * Diagonal(2 * rand(d) .+ 0.5) * randn(d, nk) .+ randn(d)
    yk = fill(k, nk)
    uk = vec(mean(Xk, dims=2))
    Zk = Xk .- uk
    Sk = Zk * Zk'

    push!(Xs, Xk)
    push!(ys, yk)
    push!(Ss, Sk)
    cmeans[:,k] .= uk
end

X = hcat(Xs...)
y = vcat(ys...)
mv = vec(mean(X, dims=2))
Sw = zeros(d, d)
for k = 1:nc
    Sw += Ss[k]
end

Sb = zeros(d, d)
for k = 1:nc
    dv = cmeans[:,k] - mv
    Sb += ns[k] * (dv * dv')
end

Z = X .- mv
Sa = Z * Z'
@test Sw + Sb ≈ Sa

@assert size(X) == (d, n)
@assert size(y) == (n,)

## Stats

S = multiclass_lda_stats(nc, X, y)

@test S.dim == d
@test S.nclasses == nc

@test classweights(S) == ns
@test classmeans(S) ≈ cmeans
@test mean(S) ≈ mv

@test withclass_scatter(S) ≈ Sw
@test betweenclass_scatter(S) ≈ Sb

## Solve

emax = maximum(eigvals(Sw))
Sw_c = copy(Sw)
Sb_c = copy(Sb)

lambda = 1.0e-3
Sw_r = Sw + (lambda * emax) * Matrix(I, d, d)

P1 = mclda_solve(Sb, Sw, :gevd, nc-1, lambda)
@test P1' * Sw_r * P1 ≈ Matrix(I, nc-1, nc-1)
U = Sb * P1
V = Sw_r * P1
# test whether U is proportional to V,
# which indicates P is the generalized eigenvectors
@test U ≈ V*Diagonal(vec(mean(U./V, dims=1)))

P2 = mclda_solve(Sb, Sw, :whiten, nc-1, lambda)
@test P2' * Sw_r * P2 ≈ Matrix(I, nc-1, nc-1)

test_approx_eq_vecs(P1, P2)

## LDA

for T in (Float32, Float64)
    M = fit(MulticlassLDA, nc, convert(Matrix{T}, X), y; method=:gevd, regcoef=convert(T, lambda))
    @test indim(M) == d
    @test outdim(M) == nc - 1
    @test projection(M) ≈ P1
    @test M.pmeans ≈ M.proj'cmeans
    @test transform(M, X) ≈ M.proj'X

    M = fit(MulticlassLDA, nc, convert(Matrix{T}, X), y; method=:whiten, regcoef=convert(T, lambda))
    @test indim(M) == d
    @test outdim(M) == nc - 1
    # @test projection(M) P2  # signs may change
    @test M.pmeans ≈ M.proj'cmeans
    @test transform(M, X) ≈ M.proj'X
end

## High-dimensional LDA (subspace LDA)

# Low-dimensional case (no singularities)

centers = [zeros(5) [10.0;zeros(4)] [0.0;10.0;zeros(3)]]

# Case 1: 3 groups of 500
dX = randn(5,1500);
for i = 0:500:1000
    dX[:,(1:500).+i] .= dX[:,(1:500).+i] .- mean(dX[:,(1:500).+i], dims=2)  # make the mean of each 0
end
dX1 = dX
X1 = [dX[:,1:500].+centers[:,1] dX[:,501:1000].+centers[:,2] dX[:,1001:1500].+centers[:,3]]
label1 = [fill(1,500); fill(2,500); fill(3,500)]
# Case 2: 3 groups, one with 1000, one with 100, and one with 10
dX = randn(5,1110);
dX[:,   1:1000] .= dX[:,   1:1000] .- mean(dX[:,   1:1000], dims=2)
dX[:,1001:1100] .= dX[:,1001:1100] .- mean(dX[:,1001:1100], dims=2)
dX[:,1101:1110] .= dX[:,1101:1110] .- mean(dX[:,1101:1110], dims=2)
dX2 = dX
X2 = [dX[:,1:1000].+centers[:,1] dX[:,1001:1100].+centers[:,2] dX[:,1101:1110].+centers[:,3]]
label2 = [fill(1,1000); fill(2,100); fill(3,10)]

for (X, dX, label) in ((X1, dX1, label1), (X2, dX2, label2))
    w = [length(findall(label.==1)), length(findall(label.==2)), length(findall(label.==3))]
    M = fit(SubspaceLDA, X, label)
    @test indim(M) == 5
    @test outdim(M) == 2
    totcenter = vec(sum(centers.*w', dims=2)./sum(w))
    @test mean(M) ≈ totcenter
    @test classmeans(M) ≈ centers
    @test classweights(M) == w
    x = rand(5)
    @test transform(M, x) ≈ projection(M)'*x
    dcenters = centers .- totcenter
    Hb = dcenters.*sqrt.(w)'
    Sb = Hb*Hb'
    Sw = dX*dX'
    # When there are no singularities, we need to solve the "regular" LDA eigenvalue equation
    proj = projection(M)
    @test Sb*proj ≈ Sw*proj*Diagonal(M.λ)
    @test proj'*Sw*proj ≈ Matrix(I,2,2)

    # also check that this is consistent with the conventional algorithm
    Mld = fit(MulticlassLDA, 3, X, label)
    for i = 1:2
        @test abs(dot(normalize(proj[:,i]), normalize(Mld.proj[:,i]))) ≈ 1
    end
end

# High-dimensional case (undersampled => singularities)
X = randn(10^6, 9)
label = rand(1:3, 9); label[1:3] = 1:3
M = fit(SubspaceLDA, X, label)
centers = M.cmeans
for i = 1:3
    flag = label.==i
    @test centers[:,i] ≈ mean(X[:,flag], dims=2)
    @test M.cweights[i] == sum(flag)
end
dcenters = centers .- mean(X, dims=2)
Hb = dcenters*Diagonal(sqrt.(M.cweights))
Hw = X - centers[:,label]
@test (M.projw'*Hb)*(Hb'*M.projw)*M.projLDA ≈ (M.projw'*Hw)*(Hw'*M.projw)*M.projLDA*Diagonal(M.λ)
Gw = projection(M)'*Hw
@test Gw*Gw' ≈ Matrix(I,2,2)

# Test that nothing breaks if one class has no members
label[1:4] .= 1
label[5:9] .= 3  # no data points have label == 2
M = fit(SubspaceLDA, X, label)
centers = M.cmeans
dcenters = centers .- mean(X, dims=2)
Hb = dcenters*Diagonal(sqrt.(M.cweights))
Hw = X - centers[:,label]
@test (M.projw'*Hb)*(Hb'*M.projw)*M.projLDA ≈ (M.projw'*Hw)*(Hw'*M.projw)*M.projLDA*Diagonal(M.λ)

# Test normalized LDA
function gen_ldadata_2(centers, n1, n2)
    d = size(centers, 1)
    X = randn(d, n1+n2)
    X[:,1:n1]       .-= vec(mean(X[:,1:n1], dims=2))
    X[:,n1+1:n1+n2] .-= vec(mean(X[:,n1+1:n1+n2], dims=2))
    dX = copy(X)
    X[:,1:n1]       .+= centers[:,1]
    X[:,n1+1:n1+n2] .+= centers[:,2]
    label = [fill(1,n1); fill(2,n2)]
    X, dX, label
end
centers = zeros(2,2); centers[1,2] = 10
n1 = 100
for n2 in (100, 1000, 10000)
    X, dX, label = gen_ldadata_2(centers, n1, n2)
    M = fit(SubspaceLDA, X, label; normalize=true)
    proj = projection(M)
    Hb = centers .- mean(centers, dims=2)   # not weighted by number in each cluster
    Sb = Hb*Hb'
    Sw = dX*dX'/(n1+n2)
    @test Sb*proj ≈ Sw*proj*Diagonal(M.λ)
end
end


# ---
# title: 862. Shortest Subarray with Sum at Least K
# id: problem862
# author: Indigo
# date: 2021-02-17
# difficulty: Hard
# categories: Binary Search, Queue
# link: <https://leetcode.com/problems/shortest-subarray-with-sum-at-least-k/description/>
# hidden: true
# ---
#
# Return the **length** of the shortest, non-empty, contiguous subarray of `A`
# with sum at least `K`.
#
# If there is no non-empty subarray with sum at least `K`, return `-1`.
#
#
#
# **Example 1:**
#
#
#
#     Input: A = [1], K = 1
#     Output: 1
#
#
# **Example 2:**
#
#
#
#     Input: A = [1,2], K = 4
#     Output: -1
#
#
# **Example 3:**
#
#
#
#     Input: A = [2,-1,2], K = 3
#     Output: 3
#
#
#
#
# **Note:**
#
#   1. `1 <= A.length <= 50000`
#   2. `-10 ^ 5 <= A[i] <= 10 ^ 5`
#   3. `1 <= K <= 10 ^ 9`
#
#
## @lc code=start
using LeetCode

function shortest_subarray(A::Vector{Int}, K::Int)
    dq = Deque{Int}()
    prex = fill(0, length(A) + 1)
    cumsum!(@view(prex[2:end]), A)
    res = typemax(Int)
    for i in 1:length(prex)
        while !isempty(dq) && prex[i] - prex[first(dq)] ≥ K
            res = min(res, i - first(dq))
            popfirst!(dq)
        end
        while !isempty(dq) && prex[i] ≤ prex[last(dq)]
            pop!(dq)
        end
        push!(dq, i)
    end
    res == typemax(Int) ? -1 : res
end
## @lc code=end


@testset "minimal.absRevBin_test" begin

    @test abs_rev(emptyinterval(), entireinterval())[2] === emptyinterval()

    @test abs_rev(interval(0.0, 1.0), emptyinterval())[2] === emptyinterval()

    @test abs_rev(interval(0.0, 1.0), interval(7.0, 9.0))[2] === emptyinterval()

    @test abs_rev(emptyinterval(), interval(0.0, 1.0))[2] === emptyinterval()

    @test abs_rev(interval(-2.0, -1.0), entireinterval())[2] === emptyinterval()

    @test abs_rev(interval(1.0, 1.0), entireinterval())[2] === Interval(-1.0, 1.0)

    @test abs_rev(interval(0.0, 0.0), entireinterval())[2] === Interval(0.0, 0.0)

    @test abs_rev(interval(-1.0, -1.0), entireinterval())[2] === emptyinterval()

    @test abs_rev(interval(0x1.FFFFFFFFFFFFFp1023, 0x1.FFFFFFFFFFFFFp1023), entireinterval())[2] === Interval(-0x1.FFFFFFFFFFFFFp1023, 0x1.FFFFFFFFFFFFFp1023)

    @test abs_rev(interval(0x1p-1022, 0x1p-1022), entireinterval())[2] === Interval(-0x1p-1022, 0x1p-1022)

    @test abs_rev(interval(-0x1p-1022, -0x1p-1022), entireinterval())[2] === emptyinterval()

    @test abs_rev(interval(-0x1.FFFFFFFFFFFFFp1023, -0x1.FFFFFFFFFFFFFp1023), entireinterval())[2] === emptyinterval()

    @test abs_rev(interval(1.0, 2.0), entireinterval())[2] === Interval(-2.0, 2.0)

    @test abs_rev(interval(1.0, 2.0), interval(0.0, 2.0))[2] === Interval(1.0, 2.0)

    @test abs_rev(interval(0.0, 1.0), interval(-0.5, 2.0))[2] === Interval(-0.5, 1.0)

    @test abs_rev(interval(-1.0, 1.0), entireinterval())[2] === Interval(-1.0, 1.0)

    @test abs_rev(interval(-1.0, 0.0), entireinterval())[2] === Interval(0.0, 0.0)

    @test abs_rev(interval(0.0, Inf), entireinterval())[2] === entireinterval()

    @test abs_rev(entireinterval(), entireinterval())[2] === entireinterval()

    @test abs_rev(interval(-Inf, 0.0), entireinterval())[2] === Interval(0.0, 0.0)

    @test abs_rev(interval(1.0, Inf), interval(-Inf, 0.0))[2] === Interval(-Inf, -1.0)

    @test abs_rev(interval(-1.0, Inf), entireinterval())[2] === entireinterval()

    @test abs_rev(interval(-Inf, -1.0), entireinterval())[2] === emptyinterval()

    @test abs_rev(interval(-Inf, 1.0), entireinterval())[2] === Interval(-1.0, 1.0)

end


using PyCall
using Plots; pyplot()
using Statistics
using LinearAlgebra

vtk = pyimport("vtk")
np = pyimport("numpy")

reader = vtk.vtkUnstructuredGridReader()
reader.SetFileName("../data/h06.vtk")
reader.Update()
output = reader.GetOutput()
points = output.GetPoints()
r = [collect(points.GetPoint(i-1)) for i in 1:points.GetNumberOfPoints()]
length(r)

z = [r[i][3] for i=1:262144]

println(sum( abs.(z.-mean(z)))/length(z))
println(std(z))

z = reshape(z,512,512)
heatmap(z)

include("dfa.jl")
dfa2d(z)

function fbmArea(z)
    nx, ny = size(z)
    area = 0.0
    for i=1:nx-1
        for j=1:ny-1
            v1 = [0; 1; z[i,j+1] - z[i,j]]
            v2 = [1; 0; z[i+1,j] - z[i,j]]
            v3 = [0; -1; z[i+1,j] - z[i+1,j+1]]
            v4 = [-1; 0; z[i,j+1] - z[i+1,j+1]]
            area += (norm(v2 × v1)+norm(v4 × v3))/2
        end
    end

    Lp  = 0.0
    for i=1:nx-1 # eval Lp for each line

        for j=1:ny-1
            v1 = [1; 0; z[i+1,j] - z[i,j]]
            Lp += norm(v1)
        end

    end

    return area/(nx*ny), (Lp/ny)/nx
end

fbmArea(z)

2^9


#=
Cyclical Coordinate Descent for lasso with binary features
=#

function soft_thresh(
        z::Real,
        λ::Real
    )::Float64
    if abs(z) ≤ λ
        return 0
    elseif z > 0
        return z - λ
    else
        return z + λ
    end
end

function coordinate_descent(
        X::Bases,
        Y::Vector{Float64};
        λ::Real = 1,
        β::Dict{BasisIndex, Float64} = Dict{BasisIndex, Float64}(INTERCEPT => 0.0),
        tol::Number = 1e-4,
    )::Tuple{Dict{BasisIndex, Float64}, Vector{Float64}, Vector{Float64}}

    #=
    A fast coordinate descent algorithm for lasso with binary features. Can be warm-started by passing β.
    =#

    n = length(Y)

    residual = Y - X*β # can pass from prev iter
    loss(residual, β) = (1/n) * ( (1/2)sum(residual.^2) + λ*sum(abs.(values(β))) )

    loss_0 = loss(residual, β)
    cycle_loss = [loss_0]

    loop_bases = keys(X)

    @inbounds while true
        if length(cycle_loss) > 10000
            print(cycle_loss[(end-10):end])
            break
        end

        # update cycle
        for b in loop_bases
            βb = get(β, b, 0.0)
            sb = X.sum[b]
            ρ = residual * X[b]

            β̃b = soft_thresh(βb*sb + ρ, λ) / sb
            Δβb = β̃b - βb

            if Δβb ≠ 0 # tried with deleting β[b] if βb=0, faster as-is
                β[b] = β̃b
                for i in X[b].nzind
                    residual[i] -= Δβb
                end
            end
        end

        # check convergence and set loop over active set
        push!(cycle_loss, loss(residual, β))
        if ( abs(cycle_loss[end] - cycle_loss[end-1]) < tol ) # check loss convergence
            if loop_bases == keys(X)
                break
            end
            loop_bases = keys(X)
        else
            loop_bases = keys(β)
        end

    end

    β = Dict(
        b=>βb for (b,βb) in β
        if (βb ≠ 0.0) | ( b == INTERCEPT )
    )
    return β, residual, cycle_loss
end


using ScalarWaveEquation
using Test

function check_similar1D(ufun1::FunArr{T, U}, ufun2::FunArr{T, U}, tol::T) where {T, U}
	# checks if two FunArr instances are equal to within a tolerance
	N  = length(ufun1.ydat)
	N2 = length(ufun2.ydat)
	@assert N == N2

	dx = ufun1.dx
	total = 0.0
	for ix in 1:N
		diff = ufun1.ydat[ix] - ufun2.ydat[ix]
		total += dx*diff*diff
	end

	return total < tol
end

@testset "FunArr" begin
	# FunArr cannot hold an Array smaller than 3
	@test_throws ErrorException ufun = FunArr{Float64, Float64}([1 2])

	# Neither of FunArr2D's dimensions can be smaller than 3
	A = [1.0 2.0 3.0; 4.0 5.0 6.0]
	B = [1.0 2.0; 3.0 4.0; 5.0 6.0]
	C = ones(3, 3)
	@test_throws ErrorException u2fun = FunArr2D{Float64, Float64, Float64}(A, 1.0)
	@test_throws ErrorException u2fun = FunArr2D{Float64, Float64, Float64}(B, 1.0)

	# the duration of elapsed time must be positive
	@test_throws ErrorException u2fun = FunArr2D{Float64, Float64, Float64}(C, 0.0)
end

# test for 1st deriv and 2nd deriv
@testset "Derivatives" begin
	import ScalarWaveEquation
	first_deriv  = ScalarWaveEquation.first_deriv
	second_deriv  = ScalarWaveEquation.second_deriv

	size  = 100
	xdata = range(0.0, stop = 1.0, length = size)

	# create the constant array of value 1.0
	ufun1 = FunArr{Float64, Float64}(ones(Float64, size))

	# create the array of y = x, then take the 1st derivative
	ufunx = FunArr{Float64, Float64}(xdata)
	ufun2 = first_deriv(ufunx)

	# create the array of y = 0.5*x^x, then take the 2nd derivative
	x2data = 0.5 * xdata .* xdata
	ufun2x = FunArr{Float64, Float64}(x2data)
	ufun3  = second_deriv(ufun2x)

	tolerance = 1e-12
	@test check_similar1D(ufun1, ufun2, tolerance)
	@test check_similar1D(ufun1, ufun3, tolerance)
end

@testset "Forward Integration" begin
	import ScalarWaveEquation
	forward_euler2_disc = ScalarWaveEquation.forward_euler2_disc

	# test forward integration using euler2
	# y'' = 1, y'(0) = 0, y(0) = 0, should get y = 0.5*x^2

	size = 100
	afun = FunArr{Float64, Float64}(ones(Float64, size))
	vfun = FunArr{Float64, Float64}(zeros(Float64, size))
	yfun = FunArr{Float64, Float64}(zeros(Float64, size))

	dx = yfun.dx
	for it in 2:size
		y1, v1 = forward_euler2_disc(it-1, yfun, vfun, afun, dx)
		yfun.ydat[it] = y1
		vfun.ydat[it] = v1
	end

	# compare yfun and vfun to their exact versions
	yexact = [0.5*x^2 for x in range(0.0, stop = 1.0, length = size)]
	yfun_exact = FunArr{Float64, Float64}(yexact)
	vexact = range(0.0, stop = 1.0, length = size)
	vfun_exact = FunArr{Float64, Float64}(vexact)

	tolerance = 1e-12
	@test check_similar1D(yfun, yfun_exact, tolerance)
	@test check_similar1D(vfun, vfun_exact, tolerance)
end


# ---
# title: 1342. Number of Steps to Reduce a Number to Zero
# id: problem1342
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: Bit Manipulation
# link: <https://leetcode.com/problems/number-of-steps-to-reduce-a-number-to-zero/description/>
# hidden: true
# ---
#
# Given a non-negative integer `num`, return the number of steps to reduce it to
# zero. If the current number is even, you have to divide it by 2, otherwise,
# you have to subtract 1 from it.
#
#
#
# **Example 1:**
#
#
#
#     Input: num = 14
#     Output: 6
#     Explanation:
#     Step 1) 14 is even; divide by 2 and obtain 7.
#     Step 2) 7 is odd; subtract 1 and obtain 6.
#     Step 3) 6 is even; divide by 2 and obtain 3.
#     Step 4) 3 is odd; subtract 1 and obtain 2.
#     Step 5) 2 is even; divide by 2 and obtain 1.
#     Step 6) 1 is odd; subtract 1 and obtain 0.
#
#
# **Example 2:**
#
#
#
#     Input: num = 8
#     Output: 4
#     Explanation:
#     Step 1) 8 is even; divide by 2 and obtain 4.
#     Step 2) 4 is even; divide by 2 and obtain 2.
#     Step 3) 2 is even; divide by 2 and obtain 1.
#     Step 4) 1 is odd; subtract 1 and obtain 0.
#
#
# **Example 3:**
#
#
#
#     Input: num = 123
#     Output: 12
#
#
#
#
# **Constraints:**
#
#   * `0 <= num <= 10^6`
#
#
## @lc code=start
using LeetCode

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


"""

    moment_constraint(order::Integer, E::Real)

Compute the moment constraint for a given order.

# Notes
Sardeshmukh et al. (2015) states that a necessary condition for the moment
``\\langle x^{n} \\rangle`` to exist (where `n` is the moment order) is that
``E^{2} < \\frac{2}{n-1}``.

"""
function moment_constraint(order::Integer, E::Real)
    return E^2 < 2 / (order - 1)
end

"""

    small_E_constraint(x::Real, skew::Real)

Compute the small E constraint for the first-order approximation to the SGS
pdf in the limit as E approaches zero.

# Examples
```jldoctest
julia> small_E_constraint(0.0, 1.0)
true

```

# Notes
Sardeshmukh et al. (2015) states that in order for the first-order approximation
of the SGS pdf to be valid in the limit ``E \\to 0``, ``|Sx| \\le 2`` where `x` is
the standardized anomaly.

"""
function small_E_constraint(x::Real, skew::Real)
    return abs(skew * x) ≤ 2
end

"""

    epsilon_condition(skew::Real)

Solve for the epsilon condition used in the epsilon constraint.

# Examples
```jldoctest
julia> epsilon_condition(1.0)
1.118033988749895

```

"""
function epsilon_condition(skew::Real)
    return sqrt(1.0 + (skew^2 / 4.0))
end

"""

    epsilon_condition(skew::AbstractArray)

Solve for the epsilon condition used in the epsilon constraint.

# Examples
```jldoctest
julia> epsilon_condition([1, 3, 4])
3-element Array{Float64,1}:
 1.118033988749895
 1.8027756377319946
 2.23606797749979

```

"""
function epsilon_condition(skew::AbstractArray)
    return sqrt.(1.0 .+ (skew .^2 ./ 4.0))
end

"""

    epsilon_constraint(skew::Real, kurt::Real)

Test the epsilon constraint used in the method of moments calculation for
parameter E in Sardeshmukh et al. (2015).

# Examples
```jldoctest
julia> epsilon_constraint(1, 0.5)
true

```

"""
function epsilon_constraint(skew::Real, E::Real)
    return E^2 > (epsilon_condition(skew) - 1.0) / epsilon_condition(skew)
end

"""

    epsilon_constraint(skew::Real, kurt::Real)

Test the epsilon constraint used in the method of moments calculation for
parameter E in Sardeshmukh et al. (2015).

# Examples
```jldoctest
julia> epsilon_constraint([1, 2, 3], [0.5, 1, 1.5])
3-element BitArray{1}:
 1
 1
 1

```

"""
function epsilon_constraint(skew::AbstractArray, E::AbstractArray)
    return E .^2 .> (epsilon_condition(skew) .- 1.0) ./ epsilon_condition(skew)
end

"""

    ks_constraint(skew::Real, kurt::Real)

Test the skewness-kurtosis inequality.

# Examples
```jldoctest
julia> ks_constraint(1, 1)
false

```

"""
function ks_constraint(skew::Real, kurt::Real)
    return kurt > 1.5 * (skew^2)
end

"""

    ks_constraint(skew::Real, kurt::Real)

Test the skewness-kurtosis inequality.

# Examples
```jldoctest
julia> ks_constraint([0.5, 1, 1.5], [3, 3, 3])
3-element BitArray{1}:
 1
 1
 0

```

"""
function ks_constraint(skew::AbstractArray, kurt::AbstractArray)
    return kurt .> 1.5 .* (skew .^2)
end


# wrapper functions for convienence
nnls(A,b) = nonneg_lsq(A,b;alg=:fnnls)
cdsc(A,b) = nonneg_lsq(A,b;alg=:cd,max_iter = 2000)

#This test fails for this method...
# Solve A*x = b for x, subject to x >=0
# A = [ 0.53879488  0.65816267
#       0.12873446  0.98669198
#       0.24555042  0.00598804
#       0.80491791  0.32793762 ]

# b = [0.888,  0.562,  0.255,  0.077]

# # Test that nnls produces the same solution as scipy
# x = [0.15512102, 0.69328985] # approx solution from scipy
# @test norm(cdsc(A,b)-x) < 1e-5

## A second test case
A2 = [ -0.24  -0.82   1.35   0.36   0.35
       -0.53  -0.20  -0.76   0.98  -0.54
        0.22   1.25  -1.60  -1.37  -1.94
       -0.51  -0.56  -0.08   0.96   0.46
        0.48  -2.25   0.38   0.06  -1.29 ]
b2 = [-1.6,  0.19,  0.17,  0.31, -1.27]
x2 = [2.2010416, 1.19009924, 0.0, 1.55001345, 0.0]
@test norm(cdsc(A2,b2)-x2) < 1e-5

#X - Test a bunch of random cases against python
# Methods internal to NNLS.jl have already been validated

# Test a bunch of random cases against nnls
for i = 1:10
  m,n = rand(1:10),rand(1:10)
  A4 = randn(m,n)
  b4 = randn(m)
  x4 = nnls(A4,b4)
  @test norm(cdsc(A4,b4)-x4) < 1e-5
end