####################################################################################################
## Differential / recurrence equations (formal) manipulation
####################################################################################################

## diffeq -> rec

parametered_rec := proc(deq::set, yofz::function(name), $)
    description "Computes a recurrence satisfied by the Taylor coefficients of the formal",
        "fundamental solutions of deq (as a function of the expansion point and the 'diagonal'",
        "initial values).";
    option remember;
    local
        f,n,z0,delta, # returned
        y,z,orddeq,deq0,i,rec;
    y,z := getname(yofz);
    orddeq := orddiffeq(deq, yofz);
    deq0 := {
        algebraicsubs(deq, y = z+z0, y(z)), # Initial conditions get lost here.
        seq((D@@i)(y)(0) = delta(i)*i!, i=0..orddeq-1) };
            # *Multiply* by i! to compute Taylor coefficients from derivatives
    rec := rectohomrec(diffeqtorec(deq0, y(z), f(n)), f(n));
    return(rec,f,n,z0,delta);
end proc:

## Initial conditions

deq_inicond_vector := proc(inicond_eqns::set, yofz::function(name), r::posint, $)::Vector[column];
    description "Computes the coordinates of a solution (specified by gfun deq / initial"
        "conditions) in the canonical basis in zero.";
    local y,z,i;
    y,z := getname(yofz);
    Vector([seq(
        subs( 
            select(type, inicond_eqns, `=`),
            (D@@i)(y)(0) / i! ), 
        i = 0..r-1 )]);
end proc:            

## Recurrences for derivatives

# Based on code by Bruno Salvy.
rectodiffrec := proc(rec::set, uofk::function(name), $)::set;
    description "Given a recurrence satisfied by the coefficients of a formal series, compute one",
        "satisfied by those of its derivative, propagating initial values as needed.";
    local newini, s, i, rrec, ini, u, k, nextvalue;
    u,k := getname(uofk);
    s := ordrec(rec, uofk);
    ini,rrec := selectremove(type,rec,`=`);
    rrec := op(rrec);
    nextvalue := solve(
        subs(ini,eval(rrec,k=0)),
        u(s));
    newini := subs(
        ini union {u(s)=nextvalue},
        [seq(i*u(i),i=1..s)]);
    newini := {seq(u(i)=newini[i+1],i=0..s-1)};
    rrec := numer(
        subs(
            k=k+1,
            eval(rrec,u=proc(k) u(k-1)/k end proc)));
    newini union {rrec}
end proc:

####################################################################################################
## Analytic continuation
####################################################################################################

