#=
The following examples are taken from:
W.H. Enright and H. Hayashi, The evaluation of numerical software for delay
differential equations.
=#

# Problem A1
@doc raw"""
    prob_dde_DDETST_A1

Delay differential equation model of blood production, given by

```math
u'(t) = \frac{0.2 u(t - 14)}{1 + u(t - 14)^{10}} - 0.1 u(t)
```

for ``t \in [0, 500]`` and history function ``\phi(t) = 0.5`` for ``t \leq 0``.

# References

Mackey, M. C. and Glass, L. (1977). Oscillation and chaos in physiological control systems,
Science (197), pp. 287-289.
"""
prob_dde_DDETST_A1

function f_dde_DDETST_A1(u, h, p, t)
  z = h(p, t - 14)

  0.2 * z / (1 + z^10) - 0.1 * u
end

function h_dde_DDETST_A1(p, t)
  t ≤ 0 || error("history function is only implemented for t ≤ 0")

  0.5
end

const prob_dde_DDETST_A1 =
  DDEProblem(f_dde_DDETST_A1, h_dde_DDETST_A1, (0.0, 500.0); constant_lags = [14])

# Problem A2
@doc raw"""
    prob_dde_DDETST_A2

Delay differential equation model of chronic granulocytic leukemia, given by

```math
u_1'(t) = \frac{1.1}{1 + \sqrt{10} u_1(t - 20)^{5/4}} - \frac{10 u_1(t)}{1 + 40 u_2(t)},
```
```math
u_2'(t) = \frac{100 u_1(t)}{1 + 40 u_2(t)} - 2.43 u_2(t),
```

for ``t \in [0, 100]`` and history function

```math
\phi_1(t) = 1.05767027/3,
```
```math
\phi_2(t) = 1.030713491/3,
```

for ``t \leq 0``.

# References

Wheldon, T., Kirk, J. and Finlay, H. (1974). Cyclical granulopoiesis in chronic granulocytic
leukemia: A simulation study., Blood (43), pp. 379-387.
"""
prob_dde_DDETST_A2

function f_dde_DDETST_A2!(du, u, h, p, t)
  z = 10 * u[1] / (1 + 40 * u[2])

  du[1] = 1.1 / (1 + sqrt(10) * (h(p, t - 20; idxs = 1)^(5/4))) - z
  du[2] = 10 * z - 2.43 * u[2]

  nothing
end

function h_dde_DDETST_A2(p, t; idxs::Union{Nothing,Int} = nothing)
  t ≤ 0 || error("history function is only implemented for t ≤ 0")

  if idxs === nothing
    [1.05767027/3, 1.030713491/3]
  elseif idxs == 1
    1.05767027/3
  elseif idxs == 2
    1.030713491/3
  else
    error("delay differential equation consists of two components")
  end
end

const prob_dde_DDETST_A2 =
  DDEProblem(f_dde_DDETST_A2!, h_dde_DDETST_A2, (0.0, 100.0); constant_lags = [20])

# Problem B2
@doc raw"""
    prob_dde_DDETST_B1

Delay differential equation

```math
u'(t) = 1 - u(\exp(1 - 1/t))
```

for ``t \in [0.1, 10]`` with history function ``\phi(t) = \log t`` for ``t \in (0, 0.1]``.

# Solution

The analytical solution for ``t \in [0.1, 10]`` is

```math
u(t) = \log t.
```

# References

Neves, K. W. (1975). Automatic integration of functional differential equations: An
approach, ACM Trans. Math. Soft. (1), pp. 357-368.
"""
prob_dde_DDETST_B1

function f_dde_DDETST_B1(u, h, p, t)
  1 - h(p, exp(1 - 1/t))
end

function h_dde_DDETST_B1(p, t)
  0 < t ≤ 0.1 || error("history function is only implemented for t ∈ (0, 0.1]")

  log(t)
end

function fanalytic_dde_DDETST_B1(u₀, ::typeof(h_dde_DDETST_B1), p, t)
  0.1 ≤ t ≤ 10 && u₀ == log(0.1) ||
    error("analytical solution is only implemented for t ∈ [0.1, 10] and u(0) = log 0.1")

  log(t)
end

const prob_dde_DDETST_B1 =
  DDEProblem(DDEFunction(f_dde_DDETST_B1; analytic = fanalytic_dde_DDETST_B1),
             h_dde_DDETST_B1, (0.1, 10.0);
             dependent_lags = ((u, p, t) -> t - exp(1 - 1/t),))

# Problem B2
@doc raw"""
    prob_dde_DDETST_B2

Delay differential equation

```math
u'(t) = - 1 - u(t) + 2 [u(t / 2) < 0]
```

for ``t \in [0, 2 \log 66]`` with history function ``\phi(0) = 1``.

# Solution

The analytical solution for ``t \in [0, 2 \log 66]`` is

```math
u(t) = \begin{cases}
  2 \exp(-t) - 1 & \text{if } t \in [0, 2 \log 2], \\
  1 - 6 \exp(-t) & \text{if  } t \in (2 \log 2, 2 \log 6], \\
  66 \exp(-t) - 1 & \text{if } t \in (2 \log 6, 2 \log 66].
\end{cases}
```

# References

Neves, K. W. and Thompson, S. (1992). Solution of systems of functional differential
equations with state dependent delays, Technical Report TR-92-009, Computer Science,
Radford University.
"""
prob_dde_DDETST_B2

function f_dde_DDETST_B2(u, h, p, t)
  if h(p, t/2) < 0
    1 - u
  else
    -1 - u
  end
end

function h_dde_DDETST_B2(p, t)
  t == 0 || error("history function is only implemented for t = 0")

  1.0
end

function fanalytic_dde_DDETST_B2(u₀, ::typeof(h_dde_DDETST_B2), p, t)
  0 ≤ t ≤ 2 * log(66) && u₀ == 1 ||
    error("analytical solution is only implemented for t ∈ [0, 2 log(66)] and u(0) = 1")

  if t ≤ 2 * log(2)
    2 * exp(-t) - 1
  elseif t ≤ 2 * log(6)
    1 - 6 * exp(-t)
  else
    66 * exp(-t) - 1
  end
end

const prob_dde_DDETST_B2 =
  DDEProblem(DDEFunction(f_dde_DDETST_B2; analytic = fanalytic_dde_DDETST_B2),
             h_dde_DDETST_B2, (0.0, 2 * log(66));
             dependent_lags = ((u, p, t) -> t / 2,))

# Problem C1
@doc raw"""
    prob_dde_DDETST_C1

Delay differential equation

```math
u'(t) = - 2 u(t - 1 - |u(t)|) (1 - u(t)^2)
```

for ``t \in [0, 30]`` with history function ``\phi(t) = 0.5`` for ``t \leq 0``.

# References

Paul, C. A. H. (1994). A test set of functional differential equations, Technical Report
249, The Department of Mathematics, The University of Manchester, Manchester, England.
"""
prob_dde_DDETST_C1

f_dde_DDETST_C1(u, h, p, t) = - 2 * h(p, t - 1 - abs(u)) * (1 - u^2)

function h_dde_DDETST_C1(p, t)
  t ≤ 0 || error("history function is only implemented for t ≤ 0")

  0.5
end

const prob_dde_DDETST_C1 =
  DDEProblem(f_dde_DDETST_C1, h_dde_DDETST_C1, (0.0, 30.0);
             dependent_lags = ((u, p, t) -> 1 + abs(u),))

# Problem C2
@doc raw"""
    prob_dde_DDETST_C2

Delay differential equation

```math
u_1'(t) = - 2 u_1(t - u_2(t)),
```
```math
u_₂'(t) = \frac{|u_1(t - u_2(t))| - |u_1(t)|}{1 + |u_1(t - u_2(t))|},
```

for ``t \in [0, 40]`` with history function

```math
\phi_1(t) = 1,
```
```math
\phi_2(t) = 0.5,
```

for ``t \leq 0``.

# References

Paul, C. A. H. (1994). A test set of functional differential equations, Technical Report
249, The Department of Mathematics, The University of Manchester, Manchester, England.
"""
prob_dde_DDETST_C2

function f_dde_DDETST_C2!(du, u, h, p, t)
  z = h(p, t - u[2]; idxs = 1)
  absz = abs(z)

  du[1] = -2 * z
  du[2] = (absz - abs(u[1])) / (1 + absz)

  nothing
end

function h_dde_DDETST_C2(p, t; idxs::Union{Nothing,Int} = nothing)
  t ≤ 0 || error("history function is only implemented for t ≤ 0")

  if idxs === nothing
    [1.0, 0.5]
  elseif idxs == 1
    1.0
  elseif idxs == 2
    0.5
  else
    error("delay differential equation consists of two components")
  end
end

const prob_dde_DDETST_C2 =
  DDEProblem(f_dde_DDETST_C2!, h_dde_DDETST_C2, (0.0, 30.0);
             dependent_lags = ((u, p, t) -> u[2],))

# Problem C3
@doc raw"""
    prob_dde_DDETST_C3

Delay differential equation model of hematopoiesis, given by

```math
u_1'(t) = \hat{s}_0 u_2(t - T_1) - \gamma u_1(t) - Q,
```
```math
u_2'(t) = f(u_1(t)) - k u_2(t),
```
```math
u_3'(t) = 1 - \frac{Q \exp(\gamma u_3(t))}{\hat{s}_0 u_2(t - T_1 - u_3(t))},
```

for ``t \in [0, 300]`` with history function ``\phi_1(0) = 3.325``, ``\phi_3(0) = 120``, and

```math
\phi_2(t) = \begin{cases}
  10 & \text{if } t \in [- T_1, 0],\\
  9.5 & \text{if } t < - T_1,
\end{cases}
```

where ``f(y) = a / (1 + K y^r)``, ``\hat{s}_0 = 0.0031``, ``T_1 = 6``, ``\gamma = 0.001``,
``Q = 0.0275``, ``k = 2.8``, ``a = 6570``, ``K = 0.0382``, and ``r = 6.96``.

# References

Mahaffy, J. M., Belair, J. and Mackey, M. C. (1996). Hematopoietic model with moving
boundary condition and state dependent delay, Private communication.
"""
const prob_dde_DDETST_C3 =
  let s₀ = 0.0031, T₁ = 6, γ = 0.001, Q = 0.0275, k = 2.8, a = 6570, K = 0.0382, r = 6.96
    global function f_dde_DDETST_C3!(du, u, h, p, t)
      du[1] = s₀ * h(p, t - T₁; idxs = 2) - γ * u[1] - Q
      du[2] = a / (1 + K * u[1]^r) - k * u[2]
      du[3] = 1 - Q * exp(γ * u[3]) / (s₀ * h(p, t - T₁ - u[3]; idxs = 2))

      nothing
    end

    global function h_dde_DDETST_C3(p, t; idxs::Union{Nothing,Int} = nothing)
      t ≤ 0 || error("history function is only implemented for t ≤ 0")

      if idxs === nothing
        [3.325, t < - T₁ ? 9.5 : 10.0, 120.0]
      elseif idxs == 1
        3.325
      elseif idxs == 2
        t < - T₁ ? 9.5 : 10.0
      elseif idxs == 3
        120.0
      else
        error("delay differential equation consists of three components")
      end
    end

    DDEProblem(f_dde_DDETST_C3!, h_dde_DDETST_C3, (0.0, 300.0);
               constant_lags = [T₁], dependent_lags = ((u, p, t) -> T₁ + u[3],))
  end

# Problem C4
@doc raw"""
    prob_dde_DDETST_C4

Delay differential equation model of hematopoiesis, given by the same delay differential
equation as [`prob_dde_DDETST_C3`](@ref)

```math
u_1'(t) = \hat{s}_0 u_2(t - T_1) - \gamma u_1(t) - Q,
```
```math
u_2'(t) = f(u_1(t)) - k u_2(t),
```
```math
u_3'(t) = 1 - \frac{Q \exp(\gamma u_3(t))}{\hat{s}_0 u_2(t - T_1 - u_3(t))},
```

for ``t \in [0, 100]`` with history function
``\phi_1(0) = 3.5``, ``\phi_3(0) = 50``, and ``\phi_2(t) = 10`` for ``t \leq 0``, where
``f(y) = a / (1 + K y^r)``, ``\hat{s}_0 = 0.00372``, ``T_1 = 3``, ``\gamma = 0.1``,
``Q = 0.00178``, ``k = 6.65``, ``a = 15600``, ``K = 0.0382``, and ``r = 6.96``.

# References

Mahaffy, J. M., Belair, J. and Mackey, M. C. (1996). Hematopoietic model with moving
boundary condition and state dependent delay, Private communication.
"""
const prob_dde_DDETST_C4 =
  let s₀ = 0.00372, T₁ = 3, γ = 0.01, Q = 0.00178, k = 6.65, a = 15600, K = 0.0382, r = 6.96
    global function f_dde_DDETST_C4!(du, u, h, p, t)
      du[1] = s₀ * h(p, t - T₁; idxs = 2) - γ * u[1] - Q
      du[2] = a / (1 + K * u[1]^r) - k * u[2]
      du[3] = 1 - Q * exp(γ * u[3]) / (s₀ * h(p, t - T₁ - u[3]; idxs = 2))

      nothing
    end

    global function h_dde_DDETST_C4(p, t; idxs::Union{Nothing,Int} = nothing)
      t ≤ 0 || error("history function is only implemented for t ≤ 0")

      if idxs === nothing
        [3.5, 10.0, 50.0]
      elseif idxs == 1
        3.5
      elseif idxs == 2
        10.0
      elseif idxs == 3
        50.0
      else
        error("delay differential equation consists of three components")
      end
    end

    DDEProblem(f_dde_DDETST_C4!, [3.5, 10.0, 50.0], h_dde_DDETST_C4, (0.0, 100.0);
               constant_lags = [T₁], dependent_lags = ((u, p, t) -> T₁ + u[3],))
  end

# Problem D1
@doc raw"""
    prob_dde_DDETST_D1

Delay differential equation

```math
u_1'(t) = u_2(t),
```
```math
u_2'(t) = - u_2(\exp(1 - u_2(t))) u_2(t)^2 \exp(1 - u_2(t)),
```

for ``t \in [0.1, 5]`` with history function

```math
\phi_1(t) = \log t,
```
```math
\phi_2(t) = 1 / t,
```

for ``t \in (0, 0.1]``.

# Solution

The analytical solution for ``t \in [0.1, 5]`` is

```math
u_1(t) = \log t,
```
```math
u_2(t) = 1 / t.
```

# References

Neves, K. W. (1975). Automatic integration of functional differential equations: An
approach, ACM Trans. Math. Soft. (1), pp. 357-368.
"""
prob_dde_DDETST_D1

function f_dde_DDETST_D1!(du, u, h, p, t)
  s = exp(1 - u[2])

  du[1] = u[2]
  du[2] = -h(p, s; idxs = 2) * u[2]^2 * s

  nothing
end

function h_dde_DDETST_D1(p, t; idxs::Union{Nothing,Int} = nothing)
  0 < t ≤ 0.1 || error("history function is only implemented for 0 < t ≤ 0.1")

  if idxs === nothing
    [log(t), 1 / t]
  elseif idxs == 1
    log(t)
  elseif idxs == 2
    1 / t
  else
    error("delay differential equation consists of two components")
  end
