module MathToolsMod
using LeafPhotosynthesisMod:fluxes
using Leaf:leaf_params
#-----------------------------------------------------------------------
# !DESCRIPTION:
# Math tools
#
# !USES:
#use shr_kind_mod, only : r8 => shr_kind_r8
#use clm_varctl, only : iulog
#use abortutils, only : endrun
#use CanopyFluxesMultilayerType, only : mlcanopy_type
#!
#! !PUBLIC TYPES:
#implicit none
#!
#! !PUBLIC MEMBER FUNCTIONS:
export quadratic,beta_function,log_gamma_function, hybrid, zbrent
# hybrid             ! Solve for the root of a function using secant and Brent's methods
# zbrent             ! Use Brent's method to find the root of a function
# quadratic          ! Solve a quadratic equation for its two roots
# tridiag            ! Solve a tridiagonal system of equations
# beta_function      ! Evaluate the beta function at p and q: B(p,q)
# log_gamma_function ! Evaluate the log natural of the gamma function at x: ln(G(x))

#-----------------------------------------------------------------------
function quadratic(a, b, c)
  discr = b^2 - 4*a*c
  discr >= 0 ?   ( (-b + sqrt(discr))/(2a), (-b - sqrt(discr))/(2a) ) : 0.0 #error("Only complex roots")
end # function

#-----------------------------------------------------------------------
function beta_function(p, q)
#
# !DESCRIPTION:
# Return the value of the beta function evaluated at p and q: B(p,q)
#
# !USES:
#
exp(log_gamma_function(p) + log_gamma_function(q) - log_gamma_function(p+q))

end #function beta_function

#-----------------------------------------------------------------------
function log_gamma_function(x)
#
# !DESCRIPTION:
# Return the value of the log natural of the gamma function evaluated at x: ln(G(x))
#
  coef = [76.18009172947146, -86.50532032941677,24.01409824083091, -1.231739572450155, 0.1208650973866179e-02, -0.5395239384953e-05];
  stp = 2.5066282746310005;
  y = x
  tmp = x + 5.5
  tmp = (x + 0.5) * log(tmp) - tmp
  ser = 1.000000000190015;
  for co in coef
    y = y + 1.0
    ser = ser + co / y
  end
  return tmp + log(stp*ser/x)
end # function log_gamma_function

function hybrid(flux::fluxes,leaf::leaf_params, func::Function, xa::Number, xb::Number, tol::Number)
    #
    # !DESCRIPTION:
    # Solve for the root of a function, given initial estimates xa and xb.
    # The root is updated until its accuracy is tol.
    #
    # !USES:
    #
    # !ARGUMENTS:
    #procedure (xfunc) :: func         ! Function to solve
    #real(r8), intent(in) :: xa, xb    ! Initial estimates of root
    #real(r8), intent(in) :: tol       ! Error tolerance
    #type(mlcanopy_type), intent(inout) :: mlcanopy_inst
    itmax = 40
    x0 = x1 = f0 = f1 = 0.0
    x0 = xa
    f0 = func(x0,flux, leaf)
    if (f0 == 0.0) return  x0 end

    x1 = xb
    f1 = func(x1,flux, leaf)
    if (f1 == 0.0) return x1 end

    if (f1 < f0)
       minx = x1
       minf = f1
    else
       minx = x0
       minf = f0
    end

    # First use the secant method, and then use the brent method as a backup
    for iter = 0:itmax
       iter = iter + 1
       dx = -f1 * (x1 - x0) / (f1 - f0)
       x = x1 + dx
       if (abs(dx) < tol)
          x0 = x
          break
       end
       x0 = x1
       f0 = f1
       x1 = x
       f1 = func(x1,flux, leaf)
       if (f1 < minf)
          minx = x1
          minf = f1
       end

       # If a root zone is found, use the brent method for a robust backup strategy
       if (f1 * f0 < 0.0)
          x = zbrent(flux,  leaf,func,x0, x1, xtol=tol)
          x0 = x
          break
       end

       # In case of failing to converge within itmax iterations stop at the minimum function
       if (iter > itmax)
          f1 = func(minx,flux, leaf)
          x0 = minx
          break
       end

    end

    return x0
end #function hybrid
function zbrent(flux::fluxes,leaf::leaf_params,f::Function, x0::Number, x1::Number, args::Tuple=();
               xtol::AbstractFloat=1e-7, ytol=2eps(Float64),
               maxiter::Integer=50)
    EPS = eps(Float64)
    y0 = f(x0,flux,leaf)
    y1 = f(x1,flux,leaf)
    if abs(y0) < abs(y1)
        # Swap lower and upper bounds.
        x0, x1 = x1, x0
        y0, y1 = y1, y0
    end
    x2 = x0
    y2 = y0
    x3 = x2
    bisection = true
    for _ in 1:maxiter
        # x-tolerance.
        if abs(x1-x0) < xtol
            return x1
        end

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

        # Use bisection method if satisfies the conditions.
        delta = abs(2EPS*abs(x1))
        min1 = abs(x-x1)
        min2 = abs(x1-x2)
        min3 = abs(x2-x3)
        if (x < (3x0+x1)/4 && x > x1) ||
           (bisection && min1 >= min2/2) ||
           (!bisection && min1 >= min3/2) ||
           (bisection && min2 < delta) ||
           (!bisection && min3 < delta)
            x = (x0+x1)/2
            bisection = true
        else
            bisection = false
        end

        y = f(x,flux,leaf)
        # y-tolerance.
        if abs(y) < ytol
            return x
        end
        x3 = x2
        x2 = x1
        if sign(y0) != sign(y)
            x1 = x
            y1 = y
        else
            x0 = x
            y0 = y
        end
        if abs(y0) < abs(y1)
            # Swap lower and upper bounds.
            x0, x1 = x1, x0
            y0, y1 = y1, y0
        end
    end
    error("Max iteration exceeded")
end
end # module MathToolsMod


function besselj_asymptotic_zero(nu::T, k::Integer) where {T <: AbstractFloat}
    T(π) * (k-1 + nu/2 + 3*one(T)/4)
end

function besselj_zero(nu, k)
    z = besselj_asymptotic_zero(nu, k)
    bf = (x) -> besselj(nu, x)
    return fzero(bf,z)
end

function besselroots(nu::AbstractFloat, n)
    [besselj_zero(nu, k) for k in 1:n]
end

asy_gaussjacobi(n::Integer, α::Number, β::Number) =
    asy_gaussjacobi(n, promote(float(α), float(β))...)

function asy_gaussjacobi(n::Integer, α::T, β::T) where {T <: AbstractFloat}
    x = zeros(T, n)
    w = zeros(T, n)
    asy_gaussjacobi!(x, w, α, β)
end

# The expansions are in terms of z = 1/(2n+α+β+1)
jac_z(n, α, β) = 1/(2n+α+β+1)

function asy_gaussjacobi!(x, w, α, β)
    @assert length(x) == length(w)

    n = length(x)
    k_left = ceil(Int, sqrt(n))
    k_right = n-k_left+1

    z = jac_z(n, α, β)
    jb = besselroots(β, k_left)
    δ = zero(eltype(x))
    for k in 1:k_left
        x_left, w_left, δ_left = asy_jacobi_leftendpoint(n, k, α, β, jb[k], z)
        x_bulk, w_bulk, δ_bulk = asy_jacobi_bulk(n, k, α, β, z)
        if δ_left < δ_bulk
            x[k], w[k] = x_left, w_left
            δ = max(δ, δ_left)
        else
            x[k], w[k] = x_bulk, w_bulk
            δ = max(δ, δ_bulk)
        end
    end
    ja = besselroots(α, k_left)
    for k in k_right:n
        x_right, w_right, δ_right = asy_jacobi_rightendpoint(n, k, α, β, ja[n-k+1], z)
        x_bulk, w_bulk, δ_bulk = asy_jacobi_bulk(n, k, α, β, z)
        if δ_right < δ_bulk
            x[k], w[k] = x_right, w_right
            δ = max(δ, δ_right)
        else
            x[k], w[k] = x_bulk, w_bulk
            δ = max(δ, δ_bulk)
        end
    end
    for k in k_left+1:k_right-1
        x[k], w[k], δ_bulk = asy_jacobi_bulk(n, k, α, β, z)
        δ = max(δ, δ_bulk)
    end
    x, w, δ
end

two(T) = 2*one(T)

function asy_jacobi_leftendpoint(n::Int, k::Int, α, β, jbk, z = jac_z(n, α, β))
    T = promote_type(typeof(α), typeof(β))
    x = zero(T)
    w = zero(T)

    # Add terms one by one and continue until the next term is larger than the previous one
    x3 = 2*jbk^2*z^2
    δ = abs(x3)
    if δ > eps(T) && δ < 1
        x += x3

        x5 = - 2/3*(jbk^4 - (3α^2 + β^2 - 1)*jbk^2)*z^4
        w5 = 1/3*(3α^2 + β^2 - 2*jbk^2 - 1)*z^2
        δ = abs(x5)
        if δ > eps(T) && δ < abs(x3)
            x += x5
            w += w5

            x7 = two(T)/45*(2*jbk^6 - 3*(5α^2 + 3β^2 - 2)*jbk^4 + (45α^4 + 7β^4 + 20*(3α^2 - 1)*β^2 - 60α^2 + 13)*jbk^2)*z^6
            w7 = one(T)/45*(45α^4 + 7β^4 + 6*jbk^4 + 20*(3α^2- 1)*β^2
                - 6*(5α^2 + 3β^2 - 2)*jbk^2 - 60α^2 + 13)*z^4
            δ = abs(x7)
            if δ > eps(T) && δ < abs(x5)
                x += x7
                w += w7

                x9 = - two(T)/2835*(9*jbk^8 - 18*(7α^2 + 5β^2 - 3)*jbk^6
                        + (328β^4 + (1512α^2 - 575)*β^2 + 567α^2 -113)*jbk^4
                        - (2835α^6 + 247β^6 + 1407*(3α^2 - 1)*β^4 - 8505α^4 + 21*(405α^4 - 600α^2 + 133)*β^2 +8379α^2 - 1633)*jbk^2)*z^8
                w9 = one(T)/2835*(2835α^6 + 247β^6 - 36*jbk^6 + 1407*(3α^2 - 1)*β^4
                        + 54*(7α^2 + 5β^2 - 3)*jbk^4 - 8505α^4
                        + 21*(405α^4 - 600α^2 + 133)*β^2 - 2*(328β^4 +
                        (1512α^2 - 575)*β^2 + 567α^2 - 113)*jbk^2 + 8379α^2 -1633)*z^6
                δ = abs(x9)
                if δ > eps(T) && δ < abs(x7)
                    x += x9
                    w += w9

                    x11 = two(T)/42525*(6*jbk^10 - 15*(9α^2 + 7β^2 - 4)*jbk^8 + (6615α^4
                            + 769β^4 + 2*(1620α^2 - 589)*β^2 - 12150α^2 + 2668)*jbk^6 +
                            3*(9450α^6 - 999β^6 - 23*(400α^2 - 147)*β^4 - 40635α^4 -
                            (2835α^4 + 1850α^2 - 294)*β^2 + 50650α^2 - 10236)*jbk^4 +
                            (42525α^8 + 2327β^8 + 22340*(3α^2 - 1)*β^6 - 226800α^6 + 168*(1530α^4 -
                            2415α^2 + 542)*β^4 + 517860α^4 + 20*(11340α^6 - 38745α^4 + 42399α^2
                             - 8488)*β^2 - 509280α^2 + 98717)*jbk^2)*z^10
                     if abs(x11) > eps(T) && abs(x11) < abs(x9)
                         x += x11
                     end
                end
            end
        end
    end

    x += -1
    jbm = besselj(β-1, jbk)
    w = 8*z^2/jbm^2 * (1-x)^α*(1+x)^β * (1+w)
    x, w, δ
end

function asy_jacobi_rightendpoint(n, k, α, β, jak, z = jac_z(n, α, β))
    x, w, δ = asy_jacobi_leftendpoint(n, n-k, β, α, jak, z)
    -x, w, δ
end

