Jump to my Home Page Send me a message Check out stuff on GitHub Check out my photography on Instagram Check out my profile on LinkedIn Check out my profile on reddit Check me out on Facebook

Mitch Richling: Hopalong Fractals

Author: Mitch Richling
Updated: 2024-09-17

barrymartin_00_5.png barrymartin_04_5.png barrymartin_01_5.png

Table of Contents

1. Introduction

Barry Martin's "Hopalong" orbit fractals are a family of discrete-time dynamical systems:

  • Classic Barry Martin fractal: \[ \begin{align*} x_{n+1} & = y_n - \mathrm{sgn}(x_n) \cdot \sqrt{\vert b x_n-c\vert} \\ y_{n+1} & = a-x_n \end{align*} \]
  • Positive Barry Martin fractal: \[ \begin{align*} x_{n+1} & = y_n + \mathrm{sgn}(x_n) \cdot \sqrt{\vert b x_n-c\vert} \\ y_{n+1} & = a-x_n \end{align*} \]
  • Additive Barry Martin fractal: \[ \begin{align*} x_{n+1} & = y_n + \sqrt{\vert b x_n-c\vert} \\ y_{n+1} & = a-x_n \end{align*} \]
  • Sinusoidal Barry Martin fractal: \[ \begin{align*} x_{n+1} & = y_n + \sin{(b x_n-c)} \\ y_{n+1} & = a-x_n \end{align*} \]

All of these maps, and others, may be constructed from the following system as special cases: \[ \begin{align*} x_{n+1} & = y_n+d \cdot \mathrm{ssgn}(x_n) \cdot \left(f \cdot \sqrt{\vert b x_n-c\vert} + g \cdot \sin{(b x_n-c)} + h \cdot \vert b x_n-c\vert \right) \\ y_{n+1} & = a-x_n \end{align*} \] Where \[ \mathrm{ssgn}(v) = \cases{ s & $v\lt 0$ \cr 1 & $v\ge 0$ } \] \[ \mathrm{sgn}(v) = \cases{ -1 & $v\lt 0$ \cr +1 & $v\ge 0$ } \]

Note the simplified form of the \(\mathrm{sgn}(v)\) function used – the standard definition may be used for nearly identical results.

Some special cases:

  • When \( d=-1, s=-1, f=1, g=0, h=0 \), the map becomes the "Classic Barry Martin fractal"
  • When \( d=1, s=-1, f=1, g=0, h=0 \), the map becomes the "Positive Barry Martin fractal"
  • When \( d=1, s=1, f=1, g=0, h=0 \), the map becomes the "Additive Barry Martin fractal"
  • When \( d=1, s=1, f=0, g=1, h=0, c=0 \), the map becomes the "Sinusoidal Barry Martin fractal"
  • When \( d=1, s=1, f=0, g=0, h=1, c=0 \), the map becomes the "The Gingerbread Man": \[ \begin{align*} x_{n+1} & = y_n + \sin{(b x_n)} \\ y_{n+1} & = a-x_n \end{align*} \]
  • When \( d=1, s=1, f=0, g=0, h=1, c\ne0 \), the map becomes the "The Shifted Gingerbread Man": \[ \begin{align*} x_{n+1} & = y_n + \sin{(b x_n-c)} \\ y_{n+1} & = a-x_n \end{align*} \]

2. Algorithms

Just as with the Peter de Jong attractors we look at the probability density function, \(P(x,y)\), defined by the probability that some \((x_n, y_n)\) from the map will be in a neighborhood of \((x,y)\). While that sounds complicated, producing a visualization of this PDF is quite simple in practice – we simply visualize a histogram based on computed values of the map. The first step is to define a rectangular region of the plane over which we wish to visualize the PDF, and then define a set of discrete pixels over that region. Then we compute a few million, or billion, iterations of the map, and count the number of times we hit each pixel.

#include "ramCanvas.hpp"

