using Pkg
Pkg.activate(joinpath(Pkg.devdir(), "MLCourse"))
using MLCourse, CSV, MLJ, DataFrames, MLJLinearModels, Random, Flux, MLJFlux, MLJXGBoostInterface, MLJDecisionTreeInterface
include("./Data_Processing.jl")

#Load the data
drop_std = generate(option = "drop", std = "true", valid = "false", test = "true");
med_std = generate(option = "med", std = "true", valid = "false", test = "true");
drop = generate(option = "drop", std = "false", valid = "false", test = "true");
med = generate(option = "med", std = "false", valid = "false", test = "true");

#Function to tune the model with hyper-parameters eta, num_round, and max_depth
function TunedModel_XGB(data)
    Random.seed!(2711)
    model = XGBoostClassifier()
    self_tuning_model = TunedModel(model = model,
                                resampling = CV(nfolds = 10),
                                tuning = Grid(goal = 100),
                                range = [range(model, :eta,
                                        lower = 1e-4, upper = .1, scale = :log),
                                 range(model, :num_round, lower = 50, upper = 500),
                                 range(model, :max_depth, lower = 2, upper = 6)],
                                measure = auc)
    self_tuning_mach = machine(self_tuning_model,
                            select(data, Not(:precipitation_nextday)),
                            data.precipitation_nextday) |> fit!
    self_tuning_mach
end

#Test the tuned model on the drop data set and med data set with both standardized and not-standardized data:
rep1 = report(TunedModel_XGB(drop_std.train));
rep1.best_history_entry.measurement

rep2 = report(TunedModel_XGB(med_std.train));
rep2.best_history_entry.measurement #AUC for med, standardized data 0.93; best hyper-parameters being eta = 0.1, num_round = 500, max_depth = 6

rep3 = report(TunedModel_XGB(drop.train));
rep3.best_history_entry.measurement #AUC for dropped, non standardized data 0.9176; best hyper-parameters being eta = 0.1, num_round = 400, max_depth = 4

rep4 = report(TunedModel_XGB(med.train));
rep4.best_history_entry.measurement #AUC for med, non standardized data 0.9297; best hyper-parameters being eta = 0.1, num_round = 310, max_depth = 6

#Test the best model over the test set with the best hyper-parameters to pick the best one:
best_mach1 = machine(XGBoostClassifier(eta = rep1.best_model.eta, num_round = rep1.best_model.num_round, max_depth = rep1.best_model.max_depth), select(drop_std.train[:,:], Not(:precipitation_nextday)), drop_std.train.precipitation_nextday)|> fit!
best_mach2 = machine(XGBoostClassifier(eta = rep2.best_model.eta, num_round = rep2.best_model.num_round, max_depth = rep2.best_model.max_depth), select(med_std.train[:,:], Not(:precipitation_nextday)), med_std.train.precipitation_nextday)|> fit!
best_mach3 = machine(XGBoostClassifier(eta = rep3.best_model.eta, num_round = rep3.best_model.num_round, max_depth = rep3.best_model.max_depth), select(drop.train[:,:], Not(:precipitation_nextday)), drop.train.precipitation_nextday)|> fit!
best_mach4 = machine(XGBoostClassifier(eta = rep4.best_model.eta, num_round = rep4.best_model.num_round, max_depth = rep4.best_model.max_depth), select(med.train[:,:], Not(:precipitation_nextday)), med.train.precipitation_nextday)|> fit!
losses(best_mach1, select(drop_std.test, Not(:precipitation_nextday)), drop_std.test.precipitation_nextday)
losses(best_mach2, select(med_std.test, Not(:precipitation_nextday)), med_std.test.precipitation_nextday)
losses(best_mach3, select(drop.test, Not(:precipitation_nextday)), drop.test.precipitation_nextday)
losses(best_mach4, select(med.test, Not(:precipitation_nextday)), med.test.precipitation_nextday)

#med with standardized data seems to perform the best

#Write in the submission file with the best machine trained on all data
train, test = generate(option = "med", std = "true", valid = "false", test = "false");
best_mach = machine(XGBoostClassifier(eta = rep2.best_model.eta, num_round = rep2.best_model.num_round, max_depth = rep2.best_model.max_depth), select(train[:,:], Not(:precipitation_nextday)), train.precipitation_nextday)|> fit!
sample = CSV.read(joinpath(@__DIR__, "data", "sample_submission.csv"), DataFrame);
pred = pdf.(predict(best_mach, test), true);
sample.precipitation_nextday = pred;
CSV.write(joinpath(@__DIR__,"results","XGBoost_submission.csv"), sample);


using .DOS
using SpecialFunctions

"""
    fermi_dirac(E, kT=0.026)

Compute the value of the Fermi-Dirac distribution at energy `E` (relative to the Fermi energy) and thermal energy `kT`.
"""
fermi_dirac(E; kT = 0.026) = inv.(1 .+ exp.(E ./ kT))

abstract type KineticModel end

# dispatch for net rates
(km::KineticModel)(V_app; kT = 0.026) =
    abs.(km(V_app, true; kT = kT) - km(V_app, false; kT = kT))

# return a new one with a scaled prefactor
import Base.*
function *(c::Real, km::KineticModel)
    new_A = c*km.A
    other_params = getfield.([km], propertynames(km))[2:end]
    typeof(km)(new_A, other_params...)
end

# generic pretty printing
function Base.show(io::IO, m::KineticModel)
    s = repr(typeof(m)) * "("
    for field in propertynames(m)
        s =
            s *
            string(field) *
            "=" *
            string(round(getproperty(m, field), sigdigits = 3)) *
            ", "
    end
    s = s[1:end-2] * ")"
    print(io, s)
end

"""
    ButlerVolmer(A, α)
    ButlerVolmer(A)

Computes Butler-Volmer kinetics.

If initialized with one argument, assumes symmetric electron transfer (α=0.5) and sets this to be the prefactor A. Note that this prefactor implicitly contains information about equilibrium activation energies, as well as geometric information.
"""
struct ButlerVolmer <: KineticModel
    A::Float64
    α::Float64
end

# default to unit prefactor and symmetric response
ButlerVolmer() = ButlerVolmer(1.0, 0.5)
ButlerVolmer(A) = ButlerVolmer(A, 0.5)

function (bv::ButlerVolmer)(V_app, ox::Bool; kT::Real = 0.026)
    local exp_arg
    if ox
        exp_arg = (bv.α .* V_app) ./ kT
    else
        exp_arg = -((1 .- bv.α) .* V_app) ./ kT
    end
    bv.A .* exp.(exp_arg)
end

"""
    AsymptoticMarcusHushChidsey(A, λ)
    AsymptoticMarchusHushChidsey(λ)

Computes asymptotic solution to MHC model, as described in Zeng et al.: 10.1016/j.jelechem.2014.09.038d

If initialized with one argument, assumes this to be the reorganization energy λ and sets the prefactor to 1.0.
"""
struct AsymptoticMarcusHushChidsey <: KineticModel
    A::Float64
    λ::Float64
end

# default prefactor is 1
AsymptoticMarcusHushChidsey(λ) = AsymptoticMarcusHushChidsey(1.0, λ)

function (amhc::AsymptoticMarcusHushChidsey)(V_app, ox::Bool; kT::Real = 0.026)
    η = (2 * ox - 1) .* V_app ./ kT
    λ_nondim = amhc.λ / kT
    a = 1 + sqrt(λ_nondim)
    arg = (λ_nondim .- sqrt.(a .+ η^2)) ./ (2 * sqrt(λ_nondim))
    pref = sqrt(π * λ_nondim) / (1 + exp(-η))
    return amhc.A .* pref .* erfc.(arg)
end

abstract type IntegralModel <: KineticModel end # "Marcus-like"

# check to catch missed dispatches for new types
# integrand(km::IntegralModel, V_dl, ox::Bool; kwargs...) =
#     error("An integral-based kinetic model must dispatch the `integrand` function!")

# TODO: check that this passes through both kT and V_q appropriately
# dispatch for net rates
integrand(km::IntegralModel, V_dl; kwargs...) =
    E -> abs.(
        integrand(km, V_dl, true; kwargs...)(E) - integrand(km, V_dl, false; kwargs...)(E),
    )

"""
    Marcus(A, λ)
    Marcus(λ)

Computes Marcus kinetics.

If initialized with one argument, assumes this to be the reorganization energy λ and sets the prefactor A to 1.0.
"""
struct Marcus <: KineticModel
    A::Float64
    λ::Float64
end

# default prefactor is 1
Marcus(λ) = Marcus(1.0, λ)

function (m::Marcus)(V_app, ox::Bool; kT::Real = 0.026)
    local exp_arg
    if ox
        exp_arg = -(m.λ .+ V_app).^2 ./ (4 .* m.λ .* kT)
    else
        exp_arg = -(m.λ .- V_app).^2 ./ (4 .* m.λ .* kT)
    end
    m.A .* exp.(exp_arg)
end

"""
    MarcusHushChidsey(A, λ, average_dos)
    MarcusHushChidsey(λ, average_dos)

Computes Marcus-Hush-Chidsey kinetics: 10.1126/science.251.4996.919

Note that strictly speaking, `average_dos` and the prefactor `A` are redundant. They are both included primarily to facilitate comparisons with similarly parametrized `Marcus` models.
"""
struct MarcusHushChidsey <: IntegralModel
    A::Real
    λ::Real
    average_dos::Real
end

# default prefactor is 1
MarcusHushChidsey(λ, avg_dos) = MarcusHushChidsey(1.0, λ, avg_dos)
# convert more detailed DOS information to just pull out average
MarcusHushChidsey(A, λ, dd::DOSData) = MarcusHushChidsey(A, λ, dd.average_value)
MarcusHushChidsey(A, λ, dos_file::Union{Matrix,String}; kwargs...) =
    MarcusHushChidsey(A, λ, DOSData(dos_file; kwargs...))

# TODO: Check that both this and +DOS versions still match original paper
function integrand(mhc::MarcusHushChidsey, V_dl, ox::Bool; kT::Real = 0.026)
    function marcus_term(E)
        local exp_arg
        if ox
            exp_arg = -( ((E .- (mhc.λ .+ V_dl)) .^ 2)) ./ (4 * mhc.λ * kT)
        else
            exp_arg = -( ((E .- (mhc.λ .- V_dl)) .^2)) ./ (4 * mhc.λ * kT)
        end
        exp.(exp_arg)
    end
    E -> (mhc.A * mhc.average_dos) .* marcus_term(E) .* fermi_dirac(E; kT = kT)
end

"""
    MarcusHushChidseyDOS(A=1.0, λ, dos)
    MarcusHushChidseyDOS(A=1.0, λ, dos_file)

Computes Marcus-Hush-Chidsey + DOS kinetics as described in Kurchin and Viswanathan: 10.1063/5.0023611
"""
struct MarcusHushChidseyDOS <: IntegralModel
    A::Float64
    λ::Float64
    dos::DOSData
end

function Base.show(io::IO, mhcd::MarcusHushChidseyDOS)
    s = repr(typeof(mhcd)) * "("
    s *= "A=$(round(mhcd.A, sigdigits=3)), λ=$(round(mhcd.λ, sigdigits=3)))"
    print(io, s)
end

# default prefactor is 1
MarcusHushChidseyDOS(λ, dd::DOSData) = MarcusHushChidseyDOS(1.0, λ, dd)

MarcusHushChidseyDOS(A, λ, dos_file::Union{Matrix,String}; kwargs...) =
    MarcusHushChidseyDOS(A, λ, DOSData(dos_file; kwargs...))

MarcusHushChidseyDOS(λ, dos_file::Union{Matrix,String}; kwargs...) = MarcusHushChidseyDOS(1.0, λ, DOSData(dos_file; kwargs...))

function integrand(
    mhcd::MarcusHushChidseyDOS,
    V_dl,
    ox::Bool;
    kT::Real = 0.026,
    V_q = 0.0,
)
    function marcus_term(E)
        local exp_arg
        if ox
            exp_arg = -(( (mhcd.λ .- V_dl) .+ E) .^ 2) ./ (4 * mhcd.λ * kT)
        else
            exp_arg = -(( (mhcd.λ .+ V_dl) .- E) .^ 2) ./ (4 * mhcd.λ * kT)
        end
        exp.(exp_arg)
    end
    fd(E) = ox ? 1 .- fermi_dirac(E; kT = kT) : fermi_dirac(E; kT = kT)
    E -> mhcd.A .* mhcd.dos.interp_func.(E .+ V_q) .* fd(E) .* marcus_term(E)
end


#=
some code taken and modified from PiecewiseLinearOpt.jl
https://github.com/joehuchette/PiecewiseLinearOpt.jl
Copyright (c) 2016: Joey Huchette

We homogenize using a given perspective variable v. σ ≥ 0 and sum(σ) = v are already imposed.
In the "bounded" case, we assume v ∈ [0,1] (MIP formulation can be linear/big-M).
In the "nonneg" case, we assume v ≥ 0 (MIP formulation needs conic constraints).
=#

import PiecewiseLinearOpt
const PLO = PiecewiseLinearOpt

const FloatOrVar = Union{Float64, JuMP.VariableRef}

# SOS2 formulation types
abstract type PWLSOS2 end

struct SOS2 <: PWLSOS2 end
struct CCBounded <: PWLSOS2 end
struct CCNonneg <: PWLSOS2 end
struct LogIBBounded <: PWLSOS2 end
struct LogIBNonneg <: PWLSOS2 end

function PWL_SOS2(::SOS2, model::JuMP.Model, σ::Vector{JuMP.VariableRef}, ::FloatOrVar)
    JuMP.@constraint(model, σ in JuMP.SOS2())
    return
end

function PWL_SOS2(
    ::CCBounded,
    model::JuMP.Model,
    σ::Vector{JuMP.VariableRef},
    binper::FloatOrVar, # binary or 1
)
    n = length(σ)
    y = JuMP.@variable(model, [1:(n - 1)], Bin)
    JuMP.@constraints(model, begin
        sum(y) <= binper
        σ[1] <= y[1]
        [i in 2:(n - 1)], σ[i] <= y[i - 1] + y[i]
        σ[n] <= y[n - 1]
    end)
    return
end

function PWL_SOS2(
    ::CCNonneg,
    model::JuMP.Model,
    σ::Vector{JuMP.VariableRef},
    binper::FloatOrVar, # binary or 1
)
    n = length(σ)
    y = JuMP.@variable(model, [1:(n - 1)], Bin)
    z = JuMP.@variable(model) # TODO or add n variables?
    RSOC = JuMP.RotatedSecondOrderCone()
    JuMP.@constraints(model, begin
        sum(y) <= binper
        [z, y[1], σ[1]] in RSOC
        [i in 2:(n - 1)], [z, y[i - 1] + y[i], σ[i]] in RSOC
        [z, y[n - 1], σ[n]] in RSOC
    end)
    return
end

function _logIB_H(k::Int)
    _H = PLO.reflected_gray_codes(k)
    d = length(_H)
    H = Dict(i => _H[i] for i in 1:d)
    H[0] = H[1]
    H[d + 1] = H[d]
    return H
end

function PWL_SOS2(
    ::LogIBBounded,
    model::JuMP.Model,
    σ::Vector{JuMP.VariableRef},
    binper::FloatOrVar, # binary or 1
)
    n = length(σ)
    k = ceil(Int, log2(n - 1))
    y = JuMP.@variable(model, [1:k], Bin)
    H = _logIB_H(k)
    for j in 1:k
        H1 = JuMP.@expression(model, sum(σ[i] for i in 1:n if H[i - 1][j] == H[i][j] == 1))
        H0 = JuMP.@expression(model, sum(σ[i] for i in 1:n if H[i - 1][j] == H[i][j] == 0))
        JuMP.@constraint(model, H1 <= y[j])
        JuMP.@constraint(model, H0 <= binper - y[j])
    end
    return
end

function PWL_SOS2(
    ::LogIBNonneg,
    model::JuMP.Model,
    σ::Vector{JuMP.VariableRef},
    binper::FloatOrVar,
)
    n = length(σ)
    k = ceil(Int, log2(n - 1))
    y = JuMP.@variable(model, [1:k], Bin)
    z = JuMP.@variable(model) # TODO or add k variables?
    RSOC = JuMP.RotatedSecondOrderCone()
    H = _logIB_H(k)
    for j in 1:k
        H1 = JuMP.@expression(model, sum(σ[i] for i in 1:n if H[i - 1][j] == H[i][j] == 1))
        H0 = JuMP.@expression(model, sum(σ[i] for i in 1:n if H[i - 1][j] == H[i][j] == 0))
        JuMP.@constraint(model, [z, y[j], H1] in RSOC)
        JuMP.@constraint(model, [z, binper - y[j], H0] in RSOC)
    end
    return
end


using Test
using LinearAlgebra
using SparseArrays
using SimplePartitions
using SimplePolynomials # Polynomials
# using Pkg
# Pkg.add(PackageSpec(name = "SimpleTools", rev = "master"))
# Pkg.resolve()
# using SimpleTools
using SimpleGraphs

@testset "Core" begin
    G = Path(10)
    name(G, "Example")
    @test name(G) == "Example"
    @test NV(G) == 10
    @test NE(G) == 9
    @test G[2] == [1, 3]
    @test deg(G, 2) == 2
    @test sum(deg(G)) == 2NE(G)

    G = IntGraph(2)
    add!(G, 1, 2)
    add!(G, 1, 3)
    add!(G, 2, 3)
    @test NV(G) == NE(G)
    A = adjacency(G)
    H = SimpleGraph(A)
    @test G == H
    H = SimpleGraph(convert(BitMatrix, A))
    @test G == H

    @test get_edge(G, 1, 2) == get_edge(G, 2, 1)

    G = StringGraph()
    add!(G, "alpha", "beta")
    @test G["alpha", "beta"]

    G = SimpleGraph{Complex{Float64}}()
    H = SimpleGraph{Complex{Float64}}()
    add!(G, im, -im)
    add!(H, -im, im)
    @test G == H
end

@testset "Ops" begin
    G = IntGraph(3)
    ee = [(1, 2), (2, 3), (1, 3)]
    add_edges!(G, ee)
    delete!(G, 1, 2)
    @test NE(G) == 2
    G = Cycle(5)
    delete!(G, 5)
    @test G == Path(4)

    G = line_graph(Cycle(5))
    @test deg(G) == 2 * ones(5)

    H = G'
    @test G == H'

    G = Cycle(4)
    H = cartesian(G, G)
    @test NV(H) == NV(G)^2

    H = disjoint_union(Cycle(4), Cycle(5))
    @test NV(H) == 9
    @test NE(H) == 9
    @test H == Cycle(4) + Cycle(5)

    @test 2H == H + H

    G = join(IntGraph(5), IntGraph(4))
    @test NE(G) == 20

    H = IntGraph(5) ∨ IntGraph(4)
    @test G == H

    G = Cycle(9)
    H = Cycle(10)
    contract!(H, 1, 10)
    @test G == H

    G = Path(9)
    H = trim(G, 1)
    @test NV(H) == 0

    G = Complete(5)
    H = subdivide(G)
    @test NV(H) == NV(G) + NE(G)
    @test NE(H) == 2 * NE(G)

end

