##############################
# TO DO
# 5-2. Dominant Cycle Measured by Zero Crossings of the Band-Pass Filter - Validated against TS up to DC portion
# 8-3. Autocorrelation Periodogram - Validated against TS up to Normalization
# Outstanding
# 9-1 onwards
##############################

using Statistics

@doc """
    SuperSmoother(x::Array{Float64}; n::Int64=10)::Array{Float64}

Super Smoother - Equation 3-3
"""
function SuperSmoother(x::Array{Float64}; n::Int64=10)::Array{Float64}
    @assert n<size(x,1) && n>0 "Argument n out of bounds."
    a = exp(-1.414*3.14159 / n)
    b = 2 * a * cosd(1.414 * 180 / n)
    c2 = b
    c3 = -a * a
    c1 = 1 - c2 - c3
    Super = zeros(size(x,1))
    @inbounds for i = 3:length(x)
        Super[i] = c1 * (x[i] + x[i-1]) / 2 + c2 * Super[i-1] + c3 * Super[i-2]
    end
    return Super
end

@doc """
    Decycler(x::Array{Float64}; n::Int64=60)::Array{Float64}

Decycler - Equation 4-1
"""
function Decycler(x::Array{Float64}; n::Int64=60)::Array{Float64}
    @assert n<size(x,1) && n>0 "Argument n out of bounds."
    #Highpass filter cyclic components whose periods are shorter than “cutoff” bars
    alpha1 = ((cosd(360 / n) + sind(360 / n) - 1)) / (cosd(360 / n))
    Decycle = zeros(size(x,1))
    @inbounds for i in 2:length(x)
        Decycle[i] = (alpha1 / 2)*(x[i] + x[i-1]) + (1- alpha1)*Decycle[i-1]
    end
    return Decycle
end

@doc """
    Decycle_OSC(x::Array{Float64}; LPPeriod::Int64=30, HPPeriod::Int64=60)::Array{Float64}

Decycle Oscillator - Equation 4-2
"""
function Decycle_OSC(x::Array{Float64}; LPPeriod::Int64=30, HPPeriod::Int64=60)::Array{Float64}
    @assert HPPeriod<size(x,1) && HPPeriod>0 "Argument HPPeriod out of bounds."
    alpha1 = (cosd(.707*360 / LPPeriod) + sind(.707*360 / LPPeriod) - 1) / cosd(.707*360 / LPPeriod)
    alpha2 = (cosd(.707*360 / HPPeriod) + sind(.707*360 / HPPeriod) - 1) / cosd(.707*360 / HPPeriod)
    HP1 = zeros(size(x,1))
    HP2 = zeros(size(x,1))
    Decycle_OSC = zeros(size(x,1))
    @inbounds for i in 3:length(x)
        HP1[i] = (1 - alpha1 / 2)*(1 - alpha1 / 2)*(x[i] - 2*x[i-1] + x[i-2]) + 2*(1 - alpha1)*HP1[i-1] - (1 - alpha1)* (1 - alpha1)*HP1[i-2]
        HP2[i] = (1 - alpha2 / 2)*(1 - alpha2 / 2)*(x[i] - 2*x[i-1] +x[i-2]) + 2*(1 - alpha2)*HP2[i-1] - (1 - alpha2)*(1 - alpha2)*HP2[i-2]
    end
    Decycle_OSC .= HP2 .- HP1
    return Decycle_OSC
end

@doc """
    BandPassFilter(x::Array{Float64}; n::Int64=30, bandwidth::Float64=.3)::Array{Float64}

Band Pass Filter - Equation 5-1
"""
function BandPassFilter(x::Array{Float64}; n::Int64=30, bandwidth::Float64=.3)::Array{Float64}
    @assert n<size(x,1) && n>0 "Argument n out of bounds."
    alpha2 = (cosd(.25*bandwidth*360 / n) + sind(.25*bandwidth*360 / n) - 1) / cosd(.25*bandwidth*360 /n)
    beta1 = cosd(360 / n);
    gamma1 = 1 / cosd(360*bandwidth / n)
    alpha1 = gamma1 - sqrt(gamma1*gamma1 - 1)
    HP = zeros(size(x,1))
    BP = zeros(size(x,1))
    @inbounds for i in 3:length(x)
    HP[i] = (1 + alpha2 / 2)*(x[i] - x[i-1]) + (1- alpha2)*HP[i-1]
    BP[i] = .5*(1 - alpha1)*(HP[i] - HP[i-2]) + beta1*(1 + alpha1)*BP[i-1] - alpha1*BP[i-2]
    end
    # Signal
    Signal = zeros(size(x,1))
    Peak = zeros(size(x,1))
    @inbounds for i in 2:length(BP)
        Peak[i] = .991*Peak[i-1]
        if abs(BP[i]) > Peak[i]
            Peak[i] = abs(BP[i])
        if Peak[i] != 0
            Signal[i] = BP[i] / Peak[i]
        end
        else
        Signal[i] = BP[i] / Peak[i]
    end
    end

    # Replace Nan to 0
    @inbounds for i in 1:length(Signal)
    if isnan(Signal[i]) == 1
        Signal[i] = 0.0
    else
        Signal[i] = Signal[i]
    end
    end

    # Trigger
    alpha2 = (cosd(1.5*bandwidth*360 / n) + sind(1.5*bandwidth*360 / n) - 1) / cosd(1.5*bandwidth*360 /n)
    BP_Trigger = zeros(size(x,1))
    i=1
    @inbounds for i = 2:length(x)
        BP_Trigger[i] = (1 + alpha2 / 2)*(Signal[i] - Signal[i-1]) +(1 -alpha2)*BP_Trigger[i-1]
    end
    return BP_Trigger
end

#="""
    TO DO - Help Wanted - DC Portion of Calculation

Zero Crossings of the Band-Pass Filter - Equation 5-2
"""
=#

@doc """
    HurstCoefficient(x::Array{Float64}; n::Int64=30, LPPeriod::Int64=20)::Array{Float64}

Hurst Coefficient - Equation 6-1
"""
function HurstCoefficient(x::Array{Float64}; n::Int64=30, LPPeriod::Int64=20)::Array{Float64}
    @assert n<size(x,1) && n>0 "Argument n out of bounds."
    @assert iseven(n) "n must be an even number."
    # Smooth with a Super Smoother Filter from equation 3-3
    half_n = Int64(n/2)
    a1 = exp(-1.414*3.14159 / LPPeriod)
    b1 = 2*a1*cosd(1.414*180 / LPPeriod)
    c2 = b1
    c3 = -a1*a1
    c1 = 1 - c2 - c3
    # Find rolling maximum and minimum
    HH = zeros(size(x,1))
    LL = zeros(size(x,1))
    N3 = zeros(size(x,1))
    @inbounds for i = n:size(x,1)
    HH[i] = maximum(x[i-n+1:i])
    LL[i] = minimum(x[i-n+1:i])
    N3[i] = (HH[i] - LL[i]) / n
    end
    # Rolling min and max half of n
    HH = zeros(size(x,1))
    LL = zeros(size(x,1))
    N1 = zeros(size(x,1))
    @inbounds for i = half_n:size(x,1)
    HH[i] = maximum(x[i-half_n+1:i])
    LL[i] = minimum(x[i-half_n+1:i])
    N1[i] = (HH[i] - LL[i]) / half_n
    end
    # Set trailing close half of n
    HH = [fill(0,half_n); x[1:length(x)-half_n]]
    LL = [fill(0,half_n); x[1:length(x)-half_n]]
    HH_out = zeros(size(x,1))
    LL_out = zeros(size(x,1))
    N2 = zeros(size(x,1))
    @inbounds for i = half_n:size(x,1)
    HH_out[i] = maximum(HH[i-half_n+1:i])
    LL_out[i] = minimum(LL[i-half_n+1:i])
    N2[i] = (HH_out[i] - LL_out[i])/(half_n)
    end
    # Hurst
    Dimen = zeros(size(x,1))
    Hurst = zeros(size(x,1))
    SmoothHurst = zeros(size(x,1))
    @inbounds for i = 3:size(x,1)
    if N1[i] > 0 && N2[i] > 0 && N3[i] > 0
    Dimen[i] = .5*((log(N1[i]+ N2[i]) - log(N3[i])) / log(2) + Dimen[i-1])
    Hurst[i] = 2 - Dimen[i]
    SmoothHurst[i] = c1*(Hurst[i] + Hurst[i-1]) / 2 + c2*SmoothHurst[i-1]+ c3*SmoothHurst[i-2];
    end
    end
    return SmoothHurst
end

@doc """
    HPLPRoofingFilter(x::Array{Float64}; HPPeriod::Int64=48, LPPeriod::Int64=10)::Array{Float64}

HP LP Roofing Filter - Equation 7-1
"""
function HPLPRoofingFilter(x::Array{Float64}; HPPeriod::Int64=48, LPPeriod::Int64=10)::Array{Float64}
    @assert HPPeriod<size(x,1) && HPPeriod>0 "Argument HPPeriod out of bounds."
    # Highpass filter cyclic components whose periods are shorter than 48 bars
    alpha1 = (cosd(360 / HPPeriod) + sind(360 / HPPeriod) - 1) / cosd(360 / HPPeriod)
    HP = zeros(size(x,1))
    @inbounds for i = 2:size(x,1)
    HP[i] = (1 - alpha1 / 2)*(x[i] - x[i-1]) + (1 - alpha1)*HP[i-1]
    end
    # Smooth with a Super Smoother Filter from equation 3-3
    a1 = exp(-1.414*3.14159 / LPPeriod)  # may wish to make this an argument in function
    b1 = 2*a1*cosd(1.414*180 / LPPeriod) # may wish to make this an argument in function
    c2 = b1
    c3 = -a1*a1
    c1 = 1 - c2 - c3
    LP_HP_Filt = zeros(size(x,1))
    @inbounds for i = 3:size(x,1)
        LP_HP_Filt[i] = c1*(HP[i] + HP[i-1]) / 2 + c2*LP_HP_Filt[i-1] + c3*LP_HP_Filt[i-2]
    end
    return LP_HP_Filt
end

@doc """
    ZeroMeanRoofingFilterK0(x::Array{Float64}; HPPeriod::Int64=48, Smooth::Int64=10)::Array{Float64}

Zero Mean Roofing Filter - Lag 0 - Equation 7-2
K0 = Lag 0
# Lag 0 Is Most Responsive
# Ehlers describes using Lag 0 and Lag 1 cross overs/unders as a signal trigger for buying / selling
"""
function ZeroMeanRoofingFilterK0(x::Array{Float64}; HPPeriod::Int64=48, Smooth::Int64=10)::Array{Float64}
    @assert HPPeriod<size(x,1) && HPPeriod>0 "Argument HPPeriod out of bounds."
    # Highpass filter cyclic components whose periods are shorter than 48 bars
    alpha1 = (cosd(360 / HPPeriod) + sind(360 / HPPeriod) - 1) /cosd(360 / HPPeriod)
    HP = zeros(size(x,1))
    @inbounds for i = 2:size(x,1)
        HP[i] = (1 - alpha1 / 2)*(x[i] - x[i-1]) +(1 - alpha1)*HP[i-1]
    end
    #Smooth with a Super Smoother Filter from equation 3-3
    a1 = exp(-1.414*3.14159 / Smooth)
    b1 = 2*a1*cosd(1.414*180 / Smooth)
    c2 = b1
    c3 = -a1*a1
    c1 = 1 - c2 - c3
    Zero_Mean_Filt = zeros(size(x,1))
    Zero_Mean_Filt2 = zeros(size(x,1))
    @inbounds for i = 3:size(x,1)
        Zero_Mean_Filt[i] = c1*(HP[i] + HP[i-1]) / 2 + c2*Zero_Mean_Filt[i-1] + c3*Zero_Mean_Filt[i-2]
        Zero_Mean_Filt2[i] = (1 - alpha1 / 2)*(Zero_Mean_Filt[i] - Zero_Mean_Filt[i-1]) + (1 - alpha1)*Zero_Mean_Filt2[i-1]
    end
    return Zero_Mean_Filt
end

@doc """
    ZeroMeanRoofingFilterK1(x::Array{Float64}; HPPeriod::Int64=48, Smooth::Int64=10)::Array{Float64}

Zero Mean Roofing Filter - Lag 1 - Equation 7-2
K1 = Lag 1
"""
function ZeroMeanRoofingFilterK1(x::Array{Float64}; HPPeriod::Int64=48, Smooth::Int64=10)::Array{Float64}
    @assert HPPeriod<size(x,1) && HPPeriod>0 "Argument HPPeriod out of bounds."
    # Highpass filter cyclic components whose periods are shorter than 48 bars
    alpha1 = (cosd(360 / HPPeriod) + sind(360 / HPPeriod) - 1) /cosd(360 / HPPeriod)
    HP = zeros(size(x,1))
    @inbounds for i = 2:size(x,1)
        HP[i] = (1 - alpha1 / 2)*(x[i] - x[i-1]) +(1 - alpha1)*HP[i-1]
    end
    #Smooth with a Super Smoother Filter from equation 3-3
    a1 = exp(-1.414*3.14159 / Smooth)
    b1 = 2*a1*cosd(1.414*180 / Smooth)
    c2 = b1
    c3 = -a1*a1
    c1 = 1 - c2 - c3
    Zero_Mean_Filt = zeros(size(x,1))
    Zero_Mean_Filt2 = zeros(size(x,1))
    @inbounds for i = 3:size(x,1)
        Zero_Mean_Filt[i] = c1*(HP[i] + HP[i-1]) / 2 + c2*Zero_Mean_Filt[i-1] + c3*Zero_Mean_Filt[i-2]
        Zero_Mean_Filt2[i] = (1 - alpha1 / 2)*(Zero_Mean_Filt[i] - Zero_Mean_Filt[i-1]) + (1 - alpha1)*Zero_Mean_Filt2[i-1]
    end
    return Zero_Mean_Filt2
end

@doc """
    RoofingFilterIndicator(x::Array{Float64}; LPPeriod::Int64=40,HPPeriod::Int64=80)::Array{Float64}

