@testset "4.5.4.1 (a+b sec)^m (A+B sec+C sec^2)" begin
    (A, B, C, b, c, d, e, f, m, x, ) = @variables A B C b c d e f m x

    #= ::Package:: =#

    #= ::Title:: =#
    #=Integrands*of*the*form*(b*sec(e+f*x))^m*(A+B*sec(e+f*x)+C*sec(e+f*x)^2)=#

    #= ::Section::Closed:: =#
    #=Integrands*of*the*form*(b*sec(e+f*x))^m*(A+C*sec(e+f*x)^2)=#

    #= ::Subsection::Closed:: =#
    #=Integrands*of*the*form*sec(e+f*x)^m*(A+C*sec(e+f*x)^2)=#

    @test_int [sec(c + d*x)^6*(A + C*sec(c + d*x)^2), x, 3, ((7*A + 6*C)*tan(c + d*x))/(7*d) + (C*sec(c + d*x)^6*tan(c + d*x))/(7*d) + (2*(7*A + 6*C)*tan(c + d*x)^3)/(21*d) + ((7*A + 6*C)*tan(c + d*x)^5)/(35*d)]
    @test_int [sec(c + d*x)^5*(A + C*sec(c + d*x)^2), x, 4, ((6*A + 5*C)*atanh(sin(c + d*x)))/(16*d) + ((6*A + 5*C)*sec(c + d*x)*tan(c + d*x))/(16*d) + ((6*A + 5*C)*sec(c + d*x)^3*tan(c + d*x))/(24*d) + (C*sec(c + d*x)^5*tan(c + d*x))/(6*d)]
    @test_int [sec(c + d*x)^4*(A + C*sec(c + d*x)^2), x, 3, ((5*A + 4*C)*tan(c + d*x))/(5*d) + (C*sec(c + d*x)^4*tan(c + d*x))/(5*d) + ((5*A + 4*C)*tan(c + d*x)^3)/(15*d)]
    @test_int [sec(c + d*x)^3*(A + C*sec(c + d*x)^2), x, 3, ((4*A + 3*C)*atanh(sin(c + d*x)))/(8*d) + ((4*A + 3*C)*sec(c + d*x)*tan(c + d*x))/(8*d) + (C*sec(c + d*x)^3*tan(c + d*x))/(4*d)]
    @test_int [sec(c + d*x)^2*(A + C*sec(c + d*x)^2), x, 3, ((3*A + 2*C)*tan(c + d*x))/(3*d) + (C*sec(c + d*x)^2*tan(c + d*x))/(3*d)]
    @test_int [sec(c + d*x)^1*(A + C*sec(c + d*x)^2), x, 2, ((2*A + C)*atanh(sin(c + d*x)))/(2*d) + (C*sec(c + d*x)*tan(c + d*x))/(2*d)]
    @test_int [sec(c + d*x)^0*(A + C*sec(c + d*x)^2), x, 3, A*x + (C*tan(c + d*x))/d]
    @test_int [cos(c + d*x)^1*(A + C*sec(c + d*x)^2), x, 2, (C*atanh(sin(c + d*x)))/d + (A*sin(c + d*x))/d]
    @test_int [cos(c + d*x)^2*(A + C*sec(c + d*x)^2), x, 2, (1/2)*(A + 2*C)*x + (A*cos(c + d*x)*sin(c + d*x))/(2*d)]
    @test_int [cos(c + d*x)^3*(A + C*sec(c + d*x)^2), x, 3, ((A + C)*sin(c + d*x))/d - (A*sin(c + d*x)^3)/(3*d)]
    @test_int [cos(c + d*x)^4*(A + C*sec(c + d*x)^2), x, 3, (1/8)*(3*A + 4*C)*x + ((3*A + 4*C)*cos(c + d*x)*sin(c + d*x))/(8*d) + (A*cos(c + d*x)^3*sin(c + d*x))/(4*d)]
    @test_int [cos(c + d*x)^5*(A + C*sec(c + d*x)^2), x, 4, ((A + C)*sin(c + d*x))/d - ((2*A + C)*sin(c + d*x)^3)/(3*d) + (A*sin(c + d*x)^5)/(5*d)]
    @test_int [cos(c + d*x)^6*(A + C*sec(c + d*x)^2), x, 4, (1/16)*(5*A + 6*C)*x + ((5*A + 6*C)*cos(c + d*x)*sin(c + d*x))/(16*d) + ((5*A + 6*C)*cos(c + d*x)^3*sin(c + d*x))/(24*d) + (A*cos(c + d*x)^5*sin(c + d*x))/(6*d)]

    @test_int [sec(c + d*x)^m*(-C*m/(m + 1) + C*sec(c + d*x)^2), x, 1, (C*sec(c + d*x)^(1 + m)*sin(c + d*x))/(d*(1 + m))]
    @test_int [sec(c + d*x)^m*(A - A*(m + 1)/m*sec(c + d*x)^2), x, 1, -((A*sec(c + d*x)^(1 + m)*sin(c + d*x))/(d*m))]

    #= ::Subsection::Closed:: =#
    #=Integrands*of*the*form*(b*sec(e+f*x))^(m/2)*(A+C*sec(e+f*x)^2)=#

    @test_int [(A + C*sec(c + d*x)^2)*(b*sec(c + d*x))^(5/2), x, 4, (2*b^2*(7*A + 5*C)*sqrt(cos(c + d*x))*Elliptic.F((1/2)*(c + d*x), 2)*sqrt(b*sec(c + d*x)))/(21*d) + (2*b*(7*A + 5*C)*(b*sec(c + d*x))^(3/2)*sin(c + d*x))/(21*d) + (2*C*(b*sec(c + d*x))^(5/2)*tan(c + d*x))/(7*d)]
    @test_int [(A + C*sec(c + d*x)^2)*(b*sec(c + d*x))^(3/2), x, 4, -((2*b^2*(5*A + 3*C)*Elliptic.E((1/2)*(c + d*x), 2))/(5*d*sqrt(cos(c + d*x))*sqrt(b*sec(c + d*x)))) + (2*b*(5*A + 3*C)*sqrt(b*sec(c + d*x))*sin(c + d*x))/(5*d) + (2*C*(b*sec(c + d*x))^(3/2)*tan(c + d*x))/(5*d)]
    @test_int [(A + C*sec(c + d*x)^2)*(b*sec(c + d*x))^(1/2), x, 3, (2*(3*A + C)*sqrt(cos(c + d*x))*Elliptic.F((1/2)*(c + d*x), 2)*sqrt(b*sec(c + d*x)))/(3*d) + (2*C*sqrt(b*sec(c + d*x))*tan(c + d*x))/(3*d)]
    @test_int [(A + C*sec(c + d*x)^2)/(b*sec(c + d*x))^(1/2), x, 3, (2*(A - C)*Elliptic.E((1/2)*(c + d*x), 2))/(d*sqrt(cos(c + d*x))*sqrt(b*sec(c + d*x))) + (2*C*tan(c + d*x))/(d*sqrt(b*sec(c + d*x)))]
    @test_int [(A + C*sec(c + d*x)^2)/(b*sec(c + d*x))^(3/2), x, 3, (2*(A + 3*C)*sqrt(cos(c + d*x))*Elliptic.F((1/2)*(c + d*x), 2)*sqrt(b*sec(c + d*x)))/(3*b^2*d) + (2*A*tan(c + d*x))/(3*d*(b*sec(c + d*x))^(3/2))]
    @test_int [(A + C*sec(c + d*x)^2)/(b*sec(c + d*x))^(5/2), x, 3, (2*(3*A + 5*C)*Elliptic.E((1/2)*(c + d*x), 2))/(5*b^2*d*sqrt(cos(c + d*x))*sqrt(b*sec(c + d*x))) + (2*A*tan(c + d*x))/(5*d*(b*sec(c + d*x))^(5/2))]
    @test_int [(A + C*sec(c + d*x)^2)/(b*sec(c + d*x))^(7/2), x, 4, (2*(5*A + 7*C)*sqrt(cos(c + d*x))*Elliptic.F((1/2)*(c + d*x), 2)*sqrt(b*sec(c + d*x)))/(21*b^4*d) + (2*(5*A + 7*C)*sin(c + d*x))/(21*b^3*d*sqrt(b*sec(c + d*x))) + (2*A*tan(c + d*x))/(7*d*(b*sec(c + d*x))^(7/2))]
    @test_int [(A + C*sec(c + d*x)^2)/(b*sec(c + d*x))^(9/2), x, 4, (2*(7*A + 9*C)*Elliptic.E((1/2)*(c + d*x), 2))/(15*b^4*d*sqrt(cos(c + d*x))*sqrt(b*sec(c + d*x))) + (2*(7*A + 9*C)*sin(c + d*x))/(45*b^3*d*(b*sec(c + d*x))^(3/2)) + (2*A*tan(c + d*x))/(9*d*(b*sec(c + d*x))^(9/2))]

    @test_int [(3 + 3*sec(c + d*x)^2)/sec(c + d*x)^(1/2), x, 1, (6*sqrt(sec(c + d*x))*sin(c + d*x))/d]

    #= ::Subsection::Closed:: =#
    #=Integrands*of*the*form*sec(e+f*x)^m*(A+C*sec(e+f*x)^2)*when*A*(m+1)+C*m=0=#

    @test_int [sec(e + f*x)^m*(m - (m + 1)*sec(e + f*x)^2), x, 1, -((sec(e + f*x)^(1 + m)*sin(e + f*x))/f)]

    @test_int [sec(e + f*x)^5*(5 - 6*sec(e + f*x)^2), x, 1, -((sec(e + f*x)^5*tan(e + f*x))/f)]
    @test_int [sec(e + f*x)^4*(4 - 5*sec(e + f*x)^2), x, 1, -((sec(e + f*x)^4*tan(e + f*x))/f)]
    @test_int [sec(e + f*x)^3*(3 - 4*sec(e + f*x)^2), x, 1, -((sec(e + f*x)^3*tan(e + f*x))/f)]
    @test_int [sec(e + f*x)^2*(2 - 3*sec(e + f*x)^2), x, 1, -((sec(e + f*x)^2*tan(e + f*x))/f)]
    @test_int [sec(e + f*x)^1*(1 - 2*sec(e + f*x)^2), x, 1, -((sec(e + f*x)*tan(e + f*x))/f)]
    @test_int [sec(e + f*x)^0*(0 - 1*sec(e + f*x)^2), x, 2, -(tan(e + f*x)/f)]
    @test_int [cos(e + f*x)^1*(-1 - 0*sec(e + f*x)^2), x, 1, -(sin(e + f*x)/f)]
    @test_int [cos(e + f*x)^2*(-2 + 1*sec(e + f*x)^2), x, 1, -((cos(e + f*x)*sin(e + f*x))/f)]
    @test_int [cos(e + f*x)^3*(-3 + 2*sec(e + f*x)^2), x, 1, -((cos(e + f*x)^2*sin(e + f*x))/f)]
    @test_int [cos(e + f*x)^4*(-4 + 3*sec(e + f*x)^2), x, 1, -((cos(e + f*x)^3*sin(e + f*x))/f)]
    @test_int [cos(e + f*x)^5*(-5 + 4*sec(e + f*x)^2), x, 1, -((cos(e + f*x)^4*sin(e + f*x))/f)]

    #= ::Section::Closed:: =#
    #=Integrands*of*the*form*(b*sec(e+f*x))^m*(B*sec(e+f*x)+C*sec(e+f*x)^2)=#

    #= ::Subsection::Closed:: =#
    #=Integrands*of*the*form*sec(e+f*x)^m*(B*sec(e+f*x)+C*sec(e+f*x)^2)=#

    @test_int [sec(c + d*x)^3*(B*sec(c + d*x) + C*sec(c + d*x)^2), x, 7, (3*C*atanh(sin(c + d*x)))/(8*d) + (B*tan(c + d*x))/d + (3*C*sec(c + d*x)*tan(c + d*x))/(8*d) + (C*sec(c + d*x)^3*tan(c + d*x))/(4*d) + (B*tan(c + d*x)^3)/(3*d)]
    @test_int [sec(c + d*x)^2*(B*sec(c + d*x) + C*sec(c + d*x)^2), x, 6, (B*atanh(sin(c + d*x)))/(2*d) + (C*tan(c + d*x))/d + (B*sec(c + d*x)*tan(c + d*x))/(2*d) + (C*tan(c + d*x)^3)/(3*d)]
    @test_int [sec(c + d*x)^1*(B*sec(c + d*x) + C*sec(c + d*x)^2), x, 6, (C*atanh(sin(c + d*x)))/(2*d) + (B*tan(c + d*x))/d + (C*sec(c + d*x)*tan(c + d*x))/(2*d)]
    @test_int [sec(c + d*x)^0*(B*sec(c + d*x) + C*sec(c + d*x)^2), x, 4, (B*atanh(sin(c + d*x)))/d + (C*tan(c + d*x))/d]
    @test_int [cos(c + d*x)^1*(B*sec(c + d*x) + C*sec(c + d*x)^2), x, 4, B*x + (C*atanh(sin(c + d*x)))/d]
    @test_int [cos(c + d*x)^2*(B*sec(c + d*x) + C*sec(c + d*x)^2), x, 3, C*x + (B*sin(c + d*x))/d]
    @test_int [cos(c + d*x)^3*(B*sec(c + d*x) + C*sec(c + d*x)^2), x, 5, (B*x)/2 + (C*sin(c + d*x))/d + (B*cos(c + d*x)*sin(c + d*x))/(2*d)]
    @test_int [cos(c + d*x)^4*(B*sec(c + d*x) + C*sec(c + d*x)^2), x, 6, (C*x)/2 + (B*sin(c + d*x))/d + (C*cos(c + d*x)*sin(c + d*x))/(2*d) - (B*sin(c + d*x)^3)/(3*d)]
    @test_int [cos(c + d*x)^5*(B*sec(c + d*x) + C*sec(c + d*x)^2), x, 7, (3*B*x)/8 + (C*sin(c + d*x))/d + (3*B*cos(c + d*x)*sin(c + d*x))/(8*d) + (B*cos(c + d*x)^3*sin(c + d*x))/(4*d) - (C*sin(c + d*x)^3)/(3*d)]
    @test_int [cos(c + d*x)^6*(B*sec(c + d*x) + C*sec(c + d*x)^2), x, 7, (3*C*x)/8 + (B*sin(c + d*x))/d + (3*C*cos(c + d*x)*sin(c + d*x))/(8*d) + (C*cos(c + d*x)^3*sin(c + d*x))/(4*d) - (2*B*sin(c + d*x)^3)/(3*d) + (B*sin(c + d*x)^5)/(5*d)]

    #= ::Subsection::Closed:: =#
    #=Integrands*of*the*form*(b*sec(e+f*x))^(m/2)*(B*sec(e+f*x)+C*sec(e+f*x)^2)=#

    @test_int [(B*sec(c + d*x) + C*sec(c + d*x)^2)*(b*sec(c + d*x))^(3/2), x, 10, -((6*b^2*C*Elliptic.E((1/2)*(c + d*x), 2))/(5*d*sqrt(cos(c + d*x))*sqrt(b*sec(c + d*x)))) + (2*b*B*sqrt(cos(c + d*x))*Elliptic.F((1/2)*(c + d*x), 2)*sqrt(b*sec(c + d*x)))/(3*d) + (6*b*C*sqrt(b*sec(c + d*x))*sin(c + d*x))/(5*d) + (2*B*(b*sec(c + d*x))^(3/2)*sin(c + d*x))/(3*d) + (2*C*(b*sec(c + d*x))^(5/2)*sin(c + d*x))/(5*b*d)]
    @test_int [(B*sec(c + d*x) + C*sec(c + d*x)^2)*(b*sec(c + d*x))^(1/2), x, 9, -((2*b*B*Elliptic.E((1/2)*(c + d*x), 2))/(d*sqrt(cos(c + d*x))*sqrt(b*sec(c + d*x)))) + (2*C*sqrt(cos(c + d*x))*Elliptic.F((1/2)*(c + d*x), 2)*sqrt(b*sec(c + d*x)))/(3*d) + (2*B*sqrt(b*sec(c + d*x))*sin(c + d*x))/d + (2*C*(b*sec(c + d*x))^(3/2)*sin(c + d*x))/(3*b*d)]
    @test_int [(B*sec(c + d*x) + C*sec(c + d*x)^2)/(b*sec(c + d*x))^(1/2), x, 8, -((2*C*Elliptic.E((1/2)*(c + d*x), 2))/(d*sqrt(cos(c + d*x))*sqrt(b*sec(c + d*x)))) + (2*B*sqrt(cos(c + d*x))*Elliptic.F((1/2)*(c + d*x), 2)*sqrt(b*sec(c + d*x)))/(b*d) + (2*C*sqrt(b*sec(c + d*x))*sin(c + d*x))/(b*d)]
    @test_int [(B*sec(c + d*x) + C*sec(c + d*x)^2)/(b*sec(c + d*x))^(3/2), x, 7, (2*B*Elliptic.E((1/2)*(c + d*x), 2))/(b*d*sqrt(cos(c + d*x))*sqrt(b*sec(c + d*x))) + (2*C*sqrt(cos(c + d*x))*Elliptic.F((1/2)*(c + d*x), 2)*sqrt(b*sec(c + d*x)))/(b^2*d)]
    @test_int [(B*sec(c + d*x) + C*sec(c + d*x)^2)/(b*sec(c + d*x))^(5/2), x, 8, (2*C*Elliptic.E((1/2)*(c + d*x), 2))/(b^2*d*sqrt(cos(c + d*x))*sqrt(b*sec(c + d*x))) + (2*B*sqrt(cos(c + d*x))*Elliptic.F((1/2)*(c + d*x), 2)*sqrt(b*sec(c + d*x)))/(3*b^3*d) + (2*B*sin(c + d*x))/(3*b^2*d*sqrt(b*sec(c + d*x)))]
    @test_int [(B*sec(c + d*x) + C*sec(c + d*x)^2)/(b*sec(c + d*x))^(7/2), x, 9, (6*B*Elliptic.E((1/2)*(c + d*x), 2))/(5*b^3*d*sqrt(cos(c + d*x))*sqrt(b*sec(c + d*x))) + (2*C*sqrt(cos(c + d*x))*Elliptic.F((1/2)*(c + d*x), 2)*sqrt(b*sec(c + d*x)))/(3*b^4*d) + (2*B*sin(c + d*x))/(5*b^2*d*(b*sec(c + d*x))^(3/2)) + (2*C*sin(c + d*x))/(3*b^3*d*sqrt(b*sec(c + d*x)))]
    @test_int [(B*sec(c + d*x) + C*sec(c + d*x)^2)/(b*sec(c + d*x))^(9/2), x, 10, (6*C*Elliptic.E((1/2)*(c + d*x), 2))/(5*b^4*d*sqrt(cos(c + d*x))*sqrt(b*sec(c + d*x))) + (10*B*sqrt(cos(c + d*x))*Elliptic.F((1/2)*(c + d*x), 2)*sqrt(b*sec(c + d*x)))/(21*b^5*d) + (2*B*sin(c + d*x))/(7*b^2*d*(b*sec(c + d*x))^(5/2)) + (2*C*sin(c + d*x))/(5*b^3*d*(b*sec(c + d*x))^(3/2)) + (10*B*sin(c + d*x))/(21*b^4*d*sqrt(b*sec(c + d*x)))]

    #= ::Section::Closed:: =#
    #=Integrands*of*the*form*(b*sec(e+f*x))^m*(A+B*sec(e+f*x)+C*sec(e+f*x)^2)=#

    #= ::Subsection::Closed:: =#
    #=Integrands*of*the*form*sec(e+f*x)^m*(A+B*sec(e+f*x)+C*sec(e+f*x)^2)=#

    @test_int [sec(c + d*x)^4*(A + B*sec(c + d*x) + C*sec(c + d*x)^2), x, 7, (3*B*atanh(sin(c + d*x)))/(8*d) + ((5*A + 4*C)*tan(c + d*x))/(5*d) + (3*B*sec(c + d*x)*tan(c + d*x))/(8*d) + (B*sec(c + d*x)^3*tan(c + d*x))/(4*d) + (C*sec(c + d*x)^4*tan(c + d*x))/(5*d) + ((5*A + 4*C)*tan(c + d*x)^3)/(15*d)]
    @test_int [sec(c + d*x)^3*(A + B*sec(c + d*x) + C*sec(c + d*x)^2), x, 6, ((4*A + 3*C)*atanh(sin(c + d*x)))/(8*d) + (B*tan(c + d*x))/d + ((4*A + 3*C)*sec(c + d*x)*tan(c + d*x))/(8*d) + (C*sec(c + d*x)^3*tan(c + d*x))/(4*d) + (B*tan(c + d*x)^3)/(3*d)]
    @test_int [sec(c + d*x)^2*(A + B*sec(c + d*x) + C*sec(c + d*x)^2), x, 6, (B*atanh(sin(c + d*x)))/(2*d) + ((3*A + 2*C)*tan(c + d*x))/(3*d) + (B*sec(c + d*x)*tan(c + d*x))/(2*d) + (C*sec(c + d*x)^2*tan(c + d*x))/(3*d)]
    @test_int [sec(c + d*x)^1*(A + B*sec(c + d*x) + C*sec(c + d*x)^2), x, 5, ((2*A + C)*atanh(sin(c + d*x)))/(2*d) + (B*tan(c + d*x))/d + (C*sec(c + d*x)*tan(c + d*x))/(2*d)]
    @test_int [sec(c + d*x)^0*(A + B*sec(c + d*x) + C*sec(c + d*x)^2), x, 4, A*x + (B*atanh(sin(c + d*x)))/d + (C*tan(c + d*x))/d]
    @test_int [cos(c + d*x)^1*(A + B*sec(c + d*x) + C*sec(c + d*x)^2), x, 4, B*x + (C*atanh(sin(c + d*x)))/d + (A*sin(c + d*x))/d]
    @test_int [cos(c + d*x)^2*(A + B*sec(c + d*x) + C*sec(c + d*x)^2), x, 4, (1/2)*(A + 2*C)*x + (B*sin(c + d*x))/d + (A*cos(c + d*x)*sin(c + d*x))/(2*d)]
    @test_int [cos(c + d*x)^3*(A + B*sec(c + d*x) + C*sec(c + d*x)^2), x, 6, (B*x)/2 + ((A + C)*sin(c + d*x))/d + (B*cos(c + d*x)*sin(c + d*x))/(2*d) - (A*sin(c + d*x)^3)/(3*d)]
    @test_int [cos(c + d*x)^4*(A + B*sec(c + d*x) + C*sec(c + d*x)^2), x, 6, (1/8)*(3*A + 4*C)*x + (B*sin(c + d*x))/d + ((3*A + 4*C)*cos(c + d*x)*sin(c + d*x))/(8*d) + (A*cos(c + d*x)^3*sin(c + d*x))/(4*d) - (B*sin(c + d*x)^3)/(3*d)]
    @test_int [cos(c + d*x)^5*(A + B*sec(c + d*x) + C*sec(c + d*x)^2), x, 8, (3*B*x)/8 + ((A + C)*sin(c + d*x))/d + (3*B*cos(c + d*x)*sin(c + d*x))/(8*d) + (B*cos(c + d*x)^3*sin(c + d*x))/(4*d) - ((2*A + C)*sin(c + d*x)^3)/(3*d) + (A*sin(c + d*x)^5)/(5*d)]
    @test_int [cos(c + d*x)^6*(A + B*sec(c + d*x) + C*sec(c + d*x)^2), x, 7, (1/16)*(5*A + 6*C)*x + (B*sin(c + d*x))/d + ((5*A + 6*C)*cos(c + d*x)*sin(c + d*x))/(16*d) + ((5*A + 6*C)*cos(c + d*x)^3*sin(c + d*x))/(24*d) + (A*cos(c + d*x)^5*sin(c + d*x))/(6*d) - (2*B*sin(c + d*x)^3)/(3*d) + (B*sin(c + d*x)^5)/(5*d)]

    #= ::Subsection::Closed:: =#
    #=Integrands*of*the*form*(b*sec(e+f*x))^(m/2)*(A+B*sec(e+f*x)+C*sec(e+f*x)^2)=#

    @test_int [(A + B*sec(c + d*x) + C*sec(c + d*x)^2)*(b*sec(c + d*x))^(3/2), x, 8, -((2*b^2*(5*A + 3*C)*Elliptic.E((1/2)*(c + d*x), 2))/(5*d*sqrt(cos(c + d*x))*sqrt(b*sec(c + d*x)))) + (2*b*B*sqrt(cos(c + d*x))*Elliptic.F((1/2)*(c + d*x), 2)*sqrt(b*sec(c + d*x)))/(3*d) + (2*b*(5*A + 3*C)*sqrt(b*sec(c + d*x))*sin(c + d*x))/(5*d) + (2*B*(b*sec(c + d*x))^(3/2)*sin(c + d*x))/(3*d) + (2*C*(b*sec(c + d*x))^(3/2)*tan(c + d*x))/(5*d)]
    @test_int [(A + B*sec(c + d*x) + C*sec(c + d*x)^2)*(b*sec(c + d*x))^(1/2), x, 7, -((2*b*B*Elliptic.E((1/2)*(c + d*x), 2))/(d*sqrt(cos(c + d*x))*sqrt(b*sec(c + d*x)))) + (2*(3*A + C)*sqrt(cos(c + d*x))*Elliptic.F((1/2)*(c + d*x), 2)*sqrt(b*sec(c + d*x)))/(3*d) + (2*B*sqrt(b*sec(c + d*x))*sin(c + d*x))/d + (2*C*sqrt(b*sec(c + d*x))*tan(c + d*x))/(3*d)]
    @test_int [(A + B*sec(c + d*x) + C*sec(c + d*x)^2)/(b*sec(c + d*x))^(1/2), x, 6, (2*(A - C)*Elliptic.E((1/2)*(c + d*x), 2))/(d*sqrt(cos(c + d*x))*sqrt(b*sec(c + d*x))) + (2*B*sqrt(cos(c + d*x))*Elliptic.F((1/2)*(c + d*x), 2)*sqrt(b*sec(c + d*x)))/(b*d) + (2*C*tan(c + d*x))/(d*sqrt(b*sec(c + d*x)))]
    @test_int [(A + B*sec(c + d*x) + C*sec(c + d*x)^2)/(b*sec(c + d*x))^(3/2), x, 6, (2*B*Elliptic.E((1/2)*(c + d*x), 2))/(b*d*sqrt(cos(c + d*x))*sqrt(b*sec(c + d*x))) + (2*(A + 3*C)*sqrt(cos(c + d*x))*Elliptic.F((1/2)*(c + d*x), 2)*sqrt(b*sec(c + d*x)))/(3*b^2*d) + (2*A*tan(c + d*x))/(3*d*(b*sec(c + d*x))^(3/2))]
    @test_int [(A + B*sec(c + d*x) + C*sec(c + d*x)^2)/(b*sec(c + d*x))^(5/2), x, 7, (2*(3*A + 5*C)*Elliptic.E((1/2)*(c + d*x), 2))/(5*b^2*d*sqrt(cos(c + d*x))*sqrt(b*sec(c + d*x))) + (2*B*sqrt(cos(c + d*x))*Elliptic.F((1/2)*(c + d*x), 2)*sqrt(b*sec(c + d*x)))/(3*b^3*d) + (2*B*sin(c + d*x))/(3*b^2*d*sqrt(b*sec(c + d*x))) + (2*A*tan(c + d*x))/(5*d*(b*sec(c + d*x))^(5/2))]
    @test_int [(A + B*sec(c + d*x) + C*sec(c + d*x)^2)/(b*sec(c + d*x))^(7/2), x, 8, (6*B*Elliptic.E((1/2)*(c + d*x), 2))/(5*b^3*d*sqrt(cos(c + d*x))*sqrt(b*sec(c + d*x))) + (2*(5*A + 7*C)*sqrt(cos(c + d*x))*Elliptic.F((1/2)*(c + d*x), 2)*sqrt(b*sec(c + d*x)))/(21*b^4*d) + (2*B*sin(c + d*x))/(5*b^2*d*(b*sec(c + d*x))^(3/2)) + (2*(5*A + 7*C)*sin(c + d*x))/(21*b^3*d*sqrt(b*sec(c + d*x))) + (2*A*tan(c + d*x))/(7*d*(b*sec(c + d*x))^(7/2))]

    #= ::Title:: =#
    #=Integrands*of*the*form*(a+b*sec(e+f*x))^m*(A+B*sec(e+f*x)+C*sec(e+f*x)^2)=#
    end

test/4 Trig functions/4.5 Secant/4.5.4.1 (a+b sec)^m (A+B sec+C sec^2).jl


# load required tensor flow functionality

ENV["PYTHON"] = "/usr/bin/python3.6"

using Pkg
Pkg.build("PyCall")

using PyCall

# tf = pyimport("tensorflow")
# tf.keras.models.load_model

py"""
import sys
sys.path.insert(0, "/home/singhk/learning-odometry/")
"""

# @show tfmodeldir = joinpath(ENV["HOME"],"learning-odometry/")
# Base.cd(tfmodeldir)

PyTFOdoPredictorPoint2 = pyimport("queryAllModels")."predictSinglePose" # was ["predictSinglePose"]

# test that its working
A = [rand(4) for i in 1:25]
PyTFOdoPredictorPoint2(A)
# returns  10×3 Array{Real,2}:

# mmodel = tf.keras.models.load_model(joinpath(tfmodeldir, "original_network0.h5"))
# mmodel.predict
# mmodel.predict([[A]])
# 1×2 Array{Float32,2}:
#  0.0169379  1.58055


using Laszip

"""
Write XYZ data to a LIDAR laz (laszip compressed) or las format file. Usage:

	dat2las(FileName::AbstractString, xyz, hdr_vec=[]; scaleX=[], scaleY=[], scaleZ=[], offX=[], offY=[], offZ=[])

	Where:
		"FileName" Name of the output LIDAR file
		xyz  A Mx3 array with the point coordinates

Example. To write the x,y,z data to file "lixo.laz" do:

	dat2las("lixo.laz", xyz)
"""
function dat2las(fname::AbstractString, xyz, hdr_vec=[]; scaleX=[], scaleY=[], scaleZ=[], offX=[], offY=[], offZ=[])

	parse_inputs_dat2las(xyz, hdr_vec)
	n_rows, n_cols = size(xyz)

	if (!isempty(hdr_vec))
		min_x, max_x, min_y, max_y, min_z, max_z = hdr_vec[1:6]
	end

	#  Create the writer
	laszip_writer = convert(Ptr{Ptr{Cvoid}},pointer([pointer([0])]))
	if (laszip_create(laszip_writer) != 0)
		msgerror(laszip_writer, "creating laszip writer")
	end

	#header = pointer([pointer([create_empty_header()])])    # Get an empty header directly from C
	header = pointer([pointer([create_header()])])    # Get an empty header directly from C
	laszip_writer = unsafe_load(laszip_writer)

	if (laszip_get_header_pointer(laszip_writer, header) != 0)      # Get the header pointer
		msgerror(laszip_writer, "getting header pointer from laszip writer")
	end

	hdr = unsafe_load(unsafe_load(header))      # Get back the straight type

	# Populate the header
	hdr.version_major = 1
	hdr.version_minor = 2
	hdr.point_data_format = 1
	hdr.number_of_point_records = n_rows
	hdr.x_scale_factor = 1.0
	hdr.y_scale_factor = 1.0
	hdr.z_scale_factor = 1.0
	if (n_cols == 3 && isempty(hdr_vec))		# The 'regular' situation
		min_x, min_y, min_z = minimum(xyz, dims=1)
		max_x, max_y, max_z = maximum(xyz, dims=1)
	end
	hdr.max_x = max_x
	hdr.min_x = min_x
	hdr.max_y = max_y
	hdr.min_y = min_y
	hdr.max_z = max_z
	hdr.min_z = min_z

	# ----------------- Find reasonable scale_factor and offset -----------------------------------------
	if (hdr.min_x >= -360 && hdr.max_x <= 360 && hdr.min_y >= -90 && hdr.max_y <= 90)	# Assume geogs
		if (isempty(scaleX))  hdr.x_scale_factor = 1e-7
		else                  hdr.x_scale_factor = scaleX
		end
		if (isempty(scaleY))  hdr.y_scale_factor = 1e-7
		else                  hdr.y_scale_factor = scaleY
		end
	else
		if (isempty(scaleY))  hdr.x_scale_factor = 1e-3
		else                  hdr.x_scale_factor = scaleX
		end
		if (isempty(scaleY))  hdr.y_scale_factor = 1e-3
		else                  hdr.y_scale_factor = scaleY
		end
	end
	if (isempty(scaleZ))  hdr.z_scale_factor = 1e-2
	else                  hdr.z_scale_factor = scaleZ
	end

	if (!isnan(hdr.min_x) && !isnan(hdr.max_x))
		if (isempty(offX))
			hdr.x_offset = (floor((hdr.min_x + hdr.max_x)/hdr.x_scale_factor/20000000)) * 10000000 * hdr.x_scale_factor
		else
			hdr.x_offset = offX
		end
	end
	if (!isnan(hdr.min_y) && !isnan(hdr.max_y))
		if (isempty(offY))
			hdr.y_offset = (floor((hdr.min_y + hdr.max_y)/hdr.y_scale_factor/20000000)) * 10000000 * hdr.y_scale_factor
		else
			hdr.y_offset = offY
		end
	end
	if (!isnan(hdr.min_z) && !isnan(hdr.max_z))
		if (isempty(offZ))
			hdr.z_offset = (floor((hdr.min_z + hdr.max_z)/hdr.z_scale_factor/20000000)) * 10000000 * hdr.z_scale_factor
		else
			hdr.z_offset = offZ
		end
	end
	# ---------------------------------------------------------------------------------------------------

	# This is the case where we are storing a grid pretending it's a regular LAZ file. Must hijack some header members
	if (!isempty(hdr_vec))
		hdr.x_scale_factor = hdr.z_scale_factor		# Because in fact we only have zz's
		hdr.y_scale_factor = hdr.z_scale_factor
		hdr.x_offset = hdr.z_offset
		hdr.y_offset = hdr.z_offset
		hdr.number_of_point_records = UInt32(ceil(n_rows / 3))
		hdr.global_encoding = 32768;	# Use this number to codify as GRID. bin(UInt16(32768)) = "1000000000000000"

		one = (hdr_vec[7] == 0 ? 1 : 0)
		hdr.project_ID_GUID_data_1 = hdr_vec[7]
		hdr.project_ID_GUID_data_2 = round(Int, (hdr.max_y - hdr.min_y) / hdr_vec[8]) + one 	# n_rows in 2D array
		hdr.project_ID_GUID_data_3 = round(Int, (hdr.max_x - hdr.min_x) / hdr_vec[9]) + one 	# n_cols in 2D array

		#hdr.system_identifier.d1 = UInt8('G')	# This is ugly. There must be a better way
		#hdr.system_identifier.d2 = UInt8('R')
		#hdr.system_identifier.d3 = UInt8('D')
		#mutateit(hdr.system_identifier, "d1", 'G')
		#mutateit(hdr.system_identifier, "d2", 'R')
		#mutateit(hdr.system_identifier, "d3", 'D')
	end

	# Save back the header to its C pointer
	unsafe_store!(unsafe_load(header), hdr)

	if (laszip_open_writer(laszip_writer, fname, 1) != 0)
		msgerror(laszip_writer, @sprintf("opening laszip writer for %s", fname))
	end

	# Get a pointer to the points that will be written
	#point = pointer([pointer([create_empty_point()])])
	point = pointer([pointer([create_point()])])
	if (laszip_get_point_pointer(laszip_writer, point) != 0)
		msgerror(laszip_writer, "getting point pointer from laszip writer")
	end
	point = unsafe_load(point)

	coordinates = zeros(3)
	if (n_cols == 3)
		for n = 1:n_rows
			#=
			pt = unsafe_load(point)
			pt.X = round(Int32, (xyz[n,1]-hdr.x_offset) * hdr.x_scale_factor)
			pt.Y = round(Int32, (xyz[n,2]-hdr.y_offset) * hdr.y_scale_factor)
			pt.Z = round(Int32, (xyz[n,3]-hdr.z_offset) * hdr.z_scale_factor)
			unsafe_store!(point, pt)
			=#
			coordinates[1] = xyz[n,1]
			coordinates[2] = xyz[n,2]
			coordinates[3] = xyz[n,3]
			laszip_set_coordinates(laszip_writer, convert(Ptr{Cdouble}, pointer(coordinates)))
			laszip_write_point(laszip_writer)
		end
	else
		# Deal with special case of a Z column only where we will cheat by splitting trhough the XYZ
		r = rem(n_rows, 3)
		last_ind = (r == 0 ? n_rows : n_rows - 1)
		for n = 1:3:last_ind
			#pt = unsafe_load(point)
			#pt.X = round(Int32, (xyz[n,1]-hdr.z_offset)   * hdr.z_scale_factor)
			#pt.Y = round(Int32, (xyz[n+1,1]-hdr.z_offset) * hdr.z_scale_factor)
			#pt.Z = round(Int32, (xyz[n+2,1]-hdr.z_offset) * hdr.z_scale_factor)
			coordinates[1] = xyz[n]
			coordinates[2] = xyz[n+1]
			coordinates[3] = xyz[n+2]
			laszip_set_coordinates(laszip_writer, convert(Ptr{Cdouble}, pointer(coordinates)))
			laszip_write_point(laszip_writer)
		end
		#pt = unsafe_load(point)
		if (r == 1)
			#pt.X = round(Int32, (xyz[last_ind+1,1]-hdr.z_offset) * hdr.z_scale_factor)
			#pt.Y = pt.X
			#pt.Z = pt.X
			coordinates[1] = xyz[last_ind+1]
			laszip_set_coordinates(laszip_writer, convert(Ptr{Cdouble}, pointer(coordinates)))
			laszip_write_point(laszip_writer)
		elseif (r == 2)
			#pt.X = round(Int32, (xyz[last_ind+1,1]-hdr.z_offset) * hdr.z_scale_factor)
			#pt.Y = round(Int32, (xyz[last_ind+2,1]-hdr.z_offset) * hdr.z_scale_factor)
			#pt.Z = pt.Y
			coordinates[1] = xyz[last_ind+1]
			coordinates[2] = xyz[last_ind+2]
			laszip_set_coordinates(laszip_writer, convert(Ptr{Cdouble}, pointer(coordinates)))
			laszip_write_point(laszip_writer)
		end
	end

	# Close the writer
	if (laszip_close_writer(laszip_writer) != 0)
		msgerror(laszip_writer, "closing laszip writer")
	end
	# Destroy the writer
	if (laszip_destroy(laszip_writer) != 0)
		msgerror(laszip_writer, "destroying laszip writer")
	end

end

# --------------------------------------------------------------------------
function parse_inputs_dat2las(xyz, hdr_vec=[])
# Check validity of input and in future will parse string options

	n_rows, n_cols = size(xyz)
	if ((n_cols != 3 && n_cols != 1))
		error("Input array can only have 1 or 3 columns OR be a 2D array")
	end
	if (!isempty(hdr_vec))
		if (length(hdr_vec) < 7)
			error("HDR argument does not have at least 7 elements")
		end
	end
end


# UTILITY FUNCTIONS AND DEFINITIONS, INCLUDING FOR CONVERTING USER INPUT TO AMENABLE FORM

#*========== NETWORK TOPOLOGY ===========

# list of regular generators at a particular bus
const regGenAtBus = Array[]

# list of wind generators at a particular bus
const windGenAtBus = Array[]

# list of loads at a particular bus
const loadAtBus = Array[]

# initialize Arrays
for i = 1:numBuses
	push!(regGenAtBus,Int64[])
	push!(windGenAtBus,Int64[])
	push!(loadAtBus,Int64[])
end

# populate regular generators first
for i = 1:numRegGen
	push!(regGenAtBus[regGenToBus[i]],i)
end

# next, populate wind generators
# offset the indices by numRegGen to account for regular generators first
for i = 1:numWindGen
	push!(windGenAtBus[windGenToBus[i]],i+numRegGen)
end

# finally, populate load indices
for j = 1:numLoads
	push!(loadAtBus[loadToBus[j]],j)
end

# store the number of lines
const numLines = size(lines,1)

# store the indices of buses that share an edge/line with current bus
# also store the corresponding line susceptances
const linesAdjBus = Array[]
const betaAdjBus = Array[]

for i = 1:numBuses
	push!(linesAdjBus,Int64[])
	push!(betaAdjBus, Float64[])
end

for k = 1:numLines
	push!(linesAdjBus[lines[k][1]],lines[k][2])
	push!(linesAdjBus[lines[k][2]],lines[k][1])

	push!(betaAdjBus[lines[k][1]],beta[k])
	push!(betaAdjBus[lines[k][2]],beta[k])
end

#*========== GENERATOR BOUNDS ===========

# minimum and maximum generator power outputs
const pmin = zeros(Float64,numGen)
const pmax = zeros(Float64,numGen)

# include regular generators first followed by wind generators
pmin[1:numRegGen] = regGenMinPow
pmin[numRegGen+1:end] = windGenMinPow

# ignore the upper bounds for the wind generators since they are random
pmax[1:numRegGen] = regGenMaxPow

# upper bounds for wind generator outputs in the deterministic setting
const pmax_det = 1.0*pmax
for i = 1:numWindGen
	pmax_det[numRegGen+i] = windPredMean[i]
end

# upper bounds for generator outputs to make first-stage decisions
const pmax_FS = 1.0*pmax
for i = 1:numWindGen
	pmax_FS[numRegGen+i] = windPredMean[i] + windBoundFactor*windPredStdev[i]
end

#*========== GENERATOR COST COEFFICIENTS ===========

# generator cost coefficients, including costs of reserves
const genCost = zeros(Float64,numGen)
const posResCost = zeros(Float64,numGen)
const negResCost = zeros(Float64,numGen)

# include regular generators first followed by wind generators
genCost[1:numRegGen] = regGenCost
genCost[numRegGen+1:end] = windGenCost

posResCost[1:numRegGen] = regGenPosResCost
posResCost[numRegGen+1:end] = windGenPosResCost

negResCost[1:numRegGen] = regGenNegResCost
negResCost[numRegGen+1:end] = windGenNegResCost

#*========== GENERATOR RESERVES INFORMATION ===========

# which generators are ready to provide reserves?
const genRes = falses(numGen)

# which generators are ALWAYS READY TO PROVIDE RESERVES?
const genGuarRes = falses(numGen)

# populate the regular generator options followed by the wind generator options
genRes[1:numRegGen] = regGenRes
genRes[numRegGen+1:end] = windGenRes

genGuarRes[1:numRegGen] = regGenGuarRes
genGuarRes[numRegGen+1:end] = windGenGuarRes

#*========== INITIAL GUESSES ===========

# power commitment variables for regular and wind generators
const p0_init = zeros(Float64,numGen)

# positive reserve variables for regular and wind generators
const rp_init = zeros(Float64,numGen)

# negative reserve variables for regular and wind generators
const rm_init = zeros(Float64,numGen)

# participation factor variables for regular and wind generators
const alpha_init = ones(Float64,numGen)/numGen

#*========== OTHER UTILITIES ===========

# update generator upper bounds based on wind levels
function updateGenBounds(omega_w::Array{Float64})

	for i = 1:numWindGen
		if(omega_w[i] < pmin[numRegGen+i])
			println("Wind availability at the ",i,"th generator = ",omega_w[i],"   < corresponding pmin = ",pmin[numRegGen+i])
			throw(ErrorException("Invalid wind generator upper bound!"))
		end
		pmax[numRegGen+i] = omega_w[i]
	end
end

# compute the variance and stdev of sum of demand deviations for the CAP model
const sumDemandDeviationVar = ones(Float64,numLoads)'*demandCovMat*ones(Float64,numLoads)
const sumDemandDeviationStdev = sqrt(sumDemandDeviationVar)

#*========== FACTORIZE AND STORE MATRICES FOR LINEAR SOLVES ===========

# pre-factorize and store matrix corresponding to DC-OPF equations
# the last bus is chosen as the reference bus and substituted out
const rowIndices = []
const colIndices = []
const matValues = []

const sumBeta = zeros(Float64,numBuses-1)

# also construct a matrix of lines with the reference bus ignored
const Line_mat = zeros(Float64,numLines,numBuses-1)

for k = 1:numLines
	fromBus::Int64 = lines[k][1]
	toBus::Int64 = lines[k][2]

	if(fromBus < numBuses)
		Line_mat[k,fromBus] = 1.0
		if(toBus < numBuses)
			push!(rowIndices,fromBus)
			push!(colIndices,toBus)
			push!(matValues,-beta[k])
		end

		sumBeta[fromBus] += beta[k]
	end

	if(toBus < numBuses)
		Line_mat[k,toBus] = -1.0
		if(fromBus < numBuses)
			push!(rowIndices,toBus)
			push!(colIndices,fromBus)
			push!(matValues,-beta[k])
		end

		sumBeta[toBus] += beta[k]
	end
end

for i = 1:numBuses-1
	push!(rowIndices,i)
	push!(colIndices,i)
	push!(matValues,sumBeta[i])
end

const B_mat = sparse(rowIndices,colIndices,matValues)
const B_fact = lufact(B_mat)

# construct matrices for generating the rhs of the DCOPF equations
# it appears that storing these in a sparse format is not worth it
# first, construct the matrix corresponding to the generators
const Gen_mat = zeros(Float64,numBuses-1,numGen)

for i = 1:numBuses-1
	for k = 1:size(regGenAtBus[i],1)
		Gen_mat[i,regGenAtBus[i][k]] = 1.0
	end

	for k = 1:size(windGenAtBus[i],1)
		Gen_mat[i,windGenAtBus[i][k]] = 1.0
	end
end

# next, construct the matrix corresponding to the loads
const Load_mat = zeros(Float64,numBuses-1,numLoads)

for i = 1:numBuses-1
	for k = 1:size(loadAtBus[i],1)
		Load_mat[i,loadAtBus[i][k]] = 1.0
	end
end

# store the solution of B_mat*x = Gen_mat
const B_over_G = B_fact\Gen_mat

# store the solution of B_mat*x = Load_mat
const B_over_L = B_fact\Load_mat

# store (Line_mat*B_over_G)'
const Line_mat_times_B_over_G_trans = (B_over_G')*(Line_mat')


# This file was generated, do not modify it. # hide
LinMod = Pipeline(
    FeatureSelector(features=[:hp1]),
    LR()
);


module CausalityTools
    using Reexport
    import DynamicalSystemsBase: trajectory, DiscreteDynamicalSystem, ContinuousDynamicalSystem
    import DelayEmbeddings: Dataset
    export trajectory, DiscreteDynamicalSystem, ContinuousDynamicalSystem, Dataset

    import TransferEntropy
    import TransferEntropy: transferentropy, mutualinfo, conditional_mutualinfo, Hilbert
    export transferentropy, mutualinfo, conditional_mutualinfo, Hilbert
    @reexport using Entropies
    @reexport using TransferEntropy
    @reexport using TimeseriesSurrogates

    include("JointDistanceDistribution/JointDistanceDistribution.jl")
    include("CrossMappings/CrossMappings.jl")
    include("SMeasure/smeasure.jl")
    include("PredictiveAsymmetry/PredictiveAsymmetry.jl")
    include("example_systems/ExampleSystems.jl")

    using Requires
    function __init__()
        #@require UncertainData="dcd9ba68-c27b-5cea-ae21-829cd07325bf" begin
        #   include("integrations/uncertaindata.jl")
        #end

        #@require Simplices="d5428e67-3037-59ba-9ab1-57a04f0a3b6a" begin
        # #   import PerronFrobenius: SimplexExact, SimplexPoint
        #    export SimplexExact, SimplexPoint
        #end
    end
