"""
    calc_sun_position_hor(datetime, lat, long)

Compute the Sun position at given time and observer coordinates in horizontal coordinates.

# Arguments:
- `datetime`: time: Either a `ZonedDateTime`, or `DateTime` assumed in UTC
- `lat`, `long`: latitude and longitude in degree

# Value
`NamedTuple`: sun position with entries of the same type as `lat` and `long`
- `altitude`: angle above the horizon [rad].
- `azimuth`: angle ange the horizon plane eastwards of north [rad]
- `hourangle`: [rad] as output by AstroLib.eq2hor
   Seems to represent time [day/2pi] after solar noon.
   Value at local timezone noon provdes (local time - solar time).
"""
function calc_sun_position_hor(datetime::ZonedDateTime, lat, long)
  datetimeUTC = DateTime(datetime,UTC)
  calc_sun_position_hor(datetimeUTC, lat, long)
end
function calc_sun_position_hor(datetime::DateTime, lat::FT, long::FT) where FT
    deg2rad = FT(π/180)
    jd = FT(datetime2julian(datetime))
    pos_eq = calc_sun_position_MOD(jd)
    # precession is already account for in MOD
    pos_hor = eq2hor(pos_eq.α/deg2rad, pos_eq.δ/deg2rad, jd, lat, long; precession = false) .* deg2rad
    (altitude = FT(pos_hor[1]), azimuth = FT(pos_hor[2]), hourangle = FT(pos_hor[3]))
end

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Description
#
#   Compute the sun position.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# References
#
#   [1] The Astronomical Almanac for the year 2000 (p. C24).
#
#   [2] http://aa.usno.navy.mil/faq/docs/SunApprox.php
#
#   [3] Vallado, D. A (2013). Fundamentals of Astrodynamics and Applications.
#       Microcosm Press, Hawthorne, CA.
#
#   [4] The Astronomical Almanac for the year 2006.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# modified from https://github.com/JuliaSpace/SatelliteToolbox.jl/blob/c5244c50e55e85f6b3a1702de16f00a9c726ee24/src/sun/sun_position.jl#L24-L30
#
# https://en.wikipedia.org/wiki/Ecliptic_coordinate_system

"""
    calc_sun_position_MOD(JD::Number)

Compute the Sun position at the Julian Day `JD`.

Results are represented in the Mean Equinox of Date (MOD),
i.e. accounting for precession but not for nutation and smaller pertubation
of the polar axes, in spherical ecliptic and equatorial coordinates.
The algorithm was adapted from [Vallado 2013, p. 277-279].

# Arguments:
- `JD`: time given as Julian Day .

# Value
`NamedTuple`: sun position where
- Ecliptic coordinates (1:3)
  - λ: Ecliptic longitude of the Sun [rad].
  - β: Ecliptic latitude of the Sun [rad] is assumed 0.
  - r: Distance of the Sun from Earth [m].
- Equatorial coordinate (4:5)
  - α: ascention [rad]
  - δ: declination [rad]
- ϵ: Obliquity of the ecliptic [rad].

# References
- Vallado, D. A (2013). Fundamentals of Astrodynamics and Applications.
       Microcosm Press, Hawthorne, CA.
"""
function calc_sun_position_MOD(JD::Number)
    # Constants (apdated from SatelliteToolbox)
    deg2rad = π/180
    au2m = 149597870700.0
    JD_J2000 = 2.451545e6

    # Number of Julian centuries from J2000 epoch.
    T_UT1 = (JD-JD_J2000)/36525.0

    # Here, we will assume that T_TBD ≈ T_UT1. This can be done because we are
    # using a low-precision algorithm [3].
    T_TBD = T_UT1

    # Mean anomaly of the Sun [deg].
    Ms = 357.529_109_2 + 35_999.050_34T_TBD

    # Convert Ms to [rad] and limit to the interval [0,2π].
    Ms = mod2pi(Ms*deg2rad)

    # Compute auxiliary variables.
    sinMs,  cosMs  = sincos(Ms)
    sin2Ms, cos2Ms = sincos(2Ms)

    # Mean longitude of the Sun [deg].
    λ_m = 280.460 + 36_000.771T_UT1

    # Ecliptic latitude of the Sun [deg]. # twutz 2110 longitude?
    λ_e = λ_m + 1.914_666_471sinMs + 0.019_994_643sin2Ms

    # Obliquity of the ecliptic [deg].
    ϵ = 23.439_291 - 0.013_004_2T_TBD

    # Convert λ_e and ϵ to [rad] and limit to the interval [0,2π].
    λ_e = mod2pi(λ_e*deg2rad)
    ϵ   = mod2pi(  ϵ*deg2rad)

    # Auxiliary variables.
    sinϵ   , cosϵ   = sincos(ϵ)
    sinλ_e , cosλ_e = sincos(λ_e)

    # Distance of the Sun from Earth [m].
    r = (1.000_140_612 - 0.016_708_617cosMs - 0.000_139_589cos2Ms )*au2m

    # ascension
    α = atan(cosϵ * sinλ_e, cosλ_e)

    # declination
    δ = asin(sinϵ * sinλ_e)

    S_MOD_rad = (
        λ = λ_e, β = 0.0, r = r,
        α = α, δ = δ,
        ϵ = ϵ
        )
end


using Morbit
M = Morbit
MOI = M.MOI
#%%

Morbit.print_all_logs()

mop = M.MixedMOP(2)
f1 = x -> sum( (x .- 1).^2 )
f2 = x -> sum( (x .+ 1).^2 )

#M.add_objective!(mop, f1, M.ExactConfig())
#M.add_objective!(mop, f2, M.ExactConfig())
#M.add_objective!(mop, f1, M.RbfConfig(;use_max_points = false))
#M.add_objective!(mop, f2, M.RbfConfig(kernel = :gaussian))
#M.add_objective!(mop, f2, M.RbfConfig())
#M.add_objective!(mop, f1, M.TaylorConfig(; gradients = M.RFD.CFDStamp(1,3), hessians = M.RFD.CFDStamp(1,5)))
M.add_objective!(mop, f1, M.TaylorConfig())
M.add_objective!(mop, f2, M.TaylorConfig())

algo_config = AlgoConfig(;
	max_iter = 10
)
populated_db = nothing

x0 = rand(2)
fx0 = []
#%%
#mop, id, db, sc, ac = M.initialize_data(mop,x0);

X, FX, ret, db = Morbit.optimize( mop, x0; algo_config )


module G1
function f(q::Vector{T}, q_dot::Vector{T}, xi::T) where T
    l1 = q[1]
    l2 = q[2]
    l3 = q[3]
    l1_dot = q_dot[1]
    l2_dot = q_dot[2]
    l3_dot = q_dot[3]

    1.8558518988717e-6*l1.^5 + 9.70087471350808e-6*l1.^4.*l2 - 1.89926015002423e-5*l1.^4.*l3 - 5.70920347683978e-8*l1.^4 + 8.93480174177124e-6*l1.^3.*l2.^2 - 5.67541019579733e-5*l1.^3.*l2.*l3 - 4.60335359066924e-8*l1.^3.*l2 + 6.64276883744224e-5*l1.^3.*l3.^2 + 2.74798041182713e-7*l1.^3.*l3 - 2.44419795743875e-10*l1.^3 - 1.56450323689193e-5*l1.^2.*l2.^3 + 1.99678170677335e-5*l1.^2.*l2.^2.*l3 + 3.09604520022297e-7*l1.^2.*l2.^2 + 6.54477101135341e-5*l1.^2.*l2.*l3.^2 - 4.80212518198379e-7*l1.^2.*l2.*l3 - 4.07104029469703e-10*l1.^2.*l2 - 8.84038175553207e-5*l1.^2.*l3.^3 - 1.7313330904159e-7*l1.^2.*l3.^2 + 1.14159743254739e-9*l1.^2.*l3 - 3.20459750258103e-13*l1.^2 - 1.62995586742055e-5*l1.*l2.^4 + 9.63865835476286e-5*l1.*l2.^3.*l3 - 5.76159669483844e-8*l1.*l2.^3 - 0.000164232243744917*l1.*l2.^2.*l3.^2 - 4.47135751195212e-7*l1.*l2.^2.*l3 + 7.88991063020458e-10*l1.*l2.^2 + 6.5456473828544e-5*l1.*l2.*l3.^3 + 9.27226967263222e-7*l1.*l2.*l3.^2 - 7.60394380347721e-10*l1.*l2.*l3 - 7.1886514515434e-13*l1.*l2 + 2.80178737068122e-5*l1.*l3.^4 - 1.9291801143875e-7*l1.*l3.^3 - 7.64342748517079e-10*l1.*l3.^2 + 1.36072328692881e-12*l1.*l3 + 1.77973142550235e-5*l1 - 3.71608982966903e-6*l2.^5 + 3.4860347811527e-5*l2.^4.*l3 - 8.99561440808747e-8*l2.^4 - 0.000117823809431305*l2.^3.*l3.^2 + 4.16948693822733e-7*l2.^3.*l3 + 3.53672039292904e-11*l2.^3 + 0.000172372563165961*l2.^2.*l3.^3 - 4.0073008496488e-7*l2.^2.*l3.^2 - 8.95304236140046e-10*l2.^2.*l3 + 4.99582038836009e-13*l2.^2 - 0.000102352765801864*l2.*l3.^4 - 4.26318851821059e-8*l2.*l3.^3 + 1.27404682191573e-9*l2.*l3.^2 - 2.77898404065488e-13*l2.*l3 - 2.25988265609486e-5*l2 + 1.47914348941021e-5*l3.^5 + 5.88810194343027e-8*l3.^4 - 1.68437205927552e-10*l3.^3 - 5.43101594146752e-13*l3.^2 + 4.6819529309252e-6*l3 + 1.83176148000071e-8
end
end


using OrdinaryDiffEq, RecursiveArrayTools, Test, StaticArrays

f = function (u,p,t)
  - u + sin(-t)
end

prob = ODEProblem(f,1.0,(0.0,-10.0))

condition= function (u,t,integrator) # Event when event_f(u,t,k) == 0
  - t - 2.95
end

affect! = function (integrator)
  integrator.u = integrator.u + 2
end

callback = ContinuousCallback(condition,affect!)

sol = solve(prob,Tsit5(),callback=callback)

f = function (du,u,p,t)
  du[1] = - u[1] + sin(t)
end

prob = ODEProblem(f,[1.0],(0.0,10.0))

condtion= function (u,t,integrator) # Event when event_f(u,t,k) == 0
  t - 2.95
end

affect! = function (integrator)
  integrator.u = integrator.u .+ 2
end

callback = ContinuousCallback(condtion,affect!)

sol = solve(prob,Tsit5(),callback=callback,abstol=1e-8,reltol=1e-6)

f = function (du,u,p,t)
  du[1] = u[2]
  du[2] = -9.81
end

condtion= function (u,t,integrator) # Event when event_f(u,t,k) == 0
  u[1]
end

affect! = nothing
affect_neg! = function (integrator)
  integrator.u[2] = -integrator.u[2]
end

callback = ContinuousCallback(condtion,affect!,affect_neg!,interp_points=100)

u0 = [50.0,0.0]
tspan = (0.0,15.0)
prob = ODEProblem(f,u0,tspan)

sol = solve(prob,Tsit5(),callback=callback,adaptive=false,dt=1/4)

condtion_single = function (u,t,integrator) # Event when event_f(u,t,k) == 0
  u
end

affect! = nothing
affect_neg! = function (integrator)
  integrator.u[2] = -integrator.u[2]
end

callback_single = ContinuousCallback(condtion_single,affect!,affect_neg!,interp_points=100,idxs=1)

u0 = [50.0,0.0]
tspan = (0.0,15.0)
prob = ODEProblem(f,u0,tspan)

sol = solve(prob,Tsit5(),callback=callback_single,adaptive=false,dt=1/4)

#plot(sol,denseplot=true)

sol = solve(prob,Vern6(),callback=callback)
#plot(sol,denseplot=true)
sol = solve(prob,BS3(),callback=callback)

sol33 = solve(prob,Vern7(),callback=callback)

bounced = ODEProblem(f,sol[8],(0.0,1.0))
sol_bounced = solve(bounced,Vern6(),callback=callback,dt=sol.t[9]-sol.t[8])
#plot(sol_bounced,denseplot=true)
sol_bounced(0.04) # Complete density
@test maximum(maximum.(map((i)->sol.k[9][i]-sol_bounced.k[2][i],1:length(sol.k[9])))) == 0

sol2= solve(prob,Vern6(),callback=callback,adaptive=false,dt=1/2^4)
#plot(sol2)

sol2= solve(prob,Vern6())

sol3= solve(prob,Vern6(),saveat=[.5])

## Saving callback

condtion = function (u,t,integrator)
  true
end
affect! = function (integrator) end

save_positions = (true,false)
saving_callback = DiscreteCallback(condtion,affect!,save_positions=save_positions)

sol4 = solve(prob,Tsit5(),callback=saving_callback)

@test sol2(3) ≈ sol(3)

affect! = function (integrator)
  u_modified!(integrator,false)
end
saving_callback2 = DiscreteCallback(condtion,affect!,save_positions=save_positions)
sol4 = solve(prob,Tsit5(),callback=saving_callback2)

cbs = CallbackSet(saving_callback,saving_callback2)
sol4_extra = solve(prob,Tsit5(),callback=cbs)

@test length(sol4_extra) == 2length(sol4) - 1

condtion= function (u,t,integrator)
  u[1]
end

affect! = function (integrator)
  terminate!(integrator)
end

terminate_callback = ContinuousCallback(condtion,affect!)

tspan2 = (0.0,Inf)
prob2 = ODEProblem(f,u0,tspan2)

sol5 = solve(prob2,Tsit5(),callback=terminate_callback)

@test sol5[end][1] < 3e-12
@test sol5.t[end] ≈ sqrt(50*2/9.81)

affect2! = function (integrator)
  if integrator.t >= 3.5
    terminate!(integrator)
  else
    integrator.u[2] = -integrator.u[2]
  end
end
terminate_callback2 = ContinuousCallback(condtion,nothing,affect2!,interp_points=100)

sol5 = solve(prob2,Vern7(),callback=terminate_callback2)

@test sol5[end][1] < 1.3e-10
@test_broken sol5.t[end] ≈ 3*sqrt(50*2/9.81)

condtion= function (u,t,integrator) # Event when event_f(u,t,k) == 0
  t-4
end

affect! = function (integrator)
  terminate!(integrator)
end

terminate_callback3 = ContinuousCallback(condtion,affect!,interp_points=1000)

bounce_then_exit = CallbackSet(callback,terminate_callback3)

sol6 = solve(prob2,Vern7(),callback=bounce_then_exit)