Roofing Filter As Indicator - Equation 7-3
"""
function RoofingFilterIndicator(x::Array{Float64}; LPPeriod::Int64=40,HPPeriod::Int64=80)::Array{Float64}
    @assert HPPeriod<size(x,1) && HPPeriod>0 "Argument HPPeriod out of bounds."
    # Highpass filter cyclic components whose periods are shorter than 48 (n) bars
    alpha1 = (cosd(.707*360 / HPPeriod) + sind(.707*360 /HPPeriod) - 1) / cosd(.707*360 / HPPeriod)
    HP = zeros(size(x,1))
    @inbounds for i = 3:length(x)
    HP[i] = (1 - alpha1 / 2)*(1 - alpha1 / 2)*(x[i] - 2*x[i-1] + x[i-2]) + 2*(1 - alpha1)*HP[i-1] - (1 - alpha1)*(1 - alpha1)*HP[i-2]
    end
    #Smooth with a Super Smoother Filter from equation 3-3
    a1 = exp(-1.414*3.14159 / LPPeriod)
    b1 = 2*a1*cosd(1.414*180 / LPPeriod)
    c2 = b1
    c3 = -a1*a1
    c1 = 1 - c2 - c3
    Roof_filt_Indicator = zeros(size(x,1))
    @inbounds for i = 3:length(x)
        Roof_filt_Indicator[i] = c1*(HP[i] + HP[i-1]) / 2 + c2*Roof_filt_Indicator[i-1] + c3*Roof_filt_Indicator[i-2]
    end
    return Roof_filt_Indicator
end

@doc """
    ModifiedStochastic(x::Array{Float64}; n::Int64=20, HPPeriod::Int64=48, LPPeriod::Int64=10)::Array{Float64}

Modified Stochastic - Equation 7-4
"""
function ModifiedStochastic(x::Array{Float64}; n::Int64=20, HPPeriod::Int64=48, LPPeriod::Int64=10)::Array{Float64}
    @assert n<size(x,1) && n>0 "Argument n out of bounds."
    #Highpass filter cyclic components whose periods are shorter than 48 bars
    alpha1 = (cosd(.707*360 / HPPeriod) + sind(.707*360 / HPPeriod) - 1) /cosd(.707*360 / HPPeriod)
    HP = zeros(size(x,1))
    @inbounds for i = 3:size(x,1)
        HP[i] = (1 - alpha1 / 2)*(1 - alpha1 / 2)*(x[i] - 2*x[i-1]+ x[i-2]) + 2*(1 - alpha1)*HP[i-1] - (1 - alpha1)*(1 -alpha1)*HP[i-2]
    end
    # Smooth with a Super Smoother Filter from equation 3-3
    a1 = exp(-1.414*3.14159 / LPPeriod)
    b1 = 2*a1*cosd(1.414*180 / LPPeriod)
    c2 = b1
    c3 = -a1*a1
    c1 = 1 - c2 - c3
    Filt = zeros(size(x,1))
    @inbounds for i = 3:size(x,1)
        Filt[i] = c1*(HP[i] + HP[i-1]) / 2 + c2*Filt[i-1] + c3*Filt[i-2]
    end
    # Highest and lowest filt over n width
    HighestC = zeros(size(x,1))
    LowestC = zeros(size(x,1))
    Stoc = zeros(size(x,1))
    MyStochastic = zeros(size(x,1))
    @inbounds for i = n:size(x,1)
    HighestC[i] = maximum(Filt[i-n+1:i])
    LowestC[i] = minimum(Filt[i-n+1:i])
    Stoc[i] = (Filt[i] - LowestC[i]) / (HighestC[i] - LowestC[i])
    MyStochastic[i] = c1*(Stoc[i] + Stoc[i-1]) / 2 + c2*MyStochastic[i-1] + c3*MyStochastic[i-2]
    end
    return MyStochastic
end

@doc """
    ModifiedRSI(x::Array{Float64}; n::Int64=10, HPPeriod::Int64=48, LPPeriod::Int64=10)::Array{Float64}

Modified RSI - Equation 7-5
"""
function ModifiedRSI(x::Array{Float64}; n::Int64=10, HPPeriod::Int64=48, LPPeriod::Int64=10)::Array{Float64}
    @assert n<size(x,1) && n>0 "Argument n out of bounds."
    # Highpass filter cyclic components whose periods areshorter than 48 bars
    alpha1 = (cosd(.707*360 / HPPeriod) + sind(.707*360 / HPPeriod) - 1) /cosd(.707*360 / HPPeriod)
    HP = zeros(size(x,1))
    @inbounds for i =3:size(x,1)
        HP[i] = (1 - alpha1 / 2)*(1 - alpha1 / 2)*(x[i] - 2*x[i-1] +x[i-2]) + 2*(1 - alpha1)*HP[i-1] - (1 - alpha1)*(1 -alpha1)*HP[i-2]
    end
    # Smooth with a Super Smoother Filter from equation 3-3
    a1 = exp(-1.414*3.14159 / LPPeriod)
    b1 = 2*a1*cosd(1.414*180 / LPPeriod)
    c2 = b1
    c3 = -a1*a1
    c1 = 1 - c2 - c3
    Filt = zeros(size(x,1))
    @inbounds for i = 3:size(x,1)
        Filt[i] = c1*(HP[i] + HP[i-1]) / 2 + c2*Filt[i-1] + c3*Filt[i-2]
    end
    ClosesUp = zeros(size(x,1))
    ClosesDn = zeros(size(x,1))
    filtdiff = zeros(size(x,1))
    posDiff= zeros(size(x,1))
    negDiff= zeros(size(x,1))
    # pos and neg diffs
    @inbounds for i = 2:size(x,1)
        # difference
        filtdiff[i] = Filt[i] - Filt[i-1]
        if filtdiff[i] > 0
            posDiff[i] = filtdiff[i]
        elseif filtdiff[i] < 0
            negDiff[i] = abs(filtdiff[i])
            end
        end
        # Running Sums of Filt
        posSum = zeros(size(x,1))
        negSum = zeros(size(x,1))
        denom = zeros(size(x,1))
        rsi = zeros(size(x,1))
        @inbounds for i = n:size(x,1)
            posSum[i] = sum(posDiff[i-n+1:i])
            negSum[i] = sum(negDiff[i-n+1:i])
            denom[i] = posSum[i]+negSum[i]
        end
        # RSI
        MyRSI = zeros(size(x,1))
        @inbounds for i = 3:size(x,1)
            if denom != 0 && denom[i-1] != 0
                MyRSI[i] = c1*(posSum[i] /denom[i] + posSum[i-1] / denom[i-1]) / 2 + c2*MyRSI[i-1] +c3*MyRSI[i-2]
            end
        end
        return MyRSI
end

@doc """
    AutoCorrelationIndicator(x::Array{Float64}; min_lag::Int64=1, max_lag::Int64=48, HPPeriod::Int64=48, LPPeriod::Int64=10)::Array{Float64}

Autocorrelation Indicator - Equation 8-2
"""
function AutoCorrelationIndicator(x::Array{Float64}; min_lag::Int64=3, max_lag::Int64=48, HPPeriod::Int64=48, LPPeriod::Int64=10)::Array{Float64}
    @assert max_lag<size(x,1) && max_lag>0 "Argument max_lag out of bounds."
    # Highpass filter cyclic components whose periods areshorter than 48 bars
    alpha1 = (cosd(.707*360 / HPPeriod) + sind(.707*360 / HPPeriod) - 1) / cosd(.707*360 / HPPeriod)
    HP = zeros(size(x,1))
    @inbounds for i = 3:size(x,1)
        HP[i] = (1 - alpha1 / 2)*(1 - alpha1 / 2)*(x[i] - 2*x[i-1] +x[i-2]) + 2*(1 - alpha1)*HP[i-1] - (1 - alpha1)*(1 - alpha1)*HP[i-2]
    end
    # Smooth with a Super Smoother Filter from equation 3-3
    a1 = exp(-1.414*3.14159 / LPPeriod)
    b1 = 2*a1*cosd(1.414*180 / LPPeriod)
    c2 = b1
    c3 = -a1*a1
    c1 = 1 - c2 - c3
    Filt = zeros(size(x,1))
    @inbounds for i = 3:size(x,1)
        Filt[i] = c1*(HP[i] + HP[i-1]) / 2 + c2*Filt[i-1] + c3*Filt[i-2]
    end
    # Pearson correlation for each value of lag
    lags = min_lag:max_lag
    AutoCorrOut = zeros(size(x,1), max_lag)
    @inbounds for j = lags
    # Lag series
        lagged = [fill(0,j); Filt[1:length(Filt)-j]]
    # Roll correlation width of lag and lagged version of itself
    @inbounds for i = max_lag:size(x,1)
        AutoCorrOut[i,j] = cor(lagged[i-j+1:i], Filt[i-j+1:i])
        # Scale each correlation to range between 0 and 1
        AutoCorrOut[i,j]= .5*(AutoCorrOut[i,j] + 1)
        end
    end
    return AutoCorrOut
end

@doc """
    SingleLagAutoCorrelationIndicator(x::Array{Float64}; lag::Int64=10, HPPeriod::Int64=48, LPPeriod::Int64=10)::Array{Float64}

Single Lag Autocorrelation Indicator - Equation 8-2
"""
function SingleLagAutoCorrelationIndicator(x::Array{Float64}; lag::Int64=10, HPPeriod::Int64=48, LPPeriod::Int64=10)::Array{Float64}
    @assert lag<size(x,1) && lag>0 "Argument n out of bounds."
    # Highpass filter cyclic components whose periods areshorter than 48 bars
    alpha1 = (cosd(.707*360 / HPPeriod) + sind(.707*360 / HPPeriod) - 1) / cosd(.707*360 / HPPeriod)
    HP = zeros(size(x,1))
    @inbounds for i = 3:size(x,1)
        HP[i] = (1 - alpha1 / 2)*(1 - alpha1 / 2)*(x[i] - 2*x[i-1] +x[i-2]) + 2*(1 - alpha1)*HP[i-1] - (1 - alpha1)*(1 - alpha1)*HP[i-2]
    end
    # Smooth with a Super Smoother Filter from equation 3-3
    a1 = exp(-1.414*3.14159 / LPPeriod)
    b1 = 2*a1*cosd(1.414*180 / LPPeriod)
    c2 = b1
    c3 = -a1*a1
    c1 = 1 - c2 - c3
    Filt = zeros(size(x,1))
    @inbounds for i = 3:size(x,1)
        Filt[i] = c1*(HP[i] + HP[i-1]) / 2 + c2*Filt[i-1] + c3*Filt[i-2]
    end
    # Pearson correlation for specified lag
    AutoCorrOut = zeros(size(x,1))
    # Lag series
        lagged = [fill(0,lag); Filt[1:length(Filt)-lag]]
    # Roll correlation width of lag and lagged version of itself
    @inbounds for i = lag:size(x,1)
        AutoCorrOut[i] = cor(lagged[i-lag+1:i], Filt[i-lag+1:i])
        #Scale each correlation to range between 0 and 1
        AutoCorrOut[i]= .5*(AutoCorrOut[i] + 1)
        end
    return AutoCorrOut
end

@doc """
    AutoCorrelationPeriodogram(x::Array{Float64}; min_lag::Int64=1, max_lag::Int64=48,HPPeriod::Int64=48, LPPeriod::Int64=10)::Array{Float64}

Autocorrelation Periodogram- Equation 8-3
"""
function AutoCorrelationPeriodogram(x::Array{Float64}; min_lag::Int64=3, max_lag::Int64=48,HPPeriod::Int64=48, LPPeriod::Int64=10)::Array{Float64}
        @assert max_lag<size(x,1) && max_lag>0 "Argument max_lag out of bounds."
        alpha1 = (cosd(.707*360 / HPPeriod) + sind(.707*360 / HPPeriod) - 1) / cosd(.707*360 / HPPeriod)
        HP = zeros(size(x,1))
        @inbounds for i = 3:size(x,1)
            HP[i] = (1 - alpha1 / 2)*(1 - alpha1 / 2)*(x[i] - 2*x[i-1] +x[i-2]) + 2*(1 - alpha1)*HP[i-1] - (1 - alpha1)*(1 - alpha1)*HP[i-2]
        end
        # Smooth with a Super Smoother Filter from equation 3-3
        a1 = exp(-1.414*3.14159 / LPPeriod)
        b1 = 2*a1*cosd(1.414*180 / LPPeriod)
        c2 = b1
        c3 = -a1*a1
        c1 = 1 - c2 - c3
        Filt = zeros(size(x,1))
        @inbounds for i = 3:size(x,1)
            Filt[i] = c1*(HP[i] + HP[i-1]) / 2 + c2*Filt[i-1] + c3*Filt[i-2]
        end
        # Pearson correlation for each value of lag
        # Initialize correlation sums
        lags = min_lag:max_lag
        avglength = 3
        temp = zeros(size(x,1))
        Avg_Corr_Out = zeros(size(x,1), max_lag)
        @inbounds for j = lags
            # Lag series
            lagged = [fill(0,j); Filt[1:length(Filt)-j]]
        # Roll correlation width of lag and lagged version of itself
        @inbounds for i = max_lag:size(x,1)
            Avg_Corr_Out[i,j] = cor(lagged[i-avglength+1:i], Filt[i-avglength+1:i])
            end
        end
        # Calcualte sine and cosine part
        cosinePart = zeros(size(x,1), max_lag)
        sinePart = zeros(size(x,1), max_lag)
        sqSum = zeros(size(x,1), max_lag)
        @inbounds for j = min_lag:max_lag
            for k = 3:48
                cosinePart[:,j] .= cosinePart[:,j] .+ Avg_Corr_Out[:,k] .* cosd(370 * k / j)
                sinePart[:,j] .= sinePart[:,j] .+ Avg_Corr_Out[:,k] .* sind(370 * k / j)
                sqSum[:,j] .= cosinePart[:,j].^2 .+ sinePart[:,j].^2
            end
        end
        # Iterate over every i in j and smooth R by the .2 and .8 factors
        R = zeros(size(x,1), max_lag)
        @inbounds for j = min_lag:max_lag
            @inbounds for i = 2:size(x,1)
                R[i,j] = (.2*sqSum[i,j]) * (sqSum[i,j]) + (.8 *R[i-1,j])
            end
        end
#### validated against TS above ^^^^^ ###############
## although followed logic for normalization as below could not reproduce same result  = revisit.

    # Find Maximum Power Level for Normalization
    # need to validate this and below!
    MaxPwr = zeros(size(x,1), max_lag)
    #MaxPwr = 0
    @inbounds for j = min_lag:max_lag
        @inbounds for i = 2:size(x,1)
            MaxPwr[i,j] = .995*MaxPwr[i-1,j]
        if R[i,j] > MaxPwr[i,j]
            MaxPwr[i,j]= R[i,j]
            end
        end
    end

    Pwr = zeros(size(x,1), max_lag)
    @inbounds for j = min_lag:max_lag
        @inbounds for i = 1:size(x,1)
            Pwr[i,j] = R[i,j] / MaxPwr[i,j]
        end
    end

    # Replace Nan to 0
    @inbounds for j = 1:max_lag
        @inbounds for i = 1:size(x,1)
    if isnan(Pwr[i,j]) == 1
        Pwr[i,j] = 0.0
    else
        Pwr[i,j] = Pwr[i,j]
    end
    end
    end

    # Compute the dominant cycle using the CG of the spectrum
    Spx = zeros(size(x,1))
    Sp = zeros(size(x,1))
    for j = min_lag:max_lag
        Spx .= ifelse.(Pwr[:,j] .>= 0.5, Spx .+ j .* Pwr[:,j],Spx)
        Sp .= ifelse.(Pwr[:,j] .>= 0.5,Sp .+ Pwr[:,j],Sp)
    end

    DominantCycle = zeros(size(x,1))
    for i = 1:size(x,1)
        if Sp[i] != 0
            DominantCycle[i] = Spx[i] / Sp[i]
        end
    end
    return DominantCycle
end

@doc """
    AutoCorrelationReversals(x::Array{Float64}; min_lag::Int64=1, max_lag::Int64=48, LPPeriod::Int64=10, HPPeriod::Int64=48, AvgLength::Int64=3)::Array{Float64}

