)abbrev package FS2UPS2 FunctionSpaceToUnivariatePowerSeries2
++ Author: Clifton J. Williamson
++ Date Created: 21 March 1989
++ Basic Operations:
++ Related Domains:
++ Also See:
++ AMS Classifications:
++ Keywords: elementary function, power series
++ Examples:
++ References:
++ Description:
++   This package converts expressions in some function space to power
++   series in a variable x with coefficients in that function space.
++   The function \spadfun{exprToUPS} converts expressions to power series
++   whose coefficients do not contain the variable x. The function
++   \spadfun{exprToGenUPS} converts functional expressions to power series
++   whose coefficients may involve functions of \spad{log(x)}.
FunctionSpaceToUnivariatePowerSeries2(R, FE, Expon, UPS, TRAN, UTS, TEXP,
                                     coerce_Ex : Expon -> FE, x) : _
 Exports == Implementation where
  R     : Join(GcdDomain, Comparable, RetractableTo Integer,
               LinearlyExplicitOver Integer)
  FE    : Join(AlgebraicallyClosedField, TranscendentalFunctionCategory,
               Algebra(Fraction(Integer)), FunctionSpace R)
  Expon : Join(OrderedAbelianMonoid, OrderedRing)
  UPS   : Join(UnivariatePowerSeriesCategory(FE, Expon), Field,
               PowerSeriesCategory(FE, Expon, SingletonAsOrderedSet),
               TranscendentalFunctionCategory)
  TRAN  : PartialTranscendentalFunctions UPS
  UTS : UnivariateTaylorSeriesCategory(FE)
  TEXP : TaylorSeriesExpansion(FE, Expon, SingletonAsOrderedSet, UPS, UTS)
  x     : Symbol
  B       ==> Boolean
  BOP     ==> BasicOperator
  I       ==> Integer
  NNI     ==> NonNegativeInteger
  K       ==> Kernel FE
  L       ==> List
  RN      ==> Fraction Integer
  ATANFLAG ==> Union("complex", _
                    "real: two sides", _
                    "real: left side", _
                    "real: right side", _
                    "just do it")
  PROBLEM ==> String
  FUNCTIONNAME ==> String
  SY      ==> Symbol
  PCL     ==> PolynomialCategoryLifting(IndexedExponents K, K, R, SMP, FE)
  POL     ==> Polynomial R
  SMP     ==> SparseMultivariatePolynomial(R, K)
  SUP     ==> SparseUnivariatePolynomial Polynomial R
  Problem ==> Record(func : String, prob : String)
  Result  ==> Union(%series : UPS, %problem : Problem)
  SIGNEF  ==> ElementaryFunctionSign(R, FE)
  U1      ==> Union("invertible", "zero", "bad")
  OPT_MAP ==> Union(FE -> Boolean, "none")
  OPT_REC ==> Record(pos_Check? : Boolean, atan_Flag : ATANFLAG,
                     coeff_check? : OPT_MAP, inv_check? : OPT_MAP,
                       zero_check? : OPT_MAP, log_x_replace : Boolean,
                        log_x_val : FE)

  Exports ==> with
    exprToUPS : (FE, B, ATANFLAG) -> Result
      ++ exprToUPS(fcn, posCheck?, atanFlag) converts the expression
      ++ \spad{fcn} to a power series.  If \spad{posCheck?} is true,
      ++ log's of negative numbers are not allowed nor are nth roots of
      ++ negative numbers with n even.  If \spad{posCheck?} is false,
      ++ these are allowed.  \spad{atanFlag} determines how the case
      ++ \spad{atan(f(x))}, where \spad{f(x)} has a pole, will be treated.
      ++ The possible values of \spad{atanFlag} are \spad{"complex"},
      ++ \spad{"real: two sides"}, \spad{"real: left side"},
      ++ \spad{"real: right side"}, and \spad{"just do it"}.
      ++ If \spad{atanFlag} is \spad{"complex"}, then no series expansion
      ++ will be computed because, viewed as a function of a complex
      ++ variable, \spad{atan(f(x))} has an essential singularity.
      ++ Otherwise, the sign of the leading coefficient of the series
      ++ expansion of \spad{f(x)} determines the constant coefficient
      ++ in the series expansion of \spad{atan(f(x))}.  If this sign cannot
      ++ be determined, a series expansion is computed only when
      ++ \spad{atanFlag} is \spad{"just do it"}.  When the leading term
      ++ in the series expansion of \spad{f(x)} is of odd degree (or is a
      ++ rational degree with odd numerator), then the constant coefficient
      ++ in the series expansion of \spad{atan(f(x))} for values to the
      ++ left differs from that for values to the right.  If \spad{atanFlag}
      ++ is \spad{"real: two sides"}, no series expansion will be computed.
      ++ If \spad{atanFlag} is \spad{"real: left side"} the constant
      ++ coefficient for values to the left will be used and if \spad{atanFlag}
      ++ \spad{"real: right side"} the constant coefficient for values to the
      ++ right will be used.
      ++ If there is a problem in converting the function to a power series,
      ++ a record containing the name of the function that caused the problem
      ++ and a brief description of the problem is returned.
      ++ When expanding the expression into a series it is assumed that
      ++ the series is centered at 0.  For a series centered at a, the
      ++ user should perform the substitution \spad{x -> x + a} before calling
      ++ this function.

    exprToGenUPS : (FE, B, ATANFLAG) -> Result
      ++ exprToGenUPS(fcn, posCheck?, atanFlag) converts the expression
      ++ \spad{fcn} to a generalized power series.  If \spad{posCheck?}
      ++ is true, log's of negative numbers are not allowed nor are nth roots
      ++ of negative numbers with n even. If \spad{posCheck?} is false,
      ++ these are allowed.  \spad{atanFlag} determines how the case
      ++ \spad{atan(f(x))}, where \spad{f(x)} has a pole, will be treated.
      ++ The possible values of \spad{atanFlag} are \spad{"complex"},
      ++ \spad{"real: two sides"}, \spad{"real: left side"},
      ++ \spad{"real: right side"}, and \spad{"just do it"}.
      ++ If \spad{atanFlag} is \spad{"complex"}, then no series expansion
      ++ will be computed because, viewed as a function of a complex
      ++ variable, \spad{atan(f(x))} has an essential singularity.
      ++ Otherwise, the sign of the leading coefficient of the series
      ++ expansion of \spad{f(x)} determines the constant coefficient
      ++ in the series expansion of \spad{atan(f(x))}.  If this sign cannot
      ++ be determined, a series expansion is computed only when
      ++ \spad{atanFlag} is \spad{"just do it"}.  When the leading term
      ++ in the series expansion of \spad{f(x)} is of odd degree (or is a
      ++ rational degree with odd numerator), then the constant coefficient
      ++ in the series expansion of \spad{atan(f(x))} for values to the
      ++ left differs from that for values to the right.  If \spad{atanFlag}
      ++ is \spad{"real: two sides"}, no series expansion will be computed.
      ++ If \spad{atanFlag} is \spad{"real: left side"} the constant
      ++ coefficient for values to the left will be used and if \spad{atanFlag}
      ++ \spad{"real: right side"} the constant coefficient for values to the
      ++ right will be used.
      ++ If there is a problem in converting the function to a power
      ++ series, we return a record containing the name of the function
      ++ that caused the problem and a brief description of the problem.
      ++ When expanding the expression into a series it is assumed that
      ++ the series is centered at 0.  For a series centered at a, the
      ++ user should perform the substitution \spad{x -> x + a} before calling
      ++ this function.
    exprToPS : (FE, B, ATANFLAG, B, FE) -> Result
      ++ exprToPS(fcn, posCheck?, atanFlag, log_flag, log_val) is
      ++ like exprToUPS, but if \spad{log_flag} is true it replaces
      ++ logarithms of expansion variable by \spad{log_val}
    exprToPS : (FE, B, ATANFLAG, B, FE, FE -> B, FE -> B, FE -> B) -> Result
      ++ exprToPS(fcn, posCheck?, atanFlag, log_flag, log_val, coef_chk,
      ++ inv_chk, zero_chk) is like exprToPS(fcn, posCheck?, atanFlag,
      ++ log_flag, log_val), but fails if \spad{coef_chk} is false for some
      ++ coefficient of \spad{fcn} or if expansion process needs to
      ++ invert element of \spad{FE} for which \spad{inv_chk} is
      ++ false
    localAbs : FE -> FE
      ++ localAbs(fcn) = \spad{abs(fcn)} or \spad{sqrt(fcn^2)} depending
      ++ on whether or not FE has a function \spad{abs}.  This should be
      ++ a local function, but the compiler won't allow it.
    atan1 : UPS -> UPS
    integ : UPS -> UPS
      ++ integ should be local but conditional
    integ_df : (UPS, UPS) -> UPS
      ++ integ_df should be local but conditional
    powToUPS : (List(FE), OPT_REC) -> Result
      ++ powToUPS should be local but conditional

  Implementation ==> add

    ratIfCan            : FE -> Union(RN,"failed")
    carefulNthRootIfCan : (UPS, NNI, OPT_REC, B) -> Result
    stateProblem        : (PROBLEM, PROBLEM) -> Result
    polyToUPS           : (SUP, OPT_REC) -> Result
    listToUPS           : (L FE, (FE, OPT_REC) -> Result, OPT_REC,
                           UPS, (UPS, UPS) -> UPS) -> Result
    isNonTrivPower      : FE -> Union(Record(val:FE,exponent:I),"failed")
    i_expr_to_PS        : (FE, OPT_REC) -> Result
    powerToUPS          : (FE, I, OPT_REC) -> Result
    kernelToUPS         : (K, OPT_REC) -> Result
    nthRootToUPS        : (FE, NNI, OPT_REC) -> Result
    logToUPS            : (FE, OPT_REC) -> Result
    atancotToUPS        : (FE, OPT_REC, I) -> Result
    applyIfCan          : (UPS -> Union(UPS,"failed"), FE, FUNCTIONNAME,
                           OPT_REC) -> Result
    tranToUPS           : (K, FE, OPT_REC) -> Result
    newElem             : FE -> FE
    smpElem             : SMP -> FE
    k2Elem              : K -> FE
    contOnReals?        : FUNCTIONNAME -> B
    bddOnReals?         : FUNCTIONNAME -> B
    iExprToGenUPS       : (FE, OPT_REC) -> Result
    opsInvolvingX       : FE -> L BOP
    opInOpList?         : (SY, L BOP) -> B
    exponential?        : FE -> B
    productOfNonZeroes? : FE -> B
    powerToGenUPS       : (FE, I, OPT_REC) -> Result
    kernelToGenUPS      : (K, OPT_REC) -> Result
    nthRootToGenUPS     : (FE, NNI, OPT_REC) -> Result
    logToGenUPS         : (FE, OPT_REC) -> Result
    expToGenUPS         : (FE, OPT_REC) -> Result
    expGenUPS           : (UPS, OPT_REC) -> Result
    atancotToGenUPS     : (FE, FE, OPT_REC, I) -> Result
    genUPSApplyIfCan    : (UPS -> Union(UPS,"failed"), FE, FUNCTIONNAME,
                           OPT_REC) -> Result
    applyBddIfCan       : (FE, UPS -> Union(UPS,"failed"), FE, FUNCTIONNAME,
                           OPT_REC) -> Result
    tranToGenUPS        : (K, FE, OPT_REC) -> Result
    powToGenUPS         : (L FE, OPT_REC) -> Result

    ZEROCOUNT : I := 1000
    -- number of zeroes to be removed when taking logs or nth roots

    ratIfCan fcn == retractIfCan(fcn)@Union(RN,"failed")

    check_inverse(coef : FE, opt_rec : OPT_REC) : B ==
        if opt_rec.inv_check? case (FE -> B) then
            ((opt_rec.inv_check?)::(FE -> B))(coef)
        else
            true

    check_zero(coef : FE, opt_rec : OPT_REC) : B ==
        if opt_rec.zero_check? case (FE -> B) then
            ((opt_rec.zero_check?)::(FE -> B))(coef)
        else
            coef = 0

    carefulNthRootIfCan(ups, n, opt_rec, rightOnly?) ==
      -- similar to 'nthRootIfCan', but it is fussy about the series
      -- it takes as an argument.  If 'n' is EVEN and 'posCheck?'
      -- is true then the leading coefficient of the series must
      -- be POSITIVE.  In this case, if 'rightOnly?' is false, the
      -- order of the series must be zero.  The idea is that the
      -- series represents a real function of a real variable, and
      -- we want a unique real nth root defined on a neighborhood
      -- of zero.
      posCheck? := opt_rec.pos_Check?
      n < 1 => error "nthRoot: n must be positive"
      deg := degree ups
      deg1 := deg + ZEROCOUNT :: Expon
      while check_zero(coef := coefficient(ups, deg), opt_rec)
            and deg < deg1 repeat
          if coef ~= 0 then ups := reductum(ups)
          deg := order(ups, deg1)
          (coef := coefficient(ups, deg)) = 0 =>
              error "log of series with many leading zero coefficients"
      -- if 'posCheck?' is true, we do not allow nth roots of negative
      -- numbers when n in even
      if even?(n :: I) then
        if posCheck? and ((signum := sign(coef)$SIGNEF) case I) then
          (signum :: I) = -1 =>
            return stateProblem("nth root","negative leading coefficient")
          not rightOnly? and not zero? deg => -- nth root not unique
            return stateProblem("nth root","series of non-zero order")
      not(check_inverse(coef, opt_rec)) =>
          stateProblem("nth root", "need to invert bad coefficient")
      (ans := nthRootIfCan(ups,n)) case "failed" =>
        stateProblem("nth root","no nth root")
      [ans :: UPS]

    stateProblem(function, problem) ==
      -- records the problem which occured in converting an expression
      -- to a power series
      [[function, problem]]

    exprToUPS(fcn, posCheck?, atanFlag) ==
        i_expr_to_PS(fcn, [posCheck?, atanFlag, "none", "none",
                           "none", false, 0]$OPT_REC)

    exprToPS(fcn, posCheck?, atanFlag, log_flag, log_val) ==
        i_expr_to_PS(fcn, [posCheck?, atanFlag, "none", "none", "none",
                           log_flag, log_val]$OPT_REC)

    exprToPS(fcn, posCheck?, atanFlag, log_flag, log_val,
             coef_chk, inv_chk, zero_chk) ==
       i_expr_to_PS(fcn, [posCheck?, atanFlag, coef_chk, inv_chk, zero_chk,
                           log_flag, log_val]$OPT_REC)

    i_expr_to_PS(fcn, opt_rec) ==
      -- converts a functional expression to a power series
      --!! The following line is commented out so that expressions of
      --!! the form a^b will be normalized to exp(b * log(a)) even if
      --!! 'a' and 'b' do not involve the limiting variable 'x'.
      --!!                         - cjw 1 Dec 94
      --not member?(x, variables fcn) => [monomial(fcn, 0)]
      (poly := retractIfCan(fcn)@Union(POL,"failed")) case POL =>
          polyToUPS(univariate(poly::POL, x), opt_rec)
      (sum := isPlus fcn) case L(FE) =>
          listToUPS(sum :: L(FE), i_expr_to_PS, opt_rec, 0,
                  (y1, y2) +-> y1 + y2)
      (prod := isTimes fcn) case L(FE) =>
          listToUPS(prod :: L(FE), i_expr_to_PS, opt_rec, 1,
                  (y1, y2) +-> y1*y2)
      (expt := isNonTrivPower fcn) case Record(val : FE, exponent : I) =>
          power := expt :: Record(val : FE, exponent : I)
          powerToUPS(power.val, power.exponent, opt_rec)
      (ker := retractIfCan(fcn)@Union(K,"failed")) case K =>
          kernelToUPS(ker :: K, opt_rec)
      error "exprToUPS: neither a sum, product, power, nor kernel"

    polyToUPS(poly, opt_rec) ==
      -- converts a polynomial to a power series
      zero? poly => [0$UPS]
      -- we don't start with 'ans := 0' as this may lead to an
      -- enormous number of leading zeroes in the power series
      deg  := degree poly
      coef := leadingCoefficient(poly) :: FE
      if opt_rec.coeff_check? case (FE -> B) then
          if not((opt_rec.coeff_check?::(FE -> B))(coef)) then
              return stateProblem("polyToUPS", "bad coeff")
      ans  := monomial(coef, deg :: Expon)$UPS
      poly := reductum poly
      while not zero? poly repeat
        deg  := degree poly
        coef := leadingCoefficient(poly) :: FE
        if opt_rec.coeff_check? case (FE -> B) then
            if not((opt_rec.coeff_check?::(FE -> B))(coef)) then
                return stateProblem("polyToUPS", "bad coeff")
        ans  := ans + monomial(coef, deg :: Expon)$UPS
        poly := reductum poly
      [ans]

    listToUPS(list, feToUPS, opt_rec, ans, op) ==
      -- converts each element of a list of expressions to a power
      -- series and returns the sum of these series, when 'op' is +
      -- and 'ans' is 0, or the product of these series, when 'op' is *
      -- and 'ans' is 1
      while not(empty?(list)) repeat
        (term := feToUPS(first list, opt_rec)) case %problem =>
          return term
        ans := op(ans, term.%series)
        list := rest list
      [ans]

    isNonTrivPower fcn ==
      -- is the function a power with exponent other than 0 or 1?
      (expt := isPower fcn) case "failed" => "failed"
      power := expt :: Record(val : FE, exponent : I)
      (power.exponent = 1) => "failed"
      power

    powerToUPS(fcn, n, opt_rec) ==
      -- converts an integral power to a power series
      (b := i_expr_to_PS(fcn, opt_rec)) case %problem => b
      n > 0 => [(b.%series) ^ (n::PositiveInteger)]
      -- check lowest order coefficient when n < 0
      ups := b.%series; deg := degree ups
      deg1 := deg + ZEROCOUNT :: Expon
      while check_zero(coef := coefficient(ups, deg), opt_rec)
            and deg < deg1 repeat
          if coef ~= 0 then ups := reductum(ups)
          deg := order(ups, deg1)
          (coef := coefficient(ups, deg)) = 0 =>
              error "inverse of series with many leading zero coefficients"
      not(check_inverse(coef, opt_rec)) =>
          stateProblem("power", "need to invert bad coefficient")
      [ups ^ n]

    ELITS := EllipticFunctionsUnivariateTaylorSeries(FE, UTS)

    ARES ==> Record(osers : L(UPS), sers : L(UPS), sere : L(FE), sers0 : L(FE))
    ARESU ==> Union(ARES, Result)

    handle_args(args : L(FE), opt_rec : OPT_REC) : ARESU ==
        losers : L(UPS) := []
        lsers :  L(UPS) := []
        lsere : L(FE) := []
        lcoef : L(FE) := []
        for arg in args repeat
            not(member?(x, variables(arg))) =>
                losers := cons(arg::UPS, losers)
                lsers := cons(0$UPS, lsers)
                lsere := cons(0$FE, lsere)
                lcoef := cons(arg, lcoef)
            (nsu := i_expr_to_PS(arg, opt_rec)) case %problem =>
                return nsu
            ups := nsu.%series
            order(ups, 0) < 0 =>
                return stateProblem("handle_args", "argument not Taylor")
            coef := coefficient(ups, 0)
            losers := cons(ups, losers)
            lcoef := cons(coef, lcoef)
            lsere := cons(1$FE, lsere)
            lsers := cons(ups - coef::UPS, lsers)
        [reverse!(losers), reverse!(lsers), reverse!(lsere),
          reverse!(lcoef)]$ARES

    do_taylor_via_deriv(nf : UPS, lsyms : L(Symbol), lser : L(UPS)) : Result ==
        lders : L(FE -> FE) := ([(c : FE) : FE +->
                   differentiate(c, sym) for sym in lsyms])
        ups : UPS := taylor_via_deriv(nf, lser, lders)$TEXP
        [map((c : FE) : FE +-> eval(c, [sym::FE = 0$FE for sym in lsyms]), ups)]

    do_taylor_via_deriv2(nk : FE, lsyms : L(Symbol), lser : L(UPS)) : Result ==
        lders : L(FE -> FE) := ([(c : FE) : FE +->
                   differentiate(c, sym) for sym in lsyms])
        ups : UPS := taylor_via_deriv(nk, lser, lders)$TEXP
        [map((c : FE) : FE +-> eval(c, [sym::FE = 0$FE for sym in lsyms]), ups)]

    NARGS ==> Record(nargs0 : L(FE), sers : L(UPS), syms : L(Symbol))

    convert_args(lsers : L(UPS), lsere : L(FE), lser0 : L(FE)) : NARGS ==
        lsyms : List(Symbol) := []
        lser : List(UPS) := []
        nargs : List(FE) := []
        for s in lsers for e in lsere for c in lser0 repeat
            e = 0 =>
                nargs := cons(c, nargs)
            nsym := new()$Symbol
            nargs := cons(c + nsym::FE, nargs)
            lsyms := cons(nsym, lsyms)
            lser := cons(s, lser)
        nargs := reverse!(nargs)
        [nargs, lser, lsyms]

    do_ell(losers : L(UPS), lsers : L(UPS), lsere : L(FE),
            lser0 : L(FE), ef : (UTS, L(FE)) -> UTS) : Result ==
        cargs := convert_args(rest lsers, rest lsere, rest lser0)
        nres := applyTaylor(f +-> ef(f, cargs.nargs0), losers(1))
        do_taylor_via_deriv(nres, cargs.syms, cargs.sers)

    do_ell2(losers : L(UPS), lsers : L(UPS), lsere : L(FE),
            lser0 : L(FE), ef : (UTS, FE) -> UTS) : Result ==
        do_ell(losers, lsers, lsere, lser0, (f, l) +-> ef(f, l(1)))

    do_ell3(losers : L(UPS), lsers : L(UPS), lsere : L(FE),
            lser0 : L(FE), ef : (UTS, FE, FE) -> UTS) : Result ==
        do_ell(losers, lsers, lsere, lser0, (f, l) +-> ef(f, l(1), l(2)))

    SupF ==> SparseUnivariatePolynomial(FE)
    -- zvar := monomial(1, 1)$SupF
    -- zvar := monomial(1, 1::Expon)$UPS

    -- taylor_via_deriv is good enough for Airy functions
    -- f'' - zf = 0
    -- airyEq(lc : L(FE), z0 : FE) : L(UPS) ==
    --    zvar := monomial(1, 1::Expon)$UPS + z0::UPS
    --    [1, 0, zvar]

    -- z^2f'' + zf' +(z^2 - v^2)f = 0
    besselEq(lc : L(FE), z0 : FE) : L(UTS) ==
        v := lc(1)::UTS
        zvar := monomial(1, 1)$UTS + z0::UTS
        [zvar^2, zvar, zvar^2 - v^2]

    -- z^2f'' + zf' +(z^2 + v^2)f = 0
    besselEqm(lc : L(FE), z0 : FE) : L(UTS) ==
        v := lc(1)::UTS
        zvar := monomial(1, 1)$UTS + z0::UTS
        [zvar^2, zvar, zvar^2 + v^2]

    -- z^4f^(4) + 2z^3f^(3) - (1 + 2v^2)z^2f'' + (1 + 2v^2)zf'
    -- (v^2 - 4v^2 + z^4)f = 0
    kelvinEq(lc : L(FE), z0 : FE) : L(UTS) ==
        v := lc(1)::UTS
        zvar := monomial(1, 1)$UTS + z0::UTS
        v2 := 1$UTS + 2*v^2
        [zvar^4, 2*zvar^3, -v2*zvar^2, v2*zvar, v^2 - 4*v^2 + zvar^4]

    -- zf'' + (b-z)f' - af = 0
    kummerEq(lc : L(FE), z0 : FE) : L(UTS) ==
        a := lc(1)::UTS
        b := lc(2)::UTS
        zvar := monomial(1, 1)$UTS + z0::UTS
        [zvar, (b - zvar), -a]

    -- (1 - z^2)^2f'' -2z(1-z^2)f' + (nu(nu+1)(1-z^2) - mu)f = 0
    legendreEq(lc : L(FE), z0 : FE) : L(UTS) ==
        nu := lc(1)::UTS
        mu := lc(2)::UTS
        zvar := monomial(1, 1)$UTS + z0::UTS
        z2 := 1 - zvar^2
        [z2^2, -2*zvar*z2, nu*(nu + 1)*z2 - mu]

    --z^2f'' + (1/4 - m^2 + kz - z^2/4)f = 0
    whittakerEq(lc : L(FE), z0 : FE) : L(UTS) ==
        k := lc(1)::UTS
        m := lc(2)::UTS
        o4 := (1/(4::R::FE))::UTS
        zvar := monomial(1, 1)$UTS + z0::UTS
        [zvar^2, 0, o4 - m^2 + k*zvar - zvar^2/(4::R::FE)]

    do_diff_eq(ker : K, losers : L(UPS), lsers : L(UPS),
               lsere : L(FE), lser0 : L(FE),
               getEq : (L(FE), FE) -> L(UTS)) : Result ==
        cargs := convert_args(lsers, lsere, lser0)
        nargs := cargs.nargs0
        last(lsere) = 0 =>
            nker : FE := kernel(operator(ker), nargs)
            do_taylor_via_deriv2(nker, cargs.syms, cargs.sers)
        z0 := last(lser0)
        ecl := getEq(nargs, z0)
        cn := first(ecl)
        cn1u := recip(cn)
        cn1u case "failed" =>
            error "do_diff_eq: called at singular point"
        cn1 := -(cn1u::UTS)
        ecl := [c*cn1 for c in rest(ecl)]
        nker : FE := kernel(operator(ker), nargs)
        lc : L(FE) := [nker]
        lsyms := cargs.syms
        sym := first(lsyms)
        lsyms := rest(lsyms)
        lsers := rest(cargs.sers)
        for c in rest(ecl) repeat
            nker := differentiate(nker, sym)
            lc := cons(nker, lc)
        lc := [eval(c, sym::FE = 0$FE) for c in reverse!(lc)]
        ups := taylor_via_lode(reverse(ecl), first(cargs.sers), lc)
        do_taylor_via_deriv(ups, lsyms, lsers)

    SPFUTS ==> SpecialFunctionUnivariateTaylorSeries(FE, UTS)

    lambertW0(arg : UPS) : UPS ==
        applyTaylor(lambertW0$SPFUTS, arg)

    do_weierstrass(losers : L(UPS), lsers : L(UPS), lsere : L(FE),
          lser0 : L(FE), ef : (FE, FE, UTS) -> UTS, k : I, cz : I,
          opt_rec : OPT_REC) : Result ==
        lsere(3) = 0 => error "expansion at 0"
        z_ser := losers(3)
        z_inv : UPS
        if cz ~= 0 then
            deg := order(z_ser, ZEROCOUNT :: Expon)
            (coef := coefficient(z_ser, deg)) = 0 =>
                error "inverse of series with many leading zero coefficients"
            if not(check_inverse(coef, opt_rec)) then
                return stateProblem("weierstrass",
                                    "need to invert bad coefficient")
            z_inv := cz*z_ser^k
        else
            z_inv := 0
        cargs := convert_args(lsers, lsere, lser0)
        nargs := cargs.nargs0
        nres := applyTaylor((f : UTS) : UTS +-> ef(nargs(1), nargs(2), f),
                            z_ser)
        lsyms := rest(cargs.syms)
        lsers := rest(cargs.sers)
        nres2 := do_taylor_via_deriv(nres, lsyms, lsers)
        nres2 case %problem => return nres2
        [z_inv + nres2.%series]

    make_taylor(f : Integer -> FE) : UTS ==
        rn : Reference(Integer) := ref(1)
        genc := (s : FE) : FE +->
            n := deref(rn)
            val := f(n)
            setref(rn, n + 1)
            val
        sc : Stream(FE) := stream(genc, f(0))
        series(sc)$UTS

    lpi : FE := log(2::FE*pi()$FE)/2::FE

    sqpi : FE := 1/sqrt(pi())

    gen_erfs(i : Integer, rv : Reference(FE)) : FE ==
        i = 1 => 1
        even?(i) => 0
        val := (2 - i)::FE/(2::FE)*deref(rv)
        setref(rv, val)
        val

    do_erfs(iups : UPS) : Result ==
        rv : Reference(FE) := ref(1)
        lerfs := make_taylor((i : Integer) : FE +-> gen_erfs(i, rv))
        [sqpi*apply_taylor(lerfs, iups)]

    gen_erfis(i : Integer, rv : Reference(FE)) : FE ==
        i = 1 => 1
        even?(i) => 0
        val := (i - 2)::FE/(2::FE)*deref(rv)
        setref(rv, val)
        val

    do_erfis(iups : UPS) : Result ==
        rv : Reference(FE) := ref(1)
        lerfs := make_taylor((i : Integer) : FE +-> gen_erfis(i, rv))
        [sqpi*apply_taylor(lerfs, iups)]

    gen_eis(i : Integer, rv : Reference(FE)) : FE ==
        i = 0 => 0
        i = 1 => 1
        val := (i - 1)::FE*deref(rv)
        setref(rv, val)
        val

    do_eis(iups : UPS) : Result ==
        rv : Reference(FE) := ref(1)
        leis :=  make_taylor(i +-> gen_eis(i, rv))
        [apply_taylor(leis, iups)]


    gen_lg(i : Integer) : FE ==
        i = 0 => lpi
        even?(i) => 0
        k := i + 1
        bernoulli(k)$IntegerNumberTheoryFunctions::FE/(k*i)::FE

    do_log_gamma(lc : FE, k : FE, lx : FE, ups : UPS, ups1 : UPS,
              iups : UPS) : Result ==
        lgs := make_taylor(gen_lg)
        c1 := k*lx + log(lc)
        l1 := log(ups1)
        r1 := (ups - (1/2::FE)::UPS) *$UPS (l1 + c1::UPS) -$UPS ups
        r2 : UPS := apply_taylor(lgs, iups)$TEXP
        [r1 +$UPS r2]

    do_digamma(lc : FE, k : FE, lx : FE, ups1 : UPS, iups : UPS
              ) : Result ==
        lgs := make_taylor(gen_lg)
        ldig := monomial(-1, 2)$UTS*differentiate(lgs)$UTS
        c1 := k*lx + log(lc)
        r1  : UPS := log(ups1) + c1::UPS + (-1/2::FE)*iups
        r2 : UPS := apply_taylor(ldig, iups)$TEXP
        [r1 +$UPS r2]

    spec_to_UPS(ker : K, args : L(FE), opt_rec : OPT_REC) : Result ==
        nm := name(ker)
        if nm = 'digamma or nm = '%logGamma or nm = '%eis or nm = '%erfs
           or nm = '%erfis then
            arg1 := args(1)
            (nsu := i_expr_to_PS(arg1, opt_rec)) case %problem =>
                return nsu
            ups := nsu.%series
            if (ord := order(ups, 0)) < 0 then return
                not(opt_rec.log_x_replace) =>
                    stateProblem(string(nm), "argument not Taylor")
                lc := coefficient(ups, ord)
                (signum := sign(lc)$SIGNEF) case "failed" =>
                    stateProblem(string(nm), "branch problem")
                signum = -1 =>
                    stateProblem(string(nm), "expansion at - infinity")
                iups := ups^(-1$Integer)
                nm = '%eis => do_eis(iups)
                nm = '%erfs => do_erfs(iups)
                nm = '%erfis => do_erfis(iups)
                ups1 := monomial(1/lc, -ord)*ups
                nm = 'digamma =>
                    do_digamma(lc, coerce_Ex(ord), opt_rec.log_x_val,
                               ups1, iups)
                nm = '%logGamma =>
                    do_log_gamma(lc, coerce_Ex(ord), opt_rec.log_x_val,
                                 ups, ups1, iups)
        (aresu := handle_args(args, opt_rec)) case Result =>
             aresu
        ares := aresu::ARES
        losers := ares.osers
        lsers := ares.sers
        lsere := ares.sere
        lser0 := ares.sers0
        nm := name(ker)
        nm = 'besselI or nm = 'besselK =>
            lser0(2) = 0 => stateProblem(string(nm), "expansion at 0")
            do_diff_eq(ker, losers, lsers, lsere, lser0, besselEqm)
        nm = 'besselJ or nm = 'besselY or nm = 'hankelH1 or nm = 'hankelH2 =>
            lser0(2) = 0 => stateProblem(string(nm), "expansion at 0")
            do_diff_eq(ker, losers, lsers, lsere, lser0, besselEq)
        nm = 'kummerM or nm = 'kummerU =>
            lser0(3) = 0 => stateProblem(string(nm), "expansion at 0")
            do_diff_eq(ker, losers, lsers, lsere, lser0, kummerEq)
        nm = 'whittakerM or nm = 'whittakerW =>
            lser0(3) = 0 => stateProblem(string(nm), "expansion at 0")
            do_diff_eq(ker, losers, lsers, lsere, lser0, whittakerEq)
        nm = 'kelvinBer or nm = 'kelvinBei or nm = 'kelvinKer
          or nm = 'kelvinKei =>
            lser0(2) = 0 => stateProblem(string(nm), "expansion at 0")
            do_diff_eq(ker, losers, lsers, lsere, lser0, kelvinEq)
        nm = 'legendreP or nm = 'legendreQ =>
            lser0(3) = 1 or lser0(3) = -1 =>
                stateProblem(string(nm), "expansion at +-1")
            do_diff_eq(ker, losers, lsers, lsere, lser0, legendreEq)
        nm = 'lambertW and lser0(1) = 0 =>
            [lambertW0(losers(1))]
)if false
        nm = 'hypergeometricF =>
        nm = 'meijerG =>
        nm = 'struveH =>
        nm = 'struveL =>
        nm = 'lommelS1 =>
        nm = 'lommelS2 =>
)endif
        nm = 'jacobiSn =>
            do_ell2(losers, lsers, lsere, lser0, jacobiSn$ELITS)
        nm = 'jacobiCn =>
            do_ell2(losers, lsers, lsere, lser0, jacobiCn$ELITS)
        nm = 'jacobiDn =>
            do_ell2(losers, lsers, lsere, lser0, jacobiDn$ELITS)
        nm = 'ellipticE2 =>
            do_ell2(losers, lsers, lsere, lser0, ellipticE$ELITS)
        nm = 'ellipticF =>
            do_ell2(losers, lsers, lsere, lser0, ellipticF$ELITS)
        nm = 'ellipticPi =>
            do_ell3(losers, lsers, lsere, lser0, ellipticPi$ELITS)
        nm = 'weierstrassP and lser0(3) = 0 =>
            do_weierstrass(losers, lsers, lsere, lser0,
                           weierstrassP0$SPFUTS, -2, 1, opt_rec)
        nm = 'weierstrassPPrime and lser0(3) = 0 =>
            do_weierstrass(losers, lsers, lsere, lser0,
                           weierstrassPPrime0$SPFUTS, -3, -2, opt_rec)
        nm = 'weierstrassZeta and lser0(3) = 0 =>
            do_weierstrass(losers, lsers, lsere, lser0,
                           weierstrassZeta0$SPFUTS, -1, 1, opt_rec)
        nm = 'weierstrassSigma and lser0(3) = 0 =>
            do_weierstrass(losers, lsers, lsere, lser0,
                           weierstrassSigma0$SPFUTS, 0, 0, opt_rec)
        if nm = 'Gamma then
            if (ir := retractIfCan(lser0(1))@Union(R, "failed")) case R and
               (ii := retractIfCan(ir)@Union(Integer, "failed")) case Integer
                  and ii <= 0 then
                    arg1 := args(1)
                    narg1 := 1 - arg1
                    nker := kernel(operator(ker), [narg1])
                    nexpr := pi()$FE/(nker::FE*sin(pi()$FE*arg1))
                    return i_expr_to_PS(nexpr, opt_rec)
        cargs := convert_args(lsers, lsere, lser0)
        nargs := cargs.nargs0
        nker : FE := kernel(operator(ker), nargs)
        do_taylor_via_deriv2(nker, cargs.syms, cargs.sers)

    can_integrate : Boolean := false

    if UPS has differentiate : UPS -> UPS and
      UPS has integrate : UPS -> UPS then

        can_integrate := true

        atan1(ups) ==
            y := differentiate(ups)$UPS/(1$UPS + ups*ups)
            yCoef := coefficient(y, -1)
            monomial(log yCoef, 0) + integrate(y - monomial(yCoef, -1)$UPS)

        integ(f) == integrate(f)$UPS

        integ_df(f : UPS, xs : UPS) : UPS ==
            integrate(differentiate(xs)$UPS*f)$UPS

    else

        atan1(ups) == error "atan1 called, but no integrate"

        integ(f) == error "integ called, but no integrate"

        integ_df(f : UPS, xs : UPS) : UPS ==
            error "integ_df called, but no integrate"

    do_prim(ker : K, arg0 : FE, opt_rec : OPT_REC) : Result ==
        can_integrate =>
            c0 := operator(ker)(arg0)
            nf := differentiate(ker::FE, x)
            ns := i_expr_to_PS(nf, opt_rec)
            ns case %problem => return ns
            [(c0::UPS) + integ(ns.%series)]
        spec_to_UPS(ker, argument(ker), opt_rec)

    prim_to_UPS(ker : K, args : L(FE), opt_rec : OPT_REC) : Result ==
        nm := name(ker)
        #args ~= 1 =>
            stateProblem(string nm, "multivariate primitive")
        arg := first(args)
        (nsu := i_expr_to_PS(arg, opt_rec)) case %problem =>
            return nsu
        ups := nsu.%series
        order(ups, 0) < 0 =>
            -- FIXME: handle expansions at infinity
            return stateProblem("handle_args", "argument not Taylor")
        coef := coefficient(ups, 0)
        nm = 'Ei or nm = 'Ci or nm = 'Chi or nm = 'dilog =>
            coef = 0 => stateProblem(string(nm), "expansion at 0")
            do_prim(ker, coef, opt_rec)
        nm = 'li =>
            coef = 0 => stateProblem(string(nm), "expansion at 0")
            coef = 1 => stateProblem(string(nm), "expansion at 1")
            do_prim(ker, coef, opt_rec)
        nm = 'Si or nm = 'Shi or nm = 'erf or nm = 'erfi or
          nm = 'fresnelS or nm = 'fresnelC =>
            do_prim(ker, coef, opt_rec)
        stateProblem(string name ker,"unimplemented")

    unknown_to_UPS(ker : K, args : L(FE), opt_rec : OPT_REC) : Result ==
        (aresu := handle_args(args, opt_rec)) case Result =>
             aresu
        ares := aresu::ARES
        losers := ares.osers
        lsers := ares.sers
        lsere := ares.sere
        lser0 := ares.sers0
        cargs := convert_args(lsers, lsere, lser0)
        nargs := cargs.nargs0
        nker : FE := kernel(operator(ker), nargs)
        do_taylor_via_deriv2(nker, cargs.syms, cargs.sers)

    kernelToUPS(ker, opt_rec) ==
      -- converts a kernel to a power series
      (sym := symbolIfCan(ker)) case Symbol =>
        (sym :: Symbol) = x => [monomial(1, 1)]
        [monomial(ker :: FE, 0)]
      empty?(args := argument ker) => [monomial(ker :: FE, 0)]
      not member?(x, variables(ker :: FE)) => [monomial(ker :: FE, 0)]
      op := operator(ker)
      has?(op, 'special) =>
          spec_to_UPS(ker, args, opt_rec)
      has?(op, 'prim) =>
          prim_to_UPS(ker, args, opt_rec)
      empty? rest args =>
        arg := first args
        is?(ker, 'abs) =>
            nthRootToUPS(arg*arg, 2, opt_rec)
        is?(ker, "%paren"::Symbol) => i_expr_to_PS(arg, opt_rec)
        is?(ker, 'log) => logToUPS(arg, opt_rec)
        is?(ker, 'exp) =>
            applyIfCan(expIfCan, arg, "exp", opt_rec)
        tranToUPS(ker, arg, opt_rec)
      is?(ker, "%power"::Symbol) => powToUPS(args, opt_rec)
      is?(ker, 'nthRoot) =>
        n := retract(second args)@I
        nthRootToUPS(first args, n::NNI, opt_rec)
      unknown_to_UPS(ker, args, opt_rec)

    nthRootToUPS(arg, n, opt_rec) ==
      -- converts an nth root to a power series
      -- this is not used in the limit package, so the series may
      -- have non-zero order, in which case nth roots may not be unique
      (result := i_expr_to_PS(arg, opt_rec)) case %problem => result
      ans := carefulNthRootIfCan(result.%series, n, opt_rec, false)
      ans case %problem => ans
      [ans.%series]

    logToUPS(arg, opt_rec) ==
      -- converts a logarithm log(f(x)) to a power series
      -- f(x) must have order 0 and if 'posCheck?' is true,
      -- then f(x) must have a non-negative leading coefficient
      (result := i_expr_to_PS(arg, opt_rec)) case %problem => result
      ups := result.%series
      deg := degree ups
      coef : FE
      deg1 := deg + ZEROCOUNT :: Expon
      while check_zero(coef := coefficient(ups, deg), opt_rec)
            and deg < deg1 repeat
          if coef ~= 0 then ups := reductum(ups)
          deg := order(ups, deg1)
          (coef := coefficient(ups, deg)) = 0 =>
              error "log of series with many leading zero coefficients"
      not (opt_rec.log_x_replace or zero? deg) =>
        stateProblem("log","series of non-zero order")
      -- if 'posCheck?' is true, we do not allow logs of negative numbers
      if opt_rec.pos_Check? then
        if ((signum := sign(coef)$SIGNEF) case I) then
          (signum :: I) = -1 =>
            return stateProblem("log","negative leading coefficient")
      if not(check_inverse(coef, opt_rec)) then
          return stateProblem("log", "need to invert bad coefficient")
      zero? deg => [logIfCan(ups)::UPS]
      lt := monomial(coef, deg)$UPS
      logTerm : FE := log(coef) + coerce_Ex(deg)*opt_rec.log_x_val
      [monomial(logTerm, 0) + log(ups/lt)]

    if FE has abs : FE -> FE then
      localAbs fcn == abs fcn
    else
      localAbs fcn == sqrt(fcn * fcn)

    signOfExpression : FE -> FE
    signOfExpression arg == localAbs(arg)/arg

    atanacot2(ups : UPS, coef : FE, ord : Expon, opt_rec : OPT_REC,
              plusMinus : I) : Result ==
      atanFlag := opt_rec.atan_Flag
      cc : FE :=
        ord < 0 =>
          atanFlag = "complex" =>
            return stateProblem("atan","essential singularity")
          (rn := ratIfCan(coerce_Ex(ord))) case "failed" =>
            -- this condition usually won't occur because exponents will
            -- be integers or rational numbers
            return stateProblem("atan","branch problem")
          if (atanFlag = "real: two sides") and (odd? numer(rn :: RN)) then
            -- expansions to the left and right of zero have different
            -- constant coefficients
            return stateProblem("atan","branch problem")
          lc := coefficient(ups, ord)
          (signum := sign(lc)$SIGNEF) case "failed" =>
            -- can't determine sign
            atanFlag = "just do it" =>
              plusMinus = 1 => pi()/(2 :: FE)
              0
            posNegPi2 := signOfExpression(lc) * pi()/(2 :: FE)
            plusMinus = 1 => posNegPi2
            pi()/(2 :: FE) - posNegPi2
            --return stateProblem("atan","branch problem")
          left? : B := atanFlag = "real: left side"; n := signum :: Integer
          (left? and n = 1) or (not left? and n = -1) =>
            plusMinus = 1 => -pi()/(2 :: FE)
            pi()
          plusMinus = 1 => pi()/(2 :: FE)
          0
        plusMinus = 1 => atan coef
        acot coef
      [(cc :: UPS) + integ_df(plusMinus*(1$UPS/(1 + ups*ups)), ups)]

    atancotToUPS(arg, opt_rec, plusMinus) ==
        -- converts atan(f(x)) to a power series
        (result := i_expr_to_PS(arg, opt_rec)) case %problem => result
        ups := result.%series; coef := coefficient(ups, 0)
        (ord := order(ups, 0)) = 0 and coef * coef = -1 =>
            -- series involves complex numbers
            return stateProblem("atan","logarithmic singularity")
        can_integrate =>
            atanacot2(ups, coef, ord, opt_rec, plusMinus)
        ord < 0 => stateProblem("atan","argument of negative order")
        res1 : Union(UPS, "failed") :=
            plusMinus = 1 => atanIfCan(ups)
            acotIfCan(ups)
        res1 case "failed" => stateProblem("atan","failed")
        [res1::UPS]

    applyIfCan(fcn, arg, fcnName, opt_rec) ==
      -- converts fcn(arg) to a power series
      (ups := i_expr_to_PS(arg, opt_rec)) case %problem => ups
      ans := fcn(ups.%series)
      ans case "failed" => stateProblem(fcnName,"essential singularity")
      [ans :: UPS]

    tranToUPS(ker, arg, opt_rec) ==
      -- converts ker to a power series for certain functions
      -- in trig or hyperbolic trig categories
      is?(ker, 'sin) =>
          applyIfCan(sinIfCan, arg, "sin", opt_rec)
      is?(ker, 'cos) =>
          applyIfCan(cosIfCan, arg, "cos", opt_rec)
      is?(ker, 'tan) =>
          applyIfCan(tanIfCan, arg, "tan", opt_rec)
      is?(ker, 'cot) =>
          applyIfCan(cotIfCan, arg, "cot", opt_rec)
      is?(ker, 'sec) =>
          applyIfCan(secIfCan, arg, "sec", opt_rec)
      is?(ker, 'csc) =>
          applyIfCan(cscIfCan, arg, "csc", opt_rec)
      is?(ker, 'asin) =>
          applyIfCan(asinIfCan, arg, "asin", opt_rec)
      is?(ker, 'acos) =>
          applyIfCan(acosIfCan, arg, "acos", opt_rec)
      is?(ker, 'atan) => atancotToUPS(arg, opt_rec, 1)
      is?(ker, 'acot) => atancotToUPS(arg, opt_rec, -1)
      is?(ker, 'asec) =>
          applyIfCan(asecIfCan, arg, "asec", opt_rec)
      is?(ker, 'acsc) =>
          applyIfCan(acscIfCan, arg, "acsc", opt_rec)
      is?(ker, 'sinh) =>
          applyIfCan(sinhIfCan, arg, "sinh", opt_rec)
      is?(ker, 'cosh) =>
          applyIfCan(coshIfCan, arg, "cosh", opt_rec)
      is?(ker, 'tanh) =>
          applyIfCan(tanhIfCan, arg, "tanh", opt_rec)
      is?(ker, 'coth) =>
          applyIfCan(cothIfCan, arg, "coth", opt_rec)
      is?(ker, 'sech) =>
          applyIfCan(sechIfCan, arg, "sech", opt_rec)
      is?(ker, 'csch) =>
          applyIfCan(cschIfCan, arg, "csch", opt_rec)
      is?(ker, 'asinh) =>
          applyIfCan(asinhIfCan, arg, "asinh", opt_rec)
      is?(ker, 'acosh) =>
          applyIfCan(acoshIfCan, arg, "acosh", opt_rec)
      is?(ker, 'atanh) =>
          applyIfCan(atanhIfCan, arg, "atanh", opt_rec)
      is?(ker, 'acoth) =>
          applyIfCan(acothIfCan, arg, "acoth", opt_rec)
      is?(ker, 'asech) =>
          applyIfCan(asechIfCan, arg, "asech", opt_rec)
      is?(ker, 'acsch) =>
          applyIfCan(acschIfCan, arg, "acsch", opt_rec)
      unknown_to_UPS(ker, [arg], opt_rec)

    powToUPS2(args : L FE, opt_rec : OPT_REC) : Result ==
      -- converts a power f(x) ^ g(x) to a power series
      (logBase := logToUPS(first args, opt_rec)) case %problem =>
        logBase
      (expon := i_expr_to_PS(second args, opt_rec)) case %problem =>
        expon
      ans := expIfCan((expon.%series) * (logBase.%series))
      ans case "failed" => stateProblem("exp","essential singularity")
      [ans :: UPS]

    if Expon has RetractableFrom(FE) and Expon has Ring then

        powToUPS(args, opt_rec) ==
            pow := second args
            differentiate(pow, x) ~= 0 => powToUPS2(args, opt_rec)
            (ep := retractIfCan(pow)@Union(Expon, "failed")) case Expon =>
                pow_base : Result := i_expr_to_PS(first args, opt_rec)
                bs : UPS
                pow_base case %problem => pow_base
                bs := pow_base.%series
                eb := degree(bs)
                if coefficient(bs, eb) = 0 then
                    eb := order(bs, eb + 1000::Expon)
                    coefficient(bs, eb) = 0 =>
                        error "powToUPS: series with many zero terms"
                b1 := monomial(1, -eb)*bs
                lbu := logIfCan(b1)
                lbu case "failed" => stateProblem("pow", "transcendental log")
                ans1 := expIfCan(pow*(lbu::UPS))
                ans1 case "failed" => stateProblem("pow", "transcendental exp")
                [monomial(1, ep*eb)*ans1]
            powToUPS2(args, opt_rec)

    else

        powToUPS(args, opt_rec) == powToUPS2(args, opt_rec)

-- Generalized power series: power series in x, where log(x) and
-- bounded functions of x are allowed to appear in the coefficients
-- of the series.  Used for evaluating REAL limits at x = 0.

    newElem f ==
    -- rewrites a functional expression; all trig functions are
    -- expressed in terms of sin and cos; all hyperbolic trig
    -- functions are expressed in terms of exp
      smpElem(numer f) / smpElem(denom f)

    smpElem p == map(k2Elem, (x1 : R) : FE +-> x1::FE, p)$PCL

    k2Elem k ==
    -- rewrites a kernel; all trig functions are
    -- expressed in terms of sin and cos; all hyperbolic trig
    -- functions are expressed in terms of exp
      empty?(args := [newElem a for a in argument k])$List(FE) => k::FE
      iez  := inv(ez  := exp(z := first args))
      sinz := sin z; cosz := cos z
      is?(k, 'tan)  => sinz / cosz
      is?(k, 'cot)  => cosz / sinz
      is?(k, 'sec)  => inv cosz
      is?(k, 'csc)  => inv sinz
      is?(k, 'sinh) => (ez - iez) / (2 :: FE)
      is?(k, 'cosh) => (ez + iez) / (2 :: FE)
      is?(k, 'tanh) => (ez - iez) / (ez + iez)
      is?(k, 'coth) => (ez + iez) / (ez - iez)
      is?(k, 'sech) => 2 * inv(ez + iez)
      is?(k, 'csch) => 2 * inv(ez - iez)
      (operator k) args

    CONTFCNS : L FUNCTIONNAME := ["sin","cos","atan","acot","exp","asinh"]
    -- functions which are defined and continuous at all real numbers

    BDDFCNS : L FUNCTIONNAME := ["sin","cos","atan","acot"]
    -- functions which are bounded on the reals

    contOnReals? fcn == member?(fcn, CONTFCNS)
    bddOnReals? fcn  == member?(fcn, BDDFCNS)

    exprToGenUPS(fcn, posCheck?, atanFlag) ==
      -- converts a functional expression to a generalized power
      -- series; "generalized" means that log(x) and bounded functions
      -- of x are allowed to appear in the coefficients of the series
      iExprToGenUPS(newElem fcn, [posCheck?, atanFlag, "none", "none",
                                  "none", false, 0]$OPT_REC)

    iExprToGenUPS(fcn, opt_rec) ==
      -- converts a functional expression to a generalized power
      -- series without first normalizing the expression
      --!! The following line is commented out so that expressions of
      --!! the form a^b will be normalized to exp(b * log(a)) even if
      --!! 'a' and 'b' do not involve the limiting variable 'x'.
      --!!                         - cjw 1 Dec 94
      --not member?(x, variables fcn) => [monomial(fcn, 0)]
      (poly := retractIfCan(fcn)@Union(POL,"failed")) case POL =>
          polyToUPS(univariate(poly::POL, x), opt_rec)
      (sum := isPlus fcn) case L(FE) =>
        listToUPS(sum :: L(FE), iExprToGenUPS, opt_rec, 0,
                  (y1, y2) +-> y1 + y2)
      (prod := isTimes fcn) case L(FE) =>
        listToUPS(prod :: L(FE), iExprToGenUPS, opt_rec, 1,
                  (y1, y2) +-> y1*y2)
      (expt := isNonTrivPower fcn) case Record(val : FE, exponent : I) =>
        power := expt :: Record(val : FE, exponent : I)
        powerToGenUPS(power.val, power.exponent, opt_rec)
      (ker := retractIfCan(fcn)@Union(K,"failed")) case K =>
        kernelToGenUPS(ker :: K, opt_rec)
      error "exprToGenUPS: neither a sum, product, power, nor kernel"

    opsInvolvingX fcn ==
      opList := [op for k in tower fcn | unary?(op := operator k) _
                 and member?(x, variables first argument k)]
      removeDuplicates opList

    opInOpList?(name, opList) ==
      for op in opList repeat
        is?(op, name) => return true
      false

    exponential? fcn ==
      -- is 'fcn' of the form exp(f)?
      (ker := retractIfCan(fcn)@Union(K,"failed")) case K =>
        is?(ker::K, 'exp)
      false

    productOfNonZeroes? fcn ==
      -- is 'fcn' a product of non-zero terms, where 'non-zero'
      -- means an exponential or a function not involving x
      exponential? fcn => true
      (prod := isTimes fcn) case "failed" => false
      for term in (prod :: L(FE)) repeat
        (not exponential? term) and member?(x, variables term) =>
          return false
      true

    powerToGenUPS(fcn, n, opt_rec) ==
      -- converts an integral power to a generalized power series
      -- if n < 0 and the lowest order coefficient of the series
      -- involves x, we are careful about inverting this coefficient
      -- the coefficient is inverted only if
      -- (a) the only function involving x is 'log', or
      -- (b) the lowest order coefficient is a product of exponentials
      --     and functions not involving x
      (b := iExprToGenUPS(fcn, opt_rec)) case %problem => b
      n > 0 => [(b.%series) ^ (n::PositiveInteger)]
      -- check lowest order coefficient when n < 0
      ups := b.%series; deg := degree ups
      if (coef := coefficient(ups, deg)) = 0 then
        deg := order(ups, deg + ZEROCOUNT :: Expon)
        (coef := coefficient(ups, deg)) = 0 =>
          error "inverse of series with many leading zero coefficients"
      xOpList := opsInvolvingX coef
      -- only function involving x is 'log'
      empty?(xOpList) => [ups ^ n]
      (empty?(rest(xOpList)) and is?(first(xOpList), 'log)) =>
        [ups ^ n]
      -- lowest order coefficient is a product of exponentials and
      -- functions not involving x
      productOfNonZeroes? coef => [ups ^ n]
      stateProblem("inv","lowest order coefficient involves x")

    kernelToGenUPS(ker, opt_rec) ==
      -- converts a kernel to a generalized power series
      (sym := symbolIfCan(ker)) case Symbol =>
        (sym :: Symbol) = x => [monomial(1, 1)]
        [monomial(ker :: FE, 0)]
      empty?(args := argument ker) => [monomial(ker :: FE, 0)]
      empty? rest args =>
          arg := first args
          is?(ker, 'abs) =>
              nthRootToGenUPS(arg*arg, 2, opt_rec)
          is?(ker, "%paren"::Symbol) => iExprToGenUPS(arg, opt_rec)
          is?(ker, 'log) => logToGenUPS(arg, opt_rec)
          is?(ker, 'exp) => expToGenUPS(arg, opt_rec)
          tranToGenUPS(ker, arg, opt_rec)
      is?(ker, "%power"::Symbol) => powToGenUPS(args, opt_rec)
      is?(ker, 'nthRoot) =>
        n := retract(second args)@I
        nthRootToGenUPS(first args, n::NNI, opt_rec)
      stateProblem(string name ker,"unknown kernel")

    nthRootToGenUPS(arg, n, opt_rec) ==
      -- convert an nth root to a power series
      -- used for computing right hand limits, so the series may have
      -- non-zero order, but may not have a negative leading coefficient
      -- when n is even
      (result := iExprToGenUPS(arg, opt_rec)) case %problem =>
        result
      ans := carefulNthRootIfCan(result.%series, n, opt_rec, true)
      ans case %problem => ans
      [ans.%series]

    logToGenUPS(arg, opt_rec) ==
      -- converts a logarithm log(f(x)) to a generalized power series
      (result := iExprToGenUPS(arg, opt_rec)) case %problem =>
        result
      ups := result.%series; deg := degree ups
      if (coef := coefficient(ups, deg)) = 0 then
        deg := order(ups, deg + ZEROCOUNT :: Expon)
        (coef := coefficient(ups, deg)) = 0 =>
          error "log of series with many leading zero coefficients"
      -- if 'posCheck?' is true, we do not allow logs of negative numbers
      if opt_rec.pos_Check? then
        if ((signum := sign(coef)$SIGNEF) case I) then
          (signum :: I) = -1 =>
            return stateProblem("log","negative leading coefficient")
      -- create logarithmic term, avoiding log's of negative rationals
      lt := monomial(coef, deg)$UPS; cen := center lt
      -- check to see if lowest order coefficient is a negative rational
      negRat? : Boolean :=
        ((rat := ratIfCan coef) case RN) =>
          (rat :: RN) < 0 => true
          false
        false
      logTerm : FE :=
        mon : FE := (x :: FE) - (cen :: FE)
        pow : FE := mon^coerce_Ex(deg)
        negRat? => log(coef * pow)
        term1 : FE := coerce_Ex(deg)*log(mon)
        log(coef) + term1
      [monomial(logTerm, 0) + log(ups/lt)]

    expToGenUPS(arg, opt_rec) ==
      -- converts an exponential exp(f(x)) to a generalized
      -- power series
      (ups := iExprToGenUPS(arg, opt_rec)) case %problem => ups
      expGenUPS(ups.%series, opt_rec)

    expGenUPS(ups, opt_rec) ==
      -- computes the exponential of a generalized power series.
      -- If the series has order zero and the constant term a0 of the
      -- series involves x, the function tries to expand exp(a0) as
      -- a power series.
      (deg := order(ups, 1)) < 0 =>
        stateProblem("exp","essential singularity")
      deg > 0 => [exp ups]
      lc := coefficient(ups, 0); xOpList := opsInvolvingX lc
      not opInOpList?('log, xOpList) => [exp ups]
      -- try to fix exp(lc) if necessary
      expCoef :=
        normalize(exp lc, x)$ElementaryFunctionStructurePackage(R, FE)
      opInOpList?('log, opsInvolvingX expCoef) =>
        stateProblem("exp","logs in constant coefficient")
      result := iExprToGenUPS(newElem expCoef, opt_rec)
      result case %problem => result
      [(result.%series) * exp(ups - monomial(lc, 0))]

    atancotToGenUPS(fe, arg, opt_rec, plusMinus) ==
      -- converts atan(f(x)) to a generalized power series
      (result := iExprToGenUPS(arg, opt_rec)) case %problem =>
        trouble := result.%problem
        trouble.prob = "essential singularity" => [monomial(fe,0)$UPS]
        result
      ups := result.%series; coef := coefficient(ups, 0)
      -- series involves complex numbers
      (ord := order(ups, 0)) = 0 and coef * coef = -1 =>
          [atan1(ups)]
      atanacot2(ups, coef, ord, opt_rec, plusMinus)

    genUPSApplyIfCan(fcn, arg, fcnName, opt_rec) ==
      -- converts fcn(arg) to a generalized power series
      (series := iExprToGenUPS(arg, opt_rec)) case %problem =>
        series
      ups := series.%series
      (deg := order(ups, 1)) < 0 =>
        stateProblem(fcnName,"essential singularity")
      deg > 0 => [fcn(ups) :: UPS]
      lc := coefficient(ups, 0); xOpList := opsInvolvingX lc
      empty?(xOpList) => [fcn(ups) :: UPS]
      opInOpList?('log, xOpList) =>
        stateProblem(fcnName,"logs in constant coefficient")
      contOnReals? fcnName => [fcn(ups) :: UPS]
      stateProblem(fcnName,"x in constant coefficient")

    applyBddIfCan(fe, fcn, arg, fcnName, opt_rec) ==
      -- converts fcn(arg) to a generalized power series, where the
      -- function fcn is bounded for real values
      -- if fcn(arg) has an essential singularity as a complex
      -- function, we return fcn(arg) as a monomial of degree 0
      (ups := iExprToGenUPS(arg, opt_rec)) case %problem =>
        trouble := ups.%problem
        trouble.prob = "essential singularity" => [monomial(fe,0)$UPS]
        ups
      (ans := fcn(ups.%series)) case "failed" => [monomial(fe,0)$UPS]
      [ans :: UPS]

    tranToGenUPS(ker, arg, opt_rec) ==
      -- converts op(arg) to a power series for certain functions
      -- op in trig or hyperbolic trig categories
      -- N.B. when this function is called, 'k2Elem' will have been
      -- applied, so the following functions cannot appear:
      -- tan, cot, sec, csc, sinh, cosh, tanh, coth, sech, csch
      is?(ker, 'sin) =>
          applyBddIfCan(ker::FE, sinIfCan, arg, "sin", opt_rec)
      is?(ker, 'cos) =>
          applyBddIfCan(ker::FE, cosIfCan, arg, "cos", opt_rec)
      is?(ker, 'asin) =>
          genUPSApplyIfCan(asinIfCan, arg, "asin", opt_rec)
      is?(ker, 'acos) =>
          genUPSApplyIfCan(acosIfCan, arg, "acos", opt_rec)
      is?(ker, 'atan) =>
          atancotToGenUPS(ker::FE, arg, opt_rec, 1)
      is?(ker, 'acot) =>
          atancotToGenUPS(ker::FE, arg, opt_rec, -1)
      is?(ker, 'asec) =>
          genUPSApplyIfCan(asecIfCan, arg, "asec", opt_rec)
      is?(ker, 'acsc) =>
          genUPSApplyIfCan(acscIfCan, arg, "acsc", opt_rec)
      is?(ker, 'asinh) =>
          genUPSApplyIfCan(asinhIfCan, arg, "asinh", opt_rec)
      is?(ker, 'acosh) =>
          genUPSApplyIfCan(acoshIfCan, arg, "acosh", opt_rec)
      is?(ker, 'atanh) =>
          genUPSApplyIfCan(atanhIfCan, arg, "atanh", opt_rec)
      is?(ker, 'acoth) =>
          genUPSApplyIfCan(acothIfCan, arg, "acoth", opt_rec)
      is?(ker, 'asech) =>
          genUPSApplyIfCan(asechIfCan, arg, "asech", opt_rec)
      is?(ker, 'acsch) =>
          genUPSApplyIfCan(acschIfCan, arg, "acsch", opt_rec)
      stateProblem(string name ker,"unknown kernel")

    powToGenUPS(args, opt_rec) ==
      -- converts a power f(x) ^ g(x) to a generalized power series
      (logBase := logToGenUPS(first args, opt_rec)) case %problem =>
        logBase
      expon := iExprToGenUPS(second args, opt_rec)
      expon case %problem => expon
      expGenUPS((expon.%series) * (logBase.%series), opt_rec)



)abbrev package FS2UPS FunctionSpaceToUnivariatePowerSeries
++ Author: Clifton J. Williamson
++ Date Created: 21 March 1989
++ Basic Operations:
++ Related Domains:
++ Also See:
++ AMS Classifications:
++ Keywords: elementary function, power series
++ Examples:
++ References:
++ Description:
++   This package converts expressions in some function space to power
++   series in a variable x with coefficients in that function space.
++   The function \spadfun{exprToUPS} converts expressions to power series
++   whose coefficients do not contain the variable x. The function
++   \spadfun{exprToGenUPS} converts functional expressions to power series
++   whose coefficients may involve functions of \spad{log(x)}.
FunctionSpaceToUnivariatePowerSeries(R, FE, Expon, UPS, TRAN, UTS, TEXP, x) : _
 Exports == Implementation where
  R     : Join(GcdDomain, Comparable, RetractableTo Integer,
               LinearlyExplicitOver Integer)
  FE    : Join(AlgebraicallyClosedField, TranscendentalFunctionCategory,
               Algebra(Fraction(Integer)), FunctionSpace R)
            with
              coerce : Expon -> %
                ++ coerce(e) converts an 'exponent' e to an 'expression'
  Expon : Join(OrderedAbelianMonoid, OrderedRing)
  UPS   : Join(UnivariatePowerSeriesCategory(FE, Expon), Field,
               PowerSeriesCategory(FE, Expon, SingletonAsOrderedSet),
               TranscendentalFunctionCategory)
  TRAN  : PartialTranscendentalFunctions UPS
  UTS : UnivariateTaylorSeriesCategory(FE)
  TEXP : TaylorSeriesExpansion(FE, Expon, SingletonAsOrderedSet, UPS, UTS)
  x     : Symbol
  B       ==> Boolean
  BOP     ==> BasicOperator
  I       ==> Integer
  NNI     ==> NonNegativeInteger
  K       ==> Kernel FE
  L       ==> List
  RN      ==> Fraction Integer
  ATANFLAG ==> Union("complex", _
                    "real: two sides", _
                    "real: left side", _
                    "real: right side", _
                    "just do it")
  PROBLEM ==> String
  FUNCTIONNAME ==> String
  SY      ==> Symbol
  PCL     ==> PolynomialCategoryLifting(IndexedExponents K, K, R, SMP, FE)
  POL     ==> Polynomial R
  SMP     ==> SparseMultivariatePolynomial(R, K)
  SUP     ==> SparseUnivariatePolynomial Polynomial R
  Problem ==> Record(func : String, prob : String)
  Result  ==> Union(%series : UPS, %problem : Problem)

  Exports ==> with
    exprToUPS : (FE, B, ATANFLAG) -> Result
      ++ exprToUPS(fcn, posCheck?, atanFlag) converts the expression
      ++ \spad{fcn} to a power series.  If \spad{posCheck?} is true,
      ++ log's of negative numbers are not allowed nor are nth roots of
      ++ negative numbers with n even.  If \spad{posCheck?} is false,
      ++ these are allowed.  \spad{atanFlag} determines how the case
      ++ \spad{atan(f(x))}, where \spad{f(x)} has a pole, will be treated.
      ++ The possible values of \spad{atanFlag} are \spad{"complex"},
      ++ \spad{"real: two sides"}, \spad{"real: left side"},
      ++ \spad{"real: right side"}, and \spad{"just do it"}.
      ++ If \spad{atanFlag} is \spad{"complex"}, then no series expansion
      ++ will be computed because, viewed as a function of a complex
      ++ variable, \spad{atan(f(x))} has an essential singularity.
      ++ Otherwise, the sign of the leading coefficient of the series
      ++ expansion of \spad{f(x)} determines the constant coefficient
      ++ in the series expansion of \spad{atan(f(x))}.  If this sign cannot
      ++ be determined, a series expansion is computed only when
      ++ \spad{atanFlag} is \spad{"just do it"}.  When the leading term
      ++ in the series expansion of \spad{f(x)} is of odd degree (or is a
      ++ rational degree with odd numerator), then the constant coefficient
      ++ in the series expansion of \spad{atan(f(x))} for values to the
      ++ left differs from that for values to the right.  If \spad{atanFlag}
      ++ is \spad{"real: two sides"}, no series expansion will be computed.
      ++ If \spad{atanFlag} is \spad{"real: left side"} the constant
      ++ coefficient for values to the left will be used and if \spad{atanFlag}
      ++ \spad{"real: right side"} the constant coefficient for values to the
      ++ right will be used.
      ++ If there is a problem in converting the function to a power series,
      ++ a record containing the name of the function that caused the problem
      ++ and a brief description of the problem is returned.
      ++ When expanding the expression into a series it is assumed that
      ++ the series is centered at 0.  For a series centered at a, the
      ++ user should perform the substitution \spad{x -> x + a} before calling
      ++ this function.

    exprToGenUPS : (FE, B, ATANFLAG) -> Result
      ++ exprToGenUPS(fcn, posCheck?, atanFlag) converts the expression
      ++ \spad{fcn} to a generalized power series.  If \spad{posCheck?}
      ++ is true, log's of negative numbers are not allowed nor are nth roots
      ++ of negative numbers with n even. If \spad{posCheck?} is false,
      ++ these are allowed.  \spad{atanFlag} determines how the case
      ++ \spad{atan(f(x))}, where \spad{f(x)} has a pole, will be treated.
      ++ The possible values of \spad{atanFlag} are \spad{"complex"},
      ++ \spad{"real: two sides"}, \spad{"real: left side"},
      ++ \spad{"real: right side"}, and \spad{"just do it"}.
      ++ If \spad{atanFlag} is \spad{"complex"}, then no series expansion
      ++ will be computed because, viewed as a function of a complex
      ++ variable, \spad{atan(f(x))} has an essential singularity.
      ++ Otherwise, the sign of the leading coefficient of the series
      ++ expansion of \spad{f(x)} determines the constant coefficient
      ++ in the series expansion of \spad{atan(f(x))}.  If this sign cannot
      ++ be determined, a series expansion is computed only when
      ++ \spad{atanFlag} is \spad{"just do it"}.  When the leading term
      ++ in the series expansion of \spad{f(x)} is of odd degree (or is a
      ++ rational degree with odd numerator), then the constant coefficient
      ++ in the series expansion of \spad{atan(f(x))} for values to the
      ++ left differs from that for values to the right.  If \spad{atanFlag}
      ++ is \spad{"real: two sides"}, no series expansion will be computed.
      ++ If \spad{atanFlag} is \spad{"real: left side"} the constant
      ++ coefficient for values to the left will be used and if \spad{atanFlag}
      ++ \spad{"real: right side"} the constant coefficient for values to the
      ++ right will be used.
      ++ If there is a problem in converting the function to a power
      ++ series, we return a record containing the name of the function
      ++ that caused the problem and a brief description of the problem.
      ++ When expanding the expression into a series it is assumed that
      ++ the series is centered at 0.  For a series centered at a, the
      ++ user should perform the substitution \spad{x -> x + a} before calling
      ++ this function.
    exprToPS : (FE, B, ATANFLAG, B, FE) -> Result
      ++ exprToPS(fcn, posCheck?, atanFlag, log_flag, log_val) is
      ++ like exprToUPS, but if \spad{log_flag} is true it replaces
      ++ logarithms of expansion variable by \spad{log_val}
    exprToPS : (FE, B, ATANFLAG, B, FE, FE -> B, FE -> B, FE -> B) -> Result
      ++ exprToPS(fcn, posCheck?, atanFlag, log_flag, log_val, coef_chk,
      ++ inv_chk, zero_chk) is like exprToPS(fcn, posCheck?, atanFlag,
      ++ log_flag, log_val), but fails if \spad{coef_chk} is false for some
      ++ coefficient of \spad{fcn} or if expansion process needs to
      ++ invert element of \spad{FE} for which \spad{inv_chk} is
      ++ false

  Implementation ==> FunctionSpaceToUnivariatePowerSeries2(R,
                       FE, Expon, UPS, TRAN, UTS, TEXP,
                        (coerce$FE)@(Expon -> FE), x)

--Copyright (c) 1991-2002, The Numerical ALgorithms Group Ltd.
--All rights reserved.
--
--Redistribution and use in source and binary forms, with or without
--modification, are permitted provided that the following conditions are
--met:
--
--    - Redistributions of source code must retain the above copyright
--      notice, this list of conditions and the following disclaimer.
--
--    - Redistributions in binary form must reproduce the above copyright
--      notice, this list of conditions and the following disclaimer in
--      the documentation and/or other materials provided with the
--      distribution.
--
--    - Neither the name of The Numerical ALgorithms Group Ltd. nor the
--      names of its contributors may be used to endorse or promote products
--      derived from this software without specific prior written permission.
--
--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
--IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
--TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
--PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
--OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
--EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
--PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
--PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
--LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
--NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