@testset "Constructors" begin
    G = Complete(5)
    @test NE(G) == 10
    G = Complete(4, 5)
    @test NE(G) == 20
    G = Complete([5, 5, 5])
    @test NE(G') == 30

    G = RandomTree(10)
    @test NE(G) == 9
    G = Grid(3, 3)
    @test NV(G) == 9
    G = Wheel(10)
    @test NV(G) == 10
    G = Cube(4)
    @test NV(G) == 16
    G = BuckyBall()
    @test NE(G) == 90
    G = Petersen()
    @test NE(G) == 15
    G = Paley(13)
    @test NE(G) == NE(G')
    G = RandomRegular(10, 3)
    @test NE(G) == 15
    G = Knight(5, 5)
    @test NV(G) == 25
    @test NE(HoffmanSingleton()) == 175
    p1 = char_poly(Hoffman())
    p2 = char_poly(Cube(4))
    @test p1 == p2
end

@testset "Platonics" begin
    G = Icosahedron()
    H = Dodecahedron()
    @test NE(G) == NE(H)
    G = Octahedron()
    H = Complete([2, 2, 2])
    @test NE(G) == NE(H)
    @test Tetrahedron() == Complete(4)
end

@testset "Unit distance" begin
    G = Spindle()
    @test is_unit_distance(G)
    G = Golomb()
    @test is_unit_distance(G)
end

@testset "Connectivity" begin
    G = Path(10)
    @test diam(G) == 9
    @test eccentricity(G, 2) == 8
    @test num_components(G) == 1
    @test spanning_forest(G) == G
    @test is_acyclic(G)
    @test is_cut_edge(G, 3, 4)
    @test radius(G) == 5
    @test graph_center(G) == Set([5, 6])

    G = Complete(5, 5)'
    @test num_components(G) == 2
    H = spanning_forest(G)
    @test num_components(H) == 2
    @test NE(H) == 8

    G = Cycle(5) + Cycle(8) + Cycle(10)
    A = max_component(G)
    @test length(A) == 10
end

@testset "Matrices" begin
    G = Petersen()
    M = incidence(G)
    L = laplace(G)
    @test L == M * M'
    A = adjacency(G)
    v = A * ones(10)
    @test v == 3 * ones(Int, 10)
end

@testset "Twins" begin
    G = Complete(5, 3)
    @test twins(G) == bipartition(G)
end

@testset "Coloring" begin
    G = RandomTree(10)
    d = two_color(G)
    @test Set(values(d)) == Set([1, 2])
    f = greedy_color(G)
    @test length(keys(f)) == NV(G)
end

@testset "Euler" begin
    G = Cube(4)
    tour = euler(G)
    @test length(tour) == NE(G) + 1
    @test tour[1] == tour[end]
end

@testset "Hamiltonian" begin
    G = Cube(4)
    tour = hamiltonian_cycle(G)
    @test length(tour) == NV(G)
end

@testset "Girth" begin
    G = Cube(4)
    @test girth(G) == 4
    G = RandomTree(10)
    @test girth(G) == 0
end

@testset "Bisect" begin
    G = RandomTree(10)
    A, B = bisect(G)
    @test G.V == union(A, B)
    @test length(intersect(A, B)) == 0
    @test length(cross_edges(G, A, B)) == 1
end

@testset "Transitive" begin
    G = RandomTree(10)
    D = transitive_orientation(G)
    @test G == simplify(D)
    @test num_trans_orientations(G) == 2
end

@testset "Prufer" begin
    for k = 1:10
        G = RandomTree(10)
        code = prufer_code(G)
        H = prufer_restore(code)
        @test G == H
    end
end

@testset "Polynomials" begin
    G = Complete(3, 4)
    p = indep_poly(G)
    @test coeffs(p) == [1; 7; 9; 5; 1]

    p = matching_poly(G)
    @test coeffs(p) == [0, -24, 0, 36, 0, -12, 0, 1]

    p = interlace(G)
    @test coeffs(p) == [0, 2, 3, 4, 3, 1]

end

# DIRECTED GRAPH STUFF

@testset "Basic Directed" begin
    G = StringDigraph()
    add!(G, "alpha", "bravo")
    add!(G, "bravo", "charlie")
    @test NV(G) == 3
    @test NE(G) == 2
    @test sum(out_deg(G)) == sum(in_deg(G))

    H = simplify(relabel(G))
    @test H == Path(3)
end

@testset "Digraph Constructors" begin
    G = DirectedPath(10)
    add!(G, 10, 1)
    @test G == DirectedCycle(10)

    G = DirectedComplete(10, false)
    @test NE(G) == 10 * 9
    G = DirectedComplete(10, true)
    @test NE(G) == 10 * 10

    G = RandomTournament(10)
    @test NE(G) == 45
end

@testset "Directed Distance" begin
    G = DirectedCycle(10)
    @test diam(G) == 9
end

@testset "Directed Euler" begin
    G = TorusDigraph(4, 4)
    P = euler(G)
    @test length(P) == NE(G) + 1
end

@testset "Directed Hamiltonian Cycle" begin
    G = TorusDigraph(4, 4)
    P = hamiltonian_cycle(G)
    @test length(P) == NV(G)
end

@testset "Strong Connectivity" begin
    G = TorusDigraph(5, 5)
    @test is_strongly_connected(G)
end

@testset "Directed Matrices" begin
    G = RandomTournament(10)
    A = adjacency(G)
    B = A + A'
    @test B == adjacency(Complete(10))

    G = RandomTournament(10)
    M = incidence(G)
    @test M * M' == laplace(Complete(10))
end

@testset "Hypergraphs" begin
    H = IntHypergraph(2)

    add!(H, [1, 2, 3])
    add!(H, [3, 4])

    @test eltype(H) == Int

    @test sort(vlist(H)) == [1, 2, 3, 4]
    @test length(elist(H)) == 2

    @test NV(H) == 4
    @test NE(H) == 2
    @test has(H, 3)
    @test has(H, [2, 3, 1])

    G = SimpleGraph(H)
    @test is_connected(G)

    @test deg(H, 3) == 2

    add!(H, [4, 5])
    @test delete!(H, 4)
    @test NV(H) == 4
    @test NE(H) == 1

    add!(H, 4, 5, 6)
    @test is_uniform(H)

    G = Cycle(8)
    H = SimpleHypergraph(G)
    @test is_uniform(H)
    @test G.V == H.V
    @test NE(H) == NE(G)

    A = rand(6, 10) .> 0.5
    H = SimpleHypergraph(A)
    B = Float64.(A)
    K = SimpleHypergraph(B)
    @test H == K

    H = CompleteHypergraph(6, 3)
    @test NE(H) == 20

end

@testset "Rotation Systems" begin
    G = Grid(3, 3)
    @test euler_char(G) == 2
    G = Cycle(5)
    set_rot(G)
    @test euler_char(G) == 2
    G = Dodecahedron()
    @test euler_char(G) == 2
    H = dual(G)
    @test char_poly(Icosahedron()) == char_poly(H)
    @test euler_char(H) == 2
end


@testset "Assign bin labels" begin
    D = 3
    E = cembed([rand(30) for i = 1:D])
    npts = size(E.points, 2)

    @testset "ϵ is an Int" begin
        labels = assign_bin_labels(E, 10)
        @test size(labels, 1) == D
        @test size(labels, 2) == npts
    end

    @testset "ϵ is a Float64" begin
        labels = assign_bin_labels(E, 0.2)
        @test size(labels, 1) == D
        @test size(labels, 2) == npts
    end

    @testset "ϵ is a Vector{Float64}" begin
        labels = assign_bin_labels(E, [0.2, 0.3, 0.1])
        @test size(labels, 1) == D
        @test size(labels, 2) == npts
    end

    @testset "ϵ is a Vector{Int}" begin
        labels = assign_bin_labels(E, [3, 4, 2])
        @test size(labels, 1) == D
        @test size(labels, 2) == npts
    end
end

@testset "Marginal visitation frequency" begin
    o = rand(100, 3)
    x, y = o[:, 1], o[:, 2]
    E = cembed([x, y], [2, 2, 1], [1, 0, 0])
    n_bins = [4, 3, 4]
    # Which bins get visited by every point of the orbit?
    visited_bins_inds = assign_bin_labels(E, n_bins)
    along_which_axes = [1, 2]
    m = marginal_visitation_freq(along_which_axes, visited_bins_inds)

    @test sum(m) ≈ 1
end

# @testset "Simplex triangulation" begin
#     n_pts = 30
# 	@testset "Triangulation" begin
#         E_3D = cembed([rand(n_pts) for i = 1:3])
#         E_4D = cembed([rand(n_pts) for i = 1:4])
#
#         T_3D = triangulate(E_3D)
#         @test typeof(T_3D) <: Partitioning.AbstractTriangulation
#
#         T_4D = triangulate(E_4D)
#         @test typeof(T_4D) <: Partitioning.AbstractTriangulation
#     end
#
#     @testset "LinearlyInvariantTriangulation" begin
#         E_3D = invariantize(cembed([rand(n_pts) for i = 1:3]))
#         E_4D = invariantize(cembed([rand(n_pts) for i = 1:4]))
#
#         T_3D = triangulate(E_3D)
#         @test typeof(T_3D) == Partitioning.LinearlyInvariantTriangulation
#
#         T_4D = triangulate(E_4D)
#         @test typeof(T_4D) == Partitioning.LinearlyInvariantTriangulation
#
#     end
# end


using FFTW
using MAT
using Random
using Printf
using StaticArrays

include("models.jl")

"""
Generate magnetic susceptibility maps and the corresponding fields due to
randomly placed spheres and randomly oriented infinite cylinders subject to a
static B-field inside a 3d rectangular volume.

Method
------
    1. Pick array size, voxel size for grid (N/n grid)
    2. for n iterations
        i.   pick number of tries, ntry, to check for valid config
        ii.  randomly place sources inside grid until no valid config can be
             found ntry times in a row
        iii. valid config means no overlapping structures. concentric allowed.
"""
function generate(
    sz::NTuple{N, Integer},
    vsz::NTuple{3, Real};
    outpath::Union{Nothing, AbstractString} = nothing,
    nsources::Integer = typemax(Int)-1,
    density::Real = 0.51,
    bdir::NTuple{3, Real} = (0, 0, 1),
    χμ::Float64 = 1e-6,
    χσ::Float64 = 0.07,
    seed::Int = 12345,
) where {N}
    @assert nsources > 0
    @assert density > 0 && density < 1
    Random.seed!(seed)

    if !(norm(bdir) ≈ 1)
        @warn "B direction vector is not normalized. Normalizing..."
        bdir = bdir ./ norm(bdir)
    end

    x = zeros(sz)
    f = zeros(sz)
    m = Array{Bool}(falses(sz))

    rmax = cld(minimum(sz), 5)
    rmax = 7
    Cs = ntuple(i -> rmax+1:sz[i]-(rmax+1)-1, Val(3))
    Cs = ntuple(i -> 10*rmax+1:sz[i]-(10*rmax+1)-1, Val(3))

    ns = 0
    while true
        model = rand((:sphere, :cylinder))
        model = :sphere

        r = rand(3:rmax)
        c = rand.(Cs)
        χ = χμ + χσ*randn()

        if model == :sphere
            x, f, m = sphere!(x, f, m, c, r, χ, bdir, vsz)
            ns += 1

        elseif model == :cylinder
            θ = π*rand()
            ϕ = 2π*rand()

            x, f, m = cylinder!(x, f, m, c, r, θ, ϕ, χ, bdir, vsz)
            ns += 1
        end

        if sum(m)/length(m) > density || ns >= nsources
            break
        end
    end

    if outpath !== nothing
        matwrite(outpath, Dict(:x => x, :f => f, :m => m), compress=true)
    end

    return x, f, m
end


#=

  Test of of global constraints atmost, atleast, and exactly in Julia ConstraintsSolver.jl

  This Picat model was created by Hakan Kjellerstrand, hakank@gmail.com
  See also my Julia page: http://www.hakank.org/julia/

=#
using ConstraintSolver, JuMP
using Cbc, GLPK, Ipopt
const CS = ConstraintSolver
include("constraints_utils.jl")

# The constraints atmost, atleast, and exactly
# are defined in constraints_utils.jl

function atmost_test(n=4,print_solutions=true,all_solutions=true)

    cbc_optimizer = optimizer_with_attributes(Cbc.Optimizer, "logLevel" => 0)
    glpk_optimizer = optimizer_with_attributes(GLPK.Optimizer)
    ipopt_optimizer = optimizer_with_attributes(Ipopt.Optimizer)

    model = Model(optimizer_with_attributes(CS.Optimizer,   "all_solutions"=> all_solutions,
                                                            # "all_optimal_solutions"=>true,
                                                            "logging"=>[],

                                                            "traverse_strategy"=>:BFS,
                                                            # "traverse_strategy"=>:DFS,
                                                            # "traverse_strategy"=>:DBFS,

                                                            # "branch_split"=>:Smallest,
                                                            # "branch_split"=>:Biggest,
                                                            "branch_split"=>:InHalf,

                                                            # https://wikunia.github.io/ConstraintSolver.jl/stable/options/#branch_strategy-(:Auto)
                                                            # "branch_strategy" => :IMPS, # default
                                                            "branch_strategy" => :ABS, # Activity Based Search
                                                            "activity.decay" => 0.999, # default 0.999
                                                            "activity.max_probes" => 100, # default, 10
                                                            "activity.max_confidence_deviation" => 20, # default 20

                                                            # "simplify"=>false,
                                                            # "simplify"=>true, # default

                                                            "time_limit"=>6,

                                                            # "backtrack" => false, # default true
                                                            # "backtrack_sorting" => false, # default true

                                                            # "lp_optimizer" => cbc_optimizer,
                                                            # "lp_optimizer" => glpk_optimizer,
                                                            # "lp_optimizer" => ipopt_optimizer,
                                        ))

    @variable(model, 1 <= x[1:n] <= n, Int)

    # Here we require that the must be
    #  - max 2 1s
    #  - min 2 2s
    #  - exactly 1 3
    atmost(model,x,1,2)
    atmost(model,x,n,1)
    atleast(model,x,2,2)
    exactly(model,x,3,1)

    # Solve the problem
    optimize!(model)

    status = JuMP.termination_status(model)
    # println("status:$status")
    if status == MOI.OPTIMAL
        num_sols = MOI.get(model, MOI.ResultCount())
        println("num_sols:$num_sols\n")
        if print_solutions
            for sol in 1:num_sols
                # println("solution #$sol")
                xx = convert.(Integer,JuMP.value.(x; result=sol))
                println("x:$xx")

            end
        end
    else
        println("status:$status")
    end

    return status
end

@time atmost_test(5)


#' # Tools - Using arrays to store data
#'
#' ### Introduction to arrays
#'
#' **Arrays are collections of boxes that we can use to store data.**
#' In the last notebook, we saw an image that can help us to picture a 1-dimensional array:
#'
#' ![Drawing](data/array_cartoon_s.png)
#'
#' *Why do we want an object like this to store our data?*
#'
#' An alternative to using an array in some contexts would be to name every individual piece of data, as follows:
# Assign to variable
a = 1.1;
b = 2.2;
c = 3.3;
# - Avoid printing 3.3
#' We can visualize how this data is stored:
#'
#' ![No Array](data/without_arrays_s.png)
#'
#' This is like having a separate box for every piece of data, rather than a series of connected boxes for all our data.
#'
#' The more data we have, the more annoying it becomes to keep track of all these boxes and their names.
#'Furthermore, if we want to do the same thing with many pieces of data, it's much easier to put all of these pieces of data in one place to work with them at once.
#'
#' For example, we may want to multiply `a`, `b`, and `c` by `2`. We could multiply three times:
# Assign to individual variables
a * 2
b * 2
c * 2
# -
#'
#' Or, instead, we could create one array (let's call it `numbers`) and multiply
#' that array by `2`:
#' ```julia
#' numbers * 2
#' ```
#'
#' The syntax for creating this array, `numbers`, is
#'
numbers = [a, b, c]
#'
#' Or, we could have just written
#'
numbers = [1.1, 2.2, 3.3]
# Multiple the whole array by 2
numbers*2
#'
#' It's worth noting that in Julia, 1-dimensional arrays are also called "vectors".

#' ### Creating arrays
#'
#' In the last section, we saw that we could create the array `numbers` by
#' typing our elements, `a`, `b`, and `c` (or `1.1`, `2.2`, and `3.3`),
#' inside square brackets.
#' #### Exercise 1
#' Create an array called `first_array` that stores the numbers 10 through 20.
first_array = collect(10.0:20.0)


# coding: utf-8

# In[1]:

#bitonic sort is my special feature in this repository!!!
#bitonic sort is cool as well as difficult to understand
#if you look for detailed explanation, you should check out geeksforgeeks
# https://www.geeksforgeeks.org/bitonic-sort/
#its python version is written in recursion
#which i found it really difficult to understand what the heck it was doing
#so i spent the whole day scratching my head to finish this non-recursive version
#a more straight forward explanation is the visualization from wikipedia
# https://upload.wikimedia.org/wikipedia/commons/thumb/b/bd/BitonicSort1.svg/843px-BitonicSort1.svg.png

#enjoy~~~

# In[2]:

function bitonic_sort(arr)

    if length(arr)==1

        return arr

    end

    i=1

    #primary iteration
    while 2^(i-1)<length(arr)/2+1

        j=2^i

        direction=get_direction(j,length(arr))

        #secondary iteration
        while j>=2

            #double loop helps us to find out who is comparing with who for each round
            #the sorting order is given by another function called get_direction
            for k in 1:j:length(arr)

                #k+Int32(j/2)-1 is crucial
                #make sure it doesnt go out of bound
                for l in k:k+Int32(j/2)-1

                    if (arr[l]>arr[l+Int32(j/2)]) && (direction[l]==1)

                        arr[l],arr[l+Int32(j/2)]=arr[l+Int32(j/2)],arr[l]

                    end

                    if (arr[l]<arr[l+Int32(j/2)]) && (direction[l]==-1)

                        arr[l],arr[l+Int32(j/2)]=arr[l+Int32(j/2)],arr[l]

                    end

               end

            end

            #Int32 is important
            #list indices dont support float
            #when u do /, we get float by default
            j=Int32(j/2)

        end

        i+=1

    end

    return arr

end

# In[3]:

#the annoying part of bitonic sort is that
#some parts of the sequence are sorted by descending order
#other parts of the sequence are sorted by ascending order
#for each primary iteration, we create a sorting order sequence for each element
#for each secondary iteration inside a primary iteration
#the sorting order for each element is the same
function get_direction(iteration_number,len)

    direction=[0 for _ in 1:len]

    ind=[i for i in 1:iteration_number:len]

    for i in ind[1:2:length(ind)]

        direction[i]=1

    end

    for i in ind[2:2:length(ind)]

        direction[i]=-1

    end

    for i in 1:length(direction)

        if direction[i]==0

            direction[i]=direction[i-1]

        end

    end

    return direction

end

# In[4]:

#bitonic sort only works on a list with 2^n elements
for _ in 1:100

    test_arr=rand(2^rand(1:10))

    if !(bitonic_sort(test_arr)==sort(test_arr))
        printstyled("Erreur",color=:red)
    end

end


function jump_mimo(solver, seed, n; verbose = false, test = false)

    # n = 3
    m = 10n
    s, H, y, L = mimo_data(seed, m, n)

    nvars = sympackedlen(n + 1)

    model = Model(with_optimizer(solver))
    @variable(model, X[1:n+1, 1:n+1], PSD)
    for j in 1:(n+1), i in j:(n+1)
        @constraint(model, X[i, j] <=  1.0)
        @constraint(model, X[i, j] >= -1.0)
    end
    @objective(model, Min, sum(L[i, j] * X[i, j] for j in 1:n+1, i in 1:n+1))
    @constraint(model, ctr[i in 1:n+1], X[i, i] == 1.0)

    teste = @time optimize!(model)

    XX = value.(X)

    if test
        for j in 1:n+1, i in 1:n+1
            @test 1.01 > abs(XX[i,j]) > 0.99
        end
    end

    verbose && mimo_eval(s,H,y,L,XX)

    objval = objective_value(model)
    stime = MOI.get(model, MOI.SolveTime())

    # @show tp = typeof(model.moi_backend.optimizer.model.optimizer)
    # @show fieldnames(tp)
    rank = -1
    try
        @show rank = model.moi_backend.optimizer.model.optimizer.sol.final_rank
    catch
    end
    status = 0
    if JuMP.termination_status(model) == MOI.OPTIMAL
        status = 1
    end
    return (objval, stime, rank, status)
    # return (objval, stime)
end


using Gadfly
using MLLibrary
using Colors
path =Pkg.dir("MLLibrary","resources","datasets")
week3=joinpath("Week3")
# resources = #[["res"=>"regression_test.csv","path"=>joinpath(path,week3),"proxy"=>[]]]
resources = [["res"=>"regression-0.05.csv","path"=>joinpath(path,week3),"proxy"=>[]],["res"=>"regression-A.csv","path"=>joinpath(path,week3),"proxy"=>[]],["res"=>"regression-B.csv","path"=>joinpath(path,week3),"proxy"=>[]],["res"=>"regression-C.csv","path"=>joinpath(path,week3),"proxy"=>[]],["res"=>"abalone.data","path"=>joinpath(path), "proxy"=>[Dict("column"=>1,"proxy"=>["M" "F" "I"])]]]
# [["res"=>"abalone.data","path"=>joinpath(path), "proxy"=>[Dict("column"=>1,"proxy"=>["M" "F" "I"])]]]

errors = Dict()
for res in resources
  seed=123
  training, test = MLLibrary.DataSampling.ThresholdSampling.getSets(joinpath(res["path"],res["res"]),",",.1,seed)

  proxy = res["proxy"]

  training=[MLLibrary.DataManipulation.DataTransform.addProxyColumns(training[:,1:end-1],proxy) training[:,end]]

  test=[MLLibrary.DataManipulation.DataTransform.addProxyColumns(test[:,1:end-1],proxy) test[:,end]]

# println(training)
# println(test)

  training = MLLibrary.DataManipulation.DataTransform.convertToFloat(training)
  test= MLLibrary.DataManipulation.DataTransform.convertToFloat(test)
  println("Data is ready")
  println(typeof(training))

  betacol = MLLibrary.LearningAlgorithms.LinearRegression.linearRegression(training[:,1:end-1],training[:,end],MLLibrary.LinearAlgebra.HouseHolders.householdDecompose)

  println("finished dcomposing")
  test_y = test[:,end]
  test = test[:,1:end-1]

  observed = test*betacol

  #println((observed),(test_y))

  rms = round(MLLibrary.Utils.ErrorAnalysis.calculateRootMeanStandardError(observed,test_y),2)
  errors[res["res"]]=rms
end

println(errors)

p = plot(

  Guide.ylabel("Root Mean Square Error"),
  Guide.xlabel("Data Set"),
  Guide.title("Root Mean Square Errors"),
  x=collect(keys(errors)),
  y=collect(values(errors)),
  Geom.bar,
  Geom.label(position=:above),
  label=map(string,(values(errors)))
  #color="DataSet"
)
# index = 1
# color_keys = []
# colors = distinguishable_colors(length(errors))
#
# for (k,v) in errors
#   a = layer(
#     x=k,
#     y=v,
#     Geom.histogram,
#     Theme(default_color=(colors[index]))
#     #Guide.colorkey(k)
#   )
#   push!(color_keys,k)
#
#   index+=1
#   push!(p,a)
# end
#
# push!(p,  Guide.manual_color_key("Legend", color_keys, colors))

draw(PNG(joinpath(Pkg.dir("MLLibrary"),"resources","paResources","Week3","RMSEErrors.png"), 800px, 600px), p)


using ProgressMeter

ftc = Dict()
ft = Dict()
fc = Dict()
N = 0

open("82.jl.out") do f
    @showprogress 1 "ahaha" for line in readlines(f)
        global N, ftc,ft,fc
        words = filter(x -> !isempty(x), lowercase.(strip.(split(line))))
        words = filter(x -> x != "n/a", words)
        word, context = words[1], words[2:end]

        # f(t,c)
        for c in context
            t = (word, c)
            if t in keys(ftc)
                ftc[t] += 1
            else
                ftc[t] = 1
            end
        end

        # f(t,*)
        if word in keys(ft)
            ft[word] += 1
        else
            ft[word] = 1
        end

        # f(*.c)
        for c in context
            if c in keys(fc)
                fc[c] += 1
            else
                fc[c] = 1
            end
        end

        N += 1
    end
end

vocab = unique(union(map(x->x[1], keys(ftc)),map(x->x[2], keys(ftc))))
vocab2idx = Dict()
idx2vocab = Dict()
for (idx,word) in enumerate(vocab)
    vocab2idx[word] = idx
    idx2vocab[idx] = word
end
# indices = collect(1:length(vocab))

Nv = length(vocab)

X = spzeros(Nv,Nv)
for ((t,c), v) in ftc
    tidx, cidx = vocab2idx[t],vocab2idx[c]
    if v >= 10
        vv = max(log(N*v/(ft[t]*fc[c])), 0.0)
        # [n_samples x n_features]
        # this is important to find meanings in svd results
        X[tidx,cidx] = vv
    end
end

@show size(X)

# λ, ϕ = eigs(X, nev=300)
# @show size(ϕ)
@time S = svds(X, nsv=300)[1]

X_transformed = S[:U] * diagm(S[:S])

using JLD

jldopen("85.jld", "w") do file
    write(file, "X", X_transformed)
    write(file, "vocab", vocab)
    write(file, "vocab2idx", vocab2idx)
    write(file, "idx2vocab", idx2vocab)
end


"""
Created on Fri 15 May 2020

@author: Yoann Pradat

Functions for simulation different matrices for the factorization V =  WH + E
"""

using Combinatorics
using LinearAlgebra

function scale_col(X; p=1)
    norms_X = mapslices(x -> norm(x, p), X, dims=1)
    X .* repeat(norms_X .^ -1, size(X, 1), 1)
end

function random_fixed_total(n::Integer, total::Integer, rng::AbstractRNG)
    μ = total÷n
    T = zeros(Integer, n)
    T[1] = total

    while sum(T[1:n-1]) >= total
        T[1:n-1] .= rand(rng, 1:2μ,  n-1)
    end

    T[n] = total - sum(T[1:n-1])

    T
end

"""
    simulate_X_clustered_a(K, N, n_clusters, dist_X, rng)

Exaclty one cluster of rows is active in one cluster of columns. Number of clusters of rows is equal to the number of
clusters of columns.
"""
function simulate_X_clustered_a(K::Integer, N::Integer, n_clusters::Integer, dist_X::Distribution, rng::AbstractRNG)
    X = zeros(Float64, K,N)

    lims_K = cumsum(random_fixed_total(n_clusters, K, rng))
    lims_N = cumsum(random_fixed_total(n_clusters, N, rng))
    pushfirst!(lims_K, 0)
    pushfirst!(lims_N, 0)

    for i = 1:n_clusters
        range_K = (lims_K[i]+1):lims_K[i+1]
        range_N = (lims_N[i]+1):lims_N[i+1]
        X[range_K, range_N] .= rand(rng, dist_X, length(range_K), length(range_N))
    end

    X, lims_K, lims_N, Matrix{Int}(I,n_clusters, n_clusters)
end

# """
#     simulate_X_clustered_b(K, N, n_clusters, dist_X, rng)
#
# One or more clusters of rows are active in one cluster of columns. The number of clusters of rows is equal to the number
# of clusters of columns.
# """
# function simulate_X_clustered_b(K::Integer, N::Integer, n_clusters::Integer, dist_X::Distribution, rng::AbstractRNG)
#     X = zeros(Float64, K,N)
#
#     lims_K = cumsum(random_fixed_total(n_clusters, K, rng))
#     lims_N = cumsum(random_fixed_total(n_clusters, N, rng))
#     pushfirst!(lims_K, 0)
#     pushfirst!(lims_N, 0)
#
#     active_clusters = zeros(Int, n_clusters, n_clusters)
#     combinations_K  = collect(combinations(1:n_clusters))
#     randperm_comb_K = randperm(rng, 2^n_clusters-1)
#     for j = 1:n_clusters
#         active_clusters[combinations_K[randperm_comb_K[j]], j] .= 1
#     end
#
#     for j = 1:n_clusters
#         range_N = (lims_N[j]+1):lims_N[j+1]
#         for i in 1:n_clusters
#             range_K = (lims_K[i]+1):lims_K[i+1]
#             if active_clusters[i,j] == 1
#                 X[range_K, range_N] .= rand(rng, dist_X, length(range_K), length(range_N))
#             end
#         end
#     end
#
#     X, lims_K, lims_N, active_clusters
# end

"""
    simulate_X_clustered_b(K, N, n_clusters_K, n_clusters_N, dist_X, rng)

One or more clusters of rows are active in one cluster of columns. Number of clusters of rows is inferior or equal to
the number of clusters of columns.
"""
function simulate_X_clustered_b(K::Integer, N::Integer, n_clusters_K::Integer, n_clusters_N::Integer, dist_X::Distribution, rng::AbstractRNG)
    X = zeros(Float64, K, N)

    n_clusters_K <= n_clusters_N || throw(ArgumentError("Please choose n_clusters_K <= n_clusters_N"))

    lims_K = cumsum(random_fixed_total(n_clusters_K, K, rng))
    lims_N = cumsum(random_fixed_total(n_clusters_N, N, rng))
    pushfirst!(lims_K, 0)
    pushfirst!(lims_N, 0)

    active_clusters = zeros(Int, n_clusters_K, n_clusters_N)
    combinations_K  = collect(combinations(1:n_clusters_K))
    randperm_comb_K = randperm(rng, 2^n_clusters_K-1)
    for j = 1:n_clusters_N
        active_clusters[combinations_K[randperm_comb_K[j]], j] .= 1
    end

    for j = 1:n_clusters_N
        range_N = (lims_N[j]+1):lims_N[j+1]
        for i in 1:n_clusters_K
            range_K = (lims_K[i]+1):lims_K[i+1]
            if active_clusters[i,j] == 1
                X[range_K, range_N] .= rand(rng, dist_X, length(range_K), length(range_N))
            end
        end
    end

    X, lims_K, lims_N, active_clusters
end

"""
    simulate_WH_clustered(; F, N, K, n_clusters_K, n_clusters_N, dist_W, dist_H, H_cluster_mode, rng)

Simulate W and H with clustering on H.
"""
function simulate_WH_clustered(;F::Integer, N::Integer, K::Integer, n_clusters_K::Integer=0, n_clusters_N::Integer, dist_W::Distribution, dist_H::Distribution, H_cluster_mode::Symbol, rng::AbstractRNG)
    K = K

    W = rand(rng, dist_W, F, K)
    W = scale_col(W, p=1)

    if H_cluster_mode == :a
        H, lims_K, lims_N, active_clusters = simulate_X_clustered_a(K, N, n_clusters_N, dist_H, rng)
    elseif H_cluster_mode == :b
        H, lims_K, lims_N, active_clusters = simulate_X_clustered_c(K, N, n_clusters_K, n_clusters_N, dist_H, rng)
    end

    W, H, lims_K, lims_N, active_clusters
end

"""
    simulate_WH(F, N; n_factors, dist_W, dist_H, rng)

Simulate W and H.
"""
function simulate_WH(;F::Integer, N::Integer, n_factors::Integer, dist_W::Distribution, dist_H::Distribution, rng::AbstractRNG)
    K = n_factors

    W = rand(rng, dist_W, F, K)
    W = scale_col(W, p=1)

    H = rand(rng, dist_H, K, N)

    W, H
end


# empirical cumulative distribution function (ECDF)
using CairoMakie

f = Figure()
Axis(f[1, 1])

x = randn(200)
ecdfplot!(x, color = (:blue, 0.3))
ecdfplot!(x, color = :red, npoints=10)


"""
    Ground(;name)

Ground of an electrical circuit. The potential at the ground node is zero. Every circuit must have one ground node.
"""
function Ground(;name)
    @named g = Pin()
    eqs = [g.v ~ 0]
    ODESystem(eqs, t, [], []; systems=[g], name=name)
end

"""
    Resistor(;name, R=1.0)

Ideal linear electrical resistor.

# Parameters:
- `R`: [Ω] Resistance
"""
function Resistor(;name, R=1.0)
    @named oneport = OnePort()
    @unpack v, i = oneport
    pars = @parameters R=R
    eqs = [
        v ~ i * R
    ]
    extend(ODESystem(eqs, t, [], pars; name=name), oneport)
end

"""
    Capacitor(;name, C=1.0, v_start=0.0)

Ideal linear electrical capacitor.

# Parameters:
- `C`: [F] Capacity
- `v_start`: [V] Initial voltage of capacitor
"""
function Capacitor(;name, C=1.0, v_start=0.0)
    @named oneport = OnePort(;v_start=v_start)
    @unpack v, i = oneport
    pars = @parameters C=C
    eqs = [
        D(v) ~ i / C
    ]
    extend(ODESystem(eqs, t, [], pars; name=name), oneport)
end

"""
    Inductor(;name, L=1.0e-6, i_start=0.0)

Ideal linear electrical inductor.

# Parameters:
- `L`: [H] Inductance
- `i_start`: [A] Initial current through inductor
"""
function Inductor(;name, L=1.0e-6, i_start=0.0)
    @named oneport = OnePort(;i_start=i_start)
    @unpack v, i = oneport
    pars = @parameters L=L
    eqs = [
        D(i) ~ 1 / L * v
    ]
    extend(ODESystem(eqs, t, [], pars; name=name), oneport)
end

"""
    IdealOpAmp(;name)

Ideal operational amplifier (norator-nullator pair).
The ideal OpAmp is a two-port. The left port is fixed to v1=0 and i1=0 (nullator).
At the right port both any voltage v2 and any current i2 are possible (norator).
"""
function IdealOpAmp(;name)
    @named p1 = Pin()
    @named p2 = Pin()
    @named n1 = Pin()
    @named n2 = Pin()
    sts = @variables begin
        v1(t)
        v2(t)
        i1(t)
        i2(t)
    end
    eqs = [
        v1 ~ p1.v - n1.v
        v2 ~ p2.v - n2.v
        0 ~ p1.i + n1.i
        0 ~ p2.i + n2.i
        i1 ~ p1.i
        i2 ~ p2.i
        v1 ~ 0
        i1 ~ 0
    ]
    ODESystem(eqs, t, sts, [], systems=[p1, p2, n1, n2], name=name)
end


function parstring(n::Int)
    if n < 0
        return string([')' for i=1:abs(n)]...)
    elseif n > 0
        return string(['(' for i=1:n]...)
    end
    ""
end

function nestingdepth(nums::Vector{Int})
    ans = Vector{String}()
    prev = 0
    for i in 1:length(nums)
        push!(ans, parstring(nums[i] - prev) * string(nums[i]))
        prev = nums[i]
    end
    push!(ans, parstring(-prev))
    reduce(*, ans)
end

function solution()
    n_cases = parse(Int, readline())
    for x in 1:n_cases
        nums = [parse(Int, i) for i in readline()]
        println( "Case #$x: $(nestingdepth(nums))")
    end
end

solution()


module TestStationary

using Test
using LinearAlgebra
# using LinearAlgebraExtensions: LowRank

using CovarianceFunctions
using CovarianceFunctions: MercerKernel, StationaryKernel, isstationary, isisotropic,
                    Constant, EQ, RQ, Exp, γExp, Delta, Cosine, MaternP, Matern#, SM
using CovarianceFunctions: iscov, enorm, Normed
using ForwardDiff: derivative

# isotropic kernels
const iso_k_strings = ["Exponentiated Quadratic", "Exponential", "δ",
            "Constant", "Rational Quadratic",
            "γ-Exponential", "Matern"]
# all stationary kernels
const k_strings = vcat(iso_k_strings, "Cosine")
                #, "Spectral Mixture"]

r = 2*rand()
const iso_k_arr = [EQ(), Exp(), Delta(),
        Constant(r), RQ(r),
        γExp(r), Matern(r)]
const k_arr = vcat(iso_k_arr, Cosine(r))
        #, SM]

const tol = 1e-12

# TODO:
# test higher input dimensions
# test type stability

@testset "basic properties" begin
    T = Float64
    n = 16
    x = randn(T, n) # IDEA: include higher-dimensional test cases
    Σ = zeros(T, (n, n))
    r² = sum(abs2, x[1] - x[2])
    for k in iso_k_arr
        @test k(x[1], x[2]) ≈ k(r²)
    end
    for (k, k_str) in zip(k_arr, k_strings)
        @testset "$k_str" begin
            Σ .= k.(x, permutedims(x))
            @test iscov(Σ, tol)
            @test isstationary(k, x)
        end
    end

    @testset "Matern" begin
        # testing different inputs
        for p = 0:8
            k = MaternP(p)
            Σ .= k.(x, permutedims(x))
            @test iscov(Σ, tol)
            @test isstationary(k, x)
        end

        # testing against control implementation
        # r² = x.^2
        r² = 10.0.^(1:16) * eps()
        p = 0
        k = MaternP(p)
        @test k.(r²) ≈ MaternP.(r², p)
        for p = 2:3
            k = MaternP(p)
            @test k.(0) ≈ MaternP.(0, p) # evaluation at zero
            @test k.(r²) ≈ MaternP.(r², p) # for small numbers up to O(1)
            # testing differentiability
            # k_naive = z->MaternP(z, p)
            k_naive = z->Matern(z, p + 1/2)
            dk_taylor = derivative.((k,), r²)
            dk_naive = derivative.((k_naive,), r²)
            #
            @test isapprox(dk_taylor, dk_naive, atol = 1e-6)

            # second order problem: naïve implementation is not as accurate anymore
            dk_taylor = derivative.((z->derivative(k, z),), r²)
            dk_naive = derivative.((z->derivative(k_naive, z),), r²)
            @test isapprox(dk_taylor, dk_naive, atol = 1e-5)
        end

        # testing constructor for invalid inputs
        @test_throws DomainError MaternP(-1)

        # can be constructed via Matern constructor with appropriate ν
        # we might not want to do that if we want to optimize ν
        for p = 0:4
            @test MaternP(Matern(p+1/2)) isa MaternP
        end

        # k = Matern(1.5)
        # @test derivative(k, 0) ≈ ... # this needs to be fixed with taylor expansion
    end

end

@testset "multidimensional inputs" begin
    n = 16
    Σ = zeros(n, n)
    for (k, k_str) in zip(k_arr, k_strings)
        if typeof(k) <: CovarianceFunctions.IsotropicKernel
            @testset "$k_str" begin
                for d = 2:3
                    x = [randn(d) for _ in 1:n]
                    K = k.(x, permutedims(x))
                    Σ = k.(x, permutedims(x))
                    @test iscov(Σ, tol)
                    @test isstationary(k, x)
                end
            end
        end
    end
end

@testset "kernel modifications" begin

    using CovarianceFunctions: Lengthscale
    l = exp(randn())
    for d = 1:3
        r = randn(d)
        for (k, k_str) in zip(iso_k_arr, iso_k_strings)
            kl = Lengthscale(k, l)
            @test kl(r) ≈ k(sum(abs2, r)/l^2)
            @test typeof(kl) <: CovarianceFunctions.IsotropicKernel
            @test isstationary(kl)
        end
    end

    ########### Testing ARD
    d = 2
    k = CovarianceFunctions.EQ()
    l = ones(d)
    kl = CovarianceFunctions.ARD(k, l)
    x = randn(d)
    y = randn(d)

    @test kl(x, y) ≈ k(x, y)

    @test sum(abs2, x-y) ≈ kl.n²(x-y) # this tests Metrics
    c = 2
    l = c^2*l
    kl = CovarianceFunctions.ARD(k, l)
    @test 1/c^2*sum(abs2, x-y) ≈ kl.n²(x-y)

    l = exp.(randn(d))
    w = @. sqrt(1/l)
    kl = CovarianceFunctions.ARD(k, l)
    @test sum(abs2, (x-y).*w) ≈ kl.n²(x-y)
    xl = x .* w
    yl = y .* w
    @test kl(x, y) ≈ k(xl, yl)

    d = 3
    U = randn(1, d)
    n²(x) = CovarianceFunctions.enorm2(U'U, x)
    kl = CovarianceFunctions.Normed(k, n²)
    x = randn(d)
    y = randn(d)
    @test kl(x, y) ≈ k(U*x, U*y)
    @test kl(x, y) ≈ k((x-y)'*(U'U)*(x-y))

    S = U'U # LowRank(U')
    kSLR = CovarianceFunctions.Energetic(k, S)
    @test kSLR(x, y) ≈ kl(x, y)

    # periodic CovarianceFunctions
    k = CovarianceFunctions.EQ()
    p = CovarianceFunctions.Periodic(k)
    x, y = randn(2)
    @test p(x, y) isa Real
    @test p(1+x) ≈ p(x) # 1 periodic
    @test p(x, 1+y) ≈ p(x, y)  # 1 periodic
    @test p(x+1, y-1) ≈ p(x, y)  # 1 periodic
    @test p(x+7, y-3) ≈ p(x, y)  # 1 periodic

    # TODO: write benchmarks
    # println(size(U*x))
    # println(size(x))
    # L = randn(d, 16)
    # @time k(L'x, L'y)
    # @time k(U*x, U*y)
    # @time kl(x, y)
    # using MyFactorizations: SymmetricLowRank
    # S = SymmetricLowRank(U)
    # kSLR = CovarianceFunctions.Subspace(k, S)

end # CovarianceFunctions modifications

end # TestStationary

# @testset "Rational Quadratic" begin
#     α = exp(randn())
#     k = RQ(α)
#     Σ .= k.(x, permutedims(x))
#     @test iscov(Σ)
#     @test isstationary(k, x)
#     # test constructor for negative input
# end
#
# @testset "Exponential" begin
#     k = Exp()
#     Σ .= k.(x, permutedims(x))
#     @test iscov(Σ)
#     @test isstationary(k, x)
# end
#
# @testset "γ-Exponential CovarianceFunctions" begin
#     γ = 2rand()
#     k = ΓExp(γ)
#     Σ .= k.(x, permutedims(x))
#     @test iscov(Σ)
#     @test isstationary(k, x)
#     # test constructor for γ outside of [0,2]
# end
#
# @testset "δ CovarianceFunctions" begin
#     k = Delta()
#     Σ .= k.(x, permutedims(x))
#     @test iscov(Σ)
#     @test isstationary(k, x)
# end


using StaticArrays
using Base: @_inline_meta
export reconstruct, DelayEmbedding, embed, τrange
export AbstractEmbedding

#####################################################################################
# Univariate Delay Coordinates
#####################################################################################
"Super-type of embedding methods."
abstract type AbstractEmbedding end

"""
    DelayEmbedding(γ, τ, h = nothing) → `embedding`
Return a delay coordinates embedding structure to be used as a function-like-object,
given a timeseries and some index. Calling
```julia
embedding(s, n)
```
will create the `n`-th delay vector of the embedded space, which has `γ`
temporal neighbors with delay(s) `τ`.
`γ` is the embedding dimension minus 1, `τ` is the delay time(s) while `h` are
extra weights, as in [`embed`](@ref) for more.

**Be very careful when choosing `n`, because `@inbounds` is used internally.**
Use [`τrange`](@ref)!
"""
struct DelayEmbedding{γ, H} <: AbstractEmbedding
    delays::SVector{γ, Int}
    h::SVector{γ, H}
end
@inline DelayEmbedding(γ, τ, h = nothing) = DelayEmbedding(Val{γ}(), τ, h)
@inline function DelayEmbedding(g::Val{γ}, τ::Int, h::H) where {γ, H}
    idxs = [k*τ for k in 1:γ]
    hs = hweights(g, h)
    htype = H <: Union{Nothing, Real} ? H : eltype(H)
    return DelayEmbedding{γ, htype}(SVector{γ, Int}(idxs...), hs)
end
@inline function DelayEmbedding(g::Val{γ}, τ::AbstractVector, h::H) where {γ, H}
    γ != length(τ) && throw(ArgumentError(
        "Delay time vector length must equal the embedding dimension minus 1."
    ))
    hs = hweights(g, h)
    htype = H <: Union{Nothing, Real} ? H : eltype(H)
    return DelayEmbedding{γ, htype}(SVector{γ, Int}(τ...), hs)
end
hweights(::Val{γ}, h::Nothing) where {γ} = SVector{γ, Nothing}(fill(nothing, γ))
hweights(::Val{γ}, h::T) where {γ, T<:Real} = SVector{γ, T}([h^b for b in 1:γ]...)
hweights(::Val{γ}, h::AbstractVector) where {γ} = SVector{γ}(h)

@generated function (r::DelayEmbedding{γ, Nothing})(s::AbstractArray{T}, i) where {γ, T}
    gens = [:(s[i + r.delays[$k]]) for k=1:γ]
    quote
        @_inline_meta
        @inbounds return SVector{$γ+1,T}(s[i], $(gens...))
    end
end

# This is the version with weights
@generated function (r::DelayEmbedding{γ})(s::AbstractArray{T}, i) where {γ, T, R<:Real}
    gens = [:(r.h[$k]*(s[i + r.delays[$k]])) for k=1:γ]
    quote
        @_inline_meta
        @inbounds return SVector{$γ+1,T}(s[i], $(gens...))
    end
end

"""
    embed(s, d, τ [, h])
Embed `s` using delay coordinates with embedding dimension `d` and delay time `τ`
and return the result as a [`Dataset`](@ref). Optionally use weight `h`, see below.

Here `τ > 0`, use [`genembed`](@ref) for a generalized version.

## Description
If `τ` is an integer, then the ``n``-th entry of the embedded space is
```math
(s(n), s(n+\\tau), s(n+2\\tau), \\dots, s(n+(d-1)\\tau))
```
If instead `τ` is a vector of integers, so that `length(τ) == d-1`,
then the ``n``-th entry is
```math
(s(n), s(n+\\tau[1]), s(n+\\tau[2]), \\dots, s(n+\\tau[d-1]))
```

The resulting set can have same
invariant quantities (like e.g. lyapunov exponents) with the original system
that the timeseries were recorded from, for proper `d` and `τ`.
This is known as the Takens embedding theorem [^Takens1981] [^Sauer1991].
The case of different delay times allows embedding systems with many time scales,
see[^Judd1998].

If provided, `h` can be weights to multiply the entries of the embedded space.
If `h isa Real` then the embedding is
```math
(s(n), h \\cdot s(n+\\tau), h^2 \\cdot s(n+2\\tau), \\dots,h^{d-1} \\cdot s(n+γ\\tau))
```
Otherwise `h` can be a vector of length `d-1`, which the decides the weights of each
entry directly.

## References
[^Takens1981] : F. Takens, *Detecting Strange Attractors in Turbulence — Dynamical
Systems and Turbulence*, Lecture Notes in Mathematics **366**, Springer (1981)

[^Sauer1991] : T. Sauer *et al.*, J. Stat. Phys. **65**, pp 579 (1991)

[^Judd1998]: K. Judd & A. Mees, [Physica D **120**, pp 273 (1998)](https://www.sciencedirect.com/science/article/pii/S0167278997001188)

[^Farmer1988]: Farmer & Sidorowich, [Exploiting Chaos to Predict the Future and Reduce Noise"](http://www.nzdl.org/gsdlmod?e=d-00000-00---off-0cltbibZz-e--00-1----0-10-0---0---0direct-10---4-------0-1l--11-en-50---20-home---00-3-1-00-0--4--0--0-0-11-10-0utfZz-8-00&a=d&cl=CL3.16&d=HASH013b29ffe107dba1e52f1a0c_1245)

"""
function embed(s::AbstractVector{T}, d, τ, h::H = nothing) where {T, H}
    if d == 1
        return Dataset(s)
    end
    htype = H <: Union{Nothing, Real} ? H : eltype(H)
    de::DelayEmbedding{d-1, htype} = DelayEmbedding(d-1, τ, h)
    return embed(s, de)
end

@inline function embed(s::AbstractVector{T}, de::DelayEmbedding{γ}) where {T, γ}
    r = τrange(s, de)
    data = Vector{SVector{γ+1, T}}(undef, length(r))
    @inbounds for i in r
        data[i] = de(s, i)
    end
    return Dataset{γ+1, T}(data)
end

"""
    τrange(s, de::AbstractEmbedding)
Return the range `r` of valid indices `n` to create delay vectors out of `s` using `de`.
"""
τrange(s, de::AbstractEmbedding) = 1:(length(s) - maximum(de.delays))

#####################################################################################
# Generalized embedding (arbitrary combination of timeseries and delays)
#####################################################################################
export GeneralizedEmbedding, genembed

"""
    GeneralizedEmbedding(τs, js = ones(length(τs)), ws = nothing) -> `embedding`
Return a delay coordinates embedding structure to be used as a function.
Given a timeseries *or* trajectory (i.e. `Dataset`) `s` and calling
```julia
embedding(s, n)
```
will create the delay vector of the `n`-th point of `s` in the embedded space using
generalized embedding (see [`genembed`](@ref)).

`js` is ignored for timeseries input `s` (since all entries of `js` must be `1` in
this case) and in addition `js` defaults to `(1, ..., 1)` for all `τ`.

**Be very careful when choosing `n`, because `@inbounds` is used internally.**
Use [`τrange`](@ref)!
"""
struct GeneralizedEmbedding{D, W} <: AbstractEmbedding
    τs::NTuple{D, Int}
    js::NTuple{D, Int}
    ws::NTuple{D, W}
end
function GeneralizedEmbedding(τs::NTuple{D, Int}) where {D} # type stable version
    GeneralizedEmbedding{D, Nothing}(
        τs, NTuple{D, Int}(ones(D)), NTuple{D, Nothing}(fill(nothing, D))
    )
end

function GeneralizedEmbedding(τs, js = ones(length(τs)), ws = nothing)
    D = length(τs)
    a = NTuple{D, Int}(τs)
    b = NTuple{D, Int}(js)
    W = isnothing(ws) ? Nothing : typeof(promote(ws...)[1])
    c = isnothing(ws) ? NTuple{D, W}(fill(nothing, D)) : NTuple{D, W}(promote(ws...))
    return GeneralizedEmbedding{D, W}(a, b, c)
end

function Base.show(io::IO, g::GeneralizedEmbedding{D, W}) where {D, W}
    print(io, "$D-dimensional generalized embedding\n")
    print(io, "  τs: $(g.τs)\n")
    print(io, "  js: $(g.js)\n")
    wp = W == Nothing ? "nothing" : g.ws
    print(io, "  ws: $(wp)")
end

const Data{T} = Union{Dataset{D, T}, AbstractVector{T}} where {D}

# dataset version
@generated function (g::GeneralizedEmbedding{D, W})(s::AbstractDataset{Z, T}, i::Int) where {D,W,Z,T}
    gens = if W == Nothing
        [:(s[i + g.τs[$k], g.js[$k]]) for k=1:D]
    else
        [:(g.ws[$k]*s[i + g.τs[$k], g.js[$k]]) for k=1:D]
    end
    X = W == Nothing ? T : promote_type(T, W)
    quote
        @_inline_meta
        @inbounds return SVector{$D,$X}($(gens...))
    end
end

# vector version
@generated function (g::GeneralizedEmbedding{D, W})(s::AbstractVector{T}, i::Int) where {D,W,T}
    gens = if W == Nothing
        [:(s[i + g.τs[$k]]) for k=1:D]
    else
        [:(g.ws[$k]*s[i + g.τs[$k]]) for k=1:D]
    end
    X = W == Nothing ? T : promote_type(T, W)
    quote
        @_inline_meta
        @inbounds return SVector{$D,$X}($(gens...))
    end
end

τrange(s, ge::GeneralizedEmbedding) =
max(1, (-minimum(ge.τs) + 1)):min(length(s), length(s) - maximum(ge.τs))

"""
    genembed(s, τs, js = ones(...); ws = nothing) → dataset
Create a generalized embedding of `s` which can be a timeseries or arbitrary `Dataset`,
and return the result as a new `Dataset`.

The generalized embedding works as follows:
- `τs` denotes what delay times will be used for each of the entries
  of the delay vector. It is recommended that `τs[1] = 0`.
  `τs` is allowed to have *negative entries* as well.
- `js` denotes which of the timeseries contained in `s`
  will be used for the entries of the delay vector. `js` can contain duplicate indices.
- `ws` are optional weights that weight each embedded entry (the i-th entry of the
    delay vector is weighted by `ws[i]`). If provided, it is recommended that `ws[1] == 1`.

`τs, js, ws` are tuples (or vectors) of length `D`, which also coincides with the embedding
dimension. For example, imagine input trajectory ``s = [x, y, z]`` where ``x, y, z`` are
timeseries (the columns of the `Dataset`).
If `js = (1, 3, 2)` and `τs = (0, 2, -7)` the created delay vector at
each step ``n`` will be
```math
(x(n), z(n+2), y(n-7))
```
Using `ws = (1, 0.5, 0.25)` as well would create
```math
(x(n), \\frac{1}{2} z(n+2), \\frac{1}{4} y(n-7))
```

`js` can be skipped, defaulting to index 1 (first timeseries) for all delay entries, while
it has no effect if `s` is a timeseries instead of a `Dataset`.

See also [`embed`](@ref). Internally uses [`GeneralizedEmbedding`](@ref).
"""
function genembed(s, τs, js = ones(length(τs)); ws = nothing)
    D = length(τs)
    W = isnothing(ws) ? Nothing : typeof(promote(ws...)[1])
    ge::GeneralizedEmbedding{D, W} = GeneralizedEmbedding(τs, js, ws)
    return genembed(s, ge)
end

function genembed(s, ge::GeneralizedEmbedding{D, W}) where {D, W}
    r = τrange(s, ge)
    T = eltype(s)
    X = W == Nothing ? T : promote_type(T, W)
    data = Vector{SVector{D, X}}(undef, length(r))
    @inbounds for (i, n) in enumerate(r)
        data[i] = ge(s, n)
    end
    return Dataset{D, X}(data)
end


"""

# `check_open`

$(SIGNATURES)

Internal.
"""
function check_open(d::DAG, path::Vector{Symbol}, conditioning_set::SymbolList; debug=false)
  res = :open
  path_closed = false
  if typeof(conditioning_set) == Symbol
    conditioning_set = [conditioning_set]
  end
  r = range(2, stop=length(path)-1)
  for i in r

    # If this vertex a collider, i.e. path[i-1] -> path[i] <- path[i+1], and
    # 1. not in the conditioning_set or
    # 2. has does not have a causal descendent that is in the conditioning set
    # then the path is blocked

    vertex_type = :unknown

    if d.a[path[i], path[i-1]] == 1 && d.a[path[i], path[i+1]] == 1
      vertex_type = :collider
      if length(conditioning_set) > 0
        path_closed = path[i] in conditioning_set ? false : true
      else
        path_closed = true
      end
    elseif d.a[path[i-1], path[i]] == 1 && d.a[path[i+1], path[i]] == 1
      vertex_type = :fork
      if length(conditioning_set) > 0
        path_closed = path[i] in conditioning_set ? true : false
      else
        path_closed = false
      end
    elseif d.a[path[i-1], path[i]] == 1 && d.a[path[i], path[i+1]] == 1 ||
        d.a[path[i], path[i-1]] == 1 && d.a[path[i+1], path[i]] == 1
      vertex_type = :chain
      if length(conditioning_set) > 0
        path_closed = path[i] in conditioning_set ? true : false
      else
        path_closed = false
      end
    else
      @info "Vertex_type of $(path[i]) = $(vertex_type)\n"*
        ""
    end
    debug && println("Vertex_type of $(path[i]) in $path = $(vertex_type)\n")

    # If path_closed == true, check that no causal descendent
    # of path[i] is in conditioning set. If that is the case,
    # flip the state. Max_length of decendent path? What if forks
    # in the path?

    res = path_closed ? :closed : :open
    res == :closed && break
  end
  res
end

"""

# `open_paths`

$(SIGNATURES)

Internal.
"""
function open_paths(d::DAG, paths::Vector{Vector{Symbol}}, cond::Vector{Symbol};debug=false)
  open = fill(:closed, length(paths))
  for (ind, path) in enumerate(paths)
    if length(path) == 2
      if d.a[path[2], path[1]] == 1
        open[ind] = :open
      end
    else
      open[ind] = check_open(d, path, cond)
    end
  end
  ind = findall(x -> x == :open, open)
  paths[ind]
end

export
  open_paths,
  check_open


# # Beam under on/off loading: transient response

# Source code: [`beam_load_on_off_tut.jl`](beam_load_on_off_tut.jl)

# ## Description

# A cantilever beam is loaded by a trapezoidal-pulse traction load at its free
# cross-section. The load is applied within 0.015 seconds and taken off after
# 0.37 seconds. The beam oscillates about its equilibrium configuration.

# The beam is modeled as a solid. Trapezoidal rule is used to integrate the
# equations of motion in time. Rayleigh mass- and stiffness-proportional
# damping is incorporated. The dynamic stiffness is factorized for efficiency.

# ![](beam_load_on_off_tut.png)

# ## Goals

# - Show how to create the discrete model, with implicit dynamics and proportional damping.
# - Apply distributed loading varying in time.
# - Demonstrate  trapezoidal-rule time stepping.

##
# ## Definitions

# Basic imports.
using LinearAlgebra
using Arpack

# This is the finite element toolkit itself.
using FinEtools

# The linear stress analysis application is implemented in this package.
using FinEtoolsDeforLinear
using FinEtoolsDeforLinear.AlgoDeforLinearModule

# Input parameters
E = 205000*phun("MPa");# Young's modulus
nu = 0.3;# Poisson ratio
rho = 7850*phun("KG*M^-3");# mass density
loss_tangent = 0.005;
L = 200*phun("mm");
W = 4*phun("mm");
H = 8*phun("mm");
tolerance = W/500;
qmagn = 0.1*phun("MPa");
tend = 0.5*phun("SEC");

##
# ## Create the discrete model

MR = DeforModelRed3D
fens,fes  = H8block(L, W, H, 50, 2, 4)

geom = NodalField(fens.xyz)
u = NodalField(zeros(size(fens.xyz,1),3)) # displacement field

nl = selectnode(fens, box=[L L -Inf Inf -Inf Inf], inflate=tolerance)
setebc!(u, nl, true, 1)
setebc!(u, nl, true, 2)
setebc!(u, nl, true, 3)
applyebc!(u)
numberdofs!(u)

corner = selectnode(fens, nearestto=[0 0 0])
cornerzdof = u.dofnums[corner[1], 3]

material = MatDeforElastIso(MR, rho, E, nu, 0.0)

femm = FEMMDeforLinearMSH8(MR, IntegDomain(fes, GaussRule(3,2)), material)
femm = associategeometry!(femm, geom)
K = stiffness(femm, geom, u)
femm = FEMMDeforLinear(MR, IntegDomain(fes, GaussRule(3,3)), material)
M = mass(femm, geom, u)

# Find the boundary finite elements at the tip cross-section of the beam. The
# uniform distributed loading will be applied to these elements.
bdryfes = meshboundary(fes)
# Those facing in the positive X direction will be chosen:
tipbfl = selectelem(fens, bdryfes, facing=true, direction=[-1.0 0.0 0.0])
# A base finite element model machine will be created to evaluate the loading.
# The force intensity is created as driven by a function, but the function
# really only just fills the buffer with the constant loading vector.
function pfun(forceout::FVec{T}, XYZ::FFltMat, tangents::FFltMat, fe_label::FInt) where {T}
    forceout .=  [0.0, 0.0, qmagn]
    return forceout
end
fi = ForceIntensity(FFlt, 3, pfun);

# The loading vector is lumped from the distributed uniform loading by
# integrating on the boundary. Hence, the dimension of the integration domain
# is 2.
el1femm =  FEMMBase(IntegDomain(subset(bdryfes,tipbfl), GaussRule(2,2)))
F = distribloads(el1femm, geom, u, fi, 2);

# The loading function is defined as a time -dependent multiplier of the
# constant distribution of the loading on the structure.
function tmult(t)
    if (t <= 0.015)
        t/0.015
    else
        if (t >= 0.4)
            0.0
        else
            if (t <= 0.385)
                1.0
            else
                (t - 0.4)/(0.385 - 0.4)
            end
        end
    end
end

##
# ## Time step determination

# We figure out the fundamental mode frequency, which will determine the time
# step is a fraction of the period.
evals, evecs = eigs(K, M; nev=1, which=:SM);

# The fundamental angular frequency is then:
@show omega_f = sqrt(evals[1]);

# We take  the time step to be a fraction of the period of vibration  in the
# fundamental mode.
@show dt = 0.05 * 1/(omega_f/2/pi);

##
# ## Damping model

# We take the damping to be representative of what's happening at the
# fundamental vibration frequency.

# For a given loss factor at a certain  frequency $\omega_f$, the
# stiffness-proportional damping coefficient may be estimated as
# 2*loss_tangent/$\omega_f$, and the mass-proportional damping coefficient may be
# estimated as 2*loss_tangent*$\omega_f$.
Rayleigh_mass = (loss_tangent/2)*omega_f;
Rayleigh_stiffness = (loss_tangent/2)/omega_f;

# Now we construct the Rayleigh damping matrix as a linear combination of the
# stiffness and mass matrices.
C = Rayleigh_stiffness * K + Rayleigh_mass * M

# The time stepping loop is protected by `let end` to avoid unpleasant surprises
# with variables getting clobbered by globals.
ts, corneruzs = let dt = dt, F = F
    # Initial displacement, velocity, and acceleration.
    U0 = gathersysvec(u)
    v = deepcopy(u)
    V0 = gathersysvec(v)
    U1 = fill(0.0, length(V0))
    V1 = fill(0.0, length(V0))
    F0 = deepcopy(F)
    F1 = fill(0.0, length(V0))
    R  = fill(0.0, length(V0))

    # Factorize the dynamic stiffness
    DSF = cholesky((M + (dt/2)*C + ((dt/2)^2)*K))

    # The times and displacements of the corner will be collected into two vectors
    ts = Float64[]
    corneruzs = Float64[]
    # Let us begin the time integration loop:
    t = 0.0;
    step = 0;
    F0 .= tmult(t) .* F
    while t < tend
        push!(ts, t)
        push!(corneruzs, U0[cornerzdof])
        t = t+dt;
        step = step + 1;
        (mod(step,100)==0) && println("Step $(step): $(t)")
        # Set the time-dependent load
        F1 .= tmult(t) .* F
        # Compute the out of balance force.
        R = (M*V0 - C*(dt/2*V0) - K*((dt/2)^2*V0 + dt*U0) + (dt/2)*(F0+F1));
        # Calculate the new velocities.
        V1 = DSF\R;
        # Update the velocities.
        U1 = U0 + (dt/2)*(V0+V1);
        # Switch the temporary vectors for the next step.
        U0, U1 = U1, U0;
        V0, V1 = V1, V0;
        F0, F1 = F1, F0;
        if (t == tend) # Are we done yet?
            break;
        end
        if (t+dt > tend) # Adjust the last time step so that we exactly reach tend
            dt = tend-t;
        end
    end
    ts, corneruzs # return the collected results
end

##
# ## Plot the results

using Gnuplot

@gp "set terminal windows 0 " :-
@gp  :- ts corneruzs./phun("mm") "lw 2 lc rgb 'red' with lines title 'Displacement of the corner' "
@gp  :- "set xlabel 'Time [s]'"
@gp  :- "set ylabel 'Displacement [mm]'"

# The end.
true


###############################################################################
#
# Volume scattering functions adatpted from Volscatt version 2 by W. Verhoef
#
###############################################################################
"""
    volscatt!(cache::Array{FT,1},
              sza::FT,
              vza::FT,
              raa::FT,
              ttl::FT
    ) where {FT<:AbstractFloat}

Calculate interception parameters (`chi_s` and `chi_s`) and leaf reflectance
    multiplier (`frho`) and transmittance multiplier (`ftau`), given
- `cache` Array cache for results
- `sza` Solar zenith angle
- `vza` Viewing zenith angle
- `raa` Relative azimuth angle
- `ttl` Leaf inclination angle
"""
function volscatt!(
            cache::Array{FT,1},
            sza::FT,
            vza::FT,
            raa::FT,
            ttl::FT
) where {FT<:AbstractFloat}
    psi_rad = deg2rad(raa);
    cos_raa = cosd(raa);
    cos_ttl = cosd(ttl);
    sin_ttl = sind(ttl);
    cos_sza = cosd(sza);
    sin_sza = sind(sza);
    cos_vza = cosd(vza);
    sin_vza = sind(vza);
    Cs      = cos_ttl*cos_sza;
    Ss      = sin_ttl*sin_sza;
    Co      = cos_ttl*cos_vza;
    So      = sin_ttl*sin_vza;

    cosbts = FT(1.0);
    cosbto = FT(1.0);
    if (abs(Ss)>1e-6)
        cosbts = -Cs / Ss;
    end
    if (abs(So)>1e-6)
        cosbto = -Co / So;
    end

    if (abs(cosbts)<1)
        bts = acos(cosbts);
        ds  = Ss;
    else
        bts = FT(pi);
        ds  = Cs;
    end

    if abs(cosbto)<1
        bto = acos(cosbto);
        doo = So;
    elseif vza<90
        bto = FT(pi);
        doo = Co;
    else
        bto = 0;
        doo = -Co;
    end

    chi_s = 2 / FT(pi) * ( (bts - FT(pi)/2) * Cs + sin(bts) * Ss );
    chi_o = 2 / FT(pi) * ( (bto - FT(pi)/2) * Co + sin(bto) * So );

    # Computation of auxiliary azimut angles bt1, bt2, bt3 used
    # for the computation of the bidirectional scattering coefficient w

    btran1 = abs(bts - bto);
    btran2 = 2FT(pi) - bts - bto;
    #btran_=pi-abs(bts+bto-FT(pi))

    if psi_rad <= btran1
        bt1 = psi_rad;
        bt2 = btran1;
        bt3 = btran2;
    else
        bt1 = btran1;
        if psi_rad <= btran2
            bt2 = psi_rad;
            bt3 = btran2;
        else
            bt2 = btran2;
            bt3 = psi_rad;
        end
    end

    t1 = 2Cs * Co + Ss * So * cos_raa;
    t2 = 0;
    if bt2 > 0
        t2 = sin(bt2) * ( 2ds * doo + Ss * So * cos(bt1) * cos(bt3) );
    end

    denom = 2 * FT(pi)^2;
    frho  = ((pi-bt2) * t1 + t2) / denom;
    ftau  = (-bt2     * t1 + t2) / denom;

    # fill the values to cache
    cache[1] = chi_s;
    cache[2] = abs(chi_o);
    cache[3] = frho;
    cache[4] = ftau;

    return nothing
end


# Minimal implementation of 3-layer MLP that solves Xor problem.

X = [[0, 0], [0, 1], [1, 0], [1, 1]]
Y = (x -> [xor(x...)]).(X)
η = 0.1
W = rand(2, 3)
V = rand(1, 3)
W ∗ x = W * [x; 1]
for _ in 1:1000, (x, y) in zip(X, Y)
    z = W ∗ x
    h = tanh.(z)
    o = V ∗ h
    ∇o = o - y
    ∇h = ∇o * V[:, 1:(end - 1)]
    ∇V = ∇o * [h; 1]'
    ∇W = (@. ∇h * (1.0 - h' ^ 2))' * [x; 1]'
    V .-= η * ∇V
    W .-= η * ∇W
end
predict(x) = round.(Int, V ∗ tanh.(W ∗ x))
@assert predict.(X) == Y


# Implements linear fitting (analagous to IDL's regress function)

# Dependent in the vector Y, which should have length N_data
# N_var independent variables are contained in the array X.
# X should have the size [N_var,N_data].

function regress(X,Y,sigY)
# Check that X has two dimensions, and that
# X and Y have are compatible (second dimension of X
# should equal the dimension of Y):
nx = ndims(X)
sx = size(X)
sy = size(Y)
if (nx == 2) & (sx[2] == sy[1]) then
# Set up array for computing coefficients:
  A = zeros(sx[1],sx[1])
  B = zeros(sx[1])
# Compute A = \sum_i \sum_j \sum_k X_{i,k} X_{j,k}/\sigY_k^2
  for i = 1:sx[1]
    for j = 1:sx[1]
      for k = 1:sx[2]
        A[i,j] += X[i,k]*X[j,k]/sigY[k]^2
      end
    end
# Compute B = \sum_i \sum_k X_{i,k} Y_k/\sigY_k^2
    for k = 1:sx[2]
      B[i] += X[i,k]*Y[k]/sigY[k]^2
    end
  end
# Covariance:
  covC = inv(A)
# Now, solve for the coefficients, A*C = B:
  C = \(A,B)
else
  println("Error")
end
return C,covC
end


module Truss

using LinearAlgebra
using Plots
using GraphRecipes

export define, solve, show

"""
        truss_object

This object holds all the definitions (material properties, section properties, geometry) and the solution (displacements, reactions, internal forces) for a system of truss elements.

num_dof_per_node::Int64
members::Array{NTuple{4,Int64},1}
node_geometry::Array{Float64,2}
section_properties::Array{Float64,1}
material_properties::Array{Float64,1}
supports::Array{Int64,1}
external_forces::Array{Float64,1}
L::Array{Float64,1}
E::Array{Float64,1}
A::Array{Float64,1}
θ::Array{Float64,1}
K::Array{Float64,2}
Kff::Array{Float64,2}
Ksf::Array{Float64,2}
Kfs::Array{Float64,2}
Kss::Array{Float64,2}
F::Array{Float64,1}
Fs::Array{Float64,1}
Ff::Array{Float64,1}
s::Array{Int64,1}
f::Array{Int64,1}
T::Array{Array{Float64,2},1}
k_element_local::Array{Array{Float64,2},1}
k_element_global::Array{Array{Float64,2},1}
u::Array{Float64,1}
uf::Array{Float64,1}
f_element::Array{Array{Float64,1},2}

"""

mutable struct truss_object

    num_dof_per_node::Int64
    members::Array{NTuple{4,Int64},1}
    node_geometry::Array{Float64,2}
    section_properties::Array{Float64,1}
    material_properties::Array{Float64,1}
    supports::Array{Int64,1}
    external_forces::Array{Float64,1}
    L::Array{Float64,1}
    E::Array{Float64,1}
    A::Array{Float64,1}
    θ::Array{Float64,1}
    K::Array{Float64,2}
    Kff::Array{Float64,2}
    Ksf::Array{Float64,2}
    Kfs::Array{Float64,2}
    Kss::Array{Float64,2}
    F::Array{Float64,1}
    Fs::Array{Float64,1}
    Ff::Array{Float64,1}
    s::Array{Int64,1}
    f::Array{Int64,1}
    T::Array{Array{Float64,2},1}
    k_element_local::Array{Array{Float64,2},1}
    k_element_global::Array{Array{Float64,2},1}
    u::Array{Float64,1}
    uf::Array{Float64,1}
    f_element::Array{Array{Float64,1},2}

    #This allows for the construction of a new object.
    truss_object() = new()

end

"""
        calculate_truss_element_lengths(members, node_geometry)

Calculate the truss member lengths in a structural system.

`members` is a NTuple{4, Int64} containing node i, node j, A, E assignments
`node_geometry` is array with the global x-y coordinates

"""

function calculate_truss_element_lengths(members, node_geometry)

    #Initialize array to hold truss lengths.
    truss_lengths=zeros(Float64, length(members))

    #Loop over each truss element.
    for i=1:length(members)

        #Define truss node numbers.
        node_i=members[i][1]
        node_j=members[i][2]

        #Define truss node coordinates.
        node_i_xy=node_geometry[node_i,:]
        node_j_xy=node_geometry[node_j,:]

        #Calculate the truss element length.
        truss_lengths[i] =norm(node_i_xy - node_j_xy)

    end

    return truss_lengths

end

"""
    define_local_truss_element_stiffness_matrix(E, A, L)

Define a truss element stiffness matrix in its local coordinate system.

`E` is elastic modulus.
`A` is the cross-sectional area.
`L` is truss length.

There are 4 degrees of freedom for the truss element.

      2                 4
    1>^--------------->3^

"""

function define_local_truss_element_stiffness_matrix(E, A, L)

	k_truss_element_local = E * A/ L * [1  0 -1 0
		                  0  0 0 0
		                  -1 0  1 0
	                      0  0  0 0]

	return k_truss_element_local

end

"""
        define_truss_element_orientations(members, node_geometry)

Define the truss element orientation angle.

`members` is a NTuple{4, Int64} containing node i, node j, A, E assignments
`node_geometry` is array with the global x-y coordinates

A positive angle is defined counterclockwise from the horizon, rotating about node i.

"""

function define_truss_element_orientations(members, node_geometry)

    # Initialize the orientation array for all truss elements in a structural system.
    θ = zeros(Float64, length(members))

    # Loop over the truss elements.
    for i=1:length(members)

        point_i = node_geometry[members[i][1], :]
        point_j = node_geometry[members[i][2], :]

        element_vector = point_j - point_i

        #Calculate the orientation angle.
        #https://en.wikipedia.org/wiki/Atan2
        θ[i] = atan(element_vector[2], element_vector[1])

    end

    return θ

end

"""
        vector_rotation_operator(θ)

Define an operator matrix that converts truss element displacements to a new, rotated coordinate system.

`θ` is the orientation angle of a truss element.

A positive angle is defined counterclockwise from the horizon, rotating about node i.

"""

function vector_rotation_operator(θ)

	T = [cos(θ) sin(θ) 0 0
		 -sin(θ) cos(θ) 0 0
		 0 0 cos(θ) sin(θ)
		 0 0  -sin(θ) cos(θ)]

	return T

end

"""
    assign_member_properties(members, member_property, property_order, property_type)

Define an array containing the member property values for each element in a structural system.   For example, define all the cross-sectional areas for all the members in a truss.

`members` is a NTuple{4, Int64} containing node i, node j, A, E assignments.
`member_property` is an array containing all the available properties that a member can take on.
`property order` is the index in the `members` NTuple where the specific property is assigned.

"""

function assign_member_properties(members, member_property, property_order, property_type)

    #Initialize the property array
    property = zeros(Float64, length(members))

    #Loop over all the members.
    for i=1:length(members)

        #Assign a specific property (area, elastic modulus) to each element in a structural system.
        property[i] = member_property[members[i][property_order]][property_type]

    end

    return property

end

"""
    calculate_global_element_stiffness_matrix(k_element_local, θ)

Transform a local element stiffness matrix to global coordinates.

`k_element_local` is the element stiffness matrix in its local coordinate system.
`θ` is the orientation angle of the element.

"""

function calculate_global_element_stiffness_matrix(k_element_local, T)

    #Transform the local element stiffness matrix to global coordinates.
    k_element_global = T' * k_element_local * T

    return k_element_global

end

"""
    assemble_global_stiffness_matrix(node_geometry, members, k_element_global, num_dof_per_node)

Assemble all the element stiffness matrices into the global system stiffness matrix.

`node_geometry` is an array with the global node x-y coordinates.
`members` is a NTuple{4, Int64} containing node i, node j, and property assignments.
`k_element_global` are all the element stiffness matrices in a 3D array.
`num_dof_per_node` defines the number of degrees of freedom per element node.  For a truss, this is 2, for a beam it is 3, ...

"""

function assemble_global_stiffness_matrix(node_geometry, members, k_element_global, num_dof_per_node)

    num_nodes = size(node_geometry)[1]

    k_system_global = zeros(Float64, num_nodes * num_dof_per_node, num_nodes * num_dof_per_node)

    for i=1:length(members)

        node_i = members[i][1]
        node_j = members[i][2]

        node_i_dof = [1;2] .+ (node_i - 1) * num_dof_per_node
        node_j_dof = [1;2] .+ (node_j - 1) * num_dof_per_node

        global_dof = [node_i_dof; node_j_dof]

        k_system_global[global_dof, global_dof] = k_system_global[global_dof, global_dof] + k_element_global[i]

    end

    return k_system_global

end

"""
    define(members, section_properties, material_properties, node_geometry, supports, external_forces)

Define and partition the global system stiffness matrix and external force vector.

`members` is a NTuple{4, Int64} containing node i, node j, and property assignments.
'section_properties' defines a library of section properties.
'material_properties' defines a library of material properties.
`node_geometry` is an array with the global node x-y coordinates.
`supports` defines the degrees of freedom that are free (0) and fixed(1).
`external_forces` is an array of the external forces applied to the nodes, in global coordinates.

Partition the stiffness matrix into Kff (free free), Kfs (free supported), Ksf (supported free), and Kss (supported supported).  Partition the external forces at the free dof into Ff. Also return `f` array of free global degrees of freedom, `s` fixed global degrees of freedom, `T` all the rotation matrices for each truss element, and the `k_element_local` and `k_element_global` matrices.

"""

function define(members, section_properties, material_properties, node_geometry, supports, external_forces)

    truss = truss_object()

    truss.members = members
    truss.section_properties = section_properties
    truss.material_properties = material_properties
    truss.node_geometry = node_geometry
    truss.supports = supports

    num_nodes = size(node_geometry)[1]
    truss.external_forces = zeros(Float64, num_nodes * 2)
    truss.external_forces .= external_forces

    #Define the number of degrees of freedom per node.
    truss.num_dof_per_node = 2

    #Calculate truss member lengths.
    truss.L = calculate_truss_element_lengths(members, node_geometry)

    #Calculate truss member orientations.
    truss.θ = define_truss_element_orientations(members, node_geometry)

    #Define truss properties for stiffness matrix calculations.
    truss.A = assign_member_properties(members, section_properties, 3, 1)
    truss.E = assign_member_properties(members, material_properties, 4, 1)

    #Calculate the local stiffness matrix for each element.
    truss.k_element_local = define_local_truss_element_stiffness_matrix.(truss.A, truss.E, truss.L)

    #Define rotation matrix for each element.
    truss.T = vector_rotation_operator.(truss.θ)

    #Rotate local stiffness matrix into global coordinates.
    truss.k_element_global = calculate_global_element_stiffness_matrix.(truss.k_element_local, truss.T)

    #Assemble the global stiffness matrix.
    truss.K = assemble_global_stiffness_matrix(node_geometry, members, truss.k_element_global, truss.num_dof_per_node)

    #Define free degrees of freedom.
    truss.f = findall(x-> x==0, supports)

    #Define number of free dof.
    num_free_dof = length(truss.f)

    #Define fixed degrees of freedom.
    truss.s = findall(x-> x==1, supports)

    #Define number of fixed dof.
    num_fixed_dof = length(truss.s)

    #Initialize partitioned stiffness matrices.
    truss.Kff = zeros(Float64, (num_free_dof, num_free_dof))
    truss.Ksf = zeros(Float64, (num_fixed_dof, num_free_dof))
    truss.Kfs = zeros(Float64, (num_free_dof, num_fixed_dof))
    truss.Kss = zeros(Float64, (num_fixed_dof, num_fixed_dof))

    #Partition the global stiffness matrix.
    truss.Kff .= truss.K[truss.f, truss.f]
    truss.Ksf .= truss.K[truss.s, truss.f]
    truss.Kfs .= truss.K[truss.f, truss.s]
    truss.Kss .= truss.K[truss.s, truss.s]

    #Partition the external force vectors.
    truss.F = external_forces
    truss.Ff = zeros(Float64, length(truss.f))
    truss.Ff .= truss.F[truss.f]

    return truss

end

"""
    solve(K, Kff, Ksf, F, Ff, s, f, T, k_element_local)

Solve for system displacements, reactions, and internal forces.

`K` is the global stiffness matrix.
'Kff' is the partitioned portion of the global stiffness matrix, free by free.
'Ksf' is the partitioned portion of the global stiffness matrix, support by free.
`F` is an array of the external forces.
`s` is an array of the fixed degrees of freedom.
`f` is an array of the support degrees of freedom.
`T` are all the rotation matrices for each element.
`k_element_local` are all the element local stiffness matrices.

Calculate the global displacement vector `uf` for the free degrees of freedom, provide the full global displacement vector `u`, and calculate all the element internal forces as an array of arrays `f_element`.

"""

function solve(truss)

    #Define the total number of degrees of freedom in the system.
    num_dof = size(truss.K)[1]

    #Initialize the global displacement vector.
    truss.u = zeros(Float64, num_dof)

    #Calculate the global displacements at the free degrees of freedom.
    truss.uf = truss.Kff^-1 * truss.Ff

    #Insert uf into u.
    truss.u[truss.f] .= truss.uf

    #Calculate the global system reactions.
    truss.Fs = truss.Ksf * truss.uf

    #Insert Fs into F.
    truss.F[truss.s] .= truss.Fs

    #Define the number of elements.
    num_elem = length(truss.A)

    #Initialize an array of element force vectors.
    truss.f_element = fill(Float64[], num_elem, 1)

    #Calculate the internal forces in each element.
    for i = 1:num_elem

        #Define node i and j for an element.
        node_i = truss.members[i][1]
        node_j = truss.members[i][2]

        #Calculate the global dof numbers at each element nodes i and j.
        node_i_dof = [1;2] .+ (node_i - 1) * truss.num_dof_per_node
        node_j_dof = [1;2] .+ (node_j - 1) * truss.num_dof_per_node

        #Assemble all the global dof numbers for the element.
        global_dof = [node_i_dof; node_j_dof]

        #Find the element displacements in global coordinates.
        u_element_global = zeros(Float64, length(global_dof))
        u_element_global .= truss.u[global_dof]

        #Transform the element displacements into local coordinates.
        u_element_local = truss.T[i] * u_element_global

        #Calculate the element local forces.
        truss.f_element[i] = truss.k_element_local[i] * u_element_local

    end

    return truss

end

function show(truss_object, scale_x, scale_y)

	δx = truss_object.u[1:2:end]
	δy = truss_object.u[2:2:end]

	num_nodes = floor(Int, length(truss_object.u)/2)

	g = zeros(Int64, (num_nodes, num_nodes))

	start_nodes = [x[1] for x in truss_object.members]
	end_nodes = [x[2] for x in truss_object.members]

	for i = 1:num_nodes

		index = findall(x->x==i, start_nodes)

		g[i, end_nodes[index]] .= 1

	end

	graphplot(g,
          x=truss_object.node_geometry[:,1], y=truss_object.node_geometry[:,2],
          nodeshape=:circle, nodesize=2,
          axis_buffer=0.01,
          curves=false,
          color=:red,
          linewidth=0.5,
	      arrowlengthfrac=0)

	graphplot!(g,
          x=truss_object.node_geometry[:,1] .+ δx .* scale_x, y=truss_object.node_geometry[:,2] .+ δy .* 	scale_y,
          nodeshape=:circle, nodesize=2,
          axis_buffer=0.01,
          curves=false,
          color=:black,
          linewidth=2,
	      arrowlengthfrac=0)

	quiver!(truss_object.node_geometry[:,1],truss_object.node_geometry[:,2],quiver=(truss_object.external_forces[1:2:end]./10, truss_object.external_forces[2:2:end]./10))

	return current()

end

end #module


module GeometryModule
export Geometry, readGeometryXYZ, bohr2angstrom, angstrom2bohr, angstrom2bohr!, computeEnergyInteratomicRepulsion, computeNumberElectrons, computeRotationalConstants, computeMatrixInertiaTensor

using ..BaseModule
using ..AtomModule
using LinearAlgebra

using Printf
using Markdown
import Base.show

struct Geometry
	atoms::Array{Atom,1}
end

function show(io::IO,mime::MIME"text/plain",geo::Geometry)
  show(io,mime,Markdown.parse("    \t      x                  y                  z")); print(io,"\n")
  for at in geo.atoms
    show(io,mime,Markdown.parse("**$(at.element.symbol)**"))
    print(io,@sprintf("\t% 18.10f % 18.10f % 18.10f\n",at.position.x,at.position.y,at.position.z))
  end
end

"""
    readGeometryXYZ(filename::AbstractString; unit::Symbol=:Bohr)

reads an .xyz-Geometry file (acc. to the unofficial standard the coordinates
of which are interpreted as Angstrom) and yields a Geometry object in `unit`.
"""
function readGeometryXYZ(filename::AbstractString; unit::Symbol=:Bohr)
	geo = Geometry([])
	lines = open(readlines,filename)
	for line in lines[3:end]	# line 1 is n. of atoms, line 2 is comment
		columns = split(line)
		elem = Element(columns[1])
		pos = Position(map(Meta.parse,columns[2:4])...)	# float is not recommended for use on tuples
		append!(geo.atoms,[Atom(elem,pos)])
	end
	if (unit == :Bohr) angstrom2bohr!(geo) end	# Should be default as Bohr is an atomic unit
	return geo
end

function Geometry(filename::AbstractString)
  if isfile(filename)
    return readGeometryXYZ(filename)
  end
  for ext in ("xyz", "XYZ")
    if isfile("$filename.$ext")
      return readGeometryXYZ("$filename.$ext")
    end
  end
  error("Couldn't find file: $filename")
end

angstrom2bohr(x::Real) = x/0.529177210
bohr2angstrom(x::Real) = x*0.529177210
angstrom2bohr(pos::Position) = (return Position(angstrom2bohr(pos.x),angstrom2bohr(pos.y),angstrom2bohr(pos.z)))
function angstrom2bohr!(geo::Geometry)
  for atom in geo.atoms
    atom.position = angstrom2bohr(atom.position)
  end
end

function computeEnergyInteratomicRepulsion(
  atom1::Atom,
  atom2::Atom)
  q1 = atom1.element.atomicNumber
  q2 = atom2.element.atomicNumber
  A = atom1.position
  B = atom2.position
  return q1*q2/(distance(A,B))
end

function computeEnergyInteratomicRepulsion(geo::Geometry)
  result = 0.
  for A in 1:length(geo.atoms), B in 1:A-1
      result += computeEnergyInteratomicRepulsion(geo.atoms[A],geo.atoms[B])
  end
  return result
end

function computeNumberElectrons(geo::Geometry,charge::Int=0)
  sum(at.element.atomicNumber for at in geo.atoms) - charge
end

function computePositionCenterOfMass(geo)
  weight = AtomModule.atomicWeight
  sum(weight[at.element.atomicNumber] * at.position for at in geo.atoms)/sum(weight[at.element.atomicNumber] for at in geo.atoms)
end

function computeMatrixInertiaTensor(geo)
  weight = AtomModule.atomicWeight

  com = computePositionCenterOfMass(geo)
  X,Y,Z = com.x,com.y,com.z

  Ixx = sum([weight[at.element.atomicNumber] * ((at.position.y-Y)^2 + (at.position.z-Z)^2) for at in geo.atoms])
  Iyy = sum([weight[at.element.atomicNumber] * ((at.position.x-X)^2 + (at.position.z-Z)^2) for at in geo.atoms])
  Izz = sum([weight[at.element.atomicNumber] * ((at.position.x-X)^2 + (at.position.y-Y)^2) for at in geo.atoms])

  Ixy = Iyx = sum([weight[at.element.atomicNumber] * -((at.position.x-X)*(at.position.y-Y)) for at in geo.atoms])
  Ixz = Izx = sum([weight[at.element.atomicNumber] * -((at.position.x-X)*(at.position.z-Z)) for at in geo.atoms])
  Iyz = Izy = sum([weight[at.element.atomicNumber] * -((at.position.y-Y)*(at.position.z-Z)) for at in geo.atoms])

  return [Ixx Ixy Ixz; Iyx Iyy Iyz; Izx Izy Izz]
end

function computeRotationalConstants(geo;unit=:MHz)
  I = computeMatrixInertiaTensor(geo) * 1822.89 # amu Bohr^2 * mₑ/amu (kg/mol==amu)
  B = 1 ./ (2*eigvals(I)) # 1/(mₑ Bohr^2)  (* 1 Hartree*s)^2  => Hartree^2/Hartree => Hartree
  if unit==:hartree
    return B
  elseif unit==:cm1
    return B*2.1947e5 # Hartree -> cm-1
  elseif unit==:MHz
    return B*2.1947e5*2.99792458e4 # Hartree -> cm-1 -> MHz
  end
end

end #module


module TestVisualizationData

using Test
using Gridap.Geometry
using Gridap.CellData
using Gridap.FESpaces
using Gridap.TensorValues
using Gridap.Algebra
using Gridap.Visualization
using Gridap.ReferenceFEs
using Gridap.Geometry: get_reffes

domain = (0,2pi, 0, 4pi)
partition = (2, 3)
model = simplexify(CartesianDiscreteModel(domain,partition))
f(pt) = sin(pt[1])
V0 = TestFESpace(
  reffe=:Lagrangian, order=1, valuetype=Float64,
  conformity=:H1, model=model)
U = TrialFESpace(V0)
trian = Triangulation(model)
degree = 2
quad = CellQuadrature(trian,degree)
A(u, v) = u ⊙ v
b(v) = v⊙f
t_Ω = AffineFETerm(A,b,trian,quad)
op = AffineFEOperator(U,V0,t_Ω)
u = solve(op)

visdata = visualization_data(trian, cellfields=Dict("u" =>u))

ncells = num_cells(visdata.grid)
nodes_per_cell = num_nodes(first(get_reffes(visdata.grid)))
@test size(visdata.nodaldata["u"]) == (ncells * nodes_per_cell,)
@test size(visdata.celldata["cell"]) == (ncells,)
@test visdata.grid isa Visualization.VisualizationGrid
@test visdata isa Visualization.VisualizationData

end#module