step_transition_matrix := proc(deq::set, yofz::function(name), z0::complexcons, z1::complexcons,
                    epsilon::realcons, lines::Or(identical("first"),identical("all")):="all")

    description "Compute the transition matrix for one step of analytic continuation (z0 to z1).";
    local
        generic_rec,f,n,a,delta,ordeq,nb_derivatives,i,l,coefrec,fcoefrec,ordcoefrec,ini,
        first_partial_sums,nterms,Nth_term_form,row,mu,den,P,c;
    global exact;

    ordeq := orddiffeq(deq,yofz);
    userinfo(5, ancont, `lines` = lines);
    nb_derivatives := `if`(lines="first", 1, ordeq);
    
    if z0 = z1 then
        userinfo(5, ancont, "step_transition_matrix called with z0=z1");
        return(ndmatrix(IdentityMatrix(ordeq),1))
    end if;

    generic_rec, f::name, n::name, a::name, delta::name := parametered_rec(deq,yofz);

    # coefrec is the recurrence satisfied by the Taylor coefficients *in z0* of the *i-th
    # derivative*
    coefrec := subs(a=z0, generic_rec);
    
    for i from 0 to nb_derivatives-1 do  # Compute the (i+1)th line of the transition matrix
        
        # Recurrence satisfied by the coefs of the i-th derivative, with initial conditions.
        fcoefrec, ordcoefrec, ini := read_rec(coefrec,f(n)); # ini = equations
        
        # Initial coefficients of each fundamental solution. (Each column of the matrix contains
        # the first coefficients of the series giving (as a function of z1-z0) the i-th Taylor
        # coefficient in z1 of one fundamental solution. Without the factor 1/i!, one would get the
        # value of the i-th derivative.)
        mu := [ seq( [ seq(
            subs(
                eval(ini, delta = proc(x) if x=c then 1 else 0 end if end proc),
                1/i! * f(l) * (z1-z0)^l ), 
            c = 0..ordeq-1) ], l=0..ordcoefrec-1) ]; 
        first_partial_sums := convert(mu, ndmatrix);
        userinfo(5,step_transition_matrix,print(Matrix(mu)));

        # BOUND! -- -floor ???
        nterms := floor(NumGfun_settings:-terms_factor * needed_terms(deq,yofz,i,z0,z1,epsilon/ordeq) 
            + NumGfun_settings:-terms_delta);
        userinfo(3, ancont, z0, `-->`, z1, `difforder = `, i, `#terms = `, nterms);
        Nth_term_form := `Nth_term/ndseries`(fcoefrec, n, z1-z0, nterms);
        row[i] := ndmatrix_multiply(Nth_term_form,first_partial_sums);
        
        coefrec := rectodiffrec(coefrec, f(n));
        
    end do;
    
    # Gather the rows we computed in one matrix
    den := mul(op(2,row[i]), i=0..nb_derivatives-1);
    P := ndmatrix(
        Matrix([seq(
            convert(den/op(2,row[i]) * op(1,row[i]), list),
            i=0..nb_derivatives-1)]),
        den);
    userinfo(5,step_transition_matrix,print(evalf(P)));
    return(P);

end proc:

path_transition_matrix := proc(deq, yofz, path, epsilon, $)
    description "Computes the transition matrix along any path (multiplying those of each step by"
        "binary splitting).";
    local steps,P,eps,m,stage,i;
    steps := nops(path) - 1;
    if steps <= 0 then
        return(ndmatrix(IdentityMatrix(orddiffeq(deq,yofz)),1));
    elif steps = 1 then
        return(step_transition_matrix(deq,yofz,op(path),epsilon));
    else
        m := iquo(steps,2);
        stage := [ path[1..m+1], path[m+1..-1] ];
        for i from 1 to 2 do
            eps := epsilon / (2 * bound_transition_matrix(deq,yofz,stage[-i]) );  #BOUND!
            P[i] := path_transition_matrix(deq,yofz,stage[i],eps);
        end do;
        return(ndmatrix_multiply(P[2],P[1])); # ou ring/pr ?
    end if;
end proc:

transition_matrix := proc(deq, yofz::function(name), path::list(complexcons), precision::posint, $)
                        ::Matrix(complex(float));
    fail_if_singular(deq, yofz, path);
    `makeitfloat/ndmatrix`(path_transition_matrix(deq, yofz, path, 10^(-precision)), precision);
end proc:

####################################################################################################
## Evaluation
####################################################################################################

rewrite_path := proc(userdeq, yofz::function(name), inipath::list(complexcons),
                    { usebitburst::boolean:=false }, $)
                    :: list(complexcons);
    local path;
    path := inipath;
    if usebitburst then
        path := [ op(path[1..-3]), op(bit_burst_path(path[-2..-1])) ];
    end if;
    path := map(convert, path, rational, exact);
    return path;
end proc:

ac_eval := proc(userdeq, yofz::function(name), userpath::list(complexcons), precision::posint,
                { usebitburst::boolean:=false }, $)
                ::complex(float);
    
    local deq,Ini,P,Q,L,epsilon,nbsteps,normIni,boundP,precisionX,epsP,epsQ,path,result_precision;
    global exact;#, param_precision_factor, param_precision_delta;
    
    if userpath[1] <> 0 then
        error "The path should start in 0."
    end if;
    deq := diffeqtohomdiffeq(userdeq, yofz);  # à voir...
    path := rewrite_path(deq, yofz, userpath, _options); 
    userinfo(3, ancont, "analytic continuation along path", path);
    fail_if_singular(deq, yofz, path);
    
    Ini := deq_inicond_vector(deq,yofz,orddiffeq(deq,yofz));
    normIni := evalf(Norm(Ini,2));
    nbsteps := nops(path)-1;
    epsilon := evalf(10^(-precision) / (normIni * nbsteps)); # BOUND!
        # Frobenius mieux ? Cf. ce qu'on fait en analyse numérique.
    
    boundP := bound_transition_matrix(deq,yofz, path[1..-2]); # BOUND!
    # Last step: direct evaluation (w/o analytic continuation)
    epsQ := evalf( epsilon / boundP ); # BOUND!
    Q := step_transition_matrix(deq, yofz, path[-2], path[-1], epsQ, "first");
    # Previous steps: analytic continuation
    epsP := evalf((nbsteps-1)*epsilon/ndmatrix_norm(Q));
    P := path_transition_matrix(deq, yofz, path[1..-2], epsP);
    # L is the linear form "initial conditions --> value in z_m"
    L := ndmatrix_multiply(Q,P);

    # Apply to initial conditions and return
    # BOUND! -- evalf prend un nombre de *chiffres significatifs*
    # without +1 arg of log() may be 0
    precisionX := NumGfun_settings:-precision_factor*ilog10(normIni*ndmatrix_norm(L)/epsilon+1)
                    +NumGfun_settings:-precision_delta+1;
    Ini := convert(convert(evalf[precisionX](Ini),rational,exact),ndmatrix);
    userinfo(5, settings, 'precision_factor' = NumGfun_settings:-precision_factor,
        'precision_delta' = NumGfun_settings:-precision_delta);
    result_precision := NumGfun_settings:-precision_factor * precision +
        NumGfun_settings:-precision_delta;
    return(makeitfloat(ndmatrix_multiply(L,Ini), result_precision)[1,1]);
    # Note to self: ne pas confondre taille du point initial et taille des conditions initiales

end proc:

####################################################################################################
## Monodromy
####################################################################################################

local_monodromy := proc(deq, yofz, z0, start, precision)
    local rad, path;
    rad := start - z0;
    path := [ seq(convert(evalf[2](z0 + rad * exp(k*2*I*Pi/17)), rational, exact), k=0..17) ];
    transition_matrix(deq, yofz, path, precision);
end proc:

monodromy := proc()
    error("Not implemented yet.");
end proc:

####################################################################################################
## Bit burst 
####################################################################################################

# ac_eval(deq[arctan], y(z), [0,evalf[1000](Pi/5)], 1000, usebitburst); vs sans usebitburst
bit_burst_path := proc(step::[complexcons, complexcons]) :: list(complexcons);
    local z0::complexcons, z1::complexcons, path_begin, path_end, dz::complexcons,
        p::complex(integer), q::posint, q0::posint, Q::posint;
    # ensure |z1-z0| < 1
    if abs(step[2]-step[1]) < 1 then
        z0 := step[1];
        path_begin := z0;
    else
        z0 := convert(evalf[2](step[2] - (step[2]-step[1])/(2*abs(step[2]-step[1]))), rational);
        path_begin := step[1], z0;
    end if;
    z1 := convert(step[2],rational,exact);
    path_end := z1;
    dz := z1 - z0;
    p, q := numer(dz), denom(dz);
    q0 := min(denom(z0),2);
    while  p <> 0 and q > q0^2 do
        Q := q;
        q := isqrt(q);
        p := trunc(p * q/Q); # dans le disque de cvgce (enfin je crois)
        path_end := z0 + p/q, path_end;
    end do;
    userinfo(4, ancont, "rewritten path to do bit-burst evaluation along", path_end);
    return [path_begin, path_end];
end proc:
