#=
RobustMVO
=========
Robust Mean-Variance Optimization Model

Author: Azamat Berdyshev
Date: 02/03/2016
=#

type RobustMVO{R<:Real, S<:AbstractString} <: AbstractModel
    sense::Symbol
    vars::Vector{Expr}
    _objective::Expr
    _default_constraints::Vector{Expr}
    constraints::Dict{Symbol,Expr}
    assets::AssetsCollection{R,S}
    objVal::R
    weights::Vector{R}
    status::Symbol

    # Inner constructor
    function RobustMVO(assets::AssetsCollection{R, S},
                        r_min::R,
                        constraints::Dict{Symbol,Expr},
                        uncertaintySet::Matrix{R},
                        uncertaintySetSize::R,
                        short_sale::Bool)

        n = length(assets)
        Σ = getCovariance(assets)
        μ = getReturns(assets)
        Θ = uncertaintySet
        ϵ = uncertaintySetSize

        # if no short sale => add corresponding constraint
        if short_sale
            vars = [:(w[1:$n])]
        else
            vars = [:(w[1:$n] >= 0)]
        end

        _objective = :(dot(w,$Σ*w))

        _default_constraints = [:(dot($μ,w) ≥ $r_min),
                                :(dot(ones($n),w) == 1),
                                :(dot(w,$Θ*w) ≤ $ϵ)]

        new(:Min,
            vars,
            _objective,
            _default_constraints,
            constraints,
            assets,
            NaN,
            fill(NaN,n),
            :Unsolved)
    end
end

# Outer constructor
RobustMVO{R<:Real, S<:AbstractString}(
            assets::AssetsCollection{R, S},
            r_min::R,
            constraints=Dict{Symbol,Expr}()::Dict{Symbol,Expr};
            uncertaintySet=diagm(diag(getCovariance(assets)))::Matrix{R},
            uncertaintySetSize=(dot(ones(length(assets)), uncertaintySet*ones(length(assets)))/length(assets)^2)::R,
            short_sale=false::Bool) = RobustMVO{R, S}(
                                                assets,
                                                r_min,
                                                constraints,
                                                uncertaintySet,
                                                uncertaintySetSize,
                                                short_sale)


@testset "Tajima's D" begin
    n = (10, 77, 72)
    a1 = (2.828968, 4.914514, 4.846921)
    a2 = (1.539768, 1.631862, 1.630948)
    b1 = (0.407407, 0.342105, 0.342723)
    b2 = (0.279012, 0.228184, 0.228612)
    c1 = (0.053922, 0.138626, 0.136406)
    c2 = (0.047227, 0.086985, 0.085989)
    e1 = (0.019061, 0.028208, 0.028143)
    e2 = (0.004949, 0.003374, 0.003423)
    π = (3.888889, 8.438483, 15.339984)
    S = (16, 103, 88)
    D = (-1.446172, -2.021749, -0.525801)

    for i in eachindex(n)
        @test NaturalSelection.td_a1(n[i]) ≈ a1[i] atol=10e-5
        @test NaturalSelection.td_a2(n[i]) ≈ a2[i] atol=10e-5
        @test NaturalSelection.td_b1(n[i]) ≈ b1[i] atol=10e-5
        @test NaturalSelection.td_b2(n[i]) ≈ b2[i] atol=10e-5
        @test NaturalSelection.td_c1(a1[i], b1[i]) ≈ c1[i] atol=10e-5
        @test NaturalSelection.td_c2(n[i], a1[i], a2[i], b2[i]) ≈ c2[i] atol=10e-5
        @test NaturalSelection.td_e1(a1[i], c1[i]) ≈ e1[i] atol=10e-5
        @test NaturalSelection.td_e2(a1[i], a2[i], c2[i]) ≈ e2[i] atol=10e-5
        @test tajimad(π[i], S[i], n[i]) ≈ D[i] atol=10e-5
    end

    testpop = [dna"ATAATAAAAAAATAATAAAAAAATAAAAAAAATAAAAAAAA",
               dna"AAAAAAAATAAATAATAAAAAAATAAAAAAAAAAAAAAAAA",
               dna"AAAATAAAAATATAATAAAAAAATATAAAAAAAAAAAAAAA",
               dna"AAAAAAAAAAAATAATAAAAAAATAAATAAATAAAAAAAAA",
               dna"AAAATAAAAAAAATATAAAAAAATAAAAAAAAAAAAAAAAA",
               dna"AAAATAAAAAAAAAATAAAAAAAAAAAAAAAAAAATAAAAA",
               dna"AAAAAATAAAAATAATAAAAAAATAAAAAAAAAAAAAAAAA",
               dna"AAAAAAAAAAAAAAATAAAAAAATAAAAAAAAAAAAAAATA",
               dna"AAAAAAAAAAAAAAAAAAAAAAATAAAAAAAAAAAAAAAAA",
               dna"AAAAAAAAAAAAAAATAAAAAAATAATAAAAAAAAAAAAAA"]

    @test tajimad(testpop) ≈ -1.446172 atol=10e-5
end


for f in (:real, :imag, :conj)
    @eval ($f)(a::STaylor1{N,T}) where {N,T<:Number} = STaylor1{N,T}(($f).(a.coeffs))
end

adjoint(a::STaylor1) = conj(a)
isinf(a::STaylor1) = any(isinf.(a.coeffs))
isnan(a::STaylor1) = any(isnan.(a.coeffs))

function abs(a::STaylor1{N,T}) where {N,T<:Real}
    if a[0] > zero(T)
        return a
    elseif a[0] < zero(T)
        return -a
    else
        throw(ArgumentError(
        """The 0th order Taylor1 coefficient must be non-zero
        (abs(x) is not differentiable at x=0)."""))
    end
end

abs2(a::STaylor1{N,T}) where {N,T<:Real} = a^2

deg2rad(z::STaylor1{N, T}) where {N, T<:AbstractFloat} = z * (convert(T, pi) / 180)
deg2rad(z::STaylor1{N, T}) where {N, T<:Real} = z * (convert(float(T), pi) / 180)

rad2deg(z::STaylor1{N, T}) where {N, T<:AbstractFloat} = z * (180 / convert(T, pi))
rad2deg(z::STaylor1{N, T}) where {N, T<:Real} = z * (180 / convert(float(T), pi))

function mod(a::STaylor1{N,T}, x::T) where {N, T<:Real}
    return STaylor1{N,T}(ntuple(i -> i == 1 ? mod(constant_term(a), x) : a.coeffs[i], Val(N)))
end

function mod(a::STaylor1{N,T}, x::S) where {N, T<:Real, S<:Real}
    R = promote_type(T, S)
    a = convert(STaylor1{N,R}, a)
    return mod(a, convert(R, x))
end

function rem(a::STaylor1{N,T}, x::T) where {N, T<:Real}
    return STaylor1{N,T}(ntuple(i -> i == 1 ? rem(constant_term(a), x) : a.coeffs[i], Val(N)))
end

function rem(a::STaylor1{N,T}, x::S) where {N, T<:Real, S<:Real}
    R = promote_type(T, S)
    a = convert(STaylor1{N,R}, a)
    return rem(a, convert(R, x))
end

function mod2pi(a::STaylor1{N,T}) where {N, T<:Real}
    return STaylor1{N,T}(ntuple(i -> i == 1 ? mod2pi(constant_term(a)) : a.coeffs[i], Val(N)))
end


### A Pluto.jl notebook ###
# v0.15.1

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
        el
    end
end

# ╔═╡ 0bc12fe2-4d08-4840-b651-b58df23f0277
begin
	using Pkg;
	Pkg.activate(".")
	using IntervalLinearAlgebra, LazySets, Plots, Images, StaticArrays, PlutoUI
end

# ╔═╡ f403b050-e2e6-11eb-1c44-911de0d748bd
html"<button onclick='present()'>present</button>"

# ╔═╡ c9e05bd9-1d7b-41c0-99dc-29b1c1e20090
html"""

IntervalLinearAlgebra.jl

Linear algebra done rigorously

Luca Ferranti, David Sanders, Marcelo Forets

Google Summer of Code (GSoC) 2021

Presentation for JuliaCon 2021

"""

# ╔═╡ c3a5fd72-9a9d-45f3-aaf3-bd61637d286c
md"""
## Interval matrices

An interval matrix $\mathbf{A}\in\mathbb{I}\mathbb{R}^{m\times n}$ is defined as

$\mathbf{A} = \{A \in \mathbb{R}^{m\times n} | A_{ij}\in\mathbf{A}_{ij}\quad i=1,\ldots,m\quad j=1\ldots,n\}$

for example
"""

# ╔═╡ f2cff36e-9136-4eb1-acff-c97af31c2450
AA = [1..2 3..4; 5..6 7..8]

# ╔═╡ 286f40a2-840d-431b-a531-fe9e09dc55f1
md"""
##

 $\mathbf{A}$ can also be represented in *midpoint-radius* notation $A_c\pm A_\Delta$, where $A_c$ is the *midpoint matrix* and $A_\Delta$ is the *radius matrix*.
"""

# ╔═╡ 97e498f5-2e0c-4139-9774-86f58d59d511
AA

# ╔═╡ bb878728-d4ca-4c01-a4f7-bf1811a8f5c2
mid.(AA)

# ╔═╡ a89ee6e9-565a-46d5-82db-bafb251a2200
radius.(AA)

# ╔═╡ 4848561e-2336-40fb-a5c6-8705da85c2ee
md"""
##

### Regular matrices
We say that an interval matrix $\mathbf{A}$ is **regular** if all $A\in\mathbf{A}$ are invertible. Otherwise, the interval matrix is **singular**.

- In general, checking for regularity or singularity is computationally expensive.
"""

# ╔═╡ 5b111f71-4d41-43cf-a3cf-e2a6f4c2adba
html"

    real
    interval
	check property

    invertible
    regular
	coNP-complete

    singular
    singular
	NP-complete

"

# ╔═╡ 395b75e5-e76f-4f3a-b51f-57eaa3de2d4e
md"""
## Interval linear systems

We are now ready to study the square interval linear system $\mathbf{Ax}=\mathbf{b}$ with $\mathbf{A}\in\mathbb{IR}^{n\times n}$ and $\mathbf{b}\in\mathbb{IR}^n$.
"""

# ╔═╡ f39f94f7-59ff-4dd0-93dd-4f9f158c673c
md"""
The solution set $\mathbf{x}$ is defined as

$\mathbf{x}=\{x\in\mathbb{R}^n | Ax=b\text{ for some } A\in\mathbf{A}, b\in\mathbf{b}\}$
"""

# ╔═╡ 3f5a6fed-6175-4459-8be2-9a330cedc938
md"""
- If $\mathbf{A}$ is regular, then the solution set is non-empty and bounded.
"""

# ╔═╡ c223f95a-9885-48ae-b939-4f3fa54a7987
md"""
##
### Finding the solution set

- How do we solve the square interval linear system $\mathbf{A}\mathbf{x}=\mathbf{b}$?
- A naive approach might be to use Monte Carlo, i.e. generate several random real instances from the interval system.
- We will use the following as running example
"""

# ╔═╡ 32f0ff22-d42f-410f-a1a6-b8ea77b696ff
A = [2..4 -2..1;-1..2 2..4]

# ╔═╡ a79e27c9-399c-44e1-ac79-f65480fe5178
b = [-2..2, -2..2]

# ╔═╡ ba3a1ca6-04dc-45db-b686-a036519f2bc9
md"""
##
"""

# ╔═╡ 38883c77-d506-4e81-9d25-0ac78323241a
begin
	Ns = 100_000
	Astatic = @SMatrix [2..4 -2..1;-1..2 2..4]
	bstatic = @SVector [-2..2, -2..2]
	xs = [rand.(A)\rand.(b) for _ in 1:Ns]
	x = [xs[i][1] for i in 1:Ns]
	y = [xs[i][2] for i in 1:Ns]
	histogram2d(x, y, ratio=1)
end

# ╔═╡ 4549ca50-615c-4fba-afde-303ad231f648
md"""
- Pictures obtained by uniformly samplying 100_000 times each interval.
- **Did we cover the whole set?**
"""

# ╔═╡ 0f196850-f4b1-4830-9198-d72a281da05c
md"""
## Solution set characterization

An important theorem, known as **Oettli-Präger theorem**, helps us describing the solution set $\mathbf{x}$.

###### Oettli-Präger theorem
Given the square interval linear system $\mathbf{Ax}=\mathbf{b}$, then

$
x\in\mathbf{x}\Leftrightarrow|A_cx-b_c|\le A_\Delta|x|+b_\Delta$

- The absolute value is taken elementwise.

- We can remove the absolute values by considering one orthant at the time, obtaining $2^n$ systems of linear inequalities.

- Alternatively, the non-linear inequalities can be solved directly using `IntervalConstraintProgramming.jl`

- Both impractical for higher dimensions!
"""

# ╔═╡ d5e3856d-07e7-4e50-9e4d-c0afb7091d8b
md"""
##
"""

# ╔═╡ 95edbbbf-2e35-446d-9eeb-a53f5c6b10ec
polytopes = solve(A, b, LinearOettliPrager())

# ╔═╡ 4880c8a6-de84-4e55-be90-f73256b0888a
begin
	plot(polytopes, ratio=1)
	histogram2d!(x, y)
end

# ╔═╡ 75a66d2a-01b9-4d6a-8fed-8d8159ba6354
md"""
##
### A 3D example
Below the solution of the interval system with

$
\begin{bmatrix}
[4.5, 4.5]&[0, 2]&[0, 2]\\
[0, 2]&[4.5, 4.5]&[0, 2]\\
[0, 2]&[0, 2]& [4.5, 4.5]
\end{bmatrix}\mathbf{x}=\begin{bmatrix}[-1, 1]\\
[-1, 1]\\
[-1, 1]\end{bmatrix}$

"""

# ╔═╡ cd558a80-2825-4953-870c-93551f60c10e
load("./3dexample.png")

# ╔═╡ c5aa282c-c82b-4521-994d-235cb7934c83
md"""
##
### Exact solution, conclusions
* In general, the solution set of an interval linear system is a non-convex polytope.
  - however it is convex in each orthant.
* The computational complexity to find the solution set grows exponentially with the dimension,
* An alternative more feasible approach is to find a *tight enclosure* of the solution.
"""

# ╔═╡ 32481e35-1a98-4b65-b925-264d51d24743
md"""
## Enclosures of interval linear systems

- We say that an interval box $\mathbf{\Sigma}$ is an enclosure of the set $\mathbf{x}$ if $\mathbf{x}\subseteq\mathbf{\Sigma}$

- Ideally, we want the *hull* of the solution $\mathbf{\Sigma}_{H}$, that is the tightest interval box.

- However, finding the exact hull is in general NP-hard
"""

# ╔═╡ 58b8b1b8-93e7-46c0-b0a0-28532b152908
begin
	plot(interval_hull(ConvexHullArray(polytopes)), label="hull", α=0.3)
	plot!(polytopes, ratio=1, α=1)
