using NLPModelsJuMP
using JuMP

function Jason1()
  P1 = JuMP.Model()
  JuMP.@variable(P1, x[1:2], start = 1.0)
  JuMP.@constraint(P1, x[1]^2 + x[2]^2 - 1 <= 0)
  JuMP.@constraint(P1, x[1] >= 0)
  JuMP.@constraint(P1, x[2] >= 0)
  JuMP.@NLobjective(P1, Min, 0.5 * (x[1] - 2 / 3)^2)
  Player1 = MathProgNLPModel(P1)

  P2 = JuMP.Model()
  JuMP.@variable(P2, x[1:2], start = 1.0)
  JuMP.@constraint(P2, x[2] + x[1] - 1 <= 0)
  JuMP.@constraint(P2, x[1] >= 0)
  JuMP.@constraint(P2, x[2] >= 0)
  JuMP.@NLobjective(P2, Min, 0.5 * (x[2] - 1 / 3)^2)
  Player2 = MathProgNLPModel(P2)
  sol = x -> (x[1] == 2 / 3 && x[2] == 1 / 3)

  return GNEP(2, [1, 1], [Player1, Player2], sol = sol)
end

function Jason2()
  P1 = JuMP.Model()
  JuMP.@variable(P1, x[1:3], start = 1.0)
  JuMP.@constraint(P1, x[1] + x[2] + x[3] - 3 <= 0)
  JuMP.@constraint(P1, x[1] >= 0)
  JuMP.@constraint(P1, x[2] >= 0)
  JuMP.@constraint(P1, x[3] >= 0)
  JuMP.@NLobjective(P1, Min, 0.5 * (x[1] - 1)^2 + 0.5 * (x[2] - 1)^2)
  Player1 = MathProgNLPModel(P1)

  P2 = JuMP.Model()
  JuMP.@variable(P2, x[1:3], start = 1.0)
  JuMP.@constraint(P2, -x[1] - x[2] - x[3] + 3 <= 0)
  JuMP.@constraint(P2, x[1]^2 + x[2]^2 + x[3]^2 - 9 <= 0)
  JuMP.@constraint(P2, x[1] >= 0)
  JuMP.@constraint(P2, x[2] >= 0)
  JuMP.@constraint(P2, x[3] >= 0)
  JuMP.@NLobjective(P2, Min, 0.5 * x[1] * x[2] * x[3]^2)
  Player2 = MathProgNLPModel(P2)
  sol = x -> (t = 1 - x[1]; (x[2] == 1 - t) && (x[3] == 1 + 2 * t))

  return GNEP(2, [2, 1], [Player1, Player2], sol = sol)
end

