# rootsah for BigInt and BigFloat
function rootsah(pol::Union{Poly{BigInt},Poly{BigFloat}}, D::Vector{Float64})

    # COMPUTES: the roots of polynomials with all distinct real roots.
    # The computation is forward stable. The program uses SymArrow (arrowhead) companion matrix and
    # corresponding eig routine
    # D are barycentric coordinates - elements od D must interpolate the roots of P,
    # for example
    #              D=roots(polyder(pol))
    # RETURNS: roots E

    T = BigFloat

    # tols = [1e2,1e2,1e2,1e2,1e2] or similar is the vector of tolerances for eig
    tols=[1e2,1e2,1e2,1e2,1e2]

    Dm=map(T,D)
    Dm=sort(Dm,rev=true)
    D=sort(D,rev=true)

    p=map(T,[pol[i] for i=0:1:length(pol)-1])
    p=p[end:-1:1]

    # Compute z of the arrowhead
    # First we compute values s=p(D) we use Horner sheme with double the working precision
    set_bigfloat_precision(512)

    n=length(p)-1
    # pD=map(Double,p)
    # DD=map(Double,Dm)
    # oneD=Double(one(T))

    s=Array(BigFloat,n-1)
    for i=1:n-1
        s[i]=one(T)
    end
    s=p[1]*s
    for i=2:n+1
        r=s.*Dm
        s=r+p[i]
    end

    # Compute t's
    t=Array(BigFloat,n-1)
    for j=1:n-1
        h=one(T)
        for i=1:j-1
            g=Dm[j]-Dm[i]
            h=h*g
        end
        for i=j+1:n-1
            g=Dm[j]-Dm[i]
            h=h*g
        end
        t[j]=h
    end
    # Compute alphaD
    alphaD=p[2]/p[1]
    for i=1:n-1
        alphaD+=Dm[i]
    end
    alphaD=-alphaD
    #  Compute z
    zD=Array(BigFloat,n-1)
    for i=1:n-1
        zD[i]=sqrt((-s[i])/(t[i]*p[1]))
    end

    z=Array(Float64,n-1)
    for i=1:n-1
        z[i]=Float64(zD[i])
    end
    alpha=Float64(alphaD)
    A=SymArrow(D,z,alpha,n)

    E=zeros(Float64,n)
    Qout=zeros(Int64,n)

    for k=1:n
        E[k], Qout[k] = eig( A,zD,alphaD,k,tols )
    end

    # reset the bigfloat_precision
    set_bigfloat_precision(256)

    E, Qout
end

#------------------------
#--------  Functions

#-------- Inverses

function inv{T}(A::SymArrow{T},zD::Vector{BigFloat}, alphaD::BigFloat,i::Int64,tols::Vector{Float64})

    # COMPUTES: inverse of a SymArrow matrix A, inv(A-A.D[i]*I) which is again SymArrow
    # uses higher precision to compute top of the arrow element accurately, if
    # needed.
    # tols=[tolb,tolz] are tolerances, usually [1e3, 10*n]
    # [0.0,0.0] forces DoubleDouble, [1e50,1e50] would never use it
    # RETURNS:  SymArrow(D,z,b,i), Kb, Kz, Qout
    # Kb - condition Kb, Kz - condition Kz, Qout = 1 / 0 - double was / was not used

    n=length(A.D)
    D=Array(T,n)
    z=Array(T,n)
    wz=1/A.z[i]
    shift=A.D[i]

    for k=1:i-1
        D[k]=one(T)/(A.D[k]-shift)
        z[k]=-A.z[k]*D[k]*wz
    end
    for k=i+1:n
        D[k-1]=one(T)/(A.D[k]-shift)
        z[k-1]=-A.z[k]*D[k-1]*wz
    end

    # D=[1./(A.D[1:i-1]-shift),1./(A.D[i+1:end]-shift)]
    # wz=1/A.z[i]
    # z=-[A.z[1:i-1], A.z[i+1:end]].*D1*wz1
    a=A.a-A.D[i]

    # compute the sum in a plain loop
    P=zero(T)
    Q=zero(T)
    Qout=0

    nn=n-1
    for k=1:i-1
        D[k]>0.0 ? P+=A.z[k]^2*D[k] : Q+=A.z[k]^2*D[k]
    end
    for k=i:nn
        D[k]>0.0 ? P+=A.z[k+1]^2*D[k] : Q+=A.z[k+1]^2*D[k]
    end
    a<0 ? P=P-a : Q=Q-a

    Kb=(P-Q)/abs(P+Q)
    Kz=maxabs(A.z)*abs(wz)

    if Kb<tols[1] ||  Kz<tols[2]
        b=(P+Q)*wz*wz
    else  # recompute in double the working precision
        Qout=1
        AD=map(BigFloat,A.D)
        shiftd=AD[i]
        #shiftd=map(Double,A.D[i])
        Dd=[[AD[k]-shiftd for k=1:i-1];
            [AD[k]-shiftd for k=i+1:length(A.D)]]
        wzd=zD[i] #
        ad=alphaD-shiftd

        Pd,Qd=map(BigFloat,(0.0,0.0))

        for k=1:i-1
            Dd[k] > 0.0 ? Pd+=zD[k]^2/Dd[k] :
            Qd+=zD[k]^2/Dd[k]
        end

        for k=i:nn
            Dd[k] > 0.0 ? Pd+=zD[k+1]^2/Dd[k] :
            Qd+=zD[k+1]^2/Dd[k]
        end

        ad<0 ?   Pd=Pd-ad : Qd=Qd-ad

        bd=(Pd+Qd)/(wzd*wzd)
        b=Float64(bd)
    end

    if i<A.i
        # SymArrow([D[1:A.i-2],zero(T),D[A.i-1:end]],[z[1:A.i-2],wz,z[A.i-1:end]],b,i),Kb,Kz,Qout
        # This reduces memory allocation (4 vectors) and is 30-40% faster
        for k=n-1:-1:A.i-1
            D[k+1]=D[k]
            z[k+1]=z[k]
        end
        D[A.i-1]=zero(T)
        z[A.i-1]=wz
        SymArrow(D,z,b,i),Kb,Kz,Qout
    else
        # SymArrow([D[1:A.i-1],zero(T),D[A.i:end]],[z[1:A.i-1],wz,z[A.i:end]],b,i+1),Kb,Kz,Qout
        for k=n-1:-1:A.i
            D[k+1]=D[k]
            z[k+1]=z[k]
        end
        D[A.i]=zero(T)
        z[A.i]=wz
        SymArrow(D,z,b,i+1),Kb,Kz,Qout
    end

end # inv

function inv{T}(A::SymArrow{T}, zD::Vector{BigFloat}, alphaD::BigFloat, shift::Float64, tolr::Float64)

    # COMPUTES: inverse of the shifted SymArrow A, inv(A-shift*I) which is SymDPR1
    # uses DoubleDouble to compute rho accurately, if needed.
    # tolr is tolerance, usually 1e3,  0.0 forces Double, 1e50 would never use it
    # RETURNS: SymDPR1(D,u,rho), Krho, Qout
    # Krho - condition Krho, Qout = 1 / 0 - Double was / was not used

    n=length(A.D)
    D=Array(T,n+1)
    u=Array(T,n+1)

    # Ds=A.D-shift

    # D=[1./Ds[1:A.i-1];0.0;1./Ds[A.i:end]];
    # u=[A.z[1:A.i-1]./Ds[1:A.i-1];-1.0;A.z[A.i:end]./Ds[A.i:end]];

    for k=1:A.i-1
        D[k]=one(T)/(A.D[k]-shift)
        u[k]=A.z[k]*D[k]
    end
    D[A.i]=zero(T)
    u[A.i]=-one(T)
    for k=A.i:n
        D[k+1]=one(T)/(A.D[k]-shift)
        u[k+1]=A.z[k]*D[k+1]
    end

    # compute rho and Krho
    #--- compute the sum in a plain loop

    a=A.a-shift
    P=zero(T)
    Q=zero(T)
    Qout=0

    for k=1:A.i-1
        D[k]>0.0 ? P=P+A.z[k]^2*D[k] : Q=Q+A.z[k]^2*D[k]
    end
    for k=A.i:n
        D[k+1]>0.0 ? P=P+A.z[k]^2*D[k+1] : Q=Q+A.z[k]^2*D[k+1]
    end

    a<0 ? P=P-a : Q=Q-a

    # Condition of rho
    Krho=(P-Q)/abs(P+Q)

    if Krho<tolr
        rho=-1.0/(P+Q)

    else  # recompute in double the working precision
        Qout=1
        Pd,Qd=map(BigFloat,(0.0,0.0))
        AD=map(BigFloat,A.D)
        shiftd=map(BigFloat,shift)
        ad=alphaD-shiftd
        #   Dd=[Double(A.D[k])-shiftd for k=1:length(A.D) ]
        #   Dd=map(Double,A.D)-shiftd
        for k=1:A.i-1
            D[k]>0.0 ? Pd=Pd+zD[k]^2/(AD[k]-shiftd) : Qd=Qd+zD[k]^2/(AD[k]-shiftd)
        end
        for k=A.i:n
            D[k+1]>0.0 ? Pd=Pd+zD[k]^2/(AD[k]-shiftd) : Qd=Qd+zD[k]^2/(A.D[k]-shiftd)
        end

        # for k=1:length(A.D)
        #    Dd[k].hi>0.0 ? Pd=Pd+Double(A.z[k])^2/Dd[k] : Qd=Qd+Double(A.z[k])^2/Dd[k]
        # end
        ad<0 ? Pd=Pd-ad : Qd=Qd-ad
        r=map(BigFloat,1.0)/(Pd+Qd)
        rho=Float64(-r)
    end

    # returns the following
    SymDPR1(D,u,rho), Krho, Qout

end # inv

function inv{T}(A::SymArrow{T}, zD::Vector{BigFloat}, alphaD::BigFloat, shift::BigFloat)

    # COMPUTES: inverse of the shifted SymArrow A, inv(A-shift*I), which is a SymDPR1
    # here shift is BigFloat so it uses double the working precision to compute everything
    # RETURNS: SymDPR1(D1,u1,rho), Qout
    # Qout = 1 on exit meaning Double was used

    n=length(A.D)
    D=Array(BigFloat,n+1)
    u=Array(BigFloat,n+1)

    # D[1:n]=map(Double,A.D)-shift
    for k=1:n
        D[k]=map(BigFloat,A.D[k])-shift
    end

    u[1:n]=zD
    a=alphaD-shift
    i=A.i

    oned=one(BigFloat)
    zerod=zero(BigFloat)

    for k=1:i-1
        u[k]=u[k]/D[k]
        D[k]=oned/D[k]
    end

    for k=i:n
        u[k+1]=u[k]/D[k]
        D[k+1]=oned/D[k+1]
    end

    D[i]=zerod
    u[i]=-oned

    # compute rho and Krho
    #--- compute the sum in a plain loop

    P,Q=zerod,zerod
    Qout=1

    for k=1:i-1
        # D[k].hi+D[k].lo > 0.0  # .lo cannot change sign, I think
        # D[k].hi > 0.0 ? P=P+Double(A.z[k])^2*D[k] : Q=Q+Double(A.z[k])^2*D[k]
        D[k] > 0.0 ? P=P+zD[k]*u[k] : Q=Q+zD[k]*u[k]
    end
    for k=i:n
        D[k+1] > 0.0 ? P=P+zD[k]*u[k+1] : Q=Q+zD[k]*u[k+1]
    end

    a<0.0  ? P=P-a : Q=Q-a
    r=oned/(P+Q)

    rho=Float64(-r)

    # returns the following
    D1=Array(T,n+1)
    u1=Array(T,n+1)
    for k=1:n+1
        D1[k]=Float64(D[k])
    end
    for k=1:n+1
        u1[k]=Float64(u[k])
    end

    SymDPR1(D1,u1,rho), Qout

end # inv