end

# ╔═╡ faa03639-a431-4cbf-ac03-bbb33fb7b5b8
md"""
## Algorithms to find the enclosure of the system

`InteralLinearAlgebra.jl` has several algorithms to compute an enclosure of $\mathbf{Ax}=\mathbf{b}$ and an user friendly interface to choose what algorithm and precondition mechanisms to use.

#### Implemented algorithms
- Gaussian elimination
- Gauss-Seidel
- Jacobi
- Hansen-Bliek-Rohn
- Krawczyk
"""

# ╔═╡ 5e0cf15b-21ab-45bb-81fa-ee962a46004f
Xge = solve(A, b, GaussianElimination(), NoPrecondition())

# ╔═╡ fc08d410-02fa-480b-a999-841476dfaaf6
md"""
##
"""

# ╔═╡ 57a5fb2a-5cb2-412f-a1a0-5d27e0a5b8f7
begin
	plot(interval_hull(ConvexHullArray(polytopes)), label="hull", α=0.3)
	plot!(IntervalBox(Xge), label="Gaussian elimination", α=0.2, legend=:right)
	plot!(polytopes, ratio=1, α=1)
end

# ╔═╡ a70a6137-88cf-4fd5-ad50-8d76a1379a27
md"""
- For some special cases, the algorithm will return the hull, but in general some overestimation will occur.
"""

# ╔═╡ 0308378a-2d36-4c47-add7-0f53dc9011ca
md"""
## Preconditioning

- For the previous algorithms to work and be numerically stable, there are some requirements on the matrix $\mathbf{A}$.
- If those requirements are not met, one can try to precondition the problem with a *real* matrix $C$. that is apply the algorithms to the linear system

$
C\mathbf{Ax}=C\mathbf{b}$
- A particularly good choice is $C\approx A_c^{-1}$
"""

# ╔═╡ 9dba04bd-a8c3-44da-a1ab-855829c824ae
md"""
##
To understand the need for preconditioning, let us consider the following example.
"""

# ╔═╡ 8f68a5f0-4ec9-40d5-89be-cddf0c34a977
@bind N Slider(2:100; show_value=true, default=5)

# ╔═╡ 22abccab-5c93-4aa4-90e4-8209997a9a86
A1 = tril(fill(1..1, N, N))

# ╔═╡ e77a478d-0270-4120-a2d4-36efc455fb2e
b1 = [-2..2, fill(0..0, N-1)...]

# ╔═╡ 1ec87e5e-2f90-490b-a94f-05ea53d4cb1d
md"""
The correct solution is $[[-2, 2], [-2, 2], [0, 0], [0, 0], \ldots]$
"""

# ╔═╡ de8734a4-46b8-45ba-aff1-fa6a73e85d96
md"""
##
"""

# ╔═╡ e3bee176-9b9a-448f-b2d0-fa53cad35555
solve(A1, b1, GaussianElimination(), NoPrecondition())

# ╔═╡ 495e13e3-bf20-4541-ab6c-f6cb9572e9ed
solve(A1, b1, HansenBliekRohn(), NoPrecondition())

# ╔═╡ f223ac43-79cd-43be-b585-907a0bfb8f27
md"""
##
"""

# ╔═╡ acb54294-917d-41b0-b501-364b9228531b
solve(A1, b1, GaussianElimination(), InverseMidpoint())

# ╔═╡ ea231b9c-4cc9-4d45-b171-4c66536b02a1
solve(A1, b1, HansenBliekRohn(), InverseMidpoint())

# ╔═╡ 44813e6f-d7a2-47d6-9862-b9fd3e102ee2
md"""
##
### Downsides of preconditioning

- In general, the solution set of the preconditioned problem is **not** the solution set of of the original problem.
- Let us consider our previous 2D example.
"""

# ╔═╡ 1a1b8edf-bc1a-466e-b714-3eb71aa145ce
begin
	polytopes2 = solve(A, b, LinearOettliPrager(), InverseMidpoint())
	plot(UnionSetArray(polytopes2), ratio=1, label="preconditioned", legend=:right)
	plot!(UnionSetArray(polytopes), label="original", α=1)
end

# ╔═╡ d8735a5e-8444-4724-9796-a59b37efd0e2
md"""
##
### Take-home lesson
- In general preconditioning is needed to achieve numerical stability.
- This may however enlarge the solution set.
- If preconditioning is not specified, the package performs some heuristic checks to decide a precondition strategy.
"""

# ╔═╡ bbd4086e-af3f-4743-b9fb-01559f567560
solve(A1, b1, GaussianElimination())

# ╔═╡ 1e9bf340-2dca-4602-a60c-b2ccdc9cfb85
md"""
## Conclusions

- [IntervalLinearAlgebra.jl](https://github.com/lucaferranti/IntervalLinearAlgebra.jl) offers (will offer) a toolbox to deal with interval linear systems.
- New features (possibly!) coming:
  - Parametric interval systems
  - Eigenvalues computation
  - Determinant computation

- Ultimate goal: **Linear algebra done rigorously!**
- Download the slides: <https://github.com/lucaferranti/ILAjuliacon2021>
"""

# ╔═╡ Cell order:
# ╟─f403b050-e2e6-11eb-1c44-911de0d748bd
# ╟─0bc12fe2-4d08-4840-b651-b58df23f0277
# ╟─c9e05bd9-1d7b-41c0-99dc-29b1c1e20090
# ╟─c3a5fd72-9a9d-45f3-aaf3-bd61637d286c
# ╟─f2cff36e-9136-4eb1-acff-c97af31c2450
# ╟─286f40a2-840d-431b-a531-fe9e09dc55f1
# ╟─97e498f5-2e0c-4139-9774-86f58d59d511
# ╠═bb878728-d4ca-4c01-a4f7-bf1811a8f5c2
# ╠═a89ee6e9-565a-46d5-82db-bafb251a2200
# ╟─4848561e-2336-40fb-a5c6-8705da85c2ee
# ╟─5b111f71-4d41-43cf-a3cf-e2a6f4c2adba
# ╟─395b75e5-e76f-4f3a-b51f-57eaa3de2d4e
# ╟─f39f94f7-59ff-4dd0-93dd-4f9f158c673c
# ╟─3f5a6fed-6175-4459-8be2-9a330cedc938
# ╟─c223f95a-9885-48ae-b939-4f3fa54a7987
# ╟─32f0ff22-d42f-410f-a1a6-b8ea77b696ff
# ╟─a79e27c9-399c-44e1-ac79-f65480fe5178
# ╟─ba3a1ca6-04dc-45db-b686-a036519f2bc9
# ╟─38883c77-d506-4e81-9d25-0ac78323241a
# ╟─4549ca50-615c-4fba-afde-303ad231f648
# ╟─0f196850-f4b1-4830-9198-d72a281da05c
# ╟─d5e3856d-07e7-4e50-9e4d-c0afb7091d8b
# ╠═95edbbbf-2e35-446d-9eeb-a53f5c6b10ec
# ╟─4880c8a6-de84-4e55-be90-f73256b0888a
# ╟─75a66d2a-01b9-4d6a-8fed-8d8159ba6354
# ╟─cd558a80-2825-4953-870c-93551f60c10e
# ╟─c5aa282c-c82b-4521-994d-235cb7934c83
# ╟─32481e35-1a98-4b65-b925-264d51d24743
# ╟─58b8b1b8-93e7-46c0-b0a0-28532b152908
# ╟─faa03639-a431-4cbf-ac03-bbb33fb7b5b8
# ╠═5e0cf15b-21ab-45bb-81fa-ee962a46004f
# ╟─fc08d410-02fa-480b-a999-841476dfaaf6
# ╟─57a5fb2a-5cb2-412f-a1a0-5d27e0a5b8f7
# ╟─a70a6137-88cf-4fd5-ad50-8d76a1379a27
# ╟─0308378a-2d36-4c47-add7-0f53dc9011ca
# ╟─9dba04bd-a8c3-44da-a1ab-855829c824ae
# ╟─8f68a5f0-4ec9-40d5-89be-cddf0c34a977
# ╟─22abccab-5c93-4aa4-90e4-8209997a9a86
# ╟─e77a478d-0270-4120-a2d4-36efc455fb2e
# ╟─1ec87e5e-2f90-490b-a94f-05ea53d4cb1d
# ╟─de8734a4-46b8-45ba-aff1-fa6a73e85d96
# ╠═e3bee176-9b9a-448f-b2d0-fa53cad35555
# ╠═495e13e3-bf20-4541-ab6c-f6cb9572e9ed
# ╟─f223ac43-79cd-43be-b585-907a0bfb8f27
# ╠═acb54294-917d-41b0-b501-364b9228531b
# ╠═ea231b9c-4cc9-4d45-b171-4c66536b02a1
# ╟─44813e6f-d7a2-47d6-9862-b9fd3e102ee2
# ╟─1a1b8edf-bc1a-466e-b714-3eb71aa145ce
# ╟─d8735a5e-8444-4724-9796-a59b37efd0e2
# ╠═bbd4086e-af3f-4743-b9fb-01559f567560
# ╟─1e9bf340-2dca-4602-a60c-b2ccdc9cfb85


using MicroLogging

function checkDerivative(fullF::Function,x;doPlot::Bool=false, smoothOffset::Bool=false)
    df = fullF(x)[2]'
    f(x) = fullF(x)[1]
    return checkDerivative(f,df,x,doPlot=doPlot, smoothOffset=smoothOffset)
end
function checkDerivative(f::Function,df,x;doPlot::Bool=false, smoothOffset::Bool=false)
    N = 17
    h = exp10.(2:-1:-N)
    if smoothOffset
        v = smoothArray(randn(size(x)).-0.5,5,5.0)
    else
        v = randn(size(x)).-0.5
    end
    fx = f(x)
    errquad = zeros(length(h))
    errlin  = zeros(length(h))
    for i=1:length(h)
        errlin[i]  = norm(fx             - f(x+h[i]*v))
        errquad[i] = norm(fx + h[i]*df*v - f(x+h[i]*v))
        s = @sprintf(" h: %1.1e  ||f(x+h*v)||: %2.2e   elin: %2.2e   equad: %2.2e \n",h[i], norm(f(x+h[i]*v)), errlin[i],errquad[i])
        @info s
    end
    #
    if(doPlot)
      PyPlot.rc("legend",fontsize=10)
      PyPlot.rc("xtick", labelsize=10, color="black", direction="in")
      PyPlot.rc("ytick", labelsize=10, color="black", direction="in")
      #fig = PyPlot.figure(figsize=(6,5),facecolor="white")
      PyPlot.clf()
      PyPlot.plot(h,errlin,label="||f(x) - f(x+hv)||")
      PyPlot.plot(h,errquad,".-",label="||f(x) + h nabla f v - f(x+hv)||")
      PyPlot.yscale("log")
      PyPlot.xscale("log")
      PyPlot.xlabel("h")
      PyPlot.ylabel("error")
      PyPlot.legend(loc="upper left")
    end
    return errlin, errquad
end

function checkErrorDecay(errquad::Vector)
  errorReduction = log10.(errquad[1:end-1]./errquad[2:end])
  errorReduction[isinf.(errorReduction)]=1
  @debug "# errorReduction.>1.7 = ", sum(errorReduction.>1.7)
  @info "sum(errorReduction...) = ", sum(errorReduction[errorReduction.>1.7]), "   (needs >6)"
  return sum(errorReduction[errorReduction.>1.7])>6
end


"""
    struct SymMatrix{T} <: AbstractMatrix{T}
        Q::Vector{T}
        n::Int
    end

Symmetric ``n \\times n`` matrix storing the vectorized upper triangular
part of the matrix in the `Q` vector (this corresponds to the vectorized
form of `MathOptInterface.PositiveSemidefiniteConeTriangle`).
It implement the `AbstractMatrix` interface except for `setindex!` as it might
break its symmetry. The [`symmetric_setindex!`](@ref) function should be used
instead.
"""
struct SymMatrix{T} <: AbstractMatrix{T}
    Q::Vector{T}
    n::Int
end

# i <= j
#function trimap(i, j, n) # It was used when Q was the lower triangular part
#    div(n*(n+1), 2) - div((n-i+1)*(n-i+2), 2) + j-i+1
#end

# j <= i
function trimap(i, j)
    div((i-1)*i, 2) + j
end

function trimat(::Type{T}, f, n, σ) where {T}
    Q = Vector{T}(undef, trimap(n, n))
    for i in 1:n
        for j in 1:i
            Q[trimap(i, j)] = f(σ[i], σ[j])
        end
    end
    SymMatrix{T}(Q, n)
end

Base.size(Q::SymMatrix) = (Q.n, Q.n)

"""
    symmetric_setindex!(Q::SymMatrix, value, i::Integer, j::Integer)

Set `Q[i, j]` and `Q[j, i]` to the value `value`.
"""
function symmetric_setindex!(Q::SymMatrix, value, i::Integer, j::Integer)
    Q.Q[trimap(max(i, j), min(i, j))] = value
end

function Base.getindex(Q::SymMatrix, i::Integer, j::Integer)
    return Q.Q[trimap(max(i, j), min(i, j))]
end
Base.getindex(Q::SymMatrix, I::Tuple) = Q[I...]
Base.getindex(Q::SymMatrix, I::CartesianIndex) = Q[I.I]


#using Revise, Test
using PseudoArcLengthContinuation, LinearAlgebra, Setfield
const PALC  =  PseudoArcLengthContinuation

function test_newton(x0)
	Ty = eltype(x0)

	F(x, p) = x.^3 .- 1
	Jac(x, p) = diagm(0 => 3 .* x.^2)

	opts = PALC.NewtonPar( tol = Ty(1e-8))
	sol, hist, flag, _ = PALC.newton(F, Jac, x0, nothing, opts, normN = x->norm(x,Inf))
end
######################################################################
# we test the regular newton algorithm
# simple case
test_newton(ones(10) .+ rand(10) * 0.1)

# test types for newton
sol, _, _ = test_newton(Float16.(ones(10) .+ rand(10) * 0.1))
@test eltype(sol) == Float16
######################################################################
function test_newton_palc(x0, p0)
	Ty = eltype(x0)
	N = length(x0)

	θ = Ty(0.2)
	dotθ = PALC.DotTheta(dot)

	F(x, p) = x.^3 .- p
	Jac(x, p) = diagm(0 => 3 .* x.^2)

	z0 = BorderedArray(x0, p0)
	τ0 = BorderedArray(rand(Ty, N), convert(typeof(p0), 0.2))
	zpred = BorderedArray(x0, convert(typeof(p0), 0.3))
	optn = NewtonPar{Ty, DefaultLS, DefaultEig}()
	optc = ContinuationPar{Ty, DefaultLS, DefaultEig}(newtonOptions = optn)
	sol, hist, flag, _ = @time PALC.newtonPALC(F, Jac, p0, (@lens _), z0, τ0, zpred, Ty(0.02), θ, optc, dotθ)
