## 'Tight' bounds for holonomic sequences (and more?)
## Marc Mezzarobba, Algorithms project, INRIA Rocquencourt
## 2008-02/03

use gfun in

####################################################################################################
## Utilities
####################################################################################################

## Inert Taylor coefficients

`value/Coeftayl` := proc(expr, eqn::name=complexcons, k)
    if type(k, nonnegint) then
        return(coeftayl(args))
#    elif type(expr, ratpoly) and rhs(eqn)=0 then
#        try
#            return(ratpolytocoeff(expr, lhs(eqn), k));
#        end;
    end if;
    return(Coeftayl(args))
end proc:

## Poor man's directed rounding

above_below := proc(x::realcons,i::integer)::realcons;
    local y;
    if type(x,rational) then
        return x;
    else 
       try
           op(i,op(shake(x)))
       catch:
            y := evalf(x);
            op(i,op(shake(y)))
       end;
    end if;
end proc:

below := proc() above_below(args,1) end proc:
above := proc() above_below(args,2) end proc:

## Roots & poles

root_multiplicity := proc(poly::polynom, z::name, alpha::complexcons)::nonnegint;
    local m,p;
    p := poly;
    for m from 0 while evala(subs(z=alpha,p))=0 do p := evala(p/(z-alpha)) end do;
    m;
end proc:

pole_multiplicity := proc(rat::ratpoly, z::name, alpha::complexcons)::nonnegint;
    root_multiplicity(denom(normal(rat)), z, alpha)
end proc:

dominant_root := proc(p::polynom, z::name)::[complexcons,nonnegint];
    local candidates;
    candidates :=  sort(
        map(
            x -> [x, root_multiplicity(p,z,x)],
            infsing(1/p, z)[1]),
        proc(x,y) x[2]<y[2] end proc);
    return(candidates[-1]);
end proc:

## Inequations

one_sol_of_ineq := proc(ineq::`<`,x::name, n0 := 0, $)::posint;
    description "Finds n >= n0 s.t. expr(n) < epsilon (for nonnegative expr tending to 0)";
    local expression,evalexpr,epsilon,dicho,n;
    expression := lhs(ineq);
    epsilon := rhs(ineq);
    userinfo(5, one_sol_of_ineq, 'epsilon' = evalf(epsilon));
    evalexpr := proc(t)
        local u;
        u := evalf(subs(x=evalf(t),expression)); # evalf for speed, as usual
        ASSERT(u>=0, "rounding error?");
        userinfo(5, one_sol_of_ineq, 'f'(t) = u);
        return u;
    end proc:
    dicho := proc(i, j)  # expr(j) always < epsilon (=> OK even if expr non-nonincreasing!)
        local m;
        m := iquo(i+j,2);
        if j-i <= 2 then j elif evalexpr(m) > epsilon then dicho(m,j) else dicho(i,m) end if;
    end proc:
    n := n0;
    while evalexpr(2*n) > epsilon do n := 2*n end do;
    n := dicho(n,2*n);
    return n;
end proc:


## RootOf's

# Dirty hack to get rid of abs@RootOf's (in presence of which the partial fraction decomposition in
# ratpolytocoeff fails)
`abs/RootOf` := proc()
    local z, poly, poly2, X, Y, facto, candidates, u, p, approx;
    z := RootOf(args);
    Digits := Digits + 20;
    approx := abs(evalf(z))^2;
    poly := op(1,z);
    X := op(indets(poly));
    poly2 := resultant(
        eval(poly,X=Y),
        expand(Y^(degree(poly))*eval(poly,X=X/Y)),
        Y);
    facto := [seq(u[1], u in factors(poly2)[2])];
    #map(p -> eval(p, X=approx), facto);
    candidates := select(p -> abs(eval(p, X=approx)) < 1e-20, facto);
    if candidates = [] then
        WARNING("unable to compute abs(RootOf), using numerical approximation");
        Digits := 5;
        return convert(approx,rational);
    else
        return sqrt(evala(Normal(RootOf(candidates[1], approx))));
    end if;
