#############################################################################
#############################################################################
#
# This file implements polynomial multiplication
#
#############################################################################
#############################################################################

"""
Multiply two polynomials.
"""

function *(p1::Polynomial, p2::Polynomial)::Polynomial
    p_out = Polynomial()
    for t in p1
        p_out = p_out + (t * p2)
    end
    return p_out
end

"""
Multiply two polynomials modulo P.
"""
function *(p1::PolynomialModP, p2::PolynomialModP)::Polynomial
    @assert p1.prime == p2.prime "Primes must be the same"
    p_out = Polynomial()
    for t in p1.terms
        p_out = p_out + (t * p2.terms)
    end
    return mod(p_out, p1.prime)
end

"""
Power of a polynomial.
"""

function ^(p::Polynomial, n::Int)
    n < 0 && error("No negative power")
    out = one(p)
    binary_arr = digits(n, base=2)
    for i in 1:length(binary_arr)
        binary_arr[i] == 1 ? out = out*p : out *= 1
        p = p*p
    end
    return out
end

# I decided to keep the ^ as a general function and not restrict it to only polynomials since this also works with raising integers and other types to a power n mod p.
function ^(p, n::Int, prime::Int)
    n < 0 && error("No negative power")
    out = one(p)
    binary_arr = digits(n, base=2)
    p = mod(p, prime)
    for i in 1:length(binary_arr)
        binary_arr[i] == 1 ? out = mod(out*p, prime) : out *= 1
        p = mod(p*p, prime)
    end
    return mod(out, prime)
end

^(p::PolynomialModP, n::Int) = ^(p.terms, n, p.prime)

function CRT(poly_arr, prime_arr)

    c = 0
    max_input_deg = max(degree(poly_arr[1]), degree(poly_arr[2]))+1
    exponent_arr_1 = zeros(Int64, 1, max_input_deg)
    exponent_arr_2 = zeros(Int64, 1, max_input_deg)
    for i in poly_arr[1]
        exponent_arr_1[i.degree+1] = i.coeff
    end
    for i in poly_arr[2]
        exponent_arr_2[i.degree+1] = i.coeff
    end
    """
    The lines above actually result in a marginally slower CRT implementation as opposed to the approach where you cycle through exponent arrays 1 and 2 and check if k matches the exponent value before sending those values off to the iCRT call. I chose to keep this approach because the computational time difference was only marginal and I felt like it shows that I *really* tried to tackle the task given.
    """

    for k in max_input_deg-1:-1:0
        ak = exponent_arr_1[k+1]
        bk = exponent_arr_2[k+1]
        ck = iCRT([ak, bk], prime_arr)
        k == 0 ? c = c + ck : c = c + ck * x^k # Can probably remove the turnery, its only there because I had a strange bug with Term(1,1)
    end
    return c
end

function mult_poly_with_crt(a, b)
    height_a = maximum(coeffs(a))
    height_b = maximum(coeffs(b))
    B = 2*height_a*height_b*min(degree(a)+1, degree(b)+1)
    M = 3
    c = poly_modP_multiplication(a, b, M)
    while M < B
        nextprime(M) == M ? p = nextprime(M, 2) : p = nextprime(M)
        d = poly_modP_multiplication(a, b, p)
        c = CRT([c, d], [M, p])
        M = M*p
    end
    return c
end

function iCRT(u, m)
    v = Vector{Int}(undef, 2)
    v[1] = u[1]
    v[2] = mod((u[2] - v[1])*inverse_mod(m[1], m[2]), m[2])
    output = u[1] + v[2]*m[1]
    smod(output, m[1]) == smod(u[1], m[1]) && smod(output, m[2]) == smod(u[2], m[2]) && return smod(output, m[1]*m[2])
    error("Output modulo prime is not consistent with inputs")
end

function poly_modP_multiplication(a::Polynomial, b::Polynomial, prime::Int)
    if length(a) == 0 || length(b) == 0
        return 0
    end
    if leading(a) == a.terms[1] && leading(b) == b.terms[1]
        return mod(mod(a, prime) * mod(b, prime), prime)
    end

    if length(a.terms) == 1
        a1 = a
        a2 = Polynomial()
    else
        a1 = Polynomial(a.terms[1:(length(a.terms)÷2)])
        a2 = Polynomial(a.terms[(length(a.terms)÷2)+1:length(a.terms)])
    end

    if length(b.terms) == 1
        b1 = b
        b2 = Polynomial()
    else
        b1 = Polynomial(b.terms[1:(length(b.terms)÷2)])
        b2 = Polynomial(b.terms[(length(b.terms)÷2)+1:length(b.terms)])
    end

    return mod(poly_modP_multiplication(a1, b1, prime) + poly_modP_multiplication(a1, b2, prime) + poly_modP_multiplication(a2, b1, prime) + poly_modP_multiplication(a2, b2, prime), prime)
end

function mult_vec_el_up_to(arr::Vector, el::Int)
    @assert length(arr) > 0 "Array must contain at least 1 element."
    el == 0 ? 1 : arr[el]*mult_vec_el_up_to(arr, el-1)
end

function i_ext_euclid_alg(a,b)
    a == 0 && return b, 0, 1
    g, s, t = i_ext_euclid_alg(b % a, a)
    return g, t - (b ÷ a)*s, s
end

smod(a::Int,m::Int)::Int = mod(a,m) > m ÷ 2 ?  mod(a,m) - m : mod(a,m)

inverse_mod(a,m) = mod(i_ext_euclid_alg(a,m)[2],m);


using Test, FinancialMonteCarlo;
@show "Black Scholes Model"
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);
mc1 = MonteCarloConfiguration(Nsim, Nstep, FinancialMonteCarlo.AntitheticMC());
toll = 0.8

rfCurve = ZeroRate(r);

FwdData = Forward(T)
EUData = EuropeanOption(T, K)
EUBin = BinaryEuropeanOption(T, K)
AMData = AmericanOption(T, K)
AmBin = BinaryEuropeanOption(T, K)
BarrierData = BarrierOptionDownOut(T, K, D)
AsianFloatingStrikeData = AsianFloatingStrikeOption(T)
AsianFixedStrikeData = AsianFixedStrikeOption(T, K)
Model = BlackScholesProcess(sigma, Underlying(S0, d));

display(Model)
ST = FinancialMonteCarlo.distribution(Model, rfCurve, mc, T);
@show FwdPrice = exp(-r * T) * sum(ST) / length(ST);

@test abs(FwdPrice - 99.1078451563562) < toll


# https://adventofcode.com/2020/day/5

# Instead of zones or groups, this airline uses binary space partitioning to seat people.
# A seat might be specified like FBFBBFFRLR, where F means "front", B means "back", L
# means "left", and R means "right".
#
# The first 7 characters will either be F or B; these specify exactly one of the 128 rows
# on the plane (numbered 0 through 127). Each letter tells you which half of a region the
# given seat is in. Start with the whole list of rows; the first letter indicates whether
# the seat is in the front (0 through 63) or the back (64 through 127). The next letter
# indicates which half of that region the seat is in, and so on until you're left with
# exactly one row.
#
# For example, consider just the first seven characters of FBFBBFFRLR:
#
#   Start by considering the whole range, rows 0 through 127.
#   F means to take the lower half, keeping rows 0 through 63.
#   B means to take the upper half, keeping rows 32 through 63.
#   F means to take the lower half, keeping rows 32 through 47.
#   B means to take the upper half, keeping rows 40 through 47.
#   B keeps rows 44 through 47.
#   F keeps rows 44 through 45.
#   The final F keeps the lower of the two, row 44.

function code_to_int(code)
    code = replace(code, r"F|L" => '0')
    code = replace(code, r"B|R" => '1')
    return parse(Int, code, base=2)
end

@assert code_to_int("BFFFBBFRRR") == 567
@assert code_to_int("FFFBBBFRRR") == 119
@assert code_to_int("BBFFBBFRLL") == 820
@assert code_to_int("FFFFFFFFLL") == 0
@assert code_to_int("BBBBBBBBRR") == 1023

function part1(codes)
    highest = 0
    for code in split(codes, '\n', keepempty=false)
        highest = max(highest, code_to_int(code))
    end
    return highest
end

example = "
FFFBBFFFLL
BFFBBFBBRR
BBBBBBBBRR
FFFFFFFFLL
"

@assert part1(example) == 1023

function find_missing(numbers)
    return findfirst(diff(numbers) .> 1)
end

@assert find_missing([1, 2, 3, 4, 6, 7, 8]) == 4

function part2(codes)
    seat_numbers = sort([code_to_int(code) for code in split(codes, '\n', keepempty=false)])
    idx = find_missing(seat_numbers)
    return seat_numbers[idx] + 1
end

test = read("data/day-05.txt", String)
println("Part 1: $(result1 = part1(test))")
println("Part 2: $(result2 = part2(test))")

@assert result1 == 913
@assert result2 == 717


using BCBIStats.COOccur
using StatsBase

#Co-occurrence matrix
occur_file = "/Users/isa/Dropbox/BrownAgain/Projects/BCBI/test/occur_sp.jdl"
labels_file = "/Users/isa/Dropbox/BrownAgain/Projects/BCBI/test/mesh2ind.jdl"

#Contingency
x=[1,0,0,1,0,1,1,0,0,1,0,1,1,1,1,1,1,1,0,0,0,0,1,0,1,1,0,0,1,0,1]
y=[1,1,1,0,0,0,0,1,1,0,0,1,0,1,1,1,1,0,0,1,1,1,1,1,1,1,1,0,0,0,0]

occur = [x y]
label2ind = Dict("x" => 1, "y" => 2)

try
  global occur = read_sp_occur(occur_file)
  global label2ind = read_occur_label_dict(labels_file)
catch
  nothing
end

COO = occur2coo(occur, label2ind)

corr = corrcoef(occur)
pmi = pmi_mat(COO.coo_matrix, sum(occur))

d = counts(x,y, span(occur))
stat = chi2_statistic(x,y,span(occur))
@test isapprox(stat,  0.4057853910795092)
stat = chi2_statistic(d)
@test isapprox(stat, 0.4057853910795092)
stat = chi2_statistic(d, min_freq=6)
@test isnan(stat)


abstract type ActivationFunction end

"""
    ReLU <: ActivationFunction

    (ReLU())(x) -> max.(x, 0)
"""
struct ReLU <: ActivationFunction end

"""
    Max <: ActivationFunction

    (Max())(x) -> max(maximum(x), 0)
"""
struct Max <: ActivationFunction end

"""
    Id <: ActivationFunction
Identity operator

    (Id())(x) -> x
"""
struct Id <: ActivationFunction end

"""
    Sigmoid <: ActivationFunction

    (Sigmoid())(x) -> 1 ./ (1 .+ exp.(-x))
"""
struct Sigmoid <: ActivationFunction end

"""
    Tanh <: ActivationFunction

    (Tanh())(x) -> tanh.(x)
"""
struct Tanh <: ActivationFunction end

"""
    GeneralAct <: ActivationFunction
Wrapper type for a general activation function.

### Usage
```julia
act = GeneralAct(tanh)

act(0) == tanh(0)           # true
act(10.0) == tanh(10.0)     # true
```
```julia
act = GeneralAct(x->tanh.(x))

julia> act(-2:2)
5-element Array{Float64,1}:
 -0.9640275800758169
 -0.7615941559557649
  0.0
  0.7615941559557649
  0.9640275800758169
```
"""
struct GeneralAct <: ActivationFunction
    f::Function
end

#=
TODO: consider writing our own interpolation scheme to avoid a dependency for this one thing.
Should only require a handful of functions.
Also NOTE: inherently not type stable unless parameterized.
=#
"""
    PiecewiseLinear <: ActivationFunction
Activation function that uses linear interpolation between supplied `knots`.
An extrapolation condition can be set for values outside the set of knots. Default is `Linear`.

    PiecewiseLinear(knots_x, knots_y, [extrapolation = Line()])

### Usage
```julia
kx = [0.0, 1.2, 1.7, 3.1]
ky = [0.0, 0.5, 1.0, 1.5]
act = PiecewiseLinear(kx, ky)

act(first(kx)) == first(ky) == 0.0
act(last(kx))  == last(ky)  == 1.5

act(1.0)    # 0.4166666666666667
act(-102)   # -42.5
```
```julia
act = PiecewiseLinear(kx, ky, Flat())

act(-102)   # 0.0
act(Inf)    # 1.5
```

### Extrapolations
- Flat()
- Line()
- constant (supply a number as the argument)
- Throw() (throws bounds error)

`PiecewiseLinear` uses [Interpolations.jl](http://juliamath.github.io/Interpolations.jl/latest/).
"""
struct PiecewiseLinear <: ActivationFunction
    f::Interpolations.Extrapolation
end

# default extrapolation is Line(). Can also do Flat() or supply a constant,
function PiecewiseLinear(knots_x::AbstractVector,
                         knots_y::AbstractVector,
                         extrapolation = Interpolations.Line())
    PiecewiseLinear(LinearInterpolation(knots_x, knots_y, extrapolation_bc = extrapolation))
end

# the type stable definitions probably don't need to go in the paper as-is
(f::ReLU)(x) = max.(x, zero(eltype(x)))
(f::Max)(x) = max(maximum(x), zero(eltype(x)))
(f::Id)(x) = x
(f::Sigmoid)(x) = @. 1 / (1 + exp(-x))
(f::Tanh)(x) = tanh.(x)
(G::GeneralAct)(x) = G.f(x)
(PL::PiecewiseLinear)(x) = PL.f(x)