end

sol, _, _, _ = test_newton_palc(ones(10) .+ rand(10) * 0.1, 1.)

#test type
sol, _, _, _ = test_newton_palc(Float32.(ones(10) .+ rand(10) * 0.1), Float32(1.))
@test typeof(sol) == BorderedArray{Vector{Float32}, Float32}


len = length
dc  = deepcopy

macro clear()
  run(`clear`)
end

macro timeit( expression )
  maxtime = 2. # seconds
  tic()
  expression
  dt = toq()
  maxiters = int( maxtime / dt )
  times = zeros(maxiters)
  println("Running for maximum of $maxtime seconds...")
  for k = 1:maxiters
    tic()
    expression
    times[k] = toq();
  end
  println("Number of iterations: $maxiters")
  println("Units:                seconds          ")
  println("Best time:            $(minimum(times))")
  println("Worst time:           $(maximum(times))")
  println("Average time:         $(sum(times)/len(times))")
end

macro LStrings()
  include("/Users/terra/.julia/mymods/latex.jl")
end

macro MyPyPlotHelp()
  println("plt.title(titlestr, y = 1.05 )")
end

function Id( n )
  #= Makes a square Identity Matrix (n by n) =#
  return diagm(ones(n))
end

function symplecticMatrix( n )
  #= Makes a square Symplectic Block Matrix Omega (2n by 2n)
  #  Ω = [ 0 I_n; -I_n 0 ]
  =#
  I = Id( n )
  Z = zeros(n,n);
  Ω = [ Z I; -I Z ]
  return Ω
end

