alias( PS = `FPS/FPS` ):
alias( ComplexApart = `FPS/ComplexApart` ):
alias( RationalAlgorithm = `FPS/RationalAlgorithm` ):
alias( constantRE = `FPS/constantRE` ):
alias( hypergeomRE = `FPS/hypergeomRE` ):
alias( UpdateCoeff = `FPS/UpdateCoeff` ):
alias( PSInt = `FPS/Int` ):
alias( de2re = `FPS/de2re` ):
alias( hypergeomRsolve = `FPS/hypergeomRsolve` ):
alias( printDE = `FPS/printDE` ):
alias( DIRECTION = `FPS/DIRECTION` ):
alias( simpl = `FPS/simpl` ):
alias( FindDE = `FPS/FindDE` ):
alias( Limit = `FPS/Limit` ):
alias( Recursion =  `FPS/Recursion` ):
alias( RecursionSimplify = `FPS/RecursionSimplify` ):

FormalPowerSeries := proc(f, x) local i, z, S, dir, argseq; global infolevel:
   option `Copyright 1993 Dominik Gruntz, Wissenschaftliches Rechnen, ETH Zurich`;

   if not assigned(infolevel['FormalPowerSeries']) then infolevel['FormalPowerSeries'] := 1 fi:
   argseq := NULL:
   
   for i from 3 to nargs do
      if member(args[i], {left,right,real,complex}) then dir := args[i]
      else argseq := argseq,args[i]
      fi;
   od;

   if type(x, equation) then z := lhs(x);
      if   rhs(x)= infinity then
         if dir = right or dir = real then 
            ERROR(`inconsistent direction with infinities`) fi;
         userinfo(2, 'FormalPowerSeries', `=> f := `, subs(z=1/z, f) );
         if dir = left or not assigned(dir) then
            S := traperror(subs(z= 1/z, PS(subs(z= 1/z, f), z, right, argseq)));
         else
            S := traperror(subs(z= 1/z, PS(subs(z= 1/z, f), z, dir, argseq)));
         fi
      elif rhs(x)=-infinity then
         if dir = left or dir = real then
            ERROR(`inconsistent direction with infinities`) fi;
         userinfo(2, 'FormalPowerSeries', `=> f := `, subs(z=-1/z, f) );
         if dir = right or not assigned(dir) then
            S := traperror(subs(z=-1/z, PS(subs(z=-1/z, f), z, right, argseq)));
         else
            S := traperror(subs(z=-1/z, PS(subs(z=-1/z, f), z, dir, argseq)));
         fi
      else
         userinfo(2, 'FormalPowerSeries', `=> f := `, subs(z = z+rhs(x), f) );
         if assigned(dir) then
            S := traperror(subs(z=z-rhs(x), PS(subs(z = z+rhs(x), f), z, dir, argseq)))
         else
            S := traperror(subs(z=z-rhs(x), PS(subs(z = z+rhs(x), f), z, argseq)))
         fi
      fi
   else 
      if assigned(dir) then 
         S := traperror(PS(f, x, dir, argseq))
      else
         S := traperror(PS(f, x, argseq))
      fi
   fi;
   if S=lasterror then 'procname(args)'
   else S
   fi;

end: # FormalPowerSeries
FPS := FormalPowerSeries:

SimpleDE := proc(f, x)
   local Nmax, A, F, G, DF, degreeOfDE, deq, i, j, result;
   option `Copyright 1993 Dominik Gruntz, Wissenschaftliches Rechnen, ETH Zurich`;
   
   for i from 3 to nargs do
      if type(args[i], integer) or args[i]=infinity then Nmax := args[i] fi;
      if type(args[i], name) then F := args[i] fi;
   od;
   if not assigned(Nmax) then Nmax := 5 fi;

   DF[0] := simpl(f);
   for degreeOfDE while degreeOfDE <= Nmax do
      DF[degreeOfDE] := simpl(diff(DF[degreeOfDE-1], x));
      A := 'A':
      deq := FindDE(DF, x, degreeOfDE, A, G);
      if deq=FAIL then next fi;
      
      for j from 0 to degreeOfDE-1 do
         if not assigned(A[j]) then A[j] := 0 fi
      od;
      
      deq := collect(deq, {seq(G(i), i=0..degreeOfDE)}, recursive, factor);
      RETURN(eval(subs(G(0)=F(x), subs(['xx'=x,'FF'=F], G = (n -> diff(FF(xx),xx$n))), deq=0)))
   od;
   ERROR(`no simple DE found`)
end:

SimpleRE := proc(f,x)
   local Nmax, A, F, DF, degreeOfDE, deq, i, j, a, k, req;
   option `Copyright 1993 Dominik Gruntz, Wissenschaftliches Rechnen, ETH Zurich`;
   
   for i from 3 to nargs do
      if type(args[i], integer) or args[i]=infinity then Nmax := args[i] fi;
      if type(args[i], name) then
         if not assigned(a) then a :=args[i]
         elif not assigned(k) then k := args[i]
         fi
      fi
   od;
   if not assigned(Nmax) then Nmax := 5 fi;

   DF[0] := simpl(f);
   for degreeOfDE while degreeOfDE <= Nmax do
      DF[degreeOfDE] := simpl(diff(DF[degreeOfDE-1], x));
      A := 'A':
      deq := FindDE(DF, x, degreeOfDE, A, F);
      if deq=FAIL then next fi;
      
      for j from 0 to degreeOfDE-1 do
         if not assigned(A[j]) then A[j] := 0 fi
      od;
      
      req := de2re(deq, F, x, a, k);
      RETURN(collect(req, indets(req, 'a'(anything)), recursive, factor)=0)
   od;
   ERROR(`no simple DE found`)
end:

# Private Part
###############
# The whole algorithm assumes, that intermediate powerseries are always represented
# in the form
#   Sum(ak*x^f(k), k=0..infinity)
# i.e. the indeterminante x is present in a term of type `^` 
# and the range always goes from 0 to infinity.
#