end proc:

##provisoire
  # tout d'un coup le mydiffeqtohomdiffeq ci-dessous ne trouve plus makediffeq ?!
   makediffeq:=proc (deq, y, x, ini)
   option `Copyright (c) 1992-2007 by Algorithms Project, INRIA France. All rights reserved.`;
   local r, i;
      r:=add(deq[i+2]*diff(y(x),[x$i]),i=0..nops(deq)-2)+deq[1];
      if nargs=3 or ini={} then r
      else {r,op(ini)} end if
   end proc: # makediffeq
   mydiffeqtohomdiffeq:=proc (Deq, yofx)
   local deq, y, x, ini, c, dc, n, i, iszero;
      deq:=formatdiffeq([args],y,x,ini);
      if deq[1]=0 then Deq
      else
         n:=nops(deq);
        if ini<>{} or n = 2 then
        ini,iszero:=`goodinitvalues/diffeq`(deq,y,x,ini,n-2);
        if iszero then return [0,1] fi end if;
         c:=deq[1];
         dc:=-diff(c,x);
         makediffeq(map(collect,[0,dc*deq[2]+c*diff(deq[2],x),
             seq(dc*deq[i]+c*diff(deq[i],x)+c*deq[i-1],i=3..n),c*deq[n]],x),
                    y,x,ini)
      end if
   end proc: # diffeqtohomdiffeq
##

####################################################################################################
## Majorant series for rational polynomials
####################################################################################################

parse_exppoly_term := proc(t, n::name,{numeric::boolean:=false})::[complexcons,integer,complexcons];
    description "Takes n and t=c·n^d·a^n and returns [c,d,a].";
    local c,k,a,f,d, dummy, check;
    c := select(type, t*dummy, constant);
    if c = undefined or c = NULL then
        c := 1
    elif c = 0 then
        return [0, 1, 1]
    end if;
    f := t/c;
    k := select(type, dummy*f, ratpoly(constant,n));
    if k = undefined or k = NULL then k := 1 end if;
    d := degree(k,n);
    a := simplify((f/k)^(1/n),'symbolic');
    # Check result...
    check := simplify(c*n^d*a^n/t) assuming n::integer;
    if not numeric then
        ASSERT( check = 1, sprintf("unable to parse: %a (%a != 1)",t, check) );
    else
        check := evalf(subs(n=10,check));
        ASSERT( abs(check-1) < 0.01, sprintf("unable to parse: %a (numeric mode)",t) );
    end if;
    return [c,d,a] ;
end proc:

max_of_exppoly_term := proc(term, n::name, {numeric::boolean:=false})::nonnegint;
    local t,lambda;
    t := parse_exppoly_term(term,n,_options);
    lambda := abs(t[3]);
    ASSERT(evalf(lambda) <= 1);
    # moyen de se débarrasser de evalf ci-dessous ?
    if t[2] <= 0 or lambda = 1 then
        return 1
    else
        return ceil(evalf(t[2]/log(1/lambda)))
    end if;
end proc:

# - Hypothèses sur alpha et m : ce sont 'au pire' l'inverse du premier pôle, et sa multiplicité.
# - Peut-être à raffiner : borne valable à partir d'un certain rang ???
# - Garanties (evalr et tout ça) à améliorer...
# - la finesse n'est pas satisfaisante pour les exposants (K)
bound_ratpoly_coeffs :=
proc(rat::ratpoly, z::name, Alpha::realcons, m::posint,
{numeric::boolean:=false, precision::And(constant,positive):=1, max_expand::posint:=100}, $)
::realcons;
    description "Computes M s.t. M/(1-abs(alpha)·z)^m is a majorant series for rat---that is,"
        "rat(z) = Sum(u_n·z^n) where |u_n| <= M·binomial(n+m-1,m-1)·alpha^n.";
    option remember;
    local den,polypart,auxrat,vofn,n0,tail_bound_fun,head_bound,head,
        tail_bound,bound, alpha, dummy;
    den := denom(rat);
    polypart := quo(numer(rat), den, z, 'num');
    if numeric then
        userinfo(3, bounds, "using numerical approximation to speed up " ||
                "bound_ratpoly_coeffs (as requested)");
        alpha := 10/9*convert(evalf[4](Alpha), 'rational', 'exact');
        if not(alpha > 0) then error "doesnt' work, try exact mode. Aborting." end if;
    else
        alpha := Alpha;
    end if;
    auxrat := evala(subs(z=z/alpha, num/den)); # sic (le nouveau z est alpha fois l'ancien !)
    # déplacer la conversion en flottant ici --> "floats not handled yet"
    # trivial piecewise's may appear, convert/piecewise gets rid of them
    vofn := convert(ratpolytocoeff(auxrat, z, n),piecewise) / n^(m-1);
    if numeric then vofn := evalf(vofn) end if;
    # le +1 assure (enfin...) que le résultat est une somme (pour les map)
    vofn := expand(allvalues(vofn));  # needs Maple >= 11
    # À ce stade chaque terme est de la forme cste·n^k·a^n, avec a<=1 toujours et a<1 si k>0. Il est
    # donc croissant pour n<=k/log(1/abs(a)) et décroissant au-delà. On peut juste prendre le
    # maximum de chaque terme et basta. Variante plus fine : soit n0 tq a) n0 >= deg polypart et b) en
    # n0 chaque terme a dépassé le max de sa valeur absolue. On compare la somme des v.a. en n0 et
    # le max des termes jusqu'à n0, si celui-ci est plus grand c'est notre borne, sinon on double n0
    # (or l'un croît et l'autre décroît).
    #
    # n0 tq pour n >= n0 tous les termes aient passé leur max en valeur absolue
    # +1 ensures the expression is a sum, so that map does what we want
    n0 := max(1, degree(polypart) + 1, op(map(max_of_exppoly_term, vofn+1, n, _options)) );
    # The factor (m-1)! comes from the division by n^(m-1) above (while we are looking for a bound
    # of the form binomial(n+m-1,m-1)).
    tail_bound_fun := (m-1)!*(map(abs, vofn+dummy) - abs(dummy)) assuming n::posint; 
    # tail_bound_fun is nonincreasing for n>=n0
    userinfo(5, bounds, printf("start search, n0 = %a <= max = %a\n",n0,max_expand));
    do
        head := convert(series(rat,z,n0+1),polynom);
        head_bound := max(seq((abs(coeff(head,z,j)/(binomial(j+m-1,m-1)*alpha^j))),j=0..n0));
        # réel mais le calcul numérique peut faire apparaître des epsilon*I
        tail_bound := Re(eval(tail_bound_fun, n=n0));
        userinfo(5, bounds, 'n0' = n0 , 'head_bound' = head_bound, 'tail_bound' = evalf(tail_bound));
        if above(tail_bound - head_bound) <= precision or n0 >= max_expand then break end if;
        n0 := 2*n0;
    end do;
    # Final bound
    Digits := 4;
    bound := max(above(head_bound), above(tail_bound));
    convert(bound,rational,exact);
end proc:

###################################################################################################
## Parameter computation for majorant series (normal case)
###################################################################################################