function symplecticTest( A, tol=1e-10 )
  #=
    symplecticTest( A[,tolerance=1e-10])

    Tests if matrix is Symplectic with the SBM Omega.
      A must be a square matrix of size 2n by 2n.
      Also, A'*Ω*A - Ω < tol = 1e-10 by default.
    Returns Boolean.
  =#
  M,N = size(A)
  if M != N
    println("ERROR: MATRIX NOT SQUARE")
    return false
  elseif mod(M,2) != 0
    println("ERROR: MATRIX NOT 2n BY 2n")
    return false
  end
  tol = 1e-10
  n   = int(M / 2)
  Ω   = symplecticMatrix( n )

  if maximum(maximum(abs(A' * Ω * A - Ω))) < tol
    return true
  end
  return false
end

function tayCoefMatrix( q, j=q )
  A = ones(q+1,j+1)
  J = collect(0:j)
  for k = 1:q+1
    p = k - 1
    f = factorial(p)
    A[k,:] = J.^p ./ f                             # Note 0^0 := 1
  end
  return A
end

function minor( A, j, k )
  s = size(A);
  if s[1] != s[2]
    print("Error -- Matrix is nonsquare")
    return None
  end
  small_size = (2,2);
  if s == small_size
    return A
  end
  t = (s[1]-1,s[2]-1)
  B = zeros(t)
  vc = 1
  for v = 1:s[2]
    if v != k
      uc = 1
      for u = 1:s[1]
        if u != j
          B[uc,vc] = A[u,v]
          uc += 1
        end
      end
    vc += 1
    end
  end
  return B
end

function diagDet( A )
  s = size(A)
  if s[1] != s[2]
    print("Error -- Matrix is nonsquare")
    return None
  end
  d = 0
  for k = 1:s[2]
    d += A[k,k] * det(minor(A,k,k))
  end
  return d
end

function welfertCheck( A )
  s = size(A)
  if s[1] != s[2]
    print("Error -- Matrix is nonsquare")
    return None
  end
  dd = diagDet(A)
  d  = det(A)
  return d - dd
end

function avg( a, slice=1 )
  return sum(avg,slice) / size(avg,slice)
end

function average( a, slice=1 )
  return avg(a, slice)
end


using BenchmarkTools

# ## Starting a Cluster
using Distributed
procs()

# change to 2 if started julia with -p2
addprocs(4)

procs()

# addprocs(["10.0.2.1", "10.0.2.2"]) ;

# ## Communication between Julia processes

a = remotecall(sqrt, 2, 4.0)

wait(a)

fetch(a)

remotecall_fetch(sqrt, 2, 4.0)

# # Programming parallel tasks

using Pkg
Pkg.add("Distributions")
using Distributions  # precompile

@everywhere using Distributions

@everywhere println(rand(Normal()))

# ## @spawn macro

a=@spawn randn(5,5)^2

fetch(a)

b=rand(5,5)

a=@spawn b^2

fetch(a)

@time begin
   A = rand(1000,1000)
   Bref = @spawn A^2
   fetch(Bref)
end;

@time begin
   Bref = @spawn rand(1000,1000)^2
   fetch(Bref)
end;

# ## @spawnat

r = remotecall(rand, 2, 2, 2)

s = @spawnat 3 1 .+ fetch(r)

fetch(s)

# ## @parallel for

function serial_add()
    s=0.0
    for i = 1:1000000
         s=s+randn()
    end
    return s
end

function parallel_add()
    return @distributed (+) for i=1:1000000
       randn()
    end
end

@btime serial_add()

@btime parallel_add()

# ## Parallel map

x=[rand(100,100) for i in 1:10];

@everywhere using LinearAlgebra

@btime map(svd, x);

@btime pmap(svd, x);

# ## Distributed Monte Carlo

@everywhere function darts_in_circle(N)
    n = 0
    for i in 1:N
        if rand()^2 + rand()^2 < 1
            n += 1
        end
    end
    return n
end

function pi_distributed(N, loops)
    n = sum(pmap((x)->darts_in_circle(N), 1:loops))
    4 * n / (loops * N)
end

function pi_serial(n)
   return 4 * darts_in_circle(n) / n
end

@btime pi_distributed(1_000_000, 50)

@btime pi_serial(50_000_000)

# ## Distributed Arrays

using Pkg
Pkg.add("DistributedArrays")
using DistributedArrays
@everywhere using DistributedArrays
d=dzeros(12, 12)
x=rand(10,10);
dx = distribute(x)

@everywhere function par(I)
    d=(size(I[1], 1), size(I[2], 1))
    m = fill(myid(), d)
    return m
end

m = DArray(par, (800, 800))

d.indices

r = @spawnat 2 localpart(d)

fetch(r)

@distributed (+) for i in 1:nworkers()
   sum(localpart(m))
end

# ### Game of Life

function life_step(d::DArray)
   DArray(size(d),procs(d)) do I
      top = mod1(first(I[1])-1,size(d,1))  #outside edge
      bot = mod1( last(I[1])+1,size(d,1))
      left = mod1(first(I[2])-1,size(d,2))
      right = mod1( last(I[2])+1,size(d,2))
      old = Array{Bool}(undef, length(I[1])+2, length(I[2])+2) #temp array
      old[1 , 1 ] = d[top , left]  #get from remote
      old[2:end-1, 1 ] = d[I[1], left] # left
      old[end , 1 ] = d[bot , left]
      old[1 , end ] = d[top , right]
      old[2:end-1, end ] = d[I[1], right] # right
      old[end , end ] = d[bot , right]
      old[1 , 2:end-1] = d[top , I[2]] # top
      old[end , 2:end-1] = d[bot , I[2]] # bottom
      old[2:end-1, 2:end-1] = d[I[1], I[2]] # middle (local)

      life_rule(old) # Step!
   end
end

@everywhere function life_rule(old)
    m, n = size(old)
    new = similar(old, m-2, n-2)
    for j = 2:n-1
        @inbounds for i = 2:m-1
            nc = (+)(old[i-1,j-1], old[i-1,j], old[i-1,j+1],
                     old[i ,j-1], old[i ,j+1],
                     old[i+1,j-1], old[i+1,j], old[i+1,j+1])
            new[i-1,j-1] = (nc == 3 || nc == 2 && old[i,j])
        end
    end
    new
end

A = DArray(I->rand(Bool, length.(I)), (20,20))

using Pkg; Pkg.add("Colors")
using Colors
Gray.(A)

B = copy(A)
B = Gray.(life_step(B))

# # Shared Arrays
using SharedArrays
S=SharedArray{Float64}((100, 100, 5), pids=[2,3, 4, 5]);

pmap(x->S[x]=myid(), eachindex(S));

S

# ## Parallel Prefix sum

function prefix_shared!(y::SharedArray)
    l=length(y)
    k=ceil(Int, log2(l))
    for j=1:k
        @sync @distributed for i=2^j:2^j:min(l, 2^k)
            @inbounds y[i] = y[i-2^(j-1)] + y[i]
        end
    end
    for j=(k-1):-1:1
        @sync @distributed for i=3*2^(j-1):2^j:min(l, 2^k)
            @inbounds y[i] = y[i-2^(j-1)] + y[i]
        end
    end
    y
end


using OpenQuantumTools, Test, OrdinaryDiffEq

H = DenseHamiltonian([(s) -> 1.0], [σi], unit = :ħ)
u0 = PauliVec[1][1]
coupling = ConstantCouplings(["Z"], unit = :ħ)

# test for OhmicBath
bath = Ohmic(1e-4, 4, 16)
annealing = Annealing(H, u0; coupling = coupling, bath = bath)
tf = 10

γ = spectrum(0, bath)

sol = solve_ame(
    annealing,
    tf,
    alg = Tsit5(),
    ω_hint = range(-2, 2, length = 100),
    abstol = 1e-8,
    reltol = 1e-8,
)
@test sol(tf)[1, 2] ≈ exp(-2 * γ * tf) * 0.5 atol = 1e-5 rtol = 1e-5


function downconvert!(
    downconverted_signal::StructArray{Complex{T}, 1},
    signal::StructArray{Complex{ST}, 1},
    carrier_replica::StructArray{Complex{T}, 1},
    start_sample::Integer,
    num_samples::Integer
) where {T, ST}
    ds_re = downconverted_signal.re; ds_im = downconverted_signal.im
    s_re = signal.re; s_im = signal.im
    c_re = carrier_replica.re; c_im = carrier_replica.im
    @avx for i = start_sample:num_samples + start_sample - 1
        ds_re[i] = s_re[i] * c_re[i] + s_im[i] * c_im[i]
        ds_im[i] = s_im[i] * c_re[i] - s_re[i] * c_im[i]
    end
    downconverted_signal
end

# This function creates the carrier and downconverts
# directly. It is a little bit faster than first creating the
# replica and then downconverting. This is not in use at the moment
# to be consistent (see next function).
function downconvert!(
    downconverted_signal::StructArray{Complex{T}},
    signal::StructArray{Complex{TS}},
    carrier_frequency,
    sampling_frequency,
    start_phase,
    start_sample,
    num_samples
) where {T, TS}
    ds_re = downconverted_signal.re; ds_im = downconverted_signal.im
    s_re = signal.re; s_im = signal.im
    carrier_freq = upreferred(carrier_frequency / Hz)
    sampling_freq = upreferred(sampling_frequency / Hz)
    @avx for i = start_sample:start_sample + num_samples - 1
        c_im, c_re = sincos(T(2π) * ((i - start_sample) * T(carrier_freq) / T(sampling_freq) + T(start_phase)))
        ds_re[i] = s_re[i] * c_re + s_im[i] * c_im
        ds_im[i] = s_im[i] * c_re - s_re[i] * c_im
    end
    downconverted_signal
end

# Same as above but for the multiple antenna case. It is faster
# for less than 4 antennas, but slower otherwise. This is not in
# use due to this circumstances.
function downconvert!(
    downconverted_signal::StructArray{Complex{T}, 2},
    signal::StructArray{Complex{TS}, 2},
    carrier_frequency,
    sampling_frequency,
    start_phase,
    start_sample,
    num_samples
) where {N, T, TS}
    ds_re = downconverted_signal.re; ds_im = downconverted_signal.im
    s_re = signal.re; s_im = signal.im
    carrier_freq = upreferred(carrier_frequency / Hz)
    sampling_freq = upreferred(sampling_frequency / Hz)
    @avx for i = start_sample:start_sample + num_samples - 1
        c_im, c_re = sincos(T(2π) * ((i - start_sample) * T(carrier_freq) / T(sampling_freq) + T(start_phase)))
        for j = 1:size(s_re, 2)
            ds_re[i,j] = s_re[i,j] * c_re + s_im[i,j] * c_im
            ds_im[i,j] = s_im[i,j] * c_re - s_re[i,j] * c_im
        end
    end
    downconverted_signal
end

function downconvert!(
    downconverted_signal::StructArray{Complex{T}, 2},
    signal::StructArray{Complex{ST}, 2},
    carrier_replica::StructArray{Complex{T}, 1},
    start_sample::Integer,
    num_samples::Integer
) where {T, ST}
    ds_re = downconverted_signal.re; ds_im = downconverted_signal.im
    s_re = signal.re; s_im = signal.im
    c_re = carrier_replica.re; c_im = carrier_replica.im
    @avx for i = start_sample:num_samples + start_sample - 1, j = 1:size(s_re, 2)
        ds_re[i, j] = s_re[i, j] * c_re[i] + s_im[i, j] * c_im[i]
        ds_im[i, j] = s_im[i, j] * c_re[i] - s_re[i, j] * c_im[i]
    end
    downconverted_signal
end

@static if VERSION >= v"1.6"
    function downconvert!(
        downconverted_signal::StructArray{Complex{T}, 1},
        signal::AbstractArray{Complex{ST}, 1},
        carrier_replica::StructArray{Complex{T}, 1},
        start_sample::Integer,
        num_samples::Integer
    ) where {T, ST}
        signal_real = reinterpret(reshape, ST, signal)
        ds_re = downconverted_signal.re; ds_im = downconverted_signal.im
        c_re = carrier_replica.re; c_im = carrier_replica.im
        @avx for i = start_sample:num_samples + start_sample - 1
            ds_re[i] = signal_real[1, i] * c_re[i] + signal_real[2, i] * c_im[i]
            ds_im[i] = signal_real[2, i] * c_re[i] - signal_real[1, i] * c_im[i]
        end
        downconverted_signal
    end

    function downconvert!(
        downconverted_signal::StructArray{Complex{T}, 2},
        signal::AbstractArray{Complex{ST}, 2},
        carrier_replica::StructArray{Complex{T}, 1},
        start_sample::Integer,
        num_samples::Integer
    ) where {T, ST}
        signal_real = reinterpret(reshape, ST, signal)
        ds_re = downconverted_signal.re; ds_im = downconverted_signal.im
        c_re = carrier_replica.re; c_im = carrier_replica.im
        @avx for i = start_sample:num_samples + start_sample - 1, j = 1:size(signal_real, 3)
            ds_re[i, j] = signal_real[1, i, j] * c_re[i] + signal_real[2, i, j] * c_im[i]
            ds_im[i, j] = signal_real[2, i, j] * c_re[i] - signal_real[1, i, j] * c_im[i]
        end
        downconverted_signal
    end
else
    function downconvert!(
        downconverted_signal,
        signal::AbstractMatrix,
        carrier_replica,
        start_sample::Integer,
        num_samples::Integer
    )
        sample_range = start_sample:start_sample + num_samples - 1
        downconverted_signal[sample_range,:] .= @view(signal[sample_range,:]) .* conj.(@view(carrier_replica[sample_range]))
        downconverted_signal
    end

    function downconvert!(
        downconverted_signal,
        signal::AbstractVector,
        carrier_replica,
        start_sample::Integer,
        num_samples::Integer
    )
        sample_range = start_sample:start_sample + num_samples - 1
        downconverted_signal[sample_range,:] .= @view(signal[sample_range]) .* conj.(@view(carrier_replica[sample_range]))
        downconverted_signal
    end
end


using Unitful.DefaultSymbols
import SymPy.Physics

function clebsch_gordan(lower::StateSpec, upper::StateSpec)
    lo = NoHyperfineNumberSpec(lower.level)
    hi = NoHyperfineNumberSpec(upper.level)
    # TODO: Verify transition type.
    sqrt(2 * hi.j + 1) * sum(Physics.wigner_3j(lo.j, 1, hi.j, -lower.m, q, upper.m) for q in -1:1) |> Float64
end

function rabi_frequency(species, lower::StateSpec, upper::StateSpec, intensity)
    i0 = saturation_intensity(species, lower.level, upper.level)
    cg = clebsch_gordan(lower, upper)
    a = einstein_a(species, lower.level, upper.level) |> get
    τ = lifetime(species, upper.level) |> get
    uconvert(kHz, sqrt(intensity / i0 * cg^2 * a / τ))
end

export rabi_frequency


module Julia

include("pix.jl")
include("ppm.jl")

function julia(w, h)
  pix = Pix.zero(w,h)

  xa = -2.0
  xb = 2.0
  ya = -1.5
  yb = 1.5
  max_it = 65535

  # Find a good julia spot from the mandelbrot set.
  c = 0
  while true
    cx = rand() * (xb - xa) + xa
    cy = rand() * (yb - ya) + ya
    c = cx + cy * 1im
    z = c
    i = 0
    for i=0:max_it
      if abs(z) > 2.0
        break
      end
      z = z * z + c
    end
    if i > 10 && i < 32000
      break
    end
  end

  # Write the pixels
  for ih = 1:h
    zy = ih * (yb - ya) / (h - 1) + ya
    for iw = 1:w
      zx = iw * (xb - xa) / (w - 1) + xa
      z = zx + zy * 1im
      i = 0
      for i = 0:max_it
        if abs(z) > 2.0
          break
        end
        z = z * z + c
      end
      pix[iw,ih][1] = i % 128 * 512
      pix[iw,ih][2] = i % 256 * 256
      pix[iw,ih][3] = i % 512 * 128
    end
  end

  return pix
end

PPM.write(julia(1024,1024))

end


using SemidefiniteProgramming

sdp = SparseSDP(maximize=true)

setobj!(sdp, 1, 1, 1, 1.0)
setobj!(sdp, 2, 1, 1, 2.0)
setobj!(sdp, 3, 1, 1, 3.0)
setobj!(sdp, 3, 2, 2, 4.0)

setrhs!(sdp, 1, 10.0)
setcon!(sdp, 1, 1, 1, 1, 1.0)
setcon!(sdp, 1, 2, 1, 1, 1.0)

setrhs!(sdp, 2, 20.0)
setcon!(sdp, 2, 2, 1, 1, 1.0)
setcon!(sdp, 2, 3, 1, 1, 5.0)
setcon!(sdp, 2, 3, 1, 2, 2.0)
setcon!(sdp, 2, 3, 2, 1, 2.0)
setcon!(sdp, 2, 3, 2, 2, 6.0)

println(obj(solve(sdp, CSDP())))


using BenchmarkTools

function calc_pi_by_gcd(N)
    s = 0
    for a in 1:N
        for b in 1:N
            s += ifelse(gcd(a,b)==1, 1, 0)
        end
        s
    end
    println(sqrt(6N^2/s))
end

function main()
    @time calc_pi_by_gcd(10000)
end

main()


using DataStructures

function countcard(str::String)::Accumulator{Int, Int}
    counter([parse(Int, x) for x in str])
end

function score(cards::Accumulator{Int, Int})::Int64
    ret = 0
    for i=1:9
        ret += i*10^cards[i]
    end
    ret
end

function solve()
    k = parse(Int, readline())
    s = readline()
    t = readline()

    s = countcard(s[1:end-1])
    t = countcard(t[1:end-1])

    total = 0
    win = 0
    for a=1:9, b=1:9
        n = if a == b
            (k - (s[a] + t[a])) * (k - (s[b] + t[b] + 1))
        else
            (k - (s[a] + t[a])) * (k - (s[b] + t[b]))
        end
        total += n

        s[a] += 1
        t[b] += 1
        if score(s) > score(t)
            win += n
        end
        s[a] -= 1
        t[b] -= 1
    end
    win / total
end

println(solve())


################################################################################
#  Copyright 2022, Tom Van Acker (BASF)                                        #
################################################################################
# TFT.jl                                                                       #
# A Julia package for Taylor-Fourier Transform.                                #
# See http://github.com/timmyfaraday/TFT.jl                                    #
################################################################################

@testset "FTFT" begin

    # fundamental frequency and angular frequency
    F   = 50.0
    ω   = 2 * pi * F

    # tft input
    D   = 2
    K   = 9

    # discrete time
    t   = 0.0:0.001:1.0
    tm  = 0.536
    idm = findfirst(x -> x == tm, t)

    @testset "Zeroth Harmonic Constant Signal" begin
        # input - amplitude
        A(t)    = 10.0

        # derivatives - amplitude and anti-rotating phase
        dA(t)   = _FD.derivative(A,t)
        d2A(t)  = _FD.derivative(dA,t)

        # derived input
        Ξ(t)    = A.(t)
        Ψ(t)    = A.(t)
        S(t)    = A.(t)

        # perform taylor-fourier transform
        sol = ftft(S.(t), collect(t), [0,10], D, F, K)

        # tests
        ## amplitude
        @test isapprox(TFT.a(sol,0,0)[idm], A(tm), atol=atol)
        @test isapprox(TFT.a(sol,1,0)[idm], dA(tm), atol=atol)
        @test isapprox(TFT.a(sol,2,0)[idm], d2A(tm), atol=atol)
        ### --- amplitude of non-present harmonic should be zero
        @test isapprox(TFT.a(sol,0,10)[idm], 0.0, atol=atol)
        ## phase
        @test isapprox(TFT.ϕ(sol,0,0)[idm], 0.0, atol=atol)
        ## anti-rotating phase
        @test isapprox(TFT.φ(sol,0,0)[idm], 0.0, atol=atol)
        ## dynamic phasor
        @test isapprox(TFT.ξ(sol,0,0)[idm], Ξ(tm), atol=atol)
        ## anti-rotating dynamic phasor
        @test isapprox(TFT.ψ(sol,0,0)[idm], Ψ(tm), atol=atol)
        @test isapprox(TFT.ψ(sol,1,0)[idm], TFT.ξ(sol,1,0)[idm], atol=atol)
        @test isapprox(TFT.ψ(sol,2,0)[idm], TFT.ξ(sol,2,0)[idm], atol=atol)
        ## signal
        @test isapprox(TFT.signal(sol)[idm], S(tm), atol=atol)
        @test isapprox(TFT.signal(sol,0)[idm], S(tm), atol=atol)
        ### --- signal of non-present harmonic should be zero
        @test isapprox(TFT.signal(sol,10)[idm], 0.0, atol=atol)
        ## error
        @test isapprox(TFT.error(sol)[idm], 0.0, atol=atol)

    end

    @testset "Zeroth Harmonic Linear Signal" begin
        # input - amplitude
        A(t)    = 10.0 .- t

        # derivatives - amplitude and anti-rotating phase
        dA(t)   = _FD.derivative(A,t)
        d2A(t)  = _FD.derivative(dA,t)

        # derived input
        Ξ(t)    = A.(t)
        Ψ(t)    = A.(t)
        S(t)    = A.(t)

        # perform taylor-fourier transform
        sol = ftft(S.(t), collect(t), [0,10], D, F, K)

        # tests
        ## amplitude
        @test isapprox(TFT.a(sol,0,0)[idm], A(tm), atol=atol)
        @test isapprox(TFT.a(sol,1,0)[idm], dA(tm), atol=atol)
        @test isapprox(TFT.a(sol,2,0)[idm], d2A(tm), atol=atol)
        ### --- amplitude of non-present harmonic should be zero
        @test isapprox(TFT.a(sol,0,10)[idm], 0.0, atol=atol)
        ## phase
        @test isapprox(TFT.ϕ(sol,0,0)[idm], 0.0, atol=atol)
        ## anti-rotating phase
        @test isapprox(TFT.φ(sol,0,0)[idm], 0.0, atol=atol)
        ## dynamic phasor
        @test isapprox(TFT.ξ(sol,0,0)[idm], Ξ(tm), atol=atol)
        ## anti-rotating dynamic phasor
        @test isapprox(TFT.ψ(sol,0,0)[idm], Ψ(tm), atol=atol)
        @test isapprox(TFT.ψ(sol,1,0)[idm], TFT.ξ(sol,1,0)[idm], atol=atol)
        @test isapprox(TFT.ψ(sol,2,0)[idm], TFT.ξ(sol,2,0)[idm], atol=atol)
        ## signal
        @test isapprox(TFT.signal(sol)[idm], S(tm), atol=atol)
        @test isapprox(TFT.signal(sol,0)[idm], S(tm), atol=atol)
        ### --- signal of non-present harmonic should be zero
        @test isapprox(TFT.signal(sol,10)[idm], 0.0, atol=atol)
        ## error
        @test isapprox(TFT.error(sol)[idm], 0.0, atol=atol)

    end

    @testset "Zeroth Harmonic Quadratic Signal" begin
        # input - amplitude
        A(t)    = 10.0 .- t .+ 2.0 .* t.^2

        # derivatives - amplitude and anti-rotating phase
        dA(t)   = _FD.derivative(A,t)
        d2A(t)  = _FD.derivative(dA,t)

        # derived input
        Ξ(t)    = A.(t)
        Ψ(t)    = A.(t)
        S(t)    = A.(t)

        # perform taylor-fourier transform
        sol = ftft(S.(t), collect(t), [0,10], D, F, K)

        # tests
        ## amplitude
        @test isapprox(TFT.a(sol,0,0)[idm], A(tm), atol=atol)
        @test isapprox(TFT.a(sol,1,0)[idm], dA(tm), atol=atol)
        @test isapprox(TFT.a(sol,2,0)[idm], d2A(tm), atol=atol)
        ### --- amplitude of non-present harmonic should be zero
        @test isapprox(TFT.a(sol,0,10)[idm], 0.0, atol=atol)
        ## phase
        @test isapprox(TFT.ϕ(sol,0,0)[idm], 0.0, atol=atol)
        ## anti-rotating phase
        @test isapprox(TFT.φ(sol,0,0)[idm], 0.0, atol=atol)
        ## dynamic phasor
        @test isapprox(TFT.ξ(sol,0,0)[idm], Ξ(tm), atol=atol)
        ## anti-rotating dynamic phasor
        @test isapprox(TFT.ψ(sol,0,0)[idm], Ψ(tm), atol=atol)
        @test isapprox(TFT.ψ(sol,1,0)[idm], TFT.ξ(sol,1,0)[idm], atol=atol)
        @test isapprox(TFT.ψ(sol,2,0)[idm], TFT.ξ(sol,2,0)[idm], atol=atol)
        ## signal
        @test isapprox(TFT.signal(sol)[idm], S(tm), atol=atol)
        @test isapprox(TFT.signal(sol,0)[idm], S(tm), atol=atol)
        ### --- signal of non-present harmonic should be zero
        @test isapprox(TFT.signal(sol,10)[idm], 0.0, atol=atol)
        ## error
        @test isapprox(TFT.error(sol)[idm], 0.0, atol=atol)

    end

    @testset "Fundamental Periodic Signal" begin
        # input - amplitude and anti-rotating phase
        A(t)    = 10.0
        Φ(t)    = 2 * pi / 3

        # derivatives - amplitude and anti-rotating phase
        dA(t)   = _FD.derivative(A,t)
        d2A(t)  = _FD.derivative(dA,t)
        dΦ(t)   = _FD.derivative(Φ,t)
        d2Φ(t)  = _FD.derivative(dΦ,t)

        # derived input
        Φr(t)   = rem.(ω .*t .+ Φ.(t) .+ pi, 2 * pi) .- pi
        Fr(t)   = F + dΦ(t) / (2 * pi)
        Rr(t)   = d2Φ(t) / (2 * pi)^2
        Ξ(t)    = A.(t) ./ 2 .* exp.(im .* Φ.(t)) .* exp.(im .* ω .* t)
        Ψ(t)    = A.(t) ./ 2 .* exp.(im .* Φ.(t))
        S(t)    = real.(A.(t) ./ 2 .* exp.(im .* Φ.(t)) .* exp.(im .* ω .* t) .+
                  conj.(A.(t) ./ 2 .* exp.(im .* Φ.(t)) .* exp.(im .* ω .* t)))

        # perform taylor-fourier transform
        sol = ftft(S.(t), collect(t), [1,10], D, F, K)

        # tests
        ## amplitude
        @test isapprox(TFT.a(sol,0,1)[idm], A(tm), atol=atol)
        @test isapprox(TFT.a(sol,1,1)[idm], dA(tm), atol=atol)
        @test isapprox(TFT.a(sol,2,1)[idm], d2A(tm), atol=atol)
        ### --- amplitude of derivative degree higher than two not supported
        @test TFT.a(sol,3,1) === nothing
        ### --- amplitude of non-present harmonic should be zero
        @test isapprox(TFT.a(sol,0,10)[idm], 0.0, atol=atol)
        ## phase
        @test isapprox(TFT.ϕ(sol,0,1)[idm], Φr(tm), atol=atol)
        @test isapprox(TFT.ϕ(sol,1,1)[idm], dΦ(tm), atol=atol)
        @test isapprox(TFT.ϕ(sol,2,1)[idm], d2Φ(tm), atol=atol)
        ### --- phase of derivative degree higher than two not supported
        @test TFT.ϕ(sol,3,1) === nothing
        ## anti-rotating phase
        @test isapprox(TFT.φ(sol,0,1)[idm], Φ(tm), atol=atol)
        @test isapprox(TFT.φ(sol,1,1)[idm], dΦ(tm), atol=atol)
        @test isapprox(TFT.φ(sol,2,1)[idm], d2Φ(tm), atol=atol)
        ### --- ar phase of derivative degree higher than two not supported
        @test TFT.φ(sol,3,1) === nothing
        ## frequency
        @test isapprox(TFT.f(sol,1)[idm], Fr(tm), atol=atol)
        ## rocof
        @test isapprox(TFT.r(sol,1)[idm], Rr(tm), atol=atol)
        ## dynamic phasor
        @test isapprox(TFT.ξ(sol,0,1)[idm], Ξ(tm), atol=atol)
        ## anti-rotating dynamic phasor
        @test isapprox(TFT.ψ(sol,0,1)[idm], Ψ(tm), atol=atol)
        @test isapprox(TFT.ψ(sol,1,1)[idm], TFT.ξ(sol,1,1)[idm] * exp(-im * ω * tm), atol=atol)
        @test isapprox(TFT.ψ(sol,2,1)[idm], TFT.ξ(sol,2,1)[idm] * exp(-im * ω * tm), atol=atol)
        ## signal
        @test isapprox(TFT.signal(sol)[idm], S(tm), atol=atol)
        @test isapprox(TFT.signal(sol,1)[idm], S(tm), atol=atol)
        ### --- signal of non-present harmonic should be zero
        @test isapprox(TFT.signal(sol,10)[idm], 0.0, atol=atol)
        ## error
        @test isapprox(TFT.error(sol)[idm], 0.0, atol=atol)
    end

    @testset "Fundamental Aperiodic Linear Signal" begin
        # input - amplitude and anti-rotating phase
        A(t)    = 10.0 .- t
        Φ(t)    = pi / 2 .* t

        # derivatives - amplitude and anti-rotating phase
        dA(t)   = _FD.derivative(A,t)
        d2A(t)  = _FD.derivative(dA,t)
        dΦ(t)   = _FD.derivative(Φ,t)
        d2Φ(t)  = _FD.derivative(dΦ,t)

        # derived input
        Φr(t)   = rem.(ω .*t .+ Φ.(t) .+ pi, 2 * pi) .- pi
        Fr(t)   = F + dΦ(t) / (2 * pi)
        Rr(t)   = d2Φ(t) / (2 * pi)^2
        Ξ(t)    = A.(t) ./ 2 .* exp.(im .* Φ.(t)) .* exp.(im .* ω .* t)
        Ψ(t)    = A.(t) ./ 2 .* exp.(im .* Φ.(t))
        S(t)    = real.(A.(t) ./ 2 .* exp.(im .* Φ.(t)) .* exp.(im .* ω .* t) .+
                  conj.(A.(t) ./ 2 .* exp.(im .* Φ.(t)) .* exp.(im .* ω .* t)))

        # perform taylor-fourier transform
        sol = ftft(S.(t), collect(t), [1,10], D, F, K)

        # tests
        ## amplitude
        @test isapprox(TFT.a(sol,0,1)[idm], A(tm), atol=atol)
        @test isapprox(TFT.a(sol,1,1)[idm], dA(tm), atol=atol)
        @test isapprox(TFT.a(sol,2,1)[idm], d2A(tm), atol=atol)
        ### --- amplitude of derivative degree higher than two not supported
        @test TFT.a(sol,3,1) === nothing
        ### --- amplitude of non-present harmonic should be zero
        @test isapprox(TFT.a(sol,0,10)[idm], 0.0, atol=atol)
        ## phase
        @test isapprox(TFT.ϕ(sol,0,1)[idm], Φr(tm), atol=atol)
        @test isapprox(TFT.ϕ(sol,1,1)[idm], dΦ(tm), atol=atol)
        @test isapprox(TFT.ϕ(sol,2,1)[idm], d2Φ(tm), atol=atol)
        ### --- phase of derivative degree higher than two not supported
        @test TFT.ϕ(sol,3,1) === nothing
        ## anti-rotating phase
        @test isapprox(TFT.φ(sol,0,1)[idm], Φ(tm), atol=atol)
        @test isapprox(TFT.φ(sol,1,1)[idm], dΦ(tm), atol=atol)
        @test isapprox(TFT.φ(sol,2,1)[idm], d2Φ(tm), atol=atol)
        ### --- ar phase of derivative degree higher than two not supported
        @test TFT.φ(sol,3,1) === nothing
        ## frequency
        @test isapprox(TFT.f(sol,1)[idm], Fr(tm), atol=atol)
        ## rocof
        @test isapprox(TFT.r(sol,1)[idm], Rr(tm), atol=atol)
        ## dynamic phasor
        @test isapprox(TFT.ξ(sol,0,1)[idm], Ξ(tm), atol=atol)
        ## anti-rotating dynamic phasor
        @test isapprox(TFT.ψ(sol,0,1)[idm], Ψ(tm), atol=atol)
        @test isapprox(TFT.ψ(sol,1,1)[idm], TFT.ξ(sol,1,1)[idm] * exp(-im * ω * tm), atol=atol)
        @test isapprox(TFT.ψ(sol,2,1)[idm], TFT.ξ(sol,2,1)[idm] * exp(-im * ω * tm), atol=atol)
        ## signal
        @test isapprox(TFT.signal(sol)[idm], S(tm), atol=atol)
        @test isapprox(TFT.signal(sol,1)[idm], S(tm), atol=atol)
        ### --- signal of non-present harmonic should be zero
        @test isapprox(TFT.signal(sol,10)[idm], 0.0, atol=atol)
        ## error
        @test isapprox(TFT.error(sol)[idm], 0.0, atol=atol)
    end

    @testset "Fundamental Aperiodic Quadratic Signal" begin
        # input - amplitude and anti-rotating phase
        A(t)    = 10.0 .- t .+ 2.0 .* t.^2
        Φ(t)    = pi / 2 .* t.^2

        # derivatives - amplitude and anti-rotating phase
        dA(t)   = _FD.derivative(A,t)
        d2A(t)  = _FD.derivative(dA,t)
        dΦ(t)   = _FD.derivative(Φ,t)
        d2Φ(t)  = _FD.derivative(dΦ,t)

        # derived input
        Φr(t)   = rem.(ω .* t .+ Φ.(t) .+ pi, 2 * pi) .- pi
        Fr(t)   = F + dΦ(t) / (2 * pi)
        Rr(t)   = d2Φ(t) / (2 * pi)^2
        Ξ(t)    = A.(t) ./ 2 .* exp.(im .* Φ.(t)) .* exp.(im .* ω .* t)
        Ψ(t)    = A.(t) ./ 2 .* exp.(im .* Φ.(t))
        S(t)    = real.(A.(t) ./ 2 .* exp.(im .* Φ.(t)) .* exp.(im .* ω .* t) .+
                  conj.(A.(t) ./ 2 .* exp.(im .* Φ.(t)) .* exp.(im .* ω .* t)))

        # perform taylor-fourier transform -- not passing specific harmonics
        # to use the alternative problem_builder function.
        sol = ftft(S.(t), collect(t), D, F, K)

        # tests
        ## amplitude
        @test isapprox(TFT.a(sol,0,1)[idm], A(tm), atol=atol)
        @test isapprox(TFT.a(sol,1,1)[idm], dA(tm), atol=atol)
        @test isapprox(TFT.a(sol,2,1)[idm], d2A(tm), atol=atol)
        ### --- amplitude of derivative degree higher than two not supported
        @test TFT.a(sol,3,1) === nothing
        ### --- amplitude of non-present harmonic should be zero
        @test isapprox(TFT.a(sol,0,10)[idm], 0.0, atol=atol)
        ## phase
        @test isapprox(TFT.ϕ(sol,0,1)[idm], Φr(tm), atol=atol)
        @test isapprox(TFT.ϕ(sol,1,1)[idm], dΦ(tm), atol=atol)
        @test isapprox(TFT.ϕ(sol,2,1)[idm], d2Φ(tm), atol=atol)
        ### --- amplitude of derivative degree higher than two not supported
        @test TFT.ϕ(sol,3,1) === nothing
        ## anti-rotating phase
        @test isapprox(TFT.φ(sol,0,1)[idm], Φ(tm), atol=atol)
        @test isapprox(TFT.φ(sol,1,1)[idm], dΦ(tm), atol=atol)
        @test isapprox(TFT.φ(sol,2,1)[idm], d2Φ(tm), atol=atol)
        ### --- ar phase of derivative degree higher than two not supported
        @test TFT.φ(sol,3,1) === nothing
        ## frequency
        @test isapprox(TFT.f(sol,1)[idm], Fr(tm), atol=atol)
        ## rocof
        @test isapprox(TFT.r(sol,1)[idm], Rr(tm), atol=atol)
        ## dynamic phasor
        @test isapprox(TFT.ξ(sol,0,1)[idm], Ξ(tm), atol=atol)
        ## anti-rotating dynamic phasor
        @test isapprox(TFT.ψ(sol,0,1)[idm], Ψ(tm), atol=atol)
        @test isapprox(TFT.ψ(sol,1,1)[idm], TFT.ξ(sol,1,1)[idm] * exp(-im * ω * tm), atol=atol)
        @test isapprox(TFT.ψ(sol,2,1)[idm], TFT.ξ(sol,2,1)[idm] * exp(-im * ω * tm), atol=atol)
        ## signal
        @test isapprox(TFT.signal(sol)[idm], S(tm), atol=atol)
        @test isapprox(TFT.signal(sol,1)[idm], S(tm), atol=atol)
        ### --- signal of non-present harmonic should be zero
        @test isapprox(TFT.signal(sol,10)[idm], 0.0, atol=atol)
        ## error
        @test isapprox(TFT.error(sol)[idm], 0.0, atol=atol)
    end

end


using TensorOperations
using KrylovKit
using LinearAlgebra

# MPS A-matrix is a 3-index tensor, A[i,s,j]
#    s
#    |
# i -A- j
#
# [s] acts on the local Hilbert space
# [i,j] act on the virtual bonds

# MPO W-matrix is a 4-index tensor, W[i,j,s,t]
#     s
#     |
#  i -W- j
#     |
#     t
#
# [s,t] act on the local Hilbert space,
# [i,j] act on the virtual bonds

function contract_from_left_MPS(A,E,B)
    @tensor temp_1[:] := E[-1,1] * A[1,-2,-3]
    @tensor temp[:] := temp_1[1,2,-1] * conj( B[1,2,-2] )
    return temp
end

## tensor contraction from the left hand side
## +-    +--A-
## |     |  |
## L' =  L--R-
## |     |  |
## +-    +--B-

function contract_from_left(E_L,X,W)
    @tensor temp_1[:] := E_L[1,-3,-4]  * X[1,-2,-1]
    @tensor temp_2[:] := temp_1[-1, -2, -5, 1 ]* conj( X[1, -4,-3] )
    @tensor temp[:] := temp_2[ -1, 2, -3, 3, 1 ] * W[1,-2, 2, 3]
    return temp
end

## tensor contraction from the right hand side
##  -+     -A--+
##   |      |  |
##  -R' =  -W--R
##   |      |  |
##  -+     -B--+
function contract_from_right(E_R,Y,W)
    @tensor temp_1[:] :=  Y[-1,-2,1]  * E_R[ 1, -3,-4]
    @tensor temp_2[:] := temp_1[ -1, -2, -5,  1] * conj( Y[-3,-4,1] )
    @tensor temp[:] := temp_2[ -1, 1, -3, 2, 6] * W[ -2 6 1 2 ]
    return temp
end


module Filters

using LinearAlgebra, GaussQuadrature, GPUifyLoops
using ..Grids

export AbstractSpectralFilter, AbstractFilter
export ExponentialFilter, CutoffFilter, TMARFilter

abstract type AbstractFilter end
abstract type AbstractSpectralFilter <: AbstractFilter end

include("Filters_kernels.jl")

"""
    spectral_filter_matrix(r, Nc, σ)

Returns the filter matrix that takes function values at the interpolation
`N+1` points, `r`, converts them into Legendre polynomial basis coefficients,
multiplies
```math
σ((n-N_c)/(N-N_c))
```
against coefficients `n=Nc:N` and evaluates the resulting polynomial at the
points `r`.
"""
function spectral_filter_matrix(r, Nc, σ)
  N = length(r)-1
  T = eltype(r)

  @assert N >= 0
  @assert 0 <= Nc <= N

  a, b = GaussQuadrature.legendre_coefs(T, N)
  V = GaussQuadrature.orthonormal_poly(r, a, b)

  Σ = ones(T, N+1)
  Σ[(Nc:N).+1] .= σ.(((Nc:N).-Nc)./(N-Nc))

  V*Diagonal(Σ)/V
end

"""
    ExponentialFilter(grid, Nc=0, s=32, α=-log(eps(eltype(grid))))

Returns the spectral filter with the filter function
```math
σ(η) = \exp(-α η^s)
```
where `s` is the filter order (must be even), the filter starts with
polynomial order `Nc`, and `alpha` is a parameter controlling the smallest
value of the filter function.
"""
struct ExponentialFilter <: AbstractSpectralFilter
  "filter matrix"
  filter

  function ExponentialFilter(grid, Nc=0, s=32,
                             α=-log(eps(eltype(grid))))
    AT = arraytype(grid)
    N = polynomialorder(grid)
    ξ = referencepoints(grid)

    @assert iseven(s)
    @assert 0 <= Nc <= N

    σ(η) = exp(-α*η^s)
    filter = spectral_filter_matrix(ξ, Nc, σ)

    new(AT(filter))
  end
end

"""
    CutoffFilter(grid, Nc=polynomialorder(grid))

Returns the spectral filter that zeros out polynomial modes greater than or
equal to `Nc`.
"""
struct CutoffFilter <: AbstractSpectralFilter
  "filter matrix"
  filter

  function CutoffFilter(grid, Nc=polynomialorder(grid))
    AT = arraytype(grid)
    ξ = referencepoints(grid)

    σ(η) = 0
    filter = spectral_filter_matrix(ξ, Nc, σ)

    new(AT(filter))
  end
end

"""
    TMARFilter()

Returns the truncation and mass aware rescaling nonnegativity preservation
filter.  The details of this filter are described in

    @article{doi:10.1175/MWR-D-16-0220.1,
      author = {Light, Devin and Durran, Dale},
      title = {Preserving Nonnegativity in Discontinuous Galerkin
               Approximations to Scalar Transport via Truncation and Mass
               Aware Rescaling (TMAR)},
      journal = {Monthly Weather Review},
      volume = {144},
      number = {12},
      pages = {4771-4786},
      year = {2016},
      doi = {10.1175/MWR-D-16-0220.1},
    }

Note this needs to be used with a restrictive time step or a flux correction
to ensure that grid integral is conserved.

## Examples

This filter can be applied to the 3rd and 4th fields of an `MPIStateArray` `Q`
with the code

```julia
Filters.apply!(Q, (3, 4), grid, TMARFilter())
```

where `grid` is the associated `DiscontinuousSpectralElementGrid`.
"""
struct TMARFilter <: AbstractFilter
end

"""
    apply!(Q, states, grid::DiscontinuousSpectralElementGrid,
           filter::AbstractSpectralFilter; horizontal = true,
           vertical = true)

Applies `filter` to the `states` of `Q`.

The arguments `horizontal` and `vertical` are used to control if the filter
is applied in the horizontal and vertical reference directions, respectively.
Note, it is assumed that the trailing dimension is the vertical dimension and
the rest are horizontal.
"""
function apply!(Q, states, grid::DiscontinuousSpectralElementGrid,
                filter::AbstractSpectralFilter;
                horizontal = true, vertical = true)
  topology = grid.topology

  dim = dimensionality(grid)
  N = polynomialorder(grid)

  nstate = size(Q, 2)

  filtermatrix = filter.filter
  device = typeof(Q.data) <: Array ? CPU() : CUDA()

  nelem = length(topology.elems)
  Nq = N + 1
  Nqk = dim == 2 ? 1 : Nq

  nrealelem = length(topology.realelems)

  @launch(device, threads=(Nq, Nq, Nqk), blocks=nrealelem,
          knl_apply_filter!(Val(dim), Val(N), Val(nstate), Val(horizontal), Val(vertical),
                            Q.data, Val(states), filtermatrix, topology.realelems))
end

"""
    apply!(Q, states, grid::DiscontinuousSpectralElementGrid, ::TMARFilter)

Applies the truncation and mass aware rescaling to `states` of `Q`.  This
rescaling keeps the states nonegative while keeping the element average
the same.
"""
function apply!(Q, states, grid::DiscontinuousSpectralElementGrid,
                ::TMARFilter)
  topology = grid.topology

  device = typeof(Q.data) <: Array ? CPU() : CUDA()

  dim = dimensionality(grid)
  N = polynomialorder(grid)
  Nq = N + 1
  Nqk = dim == 2 ? 1 : Nq

  nrealelem = length(topology.realelems)
  nreduce = 2^ceil(Int, log2(Nq*Nqk))

  @launch(device, threads=(Nq, Nqk, 1), blocks=nrealelem,
          knl_apply_TMAR_filter!(Val(nreduce), Val(dim), Val(N), Q.data,
                                 Val(states), grid.vgeo, topology.realelems))
end

end


using RandomizedLinAlg
using Test, Random, LinearAlgebra

@testset "ID" begin
    Random.seed!(1)
    M = randn(4,5)
    k = 3

    F = id(M, k, 3)
    @test norm(F.B * F.P - M) ≤ 2svdvals(M)[k + 1]
end


using MovingWeightedLeastSquares

ins = [1. 1.; 1. -1.0; -1.0 1.0; -1.0 -1.0; 0. 0.; 1. 0.; -1. 0.; 0. 1.; 0. -1]
outs = [1., -0.5, 1., 1., -1., 0., 0., 0., 0.]

m = mwls(ins, outs, 1., (d, e) -> exp(-d^2))

println(MovingWeightedLeastSquares.diff(m, [0., 0.], (1, 1); dist = 10.))


function shock_loading(m::HetDSGE, TTT_jump::Matrix{Float64})
    exo = m.exogenous_shocks
    endo = m.endogenous_states

    _RRR = zeros(n_backward_looking_states(m), n_shocks_exogenous(m))
    RRR  = zeros(n_model_states(m), n_shocks_exogenous(m))

    _RRR[endo[:b′_t], exo[:b_sh]] .= 1.
    _RRR[endo[:g′_t], exo[:g_sh]] .= 1.
    _RRR[endo[:z′_t], exo[:z_sh]] .= 1.
    _RRR[endo[:μ′_t], exo[:μ_sh]] .= 1.
    _RRR[endo[:λ_w′_t], exo[:λ_w_sh]] .= 1.
    _RRR[endo[:λ_f′_t], exo[:λ_f_sh]] .= 1.
    _RRR[endo[:rm′_t], exo[:rm_sh]] .= 1.

    # Loading on states and jumps
    RRR[1:n_backward_looking_states(m), :] = _RRR
    RRR[n_backward_looking_states(m)+1:end, :] = TTT_jump*_RRR

    return RRR
end


"""
```
    Equalization <: AbstractHistogramAdjustmentAlgorithm
    Equalization(; nbins = 256, minval = 0, maxval = 1)

    adjust_histogram([T,] img, f::Equalization)
    adjust_histogram!([out,] img, f::Equalization)
```

Returns a histogram equalized image with a granularity of `nbins` number of bins.

# Details

Histogram equalization was initially conceived to  improve the contrast in a
single-channel grayscale image. The method transforms the
distribution of the intensities in an image so that they are as uniform as
possible [1]. The natural justification for uniformity
is that the image has better contrast  if the intensity levels of an image span
a wide range on the intensity scale. As it turns out, the necessary
transformation is a mapping based on the cumulative histogram.

One can consider an ``L``-bit single-channel ``I \\times J`` image with gray
values in the set ``\\{0,1,\\ldots,L-1 \\}``, as a collection of independent and
identically distributed random variables. Specifically, let the sample space
``\\Omega`` be the set of all ``IJ``-tuples ``\\omega
=(\\omega_{11},\\omega_{12},\\ldots,\\omega_{1J},\\omega_{21},\\omega_{22},\\ldots,\\omega_{2J},\\omega_{I1},\\omega_{I2},\\ldots,\\omega_{IJ})``,
where each ``\\omega_{ij} \\in \\{0,1,\\ldots, L-1 \\}``. Furthermore, impose a
probability measure on ``\\Omega`` such that the functions ``\\Omega \\ni
\\omega \\to \\omega_{ij} \\in \\{0,1,\\ldots,L-1\\}`` are independent and
identically distributed.

One can then regard an image as a matrix of random variables ``\\mathbf{G} =
[G_{i,j}(\\omega)]``, where each function ``G_{i,j}: \\Omega \\to \\mathbb{R}``
is defined by
```math
G_{i,j}(\\omega) = \\frac{\\omega_{ij}}{L-1},
```
and each ``G_{i,j}`` is distributed according to some unknown density ``f_{G}``.
While ``f_{G}`` is unknown, one can approximate it with a normalized histogram
of gray levels,

```math
\\hat{f}_{G}(v)= \\frac{n_v}{IJ},
```
where
```math
n_v = \\left | \\left\\{(i,j)\\, |\\,  G_{i,j}(\\omega)  = v \\right \\} \\right |
```
represents the number of times a gray level with intensity ``v`` occurs in
``\\mathbf{G}``. To transform the distribution of the intensities so that
they are as uniform as possible one needs to find a mapping ``T(\\cdot)`` such
that ``T(G_{i,j}) \\thicksim U ``. The required mapping turns out to be the
cumulative distribution function (CDF) of the empirical density
``\\hat{f}_{G}``,
```math
 T(G_{i,j}) = \\int_0^{G_{i,j}}\\hat{f}_{G}(w)\\mathrm{d} w.
```

# Options

Various options for the parameters of the `adjust_histogram` function and
`Equalization` type are described in more detail below.

## Choices for `img`

The `adjust_histogram` function can handle a variety of
input types.  By default type of the returned image matches the input type.

For colored images, the input is converted to
[YIQ](https://en.wikipedia.org/wiki/YIQ) type and the Y channel is equalized.
This is the combined with the I and Q channels and the resulting image converted
to the same type as the input.

## Choices for `nbins` in `Equalization`

You can specify the total number of bins in the histogram.

## Choices for `minval` and `maxval` in `Equalization`

If `minval` and `maxval` are specified then intensities are equalized to the range
[`minval`, `maxval`]. The default values are 0 and 1.

# Example

```julia

using TestImages, FileIO, ImageView

img =  testimage("mandril_gray")
imgeq = adjust_histogram(img, Equalization(nbins = 256, minval = 0, maxval = 1))

imshow(img)
imshow(imgeq)
```

# References
1. R. C. Gonzalez and R. E. Woods. *Digital Image Processing (3rd Edition)*.  Upper Saddle River, NJ, USA: Prentice-Hall,  2006.
"""
@with_kw struct Equalization{T₁ <: Union{Real,AbstractGray},
                                T₂ <: Union{Real,AbstractGray}} <: AbstractHistogramAdjustmentAlgorithm
    nbins::Int = 256
    minval::T₁ = 0.0
    maxval::T₂ = 1.0
end

function (f::Equalization)(out::GenericGrayImage, img::GenericGrayImage)
    minval, maxval = convert(eltype(out), f.minval), convert(eltype(out), f.maxval)
    edges, histogram = build_histogram(img, f.nbins, minval = minval, maxval = maxval)
    lb = first(axes(histogram,1))
    ub = last(axes(histogram,1))
    N = length(img)
    cdf = cumsum(histogram[lb:ub]/N)
    newvals = build_lookup(cdf, minval, maxval)
    transform_density!(out, img, edges, newvals)
end

function (f::Equalization)(out::AbstractArray{<:Color3}, img::AbstractArray{<:Color3})
    T = eltype(img)
    yiq = convert.(YIQ, img)
    yiq_view = channelview(yiq)
    adjust_histogram!(view(yiq_view,1,:,:), f)
    out .= convert.(T, yiq)
end

(f::Equalization)(out::GenericGrayImage, img::AbstractArray{<:Color3}) =
    f(out, of_eltype(Gray, img))


# by Lazaro Alonso
# using GLMakie # HIDE
using GLMakie
GLMakie.activate!() # HIDE
let
    x = -1:0.2:1
    y = -1:0.2:1
    z = -1:0.2:1
    vol1 = [ix*iy*iz for ix in x, iy in y, iz in z]
    points3d = [Point3f0(ix, iy, iz) for ix in x, iy in y, iz in z]
    vol2 = (vol1 .+ 1)./2 # send everything to the interval 0,1 (things don't seem to work with colorrange)
    # colormap with transparency in the middle
    cmap = :viridis
    colors = to_colormap(cmap, 101)
    n = length(colors)
    g(x) = x^2
    alphas = [g(x) for x in LinRange(-1,1,101)]
    cmap_alpha = RGBAf0.(colors, alphas)
    # the plot
    fig = Figure(resolution = (1200,1200))
    ax1 = Axis3(fig[1,1], perspectiveness = 0.5, azimuth = 7.19,
        elevation = 0.57, aspect = (1,1,1))
    ax2 = Axis3(fig[1,2], perspectiveness = 0.5, azimuth = 6.62,
        elevation = 0.57, aspect = (1,1,1))
    ax3 = Axis3(fig[2,1], perspectiveness = 0.5, azimuth = 7.38,
        elevation = 0.57, aspect = (1,1,1))
    ax4 = Axis3(fig[2,2], perspectiveness = 0.5, azimuth = 6.64,
        elevation = 0.57, aspect = (1,1,1))

    volume!(ax1, x, y, z, vol2; colormap = cmap, transparency = true)
    contour!(ax2, x, y, z, vol1; colormap = cmap, alpha=0.05,
        levels=[collect(-1:0.01:-0.3)..., collect(0.3:0.01:1)...])
    meshscatter!(ax3, vec(points3d); color = vec(vol1), colormap = cmap_alpha)
    meshscatter!(ax4, vec(points3d); color = vec(vol1), colormap = cmap_alpha,
        marker =  FRect3D(Vec3f0(0), Vec3f0(2)))
    xlims!(ax4, -1.2,1.2)
    ylims!(ax4, -1.2,1.2)
    zlims!(ax4, -1.2,1.2)
    fig
    save(joinpath(@__DIR__, "output", "volumeTransparent.png"), fig, px_per_unit = 2.0) # HIDE
end
using Pkg # HIDE
Pkg.status(["GLMakie"]) # HIDE


# ---
# title: 1143. Longest Common Subsequence
# id: problem1143
# author: Qling
# date: 2021-04-03
# difficulty: Medium
# categories: Dynamic Programming
# link: <https://leetcode.com/problems/longest-common-subsequence/description/>
# hidden: true
# ---
#
# Given two strings `text1` and `text2`, return the length of their longest
# common subsequence.
#
# A _subsequence_ of a string is a new string generated from the original string
# with some characters(can be none) deleted without changing the relative order
# of the remaining characters. (eg, "ace" is a subsequence of "abcde" while
# "aec" is not). A _common subsequence_  of two strings is a subsequence that is
# common to both strings.
#
#
#
# If there is no common subsequence, return 0.
#
#
#
# **Example 1:**
#
#
#
#     Input: text1 = "abcde", text2 = "ace"
#     Output: 3
#     Explanation: The longest common subsequence is "ace" and its length is 3.
#
#
# **Example 2:**
#
#
#
#     Input: text1 = "abc", text2 = "abc"
#     Output: 3
#     Explanation: The longest common subsequence is "abc" and its length is 3.
#
#
# **Example 3:**
#
#
#
#     Input: text1 = "abc", text2 = "def"
#     Output: 0
#     Explanation: There is no such common subsequence, so the result is 0.
#
#
#
#
# **Constraints:**
#
#   * `1 <= text1.length <= 1000`
#   * `1 <= text2.length <= 1000`
#   * The input strings consist of lowercase English characters only.
#
#
## @lc code=start
using LeetCode

function longest_common_subsequence(itr1, itr2)::Int32
    m, n = length(itr1) + 1, length(itr2) + 1
    dp = fill(0, m, n)

    for i in 2:m, j in 2:n
        dp[i, j] = (itr1[i - 1] == itr2[j - 1]) ? (dp[i - 1, j - 1] + 1) : max(dp[i - 1, j], dp[i, j - 1])
    end

    return dp[m, n]
end
## @lc code=end


"A `FunctionLevelSet` is a set that derives from the levels of a function."
abstract type FunctionLevelSet{T} <: Domain{T} end

# Convenience: assume the function and the level are stored fields
levelfun(d::FunctionLevelSet) = d.f
levelfun(d::FunctionLevelSet, x) = d.f(x)
level(d::FunctionLevelSet) = d.level

"Supertype of level set domains of the form `f(x)=C`."
abstract type AbstractLevelSet{T} <: FunctionLevelSet{T} end

indomain(x, d::AbstractLevelSet) = levelfun(d, x) == level(d)

show(io::IO, d::AbstractLevelSet) =
    print(io, "level set f(x) = $(level(d)) with f = $(levelfun(d))")

==(d1::AbstractLevelSet, d2::AbstractLevelSet) = levelfun(d1)==levelfun(d2) &&
    level(d1)==level(d2)
hash(d::AbstractLevelSet, h::UInt) = hashrec("AbstractLevelSet", levelfun(d), level(d), h)

"The domain defined by `f(x)=0` for a given function `f`."
struct ZeroSet{T,F} <: AbstractLevelSet{T}
    f   ::  F
end

ZeroSet(f) = ZeroSet{Float64}(f)
ZeroSet{T}(f::F) where {T,F} = ZeroSet{T,F}(f)

level(d::ZeroSet) = 0

similardomain(d::ZeroSet, ::Type{T}) where {T} = ZeroSet{T}(levelfun(d))

"The domain defined by `f(x)=C` for a given function `f` and constant `C`."
struct LevelSet{T,F,S} <: AbstractLevelSet{T}
    f       ::  F
    level   ::  S
end

LevelSet(f, level) = LevelSet{typeof(level)}(f, level)
LevelSet{T}(f::F, level::S) where {T,F,S} = LevelSet{T,F,S}(f, level)

similardomain(d::LevelSet, ::Type{T}) where {T} = LevelSet{T}(levelfun(d), level(d))

convert(::Type{LevelSet}, d::ZeroSet{T}) where {T} = LevelSet{T}(levelfun(d), level(d))
convert(::Type{LevelSet{T}}, d::ZeroSet) where {T} = LevelSet{T}(levelfun(d), level(d))

"Supertype of sublevel set domains."
abstract type AbstractSublevelSet{T,C} <: FunctionLevelSet{T} end

indomain(x, d::AbstractSublevelSet{T,:closed}) where {T} = levelfun(d, x) <= level(d)
indomain(x, d::AbstractSublevelSet{T,:open}) where {T} = levelfun(d, x) < level(d)

show(io::IO, d::AbstractSublevelSet{T,:closed}) where {T} =
    print(io, "sublevel set f(x) <= $(level(d)) with f = $(levelfun(d))")
show(io::IO, d::AbstractSublevelSet{T,:open}) where {T} =
    print(io, "sublevel set f(x) < $(level(d)) with f = $(levelfun(d))")

==(d1::AbstractSublevelSet, d2::AbstractSublevelSet) = levelfun(d1)==levelfun(d2) &&
    level(d1)==level(d2)
hash(d::AbstractSublevelSet, h::UInt) =
    hashrec("AbstractSublevelSet", levelfun(d), level(d), h)

"The domain where `f(x) <= 0` (or `f(x) < 0`)."
struct SubzeroSet{T,C,F} <: AbstractSublevelSet{T,C}
    f   ::  F
end

SubzeroSet(f) = SubzeroSet{Float64}(f)
SubzeroSet{T}(f) where {T} = SubzeroSet{T,:closed}(f)
SubzeroSet{T,C}(f::F) where {T,C,F} = SubzeroSet{T,C,F}(f)

level(d::SubzeroSet) = 0

similardomain(d::SubzeroSet{S,C}, ::Type{T}) where {S,C,T} =
    SubzeroSet{T,C}(levelfun(d))

interior(d::SubzeroSet{T}) where {T} = SubzeroSet{T,:open}(d.f)
closure(d::SubzeroSet{T}) where {T} = SubzeroSet{T,:closed}(d.f)

boundary(d::SubzeroSet{T}) where {T} = ZeroSet{T}(d.f)

"The domain defined by `f(x) <= C` (or `f(x) < C`) for a given function `f` and constant `C`."
struct SublevelSet{T,C,F,S} <: AbstractSublevelSet{T,C}
    f       ::  F
    level   ::  S
end

SublevelSet(f, level) = SublevelSet{typeof(level)}(f, level)
SublevelSet{T}(f, level) where {T} = SublevelSet{T,:closed}(f, level)
SublevelSet{T,C}(f::F, level::S) where {T,C,F,S} = SublevelSet{T,C,F,S}(f, level)

similardomain(d::SublevelSet{S,C}, ::Type{T}) where {S,C,T} =
    SublevelSet{T,C}(levelfun(d), level(d))

interior(d::SublevelSet{T}) where {T} = SublevelSet{T,:open}(d.f, d.level)
closure(d::SublevelSet{T}) where {T} = SublevelSet{T,:closed}(d.f, d.level)

boundary(d::SublevelSet{T}) where {T} = LevelSet{T}(d.f, d.level)

"Supertype of superlevel set domains."
abstract type AbstractSuperlevelSet{T,C} <: FunctionLevelSet{T} end

indomain(x, d::AbstractSuperlevelSet{T,:closed}) where {T} = levelfun(d, x) >= level(d)
indomain(x, d::AbstractSuperlevelSet{T,:open}) where {T} = levelfun(d, x) > level(d)

show(io::IO, d::AbstractSuperlevelSet{T,:closed}) where {T} =
    print(io, "superlevel set f(x) >= $(level(d)) with f = $(levelfun(d))")
show(io::IO, d::AbstractSuperlevelSet{T,:open}) where {T} =
    print(io, "superlevel set f(x) > $(level(d)) with f = $(levelfun(d))")

==(d1::AbstractSuperlevelSet, d2::AbstractSuperlevelSet) =
    levelfun(d1)==levelfun(d2) && level(d1)==level(d2)
hash(d::AbstractSuperlevelSet, h::UInt) =
    hashrec("AbstractSuperlevelSet", levelfun(d), level(d), h)

"The domain where `f(x) >= 0` (or `f(x) > 0`)."
struct SuperzeroSet{T,C,F} <: AbstractSuperlevelSet{T,C}
    f   ::  F
end

SuperzeroSet(f) = SuperzeroSet{Float64}(f)
SuperzeroSet{T}(f) where {T} = SuperzeroSet{T,:closed}(f)
SuperzeroSet{T,C}(f::F) where {T,C,F} = SuperzeroSet{T,C,F}(f)

level(d::SuperzeroSet) = 0

similardomain(d::SuperzeroSet{S,C}, ::Type{T}) where {S,C,T} =
    SuperzeroSet{T,C}(levelfun(d))

interior(d::SuperzeroSet{T}) where {T} = SuperzeroSet{T,:open}(d.f)
closure(d::SuperzeroSet{T}) where {T} = SuperzeroSet{T,:closed}(d.f)

boundary(d::SuperzeroSet{T}) where {T} = ZeroSet{T}(d.f)

"The domain defined by `f(x) >= C` (or `f(x) > C`) for a given function `f` and constant `C`."
struct SuperlevelSet{T,C,F,S} <: AbstractSuperlevelSet{T,C}
    f       ::  F
    level   ::  S
end

SuperlevelSet(f, level) = SuperlevelSet{typeof(level)}(f, level)
SuperlevelSet{T}(f, level) where {T} = SuperlevelSet{T,:closed}(f, level)
SuperlevelSet{T,C}(f::F, level::S) where {T,C,F,S} = SuperlevelSet{T,C,F,S}(f, level)

similardomain(d::SuperlevelSet{S,C}, ::Type{T}) where {S,C,T} =
    SuperlevelSet{T,C}(levelfun(d), level(d))

interior(d::SuperlevelSet{T}) where {T} = SuperlevelSet{T,:open}(d.f, d.level)
closure(d::SuperlevelSet{T}) where {T} = SuperlevelSet{T,:closed}(d.f, d.level)

boundary(d::SuperlevelSet{T}) where {T} = LevelSet{T}(d.f, d.level)

## Additional functionality

pseudolevel(d::AbstractLevelSet, epsilon) =
    _pseudolevel(d, epsilon, levelfun(d), level(d))
_pseudolevel(d::AbstractLevelSet{T}, epsilon, fun, C) where {T} =
    SublevelSet{T,:open}(x -> norm(fun(x)-C), epsilon)

pseudolevel(d::Domain, epsilon) = pseudolevel(convert(LevelSet, d), epsilon)


using MIRT
using MIRTjim: jim
using MATLAB
using Sinograms: bdd_2d
using Plots

# setup matlab to use MIRT
if !@isdefined(irtdir)
	ENV["MATLAB_ROOT"] = "/Applications/matlab"

	irtdir = "/Users/soniakim/Documents/matlab"
	tmp = "addpath('$irtdir')"
	eval_string(tmp)
	mat"setup"
end

deg = 1
geo = (DSD = 100000, DSO = 99700, pSize = 1, dSize = 0.5, nPix = 256, nDet = 1024,
        theta = deg2rad.(0:deg:360-deg), isoX = 0, isoY = 0)

"""
	phantomImg = shepp_logan(pixel, version)
Generates a shepp-logan phantom image for testing of image reconstruction algorithms,
for a pixel size and a version of shepp-logan
"""
phantomImg = shepp_logan(geo.nPix, SheppLoganToft())
sinogramB = projection(phantomImg',geo)
imageB = backprojection(sinogramB,geo)

imFP_julia = sinogramB'

@mput deg, geo, phantomImg, sinogramB

mat"imFP_mat = sinogramB'"

@mget imFP_mat

plot(jim(1:geo.nDet, 1:length(geo.theta), imFP_julia, "julia_FP"; aspect_ratio = :auto, clim=(0,68)),
	 jim(1:geo.nDet, 1:length(geo.theta), imFP_mat, "matlab_FP"; aspect_ratio = :auto, clim=(0,68)),
	 jim(imFP_mat - imFP_julia))

imBP_julia = imageB'

@mput deg, geo, phantomImg, imageB

mat"imBP_mat = imageB'"

@mget imBP_mat

plot!(jim(1:geo.nPix, 1:geo.nPix, imBP_julia, "julia_BP"; aspect_ratio = :auto, clim=(30,95)),
	  jim(1:geo.nPix, 1:geo.nPix, imBP_mat, "matlab_BP"; aspect_ratio = :auto, clim=(30,95)),
	  jim(imBP_mat - imBP_julia, "diff"))


#= -------------------------------------------------------------------------
# @file problema1_sin_disipador_sundials.jl
#
# @date 11/27/17 18:07:28
# @author Martin Noblia
# @email mnoblia@disroot.org
#
# @brief
#
# @detail
#
#  Licence:
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
---------------------------------------------------------------------------=#
import Sundials
using PyPlot

const  C_j   =  0.5
const  C_c   =  6.8
const  R_jc  =  1.0
const  R_ca  =  30
const  T_a   =  30
P_d  = (150 - 30) / (1+30)
a_11 = (-1 / C_c) * ( ( 1 / R_jc ) + ( 1 / R_ca ))
a_12 = (1 / (R_jc * C_c))
a_21 = (1 / (C_j * R_jc))
a_22 = (-1/ (C_j * R_jc))

step(t) = (t >=0.0) ? P_d: 0.0
pulse(t) = (P_d * sin(2.0*π*100.0*t) + P_d) / 2.0
"""
docs
"""
function system(t, T, dT)
   dT[1] = a_11 * T[1] + a_12 * T[2] + (T_a / (R_ca * C_c))
   dT[2] = a_21 * T[1] + a_22 * T[2] + (pulse(t) / C_j)
end

T₀ = [0.0, 0.0] # initial condition
t = linspace(0, 1300, 1000000) # time vector
sol = Sundials.cvode(system, T₀, collect(t))
# plot the results
plot(t, sol[:,1], label=L"T_c")
plot(t, sol[:,2], label=L"T_j")
xlabel(L"t")
ylabel(L"T")
legend()


using MultipleViewGeometry, Test, LinearAlgebra

𝐊₁ = Matrix{Float64}(I, 3, 3)
𝐑₁ = Matrix{Float64}(I, 3, 3)
𝐭₁ = [1.0, 1.0, 1.0]
𝐊₂ = Matrix{Float64}(I, 3, 3)
𝐑₂ = Matrix{Float64}(I, 3, 3)
𝐭₂ = [2.0, 2.0, 2.0]

@test construct(FundamentalMatrix(),𝐊₁,𝐑₁,𝐭₁,𝐊₂,𝐑₂,𝐭₂) == vec2antisym([-1,-1,-1])


import Limbdark: Mn_series_coeff!

# Randomizer seed
Random.seed!(42)

#@testset "transit_poly" begin

# Now, integrate by hand:
function transit_poly_int(r,b,u,ns)
n = length(u)
if b < (1.0+r)
  s_1 = maximum([0.0,b-r]); s_2=minimum([1.0,b+r])
  ds=(s_2-s_1)/ns
  s = linearspace(s_1+.5*ds,s_2-.5*ds,ns)
  fobs = zero(r)
  for j=1:ns
    if s[j] < r-b
      dphi = 2pi
    else
      dphi = 2*acos((s[j]^2+b^2-r^2)/(2*b*s[j]))
    end
    z = sqrt(1-s[j]^2)
    imu = 1.0
    for i=1:n
      imu -= u[i]*(1-z)^i
    end
    fobs += s[j]*ds*dphi*imu
  end
  norm = 1.
  for i=1:n
    norm -= 2*u[i]/(2+3*i+i^2)
  end
  fobs /= pi*norm
  fobs = 1-fobs
  return fobs
else
  return 1.0
end
end

r0=[0.01,0.01,0.01,0.01,0.01,0.1,0.1,0.1,0.1,0.1,1.0,1.0,1.0,10.,10.,10.,100.,100.,100.]
b0=[0.,0.01,0.99,1.0,1.01, 0.,0.1,0.9,1.0,1.1, 0.,1.0,2.0, 9.,10.,11., 99.,100.,101.]

n = 2+ceil(Int64,rand()*20); u = rand(n); u *= rand()/sum(u)

ns = 5000
trans = transit_init(r0[1],b0[1],u,false)
for i=1:length(r0)
  trans.r = r0[i]; trans.b = b0[i]
  flux = transit_poly!(trans)
  @time flux = transit_poly!(trans)
  f_num = transit_poly_int(r0[i],b0[i],u,ns)
  @time f_num = transit_poly_int(r0[i],b0[i],u,ns)
  println("r: ",r0[i]," b: ",b0[i]," f_an: ",flux," f_num: ",f_num," diff: ",flux-f_num)
  @test isapprox(f_num,flux,atol=1e-5)
end

# Now, compute light curves and plot:

npts = 1000
iu = 30
u_n = ones(iu) / iu
b0 = zeros(npts)
for i=1:npts
    b0[i] = 3.0 * (i / float(npts) - 0.5)
end

lc_ana = zeros(npts); lc_num=zeros(npts)
rtest = 0.1
trans = transit_init(rtest,b0[1],u_n,false)

for i=1:length(b0)
#  if mod(i,10) == 0
#    println("i: ",i," b: ",b)
#  end
#  lc_ana[i] = transit_poly(r,abs(b),u_n)
  trans.b = abs(b0[i])
  lc_ana[i] = transit_poly!(trans)
  lc_num[i] = transit_poly_int(rtest,abs(b0[i]),u_n,ns)
end

using PyPlot

plot(b0,lc_ana, linewidth=2, label="Analytic")
plot(b0,lc_num, linewidth=1, label="Numeric")

println("Maximum difference of lightcurve for N=",iu,": ",maximum(abs,lc_ana-lc_num))
@test isapprox(lc_ana,lc_num,atol=1e-5)

#end


using Base.Math: @horner, significand_bits, exponent_raw_max, exponent_bias

if VERSION < v"0.7.0-DEV.1430"
    using Base.Math: fpinttype
else
    using Base: uinttype
end

# log_fast from
# https://github.com/musm/SLEEF.jl/blob/c9dcd2eb090d69ec40790f19798c5fef2aba2616/src/log.jl

const MLN2  = 6.931471805599453094172321214581765680755001343602552541206800094933936219696955e-01 # log(2)

@inline float2integer(d::Float64) = (reinterpret(Int64, d) >> significand_bits(Float64)) % Int
@inline float2integer(d::Float32) = (reinterpret(Int32, d) >> significand_bits(Float32)) % Int

@inline function ilogb2k(d::T) where {T<:Union{Float32,Float64}}
    (float2integer(d) & exponent_raw_max(T)) - exponent_bias(T)
end

@inline function ldexp3k(x::T, e::Int) where {T<:Union{Float32,Float64}}
    @static if VERSION < v"0.7.0-DEV.1430"
        reinterpret(T, reinterpret(Unsigned, x) + (Int64(e) << significand_bits(T)) % fpinttype(T))
    else
        reinterpret(T, reinterpret(Unsigned, x) + (Int64(e) << significand_bits(T)) % uinttype(T))
    end
end

"""
    log_fast(x)
Compute the natural logarithm of `x`. The inverse of the natural logarithm is
the natural expoenential function `exp(x)`
"""
function log_fast end

let
global log_fast

c8d = 0.153487338491425068243146
c7d = 0.152519917006351951593857
c6d = 0.181863266251982985677316
c5d = 0.222221366518767365905163
c4d = 0.285714294746548025383248
c3d = 0.399999999950799600689777
c2d = 0.6666666666667778740063
c1d = 2.0

c5f = 0.2392828464508056640625f0
c4f = 0.28518211841583251953125f0
c3f = 0.400005877017974853515625f0
c2f = 0.666666686534881591796875f0
c1f = 2f0

global @inline log_fast_kernel(x::Float64) = @horner x c1d c2d c3d c4d c5d c6d c7d c8d
global @inline log_fast_kernel(x::Float32) = @horner x c1f c2f c3f c4f c5f

function log_fast(d::T) where {T<:Union{Float32,Float64}}
    o = d < realmin(T)
    o && (d *= T(Int64(1) << 32) * T(Int64(1) << 32))

    e = ilogb2k(d * T(1.0/0.75))
    m = ldexp3k(d, -e)
    o && (e -= 64)

    x  = (m - 1) / (m + 1)
    x2 = x * x

    t = log_fast_kernel(x2)

    x = x * t + T(MLN2) * e

    isinf(d) && (x = T(Inf))
    (d < 0 || isnan(d)) && (x = T(NaN))
    d == 0 && (x = -T(Inf))

    return x
end
end

log_fast(x::Union{Int32,Int64}) = log_fast(float(x))

# Derivatives

@init @require ForwardDiff="f6369f11-7733-5829-9624-2563aa707210" begin
  function log_fast(d::ForwardDiff.Dual{T,<:Union{Float32,Float64}}) where T
    x = ForwardDiff.value(d)
    Dual{T}(log_fast(x), inv(x) * ForwardDiff.partials(d))
  end
end


@doc raw"""
    CircleGroup <: GroupManifold{Circle{ℂ},MultiplicationOperation}

The circle group is the complex circle ([`Circle(ℂ)`](@ref)) equipped with
the group operation of complex multiplication ([`MultiplicationOperation`](@ref)).
"""
const CircleGroup = GroupManifold{ℂ,Circle{ℂ},MultiplicationOperation}

CircleGroup() = GroupManifold(Circle{ℂ}(), MultiplicationOperation())

@inline function active_traits(f, M::CircleGroup, args...)
    return merge_traits(
        IsGroupManifold(M.op),
        IsDefaultMetric(EuclideanMetric()),
        HasBiinvariantMetric(),
        active_traits(f, M.manifold, args...),
        IsExplicitDecorator(),
    )
end

Base.show(io::IO, ::CircleGroup) = print(io, "CircleGroup()")

adjoint_action(::CircleGroup, p, X) = X

adjoint_action!(::CircleGroup, Y, p, X) = copyto!(Y, X)

function compose(
    ::MultiplicationGroupTrait,
    G::CircleGroup,
    p::AbstractArray{<:Any,0},
    q::AbstractArray{<:Any,0},
)
    return map((pp, qq) -> compose(G, pp, qq), p, q)
end

function compose!(
    ::MultiplicationGroupTrait,
    G::CircleGroup,
    x,
    p::AbstractArray{<:Any,0},
    q::AbstractArray{<:Any,0},
)
    return copyto!(x, compose(G, p, q))
end

identity_element(G::CircleGroup) = 1.0
identity_element(::CircleGroup, p::Number) = one(p)

Base.inv(G::CircleGroup, p::AbstractArray{<:Any,0}) = map(pp -> inv(G, pp), p)

function inverse_translate(
    ::CircleGroup,
    p::AbstractArray{<:Any,0},
    q::AbstractArray{<:Any,0},
    ::LeftAction,
)
    return map(/, q, p)
end
function inverse_translate(
    ::CircleGroup,
    p::AbstractArray{<:Any,0},
    q::AbstractArray{<:Any,0},
    ::RightAction,
)
    return map(/, q, p)
end

lie_bracket(::CircleGroup, X, Y) = zero(X)

lie_bracket!(::CircleGroup, Z, X, Y) = fill!(Z, 0)

translate_diff(::GT, p, q, X, ::ActionDirection) where {GT<:CircleGroup} = map(*, p, X)
function translate_diff(
    ::CircleGroup,
    ::Identity{MultiplicationOperation},
    q,
    X,
    ::ActionDirection,
)
    return X
end

function translate_diff!(G::CircleGroup, Y, p, q, X, conv::ActionDirection)
    return copyto!(Y, translate_diff(G, p, q, X, conv))
end

function exp_lie(::CircleGroup, X)
    return map(X) do imθ
        θ = imag(imθ)
        sinθ, cosθ = sincos(θ)
        return Complex(cosθ, sinθ)
    end
end

exp_lie!(G::CircleGroup, q, X) = (q .= exp_lie(G, X))

function _log_lie(::CircleGroup, q)
    return map(q) do z
        cosθ, sinθ = reim(z)
        θ = atan(sinθ, cosθ)
        return θ * im
    end
end

_log_lie!(G::CircleGroup, X, q) = (X .= _log_lie(G, q))

function number_of_coordinates(G::CircleGroup, B::AbstractBasis)
    return number_of_coordinates(base_manifold(G), B)
end

@doc raw"""
    RealCircleGroup <: GroupManifold{Circle{ℝ},AdditionOperation}

The real circle group is the real circle ([`Circle(ℝ)`](@ref)) equipped with
the group operation of addition ([`AdditionOperation`](@ref)).
"""
const RealCircleGroup = GroupManifold{ℝ,Circle{ℝ},AdditionOperation}

RealCircleGroup() = GroupManifold(Circle{ℝ}(), AdditionOperation())

@inline function active_traits(f, M::RealCircleGroup, args...)
    return merge_traits(
        IsGroupManifold(M.op),
        IsDefaultMetric(EuclideanMetric()),
        HasBiinvariantMetric(),
        active_traits(f, M.manifold, args...),
        IsExplicitDecorator(),
    )
end

Base.show(io::IO, ::RealCircleGroup) = print(io, "RealCircleGroup()")

is_default_metric(::RealCircleGroup, ::EuclideanMetric) = true

# Lazy overwrite since this is a rare case of nonmutating foo.
compose(::RealCircleGroup, p, q) = sym_rem(p + q)
compose(::RealCircleGroup, ::Identity{AdditionOperation}, q) = sym_rem(q)
compose(::RealCircleGroup, p, ::Identity{AdditionOperation}) = sym_rem(p)
function compose(
    ::RealCircleGroup,
    e::Identity{AdditionOperation},
    ::Identity{AdditionOperation},
)
    return e
end

compose!(::RealCircleGroup, x, p, q) = copyto!(x, sym_rem(p + q))
compose!(::RealCircleGroup, x, ::Identity{AdditionOperation}, q) = copyto!(x, sym_rem(q))
compose!(::RealCircleGroup, x, p, ::Identity{AdditionOperation}) = copyto!(x, sym_rem(p))
function compose!(
    ::RealCircleGroup,
    ::Identity{AdditionOperation},
    e::Identity{AdditionOperation},
    ::Identity{AdditionOperation},
)
    return e
end

identity_element(G::RealCircleGroup) = 0.0
identity_element(::RealCircleGroup, p::AbstractArray) = map(i -> zero(eltype(p)), p)

Base.inv(G::RealCircleGroup, p::AbstractArray{<:Any,0}) = map(pp -> inv(G, pp), p)

function inverse_translate(
    ::RealCircleGroup,
    p::AbstractArray{<:Any,0},
    q::AbstractArray{<:Any,0},
    ::LeftAction,
)
    return map((x, y) -> sym_rem(x - y), q, p)
end
function inverse_translate(
    ::RealCircleGroup,
    p::AbstractArray{<:Any,0},
    q::AbstractArray{<:Any,0},
    ::RightAction,
)
    return map((x, y) -> sym_rem(x - y), q, p)
end

function exp_lie(::RealCircleGroup, X)
    return sym_rem(X)
end

exp_lie!(G::RealCircleGroup, q, X) = (q .= sym_rem(X))


# ---
# title: 378. Kth Smallest Element in a Sorted Matrix
# id: problem378
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Binary Search, Heap
# link: <https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix/description/>
# hidden: true
# ---
#
# Given a _n_ x _n_ matrix where each of the rows and columns are sorted in
# ascending order, find the kth smallest element in the matrix.
#
# Note that it is the kth smallest element in the sorted order, not the kth
# distinct element.
#
# **Example:**
#
#
#
#     matrix = [
#        [ 1,  5,  9],
#        [10, 11, 13],
#        [12, 13, 15]
#     ],
#     k = 8,
#
#     return 13.
#
#
# **Note:**
# You may assume k is always valid, 1 ≤ k ≤ n2.
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


module NBTest_Water_Analysis
using NBTesting
is_testing = true

NBTesting.clear_test_times!()

# -----------------------------------
println("# Top title"); flush(STDOUT)

# Cell #1
using NBTesting
using Base.Test

using PyPlot

# This defines
#     plot(args...; kwargs...) = nothing
# but only during testing.
@testing_noop plot

# -----------------------------------
println("### Basic Arithmetic"); flush(STDOUT)

# Cell #2
x = 10 / 2
@test x+x == 10
@test 6*10 == 60

# Cell #3
N = is_testing ? 5 : 100

srand(1)
M1 = ones(N, N)
M2 = rand(N, N);

if is_testing
    @test 30 < sum(M1 * M2) < 100
end

sum(M1 * M2)

# -----------------------------------
println("#### Some plot"); flush(STDOUT)

# Cell #4
plot([1,2,1]);

end  # module


# Some thresholds, implemented as types so we can save them with JLD2
# (which does not allow saving functions directly)

# Threshold from Optimal Best Arm Identification with Fixed Confidence
# (Garivier and Kaufmann 2016). Recommended in section 6.
struct GK16
    δ;
end

function (β::GK16)(t)
    log((log(t)+1)/β.δ)
end


#   Copyright (c) 2020-2022 Velexi Corporation
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

"""
add.jl defines methods for the +(x, y) function
"""

# --- Exports

import Base.:(+)

# --- Method definitions

# ------ Specializations involving an AbstractMultivector instance

# M::AbstractMultivector, B::One
# B::One,M::AbstractMultivector
@inline +(M::AbstractMultivector, B::One) = Multivector(vcat([B], blades(M)))
@inline +(B::One, M::AbstractMultivector) = B + M

# M::AbstractMultivector, B::Zero
# B::Zero, M::AbstractMultivector
@inline +(M::AbstractMultivector, B::Zero) = M
@inline +(B::Zero, M::AbstractMultivector) = M

# ------ Specializations involving a Blade instance

# B::Blade, C::Blade
function +(B::Blade, C::Blade)
    assert_dim_equal(B, C)

    if grade(B) == grade(C) == 1
        return Blade(volume(B) * basis(B) + volume(C) * basis(C))
    end

    # TODO: general blade sum
end

# ------ Specializations involving a Pseudoscalar instance

# B::Pseudoscalar, C::Pseudoscalar
@inline +(B::Pseudoscalar, C::Pseudoscalar) =
    Pseudoscalar(B, value=value(B) + value(C))

# ------ Specializations involving an AbstractScalar instance

# B::AbstractScalar, C::AbstractScalar
@inline +(B::AbstractScalar, C::AbstractScalar) =
    Scalar{typeof(value(B))}(value(B) + value(C))

# B::AbstractScalar, C::One
# B::One, C::AbstractScalar
@inline +(B::AbstractScalar, C::One) = Scalar{typeof(value(B))}(value(B) + 1)
@inline +(B::One, C::AbstractScalar) = C + B

# B::AbstractScalar, C::Zero
# B::Zero, C::AbstractScalar
@inline +(B::AbstractScalar, C::Zero) = B
@inline +(B::Zero, C::AbstractScalar) = C

# B::AbstractScalar, x::Real
# x::Real, B::AbstractScalar
@inline +(B::AbstractScalar, x::Real) = Scalar{typeof(value(B))}(value(B) + x)
@inline +(x::Real, B::AbstractScalar) = B + x

# ------ Specializations involving a One instance

# B::One, C::One
@inline +(B::One, C::One) = Scalar{typeof(value(B))}(2)

# B::One, C::Zero
# B::Zero, C::One
@inline +(B::One, C::Zero) = B
@inline +(B::Zero, C::One) = C

# ------ Specializations involving a Zero instance

# B::Zero, C::Zero
@inline +(B::Zero, C::Zero) = B

# B::Zero, v::Vector
# v::Vector, B::Zero
@inline +(B::Zero, v::Vector{<:Real}) = v
@inline +(v::Vector{<:Real}, B::Zero) = v


using TruncatedNormal, Test, SpecialFunctions
using TruncatedNormal: xerfcx_asym, xerfcx_asym_pi

@test isnan(erf(NaN, NaN))
@test isnan(erf(0, NaN))
@test isnan(erf(NaN, 0))

@test erf(0, 0) == 0
@test erf(+1, +1) == 0
@test erf(-1, -1) == 0

@test erf(0, +Inf) == +1
@test erf(-Inf, 0) == +1
@test erf(+Inf, 0) == -1
@test erf(0, -Inf) == -1

@test erf(-1, 1) ≈ 1.68540158589942973868244127017
@test erf(-2, 2) ≈ 1.99064453003790546832413851273
@test erf(-5, 5) ≈ 1.99999999999692508041114393030

@test xerfcx_asym(500, 3) ≈ 500erfcx(500)
@test xerfcx_asym(1e6, 3) ≈ 1e6erfcx(1e6)
@test xerfcx_asym_pi(500, 3) ≈ 1.12837239688821180302500663804e-6
@test xerfcx_asym_pi(9e5, 3) ≈ 3.48265175028834253964811258438e-13
@test xerfcx_asym_pi(1e6, 3) ≈ 2.82094791773455001286379966421e-13