global test_suite_polyhedra

for N in [Float64, Rational{Int}, Float32]
    # random polytopes
    if test_suite_polyhedra
        rand(HPolytope)
    else
        @test_throws AssertionError rand(HPolytope)
    end
    rand(VPolytope)

    # -----
    # H-rep
    # -----

    # constructor from matrix and vector
    A = [N(1) N(2); N(-1) N(1)]
    b = [N(1), N(2)]
    p = HPolytope(A, b)
    c = p.constraints
    @test c isa Vector{LinearConstraint{N}}
    @test c[1].a == N[1, 2] && c[1].b == N(1)
    @test c[2].a == N[-1, 1] && c[2].b == N(2)
    @test_throws AssertionError HPolytope(N[1 0; 0 1], N[1, 1];
                                         check_boundedness=true)

    # convert back to matrix and vector
    A2, b2 = tosimplehrep(p)
    @test A == A2 && b == b2

    # 2D polytope
    p = HPolytope{N}()
    c1 = LinearConstraint(N[2, 2], N(12))
    c2 = LinearConstraint(N[-3, 3], N(6))
    c3 = LinearConstraint(N[-1, -1], N(0))
    c4 = LinearConstraint(N[2, -4], N(0))
    addconstraint!(p, c3)
    addconstraint!(p, c1)
    addconstraint!(p, c4)
    addconstraint!(p, c2)

    # support vector
    d = N[1, 0]
    @test σ(d, p) == N[4, 2]
    d = N[0, 1]
    @test σ(d, p) == N[2, 4]
    d = N[-1, 0]
    @test σ(d, p) == N[-1, 1]
    d = N[0, -1]
    @test σ(d, p) == N[0, 0]

    # support vector of polytope with no constraints
    @test_throws ErrorException σ(N[0], HPolytope{N}())

    # boundedness
    @test isbounded(p) && isbounded(p, false)
    p2 = HPolytope{N}()
    @test isbounded(p2) && !isbounded(p2, false)

    # membership
    @test N[5 / 4, 7 / 4] ∈ p
    @test N[4, 1] ∉ p

    # singleton list (only available with Polyhedra library)
    if test_suite_polyhedra
        @test length(singleton_list(p)) == 4
    else
        @test_throws AssertionError singleton_list(p)
    end

    if test_suite_polyhedra
        # conversion to and from Polyhedra's VRep data structure
        cl = constraints_list(HPolytope(polyhedron(p)))
        @test length(p.constraints) == length(cl)
    end

    # vertices_list of "universal polytope" (strictly speaking: illegal input)
    @test vertices_list(HPolytope{N}()) == Vector{Vector{N}}()

    if test_suite_polyhedra
        # convert hyperrectangle to a HPolytope
        H = Hyperrectangle(N[1, 1], N[2, 2])
        P = convert(HPolytope, H)
        vlist = vertices_list(P)
        @test ispermutation(vlist, [N[3, 3], N[3, -1], N[-1, -1], N[-1, 3]])
        # check boundedness after conversion
        HPolytope(constraints_list(H); check_boundedness=true)

        # isempty
        @test !isempty(p)
        @test !isempty(HPolytope{N}())

        # H-representaion of an empty v-polytope
        @test tohrep(VPolytope{N}()) == EmptySet{N}()
    end

    # remove redundant constraints
    P = HPolytope([HalfSpace(N[1, 0], N(1)),
                   HalfSpace(N[0, 1], N(1)),
                   HalfSpace(N[-1, -0], N(1)),
                   HalfSpace(N[-0, -1], N(1)),
                   HalfSpace(N[1, 0], N(2))]) # redundant

    Pred = remove_redundant_constraints(P)
    @test length(Pred.constraints) == 4
    @test length(P.constraints) == 5

    # test in-place removal of redundancies
    remove_redundant_constraints!(P)
    @test length(P.constraints) == 4

    # translation
    P2 = translate(P, N[1, 2])
    @test P2 isa HPolytope && ispermutation(constraints_list(P2),
        [HalfSpace(N[1, 0], N(2)), HalfSpace(N[0, 1], N(3)),
         HalfSpace(N[-1, -0], N(0)), HalfSpace(N[-0, -1], N(-1))])

    # subset
    H = BallInf(N[0, 0], N(1))
    P = convert(HPolytope, H)
    @test BallInf(N[0, 0], N(1)) ⊆ P
    @test !(BallInf(N[0, 0], N(1.01)) ⊆ P)

    # =====================
    # Concrete linear map
    # =====================
    LM = linear_map(N[2 3; 1 2], P) # invertible matrix
    @test LM isa HPolytope
    if test_suite_polyhedra
        LM = linear_map(N[2 3; 0 0], P)  # non-invertible matrix
        @test LM isa VPolygon
    end

    M = N[2 1; 0 1]
    L1 = linear_map(M, P, use_inv=true)  # calculates inv(M) explicitly
    L2 = linear_map(M, P, use_inv=false) # uses transpose(M) \ c.a for each constraint c of P
    L3 = linear_map(M, P, cond_tol=1e3)  # set a custom tolerance for the condition number (invertibility check)
    # needs M * an_element(Li) to be stable for rational, see #1105
    p = convert(Vector{N}, an_element(P))
    @assert p ∈ P
    @test all([M * p ∈ Li for Li in [L1, L2, L3]])

    # do not check for invertibility => use the vertices
    L4 = linear_map(M, P, check_invertibility=false)
    @test L4 isa VPolygon

    # linear map for mixed types
    M = [2 1; 0 1] # Int's
    LM = linear_map(M, P)
    @test LM isa HPolytope{N}

    # -----
    # V-rep
    # -----

    # constructor from a VPolygon
    polygon = VPolygon([N[0, 0], N[1, 0], N[0, 1]])
    p = VPolytope(polygon)
    @test vertices_list(polygon) == vertices_list(p)

    # dim
    @test dim(p) == 2

    # support vector
    d = N[1, 0]
    @test σ(d, p) == N[1, 0]

    # boundedness
    @test isbounded(p)

    # isempty
    @test !isempty(p)
    @test isempty(VPolytope{N}())

    # vertices_list function
    @test vertices_list(p) == p.vertices

    if test_suite_polyhedra
        V = VPolytope(polyhedron(p))

        # conversion to and from Polyhedra's VRep data structure
        vl = vertices_list(V)
        @test length(p.vertices) == length(vl) && vl ⊆ p.vertices

        # list of constraints of a VPolytope; calculates tohrep
        @test ispermutation(constraints_list(V), constraints_list(tohrep(V)))

        # convert empty VPolytope to a polyhedron
        Vempty = VPolytope()
        @test_throws ErrorException polyhedron(Vempty) # needs to pass the (relative) dim
        Pe = polyhedron(Vempty, relative_dimension=2)
    end

    # membership
    @test N[.49, .49] ∈ p
    @test N[.51, .51] ∉ p

    # translation
    @test translate(p, N[1, 2]) == VPolytope([N[1, 2], N[2, 2], N[1, 3]])

    # copy (see #1002)
    p, q = [N(1)], [N(2)]
    P = VPolytope([p, q])
    Pcopy = copy(P)
    p[1] = N(5)
    # test that Pcopy is independent of P ( = deepcopy)
    @test Pcopy.vertices[1] == [N(1)]
end

# default Float64 constructors
@test HPolytope() isa HPolytope{Float64}
@test VPolytope() isa VPolytope{Float64}

# Polyhedra tests that only work with Float64
if test_suite_polyhedra
    for N in [Float64]
        # -----
        # H-rep
        # -----
        # support function/vector
        d = N[1, 0]
        p_unbounded = HPolytope([LinearConstraint(N[-1, 0], N(0))])
        @test_throws ErrorException σ(d, p_unbounded)
        @test_throws ErrorException ρ(d, p_unbounded)
        p_infeasible = HPolytope([LinearConstraint(N[1], N(0)),
                                  LinearConstraint(N[-1], N(-1))])
        @test_throws ErrorException σ(N[1], p_infeasible)
        @test_throws ErrorException ρ(N[1], p_infeasible)

        # empty intersection
        A = [N(0) N(-1); N(-1) N(0); N(1) N(1)]
        b = N[0, 0, 1]
        p1 = HPolytope(A, b)
        A = [N(0) N(1); N(1) N(0); N(-1) N(-1)]
        b = N[2, 2, -2]
        p2 = HPolytope(A, b)
        @test intersection(p1, p2) isa EmptySet{N}
        @test intersection(p1, p2; backend=Polyhedra.default_library(2, N)) isa EmptySet{N}
        @test intersection(p1, p2; backend=CDDLib.Library()) isa EmptySet{N}

        # intersection with half-space
        hs = HalfSpace(N[2, -2], N(-1))
        c1 = constraints_list(intersection(p1, hs))
        c2 = constraints_list(intersection(hs, p1))
        @test length(c1) == 3 && ispermutation(c1, c2)

        # Cartesian product
        A = [N(1) N(-1)]'
        b = N[1, 0]
        p1 = HPolytope(A, b)
        p2 = copy(p1)
        cp = cartesian_product(p1, p2)
        cl = constraints_list(cp)
        @test length(cl) == 4

        # vertices_list
        vl = vertices_list(p1)
        @test ispermutation(vl, [N[0], N[1]])

        # checking for emptiness
        P = HPolytope([LinearConstraint(N[1, 0], N(0))])      # x <= 0
        @test !isempty(P)
        addconstraint!(P, LinearConstraint(N[-1, 0], N(-1)))  # x >= 1
        @test isempty(P)

        # checking for empty intersection (also test symmetric calls)
        P = convert(HPolytope, BallInf(zeros(N, 2), N(1)))
        Q = convert(HPolytope, BallInf(ones(N, 2), N(1)))
        R = convert(HPolytope, BallInf(3*ones(N, 2), N(1)))
        res, w = isdisjoint(P, Q, true)
        @test !isdisjoint(P, Q) && !res && w ∈ P && w ∈ Q
        @test !isdisjoint(P, Q; algorithm="sufficient")
        res, w = isdisjoint(Q, P, true)
        @test !isdisjoint(Q, P) && !res && w ∈ P && w ∈ Q
        res, w = isdisjoint(Q, R, true)
        @test !isdisjoint(Q, R) && !res && w ∈ Q && w ∈ R
        res, w = isdisjoint(R, Q, true)
        @test !isdisjoint(R, Q) && !res && w ∈ Q && w ∈ R
        res, w = isdisjoint(P, R, true)
        @test isdisjoint(P, R) && res && w == N[]
        res, w = isdisjoint(R, P, true)
        @test isdisjoint(R, P) && res && w == N[]
        res, w = isdisjoint(P, R, true; algorithm="sufficient")
        @test isdisjoint(P, R; algorithm="sufficient") && res && w == N[]

        # test that one can pass a sparse vector as the direction (see #1011)
        P = HPolytope([HalfSpace(N[1, 0], N(1)),
                       HalfSpace(N[0, 1], N(1)),
                       HalfSpace(N[-1, -1], N(-1))])
        @test an_element(P) ∈ P

        # tovrep from HPolytope
        A = N[0 1; 1 0; -1 -1]
        b = N[0.25, 0.25, -0]
        P = HPolytope(A, b)
        @test tovrep(P) isa VPolytope{N}
        @test tohrep(P) isa HPolytope{N}  # no-op

        # -----
        # V-rep
        # -----

        # intersection
        p1 = VPolytope(vertices_list(BallInf(N[0, 0], N(1))))
        p2 = VPolytope(vertices_list(BallInf(N[2, 2], N(1))))
        cap = intersection(p1, p2)
        @test vertices_list(cap) ≈ [N[1, 1]]
        # other polytopic sets
        p3 = VPolygon(vertices_list(p2))
        cap = intersection(p1, p3)
        @test vertices_list(cap) ≈ [N[1, 1]]
        p4 = BallInf(N[2, 2], N(1))
        cap = intersection(p1, p4)
        vlist = vertices_list(cap)  # contains duplicates (see #1405)
        @test all(v -> v == N[1, 1], vlist)

        # convex hull
        v1 = N[1, 0]
        v2 = N[0, 1]
        v3 = N[-1, 0]
        v4 = N[0, -1]
        v5 = N[0, 0]
        p1 = VPolytope([v1, v2, v5])
        p2 = VPolytope([v3, v4])
        ch = convex_hull(p1, p2)
        vl = vertices_list(ch)
        @test ispermutation(vl, [v1, v2, v3, v4])

        # Cartesian product
        p1 = VPolytope([N[0, 0], N[1, 1]])
        p2 = VPolytope([N[2]])
        cp = cartesian_product(p1, p2)
        vl = vertices_list(cp)
        @test ispermutation(vl, [N[0, 0, 2], N[1, 1, 2]])

        # tohrep from VPolytope
        P = VPolytope([v1, v2, v3, v4, v5])
        @test tohrep(P) isa HPolytope{N}
        @test tovrep(P) isa VPolytope{N}  # no-op

        # subset (see #974)
        H = BallInf(N[0, 0], N(1))
        P = convert(VPolytope, H)
        @test BallInf(N[0, 0], N(1)) ⊆ P
        @test !(BallInf(N[0, 0], N(1.01)) ⊆ P)

        # concrete minkowski sum
        B = convert(VPolytope, BallInf(N[0, 0, 0], N(1)))
        X = minkowski_sum(B, B)
        twoB = 2.0*B
        @test X ⊆ twoB && twoB ⊆ X

        # same but specifying a custom polyhedral computations backend (CDDLib)
        X = minkowski_sum(B, B, backend=CDDLib.Library())
        twoB = 2.0*B
        @test X ⊆ twoB && twoB ⊆ X

        P1 = VPolytope([N[0, 0, 0], N[0, 1, 0]])
        P2 = VPolytope([N[0, 0, 0], N[1, 0, 0]])
        Q = minkowski_sum(P1, P2)
        @test ispermutation(vertices_list(Q), [N[0, 0, 0], N[0, 1, 0], N[1, 0, 0], N[1, 1, 0]])

        # -----------------
        # mixed H-rep/V-rep
        # -----------------

        # intersection
        p1 = convert(HPolytope, BallInf(N[0, 0], N(1)))
        p2 = convert(VPolytope, BallInf(N[1, 1], N(1)))
        cap1 = intersection(p1, p2)
        cap2 = intersection(p2, p1)
        @test ispermutation([round.(v) for v in vertices_list(cap1)],
                            [N[1, 1], N[0, 1], N[1, 0], N[0, 0]])
        @test ispermutation([round.(v) for v in vertices_list(cap1)],
                            [round.(v) for v in vertices_list(cap2)])
    end
