module TSMLTransformers

using MLDataUtils
using Dates
using DataFrames
using Statistics
using Random
using CSV

export fit!,transform!

export Transformer,TSLearner
export Imputer,Pipeline,SKLLearner,OneHotEncoder,Wrapper

export Matrifier,Dateifier
export DateValizer,DateValgator,DateValNNer
export CSVDateValReader, CSVDateValWriter

using TSML.TSMLTypes
import TSML.TSMLTypes.fit! # to overload
import TSML.TSMLTypes.transform! # to overload
using TSML.Utils

include("valdate.jl")

# Transforms instances with nominal features into one-hot form
# and coerces the instance matrix to be of element type Float64.
mutable struct OneHotEncoder <: Transformer
  model
  args

  function OneHotEncoder(args=Dict())
    default_args = Dict(
      # Nominal columns
      :nominal_columns => nothing,
      # Nominal column values map. Key is column index, value is list of
      # possible values for that column.
      :nominal_column_values_map => nothing
    )
    new(nothing, mergedict(default_args, args))
  end
end

function fit!(ohe::OneHotEncoder, features::T, labels::Vector) where {T<:Union{Vector,Matrix,DataFrame}}
  instances=convert(Matrix,features)
  # Obtain nominal columns
  nominal_columns = ohe.args[:nominal_columns]
  if nominal_columns == nothing
    nominal_columns = find_nominal_columns(instances)
  end

  # Obtain unique values for each nominal column
  nominal_column_values_map = ohe.args[:nominal_column_values_map]
  if nominal_column_values_map == nothing
    nominal_column_values_map = Dict{Int, Any}()
    for column in nominal_columns
      nominal_column_values_map[column] = unique(instances[:, column])
    end
  end

  # Create model
  ohe.model = Dict(
    :nominal_columns => nominal_columns,
    :nominal_column_values_map => nominal_column_values_map
  )
end