end

function fanalytic_dde_DDETST_D1(u₀, ::typeof(h_dde_DDETST_D1), p, t)
  0.1 ≤ t ≤ 5 && u₀ == [log(0.1), 10] ||
    error("analytical solution is only implemented for t ∈ [0.1, 5] and u(0.1) = [log 0.1, 10]")

  [log(t), 1 / t]
end

const prob_dde_DDETST_D1 =
  DDEProblem(DDEFunction(f_dde_DDETST_D1!; analytic = fanalytic_dde_DDETST_D1),
             h_dde_DDETST_D1, (0.1, 5.0);
             dependent_lags = ((u, p, t) -> t - exp(1 - u[2]),))

# Problem D2
@doc raw"""
    prob_dde_DDETST_D2

Delay differential equation model of antigen antibody dynamics with fading memory, given by

```math
u_1'(t) = - r_1 u_1(t) u_2(t) + r_2 u_3(t),
```
```math
u_2'(t) = - r_1 u_1(t) u_2(t) + \alpha r_1 u_1(t - u_4(t)) u_2(t - u_4(t)),
```
```math
u_3'(t) = r_1 u_1(t) u_2(t) - r_2 u_3(t),
```
```math
u_4'(t) = 1 + \frac{3 \delta - u_1(t) u_2(t) - u_3(t)}{u_1(t - u_4(t)) u_2(t - u_4(t)) + u_3(t - u_4(t))} \exp(\delta u_4(t)),
```

for ``t \in [0, 40]`` with history function

```math
\phi_1(t) = 5,
```
```math
\phi_2(t) = 0.1,
```
```math
\phi_3(t) = 0,
```
```math
\phi_4(t) = 0,
```

for ``t \leq 0``, where ``r_1 = 0.02``, ``r_2 = 0.005``, ``\alpha = 3``, and ``\delta = 0.01``.

# References

Gatica, J. and Waltman, P. (1982). A threshold model of antigen antibody dynamics with
fading memory, in Lakshmikantham (ed.), Nonlinear phenomena in mathematical science,
Academic Press, New York, pp. 425-439.
"""
const prob_dde_DDETST_D2 =
  let r₁ = 0.02, r₂ = 0.005, α = 3, δ = 0.01
    global function f_dde_DDETST_D2!(du, u, h, p, t)
      u₁u₂ = u[1] * u[2]
      r₁u₁u₂ = r₁ * u₁u₂
      r₂u₃ = r₂ * u[3]

      v = h(p, t - u[4])
      v₁v₂ = v[1] * v[2]

      du[1] = -r₁u₁u₂ + r₂u₃
      du[2] = -r₁u₁u₂ + α * r₁ * v₁v₂
      du[3] = -du[1]
      du[4] = 1 + (3 * δ - u₁u₂ - u[3]) * exp(δ * u[4]) / (v₁v₂ + v[3])

      nothing
    end

    global function h_dde_DDETST_D2(p, t)
      t ≤ 0 || error("history function is only implemented for t ≤ 0")

      [5.0, 0.1, 0.0, 0.0]
    end

    DDEProblem(f_dde_DDETST_D2!, h_dde_DDETST_D2, (0.0, 40.0);
               dependent_lags = ((u, p, t) -> u[4],))
  end

# Problem E1
@doc raw"""
    prob_dde_DDETST_E1

Delay differential equation model of a food-limited population, given by

```math
u(t) = r u(t) (1 - u(t - 1) - c u'(t - 1))
```

for ``t \in [0, 40]`` with history function ``\phi(t) = 2 + t`` for ``t \leq 0``, where
``r = \pi / \sqrt{3} + 1/20`` and ``c = \sqrt{3} / (2 \pi) - 1 / 25``.

# References

Kuang, Y. and Feldstein, A. (1991). Boundedness of solutions of a nonlinear nonautonomous
neutral delay equation, J. Math. Anal. Appl. (156), pp. 293-304.
"""
const prob_dde_DDETST_E1 =
  let r = π / sqrt(3) + 1 / 20, c = sqrt(3) / (2 * π) - 1 / 25
    global f_dde_DDETST_E1(u, h, p, t) = r * u * (1 - h(p, t - 1) - c * h(p, t - 1, Val{1}))

    global function h_dde_DDETST_E1(p, t)
      t ≤ 0 || error("history function is only implemented for t ≤ 0")

      2 + t
    end

    global function h_dde_DDETST_E1(p, t, ::Type{Val{1}})
      t ≤ 0 || error("history function is only implemented for t ≤ 0")

      1.0
    end

    DDEProblem(f_dde_DDETST_E1, h_dde_DDETST_E1, (0.0, 40.0);
               constant_lags = [1], neutral = true)
  end

# Problem E2
@doc raw"""
    prob_dde_DDETST_E2

Delay differential equation model of a logistic Gauss-type predator-prey system, given by

```math
u_1'(t) = u_1(t) (1 - u_1(t - \tau) - \rho u_1'(t - \tau)) - \frac{u_2(t) u_1(t)^2}{u_1(t)^2 + 1},
```
```math
u_2'(t) = u_2(t) \left(\frac{u_1(t)^2}{u_1(t)^2 + 1} - \alpha\right),
```

for ``t \in [0, 2]`` with history function

```math
\phi_1(t) = 0.33 - t / 10,
```
```math
\phi_2(t) = 2.22 + t / 10,
```

for ``t \leq 0``, where ``\alpha = 0.1``, ``\rho = 2.9``, and ``\tau = 0.42``.

# References

Kuang, Y. (1991). On neutral delay logistics Gauss-type predator-prey systems, Dyn. Stab.
Systems (6), pp. 173-189.
"""
const prob_dde_DDETST_E2 =
  let α = 0.1, ρ = 2.9, τ = 0.42
    global function f_dde_DDETST_E2!(du, u, h, p, t)
      v = u[1]^2
      z = u[2] * v / (v + 1)

      du[1] = u[1] * (1 - h(p, t - τ; idxs = 1) - ρ * h(p, t - 1, Val{1}; idxs = 1)) - z
      du[2] = z - α * u[2]

      nothing
    end

    global function h_dde_DDETST_E2(p, t; idxs::Union{Nothing,Int} = nothing)
      t ≤ 0 || error("history function is only implemented for t ≤ 0")

      if idxs === nothing
        [0.33 - 0.1 * t, 2.22 + 0.1 * t]
      elseif idxs == 1
        0.33 - 0.1 * t
      elseif idxs == 2
        2.22 + 0.1 * t
      else
        error("delay differential equation consists of two components")
      end
    end

    global function h_dde_DDETST_E2(p, t, ::Type{Val{1}};
                                    idxs::Union{Nothing,Int} = nothing)
      t ≤ 0 || error("history function is only implemented for t ≤ 0")

      if idxs === nothing
        [-0.1, -0.1]
      elseif idxs == 1 || idxs == 2
        -0.1
      else
        error("delay differential equation consists of two components")
      end
    end

    DDEProblem(f_dde_DDETST_E2!, h_dde_DDETST_E2, (0.0, 2.0);
               constant_lags = [τ], neutral = true)
  end

# Problem F1
@doc raw"""
    prob_dde_DDETST_F1

Delay differential equation

```math
u'(t) = 2 \cos(2t) u(t / 2)^{2 \cos t} + \log(u'(t / 2)) - \log(2 \cos t) - \sin t
```

for ``t \in [0, 1]`` with history function ``\phi(0) = 1`` and ``\phi'(0) = 2``.

# Solution

The analytical solution for ``t \in [0, 1]`` is

```math
u(t) = \exp(\sin(2t)).
```

# References

Jackiewicz, Z. (1981). One step methods for the numerical solution of Volterra functional
differential equations of neutral type, Applicable Anal. (12), pp. 1-11.
"""
prob_dde_DDETST_F1

function f_dde_DDETST_F1(u, h, p, t)
  s, c = sincos(t)
  c2 = 2 * c

  to2 = t / 2
  h0 = h(p, to2)
  h1 = h(p, to2, Val{1})

  2 * cos(2 * t) * h0^c2 + log(h1) - log(c2) - s
end

function h_dde_DDETST_F1(p, t)
  iszero(t) || error("history function is only implemented for t = 0")

  1.0
end

function h_dde_DDETST_F1(p, t, ::Type{Val{1}})
  iszero(t) || error("history function is only implemented for t = 0")

  2.0
end

function fanalytic_dde_DDETST_F1(u₀, ::typeof(h_dde_DDETST_F1), p, t)
  0 ≤ t ≤ 1 && u₀ == 1 ||
    error("analytical solution is only implemented for t ∈ [0, 1] and u(0) = 1")

  exp(sin(2 * t))
end

const prob_dde_DDETST_F1 =
  DDEProblem(DDEFunction(f_dde_DDETST_F1; analytic = fanalytic_dde_DDETST_F1),
             h_dde_DDETST_F1, (0.0, 0.1);
             dependent_lags = ((u, p, t) -> t / 2,), neutral = true)

# Problem F2
@doc raw"""
    prob_dde_DDETST_F2

Delay differential equation

```math
u'(t) = u'(2t - 0.5)
```

for ``t \in [0.25, 0.499]`` with history function ``\phi(t) = \exp(-t^2)`` and
``\phi'(t) = -2t \exp(-t^2)`` for ``t \leq 0.25``.

# Solution

The analytical solution for ``t \in [0.25, 0.499]`` is

```math
u(t) = u_i(t) = \exp(-4^i t^2 + B_i t + C_i) / 2^i + K_i
```

if ``t \in [x_i, x_{i + 1}]``, where

```math
x_i = (1 - 2^{-i}) / 2,
```
```math
B_i = 2 (4^{i-1} + B_{i-1}),
```
```math
C_i = - 4^{i-2} - B_{i-1} / 2 + C_{i-1},
```
```math
K_i = - \exp(-4^i x_i^2 + B_i x_i + C_i) / 2^i + u_{i-1}(x_i),
```

and ``B_0 = C_0 = K_0 = 0``.

# References

Neves, K. W. and Thompson, S. (1992). Solution of systems of functional differential
equations with state dependent delays, Technical Report TR-92-009, Computer Science,
Radford University.
"""
prob_dde_DDETST_F2

f_dde_DDETST_F2(u, h, p, t) = h(p, 2 * t - 0.5, Val{1})

function h_dde_DDETST_F2(p, t)
  t ≤ 0.25 || error("history function is only implemented for t ≤ 0.25")

  exp(-t^2)
end

function h_dde_DDETST_F2(p, t, ::Type{Val{1}})
  t ≤ 0.25 || error("history function is only implemented for t ≤ 0.25")

  -2 * t * exp(-t^2)
end

function fanalytic_dde_DDETST_F2(u₀, ::typeof(h_dde_DDETST_F2), p, t)
  0.25 ≤ t ≤ 0.499 && u₀ == exp(-0.25^2) ||
    error("analytical solution is only implemented for t ∈ [0.25, 0.499] and u(0.25) = exp(-0.0625)")

  i = 0
  x = 0.0
  B = C = K = 0.0

  while true
    i += 1
    y = (1 - 2^(-i)) / 2

    if y ≥ t
      return exp(- 4^(i - 1) * t^2 + B * t + C) / 2^(i - 1) + K
    else
      K += exp(- 4^(i - 1) * x^2 + B * x + C) / 2^(i-1)
      C += - 4^(i - 2) - B / 2
      B = 2 * (4^(i - 1) + B)
      K -= exp(- 4^i * x^2 + B * x + C) / 2^i
      x = y
    end
  end
end

const prob_dde_DDETST_F2 =
  DDEProblem(DDEFunction(f_dde_DDETST_F2; analytic = fanalytic_dde_DDETST_F2),
             h_dde_DDETST_F2, (0.25, 0.499);
             dependent_lags = ((u, p, t) -> 1/2 - t,), neutral = true)

# Problem F3
@doc raw"""
    prob_dde_DDETST_F3

Delay differential equation

```math
u'(t) = \exp(-u(t)) + L_3 \left[\sin(u'(\alpha(t))) - \sin\left(\frac{1}{3 + \alpha(t)}\right)\right]
```

for ``t \in [0, 10]`` with history function ``\phi(0) = \log 3`` and ``\phi'(0) = 1 / 3``,
where ``\alpha(t) = 0.5 t (1 - \cos(2 \pi t))`` and ``L_3 = 0.2``.

# Solution

The analytical solution for ``t \in [0, 10]`` is

```math
u(t) = \log(t + 3).
```
"""
prob_dde_DDETST_F3

let L₃ = 0.2
  global function f_dde_DDETST_F3(u, h, p, t)
    α = 0.5 * t * (1 - cos(2 * π * t))

    exp(-u) + L₃ * (sin(h(p, α, Val{1})) - sin(inv(3 + α)))
  end
end

function h_dde_DDETST_F345(p, t)
  iszero(t) || error("history function is only implemented for t = 0")

  log(3)
end

function h_dde_DDETST_F345(p, t, ::Type{Val{1}})
  iszero(t) || error("history function is only implemented for t = 0")

  inv(3)
end

function fanalytic_dde_DDETST_F345(u₀, ::typeof(h_dde_DDETST_F345), p, t)
  0 ≤ t ≤ 10 && u₀ == log(3) ||
    error("analytical solution is only implemented for t ∈ [0, 10] and u(0) = log 3")

  log(t + 3)
end

const prob_dde_DDETST_F3 =
  DDEProblem(DDEFunction(f_dde_DDETST_F3; analytic = fanalytic_dde_DDETST_F345),
             h_dde_DDETST_F345, (0.0, 10.0);
             dependent_lags = ((u, p, t) -> 0.5 * t * (1 + cos(2 * π * t)),),
             neutral = true)

# Problem F4
"""
    prob_dde_DDETST_F4

Same delay differential equation as [`prob_dde_DDETST_F3`](@ref) with ``L_3 = 0.4``.
"""
prob_dde_DDETST_F4

let L₃ = 0.4
  global function f_dde_DDETST_F4(u, h, p, t)
    α = 0.5 * t * (1 - cos(2 * π * t))

    exp(-u) + L₃ * (sin(h(p, α, Val{1})) - sin(inv(3 + α)))
  end
end

const prob_dde_DDETST_F4 =
  remake(prob_dde_DDETST_F3;
         f = DDEFunction(f_dde_DDETST_F4; analytic = fanalytic_dde_DDETST_F345))

# Problem F5
"""
    prob_dde_DDETST_F5

Same delay differential equation as [`prob_dde_DDETST_F3`](@ref) with ``L_3 = 0.6``.
"""
prob_dde_DDETST_F5

let L₃ = 0.6
  global function f_dde_DDETST_F5(u, h, p, t)
    α = 0.5 * t * (1 - cos(2 * π * t))

    exp(-u) + L₃ * (sin(h(p, α, Val{1})) - sin(inv(3 + α)))
  end
end

const prob_dde_DDETST_F5 =
  remake(prob_dde_DDETST_F3;
         f = DDEFunction(f_dde_DDETST_F5; analytic = fanalytic_dde_DDETST_F345))

