"""
    SeisRadon_tx(In,Par,itype);
RADON_FX: Operators for f-x, tau-p forward and adjoint Radon transforms
          to compute linear or  parabolic Radon transform.
IN    Radon coefficients if itype =  1 (Forward transform)  with In(np,nt)
      CMP gather         if itype = -1 (Adjoint transform)  with In(nh,nt)

OUT   CMP gather         if itype =  1 (Forward transform)  with Out(nh,nt)
      Radon coeffcients  if itype = -1 (Adjoint transform)  with Out(np,nt)
# Parameters
- `Par.h`:vector containing the nh offsets
- `Par.p`:vector containing the np curvatures normalized by far offset (s) if itype='parab'
- `Par.p`:vector containing the np dips in s/m if itype='linear'
- `Par.dt`:sampling interval
- `Par.f`:frequency corners of BP operator - this acts like a zero phase wavelet.
- `Par.transf `:'parab', 'linear', 'hyperb'
itype =  1  :  forward
itype = -1  :  adjoint
"""
function SeisRadon_tx(In,Par,itype);
    h = Par[1][:h];
    p = Par[2][:p];
    dt = Par[3][:dt];
    f  = Par[4][:f];
    f1 = f[1];
    f2 = f[2];
    f3 = f[3];
    f4 = f[4];
    np = length(p);
    nh = length(h);
    if itype == 1;
       m = In;
       (nt, np) = size(m);
       d = zeros(nt,nh);
       m = SeisBPFilter(m,dt,f1,f2,f3,f4);
    end;
    if itype == -1;
       d = In;
       (nt, nh) = size(d);
       m = zeros(nt,np);
    end;
   tmax = (nt-1)*dt;
   tau = collect(0:1:nt-1)*dt;

     for itau = 1:1:nt
       for ih = 1:nh
        for ip = 1:np
           if cmp(Par[5][:transf],"parab")==0 ;
            hmax = maximum(abs.(h));
            t=tau[itau]+ (h[ih]/hmax)^2*p[ip];
         elseif cmp(Par[5][:transf],"linear")==0;
            t=tau[itau] + (h[ih]*p[ip]);
         elseif cmp(Par[5][:transf],"hyperb")==0;
            t=sqrt(tau[itau]^2 + (h[ih]^2/p[ip]^2));
         end
          if t>0 && t<tmax
            it    = t/dt+1;
            it1   = floor(Int,it);
            it2   = it1 + 1;
            alpha = it  - it1;
               if itype == 1;
                    d[it1,ih] = d[it1,ih]+ (1-alpha) * m[itau,ip];
                    d[it2,ih] = d[it2,ih] +     alpha * m[itau,ip];
                 else
                    m[itau,ip] = m[itau,ip] + (1-alpha) * d[it1,ih] + alpha*d[it2,ih];

          end
        end
       end
      end
   end
   if itype == 1;
      Out = d;
   end;

   if itype == -1;
      m = SeisBPFilter(m,dt,f1,f2,f3,f4);
      Out = m;
   end;
   return Out
end


##############################################################################
##
## Solution method: coordinate gradient descent
## (also called alternative regressions, Gauss Seidel)
##
##############################################################################

##############################################################################
##
## Factor Model (no regressors)
##
##############################################################################

function fit!(::Type{Val{:gauss_seidel}},
    fp::FactorModel,
    fs::FactorSolution;
    maxiter::Integer  = 100_000,
    tol::Real = 1e-9,
    lambda::Real = 0.0
    )
    lambda == 0.0 || error("The alternative regression method only works with lambda = 0.0")

    # initialize
    iter = 0
    converged = true

    fp = FactorModel(deepcopy(fp.y), fp.sqrtw, fp.idrefs, fp.timerefs, rank(fp))
    idscale = Array(Float64, size(fs.idpool, 1))
    timescale = Array(Float64, size(fs.timepool, 1))

    for r in 1:rank(fp)
        fsr = view(fs, :, r)
        oldf_x = ssr(fp, fsr)
        iter_inner = 0
        while iter_inner < maxiter
            iter_inner += 1
            update!(Val{:gauss_seidel}, fp.y, fp.sqrtw, fp.idrefs, fp.timerefs, fsr.idpool,
                idscale, fsr.timepool)
            update!(Val{:gauss_seidel}, fp.y, fp.sqrtw, fp.timerefs, fp.idrefs, fsr.timepool,
                timescale, fsr.idpool)
            f_x = ssr(fp, fsr)
            if abs(f_x - oldf_x) < (abs(f_x) + tol) * tol
                break
            else
                oldf_x = f_x
            end
        end
        iter = max(iter_inner, iter)
        converged = (iter_inner < maxiter) && converged
        if r < rank(fp)
            rescale!(fsr)
            subtract_factor!(fp, fsr)
        end
    end
    return rescale(fs), iter, converged
end

##############################################################################
##
## Interactive FixedEffectModel
##
##############################################################################

function fit!(::Type{Val{:gauss_seidel}},
    fp::InteractiveFixedEffectsModel,
    fs::InteractiveFixedEffectsSolution;
    maxiter::Integer = 100_000,
    tol::Real = 1e-9,
    lambda::Real = 0.0)
    lambda == 0.0 || error("The alternative regression method only works with lambda = 0.0")

    N = size(fs.idpool, 1)
    T = size(fs.timepool, 1)

    #qr fact factorization cannot divide in place for now
    crossx = cholfact!(At_mul_B(fp.X, fp.X))
    M = crossx \ fp.X'

    yoriginal = deepcopy(fp.y)
    fp = InteractiveFixedEffectsModel(deepcopy(fp.y), fp.sqrtw, fp.X, fp.idrefs, fp.timerefs, rank(fp))

    idscale = Array(Float64, size(fs.idpool, 1))
    timescale = Array(Float64, size(fs.timepool, 1))

    # starts loop
    converged = false
    iterations = maxiter
    iter = 0
    f_x = Inf
    oldf_x = Inf
    while iter < maxiter
        iter += 1
        (f_x, oldf_x) = (oldf_x, f_x)

        # Given beta, compute incrementally an approximate factor model
        copy!(fp.y, yoriginal)
        BLAS.gemm!('N', 'N', -1.0, fp.X, fs.b, 1.0, fp.y)
        for r in 1:rank(fp)
            fsr = view(fs, :, r)
            update!(Val{:gauss_seidel}, fp.y, fp.sqrtw, fp.idrefs, fp.timerefs, fsr.idpool, idscale, fsr.timepool)
            update!(Val{:gauss_seidel}, fp.y, fp.sqrtw, fp.timerefs, fp.idrefs, fsr.timepool, timescale, fsr.idpool)
            subtract_factor!(fp, fsr)
        end

        # Given factor model, compute beta
        copy!(fp.y, yoriginal)
        subtract_factor!(fp, fs)
        ## corresponds to Gauss Niedel with acceleration
        scale!(fs.b, -0.5)
        BLAS.gemm!('N', 'N', 1.5, M, fp.y, 1.0, fs.b)

        # Check convergence
        BLAS.gemm!('N', 'N', -1.0, fp.X, fs.b, 1.0, fp.y)
        f_x = sumabs2(fp.y)
        if abs(f_x - oldf_x) < (abs(f_x) + tol) * tol
            converged = true
            iterations = iter
            break
        end
    end
    return rescale(fs), iterations, converged
end

##############################################################################
##
## update! by alternative regressions
##
##############################################################################

function update!{R1, R2}(::Type{Val{:gauss_seidel}},
    y::Vector{Float64},
    sqrtw::AbstractVector{Float64},
    p1refs::Vector{R1},
    p2refs::Vector{R2},
    p1::AbstractVector{Float64},
    p1scale::Vector{Float64},
    p2::AbstractVector{Float64}
    )
    fill!(p1, zero(Float64))
    fill!(p1scale, zero(Float64))
    @inbounds @simd for i in 1:length(y)
        p1i = p1refs[i]
        xi = sqrtw[i] * p2[p2refs[i]]
        p1[p1i] += xi * y[i]
        p1scale[p1i] += abs2(xi)
    end
    @inbounds @simd for i in 1:length(p1)
        s = p1scale[i]
        if s > 0
            p1[i] /= s
        end
    end
end

## compute sum of squared residuals
function ssr(fm::FactorModel, fs::FactorSolution{1})
    out = zero(Float64)
    @inbounds @simd for i in 1:length(fm.y)
        out += abs2(fm.y[i] - fm.sqrtw[i] * fs.idpool[fm.idrefs[i]] * fs.timepool[fm.timerefs[i]])
    end
    return out
end


using FourierTools, BenchmarkTools

function main()
    x = randn((133, 513, 33))
    y = copy(x)
    @btime $y .= real.(ifft(fft($x)));
    @btime $y .= real.(ifft(ifftshift(fftshift(fft($x)))));
    @btime $y .= real.(iffts(ffts($x)));
    @btime $y .= real.(ift(ft($x)));

    return
end

main()


## Environment and packages
using Statistics
using LinearAlgebra
using Random
using OrdinaryDiffEq
using DiffEqFlux
using Flux
using Tullio
using RecursiveArrayTools

const t₁ = 5                 # number of simulation years
const ρ = 900                     # Ice density [kg / m^3]
const g = 9.81                    # Gravitational acceleration [m / s^2]
const n = 3                      # Glen's flow law exponent
const maxA = 8e-16
const minA = 3e-17
const maxT = 1
const minT = -25

@views avg(A) = 0.25 .* ( A[1:end-1,1:end-1] .+ A[2:end,1:end-1] .+ A[1:end-1,2:end] .+ A[2:end,2:end] )
@views avg_x(A) = 0.5 .* ( A[1:end-1,:] .+ A[2:end,:] )
@views avg_y(A) = 0.5 .* ( A[:,1:end-1] .+ A[:,2:end] )
@views diff_x(A) = (A[begin + 1:end, :] .- A[1:end - 1, :])
@views diff_y(A) = (A[:, begin + 1:end] .- A[:, 1:end - 1])
@views inn(A) = A[2:end-1,2:end-1]

function loss_iceflow(θ, context, UA)
    H = context[2]
    tspan = (0.0,t₁)

    iceflow_UDE!(dH, H, θ, t) = iceflow_NN!(dH, H, θ, t, context, temp_series[5], UA) # closure
    iceflow_prob = ODEProblem(iceflow_UDE!,H,tspan,θ)
    H_pred = solve(iceflow_prob, BS3(), u0=H, p=θ, reltol=1e-6,
                    sensealg = InterpolatingAdjoint(autojacvec=ZygoteVJP()), save_everystep=false,
                    progress=true, progress_steps = 10)

    # Compute loss function for the full batch
    l_H = sum(H_pred.u[end])

    return l_H
end

function iceflow_NN!(dH, H, θ, t, context, temps, UA)
    # ArrayPartition(B, H, current_year, temp_series, batch_idx)

    year = floor(Int, t) + 1
    if year <= t₁
        temp = temps[year]
    else
        temp = temps[year-1]
    end

    A = predict_A̅(UA, θ, [temp]) # FastChain prediction requires explicit parameters
    # Compute the Shallow Ice Approximation in a staggered grid
    dH .= SIA(dH, H, A, context)
end

# Function without mutation for Zygote, with context as an ArrayPartition
function SIA(dH, H, A, context)
    # Retrieve parameters
    B = context[1]
    # Update glacier surface altimetry
    S = B .+ H

    # All grid variables computed in a staggered grid
    # Compute surface gradients on edges
    dSdx = diff_x(S) / Δx
    dSdy = diff_y(S) / Δy
    ∇S = (avg_y(dSdx).^2 .+ avg_x(dSdy).^2).^((n - 1)/2)

    Γ = 2 * A * (ρ * g)^n / (n+2) # 1 / m^3 s
    D = Γ .* avg(H).^(n + 2) .* ∇S

    # Compute flux components
    dSdx_edges = diff_x(S[:,2:end - 1]) / Δx
    dSdy_edges = diff_y(S[2:end - 1,:]) / Δy
    Fx = .-avg_y(D) .* dSdx_edges
    Fy = .-avg_x(D) .* dSdy_edges

    #  Flux divergence
    @tullio dH[i,j] := -(diff_x(Fx)[pad(i-1,1,1),pad(j-1,1,1)] / Δx + diff_y(Fy)[pad(i-1,1,1),pad(j-1,1,1)] / Δy) # MB to be added here

    return dH
end

predict_A̅(UA, θ, temp) = UA(temp, θ) .* 1e-16

function fake_temp_series(t, means=Array{Float64}([0,-2.0,-3.0,-5.0,-10.0,-12.0,-14.0,-15.0,-20.0]))
    temps, norm_temps, norm_temps_flat = [],[],[]
    for mean in means
       push!(temps, mean .+ rand(t).*1e-1) # static
    end

    return temps
end

##################################################
#### Generate reference dataset ####
##################################################

nx = ny = 100
const B = zeros(Float64, (nx, ny))
const σ = 1000
H₀ = Matrix{Float64}([ 250 * exp( - ( (i - nx/2)^2 + (j - ny/2)^2 ) / σ ) for i in 1:nx, j in 1:ny ])
Δx = Δy = 50 #m
ensemble = EnsembleSerial()
const minA_out = 0.3
const maxA_out = 8
sigmoid_A(x) = minA_out + (maxA_out - minA_out) / ( 1 + exp(-x) )

const temp_series = fake_temp_series(t₁)

# Train UDE
UA = FastChain(
        FastDense(1,3, x->tanh.(x)),
        FastDense(3,10, x->tanh.(x)),
        FastDense(10,3, x->tanh.(x)),
        FastDense(3,1, sigmoid_A)
    )
θ = initial_params(UA)

H = deepcopy(H₀)
current_year = 0f0
# Tuple with all the temp series and H_refs
context = (B, H, current_year, temp_series)
loss(θ) = loss_iceflow(θ, context, UA) # closure

# Debugging
println("Gradients: ", gradient(loss, θ))


"""
A two dimensional nonlinear discrete time (DT) system.
"""

Base.@kwdef struct TwoDimensionalNonlinearDTSystem <: AbstractEnv
    c = 0.5
    d = 1.0
end

function State(env::TwoDimensionalNonlinearDTSystem)
    return function (x1, x2)
        ComponentArray(x1=x1, x2=x2)
    end
end

function V1(env::TwoDimensionalNonlinearDTSystem)
    return function (x)
        if x > 0
            ans = x^(1/4)
        else
            ans = x^4
        end
        ans
    end
end

function Dynamics!(env::TwoDimensionalNonlinearDTSystem)
    @unpack c, d = env
    @Loggable function dynamics!(dx, x, p, t; u)
        @unpack x1, x2 = x
        @log state = x
        @log input = u
        dx.x1 = c*(x1 + x2)  # x1_next = dx.x1
        dx.x2 = c*x2 + u[1]  # x2_next = dx.x2
        @onlylog next_state = dx
    end
end

function CubicSolution(env::TwoDimensionalNonlinearDTSystem)
    @unpack c, d = env
    return function (x2)
        a = (
             (cbrt(sqrt(3)*sqrt(27*c^2*x2^2 + 8) + 9*c*x2) / 3^(2/3))
             - (2 / (cbrt(3) * cbrt(sqrt(3)*sqrt(27*c^2*x2^2 + 8) + 9*c*x2)))
            )  # solve a^3 + 2a - 2*c*x2 for a ∈ ℝ
    end
end

function RunningCost(env::TwoDimensionalNonlinearDTSystem)
    @unpack c, d = env
    return function (x, u)
        @unpack x1, x2 = x
        dx = copy(x)  # will be next state
        Dynamics!(env)(dx, x, nothing, nothing; u=u)
        a = CubicSolution(env)(x2)
        r̄ = (
             (3/4)*a^4 + a^2
             - d*V1(env)(dx.x1) + d*V1(env)(x1)
             + (1-c^2)*x2^2
            )  # for V(x) = d*sqrt(abs(x1)) + x2^2
        r = r̄ + (1/4)*u[1]^4
    end
end

function OptimalValue(env::TwoDimensionalNonlinearDTSystem)
    @unpack c, d = env
    return function (x)
        @unpack x1, x2 = x
        d*V1(env)(x1) + x2^2
    end
end

function OptimalQValue(env::TwoDimensionalNonlinearDTSystem)
    return function (x, u)
        dx = copy(x)
        Dynamics!(env)(dx, x, nothing, nothing; u=u)
        OptimalValue(env)(dx) + RunningCost(env)(x, u)  # V(x_next) + r(x, u)
    end
end

function OptimalControl(env::TwoDimensionalNonlinearDTSystem)
    return function (x)
        @unpack x2 = x
        a = CubicSolution(env)(x2)
        -a
    end
end


using Statistics

function fastPooling!(
    cLayer::OneD,
    Ai::AbstractArray{T,3},
) where {OneD<:Union{MaxPool1D,AveragePool1D},T}

    Aip = padding(cLayer, Ai)
    n_Hi, c, m = size(Aip)
    s_H = cLayer.s
    f_H = cLayer.f
    n_H = (n_Hi - f_H) ÷ s_H + 1

    if cLayer isa MaxPoolLayer
        cLayer.A = reshape(
            maximum(reshape(Aip, f_H, n_H, c, m), dims = 1),
            n_H,
            c,
            m,
        )
    else
        cLayer.A =
            reshape(mean(reshape(Aip, f_H, n_H, c, m), dims = 1), n_H, c, m)
    end #if cLayer isa MaxPoolLayer

    return nothing
end #function fastPooling!(cLayer::OneD

function fastPooling!(
    cLayer::TwoD,
    Ai::AbstractArray{T,4},
) where {TwoD<:Union{MaxPool2D,AveragePool2D},T}

    Aip = padding(cLayer, Ai)
    n_Hi, n_Wi, c, m = size(Aip)
    s_H, s_W = cLayer.s
    f_H, f_W = cLayer.f
    n_H = (n_Hi - f_H) ÷ s_H + 1
    n_W = (n_Wi - f_W) ÷ s_W + 1

    if cLayer isa MaxPoolLayer
        cLayer.A = reshape(
            maximum(
                permutedims(
                    reshape(Aip, f_H, n_H, f_W, n_W, c, m),
                    [1, 3, 2, 4, 5, 6],
                ),
                dims = 1:2,
            ),
            n_H,
            n_W,
            c,
            m,
        )
    else
        cLayer.A = reshape(
            mean(
                permutedims(
                    reshape(Aip, f_H, n_H, f_W, n_W, c, m),
                    [1, 3, 2, 4, 5, 6],
                ),
                dims = 1:2,
            ),
            n_H,
            n_W,
            c,
            m,
        )
    end #if cLayer isa MaxPoolLayer

    return nothing
end #function fastPooling!(cLayer::TwoD,

function fastPooling!(
    cLayer::ThreeD,
    Ai::AbstractArray{T,5},
) where {ThreeD<:Union{MaxPool3D,AveragePool3D},T}

    Aip = padding(cLayer, Ai)
    n_Hi, n_Wi, n_Di, c, m = size(Aip)
    s_H, s_W, s_D = cLayer.s
    f_H, f_W, f_D = cLayer.f

    n_H = (n_Hi - f_H) ÷ s_H + 1
    n_W = (n_Wi - f_W) ÷ s_W + 1
    n_D = (n_Di - f_D) ÷ s_D + 1

    if cLayer isa MaxPoolLayer
        cLayer.A = reshape(
            maximum(
                permutedims(
                    reshape(Aip, f_H, n_H, f_W, n_W, f_D, n_D, c, m),
                    [1, 3, 5, 2, 4, 6, 7, 8],
                ),
                dims = 1:3,
            ),
            n_H,
            n_W,
            n_D,
            c,
            m,
        )
    else
        cLayer.A = reshape(
            mean(
                permutedims(
                    reshape(Aip, f_H, n_H, f_W, n_W, f_D, n_D, c, m),
                    [1, 3, 5, 2, 4, 6, 7, 8],
                ),
                dims = 1:3,
            ),
            n_H,
            n_W,
            n_D,
            c,
            m,
        )
    end #if cLayer isa MaxPoolLayer

    return nothing
end #function fastPooling!(cLayer::ThreeD,

export fastPooling!

### dfastPooling

function dfastPooling!(
    cLayer::OneD,
    Ai::AbstractArray{T,3},
    dAi::AbstractArray{T,3},
    Ao::AbstractArray{T,3},
    dAo::AbstractArray{T,3},
) where {OneD<:Union{MaxPool1D,AveragePool1D},T}

    padS = paddingSize(cLayer, Ai)
    Aip = padding(cLayer, Ai)
    n_Hi, c, m = size(Aip)
    s_H = cLayer.s
    f_H = cLayer.f
    n_H = (n_Hi - f_H) ÷ s_H + 1

    Aipr = reshape(Aip, f_H, n_H, c, m)
    if cLayer isa MaxPoolLayer
        mask = Aipr .== reshape(Ao, 1, n_H, c, m)
    else
        mask = similar(Aipr) .= 1 / prod((cLayer.f))
    end #if cLayer isa MaxPoolLayer
    dAi .= reshape(reshape(dAo, 1, n_H, c, m) .* mask, n_Hi, c, m)[1+padS[1]:end-padS[2], :, :]

    mask = nothing
    return nothing
end #function dfastPooling!(cLayer::OneD

function dfastPooling!(
    cLayer::TwoD,
    Ai::AbstractArray{T,4},
    dAi::AbstractArray{T,4},
    Ao::AbstractArray{T,4},
    dAo::AbstractArray{T,4},
) where {TwoD<:Union{MaxPool2D,AveragePool2D},T}

    padS = paddingSize(cLayer, Ai)
    Aip = padding(cLayer, Ai)
    n_Hi, n_Wi, c, m = size(Aip)
    s_H, s_W = cLayer.s
    f_H, f_W = cLayer.f
    n_H = (n_Hi - f_H) ÷ s_H + 1
    n_W = (n_Wi - f_W) ÷ s_W + 1

    Aipr =
        permutedims(reshape(Aip, f_H, n_H, f_W, n_W, c, m), [1, 3, 2, 4, 5, 6])

    if cLayer isa MaxPoolLayer
        mask = Aipr .== reshape(Ao, 1, 1, n_H, n_W, c, m)
    else
        mask = similar(Aipr) .= 1 / prod((cLayer.f))
    end
    dAi .= reshape(
        permutedims(
            reshape(dAo, 1, 1, n_H, n_W, c, m) .* mask,
            [1, 3, 2, 4, 5, 6],
        ),
        n_Hi,
        n_Wi,
        c,
        m,
    )[1+padS[1]:end-padS[2], 1+padS[3]:end-padS[4], :, :]

    mask = nothing
    return nothing
end #function dfastPooling!(cLayer::TwoD,

function dfastPooling!(
    cLayer::ThreeD,
    Ai::AbstractArray{T,5},
    dAi::AbstractArray{T,5},
    Ao::AbstractArray{T,5},
    dAo::AbstractArray{T,5},
) where {ThreeD<:Union{MaxPool3D,AveragePool3D},T}

    padS = paddingSize(cLayer, Ai)
    Aip = padding(cLayer, Ai)
    n_Hi, n_Wi, n_Di, c, m = size(Aip)
    s_H, s_W, s_D = cLayer.s
    f_H, f_W, f_D = cLayer.f

    n_H = (n_Hi - f_H) ÷ s_H + 1
    n_W = (n_Wi - f_W) ÷ s_W + 1
    n_D = (n_Di - f_D) ÷ s_D + 1

    Aipr = permutedims(
        reshape(Aip, f_H, n_H, f_W, n_W, f_D, n_D, c, m),
        [1, 3, 5, 2, 4, 6, 7, 8],
    )

    if cLayer isa MaxPoolLayer
        mask = Aipr .== reshape(Ao, 1, 1, 1, n_H, n_W, n_D, c, m)
    else
        mask = similar(Aipr) .= 1 / prod((cLayer.f))
    end

    dAi .= reshape(
        permutedims(
            reshape(dAo, 1, 1, 1, n_H, n_W, n_D, c, m) .* mask,
            [1, 4, 2, 5, 3, 6, 7, 8],
        ),
        n_Hi,
        n_Wi,
        n_Di,
        c,
        m,
    )[1+padS[1]:end-padS[2],
        1+padS[3]:end-padS[4],
        1+padS[5]:end-padS[6],
        :,
        :,
    ]

    mask = nothing
    return nothing
end #function dfastPooling!(cLayer::ThreeD,

export dfastPooling!


# External packages
using Revise, DelimitedFiles, SymPy

# Add Exasim to Julia search path
cdir = pwd(); ii = findlast("Exasim", cdir);
include(cdir[1:ii[end]] * "/Installation/setpath.jl");

# Exasim packages
using Preprocessing, Mesh, Gencode, Postprocessing

# create pde structure and mesh structure
pde, mesh = Preprocessing.initializeexasim();

# Define PDE model: governing equations, initial solutions, and boundary conditions
pde.model = "ModelC";            # ModelC, ModelD, ModelW
include("pdemodel.jl");          # include the PDE model file

# Set discretization parameters, physical parameters, and solver parameters
pde.porder = 3;          # polynomial degree
pde.torder = 2;          # time-stepping order of accuracy
pde.nstage = 2;          # time-stepping number of stages
pde.dt = 0.02*ones(200);   # time step sizes
pde.soltime = collect(10:10:length(pde.dt)); # steps at which solution are collected
pde.visdt = 1; # visualization timestep size

gam = 1e4;                      # gravity
pde.physicsparam = [gam 0.0];
pde.tau = [1.0];          # DG stabilization parameter
pde.GMRESrestart=15;            # number of GMRES restarts
pde.linearsolvertol=1e-12;      # GMRES tolerance
pde.linearsolveriter=16;        # number of GMRES iterations
pde.precMatrixType=2;           # preconditioning type
pde.NLtol = 1e-12;              # Newton tolerance
pde.NLiter=2;                   # Newton iterations

# Choose computing platform and set number of processors
#pde.platform = "gpu";           # choose this option if NVIDIA GPUs are available
pde.mpiprocs = 1;                # number of MPI processors

# create a linear mesh for a square domain
mesh.p, mesh.t = Mesh.SquareMesh(64,64,1); # a mesh of 8 by 8 quadrilaterals
mesh.p = (4*pi)*mesh.p .- 2*pi;
# expressions for disjoint boundaries
mesh.boundaryexpr = [p -> (p[2,:] .< -2*pi+1e-3), p -> (p[1,:] .> 2*pi-1e-3), p -> (p[2,:] .> 2*pi-1e-3), p -> (p[1,:] .< -2*pi+1e-3)];
mesh.boundarycondition = [1 1 1 1]; # Set boundary condition for each disjoint boundary
mesh.periodicexpr = [2 p->p[2,:] 4 p->p[2,:]; 1 p->p[1,:] 3 p->p[1,:]];

# call exasim to generate and run C++ code to solve the PDE model
#sol, pde, mesh,~,~,~,~  = Postprocessing.exasim(pde,mesh);

# search compilers and set options
pde = Gencode.setcompilers(pde);

# generate input files and store them in datain folder
pde, mesh, master, dmd = Preprocessing.preprocessing(pde,mesh);

# generate source codes and store them in app folder
Gencode.gencode(pde);

# compile source codes to build an executable file and store it in app folder
compilerstr = Gencode.compilecode(pde);

# run executable file to compute solution and store it in dataout folder
runstr = Gencode.runcode(pde);

# get solution from output files in dataout folder
sol = Postprocessing.fetchsolution(pde,master,dmd);


export bezsurf

"""
	bezsurf(npts, mptsg, b[], udpts, wdpts)

Calculate a Bezier surface over the `npts` by `mpts` control net points in `b`.

The method evaluates `udpts` by `wdpts` points of the curve and gives back the tuple `(P, EV, FV)`.
Here:
 - `P` is the vector of points memorized by columns;
 - `EV` is the `1`-dimensional cellular complex of the edges;
 - `FV` is the `2`-dimensional cellular complex of the faces.

This structure as it is formed could be directly imported in Plasm module by typing:
```julia
julia> P, EV, FV = bezier(npts, b[], dpts);
julia> Plasm.view(Plasm.mkpol(P, FV))
```

---

# Arguments

- `npts::Int64`: the number of the control net rows in `u` direction.
- `mpts::Int64`: the number of the control net rows in `w` direction;
- `b::Array{Float64, 2}`: 2-dimensional Array containing the control polygon vertices in the x, y, z coordinates for columns.
- `udpts::Int64`: number of data net rows in `u` direction.
- `wdpts::Int64`: number of data net rows in `w` direction.

---

# Examples

```jldoctest
julia> b = [
		  0.0 0.0 0.0 1.0 1.0 1.0 2.0 2.0 2.0;
		  0.0 2.0 4.0 0.0 2.0 4.0 0.0 2.0 4.0;
		  2.0 0.0 2.0 0.0 1.0 0.0 2.0 0.0 2.0
		]
3×9 Array{Float64,2}:
 0.0  0.0  0.0  1.0  1.0  1.0  2.0  2.0  2.0
 0.0  2.0  4.0  0.0  2.0  4.0  0.0  2.0  4.0
 2.0  0.0  2.0  0.0  1.0  0.0  2.0  0.0  2.0

julia> bezsurf(3, 3, b, 5, 5)[1]
3×25 Array{Float64,2}:
 0.0  0.5   1.0  1.5   2.0  0.0   0.5       1.0     1.5       2.0   0.0  0.5     1.0   1.5     2.0  0.0   0.5       1.0     1.5       2.0   0.0  0.5   1.0  1.5   2.0
 0.0  0.0   0.0  0.0   0.0  1.0   1.0       1.0     1.0       1.0   2.0  2.0     2.0   2.0     2.0  3.0   3.0       3.0     3.0       3.0   4.0  4.0   4.0  4.0   4.0
 2.0  1.25  1.0  1.25  2.0  1.25  0.921875  0.8125  0.921875  1.25  1.0  0.8125  0.75  0.8125  1.0  1.25  0.921875  0.8125  0.921875  1.25  2.0  1.25  1.0  1.25  2.0

julia> bezsurf(3, 3, b, 5, 5)[2]
44-element Array{Array{Int64,1},1}:
 [1, 2]
 [2, 3]
 [3, 4]
 [4, 5]
 [5, 6]
 [6, 7]
 [7, 8]
 [8, 9]
 [9, 10]
 [10, 11]
 ⋮
 [12, 17]
 [13, 18]
 [14, 19]
 [15, 20]
 [16, 21]
 [17, 22]
 [18, 23]
 [19, 24]
 [20, 25]

julia> bezsurf(3, 3, b, 5, 5)[3]
16-element Array{Array{Int64,1},1}:
 [1, 2, 6, 7]
 [2, 3, 7, 8]
 [3, 4, 8, 9]
 [4, 5, 9, 10]
 [6, 7, 11, 12]
 [7, 8, 12, 13]
 [8, 9, 13, 14]
 [9, 10, 14, 15]
 [11, 12, 16, 17]
 [12, 13, 17, 18]
 [13, 14, 18, 19]
 [14, 15, 19, 20]
 [16, 17, 21, 22]
 [17, 18, 22, 23]
 [18, 19, 23, 24]
 [19, 20, 24, 25]
```

---

_By Elia Onofri_
"""

function bezsurf(npts::Int64, mpts::Int64, b::Array{Float64,2}, udpts::Int64, wdpts::Int64)::Tuple{Array{Float64,2}, Array{Array{Int64,1},1}, Array{Array{Int64,1},1}}

	@assert npts*mpts == length(b[1,:]) ("ERROR: there are not npts * mpts = $(npts) * $(mpts) = $(npts * mpts) points of the net in b[]")

	ustep::Float64 = 1 / (udpts-1)
	wstep::Float64 = 1 / (wdpts - 1)
	n::Int64 = npts - 1
	m::Int64 = mpts - 1

	#=== x, y, z components of B ===#
	x::Array{Float64,2} = zeros(npts, mpts)
	y::Array{Float64,2} = zeros(npts, mpts)
	z::Array{Float64,2} = zeros(npts, mpts)

	for i = 1 : npts
		for j = 1 : mpts
			ij = (i - 1) * mpts + j
			x[i, j] = b[1, ij]
			y[i, j] = b[2, ij]
			z[i, j] = b[3, ij]
		end
	end

	#=== SYNC BLOCK BEGIN ===#
	#=== all the following @async are totally indipendent blocks ===#
	@sync begin

		#=== N = C * D ===#
		C::Array{Float64,2} = zeros(2, 2)
		D::Array{Float64,2} = zeros(2, 2)

		@async begin
			C = zeros(npts, npts)
			D = zeros(npts, npts)
			for i = 0 : n
				for j = 0 : (n - i)
					if (n-j-i)%2==0
						C[i+1, j+1] = binomial(n-j, n-j-i)
					else
						C[i+1, j+1] = 0.0-binomial(n-j, n-j-i)
					end
				end
			end

			for i = 1 : npts
				D[i, i] = abs(C[i, 1])
			end
		end

		#=== M = E * F ===#
		E::Array{Float64,2} = zeros(2, 2)
		F::Array{Float64,2} = zeros(2, 2)

		@async begin
			E = zeros(mpts, mpts)
			F = zeros(mpts, mpts)
			for i = 0 : m
				for j = 0 : (m - i)
					if (m - j - i) % 2 == 0
						E[i+1, j+1] = binomial(m-j, m-j-i)
					else
						E[i+1, j+1] = 0.0 - binomial(m-j, m-j-i)
					end
				end
			end

			for i = 1 : mpts
				F[i, i] = abs(E[i, 1])
			end
		end

		#=== U = [u^n ... u 1] ===#
		U::Array{Float64,2} = zeros(2, 2)

		@async begin
			U = ones(udpts, npts)
			u::Float64 = 0.0
			for i = 1 : udpts
				for j = 0 : n-1  #Per j=0 il valore è 1 ed è già impostato
					U[i, n-j] = U[i, npts-j] * u
				end
				u = u + ustep
			end
		end

		#=== W = [w^m ... w 1] ===#
		W::Array{Float64,2} = zeros(2, 2)

		@async begin
			W = ones(wdpts, mpts)
			w::Float64 = 0.0
			for i = 1 : wdpts
				for j = 0 : m-1  #Per j=0 il valore è 1 ed è già impostato
					W[i, m-j] = W[i, mpts-j] * w
				end
				w = w + wstep
			end
		end

		#=== Edge Vector ===#
		EV::Array{Array{Int64,1},1} = Array{Int64,1}[] #1-dimensional cellular complex used for plotting the curve with Plasm package

		@async begin
			for i = 1 : ((udpts * wdpts) - 1)
				app = Array{Int64}(2)
				app[1] = i
				app[2] = i+1
				push!(EV, app)
			end

			for i = 1 : (wdpts * (udpts - 1))
				app = Array{Int64}(2)
				app[1] = i
				app[2] = i + wdpts
				push!(EV, app)
			end
		end

		#=== Faces Vector ===#
		FV::Array{Array{Int64,1},1} = Array{Int64,1}[] #1-dimensional cellular complex used for plotting the curve with Plasm package

		@async begin
			for j = 0 : (udpts - 2)
				for i = 1 : wdpts - 1
					app = Array{Int64}(4)
					app[1] = j * wdpts + i
					app[2] = j * wdpts + i + 1
					app[3] = (j + 1) * wdpts + i
					app[4] = (j + 1) * wdpts + i + 1
					push!(FV, app)
				end
			end
		end

	end
	#=== SYNC BLOCK END ===#

	#=== Points Vector ===#
	UCD::Array{Float64,2} = U * C * D
	FEW::Array{Float64,2} = transpose(W * E * F)
	xP::Array{Float64,2} = transpose(UCD * x * FEW)
	yP::Array{Float64,2} = transpose(UCD * y * FEW)
	zP::Array{Float64,2} = transpose(UCD * z * FEW)

	P::Array{Float64,2} = zeros(3, udpts * wdpts)

	for i = 1 : udpts
		for j = 1 : wdpts
			ij::Int64 = (i-1) * udpts + j
			P[1, ij] = xP[i, j]
			P[2, ij] = yP[i, j]
			P[3, ij] = zP[i, j]
		end
	end

	return (P, EV, FV)
end


type SDF_MYD_BW <: SDFDynamicSystem
    omg::Float64
    zta::Float64
    kpa::Float64
    phi::Float64
    alpha::Float64
    u0::Float64
    v0::Float64
    z0::Float64
    n::Int64
    bta::Float64

    SDF_MYD_BW(omg::Float64, zta::Float64, kpa::Float64, phi::Float64, alpha::Float64, n::Int64, bta::Float64) = new(omg, zta, kpa, phi, alpha, 0.0, 0.0, 0.0, n, bta)
    SDF_MYD_BW(omg::Float64, zta::Float64, kpa::Float64, phi::Float64, alpha::Float64) = new(omg, zta, kpa, phi, alpha, 0.0, 0.0, 0.0, 5, 0.5)
    SDF_MYD_BW(omg::Float64, zta::Float64, kpa::Float64, phi::Float64) = new(omg, zta, kpa, phi, 0.0, 0.0, 0.0, 0.0, 5, 0.5)
end

function systemEquation(s::SDF_MYD_BW, p::Function)
    function dydt(y::AbstractArray{Float64,1}, t::Float64)
        u = y[1]
        v = y[2]
        z = y[3]
        omg2 = s.omg*s.omg
        dvdt = p(t)-2.0*s.zta*s.omg*v-(s.alpha*s.kpa*omg2*u+(1.0-s.alpha)*s.phi*g*z)-omg2*u
        dzdt = v - (s.bta*abs(v)*abs(z)^(s.n-1)*z + (1.0-s.bta)*v*abs(z)^(s.n))
        dzdt *= s.kpa*omg2/(s.phi*g)
        [v; dvdt; dzdt]
    end
    dydt
end

function response(s::SDF_MYD_BW, p::Function, t::AbstractArray{Float64,1})
    y0 = [s.u0; s.v0; s.z0]

    f = systemEquation(s, p)

    y = ode4(f, y0, t)

    u = y[:,1]
    v = y[:,2]
    z = y[:,3]
    omg2 = s.omg*s.omg
    fdz = 2.0*s.zta*s.omg*v
    fdp = s.alpha*s.kpa*omg2*u+(1.0-s.alpha)*s.phi*g*z
    a = map(p,t)-fdz-fdp-s.omg*s.omg*u

    [u v a fdz fdp]
end


using DelimitedFiles
using IterTools

const data = dropdims(readdlm("day10.txt", Int); dims=2)

function wrangle!(data::Vector{Int})
    pushfirst!(data, 0)
    sort!(data)
    return data
end

wrangle!(data)

function count_diff(data::Vector{Int})
    diff_data = diff(data)
    # +1 because there is a fake final point that is 3 above the max value as
    # stated in the instructions.
    return count(==(1), diff_data) * (count(==(3), diff_data) + 1)
end

println("Part 1: $(count_diff(data))")

function part2(data::Vector{Int})
    total = 1
    for consecutive in groupby(==(1), diff(data))
        if length(consecutive) > 1 && consecutive[1] == 1
            total *= 1 + binomial(length(consecutive), 2)
        end
    end
    return total
end

println("Part 2: $(part2(data))")


abstract type AbstractCoord{N,T} end

coord_axis(A::AbstractCoord{N,T}) where{N,T} = N
coord_eltype(A::AbstractCoord{N,T}) where{N,T} = T

struct CartesianCoord{N,T<:Real} <: AbstractCoord{N,T}
    name  :: String
    min   :: T
    max   :: T
    nodes :: Int
end

struct GaussLobattoCoord{N,T<:Real} <: AbstractCoord{N,T}
    name  :: String
    min   :: T
    max   :: T
    nodes :: Int
end

coord_type(coord::CartesianCoord)    = "Cartesian"
coord_type(coord::GaussLobattoCoord) = "GaussLobatto"

struct Cartesian{N} end
function Cartesian{N}(name::String, xmin::T, xmax::T, nodes::Integer;
                      endpoint::Bool=true) where {T<:Real,N}
    min_ = xmin
    if (!endpoint)
        h    = (xmax - xmin) / nodes
        max_ = xmax - h
    else
        max_ = xmax
    end
    CartesianCoord{N,T}(name, min_, max_, nodes)
end
Cartesian(args...; endpoint=true) = Cartesian{1}(args...; endpoint=endpoint)

struct GaussLobatto{N} end
function GaussLobatto{N}(name::String, xmin::T, xmax::T,
                          nodes::Integer) where {T<:Real,N}
    GaussLobattoCoord{N,T}(name, xmin, xmax, nodes)
end
GaussLobatto(args...) = GaussLobatto{1}(args...)

struct Coord{N} end
function Coord{N}(coord_type::String, name::String, min::T, max::T, nodes::Int) where {T<:Real,N}
    if coord_type == "Cartesian"
        return CartesianCoord{N,T}(name, min, max, nodes)
    elseif coord_type == "GaussLobatto"
        return GaussLobattoCoord{N,T}(name, min, max, nodes)
    else
        error("Unknown coord type")
    end
end

@inline function delta(coord::CartesianCoord)
    (coord.max - coord.min) / (coord.nodes - 1)
end

# a Gauss-Lobatto grid has non-uniform spacing. not sure if the best is to
# return "missing", not define the method, or just have it return NaN, as now.
@inline delta(coord::GaussLobattoCoord) = NaN

@inline function Base.getindex(coord::CartesianCoord, i::Union{Int, UnitRange})
    h = delta(coord)
    coord.min .+ (i .- 1) * h
end

@inline function Base.getindex(coord::GaussLobattoCoord{N,T},
                               j::Union{Int, UnitRange}) where {N,T}
    xmin  = coord.min
    xmax  = coord.max
    M     = coord.nodes - 1
    xj    = -cos.( (j .- 1) * (T(pi) / M))
    0.5 * (xmax .+ xmin .+ (xmax - xmin) * xj)
end

@inline Base.firstindex(coord::AbstractCoord) = 1
@inline Base.lastindex(coord::AbstractCoord)  = coord.nodes
@inline Base.length(coord::AbstractCoord) = coord.nodes

@inline Base.getindex(coord::AbstractCoord, ::Colon) = [coord[i] for i in 1:coord.nodes]

struct Chart{N,T,A}
    coords :: A
    function Chart{A}(coords) where {A}
        N = length(coords)
        T = coord_eltype(coords[1])
        for a in 1:N
            @assert(coord_axis(coords[a]) == a, "wrong order in grid array")
        end
        new{N,T,A}(coords)
    end
end
"""
    Chart(coords::A)

A `Chart` is a collection of `AbstractCoord`s
"""
Chart(coords::A) where {A} = Chart{A}(coords)

Chart(coords::Vararg{AbstractCoord,N}) where {N} = Chart(coords)

function Chart(coord_types::Vector, names::Vector, mins::Vector, maxs::Vector,
               nodess)
    dim_ = length(names)
    @assert(length(mins) == length(maxs) == length(nodess) == length(coord_types) == dim_)
    coords_ = [Coord{i}(coord_types[i], names[i], mins[i], maxs[i], nodess[i]) for i in 1:dim_]
    coords  = Tuple(coords_)
    Chart(coords)
end

@inline Base.ndims(chart::Chart{N}) where {N} = N

@inline function Base.getindex(chart::Chart{N}, idx::Vararg) where {N}
    [chart.coords[a][idx[a]] for a in 1:N]
end

@inline function Base.getindex(chart::Chart{N}, ::Colon) where {N}
    [chart.coords[a][:] for a in 1:N]
end

@inline function name(chart::Chart{N}) where {N}
    [chart.coords[a].name for a in 1:N]
end

@inline function min(chart::Chart{N}) where {N}
    [chart.coords[a].min for a in 1:N]
end

@inline function max(chart::Chart{N}) where {N}
    [chart.coords[a].max for a in 1:N]
end

@inline function nodes(chart::Chart{N}) where {N}
    [chart.coords[a].nodes for a in 1:N]
end

@inline function delta(chart::Chart{N}) where {N}
    [delta(chart.coords[a]) for a in 1:N]
end

@inline function coord_type(chart::Chart{N}) where {N}
    [coord_type(chart.coords[a]) for a in 1:N]
end

@inline Base.size(chart::Chart) = Tuple(nodes(chart))

"""
    Atlas{N,A<:Chart} <: AbstractPartition{N,A}

An `Atlas` is a collection of `Chart`s
"""
struct Atlas{N,A<:Chart} <: AbstractPartition{N,A}
    x :: NTuple{N,A}
end

"""
    Atlas(charts::A)
"""
Atlas(charts::Vararg{Chart,N}) where {N} = Atlas(charts)
Atlas(xx::Vector) = Atlas(Tuple(xx))

function Atlas(chart::Chart)
    charts = (chart)
    Atlas(charts)
end


module CellFieldsTests

using Test
using BlockArrays
using FillArrays
using Gridap.Arrays
using Gridap.TensorValues
using Gridap.Fields
using Gridap.ReferenceFEs
using Gridap.Geometry
using Gridap.CellData
using Gridap.FESpaces
using Random
using StaticArrays

domain = (0,1,0,1)
cells = (3,3)
model = CartesianDiscreteModel(domain,cells)

trian = Triangulation(model)
trian_N =BoundaryTriangulation(model)
trian_D =BoundaryTriangulation(model,tags="tag_8")
trian_S =SkeletonTriangulation(model)
trian_0 =Triangulation(trian_D,Int[])

ϕ = GenericCellField(get_cell_map(trian),trian,ReferenceDomain())
ϕinv = GenericCellField(lazy_map(inverse_map,get_cell_map(trian)),trian,PhysicalDomain())
xref = get_cell_points(trian)
xphy = CellPoint(get_array(xref),trian,PhysicalDomain())
test_array(ϕ(xref),collect1d(ϕ(xphy)),(i,j)->all(map(≈,i,j)))
test_array(ϕinv(xref),collect1d(ϕinv(xphy)),(i,j)->all(map(≈,i,j)))

x = get_cell_points(trian)
@test DomainStyle(x) == ReferenceDomain()
@test get_array(x) == get_cell_coordinates(trian)
@test get_data(x) == get_cell_ref_coordinates(trian)

px = get_physical_coordinate(trian)
test_array(px(x),collect1d(get_array(x)))

_x = change_domain(x,PhysicalDomain())
@test DomainStyle(_x) == PhysicalDomain()
@test get_array(_x) == get_cell_coordinates(trian)
@test get_data(_x) == get_cell_coordinates(trian)

_x = change_domain(x,ReferenceDomain())
@test DomainStyle(_x) == ReferenceDomain()
@test get_array(x) == get_cell_coordinates(trian)
@test get_data(x) == get_cell_ref_coordinates(trian)

ffun(x) = 2*x[1]
f = CellField(ffun,trian)
fx = f(x)
r = map(xs->ffun.(xs),get_array(x))
r = reshape(r,length(r))
test_array(fx,r,≈)

x_0 = get_cell_points(trian_0)
fx_0 = f(x_0)
test_array(fx_0,collect(fx_0))

n_S = get_normal_vector(trian_S)
x_S = get_cell_points(trian_S)

nf_S = n_S⋅∇(f)

jnf_S = jump(n_S⋅∇(f))
jnfx_S = jnf_S(x_S)
test_array(jnfx_S,0*collect(jnfx_S))

h = CellField(rand(num_cells(trian_S)),trian_S)*jump(∇(f))
hx_S = h(x_S)
test_array(hx_S,collect(hx_S))

h = 3*mean(f)⋅jump(n_S⋅∇(f))
hx_S = h(x_S)
test_array(hx_S,0*collect(hx_S))

aa(f) = 4*f
test_array((aa∘f)(x),4*r,≈)

f1 = f
f2 = 2*f
b(f1,f2) = f1+f2
test_array((b∘(f1,f2))(x),3*r,≈)

f2 = ones(num_cells(trian))
test_array((b∘(f1,f2))(x),map(i->i.+1,r),≈)

v = GenericCellField(get_cell_shapefuns(trian),trian,ReferenceDomain())
vx = v(x)
test_array(vx,collect(vx))

u = GenericCellField(lazy_map(transpose,get_data(v)),v.trian,v.domain_style)
m = v*u
test_array(m(x),collect(m(x)))
m = ∇(v)⋅∇(u)
test_array(m(x),collect(m(x)))

∇vx = ∇(v)(x)
test_array(∇vx,collect(∇vx))

∇fx = ∇(f)(x)
test_array(∇fx,collect(∇fx))

k = VectorValue(1.0,2.0)
∇kfx = ((∇+k)(f))(x)
test_array(∇kfx,collect(∇kfx))

∇kvx = ((∇+k)(v))(x)
test_array(∇kvx,collect(∇kvx))

β(x) = 2*x[1]
α = CellField(x->2*x,trian)
ax = ((∇+k)(β*α))(x)
test_array(ax,collect(ax))

ν = CellField(x->2*x,trian)
ax =((∇-k)⋅ν)(x)
test_array(ax,collect(ax))

ax =((∇-k)×ν)(x)
test_array(ax,collect(ax))

ax =((∇-k)⊗ν)(x)
test_array(ax,collect(ax))

ax =(∇.*ν)(x)
test_array(ax,collect(ax))

ax =(ν.*ν)(x)
test_array(ax,collect(ax))

ax =((∇-k).*ν)(x)
test_array(ax,collect(ax))

ax =(ν⊗(∇-k))(x)
test_array(ax,collect(ax))

σ(x) = diagonal_tensor(VectorValue(1*x[1],2*x[2]))
Fields.gradient(::typeof(σ)) = x-> ThirdOrderTensorValue{2,2,2,Float64}(1,0,0,0,0,0,0,2)
ax = ((∇+k)(σ⋅α))(x)
test_array(ax,collect(ax))

h = Operation(*)(2,f)
hx = h(x)
test_array(hx,2*fx)

a = fill(2,num_cells(trian))
h = Operation(*)(a,f)
hx = h(x)
test_array(hx,2*fx)

fx = evaluate(ffun,x)
test_array(fx,r,≈)

f_N = CellField(ffun,trian_N)
x_N = get_cell_points(trian_N)
fx_N = f_N(x_N)
test_array(fx_N,collect(fx_N))

n_N = get_normal_vector(trian_N)
nx_N = n_N(x_N)
test_array(nx_N,collect(nx_N))

h = f*n_N
hx = h(x_N)
test_array(hx,collect(hx))

gfun(x) = 3*x
g = CellField(gfun,trian)

h = Operation(*)(f,g)
gx = g(x)
hx = h(x)
r = map((i,j)->broadcast(*,i,j),fx,gx)
test_array(hx,r)

h_N = Operation(*)(f_N,g)
gx_N = g(x_N)
r = map((i,j)->broadcast(*,i,j),fx_N,gx_N)
hx_N = h_N(x_N)
test_array(hx_N,r)

g_D = CellField(gfun,trian_D)

cell_h = rand(num_cells(trian))
h = CellField(cell_h,trian)
test_array(h(x),collect(h(x)))
test_array(h(x_N),collect(h(x_N)))

h_N = (2*f_N+g)⋅g
hx_N = h_N(x_N)
test_array(hx_N,collect(hx_N))

# Test function evaluation

# Set reproducible random number seed
Random.seed!(0)
@testset "evaluating functions" for D in 1:3
    xmin = 0
    xmax = 1
    domain = repeat([xmin, xmax], D)
    ncells = 3
    partition = repeat([ncells], D)
    base_model = CartesianDiscreteModel(domain, partition)
    order = 2
    reffe = ReferenceFE(lagrangian, Float64, order)

    for M ∈ [:hypercubes, :simplices]
        model = (M == :hypercubes) ? base_model : simplexify(base_model)

        V = FESpace(model, reffe)

        coeff0 = rand(Float64)
        coeffs = rand(SVector{D,Float64})
        f(x) = coeffs ⋅ SVector(Tuple(x)) + coeff0
        # TODO: use this mechanism instead to project
        # Francesc Verdugo @fverdugo 13:11
        # a(u,v) = ∫( u*v )dΩ
        # l(v) = a(f,v)
        # Solve a fe problem with this weak form
        # See also tutorial 10, "Isotropic damage model", section "L2
        # projection", function "project"
        fh = interpolate_everywhere(f, V)
        fhcache = return_cache(fh, VectorValue(zeros(D)...))

        # Test Random points
        xs = [VectorValue(rand(D)...) for i in 1:10]
        for x in xs
            x = VectorValue(rand(D)...)
            fx = f(x)
            fhx = evaluate!(fhcache, fh, x)
            @test fhx ≈ fx
        end
        fhxs = fh(xs)
        @test fhxs ≈ f.(xs)

        nv = num_vertices(model) # Number of vertices
        nf = num_faces(model,D-1) # Number of faces
        trian = Triangulation(model)
        topo = GridTopology(model)

        pts = get_vertex_coordinates(topo) # Vertex coordinates
        face_nodes = get_face_nodes(model, D-1) # face-to-node numbering
        face_coords = lazy_map(Broadcasting(Reindex(pts)), face_nodes) # Get LazyArray of coordinates of face

        # Test a random vertex from the triangulation
        pt = pts[rand(1:nv)]
        fhpt = evaluate!(fhcache, fh, pt)
        @test fhpt .≈ f(pt)

        # Test a random point lying on the face of the polytope
        face_coord = face_coords[rand(1:nf)]
        λ = rand(length(face_coord));
        λ = (D > 1) ? λ./sum(λ) : λ
        pt = face_coord ⋅ λ # Point on the face
        fhpt = evaluate!(fhcache, fh, pt)
        @test fhpt .≈ f(pt)

        # Test with CellPoint
        # Build cell_to_fxs manually
        cache1,cache2 = fhcache
        ncells = num_cells(model)
        x_to_cell(x) = CellData._point_to_cell!(cache1, x)
        point_to_cell = map(x_to_cell, xs)
        cell_to_points, point_to_lpoint = make_inverse_table(point_to_cell, ncells)
        cell_to_xs = lazy_map(Broadcasting(Reindex(xs)), cell_to_points)
        cell_to_f = get_array(fh)
        cell_to_fxs = lazy_map(evaluate, cell_to_f, cell_to_xs)

        # Now build CellPoint with xs instead of building cell_to_xs
        cell_point_xs = compute_cell_points_from_vector_of_points(xs, trian, PhysicalDomain())
        cell_point_fxs = evaluate(fh, cell_point_xs)
        @test cell_point_fxs ≈ cell_to_fxs

    end
end

p = QUAD
D = num_dims(QUAD)
et = Float64
source_model = CartesianDiscreteModel((0,1,0,1),(2,2))

@testset "Test interpolation Lagrangian" begin
  # Lagrangian space -> Lagrangian space
  f(x) = x[1] + x[2]
  reffe = LagrangianRefFE(et, p, 1)
  V₁ = FESpace(source_model, reffe, conformity=:H1)
  fh = interpolate_everywhere(f, V₁)
  # Target Lagrangian Space
  reffe = LagrangianRefFE(et, p, 2)
  model = CartesianDiscreteModel((0,1,0,1),(4,4))
  V₂ = FESpace(model, reffe, conformity=:H1)

  ifh = Interpolable(fh)
  try
    interpolate_everywhere(fh, V₂)
  catch
    gh = interpolate_everywhere(ifh, V₂)
    pts = [VectorValue(rand(2)) for i=1:10]
    for pt in pts
      @test gh(pt) ≈ fh(pt)
    end
  end

  # VectorValued Lagrangian
  fᵥ(x) = VectorValue([x[1], x[1]+x[2]])
  reffe = ReferenceFE(lagrangian, VectorValue{2,et}, 1)
  V₁ = FESpace(source_model, reffe, conformity=:H1)
  fh = interpolate_everywhere(fᵥ, V₁)
  # Target
  reffe = ReferenceFE(lagrangian, VectorValue{2,et},  2)
  V₂ = FESpace(model, reffe, conformity=:H1)

  ifh = Interpolable(fh);
  gh = interpolate_everywhere(ifh, V₂)
  pts = [VectorValue(rand(2)) for i=1:10]
  for pt in pts
    @test gh(pt) ≈ fh(pt)
  end
end

@testset "Test interpolation RT" begin
  # RT Space -> RT Space
  f(x) = VectorValue([x[1], x[2]])
  reffe = RaviartThomasRefFE(et, p, 0)
  V₁ = FESpace(source_model, reffe, conformity=:HDiv)
  fh = interpolate_everywhere(f, V₁);
  # Target RT Space
  reffe = RaviartThomasRefFE(et, p, 1)
  model = CartesianDiscreteModel((0,1,0,1),(40,40))
  V₂ = FESpace(model, reffe, conformity=:HDiv)

  ifh = Interpolable(fh)
  gh = interpolate_everywhere(ifh, V₂)
  pts = [VectorValue(rand(2)) for i=1:10]
  for pt in pts
    @test gh(pt) ≈ fh(pt)
  end
end

#np = 3
#ndofs = 4
#
#p = Point(1,2)
#x = fill(p,np)
#z = 2.0
#
#v = VectorValue(3.0,1.5)
#w = VectorValue(3.4,3.5)
#a = MockBasis{2}(v,ndofs)
#b = MockBasis{2}(w,ndofs)
#c = fill(1.0,ndofs)
#f = OtherMockBasis{2}(ndofs)
#
#g = MockField{2}(v)
#
#l = 10
#xl = Fill(x,l)
#zl = [ z for  i in 1:l]
#cl = fill(c,l)
#fl = Fill(f,l)
#ϕl = lincomb(fl,cl)
#gl = fill(g,l)
#al = Fill(a,l)
#bl = fill(b,l)
#
#gf = GenericCellField(gl,ϕl,Val(true))
#gf_x = evaluate(gf,xl)
#∇gf_x = evaluate(∇(gf),xl)
#test_cell_field(gf,xl,gf_x,grad=∇gf_x)
#
#af = GenericCellField(al,ϕl,Val(true),Fill((Base.OneTo(ndofs),),l),Val((:,)))
#bf = GenericCellField(bl,ϕl,Val(true),Fill((Base.OneTo(ndofs),),l),Val((:,)))
#zf = convert_to_cell_field(zl,ϕl)
#df = af*zf
#dft = trialize_cell_basis(df)
#
#zfr = reindex(zf,[1,4,3])
#@test length(zfr) == 3
#@test length(get_array(zfr)) == 3
#@test length(get_cell_map(zfr)) == 3
#@test length(get_cell_axes(zfr)) == 3
#
## Check memoization
#df_x1 = evaluate(df,xl)
#df_x2 = evaluate(df,xl)
#@test df_x1 === df_x2
#∇gf1 = ∇(gf)
#∇gf2 = ∇(gf)
#@test ∇gf1 === ∇gf2
#@test evaluate(∇gf1,xl) === evaluate(∇gf2,xl)
#εgf1 = ε(gf)
#εgf2 = ε(gf)
#@test εgf1 === εgf2
#@test ∇×gf === ∇×gf
#@test evaluate(∇×gf,xl) === evaluate(∇×gf,xl)
#
#@test is_test(af)
#@test is_trial(dft)
#@test is_basis(af)
#@test is_basis(dft)
#mf = af⋅dft
#@test get_metasize(mf) == (:,:)
#mf_x = evaluate(mf,xl)
#@test size(mf_x[1]) == (np,ndofs,ndofs)
#
#@test get_cell_axes(mf) == Fill((Base.OneTo(ndofs),Base.OneTo(ndofs)),l)
#
#idsL = [ i*collect(1:ndofs)  for i in 1:l]
#idsR = [ 2*i*collect(1:ndofs)  for i in 1:l]
#axesL = Fill((Base.OneTo(ndofs),),l)
#axesR = Fill((Base.OneTo(ndofs),),l)
#
#idsS = merge_cell_dofs_at_skeleton(idsL,idsR,axesL,axesR)
#@test isa(idsS,VectorOfBlockArrayCoo)
#
#afS = merge_cell_fields_at_skeleton(af,2*af)
#@test isa(afS,SkeletonCellField)
#
#afL_x = evaluate(afS.plus,xl)
#afR_x = evaluate(afS.minus,xl)
#@test isa(afL_x,VectorOfBlockArrayCoo)
#@test isa(afR_x,VectorOfBlockArrayCoo)
#
#@test isa(afS*2,SkeletonCellField)
#@test isa(afS+afS,SkeletonCellField)
#
## Checks associated with trial bases
#df = bf
#dft = trialize_cell_basis(df)
#@test is_trial(dft) == true
#cell_vals = [rand(ndofs) for  i in 1:l]
#cell_field = lincomb(df,cell_vals)
#@test isa(cell_field,CellField)
#@test get_metasize(cell_field) == ()
#cell_field_x = evaluate(cell_field,xl)
#@test isa(cell_field_x[1],AbstractVector)

end # module


"""
    DTensor{T,N} <: AbstractTensor{T,N}

wrapper for generic Array{T,N} to represent dense tensors.
"""
struct DTensor{T,N} <: AbstractTensor{T,N}
    array::Array{T,N}
end

DTensor(T::Type = ComplexF64) = DTensor{T,0}(Array{T,0}(undef))
DTensor{T,N}(ds::NTuple{N,Int}) where {T,N} = DTensor{T,N}(Array{T,N}(undef, ds...))
DTensor{T}(ds::NTuple{N,Int}) where {T,N} = DTensor{T,N}(ds)

function Base.show(io::IO, A::DTensor)
    print(io, typeof(A))
    print(io, A.array)
end

Base.ndims(A::DTensor) = ndims(A.array)
Base.getindex(A::DTensor, args...) = getindex(A.array, args...)
Base.setindex!(A::DTensor, args...) = setindex!(A.array, args...)
Base.size(A::DTensor,i...) = size(A.array,i...)
Base.isapprox(A::DTensor, B::DTensor) = A.array ≈ B.array
Base.:(==)(A::DTensor, B::DTensor) = A.array == B.array
Base.similar(A::DTensor, args...) = DTensor(Base.similar(A.array, args...))
Base.copy(A::DTensor) = DTensor(copy(A.array))
Base.copy!(dest::DTensor, source::DTensor) = DTensor(copy!(dest.array, source.array))
Base.copyto!(dest::DTensor, source::DTensor) = (Base.copyto!(dest.array, source.array); dest)
Base.eltype(A::DTensor{T,N}) where {T,N} = T
LA.diag(a::DTensor) = LA.diag(a.array,0)
toarray(a::DTensor) = a.array

function initwith!(A::DTensor{T,N}, fun) where {T,N}
    A.array .= fun(T, size(A))
    return A
end

initwithzero!(A::DTensor) = initwith!(A, zeros)
initwithrand!(A::DTensor) = initwith!(A, rand)


# test_generic_domain.jl

# We test the generic functionality of a domain.
# These tests check whether the given domain correctly implements the
# interface of a domain.
function test_generic_domain(d::Domain)
    @test eltype(eltype(d)) == subeltype(d)
    @test isreal(d) == isreal(subeltype(d))

    if !isempty(d)
        x = point_in_domain(d)
        @test x ∈ d
    else
        try
            x = point_in_domain(d)
            @test false
        catch
        end
    end
end

@testset "generic domains" begin
    domains = [
        0..1,
        UnitInterval()^3
    ]

    @testset "$(rpad("Generic domains",80))" begin
        for domain in domains
            test_generic_domain(domain)
        end
    end
end


using LinearAlgebra: norm
using Printf: @printf

"""
    QuadDE(T::Type{<:AbstractFloat}; maxlevel::Integer=10, h0::Real=one(T)/8)

A callable object to integrate an function over an arbitrary interval, which
uses the *double exponential formula*s also known as the *tanh-sinh quadrature*
and its variants. It utilizes the change of variables to transform the
integrand into a form well-suited to the trapezoidal rule.

`QuadDE` tries to calculate integral values `maxlevel` times at a maximum;
the step size of a trapezoid is started from `h0` and is halved in each
following repetition for finer accuracy. The repetition is terminated when the
difference from the previous estimation gets smaller than a certain threshold.
The threshold is determined by the runtime parameters, see below.

The type `T` represents the accuracy of interval. The integrand should accept
values `x<:T` as its parameter.

---

    I, E = (q::QuadDE{T})(f::Function, a::Real, b::Real, c::Real...;
                          atol::Real=zero(T),
                          rtol::Real=atol>0 ? zero(T) : sqrt(eps(T)))
                          where {T<:AbstractFloat}

Numerically integrate `f(x)` over an arbitrary interval [a, b] and return the
integral value `I` and an estimated error `E`. The `E` is not exactly equal to
the difference from the true value. However, one can expect that the integral
value `I` is converged if `E <= max(atol, rtol*norm(I))` is true. Otherwise,
the obtained `I` would be unreliable; the number of repetitions exceeds the
`maxlevel` before converged. Optionally, one can divide the integral interval
[a, b, c...], which returns `∫f(x)dx in [a, b] + ∫f(x)dx in [b, c[1]] + ⋯`.
It is worth noting that each endpoint allows discontinuity or singularity.

The integrand `f` can also return any value other than a scalar, as far as
`+`, `-`, multiplication by real values, and `LinearAlgebra.norm`, are
implemented. For example, `Vector` or `Array` of numbers are acceptable
although, unfortunately, it may not be very performant.

# Examples
```jldoctest
julia> using DoubleExponentialFormulas

julia> using LinearAlgebra: norm

julia> qde = QuadDE(Float64);

julia> f(x) = 2/(1 + x^2);

julia> I, E = qde(f, -1, 1);

julia> I ≈ π
true

julia> E ≤ sqrt(eps(Float64))*norm(I)
true

julia> g(x) = [1/(1 + x^2), 2/(1 + x^2)];

julia> I, E = qde(g, 0, Inf);

julia> I ≈ [π/2, π]
true

julia> E ≤ sqrt(eps(Float64))*norm(I)
true

julia> h(x) = 1/sqrt(abs(x));  # singular point at x = 0

julia> I, E = qde(h, -1, 0, 1);

julia> I ≈ 4
true

julia> E ≤ sqrt(eps(Float64))*norm(I)
true
```
"""
struct QuadDE{T<:AbstractFloat,N}
    qts::QuadTS{T,N}
    qes::QuadES{T,N}
    qss::QuadSS{T,N}
end
function QuadDE(T::Type{<:AbstractFloat}; maxlevel::Integer=12, kwargs...)
    @assert maxlevel > 0
    qts = QuadTS(T; maxlevel=maxlevel, kwargs...)
    qes = QuadES(T; maxlevel=maxlevel, kwargs...)
    qss = QuadSS(T; maxlevel=maxlevel, kwargs...)
    return QuadDE{T,maxlevel}(qts, qes, qss)
end

function (q::QuadDE{T,N})(f::Function, a::Real, b::Real;
                          atol::Real=zero(T),
                          rtol::Real=atol>0 ? zero(T) : sqrt(eps(T))) where {T<:AbstractFloat,N}
    if a > b
        I, E = q(f, b, a; atol=atol, rtol=rtol)
        return -I, E
    end

    if a == b
        return zero(f(T(a))), zero(T)
    end

    if a == -Inf && b == Inf
        # integrate over [-∞, ∞]
        return q.qss(f; atol=atol, rtol=rtol)
    elseif b == Inf
        # integrate over [a, ∞]
        if a == 0
            return q.qes(f; atol=atol, rtol=rtol)
        else
            return q.qes(u -> f(u + T(a)); atol=atol, rtol=rtol)
        end
    elseif a == -Inf
        # integrate over [-∞, b]
        if b == 0
            return q.qes(u -> f(-u); atol=atol, rtol=rtol)
        else
            return q.qes(u -> f(-u + T(b)); atol=atol, rtol=rtol)
        end
    else
        # integrate over [a, b]
        if a == -1 && b == 1
            return q.qts(f; atol=atol, rtol=rtol)
        else
            _a = T(a)
            _b = T(b)
            s = (_b + _a)/2
            t = (_b - _a)/2
            I, E = q.qts(u -> f(s + t*u); atol=atol/t, rtol=rtol)
            return I*t, E*t
        end
    end
end
function (q::QuadDE{T,N})(f::Function, a::Real, b::Real, c::Real...;
                          atol::Real=zero(T),
                          rtol::Real=atol>0 ? zero(T) : sqrt(eps(T))) where {T<:AbstractFloat,N}
    bc = (b, c...)
    n = length(bc)
    _atol = atol/n
    I, E = q(f, a, b; atol=_atol, rtol=rtol)
    for i in 2:n
        dIh, dE = q(f, bc[i-1], bc[i]; atol=_atol, rtol=rtol)
        I += dIh
        E += dE
    end
    return I, E
end

function Base.show(io::IO, ::MIME"text/plain", q::QuadDE{T,N}) where {T<:AbstractFloat,N}
    @printf(io, "DoubleExponentialFormulas.QuadDE{%s}: maxlevel=%d, h0=%.3e",
            string(T), N, q.qts.h0)
end

const DEFAULT_QUADDE = QuadDE(Float64)

"""
    I, E = quadde(f::Function, a::Real, b::Real, c::Real...;
                  atol::Real=zero(Float64),
                  rtol::Real=atol>0 ? zero(Float64) : sqrt(eps(Float64)))

Numerically integrate `f(x)` over an arbitrary interval [a, b] and return the
integral value `I` and an estimated error `E`. The `E` is not exactly equal to
the difference from the true value. However, one can expect that the integral
value `I` is converged if `E <= max(atol, rtol*norm(I))` is true. Otherwise,
the obtained `I` would be unreliable; the number of repetitions exceeds the
`maxlevel` before converged. Optionally, one can divide the integral interval
[a, b, c...], which returns `∫ f(x)dx in [a, b] + ∫f(x)dx in [b, c[1]] + ⋯`.
It is worth noting that each endpoint allows discontinuity or singularity.

The integrand `f` can also return any value other than a scalar, as far as
`+`, `-`, multiplication by real values, and `LinearAlgebra.norm`, are
implemented. For example, `Vector` or `Array` of numbers are acceptable
although, unfortunately, it may not be very performant.

# Examples
```jldoctest
julia> using DoubleExponentialFormulas

julia> using LinearAlgebra: norm

julia> f(x) = 2/(1 + x^2);

julia> I, E = quadde(f, -1, 1);

julia> I ≈ π
true

julia> E ≤ sqrt(eps(Float64))*norm(I)
true

julia> I, E = quadde(x -> [1/(1 + x^2), 2/(1 + x^2)], 0, Inf);

julia> I ≈ [π/2, π]
true

julia> E ≤ sqrt(eps(Float64))*norm(I)
true

julia> I, E = quadde(x -> 1/sqrt(abs(x)), -1, 0, 1);  # singular point at x = 0

julia> I ≈ 4
true

julia> E ≤ sqrt(eps(Float64))*norm(I)
true
```
"""
quadde(f::Function, a::Real, b::Real, c::Real...; kwargs...) =
    DEFAULT_QUADDE(f, a, b, c...; kwargs...)


function main_func()
    ands::Array{Bool} = []
    ors::Array{Bool} = []
    xors::Array{Bool} = []
    for a in [false, true]
        for b in [false, true]
            push!(ands, (a & b))
            push!(ors, (a | b))
            push!(xors, (a ⊻ b))
        end
    end
    @assert(ands == [false, false, false, true])
    @assert(ors == [false, true, true, true])
    @assert(xors == [false, true, true, false])
    println(join(["OK"], " "))
end

function main()
    main_func()
end

main()


using LSODA, Printf

function fex(t::T1, y::T2, ydot::T3, data::T4) where {T1, T2, T3, T4}
	x = unsafe_wrap(Array,y,neq)
	xdot = unsafe_wrap(Array,ydot,neq)

	xdot[1]=1.0E4 * x[2] * x[3] - .04E0 * x[1]
	xdot[3]=3.0E7 * x[2] * x[2]
	xdot[2]= -xdot[1] - xdot[3]

	return Int32(0)
end

fex_c = @cfunction(fex,Cint,(Cdouble,Ptr{Cdouble},Ptr{Cdouble},Ptr{Nothing}))

const atol = Array{Float64}(undef,3)
const rtol = Array{Float64}(undef,3)
t = Array{Float64}(undef,1)
tout = Array{Float64}(undef,1)
const y = Array{Float64}(undef,3)

const neq = Int32(3)

y[1] = 1.0E0
  y[2] = 0.0E0
  y[3] = 0.0E0
  t[1] = 0.0E0
  tout[1] = 0.4E0

opt = lsoda_opt_t()
  opt.ixpr = 0
  opt.rtol = pointer(rtol)
  opt.atol = pointer(atol)
  opt.itask = 1

rtol[1] = 1.0E-4
	rtol[2] = 1.0E-4
  rtol[3] = 1.0E-4
	atol[1] = 1.0E-6
	atol[2] = 1.0E-10
	atol[3] = 1.0E-6

ctx = lsoda_context_t()
  ctx.function_ = fex_c
  ctx.neq = neq
  ctx.state = 1

lsoda_prepare(ctx,opt)

@time for i=1:12
  lsoda(ctx,y,t,tout[1])
  Printf.@printf("at t = %12.4e y= %14.6e %14.6e %14.6e\n",t[1],y[1], y[2], y[3])
  if (ctx.state <= 0)
			error("error istate = ", ctx.state)
	end
	tout[1] *= 10.0E0
end
println("Done!")
lsoda_free(ctx)


"""
BarycentricInterpolation
========================

This package implements the Barycentric formula for polynomial interpolation on
equispaced points and Chebyshev points of the first and second kind. The
formulae used are taken from the paper of Berrut and Trefethen, SIAM Review,
2004.

Additional Barycentric weights for Legendre points are taken from the paper of Wang,
Huybrechs, and Vandewalle, Mathematics of Computation, 83 (290) 2893-2914, 2014.

Other packages that may be of interest are FastGaussQuadrature and ApproxFun.

Written by David A.W. Barton (david.barton@bristol.ac.uk) 2016-2021 and licensed
under the MIT license <https://opensource.org/licenses/MIT>
"""
module BarycentricInterpolation

using FastGaussQuadrature: gausslegendre

export Chebyshev1, Chebyshev2, Legendre, Equispaced, ArbitraryPolynomial

export weights, nodes, interpolate, interpolation_matrix, differentiation_matrix, degree

#-- Node distributions

abstract type AbstractPolynomial{N, T <: Number} end

for name in [:Chebyshev1, :Chebyshev2, :Legendre, :Equispaced]
    @eval struct $name{N, T <: Number} <: AbstractPolynomial{N, T}
        shift::T
        scale::T
        nodes::Vector{T}
        weights::Vector{T}
        function $name{N, T}(start::T, stop::T) where {N, T <: Number}
            _shift = T(stop + start)/2
            _scale = T(stop - start)/2
            (_nodes, _weights) = nodes_weights($name{N, T}, _shift, _scale)
            new{N, T}(_shift, _scale, _nodes, _weights)
        end
    end
    @eval $name{N, T}(start=-1, stop=1) where {N, T} = $name{N, T}(convert(T, start), convert(T, stop))
    @eval $name{N}(start=-1, stop=1) where N = $name{N, Float64}(convert(Float64, start), convert(Float64, stop))
    @eval (poly::$name)(y) = interpolate(poly, y)
    @eval (poly::$name)(y, x) = interpolate(poly, y, x)
end

struct ArbitraryPolynomial{N, T <: Number} <: AbstractPolynomial{N, T}
    nodes::Vector{T}
    weights::Vector{T}
    function ArbitraryPolynomial(nodes::Vector{T}) where T <: Number
        N = length(nodes)-1
        new{N, T}(nodes, weights(ArbitraryPolynomial{N, T}, nodes))
    end
end
(poly::ArbitraryPolynomial)(y) = interpolate(poly, y)
(poly::ArbitraryPolynomial)(y, x) = interpolate(poly, y, x)

"""
    degree(poly)

Return the degree of the polynomial specified.
"""
degree(poly::AbstractPolynomial{N}) where N = N

"""
    nodes_weights(poly)

Return the nodes and weights of the polynomial specified.
"""
function nodes_weights(poly::Type{<:AbstractPolynomial}, shift=0, scale=1)
    return (nodes(poly, shift, scale), weights(poly))
end

nodes_weights(poly::AbstractPolynomial) = (poly.nodes, poly.weights)

function nodes_weights(::Type{<:Legendre{N, Float64}}, shift=0, scale=1) where N
    (x, w) = gausslegendre(N+1)
    _nodes = [xᵢ * scale + shift for xᵢ in x]
    _weights = [(2*isodd(i)-1)*sqrt((1-x[i]^2)*w[i]) for i in eachindex(x)]
    return (_nodes, _weights)
end

"""
    weights(poly)

Return the Barycentric weights for the specified orthogonal polynomials.
"""
function weights end

weights(poly::Type{<: AbstractPolynomial{N}}) where N = [_weight(poly, j) for j = 0:N]

# Eq. (5.1)
@inline _weight(poly::Type{<: Equispaced{N, T}}, j::Integer) where {N, T} = T((2*xor(isodd(N), iseven(j))-1)*binomial(N, j))

# Eq. (5.3)
@inline _weight(poly::Type{<: Chebyshev1{N, T}}, j::Integer) where {N, T} = -(2*iseven(j)-1)*sinpi(T(2j + 1)/(2N + 2))

# Eq. (5.4)
@inline _weight(poly::Type{<: Chebyshev2{N, T}}, j::Integer) where {N, T} = -T((1.0 - 0.5*((j == 0) || (j == N)))*(2*iseven(j) - 1))

function weights(poly::Type{<: ArbitraryPolynomial{N, T}}, x::AbstractVector{T}) where {N, T}
    w = similar(x)
    for i = eachindex(w)
        xᵢ = x[i]
        wᵢ = one(T)
        for j = 1:i-1
            wᵢ *= xᵢ - x[j]
        end
        for j = i+1:N+1
            wᵢ *= xᵢ - x[j]
        end
        w[i] = 1/wᵢ
    end
    return w
end

weights(poly::AbstractPolynomial) = poly.weights

"""
    nodes(poly)

Return the nodes for the specified orthogonal polynomials.
"""
function nodes end

nodes(poly::Type{<: AbstractPolynomial{N, T}}) where {N, T} = nodes(poly, zero(T), one(T))

nodes(poly::Type{<: AbstractPolynomial{N}}, shift::Number, scale::Number) where N = [_node(poly, j)*scale + shift for j = 0:N]

nodes(poly::Type{<: Equispaced{N}}, shift::Number, scale::Number) where N = range(shift - scale, stop=shift + scale, length=N+1)

@inline _node(poly::Type{<: Chebyshev1{N, T}}, j::Integer) where {N, T} = -cospi(T(2j + 1)/(2N + 2))

@inline _node(poly::Type{<: Chebyshev2{N, T}}, j::Integer) where {N, T} = -cospi(T(j)/N)

nodes(poly::AbstractPolynomial) = poly.nodes

"""
    interpolation_matrix(poly::AbstractPolynomial, x)

Return the interpolation matrix from the nodes of `poly` to the point(s) `x`.
For example :

    P = Chebyshev2{5}()
    x = range(-1, stop=1, length=10)
    M = interpolation_matrix(P, x)

Now `y(x) ≈ M*y₀` given that `y(nodes(poly)) = y₀.
"""
function interpolation_matrix(poly::AbstractPolynomial{N, T}, x::Union{Number, AbstractVector}) where {N, T}
    w = weights(poly)
    x₀ = nodes(poly)
    M = Matrix{T}(undef, length(x), N+1)
    # Eq. (4.2)
    for j = eachindex(x)
        xx = convert(T, x[j])
        Msum = zero(T)
        exact = 0
        for i = Base.OneTo(N + 1)
            exact = ifelse(xx == x₀[i], i, exact)
            M[j, i] = w[i] / (xx - x₀[i])
            Msum += M[j, i]
        end
        if Msum == 0
            for i = Base.OneTo(N + 1)
                M[j, i] = zero(T)
            end
        elseif exact > 0
            for i = Base.OneTo(N + 1)
                M[j, i] = zero(T)
            end
            M[j, exact] = one(T)
        else
            for i = Base.OneTo(N + 1)
                M[j, i] /= Msum
            end
        end
    end
    return M
end

"""
    interpolate(poly::AbstractPolynomial, y₀, [x])

Return the value of `y(x)` given that `y(nodes(poly)) = y₀`. If the value of `x`
is not provided, return a function `y(x)` that evaluates the interpolant at any
`x`.
"""
function interpolate(poly::AbstractPolynomial{N, T}, y₀::AbstractVector{<: Number}, x::Number) where {N, T}
    w = weights(poly)
    x₀ = nodes(poly)
    xx = convert(T, x)
    # Eq. (4.2)
    numer = zero(T)
    denom = zero(T)
    exact = 0
    for j = Base.OneTo(N+1)
        xdiff = xx - x₀[j]
        temp = w[j] / xdiff
        numer += temp*convert(T, y₀[j])
        denom += temp
        exact = ifelse(xdiff == 0, j, exact)
    end
    if exact > 0
        return convert(T, y₀[exact])
    else
        return numer/denom
    end
end

interpolate(poly::AbstractPolynomial{N, T}, y₀::AbstractVector{<: Number}) where {N, T} = x -> interpolate(poly, y₀, x)
interpolate(poly::AbstractPolynomial{N, T}, y₀::AbstractVector{<: Number}, x::AbstractVector{<: Number}) where {N, T} = [interpolate(poly, y₀, xᵢ) for xᵢ in x]

"""
    differentiation_matrix(poly::AbstractPolynomial)

Return the differentiation matrix at the nodes of the polynomial specified.

    P = Chebyshev2{5}()
    D = differentiation_matrix(P)

Now dy/dx ≈ `D*y` at the nodes of the polynomial.
"""
function differentiation_matrix(poly::AbstractPolynomial{N, T}) where {N, T}
    # Eqs. (9.4) and (9.5)
    w = weights(poly)
    x = nodes(poly)
    D = Matrix{T}(undef, N+1, N+1)
    for i = Base.OneTo(N+1)
        Dsum = zero(T)
        for j = Base.OneTo(i-1)
            temp = (w[j] / w[i]) / (x[i] - x[j])
            D[i, j] = temp
            Dsum += temp
        end
        for j = i+1:N+1
            temp = (w[j] / w[i]) / (x[i] - x[j])
            D[i, j] = temp
            Dsum += temp
        end
        D[i, i] = -Dsum
    end
    return D
end

end  # module


# Collections of ScalarGridData

macro collectionfield(wrapper,nctypes)

  ctype = Symbol[]
  ctypetype = Expr[]
  crtype = Expr[]
  for i in 1:eval(nctypes)
    c = Symbol("C",string(i))
    push!(ctype,c)
    push!(ctypetype,:(::Type{$c}))
    push!(crtype,:($c<:CellType))
  end

  return esc(quote

    # This is the basic function that uses reshape(view()) of an input vector
    # to provide the components of this type
    function (::Type{$wrapper{$(ctype...),NX,NY,T,DT}})(data::AbstractVector{R}) where
                              {$(ctype...),NX,NY,T<:Number,DT,R}
      n0 = 0
      flist = []
      gtype = $wrapper{$(ctype...),NX,NY,R,typeof(data)}
      for ft in _fieldtypes(gtype)
        if ft <: GridData
          ftype = griddatatype(ft)
          dims = size(ft)
          dn = prod(dims)
          fdata = reshape(view(data,n0+1:n0+dn),dims)
          n0 += dn
          push!(flist,ftype{celltype(ft),NX,NY,R,typeof(fdata)}(fdata))
        end
      end
      gtype(data,flist...)
    end

    # This provides the most straightforward constructor, calling upon the constructor
    # above to deal with the split between components
    function $wrapper($(ctypetype...), dualnodedims::Tuple{Int, Int};dtype=Float64) where {$(crtype...)}
        len = 0
        for ft in _fieldtypes($wrapper{$(ctype...),dualnodedims...,dtype})
          if ft <: GridData
            len += prod(size(ft))
          end
        end
        data = zeros(dtype,len)
        $wrapper{$(ctype...),dualnodedims...,dtype,typeof(data)}(data)
    end

    # Create several other basic functions for this type
    @griddata($wrapper,$nctypes)

  end)
end

"""
    Edges

`Edges` is a wrapper for vector-valued data that lie at the faces of either dual cells or
primary cells. `Edges` type data have fields `u` and `v` for the components of the
vector field. These are the normal components of the vector field on the vertical
and horizontal faces of the corresponding cell.

# Constructors
- `Edges(C,dims)` creates a vector field of zeros in cells of type `C` (where `C` is
  either `Dual` or `Primal`), on a grid of dimensions `dims`. Note that `dims`
  represent the number of dual cells on the grid.
- `Edges(C,w)` performs the same construction, but uses existing field data `w`
  of `GridData` type to determine the size of the grid.
-  Adding the `dtype=` keyword allows the data type of the field data to be
  changed. The default is `Float64`, but can be changed to, e.g., `ComplexF64`
"""
struct Edges{C <: CellType, NX, NY, T <: Number, DT} <: VectorGridData{NX,NY,T}
    data::DT
    u::XEdges{C,NX,NY,T}
    v::YEdges{C,NX,NY,T}
end

@collectionfield Edges 1

# Based on number of dual nodes, return the number of edges
edge_inds(::Type{C},   dualnodedims) where {C <: CellType} =
            xedge_inds(C,dualnodedims), yedge_inds(C,dualnodedims)

function Base.show(io::IO, edges::Edges{C, NX, NY, T, DT}) where {C, NX, NY, T, DT}
    nodedims = "(nx = $NX, ny = $NY)"
    udims = "(nx = $(size(edges.u,1)), ny = $(size(edges.u,2)))"
    vdims = "(nx = $(size(edges.v,1)), ny = $(size(edges.v,2)))"
    println(io, "$C edges for a $nodedims cell grid of type $T data")
    println(io, "  Internal u-faces: $udims")
    print(io, "  Internal v-faces: $vdims")
end

function Base.show(io::IO, m::MIME"text/plain", edges::Edges)
    println(io,"$(typeof(edges)) data")
    println(io,"u (in grid orientation)")
    show(io,m,reverse(transpose(edges.u),dims=1))
    println(io)
    println(io,"v (in grid orientation)")
    show(io,m,reverse(transpose(edges.v),dims=1))
end

### Edge gradient ###

"""
    EdgeGradient

`EdgeGradient` is a wrapper for tensor-valued data that lie partly at the nodes of dual cells and
primary cells. `EdgeGradient` type data have fields `dudx`, `dudy`, `dvdx`, `dvdy` for the components of the
tensor field. The diagonal components lie at one set of nodes (e.g. Primal),
and the offdiagonal at the other set (e.g. Dual).

# Constructors
- `EdgeGradient(C,dims)` creates a tensor field of zeros in cells of type `C` (where `C` is
  either `Dual` or `Primal`), on a grid of dimensions `dims`. Note that `dims`
  represent the number of dual cells on the grid.
- `EdgeGradient(C,w)` performs the same construction, but uses existing field data `w`
  of `GridData` type to determine the size of the grid.
-  Adding the `dtype=` keyword allows the data type of the field data to be
  changed. The default is `Float64`, but can be changed to, e.g., `ComplexF64`
"""
struct EdgeGradient{C <: CellType,D <: CellType, NX,NY, T<: Number, DT} <: GridData{NX,NY,T}
  data :: DT
  dudx :: Nodes{C,NX,NY,T}
  dudy :: Nodes{D,NX,NY,T}
  dvdx :: Nodes{D,NX,NY,T}
  dvdy :: Nodes{C,NX,NY,T}
end

@collectionfield EdgeGradient 2

# Only provide one of the cell types -- the other cell type is set automatically
EdgeGradient(::Type{C}, dualnodedims::Tuple{Int, Int};dtype=Float64) where {C <: CellType} =
    EdgeGradient(C,othertype(C),dualnodedims,dtype=dtype)

function Base.show(io::IO, nodes::EdgeGradient{R, S, NX, NY, T, DT}) where {R, S, NX, NY, T, DT}
    nodedims = "(nx = $NX, ny = $NY)"
    udims = "(nx = $(size(nodes.dudx,1)), ny = $(size(nodes.dudx,2)))"
    vdims = "(nx = $(size(nodes.dudy,1)), ny = $(size(nodes.dudy,2)))"
    println(io, "Edge gradient data in a $nodedims cell grid of type $T data")
    println(io, "  du/dx and dv/dy as $R nodes : $udims")
    println(io, "  du/dy and dv/dx as $S nodes : $vdims")
end

function Base.show(io::IO, m::MIME"text/plain", nodes::EdgeGradient)
    println(io,"$(typeof(nodes)) data")
    println(io,"du/dx (in grid orientation)")
    show(io,m,reverse(transpose(nodes.dudx),dims=1))
    println(io)
    println(io,"du/dy (in grid orientation)")
    show(io,m,reverse(transpose(nodes.dudy),dims=1))
    println(io)
    println(io,"dv/dx (in grid orientation)")
    show(io,m,reverse(transpose(nodes.dvdx),dims=1))
    println(io)
    println(io,"dv/dy (in grid orientation)")
    show(io,m,reverse(transpose(nodes.dvdy),dims=1))
end

### Node pair ###

"""
    NodePair

`NodePair` is a wrapper for vector-valued data that lie at the nodes of dual cells and
primal cells. `NodePair` type data have fields `u` and `v` for the components of the
vector field. These are the normal components of a vector field on nodes that
form the faces of a virtual cell centered at one of the faces of the primal cell.

# Constructors
- `NodePair(C,dims)` creates a vector field of zeros in cells of type `C` (where `C` is
  either `Dual` or `Primal`), on a grid of dimensions `dims`. Note that `dims`
  represent the number of dual cells on the grid.
- `NodePair(C,w)` performs the same construction, but uses existing field data `w`
  of `GridData` type to determine the size of the grid.
-  Adding the `dtype=` keyword allows the data type of the field data to be
  changed. The default is `Float64`, but can be changed to, e.g., `ComplexF64`
"""
struct NodePair{C <: CellType,D <: CellType, NX,NY,T, DT} <: GridData{NX,NY,T}
  data :: DT
  u :: Nodes{C,NX,NY,T}
  v :: Nodes{D,NX,NY,T}
end

@collectionfield NodePair 2

# Only provide one of the cell types -- the other cell type is set automatically
NodePair(::Type{C}, dualnodedims::Tuple{Int, Int};dtype=Float64) where {C <: CellType} =
    NodePair(C,othertype(C),dualnodedims,dtype=dtype)

function Base.show(io::IO, nodes::NodePair{R, S, NX, NY, T}) where {R, S, NX, NY, T}
    nodedims = "(nx = $NX, ny = $NY)"
    udims = "(nx = $(size(nodes.u,1)), ny = $(size(nodes.u,2)))"
    vdims = "(nx = $(size(nodes.v,1)), ny = $(size(nodes.v,2)))"
    println(io, "($R,$S) node data pair in a $nodedims cell grid of type $T data")
    println(io, "  Number of $R nodes: $udims")
    println(io, "  Number of $S nodes: $vdims")
end

function Base.show(io::IO, m::MIME"text/plain", nodes::NodePair)
    println(io,"$(typeof(nodes)) data")
    println(io,"u (in grid orientation)")
    show(io,m,reverse(transpose(nodes.u),dims=1))
    println(io)
    println(io,"v (in grid orientation)")
    show(io,m,reverse(transpose(nodes.v),dims=1))
end


"""
        color_graph(g::LightGraphs.AbstractGraphs, ::AcyclicColoring)

Returns a coloring vector following the acyclic coloring rules (1) the coloring
corresponds to a distance-1 coloring, and (2) vertices in every cycle of the
graph are assigned at least three distinct colors. This variant of coloring is
called acyclic since every subgraph induced by vertices assigned any two colors
is a collection of trees—and hence is acyclic.

Reference: Gebremedhin AH, Manne F, Pothen A. **New Acyclic and Star Coloring Algorithms with Application to Computing Hessians**
"""
function color_graph(g::LightGraphs.AbstractGraph, ::AcyclicColoring)

    color = zeros(Int, nv(g))
    forbidden_colors = zeros(Int, nv(g))

    set = DisjointSets{LightGraphs.Edge}([])

    first_visit_to_tree = Array{Tuple{Int, Int}, 1}(undef, ne(g))
    first_neighbor = Array{Tuple{Int, Int}, 1}(undef, nv(g))

    for v in vertices(g)
        #enforces the first condition of acyclic coloring
        for w in outneighbors(g, v)
            if color[w] != 0
                forbidden_colors[color[w]] = v
            end
        end
        #enforces the second condition of acyclic coloring
        for w in outneighbors(g, v)
            if color[w] != 0 #colored neighbor
                for x in outneighbors(g, w)
                    if color[x] != 0 #colored x
                        if forbidden_colors[color[x]] != v
                            prevent_cycle(v, w, x, g, color, forbidden_colors, first_visit_to_tree, set)
                        end
                    end
                end
            end
        end

        color[v] = min_index(forbidden_colors, v)

        #grow star for every edge connecting colored vertices v and w
        for w in outneighbors(g, v)
            if color[w] != 0
                grow_star!(set, v, w, g, first_neighbor, color)
            end
        end

        #merge the newly formed stars into existing trees if possible
        for w in outneighbors(g, v)
            if color[w] != 0
                for x in outneighbors(g, w)
                    if color[x] != 0 && x != v
                        if color[x] == color[v]
                            merge_trees!(set, v, w, x, g)
                        end
                    end
                end
            end
        end
    end

    return color
end

"""
        prevent_cycle(v::Integer,
                    w::Integer,
                    x::Integer,
                    g::LightGraphs.AbstractGraph,
                    color::AbstractVector{<:Integer},
                    forbidden_colors::AbstractVector{<:Integer},
                    first_visit_to_tree::Array{Tuple{Integer, Integer}, 1},
                    set::DisjointSets{LightGraphs.Edge})

Subroutine to avoid generation of 2-colored cycle due to coloring of vertex v,
which is adjacent to vertices w and x in graph g. Disjoint set is used to store
the induced 2-colored subgraphs/trees where the id of set is a key edge of g
"""
function prevent_cycle(v::Integer,
                        w::Integer,
                        x::Integer,
                        g::LightGraphs.AbstractGraph,
                        color::AbstractVector{<:Integer},
                        forbidden_colors::AbstractVector{<:Integer},
                        first_visit_to_tree::AbstractVector{<: Tuple{Integer, Integer}},
                        set::DisjointSets{LightGraphs.Edge})

    edge = find_edge(g, w, x)
    e = find_root(set, edge)
    p, q = first_visit_to_tree[edge_index(g, e)]
    if p != v
        first_visit_to_tree[edge_index(g, e)] = (v, w)
    elseif q != w
        forbidden_colors[color[x]] = v
    end
end

"""
        min_index(forbidden_colors::AbstractVector{<:Integer}, v::Integer)

Returns min{i > 0 such that forbidden_colors[i] != v}
"""
function min_index(forbidden_colors::AbstractVector{<:Integer}, v::Integer)
    return findfirst(!isequal(v), forbidden_colors)
end

"""
        grow_star!(set::DisjointSets{LightGraphs.Edge},
                v::Integer,
                w::Integer,
                g::LightGraphs.AbstractGraph
                first_neighbor::Array{Tuple{Integer, Integer}, 1})

Subroutine to grow a 2-colored star after assigning a new color to the
previously uncolored vertex v, by comparing it with the adjacent vertex w.
Disjoint set is used to store stars in sets, which are identified through key
edges present in g.
"""
function grow_star!(set::DisjointSets{LightGraphs.Edge},
                   v::Integer,
                   w::Integer,
                   g::LightGraphs.AbstractGraph,
                   first_neighbor::AbstractArray{<: Tuple{Integer, Integer}, 1},
                   color::AbstractVector{<: Integer})
    edge = find_edge(g, v, w)
    push!(set, edge)
    p, q = first_neighbor[color[w]]
    if p != v
        first_neighbor[color[w]] = (v, w)
    else
        edge1 = find_edge(g, v, w)
        edge2 = find_edge(g, p, q)
        e1 = find_root(set, edge1)
        e2 = find_root(set, edge2)
        union!(set, e1, e2)
    end
    return nothing
end

"""
        merge_trees!(v::Integer,
                w::Integer,
                x::Integer,
                g::LightGraphs.AbstractGraph,
                set::DisjointSets{LightGraphs.Edge})

Subroutine to merge trees present in the disjoint set which have a
common edge.
"""
function merge_trees!(set::DisjointSets{LightGraphs.Edge},
                    v::Integer,
                    w::Integer,
                    x::Integer,
                    g::LightGraphs.AbstractGraph)
    edge1 = find_edge(g, v, w)
    edge2 = find_edge(g, w, x)
    e1 = find_root(set, edge1)
    e2 = find_root(set, edge2)
    if (e1 != e2)
        union!(set, e1, e2)
    end
end

"""
        find_edge(g::LightGraphs.AbstractGraph, v::Integer, w::Integer)

Returns an edge object of the type LightGraphs.Edge which represents the
edge connecting vertices v and w of the undirected graph g
"""
function find_edge(g::LightGraphs.AbstractGraph,
                   v::Integer,
                   w::Integer)
    for e in edges(g)
        if (src(e) == v && dst(e) == w) || (src(e) == w && dst(e) == v)
            return e
        end
    end
    throw(ArgumentError("$v and $w are not connected in graph g"))
end

"""
        edge_index(g::LightGraphs.AbstractGraph, e::LightGraphs.Edge)

Returns an Integer value which uniquely identifies the edge e in graph
g. Used as an index in main function to avoid custom arrays with non-
numerical indices.
"""
function edge_index(g::LightGraphs.AbstractGraph,
                    e::LightGraphs.Edge)
    for (i, edge) in enumerate(edges(g))
        if edge == e
            return i
        end
    end
    throw(ArgumentError("Edge $e is not present in graph g"))
end


function calculate_residuals(ws::COSMO.Workspace,  IGNORESCALING_FLAG::Bool = false)

	if (ws.settings.scaling != 0 && !IGNORESCALING_FLAG)
		r_prim = norm(ws.sm.Einv * (ws.p.A * ws.vars.x + ws.vars.s - ws.p.b), Inf)
		# ∇f0 + ∑ νi ∇hi(x*) == 0 condition
		r_dual = norm(ws.sm.cinv[] * ws.sm.Dinv * (ws.p.P * ws.vars.x + ws.p.q - ws.p.A' * ws.vars.μ), Inf)
	end
	if (ws.settings.scaling == 0 || IGNORESCALING_FLAG )
		r_prim = norm(ws.p.A * ws.vars.x + ws.vars.s - ws.p.b, Inf)
		r_dual = norm(ws.p.P * ws.vars.x + ws.p.q - ws.p.A' * ws.vars.μ, Inf)
	end
	# FIXME: Why is it -A'μ ?
	return r_prim,r_dual
end

function max_res_component_norm(ws::COSMO.Workspace, IGNORESCALING_FLAG::Bool = false)
	if (ws.settings.scaling != 0 && !IGNORESCALING_FLAG)
		max_norm_prim = max.(norm(ws.sm.Einv * (ws.p.A * ws.vars.x), Inf), norm(ws.sm.Einv * ws.vars.s, Inf), norm(ws.sm.Einv * ws.p.b, Inf))
		max_norm_dual = max.(norm(ws.sm.cinv .* (ws.sm.Dinv * (ws.p.P * ws.vars.x)), Inf), norm(ws.sm.cinv .* (ws.sm.Dinv * ws.p.q), Inf), norm(ws.sm.cinv .* (ws.sm.Dinv * (ws.p.A' * ws.vars.μ)), Inf))
	end
	if (ws.settings.scaling == 0 || IGNORESCALING_FLAG)
		max_norm_prim = max.(norm(ws.p.A * ws.vars.x, Inf), norm(ws.vars.s, Inf), norm(ws.p.b, Inf))
		max_norm_dual = max.(norm(ws.p.P * ws.vars.x, Inf), norm(ws.p.q, Inf), norm(ws.p.A' * ws.vars.μ, Inf))
	end
	return max_norm_prim, max_norm_dual
end

function has_converged(ws::COSMO.Workspace, r_prim::Float64, r_dual::Float64)
	max_norm_prim, max_norm_dual = max_res_component_norm(ws)
	settings = ws.settings
	ϵ_prim = settings.eps_abs + settings.eps_rel * max_norm_prim
	ϵ_dual = settings.eps_abs + settings.eps_rel * max_norm_dual

	# if an optimal objective value was specified for the problem check if current solution is within specified accuracy
	obj_true_FLAG = true
	if !isnan(settings.obj_true)
		current_cost = ws.sm.cinv[] * (1/2 * ws.vars.x' * ws.p.P * ws.vars.x + ws.p.q' * ws.vars.x)[1]
		obj_true_FLAG = abs(settings.obj_true - current_cost) <= settings.obj_true_tol
	end

	return (r_prim < ϵ_prim  && r_dual < ϵ_dual && obj_true_FLAG)
end


# Verify that pulling data directly out of the SparseMatrixCSC form gives
# exactly what the previous hand-coded algorithm gave.
@testset "sparse matrix" begin
  function sparse_matrix_reference(A::AbstractSparseMatrix)
    m, n = size(A)
    @assert m==n

    col_start = Array{Int}(undef, n)
    col_len = Array{Int}(undef, n)
    row = Array{Int}(undef, 0)
    data = Array{Float64}(undef, 0)
    for j in 1:n
      if j==1
        col_start[j] = 1
      else
        col_start[j] = col_start[j-1] + col_len[j-1]
      end

      col_len[j] = 0
      for i in 1:n
        if A[i,j] != 0.0
          col_len[j] += 1
          push!(row, i)
          push!(data, A[i,j])
        end
      end
    end
    return col_start, col_len, row, data
  end

  sparse_matrix_reference(A::Matrix) = sparse_matrix_reference(sparse(A))

  function sparse_matrix_csc(A::SparseMatrixCSC)
    m, n = size(A)
    @assert m==n
    col_start = A.colptr[1:end-1]
    col_len = diff(A.colptr)
    row = rowvals(A)
    data = nonzeros(A)
    return col_start, col_len, row, data
  end

  sparse_matrix_csc(A::Matrix) = sparse_matrix_csc(convert(SparseMatrixCSC, A))

  Random.seed!(42)
  for i in 1:100
    n = rand(5:20)
    M = sprandn(n, n, 0.1)
    @test sparse_matrix_csc(M) == sparse_matrix_reference(M)

    Mf = Matrix(M)
    @test sparse_matrix_csc(Mf) == sparse_matrix_reference(Mf)
    @test sparse_matrix_csc(Mf) == sparse_matrix_csc(M)
  end
end


using QuantumEvolution
using Test, Random

@testset "QuantumEvolution.jl" begin
    sol = optimize(x -> (x - 1)^2, i -> randn(), 50)
    @test sol.x ≈ 1 rtol = 1e-4
    sol =
        optimize(x -> (x[1] - 1)^2 + (x[2] - x[1]^2)^2, i -> randn(2), 50, max_iters = 500)
    map(sol.x) do x
        @test x ≈ 1 rtol = 1e-4
    end
    sol = optimize(x -> ifelse(1.2 < x, Inf, (x - 1)^2), i -> rand() * 0.2 + 0.9, 50)
    @test sol.x ≈ 1 rtol = 1e-4
    @test_throws ErrorException optimize(x -> Inf, i -> nothing, 50)
end


""
function constraint_thermal_limit_from_soft(pm::AbstractPowerModel, n::Int, f_idx, rate_a)
    l,i,j = f_idx
    p_fr = var(pm, n, :p, f_idx)
    q_fr = var(pm, n, :q, f_idx)
    sm_slack = var(pm, :sm_slack, l)

    JuMP.@constraint(pm.model, p_fr^2 + q_fr^2 <= (rate_a + sm_slack)^2)
end

""
function constraint_thermal_limit_to_soft(pm::AbstractPowerModel, n::Int, t_idx, rate_a)
    l,i,j = t_idx
    p_to = var(pm, n, :p, t_idx)
    q_to = var(pm, n, :q, t_idx)
    sm_slack = var(pm, :sm_slack, l)

    JuMP.@constraint(pm.model, p_to^2 + q_to^2 <= (rate_a + sm_slack)^2)
end

""
function constraint_gen_active_deviation(pm::AbstractPowerModel, n::Int, i, pg)
    pg_var = var(pm, n, :pg, i)
    pg_delta = var(pm, n, :pg_delta, i)

    JuMP.@constraint(pm.model, pg_var == pg + pg_delta)
end

""
function constraint_branch_contingency_ptdf_thermal_limit_from_soft(pm::AbstractPowerModel, n::Int, i::Int, cut_map, rate)
    bus_injection = var(pm, :bus_pg)
    bus_withdrawal = var(pm, :bus_wdp)

    @constraint(pm.model, sum(weight*(bus_injection[bus_id] - bus_withdrawal[bus_id]) for (bus_id, weight) in cut_map) <= rate + var(pm, :branch_cont_flow_vio, i))
end

""
function constraint_branch_contingency_ptdf_thermal_limit_to_soft(pm::AbstractPowerModel, n::Int, i::Int, cut_map, rate)
    bus_injection = var(pm, :bus_pg)
    bus_withdrawal = var(pm, :bus_wdp)

    @constraint(pm.model, -sum(weight*(bus_injection[bus_id] - bus_withdrawal[bus_id]) for (bus_id, weight) in cut_map) <= rate + var(pm, :branch_cont_flow_vio, i))
end


using Plots, LaTeXStrings
using DelimitedFiles
using Revise
using JLD2, FileIO

cd("julia/")
push!(LOAD_PATH, ".")
using VirusLoadCurve

tdata = [4.0/24.0, 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12.]
RAWDATA = readdlm("../../data/Smith2018/Virus_Best10.txt", ' ', Float64, '\n')

k = 6
ttdata = repeat(tdata, inner=1)
tend = ttdata[end]
vvdata = reshape(RAWDATA[k, :], 1, :)[:]

data = VirusLoadCurve.VirusLoadData(ttdata, vvdata)

par0 = [0.70, 2.88, 6.00, 7.60, 0.20, 5.0]
resultVLF = VirusLoadCurve.fitVLF(data; ϵ=0.1)
summary(resultVLF)
save("test.jld2", Dict("result" => resultVLF, "data" => data))

resultVLF = load("test.jld2", "result")

plot(data)
plot!(resultVLF, empirical=true, ylims=(-2, 9))
ylims!((-2, 9))

pt = VirusLoadCurve.Boxplots(resultVLF)


module DrawSimpleGraphs

using Plots, SimpleGraphs, SimpleDrawing

import SimpleDrawing: newdraw, finish
export newdraw, finish, draw, draw_nodes, draw_edges
export draw_labels

function draw_one_edge(a, b, c, d, hue = "black")
    draw_segment(a, b, c, d, color = hue)
end
"""
    draw_edges(G)
Draw the edges of `G`.
"""
function draw_edges(G::SimpleGraph)
    xy = getxy(G)
    hue = get_line_color(G)
    for ee in elist(G)
        u, v = ee
        draw_one_edge(xy[u][1], xy[u][2], xy[v][1], xy[v][2], hue)
    end
    finish()
end

function draw_one_node(x, y, hue = "black", fill = "white", node_size = 6)
    draw_point(
        x,
        y,
        markerstrokecolor = hue,
        markercolor = fill,
        marker = node_size,
        markerstrokewidth = 1,
    )
end

"""
    draw_nodes(G)
Draw the vertices of `G`.
"""
function draw_nodes(G::SimpleGraph)
    hue = get_line_color(G)
    node_size = get_vertex_size(G)
    xy = getxy(G)
    for v in vlist(G)
        x, y = xy[v]
        fill = get_vertex_color(G, v)
        draw_one_node(x, y, hue, fill, node_size)
    end
    finish()
end

"""
    draw(G)
Draws `G` in its current embedding.
(If `G` does not have an embedding, then it is given a circular
embedding.)

Optional second argument, `clear_first`. If set to `false` the drawing
window is not erased before drawing the graph.
"""
function SimpleDrawing.draw(G::SimpleGraph, clear_first::Bool = true)
    ensure_embed(G)
    if clear_first
        newdraw()
    end
    draw_edges(G)
    draw_nodes(G)
    finish()
end

function draw_labels(G::SimpleGraph, fontsize = 8)
    ensure_embed(G)
    xy = getxy(G)
    for v in G.V
        x, y = xy[v]
        annotate!((x, y, string(v), fontsize))
    end
    finish()
end

include("KnightTourDrawing.jl")

end  # end of module DrawSimpleGraphs


############################################
#
#    dolfin.cpp.fem
# https://fenicsproject.org/docs/dolfin/latest/python/_autogenerated/dolfin.cpp.fem.html
#
#    dolfin.fem module
# https://fenicsproject.org/docs/dolfin/2017.2.0/python/programmers-reference/fem/index.html
#
############################################

@pyclass dolfin LinearVariationalProblem
@pyclass dolfin LinearVariationalSolver
@pyclass dolfin NonlinearVariationalProblem
@pyclass dolfin NonlinearVariationalSolver
@pyclass dolfin LocalSolver
@pyclass dolfin DirichletBC
@pyclass dolfin MultiMeshDirichletBC
@pyclass dolfin AutoSubDomain
@pyclass dolfin Assembler
@pyclass dolfin SystemAssembler
@pyclass dolfin MultiMeshAssembler
@pyclass dolfin DofMap
@pyclass dolfin DiscreteOperators
@pyclass dolfin PETScDMCollection
@pyclass dolfin PointSource
@pyclass dolfin SparsityPatternBuilder

@pyfunc dolfin assemble
@pyfunc dolfin assemble_system
@pyfunc dolfin assemble_local
@pyfunc dolfin errornorm
@pyfunc dolfin project
@pyfunc dolfin solve
@pyfunc dolfin adapt
@pyfunc dolfin create_mesh
@pyfunc dolfin dof_to_vertex_map
@pyfunc dolfin get_coordinates
@pyfunc dolfin set_coordinates
@pyfunc dolfin vertex_to_dof_map

export LinearVariationalProblem, LinearVariationalSolver, NonlinearVariationalProblem,
       NonlinearVariationalSolver, LocalSolver, DirichletBC, MultiMeshDirichletBC,
       AutoSubDomain, Assembler, SystemAssembler, MultiMeshAssembler, DofMap,
       DiscreteOperators, PETScDMCollection, PointSource, SparsityPatternBuilder,
       assemble, assemble_system, assemble_local, errornorm, project, solve,
       adapt, create_mesh, dof_to_vertex_map, get_coordinates, set_coordinates,
       vertex_to_dof_map

############################################
#
#    ufl.finiteelement
#
# https://fenicsproject.org/docs/ufl/1.6.0/ufl.finiteelement.html
#
############################################

@pyclass ufl FiniteElementBase
@pyclass ufl FiniteElement FiniteElementBase

@pyclass ufl MixedElement FiniteElementBase
@pyclass ufl VectorElement MixedElement
@pyclass ufl TensorElement MixedElement

@pyclass ufl EnrichedElement FiniteElementBase
@pyclass ufl RestrictedElement FiniteElementBase
@pyclass ufl TensorProductElement FiniteElementBase

# OuterProductElement:  no need to export
# OuterProductElement:  not exported in `ufl`
@pyclass ufl OuterProductElement FiniteElementBase

# 暂时没搞懂，先注释掉
#@pyclass ufl HDiv OuterProductElement
#@pyclass ufl HCurl OuterProductElement

# no need to export
# not exported in `ufl`
@pyclass ufl OuterProductVectorElement MixedElement
@pyclass ufl TraceElement FiniteElementBase

@pyclass ufl BrokenElement FiniteElementBase

@pyfunc ufl FacetElement
@pyfunc ufl InteriorElement

*(fe1::FiniteElementBase, fe2::FiniteElementBase) = FiniteElementBase(fe1.pyobject * fe2.pyobject)

export FiniteElementBase, FiniteElement, MixedElement, VectorElement, TensorElement, EnrichedElement,
        RestrictedElement, TensorProductElement, BrokenElement, FacetElement, InteriorElement

############################################
#
#    dolfin.cpp.adaptivity
#
# https://fenicsproject.org/docs/dolfin/2018.1.0/python/_autogenerated/dolfin.cpp.adaptivity.html?highlight=timeseries#dolfin.cpp.adaptivity.TimeSeries
#
############################################

# no need to export
# not exported in `dolfin` or `ufl`
@pyclass dolfin GoalFunctional Form
@pyclass dolfin ErrorControl Variable
@pyclass dolfin GenericAdaptiveVariationalSolver Variable

@pyclass dolfin TimeSeries Variable
@pyclass dolfin AdaptiveLinearVariationalSolver GenericAdaptiveVariationalSolver
@pyclass dolfin AdaptiveNonlinearVariationalSolver GenericAdaptiveVariationalSolver

export TimeSeries, AdaptiveLinearVariationalSolver, AdaptiveNonlinearVariationalSolver


# Classifies MNIST digits with a convolutional network.
# Writes out saved model to the file "mnist_conv.bson".
# Demonstrates basic model construction, training, saving,
# conditional early-exit, and learning rate scheduling.
#
# This model, while simple, should hit around 99% test
# accuracy after training for approximately 20 epochs.

using Flux, Flux.Data.MNIST, Statistics
using Flux: onehotbatch, onecold, logitcrossentropy
using Base.Iterators: partition
using Printf, BSON
using Parameters: @with_kw
using CUDAapi
if has_cuda()
    @info "CUDA is on"
    import CuArrays
    CuArrays.allowscalar(false)
end

@with_kw mutable struct Args
    lr::Float64 = 3e-3
    epochs::Int = 20
    batch_size = 128
    savepath::String = "./"
end

# Bundle images together with labels and group into minibatchess
function make_minibatch(X, Y, idxs)
    X_batch = Array{Float32}(undef, size(X[1])..., 1, length(idxs))
    for i in 1:length(idxs)
        X_batch[:, :, :, i] = Float32.(X[idxs[i]])
    end
    Y_batch = onehotbatch(Y[idxs], 0:9)
    return (X_batch, Y_batch)
end

function get_processed_data(args)
    # Load labels and images from Flux.Data.MNIST
    train_labels = MNIST.labels()
    train_imgs = MNIST.images()
    mb_idxs = partition(1:length(train_imgs), args.batch_size)
    train_set = [make_minibatch(train_imgs, train_labels, i) for i in mb_idxs]

    # Prepare test set as one giant minibatch:
    test_imgs = MNIST.images(:test)
    test_labels = MNIST.labels(:test)
    test_set = make_minibatch(test_imgs, test_labels, 1:length(test_imgs))

    return train_set, test_set

end

# Build model
function build_model(args; imgsize = (28,28,1), nclasses = 10)
    cnn_output_size = Int.(floor.([imgsize[1]/8,imgsize[2]/8,32]))

    return Chain(
    # First convolution, operating upon a 28x28 image
    Conv((3, 3), imgsize[3]=>16, pad=(1,1), relu),
    MaxPool((2,2)),

    # Second convolution, operating upon a 14x14 image
    Conv((3, 3), 16=>32, pad=(1,1), relu),
    MaxPool((2,2)),

    # Third convolution, operating upon a 7x7 image
    Conv((3, 3), 32=>32, pad=(1,1), relu),
    MaxPool((2,2)),

    # Reshape 3d tensor into a 2d one using `Flux.flatten`, at this point it should be (3, 3, 32, N)
    flatten,
    Dense(prod(cnn_output_size), 10))
end

# We augment `x` a little bit here, adding in random noise.
augment(x) = x .+ gpu(0.1f0*randn(eltype(x), size(x)))

# Returns a vector of all parameters used in model
paramvec(m) = vcat(map(p->reshape(p, :), params(m))...)

# Function to check if any element is NaN or not
anynan(x) = any(isnan.(x))

accuracy(x, y, model) = mean(onecold(cpu(model(x))) .== onecold(cpu(y)))

function train(; kws...)
    args = Args(; kws...)

    @info("Loading data set")
    train_set, test_set = get_processed_data(args)

    # Define our model.  We will use a simple convolutional architecture with
    # three iterations of Conv -> ReLU -> MaxPool, followed by a final Dense layer.
    @info("Building model...")
    model = build_model(args)

    # Load model and datasets onto GPU, if enabled
    train_set = gpu.(train_set)
    test_set = gpu.(test_set)
    model = gpu(model)

    # Make sure our model is nicely precompiled before starting our training loop
    model(train_set[1][1])

    # `loss()` calculates the crossentropy loss between our prediction `y_hat`
    # (calculated from `model(x)`) and the ground truth `y`.  We augment the data
    # a bit, adding gaussian random noise to our image to make it more robust.
    function loss(x, y)
        x̂ = augment(x)
        ŷ = model(x̂)
        return logitcrossentropy(ŷ, y)
    end

    # Train our model with the given training set using the ADAM optimizer and
    # printing out performance against the test set as we go.
    opt = ADAM(args.lr)

    @info("Beginning training loop...")
    best_acc = 0.0
    last_improvement = 0
    for epoch_idx in 1:args.epochs
        # Train for a single epoch
        Flux.train!(loss, params(model), train_set, opt)

        # Terminate on NaN
        if anynan(paramvec(model))
            @error "NaN params"
            break
        end

        # Calculate accuracy:
        acc = accuracy(test_set..., model)

        @info(@sprintf("[%d]: Test accuracy: %.4f", epoch_idx, acc))
        # If our accuracy is good enough, quit out.
        if acc >= 0.999
            @info(" -> Early-exiting: We reached our target accuracy of 99.9%")
            break
        end

        # If this is the best accuracy we've seen so far, save the model out
        if acc >= best_acc
            @info(" -> New best accuracy! Saving model out to mnist_conv.bson")
            BSON.@save joinpath(args.savepath, "mnist_conv.bson") params=cpu.(params(model)) epoch_idx acc
            best_acc = acc
            last_improvement = epoch_idx
        end

        # If we haven't seen improvement in 5 epochs, drop our learning rate:
        if epoch_idx - last_improvement >= 5 && opt.eta > 1e-6
            opt.eta /= 10.0
            @warn(" -> Haven't improved in a while, dropping learning rate to $(opt.eta)!")

            # After dropping learning rate, give it a few epochs to improve
            last_improvement = epoch_idx
        end

        if epoch_idx - last_improvement >= 10
            @warn(" -> We're calling this converged.")
            break
        end
    end
end

# Testing the model, from saved model
function test(; kws...)
    args = Args(; kws...)

    # Loading the test data
    _,test_set = get_processed_data(args)

    # Re-constructing the model with random initial weights
    model = build_model(args)

    # Loading the saved parameters
    BSON.@load joinpath(args.savepath, "mnist_conv.bson") params

    # Loading parameters onto the model
    Flux.loadparams!(model, params)

    test_set = gpu.(test_set)
    model = gpu(model)
    @show accuracy(test_set...,model)
end

cd(@__DIR__)
train()
test()


using TextAnalysis, Languages, Clustering

filename = joinpath(dirname(@__FILE__), "data", "highlights.csv")

function get_headers(source)
    headers = [ String(s) for s in source[1,:] ]
    source = source[2:end,:] # clear the headers
    headers, source
end

source = readcsv(filename)
headers, source = get_headers(source)

sample = source[:,5]
length(sample)

#fd = FileDocument(sample)
#sd = StringDocument(fd)

text = join(sample, ",")
sd = StringDocument(text)
crps = Corpus([sd])

remove_punctuation!(sd)
update_lexicon!(crps)
lexicon(crps)

update_inverse_index!(crps)
inverse_index(crps)
hash_function!(crps, TextHashFunction())

crps["Handelskrieg"]

m = DocumentTermMatrix(crps)
D = dtm(m, :dense)
T = tf_idf(D)
cl = kmeans(T, 5)

m = DocumentTermMatrix(crps)
k = 2            # number of topics
iteration = 1000 # number of gibbs sampling iterations
alpha = 0.1      # hyper parameter
beta = 0.1       # hyber parameter
l = lda(m, k, iteration, alpha, beta) # l is k x word matrix.
                                      # value is probablity of occurrence of a word in a topic.