"""
Cumulative Distribution Function of a Standard Gaussian Random Variable

		y=normcdf(x)

Where:\n
		x         = point of evaluation.

		y      = probability that a standard gaussian random variable is below x.

# Example
```julia-repl
julia> normcdf(0.0)
0.5
```
"""
function normcdf(x::number) where {number <: Number}
	return (1.0+erf(x/sqrt(2.0)))/2.0;
end

"""
Probability Distribution Function of a Standard Gaussian Random Variable

		y=normpdf(x)

Where:\n
		x         = point of evaluation.

		y      = value.

# Example
```julia-repl
julia> normpdf(0.0)
0.3989422804014327
```
"""
function normpdf(x::number) where {number <: Number}
	return exp(-0.5*x.^2)/sqrt(2*pi);
end

"""
Black & Scholes Price for European Options

		Price=blsprice(S0,K,r,T,σ,d=0.0,FlagIsCall=true)

Where:\n
		S0         = Value of the Underlying.
		K          = Strike Price of the Option.
		r          = Zero Rate.
		T          = Time to Maturity of the Option.
		σ          = Implied Volatility
		d          = Implied Dividend of the Underlying.
		FlagIsCall = true for Call Options, false for Put Options.

		Price      = price of the European Option.

# Example
```julia-repl
julia> blsprice(10.0,10.0,0.01,2.0,0.2,0.01)
1.1023600107733191
```
"""
function blsprice(S0::num1,K::num2,r::num3,T::num4,σ::num5,d::num6=0.0,FlagIsCall::Bool=true) where {num1 <: Number ,num2 <: Number ,num3 <: Number ,num4 <: Number ,num5 <: Number ,num6 <: Number}
	blscheck(S0,K,r,T,σ,d);
	d1=(log(S0/K)+(r-d+σ*σ*0.5)*T)/(σ*sqrt(T));
	d2=d1-σ*sqrt(T);
	Price=0.0;
	if FlagIsCall
	Price=S0*exp(-d*T)*normcdf(d1)-K*exp(-r*T)*normcdf(d2);
	else
	Price=-S0*exp(-d*T)*normcdf(-d1)+K*exp(-r*T)*normcdf(-d2);
	end
return Price;
end

"""
Black Price for European Options

		Price=blkprice(F0,K,r,T,σ,FlagIsCall=true)

Where:\n
		F0         = Value of the Forward.
		K          = Strike Price of the Option.
		r          = Zero Rate.
		T          = Time to Maturity of the Option.
		σ          = Implied Volatility.
		FlagIsCall = true for Call Options, false for Put Options.

		Price      = price of the European Option.

# Example
```julia-repl
julia> blkprice(10.0,10.0,0.01,2.0,0.2)
1.1023600107733191
```
"""
function blkprice(F0::num1,K::num2,r::num3,T::num4,σ::num5,FlagIsCall::Bool=true) where {num1 <: Number ,num2 <: Number ,num3 <: Number ,num4 <: Number ,num5 <: Number}
	blscheck(F0,K,r,T,σ);
	Price=blsprice(F0,K,r,T,σ,r,FlagIsCall);
return Price;
end

"""
Black & Scholes Delta for European Options

		Δ=blsdelta(S0,K,r,T,σ,d=0.0,FlagIsCall=true)

Where:\n
		S0         = Value of the Underlying.
		K          = Strike Price of the Option.
		r          = Zero Rate.
		T          = Time to Maturity of the Option.
		σ          = Implied Volatility
		d          = Implied Dividend of the Underlying.
		FlagIsCall = true for Call Options, false for Put Options.

		Δ          = delta of the European Option.

# Example
```julia-repl
julia> blsdelta(10.0,10.0,0.01,2.0,0.2,0.01)
0.5452173371920436
```
"""
function blsdelta(S0::num1,K::num2,r::num3,T::num4,σ::num5,d::num6=0.0,FlagIsCall::Bool=true) where {num1 <: Number ,num2 <: Number ,num3 <: Number ,num4 <: Number ,num5 <: Number ,num6 <: Number}
	blscheck(S0,K,r,T,σ,d);
	d1=(log(S0/K)+(r-d+σ*σ*0.5)*T)/(σ*sqrt(T));
	Δ=0.0;
	if FlagIsCall
	Δ=exp(-d*T)*normcdf(d1);
	else
	Δ=-exp(-d*T)*normcdf(-d1);
	end
return Δ;
end

"""
Black & Scholes Gamma for European Options

		Γ=blsgamma(S0,K,r,T,σ,d=0.0,FlagIsCall=true)

Where:\n
		S0         = Value of the Underlying.
		K          = Strike Price of the Option.
		r          = Zero Rate.
		T          = Time to Maturity of the Option.
		σ          = Implied Volatility
		d          = Implied Dividend of the Underlying.
		FlagIsCall = true for Call Options, false for Put Options.

		Γ          = gamma of the European Option.

# Example
```julia-repl
julia> blsgamma(10.0,10.0,0.01,2.0,0.2,0.01)
0.13687881535712826
```
"""
function blsgamma(S0::num1,K::num2,r::num3,T::num4,σ::num5,d::num6=0.0,FlagIsCall::Bool=true) where {num1 <: Number ,num2 <: Number ,num3 <: Number ,num4 <: Number ,num5 <: Number ,num6 <: Number}
	blscheck(S0,K,r,T,σ,d);
	d1=(log(S0/K)+(r-d+σ*σ*0.5)*T)/(σ*sqrt(T));
	Γ=exp(-d*T)*normpdf(d1)/(S0*σ*sqrt(T));
return Γ;
end

"""
Black & Scholes Vega for European Options

		ν=blsvega(S0,K,r,T,σ,d=0.0,FlagIsCall=true)

Where:\n
		S0         = Value of the Underlying.
		K          = Strike Price of the Option.
		r          = Zero Rate.
		T          = Time to Maturity of the Option.
		σ          = Implied Volatility
		d          = Implied Dividend of the Underlying.
		FlagIsCall = true for Call Options, false for Put Options.

		ν          = vega of the European Option.

# Example
```julia-repl
julia> blsvega(10.0,10.0,0.01,2.0,0.2,0.01)
5.475152614285131
```
"""
function blsvega(S0::num1,K::num2,r::num3,T::num4,σ::num5,d::num6=0.0,FlagIsCall::Bool=true) where {num1 <: Number ,num2 <: Number ,num3 <: Number ,num4 <: Number ,num5 <: Number ,num6 <: Number}
	blscheck(S0,K,r,T,σ,d);
	d1=(log(S0/K)+(r-d+σ*σ*0.5)*T)/(σ*sqrt(T));
	ν=S0*exp(-d*T)*normpdf(d1)*sqrt(T);
return ν;
end

"""
Black & Scholes Rho for European Options

		ρ=blsrho(S0,K,r,T,σ,d=0.0,FlagIsCall=true)

Where:\n
		S0         = Value of the Underlying.
		K          = Strike Price of the Option.
		r          = Zero Rate.
		T          = Time to Maturity of the Option.
		σ          = Implied Volatility
		d          = Implied Dividend of the Underlying.
		FlagIsCall = true for Call Options, false for Put Options.

		ρ          = rho of the European Option.

# Example
```julia-repl
julia> blsrho(10.0,10.0,0.01,2.0,0.2,0.01)
8.699626722294234
```
"""
function blsrho(S0::num1,K::num2,r::num3,T::num4,σ::num5,d::num6=0.0,FlagIsCall::Bool=true) where {num1 <: Number ,num2 <: Number ,num3 <: Number ,num4 <: Number ,num5 <: Number ,num6 <: Number}
	blscheck(S0,K,r,T,σ,d);
	d2=(log(S0/K)+(r-d-σ*σ*0.5)*T)/(σ*sqrt(T));
	if FlagIsCall
	ρ=K*exp(-r*T)*normcdf(d2)*T;
	else
		ρ=-K*exp(-r*T)*normcdf(-d2)*T;
	end
return ρ;
end

"""
Black & Scholes Theta for European Options

		Θ=blstheta(S0,K,r,T,σ,d=0.0,FlagIsCall=true)

Where:\n
		S0         = Value of the Underlying.
		K          = Strike Price of the Option.
		r          = Zero Rate.
		T          = Time to Maturity of the Option.
		σ          = Implied Volatility
		d          = Implied Dividend of the Underlying.
		FlagIsCall = true for Call Options, false for Put Options.

		Θ          = theta of the European Option.

# Example
```julia-repl
julia> blstheta(10.0,10.0,0.01,2.0,0.2,0.01)
-0.26273403060652334
```
"""
function blstheta(S0::num1,K::num2,r::num3,T::num4,σ::num5,d::num6=0.0,FlagIsCall::Bool=true) where {num1 <: Number ,num2 <: Number ,num3 <: Number ,num4 <: Number ,num5 <: Number ,num6 <: Number}
	blscheck(S0,K,r,T,σ,d);
	sqrtT       = sqrt(T);
	σ_sqrtT = σ .* sqrtT;

	d1 = (log(S0 ./ K) + (r - d + σ.^2 / 2) .* T)./ σ_sqrtT;

	shift = -exp(-d .* T) .* S0 .* normpdf(d1) .* σ / 2 ./ sqrtT;
	t1    = r .* K   .* exp(-r .* T);
	t2    = d .* S0 .* exp(-d .* T);
	Θ=0.0;
	if FlagIsCall
		Θ=shift - t1 .*      normcdf(d1 - σ_sqrtT)  + t2 .*  normcdf(d1)     ;
	else
		Θ=shift + t1 .* (1 - normcdf(d1 - σ_sqrtT)) + t2 .* (normcdf(d1) - 1);
	end
return Θ;
end

"""
Black & Scholes Lambda for European Options

		Λ=blslambda(S0,K,r,T,σ,d=0.0,FlagIsCall=true)

Where:\n
		S0         = Value of the Underlying.
		K          = Strike Price of the Option.
		r          = Zero Rate.
		T          = Time to Maturity of the Option.
		σ          = Implied Volatility
		d          = Implied Dividend of the Underlying.
		FlagIsCall = true for Call Options, false for Put Options.

		Λ          = lambda of the European Option.

# Example
```julia-repl
julia> blslambda(10.0,10.0,0.01,2.0,0.2,0.01)
4.945909973725978
```
"""
function blslambda(S0::num1,K::num2,r::num3,T::num4,σ::num5,d::num6=0.0,FlagIsCall::Bool=true) where {num1 <: Number ,num2 <: Number ,num3 <: Number ,num4 <: Number ,num5 <: Number ,num6 <: Number}
	blscheck(S0,K,r,T,σ,d);
	Price=blsprice(S0,K,r,T,σ,d,FlagIsCall);
	Δ=blsdelta(S0,K,r,T,σ,d,FlagIsCall);
	Λ=Δ*S0/Price;
return Λ;
end

"Check input for Black Scholes Formula"
function blscheck(S0::num1,K::num2,r::num3,T::num4,σ::num5,d::num6=0.0) where {num1 <: Number ,num2 <: Number ,num3 <: Number ,num4 <: Number ,num5 <: Number ,num6 <: Number}
lesseq(x::Complex,y::Complex) = real(x) <= real(y)
lesseq(x,y) = x <= y
if (lesseq(S0,zero(num1)))
	error("Spot Price Cannot Be Negative")
elseif (lesseq(K,zero(num2)))
	error("Strike Price Cannot Be Negative")
elseif (lesseq(T,zero(num4)))
	error("Time to Maturity Cannot Be Negative")
elseif (lesseq(σ,zero(num5)))
	error("Volatility Cannot Be Negative")
end
return;
end

"Brent Method: Scalar Equation Solver"
function brentMethod(f::Function, x0::Number, x1::Number,xtol::AbstractFloat=1e-14, ytol::AbstractFloat=1e-15)
		if xtol<0.0
		  throw(ErrorException("x tollerance cannot be negative"));
		end
	if ytol<0.0
		  throw(ErrorException("y tollerance cannot be negative"));
		end
		EPS = eps(x0)
	maxiter=80;
		y0 = f(x0)
		y1 = f(x1)
		if abs(y0) < abs(y1)
		    # Swap lower and upper bounds.
		    x0, x1 = x1, x0
		    y0, y1 = y1, y0
		end
		x2 = x0
		y2 = y0
		x3 = x2
		bisection = true
		for _ in 1:maxiter
		    # x-tolerance.
		    if abs(x1-x0) < xtol
		        return x1
		    end

		    # Use inverse quadratic interpolation if f(x0)!=f(x1)!=f(x2)
		    # and linear interpolation (secant method) otherwise.
		    if abs(y0-y2) > ytol && abs(y1-y2) > ytol
		        x = x0*y1*y2/((y0-y1)*(y0-y2)) +
		            x1*y0*y2/((y1-y0)*(y1-y2)) +
		            x2*y0*y1/((y2-y0)*(y2-y1))
		    else
		        x = x1 - y1 * (x1-x0)/(y1-y0)
		    end

		    # Use bisection method if satisfies the conditions.
		    delta = abs(2EPS*abs(x1))
		    min1 = abs(x-x1)
		    min2 = abs(x1-x2)
		    min3 = abs(x2-x3)
		    if (x < (3x0+x1)/4 && x > x1) ||
		       (bisection && min1 >= min2/2) ||
		       (!bisection && min1 >= min3/2) ||
		       (bisection && min2 < delta) ||
		       (!bisection && min3 < delta)
		        x = (x0+x1)/2
		        bisection = true
		    else
		        bisection = false
		    end
		    y = f(x)
		    # y-tolerance.
		    if abs(y) < ytol
		        return x
		    end
		    x3 = x2
		    x2 = x1
		    if sign(y0) != sign(y)
		        x1 = x
		        y1 = y
		    else
		        x0 = x
		        y0 = y
		    end
		    if abs(y0) < abs(y1)
		        # Swap lower and upper bounds.
		        x0, x1 = x1, x0
		        y0, y1 = y1, y0
		    end
		end
		throw(ErrorException("Max iteration exceeded, possible wrong result"));
end

export blsimpv
"""
Black & Scholes Implied Volatility for European Options

		σ=blsimpv(S0,K,r,T,Price,d=0.0,FlagIsCall=true,xtol=1e-14,ytol=1e-15)

Where:\n
		S0         = Value of the Underlying.
		K          = Strike Price of the Option.
		r          = Zero Rate.
		T          = Time to Maturity of the Option.
		Price      = Price of the Option.
		d          = Implied Dividend of the Underlying.
		FlagIsCall = true for Call Options, false for Put Options.

		σ          = implied volatility of the European Option.

# Example
```julia-repl
julia> blsimpv(10.0,10.0,0.01,2.0,2.0)
0.3433730534290586
```
"""
function blsimpv(S0::num1,K::num2,r::num3,T::num4,Price::num5,d::num6=0.0,FlagIsCall::Bool=true,xtol::Real=1e-14,ytol::Real=1e-15) where {num1 <: Real ,num2 <: Real ,num3 <: Real ,num4 <: Real ,num5 <: Real ,num6 <: Real}
if (Price< num5(0))
	throw(ErrorException("Option Price Cannot Be Negative"));
end
blscheck(S0,K,r,T,0.1,d);
f(x)=(blsprice(S0,K,r,T,x,d,FlagIsCall)-Price);
σ=brentMethod(f,0.001,1.2,xtol,ytol);

return σ;
end

export blkimpv
"""
Black Implied Volatility for European Options

		σ=blkimpv(F0,K,r,T,Price,FlagIsCall=true,xtol=1e-14,ytol=1e-15)

Where:\n
		F0         = Value of the Forward.
		K          = Strike Price of the Option.
		r          = Zero Rate.
		T          = Time to Maturity of the Option.
		Price      = Price of the Option.
		FlagIsCall = true for Call Options, false for Put Options.

		σ          = implied volatility of the European Option.

# Example
```julia-repl
julia> blkimpv(10.0,10.0,0.01,2.0,2.0)
0.36568658096623635
```
"""
function blkimpv(F0::num1,K::num2,r::num3,T::num4,Price::num5,FlagIsCall::Bool=true,xtol::Real=1e-14,ytol::Real=1e-15) where {num1 <: Real ,num2 <: Real ,num3 <: Real ,num4 <: Real ,num5 <: Real}
	blscheck(F0,K,r,T,0.1);
	σ=blsimpv(F0,K,r,T,Price,r,FlagIsCall,xtol,ytol)
	return σ;
end

##	ADDITIONAL Function

"""
Black & Scholes Psi for European Options

		Ψ=blspsi(S0,K,r,T,σ,d=0.0,FlagIsCall=true)

Where:\n
		S0         = Value of the Underlying.
		K          = Strike Price of the Option.
		r          = Zero Rate.
		T          = Time to Maturity of the Option.
		σ          = Implied Volatility
		d          = Implied Dividend of the Underlying.
		FlagIsCall = true for Call Options, false for Put Options.

		Ψ          = psi of the European Option.

# Example
```julia-repl
julia> blspsi(10.0,10.0,0.01,2.0,0.2,0.01)
-10.904346743840872
```
"""
function blspsi(S0::num1,K::num2,r::num3,T::num4,σ::num5,d::num6=0.0,FlagIsCall::Bool=true) where {num1 <: Number ,num2 <: Number ,num3 <: Number ,num4 <: Number ,num5 <: Number ,num6 <: Number}
	blscheck(S0,K,r,T,σ,d);
	d1=(log(S0/K)+(r-d+σ*σ*0.5)*T)/(σ*sqrt(T));
	if FlagIsCall
	Ψ=-S0*exp(-d*T)*normcdf(d1)*T;
	else
		Ψ=S0*exp(-d*T)*normcdf(-d1)*T;
	end
return Ψ;
end

"""
Black & Scholes Vanna for European Options

		Vanna=blsvanna(S0,K,r,T,σ,d=0.0,FlagIsCall=true)

Where:\n
		S0         = Value of the Underlying.
		K          = Strike Price of the Option.
		r          = Zero Rate.
		T          = Time to Maturity of the Option.
		σ          = Implied Volatility
		d          = Implied Dividend of the Underlying.
		FlagIsCall = true for Call Options, false for Put Options.

		Vanna        = vanna of the European Option.

# Example
```julia-repl
julia> blsvanna(10.0,10.0,0.01,2.0,0.2,0.01)
0.2737576307142566
```
"""
function blsvanna(S0::num1,K::num2,r::num3,T::num4,σ::num5,d::num6=0.0,FlagIsCall::Bool=true) where {num1 <: Number ,num2 <: Number ,num3 <: Number ,num4 <: Number ,num5 <: Number ,num6 <: Number}
	blscheck(S0,K,r,T,σ,d);
	d1=(log(S0/K)+(r-d+σ*σ*0.5)*T)/(σ*sqrt(T));
	d2=d1-σ*sqrt(T);
	Vanna=-exp(-d*T)*normpdf(d1)*d2/σ;
return Vanna;
end


function  compute()
	numer=1
	denom=1

	for d=10:99
		for n =10:d-1
			n0=n%10
			n1=Int((n-n0)/10)
			d0=d%10
			d1=Int((d-d0)/10)
			if (n1==d0 && n0*d==n*d1)||(n0==d1&&n1*d==n*d0)
				numer*=n
				denom*=d
			end
		end
	end

	return Int(denom/gcd(numer,denom))
end

println(compute())


using ParamLevelSet
using LinearAlgebra
using Test
using jInv.Mesh

plotting = false;
if plotting
	using jInvVisPyPlot
	using PyPlot
	close("all");
end

function getDiamondModel(n::Array{Int64})
n_tup = tuple(n...);
n = collect(n_tup);
u = zeros(Float32,n_tup);
X,Y,Z = meshgrid(-div(n[1],2):div(n[1],2),-div(n[2],2):div(n[2],2),-div(n[3],2):div(n[3],2))
I = (abs.(X)+abs.(Y)+abs.(Z)) .<= div(n[1],4);
u[I] .= 1.0;
return u;
end

println("Rotation operator transpose test");
n = [64,64,64].+1;

u = getDiamondModel(n);
u = convert(Array{Float64}, u)

if plotting
	figure()
	plotModel(u)
end

theta_phi = deg2rad.([37.0;24.0]);
display(theta_phi)
b = [1.0;2.0;3.0];

# RT = generateSamplingMatrix(u,theta_phi,1,1)
# v = RT'*u[:]
# println("The norm is:")
# println(norm(RT*v - u[:]))

v, = rotateAndMove3D(u,theta_phi,b,false);

if plotting
	figure()
	plotModel(v)
end
#uu = rotateAndMove3DTranspose(v,theta_phi,b);
uu, = rotateAndMove3D(v,theta_phi,b,true);
if plotting
	figure()
	plotModel(uu)
end

@test abs(dot(v,v) - dot(uu,u)) < 1e-5
@test norm(uu[:]-u[:])/norm(u[:]) < 1.0


# # Introduciton to Lotka-Volterra Competition

using Parameters
using DifferentialEquations
using ForwardDiff
using Plots
pyplot();

# # Introduction/Overview
# This notebook is simply looking at the two species L-V competiti9on model. Here, we are exploring
# the geometry and dynamics of this model.
#
# # Model and Equilibria
# The model we will use is the classical LV competition model:

include("lv_model.jl")

# # Evaluationg the model -- time series

let
    u0 = [0.5, 0.5]
    t_span = (0.0, 100.0)
    p = LVPar()

    prob = ODEProblem(lv_comp, u0, t_span, p)
    sol = solve(prob, reltol = 1e-8)

    plot(sol)
    xlabel!("time")
    ylabel!("Density")
end

# # Equilibria and Isoclines
# From the above equations we can generate the Jacobian:

# Make a numerical (not symbolic version using the ForwardDiff.jl library.
# This is what you will want to do most of the time)
jac(u, p) = ForwardDiff.jacobian(u -> lv_comp(u, p, NaN), u)

# Here we have the jacobian evaluated at the point `u = [0.5, 0.5]`, with the parameter set `par`
jac([0.5, 0.5], LVPar())

# First solve for $f_1$ and $f_2$ to determine when functions are equal to 0 (i.e., when $du_1/dt$
# and $du_2/dt = 0$). These are known as the isoclines, or nullclines, and describe the set of
# solutions when $u_1$ and $u_2$ do not change.

include("lv_symbolic.jl")

# We want to solve our equations for $u_1$ so that we can plot the isoclines as a function of  $u_1$
# ($u_1$ on the y-axis), but here I am keeping all possible solutions, with respect to both $u_1$
# and $u_2$. You should be able to see that the first two solutions ($u_1 = K_1 - \alpha_{12}u_2$
# and $u_2 = (K_1 - u_1)/\alpha_{12}$) are equivalent.
#
# The solutions $u_1 = K_1 - \alpha_{12}u_2$ and $u_1 = (K_2 - u_2)/\alpha_{21}$ are the equations
# for our isoclines for $du_1/dt = 0$ and $du_2/dt = 0$, respectively. We can find the interior
# equilibrium where these lines intersect (i.e., both $u_1$ and $u_2$ do not change). In this case
# we get one interior equilibrium, but note that in more complicated models we can get more interior
# equilibria.

# # Now plot the isoclines
# We will again use the manipulate function to see how our parameters change the isoclines. This can
# immediately tell us a lot about our equilibria and stability.

function iso1(u2, p)
    @unpack α12, K1 = p
    return K1 - α12 * u2 / K1
end
#-
function iso2(u2, p)
    @unpack α21, K2 = p
    return (K2 - u2) / α21
end
#-
let
    p = LVPar()
    u2s = range(0, 5, length = 100)

    fig = plot(u2s, [iso1(u2, p) for u2 in u2s], label = "u1 = 0")
    plot!(u2s, [iso2(u2, p) for u2 in u2s], label = "u2 = 0")
    xlims!(0, 3)
    ylims!(0, 5)
    xlabel!("u2")
    ylabel!("u1")
end


module LinearShiftRegisters

export LSR_period, LCG_period

function _repeated_substring(source)
	len = length(source)

	# had to account for weird edge case in which length 2 vectors always returns themselves
	if len < 3
		len == 1 && return nothing

		s1, s2 = source
		if len == 2 && s1 == s2
			return s1
		else
			return nothing
		end
	end

	# Check candidate strings
	for i in 1:(len ÷ 2 + 1)
		repeat_count, remainder = divrem(len, i)

		# Check for no leftovers characters, and equality when repeated
		if remainder == 0 && source == repeat(source[1:i], repeat_count)
			return source[1:i]#, repeat_count
		end
	end

	return nothing
end

function _LSR_period(const_vec::AbstractVector, init_vec::AbstractVector)
	sequence_vec = reverse(init_vec)

	res = nothing
	while isnothing(res)
		new_val = mod(sum(map(i -> const_vec[i] * sequence_vec[end - (i - 1)], 1:4)), 2)
		push!(sequence_vec, new_val)
		res = _repeated_substring(sequence_vec)
	end

	return res
end

@doc raw"""
```julia
LSR_period(const_str::AbstractVector{T}, init_str::AbstractVector{R}) where {T, R <: Integer} -> AbstractVector
LSR_period(const_str::AbstractString, init_str::AbstractString) -> String
```

Given a starting binary string (`init_str`) and a constant mutating string (`const_str`), `LSR_period` will find the repeating sequence generated from these.

!!! note

    As this method looks for recurring sequences, it is possible that this function does not terminate—or maybe it does terminate, but I haven't waited long enough to find out.

---

### Description

A (linear) shift register with feedback is an arrangement of registers in a row, each register being capable of holding either the digit 1 (on) or 0 (off).  Suppose the system has ``n`` registers, ``R_1,\ldots,R_n``, and ``X_i(t)`` denotes the content of register ``R_i`` at time ``t``.  Initially, let the system be given

Given

### Examples

```julia
julia> LSR_period("1001", "0100")
"001000111101011"

julia> LSR_period("1001", "0101")
"101011001000111"
```
"""
function LSR_period(const_vec::AbstractVector{T}, init_vec::AbstractVector{R}) where {T, R <: Integer}
	return _LSR_period(const_vec, init_vec)
end
function LSR_period(const_str::AbstractString, init_str::AbstractString)
	const_vec = parse.(Int8, collect(const_str))
	init_vec = parse.(Int8, collect(init_str))

	return join(_LSR_period(const_vec, init_vec))
end

function _LCG_period(m::Integer, a::Integer, b::Integer, x0::T) where {T <: Integer}
	sequence_vec = T[x0]

	new_x, res = x0, nothing
	while isnothing(res)
		new_x =  mod(a * new_x + b, m)
		push!(sequence_vec, new_x)
		res = _repeated_substring(sequence_vec)
	end

	return res
end

@doc raw"""
```julia
LCG_period(m::Integer, a::Integer, b::Integer, x0::Integer) -> String
```

Given `m`, `a`, `b`, and `x0`, finds the period generated by these parameters from a Linear Congruential Generator, and returns this sequence as a string.

!!! note

    As this method looks for recurring sequences, it is possible that this function does not terminate—or maybe it does terminate, but I haven't waited long enough to find out.

---

### Description

Choose m, a positive integer.  Choose a, b (mod m) integers.

The Linear Congruential Generator is given to us by

```math
x_1 \equiv ax_0+b\mod m\\
x_2 \equiv ax_1+b\mod m\\
\vdots\\
x_{n + 1} \equiv ax_n + b \mod m\\
\vdots
```

Eventually, ``x_n = x_i`` for some ``n``, ``i`` (let's suppose ``n>i`` and ``i`` is not necessarily ``0``).  Then, clearly,

```math
x_{n+1} = x_{i+1},\ldots.
```

So

```math
x_i, x_{i+1},\dots, x_{n-1}
```

Will be repeated forever.

From a cryptography point of view, LCG is too vulnerable (although all pseudorandom number generators are vulnerable), but it is still useful for other perposes that require pseudorandom numbers.

For essentially a more sophisticated version of LCG in the sense that it is based on recurring linear relations, see `LSR_period`.

---

### Examples

```julia
julia> println(LinearShiftRegisters._LCG_period(9, 5, 2, 0))
[0, 2, 3, 8, 6, 5]

julia> LCG_period(9, 5, 2, 0)
"023865"
```
"""
function LCG_period(m::Integer, a::Integer, b::Integer, x0::Integer)
	return join(_LCG_period(m, a, b, x0))
end

end # end module


"""
lsnormal(A,b)

Solve a linear least squares problem by the normal equations.
Returns the minimizer of ||b-Ax||.
"""
function lsnormal(A,b)

