module RulesSubtractionMarginalsTest

using Test
using ReactiveMP
using Random
import ReactiveMP: @test_marginalrules

@testset "marginalrules:typeof(-)" begin

    @testset ":in1_in2 (m_out::UnivariateNormalDistributionsFamily, m_in1::UnivariateNormalDistributionsFamily, m_in2::PointMass)" begin

        @test_marginalrules [ with_float_conversions = true ] typeof(-)(:in1_in2) [
            (
                input = (m_out = NormalMeanVariance(3.0, 4.0), m_in1 = NormalMeanVariance(2.0, 2.0), m_in2 = PointMass(2.0)),
                output = (in1 = NormalWeightedMeanPrecision(9/4, 3/4), in2 = PointMass(2.0))
            ),
            (
                input = (m_out = NormalMeanPrecision(3, 4), m_in1 = NormalMeanPrecision(1, 4), m_in2 = PointMass(1.0)),
                output = (in1 = NormalWeightedMeanPrecision(20.0, 8.0), in2 = PointMass(1.0))
            ),
            (
                input = (m_out = NormalWeightedMeanPrecision(3.0, 4.0), m_in1 = NormalWeightedMeanPrecision(1.0, 4.0), m_in2 = PointMass(1.0)),
                output = (in1 = NormalWeightedMeanPrecision(8.0, 8.0), in2 = PointMass(1.0))
            ),
            (
                input = (m_out = NormalMeanPrecision(2.0, 4.0), m_in1 = NormalMeanVariance(1.0, 2.0), m_in2 = PointMass(1.0)),
                output = (in1 = NormalWeightedMeanPrecision(25/2, 9/2), in2 = PointMass(1.0))
            ),
            (
                input = (m_out = NormalMeanVariance(2.0, 4.0), m_in1 = NormalMeanPrecision(1.0, 2.0), m_in2 = PointMass(1.0)),
                output = (in1 = NormalWeightedMeanPrecision(11/4, 9/4), in2 = PointMass(1.0))
            ),
            (
                input = (m_out = NormalMeanPrecision(3.0, 4.0), m_in1 = NormalWeightedMeanPrecision(1.0, 2.0), m_in2 = PointMass(2.0)),
                output = (in1 = NormalWeightedMeanPrecision(21.0, 6.0), in2 = PointMass(2.0))
            ),
            (
                input = (m_out = NormalWeightedMeanPrecision(2.0, 2.0), m_in1 = NormalMeanPrecision(1.0, 2.0), m_in2 = PointMass(-1.0)),
                output = (in1 = NormalWeightedMeanPrecision(2.0, 4.0), in2 = PointMass(-1.0))
            ),
            (
                input = (m_out = NormalMeanVariance(3.0, 3.0), m_in1 = NormalWeightedMeanPrecision(2.0, 1.0), m_in2 = PointMass(2.0)),
                output = (in1 = NormalWeightedMeanPrecision(11/3, 4/3), in2 = PointMass(2.0))
            ),
            (
                input = (m_out = NormalWeightedMeanPrecision(2.0, 4.0), m_in1 = NormalMeanVariance(2.0, 2.0), m_in2 = PointMass(1.0)),
                output = (in1 = NormalWeightedMeanPrecision(7.0, 9/2), in2 = PointMass(1.0))
            ),

        ]

    end

    @testset ":in1_in2 (m_out::UnivariateNormalDistributionsFamily, m_in1::PointMass, m_in2::UnivariateNormalDistributionsFamily)" begin

        @test_marginalrules [ with_float_conversions = true ] typeof(-)(:in1_in2) [
            (
                input = (m_out = NormalMeanVariance(3.0, 4.0), m_in1 = PointMass(2.0), m_in2 = NormalMeanVariance(2.0, 2.0)),
                output = (in1 = PointMass(2.0), in2 = NormalWeightedMeanPrecision(5/4, 3/4))
            ),
            (
                input = (m_out = NormalMeanPrecision(3, 4), m_in1 = PointMass(1.0), m_in2 = NormalMeanPrecision(1, 4)),
                output = (in1 = PointMass(1.0), in2 = NormalWeightedMeanPrecision(12.0, 8.0))
            ),
            (
                input = (m_out = NormalWeightedMeanPrecision(3.0, 4.0), m_in1 = PointMass(1.0), m_in2 = NormalWeightedMeanPrecision(1.0, 4.0)),
                output = (in1 = PointMass(1.0), in2 = NormalWeightedMeanPrecision(0.0, 8.0))
            ),
            (
                input = (m_out = NormalMeanPrecision(2.0, 4.0), m_in1 = PointMass(1.0), m_in2 = NormalMeanVariance(1.0, 2.0)),
                output = (in1 = PointMass(1.0), in2 = NormalWeightedMeanPrecision(9/2, 9/2))
            ),
            (
                input = (m_out = NormalMeanVariance(2.0, 4.0), m_in1 = PointMass(1.0), m_in2 = NormalMeanPrecision(1.0, 2.0)),
                output = (in1 = PointMass(1.0), in2 = NormalWeightedMeanPrecision(9/4, 9/4))
            ),
            (
                input = (m_out = NormalMeanPrecision(3.0, 4.0), m_in1 = PointMass(2.0), m_in2 = NormalWeightedMeanPrecision(1.0, 2.0)),
                output = (in1 = PointMass(2.0), in2 = NormalWeightedMeanPrecision(5.0, 6.0))
            ),
            (
                input = (m_out = NormalWeightedMeanPrecision(2.0, 2.0), m_in1 = PointMass(-1.0), m_in2 = NormalMeanPrecision(1.0, 2.0)),
                output = (in1 = PointMass(-1.0), in2 = NormalWeightedMeanPrecision(6.0, 4.0))
            ),
            (
                input = (m_out = NormalMeanVariance(3.0, 3.0), m_in1 = PointMass(2.0), m_in2 = NormalWeightedMeanPrecision(2.0, 1.0)),
                output = (in1 = PointMass(2.0), in2 = NormalWeightedMeanPrecision(7/3, 4/3))
            ),
            (
                input = (m_out = NormalWeightedMeanPrecision(2.0, 4.0), m_in1 = PointMass(1.0), m_in2 = NormalMeanVariance(2.0, 2.0)),
                output = (in1 = PointMass(1.0), in2 = NormalWeightedMeanPrecision(-1.0, 9/2))
            ),

        ]

    end

    @testset ":in1_in2 (m_out::MvNormalMeanCovariance, m_in1::MultivariateNormalDistributionsFamily, m_in2::PointMass)" begin

        @test_marginalrules [ with_float_conversions = true ] typeof(-)(:in1_in2) [
            (
                input = (m_out = MvNormalMeanCovariance([2.0, 2.0], [2.0 0.0; 0.0 2.0]), m_in1 = MvNormalMeanCovariance([1.0, 3.0], [3.0 2.0; 2.0 4.0]), m_in2 = PointMass([1.0, 1.0])),
                output = (in1 = MvNormalWeightedMeanPrecision([5/4, 19/8], [1.0 -1/4; -1/4 7/8]), in2 = PointMass([1.0, 1.0]))
            ),
            (
                input = (m_out = MvNormalMeanCovariance([3.0, 2.0], [2.0 0.0; 0.0 2.0]), m_in1 = MvNormalMeanPrecision([1.0, 1.0], [2.0 1.0; 1.0 2.0]), m_in2 = PointMass([1.0, 1.0])),
                output = (in1 = MvNormalWeightedMeanPrecision([5.0, 9/2], [5/2 1.0; 1.0 5/2]), in2 = PointMass([1.0, 1.0]))
            ),
            (
                input = (m_out = MvNormalMeanCovariance([3.0, 2.0], [4.0 2.0; 2.0 4.0]), m_in1 = MvNormalWeightedMeanPrecision([1.0, 2.0], [2.0 0.0; 0.0 2.0]), m_in2 = PointMass([2.0, 1.0])),
                output = (in1 = MvNormalWeightedMeanPrecision([13/6, 13/6], [7/3 -1/6; -1/6 7/3]), in2 = PointMass([2.0, 1.0]))
            ),

        ]

    end

    @testset ":in1_in2 (m_out::MvNormalMeanPrecision, m_in1::MultivariateNormalDistributionsFamily, m_in2::PointMass)" begin

        @test_marginalrules [ with_float_conversions = true ] typeof(-)(:in1_in2) [
            (
                input = (m_out = MvNormalMeanPrecision([2.0, 2.0], [1.0 0.0; 0.0 1.0]), m_in1 = MvNormalMeanCovariance([2.0, 3.0], [3.0 2.0; 2.0 4.0]), m_in2 = PointMass([1.0, -1.0])),
                output = (in1 = MvNormalWeightedMeanPrecision([13/4, 13/8], [3/2 -1/4; -1/4 11/8]), in2 = PointMass([1.0, -1.0]))
            ),
            (
                input = (m_out = MvNormalMeanPrecision([1.0, 1.0], [3.0 1.0; 1.0 3.0]), m_in1 = MvNormalMeanPrecision([1.0, 1.0], [2.0 1.0; 1.0 2.0]), m_in2 = PointMass([2.0, 2.0])),
                output = (in1 = MvNormalWeightedMeanPrecision([15.0, 15.0], [5.0 2.0; 2.0 5.0]), in2 = PointMass([2.0, 2.0]))
            ),
            (
                input = (m_out = MvNormalMeanPrecision([3.0, 2.0], [4.0 2.0; 2.0 4.0]), m_in1 = MvNormalWeightedMeanPrecision([1.0, 2.0], [3.0 1.0; 1.0 1.0]), m_in2 = PointMass([-2.0, 1.0])),
                output = (in1 = MvNormalWeightedMeanPrecision([11.0, 16.0], [7.0  3.0; 3.0 5.0]), in2 = PointMass([-2.0, 1.0]))
            ),

        ]

    end

    @testset ":in1_in2 (m_out::MvNormalWeightedMeanPrecision, m_in1::MultivariateNormalDistributionsFamily, m_in2::PointMass)" begin

        @test_marginalrules [ with_float_conversions = true ] typeof(-)(:in1_in2) [
            (
                input = (m_out = MvNormalWeightedMeanPrecision([2.0, 2.0], [1.0 0.0; 0.0 1.0]), m_in1 = MvNormalMeanCovariance([2.0, 3.0], [3.0 2.0; 2.0 4.0]), m_in2 = PointMass([1.0, -1.0])),
                output = (in1 = MvNormalWeightedMeanPrecision([13/4, 13/8], [3/2 -1/4; -1/4 11/8]), in2 = PointMass([1.0, -1.0]))
            ),
            (
                input = (m_out = MvNormalWeightedMeanPrecision([1.0, 1.0], [3.0 1.0; 1.0 2.0]), m_in1 = MvNormalMeanPrecision([1.0, 1.0], [2.0 1.0; 1.0 2.0]), m_in2 = PointMass([2.0, 2.0])),
                output = (in1 = MvNormalWeightedMeanPrecision([12.0, 10.0], [5.0 2.0; 2.0 4.0]), in2 = PointMass([2.0, 2.0]))
            ),
            (
                input = (m_out = MvNormalWeightedMeanPrecision([1.0, 2.0], [4.0 1.0; 1.0 4.0]), m_in1 = MvNormalWeightedMeanPrecision([1.0, 1.0], [3.0 0.0; 0.0 2.0]), m_in2 = PointMass([-1.0, 1.0])),
                output = (in1 = MvNormalWeightedMeanPrecision([-1.0, 6.0], [7.0  1.0; 1.0 6.0]), in2 = PointMass([-1.0, 1.0]))
            ),

        ]

    end

    @testset ":in1_in2 (m_out::MvNormalMeanCovariance, m_in1::PointMass, m_in2::PointMassMultivariateNormalDistributionsFamily)" begin

        @test_marginalrules [ with_float_conversions = true ] typeof(-)(:in1_in2) [
            (
                input = (m_out = MvNormalMeanCovariance([2.0, 2.0], [2.0 0.0; 0.0 2.0]), m_in1 = PointMass([1.0, 1.0]), m_in2 = MvNormalMeanCovariance([1.0, 3.0], [3.0 2.0; 2.0 4.0])),
                output = (in1 = PointMass([1.0, 1.0]), in2 = MvNormalWeightedMeanPrecision([1/4, 11/8], [1.0 -1/4; -1/4 7/8]))
            ),
            (
                input = (m_out = MvNormalMeanCovariance([3.0, 2.0], [2.0 0.0; 0.0 2.0]), m_in1 = PointMass([1.0, 1.0]), m_in2 = MvNormalMeanPrecision([1.0, 1.0], [2.0 1.0; 1.0 2.0])),
                output = (in1 = PointMass([1.0, 1.0]), in2 = MvNormalWeightedMeanPrecision([4.0, 7/2], [5/2 1.0; 1.0 5/2]))
            ),
            (
                input = (m_out = MvNormalMeanCovariance([3.0, 2.0], [4.0 2.0; 2.0 4.0]), m_in1 = PointMass([2.0, 1.0]), m_in2 = MvNormalWeightedMeanPrecision([1.0, 2.0], [2.0 0.0; 0.0 2.0])),
                output = (in1 = PointMass([2.0, 1.0]), in2 = MvNormalWeightedMeanPrecision([7/6, 13/6], [7/3 -1/6; -1/6 7/3]))
            ),

        ]

    end

    @testset ":in1_in2 (m_out::MvNormalMeanPrecision, m_in1::, m_in2::MultivariateNormalDistributionsFamilyPointMass)" begin

        @test_marginalrules [ with_float_conversions = true ] typeof(-)(:in1_in2) [
            (
                input = (m_out = MvNormalMeanPrecision([2.0, 2.0], [1.0 0.0; 0.0 1.0]), m_in1 = PointMass([1.0, -1.0]), m_in2 = MvNormalMeanCovariance([2.0, 3.0], [3.0 2.0; 2.0 4.0])),
                output = (in1 = PointMass([1.0, -1.0]), in2 = MvNormalWeightedMeanPrecision([5/4, 29/8], [3/2 -1/4; -1/4 11/8]))
            ),
            (
                input = (m_out = MvNormalMeanPrecision([1.0, 1.0], [3.0 1.0; 1.0 3.0]), m_in1 = PointMass([2.0, 2.0]), m_in2 = MvNormalMeanPrecision([1.0, 1.0], [2.0 1.0; 1.0 2.0])),
                output = (in1 = PointMass([2.0, 2.0]), in2 = MvNormalWeightedMeanPrecision([-1.0, -1.0], [5.0 2.0; 2.0 5.0]))
            ),
            (
                input = (m_out = MvNormalMeanPrecision([3.0, 2.0], [4.0 2.0; 2.0 4.0]), m_in1 = PointMass([-2.0, 1.0]), m_in2 = MvNormalWeightedMeanPrecision([1.0, 2.0], [3.0 1.0; 1.0 1.0])),
                output = (in1 = PointMass([-2.0, 1.0]), in2 = MvNormalWeightedMeanPrecision([23.0, 16.0], [7.0  3.0; 3.0 5.0]))
            ),

        ]

    end

    @testset ":in1_in2 (m_out::MvNormalWeightedMeanPrecision, m_in1::PointMass, m_in2::MultivariateNormalDistributionsFamily)" begin

        @test_marginalrules [ with_float_conversions = true ] typeof(-)(:in1_in2) [
            (
                input = (m_out = MvNormalWeightedMeanPrecision([2.0, 2.0], [1.0 0.0; 0.0 1.0]), m_in1 = PointMass([1.0, -1.0]), m_in2 = MvNormalMeanCovariance([2.0, 3.0], [3.0 2.0; 2.0 4.0])),
                output = (in1 = PointMass([1.0, -1.0]), in2 = MvNormalWeightedMeanPrecision([5/4, 29/8], [3/2 -1/4; -1/4 11/8]))
            ),
            (
                input = (m_out = MvNormalWeightedMeanPrecision([1.0, 1.0], [3.0 1.0; 1.0 2.0]), m_in1 = PointMass([2.0, 2.0]), m_in2 = MvNormalMeanPrecision([1.0, 1.0], [2.0 1.0; 1.0 2.0])),
                output = (in1 = PointMass([2.0, 2.0]), in2 = MvNormalWeightedMeanPrecision([-4.0, -2.0], [5.0 2.0; 2.0 4.0]))
            ),
            (
                input = (m_out = MvNormalWeightedMeanPrecision([1.0, 2.0], [4.0 1.0; 1.0 4.0]), m_in1 = PointMass([-1.0, 1.0]), m_in2 = MvNormalWeightedMeanPrecision([1.0, 1.0], [3.0 0.0; 0.0 2.0])),
                output = (in1 = PointMass([-1.0, 1.0]), in2 = MvNormalWeightedMeanPrecision([5.0, 0.0], [7.0  1.0; 1.0 6.0]))
            ),

        ]

    end

    @testset ":in1_in2 (m_out::NormalDistributionsFamily, m_in1::NormalDistributionsFamily, m_in2::NormalDistributionsFamily)" begin

        @test_marginalrules [ with_float_conversions = true ] typeof(-)(:in1_in2) [
            (
                input = (m_out = NormalMeanVariance(1.0, 2.0), m_in1 = NormalMeanVariance(3.0, 4.0), m_in2 = NormalMeanVariance(5.0, 6.0)),
                output = (MvNormalWeightedMeanPrecision([5/4, 1/3], [3/4 -1/2; -1/2 2/3]))
            ),
            (
                input = (m_out = NormalMeanPrecision(1.0, 2.0), m_in1 = NormalMeanPrecision(3.0, 4.0), m_in2 = NormalMeanPrecision(5.0, 6.0)),
                output = (MvNormalWeightedMeanPrecision([14.0, 28.0], [6.0 -2.0; -2.0 8.0]))
            ),
            (
                input = (m_out = NormalWeightedMeanPrecision(1.0, 2.0), m_in1 = NormalWeightedMeanPrecision(3.0, 4.0), m_in2 = NormalWeightedMeanPrecision(5.0, 6.0)),
                output = (MvNormalWeightedMeanPrecision([4.0, 4.0], [6.0 -2.0; -2.0 8.0]))
            ),
            (
                input = (m_out = NormalMeanVariance(1.0, 2.0), m_in1 = NormalMeanPrecision(3.0, 4.0), m_in2 = NormalWeightedMeanPrecision(5.0, 6.0)),
                output = (MvNormalWeightedMeanPrecision([25/2, 9/2], [9/2 -1/2; -1/2 13/2]))
            ),
            (
                input = (m_out = MvNormalMeanCovariance([1.0, 2.0], [3.0 1.0; 1.0 2.0]), m_in1 = MvNormalMeanCovariance([2.0, 3.0], [3.0 1.0; 1.0 2.0]), m_in2 = MvNormalMeanCovariance([1.0, 2.0], [3.0 1.0; 1.0 2.0])),
                output = (MvNormalWeightedMeanPrecision([1/5, 12/5, 0.0, 0.0], [[0.8 -0.4 -0.4 0.2; -0.4 1.2 0.2 -0.6]; [-0.4 0.2 0.8 -0.4; 0.2 -0.6 -0.4 1.2]]))
            ),
            (
                input = (m_out = MvNormalMeanPrecision([1.0, 2.0], [3.0 1.0; 1.0 2.0]), m_in1 = MvNormalMeanPrecision([1.0, 2.0], [3.0 1.0; 1.0 2.0]), m_in2 = MvNormalMeanPrecision([1.0, 2.0], [3.0 1.0; 1.0 2.0])),
                output = (MvNormalWeightedMeanPrecision([10.0, 10.0, 0.0, 0.0], [6.0 2.0 -3.0 -1.0; 2.0 4.0 -1.0 -2.0; -3.0 -1.0 6.0 2.0; -1.0 -2.0 2.0 4.0]))
            ),
            (
                input = (m_out = MvNormalWeightedMeanPrecision([1.0, 2.0], [3.0 1.0; 1.0 1.0]), m_in1 = MvNormalWeightedMeanPrecision([1.0, 2.0], [3.0 1.0; 1.0 1.0]), m_in2 = MvNormalWeightedMeanPrecision([1.0, 2.0], [3.0 1.0; 1.0 1.0])),
                output = (MvNormalWeightedMeanPrecision([2.0, 4.0, 0.0, 0.0], [6.0 2.0 -3.0 -1.0; 2.0 2.0 -1.0 -1.0; -3.0 -1.0 6.0 2.0; -1.0 -1.0 2.0 2.0]))
            ),
            (
                input = (m_out = MvNormalMeanCovariance([1.0, 1.0], [3.0 1.0; 1.0 2.0]), m_in1 = MvNormalMeanPrecision([1.0, 1.0], [3.0 1.0; 1.0 2.0]), m_in2 = MvNormalWeightedMeanPrecision([1.0, 1.0], [3.0 1.0; 1.0 2.0])),
                output = (MvNormalWeightedMeanPrecision([21/5, 17/5, 4/5, 3/5], [17/5 4/5 -2/5 1/5; 4/5 13/5 1/5 -3/5; -2/5 1/5 17/5 4/5; 1/5 -3/5 4/5 13/5]))
            ),

        ]

    end

