ttyoff:true$				/* -*-Macsyma-*- */

/*  Linear difference equation package	*/ 

magnitude(vector):=
block([scalarmatrix:true],
      if listp(vector) or length(vector)=1
       then return (sqrt(vector.transpose(vector))),
      if length(transpose(vector))=1
       then return (sqrt(transpose(vector).vector)),
      print ("magnitude:  not a vector --",vector), false)$

/*  Returns a list of the eigenvalues of a matrix.
    Does not handle repeated eigenvalues.  */

eigenvalues(mx):=
block([loadprint:false,programmode:true,charpoly,lambda,result],
      if length(mx) # length(transpose(mx))
      then (print ("eigenvalues:  not a square matrix --",mx), return(false)),
      charpoly:charpoly(mx,lambda),
      result:solve(charpoly,lambda),
      makelist(rhs(x),x,result))$


/*  Returns a list which is a transpose of the eigenvector  */

eigenvector(mx,eigenvalue):=
block([loadprint:false,programmode:true,ttyoff:true,
       degree,xlist,xvector,eqnlist,result],
      degree:length(mx),
      if degree # length(transpose(mx))
      then (print("eigenvector:  not a square matrix --",mx), return(false)),
      xlist:makelist(concat('x,i),i,1,degree),
      xvector:transpose(matrix(xlist)),
      mx:mx.xvector-eigenvalue*xvector,
      eqnlist:makelist(mx[i,1]=0,i,1,degree),
      if member(0=0,eqnlist)
         then eqnlist:delete(0=0,eqnlist)
	 else eqnlist:rest(eqnlist),
      result:first(solve(cons(x1=1,eqnlist),xlist)),
      result:makelist(part(x,2),x,result),
      if normalize=true then result/magnitude(result) else result)$

/* Puts equations in the form x[n+1] = a x[n] + b y[n] + c z[n] + ...	     */
/* Be careful for the case where eqn is x[n+1] = x[n+1] and var is x[n]	     */
/* SOLVE returns ALL for SOLVE(X=X, X), i.e. arbitrarily many solutions.     */

standardize(eqn,var):=
block([x,y],
      x:part(var,0)[part(var,1)+1],
      y:solve(eqn,x),
      if y = [] then (print("difference:  no",x,"term --",eqn),
                      throw(missing_term)),
      if y = 'all then eqn else first(y)
      )$

/*  Solves single first order equations of the form f[n+1] = a f[n] + b  */

first_order_difference(eqn,var):=
block([a,b,simpsum:true],
      a:coeff(rhs(eqn),var),
      b:rhs(eqn)-a*var,
      var=a^index*array[0]+b*sum(a^k,k,0,index-1))$

/*  Solves a single higher order equation by converting to a first order system  */

second_order_difference(eqn,var):= block([],
      part(system([eqn,array[index+1]=array[index+1]],[array[index+1],var]),2,1))$

/*  Solves a system of first order equations  */

system(eqnlist,varlist):=
block([listarith:true,normalize:false,a,u,lambdas,s,sinverse,d],
      eqnlist:map(lambda([x,y],rhs(standardize(x,y))),eqnlist,varlist),
      a:makelist(makelist(coeff(eqn,var),var,varlist),eqn,eqnlist),
      a:apply(matrix,a),
      u[0]:transpose(makelist(ev(var,solve(index=0)),var,varlist)),
      u[index]:transpose(varlist),
      if differverbose=true then (ldisplay(u[index],u[0],a),
                                  ldisp('u[index+1]='a*'u[index],
				        'u[index]='(s.lambda^n.s^"- 1".u[0]))),
      lambdas:eigenvalues(a),
      s:makelist(eigenvector(a,lambda),lambda,lambdas),
      s:transpose(apply(matrix,s)),
      sinverse:s^^-1,
      d:lambdas*ident(length(lambdas)),
      u[index]:s.d^index.sinverse.u[0],
      transpose(map("=",varlist,part(transpose(u[index]),1))))$

/*  General toplevel function  */

difference(eqn,var):=
block([loadprint:false,programmode:true,array,index,higherorder],
      if listp(eqn) then (array:makelist(part(x,0),x,var),
                          index:part(first(var),1),
                          return (system(eqn,var))),
      array:part(var,0),
      index:part(var,1),
      higherorder:makelist(array[index+n],n,2,5),
      eqn:catch(standardize(eqn,var)),
      if eqn = missing_term then return(done),
      if apply("and",map(lambda([x],freeof(x,eqn)),higherorder))
         then first_order_difference(eqn,var)
	 else second_order_difference(eqn,var))$

/* Bugs, comments to CWH@MC
   Things to do:
   Rewrite this in Lisp
   Put a catch around the call to standardize from difference.
   Become more intelligent about cases like f[n]=f[n-1]+f[n-2]. */

ttyoff:false$
