module TestBarnesHut
using LinearAlgebra
using CovarianceFunctions
using CovarianceFunctions: BarnesHutFactorization, barneshut!, vector_of_static_vectors,
        node_sums, euclidean, GradientKernel, taylor!
using NearestNeighbors
using NearestNeighbors: isleaf, getleft, getright, get_leaf_range
using Test

function barneshut_no_far_field!(b::AbstractVector, F::BarnesHutFactorization, w::AbstractVector,
                    α::Number = 1, β::Number = 0, θ::Real = F.θ)
    D = length(eltype(F.x))
    sums_a = node_sums(w, F.Tree)
    # centers_of_mass = compute_centers_of_mass(F.y, w, F.Tree)
    centers_of_mass = [hyp.center for hyp in F.Tree.hyper_spheres]
    for i in eachindex(F.x)
        Fa_i = bh_recursion_no_far_field(1, F.k, F.x[i], F.y, w, sums_a, θ, F.Tree, centers_of_mass)
        b[i] = α * Fa_i + β * b[i]
    end
    return b
end

function bh_recursion_no_far_field(index, k, x, y::AbstractVector,
            w::AbstractVector, sums_w::AbstractVector{<:Number}, θ::Real, T::BallTree, centers_of_mass)
    h = T.hyper_spheres[index]
    if isleaf(T.tree_data.n_internal_nodes, index) # do direct computation
        y_ind = get_leaf_range(T.tree_data, index)
        y_i, w_i = @views y[T.indices[y_ind]], w[T.indices[y_ind]]

        elty = promote_type(eltype(x), eltype(eltype(y_i)), eltype(w_i))
        val = zero(elty)
        @inbounds @simd for j in eachindex(y_i)
          val += k(x, y_i[j]) * w_i[j]
        end
        return val

    elseif h.r < θ * euclidean(x, centers_of_mass[index]) # compress

        return 0

    else # recurse

        l = bh_recursion_no_far_field(getleft(index), k, x, y, w, sums_w, θ, T, centers_of_mass) # IDEA: these two are spawnable
        r = bh_recursion_no_far_field(getright(index), k, x, y, w, sums_w, θ, T, centers_of_mass)
        return l + r
    end
end

verbose = false
# IDEA: loop over element types, loop over kernels
@testset "barneshut" begin
    n = 1024
    d = 2
    rand_sign = rand(n) .* rand((-1, 1), n)
    weight_vectors = [ones(n), rand(n), rand_sign, randn(n)]
    weight_names = ["ones", "rand", "rand + sign", "randn"]

    X = randn(d, n)
    x = vector_of_static_vectors(X)
    # k = (x, y) -> inv(1 + sum(abs2, x-y))
    k = CovarianceFunctions.Cauchy()
    # k = (x, y) -> CovarianceFunctions.Exp()(x, y) # making it anonymous to test independence to kernel types
    K = gramian(k, x)
    # k = CovarianceFunctions.EQ()
    # k = function (x, y)
    #         r = sum(abs2, x-y)
    #         iszero(r) ? 0 : inv(r^2)
    #     end

    # test factorization syntax
    # leafsize = 16 # could also loop over this
    D = 1e-2
    F = BarnesHutFactorization(k, x, x, D; θ = 1/8, leafsize = 16)

    @testset "weight vector $name" for (w, name) in zip(weight_vectors, weight_names)
        # display(sum(eigvals(K + D*I) .> 1e-6))

        b = K*w + D*w
        b_bh = F*w
        @test isapprox(b, b_bh, rtol = 1e-3)
        w_bh = \(F, b_bh, maxiter = 128, verbose = false)
        @test isapprox(b_bh, F*w_bh, rtol = 1e-3)
        # @test isapprox(w, w_bh, rtol = 1e-3) # usually 1e-2 and less robust to smaller diagonal
        if verbose
            println("relative errors")
            println(norm(b-b_bh) / norm(b))
            println(norm(b_bh - F*w_bh) / norm(b_bh))
            println(norm(w - w_bh) / norm(w))
        end
        θ = 0
        b_bh = zero(b)
        barneshut!(b_bh, F, w, 1, 0, θ)
        @test b ≈ b_bh # testing that barnes hut is exact with θ = 0

        nexp = 16
        err = zeros(nexp)
        err_no_split = zeros(nexp)
        err_hyp = zeros(nexp)
        err_hyp_no_split = zeros(nexp)
        err_nff = zeros(nexp)
        err_taylor = zeros(nexp)
        theta_array = range(1e-1, 1, length = nexp)
        for (i, θ) in enumerate(theta_array)
            barneshut!(b_bh, F, w, 1, 0, θ, split = true)
            err[i] = norm(b - b_bh)

            barneshut!(b_bh, F, w, 1, 0, θ, split = false)
            err_no_split[i] = norm(b - b_bh)

            barneshut_no_far_field!(b_bh, F, w, 1, 0, θ) # compare against pseudo barnes hut where far field = 0
            err_nff[i] = norm(b - b_bh)

            taylor!(b_bh, F, w, 1, 0, θ) # compare against pseudo barnes hut where far field = 0
            err_taylor[i] = norm(b - b_bh)
        end

        rel_err = err / norm(b)
        @test all(<(1e-1), rel_err[1:findlast(<(0.5), theta_array)])
        @test all(<(1e-2), rel_err[1:findlast(<(0.2), theta_array)])

        # plotting code
        # using Plots
        # plotly()
        #
        # rel_err_nff = err_nff / norm(b)
        # rel_err_no_split = err_no_split / norm(b)
        # rel_err_taylor = err_taylor / norm(b)
        #
        # plot(theta_array, rel_err, yscale = :log10, label = "barneshut", ylabel = "relative error", xlabel = "θ")
        # plot!(theta_array, rel_err_no_split, yscale = :log10, label = "no split")
        # plot!(theta_array, rel_err_nff, yscale = :log10, label = "sparse")
        # plot!(theta_array, rel_err_taylor, yscale = :log10, label = "taylor")
        # gui()

    end # testset weight vectors

    # @testset "gradient kernels" begin
    #     k = CovarianceFunctions.Cauchy()
    #     g = CovarianceFunctions.GradientKernel(k)
    #
    #     F = BarnesHutFactorization(g, x)
    #     @test F isa BarnesHutFactorization
    #     @test eltype(F) <: Diagonal
    #     @test size(F) == (n, n)
    #     @test size(F[1, 1]) == (d_out, d_out)
    # end # testset matrix valued bh

end # testset

end # module


function randdom_search(objective::Function, search_space::Tuple{Real,Real};
    max_iter::Int=100, op = <)
    rnd() = search_space[1] + (search_space[2] - search_space[1]) * rand()
    best = objective(rnd())
    for i = 2:max_iter
        candidate = objective(rnd())
        op(candidate, best) && (best = candidate)
    end
    best
end


using PiecewiseDeterministicMarkovProcesses, Random, DifferentialEquations
	const PDMP = PiecewiseDeterministicMarkovProcesses

function F_tcp!(ẋ, xc, xd, parms, t)
	if mod(xd[1],2)==0
		 ẋ[1] =  1.0
		 ẋ[2] = -1.0 * xc[2]
	else
		 ẋ[1] = -1.0 * xc[1]
		 ẋ[2] =  1.0
	end
	nothing
end

R(x) = x

function R_tcp!(rate, xc, xd, parms, t, issum::Bool)
		rate[1] = R(xc[1]) +  R(xc[2])
		rate[2] = parms[1] * xd[1] * xc[1]
	if issum == false
		return 0.
	else
		return sum(rate)
	end
end

xc0 = [0.05, 0.075]
xd0 = [0, 1]

nu_tcp = [1 0;0 -1]
parms = [0.1]
tf = 10000.0
nj = 1000

Random.seed!(1234)
	problem = PDMP.PDMPProblem(F_tcp!, R_tcp!, nu_tcp, xc0, xd0, parms, (0.0, tf))
	result1 = @time PDMP.solve(problem, CHV(Tsit5()); n_jumps = nj, save_positions = (false, false))

Random.seed!(1234)
	result2 = PDMP.solve(problem, CHV(:cvode); n_jumps = nj, save_positions = (false, false))

Random.seed!(1234)
	result3 = PDMP.solve(problem, CHV(:lsoda); n_jumps = 2, save_positions = (false, false))

#test auto-differentiation
# result4 = @time PDMP.solve(problem, CHV(Rodas5()))

####################################################################################################
# DEBUG DEBUG
#
# algo = CHV(Tsit5())
# xd1 = zeros(Float64, length(xc0)+1)
# xdd1 = similar(xd1)
#
# J = zeros(3,3)
# algo(xdd1,xd1,problem.caract,0.)
#
# vf = (dx,x) -> algo(dx,x,problem.caract,0.)
# #it works!
# vf(xdd1,xd1)
#
# ForwardDiff.jacobian!(J, vf, xdd1, xd1)


using SpectralKit: __block_length, __cumulative_block_length, SmolyakIndices,
    __smolyak_length, SmolyakGridShuffle, SmolyakProduct

####
#### blocks
####

@testset "block length" begin
    @test __cumulative_block_length(0) == 1
    @test __cumulative_block_length(1) == 1 + 2
    @test __cumulative_block_length(2) == 1 + 2 + 2
    @test __cumulative_block_length(3) == 1 + 2 + 2 + 4
    @test __cumulative_block_length(4) == 1 + 2 + 2 + 4 + 8
    for i in 1:10
        c = __cumulative_block_length(i)
        cprev = i == 0 ? 0 : __cumulative_block_length(i - 1)
        @test __block_length(i) == c - cprev
    end
end

@testset "block shuffle" begin
    results = Dict([0 => [1],
                    1 => [2, 1, 3],
                    2 => [3, 1, 5, 2, 4],
                    3 => vcat([5, 1, 9, 3, 7], 2:2:8),
                    4 => vcat([9, 1, 17, 5, 13], 3:4:17, 2:2:17)])
    for (b, i) in pairs(results)
        len = length(i)
        @test len == __cumulative_block_length(b)
        ι = SmolyakGridShuffle(len)
        @test length(ι) == len
        @test @inferred eltype(ι) == Int
        @test collect(ι) == i
    end
end

####
#### traversal
####

"""
Naive implementation of Smolyan index iteration, traversing a `CartesianIndices` and keeping
valid indexes. For testing/comparison. Returns a vector of `indexes => blocks` pairs.
"""
function smolyak_indices_check(N, B, M)
    m = __cumulative_block_length(M)
    b_table = fill(M, m)
    for b in (M-1):(-1):0
        b_table[1:__cumulative_block_length(b)] .= b
    end
    T = NTuple{N,Int}
    result = Vector{Pair{T,T}}()
    for ι in CartesianIndices(ntuple(_ -> 1:m, N))
        ix = Tuple(ι)
        blocks = map(i -> b_table[i], ix)
        if sum(blocks) ≤ B
            push!(result, ix => blocks)
        end
    end
    result
end

@testset "Smolyak indices" begin
    for B in 0:3
        for M in 0:B
            for N in 1:4
                ι = SmolyakIndices{N}(SmolyakParameters(B, M))
                x1 = @inferred collect(ι)
                x2 = first.(smolyak_indices_check(N, B, M))
                len = @inferred __smolyak_length(Val(N), Val(B), M)
                @test x1 == x2
                @test len == length(x1) == length(ι) == length(x2)
            end
        end
    end
end

@testset "Smolyak product primitives" begin
    for B in 0:3
        for M in 0:B
            for N in 1:4
                ι = SmolyakIndices{N}(SmolyakParameters(B, M))
                ℓ = __cumulative_block_length(min(B,M))
                sources = SVector{N}([rand(SVector{ℓ, Float64}) for _ in 1:N])
                P = SmolyakProduct(ι, sources)
                @test length(ι) == length(P)
                @test eltype(P) == Float64
                for (i, p) in zip(ι, P)
                    @test prod(getindex.(sources, i)) ≈ p
                end
            end
        end
    end
end

####
#### api
####

@testset "Smolyak API sanity checks" begin
    f(x) = (x[1] - 3) * (x[2] + 5) # linear function, just a sanity check
    basis = smolyak_basis(Chebyshev, InteriorGrid(), SmolyakParameters(3),
                          (BoundedLinear(0, 4), BoundedLinear(0, 3)))
    @test @inferred(domain(basis)) == ((0, 4), (0, 3))
    x = @inferred grid(Float64, basis)
    M = @inferred collocation_matrix(basis, x)
    θ = M \ f.(x)
    @test sum(abs.(θ) .> 1e-8) == 4
    y1, y2 = range(domain(basis)[1]...; length = 100), range(domain(basis)[2]...; length = 100)
    for y1 in y1
        for y2 in y2
            y = SVector(y1, y2)
            @test linear_combination(basis, θ, y) ≈ f(y)
        end
    end
end

@testset "Smolyak API allocations" begin
    basis = smolyak_basis(Chebyshev, InteriorGrid(), SmolyakParameters(3),
                          (BoundedLinear(0, 4), BoundedLinear(0, 3)))
    y = SVector(1.0, 2.0)
    θ = randn(dimension(basis))
    @inferred linear_combination(basis, θ, y)
    @test @ballocated(linear_combination($basis, $θ, $y)) == 0
end

@testset "Smolyak approximation" begin
    function f(x)               # cross-exp is particularly nasty for Smolyak
        x1, x2, x3, x4 = x
        exp(0.3 * (x1 - 2.0) * (x2 - 1.0)) + exp(-x3 * (x2 + 5)) - 1 / log(x4^2 + 10)
    end
    basis = smolyak_basis(Chebyshev, InteriorGrid(), SmolyakParameters(5),
                          (BoundedLinear(0, 4), BoundedLinear(-2.0, 2.0),
                           SemiInfRational(1.0, 2.0), InfRational(0, 4)))
    x = grid(basis)
    θ = collocation_matrix(basis, x) \ f.(x)
    for x in x
        @test linear_combination(basis, θ, x) ≈ f(x) atol = 1e-10
    end
    s = SobolSeq([0, -2, 1, -10], [4, 2, 5, 10])
    skip(s, 1 << 5)
    Δabs, Δrel = mapreduce((x, y) -> max.(x, y), Iterators.take(s, 10^5)) do x
        fx = f(x)
        Δabs = abs(fx - linear_combination(basis, θ, x))
        Δrel = Δabs / (1 + abs(fx))
        Δabs, Δrel
    end
    @test Δabs ≤ 1e-3
    @test Δrel ≤ 5e-4
