module Systems

using SymEngine
using ..HopfNormalForm: calculate_normal_form
const I=1im
const i=1im

function example1()
    norder = 3; # Choose the order of power series representation of Centermanifold
    cm = 3; # Dimension of Centermanifold of the dynamical system
    m1 = 1; # Number of the zero eigenvalue corresponding to the center space of the Jacobian
    m2 = 1; # Number of pair of complex conjugate eigenvalue corresponding to the center space of the Jacobian
    m3 = 4; # Dimension of the stable manifold of the dynmical system
    n = m1+2*m2+m3; # Total dimension of the dynamical system
    ω_h = 15.4793; # Eigenvalue that crosses unit circle at Hopf bifurcation point ± ω_h
    zero_tol = 1e-13; # zero tolerance to chop out very small coeffs of symbolic computation

    v = [symbols("v_$i") for i in 1:n]; # Define state-space variables
    f = Array{Basic}(undef, n);

    @vars ka2, ka3, ν  # Define symbolic variables which is needed to define the equation of motion
    # In this example the variables are nonlinear stifness parameters

    # Define the equation of motion at the Hopf bifurcation point where the dimension of centermanifold is 2
    # Let the dimension of control parameter vector be 1, and express it as a one of state-space variable v[1]
    # d/dt v[1]=0 and let rest of the state-space variable be v[2],v[3],…,v[n].
    f[1]=0

    f[2]=v[2]*15.4793i - 1.0*v[5]*(- v[1]^3*(0.0000344811 - 0.0000411838i) + v[1]^2*(0.00239738 + 0.00406001i) + v[1]*(0.128199 + 0.108645i)) - 1.0*v[7]*(v[1]^3*(0.00028883 - 0.000720132i) + v[1]^2*(0.0132244 - 0.0199877i) + v[1]*(0.167761 - 0.219131i)) + ka2*(- v[2]*v[3]*(0.00329 + 0.00786i) + v[2]*v[4]*(0.00457 + 0.00683i) - v[2]*v[5]*(0.00165 + 0.00805i) - v[3]*v[4]*(0.00457 + 0.00683i) - v[2]*v[6]*(0.00018 - 0.00007i) + v[3]*v[5]*(0.00165 + 0.00805i) - v[2]*v[7]*(0.00006 - 0.00002i) + v[3]*v[6]*(0.00018 - 0.00007i) - v[4]*v[5]*(0.00306 + 0.00731i) + v[3]*v[7]*(0.00006 - 0.00002i) - v[4]*v[6]*(0.00015 - 0.0001i) - v[4]*v[7]*(0.00005 - 0.00003i) + v[5]*v[6]*(0.00018 - 0.00004i) + v[5]*v[7]*(0.00006 - 0.00001i) + v[2]^2*(0.00164 + 0.00393i) + v[3]^2*(0.00164 + 0.00393i) + v[4]^2*(0.00279 + 0.00281i) + v[5]^2*(0.00004 + 0.00396i)) - 1.0*v[6]*(- v[1]^3*(0.0000447835 - 0.000111657i) + v[1]^2*(0.00116393 - 0.00491519i) + v[1]*(0.113807 - 0.0877385i)) + v[4]*(v[1]^3*(0.00000353325 - 0.0000535964i) + v[1]^2*(0.00520619 - 0.00123418i) + v[1]*(0.167966 + 0.0119843i)) - 1.0*v[3]*(- v[1]^3*(6.45125e-7 - 0.000040738i) + v[1]^2*(0.00381858 + 0.00281874i) + v[1]*(0.150471 + 0.0573303i)) + v[2]*(v[1]^3*(0.0000276837 - 0.0000298934i) + v[1]^2*(0.00508047 + 0.000946023i) + v[1]*(0.136146 + 0.057017i)) + ka3*(v[2]*v[3]^2*(0.0007 - 0.00029i) - v[2]^2*v[3]*(0.0007 - 0.00029i) + v[2]*v[4]^2*(0.0005 - 0.0005i) + v[2]^2*v[4]*(0.00061 - 0.00041i) + v[2]*v[5]^2*(0.00071 - 0.00001i) - v[3]*v[4]^2*(0.0005 - 0.0005i) - v[2]^2*v[5]*(0.00072 - 0.00015i) + v[3]^2*v[4]*(0.00061 - 0.00041i) - v[3]*v[5]^2*(0.00071 - 0.00001i) + v[2]^2*v[6]*(0.00001 + 0.00002i) - v[3]^2*v[5]*(0.00072 - 0.00015i) + v[4]*v[5]^2*(0.00067 - 0.00014i) + v[3]^2*v[6]*(0.00001 + 0.00002i) - v[4]^2*v[5]*(0.00057 - 0.00038i) + v[4]^2*v[6]*(0.00001 + 0.00001i) + v[5]^2*v[6]*0.00002i + v[2]^3*(0.00023 - 0.0001i) - v[3]^3*(0.00023 - 0.0001i) + v[4]^3*(0.00013 - 0.00019i) - v[5]^3*(0.00022 + 0.00004i) - v[2]*v[3]*v[4]*(0.00122 - 0.00082i) + v[2]*v[3]*v[5]*(0.00144 - 0.0003i) - v[2]*v[3]*v[6]*(0.00001 + 0.00003i) - v[2]*v[4]*v[5]*(0.00131 - 0.00055i) - v[2]*v[3]*v[7]*0.00001i + v[2]*v[4]*v[6]*(0.00002 + 0.00003i) + v[3]*v[4]*v[5]*(0.00131 - 0.00055i) + v[2]*v[4]*v[7]*(0.00001 + 0.00001i) - v[2]*v[5]*v[6]*(0.00001 + 0.00003i) - v[3]*v[4]*v[6]*(0.00002 + 0.00003i) - v[2]*v[5]*v[7]*0.00001i - v[3]*v[4]*v[7]*(0.00001 + 0.00001i) + v[3]*v[5]*v[6]*(0.00001 + 0.00003i) + v[3]*v[5]*v[7]*0.00001i - v[4]*v[5]*v[6]*(0.00001 + 0.00003i) - v[4]*v[5]*v[7]*0.00001i)

    f[3]=- v[3]*15.4793i - 1.0*v[4]*(- v[1]^3*(0.0000344811 + 0.0000411838i) + v[1]^2*(0.00239738 - 0.00406001i) + v[1]*(0.128199 - 0.108645i)) - 1.0*v[7]*(v[1]^3*(0.00028883 + 0.000720132i) + v[1]^2*(0.0132244 + 0.0199877i) + v[1]*(0.167761 + 0.219131i)) + ka2*(- v[2]*v[3]*(0.00329 - 0.00786i) + v[2]*v[4]*(0.00165 - 0.00805i) - v[2]*v[5]*(0.00457 - 0.00683i) - v[3]*v[4]*(0.00165 - 0.00805i) + v[2]*v[6]*(0.00018 + 0.00007i) + v[3]*v[5]*(0.00457 - 0.00683i) + v[2]*v[7]*(0.00006 + 0.00002i) - v[3]*v[6]*(0.00018 + 0.00007i) - v[4]*v[5]*(0.00306 - 0.00731i) - v[3]*v[7]*(0.00006 + 0.00002i) + v[4]*v[6]*(0.00018 + 0.00004i) + v[4]*v[7]*(0.00006 + 0.00001i) - v[5]*v[6]*(0.00015 + 0.0001i) - v[5]*v[7]*(0.00005 + 0.00003i) + v[2]^2*(0.00164 - 0.00393i) + v[3]^2*(0.00164 - 0.00393i) + v[4]^2*(0.00004 - 0.00396i) + v[5]^2*(0.00279 - 0.00281i)) - 1.0*v[6]*(- v[1]^3*(0.0000447835 + 0.000111657i) + v[1]^2*(0.00116393 + 0.00491519i) + v[1]*(0.113807 + 0.0877385i)) + v[5]*(v[1]^3*(0.00000353325 + 0.0000535964i) + v[1]^2*(0.00520619 + 0.00123418i) + v[1]*(0.167966 - 0.0119843i)) - 1.0*v[2]*(- v[1]^3*(6.45125e-7 + 0.000040738i) + v[1]^2*(0.00381858 - 0.00281874i) + v[1]*(0.150471 - 0.0573303i)) + v[3]*(v[1]^3*(0.0000276837 + 0.0000298934i) + v[1]^2*(0.00508047 - 0.000946023i) + v[1]*(0.136146 - 0.057017i)) + ka3*(- v[2]*v[3]^2*(0.0007 + 0.00029i) + v[2]^2*v[3]*(0.0007 + 0.00029i) - v[2]*v[4]^2*(0.00071 + 0.00001i) - v[2]^2*v[4]*(0.00072 + 0.00015i) - v[2]*v[5]^2*(0.0005 + 0.0005i) + v[3]*v[4]^2*(0.00071 + 0.00001i) + v[2]^2*v[5]*(0.00061 + 0.00041i) - v[3]^2*v[4]*(0.00072 + 0.00015i) + v[3]*v[5]^2*(0.0005 + 0.0005i) + v[2]^2*v[6]*(0.00001 - 0.00002i) + v[3]^2*v[5]*(0.00061 + 0.00041i) - v[4]*v[5]^2*(0.00057 + 0.00038i) + v[3]^2*v[6]*(0.00001 - 0.00002i) + v[4]^2*v[5]*(0.00067 + 0.00014i) - v[4]^2*v[6]*0.00002i + v[5]^2*v[6]*(0.00001 - 0.00001i) - v[2]^3*(0.00023 + 0.0001i) + v[3]^3*(0.00023 + 0.0001i) - v[4]^3*(0.00022 - 0.00004i) + v[5]^3*(0.00013 + 0.00019i) + v[2]*v[3]*v[4]*(0.00144 + 0.0003i) - v[2]*v[3]*v[5]*(0.00122 + 0.00082i) - v[2]*v[3]*v[6]*(0.00001 - 0.00003i) + v[2]*v[4]*v[5]*(0.00131 + 0.00055i) + v[2]*v[3]*v[7]*0.00001i + v[2]*v[4]*v[6]*(0.00001 - 0.00003i) - v[3]*v[4]*v[5]*(0.00131 + 0.00055i) - v[2]*v[4]*v[7]*0.00001i - v[2]*v[5]*v[6]*(0.00002 - 0.00003i) - v[3]*v[4]*v[6]*(0.00001 - 0.00003i) - v[2]*v[5]*v[7]*(0.00001 - 0.00001i) + v[3]*v[4]*v[7]*0.00001i + v[3]*v[5]*v[6]*(0.00002 - 0.00003i) + v[3]*v[5]*v[7]*(0.00001 - 0.00001i) - v[4]*v[5]*v[6]*(0.00001 - 0.00003i) + v[4]*v[5]*v[7]*0.00001i)

    f[4]=- v[4]*(3.22105 - 16.4334i) + ka3*(0.00001*v[2]^2*v[7] + v[2]^2*v[3]*(0.00042 + 0.00167i) - v[2]*v[4]^2*(0.00095 + 0.00129i) - v[2]^2*v[4]*(0.00071 + 0.0015i) + v[2]*v[5]^2*(0.00022 - 0.00158i) + v[3]*v[4]^2*(0.00095 + 0.00129i) + v[2]^2*v[5]*(0.00009 + 0.00165i) - v[3]^2*v[4]*(0.00071 + 0.0015i) - v[3]*v[5]^2*(0.00022 - 0.00158i) + v[2]^2*v[6]*(0.00004 - 0.00001i) + v[3]^2*v[5]*(0.00009 + 0.00165i) - v[4]*v[5]^2*(0.00008 + 0.00154i) - v[2]*v[3]^2*(0.00042 + 0.00167i) + v[3]^2*v[6]*(0.00004 - 0.00001i) + v[4]^2*v[5]*(0.00066 + 0.00139i) + 0.00001*v[3]^2*v[7] + v[4]^2*v[6]*(0.00003 - 0.00002i) + v[4]^2*v[7]*(0.00001 - 0.00001i) + 0.00004*v[5]^2*v[6] + 0.00001*v[5]^2*v[7] - v[2]^3*(0.00014 + 0.00056i) + v[3]^3*(0.00014 + 0.00056i) - v[4]^3*(0.00038 + 0.00035i) - v[5]^3*(0.00017 - 0.00049i) + v[2]*v[3]*v[4]*(0.00142 + 0.003i) - v[2]*v[3]*v[5]*(0.00018 + 0.00331i) - v[2]*v[3]*v[6]*(0.00007 - 0.00002i) + v[2]*v[4]*v[5]*(0.00078 + 0.0031i) - v[2]*v[3]*v[7]*(0.00002 - 0.00001i) + v[2]*v[4]*v[6]*(0.00007 - 0.00003i) - v[3]*v[4]*v[5]*(0.00078 + 0.0031i) + v[2]*v[4]*v[7]*(0.00002 - 0.00001i) - 0.00007*v[2]*v[5]*v[6] - v[3]*v[4]*v[6]*(0.00007 - 0.00003i) - 0.00002*v[2]*v[5]*v[7] - v[3]*v[4]*v[7]*(0.00002 - 0.00001i) + 0.00007*v[3]*v[5]*v[6] + 0.00002*v[3]*v[5]*v[7] - v[4]*v[5]*v[6]*(0.00007 - 0.00002i) - v[4]*v[5]*v[7]*(0.00002 - 0.00001i)) - 1.0*v[2]*(v[1]^3*(0.0000420472 - 0.0000200759i) + v[1]^2*(0.00501926 + 0.00319374i) + v[1]*(0.152099 + 0.126793i)) + v[7]*(v[1]^3*(0.000611125 - 0.000643319i) + v[1]^2*(0.0231344 - 0.0150345i) + v[1]*(0.280706 - 0.152463i)) + v[6]*(- v[1]^3*(0.0000947556 - 0.0000997473i) + v[1]^2*(0.00321416 - 0.00482834i) + v[1]*(0.169289 - 0.0352994i)) + v[3]*(- v[1]^3*(0.0000178927 - 0.0000430216i) + v[1]^2*(0.00282464 + 0.00459i) + v[1]*(0.104275 + 0.115717i)) + ka2*(- v[2]*v[3]*(0.01859 - 0.00471i) + v[2]*v[4]*(0.01672 - 0.00791i) - v[2]*v[5]*(0.01846 - 0.00101i) - v[3]*v[4]*(0.01672 - 0.00791i) + v[2]*v[6]*(0.00011 + 0.00042i) + v[3]*v[5]*(0.01846 - 0.00101i) + v[2]*v[7]*(0.00003 + 0.00013i) - v[3]*v[6]*(0.00011 + 0.00042i) - v[4]*v[5]*(0.01728 - 0.00438i) - v[3]*v[7]*(0.00003 + 0.00013i) + v[4]*v[6]*(0.00018 + 0.00037i) + v[4]*v[7]*(0.00006 + 0.00012i) - v[5]*v[6]*(0.00002 + 0.00041i) - v[5]*v[7]*(0.00001 + 0.00013i) + v[2]^2*(0.0093 - 0.00236i) + v[3]^2*(0.0093 - 0.00236i) + v[4]^2*(0.00717 - 0.00529i) + v[5]^2*(0.00883 + 0.00123i)) + v[5]*(- v[1]^3*(0.00005404 - 0.0000292036i) + v[1]^2*(0.000804306 + 0.00527276i) + v[1]*(0.0564505 + 0.15966i)) - 1.0*v[4]*(v[1]^3*(0.0000263932 - 0.0000554668i) + v[1]^2*(0.00610947 + 0.000914594i) + v[1]*(0.207125 + 0.0938587i))

    f[5]=- v[5]*(3.22105 + 16.4334i) + ka3*(v[2]*v[3]^2*(0.00042 - 0.00167i) - v[2]^2*v[3]*(0.00042 - 0.00167i) - v[2]*v[4]^2*(0.00022 + 0.00158i) + v[2]^2*v[4]*(0.00009 - 0.00165i) + v[2]*v[5]^2*(0.00095 - 0.00129i) + v[3]*v[4]^2*(0.00022 + 0.00158i) - v[2]^2*v[5]*(0.00071 - 0.0015i) + v[3]^2*v[4]*(0.00009 - 0.00165i) - v[3]*v[5]^2*(0.00095 - 0.00129i) + v[2]^2*v[6]*(0.00004 + 0.00001i) - v[3]^2*v[5]*(0.00071 - 0.0015i) + v[4]*v[5]^2*(0.00066 - 0.00139i) + 0.00001*v[2]^2*v[7] + v[3]^2*v[6]*(0.00004 + 0.00001i) - v[4]^2*v[5]*(0.00008 - 0.00154i) + 0.00001*v[3]^2*v[7] + 0.00004*v[4]^2*v[6] + 0.00001*v[4]^2*v[7] + v[5]^2*v[6]*(0.00003 + 0.00002i) + v[5]^2*v[7]*(0.00001 + 0.00001i) + v[2]^3*(0.00014 - 0.00056i) - v[3]^3*(0.00014 - 0.00056i) - v[4]^3*(0.00017 + 0.00049i) - v[5]^3*(0.00038 - 0.00035i) - v[2]*v[3]*v[4]*(0.00018 - 0.00331i) + v[2]*v[3]*v[5]*(0.00142 - 0.003i) - v[2]*v[3]*v[6]*(0.00007 + 0.00002i) - v[2]*v[4]*v[5]*(0.00078 - 0.0031i) - v[2]*v[3]*v[7]*(0.00002 + 0.00001i) + 0.00007*v[2]*v[4]*v[6] + v[3]*v[4]*v[5]*(0.00078 - 0.0031i) + 0.00002*v[2]*v[4]*v[7] - v[2]*v[5]*v[6]*(0.00007 + 0.00003i) - 0.00007*v[3]*v[4]*v[6] - v[2]*v[5]*v[7]*(0.00002 + 0.00001i) - 0.00002*v[3]*v[4]*v[7] + v[3]*v[5]*v[6]*(0.00007 + 0.00003i) + v[3]*v[5]*v[7]*(0.00002 + 0.00001i) - v[4]*v[5]*v[6]*(0.00007 + 0.00002i) - v[4]*v[5]*v[7]*(0.00002 + 0.00001i)) - 1.0*v[3]*(v[1]^3*(0.0000420472 + 0.0000200759i) + v[1]^2*(0.00501926 - 0.00319374i) + v[1]*(0.152099 - 0.126793i)) + v[7]*(v[1]^3*(0.000611125 + 0.000643319i) + v[1]^2*(0.0231344 + 0.0150345i) + v[1]*(0.280706 + 0.152463i)) + v[6]*(- v[1]^3*(0.0000947556 + 0.0000997473i) + v[1]^2*(0.00321416 + 0.00482834i) + v[1]*(0.169289 + 0.0352994i)) + v[2]*(- v[1]^3*(0.0000178927 + 0.0000430216i) + v[1]^2*(0.00282464 - 0.00459i) + v[1]*(0.104275 - 0.115717i)) + ka2*(- v[2]*v[3]*(0.01859 + 0.00471i) + v[2]*v[4]*(0.01846 + 0.00101i) - v[2]*v[5]*(0.01672 + 0.00791i) - v[3]*v[4]*(0.01846 + 0.00101i) - v[2]*v[6]*(0.00011 - 0.00042i) + v[3]*v[5]*(0.01672 + 0.00791i) - v[2]*v[7]*(0.00003 - 0.00013i) + v[3]*v[6]*(0.00011 - 0.00042i) - v[4]*v[5]*(0.01728 + 0.00438i) + v[3]*v[7]*(0.00003 - 0.00013i) - v[4]*v[6]*(0.00002 - 0.00041i) - v[4]*v[7]*(0.00001 - 0.00013i) + v[5]*v[6]*(0.00018 - 0.00037i) + v[5]*v[7]*(0.00006 - 0.00012i) + v[2]^2*(0.0093 + 0.00236i) + v[3]^2*(0.0093 + 0.00236i) + v[4]^2*(0.00883 - 0.00123i) + v[5]^2*(0.00717 + 0.00529i)) + v[4]*(- v[1]^3*(0.00005404 + 0.0000292036i) + v[1]^2*(0.000804306 - 0.00527276i) + v[1]*(0.0564505 - 0.15966i)) - 1.0*v[5]*(v[1]^3*(0.0000263932 + 0.0000554668i) + v[1]^2*(0.00610947 - 0.000914594i) + v[1]*(0.207125 - 0.0938587i))

    f[6]=- 35.42*v[6] + ka2*(0.00156*v[2]^2 + v[2]*v[4]*(0.00296 - 0.00058i) - v[2]*v[5]*(0.00296 + 0.00058i) - v[3]*v[4]*(0.00296 - 0.00058i) + v[2]*v[6]*0.00007i + v[3]*v[5]*(0.00296 + 0.00058i) + v[2]*v[7]*0.00002i - v[3]*v[6]*0.00007i - 0.00291*v[4]*v[5] - v[3]*v[7]*0.00002i + v[4]*v[6]*(0.00001 + 0.00007i) + v[4]*v[7]*0.00002i + v[5]*v[6]*(0.00001 - 0.00007i) - v[5]*v[7]*0.00002i - 0.00313*v[2]*v[3] + 0.00156*v[3]^2 + v[4]^2*(0.00135 - 0.00055i) + v[5]^2*(0.00135 + 0.00055i)) + v[4]*(- v[1]^3*(0.0000100928 - 0.00000329552i) + v[1]^2*(0.00800912 - 0.00360892i) + v[1]*(0.277632 - 0.00848122i)) + v[5]*(- v[1]^3*(0.0000100928 + 0.00000329552i) + v[1]^2*(0.00800912 + 0.00360892i) + v[1]*(0.277632 + 0.00848122i)) - 1.0*ka3*(v[2]^3*0.00009i - v[2]^2*v[3]*0.00028i + v[2]^2*v[4]*(0.00005 + 0.00027i) + v[2]^2*v[5]*(0.00005 - 0.00027i) - 0.00001*v[6]*v[2]^2 + v[2]*v[3]^2*0.00028i - v[2]*v[3]*v[4]*(0.0001 + 0.00053i) - v[2]*v[3]*v[5]*(0.0001 - 0.00053i) + 0.00001*v[6]*v[2]*v[3] + v[2]*v[4]^2*(0.0001 + 0.00024i) - v[2]*v[4]*v[5]*0.00052i - 0.00001*v[6]*v[2]*v[4] - v[2]*v[5]^2*(0.0001 - 0.00024i) + 0.00001*v[6]*v[2]*v[5] - v[3]^3*0.00009i + v[3]^2*v[4]*(0.00005 + 0.00027i) + v[3]^2*v[5]*(0.00005 - 0.00027i) - 0.00001*v[6]*v[3]^2 - v[3]*v[4]^2*(0.0001 + 0.00024i) + v[3]*v[4]*v[5]*0.00052i + 0.00001*v[6]*v[3]*v[4] + v[3]*v[5]^2*(0.0001 - 0.00024i) - 0.00001*v[6]*v[3]*v[5] + v[4]^3*(0.00005 + 0.00007i) - v[4]^2*v[5]*(0.00005 + 0.00025i) - 0.00001*v[6]*v[4]^2 - v[4]*v[5]^2*(0.00005 - 0.00025i) + 0.00001*v[6]*v[4]*v[5] + v[5]^3*(0.00005 - 0.00007i) - 0.00001*v[6]*v[5]^2) - 1.0*v[7]*(- v[1]^3*(0.000153368 - 3.3098e-20i) + v[1]^2*(0.126015 + 1.35887e-18i) + v[1]*(1.95201 + 1.68276e-17i)) + v[2]*(- v[1]^3*(0.00000752126 + 0.00000287923i) + v[1]^2*(0.0061892 + 0.00149802i) + v[1]*(0.0833156 - 0.0381373i)) + v[3]*(- v[1]^3*(0.00000752126 - 0.00000287923i) + v[1]^2*(0.0061892 - 0.00149802i) + v[1]*(0.0833156 + 0.0381373i)) - 1.0*v[6]*(v[1]^3*(0.0000237799 - 5.13187e-21i) - v[1]^2*(0.0212456 - 1.57651e-19i) + v[1]*(1.96145 + 1.07687e-17i))

    f[7]=- 5.41329*v[7] - 1.0*v[6]*(v[1]^3*(0.0000032869 - 4.60936e-22i) - v[1]^2*(0.00321098 + 2.22918e-19i) + v[1]*(0.297817 + 2.42034e-17i)) + v[4]*(- v[1]^3*(0.00000139504 - 4.55513e-7i) + v[1]^2*(0.00122348 - 0.000536855i) + v[1]*(0.0383104 - 0.000859108i)) + v[5]*(- v[1]^3*(0.00000139504 + 4.55513e-7i) + v[1]^2*(0.00122348 + 0.000536855i) + v[1]*(0.0383104 + 0.000859108i)) + v[2]*(- v[1]^3*(0.0000010396 + 3.97973e-7i) + v[1]^2*(0.000942262 + 0.000240279i) + v[1]*(0.00883636 - 0.00537487i)) + v[3]*(- v[1]^3*(0.0000010396 - 3.97973e-7i) + v[1]^2*(0.000942262 - 0.000240279i) + v[1]*(0.00883636 + 0.00537487i)) - 1.0*v[7]*(- v[1]^3*(0.0000211988 - 2.9728e-21i) + v[1]^2*(0.0191876 + 1.65108e-18i) + v[1]*(0.29695 + 2.49701e-17i)) + ka2*(0.00138*v[2]^2 + v[2]*v[4]*(0.00261 - 0.00051i) - v[2]*v[5]*(0.00261 + 0.00051i) - v[3]*v[4]*(0.00261 - 0.00051i) + v[2]*v[6]*0.00006i + v[3]*v[5]*(0.00261 + 0.00051i) + v[2]*v[7]*0.00002i - v[3]*v[6]*0.00006i - 0.00256*v[4]*v[5] - v[3]*v[7]*0.00002i + v[4]*v[6]*(0.00001 + 0.00006i) + v[4]*v[7]*0.00002i + v[5]*v[6]*(0.00001 - 0.00006i) - v[5]*v[7]*0.00002i - 0.00276*v[2]*v[3] + 0.00138*v[3]^2 + v[4]^2*(0.00119 - 0.00048i) + v[5]^2*(0.00119 + 0.00048i)) - 1.0*ka3*(v[2]^3*0.00008i - v[2]^2*v[3]*0.00025i + v[2]^2*v[4]*(0.00005 + 0.00023i) + v[2]^2*v[5]*(0.00005 - 0.00023i) - 0.00001*v[6]*v[2]^2 + v[2]*v[3]^2*0.00025i - v[2]*v[3]*v[4]*(0.00009 + 0.00047i) - v[2]*v[3]*v[5]*(0.00009 - 0.00047i) + 0.00001*v[6]*v[2]*v[3] + v[2]*v[4]^2*(0.00009 + 0.00021i) - v[2]*v[4]*v[5]*0.00046i - 0.00001*v[6]*v[2]*v[4] - v[2]*v[5]^2*(0.00009 - 0.00021i) + 0.00001*v[6]*v[2]*v[5] - v[3]^3*0.00008i + v[3]^2*v[4]*(0.00005 + 0.00023i) + v[3]^2*v[5]*(0.00005 - 0.00023i) - 0.00001*v[6]*v[3]^2 - v[3]*v[4]^2*(0.00009 + 0.00021i) + v[3]*v[4]*v[5]*0.00046i + 0.00001*v[6]*v[3]*v[4] + v[3]*v[5]^2*(0.00009 - 0.00021i) - 0.00001*v[6]*v[3]*v[5] + v[4]^3*(0.00004 + 0.00006i) - v[4]^2*v[5]*(0.00004 + 0.00022i) - v[4]*v[5]^2*(0.00004 - 0.00022i) + 0.00001*v[6]*v[4]*v[5] + v[5]^3*(0.00004 - 0.00006i))

    f[2] = f[2]/ω_h; f[3] = f[3]/ω_h; f[4] = f[4]/ω_h; f[5] = f[5]/ω_h; f[6] = f[6]/ω_h; f[7] = f[7]/ω_h

    return calculate_normal_form(f, v, norder, cm, m1, m2, m3, n, ω_h, zero_tol)
