struct PCA
    Scores::Array{Float64,2}
    Loadings::Array{Float64,2}
    Values::Array
    algorithm::String
end

"""
    PCA_NIPALS(X; Factors = minimum(size(X)) - 1, tolerance = 1e-7, maxiters = 200)

Compute's a PCA from `x` using the NIPALS algorithm with a user specified number of latent variables(`Factors`).
The tolerance is the minimum change in the F norm before ceasing execution. Returns a PCA object.

"""
function PCA_NIPALS(X; Factors = minimum(size(X)) - 1, tolerance = 1e-7, maxiters = 200)
    tolsq = tolerance * tolerance
    #Instantiate some variables up front for performance...
    Xsize = size(X)
    Tm = zeros( ( Xsize[1], Factors ) )
    Pm = zeros( ( Factors, Xsize[2] ) )
    t = zeros( ( 1, Xsize[1] ) )
    p = zeros( ( 1, Xsize[2] ) )
    #Set tolerance to floating point precision
    Residuals = copy(X)
    for factor in 1:Factors
        lastErr = sum(abs.(Residuals)); curErr = tolerance + 1;
        t = Residuals[:, 1]
        iterations = 0
        while (abs(curErr - lastErr) > tolsq) && (iterations < maxiters)
            p = Residuals' * t
            p = p ./ sqrt.( p' * p )
            t = Residuals * p
            #Track change in Frobenius norm
            lastErr = curErr
            curErr = sqrt(sum( ( Residuals - ( t * p' ) ) .^ 2))
            iterations += 1
        end
        Residuals -= t * p'
        Tm[:,factor] = t
        Pm[factor,:] = p
    end
    #Find singular values/eigenvalues
    EigVal = sqrt.( LinearAlgebra.diag( Tm' * Tm ) )
    #Scale loadings by singular values
    Tm = Tm * LinearAlgebra.Diagonal( 1.0 / EigVal )
    return PCA(Tm, Pm, EigVal, "NIPALS")
end

"""
    PCA(X; Factors = minimum(size(X)) - 1)

Compute's a PCA from `x` using LinearAlgebra's SVD algorithm with a user specified number of latent variables(`Factors`).
Returns a PCA object.

"""
function PCA(Z; Factors = minimum(size(Z)) - 1)
    svdres = LinearAlgebra.svd(Z)
    return PCA(svdres.U[:, 1:Factors], svdres.Vt[1:Factors, :], svdres.S[1:Factors], "SVD")
end

"""
    (T::PCA)(Z::Array; Factors = length(T.Values), inverse = false)

Calling a PCA object on new data brings the new data `Z` into or out of (`inverse` = true) the PCA basis.

"""
function (T::PCA)(Z::Array; Factors = length(T.Values), inverse = false)
    if (inverse)
        return Z * (Diagonal(T.Values[1:Factors]) * T.Loadings[1:Factors,:])
    else
        return Z * (Diagonal( 1.0 ./ T.Values[1:Factors]) * T.Loadings[1:Factors,:])'
    end
end

struct LDA
    Scores::Array{Float64,2}
    Loadings::Array{Float64,2}
    Values::Array
end

"""
    LDA(X, Y; Factors = 1)

Compute's a LinearDiscriminantAnalysis transform from `x` with a user specified number of latent variables(`Factors`).
Returns an LDA object.
"""
function LDA(X, Y; Factors = 1)
    (Obs, ClassNumber) = size( Y )
    @assert Factors < ClassNumber
    Variables = size( X )[ 2 ]
    #Instantiate some variables...
    ClassMeans = zeros( ClassNumber, Variables )
    ClassSize = zeros( ClassNumber )
    WithinCovariance = zeros( Variables, Variables )
    BetweenCovariance = zeros( Variables, Variables  )
    ClassCovariance = zeros( Variables, Variables  )
    for class in 1 : ClassNumber
        Members = Y[ :, class ] .== 1
        ClassSize[class] = sum(Members)
        ClassMeans[class,:] = StatsBase.mean(X[Members,:], dims = 1)
    end
    GlobalMean = StatsBase.mean(X, dims = 1)

    for class in 1 : ClassNumber
        Members = Y[ :, class ] .== 1
        #calculate the between class covariance matrix
        Diff = ClassMeans[class,:] .- GlobalMean'
        BetweenCovariance .+= ClassSize[class] .* ( Diff * Diff' )
        #calculate the within class covariance matrix
        for member in findall(Members .== true)
            MeanCentered = X[member,:] .- ClassMeans[class, : ]
            WithinCovariance .+= ( MeanCentered * MeanCentered' )
        end
    end
    #Calculate the discriminant axis'
    #eig = LinearAlgebra.eigen(Base.inv(WithinCovariance) * BetweenCovariance)
    eig = LinearAlgebra.eigen(WithinCovariance \ BetweenCovariance)
    if any( imag.( eig.values ) .> 1e-1)
        println("Warning: Some eigenvalues found to have complex contributions > 0.1")
    end
    #Maybe reccomend to the user to do pca first or centerscale or both?
    ReVals = real.(eig.values)
    Sorted = sortperm( ReVals, rev = true)
    Contributions = ReVals[Sorted] .>= 1e-9
    Loadings = real.(eig.vectors[:,Sorted[ Contributions ][1:Factors] ] )
    #Project the X data into the LDA basis
    Scores = X * Loadings
    return LDA( Scores, Loadings, ReVals[ Sorted[ Contributions][1:Factors] ])
end

"""
    ( model::LDA )( Z; Factors = length(model.Values) )

Calling a LDA object on new data brings the new data `Z` into the LDA basis.

"""
function ( model::LDA )( Z; Factors = length(model.Values) )
     Projected = Z * model.Loadings[:,1:Factors]
end

"""
    HLDA(X, YHOT; K = 1, Factors = 1)

Compute's a Hierarchical LinearDiscriminantAnalysis transform from `x` with a user
specified number of latent variables(`Factors`). The adjacency matrices are created
from `K` nearest neighbors.

Returns an LDA object.
*Note: this can be used with any other LDA functions such as Gaussian discriminants
or explained variance*.

Lu D, Ding C, Xu J, Wang S. Hierarchical Discriminant Analysis. Sensors (Basel).
2018 Jan 18;18(1). pii: E279. doi: 10.3390/s18010279.
"""
function HLDA(X, YHOT; K = 1, Factors = 2)
    Dist = SquareEuclideanDistance( X )
    ICAM = OutOfClassAdjacencyMatrix(Dist, YHOT, K)
    ICD = LinearAlgebra.Diagonal(sum( ICAM, dims = 1) )
    ICS = ICD .- ICAM

    Decomp = eigen(X' * ICS * X)
    ReVals = real.(Decomp.values)
    Sorted = sortperm( ReVals, rev = false)
    Loadings = real.(Decomp.vectors[ : , Sorted[ 1:Factors ] ] )

    Dist = SquareEuclideanDistance( X * Loadings[:,1:Factors] )
    OCAM = InClassAdjacencyMatrix(Dist, YHOT, K)
    OCD = LinearAlgebra.Diagonal(sum( OCAM, dims = 1) )
    OCS = OCD .- OCAM

    Decomp = eigen(X' * OCS * X)
    ReVals = real.(Decomp.values)
    Sorted = sortperm( ReVals, rev = true)
    Contributions = ReVals[Sorted] .>= 1e-16
    if Factors > sum(Contributions)
        Factors = sum(Contributions)
    end
    Loadings = real.(Decomp.vectors[ : , Sorted[ Contributions ][ 1 : Factors ] ] )

    return LDA( X * Loadings,  Loadings, ReVals[ Sorted[ Contributions][1:Factors] ] )
end

#Untested...
struct CanonicalCorrelationAnalysis
    U
    V
    r
end

"""
    CanonicalCorrelationAnalysis(A, B)

Returns a CanonicalCorrelationAnalysis object which contains (U, V, r) from Arrays A and B.
Currently Untested for correctness but should compute....
"""
function CanonicalCorrelationAnalysis(A, B)
    (Obs,Vars) = size(A);;
    CAA = (1/Obs) .* A * A'
    CBB = (1/Obs) .* B * B'
    CAB = (1/Obs) .* A * B'
    maxrank = min( LinearAlgebra.rank( A ), LinearAlgebra.rank( B ) )
    CAAInvSqrt = CAA ^ -0.5
    CBBInvSqrt = CBB ^ -0.5
    singvaldecomp = LinearAlgebra.svd( CAAInvSqrt * CAB * CBBInvSqrt )
    Aprime = CAAInvSqrt * singvaldecomp.U[ :,1 : maxrank ]
    Bprime = CAAInvSqrt * singvaldecomp.Vt[ :,1 : maxrank ]
    return CanonicalCorrelationAnalysis(Aprime' * A, Bprime' * B, singvaldecomp.S[1 : maxrank] )
end

"""
    findpeaks( vY; m = 3)

Finds the indices of peaks in a vector vY with a window span of `2m`.
Original R function by Stas_G:(https://stats.stackexchange.com/questions/22974/how-to-find-local-peaks-valleys-in-a-series-of-data)
This version is based on a C++ variant by me.
"""
function findpeaks( vY; m = 3)
    @assert length(size(vY)) == 1
    sze = size(vY)[1];
    (i,q) = (0,0);#generic iterator, second generic iterator
    (lb,rb) = (0,0);#left bound, right bound
    ret = [];
    for i in 1 : ( sze - 2 )
        #Find all regions with negative laplacian between neighbors
        if (sign( vY[ i + 2 ]  - vY[ i + 1 ] ) - sign( vY[ i + 1 ]  - vY[ i ] ) ) < 0
            #Now assess all regions with negative laplacian between neighbors...
            lb = i - m - 1;# define left bound of vector
            lb = (lb < 1) ? 1 : lb
            rb = i + m + 1# define right bound of vector
            rb = (rb >= (sze-2)) ? rb = (sze-3) : rb
            #We have found a peak by our criterion
            if !any(vY[lb:rb] .> vY[i+1])
                push!( ret, i + 1 );
            end #End if found peak
        end #End if laplace condition
    end #End loop
    return ret
end

function RecursiveAlignment(aligned, reference; maxlags::Int = 800, lookahead::Int = 0, minlength::Int = 20, mincorr::Float64 = 0.05)
    if length(aligned) < minlength
        return aligned;
    end
    #Calculate cross correlation between the spectrum and the reference
    lagspan = min( length( aligned ) - 1, maxlags )
    r = StatsBase.crosscor( aligned, reference, -lagspan : lagspan  )
    maxi = argmax( r ) #Find largest value
    if r[ maxi ] <= mincorr # is the largest correlation junk?
        return aligned
    end
    lag = (maxi > lagspan/2) ? maxi - lagspan - 1 : maxi - 1;

    if lag == 0
        if lookahead <= 0
            return aligned
        end
        lookahead -= 1
    end

    #Pad spectra based on lags
    if !( ( lag == 0 ) || ( lag >= length( aligned ) ) )
        if lag > 0
    	   aligned = vcat( ones( lag ) * aligned[ 1 ], aligned[ 1 : (length(aligned) - lag) ] );
        elseif lag < 0
    	   lag = abs( lag );
    	   aligned = vcat( aligned[ ( lag + 1 ) : length( aligned ) ], ones( lag ) * aligned[ end ] );
        end
    end
    #Find new branch point
    middle = round( length( aligned ) / 2) |> Int
    quarter = floor( middle / 4 ) |> Int
    mini = argmin( aligned[ (middle - quarter ) : ( middle + quarter ) ] ) |> Int
    midpnt = mini + middle - quarter
    #Calculate branched components
    LHS = RecursiveAlignment(aligned[ 1 : midpnt ], reference[ 1 : midpnt ];
                        maxlags = maxlags, lookahead = lookahead, minlength = minlength, mincorr = mincorr);
    RHS = RecursiveAlignment(aligned[ (midpnt + 1) : end ], reference[ (midpnt + 1) : end ];
                        maxlags = maxlags, lookahead = lookahead, minlength = minlength, mincorr = mincorr);
    return vcat( LHS, RHS )
end

"""
    RAFFT(raw, reference; maxlags::Int = 500, lookahead::Int = 1, minlength::Int = 20, mincorr::Float64 = 0.05)

RAFFT corrects shifts in the `raw` spectral bands to be similar to those in a given `reference` spectra through
the use of "recursive alignment by FFT". It returns an array of corrected spectra/chromatograms. The number of maximum lags can be
specified, the `lookahead` parameter ensures that additional recursive executions are performed so the first solution
found is not preemptively accepted, the minimum segment length(`minlength`) can also be specified if FWHM are estimable,
and the minimum cross correlation(`mincorr`) for a match can dictate whether peaks were found to align or not.

*Note* This method works best with flat baselines because it repeats last known values when padding aligned spectra.
It is highly efficient, and in my tests does a good job, but other methods definitely exist. Let me know if other peak Alignment
methods are important for your work-flow, I'll see if I can implement them.

Application of Fast Fourier Transform Cross-Correlation for the Alignment of Large Chromatographic and Spectral Datasets
Jason W. H. Wong, Caterina Durante, and, Hugh M. Cartwright. Analytical Chemistry 2005 77 (17), 5655-5661
"""
function RAFFT(raw, reference; maxlags::Int = 500, lookahead::Int = 1, minlength::Int = 20, mincorr::Float64 = 0.05)
    corrected = zeros( size( raw ) )
    for sample in 1 : size( raw )[ 1 ]
        corrected[ sample, : ] = RecursiveAlignment( raw[ sample, : ], reference;
                                                    maxlags = maxlags,
                                                    lookahead = lookahead,
                                                    minlength = minlength,
                                                    mincorr = mincorr)
    end
    return corrected
end

struct COW
   Nodes::Array
   Lengths::Array
end

"""
    COW( A, B; segments = 20, slack = 1,
               maxslack = Int( floor( length(A) / segments ) ) - 2 )

COW makes a CorrelationOptimizedWarping object which warp corrects spectra `A`
to reference `B`. The user can select the number of segments, slack size,
and optionally the maximum slack parameter.

Note: Not fully tested.

"Aligning of single and multiple wavelength chromatographic profiles for chemometric data analysis using correlation optimised warping"
Nielsen, N. P. V.; Carstensen, J. M.; Smedsgaard, J.Journal of Chromatography A. 1998,805, 17–35.
"""
function COW( A, B;
                  segments = 20, slack = 1,
                  maxslack = Int( floor( length(A) / segments ) ) - 2 )
   @warn "This code (COW) is mostly untested. Please report any bugs!"
   vars = length( A )
   seglen = Int( floor( vars / segments ) )
   #@assert( vars == length( B ), "spectra should have the same dimensionality" )
   @assert(seglen > slack, "Slack cannot exceed the length of segments ($seglen)")
   @assert(maxslack < (seglen - 2), "Maximum slack cannot exceed the length of segments ($seglen - 2)")
   if seglen < 10
      @warn "the segment length is less then 10 bins. This could be a bad idea!"
   end
   seginit = collect(1 : seglen : vars)
   seginit[end] = vars
   extent = ( (length(seginit) - segments) == 1 ) ? (1:segments) : (1:(segments - 1))
   goalsegments = [ B[ seginit[i]:seginit[(i + 1)] ] for i in extent ]
   goallengths = length.(goalsegments)
   slacks = (-slack):slack
   nodes = copy(seginit)
   corr, oldstate = zeros( segments -1 ), zeros(Int, segments - 2 )
   highscore = -1.1 * segments
   bestslacks = zeros( segments-2 )
   iterate = Iterators.product( repeat( [ slacks ], segments - 2 )... )
   for i in iterate
      #Check change
      curstate = reverse( i ) #The reverse is easier to think about for me...
      difference = findfirst( oldstate .!= curstate )
      #Compute state
      update = seginit[(difference+1):(end-1)] .+ curstate[difference:end]
      nodes[ (difference+1):(end-1) ] = collect(update)
      #Check validity of segments
      if all(nodes[2:(end-1)] .<= vars) && all( abs.(nodes[2:(end-1)] .- seginit[2:(end-1)]) .<= maxslack )
         #Get score
         for d in difference : ( segments - 1 )
            span = nodes[ d ]: (nodes[ d + 1 ] - 1)
            resampled = LinearResample( A[ span ], goallengths[d] )
            corr[ d ] = Statistics.cor( resampled, goalsegments[d] )
         end
         #Store the best score
         if sum(corr) > highscore
            highscore = sum(corr)
            bestslacks = curstate
         end
      end
      #Update state
      oldstate = curstate
   end
   #Warp to best state
   warped = copy( A )
   update = seginit[2:(segments-1)] .+  bestslacks
   nodes = copy(seginit)
   nodes[ 2:(segments-1) ] = update
   return COW(nodes, length.(goalsegments) )
end

"""
    (moo::COW)(A)

Applies a learned warping from a COW object to spectrum A.
"""
function (moo::COW)(A)
   warped = []
   for s in 1 : (length(moo.Nodes) - 1)
      if s == 1
         span = 1 : (moo.Nodes[ s + 1 ] - 1 )
      else
         span = (moo.Nodes[ s ]+1) : (moo.Nodes[ s + 1 ] - 1 )
      end
      fudge = (s == (length(moo.Nodes) - 1)) ? -1 : 0
      warped = vcat(warped, LinearResample( A[ span ], moo.Lengths[ s ] + fudge ) )
   end
   return warped
end

"""
    DynamicTimeWarping( A, B )

Applies Dynamic Time Warping of spectrum `A` so it maps to reference `B`.

Returns the DTW distance, shortest path, and the DTW cost matrix. This does not
automatically rescale `A` to `B`, the way inwhich you want to do that is up to the user
for now.

Sakoe, Hiroaki; Chiba, Seibi (1978). "Dynamic programming algorithm optimization for spoken word recognition".
IEEE Transactions on Acoustics, Speech, and Signal Processing. 26 (1): 43–49. doi:10.1109/tassp.1978.1163055.
"""
function DynamicTimeWarping( A, B )
    #TODO: Add different distances
    DTW = SquareEuclideanDistance( A, B )
    Na, Nb = size( DTW )
    DTW[1,:] = cumsum(DTW[1,:])
    DTW[:,1] = cumsum(DTW[:,1])
    for i = 2:Na, j = 2:Nb
        options = DTW[i-1, j-1]
        if i < Na
            options = vcat( options, DTW[i-1, j] )
        end
        if j < Nb
            options = vcat( options, DTW[i, j-1] )
        end
        DTW[i, j] += minimum(options)
    end
    shortestpath = [(Na, Nb)]
    cost = 0.0
    for step in 2 : Na
        lastposition = shortestpath[end]
        if lastposition[1] == 1
            push!(shortestpath, (1, lastposition[2] - 1) )
        elseif lastposition[1] == 1
            push!(shortestpath, (lastposition[1] - 1, 1) )
        else
            legalmoves = [ lastposition .- 1,
                            (lastposition[1], lastposition[2] - 1),
                            (lastposition[1] - 1, lastposition[2]) ]
            bestmove = argmin( DTW[CartesianIndex.( legalmoves )] )
            push!(shortestpath, legalmoves[bestmove] )
        end
    end
    if shortestpath[end] != (1,1)
        push!(shortestpath, (1,1))
    end
    cost = sum( DTW[ CartesianIndex.( shortestpath ) ] )
    return cost, shortestpath, DTW
end

"""
    AssessHealth( X )

Returns a somewhat detailed Dict containing information about the 'health' of a dataset. What is included is the following:
    - PercentMissing: percent of missing entries (includes nothing, inf / nan) in the dataset
    - EmptyColumns: the columns which have only 1 value
    - RankEstimate: An estimate of the rank of X
    - (optional)Duplicates: returns the rows of duplicate observations
"""
function AssessHealth( X; checkduplicates = true )
    (obs, vars) = size( X )
    NumberMissing = sum( map( x -> isa(x, Missing) || isa(x, Nothing) || isnan(x) || isinf(x), X ) )
    PercentMissing = NumberMissing / (obs * vars)
    empties = []
    for c in 1 : vars
        if length(unique(X[:,c])) == 1
            push!(empties, c)
        end
    end
    RankEst = (NumberMissing == 0) ? LinearAlgebra.rank(X) : missing
    InfoDict = Dict("PercentMissing" => PercentMissing,
                    "EmptyColumns" => empties,
                    "RankEstimate" => RankEst)
    if checkduplicates
        if NumberMissing == 0
            dupes = []
            for o1 in 1:obs
                for o2 in (o1 + 1):obs
                    if X[o1,:] == X[o2,:]
                        push!(dupes, o1)
                    end
                end
            end
            InfoDict["Duplicates"] = dupes
        else
            InfoDict["Duplicates"] = missing
        end
    end
    return InfoDict
end


using Revise
using Soss
using ResumableFunctions

hmmStep = @model (s0, step, noise) begin
    s1 ~ EqualMix(step.(s0))
    y ~ noise(s1.x)
end;

function step(s)
    m = @model s begin
        ν ~ HalfCauchy()
        x ~ StudentT(ν, s,1)
    end
    m(s=s)
end;

function noise(s)
    m = @model s begin
        y ~ Normal(s,1)
    end
    m(s=s)
end;

s0 = rand(Normal(0,10), 100);
particles(s0)

rand(hmmStep(s0=s0, step=step, noise=noise)) |> pairs

dynamicHMC(hmmStep(s0=s0, step=step, noise=noise), (y=(y=1.0,),)) |> particles

step(s) = StudentT(3,s,1);
noise(s) = Normal(s,1);

struct Chain
    init
    step
end

@resumable function Base.rand(c::Chain)
    x = rand(c.init)
    @yield x
    while true
        x = rand(c.step(x))
        @yield x
    end
end

# function Distributions.logpdf(c::Chain, xs)

c = Chain(s0, step)

for(n,x) in enumerate(rand(c))
    n > 10 && break
    println(x)
end

m = @model ch begin
    c ~ ch(Normal(), x -> Normal(x,1))
end


sum1 = 0
sum2 = 0
for i = 1:100
    global sum1+=i
    global sum2+=i^2
end
sum1=sum1^2
println(sum1-sum2)


__precompile__()
module eps_InAs_ntype
using QuadGK
export OmegatoeV, m_star, Gamma_ntype, E0_T_InAs,epsFCL,epsIB
#from integration import Integrate
#from scipy.integrate import quad
 #but I can't use QuadGk for larger values of eV, can only functionine function Integrate, tested E = 0.0037eV, get same eps of 10.240628772514803 + 0.03160036047883907im

const e = 1.602*^(10,-19)
const eV = 1.60218*^(10,-19)
const eps_0 = 8.8542*^(10,-12)
const c = 3*^(10,8)
const hbar = 1.05457*^(10,-34) #m2kg/s
const hbar_eV = 6.5821*^(10,-16) #eV s
const m_e =9.109*^(10,-31)#kg
const m_0 = 0.9109*^(10,-30)
const m_0_eV = 0.51099906*^(10,6) #eV

const E0_InAs = 0.42
const dt0_InAs = 0.28*0.001
const b0_InAs = 93
const A_InAs = 14*10^(-9) #for n-type  InAs
const B_InAs = 1.97*10^(-7)
const C_InAs = 57.9*10^(-12) #N in m^(-3)

@inline function OmegatoeV(omega)
    return omega*hbar_eV
end
precompile(OmegatoeV,(Float64,))

@inline function m_star(n) # change m_e according to MB shift
    P_square = 11.9   #eV
    E_g= 0.416 #eV
    term = 8.0*P_square/eV*hbar^2*(3.0*pi^2*n)^(2/3)/(3.0*m_e*E_g^2)
    return (1.0+(4.0*P_square/(3.0*E_g))*(1.0+term)^(-1/2))^(-1)*m_0
end
precompile(m_star, (Float64,))

@inline function Gamma_ntype(N_Base,T)
    umin = 1000.0
    umax = 34000.0
    Nref = 1.1*10^17*10^6 #in m^(-3) since N_base in m^(-3)
    phi = 0.32
    t1 = 1.57
    t2 = 3.0
    mu_franc =  umin + (umax*(300.0/T)^t1-umin)/(1+(N_Base/(Nref*(T/300.0)^t2))^phi)#from Francoeur
    return e/(m_star(N_Base)*mu_franc*10000.0) #s^(-1) change mu from cm^(-2) to m^(-2)
end
precompile(Gamma_ntype, (Float64,Float64))

@inline function E0_T_InAs(N0,T)
    delta_Eg_InAs = A_InAs*(N0/10^6)^(1/3) + B_InAs*(N0/10^6)^(1/4) + C_InAs*(N0/10^6)^(1/2)
    mstar = m_star(N0)
    #Need to include the effect of Moss Burstein shift on E0_InAs??
    E_T = E0_InAs+ (hbar^2/(2*mstar))*(3*pi^2*N0)^(2/3)-dt0_InAs*T^2/(T+b0_InAs)
    return E_T+ delta_Eg_InAs
end
precompile(E0_T_InAs, (Float64,Float64))

@inline function epsFCL(omega,mstar,gamma,N_base)
    eps_inf_InAs = 12.3
    g = 2.89*10.0^11  #s^(-1), gamma
    o_TO = 4.12*10.0^(13) #s^(-1)
    o_LO = 4.58*10.0^(13)
    o_p_square=N_base*e^2/(12.3*eps_0*mstar) #s^(-2)
    return eps_inf_InAs*(1.0+(o_LO^2-o_TO^2)/(o_TO^2-omega^2-im*omega*g)-o_p_square/(omega*(omega+ im*gamma)))
end
precompile(epsFCL,(Float64,Float64,Float64,Float64))

@inline function epsIB(omega,N0,T,E0_T_InAs_value)
    if(hbar_eV*omega<=E0_T_InAs_value)
        alpha_lb_2=0.0
    else
        alpha_lb_2= (3*10^8*(hbar_eV*omega-E0_T_InAs_value))^(1/2)*100
    end
    m_IB_pp = alpha_lb_2*c/(2*omega)
    #imaginary part is 0 when below bandgap
    integratedfn(omega_p) = alpha_lb_2(omega_p,N0,T)/(omega^2-omega_p^2)
    if(hbar_eV*omega<E0_T_InAs_value)
        m_IB_p_value =1.0
    else
     #   result  =1.0 + (c/pi)*quadgk(integratedfn,100.0,10.0^25,rtol=1e-8)[1] # result  =(1.0 + (c/pi)*Integrate(integratedfn, 10000,100.0,10.0^25))
        m_IB_p_value = (1.0 + 0.00026172525)
    end
    return (m_IB_p_value^2-m_IB_pp^2) + im*2*m_IB_p_value*m_IB_pp
end
precompile(epsIB,(Float64,Float64,Float64,Float64))

end
#function eps_InAs_ntype(E_photon::Float64,N_base::Float64)::ComplexF64 #N_base in m^(-3)
    #E_photon= eVtoOmega(omega)
#    omega = eVtoOmega(E_photon)
#    T=300.0 #o_angular = 5.38*10^14   #hbar*5.38*10^14/eV = 0.35eV
#    if(E_photon>0.35)
#        return epsIB(omega,N_base,T) +  epsFCL(omega,N_base,T)
#    else
#        return epsFCL(omega,N_base,T)
#    end

#end

#omegalist= 2.0*10.0^14:5.75*10.0^13:2.5*10.0^15#
#eVlist=  0.2:0.02:1.5
#epslist = []
#for ind = 1:length(eVlist)
#    eps = eps_InAs_ntype(eVlist[ind],10.0^23)
#    println("eps_InAs",eps)
#    push!(epslist, imag(eps))
#end
#plot(eVlist,epslist)
#savefig("test_InAs_real.png")


using LinearAlgebra

"""
    implementation of the Limiter
"""

# functions to be called when there is a need of Limiting the slopes
# data= dofs of particular cell
# k = varible number . which column in dofs to be modify
# basis =basis of a problem , node informaton
# a = a in linear eqaution, of that particular k
# b = b in linear eqaution, of that particular k
# c = c in linear eqaution, of that particular k
function update_solution(data, k, basis, a, b, c, cell)

    linearindex = vec(collect(Iterators.product(1:basis.order, 1:basis.order))) #linear indexing
    cellcenter = cell.center

    for i in 1:length(basis)
        x, y = globalposition(cell, (basis.quadpoints[linearindex[i][1]], basis.quadpoints[linearindex[i][2]]))
        data[i,k] = a + b * ( x - cellcenter[1] ) + c * ( y - cellcenter[2] )
    end

end

# Caluations required to decide limiting
function minmod_limiter(eq, scenario, dofs, grid, basis, ndofs, ndims)
    # Travelling each cell in a grid
    for i in eachindex(grid.cells)
        @views cell = grid.cells[i]
        facetypes = cell.facetypes
        @views data = dofs[:,:, cell.dataidx]
        weights = kron(basis.quadweights, basis.quadweights)

        # calculation of cell (weighted) averages of own cell and its neighbouring cells
        # calculation of a
        # a = ∑_n w_n u_n
        uown = vec(data' * weights)

        # For all neighbouring cells
        u_mean_neighbour = zeros(4,ndofs)
        for (i, neigh) in enumerate(cell.neighbors)
            @views dofsneigh = dofs[:,:,neigh.dataidx]
            u_mean_neighbour[i,:] =  dofsneigh' * weights
        end

        needs_limiting = BitArray(undef, ndofs) #initialise boolean for whether this cell will use limiting
        s_limit = Array{Float64}(undef, ndofs, ndims)

        for j in 1:ndims
            # points for (x - 1/2) or (y - 1/2)
            if j == 1
                points = repeat(basis.quadpoints, length(basis.quadpoints))
            else
                points = vcat(fill.(basis.quadpoints, length(basis.quadpoints))...)
            end

            #for x-dim:  s1 = 12* ∑_n w_n (x_n - 1/2) u_n
            s1 = 12 * vec(sum(weights .* (points .- 0.5) .* data, dims=1))
            s1 /= cell.size

            s2 = (uown - u_mean_neighbour[j, :]) / cell.size
            if facetypes[j] == 2  #at boundaries set slope to 0
                s2 = 0
            end
            s3 = (u_mean_neighbour[j + 2, :] - uown) / cell.size
            if facetypes[j + 2] == 2 #at boundaries set slope to 0
                s3 = 0
            end

            for k in 1:ndofs
                if s1[k]*s2[k] > 0 && s1[k]*s3[k] > 0  #if all have same sign
                    s_limit[k, j] = sign(s1[k])* min(abs(s1[k]),abs(s2[k]), abs(s3[k]))
                else
                    s_limit[k, j] = 0
                end

                if s_limit[k, j] != s1[k]
                    needs_limiting[k] = true
                end

            end

        end
        for k in 1:ndofs
            if needs_limiting[k]
                update_solution(data, k, basis, uown[k], s_limit[k,1], s_limit[k,2], cell)
            end
        end

    end
end


#=
Copyright (c) 2018-2022 Chris Coey, Lea Kapelevich, and contributors

This Julia package Hypatia.jl is released under the MIT license; see LICENSE
file in the root directory or at https://github.com/chriscoey/Hypatia.jl

see "A Direct Formulation for Sparse PCA Using Semidefinite Programming" by
Alexandre d’Aspremont, Laurent El Ghaoui, Michael I. Jordan, Gert R. G. Lanckriet
=#

import Distributions
using SparseArrays

struct SparsePCANative{T <: Real} <: ExampleInstanceNative{T}
    p::Int
    k::Int
    use_epinorminfdual::Bool # use dual of epinorminf cone, else nonnegative cones
    noise_ratio::T
end

function build(inst::SparsePCANative{T}) where {T <: Real}
    (p, k, noise_ratio) = (inst.p, inst.k, inst.noise_ratio)
    @assert 0 < k <= p

    # sample components that will carry the signal
    signal_idxs = Distributions.sample(1:p, k, replace = false)
    if noise_ratio <= 0
        # noiseless model
        x = zeros(T, p)
        x[signal_idxs] = rand(T, k)
        sigma = x * x'
        sigma ./= tr(sigma)
    else
        # simulate some observations with noise
        x = randn(T, p, 100)
        sigma = x * x'
        y = rand(Distributions.Normal(zero(T), noise_ratio), k)
        sigma[signal_idxs, signal_idxs] .+= y * y'
        sigma ./= 100
    end

    dimx = Cones.svec_length(p)
    # x is the svec (lower triangle, row-wise) of the matrix solution
    c = Cones.smat_to_svec!(zeros(T, dimx), -sigma, sqrt(T(2)))
    b = T[1]
    A = zeros(T, 1, dimx)
    for i in 1:p
        A[sum(1:i)] = 1
    end
    hpsd = zeros(T, dimx)
    cones = Cones.Cone{T}[Cones.PosSemidefTri{T, T}(dimx)]

    if inst.use_epinorminfdual
        # l1 cone
        # double off-diagonals, which are already scaled by rt2
        Gl1vec = fill(-one(T), dimx)
        Cones.scale_svec!(Gl1vec, sqrt(T(2)))
        G = [
            sparse(-one(T) * I, dimx, dimx);
            spzeros(T, 1, dimx);
            Diagonal(Gl1vec);
            ]
        h = vcat(hpsd, T(k), zeros(T, dimx))
        push!(cones, Cones.EpiNormInf{T, T}(1 + dimx, use_dual = true))
    else
        l1 = Cones.scale_svec!(ones(T, dimx), sqrt(T(2)))
        G = [
            -I    spzeros(T, dimx, 2 * dimx);
            spzeros(T, 2 * dimx, dimx)    -I;
            spzeros(T, 1, dimx)    repeat(l1', 1, 2);
            ]
        A = [
            sparse(A)    spzeros(T, 1, 2 * dimx);
            -I    -I    I;
            ]
        c = vcat(c, zeros(T, 2 * dimx))
        b = vcat(b, zeros(T, dimx))
        h = vcat(hpsd, zeros(T, 2 * dimx), k)
        push!(cones, Cones.Nonnegative{T}(2 * dimx + 1))
    end

    model = Models.Model{T}(c, A, b, G, h, cones)
    return model
end

function test_extra(
    inst::SparsePCANative{T},
    solve_stats::NamedTuple,
    ::NamedTuple,
    ) where T
    @test solve_stats.status == Solvers.Optimal
    if solve_stats.status == Solvers.Optimal && iszero(inst.noise_ratio)
        # check objective value is correct
        tol = eps(T)^0.25
        @test solve_stats.primal_obj ≈ -1 atol=tol rtol=tol
    end
end


# This file contains the types and definitions for the
# downsampled lippmann-Schwinger equation
using Interpolations

type FastDualDownSampled
    # type to encapsulate the fast application of M = I + omega^2G*spadiagm(nu)
    # Fast:: FastMslow
    Fast:: FastMslowDual
    n  :: Int64
    m  :: Int64
    # downsampling factor in each dimension
    downsampleX :: Int64
    downsampleY :: Int64
    # Sampling Matrix
    S :: SparseMatrixCSC{Float64,Int64}

    function FastDualDownSampled(Fast, downsampleX, downsampleY)
        # Building the Sampling matrix
        index1 = 1:downsampleX:Fast.n
        index2 = 1:downsampleY:Fast.m
        Sindex = spzeros(n,m)
        for ii = 0:round(Integer,(n-1)/downsampleX)
            for jj = 0:round(Integer,(m-1)/downsampleY)
                Sindex[1+ii*downsampleX,1+jj*downsampleY ] = 1
            end
        end

        index = find(Sindex[:])
        S = speye(n*m, n*m)
        Sampling = S[index,:];
        return new( Fast, Fast.n, Fast.m, downsampleX, downsampleY,Sampling)
    end
end

# we need to overload these functions for gmres
import Base.*
import Base.A_mul_B!
import Base.size

function size(M::FastDualDownSampled, dim::Int64)
  # function to returns the size of the underliying matrix i.e. the rank of M.S
  return size(M.S,1)
end

function A_mul_B!(Y,
                  M::FastDualDownSampled,
                  V)
    # in place matrix matrix multiplication
    @assert(size(Y) == size(V))
    # print(size(V))
    for ii = 1:size(V,2)
        Y[:,ii] = M*V[:,ii]
    end
end

function *(M::FastDualDownSampled, b::Array{Complex128,1})
    knots = (collect(1:M.downsampleX:M.n), collect(1:M.downsampleY:M.m))
    nDown = round(Integer, (M.n-1)/M.downsampleX +1)
    mDown = round(Integer, (M.m-1)/M.downsampleY +1)
    B = reshape(b, nDown,mDown);

    itp_real = interpolate(real(B), BSpline(Quadratic(Reflect())), OnGrid())
    itp_imag = interpolate(imag(B), BSpline(Quadratic(Reflect())), OnGrid())

    interpU =     itp_real[collect(M.downsampleX:M.n+M.downsampleX-1)/M.downsampleX,
                           collect(M.downsampleY:M.m+M.downsampleY-1)/M.downsampleY] +
              1im*itp_imag[collect(M.downsampleX:M.n+M.downsampleX-1)/M.downsampleX,
                           collect(M.downsampleY:M.m+M.downsampleY-1)/M.downsampleY];

    u = M.Fast*interpU[:]
    size(u)
    # Subsampling the wavefield
    return (M.S*u)[:]
end


export Sector, SectoralPrism

#= Sector (for a base shape) =#

mutable struct Sector{D,T} <: Shape{2,4,D,T}  # M = 2K
    c::SVector{2,T}  # center of circle
    r::T  # radius of circle
    ϕ₀::T  # center angle bisecting sector: -π ≤ ϕ₀ < π  (π excluded)
    ∆ϕ2::T  # "radius" in angle dimension: 0 ≤ ∆ϕ2 ≤ π (sector spans from ϕ₀ - ∆ϕ2 to ϕ₀ + ∆ϕ2)
    data::D  # auxiliary data
    Sector{D,T}(c,r,ϕ₀,∆ϕ,data) where {D,T<:Real} = new(c,r,ϕ₀,∆ϕ,data)  # suppress default outer constructor
end

function Sector(c::SVector{2,<:Real}, r::Real, ϕ::Real, ∆ϕ::Real, data::D=nothing) where {D}
    r≥0 || throw(ArgumentError("r = $r must be nonnegative."))
    -2π≤∆ϕ≤2π  || throw(ArgumentError("∆ϕ = $∆ϕ must be between -2π and 2π, inclusive."))

    ϕ₀ = rem(ϕ + ∆ϕ/2, 2π, RoundNearest)  # put ϕ₀ in [-π, π)
    ∆ϕ2 = abs(∆ϕ/2)

    return Sector{D}(c, r, ϕ₀, ∆ϕ2, data)
end

Sector(c::AbstractVector{<:Real},  # center of circle
       r::Real,  # radius of circle
       ϕ::Real,  # start angle
       ∆ϕ::Real,  # width in angle dimension; can be negative (-2π ≤ ∆ϕ ≤ 2π)
       data=nothing) =
    Sector(SVector{2}(c), r, ϕ, ∆ϕ, data)

Base.:(==)(s1::Sector, s2::Sector) = s1.c==s2.c && s1.r==s2.r && s1.ϕ₀==s2.ϕ₀ && s1.∆ϕ2==s2.∆ϕ2 && s1.data==s2.data
Base.isapprox(s1::Sector, s2::Sector) = s1.c≈s2.c && s1.r≈s2.r && s1.ϕ₀≈s2.ϕ₀ && s1.∆ϕ2≈s2.∆ϕ2 && s1.data==s2.data
Base.hash(s::Sector, h::UInt) = hash(s.c, hash(s.r, hash(s.ϕ₀, hash(s.∆ϕ2, hash(s.data, hash(:Sector, h))))))

distangle(ϕ::Real, ϕ₀:: Real) = rem(ϕ-ϕ₀, 2π, RoundNearest)  # ϕ measured from ϕ₀; result within [-π, π)

function Base.in(x::SVector{2,<:Real}, s::Sector)
    d = x - s.c
    ld = norm(d)
    ϕ = ld==0 ? s.ϕ₀ : atan(d[2], d[1])  # angle to x with respect to c

    return ld ≤ s.r && abs(distangle(ϕ, s.ϕ₀)) ≤ s.∆ϕ2
end

function surfpt_nearby(x::SVector{2,<:Real}, s::Sector{D,T}) where {D,T<:Real}
    # Basically mimic the same function for Prism, but proceeds in the (ρ,ϕ) domain.
    d = x - s.c
    ld = norm(d)

    # Calculate the closest point in the ρ dimension and outward normal direciton there.
    r2 = s.r / 2
    ρ = ld - r2  # positive if closer to arc; negative if closer to center
    d̂ = ld ≤ Base.rtoldefault(T) * r2  ? SVector(cos(s.ϕ₀),sin(s.ϕ₀)) : normalize(d)

    surfρ = ρ<0 ? 0.0 : s.r  # scalar: closest point to x between center and perimeter point
    noutρ = copysign(1.0,ρ) * d̂  # SVector{2}: outward direction normal at surfρ

    absρ = abs(ρ)
    abs∆ρ = abs(r2 - absρ)  # radial distance between x and either center or perimeter, whichever closer to x

    onbndρ = abs∆ρ ≤ Base.rtoldefault(T) * r2  # basically r2 ≈ ρ but faster
    isoutρ = (r2 < absρ) || onbndρ

    # Calculate the closest point in the ϕ dimension and outward normal direciton there.
    ϕ = ld==0 ? 0.0 : distangle(atan(d[2], d[1]), s.ϕ₀)  # positive if closer to end side; negative if closer to start side

    ϕtemp = ϕ<0 ? s.ϕ₀ - s.∆ϕ2 : s.ϕ₀ + s.∆ϕ2
    surfϕ = SVector(cos(ϕtemp), sin(ϕtemp))  # SVector{2}: closest point in ϕ dimension
    cosθ = d̂⋅surfϕ  # cosine of angle between d and surfϕ
    ldcosθ = ld*cosθ  # ldconθ .* surfϕ is surface point
    ldsinθ = ld*sqrt(1-cosθ^2)  # always positive

    ϕtemp += copysign(π/2,ϕ)
    noutϕ = SVector(cos(ϕtemp), sin(ϕtemp))  # SVector{2}: outward direction normal at surfϕ

    absϕ = abs(ϕ)
    abs∆ϕ = abs(s.∆ϕ2 - absϕ)  # angular distance between x and closer side of sector

    onbndϕ = abs∆ϕ ≤ Base.rtoldefault(T) * s.∆ϕ2  # basically ∆ϕ2 ≈ ϕ but faster
    isoutϕ = (s.∆ϕ2 < absϕ) || onbndϕ

    # Pick the surface point and outward direction normal depending on the location of x.
    if isoutρ && isoutϕ  # x outside in both ρ and ϕ dimensions
        surf = surfρ .* surfϕ  # one of two end points of arc
        nout = (onbndρ && onbndϕ) ? (noutρ + noutϕ) : (d - surf)
        nout = normalize(nout)
    elseif !isoutρ && isoutϕ  # x inside in ρ dimension, but outside in ϕ dimension
        (surf, nout) = (absϕ < s.∆ϕ2+π/2) ? (ldcosθ .* surfϕ, noutϕ) : (SVector(0.0,0.0), d̂)
    elseif isoutρ && !isoutϕ  # x outside in ρ dimension, but inside in ϕ dimension
        (surf, nout) = (surfρ .* d̂, noutρ)
    else  # !isoutρ && !isoutϕ: x strictly inside sector
        (surf, nout) = (abs∆ρ ≤ ldsinθ) ? (surfρ .* d̂, noutρ) : (ldcosθ .* surfϕ, noutϕ)
    end

    return surf+s.c, nout
end

translate(s::Sector{D}, ∆::SVector{2,T}) where {D,T<:Real} = Sector{D,T}(s.c+∆, s.r, s.ϕ₀, s.∆ϕ2, s.data)

function bounds(s::Sector)
    # Find the minimum and maximum coordinates among the center and two ends of the arc.
    ϕ = SVector(s.ϕ₀ - s.∆ϕ2, s.ϕ₀ + s.∆ϕ2)  # [start angle, end angle]
    v = s.r .* [SVector(0.0,0.0) [cos.(ϕ) sin.(ϕ)]']  # [center, start point of arc, end point of arc]

    # Consider using the code below once https://github.com/JuliaArrays/StaticArrays.jl/issues/498
    # is resolved:
    # l = minimum(v, dims=Val(2))[:,1]
    # u = maximum(v, dims=Val(2))[:,1]

    xs = v[1,:]
    ys = v[2,:]

    xmin = minimum(xs)
    xmax = maximum(xs)

    ymin = minimum(ys)
    ymax = maximum(ys)

    # Consider the extreme points on the arc in the Cartesian directions.
    abs(distangle(0, s.ϕ₀)) ≤ s.∆ϕ2 && (xmax = max(xmax,s.r))  # sector contains +x-direction from center
    abs(distangle(π/2, s.ϕ₀)) ≤ s.∆ϕ2 && (ymax = max(ymax, s.r))  # sector contains +y-direction from center
    abs(distangle(π, s.ϕ₀)) ≤ s.∆ϕ2  && (xmin = min(xmin, -s.r))  # sector contains -x-direction from center
    abs(distangle(3π/2, s.ϕ₀)) ≤ s.∆ϕ2  && (ymin = min(ymin, -s.r))  # sector contains -y-direction from center

    return (SVector(xmin,ymin)+s.c, SVector(xmax,ymax)+s.c)
end

#= Sectoral prism =#
const SectoralPrism = Prism{Sector{Nothing}}

# Below, if we called SectoralPrism(c, ...) in the function body, it would call the inner
# constructor Prism{Sector{Nothing}}(c, ...) because SectoralPrism = Prism{Sector{Nothing}},
# which is not what we want.
# To call the outer constructor of Prism, we should call Prism(c, ...) instead of SectoralPrism(c, ...).
SectoralPrism(c::SVector{3,<:Real},
              r::Real,
              ϕ::Real,
              ∆ϕ::Real,
              h::Real=Inf,
              a::SVector{3,<:Real}=SVector(0.0,0.0,1.0),
              data=nothing) where {K} =
    (â = normalize(a); Prism(c, Sector(SVector(0.0,0.0),r,ϕ,∆ϕ), h, [orthoaxes(â)... â], data))

SectoralPrism(c::AbstractVector{<:Real},  # center of prism
              r::Real,  # radius of sectoral base
              ϕ::Real,  # start angle of sectoral base: 0 ≤ ϕₛ < 2π  (2π excluded)
              ∆ϕ::Real,  # end angle sectoral base: 0 ≤ ϕₑ-ϕₛ ≤ 2π
              h::Real=Inf,  # height of prism
              a::AbstractVector{<:Real}=[0.0,0.0,1.0],  # axis direction of prism
              data=nothing) =
    SectoralPrism(SVector{3}(c), r, ϕ, ∆ϕ, h, SVector{3}(a), data)

function bounds_ctrcut(s::SectoralPrism)
    ax = inv(s.p)  # prism axes: columns are not only unit vectors, but also orthogonal
    if ax ≈ I  # prism axes are aligned with Cartesian directions (this covers most usage)
        l, u = bounds(s.b)  # (SVector{2}, SVector{2})
        a₁₂ = ax[:,SVector(1,2)]  # SMatrix{3,2}

        return a₁₂*l, a₁₂*u
    else  # prism axes are not aligned with Cartesian directions
        b = s.b
        r = b.r

        el = Ellipsoid(SVector(0.0,0.0,0.0), SVector(r,r,0.0), ax)  # center is set at origin to return bounds with respect to prism center
        bp = boundpts(el)  # SMatrix{3,3}: boundary points; all three columns of b are free of NaN because no column of ax is aligned with Cartesian directions
        bp′ = s.p * bp  # SMatrix{3,3}: bp in prism coordinates
        bp2′ = bp′[SVector(1,2),:]  # SMatrix{2,3}: in each column of bp′, third entry is in axis dimension, so must be zero mathematically

        ϕ = atan.(bp2′[2,:], bp2′[1,:])  # SVector{3}: angles of boundary points in base plane
        ϕsym = [ϕ; ϕ.+π]  # SVector{6}: include symmetric points with respect to base center
        ϕall = [ϕsym; SVector(b.ϕ₀-b.∆ϕ2, b.ϕ₀+b.∆ϕ2)]  # SVector{8}: angles of all boundary point candidates, except base center

        bpall′ = r .* [cos.(ϕall) sin.(ϕall) @SVector(zeros(8))]'  # SMatrix{3,8}: each column is boundary point candidate in prism coordinates
        bpall = ax * bpall′  # SMatrix{3,8}: each column is boundary point candidate in external coordinates
        bpallc = [bpall SVector(0.0,0.0,0.0)]  # SMatrix{3,9}: include base center
        ind = abs.(distangle.(ϕsym, b.ϕ₀)) .≤ b.∆ϕ2  # SVector{6}: indices of angles contained in base arc
        indallc = [ind; SVector(true,true,true)]  # SVector{9}: include arc ends and base center

        xs = bpallc[1,:]
        ys = bpallc[2,:]
        zs = bpallc[3,:]

        # It might be possible to implement the following more efficiently using reduce().
        xmin = ymin = zmin = Inf
        xmax = ymax = zmax = -Inf
        for i = 1:9
            if indallc[i]
                xmin = min(xmin, xs[i])
                ymin = min(ymin, ys[i])
                zmin = min(zmin, zs[i])

                xmax = max(xmax, xs[i])
                ymax = max(ymax, ys[i])
                zmax = max(zmax, zs[i])
            end
        end

        return (SVector(xmin,ymin,zmin), SVector(xmax,ymax,zmax))
    end
end


path_valid = "C:\\Users\\wilhe\\Dropbox\\My PC (DESKTOP-P6322LG)\\Desktop\\Final Training Work\\Good ANNs\\Subsea Classifier\\SILU_2l_2n_1sig.csv"
nn_data_raw_valid = CSV.read(path_valid, DataFrame)
size_df_valid = size(nn_data_raw_valid)

input_len = 4
hidden_len = 2
output_len = 1
depth = 2
g = swish1

xmin = [8.23365; 0.35171; 0.35; 0.10012]
xmax = [19.5046; 0.8; 0.499985; 0.25]

ymin1 = 0.0
ymax1 = 1.0

# extracts vector of W and b that
W = Matrix{Float64}[]
b = Vector{Float64}[]

# extract first hidden layer info
H1frame = filter(row -> row.Layer === "H1", nn_data_raw_valid)
H1frame_W = H1frame[:, [Symbol("W"*string(i)) for i=1:input_len]]
H1frame_b = H1frame[:, [:b]]
Welement = Matrix{Float64}(H1frame_W)
belement = Matrix{Float64}(H1frame_b)
push!(W, Welement)
push!(b, Float64[belement[i] for i = 1:size(belement,1)])

# extract info for 2 to last hidden layer
for i = 2:depth
    Hiframe = filter(row -> row.Layer === "H$i", nn_data_raw_valid)
    Hiframe_W = Hiframe[:, [Symbol("W"*string(i)) for i=1:hidden_len]]
    Hiframe_b = Hiframe[:, [:b]]
    Welement2 = Matrix{Float64}(Hiframe_W)
    belement2 = Matrix{Float64}(Hiframe_b)
    push!(W, Welement2)
    push!(b, Float64[belement2[i] for i = 1:size(belement2,1)])
end

# extract info for output layer
Hoframe = filter(row -> row.Layer === "Output", nn_data_raw_valid)
Hoframe_W = Hoframe[:, [Symbol("W"*string(i)) for i=1:hidden_len]]
Hoframe_b = Hoframe[:, [:b]]
Welement = Matrix{Float64}(Hoframe_W)
belement = Matrix{Float64}(Hoframe_b)
push!(W, Welement)
push!(b, Float64[belement[i] for i = 1:size(belement,1)])

function ANN_valid(x)
    xs = (x .- xmin)./(xmax .- xmin)

    yi1 = W[1]*xs .+ b[1]
    y1 = swish1.(yi1)

    yi2 = W[2]*y1 .+ b[2]
    y2 = swish1.(yi2)

    yi3 = W[3]*y2 .+ b[3]
    y3 = sigmoid.(yi3)

    return y3[1]*(ymax1 - ymin1) + ymin1
    #return y4[1]*(ymax1 - ymin1) + ymin1
end

path_perform = "C:\\Users\\wilhe\\Dropbox\\My PC (DESKTOP-P6322LG)\\Desktop\\Final Training Work\\Good ANNs\\Subsea Surrogate\\SILU_2l_12n_4sig.csv"
nn_data_raw_perform = CSV.read(path_perform, DataFrame)
size_df_perform = size(nn_data_raw_perform)

input_len = 4
hidden_len = 12
output_len = 4
depth = 2
g = swish1

# extracts vector of W and b that
W2 = Matrix{Float64}[]
b2 = Vector{Float64}[]

# extract first hidden layer info
H1frame = filter(row -> row.Layer === "H1", nn_data_raw_perform)
H1frame_W = H1frame[:, [Symbol("W"*string(i)) for i=1:input_len]]
H1frame_b = H1frame[:, [:b]]
Welement = Matrix{Float64}(H1frame_W)
belement = Matrix{Float64}(H1frame_b)
push!(W2, Welement)
push!(b2, Float64[belement[i] for i = 1:size(belement,1)])

# extract info for 2 to last hidden layer
for i = 2:depth
    Hiframe = filter(row -> row.Layer === "H$i", nn_data_raw_perform)
    Hiframe_W = Hiframe[:, [Symbol("W"*string(i)) for i=1:hidden_len]]
    Hiframe_b = Hiframe[:, [:b]]
    Welement2 = Matrix{Float64}(Hiframe_W)
    belement2 = Matrix{Float64}(Hiframe_b)
    push!(W2, Welement2)
    push!(b2, Float64[belement2[i] for i = 1:size(belement2,1)])
end

# extract info for output layer
Hoframe = filter(row -> row.Layer === "Output", nn_data_raw_perform)
Hoframe_W = Hoframe[:, [Symbol("W"*string(i)) for i=1:hidden_len]]
Hoframe_b = Hoframe[:, [:b]]
Welement = Matrix{Float64}(Hoframe_W)
belement = Matrix{Float64}(Hoframe_b)
push!(W2, Welement)
push!(b2, Float64[belement[i] for i = 1:size(belement,1)])

ymin2 = [0.462387; 0.00946356; 4e+06; 825.87]
ymax2 = [0.799078; 0.138537; 4.01e+06; 911.39]

function ANN_perform(x)
    xs = (x .- xmin)./(xmax .- xmin)

    yi1 = W2[1]*xs .+ b2[1]
    y1 = swish1.(yi1)

    yi2 = W2[2]*y1 .+ b2[2]
    y2 = swish1.(yi2)

    yi3 = W2[3]*y2 .+ b2[3]
    y3 = sigmoid.(yi3)

    ys5 = (ymax2 .- ymin2).*y3 .+ ymin2

    #return y5[1], y5[2], y5[3], y5[4]
    return ys5[1], ys5[2], ys5[3], ys5[4]
end


#
# Invariant manifolds in the CR3BP
#

"""
An alias for some abstract `EnsembleSolution`
with `States` state vector and parameter
vector types.
"""
const AbstractOrbitalEnsembleSolution = SciMLBase.AbstractEnsembleSolution{T,N,<:AbstractVector{U}} where {T,N,U<:Trajectory}

"""
An wrapper for a `SciMLBase.ODESolution` with a `GeneralAstrodynamics.States.AbstractState`
state vector type. This represents an object's `Trajectory` in space!
"""
struct Manifold{FR, S, P, E, O<:AbstractOrbitalEnsembleSolution}
    solution::O
end

"""
Calculates the eigenvector associated with the __stable manifold__
of a Monodromy matrix.
"""
function stable_eigenvector(monodromy::AbstractMatrix; verify=true)
    evals, evecs = eigen(monodromy)
    evals = filter(isreal, evals) .|> real
    evecs = filter(x->!isempty(x), map(vec->filter(x->all(isreal.(vec)), vec), eachcol(evecs))) .|> real

    imin = findmin(evals)[2]
    imax = findmax(evals)[2]

    !verify || @assert (evals[imin] * evals[imax]) ≈ one(eltype(evals)) "Min and max eigenvalue should be multiplicative inverses. Invalid Monodromy matrix. Product equals $(evals[imin] * evals[imax]), not $(one(eltype(evals)))."
    return evecs[imin]
end

"""
Calculates the eigenvector associated with the __unstable manifold__
of a Monodromy matrix.
"""
function unstable_eigenvector(monodromy::AbstractMatrix; verify=true)
    evals, evecs = eigen(monodromy)
    evals = filter(isreal, evals) .|> real
    evecs = filter(x->!isempty(x), map(vec->filter(x->all(isreal.(vec)), vec), eachcol(evecs))) .|> real

    imin = findmin(evals)[2]
    imax = findmax(evals)[2]

    !verify || @assert (evals[imin] * evals[imax]) ≈ one(eltype(evals)) "Min and max eigenvalue should be multiplicative inverses. Invalid Monodromy matrix. Product equals $(evals[imin] * evals[imax]), not $(one(eltype(evals)))."
    return evecs[imax]
end

"""
Perturbs a `CartesianStateWithSTM` in the direction of an
eigenvector `V`.
"""
function perturb(state::CartesianStateWithSTM, V::AbstractVector; verify=true, eps = 1e-8)
    if verify && length(V) != 6
        throw(ArgumentError("Perturbation vector `V` must have length 6. Vector provided has length $(length(V))"))
    end

    V = normalize(States.get_stm(state) * normalize(V))
    r = States.get_r(state) + eps * V[1:3]
    v = States.get_v(state) + eps * V[4:6]

    return CartesianState(vcat(r,v); lengthunit=lengthunit(state), timeunit=timeunit(state), angularunit=angularunit(state))
end

"""
Returns an orbit perturbed in the direction of the local linearization right-multiplied
by the provided eigenvector `V`. Only available for `Trajectory` instances with
`CartesianStateWithSTM` state types.
"""
function perturb(traj::Trajectory{FR, S}, t, V::AbstractVector; eps=1e-8) where {FR, S<:CartesianStateWithSTM}
    state = perturb(state(traj, t), V; eps=eps)
    return Orbit(state, system(traj), epoch(traj,t))
end

"""
Returns an `EnsembleProblem` which represents perturbations
off of a Halo orbit onto a stable or unstable manifold.
Use `kwarg` `direction=Val{:stable}` or `direction=Val{:unstable}`
to specify whether you want to solve for the stable or unstable
invariant manifold about the provided Halo orbit.
All `kwargs` arguments are passed directly to `DifferentialEquations`
solvers.
"""
function SciMLBase.EnsembleProblem(halo::Trajectory{FR, <:CartesianStateWithSTM} where FR, duration::Number=(solution(halo).t[end] - solution(halo).t[1]); direction=Val{:unstable}, distributed=Val{false}, Trajectory=Val{false}, verify=true, eps=1e-8, kwargs...)

    if verify && halo[1][1:6] ≉ halo[end][1:6]
        throw(ArgumentError("The Halo orbit `Trajectory` provided is not periodic!"))
    end

    if direction ∉ (Val{:stable}, Val{:unstable})
        throw(ArgumentError("Invalid direction provided: $(direction)"))
    end

    T₀      = solution(halo).t[1]
    T       = solution(halo).t[end]
    dur     = duration isa Unitful.Time ? duration / timeunit(halo) : duration
    Φ       = monodromy(Orbit(halo, 0), T; verify=verify)
    V       = direction == Val{:stable} ? stable_eigenvector(Φ; verify=verify) : unstable_eigenvector(Φ; verify=verify)
    start   = CartesianState(state(halo, 0))
    nominal = ODEProblem(Orbit(start, system(halo), initialepoch(halo)), dur; kwargs...)

    prob_func   = distributed == Val{true} ? DistributedManifoldIteration(halo, V, dur; verify=verify, eps=eps) : ManifoldIteration(halo, V, dur; verify=verify, eps=eps)
    output_func = Trajectory  == Val{true} ? ManifoldTrajectoryOutput(halo) : ManifoldSolutionOutput(halo)

    return EnsembleProblem(
        nominal; prob_func = prob_func, output_func = output_func, safetycopy = false
    )
end

"""
Returns an `EnsembleProblem` which represents perturbations
off of a Halo orbit onto a stable or unstable manifold.
Use `kwarg` `direction=Val{:stable}` or `direction=Val{:unstable}`
to specify whether you want to solve for the stable or unstable
invariant manifold about the provided Halo orbit.
All `kwargs` arguments are passed directly to `DifferentialEquations`
solvers.
"""
function SciMLBase.EnsembleProblem(orbit::Orbit, period::Number, duration::Number=period; kwargs...)
    cart  = state(orbit) isa CartesianStateWithSTM ? CartesianStateWithSTM(CartesianState(state(orbit))) : CartesianStateWithSTM(state(orbit))
    start = Orbit(cart, system(orbit), epoch(orbit); frame=frame(orbit))
    T     = period isa Unitful.Time ? period / timeunit(orbit) : period
    traj  = propagate(start, T)
    return EnsembleProblem(traj, duration; kwargs...)
end

"""
Distributed perturbation for `EnsembleProblem` itration.
"""
function DistributedManifoldIteration(traj::Trajectory, V::AbstractVector, dur::Real; verify=true, eps=1e-8)
    T₀ = solution(traj).t[1]
    T  = solution(traj).t[end]
    return @everywhere function f(prob, i, repeat)
        t = rand() * T
        remake(prob, u0 = perturb(state(traj, t), V; verify=verify, eps=eps), tspan = (T₀+t, T₀+t+dur))
    end
end

"""
Non-distributed perturbation for `EnsembleProblem` itration.
"""
function ManifoldIteration(traj::Trajectory, V::AbstractVector, dur::Real; verify=true, eps=1e-8)
    T₀ = solution(traj).t[1]
    T  = solution(traj).t[end]
    return function f(prob, i, repeat)
        t = rand() * T
        remake(prob, u0 = perturb(state(traj, t), V; verify=verify, eps=eps), tspan = (T₀+t, T₀+t+dur))
    end
end

"""
Maps a solution iteration within an `EnsembleProblem` solver to a `Trajectory`.
"""
function ManifoldTrajectoryOutput(traj::Trajectory)
    return function f(sol,i)
        e = epoch(traj, sol.t[1])
        return (
            Trajectory{frame(traj), typeof(sol.prob.u0), typeof(sol.prob.p), typeof(e), typeof(sol)}(e, sol),
            false
        )
    end
end

"""
Maps a solution iteration within an `EnsembleProblem` solver to a `Trajectory`.
"""
function ManifoldSolutionOutput(traj::Trajectory)
    return function OutputSolution(sol,i)
        return (sol, false)
    end
end

"""
Perturbs a periodic orbit's `Trajectory` in the direction of the stable or
unstable eigenvector of its `monodromy` matrix to form a stable
or unstable `manifold`.
"""
function manifold(orbit::Orbit, period::Number, duration::Number=period; kwargs...)
    start = Orbit(CartesianStateWithSTM(state(orbit)), system(orbit), epoch(orbit); frame=frame(orbit))
    return manifold(propagate(start, period), duration; kwargs...)
end

"""
Perturbs a periodic orbit `traj` in the direction of the stable or
unstable eigenvector of its `monodromy` matrix to form a stable
or unstable `manifold`.
"""
function manifold(traj::Trajectory{FR, S, P, E}, duration::Number=(solution(traj).t[end] - solution(traj).t[1]); verify=true, direction=Val{:unstable}, algorithm=nothing, ensemble_algorithm=nothing, trajectories=100, kwargs...) where {FR, S<:CartesianStateWithSTM, P, E}
    problem  = EnsembleProblem(traj, duration; Trajectory=Val{true}, direction=direction, verify=verify)
    if isnothing(algorithm)
        isnothing(ensemble_algorithm) || @warn "Argument `algorithm` is nothing: `ensemble_algorithm` keyword argument will be ignored."
        solutions = solve(problem; trajectories=trajectories, kwargs...)
    else
        if isnothing(ensemble_algorithm)
            solutions = solve(problem, algorithm; trajectories=trajectories, kwargs...)
        else
            solutions = solve(problem, algorithm, ensemble_algorithm; trajectories=trajectories, kwargs...)
        end
    end

    return Manifold{FR, typeof(initialstate(first(solutions.u))), P, E, typeof(solutions)}(solutions)
end


# MIT license
# Copyright (c) Microsoft Corporation. All rights reserved.
# See LICENSE in the project root for full license information.

""" Base class for defining points on a lattice. Lattice points are defined by basis vectors eᵢ and lattice indices indexᵢ:

point = Σᵢ indexᵢ*eᵢ

Example: define a 2D basis.

```julia
julia> a = LatticeBasis([1,2],[3,4])
LatticeBasis{2, Int64}(SVector{2, Int64}[[1, 2], [3, 4]])
```

Array indexing is used to generate a lattice point: a[1,2] = 1*[1,2] + 2*[3,4]

```julia
julia> a[1,2]
2-element SVector{2, Int64} with indices SOneTo(2):
  7
 10
```

Subtypes supporting the AbstractBasis interface should implement these functions:

Returns the neighbors in ring n surrounding centerpoint, excluding centerpoint
```
neighbors(::Type{B},centerpoint::Tuple{T,T},neighborhoodsize::Int) where{T<:Real,B<:AbstractBasis}
```
Returns the lattice basis vectors that define the lattice
```
basis(a::S) where{S<:AbstractBasis}
```
Returns the vertices of the unit polygon that tiles the plane for the basis
```
tilevertices(a::S) where{S<:AbstractBasis}
```
"""
abstract type AbstractBasis{N,T <: Real} end
export AbstractBasis

function Base.getindex(A::B1, indices::Vararg{Int,N}) where {N,T,B1 <: AbstractBasis{N,T}}
    return basismatrix(A) * SVector{N,Int}(indices)
end

#     temp::SVector{N,T} = (basis(A)*SVector{N,Int}(indices))::SVector{N,T}
#     return temp
# end

Base.setindex!(A::AbstractBasis{N,T}, v, I::Vararg{Int,N}) where {T,N} = nothing # can't set lattice points. Might want to throw an exception instead.

"""computes the tile vertices at the offset lattice coordinate."""
tilevertices(latticecoords::Union{AbstractVector,NTuple},lattice::AbstractBasis) = lattice[latticecoords...] .+ tilevertices(lattice)

struct LatticeBasis{N,T <: Real} <: AbstractBasis{N,T}
    basisvectors::SMatrix{N,N,T}

    """ Convenience constructor that lets you use tuple arguments to describe the basis instead of SVector

    Example:
    ```
    basis = LatticeBasis{2}((1.0,0.0),(0.0,1.0)) #basis for rectangular lattice
    ```
    This allocates 48 bytes for a 2D basis in Julia 1.6.2. It  shouldn't allocate anything but not performance critical.
    """
    function LatticeBasis(vectors::Vararg{NTuple{N,T},N}) where {T,N}
        temp = MMatrix{N,N,T}(undef)

        for (j, val) in pairs(vectors)
            for i in 1:N
                temp[i,j] = val[i]
            end
        end

        return new{N,T}(SMatrix{N,N,T}(temp))
    end

     LatticeBasis(vectors::SMatrix{N,N,T}) where {N,T <: Real} = new{N,T}(vectors)
end
export LatticeBasis

function basismatrix(a::LatticeBasis{N,T})::SMatrix{N,N,T,N * N} where {N,T}
    return a.basisvectors
end

"""Can access any point in a lattice so the range of indices is unlimited"""
function Base.size(a::LatticeBasis{N,T}) where {N,T}
    return ntuple((i) -> Base.IsInfinite(), N)
end

# These functions are used to find the lattice tiles that lie inside a polygonal shape

"""computes the matrix that transforms the basis set into the canonical basic vectors, eᵢ"""
basisnormalization(a::Repeat.AbstractBasis) = Matrix(inv(Repeat.basismatrix(a))) # unfortunately LazySets doesn't work with StaticArrays. If you return a static Array is messes with their functions.

"""warp the matrix into a coordinate frame where the basis vectors are canonical, eᵢ"""
function normalizedshape(a::Repeat.AbstractBasis, shape::LazySets.VPolygon)
    normalizer = basisnormalization(a)
    return normalizer * shape
end
export normalizedshape

""" compute a conservative range of lattice indices that might intersect containingshape"""
function latticebox(containingshape::LazySets.VPolygon, lattice::Repeat.AbstractBasis)
    warpedshape = normalizedshape(lattice, containingshape)
    box = LazySets.interval_hull(warpedshape)
    return LazySets.Hyperrectangle(round.(box.center), (1, 1) .+ ceil.(box.radius)) # center the hyperrectangle on an integer point and enlarge the radius to take this into account
end
export latticebox

"""Returns the lattice coordinates of all tiles that lie at least partly inside containingshape.

Compute the lattice tiles with non-zero intersection with containingshape. First compute a transformation that maps the lattice basis vectors to canonical unit basis vectors eᵢ (this is the inverse of the lattice basis matrix). Then transform containingshape into this coordinate frame and compute a bounding box. Unit steps along the coordinate axes in this space represent unit *lattice* steps in the original space. This makes it simple to determine coordinate bounds in the original space. Then test for intersection in the original space."""
function tilesinside(containingshape::LazySets.VPolygon, lattice::Repeat.AbstractBasis{2,T}) where {T}
    coordtype = Int64

    box = latticebox(containingshape, lattice)

    coords = coordtype.(box.radius)
    tilevertices = LazySets.VPolygon(Matrix(Repeat.tilevertices(lattice))) # VPolygon will accept StaticArrays but other LazySets function will barf.
    result = Matrix{coordtype}(undef, 2, 0)

    for i in -coords[1]:coords[1]
        for j in -coords[2]:coords[2]
            offsetcoords =   (Int64.(box.center) + [i,j])
            center = Vector(lattice[offsetcoords...]) #[] indexer for lattices returns SVector which LazySets doesn't handle. Convert to conventional Vector.
            offsethex = LazySets.translate(tilevertices, center)

            if !isempty(offsethex ∩ containingshape)
                 result = hcat(result, offsetcoords)
            end
        end
    end
    return result
end
export tilesinside

"""The vertices of the containing shape are the columns of the matrix containingshape"""
tilesinside(containingshape::AbstractMatrix,lattice::Repeat.AbstractBasis) = tilesinside(LazySets.VPolygon(containingshape), lattice)

tilesinside(xmin::T,ymin::T,xmax::T,ymax::T,lattice::Repeat.AbstractBasis) where {T <: AbstractFloat} = tilesinside(SMatrix{2,4,T}(xmin, ymin, xmin, ymax, xmax, ymax, xmax, ymin), lattice)

using Plots

""" to see what the objects look like in the warped coordinate frame use inv(basismatrix(lattice)) as the transform"""
function plotall(containingshape, lattice, transform=[1.0 0.0;0.0 1.0])
    temp = Vector{SMatrix}(undef, 0)

    for coordinate in eachcol(tilesinside(containingshape, lattice))
        println(typeof(coordinate))
        push!(temp, tilevertices(coordinate, lattice))
    end

    tiles = LazySets.VPolygon.(temp)
    for tile in tiles
        plot!(transform * tile, aspectratio=1)
    end
    plot!(containingshape, aspectratio=1)
end
export plotall

function testtilesinside()
    # poly = LazySets.VPolygon([-3.0 3.0 3.0; -3.0 3.0 -3.0])
    hex = HexBasis1()
    poly = LazySets.VPolygon(3 * tilevertices(hex))
    tilecoords = tilesinside(poly, hex)
     insidetiles = LazySets.VPolygon.([tilevertices(x,hex) for x in eachcol(tilecoords)])
    # plotall(poly, hex)
    for tile in insidetiles
        plot!(tile)
end
end
export testtilesinside

"""This function returns the radius of the longest basis vector of the lattice cluster. Most lattices defined in this project have symmetric basis vectors so the radii of all basis vectors will be identical. This function is used in determing which clusters "fit" within the eye pupil."""
function euclideandiameter(basismatrix::SMatrix)
    maximum(norm.([basismatrix[:,i] for i in 1:size(basismatrix)[2]]))
end
export euclideandiameter

""" Lattic bases can have real basis vectors. This returns the maximum Euclidean distance between lattice basis center points."""
euclideandiameter(a::Repeat.AbstractBasis) =  euclideandiameter(Repeat.basismatrix(a))

"""Only will work properly if lattice basis matrix contains only integer or rational terms. Returns the integer lattice coords of point in the given basis if the point is in the span of latticebasis. Otherwise returns nothing"""
function latticepoint(lattice_basis_matrix::AbstractMatrix, origin, point)
    Ainv = inv(Rational.(lattice_basis_matrix))
    b = [(point .- origin)...]
    x = Ainv * b
    if reduce(&, (1, 1) .== denominator.(x))
        return Integer.(x)
    else
        return nothing
    end
end
export latticepoint

latticepoint(latticebasis::AbstractBasis,origin,point) = latticepoint(basismatrix(latticebasis),origin,point)


module DoseResponse

    using LsqFit
    import LsqFit: LsqFitResult
    using StatsBase
    using GLM
    using Distributions
    using LinearAlgebra

    struct DoseResponseModel{Y <: AbstractVector, X <: AbstractVector, P,R,J,W <: AbstractArray} <: StatisticalModel
        result::LsqFitResult{P,R,J,W}
        response::Y
        dose::X
    end

    function DoseResponseModel(response::AbstractVector{T}, dose::AbstractVector{T}, theta0::AbstractVector{T}) where T <: Real
        fit = curve_fit(generalized_logistic_5PL!, jacobian_5PL!, dose, response, theta0, inplace=true)
        DoseResponseModel(fit, response, dose)
    end
    function DoseResponseModel(response::AbstractVector{T}, dose::AbstractVector{T}) where T <: Real
        theta0 = zeros(eltype(response), 5)
        initial_values!(theta0, response, dose)
        fit = curve_fit(generalized_logistic_5PL!, jacobian_5PL!, dose, response, theta0, inplace=true)
        DoseResponseModel(fit, response, dose)
    end
    function initial_values!(theta0::AbstractVector{T}, response::AbstractVector{T}, dose::AbstractVector{T}) where T <: Real
        ymin, ymax = minimum(response), maximum(response)
        theta0[1] = ymin - 0.001 * (ymax-ymin)
        theta0[2] = ymax + 0.001 * (ymax-ymin)

        ytrans = log.((theta0[2] .- response) ./ (response .- theta0[1]))
        idx = findall(dose .> 0)
        m = lm([ones(length(idx)) log.(x[idx])], ytrans[idx])
        theta0[3] = -coef(m)[2]
        theta0[4] = -coef(m)[1]/(-theta0[3])
        theta0[5] = 0.0
    end

    StatsBase.coef(m::DoseResponseModel) = m.result.param
    StatsBase.coefnames(m::DoseResponseModel) = ["A","K","B","M","logv"]
    StatsBase.confint(m::DoseResponseModel) = confidence_interval(m.result)
    StatsBase.dof(m::DoseResponseModel) = length(m.result.param) + 1
    StatsBase.nobs(m::DoseResponseModel) = length(m.result.resid)
    StatsBase.rss(m::DoseResponseModel) = sum(abs2, m.result.resid)
    StatsBase.weights(m::DoseResponseModel) = m.result.wt
    StatsBase.residuals(m::DoseResponseModel) = m.result.resid
    StatsBase.dof_residual(m::DoseResponseModel) = nobs(m) - length(coef(m))
    mse(m::DoseResponseModel) = rss(m)/dof_residual(m)

    function StatsBase.stderror(m::DoseResponseModel; rtol::Real=NaN, atol::Real=0)
        # computes standard error of estimates from
        #   fit   : a LsqFitResult from a curve_fit()
        #   atol  : absolute tolerance for approximate comparisson to 0.0 in negativity check
        #   rtol  : relative tolerance for approximate comparisson to 0.0 in negativity check
        covar = LsqFit.estimate_covar(m.result)
        # then the standard errors are given by the sqrt of the diagonal
        vars = diag(covar)
        vratio = minimum(vars)/maximum(vars)
        if !isapprox(vratio, 0.0, atol=atol, rtol=isnan(rtol) ? Base.rtoldefault(vratio, 0.0, 0) : rtol) && vratio < 0.0
            error("Covariance matrix is negative for atol=$atol and rtol=$rtol")
        end
        return sqrt.(abs.(vars))
    end

    function StatsBase.coeftable(m::DoseResponseModel; level::T=0.95) where T <: Real
        cc = coef(m)
        se = stderror(m)
        tt = cc ./ se
        p = ccdf.(Ref(FDist(1, dof_residual(m))), abs2.(tt))
        ci = se*quantile(TDist(dof_residual(m)), (1-level)/2)
        levstr = isinteger(level*100) ? string(Integer(level*100)) : string(level*100)
        CoefTable(hcat(cc,se,tt,p,cc+ci,cc-ci),
                ["Coef.","Std. Error","t","Pr(>|t|)","Lower $levstr%","Upper $levstr%"],
                coefnames(m), 4, 3)
    end

    StatsBase.loglikelihood(m::DoseResponseModel) = -nobs(m)/2 * (log(2π) + log(rss(m)) - log(nobs(m))+1)

    StatsBase.predict(m::DoseResponseModel) = generalized_logistic_5PL(m.dose, m.result.param)
    StatsBase.predict(m::DoseResponseModel{Y,X,P,R,J,W}, newX::X) where {Y<:AbstractVector,X<:AbstractVector,P,R,J,W<:AbstractArray} = generalized_logistic_5PL(newX, m.result.param)
    StatsBase.mss(m::DoseResponseModel) = sum(abs2, predict(m) .- mean(m.response))
    StatsBase.r2(m::DoseResponseModel) = 1 - rss(m)/sum(abs2,m.response .- mean(m.response))
    StatsBase.islinear(m::DoseResponseModel) = false

    ## 4PL
    function generalized_logistic_4PL!(F, x,theta)
        F .= ifelse.(isfinite.(log.(x)), theta[1] .+ (theta[2] - theta[1]) ./ (1.0 .+ exp.(-theta[3] .* (log.(x) .- theta[4]))), theta[1] + (theta[2] - theta[1]) * ((theta[3] < 0) && ((theta[2] - theta[1]) > 0)))
    end
    function generalized_logistic_4PL(x,theta)
        ifelse.(isfinite.(log.(x)), theta[1] .+ (theta[2] - theta[1]) ./ (1.0 .+ exp.(-theta[3] .* (log.(x) .- theta[4]))), theta[1] + (theta[2] - theta[1]) * ((theta[3] < 0) && ((theta[2] - theta[1]) > 0)))
    end

    function jacobian_4PL!(J, x, theta)
        J[:,1] .= 1 .- (1 .+ exp.(-theta[3] .* (log.(x) .- theta[4])))
        J[:,2] .= (1 .+ exp.(-theta[3] .* (log.(x) .- theta[4])))
        J[:,3] .= ifelse.(isfinite.(log.(x)), (theta[2] - theta[1]) .* (log.(x) .- theta[4]) .* exp.(-theta[3] .* (log.(x) .- theta[4])) ./ (1 .+ exp.(-theta[3] .* (log.(x) .- theta[4])) ), 0.0)
        J[:,4] .=  -(theta[2] - theta[1]) .* theta[3] .* exp.(-theta[3] .* (log.(x) .- theta[4])) ./ (1 .+ exp.(-theta[3] .* (log.(x) .- theta[4])) )
    end

    ## 5PL
    function generalized_logistic_5PL!(F, x, theta)
        F .= ifelse.(isfinite.(log.(x)), theta[1] .+ (theta[2] - theta[1]) ./ (1.0 .+ exp.(-theta[3] .* (log.(x) .- theta[4]))) .^ (1 / exp(theta[5])), theta[1] + (theta[2] - theta[1]) * ((theta[3] < 0) && ((theta[2] - theta[1]) > 0)))
    end
    function generalized_logistic_5PL(x, theta)
        ifelse.(isfinite.(log.(x)), theta[1] .+ (theta[2] - theta[1]) ./ (1.0 .+ exp.(-theta[3] .* (log.(x) .- theta[4]))) .^ (1 / exp(theta[5])), theta[1] + (theta[2] - theta[1]) * ((theta[3] < 0) && ((theta[2] - theta[1]) > 0)))
    end

    function jacobian_5PL!(J, x, theta)
        J[:,1] .= 1 .- (1 .+ exp.(-theta[3] .* (log.(x) .- theta[4]))) .^ (-1 / exp(theta[5]))
        J[:,2] .= (1 .+ exp.(-theta[3] .* (log.(x) .- theta[4]))) .^ (-1 / exp(theta[5]))
        J[:,3] .= ifelse.(isfinite.(log.(x)), (theta[2] - theta[1]) .* (log.(x) .- theta[4]) .* exp.(-theta[3] .* (log.(x) .- theta[4])) ./ (exp(theta[5]) .* (1 .+ exp.(-theta[3] .* (log.(x) .- theta[4]))) .^ (1/exp(theta[5]) + 1)), 0.0)
        J[:,4] .=  -(theta[2] - theta[1]) .* theta[3] .* exp.(-theta[3] .* (log.(x) .- theta[4])) ./ (exp(theta[5]) .* (1 .+ exp.(-theta[3] .* (log.(x) .- theta[4]))) .^ (1/exp(theta[5]) + 1))
        J[:,5] .= (theta[2] - theta[1]) .* log.(1 .+ exp.(-theta[3] .* (log.(x) .- theta[4]))) ./ (exp(theta[5]) .*(1 .+ exp.(-theta[3] .* (log.(x) .- theta[4]))) .^ (1/exp(theta[5])))
    end

end # module


module OptimNewton

function optimize_newton(x, f, g, h;
                         grtol=1e-4,
                         gatol=1e-4,
                         prints=false)
    while true
        fx = f(x)
        gx = g(x)
        hx = h(x)
        #convergence
        abs(abs(gx) / fx) <= grtol && break
        abs(gx) <= gatol && break

        if prints
            println("|G| = $(abs(gx))")
        end

        #move to next p
        x -= gx / hx
    end
    return x
end

end # module


export Bundle
export Clifford
export σmap
export τmap
export πmap
export λ⁻¹map
export S¹action

abstract type Bundle end

"""
    Represents a Clifford bundle, AKA the Hopf fibration of S³.

fields: total and base.
"""
struct Clifford <: Bundle
    total::S³
    base::S²
end

"""
    Clifford(w, z, A, B)

Construct a Clifford bundle with the given Complex numbers: `w`, `z`, `A` and `B`. The ratio
A:B distinguishes the fibers from one another.
"""
Clifford(w::Complex, z::Complex, A::Complex, B::Complex) = begin
    @assert(isapprox(abs(w)^2 + abs(z)^2, 1),
            "The equation |w|² + |z|² = 1 must hold for the given w and z.")
    @assert(!isapprox(abs(A), 0) || !isapprox(abs(B), 0),
            "Aw + Bz = 0, either of A or B can be zero, but not both.")
    Clifford(ComplexPlane(w, z), ComplexLine(A / B))
end

"""
    Clifford(w, z, B)

Construct a Clifford bundle with the given Complex numbers: `w`, `z` and `B`. Here, `B`
on its own identifies a point in the base space and we compute A on the fly.
"""
Clifford(w::Complex, z::Complex, B::Complex) = begin
    # Aw + Bz = 0
    # Aw = -Bz
    # A = -Bz/w
    Clifford(w, z, -B * z / w, B)
end

"""
    σmap(b)

A map that takes the base space to the total space. σ: S² → S³, σ ⊂ [|z₀| ≤ 1, z₁ > 0].
"""
function σmap(b::S²)
    p = Geographic(b)
    z₀ = exp(im * 0) * sqrt((1 + sin(p.θ)) / 2)
    z₁ = exp(im * p.ϕ) * sqrt((1 - sin(p.θ)) / 2)
    ComplexPlane(z₀, z₁)
end

"""
    τmap(b)

A map that takes the base space to the total space. σ: S² → S³, τ ⊂ [z₀ > 0, |z₁| ≤ 1].
"""
function τmap(b::S²)
    z₀, z₁ = vec(ComplexPlane(σmap(b)))
    ComplexPlane(z₁, z₀)
end

"""
    πmap(q)

Sends a point on a unit 3-sphere to a point on a unit 2-sphere with the given
point. S³ ↦ S²
"""
function πmap(q::Quaternion)
    z₁, z₂ = vec(ComplexPlane(q))
    ComplexLine(z₂ / z₁)
end

"""
λ⁻¹map(p)
Sends a point on the plane back to a point on a unit sphere with the given
point. This is the inverse stereographic projection of a 3-sphere.
"""
function λ⁻¹map(p::S²)
    r3 = ℝ³(Cartesian(p))
    p₁, p₂, p₃ = vec(r3)
    mgnitude² = norm(r3)^2
    x₁ = 2p₁ / (1 + mgnitude²)
    x₂ = 2p₂ / (1 + mgnitude²)
    x₃ = 2p₃ / (1 + mgnitude²)
    x₄ = (-1 + mgnitude²) / (1 + mgnitude²)
    Quaternion(x₁, x₂, x₃, x₄)
end

"""
    S¹action(z, s)

Aply the S¹ action on the S³ with the given point in the total space, `z`, and the circle
`s` as in the structure group of the Cliffor bundle, `s` ∈ U(1).
"""
S¹action(z::S³, s::S¹) = begin
    Quaternion(ComplexPlane(exp(im * angle(s)) .* vec(ComplexPlane(z))))
end

S¹action(z::ComplexPlane, s::U1) = ComplexPlane(exp(im * angle(s)) .* vec(z))


using Limbdark
import Limbdark: Transit_Struct
include("occultquad.jl")
#include("../../../src/transit_poly_struct.jl")
include("../../../test/loglinspace.jl")

using Optim
using Printf

if VERSION >= v"0.7"
  using Statistics
end

function sqarea_triangle(a::T,b::T,c::T) where {T <: Real}
# How to compute (sixteen times the) area squared of triangle with
# high precision (Goldberg 1991).
# First, do a quick sort of three numbers:
if c > b
  tmp = c; c = b; b = tmp
end
if c > a
  tmp = a; a = c; c = tmp
end
if b > a
  tmp = b; b = a; a = tmp
end
area = (a+(b+c))*(c-(a-b))*(c+(a-b))*(a+(b-c))
return area
end

function area_overlap(R::T,r::T,b::T) where {T <: Real}
# Compute the light curve of a uniform source:
if b <= abs(R-r)
  area = pi*minimum([r,R])^2
else
  # Twice area of kite-shaped region connecting centers of circles & intersection points:
  kite_area2 = sqrt(abs(sqarea_triangle(R,b,r)))
  # Angle of section for occultor:
  kap0  = atan(kite_area2,(r-R)*(r+R)+b^2)
  # Angle of section for source:
  kap1 = atan(kite_area2,(R-r)*(r+R)+b^2)
  # Flux of visible uniform disk:
  area = R^2*kap1 + r^2*kap0 - 0.5*kite_area2
end
return area
end

# Compute non-linear limb-darkened light curve as a sum over uniform
# sources:

function occult_nonlinear(r::T,b::T,c1::T,c2::T,c3::T,c4::T) where {T <: Real}
#  Computes non-linear limb-darkening with numerical integration
#  using the same approach as Batman:

  function Iofmu(r::T) where {T <: Real}
  mu = sqrt(one(T)-r^2)
  muh = sqrt(mu)
  return one(T)-c1-c2-c3-c4+c1*muh+c2*mu+c3*muh*mu+c4*mu^2
  end

  x0 = maximum([b-r,zero(T)]); xn = minimum([b+r,one(T)])
  theta1 = asin(x0); theta2 = asin(xn);
  nint = 16; delta0 = one(T); delta1 = zero(T)
  tol = 1e-8; iter = 0; itmax = 20
  while abs(delta0-delta1) > tol*delta0 && iter <= itmax
    if VERSION >= v"0.7"
      xgrid = sin.(range(theta1,stop=theta2,length=nint))
    else
      xgrid = sin.(linspace(theta1,theta2,nint))
    end
    delta0 = delta1
    delta1 = zero(T)
    for i=2:nint
      sb = Iofmu(0.5*(xgrid[i]+xgrid[i-1]))
      da = area_overlap(xgrid[i],r,b)-area_overlap(xgrid[i-1],r,b)
      delta1 += sb*da
    end
    nint *=2; iter +=1
  end
return delta1
end

function transit_poly_int(r,b,c,ns)
if b < (1.0+r)
  s_1 = maximum([0.0,b-r]); s_2=minimum([1.0,b+r])
  ds=(s_2-s_1)/ns
  s = linearspace(s_1+.5*ds,s_2-.5*ds,ns)
  fobs = zero(r)
  for j=1:ns
    if s[j] < r-b
      dphi = 2pi
    else
      dphi = 2*acos((s[j]^2+b^2-r^2)/(2*b*s[j]))
    end
    z = sqrt(sqrt(1-s[j]^2))
    imu = 1.0-sum(c)
#    one(T)-c1-c2-c3-c4+c1*muh+c2*mu+c3*muh*mu+c4*mu^2
    for i=1:4
      imu += c[i]*z^i
    end
    fobs += s[j]*ds*dphi*imu
  end
  norm = 1.0-sum(c)
 #omega = (1.0-c1-c2-c3-c4)+0.8*c1+2.0*c2/3.0+4.0*c3/7.0+c4/2.0
  for i=1:4
    norm += 4c[i]/(4.0+i)
  end
  fobs /= norm
#  fobs = 1-fobs
  return fobs
else
  return 1.0
end
end

nx = 1024
if VERSION >= v"0.7"
 x = range(-1.2,stop=1.2,length=nx)
else
 x = linspace(-1.2,1.2,nx)
end
y = 0.0
b = sqrt.(x.^2 .+ y.^2)
c1 = 0.2; c2 = 0.2; c3 = 0.2; c4 = 0.2
r=0.1; flux1 = zeros(nx); flux2 = zeros(nx)
omega = (1.0-c1-c2-c3-c4)+0.8*c1+2.0*c2/3.0+4.0*c3/7.0+c4/2.0
ns = 5000
for i=1:nx
  if b[i] < 1.0+r
    flux1[i]=occult_nonlinear(r,b[i],c1,c2,c3,c4)/omega
    flux2[i]=transit_poly_int(r,b[i],[c1,c2,c3,c4],ns)
  end
end

using PyPlot
clf()
plot(x, 1.0 .- flux1/pi)
flux1 = 1.0 .- flux1/pi
plot(x, 1.0 .- flux2/pi)
flux2 = 1.0 .- flux2/pi
# The following two lines are used to check occult_nonlinear
# when c1 = c3 = 0, which yields the quadratic case:
#u2 = -c4; u1 = c2-2*u2
#fquad = occultquad.(b,u1,u2,r)
#plot(x,fquad,linestyle="--")
savefig("occult_nonlinear.pdf", bbox_inches="tight")

# Now, optimize a fit with polynomial limb-darkening:

function optimize_fit!(r::T,b::Array{T,1},fobs::Array{T,1},fmod::Array{T,1},u_n::Array{T,1}) where {T <: Real}
  nb = length(b)   # Number of points in "light curve"
  nu = length(u_n) # Number of limb-darkening coefficients
  # Optimizes a polynomial limb-darkening model to "data":
  # Compute a limb-darkening model with the impact parameter grid b
  # Initialize:
  trans = transit_init(r,b[1],u_n,true)

  # Compute model:
  function transit_model(u)
    fmodel = zeros(length(b))
    trans = transit_init(r,b[1],u,true)
    for i=1:nb
      trans.b = b[i]
      fmodel[i] = transit_poly!(trans)
    end
    return fmodel
  end

  # Compute "chi-square":
  function transit_fit(u)
    chisquare = 0.0
    trans = transit_init(r,b[1],u,true)
    for i=1:nb
      trans.b = b[i]
      chisquare += (fobs[i]-transit_poly!(trans))^2
    end
    return chisquare
  end

  # Compute gradient of chi-square with respect to limb-darkening coefficients:
  function transit_gradient!(G,u)
    fill!(G,0.0)
    trans = transit_init(r,b[1],u,true)
    for i=1:nb
      trans.b = b[i]
      fmodel = transit_poly!(trans)
      for j = 1:trans.n
        G[j] -= 2*(fobs[i]-fmodel)*trans.dfdu[j]
      end
    end
    return
  end

  # Now, optimize limb-darkening coefficients of the fit:
  result = optimize(transit_fit, transit_gradient! , u_n, LBFGS())
  # Then, compute the model again:
  u_n .= result.minimizer
  println("optimized limb-darkening params: ")
  for i = 1:length(u_n)
    @printf("%.16f, ", u_n[i])
  end
  println("")
return transit_model(u_n)
end

function optimize_fit_linear!(r::T,b::Array{T,1},fobs::Array{T,1},fmod::Array{T,1},u_n::Array{T,1}) where {T <: Real}
  nb = length(b)   # Number of points in "light curve"
  nu = length(u_n) # Number of limb-darkening coefficients
  # Optimizes a polynomial limb-darkening model to "data":
  # Compute a limb-darkening model with the impact parameter grid b
  # Initialize:
  trans = transit_init(r,b[1],u_n,true)
  # Compute model & gradient at each point:
  smat = zeros(T,nu+1,nu+1); fdots = zeros(T,nu+1)
  for i=1:nb
    trans.b = b[i]
    fcur = transit_poly!(trans)
    for j=1:nu+1
      fdots[j] += fobs[i]*trans.sn[j]
      for k=1:nu+1
        smat[j,k] += trans.sn[j]*trans.sn[k]
      end
    end
  end
  # Now, invert to find coefficients of:  f_obs = \sum_j \alpha_j s_j:
  alpha = \(smat,fdots)
  fill!(fmod,zero(T))
  for i=1:nb
    trans.b = b[i]
    fcur = transit_poly!(trans)
    for j=1:nu+1
      fmod[i] += alpha[j]*trans.sn[j]
    end
  end
return fmod
end

clf()
#plot(b,flux)
fig,axes = subplots(2,1, figsize=(8,8))
ax = axes[2]
dev = zeros(6)
flux = flux1
fmod = copy(flux)
#u_n = [.8]
#fmod = optimize_fit!(r,b,flux,fmod,u_n)
#plot(b,flux-fmod,linestyle="--",label="linear")
u_n = [0.1,0.8]
#u_n = push!(u_n,0.01)
fmod = optimize_fit!(r,b,flux,fmod,u_n)
ax[:plot](b,abs.((flux-fmod)),linestyle="--",label="quadratic")
println("quad: ",maximum(abs,flux-fmod)," sig: ",std(flux-fmod))
u_n = [0.1,0.1,0.1]
#u_n = push!(u_n,0.01)
fmod = optimize_fit!(r,b,flux,fmod,u_n)
#ax[:plot](b,abs.((flux-fmod)),linestyle="--",label="cubic")
println("cubic: ",maximum(abs,flux-fmod)," sig: ",std(flux-fmod))
u_n = [0.01,0.01,0.01,0.8]
#u_n = push!(u_n,0.01)
fmod = optimize_fit!(r,b,flux,fmod,u_n)
ax[:plot](b,abs.((flux-fmod)),linestyle="--",label="quartic")
println("quartic: ",maximum(abs,flux-fmod)," sig: ",std(flux-fmod))
u_n = [0.01,0.01,0.01,0.01,0.8]
#u_n = push!(u_n,0.01)
fmod = optimize_fit!(r,b,flux,fmod,u_n)
#ax[:plot](b,abs.((flux-fmod)),linestyle="--",label="quintic")
println("quintic: ",maximum(abs,flux-fmod)," sig: ",std(flux-fmod))
u_n = [0.01,0.01,0.01,0.01,0.01,0.8]
#u_n = push!(u_n,0.01)
fmod = optimize_fit!(r,b,flux,fmod,u_n)
ax[:semilogy](b,abs.((flux-fmod)),linestyle="--",label="sextic")
println("sextic: ",maximum(abs,flux-fmod)," sig: ",std(flux-fmod))
#u_n = [0.01,0.01,0.01,0.01,0.01,0.01,0.8]
#u_n = push!(u_n,-0.01)
#fmod = optimize_fit!(r,b,flux,fmod,u_n)
#ax[:plot](b,abs.((flux-fmod)),linestyle="--",label="septic")
#println("septic: ",maximum(abs,flux-fmod)," sig: ",std(flux-fmod))
#u_n = [0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1]
#fmod = optimize_fit!(r,b,flux,fmod,u_n)
#plot(b,flux-fmod,linestyle="--",label="octic")
#println("octic: ",maximum(abs,flux-fmod)," sig: ",std(flux-fmod))
#u_n = [.1,.1,.1,.1,.1,.1,.1,.1,.1]
#fmod = optimize_fit!(r,b,flux,fmod,u_n)
#plot(b,flux-fmod,linestyle="--",label="nonic")
#println("nonic: ",maximum(abs,flux-fmod)," sig: ",std(flux-fmod))
ax[:legend](loc = "upper left",fontsize=10)
ax[:set_ylabel]("Deviation of fit", fontsize=16)
ax[:set_xlabel](L"$b$", fontsize=18)
ax[:axis]([0,1.2,-10,10])
ax = axes[1]

ax[:plot](b,flux,linewidth=2,label="non-linear")
ax[:plot](b,fmod,linestyle="--",label="sextic",linewidth=2)
ax[:set_title](L"$c_1 = c_2=c_3=c_4=0.2$")
ax[:legend](loc="upper left", fontsize=10)
ax[:axis]([0,1.2,0.984,1.001])
ax[:set_ylabel]("Relative flux", fontsize=16)

show()

savefig("occult_nonlinear_poly.pdf",bbox_inches="tight")


module MatrixAlgebra

export MElement

using LinearAlgebra
using SparseArrays: iswrsparse

struct MElement{T,N,P,M} <: AbstractArray{T,N}
    data::P
    MElement(a::P) where {T,N,P<:AbstractArray{T,N}} = new{T,N,P,mode(P)}(a)
end
mode(P::Type{<:AbstractArray}) = iswrsparse(P) ? :sparse : :dense

struct MFactorization{T,P} <: Factorization{T}
    data::P
    MFactorization(p::P) where {T,P<:Factorization{T}} = new{T,P}(p)
end

import Base: ==, +, -, *
import Base: size, getindex
import LinearAlgebra: lu, ldiv!

size(a::MElement) = size(a.data)
getindex(a::MElement, i...) = getindex(a.data, i...)

-(a::MElement) = MElement(-data.a)
==(a::MElement, b::MElement) = MElement(a.data == b.data)
+(a::MElement, b::MElement) = MElement(a.data + b.data)
-(a::MElement, b::MElement) = MElement(a.data - b.data)
*(a::MElement, b::MElement) = MElement(a.data * b.data)

lu(a::MElement{T,2}) where T = MFactorization(lu(a.data))
ldiv!(a::MFactorization, b) = MElement(ldiv!(a.data, b))

include("synchron.jl")
include("tridiagonalize.jl")
include("toeplitz.jl")
include("givens.jl")
include("zrst.jl")
include("determinant.jl")
include("eigenvalues.jl")
include("rational.jl")

end # module


"""
	make_visual_system(params::Video_Params)

Samples the matrix describing the visual system.
"""
@gen function make_visual_system(params::Video_Params)
	v = Matrix{Real}(undef, length(params.possible_objects), 2)
	#alpha = 2
	#beta = 10
	#alpha_hit = 10
	#beta_hit = 2
	for j = 1:length(params.possible_objects)
		#set lambda when target absent
		#v[j,1] = @trace(Gen.beta(alpha, beta), (:fa, j)) #leads to fa rate of around 0.1
		#v[j,1] = @trace(trunc_normal(0.002, 0.005, 0.0, 1.0), (:lambda_fa, j)) #these are lambdas per receptive field
		v[j,1] = @trace(exponential(100), (:lambda_fa, j)) #these are lambdas per receptive field
		#v[j,1] = @trace(uniform(0.0, 1.0), (:lambda_fa, j))
		#set miss rate when target present
		#v[j,2] = @trace(trunc_normal(0.25, 0.5, 0.0, 1.0), (:miss_rate, j))
		v[j,2] = @trace(uniform(0.0, 1.0), (:miss_rate, j))
	end
	return v
end
################################################################################

"""
	metacog(num_videos::Int64, num_frames::Int64)

Simulates the visual system, `num_videos` scenes each with
`num_frames` frames, and percepts generated by the visual system.
"""
@gen (static) function metacog(num_videos::Int64, num_frames::Int64)

	params = Video_Params()

    #sample parameters
    #set up visual system's parameters
    #Determining visual system V

	n = length(params.possible_objects)
	v = Matrix{Real}(undef, n, 2)

	#v[:,1] = @trace(Map(trunc_normal(0.002, 0.005, 0.0, 1.0), collect(1:n)), :lambda_fa)
	#v[:,2] = @trace(Map(trunc_normal(0.25, 0.5, 0.0, 1.0), collect(1:n)), :miss_rate)

	#temp = @trace(Map(foo)(collect(1:n)), :lambda_fa)

	v = @trace(make_visual_system(params), :v_matrix)

	receptive_fields = make_receptive_fields()

	params = Video_Params(num_receptive_fields = length(receptive_fields))

    fs = fill(num_frames, num_videos) #number of frames per video
    ps = fill(params, num_videos)
	vs = fill(v, num_videos)
	receptive_fieldses = fill(receptive_fields, num_videos)

    @trace(video_chain(fs, ps, vs, receptive_fieldses), :videos)
end

export metacog
export make_visual_system


# A giant whale has decided your submarine is its next meal, and it's much
# faster than you are. There's nowhere to run!
#
# Suddenly, a swarm of crabs (each in its own tiny submarine - it's too deep
# for them otherwise) zooms in to rescue you! They seem to be preparing to
# blast a hole in the ocean floor; sensors indicate a massive underground cave
# system just beyond where they're aiming!
#
# The crab submarines all need to be aligned before they'll have enough power
# to blast a large enough hole for your submarine to get through. However, it
# doesn't look like they'll be aligned before the whale catches you! Maybe you
# can help?
#
# There's one major catch - crab submarines can only move horizontally.
#
# You quickly make a list of the horizontal position of each crab (your puzzle
# input). Crab submarines have limited fuel, so you need to find a way to make
# all of their horizontal positions match while requiring them to spend as
# little fuel as possible.
#
# For example, consider the following horizontal positions:
#
# 16,1,2,0,4,2,7,1,2,14
#
# This means there's a crab with horizontal position 16, a crab with horizontal
# position 1, and so on.
#
# Each change of 1 step in horizontal position of a single crab costs 1 fuel.
# You could choose any horizontal position to align them all on, but the one
# that costs the least fuel is horizontal position 2:
#
# Move from 16 to 2: 14 fuel
# Move from 1 to 2: 1 fuel
# Move from 2 to 2: 0 fuel
# Move from 0 to 2: 2 fuel
# Move from 4 to 2: 2 fuel
# Move from 2 to 2: 0 fuel
# Move from 7 to 2: 5 fuel
# Move from 1 to 2: 1 fuel
# Move from 2 to 2: 0 fuel
# Move from 14 to 2: 12 fuel
#
# This costs a total of 37 fuel. This is the cheapest possible outcome; more
# expensive outcomes include aligning at position 1 (41 fuel), position 3 (39
# fuel), or position 10 (71 fuel).
#
# Determine the horizontal position that the crabs can align to using the least
# fuel possible. How much fuel must they spend to align to that position?

# read input and sort it
positions = sort!(parse.(UInt16, split(readline("input.txt"), ',')))

# Observation: if I pick a point and make all the crabs move there, it'll cost
# some amount of fuel. If I move that point one position to the left, all crabs
# to the left of the original point will spend 1 less fuel, and all crabs to
# the right will spend one more. Vice versa if I move one position to the
# right. So, if I keep track of how many crabs are left and right of my chosen
# point, and the total fuel cost of the crabs to the left and right, I could
# loop through all positions fairly quickly. Starting at position 0 (there are
# no negative positions in the input), there are no crabs to the left, but
# there may be some crabs already at position 0.
fuel_left = 0
fuel_right = sum(positions)
next_break = findfirst(>(0), positions)
num_left = 0
num_centered = next_break - 1
num_right = length(positions) - num_centered
cheapest = fuel_right
for position in 1:positions[end]
  # calculate new fuels
  global (fuel_left, fuel_right) = (fuel_left + num_left + num_centered, fuel_right - num_right)
  if fuel_left + fuel_right < cheapest
    global cheapest = fuel_left + fuel_right
  end

  # calculate how many crabs are already at this position (ie, fuel 0)
  new_num_centered = 0
  if position == positions[next_break]
    new_next_break = findnext(>(position), positions, next_break)
    if isnothing(new_next_break)
      # honestly, when this happens, we can break - continuing to move to the
      # right will only increase fuel cost because all crabs are to the left
      break
    end

    new_num_centered = new_next_break - next_break
    global next_break = new_next_break
  end

  # update crab counts
  global (num_left, num_centered, num_right) = (num_left + num_centered, new_num_centered, num_right - new_num_centered)
end

println("Cheapest fuel cost: ", cheapest)


srand(0)

facts("qraddrow") do

    m, n = 3, 3
    A = randn(m,m)
    Q, R = qr(A)
    for i in 1:100
        a = randn(m)'
        A = [A; a]
        R = qraddrow(R, a)
        @fact vecnorm( R'R - A'*A ) --> less_than(1e-5)
    end

end


# Tests on Multivariate Normal distributions

import PDMats: ScalMat, PDiagMat, PDMat

using Distributions
using LinearAlgebra, Random, Test

import Distributions: distrname

####### Core testing procedure

function test_mvnormal(g::AbstractMvNormal, n_tsamples::Int=10^6,
                       rng::Union{AbstractRNG, Missing} = missing)
    d = length(g)
    μ = mean(g)
    Σ = cov(g)
    @test partype(g) == Float64
    @test isa(μ, Vector{Float64})
    @test isa(Σ, Matrix{Float64})
    @test length(μ) == d
    @test size(Σ) == (d, d)
    @test var(g)     ≈ diag(Σ)
    @test entropy(g) ≈ 0.5 * logdet(2π * ℯ * Σ)
    ldcov = logdetcov(g)
    @test ldcov ≈ logdet(Σ)
    vs = diag(Σ)
    @test g == typeof(g)(params(g)...)

    # test sampling for AbstractMatrix (here, a SubArray):
    if ismissing(rng)
        subX = view(rand(d, 2d), :, 1:d)
        @test isa(rand!(g, subX), SubArray)
    else
        subX = view(rand(rng, d, 2d), :, 1:d)
        @test isa(rand!(rng, g, subX), SubArray)
    end

    # sampling
    if ismissing(rng)
        @test isa(rand(g), Vector{Float64})
        X = rand(g, n_tsamples)
    else
        @test isa(rand(rng, g), Vector{Float64})
        X = rand(rng, g, n_tsamples)
    end
    emp_mu = vec(mean(X, dims=2))
    Z = X .- emp_mu
    emp_cov = (Z * Z') * inv(n_tsamples)
    for i = 1:d
        @test isapprox(emp_mu[i]   , μ[i]  , atol=sqrt(vs[i] / n_tsamples) * 8.0)
    end
    for i = 1:d, j = 1:d
        @test isapprox(emp_cov[i,j], Σ[i,j], atol=sqrt(vs[i] * vs[j]) * 10.0 / sqrt(n_tsamples))
    end

    X = rand(MersenneTwister(14), g, n_tsamples)
    Y = rand(MersenneTwister(14), g, n_tsamples)
    @test X == Y
    emp_mu = vec(mean(X, dims=2))
    Z = X .- emp_mu
    emp_cov = (Z * Z') * inv(n_tsamples)
    for i = 1:d
        @test isapprox(emp_mu[i]   , μ[i]  , atol=sqrt(vs[i] / n_tsamples) * 8.0)
    end
    for i = 1:d, j = 1:d
        @test isapprox(emp_cov[i,j], Σ[i,j], atol=sqrt(vs[i] * vs[j]) * 10.0 / sqrt(n_tsamples))
    end

    # evaluation of sqmahal & logpdf
    U = X .- μ
    sqm = vec(sum(U .* (Σ \ U), dims=1))
    for i = 1:min(100, n_tsamples)
        @test sqmahal(g, X[:,i]) ≈ sqm[i]
    end
    @test sqmahal(g, X) ≈ sqm

    lp = -0.5 .* sqm .- 0.5 * (d * log(2.0 * pi) + ldcov)
    for i = 1:min(100, n_tsamples)
        @test logpdf(g, X[:,i]) ≈ lp[i]
    end
    @test logpdf(g, X) ≈ lp

    # log likelihood
    @test loglikelihood(g, X) ≈ sum([Distributions._logpdf(g, X[:,i]) for i in 1:size(X, 2)])
end

###### General Testing

@testset "MvNormal tests" begin
    mu = [1., 2., 3.]
    mu_r = 1.:3.
    va = [1.2, 3.4, 2.6]
    C = [4. -2. -1.; -2. 5. -1.; -1. -1. 6.]

    h = [1., 2., 3.]
    dv = [1.2, 3.4, 2.6]
    J = [4. -2. -1.; -2. 5. -1.; -1. -1. 6.]

    for (g, μ, Σ) in [
        (MvNormal(mu, sqrt(2.0)), mu, Matrix(2.0I, 3, 3)),
        (MvNormal(mu_r, sqrt(2.0)), mu_r, Matrix(2.0I, 3, 3)),
        (MvNormal(3, sqrt(2.0)), zeros(3), Matrix(2.0I, 3, 3)),
        (MvNormal(mu, sqrt.(va)), mu, Matrix(Diagonal(va))),
        (MvNormal(mu_r, sqrt.(va)), mu_r, Matrix(Diagonal(va))),
        (MvNormal(sqrt.(va)), zeros(3), Matrix(Diagonal(va))),
        (MvNormal(mu, C), mu, C),
        (MvNormal(mu_r, C), mu_r, C),
        (MvNormal(C), zeros(3), C),
        (MvNormalCanon(h, 2.0), h ./ 2.0, Matrix(0.5I, 3, 3)),
        (MvNormalCanon(mu_r, 2.0), mu_r ./ 2.0, Matrix(0.5I, 3, 3)),
        (MvNormalCanon(3, 2.0), zeros(3), Matrix(0.5I, 3, 3)),
        (MvNormalCanon(h, dv), h ./ dv, Matrix(Diagonal(inv.(dv)))),
        (MvNormalCanon(mu_r, dv), mu_r ./ dv, Matrix(Diagonal(inv.(dv)))),
        (MvNormalCanon(dv), zeros(3), Matrix(Diagonal(inv.(dv)))),
        (MvNormalCanon(h, J), J \ h, inv(J)),
        (MvNormalCanon(J), zeros(3), inv(J)),
        (MvNormal(mu, Symmetric(C)), mu, Matrix(Symmetric(C))),
        (MvNormal(mu_r, Symmetric(C)), mu_r, Matrix(Symmetric(C))),
        (MvNormal(mu, Diagonal(dv)), mu, Matrix(Diagonal(dv))),
        (MvNormal(mu_r, Diagonal(dv)), mu_r, Matrix(Diagonal(dv))) ]

        @test mean(g)   ≈ μ
        @test cov(g)    ≈ Σ
        @test invcov(g) ≈ inv(Σ)
        test_mvnormal(g, 10^4)

        # conversion between mean form and canonical form
        if isa(g, MvNormal)
            gc = canonform(g)
            @test isa(gc, MvNormalCanon)
            @test length(gc) == length(g)
            @test mean(gc) ≈ mean(g)
            @test cov(gc)  ≈ cov(g)
        else
            @assert isa(g, MvNormalCanon)
            gc = meanform(g)
            @test isa(gc, MvNormal)
            @test length(gc) == length(g)
            @test mean(gc) ≈ mean(g)
            @test cov(gc)  ≈ cov(g)
        end
    end
end

@testset "MvNormal constructor" begin
    mu = [1., 2., 3.]
    C = [4. -2. -1.; -2. 5. -1.; -1. -1. 6.]
    J = inv(C)
    h = J \ mu
    @test typeof(MvNormal(mu, PDMat(Array{Float32}(C)))) == typeof(MvNormal(mu, PDMat(C)))
    @test typeof(MvNormal(mu, Array{Float32}(C))) == typeof(MvNormal(mu, PDMat(C)))
    @test typeof(MvNormal(mu, 2.0f0)) == typeof(MvNormal(mu, 2.0))

    @test typeof(MvNormalCanon(h, PDMat(Array{Float32}(J)))) == typeof(MvNormalCanon(h, PDMat(J)))
    @test typeof(MvNormalCanon(h, Array{Float32}(J))) == typeof(MvNormalCanon(h, PDMat(J)))
    @test typeof(MvNormalCanon(h, 2.0f0)) == typeof(MvNormalCanon(h, 2.0))

    @test typeof(MvNormalCanon(mu, Array{Float16}(h), PDMat(Array{Float32}(J)))) == typeof(MvNormalCanon(mu, h, PDMat(J)))

    d = MvNormal(Array{Float32}(mu), PDMat(Array{Float32}(C)))
    @test typeof(convert(MvNormal{Float64}, d)) == typeof(MvNormal(mu, PDMat(C)))
    @test typeof(convert(MvNormal{Float64}, d.μ, d.Σ)) == typeof(MvNormal(mu, PDMat(C)))

    d = MvNormalCanon(Array{Float32}(mu), Array{Float32}(h), PDMat(Array{Float32}(J)))
    @test typeof(convert(MvNormalCanon{Float64}, d)) == typeof(MvNormalCanon(mu, h, PDMat(J)))
    @test typeof(convert(MvNormalCanon{Float64}, d.μ, d.h, d.J)) == typeof(MvNormalCanon(mu, h, PDMat(J)))

    @test typeof(MvNormal(mu, I)) == typeof(MvNormal(mu, 1))
    @test typeof(MvNormal(mu, 3 * I)) == typeof(MvNormal(mu, 3))
    @test typeof(MvNormal(mu, 0.1f0 * I)) == typeof(MvNormal(mu, 0.1))
end

##### MLE

# a slow but safe way to implement MLE for verification

function _gauss_mle(x::AbstractMatrix{<:Real})
    mu = vec(mean(x, dims=2))
    z = x .- mu
    C = (z * z') * (1/size(x,2))
    return mu, C
end

function _gauss_mle(x::AbstractMatrix{<:Real}, w::AbstractVector{<:Real})
    sw = sum(w)
    mu = (x * w) * (1/sw)
    z = x .- mu
    C = (z * (Diagonal(w) * z')) * (1/sw)
    LinearAlgebra.copytri!(C, 'U')
    return mu, C
end

@testset "MvNormal MLE" begin
    x = randn(3, 200) .+ randn(3) * 2.
    w = rand(200)
    u, C = _gauss_mle(x)
    uw, Cw = _gauss_mle(x, w)

    g = fit_mle(MvNormal, suffstats(MvNormal, x))
    @test isa(g, FullNormal)
    @test mean(g) ≈ u
    @test cov(g)  ≈ C

    g = fit_mle(MvNormal, x)
    @test isa(g, FullNormal)
    @test mean(g) ≈ u
    @test cov(g)  ≈ C

    g = fit_mle(MvNormal, x, w)
    @test isa(g, FullNormal)
    @test mean(g) ≈ uw
    @test cov(g)  ≈ Cw

    g = fit_mle(IsoNormal, x)
    @test isa(g, IsoNormal)
    @test g.μ       ≈ u
    @test g.Σ.value ≈ mean(diag(C))

    g = fit_mle(IsoNormal, x, w)
    @test isa(g, IsoNormal)
    @test g.μ       ≈ uw
    @test g.Σ.value ≈ mean(diag(Cw))

    g = fit_mle(DiagNormal, x)
    @test isa(g, DiagNormal)
    @test g.μ      ≈ u
    @test g.Σ.diag ≈ diag(C)

    g = fit_mle(DiagNormal, x, w)
    @test isa(g, DiagNormal)
    @test g.μ      ≈ uw
    @test g.Σ.diag ≈ diag(Cw)
end


function check(numbers, idx)
    for i in idx - 25:idx - 2
        for j in i:idx - 1
            if numbers[i] + numbers[j] == numbers[idx]
                return true
            end
        end
    end
    return false
end

function run(s)
    numbers = readlines(IOBuffer(s))
    numbers = [parse(Int, n) for n in numbers]
    # find invalid number
    invalid_number = 0
    for idx in 26:length(numbers)
        s = numbers[idx]
        if !check(numbers, idx)
            invalid_number = numbers[idx]
            break
        end
    end

    # find matching sequence
    i = 1
    j = 2
    s = sum(numbers[i:j])
    while s != invalid_number
        if s < invalid_number
            j += 1
            s += numbers[j]
        else
            s -= numbers[i]
            i += 1
        end
    end
    return minimum(numbers[i:j]) + maximum(numbers[i:j])
end

#########################################

function main()
    res, time, memory = @timed run(ARGS[1])
    println("_duration:$(time * 1000)")
    println(res)
end

main()


using BayesianDataFusion
using Distributed
using SparseArrays
using Test

X = sprand(50, 100, 0.1)
Y1 = rand(50, 3)
Y2 = rand(100, 2)

Z1 = X' * Y1
Z2 = X  * Y2
addprocs(2)
@everywhere using BayesianDataFusion

fp  = psparse(X, workers())
Z1p = transpose(fp) * Y1
Z2p = fp * Y2

@test Z1 ≈ Z1p
@test Z2 ≈ Z2p

W  = sprand(50, 10, 0.1)
rd = RelationData(W, class_cut = 0.5, feat1 = fp)
assignToTest!(rd.relations[1], 2)

result = macau(rd, burnin = 2, psamples = 2, num_latent=5, verbose=false)

@test size(rd.entities[1].model.beta) == (size(fp,2), 5)

########### parallel sparse with CSR ###########
fp2 = psparse(SparseMatrixCSC(X'), workers())

Z1r = transpose(fp2) * Y1
Z2r = fp2 * Y2

@test Z1 ≈ Z1r
@test Z2 ≈ Z2r

rd2 = RelationData(W, class_cut = 0.5, feat1 = fp2)
assignToTest!(rd2.relations[1], 2)

result = macau(rd2, burnin = 2, psamples = 2, num_latent=5, verbose=false)

rmprocs( workers() )


function make_layout(adj)
#function [x, y] = make_layout(adj)
#% MAKE_LAYOUT		Creates a layout from an adjacency matrix
#%
#%  [X, Y] = MAKE_LAYOUT(ADJ)
#%
#% Inputs :
#%	ADJ = adjacency matrix (source, sink)
#%
#% Outputs :
#%	X, Y : Positions of nodes
#%
#% Usage Example : [X, Y] = make_layout(adj);
#%
#%
#% Note	: Uses some very simple heuristics, so any other
#%         algorithm would create a nicer layout
#%
#% See also
#
#    % Uses :#
#
#% Change History :
#% Date		Time		Prog	Note
#% 13-Apr-2000	 8:25 PM	ATC	Created under MATLAB 5.3.1.29215a (R11.1)
#
#% ATC = Ali Taylan Cemgil,
#% SNN - University of Nijmegen, Department of Medical Physics and Biophysics
#% e-mail : cemgil@mbfys.kun.nl

N = size(adj,1)
tps = toposort(adj)

if !isempty(tps) # is directed ?
  level = zeros(1,N)
  for i=tps
    idx = find(adj[:,i])
    if !isempty(idx)
      l = maximum(level[idx])
      level[i]=l+1
    end
  end
else
    level = poset(adj,1)-1
end

y = (level+1)./(maximum(level)+2)
    y = 1-y
    x = zeros(size(y))
for i=0:maximum(level)
    idx = find(level.==i);
    offset = (rem(i,2)-0.5)/10
    x[idx] = (1:length(idx))./(length(idx)+1)+offset
end
    return x,y

end