end

@testset "Smolyak derivatives" begin
    function f(x)
        x1, x2 = x
        7.0 + x1 + 2*x2 + 3*x2*x1
    end
    basis = smolyak_basis(Chebyshev, InteriorGrid(), SmolyakParameters(2),
                          (BoundedLinear(0, 4), BoundedLinear(-5.0, 5.0)))
    x = grid(basis)
    θ = collocation_matrix(basis, x) \ f.(x)
    F = linear_combination(basis, θ)
    for x in x
        @test F(x) ≈ f(x) atol = 1e-10
        @test gradient(F, x) ≈ gradient(f, x)
    end
end


# Welcome.

# This is a regular file, which you can edit as much as you want,
# then save for later or discard. You can open a fresh copy by
# pressing `Ctrl-Space` and typing `tutorial` followed by `Enter`.

# Use the "toggle console" command to see any output. If you have
# issues, please let me know at
# http://discuss.junolab.org/

# ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––

# In Juno we'll be using the Julia language. This is a Julia file and
# lines beginning with `#` are comments.

# Here's a very simple code block:

3 * 6 + 4

# We can evaluate code by right clicking on it and selecting "Evaluate".
# You should see the result, "22", pop up next to the code.

# It's quicker to use Ctrl-Enter (or Cmd-Enter on Mac). Try adding "+2"
# to this line and then evaluating.

2+2

# We can also define our own functions, such as this one which doubles a
# number:

function double(x)
  return 2x
end

# Click anywhere on the function definition and press `Ctrl-Enter`
# again. You should see a tick pop up next to the "end" to show that
# the function has been defined.

# Now we can call our function:

double(-4)

# After evaluating this you should see the result "20" pop up. You may
# also notice that the "10" becomes underlined. Try clicking and
# dragging it from side to side to double another number.

# We can do more advanced things, like dealing with matrices. Try

x = rand(5, 4)

# If you evaluate this you'll see "Matrix Float64, 5×5" pop up next to it.
# A bold header like this indicates that you can click on the box
# to expand it and see the whole matrix.

# If you're not sure what a function does it's easy to find out. Just right
# click on the function's name (like "rand" above) and click "Toggle Docs".
# The shortcut for this is Ctrl-D. You can also remove the documentation
# by right clicking and selecting "Remove result".

# Let's try installing a package, then do some visualisation.

Pkg.add("Images")

# If you start to type `Images` between the quotes, you should see the
# autocomplete pop up with a list of packages. Select `Images` with tab
# and evaluate to install the package. You'll see a working indicator
# in the bottom-left corner as the package is loaded. Type `Ctrl-Space`
# followed `toggle console` and `Enter` to see the output.

# Now you can load the package.

using Images

# Let's try visualising this function. You can type latex symbols by
# typing a `\` and selecting from the autocomplete, e.g. `\phi` or `\le`.
τ = 2π
# You can use `Shift-Enter` to quickly evaluate definitions.

const ϕ = golden

function foo(z)
  c = (φ-2)+(φ-1)im
  max = 80
  for n = 1:max
    abs(z) ≥ 2 && return n-1
    z = z^2 + c
  end
  return max
end

# Let's try this out – play around with the numbers!

foo(-1)
foo(5+0.75im)

# Let's apply foo to a grid of numbers. Don't worry too much if you don't
# understand this code, but make sure to evaluate it. (Bonus points for
# using that Ctrl-D trick here, though)

foo(x, y) = foo(x + y*im)

foo_grid(n) =
  broadcast(foo,
            linspace(-0.5, 1, n)',
            linspace(-1, 0.5, n))

# Can you see the pattern?

foo_grid(10)

# Let's try it as an image:

convert(Image, scale(foo_grid(500), 1/80))

# It's a Julia set, of course!

# If you expand the image you can also click on the `Dictionary`
# field to see some interesting properties about it (if that's the
# kind of thing that floats your goat).

# If you want to learn more about Julia, take a look at
# http://julialang.org/learning/

# There is also the Julia manual and the Juno documentation, both
# of which are available from the help menu above.

# Enjoy!


# ---
# title: 77. Combinations
# id: problem77
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Backtracking
# link: <https://leetcode.com/problems/combinations/description/>
# hidden: true
# ---
#
# Given two integers _n_ and _k_ , return all possible combinations of _k_
# numbers out of 1 ... _n_.
#
# You may return the answer in **any order**.
#
#
#
# **Example 1:**
#
#
#
#     Input: n = 4, k = 2
#     Output:
#     [
#       [2,4],
#       [3,4],
#       [2,3],
#       [1,2],
#       [1,3],
#       [1,4],
#     ]
#
#
# **Example 2:**
#
#
#
#     Input: n = 1, k = 1
#     Output: [[1]]
#
#
#
#
# **Constraints:**
#
#   * `1 <= n <= 20`
#   * `1 <= k <= n`
#
#
## @lc code=start
using LeetCode

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


# Text provided under a Creative Commons Attribution license, CC-BY.  All code is made available under the FSF-approved BSD-3 license.  (c) Lorena A. Barba, Gilbert F. Forsyth 2017. Thanks to NSF for support via CAREER award #1149784.
# [@LorenaABarba](https://twitter.com/LorenaABarba)
# Translation to Julia by [@miguelraz_](https://twitter.com/miguelraz_)

# 12 steps to Navier–Stokes
# =====
# ***

# Did you make it this far? This is the last step! How long did it take you to write your own Navier–Stokes solver in Python following this interactive module? Let us know!

# Step 12: Channel Flow with Navier–Stokes
# ----
# ***

# The only difference between this final step and Step 11 is that we are going to add a source term to the $u$-momentum equation, to mimic the effect of a pressure-driven channel flow. Here are our modified Navier–Stokes equations:

# $$\frac{\partial u}{\partial t}+u\frac{\partial u}{\partial x}+v\frac{\partial u}{\partial y}=-\frac{1}{\rho}\frac{\partial p}{\partial x}+\nu\left(\frac{\partial^2 u}{\partial x^2}+\frac{\partial^2 u}{\partial y^2}\right)+F$$

# $$\frac{\partial v}{\partial t}+u\frac{\partial v}{\partial x}+v\frac{\partial v}{\partial y}=-\frac{1}{\rho}\frac{\partial p}{\partial y}+\nu\left(\frac{\partial^2 v}{\partial x^2}+\frac{\partial^2 v}{\partial y^2}\right)$$

# $$\frac{\partial^2 p}{\partial x^2}+\frac{\partial^2 p}{\partial y^2}=-\rho\left(\frac{\partial u}{\partial x}\frac{\partial u}{\partial x}+2\frac{\partial u}{\partial y}\frac{\partial v}{\partial x}+\frac{\partial v}{\partial y}\frac{\partial v}{\partial y}\right)
# $$

# ### Discretized equations

# With patience and care, we write the discretized form of the equations. It is highly recommended that you write these in your own hand, mentally following each term as you write it.

# The $u$-momentum equation:

# $$
# \begin{split}
# & \frac{u_{i,j}^{n+1}-u_{i,j}^{n}}{\Delta t}+u_{i,j}^{n}\frac{u_{i,j}^{n}-u_{i-1,j}^{n}}{\Delta x}+v_{i,j}^{n}\frac{u_{i,j}^{n}-u_{i,j-1}^{n}}{\Delta y} = \\
# & \qquad -\frac{1}{\rho}\frac{p_{i+1,j}^{n}-p_{i-1,j}^{n}}{2\Delta x} \\
# & \qquad +\nu\left(\frac{u_{i+1,j}^{n}-2u_{i,j}^{n}+u_{i-1,j}^{n}}{\Delta x^2}+\frac{u_{i,j+1}^{n}-2u_{i,j}^{n}+u_{i,j-1}^{n}}{\Delta y^2}\right)+F_{i,j}
# \end{split}
# $$

# The $v$-momentum equation:

# $$
# \begin{split}
# & \frac{v_{i,j}^{n+1}-v_{i,j}^{n}}{\Delta t}+u_{i,j}^{n}\frac{v_{i,j}^{n}-v_{i-1,j}^{n}}{\Delta x}+v_{i,j}^{n}\frac{v_{i,j}^{n}-v_{i,j-1}^{n}}{\Delta y} = \\
# & \qquad -\frac{1}{\rho}\frac{p_{i,j+1}^{n}-p_{i,j-1}^{n}}{2\Delta y} \\
# & \qquad +\nu\left(\frac{v_{i+1,j}^{n}-2v_{i,j}^{n}+v_{i-1,j}^{n}}{\Delta x^2}+\frac{v_{i,j+1}^{n}-2v_{i,j}^{n}+v_{i,j-1}^{n}}{\Delta y^2}\right)
# \end{split}
# $$

# And the pressure equation:

# $$
# \begin{split}
# & \frac{p_{i+1,j}^{n}-2p_{i,j}^{n}+p_{i-1,j}^{n}}{\Delta x^2} + \frac{p_{i,j+1}^{n}-2p_{i,j}^{n}+p_{i,j-1}^{n}}{\Delta y^2} = \\
# & \qquad \rho\left[\frac{1}{\Delta t}\left(\frac{u_{i+1,j}-u_{i-1,j}}{2\Delta x}+\frac{v_{i,j+1}-v_{i,j-1}}{2\Delta y}\right) - \frac{u_{i+1,j}-u_{i-1,j}}{2\Delta x}\frac{u_{i+1,j}-u_{i-1,j}}{2\Delta x} - 2\frac{u_{i,j+1}-u_{i,j-1}}{2\Delta y}\frac{v_{i+1,j}-v_{i-1,j}}{2\Delta x} - \frac{v_{i,j+1}-v_{i,j-1}}{2\Delta y}\frac{v_{i,j+1}-v_{i,j-1}}{2\Delta y}\right]
# \end{split}
# $$

# As always, we need to re-arrange these equations to the form we need in the code to make the iterations proceed.

# For the $u$- and $v$ momentum equations, we isolate the velocity at time step `n+1`:

# $$
# \begin{split}
# u_{i,j}^{n+1} = u_{i,j}^{n} & - u_{i,j}^{n} \frac{\Delta t}{\Delta x} \left(u_{i,j}^{n}-u_{i-1,j}^{n}\right) - v_{i,j}^{n} \frac{\Delta t}{\Delta y} \left(u_{i,j}^{n}-u_{i,j-1}^{n}\right) \\
# & - \frac{\Delta t}{\rho 2\Delta x} \left(p_{i+1,j}^{n}-p_{i-1,j}^{n}\right) \\
# & + \nu\left[\frac{\Delta t}{\Delta x^2} \left(u_{i+1,j}^{n}-2u_{i,j}^{n}+u_{i-1,j}^{n}\right) + \frac{\Delta t}{\Delta y^2} \left(u_{i,j+1}^{n}-2u_{i,j}^{n}+u_{i,j-1}^{n}\right)\right] \\
# & + \Delta t F
# \end{split}
# $$

# $$
# \begin{split}
# v_{i,j}^{n+1} = v_{i,j}^{n} & - u_{i,j}^{n} \frac{\Delta t}{\Delta x} \left(v_{i,j}^{n}-v_{i-1,j}^{n}\right) - v_{i,j}^{n} \frac{\Delta t}{\Delta y} \left(v_{i,j}^{n}-v_{i,j-1}^{n}\right) \\
# & - \frac{\Delta t}{\rho 2\Delta y} \left(p_{i,j+1}^{n}-p_{i,j-1}^{n}\right) \\
# & + \nu\left[\frac{\Delta t}{\Delta x^2} \left(v_{i+1,j}^{n}-2v_{i,j}^{n}+v_{i-1,j}^{n}\right) + \frac{\Delta t}{\Delta y^2} \left(v_{i,j+1}^{n}-2v_{i,j}^{n}+v_{i,j-1}^{n}\right)\right]
# \end{split}
# $$

# And for the pressure equation, we isolate the term $p_{i,j}^n$ to iterate in pseudo-time:

# $$
# \begin{split}
# p_{i,j}^{n} = & \frac{\left(p_{i+1,j}^{n}+p_{i-1,j}^{n}\right) \Delta y^2 + \left(p_{i,j+1}^{n}+p_{i,j-1}^{n}\right) \Delta x^2}{2(\Delta x^2+\Delta y^2)} \\
# & -\frac{\rho\Delta x^2\Delta y^2}{2\left(\Delta x^2+\Delta y^2\right)} \\
# & \times \left[\frac{1}{\Delta t} \left(\frac{u_{i+1,j}-u_{i-1,j}}{2\Delta x} + \frac{v_{i,j+1}-v_{i,j-1}}{2\Delta y}\right) - \frac{u_{i+1,j}-u_{i-1,j}}{2\Delta x}\frac{u_{i+1,j}-u_{i-1,j}}{2\Delta x} - 2\frac{u_{i,j+1}-u_{i,j-1}}{2\Delta y}\frac{v_{i+1,j}-v_{i-1,j}}{2\Delta x} - \frac{v_{i,j+1}-v_{i,j-1}}{2\Delta y}\frac{v_{i,j+1}-v_{i,j-1}}{2\Delta y}\right]
# \end{split}
# $$

# The initial condition is $u, v, p=0$ everywhere, and at the boundary conditions are:

# $u, v, p$ are periodic on $x=0,2$

# $u, v =0$ at $y =0,2$

# $\frac{\partial p}{\partial y}=0$ at $y =0,2$

# $F=1$ everywhere.

# Let's begin by importing our usual run of libraries:

using Plots

# In step 11, we isolated a portion of our transposed equation to make it easier to parse and we're going to do the same thing here.
# One thing to note is that we have periodic boundary conditions throughout this grid,
# so we need to explicitly calculate the values at the leading and trailing edge of our `u` vector.