Autocorrelation Reversals - Equation 8-4

The indicated reversals are very sensitive to the smoothing of the price data.
LPLength is made available as an indicator input to decrease or increase the number of indicated reversals as desired.
The AvgLength parameter is also made available as an indicator because this averaging also impacts the number of indicated reversals.
Care should be taken when increasing the value of this input because the lag of the indicator increases in direct proportion to the increase of the value of the AvgLength.
Typical delay of the indicator will be about three bars when the AvgLength parameter is set to a value of 3.
"""
function AutoCorrelationReversals(x::Array{Float64}; min_lag::Int64=3, max_lag::Int64=48, LPPeriod::Int64=10, HPPeriod::Int64=48, AvgLength::Int64=3)::Array{Float64}
    @assert max_lag<size(x,1) && max_lag>0 "Argument n out of bounds."
    # Highpass filter cyclic components whose periods are shorter than 48 bars
    alpha1 = (cosd(.707*360 / HPPeriod) + sind(.707*360 / HPPeriod) - 1) / cosd(.707*360 / HPPeriod)
    HP = zeros(size(x,1))
    @inbounds for i = 3:size(x,1)
        HP[i] = (1 - alpha1 / 2)*(1 - alpha1 / 2)*(x[i] - 2*x[i-1] +x[i-2]) + 2*(1 - alpha1)*HP[i-1] - (1 - alpha1)*(1 - alpha1)*HP[i-2]
    end
    # Smooth with a Super Smoother Filter from equation 3-3
    a1 = exp(-1.414*3.14159 / LPPeriod)
    b1 = 2*a1*cosd(1.414*180 / LPPeriod)
    c2 = b1
    c3 = -a1*a1
    c1 = 1 - c2 - c3
    Filt = zeros(size(x,1))
    @inbounds for i = 3:size(x,1)
        Filt[i] = c1*(HP[i] + HP[i-1]) / 2 + c2*Filt[i-1] + c3*Filt[i-2]
    end
    # Pearson correlation for each value of lag
    lags = min_lag:max_lag
    Avg_Corr_Rev_Out = zeros(size(x,1), max_lag)
    @inbounds for j = lags
    # Lag series
        lagged = [fill(0,j); Filt[1:length(Filt)-j]]
    # Roll correlation width of lag and lagged version of itself
    @inbounds for i = AvgLength:size(x,1)
        Avg_Corr_Rev_Out[i,j] = cor(lagged[i-AvgLength+1:i], Filt[i-AvgLength+1:i])
        # Scale each correlation to range between 0 and 1
        Avg_Corr_Rev_Out[i,j] = .5*(Avg_Corr_Rev_Out[i,j] + 1)
        end
    end

    # mark all > .5 and <.5 crossings
    SumDeltas = zeros(size(x,1), max_lag)
    @inbounds for j = lags
        @inbounds for i = AvgLength:size(x,1)
            if (Avg_Corr_Rev_Out[i,j] > 0.5) && (Avg_Corr_Rev_Out[i-1,j] < 0.5) || (Avg_Corr_Rev_Out[i,j] < 0.5) && (Avg_Corr_Rev_Out[i-1,j] > 0.5)
                SumDeltas[i,j] = 1.0
            else
                SumDeltas[i,j] = 0.0
                end
            end
        end

        # Sum across the matrix of all correlation 0.5 crossings
        Reversal = zeros(size(x,1))
        test_sum = zeros(size(x,1))
        @inbounds for i = 1:size(x,1)
            test_sum[i] = sum(SumDeltas[i,:])
            if sum(SumDeltas[i,:]) > 24
                Reversal[i] = 1
            else Reversal[i] =  0
            end
        end
        return Reversal
end

@doc """
DFTS(x::Array{Float64}; min_lag::Int64=1, max_lag::Int64=48, LPLength::Int64=10, HPLength::Int64=48)::Array{Float64}

Discrete Fourier Transform Sprectral Estimate - Equation 9-1
"""
function DFTS(x::Array{Float64}; min_lag::Int64=1, max_lag::Int64=48, LPLength::Int64=10, HPLength::Int64=48)::Array{Float64}
        @assert HPLength<size(x,1) && HPLength>0 "Argument n out of bounds."
# Highpass filter cyclic components whose periods are shorter than 48 bars
    alpha1 = (cosd(.707*360 / HPLength) + sind(.707*360 / HPLength) - 1) / cosd(.707*360 / HPLength)
    HP = zeros(size(x,1))
    @inbounds for i = 3:size(x,1)
        HP[i] = (1 - alpha1 / 2)*(1 - alpha1 / 2)*(x[i] - 2*x[i-1] +x[i-2]) + 2*(1 - alpha1)*HP[i-1] - (1 - alpha1)*(1 - alpha1)*HP[i-2]
    end
    # Smooth with a Super Smoother Filter from equation 3-3
    a1 = exp(-1.414*3.14159 / LPLength)
    b1 = 2*a1*cosd(1.414*180 / LPLength)
    c2 = b1
    c3 = -a1*a1
    c1 = 1 - c2 - c3
    Filt = zeros(size(x,1))
    @inbounds for i = 3:size(x,1)
        Filt[i] = c1*(HP[i] + HP[i-1]) / 2 + c2*Filt[i-1] + c3*Filt[i-2]
    end

    # Initialize matrix
    CosinePart = zeros(size(x,1), max_lag)
    SinePart = zeros(size(x,1), max_lag)
    Pwr = zeros(size(x,1), max_lag)
    # This is the DFT
    @inbounds for j = min_lag:max_lag
    @inbounds for k = 1:max_lag
        lagged_filt = [fill(0,k); Filt[1:length(Filt)-k]]
        CosinePart[:,j] .= CosinePart[:,j] .+ lagged_filt .* cosd(360 * k / j) / j
        SinePart[:,j] .= SinePart[:,j] .+ lagged_filt .* sind(360 * k / j) / j
        Pwr[:,j] .= CosinePart[:,j] .* CosinePart[:,j] .+ SinePart[:,j] .* SinePart[:,j]
        end
    end
    # Find Maximum Power Level for Normalization
    # Note difers from TS output
    MaxPwr = zeros(size(x,1), max_lag)
    @inbounds for j = min_lag:max_lag
    @inbounds for i = 2:size(x,1)
    MaxPwr[i,j]  = .995*MaxPwr[i-1,j]
        if Pwr[i,j]  > MaxPwr[i,j]
         MaxPwr[i,j] = Pwr[i,j]
        end
    end
    end

#+_+_+_+_+_+_+_+_+_+_+_+_ unable to validate the below against TS
    #Normalize Power Levels and Convert to Decibels
    @inbounds for j = min_lag:max_lag
        @inbounds for i = 1:size(x,1)
            if MaxPwr[i,j] != 0
                Pwr[i,j] = Pwr[i,j] / MaxPwr[i,j]
                end
            end
        end

    # Compute the dominant cycle using the CG of the spectrum
    Spx = zeros(size(x,1))
    Sp = zeros(size(x,1))
    for j = min_lag:max_lag
        Spx .= ifelse.(Pwr[:,j] .>= 0.5, Spx .+ j .* Pwr[:,j],Spx)
        Sp .= ifelse.(Pwr[:,j] .>= 0.5,Sp .+ Pwr[:,j],Sp)
    end

    DominantCycle = zeros(size(x,1))
    for i = 2:size(x,1)
        if Sp[i] != 0
            DominantCycle[i] = Spx[i] / Sp[i]
        else
            DominantCycle[i] = DominantCycle[i-1]  # if its zero carry forwrd previous value
            end
        end
        return DominantCycle
end

#="""
    TO DO

Comb Filter Spectral Estimate - Equation 10-1
This spectral estimate may be used to adust other indicators such as RSI, Stochastic and CCI
For example of how the indicator adjustments are made see Adaptive Indicators in equation 11-*
"""
=#
@doc """
    AdaptiveRSI(x::Array{Float64}; min_lag::Int64=1, max_lag::Int64=48,LPLength::Int64=10, HPLength::Int64=48, AvgLength::Int64=3)::Array{Float64}

Adaptive RSI - Equation 11-1
Adjust the RSI by a lookback period half the length of the dominant cycle
"""
function AdaptiveRSI(x::Array{Float64}; min_lag::Int64=1, max_lag::Int64=48,LPLength::Int64=10, HPLength::Int64=48, AvgLength::Int64=3)::Array{Float64}
    @assert max_lag<size(x,1) && max_lag>0 "Argument max_lag is out of bounds."
    #@assert max_lag<size(x,1) && max_lag>0 "Argument n is out of bounds."
    alpha1 = (cosd(.707*360 / HPLength) + sind(.707*360 / HPLength) - 1) / cosd(.707*360 / HPLength)
    HP = zeros(size(x,1))
    @inbounds for i = 3:size(x,1)
        HP[i] = (1 - alpha1 / 2)*(1 - alpha1 / 2)*(x[i] - 2*x[i-1] +x[i-2]) + 2*(1 - alpha1)*HP[i-1] - (1 - alpha1)*(1 - alpha1)*HP[i-2]
    end
    # Smooth with a Super Smoother Filter from equation 3-3
    a1 = exp(-1.414*3.14159 / LPLength)
    b1 = 2*a1*cosd(1.414*180 / LPLength)
    c2 = b1
    c3 = -a1*a1
    c1 = 1 - c2 - c3
    Filt = zeros(size(x,1))
    @inbounds for i = 3:size(x,1)
        Filt[i] = c1*(HP[i] + HP[i-1]) / 2 + c2*Filt[i-1] + c3*Filt[i-2]
    end
    # Pearson correlation for each value of lag
    # Initialize correlation sums
    lags = min_lag:max_lag
    temp = zeros(size(x,1))
    Avg_Corr_Out = zeros(size(x,1), max_lag)
    @inbounds for j = lags
    # Lag series
        lagged = [fill(0,j); Filt[1:length(Filt)-j]]
        # Roll correlation width of lag and lagged version of itself
    @inbounds for i = max_lag:size(x,1)
        Avg_Corr_Out[i,j] = cor(lagged[i-AvgLength+1:i], Filt[i-AvgLength+1:i])
        end
    end

    # Calcualte sine and cosine part
    cosinePart = zeros(size(x,1), max_lag)
    sinePart = zeros(size(x,1), max_lag)
    sqSum = zeros(size(x,1), max_lag)
    @inbounds for j = min_lag:max_lag
        @inbounds for k = 3:max_lag
            cosinePart[:,j] .= cosinePart[:,j] .+ Avg_Corr_Out[:,k] .* cosd(370 * k / j)
            sinePart[:,j] .= sinePart[:,j] .+ Avg_Corr_Out[:,k] .* sind(370 * k / j)
            sqSum[:,j] .= cosinePart[:,j].^2 .+ sinePart[:,j].^2
        end
    end

    # Iterate over every i in j and smooth R by the .2 and .8 factors
    R = zeros(size(x,1), max_lag)
    @inbounds for j = min_lag:max_lag
        @inbounds for i = 2:size(x,1)
            R[i,j] = (.2*sqSum[i,j]) * (sqSum[i,j]) + (.8 *R[i-1,j])
        end
    end
    #### validated ^^^^^ ###############
    # Find Maximum Power Level for Normalization
    # need to validate this and below!
    MaxPwr = zeros(size(x,1), max_lag)
    #MaxPwr = 0
    @inbounds for j = min_lag:max_lag
        @inbounds for i = 2:size(x,1)
            MaxPwr[i,j] = .995*MaxPwr[i-1,j]
        if R[i,j] > MaxPwr[i,j]
            MaxPwr[i,j]= R[i,j]
            end
        end
    end
    Pwr = zeros(size(x,1), max_lag)
    @inbounds for j = min_lag:max_lag
        @inbounds for i = 1:size(x,1)
            Pwr[i,j] = R[i,j] / MaxPwr[i,j]
        end
    end

    # Replace Nan to 0
    @inbounds for j = 1:max_lag
        @inbounds for i = 1:size(x,1)
    if isnan(Pwr[i,j]) == 1
        Pwr[i,j] = 0.0
    else
        Pwr[i,j] = Pwr[i,j]
    end
    end
    end

    # Compute the dominant cycle using the CG of the spectrum
    Spx = zeros(size(x,1))
    Sp = zeros(size(x,1))
    for j = min_lag:max_lag
        Spx .= ifelse.(Pwr[:,j] .>= 0.5, Spx .+ j .* Pwr[:,j],Spx)
        Sp .= ifelse.(Pwr[:,j] .>= 0.5,Sp .+ Pwr[:,j],Sp)
    end

    dominant_cycle = zeros(size(x,1))
    for i = 1:size(x,1)
        if Sp[i] != 0
            dominant_cycle[i] = Spx[i] / Sp[i]
        end
    if dominant_cycle[i] < 5
        dominant_cycle[i] = 5
    end
    if dominant_cycle[i] > max_lag
        dominant_cycle[i] = max_lag
    end
    end

    # Adaptive RSI starts here, using half the measured dominant
    ClosesUp = zeros(size(x,1))
    ClosesDn = zeros(size(x,1))
    filtdiff = zeros(size(x,1))
    posDiff= zeros(size(x,1))
    negDiff= zeros(size(x,1))
    # pos and neg diffs
    @inbounds for i = 2:size(x,1)
        # difference
        filtdiff[i] = Filt[i] - Filt[i-1]
        if filtdiff[i] > 0
            posDiff[i] = filtdiff[i]
        elseif filtdiff[i] < 0
            negDiff[i] = abs(filtdiff[i])
            end
        end

        # Running Sums of Filt
        posSum = zeros(size(x,1))
        negSum = zeros(size(x,1))
        denom = zeros(size(x,1))
        rsi= zeros(size(x,1))
        # Set width of look back 50% of the dominant cycle
        n = Int64.(round.(dominant_cycle ./ 2;digits = 0))
        for i = 1:size(n,1)
        if isnan(n[i]) == 1
            n[i] = 2.0
        else
            n[i] == n[i]
        end
    end
        @inbounds for i = n[1]:size(x,1)
            k = n[i]
            posSum[i] = sum(posDiff[i-k+1:i])
            negSum[i] = sum(negDiff[i-k+1:i])
            denom[i] = posSum[i]+negSum[i]
        end

    # RSI
    MyRSI = zeros(size(x,1))
    @inbounds for i = 3:size(x,1)
    if denom != 0 && denom[i-1] != 0
        MyRSI[i] = c1*(posSum[i] /denom[i] + posSum[i-1] / denom[i-1]) / 2 + c2*MyRSI[i-1] +c3*MyRSI[i-2]
        end
    end
    return MyRSI
end

@doc """
    AdaptiveStochastic(x::Array{Float64}; min_lag::Int64=1, max_lag::Int64=48,LPLength::Int64=10, HPLength::Int64=48, AvgLength::Int64=3)::Array{Float64}

