Decorating for Christmas with recursive snowflakes

Contents

Decorating for Christmas with recursive snowflakes#

Note

This tutorial is a little advanced and assumes that you are already somewhat familiar with functions in Python.

The image below shows an example of the decorations can create by using fractals and turtle programming.

Et bilde av det ferdigbroderte snøfnugget.

If we look closer at the “arms” of a snowflake, we may notice that it appears to contain more miniature versions of it self, smaller “arms”. When a pattern appears to contain smaller copies of it self we call it a “fractal” or recursive pattern.

../_images/SIA-SIA2013-09119.png

Fractal snowflake#

A snowflake where each branch of the arms have new branches (image by Wilson Alwyn Bentley (1865-1931)).

Fraktale mønstre dukker opp mange steder i naturen og kunst. Fractal patterns appear many places in both art and nature.

../_images/3104423642_31665c5fe4_c.jpg

Fractal cauliflower#

A cauliflower where each flower looks like a small cauliflower (image by Amber Case (CC-BY-NC)).

../_images/pexels-jeremy-bishop-14061015.jpg

Fractal tree#

The branches of a tree looks like a tree, and is therefore fractal (image by Jeremy Bishop).

../_images/5419811742_9068550c29_c.jpg

Mathematical fractal#

A visualisation of a fractal Julia set (image by Dominic Alves).

../_images/Kandariya_mahadeva_temple.jpg

The Kandariya Mahadeva Temple#

An image of the Kandariya Mahadeva Temple, which is constructed as a three dimensional fractal (image by the Wikipedia user Antorjal).

We can draw fractal patterns with code by using recursive functions. A recursive function is a function that calls itself. So what we need to do is to define a function that draws an arm that consists of smaller arms which are drawn by the same function. This may be a bit confusing, so let’s start by creating a simple draw_arm function that draws a simple snowflake arm.

 1from turtlethread import Turtle
 2
 3def draw_arm(needle, length):
 4    # Move forward a little bit
 5    needle.forward(0.4*length)
 6
 7    # Draw the first "branch" pointing a little downwards
 8    needle.right(45)
 9    needle.forward(length*0.3)
10    needle.backward(length*0.3)
11    needle.left(45)
12    # Draw the second "branch" pointing a little upwards
13    needle.left(45)
14    needle.forward(length*0.3)
15    needle.backward(length*0.3)
16    needle.right(45)
17
18    # Move forward a little bit
19    needle.forward(length*0.6)
20
21needle = Turtle()
22with needle.running_stitch(30):
23    draw_arm(needle, 400)
24
25needle.visualise()
Snowflake arm. A straight line with two branches, one pointing upwards and one pointing downwards.

If we call this function, we get a drawing of an arm, and we observe that this arm is built up of four “pieces”, where each piece is a line segment. Instead of a simple line segment, we want each piece to contain a small arm with two new branches. For this, we can replace the call to the forward function with a call to the draw_arm function. Then we get a function which calls itself, also known as a recursive function.

Image of a dictionary, focusing on the word "recursion". The definition is: "See definition of recursion".
 1from turtlethread import Turtle
 2
 3def draw_arm(needle, length):
 4    # Move forward a little bit
 5    draw_arm(needle, 0.4*length)
 6
 7    # Draw the first "branch" pointing a little downwards
 8    needle.right(45)
 9    draw_arm(needle, length*0.3)
10    needle.backward(length*0.3)
11    needle.left(45)
12    # Draw the second "branch" pointing a little upwards
13    needle.left(45)
14    draw_arm(needle, length*0.3)
15    needle.backward(length*0.3)
16    needle.right(45)
17
18    # Move forward a little bit
19    draw_arm(needle, length*0.6)
20
21needle = Turtle()
22with needle.running_stitch(30):
23    draw_arm(needle, 400)
24
25needle.visualise()

If we run this code, the turtle will never be able to finish the drawing as each branch will consist of an arm with branches where each branch consists of an arm with brances where each branch…. etc. This will result in an infinite amount of details that the computer cannot handle, so the program will terminate with a RecursionError.

To ensure that the drawing is eventually completed, we need a variable that keeps track of which “recursion level” we are at. Then we can ensure that we stop after a certain amount of levels:

 1from turtlethread import Turtle
 2
 3def draw_arm(needle, length, rekursion_level):
 4    if rekursion_level == 0:
 5        needle.forward(length)
 6    else:
 7        # Move forward a little bit
 8        draw_arm(needle, 0.4*length, rekursion_level-1)
 9
