16
\$\begingroup\$

I came across this picture the other day: (Credit to Josep M Batlle I Ferrer)

N(f(x)=cosh(x)-1)

Your job is to generate this picture. This graph is generated by repeatedly applying newton's method to the graph of:

$$f(x)=\cosh(x)-1$$

In other words, repeatedly apply the following function to every point till the rectangular distance is less than some limit to a attractor:

$$g(x)=\frac{x \cdot \sinh(x) - \cosh(x) + 1}{\sinh(x)}$$

The process is described in psuedocode below:

let n be the maximum number of iterations
for every point (as a complex number) in the image:
    repeat n times:
        point = (point * sinh(point) - cosh(point) + 1) / sinh(point)
        
        if the rectangular distance between the point and any attractor is less than 0.01:
             break
        end if
    end repeat
    color the point in the image depending on the nearest attractor and the number of iterations required

The rectangular distance is defined as max(abs(dx), abs(dy)). We use rectangular distance instead of straight line distance so we get the wave shapes that make this graph so beautiful.

There is a attractor at 0+n*2i*PI for any integer N. Thus at even increments of 2 PI in the imaginary direction.

Rules

  • Your graph must include the area from [-0.75, -0.75 + PI] to [0.75, +0.75 + PI].
  • Resolution over this needs to be at least 256 by 256. Either characters or pixels. If you decide to cover a larger area you need to increase the resolution in proportion.
  • Use at least 20 iterations.
  • Both adjacent iteration counts and adjacent attractors need to be drawn in distinct colors. You must use at least 3 different colors. Coloring should soley be based on the attractor and iteration count.
  • If any point causes a division by 0 or another math error, you may choose to render anything at this point. This is considered undefined behavior.
  • Displaying a image, saving a image, or drawing ASCII art are all acceptable, as long as the other rules are met. If you draw ASCII art characters need to be ANSI colored.
  • Please include a screenshot of the result in your answer

This is code golf, shortest answer wins. ASCII art answers do not compete against graphical answers in the same language.

Background info

\$\endgroup\$
2
  • \$\begingroup\$ Is the definition of \$g(x)\$ supposed to have \$\sinh(x)\$ on the bottom? If not, what is \$pos\$? \$\endgroup\$ Jul 2 at 11:58
  • \$\begingroup\$ @Zionmyceliaadamancy fixed, thanks \$\endgroup\$
    – mousetail
    Jul 2 at 12:02

6 Answers 6

12
\$\begingroup\$

Desmos, 401 375 357 353 bytes

-26 bytes thanks to @aiden-chow

-18 bytes thanks to flexabrotnt#1409

H(u,v)=(u.xv.x-v.yu.y,u.xv.y+v.xu.y)
F(u,v)=(u.xv.x+u.yv.y,u.yv.x-u.xv.y)/(v.x²+v.y²)
E(z)=e^{z.x}(cos(z.y),sin(z.y))
C(z,k)=H((1/2,0),E(z)+kE(H((-1,0),z)))
N(z)=F(H(z,C(z,-1))-C(z,1)+(1,0),C(z,-1))
G(z)=max(z.x²,z.y²)
Y(z)=N(N(N(N(N(z)))))
Z(z)=Y(Y(Y(Y(z))))
l=[0...2]
O=hsv(l110,1,1)
P=Z((x,y))
X=round(P.y/(2π))
G((0,2Xπ)-P)<0.01\{\mod(X,3)=l\}

Try It On Desmos!

Before golfing Rendered in desmos

\$\endgroup\$
9
  • \$\begingroup\$ Welcome to Code Golf, and nice answer! \$\endgroup\$ Jul 4 at 1:14
  • \$\begingroup\$ @NoHaxJustRadvylf Thank you! \$\endgroup\$ Jul 4 at 1:55
  • \$\begingroup\$ Amazing answer! I’m achow on discord; this is my code golf account. \$\endgroup\$
    – Aiden Chow
    Jul 4 at 2:02
  • 1
    \$\begingroup\$ I don't see the number of iterations visible in the output \$\endgroup\$
    – mousetail
    Jul 4 at 6:38
  • 1
    \$\begingroup\$ The entire red area at the bottom seems to be the exact same color, even though some areas should require less iterations than others \$\endgroup\$
    – mousetail
    Jul 4 at 8:14
8
\$\begingroup\$

Python + Pygame, 410 392 bytes (Example Submission)

-16 bytes thanks to wizzwizz4 and others

from pygame import*
from cmath import*
w=256;s=Surface((w,w));f=lambda p:p-(cosh(p)-1)/sinh(p);g=lambda t:round(t.imag/pi/2)*pi*2j;h=lambda p:max(abs(p.real),abs(p.imag))>0.01
for x in range(w):
 for y in range(w):
  t=x/w-0.5+(y/w-0.5-pi)*1j
  for i in range(99):
   try:t=f(t);1/h(g(t)-t)
   except:break
  s.set_at((x,y),[0,[w-1,5,5],[9,w-1,9]][(round(t.imag/pi/2)+i)%3])
