TTYOFF: TRUE $

/* Set up pattern-match rules to standardize bases of logs and
exponentials: */
MATCHDECLARE(XTRUE,TRUE, NON%ECONST,NON%ECONSTP) $
TELLSIMPAFTER(LOG2(XTRUE), 1.44269504*LOG(XTRUE)) $
TELLSIMP(NON%ECONST**XTRUE, %E**(LOG(NON%ECONST)*XTRUE)) $

NON%ECONSTP(EX) := IS(BASECONVERT=TRUE AND CONSTANTP(EX) AND EX#%E) $

/* Initialize global variables: */
BASECONVERT: FALSE$

TAYLORMAX: 0 $


ASYMP(FN) := BLOCK(/* Returns 'ASYMP(expr), where expr is a simplified
      asymptotic equivalent to FN.  Uses the global integer variable
      named MAXTAYLOR to control optional use of the TAYLOR-series
      technique. */
   [ASYMVAR, ASYMPNT, ANS, RATEXPAND, RATDENOMDIVIDE],
   ASYMPNT: ASYMPDATA(FN),   ASYMVAR: ASYMPNT[1],
   IF ASYMVAR=[] THEN RETURN('ASYMP(FN)),
   ASYMPNT: ANS: ASYMPNT[2],
   ANS: FOR JJ:1 THRU TAYLORMAX WHILE ANS#[]  DO (
      ANS: ERRCATCH(APPLY('TAYLOR, [FN,ASYMVAR,ASYMPNT,JJ])),
      IF ANS#[] AND(ANS:RATDISREP(FIRST(ANS)))#0 THEN RETURN(ANS)),
   IF ANS#'DONE THEN RETURN(ANS),
   ANS: FOR JJ:1 THRU TAYLORMAX WHILE ANS#[] DO (
      ANS: ERRCATCH(APPLY('TAYLOR, [1/FN,ASYMVAR,ASYMPNT,JJ])),
      IF ANS#[] AND(ANS:RATDISREP(FIRST(ANS)))#0 THEN RETURN(1/ANS)),
   IF ANS#'DONE THEN RETURN(ANS),
   RATEXPAND: TRUE,   RATDENOMDIVIDE: FALSE,
   APPLY('RATVARS, ASYMVAR),
   ANS: RATSIMP(LEADTERM(RATNUMER(FN))/LEADTERM(RATDENOM(FN))),
   RATVARS() /* Warning:  might alter user environment */,
   'ASYMP(ANS)) $

ASYMPDATA(EX) := BLOCK(/* Returns a list of 2 lists, the first
      being the asymptotic variables in expression EX and the
      second being their limits. */
   [ALLVAR, ASYMVAR, ASYMPNT, ANS],
   ALLVAR: LISTOFVARS(EX),
   ASYMVAR: ASYMPNT: [],
   FOR VAR IN ALLVAR DO
       IF (ANS:GET(VAR,'LIMIT))#FALSE THEN
          (ASYMVAR: CONS(VAR, ASYMVAR),
           ASYMPNT: CONS(ANS, ASYMPNT)),
   [ASYMVAR, ASYMPNT]) $

LEADTERM(EX) := BLOCK(/* Same as ASYMP, except EX is a numerator
      or denominator */
   [SAVED],
   SAVED: PASS1(EX),
   IF SAVED=[] THEN RETURN(EX),
   SAVED: PASS2(SAVED),
   IF SAVED=[] THEN EX
   ELSE SAVED) $

PASS1(EX) := BLOCK(/* Returns a list of lists, with each of the
      latter being an asymptotically significant cofactor followed
      by its coefficient.  If a coefficient is 0 or contains
      'IND, then cancellation does or could take place.  [] is
      returned when EX is not a sum or when dominance relations
      between terms are uncertain. */
   [SAVED],
   IF MAPATOM(EX) OR INPART(EX,0)#"+" THEN RETURN([]),
   EX: SUBSTINPART("[",EX,0),
   SAVED: [[FIRST(EX), 1]],
   WHILE (EX:REST(EX))#[] DO SAVED: UPDATE(SAVED,FIRST(EX)),
   SAVED) $

UPDATE(SAVED,TERM) := BLOCK(/* SAVED is as indicated for PASS1,
      and TERM is a nonsum.  Returns an appropriately updated version
      of SAVED, according to the dominance between TERM and each
      cofactor in SAVED. Also, sets global EX to [[]] when it is time to
      preemptively terminate the loop in PASS1.  */
   [SAV, NUSAVED, LIM],
   NUSAVED: [],
   WHILE SAVED#[] DO(
      SAV: FIRST(SAVED),
      LIM: MULTILIM(TERM/SAV[1]),
      IF LIM='SAVDOMINATES THEN(
         NUSAVED: APPEND(SAVED,NUSAVED),
         SAVED: [])
      ELSE IF LIM='TERMDOMINATES THEN SAVED: REST(SAVED)
      ELSE IF LIM='BOTHDOMINATE THEN(
         NUSAVED: CONS(SAV, NUSAVED),
         SAVED: REST(SAVED))
      ELSE IF LIM='INVALID THEN EX: [SAVED: []]
      ELSE (/* asymptotically proportional */
         NUSAVED: CONS(MRGE(SAV, TERM, LIM),APPEND(NUSAVED,REST(SAVED))),
         SAVED: [])),
   IF LIM='TERMDOMINATES OR LIM='BOTHDOMINATE THEN
      NUSAVED: CONS([TERM,1], NUSAVED),
   NUSAVED) $

MULTILIM(RATIO) := BLOCK(/* RATIO is term/savedterm.  Returns
      'SAVDOMINATES, 'TERMDOMINATES, 'BOTHDOMINATE, 'INVALID, or
      the finite asymptotic limit of RATIO.  Uses global variables
      ASYMPNT and ASYMVAR established by ASYMP. */
   [MLIM, ULIM, PNTS],
   RATIO: [RATIO,1],
   PNTS: ASYMPNT,
   MLIM: 1,
   FOR VAR IN ASYMVAR WHILE MLIM#'INVALID DO(
      RATIO: MYPARTITION(RATIO[1],VAR),
      IF FREEOF(DELETE(VAR,ASYMVAR),RATIO[2]) THEN (
         ULIM: ERRCATCH(LIMIT(RATIO[2], VAR, FIRST(PNTS))),
         IF ULIM=[] OR (ULIM:FIRST(ULIM))='UND OR
               NOT FREEOF(NOUNIFY('LIMIT),ULIM) THEN MLIM:'INVALID
         ELSE IF MLIM#'BOTHDOMINATE THEN (
            IF ULIM=0 THEN
               IF MLIM='TERMDOMINATES THEN MLIM:'BOTHDOMINATE
               ELSE MLIM: SAVDOMINATES
            ELSE IF MEMBER(ULIM,'[INF,MINF,INFINITY]) THEN
               IF MLIM='SAVDOMINATES THEN MLIM:'BOTHDOMINATE
               ELSE MLIM: 'TERMDOMINATES
            ELSE IF MLIM#'TERMDOMINATES AND MLIM#SAVDOMINATES THEN
               MLIM: ULIM*MLIM)),
      PNTS: REST(PNTS)),
   IF NOT MEMBER(MLIM,'[SAVDOMINATES,TERMDOMINATES,BOTHDOMINATE,INVALID])
      THEN RETURN(RATIO[1]*MLIM),
   MLIM) $

MYPARTITION(NONSUM,VAR) := /* Returns a list consisting of
      factors of NONSUM freeof VAR, then other factors. */
   IF MAPATOM(NONSUM) OR INPART(NONSUM,0)#"*" THEN
      IF FREEOF(VAR, NONSUM) THEN [NONSUM,1]
      ELSE [1,NONSUM]
   ELSE PARTITION(NONSUM,VAR) $

MRGE(SAV,TERM,LIM) := BLOCK( /* Returns a list consisting of the
      cofactor and coefficient of the simplest asymptotic form for TERM
      + SAV[1]*SAV[2], where TERM/SAV[1] approaches LIM. */
   [TOLD, TNEW],
   TOLD: RATSIMP(SAV[2]+LIM), TNEW: RATSIMP((LIM+SAV[2])/LIM),
   IF COMPLEXITY(SAV[1]*TOLD) < COMPLEXITY(TERM*TNEW)
      THEN [SAV[1], TOLD]
   ELSE [TERM, TNEW]) $

COMPLEXITY(EXPR) := /* Returns a complexity measure of
      expression EXPR. */
   IF MAPATOM(EXPR) THEN 1
      ELSE 1 + ARGSCOMPLEXITY(SUBSTINPART("[", EXPR, 0)) $

ARGSCOMPLEXITY(ARGS) := /* Returns a complexity measure of
      a list of expressions, ARGS. */
   IF ARGS=[] THEN 0
   ELSE COMPLEXITY(FIRST(ARGS)) + ARGSCOMPLEXITY(REST(ARGS)) $

PASS2(SAVED) := BLOCK(/* Given the list of lists returned by
      PASS1, this function returns [] if any cancellations are
      possible.  Otherwise, this function returns the sum of the
      terms represented by SAVED */
   [RES, SAV],
   RES: 0,
   WHILE SAVED#[] DO (
      SAV: FIRST(SAVED),
      IF FREEOF('IND,SAV[2]) AND ASKSIGN(ABS(SAV[2]))#'ZERO THEN(
         RES: RES + SAV[1]*SAV[2],
         SAVED: REST(SAVED))
      ELSE RES: SAVED: []),
   RES) $

THETA(EX) :=  (/* Returns 'THETA(expr), where expr is a
      simplified expression having the same exact order as EX. */
   EX: FIRST(ASYMP(EX)),
   'THETA(RATSIMP(THETA1(NUM(EX))/THETA1(DENOM(EX))))) $

THETA1(EX) := BLOCK(/* Same as THETA, except EX is a numerator
      or denominator. */
   [ASYMVAR, TERMS, GC],
   ASYMVAR: ASYMPDATA(EX)[1],
   IF MAPATOM(EX) OR INPART(EX,0)#"+" THEN TERMS:[EX]
   ELSE TERMS: SUBSTINPART("[",EX,0),
   GC: CFASYMP(FIRST(TERMS)),
   IF IS(GC=0) THEN RETURN(GC),
   TERMS: REST(TERMS),
   FOR TERM IN TERMS WHILE IS(GC#1) DO GC: GCD(GC,CFASYMP(TERM)),
   EX/GC) $

CFASYMP(TERM) := BLOCK(/* Returns the coefficient of TERM with
      respect to factors containing asymptotic variables. */
   [CF],
   IF MAPATOM(TERM) OR INPART(TERM,0)#"*" THEN TERM: [TERM]
   ELSE TERM: SUBSTINPART("[",TERM,0),
   CF: 1,
   FOR FCTR IN TERM DO IF APPLY('FREEOF, ENDCONS(FCTR,ASYMVAR))
      THEN CF: CF*FCTR,
   CF) $

OMEGA(EX) := /* Returns 'OMEGA(expr), where expr is a simplified
      expression having at most the same order as EX. */
   'OMEGA(OOROMEGA(EX,FALSE)) $

LOMEGA(EX) := /* Returns 'LOMEGA(expr), where expr is a simplified
      expression which is of lesser order than every expression which
      EX is lesser order than. */
   'LOMEGA(OOROMEGA(EX,FALSE)) $

O(EX) := /* Returns 'O(expr), where expr is a simplified expression
      having at least the same order as EX. */
   'O(OOROMEGA(EX,TRUE)) $

LO(EX) := /* Returns 'LO(expr), where expr is a simplified
      expression which is of greater order than every expression
      which EX is greater order than. */
    'LO(OOROMEGA(EX,TRUE)) $

OOROMEGA(EX,FLAG) := /* If FLAG is TRUE, returns product of
      highest-order asymptotic factors from terms of EX.  Otherwise
      returns product of lowest-order factors. */
   (EX: FIRST(THETA(EX)),
   RATDISREP(RADCAN(OOM(RATNUMER(EX),FLAG)/OOM(RATDENOM(EX),NOT FLAG)))) $

OOM(EX,BIGO) := BLOCK(/* Same as OOROMEGA, except EX is a
      numerator or denominator. */
   [ASYMVAR, ASYMPNT, RATIO, LIMRATIO, ANS, PNTS],
   ASYMPNT: ASYMPDATA(EX),
   ASYMVAR: ASYMPNT[1], ASYMPNT: ASYMPNT[2],
   IF MAPATOM(EX) OR INPART(EX,0)#"+" THEN RETURN(EX),
   EX: SUBSTINPART("[", EX, 0),
   ANS: FIRST(EX),   EX: REST(EX),
   FOR TERM IN EX WHILE ANS#'INVALID DO (
      RATIO: [TERM/ANS, 1],
      PNTS: ASYMPNT,
      FOR VAR IN ASYMVAR WHILE ANS#'INVALID DO (
         RATIO: MYPARTITION(RATIO[1],VAR),
         IF FREEOF(DELETE(VAR,ASYMVAR),RATIO[2]) THEN (
            LIMRATIO: ERRCATCH(LIMIT(RATIO[2], VAR, FIRST(PNTS))),
            IF LIMRATIO=[] OR (LIMRATIO:FIRST(LIMRATIO))='UND
               OR NOT FREEOF(NOUNIFY('LIMIT),LIMRATIO) THEN ANS:'INVALID
            ELSE IF LIMRATIO=0 AND NOT BIGO
                OR MEMBER(LIMRATIO,'[INF,MINF,INFINITY]) AND BIGO
                THEN ANS: ANS*RATIO[2]),
         PNTS: REST(PNTS))),
   ANS) $

ASYMPSERIES(UU,NT) := BLOCK(/* Returns an NT term asymptotic
      expansion for UU. */
   [ANSWER, TERM],
   ANSWER: 0,
   FOR J: 1 THRU NT DO (
      TERM: INPART(ASYMP(UU), 1),
      ANSWER: ANSWER + TERM,
      UU: UU - TERM),
   ANSWER) $

/* Establish table for combining different kinds of approximate
   expressions: */
ASYM[1]:'ASYMP$  ASYM[2]:'THETA$  ASYM[3]:'O$
ASYM[4]:'LO$  ASYM[5]:'OMEGA$  ASYM[6]:'LOMEGA$
PUT('ASYMP,1,'LEVEL)$  PUT('THETA,2,'LEVEL)$  PUT('O,3,'LEVEL)$
PUT('LO,4,'LEVEL)$  PUT('OMEGA,5,'LEVEL)$  PUT('LOMEGA,6,'LEVEL)$

ASYMPSIMP(EX) := /* Returns a simplified version of EX, if EX
      contains approximate subexpressions. */
   SCANMAP('ASIMP, EX, 'BOTTOMUP) $

ASIMP(EX) := BLOCK(/* EX has its subexpressions already simplified recursively.
      This function properly combines any top-level approximate
      subexpressions therein. */
   [INFLAG, MAXLEV, LEV, ASYMS, NONASYMS],
   IF MAPATOM(EX) THEN RETURN(EX),
   IF INPART(EX,0)="**" THEN
      IF NOT MAPATOM(INPART(EX,1)) 
         AND GET(VERBIFY(INPART(PIECE,0)),'LEVEL)#FALSE
         AND IS(SIGN(INPART(EX,2))='POS)
         THEN RETURN(APPLY(INPART(EX,1,0),
            [INPART(EX,1,1)**INPART(EX,2)]))
      ELSE RETURN(EX),
   INFLAG:TRUE,  MAXLEV:0,  ASYMS:NONASYMS:[],
   IF PIECE="*" THEN (
      FOR FCTR IN EX DO
         IF MAPATOM(FCTR) OR(LEV:GET(VERBIFY(INPART(FCTR,0)),'LEVEL))=FALSE
            THEN ASYMS: CONS(FCTR,ASYMS)
         ELSE IF INCOMENSURATE(LEV,MAXLEV) THEN
            NONASYMS: CONS(INPART(FCTR,1), NONASYMS)
         ELSE (ASYMS: CONS(INPART(FCTR,1), ASYMS),
            MAXLEV: MAX(LEV, MAXLEV)),
      IF MAXLEV=0 THEN RETURN(EX),
      ASYMS: APPLY(ASYM[MAXLEV], IF REST(ASYMS)=[] THEN ASYMS
            ELSE [APPLY("*", ASYMS)]),
      IF NONASYMS#[] THEN ASYMS: ASYMS*APPLY(
         IF MAXLEV<=4 THEN 'OMEGA ELSE 'O, IF REST(NONASYMS)=[] THEN
         NONASYMS ELSE APPLY("*",NONASYMS)),
      RETURN(ASYMS)),
   IF PIECE="+" THEN (
      FOR TRM IN EX DO
         IF MAPATOM(TRM) OR (LEV:GET(VERBIFY(INPART(TRM,0)),'LEVEL))=FALSE OR
            INCOMENSURATE(LEV,MAXLEV) THEN
            NONASYMS: CONS(TRM,NONASYMS)
         ELSE (ASYMS: CONS(INPART(TRM,1),ASYMS),
            MAXLEV: IF MAXLEV<=3 AND LEV=4 OR MAXLEV=4 AND LEV<=3 THEN 3
               ELSE IF LEV=6 AND (MAXLEV=5 OR MAXLEVEL<3) OR
                  MAXLEV=6 AND (LEV=5 OR LEV<3) THEN 5
               ELSE MAX(MAXLEV,LEV)),
      IF MAXLEV=0 THEN RETURN(EX),
      ASYMS: APPLY(ASYM[MAXLEV], IF REST(ASYMS)=[] THEN ASYMS
            ELSE [APPLY("+",ASYMS)]),
      IF NONASYMS#[] THEN ASYMS: ASYMS+(IF REST(NONASYMS)=[]
            THEN FIRST(NONASYMS) ELSE APPLY("+",NONASYMS)),
      RETURN(ASYMS)),
   ASYMS: PIECE,
   IF (LEV:GET(VERBIFY(PIECE),'LEVEL))#FALSE AND NOT MAPATOM(INPART(EX,1))
      AND (MAXLEV:GET(VERBIFY(INPART(PIECE,0)),'LEVEL))#FALSE THEN
      IF INCOMENSURATE(LEV,MAXLEV) THEN RETURN(EX)
      ELSE RETURN(APPLY(ASYM[MAX(LEV,MAXLEV)], [INPART(EX,1,1)])),
   EX) $

INCOMENSURATE(L1,L2) := /* Returns TRUE if approximation operators having
      numeric codes L1 and L2 cannot coalesce, returning FALSE
      otherwise. */
    (L1=3 OR L1=4) AND (L2=5 OR L2=6) $

TTYOFF: FALSE $