function  eig{T}( A::SymArrow{T},zD::Vector{BigFloat},alphaD::BigFloat,k::Int64,tols::Vector{Float64})

    # COMPUTES: k-th eigenvalue of an ordered irreducible SymArrow
    # A = [diag (D) z; z' alpha]
    # Specially designed to be used in the polynomial rootfinder rootsah !!!!
    # tols=[tolb,tolz,tolnu,tolrho,tollambda] = [1e3,10.0*n,1e3,1e3,1e3]
    # RETURNS: lambda
    # lambda - k-th eigenvalue
    # Kb, Kz, Knu, Krho - condition numbers
    # Qout = 1 / 0 - Double was / was not used

    # If Qout>0, quad precision was used
    # i was the shift index

    # Set the dimension
    n = length(A.D) + 1

    # Set all conditions initially to zero
    Kb,Kz,Knu,Krho=0.0,0.0,0.0,0.0
    Qout=0
    v=zeros(T,n)
    # Kz is former kappa_nu

    # Determine the shift sigma, the shift index i, and whether lambda
    # is on the left or the right side of the nearest pole
    # Exterior eigenvalues (k = 1 or k = n):

    if k == 1
        sigma,i,side = A.D[1],1,'R'
    elseif k==n
        sigma,i,side = A.D[n-1],n-1,'L'
    else
        # Interior eigenvalues (k in (2,...n-1) ):
        # We need to compute Fmiddle carefully

        Dtemp = A.D-A.D[k]
        middle = Dtemp[k-1]/2.0

        P=zero(Float64)
        Q=zero(Float64)
        P=P+sum(A.z[1:k-1].^2./(Dtemp[1:k-1]-middle))
        Q=Q+sum(A.z[k:n-1].^2./(Dtemp[k:n-1]-middle))

        P1=map(BigFloat,P)
        Q1=map(BigFloat,Q)
        atemp = (alphaD-map(BigFloat,A.D[k]))-map(BigFloat,middle)
        atemp<0 ? P1=P1-atemp : Q1=Q1-atemp

        # Fmiddle = (atemp-middle)-sum(zD.^2./(Dtemp-middle))
        Fmiddle=-(P1+Q1)
        sigma,i,side = Fmiddle < zero(BigFloat) ? (A.D[k],k,'R') : (A.D[k-1],k-1,'L')
    end

    # Compute the inverse of the shifted matrix, A_i^(-1), Kb and Kz

    Ainv,Kb,Kz,Qout = inv(A,zD,alphaD,i,tols[1:2])

    # Compute the eigenvalue of the inverse shifted matrix

    nu = bisect( Ainv,side )

    #  nu=fastzero([invD1; 0; invD2], [w1;wz;w2], b, side); # implement later
    #  [nu-nu1]/nu, [nu-nueva]/nu, pause  # and compare

    if abs(nu)==Inf
        # this is nonstandard
        # Deflation in aheig (nu=Inf)
        v[i]=1.0
        lambda=sigma
    else
        # standard case, full computation
        # nu1 is the F- or 1-norm of the inverse of the shifted matrix
        # nu10=maximum([sum(abs(Ainv.z))+abs(Ainv.a), maximum(abs(Ainv.D)+abs(Ainv.z))])
        nu1=0.0
        for l=1:n-1
            nu1=max(nu1,abs(Ainv.D[l])+abs(Ainv.z[l]))
        end
        nu1=max(sumabs(Ainv.z)+abs(Ainv.a), nu1)

        Knu=nu1/abs(nu)
        if Knu<tols[3]
            # Accuracy is fine, compute the eigenvector
            mu = 1.0/nu
            # v=[ A.z./((A.D-sigma)-mu);-1.0]
            for l=1:n-1
            	v[l] = A.z[l]/((A.D[l]-sigma)-mu)
            end
            v[n]=-1.0
            lambda, v = mu+sigma, v/norm(v)
        else
            # Remedies according to Remark 3 - we shift between original
            # eigenvalues and compute DPR1 matrix
            # 1/nu1+sigma, 1/nu+sigma
            # println("Remedy 3 ")
            nu = side=='R' ? abs(nu) : -abs(nu)
            nu1=-sign(nu)*nu1
            sigma1=(nu1+nu)/(2.0*nu*nu1)+sigma

            if findfirst(A.D-sigma1,0.0)>0 # we came back with a pole
                # recompute sigmav more accurately according with dekker
                set_bigfloat_precision(512)
                sigmav=(nu1+nu)/(BigFloat(2.0)*nu*nu1)+sigma
                # Compute the inverse of the shifted arrowhead (DPR1)
                Ainv,Qout1=inv(A,sigmav) # Ainv is Float64
                nu1=bisect(Ainv,side)
                mu1 = 1.0/nu1
                D0=map(BigFloat,A.D)-sigmav
                D1=[D0[l] for l=1:length(D0)]
                if findfirst(D1-mu1,0.0)>0
                    ind=find(D1-mu1==0.0)
                    v=zeros(n)
                    v[ind]=1.0
                else
                    v=[ A.z./(D1-mu1);-1.0]
                end
                # Shift the eigenvalue back in Double
                lam = BigFloat(1.0)/nu1+sigmav
                # Return this
                lambda,v = lam, v/norm(v)
            else
                # Compute the inverse of the shifted arrowhead (DPR1)
                Ainv, Krho,Qout1=inv(A,sigma1,tols[4]) # Ainv is Float64
                # Compute the eigenvalue by bisect for DPR1
	            # Note: instead of bisection could use dlaed4 (need a wrapper) but
	            # it is not faster. There norm(u)==1
                nu1= bisect(Ainv,side)
                mu1=1.0/nu1
                # standard v
                v=[ A.z./((A.D-sigma1)-mu1);-1.0]
                # Return this - shift the eigenvalue back and normalize the vector
                lambda, v = mu1+sigma1, v/norm(v)
            end
            Qout==1 && (Qout=Qout+2)
        end

        # Remedy according to Remark 1 - we recompute the the eigenvalue
        # near zero from the inverse of the original matrix (a DPR1 matrix).
        if (abs(A.D[i])+abs(1.0/nu))/abs(lambda)>tols[5]

            if k==1 && A.D[1]<0.0 || k==n && A.D[n-1]>0.0 || i<n-1 && side=='L' && sign(A.D[i])+sign(A.D[i+1])==0 ||
                side=='R' && sign(A.D[i])+sign(A.D[i-1])==0
                # println("Remedy 1 ")
                # Compute the inverse of the original arrowhead (DPR1)
                Ainv,Krho,Qout1 = inv(A,0.0,tols[4]) # Ainv is Float64
                Qout==1 && (Qout=Qout+4)
                if isinf(Ainv.r) || isnan(Ainv.r)
                    lambda=0.0
                else
                    # Here we do not need bisection. We compute the Rayleigh
                    # quotient by using already computed vectors which is
                    # componentwise accurate
                    nu1=sum(v.^2.*Ainv.D)+Ainv.r*sum(v.*Ainv.u)^2;
                    lambda=1.0/nu1
                end
            end

        end
    end

    # Return this
    Float64(lambda), Qout

end # eig (k)

function rootsWDK{T,T1}(p::Poly{T},x0::Vector{T1},steps)
    # Polynomial roots with Weierstrass, Durand, Kerner method
    n=length(x0)
    x=x0
    for k=1:steps
        for i=1:n
            x[i]=x[i]-polyval(p,x[i])/(p[end]*prod(x[i]-x[collect([1:i-1;i+1:n])]))
        end
    end
    x
end


export cholinv, cholsqrt

using LinearAlgebra
using PositiveFactorizations

cholinv(x)           = inv(cholesky(PositiveFactorizations.Positive, Hermitian(x)))
cholinv(x::Diagonal) = Diagonal(inv.(diag(x)))
cholinv(x::Real)     = inv(x)

cholsqrt(x)           = Matrix(cholesky(PositiveFactorizations.Positive, x).L)
cholsqrt(x::Diagonal) = Diagonal(sqrt.(diag(x)))
cholsqrt(x::Real)     = sqrt(x)


function solve()
    x, y, a, b = [parse(BigInt, x) for x in split(readline())]
    ret = 0
    while a*x ≤ x+b && a*x<y
        x *= a
        ret += 1
    end
    ret + (y-1-x)÷b
end

println(solve())


# This file is a part of BAT.jl, licensed under the MIT License (MIT).

function bat_marginalmode_impl(samples::DensitySampleVector; nbins::Union{Integer, Symbol} = 200)
    shape = varshape(samples)
    flat_samples = flatview(unshaped.(samples.v))
    n_params = size(flat_samples)[1]
    nt_samples = ntuple(i -> flat_samples[i,:], n_params)
    marginalmode_params = Vector{Float64}()

    for param in Base.OneTo(n_params)
        if typeof(nbins) == Symbol
            number_of_bins = _auto_binning_nbins(nt_samples, param, mode=nbins)
        else
            number_of_bins = nbins
        end

        marginalmode_param = find_marginalmodes(get_marginal_dist(samples, param, bins=number_of_bins).result)

        if length(marginalmode_param[1]) > 1
            @warn "More than one bin with the same weight is found. Returned the first one"
        end
        push!(marginalmode_params, marginalmode_param[1][1])
    end
    (result = strip_realscalar(shape(marginalmode_params)),)
end

