Mitch Richling: Attracting Torus
Author: | Mitch Richling |
Updated: | 2023-11-18 |
Table of Contents
1. Introduction
I first came across this strange attractor in Sprott's book "Elegant Automation" where it is the topic of chapter 43 starting on page 311. The simplified equations are:
Traditional paramater values are:
Traditional initial conditions are:
This attractor is an attractive one (pun intended):
What inspired me to code up this thing was an illustration on page 315 of Sprott's book showing the intersection of the attractor and the
2. Algorithms
We can solve this system numerically in much the same way we solved the lorenz system; however, in order to render the intersection
of the attractor with the coordinate axis plains we need to tightly control the step size. I have taken a super simple, and slow approach: At each step
I expand
#include "ramCanvas.hpp" int main(void) { std::chrono::time_point<std::chrono::system_clock> startTime = std::chrono::system_clock::now(); const int XSIZ = 7680/1; const int YSIZ = 4320/1; mjr::ramCanvas3c8b theRamCanvasX0(XSIZ, YSIZ, -10, 10, -10, 10); mjr::ramCanvas3c8b theRamCanvasY0(XSIZ, YSIZ, -10, 10, -10, 10); mjr::ramCanvas3c8b theRamCanvasZ0(XSIZ, YSIZ, -10, 10, -10, 10); mjr::ramCanvas3c8b theRamCanvasXY(XSIZ, YSIZ, -10, 10, -10, 10); mjr::ramCanvas3c8b theRamCanvasXZ(XSIZ, YSIZ, -10, 10, -10, 10); mjr::ramCanvas3c8b theRamCanvasYZ(XSIZ, YSIZ, -10, 10, -10, 10); double isectDistToGo = 1e8; /* How long should the curve be for Z0 ? */ double curveDistToGo = 1e4; /* How long should the curve be for XY, XZ, & YZ? */ double a = 4.0; /* Equation paramaters */ double b = 0.1; /* Equation paramaters */ double x = 0.0; /* Initial conditions */ double y = 2.0; /* Initial conditions */ double z = 1.0; /* Initial conditions */ double targetDist = 0.025; /* Size of each step on curve */ int maxNumUpBisect = 5; /* Max times we double tDelta to get > targetDist. */ int maxNumDownBisect = 10; /* Max times we half tDelta to get < targetDist. */ /* Solve the equations..... */ double tDelta = 1.0; double dist = 0.0; double xOld = x; double yOld = y; double zOld = z; double Xdelta, Ydelta, Zdelta, movDist; while (dist < isectDistToGo) { /* Find tDelta that gets us bigger than targetDist */ int numUpBisect = 0; do { tDelta += (2 * tDelta); Xdelta = (y) * tDelta; Ydelta = (-x-z*y) * tDelta; Zdelta = (y*y-a+b*z) * tDelta; movDist = sqrt (fabs (Xdelta * Xdelta + Ydelta * Ydelta + Zdelta * Zdelta)); numUpBisect++; } while ((numUpBisect < maxNumUpBisect) && (movDist < targetDist)); /* Find tDelta that gets us a jump of <targetDist. */ int numDownBisect = 0; do { tDelta = tDelta / 2; Xdelta = (y) * tDelta; Ydelta = (-x-z*y) * tDelta; Zdelta = (y*y-a+b*z) * tDelta; movDist = sqrt (fabs (Xdelta * Xdelta + Ydelta * Ydelta + Zdelta * Zdelta)); numDownBisect++; } while ((numDownBisect < maxNumDownBisect) && (movDist > targetDist)); /* Update and draw */ dist += movDist; x = x + Xdelta; y = y + Ydelta; z = z + Zdelta; if (dist < curveDistToGo) { theRamCanvasXY.drawPoint(x, y, mjr::ramCanvas3c8b::colorType::cornerColorEnum::WHITE); theRamCanvasXZ.drawPoint(x, z, mjr::ramCanvas3c8b::colorType::cornerColorEnum::WHITE); theRamCanvasYZ.drawPoint(y, z, mjr::ramCanvas3c8b::colorType::cornerColorEnum::WHITE); } if(z*zOld<=0.0) { if (z < 0) { theRamCanvasZ0.drawPoint((x+xOld)/2.0, (y+yOld)/2.0, theRamCanvasZ0.getPxColor((x+xOld)/2.0, (y+yOld)/2.0).setC0(255)); } else { theRamCanvasZ0.drawPoint((x+xOld)/2.0, (y+yOld)/2.0, theRamCanvasZ0.getPxColor((x+xOld)/2.0, (y+yOld)/2.0).setC2(255)); } theRamCanvasZ0.drawPoint((x+xOld)/2.0, (y+yOld)/2.0, theRamCanvasZ0.getPxColor((x+xOld)/2.0, (y+yOld)/2.0).setC1(255)); } if(x*xOld<=0.0) { if (x < 0) { theRamCanvasX0.drawPoint((y+yOld)/2.0, (z+zOld)/2.0, theRamCanvasX0.getPxColor((y+yOld)/2.0, (z+zOld)/2.0).setC0(255)); } else { theRamCanvasX0.drawPoint((x+xOld)/2.0, (y+yOld)/2.0, theRamCanvasX0.getPxColor((y+yOld)/2.0, (z+zOld)/2.0).setC2(255)); } theRamCanvasX0.drawPoint((y+yOld)/2.0, (z+zOld)/2.0, theRamCanvasX0.getPxColor((y+yOld)/2.0, (z+zOld)/2.0).setC1(255)); } if(y*yOld<=0.0) { if (y < 0) { theRamCanvasY0.drawPoint((x+xOld)/2.0, (z+zOld)/2.0, theRamCanvasY0.getPxColor((x+xOld)/2.0, (z+zOld)/2.0).setC0(255)); } else { theRamCanvasY0.drawPoint((x+xOld)/2.0, (y+yOld)/2.0, theRamCanvasY0.getPxColor((x+xOld)/2.0, (z+zOld)/2.0).setC2(255)); } theRamCanvasY0.drawPoint((x+xOld)/2.0, (z+zOld)/2.0, theRamCanvasY0.getPxColor((x+xOld)/2.0, (z+zOld)/2.0).setC1(255)); } xOld = x; yOld = y; zOld = z; } theRamCanvasXY.writeTIFFfile("attracting_torus_shadowXY.tiff"); theRamCanvasXZ.writeTIFFfile("attracting_torus_shadowXZ.tiff"); theRamCanvasYZ.writeTIFFfile("attracting_torus_shadowYZ.tiff"); theRamCanvasZ0.writeTIFFfile("attracting_torus_shadowZ0.tiff"); theRamCanvasX0.writeTIFFfile("attracting_torus_shadowX0.tiff"); theRamCanvasY0.writeTIFFfile("attracting_torus_shadowY0.tiff"); std::chrono::duration<double> runTime = std::chrono::system_clock::now() - startTime; std::cout << "Total Runtime " << runTime.count() << " sec" << std::endl; return 0; }
The above program will generate the following pictures:
3. A Movie
Now that we can see what the intersection looks like for
#include "ramCanvas.hpp" int main(void) { std::chrono::time_point<std::chrono::system_clock> startTime = std::chrono::system_clock::now(); const int XSIZ = 7680/4; const int YSIZ = 4320/4; const int NUMFRM = 120; /* Number of movie frames */ const double zLevStart = -4.0; const double zLevEnd = 4.0; const double isectDistToGo = 5e7; /* How long should the curve be for Z0 ? */ const double a = 4.0; /* Equation paramaters */ const double b = 0.1; /* Equation paramaters */ const double x0 = 0.0; /* Initial conditions */ const double y0 = 2.0; /* Initial conditions */ const double z0 = 1.0; /* Initial conditions */ const double targetDist = 0.025; /* Size of each step on curve */ const int maxNumUpBisect = 5; /* Max times we double tDelta to get > targetDist. */ const int maxNumDownBisect = 10; /* Max times we half tDelta to get < targetDist. */ # pragma omp parallel for schedule(static,1) for(int frame=0; frame<NUMFRM; frame++) { std::chrono::time_point<std::chrono::system_clock> frameStartTime = std::chrono::system_clock::now(); mjr::ramCanvas3c8b theRamCanvas(XSIZ, YSIZ, -10, 10, -10, 10); double zLev = zLevStart + frame*(zLevEnd-zLevStart)/(NUMFRM-1); /* Solve the equations..... */ double x = x0; double y = y0; double z = z0; double tDelta = 1.0; double dist = 0.0; double xOld = x; double yOld = y; double zOld = z; double Xdelta, Ydelta, Zdelta, movDist; while (dist < isectDistToGo) { /* Find tDelta that gets us bigger than targetDist */ int numUpBisect = 0; do { tDelta += (2 * tDelta); Xdelta = (y) * tDelta; Ydelta = (-x-z*y) * tDelta; Zdelta = (y*y-a+b*z) * tDelta; movDist = sqrt (fabs (Xdelta * Xdelta + Ydelta * Ydelta + Zdelta * Zdelta)); numUpBisect++; } while ((numUpBisect < maxNumUpBisect) && (movDist < targetDist)); /* Find tDelta that gets us a jump of <targetDist. */ int numDownBisect = 0; do { tDelta = tDelta / 2; Xdelta = (y) * tDelta; Ydelta = (-x-z*y) * tDelta; Zdelta = (y*y-a+b*z) * tDelta; movDist = sqrt (fabs (Xdelta * Xdelta + Ydelta * Ydelta + Zdelta * Zdelta)); numDownBisect++; } while ((numDownBisect < maxNumDownBisect) && (movDist > targetDist)); /* Update and draw */ dist += movDist; x = x + Xdelta; y = y + Ydelta; z = z + Zdelta; if((z-zLev)*(zOld-zLev)<=0.0) theRamCanvas.drawPoint((x+xOld)/2.0, (y+yOld)/2.0, mjr::ramCanvas3c8b::colorType::cornerColorEnum::WHITE); xOld = x; yOld = y; zOld = z; } theRamCanvas.writeTIFFfile("attracting_torus_shadowM_" + mjr::fmtInt(frame, 3, '0') + ".tiff"); std::chrono::duration<double> frameRunTime = std::chrono::system_clock::now() - frameStartTime; # pragma omp critical std::cout << "Frame " << frame << " of " << NUMFRM << " 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; return 0; }
And the result