using FundamentalsNumericalComputation
using Test
using LinearAlgebra

@testset "Chapter 1" begin
	@test FNC.horner([-1,3,-3,1],1.6) ≈ 0.6^3
end

@testset "Chapter 2" begin
	A = [ 1 2 3 0; -1 1 2 -1; 3 1 2 4; 1 1 1 1 ]
	L,U = FNC.lufact(A)
	@test norm(L*U - A) < 100eps()
	@test norm(U - triu(U)) < 100eps()
	@test norm(L - tril(L)) < 100eps()
	b = [1,10,0,-1] / 5;
	@test norm(L\b - FNC.forwardsub(L,b)) < 100eps()
	@test norm(U\b - FNC.backsub(U,b)) < 100eps()
	L,U,p = FNC.plufact(A)
	@test norm(L*U - A[p,:]) < 100eps()
	@test norm(U - triu(U)) < 100eps()
	@test norm(L - tril(L)) < 100eps()
end

@testset "Chapter 3" begin
	A = [3 4 5;-1 0 1;4 2 0; 1 1 2; 3 -4 1]
	b = 5:-1:1
	@test FNC.lsnormal(A,b) ≈ A\b
	@test FNC.lsqrfact(A,b) ≈ A\b
	Q,R = qr(A)
	QQ,RR = FNC.qrfact(A)
	@test Q ≈ QQ
	@test R ≈ RR[1:3,:]
end

@testset "Chapter 4" begin

	for c in [2,4,7.5,11]
		f = x -> exp(x) - x - c;
		dfdx = x -> exp(x) - 1;
		x = FNC.newton(f,dfdx,1.0);  r = x[end];
		@test abs(f(r)) < 100eps()
	end

	for c in [2,4,7.5,11]
		f = x -> exp(x) - x - c;
		dfdx = x -> exp(x) - 1;
		x = FNC.secant(f,3,0.5);  r = x[end];
		@test abs(f(r)) < 100eps()
	end

	function nlfun(x)
		f = zeros(3)
		f[1] = exp(x[2]-x[1]) - 2;
		f[2] = x[1]*x[2] + x[3];
		f[3] = x[2]*x[3] + x[1]^2 - x[2];
		return f
	end
	function nljac(x)
		J = zeros(3,3)
		J[1,:] = [-exp(x[2]-x[1]),exp(x[2]-x[1]), 0]
		J[2,:] = [x[2], x[1], 1]
		J[3,:] = [2*x[1], x[3]-1, x[2]]
		return J
	end

	x = FNC.newtonsys(nlfun,nljac,[0,0,0]);
	@test norm(nlfun(x[end])) < 100eps()
	x = FNC.newtonsys(nlfun,nljac,[1,2,3]);
	@test norm(nlfun(x[end])) < 100eps()

	x = FNC.levenberg(nlfun,[10,-4,-3])
	@test norm(nlfun(x[end])) < 1e-12

end

@testset "Chapter 5" begin
	f = t->cos(5t)
	Q,t = FNC.intadapt(f,-1,3,1e-8)
	@test Q ≈ (sin(15)+sin(5))/5 rtol = 1e-5
	T,_ = FNC.trapezoid(f,-1,3,820)
	@test T ≈ (sin(15)+sin(5))/5 rtol = 1e-4

	t = [-2,-0.5,0,1,1.5,3.5,4]/10
	w = FNC.fdweights(t.-0.12,2)
	f = x->cos(3x)
	@test dot(w,f.(t)) ≈ -9cos(0.36) rtol = 1e-3

	t = [-2,-0.5,0,1,1.5,3.5,4]/10
	H = FNC.hatfun(t,5)
	@test H(0.22) ≈ (0.22-t[5])/(t[6]-t[5])
	@test H(.38) ≈ (t[7]-.38)/(t[7]-t[6])
	@test H(0.06)==0
	@test H(t[6])==1
	@test H(t[7])==0
	@test H(t[1])==0
	p = FNC.plinterp(t,f.(t))
	@test p(0.22) ≈ f(t[5]) + (f(t[6])-f(t[5]))*(0.22-t[5])/(t[6]-t[5])
	S = FNC.spinterp(t,exp.(t))
	x = [-.17,-0.01,0.33,.38]
	@test S.(x) ≈ exp.(x) rtol = 1e-5
	@test S.(t) ≈ exp.(t) rtol = 1e-11
end

@testset "Chapter 6" begin
	f = (u,p,t) -> u + p*t^2
	û = exp(1.5) - 2*(-2 + 2*exp(1.5) - 2*1.5 - 1.5^2)
	ivp = ODEProblem(f,1,(0,1.5),-2)
	t,u = FNC.euler(ivp,4000)
	@test û ≈ u[end] rtol = 0.005
	t,u = FNC.am2(ivp,4000)
	@test û ≈ u[end] rtol = 0.005

	g = (u,p,t) -> [t+p-sin(u[2]),u[1]]
	ivp = ODEProblem(g,[-1.,4],(1.,2.),-6)
	sol = solve(ivp,Tsit5())
	t,u = FNC.euler(ivp,4000)
	@test u[end] ≈ sol.u[end] rtol=0.004
	t,u = FNC.ie2(ivp,4000)
	@test u[end] ≈ sol.u[end] rtol=0.0005
	t,u = FNC.rk4(ivp,800)
	@test u[end] ≈ sol.u[end] rtol=0.0005
	t,u = FNC.ab4(ivp,800)
	@test u[end] ≈ sol.u[end] rtol=0.0005
	t,u = FNC.rk23(ivp,1e-4)
	@test u[end] ≈ sol.u[end] rtol=0.0005
	t,u = FNC.am2(ivp,2000)
	@test u[end] ≈ sol.u[end] rtol=0.0005
end

@testset "Chapter 8" begin
	V = randn(4,4)
	D = diagm([-2,0.4,-0.1,0.01])
	A = V*D/V;

	γ,x = FNC.poweriter(A,30)
	@test γ[end] ≈ -2 rtol=1e-10
	@test abs( dot(x,V[:,1])/(norm(V[:,1])*norm(x)) ) ≈ 1 rtol=1e-10

	γ,x = FNC.inviter(A,0.37,15)
	@test γ[end] ≈ 0.4 rtol=1e-10
	@test abs( dot(x,V[:,2])/(norm(V[:,2])*norm(x)) ) ≈ 1 rtol=1e-10

	Q,H = FNC.arnoldi(A,ones(4),4)
	@test A*Q[:,1:4] ≈ Q*H

	x,res = FNC.gmres(A,ones(4),3)
	@test norm(ones(4) - A*x) ≈ res[end]
	x,res = FNC.gmres(A,ones(4),4)
	@test A*x ≈ ones(4)
end

@testset "Chapter 9" begin
	f = x -> exp(sin(x)+x^2)
	t = [-cos(k*π/40) for k in 0:40 ]
	p = FNC.polyinterp(t,f.(t))
	@test p(-0.12345) ≈ f(-0.12345)

	f = x -> exp(sin(π*x))
	n = 30
	t = [ 2k/(2n+1) for k in -n:n ]
	p = FNC.triginterp(t,f.(t))
	@test p(-0.12345) ≈ f(-0.12345)
	t = [ k/n for k in -n:n-1 ]
	p = FNC.triginterp(t,f.(t))
	@test p(-0.12345) ≈ f(-0.12345)

	F = x -> tan(x/2-0.2)
	f = x -> 0.5*sec(x/2-0.2)^2
	@test FNC.ccint(f,40)[1] ≈ F(1)-F(-1)
	@test FNC.glint(f,40)[1] ≈ F(1)-F(-1)

	f = x -> 1/(32+2x^4)
	@test FNC.intinf(f,1e-9)[1] ≈ sqrt(2)*π/32 rtol=1e-5

	f = x -> (1-x)/( sin(x)^0.5 )
	@test FNC.intsing(f,1e-8)[1] ≈ 1.34312 rtol=1e-5
end

@testset "Chapter 10" begin
	λ = 0.6
	phi = (r,w,dwdr) -> λ/w^2 - dwdr/r;
	a = eps();  b = 1;
	g₁ = (u,du)->du    # Neumann at left
	g₂ = (u,du)->u-1   # Dirichlet at right

	r,w,dwdx = FNC.shoot(phi,(a,b),g₁,g₂,[0.8,0])
	@test w[1] ≈ 0.78776 rtol=1e-4

	f = x -> exp(x^2-3x)
	df = x -> (2x-3)*f(x)
	ddf = x -> ((2x-3)^2+2)*f(x)

	t,D,DD = FNC.diffmat2(400,(-0.5,2))
	@test df.(t) ≈ D*f.(t) rtol=1e-3
	@test ddf.(t) ≈ DD*f.(t) rtol=1e-3
	t,D,DD = FNC.diffcheb(80,(-0.5,2))
	@test df.(t) ≈ D*f.(t) rtol=1e-7
	@test ddf.(t) ≈ DD*f.(t) rtol=1e-7

	exact = x -> exp(sin(x));
	p = x -> -cos(x);
	q = sin;
	r = x -> 0;
	x,u = FNC.bvplin(p,q,r,[0,pi/2],1,exp(1),300);
	@test u ≈ exact.(x) rtol=1e-3

	ϕ = (t,θ,ω) -> -0.05*ω - sin(θ);
	g1(u,du) = u-2.5
	g2(u,du) = u+2
	init = collect(range(2.5,-2,length=101));

	t,θ = FNC.bvp(ϕ,[0,5],g1,g2,init)
	@test θ[7] ≈ 2.421850016880724 rtol=1e-10

	c = x -> x^2;
	 q = x -> 4;
	f = x -> sin(π*x);
	x,u = FNC.fem(c,q,f,0,1,100)
	@test u[33] ≈ 0.1641366907307196 rtol=1e-10
end

@testset "Chapter 11" begin
	s = x -> sin(π*(x-0.2))
	c = x -> cos(π*(x-0.2))
	f = x -> 1 + s(x)^2
	df = x -> 2π*s(x)*c(x)
	ddf = x -> 2π^2*(c(x)^2 - s(x)^2)

	t,D,DD = FNC.diffper(400,(0,2))
	@test df.(t) ≈ D*f.(t) rtol=1e-3
	@test ddf.(t) ≈ DD*f.(t) rtol=1e-3

	phi = (t,x,u,uₓ,uₓₓ) -> uₓₓ + t*u
	g1(u,ux) = ux;
	g2(u,ux) = u-1;
	init(x) = x^2;
	x,u = FNC.parabolic(phi,(0,1),40,g1,g2,(0,2),init);
	@test u(0.5)[21] ≈ 0.845404 rtol=1e-4
	@test u(1)[end] ≈ 1 rtol=1e-4
	@test u(2)[1] ≈ 2.45692 rtol=1e-4
end

@testset "Chapter 13" begin

	f = (x,y) -> -sin(3*x.*y-4*y)*(9*y^2+(3*x-4)^2);
	g = (x,y) -> sin(3*x*y-4*y);
	xspan = [0,1];  yspan = [0,2];
	x,y,U = FNC.poissonfd(f,g,60,xspan,60,yspan);
	X = [x for x in x,y in y];
	Y = [y for x in x,y in y];
	@test g.(X,Y) ≈ U rtol=1e-3

	λ = 1.5
	function pde(X,Y,U,Ux,Uxx,Uy,Uyy)
		return @. Uxx + Uyy - λ/(U+1)^2   # residual
	end
	g = (x,y) -> x+y     # boundary condition
	u = FNC.elliptic(pde,g,30,[0,2.5],24,[0,1]);
	@test u(1.25,0.5) ≈ 1.7236921361 rtol = 1e-6
	@test u(1,0) ≈ 1 rtol = 1e-6
end


"""
    StVenant(; E, ν)

Hyperelastic material
#Arguments
- `λ::Float64`: Lamé parameter
- `μ::Float64`: Lamé parameter (shear modulus)
"""

struct StVenant <: AbstractMaterial
    λ::Float64
    μ::Float64
end

struct StVenantState <: AbstractMaterialState
end

function initial_material_state(::StVenant)
    return StVenantState()
end

function StVenant(; λ::T, μ::T) where T
    return StVenant(λ, μ)
end

strainmeasure(::StVenant) = RightCauchyGreen()