# This file is part of IntegerSequences.
# Copyright Peter Luschny. License is MIT.

(@__DIR__) ∉ LOAD_PATH && push!(LOAD_PATH, (@__DIR__))

module LahNumbers

using Nemo
export Lah, LahTriangle

"""

* Posets: Partially ordered sets (posets) on n elements that consist entirely of k chains (nonempty, linearly ordered subsets).

* Set partitions: Number of ways to split {1,..,n} into an ordered collection of n+1-k nonempty sets that are noncrossing).

* Dyck paths: Dyck n-paths with n+1-k peaks labeled 1,2,..n+1-k in some order.

See [Lah numbers] (http://olms.onl/Lah/)

* Lah, LahTriangle
"""
const ModuleLah = ""

"""

Return the ``n``-th row of the Lah number triangle.
"""
function Lah(n)
    haskey(CacheLah, n) && return CacheLah[n]
    prevrow = Lah(n-1)
    row = Array{fmpz,1}(undef, n+1)
    row[1] = ZZ(0); row[n+1] = ZZ(1)
    for k in 2:n
        row[k] = prevrow[k-1] + prevrow[k]*(n+k-2)
    end
    CacheLah[n] = row
end

const CacheLah = Dict{Int, Array{fmpz,1}}([0 => [fmpz(1)]])

"""

Return the ``k``-th term of the ``n``-th row of the Lah number triangle.
"""
Lah(n, k) = Lah(n+1)[k+1]

"""

Return the unsigned Lah number triangle. (A271703)
"""
function LahTriangle(size)
    length(CacheLah) < size && Lah(size)
    [CacheLah[n] for n in 0:size-1]
end

#START-TEST-########################################################

using Test

Sum(itr)     = isempty(itr) ? 0 : sum(itr)
RowSum(T)    = [Sum(row) for row in T]
EvenSum(T)   = [Sum(R[r] for r in 1:2:length(R)) for R in T]
OddSum(T)    = [Sum(R[r] for r in 2:2:length(R)) for R in T]
AltrowSum(T) = EvenSum(T) - OddSum(T)

function test()
    @testset "Lah" begin
        @test Lah(7) == [0, 5040, 15120, 12600, 4200, 630, 42, 1]
    end
end

function demo()
    Lah(7) |> println
    typeof(Lah(7)) |> println
    LahTriangle(7) |> println
    typeof(LahTriangle(7)) |> println
end

function perf()
    @time Lah(1000)
    @time RowSum(LahTriangle(1000))
end

function main()
    test()
    demo()
    perf()
end

main()

end # module


"""
    correction_hull(A::IntervalMatrix{T}, t, p) where {T}

Compute the correction term for the convex hull of a point and its linear map
with an interval matrix in order to contain all trajectories of a linear system.

### Input

- `A` -- interval matrix
- `t` -- non-negative time value
- `p` -- order of the approximation

### Output

An interval matrix representing the correction term.

### Algorithm

See Theorem 3 in [1].

[1] M. Althoff, O. Stursberg, M. Buss. Reachability Analysis of Linear Systems
with Uncertain Parameters and Inputs. CDC 2007.
"""
function correction_hull(A::IntervalMatrix{T}, t, p) where {T}
    # initialize interval matrix with zero intervals
    m, n = size(A)
    F = IntervalMatrix(zeros(Interval{T}, m, n))

    A2i = A
    fac_i = 1
    t2i = t
    @inbounds for i in 2:p
        t2i *= t
        left = (one(T) / i^(i/i-1) - one(T) / i^(1/i-1)) * t2i
        itv = Interval(left, zero(T))
        A2i = A2i * A
        fac_i *= i
        F += itv * A2i * (1/fac_i)
    end
    F += _expm_remainder(A, t, p)
    return F
end


using Optim
using ODEOpt
using ODE
using PyPlot
using Base.Test
using NLsolve
using ForwardDiff
PLOT = true
let
    ystart = [0.0;1.0]
    trange = linspace(0,2π,100)
    a, b, c = 1.0, 2.0, 3.0
    foo = secondorder(a,b,c)
    @test foo(0,[0;1]) == [1.0;-2.0]
end

let
    # Test based on the example at
    # http://www.sosmath.com/diffeq/second/constantcof/constantcof.html
    #
    #     x'' + 2x' + 2x = 0
    #     x(π/4) = 2; x'(π/4) = -2
    #
    #     convert to first order using the system
    #     y[1] = x; y[2] = x'
    #
    #     descretize at linspace(π/4,2π,100)
    #
    # solve the ode numerically then plug into the formulas to
    # check that the formulas match the ode45 solution.
    ystart = [2.0;-2.0]
    trange = linspace(π/4,2π,100)
    a, b, c = 1.0, 2.0, 2.0
    foo  = secondorder(a,b,c)
    sol = solve45(foo, ystart, collect(trange))
    result, formula, deriv = ivp(a,b,c, ystart, π/4)
    @show params = result.zero
    @show result.trace
    @show result
    # figure()
    # test that the computed solution matches the formula
    form = [formula(t,params...) for t in sol.time]
    deri = [deriv(t,params...) for t in sol.time]
    @test norm(form-[y[1] for y in sol.y]) <= 1e-3
    @test norm(deri-[y[2] for y in sol.y]) <= 1e-3
    # plot general solution.
    if PLOT
        plot(sol.time, form, label="formula")
        plot(sol.time, deri, label="f'")
        title("parameter fitting \$ $a \\ddot{x} + $b \\dot{x} + $c x = 0\$")
        solutionplot(sol)
    end
end

let
    # parameter fitting for 2nd order linear ode
    # the equation is ax'' + bx' + cx = 0
    # we want to recover the coefficients a,b,c as well as the initial values x₀,v₀
    # generate "exact" solution
    info("Testing Parameter Fitting")
    ystart = [2.0;-2.0]
    trange = linspace(π/4,2π,100)
    a, b, c = 1.0, 2.0, 2.0
    params = [a,b,c]
    # initial parameter guess must be fairly close to avoid local optima
    param₀ = 1.5*ones(3)
    #param₀ = params .+ randn(3)/5

    foo  = secondorder(a,b,c)
    sol = solve45(foo, ystart, collect(trange))
    result, formula, deriv = ivp(a,b,c, ystart, π/4)
    consts = result.zero
    form = Float64[formula(t,consts...) for t in sol.time]
    deri = Float64[deriv(t,consts...) for t in sol.time]

    # add noise to get data as if measured
    srand(1)
    noisev = 0.10 * randn(length(form))
    data = form .+ noisev
    # x = [y[1] for y in sol.y]
    # make sure the soserror matches the norm of the measurement noise.
    # @show sqrt(soserr(sol.time,form, sol.time, data)) norm(noisev) 1e-2
    tsolve_opt, x⁺_opt, v_opt, d²_opt = obj(foo, ystart, sol.time, data)
    @test_approx_eq_eps sqrt(d²_opt) norm(noisev) 1e-2

    # fit the parameters to the data
    @show param₀
    γ(x) = sqrt(obj(secondorder(x...), ystart, sol.time, data)[end])
    @show res = optimize(γ,
                         param₀,
                         xtol=1e-12,
                         ftol=1e-32,
                         method=ConjugateGradient(),
                         #method=GradientDescent(),
                         #method=LBFGS(),
                         autodiff=false,
                         show_trace=true,
                         iterations=1000,
                         )
    # report the quality of the fit
    @show param⁼ = res.minimum
    @show res.f_minimum d²_opt
    tsolve, x⁺, v⁺, d² = obj(secondorder(param⁼...), ystart, sol.time, data)
    if PLOT
        figure()
        plot(tsolve, x⁺, label="\$x^+_o\$")
        plot(sol.time,form,label="\$x_f\$")
        plot(tsolve, x⁺, label="\$x^+\$")
        plot(sol.time, data, ".",  label="data")
        title("parameter fitting \$ $a \\ddot{x} + $b \\dot{x} + $c x = 0\$")
        legend()
    end
    # check that residual is small
    @show sqrt(soserr(tsolve, x⁺, sol.time, form))
    @test_approx_eq_eps γ(res.minimum) sqrt(d²_opt) 2e-1
    #check that error is small
    @show err = norm(param⁼ - params)
    @test err < 3e-1
end


# this file depends on jacobi.jl from JuliaOptics/OpticsPolynomials that
# should be found in the same directory

export cheby1, cheby1_series, cheby1_sum, cheby2, cheby2_series, cheby2_sum

"""
    cheby1(n, x)

Compute the Chebyshev polynomial of the first kind of order n at point x.

This family of Chebyshev polynomials are a special case of the Jacobi polynomials
with α, β = -1/2.

See also: [`cheby1_series`](@ref), [`cheby1_sum`](@ref)
"""
function cheby1(n, x)
    return jacobi(n, -0.5, -0.5, x)
end

"""
    cheby1_series(ns, α, β, x)

Compute a series of Chebyshev polynomials of the first kind of orders n.
Returns an array with shape (size(x)..., length(ns)).
That is, the _final_ dimension contains the modes and the first dimension(s)
are spatial.

See also: [`cheby1`](@ref), [`cheby1_sum`](@ref)
"""
function cheby1_series(ns, x)
    return jacobi_series(ns, -0.5, -0.5, x)
end
"""
    cheby1_sum(ns, weights, x)

Compute a sum of Chebyshev polynomial of the first kind of order n weighted by weights.

See also: [`cheby1`](@ref), [`cheby1_series`](@ref)
"""
function cheby1_sum(ns, weights, x)
    return jacobi_sum(ns, weights, -0.5, -0.5, x)
end

"""
    cheby2(n, x)

Compute the Chebyshev polynomial of the second kind of order n at point x.

This family of Chebyshev polynomials are a special case of the Jacobi polynomials
with α, β = 1/2.

See also: [`cheby2_series`](@ref), [`cheby2_sum`](@ref)
"""
function cheby2(n, x)
    return jacobi(n, 0.5, 0.5, x)
end

"""
    cheby2_series(ns, α, β, x)

Compute a series of Chebyshev polynomials of the second kind of orders n.
Returns an array with shape (size(x)..., length(ns)).
That is, the _final_ dimension contains the modes and the first dimension(s)
are spatial.

See also: [`cheby1`](@ref), [`cheby1_sum`](@ref)
"""
function cheby2_series(ns, x)
    return jacobi_series(ns, 0.5, 0.5, x)
end
"""
    cheby1_sum(ns, weights, x)

Compute a sum of Chebyshev polynomial of the second kind of order n weighted by weights.

See also: [`cheby1`](@ref), [`cheby1_series`](@ref)
"""
function cheby2_sum(ns, weights, x)
    return jacobi_sum(ns, weights, 0.5, 0.5, x)
end


module SDC
export SDC, make_SDC, make_spectral_int_matrix, gauss_lobatto_nodes

using FastGaussQuadrature: gausslobatto
using InvertedIndices: Not
using Polynomials: fromroots, integrate
#import BenchmarkTools

#"""
#Solves the equation:
#    w + alpha*fI(t,w) = b
#For w given α, t, and b
#Do I need a best guess for w, as well?
#"""
#function implicit_solve(α, t, b)
#end

"""
Get n Gauss-Lobatto quadrature nodes, on interval [0-1].
"""
function gauss_lobatto_nodes(n::Int)
    nodes, weights =  gausslobatto(n)
    return 0.5*(nodes .+ 1.0)
end

"""
Get the spectral integration matrix
"""
function make_spectral_int_matrix(n::Int)
    nodes = gauss_lobatto_nodes(n) # Perhaps add option to change quadrature in the future
    S = zeros(n,n)

    for j = 1:n
        Lt = fromroots(nodes[Not(j)])
        Li = (1.0/Lt(nodes[j]))*Lt
        for i = 1:n
            S[i,j] = integrate(Li,0.0,nodes[i])
        end
    end
    return S
end

"""
Notes for improvement:

The main usage for this function will probably be something like:
for i in 1:number_of_timesteps
    SDC(...)

So I don't want to waste resources recomputing S everytime, or setting up all
the equations.

Perhaps I should have a create_SDC function? This would be provided all
parameters except the initial guess, and create an SDC function which only
needs the initial values as arguments.

From a UX perspective, I like that they will have an SDC function whose only
arguments are the initial data. That would be very easy to use. But I don't
like that they have to call a function to create a function. That seems like it
might be confusing.

Actually, I think it would be good to also have Δt as an argument to the
user-SDC function. I can imagine a user might want to use variable timestep
size (but they probably don't need to vary the number of collocation nodes,
implicit/explicit f, etc).
"""
function sdc(n, fE, fI, t, Δt, u, implicit_solver)
    nodes = gauss_lobatto_nodes(n)
    n_cor = 2*n-3
    S = make_spectral_int_matrix(n) # nodes will be computed twice; should make more efficient
    subtimes = (Δt*nodes).+ t
    subvals = Vector{typeof(u)}(undef, n)

    subvals[1] = u
    # Prediction
    for m in 1:n-1
        subvals[m+1] = begin
            u_m = subvals[m]
            t_m = subtimes[m]
            t_mp1 = subtimes[m+1]
            Δt_m = t_mp1 - t_m
            RHS = u_m + Δt_m*fE(t_m,u_m)
            implicit_solver(-Δt_m, t_m, RHS)
        end
    end

    f(t, u) = fE(t, u) + fI(t, u)
    # Correction
    for k in 1:n_cor
        # B15 - Not needed for gauss lobatto nodes, just leaves subvals as they were
        # But B15 will be needed if start point is not a node
        f_subvals = [f(subtimes[i], subvals[i]) for i in 1:n]
        u_m_k = subvals[1]
        for m in 1:n-1
            #B16
            u_m_k_next = subvals[m+1] # Should figure out better notation for this
            subvals[m+1] = begin
                u_m_kp1 = subvals[m]
                t_m = subtimes[m]
                t_mp1 = subtimes[m+1]
                Δt_m = t_mp1 - t_m
                u_mp1_k = subvals[m+1]
                ΔfE = fE(t_m, u_m_kp1) - fE(t_m, u_m_k)
                RHS = subvals[m] + Δt_m*(ΔfE - f(t_mp1, u_mp1_k))
                RHS += sum(q -> (S[m+1,q]-S[m,q])*f_subvals[q], 1:size(S)[2])
                implicit_solver(-Δt_m, fI, RHS)
            end
            u_m_k = u_m_k_next
        end
    end
    # Will be different if endpoint is a node, need to use B6
    return subvals[end]
