"""

    mpnmodel(S::Int64, Co::Float64, forbidden::Float64)

Return `UnipartiteNetwork` with links assigned according to minimum
potential niche model for given number of `S`, connectivity `Co` and
probability of `forbidden` link occurence.

> Allesina, S., Alonso, D. and Pascual, M. (2008) ‘A General Model for Food Web
> Structure’, Science, 320(5876), pp. 658–661. doi: 10.1126/science.1156269.

See also: `nichemodel`, `cascademodel`, `nestedhierarchymodel`
"""
function mpnmodel(S::Int64, Co::Float64, forbidden::Float64)

    Co >= 0.5 && throw(ArgumentError("The connectance cannot be larger than 0.5"))

    # Beta distribution parameter
    β = 1.0/(2.0*Co)-1.0

    # Pre-allocate the network
    A = UnipartiteNetwork(zeros(Bool, (S, S)))

    # Generate body size
    n = sort(rand(Uniform(0.0, 1.0), S))

    # Pre-allocate centroids
    c = zeros(Float64, S)

    # Generate random ranges
    r = n .* rand(Beta(1.0, β), S)

    # Generate random centroids
    for s in 1:S
        c[s] = rand(Uniform(r[s]/2, n[s]))
    end

    # The smallest species has a body size and range of 0
    for small_species_index in findall(x -> x == minimum(n), n)
        n[small_species_index] = 0.0
        r[small_species_index] = 0.0
    end

    # Testing
    # -------------------------------------------------------------------------
    # ranges = Dict{Int64, Tuple}()
    # -------------------------------------------------------------------------

    for consumer in 1:S

        # For testing
        # ---------------------------------------------------------------------
        # u = c[consumer] + r[consumer]
        # l = c[consumer] - r[consumer]
        # diet = findall(x -> (x > l) & (x < u), n)
        # #println("Species ", diet, " fall into diet of consumer ", consumer)
        # length(diet) != 0 && (ranges[consumer] = (diet[1], diet[end]))
        # ---------------------------------------------------------------------

        for resource in 2:(S-1)

            lower = (n[resource] > (c[consumer] - r[consumer]))
            lowerminus = (n[resource-1] > (c[consumer] - r[consumer]))
            upper = n[resource] < (c[consumer] + r[consumer])
            upperplus = n[resource+1] < (c[consumer] + r[consumer])

            if (upper & lower)

                (rand() < (1 - forbidden)) && (A[consumer, resource] = true)

                # Take care of first and last resource if they belong to the range
                ((resource-1 == 1) & lowerminus) && (A[consumer, resource-1] = true)
                ((resource+1 == S) & upperplus) && (A[consumer, resource+1] = true)

                # Edges of range
                lowerminus || (A[consumer, resource] = true)
                upperplus || (A[consumer, resource] = true)

            end

        end

    end

    return A

end

"""

    mpnmodel(N::T) where {T<: UnipartiteNetwork}

Applied to `UnipartiteNetwork` return its randomized version.
"""
function mpnmodel(N::T) where {T<: UnipartiteNetwork}

    # Estimate proportion of forbidden links

    ## Find ordering
    ## sort(N)

    ## Get the value
    forbidden = 0

    # Simulate the network
    return mpn(species(N), connectance(N), forbidden)

end

"""

    mpnmodel(parameters::Tuple)

Parameters tuple can also be provided in the form (S::Int64, Co::Float64,
forbidden::Float64).
"""
function mpnmodel(parameters::Tuple)
    return mpnmodel(parameters[1], parameters[2],parameters[3])
end

"""

    mpnmodel(mpnmodel(S::Int64, L::Int64, forbidden::Float64))

Average number of links can be specified instead of connectance.

"""
function mpnmodel(S::Int64, L::Int64, forbidden::Float64)

    Co = (L/(S*S))
    return mpnmodel(S, Co, forbidden)

end


export Bundle
export Clifford

abstract type Bundle end

"""
    Represents a Clifford bundle, AKA the Hopf fibration of S³.

fields: total and base.
"""
struct Clifford <: Bundle
    total::S³
    base::S²
end

"""
    Clifford(w, z, A, B)

Construct a Clifford bundle with the given Complex numbers: `w`, `z`, `A` and `B`. The ratio
A:B distinguishes the fibers from one another.
"""
Clifford(w::Complex, z::Complex, A::Complex, B::Complex) = begin
    @assert(isapprox(abs(w)^2 + abs(z)^2, 1),
            "The equation |w|² + |z|² = 1 must hold for the given w and z.")
    @assert(!isapprox(abs(A), 0) || !isapprox(abs(B), 0),
            "Aw + Bz = 0, either of A or B can be zero, but not both.")
    Clifford(ComplexPlane(w, z), ComplexLine(A / B))
end

"""
    Clifford(w, z, B)

Construct a Clifford bundle with the given Complex numbers: `w`, `z` and `B`. Here, `B`
on its own identifies a point in the base space and we compute A on the fly.
"""
Clifford(w::Complex, z::Complex, B::Complex) = begin
    # Aw + Bz = 0
    # Aw = -Bz
    # A = -Bz/w
    Clifford(w, z, -B * z / w, B)
end


using InformationMeasures
using Test

arr1 = rand(1000)
arr2 = rand(1000)
arr3 = rand(1000)

# Variables are shared between these tests - don't re-order
include("testDiscretization.jl")
include("testEntropy.jl")
include("testConditionalEntropy.jl")
include("testMutualInformation.jl")
include("testConditionalMutualInformation.jl")
include("testInteractionInformation.jl")
include("testTotalCorrelation.jl")
include("testPartialInformationDecomposition.jl")
include("testCrossEntropy.jl")


mutable struct LinearModel{T}
	d::Array{T,2}
	g::Array{T,2}
	s::Array{T,2}
end

mutable struct LRAOpt{T}
	nt::Int64
	nr::Int64
	nra::Int64 # reduced dimension
	dg::Array{T,2} # store gradients
	ds::Array{T,2} # store gradients
	ddcal::Array{T,2} # store error
	dvec::Vector{T}
	obs::LinearModel{T}
	cal::LinearModel{T}
end

function LinearModel(nt,nr,nra,T;
	       d=nothing,
	       g=nothing,
	       s=nothing)
	(s===nothing) && (s=zeros(T,nt,nra))
	(g===nothing) && (g=zeros(T,nra,nr))
	d=s*g
	return LinearModel(d,g,s)
end

function LRAOpt(nt,nr,nra,T)
	obs=LinearModel(nt,nr,nra,T)
	cal=LinearModel(nt,nr,nra,T)
	dg=zero(obs.g)
	ds=zero(obs.s)
	ddcal=zero(obs.d)
	dvec=zeros(nt*nr) # if data needs to be stored as vector
	return LRAOpt(nt,nr,nra,dg,ds,ddcal,dvec,obs,cal)
end


@testset "mesh_body_utility" begin
    r = 1.2
    l = 2 * r
    eM_box = eMesh_box(r)
    @test area(eM_box) ≈ 6 * l^2
    @test volume(eM_box) ≈ l^3
end


"""
    test_FM() :: Nothing

Computes structure factors for ferromagnetic correlations on different lattices and checks if the maximum resides at the Γ point.
"""
function test_FM() :: Nothing

    # init test dummy
    k = get_momenta((0.0, 1.0 * pi), (0.0, 1.0 * pi), (0.0, 1.0 * pi), (10, 10, 10))

    # initialize ferromagnetic test correlations
    @testset "FM corr" begin
        for name in ["square", "cubic", "kagome", "hyperkagome"]
            l = get_lattice(name, 6, verbose = false)
            r = get_reduced_lattice("heisenberg", [[0.0]], l, verbose = false)
            s = compute_structure_factor(Float64[exp(-norm(r.sites[i].vec)) for i in eachindex(r.sites)], k, l, r)

            @test norm(k[:, argmax(abs.(s))]) ≈ 0.0
        end
    end

    return nothing
end

"""
    test_observable() :: Nothing

Consistency checks for current implementation of observables.
"""
function test_observable() :: Nothing

    # test if ferromagnetic correlations are Fourier transformed correctly
    test_FM()

    return nothing
end


using Test
using SeDuMi

# Dual:
# max x
# [1 x
#  y 1] ⪰ 0
# which is transformed by SeDuMi into
# max x
# [    1   (x+y)/2
#  (x+y)/2     1  ] ⪰ 0
# Which is unbounded with the unbounded the ray (1, -1).
@testset "Semidefinite Programming example" begin
    @testset "Bounded" begin
        # We add y ≥ 0 to make it bounded, Now the optimal solution is `(2, 0)`
        # with the matrix [1 1
        #                  1 1] of eigenvalues (0, 2).
        A = -[0.0 0.0 0.0 1.0 0.0
              1.0 0.0 1.0 0.0 0.0]
        b = [1.0, 0.0]
        c = [0.0, 1.0, 0.0, 0.0, 1.0]
        primal, dual, info = sedumi(A, b, c, SeDuMi.Cone(0, 1, [], [], [2]),
                                    fid=0)
        @test primal ≈ [1, 1, -1, -1, 1]
        @test dual ≈ [2.0, 0.0]
        @test info["pinf"] == 0.0
        @test info["dinf"] == 0.0
        @test info["numerr"] == 0.0
        @test info["iter"] == 3.0
        @test info["feasratio"] ≈ 1.0 rtol=1e-4
    end
    @testset "Unbounded" begin
        A = -[0.0 0.0 1.0 0.0
              0.0 1.0 0.0 0.0]
        b = [1.0, 0.0]
        c = [1.0, 0.0, 0.0, 1.0]
        primal, dual, info = sedumi(A, b, c, SeDuMi.Cone(0, 0, [], [], [2]),
                                    fid=0)
        @test isempty(primal)
        @test dual == [1.0, -1.0]
        @test info["pinf"] == 1.0
    end
end


import AoC2019
using LightGraphs, MetaGraphs

test_input = """
9 ORE => 2 A
8 ORE => 3 B
7 ORE => 5 C
3 A, 4 B => 1 AB
5 B, 7 C => 1 BC
4 C, 1 A => 1 CA
2 AB, 3 BC, 4 CA => 1 FUEL
"""

example_13312 = """
157 ORE => 5 NZVS
165 ORE => 6 DCFZ
44 XJWVT, 5 KHKGT, 1 QDVJ, 29 NZVS, 9 GPVTF, 48 HKGWZ => 1 FUEL
12 HKGWZ, 1 GPVTF, 8 PSHF => 9 QDVJ
179 ORE => 7 PSHF
177 ORE => 5 HKGWZ
7 DCFZ, 7 PSHF => 2 XJWVT
165 ORE => 2 GPVTF
3 DCFZ, 7 NZVS, 5 HKGWZ, 10 PSHF => 8 KHKGT
"""

function create_mapping(lines)
    mapping = Dict()
    names_set = Set{String}()
    min_produce = Dict("ORE" => 1)
    for line in lines
      if line == ""
        continue
      end
      entries = split(line, " => ")
      @assert length(entries) == 2
      inputs = split(entries[1], ", ")
      outputs = entries[2]
      out_count = split(outputs, " ")[1]
      out_name = split(outputs, " ")[2]
      push!(names_set, out_name)
      min_produce[out_name] = parse(UInt8, out_count)
      for inp in inputs
        inp_count = split(inp, " ")[1]
        inp_name = split(inp, " ")[2]
        mapping[(inp_name, out_name)] = parse(UInt8, inp_count)
        push!(names_set, inp_name)
      end
    end
    names=collect(names_set)
    min_produce_list = map(x -> min_produce[x], names)

    return (mapping=mapping, names=names, min_produce = min_produce_list)
end

lines = AoC2019.read_lines(".julia/dev/AoC2019/src/input14.txt")

function generate_fuel(lines, fuel_count=1)
  (mapping, names, min_produce) = create_mapping(lines)
  #println(stderr, names)
  g = MetaDiGraph(length(names))

  for m in keys(mapping)
    #add_edge!(g, findfirst(isequal())
    inp_ind = findfirst(isequal(m[1]), names)
    out_ind = findfirst(isequal(m[2]), names)

    add_edge!(g, inp_ind, out_ind)
    set_props!(g, Edge(inp_ind, out_ind), Dict(:conv =>mapping[m]))

  end

  #start out with Fuel
  fuel_ind = findfirst(isequal("FUEL"), names)
  ore_ind = findfirst(isequal("ORE"), names)
  queue = Array{Tuple{Int64, Int64}, 1}()
  push!(queue, (fuel_count, fuel_ind))

  produced = zeros(Int64, length(names))
  remainder = zeros(Int64, length(names))

  while length(queue) > 0
    current_node = popfirst!(queue)
    id = current_node[2]
    current_required = current_node[1]
    extra_required = current_required - remainder[id]
    neighbors = inneighbors(g, current_node[2])

    producing = convert(Int64, ceil(extra_required / min_produce[id])) * min_produce[id]
    remainder[id] = (producing - extra_required)
    #println("Current: $(names[id]), current_required: $current_required, extra_required: $extra_required, producing: $producing")

    produced[id] += producing

    for n in neighbors
      local conversion_rate = get_prop(g, Edge(n, current_node[2]), :conv)
      #remainder = current_required - producing
      local new_val = producing / min_produce[id] * conversion_rate
      push!(queue, (new_val, n))

    end
  end
  queue_converted = map(x -> (x[1], names[x[2]]), queue)
  #for (i, name) in enumerate(names)
  #  println("Resource $name generated $(produced[i]) times")
  #end
  #for (i, name) in enumerate(names)
  #  println("Resource $name wasted $(remainder[i]) times")
  #end
  return produced[ore_ind]
end

#ore_required = generate_fuel(split(test_input, "\n"))
ore_required = generate_fuel(lines)


@testset "Torsion points" begin

  E43_a1 = @inferred EllipticCurve([0, 1, 1, 0, 0])

  curves_to_test_tor_short = [
  ([1, 2], 4),
  ([2, 3], 2),
  ([0, 1], 6),
  ([1, 0], 2),
  ([1, 1], 1)
  ]

  curves_to_test_tor_struc =
  [
  ([0, -1, 1, -7820, -263580], [1]),
  ([1, 0, 1, -2731, -55146], [2]),
  ([0, 1, 1, -9, -15], [3]),
  ([1, 1, 1, -80, 242], [4]),
  ([0, -1, 1, -10, -20], [5]),
  ([1, 0, 1, -36, -70], [6]),
  ([1, -1, 1, -3, 3], [7]),
  ([1, 1, 1, 35, -28], [8]),
  ([1, -1, 1, -14, 29], [9]),
  ([1, 0, 0, -45, 81], [10]),
  ([1, -1, 1, -122, 1721], [12]),
  ([1, 1, 1, -135, -660], [2, 2]),
  ([1, 1, 1, -10, -10], [2, 4]),
  ([1, 0, 1, -19, 26], [2, 6]),
  ([1, 0, 0, -1070, 7812], [2, 8]),
  ]

  Rx, x = PolynomialRing(QQ, "x")
  K1, a = number_field(x^2 - x - 1)
  K2, a = number_field(x^2 +2)

  curves_to_test_tor_struc_nf =
   [(map(K1, [1, 1, 1, -3, 1]), [15]),
    (map(K2, [1, 0, 0, 115, 561]), [10, 2])
   ]

  @testset "Point order computation" begin
    E = EllipticCurve([0, -1, 1, -7820, -263580])
    @test 1 == @inferred order(infinity(E))

    E = EllipticCurve([1, 0, 1, -2731, -55146])
    @test 2 == @inferred order(E([fmpq(-121, 4), fmpq(117, 8)]))

    E = EllipticCurve([0, 1, 1, -9, -15])
    @test 3 == @inferred order(E([5, 9]))

    E = EllipticCurve([1, 1, 1, -80, 242])
    @test 4 == @inferred order(E([5, -2]))

    E = EllipticCurve([0, -1, 1, -10, -20])
    @test 5 == @inferred order(E([5, 5]))

    E = EllipticCurve([1, 0, 1, -36, -70])
    @test 6 == @inferred order(E([-4, 5]))

    E = EllipticCurve([1, -1, 1, -3, 3])
    @test 7 == @inferred order(E([1, 0]))

    E = EllipticCurve([1, 1, 1, 35, -28])
    @test 8 == @inferred order(E([2, 6]))

    E = EllipticCurve([1, -1, 1, -14, 29])
    @test 9 == @inferred order(E([-3, 7]))

    E = EllipticCurve([1, 0, 0, -45, 81])
    @test 10 == @inferred order(E([0, 9]))

    E = EllipticCurve([1, -1, 1, -122, 1721])
    @test 12 == @inferred order(E([-9, 49]))
  end

  @testset "Torsion test" begin
    E = EllipticCurve([1, -1, 1, -19353, 958713])
    @test @inferred is_torsion_point(E([103,172]))
    @test @inferred !is_torsion_point(E([-121,1292]))
  end

  @testset "Torsion points (Lutz-Nagell)" begin
    for c in curves_to_test_tor_short
      E = EllipticCurve(c[1])
      T = @inferred torsion_points_lutz_nagell(E)
      @test c[2] == length(T)
    end
  end

  @testset "Torsion points (division polynomials" begin
    for c in curves_to_test_tor_short
      E = EllipticCurve(c[1])
      T = @inferred torsion_points_division_poly(E)
      @test c[2] == length(T)
    end

    for c in curves_to_test_tor_struc
      E = EllipticCurve(c[1])
      T = @inferred torsion_points_division_poly(E)
      @test prod(c[2]) == length(T)
    end
  end

  @testset "Torsion points" begin
    for c in curves_to_test_tor_short
      E = EllipticCurve(c[1])
      T = @inferred torsion_points(E)
      @test c[2] == length(T)
      # test the caching
      T = @inferred torsion_points(E)
      @test c[2] == length(T)
    end

    for c in curves_to_test_tor_struc
      E = EllipticCurve(c[1])
      T = @inferred torsion_points(E)
      @test prod(c[2]) == length(T)
      # test the caching
      T = @inferred torsion_points(E)
      @test prod(c[2]) == length(T)
    end
  end

  @testset "Torsion point structure" begin
    for c in curves_to_test_tor_struc
      E = EllipticCurve(c[1])
      T = @inferred torsion_structure(E)
      @test T[1] == c[2]
      @test order(T[2][1]) == T[1][1]
      if length(T[1]) == 2
        @test order(T[2][2]) == T[1][2]
      end
      # test the caching
      T = @inferred torsion_structure(E)
      @test T[1] == c[2]
      @test order(T[2][1]) == T[1][1]
      if length(T[1]) == 2
        @test order(T[2][2]) == T[1][2]
      end
    end
  end

  @testset "Torsion point structure number fields" begin
    for c in curves_to_test_tor_struc_nf
      E = EllipticCurve(c[1])
      T = @inferred torsion_structure(E)
      @test T[1] == c[2]
      @test order(T[2][1]) == T[1][1]
      if length(T[1]) == 2
        @test order(T[2][2]) == T[1][2]
      end
      # test the caching
      T = @inferred torsion_structure(E)
      @test T[1] == c[2]
      @test order(T[2][1]) == T[1][1]
      if length(T[1]) == 2
        @test order(T[2][2]) == T[1][2]
      end
    end
  end

  @testset "Division polynomial" begin

    Kx, x = PolynomialRing(base_field(E43_a1), "x")
    Kxy, y = PolynomialRing(Kx, "y")
    f = @inferred division_polynomial(E43_a1, 4, x, y)
    @test  f == (4*x^6 + 8*x^5 + 20*x^3 + 20*x^2 + 8*x - 2)*y + 2*x^6 + 4*x^5 + 10*x^3 + 10*x^2 + 4*x - 1

  end
end


function backward_pass!(policy::PolicyData, problem::ProblemData;
    mode=:nominal)

    # horizon
    H = length(problem.states)

    # gradients
    fx = problem.model.jacobian_state
    fu = problem.model.jacobian_action
    gx = problem.objective.gradient_state
    gu = problem.objective.gradient_action

    # Hessians
    gxx = problem.objective.hessian_state_state
    guu = problem.objective.hessian_action_action
    gux = problem.objective.hessian_action_state

    # policy
    if mode == :nominal
        K = policy.K
        k = policy.k
    else
        K = policy.K_candidate
        k = policy.k_cand
    end

    # value function approximation
    P = policy.value.hessian
    p = policy.value.gradient

    # state-action value function approximation
    Qx = policy.action_value.gradient_state
    Qu = policy.action_value.gradient_action
    Qxx = policy.action_value.hessian_state_state
    Quu = policy.action_value.hessian_action_action
    Qux = policy.action_value.hessian_action_state

    # terminal value function
    P[H] .= gxx[H]
    p[H] .= gx[H]

    for t = H-1:-1:1
        # Qx[t] .= gx[t] + fx[t]' * p[t+1]
        mul!(Qx[t], transpose(fx[t]), p[t+1])
        Qx[t] .+= gx[t]

        # Qu[t] .= gu[t] + fu[t]' * p[t+1]
        mul!(Qu[t], transpose(fu[t]), p[t+1])
        Qu[t] .+= gu[t]

        # Qxx[t] .= gxx[t] + fx[t]' * P[t+1] * fx[t]
        mul!(policy.xx̂_tmp[t], transpose(fx[t]), P[t+1])
        mul!(Qxx[t], policy.xx̂_tmp[t], fx[t])
        Qxx[t] .+= gxx[t]

        # Quu[t] .= guu[t] + fu[t]' * P[t+1] * fu[t]
        mul!(policy.ux̂_tmp[t], transpose(fu[t]), P[t+1])
        mul!(Quu[t], policy.ux̂_tmp[t], fu[t])
        Quu[t] .+= guu[t]

        # Qux[t] .= gux[t] + fu[t]' * P[t+1] * fx[t]
        mul!(policy.ux̂_tmp[t], transpose(fu[t]), P[t+1])
        mul!(Qux[t], policy.ux̂_tmp[t], fx[t])
        Qux[t] .+= gux[t]

        # K[t] .= -1.0 * Quu[t] \ Qux[t]
        # k[t] .= -1.0 * Quu[t] \ Qu[t]
		policy.uu_tmp[t] .= Quu[t]
        LAPACK.potrf!('U', policy.uu_tmp[t])
        K[t] .= Qux[t]
        k[t] .= Qu[t]
        LAPACK.potrs!('U', policy.uu_tmp[t], K[t])
		LAPACK.potrs!('U', policy.uu_tmp[t], k[t])
		K[t] .*= -1.0
		k[t] .*= -1.0

        # P[t] .=  Qxx[t] + K[t]' * Quu[t] * K[t] + K[t]' * Qux[t] + Qux[t]' * K[t]
        # p[t] .=  Qx[t] + K[t]' * Quu[t] * k[t] + K[t]' * Qu[t] + Qux[t]' * k[t]
		mul!(policy.ux_tmp[t], Quu[t], K[t])

		mul!(P[t], transpose(K[t]), policy.ux_tmp[t])
		mul!(P[t], transpose(K[t]), Qux[t], 1.0, 1.0)
		mul!(P[t], transpose(Qux[t]), K[t], 1.0, 1.0)
		P[t] .+= Qxx[t]

		mul!(p[t], transpose(policy.ux_tmp[t]), k[t])
		mul!(p[t], transpose(K[t]), Qu[t], 1.0, 1.0)
		mul!(p[t], transpose(Qux[t]), k[t], 1.0, 1.0)
		p[t] .+= Qx[t]
    end
end


#########################################################################
#Forecast using random walk model, which is a special case of AR1 model
#with AR parameter set to be 1
#########################################################################
function rwm_oos_forc(J, h, h_lag, Y, Y_lag)
    ŷ = zeros(Float64, size(J - (h_lag + h):length(Y)))
    for (n, vec_î) in enumerate(J - (h_lag + h):length(Y))
        LHS = Y[1:(vec_î - h)]
        sample_size = length(LHS)
        LHS_lag = Y_lag[1:sample_size]
        ε̂ = mean(LHS - LHS_lag)
        ŷ[n] = Y_lag[vec_î] + ε̂
    end
    RMSE = sqrt(mean((Y[J - (h_lag + h):length(Y)] - ŷ) .^ 2))
    return ŷ, RMSE
end


  using Random, Plots, LaTeXStrings, ProgressMeter
let

  # initialization
  alpha    = collect(0.1:0.1:1.14)
  results = zeros(length(alpha));
  energy   = 0.
  energy2  = 0.
  mcs = 10000000    # monte-carlo samplings