end


using FluxGAN
using Test

@testset "FluxGAN.jl" begin
    # Write your tests here.
end


using RPRMakie
using Test

RPRMakie.activate!(resource=RPR.RPR_CREATION_FLAGS_ENABLE_CPU, iterations=50)
f, ax, pl = meshscatter(rand(Point3f, 100), color=:blue)
out = joinpath(@__DIR__, "recorded")
isdir(out) && rm(out)
mkdir("recorded")
save(joinpath(out, "test.png"), ax.scene);


export LiveJournal

"""
LiveJournal is a free on-line community with almost 10 million members; a significant
fraction of these members are highly active. (For example, roughly 300,000 update their
content in any given 24-hour period.) LiveJournal allows members to maintain journals,
individual and group blogs, and it allows people to declare which other members are their
friends they belong.

This network has:
Vertices      : 4847571
Edges         : 68993773
Vertex Props  : -
Edge Props    : -
"""
abstract LiveJournal <: Dataset

function download(::Type{LiveJournal})
   dpath = filepath("LiveJournal.txt")
   tpath = "/tmp/soc-LiveJournal1.txt"

   if isfile(dpath)
      return dpath
   end

   if !isfile(tpath)
      download("http://snap.stanford.edu/data/soc-LiveJournal1.txt.gz", "/tmp/soc-LiveJournal1.txt")
      run(`gzip -cd /tmp/soc-LiveJournal1.txt.gz`)
   end

   Nv = 4847571
   Ne = 68993773

   fin = open(tpath)
   vs  = readdlm(fin, Int)
   close(fin)

   fout = open(dpath, "w")
   write(fout, "$Nv,$Ne\n")
   write(fout, "\n")
   write(fout, "\n")
   write(fout, "\n")
   write(fout, "\n")
   writedlm(fout, collect(0:(Nv-1)))
   writecsv(fout, vs)
   close(fout)

   dpath
end


# -*- coding: utf-8 -*-
using Plots
using Random
using ProgressMeter
using Revise

# +
using Uchiyama

n = 50 # number of particles
ϵ = 0.02
c = trunc(Int, 200ϵ) # marker size

rng = MersenneTwister(1234)

particles = SquareParticles(rng, n, ϵ, option = :box)
pc = ParticleCollisions(particles)
bc = BoxCollisions(particles)

# +

steps = 1000
pbar = Progress(steps)

anim = @animate for _ in 1:steps

     dt = step!(particles, pc, bc)

     p = plot(size  = (200,200),
              xlims = (-0.5,0.5),
              ylims = (-0.5,0.5),
              grid  = false,
              axis  = nothing, framestyle= :box, legend=false, widen = false)

     scatter!( getindex.(particles.q,1) .- getindex.(particles.q,2),
               getindex.(particles.q,2) .+ getindex.(particles.q,1),
               markershape  = :square,
               markersize   = c,
               aspect_ratio = :equal)
     next!(pbar)

end every 10
# -

gif(anim, joinpath(@__DIR__, "event_driven.gif"), fps = 10)


"""
    save_surfaces(filename, surfaces)

Save surface triangulation.
The files may be passed to Tetgen with `filename`.node.
"""
function save_surfaces(filename, surfaces)

    default_refinement = 0.1

    # Extract surface triangulation
    (; points, facets, facetmarkers, regions) = surfaces

    npoint = size(points, 2)
    nfacet = size(facets, 2)
    nregion = size(regions, 2)

    # Write list of nodes in .node file
    open(filename * ".node", "w") do io
        write(io, "# Part 1 - node list\n")
        write(io, "# node count, 3D, no attribute, no boundary marker\n")
        write(io, "$npoint 3 0 0\n")
        write(io, "# Node index, node coordinates\n")
        for i = 1:npoint
            write(io, @sprintf "%d %26.16f %26.16f %26.16f\n" i points[:, i]...)
        end
    end

    # Write .poly file
    open(filename * ".poly", "w") do io

        # Write list of holes (refer to separate file)
        write(io, "# Part 1 - node list\n")
        write(io, "#  0 indicates the node list is stored in file .node\n")
        write(io, "0\n")

        # Write list of facets
        write(io, "# Part 2 - facet list\n")
        write(io, "# facet count, yes boundary marker\n")
        write(io, "$nfacet 1\n")
        write(io, "# Node index, node coordinates\n")
        for ifacet = 1:nfacet
            write(io, "1 0 $(facetmarkers[ifacet])\n")
            write(io, @sprintf "%d %d %d %d \n" 3 facets[:, ifacet]...)
        end

        # Write list of holes (empty)
        write(io, "# Part 3 - hole list\n")
        write(io, "0\n")

        # Write list of interior points with their corresponding compartment
        write(io, "# Part 4 - region list\n")
        write(io, "$nregion\n")
        for i = 1:nregion
            write(
                io,
                @sprintf "%d %f %f %f %d %f\n" i regions[:, i]... i default_refinement
            )
        end

    end

end


module Birtwistle2007

using PyPlot

export runSim

include("model/simulation.jl")
using .Sim

include("plotFunc.jl");

function runSim()
    plotFunc_timecourse(Sim)
end

end  # module


#
# Factors Broadcasting
#

# THIS IS VERY POORLY WRITTEN

"""
    broadcast(f, ϕ::Factor, dims, values)

Broadcast a vector (or array of vectors) across the dimension(s) `dims`

See `Base.broadcast` for more info.
"""
Base.broadcast(f, ϕ::Factor, dims, values) = broadcast!(f, deepcopy(ϕ), dims, values)

"""
    broadcast!(f, ϕ::Factor, dims, values)

Broadcast a vector (or array of vectors) across the dimension(s) `dims`

See `Base.broadcast!` for more info.
"""
Base.broadcast!(f, ϕ::Factor, dim::Symbol, values) = Base.broadcast!(f, ϕ, [dim], [values])

function Base.broadcast!(f, ϕ::Factor, dims::Vector{Symbol}, values)
    _check_dims_valid(dims, ϕ)

    (length(dims) != length(values)) &&
        throw(DimensionMismatch("Number of dimensions does not match number of values to broadcast"))

    # broadcast will check if the dimensions of each value are valid
    # check all the dimensions match or its a singleton value
    #any( (length(ϕ, dims) .!= map(length, values)) & map(length, values) .!= 1) ) &&
    #    throw(ArgumentError("Length of dimensions don't match lengths of broadcast array"))

    inds = indexin(dims, ϕ)

    # each vector gets reshaped to be 1x1... except for the index of its dimension
    reshape_dims = ones(Int, ndims(ϕ))
    new_values = Vector{Array}(length(values))

    for (i, val) in enumerate(values)
        if isa(val, Vector)
            # reshape to the proper dimension
            dim_loc = inds[i]
            reshape_dims[dim_loc] = length(val)
            new_values[i] = reshape(val, reshape_dims...)
            reshape_dims[dim_loc] = 1
        else # its a number (?)
            new_values[i] = [val]
        end
    end

    broadcast!(f, ϕ.potential, ϕ.potential, new_values...)

    return ϕ
end


type MergedIter
    it1   # iterator 1
    it2   # iterator 2
    lt    # custom `<` operator to compare elements of 2 iterators
    MergeIter(it1, it2) = new(it1, it2, lt)
end

type MergedState
    hd1   # current head of 1st iterator
    hd2   # current head of 2nd iterator
    s1    # current state of 1st iterator
    s2    # current state of 2nd iterator
end

function Base.start(it::MergedIter)
    s1 = start(it.it1)
    s2 = start(it.it2)
    # doing 1 step ahead and caching next element of each iter
    hd1, s1 = safenext(it.it1, s1)
    hd2, s2 = safenext(it.it2, s2)
    return MergedState(hd1, hd2, s1, s2)
end

"""
Like `next`, but may be called on iterator that is already done. In this case
new item is `nothing` and new state is the same as previous.
"""
function safenext(it, s)
    if done(it, s)
        return nothing, s
    else
        return next(it, s)
    end
end

function Base.next(it::MergedIter, s::MergedState)
    lt = it.lt
    if done(it, s) throw(ArgumentError("Iterator is done")) end
    it1 = it.it1; it2 = it.it2; hd1 = s.hd1; hd2 = s.hd2; s1 = s.s1; s2 = s.s2
    while hd1 == hd2 && hd2 != nothing
        # move second iterator till heads are different or iterator is done
        hd2, s2 = safenext(it2, s2)
    end
    if complete(it1, s1, hd1)
        # 1st iterator is done, but we have one save value in hd1
        if hd1 == nothing || lt(hd2, hd1) # hd1 > hd2
            next_hd2, next_s2 = safenext(it2, s2)
            return hd2, MergedState(hd1, next_hd2, s1, next_s2)
        else  # hd1 cached and hd1 < hd2
            return hd1, MergedState(nothing, hd2, s1, s2)
        end
    elseif complete(it2, s2, hd2)
        # 2st iterator is done, but we have one save value in hd2
        if hd2 == nothing || lt(hd1, hd2) # hd2 > hd1
            next_hd1, next_s1 = safenext(it1, s1)
            return hd1, MergedState(next_hd1, hd2, next_s1, s2)
        else  # hd2 cached and hd2 < hd1
            return hd2, MergedState(hd1, nothing, s1, s2)
        end
    elseif lt(hd1, hd2) # hd1 < hd2
        next_hd1, next_s1 = safenext(it1, s1)
        return hd1, MergedState(next_hd1, hd2, next_s1, s2)
    else
        next_hd2, next_s2 = safenext(it2, s2)
        return hd2, MergedState(hd1, next_hd2, s1, next_s2)
    end
end

"""
Check if iterator is done and no head doesn't contain a cached value.
"""
function complete(it, s, hd)
    return done(it, s) && hd == nothing
end

function Base.done(it::MergedIter, s::MergedState)
    return (done(it.it1, s.s1) && done(it.it2, s.s2) &&
            s.hd1 == nothing && s.hd2 == nothing)
end

"""
Merge 2 sorted iterators. Let `hd1` be a head of 1st iterator and `hd2` -
a head of 2nd iterator. Then merged iterator works as follows:

 * if hd1 < hd2, hd1 is emitted
 * if hd1 > hd2, hd2 is emitted
 * if hd1 == hd2, hd1 is emitted and hd2 is discarded (so no duplicates
                  is produced)
 * if one of iterators is done, tail of another is taken

Comparison is done using `lt` option that defaults to `isless` function.
"""
function mergesorted(it1, it2; lt = isless)
    return MergedIter(it1, it2, lt)
end


#using ExoplanetsSysSim
using HDF5, JLD, DataFrames

filename = joinpath(Pkg.dir("ExoplanetsSysSim"), "data", "q1_q17_dr25_stellar.csv")
cks_filename = joinpath(Pkg.dir("ExoplanetsSysSim"), "data", "cks_physical_merged.csv")
stellar_catalog_file_out = joinpath(Pkg.dir("ExoplanetsSysSim"), "data", "q1_q17_dr25_cks_christiansen.jld")

  df = readtable(filename)
  cks_df = readtable(cks_filename)

  for x in 1:length(cks_df[:id_kic])
    temp_ind = findfirst(df[:kepid], cks_df[x,:id_kic])
    df[temp_ind,:teff] = cks_df[x,:iso_steff]
    df[temp_ind,:logg] = cks_df[x,:iso_slogg]
    df[temp_ind,:mass] = cks_df[x,:iso_smass]
    df[temp_ind,:mass_err1] = cks_df[x,:iso_smass_err1]
    df[temp_ind,:mass_err2] = cks_df[x,:iso_smass_err2]
    df[temp_ind,:radius] = cks_df[x,:iso_srad]
    df[temp_ind,:radius_err1] = cks_df[x,:iso_srad_err1]
    df[temp_ind,:radius_err2] = cks_df[x,:iso_srad_err2]
  end

  has_mass = ! (isna(df[:mass]) | isna(df[:mass_err1]) | isna(df[:mass_err2]))
  has_radius = ! (isna(df[:radius]) | isna(df[:radius_err1]) | isna(df[:radius_err2]))
  has_dens = ! (isna(df[:dens]) | isna(df[:dens_err1]) | isna(df[:dens_err2]))
  has_rest = ! (isna(df[:rrmscdpp04p5]) | isna(df[:dataspan]) | isna(df[:dutycycle]))
  in_Q1Q12 = []
  for x in df[:st_quarters]
    subx = string(x)
    subx = ("0"^(17-length(subx)))*subx
    indQ = search(subx, '1')
    if ((indQ < 1) | (indQ > 12))
      push!(in_Q1Q12, false)
    else
      push!(in_Q1Q12, true)
    end
  end
  is_FGK = []
  for x in 1:length(df[:teff])
    if ((df[x,:teff] > 4000.0) & (df[x,:teff] < 7000.0) & (df[x,:logg] > 4.0))
      push!(is_FGK, true)
    else
      push!(is_FGK, false)
    end
  end
  is_usable = has_radius & has_mass & has_rest & has_dens & is_FGK #& in_Q1Q12
  if contains(filename,"q1_q12_christiansen.jld")
    is_usable = is_usable & in_Q1Q12
  end
  # See options at: http://exoplanetarchive.ipac.caltech.edu/docs/API_keplerstellar_columns.html
  # TODO SCI DETAIL or IMPORTANT?: Read in all CDPP's, so can interpolate?
  symbols_to_keep = [ :kepid, :mass, :mass_err1, :mass_err2, :radius, :radius_err1, :radius_err2, :dens, :dens_err1, :dens_err2, :rrmscdpp04p5, :dataspan, :dutycycle ]
  delete!(df, [~(x in symbols_to_keep) for x in names(df)])    # delete columns that we won't be using anyway
  usable = find(is_usable)
  df = df[usable, symbols_to_keep]

save(stellar_catalog_file_out,"stellar_catalog", df)


####################################################################################################
# Produces required plots for each scenario with appropriate lambda values over a long simulation ##
####################################################################################################

include("../../src/discrete_event_simulation_project.jl")
function results(max_time)
    scenarios = get_scenarios()

    plot_simulation_summary(scenarios[1],
                            max_time = max_time,
                            lambda_range = LinRange(0.1, 5, 20),
                            scenario_label = "scenario 1",
                            save_folder = "test/project_output/scenario_1")

    plot_simulation_summary(scenarios[2],
                            max_time = max_time,
                            lambda_range = LinRange(0.1, 5, 20),
                            scenario_label = "scenario 2",
                            save_folder = "test/project_output/scenario_2")

    plot_simulation_summary(scenarios[3],
                            max_time = max_time,
                            lambda_range = LinRange(0.1, 10, 20),
                            scenario_label = "scenario 3",
                            save_folder = "test/project_output/scenario_3")

    plot_simulation_summary(scenarios[4],
                            max_time = max_time,
                            lambda_range = LinRange(0.1, 0.9, 20),
                            scenario_label = "scenario 4",
                            save_folder = "test/project_output/scenario_4")

    plot_simulation_summary(scenarios[5],
                            max_time = max_time,
                            lambda_range = LinRange(0.1, 1, 20),
                            scenario_label = "scenario 5",
                            save_folder = "test/project_output/scenario_5")

end

results(10^7)


#  Copyright 2016, Iain Dunning, Joey Huchette, Miles Lubin, and contributors
#  This Source Code Form is subject to the terms of the Mozilla Public
#  License, v. 2.0. If a copy of the MPL was not distributed with this
#  file, You can obtain one at http://mozilla.org/MPL/2.0/.
#############################################################################
# JuMP
# An algebraic modelling langauge for Julia
# See http://github.com/JuliaOpt/JuMP.jl
#############################################################################
# test/variable.jl
# Testing for Variable
#############################################################################
using JuMP, FactCheck

facts("[variable] constructors") do
    # Constructors
    mcon = Model()
    @variable(mcon, nobounds)
    @variable(mcon, lbonly >= 0)
    @variable(mcon, ubonly <= 1)
    @variable(mcon, 0 <= bothb <= 1)
    @variable(mcon, 0 <= onerange[-5:5] <= 10)
    @variable(mcon, onerangeub[-7:1] <= 10, Int)
    @variable(mcon, manyrangelb[0:1,10:20,1:1] >= 2)
    @fact getlowerbound(manyrangelb[0,15,1]) --> 2
    s = ["Green","Blue"]
    @variable(mcon, x[i=-10:10,s] <= 5.5, Int, start=i+1)
    @fact getupperbound(x[-4,"Green"]) --> 5.5
    @fact getvalue(x[-3,"Blue"]) --> -2
    @fact isequal(getvariable(mcon, :lbonly),lbonly) --> true
    @fact isequal(getvariable(mcon, :ubonly),ubonly) --> true
    @fact isequal(getvariable(mcon, :onerangeub)[-7],onerangeub[-7]) --> true
    @variable(mcon, lbonly)
    @fact_throws ErrorException getvariable(mcon, :lbonly)
    @fact_throws ErrorException getvariable(mcon, :foo)
    d = Dict()
    @variable(mcon, d["bar"][1:10] == 1)
    @fact getvalue(d["bar"][1]) --> 1
    @fact typeof(zero(nobounds)) --> AffExpr
    @fact typeof(one(nobounds)) --> AffExpr
end

facts("[variable] get and set bounds") do
    m = Model()
    @variable(m, 0 <= x <= 2)
    @fact getlowerbound(x) --> 0
    @fact getupperbound(x) --> 2
    setlowerbound(x, 1)
    @fact getlowerbound(x) --> 1
    setupperbound(x, 3)
    @fact getupperbound(x) --> 3
    @variable(m, y, Bin)
    @fact getlowerbound(y) --> 0
    @fact getupperbound(y) --> 1
    @variable(m, 0 <= y <= 1, Bin)
    @fact getlowerbound(y) --> 0
    @fact getupperbound(y) --> 1
    @variable(m, fixedvar == 2)
    @fact getvalue(fixedvar) --> 2
    @fact getlowerbound(fixedvar) --> 2
    @fact getupperbound(fixedvar) --> 2
    setvalue(fixedvar, 5)
    @fact getvalue(fixedvar) --> 5
    @fact getlowerbound(fixedvar) --> 5
    @fact getupperbound(fixedvar) --> 5
end

facts("[variable] get and set values") do
    m = Model()
    @variable(m, x[1:3])
    x0 = collect(1:3)
    setvalue(x, x0)
    @fact getvalue(x) --> x0
    @fact getvalue([x[1],x[2],x[3]]) --> x0

    @variable(m, y[1:3,1:2])
    @fact_throws DimensionMismatch setvalue(y, collect(1:6))
end

facts("[variable] get and set category") do
    m = Model()
    @variable(m, x[1:3])
    setcategory(x[2], :Int)
    @fact getcategory(x[3]) --> :Cont
    @fact getcategory(x[2]) --> :Int
end

facts("[variable] repeated elements in index set (issue #199)") do
    repeatmod = Model()
    s = [:x,:x,:y]
    @variable(repeatmod, x[s])
    @fact MathProgBase.numvar(repeatmod) --> 3
end

facts("[variable] condition in indexing") do
    fa = repl[:for_all]
    inset, dots = repl[:in], repl[:dots]
    condmod = Model()
    @variable(condmod, x[i=1:10; iseven(i)])
    @variable(condmod, y[j=1:10,k=3:2:9; isodd(j+k) && k <= 8])
    @fact length(x.tupledict) --> 5
    @fact length(y.tupledict) --> 15
    @fact string(condmod) --> "Min 0\nSubject to\n x[i] free $fa i $inset {1,2,$dots,9,10} s.t. iseven(i)\n y[j,k] free $fa j $inset {1,2,$dots,9,10}, k $inset {3,5,7,9} s.t. isodd(j + k) and k <= 8\n"
end

facts("[variable] @variable returning Array{Variable}") do
    m = Model()
    @variable(m, x[1:3,1:4,1:2])
    @variable(m, y[1:0])
    @variable(m, z[1:4])

    @fact typeof(x) --> Array{Variable,3}
    @fact typeof(y) --> Array{Variable,1}
    @fact typeof(z) --> Array{Variable,1}

    @fact typeof(getvalue(x)) --> Array{Float64,3}
    @fact typeof(getvalue(y)) --> Array{Float64,1}
    @fact typeof(getvalue(z)) --> Array{Float64,1}
end

facts("[variable] getvalue on empty things") do
    m = Model()
    @variable(m, x[1:4,  1:0,1:3])   # Array{Variable}
    @variable(m, y[1:4,  2:1,1:3]) # JuMPArray
    @variable(m, z[1:4,Set(),1:3]) # JuMPDict

    @fact getvalue(x) --> Array(Float64, 4, 0, 3)
    @fact typeof(getvalue(y)) <: JuMP.JuMPArray{Float64} --> true
    @fact JuMP.size(getvalue(y)) --> (4,0,3)
    @fact typeof(getvalue(z)) --> JuMP.JuMPArray{Float64,3,Tuple{UnitRange{Int},Set{Any},UnitRange{Int}}}
    @fact length(getvalue(z)) --> 0
end

# Slices three-dimensional JuMPContainer x[I,J,K]
# I,J,K can be singletons, ranges, colons, etc.
function sliceof(x, I, J, K)
    y = Array(Variable, length(I), length(J), length(K))

    ii = 1
    jj = 1
    kk = 1
    for i in I
        for j in J
            for k in K
                y[ii,jj,kk] = x[i,j,k]
                kk += 1
            end
            jj += 1
            kk = 1
        end
        ii += 1
        jj = 1
    end
    if VERSION >= v"0.5.0-dev+1195"
        idx = [length(I)==1, length(J)==1, length(K)==1]
    else
        idx = [false, false, length(K)==1]
        if idx[3] == true && length(J) == 1
            idx[2] = true
        end
    end
    squeeze(y, tuple(find(idx)...))
end

facts("[variable] Slices of JuMPArray (#684)") do
    m = Model()
    @variable(m, x[1:3, 1:4,1:2])
    @variable(m, y[1:3,-1:2,3:4])
    @variable(m, z[1:3,-1:2:4,3:4])
    @variable(m, w[1:3,-1:2,[:red,"blue"]])

    @fact x[:] --> vec(sliceof(x, 1:3, 1:4, 1:2))
    @fact x[:,:,:] --> sliceof(x, 1:3, 1:4, 1:2)
    @fact x[1,:,:] --> sliceof(x, 1, 1:4, 1:2)
    @fact x[1,:,2] --> sliceof(x, 1, 1:4, 2)
    @fact_throws x[1,:,3]
    @fact x[1:2,:,:] --> sliceof(x, 1:2, 1:4, 1:2)
    @fact x[1:2,:,2] --> sliceof(x, 1:2, 1:4, 2)
    @fact x[1:2,:,1:2] --> sliceof(x, 1:2, 1:4, 1:2)
    @fact_throws x[1:2,:,1:3]

    @fact y[:] --> vec(sliceof(y, 1:3, -1:2, 3:4))
    @fact y[:,:,:] --> sliceof(y, 1:3, -1:2, 3:4)
    @fact y[1,:,:] --> sliceof(y, 1, -1:2, 3:4)
    @fact y[1,:,4] --> sliceof(y, 1, -1:2, 4)
    @fact_throws y[1,:,5]
    @fact y[1:2,:,:] --> sliceof(y, 1:2, -1:2, 3:4)
    @fact y[1:2,:,4] --> sliceof(y, 1:2, -1:2, 4)
    @fact y[1:2,:,3:4] --> sliceof(y, 1:2, -1:2, 3:4)
    @fact_throws y[1:2,:,1:3]

    @fact z[:] --> vec(sliceof(z, 1:3, -1:2:4, 3:4))
    @fact z[:,1,:] --> sliceof(z, 1:3, 1, 3:4)
    @fact z[1,1,:] --> sliceof(z, 1, 1, 3:4)
    @fact_throws z[:,5,3]
    @fact z[1:2,1,:] --> sliceof(z, 1:2, 1, 3:4)
    @fact z[1:2,1,4] --> sliceof(z, 1:2, 1, 4)
    @fact z[1:2,1,3:4] --> sliceof(z, 1:2, 1, 3:4)
    @fact_throws z[1:2,1,1:3]

    @fact w[:] --> vec(sliceof(w, 1:3, -1:2, [:red,"blue"]))
    @fact_throws w[:,:,:]
    @fact w[1,:,"blue"] --> sliceof(w, 1, -1:2, ["blue"])
    @fact w[1,:,:red] --> sliceof(w, 1, -1:2, [:red])
    @fact_throws w[1,:,"green"]
    @fact w[1:2,:,"blue"] --> sliceof(w, 1:2, -1:2, ["blue"])
    @fact_throws w[1:2,:,[:red,"blue"]]
end

facts("[variable] Can't use end for indexing a JuMPContainer") do
    m = Model()
    @variable(m, x[0:2,1:4])
    @variable(m, y[i=1:4,j=1:4;true])
    @variable(m, z[0:2])
    @fact_throws x[end,1]
    @fact_throws x[end-1]
    @fact_throws x[0,end-1]
    @fact_throws y[end,end-1]
    @fact_throws y[end,1]
    @fact_throws z[end]
end


export radius, midpoint, lbound, ubound, abs_lbound, abs_ubound, getinterval, getball

"""
    radius([T, ] x::ArbOrRef)

Returns the radius of `x` as a `Mag`. If `T` is given convert to this
type, supports `Mag`, `Arf` and `Arb`.
"""
radius(::Type{Mag}, x::ArbOrRef) = Mag(radref(x))
radius(::Type{Arf}, x::ArbOrRef) = Arf(radref(x), prec = precision(x))
radius(::Type{Arb}, x::ArbOrRef) = Arb(radref(x), prec = precision(x))
radius(x::ArbOrRef) = radius(Mag, x)

"""
    midpoint([T, ] x::ArbOrRef)

Returns the midpoint of `x` as an `Arf`. If `T` is given convert to this
type, supports `Arf` and `Arb`.
"""
midpoint(::Type{Arf}, x::ArbOrRef) = Arf(midref(x))
midpoint(::Type{Arb}, x::ArbOrRef) = Arb(midref(x))
midpoint(x::ArbOrRef) = midpoint(Arf, x)

"""
    lbound([T, ] x::ArbOrRef)

Returns a lower bound of `x` as an `Arf`. If `T` is given convert to
this type, supports `Arf` and `Arb`.

If `x` contains `NaN` it returns `NaN`.
"""
lbound(::Type{Arf}, x::ArbOrRef) = get_lbound!(Arf(prec = precision(x)), x)
function lbound(::Type{Arb}, x::ArbOrRef)
    res = zero(x)
    get_lbound!(midref(res), x)
    return res
end
lbound(x::ArbOrRef) = lbound(Arf, x)

"""
    ubound([T, ] x::ArbOrRef)

Returns an upper bound of `x` as an `Arf`. If `T` is given convert to
this type, supports `Arf` and `Arb`.

If `x` contains `NaN` it returns `NaN`.
"""
ubound(::Type{Arf}, x::ArbOrRef) = get_ubound!(Arf(prec = precision(x)), x)
function ubound(::Type{Arb}, x::ArbOrRef)
    res = zero(x)
    get_ubound!(midref(res), x)
    return res
end
ubound(x::ArbOrRef) = ubound(Arf, x)

"""
    abs_lbound([T, ] x::Union{ArbOrRef,AcbOrRef})

Returns a lower bound of `abs(x)` as an `Arf`. If `T` is given convert
to this type, supports `Arf` and `Arb`.

If `x` contains `NaN` it returns `NaN`.
"""
abs_lbound(::Type{Arf}, x::Union{ArbOrRef,AcbOrRef}) =
    get_abs_lbound!(Arf(prec = precision(x)), x)
function abs_lbound(::Type{Arb}, x::Union{ArbOrRef,AcbOrRef})
    res = Arb(prec = precision(x))
    get_abs_lbound!(midref(res), x)
    return res
end
abs_lbound(x::Union{ArbOrRef,AcbOrRef}) = abs_lbound(Arf, x)

"""
    abs_ubound([T, ] x::Union{ArbOrRef,AcbOrRef})

Returns an upper bound of `abs(x)` as an `Arf`. If `T` is given
convert to this type, supports `Arf` and `Arb`.

If `x` contains `NaN` it returns `NaN`.
"""
abs_ubound(::Type{Arf}, x::Union{ArbOrRef,AcbOrRef}) =
    get_abs_ubound!(Arf(prec = precision(x)), x)
function abs_ubound(::Type{Arb}, x::Union{ArbOrRef,AcbOrRef})
    res = Arb(prec = precision(x))
    get_abs_ubound!(midref(res), x)
    return res
end
abs_ubound(x::Union{ArbOrRef,AcbOrRef}) = abs_ubound(Arf, x)

"""
    getinterval([T, ] x::ArbOrRef)

Returns a tuple `(l, u)` representing an interval `[l, u]` enclosing
the ball `x`, both of them are of type `Arf`. If `T` is given convert
to this type, supports `Arf`, `BigFloat` and `Arb`.

If `x` contains `NaN` both `l` and `u` will be `NaN`.
"""
function getinterval(::Type{Arf}, x::ArbOrRef)
    l, u = Arf(prec = precision(x)), Arf(prec = precision(x))
    get_interval!(l, u, x)
    return (l, u)
end
function getinterval(::Type{BigFloat}, x::ArbOrRef)
    l, u = BigFloat(precision = precision(x)), BigFloat(precision = precision(x))
    get_interval!(l, u, x)
    return (l, u)
end
function getinterval(::Type{Arb}, x::ArbOrRef)
    l, u = zero(x), zero(x)
    get_interval!(midref(l), midref(u), x)
    return (l, u)
end
getinterval(x::ArbOrRef) = getinterval(Arf, x)

"""
    getball([T, ] x::ArbOrRef)

Returns a tuple `(m::Arf, r::Mag)` where `m` is the midpoint of the
ball and `r` is the radius. If `T` is given convert both `m` and `r`
to this type, supports `Arb`.
"""
getball(x::ArbOrRef) = (Arf(midref(x)), Mag(radref(x)))
getball(::Type{Arb}, x::ArbOrRef) = (Arb(midref(x)), Arb(radref(x), prec = precision(x)))

"""
    union(x::ArbOrRef, y::ArbOrRef)
    union(x::AcbOrRef, y::AcbOrRef)
    union(x, y, z...)

`union(x, y)` returns a ball containing the union of `x` and `y`.

`union(x, y, z...)` returns a ball containing the union of all given
balls.
"""
Base.union(x::ArbOrRef, y::ArbOrRef) = union!(Arb(prec = _precision((x, y))), x, y)
Base.union(x::AcbOrRef, y::AcbOrRef) = union!(Acb(prec = _precision((x, y))), x, y)
# TODO: Could be optimized, both for performance and enclosure
Base.union(x::ArbOrRef, y::ArbOrRef, z::ArbOrRef, xs...) =
    foldl(union, xs, init = union(union(x, y), z))
Base.union(x::AcbOrRef, y::AcbOrRef, z::AcbOrRef, xs...) =
    foldl(union, xs, init = union(union(x, y), z))

"""
    intersect(x::ArbOrRef, y::ArbOrRef)
    intersect(x::AcbOrRef, y::AcbOrRef)
    intersect(x, y, z...)

`intersect(x, y)` returns a ball containing the intersection of `x`
and `y`. If `x` and `y` do not overlap (as given by `overlaps(a, b)`)
throws an `ArgumentError`.

`intersect(x, y, z...)` returns a ball containing the intersection of
all given balls. If all the balls do not overlap throws an
`ArgumentError`.
"""
function Base.intersect(x::ArbOrRef, y::ArbOrRef)
    overlaps(x, y) ||
        throw(ArgumentError("intersection of non-intersecting balls not allowed"))
    res = Arb(prec = _precision((x, y)))
    intersection!(res, x, y)
    return res
end
# TODO: Could be optimized, both for performance and enclosure
Base.intersect(x::ArbOrRef, y::ArbOrRef, z::ArbOrRef, xs...) =
    foldl(intersect, xs, init = intersect(intersect(x, y), z))


# Run a single regression test for a user speicfied dataset

dataset = string(ARGS[1])

include("../src/Csw.jl")

using Test, TOML, .program

configfile = TOML.parsefile("./test/data/inputs/inputs.toml")

@testset "$dataset Regression " begin

    main(configfile["$dataset"])
    @test true

end


abstract type Extrapolation end


"""
    StackVector([itr])

Construct a `StackVector` containing up to 64 `Bool` values.
A `StackVector` is stored as an integer in memory and is immutable.
"""
struct StackVector <: AbstractVector{Bool}
    x::UInt
    len::Int

    function StackVector(x::UInt, len::Int, ::Unsafe)
        new(x, len)
    end
end

function StackVector(x::UInt, len::Int)
    (len % UInt) > Sys.WORD_SIZE && throw(DomainError(len, "len must be 0:$(Sys.WORD_SIZE)"))
    return StackVector(x & mask(len), len, unsafe)
end

mask(L) = ifelse(L == 64, typemax(UInt), UInt(1) << (L & 63) - 1)
Base.size(s::StackVector) = (length(s),)
Base.length(s::StackVector) = s.len

function Base.hash(x::StackVector, h::UInt)
    base = (0x93774c8a392b33bb % UInt) * length(x)
    return hash(x.x, h ⊻ base)
end

StackVector() = StackVector(UInt(0), 0, unsafe)

@noinline function throw_stackvec_err()
    throw(DomainError("StackVector can only contain $(Sys.WORD_SIZE) elements"))
end

function StackVector(itr)
    bits = zero(UInt)
    len = 0
    for i in itr
        len += 1
        len > Sys.WORD_SIZE && throw_stackvec_err()
        val = convert(UInt, convert(Bool, i))
        bits |= (val << ((len-1) & 63))
    end
    return StackVector(bits, len, unsafe)
end

function Base.getindex(s::StackVector, i::Int)
    @boundscheck checkbounds(s, i)
    return isodd(s.x >>> unsigned(i-1))
end

"""
    setindex(collection, v, i::Int)

Return a copy of `collection` with the value at index `i` set to `v`.

# Examples

```jldoctest
julia> x = StackVector([true, false])
2-element StackVector:
 1
 0

julia> setindex(x, false, 1)
2-element StackVector:
 0
 0
```
"""
function setindex(s::StackVector, v::Bool, i::Int)
    @boundscheck checkbounds(s, i)
    u = UInt(1) << ((i-1) & 63)
    typeof(s)(ifelse(v, s.x | u, s.x & ~u), s.len, unsafe)
end

function push(s::StackVector, v::Bool, ::Unsafe)
    return StackVector(s.x | UInt(v) << (length(s) & 63), length(s)+1, unsafe)
end

function push(s::StackVector, v)
    length(s) == 64 && throw_stackvec_err()
    v_ = convert(Bool, v)
    return push(s, v_, unsafe)
end

function pop(s::StackVector, ::Unsafe)
    return StackVector(s.x & ~(UInt(1) << (length(s) & 63)), length(s) - 1, unsafe)
end

pop(s::StackVector) = isempty(s) ? throw_empty_err() : pop(s, unsafe)

function Base.iterate(s::StackVector, i::Int=0)
    i+1 > length(s) && return nothing
    isodd(s.x >>> (i&63)), i+1
end

Base.in(v::Bool, s::StackVector) = !iszero(ifelse(v, s.x, s.x ⊻ mask(length(s))))

Base.maximum(s::StackVector) = !minimum(~s)
function Base.minimum(s::StackVector)
    isempty(s) && throw_empty_err()
    return s.x == mask(length(s))
end

Base.sum(s::StackVector) = count_ones(s.x)

function Base.convert(::Type{BitVector}, s::StackVector)
    b = BitVector(undef, length(s))
    !isempty(s) && @inbounds b.chunks[1] = s.x
    b
end

Base.:~(s::StackVector) = StackVector(s.x ⊻ mask(length(s)), s.len, unsafe)

function Base.findfirst(s::StackVector)
    isempty(s) && return nothing
    i = trailing_zeros(s.x) + 1
    i > length(s) && return nothing
    return i
end

function Base.findfirst(f::Function, s::StackVector)
    isempty(s) && return nothing
    ft::Bool = f(true)
    ff::Bool = f(false)
    ft & ff && return 1
    !(ft | ff) && return nothing
    return findfirst(ifelse(ft, s, ~s))
end

Base.argmin(s::StackVector) = argmax(~s)
function Base.argmax(s::StackVector)
    isempty(s) && throw_empty_err()
    f = findfirst(s)
    return f === nothing ? 1 : f
end

function Base.reverse(s::StackVector)
    x = s.x
    x = ((x & 0xaaaaaaaaaaaaaaaa) >>> 1)  | ((x & 0x5555555555555555) << 1)
    x = ((x & 0xcccccccccccccccc) >>> 2)  | ((x & 0x3333333333333333) << 2)
    x = ((x & 0xf0f0f0f0f0f0f0f0) >>> 4)  | ((x & 0x0f0f0f0f0f0f0f0f) << 4)
    x = bswap(x)
    x >>>= sizeof(UInt) << 3 - length(s)
    typeof(s)(x, s.len, unsafe)
end

function Base.circshift(s::StackVector, k::Int)
    isempty(s) && return s
    shift = k % length(s)
    left = ifelse(k < 0, length(s)+shift, shift) & 63
    right = (length(s) - left) & 63
    bits = ((s.x << left) | (s.x >>> right)) & mask(length(s))
    return StackVector(bits, length(s), unsafe)
end


"""
    IncRel{LEFT<:AbsShapeDesc,RIGHT<:AbsShapeDesc,T}

Incidence relation expressing connectivity between an entity of the shape on the
left and a list of entities of the shape on the right.

The incidence relation may be referred to by its code: `(d1, d2)`, where `d1` is
the manifold dimension of the shape collection on the left, and `d2` is the
manifold dimension of the shape collection on the right.

Main operations referred to with the abbreviations used in the paper:

- skt = ir_skeleton
- trp = ir_transpose
- bbf = ir_bbyfacets
- bbr = ir_bbyridges
- idt = ir_identity

Access to the incidence relation data is through indexing:
For instance
```
ir[55]
```
provides access to the entire incidence relation `ir` of the entity 55.
The first component of this incidence relation for the entity 55 is
```
ir[55][1]
```
or equivalently
```
ir[55, 1]
```
"""
struct IncRel{LEFT<:AbsShapeDesc,RIGHT<:AbsShapeDesc,T}
    left::ShapeColl{LEFT}
    right::ShapeColl{RIGHT}
    _v::Vector{T} # vector of vectors of shape numbers
    name::String # name of the incidence relation
    function IncRel(
        left::ShapeColl{LEFT},
        right::ShapeColl{RIGHT},
        v::Vector{T},
        name::String,
        internal
    ) where {LEFT<:AbsShapeDesc,RIGHT<:AbsShapeDesc,T}
        @_check (nshapes(left) == length(v))
        minn = typemax(eltype(v[1]))
        maxn = typemin(eltype(v[1]))
        for i in 1:length(v)
            minn = min(minn, minimum(abs.(v[i])))
            maxn = max(maxn, maximum(abs.(v[i])))
        end
        @_check (nshapes(right) >= maxn)
        return new{LEFT,RIGHT,T}(left, right, v, name)
    end
end

# Provide access to the incidence relation data as if it was a one-dimensional
# or two dimensional array.
Base.IndexStyle(::Type{<:IncRel}) = IndexLinear()
Base.size(ir::IR) where {IR<:IncRel} = (length(ir._v),)
Base.getindex(ir::IR, i::Int) where {IR<:IncRel} = ir._v[i]
Base.getindex(ir::IR, i::Int, k::Int) where {IR<:IncRel} = ir._v[i][k]

"""
    IncRel(left::ShapeColl{LEFT}, right::ShapeColl{RIGHT}, v::Vector{T}) where {LEFT<:AbsShapeDesc,RIGHT<:AbsShapeDesc,T}

Convenience constructor with a vector of vectors and a default name.
"""
function IncRel(
    left::ShapeColl{LEFT},
    right::ShapeColl{RIGHT},
    v::Vector{T}
) where {LEFT<:AbsShapeDesc,RIGHT<:AbsShapeDesc,T}
    IncRel(left, right, deepcopy(v), "(" * left.name * ", " * right.name * ")", true)
end

"""
    IncRel(left::ShapeColl{LEFT}, right::ShapeColl{RIGHT}, v::Vector{T}, name::String) where {LEFT<:AbsShapeDesc,RIGHT<:AbsShapeDesc,T}

Convenience constructor with a vector of vectors and a name.
"""
function IncRel(
    left::ShapeColl{LEFT},
    right::ShapeColl{RIGHT},
    v::Vector{T},
    name::String
) where {LEFT<:AbsShapeDesc,RIGHT<:AbsShapeDesc,T}
    IncRel(left, right, deepcopy(v), name, true)
end

"""
    IncRel(left::ShapeColl{LEFT}, right::ShapeColl{RIGHT}, data::Matrix{MT}) where {LEFT<:AbsShapeDesc,RIGHT<:AbsShapeDesc,MT}

Convenience constructor supplying a matrix instead of a vector of vectors and a default name.
"""
function IncRel(
    left::ShapeColl{LEFT},
    right::ShapeColl{RIGHT},
    data::Matrix{MT}
) where {LEFT<:AbsShapeDesc,RIGHT<:AbsShapeDesc,MT}
    _v = [SVector{nvertices(left.shapedesc)}(data[idx, :]) for idx in 1:size(data, 1)]
    IncRel(left, right, _v, "(" * left.name * ", " * right.name * ")", true)
end

"""
    IncRel(left::ShapeColl{LEFT}, right::ShapeColl{RIGHT}, data::Matrix{MT}, name::String) where {LEFT<:AbsShapeDesc,RIGHT<:AbsShapeDesc,MT}

Convenience constructor supplying a matrix instead of a vector of vectors and a name.
"""
function IncRel(
    left::ShapeColl{LEFT},
    right::ShapeColl{RIGHT},
    data::Matrix{MT},
    name::String
) where {LEFT<:AbsShapeDesc,RIGHT<:AbsShapeDesc,MT}
    _v = [SVector{nvertices(left.shapedesc)}(data[idx, :]) for idx in 1:size(data, 1)]
    IncRel(left, right, _v, name, true)
end

"""
    indextype(ir::IncRel)

Provide type of the incidence index (entity number).
Presumably some type of integer.
"""
indextype(ir::IncRel) = eltype(eltype(ir._v))

"""
    nrelations(ir::IncRel)

Number of individual relations in the incidence relation.
"""
nrelations(ir::IncRel) = length(ir._v)

"""
    nentities(ir::IncRel, j::IT) where {IT}

Number of individual entities in the `j`-th relation in the incidence relation.
"""
nentities(ir::IncRel, j::IT) where {IT} = length(ir._v[j])

"""
    code(ir::IncRel{LEFT,RIGHT,T}) where {LEFT<:AbsShapeDesc,RIGHT<:AbsShapeDesc,T}

Formulate the code of the incidence relation.
"""
ir_code(ir::IncRel{LEFT,RIGHT,T}) where {LEFT<:AbsShapeDesc,RIGHT<:AbsShapeDesc,T} =
    (manifdim(shapedesc(ir.left)), manifdim(shapedesc(ir.right)))

"""
    ir_transpose(ir::IncRel, name = "trp")

Compute the "transposed" incidence relation `(d1, d2)`, where `d2 >= d1`.

This only makes sense for `d2 >= 0`. For `d2=1` we get for each vertex the list
of edges connected to the vertex, and analogously faces and cells for `d=2` and
`d=3`.

# Returns
Incidence relation for the transposed incidence relation. The left and right
shape collection are swapped in the output relative to the input.
"""
function ir_transpose(ir::IncRel, name = "trp")
    @_check (manifdim(ir.left) >= manifdim(ir.right))
    inttype = eltype(ir._v[1])
    # Find out how many of the transpose incidence relations there are
    nvmax = 0
    for j in 1:nrelations(ir)
        for k in 1:nentities(ir, j)
            nvmax = max(nvmax, ir[j, k])
        end
    end
    # pre-allocate relations vector
    _v = Vector{inttype}[]
    sizehint!(_v, nvmax)
    # Initialize the relations to empty
    for i in 1:nvmax
        push!(_v, inttype[])  # initially empty arrays
    end
    # Build the transpose relations
    for j in 1:nrelations(ir)
        for k in 1:nentities(ir, j)
            c = abs(ir[j, k]) # this could be an oriented entity: remove the sign
            push!(_v[c], j)
        end
    end
    return IncRel(ir.right, ir.left, _v, name)
end

function _asmatrix(ir, inttype)
    c = fill(inttype(0), nshapes(ir.left), nvertices(shapedesc(ir.left)))
    for i in 1:nshapes(ir.left)
        c[i, :] = ir[i]
    end
    return c
end

function _mysortdim2!(A)
    # Sort each row  of A in ascending order.
    m, n = size(A)
    r = zeros(eltype(A), n)
    for k in 1:m
        for i in 1:n
            r[i] = A[k, i]
        end
        sort!(r)
        for i in 1:n
            A[k, i] = r[i]
        end
    end
    return A
end

function _mysortrowsperm(A)
    # Sort the rows of A by sorting each column from back to front.
    m, n = size(A)
    indx = collect(1:m)
    sindx = zeros(eltype(A), m)
    nindx = zeros(eltype(A), m)
    col = zeros(eltype(A), m)
    for c in n:-1:1
        for i in 1:m
            col[i] = A[indx[i], c]
        end
        # Sorting a column vector is much faster than sorting a column matrix
        #sindx = sortperm(col, alg=QuickSort);
        sortperm!(sindx, col, alg = QuickSort) # available for 0.4, slightly faster
        for i in 1:m
            nindx[i] = indx[sindx[i]]
        end
        for i in 1:m
            indx[i] = nindx[i]
        end
    end
    return indx
end

function _countrepeats(A)
    m, n = size(A)
    occurrences = zeros(eltype(A), m)
    occurrences[1] = 1
    for i in 2:m
        if A[i, :] == A[i - 1, :]
            occurrences[i] = occurrences[i - 1] + 1
        else
            occurrences[i] = 1
        end
    end
    return occurrences
end

"""
    ir_skeleton(ir::IncRel, name = "skt")

Compute the "skeleton" of an incidence relation.

Compute a new incidence relation from an existing incidence relation to extract
the skeleton.

# Returns
Incidence relation for the skeleton of the input incidence relation. The left
shape collection consists of facets (shapes of manifold dimension one less than
the manifold dimension of the shapes themselves). The right shape collection is
the same as for the input.
"""
function ir_skeleton(ir::IncRel, name = "skt")
    @_check (manifdim(ir.right) == 0)
    @_check (manifdim(ir.left) > 0)
    inttype = eltype(ir._v[1])
    c = _asmatrix(ir, inttype) # incidence as a 2D array
    # construct a 2D array of the hyperface incidences
    hfc = c[:, facetconnectivity(ir.left, 1)]
    for i in 2:nfacets(shapedesc(ir.left))
        hfc = vcat(hfc, c[:, facetconnectivity(ir.left, i)])
    end
    # make a working copy
    s2hfc = deepcopy(hfc)
    _mysortdim2!(s2hfc) # sort columnwise
    idx = _mysortrowsperm(s2hfc) # find row permutation that sorts the rows
    # sort the rows of both the original hyper face array and the column-sorted array
    shfc = hfc[idx, :]
    s2hfc = s2hfc[idx, :]
    rep = _countrepeats(s2hfc) # find the repeats of the rows
    # identify boundary facets: the hyperface is boundary if it has no repeats
    isunq = [(rep[i] == 1) && (rep[i + 1] == 1) for i in 1:(length(rep) - 1)]
    push!(isunq, (rep[end] == 1)) # and the last one
    # unique rows are obtained by ignoring the repeats
    unq = findall(a -> a == 1, rep)
    unqhfc = shfc[unq, :] # unique hyper faces
    sir = IncRel(ShapeColl(facetdesc(ir.left), size(unqhfc, 1)), ir.right, unqhfc, name)
    sir.left.attributes["isboundary"] = VecAttrib(isunq[unq]) # store the is-boundary attribute
    return sir
end

"""
    ir_boundary(ir::IncRel, name = "bdr")

Compute the incidence relation for the boundary of the incidence relation on input.

The `skeleton` function.
"""
function ir_boundary(ir::IncRel, name = "bdr")
    sir = ir_skeleton(ir)
    isboundary = sir.left.attributes["isboundary"]
    ind = [i for i in 1:length(isboundary) if isboundary[i]]
    lft = ShapeColl(shapedesc(sir.left), length(ind), "facets")
    return IncRel(lft, sir.right, sir._v[ind], name)
end

function _selectrepeating(v, nrepeats)
    m = length(v)
    occurrences = zeros(eltype(v), m)
    sort!(v)
    occurrences[1] = 1
    for i in 2:m
        if v[i] == v[i - 1]
            occurrences[i] = occurrences[i - 1] + 1
        else
            occurrences[i] = 1
        end
    end
    repeats = [o == nrepeats for o in occurrences]
    return v[repeats]
end

"""
    ir_bbyfacets(ir::IncRel, fir::IncRel, tfir::IncRel, name = "bbf")

Compute the "bounded by facets" incidence relation `(d, d-1)` for `d`-dimensional shapes.

In other words, this is the incidence relation between shapes and the shapes
that bound these shapes (facets). For tetrahedra as the shapes, the incidence
relation lists the numbers of the faces that bound each individual tetrahedron.
The resulting left shape is of the same shape description as in the `ir`.

# Arguments
- `ir` = incidence relation `(d, 0)`,
- `fir` = incidence relation `(d-1, 0)`,
- `tfir` = transpose of the above, incidence relation `(0, d-1)`.

# Returns
Incidence relation for the bounded-by relationship between shape collections.
The left shape collection is the same as for the `ir`, the right shape
collection is for the facets (shapes of manifold dimension one less than the
manifold dimension of the shapes themselves).

!!! note
    The numbers of the facets are signed: positive when the facet bounds the shape
    in the sense in which it is defined by the shape as oriented with an outer
    normal; negative otherwise. The sense is defined by the numbering of the
    1st-order vertices of the facet shape.
"""
function ir_bbyfacets(ir::IncRel, fir::IncRel, tfir::IncRel, name = "bbf")
    @_check (manifdim(ir.right) == 0)
    @_check (manifdim(fir.right) == 0)
    @_check (manifdim(tfir.left) == 0)
    @_check manifdim(ir.left) == manifdim(tfir.right) + 1
    @_check manifdim(tfir.right) == manifdim(fir.left)
    inttype = eltype(ir._v[1])
    n1st = n1storderv(fir.left.shapedesc)
    nshif = nshifts(fir.left.shapedesc)
    # Sweep through the relations of (d, 0,) and use the (0, d-1) tfir
    _c = fill(inttype(0), nrelations(ir), nfacets(ir.left))
    for i in 1:nrelations(ir) # Sweep through the relations of (d, 0)
        sv = ir[i]
        c = inttype[] # this will be the list of facets at the vertices of this entity
        for j in 1:nentities(ir, i) # for all vertices
            fv = ir[i, j]
            for k in 1:nentities(tfir, fv)
                push!(c, tfir[fv, k])
            end # k
        end
        c = _selectrepeating(c, nvertices(tfir.right)) # keep the repeats
        @_check length(c) == nfacets(ir.left)
        # Now figure out the orientations
        for k in 1:nfacets(ir.left)
            fc = sv[facetconnectivity(ir.left, k)]
            sfc = sort(fc)
            for j in 1:length(c)
                oc = fir[c[j]]
                if sfc == sort(oc)
                    sgn = _sense(fc[1:n1st], oc, nshif)
                    _c[i, k] = sgn * c[j]
                    break
                end
            end # j
        end
    end
    bfacets = ShapeColl(fir.left.shapedesc, nshapes(tfir.right))
    return IncRel(ir.left, bfacets, _c, name)
end

"""
    ir_bbyfacets(ir::IncRel, fir::IncRel, name = "bbf")

Compute the "bounded by facets" incidence relation `(d, d-1)` for `d`-dimensional shapes.

Convenience function where the transpose of the incidence relation on the right
is computed on the fly.

# See also: [`ir_bbyfacets(ir::IncRel, fir::IncRel, tfir::IncRel)`](@ref)
"""
function ir_bbyfacets(ir::IncRel, fir::IncRel, name = "bbf")
    return ir_bbyfacets(ir, fir, ir_transpose(fir), name)
end

"""
    ir_bbyridges(ir::IncRel, eir::IncRel, teir::IncRel, name = "bbr")

Compute the "bounded by ridges" incidence relation `(d, d-2)` for `d`-dimensional shapes.

In other words, this is the incidence between shapes and the shapes that "bound"
the boundaries of these shapes (i. e. ridges). For tetrahedra as the shapes, the
incidence relation lists the numbers of the edges that "bound" each individual
tetrahedron. The resulting shape is of the same shape description as the
`shapes` on input.

# Returns
The incidence relation for the "bounded by" relationship between shape
collections. The left shape collection is the same as for the `ir`, the right
shape collection is for the ridges (shapes of manifold dimension two less than
the manifold dimension of the shapes themselves).

!!! note
    The numbers of the ridges are signed: positive when the ridge bounds the shape
    in the sense in which it is defined by the shape as oriented with an outer
    normal; negative otherwise. The sense is defined by the numbering of the
    1st-order vertices of the ridge shape.
"""
function ir_bbyridges(ir::IncRel, eir::IncRel, teir::IncRel, name = "bbr")
    @_check (manifdim(ir.right) == 0) && (manifdim(teir.left) == 0)
    @_check manifdim(ir.left) == manifdim(teir.right) + 2
    @_check manifdim(teir.right) == manifdim(eir.left)
    inttype = eltype(ir._v[1])
    n1st = n1storderv(eir.left.shapedesc)
    nshif = nshifts(eir.left.shapedesc)
    # Sweep through the relations of (d, 0), and use the (0, d-2) teir
    _c = fill(inttype(0), nrelations(ir), nridges(ir.left))
    for i in 1:nrelations(ir) # Sweep through the relations of (d, 0)
        sv = ir[i]
        c = inttype[] # this will be the list of facets at the vertices of this entity
        for j in 1:nentities(ir, i) # for all vertices
            fv = ir[i, j]
            for k in 1:nentities(teir, fv)
                push!(c, teir[fv, k])
            end # k
        end
        c = _selectrepeating(c, nvertices(teir.right)) # keep the repeats
        @_check length(c) == nridges(ir.left)
        # Now figure out the orientations
        for k in 1:nridges(ir.left)
            fc = sv[ridgeconnectivity(ir.left, k)]
            sfc = sort(fc)
            for j in 1:length(c)
                oc = eir[c[j]]
                if sfc == sort(oc)
                    sgn = _sense(fc[1:n1st], oc, nshif)
                    _c[i, k] = sgn * c[j]
                    break
                end
            end # j
        end
    end
    bridges = ShapeColl(eir.left.shapedesc, nshapes(teir.right))
    return IncRel(ir.left, bridges, _c, name)
end

"""
    ir_bbyridges(ir::IncRel, eir::IncRel, name = "bbr")

Compute the "bounded-by-ridges" incidence relation `(d, d-2)`.

Convenience function where the transpose of the incidence relation on the right
is computed on the fly.

# See also: [`ir_bbyridges(ir::IncRel, eir::IncRel, efir::IncRel)`](@ref)
"""
function ir_bbyridges(ir::IncRel, eir::IncRel, name = "bbr")
    return ir_bbyridges(ir, eir, ir_transpose(eir), name)
end

"""
    ir_identity(ir::IncRel, side = :left)

Compute the "identity" incidence relation `(d, d)`.

Either the left (side = :left) or the right (side = :right) shape is mapped
to itself.
"""
function ir_identity(ir::IncRel, side = :left)
    if side == :left
        return IncRel(
            ir.left,
            ir.left,
            [SVector{1,Int64}([idx]) for idx in 1:nshapes(ir.left)],
        )
    else
        return IncRel(
            ir.right,
            ir.right,
            [SVector{1,Int64}([idx]) for idx in 1:nshapes(ir.right)],
        )
    end
end

"""
    ir_subset(ir::IncRel, list)

Compute the incidence relation representing a subset of the shape collection
on the left.
"""
function ir_subset(ir::IncRel, list)
    ssl = ShapeColl(ir.left.shapedesc, length(list))
    return IncRel(ssl, ir.right, ir._v[list], ir.left.name * "subset")
end

"""
    Base.summary(ir::IncRel)

Form a brief summary of the incidence relation.
"""
function Base.summary(ir::IncRel)
    return "$(ir.name): " * summary(ir.left) * ", " * summary(ir.right)
end


"""
Helps to maintain the pool of reusable arrays of different sizes
and reduce the burden on garbage collection.
"""
immutable ArrayPool{T<:Number}
    length_pools::Dict{Int, Vector{Vector{T}}}

    ArrayPool{T}() where {T<:Number} = new(Dict{Int, Vector{Vector{T}}}())
end

"""
Gets an array of specific size from the pool.
The returned array should be returned back to the pool using `release!()`.
"""
function acquire!{T}(pool::ArrayPool{T}, size)
    len = prod(size)
    len_pool = haskey(pool.length_pools, len) ?
               pool.length_pools[len] :
               get!(pool.length_pools, len, Vector{Vector{T}}())
    isempty(len_pool) ? Array{T}(size) : reinterpret(T, pop!(len_pool), size)
end

"""
Releases an array returned by `acquire!()` back into the pool.
"""
function release!{T}(pool::ArrayPool{T}, arr::Array{T})
    len = length(arr)
    len_pool = haskey(pool.length_pools, len) ?
               pool.length_pools[len] :
               get!(pool.length_pools, len, Vector{Vector{T}}())
    push!(len_pool, reinterpret(T, arr, (len,)))
    pool
end


module DateDataFrames

using Base: padding
export DateDataFrame, resample_nearest, resample, Nearest,
        timestamp, values, names, rename, rename!, lead, moving,
        ⊕, ⊖, ⊗, ⊘

using DataFrames
import DataFrames: names, rename, rename!
using Dates
using Statistics
using RecipesBase

include("date_dataframes.jl")
include("broadcasting.jl")
include("resample.jl")
include("misc.jl")
include("infix_ops.jl")
include("recipes.jl")

end


# This file was generated, do not modify it. # hide
@load OneHotEncoder
MultinomialClassifier = @load MultinomialClassifier pkg="MLJLinearModels"


"""
    struct SkeletonPair{L,R} <: GridapType
      left::L
      right::R
    end
"""
struct SkeletonPair{L,R} <: GridapType
  left::L
  right::R
end

function reindex(a::AbstractArray,b::SkeletonPair)
  left = reindex(a,b.left)
  right = reindex(a,b.right)
  SkeletonPair(left,right)
end


#! format: off
var_map(s::String) = var_map(Val(Symbol(s)))
var_map(::Val{T}) where {T} = nothing

var_map(::Val{Symbol("ρ")}) = ("rho", ())
var_map(::Val{Symbol("ρu[1]")}) = ("u_mean", (:ρ,))
var_map(::Val{Symbol("ρu[2]")}) = ("v_mean", (:ρ,))
var_map(::Val{Symbol("moisture.ρq_tot")}) = ("qt_mean", (:ρ,))
var_map(::Val{Symbol("turbconv.updraft[1].ρa")}) = ("updraft_fraction", (:ρ,))
var_map(::Val{Symbol("turbconv.updraft[1].ρaw")}) = ("updraft_w", (:ρ, :a))
var_map(::Val{Symbol("turbconv.updraft[1].ρaq_tot")}) = ("updraft_qt", (:ρ, :a))
var_map(::Val{Symbol("turbconv.updraft[1].ρaθ_liq")}) = ("updraft_thetali", (:ρ, :a))
var_map(::Val{Symbol("turbconv.environment.ρatke")}) = ("tke_mean", (:ρ, :a))
var_map(::Val{Symbol("turbconv.environment.ρaθ_liq_cv")}) = ("env_thetali2", (:ρ, :a))
var_map(::Val{Symbol("turbconv.environment.ρaq_tot_cv")}) = ("env_qt2", (:ρ, :a))
#! format: on


import Krylov.KRYLOV_SOLVERS

function init_Ksolver(M, v, sp::SolverParams)
  kmethod = sp.kmethod
  if kmethod ∈ (:gpmr, :diom, :fom, :dqgmres, :gmres)
    return eval(KRYLOV_SOLVERS[kmethod])(M, v, sp.mem)
  end
  return eval(KRYLOV_SOLVERS[kmethod])(M, v)
end

ksolve!(
  KS::MinresSolver{T},
  K,
  rhs::AbstractVector{T},
  M;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T} = minres!(
  KS,
  K,
  rhs,
  M = M,
  verbose = verbose,
  atol = zero(T),
  rtol = zero(T),
  ratol = atol,
  rrtol = rtol,
)

ksolve!(
  KS::MinresQlpSolver{T},
  K,
  rhs::AbstractVector{T},
  M;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T} = minres_qlp!(KS, K, rhs, M = M, verbose = verbose, atol = atol, rtol = rtol)

ksolve!(
  KS::SymmlqSolver{T},
  K,
  rhs::AbstractVector{T},
  M;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T} = symmlq!(KS, K, rhs, M = M, verbose = verbose, atol = atol, rtol = rtol)

ksolve!(
  KS::CgSolver{T},
  K,
  rhs::AbstractVector{T},
  M;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T} = cg!(KS, K, rhs, M = M, verbose = verbose, atol = atol, rtol = rtol)

ksolve!(
  KS::CgLanczosSolver{T},
  K,
  rhs::AbstractVector{T},
  M;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T} = cg_lanczos!(KS, K, rhs, M = M, verbose = verbose, atol = atol, rtol = rtol)

ksolve!(
  KS::CrSolver{T},
  K,
  rhs::AbstractVector{T},
  M;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T} = cr!(KS, K, rhs, M = M, verbose = verbose, atol = atol, rtol = rtol)

ksolve!(
  KS::BilqSolver{T},
  K,
  rhs::AbstractVector{T},
  M;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T} = bilq!(KS, K, rhs, verbose = verbose, atol = atol, rtol = rtol)

ksolve!(
  KS::QmrSolver{T},
  K,
  rhs::AbstractVector{T},
  M;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T} = qmr!(KS, K, rhs, verbose = verbose, atol = atol, rtol = rtol)

ksolve!(
  KS::UsymlqSolver{T},
  K,
  rhs::AbstractVector{T},
  M;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T} = usymlq!(KS, K, rhs, rhs, verbose = verbose, atol = atol, rtol = rtol)

ksolve!(
  KS::UsymqrSolver{T},
  K,
  rhs::AbstractVector{T},
  M;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T} = usymqr!(KS, K, rhs, rhs, verbose = verbose, atol = atol, rtol = rtol)

ksolve!(
  KS::BicgstabSolver{T},
  K,
  rhs::AbstractVector{T},
  M;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T} = bicgstab!(KS, K, rhs, verbose = verbose, atol = atol, rtol = rtol)

ksolve!(
  KS::DiomSolver{T},
  K,
  rhs::AbstractVector{T},
  M;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T} = diom!(KS, K, rhs, verbose = verbose, atol = atol, rtol = rtol)

ksolve!(
  KS::FomSolver{T},
  K,
  rhs::AbstractVector{T},
  M;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T} = fom!(KS, K, rhs, verbose = verbose, atol = atol, rtol = rtol)

ksolve!(
  KS::DqgmresSolver{T},
  K,
  rhs::AbstractVector{T},
  M;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T} = dqgmres!(KS, K, rhs, verbose = verbose, atol = atol, rtol = rtol)

ksolve!(
  KS::GmresSolver{T},
  K,
  rhs::AbstractVector{T},
  M;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T} = gmres!(KS, K, rhs, verbose = verbose, atol = atol, rtol = rtol)

ksolve!(
  KS::TricgSolver{T},
  A,
  ξ1::AbstractVector{T},
  ξ2::AbstractVector{T},
  M,
  N;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
  gsp::Bool = false,
) where {T} =
  tricg!(KS, A, ξ1, ξ2, M = M, N = N, flip = true, verbose = verbose, atol = atol, rtol = rtol)

ksolve!(
  KS::TrimrSolver{T},
  A,
  ξ1::AbstractVector{T},
  ξ2::AbstractVector{T},
  M,
  N;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
  gsp::Bool = false,
) where {T} = trimr!(
  KS,
  A,
  ξ1,
  ξ2,
  M = M,
  N = (gsp == true ? I : N),
  τ = -one(T),
  ν = (gsp ? zero(T) : one(T)),
  verbose = verbose,
  atol = atol,
  rtol = rtol,
)

function ksolve!(
  KS::GpmrSolver{T},
  A,
  ξ1::AbstractVector{T},
  ξ2::AbstractVector{T},
  M,
  N;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
  gsp::Bool = false,
) where {T}
  sqrtδI = sqrt(N.λ) * I
  return gpmr!(
    KS,
    A,
    A',
    ξ1,
    ξ2,
    C = sqrt.(M),
    D = gsp ? I : sqrtδI,
    E = sqrt.(M),
    F = gsp ? I : sqrtδI,
    λ = -one(T),
    μ = gsp ? zero(T) : one(T),
    verbose = verbose,
    atol = atol,
    rtol = rtol,
  )
end

# gpmr solver for K3.5
function ksolve!(
  KS::GpmrSolver{T},
  A,
  ξ1::AbstractVector{T},
  ξ2::AbstractVector{T},
  M,
  N::AbstractLinearOperator{T};
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T}
  return gpmr!(
    KS,
    A,
    A',
    ξ1,
    ξ2,
    C = M,
    D = N,
    E = transpose(M),
    F = N,
    λ = -one(T),
    verbose = verbose,
    atol = atol,
    rtol = rtol,
  )
end

function ksolve!(
  KS::LslqSolver{T},
  A,
  ξ1::AbstractVector{T},
  M,
  δ::T;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T}
  return lslq!(
    KS,
    A,
    ξ1,
    M = M,
    N = δ > zero(T) ? (one(T) / δ) * I : I,
    verbose = verbose,
    atol = atol,
    btol = rtol,
    etol = zero(T),
    utol = zero(T),
    conlim = T(Inf),
    sqd = δ > zero(T),
  )
end

function ksolve!(
  KS::LsqrSolver{T},
  A,
  ξ1::AbstractVector{T},
  M,
  δ::T;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T}
  return lsqr!(
    KS,
    A,
    ξ1,
    M = M,
    N = δ > zero(T) ? (one(T) / δ) * I : I,
    verbose = verbose,
    axtol = atol,
    btol = rtol,
    # atol = atol,
    # rtol = rtol,
    etol = zero(T),
    conlim = T(Inf),
    sqd = δ > zero(T),
  )
end

function ksolve!(
  KS::LsmrSolver{T},
  A,
  ξ1::AbstractVector{T},
  M,
  δ::T;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T}
  return lsmr!(
    KS,
    A,
    ξ1,
    M = M,
    N = δ > zero(T) ? (one(T) / δ) * I : I,
    verbose = verbose,
    axtol = zero(T), # atol,
    btol = zero(T), # rtol,
    atol = atol,
    rtol = rtol,
    etol = zero(T),
    conlim = T(Inf),
    sqd = δ > zero(T),
  )
end

function ksolve!(
  KS::LnlqSolver{T},
  A,
  ξ2::AbstractVector{T},
  M,
  δ::T;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T}
  return lnlq!(
    KS,
    A,
    ξ2,
    N = M,
    M = δ > zero(T) ? (one(T) / δ) * I : I,
    verbose = verbose,
    atol = atol,
    rtol = rtol,
    sqd = δ > zero(T),
  )
end

function ksolve!(
  KS::CraigSolver{T},
  A,
  ξ2::AbstractVector{T},
  M,
  δ::T;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T}
  return craig!(
    KS,
    A,
    ξ2,
    N = M,
    M = δ > zero(T) ? (one(T) / δ) * I : I,
    λ = δ > zero(T) ? one(T) : zero(T),
    verbose = verbose,
    atol = atol,
    rtol = rtol,
    btol = zero(T),
    conlim = T(Inf),
  )
end

function ksolve!(
  KS::CraigmrSolver{T},
  A,
  ξ2::AbstractVector{T},
  M,
  δ::T;
  verbose::Integer = 0,
  atol::T = T(sqrt(eps(T))),
  rtol::T = T(sqrt(eps(T))),
) where {T}
  return craigmr!(
    KS,
    A,
    ξ2,
    N = M,
    M = δ > zero(T) ? (one(T) / δ) * I : I,
    verbose = verbose,
    atol = atol,
    rtol = rtol,
    sqd = true,
  )
end

function kscale!(rhs::AbstractVector{T}) where {T <: Real}
  rhsNorm = norm(rhs)
  if rhsNorm != zero(T)
    rhs ./= rhsNorm
  end
  return rhsNorm
end

function kunscale!(sol::AbstractVector{T}, rhsNorm::T) where {T <: Real}
  if rhsNorm != zero(T)
    sol .*= rhsNorm
  end
end

function update_kresiduals_history!(
  res::AbstractResiduals{T},
  K,
  sol::AbstractVector{T},
  rhs::AbstractVector{T},
) where {T <: Real}
  if typeof(res) <: ResidualsHistory
    mul!(res.KΔxy, K, sol) # krylov residuals
    res.Kres .= res.KΔxy .- rhs
  end
end

function update_kresiduals_history_K1struct!(
  res::AbstractResiduals{T},
  A,
  E,
  tmp,
  δ,
  sol::AbstractVector{T},
  rhs::AbstractVector{T},
  formul::Symbol,
) where {T <: Real}
  if typeof(res) <: ResidualsHistory
    mul!(tmp, A', sol)
    tmp ./= E
    @views mul!(res.KΔxy, A, tmp)
    if formul == :K1_1
      mul!(res.Kres, A, rhs ./ E, -one(T), zero(T))
    elseif formul == :K1_2
      res.Kres .= .-rhs
    end
    res.Kres .+= res.KΔxy .+ δ .* sol # residual computation
  end
end


# define main types used in this module

"""
  [`AbstractParamTpe`](@ref) for Navier-Stokes physics.
"""
mutable struct ParamType{Tdim, Tsol, Tres, Tmsh} <: AbstractParamType{Tdim}
  # need this in case we ever want to call node level functions in the Euler
  # module
  euler_params::EulerEquationMod.ParamType{Tdim, :conservative, Tsol, Tres, Tmsh}
  # some of these fields alias those in euler_params so the outside world
  # see a consistent view
  f::BufferedIO
  t::Float64  # current time value (for unsteady simulations)
  order::Int  # accuracy of elements (p=1,2,3...)
  time::Timings
  isViscous::Bool  # can this ever be false?
  penalty_relaxation::Float64
  const_tii::Float64
  Re::Float64  # free stream Reynolds number

  function ParamType{Tdim, Tsol, Tres, Tmsh}(euler_eqn::EulerEquationMod.EulerData, mesh, sbp, opts, order::Integer) where {Tdim, Tsol, Tres, Tmsh}

    # incomplete initialization
    params = new{Tdim, Tsol, Tres, Tmsh}()

    params.euler_params = euler_eqn.params
    params.f = params.euler_params.f  # alias
    params.t = 0.0
    params.order = order
    params.time = params.euler_params.time  # alias

    params.penalty_relaxation = opts["Cip"]
    params.isViscous = opts["isViscous"]

    if params.isViscous
      params.const_tii = calcTraceInverseInequalityConst(sbp, mesh.sbpface)
    end

    params.Re = opts["Re"]
    return params
  end

end  # end ParamType definition

"""
  Useful alias for 2D ParamType
"""
const ParamType2 = ParamType{2}

"""
  Useful alias for 3D ParamType
"""
const ParamType3 = ParamType{3}

"""
  Datatype for holding all the data required to solve the Naver-Stokes equations

  In particular, it contains the field `euler_eqn`, which is an
  [`EulerData`](@ref).  This object is used to evaluate the inviscid terms
  and generally allows the `NavierStokesMod` to build on top of
  `EulerEquationMod`.

  Note that many of the corresponding fields between `eqn_euler` and
  the `NSData` are aliased, including:

    * `q`
    * `q_vec`
    * `res`
    * `res_vec`
    * `res_edge`
    * `Minv`
    * `M`
"""
mutable struct NSData_{Tsol, Tres, Tdim, Tmsh} <: NSData{Tsol, Tres, Tdim}

  params::ParamType{Tdim, Tsol, Tres, Tmsh}
  comm::MPI.Comm
  commsize::Int
  myrank::Int

  # the nested EulerData object
  euler_eqn::EulerData_{Tsol, Tres, Tdim, Tmsh, :conservative}

  # required arrays
  q::Array{Tsol,3}        # holds conservative variables for all nodes
  q_vec::Array{Tres,1}    # initial condition in vector form
  res::Array{Tres, 3}     # element-based residual
  res_vec::Array{Tres, 1} # vector form of residual (aliases res in DG case)
  res_edge::Array{Tres, 4}       # edge based residual (not used)

  Minv3D::Array{Float64, 3}       # inverse mass matrix for application to res, not res_vec
  Minv::Array{Float64, 1}         # inverse mass matrix
  M::Array{Float64, 1}            # mass matrix

  # required functions
  multiplyA0inv::Function         # multiply an array by inv(A0), where A0
                                  # is the coefficient matrix of the time derivative
  majorIterationCallback::Function # called before every major (Newton/RK) itr

  # physics-specific stuff
  src_func::SRCType  # functor for the source term
  viscous_flux_func::FluxType  # functor for the viscous flux numerical flux function

  q_face::Array{Tsol, 4}     # q interpolated to face
  q_bndry::Array{Tsol, 3}    # q interpolated to boundary
  bndryflux::Array{Tres, 3}  # inviscid-type boundary flux
  flux_face::Array{Tres, 3}  # inviscid-type face flux
  area_sum::Array{Tmsh, 1}		  # the wet area of each element
  # vecflux_face::Array{Tres, 4}    # stores (u+ - u-)nx*, (numDofs, numNodes, numFaces)
  vecflux_faceL::Array{Tres, 4}     # stores (u+ - u-)nx*, (numDofs, numNodes, numFaces)
  vecflux_faceR::Array{Tres, 4}     # stores (u+ - u-)nx*, (numDofs, numNodes, numFaces)
  vecflux_bndry::Array{Tres, 4}     # stores (u+ - u-)nx*, (numDofs, numNodes, numFaces)

  file_dict::Dict{String, IO}  # dictionary of all files used for logging

  function NSData_{Tsol, Tres, Tdim, Tmsh}(mesh::AbstractMesh, sbp::AbstractOperator, opts) where {Tsol, Tres, Tdim, Tmsh}

    eqn = new{Tsol, Tres, Tdim, Tmsh}()  # incomplete initialization

    EulerEquationMod.checkOptions(opts)  # get any default values Euler needs
    eqn.euler_eqn = EulerEquationMod.EulerData_{Tsol, Tres, Tdim, Tmsh,
                                                :conservative}(mesh, sbp, opts)

    eqn.params = ParamType{Tdim, Tsol, Tres, Tmsh}(eqn.euler_eqn, mesh, sbp, opts, mesh.order)
    eqn.comm = eqn.euler_eqn.comm
    eqn.commsize = eqn.euler_eqn.commsize
    eqn.myrank = eqn.euler_eqn.myrank

    # alias the arrays to avoid having to update them manually
    eqn.q = eqn.euler_eqn.q
    eqn.q_vec = eqn.euler_eqn.q_vec
    eqn.res = eqn.euler_eqn.res
    eqn.res_vec = eqn.euler_eqn.res_vec
    eqn.res_edge = eqn.euler_eqn.res_edge
    eqn.q_face = eqn.euler_eqn.q_face
    eqn.q_bndry = eqn.euler_eqn.q_bndry
    eqn.bndryflux = eqn.euler_eqn.bndryflux
    eqn.flux_face = eqn.euler_eqn.flux_face
    eqn.Minv3D = eqn.euler_eqn.Minv3D
    eqn.Minv = eqn.euler_eqn.Minv
    eqn.M = eqn.euler_eqn.M
    eqn.file_dict = eqn.euler_eqn.file_dict

    # because NSData is solving in the same variables as Euler, we can reuse
    # their A0inv function
    eqn.multiplyA0inv = eqn.euler_eqn.multiplyA0inv

    eqn.majorIterationCallback = majorIterationCallback

   if eqn.params.isViscous
     numfacenodes = mesh.numNodesPerFace
     numfaces = mesh.numInterfaces
     numBndFaces = mesh.numBoundaryFaces
     numvars  = mesh.numDofPerNode
     # eqn.vecflux_face = zeros(Tsol, Tdim, numvars, numfacenodes, numfaces)
     eqn.vecflux_faceL = zeros(Tsol, Tdim, numvars, numfacenodes, numfaces)
     eqn.vecflux_faceR = zeros(Tsol, Tdim, numvars, numfacenodes, numfaces)
     eqn.vecflux_bndry = zeros(Tsol, Tdim, numvars, numfacenodes, numBndFaces)
     eqn.area_sum = zeros(Tmsh, mesh.numEl)
     calcElemSurfaceArea(mesh, sbp, eqn)
   else
     # eqn.vecflux_face  = Array{Tsol}(0, 0, 0, 0)
     eqn.vecflux_faceL = Array{Tsol}(0, 0, 0, 0)
     eqn.vecflux_faceR = Array{Tsol}(0, 0, 0, 0)
     eqn.vecflux_bndry = Array{Tsol}(0, 0, 0, 0)
     eqn.area_sum = Array{Tsol}(0)
   end

   return eqn
 end  # end constructor

end  # end NSData_ type definition

import PDESolver: copyParameters

function copyParameters(eqn_old::NSData, eqn_new::NSData)

  EulerEquationMod.copyParameters(eqn_old.euler_params, eqn_new.euler_params)
  eqn_new.params.order = eqn_old.params.order
  eqn_new.params.isViscous = eqn_old.params.isViscous
  eqn_new.params.penalty_relaxation = eqn_old.params.penalty_relaxation
  eqn_new.params.const_tii = eqn_old.params.const_tii
  eqn_new.params.Re = eqn_old.params.Re

  return nothing
end