std::vector<std::array<double, 14>> params {
  /*        a         b         c       d,      s,   f,   g,   h,     n,   k, x-min, x-max,  y-min, y-max */
  {  -2.00000, -0.33000,  0.01000,   -1.0,   -1.0, 1.0, 0.0, 0.0, 4.0e7, 1.0, -18.0,  17.0,  -18.0,  17.0}, //  0 
  {   0.40000,  1.10000,  0.00000,   -1.0,   -1.0, 1.0, 0.0, 0.0, 2.5e7, 1.0,  -7.0,   7.0,   -7.0,   7.0}, //  1 
  {  -3.14000,  0.20000,  0.30000,   -1.0,   -1.0, 1.0, 0.0, 0.0, 3.0e7, 1.0, -40.0,  40.0,  -40.0,  40.0}, //  2 
  {  -3.14000,  0.19000,  0.32000,    1.0,   -1.0, 1.0, 0.0, 0.0, 1.0e7, 1.0, -38.0,  35.0,  -38.0,  35.0}, //  3 
  {  -2.14000, -0.20000,  0.30000,    1.0,    1.0, 1.0, 0.0, 0.0, 3.0e7, 0.5,  -9.0,   7.0,   -9.0,   7.0}, //  4 
  {   0.40000,  1.00000,  0.00000,   -1.0,   -1.0, 0.0, 1.0, 0.0, 2.0e6, 1.0, -70.0,  70.0,  -70.0,  70.0}, //  5 
  {  -3.14000,  0.20000,  0.30000,   -1.0,   -1.0, 0.0, 1.0, 0.0, 6.0e7, 1.0, -45.0,  45.0,  -45.0,  45.0}, //  6 
  {  -3.14000,  0.20000,  0.30000,    1.0,   -1.0, 0.0, 1.0, 0.0, 1.0e7, 1.0, -35.0,  35.0,  -35.0,  35.0}, //  7 
  {  -2.00000, -0.33000,  0.01000,   -1.0,   -1.0, 1.0, 0.0, 1.0, 4.0e7, 1.0, -25.0,  25.0,  -25.0,  25.0}, //  8 
  {  -3.14000,  0.20000,  0.30000,   -1.0,   -1.0, 1.0, 0.0, 1.0, 4.0e7, 1.0, -60.0,  60.0,  -60.0,  60.0}, //  9 
  {  -3.14000,  0.20000,  0.30000,    1.0,   -1.0, 1.0, 0.0, 1.0, 6.0e7, 1.0, -90.0,  90.0,  -90.0,  90.0}, // 10 
  {  -3.14000,  0.20000,  0.30000,   -1.0,   -1.0, 0.0, 0.0, 1.0, 2.0e7, 1.0, -17.0,  15.0,  -17.0,  15.0}, // 11 
  {  -3.14000,  0.20000,  0.30000,    1.0,   -1.0, 0.0, 0.0, 1.0, 3.0e7, 1.0, -17.0,  14.0,  -17.0,  14.0}, // 12 
  {  -3.14000,  0.20000,  0.30000,    1.0,   -1.0, 0.0, 0.0, 1.0, 1.0e7, 1.0, -17.0,  14.0,  -17.0,  14.0}, // 13 
  {  -1.14000,  0.01000,  0.05000,    1.0,   -1.0, 0.0, 0.0, 1.0, 5.0e6, 1.0,  -3.5,   2.5,   -3.5,   2.5}, // 14 
  {  -1.14000,  0.10000,  0.50000,    1.0,   -1.0, 0.0, 0.0, 1.0, 6.0e7, 1.0, -17.0,  14.0,  -17.0,  14.0}, // 15 
};

