1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
\extrapart{Example} % -*- Mode: Lisp; Package: SCHEME; Syntax: Common-lisp -*-
\nobreak
{\cf Integrate-system} integrates the system
$$y_k^\prime = f_k(y_1, y_2, \ldots, y_n), \; k = 1, \ldots, n$$
of differential equations with the method of Runge-Kutta.
The parameter {\tt system-derivative} is a function that takes a system
state (a vector of values for the state variables $y_1, \ldots, y_n$)
and produces a system derivative (the values $y_1^\prime, \ldots,
y_n^\prime$). The parameter {\tt initial-state} provides an initial
system state, and {\tt h} is an initial guess for the length of the
integration step.
The value returned by {\cf integrate-system} is an infinite stream of
system states.
\begin{schemenoindent}
(define integrate-system
(lambda (system-derivative initial-state h)
(let ((next (runge-kutta-4 system-derivative h)))
(letrec ((states
(cons initial-state
(delay (map-streams next
states)))))
states))))%
\end{schemenoindent}
{\cf Runge-Kutta-4} takes a function, {\tt f}, that produces a
system derivative from a system state. {\cf Runge-Kutta-4}
produces a function that takes a system state and
produces a new system state.
\begin{schemenoindent}
(define runge-kutta-4
(lambda (f h)
(let ((*h (scale-vector h))
(*2 (scale-vector 2))
(*1/2 (scale-vector (/ 1 2)))
(*1/6 (scale-vector (/ 1 6))))
(lambda (y)
;; y {\rm{}is a system state}
(let* ((k0 (*h (f y)))
(k1 (*h (f (add-vectors y (*1/2 k0)))))
(k2 (*h (f (add-vectors y (*1/2 k1)))))
(k3 (*h (f (add-vectors y k2)))))
(add-vectors y
(*1/6 (add-vectors k0
(*2 k1)
(*2 k2)
k3))))))))
%|--------------------------------------------------|
(define elementwise
(lambda (f)
(lambda vectors
(generate-vector
(vector-length (car vectors))
(lambda (i)
(apply f
(map (lambda (v) (vector-ref v i))
vectors)))))))
%|--------------------------------------------------|
(define generate-vector
(lambda (size proc)
(let ((ans (make-vector size)))
(letrec ((loop
(lambda (i)
(cond ((= i size) ans)
(else
(vector-set! ans i (proc i))
(loop (+ i 1)))))))
(loop 0)))))
(define add-vectors (elementwise +))
(define scale-vector
(lambda (s)
(elementwise (lambda (x) (* x s)))))%
\end{schemenoindent}
{\cf Map-streams} is analogous to {\cf map}: it applies its first
argument (a procedure) to all the elements of its second argument (a
stream).
\begin{schemenoindent}
(define map-streams
(lambda (f s)
(cons (f (head s))
(delay (map-streams f (tail s))))))%
\end{schemenoindent}
Infinite streams are implemented as pairs whose car holds the first
element of the stream and whose cdr holds a promise to deliver the rest
of the stream.
\begin{schemenoindent}
(define head car)
(define tail
(lambda (stream) (force (cdr stream))))%
\end{schemenoindent}
\bigskip
The following illustrates the use of {\cf integrate-system} in
integrating the system
$$ C {dv_C \over dt} = -i_L - {v_C \over R}$$\nobreak
$$ L {di_L \over dt} = v_C$$
which models a damped oscillator.
\begin{schemenoindent}
(define damped-oscillator
(lambda (R L C)
(lambda (state)
(let ((Vc (vector-ref state 0))
(Il (vector-ref state 1)))
(vector (- 0 (+ (/ Vc (* R C)) (/ Il C)))
(/ Vc L))))))
(define the-states
(integrate-system
(damped-oscillator 10000 1000 .001)
'\#(1 0)
.01))%
\end{schemenoindent}
\todo{Show some output?}
% (letrec ((loop (lambda (s)
% (newline)
% (write (head s))
% (loop (tail s)))))
% (loop the-states))
% #(1 0)
% #(0.99895054 9.994835e-6)
% #(0.99780226 1.9978681e-5)
% #(0.9965554 2.9950552e-5)
% #(0.9952102 3.990946e-5)
% #(0.99376684 4.985443e-5)
% #(0.99222565 5.9784474e-5)
% #(0.9905868 6.969862e-5)
% #(0.9888506 7.9595884e-5)
% #(0.9870173 8.94753e-5)
|