# Problem G1
@doc raw"""
    prob_dde_DDETST_G1

Delay differential equation

```math
u'(t) = - u'(t - u(t)^2 / 4)
```

for ``t \in [0, 1]`` with history function ``\phi(t) = 1 - t`` for ``t \leq 0`` and
``\phi'(t) = -1`` for ``t < 0``.

# Solution

The analytical solution for ``t \in [0, 1]`` is

```math
u(t) = t + 1.
```

# References

El'sgol'ts, L. E. and Norkin, S. B. (1973). Introduction to the Theory and Application of
Differential Equations with Deviating Arguments, Academic Press, New York, p. 44.
"""
prob_dde_DDETST_G1

f_dde_DDETST_G1(u, h, p, t) = - h(p, t - u^2 / 4, Val{1})

function h_dde_DDETST_G1(p, t)
  t ≤ zero(t) || error("history function is only implemented for t ≤ 0")

  1 - t
end

function h_dde_DDETST_G1(p, t, ::Type{Val{1}})
  t < zero(t) || error("history function is only implemented for t < 0")

  - 1.0
end

function fanalytic_dde_DDETST_G1(u₀, ::typeof(h_dde_DDETST_G1), p, t)
  0 ≤ t ≤ 1 && u₀ == 1 ||
    error("analytical solution is only implemented for t ∈ [0, 1] and u(0) = 1")

  t + 1
end

const prob_dde_DDETST_G1 =
  DDEProblem(DDEFunction(f_dde_DDETST_G1; analytic = fanalytic_dde_DDETST_G1),
             h_dde_DDETST_G1, (0.0, 1.0);
             dependent_lags = ((u, p, t) -> u^2 / 4,), neutral = true)

# Problem G2
@doc raw"""
    prob_dde_DDETST_G2

Delay differential equation

```math
u'(t) = - u'(u(t) - 2)
```

for ``t \in [0, 1]`` with history function ``\phi(t) = 1 - t`` for ``t \leq 0`` and
``\phi'(t) = -1`` for ``t < 0``.

# Solution

The analytical solution for ``t \in [0, 1]`` is

```math
u(t) = t + 1.
```

El'sgol'ts, L. E. and Norkin, S. B. (1973). Introduction to the Theory and Application of
Differential Equations with Deviating Arguments, Academic Press, New York, pp. 44-45.
"""
prob_dde_DDETST_G2

f_dde_DDETST_G2(u, h, p, t) = - h(p, u - 2, Val{1})

function h_dde_DDETST_G2(p, t)
  t ≤ zero(t) || error("history function is only implemented for t ≤ 0")

  1 - t
end

function h_dde_DDETST_G2(p, t, ::Type{Val{1}})
  t < zero(t) || error("history function is only implemented for t < 0")

  - 1.0
end

function fanalytic_dde_DDETST_G2(u₀, ::typeof(h_dde_DDETST_G2), p, t)
  0 ≤ t ≤ 1 && u₀ == 1.0 ||
    error("analytical solution is only implemented for t ∈ [0, 1] and u(0) = 1")

  t + 1
end

const prob_dde_DDETST_G2 =
  DDEProblem(DDEFunction(f_dde_DDETST_G2; analytic = fanalytic_dde_DDETST_G2),
             h_dde_DDETST_G2, (0.0, 1.0);
             dependent_lags = ((u, p, t) -> t + 2 -  u,), neutral = true)

# Problem H1
@doc raw"""
    prob_dde_DDETST_H1

Delay differential equation

```math
u'(t) = - \frac{4 t u(t)^2}{4 + \log(\cos(2t))^2} + \tan(2t) + 0.5 \arctan\left(u'\left(\frac{t u(t)^2}{1 + u(t)^2}\right)\right)
```

for ``t \in [0, 0.225 \pi]`` with history function ``\phi(0) = 0`` and ``\phi'(0) = 0``.

# Solution

The analytical solution for ``t \in [0, 0.225 \pi]`` is

```math
u(t) = - \log(\cos(2t)) / 2.
```

# References

Castleton, R. N. and Grimm, L. J. (1973). A first order method for differential equations of
neutral type, Math. Comput. (27), pp. 571-577.
"""
prob_dde_DDETST_H1

function f_dde_DDETST_H1(u, h, p, t)
  v = u^2
  z = t * v

  - 4 * z / (4 + log(cos(2 * t))^2) + tan(2 * t) + 0.5 * atan(h(p, z / (1 + v), Val{1}))
end

function h_dde_DDETST_H1(p, t)
  iszero(t) || error("history function is only implemented for t = 0")

  0.0
end

function h_dde_DDETST_H1(p, t, ::Type{Val{1}})
  iszero(t) || error("history function is only implemented for t = 0")

  0.0
end

function fanalytic_dde_DDETST_H1(u₀, ::typeof(h_dde_DDETST_H1), p, t)
  0 ≤ t ≤ 0.225 * π && iszero(u₀) ||
    error("analytical solution is only implemented for t ∈ [0, 0.225π] and u(0) = 0")

  - log(cos(2 * t)) / 2
end

const prob_dde_DDETST_H1 =
  DDEProblem(DDEFunction(f_dde_DDETST_H1; analytic = fanalytic_dde_DDETST_H1),
             h_dde_DDETST_H1, (0.0, 0.225 * π);
             dependent_lags = ((u, p, t) -> t / (1 + u^2),), neutral = true)

# Problem H2
@doc raw"""
    prob_dde_DDETST_H2

Delay differential equation

```math
u'(t) = \cos(t) (1 + u(t u(t)^2)) + L_3 u(t) u'(t u(t)^2) + (1 - L_3) \sin(t) \cos(t \sin(t)^2) - \sin(t + t \sin(t)^2)
```

for ``t \in [0, \pi]`` with history function ``\phi(0) = 0`` and ``\phi'(0) = 1``, where
``L_3 = 0.1``.

# Solution

The analytical solution for ``t \in [0, \pi]`` is

```math
u(t) = \sin(t).
```

# References

Hayashi, H. (1996). Numerical solution of retarded and neutral delay differential equations
using continuous Runge-Kutta methods, PhD thesis, Department of Computer Science, University
of Toronto, Toronto, Canada.
"""
prob_dde_DDETST_H2

let L₃ = 0.1
  global function f_dde_DDETST_H2(u, h, p, t)
    s, c = sincos(t)
    a = t * s^2

    z = t * u^2

    c * (1 + h(p, z)) + L₃ * u * h(p, z, Val{1}) + (1 - L₃) * s * cos(a) - sin(t + a)
  end
end

function h_dde_DDETST_H234(p, t)
  iszero(t) || error("history function is only implemented for t = 0")

  0.0
end

function h_dde_DDETST_H234(p, t, ::Type{Val{1}})
  iszero(t) || error("history function is only implemented for t = 0")

  1.0
end

function fanalytic_dde_DDETST_H234(u₀, ::typeof(h_dde_DDETST_H234), p, t)
  0 ≤ t ≤ π && iszero(u₀) ||
    error("analytical solution is only implemented for t ∈ [0, π] and u(0) = 0")

  sin(t)
end

const prob_dde_DDETST_H2 =
  DDEProblem(DDEFunction(f_dde_DDETST_H2; analytic = fanalytic_dde_DDETST_H234),
             h_dde_DDETST_H234, (0.0, π);
             dependent_lags = ((u, p, t) -> t * (1 - u^2),), neutral = true)

# Problem H3
"""
    prob_dde_DDETST_H3

Same delay differential equation as [`prob_dde_DDETST_H2`](@ref) with ``L_3 = 0.3``.

# References

Hayashi, H. (1996). Numerical solution of retarded and neutral delay differential equations
using continuous Runge-Kutta methods, PhD thesis, Department of Computer Science, University
of Toronto, Toronto, Canada.
"""
prob_dde_DDETST_H3

let L₃ = 0.3
  global function f_dde_DDETST_H3(u, h, p, t)
    s, c = sincos(t)
    a = t * s^2

    z = t * u^2

    c * (1 + h(p, z)) + L₃ * u * h(p, z, Val{1}) + (1 - L₃) * s * cos(a) - sin(t + a)
  end
end

const prob_dde_DDETST_H3 =
  remake(prob_dde_DDETST_H2;
         f = DDEFunction(f_dde_DDETST_H3; analytic = fanalytic_dde_DDETST_H234))

# Problem H4
"""
    prob_dde_DDETST_H4

Same delay differential equation as [`prob_dde_DDETST_H2`](@ref) with ``L_3 = 0.5``.

# References

Hayashi, H. (1996). Numerical solution of retarded and neutral delay differential equations
using continuous Runge-Kutta methods, PhD thesis, Department of Computer Science, University
of Toronto, Toronto, Canada.
"""
prob_dde_DDETST_H4

let L₃ = 0.5
  global function f_dde_DDETST_H4(u, h, p, t)
    s, c = sincos(t)
    a = t * s^2

    z = t * u^2

    c * (1 + h(p, z)) + L₃ * u * h(p, z, Val{1}) + (1 - L₃) * s * cos(a) - sin(t + a)
  end
end

const prob_dde_DDETST_H4 =
  remake(prob_dde_DDETST_H2;
         f = DDEFunction(f_dde_DDETST_H4; analytic = fanalytic_dde_DDETST_H234))


#=
This example demonstrates the use of callback functions working with a neighborhood struct.
It is a 2D advection-reaction equation.
It is solved in two ways:
 - u1 using a symbolic upwinding operator for the flux.
 - u2 using a callback function for the flux that uses the neighborhood of a face to do the same upwinding.

The execution time is also compared, but doesn't account for compilation/first run time.
Also, the number of flops using the symbolic version is much higher because it is actually doing
    1/2*dot(a,n)*(side1+side2) + (1-alpha)/2*abs(dot(a,n))*(side1-side2)
with a parameter alpha rather than
    dot(a,n) > 0 ? dot(a,n)*side1 : dot(a,n)*side2
=#

### If the Finch package has already been added, use this line #########
using Finch # Note: to add the package, first do: ]add "https://github.com/paralab/Finch.git"

### If not, use these four lines (working from the examples directory) ###
# if !@isdefined(Finch)
#     include("../Finch.jl");
#     using .Finch
# end
##########################################################################

init_finch("FVneighborhood");

useLog("FVneighborhoodlog", level=3)

# Configuration setup
domain(2)
solverType(FV)

timeStepper(RK4)
n = 20
mesh(QUADMESH, elsperdim=n, bids=4)
# finiteVolumeOrder(2);

# Variables and BCs
u1 = variable("u1", location=CELL) # will use symbolic upwind operator
u2 = variable("u2", location=CELL) # will use callback with neighborhood

# This boundary provides a pulsing input through a small window on one side
boundary(u1, 1, FLUX, "(abs(y-0.2) < 0.11 && sin(6*pi*t)>0) ? 1 : 0") # x=0
boundary(u1, 2, NO_BC) # x=1
boundary(u1, 3, NO_BC) # y=0
boundary(u1, 4, NO_BC) # y=1
boundary(u2, 1, FLUX, "(abs(y-0.2) < 0.11 && sin(6*pi*t)>0) ? 1 : 0") # x=0
boundary(u2, 2, NO_BC) # x=1
boundary(u2, 3, NO_BC) # y=0
boundary(u2, 4, NO_BC) # y=1

# Time interval and initial condition
T = 1;
timeInterval(T)
initial(u1, "0")
initial(u2, "0")

coefficient("a", ["cos(pi*x/2)","sin(pi*x/2)"], type=VECTOR) # advection velocity
coefficient("s", "sin(pi*x)^4 * sin(pi*y)^4") # source

# Here is the callback function for the flux that simply does first order upwinding.
@callbackFunction(
    function flux_fun(neighborhood, a, normal)
        # neighborhood is a struct containing:
        # - neighborhood.cells -> an array of two arrays of integers like [[L1, L2,...], [R1, R2,...]]
        #                         where L1 is the nearest neighbor on the left, L2 is next-nearest, etc.
        # - neighborhood.centers -> similar, but holding cell center coordinates like [ [L1x L2x ...  , [R1x R2x ...
        #                                        *note these are 2d arrays->             L1y L2y ...]    R1y R2y ...] ]
        # - neighborhood.values -> holds the values of the requested variable(s) like [[u_L1, u_L2,...], [u_R1, u_R2,...]]
        #                          *vector valued variables or multiple variables will have columns of values

        a_dot_n = a[1]*normal[1] + a[2]*normal[2]; # a and normal are 2D vectors
        if a_dot_n > 0
            result = neighborhood.values[1][1] * a_dot_n; # use the first value on the left: values[1][1]
        else
            result = neighborhood.values[2][1] * a_dot_n; # use the first value on the right: values[2][1]
        end

        return result;
    end
)

# The PDE expression input
# The flux and source terms of the conservation equation
# F and S in the following equation:
# Dt(int(u dx)) = int(S dx) - int(F.n ds)
flux(u1, "upwind(a, u1)")
source(u1, "0.1 * s")

flux(u2, "flux_fun(neighborhood(u2), a, normal())")
source(u2, "0.1 * s")

exportCode("fvneighborhoodcode") # uncomment to export generated code to a file
#importCode("fvneighborhoodcode") # uncomment to import code from a file

t1 = @elapsed(solve(u1))
t2 = @elapsed(solve(u2))

println("time for u1: "*string(t1))
println("time for u2: "*string(t2))

finalize_finch()

# uncomment to output to file
# output_values([u1,u2], "fvad2d", format="vtk");

##### Uncomment below to plot

# xy = Finch.fv_info.cellCenters

# using Plots
# pyplot();
# p1 = plot(xy[1,:], xy[2,:], u1.values[:], st=:surface)
# p2 = plot(xy[1,:], xy[2,:], u2.values[:], st=:surface)
# display(plot(p1, p2, layout=2))


# ---
# title: 39. Combination Sum
# id: problem39
# author: zhwang
# date: 2022-02-17
# difficulty: Medium
# categories: Array, Backtracking
# link: <https://leetcode.com/problems/combination-sum/description/>
# hidden: true
# ---
#
# Given an array of **distinct** integers `candidates` and a target integer
# `target`, return _a list of all **unique combinations** of _`candidates`
# _where the chosen numbers sum to_`target` _._ You may return the combinations
# in **any order**.
#
# The **same** number may be chosen from `candidates` an **unlimited number of
# times**. Two combinations are unique if the frequency of at least one of the
# chosen numbers is different.
#
# It is **guaranteed** that the number of unique combinations that sum up to
# `target` is less than `150` combinations for the given input.
#
#
#
# **Example 1:**
#
#
#
#     Input: candidates = [2,3,6,7], target = 7
#     Output: [[2,2,3],[7]]
#     Explanation:
#     2 and 3 are candidates, and 2 + 2 + 3 = 7. Note that 2 can be used multiple times.
#     7 is a candidate, and 7 = 7.
#     These are the only two combinations.
#
#
# **Example 2:**
#
#
#
#     Input: candidates = [2,3,5], target = 8
#     Output: [[2,2,2,2],[2,3,3],[3,5]]
#
#
# **Example 3:**
#
#
#
#     Input: candidates = [2], target = 1
#     Output: []
#
#
# **Example 4:**
#
#
#
#     Input: candidates = [1], target = 1
#     Output: [[1]]
#
#
# **Example 5:**
#
#
#
#     Input: candidates = [1], target = 2
#     Output: [[1,1]]
#
#
#
#
# **Constraints:**
#
#   * `1 <= candidates.length <= 30`
#   * `1 <= candidates[i] <= 200`
#   * All elements of `candidates` are **distinct**.
#   * `1 <= target <= 500`
#
#
## @lc code=start
using LeetCode

function combination_sum_39(
    nums::Union{Vector{Int},SubArray}, target::Int
)::Vector{Vector{Int}}
    target == 0 && return [Int[]]
    isempty(nums) && return Vector{Int}[]
    res, num = Vector{Int}[], nums[end]
    for i in 0:(target ÷ num)
        for vec in combination_sum_39(@view(nums[1:(end - 1)]), target - i * num)
            append!(res, [append!(vec, fill(num, i))])
        end
    end
    return res
end
## @lc code=end


# ---
# title: 1029. Two City Scheduling
# id: problem1029
# author: Tian Jun
# date: 2020-10-31
# difficulty: Medium
# categories: Greedy
# link: <https://leetcode.com/problems/two-city-scheduling/description/>
# hidden: true
# ---
#
# A company is planning to interview `2n` people. Given the array `costs` where
# `costs[i] = [aCosti, bCosti]`, the cost of flying the `ith` person to city `a`
# is `aCosti`, and the cost of flying the `ith` person to city `b` is `bCosti`.
#
# Return _the minimum cost to fly every person to a city_ such that exactly `n`
# people arrive in each city.
#
#
#
# **Example 1:**
#
#
#
#     Input: costs = [[10,20],[30,200],[400,50],[30,20]]
#     Output: 110
#     Explanation:
#     The first person goes to city A for a cost of 10.
#     The second person goes to city A for a cost of 30.
#     The third person goes to city B for a cost of 50.
#     The fourth person goes to city B for a cost of 20.
#
#     The total minimum cost is 10 + 30 + 50 + 20 = 110 to have half the people interviewing in each city.
#
#
# **Example 2:**
#
#
#
#     Input: costs = [[259,770],[448,54],[926,667],[184,139],[840,118],[577,469]]
#     Output: 1859
#
#
# **Example 3:**
#
#
#
#     Input: costs = [[515,563],[451,713],[537,709],[343,819],[855,779],[457,60],[650,359],[631,42]]
#     Output: 3086
#
#
#
#
# **Constraints:**
#
#   * `2 * n == costs.length`
#   * `2 <= costs.length <= 100`
#   * `costs.length` is even.
#   * `1 <= aCosti, bCosti <= 1000`
#
#
## @lc code=start
using LeetCode

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


function wavenumbers_refine(ω::T, medium::Medium{T}, species::Species{T};
        tol::T = 1e-7,
        k_effs::Vector{Complex{T}} = Complex{T}[],
        kws...) where T<:Number

    # the dispersion equation is given by: `dispersion(k1,k2) = 0` where k_eff = k1 + im*k2.
    dispersion = dispersion_equation(ω, medium, species; tol = tol, kws...)

    # add any specified keffs
    k_vecs = [[real(kp),imag(kp)] for kp in k_effs]

    k_vecs = map(k_vecs) do k_vec
       res = optimize(dispersion, k_vec, Optim.Options(iterations = 10 * Int(abs(round(log(tol)))), g_tol = tol^3.0, x_tol=tol))
       if res.minimum > T(100)*tol
           [zero(T),-one(T)]
       else
           res.minimizer
       end
    end
    k_vecs = reduce_kvecs(k_vecs, tol)

    # Delete unphysical waves, including waves travelling backwards with almost no attenuation. This only is important in the limit of very low frequency or very weak scatterers.
    deleteat!(k_vecs, findall([k_vec[2] < -sqrt(tol) for k_vec in k_vecs]))

    k_effs = [ k_vec[1] + k_vec[2]*im for k_vec in k_vecs]
    k_effs = sort(k_effs, by=imag)

    return k_effs
end


using Catch22
using Clustering
using CSV
using Plots
using Statistics
using UrlDownload

pyplot()
X = urldownload("https://ndownloader.figshare.com/files/24950795", true, format=:CSV, header=false, delim=",", type=Float64, silencewarnings=true) |> Array;

nomissing = F -> F[.!ismissing.(Array(F))]
X = [nomissing(collect(x)) for x in X];

F = FeatureArray(fill(NaN, length(catch22), length(X)), catch22);
@time Threads.@threads for x ∈ 1:length(X)
    F[:, x] = catch22(Float64.(X[x]))
end

covarianceimage(mapslices(Catch22.zscore, F, dims=2), background_color_legend=nothing, foreground_color_legend=nothing, colorbar_title="|r|", colormode=:top, dpi=600)


@testset "RCF" begin
  Qx, x = PolynomialRing(FlintQQ)
  k, a = NumberField(x - 1, "a")
  Z = maximal_order(k)

  function doit(u::UnitRange, p::Int = 3)
    cnt = 0
    for i in u
      I = ideal(Z, i)
      r, mr = ray_class_group(I, n_quo=p)
      for s in index_p_subgroups(r, fmpz(p), (A,x) -> quo(A, x)[2])
        a = ray_class_field(mr, s)
        if isconductor(a, I, check=false)
          K = number_field(a)
          cnt += 1
        end
      end
    end
    return cnt
  end

  @test doit(1:100) == 16
  @test doit(10^18:10^18+100) == 18
  @test doit(10^18:10^18+1000, 11) == 2
end


abstract type AbstractRiskRegion end

@deprecate RiskRegion EllipticalRiskRegion

mutable struct EllipticalRiskRegion <: AbstractRiskRegion
    μ::Vector{Float64}
    K::Cone
    inv_P::Matrix{Float64}
    α::Float64
    function EllipticalRiskRegion(μ::Vector{Float64}, Σ::Matrix{Float64},
                        K::FiniteCone, α::Float64)
        checkRiskRegionArgs(μ, Σ, K, α)
        P = LinearAlgebra.cholesky(Σ).U
        inv_P = inv(P)
        new_K = FiniteCone(P*(-K.A))
        new(μ, new_K, inv_P, α)
    end

    function EllipticalRiskRegion(μ::Vector{Float64}, Σ::Matrix{Float64},
                        K::PolyhedralCone, α::Float64)
        checkRiskRegionArgs(μ, Σ, K, α)
        P = LinearAlgebra.cholesky(Σ).U
        inv_P = inv(P)
        new_K = PolyhedralCone{Float64}((-K.A) * inv_P) # quickfix
        new(μ, new_K, inv_P, α)
    end
end

# function EllipticalRiskRegion(μ::Distributions.ZeroVector, Σ::Matrix{Float64}, K::Cone, α::Float64)
#     EllipticalRiskRegion(zeros(μ.len), Σ, K, α)
# end
EllipticalRiskRegion(dist::AbstractMvNormal, K::Cone, β::Float64) = EllipticalRiskRegion(dist.μ, dist.Σ.mat, K, quantile(Normal(), β))
EllipticalRiskRegion(dist::Distributions.AbstractMvTDist, K::Cone, β::Float64) = EllipticalRiskRegion(dist.μ, dist.Σ.mat, K, quantile(TDist(dist.df), β))
# EllipticalRiskRegion(dist::MvSkewTDist, K::Cone, β::Float64) = EllipticalRiskRegion(dist.ξ, dist.Ω.mat, K, quantile(TDist(dist.df), β))

function transformed_size(Ω::EllipticalRiskRegion, x::Vector{Float64})
    x_trans = x - Ω.μ
    y = Ω.inv_P'x_trans
    norm(project(Ω.K, y))
end

function ∈(x::Vector{Float64}, Ω::EllipticalRiskRegion)
    transformed_size(Ω::EllipticalRiskRegion, x::Vector{Float64}) >= Ω.α
end

length(Ω::EllipticalRiskRegion) = length(Ω.μ)

mutable struct MonotonicEllipticalRiskRegion <: AbstractRiskRegion
    # Note that cost function for portfolio selection is negative
    # monotonic, so roles of risk and non-risk frontiers are reversed
    Ω::EllipticalRiskRegion
    nonrisk_frontier::LinkedList{VecF64}
    risk_frontier::LinkedList{VecF64}
    max_frontier_points::Int
    function MonotonicEllipticalRiskRegion(args...)
        Ω=EllipticalRiskRegion(args...)
        new(Ω, nil(VecF64), nil(VecF64), length(Ω)*10)
    end
end

function ∈(x::VecF64, Ω::MonotonicEllipticalRiskRegion)
    # Note that cost function for portfolio selection is negative
    # monotonic, so roles of risk and non-risk frontiers are reversed
    if above_risk_frontier(x, Ω.risk_frontier) return false
    elseif below_nonrisk_frontier(x, Ω.nonrisk_frontier) return true
    else
        if x ∈ Ω.Ω
            if length(Ω.nonrisk_frontier) < Ω.max_frontier_points
                add_to_nonrisk_frontier!(Ω, x)
            end
            return true
        else
            if length(Ω.risk_frontier) < Ω.max_frontier_points
                add_to_risk_frontier!(Ω, x)
            end
            return false
        end
    end
end


using TomoForward
using XfromProjections

# using MKLSparse # uncomment if you've installed MKLSparse, which will boost the performance

# generate synthetic data
img = zeros(128, 128, 128)
img[40:70, 40:60, 50:70] .= 1.0
H, W, nslice = size(img)

nangles = 90
detcount = Int(floor(size(img,1)*1.4))
proj_geom = ProjGeom(1.0, detcount, LinRange(0,pi,nangles+1)[1:nangles])

isdefined_A = @isdefined A
if isdefined_A == false
    A = fp_op_parallel2d_strip(proj_geom, size(img, 1), size(img, 2))
end

p = zeros(nangles, nslice, detcount)
for i=1:nslice
    p[:,i,:] .= reshape(A * vec(img[:,:,i]), nangles, detcount)
end

# fbp slice by slice
q = filter_proj(p)
img3d = bp_slices(q, A, H, W)

# using PyPlot
# imshow(img3d[:,:,60])


############################################################################
# Find the ground state of the Ising Hamitlonian and get the magnetization #
############################################################################
using MatrixProductStates

let
    # Number of Spins
    N = 20
    # Bond Dimension
    D = 20
    # Physical dimension
    d = 2
    # Desired Accuracy
    acc = 1E-10
    # The number of time steps we take
    nsteps = 200
    # The size of each time step
    dt = 5E-2

    # Coupling between nearest neigbours
    J = 1.0
    # Coupling to external field
    lambda = 1.0
    # Generate Ising Hamiltonian H = -J * sum_{i=1}^{N-1} X^i X^i+1 - lambda * sum_{i=1}^N Z^i.
    H = getIsingMPO(N, J, lambda)
    # Get the MPO for the total spin
    Sz = getTotalSpinMPO(N)

    #=
    In the following we prepare the time evolution operators in MPO form using an
    odd-even decomposition for the evolution operator. Since the Ising Hamiltonian is
    of the form H = \sum_i h_{i,i+1} we can approximate the matrix exponential using
    a Suzuki-Trotter decomposition as follows
    exp(-dt * H) ≈ \prod_{i even} exp(-dt * h_{i,i+1}) \prod_{i odd} exp(-dt * h_{i,i+1}).
    Since all terms starting at an odd (even) site commute, we can find an MPO
    representation by decomping one of the local terms exp(-dt * h_{i,i+1}) into an MPO,
    and using the same tensors at every site. Special care has to be taken on the
    boundaries.
    =#

    # The required Pauli matrices for building the evolution MPOs
    Id, X, _, Z = getPauliMatrices()
    # Prepare the matrix representation for the different exponentials appearing in the odd-even decomposition
    Uloc_left_boundary = exp(-dt * (-J * kron(X, X) - lambda * kron(Z, Id) - 0.5 * lambda * kron(Id, Z)))
    Uloc_center = exp(-dt * (-J * kron(X, X) - 0.5 * lambda * (kron(Z, Id) + kron(Id, Z))))
    Uloc_right_boundary = exp(-dt * (-J * kron(X, X) - 0.5 * lambda * kron(Z, Id) - lambda * kron(Id, Z)))
    # The identity operator as MPO tensor
    id_tensor = reshape(Id, (1, 1, 2, 2))
    # Find the mpo representation for the local terms
    mpo_Uloc_left_boundary = decompose_into_mpo(Uloc_left_boundary, d)
    mpo_Uloc_center = decompose_into_mpo(Uloc_center, d)
    mpo_Uloc_right_boundary = decompose_into_mpo(Uloc_right_boundary, d)
    # Build the MPO for evolving the odd sites
    Uodd = MPO{Float64}(undef, N)
    for i = 1:2:N-1
        if i == 1
            Uodd[i] = mpo_Uloc_left_boundary[1]
            Uodd[i+1] = mpo_Uloc_left_boundary[2]
        elseif i == N - 1
            Uodd[i] = mpo_Uloc_right_boundary[1]
            Uodd[i+1] = mpo_Uloc_right_boundary[2]
        else
            Uodd[i] = mpo_Uloc_center[1]
            Uodd[i+1] = mpo_Uloc_center[2]
        end
    end
    if isodd(N)
        Uodd[N] = id_tensor
    end
    # Build the MPO for evolving the even sites
    Ueven = MPO{Float64}(undef, N)
    Ueven[1] = id_tensor
    for i = 2:2:N-1
        if i == N - 1
            Ueven[i] = mpo_Uloc_right_boundary[1]
            Ueven[i+1] = mpo_Uloc_right_boundary[2]
        else
            Ueven[i] = mpo_Uloc_center[1]
            Ueven[i+1] = mpo_Uloc_center[2]
        end
    end
    if iseven(N)
        Ueven[N] = id_tensor
    end

    # Evolve in imaginary time starting from a random, normalized state
    psi = random_mps_obc(N, D, d, Float64)
    gaugeMPS!(psi, :left, true)
    for i = 1:nsteps
        print("\rStep: ", i)
        # Apply the evolution operators
        psi = apply_operator(Uodd, psi)
        psi = apply_operator(Ueven, psi)
        # Compress and renormalize
        psi = svd_compress_mps(psi, D)
        gaugeMPS!(psi, :left, true)
    end
    E0 = expectation_value(psi, H)

    # Print the results
    println(" ")
    println("Groundstate energy:            ", E0)
    println("Groundstate energy density:    ", E0 / N)
    println("Total spin:                    ", expectation_value(psi, Sz))
    println("Entropy along center bond      ", compute_entropy(psi))

    nothing
end


using PooksoftOptionsKit
using Dates

# what is the stock price?
stock_price = 54.17
implied_volatility = 0.67
DTE = (31.0/365.0)
number_of_levels = 12
risk_free_rate = 0.15 # this is in percent
dividend_rate = 0.0 # this is in percent
strike_price = 60.0
premium_value = 2.06

# setup options calculation -
amd_option_parameters = PSOptionKitPricingParameters(implied_volatility, DTE, number_of_levels, risk_free_rate, dividend_rate)

# setup asset contract -
callOptionContract = PSCallOptionContract("AMD", Date(2020,6,25), strike_price, premium_value, 1)

# estimate the implied volatility -
iv = estimate_implied_volatility(callOptionContract,amd_option_parameters,stock_price)


##=================####==============##
## TEST SIMULATION ABM NETWORK-EPIDEMICS
##=================####==============##

##=================####==============##

using ArgParse      # For CLI arguments
using StatsBase     # For sample function
using SparseArrays  # For Adyacency Matrix
using Distributions # For probability distributions
using DelimitedFiles # For data output
using LightGraphs   # For Netwrork stuff

##=================####==============##

include("NetABM.jl") # Include file with functions and definitions

### ============== ### ============== ### ============== ###
### SYSTEM'S PARAMETERS FROM COMMAND LINE
### ============== ### ============== ### ============== ###

args_settings = ArgParseSettings()

@add_arg_table! args_settings begin
    "--numAgents", "-n"
        help = "Number of Agents"
        arg_type = Int
        default = 65536
    "--initialI", "-i"
        help = "Initial fraction of Infected Agents"
        arg_type = Float64
        default = 0.01
    "--attackR", "-a"
        help = "Attack Rate"
        arg_type = Float64
        default = 0.25
    "--meanRecT", "-t"
        help = "Mean recovery time"
        arg_type = Int64
        default = 10
    "--meanCop", "-c"
        help = "Mean cooperation probability"
        arg_type = Float64
        default = 0.5
    "--stdCop", "-s"
        help = "Std Deviation cooperation probability"
        arg_type = Float64
        default = 0.1
    "--rep", "-r"
        help = "Repetition ID (for ensemble statistics)"
        arg_type = Int
        default = 1
end

args = parse_args(args_settings)

##=================####==============##
## ASIGN SYSTEM'S PARAMETERS FROM CLI

params = Params()

params.num_agents      = args["numAgents"] # Number of agents (32768)
params.p_infected_t0   = args["initialI"] # Initial fraction of infected agents
params.μ_recovT_agents = args["meanRecT"] # Std deviation of cooperation probability
params.μ_cop_agents    = args["meanCop"] # Mean of cooperation probability
params.σ_cop_agents    = args["stdCop"] # Std deviation of cooperation probability
params.attack_rate     = args["attackR"] # Virus attack rate
params.repetition      = args["rep"] # Repetition ID for ensemble statistics

##=================####==============##
# TEST PARAMETERS

# params.num_agents    = 2^16 # Number of agents (32768)
# params.p_infected_t0 = 0.01 # Initial fraction of infected agents
# params.attack_rate   = 0.25 # Virus attack rate
# params.repetition    = 1 # Repetition ID for ensemble statistics

# params.μ_cop_agents  = 0.15 # Mean of cooperation probability
# params.σ_cop_agents  = 0.1 # Std deviation of cooperation probability
# params.μ_recovT_agents = 7 # Std deviation of cooperation probability

##=================####==============##
# PATH TO SAVE DATA (MODIFY WITH YOUR OWN!)

repo_path = joinpath(homedir(), "Code", "Collaborations", "networkEpidemiology_MX")

data_out_path = joinpath(homedir(),
    "Code", "Collaborations", "networkEpidemiology_MX", "julia_ABM", "data", "LFR")

##=================####==============##
# INITIALIZE ADJACENCY MATRIX (LFR Model)

# the_root = joinpath(repo_path, "src", "binary_networks")
#
# println("BUILDING NETWORK...")
#
# the_net, true_com = get_network(
#     the_root,
#     "julia_ABM/sims_scripts",
#     N    = params.num_agents,
#     t2   = 0.25,
#     minc = 100,
#     maxc = 1000);

the_net = readdlm(joinpath(repo_path, "julia_ABM", "sims_scripts", "network.dat"))

g, Nodes = lectura_uw(the_net)

adj_mat = adjacency_matrix(g)
rows = rowvals(adj_mat)

println("NETWORK DONE")
##=================####==============##
# INITIALIZE MEETS, COOPERATION AND RECOVERY TIMES DISTRIBUTION

rt_μ = params.μ_recovT_agents # MEAN RECOVERY TIME
pc_μ = params.μ_cop_agents # MEAN COOPERATION PROBABILITY
pc_σ = params.σ_cop_agents # STD_DEV COOPERATION PROBABILITY

# meets_dist = Exponential()
meets_dist  = Geometric()
coop_dist   = truncated(Normal(pc_μ, pc_σ), 0.0, 1.0)
recovt_dist = truncated(Poisson(rt_μ), 7, 35)

##=================####==============##

# DEFINE AGENTS POPULATION (DEFAULT VALUES)
agents = [Agent(id) for id in 1:params.num_agents]

# MODIFY AGENT'S CHARACTERISTICS ACCORDING TO ITS DEMOGRAPHICS
initialize_demographics!(agents, params, coop_dist, meets_dist, recovt_dist)

# ASSGINS CONTACTS TO agent FROM adj_mat
for ag in agents
    assign_contacts!(ag, agents, adj_mat, rows)
end

##=================####==============##
# SYSTEM EVOLUTION

# TIME AT WHICH LOCKDOWN IS ANNOUNCED
time_lockdown = 15

tau = []
num_S = []
num_I = []
num_R = []

# INITIALIZE TIME AND POPULATIONS
params.now_t = 0
populations = get_populations(agents, params)

##=================####==============##

println("SIM STARTS")

while populations[2] > 0.0 # WHILE THERE ARE INFECTED AGENTS

    # RECORD DATA
    push!(tau, params.now_t)
    push!(num_S, populations[1])
    push!(num_I, populations[2])
    push!(num_R, populations[3])

    # EACH AGENTS DECIDES TO STAY AT HOME/ NOT TO STAY AT HOME
    if params.now_t >= time_lockdown
        set_coop_agents!(agents, params)
    end

    # FINDS NEXT STATE AND UPDATES ALL AGENTS
    update_all_agents!(agents, params)

    # GET CURRENT POPULATIONS AND UPDATE TIME
    global populations = get_populations(agents, params);
    params.now_t += 1
    println(params.now_t, populations)
end

println("NO MORE INFECTED!")

##=================####==============##
# EXPORT PARAMETERS
export_parameters(params, data_out_path)

##=================####==============##
# EXPORT POPULATIONS DATA

params_filename =
    "_I0_$(params.p_infected_t0)_AR_$(params.attack_rate)_CA_$(params.μ_cop_agents)_$(params.repetition).csv"

file_name = "populations_N_$(params.num_agents)"*params_filename

open(joinpath(data_out_path, file_name), "w") do io
    println(io, "t,S,I,R")
    writedlm(io, hcat(tau, num_S, num_I, num_R), ',')
end

##=================####==============##
# EXPORT INFECTION TIME OF AGENTS

file_name = "infTime_N_$(params.num_agents)"*params_filename

open(joinpath(data_out_path, file_name), "w") do io
    println(io, "agent_ID,time_of_infection")
    for ag in agents
        println(io, "$(ag.id),$(ag.infection_t)")
    end
end

##=================####==============##

println("TAN TAN...")

##=================####==============##


using Printf, LinearAlgebra, Plots, ParallelStencil, ParallelStencil.FiniteDifferences3D

# Initialize Globals
if !@isdefined USE_GPU
    const USE_GPU = false
end

if !@isdefined BENCHMARK
    const BENCHMARK = true
end

if !@isdefined VISUALIZE
    const VISUALIZE = false
end

if !@isdefined STEADY
    const STEADY = false
end

# Initialize `ParallelStencil.jl`
@static if USE_GPU
    @init_parallel_stencil(CUDA, Float64, 3);
else
    @init_parallel_stencil(Threads, Float64, 3);
end

# Macro for shorter notation
macro inner(A) esc(:( $A[2:end-1,2:end-1,2:end-1] )) end

@parallel function compute_ResH!(H, Hold, ResH, _dt, D_dx², D_dy², D_dz²)
    @all(ResH) = -(@inn(H) - @inn(Hold)) * _dt + (@d2_xi(H)*D_dx² + @d2_yi(H)*D_dy² + @d2_zi(H)*D_dz²)
    return
end

@parallel function compute_ResH_steady!(H, ResH, D_dx², D_dy², D_dz²)
    @all(ResH) = @d2_xi(H)*D_dx² + @d2_yi(H)*D_dy² + @d2_zi(H)*D_dz²
    return
end

@parallel function compute_dHdt!(ResH, dHdt, dHdt2, damp)
    @all(dHdt2) = @all(ResH) + damp * @all(dHdt)
    return
end

@parallel function compute_H!(H2, H, dHdt, dτ)
    @inn(H2) = @inn(H) + dτ * @all(dHdt)
    return
end

"""
    diffusion_3D(res[,tol])

Runs the 3D dualtime diffusion simulation

# Arguments
- `res::Int`: xyz-resolutions of the simulation
- `tol::Float64`(optional): tolerance for convergence - default: `1e-8`

# Returns (Depending on globals)
- If BENCHMARK is true ->
    `T_eff`: Effective memory throughput [GB/s]
- If STEADY is true and BENCHMARK is false ->
    `ittot`, `xc` and `H`: Total number of iterations needed to reach steadystate, the global x-coordinate Array and the global solution array
- If STEADY and BENCHMARK are false ->
    `xc` and `H`: The global x-coordinate Array and the global solution array
"""
@views function diffusion_3D(res::Int; tol=1e-8)
    # Physics
    lx, ly, lz = 10.0, 10.0, 10.0 # domain size
    D     = 1.0                   # diffusion coefficient
    ttot  = 1.0                   # total simulation time
    dt    = 0.2                   # physical time step

    # Numerics
    nx, ny, nz = res, res, res
    itMax      = 1e4              # max number of iterations
    nout       = 10               # tol check

    # Derived numerics
    dx, dy, dz = lx/nx, ly/ny, lz/nz            # cell sizes
    dτ         = (1.0/(dx^2/D/6.1) + 1.0/dt)^-1 # iterative timestep
    damp       = 1-29/nx                        # damping (this is a tuning parameter, dependent on e.g. grid resolution)
    xc         = LinRange(dx/2, lx-dx/2, nx)    #
    yc         = LinRange(dy/2, ly-dy/2, ny)
    zc         = LinRange(dz/2, lz-dz/2, nz)
    _dt        = 1.0/dt
    D_dx²      = D/dx^2
    D_dy²      = D/dy^2
    D_dz²      = D/dz^2

    # Array allocation
    H     = @zeros(nx  , ny  , nz  )
    H2    = @zeros(nx  , ny  , nz  )
    Hᵗ    = @zeros(nx  , ny  , nz  )
    dHdt  = @zeros(nx-2, ny-2, nz-2)
    dHdt2 = @zeros(nx-2, ny-2, nz-2)
    ResH  = @zeros(nx-2, ny-2, nz-2)

    # Initial condition
    @inner(H) .= @inner(Data.Array([2.0 * exp(-(xc[ix] - 0.5*lx)^2 -(yc[iy] - 0.5*ly)^2 -(zc[iz] - 0.5*lz)^2) for ix=1:nx, iy=1:ny, iz=1:nz]))
    H2        .= H
    Hᵗ        .= H

    # Benchmark variables
    @static if BENCHMARK
        warmup = 10         # Number of warmup-iterations
        t_tic  = 0.0        # Holds start time
        itMax  = 1e4/nx     # scale max number of iterations down my grid size
    end

    # Run to Steady State variables
    @static if STEADY
        damp = 1-4/nx
    end

    # Animation object for visualization
    @static if VISUALIZE
        gr()
        ENV["GKSwstype"] = "nul"
        anim = Animation()
        H_v = zeros(nx, ny, nz)
        init_extrema = extrema(H)
    end

    # initial physical time loop conditions
    t_tic = 0.0
    t = 0.0
    it_t = 0
    ittot = 0

    @static if !STEADY
        println("Running with dual-time")

        # Physical time loop
        while t<ttot

            # initial pseudo-transient loop conditions
            it_τ = 0
            err = 2*tol

            # Pseudo-transient iteration
            while err>tol && it_τ<itMax

                # Step 1
                @parallel compute_ResH!(H, Hᵗ, ResH, _dt, D_dx², D_dy², D_dz²)
                # Step 2
                @parallel compute_dHdt!(ResH, dHdt, dHdt2, damp)
                dHdt, dHdt2 = dHdt2, dHdt # Pointer Swap
                # Step 3
                @parallel compute_H!(H2, H, dHdt, dτ)
                H, H2 = H2, H # Pointer Swap

                # Calculate L2 norm
                if (it_τ % nout == 0)
                    err = norm(ResH)/sqrt(length(ResH))
                end

                # Start the clock after warmup-iterations
                @static if BENCHMARK
                    if (it_t == 0 && it_τ == warmup+1)
                        t_tic = Base.time();
                    end
                end

                # Render a slice of the 3D diffusion-map and save as an animation frame
                @static if VISUALIZE
                    if (it_τ % nout == 0)
                        H_v .= H
                        opts = (aspect_ratio=1, xlims=extrema(xc), ylims=extrema(yc), clims=init_extrema, xlabel="lx", ylabel="ly", title="3D Diffusion (it_t=$it_t, it_τ=$it_τ)")
                        heatmap(xc, yc, H_v[:,:,nz÷2]; opts...)
                        frame(anim)
                    end
                end

                it_τ += 1
            end

            # update physical time step
            ittot += it_τ; it_t += 1; t += dt
            Hᵗ .= H
            if isnan(err) error("NaN") end
        end
    else
        println("Running in pseudo-time to steady-state")

        # initial pseudo-transient loop conditions
        it_τ = 0
        err = 2*tol

        # Pseudo-transient iteration
        while err>tol && it_τ<itMax
            # Step 1
            @parallel compute_ResH_steady!(H, ResH, D_dx², D_dy², D_dz²)
            # Step 2
            @parallel compute_dHdt!(ResH, dHdt, dHdt2, damp)
            dHdt, dHdt2 = dHdt2, dHdt # Pointer Swap
            # Step 3
            @parallel compute_H!(H2, H, dHdt, dτ)
            H, H2 = H2, H # Pointer Swap

            # Calculate L2 norm (error)
            if (it_τ % nout == 0)
                err = norm(ResH)/sqrt(length(ResH))
            end
            it_τ += 1
        end

        # update physical time step
        ittot += it_τ; it_t += 1
        if isnan(err) error("NaN") end
    end

    # Calculate performance
    @static if BENCHMARK
        t_toc = Base.time() - t_tic                            # Stop the clock
        reads = length(Hᵗ)                                     # Read Only Memory Access: Hᵗ
        updates = length(H) + length(dHdt)                     # Update Memory access: H and dHdt
        A_eff = 1e-9 * (2 * updates + reads) * sizeof(Float64) # Effective main memory access per iteration [GB]
        t_it  = t_toc/(ittot-warmup)                           # Execution time per iteration [s]
        T_eff = A_eff/t_it                                     # Effective memory throughput [GB/s]
        println("time(sec)=$t_toc T_eff=$T_eff")
    end
    @printf("Ttime steps = %d, nx = %d, iterations tot = %d \n", it_t, nx, ittot)

    # Save the animation as a GIF
    @static if VISUALIZE
        gif(anim, "diffusion3D_xpu.gif", fps = 15)
    end

    # Return T_eff if BENCHMARK and Result otherwise
    @static if BENCHMARK
        return T_eff
    elseif STEADY
        return ittot, xc, Array(H)
    else
        return xc, Array(H)
    end
end


module Chen1995

using JuMP
#using GLPKMathProgInterface # was used in solve

"""
    build(model, pqr, LWH; only_lxlzwyhz = true)
    build(model, p, q, r, L, W, H; only_lxlzwyhz = true)

Add the variables and constraints of Chen 1995 3D-packing (minimize wasted
container volume, heterogeneous containers, heterogeneous boxes, free rotation
allowed) to the model.

For more information see: Chen, C. S., S. M. Lee, and Q. S. Shen. "An Analytical Model for the Container Loading Problem." European Journal of Operational Research 80, no. 1 (January 5, 1995): 68–76. https://doi.org/10.1016/0377-2217(94)00002-T.

# Arguments
- `model`: JuMP model to which the variables and contraints will be added to.
- `pqr`: three sequences of numbers (with the same length) denoting
  respectively the length/width/height of the boxes/cartons
- `LWH`: three sequences of numbers (with the same length) denoting
  respectively the length/width/height of the containers
- `only_lxlzwyhz`: if the model should use only the orientation variables
  lx, lz, wy, and hz (fast but arcane), or all of them (legible but slow).
- `no_rotation`: if true the boxes will not be allowed to rotate and
  `only_lxlzwyhz` value has no effect; default: false.
- `max_packed_volume`: if true the model will not fail if not all boxes
  cannot be packed, but maximize the box volume that can be packed, however
  there is no guarantee of minimizing wasted container space anymore
  (i.e., if all boxes can be packed in less than all containers, the
  solver can put some boxes in each container anyway, it will not worry
  about trying to pack the boxes in the most dense way).
"""
function build(model, p, q, r, L, W, H;
  only_lxlzwyhz = true,
  no_rotation = false,
  max_packed_volume = false
)
  build(model, (p, q, r), (L, W, H);
    only_lxlzwyhz = only_lxlzwyhz,
    no_rotation = no_rotation,
    max_packed_volume = max_packed_volume
  )
end
function build(model, pqr, LWH;
  only_lxlzwyhz = true,
  no_rotation = false,
  max_packed_volume = false
)
  @assert length(pqr) == 3
  @assert (isone ∘ length ∘ unique ∘ map)(length, pqr)
  p, q, r = pqr
  N = length(pqr[1])
  @assert length(LWH) == 3
  @assert (isone ∘ length ∘ unique ∘ map)(length, LWH)
  L, W, H = LWH
  m = length(LWH[1])
  container_volumes = map(prod, zip(LWH...))
  box_volumes = map(prod, zip(pqr...))
  # highest length/width/height of a container
  ML, MW, MH = map((xs)->max(xs...), LWH)
  #ML, MW, MH = (10000, 10000, 10000)

  @variables model begin
    s[1:N, 1:m], Bin
    n[1:m], Bin
    x[1:N] >= 0
    y[1:N] >= 0
    z[1:N] >= 0
    a[1:N, 1:N], Bin
    b[1:N, 1:N], Bin
    c[1:N, 1:N], Bin
    d[1:N, 1:N], Bin
    e[1:N, 1:N], Bin
    f[1:N, 1:N], Bin
  end
  if !no_rotation
    @variables model begin
      lx[1:N], Bin
      lz[1:N], Bin
      wy[1:N], Bin
      hz[1:N], Bin
    end
    if !only_lxlzwyhz
      @variables model begin
        ly[1:N], Bin
        wx[1:N], Bin
        wz[1:N], Bin
        hx[1:N], Bin
        hy[1:N], Bin
      end
    end
  end

  # disable all unused variables (the model only access positions in
  # which i < k, so we can delete all i >= k)
  for var = [:a, :b, :c, :d, :e, :f], i = 1:N, k = 1:i
    setlowerbound(model[var][i, k], 0.0)
    setupperbound(model[var][i, k], 0.0)
  end

  if max_packed_volume
    @objective(model, Max, sum(box_volumes[i] * s[i, j] for i = 1:N, j = 1:m))
  else # minimize wasted space but expects to be possible pack all boxes
    @objective(model, Min, sum(container_volumes[j] * n[j] for j = 1:m))
  end

  for k = 1:N, i = 1:(k-1) # forall i < k ∈ N
    # Constraints (1)-(6) -- no overlap among boxes inside the same container.
    if no_rotation
      @constraints model begin
        x[i] + p[i] <= x[k] + (1 - a[i, k])*ML
        x[k] + p[k] <= x[i] + (1 - b[i, k])*ML
        y[i] + q[i] <= y[k] + (1 - c[i, k])*MW
        y[k] + q[k] <= y[i] + (1 - d[i, k])*MW
        z[i] + r[i] <= z[k] + (1 - e[i, k])*MH
        z[k] + r[k] <= z[i] + (1 - f[i, k])*MH
      end
    elseif only_lxlzwyhz
      @constraints model begin
        x[i] + p[i]*lx[i] + q[i]*(lz[i] - wy[i] + hz[i]) + r[i]*(1 - lx[i] - lz[i] + wy[i] - hz[i]) <= x[k] + (1 - a[i, k])*ML
        # same as above but swapping [i] and [k], and replacing 'a' by 'b'
        x[k] + p[k]*lx[k] + q[k]*(lz[k] - wy[k] + hz[k]) + r[k]*(1 - lx[k] - lz[k] + wy[k] - hz[k]) <= x[i] + (1 - b[i, k])*ML

        y[i] + q[i]*wy[i] + p[i]*(1 - lx[i] - lz[i]) + r[i]*(lx[i] + lz[i] - wy[i]) <= y[k] + (1 - c[i, k])*MW
        # same as above but swapping [i] and [k], and replacing 'c' by 'd'
        y[k] + q[k]*wy[k] + p[k]*(1 - lx[k] - lz[k]) + r[k]*(lx[k] + lz[k] - wy[k]) <= y[i] + (1 - d[i, k])*MW

        z[i] + r[i]*hz[i] + q[i]*(1 - lz[i] - hz[i]) + p[i]*lz[i] <= z[k] + (1 - e[i, k])*MH
        # same as above but swapping [i] and [k], and replacing 'e' by 'f'
        z[k] + r[k]*hz[k] + q[k]*(1 - lz[k] - hz[k]) + p[k]*lz[k] <= z[i] + (1 - f[i, k])*MH
      end
    else
      @constraints model begin
        x[i] + p[i]*lx[i] + q[i]*wx[i] + r[i]*hx[i] <= x[k] + (1 - a[i, k])*ML
        x[k] + p[k]*lx[k] + q[k]*wx[k] + r[k]*hx[k] <= x[i] + (1 - b[i, k])*ML
        y[i] + q[i]*wy[i] + p[i]*ly[i] + r[i]*hy[i] <= y[k] + (1 - c[i, k])*MW
        y[k] + q[k]*wy[k] + p[k]*ly[k] + r[k]*hy[k] <= y[i] + (1 - d[i, k])*MW
        z[i] + r[i]*hz[i] + q[i]*wz[i] + p[i]*lz[i] <= z[k] + (1 - e[i, k])*MH
        z[k] + r[k]*hz[k] + q[k]*wz[k] + p[k]*lz[k] <= z[i] + (1 - f[i, k])*MH
      end
    end

    for j = 1:m # forall i < k ∈ N, j ∈ m
      # Constraint (7) -- boxes in the same container must have a relative
      # position to each other.
      @constraint(model,
        a[i, k] + b[i, k] + c[i, k] + d[i, k] + e[i, k] + f[i, k] >= s[i, j] + s[k, j] - 1
      )
    end
  end # end forall i < k ∈ N

  if !no_rotation && !only_lxlzwyhz
    for i = 1:N
      # Necessary if the model includes all orientation variables.
      @constraints model begin
        lx[i] + ly[i] + lz[i] == 1
        wx[i] + wy[i] + wz[i] == 1
        hx[i] + hy[i] + hz[i] == 1
        lx[i] + wx[i] + hx[i] == 1
        ly[i] + wy[i] + hy[i] == 1
        lz[i] + wz[i] + hz[i] == 1
      end
    end
  end

  # The maximization of the volume of packed boxes make no sense if we already
  # force the model to pack all boxes.
  if !max_packed_volume
    for i = 1:N
      # Constraint (8) -- every box must be inside one container.
      @constraint(model, sum(s[i, j] for j = 1:m) == 1)
    end
  end

  # Constraint (9) -- if there is a box inside a container, the container
  # must be marked as used.
  sorted_bvs = sort!(map((xs)->*(xs...), zip(pqr...)))
  for j = 1:m
    # MJ is the basic volumetric upper bound in the amount of boxes that can be
    # fitted inside the respective container.
    #MJ = 0
    MJ = 10000
    #remaining_cv = container_volumes[j]
    #while MJ < N && remaining_cv >= sorted_bvs[MJ+1]
    #  remaining_cv -= sorted_bvs[MJ+1]
    #  MJ += 1
    #end
    @constraint(model, sum(s[i, j] for i = 1:N) <= MJ*n[j])
  end

  # Constraints (10)-(12) -- the boxes must respect the container boundaries
  # (the back-left-bottom boundary is already enforced by the '>= 0' trivial
  # constraint).
  for i = 1:N, j = 1:m
    if no_rotation
      @constraints model begin
        x[i] + p[i] <= L[j] + (1 - s[i, j])*ML
        y[i] + q[i] <= W[j] + (1 - s[i, j])*MW
        z[i] + r[i] <= H[j] + (1 - s[i, j])*MH
      end
    elseif only_lxlzwyhz
      @constraints model begin
        x[i] + p[i]*lx[i] + q[i]*(lz[i] - wy[i] + hz[i]) + r[i]*(1 - lx[i] - lz[i] + wy[i] - hz[i]) <= L[j] + (1 - s[i, j])*ML
        y[i] + q[i]*wy[i] + p[i]*(1 - lx[i] - lz[i]) + r[i]*(lx[i] + lz[i] - wy[i]) <= W[j] + (1 - s[i, j])*MW
        z[i] + r[i]*hz[i] + q[i]*(1 - lz[i] - hz[i]) + p[i]*lz[i] <= H[j] + (1 - s[i, j])*MH
      end
    else
      @constraints model begin
        x[i] + p[i]*lx[i] + q[i]*wx[i] + r[i]*hx[i] <= L[j] + (1 - s[i, j])*ML
        y[i] + q[i]*wy[i] + p[i]*ly[i] + r[i]*hy[i] <= W[j] + (1 - s[i, j])*MW
        z[i] + r[i]*hz[i] + q[i]*wz[i] + p[i]*lz[i] <= H[j] + (1 - s[i, j])*MH
      end
    end
  end

  model
end

"""
    extract_attributions(model) :: Dict{Int64, Vector{Int64}}

Returns a dictionary with the index of the containers as keys and the index
of the boxes inside that container as values.
"""
function extract_attributions(model) :: Dict{Int64, Vector{Int64}}
  s = getvalue(model[:s])
  N = size(s, 1)
  m = size(s, 2)
  d = Dict{Int64, Vector{Int64}}()
  # builds the dictionary from container index to box indexes
  for i = 1:N, j = 1:m
    if isone(round(s[i, j]))
      if haskey(d, j)
        push!(d[j], i)
      else
        d[j] = [i]
      end
    end
  end

  d
end

"""
    extract_positions(model) :: NTuple{3, Vector{Float64}}

Returns three arrays (x, y, z) of coordinates where the boxes were placed.
This model has the coordinates of he boxes as continuous variables. It is
tempting to truncate the values, as this would keep the validity of the
solution and make it more compatible with other models, but as the model
can be modified to worry about the gravity center, it is best kept as it is.
"""
function extract_positions(model) :: NTuple{3, Vector{Float64}}
  map(s ->getvalue(model[s]), (:x, :y, :z))
end

"""
    extract_oriented_sizes(model, pqr; only_lxlzwyhz = true) :: NTuple{3, Vector{Int64}}

Returns the oriented sizes of the boxes in the solved model. Note that
this function should not be called if the model was built with
`no_rotation = true` as, in this case, the model has no orientation variables
and the oriented size of the boxes is simply the original box sizes.
"""
function extract_oriented_sizes(
  model, pqr; only_lxlzwyhz = true
) :: NTuple{3, Vector{Int64}}
  # computes the oriented pqr values (sizes of the boxes when oriented)
  N = length(model[:x])
  zeroes = repeat([zero(typeof(pqr[1][1]))], N)
  p′, q′, r′ = zeroes, copy(zeroes), copy(zeroes)
  p, q, r = pqr
  lx = getvalue(model[:lx])
  lz = getvalue(model[:lz])
  wy = getvalue(model[:wy])
  hz = getvalue(model[:hz])
  if !only_lxlzwyhz
    ly = getvalue(model[:ly])
    wx = getvalue(model[:wx])
    wz = getvalue(model[:wz])
    hx = getvalue(model[:hx])
    hy = getvalue(model[:hy])
  end
  if only_lxlzwyhz
    for i = 1:N
      p′[i] = p[i]*lx[i] + q[i]*(lz[i] - wy[i] + hz[i]) +
        r[i]*(1 - lx[i] - lz[i] + wy[i] - hz[i])
      q′[i] = p[i]*(1 - lx[i] - lz[i]) + q[i]*wy[i] +
        r[i]*(lx[i] + lz[i] - wy[i])
      r′[i] = p[i]*(lz[i]) + q[i]*(1 - lz[i] - hz[i]) + r[i]*hz[i]
    end
  else
    for i = 1:N
      p′[i] = p[i]*lx[i] + q[i]*wx[i] + r[i]*hx[i]
      q′[i] = p[i]*ly[i] + q[i]*wy[i] + r[i]*hy[i]
      r′[i] = p[i]*lz[i] + q[i]*wz[i] + r[i]*hz[i]
    end
  end

  (p′, q′, r′)
end

"""
    extract_solution(model, pqr; only_lxlzwyhz = true)

Returns the triple of the results of extract\\_{attributions, positions,
oriented\\_sizes} over the solved model.

The value of only_lxlzwyhz needs to be the same passed to Chen1995.build.
"""
function extract_solution(model, pqr; only_lxlzwyhz = true)
  d = extract_attributions(model)
  c = extract_positions(model)
  pqr′ = extract_oriented_sizes(model, pqr; only_lxlzwyhz = only_lxlzwyhz)

  (d, c, pqr′)
end

#= PROBLEM: should we treat the possibility of the model being unfeasible?
"""
    solve(pqr, LWH, solver = GLPKSolverMIP())

Solves the problem defined by pqr and LWH using the Chen 1995 model in the
specified solver, and returns a triple of extract_{attributions, positions,
oriented_sizes} (which is the extracted solution).
"""
function solve(pqr, LWH, solver = GLPKSolverMIP())
  model = Model(solver = solver)
  build(model, pqr, LWH)
  status = solve(model, suppress_warnings = true)
  if
  extract_solution(model, pqr)
end=#

#=function chen1995_1to7(model, pqr, LWH)

end=#

end # module Chen1995


using Test
using ArnoldiMethod: OrderBy, OrderReverse, OrderPerm
using Base.Order: lt

@testset "Stable permutation ordering" begin
    xs = [1+3im, 1-3im, 4]
    @testset "Forward with f = $f" for f in (real, abs)
        ord = OrderPerm(xs, OrderBy(f))

        # Test whether the order is stable!
        @test lt(ord, 1, 2)
        @test !lt(ord, 2, 1)
        @test lt(ord, 1, 3)
        @test lt(ord, 2, 3)
        @test sort!([1,2,3], QuickSort, ord) == [1, 2, 3]
    end

    @testset "Backward with f = $f" for f in (real, abs)
        ord = OrderPerm(xs, OrderReverse(OrderBy(f)))

        # Test whether the order is stable even backwards -- note that i
        @test lt(ord, 1, 2)
        @test !lt(ord, 2, 1)
        @test lt(ord, 3, 1)
        @test lt(ord, 3, 2)
        @test sort!([1,2,3], QuickSort, ord) == [3, 1, 2]
    end
end


function _point_to_cartesian(layer::T, c::Point; side=:center) where {T <: SimpleSDMLayer}
    lonside, latside = true, true
    if side == :bottomleft
        lonside, latside = false, false
    end
    if side == :topright
        lonside, latside = true, true
    end
    lon = SimpleSDMLayers._match_longitude(layer, c[1]; lower=lonside)
    lat = SimpleSDMLayers._match_latitude(layer, c[2]; lower=latside)
    isnothing(lon) && return nothing
    isnothing(lat) && return nothing
    return CartesianIndex(lat, lon)
end

function _match_latitude(layer::T, lat::K; lower::Bool=true) where {T <: SimpleSDMLayer, K <: AbstractFloat}
    layer.bottom <= lat <= layer.top || return nothing
    lat == layer.bottom  && return 1
    lat == layer.top  && return size(layer, 1)
    relative = (lat - layer.bottom)/(layer.top - layer.bottom)
    fractional = relative * size(layer, 1)+1
    if lat in layer.bottom:2stride(layer,2):layer.top
        f = lower ? floor : ceil
        d = lower ? 1 : 0
        return min(f(Int64, fractional-d), size(layer, 1))
    else
        return min(floor(Int, fractional), size(layer, 1))
    end
end

function _match_longitude(layer::T, lon::K; lower::Bool=true) where {T <: SimpleSDMLayer, K <: AbstractFloat}
    layer.left <= lon <= layer.right || return nothing
    lon == layer.left  && return 1
    lon == layer.right  && return size(layer, 2)
    relative = (lon - layer.left)/(layer.right - layer.left)
    fractional = relative * size(layer, 2)+1
    if lon in layer.left:2stride(layer,1):layer.right
        f = lower ? floor : ceil
        d = lower ? 1 : 0
        return min(f(Int64, fractional-d), size(layer, 2))
    else
        return min(floor(Int, fractional), size(layer, 2))
    end
end


# Utility functions for simulating data
function sim_poisson(trans::AbstractVector{<:Integer})
    # Simulates Poisson noise for a given input vector

    # First, determine the unique distributions from which we need to draw
    λs = unique(trans)
    N = length(λs)
    for λ ∈ λs
        # Generate different Poisson distribution for each
        d = Poisson(λ)
        # Find indices to fill
        ind = findall(==(λ),trans)
        nval = length(ind)
        # Generate nval random number
        vals = rand(d,nval)
        # Put values back into the array
        trans[ind] = vals
    end
    return trans
end


#=

Author: Shunsuke Hori

=#

"""
This holds the results for Harrison Kreps.  In particular, it
accepts two matrices Qa and Qb and compares the single belief,
optimistic belief, and pessimistic belief prices
"""
struct PriceHolder{TF<:AbstractFloat}
    qa::Matrix{TF}
    qb::Matrix{TF}
    qpess::Matrix{TF}
    qopt::Matrix{TF}
    dividend_payoff::Vector{TF}
    qaprice::Vector{TF}
    qbprice::Vector{TF}
    qpessprice::Vector{TF}
    qoptprice::Vector{TF}
    optimisticprice::Vector{TF}
    pessimisticprice::Vector{TF}
    phat_a::Vector{TF}
    phat_b::Vector{TF}
end

function PriceHolder{TF<:AbstractFloat}(qa::Matrix,
                                        qb::Matrix,
                                        dividend_payoff::Vector;
                                        beta::TF=.75,
                                        max_iters::Integer=10000,
                                        tolerance::TF=1e-16)

    # Create the Pessimistic and Optimistic Beliefs
    qpess = Array{TF}(2, 2)
    qpess[1, :] = ifelse(qa[1, 2] < qb[1, 2], qa[1, :], qb[1, :])
    qpess[2, :] = ifelse(qa[2, 2] < qb[2, 2], qa[2, :], qb[2, :])
    qopt = Array{TF}(2, 2)
    qopt[1, :] = ifelse(qa[1, 2] > qb[1, 2], qa[1, :], qb[1, :])
    qopt[2, :] = ifelse(qa[2, 2] > qb[2, 2], qa[2, :], qb[2, :])

    # Price everything
    p_singlebelief, p_optimistic, phat_a, phat_b, p_pessimistic=
        create_prices(qa, qb, qpess, qopt, dividend_payoff,
                                    beta,max_iters,tolerance)

    qaprice = p_singlebelief[1]
    qbprice = p_singlebelief[2]
    qpessprice = p_singlebelief[3]
    qoptprice = p_singlebelief[4]

    return PriceHolder(qa,
                        qb,
                        qpess,
                        qopt,
                        dividend_payoff,
                        qaprice,
                        qbprice,
                        qpessprice,
                        qoptprice,
                        p_optimistic,
                        p_pessimistic,
                        phat_a,
                        phat_b)
end

"""
Computes prices under all belief systems
"""
function create_prices(qa::Matrix, qb::Matrix, qpess::Matrix, qopt::Matrix,
                       dividend_payoff::Vector, bet::AbstractFloat,
                       max_iters::Integer, tolerance::AbstractFloat)
    transitionmatrix = [qa, qb, qpess, qopt]
    # Single Belief Prices
    p_singlebelief = [price_singlebeliefs(q, dividend_payoff, beta=bet)
                         for q in transitionmatrix]

    # Compute Optimistic and Pessimistic beliefs
    p_optimistic, phat_a, phat_b =
        price_optimisticbeliefs([qa, qb], dividend_payoff,
                    beta=bet, max_iter=max_iters, tol=tolerance)
    p_pessimistic =
        price_pessimisticbeliefs([qa, qb], dividend_payoff,
                    beta=bet, max_iter=max_iters, tol=tolerance)

    return p_singlebelief, p_optimistic, phat_a, phat_b, p_pessimistic
end

function print_prices(ph::PriceHolder)
    qaprice, qbprice, qpessprice, qoptprice =
        ph.qaprice, ph.qbprice, ph.qpessprice, ph.qoptprice
    optimisticprice, pessimisticprice =
        ph.optimisticprice, ph.pessimisticprice
    phata, phatb = ph.phat_a, ph.phat_b
    println("The Single Belief Price Vectors are:")
    println(" P(Qa) = $(qaprice)\n P(Qb) = $(qbprice)\n P(Qopt) = $(qoptprice)\n P(Qpess) = $(qpessprice)\n")
    println("The Optimistic Belief Price Vector is:")
    println(" P(Optimistic) = $(optimisticprice)\n Phat(a) = $(phata)\n Phat(b) = $(phatb)\n")
    println("The Pessimistic Belief Price Vector is:")
    println(" P(Pessimistic) = $(pessimisticprice)")
end

ph=PriceHolder(qa, qb, [0.0, 1.0], beta=0.75)

print_prices(ph)


# Markov random field of binary variables. Following
#
# http://arxiv.org/pdf/1304.5299v4.pdf,
#
# the joint distribution is
#
#   P(X) \propto \prod_{i < j < k} psi_{ijk}(x_i, x_j, x_k).
#
# The log potentials log psi_{ijk} are drawn iid from a normal distribution with
# mean 0 and variance 0.02. For convenience, we store log psi_{ijk} for all
# triples (i,j,k), not just those for which i < j < k. We then impose the
# symmetry constraints
#
#   psi_{ijk}(x_i, x_j, x_k) = psi_{ikj}(x_i, x_k, x_j)
#                            = psi_{kij}(x_k, x_i, x_j),
#
# and similarly for the other three possible permutations of i,j,k. Thus the
# joint distribution can also be written
#
#   P(X) \propto (\prod_{i ≠ j ≠ k} psi_{ijk}(x_i, x_j, x_k))^{1/6}.

type SteinMRF <: SteinDistribution
    # Dimensionality
    p::Int64
    # Array of log potentials, to be symmetrized in inner constructor
    logpsi::Array{Float64}
    # Stein factors
    c1::Float64
    c2::Float64
    c3::Float64
    SteinMRF(p::Int64, logpsi::Array{Float64}, c1::Float64,
             c2::Float64, c3::Float64) = (
        logpsi_ = symmetrize(p, logpsi);
        return new(p, logpsi_, c1, c2, c3);
    )
end

function symmetrize(p::Int64, logpsi::Array{Float64})
    for i = 1:p, j = 1:p, k = 1:p
        idx = sortperm([i,j,k])
        for l = 1:2, m = 1:2, n = 1:2
            logpsi[i,j,k,l,m,n] = logpsi[[i,j,k][idx]...,[l,m,n][idx]...];
        end
    end
    return logpsi
end

# Constructor with (c1,c2,c3) = (1,1,1)
SteinMRF(p::Int64) = SteinMRF(
    p,
    sqrt(0.02)*randn(p,p,p,2,2,2),
    1.0,
    1.0,
    1.0
);

# Evaluate log odds
#   log P(y_i = 1 | y_{-i}) - log P(y_i = 0 | y_{-i})
# by summing over (j,k) such that j < k, j ≠ i, and k ≠ i.
function condlogodds(d::SteinMRF, y::Array{Float64,1}, i::Int64)
    logodds = 0.0
    for j = setdiff(1:d.p, i)
        for k = setdiff((j+1):d.p, i)
            logodds += d.logpsi[i,j,k,1+1,y[j]+1,y[k]+1]
            logodds -= d.logpsi[i,j,k,0+1,y[j]+1,y[k]+1]
        end
    end
    logodds
end

# Conditional distribution of jth coordinate of y, given all other coordinates,
# represented as a probability vector
function conddistribution(d::SteinMRF, y::Array{Float64,1}, j::Int64)
    p1 = exp(condlogodds(d, y, j))
    p1 = p1 / (1 + p1)
    [1-p1, p1]
end

function supportlowerbound(d::SteinMRF, j::Int64)
    0
end

function supportupperbound(d::SteinMRF, j::Int64)
    1
end

function numdimensions(d::SteinMRF)
    d.p
end


using GAIO
using StaticArrays
using Test

@testset "exported functionality" begin
    # a toy example with an easy attractor to test how well all the components
    # fit together
    f(x) = (x[1], x[2] * 0.5)
    test_points = [(-1.0, -1.0), (-1.0, 1.0), (1.0, -1.0), (1.0, 1.0)]
    center = (0.0, 0.0)
    radius = (1.0, 1.0)
    domain = Box(center, radius)
    g = PointDiscretizedMap(f, domain, test_points)
    partition = BoxPartition(domain)
    n = 10
    dims = (32, 32)
    partition_at_depth_n = BoxPartition(domain, dims)
    rga = relative_attractor(g, partition[:], steps = n)
    unstable = unstable_set!(g, partition_at_depth_n[:])
    # ground truths attractor and unstable set
    x_axis = [SVector(0, x) for x in range(-1, 1, length=100)]
    y_axis = [SVector(x, 0) for x in range(-1, 1, length=100)]
    gt_rga = partition_at_depth_n[y_axis]
    gt_unstable = partition_at_depth_n[x_axis]
    # make sure that the algorithms cover the ground truth (we won't have equality)
    @testset "relative attractor" begin
        @test length(gt_rga) > 0
        @test length(intersect(rga, gt_rga)) == length(gt_rga)
    end
    @testset "unstable set" begin
        @test length(gt_unstable) > 0
        @test length(intersect(unstable, gt_unstable)) == length(gt_unstable)
    end
end


@testset "StraightLineHomotopy" begin
    @polyvar x y z
    f = x + y + 2z
    g = x + z
    h = y + z

    @test StraightLineHomotopy(f, g) isa StraightLineHomotopy{Float64}
    @test StraightLineHomotopy([f, f], [g, h]) isa StraightLineHomotopy{Float64}
    @test StraightLineHomotopy{Float64}([f, g], [f, h]) isa StraightLineHomotopy{Float64}
    @test StraightLineHomotopy{Float64}(f, g) isa StraightLineHomotopy{Float64}
    @test StraightLineHomotopy([FP.Polynomial(f)], [FP.Polynomial{Complex128}(f)]) isa StraightLineHomotopy{Complex128}
    @test_throws AssertionError StraightLineHomotopy{Complex128}([f], [g])
    @test_throws AssertionError StraightLineHomotopy{Complex128}([f], [f, f])

    @test StraightLineHomotopy{BigFloat}(FP.Polynomial{Float64}.([f, f]), convert(Vector{FP.Polynomial{Float64}}, [g, h])) isa StraightLineHomotopy{BigFloat}

    H = StraightLineHomotopy{Float64}([f, g], [f, h])
    K = StraightLineHomotopy([f, g], [f, h])
    @test promote_type(typeof(H), typeof(K)) == StraightLineHomotopy{Float64}
    @test convert(StraightLineHomotopy{Float64}, K) isa StraightLineHomotopy{Float64}

    w = [1.0, 2.0, 2.0]
    @test evaluate(H, w, 1.0) == [7, 3]
    @test H(w, 1.0) == [7, 3]
    u = zeros(2)
    evaluate!(u, H, w, 1.0)
    @test u == [7, 3]

    cfg = PolynomialHomotopyConfig(H)

    @test evaluate(H, w, 1.0, cfg) == [7, 3]
    u = zeros(2)
    evaluate!(u, H, w, 1.0, cfg)
    @test u == [7, 3]

    @test promote_type(StraightLineHomotopy{Float64}, Complex128) == StraightLineHomotopy{Complex128}
    @test promote_type(StraightLineHomotopy{Float64}, StraightLineHomotopy{Complex128}) == StraightLineHomotopy{Complex128}

    @test PolynomialHomotopyConfig(H, rand(Complex128, 3)) isa PolynomialHomotopyConfig{Complex128}

    @test jacobian(H, rand(3), 0.0, cfg) == [1 1 2; 0 1 1]
    @test jacobian(H, rand(3), 1.0, cfg) == [1 1 2; 1 0 1]
    u = zeros(2, 3)
    @test jacobian!(u, H, rand(3), 0.0, cfg) == [1 1 2; 0 1 1]
    @test u == [1 1 2; 0 1 1]

    r = JacobianDiffResult(cfg)
    w = rand(3)
    jacobian!(r, H, w, 0.24, cfg)
    @test value(r) == H(w, 0.24)
    @test jacobian(r) == jacobian(H, w, 0.24, cfg)

    @test dt(H, [1.0, 2, 2], 1.0, cfg) == [0, -1]
    u = zeros(2)
    dt!(u, H, [1, 2, 2.0], 1.0, cfg)
    @test u == [0, -1]
    @test string(H) == "Homotopies.StraightLineHomotopy{Float64} with 2 polynomials in 3 variables.\n"

    r = DtDiffResult(cfg)
    w = rand(3)
    dt!(r, H, w, 0.24, cfg)
    @test value(r) == H(w, 0.24)
    @test dt(r) == dt(H, w, 0.24, cfg)

    N = weylnorm(H)
    @test N(0.0)==sqrt(8)
    @test N(0.5)==sqrt(7.5)

    H = StraightLineHomotopy(x^2+y+z, z^2+2+x+y^2)
    @test ishomogenous(H) == false
    @test ishomogenized(H) == false
    HH = homogenize(H)
    @test ishomogenous(HH)
    @test ishomogenized(HH)
    @test H != HH
    @test HH == homogenize(HH)
    @test isequal(HH, homogenize(HH))
    @test dehomogenize(HH) == H

    @test length(H) == 1
    @test nvariables(H) == 3