end

"""
This function returns a function: an SDC solver which needs only the initial
conditions as the arguments. Moreover, the quadrature and spectral integration
matrix are built-in to the function, not calculated each run.
"""
function make_SDC(n, fE, fI, implicit_solver)
    nodes = gauss_lobatto_nodes(n)
    n_cor = 2*n-3
    S = make_spectral_int_matrix(n) # nodes will be computed twice; should make more efficient

    return function small_arg_list_SDC(u, t, Δt)
        subtimes = (Δt*nodes).+ t
        subvals = Vector{typeof(u)}(undef, n)
        subvals[1] = u
        # Prediction
        for m in 1:n-1
            subvals[m+1] = begin
                u_m = subvals[m]
                t_m = subtimes[m]
                t_mp1 = subtimes[m+1]
                Δt_m = t_mp1 - t_m
                RHS = u_m + Δt_m*fE(t_m,u_m)
                implicit_solver(-Δt_m, fI, RHS)
            end
        end

        f(t, u) = fE(t, u) + fI(t, u)
        # Correction
        for k in 1:n_cor
            # B15 - Not needed for gauss lobatto nodes, just leaves subvals as they were
            # But B15 will be needed if start point is not a node
            f_subvals = [f(subtimes[i], subvals[i]) for i in 1:n]
            u_m_k = subvals[1]
            for m in 1:n-1
                #B16
                u_m_k_next = subvals[m+1] # Should figure out better notation for this
                subvals[m+1] = begin
                    u_m_kp1 = subvals[m]
                    t_m = subtimes[m]
                    t_mp1 = subtimes[m+1]
                    Δt_m = t_mp1 - t_m
                    u_mp1_k = subvals[m+1]
                    ΔfE = fE(t_m, u_m_kp1) - fE(t_m, u_m_k)
                    RHS = subvals[m] + Δt_m*(ΔfE - f(t_mp1, u_mp1_k))
                    RHS += sum(q -> (S[m+1,q]-S[m,q])*f_subvals[q], 1:size(S)[2])
                    implicit_solver(-Δt_m, fI, RHS)
                end
                u_m_k = u_m_k_next
            end
        end
        # Will be different if endpoint is a node, need to use B6
        return subvals[end]
    end
end

#function graph(u_np1_saves, T::Timing;
#              graph_name::String="SDC_evolution.png",
#              display_plot::Bool=false)
#
#    # Get exact value for comparison purposes
#    u_exact = u(T.t_n + T.Δt)
#    # Compute errors
#    u_np1_saves_errors = Vector{Tuple{Int64, Float64}}(undef, length(u_np1_saves))
#    for (i, (j, u_np1)) in enumerate(u_np1_saves)
#        #u_np1_saves_errors[i] = (j, abs(u_np1-u_exact))
#        u_np1_saves_errors[i] = (j, u_np1 - u_exact)
#    end
#
#    # Plotting
#    plot = Plots.plot(
#        getindex.(u_np1_saves_errors, 1), # No of Corrections
#        getindex.(u_np1_saves_errors, 2), # Error
#        label="Approximation of u(t_n+Δt)"
#    )
#    p = length(T.Δtimes)+1
#    Plots.plot!(
#        plot,
#        title="Error Over 1 Timestep Vs No of Corrections \nλ=$λ, Δt=$(T.Δt), t_n=$(T.t_n), p=$p (Gauss-Lobatto)",
#        xlabel="# of Corrections (0 ⟹ Initial Prediction)",
#        ylabel="Error",
#        xlim=(0, 2*p),
#        #yaxis=:log,
#    )
#    # Make a line on y=0, for easier comprehension
#    Plots.hline!(plot, [0], label="")
#
#    Plots.savefig(plot, graph_name)
#
#    if display_plot
#        display(plot)
#    end
#    return nothing
#end

#"""
#Make sure collocation nodes have proper scale and order.
#"""
#function validate_collocation_nodes(c_nodes)
#    for node in c_nodes
#        if node < 0 || node > 1
#            throw(DomainError(node, "Collocation nodes must be ∈ [0,1]."))
#        end
#    end
#    for i in 1:length(c_nodes)-1
#        if c_nodes[i+1] <= c_nodes[i]
#            throw(DomainError(node, "Collocation nodes must be obey c_m+1 > c_m ∀ m = 1, ... , p."))
#        end
#    end
#end

end


function generate_point(rng = Random.GLOBAL_RNG)
    thres = rand(rng)
    if thres >= 0.5
        rand(rng, LogNormal())
    else
        rand(rng, Uniform(2.0, 3.0))
    end
end

mt = Random.MersenneTwister(42)
xs = [generate_point(mt) for _ in 1:5000]
bandwidths = [0.05, 0.1, 0.5]
densities = [KernelDensity.kde(xs, bandwidth = bw) for bw in bandwidths]

p = Plots.plot()
for (b,d) in zip(bandwidths, densities)
    Plots.plot!(p, d.x, d.density, labels = "bw = $b")
end
Plots.xlims!(p, 0.0, 8.0)
Plots.title!("KDE with Gaussian Kernel")
Plots.savefig(joinpath(base_img, "KDE.pdf"))


"""
    omega_ratio(returns, target_return)

This function calculates the Omega ratio.

# Formula

    E[max(returns - target_return, 0)] / E[max(target_return - returns, 0)]

# Arguments
- `returns`:        Vector of asset returns.
- `target_return`:  Vector or scalar value of benchmark returns having same same frequency (e.g. daily) as the provided returns.
"""
function omega_ratio(returns, target_return)
    excess = returns .- target_return
    sum1 = sum(map(x -> max(0.0, x), excess))
    sum2 = -sum(map(x -> min(0.0, x), excess))
    sum1 / sum2
end


"""
    dominate(p, q)

Returns `1` if `p` is dominated by `q`, `-1` if otherwise, and `0` if dominance cannot be determined.
"""
function dominate(p::T, q::T) where {T <: AbstractArray}
    ret = 0
    for (i,j) in zip(p,q)
        if i < j
            ret == -1 && return 0
            ret = 1
        elseif j < i
            ret == 1 && return 0
            ret = -1
        end
    end
    return ret
end

"""
dominations(P::AbstractVector)

Returns a domination matrix of all elements in the input collection `P`.
"""
function dominations(P::AbstractVector{T}) where {T <: AbstractArray}
    l = length(P)
    D = zeros(Int8, l, l)
    for i in 1:l
        for j in (i+1):l
            D[i,j] = dominate(P[i],P[j])
            D[j,i] = -D[i,j]
        end
    end
    D
end

"""
    nondominatedsort!(R, F)

Calculate fronts for fitness values `F`, and store ranks of the individuals into `R`.
"""
function nondominatedsort!(R, P)
    n = size(P,2)
    @assert length(R) == n "Ranks must be defined for the whole population"

    Sₚ = Dict(i=>Set() for i in 1:n)
    C = zeros(Int, n)

    # construct first front
    F =[Int[]]
    for i in 1:n
        for j in i+1:n
            r = dominate(view(P,:,i), view(P,:,j)) #M[i,j]
            if r == 1
                push!(Sₚ[i], j)
                C[j] += 1
            elseif r == -1
                push!(Sₚ[j], i)
                C[i] += 1
            end
        end
        if C[i] == 0
            R[i] = 1
            push!(F[1], i)
        end
    end

    # construct rest of the fronts
    while !isempty(last(F))
        Q = Int[]
        for i in last(F)
            for j in Sₚ[i]
                C[j] -= 1
                if C[j] == 0
                    push!(Q, j)
                    R[j] = length(F) + 1
                end
            end
        end
        push!(F, Q)
    end
    isempty(last(F)) && pop!(F)

    F #, R #, Sₚ
end

"""
    crowding_distance!((C, F, fronts)

Calculate crowding distance for individuals and save the results into `C`
given the fitness values `F` and collection of `fronts`.
"""
function crowding_distance!(C::AbstractVector, F::AbstractMatrix{T}, fronts) where {T}
    for f in fronts
        cf = @view C[f]
        if length(cf) <= 2
            cf .= typemax(T)
        else
            # sort front by each objective value
            SF = F[:, f]
            d = size(SF,1)
            IX = zeros(Int, size(SF))
            IIX = zeros(Int, size(SF))
            for i in 1:d
                irow, iirow, row = view(IX,i,:), view(IIX,i,:), view(SF,i,:)
                sortperm!(irow, row)
                sortperm!(iirow, irow)
                permute!(row, irow)
            end
            nrm = SF[:,end] - SF[:,1]
            dst = (hcat(SF, fill(typemax(T), d)) - hcat(fill(typemin(T), d), SF)) ./ nrm
            dst[isnan.(dst)] .= zero(T)
            ss = sum(mapslices(v->diag(dst[:,v]) + diag(dst[:,v.+1]), IIX, dims=1), dims=1)
            cf .= vec(ss)/d
        end
    end
    C
end


# This file is a part of AstroLib.jl. License is MIT "Expat".
# Copyright (C) 2016 Mosè Giordano.

function _mag2flux{T<:AbstractFloat}(mag::T, zero_point::T, ABwave::T)
    if isnan(ABwave)
        return exp10(-0.4*(mag + zero_point))
    else
        return exp10(-0.4*(mag + 2.406 + 5*log10(float(ABwave))))
    end
end

"""
    mag2flux(mag[, zero_point, ABwave=number]) -> flux

### Purpose ###

Convert from magnitudes to flux expressed in erg/(s cm² Å).

### Explanation ###

This is the reverse of `flux2mag`.

### Arguments ###

* `mag`: the magnitude to be converted in flux.  It can be either a scalar or an
  array.
* `zero_point`: scalar giving the zero point level of the magnitude.  If not
 supplied then defaults to 21.1 (Code et al 1976).  Ignored if the `ABwave`
 keyword is supplied
* `ABwave` (optional numeric keyword): wavelength, scalar or array, in
 Angstroms.  If supplied, then the input `mag` is assumed to contain Oke AB
 magnitudes (Oke & Gunn 1983, ApJ, 266, 713;
 http://adsabs.harvard.edu/abs/1983ApJ...266..713O).

### Output ###

The flux.  It is of the same type, scalar or array, as `mag`.

If the `ABwave` keyword is set, then the flux is given by the expression

\$\$\\text{flux} = 10^{-0.4(\\text{mag} +2.406 + 4\\log_{10}(\\text{ABwave}))}\$\$

Otherwise the flux is given by

\$\$\\text{flux} =  10^{-0.4(\\text{mag} + \\text{zero point})}\$\$

### Example ###

``` julia
mag2flux(8.3)
# => 1.7378008287493692e-12
mag2flux(8.3, 12)
# => 7.58577575029182e-9
mag2flux(8.3, ABwave=12)
# => 3.6244115683017193e-7
```

### Notes ###

Code of this function is based on IDL Astronomy User's Library.
"""
mag2flux(mag::Real, zero_point::Real=21.1; ABwave::Real=NaN) =
    _mag2flux(promote(float(mag), float(zero_point), float(ABwave))...,)

function mag2flux{N<:Real}(mag::AbstractArray{N}, zero_point::Real=21.1;
                           ABwave::Real=NaN)
    flux = similar(mag, typeof(float(one(N))))
    for i in eachindex(mag)
        flux[i] = mag2flux(mag[i], zero_point, ABwave=ABwave)
    end
    return flux
end


@testset "Haar" begin

    haar_udwt = UDWT_Filter_Haar{Float64}()

    @test ϕ_filter(haar_udwt) ≈ [1/2, 1/2]
    @test tildeϕ_filter(haar_udwt) ≈ [1/2, 1/2]

    @test ψ_filter(haar_udwt) ≈ [-1/2, 1/2]
    @test tildeψ_filter(haar_udwt) ≈ [-1/2, 1/2]

end

@testset "Starck2" begin

    starck2_udwt = UDWT_Filter_Starck2{Float64}()

    @test fcoef(tildeϕ_filter(starck2_udwt)) ≈ [1]
end

@testset "UDWT Transform" begin

    signal = rand(20)

    for filter in [UDWT_Filter_Haar{Float64}()
                   UDWT_Filter_Starck2{Float64}()]

        s = 4
        m = udwt(signal,filter,scale=s)

        @test size(m.W) == (length(signal),s)
        @test size(m.V) == (length(signal),)
        @test scale(m) == s

        signal_from_inv = inverse_udwt(m)

        @test signal ≈ signal_from_inv
    end
end


using Base.Test

@testset "Construction and Initialization" begin
# ============================================= #
@testset "reshape" begin
    a::Array{Float64,3} = Array{Float64,3}(3,3,3)

    # - reshape - #
    a = reshape(1:27,3,3,3)
    # - same as the code below
    # value::Int64 = 1
    # for k in 1:3
    #     for j in 1:3
    #         for i in 1:3
    #             a[i,j,k] = value
    #             value += 1
    #         end
    #     end
    # end
    @test a[1,1,1] ≈ 1.
    @test isapprox(a[2,1,1], 2.)
    @test a[3,3,3] ≈ 27.
end
# ============================================= #
# - Comprehension - #
@testset "Comprehensions" begin
    # a =
    # [1.0 4.0 7.0;
    #  2.0 5.0 8.0;
    #  3.0 6.0 9.0]
    # [10.0 13.0 16.0;
    #  11.0 14.0 17.0;
    #  12.0 15.0 18.0]
    # [19.0 22.0 25.0;
    #  20.0 23.0 26.0;
    #  21.0 24.0 27.0]
    a::Array{Float64,3} = Float64[i+3(j-1)+9(k-1) for i=1:3, j=1:3, k=1:3]
    @test a[1,1,1] ≈ 1.
    @test isapprox(a[2,1,1], 2.)
    @test a[3,3,3] ≈ 27.
end
# ============================================= #
end


"""
    density1d(x::AbstractVector, grid::AbstractVector; normalize::Bool=True)
Compute number density on a line. Normalized such that int n dx =1.
"""
function density1d(x::AbstractVector, grid::AbstractVector; normalize::Bool=true)
    bins = length(grid) -1
    # initialize array to store number
    n = zeros(bins)
    # in a cell [a,b], particles that has a<=x<b is counted.
    # for the last cell, x==b is also counted.
    for i in 1:bins
        for position in x
            if grid[i] <=position <grid[i+1]
                n[i] += 1
            end
        end
    end
    # last cell: add particles on the right boundary
    n[end] += sum(x .== grid[end])
    # use bin centers as representative locations.
    dx = diff(grid)
    bin_centers = grid[1:end-1] + dx/2
    if normalize
        n ./= dx *length(x)
    end
    return bin_centers, n
end

"""
    density1d(x::AbstractVector, xmin::Real, xmax::Real; bins::Int=100)
Compute number density on a line. Normalized such that int n dx =1.
"""
function density1d(x::AbstractVector, xmin::Real, xmax::Real; bins::Int=100, normalize::Bool=true)
    xmin < xmax ||throw(ArgumentError("xmin>=xmax"))
    grid = LinRange(xmin, xmax, bins+1)
    return density1d(x, grid, normalize=normalize)
end

"""
    density1d(x::AbstractVector; bins::Int=100, normalize::Bool=true)
Compute number density on a line.
"""
function density1d(x::AbstractVector; bins::Int=100, normalize::Bool=true)
    xmin = minimum(x)
    xmax = maximum(x)
    return density1d(x, xmin, xmax, bins=bins, normalize=normalize)
end


# This file includes portions from JuliaMath/Combinatorics.jl in modified form.
# Copyright Peter Luschny. License is MIT.

(@__DIR__) ∉ LOAD_PATH && push!(LOAD_PATH, (@__DIR__))

module CombinationsIterator

using IterTools

export ModuleCombinationsIterator
export Combinations

"""
* Combinations
"""
const ModuleCombinationsIterator = ""

#The combinations iterator
struct combinations
    n::Int
    t::Int
end

function Base.iterate(c::combinations, s=[min(c.t - 1, i) for i in 1:c.t])
    if c.t == 0 # special case to generate 1 result for t==0
        isempty(s) && return (s, [1])
        return
    end
    for i in c.t:-1:1
        s[i] += 1
        if s[i] > (c.n - (c.t - i))
            continue
        end
        for j in i + 1:c.t
            s[j] = s[j - 1] + 1
        end
        break
    end
    s[1] > c.n - c.t + 1 && return
    (s, s)
end

Base.length(c::combinations) = binomial(c.n, c.t)
Base.eltype(::Type{combinations}) = Vector{Int}

"""
Generate all Combinations of ``n`` elements from an indexable object ``a``. Because the number of Combinations can be very large, this function returns an iterator object.
 Use collect(Combinations(a, n)) to get an array of all Combinations.
"""
function Combinations(a, t::Integer)
    if t < 0
        # generate 0 Combinations for negative argument
        t = length(a) + 1
    end
    reorder(c) = [a[ci] for ci in c]
    (reorder(c) for c in combinations(length(a), t))
end

"""
Generate Combinations of the elements of ``a`` of all orders. Chaining of order iterators is eager, but the sequence at each order is lazy.
"""
Combinations(a) = Iterators.flatten([Combinations(a, k) for k = 1:length(a)])

#START-TEST-########################################################

using Test, SeqUtils

function test()
    C = collect(Combinations([2, 3, 5]))
    @testset "Combinations" begin
        @test C == [[2], [3], [5], [2, 3], [2, 5], [3, 5], [2, 3, 5]]
    end
end

function demo()
    Println([Combinations("abcd", 3)...])

    D = [2, 3, 4, 5]
    for c in Combinations(D)
        println(c)
    end

    println()
    C = collect(Combinations([2, 3, 5]))
    Println(C)
end

function perf() end

function main()
    test()
    demo()
    perf()
end

main()

end # module

#=

Array{Char,1}[['a', 'b', 'c'], ['a', 'b', 'd'], ['a', 'c', 'd'], ['b', 'c', 'd']]

[2][3]
[4]
[5]
[2, 3]
[2, 4]
[2, 5]
[3, 4]
[3, 5]
[4, 5]
[2, 3, 4]
[2, 3, 5]
[2, 4, 5]
[3, 4, 5]
[2, 3, 4, 5]

=#


# # How similar do two strings look?

# ## VisualStringDistances.jl

#

# ---

# ## Let's compare strings

#

using StringDistances

# How many single-character edits are needed to turn "Julia" into "JuIia"?

StringDistances.Levenshtein()("Julia", "JuIia")

# What about "Julia" into "JuQia"?

StringDistances.Levenshtein()("Julia", "JuQia")

# We can also compare based on how many times consecutive pairs of letters appear in each string...

StringDistances.QGram(2)("Julia", "JuIia"), StringDistances.QGram(2)("Julia", "JuQia")

# ---
# ## Visual distances

#

# But none of these take into account that "Julia" and "JuIia" look pretty similar, while "Julia" and "JuQia" look pretty different.

using VisualStringDistances: VisualStringDistances
const VSD = VisualStringDistances
VSD.visual_distance("Julia",  "JuIia"), VSD.visual_distance("Julia", "JuQia")

#

# That seems better! But how do we know it does something reasonable in other cases too? And how does it work?

#

# Just need two tools:
# 1.
 A way to translate strings into images

# 2.
 A way to compare images

# ---

# ## 1. A way to translate strings into images: GNU Unifont

VSD.printglyph("GNU Unifont"; symbols=("#", "-"))

# A bitmap font!

# ---

# Unifont stores characters as bitmaps, making things quite easy for us:

VSD.Glyph("Julia")

#

# (see also FreeTypeAbstraction.jl to render bitmaps from many fonts!)

# ---

# It is low resolution, but simple and comprehensive, with 57086 supported characters, including...

chars = [VSD.get_char(k) for k in rand(collect(keys(VSD.UNIFONT_LOOKUP)), 5)];
permutedims(chars)

# Which render as:

VSD.printglyph(join(chars, " "))

# ---

VSD.printglyph("Julia vs JuIia"); VSD.printglyph("Julia vs JuQia") # hide

# ---

# ## 2. A way to compare images: Optimal transport

#

# *
 you have $a(x_1)$ amount of stuff at site $x_1$, $a(x_2)$ amount of stuff at $x_2$, ..., $a(x_n)$ stuff at $x_n$.

# *
 you want to move it around until you have $b(y_1)$ stuff at site $y_1$, $b(y_2)$ stuff at $y_2$, ..., $b(y_m)$ stuff at $y_m$

# *
 it costs $c(x_i, y_j)$ to move one unit of mass from $x_i$ to $y_j$

# ```math
# \begin{aligned}
# \operatorname{OT}(a,b) := \text{minimize} \quad & \sum\_{x,y} π(x,y)\, c(x,y)\\\\
# \text{such that} \quad & a(x) = \sum\_{y} \pi(x,y)\\\\
# & b(y) = \sum\_{x} \pi(x,y) \\\\
# & \pi(x,y) \geq 0
# \end{aligned}
# ```

# *
 We optimize to find the variables $\pi(x,y)$ (how much stuff to move from $x$ to $y$)

# ---

# ## How does optimal transport relate to our problem?

#

# -
 If we have a black pixel in the 3rd column and 2nd row of the bitmap, we can see that as $a(1) = 1$ unit of mass at site $x_1 = (2,3)$.

# -
 In this way, we can translate the bitmap representation of the string into the language of optimal transport.

# -
 $c(x,y)$ is just the distance between those points

# -
 Note: we do two modifications to this

#   -
 we solve an approximate version for speed ("entropic regularization")

#   -
 add penalties for creating/destroying stuff for the case $\sum_x a(x) \neq  \sum_y b(y)$     [1].

#

#

#

# [1]: Séjourné, T., Feydy, J., Vialard, F.-X., Trouvé, A., Peyré, G., 2019. *Sinkhorn Divergences for Unbalanced Optimal Transport*. https://arxiv.org/abs/1910.12958.

# ---

# ## What use does this have?

# Making gifs!

# ---

# ## What use does this have?

# Adding a check for new packages being added to the General registry to try to prevent the malicious impersonation another package.

# Two main concerns:

# 1. Possibly, one will make a typo, and end up at the wrong package ("typosquatting") $\leadsto$ edit distance check
# 2. Possibly, one will copy a malicious tutorial that has mimicked the appearance of the name of a popular package $\leadsto$ visual distance

#

# ---

# ## Is this the right visual distance for an automated registry check?

# I'm not sure.

# *
 Human perception is actually a bit different

#   * e.g. we mix up "p" vs "q" more than "a" vs "e"  [2], but `visual_distance` says "p" and "q" are further apart than "a" and "e"
# *
 optimal transport is a bit slow (though not prohibitively so, with entropic regularization and the low resolution font)

# *
 there are several parameters and cutoffs to tune

#
#
# Possibly a perceptually-weighted edit distance is more sensible.

#

#

# [2]: Courrieu, Pierre, Fernand Farioli, and Jonathan Grainger. *Inverse Discrimination Time as a Perceptual Distance for Alphabetic Characters*. Visual Cognition 11, no. 7 (October 2004): 901–19. https://doi.org/10.1080/13506280444000049.

# ---

# ## References & Notes

# * Package for `visual_distance`, `printglyph`, etc: VisualStringDistances.jl
# *
 Package with the underlying algorithm optimal transport algorithm: UnbalancedOptimalTransport.jl

#

#

# References:
#

# [1]: Séjourné, T., Feydy, J., Vialard, F.-X., Trouvé, A., Peyré, G., 2019. *Sinkhorn Divergences for Unbalanced Optimal Transport*. https://arxiv.org/abs/1910.12958.
#
# [2]: Courrieu, Pierre, Fernand Farioli, and Jonathan Grainger. *Inverse Discrimination Time as a Perceptual Distance for Alphabetic Characters*. Visual Cognition 11, no. 7 (October 2004): 901–19. https://doi.org/10.1080/13506280444000049.

#

# Slides made with the help of Remark.jl, Literate.jl, and Documenter.jl; gifs made with Makie.jl.

# Thanks to Stefan Karpinski for suggesting GNU Unifont.


# # Intro to AbstractGPs: one-dimensional regression
#
# ## Setup
#
# Loading the necessary packages.

using AbstractGPs
using Distributions
using StatsFuns

using Plots
default(; legend=:outertopright, size=(700, 400))

using Random
Random.seed!(42)  # setting the seed for reproducibility of this notebook
#md nothing #hide

# Load toy regression
# [dataset](https://github.com/GPflow/GPflow/blob/7705cee6723f78066981f27954130daaede55dfc/doc/sphinx/notebooks/basics/data/regression_1D.csv)
# taken from GPflow examples.

x = [
    0.8658165855998895,
    0.6661700880180962,
    0.8049218148148531,
    0.7714303440386239,
    0.14790478354654835,
    0.8666105548197428,
    0.007044577166530286,
    0.026331737288148638,
    0.17188596617099916,
    0.8897812990554013,
    0.24323574561119998,
    0.028590102134105955,
]
y = [
    1.5255314337144372,
    3.6434202968230003,
    3.010885733911661,
    3.774442382979625,
    3.3687639483798324,
    1.5506452040608503,
    3.790447985799683,
    3.8689707574953,
    3.4933565751758713,
    1.4284538820635841,
    3.8715350915692364,
    3.7045949061144983,
]
scatter(x, y; xlabel="x", ylabel="y", legend=false)

# We split the observations into train and test data.

x_train = x[1:8]
y_train = y[1:8]
x_test = x[9:end]
y_test = y[9:end]
#md nothing #hide

# We instantiate a Gaussian process with a Matern kernel. The kernel has
# fixed variance and length scale parameters of default value 1.