@test sol6[end][1] > 0
@test sol6[end][1] < 100
@test sol6.t[end] ≈ 4

# More ODE event tests, cf. #201, #199, #198, #197
function test_callback_inplace(alg; kwargs...)
    f = (du, u, p, t) -> @. du = u
    cb = ContinuousCallback((u,t,int) -> u[1] - exp(1), terminate!)
    prob = ODEProblem(f, [1.0], (0.0, 2.0), callback=cb)
    sol = solve(prob, alg; kwargs...)
    sol.u[end][1] ≈ exp(1)
end

function test_callback_outofplace(alg; kwargs...)
    f = (u, p, t) -> copy(u)
    cb = ContinuousCallback((u,t,int) -> u[1] - exp(1), terminate!)
    prob = ODEProblem(f, [1.0], (0.0, 2.0), callback=cb)
    sol = solve(prob, alg; kwargs...)
    sol.u[end][1] ≈ exp(1)
end

function test_callback_scalar(alg; kwargs...)
    f = (u, p, t) -> u
    cb = ContinuousCallback((u,t,int) -> u - exp(1), terminate!)
    prob = ODEProblem(f, 1.0, (0.0, 2.0), callback=cb)
    sol = solve(prob, alg; kwargs...)
    sol.u[end] ≈ exp(1)
end

function test_callback_svector(alg; kwargs...)
    f = (u, p, t) -> u
    cb = ContinuousCallback((u,t,int) -> u[1] - exp(1), terminate!)
    prob = ODEProblem(f, SVector(1.0), (0.0, 2.0), callback=cb)
    sol = solve(prob, alg; kwargs...)
    sol.u[end][1] ≈ exp(1)
end

function test_callback_mvector(alg; kwargs...)
    f = (u, p, t) -> copy(u)
    cb = ContinuousCallback((u,t,int) -> u[1] - exp(1), terminate!)
    prob = ODEProblem(f, MVector(1.0), (0.0, 2.0), callback=cb)
    sol = solve(prob, alg; kwargs...)
    sol.u[end][1] ≈ exp(1)
end

println("inplace")
@test test_callback_inplace(BS3())
@test test_callback_inplace(BS5())
@test test_callback_inplace(SSPRK432())
@test test_callback_inplace(SSPRK932())
@test test_callback_inplace(OwrenZen3())
@test test_callback_inplace(OwrenZen4())
@test test_callback_inplace(OwrenZen5())
@test test_callback_inplace(DP5())
@test test_callback_inplace(DP8())
@test test_callback_inplace(Feagin10())
@test test_callback_inplace(Feagin12())
@test test_callback_inplace(Feagin14())
@test test_callback_inplace(TanYam7())
@test test_callback_inplace(Tsit5())
@test test_callback_inplace(TsitPap8())
@test test_callback_inplace(Vern6())
@test test_callback_inplace(Vern7())
@test test_callback_inplace(Vern8())
@test test_callback_inplace(Vern9())
@test test_callback_inplace(Rosenbrock23())
@test test_callback_inplace(Rosenbrock32())
@test test_callback_inplace(AB3(); dt=0.1)
@test test_callback_inplace(ABM32(); dt=0.1)
@test test_callback_inplace(AB4(); dt=0.1)
@test test_callback_inplace(ABM43(); dt=0.1)
@test test_callback_inplace(AB5(); dt=0.1)
@test test_callback_inplace(ABM54(); dt=0.1)
@test test_callback_inplace(VCAB3())
@test test_callback_inplace(VCAB4())
@test test_callback_inplace(VCAB5())
@test test_callback_inplace(VCABM3())
@test test_callback_inplace(VCABM4())
@test test_callback_inplace(VCABM5())

println("outofplace")
@test test_callback_outofplace(BS3())
@test test_callback_outofplace(BS5())
@test test_callback_outofplace(SSPRK432())
@test test_callback_outofplace(SSPRK932())
@test test_callback_outofplace(OwrenZen3())
@test test_callback_outofplace(OwrenZen4())
@test test_callback_outofplace(OwrenZen5())
@test test_callback_outofplace(DP5())
@test test_callback_outofplace(DP8())
@test test_callback_outofplace(Feagin10())
@test test_callback_outofplace(Feagin12())
@test test_callback_outofplace(Feagin14())
@test test_callback_outofplace(TanYam7())
@test test_callback_outofplace(Tsit5())
@test test_callback_outofplace(TsitPap8())
@test test_callback_outofplace(Vern6())
@test test_callback_outofplace(Vern7())
@test test_callback_outofplace(Vern8())
@test test_callback_outofplace(Vern9())
@test test_callback_outofplace(Rosenbrock23())
@test test_callback_outofplace(Rosenbrock32())
@test test_callback_outofplace(AB3(); dt=0.1)
@test test_callback_outofplace(ABM32(); dt=0.1)
@test test_callback_outofplace(AB4(); dt=0.1)
@test test_callback_outofplace(ABM43(); dt=0.1)
@test test_callback_outofplace(AB5(); dt=0.1)
@test test_callback_outofplace(ABM54(); dt=0.1)
@test test_callback_outofplace(VCAB3())
@test test_callback_outofplace(VCAB4())
@test test_callback_outofplace(VCAB5())
@test test_callback_outofplace(VCABM3())
@test test_callback_outofplace(VCABM4())
@test test_callback_outofplace(VCABM5())

println("scalar")
@test test_callback_scalar(BS3())
@test test_callback_scalar(BS5())
@test test_callback_scalar(SSPRK432())
@test test_callback_scalar(SSPRK932())
@test test_callback_scalar(OwrenZen3())
@test test_callback_scalar(OwrenZen4())
@test test_callback_scalar(OwrenZen5())
@test test_callback_scalar(DP5())
@test test_callback_scalar(DP8())
@test test_callback_scalar(Feagin10())
@test test_callback_scalar(Feagin12())
@test test_callback_scalar(Feagin14())
@test test_callback_scalar(TanYam7())
@test test_callback_scalar(Tsit5())
@test test_callback_scalar(TsitPap8())
@test test_callback_scalar(Vern6())
@test test_callback_scalar(Vern7())
@test test_callback_scalar(Vern8())
@test test_callback_scalar(Vern9())
@test test_callback_scalar(Rosenbrock23())
@test test_callback_scalar(Rosenbrock32())
@test test_callback_scalar(AB3(); dt=0.1)
@test test_callback_scalar(ABM32(); dt=0.1)
@test test_callback_scalar(AB4(); dt=0.1)
@test test_callback_scalar(ABM43(); dt=0.1)
@test test_callback_scalar(AB5(); dt=0.1)
@test test_callback_scalar(ABM54(); dt=0.1)
@test test_callback_scalar(VCAB3())
@test test_callback_scalar(VCAB4())
@test test_callback_scalar(VCAB5())
@test test_callback_scalar(VCABM3())
@test test_callback_scalar(VCABM4())
@test test_callback_scalar(VCABM5())

println("svector")
@test test_callback_svector(BS3())
@test test_callback_svector(BS5())
@test test_callback_svector(SSPRK432())
@test test_callback_svector(SSPRK932())
@test test_callback_svector(OwrenZen3())
@test test_callback_svector(OwrenZen4())
@test test_callback_svector(OwrenZen5())
@test test_callback_svector(DP5())
@test test_callback_svector(DP8())
@test test_callback_svector(Feagin10())
@test test_callback_svector(Feagin12())
@test test_callback_svector(Feagin14())
@test test_callback_svector(TanYam7())
@test test_callback_svector(Tsit5())
@test test_callback_svector(TsitPap8())
@test test_callback_svector(Vern6())
@test test_callback_svector(Vern7())
@test test_callback_svector(Vern8())
@test test_callback_svector(Vern9())
@test test_callback_svector(Rosenbrock23())
@test test_callback_svector(Rosenbrock32())
@test test_callback_svector(AB3(); dt=0.1)
@test test_callback_svector(ABM32(); dt=0.1)
@test test_callback_svector(AB4(); dt=0.1)
@test test_callback_svector(ABM43(); dt=0.1)
@test test_callback_svector(AB5(); dt=0.1)
@test test_callback_svector(ABM54(); dt=0.1)
@test test_callback_svector(VCAB3())
@test test_callback_svector(VCAB4())
@test test_callback_svector(VCAB5())
@test test_callback_svector(VCABM3())
@test test_callback_svector(VCABM4())
@test test_callback_svector(VCABM5())

println("mvector")
@test test_callback_mvector(BS3())
@test test_callback_mvector(BS5())
@test test_callback_mvector(SSPRK432())
@test test_callback_mvector(SSPRK932())
@test test_callback_mvector(OwrenZen3())
@test test_callback_mvector(OwrenZen4())
@test test_callback_mvector(OwrenZen5())
@test test_callback_mvector(DP5())
@test test_callback_mvector(DP8())
@test test_callback_mvector(Feagin10())
@test test_callback_mvector(Feagin12())
@test test_callback_mvector(Feagin14())
@test test_callback_mvector(TanYam7())
@test test_callback_mvector(Tsit5())
@test test_callback_mvector(TsitPap8())
@test test_callback_mvector(Vern6())
@test test_callback_mvector(Vern7())
@test test_callback_mvector(Vern8())
@test test_callback_mvector(Vern9())
@test test_callback_mvector(Rosenbrock23())
@test test_callback_mvector(Rosenbrock32())
@test test_callback_mvector(AB3(); dt=0.1)
@test test_callback_mvector(ABM32(); dt=0.1)
@test test_callback_mvector(AB4(); dt=0.1)
@test test_callback_mvector(ABM43(); dt=0.1)
@test test_callback_mvector(AB5(); dt=0.1)
@test test_callback_mvector(ABM54(); dt=0.1)
@test test_callback_mvector(VCAB3())
@test test_callback_mvector(VCAB4())
@test test_callback_mvector(VCAB5())
@test test_callback_mvector(VCABM3())
@test test_callback_mvector(VCABM4())
@test test_callback_mvector(VCABM5())

# Test ContinuousCallback hits values on the steps
t_event = 100.0
f_simple(u,p,t) = 1.00001*u
event_triggered = false
condition_simple(u,t,integrator) = t_event-t
function affect_simple!(integrator)
  global event_triggered
  event_triggered = true
end
cb = ContinuousCallback(condition_simple, nothing, affect_simple!)
prob = ODEProblem(f_simple, [1.0], (0.0, 2.0*t_event))
sol = solve(prob,Tsit5(),callback=cb, adaptive = false, dt = 10.0)
@test event_triggered


# -*- coding: utf-8 -*-
# ---
# jupyter:
#   jupytext:
#     formats: jl:light,ipynb
#     text_representation:
#       extension: .jl
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.5.1
#   kernelspec:
#     display_name: Julia 1.4.2
#     language: julia
#     name: julia-1.4
# ---

# # Direct Inversion of the Iterative Subspace
#
# When solving systems of linear (or nonlinear) equations, iterative methods are often employed.  Unfortunately, such methods often suffer from convergence issues such as numerical instability, slow convergence, and significant computational expense when applied to difficult problems.  In these cases, convergence accelleration methods may be applied to both speed up, stabilize and/or reduce the cost for the convergence patterns of these methods, so that solving such problems become computationally tractable.  One such method is known as the direct inversion of the iterative subspace (DIIS) method, which is commonly applied to address convergence issues within self consistent field computations in Hartree-Fock theory (and other iterative electronic structure methods).  In this tutorial, we'll introduce the theory of DIIS for a general iterative procedure, before integrating DIIS into our previous implementation of RHF.
#
# ## I. Theory
#
# DIIS is a widely applicable convergence acceleration method, which is applicable to numerous problems in linear algebra and the computational sciences, as well as quantum chemistry in particular.  Therefore, we will introduce the theory of this method in the general sense, before seeking to apply it to SCF.
#
# Suppose that for a given problem, there exist a set of trial vectors $\{\mid{\bf p}_i\,\rangle\}$ which have been generated iteratively, converging toward the true solution, $\mid{\bf p}^f\,\rangle$.  Then the true solution can be approximately constructed as a linear combination of the trial vectors,
# $$\mid{\bf p}\,\rangle = \sum_ic_i\mid{\bf p}_i\,\rangle,$$
# where we require that the residual vector
# $$\mid{\bf r}\,\rangle = \sum_ic_i\mid{\bf r}_i\,\rangle\,;\;\;\; \mid{\bf r}_i\,\rangle
# =\, \mid{\bf p}_{i+1}\,\rangle - \mid{\bf p}_i\,\rangle$$
# is a least-squares approximate to the zero vector, according to the constraint
# $$\sum_i c_i = 1.$$
# This constraint on the expansion coefficients can be seen by noting that each trial function ${\bf p}_i$ may be represented as an error vector applied to the true solution, $\mid{\bf p}^f\,\rangle + \mid{\bf e}_i\,\rangle$.  Then
# \begin{align}
# \mid{\bf p}\,\rangle &= \sum_ic_i\mid{\bf p}_i\,\rangle\\
# &= \sum_i c_i(\mid{\bf p}^f\,\rangle + \mid{\bf e}_i\,\rangle)\\
# &= \mid{\bf p}^f\,\rangle\sum_i c_i + \sum_i c_i\mid{\bf e}_i\,\rangle
# \end{align}
# Convergence results in a minimization of the error (causing the second term to vanish); for the DIIS solution vector $\mid{\bf p}\,\rangle$ and the true solution vector $\mid{\bf p}^f\,\rangle$ to be equal, it must be that $\sum_i c_i = 1$.  We satisfy our condition for the residual vector by minimizing its norm,
# $$\langle\,{\bf r}\mid{\bf r}\,\rangle = \sum_{ij} c_i^* c_j \langle\,{\bf r}_i\mid{\bf r}_j\,\rangle,$$
# using Lagrange's method of undetermined coefficients subject to the constraint on $\{c_i\}$:
# $${\cal L} = {\bf c}^{\dagger}{\bf Bc} - \lambda\left(1 - \sum_i c_i\right)$$
# where $B_{ij} = \langle {\bf r}_i\mid {\bf r}_j\rangle$ is the matrix of residual vector overlaps.  Minimization of the Lagrangian with respect to the coefficient $c_k$ yields (for real values)
# \begin{align}
# \frac{\partial{\cal L}}{\partial c_k} = 0 &= \sum_j c_jB_{jk} + \sum_i c_iB_{ik} - \lambda\\
# &= 2\sum_ic_iB_{ik} - \lambda
# \end{align}
# which has matrix representation
# \begin{equation}
# \begin{pmatrix}
#   B_{11} & B_{12} & \cdots & B_{1m} & -1 \\
#   B_{21} & B_{22} & \cdots & B_{2m} & -1 \\
#   \vdots  & \vdots  & \ddots & \vdots  & \vdots \\
#   B_{n1} & B_{n2} & \cdots & B_{nm} & -1 \\
#   -1 & -1 & \cdots & -1 & 0
# \end{pmatrix}
# \begin{pmatrix}
# c_1\\
# c_2\\
# \vdots \\
# c_n\\
# \lambda
# \end{pmatrix}
# =
# \begin{pmatrix}
# 0\\
# 0\\
# \vdots\\
# 0\\
# -1
# \end{pmatrix},
# \end{equation}
#
# which we will refer to as the Pulay equation, named after the inventor of DIIS.  It is worth noting at this point that our trial vectors, residual vectors, and solution vector may in fact be tensors of arbitrary rank; it is for this reason that we have used the generic notation of Dirac in the above discussion to denote the inner product between such objects.
#
# ## II. Algorithms for DIIS
# The general DIIS procedure, as described above, has the following structure during each iteration:
# #### Algorithm 1: Generic DIIS procedure
# 1. Compute new trial vector, $\mid{\bf p}_{i+1}\,\rangle$, append to list of trial vectors
# 2. Compute new residual vector, $\mid{\bf r}_{i+1}\,\rangle$, append to list of trial vectors
# 3. Check convergence criteria
#     - If RMSD of $\mid{\bf r}_{i+1}\,\rangle$ sufficiently small, and
#     - If change in DIIS solution vector $\mid{\bf p}\,\rangle$ sufficiently small, break
# 4. Build **B** matrix from previous residual vectors
# 5. Solve Pulay equation for coefficients $\{c_i\}$
# 6. Compute DIIS solution vector $\mid{\bf p}\,\rangle$
#
# For SCF iteration, the most common choice of trial vector is the Fock matrix **F**; this choice has the advantage over other potential choices (e.g., the density matrix **D**) of **F** not being idempotent, so that it may benefit from extrapolation.  The residual vector is commonly chosen to be the orbital gradient in the AO basis,
# $$g_{\mu\nu} = ({\bf FDS} - {\bf SDF})_{\mu\nu},$$
# however the better choice (which we will make in our implementation!) is to orthogonormalize the basis of the gradient with the inverse overlap metric ${\bf A} = {\bf S}^{-1/2}$:
# $$r_{\mu\nu} = ({\bf A}^{\rm T}({\bf FDS} - {\bf SDF}){\bf A})_{\mu\nu}.$$
# Therefore, the SCF-specific DIIS procedure (integrated into the SCF iteration algorithm) will be:
# #### Algorithm 2: DIIS within an SCF Iteration
# 1. Compute **F**, append to list of previous trial vectors
# 2. Compute AO orbital gradient **r**, append to list of previous residual vectors
# 3. Compute RHF energy
# 3. Check convergence criteria
#     - If RMSD of **r** sufficiently small, and
#     - If change in SCF energy sufficiently small, break
# 4. Build **B** matrix from previous AO gradient vectors
# 5. Solve Pulay equation for coefficients $\{c_i\}$
# 6. Compute DIIS solution vector **F_DIIS** from $\{c_i\}$ and previous trial vectors
# 7. Compute new orbital guess with **F_DIIS**