end

function example2()
    norder = 3; # Choose the order of power series representation of Centermanifold
    cm = 3; # Dimension of Centermanifold of the dynamical system
    m1 = 1; # Number of the zero eigenvalue corresponding to the center space of the Jacobian
    m2 = 1; # Number of pair of complex conjugate eigenvalue corresponding to the center space of the Jacobian
    m3 = 4; # Dimension of the stable manifold of the dynmical system
    n = m1+2*m2+m3; # Total dimension of the dynamical system
    ω_h = 15.1829; # Eigenvalue that crosses unit circle at Hopf bifurcation point ± ω_h
    zero_tol = 1e-13; # zero tolerance to chop out very small coeffs of symbolic computation

    v = [symbols("v_$i") for i in 1:n]; # Define state-space variables
    f = Array{Basic}(undef, n);

    @vars ka2, ka3, ν  # Define symbolic variables which is needed to define the equation of motion
    # In this example the variables are nonlinear stifness parameters

    # Define the equation of motion at the Hopf bifurcation point where the dimension of centermanifold is 2
    # Let the dimension of control parameter vector be 1, and express it as a one of state-space variable v[1]
    # d/dt v[1]=0 and let rest of the state-space variable be v[2],v[3],…,v[n].

    f[1]=0

    f[1]=0

    f[2]=v[2]*15.1829i + v[4]*(v[1]^3*(0.00000311958 - 0.000034438i) + v[1]^2*(0.00343708 - 0.00109031i) + v[1]*(0.143011 + 0.00225607i)) - 1.0*v[7]*(v[1]^3*(0.000169667 - 0.000348283i) + v[1]^2*(0.009276 - 0.0122307i) + v[1]*(0.154139 - 0.176441i)) - 1.0*ka2*(v[2]*v[3]*(0.00211 + 0.00537i) - v[2]*v[4]*(0.00357 + 0.00452i) + v[2]*v[5]*(0.00047 + 0.00574i) + v[3]*v[4]*(0.00357 + 0.00452i) - v[2]*v[6]*(0.00015 - 0.00006i) - v[3]*v[5]*(0.00047 + 0.00574i) + v[2]*v[7]*(0.00012 - 0.00005i) + v[3]*v[6]*(0.00015 - 0.00006i) + v[4]*v[5]*(0.00211 + 0.00535i) - v[3]*v[7]*(0.00012 - 0.00005i) - v[4]*v[6]*(0.00013 - 0.0001i) + v[4]*v[7]*(0.0001 - 0.00008i) + v[5]*v[6]*(0.00016 - 0.00001i) - v[5]*v[7]*(0.00013 - 0.00001i) - v[2]^2*(0.00106 + 0.00268i) - v[3]^2*(0.00106 + 0.00268i) - v[4]^2*(0.00235 + 0.00165i) + v[5]^2*(0.0006 - 0.00281i)) + v[2]*(v[1]^3*(0.0000207736 - 0.0000108138i) + v[1]^2*(0.00314984 + 0.00104467i) + v[1]*(0.103133 + 0.0572328i)) - 1.0*v[3]*(v[1]^3*(0.00000428964 + 0.0000230234i) + v[1]^2*(0.00240965 + 0.00190241i) + v[1]*(0.120388 + 0.0527565i)) + v[6]*(- v[1]^3*(0.0000259479 - 0.0000532643i) + v[1]^2*(0.00106845 - 0.00323481i) + v[1]*(0.0949347 - 0.0606467i)) - 1.0*v[5]*(- v[1]^3*(0.0000251947 - 0.000023684i) + v[1]^2*(0.000895386 + 0.00299059i) + v[1]*(0.0879635 + 0.11187i)) + ka3*(v[2]*v[3]^2*(0.00044 - 0.00017i) - v[2]^2*v[3]*(0.00044 - 0.00017i) + v[2]*v[4]^2*(0.00027 - 0.00039i) + v[2]^2*v[4]*(0.00037 - 0.00029i) + v[2]*v[5]^2*(0.00046 + 0.0001i) - v[3]*v[4]^2*(0.00027 - 0.00039i) - v[2]^2*v[5]*(0.00047 - 0.00004i) + v[3]^2*v[4]*(0.00037 - 0.00029i) - v[3]*v[5]^2*(0.00046 + 0.0001i) - v[2]^2*v[6]*0.00001i - v[3]^2*v[5]*(0.00047 - 0.00004i) + v[4]*v[5]^2*(0.00047 - 0.00004i) + v[2]^2*v[7]*0.00001i - v[3]^2*v[6]*0.00001i - v[4]^2*v[5]*(0.00037 - 0.00029i) + v[3]^2*v[7]*0.00001i - v[4]^2*v[6]*(0.00001 + 0.00001i) + v[4]^2*v[7]*(0.00001 + 0.00001i) - v[5]^2*v[6]*0.00001i + v[5]^2*v[7]*0.00001i + v[2]^3*(0.00015 - 0.00006i) - v[3]^3*(0.00015 - 0.00006i) + v[4]^3*(0.00005 - 0.00015i) - v[5]^3*(0.00014 + 0.00008i) - v[2]*v[3]*v[4]*(0.00074 - 0.00059i) + v[2]*v[3]*v[5]*(0.00094 - 0.00008i) + v[2]*v[3]*v[6]*(0.00001 + 0.00003i) - v[2]*v[4]*v[5]*(0.00088 - 0.00035i) - v[2]*v[3]*v[7]*(0.00001 + 0.00002i) - v[2]*v[4]*v[6]*(0.00002 + 0.00002i) + v[3]*v[4]*v[5]*(0.00088 - 0.00035i) + v[2]*v[4]*v[7]*(0.00001 + 0.00002i) + v[2]*v[5]*v[6]*0.00003i + v[3]*v[4]*v[6]*(0.00002 + 0.00002i) - v[2]*v[5]*v[7]*0.00002i - v[3]*v[4]*v[7]*(0.00001 + 0.00002i) - v[3]*v[5]*v[6]*0.00003i + v[3]*v[5]*v[7]*0.00002i + v[4]*v[5]*v[6]*(0.00001 + 0.00003i) - v[4]*v[5]*v[7]*(0.00001 + 0.00002i))

    f[3]=- v[3]*15.1829i + v[5]*(v[1]^3*(0.00000311958 + 0.000034438i) + v[1]^2*(0.00343708 + 0.00109031i) + v[1]*(0.143011 - 0.00225607i)) - 1.0*v[7]*(v[1]^3*(0.000169667 + 0.000348283i) + v[1]^2*(0.009276 + 0.0122307i) + v[1]*(0.154139 + 0.176441i)) - 1.0*ka2*(v[2]*v[3]*(0.00211 - 0.00537i) - v[2]*v[4]*(0.00047 - 0.00574i) + v[2]*v[5]*(0.00357 - 0.00452i) + v[3]*v[4]*(0.00047 - 0.00574i) + v[2]*v[6]*(0.00015 + 0.00006i) - v[3]*v[5]*(0.00357 - 0.00452i) - v[2]*v[7]*(0.00012 + 0.00005i) - v[3]*v[6]*(0.00015 + 0.00006i) + v[4]*v[5]*(0.00211 - 0.00535i) + v[3]*v[7]*(0.00012 + 0.00005i) + v[4]*v[6]*(0.00016 + 0.00001i) - v[4]*v[7]*(0.00013 + 0.00001i) - v[5]*v[6]*(0.00013 + 0.0001i) + v[5]*v[7]*(0.0001 + 0.00008i) - v[2]^2*(0.00106 - 0.00268i) - v[3]^2*(0.00106 - 0.00268i) + v[4]^2*(0.0006 + 0.00281i) - v[5]^2*(0.00235 - 0.00165i)) + v[3]*(v[1]^3*(0.0000207736 + 0.0000108138i) + v[1]^2*(0.00314984 - 0.00104467i) + v[1]*(0.103133 - 0.0572328i)) - 1.0*v[2]*(v[1]^3*(0.00000428964 - 0.0000230234i) + v[1]^2*(0.00240965 - 0.00190241i) + v[1]*(0.120388 - 0.0527565i)) + v[6]*(- v[1]^3*(0.0000259479 + 0.0000532643i) + v[1]^2*(0.00106845 + 0.00323481i) + v[1]*(0.0949347 + 0.0606467i)) - 1.0*v[4]*(- v[1]^3*(0.0000251947 + 0.000023684i) + v[1]^2*(0.000895386 - 0.00299059i) + v[1]*(0.0879635 - 0.11187i)) - 1.0*ka3*(v[2]*v[3]^2*(0.00044 + 0.00017i) - v[2]^2*v[3]*(0.00044 + 0.00017i) + v[2]*v[4]^2*(0.00046 - 0.0001i) + v[2]^2*v[4]*(0.00047 + 0.00004i) + v[2]*v[5]^2*(0.00027 + 0.00039i) - v[3]*v[4]^2*(0.00046 - 0.0001i) - v[2]^2*v[5]*(0.00037 + 0.00029i) + v[3]^2*v[4]*(0.00047 + 0.00004i) - v[3]*v[5]^2*(0.00027 + 0.00039i) - v[2]^2*v[6]*0.00001i - v[3]^2*v[5]*(0.00037 + 0.00029i) + v[4]*v[5]^2*(0.00037 + 0.00029i) + v[2]^2*v[7]*0.00001i - v[3]^2*v[6]*0.00001i - v[4]^2*v[5]*(0.00047 + 0.00004i) + v[3]^2*v[7]*0.00001i - v[4]^2*v[6]*0.00001i + v[4]^2*v[7]*0.00001i + v[5]^2*v[6]*(0.00001 - 0.00001i) - v[5]^2*v[7]*(0.00001 - 0.00001i) + v[2]^3*(0.00015 + 0.00006i) - v[3]^3*(0.00015 + 0.00006i) + v[4]^3*(0.00014 - 0.00008i) - v[5]^3*(0.00005 + 0.00015i) - v[2]*v[3]*v[4]*(0.00094 + 0.00008i) + v[2]*v[3]*v[5]*(0.00074 + 0.00059i) - v[2]*v[3]*v[6]*(0.00001 - 0.00003i) - v[2]*v[4]*v[5]*(0.00088 + 0.00035i) + v[2]*v[3]*v[7]*(0.00001 - 0.00002i) - v[2]*v[4]*v[6]*0.00003i + v[3]*v[4]*v[5]*(0.00088 + 0.00035i) + v[2]*v[4]*v[7]*0.00002i - v[2]*v[5]*v[6]*(0.00002 - 0.00002i) + v[3]*v[4]*v[6]*0.00003i + v[2]*v[5]*v[7]*(0.00001 - 0.00002i) - v[3]*v[4]*v[7]*0.00002i + v[3]*v[5]*v[6]*(0.00002 - 0.00002i) - v[3]*v[5]*v[7]*(0.00001 - 0.00002i) - v[4]*v[5]*v[6]*(0.00001 - 0.00003i) + v[4]*v[5]*v[7]*(0.00001 - 0.00002i))

    f[4]=- v[4]*(5.05926 - 16.7804i) + v[7]*(v[1]^3*(0.000397449 - 0.000246334i) + v[1]^2*(0.0177999 - 0.00583005i) + v[1]*(0.280611 - 0.0666978i)) - 1.0*v[4]*(v[1]^3*(0.0000253568 - 0.00003315i) + v[1]^2*(0.00427065 + 0.00114243i) + v[1]*(0.176935 + 0.10919i)) - 1.0*v[5]*(v[1]^3*(0.0000409701 - 0.00000795858i) + v[1]^2*(0.0010276 - 0.00355808i) + v[1]*(0.0149366 - 0.156251i)) - 1.0*ka3*(v[2]*v[3]^2*(0.00006 + 0.00136i) - v[2]^2*v[3]*(0.00006 + 0.00136i) + v[2]*v[4]^2*(0.00079 + 0.0011i) + v[2]^2*v[4]*(0.00045 + 0.00128i) - v[2]*v[5]^2*(0.0007 - 0.00116i) - v[3]*v[4]^2*(0.00079 + 0.0011i) + v[2]^2*v[5]*(0.00034 - 0.00131i) + v[3]^2*v[4]*(0.00045 + 0.00128i) + v[3]*v[5]^2*(0.0007 - 0.00116i) + 0.00004*v[2]^2*v[6] + v[3]^2*v[5]*(0.00034 - 0.00131i) - v[4]*v[5]^2*(0.00034 - 0.00131i) - 0.00003*v[2]^2*v[7] + 0.00004*v[3]^2*v[6] - v[4]^2*v[5]*(0.00044 + 0.00128i) - 0.00003*v[3]^2*v[7] + v[4]^2*v[6]*(0.00003 - 0.00002i) - v[4]^2*v[7]*(0.00002 - 0.00002i) + v[5]^2*v[6]*(0.00003 + 0.00002i) - v[5]^2*v[7]*(0.00003 + 0.00002i) + v[2]^3*(0.00002 + 0.00045i) - v[3]^3*(0.00002 + 0.00045i) + v[4]^3*(0.00036 + 0.00027i) + v[5]^3*(0.00033 - 0.0003i) - v[2]*v[3]*v[4]*(0.00089 + 0.00256i) - v[2]*v[3]*v[5]*(0.00067 - 0.00263i) - 0.00008*v[2]*v[3]*v[6] - v[2]*v[4]*v[5]*(0.00011 + 0.0027i) + 0.00006*v[2]*v[3]*v[7] + v[2]*v[4]*v[6]*(0.00007 - 0.00003i) + v[3]*v[4]*v[5]*(0.00011 + 0.0027i) - v[2]*v[4]*v[7]*(0.00006 - 0.00002i) - v[2]*v[5]*v[6]*(0.00008 + 0.00002i) - v[3]*v[4]*v[6]*(0.00007 - 0.00003i) + v[2]*v[5]*v[7]*(0.00006 + 0.00001i) + v[3]*v[4]*v[7]*(0.00006 - 0.00002i) + v[3]*v[5]*v[6]*(0.00008 + 0.00002i) - v[3]*v[5]*v[7]*(0.00006 + 0.00001i) - 0.00008*v[4]*v[5]*v[6] + 0.00006*v[4]*v[5]*v[7]) - 1.0*v[2]*(v[1]^3*(0.0000281704 + 0.00000233443i) + v[1]^2*(0.00254065 + 0.0031441i) + v[1]*(0.0954459 + 0.135962i)) - 1.0*v[6]*(- v[1]^3*(0.0000607835 - 0.0000376728i) + v[1]^2*(0.00310379 - 0.00271927i) + v[1]*(0.147457 + 0.0138217i)) + ka2*(v[2]^2*(0.00827 - 0.00035i) - v[2]*v[3]*(0.01654 - 0.0007i) + v[2]*v[4]*(0.0156 - 0.00543i) - v[2]*v[5]*(0.016 + 0.0041i) - v[2]*v[6]*(0.00002 + 0.00048i) + v[7]*v[2]*(0.00002 + 0.00036i) + v[3]^2*(0.00827 - 0.00035i) - v[3]*v[4]*(0.0156 - 0.00543i) + v[3]*v[5]*(0.016 + 0.0041i) + v[3]*v[6]*(0.00002 + 0.00048i) - v[7]*v[3]*(0.00002 + 0.00036i) + v[4]^2*(0.00667 - 0.00484i) - v[4]*v[5]*(0.01648 - 0.00069i) - v[4]*v[6]*(0.00016 + 0.00045i) + v[7]*v[4]*(0.00012 + 0.00034i) + v[5]^2*(0.00706 + 0.00426i) - v[5]*v[6]*(0.00012 - 0.00046i) + v[7]*v[5]*(0.00009 - 0.00035i) - 0.00001*v[6]^2 + 0.00001*v[7]*v[6]) + v[3]*(- v[1]^3*(0.0000104436 - 0.0000262669i) + v[1]^2*(0.0011885 + 0.00347674i) + v[1]*(0.0617113 + 0.120387i))

    f[5]=- v[5]*(5.05926 + 16.7804i) + v[7]*(v[1]^3*(0.000397449 + 0.000246334i) + v[1]^2*(0.0177999 + 0.00583005i) + v[1]*(0.280611 + 0.0666978i)) - 1.0*v[5]*(v[1]^3*(0.0000253568 + 0.00003315i) + v[1]^2*(0.00427065 - 0.00114243i) + v[1]*(0.176935 - 0.10919i)) - 1.0*v[4]*(v[1]^3*(0.0000409701 + 0.00000795858i) + v[1]^2*(0.0010276 + 0.00355808i) + v[1]*(0.0149366 + 0.156251i)) - 1.0*ka3*(0.00004*v[2]^2*v[6] + v[2]^2*v[3]*(0.00006 - 0.00136i) + v[2]*v[4]^2*(0.0007 + 0.00116i) + v[2]^2*v[4]*(0.00034 + 0.00131i) - v[2]*v[5]^2*(0.00079 - 0.0011i) - v[3]*v[4]^2*(0.0007 + 0.00116i) + v[2]^2*v[5]*(0.00045 - 0.00128i) + v[3]^2*v[4]*(0.00034 + 0.00131i) + v[3]*v[5]^2*(0.00079 - 0.0011i) - v[2]*v[3]^2*(0.00006 - 0.00136i) + v[3]^2*v[5]*(0.00045 - 0.00128i) - v[4]*v[5]^2*(0.00044 - 0.00128i) - 0.00003*v[2]^2*v[7] + 0.00004*v[3]^2*v[6] - v[4]^2*v[5]*(0.00034 + 0.00131i) - 0.00003*v[3]^2*v[7] + v[4]^2*v[6]*(0.00003 - 0.00002i) - v[4]^2*v[7]*(0.00003 - 0.00002i) + v[5]^2*v[6]*(0.00003 + 0.00002i) - v[5]^2*v[7]*(0.00002 + 0.00002i) - v[2]^3*(0.00002 - 0.00045i) + v[3]^3*(0.00002 - 0.00045i) + v[4]^3*(0.00033 + 0.0003i) + v[5]^3*(0.00036 - 0.00027i) - v[2]*v[3]*v[4]*(0.00067 + 0.00263i) - v[2]*v[3]*v[5]*(0.00089 - 0.00256i) - 0.00008*v[2]*v[3]*v[6] + v[2]*v[4]*v[5]*(0.00011 - 0.0027i) + 0.00006*v[2]*v[3]*v[7] + v[2]*v[4]*v[6]*(0.00008 - 0.00002i) - v[3]*v[4]*v[5]*(0.00011 - 0.0027i) - v[2]*v[4]*v[7]*(0.00006 - 0.00001i) - v[2]*v[5]*v[6]*(0.00007 + 0.00003i) - v[3]*v[4]*v[6]*(0.00008 - 0.00002i) + v[2]*v[5]*v[7]*(0.00006 + 0.00002i) + v[3]*v[4]*v[7]*(0.00006 - 0.00001i) + v[3]*v[5]*v[6]*(0.00007 + 0.00003i) - v[3]*v[5]*v[7]*(0.00006 + 0.00002i) - 0.00008*v[4]*v[5]*v[6] + 0.00006*v[4]*v[5]*v[7]) - 1.0*v[3]*(v[1]^3*(0.0000281704 - 0.00000233443i) + v[1]^2*(0.00254065 - 0.0031441i) + v[1]*(0.0954459 - 0.135962i)) - 1.0*v[6]*(- v[1]^3*(0.0000607835 + 0.0000376728i) + v[1]^2*(0.00310379 + 0.00271927i) + v[1]*(0.147457 - 0.0138217i)) + ka2*(v[2]^2*(0.00827 + 0.00035i) - v[2]*v[3]*(0.01654 + 0.0007i) + v[2]*v[4]*(0.016 - 0.0041i) - v[2]*v[5]*(0.0156 + 0.00543i) + v[2]*v[6]*(0.00002 - 0.00048i) - v[7]*v[2]*(0.00002 - 0.00036i) + v[3]^2*(0.00827 + 0.00035i) - v[3]*v[4]*(0.016 - 0.0041i) + v[3]*v[5]*(0.0156 + 0.00543i) - v[3]*v[6]*(0.00002 - 0.00048i) + v[7]*v[3]*(0.00002 - 0.00036i) + v[4]^2*(0.00706 - 0.00426i) - v[4]*v[5]*(0.01648 + 0.00069i) - v[4]*v[6]*(0.00012 + 0.00046i) + v[7]*v[4]*(0.00009 + 0.00035i) + v[5]^2*(0.00667 + 0.00484i) - v[5]*v[6]*(0.00016 - 0.00045i) + v[7]*v[5]*(0.00012 - 0.00034i) - 0.00001*v[6]^2 + 0.00001*v[7]*v[6]) + v[2]*(- v[1]^3*(0.0000104436 + 0.0000262669i) + v[1]^2*(0.0011885 - 0.00347674i) + v[1]*(0.0617113 - 0.120387i))

    f[6]=- 47.1636*v[6] - 1.0*ka2*(0.00112*v[2]^2 + v[2]*v[4]*(0.00215 - 0.00065i) - v[2]*v[5]*(0.00215 + 0.00065i) - v[3]*v[4]*(0.00215 - 0.00065i) - v[2]*v[6]*0.00006i + v[3]*v[5]*(0.00215 + 0.00065i) + v[2]*v[7]*0.00005i + v[3]*v[6]*0.00006i - 0.00224*v[4]*v[5] - v[3]*v[7]*0.00005i - v[4]*v[6]*(0.00002 + 0.00006i) + v[4]*v[7]*(0.00001 + 0.00005i) - v[5]*v[6]*(0.00002 - 0.00006i) + v[5]*v[7]*(0.00001 - 0.00005i) - 0.00225*v[2]*v[3] + 0.00112*v[3]^2 + v[4]^2*(0.00093 - 0.00062i) + v[5]^2*(0.00093 + 0.00062i)) - 1.0*v[4]*(- v[1]^3*(0.00000809455 - 0.00000307436i) + v[1]^2*(0.00771859 - 0.00375081i) + v[1]*(0.296768 - 0.00813346i)) - 1.0*v[5]*(- v[1]^3*(0.00000809455 + 0.00000307436i) + v[1]^2*(0.00771859 + 0.00375081i) + v[1]*(0.296768 + 0.00813346i)) + v[7]*(- v[1]^3*(0.0000970094 + 4.23489e-20i) + v[1]^2*(0.0959083 + 2.31888e-18i) + v[1]*(1.99451 + 5.6881e-17i)) - 1.0*v[2]*(- v[1]^3*(0.00000471246 + 0.00000349051i) + v[1]^2*(0.004705 + 0.0027992i) + v[1]*(0.0243829 - 0.0146621i)) - 1.0*v[3]*(- v[1]^3*(0.00000471246 - 0.00000349051i) + v[1]^2*(0.004705 - 0.0027992i) + v[1]*(0.0243829 + 0.0146621i)) - 1.0*v[6]*(v[1]^3*(0.000014836 + 6.47659e-21i) - v[1]^2*(0.0160897 + 9.75407e-19i) + v[1]*(1.96279 + 6.86328e-17i)) + ka3*(v[2]*v[3]^2*0.00018i - v[2]^2*v[3]*0.00018i + v[2]*v[4]^2*(0.0001 + 0.00015i) + v[2]^2*v[4]*(0.00005 + 0.00018i) - v[2]*v[5]^2*(0.0001 - 0.00015i) - v[3]*v[4]^2*(0.0001 + 0.00015i) + v[2]^2*v[5]*(0.00005 - 0.00018i) + v[3]^2*v[4]*(0.00005 + 0.00018i) + v[3]*v[5]^2*(0.0001 - 0.00015i) + 0.00001*v[2]^2*v[6] + v[3]^2*v[5]*(0.00005 - 0.00018i) - v[4]*v[5]^2*(0.00005 - 0.00018i) + 0.00001*v[3]^2*v[6] - v[4]^2*v[5]*(0.00005 + 0.00018i) + v[2]^3*0.00006i - v[3]^3*0.00006i + v[4]^3*(0.00005 + 0.00004i) + v[5]^3*(0.00005 - 0.00004i) - v[2]*v[3]*v[4]*(0.00011 + 0.00035i) - v[2]*v[3]*v[5]*(0.00011 - 0.00035i) - 0.00001*v[2]*v[3]*v[6] - v[2]*v[4]*v[5]*0.00037i + 0.00001*v[2]*v[3]*v[7] + 0.00001*v[2]*v[4]*v[6] + v[3]*v[4]*v[5]*0.00037i - 0.00001*v[2]*v[4]*v[7] - 0.00001*v[2]*v[5]*v[6] - 0.00001*v[3]*v[4]*v[6] + 0.00001*v[2]*v[5]*v[7] + 0.00001*v[3]*v[4]*v[7] + 0.00001*v[3]*v[5]*v[6] - 0.00001*v[3]*v[5]*v[7] - 0.00001*v[4]*v[5]*v[6] + 0.00001*v[4]*v[5]*v[7])

    f[7]=- 7.17103*v[7] + v[4]*(- v[1]^3*(0.00000200279 - 7.60675e-7i) + v[1]^2*(0.0010852 - 0.000614867i) + v[1]*(0.0374856 - 0.00440386i)) + v[5]*(- v[1]^3*(0.00000200279 + 7.60675e-7i) + v[1]^2*(0.0010852 + 0.000614867i) + v[1]*(0.0374856 + 0.00440386i)) - 1.0*v[7]*(- v[1]^3*(0.0000240026 + 1.83924e-20i) + v[1]^2*(0.0138481 - 1.01014e-18i) + v[1]*(0.290099 - 1.68169e-17i)) - 1.0*v[2]*(v[1]^3*(0.00000116598 + 8.63639e-7i) - v[1]^2*(0.000684091 + 0.000337026i) + v[1]*(0.000578732 + 0.00526632i)) - 1.0*v[3]*(v[1]^3*(0.00000116598 - 8.63639e-7i) - v[1]^2*(0.000684091 - 0.000337026i) + v[1]*(0.000578732 - 0.00526632i)) + v[6]*(v[1]^3*(0.00000367081 + 2.81283e-21i) - v[1]^2*(0.00246968 + 1.15122e-19i) + v[1]*(0.288282 - 1.04975e-17i)) - 1.0*ka3*(v[2]*v[3]^2*0.00024i - v[2]^2*v[3]*0.00024i + v[2]*v[4]^2*(0.00013 + 0.0002i) + v[2]^2*v[4]*(0.00007 + 0.00023i) - v[2]*v[5]^2*(0.00013 - 0.0002i) - v[3]*v[4]^2*(0.00013 + 0.0002i) + v[2]^2*v[5]*(0.00007 - 0.00023i) + v[3]^2*v[4]*(0.00007 + 0.00023i) + v[3]*v[5]^2*(0.00013 - 0.0002i) + 0.00001*v[2]^2*v[6] + v[3]^2*v[5]*(0.00007 - 0.00023i) - v[4]*v[5]^2*(0.00007 - 0.00022i) - 0.00001*v[2]^2*v[7] + 0.00001*v[3]^2*v[6] - v[4]^2*v[5]*(0.00007 + 0.00022i) - 0.00001*v[3]^2*v[7] + 0.00001*v[4]^2*v[6] + 0.00001*v[5]^2*v[6] + v[2]^3*0.00008i - v[3]^3*0.00008i + v[4]^3*(0.00006 + 0.00005i) + v[5]^3*(0.00006 - 0.00005i) - v[2]*v[3]*v[4]*(0.00014 + 0.00045i) - v[2]*v[3]*v[5]*(0.00014 - 0.00045i) - 0.00001*v[2]*v[3]*v[6] - v[2]*v[4]*v[5]*0.00047i + 0.00001*v[2]*v[3]*v[7] + 0.00001*v[2]*v[4]*v[6] + v[3]*v[4]*v[5]*0.00047i - 0.00001*v[2]*v[4]*v[7] - 0.00001*v[2]*v[5]*v[6] - 0.00001*v[3]*v[4]*v[6] + 0.00001*v[2]*v[5]*v[7] + 0.00001*v[3]*v[4]*v[7] + 0.00001*v[3]*v[5]*v[6] - 0.00001*v[3]*v[5]*v[7] - 0.00001*v[4]*v[5]*v[6] + 0.00001*v[4]*v[5]*v[7]) + ka2*(0.00144*v[2]^2 + v[2]*v[4]*(0.00275 - 0.00083i) - v[2]*v[5]*(0.00275 + 0.00083i) - v[3]*v[4]*(0.00275 - 0.00083i) - v[2]*v[6]*0.00008i + v[3]*v[5]*(0.00275 + 0.00083i) + v[2]*v[7]*0.00006i + v[3]*v[6]*0.00008i - 0.00287*v[4]*v[5] - v[3]*v[7]*0.00006i - v[4]*v[6]*(0.00002 + 0.00008i) + v[4]*v[7]*(0.00002 + 0.00006i) - v[5]*v[6]*(0.00002 - 0.00008i) + v[5]*v[7]*(0.00002 - 0.00006i) - 0.00288*v[2]*v[3] + 0.00144*v[3]^2 + v[4]^2*(0.0012 - 0.00079i) + v[5]^2*(0.0012 + 0.00079i))

    f[2] = f[2]/ω_h; f[3] = f[3]/ω_h; f[4] = f[4]/ω_h; f[5] = f[5]/ω_h; f[6] = f[6]/ω_h; f[7] = f[7]/ω_h;

    return calculate_normal_form(f, v, norder, cm, m1, m2, m3, n, ω_h, zero_tol)