end


module Jules

# global variables
Fs = 5000; # sampling frequency
T = 1/Fs;
# functions

function gentime(trial, sampling_freq=Fs)
    T = 1/sampling_freq
    L = size(trial, 1)
    return t = collect(0:L-1)*T
end

function genrange(start, finish)
    return convert(Integer, floor(start)):convert(Integer, floor(finish))
end

function genrangeseconds(start_sec, finish_sec, sampling_freq=Fs)
    return genrange(start_sec*sampling_freq, finish_sec*sampling_freq)
end

function fftplot(X; sampling_freq=Fs)
    T = 1/sampling_freq
    L = size(X, 1)
    t = collect(0:L-1)*T

    Y = fft(X)
    P2 = abs(Y/L)
    P1 = P2[1:div(L,2)+1]
    P1[2:end - 1] = 2*P1[2:end-1]

    freq = sampling_freq*collect(0:(L/2))/L

    subplot(2,1,1)
    plot(t,X)
    title("Time domain")
    xlabel("time (s)")
    ylabel("Amplitude")
    subplots_adjust(hspace=.5)
    subplot(2,1,2)
    plot(freq,P1)
    title("Single-Sided Amplitude Spectrum")
    xlabel("f (Hz)")
    ylabel("|P1(f)|")
    return (P1,freq)
end

function subdc(trial)
    trial - mean(trial)
end

function discreteder(trial, sampling_freq=Fs)
    T_s = 1/Fs
    b = zeros(size(trial))

    for i in 1:length(trial)-1
        b[i] = (trial[i+1] - trial[i])/T_s
    end

    return b
end

function getstats(trial_column)
    stddev = std(trial_column)
    mn = mean(trial_column)
    println("min: ", minimum(trial_column))
    println("max: ", maximum(trial_column))
    println("average: ", mn)
    println("standard dev: ", stddev)
    ran = -3.5*stddev+mn : 0.01 : 3.5*stddev+mn
    #plot(ran, gaussian(ran, mn, stddev))
    #hold
    #plot(stddev+mn,0,"o")
    #plot(-stddev+mn,0,"o")
end

function gaussian(x, avg, std_dev)
    return 1/(std_dev * sqrt(2*pi))*exp(-.5*((x-avg)/std_dev).^2)
end

function smooth(arr, num_point=5)
    if num_point % 2 == 0
        println("num_point must be odd")
        return
    end

    b = zeros(size(arr))
    span = div(num_point, 2)
    start, finish = 1, length(arr)

    for i in 1:length(arr)
        n = 0

        inspan = i-span < start ? i-start : span
        inspan = i+span > finish ? finish-i : inspan

        #println("start ", start, " finish ", finish, " i ", i, " inspan ", inspan)
        for j in max(start, i-inspan):min(finish, i+inspan)
            #println("j", j, " arr ", arr[j])
            b[i] += arr[j]
            n+=1
        end

        b[i]/=n
    end
    return b
end

#LOW-PASS WINDOWED-SINC FILTER

function windowsinc(input_arr, fc, kernel_length::Integer)
    if kernel_length % 2 == 0
        println("num_point must be odd")
        #only applies to 1 indexed languages
        return
    end
    #Set the cutoff frequency (between 0 and 0.5 relative to sampling freq)
    #kernel_length = 4/BW

    input = vcat(zeros(kernel_length), input_arr)
    kernel = zeros(kernel_length) #holds the filter kernel
    output = zeros(length(input))

    #Calculate the low-pass filter kernel

    kernel_length_half = div(kernel_length+1, 2)
    for i in 1:kernel_length
            if (i-kernel_length_half) == 0
                    kernel[i] = 2*pi*fc
            else
                    kernel[i] = sin(2*pi*fc * (i-kernel_length_half)) / (i-kernel_length_half)
                    kernel[i] = kernel[i] * (0.54 - 0.46*cos(2*pi*i/kernel_length) )
            end
    end

    #Normalize the low-pass filter kernel for unity gain at DC
    kernel_sum = sum(kernel)
    kernel = kernel ./ kernel_sum

    #Convolve the input signal & filter kernel
    for j in kernel_length+1:length(input)
            for i in 1:kernel_length
                    output[j] += input[j-i] * kernel[i]
            end
    end
    return output[kernel_length+1:end]
end

function acceltogs(x)
    return x*2.0/32768
end

function windowsinchp(input_arr, fc, kernel_length::Integer)
    if kernel_length % 2 == 0
        println("num_point must be odd")
        #only applies to 1 indexed languages
        return
    end
    #Set the cutoff frequency (between 0 and 0.5 relative to sampling freq)
    #kernel_length = 4/BW

    input = vcat(zeros(kernel_length), input_arr)
    kernel = zeros(kernel_length) #holds the filter kernel
    output = zeros(length(input))

    #Calculate the low-pass filter kernel

    kernel_length_half = div(kernel_length+1, 2)
    for i in 1:kernel_length
            if (i-kernel_length_half) == 0
                    kernel[i] = 2*pi*fc
            else
                    kernel[i] = sin(2*pi*fc * (i-kernel_length_half)) / (i-kernel_length_half)
                    kernel[i] = kernel[i] * (0.54 - 0.46*cos(2*pi*i/kernel_length) )
            end
    end

    #Normalize the low-pass filter kernel for unity gain at DC
    kernel_sum = sum(kernel)
    kernel = kernel./kernel_sum

    kernel = kernel .* -1
    kernel[kernel_length_half] += 1

    #Convolve the input signal & filter kernel
    for j in kernel_length+1:length(input)
            for i in 1:kernel_length
                    output[j] += input[j-i] * kernel[i]
            end
    end
    return output[kernel_length+1:end]
end

function findpeaks(x, tolerance=0.0001)
    peaks = []
    counter::Integer = 1
    for i in 2:length(x)-1
        if abs(x[i] - x[i+1]) > tolerance && abs(x[i] - x[i-1]) > tolerance
            append!(peaks, [(x[i], i)])
            counter += 1
        end
    end
    return peaks
end

function decimate(x, num::Integer)
    return x[1:num:length(x)]
end

function genfreqindex(finish, sampling_freq = Fs)
    return 1:finish/sampling_freq
end

function getenergy(signal)
    return sum(signal.^2)
end

function nernst(extra, intra, z, T=298)
    R = 8.314472
    F = 9.64853399 * 10.0^4
    E = (R*T)/(z*F)log(extra/intra)
end

function idft(X)
    a = conj(X)
    b = fft(a)
    return real(b)
end

function RK4(u, du, t::Float64, dt::Float64)
    k1 = du(t, u)
    k2 = du(t + dt/2, u + dt/2*k1)
    k3 = du(t + dt/2, u + dt/2*k2)
    k4 = du(t + dt, u + dt*k3)
    dU = k1 + 2*k2 + 2*k2 + k4
    return U = u + dU*dt/6.0
end

function Int4(u::Vector{Float64}, du, t, dt)
    A = 18.0/11.0
    B = -9.0/11.0
    C = 2.0/11.0
    D = 6.0/11.0

    # U = A*u[n-1] + B*[n-2] + C*[n-3] + D*du*dt
    return U = A*u[end-1] + B*u[end-2] + C*u[end-3] + D*du(t, u[end])*dt
end

function adams_bashforth_moulton(u::Float64, prev_du::Vector{Float64}, du, t::Float64, dt::Float64)
        f_k = du(t, u)
        predictor =  u + dt/24 * (-9*prev_du[end-2] + 37*prev_du[end-1] - 59*prev_du[end] + 55*f_k)
        corrector = u + dt/24 * (prev_du[end-1] - 4*prev_du[end] + 19*f_k + 9*du(t+dt, predictor))
        return corrector
end

function integ(method)
end

function constrain(a::Float64, lower_bound::Float64, upper_bound::Float64)
    if a > upper_bound
        return upper_bound
    end

    if a < lower_bound
        return lower_bound
    end

    return a
end

function interp(x_basis::Vector{Float64}, y_basis::Vector{Float64}, t::Float64)
    #print("time $t\n")
    next_index = findfirst(x -> x > t, x_basis)
    if t >= x_basis[end]
        next_index = length(x_basis)
    end
    prev_index = next_index -1
    nearest_time = x_basis[prev_index]
    #=
    print("nearest time $nearest_time\n")
    =#
    avg_constant = t - x_basis[prev_index]

    #=
    print("avg_constant ")
    print(avg_constant)
    print("\n")

    print("1 -avg_constant ")
    print(1- avg_constant)
    print("\n")
    print("1 -avg_constant ")
    print(1- avg_constant)
    print("\n")
    =#
    out = (1 - avg_constant)*y_basis[prev_index] + avg_constant*y_basis[next_index]

    #=
    print("out ")
    print(out)
    print("\n\n")
    =#

    return out
end

end


#
# Multiplicative updating algorithm
#
# Reference:
#   Daniel D. Lee and H. Sebastian Seung. Algorithms for Non-negative
#   Matrix Factorization. Advances in NIPS, 2001.
#

mutable struct MultUpdate{T}
    obj::Symbol         # objective :mse or :div
    maxiter::Int        # maximum number of iterations
    verbose::Bool       # whether to show procedural information
    tol::T              # change tolerance upon convergence
    lambda::T           # regularization coefficient

    function MultUpdate{T}(;obj::Symbol=:mse,
                            maxiter::Integer=100,
                            verbose::Bool=false,
                            tol::Real=cbrt(eps(T)),
                            lambda::Real=sqrt(eps(T))) where T

        obj == :mse || obj == :div || throw(ArgumentError("Invalid value for obj."))
        maxiter > 1 || throw(ArgumentError("maxiter must be greater than 1."))
        tol > 0 || throw(ArgumentError("tol must be positive."))
        lambda >= 0 || throw(ArgumentError("lambda must be non-negative."))

        new{T}(obj, maxiter, verbose, tol, lambda)
    end
end