f = GP(Matern52Kernel())
#md nothing #hide

# We create a finite dimensional projection at the inputs of the training dataset
# observed under Gaussian noise with variance $\sigma^2 = 0.1$, and compute the
# log-likelihood of the outputs of the training dataset.

fx = f(x_train, 0.1)
logpdf(fx, y_train)

# We compute the posterior Gaussian process given the training data, and calculate the
# log-likelihood of the test dataset.

p_fx = posterior(fx, y_train)
logpdf(p_fx(x_test), y_test)

# We plot the posterior Gaussian process (its mean and a ribbon of 2 standard deviations
# around it) on a grid along with the observations.

scatter(
    x_train,
    y_train;
    xlim=(0, 1),
    xlabel="x",
    ylabel="y",
    title="posterior (default parameters)",
    label="Train Data",
)
scatter!(x_test, y_test; label="Test Data")
plot!(0:0.001:1, p_fx; label=false, ribbon_scale=2)

# ## Markov Chain Monte Carlo
#
# Previously we computed the log likelihood of the untuned kernel parameters of the GP.
# We now also perform approximate inference over said kernel parameters using different
# Markov chain Monte Carlo (MCMC) methods. I.e., we approximate the posterior distribution
# of the kernel parameters with samples from a Markov chain.
#
# We define a function which returns the log-likelihood of the data for different variance
# and inverse lengthscale parameters of the Matern kernel. We ensure that these parameters are
# positive with the softplus function
# ```math
# f(x) = \log (1 + \exp x).
# ```

function gp_loglikelihood(x, y)
    function loglikelihood(params)
        kernel =
            softplus(params[1]) * (Matern52Kernel() ∘ ScaleTransform(softplus(params[2])))
        f = GP(kernel)
        fx = f(x, 0.1)
        return logpdf(fx, y)
    end
    return loglikelihood
end

const loglik_train = gp_loglikelihood(x_train, y_train)
#md nothing #hide

# We define a Gaussian prior for the joint distribution of the two transformed kernel
# parameters. We assume that both parameters are independent with mean 0 and variance 1.

logprior(params) = logpdf(MvNormal(2, 1), params)
#md nothing #hide

# ### Hamiltonian Monte Carlo
#
# We start with a Hamiltonian Monte Carlo (HMC) sampler. More precisely, we use the
# [No-U-Turn sampler (NUTS)](http://www.jmlr.org/papers/volume15/hoffman14a/hoffman14a.pdf),
# which is provided by the Julia packages
# [AdvancedHMC.jl](https://github.com/TuringLang/AdvancedHMC.jl/) and
# [DynamicHMC.jl](https://github.com/tpapp/DynamicHMC.jl/).
#
# #### AdvancedHMC
#
# We start with performing inference with AdvancedHMC.

using AdvancedHMC
using ForwardDiff

# Set the number of samples to draw and warmup iterations.

n_samples = 2_000
n_adapts = 1_000
#md nothing #hide

# Define a Hamiltonian system of the log joint probability.

logjoint_train(params) = loglik_train(params) + logprior(params)
metric = DiagEuclideanMetric(2)
hamiltonian = Hamiltonian(metric, logjoint_train, ForwardDiff)
#md nothing #hide

# Define a leapfrog solver, with initial step size chosen heuristically.

initial_params = rand(2)
initial_ϵ = find_good_stepsize(hamiltonian, initial_params)
integrator = Leapfrog(initial_ϵ)
#md nothing #hide

# Define an HMC sampler, with the following components:
# - multinomial sampling scheme,
# - generalised No-U-Turn criteria, and
# - windowed adaption for step-size and diagonal mass matrix

proposal = NUTS{MultinomialTS,GeneralisedNoUTurn}(integrator)
adaptor = StanHMCAdaptor(MassMatrixAdaptor(metric), StepSizeAdaptor(0.8, integrator))
#md nothing #hide

# We draw samples from the posterior distribution of kernel parameters. These samples
# are in the unconstrained space $\mathbb{R}^2$.

samples, _ = sample(
    hamiltonian, proposal, initial_params, n_samples, adaptor, n_adapts; progress=false
)
#md nothing #hide

# We transform the samples back to the constrained space and compute the mean of both
# parameters:

samples_constrained = [map(softplus, p) for p in samples]
mean_samples = mean(samples_constrained)

# We plot a histogram of the samples for the two parameters.
# The vertical line in each graph indicates the mean of the samples.