# ## III. Implementation
#
# In order to implement DIIS, we're going to integrate it into an existing RHF program.  Since we just-so-happened to write such a program in the last tutorial, let's re-use the part of the code before the SCF integration which won't change when we include DIIS:

# +
# ==> Basic Setup <==
# Import statements
using PyCall: pyimport
psi4 = pyimport("psi4")
np   = pyimport("numpy") # used only to cast to Psi4 arrays
using TensorOperations: @tensor
using LinearAlgebra: Diagonal, Hermitian, eigen, tr, norm, dot
using Printf: @printf

# Memory specification
psi4.set_memory(Int(5e8))
numpy_memory = 2

# Set output file
psi4.core.set_output_file("output.dat", false)

# Define Physicist's water -- don't forget C1 symmetry!
mol = psi4.geometry("""
O
H 1 1.1
H 1 1.1 2 104
symmetry c1
""")

# Set computation options
psi4.set_options(Dict("basis" => "cc-pvdz",
                      "scf_type" => "pk",
                      "e_convergence" => 1e-8))

# Maximum SCF iterations
MAXITER = 40
# Energy convergence criterion
E_conv = 1.0e-6
D_conv = 1.0e-3

# +
# ==> Static 1e- & 2e- Properties <==
# Class instantiation
wfn = psi4.core.Wavefunction.build(mol, psi4.core.get_global_option("basis"))
mints = psi4.core.MintsHelper(wfn.basisset())

# Overlap matrix
S = np.asarray(mints.ao_overlap()) # we only need a copy

# Number of basis Functions & doubly occupied orbitals
nbf = size(S)[1]
ndocc = wfn.nalpha()

println("Number of occupied orbitals: ", ndocc)
println("Number of basis functions: ", nbf)

# Memory check for ERI tensor
I_size = nbf^4 * 8.e-9
println("\nSize of the ERI tensor will be $I_size GB.")
memory_footprint = I_size * 1.5
if I_size > numpy_memory
    psi4.core.clean()
    throw(OutOfMemoryError("Estimated memory utilization ($memory_footprint GB) exceeds " *
                           "allotted memory limit of $numpy_memory GB."))
end

# Build ERI Tensor
I = np.asarray(mints.ao_eri()) # we only need a copy

# Build core Hamiltonian
T = np.asarray(mints.ao_kinetic()) # we only need a copy
V = np.asarray(mints.ao_potential()) # we only need a copy
H = T + V;

# +
# ==> CORE Guess <==
# AO Orthogonalization Matrix
A = mints.ao_overlap()
A.power(-0.5, 1.e-16) # ≈ Julia's A^(-0.5) after psi4view()
A = np.asarray(A)

# Transformed Fock matrix
F_p = A * H * A

# Diagonalize F_p for eigenvalues & eigenvectors with Julia
e, C_p = eigen(Hermitian(F_p))

# Transform C_p back into AO basis
C = A * C_p

# Grab occupied orbitals
C_occ = C[:, 1:ndocc]

# Build density matrix from occupied orbitals
D = C_occ * C_occ'

# Nuclear Repulsion Energy
E_nuc = mol.nuclear_repulsion_energy()
# -

# Now let's put DIIS into action.  Before our iterations begin, we'll need to create empty lists to hold our previous residual vectors (AO orbital gradients) and trial vectors (previous Fock matrices), along with setting starting values for our SCF energy and previous energy:

# ==> Pre-Iteration Setup <==
# SCF & Previous Energy
SCF_E = 0.0
E_old = 0.0;

# Now we're ready to write our SCF iterations according to Algorithm 2.  Here are some hints which may help you along the way:
#
# #### Starting DIIS
# Since DIIS builds the approximate solution vector $\mid{\bf p}\,\rangle$ as a linear combination of the previous trial vectors $\{\mid{\bf p}_i\,\rangle\}$, there's no need to perform DIIS on the first SCF iteration, since there's only one trial vector for DIIS to use!
#
# #### Building **B**
# 1. The **B** matrix in the Lagrange equation is really $\tilde{\bf B} = \begin{pmatrix} {\bf B} & -1\\ -1 & 0\end{pmatrix}$.
# 2. Since **B** is the matrix of residual overlaps, it will be a square matrix of dimension equal to the number of residual vectors.  If **B** is an $N\times N$ matrix, how big is $\tilde{\bf B}$?
# 3. Since our residuals are real, **B** will be a symmetric matrix.
# 4. To build $\tilde{\bf B}$, make an empty array of the appropriate dimension, then use array indexing to set the values of the elements.
#
# #### Solving the Pulay equation
# 1. Use built-in Julia functionality to make your life easier.
# 2. The solution vector for the Pulay equation is $\tilde{\bf c} = \begin{pmatrix} {\bf c}\\ \lambda\end{pmatrix}$, where $\lambda$ is the Lagrange multiplier, and the right hand side is $\begin{pmatrix} {\bf 0}\\ -1\end{pmatrix}$.

# +
# Start from fresh orbitals
F_p = A * H * A
e, C_p = eigen(Hermitian(F_p))
C = A * C_p
C_occ = C[:, 1:ndocc]
D = C_occ * C_occ' ;

# Trial & Residual Vector Lists
F_list = []
DIIS_RESID = []

# ==> SCF Iterations w/ DIIS <==
println("==> Starting SCF Iterations <==")
SCF_E = let SCF_E = SCF_E, E_old = E_old, D = D

   # Begin Iterations
   for scf_iter in 1:MAXITER
      # Build Fock matrix
      @tensor G[p,q] := (2I[p,q,r,s] - I[p,r,q,s]) * D[r,s]
      F = H + G

      # Build DIIS Residual
      diis_r = A * (F * D * S - S * D * F) * A

      # Append trial & residual vectors to lists
      push!(F_list, F)
      push!(DIIS_RESID, diis_r)

      # Compute RHF energy
      SCF_E = tr((H + F) * D) + E_nuc
      dE = SCF_E - E_old
      dRMS = norm(diis_r)
      @printf("SCF Iteration %3d: Energy = %4.16f dE = %1.5e dRMS = %1.5e \n",
                          scf_iter,        SCF_E, SCF_E - E_old,     dRMS)

      # SCF Converged?
      if abs(SCF_E - E_old) < E_conv && dRMS < D_conv
          break
      end
      E_old = SCF_E

      if scf_iter >= 2
         # Build B matrix
         B_dim = length(F_list) + 1
         B = zeros(B_dim, B_dim)
         B[end,   :] .= -1
         B[:  , end] .= -1
         B[end, end]  =  0
         for i in eachindex(F_list), j in eachindex(F_list)
            B[i, j] = dot(DIIS_RESID[i],  DIIS_RESID[j])
         end

         # Build RHS of Pulay equation
         rhs = zeros(B_dim)
         rhs[end] = -1

         # Solve Pulay equation for c_i's with Julia
         coeff = B \ rhs

         # Build DIIS Fock matrix
         F = zeros(size(F))
         for i in 1:length(coeff) - 1
            F += coeff[i] * F_list[i]
         end
      end

      # Compute new orbital guess with DIIS Fock matrix
      F_p =  A * F * A
      e, C_p = eigen(Hermitian(F_p))
      C = A * C_p
      C_occ = C[:, 1:ndocc]
      D = C_occ * C_occ'

      # MAXITER exceeded?
      if scf_iter == MAXITER
          psi4.core.clean()
          throw(MethodError("Maximum number of SCF iterations exceeded."))
      end
   end
   SCF_E
end

# Post iterations
println("\nSCF converged.")
println("Final RHF Energy: $SCF_E [Eh]")
# -

# Congratulations! You've written your very own Restricted Hartree-Fock program with DIIS convergence accelleration!  Finally, let's check your final RHF energy against  Psi4:

# Compare to Psi4
SCF_E_psi = psi4.energy("SCF")
psi4.compare_values(SCF_E_psi, SCF_E, 6, "SCF Energy")

# ## References
# 1. P. Pulay. *Chem. Phys. Lett.* **73**, 393-398 (1980)
# 2. C. David Sherrill. *"Some comments on accellerating convergence of iterative sequences using direct inversion of the iterative subspace (DIIS)".* Available at: vergil.chemistry.gatech.edu/notes/diis/diis.pdf. (1998)


## Code for our Types and conversion methods

## Symbol class for controlling dispatch
abstract SymbolicObject <: Number

## Basic types defined here
"""

* `Sym("x")`
* `Sym(:x)`
* `Sym("x, y, z")`

The `Sym` type is an immutable type holding a reference to an
underlying python-based SymPy object. Many methods are extended to the
`Sym` type. Instances can be constructed in many ways. The one caveat
is the variables can not be function names in base.

"""
immutable Sym <: SymbolicObject
    x::PyCall.PyObject
end
Sym(s::SymbolicObject) = s

## Matrix type
immutable SymMatrix <: SymbolicObject
    x::PyCall.PyObject
end

## sets
immutable SymSet <: SymbolicObject
    x::PyCall.PyObject
end

## complex float
## this cause issue with printing on non-complex objects
#mpctype = sympy.mpmath["ctx_mp_python"]
#pytype_mapping(mpctype, Sym)

## some typealiases
typealias SymOrReal @compat Union{Sym, Real}
typealias SymOrNumber @compat Union{Sym, Number}
typealias SymOrString @compat Union{Sym, AbstractString}
typealias SymbolicTypes  @compat Union{AbstractString, Symbol, SymbolicObject}

PyCall.PyObject(x::SymbolicObject) = x.x

## Promotion
## promote up to symbolic so that math ops work
promote_rule{T<:SymbolicObject, S<:Number}(::Type{T}, ::Type{S} ) = T

## Conversion
convert(::Type{Sym}, o::PyCall.PyObject) = Sym(o)
convert(::Type{PyObject}, s::Sym) = s.x

function convert(::Type{Tuple}, o::PyCall.PyObject)
    ## check that o is a tuple?
    ## PyCall.pytypeof(o)
    n = o[:__len__]()
    ntuple(i -> o[:__getitem__](i-1), n)
end

## rational
convert{T<:SymbolicObject}(::Type{T}, x::Rational) = sympy_meth(:Rational, x.num, x.den)::T

## big. Need mpmath installed separately -- not as a SymPy module as that is how it is called in PyCall
convert{T<:SymbolicObject}(::Type{T}, x::BigFloat) = Sym(PyCall.PyObject(x))::T
convert(::Type{Sym}, x::Complex{BigFloat}) = Sym(PyCall.PyObject(x))::Sym

## real
convert{S<:SymbolicObject, T <: Real}(::Type{S}, x::T) = sympy_meth(:sympify, x)::S
convert{T <: Real}(::Type{T}, x::Sym) = convert(T, project(x))

## complex
## IM is SymPy's "i" (sympy[:I], not Python's
## Sym(PyCall.PyObject(im)) which gives 1j.
function convert(::Type{Sym}, x::Complex)
    y = ifelse(isa(x, Complex{Bool}), real(x) + imag(x) * im, x)
    real(y) + imag(y) * IM
end
convert(::Type{Complex}, x::Sym) = complex(map(x -> convert(Float64, x), x[:as_real_imag]())...)::Sym
complex(x::Sym) = convert(Complex, x)
complex(xs::Array{Sym}) = map(complex, xs)

## matrices
convert(::Type{SymMatrix}, o::PyCall.PyObject) = SymMatrix(o)
convert(::Type{Sym}, o::SymMatrix) = Sym(o.x)
convert(::Type{SymMatrix}, o::Sym) = SymMatrix(o.x)

## string
convert(::Type{Sym}, o::AbstractString) = sympy_meth(:sympify, o)
convert(::Type{Sym}, o::Symbol) = sympy_meth(:sympify, string(o))

"""

Get the free symbols in a more convenient form than as returned by `free_symbols`.

Just `free_symbols` now does the same thing. This function will be deprecated.
"""
function get_free_symbols(ex::Sym)
    free = free_symbols(ex)
    n = free[:__len__]()
    n == 0 && return(Sym[])

    vars = [free[:pop]()]
    if n > 1
        for i in 1:(n-1)
            push!(vars, free[:pop]())
        end
    end
    vars
end

## Conversion to function a bit hacky
## we use free_symbols to get the free symbols, then create a function
## with arguments in this order. No problem with only one variable, but
## may be confusing when more than one in ex.
## Output is symbolic. Conversion is necessary to use output as Julia values.
## SymPy has `lamdify` for this task too.
function convert(::Type{Function}, ex::Sym)
    vars = free_symbols(ex)
    len = length(vars)
    if len == 0
        return x -> ex
    end
    local out
    (args...) -> begin
        out = ex
        for i in 1:len
            out = object_meth(out, :subs, vars[i], args[i]) #convert(Function, out[:subs])(project(vars[i]), args[i])
        end
        out
    end
end

## For plotting we need to know if a function has 1, 2, or 3 free variables
function as_nfunction(ex::Sym, nvars=1)
    free = free_symbols(ex)
    vars = [free[:pop]()]
    for i in 1:free[:__len__]()
        push!(vars, free[:pop]())
    end
    len = length(vars)

    if len == nvars
        convert(Function, ex)
    else
        throw(DimensionMismatch("Expecting $nvars free variables and found $len"))
    end
end

"""
Convert to a julia-valued function from R -> R::Float64
"""
type ScalarFunction; end
function Base.convert(::Type{ScalarFunction}, ex::Sym)
    vars = free_symbols(ex)
    length(vars) == 1 || error("Scalar function conversion is for expression of one variable")
    u -> convert(Float64, N(subs(ex, vars[1], u)))
end


@testset "Heuristics" begin
    @testset "farmer" begin
        NS = 3  # number of scenarios
        probability = ones(3) / 3
        CROPS = 1:3 # set of crops (wheat, corn and sugar beets, resp.)
        PURCH = 1:2 # set of crops to purchase (wheat and corn, resp.)
        SELL = 1:4  # set of crops to sell (wheat, corn, sugar beets under 6K and those over 6K)
        Cost = [150 230 260]    # cost of planting crops
        Budget = 500            # budget capacity
        Purchase = [238 210]    # purchase price
        Sell = [170 150 36 10]  # selling price
        Yield = [3.0 3.6 24.0; 2.5 3.0 20.0; 2.0 2.4 16.0]
        Minreq = [200 240 0]    # minimum crop requirement

        @testset "MILP" begin
            # This creates a Lagrange dual problem for each scenario s.
            function create_scenario_model(s::Int64)
                m = Model(GLPK.Optimizer)
                @variable(m, 0 <= x[i=CROPS] <= 500, Int)
                @variable(m, y[j=PURCH] >= 0)
                @variable(m, w[k=SELL] >= 0)

                @objective(m, Min,
                    probability[s] * sum(Cost[i] * x[i] for i=CROPS)
                    + probability[s] * sum(Purchase[j] * y[j] for j=PURCH)
                    - probability[s] * sum(Sell[k] * w[k] for k=SELL))

                @constraint(m, sum(x[i] for i=CROPS) <= Budget)
                @constraint(m, [j=PURCH], Yield[s,j] * x[j] + y[j] - w[j] >= Minreq[j])
                @constraint(m, Yield[s,3] * x[3] - w[3] - w[4] >= Minreq[3])
                @constraint(m, w[3] <= 6000)
                return m
            end

            models = Dict{Int,JuMP.Model}(s => create_scenario_model(s) for s in 1:NS)
            coupling_variables = Vector{DD.CouplingVariableRef}()
            for s in 1:NS
                model = models[s]
                xref = model[:x]
                for i in CROPS
                    push!(coupling_variables, DD.CouplingVariableRef(s, i, xref[i]))
                end
            end

            @testset "All block heuristic with trust region method" begin
                # Create DualDecomposition instance.
                algo = DD.LagrangeDual()

                # Add Lagrange dual problem for each scenario s.
                for s in 1:NS
                    DD.add_block_model!(algo, s, models[s])
                end

                # Set nonanticipativity variables as an array of symbols.
                DD.set_coupling_variables!(algo, coupling_variables)

                # Lagrange master method
                LM = DD.BundleMaster(BM.TrustRegionMethod, GLPK.Optimizer)

                # add heuristic
                DD.add!(DD.AllBlockHeuristic, algo)

                # Solve the problem with the solver; this solver is for the underlying bundle method.
                DD.run!(algo, LM)

                @show DD.primal_objective_value(algo)
                @show DD.primal_solution(algo)
                @test isapprox(DD.primal_objective_value(algo), -107701.0, rtol=1e-3)
            end

            @testset "Rounding heuristic with trust region method" begin
                # Create DualDecomposition instance.
                algo = DD.LagrangeDual()

                # Add Lagrange dual problem for each scenario s.
                for s in 1:NS
                    DD.add_block_model!(algo, s, models[s])
                end

                # Set nonanticipativity variables as an array of symbols.
                DD.set_coupling_variables!(algo, coupling_variables)

                # Lagrange master method
                LM = DD.BundleMaster(BM.TrustRegionMethod, GLPK.Optimizer)

                # add heuristic
                DD.add!(DD.RoundingHeuristic, algo)

                # Solve the problem with the solver; this solver is for the underlying bundle method.
                DD.run!(algo, LM)

                @show DD.primal_objective_value(algo)
                @show DD.primal_solution(algo)
                @test isapprox(DD.primal_objective_value(algo), -107342., rtol=1e-3)
            end
        end
    end
end


@acc function SOR_iteration(img::Array{Float64,2},img0::Array{Float64,2}, iterations::Int, om::Float64=1.0, lambda2::Float64=0.05)
    buf = Array(Float64, size(img)...)
    runStencil(buf, img, img0, iterations, :oob_wraparound) do b, a, c

        b[0,0] = (1.0-om)*a[0,0] + om*( a[0,0] + 0.15*(
                                    (-(1/12) * a[0,2] + (4/3) * a[0,1] - (5/2)*a[0,0] + (4/3) * a[0,-1] -(1/12) * a[0,-2])
                      +lambda2*     (-(1/12) * a[2,0] + (4/3) * a[1,0] - (5/2)*a[0,0] + (4/3) * a[-1,0] -(1/12) * a[-2,0])
                                   -( (1/12) * c[0,2] + (2/3) * c[0,1]                  - (2/3) * c[0,-1] -(1/12) * c[0,-2])
              ))
       return a, b, c
    end
    return img
end


using PrincipalMomentAnalysis
using Test
using LinearAlgebra
using Statistics

# change back to this simple oneliner if we can just ignore problems in StaticArrays that is imported by NearestNeighbors
# @test [] == detect_ambiguities(Base, Core, PrincipalMomentAnalysis)
@testset "Method ambiguities" begin
	ambiguities = detect_ambiguities(Base, Core, PrincipalMomentAnalysis)
	filter!(x->x[1].module==PrincipalMomentAnalysis || x[2].module==PrincipalMomentAnalysis, ambiguities)
	@test ambiguities == []
end

const simplices2kernelmatrix     = PrincipalMomentAnalysis.simplices2kernelmatrix
const simplices2kernelmatrixroot = PrincipalMomentAnalysis.simplices2kernelmatrixroot

include("test_simplices.jl")
include("test_pma.jl")
include("test_util.jl")


using BenchmarkTools
using Transducers

benchmark = true

isFloor(c) = c == '.'
isEmpty(c) = c == 'L'
isOccupied(c) = c == '#'

readLayout(filename) = readlines(filename) |> Map(line -> collect(line)) |> collect

function isOccupied(layout, rowNo, colNo)
    ((rowNo < 1) || (colNo < 1) || (rowNo > length(layout))) && return false
    row = layout[rowNo]
    ((colNo > length(row)) || !isOccupied(row[colNo])) && return false
    true
end

const directions = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]

function iterate!(layout, numberOfOccupiedSeats, occupiedSeatsThreshold)
    changes = Tuple{Int64, Int64, Char}[]
    for rowNo = 1:length(layout)
        row = layout[rowNo]
        for colNo = 1:length(row)
            c = row[colNo]
            isFloor(c) && continue
            n = numberOfOccupiedSeats(layout, rowNo, colNo)
            isEmpty(c) && n == 0 && push!(changes, (rowNo, colNo, '#'))
            isOccupied(c) && n >= occupiedSeatsThreshold && push!(changes, (rowNo, colNo, 'L'))
        end
    end
    for (rowNo, colNo, c) in changes
        layout[rowNo][colNo] = c
    end
    !isempty(changes)
end

function dump(layout)
    foreach(row -> println(String(row)), layout)
    println()
end

function process(layout, numberOfOccupiedSeats, occupiedSeatsThreshold; trace = false)
    while true
        hasChanged = iterate!(layout, numberOfOccupiedSeats, occupiedSeatsThreshold)
        trace && dump(layout)
        !hasChanged && break
    end
    layout |> Cat() |> Filter(c -> isOccupied(c)) |> Map(c -> 1) |> foldxl(+; init = 0)
end

#### part 1

function numberOfAdjacentOccupiedSeats(layout, rowNo, colNo)
    directions |>
    Map(direction -> isOccupied(layout, rowNo + direction[1], colNo + direction[2])) |>
    Map(occupied -> occupied ? 1 : 0) |>
    sum
end

part1(layout; trace = false) = process(layout, numberOfAdjacentOccupiedSeats, 4; trace = trace)

@assert part1(readLayout("src/advent/day11/example.txt")) == 37

@show part1(readLayout("src/advent/day11/input.txt"))
benchmark && @btime part1(readLayout("src/advent/day11/input.txt"));

#### part 2

function isOccupiedInDirection(layout, currentLocation, direction)
    (rowNo, colNo) = currentLocation
    while true
        rowNo += direction[1]
        colNo += direction[2]
        ((rowNo < 1) || (rowNo > length(layout)) || (colNo < 1)) && return false
        row = layout[rowNo]
        (colNo > length(row)) && return false
        c = row[colNo]
        isOccupied(c) && return true
        isEmpty(c) && return false
    end
    false
end

function numberOfVisibleOccupiedSeats(layout, rowNo, colNo)
    directions |>
    Map(direction -> isOccupiedInDirection(layout, (rowNo, colNo), direction)) |>
    Map(occupied -> occupied ? 1 : 0) |>
    sum
end

part2(layout; trace = false) = process(layout, numberOfVisibleOccupiedSeats, 5; trace = trace)

@assert part2(readLayout("src/advent/day11/example.txt")) == 26

@show part2(readLayout("src/advent/day11/input.txt"))
benchmark && @btime part2(readLayout("src/advent/day11/input.txt"));


# # Visual

# The function `visual` can be used to give data-independent visual information about the plot
# (plotting function or attributes).

# The available plotting functions are documented [here](http://makie.juliaplots.org/dev/examples/plotting_functions/).
# Refer to plotting functions using upper CamelCase for `visual`'s first argument (e.g. `visual(Scatter), visual(BarPlot)`).
# See the documentation of each plotting function to discover the available attributes.

## Examples

using AlgebraOfGraphics, CairoMakie
set_aog_theme!()

df = (x=randn(1000), y=randn(1000))
plt = data(df) * mapping(:x, :y) * AlgebraOfGraphics.density(npoints=50)
draw(plt * visual(Heatmap)) # plot as heatmap (the default)

#

draw(plt * visual(colormap=:viridis)) # set a different colormap

#

draw(plt * visual(Contour)) # plot as contour

#

draw(plt * visual(Contour, linewidth=2)) # plot as contour with thicker lines


@testset "204.count-primes.jl" begin
    @test count_primes(1) == 0
    @test count_primes(10) == count_primes(11) == 4
    @test count_primes(1000) == 168
    @test count_primes(10000) == 1229
    @test count_primes(100000) == 9592
end


# -*- coding: utf-8 -*-
# ---
# jupyter:
#   jupytext:
#     comment_magics: false
#     formats: ipynb,jl:light
#     text_representation:
#       extension: .jl
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.4.2
#   kernelspec:
#     display_name: Julia 1.4.0
#     language: julia
#     name: julia-1.4
# ---

# # Vlasov–Maxwell in 1D2V
#
# Starting from the Vlasov equation for a particle with charge `q`, and mass `m` given in
#
# $x = (x,0,0)$ and $v = (v_1,v_2,0)$
#
# as well as
#
# $$ E(x, t) = (E_1, E_2, 0), \qquad B(x, t) = (0, 0, B3), \qquad f(x, v, t) = f(x, v_1, v_2, t),$$
#
# so that the Vlasov equation takes the form
#
# $$
# \frac{\partial f(x,v,t)}{\partial t}
# + v_1 \frac{\partial f(x,v,t)}{\partial x}
# + \frac{q}{m} \Big[ 􏰋\mathbf{E}(x,t)+B_3(x,t)􏰉 \begin{pmatrix}v2\\-v_1\end{pmatrix}􏰊􏰌 \Big] \cdot \nabla f(x,v,t) = 0,
# $$
#
# while Maxwell’s equations become
# $$
# \begin{eqnarray}
# \frac{\partial E_1(x, t)}{\partial t} &=& −J_1(x), \\
# \frac{\partial E_2(x, t)}{\partial t} &=& −\frac{\partial B(x, t)}{\partial x} − J_2(x), \\
# \frac{\partial B(x, t)}{\partial t} &=& −\frac{\partial E_2(x, t)}{\partial x} \\
# \frac{\partial E_1(x,t)}{\partial x} &=& \rho + \rho_B
# \end{eqnarray}
# $$
# with sources given by
# $$
# \rho  = q􏰘 \int f dv, \qquad J_1 =q \int f v_1dv, \qquad  J_2 = q \int f v_2 dv
# $$
# Note that $\nabla \cdot B = 0$ is manifest.

using ProgressMeter, Plots
using GEMPIC

# # Weibel instability
#
# We study a reduced 1d2v model with a perturbation along $x_1$, a magnetic field along $x_3$ and electric fields along the $x_1$ and $x_2$ directions. Moreover, we assume that the distribution function is independent of $v_3$. The initial distribution and fields are of the form
# $$
# f(x,\mathbf{v},t=0)=\frac{1}{2\pi\sigma_1\sigma_2} \exp \Big( - \frac{1}{2} \big( \frac{v_1^2}{\sigma_1^2} + \frac{v_2^2}{\sigma_2^2} \big) \Big) ( 1 + \alpha cos(kx)), \qquad x \in [0, 2\pi / k),
# $$

# $$ B_3(x, t = 0) = \beta \cos(kx), \qquad E_2(x, t = 0) = 0,$$
#
# and $E_1(x, t = 0)$ is computed from Poisson’s equation.
#
# $$ \sigma_1 = 0.02 / \sqrt{2} $$
# $$ \sigma_2 = \sqrt{12} \sigma_1 $$
#
#
# $ k = 1.25, α = 0 $ and $ \beta = −10^{−4}$.

# +
"""
    pic_vm_1d2v( )
Simulation of 1d2v Vlasov-Maxwell with simple PIC method,
periodic boundary conditions, Weibel instability.
FEM with splines, degree 3 for B and 2 for E
"""

β  = 0.0001

k  = 1.25
α  = 0.0
σ  = [0.2,  0.005773502691896]
μ  = [0.0, 0.0]

nx   = 32
xmin = 0.0
xmax = 2π / k

n_particles    = 10000
sampling_case  = :sobol
symmetric      = true
splitting_case = :symplectic
spline_degree  = 3

mesh   = Mesh( xmin, xmax, nx)
Lx = xmax - xmin
xg = LinRange(xmin, xmax, nx)

beta_sin_k(x) = beta * sin(2π * x / Lx)

df = CosSumGaussian{1,2}([[k]], [α], [σ], [μ] )
# -

v1min, v1max, nv1 = -1., 1., 64
v2min, v2max, nv2 = -0.015, 0.015, 64
v1 = LinRange(v1min, v1max, nv1) |> collect
v2 = LinRange(v2min, v2max, nv2) |> collect
f = zeros(Float64,(nv1,nv2))
for i in eachindex(v1), j in eachindex(v2)
    f[i,j] = GEMPIC.eval_v_density(df, [v1[i],v2[j]])
end

contourf(v1, v2, f)

 # Initialize the particles   (mass and charge set to 1.0 with one weight)
mass, charge = 1.0, 1.0
particle_group = ParticleGroup{1,2}( n_particles)

sampler = ParticleSampler{1,2}( sampling_case, symmetric, n_particles)

sample!(  particle_group, sampler, df, mesh)

xp = view( particle_group.array, 1, :)
vp = view( particle_group.array, 2:3, :)
wp = view( particle_group.array, 4, :);

histogram(vp[1,:], weights=wp, normalize=true, bins=100)

histogram(vp[2,:], weights=wp, normalize=true, bins=100)

kernel_smoother1 = ParticleMeshCoupling( mesh, n_particles, spline_degree-1, :galerkin)
kernel_smoother0 = ParticleMeshCoupling( mesh, n_particles, spline_degree, :galerkin)
rho = zeros(Float64, nx);
efield_poisson = zeros(Float64, nx)
# Init!ialize the field solver
maxwell_solver = Maxwell1DFEM(mesh, spline_degree)
# efield by Poisson
solve_poisson!( efield_poisson, particle_group, kernel_smoother0, maxwell_solver, rho )

# +
# Initialize the arrays for the spline coefficients of the fields
efield_dofs = [copy(efield_poisson), zeros(Float64, nx)]
bfield_dofs = zeros(Float64, nx)

propagator = HamiltonianSplitting( maxwell_solver,
                                   kernel_smoother0,
                                   kernel_smoother1,
                                   particle_group,
                                   efield_dofs,
                                   bfield_dofs)

efield_dofs_n = propagator.e_dofs
# -
# bfield = beta*cos(kx): Use b = M{-1}(N_i,beta*cos(kx))
beta_cos_k(x) = β * cos(2π * x / Lx)
l2projection!( bfield_dofs, maxwell_solver, beta_cos_k, spline_degree-1)
plot( xg, bfield_dofs )

thdiag = TimeHistoryDiagnostics( particle_group, maxwell_solver,
                        kernel_smoother0, kernel_smoother1 );

# +
steps, Δt = 500, 0.05

time = @elapsed begin

    @showprogress 1 for j = 1:steps # loop over time

        # Strang splitting
        strang_splitting!(propagator, Δt, 1)

        # Diagnostics
        solve_poisson!( efield_poisson, particle_group,
                        kernel_smoother0, maxwell_solver, rho)

        write_step!(thdiag, j * Δt, spline_degree,
                        efield_dofs,  bfield_dofs,
                        efield_dofs_n, efield_poisson)

    end

end

@show time

# -
first(thdiag.data, 10)


# ---
# title: 275. H-Index II
# id: problem275
# author: Indigo
# date: 2021-06-05
# difficulty: Medium
# categories: Binary Search
# link: <https://leetcode.com/problems/h-index-ii/description/>
# hidden: true
# ---
#
# Given an array of citations **sorted  in ascending order **(each citation is a
# non-negative integer) of a researcher, write a function to compute the
# researcher's h-index.
#
# According to the [definition of h-index on
# Wikipedia](https://en.wikipedia.org/wiki/H-index): "A scientist has index  _h_
# if  _h_  of his/her  _N_  papers have  **at least**   _h_  citations each, and
# the other  _N − h_ papers have  **no more than**   _h  _citations each."
#
# **Example:**
#
#
#
#     Input: citations = [0,1,3,5,6]
#     Output: 3
#     Explanation:[0,1,3,5,6] means the researcher has 5 papers in total and each of them had
#                  received 0, 1, 3, 5, 6 citations respectively.
#                  Since the researcher has 3 papers with **at least** 3 citations each and the remaining
#                  two with **no more than** 3 citations each, her h-index is 3.
#
# **Note:**
#
# If there are several possible values for  _h_ , the maximum one is taken as
# the h-index.
#
# **Follow up:**
#
#   * This is a follow up problem to [H-Index](https://leetcode.com/problems/h-index/description/), where `citations` is now guaranteed to be sorted in ascending order.
#   * Could you solve it in logarithmic time complexity?
#
#
## @lc code=start
using LeetCode

function h_index_ii(citations::Vector{Int})
    l, r = 1, length(citations) + 1
    while l < r
        mid = l + r >> 1
        if citations[end + 1 - mid] >= mid
            l = mid
        else
            r = mid - 1
        end
    end
    return r
end
## @lc code=end


"""
    warp(img, tform, [indices], [degree = Linear()], [fill = NaN]) -> imgw

Transform the coordinates of `img`, returning a new `imgw`
satisfying `imgw[I] = img[tform(I)]`. This approach is known as
backward mode warping. The transformation `tform` must accept a
`SVector` as input. A useful package to create a wide variety of
such transformations is
[CoordinateTransformations.jl](https://github.com/FugroRoames/CoordinateTransformations.jl).

# Reconstruction scheme

During warping, values for `img` must be reconstructed at
arbitrary locations `tform(I)` which do not lie on to the lattice
of pixels. How this reconstruction is done depends on the type of
`img` and the optional parameter `degree`.

When `img` is a plain array, then on-grid b-spline interpolation
will be used. It is possible to configure what degree of b-spline
to use with the parameter `degree`. For example one can use
`degree = Linear()` for linear interpolation, `degree =
Constant()` for nearest neighbor interpolation, or `degree =
Quadratic(Flat())` for quadratic interpolation.

In the case `tform(I)` maps to indices outside the original
`img`, those locations are set to a value `fill` (which defaults
to `NaN` if the element type supports it, and `0` otherwise). The
parameter `fill` also accepts extrapolation schemes, such as
`Flat()`, `Periodic()` or `Reflect()`.

For more control over the reconstruction scheme --- and how
beyond-the-edge points are handled --- pass `img` as an
`AbstractInterpolation` or `AbstractExtrapolation` from
[Interpolations.jl](https://github.com/JuliaMath/Interpolations.jl).

# The meaning of the coordinates

The output array `imgw` has indices that would result from
applying `inv(tform)` to the indices of `img`. This can be very
handy for keeping track of how pixels in `imgw` line up with
pixels in `img`.

If you just want a plain array, you can "strip" the custom
indices with `parent(imgw)`.

# Examples: a 2d rotation (see JuliaImages documentation for pictures)

```
julia> using Images, CoordinateTransformations, TestImages, OffsetArrays

julia> img = testimage("lighthouse");

julia> axes(img)
(Base.OneTo(512),Base.OneTo(768))

# Rotate around the center of `img`
julia> tfm = recenter(RotMatrix(-pi/4), center(img))
AffineMap([0.707107 0.707107; -0.707107 0.707107], [-196.755,293.99])

julia> imgw = warp(img, tfm);

julia> axes(imgw)
(-196:709,-68:837)

# Alternatively, specify the origin in the image itself
julia> img0 = OffsetArray(img, -30:481, -384:383);  # origin near top of image

julia> rot = LinearMap(RotMatrix(-pi/4))
LinearMap([0.707107 -0.707107; 0.707107 0.707107])

julia> imgw = warp(img0, rot);

julia> axes(imgw)
(-293:612,-293:611)

julia> imgr = parent(imgw);

julia> axes(imgr)
(Base.OneTo(906),Base.OneTo(905))
```
"""
function warp(img::AbstractExtrapolation{T}, tform, inds::Tuple = autorange(img, inv(tform))) where T
    out = similar(Array{T}, inds)
    warp!(out, img, try_static(tform, img))
end

function warp!(out, img::AbstractExtrapolation, tform)
    @inbounds for I in CartesianIndices(axes(out))
        out[I] = _getindex(img, tform(SVector(I.I)))
    end
    out
end

function warp(img::AbstractArray, tform, inds::Tuple, args...)
    etp = box_extrapolation(img, args...)
    warp(etp, try_static(tform, img), inds)
end

function warp(img::AbstractArray, tform, args...)
    etp = box_extrapolation(img, args...)
    warp(etp, try_static(tform, img))
end

"""
    imrotate(img, θ, [indices], [degree = Linear()], [fill = NaN]) -> imgr

Rotate image `img` by `θ`∈[0,2π) in a clockwise direction around its center point. To rotate the image counterclockwise, specify a negative value for angle.

By default, rotated image `imgr` will not be cropped. Bilinear interpolation will be used and values outside the image are filled with `NaN` if possible, otherwise with `0`.

# Examples
```julia
julia> img = testimage("cameraman")

# rotate with bilinear interpolation but without cropping
julia> imrotate(img, π/4)

# rotate with bilinear interpolation and with cropping
julia> imrotate(img, π/4)[axes(img)...]

# rotate with nearest interpolation but without cropping
julia> imrotate(img, π/4, Constant())
```

See also [`warp`](@ref), [Interpolations.jl](https://github.com/JuliaMath/Interpolations.jl).
"""
function imrotate(img::AbstractArray{T}, θ::Real, args...) where T
    θ = floor(mod(θ,2pi)*typemax(Int16))/typemax(Int16) # periodic discretezation
    tform = recenter(RotMatrix{2}(θ), center(img))
    warp(img, tform, args...)
end


# Let X be a Permutation of N
#	X::Array{Int, 1}
#	length(X) == N
#	X[i] = j indicates that the item in position i is sent to position j

###
# Group Operations
###

# Parameters:
#	P1::Array{Int, 1}
#	- the first permutation
#	P2::Array{Int, 1}
#	- the second permutation
# Return Values:
#	Prod::Array{Int, 1}
#	- the permutation that is P1 * P2
# Notes:
# 	- P1 and P2 must be permutations of the same size
function sn_multiply(P1::Array{Int, 1}, P2::Array{Int, 1})
	N = length(P1)
	Prod = Array(Int, N)
	for i = 1:N
		Prod[i] = P1[P2[i]]
	end
	return Prod
end

# Parameters:
#	P::Array{Int, 1}
#	- a permutation
# Return Values:
#	Inv::Array{Int, 1}
#	- the permutation that is the inverse of P
function sn_inverse(P::Array{Int, 1})
	N = length(P)
	Inv = Array(Int, N)
	for i = 1:N
		Inv[P[i]] = i
	end
	return Inv
end

###
# Permutation Constructors
###

# Parameters:
#	N::Int
#   - the size of the permutation
# Return Values:
#	P::Array{Int, 1}
#	- a random permutation of N
function sn_p(N::Int)
	index = rand(1:factorial(N))
	P = index_permutation(N, index)
	return P
end

# Parameters:
#	N::Int
#	- the size of the permutation
#	LB::Int
#	- the first position that is reassigned
#	UB::Int
#	- the last position that is reassigned
# Return Values:
#	CC::Array{Int, 1}
#	- the permutation of N that is the contiguous cycle [[LB, UB]]
#	- this is the permutation that sends LB to LB + 1, LB + 1 to LB + 2, ... ,  UB - 1 to UB, and UB to LB
# Notes
#	- 1 <= LB <= UB <= N
function sn_cc(N::Int, LB::Int, UB::Int)
	CC = Array(Int, N)
	for i = 1:(LB - 1)
		CC[i] = i
	end
	for i = LB:(UB - 1)
		CC[i] = i + 1
	end
	CC[UB] = LB
	for i = (UB + 1):N
		CC[i] = i
	end
	return CC
end

# Parameters:
#	N::Int
#	- the size of the permutation
# Return Values:
#	CC::Array{Int, 1}
#	- a random contiguous cycle of N
function sn_cc(N::Int)
	lb = rand(1:N)
	ub = rand(lb:N)
	CC = sn_cc(N, lb, ub)
	return CC
end

# Parameters:
#	N::Int
#	- the size of the permutation
#	K::Int
#	- the position that is being reassigned
# Return Values:
#	AT::Array{Int, 1}
#	- the permutation of N that is the adjacent transposition (K, K+1)
#	- this is the permutation that sends K to K + 1 and K + 1 to K
# Notes:
#	- 1 <= K < N
function sn_at(N::Int, K::Int)
	AT = Array(Int, N)
	for i = 1:(K - 1)
		AT[i] = i
	end
	AT[K] = K + 1
	AT[K + 1] = K
	for i = (K + 2):N
		AT[i] = i
	end
	return AT
end

# Parameters:
#	N::Int
#	- the size of the permutation
# Return Values:
#	AT::Array{Int, 1}
#	- a random adjacent transposition of N
function sn_at(N::Int)
	k = rand(1:(N - 1))
	AT = sn_at(N, k)
	return AT
end

# Parameters:
#	N::Int
#	- the size of the permutation
#	I::Int
#	- the first postition that is being reassigned
#	J::Int
#	- the second position that is being reassigned
# Return Values:
#	Tr::Array{Int, 1}
#	- the permutation of N that is the transposition (I, J)
#	- this is the permutation that sends I to J and J to I
# Notes:
#	- 1 <= I <= N
#	- 1 <= J <= N
function sn_t(N::Int, I::Int, J::Int)
	Tr = Array(Int, N)
	for i = 1:N
		Tr[i] = i
	end
	Tr[I] = J
	Tr[J] = I
	return Tr
end