function solve!(alg::MultUpdate, X, W, H, nmfinit, nmfalg)

    if alg.obj == :mse
        nmf_skeleton!(MultUpdMSE(alg.lambda), X, W, H, alg.maxiter, alg.verbose, alg.tol, nmfinit, nmfalg)
    else # alg == :div
        nmf_skeleton!(MultUpdDiv(alg.lambda), X, W, H, alg.maxiter, alg.verbose, alg.tol, nmfinit, nmfalg)
    end
end

# the multiplicative updating algorithm for MSE objective

struct MultUpdMSE{T} <: NMFUpdater{T}
    lambda::T
end

struct MultUpdMSE_State{T}
    WH::Matrix{T}
    WtX::Matrix{T}
    WtWH::Matrix{T}
    XHt::Matrix{T}
    WHHt::Matrix{T}

    function MultUpdMSE_State{T}(X, W::Matrix{T}, H::Matrix{T}) where T
        p, n, k = nmf_checksize(X, W, H)
        new{T}(W * H,
               Matrix{T}(undef, k, n),
               Matrix{T}(undef, k, n),
               Matrix{T}(undef, p, k),
               Matrix{T}(undef, p, k))
    end
end

prepare_state(::MultUpdMSE{T}, X, W, H) where {T} = MultUpdMSE_State{T}(X, W, H)
evaluate_objv(::MultUpdMSE, s::MultUpdMSE_State, X, W, H) = sqL2dist(X, s.WH)

function update_wh!(upd::MultUpdMSE{T}, s::MultUpdMSE_State{T}, X, W::Matrix{T}, H::Matrix{T}) where T

    # fields
    lambda = upd.lambda
    WH = s.WH
    WtX = s.WtX
    WtWH = s.WtWH
    XHt = s.XHt
    WHHt = s.WHHt

    # update H
    Wt = transpose(W)
    mul!(WtX, Wt, X)
    mul!(WtWH, Wt, WH)

    @inbounds for i = 1:length(H)
        H[i] *= (WtX[i] / (WtWH[i] + lambda))
    end
    mul!(WH, W, H)

    # update W
    Ht = transpose(H)
    mul!(XHt, X, Ht)
    mul!(WHHt, WH, Ht)

    @inbounds for i = 1:length(W)
        W[i] *= (XHt[i] / (WHHt[i] + lambda))
    end
    mul!(WH, W, H)
end

# the multiplicative updating algorithm for divergence objective

struct MultUpdDiv{T} <: NMFUpdater{T}
    lambda::T
end

struct MultUpdDiv_State{T}
    WH::Matrix{T}
    sW::Matrix{T}     # sum(W, 1)
    sH::Matrix{T}     # sum(H, 2)
    Q::Matrix{T}      # X ./ (WH + lambda): size (p, n)
    WtQ::Matrix{T}    # W' * Q: size (k, n)
    QHt::Matrix{T}    # Q * H': size (p, k)

    function MultUpdDiv_State{T}(X, W::Matrix{T}, H::Matrix{T}) where T
        p, n, k = nmf_checksize(X, W, H)
        new{T}(W * H,
               Matrix{T}(undef, 1, k),
               Matrix{T}(undef, k, 1),
               Matrix{T}(undef, p, n),
               Matrix{T}(undef, k, n),
               Matrix{T}(undef, p, k))
    end
end

prepare_state(::MultUpdDiv{T}, X, W, H) where {T} = MultUpdDiv_State{T}(X, W, H)
evaluate_objv(::MultUpdDiv, s::MultUpdDiv_State, X, W, H) = gkldiv(X, s.WH)

function update_wh!(upd::MultUpdDiv{T}, s::MultUpdDiv_State{T}, X, W::Matrix{T}, H::Matrix{T}) where T

    p = size(X, 1)
    n = size(X, 2)
    k = size(W, 2)
    pn = p * n

    # fields
    lambda = upd.lambda
    sW = s.sW
    sH = s.sH
    WH = s.WH
    Q = s.Q
    WtQ = s.WtQ
    QHt = s.QHt

    @assert size(Q) == size(X)

    # update H
    @inbounds for i = 1:length(X)
        Q[i] = X[i] / (WH[i] + lambda)
    end
    mul!(WtQ, transpose(W), Q)
    sum!(fill!(sW, 0), W)
    @inbounds for j = 1:n, i = 1:k
        H[i,j] *= (WtQ[i,j] / sW[i])
    end
    mul!(WH, W, H)

    # update W
    @inbounds for i = 1:length(X)
        Q[i] = X[i] / (WH[i] + lambda)
    end
    mul!(QHt, Q, transpose(H))
    sum!(fill!(sH, 0), H)
    @inbounds for j = 1:k, i = 1:p
        W[i,j] *= (QHt[i,j] / sH[j])
    end
    mul!(WH, W, H)
end


# ---
# title: 53. Maximum Subarray
# id: problem53
# author: Tian Jun
# date: 2020-10-31
# difficulty: Easy
# categories: Array, Divide and Conquer, Dynamic Programming
# link: <https://leetcode.com/problems/maximum-subarray/description/>
# hidden: true
# ---
#
# Given an integer array `nums`, find the contiguous subarray (containing at
# least one number) which has the largest sum and return _its sum_.
#
# **Follow up:**  If you have figured out the `O(n)` solution, try coding
# another solution using the **divide and conquer** approach, which is more
# subtle.
#
#
#
# **Example 1:**
#
#
#
#     Input: nums = [-2,1,-3,4,-1,2,1,-5,4]
#     Output: 6
#     Explanation: [4,-1,2,1] has the largest sum = 6.
#
#
# **Example 2:**
#
#
#
#     Input: nums = [1]
#     Output: 1
#
#
# **Example 3:**
#
#
#
#     Input: nums = [0]
#     Output: 0
#
#
# **Example 4:**
#
#
#
#     Input: nums = [-1]
#     Output: -1
#
#
# **Example 5:**
#
#
#
#     Input: nums = [-2147483647]
#     Output: -2147483647
#
#
#
#
# **Constraints:**
#
#   * `1 <= nums.length <= 2 * 104`
#   * `-231 <= nums[i] <= 231 - 1`
#
#
## @lc code=start
using LeetCode

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


using CompositeProblems
using StructuredProximalOperators
using Random
using LinearAlgebra
using BenchmarkTools
using ForwardDiff

function check_problem_smoothoracles(pb, x, η)

    f_x = f(pb, x)
    ∇f_x = ∇f(pb, x)
    ∇²f_x_η = ∇²f_h(pb, x, η)

    ηgradfx = dot(η, ∇f_x)
    ηHessf_xη = dot(η, ∇²f_x_η)

    @show ηgradfx, ηHessf_xη

    fgrad(t) = abs(f(pb, x + t*η) - (f_x + t * ηgradfx))
    fhess(t) = abs(f(pb, x + t*η) - (f_x + t * ηgradfx + 0.5 * t^2 * ηHessf_xη))

    return compare_curves(fgrad, fhess)
end

function main()
    println("Least square test")
    n, m = 20, 15
    pb = get_random_qualifiedlasso(n, m, 0.5)

    Random.seed!(1234)
    x = rand(n)
    Random.seed!(4321)
    η = rand(n)

    res = check_problem_smoothoracles(pb, x, η)

    println("\nLogistic test")
    n, m = 200, 150
    pb = get_random_logit(n=n, m=m, sparsity=0.5)

    Random.seed!(1234)
    x = rand(n)
    Random.seed!(4321)
    η = rand(n)

    @show f(pb, x)
    @btime f($pb, $x)

    println()
    println()

    ∇fx_AD = ForwardDiff.gradient(x->f(pb, x), x)
    ∇fx_∇f = zeros(n); CompositeProblems.∇f!(pb, ∇fx_∇f, x)

    @show norm(∇fx_AD), norm(∇fx_∇f)
    @show norm(∇fx_AD - ∇fx_∇f)

    @btime CompositeProblems.∇f!($pb, $∇fx_∇f, $x)

    println()
    println()
    ∇²f_h_base = zeros(n)
    @btime CompositeProblems.∇²f_h!($pb, $∇²f_h_base, $x, $η)

    ∇²f_h_ADhess = ForwardDiff.hessian(x->f(pb, x), x) * η

    CompositeProblems.∇²f_h!(pb, ∇²f_h_base, x, η)

    @show norm(∇²f_h_ADhess)
    @show norm(∇²f_h_base)
    @show norm(∇²f_h_ADhess - ∇²f_h_base)

    @btime CompositeProblems.∇²f_h!($pb, $∇²f_h_base, $x, $η)

    # @assert false
    @btime CompositeProblems.∇²f_h!($pb, $∇²f_h_base, $x, $η)

    res = check_problem_smoothoracles(pb, x, η)
    return display_curvescomparison(res)
end

main()


"""
Parallel Tempering
"""
module ParTemp

"""
Compute the log probability of swapping chains of different temperatures.

# Example

lpdf_normal(z) = -(log(2*pi) + z*z) / 2
loglike(s) = sum(lpdf_normal.(s))
states = (randn(100), randn(100))  # needs to be the same type
temps = (1.0, 2.0)  # each has to be greater than 1
compute_log_accept_ratio(loglike, states, temps)
compute_accept_ratio(loglike, states, temps)
"""
function compute_log_accept_ratio(loglike::Function, states, temps)
  s1, s2 = states
  t1, t2 = temps
  return min((1/t1 - 1/t2) * (loglike(s2) - loglike(s1)), 0)
end

"""
Compute the probability of swapping chains of different temperatures.

See doc for compute_log_accept_ratio
"""
function compute_accept_ratio(loglike::Function, states, temps)
  return exp(compute_log_accept_ratio(loglike, states, temps))
end

end  # ParTemp


export iauTpxes
"""
In the tangent plane projection, given celestial spherical
coordinates for a star and the tangent point, solve for the star's
rectangular coordinates in the tangent plane.

This function is part of the International Astronomical Union's
SOFA (Standards of Fundamental Astronomy) software collection.

Status:  support function.

Given:
   a,b       double  star's spherical coordinates
   a0,b0     double  tangent point's spherical coordinates

Returned:
   *xi,*eta  double  rectangular coordinates of star image (Note 2)

Returned (function value):
            int     status:  0 = OK
                              1 = star too far from axis
                              2 = antistar on tangent plane
                              3 = antistar too far from axis

Notes:

   1. The tangent plane projection is also called the "gnomonic
   projection" and the "central projection".

   2. The eta axis points due north in the adopted coordinate system.
   If the spherical coordinates are observed (RA,Dec), the tangent
   plane coordinates (xi,eta) are conventionally called the
   "standard coordinates".  For right-handed spherical coordinates,
   (xi,eta) are also right-handed.  The units of (xi,eta) are,
   effectively, radians at the tangent point.

   3. All angular arguments are in radians.

   4. This function is a member of the following set:

      spherical      vector         solve for

      > iauTpxes <    iauTpxev         xi,eta
      iauTpsts      iauTpstv          star
      iauTpors      iauTporv         origin

References:

   Calabretta M.R. & Greisen, E.W., 2002, "Representations of
   celestial coordinates in FITS", Astron.Astrophys. 395, 1077

   Green, R.M., "Spherical Astronomy", Cambridge University Press,
   1987, Chapter 13.

This revision:   2018 January 2

SOFA release 2018-01-30

Copyright (C) 2018 IAU SOFA Board.  See notes at end.
"""

# int iauTpxes(double a, double b, double a0, double b0,
#              double *xi, double *eta)

function iauTpxes(a::Real, b::Real,
                  a0::Real, b0::Real)
   ref_xi = Ref{Float64}(0.0)
   ref_eta = Ref{Float64}(0.0)

   status = ccall((:iauTpxes, libsofa_c), Cint,
       (Cdouble, Cdouble, Cdouble, Cdouble,
       Ref{Cdouble}, Ref{Cdouble}),
       convert(Float64, a),
       convert(Float64, b),
       convert(Float64, a0),
       convert(Float64, b0),
       ref_xi, ref_eta)

   return status, ref_xi[], ref_eta[]
end


function make_complete!(graph, edges)
    for edge in combinations(edges, 2)
        add_edge!(graph, edge...)
    end
end

function buildmeta(g::AbstractGraph)
    mg = MetaGraph(g)
    for v in vertices(mg)
        set_indexing_prop!(mg, v, :v, v)
    end
    return mg
end

function build_graph_from_clique(clique::AbstractVector)
    nb_vertex = maximum([maximum(c) for c in clique])
    g = SimpleGraph(nb_vertex)
    for clique in clique
        make_complete!(g, clique)
    end
    return g