end

end # module


"""
    parsimonious_flux_balance_analysis(
        model::MetabolicModel,
        optimizer;
        modifications = [],
        qp_modifications = [],
        relax_bounds=[1.0, 0.999999, 0.99999, 0.9999, 0.999, 0.99],
    )

Run parsimonious flux balance analysis (pFBA) on the `model`. In short, pFBA
runs two consecutive optimization problems. The first is traditional FBA:
```
max cᵀx = μ
s.t. S x = b
     xₗ ≤ x ≤ xᵤ
```
And the second is a quadratic optimization problem:
```
min Σᵢ xᵢ²
s.t. S x = b
     xₗ ≤ x ≤ xᵤ
     μ = μ⁰
```
Where the optimal solution of the FBA problem, μ⁰, has been added as an
additional constraint. See "Lewis, Nathan E, Hixson, Kim K, Conrad, Tom M,
Lerman, Joshua A, Charusanti, Pep, Polpitiya, Ashoka D, Adkins, Joshua N,
Schramm, Gunnar, Purvine, Samuel O, Lopez‐Ferrer, Daniel, Weitz, Karl K, Eils,
Roland, König, Rainer, Smith, Richard D, Palsson, Bernhard Ø, (2010) Omic data
from evolved E. coli are consistent with computed optimal growth from
genome‐scale models. Molecular Systems Biology, 6. 390. doi:
accession:10.1038/msb.2010.47" for more details.

pFBA gets the model optimum by standard FBA (using
[`flux_balance_analysis`](@ref) with `optimizer` and `modifications`), then
finds a minimal total flux through the model that still satisfies the (slightly
relaxed) optimum. This is done using a quadratic problem optimizer. If the
original optimizer does not support quadratic optimization, it can be changed
using the callback in `qp_modifications`, which are applied after the FBA.

Thhe optimum relaxation sequence can be specified in `relax` parameter, it
defaults to multiplicative range of `[1.0, 0.999999, ..., 0.99]` of the
original bound.

Returns an optimized model that contains the pFBA solution; or `nothing` if the
optimization failed.

# Example
```
optimizer = Gurobi.Optimizer
atts = Dict("OutputFlag" => 0)
model = load_model(StandardModel, "iJO1366.json")
biomass = findfirst(model.reactions, "BIOMASS_Ec_iJO1366_WT_53p95M")
sol = pfba(model, biomass, Gurobi.optimizer)
```
"""
function parsimonious_flux_balance_analysis(
    model::MetabolicModel,
    optimizer;
    modifications = [],
    qp_modifications = [],
    relax_bounds = [1.0, 0.999999, 0.99999, 0.9999, 0.999, 0.99],
)
    # Run FBA
    opt_model = flux_balance_analysis(model, optimizer; modifications = modifications)
    is_solved(opt_model) || return nothing # FBA failed

    # get the objective
    Z = objective_value(opt_model)
    original_objective = COBREXA.JuMP.objective_function(opt_model)

    # prepare the model for pFBA
    for mod in qp_modifications
        mod(model, opt_model)
    end

    # add the minimization constraint for total flux
    v = opt_model[:x] # fluxes
    @objective(opt_model, Min, sum(dot(v, v)))

    for rb in relax_bounds
        lb, ub = objective_bounds(rb)(Z)
        @_models_log @info "pFBA step relaxed to [$lb,$ub]"
        @constraint(opt_model, pfba_constraint, lb <= original_objective <= ub)

        optimize!(opt_model)
        is_solved(opt_model) && break

        COBREXA.JuMP.delete(opt_model, pfba_constraint)
        COBREXA.JuMP.unregister(opt_model, :pfba_constraint)
    end

    is_solved(opt_model) || return nothing # pFBA failed

    return opt_model