# - TODO: remplacer par la version "en theta" un poil plus fine
bound_normal_diffeq_doit := proc(deq::hrdeq, yofz::function(name))
::[nonnegint,complexcons,posint,And(nonnegative,realcons)];
    description "Input: deq with reg. 0 and finite cvge radius, Output: parame of bound (T, alpha, K, A)";
    local y, z, coef, r, ini, p, k, t, mu, R, alpha, M, delta, c, a, rewritten_deq, Mk, K, qrest, 
        absqrest, N, pp, maj, head, majhead, A, u, n;
    ## Parse parameters
    y, z := getname(yofz);
    coef, r, ini := read_diffeq(deq, y(z));
    ASSERT(coef[1] = 0);
    p := table([seq(k = coef[k+2], k=0..r)]); # force indexation from 0
    ## Trivial case: y(z)=0
    if r = 0 then return [0, 0, 1, 0] end if;
    ## Rewrite deq to isolate [z^n]y(z)
    delta := ldegree(p[r]);
    t := normal(p[r] / z^delta);
    p[r] := undefined;
    for k from 0 to r-1 do
        # Normalisation : tout sauf y^(r) au membre droit et racine en 0 du coef de tête d'ordre r.
        p[k] := - z^(r-delta) * p[k];
        ASSERT(ldegree(p[k])>=k, "0 should be a regular point of the equation"); # Fuchs criterion
        pp := normal((p[k]/z^k)/t);
        c[k] := eval(pp, z=0);
        ASSERT(type(c[k], complexcons));
        a[k] := normal((pp - c[k])/z);
        ASSERT(type(a[k],ratpoly)); ASSERT(eval(denom(a[k]),z=0)<>0);
        ASSERT(normal((p[k]/t)-(z^k*c[k]+z^(k+1)*a[k]))=0);
    end do;
    rewritten_deq := { z^r*diff(y(z),[z$r]) - add(c[k]*z^k*diff(y(z),[z$k]),k=0..r-1)
                        = add(z^(k+1)*a[k]*diff(y(z),[z$k]),k=0..r-1) };
    userinfo(5, bounds, `equation rewritten as`, rewritten_deq);
    ### Test équation indicielle
    #q := factor(falling_factorial(n, r) - add(c[k]*falling_factorial(n,k), k=0..r-1));
    #q1 := factor(DEtools[indicialeq](op(select(has,deq,z)),n,0,y(z)));
    #q2 := factor(DEtools[indicialeq](op(select(has,rewritten_deq,z)),n,0,y(z)));
    #print("equations indicielles", q, q1, q2);
    ###
    ASSERT(colinear(coef, read_diffeq(rewritten_deq, y(z))[1], [2..r+2]));
    ## Compute bounds for the coefficients of the resulting equation
    mu := dominant_root(lcm(seq(denom(a[k]),k=0..r-1)), z)[1];
    R := max(r, seq( pole_multiplicity(a[k],z,mu) + k, k=0..r-1 ));
    userinfo(3, bounds, `if`(R=r, `regular case`, `irregular case`), R);
    alpha := 1/eval(abs(mu));
    Mk := table([seq(
        k = bound_ratpoly_coeffs(a[k], z, alpha, R-k, 'numeric'),
        k=0..r-1 )]);
    M := max(seq(Mk[k]/binomial(r-1,k), k=0..r-1));
    ## Deduce bounds for the solution (``Cauchy-Kovalevskaya method'')
    K := ceil(11/10 * M/alpha);
    # Compute N s.t. n>=N => q(n) >= (M/K·alpha)·n^r, where q(n) = indicial polynomial of deq
    qrest := expand(add(c[k]*falling_factorial(n,k), k=0..r-1));
    absqrest := map(abs, qrest) assuming n::nonnegint;
    N := 1;
    while above(eval(qrest,n=N) - (1-M/(K*alpha))*N^r) >= 0 do N := 2*N end do;
    #
    maj := `if`(R=r, 1/(1-alpha*z)^K, exp(K/((R-r)*(1-alpha*z)^(R-r))));
    Order := N+1; # no need to save
    try
        #sol := dsolve(deq, yofz, type='series');
        #head := convert(rhs(dsolve(deq, yofz, type='series')),polynom);
        head := rectoproc(diffeqtorec(deq, yofz, u(n)), u(n));
        majhead := convert(taylor(maj,z=0),polynom);
        #ASSERT(not(has(head,ln)), "Expected a power series solution (without logs)");
        #A := max(seq(abs(coeff(head,z,n))/coeff(majhead,z,n),n=0..N));
        A := max(seq(abs(head(n))/coeff(majhead,z,n),n=0..N));
        if not type(A, 'constant') then error "INICONDS" end if;
    catch: 
        WARNING("unable to compute initial values: result valid up to "
            "some multiplicative constant.");
        A := 1;
    end;
    A := above(A);
    return [R-r, alpha, K, A]; # sorted by `importance'
