Mitch Richling: Multibrot Fractal
Author: | Mitch Richling |
Updated: | 2024-10-31 |
Table of Contents
1. Introduction
The Multibrot Fractal is a family of Mandelbrot'alikes in that the generating functions are a minor modification of the one used for the Mandelbrot Set:
\[\begin{eqnarray} f(z) & = & z^2+c & \,\,\,\,\,\,\mathrm{Mandelbrot} \\ f(z) & = & z^k+c & \,\,\,\,\,\,\mathrm{Multibrot} \\ \end{eqnarray}\]
Notice the only difference is the power, \(k\). Also note that \(k\) need not be an integer.
We render Multibrot Fractals using the \(L\) function just like we did with the Mandelbrot Set. The general idea is to map an array of pixels making up the raster image to a rectangular region of the complex plane, and color each pixel of the image based upon a color scale for the computed value of the \(L\) function.
2. When \(k\) is not an integer
When \(k\) not an integer, we interesting results. I think the most instructive way to look at them is to make a movie for values of \(k\) from about \(0.9\) up to \(4\). About 200 frames captures the evolution quite nicely. The following bit of C++ will draw the frames, which may then be rendered into a movie.
#include "ramCanvas.hpp" int main(void) { std::chrono::time_point<std::chrono::system_clock> startTime = std::chrono::system_clock::now(); const double MINPOW = 0.9; const double MAXPOW = 4.0; const int NUMFRM = 192; const int NUMITR = 255; const int IMXSIZ = 7680/8; const int IMYSIZ = 4320/8; mjr::ramCanvas3c8b theRamCanvas(IMXSIZ, IMYSIZ, -2.75, 2.75, -1.55, 1.55); for(int f=0;f<NUMFRM;f++) { double p = MAXPOW*static_cast<double>(f)/static_cast<double>(NUMFRM) + MINPOW; std::chrono::time_point<std::chrono::system_clock> frameStartTime = std::chrono::system_clock::now(); theRamCanvas.clrCanvasToBlack(); for(int y=0;y<theRamCanvas.getNumPixY();y++) { for(int x=0;x<theRamCanvas.getNumPixX();x++) { std::complex<double> c(theRamCanvas.int2realX(x), theRamCanvas.int2realY(y)); std::complex<double> z(0.0, 0.0); int count = 0; while((std::norm(z)<4) && (count<=NUMITR)) { z=std::pow(z, p) + c; count++; } if(count < NUMITR) theRamCanvas.drawPoint(x, y, mjr::ramCanvas3c8b::colorType::csCColdeFireRamp::c(static_cast<mjr::ramCanvas3c8b::csIntType>(count*20))); } } theRamCanvas.writeTIFFfile("multibrotMovie_" + mjr::math::str::fmt_int(f, 3, '0') + ".tiff"); std::chrono::duration<double> frameRunTime = std::chrono::system_clock::now() - frameStartTime; std::cout << "Frame " << f << " Runtime " << frameRunTime.count() << " sec" << std::endl; } std::chrono::duration<double> runTime = std::chrono::system_clock::now() - startTime; std::cout << "Total Runtime " << runTime.count() << " sec" << std::endl; }
The above program will generate 192 frames of a movie which we can assemble into a GIF:
3. When \(k\) is an integer
When \(k\) is an integer, we see nice symmetric results. The following bit of C++ will draw the cases for \(k\) from \(1\) up to \(9\).
#include "ramCanvas.hpp" int main(void) { std::chrono::time_point<std::chrono::system_clock> startTime = std::chrono::system_clock::now(); const int MINPOW = 2; const int MAXPOW = 9; const int NUMITR = 1024; const int IMXSIZ = 7680/2; const int IMYSIZ = 4320/2; mjr::ramCanvas3c8b theRamCanvas(IMXSIZ, IMYSIZ, -2.75, 2.75, -1.55, 1.55); for(int p=MINPOW;p<=MAXPOW;p++) { std::chrono::time_point<std::chrono::system_clock> frameStartTime = std::chrono::system_clock::now(); theRamCanvas.clrCanvasToBlack(); for(int y=0;y<theRamCanvas.getNumPixY();y++) { for(int x=0;x<theRamCanvas.getNumPixX();x++) { std::complex<double> c(theRamCanvas.int2realX(x), theRamCanvas.int2realY(y)); std::complex<double> z(0.0, 0.0); int count = 0; while((std::norm(z)<4) && (count<=NUMITR)) { z=std::pow(z, p) + c; count++; } if(count < NUMITR) theRamCanvas.drawPoint(x, y, mjr::ramCanvas3c8b::colorType::csCColdeFireRamp::c(static_cast<mjr::ramCanvas3c8b::csIntType>(count*20*log(p)))); } } //theRamCanvas.autoHistStrech(); theRamCanvas.writeTIFFfile("multibrotSnaps_" + mjr::math::str::fmt_int(p, 3, '0') + ".tiff"); std::chrono::duration<double> frameRunTime = std::chrono::system_clock::now() - frameStartTime; std::cout << "Frame " << p << " Runtime " << frameRunTime.count() << " sec" << std::endl; } std::chrono::duration<double> runTime = std::chrono::system_clock::now() - startTime; std::cout << "Total Runtime " << runTime.count() << " sec" << std::endl; }