end

function build_cliquetree(clique::Vector{Vector{Int}})
    graph = SimpleGraph(length(clique))
    dstmx = zeros(Int, length(clique), length(clique))
    for i in 1:length(clique) - 1
        for j in i + 1:length(clique)
            value = length(intersect(clique[i], clique[j]))
            dstmx[i, j] = value
            dstmx[j, i] = value
            if value >= 1
                add_edge!(graph, i, j)
            end
        end
    end
    cliquetree = kruskal_mst(graph, dstmx; minimize=false)
    return cliquetree, dstmx
end

function get_cliquetree_array(cliquetree::Vector{Graphs.SimpleEdge{T}}, dstmx) where T
    cliquetree = [[edge.src, edge.dst]::Vector{Int} for edge in cliquetree]
    nb_lc = sum([dstmx[src, dst] * (2 * dstmx[src, dst] + 1) for (src, dst) in cliquetree])
    return cliquetree, nb_lc
end

function get_cliquetree_array(clique::Vector{Vector{Int}})
    cliquetree, dstmx = build_cliquetree(clique)
    return get_cliquetree_array(cliquetree, dstmx)
end


import CLBLAS
using OpenCL: cl
const clblas = CLBLAS
clblas.setup()

device, ctx, queue = clblas.get_next_compute_context()

N = Unsigned(5)
data = Vector{Float32}(5)
data[1] = 1.1
data[2] = 2.22
data[3] = 3.333
data[4] = 4.4444
data[5] = 5.55555

dat = Vector{Float32}(5)
dat[1] = 6.1
dat[2] = 7.22
dat[3] = 8.333
dat[4] = 9.4444
dat[5] = 10.55555

bufX = cl.Buffer(Float32, ctx, (:r, :copy),hostbuf=data)
bufY = cl.Buffer(Float32, ctx, (:r, :copy),hostbuf=dat)

bufDotP = cl.Buffer(Float32, ctx, :w, 1)
scratchBuff = cl.Buffer(Float32, ctx, :rw, length(data))

offDP = offy = offx = Unsigned(0)
incx = cl.cl_int(1)
incy = cl.cl_int(1)
ncq = cl.cl_uint(1)
clq = queue.id
newl = cl.cl_uint(0)

event = cl.UserEvent(ctx, retain=true)
ptrEvent = [event.id]

clblas.clblasSdot( N, bufDotP.id, offDP, bufX.id, offx, incx, bufY.id, offy, incy, scratchBuff.id, ncq, [clq], newl, C_NULL, ptrEvent);

cl.api.clWaitForEvents(cl.cl_uint(1), ptrEvent)

result = Vector{Float32}(length(bufDotP))
cl.enqueue_read_buffer(queue, bufDotP, result, Unsigned(0), nothing, true)

expected = dot(data, dat)
println(result)
println("------------")
println(expected)

try
    # locally passes without atol
    @assert(isapprox(norm(expected - result[1]), zero(Float32), atol=0.00002))
    info("success!")
finally
    clblas.teardown()
end


# implementation of the n-dimensional cubature rule (n ≥ 2) from
# A. C. Genz and A. A. Malik, "An adaptive algorithm for numeric
# integration over an N-dimensional rectangular region," J. Comput. Appl. Math.,
# vol. 6 (no. 4), 295-302 (1980).

"""
    combos(k, λ, Val{n}())

Return an array of SVector{n} of all n-component vectors
with k components equal to λ and other components equal to zero.
"""
function combos(k::Integer, λ::T, ::Val{n}) where {n, T<:Number}
    combos = Combinatorics.combinations(1:n, k)
    p = Vector{SVector{n,T}}(undef, length(combos))
    v = MVector{n,T}(undef)
    for (i,c) in enumerate(combos)
        v .= 0
        v[c] .= λ
        p[i] = v
    end
    return p
end

"""
    signcombos(k, λ, Val{n}())

Return an array of SVector{n} of all n-component vectors
with k components equal to ±λ and other components equal to zero
(with all possible signs).
"""
function signcombos(k::Integer, λ::T, ::Val{n}) where {n, T<:Number}
    combos = Combinatorics.combinations(1:n, k)
    twoᵏ = 1 << k
    p = Vector{SVector{n,T}}(undef, length(combos) * twoᵏ)
    v = MVector{n,T}(undef)
    for (i,c) in enumerate(combos)
        j = (i-1)*twoᵏ + 1
        v .= 0
        v[c] .= λ
        p[j] = v
        # use a gray code to flip one sign at a time
        graycode = 0
        for s = 1:twoᵏ-1
            graycode′ = s ⊻ (s >> 1)
            graycomp = c[trailing_zeros(graycode ⊻ graycode′) + 1]
            graycode = graycode′
            v[graycomp] = -v[graycomp]
            p[j+s] = v
        end
    end
    return p
end

"""
`GenzMalik{n,T}` holds the points and weights corresponding
to an `n`-dimensional Genz-Malik cubature rule over coordinates
of type `T`.
"""
struct GenzMalik{n,T<:AbstractFloat}
    p::NTuple{4,Vector{SVector{n,T}}} # points for the last 4 G-M weights
    w::NTuple{5,T}  # weights for the 5 terms in the G-M rule
    w′::NTuple{4,T} # weights for the embedded lower-degree rule
end

# cache the Genz-Malik rules so that we don't reconstruct them every time;
# this mainly matters for simple integrands (low-degree polynomials) that
# don't require refinement.
const gmcache = Dict{Tuple{Int,Type}, GenzMalik}()

"""
    GenzMalik(Val{n}(), T=Float64)

Construct an n-dimensional Genz-Malik rule for coordinates of type `T`.
"""
function GenzMalik(v::Val{n}, ::Type{T}=Float64) where {n, T<:AbstractFloat}
    haskey(gmcache, (n,T)) && return gmcache[n,T]::GenzMalik{n,T}

    n < 2 && throw(ArgumentError("invalid dimension $n: GenzMalik rule requires dimension > 2"))

    λ₄ = sqrt(9/T(10))
    λ₂ = sqrt(9/T(70))
    λ₃ = λ₄
    λ₅ = sqrt(9/T(19))

    twoⁿ = 1 << n
    w₁ = twoⁿ * ((12824 - 9120n + 400n^2) / T(19683))
    w₂ = twoⁿ * (980 / T(6561))
    w₃ = twoⁿ * ((1820 - 400n) / T(19683))
    w₄ = twoⁿ * (200 / T(19683))
    w₅ = 6859/T(19683)
    w₄′ = twoⁿ * (25/T(729))
    w₃′ = twoⁿ * ((265 - 100n)/T(1458))
    w₂′ = twoⁿ * (245/T(486))
    w₁′ = twoⁿ * ((729 - 950n + 50n^2)/T(729))

    p₂ = combos(1, λ₂, v)
    p₃ = combos(1, λ₃, v)
    p₄ = signcombos(2, λ₄, v)
    p₅ = signcombos(n, λ₅, v)

    g = GenzMalik{n,T}((p₂,p₃,p₄,p₅), (w₁,w₂,w₃,w₄,w₅), (w₁′,w₂′,w₃′,w₄′))
    gmcache[n,T] = g
    return g
end

countevals(g::GenzMalik{n}) where {n} = 1 + 4n + 2*n*(n-1) + (1<<n)

"""
    genzmalik(f, a, b, norm=vecnorm)

Evaluate `genzmalik::GenzMalik` for the box with min/max corners `a` and `b`
for an integrand `f`.  Returns the estimated integral `I`, the estimated
error `E` (via the given `norm`), and the suggested coordinate `k` ∈ `1:n`
to subdivide next.
"""
function (g::GenzMalik{n,T})(f, a::SVector{n}, b::SVector{n}, norm=vecnorm) where {n,T}
    c = T(0.5).*(a.+b)
    Δ = T(0.5).*abs.(b.-a)
    V = prod(Δ)

    f₁ = f(c)

    f₂ = zero(f₁)
    f₃ = zero(f₁)
    twelvef₁ = 12f₁
    maxdivdiff = zero(norm(f₁))
    divdiff = MVector{n,typeof(maxdivdiff)}(undef)
    for i = 1:n
        p₂ = Δ .* g.p[1][i]
        f₂ᵢ = f(c + p₂) + f(c - p₂)
        p₃ = Δ .* g.p[2][i]
        f₃ᵢ = f(c + p₃) + f(c - p₃)
        f₂ += f₂ᵢ
        f₃ += f₃ᵢ
        # fourth divided difference: f₃ᵢ-2f₁ - 7*(f₂ᵢ-2f₁),
        # where 7 = (λ₃/λ₂)^2 [see van Dooren and de Ridder]
        divdiff[i] = norm(f₃ᵢ + twelvef₁ - 7*f₂ᵢ)
    end

    f₄ = zero(f₁)
    for p in g.p[3]
        f₄ += f(c .+ Δ .* p)
    end

    f₅ = zero(f₁)
    for p in g.p[4]
        f₅ += f(c .+ Δ .* p)
    end

    I = V * (g.w[1]*f₁ + g.w[2]*f₂ + g.w[3]*f₃ + g.w[4]*f₄ + g.w[5]*f₅)
    I′ = V * (g.w′[1]*f₁ + g.w′[2]*f₂ + g.w′[3]*f₃ + g.w′[4]*f₄)
    E = norm(I - I′)

    # choose axis
    kdivide = 1
    δf = E / (10^n * V)
    for i = 1:n
        if (δ = divdiff[i] - maxdivdiff) > δf
            kdivide = i
            maxdivdiff = divdiff[i]
        elseif abs(δ) < δf && Δ[i] > Δ[kdivide]
            kdivide = i
        end
    end

    return I, E, kdivide
end


"""
    kde(x,w)
Weighted kernel density estimate of the data `x` ∈ ℜN with weights `w` ∈ ℜN
`xi, densityw, density = kde(x,w)`
The number of grid points is chosen automatically and will approximately be equal to N/3
The bandwidth of the Gaussian kernel is chosen based on Silverman's rule of thumb
returns both weighted and non-weighted densities `densityw, density`
"""
function kde(x,w::AbstractVector)
    @assert all(x->x>0, w) "All weights must be non-negative"
    e   = StatsBase.histrange(x,ceil(Int,length(x)/3))
    nb  = length(e)-1
    np  = length(x)
    I   = sortperm(x)
    s   = (rand()/nb+0):1/nb:e[end]
    j   = zeros(Float64,nb)
    bo = 1
    ilast = 0
    for i = 1:np
        ii = I[i]
        for b = bo:nb
            if x[ii] <= e[b+1]
                j[b] += w[ii]
                bo = b
                ilast = i
                break
            end
        end
    end
    j[end] += sum(w[I[ilast+1:np]])
    @assert all(j .>= 0) "j"
    xi      = e[1:end-1] .+ 0.5*(e[2]-e[1])
    σ       = std(x)
    if σ == 0 # All mass on one point
        return x,ones(x),ones(x)
    end
    h        = 1.06σ*np^(-1/5)
    K(x)     = exp(-(x/h).^2/2) / √(2π)
    densityw = [1/h*sum(j.*K.(xi.-xi[i])) for i=1:nb] # This density is effectively normalized by nb due to sum(j) = 1
    density  = [1/(np*h)*sum(K.(x.-xi[i])) for i=1:nb]

    # @assert all(densityw .>= 0) "densityw"
    # @assert all(density  .>= 0) "density"

    return xi, densityw, density

end

@userplot DensityPlot
@recipe function f(dp::DensityPlot)
    x = dp.args[1]
    w = length(dp.args) > 1 ? dp.args[2] : ones(x)/length(x)
    xi, densityw, density = kde(x,w)
    if maximum(x)-minimum(x) > 0
        title --> "Kernel density estimate"
        seriestype := :path
        if length(dp.args) > 1
            @series begin
                label --> "Weighted density"
                linecolor --> :blue
                xi,densityw
            end
        end
        @series begin
            label --> "Non-weighted density"
            linecolor --> :red
            xi,density
        end
    end
end

"""
    densityplot(x,[w])
Plot (weighted) particles densities
"""
densityplot

@userplot HeatboxPlot
@recipe function f(p::HeatboxPlot; nbinsy=30)
    x,t = p.args[1:2]
    seriestype := :histogram2d
    bins --> (1,nbinsy)
    if length(p.args) >= 3
        w = p.args[3]
        valid = (w .!= -Inf) .& (w .!= 0)
        weights --> w[valid]
        x = x[valid]
    end
    fill(t, length(x)), x
