; $Id: powerseries.scm,v 1.9 2008/03/08 03:52:39 uwe Exp $ ; A small package for manipulating power series implemented as streams. ; There is nothing here that forces the use of floating-point, so if user ; inputs are integers or rationals, the results will also be rational; this ; is nice for getting out exact results. ; The stream of natural numbers (0 1 2 3 ...), ; useful as a building block for stuff below (define ps-nat-numbers (letrec ((next (lambda (n) (cons n (delay (next (+ n 1))))))) (next 0))) ; Some functions for power-series specifically ; negation, addition, and subtraction are trivial (define (ps-neg ps) (stream-scale -1 ps)) (define (ps-add ps . pss) (apply stream-map (append (list + ps) pss))) (define (ps-sub ps . pss) (apply stream-map (append (list - ps) pss))) ; multiplication and reciprocal are a little less trivial (define (ps-mul ps1 ps2) (let ((h1 (car ps1)) (h2 (car ps2)) (t1 (cdr ps1)) (t2 (cdr ps2))) (cons (* h1 h2) (delay (ps-add (stream-scale h1 (force t2)) (stream-scale h2 (force t1)) (cons 0 (ps-mul (force t1) (force t2)))))))) ; It is an error to take the reciprocal of a power series without a constant ; term because such a resulting series is not representable as a power ; series: it contains negative powers of x, and these cause infinities at x ; = 0. It would be a Laurent series, and we don't handle these. (define (ps-recip ps) (when (zero? (car ps)) (raise "ps-recip error: power series has no constant term!")) (letrec* ((r (cons 1 (delay (ps-neg (ps-mul r (fcdr ps))))))) (stream-scale (/ (car ps)) r))) ; division is trivial when expressed in terms of reciprocal (define (ps-div ps1 ps2) (ps-mul ps1 (ps-recip ps2))) ; Term-by-term differentiation and integration (define (ps-derivative ps) (fcdr (stream-map * ps-nat-numbers ps))) (define (ps-integral ps ctrm) (cons ctrm (stream-map / ps (fcdr ps-nat-numbers)))) ; Semi-evaluate a power-series by multiplying each term by a specific x ; raised to the Nth power; the sum of all of these is the actual value of ; the expression, which may be approximated by the ps-sums routine below. (define (ps-eval ps x) (stream-map * ps (stream-map (lambda (n) (expt x n)) ps-nat-numbers))) ; The stream of partial sums of the input (define (ps-sums ps) (letrec ((accfn (lambda (acc str) (set! acc (+ acc (car str))) (cons acc (delay (accfn acc (fcdr str))))))) (accfn 0 ps))) ; TODO: Aitken acceleration formula (but is x_k here the kth term, or ; the kth partial sum??? check that!) ; new estimate = x_k - (x_{k+1} - x_k)^2/(x_{k+2} - 2*x_{k+1} + x_k) ; And some actual power series ; geometric series 1/(1 - x) (define ps-geom (stream-map (lambda (n) 1) ps-nat-numbers)) (define ps-exp (stream-map (lambda (n) (/ (factorial n))) ps-nat-numbers)) ; ln(1+x) -- valid only for -1 < x <= 1 ; note that this converges *very* slowly for |x| near 1 (define ps-logxp1 (cons 0 (stream-map (lambda (n) (/ (expt -1 (+ n 1)) n)) (fcdr ps-nat-numbers)))) (define ps-sin (stream-map (lambda (n) (if (even? n) 0 (/ (expt -1 (quotient (- n 1) 2)) (factorial n)))) ps-nat-numbers)) (define ps-cos (stream-map (lambda (n) (if (odd? n) 0 (/ (expt -1 (quotient n 2)) (factorial n)))) ps-nat-numbers)) (define ps-tan (ps-div ps-sin ps-cos)) (define ps-atan (stream-map (lambda (n) (if (even? n) 0 (/ (expt -1 (quotient (- n 1) 2)) n))) ps-nat-numbers)) (define ps-sinh (stream-map (lambda (n) (if (even? n) 0 (/ (factorial n)))) ps-nat-numbers)) (define ps-cosh (stream-map (lambda (n) (if (odd? n) 0 (/ (factorial n)))) ps-nat-numbers)) (define ps-tanh (ps-div ps-sinh ps-cosh)) (define ps-atanh (stream-map (lambda (n) (if (even? n) 0 (/ n))) ps-nat-numbers)) ; exp(-x^2) (define ps-gaussian (stream-map (lambda (n) (if (odd? n) 0 (begin (set! n (quotient n 2)) (/ (expt -1 n) (factorial n))))) ps-nat-numbers)) ; This is actually not quite Erf(x): there is a scale factor of 1/sqrt(pi) ; missing. I'm leaving that out so that stuff doesn't get forced to ; floating-point, since I don't have an infinite-precision rational version ; of (sqrt pi). (define ps-erf (stream-scale 2 (ps-integral ps-gaussian 0))) ; Bessel functions J_n for integer n >= 0: ; ; (-1)^m x^(2*m) ; J_n = x^n * sum_m=0^infinity ------------------- ; 2^(2*m+n)*m!*(m+n)! (define (ps-bessel-j n) (when (negative? n) (raise "ps-bessel-j can't handle negative n!")) (letrec ((term-fn (lambda (m2) (if (odd? m2) 0 (let ((m (quotient m2 2))) (/ (expt -1 m) (* (expt 2 (+ m2 n)) (factorial m) (factorial (+ m n)))))))) (cons-fn (lambda (m obj) (if (zero? m) obj (cons 0 (cons-fn (- m 1) obj)))))) (cons-fn n (stream-map term-fn ps-nat-numbers)))) ; Bessel functions I_n for integer n >= 0: ; ; x^(2*m) ; I_n = x^n * sum_m=0^infinity ------------------- ; 2^(2*m+n)*m!*(m+n)! (define (ps-bessel-i n) (when (negative? n) (raise "ps-bessel-i can't handle negative n!")) (letrec ((term-fn (lambda (m2) (if (odd? m2) 0 (let ((m (quotient m2 2))) (/ (* (expt 2 (+ m2 n)) (factorial m) (factorial (+ m n)))))))) (cons-fn (lambda (m obj) (if (zero? m) obj (cons 0 (cons-fn (- m 1) obj)))))) (cons-fn n (stream-map term-fn ps-nat-numbers)))) ; Lambert W function: this satisfies the implicit equation W*exp(W) = x. ; This series converges for |x| < exp(-1). The function is multi-valued ; for -exp(-1) < x < 0; this series converges to the value closest to 0. (define ps-lambert-w (cons 0 (stream-map (lambda (n) (/ (expt (- n) (- n 1)) (factorial n))) (fcdr ps-nat-numbers)))) ; A couple of small utility functions to more easily show streams and ; tabulate values (define (ps-show n p strm) (if (zero? p) (for-each (lambda (val) (write-string (number->string val 10) #\linefeed)) (stream-head strm n)) (for-each (lambda (val) (write-string (number->string val 10 p) #\linefeed)) (stream-head strm n)))) (define (ps-table fn nterms lo hi step) (do ((x lo (+ x step))) ((> x hi) #t) (write-string (number->string x 10 8) #\tab (number->string (last (stream-head (ps-sums (ps-eval fn x)) nterms)) 10 -8) #\linefeed)))