using Rubin
using Tests
using Elliptic
using HypergeometricFunctions
using Polylogarithms
using SpecialFunctions

@test integrate(x^n, x) == :(x^(1+n)*(1+n)^-1)
@test integrate(exp(x), x) == :(exp(x))
@test integrate(x^-1, x) == :(log(x))
@test integrate(a^x, x) == :(a^x*log(a)^-1)
@test integrate(sin(x), x) == :(-1*cos(x))
@test integrate(cos(x), x) == :(sin(x))
@test integrate(sec(x)^2, x) == :(tan(x))
@test integrate(csc(x)^2, x) == :(-1*cot(x))
@test integrate(sec(x)*tan(x), x) == :(sec(x))
@test integrate(cot(x)*csc(x), x) == :(-1*csc(x))
@test integrate(sinh(x), x) == :(cosh(x))
@test integrate(cosh(x), x) == :(sinh(x))
@test integrate(tan(x), x) == :(-1*log(cos(x)))
@test integrate(cot(x), x) == :(log(sin(x)))
@test integrate(x*sin(x), x) == :(-1*x*cos(x)+sin(x))
@test integrate(log(x), x) == :(-1x+x*log(x))
@test integrate(x^2*exp(x), x) == :(2*exp(x)+x^2*exp(x)+-2*x*exp(x))
@test integrate(exp(x)*sin(x), x) == :((1/2)*exp(x)*sin(x)+-1//2*cos(x)*exp(x))
@test integrate(arctan(x), x) == :(-1//2*log(1+x^2)+x*arctan(x))
@test integrate(x*exp(2x), x) == :(-1//4*exp(2x)+(1/2)*x*exp(2x))
@test integrate(x*cos(x), x) == :(x*sin(x)+cos(x))
@test integrate(x*sin(4x), x) == :(1//16*sin(4x)+-1//4*x*cos(4x))
@test integrate(x*log(x), x) == :(-1//4*x^2+(1/2)*x^2*log(x))
@test integrate(x^2*cos(3x), x) == :(-2//27*sin(3x)+1//3*x^2*sin(3x)+2//9*x*cos(3x))
@test integrate(x^2*sin(2x), x) == :(1//4*cos(2x)+(1/2)*x*sin(2x)+-1//2*x^2*cos(2x))
@test integrate(log(x)^2, x) == :(2x+x*log(x)^2+-2*x*log(x))
@test integrate(arcsin(x), x) == :((1+-1*x^2)^(1/2)+x*arcsin(x))
@test integrate(t*cos(t)*sin(t), t) == :(-1//4*t+(1/2)*t*sin(t)^2+1//4*cos(t)*sin(t))
@test integrate(t*sec(t)^2, t) == :(t*tan(t)+log(cos(t)))
@test integrate(t^2*log(t), t) == :(-1//9*t^3+1//3*t^3*log(t))
@test integrate(t^3*exp(t), t) == :(-6*exp(t)+t^3*exp(t)+-3*t^2*exp(t)+6*t*exp(t))
@test integrate(exp(2t)*sin(3t), t) == :(-3//13*cos(3t)*exp(2t)+2//13*exp(2t)*sin(3t))
@test integrate(cos(3t)*exp(-1t), t) == :(-1//10*cos(3t)*exp(-1t)+3//10*exp(-1t)*sin(3t))
@test integrate(y*sinh(y), y) == :(-1*sinh(y)+y*cosh(y))
@test integrate(y*cosh(a*y), y) == :(-1*a^-2*cosh(a*y)+y*a^-1*sinh(a*y))
@test integrate(t*exp(-1t), t) == :(-1*exp(-1t)+-1*t*exp(-1t))
@test integrate(t^(1/2)*log(t), t) == :(-4//9*t^3//2+2//3*t^3//2*log(t))
@test integrate(x*cos(2x), x) == :(1//4*cos(2x)+(1/2)*x*sin(2x))
@test integrate(x^2*exp(-1x), x) == :(-2*exp(-1x)+-1*x^2*exp(-1x)+-2*x*exp(-1x))
@test integrate(arccos(x), x) == :(-1*(1+-1*x^2)^(1/2)+x*arccos(x))
@test integrate(x*csc(x)^2, x) == :(-1*x*cot(x)+log(sin(x)))
@test integrate(cos(5x)*sin(3x), x) == :(-1//16*cos(8x)+1//4*cos(2x))
@test integrate(sin(2x)*sin(4x), x) == :(-1//12*sin(6x)+1//4*sin(2x))
@test integrate(cos(x)*log(sin(x)), x) == :(-1*sin(x)+log(sin(x))*sin(x))
@test integrate(x^3*exp(x^2), x) == :(-1//2*exp(x^2)+(1/2)*x^2*exp(x^2))
@test integrate((3+2x)*exp(x), x) == :(-2*exp(x)+(3+2x)*exp(x))
@test integrate(x*5^x, x) == :(-1*5^x*log(5)^-2+x*5^x*log(5)^-1)
@test integrate(cos(log(x)), x) == :((1/2)*x*cos(log(x))+(1/2)*x*sin(log(x)))
@test integrate(exp(x^(1/2)), x) == :(-2*exp(x^(1/2))+2*x^(1/2)*exp(x^(1/2)))
@test integrate(log(x^(1/2)), x) == :(-1//2*x+x*log(x^(1/2)))
@test integrate(sin(log(x)), x) == :((1/2)*x*sin(log(x))+-1//2*x*cos(log(x)))
@test integrate(sin(x^(1/2)), x) == :(2*sin(x^(1/2))+-2*x^(1/2)*cos(x^(1/2)))
@test integrate(x^5*cos(x^3), x) == :(1//3*cos(x^3)+1//3*x^3*sin(x^3))
@test integrate(x^5*exp(x^2), x) == :((1/2)*x^4*exp(x^2)+-1*x^2*exp(x^2)+exp(x^2))
@test integrate(x*arctan(x), x) == :((1/2)*arctan(x)+-1//2*x+(1/2)*x^2*arctan(x))
@test integrate(x*cos(pi*x), x) == :(pi^-2*cos(pi*x)+x*pi^-1*sin(pi*x))
@test integrate(x^(1/2)*log(x), x) == :(-4//9*x^3//2+2//3*x^3//2*log(x))
@test integrate(sin(3x)^2, x) == :((1/2)*x+-1//6*cos(3x)*sin(3x))
@test integrate(cos(x)^2, x) == :((1/2)*x+(1/2)*cos(x)*sin(x))
@test integrate(cos(x)^4, x) == :(3//8*x+1//4*cos(x)^3*sin(x)+3//8*cos(x)*sin(x))
@test integrate(sin(x)^3, x) == :(-1*cos(x)+1//3*cos(x)^3)
@test integrate(cos(x)^4*sin(x)^3, x) == :(-1//5*cos(x)^5+1//7*cos(x)^7)
@test integrate(cos(x)^3*sin(x)^4, x) == :(-1//7*sin(x)^7+1//5*sin(x)^5)
@test integrate(cos(x)^2*sin(x)^4, x) == :(1//16*x+-1//6*cos(x)^3*sin(x)^3+-1//8*cos(x)^3*sin(x)+1//16*cos(x)*sin(x))
@test integrate(cos(x)^2*sin(x)^2, x) == :(1//8*x+-1//4*cos(x)^3*sin(x)+1//8*cos(x)*sin(x))
@test integrate((1+-1*sin(2x))^2, x) == :(3//2*x+-1//4*cos(2x)*sin(2x)+cos(2x))
@test integrate(cos(x)*sin(x+1//6*pi), x) == :(-1//4*cos(2x+1//6*pi)+1//4*x)
@test integrate(cos(x)^5*sin(x)^5, x) == :(-1//4*sin(x)^8+1//6*sin(x)^6+1//10*sin(x)^10)
@test integrate(sin(x)^6, x) == :(5//16*x+-5//16*cos(x)*sin(x)+-5//24*sin(x)^3*cos(x)+-1//6*sin(x)^5*cos(x))
@test integrate(cos(x)^6, x) == :(5//16*x+1//6*cos(x)^5*sin(x)+5//16*cos(x)*sin(x)+5//24*cos(x)^3*sin(x))
@test integrate(cos(2x)^4*sin(2x)^2, x) == :(1//16*x+-1//12*cos(2x)^5*sin(2x)+1//32*cos(2x)*sin(2x)+1//48*cos(2x)^3*sin(2x))
@test integrate(sin(x)^5, x) == :(-1*cos(x)+-1//5*cos(x)^5+2//3*cos(x)^3)
@test integrate(cos(x)^4*sin(x)^4, x) == :(3//128*x+-1//8*cos(x)^5*sin(x)^3+-1//16*cos(x)^5*sin(x)+1//64*cos(x)^3*sin(x)+3//128*cos(x)*sin(x))
@test integrate(cos(x)^(1/2)*sin(x)^3, x) == :(-2//3*cos(x)^3//2+2//7*cos(x)^7//2)
@test integrate(cos(x)^3*sin(x)^(1/2), x) == :(-2//7*sin(x)^7//2+2//3*sin(x)^3//2)
@test integrate(x^-1//2*cos(x^(1/2))^2, x) == :(x^(1/2)+cos(x^(1/2))*sin(x^(1/2)))
@test integrate(x*sin(x^2)^3, x) == :(-1//2*cos(x^2)+1//6*cos(x^2)^3)
@test integrate(cos(x)^2*tan(x)^3, x) == :((1/2)*cos(x)^2+-1*log(cos(x)))
@test integrate(cot(x)^5*sin(x)^2, x) == :((1/2)*sin(x)^2+-2*log(sin(x))+-1//2*csc(x)^2)
@test integrate(cos(x)^-1*(1+-1*sin(x)), x) == :(log(1+sin(x)))
@test integrate((1+-1*sin(x))^-1, x) == :((1+-1*sin(x))^-1*cos(x))
@test integrate(tan(x)^2, x) == :(-1x+tan(x))
@test integrate(tan(x)^4, x) == :(x+-1*tan(x)+1//3*tan(x)^3)
@test integrate(sec(x)^4, x) == :(1//3*tan(x)^3+tan(x))
@test integrate(sec(x)^6, x) == :(1//5*tan(x)^5+2//3*tan(x)^3+tan(x))
@test integrate(sec(x)^2*tan(x)^4, x) == :(1//5*tan(x)^5)
@test integrate(sec(x)^4*tan(x)^2, x) == :(1//3*tan(x)^3+1//5*tan(x)^5)
@test integrate(sec(x)^3*tan(x), x) == :(1//3*sec(x)^3)
@test integrate(sec(x)^3*tan(x)^3, x) == :(-1//3*sec(x)^3+1//5*sec(x)^5)
@test integrate(tan(x)^5, x) == :(-1*log(cos(x))+-1//2*tan(x)^2+1//4*tan(x)^4)
@test integrate(tan(x)^6, x) == :(-1x+-1//3*tan(x)^3+1//5*tan(x)^5+tan(x))
@test integrate(tan(x)^5*sec(x), x) == :(-2//3*sec(x)^3+1//5*sec(x)^5+sec(x))
@test integrate(sec(x)^3*tan(x)^5, x) == :(-2//5*sec(x)^5+1//3*sec(x)^3+1//7*sec(x)^7)
@test integrate(sec(x)^6*tan(x), x) == :(1//6*sec(x)^6)
@test integrate(sec(x)^6*tan(x)^3, x) == :(-1//6*sec(x)^6+1//8*sec(x)^8)
@test integrate(cot(x)^-1*sec(x)^2, x) == :((1/2)*sec(x)^2)
@test integrate(tan(x)^2*sec(x), x) == :(-1//2*arctanh(sin(x))+(1/2)*sec(x)*tan(x))
@test integrate(cot(x)^2, x) == :(-1x+-1*cot(x))
@test integrate(cot(x)^3, x) == :(-1*log(sin(x))+-1//2*cot(x)^2)
@test integrate(cot(x)^4*csc(x)^4, x) == :(-1//5*cot(x)^5+-1//7*cot(x)^7)
@test integrate(cot(x)^3*csc(x)^4, x) == :(-1//6*csc(x)^6+1//4*csc(x)^4)
@test integrate(csc(x), x) == :(-1*arctanh(cos(x)))
@test integrate(csc(x)^3, x) == :(-1//2*arctanh(cos(x))+-1//2*cot(x)*csc(x))
@test integrate(cos(x)^2*sin(x)^-1, x) == :(-1*arctanh(cos(x))+cos(x))
@test integrate(sin(x)^-4, x) == :(-1*cot(x)+-1//3*cot(x)^3)
@test integrate(sin(2x)*sin(5x), x) == :(-1//14*sin(7x)+1//6*sin(3x))
@test integrate(cos(x)*sin(3x), x) == :(-1//4*cos(2x)+-1//8*cos(4x))
@test integrate(cos(3x)*cos(4x), x) == :((1/2)*sin(x)+1//14*sin(7x))
@test integrate(sin(3x)*sin(6x), x) == :(-1//18*sin(9x)+1//6*sin(3x))
@test integrate(cos(x)^5*sin(x), x) == :(-1//6*cos(x)^6)
@test integrate(cos(x)*cos(2x)*cos(3x), x) == :(1//4*x+1//8*sin(2x)+1//16*sin(4x)+1//24*sin(6x))
@test integrate(sec(x)^-2*(1+-1*tan(x)^2), x) == :(cos(x)*sin(x))
@test integrate(sin(2x)^-1*(cos(x)+sin(x)), x) == :((1/2)*arctanh(sin(x))+-1//2*arctanh(cos(x)))
@test integrate(sin(x)^2*tan(x), x) == :((1/2)*cos(x)^2+-1*log(cos(x)))
@test integrate(cos(x)^2*cot(x)^3, x) == :((1/2)*sin(x)^2+-2*log(sin(x))+-1//2*csc(x)^2)
@test integrate(sec(x)^3*tan(x), x) == :(1//3*sec(x)^3)
@test integrate(sec(x)^3*tan(x)^3, x) == :(-1//3*sec(x)^3+1//5*sec(x)^5)
@test integrate(x^-2*(9+-1*x^2)^(1/2), x) == :(-1*arcsin(1//3*x)+-1*x^-1*(9+-1*x^2)^(1/2))
@test integrate(x^-2*(4+x^2)^-1//2, x) == :(-1//4*x^-1*(4+x^2)^(1/2))
@test integrate(x*(4+x^2)^-1//2, x) == :((4+x^2)^(1/2))
@test integrate((x^2+-1*a^2)^-1//2, x) == :(arctanh(x*(x^2+-1*a^2)^-1//2))
@test integrate(x^3*(9+4*x^2)^-3//2, x) == :(1//16*(9+4*x^2)^(1/2)+9//16*(9+4*x^2)^-1//2)
@test integrate(x*(3+-1*x^2+-2x)^-1//2, x) == :(-1*(3+-1*x^2+-2x)^(1/2)+arcsin(-1//2+-1//2*x))
@test integrate(x^-2*(1+-1*x^2)^-1//2, x) == :(-1*x^-1*(1+-1*x^2)^(1/2))
@test integrate(x^3*(4+-1*x^2)^(1/2), x) == :(-4//3*(4+-1*x^2)^3//2+1//5*(4+-1*x^2)^5//2)
@test integrate(x*(1+-1*x^2)^-1//2, x) == :(-1*(1+-1*x^2)^(1/2))
@test integrate(x*(4+-1*x^2)^(1/2), x) == :(-1//3*(4+-1*x^2)^3//2)
@test integrate((1+-4*x^2)^(1/2), x) == :(1//4*arcsin(2x)+(1/2)*x*(1+-4*x^2)^(1/2))
@test integrate(x^3*(4+x^2)^-1//2, x) == :(-4*(4+x^2)^(1/2)+1//3*(4+x^2)^3//2)
@test integrate((9+x^2)^-1//2, x) == :(arcsinh(1//3*x))
@test integrate((1+x^2)^(1/2), x) == :((1/2)*arcsinh(x)+(1/2)*x*(1+x^2)^(1/2))
@test integrate(x^-3*(-16+x^2)^-1//2, x) == :(1//128*arctan(1//4*(-16+x^2)^(1/2))+1//32*x^-2*(-16+x^2)^(1/2))
@test integrate(x^-4*(x^2+-1*a^2)^(1/2), x) == :(1//3*a^-2*x^-3*(x^2+-1*a^2)^3//2)
@test integrate(x^-1*(-4+9*x^2)^(1/2), x) == :((-4+9*x^2)^(1/2)+-2*arctan((1/2)*(-4+9*x^2)^(1/2)))
@test integrate(x^-2*(-9+16*x^2)^-1//2, x) == :(1//9*x^-1*(-9+16*x^2)^(1/2))
@test integrate(x^2*(a^2+-1*x^2)^-3//2, x) == :(-1*arctan(x*(a^2+-1*x^2)^-1//2)+x*(a^2+-1*x^2)^-1//2)
@test integrate(x^2*(5+-1*x^2)^-1//2, x) == :(5//2*arcsin(1//5*x*5^(1/2))+-1//2*x*(5+-1*x^2)^(1/2))
@test integrate(x^-1*(3+x^2)^-1//2, x) == :(-1//3*3^(1/2)*arctanh(1//3*3^(1/2)*(3+x^2)^(1/2)))
@test integrate(x*(4+x^2)^-5//2, x) == :(-1//3*(4+x^2)^-3//2)
@test integrate(x^3*(4+-9*x^2)^(1/2), x) == :(-4//243*(4+-9*x^2)^3//2+1//405*(4+-9*x^2)^5//2)
@test integrate(x^2*(9+-1*x^2)^(1/2), x) == :(81//8*arcsin(1//3*x)+-9//8*x*(9+-1*x^2)^(1/2)+1//4*x^3*(9+-1*x^2)^(1/2))
@test integrate(5*x*(1+x^2)^(1/2), x) == :(5//3*(1+x^2)^3//2)
@test integrate((-25+4*x^2)^-3//2, x) == :(-1//25*x*(-25+4*x^2)^-1//2)
@test integrate((-1*x^2+2x)^(1/2), x) == :(-1//2*arcsin(1+-1x)+(-1*x^2+2x)^(1/2)*(-1//2+(1/2)*x))
@test integrate((8+x^2+4x)^-1//2, x) == :(arcsinh(1+(1/2)*x))
@test integrate((-8+6x+9*x^2)^-1//2, x) == :(1//3*arctanh((-8+6x+9*x^2)^-1//2*(1+3x)))
@test integrate(x^2*(-1*x^2+4x)^-1//2, x) == :(-6*arcsin(1+-1//2*x)+-3*(-1*x^2+4x)^(1/2)+-1//2*x*(-1*x^2+4x)^(1/2))
@test integrate((2+x^2+2x)^-2, x) == :((1/2)*arctan(1+x)+(4+2*x^2+4x)^-1*(1+x))
@test integrate((5+-1*x^2+-4x)^-5//2, x) == :(1//27*(5+-1*x^2+-4x)^-3//2*(2+x)+1//243*(5+-1*x^2+-4x)^-1//2*(4+2x))
@test integrate((9+-1*exp(2t))^(1/2)*exp(t), t) == :(9//2*arcsin(1//3*exp(t))+(1/2)*(9+-1*exp(2t))^(1/2)*exp(t))
@test integrate((-9+exp(2t))^(1/2), t) == :((-9+exp(2t))^(1/2)+-3*arctan(1//3*(-9+exp(2t))^(1/2)))
@test integrate((a^2+x^2)^-1//2, x) == :(arctanh(x*(a^2+x^2)^-1//2))
@test integrate((-2+x+x^2)^-1*(5+x), x) == :(-1*log(2+x)+2*log(1+-1x))
@test integrate((-1+x)^-1*(x+x^3), x) == :((1/2)*x^2+2x+2*log(1+-1x)+1//3*x^3)
@test integrate((-2x+2*x^3+3*x^2)^-1*(-1+x^2+2x), x) == :((1/2)*log(x)+-1//10*log(2+x)+1//10*log(1+-2x))
@test integrate((1+x^3+-1x+-1*x^2)^-1*(1+x^4+-2*x^2+4x), x) == :(x+(1/2)*x^2+-1*log(1+x)+2*(1+-1x)^-1+log(1+-1x))
@test integrate((x^3+4x)^-1*(4+-1x+2*x^2), x) == :((1/2)*log(4+x^2)+-1//2*arctan((1/2)*x)+log(x))
@test integrate((3+-4x+4*x^2)^-1*(2+-3x+4*x^2), x) == :(x+1//8*log(3+-4x+4*x^2)+1//8*2^(1/2)*arctan((1/2)*2^(1/2)*(1+-2x)))
@test integrate(x^-1*(1+x^2)^-3*(-1+x)^-1*(1+x+x^2)^-1*(1+x^2+x^3), x) == :(-1*log(x)+-1//2*log(1+x+x^2)+1//8*log(1+-1x)+7//16*arctan(x)+15//16*log(1+x^2)+-1*(8+8*x^2)^-1*(3+-3x)+3*x*(16+16*x^2)^-1+-1//3*3^(1/2)*arctan(1//3*3^(1/2)*(1+2x))+1//8*(1+x^2)^-2*(1+x))
@test integrate(x^-1*(1+x^2)^-2*(1+-1*x^3+-3x+2*x^2), x) == :(-2*arctan(x)+-1//2*log(1+x^2)+-1*(2+2*x^2)^-1*(1+2x)+log(x))
@test integrate((1+x^2)^-2, x) == :((1/2)*arctan(x)+x*(2+2*x^2)^-1)
@test integrate((-1+x)^-1*(2+x)^-1, x) == :(-1//3*log(2+x)+1//3*log(1+-1x))
@test integrate(7*(-12+2*x^2+5x)^-1, x) == :(-7//11*log(4+x)+7//11*log(3+-2x))
@test integrate((-1+2x)^-2*(3+2x)^-1*(-4+x^2+3x), x) == :(-9*(32+-64x)^-1+-25//128*log(3+2x)+41//128*log(1+-2x))
@test integrate((-6+x)^-1*(3+5x)^-3*(x^3+-1*x^2), x) == :(201*(45375+75625x)^-1+-12//1375*(3+5x)^-2+20//3993*log(6+-1x)+1493//499125*log(3+5x))
@test integrate((x^4+-1*x^3)^-1, x) == :(x^-1+(1/2)*x^-2+-1*log(x)+log(1+-1x))
@test integrate((x^3+-1x)^-1*(1+x^3+x^4+-1x+-1*x^2), x) == :(x+(1/2)*x^2+(1/2)*log(1+-1*x^2)+-1*log(x))
@test integrate(x^-1*(2+x^2)^-1*(-2+x^2), x) == :(-1*log(x)+log(2+x^2))
@test integrate((1+x^2)^-1*(2+x^2)^-1*(2+x^3+-4*x^2), x) == :(6*arctan(x)+-1//2*log(1+x^2)+-5*2^(1/2)*arctan((1/2)*x*2^(1/2))+log(2+x^2))
@test integrate((1+x^2)^-1*(4+x^2)^-2*(1+x^2+x^4), x) == :(1//9*arctan(x)+25//144*arctan((1/2)*x)+-13*x*(96+24*x^2)^-1)
@test integrate((-3+2x)^-1*(5+x)^-2*(1+x+x^2)^-1*(1+16x), x) == :(-79*(1365+273x)^-1+-481//5586*log(1+x+x^2)+200//3211*log(3+-2x)+2731//24843*log(5+x)+451//8379*3^(1/2)*arctan(1//3*3^(1/2)*(1+2x)))
@test integrate(x^4*(9+x^2)^-3, x) == :(1//8*arctan(1//3*x)+-3*x*(72+8*x^2)^-1+-1//4*x^3*(9+x^2)^-2)
@test integrate(19*x*(-1+x)^-3*(3+4*x^2+5x)^-2, x) == :(-1843*(4416+-4416x)^-1+-399//736*(1+-1x)^-2+-209//4608*log(3+4*x^2+5x)+209//2304*log(1+-1x)+114437//1218816*23^(1/2)*arctan(1//23*23^(1/2)*(5+8x))+1//276*(1+-1x)^-2*(3+4*x^2+5x)^-1*(741+836x))
@test integrate((x^3+x^4+2*x^2)^-1*(1+x^2+x^3), x) == :(-1//2*x^-1+-1//4*log(x)+5//8*log(2+x+x^2)+1//28*7^(1/2)*arctan(1//7*7^(1/2)*(1+2x)))
@test integrate((x^6+-1*x^3)^-1, x) == :((1/2)*x^-2+-1//6*log(1+x+x^2)+1//3*log(1+-1x)+-1//3*3^(1/2)*arctan(1//3*3^(1/2)*(1+2x)))
@test integrate(x^2*(1+x)^-1, x) == :((1/2)*x^2+-1x+log(1+x))
@test integrate(x*(-5+x)^-1, x) == :(x+5*log(5+-1x))
@test integrate((-1+x)^-1*(2+x)^-1*(-1+4x), x) == :(3*log(2+x)+log(1+-1x))
@test integrate((1+x)^-1*(2+x)^-1, x) == :(-1*log(2+x)+log(1+x))
@test integrate((3+2x)^-1*(-5+6x), x) == :(-7*log(3+2x)+3x)
@test integrate((a+x)^-1*(b+x)^-1, x) == :((a+-1b)^-1*log(b+x)+-1*(a+-1b)^-1*log(a+x))
@test integrate((x^2+-1x)^-1*(1+x^2), x) == :(x+-1*log(x)+2*log(1+-1x))
@test integrate((-12+x+x^2)^-1*(1+x^2+x^3+-12x), x) == :((1/2)*x^2+-1//7*log(4+x)+1//7*log(3+-1x))
@test integrate((1+x)^-2*(3+2x), x) == :(-1*(1+x)^-1+2*log(1+x))
@test integrate(x^-1*(1+x)^-1*(3+2x)^-1, x) == :(-1*log(1+x)+1//3*log(x)+2//3*log(3+2x))
@test integrate((x^3+-3x+2*x^2)^-1*(-3+5x+6*x^2), x) == :(2*log(1+-1x)+3*log(3+x)+log(x))
@test integrate(x*(4+x^2+4x)^-1, x) == :(2*(2+x)^-1+log(2+x))
@test integrate((-1+x)^-2*(4+x)^-1, x) == :((5+-5x)^-1+-1//25*log(1+-1x)+1//25*log(4+x))
@test integrate(x^2*(-3+x)^-1*(2+x)^-2, x) == :(4*(10+5x)^-1+9//25*log(3+-1x)+16//25*log(2+x))
@test integrate((x^3+2*x^2)^-1*(-2+3x+5*x^2), x) == :(x^-1+2*log(x)+3*log(2+x))
@test integrate((-6+x+x^3+4*x^2)^-1*(18+-4*x^2+-2x), x) == :(-3*log(3+x)+-2*log(2+x)+log(1+-1x))
@test integrate((4+x^3+3*x^2)^-1*(x^2+2x), x) == :(1//3*log(4+x^3+3*x^2))
@test integrate(x^-2*(-1+x)^-2, x) == :((1+-1x)^-1+-1*x^-1+-2*log(1+-1x)+2*log(x))
@test integrate(x^2*(1+x)^-3, x) == :(2*(1+x)^-1+-1//2*(1+x)^-2+log(1+x))
@test integrate((x^4+-1*x^2)^-1, x) == :(x^-1+-1*arctanh(x))
@test integrate((1+x^4+-1*x^2)^-1*(-1x+2*x^3), x) == :((1/2)*log(1+x^4+-1*x^2))
@test integrate(x^3*(1+x^2)^-1, x) == :((1/2)*x^2+-1//2*log(1+x^2))
@test integrate((2+x^2+2x)^-1*(-1+x), x) == :((1/2)*log(2+x^2+2x)+-2*arctan(1+x))
@test integrate(x*(1+x+x^2)^-1, x) == :((1/2)*log(1+x+x^2)+-1//3*3^(1/2)*arctan(1//3*3^(1/2)*(1+2x)))
@test integrate((5+4x+4*x^2)^-1*(7+4*x^2+5x), x) == :(x+1//8*log(5+4x+4*x^2)+3//8*arctan(1/2+x))
@test integrate((1+x^2)^-1*(-1+x)^-1*(5+-4x+3*x^2), x) == :((1/2)*log(1+x^2)+-3*arctan(x)+2*log(1+-1x))
@test integrate((x^3+3x)^-1*(3+2x), x) == :(-1//2*log(3+x^2)+2//3*3^(1/2)*arctan(1//3*x*3^(1/2))+log(x))
@test integrate((-1+x^3)^-1, x) == :(-1//6*log(1+x+x^2)+1//3*log(1+-1x)+-1//3*3^(1/2)*arctan(1//3*3^(1/2)*(1+2x)))
@test integrate(x^3*(1+x^3)^-1, x) == :(x+-1//3*log(1+x)+1//6*log(1+x^2+-1x)+1//3*3^(1/2)*arctan(1//3*3^(1/2)*(1+-2x)))
@test integrate((1+x^2)^-1*(-1+x)^-2*(-1+x^2+-2x), x) == :((-1+x)^-1+-1//2*log(1+x^2)+arctan(x)+log(1+-1x))
@test integrate(x^4*(-1+x^4)^-1, x) == :(x+-1//2*arctan(x)+-1//2*arctanh(x))
@test integrate((1+x^2)^-1*(2+x^2)^-1*(-4+-1*x^2+3*x^3+6x), x) == :(-3*arctan(x)+3//2*log(1+x^2)+2^(1/2)*arctan((1/2)*x*2^(1/2)))
@test integrate((4+x^4+5*x^2)^-1*(1+x+x^3+-2*x^2), x) == :((1/2)*log(4+x^2)+-3//2*arctan((1/2)*x)+arctan(x))
@test integrate((4+x^2+2x)^-2*(-3+x), x) == :((24+6*x^2+12x)^-1*(-7+-4x)+-2//9*3^(1/2)*arctan(1//3*3^(1/2)*(1+x)))
@test integrate(x^-1*(1+x^2)^-2*(1+x^4), x) == :((1+x^2)^-1+log(x))
@test integrate((2+sin(x)^2+-3*sin(x))^-1*(-3+2*sin(x))*cos(x), x) == :(log(2+sin(x)^2+-3*sin(x)))
@test integrate((5+cos(x)^2)^-1*cos(x)^2*sin(x), x) == :(-1*cos(x)+5^(1/2)*arctan(1//5*5^(1/2)*cos(x)))
@test integrate((-3+x^2+2x)^-1, x) == :(-1//4*log(3+x)+1//4*log(1+-1x))
@test integrate((x^2+-2x)^-1, x) == :((1/2)*log(2+-1x)+-1//2*log(x))
@test integrate((-7+4*x^2+12x)^-1*(1+2x), x) == :(1//8*log(1+-2x)+3//8*log(7+2x))
@test integrate(x*(-1+x+x^2)^-1, x) == :(1//10*(5+5^(1/2))*log(1+5^(1/2)+2x)+1//10*(5+-1*5^(1/2))*log(1+-1*5^(1/2)+2x))
@test integrate((-70+-299x+-286*x^2+-13*x^4+30*x^5+50*x^3)^-1*(-32+-27*x^2+4*x^3+5x), x) == :(-3146//80155*log(7+-3x)+-334//323*log(1+2x)+4822//4879*log(2+5x)+11049//260015*log(5+x+x^2)+3988//260015*19^(1/2)*arctan(1//19*19^(1/2)*(1+2x)))
@test integrate((4+-80*x^3+-80*x^5+-20x+41*x^2+100*x^6+116*x^4)^-1*(8+-13*x^2+-7*x^3+12*x^5), x) == :(5828*(18150+-45375x)^-1+-59096//99825*log(2+-5x)+2843//7986*log(1+2*x^2)+-1*(1452+2904*x^2)^-1*(313+502x)+503//15972*2^(1/2)*arctan(x*2^(1/2)))
@test integrate(x^-1*(4+x)^(1/2), x) == :(-4*arctanh((1/2)*(4+x)^(1/2))+2*(4+x)^(1/2))
@test integrate((x^(1/2)+-1*x^-1//3)^-1, x) == :(2*x^(1/2)+6//5*log(1+-1*x^1//6)+-1*(3//10+-3//10*5^(1/2))*log(2+x^1//6+2*x^1//3+5^(1/2)*x^1//6)+-1*(3//10+3//10*5^(1/2))*log(2+x^1//6+2*x^1//3+-1*5^(1/2)*x^1//6)+-3//5*(10+2*5^(1/2))^(1/2)*arctan((1/2)*(1/2+1//10*5^(1/2))^(1/2)*(1+5^(1/2)+4*x^1//6))+3//5*(10+-2*5^(1/2))^(1/2)*arctan((10+2*5^(1/2))^-1//2*(1+-1*5^(1/2)+4*x^1//6)))
@test integrate((-4*cos(x)+3*sin(x))^-1, x) == :(-1//5*arctanh(3//5*cos(x)+4//5*sin(x)))
@test integrate((1+x^(1/2))^-1, x) == :(-2*log(1+x^(1/2))+2*x^(1/2))
@test integrate((1+x^-1//3)^-1, x) == :(x+-1*log(x)+-3*log(1+x^-1//3)+3*x^1//3+-3//2*x^2//3)
@test integrate(x^(1/2)*(1+x)^-1, x) == :(-2*arctan(x^(1/2))+2*x^(1/2))
@test integrate(x^-1*(1+x)^-1//2, x) == :(-2*arctanh((1+x)^(1/2)))
@test integrate((x+-1*x^1//3)^-1, x) == :(3//2*log(1+-1*x^2//3))
@test integrate((x+-1*(2+x)^(1/2))^-1, x) == :(2//3*log(1+(2+x)^(1/2))+4//3*log(2+-1*(2+x)^(1/2)))
@test integrate(x^2*(-1+x)^-1//2, x) == :(2*(-1+x)^(1/2)+2//5*(-1+x)^5//2+4//3*(-1+x)^3//2)
@test integrate((1+x)^-1*(-1+x)^(1/2), x) == :(2*(-1+x)^(1/2)+-2*2^(1/2)*arctan((1/2)*2^(1/2)*(-1+x)^(1/2)))
@test integrate((1+x^(1/2))^-1//2, x) == :(-4*(1+x^(1/2))^(1/2)+4//3*(1+x^(1/2))^3//2)
@test integrate(x^(1/2)*(x+x^2)^-1, x) == :(2*arctan(x^(1/2)))
@test integrate((-1+x^(1/2))^-1*(1+x^(1/2)), x) == :(x+4*x^(1/2)+4*log(1+-1*x^(1/2)))
@test integrate((-1+x^-1//3)^-1*(1+x^-1//3), x) == :(-1x+-6*x^1//3+-6*log(1+-1*x^1//3)+-3*x^2//3)
@test integrate(x^3*(1+x^2)^-1//3, x) == :(-3//4*(1+x^2)^2//3+3//10*(1+x^2)^5//3)
@test integrate(x^(1/2)*(x^(1/2)+-1*x^-1//3)^-1, x) == :(x+6*x^1//6+6//5*log(1+-1*x^1//6)+-1*(3//10+-3//10*5^(1/2))*log(2+x^1//6+2*x^1//3+-1*5^(1/2)*x^1//6)+-1*(3//10+3//10*5^(1/2))*log(2+x^1//6+2*x^1//3+5^(1/2)*x^1//6)+-3//5*(10+-2*5^(1/2))^(1/2)*arctan((1/2)*(1/2+1//10*5^(1/2))^(1/2)*(1+5^(1/2)+4*x^1//6))+-3//5*(10+2*5^(1/2))^(1/2)*arctan((10+2*5^(1/2))^-1//2*(1+-1*5^(1/2)+4*x^1//6)))
@test integrate((x^(1/2)+x^-1//4)^-1, x) == :(2*x^(1/2)+-2//3*log(1+x^(1/2)+-1*x^1//4)+4//3*log(1+x^1//4)+4//3*3^(1/2)*arctan(1//3*3^(1/2)*(1+-2*x^1//4)))
@test integrate((x^-1//3+x^-1//4)^-1, x) == :(-1x+-12*log(1+x^1//12)+-6*x^1//6+-3*x^1//3+-2*x^(1/2)+4*x^1//4+12*x^1//12+-6//5*x^5//6+-6//7*x^7//6+-3//2*x^2//3+4//3*x^3//4+4//5*x^5//4+12//5*x^5//12+12//7*x^7//12+12//11*x^11//12+12//13*x^13//12)
@test integrate((x^-1*(1+-1x))^(1/2), x) == :(-1*arctan((-1+x^-1)^(1/2))+x*(-1+x^-1)^(1/2))
@test integrate((sin(x)^2+sin(x))^-1*cos(x), x) == :(-1*log(1+sin(x))+log(sin(x)))
@test integrate((2+3*exp(x)+exp(2x))^-1*exp(2x), x) == :(-1*log(1+exp(x))+2*log(2+exp(x)))
@test integrate((1+exp(x))^-1//2, x) == :(-2*arctanh((1+exp(x))^(1/2)))
@test integrate((1+-1*exp(x))^(1/2), x) == :(-2*arctanh((1+-1*exp(x))^(1/2))+2*(1+-1*exp(x))^(1/2))
@test integrate((3+-5*sin(x))^-1, x) == :(-1//4*log(-3*sin((1/2)*x)+cos((1/2)*x))+1//4*log(-1*sin((1/2)*x)+3*cos((1/2)*x)))
@test integrate((cos(x)+sin(x))^-1, x) == :(-1//2*2^(1/2)*arctanh((1/2)*2^(1/2)*(-1*sin(x)+cos(x))))
@test integrate((1+-1*cos(x)+sin(x))^-1, x) == :(-1*log(1+cot((1/2)*x)))
@test integrate((3*sin(x)+4*cos(x))^-1, x) == :(-1//5*arctanh(-4//5*sin(x)+3//5*cos(x)))
@test integrate((sin(x)+tan(x))^-1, x) == :(-1//2*csc(x)^2+-1//2*arctanh(cos(x))+(1/2)*cot(x)*csc(x))
@test integrate((2*sin(x)+sin(2x))^-1, x) == :(1//4*log(tan((1/2)*x))+1//8*tan((1/2)*x)^2)
@test integrate((1+sin(x))^-1*sec(x), x) == :((1/2)*arctanh(sin(x))+-1*(2+2*sin(x))^-1)
@test integrate((a*sin(x)+b*cos(x))^-1, x) == :(-1*(a^2+b^2)^-1//2*arctanh((a^2+b^2)^-1//2*(a*cos(x)+-1*b*sin(x))))
@test integrate((a^2*sin(x)^2+b^2*cos(x)^2)^-1, x) == :(a^-1*b^-1*arctan(a*b^-1*tan(x)))
@test integrate(x*(-1+x^2)^-1, x) == :((1/2)*log(1+-1*x^2))
@test integrate(x^(1/2)*(1+x^(1/2)), x) == :((1/2)*x^2+2//3*x^3//2)
@test integrate((1+-1*cos(x))^-1, x) == :(-1*(1+-1*cos(x))^-1*sin(x))
@test integrate(tan(x)^2*sec(x), x) == :(-1//2*arctanh(sin(x))+(1/2)*sec(x)*tan(x))
@test integrate(sec(x)^3*tan(x)^3, x) == :(-1//3*sec(x)^3+1//5*sec(x)^5)
@test integrate(exp(x^(1/2)), x) == :(-2*exp(x^(1/2))+2*x^(1/2)*exp(x^(1/2)))
@test integrate((x^3+-10x+-3*x^2)^-1*(1+x^5), x) == :(19x+-31//14*log(2+x)+-1//10*log(x)+1//3*x^3+3//2*x^2+3126//35*log(5+-1x))
@test integrate(x^-1*log(x)^-1//2, x) == :(2*log(x)^(1/2))
@test integrate((-3+x)^-1*(5+2x), x) == :(2x+11*log(3+-1x))
@test integrate(exp(x+exp(x)), x) == :(exp(exp(x)))
@test integrate(cos(x)^2*sin(x)^2, x) == :(1//8*x+-1//4*cos(x)^3*sin(x)+1//8*cos(x)*sin(x))
@test integrate((cos(x)+sin(x))^-1*(-1*cos(x)+sin(x)), x) == :(-1*log(cos(x)+sin(x)))
@test integrate(x*(1+-1*x^2)^-1//2, x) == :(-1*(1+-1*x^2)^(1/2))
@test integrate(x^3*log(x), x) == :(-1//16*x^4+1//4*x^4*log(x))
@test integrate((-2+x)^(1/2)*(2+x)^-1, x) == :(-4*arctan((1/2)*(-2+x)^(1/2))+2*(-2+x)^(1/2))
@test integrate(x*(2+x)^-2, x) == :(2*(2+x)^-1+log(2+x))
@test integrate(log(1+x^2), x) == :(-2x+2*arctan(x)+x*log(1+x^2))
@test integrate(x^-1*(1+log(x))^(1/2)*log(x)^-1, x) == :(-2*arctanh((1+log(x))^(1/2))+2*(1+log(x))^(1/2))
@test integrate((1+x^(1/2))^8, x) == :(-2//9*(1+x^(1/2))^9+1//5*(1+x^(1/2))^10)
@test integrate(sec(x)^4*tan(x)^3, x) == :(-1//4*sec(x)^4+1//6*sec(x)^6)
@test integrate(x*(2+x^2+-2x)^-1, x) == :((1/2)*log(2+x^2+-2x)+-1*arctan(1+-1x))
@test integrate(x*arcsin(x), x) == :(-1//4*arcsin(x)+(1/2)*x^2*arcsin(x)+1//4*x*(1+-1*x^2)^(1/2))
@test integrate(x^-1*(9+-1*x^2)^(1/2), x) == :((9+-1*x^2)^(1/2)+-3*arctanh(1//3*(9+-1*x^2)^(1/2)))
@test integrate(x*(2+x^2+3x)^-1, x) == :(-1*log(1+x)+2*log(2+x))
@test integrate(x^2*cosh(x), x) == :(2*sinh(x)+x^2*sinh(x)+-2*x*cosh(x))
@test integrate((x^4+2*x^2+4x)^-1*(1+x+x^3), x) == :(1//4*log(x^4+2*x^2+4x))
@test integrate((1+sin(x)^2)^-1*cos(x), x) == :(arctan(sin(x)))
@test integrate(cos(x^(1/2)), x) == :(2*cos(x^(1/2))+2*x^(1/2)*sin(x^(1/2)))
@test integrate(sin(pi*x), x) == :(-1*pi^-1*cos(pi*x))
@test integrate((1+exp(x))^-1*exp(2x), x) == :(-1*log(1+exp(x))+exp(x))
@test integrate(cos(5x)*exp(3x), x) == :(3//34*cos(5x)*exp(3x)+5//34*exp(3x)*sin(5x))
@test integrate(cos(3x)*cos(5x), x) == :(1//4*sin(2x)+1//16*sin(8x))
@test integrate((1+x+x^2+x^3)^-1, x) == :((1/2)*arctan(x)+(1/2)*log(1+x)+-1//4*log(1+x^2))
@test integrate(x^2*log(1+x), x) == :(-1//3*x+-1//9*x^3+1//3*log(1+x)+1//6*x^2+1//3*x^3*log(1+x))
@test integrate(x^5*exp(-1*x^3), x) == :(-1//3*exp(-1*x^3)+-1//3*x^3*exp(-1*x^3))
@test integrate(tan(4x)^2, x) == :(-1x+1//4*tan(4x))
@test integrate((-5+9*x^2+12x)^-1//2, x) == :(1//3*arctanh((-5+9*x^2+12x)^-1//2*(2+3x)))
@test integrate(x^2*arctan(x), x) == :(-1//6*x^2+1//6*log(1+x^2)+1//3*x^3*arctan(x))
@test integrate(x^-1//3*(1+-1*x^(1/2)), x) == :(-6//7*x^7//6+3//2*x^2//3)
@test integrate((-1*exp(-1x)+exp(x))^-1, x) == :(-1*arctanh(exp(x)))
@test integrate(x*(10+x^4+2*x^2)^-1, x) == :(1//6*arctan(1//3+1//3*x^2))
@test integrate((x+x^-1//3)^-1, x) == :(3//4*log(1+x^4//3))
@test integrate(cos(x)^4*sin(x)^2, x) == :(1//16*x+-1//6*cos(x)^5*sin(x)+1//16*cos(x)*sin(x)+1//24*cos(x)^3*sin(x))
@test integrate((5+-1*x^2+-4x)^-1//2, x) == :(-1*arcsin(-2//3+-1//3*x))
@test integrate(x*(1+(1+-1*x^2)^(1/2)+-1*x^2)^-1, x) == :(-1*log(1+(1+-1*x^2)^(1/2)))
@test integrate((1+cos(x))*csc(x), x) == :(log(1+-1*cos(x)))
@test integrate((-1+exp(2x))^-1*exp(x), x) == :(-1*arctanh(exp(x)))
@test integrate((-8+x^3)^-1, x) == :(-1//24*log(4+x^2+2x)+1//12*log(2+-1x)+-1//12*3^(1/2)*arctan(1//3*3^(1/2)*(1+x)))
@test integrate(x^5*cosh(x), x) == :(-120*cosh(x)+x^5*sinh(x)+-60*x^2*cosh(x)+-5*x^4*cosh(x)+20*x^3*sinh(x)+120*x*sinh(x))
@test integrate(cos(x)^-1*sin(x)^-1*log(tan(x)), x) == :((1/2)*log(tan(x))^2)
@test integrate(x^2+x^3+-2x, x) == :(-1*x^2+1//3*x^3+1//4*x^4)
@test integrate((1+-1*exp(x))^-1*(1+exp(x)), x) == :(x+-2*log(1+-1*exp(x)))
@test integrate(x*(1+x^2)^-1*(4+x^2)^-1, x) == :(-1//6*log(4+x^2)+1//6*log(1+x^2))
@test integrate((4+-5*sin(x))^-1, x) == :(-1//3*log(-2*sin((1/2)*x)+cos((1/2)*x))+1//3*log(-1*sin((1/2)*x)+2*cos((1/2)*x)))
@test integrate(x*(c+x)^1//3, x) == :(3//7*(c+x)^7//3+-3//4*c*(c+x)^4//3)
@test integrate(exp(x^1//3), x) == :(6*exp(x^1//3)+-6*x^1//3*exp(x^1//3)+3*x^2//3*exp(x^1//3))
@test integrate((4+x+(1+x)^(1/2))^-1, x) == :(-2//11*11^(1/2)*arctan(1//11*11^(1/2)*(1+2*(1+x)^(1/2)))+log(4+x+(1+x)^(1/2)))
@test integrate((x^3+-1*x^2)^-1*(1+x^3), x) == :(x+x^-1+-1*log(x)+2*log(1+-1x))
@test integrate((-3+x^2+4x)*sin(2x), x) == :(7//4*cos(2x)+(1/2)*x*sin(2x)+-2*x*cos(2x)+-1//2*x^2*cos(2x)+sin(2x))
@test integrate(cos(cos(x))*sin(x), x) == :(-1*sin(cos(x)))
@test integrate((16+-1*x^2)^-1//2, x) == :(arcsin(1//4*x))
@test integrate(x^3*(1+x)^-10, x) == :(-3//8*(1+x)^-8+-1//6*(1+x)^-6+1//9*(1+x)^-9+3//7*(1+x)^-7)
@test integrate(cot(2x)^3*csc(2x)^3, x) == :(-1//10*csc(2x)^5+1//6*csc(2x)^3)
@test integrate((x+sin(x))^2, x) == :((1/2)*x+2*sin(x)+1//3*x^3+-2*x*cos(x)+-1//2*cos(x)*sin(x))
@test integrate((1+x^2)^-1*exp(arctan(x)), x) == :(exp(arctan(x)))
@test integrate(x^-1*(1+x^4)^-1, x) == :(-1//4*log(1+x^4)+log(x))
@test integrate(t^3*exp(-2t), t) == :(-3//8*exp(-2t)+-3//4*t*exp(-2t)+-3//4*t^2*exp(-2t)+-1//2*t^3*exp(-2t))
@test integrate(t^(1/2)*(1+t^1//3)^-1, t) == :(-6*t^1//6+2*t^(1/2)+6*arctan(t^1//6)+-6//5*t^5//6+6//7*t^7//6)
@test integrate(sin(x)*sin(2x)*sin(3x), x) == :(-1//8*cos(2x)+-1//16*cos(4x)+1//24*cos(6x))
@test integrate(log((1/2)*x), x) == :(-1x+x*log((1/2)*x))
@test integrate(((1+-1x)^-1*(1+x))^(1/2), x) == :(2*arctan(((1+-1x)^-1*(1+x))^(1/2))+-1*((1+-1x)^-1*(1+x))^(1/2)*(1+-1x))
@test integrate(x*(-1+x^2)^-1//2*log(x), x) == :(-1*(-1+x^2)^(1/2)+(-1+x^2)^(1/2)*log(x)+arctan((-1+x^2)^(1/2)))
@test integrate((a^2+x^2)^-1*(a+x), x) == :((1/2)*log(a^2+x^2)+arctan(x*a^-1))
@test integrate((1+x+-1*x^2)^(1/2), x) == :(-5//8*arcsin(1//5*5^(1/2)*(1+-2x))+-1//4*(1+x+-1*x^2)^(1/2)*(1+-2x))
@test integrate(x^4*(16+x^10)^-1, x) == :(1//20*arctan(1//4*x^5))
@test integrate((2+x+x^2)^-1*(2+x), x) == :((1/2)*log(2+x+x^2)+3//7*7^(1/2)*arctan(1//7*7^(1/2)*(1+2x)))
@test integrate(x*sec(x)*tan(x), x) == :(-1*arctanh(sin(x))+x*sec(x))
@test integrate(x*(x^4+-1*a^4)^-1, x) == :(-1//2*a^-2*arctanh(a^-2*x^2))
@test integrate((x^(1/2)+(1+x)^(1/2))^-1, x) == :(-2//3*x^3//2+2//3*(1+x)^3//2)
@test integrate((1+-1*exp(-1x)+2*exp(x))^-1, x) == :(-1//3*log(1+exp(x))+1//3*log(1+-2*exp(x)))
@test integrate(x^-1//2*arctan(x^(1/2)), x) == :(-1*log(1+x)+2*x^(1/2)*arctan(x^(1/2)))
@test integrate(x^-2*log(1+x), x) == :(-1*log(1+x)+-1*x^-1*log(1+x)+log(x))
@test integrate((-1*exp(x)+exp(3x))^-1, x) == :(-1*arctanh(exp(x))+exp(-1x))
@test integrate((1+-1*cos(x)^2)^-1*(1+cos(x)^2), x) == :(-1x+-2*cot(x))
@test integrate(x^-1*(-25+2x)^-1//2, x) == :(2//5*arctan(1//5*(-25+2x)^(1/2)))
@test integrate((9+-1*cos(x)^4)^-1//2*sin(2x), x) == :(-1*arcsin(1//3*cos(x)^2))
@test integrate(x^2*(5+-4*x^2)^-1//2, x) == :(5//16*arcsin(2//5*x*5^(1/2))+-1//8*x*(5+-4*x^2)^(1/2))
@test integrate(x^3*sin(x), x) == :(-6*sin(x)+-1*x^3*cos(x)+3*x^2*sin(x)+6*x*cos(x))
@test integrate(x*(4+x^2+2x)^(1/2), x) == :(-3//2*arcsinh(1//3*3^(1/2)*(1+x))+1//3*(4+x^2+2x)^3//2+-1//2*(4+x^2+2x)^(1/2)*(1+x))
@test integrate(x*(5+x^2)^8, x) == :(1//18*(5+x^2)^9)
@test integrate(cos(x)^2*sin(x)^5, x) == :(-1//3*cos(x)^3+-1//7*cos(x)^7+2//5*cos(x)^5)
@test integrate(cos(4x)*exp(-3x), x) == :(-3//25*cos(4x)*exp(-3x)+4//25*exp(-3x)*sin(4x))
@test integrate(csc((1/2)*x)^3, x) == :(-1*arctanh(cos((1/2)*x))+-1*cot((1/2)*x)*csc((1/2)*x))
@test integrate(x^-2*(-1+9*x^2)^(1/2), x) == :(3*arctanh(3*x*(-1+9*x^2)^-1//2)+-1*x^-1*(-1+9*x^2)^(1/2))
@test integrate(x^-1*(4+-3*x^2)^(1/2), x) == :((4+-3*x^2)^(1/2)+-2*arctanh((1/2)*(4+-3*x^2)^(1/2)))
@test integrate(x^2*exp(3x), x) == :(2//27*exp(3x)+-2//9*x*exp(3x)+1//3*x^2*exp(3x))
@test integrate((1+sin(x))^-1//2*cos(x)*sin(x), x) == :(-2*(1+sin(x))^(1/2)+2//3*(1+sin(x))^3//2)
@test integrate(x*arcsin(x^2), x) == :((1/2)*(1+-1*x^4)^(1/2)+(1/2)*x^2*arcsin(x^2))
@test integrate(x^3*arcsin(x^2), x) == :(-1//8*arcsin(x^2)+1//4*x^4*arcsin(x^2)+1//8*x^2*(1+-1*x^4)^(1/2))
@test integrate(exp(x)*sech(exp(x)), x) == :(arctan(sinh(exp(x))))
@test integrate(x^2*cos(3x), x) == :(-2//27*sin(3x)+1//3*x^2*sin(3x)+2//9*x*cos(3x))
@test integrate((5+-1*x^2+-4x)^(1/2), x) == :(-9//2*arcsin(-2//3+-1//3*x)+(1/2)*(5+-1*x^2+-4x)^(1/2)*(2+x))
@test integrate(x^5*(2^(1/2)+x^2)^-1, x) == :(1//4*x^4+-1//2*2^(1/2)*x^2+log(2^(1/2)+x^2))
@test integrate(sec(x)^5, x) == :(3//8*arctanh(sin(x))+1//4*sec(x)^3*tan(x)+3//8*sec(x)*tan(x))
@test integrate(sin(2x)^6, x) == :(5//16*x+-5//32*cos(2x)*sin(2x)+-5//48*sin(2x)^3*cos(2x)+-1//12*sin(2x)^5*cos(2x))
@test integrate(sin(x)^2*cos(x)*log(sin(x)), x) == :(-1//9*sin(x)^3+1//3*sin(x)^3*log(sin(x)))
@test integrate((1+2*exp(x))^-1*exp(-1x), x) == :(-1*exp(-1x)+-2x+2*log(1+2*exp(x)))
@test integrate((2+3*cos(x))^(1/2)*tan(x), x) == :(-2*(2+3*cos(x))^(1/2)+2*2^(1/2)*arctanh((1/2)*2^(1/2)*(2+3*cos(x))^(1/2)))
@test integrate(x*(x^2+-4x)^-1//2, x) == :((x^2+-4x)^(1/2)+4*arctanh(x*(x^2+-4x)^-1//2))
@test integrate(cos(x)^5, x) == :(-2//3*sin(x)^3+1//5*sin(x)^5+sin(x))
@test integrate(x^4*exp(-1x), x) == :(-24*exp(-1x)+-1*x^4*exp(-1x)+-24*x*exp(-1x)+-12*x^2*exp(-1x)+-4*x^3*exp(-1x))
@test integrate(x^4*(-2+x^10)^-1//2, x) == :(1//5*arctanh(x^5*(-2+x^10)^-1//2))
@test integrate(cos(4+3x)*exp(x), x) == :(1//10*cos(4+3x)*exp(x)+3//10*exp(x)*sin(4+3x))
@test integrate(x^2*arctan(x), x) == :(-1//6*x^2+1//6*log(1+x^2)+1//3*x^3*arctan(x))
@test integrate((-1+exp(2x))^(1/2), x) == :((-1+exp(2x))^(1/2)+-1*arctan((-1+exp(2x))^(1/2)))
@test integrate(exp(sin(x))*sin(2x), x) == :(-2*exp(sin(x))+2*exp(sin(x))*sin(x))
@test integrate(x^2*(5+-1*x^2)^(1/2), x) == :(25//8*arcsin(1//5*x*5^(1/2))+-5//8*x*(5+-1*x^2)^(1/2)+1//4*x^3*(5+-1*x^2)^(1/2))
@test integrate(x^2*(1+x^3)^4, x) == :(1//15*(1+x^3)^5)
@test integrate(cos(x)^3*sin(x)^3, x) == :(-1//6*sin(x)^6+1//4*sin(x)^4)
@test integrate(sec(x)^4*tan(x)^2, x) == :(1//3*tan(x)^3+1//5*tan(x)^5)
@test integrate(x*(1+2x)^(1/2), x) == :(-1//6*(1+2x)^3//2+1//10*(1+2x)^5//2)
@test integrate(sin(x)^4, x) == :(3//8*x+-3//8*cos(x)*sin(x)+-1//4*sin(x)^3*cos(x))
@test integrate(tan(x)^3, x) == :((1/2)*tan(x)^2+log(cos(x)))
@test integrate(x^5*(1+x^2)^(1/2), x) == :(-2//5*(1+x^2)^5//2+1//3*(1+x^2)^3//2+1//7*(1+x^2)^7//2)

test/0 Independent test suites/Stewart Problems.jl


using NLSolvers, StaticArrays, Test
@testset "mixed optimization problems" begin
    function theta(x)
        if x[1] > 0
            return atan(x[2] / x[1]) / (2.0 * pi)
        else
            return (pi + atan(x[2] / x[1])) / (2.0 * pi)
        end
    end
    f(x) = 100.0 * ((x[3] - 10.0 * theta(x))^2 + (sqrt(x[1]^2 + x[2]^2) - 1.0)^2) + x[3]^2

    function f∇f!(x, ∇f)
        if !(∇f == nothing)
            if (x[1]^2 + x[2]^2 == 0)
                dtdx1 = 0
                dtdx2 = 0
            else
                dtdx1 = -x[2] / (2 * pi * (x[1]^2 + x[2]^2))
                dtdx2 = x[1] / (2 * pi * (x[1]^2 + x[2]^2))
            end
            ∇f[1] =
                -2000.0 * (x[3] - 10.0 * theta(x)) * dtdx1 +
                200.0 * (sqrt(x[1]^2 + x[2]^2) - 1) * x[1] / sqrt(x[1]^2 + x[2]^2)
            ∇f[2] =
                -2000.0 * (x[3] - 10.0 * theta(x)) * dtdx2 +
                200.0 * (sqrt(x[1]^2 + x[2]^2) - 1) * x[2] / sqrt(x[1]^2 + x[2]^2)
            ∇f[3] = 200.0 * (x[3] - 10.0 * theta(x)) + 2.0 * x[3]
        end

        fx = f(x)
        objective_return(fx, ∇f)
    end

    function f∇f(x, ∇f)
        if !(∇f == nothing)
            ∇f = similar(x)
        end
        fx, ∇f = f∇f!(gx, x)
        objective_return(fx, ∇f)
    end
    function f∇fs(x, ∇f)
        if !(∇f == nothing)
            if (x[1]^2 + x[2]^2 == 0)
                dtdx1 = 0
                dtdx2 = 0
            else
                dtdx1 = -x[2] / (2 * pi * (x[1]^2 + x[2]^2))
                dtdx2 = x[1] / (2 * pi * (x[1]^2 + x[2]^2))
            end

            s1 =
                -2000.0 * (x[3] - 10.0 * theta(x)) * dtdx1 +
                200.0 * (sqrt(x[1]^2 + x[2]^2) - 1) * x[1] / sqrt(x[1]^2 + x[2]^2)
            s2 =
                -2000.0 * (x[3] - 10.0 * theta(x)) * dtdx2 +
                200.0 * (sqrt(x[1]^2 + x[2]^2) - 1) * x[2] / sqrt(x[1]^2 + x[2]^2)
            s3 = 200.0 * (x[3] - 10.0 * theta(x)) + 2.0 * x[3]
            ∇f = @SVector [s1, s2, s3]
        end
        objective_return(f(x), ∇f)
    end
    obj_inplace = OnceDiffed(f∇f!)
    obj_outofplace = OnceDiffed(f∇f)
    obj_static = OnceDiffed(f∇fs)

    x0 = [-1.0, 0.0, 0.0]
    xopt = [1.0, 0.0, 0.0]
    x0s = @SVector [-1.0, 0.0, 0.0]

    println("Starting  from: ", x0)
    println("Targeting     : ", xopt)

    res = minimize(obj_inplace, copy(x0), NelderMead(), MinOptions())
    print("NN  $(summary(NelderMead()))         ")
    @printf(
        "%2.2e  %2.2e %2.2e %d\n",
        norm(res.info.solution - xopt, Inf),
        norm(res.info.nm_obj, Inf),
        res.info.minimum,
        res.info.iter
    )
    res = minimize(obj_inplace, copy(x0), SimulatedAnnealing(), MinOptions())
    print("NN  $(summary(SimulatedAnnealing()))         ")
    @printf(
        "%2.2e  %2.2e %d\n",
        norm(res.info.solution - xopt, Inf),
        res.info.minimum,
        res.info.iter
    )
    res = minimize(obj_inplace, copy(x0), ConjugateGradient(update = HZ()), MinOptions())
    print("NN  $(summary(ConjugateGradient(update=HZ())))         ")
    @printf(
        "%2.2e  %2.2e %d\n",
        norm(res.info.solution - xopt, Inf),
        res.info.minimum,
        res.info.iter
    )
    res = minimize(obj_inplace, copy(x0), ConjugateGradient(update = CD()), MinOptions())
    print("NN  $(summary(ConjugateGradient(update=CD())))         ")
    @printf(
        "%2.2e  %2.2e %d\n",
        norm(res.info.solution - xopt, Inf),
        res.info.minimum,
        res.info.iter
    )
    res = minimize(obj_inplace, copy(x0), ConjugateGradient(update = HS()), MinOptions())
    print("NN  $(summary(ConjugateGradient(update=HS())))         ")
    @printf(
        "%2.2e  %2.2e %d\n",
        norm(res.info.solution - xopt, Inf),
        res.info.minimum,
        res.info.iter
    )
    res = minimize(obj_inplace, copy(x0), ConjugateGradient(update = FR()), MinOptions())
    print("NN  $(summary(ConjugateGradient(update=FR())))         ")
    @printf(
        "%2.2e  %2.2e %d\n",
        norm(res.info.solution - xopt, Inf),
        res.info.minimum,
        res.info.iter
    )
    res = minimize(obj_inplace, copy(x0), ConjugateGradient(update = PRP()), MinOptions())
    print("NN  $(summary(ConjugateGradient(update=PRP(;plus=false))))         ")
    @printf(
        "%2.2e  %2.2e %d\n",
        norm(res.info.solution - xopt, Inf),
        res.info.minimum,
        res.info.iter
    )
    res = minimize(
        obj_inplace,
        copy(x0),
        ConjugateGradient(update = PRP(plus = true)),
        MinOptions(),
    )
    print("NN  $(summary(ConjugateGradient(update=PRP(;plus=true))))         ")
    @printf(
        "%2.2e  %2.2e %d\n",
        norm(res.info.solution - xopt, Inf),
        res.info.minimum,
        res.info.iter
    )
    res = minimize(obj_inplace, copy(x0), ConjugateGradient(update = VPRP()), MinOptions())
    print("NN  $(summary(ConjugateGradient(update=VPRP())))         ")
    @printf(
        "%2.2e  %2.2e %d\n",
        norm(res.info.solution - xopt, Inf),
        res.info.minimum,
        res.info.iter
    )
    res = minimize(obj_inplace, copy(x0), ConjugateGradient(update = LS()), MinOptions())
    print("NN  $(summary(ConjugateGradient(update=LS())))         ")
    @printf(
        "%2.2e  %2.2e %d\n",
        norm(res.info.solution - xopt, Inf),
        res.info.minimum,
        res.info.iter
    )
    res = minimize(obj_inplace, copy(x0), ConjugateGradient(update = DY()), MinOptions())
    print("NN  $(summary(ConjugateGradient(update=DY())))         ")
    @printf(
        "%2.2e  %2.2e %d\n",
        norm(res.info.solution - xopt, Inf),
        res.info.minimum,
        res.info.iter
    )

    for _method in (GradientDescent, LBFGS, BFGS, DBFGS, DFP, SR1)
        # for _method in (GradientDescent, BFGS, DBFGS, DFP, SR1)
        methodtxt = summary(_method())
        for m in (Inverse(), Direct())
            mtxt = m isa Inverse ? "(inverse): " : "(direct):  "
            if _method == LBFGS && m isa Inverse
                res = minimize!(obj_inplace, copy(x0), LineSearch(_method(m)), MinOptions())
                print("NN! $_method    $mtxt")
                @printf(
                    "%2.2e  %2.2e %2.2e  %d\n",
                    norm(res.info.solution - xopt, Inf),
                    norm(res.info.∇fz, Inf),
                    res.info.minimum,
                    res.info.iter
                )
            elseif _method !== LBFGS
                res = minimize(obj_inplace, copy(x0), LineSearch(_method(m)), MinOptions())
                print("NN  $_method    $mtxt")
                @printf(
                    "%2.2e  %2.2e %2.2e  %d\n",
                    norm(res.info.solution - xopt, Inf),
                    norm(res.info.∇fz, Inf),
                    res.info.minimum,
                    res.info.iter
                )
                res = minimize!(obj_inplace, copy(x0), LineSearch(_method(m)), MinOptions())
                print("NN! $_method    $mtxt")
                @printf(
                    "%2.2e  %2.2e %2.2e  %d\n",
                    norm(res.info.solution - xopt, Inf),
                    norm(res.info.∇fz, Inf),
                    res.info.minimum,
                    res.info.iter
                )
                res = minimize(obj_static, x0s, LineSearch(_method(m)), MinOptions())
                print("NN  $_method(S) $mtxt")
                @printf(
                    "%2.2e  %2.2e %2.2e  %d\n",
                    norm(res.info.solution - xopt, Inf),
                    norm(res.info.∇fz, Inf),
                    res.info.minimum,
                    res.info.iter
                )
                if m isa Direct && !(_method == GradientDescent)
                    println("Trust region: NWI")
                    res = minimize!(
                        obj_inplace,
                        copy(x0),
                        TrustRegion(_method(Direct()), NWI()),
                        MinOptions(),
                    )
                    print("NN! $_method    $mtxt")
                    @printf(
                        "%2.2e  %2.2e %2.2e  %d\n",
                        norm(res.info.solution - xopt, Inf),
                        norm(res.info.∇fz, Inf),
                        res.info.minimum,
                        res.info.iter
                    )
                    println("Trust region: NTR")
                    res = minimize!(
                        obj_inplace,
                        copy(x0),
                        TrustRegion(_method(Direct()), NTR()),
                        MinOptions(),
                    )
                    print("NN! $_method    $mtxt")
                    @printf(
                        "%2.2e  %2.2e %2.2e  %d\n",
                        norm(res.info.solution - xopt, Inf),
                        norm(res.info.∇fz, Inf),
                        res.info.minimum,
                        res.info.iter
                    )
                end
            end
        end
    end
    println()

    res = minimize(obj_inplace, x0, LineSearch(BFGS(Inverse())), MinOptions())
    @test res.info.iter == 30
    @printf(
        "NN  BFGS    (inverse): %2.2e  %2.2e %2.2e %d\n",
        norm(res.info.solution - xopt, Inf),
        norm(res.info.∇fz, Inf),
        res.info.minimum,
        res.info.iter
    )
    res = minimize!(
        obj_inplace,
        copy(x0),
        LineSearch(BFGS(Inverse()), Backtracking()),
        MinOptions(),
    )
    @test res.info.iter == 30
    @printf(
        "NN! BFGS    (inverse): %2.2e  %2.2e %2.2e %d\n",
        norm(res.info.solution - xopt, Inf),
        norm(res.info.∇fz, Inf),
        res.info.minimum,
        res.info.iter
    )
    res = minimize!(
        obj_inplace,
        copy(x0),
        LineSearch(BFGS(Inverse()), Backtracking(interp = FFQuadInterp())),
        MinOptions(),
    )
    @test res.info.iter == 30
    @printf(
        "NN! BFGS    (inverse, quad): %2.2e %2.2e %2.2e %d\n",
        norm(res.info.solution - xopt, Inf),
        norm(res.info.∇fz, Inf),
        res.info.minimum,
        res.info.iter
    )
    res = minimize(obj_static, x0s, LineSearch(BFGS(Inverse())), MinOptions())
    @test res.info.iter == 30
    @printf(
        "NN  BFGS(S) (inverse): %2.2e %2.2e %2.2e %d\n",
        norm(res.info.solution - xopt, Inf),
        norm(res.info.∇fz, Inf),
        res.info.minimum,
        res.info.iter
    )
    res = minimize(
        obj_static,
        x0s,
        LineSearch(BFGS(Inverse()), Backtracking(interp = FFQuadInterp())),
        MinOptions(),
    )
    @test res.info.iter == 30
    @printf(
        "NN  BFGS(S) (inverse, quad): %2.2e %2.2e %2.2e %d\n",
        norm(res.info.solution - xopt, Inf),
        norm(res.info.∇fz, Inf),
        res.info.minimum,
        res.info.iter
    )

    x0 = rand(3)
    x0s = SVector{3}(x0)
    println("\nFrom a random point: ", x0)
    res = minimize(obj_inplace, copy(x0), NelderMead(), MinOptions())
    print("NN  $(summary(NelderMead()))         ")
    @printf(
        "%2.2e  %2.2e %2.2e %d\n",
        norm(res.info.solution - xopt, Inf),
        norm(res.info.nm_obj, Inf),
        res.info.minimum,
        res.info.iter
    )
    res = minimize(obj_inplace, copy(x0), SimulatedAnnealing(), MinOptions())
    print("NN  $(summary(SimulatedAnnealing()))         ")
    @printf(
        "%2.2e  %2.2e %d\n",
        norm(res.info.solution - xopt, Inf),
        res.info.minimum,
        res.info.iter
    )
    res = minimize(obj_inplace, copy(x0), ConjugateGradient(), MinOptions())
    print("NN  $(summary(ConjugateGradient()))         ")
    @printf(
        "%2.2e  %2.2e %d\n",
        norm(res.info.solution - xopt, Inf),
        res.info.minimum,
        res.info.iter
    )

    for _method in (GradientDescent, LBFGS, BFGS, DBFGS, DFP, SR1)
        methodtxt = summary(_method())
        for m in (Inverse(), Direct())
            mtxt = m isa Inverse ? "(inverse): " : "(direct):  "
            if _method == LBFGS && m isa Inverse
                res = minimize!(obj_inplace, copy(x0), LineSearch(_method(m)), MinOptions())
                print("NN! $_method    $mtxt")
                @printf(
                    "%2.2e  %2.2e %2.2e  %d\n",
                    norm(res.info.solution - xopt, Inf),
                    norm(res.info.∇fz, Inf),
                    res.info.minimum,
                    res.info.iter
                )
            elseif _method !== LBFGS
                res = minimize(obj_inplace, copy(x0), LineSearch(_method(m)), MinOptions())
                print("NN  $_method    $mtxt")
                @printf(
                    "%2.2e  %2.2e %2.2e %d\n",
                    norm(res.info.solution - xopt, Inf),
                    norm(res.info.∇fz, Inf),
                    res.info.minimum,
                    res.info.iter
                )
                res = minimize!(obj_inplace, copy(x0), LineSearch(_method(m)), MinOptions())
                print("NN! $_method    $mtxt")
                @printf(
                    "%2.2e  %2.2e %2.2e %d\n",
                    norm(res.info.solution - xopt, Inf),
                    norm(res.info.∇fz, Inf),
                    res.info.minimum,
                    res.info.iter
                )
                res = minimize(obj_static, x0s, LineSearch(_method(m)), MinOptions())
                print("NN  $_method(S) $mtxt")
                @printf(
                    "%2.2e  %2.2e %2.2e %d\n",
                    norm(res.info.solution - xopt, Inf),
                    norm(res.info.∇fz, Inf),
                    res.info.minimum,
                    res.info.iter
                )
                if m isa Direct && !(_method == GradientDescent)
                    println("Trust region: NWI")
                    res = minimize!(
                        obj_inplace,
                        copy(x0),
                        TrustRegion(_method(Direct()), NWI()),
                        MinOptions(),
                    )
                    print("NN! $_method    $mtxt")
                    @printf(
                        "%2.2e  %2.2e %2.2e %d\n",
                        norm(res.info.solution - xopt, Inf),
                        norm(res.info.∇fz, Inf),
                        res.info.minimum,
                        res.info.iter
                    )
                    println("Trust region: NTR")
                    res = minimize!(
                        obj_inplace,
                        copy(x0),
                        TrustRegion(_method(Direct()), NTR()),
                        MinOptions(),
                    )
                    print("NN! $_method    $mtxt")
                    @printf(
                        "%2.2e  %2.2e %2.2e %d\n",
                        norm(res.info.solution - xopt, Inf),
                        norm(res.info.∇fz, Inf),
                        res.info.minimum,
                        res.info.iter
                    )
                end
            end
        end
    end
    println()

    function himmelblau!(x, ∇f)
        if !(∇f == nothing)
            ∇f[1] =
                4.0 * x[1]^3 + 4.0 * x[1] * x[2] - 44.0 * x[1] + 2.0 * x[1] + 2.0 * x[2]^2 -
                14.0
            ∇f[2] =
                2.0 * x[1]^2 + 2.0 * x[2] - 22.0 + 4.0 * x[1] * x[2] + 4.0 * x[2]^3 -
                28.0 * x[2]
        end

        fx = (x[1]^2 + x[2] - 11)^2 + (x[1] + x[2]^2 - 7)^2
        objective_return(fx, ∇f)
    end

    function himmelblaus(x, ∇f)
        fx = (x[1]^2 + x[2] - 11)^2 + (x[1] + x[2]^2 - 7)^2
        if !(∇f == nothing)
            ∇f1 =
                4.0 * x[1]^3 + 4.0 * x[1] * x[2] - 44.0 * x[1] + 2.0 * x[1] + 2.0 * x[2]^2 -
                14.0
            ∇f2 =
                2.0 * x[1]^2 + 2.0 * x[2] - 22.0 + 4.0 * x[1] * x[2] + 4.0 * x[2]^3 -
                28.0 * x[2]
            ∇f = @SVector([∇f1, ∇f2])
        end
        objective_return(fx, ∇f)
    end

    function himmelblau(x, ∇f)
        g = ∇f == nothing ? ∇f : similar(x)

        return himmelblau!(x, g)
    end

    him_inplace = OnceDiffed(himmelblau!)
    him_static = OnceDiffed(himmelblaus)
    him_outofplace = OnceDiffed(himmelblau)

    println("\nHimmelblau function")
    x0 = [3.0, 1.0]
    x0s = SVector{2}(x0)
    minimizers =
        [[3.0, 2.0], [-2.805118, 3.131312], [-3.779310, -3.283186], [3.584428, -1.848126]]
    for _method in (GradientDescent, LBFGS, BFGS, DBFGS, DFP, SR1)
        # for _method in (GradientDescent, BFGS, DBFGS, DFP, SR1)
        methodtxt = summary(_method())
        for m in (Inverse(), Direct())
            mtxt = m isa Inverse ? "(inverse): " : "(direct):  "
            if _method == LBFGS && m isa Inverse
                res = minimize!(him_inplace, copy(x0), LineSearch(_method(m)), MinOptions())
                print("NN! $_method    $mtxt")
                @printf(
                    "%2.2e  %2.2e %2.2e  %d\n",
                    minimum([norm(res.info.solution - xopt, Inf) for xopt in minimizers]),
                    norm(res.info.∇fz, Inf),
                    res.info.minimum,
                    res.info.iter
                )
            elseif _method !== LBFGS
                res =
                    minimize(him_outofplace, copy(x0), LineSearch(_method(m)), MinOptions())
                print("NN  $_method    $mtxt")
                @printf(
                    "%2.2e  %2.2e %2.2e  %d\n",
                    minimum([norm(res.info.solution - xopt, Inf) for xopt in minimizers]),
                    norm(res.info.∇fz, Inf),
                    res.info.minimum,
                    res.info.iter
                )
                res = minimize!(him_inplace, copy(x0), LineSearch(_method(m)), MinOptions())
                print("NN! $_method    $mtxt")
                @printf(
                    "%2.2e  %2.2e %2.2e  %d\n",
                    minimum([norm(res.info.solution - xopt, Inf) for xopt in minimizers]),
                    norm(res.info.∇fz, Inf),
                    res.info.minimum,
                    res.info.iter
                )
                res = minimize(him_static, x0s, LineSearch(_method(m)), MinOptions())
                print("NN  $_method(S) $mtxt")
                @printf(
                    "%2.2e  %2.2e %2.2e  %d\n",
                    minimum([norm(res.info.solution - xopt, Inf) for xopt in minimizers]),
                    norm(res.info.∇fz, Inf),
                    res.info.minimum,
                    res.info.iter
                )
                if m isa Direct && !(_method == GradientDescent)
                    println("Trust region: NWI")
                    res = minimize!(
                        him_inplace,
                        copy(x0),
                        TrustRegion(_method(Direct()), NWI()),
                        MinOptions(),
                    )
                    print("NN! $_method    $mtxt")
                    @printf(
                        "%2.2e  %2.2e %2.2e  %d\n",
                        minimum([
                            norm(res.info.solution - xopt, Inf) for xopt in minimizers
                        ]),
                        norm(res.info.∇fz, Inf),
                        res.info.minimum,
                        res.info.iter
                    )
                    println("Trust region: NTR")
                    res = minimize!(
                        him_inplace,
                        copy(x0),
                        TrustRegion(_method(Direct()), NTR()),
                        MinOptions(),
                    )
                    print("NN! $_method    $mtxt")
                    @printf(
                        "%2.2e  %2.2e %2.2e  %d\n",
                        minimum([
                            norm(res.info.solution - xopt, Inf) for xopt in minimizers
                        ]),
                        norm(res.info.∇fz, Inf),
                        res.info.minimum,
                        res.info.iter
                    )
                end
            end
        end
    end
    println()
    println()
    xrand = rand(2)
    xrands = SVector{2}(xrand)
    println("\nFrom a random point: ", xrand)

    for _method in (GradientDescent, LBFGS, BFGS, DBFGS, SR1)
        # for _method in (GradientDescent, BFGS, DBFGS, DFP, SR1)
        methodtxt = summary(_method())
        for m in (Inverse(), Direct())
            mtxt = m isa Inverse ? "(inverse): " : "(direct):  "
            if _method == LBFGS && m isa Inverse
                res = minimize!(him_inplace, copy(x0), LineSearch(_method(m)), MinOptions())
                print("NN! $_method    $mtxt")
                @printf(
                    "%2.2e  %2.2e %2.2e  %d\n",
                    minimum([norm(res.info.solution - xopt, Inf) for xopt in minimizers]),
                    norm(res.info.∇fz, Inf),
                    res.info.minimum,
                    res.info.iter
                )
            elseif _method !== LBFGS
                res = minimize(
                    him_outofplace,
                    copy(xrand),
                    LineSearch(_method(m)),
                    MinOptions(),
                )
                print("NN  $_method    $mtxt")
                @printf(
                    "%2.2e  %2.2e %2.2e  %d\n",
                    minimum([norm(res.info.solution - xopt, Inf) for xopt in minimizers]),
                    norm(res.info.∇fz, Inf),
                    res.info.minimum,
                    res.info.iter
                )
                res = minimize!(
                    him_inplace,
                    copy(xrand),
                    LineSearch(_method(m)),
                    MinOptions(),
                )
                print("NN! $_method    $mtxt")
                @printf(
                    "%2.2e  %2.2e %2.2e  %d\n",
                    minimum([norm(res.info.solution - xopt, Inf) for xopt in minimizers]),
                    norm(res.info.∇fz, Inf),
                    res.info.minimum,
                    res.info.iter
                )
                res = minimize(him_static, xrands, LineSearch(_method(m)), MinOptions())
                print("NN  $_method(S) $mtxt")
                @printf(
                    "%2.2e  %2.2e %2.2e  %d\n",
                    minimum([norm(res.info.solution - xopt, Inf) for xopt in minimizers]),
                    norm(res.info.∇fz, Inf),
                    res.info.minimum,
                    res.info.iter
                )
                if m isa Direct && !(_method == GradientDescent)
                    println("Trust region: NWI")
                    res = minimize!(
                        him_inplace,
                        copy(xrand),
                        TrustRegion(_method(Direct()), NWI()),
                        MinOptions(),
                    )
                    print("NN! $_method    $mtxt")
                    @printf(
                        "%2.2e  %2.2e %2.2e  %d\n",
                        minimum([
                            norm(res.info.solution - xopt, Inf) for xopt in minimizers
                        ]),
                        norm(res.info.∇fz, Inf),
                        res.info.minimum,
                        res.info.iter
                    )
                    println("Trust region: NTR")
                    res = minimize!(
                        him_inplace,
                        copy(xrand),
                        TrustRegion(_method(Direct()), NTR()),
                        MinOptions(),
                    )
                    print("NN! $_method    $mtxt")
                    @printf(
                        "%2.2e  %2.2e %2.2e  %d\n",
                        minimum([
                            norm(res.info.solution - xopt, Inf) for xopt in minimizers
                        ]),
                        norm(res.info.∇fz, Inf),
                        res.info.minimum,
                        res.info.iter
                    )
                end
            end
        end
    end
    println()

end


using VLOptionsModeling

# create an emprt contract set for now -
contract_set = Set{VLAbstractAsset}()
assetSymbol = "XYZ"
strikePrice = 50.0
expirationDate = nothing
premimumValue = 0.0
numberOfContracts = 1
sense = :buy
contractMultiplier = 100.0
put_contract = VLPutOptionContract(assetSymbol, strikePrice, premimumValue; sense=sense, contractMultiplier=1.0)
push!(contract_set, put_contract)

# setup the lattice (Example 19.1/Fig. 19.3 Hull)
𝝙t = 0.0833 # years
μ = log(1.0084) * (1 / 𝝙t)
σ = (1 / √𝝙t) * log(1.1224)
numberOfLevels = 6
underlyingPrice = 50.0

# Create the lattice model -
latticeModel = VLBinomialLattice(μ, σ, 𝝙t, numberOfLevels)

# compute the price -
price_tree = binomial_price(contract_set, latticeModel, underlyingPrice) |> check


using Images
using FileIO

# load our julia function
include("julia.jl")

h = 2500
w = 5000
iter = 10000
m = Array{Float64, 3}(undef, 3, h, w)
m = fill!(m, 0.0)

# time measurements
print("starting...\n")
tStart=time()

# for every pixel
for y=1:h, x=1:w
    # translate numbers [1:w, 1:h] -> -2:2 + -1:1 im
    c = complex((x-w/2)/(h/2), (y-h/2)/(h/2))
    # call our julia function
    color = julia(c, iter)/convert(Float64, iter)

    # color adjust weights
    red_color, blue_color, green_color = 1.0, 1.0, 1.0
    if color < 0.01
	    red_color, blue_color, green_color = 1.0, 1.0, 1.0
    else
	    red_color = color * 0.80
	    blue_color = color * 0.80
	    green_color = color * 0.15
    end

     m[1, y,x]  = red_color
     m[2, y,x]  = blue_color
     m[3, y,x]  = green_color
end

tStop = time()
print("done. took ", tStop-tStart, " seconds\n");

img = colorview(RGB, m)
save("julia.jpg", img)


# Solving the baseline version of the model with estimated parameters
using AAABK2018

# parameters
λ =  1.131874350361868; ψ =  0.037175089899834; ν =  0.205622086535123
α =  0.925520992611854; ϕ =  0.216311889844990; θˡ=  1.390807772162332
θʰ=  1.751156379867318; θᴱ=  0.023600456924424; ε =  2.900000000000000
ρ =  0.020000000000000; γ =  0.500000000000000; γᴱ=  0.500000000000000
σ =  2.000000000000000; Lˢ=  0.165500000000000; ω =  1.0

p = Params(λ,ψ,ν, α, ϕ, θˡ, θʰ, θᴱ, ε, ρ, γ, γᴱ, σ, Lˢ, ω)

# initial guess for equilibirum objects
eqInit = [ 2.00, 0.55, 0.06,
		   0.72, 0.83, 1.73]

# solve
eq,res = solveBGP(p,eqInit);

# solve with 5% incumbent R&D subsidy
pIncSub = Params(p, (sⁱ = 0.05,))   # Params(pOld, modifiedParameters)
eqIncSub,resIncSub = solveBGP(pIncSub, eqInit);

# solve with 5% operation cost subsidy
pFixSub = Params(p, (sᶠ = 0.05,))
eqFixSub,resFixSub = solveBGP(pFixSub,eqInit);

# solve with 5% entrant R&D subsidy
pEntSub = Params(p, (sᴱ = 0.05,))
eqEntSub,resEntSub = solveBGP(pEntSub,eqInit);

#social planner problem
eqSoc,resSoc = solveSocialPlanner(p)

# optimal policy
# one-tool policies
optPolInc = policy_opt(p, sⁱ = true)
optPolFix = policy_opt(p, sᶠ = true)
optPolEnt = policy_opt(p, sᴱ = true)

# two-tool policy
optPolInc_Fix = policy_opt(p, sⁱ = true, sᶠ = true)


using JLD
using DataFrames
using Dates
using LinearAlgebra
using DifferentialEquations
using Distributions
using Random
using CSV
using Printf
using RCall
using CPUTime
using StatsPlots
include("utils.jl")
include("sir_ode.jl")
include("abc.jl")
include("abcMCMC.jl")
include("abcSMC.jl")

df_post = DataFrame(CSV.File("../data/covid19_bc_post_vaccine.csv"))

 """ Post-vaccine analysis """
 tot_days = size(df_post)[1]
 time_interval = 7

 y_post = hcat([df_post[:S],df_post[:I],df_post[:R]]...)[1:7:tot_days,:]
 y_post = y_post./1e4
 y_dim = size(y_post)[1]

 function data_generator_week(p)
     initial_state = y_post[1,:]; time_window=(1,Float64(tot_days)/time_interval)
     solve_ode(initial_state,time_window,p)
 end

 algo_parameters_abc = (prior = (Truncated(Normal(0.1,0.5),0,Inf),Truncated(Normal(0.2,0.2),0,Inf)),epsilon = 0.1,
 eta= identity_mapping, d= distanceFunction)
 output_abc=ABC(y_post', data_generator_week, algo_parameters_abc, 500)

 algo_parameter_mcmc = (prior = (Truncated(Normal(0.1,0.5),0,Inf),Truncated(Normal(0.2,0.2),0,Inf)),epsilon = 1, eta = identity_mapping,
                         d= compute_norm, proposal = proposal_Normal,
                         proposalRatio = proposalRatio_Normal,sd = (0.25,0.25),
                         thinning = 10000, burn_in=100, verbose=true)

 output_mcmc = ABC_MCMC(y_post', data_generator_week, algo_parameter_mcmc, 500)

 algo_parameters_smc = (prior = (Truncated(Normal(0.1,0.5),0,Inf),Truncated(Normal(0.2,0.2),0,Inf)), time_final=5, eps_list = [10, 5, 1, 0.5, 0.1],
 eta= identity_mapping, d= distanceFunction, kernel=proposal_Normal,
 kernel_density=proposal_Normal_density,
 sd=(0.5,0.5), resample_method=systematic_resample, verbose=true)
 output_smc = ABC_SMC(y_post', data_generator_week, algo_parameters_smc, 500)

 abc_solutions=zeros(3,y_dim,500)
 abc_mcmc_solutions=zeros(3,y_dim,500)
 abc_smc_solutions=zeros(3,y_dim,500)
 for i=1:500
     abc_solutions[:,:,i] = data_generator_week(output_abc[1][i,:])[1:3,:]
     abc_mcmc_solutions[:,:,i] = data_generator_week(output_mcmc[1][i,:])[1:3,:]
     abc_smc_solutions[:,:,i] = data_generator_week(output_smc[1][i,:])[1:3,:]
 end

 save("../results/abc_solution_500_post2.jld","abc_solutions",abc_solutions)
 save("../results/abc_mcmc_solution_500_post2.jld","abc_mcmc_solutions",abc_mcmc_solutions)
 save("../results/abc_smc_solution_500_post2.jld","abc_smc_solutions",abc_smc_solutions)

 alpha_level=1
 beta_plot = density(output_abc[1][:,1],alpha=alpha_level, label="ABC",  title="Predicted β Posterior", xlabel="β", ylabel="Density")
 density!(output_mcmc[1][:,1],alpha=alpha_level, label="ABC-MCMC")
 density!(output_smc[1][:,1],alpha=alpha_level, label="ABC-SMC")
 savefig(beta_plot,"../figs/beta_plot_post2.png")

 gamma_plot = density(output_abc[1][:,2],alpha=alpha_level, label="ABC",  title="Predicted γ Posterior", xlabel="γ", ylabel="Density")
 density!(output_mcmc[1][:,2],alpha=alpha_level, label="ABC-MCMC")
 density!(output_smc[1][:,2],alpha=alpha_level, label="ABC-SMC")
 savefig(gamma_plot,"../figs/gamma_plot_post2.png")

 # ess
ess_output = zeros(3)
ess_output_time = copy(ess_output)
ess_output[1]  = try rcopy(R"mean(mcmcse::ess($(output_abc[1])))") catch; 0 end
ess_output_time[1]  = ess_output[1] / output_abc[2]
ess_output[2]  = try rcopy(R"mean(mcmcse::ess($(output_mcmc[1])))") catch; 0 end
ess_output_time[2]  = ess_output[2] / output_mcmc[2]
ess_output[3]  = try rcopy(R"mean(mcmcse::ess($(output_smc[1])))") catch; 0 end
ess_output_time[3]  = ess_output[3] / output_smc[2]

save("../results/covid/ess_post_2.jld","ess_output",ess_output)
save("../results/covid/ess_cputime_post_2.jld","ess_output_time",ess_output_time)


# Estimation of finite mixture model using EM

immutable FiniteMixtureEM
	maxiter::Int
	tol::Float64
	display::Symbol
	alpha::Float64
end

function fmm_em(;
	maxiter::Integer=100,
	tol::Real=1.0e-6,
	display::Symbol=:none,
	alpha::Float64=1.0)

	FiniteMixtureEM(maxiter, tol, display, alpha)
end

immutable FiniteMixtureEMResults{C}
	mixture::Mixture{C}
	Q::Matrix{Float64}
	L::Matrix{Float64}
	niters::Int
	converged::Bool
	objective::Float64
end

function fit_fmm!{C<:Distribution}(estimator::Estimator{C},
	data, Q::Matrix{Float64}, alg::FiniteMixtureEM)

	# basic numbers

	n = nsamples(estimator, data)
	@check_argdims size(Q, 1) == n

	K = size(Q, 2)
	maxiter = alg.maxiter
	tol = alg.tol
	verbose = verbosity_level(alg.display)

	# prepare storage

	components = Array(C, K)
	L = Array(Float64, n, K)
	E = Array(Float64, n, K)
	pi = Array(Float64, K)
	lpi = Array(Float64, K)
	qent = Array(Float64, n)
	objv = NaN

	# main loop

	t = 0
	converged = false

	if verbose >= VERBOSE_PROC
		@printf("%6s    %12s     %12s\n", "iter", "objective", "obj.change")
		println("------------------------------------------")
	end

	while !converged && t < maxiter
		t = t + 1

		# M-step
		sum!(pi, Q, 1)
		multiply!(pi, inv(n))
		for k in 1 : K
			lpi[k] = log(pi[k])
		end

		for k in 1 : K
			comp::C = estimate(estimator, data, refcolumn(Q, k))
			components[k] = comp
			logpdf!(refcolumn(L, k), comp, data)
		end

		# E-step
		vbroadcast!(Add(), E, L, lpi, 2)
		softmax!(Q, E, 2)

		# evaluate objective
		t_lpi = sum(Q * lpi)
		t_llik = dot(L, Q)
		t_qent = sum(entropy!(qent, Q, 2))
		t_lpri = 0.
		for k in 1 : K
			t_lpri += prior_score(estimator, components[k])
		end

		objv_pre = objv
		objv = t_lpi + t_llik + t_qent + t_lpri

		# decide convergence
		converged = t > 1 && abs(objv - objv_pre) < tol

		if verbose >= VERBOSE_ITER
			@printf("%6d    %12.4e     %12.4e\n", t, objv, objv - objv_pre)
		end
	end

	if verbose >= VERBOSE_PROC
		if converged
			println("EM procedure converged with $t iterations.")
		else
			println("EM procedure terminated after $t iterations without convergence.")
		end
	end

	mixture = Mixture(components, pi)
	return FiniteMixtureEMResults{C}(mixture, Q, L, t, converged, objv)
end

function fit_fmm!{C<:Distribution}(dty::Type{C}, data, Q::Matrix{Float64}, alg::FiniteMixtureEM)
	fit_fmm!(MLEstimator(C), data, Q, alg)
end

function fit_fmm{C<:Distribution}(dty::Type{C}, data, K::Int, alg::FiniteMixtureEM)
	n = nsamples(C, data)
	fit_fmm!(MLEstimator(C), data, qmatrix(n, K), alg)
end


# # Lorenz 63
#
#
# Data assimilation are numerical methods used in geosciences to
# mix the information of observations (noted as ``y``) and a dynamical
# model (noted as `f`) in order to estimate the true/hidden state of
# the system (noted as `x`) at every time step `k`. Usually, they are
# related following a nonlinear state-space model:
#
# ```math
# \begin{aligned}
# x_k & = f(x_{k-1}) + \eta_k \\
# y_k & = H x_k + \epsilon_k
# \end{aligned}
# ```
#
# with ``\eta`` and ``\epsilon`` some independant white
# Gaussian noises respectively respresenting the model forecast error
# and the error of observation.

# In classical data assimilation,
# we require multiple runs of an explicit dynamical model ``f`` with
# possible severe limitations including the computational cost, the
# lack of consistency of the model with respect to the observed data
# as well as modeling uncertainties. Here, an alternative strategy
# is explored by developing a fully data-driven assimilation. No
# explicit knowledge of the dynamical model is required. Only a
# representative catalog of trajectories of the system is assumed to
# be available. Based on this catalog, the Analog Data Assimilation
# (AnDA) is introduced by combining machine learning with the analog
# method (or nearest neighbor search) and stochastic assimilation
# techniques including Ensemble Kalman Filter and Smoother (AnEnKF,
# AnEnKS) and Particle Filter (AnPF). We test the accuracy of the technic
# on different chaotic dynamical models, the Lorenz-63 and Lorenz-96
# systems.  # # This Julia program is derived from the Python library
# is attached to the following publication: # Lguensat, R., Tandeo,
# P., Ailliot, P., Pulido, M., & Fablet, R. (2017). The Analog Data
# Assimilation. *Monthly Weather Review*, 145(10), 4093-4107.  # If
# you use this library, please do not forget to cite this work.

using Plots, DifferentialEquations, AnalogDataAssimilation

# To begin, as dynamical model ``f``, we use the Lorenz-63 chaotic
# system. First, we generate simulated trajectories from this dynamical
# model and store them into the catalog. Then, we use this catalog
# to emulate the dynamical model and we apply the analog data
# assimilation. Finally, we compare the results of this data-driven
# approach to the classical data assimilation (using the true Lorenz-63
# equations as dynamical model.

# ## Generate simulated data

σ = 10.0
ρ = 28.0
β = 8.0 / 3

dt_integration = 0.01
dt_states = 1
dt_obs = 8
parameters = [σ, ρ, β]
var_obs = [1]
nb_loop_train = 100
nb_loop_test = 10
sigma2_catalog = 0.0
sigma2_obs = 2.0

ssm = StateSpaceModel(
    lorenz63,
    dt_integration,
    dt_states,
    dt_obs,
    parameters,
    var_obs,
    nb_loop_train,
    nb_loop_test,
    sigma2_catalog,
    sigma2_obs,
)

# - compute ``u_0`` to be in the attractor space

u0 = [8.0; 0.0; 30.0]
tspan = (0.0, 5.0)
prob = ODEProblem(ssm.model, u0, tspan, parameters)
u0 = last(solve(prob, save_everystep = false))

xt, yo, catalog = generate_data(ssm, u0);

plot(xt.t, vcat(xt.u'...)[:, 1])
scatter!(yo.t, vcat(yo.u'...)[:, 1]; markersize = 2)

# ## Classical data assimilation

regression = :local_linear
sampling = :gaussian
k, np = 100, 500

DA = DataAssimilation(ssm, xt)
x̂_classical = forecast(DA, yo, AnEnKS(np), progress = false)
@time RMSE(xt, x̂_classical)

# ## Analog data assimilation

f = AnalogForecasting(k, xt, catalog; regression = regression, sampling = sampling)
DA = DataAssimilation(f, xt, ssm.sigma2_obs)
@time x̂_analog = forecast(DA, yo, AnEnKS(np), progress = false)
println(RMSE(xt, x̂_analog))

# ## Comparison between classical and analog data assimilation

plot(xt.t, xt[1], label = "true state")
plot!(xt.t, x̂_classical[1], label = "classical")
plot!(xt.t, x̂_analog[1], label = "analog")
scatter!(yo.t, yo[1]; markersize = 2, label = "observations")

# The results show that performances of the data-driven analog data
# assimilation are closed to those of the model-driven data assimilation.
# The error can be reduced by augmenting the size of the catalog
# "nb_loop_train".

# ## Remark
#
# Note that for all the previous experiments, we use the robust
# Ensemble Kalman Smoother (EnKS) with the increment or local linear
# regressions and the Gaussian sampling. If you want to have realistic
# state estimations, we preconize the use of the Particle Filter
# `PF` with the locally constant regression (regression
# = :locally_constant) and the multinomial sampler (sampling =
# :multinomial) with a large number of particles `np`. For more
# details about the different options, see the attached publication:
# Lguensat, R., Tandeo, P., Ailliot, P., Pulido, M., & Fablet, R.
# (2017). The Analog Data Assimilation. *Monthly Weather Review*,
# 145(10), 4093-4107.


"""
$(TYPEDEF)

A spatial inertia, or inertia matrix, represents the mass distribution of a
rigid body.

A spatial inertia expressed in frame ``i`` is defined as:
```math
I^i =
\\int_{B}\\rho\\left(x\\right)\\left[\\begin{array}{cc}
\\hat{p}^{T}\\left(x\\right)\\hat{p}\\left(x\\right) & \\hat{p}\\left(x\\right)\\\\
\\hat{p}^{T}\\left(x\\right) & I
\\end{array}\\right]dx=\\left[\\begin{array}{cc}
J & \\hat{c}\\\\
\\hat{c}^{T} & mI
\\end{array}\\right]
```
where ``\\rho(x)`` is the density of point ``x``, and ``p(x)`` are the coordinates
of point ``x`` expressed in frame ``i``.
``J`` is the mass moment of inertia, ``m`` is the total mass, and ``c`` is the
'cross part', center of mass position scaled by ``m``.

!!! warning

    The `moment` field of a `SpatialInertia` is the moment of inertia **about the origin of its `frame`**,
    not about the center of mass.
"""
struct SpatialInertia{T}
    frame::CartesianFrame3D
    moment::SMatrix{3, 3, T, 9}
    cross_part::SVector{3, T} # mass times center of mass
    mass::T

    @inline function SpatialInertia{T}(frame::CartesianFrame3D, moment::AbstractMatrix, cross_part::AbstractVector, mass) where T
        new{T}(frame, moment, cross_part, mass)
    end
end

"""
$(SIGNATURES)

Construct a `SpatialInertia` by specifying:

* `frame`: the frame in which the spatial inertia is expressed.
* `moment`: the moment of inertia expressed in `frame` (i.e., in `frame`'s axes and about the origin of `frame`).
* `cross_part`: the center of mass expressed in `frame`, multiplied by the mass.
* `mass`: the total mass.

For more convenient construction of `SpatialInertia`s, consider using the keyword argument constructor instead.
"""
@inline function SpatialInertia(frame::CartesianFrame3D, moment::AbstractMatrix{T1}, cross_part::AbstractVector{T2}, mass::T3) where {T1, T2, T3}
    SpatialInertia{promote_type(T1, T2, T3)}(frame, moment, cross_part, mass)
end

"""
$(SIGNATURES)

Construct a `SpatialInertia` by specifying:

* `frame`: the frame in which the spatial inertia is expressed.
* one of:
  * `moment`: the moment of inertia expressed in `frame` (i.e., about the origin of `frame` and in `frame`'s axes).
  * `moment_about_com`: the moment of inertia about the center of mass, in `frame`'s axes.
* `com`: the center of mass expressed in `frame`.
* `mass`: the total mass.

The `com` and `mass` keyword arguments are required, as well as exactly one of `moment` and `moment_about_com`
"""
@inline function SpatialInertia(frame::CartesianFrame3D;
        moment::Union{AbstractMatrix, Nothing}=nothing,
        moment_about_com::Union{AbstractMatrix, Nothing}=nothing,
        com::AbstractVector,
        mass)
    if !((moment isa AbstractMatrix) ⊻ (moment_about_com isa AbstractMatrix))
        throw(ArgumentError("Exactly one of `moment` or `moment_about_com` is required."))
    end
    _com = SVector{3}(com)
    if moment !== nothing
        _moment = moment
    else
        _moment = SMatrix{3, 3}(moment_about_com)
        _moment -= mass * hat_squared(_com) # parallel axis theorem
    end
    SpatialInertia(frame, _moment, mass * _com, mass)
end

# SpatialInertia-specific functions
Base.eltype(::Type{SpatialInertia{T}}) where {T} = T

# Construct/convert given another SpatialInertia
SpatialInertia{T}(inertia::SpatialInertia{T}) where {T} = inertia
@inline function SpatialInertia{T}(inertia::SpatialInertia) where {T}
    SpatialInertia{T}(inertia.frame,
        SMatrix{3, 3, T}(inertia.moment),
        SVector{3, T}(inertia.cross_part),
        T(inertia.mass))
end
@inline Base.convert(::Type{S}, inertia::SpatialInertia) where {S<:SpatialInertia} = S(inertia)

# Construct/convert to SMatrix
function StaticArrays.SMatrix{6, 6, T, 36}(inertia::SpatialInertia) where {T}
    J = SMatrix{3, 3, T}(inertia.moment)
    C = hat(SVector{3, T}(inertia.cross_part))
    m = T(inertia.mass)
    [[J C]; [C' SMatrix{3, 3}(m * I)]]
end
StaticArrays.SMatrix{6, 6, T}(inertia::SpatialInertia) where {T} = SMatrix{6, 6, T, 36}(inertia)
StaticArrays.SMatrix{6, 6}(inertia::SpatialInertia{T}) where {T} = SMatrix{6, 6, T}(inertia)
StaticArrays.SMatrix(inertia::SpatialInertia) = SMatrix{6, 6}(inertia)
StaticArrays.SArray(inertia::SpatialInertia) = SMatrix(inertia)
Base.convert(::Type{A}, inertia::SpatialInertia) where {A<:SArray} = A(inertia)

function Base.convert(::Type{Matrix}, inertia::SpatialInertia) where {T<:Matrix}
    Base.depwarn("This convert method is deprecated. Please use `$T(SMatrix(inertia))` instead or
    reconsider whether conversion to Matrix is necessary.", :convert)
    T(SMatrix(inertia))
end

function Base.Array(inertia::SpatialInertia)
    Base.depwarn("This Array constructor is deprecated. Please use `Matrix(SMatrix(inertia))` instead or
    reconsider whether conversion to Matrix is necessary.", :Array)
    Matrix(SMatrix(inertia))
end

"""
$(SIGNATURES)

Return the center of mass of the `SpatialInertia` as a [`Point3D`](@ref).
"""
center_of_mass(inertia::SpatialInertia) = Point3D(inertia.frame, inertia.cross_part / inertia.mass)

function Base.show(io::IO, inertia::SpatialInertia)
    println(io, "SpatialInertia expressed in $(name_and_id(inertia.frame)):")
    println(io, "mass: $(inertia.mass)")
    println(io, "center of mass: $(center_of_mass(inertia))")
    print(io, "moment of inertia (about origin of $(name_and_id(inertia.frame)):\n$(inertia.moment)")
end

Base.zero(::Type{SpatialInertia{T}}, frame::CartesianFrame3D) where {T} = SpatialInertia(frame, zero(SMatrix{3, 3, T}), zero(SVector{3, T}), zero(T))
Base.zero(inertia::SpatialInertia) = zero(typeof(inertia), inertia.frame)

function Base.isapprox(x::SpatialInertia, y::SpatialInertia; atol = 1e-12)
    x.frame == y.frame && isapprox(x.moment, y.moment; atol = atol) && isapprox(x.cross_part, y.cross_part; atol = atol) && isapprox(x.mass, y.mass; atol = atol)
end

@inline function Base.:+(inertia1::SpatialInertia, inertia2::SpatialInertia)
    @framecheck(inertia1.frame, inertia2.frame)
    SpatialInertia(inertia1.frame,
        inertia1.moment + inertia2.moment,
        inertia1.cross_part + inertia2.cross_part,
        inertia1.mass + inertia2.mass)
end

"""
$(SIGNATURES)

Transform the `SpatialInertia` to a different frame.
"""
@inline function transform(inertia::SpatialInertia, t::Transform3D)
    @framecheck(t.from, inertia.frame)
    J = inertia.moment
    m = inertia.mass
    c = inertia.cross_part
    R = rotation(t)
    p = translation(t)
    cnew = R * c
    Jnew = hat_squared(cnew)
    cnew += m * p
    Jnew -= hat_squared(cnew)
    minv = m > 0 ? inv(m) : zero(m)
    Jnew *= minv
    Jnew += R * J * R'
    SpatialInertia(t.to, Jnew, cnew, convert(eltype(Jnew), m))
end

function Random.rand(::Type{<:SpatialInertia{T}}, frame::CartesianFrame3D) where {T}
    # Try to generate a random but physical moment of inertia by constructing it from its eigendecomposition.

    # Create random principal moments of inertia (about the center of mass).
    # Scale the inertias to make the length scale of the equivalent inertial ellipsoid roughly ~1 unit
    ixx = rand(T) / 10.
    iyy = rand(T) / 10.

    # Ensure that the principal moments of inertia obey the triangle inequalities:
    # http://www.mathworks.com/help/physmod/sm/mech/vis/about-body-color-and-geometry.html
    lb = abs(ixx - iyy)
    ub = ixx + iyy
    izz = rand(T) * (ub - lb) + lb

    # Randomly rotate the principal axes.
    R = rand(RotMatrix3{T})
    moment_about_com = R * Diagonal(SVector(ixx, iyy, izz)) * R'

    SpatialInertia(frame, moment_about_com=moment_about_com, com=rand(SVector{3, T}) .- T(0.5), mass=rand(T))
end

"""
$(SIGNATURES)

Compute the mechanical power associated with a pairing of a wrench and a twist.
"""
LinearAlgebra.dot(w::Wrench, t::Twist) = begin @framecheck(w.frame, t.frame); dot(angular(w), angular(t)) + dot(linear(w), linear(t)) end
LinearAlgebra.dot(t::Twist, w::Wrench) = dot(w, t)

for (ForceSpaceMatrix, ForceSpaceElement) in (:MomentumMatrix => :Momentum, :MomentumMatrix => :Wrench, :WrenchMatrix => :Wrench)
    # MomentumMatrix * velocity vector --> Momentum
    # MomentumMatrix * acceleration vector --> Wrench
    # WrenchMatrix * dimensionless multipliers --> Wrench
    @eval function $ForceSpaceElement(mat::$ForceSpaceMatrix, x::AbstractVector)
        $ForceSpaceElement(mat.frame, SVector{3}(angular(mat) * x), SVector{3}(linear(mat) * x))
    end
end

@inline function Base.:*(inertia::SpatialInertia, twist::Twist)
    @framecheck(inertia.frame, twist.frame)
    ang, lin = mul_inertia(inertia.moment, inertia.cross_part, inertia.mass, angular(twist), linear(twist))
    Momentum(inertia.frame, ang, lin)
end

@inline function Base.:*(inertia::SpatialInertia, jac::GeometricJacobian)
    @framecheck(inertia.frame, jac.frame)
    Jω = angular(jac)
    Jv = linear(jac)
    J = inertia.moment
    m = inertia.mass
    c = inertia.cross_part
    ang = J * Jω + colwise(×, c, Jv)
    lin = m * Jv - colwise(×, c, Jω)
    MomentumMatrix(inertia.frame, ang, lin)
end

"""
$(SIGNATURES)

Apply the Newton-Euler equations to find the external wrench required to
make a body with spatial inertia ``I``, which has twist ``T`` with respect
to an inertial frame, achieve spatial acceleration ``\\dot{T}``.

This wrench is also equal to the rate of change of momentum of the body.
"""
@inline function newton_euler(inertia::SpatialInertia, spatial_accel::SpatialAcceleration, twist::Twist)
    I = inertia
    T = twist
    Ṫ = spatial_accel

    body = Ṫ.body
    base = Ṫ.base # TODO: should assert that this is an inertial frame somehow
    frame = Ṫ.frame

    @framecheck(I.frame, frame)
    @framecheck(T.body, body)
    @framecheck(T.base, base)
    @framecheck(T.frame, frame)

    ang, lin = mul_inertia(I.moment, I.cross_part, I.mass, angular(Ṫ), linear(Ṫ))
    angular_momentum, linear_momentum = mul_inertia(I.moment, I.cross_part, I.mass, angular(T), linear(T))
    ang += angular(T) × angular_momentum + linear(T) × linear_momentum
    lin += angular(T) × linear_momentum
    Wrench(frame, ang, lin)
end

@inline torque!(τ::AbstractVector, jac::GeometricJacobian, wrench::Wrench) = mul!(τ, transpose(jac), wrench)

@inline function torque(jac::GeometricJacobian, wrench::Wrench)
    τ = Vector{promote_type(eltype(jac), eltype(wrench))}(undef, size(jac, 2))
    torque!(τ, jac, wrench)
    τ
end

for (MatrixType, VectorType) in (:WrenchMatrix => :(Union{Twist, SpatialAcceleration}), :GeometricJacobian => :(Union{Momentum, Wrench}))
    @eval begin
        @inline function LinearAlgebra.mul!(
                dest::AbstractVector{T},
                transposed_mat::LinearAlgebra.Transpose{<:Any, <:$MatrixType},
                vec::$VectorType) where T
            mat = parent(transposed_mat)
            @boundscheck length(dest) == size(mat, 2) || throw(DimensionMismatch())
            @framecheck mat.frame vec.frame

            mat_angular = angular(mat)
            mat_linear = linear(mat)
            vec_angular = angular(vec)
            vec_linear = linear(vec)

            @inbounds begin
                @simd for row in eachindex(dest)
                    dest[row] =
                        mat_angular[1, row] * vec_angular[1] +
                        mat_angular[2, row] * vec_angular[2] +
                        mat_angular[3, row] * vec_angular[3]
                end
                @simd for row in eachindex(dest)
                    dest[row] +=
                        mat_linear[1, row] * vec_linear[1] +
                        mat_linear[2, row] * vec_linear[2] +
                        mat_linear[3, row] * vec_linear[3]
                end
            end
            dest
        end

        function Base.:*(transposed_mat::LinearAlgebra.Transpose{<:Any, <:$MatrixType}, vec::$VectorType)
            mat = parent(transposed_mat)
            @framecheck mat.frame vec.frame
            transpose(angular(mat)) * angular(vec) + transpose(linear(mat)) * linear(vec)
        end
    end
end

for ForceSpaceMatrix in (:MomentumMatrix, :WrenchMatrix)
    for (A, B) in ((ForceSpaceMatrix, :GeometricJacobian), (:GeometricJacobian, ForceSpaceMatrix))
        @eval begin
            function Base.:*(at::LinearAlgebra.Transpose{<:Any, <:$A}, b::$B)
                a = parent(at)
                @framecheck a.frame b.frame
                transpose(angular(a)) * angular(b) + transpose(linear(a)) * linear(b)
            end
            function Base.:*(a::$A, bt::LinearAlgebra.Transpose{<:Any, <:$B})
                b = parent(bt)
                @framecheck a.frame b.frame
                angular(a) * transpose(angular(b)) + linear(a) * transpose(linear(b))
            end
        end
    end
end

"""
$(SIGNATURES)

Compute the kinetic energy of a body with spatial inertia ``I``, which has
twist ``T`` with respect to an inertial frame.
"""
@inline function kinetic_energy(inertia::SpatialInertia, twist::Twist)
    @framecheck(inertia.frame, twist.frame)
    # TODO: should assert that twist.base is an inertial frame somehow
    ω = angular(twist)
    v = linear(twist)
    J = inertia.moment
    c = inertia.cross_part
    m = inertia.mass
    (ω ⋅ (J * ω) + v ⋅ (m * v + 2 * (ω × c))) / 2
end


using Random
using RecipesBase
using LaTeXStrings
# restricted to 2-player game

"""
    `train_both!(player1, player2, N::Int)`

Find Nash equilibrium by training two players against each other

## Example
```
RPS = MatrixGame([
    (0,0) (-1,1) (1,-1);
    (1,-1) (0,0) (-1,1);
    (-1,1) (1,-1) (0,0)
])
init_strategy = [0.1,0.3,0.6]
player1 = MatrixPlayer(RPS, 1, copy(init_strategy))
player2 = MatrixPlayer(RPS, 2, copy(init_strategy))
train_both!(player1, player2, 1000)
```
"""
function train_both! end

"""
    `train_one!(player1, player2, N::Int)`

Train only player 1 while keeping strategy of player 2 constant.
Yields strategy for player 1 that is maximally exploitative of player 2.

```
RPS = MatrixGame([
    (0,0) (-1,1) (1,-1);
    (1,-1) (0,0) (-1,1);
    (-1,1) (1,-1) (0,0)
])
init_strategy = [0.1,0.3,0.6]
player1 = MatrixPlayer(RPS, 1, copy(init_strategy))
player2 = MatrixPlayer(RPS, 2, copy(init_strategy))
train_one!(player1, player2, 1000)
```
"""
function train_one! end

"""
Matrix Game
- Solved with regret matching
- Takes reward matrix as input

    `MatrixGame(R::Matrix{NTuple{2,T}})`

## Example
```
RPS = MatrixGame([
    (0,0) (-1,1) (1,-1);
    (1,-1) (0,0) (-1,1);
    (-1,1) (1,-1) (0,0)
])
```
"""
struct MatrixGame{T}
    R::Matrix{NTuple{2,T}}
end

"""
Matrix Game Player

Instantiate with `MatrixPlayer(game, id[, initial_strategy])`
Default to uniform strategy

## Example
```
init_strategy = [0.1,0.3,0.6]
player1 = MatrixPlayer(game, 1, init_strategy)
```
"""
struct MatrixPlayer{T}
    id::Int
    game::MatrixGame{T}
    strategy::Vector{Float64}
    hist::Vector{Vector{Float64}}
    regret_sum::Vector{Float64}
    strat_sum::Vector{Float64}
end

player(g::MatrixGame, id::Int) = MatrixPlayer(g, id)
player(g::MatrixGame, id::Int, s::Vector{Float64}) = MatrixPlayer(g, id, s)

function MatrixPlayer(game::MatrixGame, id::Int)
    n_actions = size(game.R, id)
    strategy = fill(1/n_actions, n_actions)
    MatrixPlayer(
        id,
        game,
        strategy,
        [copy(strategy)],
        zeros(n_actions),
        copy(strategy)
    )
end

function clear!(p::MatrixPlayer)
    resize!(p.hist, 1)
    p.regret_sum .= 0.0
    p.strat_sum .= p.strategy
    p.hist[1] .= p.strategy
end

function MatrixPlayer(game::MatrixGame, id::Int, strategy::Vector{Float64})
    n_actions = size(game.R, id)
    @assert length(strategy) == n_actions
    MatrixPlayer(
        id,
        game,
        copy(strategy),
        [copy(strategy)],
        zeros(n_actions),
        copy(strategy)
    )
end

function regret(game::MatrixGame, i::Int, a1::Int, a2::Int)
    u = game.R[a1,a2][i]
    if i === 1
        return [
            game.R[a,a2][i] - u
            for a in 1:size(game.R, i)
            ]
    elseif i === 2
        return [
            game.R[a1,a][i] - u
            for a in 1:size(game.R, i)
            ]
    else
        error("i must be 1 or 2")
    end
end

function gen_action(rng::AbstractRNG, p::MatrixPlayer)
    σ = p.strategy
    t = rand(rng)
    i = 1
    cw = σ[1]
    while cw < t && i < length(σ)
        i += 1
        @inbounds cw += σ[i]
    end
    return i
end

gen_action(p::MatrixPlayer) = gen_action(Random.GLOBAL_RNG, p)

function update_regret!(p::MatrixPlayer, a1::Int, a2::Int)
    p.regret_sum .+= regret(p.game, p.id, a1, a2)
end

function fill_normed_regret!(v::Vector{Float64}, r::Vector)
    s = 0.0
    for (i,k) in enumerate(r)
        if k > 0
            s += k
            v[i] = k
        else
            v[i] = 0.0
        end
    end
    if s == 0
        fill!(v, 1/length(v))
    else
        v ./= s
    end
end

function update_strategy!(p::MatrixPlayer)
    fill_normed_regret!(p.strategy, p.regret_sum)
    σ = p.strategy
    p.strat_sum .+= σ
    s = copy(p.strat_sum)
    s ./= sum(s)
    push!(p.hist, s)
end

function finalize_strategy!(p::MatrixPlayer)
    σ = p.strategy .= 0.0
    for σ_i in p.hist
        σ .+= σ_i
    end
    σ ./= sum(σ)
end

function evaluate(p1::MatrixPlayer, p2::MatrixPlayer)
    # strategies assumed already finalized
    game = p1.game
    σ1 = p1.strategy
    σ2 = p2.strategy
    R = game.R
    s1,s2 = size(R)
    p1_eval = 0.0
    p2_eval = 0.0
    for i in 1:s1, j in 1:s2
        prob = σ1[i]*σ2[j]
        p1_eval += prob*R[i,j][1]
        p2_eval += prob*R[i,j][2]
    end
    return p1_eval, p2_eval
end

function train_both!(p1::MatrixPlayer, p2::MatrixPlayer, N::Int; show_progress::Bool=false)
    prog = Progress(N; enabled=show_progress)
    for i in 1:N
        a1, a2 = gen_action(p1), gen_action(p2)

        update_regret!(p1, a1, a2)
        update_regret!(p2, a1, a2)

        update_strategy!(p1)
        update_strategy!(p2)

        next!(prog)
    end
    finalize_strategy!(p1)
    finalize_strategy!(p2)
end

function train_one!(p1::MatrixPlayer, p2::MatrixPlayer, N::Int; show_progress::Bool=false)
    prog = Progress(N; enabled=show_progress)
    for i in 1:N
        a1, a2 = gen_action(p1), gen_action(p2)

        update_regret!(p1, a1, a2)

        update_strategy!(p1)
        next!(prog)
    end
    finalize_strategy!(p1)
end

function cumulative_strategies(p::MatrixPlayer)
    mat = Matrix{Float64}(undef, length(p.hist), length(p.strategy))
    σ = zeros(Float64, length(p.strategy))

    for (i,σ_i) in enumerate(p.hist)
        σ = σ + (σ_i - σ)/i
        mat[i,:] .= σ
    end
    return mat
end

@recipe function f(p1::MatrixPlayer, p2::MatrixPlayer)
    layout --> 2
    link := :both
    framestyle := [:axes :axes]

    xlabel := "Training Steps"

    L1 = length(p1.strategy)
    labels1 = Matrix{String}(undef, 1, L1)
    for i in eachindex(labels1); labels1[i] = L"a_{%$(i)}"; end

    @series begin
        subplot := 1
        ylabel := "Strategy"
        title := "Player 1"
        labels := labels1
        reduce(hcat,p1.hist)'
    end

    L2 = length(p2.strategy)
    labels2 = Matrix{String}(undef, 1, L2)
    for i in eachindex(labels2); labels2[i] = L"a_{%$(i)}"; end

    @series begin
        subplot := 2
        title := "Player 2"
        labels := labels2
        reduce(hcat,p2.hist)'
    end
end

@recipe function f(p::MatrixPlayer)

    xlabel := "Training Steps"

    L = length(p.strategy)
    labels = Matrix{String}(undef, 1, L)
    for i in eachindex(labels); labels[i] = L"a_{%$(i)}"; end

    @series begin
        subplot := 1
        ylabel := "Strategy"
        title := "Player 1"
        labels := labels
        cumulative_strategies(p)
    end
end


# ---
# title: 4. Median of Two Sorted Arrays
# id: problem4
# author: Tian Jun
# date: 2020-10-31
# difficulty: Hard
# categories: Array, Binary Search, Divide and Conquer
# link: <https://leetcode.com/problems/median-of-two-sorted-arrays/description/>
# hidden: true
# ---
#
# Given two sorted arrays `nums1` and `nums2` of size `m` and `n` respectively,
# return **the median** of the two sorted arrays.
#
# **Follow up:** The overall run time complexity should be `O(log (m+n))`.
#
#
#
# **Example 1:**
#
#
#
#     Input: nums1 = [1,3], nums2 = [2]
#     Output: 2.00000
#     Explanation: merged array = [1,2,3] and median is 2.
#
#
# **Example 2:**
#
#
#
#     Input: nums1 = [1,2], nums2 = [3,4]
#     Output: 2.50000
#     Explanation: merged array = [1,2,3,4] and median is (2 + 3) / 2 = 2.5.
#
#
# **Example 3:**
#
#
#
#     Input: nums1 = [0,0], nums2 = [0,0]
#     Output: 0.00000
#
#
# **Example 4:**
#
#
#
#     Input: nums1 = [], nums2 = [1]
#     Output: 1.00000
#
#
# **Example 5:**
#
#
#
#     Input: nums1 = [2], nums2 = []
#     Output: 2.00000
#
#
#
#
# **Constraints:**
#
#   * `nums1.length == m`
#   * `nums2.length == n`
#   * `0 <= m <= 1000`
#   * `0 <= n <= 1000`
#   * `1 <= m + n <= 2000`
#   * `-106 <= nums1[i], nums2[i] <= 106`
#
#
## @lc code=start
using LeetCode

function find_median_sorted_arrays(nums1::Vector{Int}, nums2::Vector{Int})::Float64
    n = length(nums1) + length(nums2)
    isodd(n) ? nth(nums1, nums2, n ÷ 2 + 1) :
    (nth(nums1, nums2, n ÷ 2) + nth(nums1, nums2, n ÷ 2 + 1)) / 2
end

function nth(a, b, k)
    isempty(a) && return b[k]
    isempty(b) && return a[k]
    ia, ib = cld(length(a), 2), cld(length(b), 2)
    ma, mb = a[ia], b[ib]
    if ia + ib <= k
        ma > mb ? nth(a, @view(b[(ib + 1):end]), k - ib) : ## we can safely ignore the first half in b
        nth(@view(a[(ia + 1):end]), b, k - ia)
    else
        ma > mb ? nth(@view(a[1:(ia - 1)]), b, k) : nth(a, @view(b[1:(ib - 1)]), k)
    end
end
## @lc code=end


using HOODESolver
using DifferentialEquations
using LinearAlgebra
using Plots
using Random

function getmindif(tab::Vector{Vector{BigFloat}})
    nmmin = Inf
    ret = (0, 0)
    for i=1:size(tab,1)
        for j=(i+1):size(tab,1)
            nm = norm(tab[i]-tab[j], Inf)
            if nm < nmmin
                nmmin = nm
                ret = i, j
            end
        end
    end
    return ret, nmmin
end

function fctmain(n_tau, prec)

    setprecision(prec)
    Random.seed!(5612)
    u0=rand(BigFloat,4)

    t_max = big"1.0"
    epsilon=big"1e-3"
    println("epsilon=$epsilon")
    nbmaxtest=10
    ordmax=12
    debord=3
    pasord=1
    y = ones(Float64, nbmaxtest, div(ordmax-debord,pasord)+1 )
    fct = u -> [ u[2]^2,1/(1+u[3]^2),0.5-u[4],0.8u[1] ]
    res_gen = Array{ Array{BigFloat,1}, 2}(undef, nbmaxtest, div(ordmax-debord,pasord)+1 )
    x=zeros(Float64,nbmaxtest)
    A = [0 0 1 0; 0 0 0 0;-1 0 0 0; 0 0 0 0]
    parphi = PreparePhi(epsilon, n_tau, A, fct)
    nm = NaN
    ordmax += 1
    ordprep=undef
    nb = 20*2^(nbmaxtest)
    solref=undef
    while isnan(nm)
        ordmax -= 1
        ordprep = ordmax+2
    	@time par_u0 = PrepareU0(parphi, ordprep, u0)
        @time pargen = PrepareTwoScalePureAB(nb, t_max, ordmax, par_u0)
        @time solref= twoscales_pure_ab(
pargen,
only_end=true,
diff_fft=false
)
	    nm = norm(solref, Inf)
    end

    tabsol = Array{Array{BigFloat,1},1}(undef,1)

    tabsol[1] = solref

    println("ordmax=$ordmax")

    println("solref=$solref")
    solref_gen = solref
    indref = 1

    ind=1
    for order=debord:pasord:ordmax
        ordprep=order+2
        println("eps=$epsilon solRef=$solref order=$order")
        nb = 10
        indc = 1
        labels=Array{String,2}(undef, 1, order-debord+1)
        resnorm=0
        resnormprec=1
        sol =undef
        println("preparation ordre $order + 2")
        @time par_u0 = PrepareU0(parphi, ordprep, u0)
        while indc <= nbmaxtest
            @time pargen = PrepareTwoScalePureAB(nb, t_max, order, par_u0)
            @time sol = twoscales_pure_ab(
    pargen,
    only_end=true,
    diff_fft=false
)
            push!(tabsol, sol)
            res_gen[indc,ind] = sol
            (a, b), nm = getmindif(tabsol)
            if a != indref
                println("New solref !!!! a=$a, b=$b nm=$nm")
                indref = a
                solref = tabsol[a]
                for i=1:ind
                    borne = (i <ind) ? size(res_gen,1) : indc
                    for j = 1:borne
                        nm2 = min( norm(res_gen[j,i] - solref, Inf), 1.1)
                        y[j,i] = nm2 == 0 ? nm : nm2
                   end
                end
            else
                diff=solref-sol
                y[indc,ind] = min(norm(diff,Inf), 1.1)
            end
            x[indc] = 1.0/nb
            println("result=$y")
            println("log2(y)=$(log2.(y))")
            nb *= 2
            indc += 1
        end
        for i=debord:pasord:order
            labels[1,(i-debord)÷pasord+1] = " eps,order=$(convert(Float32,epsilon)),$i "
        end
        p=Plots.plot(
                        x,
                        view(y,:,1:ind),
                        xlabel="delta t",
                        xaxis=:log,
                        ylabel="error",
                        yaxis=:log,
                        legend=:topleft,
                        label=labels,
                        marker=2
                    )
        prec_v = precision(BigFloat)
        eps_v = convert(Float32,epsilon)
        Plots.savefig(p,"out/r2_$(prec_v)_$(eps_v)_$(order)_$(n_tau)_fct3.pdf")
        if resnorm > resnormprec
            break
        end
        resnormprec = resnorm
        ind+= 1
    end
end

# testODESolver()

# for i=3:9
#     fctMain(2^i)
# end
fctmain(256,512)