function Tangi1()
  ul(i) = [0.1; 0.1; 0.1][i]
  ux(i) = [Inf; Inf; Inf][i]

  P1 = JuMP.Model()
  JuMP.@variable(P1, x[i = 1:3], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P1, x[1] + x[2] + x[3] <= 3)
  JuMP.@NLobjective(P1, Min, 0.5 * (x[1] - 1)^2 + 0.5 * (x[2] - 1)^2)
  Player1 = MathProgNLPModel(P1)

  P2 = JuMP.Model()
  JuMP.@variable(P2, x[i = 1:3], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@NLconstraint(P2, -x[1]^2 * x[3]^2 <= -0.5)
  JuMP.@NLconstraint(P2, -x[2]^2 * x[3]^2 <= -0.5)
  JuMP.@NLobjective(P2, Min, 0.5 * x[1] * x[2] * x[3]^2)
  Player2 = MathProgNLPModel(P2)

  sol = x -> (t = x[1]; (x[1] == 5 && x[2] == 9) || (x[2] == 15 - t && t <= 10 && t >= 9))
  return GNEP(2, [2, 1], [Player1, Player2], sol = sol)
end

function Jason2cvx()
  ul(i) = [log(0.1); log(0.1); log(0.1)][i]
  ux(i) = [Inf; Inf; Inf][i]

  P1 = JuMP.Model()
  JuMP.@variable(P1, y[i = 1:3], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  #JuMP.@NLconstraint(P1,exp(y[1]+log(1/3))+exp(y[2]+log(1/3))+exp(y[3]+log(1/3))<=1)
  #JuMP.@NLconstraint(P1,log(exp(y[1]+log(1/3))+exp(y[2]+log(1/3))+exp(y[3]+log(1/3)))<=0)
  JuMP.@NLconstraint(P1, log(exp(y[1]) + exp(y[2]) + exp(y[3])) <= log(3))
  #JuMP.@constraint(P1,y[1]>=log(0.1))
  #JuMP.@constraint(P1,y[2]>=log(0.1))
  #JuMP.@constraint(P1,y[3]>=log(0.1))
  JuMP.@NLobjective(P1, Min, 0.5 * (y[1])^2 + 0.5 * (y[2])^2)
  Player1 = MathProgNLPModel(P1)

  P2 = JuMP.Model()
  JuMP.@variable(P2, y[i = 1:3], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P2, 2 * y[1] + 2 * y[3] >= log(0.5))
  JuMP.@constraint(P2, 2 * y[2] + 2 * y[3] >= log(0.5))
  #JuMP.@constraint(P2,y[1]>=log(0.1))
  #JuMP.@constraint(P2,y[2]>=log(0.1))
  #JuMP.@constraint(P2,y[3]>=log(0.1))
  JuMP.@NLobjective(P2, Min, y[1] + y[2] + 2 * y[3])
  Player2 = MathProgNLPModel(P2)
  sol = x -> ((x[1] == x[2] && x[1] == (0.5 + 1 / sqrt(2))) || x == [1.0, 1.0, sqrt(2) / 2])
  #norm(xk-log.([1/2+1/sqrt(2),1/2+1/sqrt(2),1/(sqrt(2)*(1/2+1/sqrt(2)))]),Inf)
  #norm(xk-log.([1.,1.,sqrt(2)/2]),Inf)
  return GNEP(2, [2, 1], [Player1, Player2], sol = sol)
end

function Jason3()
  ul(i) = [0; 0][i]
  ux(i) = [Inf; Inf][i]

  P1 = JuMP.Model()
  JuMP.@variable(P1, x[i = 1:2], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P1, x[1]^2 + x[2]^2 - 1 <= 0)
  #JuMP.@constraint(P1,x[1]>=0)
  #JuMP.@constraint(P1,x[2]>=0)
  #JuMP.@NLobjective(P1,Min,x[1]^2-x[1]*x[2]-x[2])
  JuMP.@NLobjective(P1, Min, x[1]^2 - x[1] * x[2] - x[1] + x[2]^2)
  Player1 = MathProgNLPModel(P1)

  P2 = JuMP.Model()
  JuMP.@variable(P2, x[i = 1:2], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P2, x[1]^2 + x[2]^2 - 1 <= 0)
  #JuMP.@constraint(P2,x[1]>=0)
  #JuMP.@constraint(P2,x[2]>=0)
  #JuMP.@NLobjective(P2,Min,x[2]^2-x[1]*x[2]/2-2*x[2])
  JuMP.@NLobjective(P2, Min, x[2]^2 - x[1] * x[2] / 2 - 2 * x[2] + x[1]^2)
  Player2 = MathProgNLPModel(P2)
  sol = x -> (t = x[1]; x[2] == sqrt(1 - t^2) && t <= 4 / 5 && t >= 0)

  return GNEP(2, [1, 1], [Player1, Player2], sol = sol)
end

function TangiCEx()
  P1 = JuMP.Model()
  JuMP.@variable(P1, x[1:4], start = 1.0)
  JuMP.@constraint(P1, x[1] + x[3] >= 0)
  JuMP.@constraint(P1, x[2] + x[4] >= 0)
  JuMP.@NLobjective(P1, Min, x[1] + 2 * x[2])
  Player1 = MathProgNLPModel(P1)

  P2 = JuMP.Model()
  JuMP.@variable(P2, x[1:4], start = 1.0)
  JuMP.@constraint(P2, x[1] + x[3] >= 0)
  JuMP.@constraint(P2, x[2] + x[4] >= 0)
  JuMP.@NLobjective(P2, Min, 2 * x[3] + 4 * x[4])
  Player2 = MathProgNLPModel(P2)
  sol = x -> true
  #0 is a solution / may be unbounded!!

  return GNEP(2, [2, 2], [Player1, Player2], sol = sol)
end

function GNEP1()
  ul(i) = [0; 0][i]
  ux(i) = [Inf; Inf][i]

  P1 = JuMP.Model()
  JuMP.@variable(P1, x[i = 1:2], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P1, x[1]^2 + x[2]^2 - 1 <= 0)
  JuMP.@constraint(P1, x[1] + x[2] - 1 <= 0)
  JuMP.@NLobjective(P1, Min, 0.5 * (x[1] - 1)^2)
  Player1 = MathProgNLPModel(P1)

  P2 = JuMP.Model()
  JuMP.@variable(P2, x[i = 1:2], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P2, x[1] + x[2] - 1 <= 0)
  JuMP.@NLobjective(P2, Min, 0.5 * (x[2] - 0.5)^2)
  Player2 = MathProgNLPModel(P2)
  sol = x -> (t = x[1]; x[2] == 1 - t && 0.5 <= t && t <= 1)
  #VE: (3/4,1/4)

  return GNEP(2, [1, 1], [Player1, Player2], sol = sol)
end

function AbsVal()
  P1 = JuMP.Model()
  JuMP.@variable(P1, x[1:2], start = 1.0)
  JuMP.@constraint(P1, x[1] >= -1)
  JuMP.@constraint(P1, x[1] <= 1)
  JuMP.@NLobjective(P1, Min, 0.0)
  Player1 = MathProgNLPModel(P1)

  P2 = JuMP.Model()
  JuMP.@variable(P2, x[1:2], start = 1.0)
  JuMP.@constraint(P2, x[1] >= -1)
  JuMP.@constraint(P2, x[1] <= 1)
  JuMP.@constraint(P2, x[2] >= -x[1])
  JuMP.@constraint(P2, x[2] <= x[1])
  JuMP.@NLobjective(P2, Min, -x[2])
  Player2 = MathProgNLPModel(P2)
  sol = x -> (t = x[1]; x[2] == abs(t) && -1 <= t && t <= 1)
  #VE: (3/4,1/4)

  return GNEP(2, [1, 1], [Player1, Player2], sol = sol)
end

function A8()
  ul(i) = [0; 0; 0][i]
  ux(i) = [1; 1; 2][i]

  P1 = JuMP.Model()
  JuMP.@variable(P1, x[i = 1:3], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P1, x[1] + x[2] - 1 <= 0)
  JuMP.@constraint(P1, x[1] + x[2] - x[3] >= 0)
  #JuMP.@constraint(P1,x[1]>=0)
  #JuMP.@constraint(P1,x[2]>=0)
  #JuMP.@constraint(P1,x[3]>=0)
  #JuMP.@constraint(P1,x[3]<=2)
  JuMP.@NLobjective(P1, Min, -x[1])
  Player1 = MathProgNLPModel(P1)

  P2 = JuMP.Model()
  JuMP.@variable(P2, x[i = 1:3], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P2, x[1] + x[2] - 1 <= 0)
  JuMP.@constraint(P2, x[1] + x[2] - x[3] >= 0)
  #JuMP.@constraint(P1,x[1]>=0)
  #JuMP.@constraint(P1,x[2]>=0)
  #JuMP.@constraint(P1,x[3]>=0)
  #JuMP.@constraint(P1,x[3]<=2)
  JuMP.@NLobjective(P2, Min, 0.5 * (x[2] - 0.5)^2)
  Player2 = MathProgNLPModel(P2)

  P3 = JuMP.Model()
  JuMP.@variable(P3, x[i = 1:3], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  #JuMP.@constraint(P1,x[1]>=0)
  #JuMP.@constraint(P1,x[2]>=0)
  #JuMP.@constraint(P1,x[3]>=0)
  #JuMP.@constraint(P1,x[3]<=2)
  JuMP.@NLobjective(P3, Min, 0.5 * (x[3] - 1.5 * x[1])^2)
  Player3 = MathProgNLPModel(P3)
  sol = x -> (t = x[1]; x[2] == 1 - t && x[3] == 1.5 * t && 0.5 <= t && t <= 2 / 3)

  return GNEP(3, [1, 1, 1], [Player1, Player2, Player3], sol = sol)
end

function A8slack()
  ul(i) = [0; 0; 0; 0; 0; 0; 0][i]
  ux(i) = [1; 1; 2; Inf; Inf; Inf; Inf][i]

  P1 = JuMP.Model()
  JuMP.@variable(P1, x[i = 1:7], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P1, x[1] + x[4] - 1 == -x[2])
  JuMP.@constraint(P1, x[1] + x[4] - x[7] == x[3])
  JuMP.@NLobjective(P1, Min, -x[1])
  Player1 = MathProgNLPModel(P1)

  P2 = JuMP.Model()
  JuMP.@variable(P2, x[i = 1:7], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P2, x[1] + x[4] - 1 == -x[5])
  JuMP.@constraint(P2, x[1] + x[4] - x[7] == x[6])
  JuMP.@NLobjective(P2, Min, 0.5 * (x[2] - 0.5)^2)
  Player2 = MathProgNLPModel(P2)

  P3 = JuMP.Model()
  JuMP.@variable(P3, x[i = 1:7], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@NLobjective(P3, Min, 0.5 * (x[7] - 1.5 * x[1])^2)
  Player3 = MathProgNLPModel(P3)
  sol = x -> (t = x[1]; x[2] == 1 - t && x[3] == 1.5 * t && 0.5 <= t && t <= 2 / 3)

  return GNEP(3, [3, 3, 1], [Player1, Player2, Player3], sol = sol)
end

function Riverbasin()
  a1 = 0.01
  a2 = 0.05
  a3 = 0.01
  b = 0.01
  e1 = 2.9
  e2 = 2.88
  e3 = 2.85
  ul(i) = [0; 0; 0][i]
  ux(i) = [Inf; Inf; Inf][i]

  P1 = JuMP.Model()
  JuMP.@variable(P1, x[i = 1:3], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P1, 3.25 * x[1] + 1.25 * x[2] + 4.125 * x[3] <= 100)
  JuMP.@constraint(P1, 2.29115 * x[1] + 1.5625 * x[2] + 2.8125 * x[3] <= 100)
  JuMP.@NLobjective(P1, Min, (a1 * x[1] + b * (x[1] + x[2] + x[3]) - e1) * x[1])
  Player1 = MathProgNLPModel(P1)

  P2 = JuMP.Model()
  JuMP.@variable(P2, x[i = 1:3], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P2, 3.25 * x[1] + 1.25 * x[2] + 4.125 * x[3] <= 100)
  JuMP.@constraint(P2, 2.29115 * x[1] + 1.5625 * x[2] + 2.8125 * x[3] <= 100)
  JuMP.@NLobjective(P2, Min, (a2 * x[2] + b * (x[1] + x[2] + x[3]) - e2) * x[2])
  Player2 = MathProgNLPModel(P2)

  P3 = JuMP.Model()
  JuMP.@variable(P3, x[i = 1:3], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P3, 3.25 * x[1] + 1.25 * x[2] + 4.125 * x[3] <= 100)
  JuMP.@constraint(P3, 2.29115 * x[1] + 1.5625 * x[2] + 2.8125 * x[3] <= 100)
  JuMP.@NLobjective(P3, Min, (a3 * x[3] + b * (x[1] + x[2] + x[3]) - e3) * x[3])
  Player3 = MathProgNLPModel(P3)
  sol = x -> (t = x[1]; x[2] == 1 - t && x[3] == 1.5 * t && 0.5 <= t && t <= 2 / 3)

  return GNEP(3, [1, 1, 1], [Player1, Player2, Player3], sol = sol)
end

function Harker()
  ul(i) = [0; 0][i]
  ux(i) = [10; 10][i]

  P1 = JuMP.Model()
  JuMP.@variable(P1, x[i = 1:2], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P1, x[1] + x[2] <= 15)
  JuMP.@NLobjective(P1, Min, x[1]^2 + 8 / 3 * x[1] * x[2] - 34 * x[1])
  Player1 = MathProgNLPModel(P1)

  P2 = JuMP.Model()
  JuMP.@variable(P2, x[i = 1:2], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P2, x[1] + x[2] <= 15)
  JuMP.@NLobjective(P2, Min, x[2]^2 + 5 / 4 * x[1] * x[2] - 24.25 * x[2])
  Player2 = MathProgNLPModel(P2)

  sol = x -> (t = x[1]; (x[1] == 5 && x[2] == 9) || (x[2] == 15 - t && t <= 10 && t >= 9))
  return GNEP(2, [1, 1], [Player1, Player2], sol = sol)
end

function ContrHarker()
  ul(i) = [0; 0][i]
  ux(i) = [10; 10][i]

  theta = 0.9

  P1 = JuMP.Model()
  JuMP.@variable(P1, x[i = 1:2], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P1, x[1] + 0.9 * x[2] <= 14.4)
  JuMP.@NLobjective(P1, Min, x[1]^2 + 8 / 3 * x[1] * x[2] - 34 * x[1])
  Player1 = MathProgNLPModel(P1)

  P2 = JuMP.Model()
  JuMP.@variable(P2, x[i = 1:2], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P2, 0.9 * x[1] + x[2] <= 14.1)
  JuMP.@NLobjective(P2, Min, x[2]^2 + 5 / 4 * x[1] * x[2] - 24.25 * x[2])
  Player2 = MathProgNLPModel(P2)

  sol = x -> (t = x[1]; (x[1] == 5 && x[2] == 9) || (x[2] == 15 - t && t <= 10 && t >= 9))
  return GNEP(2, [1, 1], [Player1, Player2], sol = sol)
end

function Nabutani1()
  ul(i) = [0; 0; 0][i]
  ux(i) = [Inf; Inf; Inf][i]

  P1 = JuMP.Model()
  JuMP.@variable(P1, x[i = 1:3], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P1, x[1] + 2 * x[2] - x[3] <= 14)
  JuMP.@constraint(P1, 3 * x[1] + 2 * x[2] + x[3] <= 30)
  JuMP.@NLobjective(
    P1,
    Min,
    x[1]^2 + x[1] * x[2] + x[2]^2 + (x[1] + x[2]) * x[3] - 25 * x[1] - 38 * x[2]
  )
  Player1 = MathProgNLPModel(P1)

  P2 = JuMP.Model()
  JuMP.@variable(P2, x[i = 1:3], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P2, x[1] + 2 * x[2] - x[3] <= 14)
  JuMP.@constraint(P2, 3 * x[1] + 2 * x[2] + x[3] <= 30)
  JuMP.@NLobjective(P2, Min, x[3]^2 + (x[1] + x[2]) * x[3] - 25 * x[3])
  Player2 = MathProgNLPModel(P2)

  sol = x -> (t = x[1]; x[2] == 11 - t && x[3] == 8 - t && t >= 0 && t <= 2)
  return GNEP(2, [2, 1], [Player1, Player2], sol = sol)
end

function Ex1()
  ul(i) = [0; 0][i]
  ux(i) = [Inf; Inf][i]

  P1 = JuMP.Model()
  JuMP.@variable(P1, x[i = 1:2], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P1, x[1] + x[2] <= 1)
  JuMP.@NLobjective(P1, Min, x[1]^2 - x[1] * x[2] - x[1])
  Player1 = MathProgNLPModel(P1)

  P2 = JuMP.Model()
  JuMP.@variable(P2, x[i = 1:2], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P2, x[1] + x[2] <= 1)
  JuMP.@NLobjective(P2, Min, x[2]^2 - 1 / 2 * x[1] * x[2] - 2 * x[2])
  Player2 = MathProgNLPModel(P2)

  sol = x -> (t = x[1]; x[2] == 1 - t && t <= 2 / 3 && t >= 0)
  return GNEP(2, [1, 1], [Player1, Player2], sol = sol)
end

function Ex1bis()
  ul(i) = [0; 0][i]
  ux(i) = [Inf; Inf][i]

  P1 = JuMP.Model()
  JuMP.@variable(P1, x[i = 1:2], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P1, x[1]^2 + x[2]^2 <= 1)
  JuMP.@NLobjective(P1, Min, x[1]^2 - x[1] * x[2] - x[1])
  Player1 = MathProgNLPModel(P1)

  P2 = JuMP.Model()
  JuMP.@variable(P2, x[i = 1:2], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P2, x[1]^2 + x[2]^2 <= 1)
  JuMP.@NLobjective(P2, Min, x[2]^2 - 1 / 2 * x[1] * x[2] - 2 * x[2])
  Player2 = MathProgNLPModel(P2)

  sol = x -> (t = x[1]; x[2] == sqrt(1 - t^2) && t <= 4 / 5 && t >= 0)
  return GNEP(2, [1, 1], [Player1, Player2], sol = sol)
end

function ContreExDS()
  ul(i) = [-Inf; -Inf][i]
  ux(i) = [Inf; Inf][i]
  L = 2

  P1 = JuMP.Model()
  JuMP.@variable(P1, x[i = 1:2], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P1, x[1] <= 1)
  JuMP.@constraint(P1, -x[1] + L * x[2] <= 0)
  JuMP.@NLobjective(P1, Min, x[1])
  Player1 = MathProgNLPModel(P1)

  P2 = JuMP.Model()
  JuMP.@variable(P2, x[i = 1:2], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P2, x[1] - x[2] == 0)
  JuMP.@NLobjective(P2, Min, (x[1] - x[2])^2)
  Player2 = MathProgNLPModel(P2)

  sol = x -> (t = x[1]; x[2] == t && t == L * x[2]) #no solutions if L>1, (1,1) if L=1, any solution if L<1
  return GNEP(2, [1, 1], [Player1, Player2], sol = sol)
end

function Ex4()
  ul(i) = [0.0; -Inf; 0.0; -Inf][i]
  ux(i) = [Inf; Inf; Inf; Inf][i]

  P1 = JuMP.Model()
  JuMP.@variable(P1, x[i = 1:4], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P1, x[1] + x[3] <= 1)
  JuMP.@constraint(P1, x[1] - 2 * x[3] - x[2] <= 0)
  JuMP.@constraint(P1, -x[1] - x[3] - x[2] <= 0)
  JuMP.@NLobjective(P1, Min, x[2])
  Player1 = MathProgNLPModel(P1)

  P2 = JuMP.Model()
  JuMP.@variable(P2, x[i = 1:4], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P2, x[1] + x[3] <= 1)
  JuMP.@constraint(P2, -x[1] + x[3] - x[4] <= 0)
  JuMP.@constraint(P2, x[1] - x[3] - x[4] <= 0)
  JuMP.@NLobjective(P2, Min, x[4])
  Player2 = MathProgNLPModel(P2)

  sol = x -> (true)#((x[3]=1-x[1]) || ((x[3]==x[1]) && (x[1]<=0.5)))
  return GNEP(2, [2, 2], [Player1, Player2], sol = sol)
end

function Ex5()
  ul(i) = [-Inf; -Inf; 0][i]
  ux(i) = [Inf; Inf; Inf][i]

  P1 = JuMP.Model()
  JuMP.@variable(P1, x[i = 1:3], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P1, x[1] + x[2] + x[3] <= 1)
  JuMP.@NLobjective(P1, Min, 0.5 * (x[1] - 1)^2 - x[1] * x[2])
  Player1 = MathProgNLPModel(P1)

  P2 = JuMP.Model()
  JuMP.@variable(P2, x[i = 1:3], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P2, x[1] + x[2] + x[3] <= 1)
  JuMP.@NLobjective(P2, Min, 0.5 * (x[2] - 1)^2 + x[1] * x[2])
  Player2 = MathProgNLPModel(P2)

  P3 = JuMP.Model()
  JuMP.@variable(P3, x[i = 1:3], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P3, -x[1] - x[2] + x[3] <= 0)
  JuMP.@NLobjective(P3, Min, 0.5 * (x[3] - 1)^2)
  Player3 = MathProgNLPModel(P3)

  sol = x -> (true)

  return GNEP(3, [1, 1, 1], [Player1, Player2, Player3], sol = sol)
end

function Ex6()
  ul(i) = [-Inf; -Inf][i]
  ux(i) = [Inf; Inf][i]

  P1 = JuMP.Model()
  JuMP.@variable(P1, x[i = 1:2], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P1, 1 / 6 * x[1]^2 + x[2] - 5 / 2 <= 0)
  JuMP.@NLobjective(P1, Min, 0.5 * x[1]^2 + 32 / 5 * x[1])
  Player1 = MathProgNLPModel(P1)

  P2 = JuMP.Model()
  JuMP.@variable(P2, x[i = 1:2], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@NLobjective(P2, Min, 0.5 * x[2]^2 + x[1] * x[2] - 4 / 5 * x[2])
  Player2 = MathProgNLPModel(P2)

  sol = x -> (true)
  return GNEP(2, [1, 1], [Player1, Player2], sol = sol)
end

function Ex7()
  ul(i) = [-Inf; -Inf][i]
  ux(i) = [Inf; Inf][i]

  P1 = JuMP.Model()
  JuMP.@variable(P1, x[i = 1:2], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P1, x[1]^2 + x[2] <= 1)
  JuMP.@NLobjective(P1, Min, x[1])
  Player1 = MathProgNLPModel(P1)

  P2 = JuMP.Model()
  JuMP.@variable(P2, x[i = 1:2], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@NLobjective(P2, Min, 0.5 * x[2]^2)
  Player2 = MathProgNLPModel(P2)

  sol = x -> ((x[1] == -1.0) && (x[2] == 0.0))
  return GNEP(2, [1, 1], [Player1, Player2], sol = sol)
end

function Ex8()
  ul(i) = [-Inf; -Inf][i]
  ux(i) = [Inf; Inf][i]

  P1 = JuMP.Model()
  JuMP.@variable(P1, x[i = 1:2], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@constraint(P1, x[1]^2 + x[2] <= 0)
  JuMP.@NLobjective(P1, Min, 0.5 * x[1]^2 - 2 * x[1])
  Player1 = MathProgNLPModel(P1)

  P2 = JuMP.Model()
  JuMP.@variable(P2, x[i = 1:2], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
  JuMP.@NLobjective(P2, Min, 0.5 * x[2]^2 + (2 - x[1]^2) * x[2])
  Player2 = MathProgNLPModel(P2)

  sol = x -> ((x[1] == -1.0) && (x[2] == 0.0))
  return GNEP(2, [1, 1], [Player1, Player2], sol = sol)
end

"""
Arrow and Debreu model of a competitive economy proposed in:
Kenneth J. Arrow and Gerard Debreu. Existence of an equilibrium for a competitive
economy. Econometrica: Journal of the Econometric Society, pages 265–290, 1954.

Also known as problem A10 in:
Francisco Facchinei and Christian Kanzow. Penalty methods for the solution of general-
ized Nash equilibrium problems (with complete test problems). Sapienza University of
Rome, 2009.

This problem is specialized in several variants: a), b), c), d) and e)
"""
function A10a()
  include("A10a.jl")
  arr_player = Array{NLPModelsJuMP.MathProgNLPModel, 1}(undef, F + C + 1)
  ul(i) = zeros((F + C + 1) * P)[i]
  ux(i) = vcat(sqrt(21) * ones(P * F), 6 * ones(P * C), 1.1 * ones(P))[i]

  #Firms problems
  for j = 1:F
    firm_j = JuMP.Model()
    JuMP.@variable(
      firm_j,
      x[i = 1:((F + C + 1) * P)],
      upperbound = ux(i),
      lowerbound = ul(i),
      start = 1.0
    )
    #JuMP.@NLobjective(firm_j,Min,x[(F+C)*P+1:n]'*x[P*(j-1)+1:P*(j-1)+P])
    JuMP.@NLobjective(
      firm_j,
      Min,
      -x[22] * x[P * (j - 1) + 1] - x[23] * x[P * (j - 1) + 2] - x[24] * x[P * (j - 1) + 3]
    )
    JuMP.@constraint(firm_j, sum(x[(P * (j - 1) + 1):(P * (j - 1) + P)] .^ 2) <= 10 * j)
    arr_player[j] = MathProgNLPModel(firm_j)
  end
  #Consumers problems
  for i = 1:C
    cons_i = JuMP.Model()
    JuMP.@variable(
      cons_i,
      x[i = 1:((F + C + 1) * P)],
      upperbound = ux(i),
      lowerbound = ul(i),
      start = 1.0
    )
    #error max not min
    #JuMP.@NLobjective(cons_i,Min, -0.5*(x[1]*Q[i,1,1]*x[1]+x[1]*Q[i,1,2]*x[2]+x[1]*Q[i,1,1]*x[3] + x[2]*Q[i,2,1]*x[1]+x[2]*Q[i,2,2]*x[2]+x[2]*Q[i,2,1]*x[3] + x[3]*Q[i,3,1]*x[1]+x[3]*Q[i,3,2]*x[2]+x[3]*Q[i,3,1]*x[3]) + x[P*(F+i-1)+1]*b[i,1,1] + x[P*(F+i-1)+2]*b[i,2,1] +x[P*(F+i-1)+3]*b[i,3,1])
    JuMP.@NLobjective(
      cons_i,
      Min,
      0.5 * (
        x[1] * Q[i, 1, 1] * x[1] +
        x[1] * Q[i, 1, 2] * x[2] +
        x[1] * Q[i, 1, 1] * x[3] +
        x[2] * Q[i, 2, 1] * x[1] +
        x[2] * Q[i, 2, 2] * x[2] +
        x[2] * Q[i, 2, 1] * x[3] +
        x[3] * Q[i, 3, 1] * x[1] +
        x[3] * Q[i, 3, 2] * x[2] +
        x[3] * Q[i, 3, 1] * x[3]
      ) - x[P * (F + i - 1) + 1] * b[i, 1, 1] - x[P * (F + i - 1) + 2] * b[i, 2, 1] -
      x[P * (F + i - 1) + 3] * b[i, 3, 1]
    )
    JuMP.@constraint(
      cons_i,
      x[((F + C) * P + 1):((F + C + 1) * P)]' * x[(P * (F + i - 1) + 1):(P * (F + i - 1) + P)] -
      x[((F + C) * P + 1):((F + C + 1) * P)]' * xi[i, :, 1] <= 0
    )
    arr_player[F + i] = MathProgNLPModel(cons_i)
  end
  #Market player problem
  mark = JuMP.Model()
  JuMP.@variable(
    mark,
    x[i = 1:((F + C + 1) * P)],
    upperbound = ux(i),
    lowerbound = ul(i),
    start = 1.0
  )
  JuMP.@NLobjective(
    mark,
    Min,
    -x[22] * (
      x[7] + x[10] + x[13] + x[16] + x[19] - x[1] - x[4] - xi[1, 1, 1] - xi[2, 1, 1] - xi[3, 1, 1] -
      xi[4, 1, 1] - xi[5, 1, 1]
    ) -
    x[23] * (
      x[8] + x[11] + x[14] + x[17] + x[20] - x[2] - x[5] - xi[1, 2, 1] - xi[2, 2, 1] - xi[3, 2, 1] -
      xi[4, 2, 1] - xi[5, 2, 1]
    ) -
    x[24] * (
      x[9] + x[12] + x[15] + x[18] + x[21] - x[3] - x[5] - xi[1, 3, 1] - xi[2, 3, 1] - xi[3, 3, 1] -
      xi[4, 3, 1] - xi[5, 3, 1]
    )
  )
  JuMP.@constraint(mark, sum(x[((F + C) * P + 1):((F + C + 1) * P)]) == 1)
  arr_player[F + C + 1] = MathProgNLPModel(mark)

  sol = x -> (true)

  #initial point in the paper:
  x0 = zeros(24)
  x0[22:24] .= 1 / 3
  return GNEPmod.GNEP(F + C + 1, P * ones(Int64, F + C + 1), arr_player, sol = sol, x0 = x0)
end

"""
Inspired by an internet switching model introduced by

Alex Kesselman, Stefano Leonardi, and Vincenzo Bonifaci. Game-theoretic analysis of
internet switching with selfish users. In Xiaotie Deng and Yinyu Ye, editors, Internet and
Network Economics, pages 236–245, Berlin, Heidelberg, 2005. Springer Berlin Heidelberg.

and also analyzed as problem A14 in
Francisco Facchinei and Christian Kanzow. Penalty methods for the solution of general-
ized Nash equilibrium problems (with complete test problems). Sapienza University of
Rome, 2009.

This is my* specialization.
"""
function TwoGroupsSwitch(B1, B2)
  N1 = 2
  N2 = 3
  arr_player = Array{NLPModelsJuMP.MathProgNLPModel, 1}(undef, N1 + N2)
  ul(i) = 0.01 * ones(Int64, N1 + N2)[i]
  ux(i) = Inf * ones(Int64, N1 + N2)[i]

  for k = 1:N1
    P1 = JuMP.Model()
    JuMP.@variable(P1, x[i = 1:(N1 + N2)], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
    JuMP.@constraint(P1, sum(x[1:(N1 + N2)]) <= B1)
    JuMP.@NLobjective(P1, Min, -x[k] / (x[1] + x[2] + x[3] + x[4] + x[5]))
    arr_player[k] = MathProgNLPModel(P1)
  end

  for j = (N1 + 1):(N1 + N2)
    P2 = JuMP.Model()
    JuMP.@variable(P2, x[i = 1:(N1 + N2)], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
    JuMP.@constraint(P2, sum(x[1:(N1 + N2)] .^ 2) <= B2)
    JuMP.@NLobjective(P2, Min, -x[j] / (x[1] + x[2] + x[3] + x[4] + x[5]))
    arr_player[j] = MathProgNLPModel(P2)
  end

  sol = x -> ((x[1] == -1.0) && (x[2] == 0.0))
  return GNEP(N1 + N2, ones(Int64, N1 + N2), arr_player, sol = sol)
end

using LinearAlgebra
"""
Environmental protocol control proposed in
Breton, M., Zaccour, G., & Zahaf, M. (2006).
A game-theoretic formulation of joint implementation of environmental projects.
European Journal of Operational Research, 168(1), 221-239.
"""
function EPC()
  include("EPC.jl")

  arr_player = Array{NLPModelsJuMP.MathProgNLPModel, 1}(undef, N)

  ub = 50 * ones(Float64, N * (N + 1))
  ub[union(1, 5, 9)] = p

  ul(i) = zeros(Float64, N * (N + 1))[i]
  ux(i) = ub[i]

  for k = 1:N
    P1 = JuMP.Model()
    nu = 1 + (k - 1) * (N + 1) #index of e_k
    j = mod(k, N) + 1
    l = mod(k + 1, N) + 1
    JuMP.@variable(P1, x[i = 1:(N * (N + 1))], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
    JuMP.@constraint(P1, dot(x[nu:(N + nu)], vcat(1.0, -gam)) <= E[k])
    JuMP.@constraint(P1, -dot(x[union(1, 2, 6, 10)], vcat(1.0, -gam[1] * ones(N))) <= 0.0)
    JuMP.@constraint(P1, -dot(x[union(5, 3, 7, 11)], vcat(1.0, -gam[2] * ones(N))) <= 0.0)
    JuMP.@constraint(P1, -dot(x[union(9, 4, 8, 12)], vcat(1.0, -gam[3] * ones(N))) <= 0.0)
    JuMP.@NLobjective(
      P1,
      Min,
      -x[nu] * (p[k] - 0.5 * x[nu]) +
      0.5 * (
        x[nu + k]^2 +
        x[nu + j]^2 +
        x[nu + l]^2 +
        2 * x[nu + j] * x[1 + (j - 1) * (N + 1) + j] +
        2 * x[nu + l] * x[1 + (l - 1) * (N + 1) + l]
      ) +
      lambda[1] * (x[1] - gam[1] * (x[2] + x[6] + x[10])) +
      lambda[2] * (x[5] - gam[2] * (x[3] + x[7] + x[11])) +
      lambda[3] * (x[9] - gam[3] * (x[4] + x[8] + x[12]))
    )
    arr_player[k] = MathProgNLPModel(P1)
  end

  sol = x -> ((x[1] == -1.0) && (x[2] == 0.0))
  return GNEP(N, (N + 1) * ones(Int64, N), arr_player, sol = sol)
end

"""
Environmental protocol control proposed in
Breton, M., Zaccour, G., & Zahaf, M. (2006).
A game-theoretic formulation of joint implementation of environmental projects.
European Journal of Operational Research, 168(1), 221-239.

Variant of EPC() studied in
Cojocaru, M. G., Wild, E., & Small, A. (2018).
On describing the solution sets of generalized Nash games with shared constraints.
Optimization and Engineering, 19(4), 845-870.
"""
function EPCshared()
  include("EPC.jl")

  ub = 50 * ones(Float64, N * (N + 1))
  ub[union(1, 5, 9)] = p

  arr_player = Array{NLPModelsJuMP.MathProgNLPModel, 1}(undef, N)

  ul(i) = zeros(Float64, N * (N + 1))[i]
  ux(i) = ub[i]

  for k = 1:N
    P1 = JuMP.Model()
    nu = 1 + (k - 1) * (N + 1) #index of e_k
    j = mod(k, N) + 1
    l = mod(k + 1, N) + 1
    JuMP.@variable(P1, x[i = 1:(N * (N + 1))], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
    JuMP.@constraint(P1, dot(x[nu:(N + nu)], vcat(1.0, -gam)) <= E[k])
    JuMP.@constraint(
      P1,
      -dot(x[union(1, 2, 6, 10)], vcat(1.0, -gam[1] * ones(N))) -
      dot(x[union(5, 3, 7, 11)], vcat(1.0, -gam[2] * ones(N))) -
      dot(x[union(9, 4, 8, 12)], vcat(1.0, -gam[3] * ones(N))) <= 0.0
    )
    JuMP.@NLobjective(
      P1,
      Min,
      -x[nu] * (p[k] - 0.5 * x[nu]) +
      0.5 * (
        x[nu + k]^2 +
        x[nu + j]^2 +
        x[nu + l]^2 +
        2 * x[nu + j] * x[1 + (j - 1) * (N + 1) + j] +
        2 * x[nu + l] * x[1 + (l - 1) * (N + 1) + l]
      ) +
      lambda[1] * (x[1] - gam[1] * (x[2] + x[6] + x[10])) +
      lambda[2] * (x[5] - gam[2] * (x[3] + x[7] + x[11])) +
      lambda[3] * (x[9] - gam[3] * (x[4] + x[8] + x[12]))
    )
    arr_player[k] = MathProgNLPModel(P1)
  end

  sol = x -> ((x[1] == -1.0) && (x[2] == 0.0))
  return GNEP(N, (N + 1) * ones(Int64, N), arr_player, sol = sol)
end

"""
Environmental protocol control proposed in
Breton, M., Zaccour, G., & Zahaf, M. (2006).
A game-theoretic formulation of joint implementation of environmental projects.
European Journal of Operational Research, 168(1), 221-239.

Variant of EPC() to find the emission of each country
"""
function regularEPC()
  include("EPC.jl")
  ub = 100 * ones(Float64, 6)

  arr_player = Array{NLPModelsJuMP.MathProgNLPModel, 1}(undef, N)

  ul(i) = zeros(Int64, 2 * N)[i]
  ux(i) = ub[i]

  for k = 1:N
    P1 = JuMP.Model()
    nu = 1 + (k - 1) * 2 #index of e_k
    JuMP.@variable(P1, x[i = 1:(2 * N)], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
    JuMP.@constraint(P1, x[nu] - gam[k] * x[nu + 1] <= E[k])
    JuMP.@NLobjective(
      P1,
      Min,
      -x[nu] * (p[k] - 0.5 * x[nu]) +
      0.5 * (x[nu + 1]^2) +
      lambda[1] * (x[1] - gam[1] * (x[2])) +
      lambda[2] * (x[3] - gam[2] * (x[4])) +
      lambda[3] * (x[5] - gam[3] * (x[6]))
    )
    arr_player[k] = MathProgNLPModel(P1)
  end

  sol = x -> ((x[1] == -1.0) && (x[2] == 0.0))
  return GNEP(N, 2 * ones(Int64, N), arr_player, sol = sol)
end

"""
Environmental protocol control proposed in
Breton, M., Zaccour, G., & Zahaf, M. (2006).
A game-theoretic formulation of joint implementation of environmental projects.
European Journal of Operational Research, 168(1), 221-239.

Variant of EPC() studied in *** new ***.
"""
function EPCnonshared()
  include("EPC.jl")

  ub = 50 * ones(Float64, N * (N + 1))
  ub[union(1, 5, 9)] = p

  arr_player = Array{NLPModelsJuMP.MathProgNLPModel, 1}(undef, N)

  ul(i) = zeros(Float64, N * (N + 1))[i]
  ux(i) = ub[i]

  for k = 1:N
    P1 = JuMP.Model()
    nu = 1 + (k - 1) * (N + 1) #index of e_k
    j = mod(k, N) + 1
    l = mod(k + 1, N) + 1
    JuMP.@variable(P1, x[i = 1:(N * (N + 1))], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
    JuMP.@constraint(P1, dot(x[nu:(N + nu)], vcat(1.0, -gam)) <= E[k])
    JuMP.@constraint(
      P1,
      -dot(x[union(1, 2, 6, 10)], vcat(1.0, -gam[1] * ones(N))) -
      dot(x[union(5, 3, 7, 11)], vcat(1.0, -gam[2] * ones(N))) -
      dot(x[union(9, 4, 8, 12)], vcat(1.0, -gam[3] * ones(N))) <= 0.0
    )
    if (k == 1) || (k == 2)
      JuMP.@NLconstraint(
        P1,
        -(x[2] + x[6]) / (x[2] + x[3] + x[4] + x[6]) -
        (x[3] + x[7]) / (x[6] + x[7] + x[8] + x[3]) <= -gap
      )
    end
    JuMP.@NLobjective(
      P1,
      Min,
      -x[nu] * (p[k] - 0.5 * x[nu]) +
      0.5 * (
        x[nu + k]^2 +
        x[nu + j]^2 +
        x[nu + l]^2 +
        2 * x[nu + j] * x[1 + (j - 1) * (N + 1) + j] +
        2 * x[nu + l] * x[1 + (l - 1) * (N + 1) + l]
      ) +
      lambda[1] * (x[1] - gam[1] * (x[2] + x[6] + x[10])) +
      lambda[2] * (x[5] - gam[2] * (x[3] + x[7] + x[11])) +
      lambda[3] * (x[9] - gam[3] * (x[4] + x[8] + x[12]))
    )
    arr_player[k] = MathProgNLPModel(P1)
  end

  sol = x -> ((x[1] == -1.0) && (x[2] == 0.0))
  return GNEP(N, (N + 1) * ones(Int64, N), arr_player, sol = sol)
end

"""
Environmental protocol control proposed in
Breton, M., Zaccour, G., & Zahaf, M. (2006).
A game-theoretic formulation of joint implementation of environmental projects.
European Journal of Operational Research, 168(1), 221-239.

Variant of EPC() studied in
Cojocaru, M. G., Wild, E., & Small, A. (2018).
On describing the solution sets of generalized Nash games with shared constraints.
Optimization and Engineering, 19(4), 845-870.

N=5
"""
function EPC5shared()
  include("EPC5.jl")

  ub = 50 * ones(Float64, N * (N + 1))
  ub[union(1, 7, 13, 19, 25)] = p

  arr_player = Array{NLPModelsJuMP.MathProgNLPModel, 1}(undef, N)

  ul(i) = zeros(Float64, N * (N + 1))[i]
  ux(i) = ub[i]

  for k = 1:N
    P1 = JuMP.Model()
    nu = 1 + (k - 1) * (N + 1) #index of e_k
    j = mod(k, N) + 1
    l = mod(k + 1, N) + 1
    o = mod(k + 2, N) + 1
    m = mod(k + 3, N) + 1
    JuMP.@variable(P1, x[i = 1:(N * (N + 1))], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
    JuMP.@constraint(P1, dot(x[nu:(N + nu)], vcat(1.0, -gam)) <= E[k])
    JuMP.@constraint(
      P1,
      -dot(x[union(1, 2, 8, 14, 20, 26)], vcat(1.0, -gam[1] * ones(N))) -
      dot(x[union(7, 3, 9, 15, 21, 27)], vcat(1.0, -gam[2] * ones(N))) -
      dot(x[union(13, 4, 10, 16, 22, 28)], vcat(1.0, -gam[3] * ones(N))) -
      dot(x[union(19, 5, 11, 17, 23, 29)], vcat(1.0, -gam[4] * ones(N))) -
      dot(x[union(25, 6, 12, 18, 24, 30)], vcat(1.0, -gam[5] * ones(N))) <= 0.0
    )
    JuMP.@NLobjective(
      P1,
      Min,
      -x[nu] * (p[k] - 0.5 * x[nu]) +
      0.5 * (
        x[nu + k]^2 +
        x[nu + j]^2 +
        x[nu + l]^2 +
        x[nu + o]^2 +
        x[nu + m]^2 +
        2 * x[nu + j] * x[1 + (j - 1) * (N + 1) + j] +
        2 * x[nu + l] * x[1 + (l - 1) * (N + 1) + l] +
        2 * x[nu + o] * x[1 + (o - 1) * (N + 1) + o] +
        2 * x[nu + m] * x[1 + (m - 1) * (N + 1) + m]
      ) +
      lambda[1] * (x[1] - gam[1] * (x[2] + x[8] + x[14] + x[20] + x[26])) +
      lambda[2] * (x[7] - gam[2] * (x[3] + x[9] + x[15] + x[21] + x[27])) +
      lambda[3] * (x[13] - gam[3] * (x[4] + x[10] + x[16] + x[22] + x[28])) +
      lambda[4] * (x[19] - gam[4] * (x[5] + x[11] + x[17] + x[23] + x[29])) +
      lambda[5] * (x[25] - gam[5] * (x[6] + x[12] + x[18] + x[24] + x[30]))
    )
    arr_player[k] = MathProgNLPModel(P1)
  end

  sol = x -> ((x[1] == -1.0) && (x[2] == 0.0))
  return GNEP(N, (N + 1) * ones(Int64, N), arr_player, sol = sol)
end

"""
Environmental protocol control proposed in
Breton, M., Zaccour, G., & Zahaf, M. (2006).
A game-theoretic formulation of joint implementation of environmental projects.
European Journal of Operational Research, 168(1), 221-239.

Variant of EPC() to find the emission of each country
"""
function regularEPC5()
  include("EPC5.jl")
  ub = 100 * ones(Float64, 2 * N)

  arr_player = Array{NLPModelsJuMP.MathProgNLPModel, 1}(undef, N)

  ul(i) = zeros(Int64, 2 * N)[i]
  ux(i) = ub[i]

  for k = 1:N
    P1 = JuMP.Model()
    nu = 1 + (k - 1) * 2 #index of e_k
    JuMP.@variable(P1, x[i = 1:(2 * N)], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
    JuMP.@constraint(P1, x[nu] - gam[k] * x[nu + 1] <= E[k])
    JuMP.@NLobjective(
      P1,
      Min,
      -x[nu] * (p[k] - 0.5 * x[nu]) +
      0.5 * (x[nu + 1]^2) +
      lambda[1] * (x[1] - gam[1] * (x[2])) +
      lambda[2] * (x[3] - gam[2] * (x[4])) +
      lambda[3] * (x[5] - gam[3] * (x[6])) +
      lambda[4] * (x[7] - gam[4] * (x[8])) +
      lambda[5] * (x[9] - gam[5] * (x[10]))
    )
    arr_player[k] = MathProgNLPModel(P1)
  end

  sol = x -> ((x[1] == -1.0) && (x[2] == 0.0))
  return GNEP(N, 2 * ones(Int64, N), arr_player, sol = sol)
end

"""
Environmental protocol control proposed in
Breton, M., Zaccour, G., & Zahaf, M. (2006).
A game-theoretic formulation of joint implementation of environmental projects.
European Journal of Operational Research, 168(1), 221-239.

Variant of EPC() studied in *** new ***.
"""
function EPC5nonshared()
  include("EPC5.jl")

  ub = 50 * ones(Float64, N * (N + 1))
  ub[union(1, 7, 13, 19, 25)] = p

  arr_player = Array{NLPModelsJuMP.MathProgNLPModel, 1}(undef, N)

  ul(i) = zeros(Float64, N * (N + 1))[i]
  ux(i) = ub[i]

  for k = 1:N
    P1 = JuMP.Model()
    nu = 1 + (k - 1) * (N + 1) #index of e_k
    j = mod(k, N) + 1
    l = mod(k + 1, N) + 1
    o = mod(k + 2, N) + 1
    m = mod(k + 3, N) + 1
    JuMP.@variable(P1, x[i = 1:(N * (N + 1))], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
    JuMP.@constraint(P1, dot(x[nu:(N + nu)], vcat(1.0, -gam)) <= E[k])
    JuMP.@constraint(
      P1,
      -dot(x[union(1, 2, 8, 14, 20, 26)], vcat(1.0, -gam[1] * ones(N))) -
      dot(x[union(7, 3, 9, 15, 21, 27)], vcat(1.0, -gam[2] * ones(N))) -
      dot(x[union(13, 4, 10, 16, 22, 28)], vcat(1.0, -gam[3] * ones(N))) -
      dot(x[union(19, 5, 11, 17, 23, 29)], vcat(1.0, -gam[4] * ones(N))) -
      dot(x[union(25, 6, 12, 18, 24, 30)], vcat(1.0, -gam[5] * ones(N))) <= 0.0
    )
    if (k == 3) || (k == 4)
      JuMP.@NLconstraint(
        P1,
        -(x[16] * gam[4]) / (x[14] + x[15] + x[16] + x[17] + x[18]) -
        (x[23] * gam[3]) / (x[20] + x[21] + x[22] + x[23] + x[24]) <= -gap
      )
    end
    JuMP.@NLobjective(
      P1,
      Min,
      -x[nu] * (p[k] - 0.5 * x[nu]) +
      0.5 * (
        x[nu + k]^2 +
        x[nu + j]^2 +
        x[nu + l]^2 +
        x[nu + o]^2 +
        x[nu + m]^2 +
        2 * x[nu + j] * x[1 + (j - 1) * (N + 1) + j] +
        2 * x[nu + l] * x[1 + (l - 1) * (N + 1) + l] +
        2 * x[nu + o] * x[1 + (o - 1) * (N + 1) + o] +
        2 * x[nu + m] * x[1 + (m - 1) * (N + 1) + m]
      ) +
      lambda[1] * (x[1] - gam[1] * (x[2] + x[8] + x[14] + x[20] + x[26])) +
      lambda[2] * (x[7] - gam[2] * (x[3] + x[9] + x[15] + x[21] + x[27])) +
      lambda[3] * (x[13] - gam[3] * (x[4] + x[10] + x[16] + x[22] + x[28])) +
      lambda[4] * (x[19] - gam[4] * (x[5] + x[11] + x[17] + x[23] + x[29])) +
      lambda[5] * (x[25] - gam[5] * (x[6] + x[12] + x[18] + x[24] + x[30]))
    )
    arr_player[k] = MathProgNLPModel(P1)
  end

  sol = x -> ((x[1] == -1.0) && (x[2] == 0.0))
  return GNEP(N, (N + 1) * ones(Int64, N), arr_player, sol = sol)
end

"""
Environmental protocol control proposed in
Breton, M., Zaccour, G., & Zahaf, M. (2006).
A game-theoretic formulation of joint implementation of environmental projects.
European Journal of Operational Research, 168(1), 221-239.

Variant of EPC() studied in *** new ***.
"""
function EPC5nonsharedgreen()
  include("EPC5.jl")

  ub = 50 * ones(Float64, N * (N + 1))
  ub[union(1, 7, 13, 19, 25)] = p

  arr_player = Array{NLPModelsJuMP.MathProgNLPModel, 1}(undef, N)

  ul(i) = zeros(Float64, N * (N + 1))[i]
  ux(i) = ub[i]

  for k = 1:N
    P1 = JuMP.Model()
    nu = 1 + (k - 1) * (N + 1) #index of e_k
    j = mod(k, N) + 1
    l = mod(k + 1, N) + 1
    o = mod(k + 2, N) + 1
    m = mod(k + 3, N) + 1
    JuMP.@variable(P1, x[i = 1:(N * (N + 1))], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
    JuMP.@constraint(P1, dot(x[nu:(N + nu)], vcat(1.0, -gam)) <= E[k])
    JuMP.@constraint(
      P1,
      -dot(x[union(1, 2, 8, 14, 20, 26)], vcat(1.0, -gam[1] * ones(N))) -
      dot(x[union(7, 3, 9, 15, 21, 27)], vcat(1.0, -gam[2] * ones(N))) -
      dot(x[union(13, 4, 10, 16, 22, 28)], vcat(1.0, -gam[3] * ones(N))) -
      dot(x[union(19, 5, 11, 17, 23, 29)], vcat(1.0, -gam[4] * ones(N))) -
      dot(x[union(25, 6, 12, 18, 24, 30)], vcat(1.0, -gam[5] * ones(N))) <= 0.0
    )
    if (k == 1)
      #JuMP.@NLconstraint(P1,x[3]-1/(1e-3+x[7]-97.5)^2<=0.0)
      #JuMP.@NLconstraint(P1,x[4]-1/(1e-3+x[13]-97.5)^2<=0.0)
      #JuMP.@NLconstraint(P1,x[5]-1/(1e-3+x[19]-97.5)^2<=0.0)
      #JuMP.@NLconstraint(P1,x[6]-1/(1e-3+x[25]-97.5)^2<=0.0)
      JuMP.@NLconstraint(P1, x[3] * (x[7] - 97.5)^2 - 1 <= 0.0)
      JuMP.@NLconstraint(P1, x[4] * (x[13] - 97.5)^2 - 1 <= 0.0)
      JuMP.@NLconstraint(P1, x[5] * (x[19] - 97.5)^2 - 1 <= 0.0)
      JuMP.@NLconstraint(P1, x[6] * (x[25] - 97.5)^2 - 1 <= 0.0)
    end
    JuMP.@NLobjective(
      P1,
      Min,
      -x[nu] * (p[k] - 0.5 * x[nu]) +
      0.5 * (
        x[nu + k]^2 +
        x[nu + j]^2 +
        x[nu + l]^2 +
        x[nu + o]^2 +
        x[nu + m]^2 +
        2 * x[nu + j] * x[1 + (j - 1) * (N + 1) + j] +
        2 * x[nu + l] * x[1 + (l - 1) * (N + 1) + l] +
        2 * x[nu + o] * x[1 + (o - 1) * (N + 1) + o] +
        2 * x[nu + m] * x[1 + (m - 1) * (N + 1) + m]
      ) +
      lambda[1] * (x[1] - gam[1] * (x[2] + x[8] + x[14] + x[20] + x[26])) +
      lambda[2] * (x[7] - gam[2] * (x[3] + x[9] + x[15] + x[21] + x[27])) +
      lambda[3] * (x[13] - gam[3] * (x[4] + x[10] + x[16] + x[22] + x[28])) +
      lambda[4] * (x[19] - gam[4] * (x[5] + x[11] + x[17] + x[23] + x[29])) +
      lambda[5] * (x[25] - gam[5] * (x[6] + x[12] + x[18] + x[24] + x[30]))
    )
    arr_player[k] = MathProgNLPModel(P1)
  end

  sol = x -> ((x[1] == -1.0) && (x[2] == 0.0))
  return GNEP(N, (N + 1) * ones(Int64, N), arr_player, sol = sol)
end

"""
A scalable game
"""
function NeighborsGame(N::Int64)
  if N <= 0
    throw("Error: N must be positive.")
  end

  lb = zeros(Float64, N)
  ub = 10 * ones(Float64, N)

  arr_player = Array{NLPModelsJuMP.MathProgNLPModel, 1}(undef, N)

  ul(i) = lb[i]
  ux(i) = ub[i]

  C = 0.5 * rand(N, 4)

  for k = 1:N
    km2 = mod(k - 3, N) + 1
    km1 = mod(k - 2, N) + 1
    kp1 = mod(k, N) + 1
    kp2 = mod(k + 1, N) + 1

    P1 = JuMP.Model()
    JuMP.@variable(P1, x[i = 1:N], upperbound = ux(i), lowerbound = ul(i), start = 1.0)
    JuMP.@constraint(
      P1,
      x[k]^2 + x[km2]^2 + x[km1]^2 + x[kp1]^2 + x[kp2]^2 - C[k, 1] * x[k] * x[km2] -
      C[k, 2] * x[k] * x[km1] - C[k, 3] * x[k] * x[kp1] - C[k, 4] * x[k] * x[kp2] <= 0
    )
    JuMP.@NLobjective(
      P1,
      Min,
      x[k]^2 + x[km2]^2 + x[km1]^2 + x[kp1]^2 + x[kp2]^2 - x[k] * x[kp1] * x[kp2] -
      x[k] * x[km1] * x[km2]
    )
    arr_player[k] = MathProgNLPModel(P1)
  end

  sol = x -> ((x[1] == -1.0) && (x[2] == 0.0))
  return GNEP(N, ones(Int64, N), arr_player, sol = sol)
end


using GaussianProcesses
using GaussianProcesses: distance, KernelData
import ScikitLearnBase

d, n = 3, 5

x = 2π * rand(d, n)
y = Float64[sum(sin(x[:,i])) for i in 1:n]/d
mZero = MeanZero()
kern = SE(0.0,0.0)

gp = GP(x, y, mZero, kern)

# Function verifies that predictive mean at input observations
# are the same as the output observations
function test_pred_matches_obs(gp::GP)
    y_pred, sig = predict(gp, x)
    @test_approx_eq_eps maximum(abs(gp.y - y_pred)) 0.0 1e-4
end

test_pred_matches_obs(gp)

function sk_test_pred_matches_obs() # ScikitLearn interface test
    gp_sk = ScikitLearnBase.fit!(GP(), x', y)
    y_pred = ScikitLearnBase.predict(gp_sk, x')
    @test_approx_eq_eps maximum(abs(gp_sk.y - y_pred)) 0.0 1e-4
end

sk_test_pred_matches_obs()

# Modify kernel and update
gp.k.ℓ2 = 4.0
x_pred = 2π * rand(d, n)
GaussianProcesses.update_mll!(gp)
y_pred, sig = predict(gp, x_pred)


using JFVM
using PyPlot
include("CRT.jl")

#Calculating error as a function of timesteps
dt_mult = 0.1:0.1:2

# define the domain and constants
Lx = 1.0 # [m]
Nx = 100  # number of grids
ϕ  = 0.4 # porosity
u  = 1.0/(3600*24) # m/s, eq. 1 m/day
D  = [1e-9, 1e-9, 1e-9] # m^2/s diffusivity
K_eq = 10.0
k_f = 100.0
k_b = k_f/K_eq
nu_A = 1.0 # stoichiometric coefficient of component A
nu_B = 1.0 # stoichiometric coefficient of component B
nu_C = 1.0 # stoichiometric coefficient of component C
a = 1.0 #order of reaction
b = 1.0 #order of reaction
c = 1.0 #order of reaction
N_comp = 3 # number of components

# create the mesh
m = createMesh1D(Nx, Lx)  # mesh
# m = createMeshRadial2D(Nx, Nx, Lx, Lx)

# boundary conditions
C_bc = [1.0, 0.5, 0.0] # boundary concentration
C_0 = zeros(N_comp) # initial concentration

dt_ref = 0.1*minimum(dt_mult)*Lx/(u/ϕ)/Nx
T_ref = 1.0/(0.1*minimum(dt_mult))*Nx/2.0*dt_ref

err_array = zeros(5, length(dt_mult))

#Calculating reference "true solution"
tol = 1e-10 # convergence tolerance
C_ref = CRT.CRT_GIA_eps(m, Nx, ϕ, u, D, K_eq, nu_A, nu_B, nu_C, C_bc, C_0, N_comp, dt_ref, T_ref, tol)

for i=1:length(dt_mult)
  # Defining time-stepping parameters
  dt = dt_mult[i] * Lx/(u/ϕ)/Nx
  T = (1.0/dt_mult[i]) * Nx/2.0*dt

  #Calculating alternatives
  C_SNIA        = CRT.CRT_SNIA(m, Nx, ϕ, u, D, K_eq, nu_A, nu_B, nu_C, C_bc, C_0, N_comp, dt, T)
  C_SIA         = CRT.CRT_SIA(m, Nx, ϕ, u, D, K_eq, nu_A, nu_B, nu_C, C_bc, C_0, N_comp, dt, T, tol)
  C_GIA_eps     = CRT.CRT_GIA_eps(m, Nx, ϕ, u, D, K_eq, nu_A, nu_B, nu_C, C_bc, C_0, N_comp, dt, T, tol)
  C_GIA_kin_eq  = CRT.CRT_GIA_kin_eq(m, Nx, ϕ, u, D, K_eq, nu_A, nu_B, nu_C, a, b, c, C_bc, C_0, N_comp, dt, T, tol)
  C_GIA_kin_fb  = CRT.CRT_GIA_kin_fb(m, Nx, ϕ, u, D, k_f, k_b, nu_A, nu_B, nu_C, a, b, c, C_bc, C_0, N_comp, dt, T, tol)

  for j = 1:N_comp
    err_array[1, i] += sum(abs(internalCells(C_ref[j])-internalCells(C_SNIA[j])))/sum(abs(internalCells(C_ref[j])))
    err_array[2, i] += sum(abs(internalCells(C_ref[j])-internalCells(C_SIA[j])))/sum(abs(internalCells(C_ref[j])))
    err_array[3, i] += sum(abs(internalCells(C_ref[j])-internalCells(C_GIA_eps[j])))/sum(abs(internalCells(C_ref[j])))
    err_array[4, i] += sum(abs(internalCells(C_ref[j])-internalCells(C_GIA_kin_eq[j])))/sum(abs(internalCells(C_ref[j])))
    err_array[5, i] += sum(abs(internalCells(C_ref[j])-internalCells(C_GIA_kin_fb[j])))/sum(abs(internalCells(C_ref[j])))
  end

  println(i)
end

figure()
plot(dt_mult*Lx/(u/ϕ)/Nx, err_array[1,:])
plot(dt_mult*Lx/(u/ϕ)/Nx, err_array[2,:])
plot(dt_mult*Lx/(u/ϕ)/Nx, err_array[3,:])
plot(dt_mult*Lx/(u/ϕ)/Nx, err_array[4,:])
plot(dt_mult*Lx/(u/ϕ)/Nx, err_array[5,:])
plot([Lx/(u/ϕ)/Nx, Lx/(u/ϕ)/Nx],[0, maximum(err_array)], color=:black, linestyle="--")
ylim([0, maximum(err_array)])
grid("on")
legend(["SNIA", "SIA", "GIA ("L"$\epsilon$"")", "GIA ("L"$K_{eq}$"")", "GIA ("L"$k_fk_b$"")"])
xlabel(L"$\Delta t\ [s]$")
ylabel(L"||$C_{ref}-C||_1/||C_{ref}||_1$")
savefig("error_time_plot.png")

#CRT_SIA title:
#L"$\phi\frac{\partial C_{\alpha}}{\partial t}+\nabla\cdot(uC_{\alpha})-\nabla\cdot(D_{\alpha}\nabla C_{\alpha})+\nu_{\alpha}\epsilon=0,\quad K_{eq}=$""$K_eq"" (SIA)"

#CRT_SNIA title:
#L"$\phi\frac{\partial C_{\alpha}}{\partial t}+\nabla\cdot(uC_{\alpha})-\nabla\cdot(D_{\alpha}\nabla C_{\alpha})+\nu_{\alpha}\epsilon=0,\quad K_{eq}=$""$K_eq"" (SNIA)"

#CRT_GIA_eps title:
#L"$\phi\frac{\partial C_{\alpha}}{\partial t}+\nabla\cdot(uC_{\alpha})-\nabla\cdot(D_{\alpha}\nabla C_{\alpha})+\nu_{\alpha}\epsilon=0,\quad K_{eq}=$""$K_eq"" (FIM)"

#CRT_GIA_kin_eq title:
#L"$\phi\frac{\partial C_{\alpha}}{\partial t}+\nabla\cdot(uC_{\alpha})-\nabla\cdot(D_{\alpha}\nabla C_{\alpha})+\nu_{\alpha}(K_{eq}C_A^aC_B^b-C_C^c)=0,\quad K_{eq}=$""$K_eq"" (FIM)"

#CRT_GIA_kin_fb title:
#L"$\phi\frac{\partial C_{\alpha}}{\partial t}+\nabla\cdot(uC_{\alpha})-\nabla\cdot(D_{\alpha}\nabla C_{\alpha})+\nu_{\alpha}(k_{f}C_A^aC_B^b-k_{b}C_C^c)=0,\quad K_{eq}=$""$K_eq"" (FIM)"


function diff_matrix!(M::AbstractArray{T} where T, n)
    leg_coefs = zeros(n + 1)
    leg_coefs[end] = 1
    leg = Fun(Jacobi(0, 0), leg_coefs)
    M[1, 1] = n * (n + 1) / 4
    M[end, end] = -M[1, 1]
    τ, _ = gausslobatto(n + 1)
    for (i, τᵢ) in enumerate(τ), (j, τⱼ) in enumerate(τ)
        if τᵢ != τⱼ
            M[i, j] = leg(τᵢ) / (leg(τⱼ) * (τᵢ - τⱼ))
        end
    end
end


###Projection methods aim at post-processing the
### saved scenario projections rather than
### directly operating on the saved model fits.

"""
    function create_NB_dist(μ,α)

Create a `NegativeBinomial` distribution with the correct `r,p` parameterisation from a `μ,α` parameterisation.
"""
function create_NB_dist(μ, α)
    μ += 0.001*(μ == 0.0)

    σ² = μ + α * μ^2
    p_negbin = 1 - (α * μ^2 / σ²)
    r_negbin = 1 / α
    d = NegativeBinomial(r_negbin, p_negbin)
    return d
end


using Turing, Random, Test
using Turing: Sampler, NUTS
using MCMCChains: Chains

dir = splitdir(splitdir(pathof(Turing))[1])[1]
include(dir*"/test/test_utils/AllUtils.jl")

@testset "hmc.jl" begin
    @numerical_testset "constrained bounded" begin
        obs = [0,1,0,1,1,1,1,1,1,1]

        @model constrained_test(obs) = begin
            p ~ Beta(2,2)
            for i = 1:length(obs)
                obs[i] ~ Bernoulli(p)
            end
            p
        end

        chain = sample(
            constrained_test(obs),
            HMC(1.5, 3),# using a large step size (1.5)
            1000)

        check_numerical(chain, [:p], [10/14], atol=0.1)
    end
    @numerical_testset "contrained simplex" begin
        obs12 = [1,2,1,2,2,2,2,2,2,2]

        @model constrained_simplex_test(obs12) = begin
            ps ~ Dirichlet(2, 3)
            pd ~ Dirichlet(4, 1)
            for i = 1:length(obs12)
                obs12[i] ~ Categorical(ps)
            end
            return ps
        end

        chain = sample(
            constrained_simplex_test(obs12),
            HMC(0.75, 2),
            1000)

        check_numerical(chain, ["ps[1]", "ps[2]"], [5/16, 11/16], atol=0.015)
    end
    @numerical_testset "hmc reverse diff" begin
        Random.seed!(1)
        alg = HMC(0.1, 10)
        res = sample(gdemo_default, alg, 4000)
        check_gdemo(res, rtol=0.1)
    end
    @turing_testset "matrix support" begin
        @model hmcmatrixsup() = begin
            v ~ Wishart(7, [1 0.5; 0.5 1])
        end

        model_f = hmcmatrixsup()
        vs = []
        chain = nothing
        # n_adapts, n_samples = 1_000, 2_000
        n_samples = 1_000
        for _ in 1:3
            chain = sample(model_f, HMC(0.15, 7), n_samples)
            r = reshape(chain[:v].value, n_samples, 2, 2)
            push!(vs, reshape(mean(r, dims = [1]), 2, 2))
        end

        @test maximum(abs, mean(vs) - (7 * [1 0.5; 0.5 1])) <= 0.5
    end
    @turing_testset "multivariate support" begin
        function sigmoid(t)
            return 1 / (1 + exp.(-t))
        end

        # Define NN flow
        function nn(x, b1, w11, w12, w13, bo, wo)
            h = tanh.([w11 w12 w13]' * x + b1)
            return sigmoid((wo' * h)[1] + bo)
        end

        # Generating training data
        N = 20
        M = N ÷ 4
        x1s = rand(M) * 5
        x2s = rand(M) * 5
        xt1s = Array([[x1s[i]; x2s[i]] for i = 1:M])
        append!(xt1s, Array([[x1s[i] - 6; x2s[i] - 6] for i = 1:M]))
        xt0s = Array([[x1s[i]; x2s[i] - 6] for i = 1:M])
        append!(xt0s, Array([[x1s[i] - 6; x2s[i]] for i = 1:M]))

        xs = [xt1s; xt0s]
        ts = [ones(M); ones(M); zeros(M); zeros(M)]

        # Define model

        alpha = 0.16                  # regularizatin term
        var_prior = sqrt(1.0 / alpha) # variance of the Gaussian prior

        @model bnn(ts) = begin
            b1 ~ MvNormal([0. ;0.; 0.],
                [var_prior 0. 0.; 0. var_prior 0.; 0. 0. var_prior])
            w11 ~ MvNormal([0.; 0.], [var_prior 0.; 0. var_prior])
            w12 ~ MvNormal([0.; 0.], [var_prior 0.; 0. var_prior])
            w13 ~ MvNormal([0.; 0.], [var_prior 0.; 0. var_prior])
            bo ~ Normal(0, var_prior)

            wo ~ MvNormal([0.; 0; 0],
                [var_prior 0. 0.; 0. var_prior 0.; 0. 0. var_prior])
            for i = rand(1:N, 10)
                y = nn(xs[i], b1, w11, w12, w13, bo, wo)
                ts[i] ~ Bernoulli(y)
            end
            b1, w11, w12, w13, bo, wo
        end

        # Sampling
        chain = sample(bnn(ts), HMC(0.1, 5), 10)
    end
    Random.seed!(123)
    @numerical_testset "hmcda inference" begin
        alg1 = HMCDA(1000, 0.8, 0.015)
        # alg2 = Gibbs(HMCDA(200, 0.8, 0.35, :m), HMC(0.25, 3, :s))
        alg3 = Gibbs(PG(10, :s), HMCDA(200, 0.8, 0.005, :m))
        # alg3 = Gibbs(HMC(0.25, 3, :m), PG(30, 3, :s))
        # alg3 = PG(50, 2000)

        res1 = sample(gdemo_default, alg1, 3000)
        check_gdemo(res1)

        # res2 = sample(gdemo([1.5, 2.0]), alg2)
        #
        # @test mean(res2[:s]) ≈ 49/24 atol=0.2
        # @test mean(res2[:m]) ≈ 7/6 atol=0.2

        res3 = sample(gdemo_default, alg3, 1000)
        check_gdemo(res3)
    end
    @turing_testset "hmcda constructor" begin
        alg = HMCDA(0.8, 0.75)
        println(alg)
        sampler = Sampler(alg, gdemo_default)

        alg = HMCDA(200, 0.8, 0.75)
        println(alg)
        sampler = Sampler(alg, gdemo_default)

        alg = HMCDA(200, 0.8, 0.75, :s)
        println(alg)
        sampler = Sampler(alg, gdemo_default)

        @test isa(alg, HMCDA)
        @test isa(sampler, Sampler{<:Turing.Hamiltonian})
    end
    @numerical_testset "nuts inference" begin
        alg = NUTS(1000, 0.8)
        res = sample(gdemo_default, alg, 6000)
        check_gdemo(res)
    end
    @turing_testset "nuts constructor" begin
        alg = NUTS(200, 0.65)
        sampler = Sampler(alg, gdemo_default)

        alg = NUTS(0.65)
        sampler = Sampler(alg, gdemo_default)

        alg = NUTS(200, 0.65, :m)
        sampler = Sampler(alg, gdemo_default)
    end
    @turing_testset "check discard" begin
        alg = NUTS(100, 0.8)

        c1 = sample(gdemo_default, alg, 500, discard_adapt = true)
        c2 = sample(gdemo_default, alg, 500, discard_adapt = false)

        @test size(c1, 1) == 400
        @test size(c2, 1) == 500
    end
    @turing_testset "AHMC resize" begin
        alg1 = Gibbs(PG(10, :m), NUTS(100, 0.65, :s))
        alg2 = Gibbs(PG(10, :m), HMC(0.1, 3, :s))
        alg3 = Gibbs(PG(10, :m), HMCDA(100, 0.65, 0.3, :s))
        @test sample(gdemo_default, alg1, 300) isa Chains
        @test sample(gdemo_default, alg2, 300) isa Chains
        @test sample(gdemo_default, alg3, 300) isa Chains
    end

    @turing_testset "Regression tests" begin
        # https://github.com/TuringLang/DynamicPPL.jl/issues/27
        @model mwe(::Type{T}=Float64) where {T<:Real} = begin
            m = Matrix{T}(undef, 2, 3)
            @. m ~ MvNormal(zeros(2), 1)
        end

        @test sample(mwe(), HMC(0.2, 4), 1_000) isa Chains
    end
end


using QuasiGeostrophy, BenchmarkTools

Ω = S¹(0, 2π) × S¹(0, 2π)
Nx = Ny = 2^10;
fourier_grid = FourierGrid((Nx, Ny), Ω)
fieldnames = ("u", "v", "ψ")
u, v, ψ = create_fields(names = fieldnames, grid = fourier_grid)
∂x, ∂y = create_operators(fourier_grid)
# initialize FourierField data
x, y = fourier_grid.grid
ψ(sin.(x) .* sin.(y))
u.data .= ∂y(ψ).data
v.data .= -∂x(ψ).data
Δ = FourierOperator((∂x * ∂x + ∂y * ∂y).op, FourierOperatorMetaData("Δ"))
Δ⁻¹ = inv(Δ)
Re = 0.1

function nsrhs(u,v, ∂x, ∂y, Δ, Δ⁻¹, Re)
    rhsu = -(∂x(u*u) + ∂y(u*v)) + (1/Re)*Δ(u)
    rhsv = -(∂x(u*v) + ∂y(v*v)) + (1/Re)*Δ(v)
    p = Δ⁻¹(∂x(rhsu) + ∂y(rhsv))
    rhsu -= ∂x(p)
    rhsv -= ∂y(p)
    return rhsu, rhsv
end

function nsrhs2(u,v, ∂x, ∂y, Δ, Δ⁻¹, Re)
    uu = u*u
    uv = u*v
    vu = v*u
    vv = v*v
    uux = ∂x(uu)
    uvy = ∂y(uv)
    vux = ∂x(vu)
    vvy = ∂y(vv)
    Δu = (1/Re)*Δ(u)
    Δv = (1/Re)*Δ(v)
    rhsu = -(uux + uvy) + Δu
    rhsv = -(vux + vvy) + Δv
    p = Δ⁻¹(∂x(rhsu) + ∂y(rhsv))
    rhsu -= ∂x(p)
    rhsv -= ∂y(p)
    return rhsu, rhsv
end

P = plan_fft(u.data, flags=FFTW.PATIENT)
iP = plan_ifft(u.data, flags=FFTW.PATIENT)
function nsrhs3(u,v, ∂x, ∂y, Δ, Δ⁻¹, Re, P, iP)
    ur = iP * u.data
    vr = iP * v.data
    fmd = FourierMetaData(" ", u.metadata.grid, u.metadata.transform)
    uu = FourierField( P * (ur .* ur), fmd)
    uv = FourierField( P * (ur .* vr),fmd)
    vv = FourierField( P * (vr .* vr),fmd)
    uux = ∂x(uu)
    uvy = ∂y(uv)
    vux = ∂x(uv)
    vvy = ∂y(vv)
    Δu = (1/Re)*Δ(u)
    Δv = (1/Re)*Δ(v)
    rhsu = -(uux + uvy) + Δu
    rhsv = -(vux + vvy) + Δv
    p = Δ⁻¹(∂x(rhsu) + ∂y(rhsv))
    rhsu -= ∂x(p)
    rhsv -= ∂y(p)
    return rhsu, rhsv
end

#nonfft operations
function srhs(u,v, ∂x, ∂y, Δ, Δ⁻¹, Re)
    uu = u
    Δu = (1/Re)*Δ(u)
    Δv = (1/Re)*Δ(v)
    rhsu = -(∂x(uu) + ∂y(uu)) + Δu
    rhsv = -(∂x(uu) + ∂y(uu)) + Δv
    p = Δ⁻¹(∂x(rhsu) + ∂y(rhsv))
    rhsu -= ∂x(p)
    rhsv -= ∂y(p)
    return rhsu, rhsv
end


using ZigZagBoomerang
using Test
using ZigZagBoomerang: poisson_time
"""
int (max(a + b*t, 0) + c) dt from 0 to pi
"""
function F((a,b,c), π)
    if a <= 0 && (a + π*b <= 0)
        return π*c
    elseif a > 0 && (a + π*b < 0)
        return π*c - a^2/(2*b)
    elseif a > 0 && (a + π*b >= 0)
        return 1/2*π*(2*a + π*b + 2*c)
    else # a <= 0 && (a + π*b > 0)
        return a^2/(2*b) + π*a + (π^2*b)/2 + π*c
    end
end
@testset "poisson" begin
    for k in 1:100
        a, b = 2rand(2) .- 1
        u = rand()
        s = poisson_time((a,b), u)
        if s == Inf
            t = F((a,b,false), s)
            @test t < -log(u)
        else
            t = F((a,b,false), s)
            @test t ≈ -log(u)
        end
    end
end

@testset "poisson" begin
    for k in 1:100
        a, b = 2rand(2) .- 1
        c = rand()
        u = rand()
        s = poisson_time((a,b,c), u)
        if s == Inf
            t = F((a,b,c), s)
            @test t < -log(u)
        else

            t = F((a,b,c), s)
            @test t ≈ -log(u)
        end
    end
end


# nested archimedean copulas

# Algorithms from:
# M. Hofert, `Efficiently sampling nested Archimedean copulas` Computational Statistics and Data Analysis 55 (2011) 57–70
# M. Hofert, 'Sampling  Archimedean copulas', Computational Statistics & Data Analysis, Volume 52, 2008
# McNeil, A.J., 2008. 'Sampling nested Archimedean copulas'. Journal of Statistical Computation and Simulation 78, 567–581.

#Basically we use Alg. 5 of McNeil, A.J., 2008. 'Sampling nested Archimedean copulas'.

"""
    Nested_Clayton_cop

Fields:
- children::Vector{Clayton_cop}  vector of children copulas
- m::Int ≧ 0 - number of additional marginals modeled by the parent copula only
- θ::Real - parameter of parent copula, domain θ > 0.

Nested Clayton copula: C_θ(C_ϕ₁(u₁₁, ..., u₁,ₙ₁), ..., C_ϕₖ(uₖ₁, ..., uₖ,ₙₖ), u₁ , ... uₘ).
If m > 0, the last m variables will be modeled by the parent copula only.

Constructor

    Nested_Clayton_cop(children::Vector{Clayton_cop}, m::Int, θ::Real)

Let ϕ be the vector of parameter of children copula, sufficient nesting condition requires
θ <= minimum(ϕ)

Constructor

    Nested_Clayton_cop(children::Vector{Clayton_cop}, m::Int, θ::Real, cor::Type{<:CorrelationType})

For computing copula parameter from expected correlation use empty type cor::Type{<:CorrelationType} where
SpearmanCorrelation <:CorrelationType and KendallCorrelation<:CorrelationType. If used cor put expected correlation in the place of θ  in the constructor.
The copula parameter will be computed then. The correlation must be greater than zero.

```jldoctest
julia> a = Clayton_cop(2, 2.)
Clayton_cop(2, 2.0)

julia> Nested_Clayton_cop([a], 2, 0.5)
Nested_Clayton_cop(Clayton_cop[Clayton_cop(2, 2.0)], 2, 0.5)

julia> Nested_Clayton_cop([a, a], 2, 0.5)
Nested_Clayton_cop(Clayton_cop[Clayton_cop(2, 2.0), Clayton_cop(2, 2.0)], 2, 0.5)

```
"""
struct Nested_Clayton_cop{T}
  children::Vector{Clayton_cop{T}}
  m::Int
  θ::T
  function(::Type{Nested_Clayton_cop})(children::Vector{Clayton_cop{T}}, m::Int, θ::T) where T <: Real
      m >= 0 || throw(DomainError("not supported for m  < 0 "))
      testθ(θ, "clayton")
      ϕ = [ch.θ for ch in children]
      θ <= minimum(ϕ) || throw(DomainError("violated sufficient nesting condition"))
      maximum(ϕ) < θ+2*θ^2+750*θ^5 || @warn("θ << ϕ, marginals may not be uniform")
      new{T}(children, m, θ)
  end
  function(::Type{Nested_Clayton_cop})(children::Vector{Clayton_cop{T}}, m::Int, ρ::T, cor::Type{<:CorrelationType}) where T <: Real
      m >= 0 || throw(DomainError("not supported for m  < 0 "))
      θ = getθ4arch(ρ, "clayton", cor)
      ϕ = [ch.θ for ch in children]
      θ <= minimum(ϕ) || throw(DomainError("violated sufficient nesting condition"))
      maximum(ϕ) < θ+2*θ^2+750*θ^5 || @warn("θ << ϕ, marginals may not be uniform")
      new{T}(children, m, θ)
  end
end

"""
    Nested_AMH_cop

Nested Ali-Mikhail-Haq copula, fields:
- children::Vector{AMH _cop}  vector of children copulas
- m::Int ≧ 0 - number of additional marginals modeled by the parent copula only
- θ::Real - parameter of parent copula, domain θ ∈ (0,1).

Nested Ali-Mikhail-Haq copula: C _θ(C _ϕ₁(u₁₁, ..., u₁,ₙ₁), ..., C _ϕₖ(uₖ₁, ..., uₖ,ₙₖ), u₁ , ... uₘ).
If m > 0, the last m variables will be modeled by the parent copula only.

Constructor

    Nested_AMH_cop(children::Vector{AMH_cop}, m::Int, θ::Real)

Let ϕ be the vector of parameter of children copula, sufficient nesting condition requires
θ <= minimum(ϕ)

Constructor

    Nested_AMH_cop(children::Vector{AMH_cop}, m::Int, θ::Real, cor::Type{<:CorrelationType})

For computing copula parameter from expected correlation use empty type cor::Type{<:CorrelationType} where
SpearmanCorrelation <:CorrelationType and KendallCorrelation<:CorrelationType. If used cor put expected correlation in the place of θ  in the constructor.
The copula parameter will be computed then. The correlation must be greater than zero.

```jldoctest

julia> a = AMH_cop(2, .2)
AMH_cop(2, 0.2)

julia> Nested_AMH_cop([a, a], 2, 0.1)
Nested_AMH_cop(AMH_cop[AMH_cop(2, 0.2), AMH_cop(2, 0.2)], 2, 0.1)

```
"""
struct Nested_AMH_cop{T}
  children::Vector{AMH_cop{T}}
  m::Int
  θ::T
  function(::Type{Nested_AMH_cop})(children::Vector{AMH_cop{T}}, m::Int, θ::T) where T <: Real
      m >= 0 || throw(DomainError("not supported for m  < 0 "))
      testθ(θ, "amh")
      ϕ = [ch.θ for ch in children]
      θ <= minimum(ϕ) || throw(DomainError("violated sufficient nesting condition"))
      new{T}(children, m, θ)
  end
  function(::Type{Nested_AMH_cop})(children::Vector{AMH_cop{T}}, m::Int, ρ::T, cor::Type{<:CorrelationType}) where T <: Real
      m >= 0 || throw(DomainError("not supported for m  < 0 "))
      θ = getθ4arch(ρ, "amh", cor)
      ϕ = [ch.θ for ch in children]
      θ <= minimum(ϕ) || throw(DomainError("violated sufficient nesting condition"))
      new{T}(children, m, θ)
  end
end

"""
    Nested_Frank_cop

Fields:
- children::Vector{Frank_cop}  vector of children copulas
- m::Int ≧ 0 - number of additional marginals modeled by the parent copula only
- θ::Real - parameter of parent copula, domain θ ∈ (0,∞).

Nested Frank copula: C _θ(C _ϕ₁(u₁₁, ..., u₁,ₙ₁), ..., C _ϕₖ(uₖ₁, ..., uₖ,ₙₖ), u₁ , ... uₘ).
If m > 0, the last m variables will be modeled by the parent copula only.

Constructor

    Nested_Frank_cop(children::Vector{Frank_cop}, m::Int, θ::Real)

Let ϕ be the vector of parameter of children copula, sufficient nesting condition requires
θ <= minimum(ϕ)

Constructor

    Nested_Frank_cop(children::Vector{Frank_ cop}, m::Int, θ::Real, cor::Type{<:CorrelationType})

For computing copula parameter from expected correlation use empty type cor::Type{<:CorrelationType} where
SpearmanCorrelation <:CorrelationType and KendallCorrelation<:CorrelationType. If used cor put expected correlation in the place of θ  in the constructor.
The copula parameter will be computed then. The correlation must be greater than zero.

```jldoctests

julia> a = Frank_cop(2, 5.)
Frank_cop(2, 5.0)

julia> Nested_Frank_cop([a, a], 2, 0.1)
Nested_Frank_cop(Frank_cop[Frank_cop(2, 5.0), Frank_cop(2, 5.0)], 2, 0.1)
```
"""
struct Nested_Frank_cop{T}
  children::Vector{Frank_cop{T}}
  m::Int
  θ::T
  function(::Type{Nested_Frank_cop})(children::Vector{Frank_cop{T}}, m::Int, θ::T) where T <: Real
      m >= 0 || throw(DomainError("not supported for m  < 0 "))
      testθ(θ, "frank")
      ϕ = [ch.θ for ch in children]
      θ <= minimum(ϕ) || throw(DomainError("violated sufficient nesting condition"))
      new{T}(children, m, θ)
  end
  function(::Type{Nested_Frank_cop})(children::Vector{Frank_cop{T}}, m::Int, ρ::T, cor::Type{<:CorrelationType}) where T <: Real
      m >= 0 || throw(DomainError("not supported for m  < 0 "))
      θ = getθ4arch(ρ, "frank", cor)
      ϕ = [ch.θ for ch in children]
      θ <= minimum(ϕ) || throw(DomainError("violated sufficient nesting condition"))
      new{T}(children, m, θ)
  end
end

"""
    Nested_Gumbel_cop

Fields:
- children::Vector{Gumbel_cop}  vector of children copulas
- m::Int ≧ 0 - number of additional marginals modeled by the parent copula only
- θ::Real - parameter of parent copula, domain θ ∈ [1,∞).

Nested Gumbel copula: C _θ(C _ϕ₁(u₁₁, ..., u₁,ₙ₁), ..., C _ϕₖ(uₖ₁, ..., uₖ,ₙₖ), u₁ , ... uₘ).
If m > 0, the last m variables will be modeled by the parent copula only.

Constructor

    Nested_Gumbel_cop(children::Vector{Gumbel_cop}, m::Int, θ::Real)

Let ϕ be the vector of parameter of children copula, sufficient nesting condition requires
θ <= minimum(ϕ)

Constructor

    Nested_Gumbel_cop(children::Vector{Gumbel_cop}, m::Int, θ::Real, cor::Type{<:CorrelationType})

For computing copula parameter from expected correlation use empty type cor::Type{<:CorrelationType} where
SpearmanCorrelation <:CorrelationType and KendallCorrelation<:CorrelationType. If used cor put expected correlation in the place of θ  in the constructor.
The copula parameter will be computed then. The correlation must be greater than zero.

```jldoctest

julia> a = Gumbel_cop(2, 5.)
Gumbel_cop(2, 5.0)

julia> Nested_Gumbel_cop([a, a], 2, 2.1)
Nested_Gumbel_cop(Gumbel_cop[Gumbel_cop(2, 5.0), Gumbel_cop(2, 5.0)], 2, 2.1)
```
"""
struct Nested_Gumbel_cop{T}
  children::Vector{Gumbel_cop{T}}
  m::Int
  θ::T
  function(::Type{Nested_Gumbel_cop})(children::Vector{Gumbel_cop{T}}, m::Int, θ::T) where T <: Real
      m >= 0 || throw(DomainError("not supported for m  < 0 "))
      testθ(θ, "gumbel")
      ϕ = [ch.θ for ch in children]
      θ <= minimum(ϕ) || throw(DomainError("violated sufficient nesting condition"))
      new{T}(children, m, θ)
  end
  function(::Type{Nested_Gumbel_cop})(children::Vector{Gumbel_cop{T}}, m::Int, ρ::T, cor::Type{<:CorrelationType}) where T <: Real
      m >= 0 || throw(DomainError("not supported for m  < 0 "))
      θ = getθ4arch(ρ, "gumbel", cor)
      ϕ = [ch.θ for ch in children]
      θ <= minimum(ϕ) || throw(DomainError("violated sufficient nesting condition"))
      new{T}(children, m, θ)
  end
end

"""
    simulate_copula(t::Int, copula::Nested_Clayton_cop; rng::AbstractRNG = Random.GLOBAL_RNG)

Returns t realizations of data generated using Nested Clayton copula

```jldoctest

julia> Random.seed!(43);

julia> c1 = Clayton_cop(2, 2.)
Clayton_cop(2, 2.0)

julia> c2 = Clayton_cop(2, 3.)
Clayton_cop(2, 3.0)

julia> cp = Nested_Clayton_cop([c1, c2], 1, 1.1)
Nested_Clayton_cop(Clayton_cop[Clayton_cop(2, 2.0), Clayton_cop(2, 3.0)], 1, 1.1)

julia> simulate_copula(4, cp)
4×5 Array{Float64,2}:
0.514118  0.84089   0.870106  0.906233  0.739349
0.588245  0.85816   0.935308  0.944444  0.709009
0.59625   0.665947  0.483649  0.603074  0.153501
0.200051  0.304099  0.242572  0.177836  0.0851603
```
"""
function simulate_copula(t::Int, copula::Nested_Clayton_cop{T}; rng::AbstractRNG = Random.GLOBAL_RNG) where T <: Real
     n = [ch.n for ch in copula.children]
     n2 = sum(n)+copula.m
     U = zeros(T, t, n2)
     simulate_copula!(U, copula; rng = rng)
     return U
end

"""
    simulate_copula!(U::Matrix{Real}, copula::Nested_Clayton_cop; rng::AbstractRNG = Random.GLOBAL_RNG)

Given the preallocated output U, Returns size(U,1) realizations from the nested Clayton copula - Nested_Clayton_cop
N.o. marginals is size(U,2), these must be euqal to n.o. marginals of the copula

```jldoctest

julia> c1 = Clayton_cop(2, 2.)
Clayton_cop(2, 2.0)

julia> c2 = Clayton_cop(2, 3.)
Clayton_cop(2, 3.0)

julia> cp = Nested_Clayton_cop([c1, c2], 1, 1.1)
Nested_Clayton_cop(Clayton_cop[Clayton_cop(2, 2.0), Clayton_cop(2, 3.0)], 1, 1.1)

julia> U = zeros(5,5)
5×5 Array{Float64,2}:
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0

julia> Random.seed!(43);

julia> simulate_copula!(U, cp)

julia> U
5×5 Array{Float64,2}:
 0.514118  0.84089    0.870106   0.906233  0.739349
 0.588245  0.85816    0.935308   0.944444  0.709009
 0.59625   0.665947   0.483649   0.603074  0.153501
 0.200051  0.304099   0.242572   0.177836  0.0851603
 0.120914  0.0683055  0.0586907  0.126257  0.519241
```
"""
function simulate_copula!(U::Matrix{T}, copula::Nested_Clayton_cop{T}; rng::AbstractRNG = Random.GLOBAL_RNG) where T <: Real
      m = copula.m
      θ = copula.θ
      children = copula.children
      ϕ = [ch.θ for ch in children]
      n = [ch.n for ch in children]
      n1 = vcat([collect(1:n[1])], [collect(cumsum(n)[i]+1:cumsum(n)[i+1]) for i in 1:length(n)-1])
      n2 = sum(n)+m
      size(U, 2) == n2 || throw(AssertionError("n.o. margins in pre allocated output and copula not equal"))
      for j in 1:size(U,1)
         rand_vec = rand(rng, T, n2+1)
         U[j,:] = nested_clayton_gen(n1, ϕ, θ, rand_vec; rng=rng)
     end
end

"""
     simulate_copula(t::Int, copula::Nested_AMH_cop; rng::AbstractRNG = Random.GLOBAL_RNG)

Returns t realizations of data generated using Nested AMH copula

```jldoctest

julia> c1 = AMH_cop(2, .7)
AMH_cop(2, 0.7)

julia> c2 = AMH_cop(2, .8)
AMH_cop(2, 0.8)

julia> cp = Nested_AMH_cop([c1, c2], 1, 0.2)
Nested_AMH_cop(AMH_cop[AMH_cop(2, 0.7), AMH_cop(2, 0.8)], 1, 0.2)

julia> Random.seed!(43);

julia> simulate_copula(4, cp)
4×5 Array{Float64,2}:
 0.557393  0.902767  0.909853  0.938522  0.586068
 0.184204  0.866664  0.699134  0.226744  0.102932
 0.268634  0.383355  0.179023  0.533749  0.995958
 0.578143  0.840169  0.743728  0.963226  0.576695
```
"""
function simulate_copula(t::Int, copula::Nested_AMH_cop{T}; rng::AbstractRNG = Random.GLOBAL_RNG) where T <: Real
     n = [ch.n for ch in copula.children]
     n2 = sum(n)+copula.m
     U = zeros(T, t, n2)
     simulate_copula!(U, copula; rng = rng)
     return U
end

"""
    simulate_copula!(U::Matrix{Real}, copula::Nested_AMH_cop; rng::AbstractRNG = Random.GLOBAL_RNG)

Given the preallocated output U, Returns size(U,1) realizations from the nested AMH copula - Nested_AMH_cop
N.o. marginals is size(U,2), these must be euqal to n.o. marginals of the copula

```jldoctest

julia> c1 = AMH_cop(2, .7)
AMH_cop(2, 0.7)

julia> c2 = AMH_cop(2, .8)
AMH_cop(2, 0.8)

julia> cp = Nested_AMH_cop([c1, c2], 1, 0.2)
Nested_AMH_cop(AMH_cop[AMH_cop(2, 0.7), AMH_cop(2, 0.8)], 1, 0.2)

julia> Random.seed!(43);

julia> U = zeros(4,5)
4×5 Array{Float64,2}:
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0

julia> simulate_copula!(U, cp)

julia> U
4×5 Array{Float64,2}:
 0.557393  0.902767  0.909853  0.938522  0.586068
 0.184204  0.866664  0.699134  0.226744  0.102932
 0.268634  0.383355  0.179023  0.533749  0.995958
 0.578143  0.840169  0.743728  0.963226  0.576695
```
"""
function simulate_copula!(U::Matrix{T}, copula::Nested_AMH_cop{T}; rng::AbstractRNG = Random.GLOBAL_RNG) where T <: Real
    m = copula.m
    θ = copula.θ
    children = copula.children
    ϕ = [ch.θ for ch in children]
    n = [ch.n for ch in children]
    n1 = vcat([collect(1:n[1])], [collect(cumsum(n)[i]+1:cumsum(n)[i+1]) for i in 1:length(n)-1])
    n2 = sum(n)+m
    size(U, 2) == n2 || throw(AssertionError("n.o. margins in pre allocated output and copula not equal"))
    for j in 1:size(U,1)
       rand_vec = rand(rng, T, n2+1)
       U[j,:] = nested_amh_gen(n1, ϕ, θ, rand_vec; rng=rng)
   end
end

"""
    simulate_copula(t::Int, copula::Nested_Frank_cop; rng::AbstractRNG = Random.GLOBAL_RNG)

Returns t realizations of data generated using Nested Frank copula
```jldoctest

julia> c1 = Frank_cop(2, 4.)
Frank_cop(2, 4.0)

julia> c2 = Frank_cop(2, 5.)
Frank_cop(2, 5.0)

julia> c = Nested_Frank_cop([c1, c2],1, 2.0)
Nested_Frank_cop(Frank_cop[Frank_cop(2, 4.0), Frank_cop(2, 5.0)], 1, 2.0)

julia> Random.seed!(43);

julia> simulate_copula(1, c)
1×5 Array{Float64,2}:
 0.642765  0.901183  0.969422  0.9792  0.74155
```
"""
function simulate_copula(t::Int, copula::Nested_Frank_cop{T}; rng::AbstractRNG = Random.GLOBAL_RNG) where T <: Real
     n = [ch.n for ch in copula.children]
     n2 = sum(n)+copula.m
     U = zeros(T, t, n2)
     simulate_copula!(U, copula; rng = rng)
     return U
end

"""
    simulate_copula!(U::Matrix{Real}, copula::Nested_Frank_cop; rng::AbstractRNG = Random.GLOBAL_RNG)

Given the preallocated output U, Returns size(U,1) realizations from the nested Frank copula a - Nested_Frank_cop
N.o. marginals is size(U,2), these must be euqal to n.o. marginals of the copula

```jldoctest

julia> c1 = Frank_cop(2, 4.)
Frank_cop(2, 4.0)

julia> c2 = Frank_cop(2, 5.)
Frank_cop(2, 5.0)

julia> c = Nested_Frank_cop([c1, c2],1, 2.0)
Nested_Frank_cop(Frank_cop[Frank_cop(2, 4.0), Frank_cop(2, 5.0)], 1, 2.0)

julia> U = zeros(1,5)
1×5 Array{Float64,2}:
 0.0  0.0  0.0  0.0  0.0

julia> Random.seed!(43);

julia> simulate_copula!(U, c)

julia> U
1×5 Array{Float64,2}:
 0.642765  0.901183  0.969422  0.9792  0.74155

```
"""
function simulate_copula!(U::Matrix{T}, copula::Nested_Frank_cop{T}; rng::AbstractRNG = Random.GLOBAL_RNG) where T <: Real
    m = copula.m
    θ = copula.θ
    children = copula.children
    ϕ = [ch.θ for ch in children]
    n = [ch.n for ch in children]
    n2 = sum(n)+m
    size(U, 2) == n2 || throw(AssertionError("n.o. margins in pre allocated output and copula not equal"))

    ws = [logseriescdf(1-exp(theta)) for theta in ϕ]
    n1 = vcat([collect(1:n[1])], [collect(cumsum(n)[i]+1:cumsum(n)[i+1]) for i in 1:length(n)-1])
    w = logseriescdf(1-exp(-θ))
    for j in 1:size(U,1)
       rand_vec = rand(rng, T, n2+1)
       U[j,:] = nested_frank_gen(n1, ϕ, θ, rand_vec, w, ws; rng=rng)
   end
end

"""
    simulate_copula(t::Int, copula::Nested_Gumbel_cop; rng::AbstractRNG = Random.GLOBAL_RNG)

Returns t realizations of data generated using  Nested Gumbel copula

```jldoctest

julia> Random.seed!(43);

julia> c1 = Gumbel_cop(2, 2.)
Gumbel_cop(2, 2.0)

julia> c2 = Gumbel_cop(2, 3.)
Gumbel_cop(2, 3.0)

julia> cp = Nested_Gumbel_cop([c1, c2], 1, 1.1)
Nested_Gumbel_cop(Gumbel_cop[Gumbel_cop(2, 2.0), Gumbel_cop(2, 3.0)], 1, 1.1)

julia> simulate_copula(4, cp)
4×5 Array{Float64,2}:
 0.387085   0.693399   0.94718   0.953776  0.583379
 0.0646972  0.0865914  0.990691  0.991127  0.718803
 0.966896   0.709233   0.788019  0.855622  0.755476
 0.272487   0.106996   0.756052  0.834068  0.661432
```
"""
function simulate_copula(t::Int, copula::Nested_Gumbel_cop{T}; rng::AbstractRNG = Random.GLOBAL_RNG) where T <: Real
     n = [ch.n for ch in copula.children]
     n2 = sum(n)+copula.m
     U = zeros(T, t, n2)
     simulate_copula!(U, copula; rng = rng)
     return U
end

"""
    simulate_copula!(U::Matrix{Real}, copula::Nested_Gumbel_cop; rng::AbstractRNG = Random.GLOBAL_RNG)

Given the preallocated output U, Returns size(U,1) realizations from the nested Gumbel copula - Nested_Gumbel_cop
N.o. marginals is size(U,2), these must be euqal to n.o. marginals of the copula

```jldoctest
julia> c1 = Gumbel_cop(2, 2.)
Gumbel_cop(2, 2.0)

julia> c2 = Gumbel_cop(2, 3.)
Gumbel_cop(2, 3.0)

julia> cp = Nested_Gumbel_cop([c1, c2], 1, 1.1)
Nested_Gumbel_cop(Gumbel_cop[Gumbel_cop(2, 2.0), Gumbel_cop(2, 3.0)], 1, 1.1)

julia> u = zeros(4,5)
4×5 Array{Float64,2}:
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0

julia> Random.seed!(43);

julia> simulate_copula!(u, cp)

julia> u
4×5 Array{Float64,2}:
 0.387085   0.693399   0.94718   0.953776  0.583379
 0.0646972  0.0865914  0.990691  0.991127  0.718803
 0.966896   0.709233   0.788019  0.855622  0.755476
 0.272487   0.106996   0.756052  0.834068  0.661432
```
"""
function simulate_copula!(U::Matrix{T}, copula::Nested_Gumbel_cop{T}; rng::AbstractRNG = Random.GLOBAL_RNG) where T <: Real
    m = copula.m
    θ = copula.θ
    children = copula.children
    ϕ = [ch.θ for ch in children]
    n = [ch.n for ch in children]
    n1 = vcat([collect(1:n[1])], [collect(cumsum(n)[i]+1:cumsum(n)[i+1]) for i in 1:length(n)-1])
    n2 = sum(n)+m
    size(U, 2) == n2 || throw(AssertionError("n.o. margins in pre allocated output and copula not equal"))

    for j in 1:size(U,1)
       rand_vec = rand(rng, T, n2)
       U[j,:] = nested_gumbel_gen(n1, ϕ, θ, rand_vec; rng=rng)
   end
end

"""
    Double_Nested_Gumbel_cop

Fields:
- children::Vector{Nested _Gumbel _cop}  vector of children copulas
- θ::Real - parameter of parent copula, domain θ ∈ [1,∞).

Constructor

    Double_Nested_Gumbel _cop(children::Vector{Nested_Gumbel_cop}, θ::Real)
requires sufficient nesting condition for θ and child copulas.

Constructor

    Doulbe_Nested_Gumbel_cop(children::Vector{Nested_Gumbel_cop}, θ::Real, cor::Type{<:CorrelationType})

For computing copula parameter from expected correlation use empty type cor::Type{<:CorrelationType} where
SpearmanCorrelation <:CorrelationType and KendallCorrelation<:CorrelationType. If used cor put expected correlation in the place of θ  in the constructor.
The copula parameter will be computed then. The correlation must be greater than zero.

```jldoctest

julia> a = Gumbel_cop(2, 5.)
Gumbel_cop(2, 5.0)

julia> b = Gumbel_cop(2, 6.)
Gumbel_cop(2, 6.0)

julia> c = Gumbel_cop(2, 5.5)
Gumbel_cop(2, 5.5)

julia> p1 = Nested_Gumbel_cop([a,b], 1, 2.)
Nested_Gumbel_cop(Gumbel_cop[Gumbel_cop(2, 5.0), Gumbel_cop(2, 6.0)], 1, 2.0)

julia> p2 = Nested_Gumbel_cop([c], 2, 2.1)
Nested_Gumbel_cop(Gumbel_cop[Gumbel_cop(2, 5.5)], 2, 2.1)

julia> Double_Nested_Gumbel_cop([p1, p2], 1.5)
Double_Nested_Gumbel_cop(Nested_Gumbel_cop[Nested_Gumbel_cop(Gumbel_cop[Gumbel_cop(2, 5.0), Gumbel_cop(2, 6.0)], 1, 2.0), Nested_Gumbel_cop(Gumbel_cop[Gumbel_cop(2, 5.5)], 2, 2.1)], 1.5)
```
"""
struct Double_Nested_Gumbel_cop{T}
  children::Vector{Nested_Gumbel_cop{T}}
  θ::T
  function(::Type{Double_Nested_Gumbel_cop})(children::Vector{Nested_Gumbel_cop{T}}, θ::T) where T <: Real
      testθ(θ, "gumbel")
      ϕ = [ch.θ for ch in children]
      θ <= minimum(ϕ) || throw(DomainError("violated sufficient nesting condition"))
      new{T}(children, θ)
  end
  function(::Type{Double_Nested_Gumbel_cop})(children::Vector{Nested_Gumbel_cop{T}}, ρ::T, cor::Type{<:CorrelationType}) where T <: Real
      θ = getθ4arch(ρ, "gumbel", cor)
      ϕ = [ch.θ for ch in children]
      θ <= minimum(ϕ) || throw(DomainError("violated sufficient nesting condition"))
      new{T}(children, θ)
  end
end

"""
    simulate_copula(t::Int, copula::Double_Nested_Gumbel_cop; rng::AbstractRNG = Random.GLOBAL_RNG)

Simulate t realization of the Double Nested Gumbel copula i.e.

    Double_Nested_Gumbel_cop(vec_of_children, θ)

```jldoctest
julia> a = Gumbel_cop(2, 5.)
Gumbel_cop(2, 5.0)

julia> b = Gumbel_cop(2, 6.)
Gumbel_cop(2, 6.0)

julia> c = Gumbel_cop(2, 5.5)
Gumbel_cop(2, 5.5)

julia> p1 = Nested_Gumbel_cop([a,b], 1, 2.)
Nested_Gumbel_cop(Gumbel_cop[Gumbel_cop(2, 5.0), Gumbel_cop(2, 6.0)], 1, 2.0)

julia> p2 = Nested_Gumbel_cop([c], 2, 2.1)
Nested_Gumbel_cop(Gumbel_cop[Gumbel_cop(2, 5.5)], 2, 2.1)

julia> copula = Double_Nested_Gumbel_cop([p1, p2], 1.5)
Double_Nested_Gumbel_cop(Nested_Gumbel_cop[Nested_Gumbel_cop(Gumbel_cop[Gumbel_cop(2, 5.0), Gumbel_cop(2, 6.0)], 1, 2.0), Nested_Gumbel_cop(Gumbel_cop[Gumbel_cop(2, 5.5)], 2, 2.1)], 1.5)

julia> Random.seed!(43);

julia> simulate_copula(5, copula)
5×9 Array{Float64,2}:
 0.598555   0.671584   0.8403     0.846844  0.634609  0.686927  0.693906  0.651968    0.670812
 0.0518892  0.191236   0.0803859  0.104325  0.410727  0.529354  0.557387  0.370518    0.592302
 0.367914   0.276196   0.382616   0.470171  0.264135  0.144503  0.13097   0.00687015  0.01417
 0.632727   0.596879   0.244176   0.338809  0.58771   0.147539  0.219099  0.287937    0.0569943
 0.310365   0.0483216  0.119312   0.107155  0.336619  0.279602  0.262756  0.438432    0.403061
```
"""
function simulate_copula(t::Int, copula::Double_Nested_Gumbel_cop{T}; rng::AbstractRNG = Random.GLOBAL_RNG) where T <: Real
    v = copula.children
    ns = [[ch.n for ch in vs.children] for vs in v]
    dims = sum([sum(ns[i])+v[i].m for i in 1:length(v)])
     U = zeros(T, t, dims)
     simulate_copula!(U, copula; rng = rng)
     return U
end

"""
    simulate_copula!(U::Matrix{Real}, copula::Double_Nested_Gumbel_cop; rng::AbstractRNG = Random.GLOBAL_RNG)

Given the preallocated output U, Returns size(U,1) realizations from the double nested Gumbel copula - Double_Nested_Gumbel_cop
N.o. marginals is size(U,2), these must be euqal to n.o. marginals of the copula

```jldoctest
julia> a = Gumbel_cop(2, 5.)
Gumbel_cop(2, 5.0)

julia> b = Gumbel_cop(2, 6.)
Gumbel_cop(2, 6.0)

julia> c = Gumbel_cop(2, 5.5)
Gumbel_cop(2, 5.5)

julia> p1 = Nested_Gumbel_cop([a,b], 1, 2.)
Nested_Gumbel_cop(Gumbel_cop[Gumbel_cop(2, 5.0), Gumbel_cop(2, 6.0)], 1, 2.0)

julia> p2 = Nested_Gumbel_cop([c], 2, 2.1)
Nested_Gumbel_cop(Gumbel_cop[Gumbel_cop(2, 5.5)], 2, 2.1)

julia> copula = Double_Nested_Gumbel_cop([p1, p2], 1.5)
Double_Nested_Gumbel_cop(Nested_Gumbel_cop[Nested_Gumbel_cop(Gumbel_cop[Gumbel_cop(2, 5.0), Gumbel_cop(2, 6.0)], 1, 2.0), Nested_Gumbel_cop(Gumbel_cop[Gumbel_cop(2, 5.5)], 2, 2.1)], 1.5)

julia> u = zeros(3,9)
3×9 Array{Float64,2}:
 0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0

julia> Random.seed!(43);

julia> simulate_copula!(u, copula)

julia> u
3×9 Array{Float64,2}:
 0.598555   0.671584  0.8403     0.846844  0.634609  0.686927  0.693906  0.651968    0.670812
 0.0518892  0.191236  0.0803859  0.104325  0.410727  0.529354  0.557387  0.370518    0.592302
 0.367914   0.276196  0.382616   0.470171  0.264135  0.144503  0.13097   0.00687015  0.01417
```
"""

function simulate_copula!(U::Matrix{T}, copula::Double_Nested_Gumbel_cop{T}; rng::AbstractRNG = Random.GLOBAL_RNG) where T <: Real
    θ = copula.θ
    v = copula.children
    ns = [[ch.n for ch in vs.children] for vs in v]
    Ψs = [[ch.θ for ch in vs.children] for vs in v]
    dims = sum([sum(ns[i])+v[i].m for i in 1:length(v)])
    size(U, 2) == dims || throw(AssertionError("n.o. margins in pre allocated output and copula not equal"))

    for j in 1:size(U,1)
        X = T[]
        for k in 1:length(v)
            n = ns[k]
            n1 = vcat([collect(1:n[1])], [collect(cumsum(n)[i]+1:cumsum(n)[i+1]) for i in 1:length(n)-1])
            n2 = sum(n)+v[k].m
            rand_vec = rand(rng, T, n2)
            X = vcat(X, nested_gumbel_gen(n1, Ψs[k], v[k].θ./θ, rand_vec; rng = rng))
        end
        X = -log.(X)./levyel(θ, rand(rng), rand(rng))
        U[j,:] = exp.(-X.^(1/θ))
    end
end

"""
    Hierarchical_Gumbel_cop

Fields:
- n::Int - number of marginals
- θ::Vector{Real} - vector of parameters, must be decreasing  and θ[end] ≧ 1, for the
sufficient nesting condition to be fulfilled.

The hierarchically nested Gumbel copula C_θₙ₋₁(C_θₙ₋₂( ... C_θ₂(C_θ₁(u₁, u₂), u₃)...uₙ₋₁) uₙ)

Constructor

    Hierarchical_Gumbel_cop(θ::Vector{Real})

Constructor

    Hierarchical_Gumbel_cop(ρ::Vector{Real}, cor::Type{<:CorrelationType})

For computing copula parameters from expected correlations use empty type cor::Type{<:CorrelationType} where
SpearmanCorrelation <:CorrelationType and KendallCorrelation<:CorrelationType. If used cor put expected correlations in the place of θ  in the constructor.
The copula parameters will be computed then. The correlation must be greater than zero.

```jldoctest

julia> c = Hierarchical_Gumbel_cop([5., 4., 3.])
Hierarchical_Gumbel_cop(4, [5.0, 4.0, 3.0])

julia> c = Hierarchical_Gumbel_cop([0.95, 0.5, 0.05], KendallCorrelation)
Hierarchical_Gumbel_cop(4, [19.999999999999982, 2.0, 1.0526315789473684])
```
"""
struct Hierarchical_Gumbel_cop{T}
  n::Int
  θ::Vector{T}
  function(::Type{Hierarchical_Gumbel_cop})(θ::Vector{T}) where T <: Real
      testθ(θ[end], "gumbel")
      issorted(θ; rev=true) || throw(DomainError("violated sufficient nesting condition, parameters must be descending"))
      new{T}(length(θ)+1, θ)
  end
  function(::Type{Hierarchical_Gumbel_cop})(ρ::Vector{T}, cor::Type{<:CorrelationType}) where T <: Real
      θ = map(i -> getθ4arch(ρ[i], "gumbel", cor), 1:length(ρ))
      issorted(θ; rev=true) || throw(DomainError("violated sufficient nesting condition, parameters must be descending"))
      new{T}(length(θ)+1, θ)
  end
end

"""
    simulate_copula(t::Int, copula::Hierarchical_Gumbel_cop; rng::AbstractRNG = Random.GLOBAL_RNG)

Returns t realizations of multivariate data from hierarchically nested Gumbel copula, i.e.

    Hierarchical_Gumbel_cop(θ)

```jldoctest
julia> using Random

julia> Random.seed!(43);

julia> c = Hierarchical_Gumbel_cop([5., 4., 3.])
Hierarchical_Gumbel_cop(4, [5.0, 4.0, 3.0])

julia> simulate_copula(3, c)
3×4 Array{Float64,2}:
 0.100353  0.207903  0.0988337  0.0431565
 0.347417  0.217052  0.223734   0.042903
 0.73617   0.347349  0.168348   0.410963
```
"""
function simulate_copula(t::Int, copula::Hierarchical_Gumbel_cop{T}; rng::AbstractRNG = Random.GLOBAL_RNG) where T <: Real
    U = zeros(T, t, copula.n)
    simulate_copula!(U, copula; rng = rng)
    return U
end

"""
    simulate_copula!(U::Matrix{Real}, copula::Hierarchical_Gumbel_cop; rng::AbstractRNG = Random.GLOBAL_RNG)

Given the preallocated output U, Returns size(U,1) realizations from the hierachically nested Gumbel copula - Hierarchical_Gumbel_cop
N.o. marginals is size(U,2), these must be euqal to n.o. marginals of the copula i.e. copula.n

```jldoctest

julia> c = Hierarchical_Gumbel_cop([5., 4., 3.])
Hierarchical_Gumbel_cop(4, [5.0, 4.0, 3.0])

julia> Random.seed!(43);

julia> u = zeros(3,4)
3×4 Array{Float64,2}:
 0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0

julia> simulate_copula!(u, c)

julia> u
3×4 Array{Float64,2}:
 0.100353  0.207903  0.0988337  0.0431565
 0.347417  0.217052  0.223734   0.042903
 0.73617   0.347349  0.168348   0.410963
```
"""
function simulate_copula!(U::Matrix{T}, copula::Hierarchical_Gumbel_cop{T}; rng::AbstractRNG = Random.GLOBAL_RNG) where T <: Real
  θ = copula.θ
  θ = vcat(θ, [1.])
  size(U, 2) == copula.n || throw(AssertionError("n.o. margins in pre allocated output and copula not equal"))

  for j in 1:size(U,1)
      X = rand(rng, T)
      for i in 1:(copula.n-1)
          X = gumbel_step(vcat(X, rand(rng)), θ[i], θ[i+1]; rng = rng)
      end
     U[j,:] = X
    end
end

"""
    nested_gumbel_gen(n::Vector{Vector{Int}}, ϕ::Vector{Real},
                         θ::Real, rand_vec::Vector{Real}; rng::AbstractRNG)

Convert a vector of random independnet elements to such sampled from the
Nested Gumbel copula
"""
function nested_gumbel_gen(n::Vector{Vector{Int}}, ϕ::Vector{T},
                         θ::T, rand_vec::Vector{T}; rng::AbstractRNG) where T <: Real
    V0 = levyel(θ, rand(rng, T), rand(rng, T))
    u = copy(rand_vec)
    for i in 1:length(n)
      u[n[i]] = gumbel_step(rand_vec[n[i]], ϕ[i], θ; rng = rng)
    end
    u = -log.(u)./V0
    return exp.(-u.^(1/θ))
end

"""
    nested_amh_gen(n::Vector{Vector{Int}}, ϕ::Vector{Real},
                         θ::Real, rand_vec::Vector{Real}; rng::AbstractRNG)
"""
function nested_amh_gen(n::Vector{Vector{Int}}, ϕ::Vector{T},
                         θ::T, rand_vec::Vector{T}; rng::AbstractRNG) where T <: Real
    V0 = 1 .+ quantile.(Geometric(1-θ), rand_vec[end])
    u = copy(rand_vec[1:end-1])
    for i in 1:length(n)
      u[n[i]] = amh_step(rand_vec[n[i]], V0, ϕ[i], θ; rng = rng)
    end
    u = -log.(u)./V0
    return (1-θ) ./(exp.(u) .-θ)
end

"""
    nested_frank_gen(n::Vector{Vector{Int}}, ϕ::Vector{Real}, θ::Real, rand_vec::Vector{Real}, logseries::Vector{Real},
                         logseries_children::Vector{Vector{Real}};
                         rng::AbstractRNG)
"""
function nested_frank_gen(n::Vector{Vector{Int}}, ϕ::Vector{T},
                         θ::T, rand_vec::Vector{T}, logseries::Vector{T},
                         logseries_children::Vector{Vector{T}};
                         rng::AbstractRNG) where T <: Real
    V0 = findlast(logseries .< rand_vec[end])
    u = copy(rand_vec[1:end-1])
    for i in 1:length(n)
      u[n[i]] = frank_step(rand_vec[n[i]], V0, ϕ[i], θ, logseries_children[i]; rng = rng)
    end
    u = -log.(u)./V0
    return -log.(1 .+exp.(-u) .*(exp(-θ)-1)) ./θ
end

"""
    nested_clayton_gen(n::Vector{Vector{Int}}, ϕ::Vector{Real}, θ::Real, rand_vec::Vector{Real}; rng::AbstractRNG = Random.GLOBAL_RNG)
"""
function nested_clayton_gen(n::Vector{Vector{Int}}, ϕ::Vector{T},
                         θ::T, rand_vec::Vector{T}; rng::AbstractRNG) where T <: Real
    V0 = gamma_inc_inv(1/θ, T(1.), T(1.)-rand_vec[end])
    #quantile.(Gamma(1/θ, 1), rand_vec[end])
    u = copy(rand_vec[1:end-1])
    for i in 1:length(n)
      u[n[i]] = clayton_step(rand_vec[n[i]], V0, ϕ[i], θ; rng = rng)
    end
    u = -log.(u)./V0
    return (1 .+ u).^(-1/θ)
end

"""
    gumbel_step(u::Vector{Real}, ϕ::Real, θ::Real; rng::AbstractRNG)
"""
function gumbel_step(u::Vector{T}, ϕ::T, θ::T; rng::AbstractRNG) where T <: Real
    u = -log.(u)./levyel(ϕ/θ, rand(rng, T), rand(rng, T))
    return exp.(-u.^(θ/ϕ))
end

"""
    clayton_step(u::Vector{Real}, V0::Real, ϕ::Real, θ::Real; rng::AbstractRNG)
"""
function clayton_step(u::Vector{T}, V0::T, ϕ::T, θ::T; rng::AbstractRNG) where T <: Real
    u = -log.(u)./tiltedlevygen(V0, ϕ/θ; rng = rng)
    return exp.(V0.-V0.*(1 .+u).^(θ/ϕ))
end

"""
    frank_step(u::Vector{Real}, V0::Int, ϕ::Real, θ::Real, logseries_child::Vector{Real}; rng::AbstractRNG)
"""
function frank_step(u::Vector{T}, V0::Int, ϕ::Real, θ::T, logseries_child::Vector{T}; rng::AbstractRNG) where T <: Real
    u = -log.(u)./nestedfrankgen(ϕ, θ, V0, logseries_child; rng = rng)
    X = (1 .-(1 .-exp.(-u)*(1-exp(-ϕ))).^(θ/ϕ))./(1-exp(-θ))
    return X.^V0
end
"""
    amh_step(u::Vector{Real}, V0::Real, ϕ::Real, θ::Real; rng::AbstractRNG)
"""
function amh_step(u::Vector{T}, V0::T, ϕ::T, θ::T; rng::AbstractRNG) where T <: Real
    # TODO this need to be changed for BigFloat
    w = quantile(NegativeBinomial(V0, (1-ϕ)/(1-θ)), rand(rng, T))
    u = -log.(u)./(V0 + w)
    X = ((exp.(u) .-ϕ) .*(1-θ) .+θ*(1-ϕ)) ./(1-ϕ)
    return X.^(-V0)
end

"""
nestedcopulag(copula::String, ns::Vector{Vector{Int}}, ϕ::Vector{Real}, θ::Real, r::Matrix{Real})

Given [0,1]ᵗˣˡ ∋ r, returns t realizations of l-1 variate data from nested archimedean copula

```jldoctest
julia> Random.seed!(43)

julia> nestedcopulag("clayton", [[1,2],[3,4]], [2., 3.], 1.1, [0.1 0.2 0.3 0.4 0.5; 0.2 0.3 0.4 0.5 0.6])
julia> nestedcopulag("clayton", [[1,2],[3,4]], [2., 3.], 1.1, [0.1 0.2 0.3 0.4 0.5; 0.2 0.3 0.4 0.5 0.6])
2×4 Array{Float64,2}:
 0.153282  0.182421  0.374228  0.407663
 0.69035   0.740927  0.254842  0.279192
```
"""
function nestedcopulag(copula::String, ns::Vector{Vector{Int}}, ϕ::Vector{T}, θ::T,
                                                        r::Matrix{T}; rng::AbstractRNG) where T <: Real
    t = size(r,1)
    n = size(r,2)-1
    u = zeros(T, t, n)
    if copula == "clayton"
        for j in 1:t
            u[j,:] = nested_clayton_gen(ns, ϕ, θ, r[j,:]; rng = rng)
        end
    elseif copula == "amh"
        for j in 1:t
            u[j,:] = nested_amh_gen(ns, ϕ, θ, r[j,:]; rng = rng)
        end
    elseif copula == "frank"
        ws = [logseriescdf(1-exp(theta)) for theta in ϕ]
        w = logseriescdf(1-exp(-θ))
        for j in 1:t
            u[j,:] = nested_frank_gen(ns, ϕ, θ, r[j,:], w, ws; rng = rng)
        end
    elseif copula == "gumbel"
        v = r[:,end]
        p = invperm(sortperm(v))
        V0 = [levyel(θ, rand(rng), rand(rng)) for i in 1:t]
        V0 = sort(V0)[p]
        for j in 1:t
            rand_vec = r[j,1:end-1]
            x = copy(rand_vec)
            for i in 1:length(ϕ)
              x[ns[i]] = gumbel_step(rand_vec[ns[i]], ϕ[i], θ; rng = rng)
            end
            x = -log.(x)./V0[j]
            u[j,:] = exp.(-x.^(1/θ))
        end
    end
    return u
end


# tests

# data generation
srand(0)
Np = 50
θ0 = randn(Np)
θ0[ rand(Np) .> 0.1 ] = 0.

Nobs = 10000
x = randn(Np, Nobs)
u = vec(θ0' * x) + 0.1randn(Nobs)
y = ( 1 ./ (1. + exp(-u)) .> 0.5 ) + 0.

# convergence comparisons
model = m.riskmodel(m.LinearPred(Np), m.LogisticLoss())

θ1 = m.fbs(model, zeros(Np), x, y,
           reg=m.SqrL2Reg(10.0), λ=2e-3, maxsteps=100, maxtime=10)
θ2 = m.fista(model, zeros(Np), x, y,
             reg=m.SqrL2Reg(10.0), λ=2e-3, maxsteps=100, maxtime=10)
θ3 = m.fasta(model, zeros(Np), x, y,
             reg=m.SqrL2Reg(10.0), λ=1e-3, maxsteps=100, maxtime=10)

@test sumabs2(θ1-θ2) < 1e-2
@test sumabs2(θ2-θ3) < 1e-2

θ1 = m.fbs(model, zeros(Np), x, y,
           reg=m.L1Reg(10.0), λ=2e-3, maxsteps=1000, maxtime=10, cbinterval=1000)
θ2 = m.fista(model, zeros(Np), x, y,
             reg=m.L1Reg(10.0), λ=2e-3, maxsteps=1000, maxtime=10, cbinterval=1000)
θ3 = m.fasta(model, zeros(Np), x, y,
             reg=m.L1Reg(10.0), λ=1e-3, maxsteps=100, maxtime=10, cbinterval=100)

@test sumabs2(θ1-θ2) < 1e-2
@test sumabs2(θ2-θ3) < 1e-2

# fbs!(riskmodel(LinearPred(Np), MyLoss()), zeros(Np), x, y,
#      reg=SqrL2Reg(1.0), λ=1e-3, maxsteps=20, maxtime=10, cbinterval = 2)

# fista!(riskmodel(LinearPred(Np), MyLoss()), zeros(Np), x, y,
#      reg=SqrL2Reg(1.0), λ=1e-3, maxsteps=20, maxtime=10, cbinterval = 2)

# fasta!(riskmodel(LinearPred(Np), MyLoss()), zeros(Np), x, y,
#      reg=SqrL2Reg(1.0), λ=1e-3, maxsteps=20, maxtime=10, cbinterval = 1)
# fasta2!(riskmodel(LinearPred(Np), MyLoss()), zeros(Np), x, y,
#      reg=SqrL2Reg(1.0), λ=1e-3, maxsteps=20, maxtime=10, cbinterval = 1)

# fbs!(riskmodel(LinearPred(Np), MyLoss()), zeros(Np), x, y,
#      reg=L1Reg(10.0), λ=1e-3, maxsteps=20, maxtime=10, cbinterval = 2)

# fista!(riskmodel(LinearPred(Np), MyLoss()), zeros(Np), x, y,
#      reg=L1Reg(10.0), λ=1e-3, maxsteps=20, maxtime=10, cbinterval = 2)

# fasta!(riskmodel(LinearPred(Np), MyLoss()), zeros(Np), x, y,
#      reg=L1Reg(10.0), λ=1e-3, maxsteps=20, maxtime=10, cbinterval = 1)
# fasta2!(riskmodel(LinearPred(Np), MyLoss()), zeros(Np), x, y,
#      reg=L1Reg(10.0), λ=1e-3, maxsteps=20, maxtime=10, cbinterval = 1)

# fasta!(riskmodel(LinearPred(Np), MyLoss()), zeros(Np), x, y,
#      reg=L1Reg(10.0), λ=10., maxsteps=50, maxtime=10, cbinterval = 2)
# fasta2!(riskmodel(LinearPred(Np), MyLoss()), ones(Np), x, y,
#      reg=L1Reg(10.0), λ=10., maxsteps=50, maxtime=10, cbinterval = 2)
# fasta2!(riskmodel(LinearPred(Np), MyLoss()), ones(Np), x, y,
#      reg=L1Reg(1.0), λ=10., maxsteps=50, maxtime=10, cbinterval = 2)


using Test

# Category
##########

A, B = Ob(FreeCategory, :A), Ob(FreeCategory, :B)
f, g = Hom(:f, A, B), Hom(:g, B, A)

# Expression types
@test isa(A, FreeCategory.Ob) && isa(f, FreeCategory.Hom)
@test isa(A, ObExpr) && isa(f, HomExpr)
@test isa(A, CategoryExpr) && isa(f, CategoryExpr)

# Domains and codomains
@test dom(f) == A
@test codom(f) == B
@test dom(compose(f,g)) == A
@test codom(compose(f,g)) == A
@test_throws SyntaxDomainError compose(f,f)

# Associativity
@test compose(compose(f,g),f) == compose(f,compose(g,f))

# Extra syntax
@test compose(f,g,f) == compose(compose(f,g),f)
@test compose([f,g,f]) == compose(compose(f,g),f)
@test f⋅g == compose(f,g)
@test f⋅g⋅f⋅g == compose(f,g,f,g)

# String format
@test string(A) == "A"
@test string(f) == "f"
@test string(compose(f,g)) == "compose(f,g)"
@test string(compose(f,g,f)) == "compose(f,g,f)"
@test string(Ob(FreeCategory, nothing)) != ""

# S-expressions
@test sexpr(A) == ":A"
@test sexpr(f) == ":f"
@test sexpr(compose(f,g)) == "(compose :f :g)"
@test sexpr(compose(f,g,f)) == "(compose :f :g :f)"

# Infix notation (Unicode)
@test unicode(A) == "A"
@test unicode(f) == "f"
@test unicode(id(A)) == "id{A}"
@test unicode(compose(f,g)) == "f⋅g"

# Infix notation (LaTeX)
@test latex(A) == "A"
@test latex(f) == "f"
@test latex(id(A)) == "\\mathrm{id}_{A}"
@test latex(compose(f,g)) == "f \\cdot g"

@test latex(Ob(FreeCategory, "x")) == "x"
@test latex(Ob(FreeCategory, "sin")) == "\\mathrm{sin}"
@test latex(Ob(FreeCategory, "\\alpha")) == "\\alpha"

# 2-category
############

A, B, C, D = Ob(FreeCategory2, :A, :B, :C, :D)
f, g, F, G = [ Hom(sym, A, B) for sym in [:f,:g,:F,:G] ]
h, k, H, K = [ Hom(sym, B, C) for sym in [:h,:k,:H,:K] ]

# Domains and codomains
α, β = Hom2(:α, f, g), Hom2(:β, g, h)
@test dom(α) == f
@test codom(α) == g
@test dom(dom(α)) == A
@test codom(dom(α)) == B
@test dom(compose(α,β)) == f
@test codom(compose(α,β)) == h
@test_throws SyntaxDomainError compose2(α,β)

α, β = Hom2(:α, f, g), Hom2(:β, h, k)
@test dom(compose2(α,β)) == compose(f,h)
@test codom(compose2(α,β)) == compose(g,k)

# Infix notation (Unicode)
α, β = Hom2(:α, f, g), Hom2(:β, g, h)
@test unicode(compose(f,h)) == "f⋅h"
@test unicode(compose(α,β)) == "α⋅β"

α, β = Hom2(:α, f, g), Hom2(:β, h, k)
@test unicode(compose2(α,β)) == "α*β"

# Incompatible theories
@test_throws MethodError Hom(:f, Ob(FreeCategory, :A), Ob(FreeCategory2, :B))
f = Hom(:f, Ob(FreeCategory, :A), Ob(FreeCategory, :B))
g = Hom(:g, Ob(FreeCategory2, :B), Ob(FreeCategory2, :C))
@test_throws MethodError compose(f,g)


function axis_angle_to_quaternion(x)
    @assert length(x) == 3
    θ = norm(x)
    if θ > 0.0
        r = x ./ θ
        q = Quaternion(cos(0.5 * θ), sin(0.5 * θ) * r)
    else
        q = Quaternion(1.0, 0.0, 0.0, 0.0)
    end
    return q
end

function daxis_angle_to_quaterniondx(x)
    θ = norm(x)
    if θ > 0.0
        r = x ./ θ

        ∂qw∂x = -0.5 * sin(0.5 * θ) * transpose(x) ./ θ
        ∂qx∂x = 0.5 * cos(0.5 * θ) * transpose(x) ./ θ * r[1] + [sin(0.5 * θ) / θ 0.0 0.0] - sin(0.5 * θ) * x[1] / θ^2 * transpose(x) ./ θ
        ∂qy∂x = 0.5 * cos(0.5 * θ) * transpose(x) ./ θ * r[2] + [0.0 sin(0.5 * θ) / θ 0.0] - sin(0.5 * θ) * x[2] / θ^2 * transpose(x) ./ θ
        ∂qz∂x = 0.5 * cos(0.5 * θ) * transpose(x) ./ θ * r[3] + [0.0 0.0 sin(0.5 * θ) / θ] - sin(0.5 * θ) * x[3] / θ^2 * transpose(x) ./ θ

        return [
                ∂qw∂x;
                ∂qx∂x;
                ∂qy∂x;
                ∂qz∂x;
               ]
    else
        return [
                    0.0  0.0  0.0;
                    0.5  0.0  0.0;
                    0.0  0.5  0.0;
                    0.0  0.0  0.5;
                ]
    end
end


# first we define some sugars
⊗(A, B) = kron(A, B)

"""
    kronprod(A, B, ...)

kronnecker production of a sequence of matrixes.
"""
function kronprod(itr)
    state = start(itr)
    first, state = next(itr, state)
    second, state = next(itr, state)
    pd = kron(first, second)
    while !done(itr, state)
        val, state = next(itr, state)
        pd = kron(pd, val)
    end
    return pd
end

# we will use variational args to beautify the interface
kronprod(m::AbstractMatrix...) = kronprod(m)

# We will use a heap to store the expressions
# Heap is included in the DataStructures package
using DataStructures

# define the abstraction of all available kronnecker expression
# a kronnecker expression means the basic building blocks in an
# expression that describe some kronnecker products
abstract type KronExpr end

# An KronExpr


# get optimised hyperparameter
"""
        nlml_stheno(parm,x_train,y_train,K::Kernel)

calculates the negative log marginal likelihood for given parameters and kernel
"""
function nlml_stheno(parm,x_train,y_train,K::Kernel)
        @assert length(parm) == 2
        α,σ²= exp.(parm).+1e-6
        f = α*GP(K, GPC())
        return -logpdf(f(x_train, σ²), y_train)
end

"""
        GP_optimised(parm,constructs_train,activity_train,Mykernel::Kernel)

Optimise the hyperparameters of the GP with the given kernel.
Then calculated the posterior distribution given this optimised hyperparameters and data.
Returns Stheno object
"""

function gp_optimised(constructs_train,activity_train,Mykernel::Kernel; θ₀= zeros(2))
        results = Optim.optimize(parm -> nlml_stheno(parm,constructs_train,activity_train,Mykernel), θ₀, NelderMead())
        #get optimal hyperparameters
        α_opt,σ²_opt = exp.(Optim.minimizer(results)).+10^-6
        #set Gaussian procces in Stheno framework
        fGP = α_opt*GP(Mykernel, GPC())
        #calculate f_postirior based on training data and the prior f
        return  GP_optimised(fGP|(fGP(constructs_train,σ²_opt) ← activity_train),α_opt,σ²_opt)
end

struct GP_optimised{T}
        GP_model::T
        α_opt::Float64
        σ²_opt::Float64
end