@showprogress 1 "COmputing MC simulation"  for a in 1:length(alpha)

    for i in 0:mcs
      x = rand() / sqrt(2) / alpha[a]
      local_energy = (alpha[a])*(alpha[a]) + x*x*(1 - ((alpha[a])^4))
      energy  += local_energy
      energy2 += local_energy*local_energy
    end

    # mean energy
    energy = energy / mcs

    # standard deviation
    sd  = sqrt(((energy2 /mcs ) - (energy/mcs)^2))

    results[a] = energy

  end
  gr(dpi=600)
  plot(xlabel=L"\mathrm{\alpha}", ylabel=L"\mathrm{E_{0}}", framestyle=:box)
  plot!(xtickfontsize=14,ytickfontsize=14,xguidefontsize=14,yguidefontsize=14,legendfontsize=14)
  scatter!(alpha, results, label="MC simulations with N = $mcs")
  plot!(size(400,400))
  savefig("monte_carlo_sp.png")

  println(results[10])

end


using LinearAlgebra
@testset "error analyis" begin
    # Eval runerr
    # Same as Example 17 in manuscript
    graph = Compgraph(Float64);
    add_lincomb!(graph, :y, 1, :I, 1, :x)
    add_lincomb!(graph, :z, 1, :I, 1/2, :x)
    add_mult!(graph, :y2, :y, :y)
    add_mult!(graph, :z2, :z, :z)
    add_lincomb!(graph, :out, 1, :y2, -1, :z2)
    add_output!(graph, :out)
    x = 2. .^ -27
    running_error_bound = eval_runerr(graph, x, input = :x)
    @test running_error_bound < 3e-7;

    x = [1., 2.] .^ -27
    running_error_bound = eval_runerr(graph, x, input = :x)
    @test all(running_error_bound .< [2e-7, 3e-7])

    # compute theta
    (graph,_)=graph_ps([1.0,1.0, 1 / 2, 1 /6])
    q=compute_bnd_rel_bwd_err(:exp,graph,numterms=10,numdigits=30)
    @test q(0.1) < 1e-04

end


"""
    WignerHsize(m′ₘₐₓ, ℓₘₐₓ=-2)

Total size of array of wedges of width m′ₘₐₓ up to ℓₘₐₓ

Parameters
----------
ℓₘₐₓ : int
m′ₘₐₓ : int, optional
    If nothing, it is assumed to be at least ℓ

See Also
--------
WignerHrange : Array of (ℓ, m', m) indices corresponding to this wedge
WignerHindex : Index inside these wedges

Notes
-----
Here, it is assumed that only data with m≥|m'| are stored, and only
corresponding values are passed.  We also assume |m|≤ℓ and |m'|≤ℓ.  Neither
of these are checked.  The wedge array that this function indexes is ordered as

    [
        H(ℓ, m′, m) for ℓ in range(ℓₘₐₓ+1)
        for m′ in range(-min(ℓ, m′ₘₐₓ), min(ℓ, m′ₘₐₓ)+1)
        for m in range(abs(m′), ℓ+1)
    ]

"""
function WignerHsize(m′ₘₐₓ, ℓₘₐₓ=-2)
    if ℓₘₐₓ == -2
        ℓₘₐₓ = m′ₘₐₓ
    elseif ℓₘₐₓ < 0
        return 0
    end
    if m′ₘₐₓ === nothing || m′ₘₐₓ >= ℓₘₐₓ
        return (ℓₘₐₓ+1) * (ℓₘₐₓ+2) * (2*ℓₘₐₓ+3) ÷ 6
    else
        return ((ℓₘₐₓ+1) * (ℓₘₐₓ+2) * (2*ℓₘₐₓ+3) - 2*(ℓₘₐₓ-m′ₘₐₓ)*(ℓₘₐₓ-m′ₘₐₓ+1)*(ℓₘₐₓ-m′ₘₐₓ+2)) ÷ 6
    end
end

"""
    WignerHrange(m′ₘₐₓ, ℓₘₐₓ=-1)

Create an array of (ℓ, m', m) indices as in H array

Parameters
----------
ℓₘₐₓ : int
m′ₘₐₓ : int, optional
    If nothing, it is assumed to be at least ℓ

See Also
--------
WignerHsize : Total size of wedge array
WignerHindex : Index inside these wedges

Notes
-----
Here, it is assumed that only data with m≥|m'| are stored, and only
corresponding values are passed.  We also assume |m|≤ℓ and |m'|≤ℓ.  Neither of
these are checked.  The wedge array that this function indexes is ordered as

    [
        H(ℓ, m′, m) for ℓ in range(ℓₘₐₓ+1)
        for m′ in range(-min(ℓ, m′ₘₐₓ), min(ℓ, m′ₘₐₓ)+1)
        for m in range(abs(m′), ℓ+1)
    ]

"""
function WignerHrange(m′ₘₐₓ, ℓₘₐₓ=-1)
    if ℓₘₐₓ < 0
        ℓₘₐₓ = m′ₘₐₓ
    end
    r = zeros(typeof(m′ₘₐₓ), (WignerHsize(m′ₘₐₓ, ℓₘₐₓ), 3))
    i = 1
    for ℓ in 0:ℓₘₐₓ
        for m′ in -min(ℓ, m′ₘₐₓ):min(ℓ, m′ₘₐₓ)
            for m in abs(m′):ℓ
                r[i, 1] = ℓ
                r[i, 2] = m′
                r[i, 3] = m
                i += 1
            end
        end
    end
    return r
end

function _WignerHindex(ℓ, m′, m, m′ₘₐₓ)
    """Helper function for `WignerHindex`"""
    m′ₘₐₓ = min(m′ₘₐₓ, ℓ)
    i = WignerHsize(m′ₘₐₓ, ℓ-1)  # total size of everything with smaller ℓ
    if m′<1
        i += (m′ₘₐₓ + m′) * (2*ℓ - m′ₘₐₓ + m′ + 1) ÷ 2  # size of wedge to the left of m'
    else
        i += (m′ₘₐₓ + 1) * (2*ℓ - m′ₘₐₓ + 2) ÷ 2  # size of entire left half of wedge
        i += (m′ - 1) * (2*ℓ - m′ + 2) ÷ 2  # size of right half of wedge to the left of m'
    end
    i += m - abs(m′)  # size of column in wedge between m and |m'|
    return i + 1
end

"""
    WignerHindex(ℓ, m′, m, m′ₘₐₓ)

Index to "wedge" arrays

Parameters
----------
ℓ : int
m′ : int
m : int
m′ₘₐₓ : int, optional
    If nothing, it is assumed to be at least ℓ

See Also
--------
WignerHsize : Total size of wedge array
WignerHrange : Array of (ℓ, m', m) indices corresponding to this wedge

Notes
-----
Here, it is assumed that only data with m≥|m'| are stored, and only
corresponding values are passed.  We also assume |m|≤ℓ and |m'|≤ℓ.  Neither
of these are checked.  The wedge array that this function indexes is ordered as

    [
        H(ℓ, m′, m) for ℓ in range(ℓₘₐₓ+1)
        for m′ in range(-min(ℓ, m′ₘₐₓ), min(ℓ, m′ₘₐₓ)+1)
        for m in range(abs(m′), ℓ+1)
    ]

"""
function WignerHindex(ℓ, m′, m, m′ₘₐₓ=nothing)
    if ℓ == 0
        return 1
    end
    m′max = ℓ
    if m′ₘₐₓ !== nothing
        m′max = min(m′ₘₐₓ, m′max)
    end
    if m < -m′
        if m < m′
            return _WignerHindex(ℓ, -m′, -m, m′max)
        else
            return _WignerHindex(ℓ, -m, -m′, m′max)
        end
    else
        if m < m′
            return _WignerHindex(ℓ, m, m′, m′max)
        else
            return _WignerHindex(ℓ, m′, m, m′max)
        end
    end
end

"""
    WignerDsize(ℓₘᵢₙ, m′ₘₐₓ, ℓₘₐₓ=-1)

Compute total size of Wigner 𝔇 matrix

Parameters
----------
ℓₘᵢₙ : int
    Integer satisfying 0 <= ℓₘᵢₙ <= ℓₘₐₓ
m′ₘₐₓ : int, optional
    Integer satisfying 0 <= m′ₘₐₓ.  Defaults to ℓₘₐₓ.
ℓₘₐₓ : int
    Integer satisfying 0 <= ℓₘᵢₙ <= ℓₘₐₓ

Returns
-------
i : int
    Total size of Wigner 𝔇 matrix arranged as described below

See Also
--------
WignerDrange : Array of (ℓ, m', m) indices corresponding to the 𝔇 matrix
WignerDindex : Index of a particular element of the 𝔇 matrix

Notes
-----
This assumes that the Wigner 𝔇 matrix is arranged as

    [
        𝔇(ℓ, m′, m)
        for ℓ in range(ℓₘᵢₙ, ℓₘₐₓ+1)
        for m′ in range(-min(ℓ, m′ₘₐₓ), min(ℓ, m′ₘₐₓ)+1)
        for m in range(-ℓ, ℓ+1)
    ]

"""
function WignerDsize(ℓₘᵢₙ, m′ₘₐₓ, ℓₘₐₓ=-1)
    # from sympy import symbols, summation, horner
    # from sympy.printing.pycode import pycode
    # ℓ,m′,m,ℓₘᵢₙ,ℓₘₐₓ,m′ₘₐₓ = symbols('ℓ,m′,m,ℓₘᵢₙ,ℓₘₐₓ,m′ₘₐₓ', integer=True)
    #
    # def nice(expr)
    #     return horner(expr.expand().simplify(), (m′ₘₐₓ, ℓₘᵢₙ, ℓₘₐₓ))
    #
    # # Assuming ℓₘᵢₙ <= ℓₘₐₓ <= m′ₘₐₓ:
    # WignerDsize_ℓmin_ℓmax_m′max = horner(
    #     (
    #         # sum over all m′=0 elements
    #         summation(summation(summation(1, (m, -ℓ, ℓ)), (m′, 0, 0)), (ℓ, ℓₘᵢₙ, ℓₘₐₓ))
    #         # sum over all ℓₘᵢₙ <= ℓ <= ℓₘₐₓ elements
    #         + summation(summation(summation(2, (m, -ℓ, ℓ)), (m′, 1, ℓ)), (ℓ, ℓₘᵢₙ, ℓₘₐₓ))
    #     ).expand().simplify(),
    #     (ℓₘᵢₙ, ℓₘₐₓ)
    # )
    # print(f"({pycode(nice(3*WignerDsize_ℓmin_ℓmax_m′max.subs(ℓₘₐₓ, ℓ-1)))}) ÷ 3")
    #
    # # Assuming ℓₘᵢₙ <= m′ₘₐₓ <= ℓₘₐₓ:
    # WignerDsize_ℓmin_m′max_ℓmax = horner(
    #     (
    #         # sum over all m′=0 elements
    #         summation(summation(summation(1, (m, -ℓ, ℓ)), (m′, 0, 0)), (ℓ, ℓₘᵢₙ, ℓₘₐₓ))
    #         # sum over all ℓ <= m′ₘₐₓ elements
    #         + summation(summation(summation(2, (m, -ℓ, ℓ)), (m′, 1, ℓ)), (ℓ, ℓₘᵢₙ, m′ₘₐₓ))
    #         # sum over all ℓ >= m′ₘₐₓ elements
    #         + summation(summation(summation(2, (m, -ℓ, ℓ)), (m′, 1, m′ₘₐₓ)), (ℓ, m′ₘₐₓ+1, ℓₘₐₓ))
    #     ).expand().simplify(),
    #     (m′ₘₐₓ, ℓₘᵢₙ, ℓₘₐₓ)
    # )
    # print(f"({pycode(nice(3*WignerDsize_ℓmin_m′max_ℓmax.subs(ℓₘₐₓ, ℓ-1)))}) ÷ 3")
    #
    # # Assuming m′ₘₐₓ <= ℓₘᵢₙ <= ℓₘₐₓ:
    # WignerDsize_m′max_ℓmin_ℓmax = horner(
    #     (
    #         # sum over all m′=0 elements
    #         summation(summation(summation(1, (m, -ℓ, ℓ)), (m′, 0, 0)), (ℓ, ℓₘᵢₙ, ℓₘₐₓ))
    #         # sum over all remaining |m′| <= m′ₘₐₓ elements
    #         + summation(summation(summation(2, (m, -ℓ, ℓ)), (m′, 1, m′ₘₐₓ)), (ℓ, ℓₘᵢₙ, ℓₘₐₓ))
    #     ).expand().simplify(),
    #     (m′ₘₐₓ, ℓₘᵢₙ, ℓₘₐₓ)
    # )
    # print(f"{pycode(nice(WignerDsize_m′max_ℓmin_ℓmax.subs(ℓₘₐₓ, ℓ-1)).factor())}")
    if ℓₘₐₓ < 0
        ℓₘₐₓ = m′ₘₐₓ
    end
    if m′ₘₐₓ >= ℓₘₐₓ
        return (
            ℓₘₐₓ * (ℓₘₐₓ * (4 * ℓₘₐₓ + 12) + 11)
            + ℓₘᵢₙ * (1 - 4 * ℓₘᵢₙ^2)
            + 3
        ) ÷ 3
    end
    if m′ₘₐₓ > ℓₘᵢₙ
        return (
            3 * ℓₘₐₓ * (ℓₘₐₓ + 2)
            + ℓₘᵢₙ * (1 - 4 * ℓₘᵢₙ^2)
            + m′ₘₐₓ * (
                3 * ℓₘₐₓ * (2 * ℓₘₐₓ + 4)
                + m′ₘₐₓ * (-2 * m′ₘₐₓ - 3) + 5
            )
            + 3
        ) ÷ 3
    else
        return (ℓₘₐₓ * (ℓₘₐₓ + 2) - ℓₘᵢₙ^2) * (1 + 2 * m′ₘₐₓ) + 2 * m′ₘₐₓ + 1
    end