function build_up_b(rho, dt, dx, dy, u, v)
    b = zeros(u)
    b[2:end, 1:-1] = (rho * (1 / dt * ((u[2:end, 2:] - u[2:end, 0:-2]) / (2 * dx) +
                                      (v[2:, 1:-1] - v[0:-2, 1:-1]) / (2 * dy)) -
                            ((u[2:end, 2:] - u[2:end, 0:-2]) / (2 * dx))^2 -
                            2 * ((u[2:, 1:-1] - u[0:-2, 1:-1]) / (2 * dy) *
                                 (v[2:end, 2:] - v[2:end, 0:-2]) / (2 * dx))-
                            ((v[2:, 1:-1] - v[0:-2, 1:-1]) / (2 * dy))^2))

    # Periodic BC Pressure @ x = 2
    b[2:end, -1] = (rho * (1 / dt * ((u[2:end, 0] - u[2:end,-2]) / (2 * dx) +
                                    (v[2:, -1] - v[0:-2, -1]) / (2 * dy)) -
                          ((u[2:end, 0] - u[2:end, -2]) / (2 * dx))^2 -
                          2 * ((u[2:, -1] - u[0:-2, -1]) / (2 * dy) *
                               (v[2:end, 0] - v[2:end, -2]) / (2 * dx)) -
                          ((v[2:, -1] - v[0:-2, -1]) / (2 * dy))^2))

    # Periodic BC Pressure @ x = 0
    b[2:end, 0] = (rho * (1 / dt * ((u[2:end, 1] - u[2:end, -1]) / (2 * dx) +
                                   (v[2:, 0] - v[0:-2, 0]) / (2 * dy)) -
                         ((u[2:end, 1] - u[2:end, -1]) / (2 * dx))^2 -
                         2 * ((u[2:, 0] - u[0:-2, 0]) / (2 * dy) *
                              (v[2:end, 1] - v[2:end, -1]) / (2 * dx))-
                         ((v[2:, 0] - v[0:-2, 0]) / (2 * dy))^2))

    return b
end

# We'll also define a Pressure Poisson iterative function, again like we did in Step 11.
# Once more, note that we have to include the periodic boundary conditions at the leading and trailing edge.
# We also have to specify the boundary conditions at the top and bottom of our grid.

function pressure_poisson_periodic(p, dx, dy)
    pn = empty(p)

    for q in range(nit):
        pn = p.copy()
        p[2:end, 1:-1] = (((pn[2:end, 2:] + pn[2:end, 0:-2]) * dy^2 +
                          (pn[2:, 1:-1] + pn[0:-2, 1:-1]) * dx^2) /
                         (2 * (dx^2 + dy^2)) -
                         dx^2 * dy^2 / (2 * (dx^2 + dy^2)) * b[2:end, 1:-1])

        # Periodic BC Pressure @ x = 2
        p[2:end, -1] = (((pn[2:end, 0] + pn[2:end, -2])* dy^2 +
                        (pn[2:, -1] + pn[0:-2, -1]) * dx^2) /
                       (2 * (dx^2 + dy^2)) -
                       dx^2 * dy^2 / (2 * (dx^2 + dy^2)) * b[2:end, -1])

        # Periodic BC Pressure @ x = 0
        p[2:end, 0] = (((pn[2:end, 1] + pn[2:end, -1])* dy^2 +
                       (pn[2:, 0] + pn[0:-2, 0]) * dx^2) /
                      (2 * (dx^2 + dy^2)) -
                      dx^2 * dy^2 / (2 * (dx^2 + dy^2)) * b[2:end, 0])

        # Wall boundary conditions, pressure
        p[-1, :] =p[-2, :]  # dp/dy = 0 at y = 2
        p[0, :] = p[1, :]  # dp/dy = 0 at y = 0
        end

    return p
end

Now we have our familiar list of variables and initial conditions to declare before we start.

# Variable declarations
nx = 41
ny = 41
nt = 10
nit = 50
c = 1
dx = 2 / (nx - 1)
dy = 2 / (ny - 1)
x = LinRange(0, 2, nx)
y = LinRange(0, 2, ny)
X, Y = numpy.meshgrid(x, y)

##physical variables
rho = 1
nu = .1
F = 1
dt = .01

#initial conditions
u = zeros((ny, nx))
un = zeros((ny, nx))

v = zeros((ny, nx))
vn = zeros((ny, nx))

p = ones((ny, nx))
pn = ones((ny, nx))

b = zeros((ny, nx))

# For the meat of our computation, we're going to reach back to a trick we used in Step 9 for Laplace's Equation.
# We're interested in what our grid will look like once we've reached a near-steady state.
# We can either specify a number of timesteps `nt` and increment it until we're satisfied with the results,
# or we can tell our code to run until the difference between two consecutive iterations is very small.

# We also have to manage **8** separate boundary conditions for each iteration.
# The code below writes each of them out explicitly.  If you're interested in a challenge,
# you can try to write a function which can handle some or all of these boundary conditions.
# TODO Fix data structure recommendation
# If you're interested in tackling that, you should probably read up on Julia [dictionaries](http://docs.python.org/2/tutorial/datastructures.html#dictionaries).

udiff = 1
stepcount = 0

while udiff > .001
    un = copy(u)
    vn = copy(v)

    b = build_up_b(rho, dt, dx, dy, u, v)
    p = pressure_poisson_periodic(p, dx, dy)

    u[2:end, 2:end] = (un[2:end, 2:end] -
                     un[2:end, 2:end] * dt / dx *
                    (un[2:end, 2:end] - un[2:end, begin:end-1]) -
                     vn[2:end, 2:end] * dt / dy *
                    (un[2:end, 2:end] - un[begin:end-1, 2:end]) -
                     dt / (2 * rho * dx) *
                    (p[2:end, 3:end] - p[2:end, begin:end-1]) +
                     nu * (dt / dx^2 *
                    (un[2:end, 3:end] - 2 * un[2:end, 2:end] + un[2:end, begin:end-1]) +
                     dt / dy^2 *
                    (un[3:end, 2:end] - 2 * un[2:end, 2:end] + un[begin:end-1, 2:end])) +
                     F * dt)

    v[2:end, 2:end] = (vn[2:end, 2:end] -
                     un[2:end, 2:end] * dt / dx *
                    (vn[2:end, 2:end] - vn[2:end, begin:end-1]) -
                     vn[2:end, 2:end] * dt / dy *
                    (vn[2:end, 2:end] - vn[begin:end-1, 2:end]) -
                     dt / (2 * rho * dy) *
                    (p[3:end, 2:end] - p[begin:end-1, 2:end]) +
                     nu * (dt / dx^2 *
                    (vn[2:end, 3:end] - 2 * vn[2:end, 2:end] + vn[2:end, begin:end-1]) +
                     dt / dy^2 *
                    (vn[3:end, 2:end] - 2 * vn[2:end, 2:end] + vn[begin:end-1, 2:end])))

    # Periodic BC u @ x = 2
    u[2:end, end] = (un[2:end, end] - un[2:end, end] * dt / dx *
                  (un[2:end, end] - un[2:end, end-1]) -
                   vn[2:end, end] * dt / dy *
                  (un[2:end, end] - un[begin:end-1, end]) -
                   dt / (2 * rho * dx) *
                  (p[2:end, begin] - p[2:end, end-1]) +
                   nu * (dt / dx^2 *
                  (un[2:end, begin] - 2 * un[2:end,-1] + un[2:end, end-1]) +
                   dt / dy^2 *
                  (un[3:end, end] - 2 * un[2:end, end] + un[begin:end-1, end])) + F * dt)

    # Periodic BC u @ x = 0
    u[2:end, begin] = (un[2:end, begin] - un[2:end, begin] * dt / dx *
                 (un[2:end, begin] - un[2:end, end]) -
                  vn[2:end, begin] * dt / dy *
                 (un[2:end, begin] - un[begin:end-1, begin]) -
                  dt / (2 * rho * dx) *
                 (p[2:end, 2] - p[2:end, end]) +
                  nu * (dt / dx^2 *
                 (un[2:end, 2] - 2 * un[2:end, begin] + un[2:end, end]) +
                  dt / dy^2 *
                 (un[3:end, begin] - 2 * un[2:end, begin] + un[begin:end-1, begin])) + F * dt)

    # Periodic BC v @ x = 2
    v[2:end, end] = (vn[2:end, end] - un[2:end, end] * dt / dx *
                  (vn[2:end, end] - vn[2:end, end-1]) -
                   vn[2:end, end] * dt / dy *
                  (vn[2:end, end] - vn[begin:end-1, end]) -
                   dt / (2 * rho * dy) *
                  (p[3:end, end] - p[begin:end-1, end]) +
                   nu * (dt / dx^2 *
                  (vn[2:end, begin] - 2 * vn[2:end, end] + vn[2:end, end-1]) +
                   dt / dy^2 *
                  (vn[3:end, end] - 2 * vn[2:end, end] + vn[begin:end-1, end])))

    # Periodic BC v @ x = 0
    v[2:end, begin] = (vn[2:end, begin] - un[2:end, begin] * dt / dx *
                 (vn[2:end, begin] - vn[2:end, end]) -
                  vn[2:end, begin] * dt / dy *
                 (vn[2:end, begin] - vn[begin:end-1, begin]) -
                  dt / (2 * rho * dy) *
                 (p[3:end, begin] - p[begin:end-1, begin]) +
                  nu * (dt / dx^2 *
                 (vn[2:end, 2] - 2 * vn[2:end, begin] + vn[2:end, end]) +
                  dt / dy^2 *
                 (vn[3:end, begin] - 2 * vn[2:end, begin] + vn[begin:end-1, begin])))

    # Wall BC: u,v = 0 @ y = 0,2
    u[begin, :] .= 0
    u[end,   :] .= 0
    v[begin, :] .= 0
    v[end,   :] .= 0

    udiff = (sum(u) - sum(un)) / sum(u)
    stepcount += 1
end

# You can see that we've also included a variable `stepcount` to see how many iterations our loop went through before our stop condition was met.

print(stepcount)

# If you want to see how the number of iterations increases as our `udiff` condition gets smaller and smaller,
# try defining a function to perform the `while` loop written above that takes an input
# `udiff` and outputs the number of iterations that the function runs.

# For now, let's look at our results.  We've used the quiver function
# # to look at the cavity flow results and it works well for channel flow, too.

#TODO PLOT
fig = pyplot.figure(figsize = (11,7), dpi=100)
pyplot.quiver(X[::3, ::3], Y[::3, ::3], u[::3, ::3], v[::3, ::3]);

# The structures in the `quiver` command that look like `[::3, ::3]` are useful when dealing with large amounts of data that you want to visualize.  The one used above tells `matplotlib` to only plot every 3rd data point.  If we leave it out, you can see that the results can appear a little crowded.

# TODO PLOT
fig = pyplot.figure(figsize = (11,7), dpi=100)
pyplot.quiver(X, Y, u, v);

## Learn more
# ***

##### What is the meaning of the $F$ term?

# Step 12 is an exercise demonstrating the problem of flow in a channel or pipe.
# If you recall from your fluid mechanics class, a specified
# pressure gradient is what drives Poisseulle flow.

# Recall the $x$-momentum equation:

# $$\frac{\partial u}{\partial t}+u \cdot \nabla u = -\frac{\partial p}{\partial x}+\nu \nabla^2 u$$

# What we actually do in Step 12 is split the pressure into steady and unsteady components $p=P+p'$. The applied steady pressure gradient is the constant $-\frac{\partial P}{\partial x}=F$ (interpreted as a source term), and the unsteady component is $\frac{\partial p'}{\partial x}$. So the pressure that we solve for in Step 12 is actually $p'$, which for a steady flow is in fact equal to zero everywhere.

# Why did we do this?

# Note that we use periodic boundary conditions for this flow. For a flow with a constant pressure gradient, the value of pressure on the left edge of the domain must be different from the pressure at the right edge. So we cannot apply periodic boundary conditions on the pressure directly. It is easier to fix the gradient and then solve for the perturbations in pressure.

# Shouldn't we always expect a uniform/constant $p'$ then?

# That's true only in the case of steady laminar flows. At high Reynolds numbers, flows in channels can become turbulent, and we will see unsteady fluctuations in the pressure, which will result in non-zero values for $p'$.

# In step 12, note that the pressure field itself is not constant, but it's the pressure perturbation field that is. The pressure field varies linearly along the channel with slope equal to the pressure gradient. Also, for incompressible flows, the absolute value of the pressure is inconsequential.

# ##### And explore more CFD materials online

# The interactive module **12 steps to Navier–Stokes** is one of several components
# of the Computational Fluid Dynamics class taught by Prof. Lorena A. Barba in Boston University between 2009 and 2013.

# For a sample of what the othe components of this class are, you can explore
# the **Resources** section of the Spring 2013 version of [the course's Piazza site](https://piazza.com/bu/spring2013/me702/resources).

# ***


using Test

include("interp.jl")

@test interp(numC(5), top_level_env) == numV(5)
@test interp(numC(0), top_level_env) == numV(0)

@test interp(ifC(idC("true"), numC(-1), numC(1)), top_level_env) == numV(-1)
@test interp(ifC(idC("false"), numC(-1), numC(1)), top_level_env) == numV(1)
@test_throws ErrorException("Invalid TULI5 if statement") interp(ifC(numC(324), numC(-1), numC(1)), top_level_env)

@test interp(appC(idC("<="), [numC(3), numC(2)]), top_level_env) == boolV(false)
@test interp(appC(idC("<="), [numC(3), numC(3)]), top_level_env) == boolV(true)

@test interp(appC(idC("+"), [numC(3), numC(5)]), top_level_env) == numV(8)
@test interp(appC(idC("-"), [numC(15), numC(4.5)]), top_level_env) == numV(10.5)
@test interp(appC(idC("*"), [numC(3), numC(31)]), top_level_env) == numV(93)
@test interp(appC(idC("/"), [numC(10), numC(5)]), top_level_env) == numV(2.0)
@test interp(appC(idC("equal?"), [numC(10), numC(5)]), top_level_env) == boolV(false)
@test interp(appC(idC("equal?"), [numC(10), numC(10)]), top_level_env) == boolV(true)

@test_throws ErrorException("TULI5 invalid + call") interp(appC(idC("+"), [boolV(true), numC(5)]), top_level_env)
@test_throws ErrorException("TULI5 invalid - call") interp(appC(idC("-"), [boolV(true), numC(5)]), top_level_env)
@test_throws ErrorException("TULI5 invalid * call") interp(appC(idC("*"), [boolV(true), numC(5)]), top_level_env)
@test_throws ErrorException("TULI5 invalid <= call") interp(appC(idC("<="), [boolV(true), numC(5)]), top_level_env)
@test_throws ErrorException("TULI5 invalid / call") interp(appC(idC("/"), [boolV(true), numC(5)]), top_level_env)
@test_throws ErrorException("TULI5 division by 0") interp(appC(idC("/"), [numC(6), numC(0)]), top_level_env)