10        # Draw the first "branch" pointing a little downwards
11        needle.right(45)
12        draw_arm(needle, length*0.3, rekursion_level-1)
13        needle.backward(length*0.3)
14        needle.left(45)
15        # Draw the second "branch" pointing a little upwards
16        needle.left(45)
17        draw_arm(needle, length*0.3, rekursion_level-1)
18        needle.backward(length*0.3)
19        needle.right(45)
20
21        # Move forward a little bit
22        draw_arm(needle, length*0.6, rekursion_level-1)
23
24needle = Turtle()
25with needle.running_stitch(30):
26    draw_arm(needle, 400, 2)
27
28needle.visualise()
A snowflake arm. The arm looks like the snowflake arm above, but each straight line has been replaced with a smaller arm.
Line 3:

Here we have added an input argument, recursion_level which keeps track of which level of recursion we are currently at.

Lines 4-5:

Here we check if we have reached level 0. In that case we should only draw a simple line segment without any branches. This ensures that the drawing is eventually completed.

Lines 8, 12, 17 og 22:

When we call the draw_arm function inside the draw_arm function, we input recursion_level - 1 to indicate that we have “used up” one level of recursion. This ensures that we can keep track of how many levels we have.

Line 26:

When we call the draw_arm function outside the function to draw an arm, we can specify how many levels of recursion we want.

Below are some examples of how the arm looks for different levels of recursion:

Recursion level 0

A straight line
Kode:
 1from turtlethread import Turtle
 2
 3def draw_arm(needle, length, rekursion_level):
 4    if rekursion_level == 0:
 5        needle.forward(length)
 6    else:
 7        # Move forward a little bit
 8        draw_arm(needle, 0.4*length, rekursion_level-1)
 9
10        # Draw the first "branch" pointing a little downwards
11        needle.right(45)
12        draw_arm(needle, length*0.3, rekursion_level-1)
13        needle.backward(length*0.3)
14        needle.left(45)
15        # Draw the second "branch" pointing a little upwards
16        needle.left(45)
17        draw_arm(needle, length*0.3, rekursion_level-1)
18        needle.backward(length*0.3)
19        needle.right(45)
20
21        # Move forward a little bit
22        draw_arm(needle, length*0.6, rekursion_level-1)
23
24needle = Turtle()
25with needle.running_stitch(30):
26    draw_arm(needle, 400, 0)
27
28needle.visualise()

Recursion level 1

The same snowflake arm shown in the first code example above.
Kode:
 1from turtlethread import Turtle
 2
 3def draw_arm(needle, length, rekursion_level):
 4    if rekursion_level == 0:
 5        needle.forward(length)
 6    else:
 7        # Move forward a little bit
 8        draw_arm(needle, 0.4*length, rekursion_level-1)
 9
10        # Draw the first "branch" pointing a little downwards
11        needle.right(45)
12        draw_arm(needle, length*0.3, rekursion_level-1)
13        needle.backward(length*0.3)
14        needle.left(45)
15        # Draw the second "branch" pointing a little upwards
16        needle.left(45)
17        draw_arm(needle, length*0.3, rekursion_level-1)
18        needle.backward(length*0.3)
19        needle.right(45)
20
21        # Move forward a little bit
22        draw_arm(needle, length*0.6, rekursion_level-1)
23
24needle = Turtle()
25with needle.running_stitch(30):
26    draw_arm(needle, 400, 1)
27
28needle.visualise()

Recursion level 2

A snowflake arm The arm looks like the snowflake arm above, but each straight line has been replaced with a smaller arm.
Kode:
 1from turtlethread import Turtle
 2
 3def draw_arm(needle, length, rekursion_level):
 4    if rekursion_level == 0:
 5        needle.forward(length)
 6    else:
 7        # Move forward a little bit
 8        draw_arm(needle, 0.4*length, rekursion_level-1)
 9
10        # Draw the first "branch" pointing a little downwards
11        needle.right(45)
12        draw_arm(needle, length*0.3, rekursion_level-1)
13        needle.backward(length*0.3)
14        needle.left(45)
15        # Draw the second "branch" pointing a little upwards
16        needle.left(45)
17        draw_arm(needle, length*0.3, rekursion_level-1)
18        needle.backward(length*0.3)
19        needle.right(45)
20
21        # Move forward a little bit
22        draw_arm(needle, length*0.6, rekursion_level-1)
23
24needle = Turtle()
25with needle.running_stitch(30):
26    draw_arm(needle, 400, 2)
27
28needle.visualise()

Try it yourself:

  • Update the code to draw snowflake arms with recursion levels 3 and 4.

Click here to see the snowflake arm and the code for recursion level 3:A snowflake arm The arm looks like the snowflake arm above, but each straight line has been replaced with a smaller arm. This process is repeated twice.
 1from turtlethread import Turtle
 2
 3def draw_arm(needle, length, rekursion_level):
 4    if rekursion_level == 0:
 5        needle.forward(length)
 6    else:
 7        # Move forward a little bit
 8        draw_arm(needle, 0.4*length, rekursion_level-1)
 9