image.save(s,"f")

Result will be saved as a TGA file named "f":

enter image description here

\$\endgroup\$
9
  • 1
    \$\begingroup\$ Do you need to save as a file with a ".png" extension? \$\endgroup\$
    – emanresu A
    Jul 2 at 21:15
  • 1
    \$\begingroup\$ I think the three lines in the middle can be replaced by try:t=f(t);1/(h(g(t)-t)>=0.01) // except:break. \$\endgroup\$
    – wizzwizz4
    Jul 2 at 21:56
  • 1
    \$\begingroup\$ @emanresuA I think .png is one of the shortest extensions that can be used? The save method needs ab extension to know what format to save the image in. \$\endgroup\$ Jul 2 at 21:56
  • 1
    \$\begingroup\$ Running with save(s,"f") works. $ file f -> f: Targa image data - RGB - RLE 256 x 256 x 24 - top. \$\endgroup\$ Jul 2 at 23:56
  • 2
    \$\begingroup\$ Those spaces around the = in t = f(t) don't need to be there. Also, the explanation is slightly out of date – and I suspect that >0.01 might make a different image to >=0.01 by a few pixels. \$\endgroup\$
    – wizzwizz4
    Jul 3 at 9:48
7
\$\begingroup\$

Rust + Num ASCII Art - 420 413 bytes

-6 bytes thanks to @LF