N = A'*A;  z = A'*b;
R = cholesky(N).U
w = forwardsub(R',z)                   # solve R'z=c
x = backsub(R,w)                       # solve Rx=z

return x
end

"""
lsqrfact(A,b)

Solve a linear least squares problem by QR factorization. Returns
the minimizer of ||b-Ax||.
"""
function lsqrfact(A,b)

Q,R = qr(A)
c = Q'*b
x = backsub(R,c)

return x
end

"""
qrfact(A)

QR factorization by Householder reflections. Returns Q and R.
"""
function qrfact(A)

    m,n = size(A)
    Qt = Matrix(Diagonal(ones(m)))
    R = float(copy(A))
    for k in 1:n
      z = R[k:m,k]
      v = [ -sign(z[1])*norm(z) - z[1]; -z[2:end] ]
      nrmv = norm(v)
      if nrmv < eps() continue; end  # skip this iteration
      v = v / nrmv;                  # simplifies other formulas
      # Apply the reflection to each relevant column of A and Q
      for j in 1:n
        R[k:m,j] -= v*( 2*(v'*R[k:m,j]) )
      end
      for j in 1:m
        Qt[k:m,j] -= v*( 2*(v'*Qt[k:m,j]) )
      end
    end

    return Qt',triu(R)
end


# 1. Create example point data
x = 8 .* rand(100) .- 4
y = 8 .* rand(100) .- 4
z = sin.(x) + cos.(y)
# Contour plot on the left without color (lines labelled by default)
subplot(1, 2, 1)
contour(x, y, z, colorbar = false)
# 2. Create example grid data with a callable
x = LinRange(-2, 2, 40)
y = LinRange(0, pi, 20)
f(x, y) = sin(x) + cos(y)
# Contour plot on the right with color
# and explicit labels every three lines
subplot(1, 2, 2)
contour(x, y, f, majorlevels = 3)


# Runs a simple Metropolis-Hastings (ie MCMC) algoritm to simulate two
# jointly distributed random variuables with probability density
# p(x,y)=exp(-(x^2+y^2)/s^2)/consNorm, where s>0 and consNorm is a
# normalization constant.
#
# Author: H. Paul Keeler, 2019.
# Website: hpaulkeeler.com
# Repository: github.com/hpaulkeeler/posts

#Note: Need the .+ for adding a scalar to an array
#Also need . for sqrt, exp, cos, sin etc and assinging scalars to arrays

using Distributions #for random simulations
using PyPlot #for plotting
using StatsBase #for histograms etc
using Random
using LinearAlgebra
using HCubature #for numerical integration
#using LaTeXStrings #for LateX in labels/titles etc
PyPlot.close("all");  # close all PyPlot figures

#set random seed for reproducibility
#Random.seed!(1234)

# Simulation window parameters
xMin = -1;
xMax = 1;
yMin = -1;
yMax = 1;

numbSim = 10 ^ 5;  # number of random variables simulated
numbSteps = 25;  # number of steps for the Markov process
numbBins = 50;  # number of bins for histogram
sigma = 2;  # standard deviation for normal random steps

# probability density parameters
s = .5;  # scale parameter for distribution to be simulated

function fun_lambda(x,y)
    return (exp.(-(x.^2+y.^2)./s^2));
end

#normalization constant -- UNDER CONSTRUCTION
consNorm,errorCub=hcubature(x -> fun_lambda(x[1],x[2]), [xMin, yMin], [xMax, yMax]);

function fun_p(x,y)
    return((fun_lambda(x,y)./consNorm).*(x.>=xMin).*(y.>=yMin).*(x.<=xMax).*(y.<=yMax));
end
xRand=(xMax-xMin).*rand(numbSim).+xMin; #random initial values
yRand=(yMax-yMin).*rand(numbSim).+yMin; #random initial values

probCurrent=fun_p(xRand,yRand); #current transition probabilities
for jj=1:numbSteps
    zxRand= xRand.+sigma.*rand(Normal(),numbSim);#take a (normally distributed) random step
    zyRand= yRand.+sigma.*rand(Normal(),numbSim);#take a (normally distributed) random step

    # Conditional random step needs to be symmetric in x and y
    # For example: Z|x ~ N(x,1) (or Y=x+N(0,1)) with probability density
    # p(z|x)=e(-(z-x)^2/2)/sqrt(2*pi)
    probProposal = fun_p(zxRand, zyRand);  # proposed probability

    # acceptance rejection step
    booleAccept=rand(numbSim) .< probProposal./probCurrent;
    # update state of random walk/Marjov chain
    xRand[booleAccept] = zxRand[booleAccept];
    yRand[booleAccept] = zyRand[booleAccept];
    # update transition probabilities
    probCurrent[booleAccept] = probProposal[booleAccept];
end

#histogram section: empirical probability density
histXY=fit(Histogram, (xRand,yRand),nbins=numbBins); #find histogram data
histXY=normalize(histXY,mode=:pdf); #normalize histogram
binEdges=histXY.edges; #retrieve bin edges
xValues=(binEdges[1][2:end]+binEdges[1][1:end-1])./2; #mid-points of bins
yValues=(binEdges[2][2:end]+binEdges[2][1:end-1])./2; #mid-points of bins
p_Estimate=(histXY.weights)
#create a meshgrid
X=[xValues[ii] for ii=1:length(xValues), jj=1:length(yValues)];
Y=[yValues[jj] for ii=1:length(xValues), jj=1:length(yValues)];

#analytic solution of probability density
p_Exact = fun_p(X, Y);

# Plotting
# Plot empirical estimate
fig1 = PyPlot.figure();
PyPlot.rc("text", usetex=true);
PyPlot.rc("font", family="serif");
surf(X, Y, p_Estimate, cmap=PyPlot.cm.plasma);
PyPlot.xlabel("x");
PyPlot.ylabel("y");
PyPlot.title("p(x,y) Estimate");

# Plot exact expression
fig2 = PyPlot.figure();
PyPlot.rc("text", usetex=true);
PyPlot.rc("font", family="serif")
surf(X, Y, p_Exact, cmap=PyPlot.cm.plasma);
PyPlot.xlabel("x");
PyPlot.ylabel("y");
PyPlot.title("p(x,y) Exact Expression");

#println("The program has completed.")


# In this tutorial, we will learn
#    - How to solve a simple nonlinear PDE in Gridap
#    - How to define the weak residual and its Jacobian
#    - How to setup and use a nonlinear solver
#    - How to define new boundaries from a given discrete model
#
# ## Problem statement
#
# The goal of this tutorial is to solve a nonlinear PDE in Gridap. For the sake of simplicity, we consider the $p$-Laplacian equation as the model problem. Specifically, the PDE  we want to solve is: find the scalar-field $u$ such that
#
# ```math
# \left\lbrace
# \begin{aligned}
# -\nabla \cdot \left( |\nabla u|^{p-2} \ \nabla u \right) = f\ &\text{in}\ \Omega,\\
# u = 0 \ &\text{on} \ \Gamma_0,\\
# u = g \ &\text{on} \ \Gamma_g,\\
# \left( |\nabla u|^{p-2}\ \nabla u \right)\cdot n = 0 \ &\text{on} \ \Gamma_{\rm N},
# \end{aligned}
# \right.
# ```
# with $p>2$.
# The computational domain $\Omega$ is the one depicted in next figure, which is the same as in the first tutorial. However, we slightly change the boundary conditions here. We impose homogeneous Dirichlet and homogeneous Neumann boundary conditions on $\Gamma_0$ and $\Gamma_{\rm N}$  respectively, and in-homogeneous Dirichlet conditions on $\Gamma_g$. The Dirichlet boundaries $\Gamma_0$ and $\Gamma_g$ are defined as the closure of the green and blue surfaces in next figure respectively, whereas the Neumann boundary is $\Gamma_{\rm N}\doteq\partial\Omega \setminus (\Gamma_0\cup\Gamma_g)$. In this example, we consider the values $p=3$, $f=1$, and $g=2$.
#
# ![](../assets/p_laplacian/model.png)
#
# ## Numerical scheme
#
# We discretize the problem with conforming Lagrangian FE spaces. For this formulation, the nonlinear weak form reads: find $u\in U_g$ such that $[r(u)](v) = 0$ for all $v\in V_0$. As in previous tutorials, the space $U_g$ is the set of functions in $H^1(\Omega)$ that fulfill the Dirichlet boundary conditions, whereas $V_0$ is composed by functions in $H^1(\Omega)$ that vanish at the Dirichlet boundary. The weak residual $r(u)$ evaluated at a function  $u\in U_g$ is the linear form defined as
#
# ```math
# [r(u)](v) \doteq \int_\Omega \nabla v \cdot \left( |\nabla u|^{p-2}\ \nabla u \right) \ {\rm d}\Omega - \int_\Omega v\ f \ {\rm d}\Omega.
# ```
#
# In order to solve this nonlinear weak equation, we consider a Newton-Raphson method, which is associated with a linearization of the problem in an arbitrary direction $\delta u\in V_0$, namely $[r(u+\delta u)](v)\approx [r(u)](v) + [j(u)](\delta u,v)$. In previous formula,  $j(u)$ is the Jacobian evaluated at $u\in U_g$, which is the bilinear form
#
# ```math
# [j(u)](\delta u,v) = \int_\Omega \nabla v \cdot \left( |\nabla u|^{p-2}\ \nabla \delta u \right) \ {\rm d}\Omega + (p-2) \int_\Omega \nabla v \cdot \left(  |\nabla u|^{p-4} (\nabla u \cdot \nabla \delta u) \nabla u  \right) \ {\rm d}\Omega.
# ```
#
# Note that the solution of this nonlinear PDE with a Newton-Raphson method, will require to discretize both the residual $r$ and the Jacobian $j$. In Gridap, this is done by following an approach similar to the one already shown in previous tutorials for discretizing the bilinear and linear forms associated with a linear FE problem. The specific details are discussed now.
#
# ## Discrete model
#
# As in previous tutorials, the first step to solve the PDE is to load a discretization of the computational domain. In this case, we load the model from the same file as in the first tutorial

using Gridap
model = DiscreteModelFromFile("../models/model.json")

# As stated before, we want to impose Dirichlet boundary conditions on $\Gamma_0$ and $\Gamma_g$,  but none of these boundaries is identified in the model. E.g., you can easily see by writing the model in vtk format

writevtk(model,"model")

# and by opening the file `"model_0"` in Paraview that the boundary identified as `"sides"` only includes the vertices in the interior of $\Gamma_0$, but here we want to impose Dirichlet boundary conditions in the closure of $\Gamma_0$, i.e., also on the vertices on the contour of $\Gamma_0$. Fortunately, the objects on the contour of $\Gamma_0$ are identified  with the tag `"sides_c"` (see next figure). Thus, the Dirichlet boundary $\Gamma_0$ can be built as the union of the objects identified as `"sides"` and `"sides_c"`.
#
# ![](../assets/p_laplacian/sides_c.png)
#
# Gridap provides a convenient way to create new object identifiers (referred to as "tags") from existing ones. First, we need to extract from the model, the object that holds the information about the boundary identifiers (referred to as `FaceLabeling`):

labels = get_face_labeling(model)

# Then, we can add new identifiers (aka "tags") to it. In the next line, we create a new tag called `"diri0"` as the union of the objects identified as `"sides"` and `"sides_c"`, which is precisely what we need to represent the closure of the Dirichlet boundary $\Gamma_0$.

add_tag_from_tags!(labels,"diri0",["sides", "sides_c"])

# We follow the same approach to build a new identifier for the closure of the Dirichlet boundary $\Gamma_g$. In this case, the boundary is expressed as the union of the objects identified with the tags `"circle"`, `"circle_c"`, `"triangle"`, `"triangle_c"`, `"square"`, `"square_c"`. Thus, we create a new tag for  $\Gamma_g$, called `"dirig"` simply as follows:

add_tag_from_tags!(labels,"dirig",
  ["circle","circle_c", "triangle", "triangle_c", "square", "square_c"])

# ## FE Space
#
# Now, we can build the FE space by using the newly defined boundary tags.

reffe = ReferenceFE(lagrangian,Float64,1)
V0 = TestFESpace(model,reffe,conformity=:H1,labels=labels,dirichlet_tags=["diri0", "dirig"])

# The construction of this space is essentially the same as in the first tutorial (we build a continuous scalar-valued Lagrangian interpolation of first order). However, we also pass here the `labels` object (that contains the newly created boundary tags). From this FE space, we define the trial FE spaces

g = 1
Ug = TrialFESpace(V0,[0,g])

# ## Nonlinear FE problem
#
# At this point, we are ready to build the nonlinear FE problem. To this end, we need to define the weak residual and also its corresponding Jacobian. This is done following a similar procedure to the one considered in previous tutorials to define the bilinear and linear forms associated with linear FE problems. We first need to define the usual objects for numerical integration:

degree=2
Ω = Triangulation(model)
dΩ = Measure(Ω,degree)

#
# On the one hand, the weak residual is built as follows

using LinearAlgebra: norm
const p = 3
flux(∇u) = norm(∇u)^(p-2) * ∇u
f(x) = 1
res(u,v) = ∫( ∇(v)⊙(flux∘∇(u)) - v*f )*dΩ

# Function `res` is the one representing the integrand of the weak residual $[r(u)](v)$. The first argument of function `res` stands for the function $u\in U_g$, where the residual is evaluated, and the second argument stands for a generic test function $v\in V_0$.
#
# On the other hand,  we (optionally) implement a function `jac` representing the Jacobian.
dflux(∇du,∇u) = (p-2)*norm(∇u)^(p-4)*(∇u⊙∇du)*∇u+norm(∇u)^(p-2)*∇du
jac(u,du,v) = ∫( ∇(v)⊙(dflux∘(∇(du),∇(u))) )*dΩ

# The first argument of function `jac` stands for function $u\in U_g$, where the Jacobian is evaluated. The second argument is a test function $v\in V_0$, and the third argument represents an arbitrary direction $\delta u \in V_0$.
#
# We finally construct the nonlinear FE problem

op = FEOperator(res,jac,Ug,V0)

# Here, we have constructed an instance of `FEOperator`, which is the type that represents a general nonlinear FE problem in Gridap. The constructor takes the functions representing the weak residual and Jacobian, and the test and trial spaces. If only the function for the residual is provided, the Jacobian is computed internally with automatic differentiation.

#
# ## Nonlinear solver phase
#
# We have already built the nonlinear FE problem. Now, the remaining step is to solve it. In Gridap, nonlinear (and also linear) FE problems can be solved with instances of the type `FESolver`.
#
# We construct an instance of `FESolver` as follows:

using LineSearches: BackTracking
nls = NLSolver(
  show_trace=true, method=:newton, linesearch=BackTracking())
solver = FESolver(nls)

# Note that the `NLSolver` function used above internally calls the `nlsolve` function of the [NLsolve](https://github.com/JuliaNLSolvers/NLsolve.jl) package with the provided key-word arguments. Thus, one can use any of the nonlinear methods available via the function `nlsolve` to solve the nonlinear FE problem. Here, we have selected a Newton-Raphson method with a back-tracking line-search from the [LineSearches](https://github.com/JuliaNLSolvers/LineSearches.jl) package.
#
# We are finally in place to solve the nonlinear FE problem. The initial guess is a `FEFunction`, which we build from a vector of random (free) nodal values:

import Random
Random.seed!(1234)
x = rand(Float64,num_free_dofs(Ug))
uh0 = FEFunction(Ug,x)
uh, = solve!(uh0,solver,op)

# We finish this tutorial by writing the computed solution for visualization (see next figure).

writevtk(Ω,"results",cellfields=["uh"=>uh])

# ![](../assets/p_laplacian/sol-plap.png)
#


"""

```
sphericalshellvolume(i,step)
```

Computes the volume of the spherical shell defined within [(i-1)*step,i*step].

"""
function sphericalshellvolume(i, step)
    rmin = (i - 1) * step
    return (4 * pi / 3) * ((rmin + step)^3 - rmin^3)
end


"""
    X = spiraldata(N)

Generates data points on a 2D spiral returned as a N×2 matrix X.
"""
function spiraldata(N, seed=1)

    rg = MersenneTwister(seed)

    t = rand(rg, Uniform(3, 15), N)

    x1 = 0.04 * t .* sin.(t) .+ randn(rg, N)*0.01

    x2 = 0.04 * t .* cos.(t) .+ randn(rg, N)*0.01

    [x1 x2]

end


# ----------------------------------------------------
# --- Import modules
# ----------------------------------------------------
using DigitalComm
using DSP
using Test
# ----------------------------------------------------
# --- Tests
# ----------------------------------------------------
println("Tests for filters");
@testset "Raised Cosine filter test" begin
    # Create the vector
    h = raisedCosine(12,0.5,16)
    # Check type
    @test h isa Vector{Float64}
    # Check size
    sH = 12*16*2+1
    @test length(h)== sH
    # Check it is 1 in the middle
    @test h[1+ (sH-1)÷2] == 1
    # Check Nyquist criterion
    for n ∈ 0 : 11
        @test ≈(h[1 + n*16],0,atol=1e-8)
    end
end

@testset "Raised Cosine filter test" begin
    # Create the vector
    h = sqrtRaisedCosine(12,0.5,16)
    # Check type
    @test h isa Vector{Float64}
    # Check size
    sH = 12*16*2+1
    @test length(h)== sH
    # Check it is 1 in the middle
    @test h[1+ (sH-1)÷2] == 1
    # Check Nyquist criterion
    # sqrt is not a Nyquist filter but h*h is !
    p = conv(h,h)
    p = p /maximum(p)
    for n ∈ 0 : 2*11
        @test ≈(p[1 + n*16],0,atol=1e-4)
    end
end


#using Elefridge
using PyPlot

nbits = 8
nmax = 7
mi,ma = 1e-20,1e20
logmin = log(mi)
logmax = log(ma)
dlog = (logmax-logmin)/(2^nbits-2)              # 2^n-1-1 as 0x00 is reserved for 0
quants = exp.(logmin .+ Array(0:nmax)*dlog)     # the first quantised numbers
half_quants = quants[1:end-1] + diff(quants)/2         # half-way in lin-space
halflog_quants = exp.(logmin .+ (Array(0:nmax-1).+.5)*dlog)    # half-way in log-space
dlin = quants[2]-quants[1]

A = Array(quants[1]:dlin/100:quants[end])

# round to nearest in log space
L1 = round.((log.(A).-logmin)/dlog).+1
A1 = exp.(logmin .+ (L1.-1)*dlog)

B = vcat(A,ma)
A0 = Array(LogQuant8Array(B))

# round to nearest in lin space
c = 1/2 - 1/dlog*log(mi*(exp(dlog)+1)/2)

L2 = round.(c.+log.(A)/dlog).+1
A2 = exp.(logmin .+ (L2.-1)*dlog)

## plot
pygui(true)
fig,ax = subplots(1,1)
ax.plot(A,A1,"k--",lw=2,label="Round-to-nearest in log-space")
ax.plot(A,A2,"C1--",lw=2,label="Round-to-nearest in lin-space")
ax.plot(B,A0,"C2.-")

ax.plot([quants[1],half_quants[1]],[quants[1],quants[1]],"C1",lw=3.5)
ax.plot([quants[1],halflog_quants[1]],[quants[1],quants[1]],"k",lw=1.5)
for i in 1:length(halflog_quants)-1
    x0 = half_quants[i]
    x1 = half_quants[i+1]
    y = quants[i+1]
    ax.plot([x0,x1],[y,y],"C1",lw=3.5)
    x0 = halflog_quants[i]
    x1 = halflog_quants[i+1]
    ax.plot([x0,x1],[y,y],"k",lw=1.5)
end
ax.plot([half_quants[end],quants[end]],[quants[end],quants[end]],"C1",lw=3.5)
ax.plot([halflog_quants[end],quants[end]],[quants[end],quants[end]],"k",lw=1.5)

for (i,x) in enumerate(halflog_quants)
    labl = i==1 ? "Halfway in log-space" : nothing
    ax.axvline(x,color="k",lw=1,label=labl)
end

for (i,x) in enumerate(half_quants)
    labl = i==1 ? "Halfway in lin-space" : nothing
    ax.axvline(x,color="C1",lw=1,label=labl)
end

ax.set_yticks(quants)
ax.set_yticklabels([latexstring("x_{$i}") for i in 1:length(quants)])
ax.set_xticks(quants)
ax.set_xticklabels([latexstring("x_{$i}") for i in 1:length(quants)])
ax.set_xlabel(L"$x$ at full precision")
ax.set_ylabel(L"quantized $x$")

ax.set_xlim(quants[1],quants[end])
ax.set_ylim(quants[1]-dlin/2,quants[end]+dlin)

ax.legend(loc=2,framealpha=1)
ax.set_title("Round-to-nearest for log quantization",loc="left")

tight_layout()


# Exercise 1

using LinearAlgebra
using StaticArrays
using BenchmarkTools

N = 3
A = rand(N, N)
x = rand(N)

@btime $A * $x  # the $ in front of variable names is sometimes important
@btime inv($A)

# Benchmark

A_s = @SMatrix rand(N,N)
x_s =  @SVector rand(N)

@btime  $A_s * $x_s
@btime inv($A_s);

# very fast matrix multiplication, use $ and denote as static vectors
@btime $A_s * $x_s
@show supertypes(typeof(A_s))
@show supertypes(typeof(A));

# Exercise 2

Σ = [0.4  0.3;
     0.3  0.45]
G = I
R = 0.5 * Σ

gain(Σ, G, R) = Σ * G' * inv(G * Σ * G' + R)
@btime gain($Σ, $G, $R)

# Benchmark

Σ_s = @SMatrix [0.4  0.3; 0.3  0.45]
R_s = 0.5 * Σ_s

@show typeof(R) #static array, too

@btime gain($Σ_s, $G, $R_s);

# Exercise 3

using Polynomials

p = Polynomial([2, -5, 2], :x)  # :x just gives a symbol for display

@show p
@show typeof(p)
p′ = derivative(p)   # gives the derivative of p, another polynomial
@show p(0.1), p′(0.1)  # call like a function
@show roots(p);   # find roots such that p(x) = 0

# plot p and p'

r = range(-2.0, 2.0, length = 20)

p_r = p.(r)
p′_r = p′.(r)

plot(r[1:end-1],p′_r[1:end-1],label="Dp(x)")
plot!(r,p_r,label="p(x)")

# Exercise 4

function newtonsmethod(p::Polynomial, x_0; tolerance = 1E-7, maxiter = 100)

    p′ = derivative(p)

    #initialise loop
    iter = 1
    normdiff = Inf
    x_old = x_0

    while normdiff > tolerance && iter < maxiter

        x_new = x_old - p(x_old)/ p′(x_old)
        normdiff = norm(x_new - x_old)

        iter += 1 #continuation
        x_old = x_new
    end
    return (root = x_old, normdiff = normdiff, iter = iter )
end

# Compute roots

p = Polynomial([2, -5, 2], :x)  # :x just gives a symbol for display

@show newtonsmethod(p, 5);   # find roots such that p(x) = 0
@show roots(p)
@btime newtonsmethod(p, 5);
@btime roots(p);

# Exercise 5
function trapezoidal(f::AbstractArray{Float64,1}, x::AbstractArray{Float64,1})
    sol = similar(x)
    del = diff(x)
    for i in 2:length(x)
        sol[i] = (f_x[i]+f_x[i-1])/2*del[i-1]
    end
    return sum(sol)
end

x = range(0.0,1.0,length=9)
f(x) = x^2
f_x = f.(x)

int = trapezoidal(f_x,x)

function trapezoidal2(f::AbstractArray{Float64}, x::AbstractRange{Float64})
    sol = similar(x)
    del = step(x)
    for i in 2:length(x)
        sol[i] = (f_x[i]+ f_x[i-1])/2*del
    end
    return sum(sol)
end

x = range(0.0,1.0,length=1100)
f(x) = x^2
f_x = f.(x)

integral = trapezoidal2(f_x,x)

f(x) = x^3
x̄ = 1.0
x̲ = 0.0
N = 1100;

function trapezoidal3(f, x̲::Real , x̄::Real , N::Int64)
    x = range(x̲, x̄, length = N)
    f_x = f.(x)

    #implementation the same as trapezodal2
    sol = similar(x)
    del = step(x)
    for i in 2:length(x)
        sol[i] = (f_x[i]+ f_x[i-1])/2*del
    end
    return sum(sol)
end

integral = trapezoidal3(f ,x̲ , x̄ , N)

# Exercise 6

using ForwardDiff

function f(a, b; N::Int64 = 50)
    r = range(a, b, length=N) # one
return mean(r)
end

Df(x) = ForwardDiff.derivative(y -> f(0.0, y), x)

@show f(0.0, 3.0)
@show f(0.0, 3.1)

Df(3.0)

function f(a, b; N::Int64 = 50)
    r = range(a, b, length=N) # one
return mean(r)
end


using Test
# using Revise
using PolynomialBasis

PB = PolynomialBasis

function allequal(v1, v2)
    return all(v1 .≈ v2)
end

function f(v)
    x = v[1]
    y = v[2]
    return x^3 + 3y^3 + 2x^2*y + 8x * y
end

function fx(v)
    x = v[1]
    y = v[2]
    return 3x^2 + 4x*y + 8y
end

function fxx(v)
    x = v[1]
    y = v[2]
    return 6*x+4*y
end

function fy(v)
    x = v[1]
    y = v[2]
    return 9y^2 + 2x^2 + 8x
end

function fyy(v)
    x = v[1]
    y = v[2]
    return 18y
end

function fxy(v)
    x = v[1]
    y = v[2]
    return 4x+8
end

function testhessian(v)
    hxx = fxx(v)
    hxy = fxy(v)
    hyy = fyy(v)
    return [hxx,hxy,hyy]
end

function testgradient(v)
    gx = fx(v)
    gy = fy(v)
    return [gx,gy]
end

hermitetp = PB.HermiteTensorProductBasis(2)
interp = PB.InterpolatingPolynomial(1,hermitetp)
points = [-1.0  -1.0   1.0   1.0
          -1.0   1.0  -1.0   1.0]
v = vec(mapslices(f,points,dims=1))
vx = vec(mapslices(fx,points,dims=1))
vy = vec(mapslices(fy,points,dims=1))
vxy = vec(mapslices(fxy,points,dims=1))

coeffs = zeros(16)
coeffs[[1,5,9,13]] .= v
coeffs[[2,6,10,14]] .= vy
coeffs[[3,7,11,15]] .= vx
coeffs[[4,8,12,16]] .= vxy

update!(interp,coeffs)

testp = range(-1.0,stop=1.0,length=4)'
p = PB.tensor_product_points(2,testp)

vals = mapslices(interp,p,dims=1)
testvals = mapslices(f,p,dims=1)
@test allequal(vals,testvals)

grads = mapslices(x->vec(gradient(interp,x)),p,dims=1)
testgrads = mapslices(testgradient,p,dims=1)
@test allequal(grads,testgrads)

hess = mapslices(x->vec(hessian(interp,x)),p,dims=1)
testhess = mapslices(testhessian,p,dims=1)
@test allequal(hess,testhess)


"""
    harmonic(n::Integer)

 Calculates harmonic numbers,
   e.g., see [http://mathworld.wolfram.com/HarmonicNumber.html](http://mathworld.wolfram.com/HarmonicNumber.html)

## Arguments
* ``n`` `::Integer`: non-negative index of the Harmonic number to calculate

## Examples
```jldoctest; setup = :(using Polylogarithms)
julia> harmonic(2)
1.5
```
"""
function harmonic(n::Integer)
    if n < 0
        throw(DomainError(n))
    elseif n==0
        return 0.0 # by convention (see Crandall, p.22)
    elseif n <= 10
        # perform exact sum for small n
        total = 0.0
        for k=1:n
            total +=  1.0 / k
        end
        return total
    else
        return γ + SpecialFunctions.digamma(n+1) # digamma(m) = ψ(m)
    end
end

"""
    harmonic(x::ComplexOrReal{Float64})

 Calculates harmonic numbers extended to non-integer arguments using the
 digamma form.

## Arguments
* ``x`` `::ComplexOrReal{Float64}`: index of the Harmonic number to calculate

## Examples
```jldoctest; setup = :(using Polylogarithms)
julia> harmonic(2.0)
1.5000000000000016
```
"""
function harmonic(x::ComplexOrReal{Float64})
    return γ + SpecialFunctions.digamma(x+1)
end
#        ``digamma(m,x) = (-1)^{m+1} m! \zeta(m+1,x), m>=1``, and integer

"""
    harmonic(n::Integer,r::Real)

 Calculates generalized harmonic numbers,
   e.g., see [http://mathworld.wolfram.com/HarmonicNumber.html](http://mathworld.wolfram.com/HarmonicNumber.html)

## Arguments
* ``n`` `::Integer`: non-negative index 1 of the Harmonic number to calculate
* ``r`` `::Real`: index 2 of the Harmonic number to calculate

It should be possible to extend this to complex r, but that requires more testing.

## Examples
```jldoctest; setup = :(using Polylogarithms)
julia> harmonic(2,1.5)
1.3535533905932737
```
"""
function harmonic(n::Integer, r::Real)
    if n < 0
        throw(DomainError(n))
    end
    if n == 0
        return 0.0
    end
    if r==1
        return harmonic(n)
    end
    total = 0.0
    for k=1:n
        total +=  1.0 / k^r
    end
    return total
end

"""
    harmonic(n::Integer,r::Integer)

 Calculates generalized harmonic numbers
   e.g., see [http://mathworld.wolfram.com/HarmonicNumber.html](http://mathworld.wolfram.com/HarmonicNumber.html)
 using a better approach which works when both inputs are integers
 [https://carma.newcastle.edu.au/resources/jon/Preprints/Papers/Published-InPress/Oscillatory%20(Tapas%20II)/Papers/coffey-zeta.pdf](https://carma.newcastle.edu.au/resources/jon/Preprints/Papers/Published-InPress/Oscillatory%20(Tapas%20II)/Papers/coffey-zeta.pdf), p.341

## Arguments
* ``n`` `::Integer`: non-negative index 1 of the Harmonic number to calculate
* ``r`` `::Integer`: index 2 of the Harmonic number to calculate

## Examples
```jldoctest; setup = :(using Polylogarithms)
julia> harmonic(2,1)
1.5000000000000002
```
"""
function harmonic(n::Integer, r::Integer)
    if r<1
        throw(DomainError(r))
    end
    return (-1)^(r-1) * ( SpecialFunctions.polygamma(r-1,n+1) - SpecialFunctions.polygamma(r-1,1) ) / SpecialFunctions.gamma(r)
end

# # better approach to calculation, but doesn't seem to work
# #  using
# function harmonic3(x::Real, r::Integer)
#     if (r < 1)
#         throw(DomainError(r))
#     end
#     return SpecialFunctions.zeta(r,1) - SpecialFunctions.zeta(r,x+1)
# end


using OrdinaryDiffEq
using DiffEqFlux, Flux
using Random
using Plots;

Random.seed!(123);

function sir_ode(u,p,t)
    (s,i,r) = u
    (β,γ) = p
    ds = -β*s*i
    di = β*s*i - γ*i
    dr = γ*i
    [ds,di,dr]
end;

p = [0.5,0.25]
u0 = [0.99, 0.01, 0.0]
tspan = (0.0, 40.0)
δt = 1;

solver = Rodas5();

sir_prob = ODEProblem(sir_ode, u0, tspan, p)
sir_sol = solve(sir_prob, solver, saveat = δt);

train_time = 30.0
train_data = Array(sir_sol(0:δt:train_time));

nhidden = 8
sir_node = FastChain(FastDense(3, nhidden, tanh),
                     FastDense(nhidden, nhidden, tanh),
                     FastDense(nhidden, nhidden, tanh),
                     FastDense(nhidden, 3));

p_ = Float64.(initial_params(sir_node));
function dudt_sir_node(u,p,t)
    s,i,r = u
    ds,di,dr = ann_node([s,i,r],p)
    [ds,di,dr]
end
prob_node = ODEProblem(dudt_sir_node, u0, tspan, p_);

tspan_train = (0,train_time)
prob_node = NeuralODE(sir_node,
                      tspan_train,
                      solver,
                      saveat=δt,
                      sensealg = InterpolatingAdjoint(autojacvec=ReverseDiffVJP()))
nump = length(prob_node.p)

function loss(p)
    sol = prob_node(u0,p)
    pred = Array(sol)
    sum(abs2, (train_data .- pred)), pred
end;

const losses = []
callback = function (p, l, pred)
    push!(losses, l)
    numloss = length(losses)
    if numloss % 50 == 0
        display("Epoch: " * string(numloss) * " Loss: " * string(l))
    end
    return false
end;

res_node = DiffEqFlux.sciml_train(loss,
                                   prob_node.p,
                                   cb = callback);

plot(losses, yaxis = :log, xaxis = :log, xlabel = "Iterations", ylabel = "Loss", legend = false)

prob_node = NeuralODE(sir_node,
                      tspan_train,
                      solver,
                      saveat=δt,
                      sensealg = InterpolatingAdjoint(autojacvec=ReverseDiffVJP()),
                      p = res_node)
sol_node = prob_node(u0);

scatter(sir_sol, label=["True Susceptible" "True Infected" "True Recovered"])
plot!(sol_node, label=["Estimated Susceptible" "Estimated Infected" "Estimated Recovered"])

tspan_test = (0.0, 40.0)
prob_node_test = NeuralODE(sir_node,
                      tspan_test,
                      solver,
                      saveat=δt,
                      sensealg = InterpolatingAdjoint(autojacvec=ReverseDiffVJP()),
                      p = res_node)
sol_node_test = prob_node_test(u0);

p_node = scatter(sol_node_test, legend = :topright, label=["True Susceptible" "True Infected" "True Recovered"], title="Neural ODE Extrapolation: training until t=30")
plot!(p_node,sol_node_test, lw=5, label=["Estimated Susceptible" "Estimated Infected" "Estimated Recovered"])

newu0 = [0.95, 0.05, 0.0]
sir_prob_u0 = remake(sir_prob,u0=newu0)
sir_sol_u0 = solve(sir_prob_u0, solver, saveat =  δt)
node_sol_u0 = prob_node(newu0)
p_node = scatter(sir_sol_u0, legend = :topright, label=["True Susceptible" "True Infected" "True Recovered"], title="Neural ODE with different initial conditions")
plot!(p_node,node_sol_u0, lw=5, label=["Estimated Susceptible" "Estimated Infected" "Estimated Recovered"])

tspan_train2 = (0.0,20.0)
prob2 = ODEProblem(sir_ode, u0, tspan_train2, p)
sol2 = solve(prob2, solver, saveat = δt)
data2 = Array(sol2)
solver2 = ExplicitRK()
prob_node2 = NeuralODE(sir_node,
                      tspan_train2,
                      solver2,
                      saveat=δt,
                      sensealg = InterpolatingAdjoint(autojacvec=ReverseDiffVJP()))
function loss2(p)
    sol = prob_node2(u0,p)
    pred = Array(sol)
    sum(abs2, (data2 .- sol)), pred
end
const losses2 = []
callback2 = function (p, l, pred)
    push!(losses2, l)
    numloss = length(losses2)
    if numloss % 50 == 0
        display("Epoch: " * string(numloss) * " Loss: " * string(l))
    end
    return false
end
res_node2 = DiffEqFlux.sciml_train(loss2,
                                   prob_node2.p,
                                   cb = callback2);

prob_node2_test = NeuralODE(sir_node,
                      tspan_test,
                      solver,
                      saveat=δt,
                      sensealg = InterpolatingAdjoint(autojacvec=ReverseDiffVJP()),
                      p = res_node2)
sol_node2_test = prob_node2_test(u0);

p_node2 = scatter(sir_sol, legend = :topright, label=["True Susceptible" "True Infected" "True Recovered"], title="Neural ODE Extrapolation: training until t=20")
plot!(p_node2, sol_node2_test, lw=5, label=["Estimated Susceptible" "Estimated Infected" "Estimated Recovered"])

function loss3(p)
    sol = prob_node(u0,p)
    pred = Array(sol)
    sum(abs2, (train_data[2,:] .- sol[2,:])), pred
end
const losses3 = []
callback3 = function (p, l, pred)
    push!(losses3, l)
    numloss = length(losses3)
    if numloss % 50 ==0
        display("Epoch: " * string(numloss) * " Loss: " * string(l))
    end
    return false
end
res_node3 = DiffEqFlux.sciml_train(loss3,
                                   prob_node.p,
                                   cb = callback3);

prob_node3_test = NeuralODE(sir_node,
                      tspan_test,
                      solver,
                      saveat=δt,
                      sensealg = InterpolatingAdjoint(autojacvec=ReverseDiffVJP()),
                      p = res_node3)
sol_node3_test = prob_node3_test(u0);

p_node3 = scatter(sir_sol, legend = :topright, label=["True Susceptible" "True Infected" "True Recovered"], title="Neural ODE Extrapolation: training on I(t) until t=30")
plot!(p_node3, sol_node3_test, lw=5, label=["Estimated Susceptible" "Estimated Infected" "Estimated Recovered"])


"""

```
shellradius(i,step)
```

Compute the point in which the radius comprises half of the
volume of the shell.
"""
function shellradius(i,step)
  rmin = (i-1)*step
  return ( 0.5*( (rmin+step)^3 + rmin^3 ) )^(1/3)
end


using QuadGK

sup = (-1,1)
f1(x) = 3/4*(1-x^2)
f2(x) = x < 0 ? x+1 : 1-x

expect(f,support) = quadgk((x) -> x*f(x),support...)[1]

println("Mean 1: ", expect(f1,sup))
println("Mean 2: ", expect(f2,sup))


module aoc_2019_01

ingest(path) = [parse(Int, l) for l in readlines(path)]

function calculate_fuel_cost(mass)
    return (mass ÷ 3) - 2
end

function calculate_fuel_cost_recursive(mass)
    fuel = (mass ÷ 3) - 2
    fuel > 0 || return 0
    return fuel + calculate_fuel_cost_recursive(fuel)
end

function solve(path, calculator)
    input_values = ingest(path)
    return sum(calculator(m) for m in input_values)
end

solve_part_one(path) = solve(path, calculate_fuel_cost)
solve_part_two(path) = solve(path, calculate_fuel_cost_recursive)

end # module


#=

# 107: 1D Nonlinear Storage
([source code](SOURCE_URL))

This equation comes from the transformation of the nonlinear diffuision equation.
```math
\partial_t u^\frac{1}{m} -\Delta u = 0
```
in $\Omega=(-1,1)$ with homogeneous Neumann boundary conditions.
We can derive an exact solution from the Barenblatt solution of the previous
example.

=#

module Example107_NonlinearStorage1D
using Printf
using VoronoiFVM
using ExtendableGrids
using GridVisualize

function barenblatt(x,t,m)
    tx=t^(-1.0/(m+1.0))
    xx=x*tx
    xx=xx*xx
    xx=1- xx*(m-1)/(2.0*m*(m+1));
    if xx<0.0
        xx=0.0
    end
    return tx*xx^(1.0/(m-1.0))
end

function main(;n=20,m=2.0,Plotter=nothing,verbose=false, unknown_storage=:sparse,tend=0.01,tstep=0.0001)

    ## Create a one-dimensional discretization
    h=1.0/convert(Float64,n/2)
    X=collect(-1:h:1)
    grid=VoronoiFVM.Grid(X)

    ## Flux function which describes the flux
    ## between neigboring control volumes
    function flux!(f,u0,edge)
        u=unknowns(edge,u0)
        f[1]=u[1,1]-u[1,2]
    end

    ϵ=1.0e-10

    ## Storage term
    ## This needs to be regularized as its derivative
    ## at 0 is infinity
    function storage!(f,u,node)
        f[1]=(ϵ+u[1])^(1.0/m)
    end

    ## Create a physics structure
    physics=VoronoiFVM.Physics(
        flux=flux!,
        storage=storage!)

    ## Create a finite volume system - either
    ## in the dense or  the sparse version.
    ## The difference is in the way the solution object
    ## is stored - as dense or as sparse matrix
    sys=VoronoiFVM.System(grid,physics,unknown_storage=unknown_storage)

    ## Add species 1 to region 1
    enable_species!(sys,1,[1])

    ## Create a solution array
    inival=unknowns(sys)
    solution=unknowns(sys)
    t0=0.001

    ## Broadcast the initial value
    inival[1,:].=map(x->barenblatt(x,t0,m)^m,X)

    ## Create solver control info
    control=VoronoiFVM.NewtonControl()
    control.verbose=verbose
    control.Δu_opt=0.1
    control.force_first_step=true
    tsol=solve(inival,sys,[t0,tend],control=control)

    p=GridVisualizer(Plotter=Plotter,layout=(1,1),fast=true)
    for i=1:length(tsol)
        time=tsol.t[i]
        scalarplot!(p[1,1],grid,tsol[1,:,i],title=@sprintf("t=%.3g",time),color=:red,label="numerical")
        scalarplot!(p[1,1],grid,map(x->barenblatt(x,time,m)^m,grid),clear=false,color=:green,label="exact")
        reveal(p)
        sleep(1.0e-2)
    end

    return sum(tsol[end])
end

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

end


#=
## Integration with `NetworkDynamics.jl`
In this example we model a Network based on the [Kuroamoto model](https://en.wikipedia.org/wiki/Kuramoto_model) using the [`NetworkDynamics.jl`](https://github.com/FHell/NetworkDynamics.jl) package.
=#

using NetworkDynamics
using Graphs
using BlockSystems
using ModelingToolkit
using OrdinaryDiffEq
using Plots

#=
In the Kuramoto model each vertex $i$ has it's own intrinsic angular frequency $\omega_i$.
The angle of frequency is given by
```math
\dot\phi = \omega_i + \sum e
```
where $\sum e$ sums over all the connected edges.
=#
@parameters t ω edgesum(t)
@variables ϕ(t)
D = Differential(t)

kmvert = IOBlock([D(ϕ) ~ ω + edgesum],
                 [edgesum],
                 [ϕ],
                 name=:kmvert)

function generate_ode_vertex(iob::IOBlock, aggregator; kwargs...)
    gen = generate_io_function(iob; kwargs...)
    f = (du, u, edges, p, t) -> gen.f_ip(du, u, aggregator(edges), p, t)
    vars = ModelingToolkit.tosymbol.(gen.states)
    ODEVertex(f = f, dim = length(vars), sym = vars)
end

## allocation free oop aggregator. might be more difficult for more-dimensional systems
## unfortunately there are no vector variables in MDK and we can't model the aggregators
## as an IOSystem.
function sumedges(edges)
    r = 0.0
    for e in edges
        r += e[1]
    end
    return r
end

vertex = generate_ode_vertex(kmvert, sumedges)
nothing #hide

#=
The edges are defined as
```math
e_{i,j} = K \cdot \mathrm{sin}(\phi_i - \phi_j)
```
For the (static) edges we can't used IOSystem function building in the moment.
Well, we could but than we'd get an `ODEEdge` with zero-massmatrix.
=#
@parameters v₁(t) v₂(t) K
edge_ip = build_function([K*sin(v₁[1] - v₂[1])], [v₁], [v₂], K, t, expression=Val{false})[2]
edge = StaticEdge(f = edge_ip, dim = 1, coupling=:antisymmetric)

nothing #hide
#=
Lets runt the [same example](https://fhell.github.io/NetworkDynamics.jl/dev/heterogeneous_system/#Heterogenous-parameters) as in `NetworkDynamics.jl`
=#
N = 8
g = watts_strogatz(N,2,0) # ring network
nd = network_dynamics(vertex, edge, g)

ω = collect(1:N)./N
ω .-= sum(ω)/N
K = 3.
p = (ω, K); # p[1] vertex parameters, p[2] edge parameters

x0 = collect(1:N)./N
x0 .-= sum(x0)./N

#src using BenchmarkTools
#src @btime $nd($x0, $x0, $p, 0.0)

tspan = (0., 10.)
prob = ODEProblem(nd, x0, tspan, p)
sol = solve(prob, Tsit5())
plot(sol, ylabel="ϕ")


# SPDX-License-Identifier: MIT

"""
    struct TropicalSemiring{T<:AbstractFloat} <: Semiring
        val::T
    end

Log-semifield is defined as :
  * ``x \\oplus y \\triangleq \\text{max}(x, y)``
  * ``x \\otimes y \\triangleq x + y``
``\\forall x, y \\in \\mathbb{R}``.
"""
struct TropicalSemiring{T<:AbstractFloat} <: Semiring
    val::T
end

Base.:+(x::TropicalSemiring, y::TropicalSemiring) =
    TropicalSemiring(max(x.val, y.val))
Base.:*(x::TropicalSemiring, y::TropicalSemiring) =
    TropicalSemiring(x.val + y.val)
Base.zero(::Type{TropicalSemiring{T}}) where T = TropicalSemiring{T}(T(-Inf))
Base.one(::Type{TropicalSemiring{T}}) where T = TropicalSemiring{T}(T(0))
Base.isless(x::TropicalSemiring, y::TropicalSemiring) = isless(x.val, y.val)
Base.typemin(x::Type{TropicalSemiring{T}}) where T =
    TropicalSemiring{T}(typemin(T))
Base.typemax(x::Type{TropicalSemiring{T}}) where T =
    TropicalSemiring{T}(typemax(T))
Base.conj(x::Type{TropicalSemiring{T}}) where T = TropicalSemiring{T}(conj(x.val))


### A Pluto.jl notebook ###
# v0.16.1

using Markdown
using InteractiveUtils

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

# ╔═╡ 3f890c7b-44ad-426a-bf26-32ff17fa8ec4
md"""
# Examples in Julia - Tutorial 1
"""

# ╔═╡ 5bc2c872-fded-4bc8-8044-9eda1822c505
md"""
## Assignment 1

Using the package `Polynomials.jl`, write the function which implements
[Graeffe's method](https://en.wikipedia.org/wiki/Graeffe%27s_method)
(see also [here](http://mathworld.wolfram.com/GraeffesMethod.html))
for computing roots of polynomials with only real roots with simple moduli.

In the function, use Julia's `BigFloat` numbers to overcome the main disadvantage of the method. What is the number of significant decimal digits, and the largest and the smallest number?

Test the function on the [Wilkinson's polynomial](https://en.wikipedia.org/wiki/Wilkinson%27s_polynomial) $\omega(x)$, and the [Chebyshev polynomial](https://en.wikipedia.org/wiki/Chebyshev_polynomials) $T_{50}(x)$
(the latter needs to be transformed in order to apply the method).

Compare your solutions with the exact solutions.
"""

# ╔═╡ 5b542ad3-8471-4adc-9c89-d647000b8ca0
md"""
## Assignment 2

Write the function which computes simple LU factorization (without pivoting) where the matrix is overwritten by the factors.

Make sure that the function also works with block-matrices.

Compare the speed on standard matrices and block-matrices with the built-in LU factorization (which also uses block algorithm AND pivoting). Check the accuracy.
"""

# ╔═╡ dbd0a150-2ad2-49e3-b585-d34bfc2ff3b8
md"""
## Assignment 3

Use the function `eigvals()` to compute the eigenvalues of $k$ random matrices (with uniform and normal distribution of elements) of order $n$.

Plot the results using the macro `@bind` from the package `PlutoUI.jl`.
Use `Plots.jl` for plotting.

Are the eigenvalues random? Can you describe their behaviour? Can random matrices be used to test numerical algorithms?
"""

# ╔═╡ 4cd45baf-67f6-43cc-a9bf-3a89006c3814

# ╔═╡ Cell order:
# ╟─3f890c7b-44ad-426a-bf26-32ff17fa8ec4
# ╟─5bc2c872-fded-4bc8-8044-9eda1822c505
# ╟─5b542ad3-8471-4adc-9c89-d647000b8ca0
# ╟─dbd0a150-2ad2-49e3-b585-d34bfc2ff3b8
# ╠═4cd45baf-67f6-43cc-a9bf-3a89006c3814

src/Module A - Short Julia Course/T1 Examples in Julia.jl


module MyInterpolations

export my_lin_interp

function my_lin_interp(grid, vals)
  function func(x)
    for i in 1:(length(grid)-1)
      if (grid[i]<=x)&&(x<=grid[i+1])
        return ((vals[i+1]-vals[i])/(grid[i+1]-grid[i]))*(x-grid[i])+vals[i]
      end
    end
  end

  return func
end

end


using Quadrature , Distributions , Test

μ = [0.00 , 0.00]
Σ = [0.4 0.0 ; 0.00 0.4]
d = MvNormal(μ , Σ)
m(x , p) = pdf(d, x)
prob = QuadratureProblem(m, [-Inf , -Inf] , [Inf , Inf])
sol = solve(prob,HCubatureJL(),reltol=1e-3,abstol=1e-3)
@test  (1.00 - sol.u)^2  < 1e-6

μ = [0.00 , 0.00]
Σ = [0.4 0.0 ; 0.00 0.4]
d = MvNormal(μ , Σ)
m(x , p) = pdf(d, x)
prob = QuadratureProblem(m, [-Inf , 0.00] , [Inf , Inf])
sol = solve(prob,HCubatureJL(),reltol=1e-3,abstol=1e-3)
@test  (0.500 - sol.u)^2  < 1e-6

f(x , p) = pdf(Normal(0.00 , 1.00) , x)
prob = QuadratureProblem(f, -Inf , Inf)
sol = solve(prob,HCubatureJL(),reltol=1e-3,abstol=1e-3)
@test  (1.00 - sol.u)^2  < 1e-6

f(x , p) = pdf(Normal(0.00 , 1.00) , x)
prob = QuadratureProblem(f, -Inf , 0.00)
sol = solve(prob,HCubatureJL(),reltol=1e-3,abstol=1e-3)
@test  (0.50 - sol.u)^2  < 1e-6

f(x , p) = (1 / (x^2 + 1))
prob = QuadratureProblem(f, 0 , Inf)
sol = solve(prob,HCubatureJL(),reltol=1e-3,abstol=1e-3)
@test  (pi/2 - sol.u)^2  < 1e-6


using AbstractPlotting
using AbstractPlotting.MakieLayout
using GLMakie

using QuadGK

function riemann(f::Function, a::Real, b::Real, n::Int; method="right")
    if method == "right"
        meth = f -> (lr -> begin l,r = lr; f(r) * (r-l) end)
    elseif method == "left"
        meth = f -> (lr -> begin l,r = lr; f(l) * (r-l) end)
    elseif method == "trapezoid"
        meth = f -> (lr -> begin l,r = lr; (1/2) * (f(l) + f(r)) * (r-l) end)
    elseif method == "simpsons"
        meth = f -> (lr -> begin l,r=lr; (1/6) * (f(l) + 4*(f((l+r)/2)) + f(r)) * (r-l) end)
    end

    xs = a .+ (0:n) * (b-a)/n

    sum(meth(f), zip(xs[1:end-1], xs[2:end]))
end

"""
    integration(f)

Show graphically the left Riemann approximation, the trapezoid approximation, and Simpson's approximation to the integral of `f` over [-1,1].

Assumes `f` is non-negative.
"""
function integration(f=nothing)
    if f == nothing
        f = x -> x^2*exp(-x/3)
    end

    a, b = -1, 1

function left_riemann_pts(n)
    xs = range(a, b, length=n+1)
    pts = Point2f0[(xs[1], 0)]
    for i in 1:n
        xᵢ, xᵢ₊₁ = xs[i], xs[i+1]
        fᵢ = f(xᵢ)
        push!(pts, (xᵢ, fᵢ))
        push!(pts, (xᵢ₊₁, fᵢ))
    end
    push!(pts, (xs[end], 0))
    pts
end

function trapezoid_pts(n)
    xs = range(a, b, length=n+1)
    pts = Point2f0[(xs[1], 0)]
    for i in 1:n
        xᵢ, xᵢ₊₁ = xs[i], xs[i+1]
        fᵢ = f(xᵢ)
        push!(pts, (xᵢ, f(xᵢ)))
    end
    push!(pts, (xs[end], f(xs[end])))
    push!(pts, (xs[end], 0))
    pts
end

function simpsons_pts(n)
    xs = range(a, b, length=n+1)
    pts = Point2f0[(xs[1], 0), (xs[1], f(xs[1]))]

    for i in 1:n
        xi, xi1 = xs[i], xs[i+1]
        m = xi/2 + xi1/2
        p = x -> f(xi)*(x-m)*(x - xi1)/(xi-m)/(xi-xi1) + f(m) * (x-xi)*(x-xi1)/(m-xi)/(m-xi1) + f(xi1) * (x-xi)*(x-m) / (xi1-xi) / (xi1-m)

        xs′ = range(xi, xi1, length=10)
        for j in 2:10
            x = xs′[j]
            push!(pts, (x, p(x)))
        end
    end
    push!(pts, (xs[end], 0))
    pts
end

# where we lay our scene:
scene, layout = layoutscene()
layout.halign = :left
layout.valign = :top

p1 = layout[1,1] = LScene(scene)
p2 = layout[1,2] = LScene(scene)
p3 = layout[1,3] = LScene(scene)

n = layout[2,1] = LSlider(scene, range=2:25, startvalue=2)
output = layout[2, 2:3] = LText(scene, "...")

lpts = lift(n.value) do n
    left_riemann_pts(n)
end

poly!(p1.scene, lpts, color=:gray75)
lines!(p1.scene, a..b, f, color=:black, strokewidth=10)
title(p1.scene, "Left Riemann")

tpts = lift(n.value) do n
    trapezoid_pts(n)
end
poly!(p2.scene, tpts, color=:gray75)
lines!(p2.scene, a..b, f, color=:black, strokewidth=10)
title(p2.scene, "Trapezoid")

spts = lift(n.value) do n
    simpsons_pts(n)
end
poly!(p3.scene, spts, color=:gray75)
lines!(p3.scene, a..b, f, color=:black, strokewidth=10)
title(p3.scene, "Simpson's")

on(n.value) do n
    actual,err = quadgk(f, -1, 1)
    lrr  = riemann(f, -1, 1, n, method="left")
    trap = riemann(f, -1, 1, n, method="trapezoid")
    simp = riemann(f, -1, 1, n, method="simpsons")

    Δleft = round(abs(lrr - actual), digits=8)
    Δtrap = round(abs(trap - actual), digits=8)
    Δsimp = round(abs(simp - actual), digits=8)

    txt = "Riemann: $Δleft, Trapezoid: $Δtrap, Simpson's: $Δsimp"
    output.text[] = txt
end
n.value[] = n.value[]

scene
end


@doc raw"""
    Ellipsoid(a, b, f, finv, e2, ϵ2, name)
    Ellipsoid(a, finv, name)

Earth ellipsoid model.

# Fields

- `a::Real`: semi-major axis (m).
- `b::Real`: semi-minor axis (m).
- `f::Real`: flattening (``f``).
- `finv::Real`: inverse of flattening (``f^{-1}``).
- `e2::Real`: eccentricity squared.
- `ϵ2::Real`: second eccentricity squared.
- `name::String`: ellipsoid name.

# Notes

``a`` is the semi-major axis, ``b`` is the semi-minor axis. Ellipsoids are normally defined
by ``a`` and ``f^{-1}``. The rest of parameters are derived.

- Flattening:
```math
f = 1 - \frac{b}{a}
```
- Eccentricity (first eccentricity):
```math
e^2 = \frac{a^2 - b^2}{a^2} = f (2 - f)
```
- Second eccentricity:
```math
ϵ^2 = \frac{a^2 - b^2}{b^2} = \frac{f (2 - f)}{(1 - f)^2}
```

# References

1. Stevens, B. L., Lewis, F. L., (1992). Aircraft control and simulation: dynamics, controls
   design, and autonomous systems. John Wiley & Sons. Section 1.6 (page 23)
2. Rogers, R. M. (2007). Applied mathematics in integrated navigation systems. American
   Institute of Aeronautics and Astronautics. Chapter 4 (Nomenclature differs from 1).
3. Bowring, B. R. (1976). Transformation from spatial to geographical coordinates. Survey
   review, 23(181), 323-327.
"""
struct Ellipsoid
    "Semi-major axis (m)."
    a::Real
    "Semi-minor axis (m)."
    b::Real
    "Flattening ``f = \frac{a-b}{a} = a - \frac{b}{a}``."
    f::Real
    "Inverse of flattening."
    finv::Real
    "Eccentricity. ``e^2 = \frac{a^2 - b^2}{a^2} = f (2 - f)``"
    e2::Real
    "Second eccentricity. ``ϵ^2 = \frac{a^2 - b^2}{b^2} = \frac{f (2 - f)}{(1 - f)^2}``."
    ϵ2::Real
    "Ellipsoid name."
    name::String
end

function Ellipsoid(a, finv, name)
    f = 1 / finv
    b = a * (1 - f)
    e2 = f * (2 - f)
    ϵ2 = e2 / (1 - f)^2
    return Ellipsoid(a, b, f, finv, e2, ϵ2, name)
end

# Rogers, R. M. (2007). Applied mathematics in integrated navigation systems.
# American Institute of Aeronautics and Astronautics. (Page 76, table 4.1)
const Clarke1866 = Ellipsoid(6378206.4, 294.9786982, "Clarke1866")
const Clarke1880 = Ellipsoid(6378249.145, 294.465, "Clarke1880")
const International = Ellipsoid(6378388.0, 297.0, "International")
const Bessel = Ellipsoid(6377397.155, 299.1528128, "Bessel")
const Everest = Ellipsoid(6377276.345, 300.8017, "Everest")
const ModifiedEverest = Ellipsoid(6377304.063, 300.8017, "ModifiedEverest")
const AustralianNational = Ellipsoid(6378160.0, 298.25, "AustralianNational")
const SouthAmerican1969 = Ellipsoid(6378160.0, 298.25, "SouthAmerican1969")
const Airy = Ellipsoid(6377564.396, 299.3249646, "Airy")
const ModifiedAiry = Ellipsoid(6377340.189, 299.3249646, "ModifiedAiry")
const Hough = Ellipsoid(6378270.0, 297.0, "Hough")
const Fischer1960SouthAsia = Ellipsoid(6378155.0, 298.3, "Fischer1960SouthAsia")
const Fischer1960Mercury = Ellipsoid(6378166.0, 298.3, "Fischer1960Mercury")
const Fischer1968 = Ellipsoid(6378150.0, 298.3, "Fischer1968")
const WGS60 = Ellipsoid(6378165.0, 298.3, "WGS60")
const WGS66 = Ellipsoid(6378145.0, 298.25, "WGS66")
const WGS72 = Ellipsoid(6378135.0, 298.26, "WGS72")
const WGS84 = Ellipsoid(6378137.0, 298.257223563, "WGS84")

"""
    llh2ecef(lat, lon, height; ellipsoid=WGS84)

Transform geodetic latitude, longitude (rad) and ellipsoidal height (m) to ECEF for the
given ellipsoid (default ellipsoid is WGS84).

# References

1. Rogers, R. M. (2007). Applied mathematics in integrated navigation systems. American
   Institute of Aeronautics and Astronautics. (Page 75, equations 4.20, 4.21, 4.22)
"""
function llh2ecef(lat, lon, height; ellipsoid=WGS84)
    f = ellipsoid.f
    a = ellipsoid.a
    e2 = ellipsoid.e2

    var = a / sqrt(1.0 - e2 * sin(lat)*sin(lat))

    px = (var + height) * cos(lat)*cos(lon)
    py = (var + height) * cos(lat)*sin(lon)
    pz = (var * (1.0 - e2) + height)* sin(lat)

    return [px, py, pz]
end

"""
    ecef2llh(xecef, yecef, zecef; ellipsoid=WGS84)

Transform ECEF coordinates to geodetic latitude, longitude (rad) and ellipsoidal height (m)
for the given ellipsoid (default ellipsoid is WGS84).

# Notes

* The transformation is direct without iterations as [1] introduced the need to iterate for
  near Earth positions.
* [2] is an update of increased accuracy of [1]. The former is used in this implementation
  although the latter implementation is commented in the code.
* Model becomes unstable if latitude is close to 90º. An alternative equation
  can be found in [2] equation (16) but has not been implemented.

 # References

1. Bowring, B. R. (1976). Transformation from spatial to geographical coordinates. Survey
   review, 23(181), 323-327.
2. Bowring, B. R. (1985). The accuracy of geodetic latitude and height equations. Survey
   Review, 28(218), 202-206.
"""
function ecef2llh(xecef, yecef, zecef; ellipsoid=WGS84)

    x, y, z = xecef, yecef, zecef
    e = sqrt(ellipsoid.e2);
    e2 = ellipsoid.e2
    ϵ2 = ellipsoid.ϵ2
    a = ellipsoid.a
    b = ellipsoid.b

    p = sqrt(x*x + y*y)
    R = sqrt(p*p + z*z)
    θ = atan(z, p)
    # [1] equation (1) does not change in [2]
    lon = atan(y, x)
    # u -> geographical latitude
    # [1] below equation (4) and [2] equation (6)
    # This lead to errors in latitud with maximum value 0.0018"
    #u = atan(a/b * z/x)

    # [2] equation (17) If the latitude is also required to be very accurate
    # for outer-space positions then the value of u for (6) should be obtained
    # from:
    u = atan(b*z / (a*p) * (1 + ϵ2 * b / R))
    # [1] equation (4)
    #lat = atan((z + ϵ2 * b * sin(u)^3) / (x - e2 * a * cos(u)^3))
    # [2] equation (18)
    lat = atan((z + ϵ2 * b * sin(u)^3) / (p - e2 * a * cos(u)^3))
    # [2] after equation (1)
    v = a / sqrt(1.0 - e2*sin(lat)^2)
    # [2] equation (7)
    # height = p*cos(lat) + z*sin(lat) - a*a / v
    # equivalent to [2] equation (8)
    height = R * cos(lat - θ) - a*a / v
    # which is insensitive to the error in latitude calculation (The influence
    # is of order 2 [2] equation (12))
    return [lat, lon, height]
end


"""
    mat_center(X; mat = "her")

This function centers input.
If X is a Hermitian matrix, then ``Xcen = X - Tr(X)/N*I```.
If X is a rectangular matrix, then ``Xcen = X - X*ones(M,1)*ones(1,M)/M``.

# Arguments
-   `X`: a Hermitian or rectangular matrix
-   `mat`: the type of `X`, valid options are "her" and "rec"

# Outputs:
-   `Xc`: centered version of `X`
"""
function mat_center(X::Array{T,2}; mat = "her") where T <: Number
    N, M = size(X)

    if mat == "her"
        # for Hermitian matrix, X = X - I*tr(X)/N
        if M != N
            error("X should be a symmetric matrix")
        end
        Xc = X - I*tr(X)/M
    elseif mat == "rec"
        # for rectangular matrix, A = A - mean(A)*ones(N, M)
        Xc = X - mean(X)*ones(N, M)
    end

    return Xc
end

"""
    free_whiten(Z; mat = "her")

This function whitens input array of matrices in the free probability sense

# Arguments
-   `Z`: an array of matrices of mat and of the same dimensions
-   `mat`: the type of Z[i], valid options are "her" and "rec"

# Outputs
-   `Y`: an array of centered matrices, whitened in the free probability sense,
       that is, Tr(Y[i]*Y[j]')/size(Y[1],1) = (i == j)
-   `U`: a matrix of size s-by-s, where s = size(Z, 1)
-   `Σ`: a matrix of size s-by-s,
"""
function free_whiten(Z::Array{Array{T, 2}}; mat = "her") where T <: Number
    Zc = mat_center.(Z; mat = mat)
    N = size(Zc[1], 1)

    # compute the cov matrix such that
    # cov[i,j] = tr(Zc[i]*Zc[j]')/N
    eigcov = eigen(real(tr.(Zc*Zc'/N)));
    Λ, U = eigcov.values, eigcov.vectors # only take the real part

    Λ[Λ.<0] .= 0 # keep non-negative eigs
    Σ = Diagonal(Λ).^0.5
    Y = U*pinv(Σ)*U'*Zc

    return Y, U, Σ
end

# if Z is just a single matrix, we first convert it to [Y].
function free_whiten(Z::Array{T, 2}; mat = "her") where T <: Number
    return free_whiten([Z]; mat = mat)
end


# Rational Quadratic Isotropic Covariance Function

"""
    RQIso <: Isotropic{SqEuclidean}

Isotropic Rational Quadratic kernel (covariance)
```math
k(x,x') = σ²(1 + (x - x')ᵀ(x - x')/(2αℓ²))^{-α}
```
with length scale ``ℓ``, signal standard deviation ``σ``, and shape parameter ``α``.
"""
mutable struct RQIso{T<:Real} <: Isotropic{SqEuclidean}
    "Squared length scale"
    ℓ2::T
    "Signal variance"
    σ2::T
    "Shape parameter"
    α::T
    "Priors for kernel parameters"
    priors::Array
end

"""
Rational Quadratic Isotropic Covariance Function

    RQIso(ll:T, lσ::T, lα::T)

# Arguments
  - `ll::Real`: length scale (given on log scale)
  - `lσ::Real`: signal standard deviation (given on log scale)
  - `lα::Real`: shape parameter (given on log scale)
"""
RQIso(ll::T, lσ::T, lα::T) where T = RQIso{T}(exp(2 * ll), exp(2 * lσ), exp(lα), [])

function set_params!(rq::RQIso, hyp::AbstractVector)
    length(hyp) == 3 || throw(ArgumentError("Rational Quadratic function has three parameters"))
    rq.ℓ2, rq.σ2, rq.α = exp(2 * hyp[1]), exp(2 * hyp[2]), exp(hyp[3])
end

get_params(rq::RQIso{T}) where T = T[log(rq.ℓ2) / 2, log(rq.σ2) / 2, log(rq.α)]
get_param_names(rq::RQIso) = [:ll, :lσ, :lα]
num_params(rq::RQIso) = 3

cov(rq::RQIso, r::Number) = rq.σ2 * (1 + r / (2 * rq.α * rq.ℓ2))^(-rq.α)

@inline dk_dll(rq::RQIso, r::Real) =
    (s = r / rq.ℓ2; rq.σ2 * s * (1 + s / (2 * rq.α))^(-rq.α - 1)) # dK_d(log ℓ)dK_dℓ
@inline function dk_dlα(rq::RQIso, r::Real)
    s = r / rq.ℓ2
    part = 1 + s / (2 * rq.α)
    rq.σ2 * part^(-rq.α) * (s / (2 * part) - rq.α * log(part))  # dK_d(log α)
end
@inline function dk_dθp(rq::RQIso, r::Real, p::Int)
    if p==1
        return dk_dll(rq, r)
    elseif p==2
        return dk_dlσ(rq, r)
    elseif p==3
        return dk_dlα(rq, r)
    else
        return NaN
    end
end


using Distances

function (d::Distances.Hamming)(X::Set{T}, Y::Set{T}) where {T}
    if length(X) > length(Y)
        return d(Y,X)
    else
        n_intersect = 0
        for v in X
            n_intersect += (v ∈ Y)
        end
        return length(X) + length(Y) - 2n_intersect
    end
end

(d::Distances.Hamming)(X::Union{Set{T},Vector{T}}, Y::Nothing) where {T} = length(X)
(d::Distances.Hamming)(X::Nothing, Y::Union{Set{T},Vector{T}}) where {T} = d(Y,X)
(d::Distances.Hamming)(X::Nothing,Y::Nothing) = 0.0

# function (d::Distances.Jaccard)(X::Vector{T}, Y::Vector{T}) where {T}
#     if length(X) > length(Y)
#         return d(Y,X)
#     else
#         n_intersect = 0
#         for v in X
#             n_intersect += (v ∈ Y)
#         end

#         return 1 - n_intersect/(length(X) + length(Y) - n_intersect)
#     end
# end

function (d::Distances.Jaccard)(X::Set{T}, Y::Set{T}) where {T}
    if length(X) > length(Y)
        return d(Y,X)
    else
        n_intersect = 0
        for v in X
            n_intersect += (v ∈ Y)
        end

        return 1 - n_intersect/(length(X) + length(Y) - n_intersect)
    end
end


# Generates exponential mesh of N elements on [r_mn, r_max]
#
# The domain [r_min, r_max], the mesh will contain both endpoints:
function gen_rmesh_exp(
    r_min::Float64,
    r_max::Float64,
    a::Float64,
    Nr::Int64
)
    N = Nr - 1

    rmesh = zeros(Float64,N+1)
    SMALL = eps()

    if a < 0.0
        error("gen_rmesh_exp: a > 0 required")
    #
    elseif abs(a - 1.0) < SMALL
        α = (r_max - r_min) / N
        for i in 1:N+1
            rmesh[i] = α * (i - 1) + r_min
        end
    #
    else
        if N > 1
            β = log(a)/(N-1)
            α = (r_max - r_min) / (exp(β*N) - 1)
            for i in 1:N+1
                rmesh[i] = α * ( exp(β*(i-1)) - 1 ) + r_min
            end
        elseif N == 1
            rmesh[1] = r_min
            rmesh[2] = r_max
        else
            error("gen_rmesh_exp: N >= 1 required")
        end
    end
    return rmesh
end


# glcPointsLocal(N::Int) = [0.5(1-cos((i-1)pi/(N-1))) for i in 1:N]
# glcPointsGlobal(left::Number,right::Number,localPnts::Vector) = (right-left).*localPnts.-left

function glcPointsGlobal(left::Number,right::Number,N::Int)
    glcPointsLocal= [0.5(1-cos((i-1)pi/(N-1))) for i in 1:N]
    (right-left).*glcPointsLocal.-left
end

function numstring(x::Vector)::Float64
    pop!(cumprod(x))
end

function CofDQM(x::Vector,order::Int)
    #x: vector of sampling points;
    #order: largest derivative order;
    #return: matrix with dimension of (x,x,m)

    n = length(x)
    C = fill(0.0,(n,n,order))
    (n>order) || error("need more sampling points")

    # 1st order

    for i in 1:n, j in 1:n
        if i != j
            C[i,j,1] = numstring([((m!=i && m!=j) ? x[i]-x[m] : 1) for  m in 1:n])/numstring([((m!=j) ? x[j]-x[m] : 1) for  m in 1:n])

            # m_range_denominater = filter!(xx->xx!=i,[m for m in 1:n])
            # m_range_numerator = filter!(xx->xx!=j,m_range_denominater)
            #C[i,j,1] = numstring(filter(xx->xx!=0,[x[i]-x[m] for m in m_range_numerator]))/numstring(filter(xx->xx!=0,[x[j]-x[m] for m in m_range_denominater]))
            # C[i,j,1] = pop!(cumprod(filter(xx->xx!=0,[x[i]-x[m] for m in m_range_numerator])))/pop!(cumprod(filter(xx->xx!=0,[x[j]-x[m] for m in 1:n])))
        else

            # C[i,i,1] = sum(filter(xx->xx!=Inf,[1/(x[i]-x[m]) for m in 1:n]))
            C[i,i,1] = sum([((m!=i) ? 1/(x[i]-x[m]) : 0) for m in 1:n]) #这个慢了4个微秒

        end
    end

    # higher order
    for i in 1:n, j in 1:n, k in 2:order
        if i != j
            C[i,j,k] = k*(C[i,i,k-1]*C[i,j,1] - C[i,j,k-1]/(x[i]-x[j]))
        end
    end
    for i in 1:n, j in 1:n, k in 2:order
        if i == j
            C[i,j,k] = -sum([((m!=i) ? C[i,m,k] : 0) for m in 1:n])
        end
    end
    C
end

`
demoooo: test 1st-order approximation accuracy
`
pnts = glcPointsGlobal(-10,1,100)
# pnts = collect(0:2:10)

Coef = CofDQM(pnts,2)
f4(x::Number) = 4x^3+3x-2
f4d(x) = 12x^2+3

f4d.(pnts)-Coef[:,:,1]*f4.(pnts)


"""
    FourierV <: AbstractVApproximator{Int}

    struct FourierV <: AbstractVApproximator{Int}
        weights::Vector{Float64}
    end

Using Fourier cosine basis to approximate the state value.
`weights` is the featur vector.

See more details at Section (9.5.2) on Page 211 of the book *Sutton, Richard S., and Andrew G. Barto. Reinforcement learning: An introduction. MIT press, 2018.*
"""
struct FourierV <: AbstractVApproximator{Int}
    weights::Vector{Float64}
end

"""
    FourierV(order::Int)

By specifying the `order`, feature vector will be initialized with 0.
"""
FourierV(order::Int) = FourierV(zeros(Float64, order+1))

function (f::FourierV)(s)
    sum(w * cos((i-1) * π * s) for (i, w) in enumerate(f.weights))
end

function update!(f::FourierV, s, e)
    for i in 1:length(f.weights)
        f.weights[i] += e * cos((i-1) * π * s)
    end
end


# Code from previous section
using PyPlot
using Statistics
A = imread("sample_photo.png")
B = mean(A, dims=3)[:,:,1]
function imshow_scale(A)
    # Like imshow(A) but scales the values to [0,1] and supports grayscale

    A .-= minimum(A)            # Scale and shift to [0,1]
    A ./= maximum(A)
    if ndims(A) < 3
        A = reshape(A, size(A,1), size(A,2), 1)
    end
    if size(A,3) == 1
        A = repeat(A, 1, 1, 3)  # Set R=G=B for grayscale
    end
    imshow(A)
    end;

# Uncomment below if the package is not already installed
#using Pkg; Pkg.add("FFTW")

using FFTW

function imagefft_demo(A)
    AF = fftshift(fft(A))
    subplot(1,2,1); imshow_scale(A);
    subplot(1,2,2); imshow_scale(log.(1 .+ abs.(AF)));
    return
end

G = [zeros(256,128) ones(256,128)]
imagefft_demo(G)

G = zeros(256, 256)
G[78:178, 78:178] .= 1.0
imagefft_demo(G)

G = Float32[ (i+j<329) && (i+j>182) && (i-j>-67) && (i-j<73) for i = 1:256, j = 1:256 ]
imagefft_demo(G)

G = Float32[ sin(2pi*(10i + 20j)/256) for i = 1:256, j = 1:256 ]
imagefft_demo(G)

imagefft_demo(B)

Bpernoise = copy(B)
Bpernoise = B + 0.5*Float32[sin(2π*10j / size(B,2)) for i = 1:size(B,1), j = 1:size(B,2) ]
imagefft_demo(Bpernoise)

# Filter
BF = fftshift(fft(Bpernoise))
mid = size(B) .÷ 2 .+ 1
BF[mid[1], mid[2] + 10] = 0
BF[mid[1], mid[2] - 10] = 0
Bfiltered = real.(ifft(ifftshift(BF)))

imagefft_demo(Bfiltered)


# # [Ross-Macdonald Malaria Modeling](@id ross-macdonald)
#
#md # [![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](@__NBVIEWER_ROOT_URL__/examples/Ross-Macdonald.ipynb)

# Authors: Sean Wu and Sophie Libkind

using AlgebraicDynamics.DWDDynam
using Catlab.WiringDiagrams
using LabelledArrays

using Catlab.Graphics
using DelayDiffEq, DifferentialEquations
using Plots

# The Ross-Macdonald (RM) model is the canonical model of malaria transmission, first expressed by
# Sir Ronald Ross in 1911. The simplest form of the model is given by a system of 2 nonlinear ordinary
# differential equations describing $Z$, parasite prevalence in the mosquito vector population, and $X$,
# parasite prevalence in the vertebrate host population:
# $$ \dot X = mazbZ(1 - X) - rX, \quad \dot Z = acX (e^{-gn} - Z) - gZ  \quad (1)$$

#-

# The RM model is of supreme importance to modeling of malaria and other mosquito transmitted diseases
# because it is the simplest model that retains the fundamental processes involved in transmission: recovery
# of infected hosts, death of infected mosquitoes, and parasite transmission between discordant pairs of
# vectors and hosts. In this sense it represents the irreducible complexity to modeling mosquito borne
# diseases, such that any simpler model would be insufficient.

# The model assumes that mosquitoes take bloodmeals (bites) on vertebrate hosts with rate $a$.
# Therefore, the per-capita rate at which susceptible mosquitoes become infected is $acX$, where $c$
# is the transmission efficiency from infectious humans to susceptible mosquitoes (probability of
# parasites successfully invading the mosquito given a bite occurs) and $X$ is the probability the
#  bite lands on an infectious human. $1-Z$ is the proportion of mosquitoes who are susceptible but
# given an incubation period of $n$ days during which mosquitoes suffer per-capita mortality at rate
# $g$, only a proportion $e^{-gn}$ survive incubation to become infectious. Therefore the classic
# RM model will have statics that are consistent with the incorporation of an incubation period of
# fixed duration, but dynamics will not, as the delay is not incorporated into the equations.

#-

# Susceptible humans acquire infection at a rate $mabZ$, where $m = M/H$, the ratio of mosquitoes to
# humans, $a$ is as before, and $b$ is the transmission efficiency from infectious mosquitoes to
# susceptible humans. Therefore $mabZ$ gives the per-capita rate at which susceptible humans recieve
# parasites from the mosquito population. Infectious humans recover at a rate $r$ (the rate at which the
# immune system clears parasites). Because $m$ is considered a constant parameter, the model ignores
# seasonal fluctuations in mosquito (or host) population sizes. However, because the timescale of host
# dynamcis is typically slow in relation to mosquito dynamics, the approximation is often suitable
# over a transmission season.

#-

# The model's threshold criterion between the trivial equilibrium where both $Z,X$ are equal to zero
# (no disease) and the endemic equilibrium can be expressed by the basic reproductive number, giving
# the expected number of secondary infections arising from the introduction of a single infectious human,
# $$R_0 = \frac{ma^2bce^{-gn}}{rg}.$$ The endemic equilibrium is therefore:
# $$\bar{X}= \frac{R_{0}-1}{R_{0} + \frac{ac}{g}}, \quad \bar{Z} = \frac{ac\bar{X}}{g + ac\bar{X}} e^{-gn}.$$
#-
# From these equations, the nonlinear relationships that characterize observed malaria transmission can be
# understood. We implement them as follows:

# ## Diagram of Systems
# First we must construct a diagram of systems which describes the interaction between the mosquito and
# host populations. The arrows between the two subsystems represents the bidirectional infection during bloodmeals.

bloodmeal = WiringDiagram([], [:mosquitos, :humans])
mosq_box   = add_box!(bloodmeal, Box(:mosquitos, [:x], [:z]))
human_box  = add_box!(bloodmeal, Box(:humans, [:z], [:x]))
output_box = output_id(bloodmeal)

add_wires!(bloodmeal, Pair[
    (mosq_box, 1)  => (human_box, 1),
    (human_box, 1) => (mosq_box, 1),
    (mosq_box, 1)  => (output_box, 1),
    (human_box, 1) => (output_box, 2)]
)

to_graphviz(bloodmeal)

# ## ODE Model
# Next we implement the concrete mosquito and host dynamics given in Equation (1), and apply them to the diagram
# of systems. This composition is the complete Ross-Macdonald model which we can solve and plot.

dZdt = function(u,x,p,t)
    Z = u[1]
    X = x[1]
    [p.a*p.c*X*(exp(-p.g*p.n) - Z) - p.g*Z]
end

dXdt = function(u,x,p,t)
    X = u[1]
    Z = x[1]
    [p.m*p.a*p.b*Z*(1 - X) - p.r*X]
end

#-

mosquito_model = ContinuousMachine{Float64}(1, 1, 1, dZdt, (u,p,t) -> u)
human_model    = ContinuousMachine{Float64}(1, 1, 1, dXdt, (u,p,t) ->  u)

malaria_model = oapply(bloodmeal,
    Dict(:humans => human_model, :mosquitos => mosquito_model)
)

#-
params = LVector(a = 0.3, b = 0.55, c = 0.15,
    g = 0.1, n = 10, r = 1.0/200, m = 0.5)

u0 = [0.1, 0.3]
tspan = (0.0, 365.0*2)

prob = ODEProblem(malaria_model, u0, tspan, params)
sol = solve(prob, Tsit5());
#-

plot(sol, malaria_model,
    lw=2, title = "Ross-Macdonald Malaria model",
    xlabel = "time", ylabel = "proportion infectious",
    color = ["magenta" "blue"]
)

## Plot the equilibrium behavior as well
a, b, c, g, n, r, m = params
R0 = (m*a^2*b*c*exp(-g*n))/(r*g)
X̄ = (R0 - 1)/(R0 + (a*c)/g)
Z̄ = (a*c*X̄)/(g + a*c*X̄)*exp(-g*n)

N = length(sol)
plot!(sol.t, fill(X̄, N), label = "human equilibrium", ls = :dash, lw = 2, color = "blue")
plot!(sol.t, fill(Z̄, N), label = "mosquito equilibrium", ls = :dash, lw = 2, color = "magenta")

# ## Delay Model
# The previous models did not capture the incubation period for the disease in the
# mosquito population. To do so we can replace the models with delay differential equations
# and apply them to the same diagram of systems representing the bloodmeal.

dzdt_delay = function(u,x,h,p,t)
    Y, Z = u
    Y_delay, Z_delay = h(p, t - p.n)
    X, X_delay = x[1]

    [p.a*p.c*X*(1 - Y - Z) -
        p.a*p.c*X_delay*(1 - Y_delay - Z_delay)*exp(-p.g*p.n) -
        p.g*Y,
    p.a*p.c*X_delay*(1 - Y_delay - Z_delay)*exp(-p.g*p.n) -
        p.g*Z]
end

dxdt_delay = function(u,x,h,p,t)
    X, = u
    Z, _ = x[1]
    [p.m*p.a*p.b*Z*(1 - X) - p.r*X]
end

#-

mosquito_delay_model = DelayMachine{Float64, 2}(
    1, 2, 1, dzdt_delay, (u,h,p,t) -> [[u[2], h(p,t - p.n)[2]]])

human_delay_model = DelayMachine{Float64, 2}(
    1, 1, 1, dxdt_delay, (u,h,p,t) -> [[u[1], h(p, t - p.n)[1]]])

malaria_delay_model = oapply(bloodmeal,
    Dict(:humans => human_delay_model, :mosquitos => mosquito_delay_model)
)
#-
params = LVector(a = 0.3, b = 0.55, c = 0.15,
    g = 0.1, n = 10, r = 1.0/200, m = 0.5)

u0_delay = [0.09, .01, 0.3]
tspan = (0.0, 365.0*3)
hist(p,t) = u0_delay;

prob = DDEProblem(malaria_delay_model, u0_delay, [], hist, tspan, params)
alg = MethodOfSteps(Tsit5())
sol = solve(prob, alg)

plot(sol, label=["non-infectious mosquito population" "infectious mosquito population" "host population"],
    lw=2, title = "Ross-Macdonald malaria model",
    xlabel = "time", ylabel = "proportion infectious",
    color = ["magenta" "red" "blue"]
)

# While the equilibrium points of the two models are identical, they exhibit different dynamical behavior
# before settling down to equilibrium. Because models are often used to examine how the system may respond
# to intervention, incorporating additional biological realism can produce more plausible results regarding
# the expected time for interventions to affect the system. In any case, such simple models are best used as
# tools to explain basic concepts in malaria epidemiology rather than descriptions of real systems, which are
# likely far from equilibrium and affected by weather, climate, policy, and other external forces.


using ComputedArrays
using Test

ca1 = ComputedArray(cospi, range(0, stop=1, length=1000));
@test size(ca1) == (1000,)
@test eltype(ca1) == Float64

pot(x,i) = ceil(Int32, i*x^i);
ca2 = ComputedArray(pot, range(0, stop=1, length=11), 1:5);
@test size(ca2) == (11,5)
@test eltype(ca2) == Int32

v(x1,y1,x2,y2) = 1/hypot(x1-x2, y1-y2);
x1s = 0 .+ range(-1, stop=1, length=10) |> collect
y1s = 0 .+ range(-1, stop=1, length=11) |> collect
x2s = 3 .+ range(-1, stop=1, length=12) |> collect
y2s = 4 .+ range(-1, stop=1, length=13) |> collect
ca4 = ComputedArray(v, x1s, y1s, x2s, y2s)
@test size(ca4) == (10,11,12,13)
@test eltype(ca4) == Float64

ca4p = ComputedArray(v, x1s, y1s, x2s, y2s, order=(2,3,4,1))
@test size(ca4p) == (11,12,13,10)
@test ca4[3,5,6,8] == ca4p[5,6,8,3]
pca4 = PermutedDimsArray(ca4, (2,3,4,1))
@test ca4p == pca4


# This is Julia code for discretizing AR(1) process by using Tauchen method (1986)
# written on 2017/10/17
# z' = rho*z+e e~N(0,(sig_e)^2)
#
# INPUTS
# N:     Number of grids
# m:     Max number of std devs from mean
# rho:   Coeffecient of AR(1) process z'=mew+rho*z+e, e~N(0,sigmasq)
# sig_e: Std of innovation, e, in AR(1) process
#
# OUTPUT
# vZ:    grids
# mPI:   transition prob. matrix

## Preliminary

###############################################################
## 0. Define CDF function (from https://www.johndcook.com/blog/cpp_phi/)
###############################################################

function phi(x::Real)
    # constants
    const a1 =  0.254829592;
    const a2 = -0.284496736;
    const a3 =  1.421413741;
    const a4 = -1.453152027;
    const a5 =  1.061405429;
    const p  =  0.3275911;

    # Save the sign of x
    sign = 1;
    if (x < 0)
        sign = -1;
    end
    x = abs(x)/sqrt(2.0);

    # A&S formula 7.1.26
    t = 1.0/(1.0 + p*x);
    y = 1.0 - (((((a5*t + a4)*t) + a3)*t + a2)*t + a1)*t*exp(-x*x);

    return 0.5*(1.0 + sign*y);
end

##############################################################
##############################################################
##                          MAIN PART
##############################################################
##############################################################

###########################################################
## 1. Define Parameters
###########################################################

# Ex. Standard quarterly calibration for productivity process
# of US economy

const N = 5;            # Number of grids, N
const m = 3;            # Max number of std devs from mean
const rho = 0.95;       # AR(1) coefficient
const sig_e = 0.007;    # std of innovation

###########################################################
## 2. Generating grids
###########################################################

sig_z = sqrt((sig_e^2)/(1-rho^2));  # std of AR(1) r.v.
z_max = m*sig_z;                    # Max grid
z_min = -z_max;                     # Min grid
d = (z_max-z_min) / (N-1);          # incriment of grids
vZ = linspace(z_min, z_max, N);     # Grid vector 1*N

###########################################################
## 3. Transition matrix
###########################################################

mPI = zeros(N,N); # Transition matrix N*N

for i in 1:N
    for j in 1:N
        if j == 1
            mPI[i,j] = phi((vZ[1]+d/2-rho*vZ[i])/sig_e);       # If z' is min grid
        elseif j == N-1
            mPI[i,j] = 1 - phi((vZ[N]-d/2-rho*vZ[i])/sig_e);   # z' is the max grid
        else
            mPI[i,j] = phi((vZ[j]+d/2-rho*vZ[i])/sig_e) - phi((vZ[j]-d/2-rho*vZ[i])/sig_e);
        end
    end
end

###########################################################
## 4. Display result with 4 decimals
###########################################################
print("Grid points are \n")
for i in 1:N
    @printf "%6.4f" vZ[i]
    print(" ")
end
print("\n")
print("Transition prob. matrix is\n")
for i in 1:N
    for j in 1:N
        @printf "%6.4f" mPI[i,j]
        print(" ")
    end
    print("\n")
end