10        # Draw the first "branch" pointing a little downwards
11        needle.right(45)
12        draw_arm(needle, length*0.3, rekursion_level-1)
13        needle.backward(length*0.3)
14        needle.left(45)
15        # Draw the second "branch" pointing a little upwards
16        needle.left(45)
17        draw_arm(needle, length*0.3, rekursion_level-1)
18        needle.backward(length*0.3)
19        needle.right(45)
20
21        # Move forward a little bit
22        draw_arm(needle, length*0.6, rekursion_level-1)
23
24needle = Turtle()
25with needle.running_stitch(30):
26    draw_arm(needle, 400, 3)
27
28needle.visualise()
Klikk her for å se snøfnugg-armen og koden for rekursjonsnivå 4:A snowflake arm The arm looks like the snowflake arm above, but each straight line has been replaced with a smaller arm. This process is repeated three times.
 1from turtlethread import Turtle
 2
 3def draw_arm(needle, length, rekursion_level):
 4    if rekursion_level == 0:
 5        needle.forward(length)
 6    else:
 7        # Move forward a little bit
 8        draw_arm(needle, 0.4*length, rekursion_level-1)
 9
10        # Draw the first "branch" pointing a little downwards
11        needle.right(45)
12        draw_arm(needle, length*0.3, rekursion_level-1)
13        needle.backward(length*0.3)
14        needle.left(45)
15        # Draw the second "branch" pointing a little upwards
16        needle.left(45)
17        draw_arm(needle, length*0.3, rekursion_level-1)
18        needle.backward(length*0.3)
19        needle.right(45)
20
21        # Move forward a little bit
22        draw_arm(needle, length*0.6, rekursion_level-1)
23
24needle = Turtle()
25with needle.running_stitch(30):
26    draw_arm(needle, 400, 4)
27
28needle.visualise()

Now that we have code to draw a snowflake arm, we can use repetition to draw a complete snowflake:

 1from turtlethread import Turtle
 2
 3def draw_arm(needle, length, rekursion_level):
 4    if rekursion_level == 0:
 5        needle.forward(length)
 6    else:
 7        # Move forward a little bit
 8        draw_arm(needle, 0.4*length, rekursion_level-1)
 9
10        # Draw the first "branch" pointing a little downwards
11        needle.right(45)
12        draw_arm(needle, length*0.3, rekursion_level-1)
13        needle.backward(length*0.3)
14        needle.left(45)
15        # Draw the second "branch" pointing a little upwards
16        needle.left(45)
17        draw_arm(needle, length*0.3, rekursion_level-1)
18        needle.backward(length*0.3)
19        needle.right(45)
20
21        # Move forward a little bit
22        draw_arm(needle, length*0.6, rekursion_level-1)
23
24needle = Turtle()
25with needle.running_stitch(30):
26    for arm in range(6):
27        draw_arm(needle, 400, 2)
28        needle.backward(400)
29        needle.right(60)
30
31needle.visualise()
A snowflake where each arm has recursion level 2.

Try it yourself:

  • Run the code and see what you get.

  • Update the code, so the branches point at a different angle (e.g. 60 degrees).

  • Create a modified version of the code where each arm has two sets of branches. Below is a visualisation of how this can look.

A snowflake arm with two sets of branches.
Click here to see an example of how the finished code can look
 1from turtlethread import Turtle
 2
 3def draw_arm(needle, length, rekursion_level):
 4    if rekursion_level == 0:
 5        needle.forward(length)
 6    else:
 7        # Move forward a little bit
 8        draw_arm(needle, 0.4*length, rekursion_level-1)
 9
10        # Draw the first "branch" pointing a little downwards
11        needle.right(45)
12        draw_arm(needle, length*0.3, rekursion_level-1)
13        needle.backward(length*0.3)
14        needle.left(45)
15        # Draw the second "branch" pointing a little upwards
16        needle.left(45)
17        draw_arm(needle, length*0.3, rekursion_level-1)
18        needle.backward(length*0.3)
19        needle.right(45)
20
21        # Move forward a little bit
22        draw_arm(needle, length*0.6, rekursion_level-1)
23
24needle = Turtle()
25with needle.running_stitch(30):
26    for arm in range(6):
27        draw_arm(needle, 400, 2)
28        needle.backward(400)
29        needle.right(60)
30
31needle.visualise()
An action shot of the embroidery machine making a snowflake.

Example fractals#

Try it yourself:

  • Create your own fractal! Below is a gallery of examples:

Example fractal 1

A different type of fractal snowflake called the "Koch snowflake". A different type of fractal snowflake called the "Koch snowflake".

Example fractal 2

A fractal tree. A fractal tree.

Example fractal 3

A fractal snowflake with two different arm types, one for even recursion levels and one for odd recursion levels. A fractal snowflake with two different arm types, one for even recursion levels and one for odd recursion levels.