histogram(
    reduce(hcat, samples_constrained)';
    xlabel="sample",
    ylabel="counts",
    layout=2,
    title=["variance" "inverse length scale"],
    legend=false,
)
vline!(mean_samples'; linewidth=2)

# We approximate the log-likelihood of the test data using the posterior Gaussian processes
# for kernels with the sampled kernel parameters. We can observe that there is a significant
# improvement over the log-likelihood of the test data with respect to the posterior
# Gaussian process with default kernel parameters of value 1.

function gp_posterior(x, y, p)
    kernel = softplus(p[1]) * (Matern52Kernel() ∘ ScaleTransform(softplus(p[2])))
    f = GP(kernel)
    return posterior(f(x, 0.1), y)
end

mean(logpdf(gp_posterior(x_train, y_train, p)(x_test), y_test) for p in samples)

# We sample 5 functions from each posterior GP given by the final 100 samples of kernel
# parameters.

plt = plot(; xlim=(0, 1), xlabel="x", ylabel="y", title="posterior (AdvancedHMC)")
for (i, p) in enumerate(samples[(end - 100):end])
    sampleplot!(
        plt,
        0:0.02:1,
        gp_posterior(x_train, y_train, p);
        samples=5,
        seriescolor="red",
        label=(i == 1 ? "samples" : nothing),
    )
end
scatter!(plt, x_train, y_train; label="Train Data", markercolor=1)
scatter!(plt, x_test, y_test; label="Test Data", markercolor=2)
plt

# #### DynamicHMC
#
# We repeat the inference with DynamicHMC. DynamicHMC requires us to
# implement the LogDensityProblems interface for `loglik_train`.

using DynamicHMC
using LogDensityProblems

## Log joint density
function LogDensityProblems.logdensity(ℓ::typeof(loglik_train), params)
    return ℓ(params) + logprior(params)
end

## The parameter space is two-dimensional
LogDensityProblems.dimension(::typeof(loglik_train)) = 2

## `loglik_train` does not allow to evaluate derivatives of
## the log-likelihood function
function LogDensityProblems.capabilities(::Type{<:typeof(loglik_train)})
    return LogDensityProblems.LogDensityOrder{0}()
end

# Now we can draw samples from the posterior distribution of kernel parameters with
# DynamicHMC. Again we use [ForwardDiff.jl](https://github.com/JuliaDiff/ForwardDiff.jl)
# to compute the derivatives of the log joint density with automatic differentiation.

samples =
    mcmc_with_warmup(
        Random.GLOBAL_RNG,
        ADgradient(:ForwardDiff, loglik_train),
        n_samples;
        reporter=NoProgressReport(),
    ).chain
#md nothing #hide

# We transform the samples back to the constrained space and compute the mean of both
# parameters:

samples_constrained = [map(softplus, p) for p in samples]
mean_samples = mean(samples_constrained)

# We plot a histogram of the samples for the two parameters.
# The vertical line in each graph indicates the mean of the samples.

histogram(
    reduce(hcat, samples_constrained)';
    xlabel="sample",
    ylabel="counts",
    layout=2,
    title=["variance" "inverse length scale"],
    legend=false,
)
vline!(mean_samples'; linewidth=2)

# Again we can observe that there is a significant improvement over the log-likelihood
# of the test data with respect to the posterior Gaussian process with default kernel
# parameters.

mean(logpdf(gp_posterior(x_train, y_train, p)(x_test), y_test) for p in samples)

# We sample a function from the posterior GP for the final 100 samples of kernel
# parameters.

plt = plot(; xlim=(0, 1), xlabel="x", ylabel="y", title="posterior (DynamicHMC)")
scatter!(plt, x_train, y_train; label="Train Data")
scatter!(plt, x_test, y_test; label="Test Data")
for p in samples[(end - 100):end]
    sampleplot!(plt, 0:0.02:1, gp_posterior(x_train, y_train, p); seriescolor="red")
end
plt

# ### Elliptical slice sampling
#
# Instead of HMC, we use
# [elliptical slice sampling](http://proceedings.mlr.press/v9/murray10a/murray10a.pdf)
# which is provided by the Julia package
# [EllipticalSliceSampling.jl](https://github.com/TuringLang/EllipticalSliceSampling.jl/).

using EllipticalSliceSampling

# We draw 2000 samples from the posterior distribution of kernel parameters.

samples = sample(ESSModel(
    MvNormal(2, 1), # Gaussian prior
    loglik_train,
), ESS(), n_samples; progress=false)
#md nothing #hide

# We transform the samples back to the constrained space and compute the mean of both
# parameters:

samples_constrained = [map(softplus, p) for p in samples]
mean_samples = mean(samples_constrained)

# We plot a histogram of the samples for the two parameters.
# The vertical line in each graph indicates the mean of the samples.

histogram(
    reduce(hcat, samples_constrained)';
    xlabel="sample",
    ylabel="counts",
    layout=2,
    title=["variance" "inverse length scale"],
)
vline!(mean_samples'; layout=2, labels="mean")

# Again we can observe that there is a significant improvement over the log-likelihood
# of the test data with respect to the posterior Gaussian process with default kernel
# parameters.

mean(logpdf(gp_posterior(x_train, y_train, p)(x_test), y_test) for p in samples)

# We sample a function from the posterior GP for the final 100 samples of kernel
# parameters.

plt = plot(;
    xlim=(0, 1), xlabel="x", ylabel="y", title="posterior (EllipticalSliceSampling)"
)
scatter!(plt, x_train, y_train; label="Train Data")
scatter!(plt, x_test, y_test; label="Test Data")
for p in samples[(end - 100):end]
    sampleplot!(plt, 0:0.02:1, gp_posterior(x_train, y_train, p); seriescolor="red")
end
plt

# ## Variational Inference
#
# Sanity check for the Evidence Lower BOund (ELBO) implemented according to
# M. K. Titsias's _Variational learning of inducing variables in sparse Gaussian processes_.

elbo(VFE(f(rand(5))), fx, y_train)

# We use the LBFGS algorithm to maximize the given ELBO. It is provided by the Julia
# package [Optim.jl](https://github.com/JuliaNLSolvers/Optim.jl).

using Optim

# We define a function which returns the negative ELBO for different variance and inverse
# lengthscale parameters of the Matern kernel and different pseudo-points. We ensure that
# the kernel parameters are positive with the softplus function
# ```math
# f(x) = \log (1 + \exp x),
# ```
# and that the pseudo-points are in the unit interval $[0,1]$ with the logistic function
# ```math
# f(x) = \frac{1}{1 + \exp{(-x)}}.
# ```

jitter = 1e-6  # "observing" the latent process with some (small) amount of jitter improves numerical stability

function objective_function(x, y)
    function negative_elbo(params)
        kernel =
            softplus(params[1]) * (Matern52Kernel() ∘ ScaleTransform(softplus(params[2])))
        f = GP(kernel)
        fx = f(x, 0.1)
        z = logistic.(params[3:end])
        approx = VFE(f(z, jitter))
        return -elbo(approx, fx, y)
    end
    return negative_elbo
end
#md nothing #hide

# We randomly initialize the kernel parameters and 5 pseudo points, and minimize the
# negative ELBO with the LBFGS algorithm and obtain the following optimal parameters:

x0 = rand(7)
opt = optimize(objective_function(x_train, y_train), x0, LBFGS())

#-

opt.minimizer

# The optimized value of the variance is

softplus(opt.minimizer[1])

# and of the inverse lengthscale is

softplus(opt.minimizer[2])

# We compute the log-likelihood of the test data for the resulting approximate
# posterior. We can observe that there is a significant improvement over the
# log-likelihood with the default kernel parameters of value 1.

opt_kernel =
    softplus(opt.minimizer[1]) *
    (Matern52Kernel() ∘ ScaleTransform(softplus(opt.minimizer[2])))
opt_f = GP(opt_kernel)
opt_fx = opt_f(x_train, 0.1)
ap = posterior(VFE(opt_f(logistic.(opt.minimizer[3:end]), jitter)), opt_fx, y_train)
logpdf(ap(x_test), y_test)

# We visualize the approximate posterior with optimized parameters.

scatter(
    x_train,
    y_train;
    xlim=(0, 1),
    xlabel="x",
    ylabel="y",
    title="posterior (VI with sparse grid)",
    label="Train Data",
)
scatter!(x_test, y_test; label="Test Data")
plot!(0:0.001:1, ap; label=false, ribbon_scale=2)
vline!(logistic.(opt.minimizer[3:end]); label="Pseudo-points")

# ## Exact Gaussian Process Inference
#
# Here we use Type-II MLE to train the hyperparameters of the Gaussian process.
# This means that our loss function is the negative log marginal likelihood.

# We re-calculate the log-likelihood of the test dataset with the
# default kernel parameters of value 1 for the sake of comparison.

logpdf(p_fx(x_test), y_test)

# We define a function which returns the negative log marginal
# likelihood for different variance and inverse lengthscale parameters
# of the Matern kernel and different pseudo-points. We ensure that the
# kernel parameters are positive with the softplus function
# ``f(x) = \log (1 + \exp x)``.

function loss_function(x, y)
    function negativelogmarginallikelihood(params)
        kernel =
            softplus(params[1]) * (Matern52Kernel() ∘ ScaleTransform(softplus(params[2])))
        f = GP(kernel)
        fx = f(x, 0.1)
        return -logpdf(fx, y)
    end
    return negativelogmarginallikelihood
end

#md nothing #hide

# We randomly initialize the kernel parameters, and minimize the
# negative log marginal likelihood with the LBFGS algorithm
# and obtain the following optimal parameters:

θ0 = randn(2)
opt = Optim.optimize(loss_function(x_train, y_train), θ0, LBFGS())

#-

opt.minimizer

# The optimized value of the variance is

softplus(opt.minimizer[1])

# and of the inverse lengthscale is

softplus(opt.minimizer[2])

# We compute the log-likelihood of the test data for the resulting optimized
# posterior. We can observe that there is a significant improvement over the
# log-likelihood with the default kernel parameters of value 1.

opt_kernel =
    softplus(opt.minimizer[1]) *
    (Matern52Kernel() ∘ ScaleTransform(softplus(opt.minimizer[2])))

opt_f = GP(opt_kernel)
opt_fx = opt_f(x_train, 0.1)
opt_p_fx = posterior(opt_fx, y_train)
logpdf(opt_p_fx(x_test), y_test)

# We visualize the posterior with optimized parameters.

scatter(
    x_train,
    y_train;
    xlim=(0, 1),
    xlabel="x",
    ylabel="y",
    title="posterior (optimized parameters)",
    label="Train Data",
)
scatter!(x_test, y_test; label="Test Data")
plot!(0:0.001:1, opt_p_fx; label=false, ribbon_scale=2)


#2-dim CAs

#Two dimensional Cellular Automaton
#TODO: Change naming to be consistent
mutable struct CA2d

    #User given values
    k::Int #Number of states
    r::Int #r-nearest neigbors

    #Internal values
    cells::Array{Int8, 3}

    function CA2d(B::Array{Int,1},
                  S::Array{Int,1},
                  init::Array{Int,2},
                  gen::Int,
                  k::Int=2,
                  r::Int=1)

        h, w = size(init)

        cells = Array{Int8}(undef, (h, w, gen)) #Syntax A(T, dims) is deprecated
        cells[:, :, 1] = Array{Int8}(init[:, :])

        for g = 2:gen
            for i = 1:h, j = 1:w
                cc = -cells[i, j, g-1]
                for p = (i-r):(i+r), q = (j-r):(j+r)

                    #Cyclic boundary conditions
                    if p < 1; p = h-p; end
                    if p > h; p = p-h; end
                    if q < 1; q = w-q; end
                    if q > w; q = q-w; end

                    cc += cells[p, q, g-1]
                end
                cells[i, j, g] = eval_rule(cc, cells[i, j, g-1], B, S)
            end #hw ij
        end #gen

        new(k, r, cells)
    end
end

#Evaluate life rules
#TODO: make this more general
function eval_rule(cc, olds, B, S, k=2)
    if cc in B || (olds == 1 && cc in S)
        return 1
    end

    return 0
end


#=
This script shows how a simple MLP net may be used
for regression. It shows how data in memory may be
used for training and evaluation, and how to obtain
the predictions from the trained net.
=#
using MXNet
using Distributions
using PyPlot

# data generating process
generate_inputs(mean, var, size) = rand(MvNormal(mean, var), size)
output(data) = sin(data[1,:]).*sin(data[2,:])./(data[1,:].*data[2,:])

# create training and evaluation data sets
mean=[0.0;0.0]
var=[1.0 0.0;0.0 1.0]
samplesize  = 5000
TrainInput = generate_inputs(mean, var, samplesize)
TrainOutput = output(TrainInput)
ValidationInput = generate_inputs(mean, var, samplesize)
ValidationOutput = output(ValidationInput)

# how to set up data providers using data in memory
batchsize = 100 # can adjust this later, but must be defined now for next line
trainprovider = mx.ArrayDataProvider(:data => TrainInput, batch_size=batchsize, shuffle=true, :label => TrainOutput)
evalprovider = mx.ArrayDataProvider(:data => ValidationInput, batch_size=batchsize, shuffle=true, :label => ValidationOutput)

# create a two hidden layer MPL: try varying num_hidden, and change tanh to relu,
# or add/remove a layer
data = mx.Variable(:data)
label = mx.Variable(:label)
net  = @mx.chain    mx.FullyConnected(data = data, num_hidden=10) =>
                    mx.Activation(act_type=:tanh) =>
                    mx.FullyConnected(num_hidden=3) =>
                    mx.Activation(act_type=:tanh) =>
                    mx.FullyConnected(num_hidden=1)

# squared error loss is appropriate for regression, don't change
cost = mx.LinearRegressionOutput(data = net, label=label)

# final model definition, don't change, except if using gpu
model = mx.FeedForward(cost, context=mx.cpu())

# set up the optimizer: select one, explore parameters, if desired
#optimizer = mx.SGD(lr=0.01, momentum=0.9, weight_decay=0.00001)
optimizer = mx.ADAM()

# train, reporting loss for training and evaluation sets
# initial training with small batch size, to get to a good neighborhood
batchsize = 100
mx.fit(model, optimizer, initializer=mx.NormalInitializer(0.0,0.1), eval_metric=mx.MSE(), trainprovider, eval_data=evalprovider, n_epoch = 500)
# more training with the full sample
batchsize = samplesize
mx.fit(model, optimizer, eval_metric=mx.MSE(), trainprovider, eval_data=evalprovider, n_epoch = 500)

# obtain predictions
plotprovider = mx.ArrayDataProvider(:data => ValidationInput, :label => ValidationOutput)
fit = mx.predict(model, plotprovider)
plot(ValidationOutput,fit',".")
xlabel("true")
ylabel("predicted")
title("outputs: true versus predicted. 45º line is what we hope for")


function mult(M, V) #funkcja mnozaca macierze M V
    R = zeros(size(M,1), size(V,2))
    for i = 1:size(M,1)
        for j = 1:size(V,2)
            for k = 1:size(M,1)
                R[i,j] += M[i,k] * V[k,j]
            end
        end
    end
    return R
end

function strassen(A, B, size, stop) #algorytm Strassena, mnozy macierze A, B o rozmiarze size
   if size <= stop
      return mult(A,B)
   end
   half = div(size,2)
   a11 = A[1:half, 1:half]
   a12 = A[1:half, half+1:size]
   a21 = A[half+1:size, 1:half]
   a22 = A[half+1:size, half+1:size]

   b11 = B[1:half, 1:half]
   b12 = B[1:half, half+1:size]
   b21 = B[half+1:size, 1:half]
   b22 = B[half+1:size, half+1:size]

   m1 = strassen(a11 + a22, b11 + b22, half, stop)
   m2 = strassen(a21 + a22, b11, half, stop)
   m3 = strassen(a11, b12 - b22, half, stop)
   m4 = strassen(a22, b21 - b11, half, stop)
   m5 = strassen(a11 + a12, b22, half, stop)
   m6 = strassen(a21 - a11, b11 + b12, half, stop)
   m7 = strassen(a12 - a22, b21 + b22, half, stop)

   c11 = m1 + m4 - m5 + m7
   c12 = m3 + m5
   c21 = m2 + m4
   c22 = m1 - m2 + m3 + m6

   res = vcat(hcat(c11, c12), hcat(c21, c22))
   return res
end
function s_mult(A,B,stop)
   edge1 = size(A, 1)
   edge2 = size(A, 2)
   pow2 = 2^Integer(ceil(max(log(2, edge2),
                             log(2, edge1))))
   if edge1 < pow2 || edge2 < pow2
      zero_ = zeros(pow2-edge1, edge2)
      A = vcat(A, zero_)
      zero_ = zeros(size(A, 1), pow2-edge2)
      A = hcat(A, zero_)
      zero_ = zeros(edge2, pow2-edge1)
      B = hcat(B, zero_)
      zero_ = zeros(pow2-edge2, size(B,2))
      B = vcat(B, zero_)
   end

   res = strassen(A,B, pow2, stop)
   return res[1:edge1, 1:edge1]
end

function err(M)
   r = size(M, 1)
   c = size(M, 2)
   sum = Float64(0)
   for i in 1:r
      for j in 1:c
         k = M[r,c]
         sum += k*k
      end
   end
    return sum
end

function bestStrass(A, B)
   return s_mult(A, B, 64)
end

function compare()
    M_size = 64
    for i in 1:10
        A = rand(-100.0:0.001:100.0, M_size, M_size)
        B = rand(-100.0:0.001:100.0, M_size, M_size)
        C = rand(-100.0:0.001:100.0, M_size, M_size)
        S_result = test_str(A,B,C, 64)
        N_result = test_normal(A,B,C)
        println(err(S_result))
        println(err(N_result))
        println("Blad wzgledny:", abs(err(S_result) - err(N_result)) / err(N_result) )
    end
end
#compare()
function memory_usage()
    M_size = 512
    A = rand(-100.0:0.001:100.0, M_size, M_size)
    B = rand(-100.0:0.001:100.0, M_size, M_size)
    @timev mult(A,B)
    @timev s_mult(A,B, 64)
end
#memory_usage()

II rok/I semestr/ANM/Pracownia 1/prog/program.jl


using GlobalDiffEq, OrdinaryDiffEq, LinearAlgebra
using Test

l = 1.0                             # length [m]
m = 1.0                             # mass[m]
g = 9.81                            # gravitational acceleration [m/s²]

function pendulum!(du,u,p,t)
    du[1] = u[2]                    # θ'(t) = ω(t)
    du[2] = -3g/(2l)*sin(u[1]) + 3/(m*l^2)*p(t) # ω'(t) = -3g/(2l) sin θ(t) + 3/(ml^2)M(t)
end

θ₀ = 0.01                           # initial angular deflection [rad]
ω₀ = 0.0                            # initial angular velocity [rad/s]
u₀ = [θ₀, ω₀]                       # initial state vector
tspan = (0.0,10.0)                  # time interval

M = t->0.1sin(t)                    # external torque [Nm]

prob = ODEProblem(pendulum!,u₀,tspan,M)

v0 = solve(prob, Tsit5(), dt=0.1, reltol=1e-12, abstol=0).(1:10)
ve = solve(prob, GlobalRichardson(SSPRK33()), dt=0.2, reltol=1e-12, abstol=0).(1:10)
@test norm(ve - v0) / norm(v0) < 1e-10


# This code generates the 2l+1 dimensional matrix Fourier coefficients
# for a function defined over the rotation and rotation/reflection groups
#
# The routines in this file are based upon:
#
#   FFTs on the Rotation Group
#   Peter J. Kostelec and Daniel N. Rockmore
#   J. Fourier. Anal. Appl., Vol. 14, Issue. 2, p. 145-179, 2008

import Base.fft

function w(B::Int, k::Int)
    s = 0.0

    for j in 0:B-1
        s += sin((2j + 1) * (2k + 1) * (π / (4B))) / (2j + 1)
    end

    s *= (2/B) * sin(π * (2k + 1) / (4B))
end

function S₁(f::Function, B::Int, k::Int, j₂::Int)
    β = π * (2k + 1) / (4B)
    γ = (2π * j₂) / (2B)

    A = [exp(-im * ((2π * j₁) / (2B)) * (B - 1)) * f(O3((2π * j₁) / (2B), β, γ)) for j₁ in 0:2B-1]
    A′ = ifft(A)
end

function S₂(f::Function, B::Int, k::Int)
    S = [S₁(f, B, k, j₂) for j₂ in 0:2B-1]
    A = Vector{Vector{Complex128}}(2B)
    A′ = Vector{Vector{Complex128}}(2B)

    for M′ in -(B-1):B
        A[M′ + B] = [exp(-im * ((2π * j₂) / (2B)) * (B - 1)) * S[j₂ + 1][M′ + B] for j₂ in 0:2B-1]
        A′[M′ + B] = ifft(A[M′ + B])
    end

    return hcat(A′...)
end

function fast_fourier_coeffs(f::Function, B::Int)
    S = [S₂(f, B, k) for k in 0:2B-1]
    f̂ = [zeros(Complex128, 2l + 1, 2l + 1) for l in 0:B-1]

    for l in 0:B-1
        for M in -l:l, M′ in -l:l
            for k in 0:2B-1
                β = π * (2k + 1) / (4B)
                f̂[l + 1][M′ + l + 1, M + l + 1] += wigner_d̃(l, M, M′, β) * w(B, k) * S[k + 1][M′ + B, M + B] / sqrt(2 / (2l + 1))
            end
        end
    end

    return f̂
end

fft(f::Function, ::Type{O3}, L::Int) = fast_fourier_coeffs(f, L)


# Algorithm 3.2
# Newton's interpolatory divided difference.

function dividedDiff(x, y)
	n = length(x)
	out = zeros(n, n)
	for i ∈ 1:n
		out[i, 1] = y[i]
	end
	for i ∈ 2:n
		for j ∈ 2:i
			out[i, j] = (out[i, j - 1] - out[i - 1, j - 1]) / (x[i] - x[i - j + 1])
		end
	end
	return out
end

println(dividedDiff([1, 1.3, 1.6, 1.9, 2.2], [-0.76, -0.54, -0.57, -0.57, 0.3]))


module GmshDiscreteModelsTests

using Gridap
using GridapGmsh
using Gridap.Geometry
using Gridap.Helpers
using Test

function check_interpolation(model)
  order = 2
  reffe = ReferenceFE(lagrangian,Float64,order)
  V = FESpace(model,reffe)
  if num_dims(model) == 2
    u = x -> x[1]^2 + 2*x[2]^2
  elseif num_dims(model) == 3
    u = x -> x[1]^2 + 2*x[2]^2 + 3*x[3]^2
  else
    @unreachable
  end
  uh = interpolate(u,V)
  e = u - uh
  Ω = Triangulation(model)
  dΩ = Measure(Ω,2*order)
  @test sum(∫( e*e )dΩ) < 1.0e-9
end

mshfile = joinpath(@__DIR__,"t1.msh")
model = GmshDiscreteModel(mshfile)
test_discrete_model(model)
writevtk(model,"model")
check_interpolation(model)

mshfile = joinpath(@__DIR__,"twoTetraeder.msh")
model = GmshDiscreteModel(mshfile; renumber=true)
test_discrete_model(model)
check_interpolation(model)

mshfile = joinpath(@__DIR__,"..","demo","demo.msh")
model = GmshDiscreteModel(mshfile)
test_discrete_model(model)
check_interpolation(model)

mshfile = joinpath(@__DIR__,"square.msh")
model = GmshDiscreteModel(mshfile)
test_discrete_model(model)
check_interpolation(model)

mshfile = joinpath(@__DIR__,"cube.msh")
model = GmshDiscreteModel(mshfile)
test_discrete_model(model)
check_interpolation(model)

mshfile = joinpath(@__DIR__,"periodic.msh")
model = GmshDiscreteModel(mshfile)
test_discrete_model(model)
u(x) = 2*x[1]
reffe = ReferenceFE(lagrangian,Float64,1)
V = TestFESpace(model,reffe,dirichlet_tags=["L","PL"])
U = TrialFESpace(V,u)
Ω = Triangulation(model)
Γ = BoundaryTriangulation(model,tags="R")
n_Γ = get_normal_vector(Γ)
dΩ = Measure(Ω,2)
dΓ = Measure(Γ,2)
a(u,v) = ∫( ∇(u)⋅∇(v) )dΩ
l(v) = ∫( n_Γ⋅(v*∇(u)) )dΓ
op = AffineFEOperator(a,l,U,V)
uh = solve(op)
#writevtk(Ω,"sol",cellfields=["uh"=>uh,"u"=>u,"e"=>u-uh,"zh"=>zero(U)])
@test sum( ∫(abs2(u - uh))dΩ ) < 1.0e-9

end # module


# Source: https://julialang.org/blog/2016/02/iteration/#writing_multidimensional_algorithms_with_cartesianindex_iterators
# compute the "moving average" over a 3-by-3-by-... block around each element
# Origin code:
function boxcar3(A::AbstractArray)
    out = similar(A)
    R = CartesianIndices(A)
    Ifirst, Ilast = first(R), last(R)
    I1 = oneunit(Ifirst)
    for I in R
        n, s = 0, zero(eltype(out))
        for J in max(Ifirst, I-I1):min(Ilast, I+I1)
            s += A[J]
            n += 1
        end
        out[I] = s/n
    end
    out
end
#--------------------------------------------------------------
"""
    move_average(A, N)

compute the moving average over a (2N+1)-by-(2N+1)-by-... block around each element
"""
function move_average(A::AbstractArray, N::Integer)
    out = similar(A)
    R = CartesianIndices(A)
    Ifirst, Ilast = first(R), last(R)
    I1 = oneunit(Ifirst)
    for I in R
        n, s = 0, zero(eltype(out))
        for J in max(Ifirst, I-N*I1):min(Ilast, I+N*I1)
            s += A[J]
            n += 1
        end
        out[I] = s/n
    end
    out
end


# PAU 2016-Modelo A-Pregunta 3

# Física Enrique García @FiQuiPedia www.fiquipedia.es
# Código por Samuel Gómez @profesor_s www.ingetec.com.es
# Licencia Creative Commons
# http://creativecommons.org/licenses/by/4.0/deed.es_ES

# Una carga puntual, q = 3 μC, se encuentra situada en
# el origen de coordenadas, tal y como se muestra en la figura. Una
# segunda carga q1 = 1 μC se encuentra inicialmente en el punto P1(1,0)
# m y, recorriendo la espiral de la figura, llega al punto P2(0,2) m.
# Determine:
# a. La diferencia de potencial entre los puntos P1 y P2.
# b. El trabajo realizado para llevar la carga q1 del punto P1 al P2.
# Datos: Constante de la Ley de Coulomb; K = 9·109 N m2 C-2

using Distances     # https://github.com/JuliaStats/Distances.jl

const k = 9.00e9            # N m^2 C^-2

type carga
    punto::Vector{Float64}  # (x,y). Metros
    carga::Float16          # Culombios
end

q = carga([0,0], 3e-6)
q1 = carga([1,0], 1e-6)
destino = [0, 2]

# Potencial V
function potencial(q, r)
    return (k*q)/r
end

# Distancia entre dos puntos
function dist(a, b)
    return evaluate(Euclidean(), a, b)
end

### Resolución

# a. La diferencia de potencial entre los puntos P1 y P2.
v1 = potencial(q.carga, dist(q.punto, q1.punto))
v2 = potencial(q.carga, dist(q.punto, destino))
v = v2 - v1
println("a) La diferencia de potencial es ",round(v,2)," voltios")

# b. El trabajo realizado para llevar la carga q1 del punto P1 al P2.
w = -q1.carga * (v2-v1)
println("b) El trabajo realizado es ",round(w, 4), " julios")


function ten_to_two(n)
    b = zeros(Int8,10)
    temp_b = zeros(Int8,10)

    ite=1
    while n > 0
        b[ite] = n % 2
        n = Int((n-b[ite])/2)
        ite = ite +1
    end

    #=
    for i in 1:6
        temp_b[i] = b[6+1-i]
    end
    =#

    return b
end

function two_to_ten(b)
    n = Int8(0)

    for i in 1:length(b)
        n = n + 2^(i-1)*b[i]
    end
    return n
end

# ----------------------
# -- Inverse matrix   --
# ----------------------
function inverse_matrix(eu,ev)
# ----------------------
# -- A = ( eu[1]  ev[1])  --
# --     ( eu[2]  ev[2])  --
# ----------------------
    invA = zeros(2,2)
    detA = eu[1]*ev[2] - eu[2]*ev[1]

    invA[1,1] = (ev[2])/detA
    invA[1,2] = (-ev[1])/detA
    invA[2,1] = (-eu[2])/detA
    invA[2,2] = (eu[1])/detA
    return invA
end

function cal_morton(ulx,uly,lrx,lry,n_div) # 0<=s,tでreturn
    # 左上と右下の座標を整数で
    morton_belong = 0
    morton_num = 0

    upl_morton = zeros(Int8,10)
    lowr_morton = zeros(Int8,10)
    temp_morton = zeros(Int8,10)

    # 左上の点のモートン空間番号
    x_two = ten_to_two(ulx)
    y_two = ten_to_two(uly)

    for k in 1:5
        upl_morton[k*2-1] = x_two[k]
        upl_morton[k*2] = y_two[k]
    end

    # 右下の点のモートン空間番号
    x_two = ten_to_two(lrx)
    y_two = ten_to_two(lry)

    for k in 1:5
        lowr_morton[k*2-1] = x_two[k]
        lowr_morton[k*2] = y_two[k]
    end

    # 排他的論理和 xor
    for i in 1:10
        if upl_morton[i]+lowr_morton[i] == 2
            temp_morton[i] = 0
        else
            temp_morton[i] = upl_morton[i]+lowr_morton[i]
        end
    end

    # 所属空間のチェック

    ite = Int64(0)
    for i in 1:n_div
        s_even = 2*(n_div-(i-1))
        s_odd = 2*(n_div-(i-1)) - 1

        if temp_morton[s_even] == 1 || temp_morton[s_odd] == 1
            morton_belong = ite                             # ルート空間
            break
        end
        #=
        if temp_morton[s_even] == 1 || temp_morton[s_odd] == 1
            morton_belong = 0                             # ルート空間
        elseif temp_morton[4] == 1 || temp_morton[3] == 1
            morton_belong = 1                             # 親空間
        elseif temp_morton[2] == 1 || temp_morton[1] == 1
            morton_belong = 2                             # 子空間
        else
            morton_belong = 3                             # 孫空間
        end
        =#
        ite += 1
    end

    # 所属空間番号
    temp_morton = zeros(Int8,10)
    if morton_belong == 0                             # ルート空間
        morton_num = 0
    elseif morton_belong == 1                         # 親空間
        s_even = 2*(n_div)
        s_odd = 2*(n_div) - 1

        # 4右シフト
        temp_morton[1] = lowr_morton[s_odd]
        temp_morton[2] = lowr_morton[s_even]

        morton_num = two_to_ten(temp_morton)

    elseif morton_belong == 2                         # 子空間

        s_even = 2*(n_div)
        s_odd = 2*(n_div) - 1

        # 2右シフト
        temp_morton[1] = lowr_morton[s_odd-2]
        temp_morton[2] = lowr_morton[s_even-2]
        temp_morton[3] = lowr_morton[s_odd]
        temp_morton[4] = lowr_morton[s_even]

        morton_num = two_to_ten(temp_morton)
    elseif morton_belong == 3                         # 孫空間
        s_even = 2*(n_div)
        s_odd = 2*(n_div) - 1

        # 0右シフト
        temp_morton[1] = lowr_morton[s_odd-4]
        temp_morton[2] = lowr_morton[s_even-4]
        temp_morton[3] = lowr_morton[s_odd-2]
        temp_morton[4] = lowr_morton[s_even-2]
        temp_morton[5] = lowr_morton[s_odd]
        temp_morton[6] = lowr_morton[s_even]

        morton_num = two_to_ten(temp_morton)

    elseif morton_belong == 4                         # 孫空間
        temp_morton = copy(lowr_morton)
        morton_num = two_to_ten(temp_morton)
    end

    return morton_belong,morton_num
end

function liner_morton(n_div)
    # 両方の探査を行うため，煩雑
    s = zeros(Int64,5)

    for j in 1:5
        for i in 1:(n_div+1)
            s[j] += floor(4.0^(i-j))
        end
        s[j] += (j-1)
    end

    search0 = zeros(Int64,4^0,s[1])
    search1 = zeros(Int64,4^1,s[2])
    search2 = zeros(Int64,4^2,s[3])
    search3 = zeros(Int64,4^3,s[4])
    search4 = zeros(Int64,4^4,s[5])

    # ルート空間：s0
    for i in 1:1
        for j in 1:s[1]
            search0[i,j] = j
        end
    end

    # 親空間：s1
    for i in 1:4
        search1[i,1] = 1　　　　　　　　　　　　　　　　# ルート空間
        search1[i,2] = i+1                           # 親空間
        for j in 1:4
            search1[i,j+2] = 6 + 4*(i-1) + (j-1)     # 子空間
        end
        for j in 1:16
            search1[i,j+6] = 22 + 16*(i-1) + (j-1)   # 孫空間
        end

        if n_div ==4
            for j in 1:64
                search1[i,j+22] = 86 + 64*(i-1) + (j-1)   # ひ孫空間
            end
        end
    end

    # 子空間：s2
    for i in 1:16
        search2[i,1] = 1                             # ルート空間
        search2[i,2] = div(i-1,4) + 2                # 親空間
        search2[i,3] = i+5                           # 子空間
        for j in 1:4
            search2[i,j+3] = 22 + 4*(i-1) + (j-1)    # 孫空間
        end
        if n_div ==4
            for j in 1:16
                search2[i,j+7] = 86 + 16*(i-1) + (j-1)    # 孫空間
            end
        end
    end

    # 孫空間
    for i in 1:64
        search3[i,1] = 1                             # ルート空間
        search3[i,2] = div(i-1,16) + 2               # 親空間
        search3[i,3] = div(i-1,4) + 6                # 子空間
        search3[i,4] = 22 + (i-1)                    # 孫空間
        if n_div >=4
            for j in 1:4
                search3[i,j+4] = 86 + 4*(i-1) + (j-1)    # 孫空間
            end
        end
    end

    # ひ孫空間
    if n_div >=4
        for i in 1:256
            search4[i,1] = 1                             # ルート空間
            search4[i,2] = div(i-1,64) + 2               # 親空間
            search4[i,3] = div(i-1,16) + 6                # 子空間
            search4[i,4] = div(i-1,4) + 22                # 孫空間
            search4[i,5] = 86 + (i-1)                    # 孫空間
        end
    end

    return s,search0,search1,search2,search3,search4
end


function Clifford(x::Float64, y::Float64, a::Float64, b::Float64, c::Float64, d::Float64)
    sin(a * y) + c * cos(a * x), sin(b * x) + d * cos(b * y)
end
function De_Jong(x::Float64, y::Float64, a::Float64, b::Float64, c::Float64, d::Float64)
    sin(a * y) - cos(b * x),sin(c * x) - cos(d * y)
end
function Svensson(x::Float64, y::Float64, a::Float64, b::Float64, c::Float64, d::Float64)
    d * sin(a * x) - sin(b * y), c * cos(a * x) + cos(b * y)
end
function Bedhead(x::Float64, y::Float64, a::Float64, b::Float64, c::Float64, d::Float64)
    sin(x*y/b)*y + cos(a*x-y), x + sin(y)/b
end
function Fractal_Dream(x::Float64, y::Float64, a::Float64, b::Float64, c::Float64, d::Float64)
    sin(y*b)+c*sin(x*b), sin(x*a) + d*sin(y*a)
end
function trajectory(fn, x0::Float64, y0::Float64, a::Float64, b::Float64, c::Float64, d::Float64,
        dθ::Float64,  n::Int64)
    x, y, θ = zeros(n), zeros(n), 0.0
    x[1], y[1] = x0, y0
    for i = 1:n
        xd, yd = fn(x[i], y[i], a, b, c, d)
        @inbounds x[i+1], y[i+1] = xd*cos(θ), yd*cos(θ)
        θ += dθ
    end
    x, y
end
#Examples
cliffordExamples = [
    [-1.3, -1.3, -1.8, -1.9],
    [-1.4, 1.6, 1.0, 0.7],
    [1.7, 1.7, 0.6, 1.2],
    [1.7, 0.7, 1.4, 2.0],
    [-1.6, 1.6, 0.7, -1.0],
    [-1.32, -1.65, 0.74, 1.81]]
De_JongExamples = [
    [-1.244, -1.251, -1.815, -1.908],
    [1.4, -2.3, 2.4, -2.1],
    [1.4, 1.56, 1.4, -6.56],
    [2.01, -2.53, 1.61, -0.33],
    [-0.827, -1.637, 1.659, -0.943],
    [-0.709, 1.638, 0.452, 1.740]]
SvenssonExamples = [
    [1.5, -1.8, 1.6, 0.9],
    [-1.78, 1.29, -0.09, -1.18],
    [-0.91, -1.29, -1.97, -1.56],
    [1.4, 1.56, 1.4, -6.56],
    [-0.827, -1.637, 1.659, -0.943],
    [1.7, 1.7, 0.6, 1.2]]
BedheadExamples = [
    [-0.81, -0.92, 0.0, 0.0],
    [-0.64, 0.76, 0.0, 0.0],
    [0.06, 0.98, 0.0, 0.0],
    [-0.67, 0.83, 0.0, 0.0],
    [0.65343, 0.7345345, 0.0, 0.0],
    [1.7, 1.7, 0.0, 0.0]]

attractorExamples = OrderedDict(Clifford => cliffordExamples,
    De_Jong => De_JongExamples, Svensson => SvenssonExamples,
    Bedhead => BedheadExamples)

function pkgsVersion()
    println("Julia == " * string(VERSION))
    for (key, version) ∈ sort(collect(Pkg.installed()))
        try
            isa(eval(Symbol(key)), Module) && println(key * " == " * string(version))
        catch
        end
    end
end


"""
    function seasonal_matrices(fcycle,t,overtones=1)
# Arguments
- `fcycle`: frequency of seasonal cycle
- `t`: time
- `overtones=1`: optional argument for number of overtones
# Output
- `E`: matrix that solves E*parameters= seasonal cycle
- `F=E†`: generalized inverse of `E`
"""
function seasonal_matrices(fcycle,t,overtones=1)

    ω = 2π * fcycle # day^{-1} , seasonal frequency
    nt = length(t)
    freqs = overtones+1

    # times 2 for sines and cosines
    nβ = freqs*2 + 1 # plus one to remove mean

    # would be neat to return whatever type goes in
    E = zeros(Float32,nt,nβ)
    println(size(E))

    # mean value: make it an option
    E[:,1] = ones(Float32,nt,1)
    println(size(E))

    for i = 1:freqs # change to 3 or 4 to get higher harmonics
        println(length(sin.(i*ω*t)))
        E[:,i+1] = sin.(i*ω*t)
        E[:,i+freqs+1] = cos.(i*ω*t)
    end

    F = (E'*E)\E' # least squares estimator

    return E,F
end

"""
    function trend_matrices(t)
# Arguments
- `t`: time
# Output
- `E`: matrix that solves E*parameters= timeseries
- `F=E†`: generalized inverse of `E`
"""
function trend_matrices(t)

    nt = length(t)
    tbar = StatsBase.mean(t)

    # 2 = y-intercept and trend
    nβ = 2

    # would be neat to return whatever type goes in
    E = zeros(Float32,nt,nβ)

    # parameter 1: value at mean t
    # mean value: make it an option
    E[:,1] = ones(Float32,nt,1)

    E[:,2] = t .- tbar

    F = (E'*E)\E' # least squares estimator

    return E,F
end


function find_max_subarray(A, start, finish)
    max_sum = -Inf
    sum = 0
    max_index = -1

    start > finish ? step = -1 : step = 1

    for i in range(start, finish, step = step)
        sum += A[i]
        if sum > max_sum
            max_sum = sum
            max_index = i
        end
    end

    return (max_index, max_sum)
end

function find_max_crossing_subarray(A, low, mid, high)
    max_left, left_sum = find_max_subarray(A, mid, low)
    max_right, right_sum = find_max_subarray(A, mid+1, high)

    return (max_left, max_right, left_sum + right_sum)
end

function find_maximum_subarray(A, low, high)
    # base case only one elem
    if high == low
        return (low, high, A[low])
    end

    mid = floor(Int, (low + high) / 2)

    left_low, left_high, left_sum = find_maximum_subarray(A, low, mid)
    right_low, right_high, right_sum = find_maximum_subarray(A, mid+1, high)
    x_low, x_high, x_sum = find_max_crossing_subarray(A, low, mid, high)

    if left_sum ≥ right_sum && left_sum ≥ x_sum
        return (left_low, left_high, left_sum)
    elseif right_sum ≥ left_sum && right_sum ≥ x_sum
        return (right_low, right_high, right_sum)
    else
        return (x_low, x_high, x_sum)
    end
end

using Test

@testset "max sub array" begin
    input = [13 -3 -25 20 -3 -16 -23 18 20 -7 12 -5 -22 15 -4 7]
    output = (8, 11, 43)
    @test find_maximum_subarray(input, 1, length(input)) == output
end


## Power iteration-based centrality indices ##

"""
    eigenvector(g::Graph[, eps::Real, maxiter::Int])

Compute eigenvector centrality for all nodes in the graph.

The process does not always converge for directed graphs, in which case
the function returns an error. Oftentimes, it is useful to treat the graph
as undirected, `eigenvector(undirected(g))`.

# Arguments
* `g::Graph`: the graph itself.
* `eps::Real=0.0001`: convergence threshold (when score changes for each node
stop exceeding it, the algorithm terminates).
* `maxiter::Int=100`: maximum number of iterations.
"""
function eigenvector(g::Graph, eps::Real=1e-4, maxiter::Int=100)
    @assert(0 < eps <= 0.1, "convergence threshold (eps) should be positive and small")
    n         = nodecount(g)
    score     = fill(0.,     n)
    prevscore = fill(1. / n, n)
    maxdiff   = 1.
    iter      = 0
    @inbounds while maxdiff > eps
        for i = 1:n             # transfer the scores
            score[i] = 0.
            for j = inneighbors(g, i)
                score[i] += prevscore[j]
            end
        end
        λ = 0                   # normalize the scores
        for i = 1:n
            if abs(score[i]) > λ
                λ = score[i]
            end
        end
        if λ == 0
            error("power iteration cannot converge")
            break
        else
            maxdiff = 0.
            for i = 1:n
                score[i] /= λ
                maxdiff = max(maxdiff, abs(score[i] - prevscore[i]))
            end
        end
        iter += 1
        if iter > maxiter && maxdiff > eps
            warn("power iteration did not converge in $maxiter steps, current convergence value is $maxdiff")
            return score
        end
        prevscore, score = score, prevscore
    end
    return prevscore
end

"""
    pagerank(g::Graph[, d::Real, eps::Real])

Compute PageRank scores for all nodes in the graph.

# Arguments
* `g::Graph`: the graph itself.
* `d::Real=0.85`: damping factor (the lesser it is, the bigger proportion
    of scores is redistributed equally between all nodes at each iteration).
* `eps::Real=0.0001`: convergence threshold (when score changes for each node
    stop exceeding it, the algorithm terminates).
"""
function pagerank(g::Graph, d::Real=0.85, eps::Real=1e-4)
    @assert(0 < d < 1,     "damping factor (d) should be between 0 and 1")
    @assert(0 < eps < 0.1, "convergence threshold (eps) should be positive and small")
    n         = nodecount(g)
    score     = fill(0.,     n)
    prevscore = fill(1. / n, n)
    ratio     = [d / outdegree(g, i) for i = 1:n]
    maxdiff   = 1.
    @inbounds while maxdiff > eps
        for i = 1:n                    # transfer the scores
            score[i] = 0.
            for j = inneighbors(g, i)
                score[i] += prevscore[j] * ratio[j]
            end
        end
        leak = (1. - sum(score)) / n    # put the leaked scores back
        maxdiff = 0.
        for i = 1:n
            score[i] += leak
            maxdiff = max(maxdiff, abs(score[i] - prevscore[i]))
        end
        prevscore, score = score, prevscore
    end
    return prevscore
end


function assemble_system!(u, problem::FDGZProblem{TBC, TBC}) where {TBC<:DirichletBC}

    problem.F[1] =problem.ϵ * (u[2] - 2*u[1] + problem.left_bc.u_bc)/problem.Δx^2 +  u[1] * (1-u[1]^2);

    for i in 2:problem.n-1
        problem.F[i]= problem.ϵ * (u[i+1] - 2*u[i] +u[i-1])/problem.Δx^2 +u[i] * (1-u[i]^2);
    end
    problem.F[end] = problem.ϵ * (problem.right_bc.u_bc - 2*u[end] + u[end-1])/problem.Δx^2 +  u[end] * (1-u[end]^2);

    problem
end


function f(x::Real, σ::Real, μ::Real)
    return exp(-0.5((x-μ)/σ)^2) / (σ * sqrt(2π))
end

f(σ::Real, μ::Real) = x -> f(x, σ, μ)

surface(range::AbstractRange; σ=1, μ=0) = f(σ, μ).(range) * f(σ, μ).(range)'


using Test, SpecialFunctions
import TruncatedNormal as TN

@test TN.tnmean(-Inf, Inf) == 0
@test TN.tnmean(0, +Inf) ≈ +√(2/π)
@test TN.tnmean(-Inf, 0) ≈ -√(2/π)
@test TN.tnmean(-Inf, -Inf) == -Inf
@test TN.tnmean(+Inf, +Inf) == +Inf

for x = -10:10
    @test TN.tnmean(x, +Inf) ≈ +√(2/π) / erfcx(+x/√2)
    @test TN.tnmean(-Inf, x) ≈ -√(2/π) / erfcx(-x/√2)
end

for x = exp.(-100:100)
    @test TN.tnmean(-x, x) ≈ 0
    @test TN.tnmean(0, x) ≈ -√(2/π) * expm1(-x^2 / 2) / erf(x / √2)
end

@test TN.tnmean(1e-44, 1e-43) ≈ 5.4999999999999999999999999999999999999999e-44

@test TN.tnmean(-Inf, Inf, 0, 1) == TN.tnmean(-Inf, Inf)
for x = -100:100
    @test TN.tnmean(x, +Inf, 0, 1) ≈ TN.tnmean(x, +Inf)
    @test TN.tnmean(-Inf, x, 0, 1) ≈ TN.tnmean(-Inf, x)
end

@test TN.tnmean(-1e4, 1e4, 0, 1) == TN.tnmean(-1e4, 1e4)

for x = -100:100, y = x + 1 : 100
    @test TN.tnmean(x, y, 0, 1) == TN.tnmean(x, y)
end

@test TN.tnmean(100, 115) ≈ 100.00999800099926070518490239457545847490332879043
@test TN.tnmean(-1e6, -999000) ≈ -999000.00000100100100099899498898098
@test TN.tnmean(+1e6, +Inf) ≈ +1.00000000000099999999999800000e6
@test TN.tnmean(-Inf, -1e6) ≈ -1.00000000000099999999999800000e6

@test TN.tnmean(-1e200, 1e200) ≈ 0
@test TN.tnmean(0, +1e200) ≈ +0.797884560802865355879892119869
@test TN.tnmean(-1e200, 0) ≈ -0.797884560802865355879892119869

@test TN.tnmean(50, 70, -2, 3) ≈ 50.171943499898757645751683644632860837133138152489
@test TN.tnmean(-100.0, 0.0, 0.0, 2.0986317998643735) ≈ -1.6744659119217125058885983754999713622460154892645
@test TN.tnmean(0.0, 0.9, 0.0, 0.07132755843183151) ≈ 0.056911157632522598806524588414964004271754161737065
@test TN.tnmean(-100.0, 100.0, 0.0, 17.185261847875548) ≈ 0
@test TN.tnmean(-100.0, 0.5, 0.0, 0.47383322897860064) ≈ -0.1267981330521791493635176736743283314399
@test TN.tnmean(-100.0, 100.0, 0.0, 17.185261847875548) ≈ 0

for x = 1e1 .^ (-400:100:400)
    @test TN.tnmean(-x, x) == TN.tnmean(-x, x) || isnan(TN.tnmean(-x, x)) && isnan(TN.tnmean(-x, x))
end

for a = exp.(-10:10), b = exp.(-10:10)
    a ≤ b || continue
    @test a ≤ TN.tnmean(a, b) ≤ b
end

# https://github.com/JuliaStats/Distributions.jl/issues/827
@test TN.tnmean(0, 1000, 1000000, 1) ≈ 999.99999899899899900100501101901899090472046236710608108591983