using Test
using Sqlite3Stats
using DataFrames
using Sqlite3Stats.SQLite

@testset "Functions" begin
    db = SQLite.DB()

    SQLite.execute(db, "create table Numbers (val float, otherval float)")
    x = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
    for v in x
        y = 11.0 - v
        SQLite.execute(db, "insert into Numbers (val, otherval) values ($(v), $(y))")
    end

    result = DBInterface.execute(db, "select val, otherval from Numbers") |> DataFrame

    Sqlite3Stats.register_functions(db)

    result = DBInterface.execute(db, "select val, otherval from Numbers") |> DataFrame
    @test size(result) == (10, 2)

    @testset "Q1" begin
        result =
            DBInterface.execute(db, "select count(val), Q1(val) as MYQ1 from Numbers") |>
            DataFrame
        @test result[!, "MYQ1"] == [3.25]
    end

    @testset "Q2" begin
        result =
            DBInterface.execute(db, "select count(val), Q2(val) as MYQ2 from Numbers") |>
            DataFrame
        @test result[!, "MYQ2"] == [5.5]
    end

    @testset "MEDIAN" begin
        result =
            DBInterface.execute(
                db,
                "select count(val), MEDIAN(val) as MYMEDIAN from Numbers",
            ) |> DataFrame
        @test result[!, "MYMEDIAN"] == [5.5]
    end

    @testset "Q3" begin
        result =
            DBInterface.execute(db, "select count(val), Q3(val) as MYQ3 from Numbers") |>
            DataFrame
        @test result[!, "MYQ3"] == [7.75]
    end

    @testset "QUANTILE" begin
        result =
            DBInterface.execute(
                db,
                "select QUANTILE(val, 0.25) as MYRESULT from Numbers",
            ) |> DataFrame
        @test result[!, "MYRESULT"] == [3.25]

        result =
            DBInterface.execute(
                db,
                "select QUANTILE(val, 0.50) as MYRESULT from Numbers",
            ) |> DataFrame
        @test result[!, "MYRESULT"] == [5.5]

        result =
            DBInterface.execute(
                db,
                "select QUANTILE(val, 0.75) as MYRESULT from Numbers",
            ) |> DataFrame
        @test result[!, "MYRESULT"] == [7.75]
    end

    @testset "COV" begin
        result =
            DBInterface.execute(db, "select COV(val, val) as MYCOV from Numbers") |>
            DataFrame
        @test result[!, "MYCOV"] == [9.166666666666666]
    end

    @testset "COR" begin
        result =
            DBInterface.execute(db, "select COR(val, val) as MYCOR from Numbers") |>
            DataFrame
        @test result[!, "MYCOR"] == [1.0]

        result =
            DBInterface.execute(db, "select COR(val, otherval) as MYCOR from Numbers") |>
            DataFrame
        @test result[!, "MYCOR"] == [-1.0]
    end

    @testset "CORSPEARMAN" begin
        result =
            DBInterface.execute(db, "select CORSPEARMAN(val, val) as MYCOR from Numbers") |>
            DataFrame
        @test result[!, "MYCOR"] == [1.0]

        result =
            DBInterface.execute(
                db,
                "select CORSPEARMAN(val, otherval) as MYCOR from Numbers",
            ) |> DataFrame
        @test result[!, "MYCOR"] == [-1.0]
    end

    @testset "CORKENDALL" begin
        result =
            DBInterface.execute(db, "select CORKENDALL(val, val) as MYCOR from Numbers") |>
            DataFrame
        @test result[!, "MYCOR"] == [1.0]

        result =
            DBInterface.execute(
                db,
                "select CORKENDALL(val, otherval) as MYCOR from Numbers",
            ) |> DataFrame
        @test result[!, "MYCOR"] == [-1.0]
    end

    @testset "MAD" begin
        result =
            DBInterface.execute(db, "select MAD(val) as MYMAD from Numbers") |> DataFrame
        @test result[!, "MYMAD"] == [3.7065055462640046]
    end

    @testset "IQR" begin
        result =
            DBInterface.execute(db, "select IQR(val) as MYIQR from Numbers") |> DataFrame
        @test result[!, "MYIQR"] == [4.5]
    end

    @testset "SKEWNESS" begin
        result =
            DBInterface.execute(db, "select SKEWNESS(val) as MYSKEW from Numbers") |>
            DataFrame
        @test result[!, "MYSKEW"] == [0.0]
    end

    @testset "KURTOSIS" begin
        result =
            DBInterface.execute(db, "select KURTOSIS(val) as MYKURT from Numbers") |>
            DataFrame
        @test result[!, "MYKURT"] == [-1.2242424242424244]
    end

    @testset "GEOMEAN" begin
        result =
            DBInterface.execute(db, "select GEOMEAN(val) as MYGEOMEAN from Numbers") |>
            DataFrame
        @test result[!, "MYGEOMEAN"] == [4.528728688116766]
    end

    @testset "HARMMEAN" begin
        result =
            DBInterface.execute(db, "select HARMMEAN(val) as MYHARMMEAN from Numbers") |>
            DataFrame
        @test result[!, "MYHARMMEAN"] == [3.414171521474055]
    end

    @testset "MODE" begin
        result = DBInterface.execute(db, "select MODE(val) as m from Numbers") |> DataFrame
        @test result[!, "m"] == [1.0]

        result =
            DBInterface.execute(db, "select MODE(otherval) as m from Numbers") |> DataFrame
        @test result[!, "m"] == [10.0]
    end

    @testset "MAXAD" begin
        result =
            DBInterface.execute(db, "select MAXAD(val, val) as m from Numbers") |> DataFrame
        @test result[!, "m"] == [0.0]

        result =
            DBInterface.execute(db, "select MAXAD(val, otherval) as m from Numbers") |>
            DataFrame
        @test result[!, "m"] == [9.0]
    end

    @testset "MEANAD" begin
        result =
            DBInterface.execute(db, "select MEANAD(val, val) as m from Numbers") |>
            DataFrame
        @test result[!, "m"] == [0.0]

        result =
            DBInterface.execute(db, "select MEANAD(val, otherval) as m from Numbers") |>
            DataFrame
        @test result[!, "m"] == [5.0]
    end

    @testset "MSD" begin
        result =
            DBInterface.execute(db, "select MSD(val, val) as m from Numbers") |> DataFrame
        @test result[!, "m"] == [0.0]

        result =
            DBInterface.execute(db, "select MSD(val, otherval) as m from Numbers") |>
            DataFrame
        @test result[!, "m"] == [33.0]
    end

    SQLite.close(db)
end

@testset "Weighted Functions" begin
    db = SQLite.DB()

    SQLite.execute(db, "create table Numbers (val float, w float)")
    x = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
    w = [
        0.01818181818181818,
        0.03636363636363636,
        0.05454545454545454,
        0.07272727272727272,
        0.09090909090909091,
        0.10909090909090909,
        0.12727272727272726,
        0.14545454545454545,
        0.16363636363636364,
        0.18181818181818182,
    ]
    for i = 1:10
        v = x[i]
        y = w[i]
        SQLite.execute(db, "insert into Numbers (val, w) values ($(v), $(y))")
    end

    result = DBInterface.execute(db, "select val, w from Numbers") |> DataFrame

    Sqlite3Stats.register_functions(db)

    result = DBInterface.execute(db, "select val, w from Numbers") |> DataFrame
    @test size(result) == (10, 2)

    @testset "WMEAN" begin
        result =
            DBInterface.execute(db, "select WMEAN(val, w) as MYRESULT from Numbers") |>
            DataFrame
        @test result[!, "MYRESULT"] == [7.0]
    end

    @testset "WMEDIAN" begin
        result =
            DBInterface.execute(db, "select WMEDIAN(val, w) as MYRESULT from Numbers") |>
            DataFrame
        @test result[!, "MYRESULT"] == [7.0]
    end

    @testset "ENTROPY" begin
        result =
            DBInterface.execute(db, "select ENTROPY(w) as MYRESULT from Numbers") |>
            DataFrame
        @test result[!, "MYRESULT"] == [2.151281720651836]
    end

    SQLite.close(db)

end

@testset "Linear Regression" begin

    db = SQLite.DB()

    SQLite.execute(db, "create table Numbers (x float, y float)")
    x = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
    y = 2.0 .* x
    for i = 1:10
        u = x[i]
        v = y[i]
        SQLite.execute(db, "insert into Numbers (x, y) values ($(u), $(v))")
    end

    Sqlite3Stats.register_functions(db)

    @testset "Raw function" begin
        x = [1.0, 2.0, 3.0, 4.0, 5.0]
        y = [2.0, 4.0, 6.0, 8.0, 10.0]
        reg = Sqlite3Stats.linear_regression(x, y)
        @test reg[1] == 0.0
        @test reg[2] == 2.0
    end

    @testset "LINSLOPE" begin
        result =
            DBInterface.execute(db, "select LINSLOPE(x, y) as MYRESULT from Numbers") |>
            DataFrame
        @test result[!, "MYRESULT"] == [2.0]
    end

    @testset "LININTERCEPT" begin
        result =
            DBInterface.execute(db, "select LININTERCEPT(x, y) as MYRESULT from Numbers") |>
            DataFrame
        @test result[!, "MYRESULT"] == [0.0]
    end

    SQLite.close(db)
end

@testset "Normal Distribution" begin

    tol = 0.001

    db = SQLite.DB()

    Sqlite3Stats.register_functions(db)

    SQLite.execute(db, "create table numbers (NUM1 float, NUM2 float)")
    for i = 1:10
        a = rand()
        b = rand() * 10
        SQLite.execute(db, "insert into numbers(num1, num2) values ($a, $b)")
    end

    @testset "QNORM" begin
        result =
            DBInterface.execute(
                db,
                "select QNORM(0.025, 0, 1) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test isapprox(result[!, "MYRESULT"], [-1.95996], atol = tol)
    end

    @testset "PNORM" begin
        result =
            DBInterface.execute(
                db,
                "select PNORM(1.9599639845400576, 0, 1) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test isapprox(result[!, "MYRESULT"], [0.0584451], atol = tol)
    end

    @testset "RNORM" begin
        result =
            DBInterface.execute(
                db,
                "select RNORM(0, 1) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test result[!, "MYRESULT"][1] < 10.0
        @test result[!, "MYRESULT"][1] > -10.0
    end

    @testset "RNORM" begin
        result =
            DBInterface.execute(
                db,
                "select RNORM(10, 1) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test result[!, "MYRESULT"][1] < 30.0
        @test result[!, "MYRESULT"][1] > -10.0
    end

    SQLite.close(db)

end

@testset "T Distribution" begin

    tol = 0.001

    db = SQLite.DB()

    Sqlite3Stats.register_functions(db)

    SQLite.execute(db, "create table numbers (NUM1 float, NUM2 float)")
    for i = 1:10
        a = rand()
        b = rand() * 10
        SQLite.execute(db, "insert into numbers(num1, num2) values ($a, $b)")
    end

    @testset "PT" begin
        result =
            DBInterface.execute(
                db,
                "select PT(1.9599639845400576,30) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test isapprox(result[!, "MYRESULT"][1], 0.970326, atol = tol)
    end

    @testset "QT" begin
        result =
            DBInterface.execute(
                db,
                "select QT(0.025, 30) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test isapprox(result[!, "MYRESULT"][1], -2.04227, atol = tol)
    end

    @testset "RT" begin
        result =
            DBInterface.execute(
                db,
                "select RT(30) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test result[!, "MYRESULT"][1] < 30.0
        @test result[!, "MYRESULT"][1] > -10.0
    end

    SQLite.close(db)
end

@testset "ChiSquare Distribution" begin

    tol = 0.001

    db = SQLite.DB()

    Sqlite3Stats.register_functions(db)

    SQLite.execute(db, "create table numbers (NUM1 float, NUM2 float)")
    for i = 1:10
        a = rand()
        b = rand() * 10
        SQLite.execute(db, "insert into numbers(num1, num2) values ($a, $b)")
    end

    @testset "PCHISQ" begin
        result =
            DBInterface.execute(
                db,
                "select PCHISQ(30, 30) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test isapprox(result[!, "MYRESULT"][1], 0.534346, atol = tol)
    end

    @testset "QCHISQ" begin
        result =
            DBInterface.execute(
                db,
                "select QCHISQ(0.05, 30) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test isapprox(result[!, "MYRESULT"][1], 18.4927, atol = tol)
    end

    @testset "RCHISQ" begin
        result =
            DBInterface.execute(
                db,
                "select RCHISQ(30) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test result[!, "MYRESULT"][1] < 100.0
        @test result[!, "MYRESULT"][1] >= 0.0
    end

    SQLite.close(db)
end

@testset "F Distribution" begin

    tol = 0.001

    db = SQLite.DB()

    Sqlite3Stats.register_functions(db)

    SQLite.execute(db, "create table numbers (NUM1 float, NUM2 float)")
    for i = 1:10
        a = rand()
        b = rand() * 10
        SQLite.execute(db, "insert into numbers(num1, num2) values ($a, $b)")
    end

    @testset "PF" begin
        result =
            DBInterface.execute(
                db,
                "select PF(0.1109452, 3, 5) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test isapprox(result[!, "MYRESULT"][1], 0.0499999, atol = tol)
    end

    @testset "QF" begin
        result =
            DBInterface.execute(
                db,
                "select QF(0.05, 3, 5) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test isapprox(result[!, "MYRESULT"][1], 0.1109452, atol = tol)
    end

    @testset "RF" begin
        result =
            DBInterface.execute(
                db,
                "select RF(3, 5) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test result[!, "MYRESULT"][1] < 100.0
        @test result[!, "MYRESULT"][1] >= 0.0
    end

    SQLite.close(db)
end

@testset "Poisson Distribution" begin

    tol = 0.001

    db = SQLite.DB()

    Sqlite3Stats.register_functions(db)

    SQLite.execute(db, "create table numbers (NUM1 float, NUM2 float)")
    for i = 1:10
        a = rand()
        b = rand() * 10
        SQLite.execute(db, "insert into numbers(num1, num2) values ($a, $b)")
    end

    @testset "PPOIS" begin
        result =
            DBInterface.execute(
                db,
                "select PPOIS(1000000, 1000000) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test isapprox(result[!, "MYRESULT"][1], 0.5, atol = tol)
    end

    @testset "QPOIS" begin
        result =
            DBInterface.execute(
                db,
                "select QPOIS(0.50, 5) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test isapprox(result[!, "MYRESULT"][1], 5.0, atol = tol)
    end

    @testset "RPOIS" begin
        result =
            DBInterface.execute(
                db,
                "select RPOIS(5) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test result[!, "MYRESULT"][1] < 100.0
        @test result[!, "MYRESULT"][1] >= 0.0
    end

    SQLite.close(db)
end

@testset "Binomial Distribution" begin

    tol = 0.001

    db = SQLite.DB()

    Sqlite3Stats.register_functions(db)

    SQLite.execute(db, "create table numbers (NUM1 float, NUM2 float)")
    for i = 1:10
        a = rand()
        b = rand() * 10
        SQLite.execute(db, "insert into numbers(num1, num2) values ($a, $b)")
    end

    @testset "PBINOM" begin
        result =
            DBInterface.execute(
                db,
                "select PBINOM(5, 10, 0.5) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test isapprox(result[!, "MYRESULT"][1], 0.62304687, atol = tol)
    end

    @testset "QBINOM" begin
        result =
            DBInterface.execute(
                db,
                "select QBINOM(0.50, 10, 0.5) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test isapprox(result[!, "MYRESULT"][1], 5.0, atol = tol)
    end

    @testset "RBINOM" begin
        result =
            DBInterface.execute(
                db,
                "select RBINOM(10, 0.5) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test result[!, "MYRESULT"][1] <= 10.0
        @test result[!, "MYRESULT"][1] >= 0.0
    end

    SQLite.close(db)
end

@testset "Uniform Distribution" begin

    tol = 0.001

    db = SQLite.DB()

    Sqlite3Stats.register_functions(db)

    SQLite.execute(db, "create table numbers (NUM1 float, NUM2 float)")
    for i = 1:10
        a = rand()
        b = rand() * 10
        SQLite.execute(db, "insert into numbers(num1, num2) values ($a, $b)")
    end

    @testset "PUNIF" begin
        result =
            DBInterface.execute(
                db,
                "select PUNIF(5, 0, 10) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test isapprox(result[!, "MYRESULT"][1], 0.5, atol = tol)
    end

    @testset "QUNIF" begin
        result =
            DBInterface.execute(
                db,
                "select QUNIF(0.5, 5, 10) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test isapprox(result[!, "MYRESULT"][1], 7.5, atol = tol)
    end

    @testset "RUNIF" begin
        result =
            DBInterface.execute(
                db,
                "select RUNIF(0, 10) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test result[!, "MYRESULT"][1] <= 10.0
        @test result[!, "MYRESULT"][1] >= 0.0
    end

    SQLite.close(db)
end

@testset "Exponential Distribution" begin

    tol = 0.001

    db = SQLite.DB()

    Sqlite3Stats.register_functions(db)

    SQLite.execute(db, "create table numbers (NUM1 float, NUM2 float)")
    for i = 1:10
        a = rand()
        b = rand() * 10
        SQLite.execute(db, "insert into numbers(num1, num2) values ($a, $b)")
    end

    @testset "PEXP" begin
        result =
            DBInterface.execute(
                db,
                "select PEXP(5, 10) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test isapprox(result[!, "MYRESULT"][1], 0.39346934, atol = tol)
    end

    @testset "QEXP" begin
        result =
            DBInterface.execute(
                db,
                "select QEXP(0.5, 10) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test isapprox(result[!, "MYRESULT"][1], 6.93147180, atol = tol)
    end

    @testset "REXP" begin
        result =
            DBInterface.execute(
                db,
                "select REXP(10.0) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test result[!, "MYRESULT"][1] <= 100.0
        @test result[!, "MYRESULT"][1] >= 0.0
    end

    SQLite.close(db)
end

@testset "Beta Distribution" begin

    tol = 0.001

    db = SQLite.DB()

    Sqlite3Stats.register_functions(db)

    SQLite.execute(db, "create table numbers (NUM1 float, NUM2 float)")
    for i = 1:10
        a = rand()
        b = rand() * 10
        SQLite.execute(db, "insert into numbers(num1, num2) values ($a, $b)")
    end

    @testset "PBETA" begin
        result =
            DBInterface.execute(
                db,
                "select PBETA(0.5, 0.12, 1) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test isapprox(result[!, "MYRESULT"][1], 0.92018, atol = tol)
    end

    @testset "QBETA" begin
        result =
            DBInterface.execute(
                db,
                "select QBETA(0.5, 0.12, 1) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test isapprox(result[!, "MYRESULT"][1], 0.00310, atol = tol)
    end

    @testset "RBETA" begin
        result =
            DBInterface.execute(
                db,
                "select RBETA(0.12, 1.0) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test result[!, "MYRESULT"][1] <= 1.0
        @test result[!, "MYRESULT"][1] >= 0.0
    end

    SQLite.close(db)
end

@testset "Cauchy Distribution" begin

    tol = 0.001

    db = SQLite.DB()

    Sqlite3Stats.register_functions(db)

    SQLite.execute(db, "create table numbers (NUM1 float, NUM2 float)")
    for i = 1:10
        a = rand()
        b = rand() * 10
        SQLite.execute(db, "insert into numbers(num1, num2) values ($a, $b)")
    end

    @testset "PCAUCHY" begin
        result =
            DBInterface.execute(
                db,
                "select PCAUCHY(0, 0, 1) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test isapprox(result[!, "MYRESULT"][1], 0.5, atol = tol)
    end

    @testset "QCAUCHY" begin
        result =
            DBInterface.execute(
                db,
                "select QCAUCHY(0.05, 0, 1) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test isapprox(result[!, "MYRESULT"][1], -6.313751, atol = tol)
    end

    @testset "RCAUCHY" begin
        result =
            DBInterface.execute(
                db,
                "select RCAUCHY(0, 1) as MYRESULT from numbers limit 1",
            ) |> DataFrame
        @test result[!, "MYRESULT"][1] isa Number
        @test result[!, "MYRESULT"][1] isa Number
    end

    SQLite.close(db)
end


# -*- coding: utf-8 -*-
# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .jl
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.3.0
#   kernelspec:
#     display_name: Julia 1.3.0
#     language: julia
#     name: julia-1.3
# ---

# + [markdown] slideshow={"slide_type": "slide"}
# # DifferentialEquations.jl
#
# website : https://juliadiffeq.org
#
# *Rackauckas, C. & Nie, Q., (2017). DifferentialEquations.jl – A Performant and Feature-Rich Ecosystem for Solving Differential Equations in Julia. Journal of Open Research Software.*
#
# DifferentialEquations.jl is the core differential equation solver package in Julia. In a nutshell, it provides solvers for:
#
# - ODEs
# - DAEs
# - SODEs, SDAEs
# - Discrete stochastic (Gillespie) equations, mixed with ODEs/SDEs (jump diffusions)
# - DDEs
# - PDEs
#
# It provides wrappers to the common C++ and Fortran libraries, along with native Julia implementations with efficient generic algorithms.
#
# 40 contributors
# -

using DifferentialEquations, Plots

# + [markdown] slideshow={"slide_type": "slide"}
# ## Case Study: Lotka-Volterra in many ways
#
# First, the basic ODE with functions

# + slideshow={"slide_type": "slide"}
function f(du,u,p,t)
  x, y = u
  α, β, γ, δ = p
  du[1] = α*x - β*x*y
  du[2] = -γ*y + δ*x*y
end

p = (1.5,1.0,3.0,1.0)
u0 = [1.0;1.0]
tspan = (0.0,10.0)
prob1 = ODEProblem(f,u0,tspan,p)
sol = solve(prob1)
plot(sol)
# -

plot(sol; vars=(1, 2))

# + [markdown] slideshow={"slide_type": "slide"}
# ## DSLs and Metaprogramming for Syntactic Sugar
#
# Now let's use a DSL defined by a macro:
# -

using ParameterizedFunctions

# +
lorenz63 = @ode_def Lorenz begin
  dx = σ*(y-x)
  dy = ρ*x-y-x*z
  dz = x*y-β*z
end σ β ρ

u0 = [1., 5., 10.]
tspan = (0., 100.)
σ, β, ρ = 10.0, 8.0/3.0, 28.0
p = [σ, β, ρ]
prob = ODEProblem(lorenz63, u0, tspan, p)
sol = solve(prob);
# -

plot(sol, plotdensity=10000, vars=(1, 2, 3))

# + [markdown] slideshow={"slide_type": "slide"}
# ## Optimize the Solver By Choosing from the Largest Set of Methods
#
# http://docs.juliadiffeq.org/latest/solvers/ode_solve.html
#
# https://github.com/JuliaDiffEq/DiffEqBenchmarks.jl
# -

using BenchmarkTools

# + slideshow={"slide_type": "slide"}
@benchmark solve(prob,Vern9())

# + slideshow={"slide_type": "fragment"}
using LSODA
@benchmark solve(prob1,lsoda()) # Default method of SciPy, deSolve, etc.

# + [markdown] slideshow={"slide_type": "slide"}
# ## Generic Algorithms to Propogate Uncertainty

# + slideshow={"slide_type": "fragment"}
using Measurements
u0 = [1.0 ± 0.0 ,1.0 ± 0.0]
p = (1.5 ± 0.0,1.0 ± 0.1,3.0 ± 0.2,1.0 ± 0.1)
tspan = (0.0,10.0)
prob_error = ODEProblem(f,u0,tspan,p)
sol = solve(prob_error,Tsit5(), saveat=0.2)
plot(sol)

# + [markdown] slideshow={"slide_type": "slide"}
# ## Generic Algorithms to Calculate Sensitivities

# + slideshow={"slide_type": "fragment"}
using ForwardDiff: Dual
p1dual = Dual{Float64}(1.5, (1.0, 0.0))
p2dual = Dual{Float64}(1.0, (0.0, 1.0))
pdual = (p1dual, p2dual, 3.0, 1.0)
u0 = [Dual{Float64}(1.0, (0.0, 0.0)),Dual{Float64}(1.0, (0.0, 0.0))]
prob_dual = ODEProblem(f,u0,tspan,pdual)
sol_dual = solve(prob_dual,Tsit5(), saveat=0.2)

timepoints = [i for i in sol_dual.t]
sensitivity_forward_diff = [i[1].partials.values[1] for i in sol_dual.u]
Plots.plot(timepoints,sensitivity_forward_diff,title="dx/da",lw=3,xaxis="Time")

# + [markdown] slideshow={"slide_type": "slide"}
# ## Generic Algorithms for Arbitrary Precision

# + slideshow={"slide_type": "fragment"}
p = big.((1.5,1.0,3.0,1.0))
u0 = big.([1.0,1.0])
tspan = big.((0.0,10.0))
prob1 = ODEProblem(f,u0,tspan,p)
sol = solve(prob1,Vern9(),abstol=1e-25,reltol=1e-25)
sol[10]

# + [markdown] slideshow={"slide_type": "slide"}
# ## Generic Algorithms for Units

# + slideshow={"slide_type": "slide"}
p = (1.5,1.0/u"kg",3.0,1.0/u"kg")./u"s"
u0 = [1.0u"kg",1.0u"kg"]
tspan = (0.0u"s",10.0u"s")
prob_units = ODEProblem(f,u0,tspan,p)
sol = solve(prob_units,Tsit5())
sol.t[10],sol[10]

# + [markdown] slideshow={"slide_type": "slide"}
# # Other Features of DifferentialEquations.jl

# + [markdown] slideshow={"slide_type": "slide"}
# ## The solution is a continuous function by default

# + slideshow={"slide_type": "fragment"}
sol(5.5u"s")

# + [markdown] slideshow={"slide_type": "slide"}
# ## Easily Change to Stochastic Differential Equations

# + slideshow={"slide_type": "fragment"}
function g(du,u,p,t)
  du[1] = 0.2u[1]
  du[2] = 0.2u[2]
end
p = (1.5,1.0,3.0,1.0)
u0 = [1.0;1.0]
tspan = (0.0,10.0)
prob1 = SDEProblem(f,g,u0,tspan,p)
sol = solve(prob1)
plot(sol)

# + [markdown] slideshow={"slide_type": "slide"}
# ## And Delay Differential Equations

# + slideshow={"slide_type": "fragment"}
function f(du,u,h,p,t)
  x, y = u
  α,β,γ,δ = p
  du[1] = α*h(p,t-1)[1] - β*x*y
  du[2] = -γ*y + δ*x*y
end
p = (1.5,1.0,3.0,1.0); u0 = [1.0;1.0]
tspan = (0.0,10.0)
_h(p,t) = ones(2)
prob1 = DDEProblem(f,u0,_h,tspan,p)
sol = solve(prob1)
plot(sol)


#=

  Set covering problem in Julia ConstraintSolver.jl

  Example 9.1-2, page 354ff, from Taha "Operations Research - An Introduction"
  Minimize the number of security telephones in street corners on a campus.

  AMPL model: http://taha.ineg.uark.edu/setcover.txt

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

=#
using ConstraintSolver, JuMP
using Cbc, GLPK, Ipopt
const CS = ConstraintSolver
include("constraints_utils.jl")

function set_covering2(problem,print_solutions=true,all_solutions=true)

    cbc_optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0)
    glpk_optimizer = optimizer_with_attributes(GLPK.Optimizer)
    ipopt_optimizer = optimizer_with_attributes(Ipopt.Optimizer)

    model = Model(optimizer_with_attributes(CS.Optimizer,   # "all_solutions"=> all_solutions,
                                                            "all_optimal_solutions"=>true,
                                                            "logging"=>[],

                                                            "traverse_strategy"=>:BFS,
                                                            # "traverse_strategy"=>:DFS,
                                                            # "traverse_strategy"=>:DBFS,

                                                            # "branch_split"=>:Smallest,
                                                            # "branch_split"=>:Biggest,
                                                            "branch_split"=>:InHalf,

                                                            # https://wikunia.github.io/ConstraintSolver.jl/stable/options/#branch_strategy-(:Auto)
                                                            "branch_strategy" => :IMPS, # default
                                                            # "branch_strategy" => :ABS, # Activity Based Search
                                                            # "activity.decay" => 0.999, # default 0.999
                                                            # "activity.max_probes" => 10, # default, 10
                                                            # "activity.max_confidence_deviation" => 20, # default 20

                                                            # "simplify"=>false,
                                                            # "simplify"=>true, # default

                                                            "time_limit"=>6,

                                                            # "backtrack" => false, # default true
                                                            # "backtrack_sorting" => false, # default true

                                                            # "lp_optimizer" => cbc_optimizer,
                                                            # "lp_optimizer" => glpk_optimizer,
                                                            # "lp_optimizer" => ipopt_optimizer,
                                        ))

    n = problem[:n]
    corners = problem[:corners]
    num_rows,_ = size(corners)

    # Where to place the telephones
    @variable(model, x[1:n], Bin)
    @variable(model, 1 <= z <= n, Int)

    # All streets must be covered
    for i in 1:num_rows
        @constraint(model,x[corners[i,1]] + x[corners[i,2]] >= 1)
    end
    @constraint(model, sum(x) == z)
    @objective(model,Min,z)

    # Solve the problem
    optimize!(model)

    status = JuMP.termination_status(model)
    # println("status:$status")
    if status == MOI.OPTIMAL
        num_sols = MOI.get(model, MOI.ResultCount())
        println("num_sols:$num_sols\n")
        if print_solutions
            for sol in 1:num_sols
                println("solution #$sol")
                x_val = convert.(Integer,JuMP.value.(x; result=sol))
                z_val = convert.(Integer,JuMP.value.(z; result=sol))
                println("z:$z_val")
                println("x:$x_val")

            end
        end
    else
        println("status:$status")
    end

    return status
end

problem = Dict(
   :n => 8,
   :corners => resize_matrix([[1,2],
                              [2,3],
                              [4,5],
                              [7,8],
                              [6,7],
                              [2,6],
                              [1,6],
                              [4,7],
                              [2,4],
                              [5,8],
                              [3,5]]),

)
@time set_covering2(problem)


# v0.6

function combsort!(x::Array)::Array
    gap, swaps = length(x), true
    while gap > 1 || swaps
        gap = floor(Int, gap / 1.25)
        i, swaps = 0, false
        while i + gap < length(x)
            if x[i+1] > x[i+1+gap]
                x[i+1], x[i+1+gap] = x[i+1+gap], x[i+1]
                swaps = true
            end
            i += 1
        end
    end
    return x
end

x = randn(100)
@show x combsort!(x)
@assert issorted(x)


#=
using PETSc
using BaseTestNext
# test timestepping (TS) functions
ST = Float64
=#

@testset "TS" begin
  # solve the equation f =  [u1;u2] = [t + 1; 2t + 1] using forward Euler
  # This should be an exact case

  # the jacobian is always the identity for a case where u = f(t) only
  A = Mat(ST, 2, 2)
  A[1,1] = 1.0
  A[2,2] = 1.0

  # evaluate rhs u_t
  function rhs1(ts::TS, t, u::Vec, f::Vec, ctx)

    f_local = LocalVector(f)

    f_local[1] = 1.0
    f_local[2] = 2.0
    restore(f_local)

    return 0
  end

  ts = TS(ST, PETSc.C.TS_LINEAR, PETSc.C.TSEULER)

  set_rhs_function(ts, NullVec[ST], rhs1)
  set_rhs_jac(ts, A, A, ComputeRHSJacobianConstant)

  set_times(ts, 0.0, 0.1, 10, 50.0)  # limited by number of timesteps

  # hold IC/final solution
  u = Vec(ST, 2)
  u[1] = 1.0
  u[2] = 1.0

  solve!(ts, u)
  #not sure how to test this other than "doesn't explode everywhere"
  ksp = KSP(ts)

  # same as above, but using set_ic
  ts = TS(ST, PETSc.C.TS_LINEAR, PETSc.C.TSEULER)

  set_rhs_function(ts, NullVec[ST], rhs1)
  set_rhs_jac(ts, A, A, ComputeRHSJacobianConstant)

  set_times(ts, 0.0, 0.1, 10, 50.0)  # limited by number of timesteps

  u = Vec(ST, 2)
  u[1] = 1.0
  u[2] = 1.0
  set_ic(ts, u)
  solve!(ts)

#  petscview(u)
  @test u[1] ≈ ST(2.0)
  @test u[2] ≈ ST(3.0)

  # solve the 1D heat equation u_t = u_xx with 2nd order space, linear time
  # homogneous Dirchlet BCs

  dt = 0.05
  nsteps = 10
  dx = 0.2  # actually dx^2
  nx = 5  # number of points in domain (including boundaries
  r = dt/dx

  A_julia = zeros(ST, nx, nx)
  A_julia[1,1] = 1.0
  A_julia[nx,nx] = 1.0

  # interior discretization
  for i=2:(nx-1)
    A_julia[i, i-1] = r
    A_julia[i, i] = 1 - 2*r
    A_julia[i, i+1] = r
  end

  u_julia = ones(ST, nx)
  u_julia[1] = 0.0  # homogenous direchlet
  u_julia[nx] = 0.0

  # solve in julia
  for t = 1:(nsteps+0)
    u_next = A_julia*u_julia
    u_julia[:] = u_next
  end

  # now solve in Petsc

  function rhs_jacobian(ts::TS, t, u, A::Mat, B::Mat, ctx)
    dx = ctx[1]
    nx, ny = size(A)
    A[1,1] = 1.0
    A[nx, nx] = 1.0

    for i=2:(nx-1)
      A[i, i-1] = 1/dx
      A[i, i] = -2/dx
      A[i, i+1] = 1/dx
    end

    return 0
  end

  function rhs_func(ts::TS, t, u::Vec, F::Vec, ctx)
    dx = ctx[1]
    nx = length(u)
    u_arr = LocalVector_readonly(u)
    f_arr = LocalVector(F)

    f_arr[1] = 0.0
    f_arr[nx] = 0.0

    for i=2:(nx-1)
      f_arr[i] = u_arr[i-1]/dx + -2*u_arr[i]/dx + u_arr[i+1]/dx
    end

    restore(u_arr)
    restore(f_arr)

    return 0
  end

  ts = TS(ST, PETSc.C.TS_LINEAR, PETSc.C.TSEULER)

  A = Mat(ST, nx, nx)
  u = Vec(ST, nx)

  # set IC
  for i=2:(nx-1)
    u[i] = 1.0
  end

  ctx1 = (dx,)
  ctx2 = (dx,)
  set_rhs_function(ts, NullVec[ST], rhs_func, ctx1)
  set_rhs_jac(ts, A, A, rhs_jacobian, ctx2)

  set_times(ts, 0.0, dt, nsteps , 50.0)  # limited by number of timesteps

  solve!(ts, u)

  for i=1:nx
    @test u[i] ≈ u_julia[i]
  end

  PETSc.PetscDestroy(ts)
  @test PETSc.isfinalized(ts)

  # now solve the same problem using implicit euler
  dt = 0.05
  nsteps = 10
  dx = 0.2  # actually dx^2
  nx = 5  # number of points in domain (including boundaries
  r = dt/dx

  A_julia = zeros(ST, nx, nx)
  A_julia[1,1] = 1.0
  A_julia[nx,nx] = 1.0

  # interior discretization
  for i=2:(nx-1)
    A_julia[i, i-1] = -r
    A_julia[i, i] = 1 + 2*r
    A_julia[i, i+1] = -r
  end

  u_julia = ones(ST, nx)
  u_julia[1] = 0.0  # homogenous direchlet
  u_julia[nx] = 0.0

  # solve in julia
  for t = 1:(nsteps+0)
    u_next = A_julia\u_julia
    u_julia[:] = u_next
  end

  function lhs_func(ts::TS, t, u::Vec, ut::Vec, F::Vec, ctx)
    dx = ctx[1]
    nx = length(u)
    u_arr = LocalVector_readonly(u)
    ut_arr = LocalVector_readonly(ut)
    f_arr = LocalVector(F)

    f_arr[1] = u_arr[1]
    f_arr[nx] = u_arr[nx]

    for i=2:(nx-1)
      f_arr[i] = ut[i] - (u_arr[i-1]/dx + -2*u_arr[i]/dx + u_arr[i+1]/dx)
    end

    restore(u_arr)
    restore(ut_arr)
    restore(f_arr)

    return 0
  end

  function lhs_jacobian(ts::TS, u::Vec, ut::Vec, a, A::Mat, B::Mat, ctx)
    dx = ctx[1]
    nx, ny = size(A)
    A[1,1] = 1.0
    A[nx, nx] = 1.0

    for i=2:(nx-1)
      A[i, i-1] = -1/dx
      A[i, i] = 2/dx + a
      A[i, i+1] = -1/dx
    end

    AssemblyBegin(A, PETSc.C.MAT_FINAL_ASSEMBLY)
    AssemblyEnd(A, PETSc.C.MAT_FINAL_ASSEMBLY)

    return 0
  end

  ts = TS(ST, PETSc.C.TS_LINEAR, PETSc.C.TSBEULER)

  A = Mat(ST, nx, nx, nz = 3)
  A[1,1] = 1.0
  A[nx, nx] = 1.0
  for i=2:(nx-1)
    A[i, i-1] = 1.0
    A[i, i] = 1.0
    A[i, i+1] = 1.0
  end
  AssemblyBegin(A, PETSc.C.MAT_FINAL_ASSEMBLY)
  AssemblyEnd(A, PETSc.C.MAT_FINAL_ASSEMBLY)
  u = Vec(ST, nx)

  # set IC
  for i=2:(nx-1)
    u[i] = 1.0
  end

  ctx1 = (dx,)
  ctx2 = (dx,)
  set_lhs_function(ts, NullVec[ST], lhs_func, ctx1)
  set_lhs_jac(ts, A, A, lhs_jacobian, ctx2)

  set_times(ts, 0.0, dt, nsteps , 50.0)  # limited by number of timesteps
  AssemblyBegin(u)
  AssemblyEnd(u)
  solve!(ts, u)

  for i=1:nx
    @test u[i] ≈ u_julia[i]
  end

  PETSc.PetscDestroy(ts)

end


"""
    Valuation

A data structure for approximating the valuation of a solution path ``x(t)``.
"""
mutable struct Valuation
    val_x::Vector{Float64}
    val_tẋ::Vector{Float64}
    Δval_x::Vector{Float64}
    Δval_tẋ::Vector{Float64}

    # Data for computing the val_x and val_tẋ and ν̈ by finite differences
    val_x_data::NTuple{2,Vector{Float64}}
    val_ẋ_data::NTuple{2,Vector{Float64}}
    logx_data::NTuple{2,Vector{Float64}}
    logẋ_data::NTuple{2,Vector{Float64}}
    logt_data::NTuple{2,Float64}
end

Valuation(x::AbstractVector) = Valuation(length(x))
Valuation(n::Integer) =
    Valuation((zeros(n) for i = 1:4)..., ((zeros(n), zeros(n)) for i = 1:4)..., (NaN, NaN))

function Base.show(io::IO, val::Valuation)
    print(io, "Valuation :")
    for field in [:val_x, :val_tẋ, :Δval_x, :Δval_tẋ]
        vs = [Printf.@sprintf("%#.4g", v) for v in getfield(val, field)]
        print(io, "\n • ", field, " → ", "[", join(vs, ", "), "]")
    end
end
Base.show(io::IO, ::MIME"application/prs.juno.inline", v::Valuation) = v

"""
    init!(val::Valuation)

Initialize the valuation estimator to start from scratch.
"""
function init!(val::Valuation)
    val.val_x .= 0.0
    val.val_tẋ .= 0.0
    val.Δval_x .= 0.0
    val.Δval_tẋ .= 0.0
    val.val_x_data[1] .= val.val_x_data[2] .= 0.0
    val.val_ẋ_data[1] .= val.val_ẋ_data[2] .= 0.0
    val.logx_data[1] .= val.logx_data[2] .= 0.0
    val.logẋ_data[1] .= val.logẋ_data[2] .= 0.0
    val.logt_data = (NaN, NaN)
    val
end

function ν(x, ẋ, t)
    u, v = reim(x)
    u¹, v¹ = reim(ẋ)
    xx = abs2(x)

    μ = u * u¹ + v * v¹
    l = μ / xx

    t * l
end

function ν_ν¹(x, ẋ, x², t)
    u, v = reim(x)
    u¹, v¹ = reim(ẋ)
    u², v² = reim(x²)

    xx = abs2(x)

    μ = u * u¹ + v * v¹
    l = μ / xx

    μ¹ = u * u² + u¹^2 + v * v² + v¹^2
    l¹ = μ¹ / xx - 2 * l^2

    t * l, t * l¹ + l
end

logabs(x) = log(fast_abs(x))

function update!(val::Valuation, pred::Predictor{AD{N}}, t::Real) where {N}
    # use analytic expressions if derivatives are computed with automatic differentiation
    if N ≥ 3
        update_analytic!(val, pred.tx³, t)
        return val
    end

    # otherwise use finite difference scheme
    logt = log(t)
    ν₂, ν₁ = val.val_x_data
    logx₂, logx₁, = val.logx_data
    logẋ₂, logẋ₁, = val.logẋ_data
    logt₂, logt₁ = val.logt_data
    val_ẋ₂, val_ẋ₁ = val.val_ẋ_data
    for (i, (xᵢ, ẋᵢ)) in enumerate(pred.tx¹)
        logxᵢ = logabs(xᵢ)
        νᵢ = ν(xᵢ, ẋᵢ, t)
        Δνᵢ = finite_diff(νᵢ, logt, ν₂[i], logt₂, ν₁[i], logt₁)

        logẋᵢ = logabs(ẋᵢ)
        val_ẋᵢ = finite_diff(logẋᵢ, logt, logẋ₂[i], logt₂, logẋ₁[i], logt₁)
        Δval_ẋᵢ = finite_diff(val_ẋᵢ, logt, val_ẋ₂[i], logt₂, val_ẋ₁[i], logt₁)

        val.val_x[i] = νᵢ
        val.Δval_x[i] = Δνᵢ
        val.val_tẋ[i] = val_ẋᵢ + 1
        val.Δval_tẋ[i] = Δval_ẋᵢ

        ν₂[i], ν₁[i] = νᵢ, ν₂[i]
        logx₂[i], logx₁[i] = logxᵢ, logx₂[i]
        logẋ₂[i], logẋ₁[i] = logẋᵢ, logẋ₂[i]
        val_ẋ₂[i], val_ẋ₁[i] = val_ẋᵢ, val_ẋ₂[i]
    end
    val.logt_data = (logt, logt₂)

    val
end

function update_analytic!(val::Valuation, tx::TaylorVector, t::Real)
    for i in eachindex(tx)
        xᵢ, ẋᵢ, x²ᵢ, x³ᵢ = tx[i]

        ν, ν¹ = ν_ν¹(xᵢ, ẋᵢ, 2x²ᵢ, t)
        val.val_x[i] = ν
        val.Δval_x[i] = t * ν¹

        ν, ν¹ = ν_ν¹(ẋᵢ, 2x²ᵢ, 6x³ᵢ, t)
        val.val_tẋ[i] = ν + 1
        val.Δval_tẋ[i] = t * ν¹
    end

    val
end

"""
    finite_diff(ν₃, s₃, ν₂, s₂, ν₁, s₁)

Compute the derivative `ν̇` of a function ``ν(s)`` at `s₃` by using a finite
difference scheme. This uses the values `νᵢ = ν(sᵢ)` for ``i=1,…,3``.
Since we have a non-uniform grid, we need a more elaborate difference scheme.
The implementation follows the formulas derived in [^BS05].

[^BS05]: Bowen, M. K., and Ronald Smith. "Derivative formulae and errors for non-uniformly
  spaced points." Proceedings of the Royal Society A: Mathematical, Physical and Engineering
  Sciences 461.2059 (2005): 1975-1997.
"""
function finite_diff(ν, s, ν₂, s₂, ν₁, s₁)
    Δ₁, Δ₂, Δ₁₂ = s - s₁, s - s₂, s₁ - s₂
    ν̇ = (Δ₂ * ν₁) / (Δ₁₂ * Δ₁) - ((Δ₁₂ + Δ₂) * ν₂) / (Δ₁₂ * Δ₂) - (Δ₁₂ * ν) / (Δ₁ * Δ₂)
    # ν̈ = -2ν₁ / (Δ₁₂ * Δ₁) + 2ν₂ / (Δ₁₂ * Δ₂) + 2ν / (Δ₁ * Δ₂)
    ν̇
end

function analyze(
    val::Valuation;
    finite_tol::Float64,
    zero_is_finite::Bool,
    max_winding_number::Int,
)
    @unpack val_x, val_tẋ, Δval_x, Δval_tẋ = val

    at_infinity_tol = at_zero_tol = Inf
    finite = true
    δ = inv(max_winding_number)
    for (i, val_xᵢ) in enumerate(val_x)
        abs_Δ = abs(Δval_x[i])
        ε∞ = max(
            abs(1.0 - val_tẋ[i] / val_xᵢ),
            abs(Δval_x[i] / val_xᵢ),
            abs(Δval_tẋ[i] / val_tẋ[i]),
        )
        if isnan(abs_Δ)
            finite = false
            continue
        end
        # ∞ check
        if val_xᵢ + ε∞ < -(δ + finite_tol)
            at_infinity_tol = min(ε∞, at_infinity_tol)
        end

        # 0 check
        if !zero_is_finite && val_xᵢ - ε∞ > (δ + finite_tol)
            at_zero_tol = min(ε∞, at_zero_tol)
        end

        # finite
        # Case a: val(x) = 0
        if abs(val_xᵢ) < finite_tol
            if abs_Δ > finite_tol || val_tẋ[i] ≤ 0
                finite = false
            end
            # Case b: val(x) = val(tẋ) > 0
        elseif zero_is_finite && val_xᵢ > finite_tol
            if ε∞ > finite_tol
                finite = false
            end
        else
            finite = false
        end
    end

    return (
        val_finite = finite,
        at_infinity_tol = at_infinity_tol,
        at_zero_tol = at_zero_tol,
    )
end

function validate_coord_growth(
    val::Valuation,
    scale_old,
    scale_new;
    finite_tol::Float64,
    at_infinity_tol::Float64,
    tol::Float64,
)
    @unpack val_x, val_tẋ, Δval_x, Δval_tẋ = val

    for (i, val_xᵢ) in enumerate(val_x)
        ε∞ = max(
            abs(1.0 - val_tẋ[i] / val_xᵢ),
            abs(Δval_x[i] / val_xᵢ),
            abs(Δval_tẋ[i] / val_tẋ[i]),
        )
        # ∞ check
        if val_xᵢ + ε∞ < -finite_tol && ε∞ < at_infinity_tol
            if scale_new[i] / scale_old[i] > tol
                return true
            end
        end
    end

    false
end


# From Horowitz paper:
# choosing initial conditions uniformly
# from the simplex of species concentrations with total
# sum of concentrations fixed to 25
# (on average 1 mol/vol of each species).
function rand_concentrations(n_species::Integer, total_conc::Real)
    # see 'Uniform sampling from a simplex' from StackExchange
    # Generate a list of sorted numbers from 0 to 1
    lst = cat(dims=1, [0.0], sort(rand(n_species-1)), [1.0])
    # Take differences between consecutive elements
    return total_conc*(lst[2:end]-lst[1:end-1])
end


using PyCall
using Distributions

@pyimport matplotlib.pyplot as plt
@pyimport matplotlib.figure as pyfigure

function part1()

  un = Uniform(-2,2)

  samples = rand(un,500)
  x = 1:500
  oneoverx = map(elem -> 1/elem,x)

  plt.figure(figsize=(20,10))

  # unif
  plt.xlim(xmin=-50,xmax=550)
  plt.ylim(ymin=-5,ymax=5)
  plt.grid()
  plt.plot(x,samples,color="red")

  # sin(x)
  plt.xlim(xmin=-50,xmax=550)
  plt.ylim(ymin=-5,ymax=5)
  plt.grid()
  plt.plot(1:500,sin(x),color="blue")

  # cos(x)
  plt.xlim(xmin=-50,xmax=550)
  plt.ylim(ymin=-5,ymax=5)
  plt.grid()
  plt.plot(1:500,cos(x),color="green")

  # sin(1/x)
  plt.xlim(xmin=-50,xmax=550)
  plt.ylim(ymin=-5,ymax=5)
  plt.grid()
  plt.plot(1:500,sin(oneoverx),color="black")

  # cos(1/x)
  plt.xlim(xmin=-50,xmax=550)
  plt.ylim(ymin=-5,ymax=5)
  plt.grid()
  plt.plot(1:500,cos(oneoverx),color="pink")

  plt.show()

end

function part2()
  un = Uniform(-2,2)

  samples = rand(un,500)
  x = 1:500
  oneoverx = map(elem -> 1/elem,x)

  plt.figure(figsize=(20,10))

  # unif
  plt.subplot(331)
  plt.xlim(xmin=-50,xmax=550)
  plt.ylim(ymin=-5,ymax=5)
  plt.grid()
  plt.plot(x,samples,color="red")

  # sin(x)
  plt.subplot(332)
  plt.xlim(xmin=-50,xmax=550)
  plt.ylim(ymin=-5,ymax=5)
  plt.grid()
  plt.plot(1:500,sin(x),color="blue")

  # cos(x)
  plt.subplot(333)
  plt.xlim(xmin=-50,xmax=550)
  plt.ylim(ymin=-5,ymax=5)
  plt.grid()
  plt.plot(1:500,cos(x),color="green")

  # sin(1/x)
  plt.subplot(334)
  plt.xlim(xmin=-50,xmax=550)
  plt.ylim(ymin=-5,ymax=5)
  plt.grid()
  plt.plot(1:500,sin(oneoverx),color="black")

  # cos(1/x)
  plt.subplot(335)
  plt.xlim(xmin=-50,xmax=550)
  plt.ylim(ymin=-5,ymax=5)
  plt.grid()
  plt.plot(1:500,cos(oneoverx),color="pink")

  plt.show()

end

part2()


# kruskal_wallis.jl
# Kruskal-Wallis rank sum test
#
# Copyright (C) 2014   Christoph Sawade
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

export KruskalWallisTest

immutable KruskalWallisTest <: HypothesisTest
    n_i::Vector{Int}         # number of observations in each group
    df::Int                  # degrees of freedom
    R_i::Vector{Float64}     # rank sums
    H::Float64               # test statistic: chi-square statistic
    tie_adjustment::Float64  # adjustment for ties
end

function KruskalWallisTest{T<:Real}(groups::AbstractVector{T}...)
    (H, R_i, tieadj, n_i) = kwstats(groups...)
    if length(groups)<=3 && any(n_i .< 6)
        warn("This test is only asymptotically correct and might be inaccurate for the given group size")
    end
    df = length(groups) - 1
    KruskalWallisTest(n_i, df, R_i, H, tieadj)
end

testname(::KruskalWallisTest) = "Kruskal-Wallis rank sum test (chi-square approximation)"
population_param_of_interest(x::KruskalWallisTest) = ("Location parameters", "all equal", NaN) # parameter of interest: name, value under h0, point estimate

function show_params(io::IO, x::KruskalWallisTest, ident)
    println(io, ident, "number of observation in each group: ", x.n_i)
    println(io, ident, "χ²-statistic:                        ", x.H)
    println(io, ident, "rank sums:                           ", x.R_i)
    println(io, ident, "degrees of freedom:                  ", x.df)
    println(io, ident, "adjustment for ties:                 ", x.tie_adjustment)
end

pvalue(x::KruskalWallisTest) = pvalue(Chisq(x.df), x.H; tail=:right)

## helper

# Get H, rank sums, and tie adjustment for Kruskal-Wallis test
function kwstats{T<:Real}(groups::AbstractVector{T}...)
    n_i = [length(g) for g in groups]
    n = sum(n_i)

    # get ranks and adjustment for ties
    (ranks, tieadj) = tiedrank_adj([groups...;])
    C = 1-tieadj/(n^3 - n)

    # compute rank sums
    R_i = Array(Float64, length(groups))
    n_end = 0
    for i=1:length(groups)
        R_i[i] = sum(ranks[n_end+1:n_end+n_i[i]])
        n_end += n_i[i]
    end

    # compute test statistic and correct for ties
    H = 12 * sum(R_i.^2./n_i) / (n * (n + 1)) - 3 * (n + 1)
    H /= C

    (H, R_i, C, n_i)
end


using BenchmarkTools
using FinancialMonteCarlo
S0 = 100.0;
K = 100.0;
r = 0.02;
T = 1.0;
d = 0.01;
D = 90.0;
Nsim = 10000;
Nstep = 30;
sigma = 0.2;
mc = MonteCarloConfiguration(Nsim, Nstep);
toll = 1e-3;
rfCurve = ZeroRate(r);
FwdData = Forward(T)
EUData = EuropeanOption(T, K)
AMData = AmericanOption(T, K)
BarrierData = BarrierOptionDownOut(T, K, D)
AsianFloatingStrikeData = AsianFloatingStrikeOption(T)
AsianFixedStrikeData = AsianFixedStrikeOption(T, K)
Model = BlackScholesProcess(sigma, Underlying(S0, d));
@btime EuPrice = pricer(Model, rfCurve, mc, EUData);


using SymPy, Plots
include("../SFFM.jl")

tempCMEParams = Dict()
open("src/CMEParamsData/iltcme.json", "r") do f
    global tempCMEParams
    tempCMEParams=JSON.parse(f)  # parse and transform data
end

s = SymPy.Sym("s")
c = SymPy.Sym("c")
a = SymPy.Sym("a")
b = SymPy.Sym("b")
k = SymPy.Sym("k")
omega = SymPy.Sym("omega")

term0 = c/(1+s)
termk = (a*(1+s) + b*k*omega) / ((1+s)^2 + (k*omega)^2)

d1term0 = -SymPy.diff(term0,s,1)
d1termk = -SymPy.diff(termk,s,1)
d5term0 = -SymPy.diff(term0,s,5)
d5termk = -SymPy.diff(termk,s,5)

keysArray = []
m0Array = []
m1Array = []
m5Array = []
for key in keys(SFFM.CMEParams)
    if key < 200
        print(key); print(",")
        cVal = SFFM.CMEParams[key]["c"]
        omegaVal = SFFM.CMEParams[key]["omega"]
        aVec = SFFM.CMEParams[key]["a"]
        bVec = SFFM.CMEParams[key]["b"]
        n = length(aVec)

        m0 = N(term0(s=>0, c=>cVal, omega=>omegaVal))
        m1 = N(d1term0(s=>0, c=>cVal, omega=>omegaVal))
        m5 = N(d5term0(s=>0, c=>cVal, omega=>omegaVal))
        push!(keysArray, key)
        for kVal in 1:n
            m0 += N(termk(
                s=>0,
                c=>cVal, omega=>omegaVal,
                a=>aVec[kVal], b=>bVec[kVal],
                k=>kVal
            ))
            m1 += N(d1termk(
                s=>0,
                c=>cVal, omega=>omegaVal,
                a=>aVec[kVal], b=>bVec[kVal],
                k=>kVal
            ))
            m5 += N(d5termk(
                s=>0,
                c=>cVal, omega=>omegaVal,
                a=>aVec[kVal], b=>bVec[kVal],
                k=>kVal
            ))
        end
        push!(m0Array, m0)
        push!(m1Array, m1)
        push!(m5Array, m5)
    end
end

display(scatter(keysArray, m0Array))
display(scatter(keysArray, m1Array))
display(scatter(keysArray, log.((m5Array).^(1/5)-m1Array)))


module Test

using sbp.Config: Tv
using Test

function taylor_poly(x::AbstractArray, p::Int64)
        y = x.^p / factorial(p)
        return y
end

function test_first_derivative(D::AbstractArray, xl::AbstractArray, xr::AbstractArray, p_int::Int64, p_bnd::Int64, ml::Int64, mr::Int64)

        @test isapprox(interior(D*xr.^0, ml, mr), interior(eps(Tv) .* xl, ml, mr), atol=1.0)
        for p in 1:p_int
                yr = taylor_poly(xr, p)
                yl = taylor_poly(xl, p - 1)
                @test isapprox(interior(D*yr, ml, mr), interior(yl, ml, mr))
        end

        @test isapprox(boundary(D*xr.^0, ml, mr), boundary(eps(Tv) .* xl, ml, mr), atol=1.0)
        for p in 1:p_bnd
                yr = taylor_poly(xr, p)
                yl = taylor_poly(xl, p - 1)
                @test isapprox(boundary(D*yr, ml, mr), boundary(yl, ml, mr))
        end

end

function test_interpolation(P::AbstractArray, xl::AbstractArray, xr::AbstractArray, p_int::Int64, p_bnd::Int64, ml::Int64, mr::Int64)

        for p in 0:p_int
                yr = taylor_poly(xr, p)
                yl = taylor_poly(xl, p)
                @test isapprox(interior(P*yr, ml, mr), interior(yl, ml, mr))
        end

        for p in 0:p_bnd
                yr = taylor_poly(xr, p)
                yl = taylor_poly(xl, p)
                @test isapprox(boundary(P*yr, ml, mr), boundary(yl, ml, mr))
        end

end

function test_quadrature(H::AbstractArray, x::AbstractArray, pv::Int64)

        for p in 0:pv
                xr = taylor_poly(x, p)
                xl = taylor_poly(x, p + 1)
                @test isapprox( sum(H*xr), xl[end] - xl[1])
        end
end

function test_first_derivative_sbp(Hp::AbstractArray,
                                   Dp::AbstractArray,
                                   Hm::AbstractArray,
                                   Dm::AbstractArray,
                                   B::AbstractArray)
        @test isapprox(Hp*Dp + (Hm*Dm)', B)
end

function test_first_derivative_sbp(H::AbstractArray,
                                   D::AbstractArray,
                                   B::AbstractArray)
        @test isapprox(H*D + (H*D)', B)
end

function test_interpolation_sbp(Hp::AbstractArray,
                                Dp::AbstractArray,
                                Hm::AbstractArray,
                                Dm::AbstractArray)
        @test isapprox(Hp*Dp,  (Hm*Dm)')
end

function interior(x::AbstractArray, ml::Int64, mr::Int64)
        return x[ml+1:size(x,1) - mr]
end

function boundary(x::AbstractArray, ml::Int64, mr::Int64)
        y = zeros(ml + mr)
        for i in 1:ml
                @inbounds y[i] = x[i]
        end
        m = size(x, 1)
        for i in 1:mr
                @inbounds y[i] = x[m - mr + i]
        end
        return y
end

end


using NESSie: yukawa
using NESSie.Radon
using NESSie.Radon: radoncoll!

@testset "regularyukawapot" begin
    for T in testtypes
        # x -> ξ
        x = ones(T, 3)
        yuk = T(7)
        ret = Radon.regularyukawapot(x, x, yuk)
        @test typeof(ret) == T
        @test ret == -7
        # ξ not in origin (no cancellation)
        ξ = -ones(T, 3)
        ret = Radon.regularyukawapot(x, ξ, yuk)
        @test typeof(ret) == T
        @test ret ≈ T(-0.28867513458631466433435706025773931468027671112178301167202)
        # ξ in origin (no cancellation)
        ξ = zeros(T, 3)
        ret = Radon.regularyukawapot(x, ξ, yuk)
        @test typeof(ret) == T
        @test ret ≈ T(-0.57734713663526745894988668695023080532035174827300561882957)
        # ξ in origin (potential cancellation)
        ret = Radon.regularyukawapot(T(.001) * x, ξ, yuk)
        @test typeof(ret) == T
        @test ret ≈ T(-6.95773573664079611429119468507639364064769481076591007615189)
        # ξ in origin (potential cancellation 2)
        ret = Radon.regularyukawapot(T(.0001) * x, ξ, yuk)
        @test typeof(ret) == T
        @test ret ≈ (T == Float64 ?
            -6.99575819000175052904190394524100762244862345273988759731283 :
            -yuk)
        # ξ in origin (potential cancellation 3)
        ret = Radon.regularyukawapot(T(.00001) * x, ξ, yuk)
        @test typeof(ret) == T
        @test ret ≈ (T == Float64 ?
            -6.99957566470162580591945945320355718542304458045960164651420 :
            -yuk)
    end
end

@testset "∂ₙregularyukawapot" begin
    for T in testtypes
        # x -> ξ
        x = ones(T, 3)
        n = map(T, [1, 0, 0])
        yuk = T(7)
        ret = Radon.∂ₙregularyukawapot(x, x, yuk, n)
        @test typeof(ret) == T
        @test ret ≈ T(14.145081595145832)
        # ξ not in origin (no cancellation)
        ξ = -ones(T, 3)
        ret = Radon.∂ₙregularyukawapot(x, ξ, yuk, n)
        @test typeof(ret) == T
        @test ret ≈ T(0.048112522396707305228639137148607498838982054839859863103104)
        # ξ in origin (no cancellation)
        ξ = zeros(T, 3)
        ret = Radon.∂ₙregularyukawapot(x, ξ, yuk, n)
        @test typeof(ret) == T
        @test ret ≈ T(0.192436385477375021033020985138032408973537816345889754008150)
        # ξ in origin (potential cancellation)
        ret = Radon.∂ₙregularyukawapot(T(.001) * x, ξ, yuk, n)
        @test typeof(ret) == T
        @test ret ≈ T(14.03126641709760402264474861719654017225786809145550365963283)
        # ξ in origin (potential cancellation 2)
        ret = Radon.∂ₙregularyukawapot(T(.0001) * x, ξ, yuk, n)
        @test typeof(ret) == T
        @test ret ≈ (T == Float64 ?
            14.13365345844970855427401235115544109979343227826364222026794 :
            yuk^2 / 2 / Float32(√3))
        # ξ in origin (potential cancellation 3)
        ret = Radon.∂ₙregularyukawapot(T(.00001) * x, ξ, yuk, n)
        @test typeof(ret) == T
        @test ret ≈ (T == Float64 ?
            14.14393831379399210175378534648974379367907886588210581085651 :
            yuk^2 / 2 / Float32(√3))
    end
end

@testset "radoncoll!" begin
    for T in testtypes
        elem = [Triangle(zeros(T, 3), T[2, 0, 0], T[0, 2, 0])]
        # compute triangle area ∫dx
        res = zeros(T, 1, 1)
        radoncoll!(res, elem, [zeros(T, 3)], (_, __, ___, ____) -> one(T), zero(T))
        @test res[1] ≈ 2one(T)
        # compute prism volume
        res = zeros(T, 2, 1)
        radoncoll!(res, elem, [T[0, 0, 4], T[0, 0, 6]], (x, ξ, _, __) -> ξ[3] - x[3], zero(T))
        @test res[1] ≈ 8one(T)
        @test res[2] ≈ 12one(T)
        # compute tetrahedron volume
        res = zeros(T, 1, 1)
        radoncoll!(res, elem, [T[0, 0, 2]], (x, ξ, _, __) -> ξ[3] - x[1] - x[2], zero(T))
        @test res[1] ≈ 4one(T)/3
        # a slightly more elaborate tetrahedron volume
        elem = [Triangle(zeros(T, 3), T[3, 0, 0], T[0, 2, 0])]
        res = zeros(T, 1, 1)
        radoncoll!(res, elem, [T[0, 0, 6]], (x, ξ, _, __) -> ξ[3] - 2x[1] - 3x[2], zero(T))
        @test res[1] ≈ 6one(T)
        # ∫∫6x²-40y dA
        elem = [Triangle(T[0, 3, 0], T[1, 1, 0], T[5, 3, 0])]
        res = zeros(T, 1, 1)
        radoncoll!(res, elem, [zeros(T, 3)], (x, _, __, ___) -> 6*x[1]^2-40x[2], zero(T))
        @test res[1] ≈ T(-935/3)
    end
end


using LinearAlgebra
using NSDEBase
using RungeKutta
using Test

u0 = [1.0, 1.0, 1.0]
tspan = (0.0, 1e-2)
function f!(du, u, t)
    du .= Matrix(1.0I, 3, 3) * u
end
problem = IVP(f!, u0, tspan)

@testset "Explicit" begin
    solver = ExplicitEuler(h = 1e-2)
    @test solver isa ExplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = ExplicitMidpoint(h = 1e-2)
    @test solver isa ExplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = Heun2(h = 1e-2)
    @test solver isa ExplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = Ralston2(h = 1e-2)
    @test solver isa ExplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = Heun3(h = 1e-2)
    @test solver isa ExplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = Kutta3(h = 1e-2)
    @test solver isa ExplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = Ralston3(h = 1e-2)
    @test solver isa ExplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = SSPRK3(h = 1e-2)
    @test solver isa ExplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = RK4(h = 1e-2)
    @test solver isa ExplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = Rule38(h = 1e-2)
    @test solver isa ExplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = HeunEuler(h = 1e-2)
    @test solver isa ExplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = F45(h = 1e-2)
    @test solver isa ExplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = DP54(h = 1e-2)
    @test solver isa ExplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = V65(h = 1e-2)
    @test solver isa ExplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
end

@testset "Implicit" begin
    solver = ImplicitEuler(h = 1e-2)
    @test solver isa ImplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = ImplicitMidpoint(h = 1e-2)
    @test solver isa ImplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = CrankNicolson(h = 1e-2)
    @test solver isa ImplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = SDIRK3(h = 1e-2)
    @test solver isa ImplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = GL4(h = 1e-2)
    @test solver isa ImplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = GL6(h = 1e-2)
    @test solver isa ImplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = LobattoIIIA4(h = 1e-2)
    @test solver isa ImplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = LobattoIIIB2(h = 1e-2)
    @test solver isa ImplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = LobattoIIIB4(h = 1e-2)
    @test solver isa ImplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = LobattoIIIC2(h = 1e-2)
    @test solver isa ImplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = LobattoIIIC4(h = 1e-2)
    @test solver isa ImplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = RadauIA3(h = 1e-2)
    @test solver isa ImplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = RadauIA5(h = 1e-2)
    @test solver isa ImplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = RadauIIA3(h = 1e-2)
    @test solver isa ImplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
    solver = RadauIIA5(h = 1e-2)
    @test solver isa ImplicitRungeKuttaSolver
    solution = solve(problem, solver)
    @test solution isa RungeKuttaSolution
end

@testset "Stability" begin
    @test ℛ(0.0, Euler().tableau) == 1.0
end


##Reineke SDI chart

using RecipesBase

@userplot sdi_chart
@recipe function f(dat::sdi_chart ;maxsdi=450)

diarng = 1:1:20
#tparng = [1,1200] #unusued for now, TODO: figure out how to autoscale plot?
maxline() = maxsdi ./ (diarng ./ 10.0).^1.605

  # plot attributes
  xlabel --> "Trees Per Acre"
  ylabel --> "Quadratic Mean Diameter"
  xticks --> (0:50:450)
  yticks --> (0:20:200)
  xscale --> :log10
  yscale --> :log10

  legend := false
  primary := false

#max SDI line
@series begin
    linecolor := :black
    maxline()
end

#competition induced mortality SDI line
@series begin
    linecolor := :orange
    maxline() * 0.55
end

#crown closure SDI line
@series begin
    linecolor := :red
    maxline() * 0.35
end

 # the point and the annotations
 @series begin
    primary := true
    seriestype := :scatter
    #annotations := (100:38:442, 20:10:110, ["$i%" for i in 20:10:110])

    a,b = dat.args
    [a], [b]
 end
end


struct MISMIPMeltRateOne{T <: Real} <: AbstractMeltRate
    α  :: T
    ρi :: T
    ρw :: T
end

"""
    function MISMIPMeltRateOne(; <kwargs>)

Construct a melt rate to specify the melt rate according to the MISMIP_1r experiment

Keyword arguments
=================
- α : Calibrated melt rate prefactor (defaults to unity, value used in MISMIP+)
- ρi : Ice density
- ρw : Water density
"""
MISMIPMeltRateOne(; α = 1.0, ρi = 918.0, ρw = 1028.0) = MISMIPMeltRateOne(α,ρi, ρw)

"""
    update_melt_rate(melt_rate::MISMIPMeltRateOne, fields, grid)

Update the melt rate when for the MISMIPMeltRateOne type, used in MISMIP Ice 1x experiments
"""
function update_melt_rate!(melt_rate::MISMIPMeltRateOne, fields, grid)
    draft = -(melt_rate.ρi / melt_rate.ρw) .* fields.gh.h
    cavity_thickness = draft .- fields.gh.b
    cavity_thickness = max.(cavity_thickness, 0)
    m =  melt_rate.α .* 0.2*tanh.(cavity_thickness./75).*max.((-100 .- draft), 0)
    fields.gh.basal_melt[:] .= m[:]
end


function validate(a::Int, history::Dict{Int,Byte})::Bool
    for g in keys(history)
        if fast_wordle_score(g, a) != history[g]
            return false
        end
    end
    return true

end

"""
    filter_answers(history::Dict{Int,Byte})::Vector{Int}
Return a list of indices into ANS_LIST of words that could be valid given the history
"""
function filter_answers(history::Dict{Int,Byte})::Vector{Int}
    [a for a = 1:NA if validate(a, history)]
end

export filter_answers, validate

"""
    make_counts(g::Int, possibles::Vector{Int})
Given a guess `g` and a list of possible answers `possibles`
create a `Counter` that shows how often each score occurs.
"""
function make_counts(g::Int, possibles::Vector{Int})::Counter{Byte}
    vals = [fast_wordle_score(g, a) for a in possibles]
    return counter(vals)
end

export make_counts

# These are three scoring functions to evaluate a list of possible

"""
    min_max_score(g, possibles)
This score is the largest count returned by `make_counts`.
"""
function min_max_score(g::Int, possibles::Vector{Int})::Int
    c = make_counts(g, possibles)
    return maximum(values(c))
end

"""
    entropy_score(g, possibles)
This is the sum of `x * log(x)` where `x` is a counter value
returned by `make_counts`.
"""
function entropy_score(g::Int, possibles::Vector{Int})
    c = make_counts(g, possibles)
    return sum(x * log(x) for x in values(c))
end

"""
    equi_score(g, possibles)
Always return `1`. This treats all guesses the same.
"""
equi_score(g::Int, possibles::Vector{Int}) = 1

"""
    L2_score(g, possibles)
Sum of `x^2` where `x` is a counter values returned by
`make_counts`.
"""

function L2_score(g::Int, possibles::Vector{Int})
    c = make_counts(g, possibles)
    return sum(x * x for x in values(c))
end

"""
    best_guess(possibles, score_func)
Given a list of possible answers (given by `filter_answers`), return a best guess
based on minimizing `score_func`.
"""
function best_guess(possibles::Vector{Int}, score_func::Function = min_max_score)
    glist = shuffle(collect(1:NG))

    best_g = first(possibles)
    best_s = score_func(best_g, possibles)

    for g in glist
        s = score_func(g, possibles)
        if s < best_s
            best_g = g
            best_s = s
        end
    end
    return best_g
end

export best_guess, min_max_score, entropy_score, equi_score, L2_score

FIRST_GUESS = Dict{Function,Int}()
# @info "Precomputing first guesses"

FIRST_GUESS[min_max_score] = 1535
FIRST_GUESS[equi_score] = 1
FIRST_GUESS[entropy_score] = 10846
FIRST_GUESS[L2_score] = 9958

function make_first_guesses()
    for f in [min_max_score, entropy_score, equi_score, L2_score]
        g = best_guess(collect(1:NA), f)
        FIRST_GUESS[f] = g
        guess = GUESS_LIST[g]
        @info "First guess for $f is $guess"
    end
end

export make_first_guesses


module Wigner

using StaticArrays

__L2syms = [:s, :p, :d, :f, :g, :h, :i, :k]
__syms2L = Dict( [sym => L-1 for (L, sym) in enumerate(__L2syms)]... )
get_orbsym(L::Integer)  = __L2syms[L+1]

# Index of entries in D matrix (sign included)
struct D_Index
	sign::Int64
	μ::Int64
	m::Int64
end

# Equation (1.1) - forms the covariant matrix D(Q)(indices only)
function rotation_D_matrix(L::Integer)
	if L<0
		error("Orbital type shall be represented as a positive integer!")
	end
    D = Array{D_Index}(undef, 2 * L + 1, 2 * L + 1)
    for i = 1 : 2 * L + 1
        for j = 1 : 2 * L + 1
            D[j,i] = D_Index(1, i - 1 - L, j - 1 - L);
        end
    end
	return D
end

# Equation (1.1) - forms the covariant matrix D(Q)(indices only)
rotation_D_matrix_ast(L) =
	[ D_Index((-1)^(i+j), -(i - 1 - L), -(j - 1 - L))
	   for i = 1:2*L+1, j = 1:2*L+1 ]
# function rotation_D_matrix_ast(L::Integer)
# 	if L<0
# 		error("Orbital type shall be represented as a positive integer!")
# 	end
#     D = Array{D_Index}(undef, 2 * L + 1, 2 * L + 1)
#     for i = 1 : 2 * L + 1
#         for j = 1 : 2 * L + 1
#             D[i,j] = D_Index((-1)^(i+j), -(i - 1 - L), -(j - 1 - L));
#         end
#     end
# 	return D
# end

# D_{μm}^l(Ang2Mat_zyz(α,β,γ))
function wigner_big_D(μ,m,l,α,β,γ)
	return (exp(-im*α*m) * wigner_d(m,μ,l,β)  * exp(-im*γ*μ))'
end

# Wigner small d, modified from
# https://github.com/cortner/SlaterKoster.jl/blob/
# 8dceecb073709e6448a7a219ed9d3a010fa06724/src/code_generation.jl#L73
function wigner_d(μ, m, l, β)
    fc1 = factorial(l+m)
    fc2 = factorial(l-m)
    fc3 = factorial(l+μ)
    fc4 = factorial(l-μ)
    fcm1 = sqrt(fc1 * fc2 * fc3 * fc4)

    cosb = cos(β / 2.0)
    sinb = sin(β / 2.0)

    p = m - μ
    low  = max(0,p)
    high = min(l+m,l-μ)

    temp = 0.0
    for s = low:high
       fc5 = factorial(s)
       fc6 = factorial(l+m-s)
       fc7 = factorial(l-μ-s)
       fc8 = factorial(s-p)
       fcm2 = fc5 * fc6 * fc7 * fc8
       pow1 = 2 * l - 2 * s + p
       pow2 = 2 * s - p
       temp += (-1)^(s+p) * cosb^pow1 * sinb^pow2 / fcm2
    end
    temp *= fcm1

    return temp
end

function mat2ang(Q)
	return mod(atan(Q[2,3],Q[1,3]),2pi), acos(Q[3,3]), mod(atan(Q[3,2],-Q[3,1]),2pi);
end

# Rotation D matrix
function rot_D(L::Integer, Q)
	Mat_D = zeros(ComplexF64, 2L + 1, 2L + 1);
	D = rotation_D_matrix(L);
	α, β, γ = mat2ang(Q);
	for i = 1 : 2L + 1
		for j = 1 : 2L + 1
			Mat_D[i,j] = wigner_big_D(D[i,j].μ, D[i,j].m, L, α, β, γ);
		end
	end
	return SMatrix{2L+1, 2L+1, ComplexF64}(Mat_D)
end

function rand_QD(L)
	rotz(α) = [cos(α) -sin(α) 0; sin(α) cos(α) 0; 0 0 1]
	roty(α) = [cos(α) 0 sin(α); 0 1 0;-sin(α) 0 cos(α)]
	Ang2Mat_zyz(α,β,γ) = rotz(α)*roty(β)*rotz(γ)

	α = 2pi*rand();
	β = pi*rand();
	γ = 2pi*rand();

	# construct the Q matrix
	Q = Ang2Mat_zyz(α,β,γ)
	Q = SMatrix{3,3}(Q)

	rand_ref = rand((-1,1))

	return rand_ref * Q, rand_ref^L * rot_D(L, Q)
end

function rand_QD(L1, L2)
	rotz(α) = [cos(α) -sin(α) 0; sin(α) cos(α) 0; 0 0 1]
	roty(α) = [cos(α) 0 sin(α); 0 1 0;-sin(α) 0 cos(α)]
	Ang2Mat_zyz(α,β,γ) = rotz(α)*roty(β)*rotz(γ)

	α = 2pi*rand();
	β = pi*rand();
	γ = 2pi*rand();

	# construct the Q matrix
	Q = Ang2Mat_zyz(α,β,γ)
	Q = SMatrix{3,3}(Q)

	rand_ref = rand((-1,1))

	return rand_ref * Q, rand_ref^(L1) * rot_D(L1, Q), rand_ref^(L2) * rot_D(L2, Q)
end

end


using Combinatorics: with_replacement_combinations
using BenchmarkTools
BenchmarkTools.DEFAULT_PARAMETERS.samples = 100

function compute()::Int
    result = 0
    factorials = Dict(i => factorial(i) for i ∈ 0:9)
    for i ∈ 2:7
        for digits ∈ with_replacement_combinations(0:9, i)
            candidate = sum(factorials[j] for j ∈ digits)
            if sort(parse.(Int, collect(string(candidate)))) == sort(digits)
                result += candidate
            end
        end
    end
    return result
end

compute()

@benchmark compute()


using Surrogates

#1D
lb = 0.0
ub = 5.0
n = 20
x = sample(n,lb,ub,SobolSample())
f = x->2*x+x^2
y = f.(x)
my_ear1d = EarthSurrogate(x,y,lb,ub)
val = my_ear1d(3.0)
add_point!(my_ear1d,6.0,48.0)

#ND
lb = [0.0,0.0]
ub = [10.0,10.0]
n = 30
x = sample(n,lb,ub,SobolSample())
f = x -> x[1]*x[2] + x[1]
y = f.(x)
my_earnd = EarthSurrogate(x,y,lb,ub)
val = my_earnd((2.0,2.0))
add_point!(my_earnd,(2.0,2.0),6.0)


module Pade

using ..MittagLefflerFunctions: rΓ
using OffsetArrays
using LinearAlgebra: svd, lu

export MLPade

struct MLPade{T<:AbstractFloat}
    α :: T
    β :: T
    m :: Int64
    n :: Int64
    p :: Vector{T}
    q :: Vector{T}
end

function MLPade(α::T, β::T, m::Integer, n::Integer,
                method::Symbol) where T <: AbstractFloat
    A = pade_matrix(α, β, m, n)
    if method == :fix
        p, q = pade_fix(A)
    elseif method == :svd
        p, q, y = pade_svd(A)
    elseif method == :lu
        p, q = pade_lu(A)
    else
        error("Unknown error $method")
    end
    return MLPade(α, β, m, n, p, q)
end

function (E::MLPade{T})(x::T) where T <: AbstractFloat
    p, q = E.p, E.q
    if x < 0
        throw(DomainError(x, "argument must be greater than or equal to zero"))
    elseif x < 1
        R = evalpoly(x, p) / evalpoly(x, q)
    else
        rx = 1 / x
        R = rx * evalpoly(rx, p[end:-1:1]) / evalpoly(rx, q[end:-1:1])
    end
    return R
end

function pade_matrix(α::T, β::T,
                     m::Integer, n::Integer) where T <: AbstractFloat
    if mod(m+n,2) == 0
        throw(ArgumentError("m+n must be odd"))
    end
    r = div(m+n-1,2)
    a = OffsetArray{T}(undef, 0:m-1)
    b = OffsetArray{T}(undef, 0:n-1)
    pow = one(T)
    for k = 0:m-1
        a[k] = pow * rΓ(β+k*α)
        pow = -pow
    end
    b[0] = zero(T)
    pow = -one(T)
    for k = 1:n-1
        pow = -pow
        b[k] = pow * rΓ(β-k*α)
    end
    A = zeros(T, 2r, 2r+1)
    if m ≥ r+1
        row = 0
        for k = 0:r-1
            row += 1
            A[row,k+1] = one(T)
            for j = 0:k
                A[row,r+1+j] = -a[k-j]
            end
        end
        for k = r:m-1
            row += 1
            for j = 0:r
                A[row,r+1+j] = -a[k-j]
            end
        end
        for k = r-n+1:r-1
            row += 1
            A[row,k+1] = one(T)
            for j = k+1:r
                A[row,r+1+j] = -b[j-k]
            end
        end
    else
        row = 0
        for k = 0:m-1
            row += 1
            A[row,k+1] = one(T)
            for j = 0:k
                A[row,r+1+j] = -a[k-j]
            end
        end
        for k = -(r-m):-1
            row += 1
            for j = 0:r
                A[row,r+1+j] = -b[j-k]
            end
        end
        for k = 0:r-1
            row += 1
            A[row,k+1] = one(T)
            for j = k+1:r
                A[row,r+1+j] = -b[j-k]
            end
        end
    end
    return A
end

function pade_svd(α::T, β::T, m::Integer, n::Integer) where T <: AbstractFloat
    A = pade_matrix(α, β, m, n)
    p, q = pade_svd(A)
    return p, q
end

function pade_svd(A::Matrix{T}) where T <: AbstractFloat
    r = div(size(A,1), 2)
    F = svd(A, full=true)
    p = F.V[1:r,2r+1]
    q = F.V[r+1:2r+1,2r+1]
    q1 = q[1]
    p ./= q1
    q ./= q1
    return p, q, F.V[:,2r+1]
end

function pade_lu(A::Matrix{T}) where T <: AbstractFloat
    r = div(size(A,1), 2)
    F = lu(A)
    y = zeros(T, 2r+1)
    y[2r+1] = one(T)
    for i = 2r:-1:1
        s = zero(T)
        for j = i+1:2r+1
            s += F.U[i,j] * y[j]
        end
        y[i] = -s / F.U[i,i]
    end
    p = y[1:r]
    q = y[r+1:2r+1]
    μ = q[1]
    for k = 1:r
        p[k] /= μ
    end
    for k = 1:r+1
        q[k] /= μ
    end
    return p, q
end

function pade_fix(A::Matrix{T}) where T <: AbstractFloat
    r = div(size(A,1), 2)
    Atilde = [ A[:,1:r] A[:,r+2:2r+1] ]
    b = -A[:,r+1]
    y = Atilde \ b
    p = y[1:r]
    q = Vector{T}(undef, r+1)
    q[1] = one(T)
    q[2:r+1] = y[r+1:2r]
    return p, q
end

end # module


#!/usr/bin/env julia

using Luxor

using Test

using Random
Random.seed!(42)

function triangle(points, degree)
    sethue(cols[degree])
    poly(points, :fill)
end

function sierpinski(points, degree)
    triangle(points, degree)
    if degree > 1
        p1, p2, p3 = points
        sierpinski([p1, midpoint(p1, p2),
                        midpoint(p1, p3)], degree-1)
        sierpinski([p2, midpoint(p1, p2),
                        midpoint(p2, p3)], degree-1)
        sierpinski([p3, midpoint(p3, p2),
                        midpoint(p1, p3)], degree-1)
    end
end

function draw(fname, n)
  Drawing(200, 200, fname)
  origin()
  background("ivory")
  circle(O, 75, :clip)
  my_points = ngon(O, 150, 3, -pi/2, vertices=true)
  depth = 8 # 12 is ok, 20 is right out
  sierpinski(my_points, n)
  @test finish() == true
end

cols = distinguishable_colors(8)
fname = "sierpinski.pdf"
draw(fname, 8)
println("...finished test: output in $(fname)")


# Noise Map
using Plots
wnl = 12.0
anl = 0.15
oofnl = 0.2
oofnl2 = 0.0001

white_map = strip2.white_noise(NN, pix_size, wnl)
atmos_map = strip2.atmospheric_noise(NN, pix_size, anl)
one_ove_f = strip2.one_over_f(NN, pix_size, oofnl)
one_ove_f2 = strip2.one_over_f2(NN, pix_size, oofnl2)

noise_map = white_map .+ atmos_map .+ one_ove_f

sky = conv_map .+ noise_map

N_mask = 3.0

filtered_map = strip2.filter_noise(NN, N_mask, sky)

# white noise
strip2.Plot_Sky(NN, pix_size, white_map, cmin=-30, cmax=30)

# instrumental 1/f
strip2.Plot_Sky(NN, pix_size, one_ove_f, cmin=-80, cmax=80)
strip2.Plot_Sky(NN, pix_size, one_ove_f2, cmin=-80, cmax=80)

ell_max = 5000.0
delta_ell = 30.0

a1, b1 = strip2.get_power_spectrum(one_ove_f, one_ove_f,
                                        ell_max, delta_ell, pix_size, NN)

a2, b2 = strip2.get_power_spectrum(one_ove_f2, one_ove_f2,
                                        ell_max, delta_ell, pix_size, NN)

a2, b2 = strip2.get_power_spectrum(Map_E, Map_E,
                                        ell_max, delta_ell, pix_size, NN)

# atmospheric noise
grad = ColorGradient([:blue, :white])
strip2.Plot_Sky(NN, pix_size, strip2.convolve_beam(NN, pix_size, beam_waist, atmos_map), cmin=-100, cmax=100, gradient=grad,
                lab_title="Atmospheric Correlation Map")

# Raw map
strip2.Plot_Sky(NN, pix_size, conv_map .+ atmos_map .+ white_map .+ strip2.convolve_beam(NN, pix_size, beam_waist, one_ove_f), cmin=-500, cmax=500)

# Filtered Map
strip2.Plot_Sky(NN, pix_size, filtered_map .-conv_map, cmin=-400, cmax=400)

# @gif for iter in 1:10
#   atmos_map = strip2.atmospheric_noise(NN, pix_size, anl, iter)
#   grad = ColorGradient([:blue, :white])
#   strip2.Plot_Sky(NN, pix_size, atmos_map, cmin=-100, cmax=100, gradient=grad,
#                   lab_title="Atmospheric Correlation Map")
# end every 1
#


# =================================================================
# Quadrotor model
# See https://easychair.org/publications/paper/gjfh
# =================================================================

using Reachability, MathematicalSystems, TaylorIntegration

# parameters of the model
const g = 9.81           # gravity constant in m/s^2
const R = 0.1            # radius of center mass in m
const l = 0.5            # distance of motors to center mass in m
const Mrotor = 0.1       # motor mass in kg
const M = 1.0            # center mass in kg
const m = M + 4*Mrotor   # total mass in kg
const mg = m*g

# moments of inertia
const Jx = (2/5)*M*R^2 + 2*l^2*Mrotor
const Jy = Jx
const Jz = (2/5)*M*R^2 + 4*l^2*Mrotor
const Cyzx = (Jy - Jz)/Jx
const Czxy = (Jz - Jx)/Jy
const Cxyz = 0.0 #(Jx - Jy)/Jz

# considering the control parameters as *parameters*
const u₁ = 1.0
const u₂ = 0.0
const u₃ = 0.0

@inline function quad_property(t, x)
    b1 = (x[3] < 1.4)
    b2 = t ≥ 1.0 ? (x[3] > 0.9) : true
    b3 = t ≥ 5.0 ? (0.98 ≤ x[3] ≤ 1.02) : true
    return b1 && b2 && b3
end

@taylorize function quadrotor!(dx, x, params, t)
    # unwrap the variables and the controllers; the last three are the controllers
    # x₁, x₂, x₃, x₄, x₅, x₆, x₇, x₈, x₉, x₁₀, x₁₁, x₁₂, u₁, u₂, u₃ = x
    x₁  = x[1]
    x₂  = x[2]
    x₃  = x[3]
    x₄  = x[4]
    x₅  = x[5]
    x₆  = x[6]
    x₇  = x[7]
    x₈  = x[8]
    x₉  = x[9]
    x₁₀ = x[10]
    x₁₁ = x[11]
    x₁₂ = x[12]

    # equations of the controllers
    F = (mg - 10*(x₃ - u₁)) + 3*x₆  # height control
    τϕ = -(x₇ - u₂) - x₁₀            # roll control
    τθ = -(x₈ - u₃) - x₁₁            # pitch control
    local τψ = 0.0                   # heading is uncontrolled
    #
    Tx = τϕ/Jx
    Ty = τθ/Jy
    Tz = τψ/Jz
    F_m = F/m

    # Some abbreviations
    sx7 = sin(x₇)
    cx7 = cos(x₇)
    sx8 = sin(x₈)
    cx8 = cos(x₈)
    sx9 = sin(x₉)
    cx9 = cos(x₉)
    #
    sx7sx9 = sx7*sx9
    sx7cx9 = sx7*cx9
    cx7sx9 = cx7*sx9
    cx7cx9 = cx7*cx9
    sx7cx8 = sx7*cx8
    cx7cx8 = cx7*cx8
    sx7_cx8 = sx7/cx8
    cx7_cx8 = cx7/cx8
    #
    x4cx8 = cx8*x₄
    #
    p11 = sx7_cx8*x₁₁
    p12 = cx7_cx8*x₁₂
    xdot9 = p11 + p12

    # differential equations for the quadrotor
    #
    dx[1] = (cx9*x4cx8 + (sx7cx9*sx8 - cx7sx9)*x₅) + (cx7cx9*sx8 + sx7sx9)*x₆
    dx[2] = (sx9*x4cx8 + (sx7sx9*sx8 + cx7cx9)*x₅) + (cx7sx9*sx8 - sx7cx9)*x₆
    dx[3] = (sx8*x₄ - sx7cx8*x₅) - cx7cx8*x₆
    dx[4] = (x₁₂*x₅ - x₁₁*x₆) - g*sx8
    dx[5] = (x₁₀*x₆ - x₁₂*x₄) + g*sx7cx8
    dx[6] = (x₁₁*x₄ - x₁₀*x₅) + (g*cx7cx8 - F_m)
    dx[7] = x₁₀ + sx8*xdot9
    dx[8] = cx7*x₁₁ - sx7*x₁₂
    dx[9] = xdot9
    dx[10] = Cyzx * (x₁₁ * x₁₂) + Tx
    dx[11] = Czxy * (x₁₀ * x₁₂) + Ty
    dx[12] = Cxyz * (x₁₀ * x₁₁) + Tz
     #
    return dx
end

function quad(; T=5.0, plot_vars=[0, 3],
                property=quad_property,
                project_reachset=true)

    # equations, x' = f(x(t))
    𝐹 = BlackBoxContinuousSystem(quadrotor!, 12)

    # initial conditions
    Wpos = 0.4
    Wvel = 0.4
    X0c = zeros(12)
    ΔX0 = [Wpos, Wpos, Wpos, Wvel, Wvel, Wvel, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
    X0 = Hyperrectangle(X0c, ΔX0)

    # instantiate the IVP
    𝑃 = InitialValueProblem(𝐹, X0)

    # general options
    𝑂 = Options(:T=>T, :plot_vars=>plot_vars, :property=>property,
                :project_reachset=>project_reachset, :mode=>"check")

    return (𝑃, 𝑂)
end


using Yao
using YaoAD
using LinearAlgebra
using QuAlgorithmZoo: heisenberg, random_diff_circuit, pair_ring

# generate a Heisenberg Model Hamiltonian
nbit = 4
h = mat(heisenberg(nbit))
v0 = statevec(zero_state(nbit))
function energy(circuit)
    v = mat(circuit) * v0
    (v'* h * v)[] |> real
end

# Generate a circuit as a wave function ansatz
circuit = random_diff_circuit(nbit, 2, pair_ring(nbit))

using Flux: ADAM, Optimise
function train!(lossfunc, circuit, optimizer; maxiter::Int=200)
    dispatch!(circuit, :random)
    params = parameters(circuit)
    loss_history = Float64[]
    for i = 1:maxiter
        # collect gradients from returned structured data
        grad = collect_gradients(lossfunc'(circuit))
        Optimise.update!(optimizer, params, grad)
        dispatch!(circuit, params)
        eng = lossfunc(circuit)
        push!(loss_history, eng)
        println("Iter $i, Energy (Loss) = $(eng)")
    end
    loss_history
end

using Random
Random.seed!(5)
EG = eigvals(Matrix(h))[1]
println("$nbit site Heisenberg model, exact ground state energy = $EG")
loss_history = train!(energy, circuit, ADAM(0.1); maxiter=200)

using DelimitedFiles
writedlm("data/loss_history_$nbit.dat", loss_history)


using Compat, ClassicalLaminateTheory

forcesAndMoments =
 @compat Dict(
    :Nx => 2000,
    :Ny => 0,
    :Nxy => 0,
    :Mx => 0,
    :My => 0,
    :Mxy => 0)

materialProperties =@compat Dict(
  :E1 => 20.0*10^6,
  :E2 => 1.5*10^6,
  :G12 => 1.0*10^6,
  :ν12 => .34,
  :alpha1 => 0.2e-6,
  :alpha2 => 0.2e-4,
  :beta1 => 0.2e-6,
  :beta2 => 0.2e-4,
  :thickness => 0.0075)

laminateProperties =@compat Dict(
  :nplies => 2,
  :repeats => 1,
  :symmetric => false,
  :orientation => [0, 90],
  :deltaTemp => 0.0,
  :deltaMoisture => 0
)

println()
createLaminate!(laminateProperties, materialProperties, forcesAndMoments)
show(laminateProperties)

println("ABD matrix:")
round(laminateProperties[:ABD], 3) |> display

println()
println("Total forces and moments:")
round(laminateProperties[:fTotal], 7) |> display

[laminateProperties[:f][1], laminateProperties[:f][2]]


"""
plot_slope(x, y; slope=2, line_base=0, a=0, b=2.0, i=1,j=length(x))

plot the result from the [`check_gradient`](@ref)
on data `x,y` with two comparison lines

1) `line_base`+ t`slope`  as the goal the plot should have
2) `a`+ `b`t` on the interval [`x[i]`, `x[j]`] for some (best fitting) comparison slope
"""
function plot_slope(x, y; slope=2, line_base=0, a=0, b=2.0, i=1, j=length(x))
    fig = plot(
        x,
        y;
        xaxis=:log,
        yaxis=:log,
        label="\$E(t)\$",
        linewidth=3,
        legend=:topleft,
        color=:lightblue,
    )
    s_line = [exp10(line_base + t * slope) for t in log10.(x)]
    plot!(fig, x, s_line; label="slope s=2", linestyle=:dash, color=:black, linewidth=2)
    if (i != 0) && (j != 0)
        best_line = [exp10(a + t * b) for t in log10.(x[i:j])]
        plot!(
            fig,
            x[i:j],
            best_line;
            label="best slope $(@sprintf("%.4f", b))",
            color=:blue,
            linestyle=:dot,
            linewidth=2,
        )
    end
    gui(fig)
    return nothing
end


using PhaseRetrieval
using Plots, Images
default(aspect_ratio =:equal)

# repeating my simulations in MMA
dim =520 # total grid size for padding
s=5.2
p=150
# cell=p/s
cell = 64
d=1.2
r=cell*8/dim # aperture of 512 pixels, relative size

ph1 = SHdiversity(dim, cell, (12,-8.5), .1); # offset and rotation are chosen arbitrary
ph1 = 2*pi*ph1./(4* dim)

heatmap(phwrap.(ph1), c=:cyclic_mygbm_30_95_c78_n256, size=(dim,dim)) |> display

ap1 = [ i^2 + j^2 < r^2 for i in range(-1,1,length= dim), j in range(-1,1, length=dim)]
display(heatmap(ap1))
hp1ref = psf(ap1, ph1)
heatmap(hp1ref, c=:grays, size = (540,540), cb=:none)

# using Images instead of heatmap
Gray.(PhaseRetrieval.rescale(phwrap.(ph1)))
Gray.(PhaseRetrieval.rescale(hp1ref))

# for 4 times more lenslets (32 x 32) we need upsample it 4 times
dim=1080
q = 4
dimup = dim*q
phup = SHdiversity(dimup, cell, (12,-8.5), .1); # offset and rotation are chosen arbitrary
phup = 2*pi*phup./(2* dimup)
Gray.(PhaseRetrieval.rescale(phwrap.(phup)))
apup = [ i^2 + j^2 < r^2 for i in range(-1,1,length= dimup), j in range(-1,1, length=dimup)]
hpupref = imresize(psf(apup, phup*2), ratio=1/q)
Gray.(PhaseRetrieval.rescale(hpupref))
hp2=restrict(restrict(psf(apup, -phup*2)))
Gray.(PhaseRetrieval.rescale(hp2))

# in MMA I had dimgrid 5850, q = 5, ap= 4805, number of pixel per s/a is 145
dim=1170
q = 5
dimup = dim*q
r= 961/1170
cellup =145
phup = SHdiversity(dimup, cellup, (12,-8.5), .1); # offset and rotation are chosen arbitrary
phup = 2*pi*phup./(2* dimup)
Gray.(PhaseRetrieval.rescale(phwrap.(phup)))
apup = [ i^2 + j^2 < r^2 for i in range(-1,1,length= dimup), j in range(-1,1, length=dimup)]
hpupref = imresize(psf(apup, phup), ratio=1/q)
Gray.(PhaseRetrieval.rescale(hpupref))
save("p150f10_ref.png",Gray.(PhaseRetrieval.rescale(hpupref)))


export windprofile_logLL, windprofile_logL, windprofile_DH,
      windprofile_PL_α, windprofile_PL_α_P, windprofile_PL_n_JM,
      windprofile_PL_n_SR

include("constant.jl")
#using .constant

# Gualtieri, Giovanni. "A comprehensive review on wind resource extrapolation models applied in wind energy."
# Renewable and Sustainable Energy Reviews 102 (2019): 215-233.

# Monin-obukhov length
"""

    Monin_Obukhov_length(u✶,H)

    Monin_Obukhov_length(u✶,H,T)

calculate the Monin-Obukhov length

# Arguments

- `u✶`: m.s-1, friction velocity. `u✶` for light, moderate and strong wind conditions
can be selected as 0.1, 0.3 and 0.6 m/s respectively

- `H`: W.m-2, sensible heat flux. For mostly sunny conditions during the daytime,
`H` may be set 200 W.m-2; for cloudy conditions, day or night, `H` ~ 0 W.m-2;
for clear skies at night `H` ~ -4 W.m-2 for light wind conditions, and
  `H` ~ -20 W.m-2 for moderate or strong wind.

- `T`: ᵒC, air temperature, default `T` = 20 ᵒC

# Example:
```julia-repl
julia> Monin_Obukhov_length(0.3,-20)
```
# Reference
[1]. Foken, T, 2008: Micrometeorology. Springer, Berlin, Germany.
"""
function Monin_Obukhov_length(u✶,H,T=20)
  T += Kelvin

  L = (-ϱ0*cP*T*u✶^3) / (κ*g0*H)

  return L
end

# correction under non-neutral conditions
"""

  𝛹m(z/L)

correct deviations from the wind profile under non-neutral conditions

# Argument

- `z/L`: where `L` is the Monin-Obukhov length
"""
function 𝛹m(x)
      if x>0 # stable conditions
        return -5*x
      else # unstable conditions
        x2 = ( 1 - 15*(x) )^(1/4)
        value = 2*log( (1+x2)/2 ) + log( (1+x2^2)/2 ) - 2*atan(x2) + π/2
        return value
    end
end

"""

    𝛷m(z/L)

correct deviations from the wind profile under non-neutral conditions

# Argument

- `zg/L`: where `L` is the Monin-Obukhov length

"""
function 𝛷m(x)
  if x>0
    return 1+5*x
  else
    return (1 - 15*x)^(-1/4)
  end
end

"""

  Coriolis(lat)

calculate Coriolis frequency, f

# Arguments

- `lat`: deg, latitude

# Example
```julia-repl
julia> Coriolis(45)
```
"""
function Coriolis(lat)
  ω = 7292115*10^-11 # rad/s, rotation rate of the Earth
  f = 2*ω*sin(lat*π/180) # Coriolis parameter
  return f
end

"""

    windprofile_logLL(h,z0,vref,href)

    windprofile_logLL(h,z0,vref,href, u✶, H)

calculate wind speed at a given height using Log-linear law

# Arguments

- `h`: m, height need to extrapolate wind speed

- `z0`: m, roughness length

- `vref`: m/s, wind speed at reference height

- `href`: m, reference height

- `u✶`: m.s-1, friction velocity, default `u✶` = 0.3 for moderate wind condition

- `H`: W.m-2, sensible heat flux, default `H` = -20 for clear sky at night with
moderate wind condition

# Example
```julia-repl
julia> windprofile_logLL(10,0.05,3,1.5)
```

# Reference
[1]. Gualtieri, Giovanni. "A comprehensive review on wind resource extrapolation models applied in wind energy."
Renewable and Sustainable Energy Reviews 102 (2019): 215-233.

"""
function windprofile_logLL(h,z0,vref,href, u✶=0.3, H=-20)

    L = Monin_Obukhov_length(u✶,H)

    vh = vref*( log(h/z0) - 𝛹m(h/L) ) / ( log(href/z0) - 𝛹m(href/L) )

    return vh

end

"""

    windprofile_logL(h,z0,vref,href)

calculate wind speed at a given height using Log-linear law (neutral conditions)

# Arguments

- `h`: m, height need to extrapolate wind speed

- `z0`: m, roughness length

- `vref`: m/s, wind speed at reference height

- `href`: m, reference height

# Example
```julia-repl
julia> windprofile_logL(10,0.05,3,1.5)
```

# Reference
[1]. Gualtieri, Giovanni. "A comprehensive review on wind resource extrapolation models applied in wind energy."
Renewable and Sustainable Energy Reviews 102 (2019): 215-233.

"""
function windprofile_logL(h,z0,vref,href)
  vh = vref* log(h/z0) / log(href/z0)
  return vh
end

# Eaves and Harris model
"""

    windprofile_DH(h,z0,vref,href)

    windprofile_DH(h,z0,vref,href,u✶,lat)

calculate wind speed at a given height using Deaves and Harris model
(near neutral strong wind conditions)

# Arguments

- `h`: m, height need to extrapolate wind speed

- `z0`: m, roughness length

- `vref`: m/s, wind speed at reference height

- `href`: m, reference height

- `u✶`: m.s-1, friction velocity, default `u✶` = 0.3 for moderate wind condition

- `lat`: deg, latitude in degree, default `lat` = 30 for South Australia

# Example
```julia-repl
julia> windprofile_DH(10,0.05,3,1.5)
```

# Reference
[1]. Gualtieri, Giovanni. "A comprehensive review on wind resource extrapolation models applied in wind energy."
Renewable and Sustainable Energy Reviews 102 (2019): 215-233.
"""
function windprofile_DH(h,z0,vref,href, u✶=0.3,lat=30)

  f = Coriolis(lat)   # Coriolis parameter
  B =  6.0            # empirical constant

  hf = u✶/(B*f) # neutral ABL height

  vh = vref* ( log(h/z0) + 5.75*(h/hf) - 1.88*(h/hf)^2 - 1.33*(h/hf)^3 + 0.25*(h/hf)^4 ) /
     ( log(href/z0) + 5.75*(href/hf) - 1.88*(href/hf)^2 - 1.33*(href/hf)^3 + 0.25*(href/hf)^4 )

  return vh

end

# Power law
"""

    windprofile_PL_α(h,α,vref,href)

calculate wind speed at a given height using power law model

# Arguments

- `h`: m, height need to extrapolate wind speed

- `α`: dimensionless exponent

- `vref`: m/s, wind speed at reference height

- `href`: m, reference height

# Example
```julia-repl
julia> windprofile_PL_α(10,1/7,3,1.5)
```

# Reference
[1]. Gualtieri, Giovanni. "A comprehensive review on wind resource extrapolation models applied in wind energy."
Renewable and Sustainable Energy Reviews 102 (2019): 215-233.

"""
function windprofile_PL_α(h,α,vref,href)
  vh = vref*(h/href)^(α)

  return vh
end

"""

    windprofile_PL_α_P(h,α,vref,href)

    windprofile_PL_α_P(h,z0,vref,href, u✶ H)

calculate wind speed at a given height using MOST

# Arguments

- `h`: m, height need to extrapolate wind speed

- `α`: dimensionless exponent

- `vref`: m/s, wind speed at reference height

- `href`: m, reference height

# Example
```julia-repl
julia> windprofile_PL_α_P(10,1/7,3,1.5)
```

# Reference
[1]. Gualtieri, Giovanni. "A comprehensive review on wind resource extrapolation models applied in wind energy."
Renewable and Sustainable Energy Reviews 102 (2019): 215-233.
"""
function windprofile_PL_α_P(h,z0,vref,href, u✶=0.3, H=-20)
  zg = (h*href)^0.5

  L = Monin_Obukhov_length(u✶,H)

  α = 𝛷m(zg/L) / ( log(zg/z0) - 𝛹m(zg/L) )

  vh = vref*(h/href)^α

  return vh

end

"""

    windprofile_PL_n_JM(z2,z1,c1,k1)

    windprofile_PL_n_JM(z2,z1,c1,k1)

calculate wind speed at a given height using Weibull probability function

# Arguments

- `z2`: m, height need to extrapolate wind speed

- `z1`: height at available data

- `c1`: Weibull parameter

- `k1`: Weibull parameter

# Example
```julia-repl
julia> windprofile_PL_α_P(10,1/7,3,1.5)
```

# Reference
[1]. Gualtieri, Giovanni. "A comprehensive review on wind resource extrapolation models applied in wind energy."
Renewable and Sustainable Energy Reviews 102 (2019): 215-233.

"""
function windprofile_PL_n_JM(z2,z1,c1,k1)

  zr = 10 # reference height
  k2 = k1*( 1 - 0.0881*log(z1/zr) ) / ( 1 - 0.0881*log(z2/zr) )

  n = (0.37 - 0.0881*log(c1)) / ( 1 - 0.0881*log(z1/zr) )

  c2 = c1*(z2/z1)^n

  return c2, k2

end

"""

    windprofile_PL_n_SR(z2,z1,c1,k1)

    windprofile_PL_n_SR(z2,z1,c1,k1)

calculate wind speed at a given height using Weibull probability function

# Arguments

- `z2`: m, height need to extrapolate wind speed

- `z1`: height at available data

- `c1`: Weibull parameter

- `k1`: Weibull parameter

# Example
```julia-repl
julia> windprofile_PL_N_SR(10,1/7,3,1.5)
```

# Reference
[1]. Gualtieri, Giovanni. "A comprehensive review on wind resource extrapolation models applied in wind energy."
Renewable and Sustainable Energy Reviews 102 (2019): 215-233.

"""
function windprofile_PL_n_SR(z2,z1,c1,k1,z0)

  zr = 10 # reference height
  vh = 67 # m/s
  α0 = (z0/zr)^0.2

  k2 = k1*( 1 - 0.0881*log(z1/zr) ) / ( 1 - 0.0881*log(z2/zr) )

  n = α0*( 1 - log(c1)/log(vh) ) / ( 1 - α0*log(z1/zr)/log(vh) )

  c2 = c1*(z2/z1)^n

  return c2, k2

end


for N in [Float64, Rational{Int}, Float32]
    # singleton
    S = Singleton(N[1, -2, 3, -4])
    H = Hyperrectangle(zeros(N, 4), N[1, 2, 3, 4])
    @test symmetric_interval_hull(S) == H

    # linear map of singleton
    M = Diagonal(ones(N, 4))
    @test symmetric_interval_hull(M * S) == H

    # Minkowski sum of singletons
    H2 = Hyperrectangle(zeros(N, 4), 2 * N[1, 2, 3, 4])
    @test symmetric_interval_hull(S + S) == H2

    # linear map of hyperrectangle
    M = Diagonal(N[1, -1, -2, 2])
    H1 = Hyperrectangle(5 * ones(N, 4), N[1, 2, 3, 4])
    H2 = Hyperrectangle(zeros(N, 4), N[6, 7, 16, 18])
    @test symmetric_interval_hull(M * H1) == H2
end


include("../utils.jl")
include("group_utils.jl")

using Manifolds: connection

@testset "Cartan-Schouten connections" begin
    SO3 = SpecialOrthogonal(3)
    SO3minus = ConnectionManifold(SO3, CartanSchoutenMinus())
    SO3plus = ConnectionManifold(SO3, CartanSchoutenPlus())
    SO3zero = ConnectionManifold(SO3, CartanSchoutenZero())

    e = Matrix{Float64}(I, 3, 3)
    p = exp(hat(SO3, Identity(SO3), [1.0, 2.0, 3.0]))
    q = exp(hat(SO3, Identity(SO3), [3.0, 4.0, 1.0]))
    X = hat(SO3, Identity(SO3), [2.0, 3.0, 4.0])
    SO3e = Identity(SO3)

    @testset "connection" begin
        @test connection(SO3minus) === CartanSchoutenMinus()
        @test connection(SO3plus) === CartanSchoutenPlus()
        @test connection(SO3zero) === CartanSchoutenZero()
    end

    @testset "log/exp" begin
        for CSO3 in [SO3minus, SO3plus, SO3zero]
            @test isapprox(SO3, exp(CSO3, p, X), exp(SO3, p, X))
            @test isapprox(SO3, p, log(CSO3, p, q), log(SO3, p, q); atol=1e-6)
        end
    end

    @testset "Parallel transport" begin
        @test isapprox(SO3, q, X, vector_transport_to(SO3minus, SO3e, X, q))
        @test isapprox(SO3, q, q * X / q, vector_transport_to(SO3plus, SO3e, X, q))
        @test isapprox(
            SO3,
            q,
            vector_transport_to(SO3, e, X, q),
            vector_transport_to(SO3zero, SO3e, X, q),
        )
    end
end


using DataDrivenDiffEq
using ModelingToolkit
using OrdinaryDiffEq
using LinearAlgebra
using Plots
gr()

# Create a basis of functions aka observables ( aka features )
@variables u[1:2]
h = [u[1]; sin(u[1]); sin(u[2]); u[2]; u[1]*u[2]; u[2]^2]
basis = Basis(h, u)

# Create a test system
function test_discrete(du, u, p, t)
    du[1] = 0.9u[1] + 0.1u[2]^2
    du[2] = sin(u[1]) - 0.1u[1]
end

# Set up the problem
u0 = [1.0; 2.0]
tspan = (0.0, 50.0)
prob = DiscreteProblem(test_discrete, u0, tspan)
sol = solve(prob, FunctionMap())
# Plot the solution
plot(sol)

# Build the edmd with subset of measurements
approximator = ExtendedDMD(sol[:,:], basis)

# Lets look at the eigenvalues
scatter(eigvals(approximator))

# Get the nonlinear dynamics
dudt_ = dynamics(approximator)
# Solve the estimation problem
prob_ = DiscreteProblem(dudt_, u0, tspan, [])
sol_ = solve(prob_, FunctionMap())

# Plot the error
plot(sol_)
plot(sol.t, abs.(sol - sol_)')
norm(sol-sol_) # ≈ 4.33e-13

# Get the linear dynamics in koopman space
dψdt = linear_dynamics(approximator)
# Simply calling the EDMD struct transforms into the current basis
ψ_prob = DiscreteProblem(dψdt, approximator(u0), tspan)
ψ = solve(ψ_prob, FunctionMap())

# Plot trajectory in edmd basis
plot(sol.t, ψ')
plot(sol.t, approximator(Array(sol))')
norm((ψ - approximator(Array(sol)))) # ≈ 0.0158

# And in observable space
sol_ψ = approximator.output * ψ
plot(abs.(sol_ψ'- sol[:,:]'))
norm(sol_ψ - sol) # ≈ 0.00664

# Reduce the basis
reduced_basis = reduce_basis(approximator)

# Reiterate for the reduced approximator with few measurements
reduced_approximator = ExtendedDMD(sol[:,1:4], reduced_basis)

dψdt = linear_dynamics(reduced_approximator)
# Simply calling the EDMD struct transforms into the current basis
ψ_prob = DiscreteProblem(dψdt, reduced_approximator(u0), tspan)
ψ = solve(ψ_prob, FunctionMap())

# Plot trajectory in edmd basis with much higher accuracy than before
#plot(sol.t, ψ')
#plot!(sol.t, hcat([reduced_approximator(xi) for xi in eachcol(sol)]...)')
norm(ψ - reduced_approximator(Array(sol))) # ≈ 1.43..

# Update with all other measurements
update!(reduced_approximator, sol[:,8:end-1], sol[:, 9:end], threshold = 1e-7)

dψdt = linear_dynamics(reduced_approximator)
# Simply calling the EDMD struct transforms into the current basis
ψ_prob = DiscreteProblem(dψdt, reduced_approximator(u0), tspan)
ψ = solve(ψ_prob, FunctionMap())

# Plot trajectory in edmd basis with much higher accuracy than before
plot(sol.t, ψ')
norm(ψ - reduced_approximator(Array(sol))) # ≈ 0.347...

# And in observable space, with lower accuracy since the original states are not in the
# system anymore
sol_ψ = reduced_approximator.output * ψ
plot(abs.(sol_ψ'- sol[:,:]'))
norm(sol_ψ - sol) # ≈ 0.464