end

"""
    WignerDrange(ℓₘᵢₙ, m′ₘₐₓ, ℓₘₐₓ=-1)

Create an array of (ℓ, m', m) indices as in 𝔇 array

Parameters
----------
ℓₘᵢₙ : int
    Integer satisfying 0 <= ℓₘᵢₙ <= ℓₘₐₓ
m′ₘₐₓ : int, optional
    Integer satisfying 0 <= m′ₘₐₓ.  Default is ℓₘₐₓ.
ℓₘₐₓ : int
    Integer satisfying 0 <= ℓₘᵢₙ <= ℓₘₐₓ

See Also
--------
WignerDsize : Total size of 𝔇 array
WignerDindex : Index inside these wedges

Notes
-----
This assumes that the Wigner 𝔇 matrix is arranged as

    [
        𝔇(ℓ, m′, m)
        for ℓ in range(ℓₘᵢₙ, ℓₘₐₓ+1)
        for m′ in range(-min(ℓ, m′ₘₐₓ), min(ℓ, m′ₘₐₓ)+1)
        for m in range(-ℓ, ℓ+1)
    ]

"""
function WignerDrange(ℓₘᵢₙ, m′ₘₐₓ, ℓₘₐₓ=-1)
    if ℓₘₐₓ < 0
        ℓₘₐₓ = m′ₘₐₓ
    end
    r = zeros(typeof(ℓₘᵢₙ), (WignerDsize(ℓₘᵢₙ, m′ₘₐₓ, ℓₘₐₓ), 3))
    i = 1
    for ℓ in ℓₘᵢₙ:ℓₘₐₓ
        for m′ in -min(ℓ, m′ₘₐₓ):min(ℓ, m′ₘₐₓ)
            for m in -ℓ:ℓ
                r[i, 1] = ℓ
                r[i, 2] = m′
                r[i, 3] = m
                i += 1
            end
        end
    end
    return r
end

"""
    WignerDindex(ℓ, m′, m, ℓₘᵢₙ=0, m′ₘₐₓ=-1)

Compute index into Wigner 𝔇 matrix

Parameters
----------
ℓ : int
    Integer satisfying ℓₘᵢₙ <= ℓ <= ℓₘₐₓ
m′ : int
    Integer satisfying -min(ℓₘₐₓ, m′ₘₐₓ) <= m′ <= min(ℓₘₐₓ, m′ₘₐₓ)
m : int
    Integer satisfying -ℓ <= m <= ℓ
ℓₘᵢₙ : int, optional
    Integer satisfying 0 <= ℓₘᵢₙ <= ℓₘₐₓ.  Defaults to 0.
m′ₘₐₓ : int, optional
    Integer satisfying 0 <= m′ₘₐₓ.  Defaults to ℓ.

Returns
-------
i : int
    Index into Wigner 𝔇 matrix arranged as described below

See Also
--------
WignerDsize : Total size of the 𝔇 matrix
WignerDrange : Array of (ℓ, m', m) indices corresponding to the 𝔇 matrix

Notes
-----
This assumes that the Wigner 𝔇 matrix is arranged as

    [
        𝔇(ℓ, m′, m)
        for ℓ in range(ℓₘᵢₙ, ℓₘₐₓ+1)
        for m′ in range(-min(ℓ, m′ₘₐₓ), min(ℓ, m′ₘₐₓ)+1)
        for m in range(-ℓ, ℓ+1)
    ]

"""
function WignerDindex(ℓ, m′, m, ℓₘᵢₙ=0, m′ₘₐₓ=-1)
    if m′ₘₐₓ < 0
        m′ₘₐₓ = ℓ
    end
    i = (m′ + min(m′ₘₐₓ, ℓ)) * (2 * ℓ + 1) + m + ℓ
    if ℓ > ℓₘᵢₙ
        i += WignerDsize(ℓₘᵢₙ, m′ₘₐₓ, ℓ-1)
    end
    return i + 1
end


function RecWalk(TrainSet, ItemModel, α=0.01)
    n,m = size(TrainSet)
    Muu = speye(n)
    Mii = RowStochastic(ItemModel,"dmax")
    Hui = RowStochastic(TrainSet)
    Hiu = RowStochastic(TrainSet')
    H = vcat(hcat(spzeros(n,n),Hui), hcat(Hiu,spzeros(m,m)))
    M = vcat(hcat(Muu,spzeros(n,m)), hcat(spzeros(m,n),Mii))
    P = α*H+(1-α)*M
    return P
end

function RowStochastic(A, strategy="standard")
    if strategy == "dmax"
        row_sums = sum(A, 2)
        dmax = maximum(row_sums)
        A_temp = 1 / dmax * A
        return (I - spdiagm(vec(sum(A_temp, 2)))) + A_temp
    else
        row_sums = sum(A, 2)
        row_sums[sum(A, 2) .== 0] = 1 # replacing the zero row sums with 1
        return spdiagm(vec(1 ./ row_sums)) * A
    end

end

function readItemModel(filename, m)
    A = readdlm(filename, skipblanks = false)
    A[A .== ""] = 0; A = Array{Float64}(A)
    s1, s2 = size(A)
    rows = []; cols = []; vals = Float64[];
    for i = 1:s1
        items = Array{Int64}(A[i,1:2:s2 - 1])
        indx = find(items .> 0)
        items = items[indx]
        scores =  A[i,2 * indx]
        append!(rows, i * ones(length(items)))
        append!(cols, items)
        append!(vals, scores)
    end
    if maximum(rows) < m || maximum(cols) < m
        append!(rows, [m])
        append!(cols, [m])
        append!(vals, [0])
    end
    return sparse(rows, cols, vals)
end

@everywhere function Single_HR_RR_NDCG(Π, T, K)
    target = Π[T[1]]
    pos = sum(Π[T].>=target)
    if 1 <= pos <= K
            HR = 1; RR = 1 / pos; NDCG = 1/log2(pos+1)
    else
            HR = 0; RR = 0; NDCG = 0
    end
    return HR, RR, NDCG
end


import GenDirectionalStats: vmf_rot3
import Rotations

struct GaussianVMF <: Gen.Distribution{Pose} end
const gaussianVMF = GaussianVMF()
function Gen.random(
    ::GaussianVMF,
    center::Pose,
    positionStdev::Real,
    orientationKappa::Real,
)::Pose
    rot = Gen.random(vmf_rot3, Rotations.UnitQuaternion(1.0, 0.0, 0.0, 0.0), orientationKappa)
    offset = Pose(
        [
            Gen.normal(0, positionStdev),
            Gen.normal(0, positionStdev),
            Gen.normal(0, positionStdev),
        ],
        rot,
    )

    Pose(center.pos + offset.pos, center.orientation * offset.orientation)
end

function Gen.logpdf(
    ::GaussianVMF,
    val::Pose,
    center::Pose,
    positionStdev::Real,
    orientationKappa::Real,
)
    offset = Pose(center.pos - val.pos, center.orientation \ val.orientation)
    return +(
        Gen.logpdf(Gen.normal, offset.pos[1], 0, positionStdev),
        Gen.logpdf(Gen.normal, offset.pos[2], 0, positionStdev),
        Gen.logpdf(Gen.normal, offset.pos[3], 0, positionStdev),
        Gen.logpdf(
            vmf_rot3,
            Rotations.UnitQuaternion(offset.orientation),
            Rotations.UnitQuaternion(1.0, 0.0, 0.0, 0.0),
            orientationKappa,
        ),
    )
end
Gen.has_output_grad(::GaussianVMF) = false
Gen.has_argument_grads(::GaussianVMF) = (false, false, false)
function Gen.logpdf_grad(
    ::GaussianVMF,
    center::Pose,
    positionStdev::Real,
    orientationKappa::Real,
)
    (nothing, nothing, nothing, nothing)
end

export gaussianVMF


# This file is a part of BAT.jl, licensed under the MIT License (MIT).

struct HistogramAsUvDistribution{T <: AbstractFloat} <: ContinuousUnivariateDistribution
    h::Histogram{<:Real, 1}
    inv_weights::Vector{T}
    edges::Vector{T}
    volumes::Vector{T}

    probabilty_edges::Vector{T}
    probabilty_volumes::Vector{T}
    probabilty_inv_volumes::Vector{T}

    acc_prob::Vector{T}

    μ::T
    var::T
    cov::Matrix{T}
    σ::T
end

function HistogramAsUvDistribution(h::Histogram{<:Real, 1}, T::DataType = Float64)
    nh = normalize(h)
    probabilty_widths::Vector{T} = h.weights * inv(sum(h.weights))
    probabilty_edges::Vector{T} = Vector{Float64}(undef, length(probabilty_widths) + 1)
    probabilty_edges[1] = 0
    @inbounds for (i, w) in enumerate(probabilty_widths)
        probabilty_edges[i+1] = probabilty_edges[i] + probabilty_widths[i]
    end
    probabilty_edges[end] = 1
    volumes = diff(h.edges[1])
    mean = Statistics.mean(StatsBase.midpoints(nh.edges[1]), ProbabilityWeights(nh.weights))
    var = Statistics.var(StatsBase.midpoints(nh.edges[1]), ProbabilityWeights(nh.weights), mean = mean)

    acc_prob::Vector{T} = zeros(T, length(nh.weights))
    for i in 2:length(acc_prob)
        acc_prob[i] += acc_prob[i-1] + nh.weights[i-1] * volumes[i-1]
    end

    d::HistogramAsUvDistribution{T} = HistogramAsUvDistribution{T}(
        nh,
        inv.(nh.weights),
        nh.edges[1],
        volumes,
        probabilty_edges,
        probabilty_widths,
        inv.(probabilty_widths),
        acc_prob,
        mean,
        var,
        fill(var, 1, 1),
        sqrt(var)
    )
end

function Base.rand(rng::AbstractRNG, d::HistogramAsUvDistribution{T})::T where {T <: AbstractFloat}
    r::T = rand()
    next_inds::UnitRange{Int} = searchsorted(d.probabilty_edges, r)
    next_ind_l::Int = next_inds.start
    next_ind_r::Int = next_inds.stop
    if next_ind_l > next_ind_r
        next_ind_l = next_inds.stop
        next_ind_r = next_inds.start
    end
    ret::T = d.edges[next_ind_l]
    if next_ind_l < next_ind_r
        ret += d.volumes[next_ind_l] * (d.probabilty_edges[next_ind_r] - r) * d.probabilty_inv_volumes[next_ind_l]
    end
    return ret
end

function Distributions.pdf(d::HistogramAsUvDistribution{T}, x::Real)::T where {T <: AbstractFloat}
    i::Int = StatsBase.binindex(d.h, x)
    return @inbounds d.h.weights[i]
end

function Distributions.logpdf(d::HistogramAsUvDistribution{T}, x::Real)::T where {T <: AbstractFloat}
    return log(pdf(d, x))
end

function Distributions.cdf(d::HistogramAsUvDistribution{T}, x::Real)::T where {T <: AbstractFloat}
    i::Int = StatsBase.binindex(d.h, x)
    p::T = @inbounds sum(d.h.weights[1:i-1] .* d.volumes[1:i-1])
    p += (x - d.edges[i]) * d.h.weights[i]
    return p
end

function Distributions.minimum(d::HistogramAsUvDistribution{T})::T where {T <: AbstractFloat}
    d.edges[1]
end

function Distributions.maximum(d::HistogramAsUvDistribution{T})::T where {T <: AbstractFloat}
    d.edges[end]
end

function Distributions.insupport(d::HistogramAsUvDistribution{T}, x::Real)::Bool where {T <: AbstractFloat}
    d.edges[1] <= x <= d.edges[end]
end

function Distributions.quantile(d::HistogramAsUvDistribution{T}, x::Real)::T where {T <: AbstractFloat}
    r::UnitRange{Int} = searchsorted(d.acc_prob, x)
    idx::Int = min(r.start, r.stop)
    p::T = d.acc_prob[ idx ]
    q::T = d.edges[idx]
    missing_p::T = x - p
    inv_weight::T = d.inv_weights[idx]
    if !isinf(inv_weight)
        q += missing_p * inv_weight
    end
    return min(q, maximum(d))
end

Base.eltype(d::HistogramAsUvDistribution{T}) where {T <: AbstractFloat}= T

_np_bounds(d::HistogramAsUvDistribution) =
    HyperRectBounds(Vector{eltype(d)}([quantile(d, 0)]), Vector{eltype(d)}([quantile(d, 1)]), hard_bounds)

Statistics.mean(d::HistogramAsUvDistribution) = d.μ
Statistics.var(d::HistogramAsUvDistribution) = d.var
Statistics.cov(d::HistogramAsUvDistribution) = d.cov


### A Pluto.jl notebook ###
# v0.17.3

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el)
        el
    end
