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

using Markdown
using InteractiveUtils

# ╔═╡ c1777303-1af2-4be8-8b26-254b70f6263c
using PlutoTest

# ╔═╡ aa7f1ad0-b0e8-11eb-3904-bb242d044d08
md"""
# [Day 15: Science for Hungry People](https://adventofcode.com/2015/day/15)

Today, you set out on the task of perfecting your milk-dunking cookie recipe. All you have to do is find the right balance of ingredients.

Your recipe leaves room for exactly `100` teaspoons of ingredients. You make a list of the remaining ingredients you could use to finish the recipe (your puzzle input) and their properties per teaspoon:

-   `capacity` (how well it helps the cookie absorb milk)
-   `durability` (how well it keeps the cookie intact when full of milk)
-   `flavor` (how tasty it makes the cookie)
-   `texture` (how it improves the feel of the cookie)
-   `calories` (how many calories it adds to the cookie)

You can only measure ingredients in whole-teaspoon amounts accurately, and you have to be accurate so you can reproduce your results in the future. The total score of a cookie can be found by adding up each of the properties (negative totals become `0`) and then multiplying together everything except calories.

For instance, suppose you have these two ingredients:

```
Butterscotch: capacity -1, durability -2, flavor 6, texture 3, calories 8
Cinnamon: capacity 2, durability 3, flavor -2, texture -1, calories 3

```

Then, choosing to use `44` teaspoons of butterscotch and `56` teaspoons of cinnamon (because the amounts of each ingredient must add up to `100`) would result in a cookie with the following properties:

-   A `capacity` of `44*-1 + 56*2 = 68`
-   A `durability` of `44*-2 + 56*3 = 80`
-   A `flavor` of `44*6 + 56*-2 = 152`
-   A `texture` of `44*3 + 56*-1 = 76`

Multiplying these together (`68 * 80 * 152 * 76`, ignoring `calories` for now) results in a total score of `62842880`, which happens to be the best score possible given these ingredients. If any properties had produced a negative total, it would have instead become zero, causing the whole score to multiply to zero.

Given the ingredients in your kitchen and their properties, **what is the total score of the highest-scoring cookie you can make?**
"""

# ╔═╡ 861a2d99-e0a6-44a9-9b18-ca1b25d1eabd
puzzle_input = "Sprinkles: capacity 5, durability -1, flavor 0, texture 0, calories 5
PeanutButter: capacity -1, durability 3, flavor 0, texture 0, calories 1
Frosting: capacity 0, durability -1, flavor 4, texture 0, calories 6
Sugar: capacity -1, durability 0, flavor 0, texture 2, calories 8"

# ╔═╡ ad735981-4fdb-4527-9869-a6a748e2e5dd
example_input = """
Butterscotch: capacity -1, durability -2, flavor 6, texture 3, calories 8
Cinnamon: capacity 2, durability 3, flavor -2, texture -1, calories 3
""" |> strip

# ╔═╡ 34edc8a5-edc7-4a8d-8bbe-c9d354d8561d
function parseline(line)
	m = match(r"(\w+): capacity (-?\d+), durability (-?\d+), flavor (-?\d+), texture (-?\d+), calories (-?\d+)", line)
	m
	(name=m[1], capacity=parse(Int, m[2]), durability=parse(Int, m[3]), flavor=parse(Int, m[4]), texture=parse(Int, m[5]), calories=parse(Int, m[6]))
end

# ╔═╡ 4122ea48-c078-4b72-81ca-0f6163d7b474
example_ingredients = map(parseline, split(example_input, "\n"))

# ╔═╡ cf9d9179-cbd0-4f1d-aee7-5eb7eaed4559
function cookiescore(ingredients, ratios)
	#if sum(ratios) != 100
#		return -1
#end
	scores = [
		sum(map(((idx, ingredient),)-> ingredient.capacity * ratios[idx], enumerate(ingredients))),
		sum(map(((idx, ingredient),)-> ingredient.durability * ratios[idx], enumerate(ingredients))),
		sum(map(((idx, ingredient),)-> ingredient.flavor * ratios[idx], enumerate(ingredients))),
		sum(map(((idx, ingredient),)-> ingredient.texture * ratios[idx], enumerate(ingredients))),
	]
	prod(map(score -> max(0, score), scores))
end

# ╔═╡ 782f3c0c-d95f-402f-8e1a-ff640b885c2b
function cookiecalories(ingredients, ratios)
	calories = 0
	for (idx, ratio) in enumerate(ratios)
		calories += ingredients[idx][6] * ratio
	end
	calories
end

# ╔═╡ b9a51427-a73f-4b53-aac0-35c49846ec3e
@assert cookiescore(example_ingredients, (44, 56)) == 62842880

# ╔═╡ 65abcb4d-b961-4ad7-b78a-1d40aa64c867
cookiescore(example_ingredients, (44, 56))

# ╔═╡ 0e81de2d-45d0-4627-8479-9dc92ce4433e
function bestcookiescore1(ingredients)
	best = 0
	for a in 1:100
		for b in 1:100-a
			for c in 1:100-(a+b)
				d = 100 - a - b -c
				score = cookiescore(ingredients, (a,b,c,d))
				if score > best
					best = score
				end
			end
		end
	end
	best
end

# ╔═╡ 6e394cd1-dbdc-4540-ab6d-39bdf0299a9c
@assert bestcookiescore1(example_ingredients) == 62842880

# ╔═╡ c3d471b9-f0b5-4e29-937e-dcb917cf2a9c
bestcookiescore1(example_ingredients)

# ╔═╡ 59938b46-ff7d-441f-95ba-1a49fcaeefe7
puzzle_ingredients = map(parseline, split(puzzle_input, "\n"))

# ╔═╡ 0d4650b4-1035-43f4-b423-8824cb3436d2
part1 = bestcookiescore1(puzzle_ingredients)

# ╔═╡ fb3a890b-211c-4fa5-a85e-472183509fa8
@test part1 == 13882464

# ╔═╡ 117c9ac8-6f1e-4dc3-9b74-bb2cabe9b171
md"Your puzzle answer was `13882464`."

# ╔═╡ 9e397f78-a72b-406b-905c-dc1f79e852d0
md"""
# Part Two

Your cookie recipe becomes wildly popular! Someone asks if you can make another recipe that has exactly `500` calories per cookie (so they can use it as a meal replacement). Keep the rest of your award-winning process the same (100 teaspoons, same ingredients, same scoring system).

For example, given the ingredients above, if you had instead selected `40` teaspoons of butterscotch and `60` teaspoons of cinnamon (which still adds to `100`), the total calorie count would be `40*8 + 60*3 = 500`. The total score would go down, though: only `57600000`, the best you can do in such trying circumstances.

Given the ingredients in your kitchen and their properties, **what is the total score of the highest-scoring cookie you can make with a calorie total of `500`**?

"""

# ╔═╡ 3ae1cb15-f704-4559-9043-186c2874a035
function bestcookiescore2(ingredients)
	best = 0
	for a in 1:100
		for b in 1:100-a
			for c in 1:100-(a+b)
				d = 100 - a - b -c
				score = cookiescore(ingredients, (a,b,c,d))
				if score > best && cookiecalories(ingredients, (a,b,c,d)) == 500
					best = score
				end
			end
		end
	end
	best
end

# ╔═╡ a10ef2a7-c9d9-41f0-9cfc-6921a390306f
part2 = bestcookiescore2(puzzle_ingredients)

# ╔═╡ 9274f947-ede1-446e-9845-9e776b2bf42c
@test part2 == 11171160

# ╔═╡ da313ad1-afdf-45cd-9601-5e6dec1f92bc
md"Your puzzle answer was `11171160`."

# ╔═╡ Cell order:
# ╠═c1777303-1af2-4be8-8b26-254b70f6263c
# ╟─aa7f1ad0-b0e8-11eb-3904-bb242d044d08
# ╟─861a2d99-e0a6-44a9-9b18-ca1b25d1eabd
# ╠═ad735981-4fdb-4527-9869-a6a748e2e5dd
# ╠═34edc8a5-edc7-4a8d-8bbe-c9d354d8561d
# ╠═4122ea48-c078-4b72-81ca-0f6163d7b474
# ╠═cf9d9179-cbd0-4f1d-aee7-5eb7eaed4559
# ╠═782f3c0c-d95f-402f-8e1a-ff640b885c2b
# ╠═b9a51427-a73f-4b53-aac0-35c49846ec3e
# ╠═65abcb4d-b961-4ad7-b78a-1d40aa64c867
# ╠═0e81de2d-45d0-4627-8479-9dc92ce4433e
# ╠═6e394cd1-dbdc-4540-ab6d-39bdf0299a9c
# ╠═c3d471b9-f0b5-4e29-937e-dcb917cf2a9c
# ╠═59938b46-ff7d-441f-95ba-1a49fcaeefe7
# ╠═0d4650b4-1035-43f4-b423-8824cb3436d2
# ╠═fb3a890b-211c-4fa5-a85e-472183509fa8
# ╟─117c9ac8-6f1e-4dc3-9b74-bb2cabe9b171
# ╟─9e397f78-a72b-406b-905c-dc1f79e852d0
# ╠═3ae1cb15-f704-4559-9043-186c2874a035
# ╠═a10ef2a7-c9d9-41f0-9cfc-6921a390306f
# ╠═9274f947-ede1-446e-9845-9e776b2bf42c
# ╟─da313ad1-afdf-45cd-9601-5e6dec1f92bc


"""
    translate!(camera::CameraModel, 𝐭::AbstractArray)

Translate the camera optical center by a vector 𝐭.
"""
function translate!(camera::CameraModel, 𝐭::AbstractArray)
    camera.𝐜 = camera.𝐜 + 𝐭
    camera
end

"""
    relocate!(camera::CameraModel, 𝐭::AbstractArray)

Rotates a camera around its optical center by a rotation matrix 𝐑 and
then translates the optical center by a vector 𝐭.
"""
function relocate!(camera::CameraModel, 𝐑::AbstractArray, 𝐭::AbstractArray)
    camera.𝐞₁ = 𝐑*camera.𝐞₁
    camera.𝐞₂ = 𝐑*camera.𝐞₂
    camera.𝐞₃ = 𝐑*camera.𝐞₃
    camera.𝐜 = camera.𝐜 + 𝐭
    camera
end


# =================
# Dependencies
# =================

using ReachabilityAnalysis
using CarlemanLinearization
using Plots
using LaTeXStrings
using Plots.PlotMeasures
using LinearAlgebra
using SparseArrays
using DifferentialEquations # to plot trajectories on top

using CarlemanLinearization: _error_bound_specabs_R

include("../utils.jl")

# =================
# Model definition
# =================

# model params
r_vac = 0.19
r_tra = 0.13
T_lat = 5.2
T_inf = 2.3

# Model params

Λ = 0.0
P = 1e7 # total

@show Ptot = P
@show PS0 = Ptot * 0.6
@show PI0 = Ptot * 0.03
@show PE0 = Ptot - PS0 - PI0

function seir_model_carlin()

    F1 = zeros(3, 3)
    F1[1, 1] = -Λ/P -r_vac
    F1[2, 2] = -Λ/P -1/T_lat
    F1[3, 2] = 1/T_lat
    F1[3, 3] = -Λ/P -1/T_inf

    F2 = zeros(3, 9) # [x, x⊗x]
    F2[1, 3] = -r_tra/P
    F2[2, 3] = r_tra/P;

    return F1, F2
end

# =================
# Solution method
# =================

## Solution with CARLIN

function _solve_seir_carlin(; N=4, T=30.0, δ=0.1, radius0=0, bloat=false, resets=nothing)

    x0c = [PS0, PE0, PI0] # initial condition

    F1, F2 = seir_model_carlin()
    R, Re_λ1 = _error_bound_specabs_R(x0c, F1, F2; check=true)

    n = 3
    dirs = _template(n=n, N=N)
    alg = LGG09(δ=δ, template=dirs)

    if radius0 == 0
        X0 = convert(Hyperrectangle, Singleton(x0c))
    else
        X0 = Hyperrectangle(x0c, radius0)
    end

    if isnothing(resets)
        @time sol = _solve_CARLIN(X0, F1, F2; alg=alg, N=N, T=T, bloat=bloat);
    else
        @time sol = _solve_CARLIN_resets(X0, F1, F2; resets=resets, alg=alg, N=N, T=T, bloat=bloat);
    end
    return sol
end

## Solution with TMJETS

@taylorize function seir!(dx, x, p, t)
    P_S, P_E, P_I = x

    aux1 = Λ / P

    dx[1] = -aux1 * P_S - r_vac * P_S + Λ - r_tra * P_S * P_I / P
    dx[2] = -aux1 * P_E - P_E / T_lat + r_tra * P_S * P_I / P
    dx[3] = -aux1 * P_I + P_E / T_lat - P_I / T_inf
end

