Visualizing the Collatz Conjecture using Altair & Matplotlib


Introduced by Lothar Collatz in 1937, the conjecture can be defined as: start with any positive integer, if it is even divide it by two. If it is odd, triple it and add one. Now repeat this procedure to generate a sequence.

The great unsolved question in mathematics is to prove that this sequence will reach 1 for all positive integer initial values. The problem is widely believed to be out of reach of present day mathematics. The great martian Paul Erdos said of the conjecture, "Mathematics may not be ready for such problems."

import altair as alt
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

def collatz(n):
    if n == 1:                             
        result = [1]
    elif n % 2 == 0:
        result = collatz(n // 2) + [n]
    elif n % 2 == 1:
        result = collatz((3 * n) + 1) + [n]
    return result

runs=5000
seen = {}
sequence_lengths=[]
for i in range(1, runs):
    length = collatz(i)
    sequence_lengths.append(len(length))        

In the 1970s, mathematicians showed that almost all Collatz sequences eventually reach a number that’s smaller than where you started. Terence Tao, one of the most gifted minds of our generation, used partial differential equations to prove that 99% of starting values will reach a value that is quite close to 1. It is arguably the strongest result in the long history of the conjecture.

However, his method can not lead to a full proof and another approach is required.

“You can get as close as you want to the Collatz conjecture, but it’s still out of reach” - Terence Tao

df=pd.DataFrame(zip(np.arange(1,runs),sequence_lengths), columns=["i","len"])
df['is_even']=df['i'].apply(lambda x: 1 if x%2==0 else 0)

domain=['Odd', 'Even']
alt.Chart(df).mark_point(
    opacity=0.2
    ).encode(
    x=alt.X('i',scale=alt.Scale(domain=(0, 5050)), axis=alt.Axis(title='N')),
    y=alt.Y('len', axis=alt.Axis(title='Length of Collatz Sequence')),
    color=alt.Color('is_even:N', scale=alt.Scale(scheme="darkblue"), legend=None)


).properties(
    width=600,
    height=400,
    title='Collatz Sequence',

).configure_axis(
    grid=False
).configure_view(
    strokeOpacity=0
)

The code below visualizes collatz squences as paths for integers ranging from 1 to 2000. A rotation is applied to each point on the path based on whether the number in the sequence is odd or even.

def color_picker():
    return np.random.choice(['#FF5E02','red', 'blue','#6400FF','#E10060','#02D1FF'])


def transforms(x):
    seq=[0]
    val=[0]
    rad=0
    even=-.54* (np.pi / 180 )
    odd = 1.2* (np.pi / 180 )
    for i in range(1, len(x)):
        if x[i]%2==0:
            seq.append(seq[i-1]+np.sin(rad+even))
            rad=rad+even            
        else:
            seq.append(seq[i-1]+np.sin(rad+odd))
            rad=rad+odd
        val.append(val[i-1]+np.cos(rad))
    return val,seq
plt.figure(figsize=(10,10))
fig, ax = plt.subplots()
fig.set_figheight(10)
fig.set_figwidth(10)
runs=2000
for i in range(1, runs):
    length = collatz(i)    
    sequence_lengths.append(length)
    x,y = transforms(np.array(length))
    #ax.set_facecolor('black')
    ax.plot(x,y, alpha=0.15, color=color_picker());     
<Figure size 720x720 with 0 Axes>

png

The collatz function can also be represented as:

This is a holomorphic function, and can be used to create fractals akin to the Julia set of fractals. The traditional fractal pattern colors points that escape to infinity with lighter shades and those that are bounded with a darker shade.

The following code, creates non-traditional patterns by providing lighter shades to collatz sequences that have fallen below a certain threshold in a set number of sequences.

def collatz_escape(x, y, maxiter, escape=1000, cmap='magma'):    
    z = x + 1.0j * y.reshape(-1, 1)    
    image = np.zeros((len(x), len(y)), dtype='int')
    mask = np.full(image.shape, True)
    for i in range(maxiter):
        z[mask] = (2 + 7*abs(z[mask]) - (2 + 5*abs(z[mask]))*np.cos(np.pi*abs(z[mask])))/4;
        mask = abs(z) < escape
        image += mask
    plt.figure(figsize=(10,10))
    plt.axis('off')
    plt.imshow(image, aspect='equal', cmap=cmap, origin='lower',
               extent=(x[0], x[-1], y[0], y[-1]))
collatz_escape(x=np.linspace(-2,2,10000), y=np.linspace(-2,2,10000), maxiter=30)

png

Expanding the input range we get:
collatz_escape(x=np.linspace(-12.5,12.5,10000), y=np.linspace(-12.5,12.5,10000), maxiter=30)

png

This isn't as interesting but I was bored on a Sunday afternoon. It is a set of identical matrices colored based on the length of the collatz sequence corresponding to the row index + column index.

Does it remind you of something?

x=np.zeros((500,500))

for i in range(500):
    for j in range(500):
        x[i][j]=len(collatz(i+j+1))
fig,ax= plt.subplots(2,2, figsize=(10,10))
plt.subplots_adjust(wspace=0, hspace=0)
ax[0,0].imshow(x, aspect='auto', cmap='summer_r', origin='lower');
ax[0,0].axis('off')

ax[0,1].imshow(x, aspect='auto', cmap='Spectral', origin='lower');
ax[0,1].axis('off')

ax[1,0].imshow(x, aspect='auto', cmap='autumn_r', origin='lower');
ax[1,0].axis('off')

ax[1,1].imshow(x, aspect='auto', cmap='winter', origin='lower', alpha=0.67);
ax[1,1].axis('off');

png

XKCD on the Collatz Conjecture: