SICP stream integrator - scheme

In SICP chapter 3.5 the code for a integrator is given
(define (solve f y0 dt)
(define y (integral dy y0 dt))
(define dy (stream-map f y))
y)
a signal-processing system for solving the differential equation dy/dt=f(y)
where f
is a given function. The figure shows a mapping component, which applies f
to its input signal, linked in a feedback loop to an integrator in a manner very similar to that of the analog computer circuits that are actually used to solve such equations.
What is the real life useage of the given program?

Related

Why does this numerical integration of the solar system keep running? (MIT-Scheme SCMUTILS)

I'm trying to do a numerical integration of the solar system. I did this before in plain Scheme, now I want to do it using the very interesting SCMUTILS-library from MIT. What I did:
I took solar system data from the Jet Propulsion Laboratory; that is: the mass, the position and the velocity of the sun, mercurius, venus and earth in barycentric coordinates.
I wrote code for the differential equation, such that every object in the system (sun, mercurius, venus, earth) gets attracted by the 3 others in the correct way.
I ran the simulation through numerical integration using SCMUTILS.
If I run the simulation with the sun + 1 other planet, it works. If I try to take the sun + 2 other planets, it seems to hang. This is strange as I ran the simulation with the same data a few years ago with my own home-built Runge-Kutta integrator, and that worked fine.
Note that I'm well-known with MIT-Scheme and numerical integration, and that I only want to learn SCMUTILS. I'm doing something wrong clearly, and it would surprise me if this problem couldn't be tackled with SCMUTILS.
Also, I'm not fixed on my code: if somebody can provide me with a working implementation in SCMUTILS, then that's fine as well, as long as I understand what I'm doing wrong in my program. I just want to use SCMUTILS in an idiomatic way...
My code is below (about 60 well-documented lines). Thanks for any comments or improvements towards a working simulation.
;JPL-DE - Ephemerides from Jet Propulsion Laboratory http://ssd.jpl.nasa.gov
(define solar-system
(up (+ 0.0 (decoded-time->universal-time (make-decoded-time 0 0 0 1 1 2000 0))) ; January 1st 2000 at 0h0m0s UT
(up (up 1.3271244004193937e20 ; Sun mass * gravitational constant
(up -1068000648.30182 -417680212.56849295 30844670.2068709) ; Sun position (x,y,z) in meters in barycentric coordinates
(up 9.305300847631916 -12.83176670344807 -.1631528028381386)) ; Sun velocity (vx,vy,vz) in meters per second
(up 22031780000000. ; Mercurius
(up -22120621758.62221 -66824318296.10253 -3461601353.17608)
(up 36662.29236478603 -12302.66986781422 -4368.33605178479))
(up 324858592000000. ; Venus
(up -108573550917.8141 -3784200933.160055 6190064472.97799)
(up 898.4651054838754 -35172.03950794635 -532.0225582712421))
; (up 398600435436000. ; Earth
; (up -26278929286.8248 144510239358.6391 30228181.35935813)
; (up -29830.52803283506 -5220.465685407924 -.1014621798034465))
)))
(define (ss-time state) ; Select time from solar system state
(ref state 0))
(define (ss-planets state) ; Select up-tuple with all planets
(ref state 1))
(define (ss-planet state i) ; Select i-th planet in system (0: sun, 1: mercurius, 2: venus, 3: earth) (Note: the sun is also a "planet")
(ref (ss-planets state) i))
(define (GM planet) ; Select GM from planet (GM is gravitational constant times mass of planet)
(ref planet 0))
(define (position planet) ; Select position up-tuple (x,y,z) from planet
(ref planet 1))
(define (velocity planet) ; Select velocity up-tuple (vx,vy,vz) from planet
(ref planet 2))
(define ((dy/dt) state)
(define (gravitational-force on-planet by-planet) ; Calculate gravitational force on planet "on-planet" by "by-planet"
(if (equal? on-planet by-planet) ; Compare planets
(up 0.0 0.0 0.0) ; There is no force of a planet on itself
(let* ((r (- (position on-planet) (position by-planet))) ; Position of "on-planet" seen from "by-planet"
(d (abs r))) ; Distance between the two
(* -1.0 (GM by-planet) (/ r (* d d d)))))) ; Gravitational force is negatively directed, we divide by d^3 to cancel out r in nominator
(define (dy/dt-planet planet) ; Calculate dy/dt for a planet
(up 0.0 ; No change in GM
(velocity planet) ; Change in position is velocity
(* (s:generate (s:length (ss-planets state)) 'up (lambda (i) (gravitational-force planet (ss-planet state i)))) ; Calculate gravitation forces from
(s:generate (s:length (ss-planets state)) 'down (lambda (i) 1.0))))) ; all other planets, and sum them via inner product with down-tuple
(up 1.0 ; Timestep: dt/dt=1.0
(s:generate (s:length (ss-planets state)) 'up (lambda (i) (dy/dt-planet (ss-planet state i)))))) ; Calculate dy/dt for all planets
(define win (frame -150e9 150e9 -150e9 150e9 512 512)) ; Viewport: a square of 300e9 meters by 300e9 meters plotted in a 512 by 512 window
(define ((monitor-xy) state)
((s:elementwise (lambda (planet) (plot-point win (ref (position planet) 0) (ref (position planet) 1)))) (ss-planets state))) ; Plot X,Y (no Z) for planets
(define end ; Define end state
((evolve dy/dt) ; Run simulation
solar-system ; Starting state, Jan. 1st 2000 0h0m0s
(monitor-xy) ; Plot positions throughout simulation
(* 1.0 60 60) ; Timestep: 1 hour
(decoded-time->universal-time (make-decoded-time 0 0 0 1 1 2005 0))) ; Run for 5 years
)
The way that scmutils handles the integration is interesting. The state derivative function works with a local tuple as described in SICM, but the integrator wants to work with a function that takes an array of floats as input and produces an array of equal size as output. To do this, scmutils takes the initial state data and replaces the values in it with symbols and passes this to your derivative. This produces symbolic output, which can be used to prepare a function with the right signature for the integrator. (I can describe this process in greater detail if you would like).
Your problem is in Cartesian coordinates, however, and the resulting symbolic expression is hairy. You can see this process in action by creating your own symbolic state and passing it to the derivative function, and simplifying the output (by passing the result through pe (print expression)):
(define symbolic-system
(up 't
(up (up 'g_0
(up 'x_0 'y_0 'z_0) ; Sun position (x,y,z) in meters in barycentric coordinates
(up 'vx_0 'vy_0 'vz_0)) ; Sun velocity (vx,vy,vz) in meters per second
(up 'g_1
(up 'x_1 'y_1 'z_1)
(up 'vx_1 'vy_1 'vz_1))
; (up 'g_2
; (up 'x_2 'y_2 'z_2)
; (up 'vx_2 'vy_2 'vz_2))
; (up 'g_3
; (up 'x_3 'y_3 'z_3)
; (up 'vx_3 'vy_3 'vz_3))
)))
(pe ((dy/dt) symbolic-system))
The result is immense, so I haven't pasted it here. If you now add another planet, by uncommenting the lines with subscript 2, you will find the print hangs, which means the expression simplifier has bogged down. The numerical integrator hasn't even run yet.
What to do? You might be able to get some capacity back by eliminating the z coordinate. You could move the GM parameters, which are constant, to the argument list of the state derivative constructor function, leaving only things that will change in the state tuple itself. You could flatten the state tuple a little; its structure is entirely up to you.
Ultimately, though, the function that gets integrated will be much more complicated than the function you would have written yourself, and a lot of that has to do with the sqrt(x^2 + y^2 + ...) terms that you get from the Cartesian coordinates. Scmutils was designed for problems where the use of generalized coordinates produces compact Lagrangians, from which simpler state derivative functions can be derived (automatically, which is the magic of scmutils). I think this particular problem would be an uphill climb.
[edit] SICM (which MIT has graciously made open-access) provides two examples I think you will find illustrative: the restricted three-body problem which economizes on coordinates by focusing on the 3rd body which is assumed to be much smaller than the other two, and having the coordinate system rotate such that the first two bodies lie on the x axis. Another example is spin orbit coupling in which there are two bodies, but the satellite's out of roundness is not negligible. Both of these approaches give formulations where there are many fewer coordinates than 3 * (number of bodies)

How does this Scheme code return a value?

This code is taken from Sussman and Wisdom's Structure and Interpretation of Classical Mechanics, its purpose is to derive (close to) the smallest positive floating point the host machine supports.
https://github.com/hnarayanan/sicm/blob/e37f011db68f8efc51ae309cd61bf497b90970da/scmutils/src/kernel/numeric.scm
Running it in DrRacket results in 2.220446049250313e-016 on my machine.
My question, what causes this to even return a value? This code is tail recursive, and it makes sense at some point the computer can no longer divide by 2. Why does it not throw?
(define *machine-epsilon*
(let loop ((e 1.0))
(if (= 1.0 (+ e 1.0))
(* 2 e)
(loop (/ e 2)))))
*machine-epsilon*
This code is tail recursive, and it makes sense at some point the computer can no longer divide by 2. Why does it not throw?
No, the idea is different: at some point the computer still can divide by 2, but the result (e) becomes indistinguishable from 0 [upd: in the context of floating-point addition only - very good point mentioned in the comment] (e + 1.0 = 1.0, this is exactly what if clause is checking). We know for sure that the previous e was still greater than zero "from the machine point of view" (otherwise we wouldn't get to the current execution point), so we simply return e*2.
This form of let-binding is syntactic sugar for recursion.
You may avoid using too much syntax until you master the language and write as much as possible using the kernel language, to focus on essential problem. For example, in full SICP text, never is specified this syntactic sugar for iteration.
The r6rs definition for iteration is here.
The purpose of this code is not to find the smallest float that the machine can support: it is to find the smallest float, epsilon such that (= (+ 1.0 epsilon) 1.0) is false. This number is useful because it's the upper bound on the error you get from adding numbers In particular what you know is that, say, (+ x y) is in the range [(x+y)*(1 - epsilon), (x+y)*(1 + epsilon)], where in the second expression + &c mean the ideal operations on numbers.
In particular (/ *machine-epsilon* 2) is a perfectly fine number, as is (/ *machine-epsilon* 10000) for instance, and (* (/ *machine-epsilon* x) x) will be very close to *machine-epsilon* for many reasonable values of x. It's just the case that (= (+ (/ *machine-epsilon* 2) 1.0) 1.0) is true.
I'm not familiar enough with floating-point standards, but the number you are probably thinking of is what Common Lisp calls least-positive-double-float (or its variants). In Racket you can derive some approximation to this by
(define *least-positive-mumble-float*
;; I don't know what float types Racket has if it even has more than one.
(let loop ([t 1.0])
(if (= (/ t 2) 0.0)
t
(loop (/ t 2)))))
I am not sure if this is allowed to raise an exception: it does not in practice and it gets a reasonable-looking answer.
It becomes clearer when you get rid of the confusing named let notation.
(define (calculate-epsilon (epsilon 1.0))
(if (= 1.0 (+ 1.0 epsilon))
(* epsilon 2)
(calculate-epsilon (/ epsilon 2))))
(define *machine-epsilon* (calculate-epsilon))
Is what the code does actually.
So now we see for what the named let expression is good.
It defines locally the function and runs it. Just that the name of the function as loop was very imprecise and confusing and the naming of epsilon to e is a very unhappy choice. Naming is the most important thing for readable code.
So this example of SICP should be an example for bad naming choices. (Okay, maybe they did it by intention to train the students).
The named let defines and calls/runs a function/procedure. Avoiding it would lead to better code - since clearer.
In common lisp such a construct would be much clearer expressed:
(defparameter *machine-epsilon*
(labels ((calculate-epsilon (&optional (epsilon 1.0))
(if (= 1.0 (+ 1.0 epsilon))
(* epsilon 2)
(calculate-epsilon (/ epsilon 2)))))
(calculate-epsilon)))
In CLISP implementation, this gives: 1.1920929E-7

Scheme: variable required in this context

I use mit-scheme compiler for learning Scheme.
The program which I write must compute equation roots via Vieta theorem.
(define (roots p q x-begin x-end)
(let ((x1 0.0) (x2 0.0))
(set! (x1 x-begin)) ; Error here Variable required in this context: (x1 x-begin)
(set! (x2 x-begin))
; ...
)
)
I guess that error concerned with static scope in Scheme.
What i do wrong?
P.S. Sorry for my english.
I'm not sure how you intend to calculate the roots but I can provide some advise regarding Scheme syntax, this is incorrect:
(set! (x1 x-begin))
It should be:
(set! x1 x-begin)
In general, using set! should be avoided whenever possible: in Scheme we try real hard to write programs that follow the functional-programming paradigm, and that includes not reassigning variables.

Scheme Symbols: Continuation Passing Style Examples

In a Wikipedia article about CPS there is the following code snippet, ostensibly from Scheme:
(define (pyth& x y k)
(*& x x (lambda (x2)
(*& y y (lambda (y2)
(+& x2 y2 (lambda (x2py2)
(sqrt& x2py2 k))))))))
I cannot find any other examples, or an explanation for the usages of "pyth&", "*&", and "+&". Much Googling has turned up only that there are other forms such as "=&" and "/&".
Those are just function names, pyth& is the CPS pythagoras procedure, *& is a CPS multiplication procedure and +& is a CPS addition procedure, different from their usual counterparts because they receive a continuation in the last parameter, the & at the end is simply a naming convention to remind you of this, which is stated at the beginning of the linked paragraph:
In CPS, each procedure takes an extra argument representing what should be done with the result the function is calculating

Why do function calls slow things down in clojure?

I've been playing around with the Is Clojure is Still Fast? (and prequel Clojure is Fast) code. It seemed unfortunate that inlining the differential equation (f) is one of the steps taken to improving performance. The cleanest/fastest thing I've been able to come up without doing this is the following:
; As in the referenced posts, for giving a rough measure of cycles/iteration (I know this is a very rough
; estimate...)
(def cpuspeed 3.6) ;; My computer runs at 3.6 GHz
(defmacro cyclesperit [expr its]
`(let [start# (. System (nanoTime))
ret# ( ~#expr (/ 1.0 ~its) ~its )
finish# (. System (nanoTime))]
(println (int (/ (* cpuspeed (- finish# start#)) ~its)))))
;; My solution
(defn f [^double t ^double y] (- t y))
(defn mysolveit [^double t0 ^double y0 ^double h ^long its]
(if (> its 0)
(let [t1 (+ t0 h)
y1 (+ y0 (* h (f t0 y0)))]
(recur t1 y1 h (dec its)))
[t0 y0 h its]))
; => 50-55 cycles/it
; The fastest solution presented by the author (John Aspden) is
(defn faster-solveit [^double t0 ^double y0 ^double h ^long its]
(if (> its 0)
(let [t1 (+ t0 h)
y1 (+ y0 (* h (- t0 y0)))]
(recur t1 y1 h (dec its)))
[t0 y0 h its]))
; => 25-30 cycles/it
The type hinting in my solution helps quite a bit (it's 224 cycles/it without type hinting on either f or solveit), but it's still nearly 2x slower than the inlined version. Ultimately this performance is still pretty decent, but this hit is unfortunate.
Why is there such a performance hit for this? Is there a way around it? Are there plans to find ways of improvingthis? As pointed out by John in the original post, it seems funny/unfortunate for function calls to be inefficient in a functional language.
Note: I'm running Clojure 1.5 and have :jvm-opts ^:replace [] in a project.clj file so that I can use lein exec/run without it slowing things down (and it will if you don't do this I discovered...)
Benchmarking in the presence of a JIT compiler is tricky; you really must allow for a warm-up period, but then you also can't just run it all in a loop, since it may then be proved a no-op and optimized away. In Clojure, the usual solution is to use Hugo Duncan's Criterium.
Running a Criterium benchmark for (solveit 0.0 1.0 (/ 1.0 1000000) 1000000) for both versions of solveit results in pretty much exactly the same timings on my machine (mysolveit ~3.44 ms, faster-solveit ~3.45 ms). That's in a 64-bit JVM run with -XX:+UseConcMarkSweepGC, using Criterium 0.4.2 (criterium.core/bench). Presumably HotSpot just inlines f. In any case, there's no performance hit at all.
Adding to the already good answers, the JVM JIT most often does inline the primitive function calls when warmed up, and in this case, when you bench it with a warmed JIT you see the same results. Just wanted to say Clojure also has an inlining feature though for cases where that yields benefits.
(defn f
{:inline-arities #{2}
:inline (fn [t y] `(- (double ~t) (double ~y)))}
^double [^double t ^double y]
(- t y))
Now Clojure will compile away the calls to f, inlining the function at compile time. Whereas the JIT will inline the function at runtime as needed otherwise.
Also note that I added a ^double type hint to the return of f, if you don't do that, it gets compiled to return Object, and a cast needs to be added, I'm not sure if that really affects performance much, but if you want a fully primitive function that takes primitives and return primitives you need to type hint the return as well.

Resources