Adaptive Stochastic - Equation 11-2
Adjust the stochastic lookback period by the same value as the dominant cycle
"""
function AdaptiveStochastic(x::Array{Float64}; min_lag::Int64=1, max_lag::Int64=48,LPLength::Int64=10, HPLength::Int64=48, AvgLength::Int64=3)::Array{Float64}
    @assert max_lag<size(x,1) && max_lag>0 "Argument n out of bounds."
    alpha1 = (cosd(.707*360 / HPLength) + sind(.707*360 / HPLength) - 1) / cosd(.707*360 / HPLength)
    HP = zeros(size(x,1))
    @inbounds for i = 3:size(x,1)
        HP[i] = (1 - alpha1 / 2)*(1 - alpha1 / 2)*(x[i] - 2*x[i-1] +x[i-2]) + 2*(1 - alpha1)*HP[i-1] - (1 - alpha1)*(1 - alpha1)*HP[i-2]
    end
    # Smooth with a Super Smoother Filter from equation 3-3
    a1 = exp(-1.414*3.14159 / LPLength)
    b1 = 2*a1*cosd(1.414*180 / LPLength)
    c2 = b1
    c3 = -a1*a1
    c1 = 1 - c2 - c3
    Filt = zeros(size(x,1))
    @inbounds for i = 3:size(x,1)
        Filt[i] = c1*(HP[i] + HP[i-1]) / 2 + c2*Filt[i-1] + c3*Filt[i-2]
    end
    # Pearson correlation for each value of lag
    # Initialize correlation sums
    lags = min_lag:max_lag
    temp = zeros(size(x,1))
    Avg_Corr_Out = zeros(size(x,1), max_lag)
    @inbounds for j = lags
    # Lag series
        lagged = [fill(0,j); Filt[1:length(Filt)-j]]
        # Roll correlation width of lag and lagged version of itself
    @inbounds for i = max_lag:size(x,1)
        Avg_Corr_Out[i,j] = cor(lagged[i-AvgLength+1:i], Filt[i-AvgLength+1:i])
        end
    end

    # Calcualte sine and cosine part
    cosinePart = zeros(size(x,1), max_lag)
    sinePart = zeros(size(x,1), max_lag)
    sqSum = zeros(size(x,1), max_lag)
    @inbounds for j = min_lag:max_lag
        @inbounds for k = 3:max_lag
            cosinePart[:,j] .= cosinePart[:,j] .+ Avg_Corr_Out[:,k] .* cosd(370 * k / j)
            sinePart[:,j] .= sinePart[:,j] .+ Avg_Corr_Out[:,k] .* sind(370 * k / j)
            sqSum[:,j] .= cosinePart[:,j].^2 .+ sinePart[:,j].^2
        end
    end

    # Iterate over every i in j and smooth R by the .2 and .8 factors
    R = zeros(size(x,1), max_lag)
    @inbounds for j = min_lag:max_lag
        @inbounds for i = 2:size(x,1)
            R[i,j] = (.2*sqSum[i,j]) * (sqSum[i,j]) + (.8 *R[i-1,j])
        end
    end
    #### validated ^^^^^ ###############
    # Find Maximum Power Level for Normalization
    # need to validate this and below!
    MaxPwr = zeros(size(x,1), max_lag)
    #MaxPwr = 0
    @inbounds for j = min_lag:max_lag
        @inbounds for i = 2:size(x,1)
            MaxPwr[i,j] = .995*MaxPwr[i-1,j]
        if R[i,j] > MaxPwr[i,j]
            MaxPwr[i,j]= R[i,j]
            end
        end
    end
    Pwr = zeros(size(x,1), max_lag)
    @inbounds for j = min_lag:max_lag
        @inbounds for i = 1:size(x,1)
            Pwr[i,j] = R[i,j] / MaxPwr[i,j]
        end
    end

    # Replace Nan to 0
    @inbounds for j = 1:max_lag
        @inbounds for i = 1:size(x,1)
    if isnan(Pwr[i,j]) == 1
        Pwr[i,j] = 0.0
    else
        Pwr[i,j] = Pwr[i,j]
            end
        end
    end

    # Compute the dominant cycle using the CG of the spectrum
    Spx = zeros(size(x,1))
    Sp = zeros(size(x,1))
    for j = min_lag:max_lag
        Spx .= ifelse.(Pwr[:,j] .>= 0.5, Spx .+ j .* Pwr[:,j],Spx)
        Sp .= ifelse.(Pwr[:,j] .>= 0.5,Sp .+ Pwr[:,j],Sp)
    end

    dominant_cycle = zeros(size(x,1))
    for i = 1:size(x,1)
        if Sp[i] != 0
            dominant_cycle[i] = Spx[i] / Sp[i]
        end
    if dominant_cycle[i] < 10
        dominant_cycle[i] = 10
    end
    if dominant_cycle[i] > max_lag
        dominant_cycle[i] = max_lag
    end
    end
    # Stochastic Computation starts here
    # Highest and lowest filt over same period as dominant cycle
    HighestC = zeros(size(x,1))
    LowestC = zeros(size(x,1))
    Stoc = zeros(size(x,1))
    adaptive_stochastic = zeros(size(x,1))
    n = Int64.(round.(dominant_cycle; digits=0))
    @inbounds for i = n[1]:size(x,1)
        k = n[i]
        HighestC[i] = maximum(Filt[i-k+1:i])
        LowestC[i] = minimum(Filt[i-k+1:i])
        Stoc[i] = (Filt[i] - LowestC[i]) / (HighestC[i] - LowestC[i])
        adaptive_stochastic[i] = c1*(Stoc[i] + Stoc[i-1]) / 2 + c2*adaptive_stochastic[i-1] + c3*adaptive_stochastic[i-2]
    end
    return adaptive_stochastic
end

@doc """
    AdaptiveCCI(x::Array{Float64}; min_lag::Int64=1, max_lag::Int64=48,LPLength::Int64=10, HPLength::Int64=48, AvgLength::Int64=3)::Array{Float64}

Adaptive CCI - Equation 11-3
Adjust the lookback period by the same value as the dominant cycle
"""
function AdaptiveCCI(x::Array{Float64}; min_lag::Int64=1, max_lag::Int64=48,LPLength::Int64=10, HPLength::Int64=48, AvgLength::Int64=3)::Array{Float64}
    @assert max_lag<size(x,1) && max_lag>0 "Argument n out of bounds."
    alpha1 = (cosd(.707*360 / HPLength) + sind(.707*360 / HPLength) - 1) / cosd(.707*360 / HPLength)
    HP = zeros(size(x,1))
    @inbounds for i = 3:size(x,1)
        HP[i] = (1 - alpha1 / 2)*(1 - alpha1 / 2)*(x[i] - 2*x[i-1] +x[i-2]) + 2*(1 - alpha1)*HP[i-1] - (1 - alpha1)*(1 - alpha1)*HP[i-2]
    end
    # Smooth with a Super Smoother Filter from equation 3-3
    a1 = exp(-1.414*3.14159 / LPLength)
    b1 = 2*a1*cosd(1.414*180 / LPLength)
    c2 = b1
    c3 = -a1*a1
    c1 = 1 - c2 - c3
    Filt = zeros(size(x,1))
    @inbounds for i = 3:size(x,1)
        Filt[i] = c1*(HP[i] + HP[i-1]) / 2 + c2*Filt[i-1] + c3*Filt[i-2]
    end
    # Pearson correlation for each value of lag
    # Initialize correlation sums
    lags = min_lag:max_lag
    temp = zeros(size(x,1))
    Avg_Corr_Out = zeros(size(x,1), max_lag)
    @inbounds for j = lags
    # Lag series
        lagged = [fill(0,j); Filt[1:length(Filt)-j]]
        # Roll correlation width of lag and lagged version of itself
    @inbounds for i = max_lag:size(x,1)
        Avg_Corr_Out[i,j] = cor(lagged[i-AvgLength+1:i], Filt[i-AvgLength+1:i])
        end
    end

    # Calcualte sine and cosine part
    cosinePart = zeros(size(x,1), max_lag)
    sinePart = zeros(size(x,1), max_lag)
    sqSum = zeros(size(x,1), max_lag)
    @inbounds for j = min_lag:max_lag
        @inbounds for k = 3:max_lag
            cosinePart[:,j] .= cosinePart[:,j] .+ Avg_Corr_Out[:,k] .* cosd(370 * k / j)
            sinePart[:,j] .= sinePart[:,j] .+ Avg_Corr_Out[:,k] .* sind(370 * k / j)
            sqSum[:,j] .= cosinePart[:,j].^2 .+ sinePart[:,j].^2
        end
    end

    # Iterate over every i in j and smooth R by the .2 and .8 factors
    R = zeros(size(x,1), max_lag)
    @inbounds for j = min_lag:max_lag
        @inbounds for i = 2:size(x,1)
            R[i,j] = (.2*sqSum[i,j]) * (sqSum[i,j]) + (.8 *R[i-1,j])
        end
    end
    #### validated ^^^^^ ###############
    # Find Maximum Power Level for Normalization
    # need to validate this and below!
    MaxPwr = zeros(size(x,1), max_lag)
    #MaxPwr = 0
    @inbounds for j = min_lag:max_lag
        @inbounds for i = 2:size(x,1)
            MaxPwr[i,j] = .991*MaxPwr[i-1,j]
        if R[i,j] > MaxPwr[i,j]
            MaxPwr[i,j]= R[i,j]
            end
        end
    end
    Pwr = zeros(size(x,1), max_lag)
    @inbounds for j = min_lag:max_lag
        @inbounds for i = 1:size(x,1)
            Pwr[i,j] = R[i,j] / MaxPwr[i,j]
        end
    end

    # Replace Nan to 0
    @inbounds for j = 1:max_lag
        @inbounds for i = 1:size(x,1)
    if isnan(Pwr[i,j]) == 1
        Pwr[i,j] = 0.0
    else
        Pwr[i,j] = Pwr[i,j]
            end
        end
    end

    # Compute the dominant cycle using the CG of the spectrum
    Spx = zeros(size(x,1))
    Sp = zeros(size(x,1))

    Pwr[:,1]
    for j = min_lag:max_lag
        Spx .= ifelse.(Pwr[:,j] .>= 0.5, Spx .+ j .* Pwr[:,j],Spx)
        Sp .= ifelse.(Pwr[:,j] .>= 0.5,Sp .+ Pwr[:,j],Sp)
    end

    dominant_cycle = zeros(size(x,1))
    for i = 1:size(x,1)
        if Sp[i] != 0
            dominant_cycle[i] = Spx[i] / Sp[i]
        end
    if dominant_cycle[i] < 10 || isnan(dominant_cycle[i]) == true
        dominant_cycle[i] = 10
    end
    if dominant_cycle[i] > max_lag
        dominant_cycle[i] = max_lag
    end
    end

    # Adaptive CCI starts here, using half the measured dominant cycle for tuning
    num = zeros(size(x,1))
    denom = zeros(size(x,1))
    ratio = zeros(size(x,1))
    n = Int64.(round.(dominant_cycle; digits=0))

    @inbounds for i = n[1]:size(x,1)
        k = n[i] # dominant cycle look back
        num[i] = TP[i] - mean(Filt[i-k+1:i])
        denom[i] = 0.015 * std(Filt[i-k+1:i])
        ratio[i] = num[i] / denom[i]
    end

    adaptive_CCI = zeros(size(x,1))
    @inbounds for i = 3:size(x,1)
        adaptive_CCI[i] = c1*(ratio[i] + ratio[i-1]) / 2 + c2*adaptive_CCI[i-1] + c3*adaptive_CCI[i-2]
    end

    p1 = plot(y=adaptive_CCI,Geom.line)
    p2 = plot(y=x,Geom.line)
    out = vstack(p1,p2)

    return adaptive_CCI
end

@doc """
    AdaptiveBPFilter(x::Array{Float64}; min_lag::Int64=1, max_lag::Int64=48,LPLength::Int64=10, HPLength::Int64=48, AvgLength::Int64=3)::Array{Float64}

Adaptive BandPass Filter - Equation 11-4
Tune filter to the measured dominant cycle
"""
function AdaptiveBPFilter(x::Array{Float64}; min_lag::Int64=1, max_lag::Int64=48,LPLength::Int64=10, HPLength::Int64=48, AvgLength::Int64=3, bandwidth::Float64=.3)::Array{Float64}
    @assert max_lag<size(x,1) && max_lag>0 "Argument n out of bounds."
    alpha1 = (cosd(.707*360 / HPLength) + sind(.707*360 / HPLength) - 1) / cosd(.707*360 / HPLength)
    HP = zeros(size(x,1))
    @inbounds for i = 3:size(x,1)
        HP[i] = (1 - alpha1 / 2)*(1 - alpha1 / 2)*(x[i] - 2*x[i-1] +x[i-2]) + 2*(1 - alpha1)*HP[i-1] - (1 - alpha1)*(1 - alpha1)*HP[i-2]
    end
    # Smooth with a Super Smoother Filter from equation 3-3
    a1 = exp(-1.414*3.14159 / LPLength)
    b1 = 2*a1*cosd(1.414*180 / LPLength)
    c2 = b1
    c3 = -a1*a1
    c1 = 1 - c2 - c3
    Filt = zeros(size(x,1))
    @inbounds for i = 3:size(x,1)
        Filt[i] = c1*(HP[i] + HP[i-1]) / 2 + c2*Filt[i-1] + c3*Filt[i-2]
    end
    # Pearson correlation for each value of lag
    # Initialize correlation sums
    lags = min_lag:max_lag
    temp = zeros(size(x,1))
    Avg_Corr_Out = zeros(size(x,1), max_lag)
    @inbounds for j = lags
    # Lag series
        lagged = [fill(0,j); Filt[1:length(Filt)-j]]
        # Roll correlation width of lag and lagged version of itself
    @inbounds for i = max_lag:size(x,1)
        Avg_Corr_Out[i,j] = cor(lagged[i-AvgLength+1:i], Filt[i-AvgLength+1:i])
        end
    end

    # Calcualte sine and cosine part
    cosinePart = zeros(size(x,1), max_lag)
    sinePart = zeros(size(x,1), max_lag)
    sqSum = zeros(size(x,1), max_lag)
    @inbounds for j = min_lag:max_lag
        @inbounds for k = 3:max_lag
            cosinePart[:,j] .= cosinePart[:,j] .+ Avg_Corr_Out[:,k] .* cosd(370 * k / j)
            sinePart[:,j] .= sinePart[:,j] .+ Avg_Corr_Out[:,k] .* sind(370 * k / j)
            sqSum[:,j] .= cosinePart[:,j].^2 .+ sinePart[:,j].^2
        end
    end

    # Iterate over every i in j and smooth R by the .2 and .8 factors
    R = zeros(size(x,1), max_lag)
    @inbounds for j = min_lag:max_lag
        @inbounds for i = 2:size(x,1)
            R[i,j] = (.2 * sqSum[i,j]) * (sqSum[i,j]) + (.8 *R[i-1,j])
        end
    end

    #### validated ^^^^^ ###############
    # Find Maximum Power Level for Normalization
    # need to validate this and below!
    MaxPwr = zeros(size(x,1), max_lag)
    #MaxPwr = 0
    @inbounds for j = min_lag:max_lag
        @inbounds for i = 2:size(x,1)
            MaxPwr[i,j] = .995*MaxPwr[i-1,j]
        if R[i,j] > MaxPwr[i,j]
            MaxPwr[i,j]= R[i,j]
            end
        end
    end

    Pwr = zeros(size(x,1), max_lag)
    @inbounds for j = min_lag:max_lag
        @inbounds for i = 2:size(x,1)
            Pwr[i,j] = R[i-1,j] / MaxPwr[i,j]
        end
    end

    # Replace Nan to 0
    @inbounds for j = 1:max_lag
        @inbounds for i = 1:size(x,1)
    if isnan(Pwr[i,j]) == 1
        Pwr[i,j] = 0.0
    else
        Pwr[i,j] = Pwr[i,j]
            end
        end
    end

    # Compute the dominant cycle using the CG of the spectrum
    Spx = zeros(size(x,1))
    Sp = zeros(size(x,1))
    for j = min_lag:max_lag
        Spx .= ifelse.(Pwr[:,j] .>= 0.5, Spx .+ j .* Pwr[:,j],Spx)
        Sp .= ifelse.(Pwr[:,j] .>= 0.5, Sp .+ Pwr[:,j],Sp)
    end

    dominant_cycle = zeros(size(x,1))
    for i = 1:size(x,1)
        if Sp[i] != 0.0
            dominant_cycle[i] = Spx[i] / Sp[i]
        end
    if dominant_cycle[i] < 10
        dominant_cycle[i] = 10
    end
    if dominant_cycle[i] > max_lag
        dominant_cycle[i] = max_lag
    end
    end

    # Adaptive BandPass indicator tunes a BandPass filter to 90% of the period of the Dominant Cycle
    beta1 = cosd.(360 ./ (.9 .* dominant_cycle))
    gamma1 = 1 ./ cosd.(360 .* bandwidth ./ (.9 .* dominant_cycle))
    alpha2 = gamma1 .- sqrt.((gamma1 .* gamma1) .- 1)
    BP = zeros(size(x,1))
    peak = zeros(size(x,1))
    signal = zeros(size(x,1))
    lead = zeros(size(x,1))
    lead_peak = zeros(size(x,1))
    lead_signal = zeros(size(x,1))
    @inbounds for i = 4:size(x,1)
        BP[i] = .5*(1.0 - alpha2[i])*(Filt[i] - Filt[i-2]) + beta1[i]*(1.0 + alpha2[i])*BP[i-1] - alpha2[i]*BP[i-2]
        peak[i] = .991 * peak[i-1]
        if abs(BP[i]) > peak[i]
            peak[i] = abs(BP[i])
        end
        if peak[i] != 0.0
            signal[i] = BP[i] / peak[i]
        end

        lead[i] = 1.3*(signal[i] + signal[i-1] - signal[i-2] - signal[i-3]) / 4

        lead_peak[i] = .93 * lead_peak[i-1]
            if abs(lead[i]) > lead_peak[i]
                lead_peak[i] = abs(lead[i])
            end
            if lead_peak[i] != 0.0
                lead_signal[i] = .7*lead[i] / lead_peak[i]
            end

    end

    return signal
end

@doc """
    PFish(h::Array{Float64}, l::Array{Float64}; n::Int64=10)::Array{Float64}

- Fisher Transform - Equation 15-2 (Variation of)
- Price Normalization
- n = length of normalization period
"""
function PFish(h::Array{Float64}, l::Array{Float64}; n::Int64=10)::Array{Float64}
    @assert n<size(x,1) && n>0 "Argument n out of bounds."
    # Fisher Transform Calculation
    price_input = zeros(size(h,1))
    price_input .= (h .+ l) ./ 2
    MaxH = zeros(size(price_input,1))
    MinL = zeros(size(price_input,1))
    @inbounds for i =n:size(price_input,1)
    MaxH[i] =  maximum(price_input[i-n+1:i])
    MinL[i] = minimum(price_input[i-n+1:i])
    end
    Value1 = zeros(size(price_input,1))
    Fish_out = zeros(size(price_input,1))
    @inbounds for i = n:size(price_input,1)
        Value1[i] = .33*2*((price_input[i] - MinL[i])/(MaxH[i] - MinL[i]) - .5) + .67*Value1[i-1]
        if Value1[i] > .99
            Value1[i] = .999
        else
            Value1[i] = Value1[i]
        end
        if Value1[i] < -.99
            Value1[i] = -.999
        else
            Value1[i] = Value1[i]
        end
        Fish_out[i] = .5*log((1 + Value1[i])/(1 - Value1[i])) + .5*Fish_out[i-1]
    end
        return Fish_out
end


using LinearAlgebra
using Test

using CompScienceMeshes
using BEAST

import CompScienceMeshes.SComplex2D

fn = joinpath(@__DIR__, "assets", "thick_cladding.msh")
G01 = SComplex2D(CompScienceMeshes.read_gmsh_mesh(fn, physical="Gamma01"))
G02 = SComplex2D(CompScienceMeshes.read_gmsh_mesh(fn, physical="Gamma02"))
G12 = SComplex2D(CompScienceMeshes.read_gmsh_mesh(fn, physical="Gamma12"))

G = SComplex2D(CompScienceMeshes.read_gmsh_mesh(fn))

b = boundary(G02)
G = weld(G01,G02,seam=b)
@test numcells(G01) + numcells(G02) == numcells(G)
@test numcells(skeleton(G01,1)) + numcells(skeleton(G02,1)) == numcells(skeleton(G,1)) + numcells(b)

G = weld(G,G12,seam=b)
@test numcells(G01) + numcells(G02) + numcells(G12) == numcells(G)
@test numcells(skeleton(G01,1)) + numcells(skeleton(G02,1)) + numcells(skeleton(G12,1)) == numcells(skeleton(G,1)) + 2*numcells(b)

G1 = weld(-G01, -G12, seam=b)
@test CompScienceMeshes.isoriented(G1)
G2 = weld(-G02, G12, seam=b)
@test CompScienceMeshes.isoriented(G2)

isclosed(m) = (numcells(boundary(m)) == 0)

@test isclosed(G1)
@test isclosed(G2)

E = skeleton(G,1)
E1 = skeleton(G1,1)
A1 = CompScienceMeshes.embedding(E1,E)

nd = BEAST.nedelec(G,E)
@test numfunctions(nd) == numcells(E)
nd1 = BEAST.nedelec(G1,E1)
@test numfunctions(nd1) == numcells(E1)
Z_direct = assemble(BEAST.Identity(), nd1, nd)
Z_expand = assemble(BEAST.Identity(), nd1, nd1) * A1
@test norm(Z_direct - Z_expand, Inf) ≤ √(eps(1.0))


"""
$(TYPEDSIGNATURES)