function ψ(mp::StVenant, C)
    μ = mp.μ
    λ = mp.λ
    Ic = tr(C)
    IIc = tr(det(C) * inv(C)')
    return λ / 8 * (Ic - 3)^2 + μ / 4 * (Ic^2 - 2 * Ic - 2 * IIc + 3)
end

function material_response(mp::StVenant, C::SymmetricTensor{2,3}, state::StVenantState = StVenantState(),
                           Δt=nothing; cache=nothing, options=nothing)

    μ = mp.μ
    λ = mp.λ
    I = one(SymmetricTensor{2,3})

    S = λ/2 * (tr(C) - 3)*I + μ*(C-I)
    ∂S∂C = λ*(I⊗I) + μ*(otimesu(I,I) + otimesl(I,I))
    ∂S∂C = symmetric(∂S∂C)

    return S, ∂S∂C, StVenantState()
end


function solve()
    n = parse(Int, readline())
    while n % 2 == 0
        n ÷= 2
    end
    sq = floor(Int, sqrt(n))
    ret = 0
    for i=1:sq
        if n % i == 0
            ret += 2
        end
    end
    if sq * sq == n
        ret -= 1
    end
    2ret
end

println(solve())


function compute_g_n!(t::Transit_Struct{T}) where {T <: Real}
# Transform the u_n coefficients to g_n, which are coefficients
# of the basis in which the P(G_n) functions are computed.
a_n = zeros(T,t.n+1)
# Pre-allocated memory - just need to zero terms:
a_n[1] = one(T)  # Add in the first constant coefficient term
for i=1:t.n
  # Compute the contribution to a_n*\mu^n
  bcoeff = one(T)
  for j=0:i
#    a_n[j+1] -= t.u_n[i]*binomial(i,j)*(-1)^j
    a_n[j+1] -= t.u_n[i]*bcoeff*(-1)^j
    bcoeff *= (i-j)/(j+1)
#    println("i: ",i," j: ",j," a_i: ",a_n[j+1])
  end
end
# Now, compute the g_n coefficients:
for j=t.n:-1:2
  if j >= t.n-1
    t.g_n[j+1] = a_n[j+1]/(j+2)
  else
    t.g_n[j+1] = a_n[j+1]/(j+2)+t.g_n[j+3]
  end
end
if t.n >= 3
  t.g_n[2] = a_n[2]+3*t.g_n[4]
else
  t.g_n[2] = a_n[2]
end
if t.n >= 2
  t.g_n[1] = a_n[1]+2*t.g_n[3]
else
  t.g_n[1] = a_n[1]
end
return
end

function compute_g_n_grad!(t::Transit_Struct{T}) where {T <: Real}
# Transform the u_n coefficients to g_n, which are coefficients
# of the basis in which the P(G_n) functions are computed.
# Compute the derivatives of the flux with respect to the u coefficients.
# We define g_n with two extra elements which are zero:
a_n = zeros(T,t.n+1)
dadu = zeros(T,t.n+1,t.n)
a_n[1] = one(T)  # Add in the first constant coefficient term
for i=1:t.n
  # Compute the contribution to a_n*\mu^n
  bcoeff = one(T)
  for j=0:i
#    a_n[j+1] -= t.u_n[i]*binomial(i,j)*(-1)^j
    a_n[j+1] -= t.u_n[i]*bcoeff*(-1)^j
#    dadu[j+1,i] -= binomial(i,j)*(-1)^j
    dadu[j+1,i] -= bcoeff*(-1)^j
    bcoeff *= (i-j)/(j+1)
#    println("i: ",i," j: ",j," a_i: ",a_n[j+1])
  end
end
# Now, compute the g_n coefficients and propagate derivatives:
for j=t.n:-1:2
  if j >= t.n-1
    t.g_n[j+1] = a_n[j+1]/(j+2)
    for i=1:t.n
      t.dgdu[j+1,i] = dadu[j+1,i]/(j+2)
    end
  else
    t.g_n[j+1] = a_n[j+1]/(j+2)+t.g_n[j+3]
    for i=1:t.n
      t.dgdu[j+1,i] = dadu[j+1,i]/(j+2) + t.dgdu[j+3,i]
    end
  end
end
if t.n >= 3
  t.g_n[2] = a_n[2]+3*t.g_n[4]
  for i=1:t.n
    t.dgdu[2,i] = dadu[2,i] + 3*t.dgdu[4,i]
  end
else
  t.g_n[2] = a_n[2]
  for i=1:t.n
    t.dgdu[2,i] = dadu[2,i]
  end
end
if t.n >= 2
  t.g_n[1] = a_n[1]+2*t.g_n[3]
  for i=1:t.n
    t.dgdu[1,i] = dadu[1,i] + 2*t.dgdu[3,i]
  end
else
  t.g_n[1] = a_n[1]
  for i=1:t.n
    t.dgdu[1,i] = dadu[1,i]
  end
end
return
end


# Attempting to use finitediff instead of autodiff for linearizations
using FiniteDifferences
x0, u0 = getXU_0(j);

m = central_fdm(10,1)
A1 = FiniteDifferences.jacobian(m, z->fxdot(z,u0,j,g),x0)[1]
B1 = FiniteDifferences.jacobian(m, z->fxdot(x0,z,j,g),u0)[1]

A2, B2 = linearizeDiff(x0,u0,j)
##
println()
println("Aerr = ", norm(A1-A2))

println("Berr = ", norm(B1 - B2))


compute(n::Int)::Int = (((4n + 3)n + 8)n - 9) ÷ 6


module AccurateArithmetic

# Adapted from AccurateArithmetic.jl

export two_sum, apply_offset

@inline function two_sum(a, b)
    hi = a + b
    a1 = hi - b
    b1 = hi - a1
    lo = (a - a1) + (b - b1)
    return hi, lo
end

@inline function two_hilo_sum(a, b)
    hi = a + b
    lo = b - (hi - a)
    return hi, lo
end

function four_sum(a, b, c, d)
    t0, t1 = two_sum(a,  b)
    t2, t3 = two_sum(c,  d)
    hi, t4 = two_sum(t0, t2)
    t5, lo = two_sum(t1, t3)
    hm, ml = two_sum(t4, t5)
    ml, lo = two_hilo_sum(ml, lo)
    hm, ml = two_hilo_sum(hm, ml)
    hi, hm = two_hilo_sum(hi,hm)
    return hi, hm, ml, lo
end

function handle_infinity(fraction)
    second = ifelse(fraction < 0, typemin(Int64), typemax(Int64))
    return second, fraction, zero(fraction)
end

function apply_offset(s1::Int64, f1, e1, s2::Int64, f2, e2)
    isfinite(f1 + f2) || return handle_infinity(f1 + f2)

    sum, residual, _ = four_sum(f1, f2, e1, e2)
    int_seconds = floor(Int64, sum)
    second = s1 + s2 + int_seconds
    fraction = sum - int_seconds
    return second, fraction, residual
end

function apply_offset(s1::Int64, f1, e1, offset)
    isfinite(f1 + offset) || return handle_infinity(f1 + offset)

    s2 = floor(Int64, offset)
    f2 = offset - s2
    return apply_offset(s1, f1, e1, s2, f2, 0.0)
end

end


using DocStringExtensions
import Base
using DataStructures

"""
A node represents either a number or operator.
  If number, then the node is a leaf.
  If operator, then it has left and right nodes.
A mathematical expression can be represented by a binary tree of the nodes.
E.g.,
(1 + 2) * (7 - 2) is represented by

*
├─ +
│  ├─ 1
│  └─ 2
└─ -
   ├─ 7
   └─ 2
"""
struct Node
    value::Rational
    str::String
    isnumber::Bool
    left::Union{Node,Nothing}
    right::Union{Node,Nothing}

    Node(x::Rational) = new(x, replace(string(x), r"//" => "/"),
                            true, nothing, nothing)
    Node(x::Int) = new(Rational(x), string(x), true, nothing, nothing)
    Node(op, left::Node, right::Node) = new(op(left.value, right.value),
                                            op == (//) ? "/" : string(op),
                                            false, left, right)
end

Base.:+(a::Node, b::Node) = Node(+, a, b)
Base.:-(a::Node, b::Node) = Node(-, a, b)
Base.:*(a::Node, b::Node) = Node(*, a, b)
Base.:/(a::Node, b::Node) = Node(//, a, b)

"""
Convert a tree into a mathematical expression
"""
function toexpr(node::Node, opprev::String, isright::Bool)::String
    if node.isnumber
        if occursin("/", node.str)
            # rational number should be treated as a chunk
            return "($(node.str))"
        else
            return node.str
        end
    end

    paren = needparen(opprev, node.str, isright)
    ret = ""
    paren && (ret *= "(")
    ret *= toexpr(node.left, node.str, false)
    ret *= " $(node.str) "
    ret *= toexpr(node.right, node.str, true)
    paren && (ret *= ")")
    ret
end

toexpr(node::Node) = toexpr(node, "", false)

needparen(opprev::String, op::String, isright::Bool) =
    (op=="/" && opprev in ("*","/") && isright) ||
    (op in ("+", "-") && opprev in ("*","/")) ||
    (op in ("+", "-") && opprev=="-" && isright)

#===================================================================================#

"""
Reduction iterator, given a set of nodes, generates a set of nodes reduced by one-step computation
"""
struct Reductions
    ctr::Accumulator{Node,Int}
    nodes::Vector{Node}
    counts::Vector{Int}

    function Reductions(ctr::Accumulator{Node,Int})
        nodes = [k for k in keys(ctr)]
        counts = [v for v in values(ctr)]
        new(ctr, nodes, counts)
    end
    Reductions(xs::Node...) = Reductions(counter(xs))
end

function Base.iterate(r::Reductions, state::Int)
    n = length(r.nodes)  # number of unique nodes
    (state >= 4*n*n) && return nothing

    k = div(state, n*n)
    i = div(state-n*n*k, n)
    j = state-n*n*k-n*i
    i += 1; j += 1; k += 1

    n1, n2 = r.nodes[i], r.nodes[j]
    op = (+,-,*,/)[k]

    # invalid cases
    # same node, but count is smaller than 2
    (i==j) && (r.counts[i] < 2) && return iterate(r, state+1)
    # + and * are order independent
    (i>j) && (op in (+,*)) && return iterate(r, state+1)
    # zero division
    (n2.value==Rational(0)) && (op == (/)) && return iterate(r, state+1)

    remain = copy(r.ctr)
    n = op(n1, n2)
    inc!(remain, n)
    dec!(remain, n1)==0 && delete!(remain.map, n1)
    dec!(remain, n2)==0 && delete!(remain.map, n2)
    remain, state+1
end

Base.iterate(r::Reductions) = iterate(r, 0)

#===================================================================================#

"""
Extract only numbers from a set of nodes
"""
valueonly(xs::Accumulator{Node,Int}) = Set(Pair(n.value,c) for (n,c) = xs)

const NOTFOUND = "NOT FOUND"

"""
Solver of a ten puzzle
"""
function solve!(xs::Accumulator{Node,Int};
                tgt=Rational(10), failed=Set{Set{Pair{Rational,Int}}}())::String
    valueonly(xs) in failed && return NOTFOUND

    if (sum(xs)==1)
        node = collect(keys(xs))[1]
        return node.value==tgt ? toexpr(node) : NOTFOUND
    end
    for ys = Reductions(xs)
        ans = solve!(ys, tgt=tgt)
        ans == NOTFOUND ? push!(failed, valueonly(ys)) : return ans
    end
    NOTFOUND
end
#===================================================================================#

"""
$(SIGNATURES)

Make ten (or given target value) using given numbers and four arithmetic operations

#Examples
```julia-repl
julia> maketen(1,1,9,9)
"(1 / 9 + 1) * 9"
```
"""
maketen(xs::Union{Int,Rational}...; tgt=10) = solve!(counter(Node.(xs)), tgt=Rational(tgt))


#===============================================================================
 Robust NonMonotone Line search
 From DF-Sane paper https://www.ams.org/journals/mcom/2006-75-255/S0025-5718-06-01840-0/S0025-5718-06-01840-0.pdf
 This line search doesn't fit the rest of the API so I need to come up with an
 interface for nonmonotone line searches (it has to accept an fbar (or Q in Hager
 Zhang's notion) and a forcing term)
===============================================================================#
struct RNMS end

function find_steplength(rnms::RNMS, φ, φ0, fbar, ηk::T, τmin, τmax) where T
  α₊ = T(1)
  α₋ = α₊
  γ = T(1)/10000
  for k = 1:100
    φα₊ = φ(α₊)
    if φα₊ ≤ fbar + ηk - γ*α₊^2*φ0
      return α₊, φα₊
    end
    φα₋ = φ(-α₋)
    if φα₋ ≤ fbar + ηk - γ*α₋^2*φ0
      return -α₋, φα₋
    end

    # update alpha+ and alpha-
    αt = α₊^2*φ0/(φα₊ + (2*α₊-1)*φ0)
    α₊ = clamp(αt, τmin*α₊, τmax*α₊)

    αt = α₋^2*φ0/(φα₋ + (2*α₋-1)*φ0)
    α₋ = clamp(αt, τmin*α₋, τmax*α₋)
  end
  throw(ErrorException("Line search failed."))
end

function safeguard_σ(σ::T, σmin, σmax, F) where T
  if abs(σ) < σmin || abs(σ) > σmax
    normF = norm(F)
    if normF > T(1)
      σ = T(1)
    elseif T(1)/10^5 ≤ normF ≤ T(1)
      σ = inv(normF)
    elseif normF < T(1)/10^5
      σ = T(10)^5
    end
  end
  return σ
end

struct DFSANE end
function solve(prob::NEqProblem, x0, method::DFSANE, options::NEqOptions)
  if !(mstyle(prob) === InPlace())
    throw(ErrorException("solve() not defined for OutOfPlace() with DFSANE"))
  end

  t0 = time()
  F = prob.R.F
  T = eltype(x0)

  σmin, σmax = 1e-5, 1e5
  τmin, τmax = T(1)/10, T(1)/2
  M = 4
  nexp = 2

  x = copy(x0)
  ρs = norm(x)
  Fx = copy(x0)
  Fx = F(Fx, x)
  y = copy(Fx)
  ρ2F0 = norm(Fx, 2)
  ρF0 = norm(Fx, Inf)
  fx = ρ2F0^nexp
  fvals = [fx]

  abstol = 1e-5
  reltol = 1e-8

  σ₀ = T(1)
  σ = safeguard_σ(σ₀, σmin, σmax, Fx)
  iter = 0
  while iter < options.maxiter
    iter += 1
    z = copy(x) # FIXME
    push!(fvals, fx)
    if length(fvals) > M
      popfirst!(fvals)
    end
    fbar = maximum(fvals)
    d = -σ*Fx
    ηk = ρ2F0/(1+iter)^2
    φ(α) = norm(F(Fx, x.+α.*d))^nexp
    φ0 = fx
    α, φα = find_steplength(RNMS(), φ, φ0, fbar, ηk, τmin, τmax)
    s = α*d
    ρs = norm(s)
    x .= x.+s
    y .= -Fx
    Fx = F(Fx, x)
    y .+= Fx
    ρ2fx = norm(Fx, 2)
    ρfx = norm(Fx ,Inf)
    fx = ρ2fx^nexp
    σ = norm(s)^2/dot(s, y)
    σ = safeguard_σ(σ, σmin, σmax, Fx)
    # use sqrt(length(x))*abs or abstol?
    if ρfx < abstol + reltol*ρF0
      break
    end
  end
  ConvergenceInfo(method, (solution=x, best_residual=Fx, ρF0=ρF0, ρ2F0=ρ2F0, ρs=ρs, iter=iter, time=time()-t0), options)
end


using Polynomials

p = Poly([-42,0,-12,1])
q = Poly([-3,1])

d, r = divrem(p,q)

println(p, " divided by ", q, " is ", d, " with remainder ", r, ".")


function integrate_trapz_7(f, dr)
    N = size(dr, 1)
    g = f .* dr
    s = (  36799 * ( g[1] + g[N  ] ) +
          176648 * ( g[2] + g[N-1] ) +
           54851 * ( g[3] + g[N-2] ) +
          177984 * ( g[4] + g[N-3] ) +
           89437 * ( g[5] + g[N-4] ) +
          130936 * ( g[6] + g[N-5] ) +
          119585 * ( g[7] + g[N-6] )
    ) / 120960
    for i in 8:N-7
        s = s + g[i]
    end
    return s
end


function spharm( l, m, Gvec, cost, sint, phi )

  Ng = size(Gvec)[2]

  ylm = zeros( Float64, Ng )

  if l == 0
    for ig = 1:Ng
      ylm[ig] = 0.5*sqrt(1.0/pi)
    end
    return ylm

  elseif l == 1
    # py
    if m == -1
      for ig = 1:Ng
        ylm[ig] = 0.5*sqrt(3/pi)*sint[ig]*sin(phi[ig])
      end
      return ylm
    # pz
    elseif m == 0
      for ig = 1:Ng
        ylm[ig] = 0.5*sqrt(3/pi)*cost[ig]
      end
      return ylm
    # px
    elseif m == 1
      for ig = 1:Ng
        ylm[ig] = 0.5*sqrt(3/pi)*sint[ig]*cos(phi[ig])
      end
      return ylm
    end

  elseif l == 2
    # dxy
    if m == -2
      for ig = 1:Ng
        ylm[ig] = sqrt(15/16/pi)*sint[ig]^2*sin(2*phi[ig])
      end
      return ylm
    # dyz
    elseif m == -1
      for ig = 1:Ng
        ylm[ig] = sqrt(15/(4*pi))*cost[ig]*sint[ig]*sin(phi[ig])
      end
      return ylm
    # dz2
    elseif m == 0
      for ig = 1:Ng
        ylm[ig] = 0.25*sqrt(5/pi)*( 3*cost[ig]^2 - 1 )
      end
      return ylm
    # dxz
    elseif m == 1
      for ig = 1:Ng
        ylm[ig] = sqrt(15/4/pi)*cost[ig]*sint[ig]*cos(phi[ig])
      end
      return ylm
    # dx2-y2
    elseif m == 2
      for ig = 1:Ng
        ylm[ig] = 0.5*sqrt(15/4/pi)*sint[ig]^2*cos(2*phi[ig])
      end
      return ylm

    end

  elseif l == 3

    if m == -3
      for ig = 1:Ng
        ylm[ig] = 0.25*sqrt(35/2/pi)*sint[ig]^3*sin(3*phi[ig])
      end
      return ylm

    elseif m == -2
      for ig = 1:Ng
        ylm[ig] = 0.25*sqrt(105/pi)*sint[ig]^2*cost[ig]*sin(2*phi[ig])
      end
      return ylm

    elseif m == -1
      for ig = 1:Ng
        ylm[ig] = 0.25*sqrt(21/2/pi)*sint[ig]*( 5*cost[ig]^2 - 1 )*sin(phi[ig])
      end
      return ylm

    elseif m == 0
      for ig = 1:Ng
        ylm[ig] = 0.25*sqrt(7/pi)*( 5*cost[ig]^3 - 3*cost[ig] )
      end
      return ylm

    elseif m == 1
      for ig = 1:Ng
        ylm[ig] = 0.25*sqrt(21/2/pi)*sint[ig]*( 5*cost[ig]^2 - 1 )*cos(phi[ig])
      end
      return ylm

    elseif m == 2
      for ig = 1:Ng
        ylm[ig] = 0.25*sqrt(105/pi)*sint[ig]^2*cost[ig]*cos(2*phi[ig])
      end
      return ylm

    elseif m == 3
      for ig = 1:Ng
        ylm[ig] = 0.25*sqrt(35/2/pi)*sint[ig]^3*cos(3*phi[ig])
      end
      return ylm

    end

  end

end


facts("CANDECOMP") do
r = 2
T = rand_kruskal3(r, (10, 20, 30), false)

context("Incorrect method") do
  @fact_throws candecomp(T, r, compute_error=true, method=:ALdS) ArgumentError
end

context("ALS (Alternating least squares)") do
    @time factors = candecomp(T, r, compute_error=true, method=:ALS)
    @fact length(factors.factors) --> ndims(T)
    @fact map(size, factors.factors) --> (collect(zip(size(T), (r, r, r)))...)
    @fact rank(factors) --> r
    @fact rel_residue(factors) --> less_than(1e-5)
end

context("SGSD (Simultaneous generalized Schur decomposition)") do
    @time factors = candecomp(T, r, compute_error=true, method=:SGSD)
    @fact length(factors.factors) --> ndims(T)
    @fact map(size, factors.factors) --> (collect(zip(size(T), (r, r, r)))...)
    @fact rank(factors) --> r
    @fact length(factors.lambdas) --> r
    @fact rel_residue(factors) --> less_than(1e-5)
end

end


# Cells that are commented out ( that is, starting with `#` ) are supposed to be run once, if the cells underneath are not running correctly.

## using Pkg
## pkg"add PyCall RCall"
#----------------------------------------------------------------------------

# This is loading the `PyCall` and `RCall` packages.

using PyCall,RCall;
#----------------------------------------------------------------------------

# We can inspect the names exported by a Package with the `names` function. We use the `show` function to view the full output instead of the truncated one.

show(names(RCall))
#----------------------------------------------------------------------------

# The documentation string of a function can be viewed by prefixing a `?`

?@rput
#----------------------------------------------------------------------------

# ## Using Python from Julia

# Python code can be written via the python string macro ( `@py_str` ) as shown below.

py"""
def fib(n):
    if n<2:
        return 1
    x,y = 1,1
    for i in range(n-2):
        x,y = y,x+y
    return y
"""
#----------------------------------------------------------------------------

# Python libraries can be imported in julia via the `@pyimport` macro.

@pyimport numpy
#----------------------------------------------------------------------------

##Pkg.add("PyPlot");
#----------------------------------------------------------------------------

using PyPlot ## that's using matplotlib from julia
#----------------------------------------------------------------------------

# The multiline string version ( `py""" ... """` ) does return `nothing` whereas the inline string version  ( `py" ... "` ) returns the last expression.

py"""
import numpy as np
X = np.arange(1,30)
Y = np.array([fib(x) for x in X])
ax.scatter(X, Y)
ax.set(xlabel='n', ylabel='fib(n)',
       title='The Fibonacci sequence grows fast !')
"""
X, Y = py"X", py"Y"
function fibplot( x, y )
    scatter( x, y )
    xlabel("n")
    ylabel("fib(n)")
    title("The Fibonacci sequence grows fast !")
end
fibplot( X, Y );
#----------------------------------------------------------------------------

# ## Using R from Julia
# Here we are passing the arrays that we created with python and plotted in Julia to R

@rput X Y;
#----------------------------------------------------------------------------

R"""
my_summary = summary(lm(log(Y)~X))
val <- my_summary$coefficients

plot(X, log(Y))
abline(my_summary)
""";
#----------------------------------------------------------------------------

# ## Writing, compiling and executing C from Julia

# And now we write and compile the same function in C and wrap it in Julia

## Pkg.add("Libdl")
using Libdl
C_code = """
int cfib(int n)
{
    int res=0;
    if (n <= 2){
        res = 1;
    } else {
        res = cfib(n-1)+cfib(n-2);
    }
    return res;
}
"""

const Clib = tempname()   ## make a temporary file

## compile to a shared library by piping C_code to gcc
## (works only if you have gcc installed):

open(`gcc -fPIC -O3 -msse3 -xc -shared -o $(Clib * "." * Libdl.dlext) -`, "w") do f
    print(f, C_code)
end

## define a Julia function that calls the C function:
c_fib(X::Int) = ccall(("cfib", Clib), Int, (Int,), X)
#----------------------------------------------------------------------------

x = 1:30
y = [c_fib(ξ) for ξ in x] ## ξ is \xi<tab>
fibplot( x, y );
#----------------------------------------------------------------------------


using DataFrames, CSV, Dates, Statistics, CategoricalArrays
data = dropmissing(CSV.read("data/purchaseData.csv", DataFrame))

data[!,:Date] = Date.(data[!,:Date], "d/m/y")
println(first(sort(data, :Date), 3),"\n")

println(first(filter(row -> row[:Price] > 50000, data),3 ),"\n")

#categorical!(data, :Grade)
data[!, :Grade] = categorical(data[!, :Grade])
println(first(data, 3), "\n")

println(
#    by(data, :Grade, :Price =>
#        x -> ( NumSold=length(x), AvgPrice=mean(x)) )
    combine(
        groupby(data, :Grade),
        :Price=> (x -> [length(x) mean(x)]) => [:NumSold, :AvgPrice]
        )
)


@testset "786.k-th-smallest-prime-fraction.jl" begin
    res = [1//5, 1//3, 2//5, 1//2, 3//5, 2//3]
    arr = [1,2,3,5]
    for i in 1:6
        @test kth_smallest_prime_fraction(arr, i) == res[i]
    end
    @test kth_smallest_prime_fraction([1, 7], 1) == 1//7
    @test kth_smallest_prime_fraction([1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47], 42) == 7//37
    @test kth_smallest_prime_fraction([1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47], 24) == 3//29
    @test kth_smallest_prime_fraction([1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47], 13) == 1//17
end


# downsample.jl
# Copyright 2019-03-05, Jeff Fessler, University of Michigan

using Printf
using Test

"""
`y = downsample1(x, down; warn=true)`
downsample by factor m along first dimension by averaging

in
* x	[n1 (Nd)]
* down			integer downsampling factor

option
`warn`	warn if noninteger multiple; default true

out
* y	[n1/down (Nd)]

Copyright 2019-03-05, Jeff Fessler, University of Michigan
"""
function downsample1(x, down::Integer; warn::Bool=true)

	dim = size(x)
	dim1 = dim[1]
	x = reshape(x, dim1, :) # [n1 *Nd]
	m1 = Int(floor(dim1 / down))
	if m1 * down < dim1
		if warn
			@warn(@sprintf("truncating input size %d to %d", dim1, m1 * down))
		end
		x = x[1:(m1*down),:]
	end
	y = reshape(x, down, :)
	y = sum(y, dims=1) / down # mean
	y = reshape(y, m1, dim[2:end]...)
	return y
end

"""
downsample1_test()
"""
function downsample1_test()
	x = reshape(2:2:48, 4, 6)
	y = downsample1(x, 2)
	@test y == reshape(3:4:47, 2, 6)
	true
end

"""
`y = downsample2(x, down, warn=true)`

downsample by averaging by integer factors
in
* `x` [nx ny]
* `down` can be a scalar (same factor for both dimensions) or a 2-vector

option
* `warn`	warn if noninteger multiple; default true

out
`y`	[nx/down ny/down]
"""
function downsample2(x, down; warn::Bool=true)
	fun = (x, d) -> downsample1(x, d, warn=warn)
	y = fun(x, down[1])
	y = fun(y', down[end])'
	return y
end

"""
`downsample2_test()`
"""
function downsample2_test()
	x = reshape(1:24, 4, 6)
	y = downsample2(x, 2)
	@test y ==  [3.5 11.5 19.5; 5.5 13.5 21.5]
	true
end

"""
`y = downsample3(x, down, warn=true)`

downsample by averaging by integer factors
in
* `x` [nx ny nz]
* `down` can be a scalar (same factor for all dimensions) or a 3-vector

option
* `warn`	warn if noninteger multiple; default true

out
`y`	[nx/down ny/down nz/down]
"""
function downsample3(x, down; warn::Bool=true)

	if ndims(x) == 2
		@warn("2d case")
		if length(down) == 1; down = [down, down, 1]; end
		length(down) != 3 & throw(DimensionMismatch("$(length(down)) != 3"))&
		return downsample2(x, down[1:2])
	end

	ndims(x) != 3 && throw(DimensionMismatch("ndims(x)=$(ndims(x)) != 3"))

	if length(down) == 1
		down = [down,down,down]
	end

	length(down) != ndims(x) && throw(DimensionMismatch("length(down) != ndims(x)"))

	# downsample along each dimension
	y = downsample1(x, down[1])
	y = downsample1(permutedims(y, [2, 1, 3]), down[2])
	y = downsample1(permutedims(y, [3, 2, 1]), down[3]) # [3 1 2] order
	y = permutedims(y, [2, 3, 1])
	return y
end

function downsample3_test()
	x = [6, 5, 2]
	x = reshape(2*(1:prod(x)), x...)
	for down = 1:2
		y = downsample3(x, down)
		if down == 1
			@test y == x
		elseif down == 2
			@test y == reshape([39 63; 43 67; 47 71], 3,2,1) # squeeze
		end
	end
	true
end

#=
if has_aspire # todo
	filex = [test_dir filesep 'testx.fld']
	filey = [test_dir filesep 'testy.fld']
	fld_write(filex, x)
	delete(filey)
	com = ['op sample3 mean ' filey ' ' filex ' %d %d %d']
	com = sprintf(com, down, down, down)
	os_run(com)
	z = fld_read(filey)
	!isequal(y, z) && throw("aspire/matlab mismatch")
end
=#

"""
`downsample(:test)` self test
"""
function downsample(test::Symbol)
	test != :test && throw(ArgumentError("test $test"))
	downsample1_test()
	downsample2_test()
	downsample3_test()
	true
end


export HSegment, RandomHSegment, endpoints, collinear, bisector

"""
`HSegment(A,B)` creates a new line segment with endpoints `A` and `B`.
"""
struct HSegment <: HObject
    A::HPoint
    B::HPoint
    attr::Dict{Symbol,Any}
    function HSegment(a::HPoint,b::HPoint)
        @assert a!=b "The end points of a segment must be distinct"
        S = new(a,b,Dict{Symbol,Any}())
        set_color(S)
        set_thickness(S)
        set_line_style(S)
        return S
    end
end
HSegment(S::HSegment) = HSegment(S.A, S.B)  # copy constructor
HSegment(a::Number, B::HPoint) = HSegment(HPoint(a), B)
HSegment(A::HPoint, b::Number) = HSegment(A, HPoint(b))
HSegment(a::Number, b::Number) = HSegment(HPoint(a), HPoint(b))
HSegment(A::HPoint) = HSegment(A, HPoint())
HSegment(a::Number) = HSegment(HPoint(a), HPoint())

(+)(A::HPoint, B::HPoint) = HSegment(A,B)

function show(io::IO, L::HSegment)
    p,q = endpoints(L)
    a = getz(p)
    b = getz(q)
    print(io,"HSegment($a,$b)")
end

endpoints(L::HSegment) = [L.A, L.B]

RandomHSegment() = HSegment(RandomHPoint(), RandomHPoint())

function (==)(L::HSegment, LL::HSegment)
    return (endpoints(L)==endpoints(LL)) || (endpoints(L)==(LL.B,LL.A))
end

midpoint(L::HSegment) = midpoint(endpoints(L)...)
length(L::HSegment) = dist(endpoints(L)...)

"""
`collinear` checks if the arguments are collinear. Arguments are:
+ `a,b,c`: three points
+ `a,L`: point and segment (in either order)
+ `L,LL`: two segments
"""
function collinear(a::HPoint, b::HPoint, c::HPoint)::Bool
    if a==b || b==c || a==c
        return true
    end
    aa = reflect_across(a,HSegment(b,c))
    return aa == a
end

collinear(a::HPoint, L::HSegment) = collinear(a,endpoints(L)...)
collinear(L::HSegment, a::HPoint) = collinear(a,L)

function collinear(L::HSegment, LL::HSegment)
    a,b = endpoints(LL)
    return collinear(a,L) && collinear(b,L)
end

function in(a::HPoint, L::HSegment)
    x,y = endpoints(L)
    return between(x,a,y)
end

"""
`bisector(S::HSegment)` yields an `HLine` that's the perpendicular
bisector of the segment `S`. May also be invoked `bisector(A,B)`
where `A` and `B` are points.
"""
function bisector(S::HSegment)
    A,B = endpoints(S)
    @assert A != B "The end points of the segment must be distinct"
    P = midpoint(S)
    f = move2xplus(P,B)
    g = inv(f)
    zs = g(im)
    zt = g(-im)

    return HLine(angle(zs), angle(zt))
end

bisector(A::HPoint, B::HPoint) = bisector(A+B)


#=
Created on Tuesday 28 January 2020
Last update: -

@author: Michiel Stock
michielfmstock@gmail.com

Implementation of MST
=#

module MST

using DataStructures

using STMO

export prim, kruskal

"""
    prim(vertices::Vertices{T}, edges::WeightedEdgeList{R,T},
                start::T) where {R<:Real, T}

Prim's algorithm for finding the minimum spanning tree. Inputs the vertices
(`vertices`), a list of weighted edges (`vertices`) and a starting vertex (`start`).
"""
function prim(vertices::Vertices{T}, edges::WeightedEdgeList{R,T},
                start::T) where {R<:Real, T}
    u = start
    adjlist = edges2adjlist(edges)
    mst_edges = eltype(edges)[]
    mst_vertices = Set{T}([u])
    edges_to_check = [(w, u, n) for (w, n) in adjlist[u]]
    cost = zero(R)
    heapify!(edges_to_check)
    while length(mst_edges) < length(vertices) && length(edges_to_check) > 0
        # pop shortest edge, u part of tree, v might be new
        w, u, v = heappop!(edges_to_check)
        if v ∉ mst_vertices
            # add to MST
            push!(mst_edges, (w, u, v))
            push!(mst_vertices, v)
            # update cost
            cost += w
            # add neighbours of v
            for (wn, n) in adjlist[v]
                n ∉ mst_vertices && heappush!(edges_to_check, (wn, v, n))
            end
        end
    end
    return mst_edges, cost
end

"""
Prim's algorithm for finding a minimum spanning tree, chooses a reandom starting
node.
"""
prim(vertices::Vertices{T}, edges::WeightedEdgeList{R,T})  where {R<:Real, T} =
                prim(vertices, edges, rand(vertices))

"""
    kruskal(vertices::Vertices{T}, edges::WeightedEdgeList{R,T}) where {R<:Real, T}

Kruskal's algorithm for finding the minimum spanning tree. Inputs the vertices
(`vertices`) and a list of weighted edges (`vertices`).
"""
function kruskal(vertices::Vertices{T}, edges::WeightedEdgeList{R,T}) where {R<:Real, T}
    usf = DisjointSets(vertices)
    mst_edges = eltype(edges)[]
    mst_vertices = Set{T}()
    sort!(edges)
    cost = zero(R)
    for (w, u, v) in edges
        if !in_same_set(usf, u, v)
            push!(mst_edges, (w, u, v))
            union!(usf, u, v)
            push!(mst_vertices, u)
            push!(mst_vertices, v)
            cost += w
            if length(mst_vertices) == length(vertices)
                break
            end
        end
    end
    return mst_edges, cost
end

end  # module MST


# # 150: Impedance calculation
# ([source code](SOURCE_URL))
#
#  Impedance calculation for
#
#    C u_t - (D u_x)_x + Ru = 0   in (0,1)
#      u(0,t)=1 + exp(iωt)
#      u(1,t)=0
#
#    Measurement: I(t)= D u_x(1,t)
#
#    Steady state:
#    - (D u0_x)_x + Ru0 = 0
#    u0(0,t)=1
#    u0(1,t)=0
#
#    Small signal ansatz for ω
#
#    u(x,t)= u0(x)+ ua(x) exp(iωt)
#
#    iωC ua - (D ua_x)_x + R u_a =0
#      ua(0)=1
#      ua(1)=0
#
#

module Example150_Impedance1D

using Printf
using VoronoiFVM
using ExtendableGrids
using GridVisualize

function main(;nref=0,Plotter=nothing,verbose=false, unknown_storage=:sparse)

    L=1.0

    # Create array which is refined close to 0
    h0=0.1/2.0^nref
    h1=0.5/2.0^nref
    X=VoronoiFVM.geomspace(0.0,L,h0,h1)

    # Create discretitzation grid
    grid=VoronoiFVM.Grid(X)

    # Create and fill data
    data = (R=1, D=1, C=2)

    # Declare constitutive functions
    flux=function(f,u0,edge,data)
        u=unknowns(edge,u0)
        f[1]=data.D*(u[1,1]-u[1,2])
    end

    storage=function(f,u,node,data)
        f[1]=data.C*u[1]
    end

    reaction=function(f,u,node,data)
        f[1]=data.R*u[1]
    end

    # Create physics struct
    physics=VoronoiFVM.Physics(data=data,
                               flux=flux,
                               storage=storage,
                               reaction=reaction
                               )
    # Create discrete system and enabe species
    sys=VoronoiFVM.System(grid,physics,unknown_storage=unknown_storage)
    enable_species!(sys,1,[1])

    # Create test functions for current measurement

    excited_bc=1
    excited_bcval=1.0
    excited_spec=1

    factory=VoronoiFVM.TestFunctionFactory(sys)
    measurement_testfunction=testfunction(factory,[1],[2])

    boundary_dirichlet!(sys,excited_spec,excited_bc,excited_bcval)
    boundary_dirichlet!(sys,1,2,0.0)

    inival=unknowns(sys)
    steadystate=unknowns(sys)
    inival.=0.0
    solve!(steadystate,inival,sys)

    function meas_stdy(meas,U)
        u=reshape(U,sys)
        meas[1]=VoronoiFVM.integrate_stdy(sys,measurement_testfunction,u)[1]
        nothing
    end

    function meas_tran(meas,U)
        u=reshape(U,sys)
        meas[1]=VoronoiFVM.integrate_tran(sys,measurement_testfunction,u)[1]
        nothing
    end

    dmeas_stdy=measurement_derivative(sys,meas_stdy,steadystate)
    dmeas_tran=measurement_derivative(sys,meas_tran,steadystate)

    # Create Impeadancs system from steady state
    excited_spec=1
    excited_bc=1
    isys=VoronoiFVM.ImpedanceSystem(sys,steadystate,excited_spec, excited_bc)

    # Prepare recording of impedance results
    allomega=zeros(0)

    # for calculated data
    allI0=zeros(Complex{Float64},0)
    allIL=zeros(Complex{Float64},0)

    # for exact data
    allIx0=zeros(Complex{Float64},0)
    allIxL=zeros(Complex{Float64},0)

    ω0=0.5
    ω1=1.0e4
    ω=ω0

    testval=0.0
    UZ=unknowns(isys)
    while ω<ω1

        iω=1im*ω

        # solve impedance system
        solve!(UZ,isys,ω)

        # calculate aproximate solution
        # obtain measurement in frequency  domain
        IL=freqdomain_impedance(isys,ω,steadystate,excited_spec,excited_bc,excited_bcval,dmeas_stdy, dmeas_tran)

        # record approximate solution
        push!(allomega, ω)
        push!(allIL,IL)

        # record exact solution
        z=sqrt(iω*data.C/data.D+data.R/data.D);
        eplus=exp(z*L);
        eminus=exp(-z*L);
        IxL=2.0*data.D*z/(eminus-eplus);
        push!(allIxL,IxL)

        # increase omega
        ω=ω*1.2

    end

    p=GridVisualizer(Plotter=Plotter)
    scalarplot!(p,real(allIL),imag(allIL),label="calc",color=:red)
    scalarplot!(p,real(allIxL),imag(allIxL),label="exact",show=true,clear=false,color=:blue)
    return  imag(allIL[5])
end

function test()
    main(unknown_storage=:dense) ≈ 0.23106605162049176 &&  main(unknown_storage=:sparse) ≈ 0.23106605162049176
end

end


"""
    row_mul(v::AbstractArray, A::AbstractMatrix)
Multiply all the rows of a matrix `A` by some vector `v` element by element.

"""
function row_mul(v::AbstractVector, A::AbstractMatrix)

    #Error
    if length(v) != size(A,2)
        return ErrorException("The number of colums of the matrix ($(size(A,2))) must be equal to the length of the vector ($(length(v)))" )
    end

    B = similar(A)

    @simd for i in 1:size(A,1)
        @simd for j in 1:size(A,2)
            @inbounds B[i,j] = v[j]*A[i,j]
        end
    end

    return B
end

"""
    rand_exclusive(v::AbstractArray,n::Integer)
Returns an array with `n` different random elements of the array `v`.
"""
function rand_exclusive(v::AbstractVector,n::Integer)

    #Error checks
    if n>length(v)
        return ErrorException("Length of the array `v` ($(length(v))) must be less or equal than `n` ($n)")
    end

    a = Array{Number}(undef,n)
    a[1] = rand(v)

    for i in 2:n
        tmp = rand(v)
        while tmp in a[1:i-1]
            tmp = rand(v)
        end
        a[i] = tmp
    end
    return a
end

function best_individue(fob::Function,A::AbstractMatrix)
    vals = findmin(@inbounds [fob(A[i,:]...) for i in 1:size(A,1)])
    return (vals[1],A[vals[2],:])
end

"""
    rand_cauchy_trunc(μ::Real, c::Real)
Returns a random number with a *Cauchy's distribution* with **mean `μ`** and truncated between (0,1]. If the value calculated exceds 1, the returned value is 1.
If the value is less or equal to zero, the value es calculated again.
"""
function rand_cauchy_trunc(μ::Real, c::Real)
    r = c*tan(pi*(rand()-0.5)) + μ
    if r > 1
        return 1
    elseif r <= 0
        return rand_cauchy_trunc(μ::Real, c::Real)
    end
    return r
end

"""
    rand_normal_trunc(μ::Real, σ::Real)
Returns a random number with a *normal* distribution with **mean `μ`** and **standard deviation `σ`** truncated between [0,1].
"""
function rand_normal_trunc(μ::Real, σ::Real)
    r = σ*randn() + μ
    if r > 1
        return 1
    elseif r < 0
        return 0
    end
    return r
end

"""
    Lehmer_mean(X::AbstractArray,p::Real)
Return the **Lehmer mean** of degree `p` of an array `X`.
"""
function Lehmer_mean(X::AbstractVector, p::Real)
    return sum(X.^p)/sum(X.^(p-1))
end

"""
    pbest(fob::Function, A::AbstractMatrix, p::Real)
Return the top 100*p% best individues of a population evaluated in a function `fob`.
"""
function pbest(fob::Function, A::AbstractMatrix, p::Real)
    len = size(A,1)
    plen = floor(Integer,p*len)
    ppop = sortperm(@inbounds [fob(A[i,:]...) for i in 1:len])[1:plen]
    return A[ppop,:]
end


# Brusselator figure
using DrWatson
@quickactivate "NonlinearDynamicsTextbook"
include(srcdir("style.jl"))
using DynamicalSystems, PyPlot

function brusselator_f(x, p, t)
    u, v = x
    a, b = p
    du = 1 - (b+1)*u + a*u^2*v
    dv = b*u - a*u^2*v
    return SVector(du, dv)
end

a = 0.3; b = 1.5
p0 = [a, b]
u0 = [2.0, 2.0]

ds = ContinuousDynamicalSystem(brusselator_f, u0, p0)

Δt = 0.001
T = 100
tr = trajectory(ds, T; Δt, Ttr = 100)
t = 0:Δt:T
u, v = columns(tr)

period = estimate_period(u, :zerocrossing, t)
ip = round(Int, period/Δt)

fig = figure(figsize = (figx/2, figy))
ax = gca()

sc = ax.scatter(u[1:ip], v[1:ip]; c = range(0, 2π, length = ip), cmap = :twilight, s = 20)
cb = colorbar(sc)
cb.set_ticks([0, 2π])
cb.set_ticklabels(["\$0\$", "\$2\\pi\$"])
cb.set_label("\$\\phi\$", labelpad = -30)
ax.set_xticks([0.5, 2.0])
ax.set_yticks([3, 5])
ax.set_xlabel("\$u\$", labelpad = -20)
ax.set_ylabel("\$w\$", labelpad = -10)
ax.plot(1, b/a; marker = "o", mec = "C2", mew = 2,
markersize = 10, mfc = :white)

r = ip ÷ 10
ax.scatter(u[1:r:ip], v[1:r:ip]; color = "C2", marker = "D", s = 150, edgecolors="white")

fig.tight_layout(pad=0.3)

# wsave(plotsdir("2", "brusselator"), fig)


### FixedDAryTree

"""
A fixed full-d-ary tree, i.e., in a tree each node has either d or 0 children and each level is
full.
"""
struct FullDAryTree <: FiniteFixedTree
    spec::TreeSpec
    height::Int
end

"""
    FullDAryTree(height::Int, d::Int)

Construct a `FullDAryTree` of height `height` and maximum degree `d`.
"""
function FullDAryTree(height::Int, d::Int)
    if d > 1
        return FullDAryTree(TreeSpec((d^(height+1)-1)//(d-1), "Full $d-arry tree (height $height)", d), height)
    else
        return FullDAryTree(TreeSpec(height+1, "Full $d-arry tree (height $height)", d), height)
    end
end

# FixedDAryTree

degrees(tree::FullDAryTree) = tree
eltype(tree::FullDAryTree) = Int
length(tree::FullDAryTree) = size(tree)

function iterate(tree::FullDAryTree)
    stack = Stack{Tuple{Int, Int}}()
    if tree.height == 0
        return (0, stack)
    else
        push!(stack, (0, maxdegree(tree)))
        return (maxdegree(tree), stack)
    end
end

# We have to do a DFS to work out the degrees
function iterate(tree::FullDAryTree, stack)
    if isempty(stack)
        return nothing
    end

    d = maxdegree(tree)

    parent_height, child_left = pop!(stack)

    if child_left > 1
        push!(stack, (parent_height, child_left - 1))
    end

    current_height = parent_height += 1
    current_degree = 0

    if current_height < tree.height
        current_degree = d
        push!(stack, (current_height, d))
    end

    return (current_degree, stack)
end


using Pkg
Pkg.add("Distributions")
Pkg.add("Plots")
using Distributions
using Plots

days = 65

alive_plants = 1000

for i in 1:days
    days = i

    binom = Binomial(1, .999)

    live_dead = rand(binom, alive_plants)

    daily_dead = sum(live_dead[isequal.(live_dead, 0)])

    println("On day $days, $daily_dead plants died")

    alive_plants = alive_plants - daily_dead
end

alive_plants

alive_runs = Vector{Any}()

for j in 1:1000
    days = 65

    alive_plants = 1000

for i in 1:days
    days = i

    binom = Binomial(1, .999)

    live_dead = rand(binom, alive_plants)

    daily_dead = sum(live_dead[isequal.(live_dead, 0)])

    alive_plants = alive_plants - daily_dead
end

push!(alive_runs, alive_plants)

end

histogram(alive_runs)


const Orbits = [(1,0), (2,0), (0,1), (1,1), (0,2)]

"""
    flatten(pdm::Dict{Int, <:PersistenceDiagram}) -> PersistenceDiagram

Flatten a multi-dimensional pd into a barcode.
"""
flatten(pdm::Dict{Int, PD}) where {T<:Real, PD<:PersistenceDiagram{T}} =
    (collect∘Iterators.flatten∘values)(pdm)

"""
    maxsize(pds) -> Int

Return the number of bars in the largest barcode from the collection of barcodes `pds`
"""
maxsize(pds::AbstractVector{PD}) where {T<:Real, PD<:PersistenceDiagram{T}} =
    maximum(map(length, pds))

tropicdim(n) = n+(div(n*(n+1),2)) # codomain dimension

function tropicstats(pds::AbstractVector{PD}) where {T<:Real, PD<:PersistenceDiagram{T}}
    n = ComputationalHomology.maxsize(pds)
    d = ComputationalHomology.tropicdim(n)
    maxval = filter!(!isinf, map(i->max(birth(i),death(i)), Iterators.flatten(pds))) |> maximum
    m =  map(i->birth(i)/-ComputationalHomology.birthx(i), Iterators.flatten(pds)) |> maximum
    (d, n, ceil(Int, m), maxval)
end

"""
Caluclate 2-symmetric tropical rational polynomial for the persistence diagram
"""
function tropicp(pd::PersistenceDiagram{T},m,l,p; maxval=T(Inf)) where {T<:Real}
    B = Tuple{T,T}[]
    for i in pd
        d = min(maxval, death(i))
        b = birth(i)
        push!(B, (min(m*(d-b), b), d-b))
    end
    # pad zeros
    while length(B) < l+p
        push!(B, (zero(T), zero(T)))
    end
    n = length(B)

    if l == 0 && p == 0
        0
    elseif l == 0
        sort!(map(sum, B), rev=true)[1:p] |> sum
    elseif p == 0
        sort!(map(last,B), rev=true)[1:l] |> sum
    else
        res = 0
        L = hcat(combinations(1:n,l)...)'
        for i in 1:length(L)
            ii = L[i,:]
            e0 = sum(last, B[ii])
            K = hcat(combinations(setdiff(1:n, ii),p)...)'
            for j = 1:length(K)
                jj = K[j,:]
                e1 = sum(map(first, B[jj]) .+ map(last, B[jj]))
                res = max(res, e0 + e1)
            end
        end
        res
    end
end

"""
    tropic(pd)

Calculate a tropical coordinates for a persistent diagram `pd`

Paper: "Tropical Sufficient Statistics for Persistent Homology" by A.Monod et al.
Ref: https://arxiv.org/abs/1709.02647
"""
function tropic(pds::AbstractVector{PD}) where {T<:Real, PD<:PersistenceDiagram{T}}
    d,n,m,mv = ComputationalHomology.tropicstats(pds)
    [tropicp(pd,m,i,j,maxval=mv) for (i,j) in ComputationalHomology.Orbits, pd in pds ]
end
tropic(pd::PersistenceDiagram{T}) where {T<:Real} = tropic([pd]) |> vec
tropic(pdd::Dict{Int, PD}) where {T<:Real, PD<:PersistenceDiagram{T}} = tropic(flatten(pdd))


# use this to time regular mcmc(), but without plots.
using Distributions, Random
function main()
    y = rand(Exponential(3.0),30)
    # set prior, likelihood and proposal
    Prior = θ -> pdf.(Ref(LogNormal(1.0,1.0)), θ)
    lnL = θ -> sum(logpdf.(Ref(Exponential(θ)), y))
    tuning = 0.5
    Proposal = θ -> rand(LogNormal(log(θ),tuning))
    # get the chain, plot posterior, and descriptive stats
    chain = mcmc(1.0, 100000, 10000, Prior, lnL, Proposal, false) # start value, chain length, and burnin
end


import Base.*
import Base.vec
import Base.adjoint
import Base.isapprox

export S³
export ComplexPlane
export SU2
export Quaternion

"""
    Represents a point in a 3-sphere.
"""
abstract type S³ end

"""
    Represents a point in the complex plane.

fields: z₁ and z₂.
"""
struct ComplexPlane <: S³
    z₁::Complex
    z₂::Complex
end

"""
    Represents a Quaternion.

field: q.
"""
struct Quaternion <: S³
    r::ℝ⁴
    Quaternion(r4::ℝ⁴) = new(r4)
    Quaternion(a::Real, b::Real, c::Real, d::Real) = new(ℝ⁴(a, b, c, d))
    Quaternion(a::Array) = begin
        @assert(length(a) == 4, "The input must be a 4-vector.")
        Quaternion(a...)
    end
    Quaternion(θ::Real, u::ℝ³) = Quaternion([cos(θ); vec(sin(θ) * normalize(u))])
end

"""
    Represents a point in SU(2)

field: a.
"""
struct SU2 <: S³
    a::Array{Complex,2}
end

Base.vec(q::Quaternion) = vec(q.r)
Base.vec(cp::ComplexPlane) = [cp.z₁; cp.z₂]

Base.adjoint(s::SU2) = SU2(convert(Array{Complex,2}, adjoint(s.a)))
Base.conj(q::Quaternion) = Quaternion(vec(q)[1], -vec(q)[2], -vec(q)[3], -vec(q)[4])

ComplexPlane(s::SU2) = ComplexPlane(s.a[1,1], -s.a[2,1])
ComplexPlane(q::Quaternion) = ComplexPlane(vec(q)[1] + im * vec(q)[2],
                                           vec(q)[3] + im * vec(q)[4])
SU2(cp::ComplexPlane) = SU2([cp.z₁ conj(cp.z₂); -cp.z₂ conj(cp.z₁)])
SU2(q::Quaternion) = SU2(ComplexPlane(q))
Quaternion(cp::ComplexPlane) = Quaternion(real(cp.z₁),
                                          imag(cp.z₁),
                                          real(cp.z₂),
                                          imag(cp.z₂))
Quaternion(s::SU2) = Quaternion(ComplexPlane(s))
normalize(q::Quaternion) = Quaternion(normalize(q.r))
norm(q::Quaternion) = norm(q.r)
dot(q1::Quaternion, q2::Quaternion) = dot(q1.r, q2.r)

(*)(s1::SU2, s2::SU2) = SU2(s1.a * s2.a)
(*)(cp1::S³, cp2::S³) = ComplexPlane(SU2(cp1) * SU2(cp2))
(*)(q1::Quaternion, q2::Quaternion) = Quaternion(SU2(q1) * SU2(q2))
(*)(q::Quaternion, λ::Real) = Quaternion(λ * q.r)
(*)(λ::Real, q::Quaternion) = q * λ
(+)(q1::Quaternion, q2::Quaternion) = Quaternion(vec(q1) + vec(q2))
(-)(q1::Quaternion, q2::Quaternion) = Quaternion(vec(q1) - vec(q2))
+(q::Quaternion) = q
-(q::Quaternion) = Quaternion(-vec(q))

Base.isapprox(q1::Quaternion, q2::Quaternion) = isapprox(vec(q1), vec(q2))
Base.isapprox(q::Quaternion, s::S³) = isapprox(q, Quaternion(s))
Base.isapprox(s::S³, q::Quaternion) = isapprox(Quaternion(s), q)
Base.isapprox(s1::S³, s2::S³) = isapprox(Quaternion(s1), Quaternion(s2))


module ExponentialUtilities
using LinearAlgebra, SparseArrays
using LinearAlgebra: exp!

include("phi.jl")
include("arnoldi.jl")
include("krylov_phiv.jl")
include("krylov_phiv_adaptive.jl")
include("krylov_expv.jl")

export phi, phi!, KrylovSubspace, arnoldi, arnoldi!, lanczos!, ExpvCache, PhivCache,
    expv, expv!, phiv, phiv!, expv_timestep, expv_timestep!, phiv_timestep, phiv_timestep!,
    StegrCache, get_subspace_cache
end


@testset "Test the Jacobian of the pressure with respect to the position for a set of point vortices" begin

	atol_AD  = 1000*eps()

	Ny = 15
	sensors = randn(ComplexF64, Ny)

	# Test for a set of vortices
	Ns = 10
	zs = randn(ComplexF64, Ns)
	Ss = randn(Ns)

	sys = Vortex.Point.(zs, Ss)
	U = 0.0*im
	freestream = Freestream(U)

	dpdzAD, dpdzstarAD = PotentialFlow.Elements.jacobian_position(x->pressure_AD(sensors, x, 0.0), sys)

	dpdz, dpdzstar = analytical_jacobian_position(sensors, sys, freestream, 0.0)

	@test isapprox(dpdz, dpdzAD, atol = atol_AD)
	@test isapprox(dpdzstar, dpdzstarAD, atol = atol_AD)
end

@testset "Test the Jacobian of the pressure with respect to the strength for a set of point vortices" begin

	atol_AD  = 1000*eps()

	Ny = 15
	sensors = randn(ComplexF64, Ny)

	# Test for a set of vortices
	Ns = 10
	zs = randn(ComplexF64, Ns)
	Ss = randn(Ns)

	sys = Vortex.Point.(zs, Ss)
	U = 0.0*im
	freestream = Freestream(U)

	dpdSAD = PotentialFlow.Elements.jacobian_strength(x->pressure_AD(sensors, x, 0.0), sys)

	dpdS, dpdSstar = analytical_jacobian_strength(sensors, sys, freestream, 0.0)

	# Here ∂p/∂ΓL = -i(∂p/∂SL - ∂p/∂S̄L) with SL = QL - iΓL, as S = Γ + iQ is used as the convention in PotentialFlow.jl
	@test isapprox(-im*(dpdS - dpdSstar), dpdSAD, atol = atol_AD)
end

@testset "Test the Jacobian of the pressure with respect to the position for a set of regularized vortices" begin

	atol_AD  = 100*eps()

	Ny = 15
	sensors = randn(ComplexF64, Ny)

	δ = 1e-1

	# Test for a set of vortices
	Ns = 10
	zs = randn(ComplexF64, Ns)
	Ss = randn(Ns)

	sys = Vortex.Blob.(zs, Ss, δ*ones(Ns))
	U = 0.0*im
	freestream = Freestream(U)

	dpdzAD, dpdzstarAD = PotentialFlow.Elements.jacobian_position(x->pressure_AD(sensors, x, 0.0), sys)

	dpdz, dpdzstar = analytical_jacobian_position(sensors, sys, freestream, 0.0)

	@test isapprox(dpdz, dpdzAD, atol = atol_AD)
	@test isapprox(dpdzstar, dpdzstarAD, atol = atol_AD)
end

@testset "Test the Jacobian of the pressure with respect to the strength for a set of regularized vortices" begin

	atol_AD  = 1000*eps()

	Ny = 15
	sensors = randn(ComplexF64, Ny)

	δ = 1e-1

	# Test for a set of vortices
	Ns = 10
	zs = randn(ComplexF64, Ns)
	Ss = randn(Ns)

	sys = Vortex.Blob.(zs, Ss, δ*ones(Ns))
	U = 0.0*im
	freestream = Freestream(U)

	dpdSAD = PotentialFlow.Elements.jacobian_strength(x->pressure_AD(sensors, x, 0.0), sys)

	dpdS, dpdSstar = analytical_jacobian_strength(sensors, sys, freestream, 0.0)

	# Here ∂p/∂ΓL = -i(∂p/∂SL - ∂p/∂S̄L) with SL = QL - iΓL, as S = Γ + iQ is used as the convention in PotentialFlow.jl
	@test isapprox(-im*(dpdS - dpdSstar), dpdSAD, atol = atol_AD)
end

@testset "Validate analytical_jacobian_pressure for a set of singularities" begin
    atol_AD = 1000*eps()
	Nv = 10
	zv = randn(Nv) + im *rand(Nv)
	Sv = randn(Nv)

	sensors = randn(ComplexF64, 15)

	sys = Vortex.Point.(zv, Sv)
	U = 0.0*im
	freestream = Freestream(U)

	∂p = analytical_jacobian_pressure(sensors, sys, freestream, 0.0)

	∂pAD = AD_symmetric_jacobian_pressure(sensors, sys, 0.0)

	@test isapprox(∂p, ∂pAD, atol = atol_AD)
end

@testset "Validate analytical_jacobian_pressure for a set of regularized singularities" begin
    atol_AD = 1000*eps()
	Nv = 10
	zv = randn(Nv) + im *rand(Nv)
	Sv = randn(Nv)
    δ = 1e-1

	sensors = randn(ComplexF64, 15)

	sys = Vortex.Blob.(zv, Sv, δ*ones(Nv))
	U = 0.0*im
	freestream = Freestream(U)

	∂p = analytical_jacobian_pressure(sensors, sys, freestream, 0.0)

	∂pAD = AD_symmetric_jacobian_pressure(sensors, sys, 0.0)

	@test isapprox(∂p, ∂pAD, atol = atol_AD)
end


using Singular

function is_gb(pols::Vector{MP}) where {MP <: Singular.MPolyElem}
    id = Singular.Ideal(parent(first(pols)), pols)
    id.isGB = true
    gb_id = std(id)
    all(p -> iszero(Singular.reduce(p, id)), Singular.gens(gb_id))
end

function cyclic(vars)
    n = length(vars)
    pols = [sum(prod(vars[j%n+1] for j in k:k+i) for k in 1:n) for i in 0:n-2]
    push!(pols, prod(vars[i] for i in 1:n)-1)
    return pols
end

function gen_example_file(I::Vector{MP},
                          filename::String) where {MP <: AbstractAlgebra.MPolyElem}
    f = open(string(filename, ".jl"), write = true, create = true)
    println(f, "using Singular")
    println(f, "using SignatureGB")
    R = parent(first(I))
    vars = ["x$(i)" for i in 1:Singular.nvars(R)]
    nvr = Singular.nvars(R)
    println(f, "R, ($(["x$(i), " for i in 1:nvr-1]...), $("x$(nvr)")) = Singular.PolynomialRing(Fp($(Singular.characteristic(R))), $(vars))")
    println(f, "I = $(I)")
    println(f, "comp_ideal = gens(R)")
    println(f, "SignatureGB.f5(comp_ideal)")
    println(f, "gb = SignatureGB.f5(I, verbose = true)")
    println(f, "SignatureGB.is_gb(gb)")
    close(f)
end


# SPDX-License-Identifier: MIT

abstract type AbstractNormal <: ExponentialFamilyDistribution end

struct Normal{T1<:AbstractVector,T2<:AbstractVector,T3<:AbstractVector} <: AbstractNormal
    μ::T1
    lnλ::T2 # Logarithm of the diagonal of the L matrix.
    vechL::T3 # Half-vectorization of the L matrix
end

function Normal(μ::AbstractVector, Σ::AbstractMatrix)
    L = cholesky(inv(Σ)).L
    lnλ = log.(diag(L))
    vechL = vech(L, 1)
    Normal(μ, lnλ, vechL)
end

# Utility function to get the standard parameters from the expectation
# parameters
function stdparams(n::Normal, packed_μ)
    unpacked_μ = unpack(n,  packed_μ)
    x̄, diagx̄x̄ᵀ, vechx̄x̄ᵀ = unpacked_μ
    x̄x̄ᵀ = diagm(diagx̄x̄ᵀ) + CompressedSymmetric(size(x̄, 1), 1, vechx̄x̄ᵀ)
    x̄, Symmetric(x̄x̄ᵀ - x̄ * x̄')
end

Normal(μ::AbstractVector) =
    Normal(μ, copyto!(similar(μ, size(μ, 1), size(μ, 1)), I))

Normal(D::Int) = Normal(zeros(D), Matrix(I, D, D))

function η(p::AbstractNormal)
    D = length(p.μ)
    L = diagm(exp.(p.lnλ)) .+ CompressedLowerTriangular(D, 1, p.vechL)
    Λ = L * L'
    vcat(Λ*p.μ, -(1/2)*diag(Λ), -vech(Λ, 1))
end

function ξ(p::AbstractNormal, η)
	D = length(p.μ)
    diagΛ = -2 * diagm(η[D+1:2*D])
    shΛ = - CompressedSymmetric(D, 1, η[2*D+1:end])
    Λ = diagΛ + shΛ
    L = cholesky(Λ).L
    L⁻¹ = inv(L)
    μ = L⁻¹' * L⁻¹ * η[1:D]
    vcat(μ, log.(diag(L)), vech(L, 1))
end

function unpack(p::AbstractNormal, μ)
	D = length(p.μ)
	x = μ[1:D]
    diagxxᵀ = μ[D+1:2*D]
    vechxxᵀ = μ[2*D+1:end]
	(x=x, diagxxᵀ=diagxxᵀ, vechxxᵀ=vechxxᵀ)
end

function A(p::AbstractNormal, η)
	D = length(p.μ)

    diagΛ = -2 * diagm(η[D+1:2*D])
    shΛ = -CompressedSymmetric(D, 1, η[2*D+1:end])
    Λ = diagΛ + shΛ
    L = cholesky(Symmetric(Λ)).L
    L⁻¹ = inv(L)
    μ = L⁻¹' * L⁻¹ * η[1:D]

    #-(1/2)*logdet(Symmetric(Λ)) + (1/2)* μ' * η[1:D]
    -sum(log.(diag(L))) + (1/2)* μ' * η[1:D]
end

function sample(p::AbstractNormal)
	D = length(p.μ)
    L = diagm(exp.(p.lnλ)) .+ CompressedLowerTriangular(D, 1, p.vechL)
    Λ = Symmetric(L * L')
    p.μ + cholesky(inv(Λ)).L*randn(D)
end

struct NormalDiag{T1<:AbstractVector,T2<:AbstractVector} <: AbstractNormal
    μ::T1
	lnλ::T2
end

NormalDiag(μ::AbstractVector) =
    NormalDiag(μ, fill!(similar(μ, size(μ, 1), zero(eltype(μ)))))

NormalDiag(D::Int) = NormalDiag(zeros(D), zeros(D))

function η(p::NormalDiag)
    λ = exp.(p.lnλ)
	vcat(λ .* p.μ, -(1/2)*λ)
end

function ξ(p::NormalDiag, η)
	D = length(p.μ)
	λ = -2* η[D+1:end]
    μ = (1 ./ λ) .* η[1:D]
    vcat(μ, log.(λ))
end

function unpack(p::NormalDiag, μ)
	D = length(p.μ)
	x² = μ[D+1:end]
	x = μ[1:D]
    (x=x, x²=x²)
end

function A(p::NormalDiag, η)
	D = length(p.μ)

	λ = -2 * η[D+1:end]
	μ = (1 ./ λ) .* η[1:D]

    -(1/2) * sum(log.(λ)) + (1/2) * sum(μ .* λ .* μ)
end

function sample(p::NormalDiag)
	D = length(p.μ)
    p.μ + sqrt.(1 ./ exp.(p.λ)) .* randn(D)
end

struct NormalIso{T1<:AbstractVector,T2<:Real} <: AbstractNormal
    μ::T1
	lnλ::T2
end

function η(p::NormalIso)
    λ = exp.(p.lnλ)
	vcat(λ .* p.μ, -(1/2)*λ)
end

function ξ(p::NormalIso, η)
	D = length(p.μ)
	λ = -2* η[D+1]
    μ = (1 / λ) .* η[1:D]
    vcat(μ, log(λ))
end

function unpack(p::NormalIso, μ)
	D = length(p.μ)
	x² = μ[D+1]
	x = μ[1:D]
    (x=x, x²=x²)
end

function A(p::NormalIso, η)
	D = length(p.μ)

	λ = -2 * η[D+1]
	μ = (1 / λ) .* η[1:D]

    -(D/2) * log(λ) + (1/2) * sum(μ .* λ .* μ)
end

function sample(p::NormalIso)
	D = length(p.μ)
    p.μ + sqrt.(1 / exp.(p.lnλ)) .* randn(D)
end


export energy, meantime, rmsduration, meanfrequency, rmsbandwidth, ifrequency

"""
$(SIGNATURES)
Computes total signal energy.
"""
energy(s::AbstractVector; fs=framerate(s)) = sum(abs2, s)/inHz(fs)
energy(s::AbstractMatrix; fs=framerate(s)) = vec(sum(abs2, s; dims=1))./inHz(fs)

"""
$(SIGNATURES)
Computes mean time of the signal.
"""
meantime(s::SampleBuf) = wmean(domain(s), abs2.(samples(s)))
meantime(s; fs) = wmean((0:size(s,1)-1)/fs, abs2.(s))

"""
$(SIGNATURES)
Computes RMS duration of the signal.
"""
rmsduration(s::SampleBuf) = sqrt.(wmean(domain(s).^2, abs2.(samples(s))) .- meantime(s).^2)
rmsduration(s; fs) = sqrt.(wmean(((0:size(s,1)-1)/fs).^2, abs2.(s)) .- meantime(s; fs=fs).^2)

"""
$(SIGNATURES)
Computes instantaneous frequency of the signal.
"""
function ifrequency(s; fs=framerate(s))
  s1 = analytic(s)
  f1 = inHz(fs)/(2π) * diff(unwrap(angle.(s1); dims=1); dims=1)
  f2 = Array{Float32}(undef, size(s))
  f2[1,:] = f1[1,:]
  f2[2:end-1,:] .= (f1[1:end-1,:] .+ f1[2:end,:])./2
  f2[end,:] = f1[end,:]
  signal(f2, fs)
end

"""
$(SIGNATURES)
Computes mean frequency of a signal.
"""
function meanfrequency(s::AbstractMatrix; fs=framerate(s), nfft=1024, window=nothing)
  fs = inHz(fs)
  rv = mapslices(samples(s); dims=1) do s1
    p = welch_pgram(s1, min(nfft, length(s1)); fs=fs, window=window)
    f = freq(p)
    wmean(f, power(p))
  end
  dropdims(rv, dims=1)
end

function meanfrequency(s::AbstractVector; fs=framerate(s), nfft=1024, window=nothing)
  p = welch_pgram(s, min(nfft, length(s)); fs=inHz(fs), window=window)
  f = freq(p)
  wmean(f, power(p))
end

"""
$(SIGNATURES)
Computes RMS bandwidth of a signal.
"""
function rmsbandwidth(s::AbstractMatrix; fs=framerate(s), nfft=1024, window=nothing) where T
  fs = inHz(fs)
  rv = mapslices(samples(s); dims=1) do s1
    p = welch_pgram(s1, min(nfft, length(s1)); fs=fs, window=window)
    f = freq(p)
    f0 = wmean(f, power(p))
    sqrt.(wmean((f.-f0).^2, power(p)))
  end
  dropdims(rv, dims=1)
end

function rmsbandwidth(s::AbstractVector; fs=framerate(s), nfft=1024, window=nothing) where T
  p = welch_pgram(s, min(nfft, length(s)); fs=inHz(fs), window=window)
  f = freq(p)
  f0 = wmean(f, power(p))
  sqrt.(wmean((f.-f0).^2, power(p)))
end

### utility functions

wmean(x::AbstractVector, w::AbstractVector) = (x'w) / sum(w)
wmean(x, w::AbstractVector) = sum(x.*w) ./ sum(w)
wmean(x, w::AbstractMatrix) = dropdims(sum(x.*w; dims=1) ./ sum(w; dims=1), dims=1)


using
  FourierFlows.KuramotoSivashinsky,
  PyPlot

nx, Lx = 256, 4π
dt, nt = 1e-6, 100
prob = InitialValueProblem(nx=nx, Lx=Lx, dt=dt, stepper="ETDRK4")
x = prob.grid.x

a = 1e-4 # amplitude of initial condition
u0 = @. a*cos(x/2) # initial condition
set_u!(prob, u0)

ua(x, t) = a*exp(3t/16)*cos(x/2) + a^2*2/3*(exp(3t/8)-1)*sin(x) # analytical solution

t = dt*(1:nt)
T = [ t[j] for i=1:nx, j=1:nt ]
X = [ x[i] for i=1:nx, j=1:nt ]
U = zeros(nx, nt)
Ua = ua.(X, T)

for i = 1:nt
  stepforward!(prob)
  updatevars!(prob)
  U[:, i] .= prob.vars.u
end

L1(u) = prob.grid.dx*dropdims(sum(abs.(u), dims=1), dims=1)/Lx
L1(u::Array{T,1}) where {T} = prob.grid.dx*sum(abs.(u))/Lx

close("all")
fig, ax = subplots()
plot(t, L1(U-Ua)/L1(ua.(x, 0)))
ylabel(L"\mathrm{L1} = \frac{1}{L}\int |u-u_a| \, \mathrm{d} x")
xlabel(L"t")
show()


using ModelingToolkit

@variables x1 x2
@parameters dt

eq = [
    -1 + 9 *x1 - 2*x1^3 + 9 *x2 - 2*x2^3,
    1 - 11*x1 + 2*x1^3 + 11*x2 - 2*x2^3
]

ModelingToolkit.gradient.(eq, [[x1,x2]])

#=
os = OptimizationSystem(eq, [], [x1,x2])

calculate_gradient(os)
=#


using LinearAlgebra

"""
Implements the Hooke & Jeeves direct search method.

Arguments:
    fn: objective function
    x0: starting point
    h0: starting step size
    h_min: minimum step size
    r: reduction factor for step size

Returns:
    `Response` struct if successful, else `nothing`
"""
function hooke_jeeves(fn, x0; h0=1/10, h_min=.01, r=1/2, max_iter=1000)
    x = copy(x0)
    n = length(x)
    e_hat = Matrix(1I, n, n)
    k = 0
    h = h0
    x_b = x
    fx = fn(x)
    results = [(x, fx)]

    while k < max_iter
        for i = 1:n
            x, fx = results[end]
            # try searching in each linearly independent direction
            x_new = x + h * e_hat[:, i]
            f_new = fn(x_new)

            if f_new <= fx
                k += 1
                push!(results, (x_new, f_new))
            else
                x_new = x - h * e_hat[:, i]
                f_new = fn(x_new)

                if f_new <= fx
                    k += 1
                    push!(results, (x_new, f_new))
                end
            end
        end

        x, fx = results[end]
        # if the base hasn't changed, reduce step size
        if x_b == x
            h *= r

            # convergence reached
            if h < h_min
                return Response(x, fx, k, true)
            end
        end

        # perform acceleration step
        x_new = 2x - x_b
        x_b = x
        f_new = fn(x_new)

        if f_new <= fx
            k += 1
            push!(results, (x_new, f_new))
        end
    end

    println("Maximum iterations reached.")
    x, fx = results[end]
    return Response(x, fx, k, false)
end


mutable struct Cube
    a::Vector{Int}
    b::Vector{Int}
end

function day22(n=0)
    lines = filter(!isempty, strip.(readlines("AoCdata/AoC_2021_day22.txt")))
    oldlist = Cube[]
    for (j, line) in enumerate(lines)
        n != 0 && j > n && break
        arr = split(line, r"[\s\,][xyz]\=|\.\.|\,")
        newc = Cube(
            [parse.(Int, s) for s in arr[2:2:6]], [parse.(Int, s) + 1 for s in arr[3:2:7]]
        )
        newlist = Cube[]
        for oldc in oldlist
            if any(oldc.b .<= newc.a .|| newc.b .<= oldc.a) # no overlap
                push!(newlist, oldc)
            else
                for i in 1:3
                    if oldc.a[i] < newc.a[i] && newc.b[i] < oldc.b[i]
                        c1, c2 = deepcopy(oldc), deepcopy(oldc)
                        c1.b[i], c2.a[i] = newc.a[i], newc.b[i]
                        push!(newlist, c1, c2)
                        oldc.a[i], oldc.b[i] = newc.a[i], newc.b[i]
                    elseif oldc.a[i] < newc.a[i] <= oldc.b[i]
                        c1 = deepcopy(oldc)
                        c1.b[i] = newc.a[i]
                        push!(newlist, c1)
                        oldc.a[i] = newc.a[i]
                    elseif oldc.a[i] <= newc.b[i] < oldc.b[i]
                        c1 = deepcopy(oldc)
                        c1.a[i] = newc.b[i]
                        push!(newlist, c1)
                        oldc.b[i] = newc.b[i]
                    end
                end
            end
        end
        oldlist = newlist
        first(arr) == "on" && push!(oldlist, newc)
    end
    return sum(prod(abs.(cub.a .- cub.b)) for cub in oldlist)
end

println("Part 1: ", day22(20))
println("Part 2: ", day22())


using PrinciplesCTImaging: Ellipse, isinside

@testset "points inside ellipse" begin
    e = Ellipse(0.0, 0.0, 1.0, 1.0, 0.0, 1.0)
    @test isinside(e, 0.0, 0.0) == true
    @test isinside(e, 1.0, 0.0) == true
    @test isinside(e, 1.0, 1.0) == false
end

using PrinciplesCTImaging: SheppLoganPhantom, construct

@testset "SheppLogan phantom construction" begin
    pixels = 128
    I = construct(SheppLoganPhantom, pixels)
    @test I[1, 1] ≈ 0.0
    @test I[25, 50] ≈ 1.02
    @test I[50, 50] ≈ 1.0
end


using DSP: conv

@doc raw"""
    synthesis(wt::WaveletTransformed)

Implements the continuous wavelet synthesis on some `WaveletTransformed` object. That is, it
reconstructs the signal, `wt` was created from. The optional keyword argument `hilbert` specifies
whether the complex Hilbert transformed of the original signal or the real signal is returned. By
default the real reconstruction is returned.
"""
function synthesis(wt::ContinuousWavelet.ContinuousWaveletTransformed; hilbert=false)
    N, M = size(wt)
    res = zeros(Complex{Float64}, N)
    # First convolution with smallest scale
    K = Int32(exp2(ceil(log2(2*cutoff_time(wt.wavelet)*wt.scales[1])))); off = K÷2
    kern = map(t -> eval_synthesis(wt.wavelet,
        (t-K/2)/wt.scales[1])/wt.scales[1]/wt.scales[1], 1:K)
    last = conv(kern, wt[:,1])[(off):(off+N-1)]

    current = similar(last)
    for i in 2:M
        # eval kernel for current scale
        K = Int32(exp2(ceil(log2(2*cutoff_time(wt.wavelet)*wt.scales[i])))); off = K÷2
        kern = map(t -> eval_synthesis(wt.wavelet,
            (t-K/2)/wt.scales[i])/wt.scales[i]/wt.scales[i], 1:K)
        # convolution of i-th voice with kernel at i-th scale
        current[:] = conv(kern, wt[:,i])[(off):(off+N-1)]
        # integrate by mid-point evaluation
        res[:] += ( (current+last) .* ((wt.scales[i]-wt.scales[i-1]))/2 )
        # swap current <-> last w/o copy
        tmp = last;
        last = current;
        current = tmp;
    end
    # If Hilbert transformed result is requested -> complex otherwise -> real
    (hilbert) ? res : 2*real.(res)
end


## A script to generate a tab-separated-values file
## containing questions for a pool or test to upload
## in batch mode to BlackBoard
## Questions can be formatted using MarkDown
## or LaTeX and can be templated so that randomization
## (for pools) can be performed
## Several BlackBoard question types can be used.

using JuliaBlackBoard

# used to create .txt file with same basename and same directory as script
OPEN = JuliaBlackBoard.OPEN(@__FILE__)

# Packages used in the following examples
using Plots
using StatsBase # for mean
using GLM       # for lm
using RCall     # for calling out to R for an example

OPEN() do io

    ## ---- Question ----
    ## Simple Markdown question
    ## mt"..." is exported from Mustache
    q = mt"""
# The mean

Find the mean of

```
2 3 4
```
"""

    # use a NUM type with answer computed
    question(io, NUM,
             q,                  # question
             mean((2,3,4))       # answer
             )

    ## Note: the use of mt"""...""" makes the multi-line string into a Mustache
    ## template. For randomization problems this will be more performant.
    ## This is not necessary for the above, but is useful in general.
    ## For string interpolation, use just """...""".

    ## ---- Question ----
    ## Same as above, but we use a Mustache template
    ## so that we can do randomization
    ##
    ## The bit below: {{{:xs}}}
    ## will be substituted in by the keyword argument to `q` (defined, as `q = mt...`)
    q = mt"""
# The mean

Find the mean of

```
{{{:xs}}}
```
"""

    for xs in ((2,3,4),
               (3,4,5))
        # use a NUM type with answer computed
        question(io, NUM,
                 q(xs = join(xs, ",")), # question
                 mean(xs))              # answer
    end

    ## For more detail, when `mt` is used to create a question, then the
    ## substitution of template variables (e.g. `{{{:xs}}}`) is done by
    ## calling the question like a function with `xs` as a named argument.
    ## This allows 0 or more such subsitutions. (Variables are specified as symbol in the
    ## template). The distinction between triple and double brackets is related to HTML
    ## replacement of special entitities. Using triple braces is recommended.

    ## ---- Question ----
    ## Same question with some LaTeX code. This uses HTML to represent the LaTeX
    ## BlackBoard does not allow us to upload ŷ (unicode) or MathML so we
    ## resort to HTML as generated by `tth`.
    ##
    ## This requires `tth` (https://sourceforge.net/projects/tth/) to be available as `tth`.
    ##
    ## tth does a poor job with inline math that has height, e.g. $\hat{y}$, $\bar{x}$. The -t
    ## switch is used, as the alternative doesn't seem to work at all. It is suggested to avoid these
    ## annotations, as done here:
     q = mt"""
# The mean

Find the mean,

$$
xbar = \frac{x_1 + x_2 + \cdots + x_n}{n}.
$$

of

```
2 3 4
```
"""
    question(io, NUM, q, mean((2,3,4)))

    ## ---- Question ----
    ## Same question with some LaTeX code.
    ENV["USE_MATHJAX"] = true
    ## By setting this environment variable, questions can be rendered within
    ## BlackBoard with MathML markup **PROVIDED** one manually edits them
    ## through the web editor and saves them. (Just edit then submit, no change
    ## required
    q = mt"""
# The mean

Find the mean,

$$
\bar{x} = \frac{x_1 + x_2 + \cdots + x_n}{n}.
$$

of

```
2 3 4
```
"""
    question(io, NUM, q, mean((2,3,4)))
    ENV["USE_MATHJAX"] = false

    ## ---- Question ----
    ## Same question with LaTeX generating an image for substition. We use
    ## a different template for the question -- one
    ## that only loads an image (png) of the file generated by calling `LaTeX` on
    ## the question.
    q = mt"""
\subsection{The mean}

Find the mean,

$$
\bar{x} = \frac{x_1 + x_2 + \cdots + x_n}{n}.
$$

of

\begin{verbatim}
2 3 4
\end{verbatim}
"""

    question(io, NUM, LaTeX(q), mean((2,3,4)))

    ## ---- Question ----
    ## Same question with LaTeX generating an image for substition.
    ## However, this uses Markdown for non math-mode markup
    q = mt"""
# The mean

Find the mean,

$$
\bar{x} = \frac{x_1 + x_2 + \cdots + x_n}{n}.
$$

of

```
2 3 4
```
"""
    question(io, NUM, LaTeX′(q), mean((2,3,4)))

    ## ---- Question ----
    ## Same question with randomization.
    q = mt"""
Find the mean, $\bar{x}$, of

\begin{verbatim}
{{:xs}}
\end{verbatim}
"""

    ## Note: When viewing the above question on blackboard, there will
    ## be no identifying text in the short preview mode of the pool
    ## viewer. If might be a good idea to have a slightly more general
    ## question template, something like this where the "section" is
    ## moved into the question template
    question_tpl = mt"""
## The mean

![]({{{:latex}}})
"""
    for xs in ((1,2,3), (4,5,6))
        str = q(xs=join(xs, ", "))

        question(io, NUM,
                 question_tpl(latex=File(JuliaBlackBoard.latex_to_png(str))),
                 mean((2,3,4)))
    end

    ## ---- Question ----
    ## Using Plots to generate a plot and show inline
    ## We use `Plot(p)` to generate the plot for rendering (using Base64 encoding)

    q = mt"""
# Regression line

Estimate the slope of the regression line from the following graph

![]({{{:plot}}})
"""

    xs = rand(25)
    ys = 1 .+ 2*xs + 0.25 * rand(25)
    res = lm(@formula(y ~ x), (y=ys, x=xs))
    a, b = coef(res)
    p = scatter(xs, ys, legend=false)
    plot!(p, x -> a + b*x, extrema(xs)...)

    question(io, NUM, q(plot=Plot(p)), b, 1/10) # last number is tolerance

    ## ---- Question ----
    ## Using R to generate a plot
    ## This is almost the same
    ## Here we create a png file on the file system, so we use `File` to produce the
    ## the graph for rendering. File could be used for any png file on a local drive
    q = mt"""
# Regression line

Estimate the slope of the regression line from the following graph

![]({{{:plot}}})
"""

    pngfile = tempname() * ".png"

    @rput pngfile
    R"""
xs = rnorm(20)
ys = rnorm(20)
f = ys ~ xs
res = lm(f)

png(pngfile)
plot(f)
abline(res)
dev.off()
"""
    res = @rget res
    a, b = round.(res[:coefficients], digits=1)

    question(io, NUM,
             q(plot=File(pngfile)),   # question
             b, 0.5)                  # answer and tolerance

    ## ---- Question ----
    ## Different question types:
    ## There a some of https://help.blackboard.com/Learn/Instructor/Tests_Pools_Surveys/Reuse_Questions/Upload_Questions
    ## supported.
    ## The answers are formatted differently for each. For the above we
    ## had NUM, an answer is just a number and an optional absolute tolerance

    ## Multiple Choice
    ## answer is a  tuple of pairs ("answer 1"=>false, "answer 2"=>true)

    q = mt"""
## Multiple choice

Which is true
"""

    # tuple of pairs "possible answer" => boolean; only 1 is true
    # answers can have markup in Markdown or LaTeX (if suitably escaped)
    answers = ("The **earth** is flat"  => false,
               "The **sky** is falling" => false,
               "climate change is real" => true)

    question(io, MC, q, answers)

    ## ---- Question ----

    ## Multiple answer
    ## As with MC, only 1 or more true answers
    q = mt"""
## Multiple answer

Which is **false**
"""

    answers = ("The earth is flat"      => true,
               "The sky is falling"     => true,
               "climate change is real" => false)

    question(io, MA, q, answers)

    ## ---- Question ----
    ## True False has a boolean answer

    q = mt"""
## True/false

Is $2 > 3$?
"""
    question(io, TF, q, false)

    ## ---- Question ----
    ## Fill in the blank.
    ## Answer is a tuple of values for matching, case insensitively
    ## Would need to hand edit for other matches

    q = mt"""
## Fill in the blanks

The ____ brown fox jumped over the lazy dog.
"""

    answers = ("quick",) # only one option for an answer
    question(io, FIB, q, answers)

    ## ---- Question ----
    ## Fill in blank plus
    ##
    ## The question puts variables in between [], as in [var1]
    ## The answers are pairs ("var1"=>("possible", "exact", "answers"))
    q = mt"""
## Fill in blank -- plus!

Use a letter or name

The first three non-negative integers are [one], [two], and [three]
"""
    # Collection of pairs: "variable"=>("different", "exact", "matches", "allowed")
    answers = ("one"   => ("one", "1"),
               "two"   => ("two", "2"),
               "three" => ("three", "3")
               )
    question(io, :FIB_PLUS, q, answers)

    ## ---- Question ----
    ## ORD:
    ## Text within ( ) may be repeated for
    ## each of the answers that are part of the Ordering question. The
    ## maximum number of answers is 100. The order typed in the file
    ## is the correct order. The system will randomly order the
    ## answers.
    q = mt"""

## Ordered

The numbers in order are:
"""
    # tuple of answers
    answers = ("one", "two", "three", "four", "five")
    question(io, ORD, q, answers)

    ## ---- Question ----
    ## MAT
    ## Text within ( ) may be repeated for each of the answers that
    ## are part of the Matching question. The maximum number of
    ## answers is 100. The system will randomly order the answers and
    ## their question. When uploading a matching question, you must
    ## have a one-to-one relationship between questions and
    ## answers. If not, correct answers may be marked incorrect if
    ## more than one answer has the same value.

    q = mt"""
## Matching

Match phonetically:
"""
    # collection of pairs
    answers = ("a"   => "Eh",
               "one" => "won")
    question(io, MAT, q, answers)

    ## ---- Question ----
    ## Likert scale
    q = mt"""
# Likert scale (for surveys)

Do you like BlackBoard?
"""
    question(io, OP, q)

    ## ---- Question ----
    ## JUMBLED_SENTENCE
    ##
    ## The question marks blanks by [varname]
    ## The answer is a collection of pairs with
    ## "answer" => ("varname",) where an answer can matche more than one variable or *no*
    ## variable.
    q = mt"""

## Jumbled sentence

Question goes here [var1] (choice 1) [var2] (choice 2)
"""

    # collection of pairs "variable" => "choice in popdown menu"
    # and possible nothing => (distractors...)
    as = ("var1"  => "choice 1",
          "var2"  => "choice 2",
          nothing => ("choice 3","choice 4")) # 2 distractors

    question(io, JUMBLED_SENTENCE, q, as)

    ## Short response
    ## answer is a prompt

    q = mt"""
## Short response

Did you like this test?
"""
    question(io, SR, q, "A quick answer")

    ## ---- Question ----
    ## Essay. No context or answer is necessary
    q = mt"""
## Essay

Please write more...
"""
    question(io, ESS, q)

    ## ---- Question ----
    ## File upload. No context or answer is necessary

    q = mt"""
## File upload

Upload a screen shot. Please use a scanning feature on your phone (e.g., as provided  in the iPhone's
Notes app)
"""
    question(io, FIL, q)

end


using StaticUnivariatePolynomials
using Test
using ForwardDiff

import StaticUnivariatePolynomials: constant, derivative, integral, coefficient_gradient, exponential_integral

include("monomial_basis.jl")
include("bernstein_basis.jl")


@symbol_func function volume(cur_reactor::AbstractReactor)
  cur_volume = 2.0

  cur_volume *= cur_reactor.pi ^ 2

  cur_volume *= cur_reactor.R_0

  cur_volume *= a(cur_reactor) ^ 2

  cur_volume *= cur_reactor.kappa_95

  cur_volume *= g(cur_reactor)

  cur_volume
end


# sparseDirichletMixture.jl

export SparseDirMix, logSDMweights,
    logMarginal, rand;

struct SparseDirMix
  α::Union{Float64, Vector{Float64}}
  β::Float64
  SparseDirMix(α, β) = all(α .> 0.0) && β > 1.0 ? new(α, β) :
    error("Invalid parameter values.")
end

"""
    logSDMweights(d::SparseDirMix)

  Calculate the log of mixture weights for the SDM prior.
### Example
```julia
    α = exp.(rand(5))
    β = 2.0
    d = SparseDirMix(α, β)
    logSDMweights(d)
```
"""
function logSDMweights(d::SparseDirMix)
  K = length(d.α)
  X = reshape(repeat(d.α, inner=K), (K,K)) + Diagonal(d.β*ones(K))
  lgX = loggamma.(X)
  lpg = reshape(sum(lgX, dims=2), K)
  lpg_denom = logsumexp(lpg)

  lw = lpg .- lpg_denom
  lw
end

"""
    logMarginal(x, d::SparseDirMix)

Calculate the log of the SDM-multinomial compound (marginal) probability mass function.

"""
function logMarginal(prior::SparseDirMix, x::Vector{Int})
    lwSDM = logSDMweights(prior)

    K = length(prior.α)
    A = reshape(repeat(prior.α, inner=K), (K,K)) + Diagonal(prior.β*ones(K))
    AX = A + reshape(repeat(x, inner=K), (K,K))

    lnum = [ lmvbeta(AX[k,:]) for k in 1:K ]
    ldenom = [ lmvbeta(A[k,:]) for k in 1:K ]

    lv = lwSDM .+ lnum .- ldenom

    logsumexp( lv )
end

"""
    rand(d::SparseDirMix[, logscale=FALSE])

  Draw from sparse Dirichlet mixture: p(Θ) ∝ Dir(α)⋅∑Θ^β

"""
function Base.rand(d::SparseDirMix; logout::Bool=false)
  K = length(d.α)
  X = reshape(repeat(d.α, inner=K), (K,K)) + Diagonal(d.β*ones(K))
  lgX = loggamma.(X)
  lpg = reshape(sum(lgX, dims=2), K)
  lpg_denom = logsumexp(lpg)

  lw = lpg .- lpg_denom
  z = StatsBase.sample(Weights( exp.(lw) ))

  rDirichlet(X[z,:], logout=logout)
end


using DifferentialEvolutionMCMC, Test, Random, Turing, Parameters, Distributions
using SequentialSamplingModels, LinearAlgebra
Random.seed!(9918)

dist = LNR(μ=[-2.,-2.,-3.,-3], σ=1.0, ϕ=.5)
data = rand(dist, 100)

function loglike(data, μ, σ, ϕ)
    dist = LNR(μ, σ, ϕ)
    return sum(logpdf.(dist, data))
end

function prior_loglike(μ, σ, ϕ)
    LL = 0.0
    LL += sum(logpdf.(Normal(0, 3), μ))
    LL += logpdf(truncated(Cauchy(0, 1), 0.0, Inf), σ)
    LL += logpdf(Uniform(0., min_rt), ϕ)
end

function sample_prior()
    LL = 0.0
    μ = rand(Normal(0, 3), 4)
    σ = rand(truncated(Cauchy(0, 1), 0.0, Inf))
    ϕ = rand(Uniform(0, min_rt))
    return as_union([μ,σ,ϕ])
end

min_rt = minimum(x -> x[2], data)

bounds = ((-Inf,0.),(1e-10,Inf),(0.,min_rt))
names = (:μ,:σ,:ϕ)

model = DEModel(;
    sample_prior,
    prior_loglike,
    loglike,
    data,
    names
)

de = DE(;sample_prior, bounds, burnin=2000, Np=24, n_groups=4)
n_iter = 4000
chains = sample(model, de, MCMCThreads(), n_iter, progress=true)

μ_de = describe(chains)[1][:,:mean]
σ_de = describe(chains)[1][:,:std]
rhat = describe(chains)[1][:,:rhat]

@model model1(data) = begin
    min_rt = minimum(x -> x[2], data)
    μ ~ MvNormal(zeros(4), I * 3^2)
    σ ~ truncated(Cauchy(0, 1), 0.0, Inf)
    ϕ ~ Uniform(0.0, min_rt)
    data ~ LNR(μ=μ, σ=σ, ϕ=ϕ)
end

chn = sample(model1(data), NUTS(1500, .85), 3000)
μ_nuts = describe(chn)[1][:,:mean]
σ_nuts = describe(chn)[1][:,:std]

@test all(isapprox.(rhat, fill(1.0, 6), atol=.05))
@test all(isapprox.(μ_nuts, μ_de, rtol=.05))
@test all(isapprox.(σ_nuts, σ_de, rtol=.05))


import Base: show, eltype, length, size, getindex, ==, (*), iszero
import Base: Vector, Matrix, adjoint, hash

import LinearAlgebra: dot
export HVector, HMatrix, dot, hash

"""
An `HVector` is a homogeneous vector; two `HVector`s are equal
iff they are nonzero multiples of each other.

`HVector(a,b,c,...)` or `HVector([a,b,c,...])` creates a new `HVector `
with coordinates `(a,b,c,...)`.
"""
struct HVector{T}
    data::Vector{T}
    function HVector(dat::Vector{T}) where {T}
        data = dat .// one(T)
        S = typeof(sum(data))
        new{S}(_canonical(data))
    end

end
HVector(x...) = HVector(collect(x))
HVector(x::HVector) = HVector(x.data)

"""
An `HMatrix` is a homogenous matrix; two of these are equal if they are
nonzero multiples of each other.

Given a two-dimensional array `A`, `HMatrix(A)` creates a new homogeneous
matrix.
"""
struct HMatrix{T}
    data::Matrix{T}
    function HMatrix(dat::AbstractMatrix{T}) where {T}
        data = dat .// one(T)
        S = typeof(sum(data))
        M = new{S}(_canonical(data))
        return M
    end
end
HMatrix(x::HMatrix) = HMatrix(x.data)

function _canonical(x)
    if length(x) == 0 || iszero(x)
        return x
    end
    idx = findlast(x .!= 0)
    a = x[idx]
    return x .// a
end

HObject = Union{HVector,HMatrix}

eltype(x::HObject) = eltype(x.data)
length(x::HObject) = length(x.data)
size(x::HObject) = size(x.data)
getindex(x::HObject, k...) = x.data[k...]
(==)(x::HObject, y::HObject) = x.data == y.data
iszero(x::HObject) = iszero(x.data)

"""
The `dot` produce of two `HVector`s is either `0`
(if they are orthogonal) or `1` (otherwise).
"""
function dot(x::HVector, y::HVector)::Int
    d = dot(x.data, y.data)
    if d == 0
        return 0
    end
    return 1
end

function show(io::IO, x::HVector)
    print(io, "HVector(")
    n = length(x.data)
    for j = 1:n-1
        print(io, "$(x.data[j]), ")
    end
    print(io, x.data[end], ")")
end

function show(io::IO, x::HMatrix)
    print(io, "HMatrix: ")
    print(io, x.data)
end

function (*)(A::AbstractArray{T,2}, v::HVector) where {T}
    Av = A * v.data
    return HVector(Av)
end

function (*)(A::AbstractArray{T,2}, B::HMatrix) where {T}
    AB = A * B.data
    return HMatrix(AB)
end

(*)(A::HMatrix, v::HVector) = HVector(A.data * v.data)
(*)(A::HMatrix, B::HMatrix) = HMatrix(A.data * B.data)

adjoint(A::HMatrix) = HMatrix(adjoint(A.data))

hash(x::HObject, h::UInt = UInt(0)) = hash(x.data, h)

Vector(x::HVector) = copy(x.data)
Matrix(x::HMatrix) = copy(x.data)


function assess_convergence(x::Array,
                            x_previous::Array,
                            f_x::Real,
                            f_x_previous::Real,
                            gr::Array,
                            x_tol::Real,
                            f_tol::Real,
                            g_tol::Real)
    x_converged, f_converged, g_converged = false, false, false

    if maxdiff(x, x_previous) < x_tol
        x_converged = true
    end

    # Absolute Tolerance
    # if abs(f_x - f_x_previous) < f_tol
    # Relative Tolerance
    if abs(f_x - f_x_previous) / (abs(f_x) + f_tol) < f_tol || nextfloat(f_x) >= f_x_previous
        f_converged = true
    end

    if vecnorm(gr, Inf) < g_tol
        g_converged = true
    end

    converged = x_converged || f_converged || g_converged

    return x_converged, f_converged, g_converged, converged
end


import DelimitedFiles
import Optim

function part1(fname)
  initial_positions = vec(DelimitedFiles.readdlm(fname, ',', Int))

  f(x) = sum(abs.(initial_positions .- x))
  res = Optim.optimize(f, 0., maximum(initial_positions))

  optimal_position = ceil(Optim.minimizer(res))
  fuel_used = Int(minimum((f(optimal_position - 1), f(optimal_position))))
  println("Minimimal amount of fuel used: $fuel_used units.")
end

function part2(fname)
  initial_positions = vec(DelimitedFiles.readdlm(fname, ',', Int))

  f(x) = sum([sum(0:1:n_steps) for n_steps in (abs.(initial_positions .- x))])
  res = Optim.optimize(f, 0., maximum(initial_positions))

  optimal_position = ceil(Optim.minimizer(res))
  fuel_used = Int(minimum((f(optimal_position - 1), f(optimal_position))))
  println("Minimimal amount of fuel used: $fuel_used units.")
end

part1("test_input.txt")
part2("test_input.txt")

part1("input.txt")
part2("input.txt")


# Contains evaluation functions
# ----------------------------------
"""
	 Evaluates the FIS

	eval_fis(fis, input_values,defuzz_method = "WTAV")

	 Parameters
	 ----------
	 `fis` is the inference system to evaluate
	 `input_values` is a Vector of inputs
	 `defuzz_method` is the method for defuzzification, see defuzz function definition

"""
function eval_fis(fis::FISMamdani, input_values::Vector{<:AbstractFloat},defuzz_method = "WTAV")

	firing_strengths = AbstractFloat[]
	for rule in fis.rules
		tmp_strengths = AbstractFloat[]
		for i in 1:length(rule.input_mf_names)
			push!(tmp_strengths, fis.input_mfs_dicts[i][rule.input_mf_names[i]].eval(input_values[i]))
		end
			push!(firing_strengths, firing(tmp_strengths,rule.firing_method ))
	end
	defuzz(firing_strengths, fis, defuzz_method)

end

"""
	 Evaluates the FIS

	eval_fis(fis, input_values)

	 Parameters
	 ----------
	 `fis` is the inference system to evaluate
	 `input_values` is a Vector of inputs

"""
function eval_fis(fis::FISSugeno, input_values::Vector{<:AbstractFloat})

	firing_strengths = AbstractFloat[]
	for rule in fis.rules
		tmp_strengths = AbstractFloat[]
		for i in 1:length(rule.input_mf_names)
			push!(tmp_strengths, fis.input_mfs_dicts[i][rule.input_mf_names[i]].eval(input_values[i]))
		end
			push!(firing_strengths, firing(tmp_strengths,rule.firing_method ))
	end

	push!(input_values, 1)
	n_firing_strengths = firing_strengths / sum(firing_strengths)
	out = 0.0
	for i = 1:length(fis.rules)
		out += n_firing_strengths[i] * (fis.rules[i].output_mf' * input_values)[1]
	end
	pop!(input_values)
	return out

end

"""
	 Defuzzifies the output using the given firing strengths

	defuzz(firing_strengths, rules, output_mfs_dict, defuzz_method)

	 Parameters
	 ----------
	 `firing_strengths` is a Vector of firing strengths
	 		one for each output membership function
	 `rules` is a Vector of Rule
	 `output_mfs_dict` is a Dict of output membership functions
	 `defuzz_method` is the method for defuzzification
	 		"MOM" - Mean of Maximum
	 		"WTAV" - Weighted Average
			"COG" - Center of Gravity
"""

include("Utils.jl")

function defuzz(firing_strengths::Vector{AbstractFloat}, fis::FISMamdani, defuzz_method::AbstractString)
	rules = fis.rules
	output_mfs_dict = fis.output_mfs_dict
	domain = fis.output_domain
	if defuzz_method == "MOM"
		max_firing_index = argmax(firing_strengths)
		max_fired_mf_name = rules[max_firing_index].output_mf
		output_mfs_dict[max_fired_mf_name].mean_at(maximum(firing_strengths))
	elseif defuzz_method == "WTAV"
		mean_vec = AbstractFloat[]
		for i in 1:length(rules)
			push!(mean_vec, output_mfs_dict[rules[i].output_mf].mean_at(firing_strengths[i]))
		end
		(mean_vec' * firing_strengths)[1] / sum(firing_strengths)
	elseif defuzz_method == "COG"
		cutted = []
		for i in 1:length(rules)
			push!(cutted, output_mfs_dict[rules[i].output_mf].cut(firing_strengths[i]))
		end
		agg(x) = maximum(map(y -> y.eval(x), cutted))
		agg_aux(x) = x*agg(x)
		n = sum(map(y -> y.get_n(1e-5), cutted))
		return trapz(agg_aux, domain[1], domain[2], n)/trapz(agg, domain[1], domain[2], n)
	end

end

"""
	 Docs goes here
"""
function firing(tmp_strengths::Vector{<:AbstractFloat},firing_method::AbstractString)
	if firing_method == "MIN"
		return	minimum_value(tmp_strengths)
	elseif firing_method == "A-PROD"
		return algebraic_product(tmp_strengths)
	elseif firing_method == "B-DIF"
		return bounded_difference(tmp_strengths)
	elseif firing_method == "D-PROD"
		return drastic_product(tmp_strengths)
	elseif firing_method == "E-PROD"
		return einstein_product(tmp_strengths)
	elseif firing_method == "H-PROD"
		return hamacher_product(tmp_strengths)
	elseif firing_method == "MAX"
		return	maximum_value(tmp_strengths)
	elseif firing_method == "A-SUM"
		return algebraic_sum(tmp_strengths)
	elseif firing_method == "B-SUM"
		return bounded_sum(tmp_strengths)
	elseif firing_method == "D-SUM"
		return drastic_sum(tmp_strengths)
	elseif firing_method == "E-SUM"
		return einstein_sum(tmp_strengths)
	elseif firing_method == "H-SUM"
		return hamacher_sum(tmp_strengths)
	end
end


# # Online Gaussian Process
#
# ### Loading necessary packages
# ## Preliminary steps
# ### Load the necessary packages
using Plots
using AugmentedGaussianProcesses
using MLDataUtils, Distributions

# ### We create a toy dataset with a noisy sinus
N = 2000
σ = 0.1
X, y = noisy_sin(N, 0, 20; noise=σ)
X_train = X[1:2:end];
y_train = y[1:2:end]; # We split the data equally
X_test = X[2:2:end];
y_test = y[2:2:end];
scatter(X_train, y_train)

# ### Plot model at each step
function plot_model(model, X, X_test, X_train, y_train)
    y_pred, sig_pred = proba_y(model, X_test)
    plot(X, sin; lab="f", color=:black, lw=3.0, ylims=(-2, 2))
    plot!(X_test, y_pred; ribbon=sqrt.(sig_pred), lab="Prediction", lw=3.0)
    scatter!(X_train, y_train; msw=0.0, alpha=0.5, lab="Data")
    return scatter!(first.(model[1].Z), mean(model[1]); lab="IP")
end

# ## Model training
# ### Create a kernel
k = SqExponentialKernel();
# ### Create an inducing point selection method
IP_alg = OIPS(0.8);
# ### Create the model and stream the data
model = OnlineSVGP(k, GaussianLikelihood(σ), AnalyticVI(), IP_alg; optimiser=false)
anim = Animation()
size_batch = 100
for (i, (X_batch, y_batch)) in enumerate(eachbatch((X_train, y_train); obsdim=1, size=size_batch))
    train!(model, X_batch, y_batch; iterations=3)
    plot_model(model, X, X_test, X_train[1:(i * size_batch)], y_train[1:(i * size_batch)])
    frame(anim)
end
gif(anim; fps=4)
# This works just as well with any likelihood! Just try it out!