end proc:

###################################################################################################
## Parameter computation for majorant series: reduction of general case to normal case
###################################################################################################

normal_type := proc(rec::hrrec, uofn::function(name))
    description "Tests for normal (Poincaré) type";
    local coef, s, ini;
    coef, s, ini := read_rec(rec, uofn);
    return evalb( degree(coef[s+2]) = max(seq(degree(coef[j+2]),j=0..s-1)) );
end proc:

# rec*rec fait une erreur parce qu'il ne trouve pas GroebnerBasis si l'on ne donne pas de conditions
# initiales -- enfin, avec algolib pour Maple 10 utilisé avec Maple 11. Et même avec conditions
# initiales, dans certains cas :-(
bound_rec_doit := proc(rec::hrrec, uofn::function(name))
    description "Input: rec, Output: parameters of bound (kappa, T, alpha, K, A)";
    local i, u, n, coef, s, ini, factorial_growth, j, A, p, q, normalrec, deq, TalphaKA;
    u, n := getname(uofn);
    coef, s, ini := read_rec(rec, uofn);
    # *opposé* de la pente
    factorial_growth := max(seq((degree(coef[j+2])-degree(coef[s+2]))/(s-j), j=0..s-1)); 
    if factorial_growth = -infinity then # trivial rec (only one nonzero term)
        A := max(1, op(map(abs,select(type,eval([seq(u(j),j=0..s-1)],ini),constant))));
        return [-infinity, 0, 1, s-1, A]; # semble marcher, mais pas très satisfaisant
            # (alpha=1 pour faire plaisir aux trucs qui font 1/alpha)
    else 
        p,q := numer(factorial_growth), denom(factorial_growth);
        normalrec := `rec*rec`(rec, {(n+q)^p*u(n+q)=u(n), seq(u(i)=1,i=0..q-1)}, uofn);
        ASSERT(normal_type(normalrec, uofn));
        deq := rectodiffeq(normalrec, uofn, y(z));
        deq := mydiffeqtohomdiffeq(deq,y(z));
        TalphaKA := bound_normal_diffeq_doit(deq, y(z));
        return [factorial_growth, op(TalphaKA)];
    end if;
end proc:

bound_diffeq_doit := proc(deq::hrdeq, yofz::function(name))
    description "Input: deq, Output: parameters of bound (kappa, T, alpha, K, A)";
    option remember; # virer ça et créer bound_diffeq_doit_mem si nécessaire
    local rec, u, n;
    rec := rectohomrec(diffeqtorec(deq, yofz, u(n)), u(n));
    if normal_type(rec, u(n)) then
        return [0, op(bound_normal_diffeq_doit(deq, yofz))];
    else
        return bound_rec_doit(rec, u(n))
    end if;
end proc:

###################################################################################################
## Explicit bounds (including human-readable formulae) associated with parameters
###################################################################################################

normal_majorant_series_formula := proc(T, alpha, K, A, z)
    if T = 0 then
        return A/(1-alpha*z)^K;
    else
        return A*exp((K/T)/(1-alpha*z)^T);
    end if;
end proc:

normal_coeff_formula := proc(T, alpha, K, A, n)
    local maj, z;
    if T = 0 then
        return A * binomial(n+K-1,K-1) * alpha^n
    else
        maj := normal_majorant_series_formula(T,alpha,K,A,z);
        return Coeftayl(maj, z=0, n);
    end if;
end proc:

tail_constant := proc(x, tau::extended_rational)
    description "Bound for sum(1/n!^tau * x^n, n=0..infinity)";
    local p,q;
    if tau = 0 then
        if type(x, constant) and below(abs(x)) >= 1 then error "outside convergence disk" end if;
        return 1/(1-x);
    elif tau > 0 then
        p, q := numer(tau), denom(tau);
        return add(x^k, k=0..q-1) * exp(p/q*x^(q/p));
    elif tau = infinity then
        return 1;
    else
        error "divergent sum"
    end if;