```jldoctest
julia> random_design((Uniform(2, 3), DiscreteUniform(-1, 5), Uniform(5, 10)), 10)
10×3 Array{Real,2}:
 2.04922   5  8.85741
 2.25659  -1  6.57617
 2.86526   4  5.41422
 2.81201  -1  5.40944
 2.92412   1  6.22902
 2.20051   5  8.80749
 2.69459   3  6.34626
 2.28902   2  8.72759
 2.95173   0  5.42517
 2.35163   4  5.89413

```
"""
function random_design(distributions::Tuple, size::Int)
    permutedims(foldl((x, y) -> hcat(collect(x),
                                     collect(y)),
                      (rand(d) for d in distributions) for i = 1:size))
end


# #----------------------------------------------------------------------------------------------------------------------
# #----------------------------------------------------------------------------------------------------------------------
# This file contains functions and other snippets of code that are used in various calculations.
# #----------------------------------------------------------------------------------------------------------------------
# #----------------------------------------------------------------------------------------------------------------------

#######################################################################################################################
# CALCULATE REGIONAL CO₂ MITIGATION.
########################################################################################################################
# Description: This function calculates regional CO₂ mitigation levels as a function of a global carbon tax. It
#              uses the RICE2010 backstop price values and assumes a carbon tax of $0 in period 1.  If the number of
#              tax values is less than the total number of model time periods, the function assumes full decarbonization
#              (e.g. the tax = the backstop price) for all future periods without a specified tax.
#
# Function Arguments:
#
#       optimal_tax:    A vector of global carbon tax values to be optimized.
#       backstop_price: The regional backstop prices from RICE2010 (units must be in $1000s)
#       theta2:         The exponent on the abatement cost function (defaults to RICE2010 value).
#
# Function Output:
#
#       mitigation:     Regional mitigation rates resulting from the global carbon tax.
#----------------------------------------------------------------------------------------------------------------------

function mitigation_from_tax(optimal_tax::Array{Float64,1}, backstop_prices::Array{Float64,2}, theta2::Float64)

    # Initialize full tax vector with $0 tax in period 1 and maximum of the backstop price across all regions for remaining periods.
    full_tax = [0.0; maximum(backstop_prices, dims=2)[2:end]]

    # Set the periods being optimized to the optimized tax value (assuming full decarbonization for periods after optimization time frame).
    full_tax[2:(length(optimal_tax)+1)] = optimal_tax

    # Calculate regional mitigation rates from the full tax vector.
    mitigation = min.((max.(((full_tax ./ backstop_prices) .^ (1 / (theta2 - 1.0))), 0.0)), 1.0)

    return mitigation
end

#######################################################################################################################
# CREATE RICE OBJECTIVE FUNCTION.
########################################################################################################################
# Description: This function creates an objective function and an instance of RICE with user-specified parameter settings.
#              The objective function will take in a vector of global carbon tax values (cost-minimization) or regional
#              CO₂ mitigation rates (utilitarian) and returns the total economic welfare generated by that specifc
#              climate policy.
#
# Function Arguments:
#
#       run_utilitarian: A true/false indicator for whether or not to run the utilitarian optimization (true = run utilitarian).
#       ρ:                  Pure rate of time preference.
#       η:                  Elasticity of marginal utility of consumption.
#       backstop_prices:    The regional backstop prices from RICE2010 (units must be in dollars).
#       remove_negishi:     A true/false indicator for whether RICE should use a social welfare function with Negishi weights (true = remove Negishi weights).
#                           *Note: if using Negishi weights, RICE's discounting parameters default to η=1.5 and ρ=1.5%.
#
# Function Output:
#
#       rice_objective:     The objective function specific to user model settings.
#       m:                  An instance of RICE2010 consistent with user model settings.
#----------------------------------------------------------------------------------------------------------------------

function construct_rice_objective(run_utilitarian::Bool, ρ::Float64, η::Float64, backstop_prices::Array{Float64,2}, remove_negishi::Bool)

    # Get an instance of RICE given user settings.
    m = create_rice(ρ, η, remove_negishi)

    #--------------------------------------------------------------------------------------------------------
    # Create either a (i) cost-minimization or (ii) utilitarian objective function for this instance of RICE.
    #--------------------------------------------------------------------------------------------------------
    rice_objective = if run_utilitarian == false

        #---------------------------------------
        # Cost-minimzation price objective function.
        #---------------------------------------
        function(optimal_global_tax::Array{Float64,1})
            # Set the regional mitigation rates to the value implied by the global optimal carbon tax and return total welfare.
            update_param!(m, :MIU, mitigation_from_tax(optimal_global_tax, backstop_prices, 2.8))
            run(m)
            return m[:welfare, :UTILITY]
        end

    else

        #---------------------------------------
        # Utilitarian objective function.
        #---------------------------------------
        function(optimal_mitigation_vector::Array{Float64,1})
            # Number of periods with optimized rates (optimization program requires a vector).
            n_opt_periods = Int(length(optimal_mitigation_vector) / 12)
            # NOTE: for convenience, this objective directly optimizes the regional mitigation rates.
            # Initialze a regional mitigation array, assuming first period = 0% mitigation and periods after optimization achieve full decarbonization.
            optimal_regional_mitigation = vcat(zeros(1,12), ones(59,12))
            optimal_regional_mitigation[2:(n_opt_periods + 1), :] = reshape(optimal_mitigation_vector, (n_opt_periods, 12))
            # Set the optimal regional mitigation rates and return total welfare.
            update_param!(m, :MIU, optimal_regional_mitigation)
            run(m)
            return m[:welfare, :UTILITY]
        end

    end

    # Return the newly created objective function and the specific instance of RICE.
    return rice_objective, m
end

#######################################################################################################################
# OPTIMIZE RICE.
########################################################################################################################
# Description: This function takes an objective function (given user-supplied model settings), and optimizes it for the
#              cost-minimization (global carbon taxes) or utilitarian (regional carbon taxes) approach to find the
#              policy that maximizes global economic welfare. Note that the utilitarian objective function optimizes
#              on the decarbonization fraction (for more efficient code) and then calculates the corresponding regional
#              carbon tax values.
#
# Function Arguments:
#
#       optimization_algorithm:  The optimization algorithm to use from the NLopt package.
#       n_opt_periods:           The number of model time periods to optimize over.
#       stop_time:               The length of time (in seconds) for the optimization to run in case things do not converge.
#       tolerance:               Relative tolerance criteria for convergence (will stop if |Δf| / |f| < tolerance from one iteration to the next.)
#       backstop_price:          The regional backstop prices from RICE2010 (units must be in dollars).
#       run_utilitarian:      A true/false indicator for whether or not to run the utilitarian optimization (true = run utilitarian).
#       ρ:                       Pure rate of time preference.
#       η:                       Elasticity of marginal utility of consumption.
#       remove_negishi:          A true/false indicator for whether RICE should use a social welfare function with Negishi weights (true = remove Negishi weights).
#
# Function Output
#
#       optimized_policy_vector: The vector of optimized policy values returned by the optimization algorithm.
#       optimal_emissions:       Optimal emissions for all time periods.
#       optimal_mitigation:      Optimal mitigation rates for all time periods (2005-2595) resulting form the optimization.
#       optimal_tax:             The optimal tax for all periods and regions used to run the model.
#       opt_model:               An instance of RICE (with user-defined settings) set with the optimal CO₂ mitigation policy.
#       convergence_result:      Indicator for whether or not the optimization converged.
#----------------------------------------------------------------------------------------------------------------------

function optimize_rice(optimization_algorithm::Symbol, n_opt_periods::Int, stop_time::Int, tolerance::Float64, backstop_prices::Array{Float64,2}; run_utilitarian::Bool=true, ρ::Float64=0.008, η::Float64=1.5, remove_negishi::Bool=true)

    # -------------------------------------------------------------
    # Create objective function and values needed for optimization.
    #--------------------------------------------------------------

    # Create objective function and instance of RICE, given user settings.
    objective_function, optimal_model = construct_rice_objective(run_utilitarian, ρ, η, backstop_prices, remove_negishi)

    # Set objective function, upper bound, and number of optimzation objectives (will differ between cost-minimization and utilitarian approaches).
    if run_utilitarian == false
        # Number of objectives is equal to time periods being optimized.
        n_objectives = n_opt_periods
        # Upper bound is maximum of backstop price, across all regions.
        upper_bound = maximum(backstop_prices, dims=2)[2:(n_objectives+1)]
    else
        # Number of objectives is equal to time periods being optimizer × 12 regions.
        n_objectives = n_opt_periods * 12
        # Upper bound is 1.0 because we are optimizing the decarbonization rate (1 = full mitigation).
        upper_bound = ones(n_objectives)
    end

    # Create lower bound.
    lower_bound = zeros(n_objectives)

    # Create initial condition for algorithm (set at 50% of upper bound).
    starting_point = upper_bound/2

    # -------------------------------------------------------
    # Create an NLopt optimization object and optimize model.
    #--------------------------------------------------------
    opt = Opt(optimization_algorithm, n_objectives)

    # Set the bounds.
    lower_bounds!(opt, lower_bound)
    upper_bounds!(opt, upper_bound)

    # Assign the objective function to maximize.
    max_objective!(opt, (x, grad) -> objective_function(x))

    # Set termination time.
    maxtime!(opt, stop_time)

    # Set optimizatoin tolerance (will stop if |Δf| / |f| < tolerance from one iteration to the next).
    ftol_rel!(opt, tolerance)

    # Optimize model.
    maximum_objective_value, optimized_policy_vector, convergence_result = optimize(opt, starting_point)

    # Create optimal decarbonization rates for all time periods (approach to do so will differ between cost-minimization and utilitarian).
    if run_utilitarian == false
        optimal_mitigation = mitigation_from_tax(optimized_policy_vector, backstop_prices, 2.8)
    else
        optimal_mitigation = vcat(zeros(1,12), ones(59,12))
        optimal_mitigation[2:(n_opt_periods+1), :] = reshape(optimized_policy_vector, (n_opt_periods, 12))
    end

    # Run user-specified version of RICE with optimal mitigation policy.
    update_param!(optimal_model, :MIU, optimal_mitigation)
    run(optimal_model)

    # Create optimal tax rates for all time periods (approach to do so will differ between cost-minimization and utilitarian).
    if run_utilitarian == false
        optimal_tax = [0.0; maximum(backstop_prices, dims=2)[2:end]]
        optimal_tax[2:(length(optimized_policy_vector)+1)] = optimized_policy_vector
    else
        optimal_tax = optimal_model[:emissions, :CPRICE]
    end

    # Create optimal industiral emissions for all time periods.
    optimal_emissions = optimal_model[:emissions, :EIND]

    # Return results of optimization, optimal emissions, optimal mitigation rates, optimal taxes, and RICE run with optimal mitigation policies.
    return optimized_policy_vector, optimal_emissions, optimal_mitigation, optimal_tax, optimal_model, convergence_result
end

#######################################################################################################################
# LINEAR INTERPOLATION FUNCTION
########################################################################################################################
# Description: This function will carry out a linear interpolation between optimal tax rates for a given time spread to
#              produce a series of annual carbon tax values.
#
# Function Arguments:
#
#       years_with_data: Years along the model time horizon with optimized tax values (will interpolate between these).
#       data:            The data points to be interpolated between (in this case a vector of tax values).
#       spacing:         The number of years between each tax value.
#
# Function Output:
#
#       results:         A vector of interpolated values based on the original input data.
#----------------------------------------------------------------------------------------------------------------------

function interpolate(years_with_data::Array{Int,1}, data::Array{Float64,1}, spacing::Int)
    # Create a vector of the annual years to interpolate across.
    annual_years = collect(years_with_data[1]:years_with_data[end])
    # Create a vector for results and set last value (for convenience).
    results = zeros(length(annual_years))
    results[end] = data[end]
    # Loop through, linearly interpolating between each set of periods with data.
    for index in 1:(length(years_with_data)-1)
        for t = (spacing*(index-1)+1):(spacing*index)
            results[t] = (annual_years[t] - years_with_data[index])/spacing * data[index+1] + (years_with_data[index+1]-annual_years[t])/spacing*data[index]
        end
    end
    # Return interpolated results.
    return results
end

#######################################################################################################################
# CALCULATE INDEX FOR A GIVEN FUND YEAR
########################################################################################################################
# Description: Just for convenience, this function gives the index for a given year within the FUND model time horizon.
#
# Function Arguments:
#
#       index_year: The year to get an index for.
#
# Function Output:
#
#        output:    An index for the supplied year (within the time frame 1950 - final year).
#----------------------------------------------------------------------------------------------------------------------

function fund_year_index(index_year::Int)
    return index_year - 1950 + 1
end

#######################################################################################################################
# CREATE AN ANNUAL GLOBAL CARBON TAX ARRAY
########################################################################################################################
# Description: This function will take a vector of carbon tax values and create a linear interpolation across the extent
#              of the FUND model time horizon. It assumes an optimized tax value every 10 years from 2010-2200 (with linear
#              interpolation between). After 2200, the tax rate stays constant until 2300. The function will replicate
#              this vector for each FUND region so they all face the same carbon tax.
#
# Function Arguments:
#
#       tax:               A vector of tax values.
#       welfare_year:      The year to start calculating how a climate policy affects total welfare (FUND has a historic spin up period).
#       end_year:          The last year to run FUND.
#
# Function Output:
#
#       global_tax_vector: An interpolated tax vector for all time periods, replicated 16 times for each FUND region.
#----------------------------------------------------------------------------------------------------------------------

function create_global_tax_fund(tax::Array{Float64,1}, welfare_year::Int, end_year::Int)
    # Initialize global tax vector for entire length of FUND time horizon.
    global_tax_vector = zeros(length(1950:end_year))
    # Linearly interpolate between values from 2010-2200 (i.e. 20 tax values).
    global_tax_vector[fund_year_index(welfare_year):fund_year_index(2200)] = interpolate(collect(welfare_year:10:2200), tax[1:20], 10)
    # Assume tax rate remains constant after 2200.
    global_tax_vector[fund_year_index(2201):end] .= global_tax_vector[fund_year_index(2200)]
    # Repeat vector 16 times for each region.
    return repeat(global_tax_vector, outer=[1, 16])
end

#######################################################################################################################
# CREATE AN ANNUAL REGIONAL CARBON TAX ARRAY
########################################################################################################################
# Description: This function will take a vector of carbon tax values specific to each FUND region, and create a linear
#              interpolation across the extent of the FUND model time horizon. For each region, it assumes an optimized
#              tax value every 10 years from 2010-2200 (with linear interpolation between). After 2200, the tax rate stays
#              constant until 2300. The function will return an array, where each column is a unique regional tax series.
#
# Function Arguments:
#
#       tax:                 A vector of tax values for all regions.
#       welfare_year:        The year to start calculating how a climate policy affects total welfare (FUND has a historic spin up period).
#       end_year:            The last year to run FUND.
#
# Function Output:
#
#       regional_tax_matrix: A matrix of interpolated tax values for all time periods, with each column representing a FUND region.
#----------------------------------------------------------------------------------------------------------------------

function create_regional_tax_fund(tax::Array{Float64,1}, welfare_year::Int, end_year::Int)
    # Initialize regional tax array for entire length of FUND time horizon.
    regional_tax_matrix = zeros(length(1950:end_year), 16)
    # Reshape optimal tax vector to be a matrix (just makes indexing easier). Each row = a decade's tax value, each column = a region.
    opt_tax = reshape(tax, (20,16))

    # Loop through and create annual tax values for each region.
    for r = 1:16
        regional_tax_matrix[fund_year_index(welfare_year):fund_year_index(2200), r] = interpolate(collect(welfare_year:10:2200), opt_tax[1:20,r], 10)
        # Set all values after 2200 to a constant tax value.
        regional_tax_matrix[fund_year_index(2201):end, r] .= regional_tax_matrix[fund_year_index(2200), r]
    end

    return regional_tax_matrix
end

#######################################################################################################################
# CREATE REGIONAL CO₂ MITIGATION RATES GIVEN A CARBON TAX
########################################################################################################################
# Description: This function will take an instance of FUND run with an optimal carbon tax policy and return the resulting
#              CO₂ mitigation rates for each region. Note that a carbon tax in FUND has both a temporary and permanent
#              effect on emission reductions. The mitigation rates calculated here represent the percentage difference in
#              emissions for a given year relative to baseline case with no carbon tax or climate policy.
#
# Function Arguments:
#
#       optimal_model:    An instance of Mimi-FUND that has been run with an optimal carbon tax policy.
#       end_year:         The last year to run FUND.
#
# Function Output:
#
#       mitigation_rates: An array of CO₂ emission reduction rates caused by the carbon tax for each FUND region.
#----------------------------------------------------------------------------------------------------------------------

function regional_mitigation(optimal_model::Model, end_year::Int)

    # Get baseline version of FUND with no carbon tax but same parameter settings as optimal model.
    base_model = deepcopy(optimal_model)
    update_param!(base_model, :currtax, zeros(length(1950:end_year), 16))
    run(base_model)

    # Initialze an array to store regional mitigation rates.
    mitigation_rates = zeros(length(1950:end_year), 16)

    # Loop through each region and calculate mitigation relative to base case with no policy.
    # Note, first FUND period (1950) is a 'missing' value, so skip that.
    for r = 1:16
        mitigation_rates[2:end,r] = (base_model[:emissions, :emissionwithforestry][2:end,r] .- optimal_model[:emissions, :emissionwithforestry][2:end,r]) ./ base_model[:emissions, :emissionwithforestry][2:end,r]
    end

    return mitigation_rates
end

#######################################################################################################################
# CREATE FUND OBJECTIVE FUNCTION.
########################################################################################################################
# Description: This function creates an objective function and an instance of FUND with user-specified parameter settings.
#              The objective function will take in a vector of global or regional (utilitarian)
#              carbon tax values and returns the total economic welfare generated by that specifc climate policy.
#
# Function Arguments:
#
#       run_utilitarian: A true/false indicator for whether or not to run the utilitarian optimization (true = run utilitarian).
#       ρ:                  Pure rate of time preference.
#       η:                  Elasticity of marginal utility of consumption.
#       welfare_year:       The year to start calculating how a climate policy affects total welfare (FUND has a historic spin up period).
#       end_year:           The last year to run FUND.
#
# Function Output:
#
#       fund_objective:     The objective function specific to user model settings.
#       m:                  An instance of FUND consistent with user model settings.
#----------------------------------------------------------------------------------------------------------------------

function construct_fund_objective(run_utilitarian::Bool, ρ::Float64, η::Float64, welfare_year::Int, end_year::Int)

    # Get an instance of FUND given user settings.
    m = create_fund(ρ, η, welfare_year)

    #--------------------------------------------------------------------------------------------------------
    # Create either a (i) cost-minimization price or (ii) utilitarian objective function for this instance of FUND.
    #--------------------------------------------------------------------------------------------------------
    fund_objective = if run_utilitarian == false

        #---------------------------------------
        # Cost-minimization Price objective function.
        #---------------------------------------
        function(optimal_global_tax::Array{Float64,1})
            # Apply a global carbon tax to each reagion, run the model, and return total welfare.
            update_param!(m, :currtax, create_global_tax_fund(optimal_global_tax, welfare_year, end_year))
            run(m)
            return m[:fund_welfare, :UTILITY][end]
        end

    else

        #---------------------------------------
        # Utilitarian objective function.
        #---------------------------------------
        function regional_tax_fund_objective(regional_tax_vector::Array{Float64,1})
            # Create an array of unique optimal tax paths for each FUND region
            update_param!(m, :currtax, create_regional_tax_fund(regional_tax_vector, welfare_year, end_year))
            run(m)
            return m[:fund_welfare, :UTILITY][end]
        end
    end

    # Return the newly created objective function and the specific instance of RICE.
    return fund_objective, m
end

#######################################################################################################################
# OPTIMIZE FUND.
########################################################################################################################
# Description: This function takes an objective function (given user-supplied model settings), and optimizes it for the
#              cost-minimization (global carbon taxes) or utilitarian (regional carbon taxes) approach to find the
#              policy that maximizes global economic welfare.
#
# Function Arguments:
#
#       optimization_algorithm:  The optimization algorithm to use from the NLopt package.
#       n_opt_periods:           The number of model time periods to optimize over.
#       stop_time:               The length of time (in seconds) for the optimization to run in case things do not converge.
#       tolerance:               Relative tolerance criteria for convergence (will stop if |Δf| / |f| < tolerance from one iteration to the next.)
#       run_utilitarian:      A true/false indicator for whether or not to run the utilitarian optimization (true = run utilitarian).
#       ρ:                       Pure rate of time preference.
#       η:                       Elasticity of marginal utility of consumption.
#       welfare_year:            The year to start calculating how a climate policy affects total welfare (FUND has a historic spin up period).
#       end_year:                The last year to run FUND.
#
# Function Output
#
#       optimized_policy_vector: The vector of optimized policy values returned by the optimization algorithm.
#       optimal_mitigation:      Optimal mitigation rates for all time periods (2005-2595) resulting form the optimization.
#       optimal_tax:             The optimal tax for all periods and regions used to run the model.
#       opt_model:               An instance of FUND (with user-defined settings) set with the optimal CO₂ mitigation policy.
#       convergence_result:      Indicator for whether or not the optimization converged.
#----------------------------------------------------------------------------------------------------------------------

function optimize_fund(optimization_algorithm::Symbol, n_opt_periods::Int, stop_time::Int, tolerance::Float64; run_utilitarian::Bool=true, ρ::Float64=0.008, η::Float64=1.5, welfare_year::Int=2010, end_year::Int=2300)

    # -------------------------------------------------------------
    # Create objective function and values needed for optimization.
    #--------------------------------------------------------------

    # Create objective function and instance of FUND, given user settings.
    objective_function, optimal_model = construct_fund_objective(run_utilitarian, ρ, η, welfare_year, end_year)

    # Set number of optimzation objectives (will differ between cost-minimization and utilitarian approaches).
    if run_utilitarian == false
        # Number of objectives is equal to the number of tax values (one for every 10 years) being optimized.
        n_objectives = n_opt_periods
    else
        # Number of objectives is equal to number of tax values being optimized × 16 regions.
        n_objectives = n_opt_periods * 16
    end

    # Create lower and upper bounds (assume tax cannot go above $5000/ton CO₂).
    lower_bound = zeros(n_objectives)
    upper_bound = ones(n_objectives) .* 5000.0

    # Create initial condition for algorithm (start tax at $50 for all time periods and regions).
    starting_point = ones(n_objectives) .* 50.0

    # -------------------------------------------------------
    # Create an NLopt optimization object and optimize model.
    #--------------------------------------------------------
    opt = Opt(optimization_algorithm, n_objectives)

    # Set the bounds.
    lower_bounds!(opt, lower_bound)
    upper_bounds!(opt, upper_bound)

    # Assign the objective function to maximize.
    max_objective!(opt, (x, grad) -> objective_function(x))

    # Set termination time.
    maxtime!(opt, stop_time)

    # Set optimizatoin tolerance (will stop if |Δf| / |f| < tolerance from one iteration to the next).
    ftol_rel!(opt, tolerance)

    # Optimize model.
    maximum_objective_value, optimized_policy_vector, convergence_result = optimize(opt, starting_point)

    # Calculate the full tax series for all time periods and regions (approach to do so will differ between cost-minimization and utilitarian).
    if run_utilitarian == false
        optimal_tax = create_global_tax_fund(optimized_policy_vector, welfare_year, end_year)
    else
        optimal_tax = create_regional_tax_fund(optimized_policy_vector, welfare_year, end_year)
    end

    # Run user-specified version of FUND with optimal carbon tax.
    update_param!(optimal_model, :currtax, optimal_tax)
    run(optimal_model)

    # Create optimal regional decarbonization rates for all time periods resulting from the carbon tax.
    optimal_mitigation = regional_mitigation(optimal_model, end_year)

    # Return results of optimization, optimal mitigation rates, optimal taxes, and FUND run with optimal mitigation policies.
    return optimized_policy_vector, optimal_mitigation, optimal_tax, optimal_model, convergence_result
end


using Photon
using Knet:relu, softmax

# Define a model with fully connected layers.
model = Sequential(
      Dense(256, relu),
      Dense( 10),
      softmax,
)

# Lets use the CPU for this, even if we have a GPU.
setContext(device=:cpu)

# We can call the model to predict a minibatch
x = randn(Float32, 10, 16)
y = model(x)

# Or use the predict function for a single sample.
predict(model, randn(10))

println("### Done ###")


module ControlSystems

export  LTISystem,
        StateSpace,
        TransferFunction,
        ss,
        tf,
        tfg,
        zpk,
        ss2tf,
        # Linear Algebra
        balance,
        care,
        dare,
        dlyap,
        lqr,
        dlqr,
        kalman,
        dkalman,
        covar,
        norm,
        gram,
        ctrb,
        obsv,
        place,
        # Model Simplification
        reduce_sys,
        sminreal,
        minreal,
        balreal,
        # Stability Analysis
        isstable,
        pole,
        tzero,
        dcgain,
        zpkdata,
        damp,
        dampreport,
        markovparam,
        margin,
        delaymargin,
        gangoffour,
        # Connections
        append,
        series,
        parallel,
        feedback,
        feedback2dof,
        # Discrete
        c2d,
        # Time Response
        step,
        impulse,
        lsim,
        # Frequency Response
        freqresp,
        evalfr,
        bode,
        nyquist,
        sigma,
        # utilities
        numpoly,
        denpoly

import Plots
import Base: +, -, *, /, (./), (==), (.+), (.-), (.*), (!=), isapprox, call, convert, promote_op

include("types/lti.jl")
include("types/transferfunction.jl")
include("types/statespace.jl")
include("types/tf2ss.jl")

include("connections.jl")
include("discrete.jl")
include("matrix_comps.jl")
include("simplification.jl")
include("synthesis.jl")
include("analysis.jl")
include("timeresp.jl")
include("freqresp.jl")
include("utilities.jl")
include("plotting.jl")
include("pid_design.jl")

# The path has to be evaluated upon initial import
const __CONTROLSYSTEMS_SOURCE_DIR__ = dirname(Base.source_path())

end


function numerical_diff(f::Union{Function,Core.Function}, x::Variable, eps::Float64=1e-4)
    x0 = Variable(x.data .- eps)
    x1 = Variable(x.data .+ eps)
    y0::Variable = f(x0)
    y1::Variable = f(x1)
    return (y1.data - y0.data) / (2 * eps)
end


using AQFED, Test
using StatsBase
import AQFED.American:
    AndersenLakeRepresentation,
    AndersenLakeNRepresentation,
    priceAmerican,
    americanBoundaryPutQDP
import AQFED.TermStructure: ConstantBlackModel

@testset "ALNegative" begin
    spot = 80.0
    strike = 100.0
    r = -0.005
    q = -0.05
    tte = 1.0
    vol = 0.2

    model = ConstantBlackModel(vol, r, q)
    pricer = AndersenLakeNRepresentation(model, tte, 1e-8, 5, 16, 21, 201)
    price = priceAmerican(pricer, strike, spot)
    println(price)
    @test isapprox(20.030946627004035, price, atol = 1e-4)
end

@testset "ALQ0" begin
    strike = 100.0
    spot = 80.0
    vol = 0.3
    tte = 0.75
    q = 0.0
    r = 0.04
    model = ConstantBlackModel(vol, r, q)
    pricer = AndersenLakeRepresentation(model, tte, 1e-8, 10, 32, 101, 101)
    refPrice = priceAmerican(pricer, strike, spot)
    pricer = AndersenLakeRepresentation(model, tte, 1e-8, 7, 8, 15, 31)
    price = priceAmerican(pricer, strike, spot)
    println(price, " ", refPrice, " ", price - refPrice)
    @test isapprox(refPrice, price, atol = 1e-5)
    pricer = AndersenLakeRepresentation(model, tte, 1e-8, 16, 16, 31, 63)
    price = priceAmerican(pricer, strike, spot)
    println(price, " ", refPrice, " ", price - refPrice)
    @test isapprox(refPrice, price, atol = 1e-5)

    spot = 100.0
    vol = 0.1
    tte = 0.25
    q = 0.0
    r = 0.02
    model = ConstantBlackModel(vol, r, q)
    pricer = AndersenLakeRepresentation(model, tte, 1e-8, 10, 32, 101, 101)
    refPrice = priceAmerican(pricer, strike, spot)
    pricer = AndersenLakeRepresentation(model, tte, 1e-8, 7, 8, 15, 31)
    price = priceAmerican(pricer, strike, spot)
    println(price, " ", refPrice, " ", price - refPrice)
    @test isapprox(refPrice, price, atol = 1e-5)

end

@testset "ALInstability" begin
    spot = 100.0
    strike = 100.0
    vol = 0.1
    r = 0.1
    q = 0.001
    tte = 3.0
    isCall = false
    model = ConstantBlackModel(vol, r, q)
    pricer = AndersenLakeRepresentation(model, tte, 1e-8, 10, 32, 101, 101)
    price = priceAmerican(pricer, strike, spot)
    println(price)
    @test isapprox(1.7910185909001428, price, atol = 1e-12)
end

skipRInfQ(rInfq::Int, r::Float64, q::Float64)::Bool =
    (rInfq == 1 && r >= q) || (rInfq == -1 && r <= q)

function batchAL(threshold, rInfq, isBatch, m, n, l, p)
    rs = [0.02, 0.04, 0.06, 0.08, 0.10]
    qs = [0.0, 0.04, 0.08, 0.12]
    spots = [25.0, 50.0, 80.0, 90.0, 100.0, 110.0, 120.0, 150.0, 175.0, 200.0]
    ttes = [1.0 / 12, 0.25, 0.5, 0.75, 1.0]
    sigmas = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6]
    strike = 100.0

    refPrices = zeros((length(rs), length(qs), length(ttes), length(sigmas), length(spots)))
    @elapsed for (ir, r) in enumerate(rs), (iq, q) in enumerate(qs)
        if !skipRInfQ(rInfq, r, q)
            for (it, tte) in enumerate(ttes), (iv, vol) in enumerate(sigmas)
                model = ConstantBlackModel(vol, r, q)
                pricer = AndersenLakeRepresentation(model, tte, 1e-8, 16, 16, 31, 63)
                for (is, spot) in enumerate(spots)
                    refPrices[ir, iq, it, iv, is] = priceAmerican(pricer, strike, spot)
                end

            end
        end

    end
    prices = zeros((length(rs), length(qs), length(ttes), length(sigmas), length(spots)))
    elap = @elapsed for (ir, r) in enumerate(rs), (iq, q) in enumerate(qs)
        if !skipRInfQ(rInfq, r, q)
            for (it, tte) in enumerate(ttes), (iv, vol) in enumerate(sigmas)
                model = ConstantBlackModel(vol, r, q)
                local pricer
                if isBatch
                    pricer = AndersenLakeRepresentation(model, tte, 1e-8, m, n, l, p)
                end
                for (is, spot) in enumerate(spots)
                    if refPrices[ir, iq, it, iv, is] > threshold
                        if !isBatch
                            pricer =
                                AndersenLakeRepresentation(model, tte, 1e-8, m, n, l, p)
                        end
                        prices[ir, iq, it, iv, is] = priceAmerican(pricer, strike, spot)
                        if abs(prices[ir, iq, it, iv, is] - refPrices[ir, iq, it, iv, is]) > 0.1
                            println(
                                ir,
                                " ",
                                iq,
                                " ",
                                it,
                                " ",
                                iv,
                                " ",
                                is,
                                " ",
                                refPrices[ir, iq, it, iv, is],
                                " ",
                                prices[ir, iq, it, iv, is],
                                " ",
                                spot,
                                " ",
                                vol,
                                " ",
                                tte,
                                " ",
                                q,
                                " ",
                                r,
                            )
                        end
                    end
                end

            end
        end

    end
    return prices, refPrices, elap
end
@testset "ALPositiveSet" begin
    threshold = 0.5
    rInfq = 0 #1, -1 or 0
    isBatch = false
    prices, refPrices, elap = batchAL(threshold, rInfq, isBatch, 7, 8, 15, 31)
    thrIndices = findall(z -> z > threshold, refPrices)
    mae = maxad(prices[thrIndices], refPrices[thrIndices])
    mre = maxad(prices[thrIndices] ./ refPrices[thrIndices], ones(length(thrIndices)))
    rmse = rmsd(prices[thrIndices], refPrices[thrIndices])
    println(
        "AL ",
        7,
        " ",
        rmse,
        " ",
        mae,
        " ",
        mre,
        " ",
        elap,
        " ",
        length(thrIndices) / elap,
    )

end

@testset "QDHalley" begin
    r = 0.02
    q = 0.04
    vol = 0.4
    T = 0.015
    K = 100.0
    Szero = 90.0
    #Szero = K * min(r / q, 1)
    T = 5.0
    model = ConstantBlackModel(vol, r, q)
    for i = 1:100
        tte = T * i / 100
        # americanBoundaryPutQDP(false, Szero, K, r, q, tte, vol, 1e-6, Halley)
        # americanBoundaryPutQDP(false, Szero, K, r, q, tte, vol, 1e-6, InverseQuadratic)
        Szero = americanBoundaryPutQDP(false, model, Szero, K, tte, 1e-6)
        # Szero = americanBoundaryPutQDP(false, Szero, K, r, q, tte, vol, 1e-6, SuperHalley)
        #        println("Szero = ", Szero)
        @test isless(Szero, 50.0)
    end
    @test isapprox(Szero, 22.665, atol = 1e-3)

end


#Naive convolution method using for loops with some parallel using
#@simd and @inbounds

function convolve!(cLayer::Conv1D, Ai::AbstractArray{T,3}) where {T}

    Aip = padding(cLayer, Ai)

    f_H = cLayer.f
    s_H = cLayer.s
    lastDim = ndims(Ai)
    n_H, c, m = size(cLayer.Z)
    W = cLayer.W
    B = cLayer.B
    @simd for mi = 1:m
        @simd for ci = 1:c
            @simd for hi = 1:n_H
                h_start = hi * s_H - (s_H == 1 ? 0 : s_H - 1)
                h_end = hi * s_H - (s_H == 1 ? 0 : s_H - 1) + f_H - 1
                @inbounds ai = Aip[h_start:h_end, :, mi]
                @inbounds cLayer.Z[hi, ci, mi] = W[:, :, ci] ⋅ ai
                # ai = nothing
                # Base.GC.gc()
            end #for mi=1:m, hi=1:n_H
        end #for ci=1:c
    end #for mi=1:m
    @inbounds cLayer.Z .+= B[:, 1, :]
    Z = cLayer.Z
    actFun = cLayer.actFun
    cLayer.A = eval(:($actFun($Z)))

    W = B = Z = nothing

    Ai = nothing

    # Base.GC.gc()

    return nothing
end #function convolve(cLayer::Conv1D

function convolve!(cLayer::Conv2D, Ai::AbstractArray{T,4}) where {T}

    Aip = padding(cLayer, Ai)

    f_H, f_W = cLayer.f
    s_H, s_W = cLayer.s
    lastDim = ndims(Ai)
    n_H, n_W, c, m = size(cLayer.Z)
    W = cLayer.W
    B = cLayer.B
    @simd for mi = 1:m
        @simd for ci = 1:c
            @simd for wi = 1:n_W
                @simd for hi = 1:n_H
                    h_start = hi * s_H - (s_H == 1 ? 0 : s_H - 1)
                    h_end = hi * s_H - (s_H == 1 ? 0 : s_H - 1) + f_H - 1
                    w_start = wi * s_W - (s_W == 1 ? 0 : s_W - 1)
                    w_end = wi * s_W - (s_W == 1 ? 0 : s_W - 1) + f_W - 1
                    @inbounds ai = Aip[h_start:h_end, w_start:w_end, :, mi]
                    @inbounds cLayer.Z[hi, wi, ci, mi] = W[:, :, :, ci] ⋅ ai
                end #for hi=1:n_H
            end #for mi=1:m, wi=1:n_W, hi=1:n_H
        end #for ci=1:c
    end #for mi=1:m
    @inbounds cLayer.Z .+= B[:, :, 1, :]
    Z = cLayer.Z
    actFun = cLayer.actFun
    cLayer.A = eval(:($actFun($Z)))

    # Base.GC.gc()

    return nothing
end #function convolve(cLayer::Conv2D

function convolve!(cLayer::Conv3D, Ai::AbstractArray{T,5}) where {T}

    Aip = padding(cLayer, Ai)

    f_H, f_W, f_D = cLayer.f
    s_H, s_W, s_D = cLayer.s
    lastDim = ndims(Ai)
    n_H, n_W, n_D, c, m = size(cLayer.Z)
    W = cLayer.W
    B = cLayer.B
    @simd for mi = 1:m
        @simd for ci = 1:c
            @simd for di = 1:n_D
                @simd for wi = 1:n_W
                    @simd for hi = 1:n_H
                        h_start = hi * s_H - (s_H == 1 ? 0 : s_H - 1)
                        h_end = hi * s_H - (s_H == 1 ? 0 : s_H - 1) + f_H - 1
                        w_start = wi * s_W - (s_W == 1 ? 0 : s_W - 1)
                        w_end = wi * s_W - (s_W == 1 ? 0 : s_W - 1) + f_W - 1
                        d_start = di * s_D - (s_D == 1 ? 0 : s_D - 1)
                        d_end = di * s_D - (s_D == 1 ? 0 : s_D - 1) + f_D - 1
                        @inbounds ai = Aip[
                            h_start:h_end,
                            w_start:w_end,
                            d_start:d_end,
                            :,
                            mi,
                        ]
                        @inbounds cLayer.Z[hi, wi, di, ci, mi] =
                            W[:, :, :, :, ci] ⋅ ai
                        # ai = nothing
                        # Base.GC.gc()
                    end #for hi=1:n_H
                end #for wi=1:n_W
            end #for di=1:n_D
        end #for ci=1:c
    end #for mi=1:m
    @inbounds cLayer.Z .+= B[:, :, :, 1, :]
    Z = cLayer.Z
    actFun = cLayer.actFun
    cLayer.A = eval(:($actFun($Z)))

    W = B = Z = nothing

    Ai = nothing

    # Base.GC.gc()

    return nothing
end #function convolve(cLayer::Conv3D

export convolve!

### dconvolve

#Naive convolution method using for loops with some parallel using
#@simd and @inbounds

function dconvolve!(
    cLayer::Conv1D,
    Ai::AbstractArray{T,3},
    dAi::AbstractArray{T,3},
    dZ::AbstractArray{T,3},
) where {T}

    Aip = padding(cLayer, Ai)
    padS = paddingSize(cLayer, Ai)
    dAip = similar(Aip)
    dAip .= 0

    f_H = cLayer.f
    s_H = cLayer.s
    lastDim = ndims(Ai)
    n_H, c, m = size(cLayer.Z)
    W = cLayer.W
    cLayer.dW = similar(W)
    cLayer.dW .= 0
    B = cLayer.B
    cLayer.dB = similar(B)
    cLayer.dB .= 0
    @simd for mi = 1:m
        @simd for ci = 1:c
            for hi = 1:n_H
                h_start = hi * s_H - (s_H == 1 ? 0 : s_H - 1)
                h_end = hi * s_H - (s_H == 1 ? 0 : s_H - 1) + f_H - 1
                @inbounds ai = Aip[h_start:h_end, :, mi]
                @inbounds dAip[h_start:h_end, :, mi] .+=
                    dZ[hi, ci, mi] .* W[:, :, ci]

                @inbounds cLayer.dW[:, :, ci] .+= (ai .* dZ[hi, ci, mi])
                @inbounds cLayer.dB[:, :, ci] .+= dZ[hi, ci, mi]
            end #for
        end #for ci=1:c
    end #for mi=1:m

    dAi .= dAip[1+padS[1]:end-padS[2], :, :]

    return dAi
end #function dconvolve(cLayer::Conv1D

function dconvolve!(
    cLayer::Conv2D,
    Ai::AbstractArray{T,4},
    dAi::AbstractArray{T,4},
    dZ::AbstractArray{T,4},
) where {T}

    Aip = padding(cLayer, Ai)
    padS = paddingSize(cLayer, Ai)
    dAip = similar(Aip)
    dAip .= 0

    f_H, f_W = cLayer.f
    s_H, s_W = cLayer.s
    lastDim = ndims(Ai)
    n_H, n_W, c, m = size(cLayer.Z)
    W = cLayer.W
    cLayer.dW = similar(W)
    cLayer.dW .= 0
    B = cLayer.B
    cLayer.dB = similar(B)
    cLayer.dB .= 0
    @simd for mi = 1:m
        @simd for ci = 1:c
            for wi = 1:n_W, hi = 1:n_H
                h_start = hi * s_H - (s_H == 1 ? 0 : s_H - 1)
                h_end = hi * s_H - (s_H == 1 ? 0 : s_H - 1) + f_H - 1
                w_start = wi * s_W - (s_W == 1 ? 0 : s_W - 1)
                w_end = wi * s_W - (s_W == 1 ? 0 : s_W - 1) + f_W - 1
                @inbounds ai = Aip[h_start:h_end, w_start:w_end, :, mi]
                @inbounds dAip[h_start:h_end, w_start:w_end, :, mi] .+=
                    dZ[hi, wi, ci, mi] .* W[:, :, :, ci]

                @inbounds cLayer.dW[:, :, :, ci] .+= (ai .* dZ[hi, wi, ci, mi])
                @inbounds cLayer.dB[:, :, :, ci] .+= dZ[hi, wi, ci, mi]
            end #for
        end #for ci=1:c
    end #for mi=1:m

    dAi .= dAip[1+padS[1]:end-padS[2], 1+padS[3]:end-padS[4], :, :]

    return dAi
end #function dconvolve(cLayer::Conv2D

function dconvolve!(
    cLayer::Conv3D,
    Ai::AbstractArray{T,5},
    dAi::AbstractArray{T,5},
    dZ::AbstractArray{T,5},
) where {T}

    Aip = padding(cLayer, Ai)
    padS = paddingSize(cLayer, Ai)
    dAip = similar(Aip)
    dAip .= 0

    f_H, f_W, f_D = cLayer.f
    s_H, s_W, s_D = cLayer.s
    lastDim = ndims(Ai)
    n_H, n_W, n_D, c, m = size(cLayer.Z)
    W = cLayer.W
    cLayer.dW = similar(W)
    cLayer.dW .= 0
    B = cLayer.B
    cLayer.dB = similar(B)
    cLayer.dB .= 0
    @simd for mi = 1:m
        @simd for ci = 1:c
            for wi = 1:n_W, hi = 1:n_H, di = 1:n_D
                h_start = hi * s_H - (s_H == 1 ? 0 : s_H - 1)
                h_end = hi * s_H - (s_H == 1 ? 0 : s_H - 1) + f_H - 1
                w_start = wi * s_W - (s_W == 1 ? 0 : s_W - 1)
                w_end = wi * s_W - (s_W == 1 ? 0 : s_W - 1) + f_W - 1
                d_start = di * s_D - (s_D == 1 ? 0 : s_D - 1)
                d_end = di * s_D - (s_D == 1 ? 0 : s_D - 1) + f_D - 1
                @inbounds ai =
                    Aip[h_start:h_end, w_start:w_end, d_start:d_end, :, mi]
                @inbounds dAip[
                    h_start:h_end,
                    w_start:w_end,
                    d_start:d_end,
                    :,
                    mi,
                ] .+= dZ[hi, wi, di, ci, mi] .* W[:, :, :, :, ci]

                @inbounds cLayer.dW[:, :, :, :, ci] .+=
                    (ai .* dZ[hi, wi, ci, mi])
                @inbounds cLayer.dB[:, :, :, :, ci] .+= dZ[hi, wi, di, ci, mi]
            end #for
        end #for ci=1:c
    end #for mi=1:m

    dAi .= dAip[1+padS[1]:end-padS[2],
        1+padS[3]:end-padS[4],
        1+padS[5]:end-padS[6],
        :,
        :,
    ]

    return nothing
end #function dconvolve(cLayer::Conv3D

export dconvolve!


include("../examples/lorenz63.jl")
using Test
using DiffEqSensitivity, OrdinaryDiffEq, ForwardDiff
using Zygote
function time_average_obj(s, n, u0)
	u = copy(u0)
	J_avg = 0.
	for i = 1:n
		u = lorenz63_ad(u, s, 1)
		J_avg += u[end]/n
	end
	return J_avg
end
	s = [10., 28., 8/3]
	m = 1
    n = 200
    n_runup = 5000
	n_samples = 1000
    u0 = rand(3)
	u0 = lorenz63_ad(u0, s, n_runup)
	dJds = zeros(3,n_samples)
	for i = 1:n_samples
		u0 .= lorenz63_ad(u0, s, 1)
		dJds[:,i] .= ForwardDiff.gradient(s->time_average_obj(
										s,n,u0), s)
	end
	@show sum(dJds, dims=2)/n_samples