function _solve_seir_carlin_TM(; T=30.0, radius0=0, trajectories=-1)
    x0c = [PS0, PE0, PI0] # initial condition
    if radius0 == 0
        X0 = convert(Hyperrectangle, Singleton(x0c))
    else
        X0 = Hyperrectangle(x0c, radius0)
    end
    prob = @ivp(x' = seir!(x), x(0) ∈ X0, dim=3)

    if trajectories == -1
        @time sol_tm = solve(prob, T=T, alg=TMJets())
    else
        sol_tm = solve(prob, T=T, alg=TMJets(), ensemble=true, trajectories=trajectories)
    end
    return sol_tm
end

# ===============
# Results
# ===============

# parameters
Tmax = 30.0
rr0  = fill(1.0 * 1e5, 3)

# taylor models solution
_solve_seir_carlin_TM(T=Tmax, radius0=rr0) # warm-up
time_TM = @elapsed _solve_seir_carlin_TM(T=Tmax, radius0=rr0)

# no error bounds, N = 2
_solve_seir_carlin(N=2, T=Tmax, δ=0.1, radius0=rr0, bloat=false) # warm-up
time_NoError_N2 = @elapsed _solve_seir_carlin(N=2, T=Tmax, δ=0.1, radius0=rr0, bloat=false)

# including error bounds, N = 5
_solve_seir_carlin(N=5, T=Tmax, δ=0.1, radius0=rr0, bloat=true, resets=[4.0]) # warm-up
time_Error_N5 = @elapsed _solve_seir_carlin(N=5, T=Tmax, δ=0.1, radius0=rr0, bloat=true, resets=[4.0])

# save times
print(io, "SEIR, Carleman, no error bound, N=2, $(time_NoError_N2)\n")
print(io, "SEIR, Carleman, including error bound, N=5, $(time_Error_N5)\n")
print(io, "SEIR, TMJets, -, -, $(time_TM)\n")

# ===============
# Figures
# ===============

# figure with NO error bounds
function figure_seir()

    Tmax = 30.0
    rr0  = fill(1.0 * 1e5, 3)
    solN2 = _solve_seir_carlin(N=2, T=Tmax, δ=0.1, radius0=rr0, bloat=false);

    # taylor models solution
    sol_tm = _solve_seir_carlin_TM(T=Tmax, radius0=rr0)

    # trajectories
    sol_tm_traj = _solve_seir_carlin_TM(T=Tmax, radius0=rr0, trajectories=5)

    fig = plot(legend=:topright, xlab=L"\textrm{Time (days)}", ylab=L"\textrm{Individuals } (\times 10^6)",
               legendfontsize=25,
               tickfont=font(25, "Times"),
               guidefontsize=25,
               xguidefont=font(35, "Times"),
               yguidefont=font(35, "Times"),
               xtick = ([0, 10, 20, 30], [L"0", L"10", L"20", L"30"]),
               ytick = ([0, 1e6, 2e6, 3e6, 4e6, 5e6, 6e6],
                        [L"0", L"1", L"2", L"3", L"4", L"5", L"6"]),
               xlims=(0, 30),
               ylims=(0, 6e6),
               bottom_margin=10mm, left_margin=6mm, right_margin=6mm, top_margin=3mm, size=(900, 600))

    # carleman linearization solution
    plot!(fig, solN2, vars=(0, 1), lab=L"S~(N = 2)", alpha=1., c=:blue, lc=:blue)
    plot!(fig, sol_tm, vars=(0, 1), lab=L"S~(TM)", alpha=1., c=:aquamarine, lc=:aquamarine)

    plot!(fig, solN2, vars=(0, 2), lab=L"E~(N=2)", alpha=1., c=:red, lc=:red)
    plot!(fig, sol_tm, vars=(0, 2), lab=L"E~(TM)", alpha=1., c=:darksalmon, lc=:darksalmon)

    plot!(fig, solN2, vars=(0, 3), lab=L"I~(N=2)", alpha=1., c=:orange, lc=:orange)
    plot!(fig, sol_tm, vars=(0, 3), lab=L"I~(TM)", alpha=1., c=:darkseagreen, lc=:darkseagreen)

    # lens
    lens!(fig, [18, 22], [7.5e4, 1.75e5], inset = (1, bbox(0.32, 0.34, 0.28, 0.24)),
           tickfont=font(14, "Times"),
           subplot=2,
           xticks=xticklatex([18., 20., 22.], 2),
           yticks=([7.5e4, 1.25e5, 1.75e5], [L"0.075", L"0.125", L"0.175"]))

    return fig
end

fig = figure_seir()
savefig(fig, joinpath(TARGET_FOLDER, "figure_seir_noerror.pdf"))

# figure with error bounds
function figure_seir_errors()

    Tmax = 30.0
    rr0  = fill(1.0 * 1e5, 3)

    N = 5
    solN5 = _solve_seir_carlin(N=N, T=Tmax, δ=0.1, radius0=rr0, bloat=false);

    solN5bloat = _solve_seir_carlin(N=N, T=Tmax, δ=0.1, radius0=rr0, bloat=true, resets=[4.0]);

    fig = plot(legend=:topright, xlab=L"\textrm{Time (days)}", ylab=L"\textrm{Individuals } (\times 10^6)",
               legendfontsize=25,
               tickfont=font(25, "Times"),
               guidefontsize=25,
               xguidefont=font(35, "Times"),
               yguidefont=font(35, "Times"),
               xtick = ([0, 10, 20, 30], [L"0", L"10", L"20", L"30"]),
               ytick = ([0, 1e6, 2e6, 3e6, 4e6, 5e6, 6e6],
                        [L"0", L"1", L"2", L"3", L"4", L"5", L"6"]),
               xlims=(0, 30),
               ylims=(0, 6e6),
               bottom_margin=10mm, left_margin=6mm, right_margin=6mm, top_margin=3mm, size=(900, 600))

    # carleman linearization solution

    plot!(fig, solN5bloat, vars=(0, 1), lab=L"err(S)", alpha=1., c=:blue, lc=:blue)

    plot!(fig, solN5bloat, vars=(0, 2), lab=L"err(E)", alpha=1., c=:red, lc=:red)

    plot!(fig, solN5bloat, vars=(0, 3), lab=L"err(I)", alpha=1., c=:orange, lc=:orange)

    plot!(fig, solN5, vars=(0, 1), lab=L"S~(N = %$N)", alpha=1., c=:aquamarine, lc=:aquamarine)
    plot!(fig, solN5, vars=(0, 2), lab=L"E~(N = %$N)", alpha=1., c=:darksalmon, lc=:darksalmon)
    plot!(fig, solN5, vars=(0, 3), lab=L"I~(N = %$N)", alpha=1., c=:darkseagreen, lc=:darkseagreen)

    ylims!(0, 6e6)

    a = [4.0, 0.0]; b = [4.0, 1.3e6]
    plot!(LineSegment(a, b), markershape=:none, seriestype=:shape, alpha=1., c=:black, ls=:dash, lw=2.0)

    # lens
    lens!(fig, [3.5, 4.5], [0.6e6, 1.3e6], inset = (1, bbox(0.32, 0.34, 0.28, 0.24)),
           tickfont=font(14, "Times"),
           subplot=2,
           xticks=xticklatex([3.5, 4.0, 4.5], 2),
           yticks=([0.7e6, 1.0e6, 1.3e6], [L"0.7", L"1.0", L"1.3"]))

    return fig
end

fig = figure_seir_errors()
savefig(fig, joinpath(TARGET_FOLDER, "figure_seir_error.pdf"))


using Plots
using Convex
using Mosek
using SCS
using DelimitedFiles

data = readdlm(joinpath(dirname(@__FILE__),"data","snp500.txt"))
n = size(data)[1]

#p = plot(1:length(data), data)

N = length(data)
H =  spdiagm((ones(N-2), -2*ones(N-2), ones(N-2)), (0,1,2))

x = Variable(N)

λ = 50
problem = minimize(0.5*sumsquares(x - data) + λ*norm(H*x, 1))

# Solve the problem by calling solve!
solve!(problem, MosekSolver())

# Check the status of the problem
problem.status # :Optimal, :Infeasible, :Unbounded etc.

# Get the optimal value
problem.optval

plot(data)
plot!(evaluate(x))


numterms = 200
C4 = Array{BigInt}(numterms+1) # memoization dictionary
fill!(C4,-1) # -1 implies not yet computed
C4[1] = 1 # Base case for n=0, C[n+1] provides nth term
# memoize partial sums as well
sum2store = similar(C4)
sum3store = similar(C4)
fill!(sum2store, -1)
fill!(sum3store, -1)
function catalan4(n,C)
    C[n+1] == -1 || return C[n+1]
    sum1 = convert(BigInt,0)
    for i in 1:n
        if sum2store[n-i+1] == -1
            sum2 = convert(BigInt,0)
            for j in 1:(n-i+1)
                if sum3store[n-i-j+2] == -1
                    sum3 = convert(BigInt,0)
                    for k in 1:(n-i-j+2)
                        sum3+= catalan4(k-1,C)*catalan4(n-i-j-k+2,C)
                    end
                    sum3store[n-i-j+2] = sum3
                end
                sum2 += catalan4(j-1,C)*sum3store[n-i-j+2]
            end
            sum2store[n-i+1] = sum2
        end
        sum1 += catalan4(i-1,C)*sum2store[n-i+1]
    end
    C[n+1] = sum1
    return sum1
end
for i in 1:numterms
    println(i,"\t",catalan4(200,C4))
end


module GeneralizedSampling

using Compat
import Compat: view

using Base.Cartesian

using NFFT

using IntervalWavelets
import IntervalWavelets: isuniform, van_moment

import VoronoiCells: density, voronoiarea
import Wavelets: wavelet, WT
import ArrayViews: flatten_view, reshape_view
import Base: A_mul_B!, Ac_mul_B!

export
	# Types
	CoB,
	Freq2Wave,
	Freq2BoundaryWave,
	Freq2NoBoundaryWave,

	# Fourier transforms
	FourScalingFunc,
	FourHaarScaling,
	FourDaubScaling,

	# Linear equation solvers
	cgnr,

	# Special CoB functions
	collect,

	# misc
	had!,
	hadc!,
	yphad!,
	isuniform,
	grid,
	weights,
	density,
	van_moment,
	isdaubechies,
	ishaar,
	hasboundary,
	dim,
	split,
	wscale,
	wsize,
	frac,
	frac!

const sqrt2 = sqrt(2)
const SMALL_EPS = eps()
const LARGE_EPS= sqrt(eps())
const ComplexOne = one(Complex{Float64})
const ComplexZero = zero(Complex{Float64})
const twoπ = 2.0*pi

include("Freq2Wave/Types.jl")
include("Misc.jl")
include("FourierTransforms.jl")
include("NFFT.jl")
include("CGNR.jl")

end # module


using KitBase, FluxReconstruction, OrdinaryDiffEq

begin
    x0 = -1
    x1 = 1
    ncell = 100
    nface = ncell + 1
    dx = (x1 - x0) / ncell
    deg = 2 # polynomial degree
    nsp = deg + 1
    cfl = 0.1
    dt = cfl * dx
    t = 0.0
    a = 1.0
end

pspace = FRPSpace1D(x0, x1, ncell, deg)

begin
    xFace = collect(x0:dx:x1)
    xGauss = legendre_point(deg)
    xsp = global_sp(xFace, xGauss)
    ll = lagrange_point(xGauss, -1.0)
    lr = lagrange_point(xGauss, 1.0)
    lpdm = ∂lagrange(xGauss)
end

u = zeros(ncell, nsp)
for i = 1:ncell, ppp1 = 1:nsp
    u[i, ppp1] = exp(-20.0 * xsp[i, ppp1]^2)
end
uold = deepcopy(u)
f = zeros(ncell, nsp)
u_face = zeros(ncell, 2)
f_face = zeros(ncell, 2)
au = zeros(nface);
f_interaction = zeros(nface)

e2f = zeros(Int, ncell, 2)
for i = 1:ncell
    if i == 1
        e2f[i, 2] = nface
        e2f[i, 1] = i + 1
    elseif i == ncell
        e2f[i, 2] = i
        e2f[i, 1] = 1
    else
        e2f[i, 2] = i
        e2f[i, 1] = i + 1
    end
end
f2e = zeros(Int, nface, 2)
for i = 1:nface
    if i == 1
        f2e[i, 1] = i
        f2e[i, 2] = ncell
    elseif i == nface
        f2e[i, 1] = 1
        f2e[i, 2] = i - 1
    else
        f2e[i, 1] = i
        f2e[i, 2] = i - 1
    end
end

function mol!(du, u, p, t) # method of lines
    xFace, e2f, f2e, a, deg, ll, lr, lpdm = p

    ncell = size(u, 1)
    nsp = size(u, 2)

    f = zeros(ncell, nsp)
    for i = 1:ncell, j = 1:nsp
        J = (xFace[i+1] - xFace[i]) / 2.0
        f[i, j] = advection_flux(u[i, j], a) / J
    end

    u_face = zeros(ncell, nsp)
    f_face = zeros(ncell, nsp)

    u_face[:, 1] .= u * lr
    f_face[:, 1] .= f * lr
    u_face[:, 2] .= u * ll
    f_face[:, 2] .= f * ll
    #=for i = 1:ncell, j = 1:nsp
        # right face of element i
        u_face[i, 1] += u[i, j] * lr[j]
        f_face[i, 1] += f[i, j] * lr[j]

        # left face of element i
        u_face[i, 2] += u[i, j] * ll[j]
        f_face[i, 2] += f[i, j] * ll[j]
    end=#

    au = zeros(nface)
    for i = 1:nface
        au[i] =
            (f_face[f2e[i, 1], 2] - f_face[f2e[i, 2], 1]) /
            (u_face[f2e[i, 1], 2] - u_face[f2e[i, 2], 1] + 1e-6)
    end

    f_interaction = zeros(nface)
    for i = 1:nface
        f_interaction[i] = (
            0.5 * (f_face[f2e[i, 2], 1] + f_face[f2e[i, 1], 2]) -
            0.5 * abs(au[i]) * (u_face[f2e[i, 1], 2] - u_face[f2e[i, 2], 1])
        )
    end

    dgl, dgr = ∂radau(deg, xGauss)

    rhs1 = zeros(ncell, nsp)
    for i = 1:ncell, ppp1 = 1:nsp, k = 1:nsp
        rhs1[i, ppp1] += f[i, k] * lpdm[ppp1, k]
    end

    for i = 1:ncell, ppp1 = 1:nsp
        du[i, ppp1] =
            -(
                rhs1[i, ppp1] +
                (f_interaction[e2f[i, 2]] - f_face[i, 2]) * dgl[ppp1] +
                (f_interaction[e2f[i, 1]] - f_face[i, 1]) * dgr[ppp1]
            )
    end
end

tspan = (0.0, 2.0)
p = (xFace, e2f, f2e, a, deg, ll, lr, lpdm)
prob = ODEProblem(mol!, u, tspan, p)
sol = solve(prob, Tsit5(), reltol = 1e-8, abstol = 1e-8, progress = true)

using Plots
plot(xsp[:, 2], sol.u[end][:, 2], label = "t=2")
scatter!(xsp[:, 2], exp.(-20 .* xsp[:, 2] .^ 2), label = "t=0")


using RobustAndOptimalControl, ControlSystems

G = tf(200, [10, 1])*tf(1, [0.05, 1])^2     |> ss
Gd = tf(100, [10, 1])                       |> ss
W1 = tf([1, 2], [1, 1e-6])                  |> ss
Gs = G*W1
Ks, γ, info = glover_mcfarlane(Gs, 1.1)
@test info.γmin ≈ 2.34 atol=0.005

if isinteractive()
    bodeplot([G, Gs, Gs*Ks]) |> display

    plot( step(Gd*feedback(1, G*W1), 3))
    plot!(step(Gd*feedback(1, G*W1*Ks), 3)) |> display

    nyquistplot([G*W1, G*W1*Ks], ylims=(-2,1), xlims=(-2, 1), Ms_circles=1.5) |> display
end

## Discrete case
disc = G->c2d(G, 0.01)
G = tf(200, [10, 1])*tf(1, [0.05, 1])^2     |> ss |> disc
Gd = tf(100, [10, 1])                       |> ss |> disc
W1 = tf([1, 2], [1, 1e-6])                  |> ss |> disc
K, γ, info = glover_mcfarlane(G, 1.1; W1)
@test info.γmin ≈ 2.4086 atol=0.005

Gcl0 = let
    tempA = [0.6542 -0.2067 -1.6744 0.2619 -0.3391 -1.0397 -6.283 -0.6716; 0.1309 0.9823 -0.1434 0.0224 -0.029 -0.089 -0.538 -0.0575; 0.0014 0.0199 0.999 0.0002 -0.0002 -0.0006 -0.0037 -0.0004; 0.0 0.0 -0.1271 1.0 -0.0259 -0.0794 -0.4799 -0.0513; 0.0 0.0 -1.8079 0.0 0.315 -1.2464 -6.1494 -0.4097; 0.0 0.0 0.3374 0.0 0.1019 0.8933 -1.0187 -0.0351; 0.0 0.0 0.1454 0.0 0.0012 0.0193 0.8499 -0.0002; 0.0 0.0 0.0181 0.0 -0.0259 -0.0794 -0.6251 0.9487]
    tempB = [-0.1065 0.1309; -0.0091 0.0112; -0.0001 0.0001; -0.0081 0.0; -0.1157 0.0; 0.0216 0.0; 0.0093 0.0; 0.0012 0.0]
    tempC = [0.0 0.0 15.625 0.0 0.0 0.0 0.0 0.0; 0.0 0.0 -12.7105 2.0 -2.5903 -7.941 -47.9883 -5.1294]
    tempD = [1.0 0.0; -0.8135 0.0]
    ss(tempA, tempB, tempC, tempD, 0.01)
end

@test hinfnorm2(Gcl0 - info.Gcl)[1] < 0.02

if isinteractive()
    bodeplot([G, G*K]) |> display
    plot( step(Gd*feedback(1, info.Gs), 3))
    plot!(step(Gd*feedback(1, G*K), 3)) |> display
    nyquistplot([info.Gs, G*K], ylims=(-2,1), xlims=(-2, 1), Ms_circles=1.5) |> display
end

Ko = observer_controller(info)
@test nugap(G*K, info.Gs*Ko)[1] < 1e-6 # The loop transfer is used since the observer controller is for the scaled plant whereas K is for the unscaled plant.
# bodeplot([G*K, info.Gs*Ko])

## Test the observer predictor
Gs = info.Gs
Ph = observer_predictor(info)
u = randn(Gs.nu, 100)
res = lsim(Gs, u)
uy = [u; res.y]
reso = lsim(Ph, uy)

# plot(res)
# plot!(reso) |> display
# plot(res.x')
# plot!(reso.x') |> display

@test res.y ≈ reso.y
@test res.x ≈ reso.x

## Strictly proper case
Ksp, γsp, infosp = glover_mcfarlane(G, 1.1; W1, strictly_proper=true)
@test infosp.γmin > 2.4086
@test iszero(Ksp.D)

Ko = observer_controller(infosp)
@test nugap(G*Ksp, infosp.Gs*Ko)[1] < 1e-6
# bodeplot([G*Ksp, infosp.Gs*Ko])

# Can be manually verified to be "slightly worse" than the controller without strictly proper restriction.
# if isinteractive()
#     plot( step(Gd*feedback(1, info.Gs), 3))
#     plot!(step(Gd*feedback(1, G*K), 3))
#     plot!(step(Gd*feedback(1, G*Ksp), 3)) |> display
#     nyquistplot([info.Gs, G*K, G*Ksp], ylims=(-2,1), xlims=(-2, 1), Ms_circles=1.5) |> display
# end

Gs = info.Gs
Ph = observer_predictor(infosp)
u = randn(Gs.nu, 100)
res = lsim(Gs, u)
uy = [u; res.y]
reso = lsim(Ph, uy)
@test res.y ≈ reso.y
@test res.x ≈ reso.x

##
W1h = hanus(W1)
@test W1h.nu == 2W1.nu

##

P = tf([1, 5], [1, 2, 10])
W1 = tf(1,[1, 0])

K,γ,info = glover_mcfarlane(ss(P); W1)

Gcl0 = let
    tempA = [-2.0 -2.5 2.0 0.0 0.0 0.0; 4.0 0.0 0.0 0.0 0.0 0.0; 0.0 0.0 0.0 -0.7189 0.0453 -2.9175; 0.0493 0.0616 0.0 -2.0493 -2.5616 2.0; 0.399 0.4988 0.0 3.601 -0.4988 0.0; 0.5 0.625 0.0 -1.2189 -0.5797 -2.9175]
    tempB = [0.0 2.0; 0.0 0.0; 0.0 0.0; 0.0985 0.0; 0.7981 0.0; 1.0 0.0]
    tempC = [0.5 0.625 0.0 0.0 0.0 0.0; 0.0 0.0 1.0 0.0 0.0 0.0]
    tempD = [1.0 0.0; 0.0 0.0]
    ss(tempA, tempB, tempC, tempD)
end

K0 = let
    tempA = [0.0 -0.7189 0.0453 -2.9175; 0.0 -2.0493 -2.5616 2.0; 0.0 3.601 -0.4988 0.0; 0.0 -1.2189 -0.5797 -2.9175]
    tempB = [0.0; 0.0985; 0.7981; 1.0;;]
    tempC = [1.0 0.0 0.0 0.0]
    tempD = [0.0;;]
    ss(tempA, tempB, tempC, tempD)
end

Gcl = extended_gangoffour(P, K)
@test hinfnorm2(Gcl-Gcl0)[1] < 1e-4
@test hinfnorm2(info.Gcl-Gcl0)[1] < 1e-4
@test nugap(K, -K0)[1] < 1e-4
@test info.margin ≈ 0.6325 atol=1e-3

@test ncfmargin(P, K)[1] ≈ 0.4472 atol=1e-3

p = ss(tf(4, [1, -0.001]))
cL = 1
cH = 10
@test ncfmargin(p,cL)[1] ≈ 0.7069 atol=1e-3

P = ss(tf(1, [1,1]))
K = ss(1)
S, PS, CS, T = RobustAndOptimalControl.gangoffour2(P,K)
# gangoffourplot(P, K)
# bodeplot!(extended_gangoffour(P, K), plotphase=false)

@test all(isstable.((S, PS, CS, T)))
gof = extended_gangoffour(P, K)
isstable(gof)
@test nugap(S, gof[1,1])[1] < 1e-6
@test nugap(PS, gof[1,2])[1] < 1e-6
@test nugap(CS, -gof[2,1])[1] < 1e-6 # NOTE: slightly disturbing to have - here
@test nugap(T, -gof[2,2])[1] < 1e-6

## 2 DOF GMcF

P = tf([1, 5], [1, 2, 10])
W1 = tf(1,[1, 0]) |> ss

Tref = tf(1, [1, 1]) |> ss

K1dof, γ1, info1 = glover_mcfarlane(ss(P), 1.1; W1)
K2dof, γ2, info2 = RobustAndOptimalControl.glover_mcfarlane_2dof(ss(P), Tref, 1.1, 1.1; W1)

G1 = feedback(P*K1dof)
G2 = info2.Gcl

plot([step(G1, 15), step(G2, 15), step(Tref, 15)], lab=["1-DOF" "2-DOF" "Tref"])
w = exp10.(LinRange(-3, 2, 200))
bodeplot(info2.K1, w, lab="Feedforward filter")

@test dcgain(G2)[] ≈ 1 rtol=1e-4


using Metatheory
using Metatheory.EGraphs
using Metatheory.Library

Context = commutative_monoid(:∧, :(:I)) ∪ @theory begin
    (A.Ob ⊢ id(A)) == (A.Ob ∧ id(A).(A→A) ⊢ id(A))
end

println(Context)

using Test
@testset "Context" begin
    @testset "Commutative Monoid in the Context" begin
        @test @areequal Context (A ∧ B⊢ f) (B ∧ A ⊢ f)
        @test @areequal Context (A ∧ (B ∧ C) ⊢ f) ((A∧B) ∧ C ⊢ f)
        @test @areequal Context (A ∧ (B ∧ C) ⊢ f) ((B∧A) ∧ C ⊢ f)
        @test @areequal Context (A ∧ (B ∧ C) ⊢ f) ((C∧B) ∧ A ⊢ f)
        @test @areequal Context ((C ∧ A) ∧ B ⊢ f) ((C ∧ B) ∧ A ⊢ f)
        @test @areequal Context ((A ∧ A) ∧ B ⊢ f) (B ∧ A ⊢ f)
        @test @areequal Context (B∧(A ∧ A) ∧ B ⊢ f) (B ∧ A ⊢ f)
    end
    @testset "Not equal things" begin
        @test ! @areequal Context (A∧B ⊢ A)  (A ⊢ A)
        @test ! @areequal Context (A ⊢ A)  (A∧B ⊢ A)
        @test ! @areequal Context (A ⊢ A)  (A ⊢ A∧B)
        @test ! @areequal Context (A ⊢ A)  (A ⊢ A ∨ B)
    end
    @testset "Unitality" begin
        @test @areequal Context A.Ob ⊢ id(A)        A.Ob∧ id(A).(A→A) ⊢ id(A)
        @test @areequal Context ((A.Ob ∧ A.Ob) ⊢ id(A))        ((A.Ob) ⊢ id(A))
        @test @areequal Context (A.Ob ∧ A.Ob) ⊢ id(A)        A.Ob ∧id(A).(A→A)⊢ id(A)
        @test @areequal Context A.Ob ∧ B.Ob ⊢ id(A)        B.Ob ∧ A.Ob ∧ id(A).(A→A) ⊢ id(A)
        @test @areequal Context (A.Ob ∧ B.Ob ⊢ id(A))        A.Ob ∧ id(A).(A→A) ⊢ id(A)
        @test ! @areequal Context A.Ob ∧ B.Ob ⊢ id(A)  B.Ob ∧ id(B).(A→A)⊢ id(B)
    end
end


import Distributions
import Statistics

abstract type TheoreticalFittedUncertainScalar <: TheoreticalDistributionScalarValue end

Broadcast.broadcastable(uv::TheoreticalFittedUncertainScalar) = Ref(uv.distribution)

"""
UncertainScalarTheoreticalFit

An empirical value represented by a distribution estimated from actual data.

## Fields
- **`distribution`** The distribution describing the value.
- **`values`**: The values from which `distribution` is estimated.
"""
struct UncertainScalarTheoreticalFit{D <: Distribution, T} <: TheoreticalFittedUncertainScalar
    distribution::FittedDistribution{D} # S may be Continuous or Discrete
    values::AbstractVector{T}
end

"""
    ConstrainedUncertainScalarTheoreticalFit

An empirical value represented by a distribution estimated from actual data.

## Fields
- **`distribution`** The truncated version of the distribution describing the
    value.
- **`values`**: The values from which the original distribution was estimated.
"""
struct ConstrainedUncertainScalarTheoreticalFit{D <: Distribution, T} <: TheoreticalFittedUncertainScalar
    distribution::FittedDistribution{D} # S may be Continuous or Discrete
    values::AbstractVector{T}
end

""" Truncate a fitted distribution. """
Distributions.truncated(fd::FittedDistribution, lower, upper) =
    Distributions.truncated(fd.distribution, lower, upper)

Base.rand(fd::UncertainScalarTheoreticalFit) = rand(fd.distribution.distribution)
Base.rand(fd::UncertainScalarTheoreticalFit, n::Int) = rand(fd.distribution.distribution, n)

# For the fitted distributions, we need to access the fitted distribution's distribution
Distributions.pdf(fd::UncertainScalarTheoreticalFit, x) = pdf(fd.distribution.distribution, x)
StatsBase.mode(uv::UncertainScalarTheoreticalFit) = mode(uv.distribution.distribution)
Statistics.mean(uv::UncertainScalarTheoreticalFit) = mean(uv.distribution.distribution)
Statistics.median(uv::UncertainScalarTheoreticalFit) = median(uv.distribution.distribution)
Statistics.quantile(uv::UncertainScalarTheoreticalFit, q) = quantile(uv.distribution.distribution, q)
Statistics.std(uv::UncertainScalarTheoreticalFit) = std(uv.distribution.distribution)
Statistics.var(uv::UncertainScalarTheoreticalFit) = var(uv.distribution.distribution)

export
TheoreticalFittedUncertainScalar,
UncertainScalarTheoreticalFit,
ConstrainedUncertainScalarTheoreticalFit


function laser_over_sensors(laser::PlaneWave, sensors)
    direction, s, Δ = laser.direction, laser.s, laser.Δ

    E₀ = estimate_E₀(laser)
    atomic_component = select_sensor_axes(sensors, direction)

    Threads.@threads for n in 1:Nsensors #
        @inbounds E[n] = E₀ * cis(k₀ * atomic_component[n])
    end
    return E
end

function laser_over_sensors(laser::Gaussian3D, sensors)
    ω₀, s, Δ = laser.ω₀, laser.s, laser.Δ
    Nsensors = length(sensors)

    E = zeros(ComplexF64, Nsensors)
    E₀ = estimate_E₀(laser)

    Threads.@threads for n in 1:Nsensors #
        oneSensor = get_one_sensor(sensors, n)
        @inbounds E[n] = _core_Gaussian(oneSensor, E₀, ω₀)
    end
    return E
end

### --------------- PLANE WAVES ---------------
laser_over_atoms(laser::PlaneWave, atoms) = apply_plane_wave_field(laser, atoms)

function apply_plane_wave_field(laser, atoms)
    direction, s, Δ = laser.direction, laser.s, laser.Δ

    E₀ = estimate_E₀(laser)
    atomic_component = select_atoms_axes(atoms, direction)
    return E₀ .* exp.(+im * k₀ * atomic_component)
end

estimate_E₀(laser) = √(laser.s * (1 + 4(laser.Δ / Γ)^2) / 2)
### --------------- GAUSSIANS ---------------
laser_over_atoms(laser::Gaussian3D, atoms) = apply_gaussian3D_field(laser, atoms)

"""
    Gaussian Beam along z direction
"""
function apply_gaussian3D_field(laser, atoms)
    ω₀, s, Δ = laser.ω₀, laser.s, laser.Δ

    E = zeros(ComplexF64, atoms.N)
    E₀ = estimate_E₀(laser)

    Threads.@threads for n in 1:(atoms.N) #
        oneAtom = get_one_atom(atoms, n)
        @inbounds E[n] = _core_Gaussian(oneAtom, E₀, ω₀)
    end
    return E
end

function _core_Gaussian(oneAtom, E₀, ω₀)
    x, y, z = oneAtom[1], oneAtom[2], oneAtom[3]

    ## This formula is stable for z==0
    # Ref: Eq 3.11 from "CHAPTER 3. PROPAGATION AND FOCUSING OF OPTICAL FIELDS"
    denominator_factor = 1 .+ 2im .* z / (k₀ * ω₀^2)
    Eᵢ = E₀ .* exp.(+im * k₀ * z)
    Eᵢ = Eᵢ .* exp.(-(x .^ 2 + y .^ 2) ./ (denominator_factor .* ω₀^2))
    Eᵢ = Eᵢ ./ denominator_factor
    return Eᵢ
end

function estimate_waist(atoms::Cube)
    return size(atoms) / 4
end
function estimate_waist(atoms::Sphere)
    return size(atoms) / 4
end


module BayesianExperiments

import Statistics: mean, rand, std
using Base
using StaticArrays:SVector
using Distributions:
    pdf,
    Beta, Gamma, LogNormal, Bernoulli, Exponential, Poisson,
    InverseGamma, Normal,
    ContinuousUnivariateDistribution, UnivariateDistribution
using QuadGK

include("util.jl")
include("data.jl")
include("distribution.jl")
include("models/common.jl")
include("models/conjugate.jl")
include("models/bayesfactor.jl")
include("rule.jl")
include("experiment.jl")
include("simulation.jl")

# external dependencies
export
    # Distributions.jl
    Bernoulli,
    Exponential,
    Normal,
    LogNormal

# package exports
export
    # shared methods
    update!,
    rand,
    mean,
    convert,

    # data.jl
    BetaStatistics,
    GammaStatistics,
    NormalStatistics,
    LogNormalStatistics,
    TwoNormalStatistics,
    StudentTStatistics,

    merge,
    tstat,
    tstatpooled,
    tstatwelch,

    # experiment.jl
    Experiment,
    ExperimentABN,
    ExperimentAB,
    ExperimentBF,

    expectedloss,
    expectedlosses,
    probbeatall,
    bayesfactor,

    decide!,
    upliftloss,
    metrics,

    # common.jl
    ProbabilisticModel,

    # conjugate.jl
    ConjugateModel,

    ConjugateBernoulli,
    BernoulliPosteriorSample,

    ConjugateExponential,
    ExponentialPosteriorSample,

    ConjugatePoisson,
    PoissonPosteriorSample,

    ConjugateNormal,
    NormalPosteriorSample,

    ConjugateLogNormal,
    LogNormalPosteriorSample,

    ChainedModel,
    ChainOperator,
    MultiplyOperator,

    samplepost,
    samplestats,
    lognormalparams,

    # bayesfactor.jl
    NormalEffectSize,
    StudentTEffectSize,

    effectsize,

    # rule.jl
    ExpectedLossThresh,
    ProbabilityBeatAllThresh,
    OneSidedBFThresh,
    TwoSidedBFThresh,

    # simulation.jl
    Simulation,
    DataGeneratingDistibutions,

    runonce,
    runsequential,
    updateonce!,

    # util.jl
    catbyrow,
    zstat,
    effsamplesize,
    pooledsd

end # module


@inline S_inner_to_outer(S_in, u, xi, phi0) =
    1/u + xi - u * phi0*phi0/3 * (1 - u * xi) + u*u*u * S_in

@inline S_u_inner_to_outer(S_u_in, S_in, u, xi, phi0) =
    -1/(u*u) - phi0*phi0/3 * (1 - u * xi) + u * xi * phi0*phi0/3 +
    3 * u*u * S_in + u*u*u * S_u_in

@inline F_inner_to_outer(F_in, u) = u*u * F_in

@inline F_u_inner_to_outer(F_u_in, F_in, u) = 2 * u * F_in + u*u * F_u_in

@inline Sd_inner_to_outer(Sd_in, u, xi, phi0) =
    1/(2*u*u) + xi/u + xi*xi/2 - phi0*phi0/6 + u*u * Sd_in

@inline Bd_inner_to_outer(Bd_in, u) = u*u*u * Bd_in

@inline phid_inner_to_outer(phid_in, u, phi0) =
    -phi0/2 + u*u * phi0*phi0*phi0 * phid_in

@inline A_inner_to_outer(A_in, u, xi, phi0) =
    1/(u*u) + 2*xi/u + xi*xi - 2/3 * phi0*phi0 + u*u * A_in

@inline A_u_inner_to_outer(A_u_in, A_in, u, xi, phi0) =
    -2/(u*u*u) - 2*xi/(u*u) + 2*u * A_in + u*u * A_u_in


#=
exp_xform.jl

see also MIRT/time/exp_xform.jl

2019-12-08 Connor Martin
Based on exp_xform_mex.c
Copyright 2004-9-23, Jeff Fessler, University of Michigan
=#

export exp_xform

using Test: @test, @test_throws, @inferred

"""
    exp_xform(x, u, v ; mode::Symbol = :matrix)

in:
* `x [N L]` possibly complex vector(s)
* `u [D N]` possibly complex vectors
* `v [D M]` possibly complex vectors
* `mode::Symbol` `:matrix` (default) | `:element` | `:row` | `:column`

out:
* `y [M L]` typically complex vector
 `y[m,l] = sum_n x[n,l] exp(-sum(u[:,n] .* v[:,m]))`

Iterates through subsets of the ML matrix designated by `:mode`
(i.e. row, column, element, or just computing the entire matrix)
This is the 'slow' 'exact' transform model for MRI.

Output type will depend on input types.
"""
function exp_xform(x::AbstractMatrix{<:Number},
        u::AbstractMatrix{<:Number},
        v::AbstractMatrix{<:Number}
        ; mode::Symbol = :matrix)

    T = promote_type(eltype(u), eltype(v), eltype(x), ComplexF32)

    N = size(u,2)
    M = size(v,2)
    out = zeros(T, M, size(x,2))

    if (mode === :matrix)
        return exp.(-(transpose(v) * u)) * x # [M D] * [D N] * [N L]

    elseif (mode === :element)
        # avoid generating intermediate steps by iterating thru N and M
        # dot the columns
        for n = 1:N
            for m = 1:M
                t = transpose(u[:,n]) * v[:,m] #calculate one spot in the array
                out[m,:] .+= (exp.(-t) .* x[n,:])
            end
        end
        return out

    elseif (mode === :row) # iterate through rows of the large matrix
        for m = 1:M
            t = transpose(v[:,m]) * u # [1 D] * [D N] -> [1 N]
            # m == 1 && @show size(t), size(exp.(-t) * x)
            # product is a 2d matrix (row), but should be a column vector:
            out[m,:] .+= (exp.(-t) * x)[1,:]
        end
        return out

    elseif (mode === :column)
        for n = 1:N
            t = transpose(v) * u[:,n] # [M D] * [D .] -> [M]
            # n == 1 && @show size(t), size(x[n,:])
            out .+= (exp.(-t) * transpose(x[n,:])) # outer product [M 1] * [1 L]
        end
        return out

    else
        throw("Invalid mode parameter $mode")
    end
end

# handle "vector" case where L=1
exp_xform(x::AbstractVector{<:Number},
        u::AbstractMatrix{<:Number},
        v::AbstractMatrix{<:Number}
        ; kwargs...) = exp_xform(reshape(x, :, 1), u, v ; kwargs...)[:,1]

# test for given data type
function exp_xform_test( ; T::DataType = ComplexF32)

    modes = (:element, :row, :column)

    isinteractive() && (@info "1D tests with $T")
    N = 500
    M = 6000
    D = 3
    X = randn(T, N)
    U = randn(T, D, N)
    V = randn(T, D, M)
    y1 = @inferred exp_xform(X, U, V ; mode = :matrix)

    for mode in modes
        y2 = @inferred exp_xform(X, U, V ; mode=mode)
        @test y1 ≈ y2
    end

    isinteractive() && (@info "2D tests with $T")
    N = 10000
    M = 100
    D = 20
    L = 10
    X = randn(T, N, L)
    U = randn(T, D, N)
    V = randn(T, D, M)
    y1 = @inferred exp_xform(X, U, V ; mode = :matrix)

    for mode in modes
        y2 = @inferred exp_xform(X, U, V ; mode=mode)
        @test y1 ≈ y2
    end

    true
end

"""
    exp_xform(:test)
self test
"""
function exp_xform(test::Symbol)
    test != :test && throw("Invalid argument for exp_xform.")
	@test_throws String exp_xform(ones(2,2), ones(2,2), ones(2,2) ; mode=:bad)

#    for T in (ComplexF32, ComplexF64)
        @test exp_xform_test() # T=T
#    end
    true
end


module SolovevAbstract

    import ..ElectromagneticFields
    import ..ElectromagneticFields: AnalyticEquilibrium, AnalyticPerturbation

    export AbstractSolovevEquilibrium

    abstract type AbstractSolovevEquilibrium <: AnalyticEquilibrium end

    R(x::AbstractVector, equ::AbstractSolovevEquilibrium) = x[1] * equ.R₀
    Z(x::AbstractVector, equ::AbstractSolovevEquilibrium) = x[2] * equ.R₀
    ϕ(x::AbstractVector, equ::AbstractSolovevEquilibrium) = x[3]

    X(x::AbstractVector, equ::AbstractSolovevEquilibrium) = R(x,equ) * cos(ϕ(x,equ))
    Y(x::AbstractVector, equ::AbstractSolovevEquilibrium) = R(x,equ) * sin(ϕ(x,equ))
    θ(x::AbstractVector, equ::AbstractSolovevEquilibrium) = atan(Z(x,equ), R(x,equ) - equ.R₀)

    r²(x::AbstractVector, equ::AbstractSolovevEquilibrium) = (R(x,equ) - equ.R₀)^2 + Z(x,equ)^2
    r(x::AbstractVector, equ::AbstractSolovevEquilibrium) = sqrt(r²(x, equ))

    ElectromagneticFields.J(x::AbstractVector, equ::AbstractSolovevEquilibrium) = R(x,equ) * equ.R₀^2

    ElectromagneticFields.A₁(x::AbstractVector, equ::AbstractSolovevEquilibrium) = + equ.B₀ * equ.R₀ * x[2] / x[1] / 2
    ElectromagneticFields.A₂(x::AbstractVector, equ::AbstractSolovevEquilibrium) = - equ.B₀ * equ.R₀ * log(x[1]) / 2

    ElectromagneticFields.x¹(ξ::AbstractVector, equ::AbstractSolovevEquilibrium) = X(ξ,equ)
    ElectromagneticFields.x²(ξ::AbstractVector, equ::AbstractSolovevEquilibrium) = Y(ξ,equ)
    ElectromagneticFields.x³(ξ::AbstractVector, equ::AbstractSolovevEquilibrium) = Z(ξ,equ)

    ElectromagneticFields.ξ¹(x::AbstractVector, equ::AbstractSolovevEquilibrium) = sqrt(x[1]^2 + x[2]^2) / equ.R₀
    ElectromagneticFields.ξ²(x::AbstractVector, equ::AbstractSolovevEquilibrium) = x[3] / equ.R₀
    ElectromagneticFields.ξ³(x::AbstractVector, equ::AbstractSolovevEquilibrium) = atan(x[2], x[1])

    ElectromagneticFields.g₁₁(x::AbstractVector, equ::AbstractSolovevEquilibrium) = equ.R₀^2
    ElectromagneticFields.g₂₂(x::AbstractVector, equ::AbstractSolovevEquilibrium) = equ.R₀^2
    ElectromagneticFields.g₃₃(x::AbstractVector, equ::AbstractSolovevEquilibrium) = R(x, equ)^2

    ElectromagneticFields.get_functions(::AbstractSolovevEquilibrium) = (X=X, Y=Y, Z=Z, R=R, r=r, θ=θ, ϕ=ϕ, r²=r²)
    ElectromagneticFields.get_parameters(::AbstractSolovevEquilibrium) = (:R₀, :B₀, :ϵ, :κ, :δ, :α)

    function ElectromagneticFields.periodicity(x::AbstractArray{T,1}, ::AbstractSolovevEquilibrium) where {T <: Number}
        p = zero(x)
        p[3] = 2π
        return p
    end

end


@testset "Percolation" begin

    @test isapprox(percolation(gridnetwork(5, 5)), 0.574468, rtol = 1e-5)
    @test isapprox(percolation(gridnetwork(5, 6)), 0.584746, rtol = 1e-5)
    @test isapprox(percolation(gridnetwork(6, 6)), 0.594595, rtol = 1e-5)
end


module Ising

export Ensamble, medicionesIndependientes,magnetizaciónensamble,Energíaensamble_Fperiódica, correSweeps

type Ensamble
    configuración::Array{Int}
    H::Float64
    β::Float64
    M::Float64
    δM::Float64
    E::Float64
    δE::Float64
    J::Float64
    function Ensamble(a::Tuple,β::Float64)
	    configuración = rand(-1:2:1,a)
	    new(configuración,0,β,0,0,0,0,1)
    end
end

medicionesIndependientes(a,τ) = a[2*τ:2*τ:(length(a)-mod(length(a),2*τ))]

function eligeEspín(ensamble)
    configuración = ensamble.configuración
    tamaño = size(configuración)
    m = tamaño[1]
    n = tamaño[2]
    ñ = tamaño[3]
    espín = (rand(1:m), rand(1:n), rand(1:ñ))
end

function Erenglónensamble_Flibre(renglón)
    E=0
    l = length(renglón)
    for i in 1:l-1
        E += - renglón[i] * renglón[i+1]
    end
    E
end

function Energíaensamble_Flibre(ensamble)
    configuración = ensamble.configuración
    E = 0
    tamaño = size(configuración)
    m = tamaño[1]
    n = tamaño[2]
    ñ = tamaño[3]
    for i in 1:m, j in 1:n
        E += ensamble.J*Erenglónensamble_Flibre(configuración[i,j,1:end])
    end
    for i in 1:n, j in 1:ñ
        E += ensamble.J*Erenglónensamble_Flibre(configuración[1:end,i,j])
    end
    for i in 1:m, j in 1:ñ
        E += ensamble.J*Erenglónensamble_Flibre(configuración[i,1:end,j])
    end
    E
end

function magnetizaciónensamble(ensamble::Ensamble)
    configuración = ensamble.configuración
    sum(configuración)
end
function magnetizaciónensamble(configuración::Array)
    sum(configuración)
end

function δEnergía(ensamble,espín)
	configuración = ensamble.configuración
	tamaño = size(configuración)
	m = tamaño[1]
	n = tamaño[2]
	ñ = tamaño[3]
	i=espín[1]
	j=espín[2]
	k=espín[3]
	δE = (ensamble.J*configuración[i,j,mod1(k+1,ñ)]+
	ensamble.J*configuración[i,j,mod1(k-1,ñ)]+
	ensamble.J*configuración[i,mod1(j+1,n),k]+
	ensamble.J*configuración[i,mod1(j-1,n),k]+
	ensamble.J*configuración[mod1(i+1,m),j,k]+
	ensamble.J*configuración[mod1(i-1,m),j,k]+
	ensamble.H*ensamble.J/abs(ensamble.J)
	)*2*configuración[i,j,k]
end

function δMagnetización(ensamble,espín)
	configuración = ensamble.configuración
	δM = -2*configuración[espín...]
end

function cambioestado(ensamble,espín)
       ensamble.δE += Ising.δEnergía(ensamble,espín)
       ensamble.δM += -2*ensamble.configuración[espín...]
       end

function paso(ensamble)
	espín = eligeEspín(ensamble)
	δE = δEnergía(ensamble,espín)
	if exp(-ensamble.β*δE)>rand()
		cambioestado(ensamble,espín)
		flipensamble!(ensamble,espín)
	end
end

function sweep(ensamble)
	for j in 1:length(ensamble.configuración)
		paso(ensamble)
	end
end

function correSweeps(ensamble,sweeps)
	Ms = zeros(sweeps)
	Es = zeros(sweeps)
	ensamble.E = Energíaensamble_Fperiódica(ensamble)
	ensamble.M = magnetizaciónensamble(ensamble)
	ensamble.δE = 0
	ensamble.δM = 0
	for i in 1:sweeps
		sweep(ensamble)
		Ms[i] = ensamble.M + ensamble.δM
		Es[i] = ensamble.E + ensamble.δE
	end
	{"M"=>Ms,"E"=>Es}
end

function flipensamble!(ensamble,espín)
    configuración = ensamble.configuración
    configuración[espín...]*= -1
    return espín
end
function Erenglónensamble_Fperiódica(renglón)
    l = length(renglón)
    E = - renglón[1] * renglón[l]
    for i in 1:l-1
        E += - renglón[i] * renglón[i+1]
    end
    E
end

function Energíaensamble_Fperiódica(ensamble::Ensamble)
	configuración=ensamble.configuración
    E = 0
    tamaño = size(configuración)
    m = tamaño[1]
    n = tamaño[2]
    ñ = tamaño[3]
    for i in 1:m, j in 1:n
        E += ensamble.J*Erenglónensamble_Fperiódica(configuración[i,j,1:end])
    end
    for i in 1:n, j in 1:ñ
        E += ensamble.J*Erenglónensamble_Fperiódica(configuración[1:end,i,j])
    end
    for i in 1:m, j in 1:ñ
        E += ensamble.J*Erenglónensamble_Fperiódica(configuración[i,1:end,j])
    end
    E+magnetizaciónensamble(ensamble)*ensamble.H*ensamble.J/abs(ensamble.J)
end

end


using HMatrices
using LinearAlgebra
using Test
using StaticArrays
using HMatrices: RkMatrix, compression_ratio

@testset "RkMatrix" begin
    @testset "Scalar entries" begin
        m = 20
        n = 30
        r = 5
        A = rand(ComplexF64,m,r)
        B = rand(ComplexF64,n,r)
        R  = RkMatrix(A,B);
        Ra  = adjoint(R);
        M  = A*adjoint(B)
        Ma  = adjoint(M)

        ## basic tests
        @test size(R) == (m,n)
        @test rank(R) == r
        @test Matrix(R) ≈ M
        @test compression_ratio(R) ≈ m*n / (r*(m+n))
        @test R[:,5] ≈ M[:,5]
        @test Ra[:,5] ≈ Ma[:,5]
    end

    @testset "Matrix entries" begin
        m = 20
        n = 30
        r = 10
        T = SMatrix{3,3,ComplexF64,9}
        A = rand(T,m,r)
        B = rand(T,n,r)
        R  = RkMatrix(A,B)
        ## basic tests
        @test size(R) == (m,n)
        @test rank(R) == r
        @test compression_ratio(R) ≈ m*n / (r*(m+n))
    end
end


using QuantumOptics
using PyPlot

b = SpinBasis(1//2)
sx = 1/2*sigmax(b)
sy = 1/2*sigmay(b)
sz = 1/2*sigmaz(b)
XX = sx ⊗ sx
YY = sy ⊗ sy
ZZ = sz ⊗ sz

Jxy=1
Jz=1

function basis(N)
    spinchain=b
    for ispin in 2:N
        spinchain = spinchain ⊗ b

    end
    return spinchain # same local
end

function CrtModelH(N)
    Htwoint=Jxy*(XX+YY)+Jz*ZZ
    HXXZ = embed(basis(N), [1,2] , Htwoint)
    for s in 2:N-1
        HXXZ += embed(basis(N), [s,s+1] , Htwoint)
    end
    return HXXZ
end

s5z = embed(basis(10) , [5] , sz)
s6z = embed(basis(10) , [6] , sz)
sup = spinup(b)
sdo = spindown(b)
psi0 = sup
for ispin in 2:10
    global psi0
    if ispin == 5
       psi0 = psi0 ⊗ sdo
    else
       psi0 = psi0 ⊗ sup
    end
end

times = 0:.1:12
tout, psi_t = timeevolution.schroedinger(times, psi0, CrtModelH(10))

m5z = expect(s5z, psi_t)
m6z = expect(s6z, psi_t)


import Pkg; Pkg.activate(".")
using SimpleSDMLayers
using Plots
using StatsPlots
using Statistics
using StatsBase
using GBIF

## World-scale example

# Get world temperature
temperature = worldclim(1)
# Verify size
size(temperature)
# Map temperature
temperature_plot = plot(temperature, xguide = "Longitude", yguide = "Latitude", colorbar_title = "Average Temperature (°C)")
# Save result
savefig(temperature_plot, "fig/temperature.png")

## Common manipulations

# Clip to Europe
temperature_europe = temperature[left=-11.2, right=30.6, bottom=29.2, top=71.0];
# Verify size again
size(temperature_europe)
# Map result
temperature_europlot = plot(temperature_europe, frame = :box, ticks = false, colorbar = false)
# Save
savefig(temperature_europlot, "fig/temperature_europe.png")

# Coarsen example
temperature_europe_coarse = coarsen(temperature_europe, Statistics.mean, (4, 4))
plot(temperature_europe_coarse, frame = :box, ticks = false, colorbar = false)
# Save
savefig("fig/coarsen.png")

# Sliding window example
averaged = slidingwindow(temperature_europe, Statistics.mean, 100.0)
plot(averaged, frame = :box, ticks = false, colorbar = false)
# Save
savefig("fig/slidingwindow.png")

## GBIF integration

# Get taxon & occurrences
kingfisher = GBIF.taxon("Megaceryle alcyon", strict=true)
kf_occurrences = occurrences(kingfisher)

# Get some more occurrences
for i in 1:9
  occurrences!(kf_occurrences)
end

# Filter for correct coordinates
# filter!(GBIF.have_ok_coordinates, kf_occurrences)
# @info kf_occurrences

# Clip layer to occurrences
temperature_clip = clip(temperature, kf_occurrences)

# Plot occurrences
contour(temperature_clip, fill=true, colorbar_title = "Average temperature (°C)",
                  xguide = "Longitude", yguide = "Latitude")
scatter!(longitudes(kf_occurrences), latitudes(kf_occurrences),
         label = "Kingfisher occurrences", legend = :bottomleft,
         c=:white, msc=:orange)
# Save
savefig("fig/occurrences.png")

## BIOCLIM example

include("bioclim.jl")

# Get all worldclim variables
vars = worldclim(1:19)

# Get prediction for each variable
vars_predictions = [bioclim(v, kf_occurrences) for v in vars]

# Get minimum prediction per site
sdm_predictions = reduce(min, vars_predictions)

# Set value to nothing if prediction is zero
replace!(x -> isnothing(x) || iszero(x) ? nothing : x, sdm_predictions.grid)

# Filter predictions with threshold
# threshold = quantile(filter(!isnothing, sdm_predictions.grid), 0.05)
# replace!(x -> isnothing(x) || x <= threshold ? nothing : x, sdm_predictions.grid)

# Map background
plot(temperature_clip, c = :lightgrey,
     xguide = "Longitude", yguide = "Latitude")
# Map predictions
plot!(clip(sdm_predictions, kf_occurrences), c = :viridis,
      clim = (minimum(sdm_predictions), maximum(sdm_predictions)),
      colorbar_title = "Predicted suitability score")
# Map occurrences
# scatter!(longitudes(kf_occurrences), latitudes(kf_occurrences),
#          label = "Kingfisher occurrences", legend = :bottomleft,
#          c=:white, msc=:orange)
# Save result
savefig("fig/bioclim.png")


#####################################
#ScalarExpr##########################
#####################################

immutable ScalarExpr <: AbstractScalar
	ex::Expr
end

isequal(a::ScalarExpr, b::ScalarExpr) = isequal(a.ex, b.ex)
==(a::ScalarExpr, b::ScalarExpr) = ==(a.ex, b.ex)

convert{N<:Union(InnerProduct, Number)}(::Type{ScalarExpr}, i::N) = ScalarExpr(:(1*$i))
promote_rule{N<:Union(InnerProduct, Number)}(::Type{ScalarExpr}, ::Type{N}) = ScalarExpr
promote_rule{N<:Union(InnerProduct, Number), T}(::Type{Array{ScalarExpr,T}}, ::Type{Array{N,T}}) = Array{ScalarExpr,T}
promote_rule{N<:Union(InnerProduct, Number), T}(::Type{SparseMatrixCSC{ScalarExpr,T}}, ::Type{SparseMatrixCSC{N,T}}) = SparseMatrixCSC{ScalarExpr,T}

scalar(ex::Expr) = ScalarExpr(ex)
scalar(x) = convert(ScalarExpr, x)

#####################################
#Misc Functions######################
#####################################
qexpr(s::ScalarExpr) = s.ex
qexpr(x) = x
qeval(f::Function, s::ScalarExpr) = eval(qreduce(f, s.ex))
qeval(f::Function, i::InnerProduct) = eval(f(i.bra, i.ket))
qeval(f::Function, n::Number) = n

function qreduce(f::Function, ex::Expr)
	ex = copy(ex)
	for i=1:length(ex.args)
		if typeof(ex.args[i])<:InnerProduct
			ex.args[i] = f(ex.args[i].bra, ex.args[i].ket)
		elseif typeof(ex.args[i])==Expr
			ex.args[i] = qreduce(f, ex.args[i])
		end
	end
	return ex
end

#####################################
#Arithmetic Operations###############
#####################################
function ^(d::DiracCoeff, n::Integer)
	if n==1
		return scalar(d)
	elseif n==0
		return scalar(1)
	else
		return scalar(:($(qexpr(d))^$(qexpr(n))))
	end
end

function ^(a::DiracCoeff, b::DiracCoeff)
	if b==1
		return scalar(a)
	elseif b==0
		return scalar(1)
	else
		return scalar(:($(qexpr(a))^$(qexpr(b))))
	end
end

function *(a::DiracCoeff, b::DiracCoeff)
	if a==1
		return scalar(b)
	elseif b==1
		return scalar(a)
	elseif a==0 || b==0
		return scalar(0)
	else
		return scalar(:($(qexpr(a))*$(qexpr(b))))
	end
end

function +(a::DiracCoeff, b::DiracCoeff)
	if a==0
		return scalar(b)
	elseif b==0
		return scalar(a)
	else
		scalar(:($(qexpr(a))+$(qexpr(b))))
	end
end

function -(a::DiracCoeff, b::DiracCoeff)
	if a==0
		return scalar(-b)
	elseif b==0
		return scalar(a)
	elseif a==b
		return scalar(0)
	else
		return scalar(:($(qexpr(a))-$(qexpr(b))))
	end
end

-(d::DiracCoeff) = ScalarExpr(:(-$(qexpr(d))))
-(s::ScalarExpr) = length(s.ex.args)==2 && s.ex.args[1]==:- ? ScalarExpr(s.ex.args[2]) :  ScalarExpr(:(-$(qexpr(s))))

abs(i::InnerProduct) = ScalarExpr(:(abs($i)))
abs(s::ScalarExpr) = length(s.ex.args)==2 && s.ex.args[1]==:abs ? s :  ScalarExpr(:(abs($(qexpr(s)))))

exp(a::AbstractScalar) = ScalarExpr(:(exp($(qexpr(a)))))

function /(a::DiracCoeff, b::DiracCoeff)
	if a==0
		return b
	elseif b==0
		error("Zero in denominator of ScalarExpr")
	elseif a==b
		return 1
	else
		ScalarExpr(:($(qexpr(a))/$(qexpr(b))))
	end
end

conj(s::ScalarExpr)	= length(s.ex.args)==2 && s.ex.args[1]==:conj ? ScalarExpr(s.ex.args[2]) :  ScalarExpr(:(conj($(qexpr(s)))))
ctranspose(s::ScalarExpr) = conj(s)

for op=(:*,:-,:+,:/,:^) #define for elementwise operators
	elop = symbol(string(:.) * string(op))
	@eval ($elop)(a::DiracCoeff, b::DiracCoeff) = ($op)(a,b)
	@eval ($elop)(a::AbstractScalar, arr::AbstractArray) = broadcast(($op), [a], arr)
	@eval ($elop)(arr::AbstractArray, a::AbstractScalar) = broadcast(($op), arr, [a])
end

*(a::AbstractScalar, arr::AbstractArray) = a.*arr
*(arr::AbstractArray, a::AbstractScalar) = arr.*a
/(arr::AbstractArray, a::AbstractScalar) = arr./a


module GaussLegendre
	using LinearAlgebra
	using Printf

	function gausslegendre(n)
		# COMPUTE GAUSS-LEGENDRE NODES AND WEIGHTS USING ASYMPTOTIC EXPANSIONS.
		# COMPLEXITY O(n).

		# Nodes and weights:
		m = (n + 1) >> 1
		a = besselZeroRoots(m)
		rmul!(a, BigFloat(1) / (BigFloat(n) + BigFloat(1) / BigFloat(2)))
		x = legpts_nodes(n, a)
		w = legpts_weights(n, m, a)
		# Use symmetry to get the others:
		resize!(x, n)
		resize!(w, n)
		@inbounds for i in 1:m
			x[n + 1 - i] = x[i]
			w[n + 1 - i] = w[i]
		end
		@inbounds for i in 1:m
			x[i] = -x[i]
		end
		@inbounds mod(n, 2) == 1 && (x[m] = 0.0)
		x, w
	end

	function legpts_nodes(n, a)
		# ASYMPTOTIC EXPANSION FOR THE GAUSS-LEGENDRE NODES.
		vn = BigFloat(1) / (BigFloat(n) + BigFloat(1) / BigFloat(2))
		m = length(a)
		nodes = cot.(a)
		vn² = vn * vn
		vn⁴ = vn² * vn²
		@inbounds if n <= 255
			vn⁶ = vn⁴ * vn²
			for i in 1:m
				u = nodes[i]
				u² = u^2
				ai = a[i]
				ai² = ai * ai
				ai³ = ai² * ai
				ai⁵ = ai² * ai³
				node = ai + (u - BigFloat(1) / ai) / BigFloat(8) * vn²
				v1 = (BigFloat(6) * (BigFloat(1) + u²) / ai + BigFloat(25) / ai³ - u * muladd(BigFloat(31), u², BigFloat(33))) / BigFloat(384)
				v2 = u * @evalpoly(u², BigFloat(2595) / BigFloat(15360), BigFloat(6350) / BigFloat(15360), BigFloat(3779) / BigFloat(15360))
				v3 = (1 + u²) * (-muladd(BigFloat(31) / BigFloat(1024), u², BigFloat(11) / BigFloat(1024)) / ai +
								 u / BigFloat(512) / ai² + BigFloat(-25) / BigFloat(3072) / ai³)
				v4 = (v2 - BigFloat(1073) / BigFloat(5120) / ai⁵ + v3)
				node = muladd(v1, vn⁴, node)
				node = muladd(v4, vn⁶, node)
				nodes[i] = node
			end
		elseif n <= 3950
			for i in 1:m
				u = nodes[i]
				u² = u^2
				ai = a[i]
				ai² = ai * ai
				ai³ = ai² * ai
				node = ai + (u - 1 / ai) / 8 * vn²
				v1 = (BigFloat(6) * (BigFloat(1) + u²) / ai + BigFloat(25) / ai³ - u * muladd(BigFloat(31), u², BigFloat(33))) / BigFloat(384)
				node = muladd(v1, vn⁴, node)
				nodes[i] = node
			end
		else
			for i in 1:m
				u = nodes[i]
				ai = a[i]
				node = ai + (u - BigFloat(1) / ai) / BigFloat(8) * vn²
				nodes[i] = node
			end
		end
		@inbounds for jj = 1:m
			nodes[jj] = cos(nodes[jj])
		end
		nodes
	end

	function legpts_weights(n, m, a)
		# ASYMPTOTIC EXPANSION FOR THE GAUSS-LEGENDRE WEIGHTS.
		vn = BigFloat(1) / (BigFloat(n) + BigFloat(1) / BigFloat(2))
		vn² = vn^2
		weights = Array{Float64}(undef, m)
		if n <= 850000
			@inbounds for i in 1:m
				weights[i] = cot(a[i])
			end
		end
		# Split out the part that can be vectorized by llvm
		@inbounds if n <= 170
			for i in 1:m
				u = weights[i]
				u² = u^2
				ai = a[i]
				ai⁻¹ = 1 / ai
				ai² = ai^2
				ai⁻² = 1 / ai²
				ua = u * ai
				W1 = muladd(ua - BigFloat(1), ai⁻², BigFloat(1)) / BigFloat(8)
				W2 = @evalpoly(ai⁻², @evalpoly(u², BigFloat(-27), BigFloat(-84), BigFloat(-56)),
							   muladd(BigFloat(-3), muladd(u², BigFloat(-2), BigFloat(1)), BigFloat(6) * ua),
							   muladd(ua, BigFloat(-31), BigFloat(81))) / BigFloat(384)
				W3 = @evalpoly(ai⁻¹, @evalpoly(u², BigFloat(153) / BigFloat(1024), BigFloat(295) / BigFloat(256), BigFloat(187) / BigFloat(96),
											   BigFloat(151) / BigFloat(160)),
							   @evalpoly(u², BigFloat(-65) / BigFloat(1024), BigFloat(-119) / BigFloat(768), BigFloat(-35) / BigFloat(384)) * u,
							   @evalpoly(u², BigFloat(5) / BigFloat(512), BigFloat(15) / BigFloat(512), BigFloat(7) / BigFloat(384)),
							   muladd(u², BigFloat(1) / BigFloat(512), BigFloat(-13) / BigFloat(1536)) * u,
							   muladd(u², BigFloat(-7) / BigFloat(384), + BigFloat(53) / BigFloat(3072)),
							   BigFloat(3749) / BigFloat(15360) * u, BigFloat(-1125) / BigFloat(1024))
				weights[i] = @evalpoly(vn², BigFloat(1) / vn² + W1, W2, W3)
			end
		elseif n <= 1500
			for i in 1:m
				u = weights[i]
				u² = u^2
				ai = a[i]
				ai² = ai^2
				ai⁻² = 1 / ai²
				ua = u * ai
				W1 = muladd(ua - BigFloat(1), ai⁻², BigFloat(1)) / BigFloat(8)
				W2 = @evalpoly(ai⁻², @evalpoly(u², BigFloat(-27), BigFloat(-84), BigFloat(-56)),
							   muladd(BigFloat(-3), muladd(u², BigFloat(-2), BigFloat(1)), BigFloat(6) * ua),
							   muladd(ua, BigFloat(-31), BigFloat(81))) / BigFloat(384)
				weights[i] = muladd(vn², W2, 1 / vn² + W1)
			end
		elseif n <= 850000
			for i in 1:m
				u = weights[i]
				u² = u^2
				ai = a[i]
				ai² = ai^2
				ai⁻² = BigFloat(1) / ai²
				ua = u * ai
				W1 = muladd(ua - BigFloat(1), ai⁻², BigFloat(1)) / BigFloat(8)
				weights[i] = BigFloat(1) / vn² + W1
			end
		else
			for i in 1:m
				weights[i] = BigFloat(1) / vn²
			end
		end
		bJ1 = besselJ1(m)
		@inbounds for i in 1:m
			weights[i] = BigFloat(2) / (bJ1[i] * (a[i] / sin(a[i])) * weights[i])
		end
		return weights
	end

	const besselZeros_20 = [parse(BigFloat, "2.40482555769577244220158718235325068235397338867188"),
							parse(BigFloat, "5.52007811028631056871063265134580433368682861328125"),
							parse(BigFloat, "8.65372791291100718069628783268854022026062011718750"),
							parse(BigFloat, "11.7915344390142813324473536340519785881042480468750"),
							parse(BigFloat, "14.9309177084877866548140445956960320472717285156250"),
							parse(BigFloat, "18.0710639679109235089526919182389974594116210937500"),
							parse(BigFloat, "21.2116366298792584643706504721194505691528320312500"),
							parse(BigFloat, "24.3524715307493018201512313680723309516906738281250"),
							parse(BigFloat, "27.4934791320402531766831089043989777565002441406250"),
							parse(BigFloat, "30.6346064684319756565855641383677721023559570312500"),
							parse(BigFloat, "33.7758202135735672300143050961196422576904296875000"),
                        	parse(BigFloat, "36.9170983536640449074184289202094078063964843750000"),
							parse(BigFloat, "40.0584257646282395626258221454918384552001953125000"),
							parse(BigFloat, "43.1997917131767295018107688520103693008422851562500"),
							parse(BigFloat, "46.3411883716618149264832027256488800048828125000000"),
							parse(BigFloat, "49.4826098973978147910202096682041883468627929687500"),
							parse(BigFloat, "52.6240518411149977850982395466417074203491210937500"),
							parse(BigFloat, "55.7655107550199815591440710704773664474487304687500"),
							parse(BigFloat, "58.9069839260809402503582532517611980438232421875000"),
							parse(BigFloat, "62.0484691902267044838481524493545293807983398437500")]

	function besselZeroRoots(m)
		# BESSEL0ROOTS ROOTS OF BESSELJ(0,x). USE ASYMPTOTICS.
		# Use McMahon's expansion for the remainder (NIST, 10.21.19):
		jk = Array{BigFloat}(undef, m)
		p = (BigFloat(1071187749376) / BigFloat(315), BigFloat(0), BigFloat(-401743168) / BigFloat(105), BigFloat(0), BigFloat(120928) / BigFloat(15),
			 BigFloat(0), BigFloat(-124) / BigFloat(3), BigFloat(0), BigFloat(1), BigFloat(0))
		# First 20 are precomputed:
		@inbounds for jj = 1:min(m, 20)
			jk[jj] = besselZeros_20[jj]
		end
		@inbounds for jj = 21:min(m, 47)
			ak = BigFloat(π) * (jj - BigFloat(1) / BigFloat(4))
			ak82 = (BigFloat(1) / BigFloat(8) / ak)^2
			jk[jj] = ak + BigFloat(1) / BigFloat(8) / ak * @evalpoly(ak82, BigFloat(1), p[7], p[5], p[3])
		end
		@inbounds for jj = 48:min(m, 344)
			ak = BigFloat(π) * (jj - BigFloat(1) / BigFloat(4))
			ak82 = (BigFloat(1) / BigFloat(8) / ak)^2
			jk[jj] = ak + BigFloat(1) / BigFloat(8) / ak * @evalpoly(ak82, BigFloat(1), p[7], p[5])
		end
		@inbounds for jj = 345:min(m,13191)
			ak = BigFloat(π) * (jj - BigFloat(1) / BigFloat(4))
			ak82 = (BigFloat(1) / BigFloat(8) / ak)^2
			jk[jj] = ak + BigFloat(1) / BigFloat(8) / ak * muladd(ak82, p[7], BigFloat(1))
		end
		@inbounds for jj = 13192:m
			ak = BigFloat(π) * (jj - BigFloat(1) / BigFloat(4))
			jk[jj] = ak + BigFloat(1) / BigFloat(8) / ak
		end
		return jk
	end

	const besselJ1_10 = [parse(BigFloat, "0.26951412394191699930425603831096479552416724046623932"),
						 parse(BigFloat, "0.11578013858220369920087677406869183804800006055233685"),
						 parse(BigFloat, "0.07368635113640830090795457351166741709059792271399662"),
						 parse(BigFloat, "0.05403757319811628461998670001810067913900791658910733"),
						 parse(BigFloat, "0.04266142901724308722505899310539410056259036644129061"),
						 parse(BigFloat, "0.03524210349099609759173136127110351041151336643781974"),
						 parse(BigFloat, "0.03002107010305467407541978396539677780280158649895994"),
                     	 parse(BigFloat, "0.02614739149530809055943469810813860988716164552883349"),
						 parse(BigFloat, "0.02315912182469139499312285631116841466379136562175513"),
						 parse(BigFloat, "0.02078382912226785687257060668341061486452086057459184")]

	function besselJ1(m)
		# BESSELJ1 EVALUATE BESSELJ(1,x)^2 AT ROOTS OF BESSELJ(0,x).
		# USE ASYMPTOTICS. Use Taylor series of (NIST, 10.17.3) and McMahon's
		# expansion (NIST, 10.21.19):
		Jk2 = Array{Float64}(undef, m)
		c = (BigFloat(-171497088497) / BigFloat(15206400), BigFloat(461797) / BigFloat(1152), BigFloat(-172913) / BigFloat(8064),
			 BigFloat(151) / BigFloat(80), BigFloat(-7) / BigFloat(24), BigFloat(0), BigFloat(2))
		# First 10 are precomputed:
		@inbounds for jj = 1:min(m, 10)
			Jk2[jj] = besselJ1_10[jj]
		end
		@inbounds for jj = 11:min(m, 15)
			ak = BigFloat(π) * (jj - BigFloat(1) / BigFloat(4))
			ak2 = (1 / ak)^2
			Jk2[jj] = 1 / (BigFloat(π) * ak) * muladd(@evalpoly(ak2, c[5], c[4], c[3],
													  c[2], c[1]), ak2^2, c[7])
		end
		@inbounds for jj = 16:min(m, 21)
			ak = BigFloat(π) * (jj - BigFloat(1) / BigFloat(4))
			ak2 = (1 / ak)^2
			Jk2[jj] = 1 / (BigFloat(π) * ak) * muladd(@evalpoly(ak2, c[5], c[4], c[3], c[2]),
											ak2^2, c[7])
		end
		@inbounds for jj = 22:min(m,55)
			ak = BigFloat(π) * (jj - BigFloat(1) / BigFloat(4))
			ak2 = (1 / ak)^2
			Jk2[jj] = 1 / (BigFloat(π) * ak) * muladd(@evalpoly(ak2, c[5], c[4], c[3]),
											ak2^2, c[7])
		end
		@inbounds for jj = 56:min(m,279)
			ak = BigFloat(π) * (jj - BigFloat(1) / BigFloat(4))
			ak2 = (BigFloat(1) / ak)^2
			Jk2[jj] = BigFloat(1) / (BigFloat(π) * ak) * muladd(muladd(ak2, c[4], c[5]), ak2^2, c[7])
		end
		@inbounds for jj = 280:min(m,2279)
			ak = BigFloat(π) * (jj - BigFloat(1) / BigFloat(4))
			ak2 = (BigFloat(1) / ak)^2
			Jk2[jj] = BigFloat(1) / (BigFloat(π) * ak) * muladd(ak2^2, c[5], c[7])
		end
		@inbounds for jj = 2280:m
			ak = BigFloat(π) * (jj - BigFloat(1) / BigFloat(4))
			Jk2[jj] = BigFloat(1) / (BigFloat(π) * ak) * c[7]
		end
		return Jk2
	end

    function gaulegtemp(x1::BigFloat, x2::BigFloat, n, digit)
        setprecision(digit)

        m = div(n + 1, 2)
	    xm = BigFloat(1) / BigFloat(2) * (x2 + x1)
	    xl = BigFloat(1) / BigFloat(2) * (x2 - x1)

		x = zeros(BigFloat, n)
		w = zeros(BigFloat, n)

		pp = BigFloat(0)
		@inbounds for i = 1:m
			z = cos(BigFloat(pi) * (BigFloat(i) - BigFloat(1) / BigFloat(4)) / (BigFloat(n) + BigFloat(1) / BigFloat(2)))

        	while true
				p1 = BigFloat(1)
				p2 = BigFloat(0)

				for j = 1:n
					p3 = p2
					p2 = p1
					p1 = (BigFloat(2 * j - 1) * z * p2 - BigFloat(j - 1) * p3) / BigFloat(j)
				end

				pp = BigFloat(n) * (z * p1 - p2) / (z * z - BigFloat(1))
				z1 = z
				z = z1 - p1 / pp

				if abs(z - z1) <= BigFloat(10)^(-BigFloat(3))
                	break
            	end
			end

			func = function (a)
				p1a = BigFloat(1)
				p2a = a

				for j = 1:n
					p3a = p2a
					p2a = p1a
					p1a = (BigFloat(2 * j - 1) * a * p2a - BigFloat(j - 1) * p3a) / BigFloat(j)
				end

				return p1a
			end

			z = find_zero(func, z)

			x[i] = xm - xl * z
			x[n + 1 - i] = xm + xl * z
			w[i] = BigFloat(2) * xl / ((BigFloat(1) - z * z ) * pp * pp)
			w[n + 1 - i] = w[i]
		end

        return x, w
	end

    function gauleg(func, x1::BigFloat, x2::BigFloat, n)
		x, w = gausslegendre(n)

		xm = BigFloat(1) / BigFloat(2) * (x1 + x2)
    	xr = BigFloat(1) / BigFloat(2) * (x2 - x1)

		return sum(i -> w[i] * func(xm + xr * x[i]), eachindex(x)) * xr
    end
end


function normal2a()
    μₑ = [2.5, 3.5]
    Σₑ = [2.0 -0.5;
        -0.5 5.0]
    a = [-2.3,-20.]
    b = [4.,12.3]
    properties = Dict{String,Any}()
    properties["length"] = 2
    properties["tp"] = 0.8551938607791414
    properties["mean"] = [2.126229598541412, 3.5930224468784577]
    properties["covariance"] = [1.0 0; 0 1.0] #QQQQ
    properties["moment123"] = 2.3 #QQQQ
    (MvNormal(μₑ,Σₑ), BoxTruncationRegion(a,b),properties)
end

function normal2b()
    μₑ = [2.5, 3.5]
    Σₑ = [  3.3 0.5;
            0.5 5.0]
    a = [-5.4,-20.]
    b = [2.4,6.3]
    properties = Dict{String,Any}()
    properties["length"] = 2
    properties["tp"] = 0.43660920327458974
    properties["mean"] = [0.9734400003512856, 2.886032492774952]
    properties["covariance"] = [1.0 0; 0 1.0] #QQQQ
    properties["moment123"] = 2.3 #QQQQ
    (MvNormal(μₑ,Σₑ), BoxTruncationRegion(a,b),properties)
end

function normal2c()
    a = [-1.5, -1.4]
    b = [1.7, 2.4]
    μ₁ = 0.45; μ₂ = -0.2
    σ₁ = 1.2; σ₂ = 0.8
    ρ = 0.6
    μₑ = [μ₁, μ₂]
    Σₑ = [σ₁^2 ρ*σ₁*σ₂ ;ρ*σ₁*σ₂ σ₂^2];
    properties = Dict{String,Any}()
    properties["length"] = 2
    properties["tp"] = 0.7517763397386328
    properties["mean"] = [0.308275927584875, -0.18541888959459515]
    properties["covariance"] = [1.0 0; 0 1.0] #QQQQ
    properties["moment123"] = 2.3 #QQQQ
    (MvNormal(μₑ,Σₑ), BoxTruncationRegion(a,b),properties)
end

function normal3a()
    μₑ = [3.5,2,3.5]
    Σₑ = [ 7. 1 0 ;
           1 3.3 2  ;
           0 2 3.8 ]
    a = [-2. ,-1 ,2]
    b = [4. ,4. , 5]
    properties = Dict{String,Any}()
    properties["length"] = 3
    properties["tp"] = 0.9 #QQQQ
    properties["mean"] = [1.0,1.0] #QQQQ
    properties["covariance"] = [1.0 0; 0 1.0] #QQQQ
    properties["moment123"] = 2.3 #QQQQ
    (MvNormal(μₑ,Σₑ), BoxTruncationRegion(a,b),properties)
end

function normal4a()
    μₑ = [3.5,2,3.5,3.5]
    Σₑ = [ 7. 1 0 1 ;
        1 3.3 2 0 ;
        0 2 3.4 0 ;
        1 0 0 4 ]
    a = [-20. ,-20, -20 ,-20]
    b = [20. ,20. ,20, 20]
    properties = Dict{String,Any}()
    properties["length"] = 4
    properties["tp"] = 0.9 #QQQQ
    properties["mean"] = [1.0,1.0] #QQQQ
    properties["covariance"] = [1.0 0; 0 1.0] #QQQQ
    properties["moment123"] = 2.3 #QQQQ
    (MvNormal(μₑ,Σₑ), BoxTruncationRegion(a,b),properties)
end

function normal5a()
    μₑ = [3.5,2,3.5,3.5,5.3]
    Σₑ = [ 7. 1   0 0 1 ;
        1  3.3 2 0 0 ;
        0 2 3.4 0 0 ;
        0 0 0 4 0;
        1 0 0 0 2]
    a = [-20.,-20,-20,-20,-20]
    b = [20.,20.,20,20,20]
    properties = Dict{String,Any}()
    properties["length"] = 5
    properties["tp"] = 0.9 #QQQQ
    properties["mean"] = [1.0,1.0] #QQQQ
    properties["covariance"] = [1.0 0; 0 1.0] #QQQQ
    properties["moment123"] = 2.3 #QQQQ
    (MvNormal(μₑ,Σₑ), BoxTruncationRegion(a,b),properties)
end

distribution_generators2 = [normal2a,normal2b,normal2c]
distribution_generators3 = [normal3a]
distribution_generators4 = [normal4a]
distribution_generators5 = [normal5a]
distribution_generators = vcat(distribution_generators2,distribution_generators3,distribution_generators4,distribution_generators5)


module MieSeries
using StaticArrays
import SpecialFunctions
import AssociatedLegendrePolynomials as Legendre

export mieseries, incident_planewave

include("legendre.jl")
include("bessel.jl")
include("scattering.jl")

end # module


# julia1_1.4.jl Julia Resources
# GLM Generealized Linear Model
#Pkg.add("Gadfly")
#using Gadfly
#Pkg.add("DataFrames")
#Pkg.add("GLM")

using Gadfly
using DataFrames, GLM

data=DataFrame(x=[0.9,2.0,3.0],y=[2.0,4.0,6.5])
ols=glm(@formula(y ~ x), data, Normal(), IdentityLink())
c=coef(ols)
#  y=c[1]+c[2]*x
#  y=-0.0370091 + 2.13746*x
plot(data,x=:x,y=:y,Geom.point,intercept=[c[1]],slope=[c[2]],Geom.abline(color="red",style=:dash))

using GLM, DataFrames
data=DataFrame(x1=[0.9,2.0,3.0,3.9],x2=[0.291,0.210,0.105,0.021],y=[2.0,4.0,6.5,7.9])
ols=glm(@formula(y ~ x1 + x2), data, Normal(), IdentityLink())

coef(ols)
stderror(ols)
predict(ols)


function isweyl(lgir::LGIrrep{3}; timereversal::Bool=true)
    irdim(lgir) ≠ 2 && return false # must be 2D irrep

    H = kdotp(lgir; timereversal)

    # must contain nonzero σ₁, σ₂, σ₃ terms, but no σ₀ terms
    #(length(H.hs) ≥ 3 && I(2) ∉ H.hs) || return false
    (length(H.hs) ≥ 3 && [0 1; 1 0] ∈ H.hs && [0 -1im; 1im 0] ∈ H.hs && [1 0; 0 -1] ∈ H.hs) || return false

    # find the "generalized" Weyl form of the k ⋅ p Hamiltonian, H = (Vk) ⋅ σ, with matrix V
    V = zeros(3,3)
    for a in eachindex(H.cs)
        # **some** prefactor value; any (not-equal & not-too-simply-related) should be OK...
        x = .1*a^(1.25)
        for d in 1:3
            V[:,d] .+= x.*H.cs[a][d][1:3]
        end
    end

    # if V is invertible, the Hamiltonian can be transformed to a coordinate setting where
    # it has the standard form of the Weyl Hamiltonian, i.e. k ⋅ σ
    return rank(V) == 3
end

function is_spinone_weyl(lgir::LGIrrep{3}; timereversal::Bool=true)
    # TODO
    # check whether H is unitarily equivalent to a matrix of the form
    #   H = k ⋅ S
    # with S = [L₁, L₂, L₃] where Lᵢ are any 3×3 matrices that fulfil the commutation
    # relation [Lᵢ,Lⱼ] = iϵᵢⱼₖLₖ. This conceptual classification can probably be generalized
    # to any irrep dimension. There's a quick discussion of the Chern number of the lowest
    # band of a spin-N particle in Girvin & Yang's book, p. 340-341.
end


using Rootionals
using Test

R = Rootional

@testset "Init" begin
    @test R(1) == R([1], [1])
    @test R(0) == R([0], [1])

    @test R(3//5) == R([3, 5], [1, -1])

    @test one(R) == R(1)
    @test zero(R) == R(0)
end

@testset "Access" begin
    @test numerator(R([2], [1//2])) == R([2], [1//2])
    @test numerator(R([2], [-1//2])) == one(R)
    @test denominator(R([2], [1//2])) == one(R)
    @test denominator(R([2], [-1//2])) == R([2], [1//2])
end

@testset "Simplify" begin
    @test simplify!(R([3,3], [4, -1//2])) == R([3], [7//2])
    @test simplify!(R([2], [0])) == one(R)
    @test simplify!(R([7], [-0//2])) == one(R)
end

@testset "Conversions" begin
    @test one(R) == 1
    @test 5 == R(5)
    @test R(3//5) == 3//5
    @test 7//7 == one(R)

    @test float(R(2)) == 2.0
    @test float(R([2], [1//2])) ≈ sqrt(2)
    @test float(R([2], [-1//2])) ≈ 1/sqrt(2)
end

@testset "Arithmetic" begin
    @testset "*" begin
        @test R(2)*R(1//3) == R(2//3)
        @test R(2)*R(1//2) == one(R)
        @test R(2)*R([2], [1//2]) == R([2], [3//2])
        @test R(2)*R([2], [-1//2]) == R([2], [1//2])
        @test 2R([2], [-1//2]) == R([2], [1//2])
        @test R([2], [-1//2])*1//2 == R([2], [-3//2])
    end

    @testset "/" begin
        @test R(2)/R(2) == one(R)
        @test R([2], [1//2])/2 == R([2], [-1//2])
        @test 2/R([4, 5], [1, 1//2]) == R([2, 5], [-1, -1//2])
        @test 2/R(16) == R([2], [-3])
    end

    @testset "^" begin
        @test R([2], [1//2])^2 == 2
        @test (2R([2], [1//2]))^2 == 8
        @test R(4//3)^(1//2) == R([2, 3], [1, -1//2])
    end

    @testset "√" begin
        @test sqrt(R([2], [1//2])) == R([2], [1//4])
        @test cbrt(R([2], [1//2])) == R([2], [1//6])
    end
end

@testset "Strings" begin
    @test string(R([2], [1//2])) == "√2"
    @test string(R([2], [-1//2])) == "1/√2"
    @test string(R([3, 2, 6], [3//4, -2, -1//3])) == "(∜3)³/2²∛6"
    @test string(R([5], [17//8])) == "(⁸√5)¹⁷"
end


abstract AbstractMesh
abstract AbstractMesh1d <: AbstractMesh

typealias BCnames Vector{String}
"""
Stores an ordered 1D mesh
"""
type Mesh1d{T <: Real} <: AbstractMesh1d
    "Number of nodes"
    nb::Int
    "Number of elements"
    nel::Int
    "Coordinates of the nodes"
    x::Vector{T}
    "Index of neighbor elements"
    neigh::Array{Int,2}
    bcs::BCnames
end

function check_elem_order{T<:Real}(x::AbstractVector{T})
    n = length(x)

    for i = 2:n
        if x[i] <= x[i-1]
            return false
        end
    end
    return true
end

"""
Outer constructer for `Mesh1d` objects

It builds a mesh from the nodes

 * `x`: Vector with node positions
 * `bcs`: Vector with boundary condition tags

Alternatively, the mesh can be built from the end points
and number of elements:

 * `a`: Left endpoint of the 1d domain
 * `b`: Right endpoint of the 1d domain
 * `nel`: Number of elements that the domain will be divided into
 * `bcs`: Vector with boundary condition tags
"""
function Mesh1d{T<:Real}(x::AbstractVector{T}, bcs=[])
    nb::Int = length(x)
    if nb < 2
        throw(DimensionMismatch("There should be at least 2 nodes in the 1d mesh"))
    end

    nel = nb-1

    if !check_elem_order(x)
        throw(ArgumentError("Nodes should be sequential from smaller to larger values"))
    end

    y = [xx for xx in x]

    if length(bcs) > 2
        throw(DimensionMismatch("There should be at most one BC for each end of the domain"))
    end

    bcs2 = String[utf8(b) for b in bcs]

    neigh = zeros(Int, 2, nel)

    for e = 1:nel-1
        neigh[1,e] = e-1
        neigh[2,e] = e+1
    end
    neigh[1,nel] = nel-1
    neigh[2,nel] = 0

    return Mesh1d(nb, nel, y, neigh, bcs2)

end

function Mesh1d{T<:Real}(a::T, b::T, nel::Int, bcs=[])

    if a >= b
        throw(ArgumentError("Nodes should be sequential from smaller to larger values"))
    end

    nb = nel + 1

    x = linspace(a, b, nb)

    return Mesh1d(x, bcs)

end

"""
Stores information about an element.
"""
immutable mshElem1d{T<:Real}
    id::Int
    a::T
    b::T
    neigh::Tuple{Int,Int}
end

import Base.getindex
getindex(msh::Mesh1d, e::Int) = mshElem1d(e, msh.x[e], msh.x[e+1], (msh.neigh[1,e], msh.neigh[2,e]))


####
#### this is for extra drawings that are not meant to be overlaid with anything
#### else such as boxplot, heatmap, polarplot, pieplot
####

"""
    boxplot(...)

Erase previous drawings and add a boxplot. Missing values are allowed but not Infinities or Nans.
"""
function boxplot(ys...; axes=nothing, o...)
    axes = check_axes(axes)
    reset!(axes; parent=axes.parent) # always on fresh axes

    isempty(first(ys)) && throw(ArgumentError("Cannot display empty vectors."))

    # setting an empty struct first so that we can exploit the options
    # the actual data will be provided after analysis
    nobj = sum(size(y, 2) for y ∈ ys)
    bp = Boxplot(Matrix{Float64}(undef,0,0), nobj)
    set_properties!(bp; defer_preview=true, o...)

    # analyse data
    stats = Matrix{Float64}(undef, nobj, 6) # 1:wlow, 2:q25, 3:q50, 4:q75, 5:whigh, 6:mean
    outliers = Vector{Vector{Float64}}(undef, nobj)

    boxcounter = 1
    axmin = Inf
    axmax = -Inf

    for y ∈ ys
        for k ∈ Base.axes(y, 2)
            yk = collect(skipmissing(view(y, :, k)))
            if any(isnan, yk) || any(isinf, yk)
                throw(ArgumentError("Inf or NaN values not allowed in boxplot."))
            end

            q00, q25, q50, q75, q100 = quantile(yk, [.0, .25, .5, .75, 1.0])
            iqr = q75 - q25
            mu  = mean(yk)

            wrlength = bp.boxstyles[k].wrlength
            wlow  = q25 - wrlength * iqr
            whigh = q75 + wrlength * iqr
            if isinf(wrlength)
                wlow, whigh = q00, q100 # min/max values
            end

            stats[k, :] = [wlow, q25, q50, q75, whigh, mu]

            # outliers
            outliers[k] = filter(e->(e<wlow || whigh<e), yk)

            # Keep track of extremes to set axis limits afterwards
            axmin_ = min(q00, wlow)
            axmin_ -= 0.5abs(axmin_)
            axmin_ < axmin && (axmin = axmin_)
            axmax_ = max(q100, whigh)
            axmax_ += 0.5abs(axmax_)
            axmax_ > axmax && (axmax = axmax_)

            boxcounter += 1
        end
    end
    #
    bp.stats = stats
    push!(axes.drawings, bp)

    if bp.horiz # horizontal boxplot
        ylim(0, nobj+1)
        yticks(1:nobj)
        xlim(axmin, axmax)
        for k ∈ 1:nobj
            bp.boxstyles[k].oshow || continue
            nok = length(outliers[k])
            s = bp.boxstyles[k].omstyle # style of outliers
            if nok > 0
                scatter!(outliers[k], fill(k, nok); marker=s.marker, msize=s.msize, mcol=s.color)
            end
        end
    else # vertical boxplot
        xlim(0, nobj+1)
        xticks(1:nobj)
        ylim(axmin, axmax)
        for k ∈ 1:nobj
            bp.boxstyles[k].oshow || continue
            nok = length(outliers[k])
            s = bp.boxstyles[k].omstyle # style of outliers
            if nok > 0
                scatter!(fill(k, nok), outliers[k]; marker=s.marker, msize=s.msize, mcol=s.color)
            end
        end
    end
    return DrawingHandle(bp)
end

####
#### HEATMAP
####

"""
    heatmap_ticks(ax)

Returns ticks position centered appropriately for a heatmap.

### Example

```julia
heatmap(randn(2, 2))
xticks(heatmap_ticks("x"), ["var1", "var2"]; angle=45)
```
"""
function heatmap_ticks(ax::String; axes=gca())
    isempty(axes.drawings) && throw(ArgumentError("No heatmap found."))
    # it must necessarily be the first object since heatmap resets the axes
    axes.drawings[1] isa Heatmap || throw(ArgumentError("No heatmap found."))
    nrows, ncols = size(axes.drawings[1].data)
    ax_lc = lowercase(ax)
    ax_lc ∈ ["x", "x2"] && return (collect(0:(ncols-1)) .+ 0.5) ./ ncols
    ax_lc ∈ ["y", "y2"] && return (collect((nrows-1):-1:0) .+ 0.5) ./ nrows
    throw(ArgumentError("Unrecognised ax descriptor expected "*
                                               "one of [x, x2, y, y2]"))
end

"""
    heatmap(X; opts...)

Creates a heatmap corresponding to matrix `X`. Doing this with matrices larger than 100x100
with the GLE backend can be slow. As an indication, 500x500 takes about 15-20 seconds on a standard
laptop. Time scales with the number of elements (so 100x100 takes under a second).
You can provide ticks using `xticks(heatmap_ticks("x"), ["tick1", ...]; opts...)`.
See also [`heatmap_ticks`](@ref).
Note: handles missing values but not NaN or Inf.
"""
function heatmap(data::Matrix{<:CanMiss{Real}}; axes=nothing, o...)
    # TODO:
    #  - deal with tables instead of forcing matrices?
    # ================================================

    nrows, ncols = size(data)
    min(nrows, ncols) <= 1000 || throw(ArgumentError("The matrix is too large to be displayed " *
                                                     "as a heatmap."))

    axes = check_axes(axes)
    reset!(axes; parent=axes.parent) # always on fresh axes

    zmin, zmax = extrema(skipmissing(data))

    h = Heatmap(data=Matrix{Int}(undef, 0, 0), zmin=zmin, zmax=zmax)
    h.transpose = (nrows < ncols)
    set_properties!(h; defer_preview=true, o...)
    push!(axes.drawings, h)

    ncolors = length(h.cmap)

    minv, maxv = extrema(skipmissing(data))
    incr = (maxv - minv) / ncolors

    data2 = Matrix{Int}(undef, nrows, ncols)
    if iszero(incr)
        data2 .= ceil(ncols/2)
    else
        @inbounds for i=1:nrows, j=1:ncols
            dij = data[i,j]
            if ismissing(dij)
                data2[i,j] = ncolors + 1 # offset doesn't matter, anything ≥ 1 is fine
            else
                data2[i,j] = ceil(Integer, (dij - minv) / incr)
            end
        end
    end
    # NOTE: there may be a few 0 (min values), will be treated as 1 see add_sub_heatmap!

    # store the color assignments
    h.data = data2

    # set axis
    xlim(0,1); ylim(0,1)

    # add labels if provided to the heatmap object
    xplaces = heatmap_ticks("x")
    yplaces = heatmap_ticks("y")

    # used for sparse labelling in case ncols > 20
    stepx    = (ncols<=50)*10 + (50<ncols<=200)*50 + (200<ncols)*100
    lastx    = ncols - mod(ncols, stepx) + 1
    maskx    = collect(1:stepx:lastx) .- 1
    maskx[1] = 1
    stepy    = (nrows<=50)*10 + (50<nrows<=200)*50 + (200<nrows)*100
    lasty    = nrows - mod(nrows, stepy) + 1
    masky    = collect(1:stepy:lasty) .- 1
    masky[1] = 1

    if ncols <= 20
        xticks(xplaces, ["$i" for i ∈ 1:ncols]; axes=axes, fontsize=12)
    else
        xticks(xplaces[maskx], ["$i" for i ∈ maskx]; axes=axes, fontsize=12)
    end
    if nrows <= 20
        yticks(yplaces, ["$i" for i ∈ 1:nrows]; axes=axes, fontsize=12)
    else
        yticks(yplaces[masky], ["$i" for i ∈ masky]; axes=axes, fontsize=12)
    end
    x2ticks("off")
    y2ticks("off")
    return DrawingHandle(h)
end


include("../Packages/CatalecticantMatrices.jl")

### Create the model
Σ = Catalecticant(2, 3)

### Compute the ML-degree of Σ
ml_degree_witness(Σ; dual=true)
# MLDegreeWitness:
#  • ML degree → 36
#  • model dimension → 15
#  • dual → true


# Julia1_1.1.jl Why Learn Julia?
# running OS utilities using run() and read()
pwd()
run(`echo hello`)  # note (`) is a backtick`
files=String(read(`ls`))
files=split(files,"\n")
println(files)

# Compact
using LinearAlgebra
x = [1 2; 3 4]
y = x^2 .+ 3x .+ 1.1       # .+ corresponding component ops

eye=[1 0; 0 1.0] # unit matrix
z = eye/y                  # matrix inversion

# Notice compactness when working with matrices
# In this example several nested loops are needed in C
# the simple formula representation would also be lost.
# also notice implicit multiplication for constant

x=2.1+3.2im # complex floating-point number
# versatile polymorphic type system

# built in Int8-Int128, UInt8-UInt128, Float16-Float64
# other compound types made from these
# designed for linear algebra
# automatic conversions and promotions

# There is no meaningful concept of compile-time type,
# all types are actual at runtime.
# Only values, not variables have types.

# multicore parallel example, 1=head, 0=tail
# How many cores do we have>
Sys.CPU_THREADS

using Distributed
  heads = @distributed (+) for i=1:200000000
  Int(floor(rand()+0.5))
end
print(heads,"\n")

# Calling a c lib function
#using Distributed already called
t1=ccall((:clock, "libc"), UInt32,())
heads = @distributed (+) for i=1:200000000
  Int(floor(rand()+0.5))
end
print(heads,"\n")
t2=ccall((:clock, "libc"), UInt32,())
print((t2-t1)/1000000.," seconds\n")
# Don't need the C clock function since Julia has a
# @time macro and other timing operations.

# unicode characters in strings
s="abπ∑ef\n"
print(s)

# ok to use unicode characters in formulas
# generally Roman and Greek characters
ß=2π/3
µ=3+3.3im
Y=[1 2im; 3.3 6+4im]
Q = π*µ .+ ß*Y^2

# the C swap macro in Julia might be:
macro swap(a, b) # this will work for any type also mixed

    quote

        local temp=$(esc(a))

        $(esc(a))=$(esc(b))
        $(esc(b))=temp
    end
end
# Don't need swap macro
a,b=b,a

# metaprogramming machinery exposed to the user in Julia
# eval(), Expr(), parse(), Symbol(), typeof(), Regex()

# always check what Julia has to offer before rolling
# your own functions, macros and extensions.


using NormalHermiteSplines
using DoubleFloats
using Test

@testset "NormalHermiteSplines.jl" begin

include("1D.jl")
include("2D.jl")
include("3D.jl")

end


# License for this file: MIT (expat)
# Copyright 2017-2018, DLR Institute of System Dynamics and Control

"""
    module Simulate_PendulumODE

Simulate PendulumODE model.
"""
module Simulate_PendulumODE

include(joinpath("models", "PendulumODE.jl"))
import .PendulumODE
import ModiaMath

model  = PendulumODE.Model(L=0.8, m=0.5, d=0.2)
result = ModiaMath.simulate!(model, stopTime=5.0, log=true)

heading = "Simulate_PendulumODE.jl (ODE as index-0 DAE)"
ModiaMath.plot(result, [:phi, :w, :der_w], figure=1, heading=heading)
ModiaMath.plot(result, :ry, xAxis=:rx, figure=2, heading=heading)

end


function mm_spec_kj3_dimspec()::MPCCDimSpec
    dimspec = MPCCDimSpec(2, 1, 2, 0, 0, 1, 0)
    return dimspec
end

function mm_spec_kj3_defn(x, pr, ps)

    f = (x[1] - pr[1])^2 + (x[2] - pr[1])^2

    ce = Vector{Num}()

    ci = Vector{Num}()

    F = Matrix{Num}(undef, 2, 1)
    F[1,1] = x[1]
    F[2,1] = x[2]

    return MPCCDefinition(f, ce, ci, F)
end

function mm_spec_kj3_nzmask()::MPCCModelNZMask
    nz_mask = MPCCModelNZMask(
        # ce
        [],
        # ci
        [   Set{Int64}([1, 2]),
            Set{Int64}([1]) ],
        # F
        [
            # F1
            [   Set{Int64}([1]) ],
            # F2
            [   Set{Int64}([2]) ]
        ] )
    return nz_mask
end

function mm_spec_kj3_testvectors()
    return Vector{MPCCModelTestVector}(undef, 0)
end

function mm_spec_kj3_knownsols()

    function local_kj3_knownsol_1(pr::Vector{R}, ps::Vector{Int64}) where {R <: Real}
        @assert ( 1 == length(pr) && 0 == length(ps) )
        t = pr[1]

        if ( t <= 0 )
            x_sol = [0, 0]
            f_sol = 2(-t)^2
        else
            x_sol = [t, 0]
            f_sol = (x_sol[1] - t)^2 + (x_sol[2] - t)^2
        end

        return (x_sol, f_sol)
    end

    function local_kj3_knownsol_2(pr::Vector{R}, ps::Vector{Int64}) where {R <: Real}
        @assert ( 1 == length(pr) && 0 == length(ps) )
        t = pr[1]

        if ( t <= 0 )
            x_sol = [0, 0]
            f_sol = 2(-t)^2
        else
            x_sol = [0, t]
            f_sol = (x_sol[1] - t)^2 + (x_sol[2] - t)^2
        end

        return (x_sol, f_sol)
    end

    return [ local_kj3_knownsol_1, local_kj3_knownsol_2 ]
end

function mm_spec_kj3_parameterisations(t)
    defn1 = MPCCParameterisationDefn(
            Vector{Num}([ t ]),
            (-5.0, 5.0),
            "Standard"
        )

    return  Vector{MPCCParameterisationDefn}( [ defn1 ] )
end


using Random

# generate a random number between 0 and 1
println(rand())

# select a 'random' element from a collection
for i in 1:5
    println(rand([1,2,3,4,5,6]))
end

# populate a collection with random values - specify 'type' and the 'number' of
# elements you want
a1 = rand(UInt16, 5)
println(typeof(a1))
println(a1)

# generate a 'random' set of characters
s1 = "World"
println(randstring(s1)) # note - default length of the output is 8
println(randstring(s1, 4)) # set the length of the output string
println(randstring('a':'z', 6)) # give it a range ...

# shuffle a collection
a2 = [1,2,3,4,5,6,7]
Random.shuffle!(a2)
println(a2)

# use a seed function
Random.seed!(100)
println(rand())
println(rand())


"""
    parse_nested(s::AbstractString (, iy = []))

return a contraction-tree consisting of `NestedEinsum`
objects as nodes and `IndexGroup` as leaves.
`s` should only contain the `ixs` part
"""

function parse_nested(s::AbstractString, iy = [])
    count(==('('),s) == count(==(')'),s) || throw(
        ArgumentError("Parentheses don't pair up in $s"))

    _, out = parse_parens(s, firstindex(s), 1)
    append!(out.iy, iy)
    filliys!(out)
    return stabilize(out)
end

"""
    parse_parens(s::AbstractString, i, narg)

parse one level of parens starting at index `i` where `narg` counts which tensor the
current group of indices, e.g. "ijk", belongs to.
Recursively calls itself for each new opening paren that's opened.
"""
function parse_parens(s::AbstractString, i, narg)
    out = NestedEinsum{Char}([], [], [])
    g = IndexGroup{Char}([],narg)

    while i <= lastindex(s)
        c = s[i]
        j = nextind(s,i)
        if c === '('
            # opening a parens means that next a new contraction is parsed
            j, out2, narg = parse_parens(s, j, narg)
            push!(out, out2)
            union!(out.inds, out2.inds)
        elseif c === ')' || c === ','
            # either closing a parens or a comma means that a tensors indices
            # are complete and can be added to the contraction-object
            if !isempty(g)
                push!(out, g)
                union!(out.inds, g.inds)
            end
            if  c === ','
                # comma implies that a new tensor is parsed for the current contraction
                narg += 1
                g = IndexGroup{Char}([], narg)
            else
                # parens implies that the current contraction is complete
                return j, out, narg
            end
        elseif isletter(c) # could allow isnumeric() too
            push!(g,c)
        else
            throw(ArgumentError("parsing $s failed, $c is not a valid entry"))
        end
        i = j
    end
    if !isempty(g)
        push!(out, g)
        union!(out.inds, g.inds)
    end
    return i, out
end

"""
    filliys!(neinsum::NestedEinsum)

goes through all `NestedEinsum` objects in the tree and saves the correct `iy` in them.
"""
function filliys!(neinsum)
    iy = neinsum.iy
    args = neinsum.args
    for i in 1:length(neinsum.args)
        arg = args[i]
        arg isa IndexGroup && continue
        union!(arg.iy, intersect(arg.inds, iy))
        for j in 1:length(neinsum.args)
            i === j && continue
            union!(arg.iy, intersect(arg.inds, args[j].inds))
        end
        filliys!(arg)
    end
    return neinsum
end

"""
    IndexGroup

Leaf in a contractiontree, contains the indices and the number of the tensor it
describes, e.g. in "ij,jk -> ik", indices "ik" belong to tensor `1`, so
would be described by IndexGroup(['i','k'], 1).
"""
struct IndexGroup{T}
    inds::Vector{T}
    n::Int
end

Base.push!(ig::IndexGroup, c) = (push!(ig.inds,c); ig)
Base.isempty(ig::IndexGroup) = isempty(ig.inds)

"""
    NestedEinsum

describes a (potentially) nested einsum. Important fields:
- `args`, vector of all inputs, either `IndexGroup` objects corresponding to tensors or `NestedEinsum`
- `iy`, indices of output
"""
struct NestedEinsum{T}
    args::Vector{Union{NestedEinsum{T}, IndexGroup{T}}}
    inds::Vector{T}
    iy::Vector{T}
end

Base.push!(neinsum::NestedEinsum, x) = (push!(neinsum.args,x); neinsum)

using MacroTools

function _nested_ein_macro(ex; einsum=:einsum)
    @capture(ex, (left_ := right_)) || throw(ArgumentError("expected A[] := B[]... "))
    @capture(left, Z_[leftind__] | [leftind__] ) || throw(
        ArgumentError("can't understand LHS, expected A[i,j] etc."))
    Z === nothing && @gensym Z
    primefix!(leftind)
    allinds = unique(leftind)

    MacroTools.postwalk(right) do x
        @capture(x, A_[inds__]) && union!(allinds, inds)
        x
    end
    primefix!(allinds)

    tensors = Symbol[]
    nein = parse_nested_expr(right, tensors, allinds)
    append!(nein.iy, indexin(leftind,allinds))
    filliys!(nein)
    snein = stabilize(nein)

    tensornames = map(esc, tensors)
    :($(esc(Z)) = $snein(($(tensornames...),)...))
end

function parse_nested_expr(expr, tensors, allinds)
    if @capture(expr, *(args__))
        einargs = map(x -> parse_nested_expr(x,tensors, allinds), args)
        intinds = union(mapreduce(x -> x.inds, vcat, einargs))
        return NestedEinsum{Int}(einargs, intinds, Int[])
    elseif @capture(expr, A_[inds__])
        push!(tensors,A)
        return IndexGroup{Int}(indexin(primefix!(inds), allinds), length(tensors))
    end
end

"""
apply a NestedEinsum to arguments evaluates the nested einsum
"""
function (neinsum::NestedEinsum)(xs...; size_dict = nothing)
    ixs = Tuple(map(extractixs, neinsum.args))
    iy = Tuple(neinsum.iy)
    EinCode(ixs,iy)(map(arg -> extractxs(xs, arg), neinsum.args)...)
end

"""
extract the indices of the tensor that is associated with x (if x isa IndexGroup)
or results from x (if x isa NestedEinsum)
"""
extractixs(x::IndexGroup) = Tuple(x.inds)
extractixs(x::NestedEinsum) = Tuple(x.iy)

"""
extract the tensor associated with x (if x isa IndexGroup)
or evaluate and return the tensor associated with x (if x isa NestedEinsum)
"""
extractxs(xs, x::NestedEinsum) = x(xs...)
extractxs(xs, x::IndexGroup) = xs[x.n]

struct NestedEinsumStable{T,S,N}
    args::S
    eins::T
end

@doc raw"
    stabilize(nein::NestedEinsum)

turn the nested einsum into a `NestedEinsumStable` which is type-stable and
more performant.
"
function stabilize(nein::OMEinsum.NestedEinsum)
    ixs = Tuple(map(OMEinsum.extractixs, nein.args))
    iy = Tuple(nein.iy)
    eins = EinCode{ixs,iy}()
    args = Tuple(map(x -> x isa OMEinsum.NestedEinsum ? stabilize(x) : x.n,nein.args))
    return NestedEinsumStable{typeof(eins), typeof(args), length(iy)}(args, eins)
end

function (neinsum::NestedEinsumStable{<:Any,<:Any,N})(xs...; size_dict = nothing) where N
    mxs = map(x -> extractxs(xs, x), neinsum.args)
    neinsum.eins(mxs...)
end

extractxs(xs, x::NestedEinsumStable) = x(xs...)
extractxs(xs, i::Int) = xs[i]

function match_rule(code::NestedEinsumStable)
    return (match_rule(code.eins), match_rule.(code.args))
end

match_rule(code::Int64) = code


@testset "Testing ecdf.jl" begin
    # set up

    obs = rand(40)
    e = ECDF(obs)

    # 1.1 is larger than all obs, so ecdf should be 1
    @test ecdf(e, 1.1) ≈ 1.0

    # -1.0 is small than all obs, so ecdf should be 0
    @test ecdf(e, -1.0) ≈ 0.0

    # larger values should have larger values on ecdf
    let x = rand()
        F_1 = ecdf(e, x)
        F_2 = ecdf(e, x*1.1)
        @test F_1 <= F_2
    end

end  # testset


using FFTW, LinearAlgebra

export MicMac, solve

mutable struct MicMac

    data      :: DataSet
    ntau      :: Int64
    ktau      :: Vector{Float64}
    matr      :: Array{ComplexF64,2}
    conjmatr  :: Array{ComplexF64,2}
    A1        :: Vector{Float64}
    A2        :: Vector{Float64}
    sigma     :: Int64
    llambda   :: Int64
    epsilon   :: Float64
    u         :: Array{ComplexF64,1}
    v         :: Array{ComplexF64,1}
    ut        :: Array{ComplexF64,2}
    vt        :: Array{ComplexF64,2}
    z         :: Array{ComplexF64,2}

    function MicMac( data :: DataSet, ntau :: Int64 )

        nx        = data.nx
        T         = data.T
        kx        = data.kx

        epsilon   = data.epsilon

        llambda   = data.llambda
        sigma     = data.sigma

        tau       = zeros(Float64, ntau)
        tau      .= T * collect(0:ntau-1) / ntau
        ktau      = similar(tau)
        ktau     .= 2π / T * vcat(0:ntau÷2-1,-ntau÷2:-1)
        ktau[1]   = 1.0

        matr      = zeros(ComplexF64,(1,ntau))
        conjmatr  = zeros(ComplexF64,(1,ntau))
        matr     .= transpose(exp.( 1im * tau))
        conjmatr .= transpose(exp.(-1im * tau))

        A1 = zeros(Float64, nx)
        A2 = zeros(Float64, nx)

        if epsilon > 0
            A1 .= (sqrt.(1 .+ epsilon * kx .^2) .- 1) / epsilon
            A2 .= (1 .+ epsilon * kx .^2) .^ (-1/2)
        else
            A1 .= 0.5 * kx .^ 2
            A2 .= 1.0
        end

        u  = zeros(ComplexF64,nx)
        v  = zeros(ComplexF64,nx)
        ut = zeros(ComplexF64,(nx, ntau))
        vt = zeros(ComplexF64,(nx, ntau))
        z  = zeros(ComplexF64,(nx, ntau))

        new( data, ntau, ktau, matr, conjmatr, A1 , A2,
             sigma, llambda, epsilon, u, v, ut, vt, z)

    end

end

include("ftau.jl")
include("C1.jl")
include("dtftau.jl")
include("duftau.jl")
include("init_2.jl")
include("champs_2.jl")

function reconstr(u, t, T, ntau)

    v    = fft(u,1)
    w    = vcat(0:ntau÷2-1, -ntau÷2:-1)
    v  .*= exp.(1im * 2π / T * w * t)

    vec(sum(v, dims=1) / ntau)

end

function solve(self, dt)

    Tfinal = self.data.Tfinal

    T  = self.data.T
    kx = self.data.kx

    epsilon = self.data.epsilon

    nx   = self.data.nx
    ntau = self.ntau

    t = 0.0
    iter = 0

    fft_u = copy(self.data.u)
    fft_v = copy(self.data.v)

    fft!(fft_u,1)
    fft!(fft_v,1)

    fft_ubar = copy(fft_u)
    fft_vbar = copy(fft_v)

    fft_ug = copy(fft_u)
    fft_vg = copy(fft_v)

    ichampgu = zeros(ComplexF64,(ntau,nx))
    ichampgv = zeros(ComplexF64,(ntau,nx))

    init_2!(ichampgu, ichampgv, self, t, fft_ubar, fft_vbar, fft_ug, fft_vg )

    champubaru = similar(fft_ubar)
    champubarv = similar(fft_vbar)
    champmoyu  = similar(fft_ug)
    champmoyv  = similar(fft_vg)

    while t < Tfinal

        iter = iter + 1
        dt   = min(Tfinal-t, dt)
        hdt  = dt / 2

        champubaru .= fft_ubar
        champubarv .= fft_vbar
        champmoyu  .= fft_ug
        champmoyv  .= fft_vg

        champs_2!(ichampgu, ichampgv,
                  self, t,
                  champubaru, champubarv,
                  champmoyu, champmoyv )

        champubaru .*= hdt
        champubaru .+= fft_ubar
        champubarv .*= hdt
        champubarv .+= fft_vbar

        champmoyu  .*= hdt
        champmoyu  .+= fft_ug
        champmoyu  .+= epsilon .* reconstr(ichampgu, (t+hdt) / epsilon, T, ntau)
        champmoyu  .-= epsilon .* reconstr(ichampgu,  t      / epsilon, T, ntau)

        champmoyv  .*= hdt
        champmoyv  .+= fft_vg
        champmoyv  .+= epsilon .* reconstr(ichampgv, (t+hdt) / epsilon, T, ntau)
        champmoyv  .-= epsilon .* reconstr(ichampgv,  t      / epsilon, T, ntau)

        champs_2!(ichampgu, ichampgv,
                  self, t + hdt,
                  champubaru, champubarv,
                  champmoyu, champmoyv )

        fft_ubar .+= dt .* champubaru
        fft_vbar .+= dt .* champubarv

        fft_ug .+= epsilon .* reconstr(ichampgu, (t + dt) / epsilon, T, ntau)
        fft_ug .-= epsilon .* reconstr(ichampgu,  t       / epsilon, T, ntau)
        fft_ug .+= dt .* champmoyu

        fft_vg .+= epsilon .* reconstr(ichampgv, (t + dt) / epsilon, T, ntau)
        fft_vg .-= epsilon .* reconstr(ichampgv,  t       / epsilon, T, ntau)
        fft_vg .+= dt .* champmoyv

        t = t + dt

        C1!(ichampgu, ichampgv, self, t, fft_ubar, fft_vbar)

        fft_u .= reconstr(ichampgu, t / epsilon, T, ntau)
        fft_v .= reconstr(ichampgv, t / epsilon, T, ntau)

        fft_u .+= fft_ug
        fft_v .+= fft_vg

    end

    fft_u .*= exp.(1im .* sqrt.(1 .+ epsilon * kx .^ 2) .* t / epsilon)
    fft_v .*= exp.(1im .* sqrt.(1 .+ epsilon * kx .^ 2) .* t / epsilon)

    ifft!(fft_u,1)
    ifft!(fft_v,1)

    fft_u, fft_v

end