# Parameters:
#	N::Int
#	- the size of the permutation
# Return Values:
#	Tr::Array{Int, 1}
#	- a random transposition of N
function sn_t(N::Int)
	i = rand(1:N)
	j = rand(1:N)
	Tr = sn_t(N, i, j)
	return Tr
end

###
# Factorizations (and related operations) on the Left Coset Tree
###

# Parameters:
#	P::Array{Int, 1}
#	- a permutation
# Return Values:
#	CCF::Array{Int, 1}
#	- the Contiguous Cycle Factoriztion of P
#	- P = product for i = 1:(N - 1) of sn_cc(N, CCF[i], N + 1 - i)
function permutation_ccf(P::Array{Int, 1})
	N = length(P)
	CCF = Array(Int, N - 1)
	i = 1
	for j = N:-1:2
		CCF[i] = P[j]
		cc = sn_cc(N, P[j], j)
		cc_inv = sn_inverse(cc)
		P = sn_multiply(cc_inv, P)
		i += 1
	end
	return CCF
end

# Parameters:
#	CCF::Array{Int, 1}
#	- a contiguous cycle factorization of some permutation
# Return Values:
#	Index::Int
#	- the unique index that the permutation corresponding to CCF maps to
function ccf_index(CCF::Array{Int, 1})
	N = length(CCF) + 1
	Index = 1
	for i = 1:(N - 1)
		N -= 1
		if CCF[i] != 1
			Index += (CCF[i] - 1) * factorial(N)
		end
	end
	return Index
end

# Parameters:
#	P::Array{Int, 1}
#	- a permutation
# Return Values:
#	Index::Int
#	- the unique index that P maps to
function permutation_index(P::Array{Int, 1})
	ccf = permutation_ccf(P)
	index = ccf_index(ccf)
	return index
end

# Parameters:
#	N::Int
#	- the size of the permutation that maps to Index
#	Index::Int
#	- the index of some permutation of N
# Return Values:
#	CCF::Array{Int, 1}
#	- the contiguous cycle factorization that corresponds to the permutation that maps to Index
function index_ccf(N::Int, Index::Int)
	CCF = Array(Int, N - 1)
	Index -= 1
	for i = 1:(N - 1)
		q = floor(Index / factorial(N - i))
		Index -= q * factorial(N - i)
		CCF[i] = q + 1
	end
	return CCF
end

# Parameters:
#	CCF::Array{Int, 1}
#	- a contiguous cycle factorization of some permutation
# Return Values:
#	P::Array{Int, 1}
#	- the permutation that corresponds to CCF
function ccf_permutation(CCF::Array{Int, 1})
	N = length(CCF) + 1
	P = Array(Int, N)
	for i = 1:N
		P[i] = i
	end
	for i = 1:(N - 1)
		cc = sn_cc(N, CCF[i], N + 1 - i)
		P = sn_multiply(P, cc)
	end
	return P
end

# Parameters:
#	N::Int
#	- the size of the permutation that maps to Index
#	Index::Int
#	- the index of some permutation of N
# Return Values:
#	P::Array{Int, 1}
#	- the permutation of N that maps to Index
function index_permutation(N::Int, Index::Int)
	ccf = index_ccf(N, Index)
	permutation = ccf_permutation(ccf)
	return permutation
end

# Parameters:
#	P::Array{Int, 1}
#	- a permutation
# Return Values
#	ATF::Array{Int, 1}
#	- the adjacent transposition factorization of P
#	- P = product for i = 1:length(ATF) of sn_at(N, ATF[i])
function permutation_atf(P::Array{Int, 1})
	N = length(P)
	CCF = permutation_ccf(P)
	dATF = Array(Array{Int, 1}, length(CCF))
	L = 0
	for i = 1:length(CCF)
		P = CCF[i]
		D = N - P
		P -= 1
		L += D
		N -= 1
		dATFs = Array(Int, D)
		for j = 1:D
			dATFs[j] = P + j
		end
		dATF[i] = dATFs
	end
	ATF = Array(Int, L)
	index = 1
	for i = 1:length(CCF)
		for j = 1:length(dATF[i])
			ATF[index] = dATF[i][j]
			index += 1
		end
	end
	return ATF
end

# Parameters:
#	P::Array{Int, 1}
#	- a permutation
#	YORnp::Array{SparseMatrixCSC, 1}
#	- YORnp[i] is Young's Orthogonal Representation for the adjacent transposition (i, i + 1) corresponding to the pth partition of n
# Return Values:
# 	RM::Array{Float64, 2}
#	- Young's Orthogonal Representation of P corresponding to the pth partition of n
function yor_permutation(P::Array{Int, 1}, YORnp::Array{SparseMatrixCSC, 1})
	ATF = permutation_atf(P)
	if length(ATF) == 0
		Dim = size(YORnp[1], 1)
		RM = eye(Dim)
		return RM
	else
		RM = copy(full(YORnp[ATF[1]]))
		for i = 2:length(ATF)
			RM = RM * full(YORnp[ATF[i]])
		end
		return RM
	end
end


module RobustStatsTutorial
#--------------------------------------------
# SOURCE CODE FOR ROBUST STATISTICS TUTORIAL
#--------------------------------------------

#Draft order
#Plot of data from t-distribution with DoF = 2. Set specific seed. Show how much better we can do by eliminating a couple of observations.
#Simulate empirical densities of sample means, sample medians, and sample trimmed means, for t-distribution with DoF = 2
#Discuss fat-tails of t-distribution with DoF = 2. Explain why kurtosis, although the common characterisation, is not an ideal measure of how fat the tails are. Possibly plot sample kurtosis as a function of sample size.
#Introduce alternative robust measure of fat-tails.
#Provide measures of fat-tails for t-distribution with DoF = 2.
#Provide measures of fat-tails for unconditional risky asset returns.
#Show how fat-tails in unconditional risky asset returns can be duplicated using a conditionally Normal model, i.e. heteroskedasticity in Normal sequence
#Something to do with estimating first moment of risky asset returns (?)
#Move onto OLS model for risky asset returns
#Compare least squares estimated forecast model for risky asset returns with robust loss estimation method
#Also compare to case where returns are standardised for volatility first
#Might need to compare to other robust methods of estimation too, ie ridge regression, lasso (? not robust...)

#Load relevant modules
using Base.Dates, Compose, Gadfly, StatsBase, KernelDensity, Distributions, DependentBootstrap, NLopt
#WARNING: DependentBootstrap is not currently an official package (although I am the author). To install, use:
#pkg.clone("https://github.com/colintbowers/DependentBootstrap.jl.git")

#Fixed output directory based on Linux OS. Will need to be adjusted for Windows or Mac users.
const outputDir = "/home/"*ENV["USER"]*"/robust_stats_tutorial_output/"::ASCIIString
const dataDir = "/home/"*ENV["USER"]*"/.julia/v"*string(VERSION.major)*"."*string(VERSION.minor)*"/RobustStatsTutorial/data/"::ASCIIString

#Fixed theme override for all plots and colour order for multivariate plots
const defaultThemeOverride = Theme(key_title_font_size=14pt, key_label_font_size=14pt, minor_label_font_size=14pt, major_label_font_size=17pt, key_title_font_size=17pt, default_color=color("blue"), default_point_size=4pt)::Theme
const colourVec = ["blue", "green", "red", "black", "purple", "dark blue", "darkgreen", "gray", "brown", "cyan",
				   "violetred", "blue2", "orange", "green2", "darkred", "gray20", "chocolate", "brown2", "darkorange", "gray40",
				   "cadetblue", "violet", "green4", "brown4", "blue4"]::Vector{ASCIIString} #A vector of 25 colours to use in plots

#Security list used throughout the tutorial
const secList = ["AMP", "ANZ", "BHP", "CBA", "CCL", "JBH", "LLC", "NAB", "RIO", "SUN", "TLS", "TOL", "WBC", "WES", "WOW"]::Vector{ASCIIString}

#Create output directory
!isdir(outputDir) && mkdir(outputDir)

#--------------------------------------------
# SIMPLE EXAMPLE WITH t-DISTRIBUTION
#--------------------------------------------
#Plot of data from t-distribution with DoF = 2. Set specific seed. Show how much better we can do by eliminating a couple of observations.
function t_dist_simple_example()
    numObs = 20 #Number of observations to use in this subsection
    tDist = TDist(2) #Initiate t-distribution with 2 degrees of freedom
    srand(78) #Specify a range that results in a good dataset for demonstrating the point (yes I mined this from the first 100 integers)
    tData = rand(tDist, numObs) #Simulate iid from t-distribution
    tDataSort = sort(tData)
    tDataMean = mean(tData)
    p = 0.2 #Set a trimming proportion
    tDataTrimMean = tmean(tDataSort, p, sorted=true) #Get trimmed mean
	println("tDist data sample mean = " * string(tDataMean))
	println("tDist data trim mean = " * string(tDataTrimMean))
    (numLower, numUpper) = tmean_num_cut(tDataSort, p/2, p/2) #Get number of observations cut below and above
    yLower = mean(tDataSort[numLower:numLower+1]) #Get the midpoint of the last lower cut observation and the first lower kept observation
    yUpper = mean(tDataSort[end-numUpper:end-numUpper+1]) #Get the midpoint of the last upper kept observation and the first upper cut observation
    println("Plotting data")
    dataPlot1 = plot(x=collect(1:numObs), y=tData, Geom.point, defaultThemeOverride)
    dataPlot2 = plot(x=collect(1:numObs), y=tData, yintercept=[yLower, yUpper], Geom.point, Geom.hline, defaultThemeOverride)
    draw_local(dataPlot1, "t_Dist_Data", dirPath=outputDir, fileType=:svg)
    draw_local(dataPlot2, "t_Dist_Data_With_Trim_Cutoff", dirPath=outputDir, fileType=:svg)
    println("Routine complete")
end

#--------------------------------------------
# ESTIMATOR DENSITIES WITH t-DISTRIBUTION
#--------------------------------------------
#Simulate empirical densities of sample means, sample medians, and sample trimmed means, for t-distribution with DoF = 2
function t_dist_estimator_densities( ; numIter::Int=5000, numObs::Int=50, tMeanProp1::Float64=0.1, tMeanProp2::Float64=0.5)
    tDist = TDist(2) #Initiate t-distribution with 2 degrees of freedom
    estVecVec = Vector{Float64}[ Array(Float64, numIter) for k = 1:4 ] #Pre-allocate structure to hold sample estimators
    println("Simulating estimators")
    for n = 1:numIter
        #Simulate t-distribution data, and then get mean, two trimmed means, and the median for each dataset
        tData = rand(tDist, numObs)
        tDataSort = sort(tData)
        estVecVec[1][n] = mean(tData)
        estVecVec[2][n] = tmean(tDataSort, tMeanProp1, sorted=true)
        estVecVec[3][n] = tmean(tDataSort, tMeanProp2, sorted=true)
        estVecVec[4][n] = median_sorted(tDataSort)
    end
    println("Building kernel densities")
    kDVec = KernelDensity.UnivariateKDE{FloatRange{Float64}}[ kde(estVecVec[k], boundary=(-1.0, 1.0)) for k = 1:4 ] #Get kernel density for each estimator
    layerVec = Vector{Gadfly.Layer}[ layer(x=collect(kDVec[k].x), y=kDVec[k].density, Geom.line, adjust_default_theme_color(defaultThemeOverride, colourVec[k])) for k = 1:4 ] #Construct plot layers using kernel densities
    println("Plotting kernel densities")
    kernelPlot1 = plot(layerVec..., Guide.xlabel("Estimator value"), Guide.ylabel("Density"), Guide.manual_color_key(default_legend(["mean", string(tMeanProp1)*" trimmed mean", string(tMeanProp2)*" trimmed mean", "median"])...), defaultThemeOverride)
    draw_local(kernelPlot1, "t_Dist_Estimator_Densities", dirPath=outputDir, fileType=:svg)
    println("Routine complete")
end

#--------------------------------------------
# MEASURING TAIL FATNESS
#--------------------------------------------
#Discuss fat-tails of t-distribution with DoF = 2. Explain why kurtosis, although the common characterisation, is not an ideal measure of how fat the tails are. Possibly plot sample kurtosis as a function of sample size.
#Introduce alternative robust measure of fat-tails and show it is stable relative to sample kurtosis
#Specifically, show that sample kurtosis diverges as sample size increases if population kurtosis is undefined
function tail_fatness_and_kurtosis( ; numIter::Int=100, numObsVec::Vector{Int}=collect(20:20:200))
    #Simulate the kurtosis and robust kurtosis as a function of sample size for t-distribution with 2 degrees of freedom
	tDist = TDist(2) #Initiate t-distribution with 2 degrees of freedom
    simKurtosisMat = Array(Float64, numIter, length(numObsVec))
	simRobustKurtosisMat = Array(Float64, numIter, length(numObsVec))
    for k = 1:length(numObsVec)
		for m = 1:numIter
			tData = rand(tDist, numObsVec[k])
        	simKurtosisMat[m, k] = kurtosis(tData)
			simRobustKurtosisMat[m, k] = hogg_robust_kurt!(tData, sorted=false, numerTail=0.05, denomTail=0.5)
		end
    end
	simKurtosis = vec(mean(simKurtosisMat, 1))
	simRobustKurtosis = vec(mean(simRobustKurtosisMat, 1))
    estVec = Vector{Float64}[simKurtosis, simRobustKurtosis]
    layerVec = Vector{Gadfly.Layer}[ layer(x=numObsVec, y=estVec[k], Geom.line, adjust_default_theme_color(defaultThemeOverride, colourVec[k])) for k = 1:2 ] #Construct plot layers for the two estimators
    estPlot1 = plot(layerVec..., Guide.xlabel("Number of observations"), Guide.ylabel("Simulated average estimator value"), Guide.manual_color_key(default_legend(["Sample kurtosis", "Robust measure"])...), defaultThemeOverride)
    println("Plotting estimators...")
    draw_local(estPlot1, "t_Dist_Kurtosis_Versus_Robust_Kurtosis", dirPath=outputDir, fileType=:svg)
	println("Routine complete")
end