end


__precompile__()

module QuantEcon

import Base: mean, std, var, show, isapprox

# 3rd party
using Distributions
import Distributions: pdf, skewness, BetaBinomial
using DSP: TFFilter, freqz
using Primes: primes
using Compat: view, @compat
using StatsBase:ecdf

@static if isdefined(Base, :Iterators)
    using Base.Iterators: cycle, take
end

# useful types
ScalarOrArray{T} = Union{T,Array{T}}

export
# arma
    ARMA,
    spectral_density, autocovariance, impulse_response, simulation,

# ecdf
    ecdf,

# lqcontrol
    LQ,
    update_values!,
    stationary_values!, stationary_values,
    compute_sequence,

# compute_fp
    compute_fixed_point,

# matrix_eqn
    solve_discrete_lyapunov,
    solve_discrete_riccati,

# discrete_rv
    DiscreteRV,
    draw,

# mc_tools
    MarkovChain, MCIndSimulator, MCSimulator,
    stationary_distributions,
    simulate, simulate!, simulate_indices, simulate_indices!,
    period, is_irreducible, is_aperiodic, recurrent_classes,
    communication_classes, n_states,
    discrete_var, Even, Quantile, Quadrature,

# modeltools
    AbstractUtility, LogUtility, CRRAUtility, CFEUtility, EllipticalUtility, derivative,

# gth_solve
    gth_solve,

# markov_approx
    tauchen,
    rouwenhorst,
    estimate_mc_discrete,

# lae
    LAE,
    lae_est,

# lqnash
    nnash,

# lss
    LSS,
    simulate,
    replicate,
    moment_sequence,
    stationary_distributions,
    geometric_sums,
    is_stable,
    remove_constants,

# kalman
    Kalman,
    set_state!,
    prior_to_filtered!,
    filtered_to_forecast!,
    update!,
    stationary_values,

# distributions
    BetaBinomial,
    pdf, mean, std, var, skewness,

# estspec
    smooth, periodogram, ar_periodogram,

# util
    meshgrid, gridmake, gridmake!, ckron, is_stable, num_compositions, simplex_grid, simplex_index,

# robustlq
    RBLQ,
    d_operator, b_operator,
    robust_rule, robust_rule_simple,
    F_to_K, K_to_F,
    compute_deterministic_entropy,
    evaluate_F,

# quad
    qnwlege, qnwcheb, qnwsimp, qnwtrap, qnwbeta, qnwgamma, qnwequi, qnwnorm,
    qnwunif, qnwlogn, qnwmonomial1, qnwmonomial2,
    quadrect,
    do_quad,

# quadsums
    var_quadratic_sum,
    m_quadratic_sum,

# random_mc
    random_markov_chain, random_stochastic_matrix, random_discrete_dp,

# ddp
    DiscreteDP, VFI, PFI, MPFI, solve, RQ_sigma,
    evaluate_policy, bellman_operator, compute_greedy,
    bellman_operator!, compute_greedy!, num_states,

# zeros / optimization
    bisect, brenth, brent, ridder, expand_bracket, divide_bracket,
    golden_method,

# interp
    interp, LinInterp,

# sampler
    MVNSampler,

# modeltools
    @def_sim

include("sampler.jl")
include("util.jl")
include("interp.jl")
include("arma.jl")
include("compute_fp.jl")
include("markov/markov_approx.jl")
include("markov/mc_tools.jl")
include("markov/ddp.jl")
include("markov/random_mc.jl")
include("discrete_rv.jl")
include("ecdf.jl")
include("estspec.jl")
include("kalman.jl")
include("lae.jl")
include("lqcontrol.jl")
include("lqnash.jl")
include("lss.jl")
include("matrix_eqn.jl")
include("robustlq.jl")
include("quad.jl")
include("quadsums.jl")
include("zeros.jl")
include("optimization.jl")
include("modeltools/utility.jl")
include("modeltools/types.jl")

end # module


@testset "SkewT" begin
  @testset "scalefromaltskewt, skewfromaltskewt" begin
    skew = -3
    scale = 2
    altskew = toaltskew(scale, skew)
    altscale = toaltscale(scale, skew)

    @test isapprox(scalefromaltskewt(altscale, altskew), scale)
    @test isapprox(skewfromaltskewt(altscale, altskew), skew)
  end

  @testset "Inference" begin
    Random.seed!(0)
    st = SkewT(2, 1, 7, -10)
    y = rand(st, 1000)
    model = MixSkewT(y, 1)
    print_model_info(model)
    nsamps, nburn, thin = (2000, 2000, 2)
    callback = make_callback(model, nsamps=nsamps, nburn=nburn, thin=thin)
    spl = make_sampler(model)
    @time chain, metrics = mcmc(spl, nsamps, nburn=nburn, thin=thin, callback=callback)

    # Tests
    @test length(unique(chain)) == nsamps
    @test isapprox(st.loc, mean(getindex.(chain, :mu))[1], atol=0.1)
    @test isapprox(st.scale, mean(getindex.(chain, :sigma))[1], atol=0.1)
    @test isapprox(st.df, mean(getindex.(chain, :nu))[1], atol=2)
    @test isapprox(st.skew, mean(getindex.(chain, :phi))[1], atol=3)
  end
end


# test_maps.jl

function suitable_point_to_map(m)
    T = domaintype(m)
    if T <: SVector
        randvec(eltype(T),length(T))
    else
        x = T(rand())
    end
end

suitable_point_to_map(m::DomainSets.EmbeddingMap{S,T}) where {S,T} = one(S)

function suitable_point_to_map(m::DomainSets.EmbeddingMap{SVector{N,S},T}) where {S,T,N}
    x = @SVector ones(N)
end

function suitable_point_to_map(m::DomainSets.ProductMap)
    x = ()
    for map in elements(m)
        x = (x..., suitable_point_to_map(map))
    end
    x
end

suitable_point_to_map(::CartToPolarMap{T}) where {T} = randvec(T,2)
suitable_point_to_map(::PolarToCartMap{T}) where {T} = randvec(T,2)
# Test a map m with dimensions n
function test_generic_map(T, m)
    # Try to map a random vector
    isreal(T(0)) && (@test isreal(m))
    x = suitable_point_to_map(m)
    y1 = applymap(m, x)
    y2 = m * x
    @test y1 == y2
    y3 = m(x)
    @test y1 == y3

    mi = inv(m)
    xi1 = applymap(mi, y1)
    @test xi1 ≈ x
    xi2 = mi * y1
    @test xi2 ≈ x
    xi3 = m\y1
    @test xi3 ≈ x
    xi4 = apply_inverse(m, y1)
    @test xi4 ≈ x

    # if is_linear(m)
    #     x = suitable_point_to_map(m, n)
    #     y1 = applymap(m, x)
    #     x0 = @SVector zeros(T,n)
    #     a,b = linearize(m, x0)
    #     y2 = a*x+b
    #     @test y1 ≈ y2
    # end
end

randvec(T,n) = SVector{n,T}(rand(n))
randvec(T,m,n) = SMatrix{m,n,T}(rand(m,n))

function test_maps(T)
    a = T(0)
    b = T(1)
    c = T(2)
    d = T(3)
    m = interval_map(a, b, c, d)
    @test m(a) ≈ c
    @test m(b) ≈ d

    test_generic_map(T, m)

    m2 = AffineMap(randvec(T, 2, 2), randvec(T, 2))
    test_generic_map(T, m2)

    m3 = LinearMap(randvec(T, 2, 2))
    test_generic_map(T, m3)
    r = randvec(T,2,1)
    @test jacobian(m3, r)*r ≈ m3*r

    # Test an affine map with a a scalar and b a vector
    m4 = AffineMap(T(1.2), randvec(T, 2))
    test_generic_map(T, m4)

    m5 = AffineMap(randvec(T, 3, 3), randvec(T, 3))
    test_generic_map(T, m5)

    m6 = m3∘m4
    @test typeof(m6) <: AffineMap
    test_generic_map(T, m6)

    # Test special maps
    test_composite_map(T)

    test_product_map(T)

    test_scaling_maps(T)

    test_identity_map(T)

    test_rotation_map(T)

    test_translation_map(T)

    test_cart_polar_map(T)

    # Diagonal maps

end

function test_scaling_maps(T)
    test_generic_map(T, scaling_map(T(2)))

    test_generic_map(T, scaling_map(T(2)))
    test_generic_map(T, scaling_map(T(2), T(3)))
    test_generic_map(T, scaling_map(T(2), T(3), T(4)))
    test_generic_map(T, scaling_map(T(2), T(3), T(4), T(5)))
end

function test_identity_map(T)
    test_generic_map(T, IdentityMap{T}())
    test_generic_map(T, IdentityMap{SVector{2,T}}())
    @test islinear(IdentityMap{T}())
end

function test_rotation_map(T)
    ϕ = T(pi)/4
    m = rotation_map(ϕ)
    x = [one(T), zero(T)]
    y = m*x
    @test y[1] ≈ sqrt(T(2))/2
    @test y[2] ≈ sqrt(T(2))/2

    ϕ = T(pi)/4
    m = rotation_map(ϕ, 0, 0)
    x = [zero(T), one(T), zero(T)]
    y = m*x
    @test y[1] ≈ 0
    @test y[2] ≈ sqrt(T(2))/2
    @test y[3] ≈ sqrt(T(2))/2

    # TODO: add more tests for a 3D rotation

    theta = T(rand())
    phi = T(rand())
    psi = T(rand())
    m2 = rotation_map(theta)
    test_generic_map(T, m2)
    m3 = rotation_map(phi, theta, psi)
    test_generic_map(T, m3)

    r = suitable_point_to_map(m2)
    @test norm(m2*r)≈norm(r)

    r = suitable_point_to_map(m3)
    @test norm(m3*r)≈norm(r)
    @test islinear(m3)
end

function test_translation_map(T)
    v = randvec(T,3)
    m = translation_map(v)
    test_generic_map(T, m)
    @test islinear(m)
end

function test_cart_polar_map(T)
    m1 = CartToPolarMap{T}()
    test_generic_map(T, m1)
    @test !islinear(m1)

    m2 = PolarToCartMap{T}()
    test_generic_map(T, m2)
    @test !islinear(m2)
end

function test_embedding_map(T1, T2)
    x = nonzero_element(T1)
    m = embedding_map(T1, T2)

    x = suitable_point_to_map(m)
    y1 = applymap(m, x)
    y2 = m * x
    @test y1 == y2
    y3 = m(x)
    @test y1 == y3

    @test applymap(m, x) == convert_space(spacetype(T2), x)
    m2 = restriction_map(T2, T1)
    y = nonzero_element(T2)
    @test applymap(m2, y) == restrict_space(spacetype(T1), y)
end

function test_isomorphism_map(T1, T2)
    test_embedding_map(T1, T2)
    test_embedding_map(T2, T1)
    @test inv(isomorphism_map(T1,T2)) == isomorphism_map(T2, T1)
    x = nonzero_element(T1)
    m = isomorphism_map(T1, T2)
    y = applymap(m, x)
    @test apply_inverse(m, y) == x
    m2 = isomorphism_map(T2, T1)
    y2 = nonzero_element(T2)
    x2 = applymap(m2, y2)
    @test apply_inverse(m2, x2) == y2
end

function test_composite_map(T)
    a = T(0)
    b = T(1)
    c = T(2)
    d = T(3)
    ma = IdentityMap{T}()
    mb = interval_map(a, b, c, d)

    r = suitable_point_to_map(ma)
    m1 = ma∘mb
    test_generic_map(T, m1)
    @test m1(r) ≈ ma(mb(r))
    m2 = m1∘mb
    test_generic_map(T, m2)
    @test m2(r) ≈ m1(mb(r))
    m3 = mb∘m2
    test_generic_map(T, m3)
    @test m3(r) ≈ mb(m2(r))
    m = m2∘m3
    test_generic_map(T, m)
    @test m(r) ≈ m2(m3(r))

    @test !(typeof(CompositeMap(ma)) <: CompositeMap)
end

function test_product_map(T)
    a = T(0)
    b = T(1)
    c = T(2)
    d = T(3)
    ma = IdentityMap{T}()
    mb = interval_map(a, b, c, d)

    r1 = suitable_point_to_map(ma)
    r2 = suitable_point_to_map(ma)
    r3 = suitable_point_to_map(ma)
    r4 = suitable_point_to_map(ma)
    r5 = suitable_point_to_map(ma)

    m1 = tensorproduct(ma,mb)
    test_generic_map(T, m1)
    @test compare_tuple(m1((r1,r2)), (ma(r1),mb(r2)))
    m2 = tensorproduct(m1,mb)
    test_generic_map(T, m2)
    @test compare_tuple(m2((r1,r2,r3)), (ma(r1),mb(r2),mb(r3)) )
    m3 = tensorproduct(mb,m2)
    test_generic_map(T, m3)
    @test compare_tuple(m3((r1,r2,r3,r4)),(mb(r1),ma(r2),mb(r3),mb(r4)))
    m = tensorproduct(m1,m2)
    test_generic_map(T, m)
    @test compare_tuple(m((r1,r2,r3,r4,r5)),(m1((r1,r2))...,m2((r3,r4,r5))...))
end

Base.isapprox(a::NTuple{L,SVector{N,T}}, b::NTuple{L,SVector{N,T}}) where {L,N,T} = compare_tuple(a,b)
Base.isapprox(a::NTuple{L,T}, b::NTuple{L,T}) where {L,T} = compare_tuple(a,b)

# Compare two iterable sequences for element-wise equality
compare_tuple(a, b) = reduce(&, map(isapprox, a, b))

@testset "maps" begin
    test_maps(Float64)
    test_maps(BigFloat)

    @testset "embedding map" begin
        T1 = Float64
        T2 = Complex{Float64}
        T3 = SVector{1,Float64}
        T4 = SVector{2,Float64}
        T5 = SVector{2,Complex{Float64}}

        test_embedding_map(T1, T2)
        test_embedding_map(T3, T4)
        test_isomorphism_map(T2, T4)
    end
end