\[M = \{c \in \mathbb{C} | lim_{n \to \infty}z_{n+1} = z_n + c \ne \infty\}\]
with \(z_0=0\). Above definition is a sloppy way of saying that any complex number \(c\) for which \(z_n\) is bound is part of the Mandelbrot set. Moreover the absoulte value \(|z_n>|\) is bound, which will be neccessary for our computation.
For numerical calculation we need to think of a criterion for deciding whether or not a complex number is part of the Mandelbrot set. The following condition is to be fulfilled:
\[|z_{1000}| < 2\]
In general instead of looking at \(|z_{1000}|\) a parameter maxIter should be used: \(|z_{\textrm{maxIter}}|\).
The "score" a complex number gets is the number n of iterations, until \(|z_n| \ge 2\). If it never gets as big as 2, maxIter will be the score.
That is all we need to know before getting started with the code. We will need to make use of complex numbers. You can use the complex numers library included in c++, however, I decided to create my own class Complex, primarily to learn operator overload. The class is not finished yet, but it works well as far as the Mandelbrot set is concerned (a blog post will follow as soon as it's done).
The class Mandelbrot contains 8 private member variables:
class Mandelbrot { private: double xmin_; double xmax_; double ymin_; double ymax_; int stepsX_; int stepsY_; int maxIter_; std::vector<std::vector< double > > *grid_;
xmin_, xmax_, ymin_, ymax_ determine the rectangle in \(\mathbb{C}\) where the Mandelbrot set is to be calculated, stepsX_ and stepsY_ give the number of intercepts on the grid, maxIter_ gives the maximum \(n\) for \(z_n\). The scores are stored in grid_.
The member functions contain constructors, a destructor, various functions to set the parameters and a function to fill the grid as well as another one to write the grid to a file. I only will present the latter here as the others are not interesting and their implementation is obvious.
public: ... void fillGrid(); bool writeToFile(const char *filename) const; };
The basic concept of fillGrid() is to iterate over all points in the grid defined by the parameters and then get the score for each point. This is quite simple. I used \(|z_n|^2\) instead of \(|z_n|\) to save computation time. The score is divided by maxIter_ to obtain scores in the interval \([0,1]\).
void Mandelbrot::fillGrid() { if (!grid_) { delete grid_; grid_ = 0; } grid_ = new std::vector<std::vector<double > >(stepsY_, std::vector<double>(stepsX_)); double stepSizeX = (xmax_ - xmin_)/stepsX_; double stepSizeY = (ymax_ - ymin_)/stepsY_; for (int y = 0; y < stepsY_; y++) { double im = ymin_ + y*stepSizeY; for (int x = 0; x < stepsX_; x++) { double re = xmin_ + x*stepSizeX; Complex c(re, im); Complex z(0, 0); int iterations = maxIter_; while (iterations > 0) { if (z.sqAbs() >= 4) { break; } z = z*z; z = z + c; iterations--; } double gridValue = 1.0*iterations/maxIter_; grid_->at(y).at(x) = gridValue; } } }
Instead of writing the score grid to an image file, I have only implemented a function to write it to a text file (CSV Style with spaces as delimiters). This file can then be read using matlab, Python, ... to easily show the image. An implementation of an export function for images will follow. The function writeToFile(const char*) simply uses std::ofstream to write to the file.
bool Mandelbrot::writeToFile(const char *filename) const { if (!grid_) { return 0; } std::ofstream f(filename, std::ios::trunc); if (f.is_open()) { for (int y = 0; y < stepsY_; y++) { for (int x = 0; x < stepsX_; x++) { f << grid_->at(y).at(x) << " "; } f << "\n"; } f.close(); } else { return 0; } return 1; }
Overall the implementation is easy and one can get cool images when looking at the right regions of \(\mathbb{C}\). In order to enhance results raising the scores \(\in [0,1]\) to a higher power is a good idea.
The source code can be found on github.
The two images on the left show the same region (\(-1.26 \le \Re \le-1.245\), \(0 \le \Im \le 0.03\)) using just the scores (grayscale) or mapping them to a colormap. In both images the scores are raised to the power of 4. The github repository contains a Makefile. You can find interesting regions on your own with that.
Future work on the Mandelbrot Class will include the option to write an image as ouput in addition to the CSV file, the file check mentioned above as well as an argument parser so the parameters can be set without compiling each time.
I found another interesting region: \(-0.72 \le \Re \le -0.7\), \(0.24 \le \Im \le 0.26 \)
No comments:
Post a Comment