#--------------------------------------------
# TAIL FATNESS OF FINANCIAL DATA VERSUS NORMAL
#--------------------------------------------
#Compare robust measure of fat-tails for lots of different stocks to value under Normal distribution and value under t-distribution with 2 DoF
#Discuss how we can generate unconditional fat-tails using a conditional Normal model with time-varying variance. Plot robust measure of fat-tails for returns standardised by realised variance type estimator. Show how it is close to Normal.
#Conclude by comparing mean of financial returns to trimmed mean and median.
function tail_fatness_financial_data( ; scaleMethod::Symbol=:historicvariance, blockLength::Float64=4.0)
	#Get robust measure of return data and compare it to Normal and t-Dist with 2 DoF
	secRet = read_local(secList, :return)
    hoggEst = Float64[ hogg_robust_kurt(secRet[j]) for j = 1:length(secList) ]
    tDist = TDist(2)
    tDistHoggEst = mean(Float64[ hogg_robust_kurt(rand(tDist, 100)) for k = 1:1000 ])
    normalHoggEst = mean(Float64[ hogg_robust_kurt(randn(100)) for k = 1:1000 ])
	println("Drawing plot 1")
	estPlot1 = plot(x=secList, y=hoggEst, yintercept=[normalHoggEst, tDistHoggEst], Geom.point, Geom.hline, Guide.xlabel("Ticker code"), Guide.ylabel("Robust kurtosis"), defaultThemeOverride)
    draw_local(estPlot1, "Robust_Kurtosis_of_Daily_Financial_Returns", dirPath=outputDir, fileType=:svg)
	#Standardise returns by a variance estimate and then repeat above exercise
	if scaleMethod == :historicvariance
		secVar = Vector{Float64}[ historical_variance(secRet[k]) for k = 1:length(secRet) ]
		secRetStd = Vector{Float64}[ secRet[j][end-length(secVar[j])+1:end] ./ sqrt(secVar[j]) for j = 1:length(secList) ]
	else
		error("Invalid scaleMethod")
	end
	hoggEstStd = Float64[ hogg_robust_kurt(secRetStd[j]) for j = 1:length(secList) ]
	println("Drawing plot 2")
	estPlot2 = plot(x=secList, y=hoggEstStd, yintercept=[normalHoggEst, tDistHoggEst], Geom.point, Geom.hline, Guide.xlabel("Ticker code"), Guide.ylabel("Robust kurtosis"), defaultThemeOverride)
    draw_local(estPlot2, "Robust_Kurtosis_of_Standardised_Daily_Financial_Returns", dirPath=outputDir, fileType=:svg)
	#Compare mean and trimmed mean on resampled financial returns (use NAB (fattest tails) and CCL (thinnest tails))
	println("Drawing mean comparison plots (full period)")
	pNABFull = kernel_plot_robust_mean_versus_trimmed_mean(secRet, "NAB", blockLength=blockLength)
	draw_local(pNABFull, "NAB_Bootstrapped_Location_Estimator_Density_(full_period)", dirPath=outputDir, fileType=:svg)
	pCCLFull = kernel_plot_robust_mean_versus_trimmed_mean(secRet, "CCL", blockLength=blockLength)
	draw_local(pCCLFull, "CCL_Bootstrapped_Location_Estimator_Density_(full_period)", dirPath=outputDir, fileType=:svg)
	#Compare mean and trimmed mean on short horizon financial returns for large robust kurtosis versus small robust kurtosis
	println("Estimating rolling robust kurtosis")
	rNAB = secRet[find(secList .== "NAB")[1]]
	rollingHoggNAB = Float64[ hogg_robust_kurt(rNAB[j:j+99]) for j = 1:length(rNAB)-99 ]
	iSmlHogg = indmin(rollingHoggNAB)
	iLrgHogg = indmax(rollingHoggNAB)
	rNABSml = rNAB[iSmlHogg:iSmlHogg+99]
	rNABLrg = rNAB[iLrgHogg:iLrgHogg+99]
	println("Drawing mean comparison plots (short horizon)")
	pNABSml = kernel_plot_robust_mean_versus_trimmed_mean(rNABSml, secStr="NAB Short Horizon (Robust kurt = " * string(rollingHoggNAB[iSmlHogg])[1:3] * ")", blockLength=blockLength)
	pNABLrg = kernel_plot_robust_mean_versus_trimmed_mean(rNABLrg, secStr="NAB Short Horizon (Robust kurt = " * string(rollingHoggNAB[iLrgHogg])[1:3] * ")", blockLength=blockLength)
	draw_local(pNABSml, "NAB_Bootstrapped_Location_Estimator_Density_(short_horizon_thin_tail)", dirPath=outputDir, fileType=:svg)
	draw_local(pNABLrg, "NAB_Bootstrapped_Location_Estimator_Density_(short_horizon_fat_tail)", dirPath=outputDir, fileType=:svg)
	println("Routine complete")
end
function kernel_plot_robust_mean_versus_trimmed_mean(secRet::Vector{Vector{Float64}}, secStr::String ; numResample::Int=1000, blockLength::Float64=4.0)
	iSec = find(secList .== secStr)
	length(iSec) != 1 && error("Unable to find " * secStr * " data")
	r = secRet[iSec[1]]
	return(kernel_plot_robust_mean_versus_trimmed_mean(r, secStr=secStr, numResample=numResample, blockLength=blockLength))
end
function kernel_plot_robust_mean_versus_trimmed_mean(r::Vector{Float64} ; numResample::Int=1000, blockLength::Float64=4.0, secStr::String="", bpMult::Int=10000)
	rCent = r - mean(r) #Centred so re-sampled data has true mean of zero
	rBoot = dbootstrapdata(rCent, blockLength=blockLength, numResample=numResample)
	bootEst = Vector{Float64}[Float64[ bpMult*mean(rBoot[:, m]) for m = 1:numResample ], Float64[ bpMult*tmean(rBoot[:, m], 0.4) for m = 1:numResample ]]
	kDVec = KernelDensity.UnivariateKDE{FloatRange{Float64}}[ kde(bootEst[k]) for k = 1:2 ]
	layerVec = Vector{Gadfly.Layer}[ layer(x=collect(kDVec[k].x), y=kDVec[k].density, Geom.line, adjust_default_theme_color(defaultThemeOverride, colourVec[k])) for k = 1:2 ]
	secStr == "" ? (localTitle = "Location estimator densities") : (localTitle = "Location estimator densities for " * secStr * " return data")
	kernelPlot = plot(layerVec..., Guide.xlabel("Estimator value (basis points)"), Guide.ylabel("Density"), Guide.title(localTitle), Guide.manual_color_key(default_legend(["Mean", "Trimmed mean (0.4)"])...), defaultThemeOverride)
end

#-------------------------------------------
# ROBUST PREDICTION OF FINANCIAL RETURNS
#-------------------------------------------
function return_prediction_financial_data( ; blockLength::Float64=4.0, numResample::Int=1000, varProxyMethod::Symbol=:historicvariance, numObs::Int=2000, rSquared::Float64=0.05, a::Float64=0.0, lag::Int=17, tCost::Float64=0.0, trimSimTail::Float64=0.0, numAsset::Int=20, tradingSimRSquared::Float64=0.01, includeConstantInSim::Bool=true)
	#Get variance proxy
	println("Reading data")
	if varProxyMethod == :realisedvariance
		v = read_local(["NAB"], :realisedvariance)[1]
	elseif varProxyMethod == :historicvariance
		rNAB = read_local(["NAB"], :return)[1]
		v = historical_variance(rNAB, lag)
	else
		error("Invalid varProxyMethod")
	end
	#Simuate returns, signal, error term, constant and coefficient
	println("Simulating and bootstrapping")
	(r, s, e, a, b) = simulate_signal_and_returns(v, rSquared=rSquared, a=a)
	#Resample r, x, and e
	inds = dbootstrapindex(length(r), blockLength, numResample=numResample)
	rBoot = r[inds]
	sBoot = s[inds]
	eBoot = e[inds]
	hoggVec = Float64[ hogg_robust_kurt(rBoot[:, m]) for m = 1:numResample ]
	qProb = Float64[0.05, 0.25, 0.5, 0.75, 0.95]
	hoggVecQ = quantile(hoggVec, qProb)
	#Run regressions on the resampled data using multiple methods and get plot of estimated coefficients
	println("Estimating coefficients on bootstrapped data")
	coefLS = Array(Vector{Float64}, numResample)
	coefLAD = Array(Vector{Float64}, numResample)
	rSqLS = Array(Float64, numResample)
	rSqLAD = Array(Float64, numResample)
	rHatLS = Array(Float64, length(r), numResample)
	rHatLAD = Array(Float64, length(r), numResample)
	for m = 1:numResample
		y = rBoot[:, m]
		x = [ones(Float64, length(y)) sBoot[:, m]]
		coefLS[m] = x \ y
		(coefLAD[m], _, _) = least_absolute_deviation(y, x)
		rHatLS[:, m] = x * coefLS[m]
		rHatLAD[:, m] = x * coefLAD[m]
		eLS = y - rHatLS[:, m]
		eLAD = y - rHatLAD[:, m]
		tSS = sumabs2(y)
		rSqLS[m] = 1 - sumabs2(eLS) / tSS
		rSqLAD[m] = 1 - sumabs2(eLAD) / tSS
	end
	#Get kernel density plot of estimated coefficients (and include true value)
	println("Building coefficient kernel densities")
	aArr = Vector{Float64}[Float64[ coefLS[m][1] for m = 1:numResample ], Float64[ coefLAD[m][1] for m = 1:numResample ]]
	bArr = Vector{Float64}[Float64[ coefLS[m][2] for m = 1:numResample ], Float64[ coefLAD[m][2] for m = 1:numResample ]]
	aKDVec = KernelDensity.UnivariateKDE{FloatRange{Float64}}[ kde(aArr[k]) for k = 1:2 ]
	bKDVec = KernelDensity.UnivariateKDE{FloatRange{Float64}}[ kde(bArr[k]) for k = 1:2 ]
	aLayerVec = Vector{Gadfly.Layer}[ layer(x=collect(aKDVec[k].x), y=aKDVec[k].density, Geom.line, adjust_default_theme_color(defaultThemeOverride, colourVec[k])) for k = 1:2 ]
	bLayerVec = Vector{Gadfly.Layer}[ layer(x=collect(bKDVec[k].x), y=bKDVec[k].density, Geom.line, adjust_default_theme_color(defaultThemeOverride, colourVec[k])) for k = 1:2 ]
	aKernelPlot = plot(aLayerVec..., Guide.xlabel("Regression constant"), Guide.ylabel("Density"), Guide.title("Density of regression constant (true value = " * string(a) * ")"), Guide.manual_color_key(default_legend(["Least squares", "Least absolute deviations"])...), defaultThemeOverride)
	bKernelPlot = plot(bLayerVec..., Guide.xlabel("Regression coefficient"), Guide.ylabel("Density"), Guide.title("Density of regression coefficient (true value = " * string(b) * ")"), Guide.manual_color_key(default_legend(["Least squares", "Least absolute deviations"])...), defaultThemeOverride)
	println("Plotting coefficient kernel densities")
	draw_local(aKernelPlot, "Regression_constant_density", dirPath=outputDir, fileType=:svg)
	draw_local(bKernelPlot, "Regression_coefficient_density", dirPath=outputDir, fileType=:svg)
	#Get kernel density plot of r-squares
	println("Building rSquared kernel density")
	rSqArr = Vector{Float64}[rSqLS, rSqLAD]
	rSqKDVec = KernelDensity.UnivariateKDE{FloatRange{Float64}}[ kde(rSqArr[k]) for k = 1:2 ]
	rSqLayerVec = Vector{Gadfly.Layer}[ layer(x=collect(rSqKDVec[k].x), y=rSqKDVec[k].density, Geom.line, adjust_default_theme_color(defaultThemeOverride, colourVec[k])) for k = 1:2 ]
	rSqKernelPlot = plot(rSqLayerVec..., Guide.xlabel("R-squared"), Guide.ylabel("Density"), Guide.title("Density of regression R-squared (true value = " * string(rSquared) * ")"), Guide.manual_color_key(default_legend(["Least squares", "Least absolute deviations"])...), defaultThemeOverride)
	println("Plotting rSquared kernel density")
	draw_local(rSqKernelPlot, "Regression_rSquared_density", dirPath=outputDir, fileType=:svg)
	println("Hogg robust estimator quantiles (" * string(qProb) * ") = " * string(hoggVecQ))
	#Perform simulations using the estimated models and plot density of terminal values
	println("Performing simple trading simulations")
	println("Total number of observations = " * string(length(v)))
	iEstEnd = Int(round(length(v) / 2))
	terminalValLS = Array(Float64, numResample)
	terminalValLAD = Array(Float64, numResample)
	avgAbsErrorLS = Array(Float64, numResample)
	avgAbsErrorLAD = Array(Float64, numResample)
	for m = 1:numResample
		rMat = Array(Float64, length(r), numAsset)
		sMat = Array(Float64, length(r), numAsset)
		eMat = Array(Float64, length(r), numAsset)
		aVec = Array(Float64, numAsset)
		bVec = Array(Float64, numAsset)
		for j = 1:numAsset
			(rMat[:, j], sMat[:, j], eMat[:, j], aVec[j], bVec[j]) = simulate_signal_and_returns(v, rSquared=tradingSimRSquared, a=0.0)
		end
		coefLSVec = Array(Vector{Float64}, numAsset)
		coefLADVec = Array(Vector{Float64}, numAsset)
		rHatLSMat = Array(Float64, length(r), numAsset)
		rHatLADMat = Array(Float64, length(r), numAsset)
		for j = 1:numAsset
			y = rMat[:, j]
			includeConstantInSim ? (x = [ones(Float64, length(y)) sMat[:, j]]) : (x = sMat[:, j]'')
			yEst = y[1:iEstEnd]
			xEst = x[1:iEstEnd, :]
			ySim = y[iEstEnd+1:end]
			xSim = x[iEstEnd+1:end, :]
			coefLSVec[j] = xEst \ yEst
			(coefLADVec[j], _, _) = least_absolute_deviation(yEst, xEst)
			any(isnan(coefLADVec[j])) && (coefLADVec[j] = coefLSVec[j])
			rHatLSMat[:, j] = x * coefLSVec[j]
			rHatLADMat[:, j] = x * coefLADVec[j]
		end
		avgAbsErrorLS[m] = mean(abs(rHatLSMat[iEstEnd+1:end, :] - rMat[iEstEnd+1:end, :]))
		avgAbsErrorLAD[m] = mean(abs(rHatLADMat[iEstEnd+1:end, :] - rMat[iEstEnd+1:end, :]))
		terminalValLS[m] = very_simple_trading_sim(rMat[iEstEnd+1:end, :], rHatLSMat[iEstEnd+1:end, :])[end]
		terminalValLAD[m] = very_simple_trading_sim(rMat[iEstEnd+1:end, :], rHatLADMat[iEstEnd+1:end, :])[end]
	end
	println("Mean absolute forecast error (LS) = " * string(mean(avgAbsErrorLS)))
	println("Mean absolute forecast error (LAD) = " * string(mean(avgAbsErrorLAD)))
	println("Proportion of samples when LAD has smaller absolute forecast error = " * string(sum(avgAbsErrorLAD .< avgAbsErrorLS) / numResample))
	println("Mean terminal value (LS) = " * string(mean(terminalValLS)))
	println("Mean terminal value (LAD) = " * string(mean(terminalValLAD)))
	println("Median terminal value (LS) = " * string(median(terminalValLS)))
	println("Median terminal value (LAD) = " * string(median(terminalValLAD)))
	sort!(terminalValLS)
	sort!(terminalValLAD)
	iLower = Int(floor(trimSimTail * length(terminalValLS)))
	iLower == 0 && (iLower = 1)
	iUpper = length(terminalValLS) - iLower + 1
	terminalValLS = terminalValLS[iLower:iUpper]
	terminalValLAD = terminalValLAD[iLower:iUpper]
	println("Plotting terminal value kernel densities")
	tV = Vector{Float64}[terminalValLS, terminalValLAD]
	tVKDVec = KernelDensity.UnivariateKDE{FloatRange{Float64}}[ kde(tV[k]) for k = 1:2 ]
	tVLayerVec = Vector{Gadfly.Layer}[ layer(x=collect(tVKDVec[k].x), y=tVKDVec[k].density, Geom.line, adjust_default_theme_color(defaultThemeOverride, colourVec[k])) for k = 1:2 ]
	tVKernelPlot = plot(tVLayerVec..., Guide.xlabel("Terminal value"), Guide.ylabel("Density"), Guide.title("Density of terminal values from simple simulations"), Guide.manual_color_key(default_legend(["Least squares", "Least absolute deviations"])...), defaultThemeOverride)
	draw_local(tVKernelPlot, "Simulated_portfolio_terminal_values", dirPath=outputDir, fileType=:svg)
	println("Routine complete")
