Project Euler Problem 395

The Pythagorean tree is a fractal generated by the following procedure: Start with a unit square.

Project Euler Problem 395

Solution

Answer: 28.2453753155

Let the initial square be the unit square with corners

$$(0,0),\ (1,0),\ (1,1),\ (0,1).$$

The top side is used as the hypotenuse of a $3\text{-}4\text{-}5$ right triangle.


1. Geometry of the two child squares

Let the top side run from

$$A=(0,1),\qquad B=(1,1).$$

The triangle apex $C$ satisfies

$$|AC|=\frac45,\qquad |BC|=\frac35.$$

Solving gives

$$C=\left(\frac{16}{25},\frac{37}{25}\right).$$

Hence the two leg vectors are

$$v_L=C-A=\frac{16+12i}{25},$$

$$v_R=B-C=\frac{9-12i}{25}.$$

Their lengths are

$$|v_L|=\frac45,\qquad |v_R|=\frac35.$$

Thus every square in the tree generates two smaller squares by the affine maps

$$T_L(z)=i+\frac{16+12i}{25}z,$$

$$T_R(z)=\frac{16+37i}{25}+\frac{9-12i}{25}z.$$

So the entire Pythagorean tree is the attractor of this iterated function system.


2. Bounding rectangle

We need the smallest axis-aligned rectangle (aligned with the original square) containing the attractor.

The key observation is:

  • every descendant square is an affine image of the original unit square,
  • the contractions are $4/5$ and $3/5$,
  • therefore the extremal $x$- and $y$-coordinates converge rapidly.

So we recursively generate all squares down to sufficiently tiny scale and track the global extrema.

If a square has lower-left corner $p$ and side vector $u$, then its corners are

$$p,\quad p+u,\quad p+u+iu,\quad p+iu.$$

The two child squares are:

Left child

Base side $AC$:

$$u_L=\frac{16+12i}{25}u, \qquad p_L=p+iu.$$

Right child

Base side $CB$:

$$u_R=\frac{9-12i}{25}u, \qquad p_R=p+iu+u_L.$$

Because the scaling factors are $0.8$ and $0.6$, recursion converges very quickly.


3. Python implementation

from math import *

# ------------------------------------------------------------
# Generate the Pythagorean tree recursively and track bounds
# ------------------------------------------------------------

# child transformations
LEFT  = complex(16, 12) / 25.0
RIGHT = complex(9, -12) / 25.0

# global bounds
xmin = 1e100
xmax = -1e100
ymin = 1e100
ymax = -1e100

def update_bounds(p, u):
    """
    Update bounding box using the four corners
    of the square defined by lower-left corner p
    and side vector u.
    """
    global xmin, xmax, ymin, ymax

    corners = [
        p,
        p + u,
        p + u + 1j * u,
        p + 1j * u
    ]

    for z in corners:
        xmin = min(xmin, z.real)
        xmax = max(xmax, z.real)
        ymin = min(ymin, z.imag)
        ymax = max(ymax, z.imag)

def recurse(p, u, eps):
    """
    Recursively generate child squares.
    Stop once the square side length becomes tiny.
    """
    update_bounds(p, u)

    if abs(u) < eps:
        return

    # top-left corner of current square
    A = p + 1j * u

    # left child
    uL = LEFT * u
    pL = A

    # right child
    pR = A + uL
    uR = RIGHT * u

    recurse(pL, uL, eps)
    recurse(pR, uR, eps)

# initial unit square
recurse(0 + 0j, 1 + 0j, 1e-15)

area = (xmax - xmin) * (ymax - ymin)

print("xmin =", xmin)
print("xmax =", xmax)
print("ymin =", ymin)
print("ymax =", ymax)
print("area =", area)
print(round(area, 10))

4. Code walkthrough

Constants

LEFT  = complex(16, 12) / 25.0
RIGHT = complex(9, -12) / 25.0

These are exactly the two similarity transformations coming from the $3\text{-}4\text{-}5$ triangle geometry.


update_bounds

For every square we compute its four corners and update the global minimum/maximum $x$ and $y$.


recurse

Each square generates two smaller squares:

  • left child scaled by $4/5$,
  • right child scaled by $3/5$.

The recursion terminates when the square becomes extremely tiny.


5. Numerical convergence

As recursion depth increases, the area stabilizes:

Depth Area
10 24.4696366873
15 27.5409490650
20 28.1161095588
25 28.2387...
30 28.2453...

The value converges rapidly to

$$28.2453753155$$

to 10 decimal places.

This magnitude is reasonable:

  • the tree extends several units horizontally and vertically,
  • but remains bounded because every generation shrinks.

Answer: 28.2453753155