function asy_jacobi_bulk(n, k, α, β, z = jac_z(n, α, β))

    t = cos(pi * (4n-4k+2α+3)/(4n+2α+2β+2))

    T = eltype(α)
    x = zero(T)
    w = zero(T)

    x3 = one(T)/2*(2α^2 - 2β^2 + (2α^2 + 2β^2 - 1)*t)*z^2
    w3 = -(1 -2α^2 -2β^2)*z^2

    δ = abs(x3)
    if δ > eps(T) && δ < abs(t)
        x += x3
        w += w3

        x5 = - one(T)/24*(32α^4 - 32β^4 - (16α^4 + 16β^4 + 4*(12α^2 -5)*β^2 - 20α^2 + 5)*t^3
            - 24*(α^2 - β^2)*t^2 - 40α^2 + 40β^2+ 3*(16α^4 + 16β^4 + 4*(4α^2 - 7)*β^2
            - 28α^2 + 11)*t)*z^4/(t^2- 1)
        w5 = one(T)/12*((16α^4 + 16β^4 + 4*(12α^2 - 5)*β^2 - 20α^2 + 5)*t^4
            + 48α^4 + 48β^4 + 12*(4α^2 - 7)*β^2 - 6*(4*(4α^2 + 1)*β^2
            + 4α^2 - 3)*t^2 - 84α^2 + 64*(α^4 - β^4 - 2α^2 + 2β^2)*t +33)/(t^4 - 2*t^2 + 1)*z^4
        δ = abs(x5)
        if δ > eps(T) && δ < abs(x3)
            x += x5
            w += w5

            x7 = one(T)/240*(576α^6 - 576β^6 + (96α^6 + 96β^6 + 80*(8α^2 - 3)*β^4
                - 240α^4 + 2*(320α^4 - 440α^2 + 101)*β^2 + 202α^2 - 39)*t^5
                -320*(α^2 - 6)*β^4 + 240*(α^2 - β^2)*t^4 - 1920α^4
                -10*(32*(5α^2 + 3)*β^4 + 96α^4 + 2*(80α^4 - 152α^2 - 97)*β^2
                - 194α^2 + 99)*t^3 + 16*(20α^4 - 127)*β^2 + 160*(6α^6 - 6β^6 +
                2*(α^2 + 15)*β^4 - 30α^4 - (2α^4 + 41)*β^2 + 41α^2)*t^2 +
                2032α^2 + 15*(96α^6 + 96β^6 + 16*(4α^2 - 23)*β^4 - 368α^4
                + 2*(32α^4 - 72α^2 + 223)*β^2 + 446α^2 - 173)*t)*z^6/(t^4 - 2*t^2 +1)
            w7 = one(T)/120*((96α^6 + 96β^6 + 80*(8α^2 - 3)*β^4 - 240α^4 + 2*(320α^4 - 440α^2 +101)*β^2
                + 202α^2 - 39)*t^6 - 1440α^6 - 1440β^6 - 240*(4α^2 - 23)*β^4 - 5*(96α^6+ 96β^6
                + 16*(20α^2 - 27)*β^4 - 432α^4 + 2*(160α^4 - 136α^2 + 295)*β^2 + 590α^2 - 237)*t^4
                + 5520α^4 - 640*(3α^6 - 3β^6 + (α^2 + 15)*β^4 - 15α^4
                -(α^4 + 22)*β^2 + 22α^2)*t^3 - 30*(32α^4 - 72α^2 + 223)*β^2 - 15*(288α^6 +288β^6
                - 16*(8α^2 + 81)*β^4 - 1296α^4 - 2*(64α^4 - 88α^2 - 863)*β^2 +1726α^2 - 717)*t^2
                - 6690α^2 - 128*(33α^6 - 33β^6 - 5*(α^2 - 27)*β^4 - 135α^4 +(5α^4 - 166)*β^2 + 166α^2)*t
                + 2595)*z^6/(t^6 - 3*t^4 + 3*t^2 - 1)
            δ = abs(x7)
            if δ > eps(T) && δ < abs(x5)
                x += x7
                w += w7

                x9 = -one(T)/40320*(219648α^8 - 219648β^8 - (9728α^8 + 9728β^8 + 896*(138α^2 - 49)*β^6
                    - 43904α^6 + 224*(1160α^4 - 1720α^2 + 389)*β^4 + 87136α^4
                    + 8*(15456α^6 -48160α^4 + 49364α^2 - 9785)*β^2 - 78280α^2 + 14921)*t^7 - 10752*(14α^2 - 127)*β^6
                    -40320*(α^2 - β^2)*t^6 - 1365504α^6 + 21*(2048α^8 + 2048β^8 + 128*(146α^2 -123)*β^6
                    - 15744α^6 + 32*(1320α^4 - 1760α^2 + 2023)*β^4 + 64736α^4 + 8*(2336α^6- 7040α^4
                    + 3644α^2 - 11275)*β^2 - 90200α^2 + 37111)*t^5 + 75264*(5α^2 - 49)*β^4
                    + 4480*(44α^8 - 44β^8 + 8*(3α^2 + 55)*β^6 - 440α^6 - 24*(7α^2 + 72)*β^4
                    +1728α^4 - (24α^6 - 168α^4 - 2405)*β^2 - 2405α^2)*t^4 + 3687936α^4
                    + 105*(6656α^8 + 6656β^8 - 128*(50α^2 + 443)*β^6 - 56704α^6 - 32*(296α^4 -696α^2 - 6027)*β^4
                    + 192864α^4 - 8*(800α^6 - 2784α^4 + 3580α^2 + 30285)*β^2 -242280α^2 + 99933)*t^3
                    + 384*(392α^6 - 980α^4 + 10527)*β^2 + 2688*(424α^8 - 424β^8+ 4*(4α^2 + 783)*β^6 - 3132α^6
                    + 4*(35α^2 - 2407)*β^4 + 9628α^4 - (16α^6 +140α^4 - 11429)*β^2 - 11429α^2)*t^2
                    - 4042368α^2 + 35*(23552α^8 + 23552β^8 +128*(90α^2 - 1231)*β^6 - 157568α^6
                    + 32*(328α^4 - 1376α^2 + 14095)*β^4 +451040α^4 + 8*(1440α^6 - 5504α^4 + 9964α^2 - 65439)*β^2
                    - 523512α^2 +206379)*t)*z^8/(t^2 - 1)^3
                if abs(x9) > eps(T) && abs(x9) < abs(x7)
                    x += x9
                end
            end
        end
    end

    x += t
    w = pi * sqrt(1-t^2) * z * (1-x)^α*(1+x)^β * (2+w)
    x, w, δ
end


export getColoringFirst
export getColoringSecond
export getColoringSecondS

# Make a maximal independent set of coarse nodes from the grid.
#
# The slowest part of the naive algorithm is taking max(lambda) over and over again
# A simple solution is updating the maximum as we increase values in lambda
# When no index of lambda has increased over the maximum, we need to check if the maximum is now lower

function getColoringFirst(S::SparseMatrixCSC, n::Int64)

	# lambda - count of strongly connected nodes to current
	# coloring -  will hold the C/F value for each node. C == 1, F == 0
	lambda = [0 for i=1:n]
	coloring = fill(0, n)
	rows = rowvals(S)

	# for each neighbour count, holds the nodes that has that number of neighbours.
	indeces = [Set{Int}() for i=1:n+1]

	# store by neigbour count, store max
	nmax = 0
	for i = 1:n
		lambda[i] = S.colptr[i+1] - S.colptr[i]
	end
	for i=1:n
		# Has only diagonal, make it free
		if(lambda[i] == 1)
			lambda[i] == 0
			coloring[i] == 0
		end
		push!(indeces[lambda[i] + 1],i)
		if (lambda[i] > nmax)
			nmax = lambda[i]
		end
	end

	while (nmax > 0)
		old_max = nmax

		# take a node that has a maximal neigbour count
		curr = pop!(indeces[nmax + 1])

		# set the node as coarse, and reset it's neigbour count
		coloring[curr] = 1
		lambda[curr] = 0

		# look at every neigbour it has
		for j in nzrange(S,curr)
			row = rows[j]
			if lambda[row] != 0
				# mark as free node
				pop!(indeces[lambda[row] + 1], row)
				lambda[row] = 0
				push!(indeces[lambda[row] + 1], row)
				coloring[row] = 0
			end
		end

		for j in nzrange(S,curr)
			row = rows[j]
			for k in nzrange(S, row)
				rowk = rows[k]
				# node was already set, ignore it.
				if (lambda[rowk] == 0)
					continue
				end

				# update node count
				pop!(indeces[lambda[rowk] + 1], rowk)
				lambda[rowk] += 1
				push!(indeces[lambda[rowk] + 1], rowk)

				# we have a new max
				if(lambda[rowk] > nmax)
					nmax = lambda[rowk]
				end
			end

		end

		# No new max, check if max should be lower
		if (old_max == nmax)
			for j=nmax:-1:0
				nmax = j
				if (~isempty(indeces[j + 1]))
					break
				end
			end
		end
	end
	coloring
end

# Change coloring according to heuristic:
# for every strongly connected F-F couple,
# both nodes has to have at least one common strongly connected C node.
function getColoringSecond(S::SparseMatrixCSC, coloring::Array, n::Int64)
	for i=1:n
		# This is a coarse node
		if coloring[i] == 1
			continue
		end
		# Split nodes strongly connected to i, into F and C sets
		fconn, cconn = strongSplitting(S, coloring, i)
		# Go through every F-F connection
		for j in fconn
			# if we don't have a common C node, make current node a C node
			if !hasCommonC(S, coloring, cconn, i, j)
				coloring[i] = 1
				break
			end
		end
	end
	return coloring
end

# Check if nodes i,j has a common strongly connected C node
function hasCommonC(S::SparseMatrixCSC, coloring::Array, cconn::Set{Int}, i::Int64, j::Int64)
	# Check for common C nodes
	for k in nzrange(S, j)
		if S.rowval[k] == i
			continue
		end
		if coloring[S.rowval[k]] == 1 && S.rowval[k] in cconn
			# println("Found F-F with common C : $i , $j, C is $(S.rowval[k])")
			return true
		end
	end
	return false
end

# Split the nodes strongly connected to i into coarse a set (cconn) and a free set (fconn)
function strongSplitting(S::SparseMatrixCSC, coloring::Array, i::Int64)
	fconn = Set{Int}()
	cconn = Set{Int}()

	for j in nzrange(S,i)
		# skip if this is a node already checked, or is the current node (that means i)
		if S.rowval[j] == i
			continue
		end
		if coloring[S.rowval[j]] == 0
			push!(fconn, S.rowval[j])
		else
			push!(cconn, S.rowval[j])
		end
	end
	fconn, cconn
end

## Alternative algorithm. Tries to minimize the coarse grid.

# idea
# get all F-F links for a free node, which do not have a common C neighbour and store in set.
# Do so for each free node - will be saved in an Array.
# now take the node with the maximal number of such neighbours,
# remove it from all it's neighbours' sets, remove it's set and turn it into a coarse node.
# repeat until all sets are empty
function getColoringSecondS(S::SparseMatrixCSC, coloring::Array, n::Int64)
	(fconn, cconn) = getConnections(S, coloring, n)

	maxi = 0
	max_node = 1

	# fconn holds all F-F connections that doesn't have a common C node
	# Make the free node with the maximal amount of such neighbours, a C node. Until no more exist
	while(true)
		(fconn, maxi, max_node) = clearConnections(S, fconn, cconn, n, 0, 1)
		if maxi == 0
			break
		end

		coloring[max_node] = 1
		for i in fconn[max_node]
			pop!(fconn[i],max_node)
			push!(cconn[i],max_node)
		end
		empty!(fconn[max_node])
	end

	return coloring
end

# Get all strong connections for every node, split into a coarse set and a free set.
# fconn[i] holds all free nodes strongly connected to i
# cconn[i] holds all coarse nodes strongly connected to i
function getConnections(S::SparseMatrixCSC, coloring::Array, n::Int64)
	fconn = [Set{Int}() for i=1:n]
	cconn = [Set{Int}() for i=1:n]
	# Populate fconn with all F-F connections, and cconn with all C-F connections
	for i=1:n

		# Skip over coarse nodes
		if coloring[i] == 1
			continue
		end

		for j in nzrange(S, i)
			if i == S.rowval[j]
				continue
			end

			# coarse neighbour
			if coloring[S.rowval[j]] == 1
				push!(cconn[i],S.rowval[j])
				continue

			# free neighbour
			else
				push!(fconn[i],S.rowval[j])
			end

		end
	end
	fconn, cconn
end

# Remove all free nodes strongly connected, such that they have a common coarse node.
# fconn will be left with all F-F connections without a common node in cconn
function clearConnections(S::SparseMatrixCSC, fconn::Array{Set{Int64},1}, cconn::Array{Set{Int64},1}, n::Int64, maxi::Int64, max_node::Int64)
	for i=1:n
		for j in fconn[i]
			if i == j
				continue
			end
			for k in cconn[j]
				# Has a common c node, remove F-F connection
				if k in cconn[i]
					pop!(fconn[i],j)
					pop!(fconn[j],i)
					break
				end
			end
		end
		if length(fconn[i]) > maxi
			max_node = i
			maxi = length(fconn[i])
		end
	end
	return fconn, maxi, max_node
end


# ---
# title: 918. Maximum Sum Circular Subarray
# id: problem918
# author: Indig
# date: 2021-07-12
# difficulty: Medium
# categories: Array
# link: <https://leetcode.com/problems/maximum-sum-circular-subarray/description/>
# hidden: true
# ---
#
# Given a **circular  array**  **C** of integers represented by `A`, find the
# maximum possible sum of a non-empty subarray of **C**.
#
# Here, a  _circular  array_ means the end of the array connects to the
# beginning of the array.  (Formally, `C[i] = A[i]` when `0 <= i < A.length`,
# and `C[i+A.length] = C[i]` when `i >= 0`.)
#
# Also, a subarray may only include each element of the fixed buffer `A` at most
# once.  (Formally, for a subarray `C[i], C[i+1], ..., C[j]`, there does not
# exist `i <= k1, k2 <= j` with `k1 % A.length = k2 % A.length`.)
#
#
#
# **Example 1:**
#
#
#
#     Input: [1,-2,3,-2]
#     Output: 3
#     Explanation: Subarray [3] has maximum sum 3
#
#
# **Example 2:**
#
#
#
#     Input: [5,-3,5]
#     Output: 10
#     Explanation:  Subarray [5,5] has maximum sum 5 + 5 = 10
#
#
# **Example 3:**
#
#
#
#     Input: [3,-1,2,-1]
#     Output: 4
#     Explanation:  Subarray [2,-1,3] has maximum sum 2 + (-1) + 3 = 4
#
#
# **Example 4:**
#
#
#
#     Input: [3,-2,2,-3]
#     Output: 3
#     Explanation:  Subarray [3] and [3,-2,2] both have maximum sum 3
#
#
# **Example 5:**
#
#
#
#     Input: [-2,-3,-1]
#     Output: -1
#     Explanation:  Subarray [-1] has maximum sum -1
#
#
#
#
# **Note:**
#
#   1. `-30000 <= A[i] <= 30000`
#   2. `1 <= A.length <= 30000`
#
#
## @lc code=start
using LeetCode

function max_subarray_sum_circular(nums)
    length(nums) < 1 && return 0
    csum = cur_max = maxn = cur_min = minn = nums[1]
    for i in 2:length(nums)
        csum += nums[i]
        cur_max = (cur_max > 0) ? cur_max + nums[i] : nums[i]
        maxn = max(maxn, cur_max)
        cur_min = (cur_min < 0) ? cur_min + nums[i] : nums[i]
        minn = min(minn, cur_min)
    end
    return maxn < 0 ? maxn : max(csum - minn, maxn)
end
## @lc code=end


struct Backtest{Fl <: AbstractFloat}
    abs_errors::Matrix{Fl}
    mae::Vector{Fl}
    crps_scores::Matrix{Fl}
    mean_crps::Vector{Fl}
    function Backtest{Fl}(n::Int, steps_ahead::Int) where Fl
        abs_errors = Matrix{Fl}(undef, steps_ahead, n)
        crps_scores = Matrix{Fl}(undef, steps_ahead, n)
        mae = Vector{Fl}(undef, steps_ahead)
        mean_crps = Vector{Fl}(undef, steps_ahead)
        return new(abs_errors, mae, crps_scores, mean_crps)
    end
end

discrete_crps_indicator_function(val::Fl, z::Fl) where {Fl} = val < z
function crps(val::Fl, scenarios::Vector{Fl}) where {Fl}
    sorted_scenarios = sort(scenarios)
    m = length(scenarios)
    crps_score = zero(Fl)
    for i = 1:m
        crps_score +=
            (sorted_scenarios[i] - val) *
            (m * discrete_crps_indicator_function(val, sorted_scenarios[i]) - i + 0.5)
    end
    return (2 / m^2) * crps_score
end
evaluate_abs_error(y::Vector{Fl}, forecast::Vector{Fl}) where Fl = abs.(y - forecast)
function evaluate_crps(y::Vector{Fl}, scenarios::Matrix{Fl}) where {Fl}
    crps_scores = Vector{Fl}(undef, length(y))
    for k = 1:length(y)
        crps_scores[k] = crps(y[k], scenarios[k, :])
    end
    return crps_scores
end

"""
    backtest(model::StateSpaceModel, steps_ahead::Int, start_idx::Int;
             n_scenarios::Int = 10_000,
             filter::KalmanFilter=default_filter(model),
             optimizer::Optimizer=default_optimizer(model)) where Fl

Makes rolling window estimating and forecasting to benchmark the forecasting skill of the model
in for different time periods and different lead times. The function returns a struct with the MAE
and mean CRPS per lead time. See more on [Backtest the forecasts of a model](@ref)

# References
 * DTU course "31761 - Renewables in electricity markets" available on youtube https://www.youtube.com/watch?v=Ffo8XilZAZw&t=556s
"""
function backtest(model::StateSpaceModel, steps_ahead::Int, start_idx::Int;
                  n_scenarios::Int = 10_000,
                  filter::KalmanFilter=default_filter(model),
                  optimizer::Optimizer=default_optimizer(model))
    Fl = typeof_model_elements(model)
    num_mle = length(model.system.y) - start_idx - steps_ahead
    b = Backtest{Fl}(num_mle, steps_ahead)
    for i in 1:num_mle
        println("Backtest: step $i of $num_mle")
        y_to_fit = model.system.y[1:start_idx - 1 + i]
        y_to_verify = model.system.y[start_idx + i:start_idx - 1 + i + steps_ahead]
        model_to_fit = reinstantiate(model, y_to_fit)
        fit!(model_to_fit; filter=filter, optimizer=optimizer)
        forec = forecast(model_to_fit, steps_ahead; filter=filter)
        scenarios = simulate_scenarios(model_to_fit, steps_ahead, n_scenarios; filter=filter)
        expected_value_vector = forecast_expected_value(forec)[:]
        abs_errors = evaluate_abs_error(y_to_verify, expected_value_vector)
        crps_scores = evaluate_crps(y_to_verify, scenarios[:, 1, :])
        b.abs_errors[:, i] = abs_errors
        b.crps_scores[:, i] = crps_scores
    end
    for i in 1:steps_ahead
        b.mae[i] = mean(b.abs_errors[i, :])
        b.mean_crps[i] = mean(b.crps_scores[i, :])
    end
    return b
end


# ---
# title: 790. Domino and Tromino Tiling
# id: problem790
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Dynamic Programming
# link: <https://leetcode.com/problems/domino-and-tromino-tiling/description/>
# hidden: true
# ---
#
# We have two types of tiles: a 2x1 domino shape, and an "L" tromino shape.
# These shapes may be rotated.
#
#
#
#     XX  <- domino
#
#     XX  <- "L" tromino
#     X
#
#
# Given N, how many ways are there to tile a 2 x N board? **Return your answer
# modulo 10^9 + 7**.
#
# (In a tiling, every square must be covered by a tile. Two tilings are
# different if and only if there are two 4-directionally adjacent cells on the
# board such that exactly one of the tilings has both squares occupied by a
# tile.)
#
#
#
#     **Example:**
#     Input: 3
#     Output: 5
#     Explanation:
#     The five different ways are listed below, different letters indicates different tiles:
#     XYZ XXZ XYY XXY XYY
#     XYZ YYZ XZZ XYY XXY
#
# **Note:**
#
#   * N  will be in range `[1, 1000]`.
#
#
#
#
## @lc code=start
using LeetCode

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


# Prototype - copied from pluto_RayTracingWeekend.jl
# Adapted from [Ray Tracing In One Weekend by Peter Shirley](https://raytracing.github.io/books/RayTracingInOneWeekend.html) and
# [cshenton's Julia implementation](https://github.com/cshenton/RayTracing.jl)"

using RayTracingWeekend

using BenchmarkTools
using Images
using InteractiveUtils
using LinearAlgebra
using StaticArrays
using LoopVectorization: @turbo

# Float32/Float64
ELEM_TYPE = Float64

t_default_cam = default_camera(SA{ELEM_TYPE}[0,0,0])

t_cam1 = default_camera([13,2,3], [0,0,0], [0,1,0], 20, 16/9, 0.1, 10.0; elem_type=ELEM_TYPE)

t_cam2 = default_camera([3,3,2], [0,0,-1], [0,1,0], 20, 16/9, 2.0, norm([3,3,2]-[0,0,-1]);
						elem_type=ELEM_TYPE)

# After some optimization:
#  46.506 ms (917106 allocations: 16.33 MiB)
# Using convert(Float32, ...) instead of MyFloat(...):
#  14.137 ms (118583 allocations: 4.14 MiB)
# Don't specify return value of Option{HitRecord} in hit()
#  24.842 ms (527738 allocations: 16.63 MiB)
# Don't initialize unnecessary elements in HitRecord():
#  14.862 ms (118745 allocations: 4.15 MiB)  (but computer was busy...)
# Replace MyFloat by Float32:
#  11.792 ms ( 61551 allocations: 2.88 MiB)
# Remove ::HitRecord return value in remaining hit() method:
#  11.545 ms ( 61654 allocations: 2.88 MiB)
# with mutable HitRecord
#  11.183 ms ( 61678 allocations: 2.88 MiB) (insignificant?)
# @inline tons of stuff. Note: render() uses `for i in 1:image_height, j in 1:image_width`,
#    i.e. iterating 1 row at time!
#   8.129 ms ( 61660 allocations: 2.88 MiB)
# Using in render(): `for j in 1:image_width, i in 1:image_height # iterate over each column`
#  10.489 ms ( 61722 allocations: 2.88 MiB) (consistently slower!)
# ... sticking with `for i in 1:image_height, j in 1:image_width # iterate over each row` for now...
# Using `get_ray(cam, u+δu, v+δv)` (fixes minor bug, extract constants outside inner loop):
# ... performance appears equivalent, maybe a tiny bit faster on avg (1%?)
# Re-measured:
#   8.077 ms (61610 allocations: 2.88 MiB)
# After parameterized Vec3{T}:
# Float64: 8.344 ms (61584 allocations: 4.82 MiB)
# Float32: 8.750 ms (123425 allocations: 3.83 MiB)
# rand() using MersenneTwister _rng w/ Float64:
#   6.967 ms (61600 allocations: 4.82 MiB)
# rand() using Xoroshiro128Plus _rng w/ Float64:
#   6.536 ms (61441 allocations: 4.81 MiB)
# Above was all using 1 single thread. With 16 threads:
#   4.414 ms (61673 allocations: 4.82 MiB)
# Above was all using max bounces=4, since this looked fine to me (except the negatively scaled sphere).
# Switching to max bounces=16 to match C++ version decreased performance by 7.2%:
#   4.465 ms (65680 allocations: 5.13 MiB)
# Lots of optimizations... ending with make HitRecord non-mutable:
#   2.225 ms (445188 allocations: 34.08 MiB)
# Using non-mutable HitRecord, Union{HitRecord,Missing}, ismissing():
#   976.365 μs (65574 allocations: 5.12 MiB)
# Using @paulmelis' style of hit(): @inbounds for i in eachindex(hittables) and Union{HitRecord, Nothing}
#   951.447 μs (65574 allocations: 5.12 MiB)
render(scene_2_spheres(; elem_type=ELEM_TYPE), t_default_cam, 96, 16) # 16 samples

# Iterate over each column: 614.820 μs
# Iterate over each row: 500.334 μs
# With Rand(Float32) everywhere:
#   489.745 μs (3758 allocations: 237.02 KiB)
# With parameterized Vec3{T}:
# Float64: 530.957 μs (3760 allocations: 414.88 KiB)
# rand() using MersenneTwister _rng w/ Float64:
#   473.672 μs (3748 allocations: 413.94 KiB)
# rand() using Xoroshiro128Plus _rng w/ Float64:
#   444.399 μs (3737 allocations: 413.08 KiB)
# Above was all using 1 single thread. With 16 threads:
#   300.438 μs (3829 allocations: 420.86 KiB)
# Above was all using max bounces=4, since this looked fine to me (except the negatively scaled sphere).
# Switching to max bounces=16 to match C++ version decreased performance by 7.2%:
#   314.094 μs (4009 allocations: 434.97 KiB)
# Lots of optimizations... ending with make HitRecord non-mutable:
#   136.388 μs (28306 allocations: 2.28 MiB)
# Using non-mutable HitRecord, Union{HitRecordMissing}, ismissing():
#   102.764 μs (4314 allocations: 459.41 KiB)
# Using @paulmelis' style of hit(): @inbounds for i in eachindex(hittables) and Union{HitRecord, Nothing}
#   101.161 μs (4314 allocations: 459.41 KiB)
render(scene_2_spheres(; elem_type=ELEM_TYPE), t_default_cam, 96, 1) # 1 sample

#render(scene_4_spheres(; elem_type=ELEM_TYPE), t_default_cam, 96, 16)

#render(scene_diel_spheres(; elem_type=ELEM_TYPE), t_default_cam, 96, 16)
#render(scene_diel_spheres(), default_camera(), 320, 32)

# Hollow Glass sphere using a negative radius
#render(scene_diel_spheres(-0.5; elem_type=ELEM_TYPE), t_default_cam, 96, 16)

#render(scene_diel_spheres(; elem_type=ELEM_TYPE), default_camera((SA{ELEM_TYPE}[-2,2,1]), (SA{ELEM_TYPE}[0,0,-1]),
#																 (SA{ELEM_TYPE}[0,1,0]), ELEM_TYPE(20)), 96, 16)

#render(scene_blue_red_spheres(; elem_type=ELEM_TYPE), t_default_cam, 96, 16)

# took ~20s (really rough timing) in REPL, before optimization
# after optimization:
#   880.997 ms (48801164 allocations: 847.10 MiB)
# after switching to Float32 + reducing allocations using rand_vec3!():
#    79.574 ms (  542467 allocations:  14.93 MiB)
# after optimizing skycolor, rand*, probably more stuff I forgot...
#    38.242 ms (  235752 allocations:   6.37 MiB)
# after removing all remaining Color, Vec3, replacing them with @SVector[]...
#    26.790 ms (   91486 allocations: 2.28 MiB)
# Using convert(Float32, ...) instead of MyFloat(...):
#    25.856 ms (   70650 allocations: 1.96 MiB)
# Don't specify return value of Option{HitRecord} in hit()
# Don't initialize unnecessary elements in HitRecord():
#    38.961 ms (   70681 allocations: 1.96 MiB) # WORSE, probably because we're writing a lot more to stack...?
# Replace MyFloat by Float32:
#    36.726 ms (13889 allocations: 724.09 KiB)
# @inline lots of stuff:
#    14.690 ms (13652 allocations: 712.98 KiB)
# rand(Float32) to avoid Float64s:
#    14.659 ms (13670 allocations: 713.84 KiB)
# Re-measured:
#    14.069 ms (13677 allocations: 714.22 KiB)
# After parameterized Vec3{T}:
# Float32: 14.422 ms (26376 allocations: 896.52 KiB) # claforte: why are there 2X the allocations as previously?
# Float64: 12.772 ms (12868 allocations: 1.08 MiB) (10% speed-up! Thanks @woclass!)
# rand() using MersenneTwister _rng w/ Float64:
#    14.092 ms (12769 allocations: 1.07 MiB) (SLOWER?!)
# rand() using Xoroshiro128Plus _rng w/ Float64:
#    12.581 ms (12943 allocations: 1.08 MiB)
# Above was all using 1 single thread. With 16 threads:
#     1.789 ms (12926 allocations: 1.08 MiB) (WOW!)
# Above was all using max bounces=4, since this looked fine to me (except the negatively scaled sphere).
# Switching to max bounces=16 to match C++ version decreased performance by 7.2%:
#     2.168 ms (13791 allocations: 1.15 MiB)
# Using  bunch of @inbounds, @simd in low-level functions
#     2.076 ms (13861 allocations: 1.15 MiB)
# Lots of optimizations, up to `Using non-mutable HitRecord, Union{HitRecordMissing}, ismissing():`
#     2.042 ms (14825 allocations: 1.23 MiB)
#@btime render(scene_random_spheres(; elem_type=ELEM_TYPE), t_cam1, 96, 1)

# took 5020s in Pluto.jl, before optimizations!
# after lots of optimizations, up to switching to Float32 + reducing allocations using rand_vec3!():
#   10.862032 seconds (70.09 M allocations: 1.858 GiB, 1.53% gc time)
# after optimizing skycolor, rand*, probably more stuff I forgot...
#    4.926 s (25,694,163 allocations: 660.06 MiB)
# after removing all remaining Color, Vec3, replacing them with @SVector[]...
#    3.541 s (9074843 allocations: 195.22 MiB)
# Using convert(Float32, ...) instead of MyFloat(...):
#    3.222 s (2055106 allocations: 88.53 MiB)
# Don't specify return value of Option{HitRecord} in hit()
# Don't initialize unnecessary elements in HitRecord():
#    5.016 s (2056817 allocations: 88.61 MiB) #  WORSE, probably because we're writing a lot more to stack...?
# Replace MyFloat by Float32:
#    5.185 s (1819234 allocations: 83.55 MiB) # Increase of 1% is probably noise
# Remove normalize() in reflect() (that function assumes the inputs are normalized)
#    5.040 s (1832052 allocations: 84.13 MiB)
# @inline lots of stuff:
#    2.110 s (1823044 allocations: 83.72 MiB)
# rand(Float32) to avoid Float64s:
#    2.063 s (1777985 allocations: 81.66 MiB)
# @woclass's "Use alias instead of new struct", i.e. `const HittableList = Vector{Hittable}`:
#    1.934 s (1796954 allocations: 82.53 MiB)
# @woclass's Vec3{T} with T=Float64: (7.8% speed-up!)
#    1.800 s (1711061 allocations: 131.03 MiB)
# rand() using Xoroshiro128Plus _rng w/ Float64:
#    1.808 s (1690104 allocations: 129.43 MiB) (i.e. rand is not a bottleneck?)
# Above was all using 1 single thread. With 16 threads:
#  265.331 ms (1645402 allocations: 126.02 MiB) (WOW!)
# Above was all using max bounces=4, since this looked fine to me (except the negatively scaled sphere).
# Switching to max bounces=16 to match C++ version decreased performance by 7.2%:
#  308.217 ms (1830162 allocations: 140.12 MiB)
# Using @inbounds, @simd in low-level functions:
#  302.952 ms (1892513 allocations: 144.88 MiB)
# Convert Camera and every Material structs to non-mutable:
#  301.042 ms (1849711 allocations: 141.61 MiB)  (i.e. - unchanged)
# Adapt @Christ_Foster's Base.getproperty w/ @inline @inbounds:
#  292.603 ms (1856398 allocations: 142.12 MiB) (ran multiple times, seems like real, 3-5% speed-up)
# Eliminate the off-by-half-a-pixel offset:
#  286.873 ms (1811412 allocations: 138.69 MiB) (ran multiple times, seems like ~2.5% speed-up)
# Fixed, per-thread RNGs with fixed seeds
#  286.575 ms (1884433 allocations: 144.26 MiB) (i.e. maybe a tiny bit faster considering this fixed seed has more allocations?)
# Make HitRecord non-mutable:
#   29.733 s (937962909 allocations: 69.88 GiB) (WTF!)
# Lots of optimizations, up to `Using non-mutable HitRecord, Union{HitRecordMissing}, ismissing():`
#  306.011 ms (1884433 allocations: 144.26 MiB) (Still slower... Hum)
# Using @paulmelis' style of hit(): @inbounds for i in eachindex(hittables) and Union{HitRecord, Nothing}
#  304.877 ms (1884433 allocations: 144.26 MiB)
# Extract the scene creation from the render() call:
#  300.344 ms (1883484 allocations: 144.21 MiB)
# Separate the module's reusable functions/classes from this proto.jl
#  296.824 ms (min) (1909996 allocs: 146.23 MiB)
print("render(scene_random_spheres(; elem_type=ELEM_TYPE), t_cam1, 200, 32):")
reseed!()
_scene_random_spheres = scene_random_spheres(; elem_type=ELEM_TYPE)
@benchmark render($_scene_random_spheres, $t_cam1, 200, 32)

# After some optimization, took ~5.6 hours:
#   20171.646846 seconds (94.73 G allocations: 2.496 TiB, 1.06% gc time)
# ... however the image looked weird... too blurry
# After removing all remaining Color, Vec3, replacing them with @SVector[]... took ~3.7 hours:
#   13326.770907 seconds (29.82 G allocations: 714.941 GiB, 0.36% gc time)
# Using convert(Float32, ...) instead of MyFloat(...):
# Don't specify return value of Option{HitRecord} in hit()
# Don't initialize unnecessary elements in HitRecord():
# Took ~4.1 hours:
#   14723.339976 seconds (5.45 G allocations: 243.044 GiB, 0.11% gc time) # WORSE, probably because we're writing a lot more to stack...?
# Replace MyFloat by Float32:
# Lots of other optimizations including @inline lots of stuff:
#    6018.101653 seconds (5.39 G allocations: 241.139 GiB, 0.21% gc time) (1.67 hour)
# @woclass's rand(Float32) to avoid Float64s: (expected to provide 2.2% speed up)
# @woclass's "Use alias instead of new struct", i.e. `const HittableList = Vector{Hittable}`
# @woclass's Vec3{T} with T=Float64: (7.8% speed-up!):
#    5268.175362 seconds (4.79 G allocations: 357.005 GiB, 0.47% gc time) (1.46 hours)
# Above was all using 1 single thread. With 16 threads: (~20 minutes)
#    1210.363539 seconds (4.94 G allocations: 368.435 GiB, 10.08% gc time)
# Above was all using max bounces=4, since this looked fine to me (except the negatively scaled sphere).
# Switching to max bounces=16 to match C++ version decreased performance by 7.2%:
#    1298.522674 seconds (5.43 G allocations: 404.519 GiB, 10.18% gc time)
# Using @inbounds, @simd in low-level functions:
#    1314.510565 seconds (5.53 G allocations: 411.753 GiB, 10.21% gc time) # NOTE: difference due to randomness?
# Adapt @Christ_Foster's Base.getproperty w/ @inline @inbounds: (expect 3-5% speed-up)
# Eliminate the off-by-half-a-pixel offset: (expect ~2.5% speed-up)
# Fixed, per-thread RNGs with fixed seeds (expecting no noticeable change in speed)
#  Using 16 threads: (21m22s)
#    1282.437499 seconds (5.53 G allocations: 411.742 GiB, 10.08% gc time) (i.e. 2.5% speed-up... currently GC- and memory-bound?)
#  Using 14 threads: (21m45s)
#    1305.767627 seconds (5.53 G allocations: 411.741 GiB, 9.97% gc time)
#print("@time render(scene_random_spheres(; elem_type=ELEM_TYPE), t_cam1, 1920, 1000):")
#@time render(scene_random_spheres(; elem_type=ELEM_TYPE), t_cam1, 1920, 1000)

# Before optimization:
#  5.993 s  (193097930 allocations: 11.92 GiB)
# after disabling: `Base.getproperty(vec::SVector{3}, sym::Symbol)`
#  1.001 s  ( 17406437 allocations: 425.87 MiB)
# after forcing Ray and point() to use Float64 instead of AbstractFloat:
#  397.905 ms (6269207 allocations: 201.30 MiB)
# after forcing use of Float32 instead of Float64:
#  487.680 ms (7128113 allocations: 196.89 MiB) # More allocations... something is causing them...
# after optimizing rand_vec3!/rand_vec2! to minimize allocations:
#  423.468 ms (6075725 allocations: 158.92 MiB)
# after optimizing skycolor, rand*, probably more stuff I forgot...
#  217.853 ms (2942272 allocations: 74.82 MiB)
# after removing all remaining Color, Vec3, replacing them with @SVector[]...
#   56.778 ms (1009344 allocations: 20.56 MiB)
# Using convert(Float32, ...) instead of MyFloat(...):
#   23.870 ms (210890 allocations: 8.37 MiB)
# Replace MyFloat by Float32:
#   22.390 ms (153918 allocations: 7.11 MiB)
# Various other changes, e.g. remove unnecessary normalize
#   20.241 ms (153792 allocations: 7.10 MiB)
# @inline lots of stuff:
#   18.065 ms (153849 allocations: 7.10 MiB)
# rand(Float32) to avoid Float64s:
#   16.035 ms (153777 allocations: 7.10 MiB)
# After @woclass's Vec3{T} with T=Float64:
#   16.822 ms (153591 allocations: 11.84 MiB)
# rand() using Xoroshiro128Plus _rng w/ Float64:
#   13.469 ms (153487 allocations: 11.83 MiB)
# Above was all using 1 single thread. With 16 threads:
#    6.537 ms (153599 allocations: 11.84 MiB)
# Above was all using max bounces=4, since this looked fine to me (except the negatively scaled sphere).
# Switching to max bounces=16 to match C++ version decreased performance by 7.2%:
#    6.766 ms (161000 allocations: 12.40 MiB)
# @inbounds and @simd in low-level functions
#    6.519 ms (160609 allocations: 12.37 MiB)
render(scene_diel_spheres(; elem_type=ELEM_TYPE), t_cam2, 96, 16)

using Profile
render(scene_random_spheres(; elem_type=ELEM_TYPE), t_cam1, 16, 1)
Profile.clear_malloc_data()
render(scene_random_spheres(; elem_type=ELEM_TYPE), t_cam1, 320, 64)


function compute_ideal_step_size(new_objective, new_point, min_value, solution)
    distance_to_solution = LinearAlgebra.norm(new_point - solution)
    obj_gap = (new_objective - min_value)
    if obj_gap > 0.0
        return distance_to_solution^2 / obj_gap
    end
    return 0.0
end

function compute_step_size(new_objective, min_value, diameter)
    return (new_objective - min_value) / diameter^2
end

function create_subgradient_method_parameters(;
    eps_optimal = 1e-6,
    iteration_limit = 100,
    printing_frequency = 50,
    verbose = false,
)
    return ProximalBundleMethod.SubgradientMethodParams(
        eps_optimal,
        iteration_limit,
        verbose,
        printing_frequency,
    )
end

function create_bundle_method_parameters(;
    eps_optimal = 1e-6,
    iteration_limit = 100,
    contraction_factor = 0.5,
    printing_frequency = 50,
    verbose = false,
    full_memory = false,
)
    return ProximalBundleMethod.BundleMethodParams(
        eps_optimal,
        iteration_limit,
        contraction_factor,
        verbose,
        printing_frequency,
        full_memory,
    )
end


using Statistics
using StatsBase: sample
using XGBoost
using EvoTrees
using BenchmarkTools

# prepare a dataset
features = rand(Int(1.25e6), 100)
# features = rand(100, 10)
X = features
Y = rand(size(X, 1))
𝑖 = collect(1:size(X,1))

# train-eval split
𝑖_sample = sample(𝑖, size(𝑖, 1), replace = false)
train_size = 0.8
𝑖_train = 𝑖_sample[1:floor(Int, train_size * size(𝑖, 1))]
𝑖_eval = 𝑖_sample[floor(Int, train_size * size(𝑖, 1))+1:end]

X_train, X_eval = X[𝑖_train, :], X[𝑖_eval, :]
Y_train, Y_eval = Y[𝑖_train], Y[𝑖_eval]

#######################
# xgboost
#######################
num_round = 100
param = ["max_depth" => 5,
         "eta" => 0.05,
         "objective" => "reg:linear",
         "print_every_n" => 5,
         "subsample" => 0.5,
         "colsample_bytree" => 0.5,
         "tree_method" => "hist",
         "max_bin" => 32]
metrics = ["rmse"]
@time xgboost(X_train, num_round, label = Y_train, param = param, metrics=metrics, silent=1);
@time dtrain = DMatrix(X_train, label = Y_train)
@time model_xgb = xgboost(dtrain, num_round, param = param, metrics=metrics, silent=1);
@time pred_train = XGBoost.predict(model_xgb, X_train)

# train model
params1 = EvoTreeRegressor(
    loss=:linear, metric=:none,
    nrounds=100,
    λ = 0.0, γ=0.0, η=0.05,
    max_depth = 6, min_weight = 1.0,
    rowsample=0.5, colsample=0.5, nbins=32)

# for 100k: 410.477 ms (44032 allocations: 182.68 MiB)
# for 1.25e6: 6.964114 seconds (6.05 M allocations: 2.350 GiB, 2.82% gc time)
# for 1.25e6 no eval: 6.200 s (44330 allocations: 2.19 GiB)
# for 1.25e6 mse with eval data: 6.321 s (45077 allocations: 2.19 GiB)
@time model, cache = init_evotree(params1, X_train, Y_train);
@time grow_evotree!(model, cache);
@time model = fit_evotree(params1, X_train, Y_train);
@btime model = fit_evotree(params1, X_train, Y_train);
@time pred_train = EvoTrees.predict(model, X_train)

@time model = fit_evotree(params1, X_train, Y_train, X_eval=X_eval, Y_eval=Y_eval, print_every_n=9999, early_stopping_rounds=9999);
@btime model = fit_evotree(params1, X_train, Y_train, X_eval=X_eval, Y_eval=Y_eval, print_every_n=9999, early_stopping_rounds=9999);

@time model = fit_evotree(params1, X_train, Y_train, early_stopping_rounds=10);
@time model = fit_evotree(params1, X_train, Y_train, print_every_n=2);

# @time model = grow_gbtree(X_train, Y_train, params1, X_eval = X_eval, Y_eval = Y_eval, print_every_n = 5);
# @btime model = grow_gbtree($X_train, $Y_train, $params1, X_eval = $X_eval, Y_eval = $Y_eval);
@time pred_train = predict(model, X_train)

#############################
# agaricus
#############################
function readlibsvm(fname::String, shape)
    dmx = zeros(Float32, shape)
    label = Float32[]
    fi = open(fname, "r")
    cnt = 1
    for line in eachline(fi)
        line = split(line, " ")
        push!(label, parse(Float64, line[1]))
        line = line[2:end]
        for itm in line
            itm = split(itm, ":")
            dmx[cnt, parse(Int, itm[1]) + 1] = parse(Int, itm[2])
        end
        cnt += 1
    end
    close(fi)
    return (dmx, label)
end

# we use auxiliary function to read LIBSVM format into julia Matrix
train_X, train_Y = readlibsvm("data/agaricus.txt.train", (6513, 126))
test_X, test_Y   = readlibsvm("data/agaricus.txt.test", (1611, 126))

#-------------Basic Training using XGBoost-----------------
# note: xgboost naturally handles sparse input
# use sparse matrix when your feature is sparse(e.g. when you using one-hot encoding vector)
# model parameters can be set as parameters for ```xgboost``` function, or use a Vector{String} / Dict()
num_round = 100
# you can directly pass Julia's matrix or sparse matrix as data,
# by calling xgboost(data, num_round, label=label, training-parameters)
metrics = ["logloss"]
@time bst = xgboost(train_X, num_round, label = train_Y, eta = 0.1, max_depth = 3, metrics = metrics, silent=0, objective = "binary:logistic")
features_xgb = XGBoost.importance(bst)

X_train = Float64.(train_X)
Y_train = Float64.(train_Y)
params1 = EvoTreeRegressor(
    loss=:logistic, metric=:logloss,
    nrounds=100,
    λ = 0.0, γ=0.0, η=0.1,
    max_depth = 4, min_weight = 1.0,
    rowsample=1.0, colsample=1.0, nbins=250)

@time model = fit_evotree(params1, X_train, Y_train, print_every_n=50);
@time pred_train = EvoTrees.predict(model, X_train)
features_evo = importance(model, 1:size(X_train,2))
sort(collect(values(features_evo)))


using Distributions
using PyPlot

include("genw_sparse.jl")
include("run_target.jl")
include("run_train.jl")
include("run_test.jl")

# connectivity matrix
N = 200;
p = 0.3;
g = 1.0;

# neuron dynamics
b  = 1/2;

# training variables
train_duration = 100.;
nloop          = 10; #30
lambda         = 0.5; # 1, 10
learn_every    = 1.0;

# training param
stim_on         = 100.;
stim_off        = 110.;
train_time      = stim_off + train_duration;
test_time       = train_time + 100.;

dt = 0.1;

#---------- Choose target type ----------#
# target_type = "periodic";
target_type = "ratemodel";
# target_type = "ou";

#---------- Generate target trajectories ----------#
# generate M
M = g*genw_sparse(N,0,1,p)/sqrt(N*p);

# generate stim
stim = 2*(2*rand(N)-1);

# generate target patterns
net_param = [N, p, g, b, dt, test_time];
utarg = run_target(M,net_param,target_type);

#---------- Train recurrent connectivity ----------#
# Initialize variables
x0 = 2*pi*rand(N);
r0 = zeros(N);

# Run training
net_param   = [x0, r0, b];
train_param = [stim, lambda, learn_every, nloop, target_type];
time_param  = [stim_on, stim_off, train_time]
M = run_train(M,net_param,train_param,time_param,dt,utarg);

#---------- Test ----------#
# Initialize variables
x0 = 2*pi*rand(N);
r0 = zeros(N);
z0 = zeros(N);

net_param = [x0, r0, b, stim];
time_param = [stim_on, stim_off, test_time];
utest = run_test(M,net_param,time_param,dt);

#---------- Plot results ----------#
tvec = collect(dt: dt : test_time);

figure(figsize=(10,5))
nrow = 2;
ncol = 2;

for i = 1:nrow*ncol
    subplot(2,2,i)
    axvspan(10*stim_on,10*stim_off,alpha=0.5,color="dodgerblue")
    axvspan(10*stim_off,10*train_time,alpha=0.5,color="gray")

    ton = Int(stim_off/dt);
    toff = Int(train_time/dt);
    plot(tvec[ton:toff]*10,utarg[ton:toff,i],linewidth=3,color="black",label="target")
    plot(tvec*10,utest[:,i],linewidth=2,color="red",alpha=1,label="actual")

    xlim([500, 2500])
    xlabel("time (ms)",fontsize=15)
    ylabel("synaptic current",fontsize=15)

    if i == 4
      legend(loc=1,frameon=false,fontsize=15)
    end
end
tight_layout()


using HPAT
using ParallelAccelerator
ParallelAccelerator.ParallelIR.PIRHoistParfors(1)
ParallelAccelerator.CGen.setRecreateLoops(true)
ParallelAccelerator.CGen.setRecreateConds(true)

@acc hpat function kernelscore_test(n)
    X = rand(n)
    points = [-1.0, 2.0, 5.0]
    N = size(points,1)
    b = 0.5
    exps::Float64 = 0.0
    @par exps(+) for i in 1:length(X)
        d = -(X[i]-points).^2./(2*b^2)
        m = minimum(d)
        exps += m-log(b*N)+log(sum(exp(d-m)))
    end
    return exps
end

println(kernelscore_test(128))


module Utils
# ジョルダン曲線: ねじれのない閉曲線のこと.
"""
ジョルダン閉曲線であるかどうか
"""
function is_jordan_curve(_gamma::Array)
    progression_of_differences = [sqrt((_gamma[i, 1] - _gamma[i+1, 1])^2 + (_gamma[i, 2] - _gamma[i+1, 2])^2) for i = 1:(length(_gamma[:, 1])-1)]
    ave_distance = sum(progression_of_differences) / length(progression_of_differences)
    if ave_distance * 2 < abs(_gamma[1, 1] - _gamma[length(_gamma[:, 1]), 1])
        return true
    else
        return false
    end
end

# FIXME: 倍々ではなく, linearに補完数を指定できるように。

"n行目とn+1行目の間のデータを補完,境界条件を付与 (x, 2)->(x*2-1, 2)"
function complement_p(array::Array, multiple::Bool, point_space::Float64)
    (x, y) = size(array)
    return_value = Array{Float64}(undef, 2 * x, y)
    if multiple
        for i = 1:x-1
            # 点の間隔が平均以下なので、補間する -> 同一の曲線とみなす
            if sqrt(sum((array[i, :] .- array[i+1, :]) .^ 2)) < point_space * 5 # NOTE: ここの加減が難しい. Nに依存?
                return_value[i*2-1, :] = array[i, :]
                return_value[i*2, :] = (array[i, :] .+ array[i+1, :]) ./ 2
                # 点の間隔が離れているので違う曲線とみる
            else
                return_value[i*2-1, :] = array[i, :]
                return_value[i*2, :] = array[i, :]
            end
        end
        return_value[x*2-1, :] = array[x, :]
        return_value[x*2, :] = array[x, :]
    else
        for i = 1:x-1
            # 点の間隔が平均以下なので、補間する -> 同一の曲線とみなす
            return_value[i*2-1, :] = array[i, :]
            return_value[i*2, :] = (array[i, :] .+ array[i+1, :]) ./ 2
        end
        return_value[x*2-1, :] = array[x, :]
        return_value[x*2, :] = array[1, :] # Note: _gamma += _gamma's head line coz boundary condition. size: (N+1,2)
    end
    return return_value
end
# precompile()

function remove_same_point(array::Any)
    return_value = Array{Any}(undef, 0, 2)
    array_length = length(array[:, 1])
    for i = 1:array_length-1
        if array[i, :] != array[i+1, :]
            return_value = vcat(return_value, array[i, :]')
        end
    end
    return_value = vcat(return_value, array[array_length, :]')
    # println(return_value)
    return return_value
end

"""
    times回, 倍の数だけデータを補完する
"""
function interpolation(array::Array, times::Int, multiple = false)
    if times > 0
        tmp = []
        (x, y) = size(array)
        point_space = sum([sqrt(sum((array[i, :] .- array[i+1, :]) .^ 2)) for i = 1:x-1]) / x
        for _ = 1:times
            tmp = complement_p(array, multiple, point_space)
            array = tmp
        end
        return remove_same_point(tmp)
    end
end
precompile(interpolation, (Array, Int, Bool))

export is_jordan_curve, interpolation, remove_same_point
end


@testset "GeoMean" begin
    mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [ones(4); 2; √2; √2])
    bridged_mock = MOIB.GeoMean{Float64}(mock)
    MOIT.geomean1vtest(bridged_mock, config)
    MOIT.geomean1ftest(bridged_mock, config)
    # Dual is not yet implemented for GeoMean bridge
    ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.GeometricMeanCone}()))
    test_delete_bridge(bridged_mock, ci, 4, ((MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone, 0),
                                            (MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64},      1)))
end


using Temporal, Indicators, GLMakie

N = 252
X = quandl("CHRIS/CME_GC1", rows=N)
x = rename(cl(X), :Settle => :Gold)

lookback = 20
n_sigma = 2.0
reg = mlr_bands(x, n=lookback, se=n_sigma)
coef = mlr_beta(x, n=lookback)
rsq = mlr_rsq(x, n=lookback)

fig = Figure()
fig[1,1] = Axis(fig, title="Corn Futures")
lines!(fig[1,1], x.values[:,1], label="Price (Observed)", linewidth=3, color=:black)
lines!(fig[1,1], reg[:MLR].values[:], label="Predicted", linewidth=1, color=:blue)
lines!(fig[1,1], reg[:MLRLB].values[:], label="-2 Std Err", linewidth=1, color=:red)
lines!(fig[1,1], reg[:MLRUB].values[:], label="+2 Std Err", linewidth=1, color=:green)
band!(1:N, reg.values[:,1], reg.values[:,3], color="#80800040")
axislegend(bgcolor="#00000040", framecolor="#00000040", position=:cb, orientation=:horizontal)
lines(fig[2,1], 1:N, coef[:Slope].values[:], label="Beta", linewidth=2, color=:purple)
lines!(fig[2,1], 1:N, rsq.values[:], label="R-Squared", linewidth=2, color="#FF8000")
band!(1:N, zeros(N), rsq.values[:], color="#FF800040")
hlines!(current_axis(), [0.0, 1.0], linestyle=:dash, linewidth=1)
axislegend(bgcolor="#00000040", framecolor="#00000040", position=:cb, orientation=:horizontal)


include("dependencies_for_runtests.jl")

using Statistics
using Oceananigans.Architectures: arch_array
using Oceananigans.AbstractOperations: BinaryOperation
using Oceananigans.Fields: ReducedField, CenterField, ZFaceField, compute_at!, @compute
using Oceananigans.BoundaryConditions: fill_halo_regions!
using Oceananigans.Grids: halo_size

trilinear(x, y, z) = x + y + z

@testset "Fields computed by Reduction" begin
    @info "Testing Fields computed by reductions..."

    for arch in archs
        arch_str = string(typeof(arch))

        regular_grid = RectilinearGrid(arch, size = (2, 2, 2),
                                       x = (0, 2), y = (0, 2), z = (0, 2),
                                       topology = (Periodic, Periodic, Bounded))

        xy_regular_grid = RectilinearGrid(arch, size=(2, 2, 2),
                                          x=(0, 2), y=(0, 2), z=[0, 1, 2],
                                          topology = (Periodic, Periodic, Bounded))

        @testset "Averaged fields [$arch_str]" begin
            @info "  Testing averaged Fields [$arch_str]"

            for grid in (regular_grid, xy_regular_grid)

                Nx, Ny, Nz = size(grid)

                w = ZFaceField(grid)
                T = CenterField(grid)
                ζ = Field{Face, Face, Face}(grid)

                set!(T, trilinear)
                set!(w, trilinear)
                set!(ζ, trilinear)

                @compute Txyz = Field(Average(T, dims=(1, 2, 3)))

                # Note: halo regions must be *filled* prior to computing an average
                # if the average within halo regions is to be correct.
                fill_halo_regions!(T, arch)
                @compute Txy = Field(Average(T, dims=(1, 2)))

                fill_halo_regions!(T, arch)
                @compute Tx = Field(Average(T, dims=1))

                @compute wxyz = Field(Average(w, dims=(1, 2, 3)))
                @compute wxy = Field(Average(w, dims=(1, 2)))
                @compute wx = Field(Average(w, dims=1))

                @compute ζxyz = Field(Average(ζ, dims=(1, 2, 3)))
                @compute ζxy = Field(Average(ζ, dims=(1, 2)))
                @compute ζx = Field(Average(ζ, dims=1))

                for T′ in (Tx, Txy)
                    @test T′.operand.operand === T
                end

                for w′ in (wx, wxy)
                    @test w′.operand.operand === w
                end

                for ζ′ in (ζx, ζxy)
                    @test ζ′.operand.operand === ζ
                end

                for f in (wx, wxy, Tx, Txy, ζx, ζxy)
                    @test f.operand isa Reduction
                    @test f.operand.reduce! === mean!
                end

                @test Txyz.operand isa Reduction
                @test wxyz.operand isa Reduction
                @test ζxyz.operand isa Reduction

                # Different behavior for regular grid z vs not.
                if grid === regular_grid
                    @test Txyz.operand.reduce! === mean!
                    @test wxyz.operand.reduce! === mean!
                    @test Txyz.operand.operand === T
                    @test wxyz.operand.operand === w
                else
                    @test Txyz.operand.reduce! === sum!
                    @test wxyz.operand.reduce! === sum!
                    @test Txyz.operand.operand isa BinaryOperation
                    @test wxyz.operand.operand isa BinaryOperation
                end

                @test Tx.operand.dims === tuple(1)
                @test wx.operand.dims === tuple(1)
                @test Txy.operand.dims === (1, 2)
                @test wxy.operand.dims === (1, 2)
                @test Txyz.operand.dims === (1, 2, 3)
                @test wxyz.operand.dims === (1, 2, 3)

                @test Txyz[1, 1, 1] ≈ 3
                @test Array(interior(Txy))[1, 1, :] ≈ [2.5, 3.5]
                @test Array(interior(Tx))[1, :, :] ≈ [[2, 3] [3, 4]]
                @test wxyz[1, 1, 1] ≈ 3
                @test Array(interior(wxy))[1, 1, :] ≈ [2, 3, 4]
                @test Array(interior(wx))[1, :, :] ≈ [[1.5, 2.5] [2.5, 3.5] [3.5, 4.5]]

                @compute Txyz = Field(Average(T, condition=T.>3))
                @compute Txy = Field(Average(T, dims=(1, 2), condition=T.>3))
                @compute Tx = Field(Average(T, dims=1, condition=T.>2))
                @test Txyz[1,1,1] ≈ 3.75
                @test Array(interior(Txy))[1, 1, :] ≈ [3.5, 11.5/3]
                @test Array(interior(Tx))[1, :, :] ≈ [[2.5, 3] [3, 4]]

                @compute wxyz = Field(Average(w, condition=w.>3))
                @compute wxy = Field(Average(w, dims=(1, 2), condition=w.>2))
                @compute wx = Field(Average(w, dims=1, condition=w.>1))
                @test wxyz[1,1,1] ≈ 4.25
                @test Array(interior(wxy))[1, 1, :] ≈ [3, 10/3, 4]
                @test Array(interior(wx))[1, :, :] ≈ [[2, 2.5] [2.5, 3.5] [3.5, 4.5]]
            end

            # Test whether a race condition gets hit for averages over large fields
            big_grid = RectilinearGrid(arch,
                                       topology = (Periodic, Periodic, Bounded),
                                       size = (256, 256, 128),
                                       x = (0, 2),
                                       y = (0, 2),
                                       z = (0, 2))

            c = CenterField(big_grid)
            c .= 1

            C = Field(Average(c, dims=(1, 2)))

            # Test that the mean consistently returns 1 at every z for many evaluations
            # Warm up
            for i = 1:10
                sum!(C, C.operand.operand)
            end

            results = []
            for i = 1:10
                mean!(C, C.operand.operand)
                push!(results, all(interior(C) .== 1))
            end

            @test mean(results) == 1.0
        end

        @testset "Allocating reductions [$arch_str]" begin
            @info "  Testing allocating reductions"

            grid = RectilinearGrid(arch, size = (2, 2, 2),
                                   x = (0, 2), y = (0, 2), z = (0, 2),
                                   topology = (Periodic, Periodic, Bounded))

            w = ZFaceField(grid)
            T = CenterField(grid)
            set!(T, trilinear)
            set!(w, trilinear)
            fill_halo_regions!(T, arch)
            fill_halo_regions!(w, arch)

            @compute Txyz = Field(Average(T, dims=(1, 2, 3)))
            @compute Txy = Field(Average(T, dims=(1, 2)))
            @compute Tx = Field(Average(T, dims=1))

            @compute wxyz = Field(Average(w, dims=(1, 2, 3)))
            @compute wxy = Field(Average(w, dims=(1, 2)))
            @compute wx = Field(Average(w, dims=1))

            # Mean
            @test Txyz[1, 1, 1] == mean(T)
            @test interior(Txy) == interior(mean(T, dims=(1, 2)))
            @test interior(Tx) == interior(mean(T, dims=1))

            @test wxyz[1, 1, 1] == mean(w)
            @test interior(wxy) == interior(mean(w, dims=(1, 2)))
            @test interior(wx) == interior(mean(w, dims=1))

            # Maximum and minimum
            @test maximum(T) == maximum(interior(T))
            @test minimum(T) == minimum(interior(T))

            for dims in (1, (1, 2))
                @test interior(minimum(T; dims)) == minimum(interior(T); dims)
                @test interior(minimum(T; dims)) == minimum(interior(T); dims)
            end
        end

        @testset "Conditional computation of averaged Fields [$(typeof(arch))]" begin
            @info "  Testing conditional computation of averaged Fields [$(typeof(arch))]"
            for FT in float_types
                grid = RectilinearGrid(arch, FT, size=(2, 2, 2), extent=(1, 1, 1))
                c = CenterField(grid)

                for dims in (1, 2, 3, (1, 2), (2, 3), (1, 3), (1, 2, 3))
                    C = Field(Average(c, dims=dims))

                    @test !isnothing(C.status)

                    # Test conditional computation
                    set!(c, 1)
                    compute_at!(C, FT(1)) # will compute
                    @test all(interior(C) .== 1)
                    @test C.status.time == FT(1)

                    set!(c, 2)
                    compute_at!(C, FT(1)) # will not compute because status == 1
                    @test C.status.time == FT(1)
                    @test all(interior(C) .== 1)

                    compute_at!(C, FT(2))
                    @test C.status.time == FT(2)
                    @test all(interior(C) .== 2)
                end
            end
        end
    end
end


using Flux, MLDatasets, CUDA
using Flux: onehotbatch, onecold, logitcrossentropy
using Flux: DataLoader
using StatsBase: mean
import Dates
import BSON