end

"""
    parsimonious_flux_balance_analysis_vec(args...; kwargs...)

Perform parsimonious flux balance analysis on `model` using `optimizer`.
Returns a vector of fluxes in the same order as the reactions in `model`.
Arguments are forwarded to [`parsimonious_flux_balance_analysis`](@ref) internally.
"""
function parsimonious_flux_balance_analysis_vec(args...; kwargs...)
    opt_model = parsimonious_flux_balance_analysis(args...; kwargs...)

    isnothing(opt_model) && return nothing

    return value.(opt_model[:x])
end

"""
    parsimonious_flux_balance_analysis_dict(model::MetabolicModel, args...; kwargs...)

Perform parsimonious flux balance analysis on `model` using `optimizer`.
Returns a dictionary mapping the reaction IDs to fluxes.
Arguments are forwarded to [`parsimonious_flux_balance_analysis`](@ref) internally.
"""
function parsimonious_flux_balance_analysis_dict(model::MetabolicModel, args...; kwargs...)
    opt_fluxes = parsimonious_flux_balance_analysis_vec(model, args...; kwargs...)

    isnothing(opt_fluxes) && return nothing

    return Dict(zip(reactions(model), opt_fluxes))
end


module accesorios

using Unitful

"""
    entrada_recta(m="Rennels")

Devuelve el coeficiente de pérdida para una entrada afilada a una
tubería. Hay seis fuentes disponibles; cuatro de ellas recomiendan
K = 0.5, el más reciente método "Rennels" recomienda K = 0.57, y el
método "Miller" recomienda de K = 0.51, leído en un gráfico.

![sdsdsd](assets/entrada_recta.png#thumbnail)

---

**Los campos de entrada son:**
- `m :: String, opcional `

Es el metodo a usar, uno de Rennels, Swamee, Blevins, Crane, Idelchik y Miller

---

**Salida:**
- `K :: Float `
Coeficiente de perdida `K`

"""
function entrada_recta(m="Rennels")
    if m==="Rennels"
        0.57
    elseif m in ("Swamee", "Blevins", "Crane", "Idelchik")
        0.5
    elseif m==="Miller"
        0.5092676683721356
    else
        error("Los métodos validos son: Rennels, Swamee, Blevins, Crane, Idelchik y Miller")
    end
end

end


using Plasmo
using Plots, PlasmoPlots
gr()

graph1 = OptiGraph()

@optinode(graph1,n1)
@variable(n1, x >= 0)
@variable(n1, y >= 2)
@constraint(n1,x + y >= 3)
@objective(n1, Min, y)

@optinode(graph1,n2)
@variable(n2,x >= 0)
@variable(n2, y >= 2)
@constraint(n2,x + y >= 3)
@objective(n2, Min, y)

@optinode(graph1,n3)
@variable(n3,x >= 0)
@variable(n3, y >= 2)
@constraint(n3,x + y >= 3)
@objective(n3, Min, y)