end

# ╔═╡ 6c692a11-cb4c-425d-9acb-9529bd382dd2
begin
    import Pkg
	Pkg.activate(Base.current_project())

	using FringeExplorer, Latexify, RadioInterferometry, Symbolics, Plots, PlotlyJS, LaTeXStrings, PlutoUI
	Latexify.set_default(fmt=FancyNumberFormatter(4, "{\\cdot}"))
	#Latexify.set_default(fmt=FancyNumberFormatter("%.1g",
	#	SubstitutionString{String}("\\g<mantissa> {\\\\cdot} 10^{\\g<exp>}")))
	#LaTeXString(numformat(latexify(FringeExplorer.wdw_m)))
	md"""
	# Fringe Explorer
	This notebook is for exploring the relative geometric delays, and their rates of change, as seen by antennas in a terrestrial radio telescope array as it tracks a sidereal source moving across the sky.

	Of course we now know that the ground based antennas are doing (most of) the moving thanks to Earth's rotation, but as observers who live in the same reference frame as the antennas it can sometimes be convenient to think in terms of the sky moving instead.
	"""
end

# ╔═╡ 655091ad-69a0-4672-abb4-37ee03b27cff
md"""
## Getting Oriented
### XYZ frame
For the purposes of this discussion we will represent antenna positions, and the array's reference position, in a right-handed XYZ frame where the Z axis is parallel to the rotational axis of Earth and the XY plane parallel to the equarorial plane of Earth.  One such frame commonly used for antenna positions is the *International Terrestrial Reference Frame* (ITRF), a geocentric frame where Z is the rotational axis of Earth, X is in the equatorial lane and passes through the prime meridian (i.e. longitude 0), and Y completes the right hand frame (i.e. passing though longitude 90 degrees).  The frame used here is a modified version of this frame where the origin has been translated to the array reference position and the frame has been rotated around the Z axis such that X points to the longitude of the array reference position.
"""

# ╔═╡ 7ff31ce8-190e-4018-ba49-c2f11ed82801
md"""
### Hour Angle and Declination frame
We will also choose to represent sidereal positions as an *hour angle*, represented by the variable ``h``, and *declination* angle, represented by the variable ``\delta``.  Being angles, we will store and calculate with them in radians, but often display them as degrees.  This choice of representation is convenient because for a given sidereal position, ``h`` is a linear function of time and ``\delta`` is a constant.  Positions on the local meridian have ``h = 0``, positions to the west have ``h > 0``.  Using ``\Omega`` to represent rate of rotation, ``h`` for a given sideral source can be expressed as ``h = \Omega t``, with ``t = 0`` corresponding to the time when the source is *transiting*, i.e. on/crossing the local meridian.
"""

# ╔═╡ 423f2ef7-932d-4eb8-8ea6-304e2b7cbf2e
md"""
### UVW frame
With these conventions established, we can now discuss how to compute the relative geometric delays between wavefront arrival times at a given antenna and the array reference position.  One way to do this is to rotate the XYZ frame we are using for antenna positions into a new frame known as the UVW frame (also right handed).  The U axis of the UVW frame points eastward, the V axis points northward, and the W axis points at the source.  Because the W axis points to the source that is moving, the UVW frame is not a fixed frame, but a rotating frame.  When representing the antenna positions in the UVW frame, the W component of this position vector represents to *line of sight* distance between the antenna and the reference position as seen from the source position.  In other words, the RF wavefront arrives at one end of the vector first, then travels a distance W before arriving at the other end of the vector.  For our purposes here we will ignore the amount of Earth rotation that happens during this time.  The XYZ coordinates of the antennas are typically specified in meters, but speed of light can be used to convert W from meters to seconds, or more typically nanoseconds, of delay which can be used along with observing frequency to convert W to units of wavelength.
"""

# ╔═╡ c02e8687-be77-4af0-a0a1-6901ad02f85e
md"""
Rotating antenna position vectors from the XYZ frame to the UVW frame can be accomplished by rotating our XYZ frame about the Z (i.e. third) axis by ``h``, producing a (X',U,Z) frame, then rotating that frame about the U (i.e. second) axis by ``\delta``, producing a (W,U,V) frame which can then permuted to (U,V,W) where U is east, V is north, and W is in the direction of ``h`` and ``\delta``.  This operation can be performed by left multiplying the antenna position vector by a 3x3 rotation matrix.

The `RadioInterferometry.jl` package provides a `xyz2uvw` function that can generate these 3x3 rotation matrices for any given ``h`` and ``\delta``.

!!! tip
    The `xyz2uvw` function supports XYZ frames that, unlike ours, are oriented with X parallel to the great circle containing the prime meridian (e.g. the ITRF XYZ frame).  This involves adjusting ``h`` by the logitude of the array's reference position, but since our X axis is already oriented to the local meridian we can simply pass 0 for longitude.

The rotation from XYZ to UVW for a given hour angle ``h`` and declination ``\delta`` can be expressed as:
"""

# ╔═╡ 0c19d2b3-3869-48f1-b2eb-1ff157e931cf
begin
	@variables x y z u v w h δ
	([u,v,w] ~ :($(xyz2uvw(h, δ, 0)) * $([x,y,z])))|>latexify
end

# ╔═╡ 11567a37-55b2-4d9e-88fb-9efdf7ada485
md"""
## Play time!

Change the sliders here to select between hour angle (in minutes) and declination (in degrees).  The values you selected are shown below followed by the XYZ to UVW transformation for the given parameters.

$(@bind h0 Slider(-360:5:360, default=0)) Hour angle (minutes)

$(@bind δ0 Slider(-90:5:90, default=0)) Declination (degrees)
"""

# ╔═╡ 1ceb4ee8-2bbd-45a7-b5a4-886c949c17e3
(h=h0, δ=δ0)

# ╔═╡ aee119f3-b190-4342-9d48-c0ee9f8e2859
begin
	([u,v,w] ~ :($(xyz2uvw(deg2rad(h0/4), deg2rad(δ0), 0)) * $([x,y,z])))|>latexify
end

# ╔═╡ 979c1962-db9a-4581-95a8-15b183afd990

# ╔═╡ d4145813-5e18-4230-8389-d8935d72ba4b
FringeExplorer.wdw_m

# ╔═╡ Cell order:
# ╟─6c692a11-cb4c-425d-9acb-9529bd382dd2
# ╟─655091ad-69a0-4672-abb4-37ee03b27cff
# ╟─7ff31ce8-190e-4018-ba49-c2f11ed82801
# ╟─423f2ef7-932d-4eb8-8ea6-304e2b7cbf2e
# ╟─c02e8687-be77-4af0-a0a1-6901ad02f85e
# ╟─0c19d2b3-3869-48f1-b2eb-1ff157e931cf
# ╟─11567a37-55b2-4d9e-88fb-9efdf7ada485
# ╟─1ceb4ee8-2bbd-45a7-b5a4-886c949c17e3
# ╟─aee119f3-b190-4342-9d48-c0ee9f8e2859
# ╠═979c1962-db9a-4581-95a8-15b183afd990
# ╠═d4145813-5e18-4230-8389-d8935d72ba4b


module CalibrationErrorsDistributions

using Reexport

@reexport using CalibrationErrors
@reexport using Distributions
@reexport using KernelFunctions

using Distances: Distances, Euclidean, SqEuclidean
using ExactOptimalTransport: ExactOptimalTransport
using PDMats: PDMats
using Tulip: Tulip

using LinearAlgebra: LinearAlgebra

const OT = ExactOptimalTransport

include("distances/types.jl")
include("distances/wasserstein.jl")

include("normal.jl")
include("laplace.jl")
include("mvnormal.jl")
include("mixturemodel.jl")

include("deprecated.jl")

end


@testset "807.max-increase-to-keep-city-skyline.jl" begin
    @test max_increase_keeping_skyline(hcat([[3,0,8,4],[2,4,5,7],[9,2,6,3],[0,3,1,0]]...)) == 35
end


"""PART 7: ARITHMETIC WITH SCALED DIMENSIONS.
S99x109_P38x41
"""
module FixPoint

#= export =#
export UNITY, TWO, INF_BAD
export half, round_decimals, mult_and_add, x_over_n, xn_over_d, badness, badness, print_scaled

#= constant =#
"[#101] `2^{16}`, rep 1.00000"
const UNITY =   65536
"[#101] `2^{17}, rep 2.00000"
const TWO =     131072

"[#108]: infinitely bad value"
const INF_BAD = 10000

#= global var =#
"[#104] has arithmetic overflow occurred recently?"
arith_error = false
"[#104] amount subtracted to get an exact division"
tex_remainder = 0

#= functions =#
"""[#105] 455, 716, 1240.
Note: `07777777777L == 1073741823L`
"""
nx_plus_y(n, x, y) = mult_and_add(n, x, y, 0o77_7777_7777)

"""[#105] 1240.
Note: `017777777777L == 2147483647L`
"""
mult_integers(n, x) = mult_and_add(n, x, 0, 0o177_7777_7777)

"""[#100] x div 2

div(x, 2, RoundNearestTiesUp)
"""
function half(x::Integer)
    if isodd(x)
        (x + 1) / 2
    else
        x / 2
    end
end

"""[#102]: create a scaled integer from a given decimal fraction.
{ dig[i], | i = k-1, 0 ≤ k ≤ 17 }
"""
function round_decimals(k, dig) end

"[#103]: prints scaled real, rounded to five digits"
function print_scaled(s) end

"[#105]: return `nx + y`"
function mult_and_add(n, x, y, max_ans) end

"[#106]: `x / n`"
function x_over_n(x, n) end

"[#107]: `x * (n / d)`"
function xn_over_d(x, n, d) end

"""[#108]: compute the “badness” of glue.
given t >= 0
when a total t is supposed to be made from amounts that sum to s.

+ badness = 100 * (t/s)^3
+ badness(t + 1,s) ≥ badness(t,s) ≥ badness(t,s + 1)
"""
function badness(t, s) end

end # FixPoint