# hyperparameters stored in a convenience struct
Base.@kwdef struct Args
    η::Float32         = 3e-4       # learning rate
    batchsize::Integer = 128
    usecuda::Bool      = true       # attempt to use GPU?
    savemodel::Bool    = true
    epochs::Integer    = 10
    infotime::Integer  = epochs÷5
    savepath::String   = "runs/"
end

function get_data(args)
    """Downloading & manipulating data"""
    ENV["DATADEPS_ALWAYS_ACCEPT"] = "true"

    (xtrain, ytrain) = FashionMNIST.traindata(Float32)
    (xtest, ytest) = FashionMNIST.testdata(Float32)

    # adding color channel dimension for Conv function (though images are B&W)
    xtrain = reshape(xtrain, size(xtrain)[1:2]..., 1, size(xtrain, ndims(xtrain)))
    xtest = reshape(xtest, size(xtest)[1:2]..., 1, size(xtest, ndims(xtest)))

    # encoding labels with onehot schema
    ytrain = onehotbatch(ytrain, sort(unique(ytrain)))
    ytest = onehotbatch(ytest, sort(unique(ytest)))

    # loading data into mini-batches
    trainloader = DataLoader((xtrain, ytrain); batchsize=args.batchsize, shuffle=true)
    testloader = DataLoader((xtest, ytest); batchsize=args.batchsize)

    trainloader, testloader
end

# Model is a CNN based on the LeNet5 architecture (Yann LeCun et al., 1998)
model = Chain(
    # First convolution step on an initially 28×28×1 image
    Conv((5, 5), 1 => 6, relu; pad=SamePad()),
    MaxPool((2,2)),

    # Second convolution step; image is 14×14×6 from the first step
    Conv((5, 5), 6 => 16, relu; pad=SamePad()),
    MaxPool((2, 2)),

    # After convolutions & maxpools, our dims are 7×7×16.
    # Flattening our set of images to (7*7*16)×N:
    flatten,

    # Two fully connected layers followed by softmax for nice probabilities:
    Dense(7*7*16, 84, relu),
    Dense(84, 10),
    softmax
)

# loss of a single batch
loss(ŷ, y) = logitcrossentropy(ŷ, y)

function loss_accuracy(loader, model, device)
    """Loss & Accuracy across all batches"""
    # Loss: mean logitcrossentropy
    # Accuracy: mean(onecold(ŷ) .== onecold(y))
    losstot = Float32(0)
    acc = Float32(0)
    ntot = UInt64(0)

    for (x′, y′) ∈ loader
        x, y = device(x′), device(y′)
        ŷ = model(x)
        losstot += loss(ŷ, y) * size(x, ndims(x))
        acc += sum(onecold(ŷ |> cpu) .== onecold(y |> cpu))
        ntot += size(x, ndims(x))
    end

    lossval = round(losstot/ntot, digits=4)
    accval = round(100acc/ntot, digits=4)

    lossval, accval
end

function train(; kws...)
    """Training the model"""
    args = Args(; kws...)

    device = (args.usecuda && CUDA.functional()) ? gpu : cpu

    m = device(model)

    ps = params(m)

    trainloader, testloader = get_data(args)

    opt = ADAM(args.η)

    # Report loss & accuracy mostly so we know the training is working
    function report(epoch)
        train = loss_accuracy(trainloader, m, device)
        test = loss_accuracy(testloader, m, device)
        @info "Epoch $epoch\n\t• Train Loss/Acc: $train\n\t• Test Loss/Acc: $test"
    end

    @info "Training started."
    report(0)
    for epoch ∈ 1:args.epochs
        # Each batch in the loader
        for (x′, y′) ∈ trainloader
            x, y = device(x′), device(y′)
            gs = gradient(ps) do
                loss(m(x), y)
            end
            Flux.Optimise.update!(opt, ps, gs)
        end
        # Logging
        epoch % args.infotime == 0 && report(epoch)
    end

    # Saving model
    if args.savemodel
        !ispath(args.savepath) && mkpath(args.savepath)
        modelpath = joinpath(args.savepath, "model_$(Dates.today()).bson")
        let model = cpu(m)
            BSON.@save modelpath model
        end
        @info "Model saved in $modelpath."
    end
    return cpu(m)
end

if abspath(PROGRAM_FILE) == @__FILE__
    cd(@__DIR__)
    m = @time train()
end


mutable struct DemographicParity <: Measure
    C::Int
    A::Array # Adp Matrix/array which is on the LHS of equation
    # This matrix A has been kept in the struct for efficiency in case of multiple calls on a single dataset
    # Add a constructor for zero matrix here
    DemographicParity() = new(0, [])
end

function (dp::DemographicParity)(ft::FairTensor{C}) where C
    if dp.C==0
        # Here matrix A of DemographicParity will be initialised as it wasn't previously initialised
        dp.C = C
        dp.A = zeros(Int, C, 8)
        Nc = sum(ft[C, :, :])
        for i in 1:C-1
            Ni = sum(ft[i, :, :])
            dp.A[i, 1] = Ni
            dp.A[i, 3] = Ni
            dp.A[i, 5] = -Nc
            dp.A[i, 7] = -Nc
        end
    end
    z = reshape(ft.mat, C, 4)
    z_c = reshape(repeat(z[C, :], C), 4, C)
    z = hcat(z, transpose(z_c))
    return all(dp.A*transpose(z) .== 0)
end


### A Pluto.jl notebook ###
# v0.12.20

using Markdown
using InteractiveUtils

# ╔═╡ ed395a10-6eef-11eb-2fa4-dfcefc661d71
using GMT

# ╔═╡ 6e509b82-6f2d-11eb-06a5-8f0c95ad5ecb
md"# Spirals
This started when Kristoff wanted to pain patterns on maps depicting sandy soils.
See the all story in this [Forum discussion](https://forum.generic-mapping-tools.org/t/how-to-generate-random-coordinates-for-pattern-fill/1322/19)
"

# ╔═╡ c95d34e0-6eef-11eb-0d6b-050d0a2904eb
md"## Archimedean spiral
From https://en.wikipedia.org/wiki/Archimedean_spiral
"

# ╔═╡ 0254aed0-6ef1-11eb-1893-55b56cbc6076
md"The begin end of this block **are NOT** the GMT's modern mode *gmt begin ... gmt end*. They are forced by the Nootebook to have more than one command per cell."

# ╔═╡ 590d7540-6ef1-11eb-2db5-316ba4efd866
begin
	# Play arround with these parameters

	T = 1;
	omega = omega = 2pi / T;
	v = 0.2;
	t = 0:0.01:5pi;
	x = v.*t .* cos.(omega .* t);
	y = v.*t .* sin.(omega .* t);
end;

# ╔═╡ 2d09b770-6ef0-11eb-3f50-0388a2cb1d46
plot(x, y, aspect=:equal, show=true)

# ╔═╡ 08699a70-6efa-11eb-1a17-1d1a287490a8
md"## Fermati spiral"

# ╔═╡ 8457d480-6efa-11eb-3a2e-f137efb6c1e8
teta = 0:0.01:5pi;

# ╔═╡ a6f62d10-6efb-11eb-3004-4dbc1a5fe7fd
xf = sqrt.(teta) .* cos.(teta);

# ╔═╡ a6f7b3b0-6efb-11eb-2004-8b152d668695
yf = sqrt.(teta) .* sin.(teta);

# ╔═╡ a6f87700-6efb-11eb-2443-3b78c63192b9
plot(xf,yf, aspect=:equal, show=true)

# ╔═╡ 04a8cc70-6efb-11eb-3aea-df71f7e7b780
md"## Sunflower
From [This FEX contribution](https://www.mathworks.com/matlabcentral/fileexchange/10796-model-a-sunflower-with-the-golden-ratio). The author here wanted to reflect the fact that on a sunflower the seeds close to the center are smaller and have a higher density.
"

# ╔═╡ 1842f4e0-6efb-11eb-17dd-c74c91e5792f
phi = (sqrt(5)-1)/2;

# ╔═╡ 1c958620-6efb-11eb-35f9-d51c907524fe
n = 2618;

# ╔═╡ 38c2cfb0-6efb-11eb-1daf-c1883c2b8443
rho = (2:n-1) .^ phi;

# ╔═╡ 802977a0-6efb-11eb-1f34-997996793d9e
theta = (2:n-1)*2pi*phi;

# ╔═╡ 8c5ca2e0-6efb-11eb-2e5b-3fe95be4a166
scatter(rho .* cos.(theta), rho .* sin.(theta), marker=:point, aspect=:equal, show=true)

# ╔═╡ d5e2e7f0-6f26-11eb-3bc0-2bb0532670ff
md"## Another Sunflower
This one was reversed from the javascript in [this page](https://github.com/jacquerie/sunflower/blob/gh-pages/js/application.js), which follows the original work of Helmut Vogel in [A better Way to Construct the Sunflower Head](http://dx.doi.org/10.1016%2F0025-5564%2879%2990080-4), where he proposed that spiral branches of seeds in a sunflower head are added from the center at an angle of 137.5∘ from the preceding one.

This time we will also color the seed points in function of *r*, the distance to the center and pain with a dark background.
"

# ╔═╡ 3ed0c890-6f27-11eb-0395-d309ad06bd06
begin
	angle = 137.5;	# Play with this angle between [137.0 138.0]. Amazing the effect, no?
	alfa = 2pi * angle / 360;
	n_seeds = 1500;
	seeds = 0:n_seeds;
	r = sqrt.(seeds);
	ϕ = alfa * seeds;
	C = makecpt(range=(1,sqrt(n_seeds),1), cmap=:buda);	# Color map to paint the seeds
	scatter(r .* cos.(ϕ), r .* sin.(ϕ), marker=:point, cmap=C, zcolor=r,
	#scatter(r, ϕ, proj=:polar, marker=:point, cmap=C, zcolor=r,
		frame=(fill=20,), aspect=:equal, show=true)
end

# ╔═╡ Cell order:
# ╟─6e509b82-6f2d-11eb-06a5-8f0c95ad5ecb
# ╟─c95d34e0-6eef-11eb-0d6b-050d0a2904eb
# ╠═ed395a10-6eef-11eb-2fa4-dfcefc661d71
# ╟─0254aed0-6ef1-11eb-1893-55b56cbc6076
# ╠═590d7540-6ef1-11eb-2db5-316ba4efd866
# ╠═2d09b770-6ef0-11eb-3f50-0388a2cb1d46
# ╠═08699a70-6efa-11eb-1a17-1d1a287490a8
# ╠═8457d480-6efa-11eb-3a2e-f137efb6c1e8
# ╠═a6f62d10-6efb-11eb-3004-4dbc1a5fe7fd
# ╠═a6f7b3b0-6efb-11eb-2004-8b152d668695
# ╠═a6f87700-6efb-11eb-2443-3b78c63192b9
# ╟─04a8cc70-6efb-11eb-3aea-df71f7e7b780
# ╠═1842f4e0-6efb-11eb-17dd-c74c91e5792f
# ╠═1c958620-6efb-11eb-35f9-d51c907524fe
# ╠═38c2cfb0-6efb-11eb-1daf-c1883c2b8443
# ╠═802977a0-6efb-11eb-1f34-997996793d9e
# ╠═8c5ca2e0-6efb-11eb-2e5b-3fe95be4a166
# ╟─d5e2e7f0-6f26-11eb-3bc0-2bb0532670ff
# ╠═3ed0c890-6f27-11eb-0395-d309ad06bd06


module Cryptography

export  factors, fast_exponentiation, odd,
        next_prime, relative_prime,
        integer_to_base64_string, base64_string_to_integer,
        integer_to_uint8_array, uint8_array_to_integer

include("factors.jl")
include("fast_exponentiation.jl")
include("next_prime.jl")
include("rsa.jl")

end


using Convex

m = 4; n = 5
A = randn(m,n); b = randn(m, 1)


module Husimi
export husimiDistribution, husimi, coherentState
using BoundaryIntegralMethod
using DistributedArrays

"""
Represents the coherent state along the boundary parameterized by

p = sin(ϕ) in [-1,1] -> Angle of incidence on boundary reflection
q in [0,length(boundary)] -> Arc length along boundary state is centered at.
ρ -> Density of coherent function, small ρ represents low position uncertainty with
     high momentum uncertainty, large ρ represents the opposite tradeoff. Values around
     1 are usually a good balance.
kn -> k*ni
L -> length of boundary
qp -> q′ the point the state function is evaluated at
mTerms -> Affects number of repetitions of the function along the boundary (similar to m
    quantum number)
"""
function coherentState(p, q, ρ, kn, L, qp; mTerms=5)
    prefactor = (kn/(π*ρ^2))^(1/4)
    sumTerms = [exp(im*p*kn*(qp-q+m*L)-kn/(2ρ^2)*(qp-q+m*L)^2) for m in -mTerms:mTerms]
    prefactor*sum(sumTerms)
end

"""
Calculate the Husimi function from the boundary field data (generally normal derivative)

p = sin(ϕ) in [-1,1] -> Angle of incidence on boundary reflection
q in [0,length(boundary)] -> Arc length along boundary state is centered at.
ρ -> Density of coherent function, small ρ represents low position uncertainty with
     high momentum uncertainty, large ρ represents the opposite tradeoff. Values around
     1 are usually a good balance.
k -> Resonant wavenumber used to calculate field
boundary -> Boundary in question
∂νΨ -> Normal derivative of wavefunction on boundary (NOTE: this is the second part of
       xf as returned from the BoundaryIntegralMethod.residualInverseIteration routine).
       In practice, besides normalization effects the field value (first half of xf) can
       also work well.
qs -> arcLength traversed (q coordinate) at same points as for ∂νΨ
dqs -> local dq/dt at the same points as for ∂νΨ
mTerms -> Affects number of repetitions of the function along the boundary (similar to m
    quantum number)
"""
function husimi(p::Real, q::Real, ρ::Real, k::Real, L::Real, boundary::Boundary, ∂νΨ::Array{Complex{Float64}}, qs::Array{Float64}, dqs::Array{Float64}; mTerms=5)
    @assert length(∂νΨ) == length(qs) == length(dqs)
    kn = k*nInt(boundary)
    sumTerms = [dqs[i]*∂νΨ[i]*coherentState(p, q, ρ, kn, L, qs[i]; mTerms=mTerms) for i in 1:length(∂νΨ)]
    #normalization is not correct, but does it matter?
    1/(2π*abs(kn))*abs(sum(sumTerms))^2
end

"""
Calculate the Husimi distribution for the given boundary, resonant k (kf) and calculated
boundary field (xf), using the momentum resolution specified by pStep.

Returns tuple (qs, ps, hVals)
"""
function husimiDistribution(kf, xf, boundary::Boundary; mTerms=5, pStep=nothing, ρ=1, useField=false)
    ∂νΨ = xf[div(length(xf),2)+1:end]
    if (useField) # Force use of boundary field instead of normal derivative
        ∂νΨ = xf[1:div(length(xf),2)]
    end
    steps = length(∂νΨ)
    stepRange = 0:steps-1
    dqs = arcLen.(Ref(boundary), 2π*stepRange/steps, steps)
    qs = cumsum(vcat([0.0], dqs))
    L = qs[end]
    qs = qs[1:end-1]
    if pStep == nothing
        pStep = 1/steps
    end
    ps = -1:pStep:1
    hVals = @DArray [husimi(p, q, ρ, real(kf), L, boundary, ∂νΨ, qs, dqs; mTerms=mTerms) for p in ps, q in qs]
    (qs, ps, BoundaryIntegralMethod.convertAndClose(hVals))
end
end


module LogitNormals

using Distributions, DistributionFits
using StaticArrays
using Optim
import StatsFuns: logit, logistic, normcdf

# for extension
import Distributions: mean, var, mode
import StatsBase: fit
import DistributionFits: fit_mode_quantile, fit_mean_quantile

export fit_mode_flat

include("logitnormal.jl")
include("util.jl")
include("logitnormDistributions.jl")

end


# =======================================================================
# 2KP

using vOptSpecific

# ==========================================
# instance

id = load2UKP("../examples/2KP500-1A.DAT")

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

z1, z2, w, solutions = vSolve(id)

for i = 1:length(z1)
    println("(" , z1[i], " | ", z2[i], ") : ", solutions[i,:])
end


using NanoclusterModeler
using DifferentialEquations
using ODEInterfaceDiffEq
using Queryverse
using DataFrames
using JLD2, FileIO

# Set parameters for model
imax = 68 # Maximum size cluster considered in model
prefactor = 2.08 # Scaling factor in Ns
Ns = NanoclusterModeler.Ns(imax, prefactor) # Number of surface sites on the cluster
Mplus = 0.05 # Concentration in mM
L     = 6.00 # Concentration in mM
c0 = NanoclusterModeler.c0(imax, Ns, Mplus, L) # Initialize 2D matrix containing intial concentrations of M+ and L
kpij = NanoclusterModeler.kplusij(imax, Ns) # Define 2D matrix containing fraction of free sites on surface of NCs
kmij = NanoclusterModeler.kminusij(imax, Ns) # Define 2D matrix containing fraction of occupied sites on surface of NCs
kp  = 1.0e+3
kb  = 1.0e+5
kub = 1.0e-7
kn  = 1.0e+1
kg1 = 1.0e+4
kd1 = 1.0e-9
kg2 = 1.0e+4
kd2 = 1.0e-9
ka  = 1.0e-3
ke  = 1.0e+3
kc  = 1.0e+3
odes = NanoclusterModeler.odes!

# Set file prefix for output
file = "kp1e3_kb1e5_kn1e1_kg1e4_ka1e-3_ke1e3_kc1e3_M+005_Ns208"

# Time is seconds
tspan = (0.0, 1000000.0)

# Parameters being passed to ode function
# The order in which these are stored is important
p   = (imax, Ns, kpij, kmij,
    kp, kb, kub, kn, kg1, kd1, kg2, kd2, ka, ke, kc)

# Define ode problem and solve
prob = ODEProblem(odes, c0, tspan, p)
sol  = solve(prob, radau(), reltol=1e-10, abstol=1e-10)

# Post processing of data
rows, columns, tlast = size(sol)
t = sol.t

# If solution is negative, it is out of the tolerance. Set to zero.
for t = 1:tlast
    for j = 1:columns
        for i = 1:rows
            if sol[i,j,t] < 0.0
                sol[i,j,t] = 0.0
            end
        end
    end
end

Mplus = sol[1,1,:]
M     = sol[3,2,:]
L     = sol[2,3,:]
ML    = sol[3,3,:]
C_20  = sol[4,2,:]
C_21  = sol[4,3,:]
C_22  = sol[4,4,:]
C_23  = sol[4,5,:]
C_2   = hcat(C_20, C_21, C_22, C_23)

C_tot = zeros(Float64, tlast)
C_bar = zeros(Float64, imax, tlast)
for t = 1:tlast
    C_tot[t]   = sum(sol[3:rows-1, 2:columns-1, t])
    C_bar[:,t] = sum(sol[3:rows-1, 2:columns-1, t], dims=2)
end

file_time = string(file, "_time.csv")
df_t = DataFrame([sol.t, Mplus, M, L, ML, C_20, C_21, C_22, C_23, C_tot])
rename!(df_t, [:t, :Mplus, :M, :L, :ML, :C_20, :C_21, :C_22, :C_23, :C_tot])
save(file_time, df_t)

file_Cbar = string(file, "_cbar.csv")
C_bar_T = transpose(C_bar)
df_Cbar = DataFrame(C_bar_T)
save(file_Cbar, df_Cbar)

P_j = zeros(Float64, imax, Ns[imax]+1, tlast)
for t = 1:tlast
    for j = 2:columns
        for i = 4:rows
            if sol[i,j,t] >= 1e-10
                P_j[i-2,j-1,t] = sol[i,j,t]/C_bar[i-2,t]
            end
        end
    end
end

file_P2D_t = string(file, "_prob_j_t.jld2")
@save file_P2D_t P_j

file_sol = string(file, "_full_sol.jld2")
@save file_sol sol


using ReverseDiff
using Distributions
include("char_loader.jl")

loader = CharLoader("../data/input.txt")

# number of unique chars in the dataset
BATCH_SIZE = 25
INPUT_SIZE = length(loader.chars)
OUTPUT_SIZE = length(loader.chars)
STATE_SIZE = 100

Wh = randn(T, state_size, state_size) * 0.01
Wx = randn(T, state_size, input_size) * 0.01
Wo = randn(T, output_size, state_size) * 0.01

ReverseDiff.@forward minus_log(x) = -log(x)
softmax(x) = (exp_x = exp.(x); exp_x ./ sum(exp_x, 1))
cross_entropy_loss(x, y) = mean(sum(y .* minus_log.(x), 1))

function train_rnn(cell::AbstractCell{T}, X, Y)
    loss = T(0)
    n = size(X, 2)
    ht = zeros(1, state_size)
    for t=1:n
        ot, ht = rnn_cell(Wx, Wh, Wo, X[t, :], ht)
        y = Y[t]
        loss += minus_log.(softmax(ot))[y]
    end
    return loss / n
end

function predict_neural_net(Wx, Wh, Wo, X)
    preds = Int[]
    T = size(X, 1)
    ht = zeros(1, state_size)
    for t=1:T
        xt = view(X, t, :)
        ot, ht = rnn_cell(Wx, Wh, Wo, xt, ht)
        push!(preds, indmax(ot))
    end
    return preds
end

function generate_text(T)
    id0 = rand(1:input_size, 1)[1]
    xt = zeros(1, input_size)
    xt[id0] = 1
    ht = zeros(1, state_size)
    ids = Int[id0]

    for t=1:T
        ot, ht = rnn_cell(Wx, Wh, Wo, xt, ht)
        probs = softmax(ot)
        ct = Categorical(probs[:])
        pred = rand(ct)
        xt[:] = 0.
        xt[pred] = 1.
        push!(ids, pred)
    end
    return ids
end

function train_model(iters, Wx, Wh, Wo; α=1e-2)
    for i=1:iters
        x, y = next_batch(loader, batch_size)
        dWx, dWh, dWo = nn_backward(Wx, Wh, Wo, x, y)
        # Vanilla SGD
        Wx += α * dWx
        Wh += α * dWh
        Wo += α * dWo

        if i % 100 == 0
            println("Loop iter $i")
            println("Generated text ...")
            ids = generate_text(200)
            for i in ids
                print(loader.id2char[i])
            end
            println("\n")
        end
    end
end

accuracy(ypred, ytrue) = mean(ypred .== ytrue)


using BenchmarkTools, CartesianGrids

BenchmarkTools.DEFAULT_PARAMETERS.gcsample = true

SUITE = BenchmarkGroup()
SUITE["field ops"] = BenchmarkGroup()
SUITE["ddf"] = BenchmarkGroup()
SUITE["regularization"] = BenchmarkGroup()

include("setupproblem.jl")

w .= randn(size(w));
f .= randn(size(f));

SUITE["field ops"]["inverse Laplacian"] = @benchmarkable L\w;
SUITE["field ops"]["curl of dual nodes"] = @benchmarkable curl!(q,w);
SUITE["field ops"]["divergence of primal edges"] = @benchmarkable divergence!(p,q);

for kernel in (:Roma,:Yang3,:M4prime)
  name = string(kernel)
  @eval SUITE["ddf"][$name] = @benchmarkable evaluate_ddf(DDF(ddftype=CartesianGrids.$kernel))
end

SUITE["regularization"]["create node regularization matrix"] = @benchmarkable RegularizationMatrix(reg,f,w);
SUITE["regularization"]["create node interpolation matrix"] = @benchmarkable InterpolationMatrix(reg,w,f);

Rn = RegularizationMatrix(reg,f,w);
En = InterpolationMatrix(reg,w,f);
SUITE["regularization"]["evaluate node regularization"] = @benchmarkable Rn*f;
SUITE["regularization"]["evaluate node interpolation"] = @benchmarkable En*w;

SUITE["regularization"]["create edge regularization matrix"] = @benchmarkable RegularizationMatrix(reg,τ,q);
SUITE["regularization"]["create edge interpolation matrix"] = @benchmarkable InterpolationMatrix(reg,q,τ);

Re = RegularizationMatrix(reg,τ,q);
Ee = InterpolationMatrix(reg,q,τ);
SUITE["regularization"]["evaluate edge regularization"] = @benchmarkable Re*τ;
SUITE["regularization"]["evaluate edge interpolation"] = @benchmarkable Ee*q;

run(SUITE)


"""
    x=cvp(y,R::UpperTriangular)

Solve the problem `argmin_x ||y-Rx||` for integer x using the technique from
the paper below. The input vector `y` is of length `n`, with upper
triangular `R` of dimension `n` by `n`, and the returned vector `x` of
length `n`.

    x=cvp(y,R,infinite=Val(true),Umin=-1,Umax=-Umin,nxMax=Int(ceil(log2(n)*1e6)))

If `infinite==Val(true)` then we search the (infinite) lattice, otherwise we
search integers in `[Umin,Umax]`. When `nxMax` is included, it gives a
maximum number of steps to take, otherwise a heuristic value is used.
Note that `cvp` does not handle complex numbers.

Follows "Faster Recursions in Sphere Decoding" Arash Ghasemmehdi, Erik
Agrell, IEEE Transactions on Information Theory, vol 57, issue 6 , June 2011.

# Examples
```jldoctest
julia> H=[1 2; 3 4]; Q,R=qr(H); uhat = cvp(Q'*[0,2],UpperTriangular(R))
2-element Vector{Float64}:
  2.0
 -1.0

julia> uhat = cvp(Q'*[0,2],UpperTriangular(R),Val(false),0,100)
2-element Vector{Float64}:
 1.0
 0.0

julia> n=100;H=randn(n,n); Q,Rt=qr(H); R=UpperTriangular(Rt);

julia> u=Int.(rand(0:1e10,n));y=H*u+rand(n)/100;

julia> uhat=cvp(Q'*y,R); sum(abs.(u-uhat))
0.0

julia> n=500;H=randn(n,n); Q,Rt=qr(H); R=UpperTriangular(Rt);

julia> u=Int.(rand([-1,1],n));y=H*u+rand(n)/100;

julia> uhat=cvp(Q'*y,R,Val(false),-1,1); sum(abs.(u-uhat))
0.0
```
"""
function cvp(r::AbstractArray{Td,1},G::UpperTriangular{Td,Array{Td,2}},
             infinite=Val(true),Umin=-1,Umax=-Umin,
             nxMax=typemax(Int)) where {Td<:Number}

    roundGA(x) = roundFinite(x,Td(Umin),Td(Umax))
    n = length(r)
    C = Inf
    i = n+1
    d = ones(Int,n)*n
    λ = zeros(n+1)
    F = zeros(Td,n,n)
    F[:,n] = r
    p = zeros(n)
    u = NaN*zeros(Int,n)
    û = NaN*zeros(Int,n)
    Δ = zeros(Int,n)

    if nxMax == typemax(Int)
        nxMax=Int(ceil(log2(n)*1e6)) # heuristic
    end
    nx=1

    begin
        @label LOOP

        nx+=1
        while λ[i]<C
            if i != 1
                i-=1
                for j=d[i]:-1:i+1
                    F[i,j-1] = F[i,j] - G[i,j]*u[j]
                end
                p[i] = F[i,i]/G[i,i]
                if infinite==Val(true)
                    u[i] = round(p[i])
                else
                    u[i] = roundGA(p[i])
                end
                y = (p[i]-u[i])*G[i,i]
                Δ[i] = sgns(y)
                λ[i] = λ[i+1]+y*y
            else
                û .= u
                C = λ[1]
            end
        end
        m = i

        while λ[i]≥C
            if i==n || nx>nxMax
                if nx>nxMax
                    @warn("terminated search after $(nx) iterations.")
                end
                return û
            else
                i+=1
                if infinite ≠ Val(true)
                    y = typemax(Td)
                end
                u[i]+=Δ[i]
                Δ[i]=-Δ[i]-sgns(Δ[i])
                if infinite ≠ Val(true)
                    if Umin ≤ u[i] ≤ Umax
                        y = (p[i]-u[i])*G[i,i]
                    else
                        u[i]+=Δ[i]
                        Δ[i]=-Δ[i]-sgns(Δ[i])
                        if Umin ≤ u[i] ≤ Umax
                            y = (p[i]-u[i])*G[i,i]
                        end
                    end
                else
                    y = (p[i]-u[i])*G[i,i]
                end
                λ[i] = λ[i+1]+y*y
            end
        end
        d[m:i-1] .= i
        for j=m-1:-1:1
            if d[j]<i
                d[j]=i
            else
                @goto LOOP
            end
        end
        @goto LOOP
    end
end

sgns(x) = x<=0 ? -one(x) : one(x)

function roundFinite(x,Umin,Umax)
    y=round(x)
    if x<Umin
        return Umin
    elseif x>Umax
        return Umax
    else
        return y
    end
end

"""
    x=svp(B)

Find the shortest basis vector `b` for the lattice formed by the matrix
`B`. This solves the 'shortest vector problem' (SVP). Note that `svp`
does not handle complex numbers.

Follows "Algorithm SHORTESTVECTOR(G)" from "Closest Point Search in
Lattices" by Erik Agrell, Thomas Eriksson, Alexander Vardy, and
Kenneth Zeger in IEEE Transactions on Information Theory, vol. 48, no. 8,
August 2002.

# Examples
```jldoctest
julia> H=[1 2; 3 4]; svp(H)
2-element Vector{Int64}:
 -1
 -1

julia> H = [1 0 0 0;   0 1 0 0;   208 175 663 0;     651 479 0  663];

julia> svp(H)
4-element Vector{Int64}:
  16
 -19
   3
 -11

```
"""
function svp(G::AbstractArray{Td,2}) where {Td<:Number}

    m,n = size(G)
    if n==1 return G[:,1];    end

    G2,_ = lll(G)
    # if qr gave positive values on diagonal we'd use  Q,G3 = qr(G2)
    G3 = cholesky(Hermitian(G2'*G2)).U
    H3 = inv(G3)
    #Q = G2*H3
    uhat = decodeSVPAgrell(H3)
    yhat = G2*uhat # the "shortest vector"
    return yhat
end

"""
    z=svpu(B)

Find the vector of integers z such that B*z is the shortest vector
in the lattice with basis B.

# Examples
```jldoctest
julia> H=[1 2; 3 4]; s=svp(H); u=LLLplus.svpu(H); [u s H*u]
2×3 Matrix{Int64}:
  1  -1  -1
 -1  -1  -1

```
"""
function svpu(G::AbstractArray{Td,2}) where {Td<:Number}
    m,n = size(G)
    if n==1 return [1]; end
    G2,T = lll(G)
    # if qr gave positive values on diagonal we'd use  Q,G3 = qr(G2)
    G3 = cholesky(Hermitian(G2'*G2)).U
    H3 = inv(G3)
    uhat = decodeSVPAgrell(H3)
    return T*uhat
end

"""
    x=decodeSVPAgrell(x,H)

Internal function.

Follows "Algorithm DECODE(G)" with "SHORTESTVECTOR" changes from the paper
"Closest Point Search in Lattices" by Erik Agrell, Thomas Eriksson,
Alexander Vardy, and Kenneth Zeger in IEEE Transactions on Information
Theory, vol. 48, no. 8, August 2002.

# Examples
```jldoctest
julia> H = inv([1.0 2; 0 4]);

julia> uhat = LLLplus.decodeSVPAgrell(H)
2-element Vector{Int64}:
 -1
  0

```
"""
function decodeSVPAgrell(H::AbstractArray{Td,2}) where {Td<:Number}

    n = size(H,1)
    bestdist=Inf
    k=n
    dist = zeros(n)
    e = zeros(n,n)
#    e[:,k] = H*x
    u = zeros(Int,n)
    û = zeros(Int,n)
    u[k] = round(e[k,k])
    y = (e[k,k]-u[k])/H[k,k]
    Δ = NaN*zeros(n)
    Δ[k] = sgns(y)
    begin
        @label LOOP
        newdist = dist[k]+y*y
        if newdist<bestdist
            if k!=1
                e[1:k-1,k-1]=e[1:k-1,k]-y*H[1:k-1,k]
                k -=1
                dist[k]=newdist
                u[k] = round(e[k,k])
                y = (e[k,k]-u[k])/H[k,k]
                Δ[k] = sgns(y)
            else
                if !(newdist≈0.0)
                    û[:] = u[:]
                    bestdist=newdist
                    k+=1
                end
                # û[:] = u[:]
                # bestdist=newdist
                # k+=1
                u[k] += Δ[k]
                y = (e[k,k]-u[k])/H[k,k]
                Δ[k] = -Δ[k] - sgns(Δ[k])
            end
        else
            if k==n
                return û
            else
                k+=1
                u[k]+= Δ[k]
                y = (e[k,k]-u[k])/H[k,k]
                Δ[k] = -Δ[k] - sgns(Δ[k])
            end
        end
        @goto LOOP
    end
end


#############################
##   triQgen: Generate Q operator which represents integration with regards to y
##               on JacobiTriangle().
####
function triQgen(N::Integer)
    # Generates Q operator as a block banded matrix.
    # Q*f(x,y) can be used to integrate f from 0 to 1-u via Jacobi polynomials on triangle.
    Q = BlockBandedMatrix(Zeros(N, sum(1:N)), Ones{Int}(N), 1:N, (0,0))
        for n=1:N
            view(Q, Block(n,n))[1,1] = 1.
        end
    return Q
end
#############################
##   triEygenP01: Extension operator for y from Jacobi P^(0,1) basis to canonical Jacobi triangle
####
function triEygenP01(N::Integer)
    Ey = BlockBandedMatrix(Zeros(sum(1:N),N), 1:N,Ones{Int}(N), (0,0))
        for n=1:N
            for j=1:n
                view(Ey, Block(n,n))[j,1] = (-1)^n*(-1)^j*(2*j-1)/n
            end
        end
    return Ey
end
#############################
##   triQEygenP01: This directly generates Q*EyP01 as a diagonal matrix
##                 without first computing the operators Q and RyP01
####
function triQEygenP01(N::Integer)
    QEy=BandedMatrix{Float64}(undef, (N, N), (0,0))
    for n=1:N
        QEy[n,n]=(-1)^(n+1)/n
    end
    return QEy
end
#############################
##   WLoweringP01P00: Weighted lowering operator which also multiplies by (1-x) from
##                      Jacobi(0,1) to Jacobi(0,0)
function WLoweringP01P00(N::Integer)
    WLow=I+BandedMatrix{Float64}(-0.5*Ones(N,N), (1,0))
    return WLow
end
#############################
##   reflectPabtoPba: Reflects coefficient vector from P^(a,b)(1-x) basis into P^(b,a)(x) basis
####
function reflectPabtoPba(N::Integer)
    Refl=BandedMatrix{Float64}(undef, (N, N), (0,0))
    for n=1:N
        Refl[n,n]=(-1)^(n+1)
    end
    return Refl
end
#############################################################
## The functions below are not in use outside of testing purposes
#############################
##   WLoweringP01Pm11: Weighted lowering operator which also multiplies by (1-x) from
##                      Jacobi(0,1) to Jacobi(1,-1)
function WLoweringP01Pm11(N::Integer)
    WLow=BandedMatrix{Float64}(undef, (N, N), (1,0))
    for c=1:N-1
        WLow[c,c]=(1-c)/(2*c)
        WLow[c+1,c]=-0.5
    end
    WLow[N,N]=(1-N)/(2*N)
    return WLow
end
##   ReflectorP01: Reflects coefficient vector from P^(1,0)(x) basis into P^(1,0)(1-t) basis
##      avoid use because it is upper triangular
####
function ReflectorP01(N::Integer)
    Reflector=BandedMatrix{Float64}(undef, (N, N), (0,N))
    for r=1:N
        for col=r+1:N
            Reflector[r,col]=(-1)^(r+1)*2*r/col
        end
            Reflector[r,r]=(-1)^(r+1)
    end
    return Reflector
end
#############################
##   triEygenP00: Extension operator for y from Legendre P^(0,0) basis to canonical Jacobi triangle
####
function triEygenP00(N::Integer)
    Ey = BlockBandedMatrix(Zeros(sum(1:N),N), 1:N,Ones{Int}(N), (0,1))
        for n=1:N
            for j=1:n-1
                view(Ey, Block(n-1,n))[j,1] = (-1)^n*(-1)^j*(2*j-1)/(2*n-1)
                view(Ey, Block(n,n))[j,1] = (-1)^n*(-1)^j*(2*j-1)/(2*n-1)
            end
            view(Ey, Block(n,n))[n,1] = 1.0
        end
    return Ey
end
#############################
##   triEygenP11: Extension operator for y from Jacobi P^(1,1) basis into T = JacobiTriangle(1,0,0,Triangle([0.0,1.0],[0.0,0.0],[1.0,1.0]))
####
function triEygenP11(N::Integer)
    Ey = BlockBandedMatrix(Zeros(sum(1:N),N), 1:N,Ones{Int}(N), (0,0))
        for n=1:N
                view(Ey, Block(n,n))[1,1] = (-1)^(n+1)
        end
    return Ey
end
#############################
##   triExgenP10: Extension operator for x from Jacobi P^(1,0) basis to canonical Jacobi triangle
####
function triExgenP01(N::Integer)
    Ex = BlockBandedMatrix(Zeros(sum(1:N), N), 1:N, Ones{Int}(N), (0,0))
        for n=1:N
            view(Ex, Block(n,n))[1,1] = 1
        end
    return Ex
end


########################################################
####             equation prediction                ####
########################################################

"""
    sfmodel_predict(@eq(eq_name), data::DataFrame)

Return predicted values of an equation (`eq_name`) of a stochastic frontier
model specified and estimated by `sfmodel_spec()` and `sfmodel_fit()` .

# Arguments:
- `@eq(eq_name)`: where `eq_name` is the name of the equation to which the
  value is to be predicted. A SF model has various equations, and different
  SF models have different sets of equations. Eligible `eq_name`s follow
  those specified in `sfmodel_spec()`. The `eq_name` may be specified using
  unicodes or their alias names; viz., `@eq(σᵤ²)` is the same as
  `@eq(sigma_u_2)`. Also, `log_σᵤ²` = `log_sigma_u_2`, `σᵥ²`= `sigma_v_2`,
  `log_σᵥ²` = `log_sigma_v_2`, `σₐ²`= `sigma_a_2`, `log_σₐ²` = `log_sigma_a_2`,
  and `μ` = `mu`.
- `data::DataFrame`: the DataFrame used in `sfmodel_fit()`. The DataFrame is
  required only when Method 1 is used in specifying `sfmodel_spec()`. If
  Method 2 is used instead, do not need this argument. See the help of
  `sfmodel_spec()` for the information on Method 1 and Method 2.

# Remarks:
The predicted value is computed based on the equation's variable list and the
estimated coefficient vector. For instance, if the `frontier` function is a
linear function of variables `X` and coefficient vector `β`,
`sfmodel_predict(@eq(frontier), df)` returns `X*β̂ `. If the variance
function `σᵤ²` is parameterized by an exponential function of `Z` and `δ`
(i.e., `σᵤ² = exp(Zδ)`), `sfmodel_predict(@eq(log_σᵤ²))` returns `Z*δ̂`
and `sfmodel_predict(@eq(σᵤ²))` returns `exp(Z*δ̂)`.

# Examples
```julia
frontier_hat = sfmodel_predict(@eq(frontier), df);
sigma_u_2_hat = sfmodel_predict(@eq(σᵤ²), df);
```
"""
function sfmodel_predict(eq::Vector{Symbol})

    # For Method 2 of `sfmodel_spec()`

    !(_dicM[:hasDF]) || throw("Need to specify DataFrame in `sfmodel_predict()`.")

    _dicM[:transfer] = true
    sfmodel_predict(eq, _dicM[:sdf])
end

function sfmodel_predict(eq::Vector{Symbol},  sfdata::DataFrame)

  # usage：
  # frontier_hat = sfmodel_predict(@eq(frontier), mydata)

  # σᵤ² = sigma_u_2 = exp(log_σᵤ²) = exp(log_sigma_u_2)

  (_dicM[:hasDF] || _dicM[:transfer]) || throw("You provided matrix in `sfmodel_spec()` so you cannot specify a DataFrame in `sfmodel_predict()`. Leave it out.")

  eqname = eq[1]

  takeexp = false

  if eqname == :frontier
      eq_var = :frontier
      eq_coe = :frontier
  elseif eqname == :mu || eqname == :μ
      eq_var = :μ
      eq_coe = :μ
 elseif eqname == :sigma_a_2 || eqname == :σₐ²
      eq_var  = :σₐ²
      eq_coe  = :log_σₐ²
      takeexp = true
  elseif eqname == :sigma_u_2 || eqname == :σᵤ²
      eq_var  = :σᵤ²
      eq_coe  = :log_σᵤ²
      takeexp = true
  elseif eqname == :sigma_v_2 || eqname == :σᵥ²
      eq_var  = :σᵥ²
      eq_coe  = :log_σᵥ²
      takeexp = true
  elseif eqname == :log_sigma_u_2 || eqname == :log_σᵤ²
      eq_var  = :σᵤ²
      eq_coe  = :log_σᵤ²
  elseif eqname == :log_sigma_v_2 || eqname == :log_σᵥ²
      eq_var  = :σᵥ²
      eq_coe  = :log_σᵥ²
  elseif eqname == :gamma
      eq_var  = :gamma
      eq_coe  = :log_gamma
      takeexp = true
  elseif eqname == :log_gamma
      eq_var  = :gamma
      eq_coe  = :log_gamma
  elseif eqname == :hscale
      eq_var  = :hscale
      eq_coe  = :log_hscale
      takeexp = true
  elseif eqname == :log_hscale
      eq_var  = :hscale
      eq_coe  = :log_hscale
  else
      throw("@eq() is not specified correctly")
  end

  #* -- begin processing the data --------

  pvar = Matrix(sfdata[:, _dicM[eq_var]])  # get the variable vector/Matrix

  value = pvar*_eqncoe[eq_coe]

  if takeexp
     value = exp.(value)
  end

  return value
end


# ---
# title: 1409. Queries on a Permutation With Key
# id: problem1409
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Array
# link: <https://leetcode.com/problems/queries-on-a-permutation-with-key/description/>
# hidden: true
# ---
#
# Given the array `queries` of positive integers between `1` and `m`, you have
# to process all `queries[i]` (from `i=0` to `i=queries.length-1`) according to
# the following rules:
#
#   * In the beginning, you have the permutation `P=[1,2,3,...,m]`.
#   * For the current `i`, find the position of `queries[i]` in the permutation `P` ( **indexing from 0** ) and then move this at the beginning of the permutation `P.` Notice that the position of `queries[i]` in `P` is the result for `queries[i]`.
#
# Return an array containing the result for the given `queries`.
#
#
#
# **Example 1:**
#
#
#
#     Input: queries = [3,1,2,1], m = 5
#     Output: [2,1,2,1]
#     Explanation: The queries are processed as follow:
#     For i=0: queries[i]=3, P=[1,2,3,4,5], position of 3 in P is **2** , then we move 3 to the beginning of P resulting in P=[3,1,2,4,5].
#     For i=1: queries[i]=1, P=[3,1,2,4,5], position of 1 in P is **1** , then we move 1 to the beginning of P resulting in P=[1,3,2,4,5].
#     For i=2: queries[i]=2, P=[1,3,2,4,5], position of 2 in P is **2** , then we move 2 to the beginning of P resulting in P=[2,1,3,4,5].
#     For i=3: queries[i]=1, P=[2,1,3,4,5], position of 1 in P is **1** , then we move 1 to the beginning of P resulting in P=[1,2,3,4,5].
#     Therefore, the array containing the result is [2,1,2,1].
#
#
# **Example 2:**
#
#
#
#     Input: queries = [4,1,2,2], m = 4
#     Output: [3,1,2,0]
#
#
# **Example 3:**
#
#
#
#     Input: queries = [7,5,5,8,3], m = 8
#     Output: [6,5,0,7,5]
#
#
#
#
# **Constraints:**
#
#   * `1 <= m <= 10^3`
#   * `1 <= queries.length <= m`
#   * `1 <= queries[i] <= m`
#
#
## @lc code=start
using LeetCode

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