function randargmin2(a)
    indices = eachindex(a)
    y = iterate(indices)
    y === nothing && throw(ArgumentError("collection must be non-empty"))
    (idx, state) = y
    minval = a[idx]
    bestidx = idx
    bestcount = 1
    y = iterate(indices, state)
    while y !== nothing
        (idx, state) = y
        curval = a[idx]
        if isless(curval, minval)
            minval = curval
            bestidx = idx
            bestcount = 1
        elseif isequal(curval, minval)
            bestcount += 1
            rand() * bestcount < 1 && (bestidx = idx)
        end
        y = iterate(indices, state)
    end
    bestidx
end

Chapter02/01. Finding the index of a random minimum element in an array/randargmin2.jl


# ---
# title: CVI Simple Example
# id: cvi_example
# cover: ../assets/logo.png
# date: 2021-12-6
# author: "[Sasha Petrenko](https://github.com/AP6YC)"
# julia: 1.6
# description: This demo is a simple example of how to use a CVI in batch mode.
# ---

# ## Overview

# This demo is a simple example of how to use CVIs in batch mode.
# Here, we load a simple dataset and run a basic clustering algorithm to prescribe a set of clusters to the features.
# It is a combination of these features and the prescribed labels that are used to compute the criterion value.
# This simple example demonstrates the usage of a single CVI, but it may be substituted for any other CVI in the `ClusterValidityIndices.jl` package.

# ## Clustering

# ### Data Setup

# First, we must load all of our dependencies.
# We will load the `ClusterValidityIndices.jl` along with some data utilities and the Julia `Clustering.jl` package to cluster that data.
using ClusterValidityIndices    # CVI/ICVI
using Clustering                # DBSCAN
using MLDatasets                # Iris dataset
using MLDataUtils               # Shuffling and splitting
using Printf                    # Formatted number printing

# We will download the Iris dataset for its small size and benchmark use for clustering algorithms.
Iris.download(i_accept_the_terms_of_use=true)
features, labels = Iris.features(), Iris.labels()

# Because the MLDatasets package gives us Iris labels as strings, we will use the `MLDataUtils.convertlabel` method with the `MLLabelUtils.LabelEnc.Indices` type to get a list of integers representing each class:
labels = convertlabel(LabelEnc.Indices{Int}, labels)
unique(labels)

# ### Fuzzy C-Means

# Get the Fuzzy C-Means clustering result
results = fuzzy_cmeans(features, 3, 2)

# Because the results are fuzzy weights, find the maximum elements along each sample
indices = argmax(results.weights, dims=2)

# Get those labels as a vector of integers
c_labels = vec([c[2] for c in indices])

# ## CVI Criterion Value Extraction

# Now that we have some data and a clustering algorithm's prescribed labels, we can compute a criterion value using a CVI in batch mode.
# First, we create a CVI object with the default constructor:

## Create a CVI object
my_cvi = CH()

# Finally we can simply get the criterion value in batch by passing all of the data and Fuzzy C-Means labels at once.

## Get the batch criterion value
criterion_value = get_cvi!(my_cvi, features, c_labels)


using OrdinaryDiffEq, DiffEqDevTools, DiffEqBase, Base.Test, ODEInterfaceDiffEq

const linear_bigα = parse(BigFloat,"1.01")
f = (t,u) -> (linear_bigα*u)
analytic = (t,u0) -> u0*exp(linear_bigα*t)
prob_ode_bigfloatlinear = ODETestProblem(f,parse(BigFloat,"0.5"),analytic,(0.0,10.0))

f = (t,u,du) -> begin
  for i in 1:length(u)
    du[i] = linear_bigα*u[i]
  end
end
probbig = ODETestProblem(f,map(BigFloat,rand(4,2)).*ones(4,2)/2,analytic,(0.0,10.0))

linear = (t,u) -> (1.01*u)
analytic_linear = (t,u0) -> u0*exp(1.01*t)
probnum = ODETestProblem(linear,1/2,analytic_linear,(0.0,10.0))

probnumbig = prob_ode_bigfloatlinear
#prob    = prob_ode_large2Dlinear

f_2dlinear = (t,u,du) -> begin
  for i in 1:length(u)
    du[i] = 1.01*u[i]
  end
end
analytic_2dlinear = (t,u0) -> u0*exp.(1.01*t)
prob = ODETestProblem(f_2dlinear,rand(4,2),analytic_2dlinear,(0.0,10.0))

dts = 1.//2.^(7:-1:4)
testTol = .2
bools = Vector{Bool}(0)

## DP5()
sim = test_convergence(dts,probnum,DP5())
@test abs(sim.𝒪est[:l2]-5) < testTol
sim = test_convergence(dts,prob,DP5())
@test abs(sim.𝒪est[:l2]-5) < testTol

tabalg = ExplicitRK()
sol1 =solve(probnum,DP5(),dt=1/2^6,adaptive=false,save_timeseries=false)
sol2 =solve(probnum,tabalg,dt=1/2^6,adaptive=false,save_timeseries=false)

@test sol1.u[end] - sol2.u[end] < 1e-10

sol1 =solve(prob,DP5(),dt=1/2^6,adaptive=false,save_timeseries=false)
sol2 =solve(prob,tabalg,dt=1/2^6,adaptive=false,save_timeseries=false)

@test minimum(sol1.u[end] - sol2.u[end] .< 3e-10)

sol1 =solve(probnum,DP5(),dt=1/2^6,beta2=0.04)
sol2 =solve(probnum,tabalg,dt=1/2^6,beta2=0.04)

# Should be identical
sol1 =solve(probnum,DP5())
sol2 =solve(probnum,tabalg,beta2=0.04,beta1=0.17)
sol3 =solve(probnum,dopri5())

@test sol1.t ≈ sol2.t ≈ sol3.t

sol1 =solve(prob,DP5(),dt=1/8)
sol2 =solve(prob,tabalg,beta2=0.04,beta1=0.17,dt=1/8)
sol3 =solve(prob,dopri5(),dt=1/8)

@test sol1.t ≈ sol2.t ≈ sol3.t

### BS3()
sim = test_convergence(dts,probnum,BS3())
@test abs(sim.𝒪est[:l2]-3) < testTol
sim = test_convergence(dts,prob,BS3())
@test abs(sim.𝒪est[:l2]-3) < testTol

tabalg = ExplicitRK(tableau=constructBogakiShampine3())
sol1 =solve(probnum,BS3(),dt=1/2^1,adaptive=false,save_timeseries=false)
sol2 =solve(probnum,tabalg,dt=1/2^1,adaptive=false,save_timeseries=false)

@test sol1.u[end] - sol2.u[end] < 1e-10

sol1 =solve(prob,BS3(),dt=1/2^1,adaptive=false,save_timeseries=false)
sol2 =solve(prob,tabalg,dt=1/2^1,adaptive=false,save_timeseries=false)

@test minimum(sol1.u[end] - sol2.u[end] .< 1e-10)

sol1 =solve(prob,tabalg,dt=1/2^6)
sol2 =solve(prob,BS3(),dt=1/2^6)

@test length(sol1) == length(sol2)

### BS5()
dts = 1.//2.^(6:-1:3)
sim = test_convergence(dts,probnumbig,BS5())
@test abs(sim.𝒪est[:l2]-5) < testTol
sim = test_convergence(dts,probbig,BS5())
@test abs(sim.𝒪est[:l2]-5) < testTol

tabalg = ExplicitRK(tableau=constructBogakiShampine5())
sol1 =solve(probnum,BS5(),dt=1/2^6,adaptive=false,save_timeseries=false)
sol2 =solve(probnum,tabalg,dt=1/2^6,adaptive=false,save_timeseries=false)

@test sol1.u[end] - sol2.u[end] < 1e-10

sol1 =solve(prob,BS5(),dt=1/2^3,adaptive=false,save_timeseries=false)
sol2 =solve(prob,tabalg,dt=1/2^3,adaptive=false,save_timeseries=false)

@test minimum(sol1.u[end] - sol2.u[end] .< 1e-10)

sol1 =solve(prob,tabalg,dt=1/2^6)
sol2 =solve(prob,BS5(),dt=1/2^6)

@test length(sol1) <= length(sol2) # Dual error estimators is more strict

### Tsit5()

dts = 1.//2.^(7:-1:3)
sim = test_convergence(dts,probnum,Tsit5())
@test abs(sim.𝒪est[:l2]-5) < testTol+.1
sim = test_convergence(dts,prob,Tsit5())
@test abs(sim.𝒪est[:l2]-5) < testTol+.1

tabalg = ExplicitRK(tableau=constructTsitouras5())
sol1 =solve(probnum,Tsit5(),dt=1/2^6,adaptive=false,save_timeseries=false)
sol2 =solve(probnum,tabalg,dt=1/2^6,adaptive=false,save_timeseries=false)

@test sol1.u[end] - sol2.u[end] < 1e-10

sol1 =solve(prob,Tsit5(),dt=1/2^3,adaptive=false,save_timeseries=false)
sol2 =solve(prob,tabalg,dt=1/2^3,adaptive=false,save_timeseries=false)

@test minimum(sol1.u[end] - sol2.u[end] .< 1e-10)

sol1 =solve(prob,tabalg,dt=1/2^6)
sol2 =solve(prob,Tsit5(),dt=1/2^6)

@test length(sol1) == length(sol2)

### Vern6()

dts = 1.//2.^(8:-1:5)
sim = test_convergence(dts,probnumbig,Vern6())
@test abs(sim.𝒪est[:l2]-6) < testTol
sim = test_convergence(dts,probbig,Vern6())
@test abs(sim.𝒪est[:l2]-6) < testTol

tabalg = ExplicitRK(tableau=constructVernerEfficient6(BigFloat))
sol1 =solve(probnumbig,Vern6(),dt=1/2^6,adaptive=false,save_timeseries=false)
sol2 =solve(probnumbig,tabalg,dt=1/2^6,adaptive=false,save_timeseries=false)

@test sol1.u[end] - sol2.u[end] < 1e-10

sol1 =solve(probbig,Vern6(),dt=1/2^3,adaptive=false,save_timeseries=false)
sol2 =solve(probbig,tabalg,dt=1/2^3,adaptive=false,save_timeseries=false)

@test minimum(sol1.u[end] - sol2.u[end] .< 1e-10)

sol1 =solve(probbig,tabalg,dt=1/2^6)
sol2 =solve(probbig,Vern6(),dt=1/2^6)

@test length(sol1) == length(sol2)

### Vern7()

dts = 1.//2.^(6:-1:3)
sim = test_convergence(dts,probnumbig,Vern7())
@test abs(sim.𝒪est[:l2]-7) < testTol
sim = test_convergence(dts,probbig,Vern7())
@test abs(sim.𝒪est[:l2]-7) < testTol

tabalg = ExplicitRK(tableau=constructVerner7(BigFloat))
sol1 =solve(probnumbig,Vern7(),dt=1/2^6,adaptive=false,save_timeseries=false)
sol2 =solve(probnumbig,tabalg,dt=1/2^6,adaptive=false,save_timeseries=false)

@test sol1.u[end] - sol2.u[end] < 1e-10

sol1 =solve(probbig,Vern7(),dt=1/2^3,adaptive=false,save_timeseries=false)
sol2 =solve(probbig,tabalg,dt=1/2^3,adaptive=false,save_timeseries=false)

@test minimum(sol1.u[end] - sol2.u[end] .< 1e-10)

sol1 =solve(probbig,tabalg,dt=1/2^6)
sol2 =solve(probbig,Vern7(),dt=1/2^6)

@test length(sol1) == length(sol2)

### TanYam7()

dts = 1.//2.^(6:-1:3)
sim = test_convergence(dts,probnumbig,TanYam7())
@test abs(sim.𝒪est[:l2]-7) < testTol
sim = test_convergence(dts,probbig,TanYam7())
@test abs(sim.𝒪est[:l2]-7) < testTol

tabalg = ExplicitRK(tableau=constructTanakaYamashitaEfficient7(Float64))
sol1 =solve(probnum,TanYam7(),dt=1/2^6,adaptive=false,save_timeseries=false)
sol2 =solve(probnum,tabalg,dt=1/2^6,adaptive=false,save_timeseries=false)

@test sol1.u[end] - sol2.u[end] < 1e-10

tabalg = ExplicitRK(tableau=constructTanakaYamashitaEfficient7(BigFloat))
sol1 =solve(probbig,TanYam7(),dt=1/2^3,adaptive=false,save_timeseries=false)
sol2 =solve(probbig,tabalg,dt=1/2^3,adaptive=false,save_timeseries=false)

@test minimum(sol1.u[end] - sol2.u[end] .< 1e-10)

sol1 =solve(prob,tabalg,dt=1/2^6)
sol2 =solve(prob,TanYam7(),dt=1/2^6)

@test length(sol1) == length(sol2)

### Vern8()

dts = 1.//2.^(6:-1:3)
sim = test_convergence(dts,probnumbig,Vern8())
@test abs(sim.𝒪est[:l2]-8) < testTol
sim = test_convergence(dts,probbig,Vern8())
@test abs(sim.𝒪est[:l2]-8) < testTol

tabalg = ExplicitRK(tableau=constructVerner8(BigFloat))
sol1 =solve(probnumbig,Vern8(),dt=1/2^6,adaptive=false,save_timeseries=false)
sol2 =solve(probnumbig,tabalg,dt=1/2^6,adaptive=false,save_timeseries=false)

@test sol1.u[end] - sol2.u[end] < 1e-10

sol1 =solve(probbig,Vern8(),dt=1/2^3,adaptive=false,save_timeseries=false)
sol2 =solve(probbig,tabalg,dt=1/2^3,adaptive=false,save_timeseries=false)

@test minimum(sol1.u[end] - sol2.u[end] .< 1e-10)

sol1 =solve(prob,tabalg,dt=1/2^6)
sol2 =solve(prob,Vern8(),dt=1/2^6)

@test length(sol1) == length(sol2)

### DP8()

dts = 1.//2.^(3:-1:1)
sim = test_convergence(dts,probnumbig,DP8())
@test abs(sim.𝒪est[:l2]-8) < testTol
sim = test_convergence(dts,probbig,DP8())
@test abs(sim.𝒪est[:l2]-8) < testTol