PS := proc(f, x)
   local A, degreeOfDE, DF, F, j, n, s, ind, deq, eq, req, ak, ak1, ak2, Nmax, M, eqq, eqr,
         R, i, m, leadcoeff, a, k, m0, S, result, RAresult, parameters, r, direction, mode,
         sol, sol1, sol2, deg, deg0, DEfound, reqnew, indnew;
   global DIRECTION;
   option `Copyright 1993 Dominik Gruntz, Wissenschaftliches Rechnen, ETH Zurich`;
   
   for i from 3 to nargs do
      if   member(args[i], {'right','left','real','complex'}) then direction := args[i]
      elif member(args[i], {'explike','rational','hypergeometric'}) then mode := args[i]
      elif type(args[i], integer) or args[i]=infinity then Nmax := args[i]
      fi
   od;
   
   # default values:
   if not assigned(direction) then direction := complex fi;
   if not assigned(Nmax) then Nmax := 5 fi;

   DIRECTION := direction;
   _EnvNestLevel := 0:
   DEfound := 0; if mode=rational then DEfound:=1 fi;

   DF[0] := simpl(f);
   if type(DF[0], polynom(anything,x)) then
      RETURN(DF[0])
   fi;

   for degreeOfDE while degreeOfDE <= Nmax do
   
      DF[degreeOfDE] := simpl(diff(DF[degreeOfDE-1], x));
      
      if mode <> rational then
         # Search for a simple DE
         userinfo(3, 'FormalPowerSeries', `looking for DE of degree`, degreeOfDE):
         A := 'A':
         deq := FindDE(DF, x, degreeOfDE, A, F);
         if deq <> FAIL then DEfound := DEfound+1;
            req := de2re(deq, F, x, a, k);
            ind := map(x -> op(1,x), indets(req, a(anything)));
            M := max(op(ind)); 
            parameters := indets(req, name) intersect {seq(A[j], j=0..degreeOfDE-1)}; 
            if parameters <> {} then # try to set free parameters
               if nops(ind) > 2 and not mode='explike' then
                  # 1. try to get hypergeometric case (nops(ind)=2)
                  for i in sort(convert(ind minus {M}, list), (a,b) -> evalb(a-b>0)) do
                     # this special sorting will make the symmetry number minimal!
                     map( (k, req, a) -> coeff(req, a(k)), ind minus {M,i}, req, a);
                     s := solve(", parameters);
                     if s <> NULL then
                        if not has(s,x) then 
                           userinfo(3, 'FormalPowerSeries', `hypergeometric type found`, s); 
                           assign(s); 
                           req := expand(normal(req)): 
                           ind := map(x -> op(1,x), indets(req, a(anything)));
                           ASSERT(nops(ind)<=2, `assertion 1`);
                           break 
                        else
                           reqnew := de2re(subs(s, deq), F, x, a, k);
                           indnew := map(x -> op(1,x), indets(reqnew, a(anything)));
                           if nops(indnew) <= 2 then
                              userinfo(3, 'FormalPowerSeries', `hypergeometric type found`, s);
                              assign(s); 
                              req := reqnew;
                              ind := indnew;
                              break
                           fi
                        fi
                     fi;
                  od;
               fi;
               if nops(ind) > 2 and has(deq, x) and not mode='hypergeometric' then
                  # 2. try to generate an 'explike' DE
                  eq := {seq(coeff(deq, F(j)), j=0..degreeOfDE-1)};
                  eq := map((t,p) -> normal(t/p), eq, coeff(deq, F(degreeOfDE)));
                  eq := select((t,p,x) -> has(t,x) or has(t,p), eq, parameters, x);
                  if parameters minus eq = {} then # all parameters must be constant.
                     eq := eq minus parameters;
                     eqq := map((t,x) -> quo(numer(t),denom(t),x), eq, x);
                     eqr := map((t,x) -> rem(numer(t),denom(t),x), eq, x);
                     eq := map(coeffs, eqq, x) minus map(coeff, eqq, x, 0)
                           union map(coeffs, eqr, x);
                     sol := solve(eq, parameters);
                     if sol <> NULL then assign(sol) fi;
                  elif not has(map((t,p,x)->has(t,x) and not has(t,p),eq,parameters,x),true) then
                     eq := {seq(op(j,eq)=C(j), j=1..nops(eq))};
                     sol1 := solve(", parameters);
                     if sol1 <> NULL then
                        # lprint(`sol1 <> NULL`);
                        map(lhs, eval(subs(sol1, eq))); # these must all be constant;
                        map(normal, "); map(diff, ", x);
                        sol2 := solve(", {seq(C(j), j=0..nops(eq))}); 
                        if sol2 <> NULL and not has(sol2,x) then
                           # lprint(`sol2 <> NULL`);
                           sol2 := map(t -> if op(1,t)=op(2,t) then op(1,t)=0 else t fi, sol2);
                           sol := eval(subs(sol2, sol1));
                           # sol := map(t -> if op(1,t)=op(2,t) then op(1,t)=0 else t fi, sol);
                           assign(sol);
                        fi;
                     fi;
                  fi;
                  deq := expand(numer(eval(deq))):
                  req := de2re(deq, F, x, a, k);
               fi;
               # set values to 0
               for j from 0 to degreeOfDE-1 do
                  if not assigned(A[j]) then A[j] := 0 fi
               od;
               deq := eval(deq):
               req := numer(eval(req)):
               ind := map(x -> op(1,x), indets(req, a(anything)));
               M := max(op(ind));
            fi; # parameters <> {}

            req := collect(req, map((t,a)->a(t), ind, a));
            leadcoeff := numer(coeff(req, a(M))); n := nops(ind);
            req := factor(readlib('isolate')(req, a(M)));

            userinfo(3, 'FormalPowerSeries', `DE of degree`, degreeOfDE, `found.`);
            userinfo(3, 'FormalPowerSeries', `DE = `, print(printDE(deq,F)));  
            userinfo(3, 'FormalPowerSeries', `RE = `, print(req));

            if n=1 and not mode='explike' then # funktionen mit endlicher darstellung
               userinfo(3, 'FormalPowerSeries', `ps with finite number of non-zero coefficients`);
               # a(k+1) = R
               R := subs(k=k+(k+1-M), rhs(req))/a(k);
               leadcoeff := subs(k=k+(k+1-M), leadcoeff);
               # solve RE a(k+1) = R(k) given DF, R, leadcoeff
               ASSERT(R=0, `R <> 0`);
               result := traperror(constantRE(1, leadcoeff, DF, k, x));
               if result <> lasterror then RETURN(result) fi;
            elif n=2 and not mode='explike' then # RE is of hypergeometric type
               m := abs(op(1,ind)-op(2,ind)); 
               R := subs(k=k+(k+m-M), rhs(req))/a(k);
               leadcoeff := subs(k=k+(k+m-M), leadcoeff);
               # solve hypergeometric RE a(k+m) = R(k) * a(k) given DF, m, R, leadcoeff
               result := traperror(hypergeomRE(m, R, leadcoeff, DF, k, x));
               if result <> lasterror then RETURN(result) fi;
#           else 
#              req := {req} union {seq(a(j) = limit(DF[j], x=0, DIRECTION), j=0..degreeOfDE-1)};
#              ak := rsolve(req, a);
#              if op(0,ak)=rsolve then result := `RE could not be solved`
#              else RETURN(Sum(ak*x^k, k=0..infinity))
#           fi;
#           # result := `RE could not be solved`
            fi;

            deq := normal(deq/coeff(deq, F(degreeOfDE)));

            if not has(deq, x) and not mode='hypergeometric' then # DE has constant coefficients

               req := a(k+degreeOfDE) + sum(A['j']*a(k+'j'), 'j'=0..degreeOfDE-1);
               userinfo(3, 'FormalPowerSeries', `DE has constant coefficients`);

               S := 0;
               for i from 0 while not has(req, a(k+i)) do
                  S := S + Limit(DF[i], x=0, DIRECTION)*x^i
               od; m0 := i;
               for i from m0 to degreeOfDE-1 do 
                  req := req, a(i) = Limit(DF[i], x=0, DIRECTION);
               od;
               r  := rsolve({req}, a);
               if type(r,'set') then r := eval(subs(r, op(1,{req}))) fi;
               ak1 := r/k!; ak2 := expand(evalc(ak1));
               if length(ak1) < length(ak2) then ak := ak1 else ak := ak2 fi;
               if ak <> 0 then S := S+Sum(ak*x^k, k=m0..infinity) fi;
               RETURN(S)
            fi;
         fi; # deq <> fail
      fi; # mode <> rational

      # either no differential equation found, or the
      # Hypergeometric algorithm failed to compute the FormalPowerSeries.
      # Try the rational algorithm in the latter case. if possible
      if (DEfound>0 or degreeOfDE=Nmax)
      and not (mode='explike' or mode='hypergeometric') then 

         if DEfound=1 then deg0 := 0 else deg0 := degreeOfDE fi;
         for deg from deg0 to degreeOfDE do

            if type(DF[deg], ratpoly(anything,x)) 
            and not (mode='explike' or mode='hypergeometric') then
						
               userinfo(3, 'FormalPowerSeries', deg, `. derivative is rational`);
						
               RAresult := traperror(RationalAlgorithm(DF[deg], x));
               if RAresult <> lasterror then
                  for j to deg do 
                     RAresult := PSInt(RAresult,x);
                     eval(subs(Sum = (
                        (ak,eq) -> if not has(op(2,eq),RootOf) then subs(op(1,eq)=0, ak) 
                                   else sum(ak,eq) 
                                   fi), RAresult));
                     s := Limit(DF[deg-j]-", x=0, DIRECTION);
                     if has(s, infinity) then ERROR(`should not happen`) fi;
                     if s=undefined then ERROR(`no initialisation found`) fi;
                     RAresult := s + RAresult
                  od;
                  RETURN(RAresult)
               fi;
            fi;
         od;
         DEfound := DEfound+1:
      fi;
      if result = `no initialisation found` 
      or result = `infinite loop detected`
      or result = `essential singularity`
      or result = `not a power series`
      then break fi;
   od;

   if not assigned(result) then 
      if assigned(req) then
         userinfo(1, 'FormalPowerSeries', `ERROR: RE could not be solved`);
         ERROR(`RE could not be solved`);
      elif deq = FAIL then
         userinfo(1, 'FormalPowerSeries', `ERROR: no DE found up to order`,Nmax);
         userinfo(1, 'FormalPowerSeries', `==> increase order (last argument)`);
         ERROR(`no DE found up to order`,Nmax,`increase order`) 
      else
         userinfo(1, 'FormalPowerSeries', `ERROR: no derivative is rational up to order`,Nmax);
         ERROR(`Ratalgo did not succeed`);
      fi;
   else 
      userinfo(1, 'FormalPowerSeries', `ERROR: `, result);
      ERROR(result)
   fi;

end: # FormalPowerSeries


FindDE := proc(DF, x, degreeOfDE, A:name, F:name)
   local eq, j, J, deq, i, ind, list1, list2, s, terms;

   eq := expand(DF[degreeOfDE] + sum(A['j']*DF['j'], 'j'=0..degreeOfDE-1));
   eq := RecursionSimplify(eq):
   eq := expand(numer(eq));

   if type(eq,`+`) then list1 := convert(eq, list) else list1 := [eq] fi;
   terms := {};
   while list1 <> {} do list2 := {}; j := list1[1]; J := j;
      for i from 2 to nops(list1) do
         if type(normal(j/list1[i]), ratpoly(anything, x)) then J := J + list1[i]
         else list2 := list2 union {list1[i]} fi;
      od; terms := terms union {J};
      list1 := list2;
   od;

#  c := indets(eq, function);       # coeffs must be rationally indipendant 
#  s := solve({coeffs(eq,c)}, ind); #-> risch/convert to exp etc.
   ind := {seq(A[j], j=0..degreeOfDE-1)};
   s := traperror(solve(terms, ind));
   if s = NULL or s=lasterror then RETURN(FAIL) fi;

   assign(s);
   for j from 0 to degreeOfDE-1 do 
      if not type(A[j], ratpoly(anything, x)) then 
         A[j] := normal(A[j]); A[j] := simplify(convert(A[j],exp))
      fi 
   od;

   deq := F(degreeOfDE) + sum(A['j']*F('j'), 'j'=0..degreeOfDE-1);
   deq := primpart(deq,  {seq(F(j), j=0..degreeOfDE)} );   #deq := numer(normal(deq));
end:


ComplexApart := proc(e,x) local f, s, location, pfd, n, d;
                          global sharename, libname;
   
   f := normal(e); 
   n := numer(f); d := denom(f); n:= n/lcoeff(d,x);
   if _EnvExplicit = false then 
      s := [] 
   else 
      userinfo(2, 'FormalPowerSeries', `ComplexApart: calling solve`);
      s := [solve(d,x)]
   fi;
   if s <> [] and not has(s,RootOf) then 
      convert(map((z,x) -> x-z, s, x),`*`);
      convert(n/", parfrac, x, true)
   else 
      if not assigned(sharename) then
         readlib('share'):
         location := `share/find`();
         if location <> FAIL then 
            sharename := location; libname := libname,sharename
         else
            userinfo(2, 'FormalPowerSeries', `ComplexApart: sharelibrary not found!`);
         fi
      fi;
      if assigned(sharename) then
	if not assigned(fparfrac) then readshare(fparfrac, algebra); fi;
         pfd := traperror('fparfrac'(f,x));
         if pfd = lasterror then
            ERROR(`fullparfrac expects rational function in x`) fi;
         pfd
      else 
         ERROR(`share library not found`)
      fi;
   fi;
end: # ComplexApart

RationalAlgorithm := proc(f,x)
   local PFD, S, ak, bk, t, d, c, j, k, xk, s, p;
   # first compute the complex partial fraction decomposition (PFD) of f
   # and store the terms in the list PFD.
   
   userinfo(2, 'FormalPowerSeries', `RationalAlgorithm called, f := `, f);

   PFD := ComplexApart(f,x);

   if type(PFD,`+`) then PFD := [op(PFD)] else PFD := [PFD] fi;
   S := 0; ak := 0;
   
   for t in PFD do
      if not has(t,x) then S := S+t
      elif type(t, polynom(anything,x)) then S := S+t
      elif has(t, Sum) then # fullparfrac-term
         if type(t,`*`) then s := select(has, t, Sum); c := t/s else s := t; c := 1 fi;
         t := op(1,s);
         d := 1/select(has, p*t, x); c := c*t*d;
         if type(d,`^`) then j := op(2,d); d := op(1,d) else j := 1 fi;
         # Sum(c/d^j, alpha=RootOf()) where d=x-alpha
         ASSERT(d=x-'alpha');
         ak := ak + Sum(c*(-1)^j/'alpha'^(j+k)*(j+k-1)!/k!/(j-1)!, op(2,s));
      else
         # t = c/(x-xk)^j
         # d is the term (x-xk)^j, and c is the constant. 
         # the variable p only assures that p*t is a product.
         d := 1/select(has, p*t, x); c := t*d;
         if type(d,`^`) then j := op(2,d); d := op(1,d) else j := 1 fi;
         if d=x then S := S+c/x^j; next fi;
         ASSERT(type(d,`+`), `RationalAlgorithm: d should be +`);
         # x may still by scaled by a factor s
         s := select(has, d, x)/x; d := d/s; c := c/s^j;
         xk := x-d; c := c*(-1)^j/xk^j; ak := ak+c*(j+k-1)!/k!/(j-1)!/xk^k
      fi
   od;
   ak := normal(ak);
   if ak = 0 then RETURN(S) fi;
   if expand(subs(k=2*k, ak))=0 then
      bk := simplify(subs(k=2*k+1, ak), power, 'symbolic');
      RETURN(S + Sum(bk*x^(2*k+1), k=0..infinity))
   fi;
   if expand(subs(k=2*k+1, ak))=0 then
      bk := simplify(subs(k=2*k, ak), power, 'symbolic');
      RETURN(S + Sum(bk*x^(2*k), k=0..infinity))
   fi;
   RETURN(S+Sum(ak*x^k, k=0..infinity))
end: # RationalAlgorithm

constantRE := proc(m, leadcoeff, DF, k, x)
# solve constant RE
#
#     leadcoeff * a(k+1) = 0
#
# where leadcoeff was the leading coeffitient of the RE
# and DF is a table where DF[i] = diff(f, x$i)
#

   local m0, m1, fract, DF2, S, i, c0, q, a, A, m2, S0, g;
   option `Copyright 1993 Dominik Gruntz, Wissenschaftliches Rechnen, ETH Zurich`;

   _EnvNestLevel := _EnvNestLevel+1:
   if _EnvNestLevel>20 then ERROR(`infinite loop detected`) fi;
   
   a := factor(leadcoeff);

   userinfo(3, 'FormalPowerSeries', `RE is constant`);
   # userinfo(3, 'FormalPowerSeries', `leadcoeff := `, a);
   userinfo(3, 'FormalPowerSeries', `RE: `, print(a*'a'(k+m)=0) );

   A := select(t -> not has(t, I),  [solve(a, k)]) ;

   fract := convert(map(t -> denom(t), A), set) minus {1};
   if fract <> {} then q := lcm(op(fract));
      g := subs(x=x^q, DF[0]);
      indets(g, (identical(x)^anything)^anything);
      map((t,x) -> t=x^(op(2,op(1,t))*op(2,t)), ", x);
      DF2[0] := eval(subs(", g));
      # DF2[0] := simplify(subs(x=x^q, DF[0]), power, 'symbolic');
      userinfo(3, 'FormalPowerSeries', `RE modified to `, k = k/q);
      userinfo(2, 'FormalPowerSeries', `=> f := `, DF2[0] );
      S := constantRE(q*m, subs(k=k/q, leadcoeff), DF2, k, x);
      S := eval(subs(x=x^(1/q), S));
      S := simplify(S, power);
      RETURN(S)
   fi;
   # {solutions in A are integer}
   
   if A <> [] then
      m1 := min(op(A));
      if type(m1, function) and op(0,m1)='min' then
         m2 := eval(select(type, m1, rational));
         if m2 = infinity then m2 := -m fi:
         userinfo(1, 'FormalPowerSeries', `provided that `, m2, ` <= `, m1);
         m1 := m2;
      fi;
      m1 := m1+m;
      if m1 <> 0 then
         DF2[0] := simplify(x^(-m1)*DF[0], power);
         userinfo(3, 'FormalPowerSeries', `working with x^`,eval(-m1),`*f`);
         userinfo(2, 'FormalPowerSeries', `=> f := `, DF2[0]);
         S := constantRE(m, subs(k=k+m1, leadcoeff), DF2, k, x);
         S := UpdateCoeff(S, x, m1);
         RETURN(S)
      fi
   fi; # { min(op(A)) = -1}
   
   A := select(type, A, integer);
   
   if A = [] then m0 := 0
   else m0 := max(op(A))+1;
   fi;   
   userinfo(3, 'FormalPowerSeries', `RE valid for all k >=`, m0);
   
   c0 := Limit(DF[0], x=0, DIRECTION);
   if type(c0,infinity) then
      # now we have either a essential or a logarithmic singularity.
      c0 := limit(x*DF[0], x=0, DIRECTION);
      if type(c0,infinity) then
         ERROR(`essential singularity`) fi;
   fi;

   S := 0;
   for i from 0 to m0+m-1 do 
      if not assigned(DF[i]) then DF[i] := simpl(diff(DF[i-1],x)) fi;

      c0 := Limit(DF[i], x=0, DIRECTION)/i!;
      userinfo(3, 'FormalPowerSeries', `a[`,i,`] = `,c0);

      if has(c0, infinity) then
         # SEEMS TO BE A LOGARITHMIC SINGULARITY since the poles have 
         # already been removed!!! log(x)*x^i
         DF2[0] := simpl(diff(x^(-i)*DF[0], x));
         userinfo(3, 'FormalPowerSeries', `logarithmic singularity, working with (f'*`, 
                                          x^(-i),`)'`);
         userinfo(2, 'FormalPowerSeries', `=> f := `,DF2[0]);

         if   type(DF2[0], polynom(anything,x)) then
            S := DF2[0]
         else 
            S := constantRE(m, subs(k=k+i+1, leadcoeff)*(k+1), DF2, k, x);
         fi;
         S := PSInt(S,x);
         S0 := Limit(x^(-i)*DF[0]-S, x=0, DIRECTION);
         S := UpdateCoeff(S0+S, x, i);
         RETURN(S);
      fi;

      S := S + c0*x^i
   od;
   RETURN(S)
end: # constantRE

hypergeomRE := proc(m, R, leadcoeff, DF, k, x)
# solve hypergeometric RE 
#
#      a(k+m) = R(k) * a(k)
#
# where leadcoeff was the leading coeffitient of the RE
# and DF is a table where DF[i] = diff(f, x$i)
#
   local fract, i, m0, m1, m2, c0, ck, S, C, DF2, q, R2, a, b, A, B,
         S0, RSOLVE, R1, lc1, lc2, cond, j, Const, g;
   global DIRECTION;
   option `Copyright 1993 Dominik Gruntz, Wissenschaftliches Rechnen, ETH Zurich`;
   
   _EnvNestLevel := _EnvNestLevel+1:
   if _EnvNestLevel>20 then ERROR(`infinite loop detected`) fi;
   
   a := factor(leadcoeff);
   b := factor(leadcoeff*R);
   
   userinfo(3, 'FormalPowerSeries', `RE is of hypergeometric type.`);
   userinfo(3, 'FormalPowerSeries', `Symmetry number m := `, m);
   # userinfo(3, 'FormalPowerSeries', `leadcoeff := `, a);
   userinfo(3, 'FormalPowerSeries', `RE: `, print(a*'a'(k+m)=b*'a'(k)) );

   A := select(t -> not has(t, I),  {solve(a, k)});

   fract := map(t -> denom(t), A) minus {1};
   if fract <> {} then q := lcm(op(fract));
      g := subs(x=x^q, DF[0]);
      indets(g, (identical(x)^anything)^anything);
      map((t,x) -> t=x^(op(2,op(1,t))*op(2,t)), ", x);
      DF2[0] := eval(subs(", g));
      # DF2[0] := simplify(subs(x=x^q, DF[0]), power, 'symbolic');
      userinfo(3, 'FormalPowerSeries', `RE modified to `, k = k/q);
      userinfo(2, 'FormalPowerSeries', `=> f := `, DF2[0]);
      S := hypergeomRE(q*m, subs(k=k/q, R), subs(k=k/q, leadcoeff), DF2, k, x);
      S := eval(subs(x=x^(1/q), S));
      S := simplify(S, power);
      RETURN(S)
   fi;
   # {solutions in denR are integer}

   # Problem: Symbole in denR? extract these elements and generate
   # a condition list.
   if A <> {} then
      m1 := min(op(A));
      if type(m1, function) and op(0,m1)='min' then
         m2 := eval(select(type, m1, rational));
         if m2 = infinity then m2 := -m fi:
         userinfo(1, 'FormalPowerSeries', `provided that `, m2, ` <= `, m1);
         m1 := m2;
      fi;
      m1 := m1+m;
      if m1 <> 0 then
         DF2[0] := simplify(x^(-m1)*DF[0], power);
         userinfo(3, 'FormalPowerSeries', `working with x^`,eval(-m1),`*f`);
         userinfo(2, 'FormalPowerSeries', `=> f := `, DF2[0]);
         S := hypergeomRE(m, subs(k=k+m1, R), subs(k=k+m1, leadcoeff), DF2, k, x);
         S := UpdateCoeff(S, x, m1);
         RETURN(S)
      fi
   fi; # { min(op(A)) = -1}
   
   A := select(type, A, integer);

   if A = {} then m0 := 0
   else m0 := max(op(A))+1;
   fi;   
   userinfo(3, 'FormalPowerSeries', `RE valid for all k >= `, m0);

   B := select(t -> not has(t, I),  {solve(b, k)});
   B := select(type, B, integer);
   
#    
# test for essential singularity:

   c0 := Limit(DF[0], x=0, DIRECTION);
   if type(c0,infinity) then
      # now we have either a essential or a logarithmic singularity.
      c0 := limit(x*DF[0], x=0, DIRECTION);
      if type(c0,infinity) then
         ERROR(`essential singularity`) fi;
   fi;

   S := 0; C := {};
   for i from 0 to m0+m-1 do 
      if not assigned(DF[i]) then DF[i] := simpl(diff(DF[i-1],x)) fi;

      if not member(i, C) then

         c0 := Limit(DF[i], x=0, DIRECTION)/i!;
         userinfo(3, 'FormalPowerSeries', `a[`,i,`] = `,c0);

         if has(c0, infinity) then
            # SEEMS TO BE A LOGARITHMIC SINGULARITY since the poles have 
            # already been removed!!! log(x)*x^i
            DF2[0] := simpl(diff(x^(-i)*DF[0], x));
            userinfo(3, 'FormalPowerSeries',
               `logarithmic singularity, working with (f*`,x^(-i),`')`);
            userinfo(2, 'FormalPowerSeries',
               `=> f := `,DF2[0]);

            if   type(DF2[0], polynom(anything,x)) then
               S := DF2[0]
            else 
               R1  := subs(k=k+i, R);
               lc1 := subs(k=k+i, leadcoeff);
               R2  := normal( subs(k=k+1, R1)*(k+m+1) / (k+1) );
               lc2 := subs(k=k+1, lc1)*(k+1);
               S := hypergeomRE(m, R2, lc2, DF2, k, x);
            fi;
            S := PSInt(S,x);
            eval(subs(Sum=0,S));
            S0 := Limit(x^(-i)*DF[0]-", x=0, DIRECTION);
            S := UpdateCoeff(S0+S, x, i);
            RETURN(S);
         fi;
         # { c0 is finite }
         
         cond := A={} or (A intersect {seq(i+j*m, j=0..iquo(max(op(A))-i,m) )}={});
         if  (member(i, B) or (c0 = 0)) and cond then
            userinfo(3, 'FormalPowerSeries', 'a'(i+'j'*m)=0, `for all j>0.`);
            C := C union {seq(i+j*m, j=1..iquo(m0,m)+1)};
            S := S + c0*x^i;
         elif cond then
            C := C union {seq(i+j*m, j=1..iquo(m0,m)+1)};
            ck := traperror(hypergeomRsolve(subs(k=m*k+i, R), k, c0, 'RSOLVE'));
            if ck=lasterror then ERROR(`not a power series`) fi;
            if type(ck, `*`) then Const := select( (t,k)->not has(t,k), ck, k) else Const := 1 fi;
            ck := ck/Const; 
            if ck = 0 then S := S + c0*x^i;
            elif RSOLVE = 'finite' then S := S+Const*expand(sum(ck*x^(m*k+i), k=0..infinity))
            else           S := S + Const*Sum(ck*x^(m*k+i), k=0..infinity)
            fi;
         elif c0 <> 0 then # single terms
            S := S + c0*x^i;
         fi;
      fi
   od;
   
   RETURN(S)
end: # hypergeomRE

UpdateCoeff := proc(S, x, m) local Sx, SSum, C;
# C * Sum(ck*x^k) => C * Sum(ck*x^(k+m))
   if not has(S,x) then 
      S*x^m
   elif type(S, `+`) then 
      map(procname, S, x, m)
   elif type(S, `*`) then
      Sx := select(has, S, x); C := S/Sx;
      if C <> 1 then 
         C*procname(Sx,x,m)
      else # all terms have x
         SSum := select(has, S, Sum); C := S/SSum;
         if type(SSum, `*`) then ERROR(`UpdateCoeff: products of Sums not allowed`)
         elif SSum=1 then simplify(S*x^m, power)
         else C*procname(SSum,x,m)
         fi;
      fi;
   elif type(S, Sum(anything, `=`) ) then
      Sum( simplify(op(1,S)*x^m, power), op(2,S) )
   else 
      simplify(S*x^m, power)
   fi
end: # UpdateCoeff

PSInt := proc(S, x) local SSum, kk, Sx, C, S1, T, n, k, xn;
# C * Sum(ck*x^k) => C * Sum(ck*x^(k+1)/(k+1))
   if not has(S,x) then S*x
   elif type(S, `+`) then 
      map('procname', S, x)
   elif type(S, `*`) then
      Sx := select(has, S, x); C := S/Sx;
      if C <> 1 then
         C*procname(Sx,x)
      else # all terms have x
         SSum := select(has, S, Sum); C := S/SSum;
         if type(SSum, `*`) then ERROR(`PSInt: products of Sums not allowed`)
         elif SSum=1 then int(S,x)
         else C*procname(SSum,x)
         fi
      fi;
   elif type(S, Sum(anything, `=`) ) then
      S1 := op(1,S); xn := op(select(has, indets(S1, `^`), x)); n := op(2,xn); 
      k := op(indets(n,name)); T := 0;
      for kk from 0 while subs(k=kk,n)<0 do
         T := T + simplify(subs(k=kk, S1))
      od;
      n  := subs(k=k+kk, n):
      S1 := subs(k=k+kk, op(1,S));
      int(T,x) + Sum( S1/x^n*x^(n+1)/(n+1), op(2,S) )
   else int(S,x)
   fi
end: # PSInt

pochhammer := proc(n,k) local i;
   if n=1/2 then (2*k)!/4^k/k!
   elif type(n,integer) then
      if type(n,posint) then (n+k-1)!/(n-1)!
      elif type(n,negint) then (-1)^k*(-n)! / (-n-k)!
      else 0 fi
   elif type(n,rational) and denom(n)=2 then
      if   n>0 then pochhammer(2*n-1,2*k)/(4^k* pochhammer((2*n-1)/2,k))
      elif n<0 then (-1)^(-n+1/2)*pochhammer(1/2,-n+1/2) * pochhammer(1/2,k+n-1/2)
      fi
   elif has(n, I) then 'procname(args)'
   elif type(k, integer) then product(n+i, i=0..k-1)
   elif type(n, numeric) then 'procname(args)'
   else (n+k-1)!/(n-1)!
   fi
end:

`simplify/pochhammer` := proc(e)
   local P, R, k, p, pk, pn, q, qk, qn, r, j;
   option `Copyright 1993 Dominik Gruntz, Wissenschaftliches Rechnen, ETH Zurich`;

   if   type(e,`+`) then map(`simplify/pochhammer`, e)
   elif type(e,`*`) then
      if denom(e) <> 1 then 
         `simplify/pochhammer`(numer(e))/`simplify/pochhammer`(denom(e))
      else
         P := select(t -> type(t, 'pochhammer'(anything, anything)), convert(e, set)); 
         R := e/convert(P,`*`);
         while P <> {} do p := op(1,P); P := P minus {p}; pn := op(1,p); pk := op(2,p);
            for q in P do qn := op(1,q); qk := op(2,q);
               if pk=qk then k := pk;
                  if has(pn, I) or has(qn, I) then
                     if pn = evalc(conjugate(qn)) then
                        P := P minus {q}; p := 1;
                        r := Product((evalc('Re'(pn))+'j')^2+evalc('Im'(pn))^2, 'j'=0..k-1);
                        R := R*r; break
                     fi
                  elif pn-qn=1/2 then 
                     P := P minus {q}; r := pochhammer(2*qn, 2*k)/4^k; p := 1;
                     if type(r, 'pochhammer'(anything, anything)) then 
                        P := P union r else R := R*r fi;
                     break
                  elif qn-pn=1/2 then r := pochhammer(2*pn, 2*k)/4^k;
                     P := P minus {q}; p := 1;
                     if type(r, 'pochhammer'(anything, anything)) then 
                        P := P union r else R := R*r fi;
                     break
                  elif (pn=1/3 and qn=2/3) or (pn=2/3 and qn=1/3) then
                     r := (3*k)!/(k!*27^k);
                     P := P minus {q}; p := 1; R := R*r; break
                  elif (pn=2/3 and qn=4/3) or (pn=4/3 and qn=2/3) then
                     r := (1+3*k)!/(27^k*k!);
                     P := P minus {q}; p := 1; R := R*r; break
                  elif qn-pn=1/3 and type(pn-1/3, integer) and pn-1/3 <> 0 then
                     r := pochhammer(3*pn,3*k)/(27^k*pochhammer(pn+2/3,k));
                     P := P minus {q}; p := 1; R := R*r; break
                  elif pn-qn=1/3 and type(qn-1/3, integer) and qn-1/3 <> 0 then
                     r := pochhammer(3*qn,3*k)/(27^k*pochhammer(qn+2/3,k));
                     P := P minus {q}; p := 1; R := R*r; break
                  fi;
               fi;
            od;
            R := R*p
         od; R
      fi
   else e
   fi
end: # `simplify/pochhammer`

# de2re : converts a simple DE (i.e. a homogeneous linear DE with polynomial coefficients)
# to a recurrence equation:   f(n) = (D@@n)(f)
de2re := proc(de, f, x, a, n) local j, k, X, F, C;
   if type(de, `+`) then map(de2re, de, f, x, a, n)
   elif type(de, f(integer)) then k := op(1,de);
      pochhammer(n+1, k) * a(n+k)
   elif type(de, name^integer) and op(1,de) = x then
      j := op(2,de); a(n-j)
   elif type(de, `*`) then
      X := select(has, de, x);
      F := select(has, de, f);
      C := de/X/F;
      if X=1 then j := 0
      elif X=x then j := 1
      elif type(X, `^`) then j := op(2,X)
      else ERROR(`assumption 1 violated`) fi;
      if type(F, f(integer)) then k := op(1,F)
      else ERROR(`assumption 2 violated`) fi;
      C * pochhammer(n+1-j,k) * a(n+k-j)
   else ERROR(`assumption 0 violated`, de)
   fi
end:

hypergeomRsolve := proc(R, k, a0, RSOLVE:name)
# solves the recurrence equation
#
#      a(k+1) = R(k) * a(k),  a(0) = a0
#
   local Re, N, D, C, P, Q, d, n, e, ak, sols, II;
   options `Copyright 1993 Dominik Gruntz, Wissenschaftliches Rechnen, ETH Zurich`;
   Re := normal(R*(k+1)); C := 1; P := NULL; Q := NULL;
   N := numer(Re); if type(N, `*`) then N := convert(N, list) else N := [N] fi;
   D := denom(Re); if type(D, `*`) then D := convert(D, list) else D := [D] fi;
   for n in N do
      if not has(n, k) then C := C*n
      elif type(n, polynom(anything, k)) then
         if type(n, polynom(anything, k)^posint) then e := op(2,n); n := op(1,n) else e := 1 fi;
         if degree(n,k) = 1 then
            C := C*coeff(n,k,1)^e; P := P, coeff(n,k,0)/coeff(n,k,1) $ e
         elif degree(n,k) = 2 then
            C := C*coeff(n,k,2)^e;
            sols := [solve(n,k)]; sols := map(t->-t, sols); P := P, sols[1] $ e, sols[2] $ e
         else ERROR(degree, degree(n,k), `this case not yet implemented`)
         fi
      else ERROR(`don't know what to do with`, n);
      fi; 
   od;
   for d in D do
      if not has(d, k) then C := C/d
      elif type(d, polynom(anything, k)) then
         if type(d, polynom(anything, k)^posint) then e := op(2,d); d := op(1,d)
         else e := 1
         fi;
         if degree(d,k) = 1 then
            C := C/coeff(d,k,1)^e; Q := Q, coeff(d,k,0)/coeff(d,k,1) $ e;
         elif degree(d,k) = 2 then
            C := C/oeff(d,k,2)^e;
            sols := [solve(d,k)]; sols := map(t->-t, sols);
            Q := Q, sols[1] $ e, sols[2] $ e
         else ERROR(degree, degree(d,k), `this case not yet implemented`)
         fi
      else ERROR(`don't know what to do with`, d);
      fi; 
   od;
   P := map(simplify, [P]); Q := map(simplify, [Q]);

   # Falls im Zaehler negative Zahl, dann endliche Reihe !!!
   if select(t -> type(t,integer) and t<=0, P) <> [] 
   then RSOLVE := 'finite'  else RSOLVE := 'infinite' fi;

   P := convert(map((t,k) -> pochhammer(t,k), P, k), `*`);
   P := simplify(P, 'pochhammer');
   Q := convert(map((t,k) -> pochhammer(t,k), Q, k), `*`);
   Q := simplify(Q, 'pochhammer');
   if Q=0 then ERROR(`0 in denominator detected`) fi;
   ak := a0*C^k*P/Q/k!:
   ak := simplify(expand(subs(I=II, ak)), power);
   
   # simplifications of the result: 
   # 1) combine powers like (-1)^k*4^k to (-4)^k
   if type(ak,`*`) then convert(ak,list) else [ak] fi;
   C  := convert(select((t,k) -> type(t, `^`) and has(op(2,t),k), ", k), `*`);
   ak := ak/C * combine(simplify(C));
   ak := subs(II=I, ak);

   # 2) combine factors like k! * (k+1) to (k+1)!
   simplify(ak, factorial);
end: # hypergeomRsolve

`simplify/factorial` := proc(e)
   local num, den, simp;
   option `Copyright 1993 Dominik Gruntz, Wissenschaftliches Rechnen, ETH Zurich`;

   if   type(e,`+`) then map(procname, e)
   elif type(e,`*`) then
      num:= numer(e); if type(num,`*`) then num:= convert(num,set) else num:= {num} fi;
      den:= denom(e); if type(den,`*`) then den:= convert(den,set) else den:= {den} fi;
      simp := proc(Nin, Din, Nout, Dout) local s, f,n,d; n := Nin; d := Din;
         s := select(t -> type(t,`!`), n);
         while s <> {} do f := op(1,s); s := s minus {f}; f := op(1,f); # f!
            if member(f+1, n) then 
               n := n minus {f!, f+1} union {(f+1)!}; s := s union {(f+1)!};
            elif member(f, d) then
               n := n minus {f!} union {(f-1)!}; d := d minus {f}; s := s union {(f-1)!}; 
            fi
         od;
         Nout := n; Dout := d;
      end:
      simp(num,den,'num','den');
      simp(den,num,'den','num');
      convert(num, `*`)/convert(den, `*`)
   else e
   fi
end: # `simplify/factorial`

printDE := proc(de, F) local t, n, i, w; global x;
   n := max(op(map(op, indets(de, F(integer)))));
   t := NULL;
   for i from 0 to n do t := t, coeff(de, F(i) ) od;
   w := 0; for i from n by -1 to 0 do w := w + t[i+1]*cat(F,`'`$i)(x) od;
   w=0
end:

simpl := proc(e) local subspat, f;
   subspat := indets(e, 'trig') union indets(e, 'arctrig');
   subspat := map(f -> f=normal(expand(f)), ");
   subspat := subspat minus select(evalb, ");
   f := subs(subspat, e);
   f := simplify(f, 'arctrig');
   f := simplify(f, 'trig');
end:

Limit := proc() local c0;
   c0 := limit(args);
   if type(c0, range) then
      userinfo(3, 'FormalPowerSeries', `the following limit is a range: `, 
         'limit'(args)=c0);
      ERROR(`no initialisation found`) 
   fi;
   if has(c0, limit) then 
      userinfo(3, 'FormalPowerSeries', `Maple could not do the following limit: `, c0);
      ERROR(`no initialisation found`)
   fi;
   if has(c0, 'undefined') then
      userinfo(3, 'FormalPowerSeries', `the following limit is undefined: `,
        'limit'(args)=c0);
      ERROR(`no initialisation found`)
   fi;
   c0
end:

# Recursions of the form F(n, X) = A*F(n-1, X)+B*F(n-2, X)
Recursion[JacobiP, 4] := (n, a, b, x) ->  
   -(((2 + a + b + 2*(-1 + n))*(-1 + a + n)*(-1 + b+ n)*
        JacobiP(-2 + n, a, b, x))/((a + b + 2*(-1 + n))*n*(a + b + n)))+
  (JacobiP(-1 + n, a, b, x)*((a^2 - b^2)*(1 + a + b + 2*(-1 + n)) +
        x*GAMMA(a+b+2*n+1)/GAMMA(a+b-3+2*n+1)))/
    (2*(a + b + 2*(-1 + n))*n*(a + b + n)):
Recursion[JacobiG, 4] := (n, p, q, x) -> 
   ((-1 + n)*(-2 + n + p)*(-2 + n + q)*(1 - n - p + q)*
      JacobiG(n-2, p, q, x))/
    ((-4 + 2*n + p)*(-3 + 2*n + p)^2*(-2 + 2*n + p)) + 
   ((-2 + 4*n - 2*n^2 + 2*p - 2*n*p + q - p*q + 3*x - 8*n*x + 4*n^2*x -
       4*p*x + 4*n*p*x + p^2*x)*JacobiG(n-1, p, q, x))/
    ((-3 + 2*n + p)*(-1 + 2*n + p)):
Recursion[ChebyshevT,2] := (n, x) -> -ChebyshevT(n-2, x) + 2*x*ChebyshevT(n-1, x):
Recursion[ChebyshevU,2] := (n, x) -> -ChebyshevU(n-2, x) + 2*x*ChebyshevU(n-1, x):
Recursion[ChebyshevS,2] := (n, x) -> -ChebyshevS(n-2, x) + x*ChebyshevS(n-1, x):
Recursion[ChebyshevC,2] := (n, x) -> -ChebyshevC(n-2, x) + x*ChebyshevC(n-1, x):
Recursion[ChebyshevTstar, 2] := (n, x) -> 
   -ChebyshevTstar(n-2, x) + (-2 + 4*x)*ChebyshevTstar(n-1, x):
Recursion[ChebyshevUstar, 2] := (n, x) -> 
   -ChebyshevUstar(n-2, x) + (-2 + 4*x)*ChebyshevUstar(n-1, x):
Recursion[LegendreP,2] := (n, x) -> 
   -(((-1 + n)*LegendreP(n-2, x))/n) + ((1 + 2*(-1 + n))*x*LegendreP(n-1, x))/n:
Recursion[LegendrePstar, 2] := (n, x) -> 
   -(((-1 + n)*LegendrePstar(n-2, x))/n) +
    ((-1 - 2*(-1 + n) + (2 + 4*(-1 + n))*x)*LegendrePstar(n-1, x))/n:
Recursion[LaguerreL, 3] := (n, a, x) -> -(((-1 + a + n)*LaguerreL(n-2, a, x))/n) +
    ((1 + a + 2*(-1 + n) - x)*LaguerreL(n-1, a, x))/n:
Recursion[LaguerreL, 2] := (n,x) ->  -(n-1)*LaguerreL(n-2,x)/n+(-1+2*n-x)*LaguerreL(n-1,x)/n:
Recursion[HermiteH, 2] := (n, x) -> -2*(-1 + n)*HermiteH(n-2, x) +2*x*HermiteH(n-1, x):
Recursion[HermiteHe, 2] := (n, x) -> -((-1 + n)*HermiteHe(n-2, x)) +x*HermiteHe(n-1, x):
# AS 22.7. 
Recursion[WhittakerW, 3] := (n, m, x) -> 
   (3/2 + m - n)*(-3/2 + m + n)* WhittakerW(n-2, m, x) - (2*(-1 + n) - x)*WhittakerW(n-1, m, x):
Recursion[Bateman, 2] := (n,x) -> 
   (Bateman(n-2,x) *(2 - n) + ( 2*(-1 + n) - 2*x)* Bateman(n-1,x))/n:

RecursionSimplify := proc(e) local eq, L1, L2, L3, L4, L5, F, Nargs, n, a, X;
   eq := e;
   L1 := indets(eq,function);
   L2 := map(t -> [op(0,t), nops(t)], L1); # function name and nargs
   for a in L2 do
      if assigned(Recursion[op(a)]) then
         F := op(1,a); Nargs := op(2,a);
         
         # select those functions.
         L3 := select((t,F,n) -> op(0,t)=F and nops(t)=n, L1, F, Nargs);
         # separaete in different Arguments, e.g. L(n,X) and L(n,Y)
         L4 := map((t,N)-> [op(2..N,t)], L3, op(2,a));
         for X in L4 do
            L5 := map(t->op(1,t), select((t,X,n)->[op(2..n,t)]=X,L3,X,Nargs));
            while nops(L5) > 2 do
               n := max(op(L5)); if type(n,function) then n := op(1,n) fi;
               if member(n-1,L5) and member(n-2,L5) then
                  eq := subs(F(n,op(X))=Recursion[F,Nargs](n,op(X)), eq);
               fi;
               L5 := L5 minus {n}
            od
         od
      fi
   od;
   eq
end:

`diff/LegendreP`:= proc(n,f,x) local i;
   if nargs <> 3 or has(n, x) then
      'diff'(LegendreP(args[1..nargs-1]),args[nargs])
   else
     diff(f,x)*(n/(1-f^2)*LegendreP(n-1,f)-n*f/(1-f^2)*LegendreP(n,f))
   fi
end:

`diff/JacobiP`:= proc(n,a,b,f,x) local i;
   if nargs <> 5 or has([n,a,b], x) then
      'diff'(JacobiP(args[1..nargs-1]),args[nargs])
   else
     diff(f,x) * ((2*(a + n)*(b + n)*JacobiP(n-1, a, b, f))/((a + b + 2*n)*(1 - f^2)) +
     (n*(a - b - (a + b + 2*n)*f)* JacobiP(n, a, b, f))/((a + b + 2*n)*(1-f^2)))
   fi
end:

`diff/JacobiG`:= proc(n,a,b,f,x)
   if nargs <> 5 or has([n,a,b], x) then
      'diff'(JacobiG(args[1..nargs-1]),args[nargs])
   else
       diff(f,x) * ((2*n!*GAMMA(a + n)*((2*(a - b + n)*(-1 + b + n)*
       GAMMA(-2 +a + 2*n)*JacobiG(n-1, a, b, f))/((-1 + a + 2*n)*
       (4*f - 4*f^2)*(n-1)!*GAMMA(-1 + a + n))+
       (2*n*(a - b + n + f - a*f - 2*n*f)*GAMMA(a + 2*n) * 
       JacobiG(n, a,b, f))/((-1 + a + 2*n)*(4*f - 4*f^2)*n!*
       GAMMA(a + n))))/GAMMA(a + 2*n))
   fi
end:

`diff/ChebyshevT`:= proc(n,f,x) local F;
   if nargs <> 3 or has(n, x) then 
      'diff'(ChebyshevT(args[1..nargs-1]),args[nargs])
   else diff(f,x) * ((n*ChebyshevT(n-1, f))/(1 - f^2) - (n*f*ChebyshevT(n, f))/(1 - f^2))
   fi
end:

`diff/ChebyshevC`:= proc(n,f,x);
   if nargs <> 3 or has(n, x) then
      'diff'(ChebyshevC(args[1..nargs-1]),args[nargs])
   else
     diff(f,x) * ((n*ChebyshevC(n-1, f))/(2*(1 - f^2/4)) -
        (n*f*ChebyshevC(n, f))/(4*(1 - f^2/4)))
   fi
end:

`diff/ChebyshevS`:= proc(n,f,x);
   if nargs <> 3 or has(n, x) then
      'diff'(ChebyshevS(args[1..nargs-1]),args[nargs])
   else
      diff(f,x) * ( (2*(1 + n)*ChebyshevS(n-1, f))/(4 - f^2) +
         (n*f*ChebyshevS(n, f))/(-4 + f^2))
   fi
end:

`diff/ChebyshevTstar`:= proc(n,f,x);
   if nargs <> 3 or has(args[1], args[3]) then
      'diff'(ChebyshevTstar(args[1..nargs-1]),args[nargs])
   else
      diff(f,x) * (-((n*ChebyshevTstar(n-1, f))/(-2*f + 2*f^2)) +
         (n*(-1 + 2*f))/(-2*f + 2*f^2) *ChebyshevTstar(n, f))
   fi
end:

`diff/ChebyshevUstar`:= proc(n,f,x);
   if nargs <> 3 or has(args[1], args[3]) then
      'diff'(ChebyshevUstar(args[1..nargs-1]),args[nargs])
   else
      diff(f,x)*( 2*(((1 + n)*ChebyshevUstar(n-1, f))/(1 - (-1 + 2*f)^2)-
         (n*(-1 + 2*f)*ChebyshevUstar(n, f))/(1 - (-1 + 2*f)^2)))
   fi
end:

`diff/chebyshevU`:= proc(n,f,x);
   if nargs <> 3 or has(args[1], args[3]) then
      'diff'(chebyshevU(args[1..nargs-1]),args[nargs])
   else
      diff(f,x)*(((1 + n)*chebyshevU(n-1, f))/(1 - f^2) -
         (n*f*chebyshevU(n, f))/(1 -f^2))
   fi
end:

`diff/LegendrePstar`:= proc(n,f,x);
   if nargs <> 3 or has(args[1], args[3]) then
      'diff'(LegendrePstar(args[1..nargs-1]),args[nargs])
   else
      diff(f,x)*(2*((n*LegendrePstar(n-1, f))/(1 - (-1 + 2*f)^2)-
      (n*(-1 + 2*f)*LegendrePstar(n, f))/(1 - (-1 + 2*f)^2)))
   fi
end:

# laguerreL
`diff/LaguerreL`:= proc(n,a,f,x) local i;
   for i from 1 to nargs-2 do
      if has(args[i],args[nargs]) then ERROR(`derivative not defined`) fi;
   od;
   if nargs = 4 then
      diff(f,x) * (((-a - n)*LaguerreL(n-1, a, f))/f + n*LaguerreL(n, a, f)/f )
   elif nargs = 3 then
      diff(a,f) *((-n/a)*LaguerreL(n-1,a) + (n/a)*LaguerreL(n,a) )
   else ERROR(`incorrect number of arguments`)
   fi
end:

`diff/HermiteH`:= proc(n,f,x);
   if nargs <> 3 or has(args[1], args[3]) then
      'diff'(HermiteH(args[1..nargs-1]),args[nargs])
   else
      diff(f,x) * (2 * n * HermiteH(n-1, f))
   fi
end:

`diff/HermiteHe`:= proc(n,f,x);
   if nargs <> 3 or has(args[1], args[3]) then
      'diff'(HermiteHe(args[1..nargs-1]),args[nargs])
   else
      diff(f,x) * (n*HermiteHe(n-1, f))
   fi
end:

`diff/WhittakerW`:= proc(n,m,f,x)
   if nargs <> 4 or has([n,m],x) then
      'diff'(WhittakerW(args[1..nargs-1]),args[nargs])
   else
      diff(f,x) * ((1/f) ((1/2 * f - n) *WhittakerW(n, m, f)-
      WhittakerW(1 + n,m, f)))
   fi
end:

`diff/ChebyshevU`:= proc(n,f,x)
   if nargs <> 3 or has(args[1], args[3]) then
      'diff'(ChebyshevU(args[1..nargs-1]),args[nargs])
   else
      diff(f,x) * (((n+1)*ChebyshevU(n-1, f))/(1 - f^2)-(n*f*ChebyshevU(n, f))/(1 - f^2))
   fi
end:

`diff/Bateman`:=  proc(n,f,x)
   if nargs <> 3 or has(args[1], args[3]) then
      'diff'(Bateman(args[1..nargs-1]),args[nargs])
   else
      diff(f,x) * (Bateman(n-1,f) - n*Bateman(n-1,x) + n*Bateman(n,f)
      - f*Bateman(n,f))/f
   fi
end:

# Help Pages
#############

`help/text/FormalPowerSeries` := TEXT(
`FUNCTION: FormalPowerSeries`,
`   `,
`CALLING SEQUENCE:`,
`   FormalPowerSeries(expr, eqn, dir, n, method)`,
`   `,
`PARAMETERS:`,
`   expr   - an expression`,
`   eqn    - an equation (e.g. x = a) or name (e.g. x)`,
`   dir    - (optional) direction: one of left, right, real, or complex`,
`   n      - (optional) a non-negative integer`,
`   method - (optional) method: one of hypergeometric, rational, explike`,
`   `,
`SYNOPSIS:    `,
`- This function can expand meromorphic functions of certain type into`,
`  their corresponding Laurent-Puiseux series as a sum of terms of the form`,
`   `,
`                   infinity`,
`                    -----`,
`                     \\                   (m k + s)/q`,
`                      )     a(k) (x - x0)`,
`                     /`,
`                    -----`,
`                    k = 0`,
`   `,
`  where m is called the "symmetry number", s is the "shift number",`,
`  and x0 is the "point of development". The following types are`,
`  supported:`,
`   `,
`       o functions of "rational type", which are either rational or have a`,
`         rational derivative of some order;`,
`   `,
`       o functions of "hypergeometric type" where a(k+m)/a(k) is a rational `,
`         function for some integer m;`,
`   `,
`       o functions of "explike type" which satisfy a linear homogeneous`,
`         differential equation with constant coefficients.`,
`   `,
`- the FormalPowerSeries function tries to find a formal power series expansion `,
`  for expr with respect to the variable x at the point of development a. `,
`  It also works for formal Laurent series, and in certain cases of `,
`  fractional, logarithmic, or essential singularities. `,
`   `,
`- FormalPowerSeries first looks for a homogeneous differential equation for`,
`  expr, hence for a function the derivatives must be known. Below you find`,
`  an example where the power series of an unknown function f is computed,`,
`  given the derivative of f.`,
`   `,
`- the integer argument n controls the depth for the search of a differential `,
`  equation for expr. Higher values for n will increase the chance to find`,
`  the solution, but increases the complexity as well. The default value`,
`  for n is 5. `,
`   `,
`- To compute asymptotic power series one may expand the function around`,
`  infinity, e.g. FormalPowerSeries(exp(x^2)*(1-erf(x)),x=infinity,left).`,
`  The result will be a power series as well. FormalPowerSeries(erf(x),`,
`  x=infinity,left) will be 1, which stands for the power series`,
`  1 + 0*x^(-1) + 0*x^(-2) + .... which is correct in the context of`,
`  asymptotic series.`,
`   `,
`- if dir is not specified, complex is chosen, except if the point of`,
`  expansion is either infinity or -infinity. In this case real is taken`,
`  as direction. See also limit[dirs].`,
`   `,
`- If a method parameter is specified, then the named method will be used.`,
`  Currently accepted names are 'hypergeometric', 'explike' and 'rational'.`, 
`   `,
`- If eqn evaluates to a name x then the equation x = 0 is assumed`,
`   `,
`- For a complete list of known functions, see ?inifcns .`,
`   `,
`REFERENCE:   `,
`-  Wolfram Koepf: Power Series in Computer Algebra, Journal of Symbolic `,
`   Computation 13, 1992, 581-603.`,
`   `,
`-  Wolfram Koepf: Algorithmic development of power series, Proceedings of`,
`   the "Conference on artificial intelligence and symbolic computations"`,
`   Karlsruhe, Germany, August 1992, LNCS, Springer.`,
`   `,
`-  Wolfram Koepf: Examples for the algorithmic calculation of formal Puiseux,`,
`   Laurent and power series. SIGSAM Bulletin 27, 1993, 20-32.`,
`   `,
`EXAMPLES:   `,
`   `,
`> FormalPowerSeries(sin(x),x);`,
`                           infinity`,
`                            -----       k  (2 k + 1)`,
`                             \\      (-1)  x`,
`                              )     ----------------`,
`                             /         (2 k + 1)!`,
`                            -----`,
`                            k = 0`,
`   `,
`> FormalPowerSeries(ln(x), x=1);`,
`                         infinity`,
`                          -----       k        (k + 1)`,
`                           \\      (-1)  (x - 1)`,
`                            )     --------------------`,
`                           /              k + 1`,
`                          -----`,
`                          k = 0`,
`   `,
`> FormalPowerSeries(exp(x)*sin(x),x);`,
`                       infinity`,
`                        -----     k 1/2                k`,
`                         \\      (2 )    sin(1/4 k Pi) x`,
`                          )     ------------------------`,
`                         /                 k!`,
`                        -----`,
`                        k = 0`,
`   `,
`> FormalPowerSeries( sqrt( (1-sqrt(1-x))/x ) , x); `,
`                             /infinity                    \\`,
`                             | -----             (- k)  k |`,
`                         1/2 |  \\       (4 k)! 16      x  |`,
`                    1/2 2    |   )     -------------------|`,
`                             |  /              2          |`,
`                             | -----   ((2 k)!)  (2 k + 1)|`,
`                             \\ k = 0                      /`,
`   `,
`> # asymptotic power series:`,
`> FormalPowerSeries(exp(x^2)*(1-erf(x)),x=infinity,left);`,
`   `,
`                infinity`,
`                 -----           (- k)     (- k)      (2 k + 1)`,
`                  \\      (2 k)! 4      (-1)      (1/x)`,
`                   )     --------------------------------------`,
`                  /                        k!`,
`                 -----`,
`                 k = 0`,
`                -----------------------------------------------`,
`                                       1/2`,
`                                     Pi`,
`   `,
`> FormalPowerSeries(sin(1/x), x=infinity);`,
`   `,
`                         infinity`,
`                          -----       k      (2 k + 1)`,
`                           \\      (-1)  (1/x)`,
`                            )     --------------------`,
`                           /           (2 k + 1)!`,
`                          -----`,
`                          k = 0`,
`   `,
`> # we define only the derivative of f(x)`,
`> ``diff/f`` := proc(a,x) f(a)*diff(a,x) end:`,
`> FormalPowerSeries(f(x),x=0);`,
`   `,
`                                   /infinity     \\`,
`                                   | -----     k |`,
`                                   |  \\       x  |`,
`                              f(0) |   )     ----|`,
`                                   |  /       k! |`,
`                                   | -----       |`,
`                                   \\ k = 0       /`,
`   `,
`> FormalPowerSeries((-1/2*x+1/6*x^3)*arctan(x)+(-1/4*x^2+1/12)*`,
`>    ln(x^2+1)+5/12*x^2+1/4, x=0, hypergeometric);`,
`   `,
`                      /infinity                                    \\`,
`                      | -----                 k  (2 k + 4)         |`,
`                      |  \\                (-1)  x                  |`,
`            1/4 + 1/4 |   )     -----------------------------------|`,
`                      |  /      (k + 2) (2 k + 1) (k + 1) (3 + 2 k)|`,
`                      | -----                                      |`,
`                      \\ k = 0                                      /`,
`   `,
`SEE ALSO:  SimpleDE, SimpleRE, inifcns, taylor, series, Sum`
):
`help/text/FPS` := `help/text/FormalPowerSeries`:

`help/text/SimpleDE` := TEXT(
`FUNCTION: SimpleDE - find a linear differential equation`,
`   `,
`CALLING SEQUENCES:`,
`   SimpleDE(f, x)`,
`   SimpleDE(f, x, n)`,
`   SimpleDE(f, x, F)`,
`   SimpleDE(f, x, n, F)`,
`   `,
`PARAMETERS:`,
`   f    -  an algebraic expression in x`,
`   x    -  a name `,
`   n    -  (optional) a nonnegative integer (maximal order for search `,
`           for linear differential equation, default = 5)`,
`   F    -  (optional) a name which is used in the result.`,
`   `,
`SYNOPSIS:    `,
`- This function tries to find a homogeneous differential equation with `,
`  polynomial coefficients of lowest possible order satisfied by a given`,
`  expression of variable x up to order n.`,
`       `,
`EXAMPLE:   `,
`   `,
`> SimpleDE(arcsin(x)^2, x, F);`,
` `,
`                       /   3      \\                     /   2      \\`,
`                       |  d       |   /  d      \\       |  d       |`,
`       (x - 1) (1 + x) |----- F(x)| + |---- F(x)| + 3 x |----- F(x)| = 0`,
`                       |   3      |   \\ dx      /       |   2      |`,
`                       \\ dx       /                     \\ dx       /`,
`  `,
`> SimpleDE(BesselI(n,x), x, B);`,
`  `,
`             /   2      \\`,
`             |  d       |  2       2    2         /  d      \\`,
`             |----- B(x)| x  + (- x  - n ) B(x) + |---- B(x)| x = 0`,
`             |   2      |                         \\ dx      /`,
`             \\ dx       /`,
`   `,
`> normal(eval(subs(B(x)=BesselI(n,x),")));`,
`  `,
`                                     0 = 0`,
`  `,
`SEE ALSO: FormalPowerSeries, SimpleRE`,
`   `):

`help/text/SimpleRE` := TEXT(
`FUNCTION: SimpleRE - find a reccurence equation for the coefficients`,
`                     of the powerseries of a given function.`,
`   `,
`CALLING SEQUENCES:`,
`   SimpleRE(f, x)`,
`   SimpleRE(f, x, n)`,
`   SimpleRE(f, x, a, k)`,
`   SimpleRE(f, x, n, a, k)`,
`   `,
`PARAMETERS:`,
`   f    -  an algebraic expression in x`,
`   x    -  a name `,
`   n    -  (optional) a nonnegative integer (maximal order for search `,
`           for linear differential equation, default = 5)`,
`   a,k  -  (optional) names which are used in the result.`,
`   `,
`SYNOPSIS:    `,
`- This function tries to find a homogeneous differential equation with `,
`  polynomial coefficients of lowest possible order satisfied by a given`,
`  expression of variable x up to order n and converts it to a recurrence`,
`  equation for the coefficients of the corresponding (Laurent-Puiseux)`,
`  power series.`,
`       `,
`EXAMPLE:   `,
`   `,
`> SimpleRE(x/(1-x-x^2), x);`,
` `,
`             (1 - k) a(k) + (k - 1) a(k - 1) + (k - 1) a(k - 2) = 0`,
`  `,
`> SimpleRE(sin(x), x, b, n);`,
`  `,
`                         2`,
`               b(n + 2) n  + 3 b(n + 2) n + 2 b(n + 2) + b(n) = 0`,
`  `,
`SEE ALSO: FormalPowerSeries, SimpleDE`,
`   `):

#save FormalPowerSeries, FPS, SimpleDE, SimpleRE,
#     RationalAlgorithm, ComplexApart,
#     constantRE, hypergeomRE,
#     PS, FindDE, de2re, hypergeomRsolve, UpdateCoeff, PSInt, 
#     printDE, simpl, Limit,
#     pochhammer, `simplify/pochhammer`, `simplify/factorial`,
#     Recursion, RecursionSimplify,
#     `diff/LegendreP`, `diff/JacobiP`, `diff/JacobiG`, `diff/ChebyshevT`, `diff/ChebyshevC`,
#     `diff/ChebyshevS`,
#     `diff/ChebyshevTstar`, `diff/ChebyshevUstar`, `diff/chebyshevU`, 
#     `diff/LegendrePstar`, `diff/LaguerreL`, `diff/HermiteH`, 
#     `diff/HermiteHe`, `diff/Bateman`, `diff/WhittakerW`, `diff/ChebyshevU`, 
#     `diff/Bateman`,
#     `help/text/FormalPowerSeries`, `help/text/FPS`,
#     `help/text/SimpleDE`, `help/text/SimpleRE`,
#     `FPS.m`;
#
#done;

alias( PS = PS ):
alias( ComplexApart = ComplexApart ):
alias( RationalAlgorithm = RationalAlgorithm ):
alias( constantRE = constantRE ):
alias( hypergeomRE = hypergeomRE ):
alias( UpdateCoeff = UpdateCoeff ):
alias( PSInt = PSInt ):
alias( de2re = de2re ):
alias( hypergeomRsolve = hypergeomRsolve ):
alias( printDE = printDE ):
alias( DIRECTION = DIRECTION ):
alias( simpl = simpl ):
alias( FindDE = FindDE ):
alias( Limit = Limit ):
alias( Recursion =  Recursion ):
alias( RecursionSimplify = RecursionSimplify ):