end
function simulate_signal_and_returns(v::Vector{Float64} ; rSquared::Float64=0.05, a::Float64=0.0)
	!(0.0 < rSquared < 1.0) && error("Invalid rSquared")
	e = sqrt(v) .* randn(length(v))
	x = randn(length(v))
	residSumSq = sumabs2(e)
	totalSumSq = residSumSq / (1 - rSquared)
	explainSumSq = totalSumSq - residSumSq
	b = sqrt(explainSumSq / sumabs2(x)) #This ensures sample rSquared will equal input rSquared (Note, sample rSquared will become population rSquared for re-sampled data)
	#b = sqrt(explainSumSq / length(v)) #This alternative would ensure population rSquared will equal input rSquared
	r = a + b*x + e
	return(r, x, e, a, b)
end
function least_absolute_deviation(y::Vector{Float64}, x::Matrix{Float64})
	#Build Opt object accepted by NLOpt
	fObj = ((param, grad) -> sumabs(y - x*param)) #Get local anonymous objective function
	initVal = zeros(Float64, size(x, 2))
	opt = Opt(:LN_COBYLA, length(initVal)) #Derivative-free convergence method (yes I'm being lazy here)
	xtol_rel!(opt, 1e-6)
	min_objective!(opt, fObj)
	#Perform optimisation
	(objFuncOpt, paramOpt, flag) = optimize(opt, initVal)
	checkNLoptFlag(flag)
	return(paramOpt, objFuncOpt, y - x*paramOpt) #Return optimal parameter, objective function at optimum, and estimated errors
end
function checkNLoptFlag(flag::Symbol)
	flag == :SUCCESS && return(true)
	flag == :XTOL_REACHED && return(true)
	if flag == :ROUNDOFF_LIMITED
		println("WARNING: Optimisation terminated due to floating point limit. Final iteration parameters used.")
		return(true)
	end
	flag == :MAXEVAL_REACHED && error("Maximum number of evaluations reached in NLopt")
	flag == :MAXTIME_REACHED && error("Maximum optimisation time reached in NLopt")
	flag == :FAILURE && error("Generic failure in NLopt")
	flag == :INVALID_ARGS && error("Invalid arguments to NLopt")
	flag == :OUT_OF_MEMORY && error("NLopt ran out of memory")
	flag == :FORCED_STOP && error("NLopt halted due to forced termination")
	error("Unexpected flag from NLopt. Flag = " * string(flag))
end
#Function for performing a very simple trading simulation. Either hold cash, or trade an asset if forecast return > 0. tCost is in basis points
function very_simple_trading_sim(r::Vector{Float64}, f::Vector{Float64} ; tCost::Float64=50.0)
	length(f) != length(r) && error("Length mismatch")
	N = length(f)
	portValue = Array(Float64, N)
	portValue[1] = 1000000.0
	for n = 2:N
		if f[n] > 0.0
			portValue[n] = portValue[n-1] * (r[n] + 1) - (1/10000) * tCost * portValue[n-1] #notional transaction cost
		else
			portValue[n] = portValue[n-1]
		end
	end
	return(portValue)
end
function very_simple_trading_sim(r::Matrix{Float64}, f::Matrix{Float64})
	size(r) != size(f) && error("Size mismatch")
	portValue = Array(Float64, size(f, 1))
	portValue[1] = 1000000.0
	for n = 2:size(r, 1)
		inds = (vec(f[n, :]) .> 0.0)
		if !any(inds)
			portValue[n] = portValue[n-1]
		else
			portValue[n] = port_value_update(portValue[n-1], vec(r[n, :])[inds])
		end
	end
	return(portValue)
end
port_value_update(p::Float64, r::Float64) = p * (r + 1)
port_value_update(p::Vector{Float64}, r::Vector{Float64}) = (length(p) == length(r)) ? sum(Float64[ port_value_update(p[j], r[j]) for j = 1:length(p) ]) : error("Length mismatch")
port_value_update(p::Float64, r::Vector{Float64}) = port_value_update((p/length(r))*ones(Float64, length(r)), r)

#-------------------------------------------
# ASYMMETRIC DISTRIBUTIONS IN FINANCIAL RETURNS
#-------------------------------------------
function asymmetric_distribution_effect( ; blockLength::Float64=5.0, numResample::Int=1000, varProxyMethod::Symbol=:historicvariance, numObs::Int=2000, rSquared::Float64=0.05, a::Float64=0.0, lag::Int=17, numAsset::Int=20, tradingSimRSquared::Float64=0.01, includeConstantInSim::Bool=true)
	#Get robust measure of return data and compare it to Normal and t-Dist with 2 DoF
	secRet = read_local(secList, :return)
    robustSkew = Float64[ robust_skew(secRet[j]) for j = 1:length(secList) ]
	logNormalRobustSkew = mean(Float64[ robust_skew(rand(LogNormal(0, 1), 100)) for k = 1:1000 ])
	println("Drawing plot 1")
	estPlot1 = plot(x=secList, y=robustSkew, yintercept=[0.0, logNormalRobustSkew], Geom.point, Geom.hline, Guide.xlabel("Ticker code"), Guide.ylabel("Robust skewness"), defaultThemeOverride)
    draw_local(estPlot1, "Robust_Skewness_of_Daily_Financial_Returns", dirPath=outputDir, fileType=:svg)
	#Compare mean and trimmed mean on resampled financial returns (use WOW (Largest right skew) and ANZ (Largest left skew))
	println("Drawing mean comparison plots (full period)")
	pWOWFull = kernel_plot_robust_mean_versus_trimmed_mean(secRet, "WOW", blockLength=blockLength)
	draw_local(pWOWFull, "WOW_Bootstrapped_Location_Estimator_Density_(full_period)", dirPath=outputDir, fileType=:svg)
	pANZFull = kernel_plot_robust_mean_versus_trimmed_mean(secRet, "ANZ", blockLength=blockLength)
	draw_local(pANZFull, "ANZ_Bootstrapped_Location_Estimator_Density_(full_period)", dirPath=outputDir, fileType=:svg)
	#Compare mean and trimmed mean on log-normal data
	kPlotLN = kernel_plot_robust_mean_versus_trimmed_mean(rand(LogNormal(0, 1), 100), bpMult=1, blockLength=1.0)
	draw_local(kPlotLN, "LogNormal_Bootstrapped_Location_Estimator_Density", dirPath=outputDir, fileType=:svg)
	println("Routine complete")
end

#--------------------------------------------
# ROBUST FUNCTIONS USED THROUGHOUT
#--------------------------------------------
#Number of observations to cut when trimming
tmean_num_cut{T}(x::AbstractVector{T}, p::Float64) = Int(floor(p*length(x)))
tmean_num_cut{T}(x::AbstractVector{T}, lp::Float64, up::Float64) = (tmean_num_cut(x, lp), tmean_num_cut(x, up))
#Trimmed mean
function tmean!{T}(x::AbstractVector{T}, lp::Float64=0.1, up::Float64=0.1 ; sorted::Bool=false)
    length(x) < 2 && error("Input must contain at least 2 observations")
    !(0.0 <= lp <= up) && error("Invalid lower portion")
	!(lp <= up <= 1.0) && error("Invalid upper portion")
    (numLower, numUpper) = tmean_num_cut(x, lp, up)
	!sorted && sort!(x)
	return(mean(sub(x, numLower+1:length(x)-numUpper)))
end
tmean!{T}(x::AbstractVector{T}, p::Float64=0.2 ; sorted::Bool=false) = tmean!(x, p/2, p/2, sorted=sorted)
tmean{T}(x::AbstractVector{T}, lp::Float64=0.1, up::Float64=0.1 ; sorted::Bool=false) = sorted ? tmean!(x, lp, up, sorted=true) : tmean!(deepcopy(x), lp, up, sorted=false)
tmean{T}(x::AbstractVector{T}, p::Float64=0.2 ; sorted::Bool=false) = tmean(x, p/2, p/2, sorted=sorted)
#Median when input is already sorted
function median_sorted{T}(x::AbstractVector{T})
    length(x) < 2 && error("Input must contain at least 2 observations")
    if iseven(length(x))
        i = Int(length(x)/2)
        return(mean(x[i:i+1]))
    else
        return(x[Int((length(x)+1)/2)])
    end
end
#Hogg's robust measure of tail-fatness.
function hogg_robust_kurt!{T}(x::Vector{T} ; sorted::Bool=false, numerTail::Float64=0.05, denomTail::Float64=0.5)
    length(x) < 10 && error("Input must contain at least 10 observations")
    !(0.0 < numerTail <= 0.2) && error("Non-sensible value for numerTail")
    !(0.3 < denomTail <= 0.5) && error("Non-sensible value for denomTail")
    numObsNumer = Int(ceil(numerTail * length(x)))
    numObsDenom = Int(ceil(denomTail * length(x)))
    !sorted && sort!(x)
    numerLeft = mean(sub(x, 1:numObsNumer))
    numerRight = mean(sub(x, length(x)-numObsNumer+1:length(x)))
    denomLeft = mean(sub(x, 1:numObsDenom))
    denomRight = mean(sub(x, length(x)-numObsDenom+1:length(x)))
    return((numerRight - numerLeft) / (denomRight - denomLeft))
end
hogg_robust_kurt{T}(x::Vector{T} ; sorted::Bool=false, numerTail::Float64=0.05, denomTail::Float64=0.5) = sorted ? hogg_robust_kurt!(x, sorted=true, numerTail=numerTail, denomTail=denomTail) : hogg_robust_kurt!(deepcopy(x), sorted=false, numerTail=numerTail, denomTail=denomTail)
#Robust skewness
function robust_skew{T<:Number}(x::Vector{T}; qProb::Float64=0.75)
	length(x) < 3 && error("Input must contain at least 10 observations")
	!(0.5 < qProb < 1.0) && error("Invalid quantile for robust skew procedure")
	qEst = quantile(x, Float64[1 - qProb, 0.5, qProb])
	return((qEst[3] + qEst[1] - 2*qEst[2]) / (qEst[3] - qEst[1]))
end

#--------------------------------------------
# ROLLING WINDOW HISTORICAL VARIANCE
#--------------------------------------------
historical_variance{T<:Number}(r::AbstractVector{T}, lagWindow::Int=100) = (0 < lagWindow < length(r)) ? Float64[ var(sub(r, n-lagWindow+1:n)) for n = lagWindow:length(r) ] : error("Invalid lagWindow")

#--------------------------------------------
# DATA READING FUNCTIONS USED THROUGHOUT
#--------------------------------------------
function read_local(secList::Vector{ASCIIString}, dataType::Symbol ; checkLength::Int=0)
    if dataType == :return
        x = Vector{Float64}[ vec(readcsv(dataDir*"ASX_"*secList[j]*"_Return-Trade_uMarketCloseAuction_Bow.csv", Float64)) for j = 1:length(secList) ]
    elseif dataType == :realisedvariance
        x = Vector{Float64}[ vec(readcsv(dataDir*"ASX_"*secList[j]*"_RealisedVariance-5Minute-BidAskMidpoint_uFirstToLast_Bow.csv", Float64)) for j = 1:length(secList) ]
    else
        error("Invalid data type")
    end
    if checkLength != 0
        any(Int[ length(x[j]) for j = 1:length(x) ] .!= checkLength) && error("Invalid data length")
    end
    return(x)
end
function read_local(dataType::Symbol)
    if dataType == :calendar
        x = vec(readcsv(dataDir*"Calendar.csv", DateTime))
    else
        error("Invalid data type")
    end
    return(x)
end

#--------------------------------------------
# PLOTTING FUNCTIONS USED THROUGHOUT
#--------------------------------------------
#Function for drawing plot to saved image file
function draw_local(p::Plot, fileName::ASCIIString ; dirPath::ASCIIString="", fileType::Symbol=:png, width::Measure=40cm, height::Measure=20cm)
	dirPath[end] != '/' && (dirPath = dirPath * "/")
    !isdir(dirPath) && error("Input directory does not exist")
    filePath = dirPath * fileName * "." * string(fileType)
    if fileType == :svg     ; draw(SVG(filePath, width, height), p)
	elseif fileType == :png ; draw(PNG(filePath, width, height), p)
	elseif fileType == :pdf ; draw(PDF(filePath, width, height), p)
	else                    ; error("Invalid fileType symbol")
	end
	return(filePath)
end
function adjust_default_theme_color(x::Theme, colourString::ASCIIString)
    xC = deepcopy(x)
    xC.default_color = parse(Compose.Colorant, colourString)
    return(xC)
end
function default_legend(legendLabel::Vector{ASCIIString})
	legendTitle = "Legend:"
    length(legendLabel) > length(colourVec) && error("Default legend function cannot handle more than " * string(length(colourVec)) * " series")
    return(legendTitle, legendLabel, deepcopy(colourVec[1:length(legendLabel)]))
end

#--------------------------------------------
# OTHER COMMON FUNCTIONS
#--------------------------------------------
arr_to_mat{T}(x::Vector{Vector{T}}) = T[ x[j][k] for j = 1:length(x), k = 1:length(x[1])]
mat_to_arr{T}(x::Matrix{T}) = Vector{T}[ x[:, j] for j = 1:size(x, 2) ]
julia_version_dir() = "v"*string(VERSION.major)*"."*string(VERSION.minor)

end # module