let
    order = 2

    # Haverly1 standard pooling instance
    x14, x24, x35, x36, x45, x46, w4 = variables(["x_{14}"; "x_{24}"; "x_{35}"; "x_{36}"; "x_{45}"; "x_{46}"; "w_4"])

    c14, c24, c35, c36, c45, c46 = [6; 16; 1; -9; -5; -15]
    q1, q2, q3, q5, q6 = [ 3; 1; 2; 5//2; 3//2 ]
    ub1, ub2, ub3, ub4, ub5, ub6 = [ 300; 300; 300; 300; 100; 200 ] // 300

    flow_eq    = [ x14+x24-(x45+x46) ]
    blend_eq   = [ w4*(x45+x46)-(q1*x14+q2*x24) ]
    qual_bnd   = [ q5*(x45+x36)-(w4*x45+q3*x35); q6*(x46+x36)-(w4*x46+q3*x36) ]
    cap_bnd    = [ ub1-x14; ub2-x24; ub3-(x35+x36); ub4-(x45+x46); ub5-(x35+x45); ub6-(x36+x46) ]
    flow_bnd   = [ x14; x24; x35; x36; x45; x46 ]

    obj = dot([c14; c24; c35; c36; c45; c46], [x14; x24; x35; x36; x45; x46])
    prob = momentprob(order, obj, [qual_bnd; cap_bnd; flow_bnd], [flow_eq; blend_eq])

    X, Z, t, y, solsta = solve_mosek(prob)
    xo = Polyopt.vectorize([x14,x24,x35,x36,x45,x46,w4],2*order)*y

    @test norm(xo-[0; 1/3; 0; 1/3; 0; 1/3; 1]) < 1e-4

    probc = momentprob_chordalembedding(order, obj, [qual_bnd; cap_bnd; flow_bnd], [flow_eq; blend_eq])
    Xc, Zc, tc, yc, solstac = solve_mosek(probc)
    xo = Polyopt.vectorize([x14,x24,x35,x36,x45,x46,w4],2*order)*yc

    @test norm(xo-[0; 1/3; 0; 1/3; 0; 1/3; 1]) < 1e-4
end


const T = Float32

@testset "functions" for i = 1:5

@testset "activation" begin
    x = parameter(randn(T,10,5))
    for i = 1:length(x.data)
        abs(x.data[i]) < 0.1 && (x.data[i] += 1)
    end
    for f in (relu,sigmoid,tanh)
        checkgrad(()->f(x), x)
    end
end

@testset "cnn" begin
    x = parameter(randn(T,10,10))
    f = Conv1d(T, 5, 10, 7, padding=2)
    checkgrad(()->f(x,[3,7]), x, f.W, f.b)
end

@testset "loss" begin
    @testset "softmax_crossentropy" begin
        p = Var(rand(1:10,5))
        q = parameter(rand(T,10,5))
        checkgrad(()->softmax_crossentropy(p,q), p, q)
    end

    @testset "l2" begin
        x = parameter(rand(T,10,5))
        # @test_grad l2(x,0.01) x
    end

    @testset "mse" begin
        x1 = parameter(rand(T,10,5))
        x2 = parameter(rand(T,10,5))
        #@test_grad mse(x1,x2) x1 x2
    end
end

@testset "math" begin
    x = parameter(rand(T,10,5) .+ T(1))
    #@test_function exp x
    #@test_function log x

    x1 = parameter(randn(T,10,5))
    x2 = parameter(randn(T,10,5))
    checkgrad(()->x1+x2, x1, x2)
    checkgrad(()->x1-x2, x1, x2)
    checkgrad(()->x1.*x2, x1, x2)

    A = parameter(randn(T,10,5))
    B = parameter(randn(T,5,7))
    checkgrad(()->A*B, A, B)

    x1 = parameter(randn(T,10,5))
    x2 = parameter(randn(T,10,1))
    checkgrad(()->x1.+x2, x1, x2)
    checkgrad(()->x1.-x2, x1, x2)
    checkgrad(()->x1.*x2, x1, x2)
end

@testset "reduction" begin
    x = parameter(randn(T,10,10)*T(10))
    @testset "max" begin
        for dim = 1:2
            checkgrad(()->max(x,dim), x)
        end
        checkgrad(()->max(x,[2,5,3]), x)
    end
end

@testset "rnn" begin
    x = parameter(randn(T,10,10))
    for nlayers = 1:1
        f = LSTM(T, 10, 5, nlayers, 0.0, true)
        checkgrad(()->f(x,[5,3,2]), x, f.weights...)
    end
end

@testset "blas" begin
    A = parameter(randn(T,10,5))
    x = parameter(randn(T,10))
    checkgrad(()->gemv('T',1,A,x), A, x)

    A = parameter(randn(T,10,5))
    B = parameter(randn(T,10,5))
    checkgrad(()->gemm('N','T',1,A,B), A, B)

    A = parameter(randn(T,10,5,7))
    B = parameter(randn(T,10,5,7))
    #test_gradient(gemm_batch, 'N', 'T', 1, A, B)
    #test_cuda(gemm_batch, 'N', 'T', 1, A, B)
end

@testset "concat" begin
    x1 = parameter(randn(T,10,5,2))
    x2 = parameter(randn(T,10,5,2))
    x3 = parameter(randn(T,10,5,2))
    for dim = 1:3
        checkgrad(()->concat(dim,x1,x2,x3), x1, x2, x3)
    end
end

@testset "dropout" begin
    x = parameter(randn(T,10,5))
    dropout(x, 0.5)
end

@testset "getindex" begin
    x = parameter(randn(T,10,5,4))
    checkgrad(()->x[2:7,:,1:3], x)
    x = parameter(randn(T,10,5))
    checkgrad(()->x[:,3:3], x)
end

@testset "linear" begin
    x = parameter(randn(T,10,5))
    f = Linear(T, 10, 7, init_b=Uniform(-1,1))
    checkgrad(()->f(x), x, f.W, f.b)
end

@testset "lookup" begin
    w = parameter(randn(T,10,15))
    x = Var(rand(1:15,10))
    checkgrad(()->lookup(w,x), w, x)
end

@testset "pack" begin
    x = parameter(randn(T,10,10))
    dims = [2, 5, 3]
    checkgrad(()->pack(x,[2,5,3],0), x)
end

@testset "reshape" begin
    x = parameter(randn(T,10,1,5))
    checkgrad(()->reshape(x,5,10), x)
    checkgrad(()->vec(x), x)
    checkgrad(()->dropdims(x), x)
end

@testset "softmax" begin
    x = parameter(rand(T,10,5))
    checkgrad(()->softmax(x), x)
    logsoftmax(x)
end

@testset "window1d" begin
    x = parameter(randn(T,10,10))
    ksize, padding, stride, dilation = 5, 2, 1, 1
    checkgrad(()->window1d(x,[3,7],ksize,padding,stride,dilation), x)
end

end


atol = 1e-5
rtol = 0.0
maxit = 100

println("=====================")
println("  Performance Tests ")
println("=====================")
for (f, df, init, _, name) in tests
   println("-------------------")
   println("  $name Test")
   println("-------------------")
   x, it_hist, ierr, x_hist = nsoli(init(), f,
                                 atol = atol, rtol = rtol, maxit = maxit)
   # improve the solution a bit
   y = copy(x)
   for i = 1:3
      y -= df(y) \ f(y)
   end
   println("     Error Message: ", ierr)
   println("      # iterations: ", length(x_hist) )
   println("          Residual: ", norm(f(x), Inf))
   println("     Residual xref: ", norm(f(y), Inf))
   println("  Error |x - xref|: ", norm(x - y, Inf))
end


# julia1_4.1.jl  Basic Stream I/O
#
# well depth example with file i/o
# constants
using Printf
g= 9.807 # acceleration due to gravity m/s^2
vs= 343.21 # velocity of sound in air m/s 20˚C
# (depth,tv)=well(t)  judging well depth using a rock
function well(t)
  tdn = (-vs + sqrt(vs^2 + 2.0*g*vs*t))/g
  tup = t - tdn
  depth = vs*tup; termvel=g*tdn
  return (depth,termvel)
end
t=5.0       # seconds from drop to ear
(d,tv)=well(t)
println(t," ",d,"  ",tv)
#rounding results
rd=(round(d*10))/10.0
rtv=(round(tv*10))/10.0
println("depth $rd meters")
println("terminal velocity $rtv meters/second")
# could also use @printf() macro for C printf
@printf("depth %6.1f meters",d)
@printf("%6.1f,%6.1f,%6.1f\n",t,d,tv)
#
###
#
win=open("wellin.dat","r")
wout=open("wellout.csv","w")
@printf(wout,"   Time  Depth Speed  Place\n")
@printf(wout,"   sec     M    M/s   CA/USA\n")
while true
  s=readline(win)
  if(s==""); break; end
  st=split(s)
  t=parse(Float64,st[1])
  (d,tv)=well(t)
  @printf(wout,"%6.1f,%6.1f,%6.1f %-22s\n",t,d,tv,st[2])
end
println("done")
close(win)
close(wout)
# other printf formats designators:
#
# many system-like directory functions
# examples:
pwd() # path to current working directory
readdir() # list of files/directories in working directory

stat("wellin.dat") # yields string, list of file parameters
# mode=6 Owner R&W, =4 Group R only, =4 Others R only
# uperm(file) user permission, gperm(file) group permission
# operm(file) other permission

chmod("wellout.csv",0o664)
stat("wellout.csv")


# script for Fig.8(b)(c), Fig.9, Fig.10(b)(c), Fig.11

using MultiscaleGraphSignalTransforms, LightGraphs, Plots; gr(dpi = 200)

## Build weighted graph
G, L, X = SunFlowerGraph(N = 400); N = nv(G)
𝛌, 𝚽 = eigen(Matrix(L))
sgn = (maximum(𝚽, dims = 1)[:] .> -minimum(𝚽, dims = 1)[:]) .* 2 .- 1
𝚽 = 𝚽 * Diagonal(sgn)
Q = incidence_matrix(G; oriented = true)
W = 1.0 * adjacency_matrix(G)
edge_weight = [e.weight for e in edges(G)]

## Build Dual Graph by DAG metric
distDAG = eigDAG_Distance(𝚽, Q, N; edge_weight = edge_weight)
Gstar_Sig = dualgraph(distDAG)
G_Sig = GraphSig(W, xy = X)
GP_dual = partition_tree_fiedler(Gstar_Sig; swapRegion = false)
GP_primal = pairclustering(𝚽, GP_dual)

@time VM_NGWP = vm_ngwp(𝚽, GP_dual) #54.986524 seconds (1.19 M allocations: 25.127 GiB, 2.36% gc time)
@time PC_NGWP = pc_ngwp(𝚽, GP_dual, GP_primal) #0.611176 seconds (225.41 k allocations: 844.488 MiB, 11.71% gc time)

#################### Fig. 8(b) barbara eye graph signal
using MAT
f = matread(joinpath(@__DIR__, "../datasets",
            "sunflower_barbara_voronoi.mat"))["f_eye_voronoi"]
G_Sig.f = reshape(f, (N, 1))
scatter_gplot(X; marker = f, ms = LinRange(4.0, 14.0, N), c = :greys);
plt = plot!(xlim = [-1.2,1.2], ylim = [-1.2,1.2], frame = :none)
# savefig(plt, joinpath(@__DIR__, "../paperfigs/SunFlower_barbara_feye.png"))

#################### Fig. 8(c) barbara eye relative l2 approximation error by various methods
DVEC = getall_expansioncoeffs(G_Sig, GP_dual, VM_NGWP, PC_NGWP, 𝚽)
approx_error_plot(DVEC);
plt = plot!(xguidefontsize = 16, yguidefontsize = 16, legendfontsize = 12)
# savefig(plt, joinpath(@__DIR__, "../paperfigs/SunFlower_barbara_feye_DAG_approx.png"))

#################### Fig. 9 barbara eye 16 most important VM-NGWP vectors (ignore the DC vector)
dmatrix_VM = ngwp_analysis(G_Sig, VM_NGWP)
dvec_vm_ngwp, BS_vm_ngwp = ngwp_bestbasis(dmatrix_VM, GP_dual)
important_idx = sortperm(dvec_vm_ngwp[:].^2; rev = true)
for i in 2:17
    dr, dc = BS_vm_ngwp.levlist[important_idx[i]]
    w = VM_NGWP[dr, dc, :]
    println("(j, k, l) = ", NGWP_jkl(GP_dual, dr, dc))
    scatter_gplot(X; marker = w, ms = LinRange(4.0, 14.0, N), c = :greys)
    plt = plot!(xlim = [-1.2, 1.2], ylim = [-1.2, 1.2], frame = :none,
                cbar = false, clims = (-0.15, 0.15))
    # savefig(plt, joinpath(@__DIR__,
    #     "../paperfigs/SunFlower_barbara_feye_DAG_VM_NGW_important_basis_vector$(lpad(i,2,"0")).png"))
end

#################### Fig. 10(b) barbara pants graph signal
f = matread(joinpath(@__DIR__, "../datasets",
            "sunflower_barbara_voronoi.mat"))["f_trouser_voronoi"]
scatter_gplot(X; marker = f, ms = LinRange(4.0, 14.0, N), c = :greys);
plt = plot!(xlim = [-1.2,1.2], ylim = [-1.2,1.2], frame = :none)
# savefig(plt, joinpath(@__DIR__, "../paperfigs/SunFlower_barbara_ftrouser.png"))

#################### Fig. 10(c) barbara eye relative l2 approximation error by various methods
G_Sig.f = reshape(f, (N, 1))
DVEC = getall_expansioncoeffs(G_Sig, GP_dual, VM_NGWP, PC_NGWP, 𝚽)
approx_error_plot(DVEC);
plt = plot!(xguidefontsize = 16, yguidefontsize = 16, legendfontsize = 12)
# savefig(plt, joinpath(@__DIR__, "../paperfigs/SunFlower_barbara_ftrouser_DAG_approx.png"))

#################### Fig. 11 barbara pants 16 most important VM-NGWP vectors (ignore the DC vector)
dmatrix_VM = ngwp_analysis(G_Sig, VM_NGWP)
dvec_vm_ngwp, BS_vm_ngwp = ngwp_bestbasis(dmatrix_VM, GP_dual)
important_idx = sortperm(dvec_vm_ngwp[:].^2; rev = true)
for i in 2:17
    dr, dc = BS_vm_ngwp.levlist[important_idx[i]]
    w = VM_NGWP[dr, dc, :]
    println("(j, k, l) = ", NGWP_jkl(GP_dual, dr, dc))
    scatter_gplot(X; marker = w, ms = LinRange(4.0, 14.0, N), c = :greys)
    plt = plot!(xlim = [-1.2, 1.2], ylim = [-1.2, 1.2], frame = :none,
                cbar = false, clims = (-0.15, 0.15))
    # savefig(plt, joinpath(@__DIR__,
    #     "../paperfigs/SunFlower_barbara_ftrouser_DAG_VM_NGW_important_basis_vector$(lpad(i,2,"0")).png"))
end


type SDF_VD <: SDFDynamicSystem
    omg::Float64
    zta::Float64
    xi::Float64
    alpha::Float64
    u0::Float64
    v0::Float64

    SDF_VD(omg::Float64, zta::Float64, xi::Float64, alpha::Float64) = new(omg, zta, xi, alpha, 0.0, 0.0)
    SDF_VD(omg::Float64, zta::Float64, xi::Float64) = new(omg, zta, xi, 1.0, 0.0, 0.0)
end

function SDF_VD(m::Float64, c::Float64, k::Float64, c_d::Float64, alpha::Float64)
    omg = sqrt(k/m)
    zta = c/(2.0*m*omg)
    xi = c_d/(2.0*m*omg)
    SDF_VD(omg, zta, xi, alpha)
end

function systemEquation(s::SDF_VD, p::Function)
    function dydt(y::AbstractArray{Float64,1}, t::Float64)
        if s.alpha == 1.0
            dvdt = p(t)-2.0*(s.zta + s.xi)*s.omg*y[2]-s.omg*s.omg*y[1]
        else
            dvdt = p(t)-2.0*s.zta*s.omg*y[2]-2.0*s.xi*s.omg*sign(y[2])*abs(y[2])^s.alpha-s.omg*s.omg*y[1]
        end
        [y[2]; dvdt]
    end
    dydt
end

function response(s::SDF_VD, p::Function, t::AbstractArray{Float64,1})
    y0 = [s.u0; s.v0]

    f = systemEquation(s, p)

    y = ode4(f, y0, t)

    u = y[:,1]
    v = y[:,2]
    fdz = 2.0*s.zta*s.omg*v
    fdx = 2.0*s.xi*s.omg*sign(v).*abs(v).^s.alpha
    a = map(p,t)-fdz-fdx-s.omg*s.omg*u

    [u v a fdz fdx]
end


# Precursor of the RF excitation code with feature directions, most of the comments below are already implemented

using Koma
using Koma: γ, Q, RF_fun, get_grads, Un, cross
using Plots, LaTeXStrings
gr(size = (800,800))

# Example
G = 30e-3 # 30 mT/m
T = 3e-3; # 3 ms
B1 = 15e-6; # 15 μT

N = 300
RFs = RF_fun(t->B1*sinc.(4(t-T/2)/T),T,N) #sinc pulse of duration T [s]
B1e = RFs.A
α = π / 2
ΔT = RFs.T
c = α / (2π*γ*sum(B1e.*ΔT))
c = c.re
RFs.A .= RFs.A * c

plot(real.(RFs.A), linewidth=5, dpi=200)

## In general, Bz = Δω(x)/γ + G ⋅ x
B1e = RFs.A
ΔT = RFs.T
Bx, By = real.(B1e), imag.(B1e)

# Plot
df = .2 #.02
ff = -1:df:1
colors = range(HSV(0,1,1), stop=HSV(-360,1,1), length=length(ff)) # inverse rotation

anim = @animate for j ∈ reverse(2:N)
plot()
for (i,f) = enumerate(ff)
Bz = zeros(size(B1e)) .+ f*.01e-3 # Bz = 0 in the rotating frame implies a non-selective excitation

φ = -2π*γ * ΔT .* sqrt.(abs.(B1e).^2 .+ abs.(Bz).^2) # angle of rotation
φ[φ.==0] .= 1e-17; # removes problems when dividing by φ
n =  2π*γ * ΔT .* [Bx By Bz]./abs.(φ) # axis of rotation
Qs = [Q(φ[i],n[i,:]) for i=1:N] # hard-pulse approximation for every RF element in RFs
Qt = *(Qs...) # Total rotation matrix

Mxy, Mz = 0. + 0. *im, 1. # [0,0,1]
M0 = Mag(Mxy, Mz) # Initial magnetization
Mt = Qt*M0 # Final magnetization
Mz = [(*(Qs[1:i]...)*M0)[2] for i=2:N-j]
Mxy = [(*(Qs[1:i]...)*M0)[1] for i=2:N-j]

# Plots
plot!(real.(Mxy),imag.(Mxy),real.(Mz),
    linewidth=5,xlabel="x",ylabel="y",zlabel="z",
    xlims=(-1,1),ylims=(-1,1),zlims=(-1,1),dpi=200,label=:none,
    linecolor=colors[i],subplot=1)
end

plot!(real.(RFs.A)[1:N-j], linewidth=5, dpi=200,
    xlims=(0,N),ylims=(-.5c*B1,1.1c*B1), ticks = nothing,
    inset_subplots=bbox(0.05, 0.05, 0.5, 0.25, :bottom, :right),
    subplot=2,label=:none)

end
#gif(anim, "anim_fps60.gif", fps = 60)

## TODO
# - A function that translates from Phantom and Sequence to a rotation matrix for each spin, something like:
# function get_rf_rotations(obj::Phantom, seq::Sequence, t)
#     Δt = t[2]-t[1]
#     Gx, Gy = get_grads(seq,t)
#     B1e = get_rfs(seq,t) #TODO: uniformly sample RF pulses with respect to the simulation sampling period Δt
#     for spin ∈ obj # this an example, it will not work
#         φ = -2π*γ * Δt .* sqrt.(abs.(B1e).^2 .+ abs.(Bz).^2) # angle of rotation
#         φ[φ.==0] .= 1e-17; # removes problems when dividing by φ
#         n =  2π*γ * Δt .* [Bx By Bz]./abs.(φ) # axis of rotation
#         Qtotal = prod([Q(φ[i],n[i,:]) for i=1:N]) # hard-pulse approximation for every RF element in RFs
#     end
#     Qtotal
# end
# - Obtaining rotation matrices efficiently: as we would NEVER acquire during an RF pulse we do not need
#   to store intermidiate steps. This is due to the fact that we need to change between Rx and Tx with a switch (hardware limitation).
# - I suggest to simulate RF blocks separatly, and then to use run_sim2D_spin() in between.
#   The only thing we need to add is a way to follow the T1 decay during the non-RF parts of the simulation.
#   The magnetization [Mx + iMx, Mz] should decay as follows (the first part is already in run_sim2D_spin()):
#                    [exp(-Δt/T2)(Mx + iMx), exp(-Δt/T1) Mz + ρ (1 - exp(-Δt/T1))].
# - Movement of the phantom during excitation? (using obj.x + obj.ux(obj.x,obj.y))
# - Effects of T1 and T2 during an RF block: For each Δt we do RF and then T1 & T2 decay.
# - Plotting function Koma.plot_grads(seq) in Display.jl is not currently plotting RF pulses
# - Pre-defined RF waveforms.
# - Shinnar-Le Roux pulse desing?


using EmpiricalCDFs: EmpiricalCDF
using MaximumLikelihoodPower: mle

@testset "ordinary RW, first return" begin
    nsamples = 10^4
    cutoff = 200
    first_return_actor = FirstReturnActor()
    ecdf_actor = ECDFValueActor(first_return_actor)
    for w in (WalkB(), WalkF())
        @time trial!(WalkPlan(w, ActorSet(StepLimitActor(10^10), first_return_actor)), SampleLoopActor(nsamples, ecdf_actor))
        alpha, err = mle(ecdf_actor[end - cutoff:end])
        @test isapprox(alpha, 3/2, atol=0.2)
    end
end

@testset "ordinary RW, mean std" begin
    nsteps = 10^4
    ntrials = 10^5
    for w in (WalkB(), WalkF())
        wp = WalkPlan(w, StepLimitActor(nsteps))
        ecdf = EmpiricalCDF{Int}()
        cdf_actor = ECDFActor(get_x, ecdf)
        trial_loop = SampleLoopActor(ntrials, cdf_actor)
        @time trial!(wp, trial_loop)
        ecdf =  ecdf ./ sqrt(nsteps)
        tol = 1e-2
        @test isapprox(std(ecdf), 1; atol=tol)
        @test isapprox(mean(ecdf), 0; atol=tol)
    end
end


using LinearAlgebra
#using Arpack # disabled. Use ArnoldMethod instead
using ..NEPCore, ..NEPTypes
import ..NEPCore.compute_Mder;
import ..NEPCore.compute_Mlincomb;
import Base.size;
export broyden;

function broyden_default_errmeasure(λ,v,r)
    return norm(r)/norm(v)
end

# The internal Broyden method (without deflation)
# which corresponds to version T in the paper below
function broyden_T(::Type{TT},nep::NEP;
                   v1=0,u1=[],λ1=TT(0),
                   CH=0,T1=0,W1=0,
                   maxit=100,
                   S=zeros(TT,0,0),X=zeros(TT,size(nep,1),0),
                   check_error_every=10,
                   print_error_every=1,
                   tol=1e-12,
                   threshold=0.4,
                   time0=time_ns(),
                   errmeasure::Function=broyden_default_errmeasure,
                   logger=0) where {TT<:Number}

    @parse_logger_param!(logger)

    # Check types are consistent

    v=Array{TT,1}(v1);
    u=Array{TT,1}(u1);
    X=Array{TT,2}(X);
    S=Array{TT,2}(S);
    CH=Array{TT,2}(CH);
    λ=TT(λ1);
    #x=vcat(v,λ);
    #x=Array{TT,1}(x);
    n=size(nep,1);
    p=size(S,1);

    rk = compute_Mlincomb(nep, λ, v+(X*((λ*Matrix{TT}(I, p, p) - S) \ u)))

    T = Matrix{TT}(T1)
    Wext = Matrix{TT}(undef, n, p+2)
    W = view(Wext, 1:n, 1:p+1)
    W[:,:] = Matrix{TT}(W1)
    errhist = Vector{real(TT)}(NaN * ones(real(TT), maxit))

    timehist = Vector{Float64}(undef, maxit) * NaN
    II = Matrix{TT}(I, p, p)

    Z = Matrix{TT}(undef, n, p+1)
    Tztilde = Vector{TT}(undef, n)
    aH = Vector{TT}(undef, p)'
    Wold = copy(W)
    ztilde = Vector{TT}(undef, n)
    waH = Vector{TT}(undef, p)'
    Z=T*W;
    for j=1:maxit

        Trk=T*rk;

        duλ=-(CH*Z)\(CH*Trk) # contains [Δu;Δλ]

        # Compute u and λ updates
        Δu=duλ[1:p];
        Δλ=duλ[end];
        # Compute v-update
        Δv=-Z*duλ-Trk

        γ=TT(1.0);
        tt=real(TT)(sqrt(abs(Δλ)^2+norm(Δv)^2));
        if (tt>threshold)  # Avoid too big λ-steps
            γ=TT(threshold)/tt;
        end

        v0=v; u0=u; λ0=λ;

        v=v+γ*Δv;
        u=u+γ*Δu;
        λ=λ+γ*Δλ;

        # Update Jacobian approximation
        vv=v+(X*((λ*II-S)\u));
        rkp::AbstractVector=compute_Mlincomb(nep,λ,vv);

        ztilde=(rkp-(1-γ)*rk)/γ;
        Tztilde=T*ztilde;

        # For w-update
        bH=[Δu' (Δλ)']/(norm(Δv)^2+norm(Δu)^2+norm(Δλ)^2);
        # For T-update
        β=norm(Δv)^2+norm(Δu)^2+abs(Δλ)^2+Δv'*Tztilde;
        aH=-(Δv'*T)/β

        # Update Z
        Z=Z+Tztilde*(aH*W+(1+aH*ztilde)*bH);

        # Update W
        W=W+ztilde*bH;

        # Update T
        T=T+((Tztilde)*aH);

        if (false) # Extra type check
            for q=[Z,T,W,Trk,duλ,Δu,Δv,λ,Δλ,Tztilde,T,aH,complex(β)]
                if (TT != eltype(q))
                    error("Bad type ",q);
                end

            end
        end

        # step forward
        rk=rkp;

        if (mod(j,check_error_every)==0)
            errhist[j]=errmeasure(λ,v+(X/(λ*II-S))*u,rk);
            timehist[j]=Float64((time_ns()-time0)*1e-9);
            if (mod(j,print_error_every)==0)
                d = opnorm(CH*v - reverse(Matrix{TT}(I, 1+p, 1), dims = 1))
            end

            push_iteration_info!(logger,j,err=errhist[j],λ=λ);
            if (errhist[j]<tol)
                return (λ,v,u,T,W,j,errhist[1:j],timehist[1:j])
            end
        end

    end

    push_info!(logger,"Too many iterations"); #" resnorm=",opnorm(rk));
    return (λ,v,u,T,W,maxit,errhist,timehist)

end

##################
"""
    S,V = broyden([eltype,]nep::NEP[,approxnep];kwargs)

Runs Broydens method (with deflation) for the nonlinear eigenvalue
problem defined by nep.
An approximate nep can be provided which is used as an
initialization of starting
matrix/vectors. The optional argument `approxnep` determines how
 to initiate the
algorithm. It can be an `NEP`, the symbol `:eye`
corresponding to starting
with an identity matrix, and a `Matrix` (of size ``n\times n``).
Beside most of the standard kwargs as described in [`augnewton`](@ref),
it supports `pmax` which is subspace used in deflation, essentially
the number of eigenvalues, `add_nans::Bool`  which
determines if `NaNs` should be added in book keeping.
`eigmethod` which can be `:eig`, `:eigs` or
`:invpow`. The `:invpow` is an implementation of the power method, which
is slow but works well e.g. for `BigFloat`. The kwarg `recompute_U`
determines if the `U`-matrix should be recomputed in every
deflation (which can be more robust). The implementation
has two loggers `logger` and `inner_logger`. The `logger`
corresponds to outer iterations (deflation)
and  `inner_logger` is the iterates in Broydens method.
The kwarg `check_error_every` and `print_error_every`
  detemine how often errors should be check and how
often they should be printed. For real problems
with complex conjugate symmetry, you may want
to set the kwarg `addconj=true` in order
to reduce computation by automatically adding the
complex conjugate vectors.

The method computes an invariant pair and can therefore find
several eigenvalues. The
retured value is (S,V) is an invariant pair and
the eigenvalues are on the diagonal of S.
Eigenpairs can be directly extracted with
[`get_deflated_eigpairs`](@ref).

# Example

```julia-repl
julia> nep=nep_gallery("dep0");
julia> S,V=broyden(nep);
julia> λ=S[1,1]
-0.15955391823299253 - 3.874865266487398e-19im
julia> minimum(svdvals(compute_Mder(nep,λ)))
1.6293996560844023e-16
julia> λ=S[2,2]
-0.5032087003825461 + 1.1969823800738464im
julia> minimum(svdvals(compute_Mder(nep,λ)))
1.1073470346550144e-15
julia> λ=S[3,3]
1.2699713558173726 + 5.342786996459857e-16im
julia> minimum(svdvals(compute_Mder(nep,λ)))
5.905315846211231e-16
julia> broyden(nep,logger=2,check_error_every=1);  # Prints out a lot more convergence info
```
In order to extract eigenpairs you can use the following:
```julia-repl
julia> (D,X)=get_deflated_eigpairs(S,V,size(nep,1));
julia> for i=1:3; @show norm(compute_Mlincomb(nep,D[i],X[:,i])); end
norm(compute_Mlincomb(nep, D[i], X[:, i])) = 8.459878994614521e-13
norm(compute_Mlincomb(nep, D[i], X[:, i])) = 1.2102336671048442e-13
norm(compute_Mlincomb(nep, D[i], X[:, i])) = 2.1012363973403225e-16
```
# References

* Jarlebring, Broyden’s method for nonlinear eigenproblems, SIAM J. Sci. Comput., 41:A989–A1012, 2019, https://arxiv.org/pdf/1802.07322

"""
broyden(nep::NEP;params...)=broyden(ComplexF64,nep,:eye;params...)
broyden(nep::NEP,approxnep;params...)=broyden(ComplexF64,nep,approxnep;params...)
broyden(T,nep::NEP;params...)=broyden(T,nep,:eye;params...)
function broyden(::Type{TT},nep::NEP,approxnep::Union{NEP,Symbol,Matrix};σ::Number=0,
                 pmax::Integer=3,
                 c::Vector=ones(TT,size(nep,1)),
                 maxit::Integer=1000,addconj=false,
                 check_error_every::Integer=10,
                 print_error_every::Integer=1,
                 threshold::Real=0.2,
                 tol::Real=1e-12,
                 errmeasure::Function=broyden_default_errmeasure,
                 add_nans::Bool=false,
                 include_restart_timing::Bool=true,
                 eigmethod::Symbol=:eig,
                 logger =0,
                 recompute_U = false,
                 inner_logger = 0
                 ) where {TT<:Number}

    @parse_logger_param!(logger)
    @parse_logger_param!(inner_logger)

    time0=time_ns();
    n=size(nep,1);
    if (pmax>size(nep,1))
        @warn "Too many eigenvalues requested. Reducing"
        pmax=size(nep,1);
    end
    σ=TT(σ);

    # Step 1. Compute M0 and T0
    if (isa(approxnep,Matrix))
        M1=approxnep;
    elseif (approxnep == :eye)
        M1=Matrix{TT}(I,n,n)
    else
        M1=compute_Mder(approxnep,σ);
    end

    T1=inv(M1);

    X=zeros(TT,n,0);
    S=zeros(TT,0,0);

    k=1;

    all_errhist=[]; sumiter=1;
    all_timehist=[];
    all_iterhist=[];
    sumiter=1;
    UU = Matrix{TT}(I, n, pmax+1) # For storage
    U1=view(UU,1:n,1:0);
    while (k<=pmax)

        # Step 5
        ## Complete recomputation of U0
        p_U1=size(U1,2);
        U1=view(UU,1:n,1:k-1);
        if recompute_U
            i_start=1;
        else
            i_start=p_U1+1; # In theory they are already satisfied
        end
        for i=i_start:k-1
            ei=zeros(TT,size(S,1)); ei[i]=1;
            f = (σ * Matrix(1.0I, k-1, k-1) - S) \ ei
            vv=X*f;
            U1[:,i]=compute_Mlincomb(nep,σ,vv);
        end

        # Step 6
        MM::Matrix{TT}=[M1 U1; X' zeros(TT,k-1,k-1)];
        push_info!(logger,"running eigval comp for deflation",continues=true);

        local d,V;
        if (eigmethod==:eig)
            d,V = eigen(MM)
        elseif (eigmethod==:eigs)
            d,V = eigs(MM,which=:SM)
        elseif (eigmethod==:invpow)
            d,V = eigs_invpow(MM,maxit=4000,sigma=0)
        else
            error("Unknown eig method",eignmethod)
        end

        push_info!(logger,"");

        x=V[:,argmin(abs.(d))];

        if (TT <: Real)
            x=real(x);
        end

        # Orthogonalize
        v0=x[1:n];
        u0=x[n+1:end];
        h=X'*v0;
        v0=v0-X*h;
        u0 = u0 + (σ * Matrix{TT}(I, k-1, k-1) - S) * h
        CH=[X';c'];

        # Normalize it
        u0=u0/(c'*v0);
        v0=v0/(c'*v0);

        if (!include_restart_timing)
            time0=time_ns();
        end

        # Step 7
        d=sqrt(eps(real(TT)));
        push_info!(logger,"Computing initial matrix",continues=true);
        f1a=(compute_Mlincomb(nep,σ+d,v0)-compute_Mlincomb(nep,σ-d,v0))/2d;
        f1b = -U1 * ((σ * Matrix{TT}(I, k-1, k-1) - S) \ u0)
        push_info!(logger,".");
        f1=f1a+f1b;
        W1=[U1 f1];

        push_info!(logger,"Starting broyden n=$n")
        T=copy(T1);

        # Main call to broyden_T
        (λm,vm,um,Tm,Wm,iter,errhist,timehist)=
        broyden_T(TT,nep,
                  v1=v0,u1=u0,λ1=σ,
                  CH=CH,T1=T1,W1=W1,
                  S=S,X=X,
                  maxit=maxit,
                  check_error_every=check_error_every,
                  print_error_every=print_error_every,
                  threshold=threshold,
                  tol=tol,
                  errmeasure=errmeasure,
                  time0=time0,
                  logger=inner_logger)

        if size(all_iterhist,1) > 0
            iterhist=(1:size(errhist,1)) .+ all_iterhist[end]
        else
            iterhist=(1:size(errhist,1))
        end

        # Deflation book keeping

        if (add_nans && size(all_iterhist,1)>1)
            all_errhist=[all_errhist;NaN]
            all_timehist=[all_timehist;NaN]
            all_iterhist=[all_iterhist;NaN]
        end
        all_errhist=[all_errhist;errhist];
        all_timehist=[all_timehist;timehist];
        all_iterhist=[all_iterhist;iterhist];
        sumiter=sumiter+iter;
        um=um/norm(vm[1:n])
        vm=vm/norm(vm[1:n]) # Normalize

        push_info!(logger,"Found an eigval $k:$λm");

        X=[X vm]
        S=[S um;zeros(1,k-1) λm]

        # Add conjugate?
        if (abs(imag(λm))>tol*10 && addconj)

            v1 = conj(vm + X[:,1:k-1] * ((λm * Matrix{TT}(I, k-1, k-1) - S[1:k-1,1:k-1]) \ um))
            λ1=conj(λm);

            rnorm=norm(compute_Mlincomb(nep,λ1,v1))
            push_info!(logger,"Adding conjugate $k")
            if (rnorm>tol*10)
                @warn "Trying to add a conjugate pair which does not have a very small residual."
            end

            h=X'*v1;
            v1t=v1-X*h;
            beta=norm(v1t);
            X=[X v1t/beta];
            k=k+1;
            S1=zeros(TT,k,k);
            S1[1:(k-1),1:(k-1)]=S;
            S1[k,k]=λ1;
            R = Matrix{TT}(I, k, k)
            R[1:k-1,end]=h; R[k,k]=beta;
            S=(R*S1)/R;
            #println("opnorm(XX-I)=",opnorm(X'*X-Matrix(1.0I, size(X,2), size(X,2))))
            #println("opnorm(MM)=",opnorm(compute_MM(nep,S,X)));
            #        X=[X v1]
            #      S=[S conj(v1[(n0+1):(n0+p)]);zeros(1,p) conj(λ1)]
            #dnep=NEPBroydenDeflated(nep,S,X);
        end
        k=k+1;
    end
    push_info!(logger,"Iterations:$sumiter")
    return S,X,T1,all_errhist,all_timehist,all_iterhist;

end

function eigs_invpow(MM;maxit=10,sigma=0)
    AA = factorize(MM - sigma * sparse(1.0I, size(MM,1), size(MM,1)))
    z=ones(size(AA,1));
    for k=1:maxit
        z=AA\z;
        z=z/opnorm(z);
    end
    lambda=z'*MM*z;
    z=reshape(z,size(AA,1),1);
    return ([lambda],z);
end