Mandelbrot for the terminal

188 views
Skip to first unread message

Frank Buss

unread,
Jul 30, 2004, 5:39:05 PM7/30/04
to
Based on a Java program ( http://tinyurl.com/57smz ) I've implemented a
Lisp program for showing the Mandelbrot set:

(loop for y from -1 to 1.1 by 0.1 do
(loop for x from -2 to 1 by 0.04 do
(let* ((c 126)
(z (complex x y))
(a z))
(loop while (< (abs
(setq z (+ (* z z) a)))
2)
while (> (decf c) 32))
(princ (code-char c))))
(format t "~%"))

I tried to make it as short as possible and it is some chars shorter than
the Java program, if written in one line without much spaces. With CLISP,
the program is only 4 times slower than the Java program. Nice result, I
didn't expect this with my unoptimized program and using complex variables.

--
Frank Buß, f...@frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de

Edi Weitz

unread,
Jul 30, 2004, 6:08:54 PM7/30/04
to
On Fri, 30 Jul 2004 21:39:05 +0000 (UTC), Frank Buss <f...@frank-buss.de> wrote:

> Based on a Java program ( http://tinyurl.com/57smz ) I've
> implemented a Lisp program for showing the Mandelbrot set:
>
> (loop for y from -1 to 1.1 by 0.1 do
> (loop for x from -2 to 1 by 0.04 do
> (let* ((c 126)
> (z (complex x y))
> (a z))
> (loop while (< (abs
> (setq z (+ (* z z) a)))
> 2)
> while (> (decf c) 32))
> (princ (code-char c))))
> (format t "~%"))
>
> I tried to make it as short as possible and it is some chars shorter
> than the Java program, if written in one line without much
> spaces. With CLISP, the program is only 4 times slower than the Java
> program. Nice result, I didn't expect this with my unoptimized
> program and using complex variables.

If I change it a little bit

(defun mandel ()


(loop for y from -1 to 1.1 by 0.1 do
(loop for x from -2 to 1 by 0.04 do
(let* ((c 126)
(z (complex x y))
(a z))

(loop while (and (< (abs


(setq z (+ (* z z) a)))
2)

(> (decf c) 32)))
(princ (code-char c))))
(princ #\Newline)))

and compile it with CMUCL (19a-pre3) then it's actually faster by a
factor of 1.3 than the Java (1.5.0-beta2) version on my
laptop. Cool... :)

--

"Lisp doesn't look any deader than usual to me."
(David Thornley, reply to a question older than most languages)

Real email: (replace (subseq "spam...@agharta.de" 5) "edi")

Edi Weitz

unread,
Jul 30, 2004, 6:19:29 PM7/30/04
to
On Sat, 31 Jul 2004 00:08:54 +0200, Edi Weitz <spam...@agharta.de> wrote:

> If I change it a little bit
>
> (defun mandel ()
> (loop for y from -1 to 1.1 by 0.1 do
> (loop for x from -2 to 1 by 0.04 do
> (let* ((c 126)
> (z (complex x y))
> (a z))
> (loop while (and (< (abs
> (setq z (+ (* z z) a)))
> 2)
> (> (decf c) 32)))
> (princ (code-char c))))
> (princ #\Newline)))
>
> and compile it with CMUCL (19a-pre3) then it's actually faster by a
> factor of 1.3 than the Java (1.5.0-beta2) version on my
> laptop. Cool... :)

Faster even if you start cheating a little:

(defun mandel ()
(loop for y from -1 to 1.1 by 0.1 do
(loop for x from -2 to 1 by 0.04 do

(let* ((z (complex x y))
(a z))
(loop for c in '#.(loop for c downfrom 126 above 32
collect (code-char c))


while (< (abs
(setq z (+ (* z z) a)))
2)

finally (princ c))))
(princ #\Newline)))

But I'll stop now... :)

Peter Seibel

unread,
Jul 30, 2004, 6:17:42 PM7/30/04
to
Edi Weitz <spam...@agharta.de> writes:

Man, that's rad! Just pasted that puppy in my REPL and boom! ASCII art
Mandlebrot. Nice.

-Peter

--
Peter Seibel pe...@javamonkey.com

Lisp is the red pill. -- John Fraser, comp.lang.lisp

neo88

unread,
Jul 30, 2004, 10:40:08 PM7/30/04
to
Peter Seibel <pe...@javamonkey.com> wrote in message news:<m3hdrpb...@javamonkey.com>...

Very cool. How did you get the ascii characters though? I don't see
them anywhere in the code. All I see that might even have something to
do with it is
(princ (code-char c))))

--
May the Source be with you.
neo88 (Philip Haddad)

Abdulaziz Ghuloum

unread,
Jul 31, 2004, 12:48:50 AM7/31/04
to
Frank Buss wrote:

> Based on a Java program ( http://tinyurl.com/57smz ) I've implemented a
> Lisp program for showing the Mandelbrot set:
>
> (loop for y from -1 to 1.1 by 0.1 do
> (loop for x from -2 to 1 by 0.04 do
> (let* ((c 126)
> (z (complex x y))
> (a z))
> (loop while (< (abs
> (setq z (+ (* z z) a)))
> 2)
> while (> (decf c) 32))
> (princ (code-char c))))
> (format t "~%"))

> ...

And a scheme version:

(define (mandel)
(do ((y -1 (+ y 0.1))) ((> y 1))
(do ((x -2 (+ x 0.04))) ((> x 1))
(let ((a (make-rectangular x y)))
(let loop ((z a) (c 126))
(let ((q (+ a (* z z))))
(if (and (< (magnitude q) 2) (> c 32))
(loop q (sub1 c))
(display (integer->char c)))))))
(newline)))


Aziz,,,

pkhuong

unread,
Jul 31, 2004, 2:46:26 AM7/31/04
to
sol...@truevine.net (neo88) wrote in message news:<6a73bb68.04073...@posting.google.com>...

> Peter Seibel <pe...@javamonkey.com> wrote in message news:<m3hdrpb...@javamonkey.com>...
> > Edi Weitz <spam...@agharta.de> writes:
[...]

> > > If I change it a little bit
> > >
> > > (defun mandel ()
> > > (loop for y from -1 to 1.1 by 0.1 do
> > > (loop for x from -2 to 1 by 0.04 do
> > > (let* ((c 126)
> > > (z (complex x y))
> > > (a z))
> > > (loop while (and (< (abs
> > > (setq z (+ (* z z) a)))
> > > 2)
> > > (> (decf c) 32)))
> > > (princ (code-char c))))
> > > (princ #\Newline)))
> > >
> > > and compile it with CMUCL (19a-pre3) then it's actually faster by a
> > > factor of 1.3 than the Java (1.5.0-beta2) version on my
> > > laptop. Cool... :)
> >
> > Man, that's rad! Just pasted that puppy in my REPL and boom! ASCII art
> > Mandlebrot. Nice.
> >
> > -Peter
>
> Very cool. How did you get the ascii characters though? I don't see
> them anywhere in the code. All I see that might even have something to
> do with it is
> (princ (code-char c))))
Yes. code-char/char-code is not specified to have anything to do with
ASCII, but i guess you can assume it does in most non-brain-damaged
implementations.

Alan Crowe

unread,
Jul 31, 2004, 2:52:35 AM7/31/04
to
neo88 asked:

> Very cool. How did you get the ascii characters though? I
> don't see them anywhere in the code. All I see that might
> even have something to do with it is
>
> (princ (code-char c))

Spot the magic numbers 32 and 126

(char-code #\space) => 32
(char-code #\~) => 126

the code commits itself to the ASCII character set.

--
Alan Crowe
Edinburgh
Scotland

Frank Buss

unread,
Jul 31, 2004, 4:53:11 AM7/31/04
to
Edi Weitz <spam...@agharta.de> wrote:

> (loop for c in '#.(loop for c downfrom 126 above 32
> collect (code-char c))

this is really nice, I didn't know the reader macros before.

Ok, and now with TGA output, in the size of my screen for a new wallpaper
(see http://www.frank-buss.de/tmp/mandelbrot.jpg for a quality reduced
version). The program finished in 346 seconds run time, but that's mainly
because the algorithm is not optimized. There is a recursive algorithm
which calculates only 4 border points of a rectangle and fills it, if it
has the same color, which should reduce the running time to less than 10
seconds, I think, because most time the algorithm is spending in the
black area.

(defun mandelbrot ()
(let* ((width 1600)
(height 1024)
(x0 0.25)
(y0 0.5)
(x1 0.43)
(y1 0.71)
(image
(make-array (list width height 3)
:element-type '(unsigned-byte 8)
:initial-element 0)))
(loop for y from 0 to (1- height) do
(loop for x from 0 to (1- width) do
(let* ((c 60)
(z (complex
(float (+ (* (- x1 x0) (/ x width)) x0))
(float (+ (* (- y1 y0) (/ y height)) y0))))
(a z))
(loop while (and (< (square


(setq z (+ (* z z) a)))

4)
(> (decf c) 0)))
(if (> c 0) (progn
(setf (aref image x y 0)
(mod (* 4 c) 256))
(setf (aref image x y 1)
(mod (* 8 c) 256))
(setf (aref image x y 2)
(mod (* 13 c) 256)))))))
(write-tga image "mandelbrot.tga")))

(defun square (z)
(+ (* (realpart z) (realpart z)) (* (imagpart z) (imagpart z))))

(defun write-tga (image filename)
(let* ((dimension (array-dimensions image))
(width (nth 0 dimension))
(height (nth 1 dimension)))
(with-open-file
(tga filename
:direction :output
:if-exists :supersede
:element-type 'unsigned-byte)
(dolist (byte (list 0 0 2 0 0 0 0 0 0 0 0 0
(mod width 256) (floor width 256)
(mod height 256) (floor height 256) 24 0))
(write-byte byte tga))
(loop for y from 0 to (1- height) do
(loop for x from 0 to (1- width) do
(write-byte (aref image x y 0) tga)
(write-byte (aref image x y 1) tga)
(write-byte (aref image x y 2) tga))))))

Wolfhard Buß

unread,
Jul 31, 2004, 8:18:40 AM7/31/04
to
Edi Weitz:

> (defun mandel ()
> (loop for y from -1 to 1.1 by 0.1 do
> (loop for x from -2 to 1 by 0.04 do
> (let* ((z (complex x y))
> (a z))
> (loop for c in '#.(loop for c downfrom 126 above 32
> collect (code-char c))
> while (< (abs
> (setq z (+ (* z z) a)))
> 2)
> finally (princ c))))
> (princ #\Newline)))
>

Not the final iteration:

(loop for y from -1 to 1.1 by 0.1 do
(loop for x from -2 to 1 by 0.04 do

(loop with a = (complex x y)
for z = a then (+ (* z z) a)
for c in '#.(loop for c downfrom 126 downto 32
collect (code-char c))
while (< (* z (conjugate z) 4)
finally (princ c)))
(princ #\Newline))

> But I'll stop now... :)

--
"Brown the leaves, gray the sky, the horizont - white."
-- Unknown

David Steuber

unread,
Jul 31, 2004, 9:23:10 AM7/31/04
to
Abdulaziz Ghuloum <aghu...@c-s-remove-dashes.indiana.edu> wrote in message news:<cef8bj$jhv$1...@hood.uits.indiana.edu>...

> And a scheme version:
>
> (define (mandel)
> (do ((y -1 (+ y 0.1))) ((> y 1))
> (do ((x -2 (+ x 0.04))) ((> x 1))
> (let ((a (make-rectangular x y)))
> (let loop ((z a) (c 126))
> (let ((q (+ a (* z z))))
> (if (and (< (magnitude q) 2) (> c 32))
> (loop q (sub1 c))
> (display (integer->char c)))))))
> (newline)))

I really should get back to my CL version that does images. My movie
is still on my website. But that was done with a combination of Perl
and C.

http://www.david-steuber.com/~david/fractal-movie/

Pascal Bourguignon

unread,
Jul 31, 2004, 11:42:16 AM7/31/04
to
pvk_g...@pvk.ca (pkhuong) writes:
> > Very cool. How did you get the ascii characters though? I don't see
> > them anywhere in the code. All I see that might even have something to
> > do with it is
> > (princ (code-char c))))
> Yes. code-char/char-code is not specified to have anything to do with
> ASCII, but i guess you can assume it does in most non-brain-damaged
> implementations.

The first ASCII art were done in EBCDIC!


--
__Pascal Bourguignon__ http://www.informatimago.com/

There is no worse tyranny than to force a man to pay for what he does not
want merely because you think it would be good for him. -- Robert Heinlein

Jim Newton

unread,
Aug 1, 2004, 4:19:10 AM8/1/04
to
i'm not sure how the algorithm is supposed to be
selecting the character to display, but it does not
seem very adapatable if i try to zoom in on the
boundary of the mandelbrot set.

Here is my attempt to allow the user to zoom
in on various parts of the picture.

After each drawing you can select a quadrant to
zoom into

|
2 | 1
|
------+------
|
3 | 4
|

However, as you zoom it does not seem to show
any extra detail. Is there something wrong with
my function?

(defun mandel ()
(let (( x0 -2.0)
( y0 -1.0)
( x1 1.0)
( y1 1.1)
midx
midy
)
(loop while t do
(window x0 y0 x1 y1)
(setf midx (/ (+ x1 x0) 2.0)
midy (/ (+ y1 y0) 2.0))
(format t "quadrant: ")
(multiple-value-setq ( x0 y0 x1 y1 )
(case (read)
( 1 (values midx midy x1 y1))
( 2 (values x0 midy midx y1))
( 3 (values x0 y0 midx midy))
( 4 (values midx y0 x1 midy))
( t (values x0 y0 x1 y1)))))))

(defun window ( x0 y0 x1 y1)
(let (( dx (/ (- x1 x0) 30.0))
( dy (/ (- y1 y0) 30.0)))
(format t "x0=~A y0=~A x1=~A y1=~A dx=~A dy=~A~%"
x0 y0 x1 y1 dx dy)

(loop for y from y0 to y1 by dy do
(loop for x from x0 to x1 by dx do


(let* ((c 126)
(z (complex x y))
(a z))
(loop while (and (< (abs
(setq z (+ (* z z) a)))
2)
(> (decf c) 32)))
(princ (code-char c))))

(princ #\Newline))))

Jim Newton

unread,
Aug 1, 2004, 4:45:48 AM8/1/04
to
ahh i see the problem... the current implementation
draws the quadrants with the y axis inverted.

|
3 | 4
|
------+------
|
2 | 1
|

i've modified the code to decrement y instead of increment.
and it seems to work well... except that
when dx or dy becomes in the range of dx=1.5894572e-8 dy=1.5894572e-8
very strange things happen... i wonder is this a problem with
percision on the x86?

x0=-0.04844904 y0=0.80464506 x1=-0.048448563 y1=0.80464554
dx=1.5894572e-8 dy=1.5894572e-8


give it a try...

(defun mandel ()
(let (( x0 -2.0) ;;( x0 -2.0)
( y0 -2.0) ;;( y0 -1.0)
( x1 2.0) ;;( x1 1.0)
( y1 2.0) ;;( y1 1.1)


midx
midy
)
(loop while t do
(window x0 y0 x1 y1)
(setf midx (/ (+ x1 x0) 2.0)
midy (/ (+ y1 y0) 2.0))
(format t "quadrant: ")
(multiple-value-setq ( x0 y0 x1 y1 )
(case (read)
( 1 (values midx midy x1 y1))
( 2 (values x0 midy midx y1))
( 3 (values x0 y0 midx midy))
( 4 (values midx y0 x1 midy))
( t (values x0 y0 x1 y1)))))))

(defun window ( x0 y0 x1 y1)
(let (( dx (/ (- x1 x0) 30.0))
( dy (/ (- y1 y0) 30.0)))
(format t "x0=~A y0=~A x1=~A y1=~A dx=~A dy=~A~%"
x0 y0 x1 y1 dx dy)

(princ "2")


(loop for x from x0 to x1 by dx do

(princ "-"))
(princ "1")
(princ #\Newline)
(loop for y from y1 downto y0 by dy do
(princ "|")


(loop for x from x0 to x1 by dx do
(let* ((c 126)
(z (complex x y))
(a z))
(loop while (and (< (abs
(setq z (+ (* z z) a)))
2)
(> (decf c) 32)))
(princ (code-char c))))

(princ "|")
(princ #\Newline))
(princ "3")


(loop for x from x0 to x1 by dx do

(princ "-"))
(princ "4")
(princ #\Newline)))

(compile 'mandel)
(compile 'window)
(mandel)

-jim

Frank Buss

unread,
Aug 1, 2004, 5:59:28 AM8/1/04
to
Jim Newton <ji...@rdrop.com> wrote:

> i'm not sure how the algorithm is supposed to be
> selecting the character to display, but it does not
> seem very adapatable if i try to zoom in on the
> boundary of the mandelbrot set.
>
> Here is my attempt to allow the user to zoom
> in on various parts of the picture.
>
> After each drawing you can select a quadrant to
> zoom into
>
> |
> 2 | 1
> |
> ------+------
> |
> 3 | 4
> |
>
> However, as you zoom it does not seem to show
> any extra detail. Is there something wrong with
> my function?

works well on my computer. But the quadrants are mirrored at the y-axis,
because you test at negative y coordinates first. Try this:

4322321221121243141341

This zooms 1 : 4,000,000, but after the last '1' the program ended in an
endless loop, because looks like my floating point precision is limited
to 8 significant digits with your program.

Jim Newton

unread,
Aug 1, 2004, 5:24:34 AM8/1/04
to
you can now call (mandel...) with the zoom
quadrants

e.g., (mandel 2 4 1 1)

after the zooming, it goes into interactive mode.
Yes there seems to be a limitation around dx=1e-8...
curious.

-jim

(defun mandel ( &rest quadrants)
(let (( x0 -2.0)
( y0 -2.0)
( x1 2.0)
( y1 2.0))

(dolist ( q quadrants)


(multiple-value-setq ( x0 y0 x1 y1 )

(select-quadrant q x0 y0 x1 y1 )))

(loop while t do
(draw-quadrant x0 y0 x1 y1)


(format t "quadrant: ")
(multiple-value-setq ( x0 y0 x1 y1 )

(select-quadrant (read) x0 y0 x1 y1)))))

(defun select-quadrant ( q x0 y0 x1 y1)
(let (( midx (/ (+ x1 x0) 2.0))
( midy (/ (+ y1 y0) 2.0)))
(case q


( 1 (values midx midy x1 y1))
( 2 (values x0 midy midx y1))
( 3 (values x0 y0 midx midy))
( 4 (values midx y0 x1 midy))
( t (values x0 y0 x1 y1)))))

(defun draw-quadrant ( x0 y0 x1 y1)


(let (( dx (/ (- x1 x0) 30.0))
( dy (/ (- y1 y0) 30.0)))
(format t "x0=~A y0=~A x1=~A y1=~A dx=~A dy=~A~%"
x0 y0 x1 y1 dx dy)

(princ "2")


(loop for x from x0 to x1 by dx do

(princ "-"))
(princ "1")
(princ #\Newline)
(loop for y from y1 downto y0 by dy do
(princ "|")

(loop for x from x0 to x1 by dx do
(let* ((c 126)
(z (complex x y))
(a z))
(loop while (and (< (abs
(setq z (+ (* z z) a)))
2)
(> (decf c) 32)))
(princ (code-char c))))

(princ "|")
(princ #\Newline))
(princ "3")

(loop for x from x0 to x1 by dx do

(princ "-"))
(princ "4")
(princ #\Newline)))

(compile 'mandel)
(compile 'select-quadrant)
(compile 'draw-quadrant)
(mandel)

Frank Buss

unread,
Aug 1, 2004, 7:48:26 AM8/1/04
to
Jim Newton <ji...@rdrop.com> wrote:

> you can now call (mandel...) with the zoom
> quadrants
>
> e.g., (mandel 2 4 1 1)
>
> after the zooming, it goes into interactive mode.
> Yes there seems to be a limitation around dx=1e-8...
> curious.

you can replace all numbers with double-floats, then you can go until
dx= double-float-epsilon (something around 1d-16 on my computer) and then
this works:

(mandel 1 3 2 3 2 3 3 4 1 1 4 4 1 3 3 1 3 3 3 1 4 4 4 1 3 4 4 4 3 3 3 4 3
4 2 3 4 4 1 1 2 4 3)

Zooming 1:8796093022208. That's like zooming from the earth fullscreen to
a flake of one hair of a person fullscreen :-)

(defun mandel ( &rest quadrants)

(let (( x0 -2d0)
( y0 -2d0)
( x1 2d0)
( y1 2d0))

(dolist ( q quadrants)
(multiple-value-setq ( x0 y0 x1 y1 )
(select-quadrant q x0 y0 x1 y1 )))

(loop while t do
(draw-quadrant x0 y0 x1 y1)
(format t "quadrant: ")
(multiple-value-setq ( x0 y0 x1 y1 )
(select-quadrant (read) x0 y0 x1 y1)))))

(defun select-quadrant ( q x0 y0 x1 y1)

(let (( midx (/ (+ x1 x0) 2d0))
( midy (/ (+ y1 y0) 2d0)))


(case q
( 1 (values midx midy x1 y1))
( 2 (values x0 midy midx y1))
( 3 (values x0 y0 midx midy))
( 4 (values midx y0 x1 midy))
( t (values x0 y0 x1 y1)))))

(defun draw-quadrant ( x0 y0 x1 y1)

(let (( dx (/ (- x1 x0) 30d0))
( dy (/ (- y1 y0) 30d0)))

--

Jim Newton

unread,
Aug 1, 2004, 6:54:44 AM8/1/04
to
does complex do something different if you
give it two double floats? i.e., is there a
difference between (complex 1.0 1.0) and (complex 1d0 1d0)
-jim

neo88

unread,
Aug 1, 2004, 9:15:10 AM8/1/04
to
Frank Buss <f...@frank-buss.de> wrote in message news:<cefmln$5dq$1...@newsreader2.netcologne.de>...

> Edi Weitz <spam...@agharta.de> wrote:
>
> > (loop for c in '#.(loop for c downfrom 126 above 32
> > collect (code-char c))
>
> this is really nice, I didn't know the reader macros before.
>
> Ok, and now with TGA output, in the size of my screen for a new wallpaper
> (see http://www.frank-buss.de/tmp/mandelbrot.jpg for a quality reduced
> version). The program finished in 346 seconds run time, but that's mainly
> because the algorithm is not optimized.

346 seconds = 5 min 46 seconds. I ran this program for about seven
minutes I'd say, on CMUCL 18e, and all it did was call the GC
endlessly until I intruppted it from Emacs. I also tried it in Allegro
CL Trial, and it instantly overflowed the allowed GC allocation.

So does this write a tga file to the directory that the program is in,
so that you can open it later, or does it write the file directly to
the screen?

Frank Buss

unread,
Aug 1, 2004, 9:19:48 AM8/1/04
to
sol...@truevine.net (neo88) wrote:

> 346 seconds = 5 min 46 seconds. I ran this program for about seven
> minutes I'd say, on CMUCL 18e, and all it did was call the GC
> endlessly until I intruppted it from Emacs. I also tried it in Allegro
> CL Trial, and it instantly overflowed the allowed GC allocation.

in CMUCL you should try (compile 'mandelbrot) and (compile 'square) first,
it greatly enhances the speed. And you can reduce the output size by
reducing the 1600x1024 size.

> So does this write a tga file to the directory that the program is in,
> so that you can open it later, or does it write the file directly to
> the screen?

it writes it in the current working directory. Perhaps you should write
somthing like "/tmp/mandelbrot.tga" to write it to a place you know where
it is.

Edi Weitz

unread,
Aug 1, 2004, 9:52:42 AM8/1/04
to
On 1 Aug 2004 06:15:10 -0700, sol...@truevine.net (neo88) wrote:

> 346 seconds = 5 min 46 seconds. I ran this program for about seven
> minutes I'd say, on CMUCL 18e, and all it did was call the GC
> endlessly until I intruppted it from Emacs.

Did you compile it?

Frank Buss

unread,
Aug 1, 2004, 1:35:16 PM8/1/04
to
Frank Buss <f...@frank-buss.de> wrote:

> Ok, and now with TGA output

and finally: ASCII output in an MPEG animation :-)

http://www.mynetcologne.de/~nc-buszfr/mandelbrot.mpg (4.9 MB)

This was easy: first I screen captured the output of char 33 to 126,
saved it to TGA and created a Lisp vector with this Lisp program from it:

(defun charset ()
(with-open-file (chars "/tmp/chars.tga" :element-type 'unsigned-byte)
(loop for c from 0 to 93 do
(princ "(")
(princ #\Newline)
(loop for y from 11 downto 0 do
(file-position chars (+ 18 (* y 752) (* c 8)))
(princ "\"")
(loop for x from 0 to 7 do
(if (= 255 (read-byte chars))
(princ "*")
(princ " ")))


(princ "\" ")
(princ #\Newline))

(princ ")")
(princ #\Newline))))

Which looked like this:

(defconstant *charset*
(make-array
'(94 12)
:initial-contents
'((
" "
" ** "
" **** "
" **** "
" **** "
" ** "
" ** "
" "
" ** "
" ** "
" "
" "
)
(
" "
" ** ** "
" ** ** "
" ** ** "
" * * "
" "
" "
" "
" "
" "
" "
" "
)
...

Then I wrote a litte class, which simulates a terminal output with TGA
and interpolates between a zooming step (see below, full program:
http://www.frank-buss.de/tmp/mandelbrot.lisp.txt ). The 344 TGAs were
converted with http://dmr.ath.cx/gfx/tga2avi/ to a 107 MB AVI file and
then with http://www.winload.de/download.php?programm_id=23273 to MPG.

(defun mandel (&rest quadrants)
(let ((x0 -2d0)
(y0 -2d0)
(x1 2d0)
(y1 2d0)
(i 0)
(steps 8))
(dolist (q quadrants)
(let ((x0-old x0)
(y0-old y0)
(x1-old x1)
(y1-old y1))
(multiple-value-setq (x0 y0 x1 y1)


(select-quadrant q x0 y0 x1 y1))

(loop for j from 0 to (1- steps) do
(format t "calculating image ~S of ~S~%" (1+ i) (* steps
(length quadrants)))
(draw-quadrant i
(+ (* (/ (- x0 x0-old) steps) j) x0-old)
(+ (* (/ (- y0 y0-old) steps) j) y0-old)
(+ (* (/ (- x1 x1-old) steps) j) x1-old)
(+ (* (/ (- y1 y1-old) steps) j) y1-old))
(incf i))))))

(defun select-quadrant (q x0 y0 x1 y1)
(let ((midx (/ (+ x1 x0) 2d0))
(midy (/ (+ y1 y0) 2d0)))
(case q
(1 (values midx midy x1 y1))
(2 (values x0 midy midx y1))
(3 (values x0 y0 midx midy))
(4 (values midx y0 x1 midy))
(t (values x0 y0 x1 y1)))))

(defun draw-quadrant (i x0 y0 x1 y1)
(let ((steps 30)
(ti (make-instance 'terminal-image)))
(char-out ti #\2)
(loop for x from 0 to steps do
(char-out ti #\-))
(char-out ti #\1)
(newline ti)
(loop for y from 0 to steps do
(char-out ti #\|)
(loop for x from 0 to steps do


(let* ((c 126)
(z (complex

(+ (* (/ (- x1 x0) steps) x) x0)
(+ (* (/ (- y1 y0) steps) y) y0)))
(a z))
(loop while (and (< (abs


(setq z (+ (* z z) a)))

2)
(> (decf c) 32)))

(char-out ti (code-char c))
))
(char-out ti #\|)
(newline ti))
(char-out ti #\3)
(loop for x from 0 to steps do
(char-out ti #\-))
(char-out ti #\4)
(newline ti)
(write-tga ti (format nil "/tmp/t~S.tga" i))))

(defclass terminal-image ()
((cols :initarg cols :initform 33)
(rows :initarg rows :initform 33)
(cursor-x :initform 0)
(cursor-y :initform 0)
width height image))

(defmethod char-out ((ti terminal-image) c)
(let ((index (- (char-code c) 33))
(x0 (* 8 (slot-value ti 'cursor-x)))
(y0 (* 12 (slot-value ti 'cursor-y))))
(if (and (<= 0 index) (< index 94))
(loop for y from 0 to 11 do
(let ((char-line (aref *charset* index y)))
(loop for x from 0 to 7 do
(if (not (eq #\Space (elt char-line x)))
(setf
(aref (slot-value ti 'image)
(+ y y0) (+ x x0))
1)))))))
(incf (slot-value ti 'cursor-x)))

(defmethod newline ((ti terminal-image))
(setf (slot-value ti 'cursor-x) 0)
(incf (slot-value ti 'cursor-y)))

(defmethod initialize-instance :after ((ti terminal-image) &key)
(let ((width (* (slot-value ti 'cols) 8))
(height (* (slot-value ti 'rows) 12)))
(setf (slot-value ti 'width) width)
(setf (slot-value ti 'height) height)
(setf (slot-value ti 'image) (make-array
(list height width)
:element-type '(unsigned-byte 1)
:initial-element 0))))

(defmethod write-tga ((ti terminal-image) filename)
(let ((width (slot-value ti 'width))
(height (slot-value ti 'height)))


(with-open-file
(tga filename
:direction :output
:if-exists :supersede
:element-type 'unsigned-byte)
(dolist (byte (list 0 0 2 0 0 0 0 0 0 0 0 0
(mod width 256) (floor width 256)
(mod height 256) (floor height 256) 24 0))
(write-byte byte tga))

(loop for y from (1- height) downto 0 do


(loop for x from 0 to (1- width) do

(let ((color
(if (= (aref (slot-value ti 'image) y x) 1)
255
0)))
(write-byte color tga)
(write-byte color tga)
(write-byte color tga)))))))

Message has been deleted

Eivind L. Rygge

unread,
Aug 4, 2004, 1:14:55 PM8/4/04
to
Frank Buss <f...@frank-buss.de> writes:

> Based on a Java program ( http://tinyurl.com/57smz ) I've implemented a
> Lisp program for showing the Mandelbrot set:
>
> (loop for y from -1 to 1.1 by 0.1 do
> (loop for x from -2 to 1 by 0.04 do
> (let* ((c 126)
> (z (complex x y))
> (a z))
> (loop while (< (abs
> (setq z (+ (* z z) a)))
> 2)
> while (> (decf c) 32))
> (princ (code-char c))))
> (format t "~%"))

(...)

... and an Emacs Lisp version:

(require 'cl)

(defun comp-mult (a b) ; a and b are lists (x y) corresponding to x + yi
(list (- (* (car a) (car b))
(* (cadr a) (cadr b)))
(+ (* (car a) (cadr b))
(* (cadr a) (car b)))))

(defun comp-add (z1 z2)
(list (+ (car z1) (car z2))
(+ (cadr z1) (cadr z2))))

(defun comp-abs (z)
(sqrt (+ (* (car z) (car z))
(* (cadr z) (cadr z)))))

(let* ((buffer-name (get-buffer-create "*Mandelbrot*")))
(switch-to-buffer buffer-name)
(erase-buffer)


(loop for y from -1 to 1.1 by 0.1 do
(loop for x from -2 to 1 by 0.04 do
(let* ((c 126)

(z (list x y))
(a z))
(loop while (< (comp-abs
(setq z (comp-add (comp-mult z z) a)))


2)
while (> (decf c) 32))

(insert (princ (make-char 'ascii c)))))
(newline)))


I had to create some helper functions for handling the complex
values. I guess I could've used the calc package's functions
concerning complex numbers, but they looked awkward to use without
running the calculator as far as I could see. Besides, not all use
the calc package anyway.

Eivind L. Rygge
Oslo, Norway

PS! First post in comp.lang.lisp :-)
Hello to all who attended the Lisp & Scheme Workshop here in Oslo this summer.

Pascal Bourguignon

unread,
Aug 4, 2004, 1:46:33 PM8/4/04
to
eiv...@hotmail.com (Eivind L. Rygge) writes:
> ... and an Emacs Lisp version:
>
> (require 'cl)
>
> ; a and b are lists (x y) corresponding to x + yi

Why not mere cons cells?

(defun realpart (c) (car c))
(defun imagpart (c) (cdr c))
(defun complex (r &optional (i 0)) (cons r i))

Or, slightly less COMMON-LISP, but slightly more efficient:

(defmacro realpart (c) `(car ,c))
(defmacro imagpart (c) `(cdr ,c))
(defmacro complex (r &optional (i 0)) `(cons ,r ,i))


> (defun comp-mult (a b)


> (list (- (* (car a) (car b))
> (* (cadr a) (cadr b)))
> (+ (* (car a) (cadr b))
> (* (cadr a) (car b)))))


(defun c* (&rest args)
(reduce (lambda (a b)
(complex (- (* (realpart a)(realpart b))
(* (imagpart a)(imagpart b)))
(+ (* (realpart a)(imagpart b))
(* (imagpart a)(realpart b)))))
args
:inital-value (complex 1 0)))


> (defun comp-add (z1 z2)
> (list (+ (car z1) (car z2))
> (+ (cadr z1) (cadr z2))))

(defun c+ (&rest args)
(reduce (lambda (a b)
(complex (+ (realpart a)(realpart b))
(+ (imagpart a)(imagpart b))))
args
:inital-value (complex 0 0)))



> (defun comp-abs (z)
> (sqrt (+ (* (car z) (car z))
> (* (cadr z) (cadr z)))))

(defun cabs (c)
(sqrt (+ (* (realpart c)(realpart c))
(* (imagpart c)(imagpart c)))))




> (let* ((buffer-name (get-buffer-create "*Mandelbrot*")))
> (switch-to-buffer buffer-name)
> (erase-buffer)
> (loop for y from -1 to 1.1 by 0.1 do
> (loop for x from -2 to 1 by 0.04 do
> (let* ((c 126)
> (z (list x y))
> (a z))
> (loop while (< (comp-abs
> (setq z (comp-add (comp-mult z z) a)))
> 2)
> while (> (decf c) 32))
> (insert (princ (make-char 'ascii c)))))
> (newline)))
>
>
> I had to create some helper functions for handling the complex
> values. I guess I could've used the calc package's functions
> concerning complex numbers, but they looked awkward to use without
> running the calculator as far as I could see. Besides, not all use
> the calc package anyway.
>
> Eivind L. Rygge
> Oslo, Norway
>
> PS! First post in comp.lang.lisp :-)
> Hello to all who attended the Lisp & Scheme Workshop here in Oslo this summer.

--
__Pascal Bourguignon__ http://www.informatimago.com/

Nobody can fix the economy. Nobody can be trusted with their finger
on the button. Nobody's perfect. VOTE FOR NOBODY.

Dave Pearson

unread,
Aug 5, 2004, 4:32:55 AM8/5/04
to
* Eivind L. Rygge <eiv...@hotmail.com>:

> ... and an Emacs Lisp version:

<URL:http://www.gnusoftware.com/Emacs/Lisp/mandel.el> might be of interest.

--
Dave Pearson
http://www.davep.org/lisp/

Pascal Costanza

unread,
Aug 5, 2004, 5:35:10 AM8/5/04
to

Eivind L. Rygge wrote:

> PS! First post in comp.lang.lisp :-)
> Hello to all who attended the Lisp & Scheme Workshop here in Oslo this summer.

Hi. ;)

--
Tyler: "How's that working out for you?"
Jack: "Great."
Tyler: "Keep it up, then."

Frank Buss

unread,
Aug 26, 2004, 12:13:52 PM8/26/04
to
Jim Newton <ji...@rdrop.com> wrote:

> does complex do something different if you
> give it two double floats? i.e., is there a
> difference between (complex 1.0 1.0) and (complex 1d0 1d0)

yes. If I'm doing this in CLISP:

(expt (/ (complex 1 1) 1e30) 2)

I got a SIMPLE-FLOATING-POINT-UNDERFLOW error, but if I do this:

(expt (/ (complex 1d0 1d0) 1e30) 2)

I got #C(0.0d0 1.9999999398101364d-60) (and a warning, which is not
generated if I use 1d30 instead of 1e30).

Reply all
Reply to author
Forward
0 new messages