@linkconstraint(graph1, n1[:x] + n2[:x] + n3[:x] == 3)

graph2 = OptiGraph()

@optinode(graph2,n4)
@variable(n4, x >= 0)
@variable(n4, y >= 2)
@constraint(n4,x + y >= 5)
@objective(n4, Min, y)

@optinode(graph2,n5)
@variable(n5, x >= 0)
@variable(n5, y >= 2)
@constraint(n5,x + y >= 5)
@objective(n5, Min, y)

@optinode(graph2,n6)
@variable(n6, x >= 0)
@variable(n6, y >= 2 )
@constraint(n6,x + y >= 5)
@objective(n6, Min, y)

@linkconstraint(graph2, n4[:x] + n5[:x] + n6[:x] == 5)

graph3 = OptiGraph()

@optinode(graph3,n7)
@variable(n7, x >= 0)
@variable(n7, y >= 2)
@constraint(n7,x + y >= 7)
@objective(n7, Min, y)

@optinode(graph3,n8)
@variable(n8, x >= 0)
@variable(n8, y >= 2)
@constraint(n8,x + y >= 7)
@objective(n8, Min, y)

@optinode(graph3,n9)
@variable(n9,x >= 0)
@variable(n9, y >= 2)
@constraint(n9,x + y >= 7)
@objective(n9, Min, y)

@linkconstraint(graph3, n7[:x] + n8[:x] + n9[:x] == 7)

graph0 = OptiGraph()
@optinode(graph0,n0)
@variable(n0,x)
@constraint(n0,x >= 0)

add_subgraph!(graph0,graph1)
add_subgraph!(graph0,graph2)
add_subgraph!(graph0,graph3)
@linkconstraint(graph0,n3[:x] + n5[:x] + n7[:x] == 10)

@linkconstraint(graph0,n0[:x] + n3[:x] == 3)
@linkconstraint(graph0,n0[:x] + n5[:x] == 5)
@linkconstraint(graph0,n0[:x] + n7[:x] == 7)

for (i,node) in enumerate(all_nodes(graph0))
    node.label = "n$i"
end

plt_graph2 = layout_plot(graph0,node_labels = true,markersize = 60,labelsize = 30,linewidth = 4,subgraph_colors = true,
layout_options = Dict(:tol => 0.001,:C => 2, :K => 4, :iterations => 5));

plt_matrix2 = matrix_plot(graph0,node_labels = true,subgraph_colors = true,markersize = 16);


"""
    FeMat{T,S}

Term with an explicit, constant matrix representation

# Fields
* `x`: matrix
* `wtx`: weighted matrix
* `piv`: pivot `Vector{Int}`` for pivoted Cholesky factorization of `wtx'wtx`
* `rank`: computational rank of `x`
* `cnames`: vector of column names
"""
mutable struct FeMat{T,S<:AbstractMatrix}
    x::S
    wtx::S
    piv::Vector{Int}
    rank::Int
    cnames::Vector{String}
end

