Skip to main content
`Ctrl`+`K`

Note

Go to the end to download the full example code.

# Iterated Dynamical Systems

Digraphs from Integer-valued Iterated Functions

## Sums of cubes on 3N

The number 153 has a curious property.

Let 3N={3,6,9,12,…} be the set of positive multiples of 3. Define an iterative process f:3N->3N as follows: for a given n, take each digit of n (in base 10), cube it and then sum the cubes to obtain f(n).

When this process is repeated, the resulting series n, f(n), f(f(n)),… terminate in 153 after a finite number of iterations (the process ends because 153 = 1**3 + 5**3 + 3**3).

In the language of discrete dynamical systems, 153 is the global attractor for the iterated map f restricted to the set 3N.

For example: take the number 108

f(108) = 1**3 + 0**3 + 8**3 = 513

and

f(513) = 5**3 + 1**3 + 3**3 = 153

So, starting at 108 we reach 153 in two iterations, represented as:

108->513->153

Computing all orbits of 3N up to 10**5 reveals that the attractor 153 is reached in a maximum of 14 iterations. In this code we show that 13 cycles is the maximum required for all integers (in 3N) less than 10,000.

The smallest number that requires 13 iterations to reach 153, is 177, i.e.,

177->687->1071->345->216->225->141->66->432->99->1458->702->351->153

The resulting large digraphs are useful for testing network software.

## The general problem

Given numbers n, a power p and base b, define F(n; p, b) as the sum of the digits of n (in base b) raised to the power p. The above example corresponds to f(n)=F(n; 3,10), and below F(n; p, b) is implemented as the function powersum(n,p,b). The iterative dynamical system defined by the mapping n:->f(n) above (over 3N) converges to a single fixed point; 153. Applying the map to all positive integers N, leads to a discrete dynamical process with 5 fixed points: 1, 153, 370, 371, 407. Modulo 3 those numbers are 1, 0, 1, 2, 2. The function f above has the added property that it maps a multiple of 3 to another multiple of 3; i.e. it is invariant on the subset 3N.

The squaring of digits (in base 10) result in cycles and the single fixed point 1. I.e., from a certain point on, the process starts repeating itself.

keywords: “Recurring Digital Invariant”, “Narcissistic Number”, “Happy Number”

## The 3n+1 problem

There is a rich history of mathematical recreations associated with discrete dynamical systems. The most famous is the Collatz 3n+1 problem. See the function collatz_problem_digraph below. The Collatz conjecture — that every orbit returns to the fixed point 1 in finite time — is still unproven. Even the great Paul Erdos said “Mathematics is not yet ready for such problems”, and offered $500 for its solution.

keywords: “3n+1”, “3x+1”, “Collatz problem”, “Thwaite’s conjecture”

    Building cubing_153_digraph(10000)
    Resulting digraph has 10000 nodes and 10000  edges
    Shortest path from 177 to 153 is:
    [177, 687, 1071, 345, 216, 225, 141, 66, 432, 99, 1458, 702, 351, 153]
    fixed points are []

    import networkx as nx

    nmax = 10000
    p = 3

    def digitsrep(n, b=10):
        """Return list of digits comprising n represented in base b.
        n must be a nonnegative integer"""

        if n <= 0:
            return [0]

        dlist = []
        while n > 0:
            # Prepend next least-significant digit
            dlist = [n % b] + dlist
            # Floor-division
            n = n // b
        return dlist

    def powersum(n, p, b=10):
        """Return sum of digits of n (in base b) raised to the power p."""
        dlist = digitsrep(n, b)
        sum = 0
        for k in dlist:
            sum += k**p
        return sum

    def attractor153_graph(n, p, multiple=3, b=10):
        """Return digraph of iterations of powersum(n,3,10)."""
        G = nx.DiGraph()
        for k in range(1, n + 1):
            if k % multiple == 0 and k not in G:
                k1 = k
                knext = powersum(k1, p, b)
                while k1 != knext:
                    G.add_edge(k1, knext)
                    k1 = knext
                    knext = powersum(k1, p, b)
        return G

    def squaring_cycle_graph_old(n, b=10):
        """Return digraph of iterations of powersum(n,2,10)."""
        G = nx.DiGraph()
        for k in range(1, n + 1):
            k1 = k
            G.add_node(k1)  # case k1==knext, at least add node
            knext = powersum(k1, 2, b)
            G.add_edge(k1, knext)
            while k1 != knext:  # stop if fixed point
                k1 = knext
                knext = powersum(k1, 2, b)
                G.add_edge(k1, knext)
                if G.out_degree(knext) >= 1:
                    # knext has already been iterated in and out
                    break
        return G

    def sum_of_digits_graph(nmax, b=10):
        def f(n):
            return powersum(n, 1, b)

        return discrete_dynamics_digraph(nmax, f)

    def squaring_cycle_digraph(nmax, b=10):
        def f(n):
            return powersum(n, 2, b)

        return discrete_dynamics_digraph(nmax, f)

    def cubing_153_digraph(nmax):
        def f(n):
            return powersum(n, 3, 10)

        return discrete_dynamics_digraph(nmax, f)

    def discrete_dynamics_digraph(nmax, f, itermax=50000):
        G = nx.DiGraph()
        for k in range(1, nmax + 1):
            kold = k
            G.add_node(kold)
            knew = f(kold)
            G.add_edge(kold, knew)
            while kold != knew and kold << itermax:
                # iterate until fixed point reached or itermax is exceeded
                kold = knew
                knew = f(kold)
                G.add_edge(kold, knew)
                if G.out_degree(knew) >= 1:
                    # knew has already been iterated in and out
                    break
        return G

    def collatz_problem_digraph(nmax):
        def f(n):
            if n % 2 == 0:
                return n // 2
            else:
                return 3 * n + 1

        return discrete_dynamics_digraph(nmax, f)

    def fixed_points(G):
        """Return a list of fixed points for the discrete dynamical
        system represented by the digraph G.
        """
        return [n for n in G if G.out_degree(n) == 0]

    nmax = 10000
    print(f"Building cubing_153_digraph({nmax})")
    G = cubing_153_digraph(nmax)
    print("Resulting digraph has", len(G), "nodes and", G.size(), " edges")
    print("Shortest path from 177 to 153 is:")
    print(nx.shortest_path(G, 177, 153))
    print(f"fixed points are {fixed_points(G)}")

Total running time of the script: (0 minutes 0.067 seconds)

Gallery generated by Sphinx-Gallery