end

@userplot TrajectoryDensity
@recipe function f(p::TrajectoryDensity; nbinsy=30, xreal=nothing)
    pf,x,w,u,y = p.args[1:5]
    N,T = size(x)
    D = length(x[1])
    P = length(y[1])
    if sum(w) ≉ T
        w = exp.(w)
        w ./= sum(w, dims=1)
    end

    w = vec(w)
    vx = vec(x)
    background_color --> :black

    layout := D+P
    label := ""
    markercolor --> :cyan
    title --> reshape([["State $d" for d = 1:D];["Measurement $d" for d = 1:P]], 1, :)
    for d = 1:D
        subplot := d
        @series begin
            seriestype := :histogram2d
            bins --> (0.5:1:T+0.5,nbinsy)
            weights --> w
            repeat((1:T)' .-0.5,N)[:], vec(getindex.(vx,d))
        end
        xreal === nothing || @series begin
            seriestype := :scatter
            1:T, getindex.(xreal,d)
        end
    end
    yhat = measurement(pf).(x,u',0) |> vec
    for d = 1:P
        subplot := d+D
        @series begin
            seriestype := :histogram2d
            bins --> (0.5:1:T+0.5,nbinsy)
            weights --> w
            repeat((1:T)' .-0.5,N)[:], vec(getindex.(yhat,d))
        end
        @series begin
            seriestype := :scatter
            1:T, getindex.(y,d)
        end
    end
end

@userplot DimensionDensity
@recipe function f(p::DimensionDensity; nbinsy=30, xreal=nothing)
    length(p.args) >=5 || throw(ArgumentError("Supply arguments: pf,x,w,y,dimension::Int"))
    pf,x,w,u,y,d = p.args[1:6]
    N,T = size(x)
    D = length(x[1])
    P = length(y[1])

    if sum(w) ≉ T
        w = exp.(w)
        w ./= sum(w, dims=1)
    end

    label := ""
    markercolor --> :cyan
    if d <= D
        @series begin
            seriestype := :histogram2d
            bins --> (0.5:1:T+0.5,nbinsy)
            weights --> vec(w)
            repeat((1:T)' .-0.5,N)[:], vec(getindex.(x,d))
        end
        xreal === nothing || @series begin
            seriestype := :scatter
            1:T, getindex.(xreal,d)
        end
    else
        d -= D
        yhat = measurement(pf).(x,u',0) |> vec
        @series begin
            seriestype := :histogram2d
            bins --> (0.5:1:T+0.5,nbinsy)
            weights --> vec(w)
            repeat((1:T)' .-0.5,N)[:], vec(getindex.(yhat,d))
        end
        @series begin
            seriestype := :scatter
            1:T, getindex.(y,d)
        end

    end

end

"""
    dimensiondensity(pf,x,we,y, dimension, nbinsy=30, xreal=nothing)
Same as trajectorydensity but only plots subplot `dimension`.
"""
dimensiondensity

"""
    trajectorydensity(pf,x,we,y, nbinsy=30, xreal=nothing)
Plots particle densities along trajectory. See the readme for an example.
"""
trajectorydensity


# Copyright 2017 Raytheon BBN Technologies
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the license at
#
#		http://www.apache.org/licenses/LICENSE-2.0
using Test

function in_range(x, minx, maxx)
  return (x >= minx) && (x <= maxx)
end

function check_fit_result(true_values::Dict{String, Float64}, result::Qlab.FitResult)
    @test Set(keys(true_values)) == Set(keys(result.fit_params))
    @test Set(keys(true_values)) == Set(keys(result.errors))

    for k in keys(true_values)
        println("$(k): $(result.fit_params[k]) => $(true_values[k]) ± $(result.errors[k])")
        @test in_range(result.fit_params[k], true_values[k]-10*result.errors[k], true_values[k]+10*result.errors[k])
    end
end

#y = a exp(-t/T₁) + b
function test_T1_fit()
    #generate some fake data
    vals = Dict("a" => 0.87, "T" => 20., "b" => 0.1)
    t = collect(range(0., stop=100., length=201))
    y = vals["a"] * exp.(-t/vals["T"]) .+ vals["b"] .+ 0.01 * randn((201,))
    result = Qlab.fit_t1(t, y)
    check_fit_result(vals, result)
end

#y = ax + b.
function test_line_fit()
    vals = Dict("a" => -0.4, "b" => 2.1)
    x = range(-4, stop=4, length=201)
    y = vals["a"] * x .+ vals["b"] .+ 0.05 * randn((201,))
    result = Qlab.fit_line(x,y)
    check_fit_result(vals, result)
end

#a*exp(-t ./ T).*cos(2πf .* t + ϕ) + b
function test_ramsey_fit()
    p = Dict("a"=>0.8, "T"=>12.3, "f"=>0.55, "ϕ"=>π/12, "b"=>-0.15)
    t = range(0, stop=25, length=201)
    y = p["a"]*exp.(-t ./ p["T"]).*cos.(2π*p["f"] .* t .+ p["ϕ"]) .+ p["b"] + 0.01 * randn((201,))
    result = Qlab.fit_ramsey(t,y)
    check_fit_result(p, result)
end

test_T1_fit()
test_line_fit()
test_ramsey_fit()


#!/usr/bin/env julia

export pe10

# need Primes.jl
# https://github.com/JuliaMath/Primes.jl
using Primes

pe10(n::Integer=2*10^6)=primes(n)|>sum

if !haskey(ENV,"PROJECT_EULER_WITHOUT_RESULT")
    pe10()|>print
end


using ModelingToolkit, OrdinaryDiffEq, Test, NonlinearSolve
using ModelingToolkit: topsort_equations

@variables t x(t) y(t) z(t) k(t)
eqs = [
       x ~ y + z
       z ~ 2
       y ~ 2z + k
      ]

sorted_eq = topsort_equations(eqs, [x, y, z, k])

ref_eq = [
          z ~ 2
          y ~ 2z + k
          x ~ y + z
         ]
@test ref_eq == sorted_eq

@test_throws ArgumentError topsort_equations([
                                             x ~ y + z
                                             z ~ 2
                                             y ~ 2z + x
                                            ], [x, y, z, k])

@parameters t σ ρ β
@variables x(t) y(t) z(t) a(t) u(t) F(t)
D = Differential(t)

test_equal(a, b) = @test isequal(simplify(a, polynorm=true), simplify(b, polynorm=true))

eqs = [
       D(x) ~ σ*(y-x)
       D(y) ~ x*(ρ-z)-y + β
       0 ~ z - x + y
       0 ~ a + z
       u ~ z + a
      ]

lorenz1 = ODESystem(eqs,t,name=:lorenz1)

lorenz1_aliased = structural_simplify(lorenz1)
io = IOBuffer(); show(io, MIME("text/plain"), lorenz1_aliased); str = String(take!(io))
@test all(s->occursin(s, str), ["lorenz1", "States (2)", "Parameters (3)"])
reduced_eqs = [
               D(x) ~ σ*(y - x)
               D(y) ~ β + x*(ρ - (x - y)) - y
              ]
test_equal.(equations(lorenz1_aliased), reduced_eqs)
@test isempty(setdiff(states(lorenz1_aliased), [x, y, z]))
test_equal.(observed(lorenz1_aliased), [
                                        u ~ 0
                                        z ~ x - y
                                        a ~ -z
                                       ])

# Multi-System Reduction

@variables s(t)
eqs1 = [
        D(x) ~ σ*(y-x) + F,
        D(y) ~ x*(ρ-z)-u,
        D(z) ~ x*y - β*z,
        u ~ x + y - z,
       ]

lorenz = name -> ODESystem(eqs1,t,name=name)
lorenz1 = lorenz(:lorenz1)
ss = ModelingToolkit.get_structure(initialize_system_structure(lorenz1))
@test isequal(ss.fullvars, [x, y, z, D(x), D(y), D(z), F, u])
lorenz2 = lorenz(:lorenz2)

connected = ODESystem([s ~ a + lorenz1.x
                       lorenz2.y ~ s
                       lorenz1.F ~ lorenz2.u
                       lorenz2.F ~ lorenz1.u],t,systems=[lorenz1,lorenz2])
@test length(Base.propertynames(connected)) == 10
@test isequal((@nonamespace connected.lorenz1.x), x)

# Reduced Flattened System

reduced_system = structural_simplify(connected)

@test setdiff(states(reduced_system), [
        s
        a
        lorenz1.x
        lorenz1.y
        lorenz1.z
        lorenz1.u
        lorenz2.x
        lorenz2.y
        lorenz2.z
        lorenz2.u
       ]) |> isempty

@test setdiff(parameters(reduced_system), [
        lorenz1.σ
        lorenz1.ρ
        lorenz1.β
        lorenz2.σ
        lorenz2.ρ
        lorenz2.β
       ]) |> isempty

reduced_eqs = [
               D(lorenz1.x) ~ lorenz1.σ*((lorenz1.y) - (lorenz1.x)) - ((lorenz2.z) - (lorenz2.x) - (lorenz2.y))
               D(lorenz1.y) ~ lorenz1.z + lorenz1.x*(lorenz1.ρ - (lorenz1.z)) - (lorenz1.x) - (lorenz1.y)
               D(lorenz1.z) ~ lorenz1.x*lorenz1.y - (lorenz1.β*(lorenz1.z))
               D(lorenz2.x) ~ lorenz2.σ*((lorenz2.y) - (lorenz2.x)) - ((lorenz1.z) - (lorenz1.x) - (lorenz1.y))
               D(lorenz2.y) ~ lorenz2.z + lorenz2.x*(lorenz2.ρ - (lorenz2.z)) - (lorenz2.x) - (lorenz2.y)
               D(lorenz2.z) ~ lorenz2.x*lorenz2.y - (lorenz2.β*(lorenz2.z))
              ]

test_equal.(equations(reduced_system), reduced_eqs)

observed_eqs = [
                s ~ lorenz2.y
                lorenz2.u ~ -((lorenz2.z) - (lorenz2.x) - (lorenz2.y))
                lorenz1.u ~ -((lorenz1.z) - (lorenz1.x) - (lorenz1.y))
                a ~ s - (lorenz1.x)
                lorenz1.F ~ lorenz2.u
                lorenz2.F ~ lorenz1.u
               ]
test_equal.(observed(reduced_system), observed_eqs)

pp = [
      lorenz1.σ => 10
      lorenz1.ρ => 28
      lorenz1.β => 8/3
      lorenz2.σ => 10
      lorenz2.ρ => 28
      lorenz2.β => 8/3
     ]
u0 = [
      lorenz1.x => 1.0
      lorenz1.y => 0.0
      lorenz1.z => 0.0
      lorenz2.x => 1.0
      lorenz2.y => 0.0
      lorenz2.z => 0.0
     ]
prob1 = ODEProblem(reduced_system, u0, (0.0, 100.0), pp)
solve(prob1, Rodas5())

# issue #724 and #716
let
    @parameters t
    D = Differential(t)
    @variables x(t) u(t) y(t)
    @parameters a b c d
    ol = ODESystem([D(x) ~ a * x + b * u; y ~ c * x + d * u], t, name=:ol)
    @variables u_c(t) y_c(t)
    @parameters k_P
    pc = ODESystem(Equation[u_c ~ k_P * y_c], t, name=:pc)
    connections = [
        ol.u ~ pc.u_c
        pc.y_c ~ ol.y
    ]
    connected = ODESystem(connections, t, systems=[ol, pc])
    @test equations(connected) isa Vector{Equation}
    reduced_sys = structural_simplify(connected)
    ref_eqs = [
               D(ol.x) ~ ol.a*ol.x + ol.b*pc.u_c
               0 ~ -pc.u_c - (pc.k_P*((-ol.c*(ol.x)) - (ol.d*(pc.u_c))))
              ]
    @test ref_eqs == equations(reduced_sys)
end

# issue #889
let
    @parameters t
    D = Differential(t)
    @variables x(t)
    @named sys = ODESystem([0 ~ D(x) + x], t, [x], [])
    sys = structural_simplify(sys)
    @test_throws ModelingToolkit.InvalidSystemException ODEProblem(sys, [1.0], (0, 10.0))
end

# NonlinearSystem
@parameters t
@variables u1(t) u2(t) u3(t)
@parameters p
eqs = [
       u1 ~ u2
       u3 ~ u1 + u2 + p
       u3 ~ hypot(u1, u2) * p
      ]
sys = NonlinearSystem(eqs, [u1, u2, u3], [p])
reducedsys = structural_simplify(sys)
@test observed(reducedsys) == [u2 ~ 0.5(u3 - p); u1 ~ u2]

u0 = [
      u1 => 1
      u2 => 1
      u3 => 0.3
     ]
pp = [2]
nlprob = NonlinearProblem(reducedsys, u0, pp)
reducedsol = solve(nlprob, NewtonRaphson())
residual = fill(100.0, length(states(reducedsys)))
nlprob.f(residual, reducedsol.u, pp)
@test all(x->abs(x) < 1e-5, residual)

N = 5
@variables xs[1:N]
A = reshape(1:N^2, N, N)
eqs = xs .~ A * xs
sys′ = NonlinearSystem(eqs, xs, [])
sys = structural_simplify(sys′)


# Resampling schemes for particle filters
# Some references
#  - http://arxiv.org/pdf/1301.4019.pdf
#  - http://people.isy.liu.se/rt/schon/Publications/HolSG2006.pdf
# Code adapted from: http://uk.mathworks.com/matlabcentral/fileexchange/24968-resampling-methods-for-particle-filtering

# default resampling scheme
function resample(w::Vector{Float64},
                  num_particles::Int = length(w))
    resampleSystematic(w, num_particles)
end

# More stable, faster version of rand(Categorical)
function randcat(p::Vector{Float64})
    # if(any(p .< 0)) error("Negative probabilities not allowed"); end
    r, s = rand(), one(Int)
    for j = 1:length(p)
        r -= p[j]
        if(r <= 0.0) s = j; break; end
    end

    s
end

function resampleMultinomial(w::Vector{Float64},
                             num_particles::Int)

    s = Distributions.sampler(Categorical(w))
    indx = rand(s, num_particles)

end

function resampleResidual(w::Vector{Float64},
                          num_particles::Int)

    M = length( w )

    # "Repetition counts" (plus the random part, later on):
    Ns = floor.(length(w) .* w)

    # The "remainder" or "residual" count:
    R = Int(sum( Ns ))

    # The number of particles which will be drawn stocastically:
    M_rdn = num_particles-R;

    # The modified weights:
    Ws = (M .* w - floor.(M .* w))/M_rdn;

    # Draw the deterministic part:
    indx1 = Array{Int}(undef, R)
    i=1
    for j=1:M
        for k=1:Ns[j]
            indx1[i]=j
            i = i + 1
        end
    end

    # And now draw the stocastic (Multinomial) part:
    s = Distributions.sampler(Categorical(w))
    indx2 = rand(s, M_rdn)
    indx = append!(indx1, indx2)

    return indx

end

function resampleStratified(w::Vector{Float64},
                            num_particles::Int)

    N = num_particles
    Q = cumsum(w)

    T = Array{Float64}(undef, N+1)
    for i=1:N,
        T[i] = rand()/N + (i-1)/N
    end
    T[N+1] = 1

    i=1
    j=1

    indx = Array{Int}(undef, N)
    while i<=N
        if T[i]<Q[j]
            indx[i]=j
            i=i+1
        else
            j=j+1
        end
    end

    return indx

end

function resampleSystematic(w::Vector{Float64},
                            num_particles::Int)

    N = num_particles
    Q = cumsum(w)

    T = collect(range(0, stop = maximum(Q)-1/N, length = N)) .+ rand()/N
    push!(T, 1)

    i=1
    j=1

    indx = Array{Int}(undef, N)
    while i<=N
        if (T[i]<Q[j])
            indx[i]=j
            i=i+1
        else
            j=j+1
        end
        # if j == length(w)
        #   indx[i] = j
        #   break;
        # end
    end

    return indx

end


using POMDPs
using FiniteHorizonPOMDPs

struct FHExampleState
    position::Int64
    epoch::Int64
    done::Bool # are we in a terminal state?
end

# initial state constructor
FHExampleState(position::Int64, epoch::Int64)::FHExampleState = FHExampleState(position, epoch, false)
FHExampleState(position::Int64)::FHExampleState = FHExampleState(position, 0, false)
FHExampleState(position::Int64, done::Bool)::FHExampleState = FHExampleState(position, 0, done)

struct FHExample <: MDP{FHExampleState, Symbol} # Note that our MDP is parametarized by the state and the action
    no_states::Int64 # number od statesL
    horizon::Int64
    actions::Vector{Symbol}
    actionCost::Float64
    actionsImpact::Base.ImmutableDict{Symbol, Int64}
    reward_states::Vector{Int64}
    reward::Float64
    discount_factor::Float64 # discount factor
    noise::Float64
end

function POMDPs.isterminal(mdp::FHExample, state::FHExampleState)::Bool
    return state.done
end

function isreward(mdp::FHExample, position::Int64)::Bool
    return position in mdp.reward_states
end

function POMDPs.reward(mdp::FHExample, s::FHExampleState, a::Symbol, sp::FHExampleState)::Float64
    isreward(mdp, sp.position) ? mdp.reward : mdp.actionCost
end

# returns mdp.no_states * 2 long Array with first mdp.no_states corresponding to current epoch's states whose util value is to be updated
# and another mdp.no_states vector of epoch + 1 states as way to store and uset their util value for computations
# first half of vector is true or false depending on terminality of each state, second half is only true because solvers are not iterate through them
function FiniteHorizonPOMDPs.stage_states(mdp::FHExample, epoch::Int64)::Array{FHExampleState}
    mdp_states = FHExampleState[]
    for i=1:mdp.no_states
        push!(mdp_states, FHExampleState(i, epoch, isreward(mdp, i) || epoch==mdp.horizon))
    end

    return mdp_states
end

# Implementation of function that are replacing POMDPs.states, POMDPs.actions and POMDPs.state_index in FiniteHorizonPOMDPs Solver
function FiniteHorizonPOMDPs.stage_stateindex(mdp::FHExample, s::FHExampleState, epoch::Int64)::Int64
    return s.position + (epoch - 1) * mdp.no_states
end

function FiniteHorizonPOMDPs.stage_stateindex(mdp::FHExample, s::FHExampleState)::Int64
    return s.position
end

function POMDPs.actions(mdp::FHExample, s::FHExampleState)
    mdp.actions
end

function POMDPs.actions(mdp::FHExample)
    mdp.actions
end

# Creates (mdp.horizon - 1) * mdp.no_states states to be evaluated and mdp.no_states sink states
function POMDPs.states(mdp::FHExample)::Array{FHExampleState}
    mdp_states = FHExampleState[]
    for e=1:mdp.horizon
        for i=1:mdp.no_states
            push!(mdp_states, FHExampleState(i, e, isreward(mdp, i) || e == mdp.horizon))
        end
    end

    return mdp_states
end

POMDPs.stateindex(mdp::FHExample, s::FHExampleState)::Int64 = s.position + (s.epoch - 1) * mdp.no_states

POMDPs.actions(mdp::FHExample)::Vector{Symbol} = mdp.actions

POMDPs.actionindex(mdp::FHExample, a::Symbol)::Int64 = findall(x->x==a, POMDPs.actions(mdp))[1]

POMDPs.discount(mdp::FHExample)::Number = mdp.discount_factor

# returns transition distributions - works only for 1D Gridworld with possible moves to left and to right
function POMDPs.transition(mdp::FHExample, s::FHExampleState, a::Symbol)::SparseCat{Vector{FHExampleState},Vector{Float64}}
    sp = FHExampleState[]
    prob = Float64[]

    # add original transition target and probability
    position = s.position + mdp.actionsImpact[a]
    push!(sp, FHExampleState(position, s.epoch + 1, isreward(mdp, position)))
    push!(prob, 1. - mdp.noise)

    # add noise transition target and probability
    noise_action = a == :l ? :r : :l
    position = s.position + mdp.actionsImpact[noise_action]
    push!(sp, FHExampleState(position, s.epoch + 1, isreward(mdp, position)))
    push!(prob, mdp.noise)

    return SparseCat(sp, prob)
end


#example of creating chains in flux
using Flux, BenchmarkTools

model = Chain(x -> 3x^2, x -> 2x, x -> x+1 )

# now take a gradient of this model
@btime df(x) = Flux.gradient(model,x)[1]

@show model(5)
@show df(5)


using HDF5
using Statistics

# ΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛ
# Data Defs
# ΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛ

"""
lattice info stored in this object:
number of spins, energy of interaction,
temperature, spin configuration and energy.
"""
mutable struct Lattice{M <: AbstractArray}
    nspins::Int64
    eps::Float64
    T::Float64
    pbc::Bool
    spins::M
    E::Float64
    M::Float64

    # object constructor, throw random spins
    function Lattice(nspins, eps, temp, pbc)
        probs = rand( nspins )
        spins = Vector( [ prob < 0.5 ? -1 : 1 for prob in probs ] )

        new{typeof(spins)}(nspins, eps, temp, pbc, spins, 0.0, 0.0)
    end
end

# ΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛ
# Physics Defs
# ΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛ

"""
Calculate the energy (Hamiltonian) of the lattice.
"""
function energy(lat::Lattice)
    nspins = lat.nspins
    spins  = lat.spins
    enrg   = [ spins[i] * spins[i+1] for i in 1:(nspins-1) ]
    if (lat.pbc)
        append!( enrg, spins[1] * spins[end] )
    end
    lat.E = -lat.eps * sum(enrg)
    return
end

function magnetization(lat::Lattice)
    return sum(lat.spins)
end

"""
The all-knowing Bolztmann factor
"""
boltz( E, T ) = ( k = 1.38064852e-23; exp( -E/(k*T) ) )

function corr(lat::Lattice)
    if (lat.pbc)
        nspins = lat.nspins
        spins  = lat.spins
        savg   = mean(spins)

        c = ones( (nspins-1)÷2 )

        for r in 1:(nspins-1)÷2
            for i in 1:nspins
                ir    = (i + r) == nspins ? nspins : mod( (i + r), nspins )
                il    = (i - r) == 0      ? nspins : mod( (i - r), nspins )
                c[r] += spins[i]*( spins[ir] + spins[il] )
            end
            c[r] *= 1.0 / ( nspins * 2.0 )
            c[r] -= savg * savg
        end
    end
    return c
end

# ΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛ
# Algorithm Defs
# ΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛ

function MC_flip(lat::Lattice, idx)
    nspins = lat.nspins
    spins  = lat.spins

    # These are the calculations to obtain ΔE
    # Ef = Ei + eps * ( spins[idx]*spins[idx-1] + spins[idx]*spins[idx+1] ) -
    #           ( (-1)*spins[idx]*spins[idx-1] + (-1)*spins[idx]*spins[idx+1] )
    # Ef = Ei + 2 * ( spins[idx]*spins[idx-1] + spins[idx]*spins[idx+1] )

    if lat.pbc
        if idx == nspins
            ΔE = 2 * spins[idx] * ( spins[idx-1] + spins[1] )
        elseif idx == 1
            ΔE = 2 * spins[idx] * ( spins[end]   + spins[idx+1] )
        else
            ΔE = 2 * spins[idx] * ( spins[idx-1] + spins[idx+1] )
        end
    else
        if idx == nspins
            ΔE = 2 * spins[idx] * spins[idx-1]
        elseif idx == 1
            ΔE = 2 * spins[idx] * spins[idx+1]
        else
            ΔE = 2 * spins[idx] * ( spins[idx-1] + spins[idx+1] )
        end
    end
    ΔM = -2 * spins[idx]
    return ΔE, ΔM
end

function MC_step!(lat::Lattice)
    nspins = lat.nspins
    idx    = ceil(Int64, rand() * nspins )

    ΔE, ΔM = MC_flip(lat, idx)

    if ΔE <= 0.0
        lat.spins[idx] *= -1
        lat.E += ΔE
        lat.M += ΔM
    else
        if ( rand() < boltz(ΔE, lat.T) )
            lat.spins[idx] *= -1
            lat.E += ΔE
            lat.M += ΔM
        end
    end
    return
end

function MC_run!(iter, lat::Lattice, data)

    energy(lat)
    magnetization(lat)

    data["mag"][1]     = lat.M
    data["energy"][1]  = lat.E
    data["spins"][:,1] = lat.spins

    Eavg = zeros(iter[2])

    i = 1
    for step in 1:iter[1]
        MC_step!(lat)
        Eavg[i] = lat.E
        i += 1

        if step%iter[2] == 0
            k = step ÷ iter[2]
            println("Step $((k-1)*iter[2])")
            data["energy"][k]  = mean(Eavg)
            data["mag"][k]     = lat.M
            data["spins"][:,k] = lat.spins
            i = 1
        end
    end

    data["corr"][:] = corr(lat)
    return
end

# ΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛ
# Interface
# ΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛΛ

function main(args)
    nspins = parse(Float64, args[1]) |> Int
    eps    = parse(Float64, args[2])
    temp   = parse(Float64, args[3])
    pbc    = true
    iters  = parse(Float64, args[4]) |> Int
    wrt_it = parse(Float64, args[5]) |> Int

    lattice_pbc = Lattice(nspins, eps, temp, pbc)

    data = h5open("./Ising1D.hdf5", "w")
    data_dims = iters ÷ wrt_it
    create_dataset( data, "energy", datatype(1.0), dataspace( (data_dims,) ) )
    create_dataset( data, "mag",    datatype(1.0), dataspace( (data_dims,) ) )
    create_dataset( data, "corr",   datatype(1.0), dataspace( ((nspins-1) ÷ 2,) ) )
    create_dataset( data, "spins",  datatype(1.0), dataspace( (nspins, data_dims) ) )

    create_dataset( data, "params", datatype(1.0), dataspace( (5,) ) )

    data["params"][1] = nspins
    data["params"][2] = eps
    data["params"][3] = temp
    data["params"][4] = iters
    data["params"][5] = wrt_it

    MC_run!( (iters, wrt_it), lattice_pbc, data )

    io = open("julia.out", "w")
    println(io, "Initial Energy = $(data["energy"][1])")
    println(io, "Final Energy = $(data["energy"][end])")
    close(io)

    "Initial Energy = $(data["energy"][1])" |> display
    "Final Energy = $(data["energy"][end])" |> display

    close(data)
end

main(ARGS)


struct AdamIterable{T, F, S, R}
    w0::T
    f::F
    stepsize::S
    beta1::R
    beta2::R
    epsilon::R
end

AdamIterable(w0, f; stepsize=1e-3, beta1=0.9, beta2=0.999, epsilon=1e-8) = AdamIterable(w0, f, to_iterator(stepsize), beta1, beta2, epsilon)

Base.IteratorSize(::Type{<:AdamIterable}) = Base.IsInfinite()

mutable struct AdamState{T, G, S, R}
    w::T
    f_w
    grad_f_w::G
    stepsize_iterator::S
    m::T
    v::T
    beta1t::R
    beta2t::R
end

function Base.iterate(iter::AdamIterable)
    w = copy(iter.w0)
    grad_f_w, f_w = gradient(iter.f, w)
    state = AdamState(
        w, f_w, grad_f_w, Iterators.Stateful(iter.stepsize),
        zero(w), zero(w), iter.beta1, iter.beta2
    )
    return IterationOutput(state.w, state.f_w, state.grad_f_w), state
end

function Base.iterate(iter::AdamIterable, state::AdamState)
    state.m .= iter.beta1 .* state.m .+ (1 - iter.beta1) .* state.grad_f_w
    state.v .= iter.beta2 .* state.v .+ (1 - iter.beta2) .* state.grad_f_w .^ 2
    stepsize = popfirst!(state.stepsize_iterator)
    alpha_t = stepsize * sqrt(1 - state.beta2t)/(1 - state.beta1t)
    state.w .-= alpha_t .* state.m ./ (sqrt.(state.v) .+ iter.epsilon)
    state.beta1t *= iter.beta1
    state.beta2t *= iter.beta2
    state.grad_f_w, state.f_w = gradient(iter.f, state.w)
    return IterationOutput(state.w, state.f_w, state.grad_f_w), state
end

struct Adam
    kwargs
    Adam(; kwargs...) = new(kwargs)
end

(alg::Adam)(args...) = AdamIterable(args...; alg.kwargs...)


include("get_div_grad.jl")

function test_symmlq()
  symmlq_tol = 1.0e-5

  # 1. Symmetric and positive definite systems.
  #
  # Cubic spline matrix.
  n = 10;
  A = spdiagm(-1 => ones(n-1), 0 => 4*ones(n), 1 => ones(n-1))
  b = A * [1:n;]

  (x, xcg, stats) = symmlq(A, b)
  r = b - A * x
  resid = norm(r) / norm(b)
  @printf("SYMMLQ: Relative residual: %8.1e\n", resid)
  @test(resid <= symmlq_tol)
  @test(stats.solved)

  # Symmetric indefinite variant.
  A = A - 3 * I
  b = A * [1:n;]
  (x, xcg, stats) = symmlq(A, b, itmax=n+1)
  r = b - A * x
  resid = norm(r) / norm(b)
  @printf("SYMMLQ: Relative residual: %8.1e\n", resid)
  @test(resid <= symmlq_tol)
  @test(stats.solved)

  # Code coverage.
  (x, xcg, stats) = symmlq(Matrix(A), b)
  show(stats)

  # Sparse Laplacian (CG point will terminate sooner).
  A = get_div_grad(16, 16, 16)
  b = ones(size(A, 1))
  (x, xcg, stats) = symmlq(A, b, atol=1e-12, rtol=1e-12)
  r = b - A * x
  resid = norm(r) / norm(b)
  @show stats
  @printf("SYMMLQ: Relative residual: %8.1e\n", resid)
  @test(resid <= symmlq_tol)
  @test(stats.solved)

  # Symmetric indefinite variant, almost singular.
  A = A - 5 * I
  (x, xcg, stats) = symmlq(A, b)
  r = b - A * x
  resid = norm(r) / norm(b)
  @printf("SYMMLQ: Relative residual: %8.1e\n", resid)
  @test(resid <= 100 * symmlq_tol)
  @test(stats.solved)

  # Test b == 0
  (x, xcg, stats) = symmlq(A, zeros(size(A,1)))
  @test x == zeros(size(A,1))
  @test stats.status == "x = 0 is a zero-residual solution"

  # Test integer values
  A = spdiagm(-1 => ones(Int, n-1), 0 => 4*ones(Int, n), 1 => ones(Int, n-1))
  b = A * [1:n;]
  (x, xcg, stats) = symmlq(A, b)
  @test stats.solved

  # Test error estimate
  A = Matrix(get_div_grad(8, 8, 8))
  b = ones(size(A, 1))
  λest = (1-1e-10)*eigmin(A)
  x_exact = A\b
  (x, xcg, stats) = symmlq(A, b, λest=λest)
  err = norm(x_exact - x)
  errcg = norm(x_exact - xcg)
  @printf("SYMMLQ    : true error: %8.1e\n", err)
  @printf("SYMMLQ-CG : true error: %8.1e\n", errcg)
  @printf("SYMMLQ    : err up-bnd : %8.1e\n", stats.errors[end])
  @printf("SYMMLQ-CG : err up-bnd : %8.1e\n", stats.errorscg[end])
  @test( err <= stats.errors[end] )
  @test( errcg <= stats.errorscg[end])
  (x, xcg, stats) = symmlq(A, b, λest=λest, window=1)
  @printf("SYMMLQ    : err up-bnd : %8.1e\n", stats.errors[end])
  @printf("SYMMLQ-CG : err up-bnd : %8.1e\n", stats.errorscg[end])
  @test( err <= stats.errors[end] )
  @test( errcg <= stats.errorscg[end])
  (x, xcg, stats) = symmlq(A, b, λest=λest, window=5)
  @printf("SYMMLQ    : err up-bnd : %8.1e\n", stats.errors[end])
  @printf("SYMMLQ-CG : err up-bnd : %8.1e\n", stats.errorscg[end])
  @test( err <= stats.errors[end] )
  @test( errcg <= stats.errorscg[end])
end

test_symmlq()


import Compat.Random.srand
srand(model::Model) = srand(model.rng)
srand(model::Model, seed...) = srand(model.rng, seed...)

@doc doc"""
Ising model with energy $E = -\sum_{ij} J_{ij} \sigma_i \sigma_j$,
where $\sigma_i$ takes value of 1 (up spin) or -1 (down spin).
"""
mutable struct Ising <: Model
    lat :: Lattice
    spins :: Vector{Int}
    rng :: Random.MersenneTwister

    function Ising(lat::Lattice)
        model = new()
        model.lat = lat
        model.rng = srand(Random.MersenneTwister(0))
        model.spins = rand(model.rng, [1,-1], numsites(lat))
        return model
    end
    function Ising(lat::Lattice, seed)
        model = new()
        model.lat = lat
        model.rng = srand(Random.MersenneTwister(0), seed...)
        model.spins = rand(model.rng, [1,-1], numsites(lat))
        return model
    end
end
@doc doc"""
    Ising(param)

Generates `Ising` using `param["Lattice"](param)` and `param["Seed"]` (if defined).
Each spin will be initialized randomly and independently.
"""
function Ising(param::Parameter)
    lat = param["Lattice"](param)
    if "Seed" in keys(param)
        return Ising(lat, param["Seed"])
    else
        return Ising(lat)
    end
end

@doc doc"""
`Q` state Potts model with energy $E = -\sum_{i,j} \delta_{\sigma_i, \sigma_j}$,
where $\sigma_i$ takes an integer value from $1$ to $Q$ and $\delta$ is a Kronecker's delta.
Order parameter (total magnetization) is defined as
\begin{equation}
    M = \frac{Q-1}{Q}N_1 - \frac{1}{Q}(N-N_1),
\end{equation}
where $N$ is the number of sites and $N_1$ is the number of $\sigma=1$ spins.
"""
mutable struct Potts <: Model
    lat :: Lattice
    Q :: Int
    spins :: Vector{Int}
    rng :: Random.MersenneTwister

    function Potts(lat::Lattice, Q::Integer)
        rng = srand(Random.MersenneTwister(0))
        spins = rand(rng, 1:Q, numsites(lat))
        return new(lat, Q, spins, rng)
    end
    function Potts(lat::Lattice, Q::Integer, seed)
        rng = srand(Random.MersenneTwister(0), seed)
        spins = rand(rng, 1:Q, numsites(lat))
        return new(lat, Q, spins, rng)
    end
end
@doc doc"""
    Potts(param)

Generates `Potts` using `param["Lattice"](param)`, `param["Q"]`, and `param["Seed"]` (if defined).
Each spin will be initialized randomly and independently.
"""
function Potts(param::Parameter)
    lat = param["Lattice"](param)
    Q = param["Q"]
    if "Seed" in keys(param)
        return Potts(lat, Q, param["Seed"])
    else
        return Potts(lat, Q)
    end
end

@doc doc"""
`Q` state clock model with energy $E = -\sum_{ij} J_{ij} \cos(\theta_i - \theta_j)$,
where $\theta_i = 2\pi \sigma_i/Q$ and $\sigma_i$ takes an integer value from $1$ to $Q$.
"""
mutable struct Clock <: Model
    lat :: Lattice
    Q :: Int
    spins :: Vector{Int}
    cosines :: Vector{Float64}
    sines :: Vector{Float64}
    sines_sw :: Vector{Float64}
    rng :: Random.MersenneTwister

    function Clock(lat::Lattice, Q::Integer)
        rng = srand(Random.MersenneTwister(0))
        spins = rand(rng, 1:Q, numsites(lat))
        cosines = [cospi(2s/Q) for s in 1:Q]
        sines = [sinpi(2s/Q) for s in 1:Q]
        sines_sw = [sinpi(2(s-0.5)/Q) for s in 1:Q]
        return new(lat, Q, spins, cosines, sines, sines_sw, rng)
    end
    function Clock(lat::Lattice, Q::Integer, seed)
        rng = srand(Random.MersenneTwister(0), seed)
        spins = rand(rng, 1:Q, numsites(lat))
        cosines = [cospi(2s/Q) for s in 1:Q]
        sines = [sinpi(2s/Q) for s in 1:Q]
        sines_sw = [sinpi(2(s-0.5)/Q) for s in 1:Q]
        return new(lat, Q, spins, cosines, sines, sines_sw, rng)
    end
end
@doc doc"""
    Clock(param)

Generates `Clock` using `param["Lattice"](param)`, `param["Q"]`,  and `param["Seed"]` (if defined).
Each spin $\sigma_i$ will be initialized randomly and independently.
"""
function Clock(param::Parameter)
    lat = param["Lattice"](param)
    Q = param["Q"]
    if "Seed" in keys(param)
        return Clock(lat, Q, param["Seed"])
    else
        return Clock(lat, Q)
    end
end

@doc doc"""
XY model with energy $E = -\sum_{ij} J_{ij} \cos(\theta_i - \theta_j)$,
where $\theta_i = 2\pi \sigma_i$ and $\sigma_i \in [0, 1)$.
"""
mutable struct XY <: Model
    lat :: Lattice
    spins :: Vector{Float64}
    rng :: Random.MersenneTwister

    function XY(lat::Lattice)
        model = new()
        model.rng = srand(Random.MersenneTwister(0))
        model.lat = lat
        model.spins = rand(model.rng, numsites(lat))
        return model
    end
    function XY(lat::Lattice, seed)
        model = new()
        model.rng = srand(Random.MersenneTwister(0), seed)
        model.lat = lat
        model.spins = rand(model.rng, numsites(lat))
        return model
    end
end
@doc doc"""
   XY(param)

Generates `XY` using `param["Lattice"](param)`,  and `param["Seed"]` (if defined).
Each spin $\sigma_i$ will be initialized randomly and independently.
"""
function XY(param::Parameter)
    lat = param["Lattice"](param)
    if "Seed" in keys(param)
        return XY(lat, param["Seed"])
    else
        return XY(lat)
    end
end


# unary.jl: Unary Array->Array operations.
# The following list comes from the NVIDIA math docs with some extras.
# http://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#mathematical-functions-appendix
# The entry format is (cudaname, julianame, kernelcode)
# With single name entries cudaname=julianame and kernelcode=name(xi).
# I commented out functions if I don't know the Julia equivalent.
unary_ops = [
("abs2", "abs2", "(xi*xi)"),
("abs", "abs", "(xi<0?-xi:xi)"),
("htanh", "htanh", "(xi<-1?-1 : (xi > 1 ? 1 :xi))"),
"acos",
"acosh",
"asin",
"asinh",
"atan",
"atanh",
"cbrt",
"ceil",
"cos",
"cosh",
"cospi",
# "cyl_bessel_i0",
# "cyl_bessel_i1",
"erf",
"erfc",
"erfcinv",
"erfcx",
"erfinv",
"exp",
"exp10",
"exp2",
"expm1",
"floor",
# "ilogb",
("invx", "invx", "1/xi"),
# "j0",
# "j1",
# "lgamma", # missing digamma for derivative
# "llrint",
# "llround",
"log",
"log10",
"log1p",
"log2",
# "logb",
# "lrint",
# "lround",
# "nearbyint",
("neg", "-", "-xi"),
# "normcdf",
# "normcdfinv",
# "rcbrt",
("relu", "relu", "(xi>0?xi:0)"),
("lzorelu", "lzorelu", "(xi<1e-6?1e-6:xi)"),
# "rint",
"round",
# "rsqrt",
("sigm", "sigm", "(xi>=0?1/(1+exp(-xi)):(exp(xi)/(1+exp(xi))))"),
("sign", "sign", "(xi>0?1:xi<0?-1:0)"),
"sin",
"sinh",
"sinpi",
"sqrt",
"tan",
"tanh",
# "tgamma",
"trunc",
# "y0",
# "y1",
("G", "G", "exp(-(xi*xi)/2) / 2.5066"),
("H", "H", "0.5 * erfc(xi / 1.4142)"),
("GH", "GH", "xi > 30 ? xi + 1/xi * (1 - 2/(xi*xi) * (1 - 5/(xi*xi) * (1 - 7.4/(xi*xi)))) : exp(-(xi*xi)/2) / (1.2533 * erfc(xi / 1.4142))"),
"atanh2Hm1",
]

function unary_op(f, j=f, o...)
    J=Symbol(j)
    if isdefined(Base, J); eval(Expr(:import,:Base,J)); end
    for S in (32,64)
        T = Symbol("Float$S")
        F = "$(f)_$S"
        @eval begin
            function $J(x::KnetArray{$T})
                y = similar(x)
                ccall(($F,$libknet8),Void,(Cint,Ptr{$T},Ptr{$T}),length(y),x,y)
                return y
            end
        end
    end
end

for f in unary_ops
    if !isa(f,Tuple); f=(f,); end
    unary_op(f...)
end

# Define some common operations as primitives for efficiency:
# 1. Avoid creating intermediate arrays
# 2. Avoid taking derivatives of intermediate operations

for (f,g,y,dx) in
    ((:invx, :invxback, :(one(T)/xi), :(-yi*yi*dyi)),
     (:relu, :reluback, :(max(zero(T),xi)), :(ifelse(yi>0,dyi,zero(T)))),
     #(:H, :G, :(H(xi)), :(dyi*G(xi))),
     (:tanx, :tanhback, :(tanh(xi)), :(dyi*(one(T)-yi*yi))),
     (:htanx, :htanhback, :(htanh(xi)), :(dyi*(-1.0<=yi<=1.0))),
     (:sigm, :sigmback,
      # Numerically stable implementation from
      # http://timvieira.github.io/blog/post/2014/02/11/exp-normalize-trick
      :(if xi>=0; z=exp(-xi); one(T)/(one(T)+z); else; z=exp(xi); z/(one(T)+z); end),
      :(dyi*yi*(one(T)-yi))),
     )
    @eval begin
        function $f{T<:AbstractFloat}(x::Array{T})
            y = similar(x)
            @inbounds for i=1:length(y)
                xi = x[i]
                y[i] = $y
            end
            return y
        end
        function $g{T<:AbstractFloat}(dy::Array{T},y::Array{T})
            dx = similar(dy)
            @inbounds for i=1:length(dx)
                yi = y[i]
                dyi = dy[i]
                dx[i] = $dx
            end
            return dx
        end
        $f{T<:Number}(xi::T)=$y
        $g{T<:Number}(dyi::T,yi::T)=$dx
        @primitive $f(x),dy,y $g(dy,y)
    end
end

"`invx(x) = (1./x)`" invx
"`relu(x) = max(0,x)`" relu
"`sigm(x) = (1./(1+exp(-x)))`" sigm

# To avoid conflict with AutoGrad:
@primitive tanh(x::Array),dy,y     tanhback(dy,y)
@primitive tanh(x::KnetArray),dy,y tanhback(dy,y)
@primitive tanhback(dy,y),ddx  ddx.*(1.-y.*y)  ddx.*(-2.*dy.*y)
@primitive H(x),dy dy*G(x)

"""

    logp(x,[dims])

Treat entries in `x` as as unnormalized log probabilities and return
normalized log probabilities.

`dims` is an optional argument, if not specified the normalization is
over the whole `x`, otherwise the normalization is performed over the
given dimensions.  In particular, if `x` is a matrix, `dims=1`
normalizes columns of `x` and `dims=2` normalizes rows of `x`.

"""

# Math for the cross-entropy loss: x is unnormalized input, p is
# target probabilities, q is estimated probabilities. Read left column
# down, right column (loss gradients) back up.

# x			dx = -p + qz/z = -p + exp(logq)
# xmax  = max(x,1)	-sum(db)=0
# logqz = x .- xmax	-p + qz/z
# qz    = exp(logqz)	rep(1/z)
# z     = sum(qz,1)	1/z
# logz  = log(z)	sum(p)=1
# logq  = logqz.-logz	-p
# plogq = p .* logq	-1
# loss  = -sum(plogq)	1

function logp(x,d...)
    if isa(x,Number)
        return zero(x)
    elseif isempty(x)
        return x
    else
        x = x .- maximum(x,d...)
        return (x .- log(sum(exp(x),d...)))
    end
end

function logpback(x,y,dy,d...)
    x = AutoGrad.getval(x)
    if isa(x,Number)
        return zero(x)
    elseif isempty(x)
        return x
    else
        return (dy - exp(y).*sum(dy,d...))
    end
end

# dy should be -p and y=logq so this should give us -p+q
@primitive  logp(x,d...),dy,y  logpback(x,y,dy,d...)


config_file = isempty(ARGS) ? joinpath(@__DIR__, "config", "config_syn.jl") : ARGS[1]
isfile(config_file) || error("Cannot read '$config_file'")
println("Config supplied: '$config_file'")
include(config_file)

using Distributions

using DelimitedFiles
using Random

function make_blobs(n_inliers=100, n_dims=2; n_outliers=0, n_clusters=3, cluster_std=0.05, threshold=1, data_range=(0, 1))
    center_box = (data_range[1] + (data_range[2] - data_range[1]) * 0.2,
                  data_range[1] + (data_range[2] - data_range[1]) * 0.8)
    centers = rand(Uniform(center_box...), (n_dims, n_clusters))
    dists = [MvNormal(centers[:, i], cluster_std) for i in 1:n_clusters]
    n_samples_per_center = fill(div(n_inliers, n_clusters), n_clusters)
    for i in 1:n_inliers % n_clusters
        n_samples_per_center[i] += 1
    end
    data_inliers = []
    for (i, d) in enumerate(dists)
        data_d = zeros(n_dims, 0)
        while size(data_d, 2) < n_samples_per_center[i]
            candidates = rand(d, (n_samples_per_center[i]) * 2)
            mask = pdf(d, candidates) .>= threshold
            candidates = candidates[:, mask]
            data_d = hcat(data_d, candidates[:, 1:min(end, n_samples_per_center[i] - size(data_d, 2))])
        end
        @assert size(data_d) == (n_dims, n_samples_per_center[i])
        push!(data_inliers, data_d)
    end
    data_inliers = hcat(data_inliers...)

    mix_dist = MixtureModel(dists)
    data_outliers = zeros(n_dims, 0)
    while size(data_outliers, 2) < n_outliers
        candidates = rand(Uniform(data_range...), (n_dims, n_outliers * 2))
        mask = pdf(mix_dist, candidates) .< threshold
        candidates = candidates[:, mask]
        data_outliers = hcat(data_outliers, candidates[:, 1:min(end, n_outliers - size(data_outliers, 2))])
    end
    d = hcat(data_inliers, data_outliers)
    d .-= minimum(d, dims=2)
    d ./= maximum(d, dims=2)
    data_inliers, data_outliers = d[:, 1:end-n_outliers], d[:, end-n_outliers+1:end]
    @assert size(data_inliers) == (n_dims, n_inliers)
    @assert size(data_outliers) == (n_dims, n_outliers)
    return data_inliers, data_outliers
end

function generate_data(n_in, dim, n_out, n_clusters, seed, name="standard")
    Random.seed!(seed)
    d_in, d_out = make_blobs(n_in, dim, n_outliers=n_out, n_clusters=n_clusters)
    d = hcat(d_in, d_out)
    l = vcat(fill(:inlier, n_in), fill(:outlier, n_out))
    folder = joinpath(data_input_root, name)
    mkpath(folder)
    file_name = "syn_$(name)_$(n_in)_in_$(n_out)_out_$(dim)_dim_$(n_clusters)_clusters_$(seed)_seed.csv"
    println(file_name)
    open(joinpath(folder, file_name), "w") do f
       writedlm(f, [d' l], ',')
    end
    return nothing
end

NUM_GENS = 5

# vary dims
N_CLUSTERS = 5
N_IN = 1000
N_OUT = 1000
DIMS = [10, 50, 100, 200, 500]
for SEED in 1:NUM_GENS
    for DIM in DIMS
        generate_data(N_IN, DIM, N_OUT, N_CLUSTERS, SEED, "dims")
    end
end

# vary size
SEED = 0
DIM = 50
N_CLUSTERS = 5
N_OUT = 1000
SIZES = [100, 500, 1000, 2500, 5000, 7500, 10000]
for SEED in 1:NUM_GENS
    for N_IN in SIZES
        generate_data(N_IN, DIM, N_OUT, N_CLUSTERS, SEED, "sizes")
    end
end

# vary clusters
DIM = 50
N_IN = 1000
N_OUT = 1000
CLUSTERS = [1, 3, 5, 10, 20]
for SEED in 1:NUM_GENS
    for N_CLUSTERS in CLUSTERS
        generate_data(N_IN, DIM, N_OUT, N_CLUSTERS, SEED, "clusters")
    end
end