function FeMat(X::AbstractMatrix, cnms)
    T = eltype(X)
    if size(X,2) > 0
        ch = statscholesky(Symmetric(X'X))
        pivot = ch.piv
        rank = ch.rank
        Xp = all(pivot .== 1:size(X, 2)) ? X : X[:, ch.piv]
        if rank < length(pivot)
            @warn "fixed-effects matrix is rank deficient"
        end
    else
        # although it doesn't take long for an empty matrix,
        # we can still skip the Cholesky step, which gets the rank
        # wrong anyway
        pivot = Int[]
        Xp = X
        rank = 0
    end
    # ch.rank is wrong for empty FE
    FeMat{T,typeof(X)}(Xp, Xp, pivot, rank, cnms[pivot])
end

function reweight!(A::FeMat{T}, sqrtwts::Vector{T}) where {T}
    if !isempty(sqrtwts)
        if A.x === A.wtx
            A.wtx = similar(A.x)
        end
        mul!(A.wtx, Diagonal(sqrtwts), A.x)
    end
    A
end

Base.adjoint(A::FeMat) = Adjoint(A)

Base.eltype(A::FeMat{T}) where {T} = T

fullrankwtx(A::FeMat) = rank(A) == size(A, 2) ? A.wtx : A.wtx[:, 1:rank(A)]

Base.length(A::FeMat) = length(A.wtx)

LinearAlgebra.rank(A::FeMat) = A.rank

Base.size(A::FeMat) = size(A.wtx)

Base.size(A::Adjoint{T,<:FeMat{T}}) where {T} = reverse(size(A.parent))

Base.size(A::FeMat, i) = size(A.wtx, i)

Base.copyto!(A::FeMat{T}, src::AbstractVecOrMat{T}) where {T} = copyto!(A.x, src)

*(adjA::Adjoint{T,<:FeMat{T}}, B::FeMat{T}) where {T} =
    fullrankwtx(adjA.parent)' * fullrankwtx(B)

LinearAlgebra.mul!(R::StridedVecOrMat{T}, A::FeMat{T}, B::StridedVecOrMat{T}) where {T} =
    mul!(R, A.x, B)

LinearAlgebra.mul!(C, adjA::Adjoint{T,<:FeMat{T}}, B::FeMat{T}) where {T} =
    mul!(C, fullrankwtx(adjA.parent)', fullrankwtx(B))

"""
    isfullrank(A::FeMat)

Does `A` have full column rank?
"""
isfullrank(A::FeMat) = A.rank == length(A.piv)

nθ(A::FeMat) = 0


include("differential/differential.jl")
include("mean.jl")
include("permutation.jl")


#=
This file is part of the replication code for: Hasenzagl, T., Pellegrino, F., Reichlin, L., & Ricco, G. (2020). A Model of the Fed's View on Inflation.
Please cite the paper if you are using any part of the code for academic work (including, but not limited to, conference and peer-reviewed papers).
=#

function get_logjacobian(θ_unb::Array{Float64,1}, MIN::Array{Float64,1}, MAX::Array{Float64,1}, opt_transf::Array{Int64, 1})

# ----------------------------------------------------------------------------------------------------------------------
# Logjacobian
# - Transformation: 1 natural logarithm, 2 no transformations, 3 generalized logit
# ----------------------------------------------------------------------------------------------------------------------

     i1 = opt_transf .== 1;
     i2 = opt_transf .== 2;
     i3 = opt_transf .== 3;

     # Log Jacobian
     JJ     = zeros(size(θ_unb));
     JJ[i1] = θ_unb[i1];
     JJ[i2] .= 0;
     JJ[i3] = log.(MAX[i3]-MIN[i3]) .+ θ_unb[i3] .- 2*log.(1 .+ exp.(θ_unb[i3]));

     return JJ;
end


export FSimGate, fsim_block

"""
    FSimGate{T<:Number} <: PrimitiveBlock{2}

The two parameter `FSim` gate.

References
-------------------------
* Arute, Frank, et al. "Quantum supremacy using a programmable superconducting processor." Nature 574.7779 (2019): 505-510.
"""
mutable struct FSimGate{T<:Number} <: PrimitiveBlock{2}
    theta::T
    phi::T
end
YaoBlocks.nqudits(fs::FSimGate) = 2

function Base.:(==)(fs1::FSimGate, fs2::FSimGate)
    return fs1.theta == fs2.theta && fs1.phi == fs2.phi
end

function YaoAPI.mat(::Type{T}, fs::FSimGate) where T
    θ, ϕ = fs.theta, fs.phi
    T[1 0          0          0;
     0 cos(θ)     -im*sin(θ) 0;
     0 -im*sin(θ) cos(θ)     0;
     0 0          0          exp(-im*ϕ)]
end

YaoAPI.iparams_eltype(::FSimGate{T}) where T = T
YaoAPI.getiparams(fs::FSimGate{T}) where T = (fs.theta, fs.phi)
function YaoAPI.setiparams!(fs::FSimGate{T}, θ, ϕ) where T
    fs.theta = θ
    fs.phi = ϕ
    return fs
end

YaoBlocks.@dumpload_fallback FSimGate FSimGate
YaoBlocks.Optimise.to_basictypes(fs::FSimGate) = fsim_block(fs.theta, fs.phi)

"""
    fsim_block(θ::Real, ϕ::Real)

The circuit representation of FSim gate.
"""
function fsim_block(θ::Real, ϕ::Real)
    if θ ≈ π/2
        return cphase(2,2,1,-ϕ)*SWAP*rot(kron(Z,Z), -π/2)*put(2,1=>phase(-π/4))
    else
        return cphase(2,2,1,-ϕ)*rot(SWAP,2*θ)*rot(kron(Z,Z), -θ)*put(2,1=>phase(θ/2))
    end
end


#=
Created on August 2019
Last update: Tuesday 01 October 2019

@author: Michiel Stock
michielfmstock@gmail.com

Methods for fitting a utility matrix to an observed interaction network.
=#

using Zygote, Optim

# regularization methods
abstract type Regularization end

struct L1 <: Regularization end
struct L2 <: Regularization end
struct ElasticNet <: Regularization
    α::Real
    function ElasticNet(α::Real=0.5)
        @assert α ≥ 0 && α ≤ 1
        return new(α)
    end
end
struct Exp <: Regularization end
struct Entropic <: Regularization
    offset::Real
end

Entropic() = Entropic(0.0)

r(x, reg::L1) = sum(abs.(x))
r(x, reg::L2) = sum(x.^2)
r(x, reg::ElasticNet) = reg.α * sum(abs.(x)) + (1 - reg.α) * sum(x.^2)
r(x, reg::Exp) = sum(exp.(x))
r(x, reg::Entropic) = sum(x .* log.(x .+ reg.offset))

function _fitM(P::AbstractMatrix; γ=1e-2, reg::Regularization=L2())

    l = M -> relative_entropy(P, optimaltransport(M)) + γ * r(M, reg)
    dM = similar(P)
    ∇l!(dM, M) = dM .= l'(M)
    return Optim.minimizer(optimize(l, ∇l!, zero(P), BFGS()))
end

function _fitM(P::AbstractMatrix, a::AbstractVector; γ=1e-2, reg::Regularization=L2())

    l = M -> relative_entropy(P, optimaltransport(M, a)) + γ * r(M, reg)
    dM = similar(P)
    ∇l!(dM, M) = dM .= l'(M)
    return Optim.minimizer(optimize(l, ∇l!, zero(P), BFGS()))
end

function _fitM(P, a::AbstractVector, b::AbstractVector; γ=1e-2,
                                    reg::Regularization=L2(),
                                    maxitter::Int=100, ϵ::Real=1e-5)

    l = M -> relative_entropy(P, optimaltransport(M, a, b; maxitter=maxitter,
                                        ϵ=ϵ)) + γ * r(M, reg)
    dM = similar(P)
    ∇l!(dM, M) = dM .= l'(M)
    return Optim.minimizer(optimize(l, ∇l!, zero(P), BFGS()))
end

function _fitMnograd(P, a::AbstractVector, b::AbstractVector; γ=1e-2,
                                    reg::Regularization=L2(),
                                    maxitter::Int=100, ϵ::Real=1e-5)

    l = M -> relative_entropy(P, optimaltransport(M, a, b; maxitter=maxitter,
                                        ϵ=ϵ)) + γ * r(M, reg)
    dM = similar(P)
    return Optim.minimizer(optimize(l, zero(P), BFGS()))
end

"""
    fitM(P::AbstractMatrix; fix_a::Bool, fix_b::Bool,
                    γ=1e-2,
                    reg::Regularization=L2(),
                    maxitter::Int=100, ϵ::Real=1e-5)

Fit a utility matrix `M` to an observed interaction frequency matrix `P`.
The parameters `fix_a` and `fix_b` determine whether the marginals should be
fixed.
"""
function fitM(P::AbstractMatrix; fix_a::Bool=true, fix_b::Bool=true,
                    γ=1e-2,
                    reg::Regularization=L2(),
                    maxitter::Int=100, ϵ::Real=1e-5)
    a, b = marginals(P)
    if fix_a && fix_b
        return _fitM(P, a, b, reg=reg, γ=γ, maxitter=maxitter, ϵ=ϵ)
    elseif fix_a
        return _fitM(P, a, reg=reg, γ=γ)
    elseif fix_b
        return _fitM(P', b, reg=reg, γ=γ)'
    else
        return _fitM(P, reg=reg, γ=γ)
    end
end

# loss over several observed frequency matrices
function global_loss(M, Ps::Array{M, 1} where M <: AbstractMatrix,
                fix_a::Bool, fix_b::Bool;
                γ=1e-2,
                reg::Regularization=L2(),
                maxitter::Int=10,
                ϵ::Real=1e-5)
    l = 0.0
    for P in Ps
        a, b = marginals(P)
        Q = optimaltransport(M, fix_a ? a : nothing,
                                fix_b ? b : nothing,
                                ϵ=ϵ, maxitter=maxitter)
        l += relative_entropy(P, Q)
    end
    l += γ * r(M, reg)
    return l
end

"""
    parallel_cross_entropy(M, A, B, Ps; maxitter::Integer=20)

Compute the cross-entropy compared to optimal transport on a series of interaction
distributions.
"""
function parallel_cross_entropy(M::AbstractMatrix,
            Ps::Array{M, 1} where M <: AbstractMatrix;
            A::AbstractMatrix,
            B::AbstractMatrix,
            maxitter::Integer=20)
    K = exp.(M)
    # perform Sinkhorn iterations
    U, V = A, B
    for itter in 1:maxitter
        U = A ./ (K * V)  # fix row marginals
        V = B ./ (K' * U)  # fix column marginals
    end
    # compute cross-entropy
    c = 0
    for Pₒ in Ps
        c -= sum(Pₒ .* M)
    end
    c -= sum(A .* log.(U))
    c -= sum(B .* log.(V))
    return c
end

function parallel_cross_entropy_A(M::AbstractMatrix,
            Ps::Array{M, 1} where M <: AbstractMatrix;
            A::AbstractMatrix)
    K = exp.(M)
    U = A ./ sum(K, dims=2)  # fix row marginals
    # compute cross-entropy
    c = 0.0
    for Pₒ in Ps
        c -= sum(Pₒ .* M)
    end
    c -= sum(A .* log.(U))
    return c
end

function parallel_cross_entropy_B(M::AbstractMatrix,
            Ps::Array{M, 1} where M <: AbstractMatrix;
            B::AbstractMatrix)
    K = exp.(M)
    V = B ./ sum(K', dims=2)  # fix row marginals
    # compute cross-entropy
    c = 0.0
    for Pₒ in Ps
        c -= sum(Pₒ .* M)
    end
    c -= sum(B .* log.(V))
    return c
end

function parallel_cross_entropy_free(M::AbstractMatrix,
            Ps::Array{M, 1} where M <: AbstractMatrix)
    logδ = exp.(M) |> sum |> log
    c = 0.0
    for Pₒ in Ps
        c -= sum(Pₒ .* M) - logδ
    end
    return c
end

"""
    fitM(Ps::Array{M, 1} where M <: AbstractMatrix,
                    fix_a::Bool, fix_b::Bool,
                    γ=1e-2,
                    reg::Regularization=L2(),
                    maxitter::Int=100, ϵ::Real=1e-5)

Fit a utility matrix `M` to a collection of observed probability matrices `Ps=[Pi]`.
"""
function fitM(Ps::Array{M, 1} where M <: AbstractMatrix; fix_a=true, fix_b=true,
                γ=1e-2,
                reg::Regularization=L2(),
                maxitter::Int=20)
    # compute marginals
    A, B = marginals(Ps)
    if fix_a && fix_b
        l = M -> parallel_cross_entropy(M, Ps, A=A, B=B, maxitter=maxitter) + γ * r(M, reg)
    elseif fix_a
        l = M -> parallel_cross_entropy_A(M, Ps, A=A) + γ * r(M, reg)
    elseif fix_b
        l = M -> parallel_cross_entropy_B(M, Ps, B=B) + γ * r(M, reg)
    else
        l = M -> parallel_cross_entropy_free(M, Ps) + γ * r(M, reg)
    end
    dM = similar(first(Ps))
    ∇l!(dM, M) = dM .= l'(M)
    return Optim.minimizer(optimize(l, ∇l!, 0.01randn(size(first(Ps))), BFGS()))
end

using StatsBase: sample

"""
    fitMsgd(Ps::Array{M, 1} where M <: AbstractMatrix;
                    fix_a::Bool, fix_b::Bool,
                    γ=1e-2,
                    reg::Regularization=L2(),
                    maxitter::Int=20,
                    ϵ::Real=1e-4,
                    α::Real=0.1,
                    β::Real=0.8,
                    nsteps::Real=100)

Fit a utility matrix `M` to a collection of observed probability matrices
`Ps=[Pi]` using stochastic gradient descent with momentum.
The parameters `fix_a` and `fix_b` determine whether the marginals should be
fixed.
"""
function fitMsgd(Ps::Array{M, 1} where M <: AbstractMatrix;
                fix_a::Bool, fix_b::Bool,
                γ=1e-2,
                reg::Regularization=L2(),
                maxitter::Int=20,
                ϵ::Real=1e-4,
                α::Real=0.1,
                β::Real=0.8,
                nsteps::Real=100)  # momentum parameter)
    global_loss = M -> global_loss(M, Ps, fix_a, fix_b, γ=γ, reg=reg,
                                    maxitter=maxitter, ϵ=ϵ)
    losses = zeros(nsteps)
    ΔM = similar(first(Ps))
    fill!(ΔM, 0.0)
    M = zeros(size(ΔM)...)
    for i in 1:nsteps
        losses[i] = global_loss(M)
        for rep in 1:length(Ps)
            P = sample(Ps)
            a, b = marginals(P)
            l = M -> relative_entropy(P,
                    optimaltransport(M, fix_a ? a : nothing,
                            fix_b ? b : nothing,
                            maxitter=maxitter, ϵ=ϵ)) + (γ / length(Ps)) * r(M, reg)
            ΔM .*= 1 - β
            ΔM .+= α * l'(M)
            M .-= ΔM
        end
    end
    return M, losses
end


const τ = 1.7724538509055159

function derivatives1!( β, Xh, xh, h, z, zr, g, dg )
    for t ∈ eachindex( xh )
        v = Xh .- xh[t]
        u = v[ -z[t] .≤ v .≤ zr[t] ]
        left = right = zero( eltype( β ) )
        for i ∈ eachindex( u )
            left += dg( u[i], 1, z[t], zr[t] )
            right += g( u[i], 1, z[t], zr[t] )
        end
        β[t,1] = - left / ( right * h )
    end
    return nothing
end

function derivativesmulti!( β, S, Xh, xh, h, z, zr, g, dg )
    mult = [ h^(s-1) / factorial(s-1) for s ∈ 1:S ]
    for t ∈ eachindex( xh )
        v = Xh .- xh[t]
        u = v[ -z[t] .≤ v .≤ zr[t] ]
        Tp = eltype( β )
        left = zeros( Tp, S ); right = zeros( Tp, S, S )
        for j ∈ 1:S
            for i ∈ eachindex( u )
                left[j] += dg( u[i], j, z[t], zr[t] )
                Rij = g( u[i], j, z[t], zr[t] )
                for s ∈ 1:S
                    right[j,s] += Rij * u[i]^(s-1) * mult[s]
                end
            end
        end
        β[t,:] = - ( right \ left ) / h
    end
    return nothing
end

function common( X, x, S, h, minx, maxx, anal )
    sanity_checks_basic( X, x, S, h, minx, maxx )
    Xh = ( X .- minx ) / h;  xh = ( x .- minx ) / h;  maxxh = (maxx - minx ) / h
    Tp = eltype( X )
    z = min.(xh, one( Tp ) );  zr = min.(maxxh .- xh, one( Tp ) )
    anal && sanity_checks_g( g, S, z, zl, zr )
    β = Array{Tp}(undef, length(x), S)
    return ( β, Xh, xh, z, zr, S )
end

"""
    β = logdensityderivatives( X, x, h; various optional arguments )

    Computes estimates of the derivatives of the log density of the data
    in the vector X evaluated at the elements in the vector x, using the
    bandwidth h. Returns a matrix β with rows corresponding to the
    evaluation points and columns to the derivatives.  This is a matrix
    even if S = 1.

# Mandatory arguments
- `X::Vector{T}`: vector of data
- `x::Vector{T}`: vector of evaluation points
- `h::T`: bandwidth

T is a floating point type

# Optional arguments
- `g::Function=g`: the function g to be used (see Pinkse and Schurter)
- `dg::Function=dg`: its derivative
- `S::Int=1`: the number of derivatives to be computed
- `minx::T=0.0`: the left hand side of the support
- `maxx::T=Inf`: the right hand side of the support
- `anal::Bool=true`: whether to run many sanity checks
"""
function logdensityderivatives(
    X::Vector{Tp},                    # data
    x::Vector{Tp},                    # evaluation points
    h::Tp;                            # bandwidth
    g = g,                            # g-function in paper
    dg = dg,                          # its derivative
    S = 1,                            # degree
    minx = 0.0,                       # bottom of support
    maxx = Inf,                       # top of support
    anal = true                       # whether to be fussy about checking arguments
    ) where {Tp<:Real}

    ( β, Xh, xh, z, zr, S ) = common( X, x, S, h, minx, maxx, anal )
    if S == 1
        derivatives1!( β, Xh, xh, h, z, zr, g, dg )
    else
        derivativesmulti!( β, S, Xh, xh, h, z, zr, g, dg )
    end
    return β
end

denfunc( x, c ) = 0.75 * ((c == 0.0) ? x - x^3 / 3.0 : exp( c * x ) * ( c*c * (1.0-x*x) + 2.0 * c * x - 2.0 ) / c^3)
function denfunc(x, a, b )
    b==0.0 && return denfunc( x, a )
    if b > 0.0
        sqrtb = sqrt(b)
        γ = 0.5 * (a + 2.0*b*x) / sqrtb
        return 0.75 * (- 0.125 * exp(-0.25*a*a/b) * ( τ * (a*a - 2.0 * b * (2.0*b+1.0) ) * erfi(γ) -
            2 * sqrtb * exp(γ*γ) * (a-2.0*b * x) ) / sqrtb^5)
    end
    b = -b
    sqrtb = sqrt(b)
    0.75 * (exp( x * (a-b*x)) * ( a + 2.0 * b *x ) * 0.25 / (b*b) - τ  * ( a * a + 2.0 * (1.0 - 2.0 * b ) * b) * exp( a*a *0.25/b) * erf( ( b*x -0.5*a ) / sqrtb ) * 0.125 / sqrtb^5)
end

"""
    (logf, β) = logdensity( X, x, h; various optional arguments )

    Computes estimates of the log density and its derivatives of the data
    in the vector X evaluated at the elements in the vector x, using the
    bandwidth h. Returns a tuple ( logf, β ) where logf is a vector whose
    elements correspond to the evaluation points and β a matrix with rows
    corresponding to the evaluation points and columns to the derivatives;
    this is a matrix even if S = 1.

# Mandatory arguments
- `X::Vector{T}`: vector of data
- `x::Vector{T}`: vector of evaluation points
- `h::T`: bandwidth

T is a floating point type

# Optional arguments
- `g::Function=g`: the function g to be used (see Pinkse and Schurter)
- `dg::Function=dg`: its derivative
- `S::Int=1`: the number of derivatives to be computed
- `minx::T=0.0`: the left hand side of the support
- `maxx::T=Inf`: the right hand side of the support
- `logf::Bool=true`: whether the log density itself should be computed
- `mz::Function=LogDensity.epanechnikov`: the kernel to be used for the log density itself
- `anal::Bool=true`: whether to run many sanity checks

Other choices for the kernel are:
LogDensity.quartic
LogDensity.gaussian
LogDensity.triweight
LogDensity.uniform
LogDensity.cosinus
"""
function logdensity(
    X::Vector{Tp},                    # data
    x::Vector{Tp},                    # evaluation points
    h::Tp;                            # bandwidth
    g = g,                            # g-function in paper
    dg = dg,                          # its derivative
    S = 1,                            # degree
    minx = 0.0,                       # bottom of support
    maxx = Inf,                       # top of support
    logf = true,                      # whether to provide log density function estimates
    mz::Function = epanechnikov,      # kernel used to provide estimates of f
    anal = true                       # whether to be fussy about checking arguments
    ) where {Tp<:Real}

    ( β, Xh, xh, z, zr, S ) = common( X, x, S, h, minx, maxx, anal )
    if S == 1
        derivatives1!( β, Xh, xh, h, z, zr, g, dg )
    else
        derivativesmulti!( β, S, Xh, xh, h, z, zr, g, dg )
    end

    logf || return ( nothing, β )

    N = length( X )
    num = zeros( Tp, length( xh ) )
    for t ∈ eachindex( xh )
        v = Xh .- xh[t]
        u = v[ -z[t] .≤ v .≤ zr[t] ]
        num[t] = sum( mz( u[i] ) for i ∈ eachindex( u ) ) / ( N * h )
    end

    if mz == epanechnikov && S ≤ 2
        if S == 1
            for t ∈ eachindex( x )
                num[t] /= ( denfunc( zr[t], β[t,1] * h ) - denfunc( -z[t], β[t,1] * h ) )
            end
        else
            for t ∈ eachindex( x )
                num[t] /= ( denfunc( zr[t],  β[t,1] * h, β[t,2] * h^2 * 0.5 ) - denfunc( -z[t], β[t,1] * h, β[t,2] * h^2 * 0.5 ) )
            end
        end
    else
        fac = [ factorial( s ) for s ∈ 1 : S ]
        for t ∈ eachindex( x )
            function numint( xx )
                mz(xx) * exp( sum( β[t,s] * (xx * h)^s / fac[s] for s = 1:S ) )
            end
            denom = try quadgk(numint, -z[t], zr[t], order = 15)[1]
                    catch e
                        jassert( false. "integration failure in denominator" )
                    end
            num[t] /= denom
        end
    end
    return ( log.( num ), β )
end


using NonlinearTestProblems
using Test

const SQUARE_PROBLEMS = [F_NWp281(), Rosenbrock(), PowellSingular(), PowellBadlyScaled(),
                         HelicalValley()]

const RECTANGULAR_PROBLEMS = [Beale()]

@testset "basic consistency checks for test functions." begin
    for f in vcat(SQUARE_PROBLEMS, RECTANGULAR_PROBLEMS)
        x0 = root(f)
        @test length(x0) == domain_dimension(f)
        @test all(lower_bounds(f) .≤ x0 .≤ upper_bounds(f))
        fx0 = @inferred f(x0)
        @test length(fx0) == range_dimension(f)
        @test sum(abs2, fx0) ≤ eps()

        x1 = starting_point(f)
        @test length(x1) == domain_dimension(f)
        @test sum(abs2, f(x1)) > eps()

        @test starting_point(f, 0) ≈ x1
        x2 = starting_point(f, 1)
        @test sum(abs2, x2 .- x0) ≥ sum(abs2, x1 .- x0)
    end
end


## spectral.jl : Gaussian random field generator based on the spectral (eigenvalue) decomposition of the covariance matrix

"""
    Spectral <: GaussianRandomFieldGenerator

A [`GaussiandRandomFieldGenerator`](@ref) based on a spectral (eigenvalue) decomposition of the covariance matrix.

# Examples
```jldoctest
julia> m = Matern(0.1,1.0)
Matérn (λ=0.1, ν=1.0, σ=1.0, p=2.0)

julia> c = CovarianceFunction(2,m)
2d Matérn covariance function (λ=0.1, ν=1.0, σ=1.0, p=2.0)

julia> pts1 = 0:0.02:1; pts2 = 0:0.02:1
0.0:0.02:1.0

julia> grf = GaussianRandomField(c,Spectral(),pts1,pts2)
Gaussian random field with 2d Matérn covariance function (λ=0.1, ν=1.0, σ=1.0, p=2.0) on a 51x51 structured grid, using a Spectral decomposition

julia> plot(grf)
[...]

```
This is also useful when computing Gaussian random fields on a Finite Element mesh using a truncated KL expansion. Here's an example that computes the first 10 eigenfunctions on an L-shaped domain.

```jldoctest
julia> p, t = Lshape();

julia> grf = grf = GaussianRandomField(CovarianceFunction(2,Matern(0.2,1.0)),Spectral(),p,t,n=10)
Gaussian random field with 2d Matérn covariance function (λ=0.2, ν=1.0, σ=1.0, p=2.0) on a mesh with 998 points and 1861 elements, using a spectral decomposition

julia> tricontourf(p[:,1],p[:,2],grf.data.eigenfunc[:,1],triangles=t-1,cmap=get_cmap("viridis"))
[...]

```
See also: [`Cholesky`](@ref), [`KarhunenLoeve`](@ref), [`CirculantEmbedding`](@ref)
"""
struct Spectral <: GaussianRandomFieldGenerator end

# container type for eigenvalues and eigenfunctions
struct SpectralData{X,Y}
    eigenval::X
    eigenfunc::Y
end

function _GaussianRandomField(mean, cov::CovarianceFunction, method::Spectral, pts...;
                              n::Integer=0, eigensolver=EigenSolver())
    C = apply(cov,pts,pts)

    # compute eigenvalue decomposition
    if n == 0
        F = eigen(C)
        idx = sortperm(F.values,rev=true)
        Λ = F.values[idx]
        U = F.vectors[:,idx]
    else # thanks to #4
        eigenval, eigenfunc = compute(C, n, eigensolver)
        idx = sortperm(eigenval,rev=true)[1:n]
        Λ = eigenval[idx]
        U = eigenfunc[:,idx]
    end

    # if negative eigenvalues detected, remove them
    m = findfirst(x -> x < 0, Λ)
    if m != nothing
        m -= 1
        @warn begin
            "$(length(Λ) - m) negative eigenvalues ≥ $(Λ[end]) detected, Gaussian " *
            "random field will be approximated (ignoring all negative eigenvalues)"
        end

        resize!(Λ, m)
        U = U[:, 1:m]
    end

    # store eigenvalues and eigenfunctions
    Λ .= sqrt.(Λ) # note: store sqrt of eigenval for more efficient sampling
    data = SpectralData(Λ, U)

    GaussianRandomField{Spectral,typeof(cov),typeof(pts),typeof(mean),typeof(data)}(mean, cov, pts, data)
end

# returns the required dimension of the random points
randdim(grf::GaussianRandomField{Spectral}) = length(grf.data.eigenval)

# see KarhunenLoeve.jl for sample function

Base.show(io::IO, ::Spectral) = print(io, "spectral decomposition")


# CORK data types

using NonlinearEigenproblemsTest
using NonlinearEigenproblems
using Test,Random
using LinearAlgebra

@bench @testset "CORK types" begin

    # Construct a low-rank NEP
    A0=[1.0 3.0; -1.0 2.0];
    v=reshape([-1.0 ; 1]/sqrt(2),2,1);
    dep=DEP([A0,v*v'],[0,1]);

    # Form a CORKPencil
    cp_org=CORKPencil(dep,IarCorkLinearization(d=9))
    # .. and compress it
    cplr=lowRankCompress(cp_org,1,1);
    (AA_LR,BB_LR)=buildPencil(cplr);

    # Manually construct a pencil
    Av=[-A0-v*v']
    Bv=[-one(A0)-v*v']
    BvLR=[v/2, -v/3, v/4, -v/5, v/6, -v/7,  v/8, -v/9]
    AvLR=zero.(BvLR);
    Z=v;

    d=9;
    M=diagm( 0 =>  ones(d) )[2:end,:]
    N=diagm( -1 =>  1 ./ (1:d-1) )[2:end,:]
    cplr2=CORKPencilLR(M,N,Av,AvLR,Bv,BvLR,Z);

    (AA2,BB2)=buildPencil(cplr2);

    # ... and check that it's the same as we had before
    @test AA2 ≈ AA_LR
    @test BB2 ≈ BB_LR

end


module _rXMSEn
export rXMSEn
using DSP.Filters: filtfilt, Butterworth, Lowpass, digitalfilter
using Statistics: std, mean, median, var
using Plots
    """
        MSx, CI = rXMSEn(Sig, Mobj)

    Returns a vector of refined multiscale cross-entropy values (`MSx`) and
    the complexity index (`CI`) between the data sequences contained in `Sig`
    using the parameters specified by the multiscale object (`Mobj`) and the
    following default parameters:   Scales = 3, Butterworth LPF Order = 6,
    Butterworth LPF cutoff frequency at scale (T): Fc = 0.5/T.
    If the entropy function specified by `Mobj` is `XSampEn` or `XApEn`,
    `rMSEn` updates the threshold radius of the data sequences (Xt) at each scale
    to 0.2std(Xt) when no `r` value is provided by `Mobj`, or r.std(Xt) if
    `r` is specified.

        MSx, CI = rXMSEn(Sig::AbstractArray{T,2} where T<:Real, Mobj::NamedTuple; Scales::Int=3,
                                F_Order::Int=6, F_Num::Float64=0.5, RadNew::Int=0, Plotx::Bool=false)

    Returns a vector of refined multiscale cross-entropy values (`MSx`) and
    the complexity index (`CI`) between the data sequences contained in `Sig`
    using the parameters specified by the multiscale object (`Mobj`) and the
    following keyword arguments:

    # Arguments:
    `Scales`   - Number of temporal scales, an integer > 1 (default: 3)  \n
    `F_Order`  - Butterworth low-pass filter order, a positive integer (default: 6)  \n
    `F_Num`    - Numerator of Butterworth low-pass filter cutoff frequency,
                 a scalar value in range [0 < `F_Num` < 1]. The cutoff frequency
                 at each scale (T) becomes: Fc = `F_Num`/T.  (default: 0.5) \n
    `RadNew`   - Radius rescaling method, an integer in the range [1 4].
                 When the entropy specified by `Mobj` is `XSampEn` or `XApEn`,
                 `RadNew` allows the radius threshold to be updated at each
                 time scale (Xt). If a radius value is specified by `Mobj` (`r`),
                 this becomes the rescaling coefficient, otherwise it is set
                 to 0.2 (default). The value of `RadNew` specifies one of the
                 following methods:  \n
                 [1]    Standard Deviation          - r*std(Xt)  (default)  \n
                 [2]    Variance                    - r*var(Xt)  \n
                 [3]    Mean Absolute Deviation     - r*mean_ad(Xt)  \n
                 [4]    Median Absolute Deviation   - r*med_ad(Xt,1) \n
    `Plotx`    - When `Plotx` == true, returns a plot of the entropy value at
                 each time scale (i.e. the multiscale entropy curve)
                 [default = false] \n

    # See also `MSobject`, `XMSEn`, `cXMSEn`, `hXMSEn`, `XSampEn`, `XApEn`, `MSEn`

    # References:
        [1]   Matthew W. Flood,
            "rXMSEn - EntropyHub Project"
            2021, https://github.com/MattWillFlood/EntropyHub

        [2]   Rui Yan, Zhuo Yang, and Tao Zhang,
            "Multiscale cross entropy: a novel algorithm for analyzing two
            time series."
            5th International Conference on Natural Computation.
            Vol. 1, pp: 411-413 IEEE, 2009.

        [3] José Fernando Valencia, et al.,
            "Refined multiscale entropy: Application to 24-h holter
            recordings of heart period variability in healthy and aortic
            stenosis subjects."
            IEEE Transactions on Biomedical Engineering
            56.9 (2009): 2202-2213.

        [4] Puneeta Marwaha and Ramesh Kumar Sunkaria,
            "Optimal selection of threshold value ‘r’for refined multiscale
            entropy."
            Cardiovascular engineering and technology
            6.4 (2015): 557-576.

        [5] Yi Yin, Pengjian Shang, and Guochen Feng,
            "Modified multiscale cross-sample entropy for complex time
            series."
            Applied Mathematics and Computation
            289 (2016): 98-110.

        [6] Antoine Jamin, et al,
            "A novel multiscale cross-entropy method applied to navigation
            data acquired with a bike simulator."
            41st annual international conference of the IEEE EMBC
            IEEE, 2019.

        [7] Antoine Jamin and Anne Humeau-Heurtier.
            "(Multiscale) Cross-Entropy Methods: A Review."
            Entropy
            22.1 (2020): 45.

    """
    function rXMSEn(Sig::AbstractArray{T,2} where T<:Real, Mobj::NamedTuple; Scales::Int=3,
        F_Order::Int=6, F_Num::Float64=0.5, RadNew::Int=0, Plotx::Bool=false)

    size(Sig,1) == 2  ?  Sig = Sig' : nothing

    (size(Sig,1)>10) ? nothing : error("Sig:   must be a numeric vector" )
    (length(Mobj) >= 1) ? nothing :  error("Mobj:    must be a multiscale entropy object created
         with the function EntropyHub.MSobject")
    (Scales>1) ? nothing : error("Scales:     must be an integer > 1")
    (F_Order>1) ? nothing :  error("F_Order:     must be an integer > 1")
    (0 < F_Num < 1) ? nothing : error("F_Num:     must be a scalar in range 0 < F_Num < 1")
    (RadNew==0 || (RadNew in 1:4 && String(Symbol(Mobj.Func)) in ("XSampEn","XApEn"))) ? nothing :
    error("RadNew:  must be 0, or an integer in range [1 4] with entropy function `XSampEn` or `XApEn`")

    MSx = zeros(Scales)
    Args = NamedTuple{keys(Mobj)[2:end]}(Mobj)
    (RadNew==0 && String(Symbol(Mobj.Func)) in ("XSampEn","XApEn")) ? RadNew=1 : nothing
    if RadNew > 0
        if RadNew == 1
            Rnew = x -> std(x, corrected=false)
        elseif RadNew == 2
            Rnew = x -> var(x, corrected=false)
        elseif RadNew == 3
            Rnew = x -> mean(abs.(x .- mean(x)))
        elseif RadNew == 4
            Rnew = x -> median(abs.(x .- median(x)))
        end

        if haskey(Mobj,:r)
            Cx = Mobj.r
        else
            Cy = ("Standard Deviation","Variance","Mean Abs Deviation",
                    "Median Abs Deviation")
            @warn("No radius value provided in Mobj.
                Default set to 0.2*$(Cy[RadNew]) of each new time-series.")
            Cx = .2
        end
    end

    for T = 1:Scales
        print(" .")
        Temp = refined(Sig,T,F_Order,F_Num)
        RadNew > 0 ? Args = (Args..., r=Cx*Rnew(Temp[:])) : nothing
        Tempx = Mobj.Func(Temp; Args...)
        typeof(Tempx)<:Tuple ? MSx[T] = Tempx[1][end] : MSx[T] = Tempx[end]
        #MSx[T] = Tempx[1][end]
    end
    CI = sum(MSx)
    print("\n")
    if any(isnan.(MSx))
        println("Some entropy values may be undefined.")
    end

    if Plotx
        p1 = plot(1:Scales, MSx, c=RGB(8/255, 63/255, 77/255), lw=3)
        scatter!(1:Scales, MSx, markersize=6, c=RGB(1, 0, 1),
        xlabel = "Scale Factor", ylabel = "Entropy Value",
        guidefont = font(12, "arial", RGB(7/255, 54/255, 66/255)),
        tickfontsize = 10, tickfontfamily="arial", legend=false,
        title = "Refined Multiscale $(Mobj.Func)",
        plot_titlefontsize=16, plot_titlefontcolor=RGB(7/255, 54/255, 66/255))
        display(p1)
    end

    return MSx, CI
    end

    function refined(Z,sx,P1,P2)
        Yt = filtfilt(digitalfilter(Lowpass(P2/sx), Butterworth(P1)), Z)
        return Yt[1:sx:end,:]
    end
end

"""
Copyright 2021 Matthew W. Flood, EntropyHub

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

For Terms of Use see https://github.com/MattWillFlood/EntropyHub
"""


using Test
using QuantumOpticsBase
using Random, SparseArrays, LinearAlgebra

@testset "operators-dense" begin

Random.seed!(0)

D(op1::AbstractOperator, op2::AbstractOperator) = abs(tracedistance_nh(dense(op1), dense(op2)))
D(x1::StateVector, x2::StateVector) = norm(x2-x1)

b1a = GenericBasis(2)
b1b = GenericBasis(3)
b2a = GenericBasis(1)
b2b = GenericBasis(4)
b3a = GenericBasis(1)
b3b = GenericBasis(5)

b_l = b1a⊗b2a⊗b3a
b_r = b1b⊗b2b⊗b3b

# Test creation
@test_throws DimensionMismatch DenseOperator(b1a, [1 1 1; 1 1 1])
@test_throws DimensionMismatch DenseOperator(b1a, b1b, [1 1; 1 1; 1 1])
op1 = DenseOperator(b1a, b1b, [1 1 1; 1 1 1])
op2 = DenseOperator(b1b, b1a, [1 1; 1 1; 1 1])
@test op1 == dagger(op2)

# Test ' shorthand
@test dagger(op2) == op2'
@test transpose(op2) == conj(op2')

# Test copy
op1 = randoperator(b1a)
op2 = copy(op1)
@test op1.data == op2.data
@test !(op1.data === op2.data)
op2.data[1,1] = complex(10.)
@test op1.data[1,1] != op2.data[1,1]

# Arithmetic operations
# =====================
op_zero = DenseOperator(b_l, b_r)
op1 = randoperator(b_l, b_r)
op2 = randoperator(b_l, b_r)
op3 = randoperator(b_l, b_r)

x1 = Ket(b_r, rand(ComplexF64, length(b_r)))
x2 = Ket(b_r, rand(ComplexF64, length(b_r)))

xbra1 = Bra(b_l, rand(ComplexF64, length(b_l)))
xbra2 = Bra(b_l, rand(ComplexF64, length(b_l)))

# Addition
@test_throws DimensionMismatch op1 + dagger(op2)
@test 1e-14 > D(op1 + op_zero, op1)
@test 1e-14 > D(op1 + op2, op2 + op1)
@test 1e-14 > D(op1 + (op2 + op3), (op1 + op2) + op3)

# Subtraction
@test_throws DimensionMismatch op1 - dagger(op2)
@test 1e-14 > D(op1-op_zero, op1)
@test 1e-14 > D(op1-op2, op1 + (-op2))
@test 1e-14 > D(op1-op2, op1 + (-1*op2))
@test 1e-14 > D(op1-op2-op3, op1-(op2+op3))

# Test multiplication
@test_throws DimensionMismatch op1*op2
@test 1e-11 > D(op1*(x1 + 0.3*x2), op1*x1 + 0.3*op1*x2)
@test 1e-11 > D((op1+op2)*(x1+0.3*x2), op1*x1 + 0.3*op1*x2 + op2*x1 + 0.3*op2*x2)

@test 1e-11 > D((xbra1+0.3*xbra2)*op1, xbra1*op1 + 0.3*xbra2*op1)
@test 1e-11 > D((xbra1+0.3*xbra2)*(op1+op2), xbra1*op1 + 0.3*xbra2*op1 + xbra1*op2 + 0.3*xbra2*op2)

@test 1e-12 > D(op1*dagger(0.3*op2), 0.3*dagger(op2*dagger(op1)))
@test 1e-12 > D((op1 + op2)*dagger(0.3*op3), 0.3*op1*dagger(op3) + 0.3*op2*dagger(op3))
@test 1e-12 > D(0.3*(op1*dagger(op2)), op1*(0.3*dagger(op2)))

tmp = copy(op1)
conj!(tmp)
@test tmp == conj(op1) && conj(tmp.data) == op1.data

# Internal layout
b1 = GenericBasis(2)
b2 = GenericBasis(3)
b3 = GenericBasis(4)
op1 = randoperator(b1, b2)
op2 = randoperator(b2, b3)
x1 = randstate(b2)
d1 = op1.data
d2 = op2.data
v = x1.data
@test (op1*x1).data ≈ [d1[1,1]*v[1] + d1[1,2]*v[2] + d1[1,3]*v[3], d1[2,1]*v[1] + d1[2,2]*v[2] + d1[2,3]*v[3]]
@test (op1*op2).data[2,3] ≈ d1[2,1]*d2[1,3] + d1[2,2]*d2[2,3] + d1[2,3]*d2[3,3]

# Test division
@test 1e-14 > D(op1/7, (1/7)*op1)

# Tensor product
# ==============
op1a = randoperator(b1a, b1b)
op1b = randoperator(b1a, b1b)
op2a = randoperator(b2a, b2b)
op2b = randoperator(b2a, b2b)
op3a = randoperator(b3a, b3b)
op123 = op1a ⊗ op2a ⊗ op3a
@test op123.basis_l == b_l
@test op123.basis_r == b_r

# Associativity
@test 1e-13 > D((op1a ⊗ op2a) ⊗ op3a, op1a ⊗ (op2a ⊗ op3a))

# Linearity
@test 1e-13 > D(op1a ⊗ (0.3*op2a), 0.3*(op1a ⊗ op2a))
@test 1e-13 > D((0.3*op1a) ⊗ op2a, 0.3*(op1a ⊗ op2a))

# Distributivity
@test 1e-13 > D(op1a ⊗ (op2a + op2b), op1a ⊗ op2a + op1a ⊗ op2b)
@test 1e-13 > D((op2a + op2b) ⊗ op3a, op2a ⊗ op3a + op2b ⊗ op3a)

# Mixed-product property
@test 1e-13 > D((op1a ⊗ op2a) * dagger(op1b ⊗ op2b), (op1a*dagger(op1b)) ⊗ (op2a*dagger(op2b)))

# Transpose
@test 1e-13 > D(dagger(op1a ⊗ op2a), dagger(op1a) ⊗ dagger(op2a))
@test 1e-13 > D(dagger(op1a ⊗ op2a), dagger(op1a) ⊗ dagger(op2a))

# Internal layout
a = Ket(b1a, rand(ComplexF64, length(b1a)))
b = Ket(b2b, rand(ComplexF64, length(b2b)))
ab = a ⊗ dagger(b)
@test ab.data[2,3] == a.data[2]*conj(b.data[3])
@test ab.data[2,1] == a.data[2]*conj(b.data[1])

shape = tuple(op123.basis_l.shape..., op123.basis_r.shape...)
idx = LinearIndices(shape)[2, 1, 1, 3, 4, 5]
@test op123.data[idx] == op1a.data[2,3]*op2a.data[1,4]*op3a.data[1,5]
@test reshape(op123.data, shape...)[2, 1, 1, 3, 4, 5] == op1a.data[2,3]*op2a.data[1,4]*op3a.data[1,5]

idx = LinearIndices(shape)[2, 1, 1, 1, 3, 4]
@test op123.data[idx] == op1a.data[2,1]*op2a.data[1,3]*op3a.data[1,4]
@test reshape(op123.data, shape...)[2, 1, 1, 1, 3, 4] == op1a.data[2,1]*op2a.data[1,3]*op3a.data[1,4]

# Test identityoperator
x1 = Ket(b_r, rand(ComplexF64, length(b_r)))
x2 = Ket(b_r, rand(ComplexF64, length(b_r)))
xbra1 = Bra(b_l, rand(ComplexF64, length(b_l)))
xbra2 = Bra(b_l, rand(ComplexF64, length(b_l)))

I = identityoperator(DenseOpType, b_r)
@test isa(I, DenseOpType)
@test identityoperator(SparseOpType, b_r) == sparse(I)
@test 1e-11 > D(I*x1, x1)
@test I == identityoperator(DenseOpType, b1b) ⊗ identityoperator(DenseOpType, b2b) ⊗ identityoperator(DenseOpType, b3b)

I = identityoperator(DenseOpType, b_l)
@test isa(I, DenseOpType)
@test identityoperator(SparseOpType, b_l) == sparse(I)
@test 1e-11 > D(xbra1*I, xbra1)
@test I == identityoperator(DenseOpType, b1a) ⊗ identityoperator(DenseOpType, b2a) ⊗ identityoperator(DenseOpType, b3a)

# Test tr and normalize
op = DenseOperator(GenericBasis(3), [1 3 2;5 2 2;-1 2 5])
@test 8 == tr(op)
op_normalized = normalize(op)
@test 8 == tr(op)
@test 1 == tr(op_normalized)
op_copy = deepcopy(op)
normalize!(op_copy)
@test tr(op) != tr(op_copy)
@test 1 ≈ tr(op_copy)
@test op === normalize!(op)

# Test partial tr of state vectors
psi1 = 0.1*randstate(b1a)
psi2 = 0.3*randstate(b2a)
psi3 = 0.7*randstate(b3a)
psi12 = psi1 ⊗ psi2
psi13 = psi1 ⊗ psi3
psi23 = psi2 ⊗ psi3
psi123 = psi1 ⊗ psi2 ⊗ psi3

@test 1e-13 > D(0.1^2*0.3^2*psi3 ⊗ dagger(psi3), ptrace(psi123, [1, 2]))
@test 1e-13 > D(0.1^2*0.7^2*psi2 ⊗ dagger(psi2), ptrace(psi123, [1, 3]))
@test 1e-13 > D(0.3^2*0.7^2*psi1 ⊗ dagger(psi1), ptrace(psi123, [2, 3]))

@test 1e-13 > D(0.1^2*psi23 ⊗ dagger(psi23), ptrace(psi123, 1))
@test 1e-13 > D(0.3^2*psi13 ⊗ dagger(psi13), ptrace(psi123, 2))
@test 1e-13 > D(0.7^2*psi12 ⊗ dagger(psi12), ptrace(psi123, 3))

@test 1e-13 > D(ptrace(psi123, [1, 2]), dagger(ptrace(dagger(psi123), [1, 2])))
@test 1e-13 > D(ptrace(psi123, 3), dagger(ptrace(dagger(psi123), 3)))

@test_throws ArgumentError ptrace(psi123, [1, 2, 3])

# Test partial tr of operators
b1 = GenericBasis(3)
b2 = GenericBasis(5)
b3 = GenericBasis(7)
op1 = randoperator(b1)
op2 = randoperator(b2)
op3 = randoperator(b3)
op123 = op1 ⊗ op2 ⊗ op3

@test 1e-13 > D(op1⊗op2*tr(op3), ptrace(op123, 3))
@test 1e-13 > D(op1⊗op3*tr(op2), ptrace(op123, 2))
@test 1e-13 > D(op2⊗op3*tr(op1), ptrace(op123, 1))

@test 1e-13 > D(op1*tr(op2)*tr(op3), ptrace(op123, [2,3]))
@test 1e-13 > D(op2*tr(op1)*tr(op3), ptrace(op123, [1,3]))
@test 1e-13 > D(op3*tr(op1)*tr(op2), ptrace(op123, [1,2]))

@test_throws ArgumentError ptrace(op123, [1,2,3])
x = randoperator(b1, b1⊗b2)
@test_throws ArgumentError ptrace(x, [1])
x = randoperator(b1⊗b1⊗b2, b1⊗b2)
@test_throws ArgumentError ptrace(x, [1, 2])
x = randoperator(b1⊗b2)
@test_throws ArgumentError ptrace(x, [1, 2])
x = randoperator(b1⊗b2, b2⊗b1)
@test_throws ArgumentError ptrace(x, [1])

op1 = randoperator(b1, b2)
op2 = randoperator(b3)

@test 1e-13 > D(op1*tr(op2), ptrace(op1⊗op2, 2))

# Test expect
b1 = GenericBasis(3)
b2 = GenericBasis(5)
b3 = GenericBasis(7)
op1 = randoperator(b1)
op2 = randoperator(b2)
op3 = randoperator(b3)
op123 = op1 ⊗ op2 ⊗ op3
b_l = b1 ⊗ b2 ⊗ b3

state = randstate(b_l)
@test expect(op123, state) ≈ dagger(state)*op123*state
@test expect(1, op1, state) ≈ expect(op1, ptrace(state, [2, 3]))
@test expect(2, op2, state) ≈ expect(op2, ptrace(state, [1, 3]))
@test expect(3, op3, state) ≈ expect(op3, ptrace(state, [1, 2]))

state = randoperator(b_l)
@test expect(op123, state) ≈ tr(op123*state)
@test expect(1, op1, state) ≈ expect(op1, ptrace(state, [2, 3]))
@test expect(2, op2, state) ≈ expect(op2, ptrace(state, [1, 3]))
@test expect(3, op3, state) ≈ expect(op3, ptrace(state, [1, 2]))

@test_throws QuantumOpticsBase.IncompatibleBases expect(op2, op1)

# Permute systems
op1 = randoperator(b1a, b1b)
op2 = randoperator(b2a, b2b)
op3 = randoperator(b3a, b3b)
op123 = op1⊗op2⊗op3

op132 = op1⊗op3⊗op2
@test 1e-14 > D(permutesystems(op123, [1, 3, 2]), op132)

op213 = op2⊗op1⊗op3
@test 1e-14 > D(permutesystems(op123, [2, 1, 3]), op213)

op231 = op2⊗op3⊗op1
@test 1e-14 > D(permutesystems(op123, [2, 3, 1]), op231)

op312 = op3⊗op1⊗op2
@test 1e-14 > D(permutesystems(op123, [3, 1, 2]), op312)

op321 = op3⊗op2⊗op1
@test 1e-14 > D(permutesystems(op123, [3, 2, 1]), op321)

# Test projector
xket = normalize(Ket(b_l, rand(ComplexF64, length(b_l))))
yket = normalize(Ket(b_l, rand(ComplexF64, length(b_l))))
xbra = dagger(xket)
ybra = dagger(yket)

@test 1e-13 > D(projector(xket)*xket, xket)
@test 1e-13 > D(xbra*projector(xket), xbra)
@test 1e-13 > D(projector(xbra)*xket, xket)
@test 1e-13 > D(xbra*projector(xbra), xbra)
@test 1e-13 > D(ybra*projector(yket, xbra), xbra)
@test 1e-13 > D(projector(yket, xbra)*xket, yket)

# Test operator exponential
op = randoperator(b1a)
@test 1e-13 > D(op^2, op*op)
@test 1e-13 > D(op^3, op*op*op)
@test 1e-13 > D(op^4, op*op*op*op)

# Test gemv
b1 = GenericBasis(3)
b2 = GenericBasis(5)
op = randoperator(b1, b2)
xket = randstate(b2)
xbra = dagger(randstate(b1))
rket = randstate(b1)
rbra = dagger(randstate(b2))
alpha = complex(0.7, 1.5)
beta = complex(0.3, 2.1)

rket_ = deepcopy(rket)
QuantumOpticsBase.mul!(rket_,op,xket,complex(1.0),complex(0.))
@test 0 ≈ D(rket_, op*xket)

rket_ = deepcopy(rket)
QuantumOpticsBase.mul!(rket_,op,xket,alpha,beta)
@test 1e-13 > D(rket_, alpha*op*xket + beta*rket)

rbra_ = deepcopy(rbra)
QuantumOpticsBase.mul!(rbra_,xbra,op,complex(1.0),complex(0.))
@test 0 ≈ D(rbra_, xbra*op)

rbra_ = deepcopy(rbra)
QuantumOpticsBase.mul!(rbra_,xbra,op,alpha,beta)
@test 1e-13 > D(rbra_, alpha*xbra*op + beta*rbra)

# # Test gemm
b1 = GenericBasis(37)
b2 = GenericBasis(53)
b3 = GenericBasis(41)
op1 = randoperator(b1, b2)
op2 = randoperator(b2, b3)
r = randoperator(b1, b3)
alpha = complex(0.7, 1.5)
beta = complex(0.3, 2.1)

r_ = deepcopy(r)
QuantumOpticsBase.mul!(r_,op1,op2,complex(1.0),complex(0.))
@test 1e-13 > D(r_, op1*op2)

r_ = deepcopy(r)
QuantumOpticsBase.mul!(r_,op1,op2,alpha,beta)
@test 1e-10 > D(r_, alpha*op1*op2 + beta*r)

dat = rand(prod(b_r.shape))
x = Ket(b_r, dat)
y = Bra(b_r, dat)
@test dm(x) == dm(y)

# Test Hermitian
bspin = SpinBasis(1//2)
bnlevel = NLevelBasis(2)
@test ishermitian(DenseOperator(bspin, bspin, [1.0 im; -im 2.0])) == true
@test ishermitian(DenseOperator(bspin, bnlevel, [1.0 im; -im 2.0])) == false

# Test broadcasting
op1_ = copy(op1)
op1 .= 2*op1
@test op1 == op1_ .+ op1_
op1 .= op1_
@test op1 == op1_
op1 .= op1_ .+ 3 * op1_
@test op1 == 4*op1_
@test_throws DimensionMismatch op1 .= op2
bf = FockBasis(3)
op3 = randoperator(bf)
@test_throws QuantumOpticsBase.IncompatibleBases op1 .+ op3
@test_throws ErrorException cos.(op1)

end # testset


function test_accuracy_layer(backend::Backend, tensor_dim, T)
  println("-- Testing AccuracyLayer on $(typeof(backend)){$T}...")

  dims = abs(rand(Int,tensor_dim)) % 6 + 6
  op_dim = max(abs(rand(Int)) % tensor_dim, 1)
  dims_label = copy(dims); dims_label[op_dim] = 1
  dims = tuple(dims...)
  dims_label = tuple(dims_label...)
  println("    > $dims (operate on dimension $op_dim)")

  eps = 1e-5
  input = rand(T, dims)
  input_blob = make_blob(backend, input)

  label = abs(rand(Int, dims_label)) % dims[op_dim]
  label = convert(Array{T}, label)
  label_blob = make_blob(backend, label)

  inputs = Blob[input_blob, label_blob]

  layer = AccuracyLayer(bottoms=[:pred, :labels], dim=op_dim)
  state = setup(backend, layer, inputs, Blob[])

  println("    > Forward")
  forward(backend, state, inputs)

  @test state.n_accum == prod(dims_label)

  dim_pre, dim_pred, dim_post = split_dims(input, op_dim)

  canonical_input = reshape(input, (dim_pre, dim_pred, dim_post))
  canonical_label = reshape(label, (dim_pre, 1, dim_post))
  expected_acc = 0.0
  for i = 1:dim_pre
    for j = 1:dim_post
      pred = canonical_input[i,:,j]
      if indmax(pred) == round(Int, canonical_label[i,1,j])+1
        expected_acc += 1
      end
    end
  end
  expected_acc /= prod(dims_label)
  @test abs(state.accuracy - expected_acc) < eps

  println("    > Forward Again")
  forward(backend, state, inputs)
  @test state.n_accum == 2*prod(dims_label)
  @test abs(state.accuracy - expected_acc) < eps

  println("    > Forward Again and Again")
  reset_statistics(state)
  forward(backend, state, inputs)
  @test state.n_accum == prod(dims_label)
  @test abs(state.accuracy - expected_acc) < eps

  shutdown(backend, state)
end
function test_accuracy_layer(backend::Backend, T)
  for i in [2,4,5]
    test_accuracy_layer(backend, i, T)
  end
end
function test_accuracy_layer(backend::Backend)
  test_accuracy_layer(backend, Float32)
  test_accuracy_layer(backend, Float64)
end

if test_cpu
  test_accuracy_layer(backend_cpu)
end
if test_gpu
  test_accuracy_layer(backend_gpu)
end


using OED
using jInv.Utils
using jInv.ForwardShare
using Base.Test
using jInv.Mesh

n = 100
nrhs = 5

A = sdiag(1./linspace(1,n,n).^4)
A = A[1:2:end,:]
N = size(A,1)
x = rand(n,nrhs); x[4:7,:] = 1;
D = A*x  + 1e-8* randn(N,nrhs)/1e3
OEDparamA = OEDTypeAParam(A,D,zeros(0,0))

# setup OED type B param
r = 20;
l = 2;
Mimg  = getRegularMesh([-1 1 -1 1.],[10,10])
Mgrid = getRegularMesh([-1.2 2.4 -1.6 1.4],[20,20])
OEDparamB = getOEDTypeBParam(r,l,x,Mimg,Mgrid,noiseLevel=0.)
OEDparamC = getOEDTypeCParam(r,l,x,Mimg,Mgrid,noiseLevel=0.)

L = 1e-1*speye(n)
pForA = UnconstrainedOEDParam(OEDparamA,L,[],[])
pForB = UnconstrainedOEDParam(OEDparamB,L,[],[])
pForC = UnconstrainedOEDParam(OEDparamC,0*L,[],[])

pFors = (pForA,pForB,pForC)

println("\n\t---test OED with unconstrained lower-level problem---")
for k=1:3
	display(pFors[k])
	np  =  getNumberOfDesignParameters(pFors[k].Design)
	p0      = rand(np)

	println("\t\tcheck reconstruction")
	xr, = getData(p0,pFors[k])
	println("\t\t\trel.error: $(norm(xr-x)/norm(x))")

	print("\t\tcheckDerivative...")

	pass, = checkDerivative(p0,pFors[k])
	@test pass
	print("passed!\n")

	print("\t\tadjoint test...")
	(nw,nv) = getSensMatSize(pFors[k])
	w = rand(nw)
	v = rand(nv)
	t1 = dot(w,getSensMatVec(v,p0,pFors[k]))
	t2 = dot(v,getSensTMatVec(w,p0,pFors[k]))
	@test norm(t1-t2)/norm(t1)<1e-10
	print("passed!\n")
end
println("\t---least squares IP: sensitivities OK ---")


function createMap(filename)
    values = readlines(filename*".txt")
    coords = Tuple[]
    instructions = String[]
    maxX = maxY = 1
    for line in values
        if !isempty(line) && line[1] == 'f'
            inst = split(line, " ")
            instructions = push!(instructions, inst[3])
        elseif !isempty(line)
            st = split(line, ",")
            x = parse(Int, st[1])
            y = parse(Int, st[2])
            juliacoords = (y+1, x+1)
            if juliacoords[1] > maxX
                maxX = juliacoords[1]
            end
            if juliacoords[2] > maxY
                maxY = juliacoords[2]
            end
            coords = push!(coords, juliacoords)
        end
    end
    grid = zeros(Int, maxX, maxY)
    for c in coords
        grid[c[1], c[2]] = 1
    end
    return grid, instructions
end

function foldH(loc, grid)
    cols = size(grid)[2]
    rows = loc - 1
    res = zeros(Int, rows, cols)
    top = grid[1:loc-1, :]
    low = grid[loc+1:size(grid)[1], :]
    for i in 1:rows
        for j in 1:cols
            if top[i, j] == 1 || low[rows+1-i, j] == 1
                res[i,j] = 1
            end
        end
    end
    return res
end

function foldV(loc, grid)
    cols = loc - 1
    rows = size(grid)[1]
    res = zeros(Int, rows, cols)
    left = grid[:, 1:loc-1]
    right = grid[:, loc+1:size(grid)[2]]
    for i in 1:rows
        for j in 1:cols
            if left[i,j] == 1 || right[i, cols+1-j] == 1
                res[i,j] = 1
            end
        end
    end
    return res
end

function solve(filename)
    grid, inst = createMap(filename)
    #println(grid)
    inst1 = inst[1]
    if inst1[1] == 'x'
        loc = parse(Int, inst1[3:length(inst1)]) + 1
        grid = foldV(loc, grid)
    else
        loc = parse(Int, inst1[3:length(inst1)]) + 1
        grid = foldH(loc, grid)
    end
    ndots = count(x -> x == 1, grid)
    println("number of dots: "*string(ndots))
end

function visualize(grid)
    for i in 1:size(grid)[1]
        line = ""
        for j in 1:size(grid)[2]
            if grid[i,j] == 1
                line = line * "⬜"
            else
                line = line * "⬛"
            end
        end
        println(line)
    end
end

function solve2(filename)
    grid, inst = createMap(filename)
    for ins in inst
        if ins[1] == 'x'
            println(ins)
            loc = parse(Int, ins[3:length(ins)]) + 1
            grid = foldV(loc, grid)
        else
            println(ins)
            loc = parse(Int, ins[3:length(ins)]) + 1
            grid = foldH(loc, grid)
        end
    end
    visualize(grid)
end
#solve("example")
solve("input")

solve2("input")