function transform!(ohe::OneHotEncoder, features::T) where {T<:Union{Vector,Matrix,DataFrame}}
  instances=convert(Matrix,features)
  nominal_columns = ohe.model[:nominal_columns]
  nominal_column_values_map = ohe.model[:nominal_column_values_map]

  # Create new transformed instance matrix of type Float64
  num_rows = size(instances, 1)
  num_columns = (size(instances, 2) - length(nominal_columns))
  if !isempty(nominal_column_values_map)
    num_columns += sum(map(x -> length(x), values(nominal_column_values_map)))
  end
  transformed_instances = zeros(Float64, num_rows, num_columns)

  # Fill transformed instance matrix
  col_start_index = 1
  for column in 1:size(instances, 2)
    if !in(column, nominal_columns)
      transformed_instances[:, col_start_index] = instances[:, column]
      col_start_index += 1
    else
      col_values = nominal_column_values_map[column]
      for row in 1:size(instances, 1)
        entry_value = instances[row, column]
        entry_value_index = findfirst(isequal(entry_value),col_values)
        if entry_value_index == 0
          warn("Unseen value found in OneHotEncoder,
                for entry ($row, $column) = $(entry_value).
                Patching value to $(col_values[1]).")
          entry_value_index = 1
        end
        entry_column = (col_start_index - 1) + entry_value_index
        transformed_instances[row, entry_column] = 1
      end
      col_start_index += length(nominal_column_values_map[column])
    end
  end

  return transformed_instances
end

# Finds all nominal columns.
#
# Nominal columns are those that do not have Real type nor
# do all their elements correspond to Real.
function find_nominal_columns(features::T) where {T<:Union{Vector,Matrix,DataFrame}}
  instances=convert(Matrix,features)
  nominal_columns = Int[]
  for column in 1:size(instances, 2)
    col_eltype = infer_eltype(instances[:, column])
    if !<:(col_eltype, Real)
      push!(nominal_columns, column)
    end
  end
  return nominal_columns
end

# Imputes NaN values from Float64 features.
mutable struct Imputer <: Transformer
  model
  args

  function Imputer(args=Dict())
    default_args = Dict(
      # Imputation strategy.
      # Statistic that takes a vector such as mean or median.
      :strategy => mean
    )
    new(nothing, mergedict(default_args, args))
  end
end

function fit!(imp::Imputer, instances::T, labels::Vector) where {T<:Union{Vector,Matrix,DataFrame}}
  imp.model = imp.args
end

function transform!(imp::Imputer, features::T)  where {T<:Union{Vector,Matrix,DataFrame}}
  instances=convert(Matrix,features)
  new_instances = copy(instances)
  strategy = imp.model[:strategy]

  for column in 1:size(instances, 2)
    column_values = instances[:, column]
    col_eltype = infer_eltype(column_values)

    if <:(col_eltype, Real)
      na_rows = map(x -> isnan(x), column_values)
      if any(na_rows)
        fill_value = strategy(column_values[.!na_rows])
        new_instances[na_rows, column] .= fill_value
      end
    end
  end

  return new_instances
end

# Chains multiple transformers in sequence.
mutable struct Pipeline <: Transformer
  model
  args

  function Pipeline(args=Dict())
    default_args = Dict(
      # Transformers as list to chain in sequence.
      :transformers => [OneHotEncoder(), Imputer()],
      # Transformer args as list applied to same index transformer.
      :transformer_args => nothing
    )
    new(nothing, mergedict(default_args, args))
  end
end

function fit!(pipe::Pipeline, features::T=[], labels::Vector=[]) where {T<:Union{Vector,Matrix,DataFrame}}
  #instances=convert(Matrix,features)
  instances=copy(features)
  transformers = pipe.args[:transformers]
  transformer_args = pipe.args[:transformer_args]

  current_instances = instances
  new_transformers = Transformer[]
  for t_index in 1:length(transformers)
    transformer = createtransformer(transformers[t_index], transformer_args)
    push!(new_transformers, transformer)
    fit!(transformer, current_instances, labels)
    current_instances = transform!(transformer, current_instances)
  end

  pipe.model = Dict(
      :transformers => new_transformers,
      :transformer_args => transformer_args
  )
end

function transform!(pipe::Pipeline, features::T=[]) where {T<:Union{Vector,Matrix,DataFrame}}
  #instances = convert(Matrix,features)
  instances = copy(features)
  transformers = pipe.model[:transformers]

  current_instances = instances
  for t_index in 1:length(transformers)
    transformer = transformers[t_index]
    current_instances = transform!(transformer, current_instances)
  end

  return current_instances
end

# Wraps around an CombineML transformer.
mutable struct Wrapper <: Transformer
  model
  args

  function Wrapper(args=Dict())
    default_args = Dict(
      # Transformer to call.
      :transformer => OneHotEncoder(),
      # Transformer args.
      :transformer_args => nothing
    )
    new(nothing, mergedict(default_args, args))
  end
end

function fit!(wrapper::Wrapper, features::T, labels::Vector) where {T<:Union{Vector,Matrix,DataFrame}}
  instances=convert(Matrix,features)
  transformer_args = wrapper.args[:transformer_args]
  transformer = createtransformer(
    wrapper.args[:transformer],
    transformer_args
  )

  if transformer_args != nothing
    transformer_args = mergedict(transformer.args, transformer_args)
  end
  fit!(transformer, instances, labels)

  wrapper.model = Dict(
    :transformer => transformer,
    :transformer_args => transformer_args
  )
end

function transform!(wrapper::Wrapper, instances::T) where {T<:Union{Vector,Matrix,DataFrame}}
  transformer = wrapper.model[:transformer]
  return transform!(transformer, instances)
end

# Create transformer
#
# @param prototype Prototype transformer to base new transformer on.
# @param options Additional options to override prototype's options.
# @return New transformer.
function createtransformer(prototype::Transformer, args=nothing)
  new_args = copy(prototype.args)
  if args != nothing
    new_args = mergedict(new_args, args)
  end

  prototype_type = typeof(prototype)
  return prototype_type(new_args)
end

end


using Mimi                                          #Make sure to call Mimi again

@defcomp emissions begin
    regions     = Index()                           #The regions index must be specified for each component

    E           = Variable(index=[time, regions])   #Total greenhouse gas emissions
    E_Global    = Variable(index=[time])            #Global emissions (sum of regional emissions)
    sigma       = Parameter(index=[time, regions])  #Emissions output ratio
    YGROSS      = Parameter(index=[time, regions])  #Gross output - Note that YGROSS is now a parameter
end

function timestep(state::emissions, t::Int)
    v = state.Variables
    p = state.Parameters
    d = state.Dimensions

    #Define an eqation for E
    for r in d.regions
        v.E[t,r] = p.YGROSS[t,r] * p.sigma[t,r]
    end

    #Define an equation for E_Global
    for r in d.regions
        v.E_Global[t] = sum(v.E[t,:])
    end
end


# global packages -
using DifferentialEquations
using DiffEqFinancial
using Statistics
using Distributions
using KernelDensity
using Random
using Dates
using DataFrames
using Reexport
@reexport using PooksoftBase

# include my code ...
include("./base/Types.jl")
include("./base/Compute.jl")
include("./base/Utility.jl")
include("./base/Return.jl")
include("./base/Factory.jl")
include("./base/Technical.jl")
include("./base/Lattice.jl")


#############################################################################
# dcp.jl
# This file handles the basic rules on interactions of mathematical expressions
# to create new expressions.
#
# For example: negative of a concaveVexity expression is convexVexity, or multiplication
# of two positive expressions continue to be positive.
#
# See: http://dcp.stanford.edu/rules or the original paper at
# http://web.stanford.edu/~boyd/papers/disc_cvx_prog.html
#############################################################################

import Base.-, Base.+, Base.*, Base./

# Vexity subtypes
abstract type Vexity end
struct ConstVexity <: Vexity              end
struct AffineVexity <: Vexity             end
struct ConvexVexity <: Vexity             end
struct ConcaveVexity <: Vexity            end

struct NotDcp <: Vexity
    function NotDcp()
        if emit_dcp_warnings()
            @warn "Expression not DCP compliant. Trying to solve non-DCP compliant problems can lead to unexpected behavior."
        end
        return new()
    end
end

# Monotonocity subtypes
abstract type Monotonicity end
struct Nonincreasing <: Monotonicity      end
struct Nondecreasing <: Monotonicity      end
struct ConstMonotonicity <: Monotonicity  end
struct NoMonotonicity <: Monotonicity     end

# Sign subtypes
abstract type Sign end
struct Positive <: Sign                   end
struct Negative <: Sign                   end
struct NoSign <: Sign                     end

# New coded

# Also create a new subtype of Sign "NotDefined to handle the ComplexSign case"
struct ComplexSign <: Sign                end

-(v::Vexity) = v
-(v::ConcaveVexity) = ConvexVexity()
-(v::ConvexVexity) = ConcaveVexity()

-(m::Monotonicity) = m
-(m::Nonincreasing) = Nondecreasing()
-(m::Nondecreasing) = Nonincreasing()

-(s::Sign) = s
-(s::Positive) = Negative()
-(s::Negative) = Positive()
-(s::ComplexSign) = ComplexSign()

+(v::NotDcp, w::NotDcp) = v
+(v::NotDcp, w::Vexity) = v
+(v::Vexity, w::NotDcp) = w

+(v::ConstVexity, w::ConstVexity) = v
+(v::ConstVexity, w::NotDcp) = w
+(v::NotDcp, w::ConstVexity) = v
+(v::ConstVexity, w::Vexity) = w
+(v::Vexity, w::ConstVexity) = v

+(v::AffineVexity, w::AffineVexity) = v
+(v::AffineVexity, w::ConvexVexity) = w
+(v::ConvexVexity, w::AffineVexity) = v
+(v::AffineVexity, w::ConcaveVexity) = w
+(v::ConcaveVexity, w::AffineVexity) = v

+(v::ConvexVexity, w::ConvexVexity) = v
+(v::ConcaveVexity, w::ConcaveVexity) = v
+(v::ConcaveVexity, w::ConvexVexity) = NotDcp()
+(v::ConvexVexity, w::ConcaveVexity) = NotDcp()

#+(::Convex.Positive, ::Convex.NoSign)
+(s::Sign) = s
+(s::Positive, t::Positive) = s
+(s::Negative, t::Negative) = s
+(s::Positive, t::Negative) = NoSign()
+(s::Negative, t::Positive) = NoSign()
+(s::NoSign, t::NoSign) = s
+(s::NoSign, t::Positive) = s
+(t::Positive, s::NoSign) = s+t
+(s::NoSign, t::Negative) = s
+(t::Negative, s::NoSign) = s+t

# Any sign + ComplexSign = ComplexSign
+(s::ComplexSign) = s
+(s::ComplexSign, t::ComplexSign) = s
+(s::Sign, t::ComplexSign) = t
+(t::ComplexSign, s::Sign) = s+t

*(s::NoSign, t::NoSign) = s
*(s::NoSign, t::Positive) = s
*(s::Positive, t::NoSign) = t
*(s::NoSign, t::Negative) = s
*(s::Negative, t::NoSign) = t
*(s::Positive, t::Positive) = s
*(s::Positive, t::Negative) = t
*(s::Negative, t::Positive) = s
*(s::Negative, t::Negative) = Positive()

# ComplexSign * Any Sign = NotDefined(Though ComplexSign and its conjugate is real but we ignore that case)
*(t::ComplexSign, s::ComplexSign) = t
*(t::ComplexSign, s::Sign) = t
*(s::Sign, t::ComplexSign) = t

*(s::Positive, m::Monotonicity) = m
*(s::Negative, m::Monotonicity) = -m
*(s::NoSign, m::Monotonicity) = NoMonotonicity()

# ComplexSign * Any monotonivity = NoMonotonicity
*(s::ComplexSign, m::Monotonicity) = NoMonotonicity()
*(m::Monotonicity, s::Sign) = s * m

*(m::Nondecreasing, v::Vexity) = v
*(m::Nonincreasing, v::Vexity) = -v
*(m::NoMonotonicity, v::Vexity) = v
*(m::NoMonotonicity, v::ConvexVexity) = NotDcp()
*(m::NoMonotonicity, v::ConcaveVexity) = NotDcp()

# ComplexSign * Affine = Affine
# ComplexSign * Concave = NotDcp
# ComplexSign * NotDcp = NotDcp
# ComplexSign * NotDcp = NotDcp
*(s::ComplexSign, v::ConstVexity) = v
*(s::ComplexSign, v::AffineVexity) = v
*(s::ComplexSign, v::ConvexVexity) = NotDcp()
*(s::ComplexSign, v::ConcaveVexity) = NotDcp()
*(s::ComplexSign, v::NotDcp) = v
*(v::Vexity, s::ComplexSign) = s*v


# Adapted from:
# Example 3.99 of
# Blekherman, G.; Parrilo, P. & Thomas, R.
# Semidefinite Optimization and Convex Algebraic Geometry
# Society for Industrial and Applied Mathematics, 2012

@testset "[BPT12] Example 3.99 with $(factory.constructor)" for factory in sdp_factories
    @polyvar x y

    m = SOSModel(factory)

    @variable m α

    @constraint(m, x^2 + α*y <= 10, domain = @set x^2 + y^2 == 1)

    @objective m Max α

    JuMP.optimize!(m)

    @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT

    @test JuMP.value(α) ≈ 6 atol=1e-6

    # FIXME JuMP does not support modification once loaded on jump/moi yet
#    @objective m Min α
#
#    JuMP.optimize!(m)
#
#    @test JuMP.primal_status(m) == MOI.FEASIBLE_POINT
#
#    @test JuMP.value(α) ≈ -6 atol=1e-6
end


@recipe function f(mds::MDS{<:Real}; mds_axes=(1,2))
    length(mds_axes) in [2,3] || throw(ArgumentError("Can only accept 2 or 3 mds axes"))
    xax = mds_axes[1]
    yax = mds_axes[2]
    ev = eigvals(mds)
    var_explained = [v / sum(ev) for v in ev]
    tfm = collect(transform(mds)')

    xlabel --> "MDS$xax"
    ylabel --> "MDS$yax"
    seriestype := :scatter
    aspect_ratio --> 1

    if length(mds_axes) == 3
        zax = mds_axes[3]
        zlabel --> "MDS$zax"
        tfm[:,xax], tfm[:,yax], tfm[:,zax]
    else
        tfm[:,xax], tfm[:,yax]
    end
end

#= This needs to wait on a different PCA API in MultivariateStats.jl
@recipe function f(pca::PCA{<:Real}; pca_axes=(1,2))
end
=#


using GeometricMultigrid
using GeometricMultigrid: multL, multU, mult, increment!, δ

# Classical smoothers
@fastmath function GS!(st;inner=3,kw...)
    @inbounds for I ∈ st.iD.R  # optimized first iteration
        st.ϵ[I] = st.iD[I]*(st.r[I]-multL(I,st.A.L,st.ϵ))
    end
    @inbounds for _ ∈ 2:inner, I ∈ st.iD.R
        st.ϵ[I] = st.iD[I]*(st.r[I]-multL(I,st.A.L,st.ϵ)-multU(I,st.A.L,st.ϵ))
    end
    increment!(st;kw...)
end
@fastmath function SOR!(st;inner=3,ω=0.9,kw...)
    @inbounds for I ∈ st.iD.R  # optimized first iteration
        st.ϵ[I] = ω*st.iD[I]*(st.r[I]-multL(I,st.A.L,st.ϵ))
     end
     @inbounds for _ ∈ 2:inner, I ∈ st.iD.R
       st.ϵ[I] = (1-ω)*st.ϵ[I]+ω*st.iD[I]*(st.r[I]-multL(I,st.A.L,st.ϵ)-multU(I,st.A.L,st.ϵ))
    end
    increment!(st;kw...)
end
@fastmath Jacobi!(st;inner=1,kw...) = for _ ∈ 1:inner
    @loop st.ϵ[I] = st.r[I]*st.iD[I]
    increment!(st;kw...)
end

# Create parameterized MG state
function state(A,x,b;p=p₀,xT::Type=eltype(p),smooth! =GS!,damp=false,kw...)
    y = zero(x,xT)     # make FieldVector of type xT
    @loop y[I] = x[I]  # copy values
    err = sum(b)/length(b)
    @loop b[I] -= err
    st = mg_state(A,y,b)
    smooth! == pseudo! && fill_pseudo!(st;p,kw...)
    damp && adjust_iD!(st;p)
    st
end
state(A,b;kw...) = state(A,zero(b),b;kw...)
fill_pseudo!(st;kw...) = !isnothing(st.child) && (st.P=PseudoInv(st.A;kw...); fill_pseudo!(st.child;kw...))
adjust_iD!(st;p) = !isnothing(st.child) && (@loop st.iD[I] *= p[end]; adjust_iD!(st.child;p))

# Apply smoother
@inline pseudo!(st;kw...) = pseudo!(st,st.P;kw...)
@fastmath pseudo!(st,P::FieldMatrix;inner=2,kw...) = for _=1:inner
    @loop st.ϵ[I] = mult(I,P.L,P.D,st.r)
    increment!(st)
end

# Parameterized approximate inverse matrix P
p₀ = Float32[-0.104447, -0.00238399, 0.00841367, -0.158046, -0.115103] # result from tune_synthetic
function PseudoInv(A::FieldMatrix; scale=maximum(A.L),p::AbstractVector{T}=p₀,
    pmodels=p->(D->1+p[1]+D*(p[2]+D*p[3]),L->L*(p[4]*(L-2)+p[5]*(L-1))),kw...) where T

    L,D,N = zeros(T,size(A.L)),zeros(T,size(A.D)),length(size(A.D))
    Dm,Lm = pmodels(p)
    for I ∈ A.R
        invD = (abs(A.D[I])<1e-8) ? 0. : inv(A.D[I])
        D[I] = invD*Dm(A.D[I]/scale)
        for i ∈ 1:N
            J = I-δ(i,N)
            invD = (abs(A.D[I]+A.D[J])<2e-8) ? 0. : 2inv(A.D[I]+A.D[J])
            L[I,i] = invD*Lm(A.L[I,i]/scale)
        end
    end
    FieldMatrix(L,D,A.R)
end


"""
    SymTriangularDist(μ,σ)

The *Symmetric triangular distribution* with location `μ` and scale `σ` has probability density function

```math
f(x; \\mu, \\sigma) = \\frac{1}{\\sigma} \\left( 1 - \\left| \\frac{x - \\mu}{\\sigma} \\right| \\right), \\quad \\mu - \\sigma \\le x \\le \\mu + \\sigma
```

```julia
SymTriangularDist()         # Symmetric triangular distribution with zero location and unit scale
SymTriangularDist(u)        # Symmetric triangular distribution with location μ and unit scale
SymTriangularDist(u, s)     # Symmetric triangular distribution with location μ and scale σ

params(d)       # Get the parameters, i.e. (μ, σ)
location(d)     # Get the location parameter, i.e. μ
scale(d)        # Get the scale parameter, i.e. σ
```
"""
struct SymTriangularDist{T<:Real} <: ContinuousUnivariateDistribution
    μ::T
    σ::T
    SymTriangularDist{T}(µ::T, σ::T) where {T <: Real} = new{T}(µ, σ)
end

function SymTriangularDist(μ::T, σ::T; check_args=true) where {T <: Real}
    check_args && @check_args(SymTriangularDist, σ > zero(σ))
    return SymTriangularDist{T}(μ, σ)
end

SymTriangularDist(μ::Real, σ::Real) = SymTriangularDist(promote(μ, σ)...)
SymTriangularDist(μ::Integer, σ::Integer) = SymTriangularDist(float(μ), float(σ))
SymTriangularDist(μ::T) where {T <: Real} = SymTriangularDist(μ, one(T))
SymTriangularDist() = SymTriangularDist(0.0, 1.0, check_args=false)

@distr_support SymTriangularDist d.μ - d.σ d.μ + d.σ

#### Conversions

function convert(::Type{SymTriangularDist{T}}, μ::Real, σ::Real) where T<:Real
    SymTriangularDist(T(μ), T(σ))
end
function convert(::Type{SymTriangularDist{T}}, d::SymTriangularDist{S}) where {T <: Real, S <: Real}
    SymTriangularDist(T(d.μ), T(d.σ), check_args=false)
end

#### Parameters

location(d::SymTriangularDist) = d.μ
scale(d::SymTriangularDist) = d.σ

params(d::SymTriangularDist) = (d.μ, d.σ)
@inline partype(d::SymTriangularDist{T}) where {T<:Real} = T

#### Statistics

mean(d::SymTriangularDist) = d.μ
median(d::SymTriangularDist) = d.μ
mode(d::SymTriangularDist) = d.μ

var(d::SymTriangularDist) = d.σ^2 / 6
skewness(d::SymTriangularDist{T}) where {T<:Real} = zero(T)
kurtosis(d::SymTriangularDist{T}) where {T<:Real} = T(-3)/5

entropy(d::SymTriangularDist) = 1//2 + log(d.σ)

#### Evaluation

zval(d::SymTriangularDist, x::Real) = (x - d.μ) / d.σ
xval(d::SymTriangularDist, z::Real) = d.μ + z * d.σ

pdf(d::SymTriangularDist{T}, x::Real) where {T<:Real} = insupport(d, x) ? (1 - abs(zval(d, x))) / scale(d) : zero(T)

function logpdf(d::SymTriangularDist{T}, x::Real) where T<:Real
    insupport(d, x) ? log((1 - abs(zval(d, x))) / scale(d)) : -convert(T, T(Inf))
end

function cdf(d::SymTriangularDist{T}, x::Real) where T<:Real
    (μ, σ) = params(d)
    x <= μ - σ ? zero(T) :
    x <= μ ? (1 + zval(d, x))^2/2 :
    x < μ + σ ? 1 - (1 - zval(d, x))^2/2 : one(T)
end

function ccdf(d::SymTriangularDist{T}, x::Real) where T<:Real
    (μ, σ) = params(d)
    x <= μ - σ ? one(T) :
    x <= μ ? 1 - (1 + zval(d, x))^2/2 :
    x < μ + σ ? (1 - zval(d, x))^2/2 : zero(T)
end

function logcdf(d::SymTriangularDist{T}, x::Real) where T<:Real
    (μ, σ) = params(d)
    x <= μ - σ ? -T(Inf) :
    x <= μ ? loghalf + 2*log1p(zval(d, x)) :
    x < μ + σ ? log1p(-1/2 * (1 - zval(d, x))^2) : zero(T)
end

function logccdf(d::SymTriangularDist{T}, x::Real) where T<:Real
    (μ, σ) = params(d)
    x <= μ - σ ? zero(T) :
    x <= μ ? log1p(-1/2 * (1 + zval(d, x))^2) :
    x < μ + σ ? loghalf + 2*log1p(-zval(d, x)) : -T(Inf)
end

quantile(d::SymTriangularDist, p::Real) = p < 1/2 ? xval(d, sqrt(2p) - 1) :
                                                       xval(d, 1 - sqrt(2(1 - p)))

cquantile(d::SymTriangularDist, p::Real) = p > 1/2 ? xval(d, sqrt(2(1-p)) - 1) :
                                                        xval(d, 1 - sqrt(2p))

invlogcdf(d::SymTriangularDist, lp::Real) = lp < loghalf ? xval(d, expm1(1/2*(lp - loghalf))) :
                                                              xval(d, 1 - sqrt(-2expm1(lp)))

function invlogccdf(d::SymTriangularDist, lp::Real)
    lp > loghalf ? xval(d, sqrt(-2*expm1(lp)) - 1) :
    xval(d, -(expm1((lp - loghalf)/2)))
end

function mgf(d::SymTriangularDist, t::Real)
    (μ, σ) = params(d)
    a = σ * t
    a == zero(a) && return one(a)
    4*exp(μ * t) * (sinh(a/2) / a)^2
end

function cf(d::SymTriangularDist, t::Real)
    (μ, σ) = params(d)
    a = σ * t
    a == zero(a) && return complex(one(a))
    4*cis(μ * t) * (sin(a/2) / a)^2
end

#### Sampling

rand(rng::AbstractRNG, d::SymTriangularDist) = xval(d, rand(rng) - rand(rng))


using SpecialFunctions

@testset "math" begin
    @testset "log10" begin
        @test testf(a->log10.(a), Float32[100])
    end

    @testset "pow" begin
        for T in (Float16, Float32, Float64, ComplexF32, ComplexF64)
            range = (T<:Integer) ? (T(5):T(10)) : T
            @test testf((x,y)->x.^y, rand(Float32, 1), rand(range, 1))
            @test testf((x,y)->x.^y, rand(Float32, 1), -rand(range, 1))
        end
    end

    @testset "isinf" begin
      for x in (Inf32, Inf, NaN32, NaN)
        @test testf(x->isinf.(x), [x])
      end
    end

    @testset "isnan" begin
      for x in (Inf32, Inf, NaN32, NaN)
        @test testf(x->isnan.(x), [x])
      end
    end

    for op in (exp, angle, exp2, exp10,)
        @testset "$op" begin
            for T in (Float16, Float32, Float64)
                @test testf(x->op.(x), rand(T, 1))
                @test testf(x->op.(x), -rand(T, 1))
            end
        end
    end

    for op in (expm1,)
        @testset "$op" begin
            # FIXME: add expm1(::Float16) to Base
            for T in (Float32, Float64)
                @test testf(x->op.(x), rand(T, 1))
                @test testf(x->op.(x), -rand(T, 1))
            end
        end
    end

    for op in (exp, abs, abs2, angle, log)
        @testset "Complex - $op" begin
            for T in (ComplexF16, ComplexF32, ComplexF64)
                @test testf(x->op.(x), rand(T, 1))
                @test testf(x->op.(x), -rand(T, 1))
            end

        end
    end
    @testset "mod and rem" begin
        for T in (Float16, Float32, Float64)
            @test testf(a->rem.(a, T(2)), T[0, 1, 1.5, 2, -1])
            @test testf(a->rem.(a, T(2), RoundNearest), T[0, 1, 1.5, 2, -1])
            @test testf(a->mod.(a, T(2)), T[0, 1, 1.5, 2, -1])
        end
    end

    @testset "rsqrt" begin
        # GPUCompiler.jl#173: a CUDA-only device function fails to validate
        function kernel(a)
            a[] = CUDA.rsqrt(a[])
            return
        end

        # make sure this test uses an actual device function
        @test_throws ErrorException kernel(ones(1))

        for T in (Float16, Float32)
            a = CuArray{T}([4])
            @cuda kernel(a)
            @test Array(a) == [0.5]
        end
    end

    @testset "fma" begin
        for T in (Float16, Float32, Float64)
            @test testf((x,y,z)->fma.(x,y,z), rand(T, 1), rand(T, 1), rand(T, 1))
            @test testf((x,y,z)->fma.(x,y,z), rand(T, 1), -rand(T, 1), -rand(T, 1))
        end
    end

    # something from SpecialFunctions.jl
    @testset "erf" begin
        @test testf(a->SpecialFunctions.erf.(a), Float32[1.0])
    end

    @testset "exp" begin
        # JuliaGPU/CUDA.jl#1085: exp uses Base.sincos performing a global CPU load
        @test testf(x->exp.(x), [1e7im])
    end
end


module ReferenceFrameRotations

export DCM, EulerAngleAxis, EulerAngles, Quaternion

import Base: +, -, *, /, conj, copy, getindex, inv, imag, real, show
import Base: zeros
import LinearAlgebra: norm

using LinearAlgebra
using StaticArrays

# Re-export `I` from LinearAlgebra.
export I

################################################################################
#                                    Types
################################################################################

"""
The Direction Cosine Matrix of type `T` is a `SMatrix{3,3,T}`, which is a 3x3
static matrix of type `T`.

"""
DCM{T} = SMatrix{3,3,T}

"""
    struct EulerAngles{T}

The definition of Euler Angles, which is composed of three angles `a1`, `a2`,
and `a3` together with a rotation sequence `rot_seq`. The latter is provided by
a symbol with three characters, each one indicating the rotation axis of the
corresponding angle (for example, `:ZYX`). The valid values for `rot_seq` are:

* `:XYX`, `:XYZ`, `:XZX`, `:XZY`, `:YXY`, `:YXZ`, `:YZX`, `:YZY`, `:ZXY`,
  `:ZXZ`, `:ZYX`, and `ZYZ`.

"""
struct EulerAngles{T}
    a1::T
    a2::T
    a3::T
    rot_seq::Symbol
end

"""
    struct EulerAngleAxis{T}

The definition of Euler Angle and Axis to represent a 3D rotation.

# Fields

* `a`: The Euler angle [rad].
* `v`: The unitary vector aligned with the Euler axis.

"""
struct EulerAngleAxis{T}
    a::T
    v::Vector{T}
end

"""
### struct Quaternion{T}

The definition of the quaternion. It has four values of type `T`. The
quaternion representation is:

    q0 + q1.i + q2.j + q3.k

"""
struct Quaternion{T}
    q0::T
    q1::T
    q2::T
    q3::T
end

################################################################################
#                                 Deprecations
################################################################################

@deprecate eye(::Type{Quaternion{T}}) where T Quaternion{T}(I)
@deprecate eye(::Type{Quaternion}) where T Quaternion{Float64}(I)
@deprecate eye(q::Quaternion{T}) where T Quaternion(I,q)

################################################################################
#                                   Includes
################################################################################

include("compose_rotations.jl")
include("DCM.jl")
include("euler_angle_axis.jl")
include("euler_angles.jl")
include("inv_rotations.jl")
include("quaternion.jl")

end # module


"""
    returns the min and max absolute non zero value
"""
function nzabs_range(col::AbstractArray{<:Number})
    nzabs = filter(!iszero, col) .|> abs
    isempty(nzabs) && return (zero(eltype(nzabs)), zero(eltype(nzabs)))
    return (minimum(nzabs), maximum(nzabs))
end

"""
    returns (a, n) so that s ≈ a^n. a ∈ (0, b]
"""
function factorize(s::Real, b::Float64 = 100.0)::Tuple{Float64,Int64}
    iszero(s) && return (0.0, 1)
    n = ceil(Int, abs(log(b, s)))
    a = s^(1/n)
    return (a, n)
end
factorize(s::Real, b::Real) = factorize(s, float(b))

"""
    Compute the future size of the scaled model
"""
compute_well_scaled_size(model::MetNet, b = 100.0) =
    size(model) .+ sum(max.(last.(factorize.(abs.(model.S[model.S .!= 0]), b)) .- 1, 0))

const LIFT_TAG = "LIFT"
function well_scaled_model(model::MetNet, b::Real = 100.0;
        lift_bound::Real = 1e50, verbose = true)

    ## Inferring final size
    eM, eN = compute_well_scaled_size(model, b)
    exp_model = expanded_model(model, eM, eN)

    verbose && (prog = Progress(size(model, 1); desc = "Scaling   ", dt = 0.5))
    for (rxni, rxn) in model.rxns |> enumerate

        metis = rxn_mets(model, rxni)
        mets = model.mets[metis]
        for (meti, met) in zip(metis, mets)

            s = model.S[meti, rxni]
            sign_s = sign(s)
            news, n = factorize(abs(s), b)

            if n > 1
                # We need to lift
                # see (https://doi.org/10.1186/1471-2105-14-240)

                # orig rxn
                exp_model.S[meti, rxni] = 0.0

                # lift met (n - 1)
                lift_met_num = n - 1
                lift_met = string(LIFT_TAG, lift_met_num, "_", met)
                lift_meti = findempty(exp_model, :mets)
                exp_model.mets[lift_meti] = lift_met
                exp_model.S[lift_meti, rxni] = sign_s * news

                # lift rxn chain
                lift_rxn_num = 1
                for i in 1:(n - 2)
                    # new lift rxn
                    lift_rxn = string(rxn, "_", LIFT_TAG, lift_rxn_num)
                    lift_rxni = findempty(exp_model, :rxns)
                    exp_model.rxns[lift_rxni] = lift_rxn
                    exp_model.lb[lift_rxni], exp_model.ub[lift_rxni] = -lift_bound, lift_bound

                    # current lift met
                    exp_model.S[lift_meti, lift_rxni] = -sign_s * 1.0
                    lift_met_num -= 1

                    # new lift met
                    lift_met = string(LIFT_TAG, lift_met_num, "_", met)
                    lift_meti = findempty(exp_model, :mets)
                    exp_model.mets[lift_meti] = lift_met
                    exp_model.S[lift_meti, lift_rxni] = sign_s * news

                    lift_rxn_num += 1
                end

                # last lift rxn (n - 1)
                lift_rxn = string(rxn, "_", LIFT_TAG, lift_rxn_num)
                lift_rxni = findempty(exp_model, :rxns)
                exp_model.rxns[lift_rxni] = lift_rxn

                # s * orig_met --> lift_met1
                exp_model.S[meti, lift_rxni] = sign_s * news # same sign
                exp_model.S[lift_meti, lift_rxni] = -sign_s * 1.0
                exp_model.lb[lift_rxni], exp_model.ub[lift_rxni] = -lift_bound, lift_bound

            end # if n > 1

        end # zip(metis, mets)

        verbose && update!(prog, rxni)
    end
    verbose && finish!(prog)

    return exp_model
end


### A Pluto.jl notebook ###
# v0.18.0

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

# ╔═╡ d7ac6a44-843f-48da-b90d-576658f33d84
begin
	using PlutoUI
	using Plots
end

# ╔═╡ 2c74a643-2db6-4693-bd46-47560b7036fb
using Statistics

# ╔═╡ f7fd08ce-78fb-11ec-2e93-5bcd26561b10
md"""
# 4. 勾配法によるパラメータ推定
## 4.1. パラメータ推定の閉じた式
## 4.2. 閉じた式の問題点
## 4.3. 最急降下法
### 4.3.1. 最急降下法の更新式
### 4.3.2. 最急降下法の実行例
```math
\begin{eqnarray}
f(x)  & = & 0.5x^2 - 5x + 13.5 & = & \frac{1}{2} \{(x-5)^2 + 2\} \\
f'(x) & = & \frac{∂f(x)}{∂x} = x-5
\end{eqnarray}
```
"""

# ╔═╡ 673191fe-f287-453a-b396-28a234132f11
f(x) = 0.5((x - 5)^2 + 2)

# ╔═╡ e114148e-10a1-4d42-89ea-1b41c5c2393b
g(x) = x - 5

# ╔═╡ 5c5e89e7-69d6-40d6-a340-834338c18c8b
function sd(f, g; x=0., η=0.01, ϵ=1e-4)
	t = 1
	H = []
	while true
		gx = g(x)
		push!(H, Dict("t"=>t,"x"=>x,"fx"=>f(x),"gx"=>g(x)))
		-ϵ < gx < ϵ && break
		x -= η * gx
		t += 1
	end
	return H
end

# ╔═╡ 74fa366c-e9b9-4668-8ac5-b82090e47006
function draw_step(h, η)

	x = range(0,10,length=1000)

	plot!(x, f.(x); c=:black, lw=1.5)

	xt, yt, gt = h["x"], h["fx"], h["gx"]

	# 接線の描画
	y0 = yt - xt * gt
	y10 = (10 - xt) * gt + yt
	tangent_line = [(0, y0), (10, y10)]
	plot!(tangent_line; c=:blue, lw=1.5)

	# 次の位置までの差を線分で描画
	next_diff = [(xt, yt), (xt - gt*η, yt)]
	plot!(next_diff; c=:green, lw=1.5)

	# 現在位置のx軸を描画
	vline!([xt]; c=:red, lw=1.5, ls=:dash)

	# 現在位置を描画
	scatter!([(xt, yt)]; c=:red, ms=5, markerstrokewidth=0)

	# 注釈を描画
	msg = "$(round(gt*η; digits=5))"
	annotate!([(xt-gt*η*0.5,yt+0.3,text(msg, 10, :center, :black))])
	msg2 = "$(round(xt; digits=5))"
	annotate!([(xt+0.2,yt-0.4,text(msg2, 10, :left, :black))])
end

# ╔═╡ 3066a735-924f-437f-a67c-1632c14d86a9
begin
	η = 0.5
	H = sd(f, g, x=9, η=η)
	@bind now Slider(1:length(H), show_value=true)
end

# ╔═╡ 5fbd3837-49a0-44f2-973e-9d15dbf5bc43
let
	plot(size=(500,500), xlim=(0,10), ylim=(0,10), framestyle=:box, legend=false)
	plot!(xlabel="x", ylabel="y")

	# anim = @animate for h in H
	# 	draw_step(H, η)
	# end
	# gif(anim, "gd.gif"; fps=2)

	draw_step(H[now], η)
end

# ╔═╡ f98e68cd-ffcc-4c73-890a-2cf34a828237
let
	plot(size=(600,400), framestyle=:box, xlim=(0,18), ylim=(0,10),
		xticks=0:2:18, yticks=1:1:10, legend=false)
	plot!(xlabel="t", ylabel="f(x)")

	ts = [h["t"] for h in H]
	fxs = [h["fx"] for h in H]
	plot!(ts, fxs; ls=:solid, shape=:auto, c=:blue)
end

# ╔═╡ a1a399f0-6a95-4003-879a-11cc0fc86049
md"""
## 4.4. 最急降下法によるパラメータ推定
### 4.4.1. 最急降下法の実装
"""

# ╔═╡ 368c6ea8-1aa8-4c38-8700-73c79c74bac9
D = [1 3; 3 6; 6 5; 8 7]

# ╔═╡ 6f5ecb62-c5d0-48aa-89a2-bee19cb2db4e
md"""
```math
\begin{eqnarray}
w^{t+1} & = & {\bf w}^{(t)} - \eta_t ∇L̂_D({\bf w}^{(t)}) \\
        & = & {\bf w}^{(t)} - 2\eta_t {\bf X}^{\rm T}({\bf ŷ}^{(t)} - {\bf y}) \\
        & = & {\bf w}^{(t)} - 2\eta_t \sum_{i=1}^{N} (ŷ_{i}^{(t)} - y_i){\bf x_i} \\

\end{eqnarray}
```
"""

# ╔═╡ f51adce8-b749-4453-b9c9-849e874c45ee
append!(D[:,1], one(D[1]))

# ╔═╡ 0289da34-98e8-4d4e-8243-00d48147c432
 function calc_gd()
	 max_epochs = 10_000
	 η = 0.001
	 ϵ = 1e-4

	 X = hcat(D[:,1], ones(length(D[:,1])))
	 y = D[:,2]
	 w = zeros(size(X)[2])
	 t = 0

	 for tmp in 1:max_epochs
	 	 ŷ = X * w
		 grad =  2X' * (ŷ - y)
		 if sum(abs.(grad)) < ϵ
			 t = tmp-1
			 break
		 end
		 w -= η*grad
	 end
	 return w, t
 end

# ╔═╡ 2205b8cf-10d9-4594-82c2-20f92753c91c
calc_gd()

# ╔═╡ bdfac505-181f-489a-aaf5-f90d06f9f685
md"""
## 4.5. 確率的勾配降下法
## 4.6. 確率的勾配降下法によるパラメータ推定
### 4.6.1 確率的勾配降下法による回帰モデルの学習

**確率的勾配降下法による線形回帰パラメータ更新式**

```math
\begin{eqnarray}
{\bf w}^{(t+1)} & = & {\bf w}^{(t)} - 2\eta_t (ŷ_i^{(t)} - y_i){\bf x}_i
\end{eqnarray}
```
"""

# ╔═╡ 6c1d8bdc-2d10-4185-9118-505f1044cf52
 function calc_sgd()
	 max_epochs = 40_000
	 η0 = 0.03
	 ϵ = 1e-4

	 X = hcat(D[:,1], ones(length(D[:,1])))
	 y = D[:,2]
	 w = zeros(size(X)[2])
	 t = 0

	 for t in 1:max_epochs
		 η = η0 / √t
		 i = rand(1:size(X)[1])
	 	 ŷ = X[i,:]' * w
		 grad =  2 * (ŷ - y[i]) * X[i,:]
		 if sum(abs.(grad)) < ϵ
			 break
		 end
		 w -= η*grad
	 end
	 return w
 end

# ╔═╡ c69d0f2d-7b30-4210-a90e-321874edca46
calc_sgd()

# ╔═╡ 6f9ef0b1-a92d-47f2-b0b8-cdf4067ee010
md"""
## 4.7. 確率的勾配降下法の特徴
### 4.7.1. 頻繁なパラメータ更新
"""

# ╔═╡ 17c6fd35-36c3-492e-966b-0f49428c24a6
md"""
### 4.7.2. 極小解に接近したのち振動
"""

# ╔═╡ 83c82cfc-b23c-414f-99a0-f77172f317b2
let
	plot(size=(400,400), framestyle=:box, xlim=(0,10), ylim=(0,10),
		xticks=0:2:10, yticks=0:2:10, legend=false)
	plot!(xlabel="x", ylabel="y")
	scatter!(D[:,1], D[:,2])
end

# ╔═╡ 403cb46f-f640-4bb9-8708-2ab6d209445b
function calc_loss(x, y, a, b)
	return (y - (a*x + b))^2
end

# ╔═╡ ae3bbea4-d7dc-42f7-bf25-17378089265d
function calc_averege_loss(D, a, b)
	return calc_loss.(D[:,1], D[:,2], a, b) |> mean
end

# ╔═╡ 13380627-fc87-4245-816f-c2cb4b647db1
function calc_minimizer(D)
	cov_value = cov(D; corrected=false)
	a = cov_value[1,2] / cov_value[1,1]
	b = mean(D[:,2]) - a * mean(D[:,1])
	return a, b
end

# ╔═╡ 0dcd1a01-5ed1-4930-8f6f-b09c619b09e2
function calc_losses(a, b, D, N, minvalue)
	losses = []
	for i in range(1, size(D)[1])
		loss_now = calc_loss.(D[i,1], D[i,2], a, b) .+ minvalue
		push!(losses, loss_now)
	end
	return losses
end

# ╔═╡ 3795afa6-1705-44be-b4bf-1adbcf29e91b
function plot_heatmap(;cb=true)
	gr()
	N = 1024
	minvalue = 1e-6     # This prevents log 0 in the logarithmic scale.
	mincontour = 1e-2   # Cut-off value for the contour plot.
	A = range(-1, 7, N)
	B = range(-1, 7, N)
	losses = calc_losses(A, B', D, N, minvalue)
	J = sum(losses)'

	plot(xlim=(-1,7), ylim=(-1,7), framestyle=:box, aspect_ratio=1)
	plot!(xticks=-1:7, yticks=-1:7)
	plot!(xlabel="a", ylabel="b", zlabel="Loss: Mean Squared Residual (MSR)")
	heatmap!(A, B, J; colorbar_scale=:log10, clim=(10^0,10^4),
		fillcolor=cgrad(:viridis), colorbar=cb )

	J_levels = [1., 10.0, 100.0, 1000.0]
	contour!(A, B, J; lc=:red, lw=0.5, ls=:dash, levels= J_levels)

	a_, b_ = calc_minimizer(D)
	scatter!([(a_,b_)]; shape=:star5, ms=7, c=:red, label="", markerstrokewidth=0)
end

# ╔═╡ 247e18dd-8298-444a-ab32-c977b2a94996
plot_heatmap()

# ╔═╡ e69398f4-23ec-4b2c-bb15-69451d0c86ae
function fill_loss_for_checkpoints(H)
	for h in H
		h["loss"] = calc_averege_loss(D, h["a"], h["b"])
	end
end

# ╔═╡ 188fdc2e-be02-43d5-bf94-dcf527a47d66
function calc_locus()
	max_epochs = 20000
	record_period = 200
	η0 = 0.03
	ϵ = 1e-4

	X = hcat(D[:,1], ones(length(D[:,1])))
	y = D[:,2]
	w = zeros(size(X)[2])
	H = []
	push!(H, Dict("t"=>0,"a"=>w[1],"b"=>w[2]))
	for t in 1:max_epochs
		η = η0 / √t
	 	ŷ = X * w
		grad =  2X' * (ŷ - y) / size(X)[1]
		if sum(abs.(grad)) < ϵ
			break
		end
		w -= η*grad
		if t%record_period == 0
			push!(H, Dict("t"=>t, "a"=>w[1], "b"=>w[2]))
		end
	end
	fill_loss_for_checkpoints(H)
	return H
end

# ╔═╡ 181d24d0-5d9b-4436-9f87-fc518e4a9504
function plot_animation(H, ci)
	plot_heatmap(cb=false)
	scatter!([H[1]["a"],], [H[1]["b"],]; shape=:star6, ms=7, c=:white, label="", markerstrokewidth=0)
	for i in 1:ci
		plot!([H[i]["a"], H[i+1]["a"]], [H[i]["b"], H[i+1]["b"]];
			  lw=3, c=:white, label="")
	end
	epoch = H[ci]["t"]
	mse = H[ci]["loss"] |> x -> round(x; digits=5)
	msg = "Epoch #$epoch, MSE = $mse"
	annotate!([(3.0,-0.7,text(msg, 12, :center, :black))])
	plot!()
end

# ╔═╡ ffbf158e-8a33-4179-b2ce-95e08cb5911e
HH = calc_locus()

# ╔═╡ 4ed5272a-6442-4a61-af37-fcca09d9f45a
function plot_regression(H, ci)

	xs = [0, 10]
	ys = [0, 10]
	plot(framestyle=:box, xlim=(xs), ylim=(ys),xticks=0:2:10, yticks=0:2:10, legend=false)
	plot!(xlabel="x", ylabel="y")
	scatter!(D[:,1], D[:,2]; markerstrokewidth=0, ms=7)
	a = H[ci]["a"]
	b =	H[ci]["b"]
	x0 = xs[1]
	x1 = xs[2]

	y0 = a * x0 + b
	y1 = a * x1 + b
	plot!([x0, x1], [y0, y1]; c=:black, lw=2)
	a_txt = round(a, digits=5)
	b_txt = round(b, digits=5)
	msg = "(a, b) = ($a_txt, $b_txt)"
	annotate!([(5.0, 0.3,text(msg, 12, :center, :black))])

end

# ╔═╡ 2dc05c65-d8a4-4539-8650-40f457b0ff13
@bind ci Slider(1:length(HH)-1, show_value=true)

# ╔═╡ fff206d6-ea3a-49b0-b90b-7a04afb24fb7
let
	p1 = plot_animation(HH, ci)
	p2 = plot_regression(HH, ci)

	plot(p1, p2; layout=(@layout [a b]), size=(900,450))
end

# ╔═╡ 4abdbcdd-bd81-4c01-a984-dd6ee1b64632
function calc_plot_sgd()
	max_epochs = 20000
	record_period = 200
	η0 = 0.03
	ϵ = 1e-4

	X = hcat(D[:,1], ones(length(D[:,1])))
	y = D[:,2]
	w = zeros(size(X)[2])
	H = []
	push!(H, Dict("t"=>0,"a"=>w[1],"b"=>w[2]))

	for t in 1:max_epochs
		η = η0 / √t
		i = rand(1:size(X)[1])
	 	ŷ = X[i,:]' * w
		grad =  2 * (ŷ - y[i]) * X[i,:]
		if sum(abs.(grad)) < ϵ
			break
		end
		w -= η*grad
		if t%record_period == 0
			push!(H, Dict("t"=>t, "a"=>w[1], "b"=>w[2]))
		end
	end
	fill_loss_for_checkpoints(H)
	return H
end

# ╔═╡ 95b271c2-3b13-4e13-8bf9-aa32e3b02ceb
HHH = calc_plot_sgd()

# ╔═╡ 713e01dc-aa60-4488-a783-623de4b23533
function plot_animation2(H, ci)
	plot_heatmap(cb=false)
	scatter!([H[1]["a"],], [H[1]["b"],]; shape=:star6, ms=7, c=:white, label="", markerstrokewidth=0)
	for i in 1:ci
		plot!([H[i]["a"], H[i+1]["a"]], [H[i]["b"], H[i+1]["b"]];
			  lw=3, c=:white, label="")
	end
	epoch = H[ci]["t"]
	mse = H[ci]["loss"] |> x -> round(x; digits=5)
	msg = "Epoch #$epoch, MSE = $mse"
	annotate!([(3.0,-0.7,text(msg, 12, :center, :black))])
	plot!()
end

# ╔═╡ aedbbdaa-19fd-4786-ac4d-8b460792c4cf
@bind cii Slider(1:length(HHH)-1, show_value=true)

# ╔═╡ c6c15ab1-1bad-4f10-ad96-798dc9aa6122
let
	p1 = plot_animation2(HHH, cii)
	p2 = plot_regression(HHH, cii)

	plot(p1, p2; layout=(@layout [a b]), size=(900,450))
end

# ╔═╡ 240ae292-6310-46a5-81c4-be22093fc667
let
	anim = @animate for i in 1:length(HHH)-1
		p1 = plot_animation2(HHH, i)
		p2 = plot_regression(HHH, i)

		plot(p1, p2; layout=(@layout [a b]), size=(900,450))
	end
	gif(anim, "sgd.gif"; fps=2)
end

# ╔═╡ 18113f21-e8a6-42a2-898b-7de075bad646

# ╔═╡ 7c7b1329-d123-4f37-91cd-4898beb0c196
function plot_heatmap2(H,ci;cb=true)
	gr()
	N = 1024
	minvalue = 1e-6     # This prevents log 0 in the logarithmic scale.
	mincontour = 1e-2   # Cut-off value for the contour plot.
	A = range(-1, 7, N)
	B = range(-1, 7, N)
	losses = calc_losses(A, B', D, N, minvalue)

	J_levels = [1, 10.0, 100.0, 1000.0]

	plot(xlim=(-1,7), ylim=(-1,7), framestyle=:box, aspect_ratio=1)
	plot!(xticks=-1:7, yticks=-1:7)
	plot!(xlabel="a", ylabel="b", title="Instance #$ci", titlefontsize=10)

	heatmap!(A, B, losses[ci]'; colorbar_scale=:log10, clim=(10^-5,10^3),
		fillcolor=cgrad(:viridis), colorbar=cb )
	contour!(A, B, losses[ci]'; lc=:red, lw=0.5, ls=:dash, levels= J_levels)

	a_, b_ = calc_minimizer(D)
	scatter!([H[1]["a"],], [H[1]["b"],]; shape=:star6, ms=7, c=:white, label="", markerstrokewidth=0)
	scatter!([(a_,b_)]; shape=:star5, ms=7, c=:red, label="", markerstrokewidth=0)
end

# ╔═╡ 1ec91a0c-b077-42d6-af27-936fd4d069d5
let

	p1 = plot_heatmap2(HHH,1;cb=false)
	p2 = plot_heatmap2(HHH,2;cb=false)
	p3 = plot_heatmap2(HHH,3;cb=false)
	p4 = plot_heatmap2(HHH,4;cb=false)

	plot(p1, p2, p3, p4; layout=(@layout [a b; c d]), size=(900,900))
end

# ╔═╡ 0c7f3c06-22f2-4e44-b709-5e42d5bdba2e
function minimizer_of_a(D,b)
	x = D[:,1] |> mean
	x2 = D[:,1].^2 |> mean
	xy = D[:,1] .* D[:,2] |> mean
	return (xy - x * b) / x2
end

# ╔═╡ 538c51ee-9003-405a-aecf-8dbc4fe165b9
let
	A = range(-1, 6, length=100)
	b = 0
	losses = [ calc_loss.(D[i,1], D[i,2], A, b) for i in 1:size(D)[1]]

	plot(xlim=(-1.5,6.5), ylim=(-25,200), xticks=-1:1:6, yticks=-25:25:200,
		framestyle=:box)
	plot!(xlabel="a", ylabel="Loss: Mean Squared Error (MSE)")

	for i in 1:size(D)[1]
		plot!(A, losses[i]; label="Instance #$i", lw=2)
	end
	plot!()
end

# ╔═╡ 5354dccc-88ef-4552-b5b1-a68efce1e736
let
	x = range(0, 10, length=1000)
	y1 = 5/6 * x
	y2 = 3x
	plot(xlim=(0,10), ylim=(0,10), xticks=0:2:10, yticks=0:2:10,
		framestyle=:box, aspect_ratio=true, legend=false)
	plot!(xlabel="x", ylabel="y")
	plot!(x, y1; c=:black, fill=(0, 0.3, :green))
	plot!(x, y2; c=:black, fill=(10, 0.3, :green))
	scatter!(D[:,1], D[:,2]; markerstrokewidth=0, ms=5, c=:blue)

	annotate!([(6, 6,text("a=5/6", 10, :center, :black))])
	annotate!([(3.3, 8,text("a=3", 10, :center, :black))])

end

# ╔═╡ d466df2c-8cb1-40d2-a671-09b2f644a1a4
let
	X = (rand(297) * 2 .- 1) * 10
	Y = X + randn(length(X))
	nX = [3, -1, 8]
	nY = [-4, 6, -3]

	plot(xlim=(-11.0, 11.0), ylim=(-12.0, 11.0), framestyle=:box)
	plot!(xlabel="x", ylabel="y", legend=false)
	scatter!(X, Y; markerstrokewidth=0, ms=4, c=:blue)
	scatter!(nX, nY; markerstrokewidth=0, ms=6, c=:blue, shape=:star6)

end

# ╔═╡ 9351f501-62a6-413f-8a1c-1fd1e889831e

# ╔═╡ 6bb18e91-5639-4fde-afae-5884a06d70eb

# ╔═╡ b5604058-ea51-4ef0-a4a2-308f2a8d963c

# ╔═╡ 5838993f-affc-4a19-8a29-6fa22e2aea9b

# ╔═╡ e5349ea7-1f1e-4d0f-aa7e-342f6ad09df7

# ╔═╡ f33c6dad-a3f6-40cd-90fd-34e252af4cca

# ╔═╡ f877a16a-ce53-4979-95b6-5f1f194937c3

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[compat]
Plots = "~1.25.6"
PlutoUI = "~0.7.30"
"""

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

julia_version = "1.7.2"
manifest_format = "2.0"

[[deps.AbstractPlutoDingetjes]]
deps = ["Pkg"]
git-tree-sha1 = "8eaf9f1b4921132a4cff3f36a1d9ba923b14a481"
uuid = "6e696c72-6542-2067-7265-42206c756150"
version = "1.1.4"

[[deps.Adapt]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "af92965fb30777147966f58acb05da51c5616b5f"
uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
version = "3.3.3"

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

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

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

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

[[deps.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 = "4b859a208b2397a7a623a03449e4636bdb17bcf2"
uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a"
version = "1.16.1+1"

[[deps.ChainRulesCore]]
deps = ["Compat", "LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "6e39c91fb4b84dcb870813c91674bdebb9145895"
uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
version = "1.11.5"

[[deps.ChangesOfVariables]]
deps = ["ChainRulesCore", "LinearAlgebra", "Test"]
git-tree-sha1 = "bf98fa45a0a4cee295de98d4c1462be26345b9a1"
uuid = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0"
version = "0.1.2"

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

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

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

[[deps.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 = "44c37b4636bc54afac5c574d2d02b625349d6582"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.41.0"

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

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

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

[[deps.DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "3daef5523dd2e769dad2365274f760ff5f282c7d"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.11"

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

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

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

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

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

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

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

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

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

[[deps.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"

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

[[deps.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"

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

[[deps.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"

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

[[deps.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"

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

[[deps.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 = "aa22e1ee9e722f1da183eb33370df4c1aeb6c2cd"
uuid = "d2c73de3-f751-5644-a686-071e5b155ba9"
version = "0.63.1+0"

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

[[deps.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"

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

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

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

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

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

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

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

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

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

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

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

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

[[deps.IterTools]]
git-tree-sha1 = "fa6287a4469f5e048d763df38279ee729fbd44e5"
uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
version = "1.4.0"

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

[[deps.JLLWrappers]]
deps = ["Preferences"]
git-tree-sha1 = "22df5b96feef82434b07327e2d3c770a9b21e023"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.4.0"

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

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

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

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

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

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

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

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

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

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

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

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

[[deps.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"

[[deps.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"

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

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

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

[[deps.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"

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

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

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

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

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

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

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

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

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

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

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

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

[[deps.NaNMath]]
git-tree-sha1 = "f755f36b19a5116bb580de457cda0c140153f283"
uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
version = "0.3.6"

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

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

[[deps.OpenBLAS_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"

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

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

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

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

[[deps.Parsers]]
deps = ["Dates"]
git-tree-sha1 = "92f91ba9e5941fc781fecf5494ac1da87bdac775"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.2.0"

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

[[deps.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"

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

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

[[deps.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", "Unzip"]
git-tree-sha1 = "db7393a80d0e5bef70f2b518990835541917a544"
uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
version = "1.25.6"

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

[[deps.Preferences]]
deps = ["TOML"]
git-tree-sha1 = "2cf929d64681236a2e074ffafb8d568733d2e6af"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.2.3"

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

[[deps.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"

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

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

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

[[deps.RecipesPipeline]]
deps = ["Dates", "NaNMath", "PlotUtils", "RecipesBase"]
git-tree-sha1 = "37c1631cb3cc36a535105e6d5557864c82cd8c2b"
uuid = "01d81517-befc-4cb6-b9ec-a95719d0359c"
version = "0.5.0"

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

[[deps.RelocatableFolders]]
deps = ["SHA", "Scratch"]
git-tree-sha1 = "cdbd3b1338c72ce29d9584fdbe9e9b70eeb5adca"
uuid = "05181044-ff0b-4ac5-8273-598c1e38db00"
version = "0.1.3"

[[deps.Requires]]
deps = ["UUIDs"]
git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7"
uuid = "ae029012-a4dd-5104-9daa-d747884805df"
version = "1.3.0"

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

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

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

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

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

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

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

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

[[deps.StaticArrays]]
deps = ["LinearAlgebra", "Random", "Statistics"]
git-tree-sha1 = "2ae4fe21e97cd13efd857462c1869b73c9f61be3"
uuid = "90137ffa-7385-5640-81b9-e52037218182"
version = "1.3.2"

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

[[deps.StatsAPI]]
git-tree-sha1 = "d88665adc9bcf45903013af0982e2fd05ae3d0a6"
uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0"
version = "1.2.0"

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

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

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

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

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

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

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

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

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

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

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

[[deps.Unzip]]
git-tree-sha1 = "34db80951901073501137bdbc3d5a8e7bbd06670"
uuid = "41fe7b60-77ed-43a1-b4f0-825fd5a5650d"
version = "0.1.2"

[[deps.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"

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

[[deps.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"

[[deps.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"

[[deps.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"

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

[[deps.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"

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

[[deps.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"

[[deps.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"

[[deps.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"

[[deps.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"

[[deps.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"

[[deps.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"

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

[[deps.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"

[[deps.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"

[[deps.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"

[[deps.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"

[[deps.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"

[[deps.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"

[[deps.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"

[[deps.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"

[[deps.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"

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

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

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

[[deps.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"

[[deps.libblastrampoline_jll]]
deps = ["Artifacts", "Libdl", "OpenBLAS_jll"]
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"

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

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

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

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

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

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

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

[[deps.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:
# ╠═d7ac6a44-843f-48da-b90d-576658f33d84
# ╠═f7fd08ce-78fb-11ec-2e93-5bcd26561b10
# ╠═673191fe-f287-453a-b396-28a234132f11
# ╠═e114148e-10a1-4d42-89ea-1b41c5c2393b
# ╠═5c5e89e7-69d6-40d6-a340-834338c18c8b
# ╠═74fa366c-e9b9-4668-8ac5-b82090e47006
# ╠═5fbd3837-49a0-44f2-973e-9d15dbf5bc43
# ╠═3066a735-924f-437f-a67c-1632c14d86a9
# ╠═f98e68cd-ffcc-4c73-890a-2cf34a828237
# ╠═a1a399f0-6a95-4003-879a-11cc0fc86049
# ╠═368c6ea8-1aa8-4c38-8700-73c79c74bac9
# ╠═6f5ecb62-c5d0-48aa-89a2-bee19cb2db4e
# ╠═f51adce8-b749-4453-b9c9-849e874c45ee
# ╠═0289da34-98e8-4d4e-8243-00d48147c432
# ╠═2205b8cf-10d9-4594-82c2-20f92753c91c
# ╠═bdfac505-181f-489a-aaf5-f90d06f9f685
# ╠═6c1d8bdc-2d10-4185-9118-505f1044cf52
# ╠═c69d0f2d-7b30-4210-a90e-321874edca46
# ╠═6f9ef0b1-a92d-47f2-b0b8-cdf4067ee010
# ╠═17c6fd35-36c3-492e-966b-0f49428c24a6
# ╠═83c82cfc-b23c-414f-99a0-f77172f317b2
# ╠═2c74a643-2db6-4693-bd46-47560b7036fb
# ╠═403cb46f-f640-4bb9-8708-2ab6d209445b
# ╠═ae3bbea4-d7dc-42f7-bf25-17378089265d
# ╠═13380627-fc87-4245-816f-c2cb4b647db1
# ╠═0dcd1a01-5ed1-4930-8f6f-b09c619b09e2
# ╠═3795afa6-1705-44be-b4bf-1adbcf29e91b
# ╠═247e18dd-8298-444a-ab32-c977b2a94996
# ╠═e69398f4-23ec-4b2c-bb15-69451d0c86ae
# ╠═188fdc2e-be02-43d5-bf94-dcf527a47d66
# ╠═181d24d0-5d9b-4436-9f87-fc518e4a9504
# ╠═ffbf158e-8a33-4179-b2ce-95e08cb5911e
# ╠═4ed5272a-6442-4a61-af37-fcca09d9f45a
# ╠═2dc05c65-d8a4-4539-8650-40f457b0ff13
# ╠═fff206d6-ea3a-49b0-b90b-7a04afb24fb7
# ╠═4abdbcdd-bd81-4c01-a984-dd6ee1b64632
# ╠═95b271c2-3b13-4e13-8bf9-aa32e3b02ceb
# ╠═713e01dc-aa60-4488-a783-623de4b23533
# ╠═c6c15ab1-1bad-4f10-ad96-798dc9aa6122
# ╠═aedbbdaa-19fd-4786-ac4d-8b460792c4cf
# ╠═240ae292-6310-46a5-81c4-be22093fc667
# ╠═18113f21-e8a6-42a2-898b-7de075bad646
# ╠═7c7b1329-d123-4f37-91cd-4898beb0c196
# ╠═1ec91a0c-b077-42d6-af27-936fd4d069d5
# ╠═0c7f3c06-22f2-4e44-b709-5e42d5bdba2e
# ╠═538c51ee-9003-405a-aecf-8dbc4fe165b9
# ╠═5354dccc-88ef-4552-b5b1-a68efce1e736
# ╠═d466df2c-8cb1-40d2-a671-09b2f644a1a4
# ╠═9351f501-62a6-413f-8a1c-1fd1e889831e
# ╠═6bb18e91-5639-4fde-afae-5884a06d70eb
# ╠═b5604058-ea51-4ef0-a4a2-308f2a8d963c
# ╠═5838993f-affc-4a19-8a29-6fa22e2aea9b
# ╠═e5349ea7-1f1e-4d0f-aa7e-342f6ad09df7
# ╠═f33c6dad-a3f6-40cd-90fd-34e252af4cca
# ╠═f877a16a-ce53-4979-95b6-5f1f194937c3
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002


@test Tsat(0.10) ≈ 0.372_755_919E3
@test Tsat(0.10u"MPa") ≈ 0.372_755_919E3u"K"

@test Tsat(1.00) ≈ 0.453_035_632E3
@test Tsat(1.00u"MPa") ≈ 0.453_035_632E3u"K"

@test Tsat(10.0) ≈ 0.584_149_488E3
@test Tsat(10.0u"MPa") ≈ 0.584_149_488E3u"K"


cd(@__DIR__); include("setups/path512.jl");
gr(dpi = 200)

## Build VM-NGWP
@time VM_NGWP = vm_ngwp(𝚽, GP_dual)

#################### Fig.5
j = 5
for k in [1, 2, 5]
   WW = sort_wavelets(VM_NGWP[GP_dual.rs[k, j]:(GP_dual.rs[k + 1, j] - 1), j, :]')
   sc = 0.75
   if k == 1
      sc = 0.5
   end
   if k == 2
      WW[:, end] *= -1
   end
   plt = wiggle(WW; sc = sc)
   savefig(plt, "../figs/Path512_VM_NGWP_j$(j-1)k$(k-1).png")
end
current()


using LinearAlgebra
@testset "biconnected" begin
    n = 10
    O = ones(Int64,n-1)
    Z = zeros(Int64,n)
    A = Tridiagonal(O,Z,O)
    B = MatrixNetwork(dropzeros!(sparse(A)))
    obj = biconnected_components(B)

    for i = 2:n-1
        @test obj.articulation_points[i]!=0
    end
    components = biconnected_components(B;art=false, components=false).number
    number_of_components = biconnected_components!(B, zeros(Bool,0), zeros(Int64,0))
    @test 2*components == 2*number_of_components == length(obj.map)

    @test obj.articulation_points[1]==0
    @test obj.articulation_points[n]==0

    B = dropzeros!(sparse(A))
    obj = biconnected_components(B)

    for i = 2:n-1
        @test obj.articulation_points[i]!=0
    end
    components = biconnected_components(B;art=false, components=false).number

    @test obj.articulation_points[1]==0
    @test obj.articulation_points[n]==0

    B = sparse_to_csr(dropzeros!(sparse(A)))
    obj = biconnected_components(B...)
    for i = 2:n-1
        @test obj.articulation_points[i]!=0
    end
    components = biconnected_components(B...;art=false, components=false).number

    @test obj.articulation_points[1]==0
    @test obj.articulation_points[n]==0

    B = findnz(dropzeros!(sparse(A)))
    obj = biconnected_components(B[1], B[2])

    for i = 2:n-1
        @test obj.articulation_points[i]!=0
    end
    components = biconnected_components(B[1], B[2];art=false, components=false).number

    @test obj.articulation_points[1]==0
    @test obj.articulation_points[n]==0

    A = load_matrix_network("minnesota")
    B = MatrixNetwork(A)
    components = biconnected_components(B;art=false, components=false).number
    number_of_components = biconnected_components!(B, zeros(Bool,0), zeros(Int64,0))
    @test components == number_of_components == 142

    A = load_matrix_network("biconnected_example")
    B = MatrixNetwork(A)
    obj = biconnected_components(B)
    components = biconnected_components(B;art=false, components=false).number
    number_of_components = biconnected_components!(B, zeros(Bool,0), zeros(Int64,0))
    @test components == number_of_components == 5
    @test obj.articulation_points[5] == 1

    A = load_matrix_network("cores_example")
    B = MatrixNetwork(A)
    obj = biconnected_components(B)
    components = biconnected_components(B;art=false, components=false).number
    number_of_components = biconnected_components!(B, zeros(Bool,0), zeros(Int64,0))
    @test components == number_of_components == 6
    @test obj.articulation_points[20] == 1
    @test obj.articulation_points[2] == 1
    @test obj.articulation_points[11] == 1
    @test obj.articulation_points[12] == 0

    A = empty_graph()
    obj = biconnected_components(A)
    components = biconnected_components(A;art=false, components=false).number
    number_of_components = biconnected_components!(A, zeros(Bool,0), zeros(Int64,0))
    @test components == number_of_components == 0

    A = load_matrix_network("clique-10")
    B = MatrixNetwork(A)
    obj = biconnected_components(B)
    components = biconnected_components(B;art=false, components=false).number
    number_of_components = biconnected_components!(B, zeros(Bool,0), zeros(Int64,0))
    @test components == number_of_components == 1
end


using MiniPETSc

vec = PetscVec()
setSize!(vec, n_local=(Int32)(3))
vec[1:3] = (Float64)[1, 2, 3]
assemble!(vec)
viewVec(vec)

sol = PetscVec()
setSize!(sol, n_local=(Int32)(3))
assemble!(sol)
viewVec(sol)

mat = PetscMat()
setSize!(mat, m_local=(Int32)(3), n_local=(Int32)(3))
setPreallocation!(mat, (Int32)[1,1,1], (Int32)[])

mat[1,1] = (Float64)(1)
mat[2,2] = (Float64)(2)
mat[3,3] = (Float64)(3)

assemble!(mat)
viewMat(mat)

ksp = PetscKSP()

setOperators(ksp, mat)

solve!(ksp, vec, sol)

viewVec(sol)


#For a Colab of this example, goto https://colab.research.google.com/drive/1xfUsBn9GEqbRjBF-UX_jnGjHZNtNsMae
using TensorBoardLogger
using Flux
using Logging
using MLDatasets
using Statistics
using CuArrays

#create tensorboard logger
logdir = "content/log"
logger = TBLogger(logdir, tb_overwrite)

#Load data
traindata, trainlabels = FashionMNIST.traindata();
testdata, testlabels = FashionMNIST.testdata();
trainsize = size(traindata, 3);
testsize = size(testdata, 3);

#Log some images
images = TBImage(traindata[:, :, 1:10], WHN)
with_logger(logger) do #log some samples
    @info "fmnist/samples" pics = images log_step_increment=0
end

#Create model
model = Chain(
    x -> reshape(x, :, size(x, 4)),
    Dense(28^2, 32, relu),
    Dense(32, 10),
    softmax
)

loss(x, y) = Flux.crossentropy(model(x), y)

accuracy(x, y) = mean(Flux.onecold(model(x) |> cpu) .== Flux.onecold(y |> cpu))

opt = ADAM()

traindata = permutedims(reshape(traindata, (28, 28, 60000, 1)), (1, 2, 4, 3));
testdata = permutedims(reshape(testdata, (28, 28, 10000, 1)), (1, 2, 4, 3));
trainlabels = Flux.onehotbatch(trainlabels, collect(0:9));
testlabels = Flux.onehotbatch(testlabels, collect(0:9));

#function to get dictionary of model parameters
function fill_param_dict!(dict, m, prefix)
    if m isa Chain
        for (i, layer) in enumerate(m.layers)
            fill_param_dict!(dict, layer, prefix*"layer_"*string(i)*"/"*string(layer)*"/")
        end
    else
        for fieldname in fieldnames(typeof(m))
            val = getfield(m, fieldname)
            if val isa AbstractArray
                val = vec(val)
            end
            dict[prefix*string(fieldname)] = val
        end
    end
end

#function to log information after every epoch
function TBCallback()
  param_dict = Dict{String, Any}()
  fill_param_dict!(param_dict, model, "")
  with_logger(logger) do
    @info "model" params=param_dict log_step_increment=0
    @info "train" loss=loss(traindata, trainlabels) acc=accuracy(traindata, trainlabels) log_step_increment=0
    @info "test" loss=loss(testdata, testlabels) acc=accuracy(testdata, testlabels)
  end
end

minibatches = []
batchsize = 100
for i in range(1, stop = trainsize÷batchsize)
  lbound = (i-1)*batchsize+1
  ubound = min(trainsize, i*batchsize)
  push!(minibatches, (traindata[:, :, :, lbound:ubound], trainlabels[:, lbound:ubound]))
end

Move data and model to gpu
traindata = traindata |> gpu
testdata = testdata |> gpu
trainlabels = trainlabels |> gpu
testlabels = testlabels |> gpu
model = model |> gpu
minibatches = minibatches |> gpu

#Train
@Flux.epochs 15 Flux.train!(loss, params(model), minibatches, opt, cb = Flux.throttle(TBCallback, 5))


# Julia code
# Copyright (c) 2022 the SLICOTMath.jl developers
# Portions extracted from SLICOT-Reference distribution:
# Copyright (c) 2002-2020 NICONET e.V.

function run_mb02jx(datfile, io=stdout)
    NIN = 5
    NOUT = 6
    KMAX = 20
    LMAX = 20
    MMAX = 20
    NMAX = 20
    LDR  = NMAX*LMAX
    LDQ  = MMAX*KMAX
    LDTC = MMAX*KMAX
    LDTR = KMAX
    LDWORK = ( MMAX*KMAX + NMAX*LMAX ) *( LMAX + 2*KMAX ) + 5*LMAX + MMAX*KMAX + NMAX*LMAX
    TC = Array{Float64,2}(undef, LDTC,LMAX)
    TR = Array{Float64,2}(undef, LDTR,NMAX*LMAX)
    Q = Array{Float64,2}(undef, LDQ,NMAX*LMAX)
    R = Array{Float64,2}(undef, LDR,NMAX*LMAX)
    JPVT = Array{BlasInt,1}(undef, NMAX*LMAX)
    DWORK = Array{Float64,1}(undef, LDWORK)
    f = open(datfile,"r")
    readline(f)
    vs = split(readline(f))
    K = parse(BlasInt, vs[1])
    L = parse(BlasInt, vs[2])
    M = parse(BlasInt, vs[3])
    N = parse(BlasInt, vs[4])
    TOL1 = parse(Float64, replace(vs[5],'D'=>'E'))
    TOL2 = parse(Float64, replace(vs[6],'D'=>'E'))
    JOB =  vs[7][1]
if ( K<=0 || K>KMAX )
elseif ( L<=0 || L>LMAX )
elseif ( M<=0 || M>MMAX )
elseif ( N<=0 || N>NMAX )
else

    vs = String[]
    _isz,_jsz = (M*K,L)
    while length(vs) < _isz*_jsz
        append!(vs, replace.(split(readline(f)),'D'=>'E'))
    end
    for i in 1:_isz
       _i0 = (i-1)*_jsz
       TC[i,1:_jsz] .= parsex.(Float64, vs[_i0+1:_i0+_jsz])
    end

    vs = String[]
    _isz,_jsz = (K,N*L-L)
    while length(vs) < _isz*_jsz
        append!(vs, replace.(split(readline(f)),'D'=>'E'))
    end
    for i in 1:_isz
       _i0 = (i-1)*_jsz
       TR[i,1:_jsz] .= parsex.(Float64, vs[_i0+1:_i0+_jsz])
    end
# interp call 1

    RNK, INFO = SLICOT.mb02jx!(JOB, K, L, M, N, TC, TR, Q, R, JPVT, TOL1, TOL2, LDWORK)
    println(io, "RNK = $RNK")
    @test INFO == 0
    INFO == 0 || return

if ( INFO!=0 )
else
if ( LSAME( JOB, 'Q' ) )
# interp output 1
    println(io,"Q:")
    _nc = RNK
    _nr = M*K
    show(io,"text/plain",Q[1:_nr,1:_nc])
    println(io,)

end # if
# interp output 2
    println(io,"R:")
    _nc = RNK
    _nr = N*L
    show(io,"text/plain",R[1:_nr,1:_nc])
    println(io,)

# interp output 3
    println(io,"JPVT:")
    _nr = min( M*K, N*L )
    show(io,"text/plain",JPVT[1:_nr])
    println(io,)

end # if
end # if
    close(f)
end # run_X()


__precompile__()

module DiffEqSensitivity

using DiffEqBase, Compat, ForwardDiff, Flux, DiffEqDiffTools, Statistics
using DiffEqCallbacks, QuadGK, RecursiveArrayTools, LinearAlgebra
using DataFrames, GLM, RecursiveArrayTools

abstract type SensitivityFunction end

include("derivative_wrappers.jl")
include("local_sensitivity.jl")
include("adjoint_sensitivity.jl")
include("morris_sensitivity.jl")
include("sobol_sensitivity.jl")
include("regression_sensitivity.jl")
include("DGSM.jl")

export extract_local_sensitivities

export ODELocalSensitivityFunction, ODELocalSensitivityProblem, SensitivityFunction,
       ODEAdjointSensitivityProblem, ODEAdjointProblem, AdjointSensitivityIntegrand,
       adjoint_sensitivities, adjoint_sensitivities_u0,
       morris_sensitivity, MorrisSensitivity, sobol_sensitivity,
       SensitivityAlg, regression_sensitivity, DGSM

end # module


#generate community
p = random_micrm_params(2,2,0.5)

#convert to ODESystem
@named sys = micrm_system(p)

@testset "MTK system" begin
    @test length(states(sys)) == 4
    @test length(parameters(sys)) == 14
end

#convert to problem
#define starting mass
u0 = fill(0.1, 4)
u0 = [states(sys)[i] => u0[i] for i = eachindex(u0)]

tspan = (0.0, 10.0) #define tspan

prob = ODEProblem(sys,u0,(0.0,100.0),[], jac = true)
sol = solve(prob)


using Formatting

# port of the SimHashIndex class

_fe = FormatExpr("{1:x},{2}")

type SimhashIndex
    # from py : list of tuples
    objs::Array{Tuple{String,Simhash}}
    f::Int
    k::Int
    count::Int

    # from py : default dict of sets
    bucket::Dict{String, Set}

    # generate the offsets from the f, k
    offsets::Array{Int}

    function SimhashIndex(objs::Array{Tuple{String,Simhash}}, f::Int, k::Int)
        bucket = Dict{String, Set}(Set())
        offsets = _offsets(f, k)
        for obj in objs
            add_to_index(bucket, obj, offsets)
        end
        new(objs, f, k, length(objs), bucket, offsets)
    end
end

function add_to_index(bucket, obj, offsets)
    # obj = Tuple(string, simhash)
    obj_id = obj[1]
    simhash = obj[2]
    for key in _get_keys(simhash, offsets)
        v = format(_fe, simhash.value, obj_id)
        if !haskey(bucket, key)
            bucket[key] = Set()
        end
        push!(bucket[key], v)
    end
end

function _offsets(f, k)
    # per the orig.: You may optimize this method according to
    #     <http://www.wwwconference.org/www2007/papers/paper215.pdf>
    return [fld(f, (k + 1)) * (i - 1) for i in 1:(k + 1)]
end

function _get_keys(simhash, offsets)
    keys = []
    fe = FormatExpr("{1:x}:{2:x}")
    for (i, offset) in enumerate(offsets)
        if i == length(offsets)
            m = 2 ^ (simhash.f - offset) - 1
        else
            m = 2 ^ (offsets[i+1] - offset) - 1
        end
        c = (simhash.value >> offset) & m
        push!(keys, format(fe, c, i-1))
    end
    return keys
end

function get_near_dups(index, simhash)
    # simhash = simhash obj to check against index
    ans = Set()

    for key in _get_keys(simhash, index.offsets)
        if !haskey(index.bucket, key)
            continue
        end
        dups = index.bucket[key]

        if length(dups) > 200
            println("Warning: big bucket found: ", key)
        end

        for dup in dups
            simhash2, obj_id = split(dup, ',', limit=2)
            simhash2 = Simhash(parse(BigInt, simhash2, 16), simhash.f)
            d = distance(simhash, simhash2)
            if d <= index.k
                push!(ans, obj_id)
            end
        end
    end

    return collect(ans)
end

function delete_from_index(index, obj_id, simhash)
    # string, simhash
    for key in _get_keys(simhash, index.offsets)
        v = format(_fe, simhash.value, obj_id)
        delete!(index.bucket[key], v)
    end
end


module KerrQuasinormalModes

using LinearAlgebra
using NLsolve
using Statistics
using CSV
using StaticArrays
using PyCall

include("AngularFunctions.jl")
include("ParameterFunctions.jl")
include("ContinuedFractionFunctions.jl")
include("SolverFunctions.jl")
include("Schwarzschild.jl")
include("ModeCalculator.jl")
include("QnmRotationSeries.jl")
include("SpinWeightedSphericalLookup.jl")
include("Interface.jl")
include("ConfluentHeun.jl")
include("LinearCombinations.jl")
include("Derivative.jl")

#Ψ = qnmfunction(s=-2,l=2,n=2,m=-2,a=0.4)
#Ψ(2.0)
function asad()
    print("blah blah blo new yesterday heres somthing new")
end

export SpinWeightedSpherical
export HeunConfluentRadial
export qnmfunction
export QuasinormalModeFunction
export asad
export ComplexPlot

#For debugging
export HeunConfluentLocalExpansion, ComputeSeries, qnm, Custom
#export importqnm, qnmfunctionnew
end


# test simple solving
Random.seed!(1234)
sA = sprand(20, 20, 0.1)
b = rand(20)
A = collect(sA)

@test fnnls(sA, b) ≈ fnnls(A, b)
@test nnls(sA, b) ≈ nnls(A, b)
@test pivot(sA, b) ≈ pivot(A, b)
@test nonneg_lsq(sA,b;alg=:pivot, variant=:cache) ≈ nonneg_lsq(A,b;alg=:pivot, variant=:cache)