# From Plots.jl, original authors Oliver Schulz and Michael K. Borregaard
function _auto_binning_nbins(vs::NTuple{N,AbstractVector}, dim::Integer; mode::Symbol = :auto) where N
    max_bins = 10_000
    _cl(x) = min(ceil(Int, max(x, one(x))), max_bins)
    _iqr(v) = (q = quantile(v, 0.75) - quantile(v, 0.25); q > 0 ? q : oftype(q, 1))
    _span(v) = maximum(v) - minimum(v)

    n_samples = length(LinearIndices(first(vs)))

    # The nd estimator is the key to most automatic binning methods, and is modified for twodimensional histograms to include correlation
    nd = n_samples^(1/(2+N))
    nd = N == 2 ? min(n_samples^(1/(2+N)), nd / (1-cor(first(vs), last(vs))^2)^(3//8)) : nd # the >2-dimensional case does not have a nice solution to correlations

    v = vs[dim]

    if mode == :auto
        mode = :fd
    end

    if mode == :sqrt  # Square-root choice
        _cl(sqrt(n_samples))
    elseif mode == :sturges  # Sturges' formula
        _cl(log2(n_samples) + 1)
    elseif mode == :rice  # Rice Rule
        _cl(2 * nd)
    elseif mode == :scott  # Scott's normal reference rule
        _cl(_span(v) / (3.5 * std(v) / nd))
    elseif mode == :fd  # Freedman–Diaconis rule
        _cl(_span(v) / (2 * _iqr(v) / nd))
    elseif mode == :wand
        _cl(wand_edges(v))  # this makes this function not type stable, but the type instability does not propagate
    else
        error("Unknown auto-binning mode $mode")
    end
end


function lis(arr::Vector)
    if length(arr) == 0 return copy(arr) end
    L = Vector{typeof(arr)}(length(arr))
    L[1] = [arr[1]]

    for i in 2:length(arr)
        nextL = []
        for j in 1:i
            if arr[j] < arr[i] && length(L[j]) ≥ length(nextL)
                nextL = L[j]
            end
        end
        L[i] = vcat(nextL, arr[i])
    end

    return L[indmax(length.(L))]
end

@show lis([3, 2, 6, 4, 5, 1])
@show lis([0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15])


export CircleRepresentationDrawing, RainbowDrawing

"""
`CircleRepresentationDrawing(list, fontsize=8)` draws the circle representation
implicit in `list`. Requires `PyPlot`. Not exporting this for that
reason.
"""
function CircleRepresentationDrawing(list::Array{T,1}, font_size = 8) where {T}
    font_size = 8
    positions = SimpleGraphRepresentations.list2locs(list)
    nn = length(list)
    verts = collect(keys(positions))
    rad = 2
    theta = collect(0:360) * pi / 180
    x = rad * map(cos, theta)
    y = rad * map(sin, theta)
    plot(x, y, linestyle = :dot, color = "black")

    for v in verts
        p = positions[v]
        a = (p[1] - 1) * 2 * pi / nn
        b = (p[2] - 1) * 2 * pi / nn
        x = rad * [cos(a), cos(b)]
        y = rad * [sin(a), sin(b)]
        plot!(x, y, color = "black")

        if font_size > 0
            mu = 1.1
            for k = 1:2
                # text(mu*x[k],mu*y[k],string(v))
                annotate!((mu * x[k], mu * y[k], string(v), font_size))
            end
        end

    end
    finish()
end

"""
`CircleRepresentationDrawing(str::String)` uses `str` as the
list of symbols.
"""
function CircleRepresentationDrawing(str::String, labels::Bool = true)
    list = [ch for ch in str]
    CircleRepresentationDrawing(list, labels)
end

function semicircle(x1::Number, x2::Number, diam = 2)
    ctr = (x1 + x2) / 2
    rad = abs(x1 - x2) / 2

    angles = collect(0:180) * (pi / 180)
    x = rad * [cos(t) for t in angles] .+ ctr
    y = rad * [sin(t) for t in angles]
    plot!(x, y, color = "black", linestyle = :dot)
    plot!([x[1]], [y[1]], marker = diam, markercolor = "black")
    plot!([x[end]], [y[end]], marker = diam, markercolor = "black")
end

"""
`RainbowDrawing(lst,font_size=8)` creates a rainbow representation
of a circle graph from a list of the positions.
"""
function RainbowDrawing(list::Array{T,1}, font_size::Int = 8) where {T}
    positions = SimpleGraphRepresentations.list2locs(list)
    nn = length(list)
    verts = collect(keys(positions))

    x = [0, nn + 1]
    y = [0, 0]
    plot(x, y, color = "black")

    gap = -0.5
    for v in verts
        p = positions[v]
        semicircle(p[1], p[2])
        if font_size > 0
            annotate!(p[1], gap, string(v), font_size)
            annotate!(p[2], gap, string(v), font_size)
        end
    end

    finish()
end


using SimpleGradientDescent
using Base.Test

@testset "naive solver" begin
    num_vars = 10

    # Let's define a very simple cost function:
    xstar = [1., 2, 3, 4, 5, 6, 7, 8, 9, 10]
    cost(x) = sum((x .- xstar) .^ 4)

    # getmodel() returns a MathProgBase model which will allow us to perform
    # the gradient descent. We can specify a particular solver if we want to,
    # or it will try to use MathProgBase's default NLP solver if we don't.
    model = getmodel(cost, num_vars, NaiveSolver(num_vars))

    # Set the initial point from which the solver will start:
    setwarmstart!(model, [0.0 for i in 1:num_vars])

    # Optimize and extract the solution:
    # (note that setwarmstart!, optimize!, status, and getsolution are all
    # just re-exported from MathProgBase for convenience)
    optimize!(model)
    @test status(model) == :Optimal
    x = getsolution(model)
    @show x xstar
    @test all(isapprox(x, xstar, atol=1e-3))

    # We can specify lower and upper bounds on the decision variables:
    model = getmodel(cost, num_vars, NaiveSolver(num_vars),
                    [-1.0 for i in 1:num_vars],
                    [1.0 for i in 1:num_vars])
    setwarmstart!(model, [0.0 for i in 1:num_vars])
    optimize!(model)
    x = getsolution(model)
    @show x xstar
    # Since we've constrained x to lie in [0, 1], it won't reach xstar
    @test all(isapprox(x, [1.0 for i in 1:num_vars], atol=1e-3))
end

@testset "naive preconditioned" begin
    num_vars = 10
    xstar = [1., 2, 3, 4, 5, 6, 7, 8, 9, 10]
    cost(x) = sum((x .- xstar) .^ 4)
    unconditioned_model = getmodel(cost, num_vars, NaiveSolver(num_vars; precondition_divisors=ones(num_vars),
        iteration_limit=2))
    setwarmstart!(unconditioned_model, [0.0 for i in 1:num_vars])
    optimize!(unconditioned_model)
    @test status(unconditioned_model) == :UserLimit

    conditioned_model = getmodel(cost, num_vars, NaiveSolver(num_vars; precondition_divisors=collect(1:num_vars),
        iteration_limit=2))
    setwarmstart!(conditioned_model, [0.0 for i in 1:num_vars])
    optimize!(conditioned_model)
    @test status(conditioned_model) == :Optimal
    x = getsolution(conditioned_model)
    @test all(isapprox(x, xstar, atol=1e-3))

end


abstract type AbstractPDE{N} end

ambient_dimension(::Type{<:AbstractPDE{N}}) where {N} = N
ambient_dimension(op::AbstractPDE) = ambient_dimension(typeof(op))

"""
    struct Laplace{N}

Laplace equation in `N` dimension: Δu = 0.
"""
struct Laplace{N} <: AbstractPDE{N} end

Laplace(;dim=3) = Laplace{dim}()

getname(::Laplace) = "Laplace"

default_kernel_eltype(::Laplace)  = Float64
default_density_eltype(::Laplace) = Float64

"""
    struct Helmholtz{N,T}

Helmholtz equation in `N` dimensions: Δu + k²u = 0.
"""
struct Helmholtz{N,K} <: AbstractPDE{N}
    k::K
end

Helmholtz(;k,dim=3) = Helmholtz{dim,typeof(k)}(k)

getname(::Helmholtz) = "Helmholtz"

default_kernel_eltype(::Helmholtz)  = ComplexF64
default_density_eltype(::Helmholtz) = ComplexF64

"""
    struct Elastostatic{N,T} <: AbstractPDE{N}

Elastostatic equation in `N` dimensions: μΔu + (μ+λ)∇(∇⋅u) = 0. Note that the
displacement u is a vector of length `N` since this is a vectorial problem.
"""
struct Elastostatic{N,T} <: AbstractPDE{N}
    μ::T
    λ::T
end
Elastostatic(;μ,λ,dim=3)               = Elastostatic{dim}(promote(μ,λ)...)
Elastostatic{N}(μ::T,λ::T) where {N,T} = Elastostatic{N,T}(μ,λ)

getname(::Elastostatic) = "Elastostatic"

default_kernel_eltype(::Elastostatic{N}) where {N}  = SMatrix{N,N,Float64,N*N}
default_density_eltype(::Elastostatic{N}) where {N} = SVector{N,Float64}

"""
    Maxwell{N,T} <: AbstractPDE{N}

Normalized Maxwell's equation ∇ × ∇ × E - k² E = 0, where
k = ω √ϵμ.
"""
struct Maxwell{N,T} <: AbstractPDE{N}
    k::T
end
Maxwell(;k,dim=3)                   = Maxwell{dim}(k)
Maxwell{N}(k::T) where {N,T}        = Maxwell{N,T}(k)

getname(::Maxwell) = "Maxwell"

default_kernel_eltype(::Maxwell{N}) where {N}  = SMatrix{N,N,ComplexF64,N*N}
default_density_eltype(::Maxwell{N}) where {N} = SVector{N,ComplexF64}


"""
    v = InnerProductVec(vec, dotf)

Create a new vector `v` from an existing vector `dotf` with a modified inner product given
by `inner`. The vector `vec`, which can be any type (not necessarily `Vector`) that supports
the basic vector interface required by KrylovKit, is wrapped in a custom struct
`v::InnerProductVec`. All vector space functionality such as addition and multiplication
with scalars (both out of place and in place using `mul!`, `rmul!`, `axpy!` and `axpby!`)
applied to `v` is simply forwarded to `v.vec`. The inner product between vectors
`v1 = InnerProductVec(vec1, dotf)` and `v2 = InnerProductVec(vec2, dotf)` is computed as
`dot(v1, v2) = dotf(v1.vec, v2.vec) = dotf(vec1, vec2)`. The inner product between vectors
with different `dotf` functions is not defined. Similarly, The norm of `v::InnerProductVec`
is defined as `v = sqrt(real(dot(v, v))) = sqrt(real(dotf(vec, vec)))`.

In a (linear) map applied to `v`, the original vector can be obtained as `v.vec` or simply
as `v[]`.
"""
struct InnerProductVec{F,T}
    vec::T
    dotf::F
end

Base.:-(v::InnerProductVec) = InnerProductVec(-v.vec, v.dotf)
Base.:+(v::InnerProductVec{F}, w::InnerProductVec{F}) where {F} =
    InnerProductVec(v.vec + w.vec, v.dotf)
Base.:-(v::InnerProductVec{F}, w::InnerProductVec{F}) where {F} =
    InnerProductVec(v.vec - w.vec, v.dotf)
Base.:*(v::InnerProductVec, a::Number) = InnerProductVec(v.vec * a, v.dotf)
Base.:*(a::Number, v::InnerProductVec) = InnerProductVec(a * v.vec, v.dotf)
Base.:/(v::InnerProductVec, a::Number) = InnerProductVec(v.vec / a, v.dotf)
Base.:\(a::Number, v::InnerProductVec) = InnerProductVec(a \ v.vec, v.dotf)

Base.similar(v::InnerProductVec, ::Type{T} = eltype(v)) where {T} =
    InnerProductVec(similar(v.vec), v.dotf)

Base.getindex(v::InnerProductVec) = v.vec

function Base.copy!(w::InnerProductVec{F}, v::InnerProductVec{F}) where {F}
    copy!(w.vec, v.vec)
    return w
end

function LinearAlgebra.mul!(
    w::InnerProductVec{F},
    a::Number,
    v::InnerProductVec{F}
) where {F}
    mul!(w.vec, a, v.vec)
    return w
end

function LinearAlgebra.mul!(
    w::InnerProductVec{F},
    v::InnerProductVec{F},
    a::Number
) where {F}
    mul!(w.vec, v.vec, a)
    return w
end

function LinearAlgebra.rmul!(v::InnerProductVec, a::Number)
    rmul!(v.vec, a)
    return v
end

function LinearAlgebra.axpy!(
    a::Number,
    v::InnerProductVec{F},
    w::InnerProductVec{F}
) where {F}
    axpy!(a, v.vec, w.vec)
    return w
end
function LinearAlgebra.axpby!(
    a::Number,
    v::InnerProductVec{F},
    b,
    w::InnerProductVec{F}
) where {F}
    axpby!(a, v.vec, b, w.vec)
    return w
end

LinearAlgebra.dot(v::InnerProductVec{F}, w::InnerProductVec{F}) where {F} =
    v.dotf(v.vec, w.vec)
LinearAlgebra.norm(v::InnerProductVec) = sqrt(real(dot(v, v)))


using Pkg
Pkg.add("JLD")

using JLD

x = collect(-3:0.1:3)
y = collect(-3:0.1:3)

xx = reshape([xi for xi in x for yj in y], length(y), length(x))
yy = reshape([yj for xi in x for yj in y], length(y), length(x))

z = sin.(xx .+ yy .^ 2)

data_dict = Dict("x" => x, "y" => y, "z" => z)

save("data_dict.jld", data_dict)

#%% restart the REPL
using JLD
data_dict2 = load("data_dict.jld")

x2 = data_dict2["x"]
y2 = data_dict2["y"]
z2 = data_dict2["z"]

using Plots
plotly()

plot(x2, y2, z2, st = :surface, color = :ice)
savefig("img1")

#%%
using JLD
struct Person
    height::Float64
    weight::Float64
end

bob = Person(1.84, 74)

dict_new = Dict("bob" => bob)
save("bob.jld", dict_new)

#%% please restart the REPL
using JLD
struct Person
    height::Float64
    weight::Float64
end
bob2 = load("bob.jld")

bob2["bob"]

#%% please restart the REPL
using JLD

bob3 = load("bob.jld")

bob3["bob"]

bob3["bob"].height


import Base.*, Base.+, Base.-, Base./, Base.show, Base.!=, Base.==, Base.<=, Base.<, Base.>, Base.>=, Base.divrem

const z0 = "0"
const z1 = "1"
const flipordered = (z1 < z0)

mutable struct Z s::String end
Z() = Z(z0)
Z(z::Z) = Z(z.s)

pairlen(x::Z, y::Z) = max(length(x.s), length(y.s))
tolen(x::Z, n::Int) = (s = x.s; while length(s) < n s = z0 * s end; s)

<(x::Z, y::Z) = (l = pairlen(x, y); flipordered ? tolen(x, l) > tolen(y, l) : tolen(x, l) < tolen(y, l))
>(x::Z, y::Z) = (l = pairlen(x, y); flipordered ? tolen(x, l) < tolen(y, l) : tolen(x, l) > tolen(y, l))
==(x::Z, y::Z) = (l = pairlen(x, y); tolen(x, l) == tolen(y, l))
<=(x::Z, y::Z) = (l = pairlen(x, y); flipordered ? tolen(x, l) >= tolen(y, l) : tolen(x, l) <= tolen(y, l))
>=(x::Z, y::Z) = (l = pairlen(x, y); flipordered ? tolen(x, l) <= tolen(y, l) : tolen(x, l) >= tolen(y, l))
!=(x::Z, y::Z) = (l = pairlen(x, y); tolen(x, l) != tolen(y, l))

function tocanonical(z::Z)
    while occursin(z0 * z1 * z1, z.s)
        z.s = replace(z.s, z0 * z1 * z1 => z1 * z0 * z0)
    end
    len = length(z.s)
    if len > 1 && z.s[1:2] == z1 * z1
        z.s = z1 * z0 * z0 * ((len > 2) ? z.s[3:end] : "")
    end
    while (len = length(z.s)) > 1 && string(z.s[1]) == z0
        if len == 2
            if z.s == z0 * z0
                z.s = z0
            elseif z.s == z0 * z1
                z.s = z1
            end
        else
            z.s = z.s[2:end]
        end
    end
    z
end

function inc(z)
    if z.s[end] == z0[1]
        z.s = z.s[1:end-1] * z1[1]
    elseif z.s[end] == z1[1]
        if length(z.s) > 1
            if z.s[end-1:end] == z0 * z1
                z.s = z.s[1:end-2] * z1 * z0
            end
        else
            z.s = z1 * z0
        end
    end
    tocanonical(z)
end

function dec(z)
    if z.s[end] == z1[1]
        z.s = z.s[1:end-1] * z0
    else
        if (m = match(Regex(z1 * z0 * '+' * '$'), z.s)) != nothing
            len = length(m.match)
            if iseven(len)
                z.s = z.s[1:end-len] * (z0 * z1) ^ div(len, 2)
            else
                z.s = z.s[1:end-len] * (z0 * z1) ^ div(len, 2) * z0
            end
        end
    end
    tocanonical(z)
    z
end

function +(x::Z, y::Z)
    a = Z(x.s)
    b = Z(y.s)
    while b.s != z0
        inc(a)
        dec(b)
    end
    a
end

function -(x::Z, y::Z)
    a = Z(x.s)
    b = Z(y.s)
    while b.s != z0
        dec(a)
        dec(b)
    end
    a
end

function *(x::Z, y::Z)
    if (x.s == z0) || (y.s == z0)
        return Z(z0)
    elseif x.s == z1
        return Z(y.s)
    elseif y.s == z1
        return Z(x.s)
    end
    a = Z(x.s)
    b = Z(z1)
    while b != y
        c = Z(z0)
        while c != x
            inc(a)
            inc(c)
        end
        inc(b)
    end
    a
end

function divrem(x::Z, y::Z)
    if y.s == z0
        throw("Zeckendorf division by 0")
    elseif (y.s == z1) || (x.s == z0)
        return Z(x.s)
    end
    a = Z(x.s)
    b = Z(y.s)
    c = Z(z0)
    while a > b
        a = a - b
        inc(c)
    end
    tocanonical(c), tocanonical(a)
end

function /(x::Z, y::Z)
    a, _ = divrem(x, y)
    a
end

show(io::IO, z::Z) = show(io, parse(BigInt, tocanonical(z).s))

function zeckendorftest()
    a = Z("10")
    b = Z("1001")
    c = Z("1000")
    d = Z("10101")

    println("Addition:")
    x = a
    println(x += a)
    println(x += a)
    println(x += b)
    println(x += c)
    println(x += d)

    println("\nSubtraction:")
    x = Z("1000")
    println(x - Z("101"))
    x = Z("10101010")
    println(x - Z("1010101"))

    println("\nMultiplication:")
    x = Z("1001")
    y = Z("101")
    println(x * y)
    println(Z("101010") * y)

    println("\nDivision:")
    x = Z("1000101")
    y = Z("101")
    println(x / y)
    println(divrem(x, y))
end

zeckendorftest()


using Plots,Statistics
dim=10

function NeighborReturner(network_, i, j)
    neighbors=[]
    if network_[i,j-1]!=0
        push!(neighbors, network_[i,j-1]) # the left neighbor--> neighbor[1]
    end
    if i!=1 && network_[i-1,j]!=0 #if true --> the neighbors[2] is the upper neighbor
        push!(neighbors,network_[i-1,j])
    end
    return neighbors
end
function InitialLabelFinder(a, L)
    while L[a] != L[L[a]]
        a = L[a]
    end
    return L[a]
end
function percolation(dim,p)
    network_=zeros(Int, dim, dim)
    L=[1]
    S=[dim]
    counter=2
    for i in 1:dim
        network_[i,1]=1
    end
    for col in 2:dim
        for row in 1:dim
            if p>rand()
                neighbors=NeighborReturner(network_, row, col)
                #println(row, col,neighbors)
                if length(neighbors)==0
                    network_[row,col]=counter
                    push!(L, counter)
                    push!(S, 1)
                    counter+=1
                elseif length(neighbors)==1
                    network_[row,col]=InitialLabelFinder(neighbors[1], L)
                    S[InitialLabelFinder(neighbors[1], L)]+=1
                elseif length(neighbors)!=0 && InitialLabelFinder(neighbors[1],L)==InitialLabelFinder(neighbors[2],L)
                    network_[row,col]=InitialLabelFinder(neighbors[1], L)
                    S[InitialLabelFinder(neighbors[1], L)]+=1
                else
                    network_[row,col]=InitialLabelFinder(neighbors[1], L)
                    L[InitialLabelFinder(neighbors[2], L)]=InitialLabelFinder(neighbors[1], L)
                    S[InitialLabelFinder(neighbors[1], L)]+=1+S[InitialLabelFinder(neighbors[2], L)]
                    S[InitialLabelFinder(neighbors[2], L)]=0

                end
            end
        end
    end
    return network_,L
end
function percolation_check(network_,L,dim)
    first_col=[]
    last_col=[]
    for i in 1:dim
        if network_[i,1]!=0
            push!(first_col,InitialLabelFinder(network_[i,1],L))
        end
    end
    for i in 1:dim
        if network_[i,dim]!=0
            push!(last_col,InitialLabelFinder(network_[i,dim],L))
        end
    end
    col=rand(1:dim) #choosing a random entry of the matrix
    row=rand(1:dim)
    if network_[row,col]!=0 && InitialLabelFinder(network_[row,col],L) in intersect(first_col,last_col)
        return 1
    else
        return 0
    end
end
#network_=percolation(dim, p)
Meanlist = []
STDlist = []
for p in 0.25:0.01:1
    check=[]
    for run_num in 1:1000
        network_,L=percolation(dim,p)
        push!(check,percolation_check(network_,L,dim))
    end
    push!(STDlist, std(check))
    push!(Meanlist, mean(check))
end
Meanlist
scatter(0.25:0.01:1, Meanlist, yerr=STDlist, xlabel="P", ylabel="Average infinite Q", legend=false)
savefig("C:\\Users\\Narges\\Documents\\GitHub\\computational_physics\\chapter4\\Fig\\4.4_L=10.png")


using ADCME
using PyPlot
db = Database("hessian.db")

close("all")
plot(npoints, npoints, "k--")

for seed in [2, 23, 233]
    c = execute(db, """
    SELECT nlambda, npoints from eigvals where seed=$seed order by npoints
    """)
    vals = collect(c)
    global npoints = [x[2] for x in vals]
    vals = [x[1] for x in vals]
    plot(npoints, vals, ".-", label = "Seed = $seed")
end
legend()
xlabel("\$||\\mathcal{I}||\$")
ylabel("Positive Eigenvalues")
ylim(0, 15)
grid("on")
minorticks_on()
savefig("hessian_eigenvalue_rank.png")


# Base Hamiltonian
function bHamilton(bitnum::Int)
    H = sparse(0.5*(I-σ₁))
    Iden = spdiagm([1,1])
    for i=2:bitnum
        H = H⊗Iden
    end

    for i=2:bitnum
        H_BC = Iden
        for j=2:bitnum
            if i==j
                H_BC = H_BC⊗sparse(0.5*(I-σ₁))
            else
                H_BC = H_BC⊗Iden
            end
        end
        H+=H_BC
    end
    return H
end

# problem Hamiltonian
function pHamilton{M,N}(ins::Instance{M,N},n::Integer)
    sum = spzeros(2^n,2^n);
    for clause in ins.c
        sum += spdiagm(Int[clause(i) for i=0:2^n-1])
    end
    return sum
end

export bHamilton,pHamilton


const ev_to_K = 11604.52500617u"K"

@testset "Frequencies" begin
    n_e = 1.0u"1/cm^3"
    ω_pe = electron_plasma_frequency(n_e)
    ω_pe_approx = 5.64e4 * sqrt(n_e |> ustrip) * u"s^-1"
    @test isapprox(ω_pe, ω_pe_approx, atol=1e2u"s^-1")

    n_i = 1.0u"1/cm^3"
    Z = 1.0
    m_i = 1.0u"g"
    μ = (m_i / ProtonMass) |> NoUnits
    ω_pi = ion_plasma_frequency(n_i, m_i, Z)
    ω_pi_approx = 1.32e3 * Z * μ^(-1/2) * sqrt(n_i |> ustrip) * u"s^-1"
    @test isapprox(ω_pi, ω_pi_approx, atol=1u"s^-1")
end

@testset "Velocities" begin
    T_eV = 1u"eV"
    TK = ustrip(u"eV", T_eV) * ev_to_K
    @test isapprox(electron_thermal_velocity(TK), 4.19e7u"cm/s", atol=1e5u"cm/s")
    @test isapprox(electron_thermal_velocity(T_eV), 4.19e7u"cm/s", atol=1e5u"cm/s")
end


"""
Calculate distances, angles, speeds and points for aviation navigation purposes.

All angles are in degrees, distances are standard in meters, and speeds are
standard in m/s.

These methods are not to be used for operational purposes.

Implemented Functions:
* distance
* angular_distance
* bearing
* final_bearing
* midpoint
* intermediate_point
* destination_point
* intersection_point
* along_track_distance
* cross_track_distance
* Vground
* head_wind
* cross_wind
* normalize
* closest_point_to_pole
* isinside

Implemented Types:
* Point(ϕ, λ)
* Airspace

Implemented constants:
* Rₑ_m    Radius Earth in [m]

Based on:
* source: www.movable-type.co.uk/scripts/latlong.html
* source: edwilliams.org/avform.htm
"""
module Navigation

import Base.-, Base.Math.rad2deg, Base.Math.deg2rad

include("utility.jl")
include("airspace.jl")
include("distances.jl")
include("bearings.jl")
include("points.jl")
include("wind.jl")

#TODO Crossing parallels
#TODO Rhumb lines

end # module


module SCP_VB

include("../Misc.jl")
using .Misc

using Distributions, SpecialFunctions

export standard_VB

function standard_VB(X::Array{ℜ,3}, X⁺::Array{ℜ,2}, R::Ƶ ; μ::ℜ=3.5, γ::ℜ=0.1, EPOCHS::Ƶ=1, ϵ::ℜ=1e-16) where {ℜ<:Real, Ƶ<:Int}
    I::Ƶ, J::Ƶ, K::Ƶ = size(X)

    a::ℜ, b::ℜ = I*J*K*γ, γ/μ
    α_R, α_KR, α_IKR, α_JKR = fill(a/R,R), fill(a/(K*R),K,R), fill(a/(I*K*R),I,K,R), fill(a/(J*K*R),J,K,R)

    log_λ = log(rand(Gamma(a,1.0/b)))
    log_θ_R = log.(rand(Dirichlet(α_R)))
    log_θ_KR = reshape([log(θ_kr) for r=1:R for θ_kr=rand(Dirichlet(α_KR[:,r] .+ 1.0/K ))],K,R)
    log_θ_IKR = reshape([log(θ_ikr) for k=1:K, r=1:R for θ_ikr=rand(Dirichlet(α_IKR[:,k,r] .+ 1.0/I))],I,K,R)
    log_θ_JKR = reshape([log(θ_jkr) for k=1:K, r=1:R for θ_jkr=rand(Dirichlet(α_JKR[:,k,r] .+ 1.0/J))],J,K,R)

    s, S_R, S_KR, S_IKR, S_JKR, S⁺ = zeros(ℜ,K,R), zeros(ℜ,R), zeros(ℜ,K,R), zeros(ℜ,I,K,R), zeros(ℜ,J,K,R), sum(X⁺)
    π, log_π, log_ρ = Array{ℜ}(undef,K,R), Array{ℜ}(undef,K,R), Array{ℜ}(undef,K,R)
    log_ρ_ij::ℜ, log_ρ_ijk::Array{ℜ} = 0.0, Array{ℜ}(undef,K)

    ELBO::Array{ℜ} = zeros(ℜ,EPOCHS)
    X_full = copy(X)

    for eph=1:EPOCHS
        S_R .= 0.0
        S_KR .= 0.0
        S_IKR .= 0.0
        S_JKR .= 0.0

        for j=1:J, i=1:I #order of traversal is important

            log_ρ .= log_λ .+ log_θ_R' .+ log_θ_KR .+ log_θ_IKR[i,:,:] .+ log_θ_JKR[j,:,:]

            if isnan(X[i,j,1])
                log_ρ_ij = logsumexp(log_ρ)
                log_π .= log_ρ .- log_ρ_ij
                π .= exp.(log_π)
                s = X⁺[i,j] .* π
                X_full[i,j,:] .= sum(s,dims=2)[:,1]
                ELBO[eph] += X⁺[i,j] * log_ρ_ij - lgamma(X⁺[i,j] + 1.0)
            else
                log_ρ_ijk = logsumexp(log_ρ;dims=2)
                log_π .= log_ρ .- log_ρ_ijk
                π .= exp.(log_π)
                s = X[i,j,:] .* π
                ELBO[eph] += sum(X[i,j,:] .* log_ρ_ijk .- lgamma.(X[i,j,:] .+ 1.0))
            end

            S_R .+= sum(s,dims=1)[1,:]
            S_KR .+= s
            S_JKR[j,:,:] .+= s
            S_IKR[i,:,:] .+= s

        end

        ELBO[eph] += a*log(b) - (S⁺+a)*log(b+1.0) + sum(lgamma.(α_KR)) - sum(lgamma.(α_KR .+ S_KR))
        ELBO[eph] += sum(lgamma.(α_IKR .+ S_IKR)) - sum(lgamma.(α_IKR))
        ELBO[eph] += sum(lgamma.(α_JKR .+ S_JKR)) - sum(lgamma.(α_JKR))
        ELBO[eph] -= S⁺ * log_λ + sum(S_R .* log_θ_R) + sum(S_KR .* log_θ_KR)
        ELBO[eph] -= sum(S_JKR .* log_θ_JKR) + sum(S_IKR .* log_θ_IKR)

        log_λ = digamma(S⁺+a) - log(b+1.0)
        log_θ_R .= digamma.(S_R.+α_R) .- digamma(S⁺+a)
        log_θ_KR .= digamma.(S_KR.+α_KR) .- digamma.(S_R.+α_R)'
        log_θ_IKR .= digamma.(S_IKR.+α_IKR) .- reshape(digamma.(S_KR.+α_KR),1,K,R)
        log_θ_JKR .= digamma.(S_JKR.+α_JKR) .- reshape(digamma.(S_KR.+α_KR),1,K,R)

    end
    return ELBO, X_full, log_λ, (log_θ_R, log_θ_KR, log_θ_IKR, log_θ_JKR)
end

end


# Plots the eccentricity prior:

#include("/Users/ericagol/Observing/Spitzer/DDT2019/Campaign04/v09/Hyak/histogram_code.jl")
include("../../../src/histogram_code.jl")
necc = 100000000
ecc = 0.1 .*rand(necc)
omega = 2pi .* rand(necc)

ecos = ecc .* cos.(omega)
esin = ecc .* sin.(omega)

ecos_bin,ecos_hist,ecos_bin_sq,ecos_hist_sq = histogram(ecos,333)
esin_bin,esin_hist,esin_bin_sq,esin_hist_sq = histogram(esin,333)
eboth_hist = 0.5*(ecos_hist_sq .+ esin_hist_sq)
ax.plot(ecos_bin_sq,eboth_hist./maximum(eboth_hist),label="Uniform eccentricity prior")
ax.axis([-0.0175,0.0175,0,1.05])
ax.legend()


using BenchmarkTools, RiskAdjustedLinearizations, MATLAB
include(joinpath(dirname(@__FILE__), "..", "rbc_cc", "rbc_cc.jl"))

# Settings: what do you want to do?
autodiff = false
time_matlab = true

# Set up
autodiff_method = autodiff ? :forward : :central
m_rbc_cc = RBCCampbellCochraneHabits()
m = rbc_cc(m_rbc_cc, 0)
z0 = copy(m.z)
y0 = copy(m.y)
Ψ0 = copy(m.Ψ)

# Use deterministic steady state as guesses
solve!(m, z0, y0; algorithm = :deterministic, autodiff = autodiff_method, verbose = :none)
zdet = copy(m.z)
ydet = copy(m.y)
Ψdet = copy(m.Ψ)

if time_matlab
    println("Relaxation algorithm in MATLAB")
    mat"""
    genaffine_rbc_cc_relaxation;
    """
end

println("Relaxation algorithm in Julia")
@btime begin # called the "iterative" method in the original paper
    solve!(m, zdet, ydet, Ψdet; algorithm = :relaxation, autodiff = autodiff_method, verbose = :none)
end

println("Relaxation algorithm with Anderson acceleration")
@btime begin
    solve!(m, zdet, ydet, Ψdet; algorithm = :relaxation, use_anderson = true, m = 3, autodiff = autodiff_method, verbose = :none)
end

sparsity, colorvec = compute_sparsity_pattern(m, :relaxation; sparsity_detection = false)
jac_cache = preallocate_jac_cache(m, :relaxation; sparsity_detection = false)

println("Relaxation with matrix coloring of sparse Jacobians")
@btime begin
    solve!(m, zdet, ydet, Ψdet; algorithm = :relaxation, autodiff = autodiff_method,
           sparse_jacobian = true, sparsity = sparsity, colorvec = colorvec, verbose = :none)
end

println("Relaxation with matrix coloring of sparse Jacobians and caching")
@btime begin
    solve!(m, zdet, ydet, Ψdet; algorithm = :relaxation, autodiff = autodiff_method,
           sparse_jacobian = true, jac_cache = jac_cache, verbose = :none)
end

if time_matlab
    println("Homotopy algorithm in MATLAB")
    mat"""
    genaffine_rbc_cc_homotopy;
    """
end

println("Homotopy algorithm in Julia")
@btime begin # called the "continuation" method in the original paper, but is called homotopy in the original code
    solve!(m, zdet, ydet, Ψdet; algorithm = :homotopy, autodiff = autodiff_method, verbose = :none)
end

sparsity, colorvec = compute_sparsity_pattern(m, :homotopy; sparsity_detection = false)
jac_cache = preallocate_jac_cache(m, :homotopy; sparsity_detection = false)

println("Homotopy with matrix coloring of sparse Jacobians")
@btime begin
    solve!(m, zdet, ydet, Ψdet; algorithm = :homotopy, autodiff = autodiff_method,
           sparse_jacobian = true, sparsity = sparsity, colorvec = colorvec, verbose = :none)
end

println("Homotopy with matrix coloring of sparse Jacobians and caching")
@btime begin
    solve!(m, zdet, ydet, Ψdet; algorithm = :homotopy, autodiff = autodiff_method,
           sparse_jacobian = true, jac_cache = jac_cache, verbose = :none)
end


using JuMP, NPZ, Tulip
using Printf
using Statistics
using LinearAlgebra

function Solve_func(year, tol)

	println("\nSolving weights for $year ...\n\n")

	# we only solve the weights for years where the targets have changed. If the
	# targets have not changed, we don't write the _input.npz file
	if isfile(string(year, "_input.npz"))
		array = npzread(string(year, "_input.npz"))
	else
		println("Skipping solver for $year \n")
		return nothing
	end

	A1 = array["A1"]
	A2 = array["A2"]
	b = array["b"]

	N = size(A1)[2]

        # scaling: determine a scaling vector with one value per constraint
	#  - the goal is to keep coefficients reasonably near 1.0
	#  - multiply each row of A1 and A2 by its specific scaling constant
	#  - multiply each element of the b target vector by its scaling constant
	#  - current approach: choose scale factors so that the sum of absolute values in each row of
	#    A1 and of A2 will equal the total number of records / 1000; maybe we can improve on this

	scale = (N / 1000.) ./ sum(abs.(A1), dims=2)

        A1s = scale .* A1
	A2s = scale .* A2
	bs = scale .* b

	model = Model(Tulip.Optimizer)
	set_optimizer_attribute(model, "OutputLevel", 1)  # 0=disable output (default), 1=show iterations
	set_optimizer_attribute(model, "IPM_IterationsLimit", 100)  # default 100 seems to be enough

	# r and s must each fall between 0 and the tolerance
	@variable(model, 0 <= r[1:N] <= tol)
	@variable(model, 0 <= s[1:N] <= tol)

	@objective(model, Min, sum(r[i] + s[i] for i in 1:N))

	# Ax = b  - use the scaled matrices and vector
	@constraint(model, [i in 1:length(bs)], sum(A1s[i,j] * r[j] + A2s[i,j] * s[j]
		                          for j in 1:N) == bs[i])

	optimize!(model)

	println("Termination status: ", termination_status(model))
	@printf "Objective = %.4f\n" objective_value(model)

	r_vec = value.(r)
	s_vec = value.(s)

	npzwrite(string(year, "_output.npz"), Dict("r" => r_vec, "s" => s_vec))

	println("\n")

	# quick checks on results

	# Did we satisfy constraints?
	rs = r_vec - s_vec
	b_calc = sum(rs' .* A1, dims=2)
	check = vec(b_calc) ./ b

	q = (0, .1, .25, .5, .75, .9, 1)
	println("Quantiles used below: ", q)

	println("\nQuantiles of ratio of calculated targets to intended targets: ")
	println(quantile!(check, q))

	# Are the ratios of new weights to old weights in bounds (within tolerances)?
	x = 1.0 .+ r_vec - s_vec  # note the .+
	println("\nQuantiles of ratio of new weight to initial weight: ")
	println(quantile!(x, q))

end

year_list = [x for x in 2012:2031]
tol_list = [0.40, 0.38, 0.35, 0.33, 0.30,
 			0.45, 0.45, 0.45, 0.45, 0.45,
			0.45, 0.45, 0.45, 0.45,	0.45,
			0.45, 0.45, 0.45, 0.45, 0.45]

# Run solver function for all years and tolerances (in order)
for i in zip(year_list, tol_list)
	Solve_func(i[1], i[2])
end


# Tested in Julia 1.5.1
using DelimitedFiles, Combinatorics
data = readdlm("./data/adventInput09.txt", Int64)
function ValidOr(preamble, nextNum; d = 2)
    pairs = permutations(preamble, d)
    c = 0
    for p in pairs
        c += 1
        if p[1] != p[2] && sum(p) == nextNum
            return true
            break
        elseif c == length(pairs)
            return false
        end
    end
end
function findNotValid(data; idx = 1, lastStep = 25)
    for i in 1:length(data)
        preamble = data[idx:lastStep]
        nextNum = data[lastStep + 1]
        if ValidOr(preamble, nextNum; d = 2) == false
            return nextNum
            break
        end
        lastStep += 1
        idx += 1
    end
end
function encryptionWeakness(data)
    notvalid = findNotValid(data)
    for i in 1:length(data)
        sumInvalid = data[i]
        for j in i+1:length(data)
            sumInvalid += data[j]
            if sumInvalid == notvalid
                return minimum(data[i:j]) + maximum(data[i:j])
                break
            end
        end
    end
end
encryptionWeakness(data)


module Doublets

export Doublet

using ..Elements
using ..Motions

struct Doublet{T <: Number} <: Element
    z::ComplexF64
    S::T
end

Elements.kind(::Doublet) = Singleton
Elements.kind(::Type{Doublet{T}}) where T = Singleton

Elements.position(d::Doublet) = d.z
Elements.streamfunction(z::ComplexF64, d::Doublet) = imag(d.S/(z - d.z)/π)
Elements.complexpotential(z::ComplexF64, d::Doublet) = d.S/(z - d.z)/π

function Motions.induce_velocity(z::ComplexF64, d::Doublet, t)
    conj(-d.S/(z - d.z)^2/π)
end

end


using Random:
    AbstractRNG, MersenneTwister, randperm, seed!, shuffle!
using Statistics: mean

using LightGraphs:
    getRNG, sample!

"""
    SimpleGraph{T}(nv, ne; seed=-1)

Construct a random `SimpleGraph{T}` with `nv` vertices and `ne` edges.
The graph is sampled uniformly from all such graphs.
If `seed >= 0`, a random generator is seeded with this value.
If not specified, the element type `T` is the type of `nv`.

### See also
[`erdos_renyi`](@ref)

## Examples
```jldoctest
julia> SimpleGraph(5, 7)
{5, 7} undirected simple Int64 graph
```
"""
function SimpleGraph{T}(nv::Integer, ne::Integer; seed::Int=-1) where T <: Integer
    tnv = T(nv)
    maxe = div(Int(nv) * (nv - 1), 2)
    @assert(ne <= maxe, "Maximum number of edges for this graph is $maxe")
    ne > (2 / 3) * maxe && return complement(SimpleGraph(nv, maxe - ne))

    rng = getRNG(seed)
    g = SimpleGraph(tnv)

    while g.ne < ne
        source = rand(rng, one(T):tnv)
        dest = rand(rng, one(T):tnv)
        source != dest && add_edge!(g, source, dest)
    end
    return g
end

SimpleGraph(nv::T, ne::Integer; seed::Int=-1) where T <: Integer =
    SimpleGraph{T}(nv, ne, seed=seed)

"""
    SimpleDiGraph{T}(nv, ne; seed=-1)

Construct a random `SimpleDiGraph{T}` with `nv` vertices and `ne` edges.
The graph is sampled uniformly from all such graphs.
If `seed >= 0`, a random generator is seeded with this value.
If not specified, the element type `T` is the type of `nv`.

### See also
[`erdos_renyi`](@ref)

## Examples
```jldoctest
julia> SimpleDiGraph(5, 7)
{5, 7} directed simple Int64 graph
```
"""
function SimpleDiGraph{T}(nv::Integer, ne::Integer; seed::Int=-1) where T <: Integer
    tnv = T(nv)
    maxe = Int(nv) * (nv - 1)
    @assert(ne <= maxe, "Maximum number of edges for this graph is $maxe")
    ne > (2 / 3) * maxe && return complement(SimpleDiGraph{T}(nv, maxe - ne))

    rng = getRNG(seed)
    g = SimpleDiGraph(tnv)
    while g.ne < ne
        source = rand(rng, one(T):tnv)
        dest = rand(rng, one(T):tnv)
        source != dest && add_edge!(g, source, dest)
    end
    return g
end

SimpleDiGraph(nv::T, ne::Integer; seed::Int=-1) where T <: Integer =
    SimpleDiGraph{Int}(nv, ne, seed=seed)

"""
    randbn(n, p, seed=-1)

Return a binomally-distribted random number with parameters `n` and `p` and optional `seed`.

### References
- "Non-Uniform Random Variate Generation," Luc Devroye, p. 522. Retrieved via http://www.eirene.de/Devroye.pdf.
- http://stackoverflow.com/questions/23561551/a-efficient-binomial-random-number-generator-code-in-java
"""
function randbn(n::Integer, p::Real, seed::Integer=-1)
    rng = getRNG(seed)
    log_q = log(1.0 - p)
    x = 0
    sum = 0.0
    while true
        sum += log(rand(rng)) / (n - x)
        sum < log_q && break
        x += 1
    end
    return x
end

"""
    erdos_renyi(n, p)

Create an [Erdős–Rényi](http://en.wikipedia.org/wiki/Erdős–Rényi_model)
random graph with `n` vertices. Edges are added between pairs of vertices with
probability `p`.

### Optional Arguments
- `is_directed=false`: if true, return a directed graph.
- `seed=-1`: set the RNG seed.
"""
function erdos_renyi(n::Integer, p::Real; is_directed=false, seed::Integer=-1)
    p >= 1 && return is_directed ? CompleteDiGraph(n) : CompleteGraph(n)
    m = is_directed ? n * (n - 1) : div(n * (n - 1), 2)
    ne = randbn(m, p, seed)
    return is_directed ? SimpleDiGraph(n, ne, seed=seed) : SimpleGraph(n, ne, seed=seed)
end

"""
    erdos_renyi(n, ne)

Create an [Erdős–Rényi](http://en.wikipedia.org/wiki/Erdős–Rényi_model) random
graph with `n` vertices and `ne` edges.

### Optional Arguments
- `is_directed=false`: if true, return a directed graph.
- `seed=-1`: set the RNG seed.
"""
function erdos_renyi(n::Integer, ne::Integer; is_directed=false, seed::Integer=-1)
    return is_directed ? SimpleDiGraph(n, ne, seed=seed) : SimpleGraph(n, ne, seed=seed)
end

"""
    expected_degree_graph(ω)

Given a vector of expected degrees `ω` indexed by vertex, create a random undirected graph in which vertices `i` and `j` are
connected with probability `ω[i]*ω[j]/sum(ω)`.

### Optional Arguments
- `seed=-1`: set the RNG seed.

### Implementation Notes
The algorithm should work well for `maximum(ω) << sum(ω)`. As `maximum(ω)` approaches `sum(ω)`, some deviations
from the expected values are likely.

### References
- Connected Components in Random Graphs with Given Expected Degree Sequences, Linyuan Lu and Fan Chung. [https://link.springer.com/article/10.1007%2FPL00012580](https://link.springer.com/article/10.1007%2FPL00012580)
- Efficient Generation of Networks with Given Expected Degrees, Joel C. Miller and Aric Hagberg. [https://doi.org/10.1007/978-3-642-21286-4_10](https://doi.org/10.1007/978-3-642-21286-4_10)
"""
function expected_degree_graph(ω::Vector{T}; seed::Int=-1) where T <: Real
    g = SimpleGraph(length(ω))
    expected_degree_graph!(g, ω, seed=seed)
end

function expected_degree_graph!(g::SimpleGraph, ω::Vector{T}; seed::Int=-1) where T <: Real
    n = length(ω)
    @assert all(zero(T) .<= ω .<= n - one(T)) "Elements of ω needs to be at least 0 and at most n-1"

    π = sortperm(ω, rev=true)
    rng = getRNG(seed)

    S = sum(ω)

    for u = 1:(n - 1)
        v = u + 1
        p = min(ω[π[u]] * ω[π[v]] / S, one(T))
        while v <= n && p > zero(p)
            if p != one(T)
                v += floor(Int, log(rand(rng)) / log(one(T) - p))
            end
            if v <= n
                q = min(ω[π[u]] * ω[π[v]] / S, one(T))
                if rand(rng) < q / p
                    add_edge!(g, π[u], π[v])
                end
                p = q
                v += 1
            end
        end
    end
    return g
end

"""
    watts_strogatz(n, k, β)

Return a [Watts-Strogatz](https://en.wikipedia.org/wiki/Watts_and_Strogatz_model)
small model random graph with `n` vertices, each with degree `k`. Edges are
randomized per the model based on probability `β`.

### Optional Arguments
- `is_directed=false`: if true, return a directed graph.
- `seed=-1`: set the RNG seed.
"""
function watts_strogatz(n::Integer, k::Integer, β::Real; is_directed=false, seed::Int=-1)
    @assert k < n / 2
    if is_directed
        g = SimpleDiGraph(n)
    else
        g = SimpleGraph(n)
    end
    rng = getRNG(seed)
    for s in 1:n
        for i in 1:(floor(Integer, k / 2))
            target = ((s + i - 1) % n) + 1
            if rand(rng) > β && !has_edge(g, s, target) # TODO: optimize this based on return of add_edge!
                add_edge!(g, s, target)
            else
                while true
                    d = target
                    while d == target
                        d = rand(rng, 1:(n - 1))
                        if s < d
                            d += 1
                        end
                    end
                    if s != d
                        add_edge!(g, s, d) && break
                    end
                end
            end
        end
    end
    return g
end

function _suitable(edges::Set{SimpleEdge{T}}, potential_edges::Dict{T,T}) where T <: Integer
    isempty(potential_edges) && return true
    list = keys(potential_edges)
    for s1 in list, s2 in list
        s1 >= s2 && continue
        (SimpleEdge(s1, s2) ∉ edges) && return true
    end
    return false
end

_try_creation(n::Integer, k::Integer, rng::AbstractRNG) = _try_creation(n, fill(k, n), rng)

function _try_creation(n::T, k::Vector{T}, rng::AbstractRNG) where T <: Integer
    edges = Set{SimpleEdge{T}}()
    m = 0
    stubs = zeros(T, sum(k))
    for i = one(T):n
        for j = one(T):k[i]
            m += 1
            stubs[m] = i
        end
    end
    # stubs = vcat([fill(i, k[i]) for i = 1:n]...) # slower

    while !isempty(stubs)
        potential_edges =  Dict{T,T}()
        shuffle!(rng, stubs)
        for i in 1:2:length(stubs)
            s1, s2 = stubs[i:(i + 1)]
            if (s1 > s2)
                s1, s2 = s2, s1
            end
            e = SimpleEdge(s1, s2)
            if s1 != s2 && ∉(e, edges)
                push!(edges, e)
            else
                potential_edges[s1] = get(potential_edges, s1, 0) + 1
                potential_edges[s2] = get(potential_edges, s2, 0) + 1
            end
        end

        if !_suitable(edges, potential_edges)
            return Set{SimpleEdge{T}}()
        end

        stubs = Vector{Int}()
        for (e, ct) in potential_edges
            append!(stubs, fill(e, ct))
        end
    end
    return edges
end

"""
    barabasi_albert(n, k)

Create a [Barabási–Albert model](https://en.wikipedia.org/wiki/Barab%C3%A1si%E2%80%93Albert_model)
random graph with `n` vertices. It is grown by adding new vertices to an initial
graph with `k` vertices. Each new vertex is attached with `k` edges to `k`
different vertices already present in the system by preferential attachment.
Initial graphs are undirected and consist of isolated vertices by default.

### Optional Arguments
- `is_directed=false`: if true, return a directed graph.
- `complete=false`: if true, use a complete graph for the initial graph.
- `seed=-1`: set the RNG seed.
"""
barabasi_albert(n::Integer, k::Integer; keyargs...) =
barabasi_albert(n, k, k; keyargs...)

"""
    barabasi_albert(n::Integer, n0::Integer, k::Integer)

Create a [Barabási–Albert model](https://en.wikipedia.org/wiki/Barab%C3%A1si%E2%80%93Albert_model)
random graph with `n` vertices. It is grown by adding new vertices to an initial
graph with `n0` vertices. Each new vertex is attached with `k` edges to `k`
different vertices already present in the system by preferential attachment.
Initial graphs are undirected and consist of isolated vertices by default.

### Optional Arguments
- `is_directed=false`: if true, return a directed graph.
- `complete=false`: if true, use a complete graph for the initial graph.
- `seed=-1`: set the RNG seed.
"""
function barabasi_albert(n::Integer, n0::Integer, k::Integer; is_directed::Bool=false, complete::Bool=false, seed::Int=-1)
    if complete
        g = is_directed ? CompleteDiGraph(n0) : CompleteGraph(n0)
    else
        g = is_directed ? SimpleDiGraph(n0) : SimpleGraph(n0)
    end

    barabasi_albert!(g, n, k; seed=seed)
    return g
end

"""
    barabasi_albert!(g::AbstractGraph, n::Integer, k::Integer)

Create a [Barabási–Albert model](https://en.wikipedia.org/wiki/Barab%C3%A1si%E2%80%93Albert_model)
random graph with `n` vertices. It is grown by adding new vertices to an initial
graph `g`. Each new vertex is attached with `k` edges to `k` different vertices
already present in the system by preferential attachment.

### Optional Arguments
- `seed=-1`: set the RNG seed.
"""
function barabasi_albert!(g::AbstractGraph, n::Integer, k::Integer; seed::Int=-1)
    n0 = nv(g)
    1 <= k <= n0 <= n ||
        throw(ArgumentError("Barabási-Albert model requires 1 <= k <= nv(g) <= n"))
    n0 == n && return g

    # seed random number generator
    seed > 0 && seed!(seed)

    # add missing vertices
    sizehint!(g.fadjlist, n)
    add_vertices!(g, n - n0)

    # if initial graph doesn't contain any edges
    # expand it by one vertex and add k edges from this additional node
    if ne(g) == 0
        # expand initial graph
        n0 += one(n0)

        # add edges to k existing vertices
        for target in sample!(collect(1:(n0 - 1)), k)
            add_edge!(g, n0, target)
        end
    end

    # vector of weighted vertices (each node is repeated once for each adjacent edge)
    weightedVs = Vector{Int}(undef, 2 * (n - n0) * k + 2 * ne(g))

    # initialize vector of weighted vertices
    offset = 0
    for e in edges(g)
        weightedVs[offset += 1] = src(e)
        weightedVs[offset += 1] = dst(e)
    end

    # array to record if a node is picked
    picked = fill(false, n)

    # vector of targets
    targets = Vector{Int}(undef, k)

    for source in (n0 + 1):n
        # choose k targets from the existing vertices
        # pick uniformly from weightedVs (preferential attachement)
        i = 0
        while i < k
            target = weightedVs[rand(1:offset)]
            if !picked[target]
                targets[i += 1] = target
                picked[target] = true
            end
        end

        # add edges to k targets
        for target in targets
            add_edge!(g, source, target)

            weightedVs[offset += 1] = source
            weightedVs[offset += 1] = target
            picked[target] = false
        end
    end
    return g
end

"""
    static_fitness_model(m, fitness)

Generate a random graph with ``|fitness|`` vertices and `m` edges,
in which the probability of the existence of ``Edge_{ij}`` is proportional
to ``fitness_i × fitness_j``.

### Optional Arguments
- `seed=-1`: set the RNG seed.

### Performance
Time complexity is ``\\mathcal{O}(|V| + |E| log |E|)``.

### References
- Goh K-I, Kahng B, Kim D: Universal behaviour of load distribution in scale-free networks. Phys Rev Lett 87(27):278701, 2001.
"""
function static_fitness_model(m::Integer, fitness::Vector{T}; seed::Int=-1) where T <: Real
    m < 0 && throw(ArgumentError("number of edges must be positive"))
    n = length(fitness)
    m == 0 && return SimpleGraph(n)
    nvs = 0
    for f in fitness
        # sanity check for the fitness
        f < zero(T) && throw(ArgumentError("fitness scores must be non-negative"))
        f > zero(T) && (nvs += 1)
    end
    # avoid getting into an infinite loop when too many edges are requested
    max_no_of_edges = div(nvs * (nvs - 1), 2)
    m > max_no_of_edges && throw(ArgumentError("too many edges requested ($m > $max_no_of_edges)"))
    # calculate the cumulative fitness scores
    cum_fitness = cumsum(fitness)
    g = SimpleGraph(n)
    _create_static_fitness_graph!(g, m, cum_fitness, cum_fitness, seed)
    return g
end

"""
    static_fitness_model(m, fitness_out, fitness_in)

Generate a random graph with ``|fitness\\_out + fitness\\_in|`` vertices and `m` edges,
in which the probability of the existence of ``Edge_{ij}`` is proportional with
respect to ``i ∝ fitness\\_out`` and ``j ∝ fitness\\_in``.

### Optional Arguments
- `seed=-1`: set the RNG seed.

### Performance
Time complexity is ``\\mathcal{O}(|V| + |E| log |E|)``.

### References
- Goh K-I, Kahng B, Kim D: Universal behaviour of load distribution in scale-free networks. Phys Rev Lett 87(27):278701, 2001.
"""
function static_fitness_model(m::Integer, fitness_out::Vector{T}, fitness_in::Vector{S}; seed::Int=-1) where T <: Real where S <: Real
    m < 0 && throw(ArgumentError("number of edges must be positive"))
    n = length(fitness_out)
    length(fitness_in) != n && throw(ArgumentError("fitness_in must have the same size as fitness_out"))
    m == 0 && return SimpleDiGraph(n)
    # avoid getting into an infinite loop when too many edges are requested
    noutvs = ninvs = nvs = 0
    @inbounds for i = 1:n
        # sanity check for the fitness
        (fitness_out[i] < zero(T) || fitness_in[i] < zero(S)) && error("fitness scores must be non-negative") # TODO 0.7: change to DomainError?
        fitness_out[i] > zero(T) && (noutvs += 1)
        fitness_in[i] > zero(S) && (ninvs += 1)
        (fitness_out[i] > zero(T) && fitness_in[i] > zero(S)) && (nvs += 1)
    end
    max_no_of_edges = noutvs * ninvs - nvs
    m > max_no_of_edges && throw(ArgumentError("too many edges requested ($m > $max_no_of_edges)"))
    # calculate the cumulative fitness scores
    cum_fitness_out = cumsum(fitness_out)
    cum_fitness_in = cumsum(fitness_in)
    g = SimpleDiGraph(n)
    _create_static_fitness_graph!(g, m, cum_fitness_out, cum_fitness_in, seed)
    return g
end

function _create_static_fitness_graph!(g::AbstractGraph, m::Integer, cum_fitness_out::Vector{T}, cum_fitness_in::Vector{S}, seed::Int) where T <: Real where S <: Real
    rng = getRNG(seed)
    max_out = cum_fitness_out[end]
    max_in = cum_fitness_in[end]
    while m > 0
        source = searchsortedfirst(cum_fitness_out, rand(rng) * max_out)
        target = searchsortedfirst(cum_fitness_in, rand(rng) * max_in)
        # skip if loop edge
        (source == target) && continue
        # is there already an edge? If so, try again
        add_edge!(g, source, target) || continue
        m -= one(m)
    end
end

"""
    static_scale_free(n, m, α)

Generate a random graph with `n` vertices, `m` edges and expected power-law
degree distribution with exponent `α`.

### Optional Arguments
- `seed=-1`: set the RNG seed.
- `finite_size_correction=true`: determines whether to use the finite size correction
proposed by Cho et al.

### Performance
Time complexity is ``\\mathcal{O}(|V| + |E| log |E|)``.

### References
- Goh K-I, Kahng B, Kim D: Universal behaviour of load distribution in scale-free networks. Phys Rev Lett 87(27):278701, 2001.
- Chung F and Lu L: Connected components in a random graph with given degree sequences. Annals of Combinatorics 6, 125-145, 2002.
- Cho YS, Kim JS, Park J, Kahng B, Kim D: Percolation transitions in scale-free networks under the Achlioptas process. Phys Rev Lett 103:135702, 2009.
"""
function static_scale_free(n::Integer, m::Integer, α::Real; seed::Int=-1, finite_size_correction::Bool=true)
    n < 0 && throw(ArgumentError("number of vertices must be positive"))
    α < 2 && throw(ArgumentError("out-degree exponent must be >= 2"))
    fitness = _construct_fitness(n, α, finite_size_correction)
    static_fitness_model(m, fitness, seed=seed)
end

"""
    static_scale_free(n, m, α_out, α_in)

Generate a random graph with `n` vertices, `m` edges and expected power-law
degree distribution with exponent `α_out` for outbound edges and `α_in` for
inbound edges.

### Optional Arguments
- `seed=-1`: set the RNG seed.
- `finite_size_correction=true`: determines whether to use the finite size correction
proposed by Cho et al.

### Performance
Time complexity is ``\\mathcal{O}(|V| + |E| log |E|)``.

### References
- Goh K-I, Kahng B, Kim D: Universal behaviour of load distribution in scale-free networks. Phys Rev Lett 87(27):278701, 2001.
- Chung F and Lu L: Connected components in a random graph with given degree sequences. Annals of Combinatorics 6, 125-145, 2002.
- Cho YS, Kim JS, Park J, Kahng B, Kim D: Percolation transitions in scale-free networks under the Achlioptas process. Phys Rev Lett 103:135702, 2009.
"""
function static_scale_free(n::Integer, m::Integer, α_out::Real, α_in::Float64; seed::Int=-1, finite_size_correction::Bool=true)
    n < 0 && throw(ArgumentError("number of vertices must be positive"))
    α_out < 2 && throw(ArgumentError("out-degree exponent must be >= 2"))
    α_in < 2 && throw(ArgumentError("out-degree exponent must be >= 2"))
    # construct the fitness
    fitness_out = _construct_fitness(n, α_out, finite_size_correction)
    fitness_in = _construct_fitness(n, α_in, finite_size_correction)
    # eliminate correlation
    shuffle!(fitness_in)
    static_fitness_model(m, fitness_out, fitness_in, seed=seed)
end

function _construct_fitness(n::Integer, α::Real, finite_size_correction::Bool)
    α = -1 / (α - 1)
    fitness = zeros(n)
    j = float(n)
    if finite_size_correction && α < -0.5
        # See the Cho et al paper, first page first column + footnote 7
        j += n^(1 + 1 / 2 * α) * (10 * sqrt(2) * (1 + α))^(-1 / α) - 1
    end
    j = max(j, n)
    @inbounds for i = 1:n
        fitness[i] = j^α
        j -= 1
    end
    return fitness
end

"""
    random_regular_graph(n, k)

Create a random undirected
[regular graph](https://en.wikipedia.org/wiki/Regular_graph) with `n` vertices,
each with degree `k`.

### Optional Arguments
- `seed=-1`: set the RNG seed.

### Performance
Time complexity is approximately ``\\mathcal{O}(nk^2)``.

### Implementation Notes
Allocates an array of `nk` `Int`s, and . For ``k > \\frac{n}{2}``, generates a graph of degree
``n-k-1`` and returns its complement.
"""
function random_regular_graph(n::Integer, k::Integer; seed::Int=-1)
    !iseven(n * k) && throw(ArgumentError("n * k must be even"))
    !(0 <= k < n) && throw(ArgumentError("the 0 <= k < n inequality must be satisfied"))
    if k == 0
        return SimpleGraph(n)
    end
    if (k > n / 2) && iseven(n * (n - k - 1))
        return complement(random_regular_graph(n, n - k - 1, seed=seed))
    end

    rng = getRNG(seed)

    edges = _try_creation(n, k, rng)
    while isempty(edges)
        edges = _try_creation(n, k, rng)
    end

    g = SimpleGraph(n)
    for edge in edges
        add_edge!(g, edge)
    end

    return g
end

"""
    random_configuration_model(n, ks)

Create a random undirected graph according to the [configuration model]
(http://tuvalu.santafe.edu/~aaronc/courses/5352/fall2013/csci5352_2013_L11.pdf)
containing `n` vertices, with each node `i` having degree `k[i]`.

### Optional Arguments
- `seed=-1`: set the RNG seed.
- `check_graphical=false`: if true, ensure that `k` is a graphical sequence
(see [`isgraphical`](@ref)).

### Performance
Time complexity is approximately ``\\mathcal{O}(n \\bar{k}^2)``.
### Implementation Notes
Allocates an array of ``n \\bar{k}`` `Int`s.
"""
function random_configuration_model(n::Integer, k::Array{T}; seed::Int=-1, check_graphical::Bool=false) where T <: Integer
    n != length(k) && throw(ArgumentError("a degree sequence of length n must be provided"))
    m = sum(k)
    !iseven(m) && throw(ArgumentError("sum(k) must be even"))
    !all(0 .<= k .< n) && throw(ArgumentError("the 0 <= k[i] < n inequality must be satisfied"))
    if check_graphical
        isgraphical(k) || throw(ArgumentError("degree sequence must be graphical"))
    end
    rng = getRNG(seed)

    edges = _try_creation(n, k, rng)
    while m > 0 && isempty(edges)
        edges = _try_creation(n, k, rng)
    end

    g = SimpleGraphFromIterator(edges)
    if nv(g) < n
        add_vertices!(g, n - nv(g))
    end
    return g
end

"""
    random_regular_digraph(n, k)

Create a random directed [regular graph](https://en.wikipedia.org/wiki/Regular_graph)
with `n` vertices, each with degree `k`.

### Optional Arguments
- `dir=:out`: the direction of the edges for degree parameter.
- `seed=-1`: set the RNG seed.

### Implementation Notes
Allocates an ``n × n`` sparse matrix of boolean as an adjacency matrix and
uses that to generate the directed graph.
"""
function random_regular_digraph(n::Integer, k::Integer; dir::Symbol=:out, seed::Int=-1)
    #TODO remove the function sample from StatsBase for one allowing the use
    # of a local rng
    !(0 <= k < n) && throw(ArgumentError("the 0 <= k < n inequality must be satisfied"))

    if k == 0
        return SimpleDiGraph(n)
    end
    if (k > n / 2) && iseven(n * (n - k - 1))
        return complement(random_regular_digraph(n, n - k - 1, dir=dir, seed=seed))
    end
    rng = getRNG(seed)
    cs = collect(2:n)
    i = 1
    I = Vector{Int}(undef, n * k)
    J = Vector{Int}(undef, n * k)
    V = fill(true, n * k)
    for r in 1:n
        l = ((r - 1) * k + 1):(r * k)
        I[l] .= r
        J[l] = sample!(rng, cs, k, exclude=r)
    end

    if dir == :out
        return SimpleDiGraph(sparse(I, J, V, n, n))
    else
        return SimpleDiGraph(sparse(I, J, V, n, n)')
    end
end

"""
    random_tournament_digraph(n)

Create a random directed [tournament graph]
(https://en.wikipedia.org/wiki/Tournament_%28graph_theory%29)
with `n` vertices.

### Optional Arguments
- `seed=-1`: set the RNG seed.
"""
function random_tournament_digraph(n::Integer; seed::Int=-1)

    rng = getRNG(seed)
    g = SimpleDiGraph(n)

    for i = 1:n, j = i + 1:n
        rand(rng, Bool) ? add_edge!(g, SimpleEdge(i, j)) : add_edge!(g, SimpleEdge(j, i))
    end

    return g
end

"""
    stochastic_block_model(c, n)

Return a Graph generated according to the Stochastic Block Model (SBM).

`c[a,b]` : Mean number of neighbors of a vertex in block `a` belonging to block `b`.
           Only the upper triangular part is considered, since the lower traingular is
           determined by ``c[b,a] = c[a,b] * \\frac{n[a]}{n[b]}``.
`n[a]` : Number of vertices in block `a`

### Optional Arguments
- `seed=-1`: set the RNG seed.

For a dynamic version of the SBM see the [`StochasticBlockModel`](@ref) type and
related functions.
"""
function stochastic_block_model(c::Matrix{T}, n::Vector{U}; seed::Int=-1) where T <: Real where U <: Integer
    size(c, 1) == size(c, 2) == length(n) || throw(ArgumentError("matrix-vector size mismatch"))

    # init dsfmt generator without altering GLOBAL_RNG
    rng = getRNG(seed)
    N = sum(n)
    K = length(n)
    nedg = zeros(Int, K, K)
    g = SimpleGraph(N)
    cum = [sum(n[1:a]) for a = 0:K]
    for a = 1:K
        ra = (cum[a] + 1):cum[a + 1]
        for b = a:K
            ((a == b) && !(c[a, b] <= n[b] - 1)) || ((a != b) && !(c[a, b] <= n[b])) &&
                error("Mean degree cannot be greater than available neighbors in the block.") # TODO 0.7: turn into some other error?

            m = a == b ? div(n[a] * (n[a] - 1), 2) : n[a] * n[b]
            p = a == b ? n[a] * c[a, b] / (2m) : n[a] * c[a, b] / m
            nedg = randbn(m, p, seed)
            rb = (cum[b] + 1):cum[b + 1]
            i = 0
            while i < nedg
                source = rand(rng, ra)
                dest = rand(rng, rb)
                if source != dest
                    if add_edge!(g, source, dest)
                        i += 1
                    end
                end
            end
        end
    end
    return g
end

"""
    stochastic_block_model(cint, cext, n)

Return a Graph generated according to the Stochastic Block Model (SBM), sampling
from an SBM with ``c_{a,a}=cint``, and ``c_{a,b}=cext``.
"""
function stochastic_block_model(cint::T, cext::T, n::Vector{U}; seed::Int=-1) where T <: Real where U <: Integer
    K = length(n)
    c = [ifelse(a == b, cint, cext) for a = 1:K, b = 1:K]
    stochastic_block_model(c, n, seed=seed)
end

"""
    StochasticBlockModel{T,P}

A type capturing the parameters of the SBM.
Each vertex is assigned to a block and the probability of edge `(i,j)`
depends only on the block labels of vertex `i` and vertex `j`.

The assignement is stored in nodemap and the block affinities a `k` by `k`
matrix is stored in affinities.

`affinities[k,l]` is the probability of an edge between any vertex in
block `k` and any vertex in block `l`.

### Implementation Notes
Graphs are generated by taking random ``i,j ∈ V`` and
flipping a coin with probability `affinities[nodemap[i],nodemap[j]]`.
"""
mutable struct StochasticBlockModel{T <: Integer,P <: Real}
    n::T
    nodemap::Array{T}
    affinities::Matrix{P}
    rng::MersenneTwister
end

==(sbm::StochasticBlockModel, other::StochasticBlockModel) =
    (sbm.n == other.n) && (sbm.nodemap == other.nodemap) && (sbm.affinities == other.affinities)

# A constructor for StochasticBlockModel that uses the sizes of the blocks
# and the affinity matrix. This construction implies that consecutive
# vertices will be in the same blocks, except for the block boundaries.
function StochasticBlockModel(sizes::AbstractVector, affinities::AbstractMatrix; seed::Int=-1)
    csum = cumsum(sizes)
    j = 1
    nodemap = zeros(Int, csum[end])
    for i in 1:csum[end]
        if i > csum[j]
            j += 1
        end
        nodemap[i] = j
    end
    return StochasticBlockModel(csum[end], nodemap, affinities, getRNG(seed))
end

### TODO: This documentation needs work. sbromberger 20170326
"""
    sbmaffinity(internalp, externalp, sizes)

Produce the sbm affinity matrix with internal probabilities `internalp`
and external probabilities `externalp`.
"""
function sbmaffinity(internalp::Vector{T}, externalp::Real, sizes::Vector{U}) where T <: Real where U <: Integer
    numblocks = length(sizes)
    numblocks == length(internalp) || throw(ArgumentError("Inconsistent input dimensions: internalp, sizes"))
    B = diagm(0 => internalp) + externalp * (ones(numblocks, numblocks) - I)
    return B
end

function StochasticBlockModel(internalp::Real,
                              externalp::Real,
                              size::Integer,
                              numblocks::Integer;
                              seed::Int=-1)
    sizes = fill(size, numblocks)
    B = sbmaffinity(fill(internalp, numblocks), externalp, sizes)
    StochasticBlockModel(sizes, B, seed=seed)
end

function StochasticBlockModel(internalp::Vector{T}, externalp::Real,
    sizes::Vector{U}; seed::Int=-1) where T <: Real where U <: Integer
    B = sbmaffinity(internalp, externalp, sizes)
    return StochasticBlockModel(sizes, B, seed=seed)
end

const biclique = ones(2, 2) - Matrix{Float64}(I, 2, 2)

#TODO: this documentation needs work. sbromberger 20170326
"""
    nearbipartiteaffinity(sizes, between, intra)

Construct the affinity matrix for a near bipartite SBM.
`between` is the affinity between the two parts of each bipartite community.
`intra` is the probability of an edge within the parts of the partitions.

This is a specific type of SBM with ``\\frac{k}{2} blocks each with two halves.
Each half is connected as a random bipartite graph with probability `intra`
The blocks are connected with probability `between`.
"""
function nearbipartiteaffinity(sizes::Vector{T}, between::Real, intra::Real) where T <: Integer
    numblocks = div(length(sizes), 2)
    return kron(between * Matrix{Float64}(I, numblocks, numblocks), biclique) + Matrix{Float64}(I, 2 * numblocks, 2 * numblocks) * intra
end

#Return a generator for edges from a stochastic block model near-bipartite graph.
nearbipartiteaffinity(sizes::Vector{T}, between::Real, inter::Real, noise::Real) where T <: Integer =
    nearbipartiteaffinity(sizes, between, inter) .+ noise

nearbipartiteSBM(sizes, between, inter, noise; seed::Int=-1) =
    StochasticBlockModel(sizes, nearbipartiteaffinity(sizes, between, inter, noise), seed=seed)

"""
    random_pair(rng, n)

Generate a stream of random pairs in `1:n` using random number generator `RNG`.
"""
function random_pair(rng::AbstractRNG, n::Integer)
    f(ch) = begin
        while true
            put!(ch, SimpleEdge(rand(rng, 1:n), rand(rng, 1:n)))
        end
    end
    return f
end

"""
    make_edgestream(sbm)

Take an infinite sample from the Stochastic Block Model `sbm`.
Pass to `Graph(nvg, neg, edgestream)` to get a Graph object based on `sbm`.
"""
function make_edgestream(sbm::StochasticBlockModel)
    pairs = Channel(random_pair(sbm.rng, sbm.n), ctype=SimpleEdge, csize=32)
    edges(ch) = begin
        for e in pairs
            i, j = Tuple(e)
            i == j && continue
            p = sbm.affinities[sbm.nodemap[i], sbm.nodemap[j]]
            if rand(sbm.rng) < p
                put!(ch, e)
            end
        end
    end
    return Channel(edges, ctype=SimpleEdge, csize=32)
end

"""
    SimpleGraph{T}(nv, ne, edgestream::Channel)

Construct a `SimpleGraph{T}` with `nv` vertices and `ne` edges from `edgestream`.
Can result in less than `ne` edges if the channel `edgestream` is closed prematurely.
Duplicate edges are only counted once.
The element type is the type of `nv`.
"""
function SimpleGraph(nvg::Integer, neg::Integer, edgestream::Channel)
    g = SimpleGraph(nvg)
    # println(g)
    for e in edgestream
        add_edge!(g, e)
        ne(g) >= neg && break
    end
    # println(g)
    return g
end

"""
    SimpleGraph{T}(nv, ne, smb::StochasticBlockModel)

Construct a random `SimpleGraph{T}` with `nv` vertices and `ne` edges.
The graph is sampled according to the stochastic block model `smb`.
The element type is the type of `nv`.
"""
SimpleGraph(nvg::Integer, neg::Integer, sbm::StochasticBlockModel) =
    SimpleGraph(nvg, neg, make_edgestream(sbm))

#TODO: this documentation needs work. sbromberger 20170326
"""
    blockcounts(sbm, A)

Count the number of edges that go between each block.
"""
function blockcounts(sbm::StochasticBlockModel, A::AbstractMatrix)
    I = collect(1:sbm.n)
    J =  [sbm.nodemap[i] for i in 1:sbm.n]
    V =  ones(sbm.n)
    Q = sparse(I, J, V)
    return (Q'A) * Q
end

function blockcounts(sbm::StochasticBlockModel, g::AbstractGraph)
    return blockcounts(sbm, adjacency_matrix(g))
end

function blockfractions(sbm::StochasticBlockModel, g::Union{AbstractGraph,AbstractMatrix})
    bc = blockcounts(sbm, g)
    bp = bc ./ sum(bc)
    return bp
end

"""
    kronecker(SCALE, edgefactor, A=0.57, B=0.19, C=0.19)

Generate a directed [Kronecker graph](https://en.wikipedia.org/wiki/Kronecker_graph)
with the default Graph500 parameters.

###
References
- http://www.graph500.org/specifications#alg:generator
"""
function kronecker(SCALE, edgefactor, A=0.57, B=0.19, C=0.19)
    N = 2^SCALE
    M = edgefactor * N
    ij = ones(Int, M, 2)
    ab = A + B
    c_norm = C / (1 - (A + B))
    a_norm = A / (A + B)

    for ib = 1:SCALE
        ii_bit = rand(M) .> (ab)  # bitarray
        jj_bit = rand(M) .> (c_norm .* (ii_bit) + a_norm .* .!(ii_bit))
        ij .+= 2^(ib - 1) .* (hcat(ii_bit, jj_bit))
    end

    p = randperm(N)
    ij = p[ij]

    p = randperm(M)
    ij = ij[p, :]

    g = SimpleDiGraph(N)
    for (s, d) in zip(@view(ij[:, 1]), @view(ij[:, 2]))
        add_edge!(g, s, d)
    end
    return g
end

"""
    dorogovtsev_mendes(n)

Generate a random `n` vertex graph by the Dorogovtsev-Mendes method (with `n \\ge 3`).

The Dorogovtsev-Mendes process begins with a triangle graph and inserts `n-3` additional vertices.
Each time a vertex is added, a random edge is selected and the new vertex is connected to the two
endpoints of the chosen edge. This creates graphs with a many triangles and a high local clustering coefficient.

It is often useful to track the evolution of the graph as vertices are added, you can access the graph from
the `t`th stage of this algorithm by accessing the first `t` vertices with `g[1:t]`.

### References
- http://graphstream-project.org/doc/Generators/Dorogovtsev-Mendes-generator/
- https://arxiv.org/pdf/cond-mat/0106144.pdf#page=24
"""
function dorogovtsev_mendes(n::Integer; seed::Int=-1)
    n < 3 && throw(DomainError("n=$n must be at least 3"))
    rng = getRNG(seed)
    g = CycleGraph(3)

    for iteration in 1:(n-3)
        chosenedge = rand(rng, 1:(2*ne(g))) # undirected so each edge is listed twice in adjlist
        u, v = -1, -1
        for i in 1:nv(g)
            edgelist = outneighbors(g, i)
            if chosenedge > length(edgelist)
                chosenedge -= length(edgelist)
            else
                u = i
                v = edgelist[chosenedge]
                break
            end
        end

        add_vertex!(g)
        add_edge!(g, nv(g), u)
        add_edge!(g, nv(g), v)
    end
    return g
end

"""
    random_orientation_dag(g)

Generate a random oriented acyclical digraph. The function takes in a simple
graph and a random number generator as an argument. The probability of each
directional acyclic graph randomly being generated depends on the architecture
of the original directed graph.

DAG's have a finite topological order; this order is randomly generated via "order = randperm()".
"""
function random_orientation_dag(g::SimpleGraph{T}, seed::Int=-1) where T <: Integer
    nvg = length(g.fadjlist)
    rng = getRNG(seed)
    order = randperm(rng, nvg)
    g2 = SimpleDiGraph(nv(g))
    @inbounds for i in vertices(g)
        for j in outneighbors(g, i)
            if order[i] < order[j]
                add_edge!(g2, i, j)
            end
        end
    end
    return g2
end


using BenchmarkTools

# preallocate arrays
A = rand(100,100)
B = rand(100,100)

function inner_rows(A,B)
    for i in 1:100, j in 1:100
    C[i,j] = A[i,j] + B[i,j]
    end
end
@btime inner_rows(A,B)

function inner_cols(A,B)
    for j in 1:100, i in 1:100
    C[i,j] = A[i,j] + B[i,j]
    end
end
@btime inner_cols(A,B)

function inner_cols(A,B)
    for j in 1:100, i in 1:100
    val = [A[i,j] + B[i,j]]
    C[i,j] = val[1]
    end
end
@btime inner_cols(A,B)

function inner_cols(A,B)
    for j in 1:100, i in 1:100
    val = A[i,j] + B[i,j]
    C[i,j] = val
    end
end
@btime inner_cols(A,B)

using StaticArrays
val = SVector{3,Float64}(1.0,2.0,3.0)
typeof(val)

function static_inner_alloc(A,B)
  for j in 1:100, i in 1:100
    val = @SVector [A[i,j] + B[i,j]]
    C[i,j] = val[1]
  end
end
@btime static_inner_alloc(A,B)

function no_prealloc(A,B)
    C = rand(100,100)
    for j in 1:100, i in 1:100
    val = A[i,j] + B[i,j]
    C[i,j] = val
    end
end
@btime no_prealloc(A,B)

C = rand(100,100)
function w_prealloc!(C,A,B)
    for j in 1:100, i in 1:100
    val = A[i,j] + B[i,j]
    C[i,j] = val
    end
end
@btime w_prealloc!(C,A,B)

function no_prealloc(A,B)
    C = rand(100,100)
    for j in 1:100, i in 1:100
    val = A[i,j] + B[i,j]
    C[i,j] = val
    end
end
@btime no_prealloc(A,B)

function f(A,B)
    # 10*(A+B)
    sum([A + B for k in 1:10]) # calculate 10 arrays and sum them
end
@btime f(A,B)

function h(A,B)
    # 10*(A+B)
    C = similar(A) # create array for output
    for k in 1:10
        C .+= (A + B) # add all of A+B first then add this temp array to C
    end
end
@btime h(A,B)

function g(A,B)
    # 10*(A+B)
    C = similar(A) # create array for output
    for k in 1:10
        C .+= A.+ B # reuse memory in each iteration
    end
end
@btime g(A,B)

function g!(C,A,B)
    # 10*(A+B)
    for k in 1:10
        C .+= A.+ B # reuse memory in each iteration
    end
end
@btime g!(C,A,B)

function g!(C,A,B)
    # 10*(A+B)
    for k in 1:10
        C .+= A.+ B # reuse memory in each iteration
    end
    return nothing
end
C = zeros(100,100)
g!(C,A,B)
C

D = Matrix{Float64}(undef, 100, 100)
function f_vector!(D,C,A,B)
    D .*= A .* B.* C
end
@btime f_vector!(D,C,A,B)

function f_loop!(D,C,A,B)
    for i in 1:length(D)
        @inbounds D[i] = A[i] * B[i] * C[i]
    end
end
@btime f_loop!(D,C,A,B)

function f_alloc(A)
    B = A[1:5,1:5]
end

function f_point(A)
    B = @view A[1:5,1:5]
end

@btime f_alloc(A)
@btime f_point(A)

f_alloc(A)
B = f_alloc(A)

function basic()
    A = zeros(10,10)
    B = A .- 2
    C = A + B
    D = A - B

    return B, D
end

B, D = basic()

ff(x,y) = 2x + 2y
ff(x::Int,y::Int) = 2x + y
ff(x::Float64,y::Float64) = x/y

@show ff(2,5)
@show ff(2.0,5.0)

ff(2.0,5)

ff(x::Float64,y::Number) = 2x

ff(2.0,5)

ff(x::Float64,y::Number) = 5x + 2y
ff(x::Number,y::Int) = x - y
ff(2.0,5)

f(x,y) = x + y
x = Number[1.0,3]
function q(x)
  a = 4
  b = 2
  c = f(x[1],a)
  d = f(b,c)
  f(d,x[2])
  return nothing
end

using BenchmarkTools
@btime q(x)

x = [1.0,3.0]
@btime q(x)

x = Number[1.0,3]
function r(x)
  a = 4
  b = 2
  for i in 1:100
    c = f(x[1],a)
    d = f(b,c)
    a = f(d,x[2])
  end
  a
end
@btime r(x)

s(x) = _s(x[1],x[2])
function _s(x1,x2)
  a = 4
  b = 2
  for i in 1:100
    c = f(x1,a)
    d = f(b,c)
    a = f(d,x2)
  end
  a
end
@btime s(x)

typeof(x)
typeof([x[1],x[2]])

A = rand(100,100)
B = rand(100,100)
C = rand(100,100)
@btime for j in 1:100, i in 1:100
  global A,B,C
  C[i,j] = A[i,j] + B[i,j]
end

function f(A,B,C)
    for j in 1:100, i in 1:100
        C[i,j] = A[i,j] + B[i,j]
    end
end
@btime f(A,B,C)


using Multisets
using Test

A = Multiset(1:5)
@test Set(A) == A
B = A + A
@test length(B) == 2 * length(A)
@test A ⊆ B
@test B ⊇ A
B[1] = 0
@test Set(B) == Set(2:5)
@test length(A ∪ B) == 9
@test length(A ∩ B) == 4
@test length(A * B) == 40
@test length(A + B) == 13
@test length(A - B) == 1
@test length(B - A) == 4
@test collect(A) == collect(1:5)
@test hash(A) != hash(B)

@test Multiset(1, 2, 3, 1) == Multiset([1, 1, 2, 3])
@test eltype(Multiset(1, 2, 3)) == Int
@test Multiset(1.0, 2.0) == Multiset(1 + 0im, big(2))

A = Multiset(1, 1, 1, 2, 1, 3, 3)
@test sum(A) == 12
@test sort(collect(keys(A))) == [1, 2, 3]
@test sort(collect(values(A))) == [1, 2, 4]
@test sort(collect(pairs(A))) == [1 => 4, 2 => 1, 3 => 2]

B = Set(3:5)
@test A - B == Multiset(1, 1, 1, 1, 2, 3)
@test B - A == BitSet(4:5)

@test length(A + B) == length(A) + length(B) == length(B + A)
@test A ∩ B == Set(3) == B ∩ A
@test A ∪ B == Multiset(1, 1, 1, 1, 2, 3, 3, 4, 5) == B ∪ A

X = Set(A)

@test X ⊆ A
@test A ⊇ X
@test 1 ∈ A
@test 9 ∉ A

A = Multiset(1, 2, 3)
B = Multiset(1, 2, 3, 4)
B[4] = 0
@test A == B
@test hash(A) == hash(B)

set_key_value_show()
A = Multiset(1, 1, 1, 2, 1, 3, 3)

@test string(A) == "Multiset{Int64}(2 => 1, 3 => 2, 1 => 4)"

B = Multiset()

@test string(B) == "Multiset{Any}()"

A = Multiset(1:5)
B = copy(A)
@test A == B
empty!(A)
@test length(A) == 0
@test A != B

nothing


using CmdStan, Turing, ContinuousBenchmarks

# Model taken from https://github.com/goedman/Stan.jl/blob/master/Examples/Mamba/EightSchools/schools8.jl

include(joinpath(ContinuousBenchmarks.STAN_DATA_DIR, "school8-stan.data.jl"))
include(joinpath(ContinuousBenchmarks.STAN_MODELS_DIR, "school8-stan.model.jl"))

global stanmodel, rc, sim
# stanmodel = Stanmodel(name="schools8", model=eightschools);
stanmodel = Stanmodel(Sample(algorithm=CmdStan.Hmc(CmdStan.Static(0.75*5),CmdStan.diag_e(),0.75,0.0),
  save_warmup=true,adapt=CmdStan.Adapt(engaged=false)),
  num_samples=2000, num_warmup=0, thin=1,
  name="schools8", model=eightschools, nchains=1);

rc, sim = stan(stanmodel, schools8data, CmdStanDir=ContinuousBenchmarks.CMDSTAN_HOME, summary=false)

stan_d = Dict()

for i = 1:8
  stan_d["eta[$i]"] = sim[:, ["eta.$i"], :].value[:]
  stan_d["theta[$i]"] = sim[:, ["theta.$i"], :].value[:]
end

stan_d["mu"] = sim[:, ["mu"], :].value[:]
stan_d["tau"] =sim[:, ["tau"], :].value[:]

for k = keys(stan_d)
  stan_d[k] = mean(stan_d[k])
end

# println("CmdStan time: $stan_time")

# describe(sim)


include("utilities.jl")

using SEAL
using Printf

"""
    example_batch_encoder()

Example to demonstrate encoding and decoding vectors of integers with the BFV scheme.
This function is based on the file `native/examples/2_encoders.cpp` of the original
SEAL library and should yield the exact same output.

* [SEAL](https://github.com/microsoft/SEAL)
* [native/examples/2_encoders.cpp](https://github.com/microsoft/SEAL/blob/master/native/examples/2_encoders.cpp)

See also: [`example_ckks_encoder`](@ref), [`example_encoders`](@ref)
"""
function example_batch_encoder()
  print_example_banner("Example: Encoders / Batch Encoder")

  parms = EncryptionParameters(SchemeType.bfv)
  poly_modulus_degree = 8192
  set_poly_modulus_degree!(parms, poly_modulus_degree)
  set_coeff_modulus!(parms, coeff_modulus_bfv_default(poly_modulus_degree))

  set_plain_modulus!(parms, plain_modulus_batching(poly_modulus_degree, 20))
  context = SEALContext(parms)
  print_parameters(context)
  println()

  context_data = first_context_data(context)
  epq = qualifiers(context_data)
  println("Batching enabled: ", using_batching(epq))

  keygen = KeyGenerator(context)
  public_key_ = PublicKey()
  create_public_key!(public_key_, keygen)
  secret_key_ = secret_key(keygen)
  relin_keys_ = RelinKeys()
  create_relin_keys!(relin_keys_, keygen)
  encryptor = Encryptor(context, public_key_)
  evaluator = Evaluator(context)
  decryptor = Decryptor(context, secret_key_)

  batch_encoder = BatchEncoder(context)
  slot_count_ = slot_count(batch_encoder)
  row_size = div(slot_count_, 2)
  println("Plaintext matrix row size: ", row_size)

  pod_matrix = zeros(UInt64, slot_count_)
  pod_matrix[1] = 0
  pod_matrix[2] = 1
  pod_matrix[3] = 2
  pod_matrix[4] = 3
  pod_matrix[row_size + 1] = 4
  pod_matrix[row_size + 2] = 5
  pod_matrix[row_size + 3] = 6
  pod_matrix[row_size + 4] = 7

  println("Input plaintext matrix:")
  print_matrix(pod_matrix, row_size)

  plain_matrix = Plaintext()
  print_line(@__LINE__)
  println("Encode plaintext matrix:")
  encode!(plain_matrix, pod_matrix, batch_encoder)

  println("    + Decode plaintext matrix ...... Correct.")
  pod_result = similar(pod_matrix)
  decode!(pod_result, plain_matrix, batch_encoder)
  print_matrix(pod_result, row_size)

  encrypted_matrix = Ciphertext()
  print_line(@__LINE__)
  println("Encrypt plain_matrix to encrypted_matrix.")
  encrypt!(encrypted_matrix, plain_matrix, encryptor)
  println("    + Noise budget in encrypted_matrix: ",
          invariant_noise_budget(encrypted_matrix, decryptor),
          " bits")

  pod_matrix2 = ones(UInt64, slot_count_)
  pod_matrix2[2:2:slot_count_] .= 2
  plain_matrix2 = Plaintext()
  encode!(plain_matrix2, pod_matrix2, batch_encoder)
  println()
  println("Second input plaintext matrix:")
  print_matrix(pod_matrix2, row_size)

  print_line(@__LINE__)
  println("Sum, square, and relinearize.")
  add_plain_inplace!(encrypted_matrix, plain_matrix2, evaluator)
  square_inplace!(encrypted_matrix, evaluator)
  relinearize_inplace!(encrypted_matrix, relin_keys_, evaluator)
  println("    + Noise budget in result: ",
          invariant_noise_budget(encrypted_matrix, decryptor),
          " bits")

  plain_result = Plaintext()
  print_line(@__LINE__)
  println("Decrypt and decode result.")
  decrypt!(plain_result, encrypted_matrix, decryptor)
  decode!(pod_result, plain_result, batch_encoder)
  println("    + Result plaintext matrix ...... Correct.")
  print_matrix(pod_result, row_size)
end

"""
    example_ckks_encoder()

Example to demonstrate encoding and decoding vectors of floating point values with the CKKS scheme.
This function is based on the file `native/examples/2_encoders.cpp` of the original
SEAL library and should yield the exact same output.

* [SEAL](https://github.com/microsoft/SEAL)
* [native/examples/2_encoders.cpp](https://github.com/microsoft/SEAL/blob/master/native/examples/2_encoders.cpp)

See also: [`example_batch_encoder`](@ref), [`example_encoders`](@ref)
"""
function example_ckks_encoder()
  print_example_banner("Example: Encoders / CKKS Encoder")

  parms = EncryptionParameters(SchemeType.ckks)

  poly_modulus_degree = 8192
  set_poly_modulus_degree!(parms, poly_modulus_degree)
  set_coeff_modulus!(parms, coeff_modulus_create(poly_modulus_degree, [40, 40, 40, 40, 40]))

  context = SEALContext(parms)
  print_parameters(context)
  println()

  keygen = KeyGenerator(context)
  public_key_ = PublicKey()
  create_public_key!(public_key_, keygen)
  secret_key_ = secret_key(keygen)
  relin_keys_ = RelinKeys()
  create_relin_keys!(relin_keys_, keygen)

  encryptor = Encryptor(context, public_key_)
  evaluator = Evaluator(context)
  decryptor = Decryptor(context, secret_key_)

  encoder = CKKSEncoder(context)

  slot_count_ = slot_count(encoder)
  println("Number of slots: ", slot_count_)

  input = Float64[0.0, 1.1, 2.2, 3.3]
  println("Input vector: ")
  print_vector(input)

  plain = Plaintext()
  initial_scale = 2.0^30
  print_line(@__LINE__)
  println("Encode input vectors.")
  encode!(plain, input, initial_scale, encoder)

  output = Vector{Float64}(undef, slot_count_)
  println("    + Decode input vector ...... Correct.")
  decode!(output, plain, encoder)
  print_vector(output)

  encrypted = Ciphertext()
  print_line(@__LINE__)
  println("Encrypt input vector, square, and relinearize.")
  encrypt!(encrypted, plain, encryptor)

  square_inplace!(encrypted, evaluator)
  relinearize_inplace!(encrypted, relin_keys_, evaluator)

  println("    + Scale in squared input: ", scale(encrypted),
          " (", log2(scale(encrypted)), " bits)")

  print_line(@__LINE__)
  println("Decrypt and decode.")
  decrypt!(plain, encrypted, decryptor)
  decode!(output, plain, encoder)
  println("    + Result vector ...... Correct.")
  print_vector(output)

end

"""
    example_encoders()

Demonstrate how vectors of integers (BFV) or floating point values (CKKS) can be encoded/decoded
with the BFV and CKKS schemes.
This function is based on the file `native/examples/2_encoders.cpp` of the original
SEAL library and should yield the exact same output.

* [SEAL](https://github.com/microsoft/SEAL)
* [native/examples/2_encoders.cpp](https://github.com/microsoft/SEAL/blob/master/native/examples/2_encoders.cpp)

See also: [`example_batch_encoder`](@ref), [`example_ckks_encoder`](@ref)
"""
function example_encoders()
  print_example_banner("Example: Encoders")

  example_batch_encoder()
  example_ckks_encoder()
end