use{num::complex::*,std::f32::consts::*};(0..255).any(|y|(0..512).any(|x|if let Some(n)=(0..99).scan(Complex::new(x as f32/256.0-1.,y as f32/256.0-0.5+PI),|s,l|{*s=*s-(s.cosh()-1.)/s.sinh();Some((*s,l))}).filter(|s|s.0.re.abs().max(s.0.im-((s.0.im/(2.*PI)).round()*2.*PI).abs())<0.01).next(){print!("\x1b[{:0<2};{:0<2}m0",30+n.1%8,40+((n.0.im/PI/2. + 36.).round()%8.)as usize)}else{print!("0")}>())||print!("
")>());

enter image description here

\$\endgroup\$
3
  • 1
    \$\begingroup\$ use{num_complex::*,std::f32::consts::*}; (using the num-complex crate directly instead of num) \$\endgroup\$
    – L. F.
    Jul 3 at 5:13
  • \$\begingroup\$ .filter(...).next() => .find(...). *s=*s-... => *s-=.... print!("\n") => println!(). \$\endgroup\$
    – alephalpha
    Jul 4 at 2:16
  • 1
    \$\begingroup\$ Can s.0.re.abs().max(s.0.im-((s.0.im/(2.*PI)).round()*2.*PI).abs())<0.01 be shortened to something like s.0.re.abs()<0.01&&s.0.im.cos()>0.01.cos() using the observation from my answer? (I don't speak Rust.) \$\endgroup\$
    – Dingus
    Jul 4 at 8:27
5
\$\begingroup\$

Pyxplot 0.8.4, 190 188 186 bytes

se nu c
su g(x,y){z=x+y*i
fo n=1to20{z=z-coth(z)+csch(z)
if(Re(z)**2<1e-4)*cos(Im(z))>cos(.01){ret ceil(Im(z)/pi-.5);}
}
}
se sa gr 1e3x1e3
se colm hsb c2:c1:1
p[-1:1][2:4]g(x,y):n w col

Conveniently for code golf, most commands in Pyxplot may be abbreviated zealously. By default (empty .pyxplotrc config file), output is written to pyxplot.eps.

enter image description here

If the text and axis labels produced by default must be removed, replace the last line with

se ax xy in
p[-1:1][2:4]g(x,y):n t''w col

at a cost of 15 bytes. Add se s s for a square aspect ratio.

Some mathematical simplifications are used. Firstly, we write \$g\$ as \$g(z) = z-\coth(z)+\mathop{\rm csch}(z)\$. Secondly, we note that \$\max(\lvert\Delta x\rvert, \lvert\Delta y \rvert) < t\$ implies that both \$\lvert\Delta x\rvert < t\$ and \$\lvert\Delta y\rvert < t\$ and treat these two constraints separately:

  • \$\lvert\Delta x\rvert = \lvert x-0\rvert = \lvert x\rvert\$, so the first constraint becomes \$x^2<t^2\$, which is shorter in Pyxplot than \$\lvert x\rvert < t\$ (which would be abs(Re(z))<.01).
  • \$\lvert\Delta y\rvert = \lvert y-2k\pi\rvert\$ for some integer \$k\$. Observe that the attractors lie at points where the cosine function is maximal, i.e. \$\cos(2k\pi)=1\$. Thus the constraint on \$y\$ may be written as \$\cos(\lvert y-2k\pi\rvert) > \cos(t)\$, which simplifies to \$\cos(y) > \cos(t)\$ by the symmetry and periodicity of the cosine function.

For the plot, an HSB colourmap is used. At each point the hue is determined by the number of iterations (n in the code), automatically rescaled to span the range from 0 and 1 (i.e. the full range of hues). The saturation is determined by the value of \$2k\$ (the return value of g(x,y)) for the point's attractor, again rescaled to span the full range of saturations. The brightness is fixed at maximum.

An iteration limit of 20 is imposed. Red and orange hues become more predominant at the expense of green and blue as this limit is increased.

In theory, it is possible to omit [-1:1][2:4] in the last line of the code (saving 10 bytes), have Pyxplot apply its default axis ranges (\$-10\le x,y\le 10\$), and increase the grid resolution (1e3x1e3) to compensate. In practice, setting the resolution to 4e3x4e3 exhausted the available memory (16 GB RAM plus 20 GB swap) on my machine.

Ungolfed code

set numerics complex
subroutine g(x, y) {
  z = x + y * i
  for n = 1 to 20 {
    z = z - coth(z) + csch(z)
    if Re(z)**2 < 1e-4 and cos(Im(z)) > cos(1e-2) {
      return ceil(Im(z) / pi - 0.5)
    }
  }
}
set samples grid 1e3 x 1e3
set colourmap hsb c2:c1:1
plot [-1:1] [2:4] g(x, y):n with colourmap
\$\endgroup\$
2
  • \$\begingroup\$ The difference in shading between attractors is very subtle but it counts \$\endgroup\$
    – mousetail
    Jul 3 at 15:28
  • 1
    \$\begingroup\$ Thank you for introducing me to pyxplot \$\endgroup\$
    – graffe
    Jul 3 at 21:32
5
\$\begingroup\$

JavaScript (ES6), 271 260 253 251 bytes

c=>{z=c.getContext`2d`;for(i=384;i--;)for(j=384;i--;)with(Math){x=i/256-.75;y=j/256-.75+PI;for(k=10;k<30>(x*x<1e-4&abs(y-2*PI*(v=round(y/2/PI)))<.01);k++)w=cosh(x)+cos(y),x-=sinh(x)/w,y-=sin(y)/w;z.fillStyle=`#0${(k+v%10)*3%10}9`;z.fillRect(i,j,1,1)}}

Takes as parameter a 384×384 canvas on which to draw the result. Demo, with whitespace for readability and animation:

f = async c => {
  z = c.getContext`2d`;
  for (i = 384; i--; ) {
    for (j = 384; j--; ) with (Math) {
      x = i / 256 - .75;
      y = j / 256 - .75 + PI;
      for (k = 10; k < 30 > (x * x < 1e-4 & abs(y - 2 * PI * (v = round(y / 2 / PI))) < .01); k++)
        w = cosh(x) + cos(y),
        x -= sinh(x) / w,
        y -= sin(y) / w;
      z.fillStyle = `#0${(k+v%10)*3%10}9`;
      z.fillRect(i, j, 1, 1);
    }
    await new Promise(resolve => requestAnimationFrame(resolve));
  }
};
f(c);
<canvas width=384 height=384 id=c>

Edit: Saved 11 bytes thanks to @Ausername. Saved 7 bytes thanks to @Steffan. Saved 2 bytes thanks to @Dingus.

\$\endgroup\$
13
  • \$\begingroup\$ You can save a few bytes by making the color `#${k%10}8f`. It actually looks better too! \$\endgroup\$
    – emanresu A
    Jul 4 at 22:59
  • \$\begingroup\$ Also (general tip) you can save a few bytes in the for loops by ++i<384 or similar \$\endgroup\$
    – emanresu A
    Jul 4 at 23:01
  • \$\begingroup\$ @emanresuA I chose a slightly different palette as I didn't like that one, but thanks for the approach. \$\endgroup\$
    – Neil
    Jul 4 at 23:06
  • \$\begingroup\$ @emanresuA I do need to have the correct values inside the loops though, so you'd need to explain how to achieve that. \$\endgroup\$
    – Neil
    Jul 4 at 23:07
  • \$\begingroup\$ i=0;i<384;i++ => i=384;i--;, and same with j \$\endgroup\$
    – Steffan
    Jul 4 at 23:14
2
\$\begingroup\$

Wolfram Language (Mathematica), 128 bytes

Image@Table[n=0;x+y*I//.z_/;Max@Abs@{Re@z,Mod[i=Im@z,Pi,n+=8t;-1]}>2t:>z-Coth@z+Csch@z;i~Hue~n,{y,-1+Pi,1+Pi,t=.005},{x,-1,1,t}]

enter image description here

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.