%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% This is for when it is used in the report %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% When used as a standalone document %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\begin{onlystandalone}
\begin{code}
module Mandel where
import Complex -- 1.3
import PortablePixmap
default ()
\end{code}
\end{onlystandalone}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
The Mandelbrot set is the set of complex numbers for which the values
of the polynomial
\begin{equation}
\label{poly}
f_c(z) = z^2 + c
\end{equation}
are connected\cite{falconer}. A graphical representation of this set
can be rendered by plotting for different points in the complex plane
an intensity that is proportional to either the rate of divergence of
the above polynomial, or a constant if the polynomial is found to converge.
We present a \DPHaskell{} implementation of the mandelbrot set in a bottom up
manner that can be decomposed into the following stages :
\begin{enumerate}
\item First we generate an infinite list of complex numbers caused by
applying the mandelbrot polynomial to a single complex number
in the complex plane.
\item Next we walk over the list, determining the rate of divergence of
the list of complex numbers.
\item We parallelize the above stages, by mapping the composition
of the preceeding functions over the complex plain.
\item Lastly we describe how the complex plain is initialised, and the
results are graphically rendered.
\end{enumerate}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{Generating the list}
\begin{code}
mandel::(Num a) => a -> [a]
mandel c = infiniteMandel
where
infiniteMandel = c : (map (\z -> z*z +c) infiniteMandel)
\end{code}
The definition of @mandel@ uses the numerically overloaded functions
@*@ and @+@; operationally it generates an infinite list of numbers caused by
itearting a number (i.e an object that has a type that belongs to class @Num@),
with the function @\z -> z*z +c@. For example the evaluation of ``@mandel 2@''
would result in the list of integers :
\begin{pseudocode}
[2, 6, 38, 1446, 2090918, 4371938082726, 19113842599189892819591078,
365338978906606237729724396156395693696687137202086, ^C{Interrupted!}
\end{pseudocode}
, whereas if the function were applied to the complex number\footnote{complex
numbers are defined in Haskells prelude as the algebraic data type
@data (RealFloat a) => Complex a = a :+ a@} (1.0 :+ 2.0), then the functions
behaviour would be equivalent of the mandelbrot polynomial(\ref{poly}) in
which \hbox{$c = 1.0 + i2.0$} :
\begin{pseudocode}
[(1.0 :+ 2.0), (-2.0 :+ 6.0), (-31.0 :+ -22.0), (478.0 :+ 1366.0),
(-1637471.0 :+ 1305898.0), ^C{Interrupted!}
\end{pseudocode}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{Walking the list}
``@whenDiverge@'' determines the rate of divergence of a list of complex
numbers. If diveregence cannot be proved within a fixed number of
iteration of the mandelbrot polynomial, convergence is assumed.
This process is encoded in \DPHaskell{} by @take@ing a @limit@ of
complex numbers off the infinite stream generated by the mandel function.
This {\em finite} list is then traversed, each element of which is checked
for divergence.
\begin{code}
whenDiverge:: Int -> Double -> Complex Double -> Int
whenDiverge limit radius c
= walkIt (take limit (mandel c))
where
walkIt [] = 0 -- Converged
walkIt (x:xs) | diverge x radius = 0 -- Diverged
| otherwise = 1 + walkIt xs -- Keep walking
\end{code}
\begin{equation}
\|x + iy\|_{2} = \sqrt{x^2 + y^2}
\end{equation}
We assume that divergence occurs if the norm ($\|x\|_{2}$) of a
complex number exceeds the radius of the region to be rendered.
The predicate @diverge@ encodes the divergence check, where @magnitude@
is the prelude function that calculates the euclidean norm
($\|x\|_{2}$) of a complex number.
\begin{code}
-- VERY IMPORTANT FUNCTION: sits in inner loop
diverge::Complex Double -> Double -> Bool
diverge cmplx radius = magnitude cmplx > radius
\end{code}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{Parallelising the mandelbrot set}
The mandelbrot set can be trivially parallelised by ``mapping''
the @whenDiverge@ function over a complex plain of values.
\begin{code}
parallelMandel:: [Complex Double] -> Int -> Double -> [Int]
parallelMandel mat limit radius
= map (whenDiverge limit radius) mat
\end{code}
\section{Initialisation of data and graphical rendering.}
We render the mandelbrot set by producing a PixMap file that
provides a portable representation of a 32bit RGB picture. The format of
this file is outside the scope of this paper, but we provide an interface
to this representation by defining an abstract data type @Pixmap@ that
has the functions @createPixmap@ (signature shown below), and @show@
defined (overloaded function from class @Text@).
\begin{pseudocode}
createPixmap :: Integer -> -- The width of the Pixmap
Integer -> -- The height of the Pixmap
Int -> -- The depth of of the RGB colour
[(Int,Int,Int)] -> -- A matrix of the RGB colours
PixMap
\end{pseudocode}
@mandelset@ controls the evaluation and rendering of the mandelbrot set. The
function requires that a ``viewport'' on the complex plane is defined,
such that the region bounded by the viewport will be rendered in a ``Window''
represented by the pixmap.
\begin{code}
mandelset::Double -> -- Minimum X viewport
Double -> -- Minimum Y viewport
Double -> -- Maximum X viewport
Double -> -- maximum Y viewport
Integer -> -- Window width
Integer -> -- Window height
Int -> -- Window depth
PixMap -- result pixmap
mandelset x y x' y' screenX screenY lIMIT
= createPixmap screenX screenY lIMIT (map prettyRGB result)
where
\end{code}
@windowToViewport@ is a function that is defined within the scope of the
mandelset function (therefore arguments to mandelset will be in scope). The
function represents the relationship between the pixels in a window,
and points on a complex plain.
\begin{code}
windowToViewport s t
= ((x + (((coerce s) * (x' - x)) / (fromInteger screenX))) :+
(y + (((coerce t) * (y' - y)) / (fromInteger screenY))))
coerce::Integer -> Double
coerce s = encodeFloat (toInteger s) 0
\end{code}
The complex plain is initialised, and the mandelbrot set is calculated.
\begin{code}
result = parallelMandel
[windowToViewport s t | t <- [1..screenY] , s<-[1..screenX]]
lIMIT
((max (x'-x) (y'-y)) / 2.0)
prettyRGB::Int -> (Int,Int,Int)
prettyRGB s = let t = (lIMIT - s) in (s,t,t)
\end{code}
|