sol1 =solve(probnum,DP8(),dt=1/2^6,adaptive=false,save_timeseries=false)
sol2 =solve(probnum,DP8(),dt=1/2^6)

# Should be identical
sol1 =solve(probnum,DP8())
sol2 =solve(probnum,dop853())

@test sol1.t ≈ sol2.t

# Should be identical
sol1 =solve(probbig,DP8(),dt=1/2^6)
sol2 =solve(probbig,dop853(),dt=1/2^6)

@test sol1.t ≈ sol2.t

### TsitPap8()

dts = 1.//2.^(6:-1:3)
sim = test_convergence(dts,probnumbig,TsitPap8())
@test abs(sim.𝒪est[:l2]-8) < testTol
sim = test_convergence(dts,probbig,TsitPap8())
@test abs(sim.𝒪est[:l2]-8) < testTol

tabalg = ExplicitRK(tableau=constructTsitourasPapakostas8(BigFloat))
sol1 =solve(probnumbig,TsitPap8(),dt=1/2^6,adaptive=false,save_timeseries=false)
sol2 =solve(probnumbig,tabalg,dt=1/2^6,adaptive=false,save_timeseries=false)

@test sol1.u[end] - sol2.u[end] < 1e-10

sol1 =solve(probbig,TsitPap8(),dt=1/2^3,adaptive=false,save_timeseries=false)
sol2 =solve(probbig,tabalg,dt=1/2^3,adaptive=false,save_timeseries=false)

@test minimum(sol1.u[end] - sol2.u[end] .< 1e-10)

sol1 =solve(prob,tabalg,dt=1/2^6)
sol2 =solve(prob,TsitPap8(),dt=1/2^6)

@test length(sol1) == length(sol2)

### Vern9()

dts = 1.//2.^(6:-1:3)
sim = test_convergence(dts,probnumbig,Vern9())
@test abs(sim.𝒪est[:l2]-9) < testTol
sim = test_convergence(dts,probbig,Vern9())
@test abs(sim.𝒪est[:l2]-9) < testTol

tabalg = ExplicitRK(tableau=constructVernerEfficient9(BigFloat))
sol1 =solve(probnumbig,Vern9(),dt=1/2^6,adaptive=false,save_timeseries=false)
sol2 =solve(probnumbig,tabalg,dt=1/2^6,adaptive=false,save_timeseries=false)

@test abs(sol1.u[end] - sol2.u[end]) < 1e-15

sol1 =solve(probbig,Vern9(),dt=1/2^3,adaptive=false,save_timeseries=false)
sol2 =solve(probbig,tabalg,dt=1/2^3,adaptive=false,save_timeseries=false)

@test minimum(abs(sol1.u[end] - sol2.u[end]) .< 1e-15)

sol1 =solve(probbig,tabalg,dt=1/2^6)
sol2 =solve(probbig,Vern9(),dt=1/2^6)

@test length(sol1) == length(sol2)


using ITensors,
      LinearAlgebra, Test

@testset "ITensor Decompositions" begin

  @testset "truncate!" begin
    a = [-0.1, -0.12]
    @test NDTensors.truncate!(a) == (0., 0.)
    @test length(a) == 1
    a = [0.1, 0.01, 1e-13]
    @test NDTensors.truncate!(a,
                             use_absolute_cutoff=true,
                             cutoff=1e-5) == (1e-13, (0.01 + 1e-13)/2)
    @test length(a) == 2
  end

  @testset "factorize" begin
    i = Index(2,"i")
    j = Index(2,"j")
    A = randomITensor(i,j)
    @test_throws ErrorException factorize(A, i, dir="left")
    @test_throws ErrorException factorize(A, i, ortho="fakedir")
  end

  @testset "factorize with eigen_perturbation" begin
    l = Index(4,"l")
    s1 = Index(2,"s1")
    s2 = Index(2,"s2")
    r = Index(4,"r")

    phi = randomITensor(l,s1,s2,r)

    drho = randomITensor(l',s1',l,s1)
    drho += swapprime(drho,0,1)
    drho .*= 1E-5

    U,B = factorize(phi,(l,s1),ortho="left",eigen_perturbation=drho)
    @test norm(U*B - phi) < 1E-5

    # Not allowed to use eigen_perturbation with which_decomp
    # other than "automatic" or "eigen":
    @test_throws ErrorException factorize(phi,(l,s1),
                                          ortho="left",
                                          eigen_perturbation=drho,
                                          which_decomp="svd")
  end

  @testset "factorize with QR" begin
    l = Index(5,"l")
    s = Index(2,"s")
    r = Index(10,"r")
    A = randomITensor(l,s,r)
    Q,R, = factorize(A,l,s; which_decomp="qr")
    q = commonind(Q,R)
    @test A ≈ Q*R atol=1e-13
    @test Q*dag(prime(Q,q)) ≈ δ(Float64,q,q') atol=1e-13

    R,Q, = factorize(A,l,s; which_decomp="qr", ortho="right")
    q = commonind(Q,R)
    @test A ≈ Q*R atol=1e-13
    @test Q*dag(prime(Q,q)) ≈ δ(Float64,q,q') atol=1e-13
  end

  @testset "eigen" begin
    i = Index(2,"i")
    j = Index(2,"j")
    A = randomITensor(i,i')
    eigA = eigen(A)
    Dt, Ut = eigen(NDTensors.tensor(A))
    eigArr = eigen(array(A))
    @test diag(array(eigA.D), 0) ≈ eigArr.values
    @test diag(array(Dt), 0) == eigArr.values
  end

  @testset "exp function" begin
    At = rand(10, 10)
    k  = Index(10, "k")
    A = itensor(At + transpose(At), k, k')
    @test Array(exp(Hermitian(NDTensors.tensor(A)))) ≈ exp(At + transpose(At))
  end

  @testset "Spectrum" begin
    i = Index(100,"i")
    j = Index(100,"j")

    U,S,V = svd(rand(100,100))
    S ./= norm(S)
    A = itensor(U*ITensors.diagm(0=>S)*V', i,j)

    spec = svd(A,i).spec

    @test eigs(spec) ≈ S .^2
    @test truncerror(spec) == 0.0

    spec = svd(A,i; maxdim=length(S)-3).spec
    @test truncerror(spec) ≈ sum(S[end-2:end].^2)

    @test entropy(Spectrum([0.5; 0.5], 0.0)) == log(2)
    @test entropy(Spectrum([1.0], 0.0)) == 0.0
    @test entropy(Spectrum([0.0], 0.0)) == 0.0

    @test isnothing(eigs(Spectrum(nothing, 1.0)))
    @test_throws ErrorException entropy(Spectrum(nothing, 1.0))
    @test truncerror(Spectrum(nothing, 1.0)) == 1.0
  end

  @testset "Eigen QN flux regression test" begin
    cutoff = 1E-12
    N = 4
    s = siteinds("S=1",N;conserve_qns=true)
    A = randomITensor(QN("Sz",2),s[1],s[2],s[3])

    R = A*dag(prime(A,s[1],s[2]))
    F = eigen(R,(s[1],s[2]),(s[1]',s[2]'))

    @test flux(F.Vt)==QN("Sz",0)
  end
end

nothing


# You're already almost 1.5km (almost a mile) below the surface of the ocean,
# already so deep that you can't see any sunlight. What you can see, however,
# is a giant squid that has attached itself to the outside of your submarine.
#
# Maybe it wants to play bingo?
#
# Bingo is played on a set of boards each consisting of a 5x5 grid of numbers.
# Numbers are chosen at random, and the chosen number is marked on all boards
# on which it appears. (Numbers may not appear on all boards.) If all numbers
# in any row or any column of a board are marked, that board wins. (Diagonals
# don't count.)
#
# The submarine has a bingo subsystem to help passengers (currently, you and
# the giant squid) pass the time. It automatically generates a random order in
# which to draw numbers and a random set of boards (your puzzle input). For
# example:
#
# 7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1
#
# 22 13 17 11  0
#  8  2 23  4 24
# 21  9 14 16  7
#  6 10  3 18  5
#  1 12 20 15 19
#
#  3 15  0  2 22
#  9 18 13 17  5
# 19  8  7 25 23
# 20 11 10 24  4
# 14 21 16 12  6
#
# 14 21 17 24  4
# 10 16 15  9 19
# 18  8 23 26 20
# 22 11 13  6  5
#  2  0 12  3  7
#
# After the first five numbers are drawn (7, 4, 9, 5, and 11), there are no
# winners, but the boards are marked as follows (shown here adjacent to each
# other to save space):
#
# 22 13 17 11  0         3 15  0  2 22        14 21 17 24  4
#  8  2 23  4 24         9 18 13 17  5        10 16 15  9 19
# 21  9 14 16  7        19  8  7 25 23        18  8 23 26 20
#  6 10  3 18  5        20 11 10 24  4        22 11 13  6  5
#  1 12 20 15 19        14 21 16 12  6         2  0 12  3  7
#
# After the next six numbers are drawn (17, 23, 2, 0, 14, and 21), there are
# still no winners:
#
# 22 13 17 11  0         3 15  0  2 22        14 21 17 24  4
#  8  2 23  4 24         9 18 13 17  5        10 16 15  9 19
# 21  9 14 16  7        19  8  7 25 23        18  8 23 26 20
#  6 10  3 18  5        20 11 10 24  4        22 11 13  6  5
#  1 12 20 15 19        14 21 16 12  6         2  0 12  3  7
#
# Finally, 24 is drawn:
#
# 22 13 17 11  0         3 15  0  2 22        14 21 17 24  4
#  8  2 23  4 24         9 18 13 17  5        10 16 15  9 19
# 21  9 14 16  7        19  8  7 25 23        18  8 23 26 20
#  6 10  3 18  5        20 11 10 24  4        22 11 13  6  5
#  1 12 20 15 19        14 21 16 12  6         2  0 12  3  7
#
# At this point, the third board wins because it has at least one complete row
# or column of marked numbers (in this case, the entire top row is marked: 14
# 21 17 24 4).
#
# The score of the winning board can now be calculated. Start by finding the
# sum of all unmarked numbers on that board; in this case, the sum is 188.
# Then, multiply that sum by the number that was just called when the board
# won, 24, to get the final score, 188 * 24 = 4512.
#
# To guarantee victory against the giant squid, figure out which board will win
# first. What will your final score be if you choose that board?

# read input
open("input.txt", "r") do io
  # read numbers
  global numbers = parse.(Int, split(readline(io), ','))

  # read boards
  global boards = []
  while !eof(io)
    # blank line
    readline(io)

    board = Array{Int, 2}(undef, 5, 5)
    @inbounds for x in 1:5
      board[x, :] = parse.(Int, split(strip(readline(io)), r"\s+"))
    end
    push!(boards, board)
  end
end

# `results` keeps track of how many rows have matched, and how many columns
# have matched, per board. When either of those counts reach 5, bingo! We also
# keep track of the sum of matched numbers per board.
numboards = length(boards)
results = zeros(UInt8, numboards, 10)
sums = zeros(Int, numboards)
for num in numbers, b in 1:numboards
  idx = findfirst(isequal(num), boards[b])
  if !isnothing(idx)
    sums[b] += num
    if (results[b, idx[1]] += 1) == 5 || (results[b, idx[2] + 5] += 1) == 5
      unmarkedsum = sum(boards[b])
      println("Bingo!: ", (unmarkedsum - sums[b]) * num)
      break
    end
  end
end


@testset "Subgroup enumeration" begin
  @testset "p-subgroups" begin
    G = abelian_group([9, 3, 3])
    H = abelian_group([9, 3, 3, 5, 7])

    GG = abelian_group([8])
    @test 4 == length(collect(subgroups(GG)))

    @testset "All subgroups" begin
      @test 50 == length(collect(psubgroups(G, 3)))
    end

    @testset "Given subgroup type" begin

      T = psubgroups(G, 3, subtype = [3])
      @test 0 == length(collect(T))

      T = psubgroups(G, 3, subtype = [1, 1, 1, 1])
      @test 0 == length(collect(T))

      T = psubgroups(G, 3, subtype = [2])
      @test 9 == length(collect(T))
      @test all([snf(t[1])[1].snf == fmpz[9] for t in T])

      T = psubgroups(G, 3, subtype = [2])
      @test 9 == length(collect(T))
      @test all([snf(t[1])[1].snf == fmpz[9] for t in T])

      T = psubgroups(G, 3, subtype = [1, 1])
      @test 13 == length(collect(T))
      @test all([snf(t[1])[1].snf == fmpz[3, 3] for t in T])

      T = psubgroups(G, 3, subtype = Int[])
      @test 1 == length(collect(T))
      @test all([order(t[1]) == 1 for t in T])

      T = psubgroups(G, 3, subtype = Int[2, 1, 1])
      @test 1 == length(collect(T))
      @test all([snf(t[1])[1].snf == fmpz[3, 3, 9] for t in T])

      TH = psubgroups(G, 3, subtype = [3])
      @test 0 == length(collect(TH))

      TH = psubgroups(G, 3, subtype = [1, 1, 1, 1])
      @test 0 == length(collect(TH))

      TH = psubgroups(G, 3, subtype = [2])
      @test 9 == length(collect(TH))
      @test all([snf(t[1])[1].snf == fmpz[9] for t in TH])

      TH = psubgroups(G, 3, subtype = [2])
      @test 9 == length(collect(TH))
      @test all([snf(t[1])[1].snf == fmpz[9] for t in TH])

      TH = psubgroups(G, 3, subtype = [1, 1])
      @test 13 == length(collect(TH))
      @test all([snf(t[1])[1].snf == fmpz[3, 3] for t in TH])

      TH = psubgroups(G, 3, subtype = Int[])
      @test 1 == length(collect(TH))
      @test all([order(t[1]) == 1 for t in TH])

      TH = psubgroups(G, 3, subtype = Int[2, 1, 1])
      @test 1 == length(collect(TH))
      @test all([snf(t[1])[1].snf == fmpz[3, 3, 9] for t in TH])
    end

    @testset "Given quotient type" begin
      T = psubgroups(G, 3, quotype = [2], fun = quo)
      @test 9 == length(collect(T))
      @test all([snf(t[1])[1].snf == fmpz[9] for t in T])

      T = psubgroups(G, 3, quotype = [1, 1], fun = quo)
      @test 13 == length(collect(T))
      @test all([snf(t[1])[1].snf == fmpz[3, 3] for t in T])

      T = psubgroups(G, 3, quotype = [2, 1, 1], fun = quo)
      @test 1 == length(collect(T))
      @test all([snf(t[1])[1].snf == fmpz[3, 3, 9] for t in T])

      T = psubgroups(G, 3, quotype = Int[], fun = quo)
      @test 1 == length(collect(T))
      @test all([order(t[1]) == 1 for t in T])

      TH = psubgroups(G, 3, quotype = [2], fun = quo)
      @test 9 == length(collect(TH))
      @test all([snf(t[1])[1].snf == fmpz[9] for t in TH])

      TH = psubgroups(G, 3, quotype = [1, 1], fun = quo)
      @test 13 == length(collect(TH))
      @test all([snf(t[1])[1].snf == fmpz[3, 3] for t in TH])

      TH = psubgroups(G, 3, quotype = [2, 1, 1], fun = quo)
      @test 1 == length(collect(TH))
      @test all([snf(t[1])[1].snf == fmpz[3, 3, 9] for t in TH])

      TH = psubgroups(G, 3, quotype = Int[], fun = quo)
      @test 1 == length(collect(TH))
      @test all([order(t[1]) == 1 for t in TH])
    end

    @testset "Given order" begin

      T = psubgroups(G, 3, order = 1)
      @test 1 == length(collect(T))
      @test all([order(t[1]) == 1 for t in T])

      T = psubgroups(G, 3, order = 3)
      @test 13 == length(collect(T))
      @test all([order(t[1]) == 3 for t in T])

      T = psubgroups(G, 3, order = 9)
      @test 22 == length(collect(T))
      @test all([order(t[1]) == 9 for t in T])

      T = psubgroups(G, 3, order = 81)
      @test 1 == length(collect(T))
      @test all([order(t[1]) == 81 for t in T])

      TH = psubgroups(G, 3, order = 1)
      @test 1 == length(collect(TH))
      @test all([order(t[1]) == 1 for t in TH])

      TH = psubgroups(G, 3, order = 3)
      @test 13 == length(collect(TH))
      @test all([order(t[1]) == 3 for t in TH])

      TH = psubgroups(G, 3, order = 9)
      @test 22 == length(collect(TH))
      @test all([order(t[1]) == 9 for t in TH])

      TH = psubgroups(G, 3, order = 81)
      @test 1 == length(collect(TH))
      @test all([order(t[1]) == 81 for t in TH])
    end

    @testset "Given index" begin

      T = psubgroups(G, 3, index = 1, fun = quo)
      @test 1 == length(collect(T))
      @test all([order(t[1]) == 1 for t in T])

      T = psubgroups(G, 3, index = 3, fun = quo)
      @test 13 == length(collect(T))
      @test all([order(t[1]) == 3 for t in T])

      T = psubgroups(G, 3, index = 9, fun = quo)
      @test 22 == length(collect(T))
      @test all([order(t[1]) == 9 for t in T])

      T = psubgroups(G, 3, index = 81, fun = quo)
      @test 1 == length(collect(T))
      @test all([order(t[1]) == 81 for t in T])

      TH = psubgroups(G, 3, index = 1, fun = quo)
      @test 1 == length(collect(TH))
      @test all([order(t[1]) == 1 for t in TH])

      TH = psubgroups(G, 3, index = 3, fun = quo)
      @test 13 == length(collect(TH))
      @test all([order(t[1]) == 3 for t in TH])

      TH = psubgroups(G, 3, index = 9, fun = quo)
      @test 22 == length(collect(TH))
      @test all([order(t[1]) == 9 for t in TH])

      TH = psubgroups(G, 3, index = 81, fun = quo)
      @test 1 == length(collect(TH))
      @test all([order(t[1]) == 81 for t in TH])
    end
  end

  @testset "Arbitrary groups" begin
    G = abelian_group([3, 5, 7, 9, 25])

    @test_throws ErrorException subgroups(G, index = 3, subtype = [3, 1])

    @testset "All subgroups" begin
      @test 280 == length(collect(subgroups(G)))
    end

    @test_throws ErrorException subgroups(G, index = 3, order = 2)

    @testset "Given subtype" begin
      T = subgroups(G, subtype = [25, 27])
      @test length(collect(T)) == 0

      T = subgroups(G, subtype = [25, 9, 3])
      @test 5 == length(collect(T))
      @test all([snf(t[1])[1].snf == fmpz[3, 225] for t in T])

      T = subgroups(G, subtype = [5, 5, 9, 3])
      @test 1 == length(collect(T))
      @test all([snf(t[1])[1].snf == fmpz[15, 45] for t in T])

      T = subgroups(G, subtype = [3, 5, 9, 5])
      @test 1 == length(collect(T))
      @test all([snf(t[1])[1].snf == fmpz[15, 45] for t in T])

    end

    @testset "Given quotype" begin
      T = subgroups(G, quotype = [25, 27], fun = quo)
      @test 0 == length(collect(T))

      T = subgroups(G, quotype = [5, 7], fun = quo)
      @test 6 == length(collect(T))
      @test all([snf(t[1])[1].snf == fmpz[35] for t in T])
    end

    @testset "Given order" begin
      T = subgroups(G, order = 5*7*3)
      @test 24 == length(collect(T))
      @test all([order(t[1]) == 5*7*3 for t in T])
    end

    @testset "Given index" begin
      T = subgroups(G, index = 7*3, fun = quo)
      @test 4 == length(collect(T))
      @test all([order(t[1]) == 7*3 for t in T])
    end
  end
end


"""
aasol(GFix!, x0, m, Vstore; maxit=20,
      rtol=1.e-10, atol=1.e-10, beta=1.0, pdata=nothing, keepsolhist = false)

C. T. Kelley, 2021

Julia code for Anderson acceleration. Nothing fancy.

Solvers fixed point problems x = G(x).

You must allocate storage for the function and fixed point map
history --> in the calling program <-- in the array Vstore.

For an n dimensional problem with Anderson(m), Vstore must have
at least 2m + 4 columns and 3m + 3 is better.  If m=0 (Picard) then
V must have at least 4 columns.

Inputs:\n

- GFix!: fixed-point map, the ! indicates that GFix! overwrites xout, your
    preallocated storage for the function value xout=G(xin).\n
    So xout=GFix!(xout,xin) or xout=GFix!(xout,xin,pdata) returns
    xout=G(xin)

- sol: Initial iterate

- m: depth for Anderson acceleration. m=0 is Picard iteration

- Vstore: Working storage array. For an n dimensional problem Vstore
  should have at least 3m+3 columns unless you are storage bound. If storage
  is a problem, then you can allocate a minimum of 2m+4 columns. The smaller
  allocation exacts a performance penalty, especially for small problems
  and small values of m. So for Anderson(3), Vstore should be no smaller
  than zeros(n,8) with zeros(n,11) a better choice. Vstore needs to
  allocate for the history of differences of the residuals and fixed
  point maps. The extra m-1 columns are for storing intermediate results
  in the downdating phase of the QR factorization for the coefficeint
  matrix of the optimization problem. See the notebook or the print book
  for the details of this mess.

  If m=0, then Vstore needs 4 columns.

Keyword Arguments (kwargs):\n

maxit: default = 20\n
limit on nonlinear iterations\n

rtol and atol: default = 1.e-10\n
relative and absolute error tolerances\n

beta:\n
Anderson mixing parameter. Changes G(x) to (1-beta)x + beta G(x).
Equivalent to accelerating damped Picard iteration. The history
vector is the one for the damped fixed point map, not the original
one. Keep this in mind when comparing results.

pdata:\n
precomputed data for the fixed point map.
Things will go better if you use this rather than hide the data
in global variables within the module for your function.

keepsolhist: default = false\n
Set this to true to get the history of the iteration in the output
tuple. This is on by default for scalar equations and off for systems.
Only turn it on if you have use for the data, which can get REALLY LARGE.

Output:\n
- A named tuple (solution, functionval, history, stats, idid,
               errcode, solhist)
where

   -- solution = converged result

   -- functionval = G(solution)
      You might want to use functionval as your solution since it's
      a Picard iteration applied to the converged Anderson result. If G
      is a contraction it will be better than the solution.

   -- history = the vector of residual norms (||x-G(x)||) for the iteration

   -- stats = named tuple (condhist, alphanorm) of the history of the
              condition numbers of the optimization problem
              and l1 norm of the coefficients.
This is only for diagosing
problems and research. Condihist[k] and alphanorm[k] are
the condition number and coefficient norm for the optimization
problem that computes iteration k+1 from iteration k.

I record this for iterations k=1, ... until the final iteration
K. So I do not record the stats for k=0 or the final iteration.
We did record the data for the final iteration in Toth/Kelley
2015 at the cost of an extra optimiztion problem solve.
Since we've already terminated, there's not any point in
collecting that data.\n
Bottom line: if history has length K+1 for iterations
0 ... K, then condhist and alphanorm have length K-1.

   -- idid=true if the iteration succeeded and false if not.

   -- errcode = 0 if if the iteration succeeded

        = -1 if the initial iterate satisfies the termination criteria

        = 10 if no convergence after maxit iterations

   -- solhist:\n
This is the entire history of the iteration if you've set
keepsolhist=true\n

solhist is an N x K array where N is the length of x and K is the number of
iterations + 1.

### Examples for aasol

#### Duplicate Table 1 from Toth-Kelley 2015.

The final entries in the condition number and coefficient norm statistics
are never used in the computation and we don't compute them in Julia.
See the docstrings, notebook, and the print book for the story on this.

```jldoctest
julia> function tothk!(G, u)
       G[1]=cos(.5*(u[1]+u[2]))
       G[2]=G[1]+ 1.e-8 * sin(u[1]*u[1])
       return G
       end
tothk! (generic function with 1 method)

julia> u0=ones(2,); m=2; vdim=3*m+3; Vstore = zeros(2, vdim);
julia> aout = aasol(tothk!, u0, m, Vstore; rtol = 1.e-10);
julia> aout.history
8-element Vector{Float64}:
 6.50111e-01
 4.48661e-01
 2.61480e-02
 7.25389e-02
 1.53107e-04
 1.18513e-05
 1.82466e-08
 1.04725e-13

julia> [aout.stats.condhist aout.stats.alphanorm]
6×2 Matrix{Float64}:
 1.00000e+00  1.00000e+00
 2.01556e+10  4.61720e+00
 1.37776e+09  2.15749e+00
 3.61348e+10  1.18377e+00
 2.54948e+11  1.00000e+00
 3.67694e+10  1.00171e+00
```

Now we put a mixing or damping paramter in there with beta = .5. This
example is nasty enough to make mixing do ok. Keep in mind
that the history is for the damped residual, not the original one.

```
julia> bout=aasol(tothk!, u0, m, Vstore; rtol = 1.e-10, beta=.5);

julia> bout.history
7-element Vector{Float64}:
 3.25055e-01
 3.70140e-02
 1.81111e-03
 9.55308e-04
 1.25936e-05
 1.40854e-09
 2.18196e-12
```

#### H-equation example with m=2. This takes more iterations than
Newton, which should surprise no one.

```jldoctest
julia> n=16; x0=ones(n,); Vstore=zeros(n,20); m=2;
julia> hdata=heqinit(x0,.99);
julia> hout=aasol(HeqFix!, x0, m, Vstore; pdata=hdata);
julia> hout.history
12-element Vector{Float64}:
 1.47613e+00
 7.47800e-01
 2.16609e-01
 4.32017e-02
 2.66867e-02
 6.82965e-03
 2.70779e-04
 6.51027e-05
 7.35581e-07
 1.85649e-09
 4.94803e-10
 5.18866e-12

```
"""
function aasol(
    GFix!,
    x0,
    m,
    Vstore;
    maxit = 20,
    rtol = 1.e-10,
    atol = 1.e-10,
    beta = 1.0,
    pdata = nothing,
    keepsolhist = false,
)
    #
    # Startup
    #
    # Set up the storage
    #
    (sol, gx, df, dg, res, DG, QP, Qd, solhist) =
           Anderson_Init(x0, Vstore, m, maxit, beta, keepsolhist)
    res = copy(sol)
    #
    #   Iteration 1
    #
    k = 0
    ~keepsolhist || (@views solhist[:, k+1] .= sol)
    gx = EvalF!(GFix!, gx, sol, pdata)
    (beta == 1.0) || (gx=betafix!(gx, sol, beta))
    res .= gx - sol
    resnorm = norm(res)
    tol = rtol * resnorm + atol
    ItData = ItStatsA(resnorm)
    toosoon = (resnorm <= tol)
#    if toosoon
#        println("aasol terminates on entry")
#    else
    if ~toosoon
    #
    #   If we need more iterations, get organized.
    #
    sol .= gx
    alpha = zeros(m + 1)
    k = k + 1
    ~keepsolhist || (@views solhist[:, k+1] .= sol)
    (gx, dg, df, res, resnorm) =
        aa_point!(gx, GFix!, sol, res, dg, df, beta, pdata)
    updateHist!(ItData, resnorm)
    end
    n=length(x0)
    RF=zeros(m,m)
    RP=zeros(m,m)
    while (k < maxit) && resnorm > tol && ~toosoon
        if m == 0
            alphanrm = 1.0
            condit = 1.0
            sol .= gx
        else
            BuildDG!(DG,m,k+1,dg)
            (QP, RP) = aa_qr_update!(QP, RP, df, m, k-1, Qd)
            mk = min(m, k)
            @views QA=QP[:,1:mk]
            @views RA=RP[1:mk,1:mk]
            theta = RA\ (QA'*res)
            condit = cond(RA)
            alphanrm = falpha(alpha, theta, min(m, k))
            copy!(sol, gx)
            @views sol .-= DG[:, 1:mk] * theta
        end
        updateStats!(ItData, condit, alphanrm)
        k += 1
        ~keepsolhist || (@views solhist[:, k+1] .= sol)
        (gx, dg, df, res, resnorm) =
            aa_point!(gx, GFix!, sol, res, dg, df, beta, pdata)
        updateHist!(ItData, resnorm)
    end
    (idid, errcode) = AndersonOK(resnorm, tol, k, toosoon)
    aaout=CloseIteration(sol, gx, ItData, idid, errcode, keepsolhist, solhist)
    return aaout
end

"""
BuildDG!(DG,m,k,dg)

Keeps the history of the fixed point map differences
"""
function BuildDG!(DG,m,k,dg)
    if m == 1
        @views copy!(DG[:, 1], dg)
    elseif k > m + 1
        for ic = 1:m-1
            @views DG[:, ic] .= DG[:, ic+1]
        end
        @views copy!(DG[:, m], dg)
    else
        @views copy!(DG[:, k-1], dg)
    end
end

"""
aa_point!(gx, gfix, sol, res, dg, df, pdata)

Evaluate the fixed point map at the new point.
Keep the books to get ready to update the coefficient matrix
for the optimization problem.
"""
function aa_point!(gx, gfix, sol, res, dg, df, beta, pdata)
    dg .= -gx
    gx = EvalF!(gfix, gx, sol, pdata)
    (beta == 1.0) || (gx=betafix!(gx, sol, beta))
    dg .+= gx
    df .= -res
    res .= gx - sol
    df .+= res
    resnorm = norm(res)
    return (gx, dg, df, res, resnorm)
end

"""
betafix(gx, sol, dg, beta)

Put the mixing parameter beta in the right place.
"""
function betafix!(gx, sol, beta)
gx=axpby!((1.0-beta),sol,beta,gx)
return gx
end


using Revise
using PowerDynamics
using Plots
using DifferentialEquations
import PowerDynamics:rhs
# some additional functions that might develop into new PD.jl features
include("PDpatches.jl")

# custom node type
include("components/ThirdOrderEq.jl")

# load example system
include("../example_cases/BasinStabilityForPD/system.jl")

function rhs(pg::PowerGrid)
    network_dynamics(map(construct_vertex, pg.nodes), map(construct_edge, pg.lines), pg.graph)
end

powergrid = PowerGrid(node_list,line_list)
rpg = rhs(powergrid)

state(vec) = State(powergrid, vec)
project(vec) = find_valid_initial_condition(powergrid, vec)

## 1 IC guess

ic1 = ones(systemsize(powergrid)) |> state
ic0 = zeros(systemsize(powergrid)) |> state
icrand = rand(systemsize(powergrid)) |> state

# let's try a type-specific guess
sl = node_list[1]
guess(::Type{SlackAlgebraic}) = [sl.U, 0.] #[:u_r, :u_i]
guess(::Type{PQAlgebraic}) = [sl.U, 0.] #[:u_r, :u_i]
guess(::Type{ThirdOrderEq}) = [sl.U, 0., 0., 0.] #[:u_r, :u_i, :θ, :ω]
type_guesses = node_list .|> typeof .|> guess
icm = vcat(type_guesses...) |> state

op_ic1 = find_operationpoint(powergrid; ic_guess=ic1.vec, tol=1E-10)
op_ic0 = find_operationpoint(powergrid; ic_guess=ic0.vec, tol=1E-10)
op_icm = find_operationpoint(powergrid; ic_guess=icm.vec, tol=1E-10)

# random is random ...
op_icrand = find_operationpoint(powergrid; ic_guess=icrand.vec, tol=1E-14)

# we can try to improve nlsolve be using the sparse structure
# this only works for icm, the others fail
sp_op_icm = find_operationpoint_sparse(powergrid; ic_guess=icm.vec, tol=1E-10)

begin
    scatter(op_ic1[:, :u], label="1", legend=:topleft)
    scatter!(op_ic0[:, :u], label="0")
    scatter!(op_icrand[:, :u], label="rand")
    scatter!(op_icm[:, :u], label="mix")
    scatter!(sp_op_icm[:, :u], label="mix sp")
end

## 2 use ForwardDiff to check stability

# apparently the op's are not stable
# actually I am not sure whether eigvals(j(vec) * pinv(M) * M) is correct
check_eigenvalues(rpg, op_ic1)
check_eigenvalues(rpg, op_ic0)
check_eigenvalues(rpg, op_icrand)

# positive EV but small, maybe actually stable
check_eigenvalues(rpg, op_icm)

# this one seems to be stable!
check_eigenvalues(rpg, sp_op_icm)

## 3 compare with SteadyStateProblem in DiffEq

perturb = Perturbation(7, :ω, Inc(0.1))

function find_steady_state(rpg, u0)
    ode = ODEProblem(rpg, u0.vec, (0., 20.), nothing)
    ssp = SteadyStateProblem(ode)
    return solve(ssp, DynamicSS(Rodas5(); abstol=1e-8,reltol=1e-6,tspan=Inf)) |> Array |> state
end

de_sp_op_icm = find_steady_state(rpg, perturb(sp_op_icm))

begin
    scatter(sp_op_icm[:, :u], ms=10, label="mix sp", legend=:topleft)
    scatter!(de_sp_op_icm[:, :u], label="SSP")
end

# is this method maybe more robust?
de_op_icm = find_steady_state(rpg, perturb(op_icm))
de_op_ic1 = find_steady_state(rpg, perturb(op_ic1))
# this one diverges
#de_op_ic0 = find_steady_state(rpg, perturb(op_ic0))
de_op_icrand = find_steady_state(rpg, perturb(op_icrand))

begin
    scatter(de_op_ic1[:, :u], label="1", legend=:topleft)
    scatter!(de_op_icrand[:, :u], label="rand")
    scatter!(de_op_icm[:, :u], label="mix")
    scatter!(de_sp_op_icm[:, :u], label="mix sp")
end

# they are all basically stable within 1E-6
check_eigenvalues(rpg, de_op_ic1)
check_eigenvalues(rpg, de_op_icm)
check_eigenvalues(rpg, de_op_icrand)
check_eigenvalues(rpg, de_sp_op_icm)

## 4 check integration as ODE problem with mass_matrix

ω_idx = findall(:ω .∈ symbolsof.(node_list))

function sim(rpg, u0)
    ode = ODEProblem(rpg, u0.vec, (0., 20.), nothing)
    dqsol = solve(ode, Rodas5(), reltol=1e-12, abstol=1e-12)
    return PowerGridSolution(dqsol, powergrid)
end

# -> looks stable :-)
perturb = Perturbation(7, :ω, Inc(0.1))
sol = sim(rpg, perturb(de_sp_op_icm))
plot(sol, ω_idx, :ω, legend=false)

# now try the case in test_basinstability
# -> it gives dtmin error and stops
perturb = Perturbation(7, :ω, Inc(29.999995))

sol = sim(rpg, perturb(de_sp_op_icm))
plot(sol, ω_idx, :ω, legend=false)

## 6 check Sundials solvers
# https://docs.sciml.ai/stable/solvers/dae_solve/#Sundials.jl-1

using Sundials

ω_idx = findall(:ω .∈ symbolsof.(node_list))

#rpg_dense = ODEFunction(rpg.f, mass_matrix=Array(rpg.mass_matrix))

function sim(rpg, u0)
    ode = ODEProblem(rpg, u0.vec, (0., 20.), nothing)
    dqsol = solve(ode, IDA(linear_solver=:GMRES), reltol=1e-12, abstol=1e-12)
    return PowerGridSolution(dqsol, powergrid)
end

perturb = Perturbation(7, :ω, Inc(0.1))
sol = sim(rpg, perturb(de_sp_op_icm))
plot(sol, ω_idx, :ω, legend=false)

## 7 investigate DAE formulation

using LinearAlgebra
using Sundials

# load example system
include("../example_cases/DAE_Solver_PD/system.jl")

powergrid = PowerGrid(node_list,line_list)

state(vec) = State(powergrid, vec)
ω_idx = findall(:ω .∈ symbolsof.(node_list))

# ND.jl gives us an ODEFunction so we have to construct one manually
rpg_ode = rhs(powergrid)

function dae_form(res, du, u, p, t)
    du_temp = similar(du)
    rpg_ode.f(du_temp, u, p, t)
    @. res = du - du_temp
end

# construct initial guess
sl = node_list[1]
guess(::Type{SlackAlgebraic}) = [sl.U, 0.] #[:u_r, :u_i]
guess(::Type{PQAlgebraic}) = [sl.U, 0.] #[:u_r, :u_i]
guess(::Type{ThirdOrderEq}) = [sl.U, 0., 0., 0.] #[:u_r, :u_i, :θ, :ω]
type_guesses = node_list .|> typeof .|> guess
icm = vcat(type_guesses...) |> state

# find PD operation point
op_icm = find_operationpoint(powergrid; tol=1E-10)

u0 = op_icm.vec
du0 = similar(u0)
diff_vars = (diag(rpg_ode.mass_matrix) .== 1)
p = nothing
tspan = (0., 20.)

rpg_dae = DAEFunction{true, true}(dae_form, syms=rpg_ode.syms)
dae = DAEProblem(rpg_dae, du0, u0, tspan, p; differential_vars=diff_vars)

#ToDO : test other solvers, e.g. IDA(linear_solver=:GMRES)
# ShampineCollocationInit
sol = solve(dae, IDA(linear_solver=:GMRES), initializealg=BrownFullBasicInit())

# apparently, this doesn't run into a stable state
plot(PowerGridSolution(sol, powergrid), ω_idx, :ω, legend=false)
plot(PowerGridSolution(sol, powergrid), :, :v, legend=false)

perturb = Perturbation(2, :ω, Inc(31.))
u0 = perturb(op_icm)
du0 = similar(u0.vec)

dae = DAEProblem(rpg_dae, du0, u0.vec, (0., 100.); differential_vars=diff_vars)
dqsol = solve(dae, IDA(linear_solver=:GMRES), initializealg=BrownFullBasicInit())
pgsol = PowerGridSolution(dqsol, powergrid)

plot(pgsol, ω_idx, :ω, legend=false)
plot(pgsol, :, :v, legend=false)


using StatsBase

"""
    connectlocalizations!(connectID::Vector{Int64},
        clusterdata::Vector{Matrix{Float32}},
        params::ParamStruct, nframes::Int64)

Connect localizations in `clusterdata` by solving a linear assignment problem.

# Description
Connect localizations in `clusterdata` by giving "connected" localizations the
same integer value for their field `smd.connectID`.  Associations are made by
solving a linear assignment problem which designates the ideal connections
between localizations.
"""
function connectlocalizations!(connectID::Vector{Int64},
    clusterdata::Vector{Matrix{Float32}}, params::ParamStruct, nframes::Int64)

    # Loop through the clusters and solve the LAP.
    maxconnectID = maximum(connectID)
    idstocheck = findall(size.(clusterdata, 1) .> 1)
    for nn in idstocheck
        # Construct the cost matrix.
        costmatrix = create_costmatrix(clusterdata, params, nn, nframes)

        # Solve the LAP.
        assignment = solveLAP(costmatrix)

        # Update `connectID` to reflect the LAP solution.
        _, maxconnectID = linkclusters!(connectID, maxconnectID,
            Int64.(clusterdata[nn][:, 8]), assignment[1])
    end

    # Ensure `connectID` is contains the set of integers 1:nclusters.
    compress_connectID!(connectID)

    return connectID
end

"""
    connectID = connectlocalizations(connectID::Vector{Int64},
        clusterdata::Vector{Matrix{Float32}},
        params::ParamStruct, nframes::Int64)

Connect localizations in `clusterdata` by solving a linear assignment problem.

# Description
Connect localizations in `clusterdata` by giving "connected" localizations the
same integer value for their field `smd.connectID`.  Associations are made by
solving a linear assignment problem which designates the ideal connections
between localizations.
"""
function connectlocalizations(connectID::Vector{Int64},
    clusterdata::Vector{Matrix{Float32}}, params::ParamStruct, nframes::Int64)

    return connectlocalizations!(deepcopy(connectID), clusterdata, params, nframes)
end


# a rule within a decision tree for a single attribute value
mutable struct OneNode
    atr_value :: String     # symbol name of an attribute value (categorical data)
    prediction :: String    # predicted class for `atr_value`
    err_rate :: Real        # fraction [0 .. 1[ of how many instances are wrongly classified by this rule
    err_count :: Integer    # number of wrongly classified instances
end

# make all nodes for attribute `column`
function get_nodes(column::AbstractVector, observations::AbstractVector, uniq_obs :: AbstractVector)
    vals = unique(column)                         # possible attribute values
    nodes = Vector{OneNode}(undef, length(vals))  # for the results

    freq_table = Dict{String, Dict{String, Int}}()    # Dict of Dicts for counting frequencies

    # for each attribute value we need a Dict to count frequencies of each possible observation
    for v in vals
        atr_freq = Dict(cat => 0 for cat in uniq_obs)
        freq_table[v] = atr_freq
    end

    # let's count the observations for each attribute value
    for i in eachindex(column)
        freq_table[column[i]][observations[i]] += 1
    end

    # build the nodes of the tree (one for each attribute value)
    for i in eachindex(vals)
        maxfreq, cat = findmax(freq_table[vals[i]]) # find the most frequent category (and the number of it's occurences)
        sumfreq = sum(values(freq_table[vals[i]]))  # number or all occurences
        errfreq = sumfreq - maxfreq                 # wrongly classified occurences
        nodes[i] = OneNode(vals[i], cat, errfreq / sumfreq, errfreq)
    end
    return(nodes)
end

# print a 'OneNode' nicely formatted
function Base.show(io::IO, ::MIME"text/plain", node::OneNode)
    print(io, "OneNode: $(node.atr_value) -> $(node.prediction) (Err $(node.err_count), $(node.err_rate))")
end

# use the same formatting in compact mode
Base.show(io::IO, node::OneNode) = show(io, "text/plain", node)


__precompile__()

module RICoverAlg

# package code goes here

export preprocessinput,convertInputsToFloats, cancelZeroStates, checkForTriviality, transform_utilities
export do_cover_iterations,cancel_zero_alternatives,run_cover_algorithm,compute_factor
export risolve
# export checkOptimalityOfp

include("preprocessinput.jl")
include("cover_algorithm.jl")

# function checkOptimalityOfp(U,g,lmd,p)
#       Z = exp.(U./lmd)
#       ϵ_max = lmd * log(findmax(compute_factor(Z,g,p))[1])
#       payoff = lmd * ((log.(p'*Z))*g)[1]
#       return payoff, ϵ_max, payoff/(ϵ_max+payoff)
# end

function risolve(U, g, lmd;
                  initialIterations::Int  = 10,
                  aTolSetChoiceProbToZero::Float64 = sqrt(eps()),
                  aTolerance4Stopping::Float64 = 0.0,
                  maxIterations::Int = 9999)
      # solution = (optimalProbabilityVector, Maximal Payoff, Number of Iterations, Message)
      ϵ_max = 0
      Z, g, shiftterm, solution = preprocessinput(U, g, lmd)

      if solution[4] == "Run Cover Algorithm"
            ## running cover algorithm
            solution = run_cover_algorithm(Z, g,
                                          initialIterations  = initialIterations,
                                          aTolSetChoiceProbToZero = aTolSetChoiceProbToZero,
                                          aTolerance4Stopping = aTolerance4Stopping,
                                          maxIterations = maxIterations)
            ## computing optimality bounds
            ϵ_max = lmd * log(findmax(compute_factor(Z,g,solution[1]))[1])
      end

      payoff = lmd*(solution[2])+shiftterm
      mention_if_beyond_maxIterations = ""
      if solution[3] >= maxIterations
            mention_if_beyond_maxIterations = ", i.e., we reached maxIterations so that we aborted"
      end
      output_message = solution[4] * "; Number of iterations: $(solution[3])"*
                        mention_if_beyond_maxIterations*
                        "; Resulting payoff $payoff is $(payoff/(ϵ_max+payoff))"*
                        " optimal (maximal payoff <= $(ϵ_max+payoff))"
      return (solution[1],payoff,output_message)
end

end # module


##
# cauchymoment implements the moments of the cauchy transform of a space
#  note that it starts from k==1
##

cauchymoment(S...)=stieltjesmoment(S...)/(-2π*im)

#=
# gives \int_-1^1 x^(k-1)/(z-x) dx
function stieltjeslegendremoment(k::Integer,z)
    if k==1
        return -(log(z-1)-log(z+1))
    elseif k==2
        return -(2-z*log(1+z)+z*log(z-1))
    end

    error("stieltjesmoment not implemented for "*string(S.a)*string(S.b))
end

function stieltjeslegendremoment(k::Integer,z,s::Bool)
    if k==1
        return -(log(1-z)+(s?1:-1)*π*im-log(z+1))
    elseif k==2
        return -(2-z*log(1+z)+z*log(1-z) + (s?1:-1)*π*im*z)
    end

    error("stieltjesmoment not implemented for "*string(S.a)*string(S.b))
end

stieltjesmoment(S::Chebyshev,k::Integer,z)=stieltjeslegendremoment(k,tocanonical(S,z))
function stieltjesmoment(J::Jacobi,k::Integer,z)
    if k==1
        return stieltjeslegendremoment(k,z)
    elseif k==2
        if J.a==J.b
            return (1+J.a)*stieltjeslegendremoment(k,z)
        else
            return (J.b-J.a)/2*stieltjeslegendremoment(1,z) + (2+J.a+J.b)/2*stieltjeslegendremoment(2,z)
        end
    end

    error("Not implemented")
end

function stieltjesmoment(S::PolynomialSpace,k::Integer,z,s::Bool)
    z=tocanonical(S,z)

    if k==1
        return -(log(1-z)+(s?1:-1)*π*im-log(z+1))
    elseif k==2
        return -(2-z*log(1+z)+z*log(1-z) + (s?1:-1)*π*im*z)
    end

    error("stieltjesmoment not implemented for "*string(S.a)*string(S.b))
end

# represents atan(sqrt(z))/sqrt(z)
function sqrtatansqrt(x)
    if  isreal(x) && x ≤ 0
        y=sqrt(-x)
        log((1+y)/(1-y))/(2y)
    else
        sqrt(1/x)*atan(sqrt(x))
    end
end

function sqrtatansqrt(x,s::Bool)
    y=sqrt(-x)
    (log((1+y)/(y-1))-(s?1:-1)*π*im)/(2y)
end
=#

stieltjesmoment(S::WeightedJacobi,n::Int,z) = stieltjesjacobimoment(S.space.a,S.space.b,n,z)
stieltjesmoment(S::WeightedJacobi,z) = stieltjesjacobimoment(S.space.a,S.space.b,z)

stieltjesmoment(S::WeightedJacobiQ,n::Int,z) = stieltjesjacobimoment(S.space.a,S.space.b,n,z)
stieltjesmoment(S::WeightedJacobiQ,z) = stieltjesjacobimoment(S.space.a,S.space.b,z)

stieltjesmoment(S::Jacobi,n::Int,z) = stieltjesjacobimoment(S.a,S.b,n,z)
stieltjesmoment(S::Jacobi,z) = stieltjesjacobimoment(S.a,S.b,z)
stieltjesmoment(S::JacobiQ,n::Int,z) = stieltjesjacobimoment(S.a,S.b,n,z)
stieltjesmoment(S::JacobiQ,z) = stieltjesjacobimoment(S.a,S.b,z)

normalization(n::Int,α::Real,β::Real) = 2^(α+β)*gamma(n+α+1)*gamma(n+β+1)/gamma(2n+α+β+2)
stieltjesjacobimoment(α::Real,β::Real,n::Int,z) = (x = 2./(1-z);normalization(n,α,β)*(-x).^(n+1).*_₂F₁(n+1,n+α+1,2n+α+β+2,x))
stieltjesjacobimoment(α::Real,β::Real,z) = stieltjesjacobimoment(α,β,0,z)

function logjacobimoment(α::Real,β::Real,n::Int,z)
    x = 2./(1-z)
    if n == 0
        2normalization(0,α,β)*(log(z-1)-dualpart(_₂F₁(dual(zero(α)+eps(α+β),one(β)),α+1,α+β+2,x)))
        # For testing purposes only, should be equivalent to above within radius of convergence
        #2normalization(0,α,β)*(log(z-1)-(α+1)/(α+β+2)*x.*_₃F₂(α+2,α+β+3,x))
    else
        -2normalization(n,α,β)/n*(-x).^n.*_₂F₁(n,n+α+1,2n+α+β+2,x)
    end
end
logjacobimoment(α::Real,β::Real,z) = logjacobimoment(α,β,0,z)

stieltjeslegendremoment(n::Int,z) = stieltjesjacobimoment(zero(real(eltype(z))),zero(real(eltype(z))),n,z)
stieltjeslegendremoment(z) = stieltjeslegendremoment(0,z)

logabslegendremoment(z) = real(z*log((z+1)/(z-1))+log(z^2-1)-2)
@vectorize_1arg Number logabslegendremoment

#=
#TODO: this is for x^k but we probably want P_k

#These formulae are from mathematica
function stieltjesjacobimoment(α,β,k::Integer,z)
    if α == β == 0
        return stieltjeslegendremoment(k,z)
    elseif isapprox(α,0) && isapprox(β,0.5)
        if k==1
            return -2*sqrt(2)*(sqrtatansqrt(2/(z-1))-1)
        elseif k==2
            return 2*sqrt(2)*(1/3*(3z-2)-z*sqrtatansqrt(2/(z-1)))
        end
    elseif isapprox(α,0) && isapprox(β,-0.5)
        if k==1
            return 2sqrt(1/(z-1))*atan(sqrt(2/(z-1)))
        elseif k==2
            return z/sqrt(1-z)*log((1+1/sqrt(1-z))*(1-sqrt(2)/sqrt(1-z))/((1-1/sqrt(1-z))*(1+sqrt(2)/sqrt(1-z))))-
                2*sqrt(2)-2z/sqrt(1-z)*asinh(sqrt(-1/z))
        end
    elseif !isapprox(α,0) && isapprox(β,0.)
        return (-1)^k*stieltjesjacobimoment(β,α,k,-z)
    end
    error("stieltjesmoment not implemented for JacobiWeight "*string(α)*string(β))
end

function stieltjesjacobimoment(α,β,k::Integer,z,s::Bool)
    if α == β == 0
        return stieltjeslegendremoment(k,z,s)
    elseif isapprox(α,0) && isapprox(β,0.5)
        if k==1
            return -2*sqrt(2)*(sqrtatansqrt(2/(z-1))-1,!s)
        elseif k==2
            return 2*sqrt(2)*(1/3*(3z-2)-z*sqrtatansqrt(2/(z-1),!s))
        end
    elseif isapprox(α,0) && isapprox(β,-0.5)
        if k==1
            return 2im*(s?-1:1)*sqrt(1/(1-z))*atan((s?-1:1)*im*sqrt(2/(1-z)))
        elseif k==2 && real(z)<0
            return z/sqrt(1-z)*(logabs((1+1/sqrt(1-z))*(1-sqrt(2)/sqrt(1-z))/((1-1/sqrt(1-z))*(1+sqrt(2)/sqrt(1-z))))-π*im*(s?1:-1))-
                            2*sqrt(2)-2z/sqrt(1-z)*asinh(sqrt(-1/z))
        elseif k==2
            return z/sqrt(1-z)*log((1+1/sqrt(1-z))*(1-sqrt(2)/sqrt(1-z))/((1-1/sqrt(1-z))*(1+sqrt(2)/sqrt(1-z))))-
                             2*sqrt(2)-2z/sqrt(1-z)*asinh(im*(s?1:-1)*sqrt(1/z))
        end
    elseif !isapprox(α,0) && isapprox(β,0.)
        return (-1)^k*stieltjesjacobimoment(β,α,k,-z,!s)
    end
    error("stieltjesmoment not implemented for JacobiWeight "*string(α)*string(β))
end

stieltjesmoment{DD}(S::JacobiWeight{Chebyshev{DD},DD},k::Integer,z,s...)=stieltjesjacobimoment(S.α,S.β,k,tocanonical(S,z),s...)

function stieltjesmoment{T,DD}(S::JacobiWeight{Jacobi{T,DD},DD},k::Integer,z,s...)
    z=tocanonical(S,z)
    J=S.space

    if k==1
        return stieltjesjacobimoment(S.α,S.β,k,z,s...)
    elseif k==2
        if J.a==J.b
            return (1+J.a)*stieltjesjacobimoment(S.α,S.β,k,z,s...)
        else
            return (J.a-J.b)/2*stieltjesjacobimoment(S.α,S.β,1,z,s...) + (2+J.a+J.b)/2*stieltjesjacobimoment(S.α,S.β,2,z,s...)
        end
    end
    error("stieltjesmoment not implemented for JacobiWeight "*string(S.α)*string(S.β))
end
=#

function hilbertmoment(S::JacobiWeight,k::Integer,x)
    x=tocanonical(S,x)

    if S.α == 0 && S.β == 0.5
        if k==1
            if isapprox(x,1.0)
                return 2*sqrt(2)/π
            else
                y=sqrt(-2/(x-1))
                return (2*sqrt(2)-sqrt(2)*(log((1+y)/(y-1))/y))/π
            end
        elseif k==2

        end
    elseif S.α == 0.5 && S.β == 0.

    end
    (stieltjesmoment(true,S,k,x)+stieltjesmoment(false,S,k,x))/(2π)::Float64
end


# Check consistency with SPTK

import SPTK

function test_poledf()
    srand(98765)
    x = rand(100)
    c = rand(21)
    order = length(c)-1

    f = AllPoleDF(order)

    delay = SPTK.poledf_delay(order)

    for i=1:length(x)
        y = SPTK.poledf(x[i], c, delay)
        ŷ = filt!(f, x[i], c)
        @test !isnan(ŷ)
        @test y ≈ ŷ
    end
end

function test_lmadf(pade::Int)
    srand(98765)
    x = rand(100)
    c = rand(21)
    order = length(c)-1

    f = LMADF(order, pade=pade)

    # setup for SPTK mlsadf
    delay = SPTK.lmadf_delay(order, pade)

    for i=1:length(x)
        y = SPTK.lmadf(x[i], c, pade, delay)
        ŷ = filt!(f, x[i], c)
        @test !isnan(ŷ)
        @test y ≈ ŷ
    end
end

function test_mlsadf(α::Float64, pade::Int)
    srand(98765)
    x = rand(100)
    mc = rand(21)
    order = length(mc)-1

    f = MLSADF(order, α, pade=pade)
    @test SynthesisFilters.allpass_alpha(f) == α
    @test SynthesisFilters.glog_gamma(f) == 0.0

    # setup for SPTK mlsadf
    delay = SPTK.mlsadf_delay(order, pade)

    for i=1:length(x)
        y = SPTK.mlsadf(x[i], mc, α, pade, delay)
        ŷ = filt!(f, x[i], mc)
        @test !isnan(ŷ)
        @test y ≈ ŷ
    end
end

function test_mglsadf(α::Float64=0.41, ns::Int=10)
    srand(98765)
    x = rand(100)
    mc = rand(21)
    order = length(mc)-1

    f = MGLSADF(order, α, ns)
    @test SynthesisFilters.allpass_alpha(f) == α
    @test nstage(f) == ns
    @test SynthesisFilters.glog_gamma(f) == -1.0/ns

    # setup for SPTK mlsadf
    delay = SPTK.mglsadf_delay(order, ns)

    for i=1:length(x)
        y = SPTK.mglsadf(x[i], mc, α, ns, delay)
        ŷ = filt!(f, x[i], mc)
        @test !isnan(ŷ)
        @test y ≈ ŷ
    end
end

println("poledf: testing")
test_poledf()

for pade in [4, 5]
    println("lmadf: testing with pade=$pade")
    test_lmadf(pade)
end

for α in [0.35, 0.41, 0.544]
    for pade in [4, 5]
        println("mlsadf: testing with α=$α, pade=$pade")
        test_mlsadf(α, pade)
    end
end

for α in [0.35, 0.41, 0.544]
    for ns in 1:10
        println("mglsadf: testing with α=$α, nstage=$ns, γ=$(-1.0/ns)")
        test_mglsadf(α, ns)
    end
end


using ModelingToolkit
using Base.Test

d = DiscreteDomain(5) * Interval(0.0,5.0)
typeof(d) <: ProductDomain
typeof(d.domains[1]) <: DiscreteDomain
typeof(d.domains[2]) <: Interval


using Lasso, FactCheck
DATADIR = joinpath(dirname(@__FILE__), "data")

# Test against fused lasso results from glmgen
lakehuron = readcsv(joinpath(DATADIR, "LakeHuron.csv"); header=true)[1][:, 3]
facts("FusedLasso") do
	for lambda in (10, 1, 0.1)
		context("λ = $(lambda)") do
			@fact coef(fit(FusedLasso, lakehuron, lambda)) => roughly(vec(readcsv(joinpath(DATADIR, "LakeHuron_lambda_$lambda.csv"))))
		end
	end
end


filename() = "day24.txt"

# Cube coordindate system
# https://www.redblobgames.com/grids/hexagons/#coordinates-cube
struct Point{T}
    x::T
    y::T
    z::T
end

# Move a single step
function step(p::Point, direction::AbstractString)
    direction == "e"  && return Point(p.x+1, p.y-1, p.z)
    direction == "ne" && return Point(p.x+1, p.y, p.z-1)
    direction == "nw" && return Point(p.x, p.y+1, p.z-1)
    direction == "w"  && return Point(p.x-1, p.y+1, p.z)
    direction == "sw" && return Point(p.x-1, p.y, p.z+1)
    direction == "se" && return Point(p.x, p.y-1, p.z+1)
    error("Unknown direction")
end

function read_data()
    regex = r"(e|se|sw|w|nw|ne)"
    return [collect(m.match for m in eachmatch(regex, line))
        for line in readlines(filename())]
end

# Flip a single tile
function flip_tile!(tiles::AbstractDict, point::Point)
    if haskey(tiles, point)
        tiles[point] = -tiles[point]   # flip
    else
        tiles[point] = 1  # black
    end
end

# Initialize the tiles by flipping certain ones to black
function prepare_floor()
    instruction_set = read_data()
    tiles = Dict{Point{Int},Int}()
    for instructions in instruction_set
        point = foldl(step, instructions; init = Point(0, 0, 0))
        flip_tile!(tiles, point)
    end
    return tiles
end

is_black(p::Point, tiles::AbstractDict) = get(tiles, p, -1) == 1
is_white(p::Point, tiles::AbstractDict) = !is_black(p, tiles)

function neighbors(p::Point)
    return (
        Point(p.x, p.y + 1, p.z - 1),
        Point(p.x, p.y - 1, p.z + 1),
        Point(p.x + 1, p.y - 1, p.z),
        Point(p.x - 1, p.y + 1, p.z),
        Point(p.x + 1, p.y, p.z - 1),
        Point(p.x - 1, p.y, p.z + 1),
    )
end

function num_adjacent_black_tiles(p::Point, tiles::AbstractDict)
    return sum(is_black(q, tiles) for q in neighbors(p))
end

me_and_neighbors(p::Point) = (p, neighbors(p)...)

black_tiles(tiles::AbstractDict) = (k for (k,v) in tiles if v == 1)

function candidates(tiles::AbstractDict)
    return Set(Iterators.flatten(me_and_neighbors(p) for p in black_tiles(tiles)))
end

function decorate!(tiles::AbstractDict)
    need_flip = Point[]
    for p in candidates(tiles)
        nb = num_adjacent_black_tiles(p, tiles)
        if (is_black(p, tiles) && (nb == 0 || nb > 2)) ||
           (is_white(p, tiles) && nb == 2)
            push!(need_flip, p)
        end
    end
    foreach(p -> flip_tile!(tiles, p), need_flip)
end

count_black_tiles(tiles) = tiles |> black_tiles |> collect |> length

part1() = count_black_tiles(prepare_floor())

function part2(n = 100)
    tiles = prepare_floor()
    foreach(_ -> decorate!(tiles), 1:n)
    return count_black_tiles(tiles)
end

# Animation

using Luxor, Images

# Returns a new Luxor.Point location relative to `p`.
# The calculation of coordination requires the `radius` of hexagon.
# Additionally, a `shift` parameter can be provided to shift
# the calculated point in the 2D plane.
function cell_location(p::Point, radius::Float64, shift = Luxor.Point(0.0, 0.0))
    # First, move right (x-axis)
    θ = deg2rad(30)
    unit = 2 * radius * cos(θ)
    x = p.x * unit
    y = 0.0

    # Then, move up-left (z-axis)
    x -= -(p.z * (radius * cos(θ)))
    y -= -(p.z * (radius * sin(θ) + radius))
    return Luxor.Point(x, y) + shift
end

function draw_tile(
    point, radius, shift = Luxor.Point(0.0, 0.0);
    stroke_color, fill_color
)
    sethue(fill_color)
    ngon(cell_location(point, radius, shift), radius, 6, π/2, :fill)
    sethue(stroke_color)
    ngon(cell_location(point, radius, shift), radius, 6, π/2, :stroke)
end

draw_white_tile(args...) = draw_tile(args...; stroke_color="white", fill_color="black")
draw_black_tile(args...) = draw_tile(args...; stroke_color="white", fill_color="red2")

# Create an animated gif.
# Each frame contains the steps and the final flipped tile of an instruction.
# Specify `max_frmaes` for quick experiementation with first few frames.
function create_animated_floor(
    w, h;
    dir = "/tmp/day24", frame_rate = 30, max_frames = typemax(Int)
)
    instruction_set = read_data()
    tiles = Dict{Point{Int},Int}()

    # I used an image size of 600x600 to determine font size & relative positions
    # So the `scale` variable can be used to adjust all size settings for a custom w x h image.
    scale = w / 600.0
    radius = 8scale

    image = buffer = zeros(UInt32, w, h)
    shift = Luxor.Point(0.0, 10scale)

    # clean output directory
    for file in readdir(dir; join = true)
        # @debug "Removing $file"
        rm(file)
    end

    # remember if a cell has been visited before
    visited = Set()

    for (i, instructions) in enumerate(instruction_set)
        Drawing(w, h, :png)
        # On first frame, put on title and footer
        if i == 1
            sethue("green1")
            fontsize(25scale)
            text("Merry Christmas!", Luxor.Point(w/2.0, 40scale), halign=:center)
            fontsize(12scale)
            text("Tom Kwong, Advent of Code (Day 24, 2020)", Luxor.Point(w/2.0, h - 10scale), halign=:center)
        end
        origin()
        point = Point(0, 0, 0)
        for direction in instructions
            point = step(point, direction)
            point ∈ visited && continue
            push!(visited, point)
            draw_white_tile(point, radius, shift)
        end
        flip_tile!(tiles, point)
        if is_black(point, tiles)
            draw_black_tile(point, radius, shift)
        else
            draw_white_tile(point, radius, shift)
        end
        image = image_as_matrix!(buffer)
        finish()
        save("$(dir)/image-$(i).png", Images.RGB.(image))
        i > max_frames && break
    end
    run(`ffmpeg -framerate $(frame_rate) -f image2 -i $(dir)/image-%d.png -y $(dir)/floor.gif`)
end

# Draw a static floor image
function draw_floor()
    floor = prepare_floor()
    radius = 7.0
    @png begin
        background("black")
        for (point, color) in floor
            if color == 1
                draw_black_tile(point, radius)
            else
                draw_white_tile(point, radius)
            end
        end
    end
end


using QuantEcon: tauchen

model = :CapitalAdjustModel
dipar_1 = Dict(:nK => 100, :nz => 20, :β => 0.9, :ρ => 0.6, :σ => 0.3)
p_1_all = createmodel(model; dipar_1..., intdim = :All)
p_1_sep = createmodel(model; dipar_1..., intdim = :Separable)
p_1_sep_states = createmodel(model; dipar_1..., intdim = :Separable_States)
p_1_sep_exogstates = createmodel(model; dipar_1..., intdim = :Separable_ExogStates)

T_1_all = transitionmatrix(p_1_all)
T_1_sep = transitionmatrix(p_1_sep)
T_1_sep_states = transitionmatrix(p_1_sep_states)
T_1_sep_exogstates = transitionmatrix(p_1_sep_exogstates)

model = :CapitalAdjustModel2
dipar_2 = Dict(:nK => 20, :nN=>15, :nz => 3, :β => 0.9, :ρ => 0.6, :σ => 0.3)
p_2_all = createmodel(model; dipar_2..., intdim = :All)
p_2_sep = createmodel(model; dipar_2..., intdim = :Separable)
p_2_sep_states = createmodel(model; dipar_2..., intdim = :Separable_States)
p_2_sep_exogstates = createmodel(model; dipar_2..., intdim = :Separable_ExogStates)

T_2_all = transitionmatrix(p_2_all)
T_2_sep = transitionmatrix(p_2_sep)
T_2_sep_states = transitionmatrix(p_2_sep_states)
T_2_sep_exogstates = transitionmatrix(p_2_sep_exogstates)

@testset "transition matrix" begin

	@testset "compare" begin

		@testset "CapitalAdjustModel" begin

			T_all = T_1_all
			T_sep = T_1_sep
			T_sep_states = T_1_sep_states
			T_sep_exogstates = T_1_sep_exogstates

			nK = dipar_1[:nK]
			nz = dipar_1[:nz]

			# compare All and Separable
			# to do

			# compare Separable and Separable_States
			# to do

			# compare Separable_States and Separable_ExogStates
			tT_test = [reshape(T_sep_states,(nK, nz, nz))[j,:,:] for j = 1 : 5]
			@test all([T_sep_exogstates == T_test for T_test in tT_test])

		end

		@testset "CapitalAdjustModel2" begin

			T_all = T_2_all
			T_sep = T_2_sep
			T_sep_states = T_2_sep_states
			T_sep_exogstates = T_2_sep_exogstates

			nK = dipar_2[:nK]
			nN = dipar_2[:nN]
			nz = dipar_2[:nz]

			# compare All and Separable
			# to do

			# compare Separable and Separable_States
			# to do

			# compare Separable_States and Separable_ExogStates
			tT_test = [reshape(T_sep_states, (nK,nN,nz,nz))[j,j,:,:] for j = 1 : 5]
			@test all([T_sep_exogstates == T_test for T_test in tT_test])

		end

	end # compare

	@testset "external transitionmatrix" begin

		nz = dipar_1[:nz]
		ρ = dipar_1[:ρ]
		σ = dipar_1[:σ]

		# have same nodes as in tauchen?
		@test isapprox(collect(tauchen(nz, ρ, σ).state_values),
			p_1_sep_exogstates.tStateVectors[2], rtol = mytol)
		solve(p_1_sep_exogstates, mTransition = tauchen(nz, ρ, σ).p)

	end

end # transition matrix


# # Advanced Example: Custom Impurity Profile

# A very important input to the simulation is the impurity profile of the semiconductor.
# It influences the electric potential and, thus, the electric field, capacitances,
# drift paths and also the induced waveforms.

# Arbitrary impurity density profiles can be defined and assigned to the semiconductor of the detector.
# Also have a look into the manual: [Impurity Densities](@ref) and [Custom Impurity Density](@ref).

# As an example, we define the impurity profile of a simple [p-n junction](https://en.wikipedia.org/wiki/P%E2%80%93n_junction)
# and calculate the electric potential and depleted region and reproduce the
# typical [plot](https://en.wikipedia.org/wiki/P%E2%80%93n_junction#/media/File:Pn-junction-equilibrium-graphs.png)
# shown in various books explaining p-n junctions.

# For this, we use one of the example detectors: the infinite parallel plate capacitor in cartesian coordinates.
# The plates span over `y` and `z`. It is made infinite by defining the world smaller than the contacts
# and using reflecting boundaries in `y` and `z`.
# Let's get started by loading the packages and the detector and have a look at its geometry:

using Plots
using SolidStateDetectors
using Unitful

sim = Simulation{Float32}(SSD_examples[:InfiniteParallelPlateCapacitor])
plot( sim.detector.semiconductor, fillalpha = 0.4)
plot!(sim.detector, xlims = (-0.006, 0.006), aspect_ratio = :none)

# ## Define the Impurity Density

# Now, we define the model for the impurity density of the p-n junction.
# In order to do so, we need to define two things.

# 1\. We have to define a `struct` which has to be
#    subtype of `SolidStateDetectors.AbstractImpurityDensity{T}`.
#    We define the model with two parameters in order to be able to vary the density level
#    in the p-type and n-type regions independently.
#    More parameters could be added, e.g., the position of the p-n junction.
struct PNJunctionImpurityDensity{T} <: SolidStateDetectors.AbstractImpurityDensity{T}
    p_type_density::T
    n_type_density::T
end
# 2\. We have to define a method for `SolidStateDetectors.get_impurity_density` for our
#    just defined impurity density.
#    It has to take two arguments. An instance of our model and a point.
#    The function returns the impurity density at the respective position.
#    The returned value has to be in units of 1/m``^{3}``.

#=
!!! note "Sign of the impurity density"
    The sign of the impurity density determines whether the semiconductor is p-type or n-type.

    p-type region <-> negative sign: Holes are the majority carriers and are free to move and diffuse into the n-type region.
        Electrons are fixed in the lattice. Thus, a negative fixed space charge density is left behind in the depleted p-type region.

    n-type region <-> positive sign: Electrons are the majority carriers and are free to move and diffuse into the p-type region.
        Holes are fixed in the lattice. Thus, a positive fixed space charge density is left behind in the depleted n-type region.
=#

function SolidStateDetectors.get_impurity_density(
    cdm::PNJunctionImpurityDensity{T},
    pt::SolidStateDetectors.AbstractCoordinatePoint{T}
)::T where {T}
    cpt = CartesianPoint(pt)
    x = cpt[1] # In this example, we only need the `x` coordinate of the point.
    if x > 0
        -cdm.p_type_density # p-type region -> electrons are fixed -> negative charge
    else
        cdm.n_type_density  # n-type region -> holes are fixed -> positive charge
    end
end

# ## Assign the Density and Calculate the Fields

# Now, we create an instance of our model with reasonable parameters
# (for the geometry and bias voltage of the example detector)
# and assign it to the detector.

pn_junction_impurity_density = PNJunctionImpurityDensity{Float32}(3e16, 1.5e16)
sim.detector = SolidStateDetector(sim.detector, pn_junction_impurity_density);

# We are now ready to calculate the electric potential.
# Depletion handling has to be turned on, via the keyword `depletion_handling = true`,
# in order to detect the regions where the detector is depleted.
# The other settings for `calculate_electric_potential!` are very specific for this effectively 1D problem
# to be calculated in 3D.
# Afterwards, we also calculate the electric field and plot the
# electric potential, impurity scale map and the point type map.
# The two latter ones show us where the detector is depleted (`imp_scale == 1`).

calculate_electric_potential!(
    sim,
    convergence_limit = 1e-6,
    max_tick_distance = (0.002, 1, 1) .* u"cm",
    min_tick_distance = (1e-7, 1, 1) .* u"m",
    refinement_limits = [0.005, 0.001],
    depletion_handling = true
)
calculate_electric_field!(sim)

plot(
    plot(sim.electric_potential, y = 0),
    plot(sim.imp_scale, y = 0),
    plot(sim.point_types, y = 0),
    layout = (3, 1),
    aspect_ratio = :none,
    size = (800, 800)
)

# ## 1D Plots of the P-N Junction

# As this is in principle a 1D simulation, it is most reasonable to plot the
# charge density from the impurities, the electric potential and
# the electric field strength (here equals the `x` component of the electric field)
# only over `x`.

xs = uconvert.(u"mm", sim.electric_potential.grid[1] * u"m");
plot(
    begin
        ρ_x = map(x -> SolidStateDetectors.get_impurity_density(
            sim.detector.semiconductor.impurity_density_model, CartesianPoint{Float32}(x, 0, 0)),
            sim.electric_potential.grid[1]
        )
        plot(xs, ρ_x .* sim.imp_scale[:, 1, 1], lw = 4, label ="", xguide = "x", unitformat = :square, yguide = "\$\\rho\$ [e/m\$^3\$]")
        vline!([sim.detector.contacts[1].geometry.origin.x] * 1000, lw = 4, color = "green", label = "N+ Contact")
        vline!([sim.detector.contacts[2].geometry.origin.x] * 1000, lw = 4, color = "red", label = "P+ Contact")
        vline!([0], lw = 4, color = "black", label = "p-n junction")
    end,
    begin
        plot(xs, sim.electric_potential.data[:,1,1], lw = 4, label ="", xguide = "x", unitformat = :square, yguide = "\$\\Phi\$ [V]")
        vline!([sim.detector.contacts[1].geometry.origin.x] * 1000, lw = 4, color = "green", label = "N+ Contact")
        vline!([sim.detector.contacts[2].geometry.origin.x] * 1000, lw = 4, color = "red", label = "P+ Contact")
        vline!([0], lw = 4, color = "black", label = "p-n junction")
    end,
    begin
        plot(xs, map(E -> E[1]/1000, sim.electric_field.data[:,1,1]), lw = 4, label ="", xguide = "x", unitformat = :square, yguide = "\$\\mathcal{E}_x\$ [V/mm]")
        vline!([sim.detector.contacts[1].geometry.origin.x] * 1000, lw = 4, color = "green", label = "N+ Contact")
        vline!([sim.detector.contacts[2].geometry.origin.x] * 1000, lw = 4, color = "red", label = "P+ Contact")
        vline!([0], lw = 4, color = "black", label = "p-n junction")
    end,
    layout = (3, 1),
    xlims = (-5.1, 5.1),
    size = (800, 800),
    lw = 2,
)


# created: 20210519
# 2D linear rate model with balanced amplification effect.

push!(LOAD_PATH, abspath(@__DIR__,".."))

using LinearAlgebra,Statistics,StatsBase
using Plots,NamedColors ; theme(:dark)
using SparseArrays
using SpikingRNNs; const global S = SpikingRNNs

## Parameters
ne = 1
ni = 1

τe = 1.
τi = 1.

α = 1.

# Define neuron types and population states

neuron_e = S.NTReLU(τe,α)
neuron_i = S.NTReLU(τi,α)
pse  = S.PSRate(neuron_e,ne)
psi  = S.PSRate(neuron_i,ni)

# Define the connections
function onesparsemat(w::Real)
  mat = Matrix{Float64}(undef,1,1)
  mat[1,1] = w
  return sparse(mat)
end

(w_ee,w_ie,w_ei,w_ii) = let w = 20. , k = 1.1
  onesparsemat.((w,w,-k*w,-k*w))
end

conn_ee = S.BaseConnection(w_ee)
conn_ei = S.BaseConnection(w_ei)
conn_ie = S.BaseConnection(w_ie)
conn_ii = S.BaseConnection(w_ii)

# inputs
in_state_e = S.PSSimpleInput(S.InputSimpleOffset(0.0))
in_state_i = S.PSSimpleInput(S.InputSimpleOffset(0.0))

# populations are population states, plus all incoming connections
# plus presynaptic population states

pop_e = S.Population(pse,(conn_ee,conn_ei,S.FakeConnection()),
  (pse,psi,in_state_e))
pop_i = S.Population(psi,(conn_ie,conn_ii,S.FakeConnection()),
  (pse,psi,in_state_i))

dt = 1E-4
T = 10.0
times = 0:dt:T
ntimes = length(times)
mynetwork = S.RecurrentNetwork(dt,(pop_e,pop_i))

e_out = Vector{Float64}(undef,ntimes)
i_out = Vector{Float64}(undef,ntimes)

##

# initial conditions
pse.state_now[1] = S.ioinv(10.0,pse)
psi.state_now[1] = S.ioinv(5.0,pse)

for (k,t) in enumerate(times)
  e_out[k] = S.iofunction(pse.state_now[1],pse)
  i_out[k] = S.iofunction(psi.state_now[1],psi)
  # rate model with constant input  does not really depend on absolute time (first argument)
  S.dynamics_step!(0.0,mynetwork)
end

plot(times,[e_out i_out];linewidth=4,leg=:topright,
  color=[colorant"Teal" colorant"Salmon"],label=["exc" "inh"])


using GoldenSequences
using Base.Iterators
using LinearAlgebra
using Statistics
using Test
using Random
using Base.MathConstants
min_dist(x) = minimum(norm(x[i] .- x[j]) for (i,j) in product(eachindex(x), eachindex(x)) if i != j)

@testset "traits" begin
    @test eltype(GoldenSequence(3)) === Tuple{Float64, Float64, Float64}
    for g in [GoldenSequence(3), GoldenIntSequence(UInt, 3), GoldenCartesianSequence((13,14))]
        @test eltype(g) == eltype(collect(take(g, 3)))
    end
end

Random.seed!(1)
for d in 1:6
    @testset "dim d=$d" begin
        n = 5000
        xrand = [ntuple(d) do i rand() end for i in 1:n]
        xgolden = collect(take(GoldenSequence(d), n))
        @test all(isa.(xgolden, Ref(eltype(GoldenSequence(d)))))
        @test eltype(xgolden) == eltype(GoldenSequence(d))
        xgolden_ = reshape(reinterpret(Float64, xgolden), (d, n))
        xrand_ = reshape(reinterpret(Float64, xrand), (d, n))

        # Var(rand()) = 1/12

        @test all(xgolden_[1:d, 1] .== xgolden[1])
        @test norm(mean(xgolden_, dims=2) .- 0.5) < 1/sqrt(n)
        @test norm(cov(xgolden_') - I/12) < 1/sqrt(n)
        @test norm(mean(xgolden_, dims=2) .- 0.5) < norm(mean(xrand_, dims=2) .- 0.5)

        @test min_dist(xgolden) > min_dist(xrand)
        #@test norm(cov(xgolden_') - I/12) <  norm(cov(xrand_') - I/12)
    end
end

@testset "Numbers" begin
    @test collect(take(GoldenSequence(0.0), 10))[2] ≈ 1/golden
end
function f()
    g = GoldenSequence((0.,0.,0.))
    x, s = iterate(g)
    xsum = x[1] + x[2] + x[3]
    for i in 1:1000-1
        x, s = iterate(g, s)
        xsum += x[1] + x[2] + x[3]
    end
    xsum
end

@testset "Allocation" begin
    @test f() ≈ 1498.3407019608956
    @test  (@allocated f()) < 200
end
nothing


module Alea

include("internal/Internal.jl")
include("math/Math.jl")
include("statistics/Statistics.jl")
include("quadrature/Quadrature.jl")
include("bases/Bases.jl")
include("gpc/GPC.jl")

using Reexport
@reexport using .Math
@reexport using .Statistics
@reexport using .Quadrature
@reexport using .Bases
@reexport using .GPC

end


# Gyromagnetic ratio
const γ = 2.675222005 * 1e2 # [rad / (ms * mT)]


function whitespacenumber(n)
    result = Char[]
    append!(result, n<0 ? '\t' : ' ' )
    if n ≠ 0
      for b in Iterators.reverse(digits(n,base=2))
          append!( result, b==0 ? ' ' : '\t' )
      end
    end
    append!(result,'\n')
    String(result)
end


function nummax(A)
    max = A[firstindex(A)]
    count = 1
    for i in (firstindex(A):lastindex(A))
        if A[i] > max
            max = A[i]
            count = 0
        end
        if A[i] == max
            count += 1
        end
    end
    return count
end

print(nummax([3,4,1,2,7,0,1,9,2,3,3,4,5,6]))


using SparseTransforms
using Test

function test_signal()
    println("Testing signal retrieval")
    n, k = 8, 3
    locs = sample(0:2^n-1, 2^k, replace=false)
    strengths = Float64.(rand(Uniform(0.1, 10), 2^k)) .* (-1) .^ rand(Bool, 2^k)
    signal = TestSignal(n, locs, strengths)

    lazy_signal = LazySignal(n, locs, strengths)

    println("Testing lazy signal")
    for i = 1:2^n
        @test signal[i] ≈ lazy_signal[i]
    end

    println("Testing multiple index subsampling")
    for _ = 1:20
        idxs = sample(1:2^n-1, 2^k, replace=false)
        @test signal[idxs] ≈ lazy_signal[idxs]
    end
end


abstract type AbstractPolynomialVectorBasis{PT<:MP.AbstractPolynomialLike, PV<:AbstractVector{PT}} <: AbstractPolynomialBasis end

Base.length(basis::AbstractPolynomialVectorBasis) = length(basis.polynomials)
Base.copy(basis::AbstractPolynomialVectorBasis) = typeof(basis)(copy(basis.polynomials))

MP.nvariables(basis::AbstractPolynomialVectorBasis) = MP.nvariables(basis.polynomials)
MP.variables(basis::AbstractPolynomialVectorBasis) = MP.variables(basis.polynomials)
MP.monomialtype(::Type{<:AbstractPolynomialVectorBasis{PT}}) where PT = MP.monomialtype(PT)

empty_basis(B::Type{<:AbstractPolynomialVectorBasis{PT, Vector{PT}}}) where PT = B(PT[])
function MP.polynomialtype(basis::AbstractPolynomialVectorBasis{PT}, T::Type) where PT
    C = MP.coefficienttype(PT)
    U = MA.promote_operation(*, C, T)
    V = MA.promote_operation(+, U, U)
    return MP.polynomialtype(PT, V)
end
function MP.polynomial(f::Function, basis::AbstractPolynomialVectorBasis)
    return MP.polynomial(mapreduce(
        ip -> f(ip[1]) * ip[2], MA.add!, enumerate(basis.polynomials)))
end

function MP.polynomial(Q::AbstractMatrix, basis::AbstractPolynomialVectorBasis,
                       T::Type)
    n = length(basis)
    @assert size(Q) == (n, n)
    return MP.polynomial(mapreduce(row -> adjoint(basis.polynomials[row]) *
        mapreduce(col -> Q[row, col] * basis.polynomials[col], MA.add!, 1:n),
        MA.add!, 1:n), T)
end

"""
    struct FixedPolynomialBasis{PT<:MP.AbstractPolynomialLike, PV<:AbstractVector{PT}} <: AbstractPolynomialBasis
        polynomials::PV
    end

Polynomial basis with the polynomials of the vector `polynomials`.
For instance, `FixedPolynomialBasis([1, x, 2x^2-1, 4x^3-3x])` is the Chebyshev
polynomial basis for cubic polynomials in the variable `x`.
"""
struct FixedPolynomialBasis{PT<:MP.AbstractPolynomialLike, PV<:AbstractVector{PT}} <: AbstractPolynomialVectorBasis{PT, PV}
    polynomials::PV
end


# ---
# title: 743. Network Delay Time
# id: problem743
# author: Indigo
# date: 2021-07-01
# difficulty: Medium
# categories: Heap, Depth-first Search, Breadth-first Search, Graph
# link: <https://leetcode.com/problems/network-delay-time/description/>
# hidden: true
# ---
#
# There are `N` network nodes, labelled `1` to `N`.
#
# Given `times`, a list of travel times as **directed** edges `times[i] = (u, v,
# w)`, where `u` is the source node, `v` is the target node, and `w` is the time
# it takes for a signal to travel from source to target.
#
# Now, we send a signal from a certain node `K`. How long will it take for all
# nodes to receive the signal? If it is impossible, return `-1`.
#
#
#
# **Example 1:**
#
# ![](https://assets.leetcode.com/uploads/2019/05/23/931_example_1.png)
#
#
#
#     Input: times = [[2,1,1],[2,3,1],[3,4,1]], N = 4, K = 2
#     Output: 2
#
#
#
#
# **Note:**
#
#   1. `N` will be in the range `[1, 100]`.
#   2. `K` will be in the range `[1, N]`.
#   3. The length of `times` will be in the range `[1, 6000]`.
#   4. All edges `times[i] = (u, v, w)` will have `1 <= u, v <= N` and `0 <= w <= 100`.
#
#
## @lc code=start
using LeetCode

function network_delay_time(times::Vector{Vector{Int}}, n::Int, k::Int)
    graph = [Tuple{Int,Int}[] for _ in 1:n]
    for (u, v, t) in times
        push!(graph[u], (v, t))
    end
    pq = PriorityQueue{Int,Int}([(k, 0)])
    res = Int[]
    while !isempty(pq)
        node, d = dequeue_pair!(pq)
        for (neb, d1) in graph[node]
            pq[neb] = min(get(pq, neb, typemax(Int)), d + d1)
        end
        push!(res, d)
    end
    return length(res) == n ? maximum(res) : -1
end
## @lc code=end


import Arpack: eigs
using LinearAlgebra

function pcca(T::Matrix, n::Integer; optimize = false, solver=BaseSolver())
    israte = isratematrix(T)
    pi     = stationarydensity(T, israte)
    X, λ   = schurvectors(T, pi, n, israte, solver)
	assertstructure(X, pi)
    chi    = makeprobabilistic(X, optimize)
end

function isratematrix(T::Matrix)
    s = sum(T[1,:])
    isapprox(s, 0) && return true
    isapprox(s, 1) && return false
    error("given matrix is neither a rate nor a probability matrix")
end

function stationarydensity(T, israte=isratematrix(T))
    which = israte ? :SM : :LM
    pi = eigs(T', nev=1, which=which)[2] |> vec
	#pi = eigvecs(T, sortby=real)[:,end]
    @assert isreal(pi)
    pi = abs.(pi)
    pi = pi / sum(pi)
end

function makeprobabilistic(X::Matrix, optimize::Bool)
    n = size(X, 2)
    A = innersimplexalgorithm(X)
    if n > 2 && optimize
        A = opt(A, X)
    end
    chi = X*A
end

function crispassignments(chi)
     assignments = mapslices(argmax, chi, dims=2) |> vec
end

function assertstructure(X, pi)
	@assert X' * Diagonal(pi) * X ≈ I
	@assert X[:,1] ≈ ones(length(pi))
end

# compute initial guess based on indexmap
innersimplexalgorithm(X) = feasiblize!(inv(X[indexmap(X), :]), X)

function indexmap(X)
    # get indices of rows of X to span the largest simplex
    rnorm(x) = sqrt.(sum(abs2.(x), dims=2)) |> vec
    ind=zeros(Int, size(X,2))
    for j in 1:length(ind)
        rownorm=rnorm(X)
        # store largest row index
        ind[j]=argmax(rownorm)
        if j == 1
            # translate to origin
            X = X .- X[ind[1],:]'
        else
            # remove subspace
            X=X/rownorm[ind[j]]
            vt=X[ind[j],:]'
            X=X-X*vt'*vt
        end
    end
    return ind
end

function feasiblize!(A,X)
    A[:,1] = -sum(A[:,2:end], dims=2)
    A[1,:] = -minimum(X[:,2:end] * A[2:end,:], dims=1)
    A / sum(A[1,:])
end

# crispness criterion, cf. Roeblitz (2013)
# only applies if X is normalized (eq. 8)
# TODO: check does this apply?
function roeblitzcrit(A)
    n = size(A,1)
    trace = 0
    for i = 1:n, j=1:n
        trace += A[i,j]^2 / A[1,j]
    end
    return trace
end

using Optim
function opt(A0, X)
    A = copy(A0)
    Av = view(A, 2:size(A,1), 2:size(A,2)) # view on the variable part

    function obj(a)
        Av[:] = a
        -roeblitzcrit(feasiblize!(A, X))
    end

    result = optimize(obj, Av[:], NelderMead())
    Av[:] = result.minimizer
    return feasiblize!(A, X)
end

function isreversible(P, pi=stationarydensity(P))
    db = [pi[i] * P[i,j] - pi[j] * P[j,i] for i =1:5, j=1:5]
    all(isapprox.(db, 0, atol=1e-8))
end


function geometrixmatrix(l::Float64)
  gm = zeros(12, 12)
  pt1 = 0.1
  opt2 = 1.2
  two = 2.0
  d15 = 15.0
  d30 = 30.0

  gm[1, 1]   = opt2 / l
  gm[1, 5]   = pt1
  gm[1, 7]   = -opt2 / l
  gm[1, 11]  = pt1

  gm[2, 2]   = opt2 / l
  gm[2, 4]   = -pt1
  gm[2, 8]   = -opt2 / l
  gm[2, 10]  = -pt1

  gm[4, 2]   = -pt1
  gm[4, 4]   = two * l / d15
  gm[4, 8]   = pt1
  gm[4, 10]  = -l / d30

  gm[5, 1]   = pt1
  gm[5, 5]   = two * l / d15
  gm[5, 7]   = -pt1
  gm[5, 11]  = -l / d30

  gm[7, 1]   = -opt2 / l
  gm[7, 5]   = -pt1
  gm[7, 7]   = opt2 / l
  gm[7, 11]  = -pt1

  gm[8, 2]   = -opt2 / l
  gm[8, 4]   = pt1
  gm[8, 8]   = opt2 / l
  gm[8, 10]  = pt1

  gm[10, 2]  = -pt1
  gm[10, 4]  = -l / d30
  gm[10, 8]  = pt1
  gm[10, 10] = two * l / d15

  gm[11, 1]  = pt1
  gm[11, 5]  = -l / d30
  gm[11, 7]  = -pt1
  gm[11, 11] = two * l / d15

  gm
end


module Symmetry

import LinearAlgebra

import MutableArithmetics
const MA = MutableArithmetics
import MultivariatePolynomials
const MP = MultivariatePolynomials
import MultivariateBases
const MB = MultivariateBases

import SymbolicWedderburn

import SumOfSquares

struct Pattern{GT,AT<:SymbolicWedderburn.Action}
    group::GT
    action::AT
end

include("utils.jl")
include("wedderburn.jl")
include("block_diag.jl")

end


using BranchAndBound
using PowerModels
using JuMP
using Ipopt

const BB = BranchAndBound
const PM = PowerModels

mutable struct NodeWRMModel <: PM.AbstractWRMModel PM.@pm_fields end

function PM.constraint_model_voltage(pm::NodeWRMModel, n::Int)

    WR = var(pm, n)[:WR]
    WI = var(pm, n)[:WI]

    nw = pm.cnw
    bus_ids = ids(pm, nw, :bus)
    lookup_w_index = Dict((bi,i) for (i,bi) in enumerate(bus_ids))

    # create voltage variables
    PM.variable_bus_voltage_real(pm, bounded=true)
    PM.variable_bus_voltage_imaginary(pm, bounded=true)

    vr = var(pm, n)[:vr]
    vi = var(pm, n)[:vi]

    # constraints on diagonal
    for (i, bus) in ref(pm, nw, :bus)
        w_idx = lookup_w_index[i]

        # vr
        JuMP.@constraint(pm.model, -bus["vmax"] * vr[i] - WR[i,i] <= 0)
        JuMP.@constraint(pm.model, bus["vmax"] * vr[i] - WR[i,i] >= 0)

        # vi
        JuMP.@constraint(pm.model, -bus["vmax"] * vi[i] - WI[i,i] <= 0)
        JuMP.@constraint(pm.model, bus["vmax"] * vi[i] - WI[i,i] >= 0)
    end

    # constraints on off-diagonal
    for (i,j) in ids(pm, nw, :buspairs)
        wi_idx = lookup_w_index[i]
        wj_idx = lookup_w_index[j]
        bus = ref(pm, nw, :bus)[i]

        # impose skew symmetric
        JuMP.@constraint(pm.model, WI[i,j] + WI[j,i] == 0)

        # vr
        JuMP.@constraint(pm.model, -bus["vmax"] * vr[j] - WR[i,j] <= 0)
        JuMP.@constraint(pm.model, bus["vmax"] * vr[j] - WR[i,j] >= 0)

        # vi
        JuMP.@constraint(pm.model, -bus["vmax"] * vi[j] - WI[i,j] <= 0)
        JuMP.@constraint(pm.model, bus["vmax"] * vi[j] - WI[i,j] >= 0)
    end
end

pm_data = PM.parse_file("../data/case9.m")
pm = PM.instantiate_model(pm_data, NodeWRMModel, PM.build_opf)

vr = var(pm, pm.cnw)[:vr]
vi = var(pm, pm.cnw)[:vi]
WR = var(pm, pm.cnw)[:WR]
WI = var(pm, pm.cnw)[:WI]

pm_data = PM.parse_file("../data/case9.m")
pm = PM.instantiate_model(pm_data, NodeWRMModel, PM.build_opf)

function BB.branch(node::BB.Node)
    vr_val = BB.node_solution.(node, vr)
    vi_val = BB.node_solution.(node, vi)
    WR_val = BB.node_solution.(node, WR)
    WI_val = BB.node_solution.(node, WI)

    for (i, bus) in ref(pm, pm.cnw, :bus)
        @show abs(vr_val[i]^2 - WR_val[i,i])
        @show abs(vi_val[i]^2 - WI_val[i,i])
    end
end


push!(LOAD_PATH,"../src/")

using TheDancer
using Base.Test

N, Edisorder, Jdisorder, modelJ, B, dipolestrength, r = init!(3, 0.0, 0.0, 0.001, 298, 20)

"""
Compares the results of a 3-site model when dipole-dipole interactions are/are
not included.

The dipole-dipole interaction field is calculated from the gradient of the
potential, which scales as r^-6 for induced dipoles. The dipoles are first found
from the field established by the charge distribution alone. The field is then
updated to include the dipole-dipole effects and the dipoles are recalculated.

Test will be successful if the effect of including these interactions results in
a difference in the maximum density of less than 0.00001.
"""
function main()

    densities = zeros(2)
    j=0
    for d_d in [false,true]
        j+=1
        S,E,H,psi,dipoles=prepare_model()
        density = abs.(psi.^2)
        for i in 1:200
            S,H,psi,density,dipoles  = AdiabaticPropagation(S,dipoles,E,false,d_d)
        end
        densities[j] = density[2]
    end

    diff = densities[1] - densities[2]
    println(diff)

    ε = 1E-5
    @test  0 ≈ diff atol = ε

end

main()


function solve()
    n = readline()
    ret = 0
    for i in 2:2:length(n)
        ret += if i < length(n)
            9 * 10^(i÷2-1)
        else
            h = i÷2
            f = parse(Int, n[1:h])
            s = parse(Int, n[h+1:end])
            x = s ≥ f ? f : f-1
            x - 10^(h-1) + 1
        end
    end
    ret
end

println(solve())


export Shamanskii_λ

function Shamanskii_λ(
    nlp_stop,
    PData::PDataFact,
    dₙ::Vector,
    gt::Vector;
    λnull::Bool = false,
)
    nlp_at_x = nlp_stop.current_state
    x = nlp_at_x.x
    n = length(x)
    xdemi = x + dₙ
    gtemp = grad(nlp_stop.pb, xdemi)

    ϵ2 = sqrt(eps(eltype(gt)))
    Γ = max.(abs.(PData.Δ), ϵ2)

    if λnull
        d̃ = PData.L \ gtemp[PData.pp]
        d̂ = PData.L' \ (PData.Q * (PData.Q' * d̃ ./ Γ))
        d = -d̂[invperm(PData.pp)]
    else
        D2 = SymTridiagonal(Hermitian(PData.D + PData.λ * Matrix(1.0I, n, n)))
        Q2 = eigvecs(D2)
        d̃ = PData.L \ gtemp[PData.pp]
        d̂ = PData.L' \ (Q2 * (Q2' * d̃ ./ Γ))
        d = -d̂[invperm(PData.pp)]
    end

    dₕₒ = dₙ .+ d

    return dₕₒ
end


using ProgressMeter
using .Threads
using Lasso
using NPZ
using Statistics
using Random
using StatsBase
using LinearAlgebra

struct InvResult{T,S}
    i::Int
    λmin::T
    dn::T
    m::S
end

function fit_model(G,dv,i)
    dn = std(dv)
    dv ./= dn
    path = fit(LassoPath, G, dv, λminratio = 1e-5, standardize=false, intercept=false, maxncoef=size(G,2))
    λmin = path.λ[argmin(aicc(path))]
    m = coef(path; select=MinAICc())
    return InvResult(i, λmin, dn, m)
end

T = Float64

G1 = npzread("Kernels/G1.npy")
G2 = npzread("Kernels/G2.npy")
G3 = npzread("Kernels/G4.npy")

d1 = npzread("Testing/das_wvt_data.npy")
d2 = npzread("Testing/nodal_wvt_data_x.npy")
d3 = npzread("Testing/nodal_wvt_data_y.npy")
nfactor = npzread("Testing/nfactor.npy")

G = [nfactor*G1 nfactor*G2;
     G3 zeros(T, size(G3));
     zeros(T, size(G3)) G3]

Gn = std(G)

G ./= Gn

d = [d1; d2; d3]

s_size = [4, 8, 16, 32, 64]
n_runs = 10

p = Progress(n_runs*(length(s_size)-1)+1, dt=0.5, barglyphs=BarGlyphs("[=> ]"), barlen=50, color=:yellow)

for B in s_size
    pvec = npzread("IncoherenceResults/res_inc_$B.npy")

    for i in 1:(B!=64 ? n_runs : 1)
        rng = MersenneTwister(i+50)
        sensors = sample(rng, 1:64, aweights(pvec), B, replace=false)
        usensors = sample(rng, 1:64, B, replace=false)
        μ = [(i in sensors ? 1.0 : 0.0) for i in 1:64]
        uμ = [(i in usensors ? 1.0 : 0.0) for i in 1:64]
        μl = vcat(ones(size(G1, 1)), μ, μ)
        uμl = vcat(ones(size(G1, 1)), uμ, uμ)
        Gμ = Diagonal(μl)*G
        Guμ = Diagonal(uμl)*G
        results = Array{Any,1}(undef, size(d,2))
        uresults = Array{Any,1}(undef, size(d,2))

        @threads for j = 1:size(d,2)
            results[j] = fit_model(Gμ, d[:,j], j)
            uresults[j] = fit_model(Guμ, d[:,j], j)
        end

        sres = reduce(hcat, [x.m.*x.dn for x in results])
        usres = reduce(hcat, [x.m.*x.dn for x in uresults])

        npzwrite("Testing/LassoOutputInc/32B_$(B)_run_$i.npy", Array{Float64}(sres))
        npzwrite("Testing/LassoOutputInc/32B_$(B)_urun_$i.npy", Array{Float64}(usres))
        npzwrite("Testing/LassoOutputInc/32B_$(B)_run_$(i)_sensors.npy", sensors)
        npzwrite("Testing/LassoOutputInc/32B_$(B)_urun_$(i)_sensors.npy", usensors)
        next!(p)
    end
end


using ContactStructures

plotlyjs()
# define a contact form for the standard contact structure
x = (x,y,z) -> x
std_form = dz + x * dy
plot_along_xy_grid(-2:0.5:2,-2:0.5:2,0,std_form;  markersize = 2.5,ε = 0.25)
plot!(xlims=(-3, 3), ylims=(-3,3), zlims=(-3,3),legend=false)