end proc;

# r may be a constant as well as a formal parameter, alpha > 0 (pb type RootOf)
tail_bound := proc(kappa::extended_rational, T::nonnegint, alpha::complexcons, K::nonnegative,
A::nonnegative, r::anything, n::name, {derivative::nonnegint:=0, simplify_hypergeom::boolean:=true})
    local saddle, x, bound, normal_part, validity, h, p::nonnegint, q::posint, k, z::name, maj;
    saddle := 1/alpha * (1 - (K/n)^(1/(T+1)));
    x := r/saddle;
    maj := diff(normal_majorant_series_formula(T, alpha, K, A, z), [z$derivative]);
    # default values that may be overriden later
    bound := 1/((n!)^(-kappa)) * normal_part;
    normal_part :=  eval(maj, z=saddle) * x^n * h;
    validity := ceil(K)+1;
    h := tail_constant(x, -kappa);
    #
    if kappa > 0 then # divergent series
        if r = 0 or A = 0 then bound := A else bound := infinity end if;
    elif kappa = 0 then # finite convergence radius
        if T = 0 or type(r, realcons) and above(r) < 1/alpha then
            # this form simplifies faster (?) than 
            # sum(product(n+k+j,j=1..K-1)*(alpha*z)^(n+k),k=0..infinity)
            normal_part := binomial(n+K-1,K-1)*(alpha*z)^n*hypergeom([1,n+K],[n+1],alpha*z);
            if simplify_hypergeom then
                # this returns P(n)·(alpha·z)^n where P \in Q(alpha·z)[n], which may be easier to
                # read than the above version but seems to lead to cancellations when evaluated
                # numerically
                normal_part := normal(expand(simplify(normal_part))); # expand/binomial
            end if;
            normal_part := eval(normal_part,z=r);
        else
            validity := K/(1-alpha*r)^(T+1) + 1;
        end if;
    elif kappa = -infinity then
        bound := 0; validity := K+1;
    else # r may be outside the convergence disk of maj (and thus > saddle for all n)
        NULL;
    end if;
    return [eval(bound), validity];
end proc:

###################################################################################################
## Bounds for numerical evaluation
###################################################################################################

# il y a sans doute des choses à améliorer dans la structure des deux procédures suivantes...
nb_terms := proc(deq::hrdeq, yofz::function(name)) 
    local maj, nmin, n, y, z, params;
    y, z := getname(yofz);
    params := bound_diffeq_doit(deq, yofz);
    proc(derivation_order, absdz, epsilon)
        local n1,gj,bound;
        bound, nmin := op(tail_bound(op(params), absdz, n, derivative=derivation_order,
                                        simplify_hypergeom=false));
        if type(bound, SymbolicInfinity) then error "no finite bound for series tail" end if;
        one_sol_of_ineq(bound < epsilon, n, nmin);
    end proc:
end proc:

needed_terms := proc(deq, yofz, derivation_order, z0, z1, epsilon)
    local y,z,localdeq;
    y,z := getname(yofz);
    localdeq := algebraicsubs(deq,y=z+z0,y(z)); 
    nb_terms(localdeq, yofz)(derivation_order,abs(z1-z0),epsilon);
end proc:

bound_transition_matrix := proc(deq, yofz::function(name), path::list(complexcons), $)::realcons;
    option remember;
    local y::name, z::name, steps::integer, m::integer, B1::realcons, B2::realcons, B3::realcons,
        localdeq::hrdeq, params, tau::extended_rational, alpha::constant, maj, t::realcons,
        r::nonnegint, k, dz::realcons;
    y,z := getname(yofz);
    steps := nops(path) - 1;
    if steps <= 0 then
        return 1
    end if;
    m := iquo(steps+1, 2);
    B1 := bound_transition_matrix(deq,yofz,path[1..m]);
    ## to be improved
    dz := abs(path[m+1]-path[m]);
    localdeq := diffeqtohomdiffeq(algebraicsubs(deq,y=z+path[m],y(z)),y(z));
    params := bound_diffeq_doit(localdeq, yofz);
    tau, alpha := -params[1], params[3];
    maj := normal_majorant_series_formula(op(params[2..-1]), z);
    t := above((dz+1/alpha)/2);
    r := orddiffeq(deq,yofz);
    B2 := r * max(seq( eval(diff(maj, [z$k]), z=t), k=0..r-1 ))
            * tail_constant(dz/t, tau);
    userinfo(4, bounds, path[m], `->`, path[m+1], `bound ~=`, evalf[2](B2));
    B3 := bound_transition_matrix(deq,yofz,path[m+1..-1]);
    above(B1 * B2 * B3);
end proc:

###################################################################################################
## Human-readable bounds
###################################################################################################

bound_ratpoly := 
proc(xrat::ratpoly, z::name,
{numeric::boolean:=false, precision::And(constant,positive):=1, max_expand::posint:=100}, $)
    description "Compute a majorant of the form P(z) or M/(1-alpha·z)^m for rat.";
      # essayer plutôt polynôme + M/... ?
    local rat, rho, m, M, B, i, alpha;
    userinfo(6, bounds, series(xrat,z));
    rat := normal(xrat);
    if type(rat, polynom) then
        return add(abs(coeff(rat,z,i))*z^i, i=0..degree(rat));
    end if;
    rho, m := op(dominant_root(denom(rat), z));
    if rho = 0 then
        error("0 is a pole of %1",xrat);
    end if;
    alpha := abs(1/rho);
    M := bound_ratpoly_coeffs(rat,z,alpha,m,_options);
    B := M*1/(1-z/abs(rho))^m;
end proc:

bound_diffeq := proc(deq::hrdeq, yofz::function(name), z0::complexcons := 0)
    local y, z, kappa, T, alpha, K, A, n, maj, k;
    y, z := getname(yofz);
    #deq := diffeqtohomdiffeq(algebraicsubs(Deq,y=z+z0,y(z)),y(z));
    kappa, T, alpha, K, A := op(bound_diffeq_doit(args));
    if kappa = 0 then
        return normal_majorant_series_formula(T, alpha, K, A, z)
    elif kappa > 0 then
        WARNING("divergent bound");
        maj := normal_majorant_series_formula(T,alpha,K,A,z);
        return Sum(n!^kappa * Coeftayl(maj, z=0, n), n=0..infinity); # to be improved...
    elif kappa = -infinity then
        return A * add(z^k, k=0..K);
    else # kappa < 0
        maj := normal_majorant_series_formula(T,alpha,K,A,z);
        return Sum(n!^kappa * Coeftayl(maj, z=0, n), n=0..infinity); # to be improved...
        # formules closes pour Laplace inverse avec la définition pertinente ici ?
    end if;
end proc:

bound_rec := proc(rec::hrrec, uofn::function(name))
    local kappa, T, alpha, K, A, u, n;
    u, n := getname(uofn);
    kappa, T, alpha, K, A := op(bound_rec_doit(args));
    if kappa = -infinity then
        return piecewise(A, n <= K, 0);
    else
        return (n!)^kappa * normal_coeff_formula(T, alpha, K, A, n);
    end if;
end proc:

bound_diffeq_tail := proc(deq::hrdeq, yofz::function(name), n::name)
    local z, maj, validity;
    z := getname(yofz)[2];
    maj, validity := op(tail_bound(op(bound_diffeq_doit(deq, yofz)), z, n));
    return [eval(maj,z=abs(z)), n>=validity];
end proc:

bound_rec_tail := proc(rec::hrrec, uofn::function(name), n0::name:=getname(uofn)[2])
    local z, maj, validity;
    maj, validity := op(tail_bound(op(bound_rec_doit(rec, uofn)), 1, n0));
    return [eval(maj,z=abs(z)), n>=validity];
end proc:

end use;