end
end


struct SARAH_prox_iterable{R<:Real,C<:RealOrComplex{R},Tx<:AbstractArray{C},Tf,Tg}
    F::Array{Tf}            # smooth term - array of f_i s
    g::Tg                   # nonsmooth part
    x0::Tx                  # initial point
    N::Int                  # of data points in the finite sum problem
    L::Maybe{Union{Array{R},R}}  # Lipschitz moduli of ∇f_i
    μ::Maybe{Union{Array{R},R}}  # convexity moduli of the gradients
    γ::Maybe{R}             # stepsize (a single scalar)
    m::Maybe{Int}           # number of inner loop updates
    ꞵ::Maybe{R}             # a scalar momentum
end

mutable struct SARAH_prox_state{R<:Real,Tx}
    γ::R                    # stepsize
    m::Maybe{Int}           # number of inner loop updates
    ꞵ::R                    # momentum
    av::Tx                  # average of ∇f_i s in outer loop
    w::Tx                   # w_{t-1} in the inner loop
    w_plus::Tx              # w_{t} in the inner loop
    ind::Array{Int}         # running idx set (1 to N)

    # some extra placeholders
    ∇f_temp::Tx             # placeholder for gradients
    temp::Tx                # placeholder for gradients
end

function SARAH_prox_state(γ::R, m, ꞵ::R, av::Tx, w::Tx, w_plus::Tx, ind) where {R,Tx}
    return SARAH_prox_state{R,Tx}(γ, m, ꞵ, av, w, w_plus, ind, copy(av), copy(av))
end

function Base.iterate(iter::SARAH_prox_iterable{R}) where {R}
    N = iter.N
    m = iter.m
    batch = 1
    ω = (3*(N-batch))/(2*batch*(N-1))
    ind = collect(1:N) # a vector of 1 to N
    # updating the stepsize
    if iter.γ === nothing
        γ = (2*sqrt(ω * m)) / (4*sqrt(ω * m) + 1)
    else
        γ = iter.γ # provided γ
    end

    if iter.ꞵ === nothing
        if iter.L === nothing
            @warn "smoothness parameter absent"
            return nothing
        else
            L_M = maximum(iter.L)
            ꞵ = 1/(L_M * sqrt(ω * m))
        end
    else
        ꞵ = iter.ꞵ
    end

    # initializing the vectors
    av = zero(iter.x0)
    w_plus = zero(iter.x0)
    for i = 1:N
        ∇f, ~ = gradient(iter.F[i], iter.x0)
        ∇f ./= N
        av .+= ∇f
    end
    w = copy(iter.x0)
    CIAOAlgorithms.prox!(w_plus, iter.g, w - γ * av, γ)
    w_plus .*= ꞵ
    w_plus .+= (1 - ꞵ) * w

    state = SARAH_prox_state(γ, m, ꞵ, av, w, w_plus, ind)
    return state, state
end

function Base.iterate(iter::SARAH_prox_iterable{R}, state::SARAH_prox_state{R}) where {R}
    # The inner cycle
    for i in rand(state.ind, state.m)
        gradient!(state.temp, iter.F[i], state.w_plus)
        gradient!(state.∇f_temp, iter.F[i], state.w)
        state.av .+= state.temp
        state.av .-= state.∇f_temp

        state.temp .= state.av
        state.temp .*= state.γ
        state.temp .*= -1
        state.temp .+= state.w_plus
        state.w .= state.w_plus
        CIAOAlgorithms.prox!(state.w_plus, iter.g, state.temp, state.γ)
        state.w_plus .*= state.ꞵ
        state.temp .= state.w
        state.temp .*= 1-state.ꞵ
        state.w_plus .+= state.temp
    end
    # full update
    state.w .= state.w_plus
    state.av .= zero(state.w)  # for next iterate
    for i = 1:iter.N
        gradient!(state.∇f_temp, iter.F[i], state.w)
        state.∇f_temp ./= iter.N
        state.av .+= state.∇f_temp
    end
    state.temp .= state.av
    state.temp .*= state.γ
    state.temp .*= -1
    state.temp .+= state.w
    CIAOAlgorithms.prox!(state.w_plus, iter.g, state.temp, state.γ)
    state.w_plus .*= state.ꞵ
    state.temp .= state.w
    state.temp .*= 1-state.ꞵ
    state.w_plus .+= state.temp

    return state, state
end

solution(state::SARAH_prox_state) = state.w_plus


## Load the packages.
using Tullio
using LoopVectorization
using Statistics
using BenchmarkTools
using PyPlot
using Interpolations

## Create the refine function.
function refine_field_nn!(hi, lo, n_hi, n_lo)
    # if n_hi ÷ n_lo == 2
    #     @tullio hi[2(i-1)+l] = lo[i] (l in 1:2)
    # elseif n_hi ÷ n_lo == 3
    #     @tullio hi[3(i-1)+l] = lo[i] (l in 1:3)
    # else
    #     throw(DomainError(n_hi/n_lo, "Refinement should be 2 or 3"))
    # end

    if n_hi ÷ n_lo == 2
        @tturbo for i in 0:2:size(hi, 1)-3
            i_lo = i÷2+2
            i_hi = i+2
            hi[i_hi  ] = lo[i_lo]
            hi[i_hi+1] = lo[i_lo]
        end
    elseif n_hi ÷ n_lo == 3
        @tturbo for i in 0:3:size(hi, 1)-3
            i_lo = i÷3+2
            i_hi = i+2
            hi[i_hi  ] = lo[i_lo]
            hi[i_hi+1] = lo[i_lo]
            hi[i_hi+2] = lo[i_lo]
        end
    else
        throw(DomainError(n_hi/n_lo, "Refinement should be 2 or 3"))
    end
end

function refine_field_int!(hi, lo, n_hi, n_lo)
    if n_hi ÷ n_lo == 2
        @tturbo for i in 0:2:size(hi, 1)-3
            i_lo = i÷2+2
            i_hi = i+2
            hi[i_hi  ] = 3//4*lo[i_lo] + 1//4*lo[i_lo-1]
            hi[i_hi+1] = 3//4*lo[i_lo] + 1//4*lo[i_lo+1]
        end
    elseif n_hi ÷ n_lo == 3
        @tturbo for i in 0:3:size(hi, 1)-3
            i_lo = i÷3+2
            i_hi = i+2
            hi[i_hi  ] = 2//3*lo[i_lo] + 1//3*lo[i_lo-1]
            hi[i_hi+1] = lo[i_lo]
            hi[i_hi+2] = 2//3*lo[i_lo] + 1//3*lo[i_lo+1]
        end
    else
        throw(DomainError(n_hi/n_lo, "Refinement should be 2 or 3"))
    end
end

function refine_field_int2!(a_hi_gc, a_lo_gc, n_hi, n_lo)
    dx_hi = 1/n_hi; dx_lo = 1/n_lo
    x_hi = -dx_hi/2:dx_hi:1+dx_hi |> collect
    x_lo = -dx_lo/2:dx_lo:1+dx_lo |> collect
    interp = LinearInterpolation(x_lo, a_lo_gc)
    a_hi_gc[2:end-1] = interp(x_hi[2:end-1])