int main(void) {
  std::chrono::time_point<std::chrono::system_clock> startTime = std::chrono::system_clock::now();
  const int BSIZ = 480*8;
  mjr::ramCanvas1c16b::colorType aColor;
  aColor.setChans(1);

  for(decltype(params.size()) j=0; j<params.size(); ++j) {
    mjr::ramCanvas1c16b theRamCanvas(BSIZ, BSIZ, params[j][10], params[j][11], params[j][12], params[j][13]);

    double a = params[j][0];
    double b = params[j][1];
    double c = params[j][2];
    double d = params[j][3];
    double s = params[j][4];
    double k = params[j][9];

    double f = params[j][5];
    double g = params[j][6];
    double h = params[j][7];

    /* Draw the Attractor on a 16-bit, greyscale canvas such that the level is the hit count for that pixel.  
       Thus we are using an "image" as a way to store field data instead of color information. */
    double x        = 0.0;
    double y        = 0.0;
    uint64_t maxII  = 0;
    uint64_t inCnt  = 0;
    uint64_t maxItr = static_cast<uint64_t>(params[j][8]);
    uint64_t iPrt   = maxItr / 5;
    for(uint64_t i=1;i<maxItr;i++) {
      double xNew = y+d*(x<0.0?s:1.0)* (f*std::sqrt(std::abs(b*x-c)) + g*std::sin(b*x-c) + h*std::abs(b*x-c));
      double yNew = a-x;
      if ( !theRamCanvas.isCliped(x, y)) {
        inCnt++;
        theRamCanvas.drawPoint(x, y, theRamCanvas.getPxColor(x, y).tfrmAdd(aColor));
        if(theRamCanvas.getPxColor(x, y).getC0() > maxII) {
          maxII = theRamCanvas.getPxColor(x, y).getC0();
          if(maxII > 16384) { // 1/4 of max possible intensity
            std::cout << "ITER(" << j <<  "): " << i << " MAXS: " << maxII << " EXIT: Maximum image intensity reached" << std::endl;
            break;
          }
        }
      }
      if((i % iPrt) == 0)
        std::cout << "ITER(" << j <<  "): " << i << " MAXS: " << maxII << " INC: " << inCnt << std::endl;
      x=xNew;
      y=yNew;
    }
    std::cout << "ITER(" << j <<  "): " << "DONE" << " MAXS: " << maxII << " INC: " << inCnt << std::endl;

    // Log image transform
    theRamCanvas.applyHomoPixTfrm(&mjr::ramCanvas1c16b::colorType::tfrmLn1);
    maxII = static_cast<uint64_t>(std::log(static_cast<double>(maxII)));

    /* Create a new image based on csCCfractal0RYBCW -- this one is 24-bit RGB color. */
    mjr::ramCanvas3c8b anotherRamCanvas(BSIZ, BSIZ);
    typedef mjr::ramCanvas3c8b::colorType::csCCfractal0RYBCW cs_t;
    for(int yi=0;yi<theRamCanvas.getNumPixY();yi++)
      for(int xi=0;xi<theRamCanvas.getNumPixX();xi++) 
        anotherRamCanvas.drawPoint(xi, yi, cs_t::c(k * theRamCanvas.getPxColor(xi, yi).getC0() / static_cast<mjr::ramCanvas3c8b::csFltType>(maxII)));

    anotherRamCanvas.writeTIFFfile("barrymartin_" + mjr::fmtInt(j, 2, '0') + ".tiff");
  }
  std::chrono::duration<double> runTime = std::chrono::system_clock::now() - startTime;
  std::cout << "Total Runtime " << runTime.count() << " sec" << std::endl;
  return 0;
}

Source Link: barrymartin.cpp

The above program will generate several images includeing the "Classic Barry Martin Atractor":

barrymartin_00_20.png

4. References

  • Martin, Barry. 1989. "Graphic Potential of Recursive Functions." In Computers in Art, Design and Animation, 109-29. Berlin, Heidelberg: Springer-Verlag.
  • A. K. Dewdney. 1986. "Wallpaper for the Mind: Computer Images That Are Almost, but Not Quite, Repetitive." Scientific American, September, 12-23.

Check out the fractals section of my reading list.

All the code used to generate everything on this page may be found on github.