@test interp(appC(lamC(["a", "b"], appC(idC("+"), [idC("a"), idC("b")])), [numC(3), numC(7)]), top_level_env) == numV(10)
@test interp(appC(lamC(["a", "b"], appC(idC("a"), [idC("b")])), [lamC(["x"], appC(idC("*"), [idC("x"), idC("x")])), numC(10)]), top_level_env) == numV(100)


function main()
    N = parse(Int, readline())
    reds = [parse.(Int, split(readline())) for _ in 1:N]
    blues = [parse.(Int, split(readline())) for _ in 1:N]

    sort!(reds, rev=true, by = x -> x[2])
    sort!(blues, by = x -> x[1])

    ans = 0
    ismatched_reds = falses(N)
    for b in blues
        for (i, r) in enumerate(reds)
            if !ismatched_reds[i] && r[1] < b[1] && r[2] < b[2]
                ismatched_reds[i] = true
                ans += 1
                break
            end
        end
    end

    println(ans)
end

main()


@testset "Cycles" begin
    completedg = CompleteDiGraph(4)
    pathdg = PathDiGraph(5)
    triangle = random_regular_graph(3, 2)
    quadrangle = random_regular_graph(4, 2)
    pentagon = random_regular_graph(5, 2)

    @test maxsimplecycles(pathdg) == 0
    @test maxsimplecycles(completedg) == 20
    @test maxsimplecycles(4) == 20
    @test maxsimplecycles(pathdg, false) == 84

    @test length(simplecycles(completedg)) == 20
    @test simplecycles(completedg) == @inferred(simplecycles_iter(completedg))
    @test simplecyclescount(completedg) == 20
    @test simplecycleslength(completedg) == ([0, 6, 8, 6], 20)

    @test simplecyclescount(pathdg) == 0
    @test length(simplecycles(pathdg)) == 0
    @test isempty(simplecycles(pathdg)) == true
    @test isempty(simplecycles_iter(pathdg)) == true
    @test simplecycleslength(pathdg) == (zeros(5), 0)

    @test simplecyclescount(completedg, 10) == 10
    @test simplecycleslength(completedg, 10)[2] == 10

    @test simplecyclescount(pathdg, 10) == 0
    @test isempty(simplecycles_iter(pathdg, 10)) == true
    @test simplecycleslength(pathdg, 10) == (zeros(5), 0)

    trianglelengths, triangletotal = simplecycleslength(DiGraph(triangle))
    @test sum(trianglelengths) == triangletotal

    quadranglelengths, quadrangletotal = simplecycleslength(DiGraph(quadrangle))
    @test sum(quadranglelengths) == quadrangletotal
    @test simplecycles(DiGraph(quadrangle)) == @inferred(simplecycles_iter(DiGraph(quadrangle)))

    pentagonlengths, pentagontotal = simplecycleslength(DiGraph(pentagon))
    @test sum(pentagonlengths) == pentagontotal

    selfloopg = DiGraph([
        0 1 0 0;
        0 0 1 0;
        1 0 1 0;
        0 0 0 1;
    ])
    cycles = simplecycles(selfloopg)
    @test [3] in cycles
    @test [4] in cycles
    @test [1, 2, 3] in cycles || [2, 3, 1] in cycles || [3, 1, 2] in cycles
    @test length(cycles) == 3

    cycles2 = simplecycles_iter(selfloopg)
    @test [3] in cycles2
    @test [4] in cycles2
    @test [1, 2, 3] in cycles2 || [2, 3, 1] in cycles2 || [3, 1, 2] in cycles2
    @test length(cycles2) == 3
end


export getreducedmargin, updatereducedmargin

"""
$(TYPEDSIGNATURES)

Computes the reduced margin of a set of multi-indices.
See Baptista, R., Zahm, O., & Marzouk, Y. (2020).
An adaptive transport framework for joint and conditional density estimation. arXiv preprint arXiv:2009.10303.
"""
function getreducedmargin(midx::Array{Int64,2})

    if isempty(midx)
        return zeros(Int64,0,0)
    end

    n, d = size(midx)

    # add [0 0 0] to compute the reduced margin
    if !any(zeros(Int64,d) in eachslice(midx; dims = 1))
            idx = vcat(zeros(Int64,1,d), midx)
    else
            idx = midx
    end

    n, d = size(idx)

    neighbours = zeros(Int64,(n,d,d))
    eye = Matrix(1*I,d,d)

    @inbounds for i=1:d
        neighbours[:,i,:]  .= idx
    end

    @inbounds for i=1:n
        neighbours[i,:,:]  .+= eye
    end

    neighbours = reshape(neighbours,(n*d,d))

    margintmp = setdiff(eachslice(neighbours; dims = 1), eachslice(idx;dims = 1))
    cardinal = size(margintmp,1)
    margin = zeros(Int64,cardinal,d)
    @inbounds for i=1:cardinal
        margin[i,:] = margintmp[i]
    end

    # margin = margin[sortperm(margin[:, 1]), :] # sorted by the 1st column
    margin = sortslices(margin; dims = 1)

    neighbours = zeros(Int64, (cardinal,d,d))

    @inbounds for i=1:d
        neighbours[:,i,:]  .= margin
    end

    @inbounds for i=1:cardinal
        neighbours[i,:,:]  .-= eye
    end

    neighbours = reshape(neighbours, cardinal*d,d)

    ok = Bool[any(x in eachslice(idx; dims = 1)) for x in eachslice(neighbours; dims = 1)]

    isout = Bool[any(x.<0) for x in eachslice(neighbours; dims = 1)]

    ok .|=  isout

    ok = reshape(ok, (cardinal, d))

    keep = Bool[all(x) for x in eachslice(ok; dims = 1)]

    return margin[keep,:]
end

"""
$(TYPEDSIGNATURES)

Updates the reduced margin.

See Baptista, R., Zahm, O., & Marzouk, Y. (2020).
An adaptive transport framework for joint and conditional density estimation. arXiv preprint arXiv:2009.10303.
"""
function updatereducedmargin(lowerset::Array{Int64,2}, reduced_margin::Array{Int64,2}, idx::Int64)
    d = size(lowerset, 2)
    nr = size(reduced_margin, 1)
    @assert idx <= nr "idx is larger than dimension of the reduced_margin"
    newidx = reduced_margin[idx:idx,:]
#     @show lowerset
#     @show reduced_margin
#     @show newidx
    lowerset = vcat(lowerset, newidx)
#     @show lowerset
    # Remove the idx-th line of reducedmargin
    toremove = Bool[!(i in idx) for i=1:nr]
    # reduced_margin = reduced_margin[1:end .!= idx,:]
    reduced_margin = reduced_margin[toremove,:]

#     @show toremove
#     @show reduced_margin

    eye =  Matrix(1*I,d,d)
    # Update the reduced margin
    candidate  = repeat(newidx, outer = (d,1)) + eye

#     @show candidate
    ok = falses(d)
#     @show ok

    @inbounds for i = 1:d
#         @show i
#         @show repeat(candidate[i,:]', outer = (d,1))
#         @show size(repeat(candidate[i,:]', outer = (d,1)))
        parents_of_candidate = repeat(candidate[i,:]', outer = (d,1)) - eye
#         @show parents_of_candidate
        tokeep = Bool[!any(x .< 0) for x in eachslice(parents_of_candidate; dims = 1)]
#         @show tokeep
        parents_of_candidate = parents_of_candidate[tokeep,:]
#         @show parents_of_candidate

#         @show [any(x in eachslice(lowerset; dims = 1)) for x in eachslice(parents_of_candidate; dims = 1)]
        ok[i] = all([any(x in eachslice(lowerset; dims = 1)) for x in eachslice(parents_of_candidate; dims = 1)])
#        @show ok
    end
    #
    candidate = candidate[ok,:]
    #
    # # Add the candidates to the reduced margin
    reduced_margin = vcat(reduced_margin, candidate)

    # reduced_margin  = sortslices(reduced_margin; dims = 1)
    # lowerset = sortslices(lowerset; dims = 1)
    return lowerset, reduced_margin
end


using ApproximateBayesianComputation

#Start Testing...
data = rand(Distributions.Normal(2.0, 0.5), 100)

summaryStatistics = mean(data)
nsteps = 10
nparticles = 200
ninitial = 1000

sample_prior, density_prior = make_model_prior(Distributions.Normal(0.0, 0.5))
sample_kernel, density_kernel = make_normal_kernel()
kernel_bandwidth = weightedstd2

forward_model(μ::Float64) = rand(Distributions.Normal(μ, 0.5), 100)
compute_distance(x::Array{Float64, 1}, y::Float64) = abs(mean(x) - y)

rank_distances = identity
shrink_threshold{A <: SingleMeasureAbcPmc}(abcpmc::A) = quantile_threshold(abcpmc, 0.3)

out = abc_pmc(summaryStatistics, nsteps, nparticles, ninitial, sample_prior,
              density_prior, sample_kernel, density_kernel, forward_model,
              compute_distance, rank_distances, kernel_bandwidth, shrink_threshold)


###########Importing Libraries and Defining Structs############
using  LinearAlgebra, SparseArrays, StaticArrays, BenchmarkTools, Base, Plots

mutable struct Qubit #type for holding qubit information
    bitpos::Int
    vec::Vector{ComplexF64}
end

mutable struct Circuit
    qreg::Array{Qubit,1}
    gates::Vector{SparseMatrixCSC{ComplexF64,Int64}}
end

function Circuit(nqubits::Int)
    G = Vector{SparseMatrixCSC{ComplexF64,Int64}}()
    return Circuit(makeregister(nqubits),G)
end

function makeregister(nqubits::Int)
    #function for generating a quantum register of qubits
    zerovec = [1.0+0.0*im, 0.0+0.0*im]
    return [Qubit(i, copy(zerovec)) for i ∈ 1:nqubits]
end

function Base.max(set::AbstractRange)
    if set[1]<set[end]
        return set[end]
    elseif set[1]>set[end]
        return set[1]
    else
        return set[1]
    end
end

function Base.min(set::AbstractRange)
    if set[1]<set[end]
        return set[1]
    elseif set[1]>set[end]
        return set[end]
    else
        return set[1]
    end
end

######Single qubit gates ###########

function U!(C::Circuit, U::AbstractArray{ComplexF64,2}, bitpos::Int)
    #=Implementation that adds the new matrix into the gates field
    of the circuit=#
    a = length(C.qreg)
    if (bitpos>a) || (bitpos<=0)
        error("The bit specified must be within the register of the circuit!")
    end
    Uf = sparse(U)
    M = similar(Uf)
    I2 = sparse(I*(1.0+0.0*im),2,2)
    for  i ∈ 1:a
        if (i == 1) && (i !== bitpos)
            copy!(M,I2)
        elseif (i == 1) && (i == bitpos)
            copy!(M,Uf)
        else
            if (i == bitpos)
                M = kron(Uf,M)
            else
                M = kron(I2,M)
            end
        end
    end
   return push!(C.gates, M)
end

function U!(C::Circuit, U::AbstractSparseArray{ComplexF64,Int}, bitpos::Int)
    #=Implementation that adds the new matrix into the gates field
    of the circuit=#
    a = length(C.qreg)
    if (bitpos>a) || (bitpos<=0)
        error("The bit specified must be within the register of the circuit!")
    end
    M = similar(U)
    I2 = sparse(I*(1.0+0.0*im),2,2)
    for  i ∈ 1:a
        if (i == 1) && (i !== bitpos)
            copy!(M,I2)
        elseif (i == 1) && (i == bitpos)
            copy!(M,U)
        else
            if (i == bitpos)
                M = kron(U,M)
            else
                M = kron(I2,M)
            end
        end
    end
   return push!(C.gates, M)
end
##Specialized methods for doing multiple gates sequentially
function U!(C::Circuit, U::AbstractArray{ComplexF64,2}, set::AbstractVector{Int})
    #=Implementation that gives user ability to define
    a range of qubits to affect with the specified gate=#
    for i in set
        U!(C,U,i)
    end
end

function U!(C::Circuit, U::AbstractSparseArray{ComplexF64,Int}, set::AbstractVector{Int})
    #=Implementation that gives user ability to define
    a range of qubits to affect with the specified gate=#
    for i in set
        U!(C,U,i)
    end
end

function H!(C::Circuit, bitpos::Int)
    #=Implementation that adds the new matrix into the gates field
    of the circuit=#
    H = sparse([1.0/sqrt(2)+0.0*im  1.0/sqrt(2)+0.0*im
                1.0/sqrt(2)+0.0*im -1.0/sqrt(2)+0.0*im])
    U!(C,H,bitpos)
end

function H!(C::Circuit, set::AbstractVector{Int})
    #=Implementation that gives user ability to define
    a range of qubits to affect with the specified gate=#
    H = sparse([1.0/sqrt(2)+0.0*im  1.0/sqrt(2)+0.0*im
                1.0/sqrt(2)+0.0*im -1.0/sqrt(2)+0.0*im])
    U!(C,H,set)
end

function T!(C::Circuit, bitpos::Int)
    #=Implementation that adds the new matrix into the gates field
    of the circuit=#
    T = sparse([1.0+0.0*im  0.0+0.0*im
                0.0+0.0*im  exp(im*π/4)])
    U!(C,T,bitpos)
end

function T!(C::Circuit, set::AbstractVector{Int})
    #=Implementation that gives user ability to define
    a range of qubits to affect with the specified gate=#
    T = sparse([1.0+0.0*im  0.0+0.0*im
                0.0+0.0*im  exp(im*π/4)])
    U!(C,T,set)
end

######## Pauli Gates ##########
function X!(C::Circuit, bitpos::Int)
    #=Implementation that adds the new matrix into the gates field
    of the circuit=#
    X = sparse([0.0+0.0*im  1.0+0.0*im
                1.0+0.0*im  0.0+0.0*im])
    U!(C,X,bitpos)
end

function X!(C::Circuit, set::AbstractVector{Int})
    #=Implementation that gives user ability to define
    a range of qubits to affect with the specified gate=#
    X = sparse([0.0+0.0*im  1.0+0.0*im
                1.0+0.0*im  0.0+0.0*im])
    U!(C,X,set)
end

function Y!(C::Circuit, bitpos::Int)
    #=Implementation that adds the new matrix into the gates field
    of the circuit=#
    Y = sparse([0.0+0.0*im  0.0-1.0*im
                0.0+1.0*im  0.0+0.0*im])
    U!(C,Y,bitpos)
end

function Y!(C::Circuit, set::AbstractVector{Int})
    #=Implementation that gives user ability to define
    a range of qubits to affect with the specified gate=#
    Y = sparse([0.0+0.0*im  0.0-1.0*im
                0.0+1.0*im  0.0+0.0*im])
    U!(C,Y,set)
end

function Z!(C::Circuit, bitpos::Int)
    #=Implementation that adds the new matrix into the gates field
    of the circuit=#
    Z = sparse([1.0+0.0*im  0.0+0.0*im
                0.0+0.0*im -1.0+0.0*im])
    U!(C,Z,bitpos)
end

function Z!(C::Circuit, set::AbstractVector{Int})
    #=Implementation that gives user ability to define
    a range of qubits to affect with the specified gate=#
    Z = sparse([1.0+0.0*im  0.0+0.0*im
                0.0+0.0*im -1.0+0.0*im])
    U!(C,Z,set)
end

############# Controlled Gates and 2 qubit Gates#############
function CU!(C::Circuit, U::AbstractArray{ComplexF64,2},control::Int,target::Int)
    #Implentation of CU gate using Kronnecker products.
    Uf = sparse(U)

    a = length(C.qreg)

    if (target==control)
        error("Target qubit and control qubit must be Different!")
    elseif ((target>a) || (control>a)) || ((target<=0) || (control<=0))
        error("The target and control qubit must be within the size of the register!")
    end

    M1 = spzeros(ComplexF64,2,2)
    M2 = copy(M1)
    I2 = sparse(I*(1.0+0.0*im),2,2)

    conket1 = sparse([0.0+0.0*im 0.0+0.0*im;
                      0.0+0.0*im 1.0+0.0*im])
    conket0 = sparse([1.0+0.0*im 0.0+0.0*im
                      0.0+0.0*im 0.0+0.0*im])

    for i ∈ 1:a

        if (i==1)
            if (i==control)
                M1 = conket1
            elseif (i==target)
                M1 = Uf
            else
                M1 = I2
            end
        else
            if (i==control)
                M1 = kron(conket1,M1)
            elseif (i==target)
                M1 = kron(Uf,M1)
            else
                M1 = kron(I2,M1)
            end
        end
    end

    for i ∈ 1:a

        if (i==1==control)
            M2 = conket0
        elseif (i==1!==control)
            M2 = I2
        else
            if (i==control)
                M2 = kron(conket0,M2)
            else
                M2 = kron(I2,M2)
            end
        end
    end
    return push!(C.gates,M1+M2)
end

function CU!(C::Circuit, U::AbstractSparseArray{ComplexF64,Int},control::Int,target::Int)
    #Implentation of CU gate using Kronnecker products.
    a = length(C.qreg)

    if (target==control)
        error("Target qubit and control qubit must be Different!")
    elseif ((target>a) || (control>a)) || ((target<=0) || (control<=0))
        error("The target and control qubit must be within the size of the register!")
    end

    M1 = spzeros(ComplexF64,2,2)
    M2 = copy(M1)
    I2 = sparse(I*(1.0+0.0*im),2,2)

    conket1 = sparse([0.0+0.0*im 0.0+0.0*im;
                      0.0+0.0*im 1.0+0.0*im])
    conket0 = sparse([1.0+0.0*im 0.0+0.0*im
                      0.0+0.0*im 0.0+0.0*im])

    for i ∈ 1:a

        if (i==1)
            if (i==control)
                M1 = conket1
            elseif (i==target)
                M1 = U
            else
                M1 = I2
            end
        else
            if (i==control)
                M1 = kron(conket1,M1)
            elseif (i==target)
                M1 = kron(U,M1)
            else
                M1 = kron(I2,M1)
            end
        end
    end

    for i ∈ 1:a

        if (i==1==control)
            M2 = conket0
        elseif (i==1!==control)
            M2 = I2
        else
            if (i==control)
                M2 = kron(conket0,M2)
            else
                M2 = kron(I2,M2)
            end
        end
    end
    return push!(C.gates,M1+M2)
end
###Methods for doing a number of gates in a row
function CU!(C::Circuit,U::AbstractArray{ComplexF64,2},controls::AbstractVector{Int},targets::AbstractVector{Int})
    #convenience method for doing mutliple CU operations at a time.
    a = length(C.qreg)
    b = length(controls)
    if length(targets)!==b
        error("The set of controls and targets must be the same length or the operation is ambiguous.")
    elseif ((max(controls)>a) || (max(targets)>a)) || ((min(controls)<=0) || (min(targets)<=0))
        error("The target and control qubits must all lie within the register!")
    end

    Uf = sparse(U)
    for i in 1:b
        CU!(C,Uf,controls[i],targets[i])
    end
end

function CU!(C::Circuit,U::AbstractArray{ComplexF64,2},control::Int,targets::AbstractVector{Int})
    #convenience method for doing mutliple CU operations at a time.
    a = length(C.qreg)
    if ((control>a) || (max(targets)>a)) || ((control<=0) || (min(targets)<=0))
        error("The target and control qubits must all lie within the register!")
    end

    Uf = sparse(U)
    for i in targets
        CU!(C,Uf,control,i)
    end
end

function CU!(C::Circuit,U::AbstractArray{ComplexF64,2},controls::AbstractVector{Int},target::Int)
    #convenience method for doing mutliple CU operations at a time.
    a = length(C.qreg)
    if ((max(controls)>a) || (target>a)) || ((min(controls)<=0) || (target<=0))
        error("The target and control qubits must all lie within the register!")
    end

    Uf = sparse(U)
    for i in controls
        CU!(C,Uf,i,target)
    end
end

function CU!(C::Circuit,U::AbstractSparseArray{ComplexF64,Int},controls::AbstractVector{Int},targets::AbstractVector{Int})
    #convenience method for doing mutliple CU operations at a time.
    a = length(C.qreg)
    b = length(controls)
    if length(targets)!==b
        error("The set of controls and targets must be the same length or the operation is ambiguous.")
    elseif ((max(controls)>a) || (max(targets)>a)) || ((min(controls)<=0) || (min(targets)<=0))
        error("The target and control qubits must all lie within the register!")
    end

    for i in 1:b
        CU!(C,U,controls[i],targets[i])
    end
end

function CU!(C::Circuit,U::AbstractSparseArray{ComplexF64,Int},control::Int,targets::AbstractVector{Int})
    #convenience method for doing mutliple CU operations at a time.
    a = length(C.qreg)
    if ((control>a) || (max(targets)>a)) || ((control<=0) || (min(targets)<=0))
        error("The target and control qubits must all lie within the register!")
    end

    for i in targets
        CU!(C,U,control,i)
    end
end

function CU!(C::Circuit,U::AbstractSparseArray{ComplexF64,Int},controls::AbstractVector{Int},target::Int)
    #convenience method for doing mutliple CU operations at a time.
    a = length(C.qreg)
    if ((max(controls)>a) || (target>a)) || ((min(controls)<=0) || (target<=0))
        error("The target and control qubits must all lie within the register!")
    end

    for i in controls
        CU!(C,U,i,target)
    end
end

function CX!(C::Circuit,control::Int,target::Int)
    #Implentation of CNOT gate using Kronnecker products.
    X = sparse([0.0+0.0*im 1.0+0.0*im;
                1.0+0.0*im 0.0+0.0*im])

    CU!(C,X,control,target)
end

function CX!(C::Circuit,controls::AbstractVector{Int},targets::AbstractVector{Int})
    #convenience method for doing mutliple CX operations at a time.
    X = sparse([0.0+0.0*im 1.0+0.0*im;
                1.0+0.0*im 0.0+0.0*im])

    CU!(C,X,controls,targets)
end

function CX!(C::Circuit,control::Int,targets::AbstractVector{Int})
    #convenience method for doing mutliple CX operations at a time.
    X = sparse([0.0+0.0*im 1.0+0.0*im;
                1.0+0.0*im 0.0+0.0*im])

    CU!(C,X,control,targets)
end

function CX!(C::Circuit,controls::AbstractVector{Int},target::Int)
    #convenience method for doing mutliple CX operations at a time.
    X = sparse([0.0+0.0*im 1.0+0.0*im;
                1.0+0.0*im 0.0+0.0*im])

    CU!(C,X,controls,target)
end

function CZ!(C::Circuit,control::Int,target::Int)
    #Implentation of CZ gate using Kronnecker products.
    Z = sparse([1.0+0.0*im  0.0+0.0*im;
                0.0+0.0*im -1.0+0.0*im])

    CU!(C,Z,control,target)
end

function CZ!(C::Circuit,controls::AbstractVector{Int},targets::AbstractVector{Int})
    #convenience method for doing mutliple CZ operations at a time.
    Z = sparse([1.0+0.0*im  0.0+0.0*im;
                0.0+0.0*im -1.0+0.0*im])

    CU!(C,Z,controls,targets)
end

function CZ!(C::Circuit,control::Int,targets::AbstractVector{Int})
    #convenience method for doing mutliple CZ operations at a time.
    Z = sparse([1.0+0.0*im  0.0+0.0*im;
                0.0+0.0*im -1.0+0.0*im])

    CU!(C,Z,control,targets)
end

function CZ!(C::Circuit,controls::AbstractVector{Int},target::Int)
    #convenience method for doing mutliple CZ operations at a time.
    Z = sparse([1.0+0.0*im  0.0+0.0*im;
                0.0+0.0*im -1.0+0.0*im])

    CU!(C,Z,controls,target)
end

function Swap!(C::Circuit,b1::Int,b2::Int)
    #quick and dirty implmentation of Swap Gate
    a = length(C.qreg)
    if b1==b2
        error("This ins't really a swap, you just tried to swap a qubit with itself!")
    elseif ((b1>a) || (b2>a)) || ((b1<=0) || (b2<=0))
        error("Both bits must be within the size of the register!")
    end
    CX!(C,b2,b1)
    CX!(C,b1,b2)
    CX!(C,b2,b1)
end

function CCX!(C::Circuit,c1::Int,c2::Int,target::Int)
    #Implementation of the Toffoli gate using Kronnecker products.
    X = sparse([0.0+0.0*im 1.0+0.0*im;
                1.0+0.0*im 0.0+0.0*im])

    a = length(C.qreg)

    if (target==c1) || (target==c2)
        error("Target qubit and control qubits must be Different!")
    elseif (c1==c2)
        error("The control qubits must be different from one another!")
    elseif ((target>a) || (c1>a) || (c2>a)) || ((target<=0) || (c1<=0) ||(c2<=0) )
        error("The target and control qubits must be within the size of the register!")
    end

    M1 = spzeros(ComplexF64,2,2)
    M2 = copy(M1)
    M3 = copy(M1)
    M4 = copy(M1)
    I2 = sparse(I*(1.0+0.0*im),2,2)

    conket1 = sparse([0.0+0.0*im 0.0+0.0*im;
                      0.0+0.0*im 1.0+0.0*im])
    conket0 = sparse([1.0+0.0*im 0.0+0.0*im
                      0.0+0.0*im 0.0+0.0*im])

    for i ∈ 1:a

        if (i==1)
            if (i==c1)||(i==c2)
                M1 = conket1
            elseif (i==target)
                M1 = X
            else
                M1 = I2
            end
        else
            if (i==c1)||(i==c2)
                M1 = kron(conket1,M1)
            elseif (i==target)
                M1 = kron(X,M1)
            else
                M1 = kron(I2,M1)
            end
        end
    end

    for i ∈ 1:a

        if (i==1)
            if (i==c1)||(i==c2)
                M2 = conket0
            else
                M2 = I2
            end
        else
            if (i==c1)||(i==c2)
                M2 = kron(conket0,M2)
            else
                M2 = kron(I2,M2)
            end
        end
    end

    for i ∈ 1:a

        if (i==1)
            if (i==c1)
                M3 = conket0
            elseif (i==c2)
                M3 = conket1
            else
                M3 = I2
            end
        else
            if (i==c1)
                M3 = kron(conket0,M3)
            elseif (i==c2)
                M3 = kron(conket1,M3)
            else
                M3 = kron(I2,M3)
            end
        end
    end

    for i ∈ 1:a

        if (i==1)
            if (i==c1)
                M4 = conket1
            elseif (i==c2)
                M4 = conket0
            else
                M4 = I2
            end
        else
            if (i==c1)
                M4 = kron(conket1,M4)
            elseif (i==c2)
                M4 = kron(conket0,M4)
            else
                M4 = kron(I2,M4)
            end
        end
    end
    return push!(C.gates,M1+M2+M3+M4)

end

########## Running the Circuit ###########
function Run_circ(C::Circuit)

    maxiter = length(C.gates)
    maxbit = length(C.qreg)

    M = copy(C.gates[1])
    ket = copy(C.qreg[1].vec)
    for i ∈ 2:maxbit
    ket = kron(C.qreg[i].vec,ket)
    end

    for i ∈ 2:maxiter
        M .= C.gates[i]*M
    end

    return M*ket
end

function shots(C::Circuit, nshots::Int)

    finalket = Run_circ(C)
    ntrials = rand(nshots)
    ensemble = zeros(Int,length(finalket))
    trials!(ensemble, finalket, ntrials)
end

function trials!(ensemble::AbstractVector{Int}, ket::AbstractVector{ComplexF64}, probs::AbstractVector{Float64})
    nshots = length(probs)

    @inbounds for j ∈ 1:nshots
        local p = probs[j]
        local sumold = 0.0
        local sumnew = 0.0

        @inbounds for i ∈ 1:length(ket)
            sumnew = sumold + real(conj(ket[i])*ket[i])
            if (p<=sumnew)
                ensemble[i] += 1
                break
            end
            sumold = sumnew
        end
    end
    return ensemble
end

function graph(data::AbstractVector)
    pyplot()
    max = length(data)-1
    nqubits = convert(Int,log(2,max+1))
    xs = [string(i,base=2,pad=nqubits) for i ∈ 0:max]

    bar(xs,data, leg=false, title="Simulation of Circuit", xlabel="qubit readout", ylabel="Counts", dpi = 300)
end


Base.@kwdef struct TKEParameters{T} <: AbstractParameters
    Cᴰ :: T = 3.2441 # Dissipation parameter
end

# Note: to increase readability, we use 'm' to refer to 'model' in function
# definitions below.

@inline production(m, i) = KU(m, i) * oncell(shear_squared, m, i)

@inline ∂z_T(m, i) = ∂z(m.solution.T, i)
@inline ∂z_S(m, i) = ∂z(m.solution.S, i)

@inline function buoyancy_flux(m, i)
    return m.constants.g * (  m.constants.α * ( - KT(m, i) * oncell(∂z_T, m, i) + NLᵀ(m, i) )
                            - m.constants.β * ( - KS(m, i) * oncell(∂z_S, m, i) + NLˢ(m, i) ) )
end

@inline dissipation(m, i) = @inbounds m.tke_equation.Cᴰ * sqrt_e(m, i)^3 / dissipation_length(m, i)


@adjoint function evaluate(s::SqEuclidean, x::AbstractVector, y::AbstractVector)
  δ = x .- y
  sum(abs2, δ), Δ -> begin
    x̄ = (2 * Δ) .* δ
    (nothing, x̄, -x̄)
  end
end

@adjoint function evaluate(s::Euclidean, x::AbstractVector, y::AbstractVector)
  D = x.-y
  δ = sqrt(sum(abs2,D))
  δ, Δ -> begin
    x̄ = Δ .* D / (δ + eps(δ))
    (nothing, x̄, -x̄)
  end
end

@adjoint function evaluate(s::DotProduct, x::AbstractVector, y::AbstractVector)
  dot(x,y), Δ -> begin
    (nothing, Δ.*y, Δ.*x)
  end
end


"""
イエーツの連続性の補正による p 値

2×2 分割表の場合，PowerDivergenceTest() で得られる検定統計量はイエーツの連続性の補正をしないものである。

イエーツの連続性の補正をする場合には，以下の関数を定義しておくとよい。
"""

using Distributions, HypothesisTests
function yates(x)
    result = PowerDivergenceTest(x, lambda=1.0)
    n = sum(x)
    a, c, b, d = x
    stat = n*max(0, abs(a*d - b*c) - 0.5n)^2 / ((a+b)*(c+d)*(a+c)*(b+d))
    p = ccdf(Chisq(1), stat)
    println("corrected χ-sq. = $stat,  df = 1,  p value = $p")
end;

"""
使用法

julia> x = [12 35
            43 56]
2×2 Matrix{Int64}:
 12  35
 43  56

julia> yates(x)
corrected χ-sq. = 3.621119907386832,  df = 1,  p value = 0.057050457416995715

"""


# Test the simple 3D BEM implementation and asymptotic compression
# See popsomer/bempp/meshes for additional meshes
using PyCall
@pyimport matplotlib.pyplot as plt
nrMesh = 0
k = 15

include("bea.jl")
include("window.jl")

(xyz, elmt) = readMesh(string("sphere", nrMesh, ".msh") )
# Prints to ignore first enty which is origin:
xyz = xyz[2:end,:]

A = @time(getSystemMatrix(xyz,elmt,k))
b = -exp(1im*k*xyz[:,1])

c = A\b
f = open(string("ck", k, "sph", nrMesh), "w") # save results
writedlm(f,[real(c) imag(c)])
close(f)
resid = A*c-b

print("Potential at -1 0 0 is ", potential(xyz,elmt,k,[-1 0 0],c), k, " =k, nrMesh = ", nrMesh, "\n\n")
checkSolution(xyz,elmt,k,c)

# Compare with compressed system matrix
Acom = @time(getComprSystemMatrix(xyz,elmt,k,cuto))
ccom = Acom\b
f = open(string("windowck", k, "sph", nrMesh), "w")
writedlm(f,[real(ccom) imag(ccom)])
close(f)
resid = A*ccom-b
residcom = Acom*ccom-b

print(resid[1:10], "=resid, residcom = ", residcom[1:10], '\n')
print("Potential at -1 0 0 is ", potential(xyz,elmt,k,[-1 0 0],ccom), "\n\n")
checkSolution(xyz,elmt,k,ccom)


function init_pot_Hcoul( LF, center )
    Npoints = LF.Nx * LF.Ny * LF.Nz
    Vpot = zeros( Float64,Npoints )
    for ip = 1:Npoints
        r = norm( LF.lingrid[:,ip] - center[:] )
        Vpot[ip] = -1.0/r
    end
    return Vpot
end


function Uk_poly_Kn(p, v, p2, ::Type{Float32})
    u0 = one(p)
    u1 = 1 / 24 * evalpoly(p2, (3, -5))
    u2 = 1 / 1152 * evalpoly(p2, (81, -462, 385))
    return evalpoly(-p/v, (u0, u1, u2))
end
function Uk_poly_Kn(p, v, p2, ::Type{T}) where T <: Float64
    u0 = one(T)
    u1 = 1 / 24 * evalpoly(p2, (3, -5))
    u2 = 1 / 1152 * evalpoly(p2, (81, -462, 385))
    u3 = 1 / 414720 * evalpoly(p2, (30375, -369603, 765765, -425425))
    u4 = 1 / 39813120 * evalpoly(p2, (4465125, -94121676, 349922430, -446185740, 185910725))
    return evalpoly(-p/v, (u0, u1, u2, u3, u4))
end
function Uk_poly_In(p, v, p2, ::Type{T}) where T <: Float64
    u0 = one(T)
    u1 = -1 / 24 * evalpoly(p2, (3, -5))
    u2 = 1 / 1152 * evalpoly(p2, (81, -462, 385))
    u3 = -1 / 414720 * evalpoly(p2, (30375, -369603, 765765, -425425))
    u4 = 1 / 39813120 * evalpoly(p2, (4465125, -94121676, 349922430, -446185740, 185910725))
    return evalpoly(-p/v, (u0, u1, u2, u3, u4))
end
function Uk_poly_In(p, v, p2, ::Type{Float32})
    u0 = one(p)
    u1 = -1 / 24 * evalpoly(p2, (3, -5))
    u2 = 1 / 1152 * evalpoly(p2, (81, -462, 385))
    return evalpoly(-p/v, (u0, u1, u2))
 end

#=
function Uk_poly_Kn(p, v, p2, ::Type{T}) where T <: BigFloat
    u0 = one(T)
    u1 = 1 / 24 * evalpoly(p2, (3, -5))
    u2 = 1 / 1152 * evalpoly(p2, (81, -462, 385))
    u3 = 1 / 414720 * evalpoly(p2, (30375, -369603, 765765, -425425))
    u4 = 1 / 39813120 * evalpoly(p2, (4465125, -94121676, 349922430, -446185740, 185910725))
    u5 = 1 / 6688604160 * evalpoly(p2, (1519035525, -49286948607, 284499769554, -614135872350, 566098157625, -188699385875))
    u6 = 1 / 4815794995200 * evalpoly(p2, (2757049477875, -127577298354750, 1050760774457901, -3369032068261860,5104696716244125, -3685299006138750, 1023694168371875))
    u7 = 1 / 115579079884800 * evalpoly(p2, (199689155040375, -12493049053044375, 138799253740521843, -613221795981706275, 1347119637570231525, -1570320948552481125, 931766432052080625,  -221849150488590625))
    u8 = 1 / 22191183337881600 * evalpoly(p2, (134790179652253125, -10960565081605263000, 157768535329832893644, -914113758588905038248, 2711772922412520971550, -4513690624987320777000, 4272845805510421639500, -2152114239059719935000, 448357133137441653125))
    u9 = 1 / 263631258054033408000 * evalpoly(p2, (6427469716717690265625, -659033454841709672064375, 11921080954211358275362500, -87432034049652400520788332, 334380732677827878090447630, -741743213039573443221773250, 992115946599792610768672500, -790370708270219620781737500, 345821892003106984030190625, -64041091111686478524109375))
    u10 = 1 / 88580102706155225088000 * evalpoly(p2, (9745329584487361980740625, -1230031256571145165088463750, 27299183373230345667273718125, -246750339886026017414509498824, 1177120360439828012193658602930, -3327704366990695147540934069220, 5876803711285273203043452095250, -6564241639632418015173104205000, 4513386761946134740461797128125, -1745632061522350031610173343750, 290938676920391671935028890625))
    return evalpoly(-p/v, (u0, u1, u2, u3, u4, u5, u6, u7, u8, u9, u10))
end

    u0 = one(x)
    u1 = p / 24 * (3 - 5*p^2) * -1 / v
    u2 = p^2 / 1152 * (81 - 462*p^2 + 385*p^4) / v^2
    u3 = p^3 / 414720 * (30375 - 369603 * p^2 + 765765*p^4 - 425425*p^6) * -1 / v^3
    u4 = p^4 / 39813120 * (4465125 - 94121676*p^2 + 349922430*p^4 - 446185740*p^6 + 185910725*p^8) / v^4
    u5 = p^5 / 6688604160 * (-188699385875*p^10 + 566098157625*p^8 - 614135872350*p^6 + 284499769554*p^4 - 49286948607*p^2 + 1519035525) * -1 / v^5
    u6 = p^6 / 4815794995200 * (1023694168371875*p^12 - 3685299006138750*p^10 + 5104696716244125*p^8 - 3369032068261860*p^6 + 1050760774457901*p^4 - 127577298354750*p^2 + 2757049477875) * 1 / v^6
    u7 = p^7 / 115579079884800 * (-221849150488590625*p^14 + 931766432052080625*p^12 - 1570320948552481125*p^10 + 1347119637570231525*p^8 - 613221795981706275*p^6 + 138799253740521843*p^4 - 12493049053044375*p^2 + 199689155040375) * -1 / v^7
    u8 = p^8 / 22191183337881600 * (448357133137441653125*p^16 - 2152114239059719935000*p^14 + 4272845805510421639500*p^12 - 4513690624987320777000*p^10 + 2711772922412520971550*p^8 - 914113758588905038248*p^6 + 157768535329832893644*p^4 - 10960565081605263000*p^2 + 134790179652253125) * 1 / v^8
    u9 = p^9 / 263631258054033408000 * (-64041091111686478524109375*p^18 + 345821892003106984030190625*p^16 - 790370708270219620781737500*p^14 + 992115946599792610768672500*p^12 - 741743213039573443221773250*p^10 + 334380732677827878090447630*p^8 - 87432034049652400520788332*p^6 + 11921080954211358275362500*p^4 - 659033454841709672064375*p^2 + 6427469716717690265625) * -1 / v^9
    u10 = p^10 / 88580102706155225088000 * (290938676920391671935028890625*p^20 - 1745632061522350031610173343750*p^18 + 4513386761946134740461797128125*p^16 - 6564241639632418015173104205000*p^14 + 5876803711285273203043452095250*p^12 - 3327704366990695147540934069220*p^10 + 1177120360439828012193658602930*p^8 - 246750339886026017414509498824*p^6 + 27299183373230345667273718125*p^4 - 1230031256571145165088463750*p^2 + 9745329584487361980740625) * 1 / v^10
    u11 = p^11 / 27636992044320430227456000 * (-1363312191078326248171914924296875*p^22 + 8997860461116953237934638500359375*p^20 - 25963913760458280822131901737878125*p^18 + 42936745153513007436411401865860625*p^16 - 44801790321820682384740638703320750*p^14 + 30589806122850866110941936529264950*p^12 - 13704902022868787041097596217578170*p^10
        + 3926191452593448964331218647028194*p^8 - 676389476843440422173605288596087*p^6 + 62011003282542082472252466220875*p^4 - 2321657500166464779660536015625*p^2 + 15237265774872558064250728125) * -1 / v^11
    u12 = p^12 / 39797268543821419527536640000 * (32426380464797989812768996474401171875*p^24 - 233469939346545526651936774615688437500*p^22 + 743739612850105971846901081692858843750*p^20 - 1378260730939829908037976894025628887500*p^18 + 1642838631056253395899341314188134178125*p^16 - 1314368459332124683504418467809129387000*p^14
        + 714528665351965363868467348116538170900*p^12 - 261201165596865827608687437905740780920*p^10 + 62055079517573388459132793029571876461*p^8 - 8958590476947726766450604043798559500*p^6 + 692277766674325563109617997687563750*p^4 - 21882222767154197351962677311437500*p^2 + 120907703923613748239829527671875) * 1 / v^12
    u13 = p^13 / 955134445051714068660879360000 * (-14020668045586884672121117629431460546875*p^26 + 109361210755577700442544717509565392265625*p^24 - 381190503845282445953419057314665534156250*p^22 + 782463969315283937856703223178540650343750*p^20
        - 1049095945162229046324321461816274931715625*p^18 + 962926533925253374359704288384340809260875*p^16 - 616410216242554698436702353237166008656700*p^14 + 274983827478138958623041508409195988431140*p^12 - 83924867223075156862785921508524155665245*p^10
        + 16843538631795795357786827345307534156063*p^8 - 2069933923586966756183324291232117362250*p^6 + 136735019134677724428035696765082813750*p^4 - 3698121486504259988094897605296209375*p^2 + 17438611142828905996129258798828125) * -1 / v^13
    u14 = p^14 / 45846453362482275295722209280000 * (13133360053559028970728309756597440972265625*p^28 - 110320224449895843354117801955418504167031250*p^26 + 417630985812040040477569028872405152769921875*p^24
        - 940627071986145750405920450097257805227812500*p^22 + 1401302601668131482630653233972052729323190625*p^20 - 1451823699927947602004297385351260623500372750*p^18 + 1070439683260179398514645952824098811025619475*p^16
        - 564850830044980230366140582063618983657685400*p^14 + 211477117385619365164298957821904115470535555*p^12 - 54857705817658080981995319669299096598482382*p^10 + 9440449669103391509091075981237243128469201*p^8
        - 1000503839668383458999731491914516564625300*p^6 + 57170953417612444837142230812990944671875*p^4 - 1338184074771428116079233795614103631250*p^2 + 5448320367052402487647812713291015625) * 1 / v^14
    u15 = p^15 / 9820310310243703368343697227776000000 * (- 59115551939078562227317999668652486368337724609375*p^30 + 532039967451707060045861997017872377315039521484375*p^28 - 2173722139119126857509156976742601742357422740234375*p^26
        + 5329871927856528282113994744458999865006055974609375*p^24 - 8735135969643867540297524795790262235822823374296875*p^22 + 10085018700249896522602267572484630409640764997271875*p^20 - 8420533422834140468835467666391400380550043688871875*p^18
        + 5136561256208409671660362298619778869859994724706875*p^16 - 2284251621937242886581917667066122422330060024456125*p^14 + 730367145705123976114617970888707594104468177381925*p^12 - 163359140754958502896104062604202934925448173291477*p^10
        + 24403480234538299231733883413666768614198435948125*p^8 - 2254933495791765108580529087615802250458013685625*p^6 + 112597271053778779753048514469995937998172890625*p^4 - 2303431987527333128955769182299845911305390625*p^2 + 8178936810213560828419581728001773291015625) * -1 / v^15

=#


using SparseArrays
using LinearAlgebra
using Plots

include("vekit.jl")

let
    # Metrizen definieren
    A=[23 4 3; 5 4 5; -30 -10 -10]
    B=[-1 0 1; 0 1 0; 1 0 -1]
    C=[2 -3 1; 3 1 3; -5 2 -4]
    # y_0 definieren
    y=[8; 3; 5]/norm([8; 3; 5])
    # epsilon definieren
    e=10^-10
    # Eigenwert und eigenvektor berechnen, Fehler auf Eigenwert berechnen
    l_A,y_A=vekit(A,y,e)
    f_A=map(x -> abs((x-maximum(eigen(A).values))/maximum(eigen(A).values)), l_A)
    l_B,y_B=vekit(B,y,e)
    f_B=map(x -> abs((x-maximum(eigen(B).values))/maximum(eigen(B).values)), l_B)
    l_C,y_C=vekit(C,y,e)
    f_C=map(x -> abs((x-maximum(eigen(C).values))/maximum(eigen(C).values)), l_C)

    #TODO: Plot
end


"""
Example of how to obtain the bid which maximises the profit of the Generator at the slack
bus using the Package MaximumGeneratorProfitTCRDD. The Algorithm is based on the
Transmission Constrained Residual Demand Derivative (TCRDD) [1].

[1] L. Xu, R. Baldick and Y. Sutjandra, "Bidding Into Electricity Markets:
    A Transmission-Constrained Residual Demand Derivative Approach," in IEEE
    Transactions on Power Systems, vol. 26, no. 3, pp. 1380-1388, Aug. 2011,
    doi: 10.1109/TPWRS.2010.2083702.
"""
# Packages
using DataFrames
using Dates
using DataStructures
using Ipopt
using MaximumGeneratorProfitTCRDD
using PowerSystems
using PowerSimulations
using Test
using TimeSeries

# Build Test Case
include("data/example_case4/buildup_case4.jl")
include("data/example_case4/c_sys_case4.jl")
sys = c_sys_case4()

# Settings
# Solver for OPF
solver = optimizer_with_attributes(Ipopt.Optimizer)
# Network representation
network = StandardPTDFModel
# Tolerance to identify binding constraints
dual_lines_tol = 1e-1
dual_gen_tol = 1e-1
# Segments to evaluate approximated profit function
segm_bid_argmax_profit = 50000
# Maximum iterations for Screening and Bisection Loops
maxit_scr = 3
maxit_bi = 5
# Convergence Tolerance between bids in [pu]
epsilon = 0.01 #tolerance bid_lo - bid_hi < epsilon
# Flags
print_results = true
print_progress = true
print_plots = true

# Initial Bid
bid0 = 0.3

# Maximize Generator Profit
bid_opt_found, bid_opt, bid_lo, bid_hi, iter_scr, iter_bi = maxGenProfit_tcrdd(
    sys,
    bid0;
    dual_lines_tol,
    dual_gen_tol,
    segm_bid_argmax_profit,
    maxit_scr,
    maxit_bi,
    epsilon,
    print_results,
    print_progress,
    print_plots,
    network,
    solver
)


@testset "Double Description" begin
    @test Polyhedra.polytypefor(Float32) == Float64
    @testset "H-representation -> V-representation" begin
        @testset "Intersection" begin
            @testset "Vector" begin
                @testset "Exact" begin
                    h = hrep([HalfSpace([ 1,  1], 1),
                              HalfSpace([ 1, -1], 0),
                              HalfSpace([-1,  0], 0)])
                    #v = @inferred doubledescription(h)
                    v = doubledescription(h)
                    @test v isa Polyhedra.Hull{Rational{BigInt},Vector{Rational{BigInt}}}
                    @test collect(points(v)) == [[1//2, 1//2], [0, 0], [0, 1]]
                    @test !hasallrays(v)
                end
                @testset "Numerical" begin
                    h = hrep([HalfSpace([ 1.,  1], 1),
                              HalfSpace([ 1., -1], 0),
                              HalfSpace([-1.,  0], 0)])
                    #v = @inferred doubledescription(h)
                    v = doubledescription(h)
                    @test v isa Polyhedra.Hull{Float64,Vector{Float64}}
                    @test collect(points(v)) == [[1/2, 1/2], [0.0, 0.0], [0.0, 1.0]]
                    @test !hasallrays(v)
                end
            end
            @testset "SVector" begin
                @testset "Exact" begin
                    h = hrep([HalfSpace((@SVector [ 1,  1]), 1),
                              HalfSpace((@SVector [ 1, -1]), 0),
                              HalfSpace((@SVector [-1,  0]), 0)])
                    #v = @inferred doubledescription(h)
                    v = doubledescription(h)
                    @test v isa Polyhedra.Hull{Rational{BigInt},SVector{2,Rational{BigInt}}}
                    @test collect(points(v)) == [(@SVector [1//2, 1//2]), (@SVector [0, 0]), (@SVector [0, 1])]
                    @test !hasallrays(v)
                end
                @testset "Numerical" begin
                    h = hrep([HalfSpace((@SVector [ 1.,  1]), 1),
                              HalfSpace((@SVector [ 1., -1]), 0),
                              HalfSpace((@SVector [-1.,  0]), 0)])
                    #v = @inferred doubledescription(h)
                    v = doubledescription(h)
                    @test v isa Polyhedra.Hull{Float64,SVector{2,Float64}}
                    @test collect(points(v)) == [(@SVector [1/2, 1/2]), (@SVector [0.0, 0.0]), (@SVector [0.0, 1.0])]
                    @test !hasallrays(v)
                end
            end
        end
        @testset "MixedMatHRep" begin
            @testset "Exact" begin
                h = hrep([ 1  1
                           1 -1
                          -1  0],
                         [1, 0, 0])
                #v = @inferred doubledescription(h)
                v = doubledescription(h)
                @test v isa Polyhedra.MixedMatVRep{Rational{BigInt}}
                @test v.V == [1//2 1//2; 0//1 0//1; 0//1 1//1]
                @test v.R == zeros(Rational{BigInt}, 0, 2)
                @test isempty(v.Rlinset)
            end
            @testset "Numerical" begin
                h = hrep([ 1  1
                           1 -1
                          -1  0],
                         [1., 0, 0])
                #v = @inferred doubledescription(h)
                v = doubledescription(h)
                @test v isa Polyhedra.MixedMatVRep{Float64}
                @test v.V == [1/2 1/2; 0 0; 0 1]
                @test v.R == zeros(0, 2)
                @test isempty(v.Rlinset)
            end
        end
        @testset "Empty Intersection" begin
            h = HyperPlane([0, 1], 1) ∩ HyperPlane([0, 1], -1)
            @testset "0x_1 + 0x_2 = 1" begin
                @testset "Exact" begin
                    h = intersect(HyperPlane([0, 0], 1))
                    #v = @inferred doubledescription(h)
                    v = doubledescription(h)
                    @test v isa Polyhedra.Hull{Rational{BigInt},Vector{Rational{BigInt}}}
                    @test !haspoints(v)
                    @test !hasallrays(v)
                end
                @testset "Numerical" begin
                    h = intersect(HyperPlane([0., 0], 1))
                    #v = @inferred doubledescription(h)
                    v = doubledescription(h)
                    @test v isa Polyhedra.Hull{Float64,Vector{Float64}}
                    @test !haspoints(v)
                    @test !hasallrays(v)
                end
            end
            @testset "-1 = 0x_1 + x_2 = 1" begin
                @testset "Exact" begin
                    h = HyperPlane([0, 1], 1) ∩ HyperPlane([0, 1], -1)
                    #v = @inferred doubledescription(h)
                    v = doubledescription(h)
                    @test v isa Polyhedra.Hull{Rational{BigInt},Vector{Rational{BigInt}}}
                    @test !haspoints(v)
                    @test !hasallrays(v)
                end
                @testset "Numerical" begin
                    h = HyperPlane([0, 1.], 1) ∩ HyperPlane([0, 1.], -1)
                    #v = @inferred doubledescription(h)
                    v = doubledescription(h)
                    @test v isa Polyhedra.Hull{Float64,Vector{Float64}}
                    @test !haspoints(v)
                    @test !hasallrays(v)
                end
            end
            @testset "1 ≤ x_1 + 0x_2 ≤ -1" begin
                @testset "Exact" begin
                    h = HalfSpace([1, 0], -1) ∩ HalfSpace([-1, 0], -1)
                    #v = @inferred doubledescription(h)
                    v = doubledescription(h)
                    @test v isa Polyhedra.Hull{Rational{BigInt},Vector{Rational{BigInt}}}
                    @test !haspoints(v)
                    @test !hasallrays(v)
                end
                @testset "Numerical" begin
                    h = HalfSpace([1, 0.], -1) ∩ HalfSpace([-1, 0.], -1)
                    #v = @inferred doubledescription(h)
                    v = doubledescription(h)
                    @test v isa Polyhedra.Hull{Float64,Vector{Float64}}
                    @test !haspoints(v)
                    @test !hasallrays(v)
                end
            end
        end
    end
    @testset "V-representation -> H-representation" begin
        @testset "Hull" begin
            @testset "Vector" begin
                @testset "Exact" begin
                    v = conichull([1, 0],
                                  [0, 1])
                    h = doubledescription(v)
                    #h = @inferred doubledescription(v)
                    @test !hashyperplanes(h)
                    @test collect(halfspaces(h)) == [HalfSpace([0, -1], 0), HalfSpace([-1, 0], 0), HalfSpace([0, 0], 1)] # FIXME get rid of (0, 0) 1
                end
                @testset "Numerical" begin
                    v = conichull([1., 0.],
                                  [0., 1.])
                    h = doubledescription(v)
                    #h = @inferred doubledescription(v)
                    @test !hashyperplanes(h)
                    @test collect(halfspaces(h)) == [HalfSpace([0, -1], 0), HalfSpace([-1, 0], 0), HalfSpace([0, 0], 1)] # FIXME get rid of (0, 0) 1
                end
            end
        end
    end
end


# 3D
function LinearAlgebra.eigvals(R::Rotation{3})
    θ = rotation_angle(R)
    return SVector(exp(-θ*im), exp(θ*im), 1)
end

function LinearAlgebra.eigvecs(R::Rotation{3})
    n3 = normalize(rotation_axis(R))
    n1 = normalize(perpendicular_vector(n3))
    n2 = normalize(n3×n1)
    v1 = normalize(n1 + n2*im)
    v2 = normalize(n1*im + n2)
    v3 = n3
    return hcat(v1,v2,v3)
end

function LinearAlgebra.eigen(R::Rotation{3})
    return eigen(AngleAxis(R))
end

function LinearAlgebra.eigen(R::AngleAxis)
    λs = eigvals(R)
    vs = eigvecs(R)
    return Eigen(λs, vs)
end

# 2D
function LinearAlgebra.eigvals(R::Rotation{2})
    θ = rotation_angle(R)
    return SVector(exp(-θ*im), exp(θ*im))
end

function LinearAlgebra.eigvecs(R::Rotation{2})
    return @SMatrix [1/√2 1/√2;im/√2 -im/√2]
end

function LinearAlgebra.eigen(R::Rotation{2})
    λs = eigvals(R)
    vs = eigvecs(R)
    return Eigen(λs, vs)
end


struct IntegralEngine{B <: AbstractBlackScholesProcess} <: PricingEngine
  process::B
end

struct Integrand{P <: StrikedTypePayoff} <: Function
  payoff::P
  s0::Float64
  drift::Float64
  variance::Float64
end

function (integrand::Integrand)(x::Float64)
  temp = integrand.s0 * exp(x)
  result = integrand.payoff(temp)
  return result * exp(-(x - integrand.drift) * (x - integrand.drift) / (2.0 * integrand.variance))
end

function _calculate!(pe::IntegralEngine, opt::EuropeanOption)
  ex = opt.exercise
  payoff = opt.payoff
  variance = black_variance(pe.process.blackVolatility, ex.dates[end], payoff.strike)

  dividendDiscount = discount(pe.process.dividendYield, ex.dates[end])
  riskFreeDiscount = discount(pe.process.riskFreeRate, ex.dates[end])

  drift_ = log(dividendDiscount / riskFreeDiscount) - 0.5 * variance

  f = Integrand(payoff, state_variable(pe.process).value, drift_, variance)

  integrator = SegmentIntegral(5000)
  infinity = 10.0 * sqrt(variance)

  opt.results.value = discount(pe.process.riskFreeRate, ex.dates[end]) / sqrt(2.0 * pi * variance) *
                      integrator(f, drift_ - infinity, drift_ + infinity)

  return opt
end


## SINGULARITY DISTRIBUTIONS
#============================================#

## Helper functions

function polar_transform(x, z, x1, x2)
    s1, s2 = x - x1, x - x2
    r1, r2 = √(s1^2 + z^2), √(s2^2 + z^2)
    θ1, θ2 = atan(z, s1), atan(z, s2)

    r1, r2, θ1, θ2
end

# "Fundamental" terms
term_1(xp, x, z, d, r1, r2, θ1, θ2) = d - z * (θ2 - θ1) + (x - xp) * log(r2 / r1)
term_2(xp, x, z, r1, r2, θ1, θ2) = (x - xp) * (θ2 - θ1) + z * log(r2 / r1)

boundary_term(xp, x, d) = (1/2π, 1/2 * (x - xp) / d)

# VORTEX SINGULARITIES
#============================================#

## Linear-strength singularities

# Streamfunctions
function linear_vortex_stream_minus(γ, x, z, x1, x2)
    r1, r2, θ1, θ2 = polar_transform(x, z, x1, x2)
    d = x2 - x1

    # Singularity checks
    ϵ = 1e-10
    if r1 < ϵ; logr1 = 0.; else logr1 = log(r1) end
    if r2 < ϵ; logr2 = 0.; else logr2 = log(r2) end

    ψ_m = γ / 2π * (z * (θ2 - θ1) - d + x * logr1 - (x - d) * logr2)
end

function linear_vortex_stream_plus(γ, x, z, x1, x2)
    r1, r2, _, _= polar_transform(x, z, x1, x2)
    d   = x2 - x1
    ψ_m = linear_vortex_stream_minus(1., x, z, x1, x2)

    ϵ = 1e-10
    if r1 < ϵ; logr1 = 0.; else logr1 = log(r1) end
    if r2 < ϵ; logr2 = 0.; else logr2 = log(r2) end

    ψ_p = γ / d * (x * ψ_m + 1/4π * (r2^2 * (logr2 - 1/2) - r1^2 * (logr1 - 1/2)))
end

# Velocities
function linear_vortex_velocity_a(γ1, x, z, x1, x2)
    r1, r2, θ1, θ2 = polar_transform(x, z, x1, x2)
    d = x2 - x1

    # ua = term_2(x2, x, z, r1, r2, θ1, θ2)
    # wa = term_1(x2, x, z, d, r1, r2, θ1, θ2)

    dlogr = log(r1 / r2)
    dθ    = (θ2 - θ1)

    u =     dθ / 2π + (z * dlogr     - x * dθ) / (2π * d)
    w = -dlogr / 2π + (x * dlogr - d + z * dθ) / (2π * d)

    γ1 * SVector(u, w)
end

function linear_vortex_velocity_b(γ2, x, z, x1, x2)
    r1, r2, θ1, θ2 = polar_transform(x, z, x1, x2)
    d = x2 - x1

    # ub = term_2(x1, x, z, r1, r2, θ1, θ2)
    # wb = term_1(x1, x, z, d, r1, r2, θ1, θ2)

    dlogr = log(r1 / r2)
    dθ    = (θ2 - θ1)

    u = -(z * dlogr     - x * dθ) / (2π * d)
    w = -(x * dlogr - d + z * dθ) / (2π * d)

    γ2 * SVector(u, w)
end

# SOURCE SINGULARITIES
#============================================#

## Constant-strength singularities

# Streamfunctions
function constant_source_stream(σ, x, z, x1, x2)
    r1, r2, θ1, θ2 = polar_transform(x, z, x1, x2)
    d = x2 - x1

    # Singularity checks
    ϵ = 1e-10;
    if r1 < ϵ; logr1 = 0.; θ1 = π; θ2 = π; else logr1 = log(r1); end
    if r2 < ϵ; logr2 = 0.; θ1 = 0.; θ2 = 0.; else logr2 = log(r2); end

    ψ = σ / 2π * (x * (θ1 - θ2) + d * θ2 + z * (logr1 - logr2))

    # Branch cuts
    dψ = x2
    if (θ1 + θ2) > π
        ψ -= 0.25 * dψ
    else
        ψ += 0.75 * dψ
    end
end

# Velocity
function constant_source_velocity(σ, x, z, x1, x2)
    r1, r2, θ1, θ2 = polar_transform(x, z, x1, x2)
    @. σ / 2π * SVector(-log(r2 / r1), θ2 - θ1)
end

## Linear-strength singularities

# Streamfunctions
function linear_source_stream_minus(σ, x, z, x1, x2)
    r1, r2, θ1, θ2 = polar_transform(x, z, x1, x2)
    d = x2 - x1

    # Branch cuts
    if θ1 < 0.; θ1 += 2π; end
    if θ2 < 0.; θ2 += 2π; end

    # Singularity checks
    ϵ = 1e-10;
    if r1 < ϵ; logr1 = 0.; θ1 = π; θ2 = π; else logr1 = log(r1); end
    if r2 < ϵ; logr2 = 0.; θ1 = 0.; θ2 = 0.; else logr2 = log(r2); end

    ψ = σ / 2π * (x * (θ1 - θ2) + d * θ2 + z * (logr1 - logr2))
end

function linear_source_stream_plus(σ, x, z, x1, x2)
    r1, r2, θ1, θ2 = polar_transform(x, z, x1, x2)
    d = x2 - x1
    ψ_m = linear_source_stream_minus(1., x, z, x1, x2)

    # Branch cuts
    if θ1 < 0.; θ1 += 2π; end
    if θ2 < 0.; θ2 += 2π; end

    # Singularity checks
    ϵ = 1e-10;
    if r1 < ϵ; logr1 = 0.; θ1 = π; θ2 = π; else logr1 = log(r1); end
    if r2 < ϵ; logr2 = 0.; θ1 = 0.; θ2 = 0.; else logr2 = log(r2); end

    ψ_p = σ / d * (x * ψ_m + 1/4π * (r2^2 * θ2 - r1^2 * θ1 - z * d))
end

# Velocities
function linear_source_velocity_a(σ1, x, z, x1, x2)
    r1, r2, θ1, θ2 = polar_transform(x, z, x1, x2)
    d = x2 - x1
    # if abs(z) <= 1e-12
        # ua, wa = # boundary_term(x2, x, d)
        # σ1 .* SVector(ua, -wa)
    # else
        # ua = term_1(x2, x, z, d, r1, r2, θ1, θ2)
        # wa = term_2(x2, x, z, r1, r2, θ1, θ2)

        # @. σ1 / (2π * (x2 - x1)) * SVector(ua, -wa)
    # end

    dlogr = log(r1 / r2)
    dθ    = (θ2 - θ1)

    u = dlogr / 2π - (x * dlogr - d + z * dθ) / (2π * d)
    w =    dθ / 2π + (z * dlogr     - x * dθ) / (2π * d)

    σ1 * SVector(u, w)
end

function linear_source_velocity_b(σ2, x, z, x1, x2)
    r1, r2, θ1, θ2 = polar_transform(x, z, x1, x2)
    d = x2 - x1
    # if abs(z) <= 1e-12
    #     ub, wb = # boundary_term(x1, x, d)
    #     σ2 .* SVector(-ub, wb)
    # else
    #     ub = term_1(x1, x, z, d, r1, r2, θ1, θ2)
    #     wb = term_2(x1, x, z, r1, r2, θ1, θ2)

    #     @. σ2 / (2π * (x2 - x1)) * SVector(-ub, wb)
    # end

    dlogr = log(r1 / r2)
    dθ    = (θ2 - θ1)

    u =  (x * dlogr - d + z * dθ) / (2π * d)
    w = -(z * dlogr     - x * dθ) / (2π * d)

    σ2 * SVector(u, w)
end


module Benitor

function fibonacciTail(n::Int,a,b)
    if n <= 0
        return b
    end
    return fibonacciTail(n-1 , b, a+b)
end

end # module


@taylorize function vanderpol!(dx, x, params, t)
    local μ = 1.0
    dx[1] = x[2]
    dx[2] = (μ * x[2]) * (1 - x[1]^2) - x[1]
    return dx
end

function vanderpol()
   X0 = Hyperrectangle(low=[1.25, 2.35], high=[1.55, 2.45])
   ivp = @ivp(x' = vanderpol!(x), dim: 2, x(0) ∈ X0)
   tspan = (0.0, 5.0)
   return ivp, tspan
end


# load

using Flux
using Flux: onehotbatch, argmax, crossentropy, throttle, @epochs
using BSON: @save, @load
using Base.Iterators: partition

m = Chain(
  Dense(28^2, 32, relu),
  Dense(32, 10),
  softmax) |> gpu

loss(x, y) = crossentropy(m(x), y)

accuracy(x, y) = mean(argmax(m(x)) .== argmax(y))

@load "fashion_mnist_mlp_model.bson" m
@load "fashion_mnist_mlp_weights.bson" weights
Flux.loadparams!(m, weights)

using MLDatasets # FashionMNIST
using ColorTypes: N0f8, Gray

const Img = Matrix{Gray{N0f8}}

function prepare_test()
    # load full test set
    test_x,  test_y  = FashionMNIST.testdata() # 10_000

    testrange = 1:1_000 # 1:10_000
    test_imgs = Img.([test_x[:,:,i] for i in testrange])
    tX = hcat(float.(reshape.(test_imgs, :))...) |> gpu
    tY = onehotbatch(test_y[testrange], 0:9) |> gpu
    tX, tY
end

tX, tY = prepare_test()
@show(accuracy(tX, tY))
# accuracy(tX, tY) = 0.839


function test_eftRecip{T<:AbstractFloat}(x::T)
    hi,lo = eftRecip(x)
    b = big(1)/big(x)
    tst = Float64(b - (big(hi)+big(lo)))
    eps(abs(lo))/2 >= abs(tst)
end


using LinearAlgebra
using CompScienceMeshes
using SauterSchwab3D
using StaticArrays

const pI   = point(1,0,0)
const pII  = point(0,1,0)
const pIII = point(0,0,0)
const pIV  = point(0,0,1)

const qIV  = point(0,0,-1)

const P = simplex(pI,pII,pIV,pIII)
const Q = simplex(pI,pII,qIV,pIII)

const sing = SauterSchwab3D.singularity_detection(P,Q)

Accuracy2 = 18
cf_ref = CommonFace6D(sing,SauterSchwab3D._legendre(Accuracy2,0.0,1.0))

function integrand(x,y)
      return ((x-pI)'*(y-pIV))*exp(-im*1*norm(x-y))/(4pi*norm(x-y))
end

function INTEGRAND(u,v)
   j1 = volume(P) * factorial(dimension(P))
   j2 = volume(Q) * factorial(dimension(Q))
   x = barytocart(P,u)
   y = barytocart(Q,v)
   output = integrand(x,y)*j1*j2

   return output
end

print("Ref: ")
ref = sauterschwab_parameterized(INTEGRAND, cf_ref)
println(ref)
println()

res_tp =[]
res_sp =[]
res_gm =[]
n1 = []
n2 = []
n3 = []
for i in 2:1:15
   Accuracy = i
   cf = CommonFace6D(sing,SauterSchwab3D._legendre(Accuracy,0.0,1.0))

   int_tp = sauterschwab_parameterized(INTEGRAND, cf)

   num_pts = 15*length(cf.qps)^6
   push!(n1,num_pts)

   push!(res_tp,int_tp)

end

for i in 2:1:7
   Accuracy = i
   cf_s = CommonFace6D_S(sing,(SauterSchwab3D._legendre(Accuracy,0.0,1.0),
                          SauterSchwab3D._shunnham2D(Accuracy),
                          SauterSchwab3D._shunnham3D(Accuracy)))

   int_sp = sauterschwab_parameterized(INTEGRAND, cf_s)

   num_pts = 3*length(cf_s.qps[1])*length(cf_s.qps[2])*length(cf_s.qps[3])+
            12*length(cf_s.qps[3])^2
   push!(n2,num_pts)
   push!(res_sp,int_sp)
end

for i in 2:1:12
   Accuracy = i
   cf_gm = CommonFace6D_S(sing,(SauterSchwab3D._legendre(Accuracy,0.0,1.0),
                          SauterSchwab3D._grundmannMoeller2D(2*Accuracy-1),
                          SauterSchwab3D._grundmannMoeller3D(2*Accuracy-1)))

   int_gm = sauterschwab_parameterized(INTEGRAND, cf_gm)

   num_pts = 3*length(cf_gm.qps[1])*length(cf_gm.qps[2])*length(cf_gm.qps[3])+
            12*length(cf_gm.qps[3])^2
   push!(n3,num_pts)
   push!(res_gm,int_gm)
end

err_tp = norm.(res_tp.-ref)/norm(ref)
err_sp = norm.(res_sp.-ref)/norm(ref)
err_gm = norm.(res_gm.-ref)/norm(ref)

println(err_tp)
println(err_sp)
println(err_gm)

using Plots
plot( yaxis=:log, xaxis=:log, fontfamily="Times")
plot!(n1,err_tp, label="Gauss Tensor-Product",markershape=:circle)
plot!(n2,err_sp, label="Simplex Tensor-Product",markershape=:rect)
#plot!(n3,err_gm, label="Simplex-Product GM",markershape=:x)
plot!(xlims=(1e2,1e7),ylims=(1e-8,1))
plot!(xlabel="#Quad. pts/Func. evals", ylabel="Rel. Error.", title="Common Face 6D",legend=:bottomleft)

using BenchmarkTools

ref = 0.0011204365593010864 - 0.000870131729342847im

cf = CommonFace6D(sing,SauterSchwab3D._legendre(4,0.0,1.0))

int_tp = sauterschwab_parameterized(INTEGRAND, cf)

num_pts = 15*length(cf.qps)^6
println("#Pts: ",num_pts)
err_tp = norm.(int_tp.-ref)/norm(ref)
println("Rel. Err: ",err_tp)
@time for i in 1:100 sauterschwab_parameterized(INTEGRAND, cf) end

cf_s = CommonFace6D_S(sing,(SauterSchwab3D._legendre(5,0.0,1.0),
                         SauterSchwab3D._shunnham2D(5),
                         SauterSchwab3D._shunnham3D(5)))

int_sp = sauterschwab_parameterized(INTEGRAND, cf_s)

num_pts = 3*length(cf_s.qps[1])*length(cf_s.qps[2])*length(cf_s.qps[3])+
            12*length(cf_s.qps[3])^2
println("#Pts: ",num_pts)
err_sp = norm.(int_sp.-ref)/norm(ref)
println("Rel Err: ",err_sp)
@time for i in 1:100 sauterschwab_parameterized(INTEGRAND, cf_s) end
#=
cf_gm = CommonFace6D_S(sing,(SauterSchwab3D._legendre(5,0.0,1.0),
                         SauterSchwab3D._grundmannMoeller2D(2*5-1),
                         SauterSchwab3D._grundmannMoeller3D(2*5-1)))

int_gm = sauterschwab_parameterized(INTEGRAND, cf_gm)

num_pts = 3*length(cf_gm.qps[1])*length(cf_gm.qps[2])*length(cf_gm.qps[3])+
            12*length(cf_gm.qps[3])^2
println("#Pts: ",num_pts)
err_gm = norm.(int_gm.-ref)/norm(ref)
println("Rel Err: ",err_gm)

=#