Mitch Richling: Julia Sets
Author: | Mitch Richling |
Updated: | 2024-10-31 |
Table of Contents
1. Introduction
Julia sets are generated in much the same way as the Mandelbrot Set. In both cases sample a rectangular part of the complex plane at a discreet grid of points, iterate a function at each point, and the color the point according to some aspect of the sequence behavior. For the Mandelbrot Set we set \(c\) in the following formula to the grid point, set \(z=0\), and iterate the function.
\[f(z)=z^2+c\]
For Julia sets we fix the value of \(c\) in the same formula to a constant. Then for each grid point, we set \(z\) equal to the grid point, and iterate the function. Note that we have a Julia set associated with each point in the complex plane!
2. Drawing A Picture
Like Mandelbrot Set, the most common choice for how to assign colors is to use the \(L\) function. Here is a C++ program that draws a Julia set for the point \(\frac{2}{5}+\frac{3}{5}i\):
#include "ramCanvas.hpp" typedef mjr::ramCanvas3c8b::colorType ct; int main(void) { std::chrono::time_point<std::chrono::system_clock> startTime = std::chrono::system_clock::now(); const int IMGSIZ = 7680; const int NUMITR = 1024; const double MAXZ = 4.0; mjr::ramCanvas3c8b theRamCanvas(IMGSIZ, IMGSIZ, -2.2, 2.2, -2.2, 2.2); std::complex<double> c(-0.4, 0.6); for(int y=0;y<theRamCanvas.getNumPixY();y++) { for(int x=0;x<theRamCanvas.getNumPixX();x++) { std::complex<double> z = theRamCanvas.int2real(x, y); int count = 0; while((std::norm(z)<MAXZ) && (count<=NUMITR)) { z=std::pow(z, 2) + c; count++; } if(count < NUMITR) theRamCanvas.drawPoint(x, y, ct::csCColdeFireRamp::c(static_cast<ct::csIntType>(count*20))); } } theRamCanvas.writeTIFFfile("julia.tiff"); 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 this picture:
3. Making A Movie
That's pretty nice, but every point in the complex plane has one of these sets. So let's move a point along one of the Mandelbrot curves around the Mandelbrot Set, and make a movie of the corresponding Julia sets. Here's some code:
#include "ramCanvas.hpp" typedef mjr::ramCanvas3c8b::colorType ct; int main(void) { std::chrono::time_point<std::chrono::system_clock> startTime = std::chrono::system_clock::now(); const int JULIA_MAXITR = 1024; const double JULIA_MAXZSQ = 4.0; const int IMGSIZ = 7680/8; const int MANDL_MAXITR = 1024; const double MANDL_MAXZSQ = 4.0; mjr::ramCanvas3c8b manRamCanvas(IMGSIZ, IMGSIZ, -2.2, 0.8, -1.5, 1.5); // Read in our curve -- not much error checking here... std::vector<double> mcX; std::vector<double> mcY; char strBuf[256]; std::ifstream iStream("../data/mandelbrot_curve_10.txt", std::ios_base::binary); if (!(iStream.good())) return 1; while(iStream.good()) { iStream.getline(strBuf, 100, '\n'); std::string strBufS(strBuf); if (strBufS.length() > 0) { std::string::size_type pos; std::string::size_type tagIdx = strBufS.find_first_of("\t ,"); mcX.push_back(std::stod(strBufS.substr(0, tagIdx), &pos)); mcY.push_back(std::stod(strBufS.substr(tagIdx+1), &pos)); } } int numPts = static_cast<int>(mcX.size()); // Draw a refrence mandelbrot set for(int y=0;y<manRamCanvas.getNumPixY();y++) { for(int x=0;x<manRamCanvas.getNumPixX();x++) { std::complex<double> c = manRamCanvas.int2real(x, y); std::complex<double> z(0.0, 0.0); int count = 0; while((std::norm(z)<MANDL_MAXZSQ) && (count<=MANDL_MAXITR)) { z=std::pow(z, 2) + c; count++; } if(count < MANDL_MAXITR) manRamCanvas.drawPoint(x, y, ct::csCColdeFireRamp::c(static_cast<ct::csIntType>(count*20))); } } // Draw our curve on the refrence mandelbrot set image for(int frame=0; frame<numPts; frame++) manRamCanvas.drawLine(mcX[frame], mcY[frame], mcX[(frame+1)%numPts], mcY[(frame+1)%numPts], "white"); // Draw frames for(int frame=0; frame<numPts; frame++) { // Draw julia curve frames mjr::ramCanvas3c8b julRamCanvas(IMGSIZ, IMGSIZ, -2.2, 2.2, -2.2, 2.2); std::cout << "Frame: " << frame << " of " << numPts << std::endl; std::complex<double> c = std::complex<double>(mcX[frame], mcY[frame]); for(int y=0;y<julRamCanvas.getNumPixY();y++) { for(int x=0;x<julRamCanvas.getNumPixX();x++) { std::complex<double> z(julRamCanvas.int2realX(x), julRamCanvas.int2realY(y)); int count = 0; while((std::norm(z)<JULIA_MAXZSQ) && (count<=JULIA_MAXITR)) { z=std::pow(z, 2) + c; count++; } if(count < JULIA_MAXITR) julRamCanvas.drawPoint(x, y, ct::csCColdeFireRamp::c(static_cast<ct::csIntType>(count*20))); } } // Draw inset image mjr::ramCanvas3c8b setRamCanvas(manRamCanvas); setRamCanvas.drawFillCircle(c, 0.04, "white"); setRamCanvas.scaleDownMean(4); julRamCanvas.insertCanvas(setRamCanvas, 0, 0); julRamCanvas.drawRectangle(0, 0, IMGSIZ/4, IMGSIZ/4, "white"); // Label the frame julRamCanvas.drawString("MJR 2022", mjr::hershey::font::ROMAN_SL_SANSERIF, IMGSIZ-IMGSIZ/6, 20, "white", 1, 20); // Dump out frame julRamCanvas.writeTIFFfile("juliaM2_" + mjr::math::str::fmt_int(frame, 3, '0') + ".tiff"); } std::chrono::duration<double> runTime = std::chrono::system_clock::now() - startTime; std::cout << "Total Runtime " << runTime.count() << " sec" << std::endl; }
And our movie: