versioninfo()

y = [3 4 5]'
X = [1 1 1; 1 6 7]'

X
y
X'y
X'X

a = [4 5 5]
b = [4, 5, 5]

(X'X)^(-1) * X'y

(X'X)^(-1) * X'y

f(x) = 2x^3 + 3x^2 + 6

cos(X'X)
cos.(X'X)

cos((X'X)[1, 1])

[2x + 3y for x = 1:5, y in [10, 20, 30]]

μ = 7
χ = 8
(μ + 8) * (χ - 7)

χμθ = 9

using Pkg
Pkg.add("CSV") # установка пакета из интернета, делается 1 раз

using CSV, Econometrics, RDatasets # library (R) or import (python)

psid = dataset("Ecdat", "PSID")
psid.Age
psid."Age"

describe(psid)
first(psid, 7)
last(psid, 6)

model_a = fit(EconometricModel,
    @formula(log(1 + Earnings) ~ Age + Educatn), psid)


function Homogenization(U, Chis, N, E, nu)
    function homogenizeP()
        function MacroP(dir::Int64, U::Matrix{Float64}, elID::Int64, coords::Matrix{Float64}, E::Float64, nu::Float64)
            gps = gaussPoints1D(2)
            if dir == 1
                P = zeros(Float64, 4, 1)
                for gp in eachrow(gps)
                    B, detJ, PK1_1D, A_2D = constitutiveEqs(U, coords, 1/2, gp[2], E, nu)
                    P += gp[1]*PK1_1D*(1/2)
                end
                P11, P21 = P[1], P[4]
                return P11, P21
            elseif dir == 2
                P = zeros(Float64, 4, 1)
                for gp in eachrow(gps)
                    B, detJ, PK1_1D, A_2D = constitutiveEqs(U, coords, gp[2], 1/2, E, nu)
                    P += gp[1]*PK1_1D*(1/2)
                end
                P22, P12 = P[2], P[3]
                return P22, P12
            end
        end

        P11, P21 = 0, 0
        for i in N:N:N*N
            Ce = vec(DOFs[i, :, :]')
            P11_i, P21_i = MacroP(1, U[Ce, :], i, elCoords[i, :, :], E, nu)
            P11 += P11_i
            P21 += P21_i
        end

        P22, P12 = 0, 0
        for i in N*(N-1)+1:1:N*N
            Ce = vec(DOFs[i, :, :]')
            P22_i, P12_i = MacroP(2, U[Ce, :], i, elCoords[i, :, :], E, nu)
            P22 += P22_i
            P12 += P12_i
        end
        return L/(L*L)*hcat(P11, P22, P12, P21)
    end

    function homogenizeA()
        function MacroA(dir::Int64, U::Matrix{Float64}, elID::Int64, coords::Matrix{Float64}, E::Float64, nu::Float64)
            gps = gaussPoints1D(2)
            if dir == 1
                Ael = zeros(Float64, 4, 4)
                Ce = vec(DOFs[elID, :, :]')
                for i in 1:4
                    for j in 1:4
                        for gp in eachrow(gps)
                            B, detJ, PK1_1D, A_2D = constitutiveEqs(U, coords, 1/2, gp[2], E, nu)
                            Ql = B*Chis[i][Ce,:]
                            Qr = B*Chis[j][Ce,:]
                            Ael[i,j] += gp[1]*(Ql'*A_2D*Qr)[1]*(dx/2)
                        end
                    end
                end
                return Ael
            elseif dir == 2
                Ael = zeros(Float64, 4, 4)
                Ce = vec(DOFs[elID, :, :]')
                for i in 1:4
                    for j in 1:4
                        for gp in eachrow(gps)
                            B, detJ, PK1_1D, A_2D = constitutiveEqs(U, coords, gp[2], 1/2, E, nu)
                            Ql = B*Chis[i][Ce,:]
                            Qr = B*Chis[j][Ce,:]
                            Ael[i,j] += gp[1]*(Ql'*A_2D*Qr)[1]*(dx/2)
                        end
                    end
                end
                return Ael
            end
        end

        A14 = zeros(Float64, 4, 4)
        for i in N:N:N*N
            Ce = vec(DOFs[i, :, :]')
            A_i = MacroA(1, U[Ce, :], i, elCoords[i, :, :], E, nu)
            A14 += A_i
        end

        A23 = zeros(Float64, 4, 4)
        for i in N*(N-1)+1:1:N*N
            Ce = vec(DOFs[i, :, :]')
            A_i = MacroA(2, U[Ce, :], i, elCoords[i, :, :], E, nu)
            A23 += A_i
        end

        return L/(L*L)*vcat(A14[1,:]', A23[2,:]', A23[3,:]', A14[4,:]')
    end
    return homogenizeP(), homogenizeA()
end


using Arpack

function circle_line_intersections(a1::T, a2::T, b::T, r::T) where T
    """
    Calculates the points intersecting points between the 2d plane defined
    as a1*x + a2*y = b
    and the circle x^2 + y^2 = r^2
    Returns x1, y1, x2, y2 such that [x1; y1] and [x2; y2] are the two intersecting points.
    If no intersection(s) exist then NaN values are returned.
    Reference: http://mathworld.wolfram.com/Circle-LineIntersection.html
    """
    # Normalize, i.e. make [a1; a2] unit norm
    norm_a = sqrt(a1^2 + a2^2)
    if norm_a < 1e-11  # Very badly scaled problem
        @warn "Badly scaled circe-line"
        return T(NaN), T(NaN), T(NaN), T(NaN)
    end
    a1 /= norm_a; a2 /= norm_a; b /= norm_a

    # First calculate a point (x, y) on the line
    # and dx, dy such that (x + dx, y + dy) lies also on the line.
    if abs(a1) > abs(a2) # Avoid dividing with something small
        x = (b - a2)/a1
        y = one(T)

        dx = -a2/a1
        dy = one(T)
    else
        x = one(T)
        y = (b - a1)/a2

        dx = one(T)
        dy = -a1/a2
    end

    # Make (dx, dy) unit norm
    norm_d = sqrt(dx^2 + dy^2)
    dx /= norm_d; dy /= norm_d

    D = x*(y + dy) - (x + dx)*y
    #= Allow for slightly infeasible solutions
    if abs(r)/abs(D) <= one(T)
        @show abs(r)/abs(D) - 1
        if abs(r)/abs(D) >= one(T) - 1e-11
            D = r
        end
    end
    =#
    if r >= abs(D)
        c = sqrt(r^2 - D^2)

        x1 = D*dy; y1 = -D*dx
        x2 = x1;  y2 = y1;

        δ1 = dx*c
        if dy < 0; δ1 = -δ1 end
        δ2 = abs(dy)*c

        x1 += δ1; y1 += δ2
        x2 -= δ1; y2 -= δ2

        return x1, y1, x2, y2
    else
        return T(NaN), T(NaN), T(NaN), T(NaN)
    end
end

function trs_robust(P::AbstractArray{T}, q::AbstractVector{T}, r::T; tol=0.0, v0=zeros((0,)), kwargs...) where T
    n = length(q)
    if n < 15
        X, info =  trs_boundary_small(Symmetric(P), q, r; kwargs...)
    else
        try
            X, info = trs_boundary(Symmetric(P), q, r; tol=tol, v0=v0, kwargs...)
        catch e
            @warn "Error:", e, " - switching to a direct solver"
            X, info = trs_boundary_small(Symmetric(P), q, r; kwargs...)
        end
    end
    # @show info, norm(P*X[:,1] + info.λ[1]*C*X[:, 1] + q)
    return X, info
end


function Collocation_Matrix(N)

      #Radau
      if  N == 2

      t1 = 0.3333333
      t2 = 1.0

      M1 = [
            t1 1 / 2 * t1^2
            t2 1 / 2 * t2^2
            ]
      M2 = [
            1 t1
            1 t2
            ]

      M = M1 * inv(M2)

      elseif  N == 3

            t1 = 0.155051
            t2 = 0.644949
            t3 = 1.0

            M1 = [
                  t1 1 / 2 * t1^2 1 / 3 * t1^3
                  t2 1 / 2 * t2^2 1 / 3 * t2^3
                  t3 1 / 2 * t3^2 1 / 3 * t3^3
                  ]
            M2 = [
                  1 t1 t1^2
                  1 t2 t2^2
                  1 t3 t3^2
                  ]

            M = M1 * inv(M2)

      elseif N == 4

            t1 = 0.088588;
            t2 = 0.409467;
            t3 = 0.787659;
            t4 = 1;

            M1 = [
                  t1 1 / 2 * t1^2 1 / 3 * t1^3 1 / 4 * t1^4
                  t2 1 / 2 * t2^2 1 / 3 * t2^3 1 / 4 * t2^4
                  t3 1 / 2 * t3^2 1 / 3 * t3^3 1 / 4 * t3^4
                  t4 1 / 2 * t4^2 1 / 3 * t4^3 1 / 4 * t4^4
                  ]
            M2 = [
                  1 t1 t1^2 t1^3
                  1 t2 t2^2 t2^3
                  1 t3 t3^2 t3^3
                  1 t4 t4^2 t4^3
                  ]

            M = M1 * inv(M2)

      elseif N == 5

            t1 = 0.057104;
            t2 = 0.276843;
            t3 = 0.583590;
            t4 = 0.860240;
            t5 = 1;

            M1 = [
                  t1 1 / 2 * t1^2 1 / 3 * t1^3 1 / 4 * t1^4  1 / 5 * t1^5
                  t2 1 / 2 * t2^2 1 / 3 * t2^3 1 / 4 * t2^4  1 / 5 * t2^5
                  t3 1 / 2 * t3^2 1 / 3 * t3^3 1 / 4 * t3^4  1 / 5 * t3^5
                  t4 1 / 2 * t4^2 1 / 3 * t4^3 1 / 4 * t4^4  1 / 5 * t4^5
                  t4 1 / 2 * t4^2 1 / 3 * t4^3 1 / 4 * t4^4  1 / 5 * t5^5
                  ]
            M2 = [
                  1 t1 t1^2 t1^3 t1^4
                  1 t2 t2^2 t2^3 t2^4
                  1 t3 t3^2 t3^3 t3^4
                  1 t4 t4^2 t4^3 t4^4
                  1 t4 t4^2 t4^3 t5^4
                  ]

            M = M1 * inv(M2)

      end

      return M
end


#bring end points to the equator -> define 3D rotation matrix
function rotate_points(lons,lats)
	#... and of end points
	x0=cosd.(lats).*cosd.(lons)
	y0=cosd.(lats).*sind.(lons)
	z0=sind.(lats)

	#get the rotation matrix:
	#1) rotate around x axis to put first point at z=0
	theta=atan(-z0[1],y0[1])
	R1=[[1;0;0] [0;cos(theta);sin(theta)] [0;-sin(theta);cos(theta)]]
	tmp0=[x0;y0;z0]; tmp1=R1*tmp0; x1=tmp1[1,:]; y1=tmp1[2,:]; z1=tmp1[3,:]
	x0=x1; y0=y1; z0=z1
	#2) rotate around z axis to put first point at y=0
	theta=atan(x0[1],y0[1])
	R2=[[cos(theta);sin(theta);0] [-sin(theta);cos(theta);0] [0;0;1]]
	tmp0=transpose([x0 y0 z0]); tmp1=R2*tmp0; x1=tmp1[1,:]; y1=tmp1[2,:]; z1=tmp1[3,:]
	x0=x1; y0=y1; z0=z1
	#3) rotate around y axis to put second point at z=0
	theta=atan(-z0[2],-x0[2])
	R3=[[cos(theta);0;-sin(theta)] [0;1;0] [sin(theta);0;cos(theta)]]
	tmp0=transpose([x0 y0 z0]); tmp1=R3*tmp0; x1=tmp1[1,:]; y1=tmp1[2,:]; z1=tmp1[3,:]
	x0=x1; y0=y1; z0=z1

	x0,y0,z0,R3*R2*R1
end

function rotate_XCYC(Γ,R)
	#3D carthesian coordinates:
	lon=Γ.XC; lat=Γ.YC
	x=cosd.(lat)*cosd.(lon)
	y=cosd.(lat)*sind.(lon)
	z=sind.(lat)

	#rotation R:
    γ=Γ.XC.grid
	tmpx=γ.write(x); tmpy=γ.write(y); tmpz=γ.write(z)
	tmp1=findall((!isnan).(tmpx))
	tmpx2=tmpx[tmp1]; tmpy2=tmpy[tmp1]; tmpz2=tmpz[tmp1]
	tmp2=[tmpx2';tmpy2';tmpz2']

	tmp3=R*tmp2

	tmpx2=tmp3[1,:]; tmpy2=tmp3[2,:]; tmpz2=tmp3[3,:]
	tmpx[tmp1]=tmpx2; tmpy[tmp1]=tmpy2; tmpz[tmp1]=tmpz2
	x=γ.read(tmpx,Γ.XC); y=γ.read(tmpy,Γ.XC); z=γ.read(tmpz,Γ.XC)

	x,y,z
end

##

function shorter_paths!(xyz,xyz0,msk_in)
	(x0,y0,z0)=xyz0[:]
	(x,y,z)=xyz[:]

	#split in two segments:
	theta=zeros(2)
	theta[1]=atan(y0[1],x0[1])
	theta[2]=atan(y0[2],x0[2])

    γ=msk_in[1].grid
	tmpx=γ.write(x); tmpy=γ.write(y); tmpz=γ.write(z);
	tmptheta=atan.(tmpy,tmpx)
	if theta[2]<0;
		tmp00=findall(tmptheta.<=theta[2])
		tmptheta[tmp00].=tmptheta[tmp00].+2*pi
		theta[2]=theta[2]+2*pi
	end

	msk_out=[]
	for kk in 1:3
		#select field to treat:
		mm=msk_in[kk]
		#select the shorther segment:
		tmpm=γ.write(mm)
       if theta[2]-theta[1]<=pi
            tmpm[findall( (tmptheta.>theta[2]).|(tmptheta.<theta[1]) )].=0.0
        else
            tmpm[findall( (tmptheta.<=theta[2]).&(tmptheta.>=theta[1]) )].=0.0
        end
        mm=γ.read(tmpm,mm);
        #store result:
		push!(msk_out,mm)
    end
	msk_out[:]
end


using Base.Test

using ucbature

const my_tol = 1e-2

const dim = 2
const nb_evals = 1e6
const xmin = fill(-1.0, dim)
const xmax = fill( 1.0, dim)
const nb_zones_per_dim = 5
const k_exploration = 0.01
const nb_init_evals_per_zone = 10

sphere(x) = vecdot(x, x) < 1.0 ? 1.0 : 0.0

res_mc = ucbature.mc(sphere, xmin, xmax, nb_evals)
@test isapprox(res_mc, pi, atol=my_tol)

res_str = ucbature.str(sphere, xmin, xmax, nb_evals, nb_zones_per_dim)
@test isapprox(res_str, pi, atol=my_tol)

res_ucb = ucbature.ucb(sphere, xmin, xmax, nb_evals, nb_zones_per_dim,
                       k_exploration, nb_init_evals_per_zone)
@test isapprox(res_ucb, pi, atol=my_tol)


using Zygote, LinearAlgebra
using ArnoldiMethod: partialschur, SR

function test_eigmin(A::AbstractMatrix)
    decomp, history = partialschur(A; nev = 1, which = SR())
    ev = minimum(real.(decomp.eigenvalues))
    return ev
end

A = rand(4, 4); A = A + A';

@show eigmin(A)
@show test_eigmin(A)
@show Zygote.gradient(test_eigmin, A)


#=##############################################################################
# DESCRIPTION
    Creating and postprocessing of observer positions.

# AUTHORSHIP
  * Author    : Eduardo J. Alvarez and Tyler Critchfield
  * Email     : Edo.AlvarezR@gmail.com
  * Created   : Nov 2019
  * License   : MIT
=###############################################################################

"""
    `observer_sphere(R, nR, ntht, nphi; Rmin=0.1, thtmin=5, thtmax=175)`

Returns a spherical grid of observers.

# ARGUMENTS
* `R::Real`                     : Sphere radius.
* `nR::Int`                     : Number of radial sections (give 0 for a hollow
                                    sphere).
* `ntht::Int`                   : Number of the elevation sections.
* `nphi::Int`                   : Number of azimuth sections.

# OPTIONAL ARGUMENTS
* `Rmin::Real`                  : Lower bound of R to mesh (given as a fraction
                                    of R).
* `thtmin::Real`                : Lower bound of elevation to mesh (in degrees).
* `thtmax::Real`                : Upper bound of elevation to mesh (in degrees).
* `phimin::Real`                : Lower bound of azimuth to mesh (in degrees).
* `phimax::Real`                : Upper bound of azimuth to mesh (in degrees).
* `half::Bool`                  : Half or full sphere.
* `C::Array{Real, 1}`           : Center [x,y,z] of the sphere.
* `rotation::Array{Real, 1}`    : Rotation of sphere (in degrees) about x, y,
                                    and z axes.
* `save_path::String`           : Saves the sphere to a this path.
* `file_name::String`           : Name of file when saving sphere.
* `fmt::String`                 : Format of the file when saving. Options:
                                    "plot3d", "vtk".

# NOTES
* Elevation angles go from 0 to 180, meanwhile azimuth angles go from 0 to 360.
* To generate a half sphere use `phimax=180`.
* Give `nphi=2*ntht` to get cells of aspect ratio 1.
* A circular line (instead of a spherical surface/volume) can be generated with
    `nphi=0` and `nR=0`. This will return a circular line laying on the
    zx-plane. Use `C` and `rotation` to translate a rotate the line.
"""
function observer_sphere(R::Real, nR::Int, ntht::Int, nphi::Int;
                            Rmin::Real=0.1,
                            thtmin=5, thtmax=175,
                            phimin=0, phimax=360,
                            C::Array{T1, 1}=zeros(3),
                            rotation::Array{T2, 1}=zeros(3),
                            save_path::Union{Nothing, String}=nothing,
                            file_name::String="sphere",
                            fmt::String="vtk"
                         ) where {T1<:Real, T2<:Real}

    P_min = [pi/180*thtmin, pi/180*phimin, R*Rmin] # Lower bound (theta, phi, r)
    P_max = [pi/180*thtmax, pi/180*phimax, R]   # Upper bound (theta, phi, r)
    NDIVS = [ntht, nphi, nR]                    # Number of divisions (cells)
                                                #  of (theta, phi, r)

    # Case that grid is a circular line
    if nphi==0 && nR==0
        P_min[3] = P_max[3]
        P_min[2] = P_max[2]
        loop_dim = Int(P_min[1]==0 && P_max[1]==2*pi) # Coordinate to loop (0==no looping)

    else

        if NDIVS[3]==0; P_min[3]=P_max[3]; end;
        loop_dim = 2*Int(P_min[2]==0 && P_max[2]==2*pi) # Coordinate to loop (0==no looping)

    end

    # Generates parametric (theta, phi, r) grid
    grid = gt.Grid(P_min, P_max, NDIVS, loop_dim)

    # Transforms the grid into a spherical cartesian space
    my_transform(X) = gt.spherical3D(vcat(X[3], X[1:2]))

    # Rotate and translate the sphere
    rotM = gt.rotation_matrix2(rotation...)
    my_transform2(X) = rotM*my_transform(X) + C

    # Apply transformation
    gt.transform!(grid, my_transform2)

    if save_path!=nothing
        if fmt=="plot3d"
            # Create .xyz file
            gt.generatePLOT3D(grid, file_name; path=save_path, trickwopwop=true)
        elseif fmt=="vtk"
            # Create .vtk file
            gt.save(grid, file_name; path=save_path)
        else
            error("Invalid file format $fmt.")
        end
    end

    return grid
end


using Test
using BoundaryIntegralMethod
using BoundaryIntegralMethod.DirectSweep

# Create a circular boundary
# Internal n
ni=2.0
# External n
ne=1.0
# Radius
r = 1.0
b = BoundaryIntegralMethod.Circle(r, ni, ne)

kTrue = 13.708675947990717 - 0.0006855557536092232im
k0= 13.7 -.0001im
n=100
(ks, xs) = directSweep(b, n, k0)
convergence = 1e-6
i = 1
(kf, xf) = residualInverseIteration(25, convergence, b, n, ks[i], xs[i,:])
@test abs(kTrue-kf) ≈ 0 atol=10e-10


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

# This file replicates tests from the official documentation of Stata
# Some DOF adjustements are necessary because Stata uses different corrections

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

# MISE EN PLACE

using Test
using CSV
using DataFrames
using Microeconometrics
using StatsBase
using StatsModels

const datadir = joinpath(dirname(@__FILE__), "..", "data")

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

S           = CSV.read(joinpath(datadir, "auto.csv"))
S[!, :gpmw] = ((1.0 ./ S[:, :mpg]) ./ S[:, :weight]) * 100000
M           = @micromodel(response => gpmw, control => foreign + 1)

@testset "OLS Homoscedastic" begin

    D = Microdata(S, M, corr = Homoscedastic())
    E = fit(OLS, D)

    β = [0.24615258; 1.60900394]
    σ = [0.05494872; 0.02996078]

    @test isapprox(coef(E), β, atol = 1e-7, rtol = 1e-7)
    @test isapprox(stderror(E), σ, atol = 1e-7, rtol = 1e-7)
    @test isapprox(loglikelihood(E), 9.39839211)
    @test isapprox(nullloglikelihood(E), 0.30171734)
    @test isapprox(deviance(E), 3.36079458)
    @test isapprox(nulldeviance(E), 4.29750019)
    @test isapprox(r2(E), 0.21796522, atol = 1e-7, rtol = 1e-7)
    @test isapprox(adjr2(E), 0.20710363, atol = 1e-7, rtol = 1e-7)
    @test dof(E) == 2
end

@testset "OLS Heteroscedastic" begin

    D = Microdata(S, M, corr = Heteroscedastic())
    E = fit(OLS, D)

    β = [0.24615258; 1.60900394]
    σ = [0.06792384; 0.02345347]
    c = (nobs(E) - dof(E)) / (nobs(E) - 1)
    σ = σ * sqrt(c)

    @test isapprox(coef(E), β, atol = 1e-7, rtol = 1e-7)
    @test isapprox(stderror(E), σ, atol = 1e-7, rtol = 1e-7)
    @test isapprox(r2(E), 0.21796522, atol = 1e-7, rtol = 1e-7)
    @test isapprox(adjr2(E), 0.20710363, atol = 1e-7, rtol = 1e-7)
end

T           = Dict(column => Union{AbstractFloat, Missing} for column in ["age", "tenure"])
S           = CSV.read(joinpath(datadir, "regsmpl.csv"), types = T)
S[!, :age2] = S[:, :age].^2
M           = @micromodel(response => ln_wage, control => age + age2 + tenure + 1)
C           = Dict(term => ContinuousTerm for term in [:age, :age2, :tenure])

@testset "OLS Clustered" begin

    W = Clustered(S, :idcode)
    D = Microdata(S, M, corr = W, hints = C)
    E = fit(OLS, D)

    β = [0.07521723; -0.00108513; 0.03908767; 0.33398213]
    σ = [0.00457113;  0.00007785; 0.00144254; 0.06419184]
    c = (nobs(E) - dof(E)) / (nobs(E) - 1)
    σ = σ * sqrt(c)

    @test isapprox(coef(E), β, atol = 1e-7, rtol = 1e-7)
    @test isapprox(stderror(E), σ, atol = 1e-7, rtol = 1e-7)
    @test isapprox(r2(E), 0.16438516, atol = 1e-7, rtol = 1e-7)
    @test isapprox(adjr2(E), 0.16429594, atol = 1e-7, rtol = 1e-7)
end

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

S = CSV.read(joinpath(datadir, "hsng2.csv"))
M = @micromodel(
    response   => rent,
    control    => pcturban + 1,
    treatment  => hsngval,
    instrument => faminc + region
)

@testset "TSLS" begin

    D = Microdata(S, M, corr = Homoscedastic())
    E = fit(IV, D)

    β = [0.00223983; 0.08151597; 120.70651454]
    σ = [0.00033876; 0.30815277;  15.70688390]

    @test isapprox(coef(E), β, atol = 1e-7, rtol = 1e-7)
    @test isapprox(stderror(E), σ, atol = 1e-7, rtol = 1e-7)
    @test isapprox(r2(E), 0.59888202, atol = 1e-7, rtol = 1e-7)
    @test isapprox(adjr2(E), 0.58181317, atol = 1e-7, rtol = 1e-7)
end

@testset "IV GMM" begin

    D = Microdata(S, M, corr = Heteroscedastic())
    E = fit(IV, D, method = "Two-step GMM")

    β = [0.00146433; 0.76154816; 112.12271295]
    σ = [0.00044727; 0.28951046;  10.80234023]
    c = nobs(E) / (nobs(E) - 1)
    σ = σ * sqrt(c)

    @test isapprox(coef(E), β, atol = 1e-7, rtol = 1e-7)
    @test isapprox(stderror(E), σ, atol = 1e-7, rtol = 1e-7)
    @test isapprox(r2(E), 0.66162834, atol = 1e-7, rtol = 1e-7)
    @test isapprox(adjr2(E), 0.64722954, atol = 1e-7, rtol = 1e-7)
end

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

S = CSV.read(joinpath(datadir, "lbw.csv"))
M = @micromodel(response => low, control => age + lwt + race + smoke + ptl + ht + ui + 1)
C = Dict(:race => DummyCoding(base = "white"))
D = Microdata(S, M, corr = Homoscedastic(), hints = C)

@testset "Logit" begin

    E = fit(Logit, D)

    β = [-0.02710031; -0.01515082; 1.26264728; 0.86207916; 0.92334482;
          0.54183656;  1.83251780; 0.75851348; 0.46122388]
    σ = [ 0.03645043;  0.00692588; 0.52641014; 0.43915315; 0.40082664;
          0.34624900; 0.69162923; 0.45937677; 1.20458975]

    @test isapprox(coef(E), β, atol = 1e-7, rtol = 1e-7)
    @test isapprox(stderror(E), σ, atol = 1e-7, rtol = 1e-7)
    @test isapprox(deviance(E), 201.44799113, atol = 1e-7, rtol = 1e-7)
    @test isapprox(loglikelihood(E), -100.72399557, atol = 1e-7, rtol = 1e-7)
    @test isapprox(nullloglikelihood(E), -117.33599810, atol = 1e-7, rtol = 1e-7)
    @test isapprox(aic(E), 219.44799113, atol = 1e-7, rtol = 1e-7)
    @test isapprox(aicc(E), 220.45357772, atol = 1e-7, rtol = 1e-7)
    @test isapprox(bic(E), 248.62371427, atol = 1e-7, rtol = 1e-7)
end

@testset "Probit" begin

    E = fit(Probit, D)

    β = [-0.01754447; -0.00882045; 0.74752563; 0.51447107; 0.56276006;
          0.31782665;  1.09945075; 0.46279438; 0.26827531]
    σ = [ 0.02162924;  0.00397343; 0.31664054; 0.25558235; 0.23577825;
          0.20012526;  0.41927933; 0.27560931; 0.70152540]

    @test isapprox(coef(E), β, atol = 1e-7, rtol = 1e-7)
    @test isapprox(stderror(E), σ, atol = 1e-7, rtol = 1e-7)
    @test isapprox(deviance(E), 201.12189887, atol = 1e-7, rtol = 1e-7)
    @test isapprox(loglikelihood(E), -100.56094943, atol = 1e-7, rtol = 1e-7)
    @test isapprox(nullloglikelihood(E), -117.33599810, atol = 1e-7, rtol = 1e-7)
end

@testset "Cloglog" begin

    E = fit(Cloglog, D)

    β = [-0.02309612; -0.01129839; 1.07937099;  0.72856146; 0.73324598;
          0.33131635;  1.42557598; 0.56457925; -0.09224780]
    σ = [ 0.02906676;  0.00521572; 0.40185917;  0.32942310; 0.30247340;
          0.20833347;  0.45334563; 0.34357409;  0.90994949]

    @test isapprox(coef(E), β, atol = 1e-7, rtol = 1e-7)
    @test isapprox(stderror(E), σ, atol = 1e-7, rtol = 1e-7)
    @test isapprox(deviance(E), 202.16661013, atol = 1e-7, rtol = 1e-7)
    @test isapprox(loglikelihood(E), -101.08330506, atol = 1e-7, rtol = 1e-7)
    @test isapprox(nullloglikelihood(E), -117.33599810, atol = 1e-7, rtol = 1e-7)
end

@testset "Gompit" begin

    E = fit(Gompit, D)

    β = [ 0.01937153;  0.00827072; -0.65246666; -0.46606001; -0.55757061;
         -0.40377232; -1.06494339; -0.51695211; -0.63626157]
    σ = [ 0.02104003;  0.00375728;  0.32497917;  0.24843060;  0.23366182;
          0.25411648;  0.48787151;  0.29917807;  0.67163691]

    @test isapprox(coef(E), β, atol = 1e-7, rtol = 1e-7)
    @test isapprox(stderror(E), σ, atol = 1e-7, rtol = 1e-7)
    @test isapprox(deviance(E), 200.35974543, atol = 1e-7, rtol = 1e-7)
    @test isapprox(loglikelihood(E), -100.17987271, atol = 1e-7, rtol = 1e-7)
    @test isapprox(nullloglikelihood(E), -117.33599810, atol = 1e-7, rtol = 1e-7)
end

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

S             = CSV.read(joinpath(datadir, "dollhill3.csv"))
S[!, :pyears] = log.(S[:, :pyears])

M = @micromodel(
    response => deaths,
    control  => smokes + agecat + 1,
    offset   => pyears
)

@testset "Poisson" begin

    D = Microdata(S, M, corr = Homoscedastic())
    E = fit(Poisson, D)

    β = [0.35453564; 1.48400701; 2.62750512; 3.35049279; 3.70009645; -7.91932571]
    σ = [0.10737412; 0.19510337; 0.18372727; 0.18479918; 0.19221951;  0.19176182]

    @test isapprox(coef(E), β, atol = 1e-7, rtol = 1e-7)
    @test isapprox(stderror(E), σ, atol = 1e-7, rtol = 1e-7)
    @test isapprox(deviance(E), 12.13236640, atol = 1e-7, rtol = 1e-7)
    @test isapprox(nulldeviance(E), 644.26899081, atol = 1e-7, rtol = 1e-7)
    @test isapprox(loglikelihood(E), -33.60015344, atol = 1e-7, rtol = 1e-7)
    @test isapprox(nullloglikelihood(E), -495.06763568, atol = 1e-7, rtol = 1e-7)
end

S = CSV.read(joinpath(datadir, "website.csv"))
M = @micromodel(
    response   => visits,
    control    => ad + female + 1,
    treatment  => time,
    instrument => phone + frfam
)

@testset "IVPoisson" begin

    D = Microdata(S, M)
    E = fit(IVPoisson, D, method = "Two-step GMM")

    β = [0.05892941; 0.13734403; -0.02477071; 1.04150546]
    σ = [0.01079421; 0.01015705;  0.03762177; 0.03858479]
    c = nobs(E) / (nobs(E) - 1)
    σ = σ * sqrt(c)

    @test isapprox(coef(E), β, atol = 1e-7, rtol = 1e-7)
    @test isapprox(stderror(E), σ, atol = 1e-7, rtol = 1e-7)
end

S = CSV.read(joinpath(datadir, "trip.csv"))
M = @micromodel(
    response   => trips,
    control    => cbd + ptn + worker + weekend + 1,
    treatment  => tcost,
    instrument => pt
)

@testset "Mullahy" begin

    D = Microdata(S, M)
    E = fit(Mullahy, D, method = "Two-step GMM")

    β = [0.03521846; -0.00839804; -0.01131463; 0.66230176; 0.30093231; 0.26544230]
    σ = [0.00981818;  0.00201721;  0.00218185; 0.05199088; 0.03626820; 0.15501267]
    c = nobs(E) / (nobs(E) - 1)
    σ = σ * sqrt(c)

    @test isapprox(coef(E), β, atol = 1e-7, rtol = 1e-7)
    @test isapprox(stderror(E), σ, atol = 1e-7, rtol = 1e-7)
end

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

S            = CSV.read(joinpath(datadir, "cattaneo2.csv"))
S[!, :mage2] = S[:, :mage].^2

M = @micromodel(
    response  => bweight,
    control   => mmarried + mage + mage2 + fbaby + medu + 1,
    treatment => mbsmoke
)

@testset "IPW" begin

    D = Microdata(S, M)
    E = fit(IPW, Probit, D)

    β = [-230.68863780; 3403.46270868]
    σ = [  25.81524380;    9.57136890]
    c = nobs(E) / (nobs(E) - 1)
    σ = σ * sqrt(c)

    @test isapprox(coef(E), β, atol = 1e-7, rtol = 1e-7)
    @test isapprox(stderror(E), σ, atol = 1e-7, rtol = 1e-7)
    @test dof(E) == 2
end

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

S = CSV.read(joinpath(datadir, "income.csv"))
M = @micromodel(response => inc, control => edu + exp + 1)

@testset "Chow" begin

    E0 = fit(OLS, Microdata(S, M, subset = (S[:, :male] .== 1)))
    E1 = fit(OLS, Microdata(S, M, subset = (S[:, :male] .== 0)))

    Microeconometrics.lmul!(((nobs(E0) - 1) / nobs(E0)) * 277 / 276, E0.V)
    Microeconometrics.lmul!(((nobs(E1) - 1) / nobs(E1)) * 277 / 276, E1.V)

    E  = chow_test(E0, E1)

    p = [0.20345785; 0.59592600; 0.28068223]

    @test isapprox(pval(E), p, atol = 1e-7, rtol = 1e-7)
end


# -----------------------------------------------------------
#
# Construction of multidimensional DG derivative matrices
# based on the 1-D derivative matrix
#
# -----------------------------------------------------------

# Efficiency criticality: HIGH
# Matrix computations are only performed once,
# but this can be the main bottleneck if not done right

# Accuracy criticality: HIGH
# Critical for accurate PDE evolution

function D_matrix(d::Int, k::Int, n::Int, srefVD::Array{NTuple{3, CartesianIndex{D}}, 1},
        srefDV::Dict{NTuple{3, CartesianIndex{D}}, Int}; scheme = "sparse") where D
    D_matrix(Val(d), k, n, srefVD, srefDV, Val(Symbol(scheme)))
end
function D_matrix(::Val{d}, k::Int, n::Int, srefVD::Array{NTuple{3, CartesianIndex{D}}, 1},
        srefDV::Dict{NTuple{3, CartesianIndex{D}}, Int}, scheme::Val{Scheme}) where {d, D, Scheme}

    # len = length(srefVD[:,1])
    len = length(srefVD)
    @assert length(srefVD[:,1]) == length(srefVD)
    V2D_1D = V2Dref(Val(1),k,n,Val(:sparse)) # Why not 'scheme'?
    D2V_1D = D2Vref(Val(1),k,n,Val(:sparse))
    I = Int[]; J = Int[]; V = Float64[]

    # 1-dimensional derivative matrix - this will give all coefficient info
    Dmat_1D = periodic_DLF_matrix(k, n)

    for j in 1:len
        lcm = srefVD[j]
        l = lcm[1][d]
        c = lcm[2][d]
        m = lcm[3][d]
        j_1D = D2V_1D[(CartesianIndex(l),CartesianIndex(c),CartesianIndex(m))]
        derivs = Dmat_1D[:, j_1D]::SparseVector{Float64,Int}

        for i_1D in derivs.nzind
            lcm_1D = V2D_1D[i_1D]
            # using tuple constructor independent of Julia's metaprogramming:
            # make_cartesian_index(d, arr1, arr2) takes arr::CartesianIndex{1}
            # and makes a new CartesianIndex{D} using arr2::CartesianIndex{D}
            # with the dth value replaced by arr1[1]
            level2 = make_cartesian_index(d, lcm_1D[1], lcm[1])
            cutoff(scheme, level2, n) && continue
            cell2 = make_cartesian_index(d, lcm_1D[2], lcm[2])
            mode2 = make_cartesian_index(d, lcm_1D[3], lcm[3])
            i = srefDV[(level2, cell2, mode2)]
            push!(I, i)
            push!(J, j)
            push!(V, derivs[i_1D])
        end
    end
    # dropzeros! does not seem helpful for this matrix:
    return sparse(I, J, V, len, len, +)
end

function D_matrix(D::Int, d::Int, k::Int, n::Int; scheme="sparse")
    VD = V2Dref(Val(D), k, n, Val(Symbol(scheme)))
    DV = D2Vref(Val(D), k, n, Val(Symbol(scheme)))
    return D_matrix(d, k, n, VD, DV; scheme=scheme)
end

function grad_matrix(D::Int, k::Int, n::Int; scheme="sparse")
    return [D_matrix(D, d, k, n; scheme=scheme) for d in 1:D]
end

function laplacian_matrix(D::Int, k::Int, n::Int; scheme="sparse")
    len = get_size(Val(D), k, n, Val(Symbol(scheme)))
    lap = spzeros(len, len)
    for i in 1:D
        D_op = D_matrix(D, i, k, n; scheme=scheme)
        lap += D_op * D_op
    end
    return lap
end


using Random: seed!
cost(x) = x ^ 2
dcost(x) = 2 * x

seed!(1)
epochs = 2000

# momentum
x_momentum = rand(1)
opt = Momentum()

for i in 1:epochs
    g = dcost(x_momentum)
    δ = update(opt, g)
    global x_momentum -= δ
end

# adagrad
x_adagrad = rand(1)
opt = Adagrad()

for i in 1:epochs
    g = dcost(x_adagrad)

    δ = update(opt, g)
    global x_adagrad -= δ
end

# adadelta
x_adadelta = rand(1)
opt = Adadelta()

for i in 1:epochs
    g = dcost(x_adadelta)

    δ = update(opt, g)
    global x_adadelta -= δ
end

# rmsprop
x_rmsprop = rand(1)
opt = RMSprop()

for i in 1:epochs
    g = dcost(x_rmsprop)

    δ = update(opt, g)
    global x_rmsprop -= δ
end

# adam
x_adam = rand(1)
opt = Adam()

for i in 1:epochs
    g = dcost(x_adam)

    δ = update(opt, g)
    global x_adam -= δ
end

# adamax
x_adamax = rand(1)
opt = Adamax()

for i in 1:epochs
    g = dcost(x_adamax)

    δ = update(opt, g)
    global x_adamax -= δ
end

# nadam
x_nadam = rand(1)
opt = Nadam()

for i in 1:epochs
    g = dcost(x_nadam)

    δ = update(opt, g)
    global x_nadam -= δ
end

# vanilla
x_vanilla = rand(1)
opt = VanillaGradDescent()

for i in 1:epochs
    g = dcost(x_vanilla)

    δ = update(opt, g)
    global x_vanilla -= δ
end

# inversedecay
x_invdec = rand(1)
opt = Inversedecay()

for i in 1:epochs
    g = dcost(x_invdec)

    δ = update(opt, g)
    global x_invdec -= δ
end

@testset "Quadatric" begin
    @test 0.0 ≈ x_momentum[1] atol=5e-2
    @test 0.0 ≈ x_adagrad[1] atol=5e-2
    @test 0.0 ≈ x_adadelta[1] atol=5e-2
    @test 0.0 ≈ x_rmsprop[1] atol=5e-2
    @test 0.0 ≈ x_adam[1] atol=5e-2
    @test 0.0 ≈ x_adamax[1] atol=5e-2
    @test 0.0 ≈ x_nadam[1] atol=5e-2
    @test 0.0 ≈ x_vanilla[1] atol=5e-2
end


"""
    LKJ(d, η)

```julia
d::Int   dimension
η::Real  positive shape
```
The [LKJ](https://doi.org/10.1016/j.jmva.2009.04.008) distribution is a distribution over
``d\\times d`` real correlation matrices (positive-definite matrices with ones on the diagonal).
If ``\\mathbf{R}\\sim \\textrm{LKJ}_{d}(\\eta)``, then its probability density function is

```math
f(\\mathbf{R};\\eta) = \\left[\\prod_{k=1}^{d-1}\\pi^{\\frac{k}{2}}
\\frac{\\Gamma\\left(\\eta+\\frac{d-1-k}{2}\\right)}{\\Gamma\\left(\\eta+\\frac{d-1}{2}\\right)}\\right]^{-1}
|\\mathbf{R}|^{\\eta-1}.
```

If ``\\eta = 1``, then the LKJ distribution is uniform over
[the space of correlation matrices](https://www.jstor.org/stable/2684832).
"""
struct LKJ{T <: Real, D <: Integer} <: ContinuousMatrixDistribution
    d::D
    η::T
    logc0::T
end

#  -----------------------------------------------------------------------------
#  Constructors
#  -----------------------------------------------------------------------------

function LKJ(d::Integer, η::Real; check_args = true)
    if check_args
        d > 0 || throw(ArgumentError("Matrix dimension must be positive."))
        η > 0 || throw(ArgumentError("Shape parameter must be positive."))
    end
    logc0 = lkj_logc0(d, η)
    T = Base.promote_eltype(η, logc0)
    LKJ{T, typeof(d)}(d, T(η), T(logc0))
end

#  -----------------------------------------------------------------------------
#  REPL display
#  -----------------------------------------------------------------------------

show(io::IO, d::LKJ) = show_multline(io, d, [(:d, d.d), (:η, d.η)])

#  -----------------------------------------------------------------------------
#  Conversion
#  -----------------------------------------------------------------------------

function convert(::Type{LKJ{T}}, d::LKJ) where T <: Real
    LKJ{T, typeof(d.d)}(d.d, T(d.η), T(d.logc0))
end

function convert(::Type{LKJ{T}}, d::Integer, η, logc0) where T <: Real
    LKJ{T, typeof(d)}(d, T(η), T(logc0))
end

#  -----------------------------------------------------------------------------
#  Properties
#  -----------------------------------------------------------------------------

dim(d::LKJ) = d.d

size(d::LKJ) = (dim(d), dim(d))

rank(d::LKJ) = dim(d)

insupport(d::LKJ, R::AbstractMatrix) = isreal(R) && size(R) == size(d) && isone(Diagonal(R)) && isposdef(R)

mean(d::LKJ) = Matrix{partype(d)}(I, dim(d), dim(d))

function mode(d::LKJ; check_args = true)
    p, η = params(d)
    if check_args
        η > 1 || throw(ArgumentError("mode is defined only when η > 1."))
    end
    return mean(d)
end

function var(lkj::LKJ)
    d = dim(lkj)
    d > 1 || return zeros(d, d)
    σ² = var(_marginal(lkj))
    σ² * (ones(partype(lkj), d, d) - I)
end

params(d::LKJ) = (d.d, d.η)

@inline partype(d::LKJ{T}) where {T <: Real} = T

#  -----------------------------------------------------------------------------
#  Evaluation
#  -----------------------------------------------------------------------------

function lkj_logc0(d::Integer, η::Real)
    d > 1 || return zero(η)
    if isone(η)
        if iseven(d)
            logc0 = -lkj_onion_loginvconst_uniform_even(d)
        else
            logc0 = -lkj_onion_loginvconst_uniform_odd(d)
        end
    else
        logc0 = -lkj_onion_loginvconst(d, η)
    end
    return logc0
end

logkernel(d::LKJ, R::AbstractMatrix) = (d.η - 1) * logdet(R)

#  -----------------------------------------------------------------------------
#  Sampling
#  -----------------------------------------------------------------------------

function _rand!(rng::AbstractRNG, d::LKJ, R::AbstractMatrix)
    R .= _lkj_onion_sampler(d.d, d.η, rng)
end

function _lkj_onion_sampler(d::Integer, η::Real, rng::AbstractRNG = Random.GLOBAL_RNG)
    #  Section 3.2 in LKJ (2009 JMA)
    #  1. Initialization
    R = ones(typeof(η), d, d)
    d > 1 || return R
    β = η + 0.5d - 1
    u = rand(rng, Beta(β, β))
    R[1, 2] = 2u - 1
    R[2, 1] = R[1, 2]
    #  2.
    for k in 2:d - 1
        #  (a)
        β -= 0.5
        #  (b)
        y = rand(rng, Beta(k / 2, β))
        #  (c)
        u = randn(rng, k)
        u = u / norm(u)
        #  (d)
        w = sqrt(y) * u
        A = cholesky(R[1:k, 1:k]).L
        z = A * w
        #  (e)
        R[1:k, k + 1] = z
        R[k + 1, 1:k] = z'
    end
    #  3.
    return R
end

#  -----------------------------------------------------------------------------
#  The free elements of an LKJ matrix each have the same marginal distribution
#  -----------------------------------------------------------------------------

function _marginal(lkj::LKJ)
    d = lkj.d
    η = lkj.η
    α = η + 0.5d - 1
    LocationScale(-1, 2, Beta(α, α))
end

#  -----------------------------------------------------------------------------
#  Test utils
#  -----------------------------------------------------------------------------

function _univariate(d::LKJ)
    check_univariate(d)
    return DiscreteNonParametric([one(d.η)], [one(d.η)])
end

function _rand_params(::Type{LKJ}, elty, n::Int, p::Int)
    n == p || throw(ArgumentError("dims must be equal for LKJ"))
    η = abs(3randn(elty))
    return n, η
end

#  -----------------------------------------------------------------------------
#  Several redundant implementations of the recipricol integrating constant.
#  If f(R; n) = c₀ |R|ⁿ⁻¹, these give log(1 / c₀).
#  Every integrating constant formula given in LKJ (2009 JMA) is an expression
#  for 1 / c₀, even if they say that it is not.
#  -----------------------------------------------------------------------------

function lkj_onion_loginvconst(d::Integer, η::Real)
    #  Equation (17) in LKJ (2009 JMA)
    sumlogs = zero(η)
    for k in 2:d - 1
        sumlogs += 0.5k*logπ + loggamma(η + 0.5(d - 1 - k))
    end
    α = η + 0.5d - 1
    loginvconst = (2η + d - 3)*logtwo + logbeta(α, α) + sumlogs - (d - 2) * loggamma(η + 0.5(d - 1))
    return loginvconst
end

function lkj_onion_loginvconst_uniform_odd(d::Integer)
    #  Theorem 5 in LKJ (2009 JMA)
    sumlogs = 0.0
    for k in 1:div(d - 1, 2)
        sumlogs += loggamma(2k)
    end
    loginvconst = 0.25(d^2 - 1)*logπ + sumlogs - 0.25(d - 1)^2*logtwo - (d - 1)*loggamma(0.5(d + 1))
    return loginvconst
end

function lkj_onion_loginvconst_uniform_even(d::Integer)
    #  Theorem 5 in LKJ (2009 JMA)
    sumlogs = 0.0
    for k in 1:div(d - 2, 2)
        sumlogs += loggamma(2k)
    end
    loginvconst = 0.25d*(d - 2)*logπ + 0.25(3d^2 - 4d)*logtwo + d*loggamma(0.5d) + sumlogs - (d - 1)*loggamma(d)
end

function lkj_vine_loginvconst(d::Integer, η::Real)
    #  Equation (16) in LKJ (2009 JMA)
    expsum = zero(η)
    betasum = zero(η)
    for k in 1:d - 1
        α = η + 0.5(d - k - 1)
        expsum += (2η - 2 + d - k) * (d - k)
        betasum += (d - k) * logbeta(α, α)
    end
    loginvconst = expsum * logtwo + betasum
    return loginvconst
end

function lkj_vine_loginvconst_uniform(d::Integer)
    #  Equation after (16) in LKJ (2009 JMA)
    expsum = 0.0
    betasum = 0.0
    for k in 1:d - 1
        α = (k + 1) / 2
        expsum += k ^ 2
        betasum += k * logbeta(α, α)
    end
    loginvconst = expsum * logtwo + betasum
    return loginvconst
end

function lkj_loginvconst_alt(d::Integer, η::Real)
    #  Third line in first proof of Section 3.3 in LKJ (2009 JMA)
    loginvconst = zero(η)
    for k in 1:d - 1
        loginvconst += 0.5k*logπ + loggamma(η + 0.5(d - 1 - k)) - loggamma(η + 0.5(d - 1))
    end
    return loginvconst
end

function corr_logvolume(n::Integer)
    #  https://doi.org/10.4169/amer.math.monthly.123.9.909
    logvol = 0.0
    for k in 1:n - 1
        logvol += 0.5k*logπ + k*loggamma((k+1)/2) - k*loggamma((k+2)/2)
    end
    return logvol
end


#!/usr/bin/env julia

using DatagenCopulaBased
using NPZ
using Distributions
using RandomNumbers.Xorshifts

## tests on simulated sata with custom rmg

c = Gumbel_cop(2, 5.)
cg = Nested_Gumbel_cop([c], 1, 2.)

c = Frank_cop(2, 5.)
cf = Nested_Frank_cop([c], 1, 2.)

c = Clayton_cop(2, 5.)
cc = Nested_Clayton_cop([c], 1, 2.)

c = AMH_cop(2, .9)
ca = Nested_AMH_cop([c], 1, .3)

cch = Chain_of_Archimedeans([-0.9, 3], "clayton")

cmo = Marshall_Olkin_cop([1., 2., 3., 1., 2., 3., 4.])

cf = Frechet_cop(3, 0.5)

Σ = [1. 0.5 0.5; 0.5 1. 0.5; 0.5 0.5 1.]
ct = Student_cop(Σ, 1)

t = 100_000

r = Xoroshiro128Plus()
x = simulate_copula(t, cc; rng = r)
npzwrite("copula.npy", x)

Σ = [1. 0.8 0.6 0.6; 0.8 1. 0.6 0.6; 0.6 0.6 1. 0.6; 0.6 0.6 0.6 1.]
x = Array(transpose(rand(MvNormal(Σ),t)))
y = gcop2arch(x, ["clayton"=>[1,2,3]])
npzwrite("transform.npy", y)


# test d2f zeros lookup functions

include("../src/overt_utils.jl")
using Test

@testset "Testing Second Derivative Zero Computation" begin
    # test get_regions_1arg
    # division
    # c/x, c > 0, c<0, interval all pos or all neg
    @test get_regions_1arg(:(5/x), :x, 1, 2) == ([], true)
    @test get_regions_1arg(:(5/x), :x, -2, -1) == ([], false)
    @test get_regions_1arg(:(-5/x), :x, 1, 2) == ([], false)
    @test get_regions_1arg(:(-5/x), :x, -2, -1) == ([], true)
    # exponent
    # c^x
    @test get_regions_1arg(:(5.1^x), :x, -1, 1) == ([], true)
    # x^c
    # fractional exponent
    @test get_regions_1arg(:(x^5.1), :x, 0, 2) == ([], true)
    # odd exponent
    @test get_regions_1arg(:(x^5), :x, 1, 2) == ([], true)
    @test get_regions_1arg(:(x^5), :x, -2, -1) == ([], false)
    @test get_regions_1arg(:(x^5), :x, -1, 2) == ([0], nothing)
    # even exponent
    @test get_regions_1arg(:(x^6), :x, -2, -1) == ([], true)

    # test get_regions_unary
    # sin, at boundary points
    @test all(get_regions_unary(:sin, -π, π)[1] .≈ [-π, 0, π])
    @test get_regions_unary(:sin, -π, π)[2] === nothing
    # at fractional points
    @test get_regions_unary(:sin, -1, 1) == ([0], nothing)
    @test all(get_regions_unary(:sin, 1, 4)[1] .≈ [π])
    @test get_regions_unary(:sin, 1, 4)[2] === nothing
    # cos
    @test get_regions_unary(:cos, -1, 1) == ([], false)
    @test all(get_regions_unary(:cos, -π, π)[1] .≈ [-π/2, π/2])
    @test get_regions_unary(:cos, -π, π)[2] === nothing
    # exp
    @test get_regions_unary(:exp, -1, 1) == ([], true)
    # log
    @test get_regions_unary(:log, .5, 1.5) == ([], false)
    # tanh
    @test get_regions_unary(:tanh, -2, 2) == ([0], nothing)
    @test get_regions_unary(:tanh, -2, -1) == ([], true)
    @test get_regions_unary(:tanh, 1, 2) == ([], false)
end