end

function refine_field_int3!(hi, hi_tmp, lo, n_hi, n_lo)
    if n_hi ÷ n_lo == 2
        @tturbo for i in 0:2:size(hi, 1)-3
            i_lo = i÷2+2
            i_hi = i+2
            hi_tmp[i_hi  ] = lo[i_lo]
            hi_tmp[i_hi+1] = lo[i_lo]
        end

        hi_tmp[1] = hi_tmp[end-1]
        hi_tmp[end] = hi_tmp[2]

        @tturbo for i in 2:size(hi, 1)-1
            hi[i] = 1//4*hi_tmp[i-1] + 1//2*hi_tmp[i] + 1//4*hi_tmp[i+1]
        end
    elseif n_hi ÷ n_lo == 3
        @tturbo for i in 0:3:size(hi, 1)-3
            i_lo = i÷3+2
            i_hi = i+2
            hi_tmp[i_hi  ] = lo[i_lo]
            hi_tmp[i_hi+1] = lo[i_lo]
            hi_tmp[i_hi+2] = lo[i_lo]
        end

        hi_tmp[1] = hi_tmp[end-1]
        hi_tmp[end] = hi_tmp[2]

        @tturbo for i in 2:size(hi, 1)-1
            hi[i] = 1//3*hi_tmp[i-1] + 1//3*hi_tmp[i] + 1//3*hi_tmp[i+1]
        end
    else
        throw(DomainError(n_hi/n_lo, "Refinement should be 2 or 3"))
    end
end

## Set up the grids.
n_hi = 48
n_lo = n_hi ÷ 3

a_lo = rand(n_lo)
a_hi = zeros(n_hi)

a_hi_gc = zeros(n_hi+2)
a_lo_gc = zeros(n_lo+2)

a_hi_gc[2:n_hi+1] = a_hi[:]
a_lo_gc[2:n_lo+1] = a_lo[:]

a_hi_gc[1] = a_hi_gc[end-1]
a_hi_gc[end] = a_hi_gc[2]
a_lo_gc[1] = a_lo_gc[end-1]
a_lo_gc[end] = a_lo_gc[2]

dx_hi = 1/n_hi; dx_lo = 1/n_lo
x_hi = dx_hi/2:dx_hi:1 |> collect
x_lo = dx_lo/2:dx_lo:1 |> collect

## Compute.
@btime refine_field_nn!(a_hi_gc, a_lo_gc, n_hi, n_lo)
a_hi_nn = a_hi_gc[2:end-1]

@btime refine_field_int!(a_hi_gc, a_lo_gc, n_hi, n_lo)
a_hi_int = a_hi_gc[2:end-1]

@btime refine_field_int2!(a_hi_gc, a_lo_gc, n_hi, n_lo)
a_hi_int2 = a_hi_gc[2:end-1]

a_hi_tmp = zeros(size(a_hi_gc))
@btime refine_field_int3!(a_hi_gc, a_hi_tmp, a_lo_gc, n_hi, n_lo)
a_hi_int3 = a_hi_gc[2:end-1]

println(mean(a_lo) ≈ mean(a_hi_nn))
println(mean(a_lo) ≈ mean(a_hi_int))
println(mean(a_lo) ≈ mean(a_hi_int2))
println(mean(a_lo) ≈ mean(a_hi_int3))

println(mean(a_lo), " ", mean(a_hi_int), " ", mean(a_hi_int3))
println(a_hi_int3 .- a_hi_int)

## Plot the output.
figure()
#plot(x_hi, a_hi_nn, "C0-o")
#plot(x_hi, a_hi_int, "C1-o")
#plot(x_hi, a_hi_int2, "C2-^")
plot(x_hi, a_hi_int3, "C3-+")
plot(x_lo, a_lo, "k:")
display(gcf())
show()


#=
  https://dtai.cs.kuleuven.be/problog/tutorial/basic/10_inhibitioneffects.html
  """
  ...
  In some cases, however, if both causes are present this reduces the likelihood of the effect happening.
  For example when the presence of a second drug blocks the impact of the original drug. In such a case we
  can use negated heads. Depending on the chosen probabilities the effect can be lower than the joint occurance
  of c1 and c2 or be lower then either p1 or p2:
  """

  cf ~/blog/inhibition1.blog
     ~/webppl/inhibition1.wppl

=#

using Turing, StatsPlots, DataFrames
include("jl_utils.jl")

@model function inhibition1()
    c1 ~ flip(0.5)
    c2 ~ flip(0.5)

    e1 ~ c1 ? flip(0.3) : c2 ? flip(0.2) : flip(0.0)
    e2 ~ c2 ? flip(0.4) : c1 ? flip(0.2) : flip(0.0)
    # ee ~ flip(0.5)
    ee ~ Dirac(e1 || e2)

    true ~ Dirac(c1 == true)
    true ~ Dirac(c2 == true)
end

model = inhibition1()

num_chns = 4

# chns = sample(model, Prior(), 10_000)

# chns = sample(model, MH(), MCMCThreads(), 10_000, num_chns)
# chns = sample(model, MH(), 10_000)

# chns = sample(model, PG(15), MCMCThreads(), 1_000, num_chns)
# chns = sample(model, PG(15), 1_000)

# chns = sample(model, SMC(1000), MCMCThreads(), 10_000, num_chns)
chns = sample(model, SMC(1000), 10_000)

# chns = sample(model, IS(), 1_000)

#
display(chns)
show_var_dist_pct(chns,:c1,20)
show_var_dist_pct(chns,:c2,20)
show_var_dist_pct(chns,:e1,20)
show_var_dist_pct(chns,:e2,20)
show_var_dist_pct(chns,:ee,20)


# julia> include("/Users/ian/jd/10-19 Code/12 Julia/12.03 PE/pe2.jl")

fibs = [1,2]

while (fibs[end-1] + fibs[end]) < 4*10^6
    push!(fibs, fibs[end-1] + fibs[end])
end

println("ans = $(sum([i for i in fibs if i%2==0]))")


### A Pluto.jl notebook ###
# v0.17.2

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el)
        el
    end
end

# ╔═╡ 1dfd0480-6b0d-11eb-1f62-dbdc1a677571
begin
	#try
		using PlutoUI
		using Plots
		using LinearAlgebra
		using SparseArrays
		md"""
		# Packages

		All needed Packages available :) """
	#=
	catch
		using Pkg;
		Pkg.activate(mktempdir())
		Pkg.add("Plots")
		Pkg.add("PlutoUI")
		using PlutoUI
		using Plots
		using LinearAlgebra
		using SparseArrays
		md"""
		# Packages

		Some Package sources not added, this will take approx. 3 minutes"""
	end
	=#
end

# ╔═╡ e6375832-6b29-11eb-38b2-7582cac61e64
md"""

# **The strange dice** - _The central limit theorem_

**Bernoulli and Laplace** invented this interesting dice game with colored faces (red for odd numbers and green for even numbers of pips).
The side facing the gambler works as a bonus / malus factor.

In the case of a green side facing the gambler, the points get doubled, in case of a red face the points get subtracted by minus 1.

At the trinautic tournament the whole crew is rolling the strange dice and the points are summed up.

Look at the distribution below that shows the probabilities for the different number of points.

**Use the slider to add crew members to play and look how the average distribution of points (total points divided by the number of gamblers) changes**

"""

# ╔═╡ 86b1d820-6b2b-11eb-19cf-6390ab5fccc8
HTML("

")

# ╔═╡ 9f155e80-6be6-11eb-22c1-1b4f6e30ec72
md"Choose number of 👉 $(@bind iterations Slider(1:60, show_value = true)) crew members that roll the strange dice."

# ╔═╡ 4976c4b5-3f45-458d-83b8-3721abff5331
md"""
# About the creators

This notebook was created by **Gerhard Dorn** in the context of the course **Bayesian probability theory**.

The course is a free massive open online course (MOOC) available on the platform [`IMOOX`](https://imoox.at/mooc/local/landingpage/course.php?shortname=bayes&lang=en)

$(Resource("https://raw.githubusercontent.com/Captain-Bayes/images/main/adventure_map.gif"))
"""

# ╔═╡ 7ecb15e0-6b26-11eb-1d2b-37dd4e07ef05
HTML("

")

# ╔═╡ 185ef260-6b0b-11eb-1fd8-b5c70ec616d7
p = [1/6 1/6 1/6 1/6 1/6 1/6]

# ╔═╡ 943dea30-6b0b-11eb-19f0-75227e40a492
A = [ 3/4 1/4; 1/2 1/2; 3/4 1/4; 1/4 3/4; 1/2 1/2; 1/4 3/4]

# ╔═╡ 036a9bb0-6b0c-11eb-0ece-d54174c4d7a0
probability_table = A.*p'

# ╔═╡ 3eb465e0-6b14-11eb-19a4-df75db84f12d
sum(probability_table)

# ╔═╡ 427d9fb0-6b10-11eb-043c-653dbc050130
begin
	B = zeros(13,2)
	B[(1:6)',2] = probability_table[:,2]
	B[:,1] = (0:12)'
	index = collect(1:6)*2
	B[index.+1,2] = B[index.+1,2] + probability_table[:,1]
end

# ╔═╡ 29969590-6b12-11eb-11d7-9d831132fe4f
begin
	plot(
    B[:,1], B[:,2],
    line = (1.0, 0.0, :bar),
    normalize = false,
    bins = 10,
	bar_width = 0.1,
    marker = (6, 0.5, :x),
    markerstrokewidth = 5.,
    color = :steelblue,
    fill = 0.9,
    orientation = :v,
    title = "The strange dice distribution",
	ylabel = "probability mass function",
	xlabel = "Points",
	label = :none,
)
plot!( B[:,1], B[:,2],
    line = (1.0, 0, :path),
    normalize = false,
    bins = 10,
	bar_width = 0.2,
    marker = (4, 0.5, :o),
    markerstrokewidth = 5.,
    color = :steelblue,
    fill = 0.9,
    orientation = :v,
    title = "The strange dice distribution",
	ylabel = "probability mass function",
	xlabel = "Points",
	label = :none,
)
end

# ╔═╡ 6df0d2c0-6b10-11eb-04d1-1b71b887b8bb
B,sum(B[:,2])

# ╔═╡ 462e27c0-6b14-11eb-2b2f-3bd578fb8712
dice_mean = sum(B[:,2].*B[:,1])

# ╔═╡ 691d5aa0-6b71-11eb-2f53-db4a2217c8e0
dice_std = sqrt(sum(B[:,2].* (B[:,1].-dice_mean).^2))

# ╔═╡ 18e26640-6be7-11eb-20b0-5542c5620d8d
iter = iterations-1

# ╔═╡ 7dbbfe90-6b20-11eb-39c8-c99fd55ccf05
sparse_vector = repeat(B[:,2]',iter*12+1)

# ╔═╡ 80f0ce50-6b21-11eb-2b31-6982188b8a41
sparse_vector[:,1]

# ╔═╡ e107cae0-6b1d-11eb-3ff5-b7861639d659
P = Array(spdiagm(0 => sparse_vector[:,1], 1 => sparse_vector[:,2], 2 => sparse_vector[:,3], 3 => sparse_vector[:,4], 4 => sparse_vector[:,5], 5 => sparse_vector[:,6], 6 => sparse_vector[:,7], 7 => sparse_vector[:,8], 8 => sparse_vector[:,9], 9 => sparse_vector[:,10], 10=> sparse_vector[:,11], 11 => sparse_vector[:,12], 12 => sparse_vector[:,13]))

# ╔═╡ 58fe1a00-6b22-11eb-207c-095c2b30a0e6
pp = append!(B[:,2], zeros((iter)*12))

# ╔═╡ b3279d30-6b22-11eb-0658-dbe663f09558
p_final = Array(pp'*P^(iter))

# ╔═╡ 6f072a20-6b23-11eb-0add-8b62821b708a
r = Array(LinRange(0,12,(iter-1)*12+13*2-1))

# ╔═╡ b7a9ad70-6b23-11eb-295b-4324ef257047
begin

	plot(
    [B[:,1], r, [dice_mean, dice_mean]], [B[:,2]./0.9,p_final[1,:]./((r[2]-r[1])*0.9), [0,1]],
    line = ([1 0.3 1], [0 0.0 3], [:bar :bar :line]),
	linestyle = [:auto :auto :dash],
    normalize = true,
    marker = (6, 0.8, [:none :none]),
    markerstrokewidth = 5.,
    color = [:steelblue :orangered :black],
    fill = [1. 1.],
	bar_width = [1*0.9 (r[2]-r[1])*0.9],
    orientation = :v,
    title = "Average distribution of \nstrange dice with "*string(iter+1)*" gamblers",
	ylabel = "probability / width of bar",
	xlabel = "Points",
	usetex = true,
	label = [:none :none "Mean"],
	ylim = [0,1],
)

	if iter > 0
	plot!([dice_mean - dice_std/sqrt(iter), dice_mean + dice_std/sqrt(iter), 3, dice_mean+dice_std/sqrt(iter), dice_mean-dice_std/sqrt(iter)], 0.08*[1, 1,NaN, 1, 1]/sqrt(((r[2]-r[1]))),
		line = (2., 2., :path),
		color = :green,
		arrow = true,
		label = "Std/sqrt(" * string(iter+1) *")",
		)
	end
	plot!([dice_mean - dice_std, dice_mean + dice_std, 3, dice_mean+dice_std, dice_mean-dice_std], 0.5.*[1, 1,NaN, 1, 1],
		line = (2., 2., :path),
		color = :magenta,
		arrow = true,
		label = "Std",
		)
end

# ╔═╡ 89f8f100-6b24-11eb-3292-c15016c60a5b
p_final[1,:]

# ╔═╡ cec01d8e-6be2-11eb-1465-ad0f3ffcc2f5
function pretty(M::Matrix{T} where T<:String)
	max_length = maximum(length.(M))
	dv="
"
	HTML(dv*join([join("
".*M[i,:].*"
", " ") for i in 1:size(M,1)]
			, "
$dv")*"
")
end

# ╔═╡ e7e029f0-6b9c-11eb-179d-1d2fca9eb8af
[string.(rand("🧔👩👨👧🧑", 1, iterations));string.(rand("🎲", 1, iterations))] |> pretty

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[compat]
Plots = "~1.24.0"
PlutoUI = "~0.7.20"
"""

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

[[AbstractPlutoDingetjes]]
deps = ["Pkg"]
git-tree-sha1 = "0bc60e3006ad95b4bb7497698dd7c6d649b9bc06"
uuid = "6e696c72-6542-2067-7265-42206c756150"
version = "1.1.1"

[[Adapt]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "84918055d15b3114ede17ac6a7182f68870c16f7"
uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
version = "3.3.1"

[[ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"

[[Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

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

[[Bzip2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "19a35467a82e236ff51bc17a3a44b69ef35185a2"
uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0"
version = "1.0.8+0"

[[Cairo_jll]]
deps = ["Artifacts", "Bzip2_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Pkg", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"]
git-tree-sha1 = "f2202b55d816427cd385a9a4f3ffb226bee80f99"
uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a"
version = "1.16.1+0"

[[ChainRulesCore]]
deps = ["Compat", "LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "f885e7e7c124f8c92650d61b9477b9ac2ee607dd"
uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
version = "1.11.1"

[[ChangesOfVariables]]
deps = ["LinearAlgebra", "Test"]
git-tree-sha1 = "9a1d594397670492219635b35a3d830b04730d62"
uuid = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0"
version = "0.1.1"

[[ColorSchemes]]
deps = ["ColorTypes", "Colors", "FixedPointNumbers", "Random"]
git-tree-sha1 = "a851fec56cb73cfdf43762999ec72eff5b86882a"
uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
version = "3.15.0"

[[ColorTypes]]
deps = ["FixedPointNumbers", "Random"]
git-tree-sha1 = "024fe24d83e4a5bf5fc80501a314ce0d1aa35597"
uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
version = "0.11.0"

[[Colors]]
deps = ["ColorTypes", "FixedPointNumbers", "Reexport"]
git-tree-sha1 = "417b0ed7b8b838aa6ca0a87aadf1bb9eb111ce40"
uuid = "5ae59095-9a9b-59fe-a467-6f913c188581"
version = "0.12.8"

[[Compat]]
deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
git-tree-sha1 = "dce3e3fea680869eaa0b774b2e8343e9ff442313"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.40.0"

[[CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"

[[Contour]]
deps = ["StaticArrays"]
git-tree-sha1 = "9f02045d934dc030edad45944ea80dbd1f0ebea7"
uuid = "d38c429a-6771-53c6-b99e-75d170b6e991"
version = "0.5.7"

[[DataAPI]]
git-tree-sha1 = "cc70b17275652eb47bc9e5f81635981f13cea5c8"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.9.0"

[[DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "7d9d316f04214f7efdbb6398d545446e246eff02"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.10"

[[DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
version = "1.0.0"

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

[[DelimitedFiles]]
deps = ["Mmap"]
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"

[[Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"

[[DocStringExtensions]]
deps = ["LibGit2"]
git-tree-sha1 = "b19534d1895d702889b219c382a6e18010797f0b"
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
version = "0.8.6"

[[Downloads]]
deps = ["ArgTools", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"

[[EarCut_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "3f3a2501fa7236e9b911e0f7a588c657e822bb6d"
uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5"
version = "2.2.3+0"

[[Expat_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "b3bfd02e98aedfa5cf885665493c5598c350cd2f"
uuid = "2e619515-83b5-522b-bb60-26c02a35a201"
version = "2.2.10+0"

[[FFMPEG]]
deps = ["FFMPEG_jll"]
git-tree-sha1 = "b57e3acbe22f8484b4b5ff66a7499717fe1a9cc8"
uuid = "c87230d0-a227-11e9-1b43-d7ebe4e7570a"
version = "0.4.1"

[[FFMPEG_jll]]
deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_jll", "Libdl", "Ogg_jll", "OpenSSL_jll", "Opus_jll", "Pkg", "Zlib_jll", "libass_jll", "libfdk_aac_jll", "libvorbis_jll", "x264_jll", "x265_jll"]
git-tree-sha1 = "d8a578692e3077ac998b50c0217dfd67f21d1e5f"
uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5"
version = "4.4.0+0"

[[FixedPointNumbers]]
deps = ["Statistics"]
git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc"
uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
version = "0.8.4"

[[Fontconfig_jll]]
deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "21efd19106a55620a188615da6d3d06cd7f6ee03"
uuid = "a3f928ae-7b40-5064-980b-68af3947d34b"
version = "2.13.93+0"

[[Formatting]]
deps = ["Printf"]
git-tree-sha1 = "8339d61043228fdd3eb658d86c926cb282ae72a8"
uuid = "59287772-0a20-5a39-b81b-1366585eb4c0"
version = "0.4.2"

[[FreeType2_jll]]
deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "87eb71354d8ec1a96d4a7636bd57a7347dde3ef9"
uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7"
version = "2.10.4+0"

[[FriBidi_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "aa31987c2ba8704e23c6c8ba8a4f769d5d7e4f91"
uuid = "559328eb-81f9-559d-9380-de523a88c83c"
version = "1.0.10+0"

[[GLFW_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libglvnd_jll", "Pkg", "Xorg_libXcursor_jll", "Xorg_libXi_jll", "Xorg_libXinerama_jll", "Xorg_libXrandr_jll"]
git-tree-sha1 = "0c603255764a1fa0b61752d2bec14cfbd18f7fe8"
uuid = "0656b61e-2033-5cc2-a64a-77c0f6c09b89"
version = "3.3.5+1"

[[GR]]
deps = ["Base64", "DelimitedFiles", "GR_jll", "HTTP", "JSON", "Libdl", "LinearAlgebra", "Pkg", "Printf", "Random", "Serialization", "Sockets", "Test", "UUIDs"]
git-tree-sha1 = "30f2b340c2fff8410d89bfcdc9c0a6dd661ac5f7"
uuid = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71"
version = "0.62.1"

[[GR_jll]]
deps = ["Artifacts", "Bzip2_jll", "Cairo_jll", "FFMPEG_jll", "Fontconfig_jll", "GLFW_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "Pixman_jll", "Pkg", "Qt5Base_jll", "Zlib_jll", "libpng_jll"]
git-tree-sha1 = "fd75fa3a2080109a2c0ec9864a6e14c60cca3866"
uuid = "d2c73de3-f751-5644-a686-071e5b155ba9"
version = "0.62.0+0"

[[GeometryBasics]]
deps = ["EarCut_jll", "IterTools", "LinearAlgebra", "StaticArrays", "StructArrays", "Tables"]
git-tree-sha1 = "58bcdf5ebc057b085e58d95c138725628dd7453c"
uuid = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
version = "0.4.1"

[[Gettext_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "XML2_jll"]
git-tree-sha1 = "9b02998aba7bf074d14de89f9d37ca24a1a0b046"
uuid = "78b55507-aeef-58d4-861c-77aaff3498b1"
version = "0.21.0+0"

[[Glib_jll]]
deps = ["Artifacts", "Gettext_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "7bf67e9a481712b3dbe9cb3dac852dc4b1162e02"
uuid = "7746bdde-850d-59dc-9ae8-88ece973131d"
version = "2.68.3+0"

[[Graphite2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "344bf40dcab1073aca04aa0df4fb092f920e4011"
uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472"
version = "1.3.14+0"

[[Grisu]]
git-tree-sha1 = "53bb909d1151e57e2484c3d1b53e19552b887fb2"
uuid = "42e2da0e-8278-4e71-bc24-59509adca0fe"
version = "1.0.2"

[[HTTP]]
deps = ["Base64", "Dates", "IniFile", "Logging", "MbedTLS", "NetworkOptions", "Sockets", "URIs"]
git-tree-sha1 = "0fa77022fe4b511826b39c894c90daf5fce3334a"
uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
version = "0.9.17"

[[HarfBuzz_jll]]
deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg"]
git-tree-sha1 = "8a954fed8ac097d5be04921d595f741115c1b2ad"
uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566"
version = "2.8.1+0"

[[Hyperscript]]
deps = ["Test"]
git-tree-sha1 = "8d511d5b81240fc8e6802386302675bdf47737b9"
uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91"
version = "0.0.4"

[[HypertextLiteral]]
git-tree-sha1 = "2b078b5a615c6c0396c77810d92ee8c6f470d238"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.3"

[[IOCapture]]
deps = ["Logging", "Random"]
git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a"
uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
version = "0.2.2"

[[IniFile]]
deps = ["Test"]
git-tree-sha1 = "098e4d2c533924c921f9f9847274f2ad89e018b8"
uuid = "83e8ac13-25f8-5344-8a64-a9f2b223428f"
version = "0.5.0"

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

[[InverseFunctions]]
deps = ["Test"]
git-tree-sha1 = "a7254c0acd8e62f1ac75ad24d5db43f5f19f3c65"
uuid = "3587e190-3f89-42d0-90ee-14403ec27112"
version = "0.1.2"

[[IrrationalConstants]]
git-tree-sha1 = "7fd44fd4ff43fc60815f8e764c0f352b83c49151"
uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
version = "0.1.1"

[[IterTools]]
git-tree-sha1 = "05110a2ab1fc5f932622ffea2a003221f4782c18"
uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
version = "1.3.0"

[[IteratorInterfaceExtensions]]
git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856"
uuid = "82899510-4779-5014-852e-03e436cf321d"
version = "1.0.0"

[[JLLWrappers]]
deps = ["Preferences"]
git-tree-sha1 = "642a199af8b68253517b80bd3bfd17eb4e84df6e"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.3.0"

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

[[JpegTurbo_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "d735490ac75c5cb9f1b00d8b5509c11984dc6943"
uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8"
version = "2.1.0+0"

[[LAME_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "f6250b16881adf048549549fba48b1161acdac8c"
uuid = "c1c5ebd0-6772-5130-a774-d5fcae4a789d"
version = "3.100.1+0"

[[LZO_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "e5b909bcf985c5e2605737d2ce278ed791b89be6"
uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac"
version = "2.10.1+0"

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

[[Latexify]]
deps = ["Formatting", "InteractiveUtils", "LaTeXStrings", "MacroTools", "Markdown", "Printf", "Requires"]
git-tree-sha1 = "a8f4f279b6fa3c3c4f1adadd78a621b13a506bce"
uuid = "23fbe1c1-3f47-55db-b15f-69d7ec21a316"
version = "0.15.9"

[[LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"

[[LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"

[[LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"

[[Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[Libffi_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "0b4a5d71f3e5200a7dff793393e09dfc2d874290"
uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490"
version = "3.2.2+1"

[[Libgcrypt_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgpg_error_jll", "Pkg"]
git-tree-sha1 = "64613c82a59c120435c067c2b809fc61cf5166ae"
uuid = "d4300ac3-e22c-5743-9152-c294e39db1e4"
version = "1.8.7+0"

[[Libglvnd_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll", "Xorg_libXext_jll"]
git-tree-sha1 = "7739f837d6447403596a75d19ed01fd08d6f56bf"
uuid = "7e76a0d4-f3c7-5321-8279-8d96eeed0f29"
version = "1.3.0+3"

[[Libgpg_error_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "c333716e46366857753e273ce6a69ee0945a6db9"
uuid = "7add5ba3-2f88-524e-9cd5-f83b8a55f7b8"
version = "1.42.0+0"

[[Libiconv_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "42b62845d70a619f063a7da093d995ec8e15e778"
uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531"
version = "1.16.1+1"

[[Libmount_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "9c30530bf0effd46e15e0fdcf2b8636e78cbbd73"
uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9"
version = "2.35.0+0"

[[Libtiff_jll]]
deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Pkg", "Zlib_jll", "Zstd_jll"]
git-tree-sha1 = "340e257aada13f95f98ee352d316c3bed37c8ab9"
uuid = "89763e89-9b03-5906-acba-b20f662cd828"
version = "4.3.0+0"

[[Libuuid_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "7f3efec06033682db852f8b3bc3c1d2b0a0ab066"
uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700"
version = "2.36.0+0"

[[LinearAlgebra]]
deps = ["Libdl"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[LogExpFunctions]]
deps = ["ChainRulesCore", "ChangesOfVariables", "DocStringExtensions", "InverseFunctions", "IrrationalConstants", "LinearAlgebra"]
git-tree-sha1 = "be9eef9f9d78cecb6f262f3c10da151a6c5ab827"
uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
version = "0.3.5"

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

[[MacroTools]]
deps = ["Markdown", "Random"]
git-tree-sha1 = "3d3e902b31198a27340d0bf00d6ac452866021cf"
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
version = "0.5.9"

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

[[MbedTLS]]
deps = ["Dates", "MbedTLS_jll", "Random", "Sockets"]
git-tree-sha1 = "1c38e51c3d08ef2278062ebceade0e46cefc96fe"
uuid = "739be429-bea8-5141-9913-cc70e7f3736d"
version = "1.0.3"

[[MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"

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

[[Missings]]
deps = ["DataAPI"]
git-tree-sha1 = "bf210ce90b6c9eed32d25dbcae1ebc565df2687f"
uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
version = "1.0.2"

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

[[MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"

[[NaNMath]]
git-tree-sha1 = "bfe47e760d60b82b66b61d2d44128b62e3a369fb"
uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
version = "0.3.5"

[[NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"

[[Ogg_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "7937eda4681660b4d6aeeecc2f7e1c81c8ee4e2f"
uuid = "e7412a2a-1a6e-54c0-be00-318e2571c051"
version = "1.3.5+0"

[[OpenSSL_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "15003dcb7d8db3c6c857fda14891a539a8f2705a"
uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
version = "1.1.10+0"

[[Opus_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "51a08fb14ec28da2ec7a927c4337e4332c2a4720"
uuid = "91d4177d-7536-5919-b921-800302f37372"
version = "1.3.2+0"

[[OrderedCollections]]
git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c"
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
version = "1.4.1"

[[PCRE_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "b2a7af664e098055a7529ad1a900ded962bca488"
uuid = "2f80f16e-611a-54ab-bc61-aa92de5b98fc"
version = "8.44.0+0"

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

[[Pixman_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "b4f5d02549a10e20780a24fce72bea96b6329e29"
uuid = "30392449-352a-5448-841d-b1acce4e97dc"
version = "0.40.1+0"

[[Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"

[[PlotThemes]]
deps = ["PlotUtils", "Requires", "Statistics"]
git-tree-sha1 = "a3a964ce9dc7898193536002a6dd892b1b5a6f1d"
uuid = "ccf2f8ad-2431-5c83-bf29-c5338b663b6a"
version = "2.0.1"

[[PlotUtils]]
deps = ["ColorSchemes", "Colors", "Dates", "Printf", "Random", "Reexport", "Statistics"]
git-tree-sha1 = "b084324b4af5a438cd63619fd006614b3b20b87b"
uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043"
version = "1.0.15"

[[Plots]]
deps = ["Base64", "Contour", "Dates", "Downloads", "FFMPEG", "FixedPointNumbers", "GR", "GeometryBasics", "JSON", "Latexify", "LinearAlgebra", "Measures", "NaNMath", "PlotThemes", "PlotUtils", "Printf", "REPL", "Random", "RecipesBase", "RecipesPipeline", "Reexport", "Requires", "Scratch", "Showoff", "SparseArrays", "Statistics", "StatsBase", "UUIDs", "UnicodeFun"]
git-tree-sha1 = "02a083caba3f73e42decb810b2e0740783022978"
uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
version = "1.24.0"

[[PlutoUI]]
deps = ["AbstractPlutoDingetjes", "Base64", "Dates", "Hyperscript", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "UUIDs"]
git-tree-sha1 = "1e0cb51e0ccef0afc01aab41dc51a3e7f781e8cb"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.20"

[[Preferences]]
deps = ["TOML"]
git-tree-sha1 = "00cfd92944ca9c760982747e9a1d0d5d86ab1e5a"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.2.2"

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

[[Qt5Base_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Fontconfig_jll", "Glib_jll", "JLLWrappers", "Libdl", "Libglvnd_jll", "OpenSSL_jll", "Pkg", "Xorg_libXext_jll", "Xorg_libxcb_jll", "Xorg_xcb_util_image_jll", "Xorg_xcb_util_keysyms_jll", "Xorg_xcb_util_renderutil_jll", "Xorg_xcb_util_wm_jll", "Zlib_jll", "xkbcommon_jll"]
git-tree-sha1 = "ad368663a5e20dbb8d6dc2fddeefe4dae0781ae8"
uuid = "ea2cea3b-5b76-57ae-a6ef-0a8af62496e1"
version = "5.15.3+0"

[[REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

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

[[RecipesBase]]
git-tree-sha1 = "6bf3f380ff52ce0832ddd3a2a7b9538ed1bcca7d"
uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"
version = "1.2.1"

[[RecipesPipeline]]
deps = ["Dates", "NaNMath", "PlotUtils", "RecipesBase"]
git-tree-sha1 = "7ad0dfa8d03b7bcf8c597f59f5292801730c55b8"
uuid = "01d81517-befc-4cb6-b9ec-a95719d0359c"
version = "0.4.1"

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

[[Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "4036a3bd08ac7e968e27c203d45f5fff15020621"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.1.3"

[[SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"

[[Scratch]]
deps = ["Dates"]
git-tree-sha1 = "0b4b7f1393cff97c33891da2a0bf69c6ed241fda"
uuid = "6c6a2e73-6563-6170-7368-637461726353"
version = "1.1.0"

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

[[SharedArrays]]
deps = ["Distributed", "Mmap", "Random", "Serialization"]
uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"

[[Showoff]]
deps = ["Dates", "Grisu"]
git-tree-sha1 = "91eddf657aca81df9ae6ceb20b959ae5653ad1de"
uuid = "992d4aef-0814-514b-bc4d-f2e9a6c4116f"
version = "1.0.3"

[[Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[SortingAlgorithms]]
deps = ["DataStructures"]
git-tree-sha1 = "b3363d7460f7d098ca0912c69b082f75625d7508"
uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c"
version = "1.0.1"

[[SparseArrays]]
deps = ["LinearAlgebra", "Random"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[[StaticArrays]]
deps = ["LinearAlgebra", "Random", "Statistics"]
git-tree-sha1 = "3c76dde64d03699e074ac02eb2e8ba8254d428da"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "1.2.13"

[[Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[[StatsAPI]]
git-tree-sha1 = "0f2aa8e32d511f758a2ce49208181f7733a0936a"
uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
version = "1.1.0"

[[StatsBase]]
deps = ["DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"]
git-tree-sha1 = "2bb0cb32026a66037360606510fca5984ccc6b75"
uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
version = "0.33.13"

[[StructArrays]]
deps = ["Adapt", "DataAPI", "StaticArrays", "Tables"]
git-tree-sha1 = "2ce41e0d042c60ecd131e9fb7154a3bfadbf50d3"
uuid = "09ab397b-f2b6-538f-b94a-2f83cf4a842a"
version = "0.6.3"

[[TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[[TableTraits]]
deps = ["IteratorInterfaceExtensions"]
git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39"
uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
version = "1.0.1"

[[Tables]]
deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "TableTraits", "Test"]
git-tree-sha1 = "fed34d0e71b91734bf0a7e10eb1bb05296ddbcd0"
uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
version = "1.6.0"

[[Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"

[[Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[URIs]]
git-tree-sha1 = "97bbe755a53fe859669cd907f2d96aee8d2c1355"
uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
version = "1.3.0"

[[UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

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

[[UnicodeFun]]
deps = ["REPL"]
git-tree-sha1 = "53915e50200959667e78a92a418594b428dffddf"
uuid = "1cfade01-22cf-5700-b092-accc4b62d6e1"
version = "0.4.1"

[[Wayland_jll]]
deps = ["Artifacts", "Expat_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg", "XML2_jll"]
git-tree-sha1 = "3e61f0b86f90dacb0bc0e73a0c5a83f6a8636e23"
uuid = "a2964d1f-97da-50d4-b82a-358c7fce9d89"
version = "1.19.0+0"

[[Wayland_protocols_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "66d72dc6fcc86352f01676e8f0f698562e60510f"
uuid = "2381bf8a-dfd0-557d-9999-79630e7b1b91"
version = "1.23.0+0"

[[XML2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "Zlib_jll"]
git-tree-sha1 = "1acf5bdf07aa0907e0a37d3718bb88d4b687b74a"
uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a"
version = "2.9.12+0"

[[XSLT_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgcrypt_jll", "Libgpg_error_jll", "Libiconv_jll", "Pkg", "XML2_jll", "Zlib_jll"]
git-tree-sha1 = "91844873c4085240b95e795f692c4cec4d805f8a"
uuid = "aed1982a-8fda-507f-9586-7b0439959a61"
version = "1.1.34+0"

[[Xorg_libX11_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libxcb_jll", "Xorg_xtrans_jll"]
git-tree-sha1 = "5be649d550f3f4b95308bf0183b82e2582876527"
uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc"
version = "1.6.9+4"

[[Xorg_libXau_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "4e490d5c960c314f33885790ed410ff3a94ce67e"
uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec"
version = "1.0.9+4"

[[Xorg_libXcursor_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXfixes_jll", "Xorg_libXrender_jll"]
git-tree-sha1 = "12e0eb3bc634fa2080c1c37fccf56f7c22989afd"
uuid = "935fb764-8cf2-53bf-bb30-45bb1f8bf724"
version = "1.2.0+4"

[[Xorg_libXdmcp_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "4fe47bd2247248125c428978740e18a681372dd4"
uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05"
version = "1.1.3+4"

[[Xorg_libXext_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "b7c0aa8c376b31e4852b360222848637f481f8c3"
uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3"
version = "1.3.4+4"

[[Xorg_libXfixes_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "0e0dc7431e7a0587559f9294aeec269471c991a4"
uuid = "d091e8ba-531a-589c-9de9-94069b037ed8"
version = "5.0.3+4"

[[Xorg_libXi_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll", "Xorg_libXfixes_jll"]
git-tree-sha1 = "89b52bc2160aadc84d707093930ef0bffa641246"
uuid = "a51aa0fd-4e3c-5386-b890-e753decda492"
version = "1.7.10+4"

[[Xorg_libXinerama_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll"]
git-tree-sha1 = "26be8b1c342929259317d8b9f7b53bf2bb73b123"
uuid = "d1454406-59df-5ea1-beac-c340f2130bc3"
version = "1.1.4+4"

[[Xorg_libXrandr_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libXext_jll", "Xorg_libXrender_jll"]
git-tree-sha1 = "34cea83cb726fb58f325887bf0612c6b3fb17631"
uuid = "ec84b674-ba8e-5d96-8ba1-2a689ba10484"
version = "1.5.2+4"

[[Xorg_libXrender_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "19560f30fd49f4d4efbe7002a1037f8c43d43b96"
uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa"
version = "0.9.10+4"

[[Xorg_libpthread_stubs_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "6783737e45d3c59a4a4c4091f5f88cdcf0908cbb"
uuid = "14d82f49-176c-5ed1-bb49-ad3f5cbd8c74"
version = "0.1.0+3"

[[Xorg_libxcb_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "XSLT_jll", "Xorg_libXau_jll", "Xorg_libXdmcp_jll", "Xorg_libpthread_stubs_jll"]
git-tree-sha1 = "daf17f441228e7a3833846cd048892861cff16d6"
uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b"
version = "1.13.0+3"

[[Xorg_libxkbfile_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libX11_jll"]
git-tree-sha1 = "926af861744212db0eb001d9e40b5d16292080b2"
uuid = "cc61e674-0454-545c-8b26-ed2c68acab7a"
version = "1.1.0+4"

[[Xorg_xcb_util_image_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "0fab0a40349ba1cba2c1da699243396ff8e94b97"
uuid = "12413925-8142-5f55-bb0e-6d7ca50bb09b"
version = "0.4.0+1"

[[Xorg_xcb_util_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libxcb_jll"]
git-tree-sha1 = "e7fd7b2881fa2eaa72717420894d3938177862d1"
uuid = "2def613f-5ad1-5310-b15b-b15d46f528f5"
version = "0.4.0+1"

[[Xorg_xcb_util_keysyms_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "d1151e2c45a544f32441a567d1690e701ec89b00"
uuid = "975044d2-76e6-5fbe-bf08-97ce7c6574c7"
version = "0.4.0+1"

[[Xorg_xcb_util_renderutil_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "dfd7a8f38d4613b6a575253b3174dd991ca6183e"
uuid = "0d47668e-0667-5a69-a72c-f761630bfb7e"
version = "0.3.9+1"

[[Xorg_xcb_util_wm_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xcb_util_jll"]
git-tree-sha1 = "e78d10aab01a4a154142c5006ed44fd9e8e31b67"
uuid = "c22f9ab0-d5fe-5066-847c-f4bb1cd4e361"
version = "0.4.1+1"

[[Xorg_xkbcomp_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_libxkbfile_jll"]
git-tree-sha1 = "4bcbf660f6c2e714f87e960a171b119d06ee163b"
uuid = "35661453-b289-5fab-8a00-3d9160c6a3a4"
version = "1.4.2+4"

[[Xorg_xkeyboard_config_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Xorg_xkbcomp_jll"]
git-tree-sha1 = "5c8424f8a67c3f2209646d4425f3d415fee5931d"
uuid = "33bec58e-1273-512f-9401-5d533626f822"
version = "2.27.0+4"

[[Xorg_xtrans_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "79c31e7844f6ecf779705fbc12146eb190b7d845"
uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10"
version = "1.4.0+3"

[[Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"

[[Zstd_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "cc4bf3fdde8b7e3e9fa0351bdeedba1cf3b7f6e6"
uuid = "3161d3a3-bdf6-5164-811a-617609db77b4"
version = "1.5.0+0"

[[libass_jll]]
deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "5982a94fcba20f02f42ace44b9894ee2b140fe47"
uuid = "0ac62f75-1d6f-5e53-bd7c-93b484bb37c0"
version = "0.15.1+0"

[[libfdk_aac_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "daacc84a041563f965be61859a36e17c4e4fcd55"
uuid = "f638f0a6-7fb0-5443-88ba-1cc74229b280"
version = "2.0.2+0"

[[libpng_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"]
git-tree-sha1 = "94d180a6d2b5e55e447e2d27a29ed04fe79eb30c"
uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f"
version = "1.6.38+0"

[[libvorbis_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Ogg_jll", "Pkg"]
git-tree-sha1 = "c45f4e40e7aafe9d086379e5578947ec8b95a8fb"
uuid = "f27f6e37-5d2b-51aa-960f-b287f2bc3b7a"
version = "1.3.7+0"

[[nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"

[[p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"

[[x264_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "4fea590b89e6ec504593146bf8b988b2c00922b2"
uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a"
version = "2021.5.5+0"

[[x265_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "ee567a171cce03570d77ad3a43e90218e38937a9"
uuid = "dfaa095f-4041-5dcd-9319-2fabd8486b76"
version = "3.5.0+0"

[[xkbcommon_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Wayland_jll", "Wayland_protocols_jll", "Xorg_libxcb_jll", "Xorg_xkeyboard_config_jll"]
git-tree-sha1 = "ece2350174195bb31de1a63bea3a41ae1aa593b6"
uuid = "d8fb68d0-12a3-5cfd-a85a-d49703b185fd"
version = "0.9.1+5"
"""

# ╔═╡ Cell order:
# ╟─e6375832-6b29-11eb-38b2-7582cac61e64
# ╟─86b1d820-6b2b-11eb-19cf-6390ab5fccc8
# ╟─29969590-6b12-11eb-11d7-9d831132fe4f
# ╟─e7e029f0-6b9c-11eb-179d-1d2fca9eb8af
# ╟─9f155e80-6be6-11eb-22c1-1b4f6e30ec72
# ╟─b7a9ad70-6b23-11eb-295b-4324ef257047
# ╟─4976c4b5-3f45-458d-83b8-3721abff5331
# ╟─1dfd0480-6b0d-11eb-1f62-dbdc1a677571
# ╟─7ecb15e0-6b26-11eb-1d2b-37dd4e07ef05
# ╟─185ef260-6b0b-11eb-1fd8-b5c70ec616d7
# ╟─943dea30-6b0b-11eb-19f0-75227e40a492
# ╟─036a9bb0-6b0c-11eb-0ece-d54174c4d7a0
# ╟─3eb465e0-6b14-11eb-19a4-df75db84f12d
# ╟─427d9fb0-6b10-11eb-043c-653dbc050130
# ╟─6df0d2c0-6b10-11eb-04d1-1b71b887b8bb
# ╟─691d5aa0-6b71-11eb-2f53-db4a2217c8e0
# ╟─462e27c0-6b14-11eb-2b2f-3bd578fb8712
# ╟─18e26640-6be7-11eb-20b0-5542c5620d8d
# ╟─7dbbfe90-6b20-11eb-39c8-c99fd55ccf05
# ╟─80f0ce50-6b21-11eb-2b31-6982188b8a41
# ╟─e107cae0-6b1d-11eb-3ff5-b7861639d659
# ╟─58fe1a00-6b22-11eb-207c-095c2b30a0e6
# ╠═b3279d30-6b22-11eb-0658-dbe663f09558
# ╠═6f072a20-6b23-11eb-0add-8b62821b708a
# ╠═89f8f100-6b24-11eb-3292-c15016c60a5b
# ╟─cec01d8e-6be2-11eb-1465-ad0f3ffcc2f5
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


# ------------------------------------------------------------------
# Licensed under the MIT License. See LICENSE in the project root.
# ------------------------------------------------------------------

"""
    boundbox(object)

Return the minimum axis-aligned bounding rectangle of the spatial `object`.
"""
boundbox(obj) = boundbox(domain(obj))

function boundbox(obj::AbstractDomain)
  N = ncoords(obj)
  T = coordtype(obj)

  lowerleft  = MVector(ntuple(i->typemax(T), N))
  upperright = MVector(ntuple(i->typemin(T), N))

  x = MVector{N,T}(undef)
  for l in 1:nelms(obj)
    coordinates!(x, obj, l)
    for d in 1:N
      x[d] < lowerleft[d]  && (lowerleft[d]  = x[d])
      x[d] > upperright[d] && (upperright[d] = x[d])
    end
  end

  Rectangle(lowerleft, upperright)
end

function boundbox(obj::RegularGrid)
  lowerleft  = origin(obj)
  upperright = origin(obj) + (size(obj) .- 1) .* spacing(obj)
  Rectangle(lowerleft, upperright)
end


# This file is a part of Julia. License is MIT: https://julialang.org/license

using Test, Random, LinearAlgebra

@testset "middle" begin
    @test middle(3) === 3.0
    @test middle(2, 3) === 2.5
    let x = ((realmax(1.0)/4)*3)
        @test middle(x, x) === x
    end
    @test middle(1:8) === 4.5
    @test middle([1:8;]) === 4.5

    # ensure type-correctness
    for T in [Bool,Int8,Int16,Int32,Int64,Int128,UInt8,UInt16,UInt32,UInt64,UInt128,Float16,Float32,Float64]
        @test middle(one(T)) === middle(one(T), one(T))
    end
end

@testset "median" begin
    @test median([1.]) === 1.
    @test median([1.,3]) === 2.
    @test median([1.,3,2]) === 2.

    @test median([1,3,2]) === 2.0
    @test median([1,3,2,4]) === 2.5

    @test median([0.0,Inf]) == Inf
    @test median([0.0,-Inf]) == -Inf
    @test median([0.,Inf,-Inf]) == 0.0
    @test median([1.,-1.,Inf,-Inf]) == 0.0
    @test isnan(median([-Inf,Inf]))

    X = [2 3 1 -1; 7 4 5 -4]
    @test all(median(X, dims=2) .== [1.5, 4.5])
    @test all(median(X, dims=1) .== [4.5 3.5 3.0 -2.5])
    @test X == [2 3 1 -1; 7 4 5 -4] # issue #17153

    @test_throws ArgumentError median([])
    @test isnan(median([NaN]))
    @test isnan(median([0.0,NaN]))
    @test isnan(median([NaN,0.0]))
    @test isequal(median([NaN 0.0; 1.2 4.5], dims=2), reshape([NaN; 2.85], 2, 1))

    @test median!([1 2 3 4]) == 2.5
    @test median!([1 2; 3 4]) == 2.5

    @test invoke(median, Tuple{AbstractVector}, 1:10) == median(1:10) == 5.5
end

@testset "mean" begin
    @test_throws ArgumentError mean(())
    @test mean((1,2,3)) === 2.
    @test mean([0]) === 0.
    @test mean([1.]) === 1.
    @test mean([1.,3]) == 2.
    @test mean([1,2,3]) == 2.
    @test mean([0 1 2; 4 5 6], dims=1) == [2.  3.  4.]
    @test mean([1 2 3; 4 5 6], dims=1) == [2.5 3.5 4.5]
    @test mean(i->i+1, 0:2) === 2.
    @test mean(isodd, [3]) === 1.
    @test mean(x->3x, (1,1)) === 3.

    @test isnan(mean([NaN]))
    @test isnan(mean([0.0,NaN]))
    @test isnan(mean([NaN,0.0]))

    @test isnan(mean([0.,Inf,-Inf]))
    @test isnan(mean([1.,-1.,Inf,-Inf]))
    @test isnan(mean([-Inf,Inf]))
    @test isequal(mean([NaN 0.0; 1.2 4.5], dims=2), reshape([NaN; 2.85], 2, 1))

    # Check that small types are accumulated using wider type
    for T in (Int8, UInt8)
        x = [typemax(T) typemax(T)]
        g = (v for v in x)
        @test mean(x) == mean(g) == typemax(T)
        @test mean(identity, x) == mean(identity, g) == typemax(T)
        @test mean(x, dims=2) == [typemax(T)]'
    end
end

@testset "quantile" begin
    @test quantile([1,2,3,4],0.5) == 2.5
    @test quantile([1,2,3,4],[0.5]) == [2.5]
    @test quantile([1., 3],[.25,.5,.75])[2] == median([1., 3])
    @test quantile(100.0:-1.0:0.0, 0.0:0.1:1.0) == 0.0:10.0:100.0
    @test quantile(0.0:100.0, 0.0:0.1:1.0, sorted=true) == 0.0:10.0:100.0
    @test quantile(100f0:-1f0:0.0, 0.0:0.1:1.0) == 0f0:10f0:100f0
    @test quantile([Inf,Inf],0.5) == Inf
    @test quantile([-Inf,1],0.5) == -Inf
    @test quantile([0,1],1e-18) == 1e-18
    @test quantile([1, 2, 3, 4],[]) == []
    @test quantile([1, 2, 3, 4], (0.5,)) == (2.5,)
    @test quantile([4, 9, 1, 5, 7, 8, 2, 3, 5, 17, 11], (0.1, 0.2, 0.4, 0.9)) == (2.0, 3.0, 5.0, 11.0)
    @test quantile([1, 2, 3, 4], ()) == ()
end

# StatsBase issue 164
let y = [0.40003674665581906, 0.4085630862624367, 0.41662034698690303, 0.41662034698690303, 0.42189053966652057, 0.42189053966652057, 0.42553514344518345, 0.43985732442991354]
    @test issorted(quantile(y, range(0.01, stop=0.99, length=17)))
end

@testset "Issue #17153 and PR #17154" begin
    a = rand(10,10)
    b = copy(a)
    x = median(a, dims=1)
    @test b == a
    x = median(a, dims=2)
    @test b == a
    x = mean(a, dims=1)
    @test b == a
    x = mean(a, dims=2)
    @test b == a
end

# dimensional correctness
isdefined(Main, :TestHelpers) || @eval Main include("TestHelpers.jl")
using .Main.TestHelpers: Furlong
@testset "Unitful elements" begin
    r = Furlong(1):Furlong(1):Furlong(2)
    a = Vector(r)
    @test sum(r) == sum(a) == Furlong(3)
    @test cumsum(r) == Furlong.([1,3])
    @test mean(r) == mean(a) == median(a) == median(r) == Furlong(1.5)

    # Issue #21786
    A = [Furlong{1}(rand(-5:5)) for i in 1:2, j in 1:2]
    @test mean(mean(A, dims=1), dims=2)[1] === mean(A)
end

# Issue #22901
@testset "quantile of Any arrays" begin
    x = Any[1, 2, 4, 10]
    y = Any[1, 2, 4, 10//1]
    @test quantile(x, 0.5)  === 3.0
    @test quantile(x, 1//2) === 3//1
end

@testset "Mean along dimension of empty array" begin
    a0  = zeros(0)
    a00 = zeros(0, 0)
    a01 = zeros(0, 1)
    a10 = zeros(1, 0)
    @test isequal(mean(a0, dims=1)      , fill(NaN, 1))
    @test isequal(mean(a00, dims=(1, 2)), fill(NaN, 1, 1))
    @test isequal(mean(a01, dims=1)     , fill(NaN, 1, 1))
    @test isequal(mean(a10, dims=2)     , fill(NaN, 1, 1))
end


# test_extensions.jl

function test_extensions()
    @testset "$(rpad("Periodic extension",80))" begin
        test_periodic_extensions() end
    @testset "$(rpad("Zero padding",80))" begin
        test_zeropadding_extensions() end
    @testset "$(rpad("Constant padding",80))" begin
        test_constantpadding_extensions() end
    @testset "$(rpad("Symmetric extension",80))" begin
        test_symmetric_extensions() end
end

# A collection of vectors of different types for use in the tests
function test_vectors()
    test_vectors = Array(Any, 0)
    # A regular array
    push!(test_vectors, [1,2,3])
    push!(test_vectors, [1.0+1.0im, 2.0+3.0im, 4.2-2im])
    # A LinSpace object
    push!(test_vectors, [1,2,3])
    push!(test_vectors, OffsetArray([1,2,3,4,5], -1:3))
    test_vectors
end

function test_periodic_extensions()
    vectors = test_vectors()
    for vector in vectors
        test_periodic_extension(vector)
    end
end

function test_periodic_extension(a)
    p = PeriodicSequence(a)

    @test eltype(p) == eltype(a)

    @test !iscompact(p)

    # Equality of the embedded elements
    for i in eachindex(a)
        @test p[i] == a[i]
    end

    firstindex = first(linearindices(a))
    lastindex = last(linearindices(a))
    n = lastindex-firstindex+1
    @test p[firstindex-1] == a[lastindex]
    @test p[firstindex-2] == a[lastindex-1]
    @test p[lastindex+1] == a[firstindex]
    @test p[lastindex+2] == a[firstindex+1]

    large_index = 100
    periodic_index = mod(large_index-firstindex, n) + firstindex
    @test p[large_index] == a[periodic_index]

    # Check time-reversal and (c)transpose
    test_index = firstindex + 1
    @test p'[-test_index] == conj(p[test_index])
    @test p.'[-test_index] == p[test_index]
end

function test_zeropadding_extensions()
    vectors = test_vectors()
    for vector in vectors
        test_zeropadding_extension(vector)
    end
end

function test_zeropadding_extension(a)
    p = ZeroPaddingSequence(a)

    @test eltype(p) == eltype(a)

    @test iscompact(p)

    # Equality of the embedded elements
    for i in eachindex(a)
        @test p[i] == a[i]
    end

    firstindex = first(linearindices(a))
    lastindex = last(linearindices(a))
    n = lastindex-firstindex+1
    @test p[firstindex-1] == 0
    @test p[firstindex-2] == 0
    @test p[lastindex+1] == 0
    @test p[lastindex+2] == 0

    # Check that the zero has the same type as a
    @test eltype(p[firstindex-1]) == eltype(a)

    # Check time-reversal and (c)transpose
    test_index = firstindex + 1
    @test p'[-test_index] == conj(p[test_index])
    @test p.'[-test_index] == p[test_index]
end

function test_constantpadding_extensions()
    vectors = test_vectors()
    for vector in vectors
        test_constantpadding_extension(vector, 1)
    end
end

function test_constantpadding_extension(a, c)
    p = ConstantPaddingSequence(a, c)

    @test eltype(p) == eltype(a)

    @test !iscompact(p)

    # Equality of the embedded elements
    for i in eachindex(a)
        @test p[i] == a[i]
    end

    firstindex = first(linearindices(a))
    lastindex = last(linearindices(a))
    n = lastindex-firstindex+1
    @test p[firstindex-1] == c
    @test p[firstindex-2] == c
    @test p[lastindex+1] == c
    @test p[lastindex+2] == c

    # Check that the constant has the same type as a
    @test eltype(p[firstindex-1]) == eltype(a)

    # Check time-reversal and (c)transpose
    test_index = firstindex + 1
    @test p'[-test_index] == conj(p[test_index])
    @test p.'[-test_index] == p[test_index]
end

function test_symmetric_extensions()
    vectors = test_vectors()
    for vector in vectors
        test_symmetric_extension(vector)
    end
end

function test_symmetric_extension(a)
    p = SymmetricSequence(a)

    @test eltype(p) == eltype(a)

    @test !iscompact(p)

    # Equality of the embedded elements
    for i in eachindex(a)
        @test p[i] == a[i]
    end

    firstindex = first(linearindices(a))
    lastindex = last(linearindices(a))
    n = lastindex-firstindex+1

    p1 = symmetric_extension_wholepoint_even(a)
    @test left_parity(p1) == :even
    @test right_parity(p1) == :even
    @test left_symmetry(p1) == :wp
    @test right_symmetry(p1) == :wp
    @test p1[lastindex+1] == a[lastindex]
    @test p1[lastindex+2] == a[lastindex-1]
    @test p1[firstindex-1] == a[firstindex]
    @test p1[firstindex-2] == a[firstindex+1]
    p2 = symmetric_extension_wholepoint_odd(a)
    @test p2[lastindex+1] == -a[lastindex]
    @test p2[lastindex+2] == -a[lastindex-1]
    @test p2[firstindex-1] == -a[firstindex]
    @test p2[firstindex-2] == -a[firstindex+1]
    p3 = symmetric_extension_halfpoint_even(a)
    @test p3[lastindex+1] == a[lastindex-1]
    @test p3[firstindex-1] == a[firstindex+1]
    p4 = symmetric_extension_halfpoint_odd(a)
    @test p4[lastindex+1] == -a[lastindex-1]
    @test p4[firstindex-1] == -a[firstindex+1]

    # A few mixed cases
    p5 = SymmetricSequence{:wp,:hp,:even,:odd,typeof(a),eltype(a)}(a)
    @test p5[lastindex+1] == -a[lastindex-1]
    @test p5[firstindex-1] == a[firstindex]

    p6 = SymmetricSequence{:hp,:wp,:odd,:even,typeof(a),eltype(a)}(a)
    @test p6[lastindex+1] ==  a[lastindex]
    @test p6[firstindex-1] == -a[firstindex+1]

    # Check time-reversal and (c)transpose
    test_index = firstindex + 1
    @test p'[-test_index] == conj(p[test_index])
    @test p.'[-test_index] == p[test_index]
end


@doc doc"""
point_estimate_avg(psm::Array{Float64, 2})

	Find optimal partition which minimizes the lower bound to the Variation of Information
	obtain from Jensen's inequality where the expectation and log are reversed.

	Code based on R implementation by Sara Wade <sara.wade@eng.cam.ac.uk>

""" ->
function point_estimate_hclust(psm::Array{Float64, 2}; maxk = -1, method = :average)

	havg = hclust(1-psm, method)
	cls = reduce(hcat, map(k -> cutree(havg; k = k), 1:maxk))

	# compute Variation of Information lower bound
	vi = variation_of_information_lb(cls, psm)

	idx = findfirst(minimum(vi) .== vi)

	if ndims(cls) == 2
		cl = cls[:,idx]
	else
		cl = cls
	end

	return (cl, minimum(vi))

end

@doc doc"""
point_estimate(psm::Array{Float64, 2})

	Find optimal partition which minimizes the lower bound to the Variation of Information
	obtain from Jensen's inequality where the expectation and log are reversed.

	Code based on R implementation by Sara Wade <sara.wade@eng.cam.ac.uk>

""" ->
function point_estimate(psm::Array{Float64, 2}; method = :avg, maxk = -1)

	methods = [:avg, :comp, :greedy]

	@assert method in methods "Method must be :avg , :comp or :greedy."

	if maxk == -1
		maxk = convert(Int, ceil(size(psm)[1] / 4.0))
	end

	if method == :avg
		return point_estimate_hclust(psm, maxk = maxk, method = :average)
	elseif method == :comp
		return point_estimate_hclust(psm, maxk = maxk, method = :complete)
	else
		println("not implemented")
	end
end

@doc doc"""
variation_of_information()
	Computes the lower bound to the posterior expected Variation of Information
""" ->
function variation_of_information_lb(cls::Array{Int, 2}, psm)

	N = size(psm)[1]
	F = zeros(size(cls)[2])

	for ci in 1:size(cls)[2]
		c = cls[:,ci]
		for i in 1:N
			ind = c .== c[i]
			F[ci] += (log2(sum(ind)) +log2(sum(psm[i,:])) -2 * log2(sum(ind' .* psm[i,:]))) / N
		end
	end

	return F

end

@doc doc"""
variation_of_information()
	Computes the lower bound to the posterior expected Variation of Information
""" ->
function variation_of_information_lb(cls::Vector{Int}, psm)

	N = size(psm)[1]
	F = zeros(1)

	for i in 1:N
		ind = cls .== cls[i]
		F[1] += (log2(sum(ind))+log2(sum(psm[i,:]))-2*log2(sum(ind' .* psm[i,:]))) / N
	end

	return F

end


"""
	prep_data_granger(data, seglen, verbose)

Checks and prepares data shape.

### Arguments

  - `data::AbstractArray`: Nx2 array for N data points in 2 channels.
  - `seglen::Integer`: segment length.
  - `verbose::Bool`: if `true`, warnings and info logs would be echoed.

### Returns

  - `data::AbstractArray`: NxM array for N data points in M channels.
"""
function prep_data_granger(data::AbstractArray, seglen::Integer, verbose::Bool)

    if ndims(data) != 2  # data dimension
        data = squeeze(data)
        ndims(data) != 2 && throw(DimensionMismatch("Data must be a 2D-array!"))
        verbose && @info "Data is squeezed to a 2D-array)"
    end
    if size(data, 1) < size(data, 2)  # should be NxM array for N data points in M channels
        verbose && @info "Data is transposed to (#samples, #channels)"
        data = reshape(data, size(data, 2), size(data, 1))
    end
    if size(data, 2) != 2  # should be exactly 2 channels
        throw("Only 2 channels are supported!")
    end
    if size(data, 1) < seglen  # seglen must be smaller than number of samples
        throw(DimensionMismatch("seglen must be smaller than number of samples!"))
    end
    return data
end

"""
	granger_est(data, seglen; order, method, verbose)

Granger's method of causal relation approximation

### Arguments

  - `data::AbstractArray`: Nx2 array for N data points in 2 channels.
  - `seglen::Integer`: segment length.

*optional arguments*

  - `method::String`: standard deviation estimation method (default is `"jackknife"`)
  - `order::Int`: Model order. Assumed time delay order of interest (default is `n_samples / 2`)
  - `verbose::Bool`: if `true`, warnings and info logs would be echoed.

### Returns

  - `Grind::Float64`: Granger causality index
  - `Grind_std::Float64`: estimated standard deviation of error

### Internal variables:

  - Covs: Concatenated covariance mats of different orders
  - Acoef: Design (A) matrix coefficients
  - Perr: Prediction Error
  - Σ: Measures the accuracy of the autoregressive prediction

### References:
  - C. W. J. Granger (1969). Investigating causal relations by econometric models
    and cross-spectral methods, Econometrica, 37, 424-438.
  - M. Ding et al. (2006). Granger Causality: Basic Theory and Application to Neuroscience
    available online: https://arxiv.org/abs/q-bio/0608035

"""
function granger_est(
    data::Array{Float64,2},
    seglen::Int;
    order::Int = 0,
    method::String = "none",
    verbose::Bool = true,
)

    data = prep_data_granger(data, seglen, verbose)

    if order == 0
        order = int(seglen / 2) - 1
    end

    Covs12 = est_sig_covs(data, order)
    Covs1 = est_sig_covs(review(data, 1), order)
    Covs2 = est_sig_covs(review(data, 2), order)

    Acoef12, Perr12 = mvar_est(Covs12)
    Acoef1, _ = mvar_est(Covs1)
    Acoef2, _ = mvar_est(Covs2)

    Σ12 = Perr12[:, end-1:end]
    Σ12 = est_noise_covs(data, Acoef12, seglen)
    Σ1 = est_noise_covs(review(data, 1), Acoef1, seglen)
    Γ1 = est_noise_covs(review(data, 2), Acoef2, seglen)
    Σ2 = Σ12[1, 1]
    Γ2 = Σ12[2, 2]

    X_to_Y = log(Γ1 / Γ2)
    Y_to_X = log(Σ1 / Σ2)

    Grind = (X_to_Y-Y_to_X)[1]

    if lowercase(method) == "jackknife"
        Grind_std = granger_jackknife(data, seglen, order, Covs12, Covs1, Covs2)
        return Grind, Grind_std
    else
        return Grind
    end

end

"""
	est_sig_covs(data, order)

Concatenated covariances of signal to the given order

### Arguments

  - `data::AbstractArray`: Nx2 array for N data points in 2 channels.
  - `order::Int`: Model order (default is nsamples/2 )

### Returns

  - `Covs::Array{Float64, 2}`: Concatenated covariances of 0 to the given order

"""
function est_sig_covs(data::AbstractArray{Float64,2}, order::Int)::Array{Float64,2}

    nsamples, nchan = size(data) # number of rows (samples) and columns (channels)

    Covs = Array{Float64}(undef, nchan, nchan * (order + 1))

    Covs[:, 1:nchan] = covariance(data)
    for k = 1:order
        @views Covs[:, (1:nchan).+k*nchan] =
            covariance(data[k+1:nsamples, :], data[1:nsamples-k, :])
    end
    return Covs
end

"""
	mvar_est(Covs)

MultiVariate AutoRegressive (MVAR) model parameter estimation using
"Levinson-Wiggens-Robinson (LWR) algorithm using unbiased correlation function"

### Arguments

  - `Covs::Array{Float64, 2}`: Concatenated covariances of 0 to the given order

### Returns

  - `ARF::Array{Float64, 2}`: Design (A) matrix coefficients
  - `PE::Array{Float64}` : model Prediction Error

### References:
  - Alois Schlögl et al. <a.schloegl@ieee.org>, part of TSA-toolbox and FieldTrip-toolbox:
    https://github.com/fieldtrip/fieldtrip/blob/master/external/biosig/mvar.m

"""
function mvar_est(Covs::Array{Float64,2})

    n_i, m_j = size(Covs)  # number of rows and columns
    order = int(m_j / n_i) - 1

    ARF = Array{Float64}(undef, n_i, n_i * order)
    ARB = Array{Float64}(undef, n_i, n_i * order)
    RCF = Array{Float64}(undef, n_i, n_i * order)
    RCB = Array{Float64}(undef, n_i, n_i * order)
    PE = Array{Float64}(undef, n_i, n_i * (order + 1))

    PE[:, 1:n_i] = Covs[:, 1:n_i]
    PEF = Covs[:, 1:n_i]  # it's a different normalization in BioSig
    PEB = Covs[:, 1:n_i]  # it's a different normalization in BioSig

    for k = 1:order
        S1 = k * n_i .+ (1:n_i)  # slice
        S2 = k * n_i .+ (1-n_i:0)  # slice

        D = Covs[:, S1]
        for L = 1:k-1
            S3 = L * n_i .+ (1-n_i:0)  # slice
            D -= ARF[:, S3] * Covs[:, (k-L)*n_i.+(1:n_i)]
        end

        ARF[:, S2] = D / PEB
        ARB[:, S2] = D' / PEF
        for L = 1:k-1
            S3 = L * n_i .+ (1-n_i:0)  # slice
            S4 = (k - L) * n_i .+ (1-n_i:0)  # slice
            ARB[:, S4], ARF[:, S3] =
                (ARB[:, S4] - ARB[:, S2] * ARF[:, S3], ARF[:, S3] - ARF[:, S2] * ARB[:, S4])
        end

        RCF[:, S2] = ARF[:, S2]
        RCB[:, S2] = ARB[:, S2]
        PEF = (identity(n_i) - ARF[:, S2] * ARB[:, S2]) * PEF
        PEB = (identity(n_i) - ARB[:, S2] * ARF[:, S2]) * PEB
        PE[:, S1] = PEF
    end

    return ARF, PE
end

"""
	est_noise_covs(data, A, seglen)

Estimation of covariance of noise

### Arguments

  - `data::AbstractArray`: NxM array for N data points in M channels.
  - `A::Array{Float64, 2}`: Design (A) matrix coefficients
  - `seglen::Integer`: segment length.

### Returns

  - `C::Array{Float64}` : covariance matrix of noise

"""
function est_noise_covs(
    data::AbstractArray{Float64,2},
    A::AbstractArray{Float64,2},
    seglen::Int,
)

    nsamples, nchan = size(data)  # number of rows and columns
    order = int(size(A, 2) / nchan)  # number of columns = order * number of channels
    nsegments = int(nsamples / seglen)  # number of segments

    A = reshape(A, nchan, nchan, :)
    C = zeros(nchan, nchan)

    @inbounds for e = 1:nsegments
        S1 = (e-1)*seglen+1:e*seglen
        S2 = order+1:seglen
        @views X = data[S1, :][S2, :]'
        for i = 1:order
            @views X -= A[:, :, i] * data[S1, :][S2.-i, :]'
        end
        C += X * X'
    end

    C /= nsegments * (seglen - order)

    return C
end

"""
	granger_jackknife(data, seglen, order, Covs12, Covs1, Covs2)

Jackknife sampling method for estimating the error standard deviation

### Arguments

  - `data::AbstractArray`: Nx2 array for N data points in 2 channels.
  - `order::Int`: Model order. Assumed time delay order of interest.
  - `seglen::Integer`: segment length.
  - `Covs12::Array{Float64, 2}`: Concatenated COvariances of 0 to the given order
  - `Covs1::Array{Float64, 2}`: Channel 1 Concatenated variances of 0 to the given order
  - `Covs2::Array{Float64, 2}`: Channel 2 Concatenated variances of 0 to the given order

### Returns

  - `::Float64`: estimated standard deviation of error
"""
function granger_jackknife(
    data::Array{Float64,2},
    seglen::Int,
    order::Int,
    Covs12::AbstractArray{Float64,2},
    Covs1::AbstractArray{Float64,2},
    Covs2::AbstractArray{Float64,2},
)

    nsamples = size(data, 1)  # number of samples
    nsegments = int(nsamples / seglen)  # number of segments
    trials = zeros(nsegments, 1)

    Threads.@threads for i = 1:nsegments
        Segment = view(data, (i-1)*seglen+1:i*seglen, :)

        Covs12_ = est_sig_covs(Segment, order)
        Covs1_ = est_sig_covs(review(Segment, 1), order)
        Covs2_ = est_sig_covs(review(Segment, 2), order)

        Covs12_s = (nsegments * Covs12 - Covs12_) / (nsegments - 1)
        Covs1_s = (nsegments * Covs1 - Covs1_) / (nsegments - 1)
        Covs2_s = (nsegments * Covs2 - Covs2_) / (nsegments - 1)

        Acoef12_s, _ = mvar_est(Covs12_s)
        Acoef1_s, _ = mvar_est(Covs1_s)
        Acoef2_s, _ = mvar_est(Covs2_s)

        SegmentC =
            vcat(view(data, 1:(i-1)*seglen, :), view(data, (i+1)*seglen.+1:nsamples, :))

        Σ12_s = est_noise_covs(SegmentC, Acoef12_s, seglen)
        Σ1_s = est_noise_covs(review(SegmentC, 1), Acoef1_s, seglen)
        Γ1_s = est_noise_covs(review(SegmentC, 2), Acoef2_s, seglen)
        Σ2_s = Σ12_s[1, 1]
        Γ2_s = Σ12_s[2, 2]

        X_to_Y_s = log(Γ1_s / Γ2_s)
        Y_to_X_s = log(Σ1_s / Σ2_s)

        trials[i, :] = X_to_Y_s - Y_to_X_s

    end
    return sqrt(nsegments) * std(trials)
end

"""
	granger_aic(data, seglen, order_range)

	Akaike Information Criterion
"""
function granger_aic(data::Array{Float64,2}, seglen::Int, order_range::UnitRange{Int64})

    nchan = size(data, 2)
    aic_range = Array{Float64}(undef, size(order_range))

    for (i, order) in enumerate(order_range)
        Covs = est_sig_covs(data, order)
        Acoef, _ = mvar_est(Covs)
        Σ = est_noise_covs(data, Acoef, seglen)
        aic_range[i] = 2 * log(det(Σ)) + (2 * order * nchan^2 / seglen)
    end
    return aic_range
end

"""
	granger_bic(data, seglen, order_range)

Bayesian Information Criterion
"""
function granger_bic(data::Array{Float64,2}, seglen::Int, order_range::UnitRange{Int64})

    nchan = size(data, 2)
    bic_range = Array{Float64}(undef, size(order_range))

    for (i, order) in enumerate(order_range)
        Covs = est_sig_covs(data, order)
        Acoef, _ = mvar_est(Covs)
        Σ = est_noise_covs(data, Acoef, seglen)
        bic_range[i] = 2 * log(det(Σ)) + (2 * order * nchan^2 * log(seglen) / seglen)
    end
    return bic_range
end


import Pkg; Pkg.add("Distributions"); Pkg.add("Plots"); Pkg.add("MLDatasets"); Pkg.add("CSV")

using CSV
using Plots
using Random, Distributions
using MLDatasets

# the implementation is based on http://www.jmlr.org/papers/volume9/vandermaaten08a/vandermaaten08a.pdf
# and uses https://raw.githubusercontent.com/danaugrs/go-tsne/master/tsne/tsne.go as a basis
# with input params borrowed from https://scikit-learn.org/stable/modules/generated/sklearn.manifold.TSNE.html

# setting up constants that we'll need
epsilon = 10^-7 # minimum gradient norm (if smaller optimization is stopped)
fake_zero = 10^-12 # can't really have 0 for some of the calculations - making a mumber really close to it
tol = 10^-5 # enthropy tolerance
max_binary = 50 # max number of steps that the binary search gets to do
init_stdev = 10^-4 # the stdev that we're initialising Y with

# and init parameters that we'll be working with
m_out = 2 # the number of dimensions we want to get as an output
T = 1000 # the number of iterations

perp = 50 # in a sense, a guess about the number of close neighbors each point has
l_rate = 300 # the learning rate

# loading our dataset
csv_data = CSV.read("tsne-julia/abalone.csv", header=false)
X = convert(Matrix, csv_data[:, 2:9])
n, m = size(X)
cols = convert(Matrix, csv_data[:, 1:1])
unique_cols = unique(cols)
colmap = ["#e6194b", "#3cb44b", "#ffe119", "#4363d8", "#f58231", "#911eb4", "#46f0f0", "#f032e6", "#bcf60c", "#fabebe", "#008080", "#e6beff", "#9a6324", "#fffac8", "#800000", "#aaffc3", "#808000", "#ffd8b1", "#000075", "#808080", "#ffffff", "#000000"]

# calculates the squared euclidean distance between two lists of the same length
function squared_euclidean(p, q, m)
  # remember that the lists in julia are 1 based
  # sicne the distance is a square root of the sum of squares we can just omit the sqrt
  # (since we're interested in the squared distance)
  return sum([(p[i]-q[i])^2 for i in 1:m])
end

# calculates a squared distance matrix for row vectors in X
function squared_distances(X, n, m)
  dists = zeros(n, n)
  for i in 1:n
    for j in (i+1):n # skip the diagonal as well
      tmp_dist = squared_euclidean(X[i,:], X[j,:], m) # and reuse the value for the pair
      dists[i, j] = tmp_dist
      dists[j, i] = tmp_dist
    end
  end
  return dists
end

function similarities(D, m, n, tol, perp, epsilon)
  # the target entropy of the gaussian kernels is the log of the target perplexity
  h_target = log(perp)

  # the similarity matrix that we're going to populate
  P = zeros(n, n)

  # work through all of the rows
  for i in 1:n
    # perform a binary search for the Gaussian kernel precision (beta)
    # such that the entropy (and thus the perplexity) of the distribution
    # given the data is the same for all data points
    beta_min = -Inf
    beta_max = Inf
    beta = 1 # initial value of precision

    for j in 1:max_binary
      # compute raw probabilities with beta precision (along with sum of all raw probabilities)
      p_sum = Float64(0)
      for k in 1:m
        p = 0
        if i != k
          p = exp(-D[i, k]*beta)
        end

        P[i, k] = p # note that we're not updating the whole thing (can't really do shorthand as a result)
        p_sum += p
      end

      # normalize probabilities and compute entropy H
      h = Float64(0) # distribution entropy
      for k in 1:m
        p = 0
        if p_sum != 0
          p = P[i, k]/p_sum
        end

        P[i, k] = p
        if p > epsilon
          h -= p*log(p)
        end
      end

      # adjust beta to move H closer to Htarget
      h_diff = h-h_target
      if h_diff > 0
        # entropy is too high (distribution too spread-out)
        # so we need to increase the precision
        beta_min = beta # move up the bounds
        if beta_max == Inf
          beta *= 2
        else
          beta = (beta+beta_max)/2
        end
      else
        # entropy is too low - need to decrease precision
        beta_max = beta # move down the bounds
        if beta_min == -Inf
          beta /= 2
        else
          beta = (beta+beta_min)/2
        end
      end

      # if current entropy is within specified tolerance - we are done with this data point
      if abs(h_diff) < tol
        break
      end
    end
  end

  return P
end

function cost_gradient(P, Y, dCdY, n, m_out, P_log_P, fake_zero)
  # q i|j (1+dist^2)^-1 / sum(i≠j, (1+dist^2)^-1)
  Y_dist = squared_distances(Y, n, m_out)
  # set up the top part of that calculation
  Q_top = zeros(n, n)
  for i in 1:n
    for j in 1:n
      if i!=j
        Q_top[i, j] = 1/(1+Y_dist[i, j])
      end
    end
  end
  # get the bottom bit of that function
  sum_q = sum(sum(Q_top))
  # build up the q i|j set
  Q = zeros(n, n)
  for i in 1:n
    for j in 1:n
      Q[i, j] = max(Q_top[i, j]/sum_q, fake_zero)
    end
  end
  # the non-constant portion of the divergence
  Q_log = zeros(n, n)
  for i in 1:n
    for j in 1:n
      Q_log[i, j] = log(Q[i, j])
    end
  end
  # compute the Kullback-Leiber divergence
  P_log_Q = sum(sum(P*Q_log))
  # work out the gradient of the Kullback-Leibler divergence between P and the Student-t based joint probability distribution Q
  mult = P-Q
  mult = mult.*4
  mult = mult*Y_dist
  for r in 1:n
    for c in 1:n
      m = mult[r, c]
      for k in 1:m_out
        y_diff = Y[r, k] - Y[c, k]
        dCdY[r, k] += m*y_diff
      end
    end
  end
  return dCdY
  #return [4*sum([(P[i][j]-Q[i][j])*(Y[i]-Y[j])/(1+Y_dist[i][j]) for j in 1:n]) for i in 1:n]
end

println("Prepping")
# compute pairwise affinities p i|j with perplexity Perp (using Equation 1)
D = squared_distances(X, n, m)
println("Distances calculated")
P = similarities(D, m, n, tol, perp, epsilon)
println("Similarities calculated")
# set p ij = (p j|i + p i|j)/2n
P = zeros(n, n)
for i in 1:n
  for j in 1:m_out
    P[i, j] = max((P[j, i]+P[i, j])/(2*n), fake_zero)
  end
end
println("Similarities adjusted")
# sample initial solution Y(0)={y1, y2, ..., yn} for N(0,10^-4*I)
init_vals = rand(Normal(0, init_stdev), m_out*n)
Y = zeros(n, m_out)
for i in 1:n
  for j in 1:m_out
    Y[i, j] = init_vals[m_out*i-j+1]
  end
end
dCdY = zeros(n, m_out)
println("Y initialised")
# compute and store the constant portion of the KL divergence
P_log_P = zeros(n, n)
for i in 1:n
  for j in 1:n
    P_log_P[i, j] = log(P[i, j])*P[i, j]
  end
end
P_log_P = sum(sum(P_log_P))
# for t=1 to T do
#
#   where T is the number of epochs that we're looking to work through
println("Training")
for t in 1:T
  global P, Y, n, m_out, P_log_P, fake_zero, cols, momentum, dCdY
  # compute the gradient matrix
  dCdY = cost_gradient(P, Y, dCdY, n, m_out, P_log_P, fake_zero)
  # apply scaled gradient
  println(Y[1:5, :])
  Y -= dCdY.*l_rate
  # reproject Y to have zero mean
  Y_mean = zeros(m_out)
  for i in 1:n
    for j in 1:m_out
      Y_mean[j] += Y[i, j]
    end
  end

  for i in 1:n
    for j in 1:m_out
      Y[i, j] -= (Y_mean[j]/n)
    end
  end
  println(Y[1:5, :])
  println(t)
  if t%10 == 0 # keeping myself from getting swamped in charts
    indices = [i for i in 1:n if cols[i]==unique_cols[1]]
    p = scatter(Y[:, 1][indices], Y[:, 2][indices], markercolor=colmap[1])
    for k = 2:length(unique_cols)
      indices = [i for i in 1:n if cols[i]==unique_cols[k]]
      scatter!(Y[:, 1][indices], Y[:, 2][indices], markercolor=colmap[k])
    end
    display(p)
  end
end


using ElectronicStructure
using ElectronicStructure.LagrangeFunction

include("init_pot_specials.jl")

function test_main( ; method = "Emin_cg" )
  # LF parameters
  NN = [25, 25, 25]
  AA = [0.0, 0.0, 0.0]
  BB = [6.0, 6.0, 6.0]

  Npoints = prod(NN)

  # Initialize LF
  LF = init_LF3d_p( NN, AA, BB )
  ΔV = LF.LFx.h * LF.LFy.h * LF.LFz.h

  # Initialize G-vectors
  L = BB - AA
  Gv = GvectorsT( NN, diagm(L) )

  # Parameter for potential
  center = AA + 0.5*(BB-AA)
  # Potential
  ω = 2.0
  V_ionic = init_pot_harm_3d( LF, ω, center )

  Ncols = 4
  Focc = 2.0*ones(Ncols)

  if method == "Emin_cg_sparse"
    #
    #∇2 = get_Laplacian3d_kron(LF)
    ∇2 = build_nabla2(LF)
    precH = prec_mkl_ilu0( -0.5*∇2 + spdiagm(V_ionic) )
    #precH = speye(Npoints)  # for testing unpreconditioned code
    Energies, evecs, Potentials = KS_solve_Emin_pcg( LF, Gv, ∇2, precH,
                                    V_ionic, Focc, Ncols, verbose=true )
    evals = calc_evals( LF, ∇2, Potentials, evecs )
    #
  elseif method == "SCF"
    ∇2 = get_Laplacian3d_kron(LF)
    precH = prec_mkl_ilu0( -0.5*∇2 + spdiagm(V_ionic) )
    #precH = speye(Npoints)
    Energies, evals, evecs, Potentials =
          KS_solve_scf( LF, Gv, ∇2, precH, V_ionic, Focc, Ncols, verbose=true )
    #
  else
    #
    Energies, evecs, Potentials = KS_solve_Emin_cg( LF, Gv, V_ionic, Focc, Ncols, verbose=true )
    evals = calc_evals( LF, Potentials, evecs )
  end

  @printf("\nEigenvalues:\n")
  for i = 1:Ncols
    @printf("%8d %f\n", i, evals[i])
  end

  rho = calc_rho( Focc, evecs )
  @printf( "\nintegRho = %lf\n", sum(rho)*ΔV )

end

@code_native test_main()
@time test_main(method="Emin_cg_sparse")
#@time test_main(method="Emin_cg")
#@time test_main(method="SCF")


@everywhere function fit_ising(T; L=7, nconfs=10_000)
    L2 = L^2
    # * Allocate space for the dataset
    # confs = Array{Float64}(undef, L, L, nconfs)
    c = Matrix{Int32}(undef, L, L)
    confs = Matrix{Int32}[]

    # * Load the datasets from their files
    @inbounds for i in 1:nconfs
        fileising = @sprintf "ising_%.2f_%d.jld2" T i
        JLD2.@load joinpath("data", fileising) c
        push!(confs, copy(c))
    end

    # * Change -1 to zeros
    broadcast!(x -> x == -1 ? 0 : 1, confs, confs)

    # * Flatten the datasets, make them a 1-D array
    newconfs = reshape(confs, (L2, nconfs))

    # for i = axes(newconfs, 2)
    #     for j in eachindex(newconfs[:, i])
    #         if newconfs[j, i] == -1
    #             newconfs[j, i] = 0
    #         end
    #     end
    # end

    # * Train the RBM for the corresponding temperature
    rbm = BernoulliRBM(L2, 32)
    fit(rbm, newconfs, n_epochs = 1000, batch_size = 50, randomize = true, lr = 0.01)

    # * Serialize and save the model
    model_name = @sprintf "model_%.2f.jld2" T
    JLD2.@save joinpath("models", model_name) rbm
end