Skip to content
0

Phyllotaxis: draw flowers using mathematics

📖 Background

Mathematics can describe many phenomena of the natural world. Excellent examples are the shape of snowflakes, the fractal geometry of Romanesco broccoli, and how self-similarity rules the growth of plants. In this competition, you will design floral patterns using scatter plots!

You don't need any mathematical background to participate in this competition, but if you want to know more about phyllotaxis, you can check out this article on Wikipedia.

💾 The (sample) data

In this competition, you will create scatter plots in two dimensions, representing flower petals. You'll generate a dataset with two variables; let's call them x and y.

To get you started, Here are 44 points in a circle with radius 1. As every (x, y) point should be in the unit circle, it follows that x² + y² = 1. You can get this using the super famous Pythagorean trigonometric identity which states that sin²(θ) + cos²(θ) = 1 for any real number θ.

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

# Create circle data to plot
t = np.arange(1, 45)
x = np.sin(t)
y = np.cos(t)
df = pd.DataFrame({'t': t, 'x': x, 'y': y})

# Make a scatter plot of points in a circle
plt.figure(figsize=(6, 6))  # Set plot size to 6x6 inches
plt.scatter(df['x'], df['y'])  # Create scatter plot
plt.xlabel('x')
plt.ylabel('y')
plt.show()

Plants can arrange their leaves in spirals. A spiral is a curve which starts from the origin and moves away from the origin as it revolves around it. In the plot above all our points are the same distance from the origin. A simple way to arrange them in a spiral is to multiply x and y by a factor which increases for each point. You could use t as that factor, as it meets these conditions, but you could do something more harmonious, e.g. the Golden Angle:

Golden Angle = π(3 − √5)

Both the Golden Ratio and the Golden Angle appear in unexpected places in nature. Apart of flower petals and plant leaves, you'll find them in seed heads, pine cones, sunflower seeds, shells, spiral galaxies, hurricanes, etc.

import numpy as np
import matplotlib.pyplot as plt

# Define the number of points for all layers
points_layer1 = 1000
points_layer2 = 1000
points_layer3 = 1000  
points_layer4 = 1000  
points_layer5 = 1000 
points_layer6 = 1000  
points_layer7 = 1000 

# Define the Golden Angle
angle = np.pi * (3 - np.sqrt(5))

# Generate values for the first layer
t1 = np.arange(1, points_layer1 + 1) * angle
x1 = np.sin(t1)
y1 = np.cos(t1)

# Generate values for the second layer with a different starting angle
starting_angle = np.pi / 3  # Different starting angle for the second layer
t2 = (np.arange(1, points_layer2 + 1) * angle) + starting_angle
x2 = np.sin(t2)
y2 = np.cos(t2)

# Generate values for the third layer with a different centre
centre_offset_x = 2  # New centre offset for x
centre_offset_y = 2  # New centre offset for y
starting_angle_layer3 = np.pi / 5
t3 = (np.arange(1, points_layer3 + 1) * angle) + starting_angle_layer3
x3 = np.sin(t3)
y3 = np.cos(t3)

# Generate values for the fourth layer with the same new centre as the third layer
starting_angle_layer4 = np.pi / 7  # Different starting angle for the fourth layer
t4 = (np.arange(1, points_layer4 + 1) * angle) + starting_angle_layer4
x4 = np.sin(t4)
y4 = np.cos(t4)

# Generate values for the fifth layer with a unique pattern
starting_angle_layer5 = np.pi / 4  # Different starting angle for the fifth layer
t5 = (np.arange(1, points_layer5 + 1) * angle) + starting_angle_layer5
x5 = np.sin(t5)
y5 = np.cos(t5)

# Generate values for the sixth layer with another unique pattern
starting_angle_layer6 = np.pi / 6  # Different starting angle for the sixth layer
t6 = (np.arange(1, points_layer6 + 1) * angle) + starting_angle_layer6
x6 = np.sin(t6)
y6 = np.cos(t6)

# Generate values for the seventh layer with yet another unique pattern
starting_angle_layer7 = np.pi / 8  # Different starting angle for the seventh layer
t7 = (np.arange(1, points_layer7 + 1) * angle) + starting_angle_layer7
x7 = np.sin(t7)
y7 = np.cos(t7)

# Make a scatter plot of points in a spiral for all layers
plt.figure(figsize=(12, 12))
plt.scatter(x1 * t1, y1 * t1, marker = "p", color='blue')
plt.scatter(x2 * t2, y2 * t2, marker = "o", color='red', alpha=0.6)
plt.scatter(x3 * t3, y3 * t3, marker = "*", color='green', alpha=0.6)
plt.scatter(x4 * t4, y4 * t4, color='purple', alpha=0.6)
plt.scatter(x5 * t5, y5 * t5, color='orange', alpha=0.6)
plt.scatter(x6 * t6, y6 * t6, color='pink', alpha=0.6)
plt.scatter(x7 * t7, y7 * t7, color='cyan', alpha=0.6)

plt.axis('off')

plt.show()
import matplotlib.pyplot as plt
import numpy as np

# Constants
n = 1000  # Number of points
layers = 3  # Number of layers
golden_angle = np.deg2rad(137.5)  # Convert to radians

# Initialize plot
plt.figure(figsize=(10, 10))

# Generate layers
for layer in range(1, layers + 1):
    theta = np.arange(n) * golden_angle
    r = np.sqrt(np.arange(n) * layer)  # Modified radius for each layer

    # Convert polar coordinates to Cartesian coordinates
    x = r * np.cos(theta)
    y = r * np.sin(theta)

    # Plot
    plt.scatter(x, y, c=theta, cmap=plt.cm.Dark2, alpha=1)  # Vary size and transparency by layer

plt.axis('off')  # Hide axes
plt.show()
# Barnsley Fern Fractal

def barnsley_fern(iterations):
    # Coefficients for the Barnsley Fern transformations
    coefficients = [
        (0.00, 0.00, 0.00, 0.16, 0.00, 0.00, 0.01),
        (0.85, 0.04, -0.04, 0.85, 0.00, 1.60, 0.85),
        (0.20, -0.26, 0.23, 0.22, 0.00, 1.60, 0.07),
        (-0.15, 0.28, 0.26, 0.24, 0.00, 0.44, 0.07)
    ]
    
    # Starting point
    x, y = 0, 0
    points = []
    
    for _ in range(iterations):
        r = np.random.random()
        sum_prob = 0
        for a, b, c, d, e, f, prob in coefficients:
            sum_prob += prob
            if r < sum_prob:
                x, y = a * x + b * y + e, c * x + d * y + f
                points.append((x, y))
                break
    
    return points

# Generate points
points = barnsley_fern(500000)
x, y = zip(*points)

# Plotting
plt.figure(figsize=(8, 10))
plt.scatter(x, y, s=0.1, color='green')
plt.axis('off')
plt.show()

# Function to rotate and scale points
def transform_points(points, angle_deg, scale):
    angle_rad = np.radians(angle_deg)
    transformation_matrix = np.array([
        [np.cos(angle_rad), -np.sin(angle_rad)],
        [np.sin(angle_rad), np.cos(angle_rad)]
    ]) * scale
    return np.dot(points, transformation_matrix)

# Generating rotated and scaled ferns
iterations = 100000
fern_points = np.array(barnsley_fern(iterations))

fig, ax = plt.subplots(figsize=(14, 14))

# Plot transformed ferns with the largest ones in the background
for i in range(12):

    largest_rotated_fern = transform_points(fern_points, i * 40, 1.9) 
    ax.scatter(largest_rotated_fern[:, 0], largest_rotated_fern[:, 1], s=0.1, color='#00d500')
    
for i in range(9):

    largest_rotated_fern = transform_points(fern_points, i * 40, 1.6)  
    ax.scatter(largest_rotated_fern[:, 0], largest_rotated_fern[:, 1], s=0.2, color='#00b000')

for i in range(6):

    larger_rotated_fern = transform_points(fern_points, i * 60, 1.3)  
    ax.scatter(larger_rotated_fern[:, 0], larger_rotated_fern[:, 1], s=0.3, color='#008a00')

for i in range(3):
    # Original size and color
    rotated_fern = transform_points(fern_points, i * 120, 1) 
    ax.scatter(rotated_fern[:, 0], rotated_fern[:, 1], s=0.5, color='darkgreen')

ax.set_aspect('equal')
plt.axis('off')
plt.show()
import numpy as np
import matplotlib.pyplot as plt

# Assuming the barnsley_fern function is defined above

def transform_points(points, angle_deg, scale, flip=False):
    angle_rad = np.radians(angle_deg)
    if flip:
        transformation_matrix = np.array([
            [-np.cos(angle_rad), np.sin(angle_rad)], 
            [np.sin(angle_rad), np.cos(angle_rad)]
        ]) * scale
    else:
        transformation_matrix = np.array([
            [np.cos(angle_rad), -np.sin(angle_rad)],
            [np.sin(angle_rad), np.cos(angle_rad)]
        ]) * scale
    return np.dot(points, transformation_matrix)

# Generate fern points
iterations = 100000
fern_points = np.array(barnsley_fern(iterations))

fig, ax = plt.subplots(figsize=(14, 14))

# Plot transformed ferns with the largest ones in the background
for i in range(12):
    # Right-leaning large ferns
    largest_rotated_fern = transform_points(fern_points, i * 30, 1.9)
    ax.scatter(largest_rotated_fern[:, 0], largest_rotated_fern[:, 1], s=0.1, color='#00d500')
    # Left-leaning large ferns
    left_leaning_fern = transform_points(fern_points, i * 30, 1.9, flip=True)
    ax.scatter(left_leaning_fern[:, 0], left_leaning_fern[:, 1], s=0.1, color='#00d500')

for i in range(9):
    # Right-leaning medium ferns
    larger_rotated_fern = transform_points(fern_points, i * 40, 1.6)
    ax.scatter(larger_rotated_fern[:, 0], larger_rotated_fern[:, 1], s=0.2, color='#00b000')
    # Left-leaning medium ferns
    left_leaning_fern = transform_points(fern_points, i * 40, 1.6, flip=True)
    ax.scatter(left_leaning_fern[:, 0], left_leaning_fern[:, 1], s=0.2, color='#00b000')

for i in range(6):
    # Right-leaning smaller ferns
    rotated_fern = transform_points(fern_points, i * 60, 1.3)
    ax.scatter(rotated_fern[:, 0], rotated_fern[:, 1], s=0.3, color='#008a00')
    # Left-leaning smaller ferns
    left_leaning_fern = transform_points(fern_points, i * 60, 1.3, flip=True)
    ax.scatter(left_leaning_fern[:, 0], left_leaning_fern[:, 1], s=0.3, color='#008a00')

for i in range(3):
    # Right-leaning smallest ferns
    smallest_rotated_fern = transform_points(fern_points, i * 120, 1)
    ax.scatter(smallest_rotated_fern[:, 0], smallest_rotated_fern[:, 1], s=0.01, color='darkgreen')
    # Left-leaning smallest ferns
    left_leaning_fern = transform_points(fern_points, i * 120, 1, flip=True)
    ax.scatter(left_leaning_fern[:, 0], left_leaning_fern[:, 1], s=0.01, color='darkgreen')

ax.set_aspect('equal')
plt.axis('off')
plt.show()
import numpy as np
import matplotlib.pyplot as plt

# Assuming the barnsley_fern function is defined above

def transform_points(points, angle_deg, scale, flip=False):
    angle_rad = np.radians(angle_deg)
    if flip:
        transformation_matrix = np.array([
            [-np.cos(angle_rad), np.sin(angle_rad)],  
            [np.sin(angle_rad), np.cos(angle_rad)]
        ]) * scale
    else:
        transformation_matrix = np.array([
            [np.cos(angle_rad), -np.sin(angle_rad)],
            [np.sin(angle_rad), np.cos(angle_rad)]
        ]) * scale
    return np.dot(points, transformation_matrix)

# Generate fern points
iterations = 100000
fern_points = np.array(barnsley_fern(iterations))

fig, ax = plt.subplots(figsize=(14, 14))

# Plot transformed ferns with the largest ones in the background
for i in range(12):
    # Right-leaning large ferns
    largest_rotated_fern = transform_points(fern_points, 15 + i * 30, 1.9)
    ax.scatter(largest_rotated_fern[:, 0], largest_rotated_fern[:, 1], s=0.5, color='#556B2F')
    # Left-leaning large ferns
    left_leaning_fern = transform_points(fern_points, -(15 + i * 30), 1.9, flip=True)
    ax.scatter(left_leaning_fern[:, 0], left_leaning_fern[:, 1], s=0.5, color='#556B2F')

for i in range(9):
    # Right-leaning medium ferns
    larger_rotated_fern = transform_points(fern_points, 20 + i * 40, 1.6)
    ax.scatter(larger_rotated_fern[:, 0], larger_rotated_fern[:, 1], s=0.5, color='#c69c11')
    # Left-leaning medium ferns
    left_leaning_fern = transform_points(fern_points, -(32 + i * 40), 1.6, flip=True)
    ax.scatter(left_leaning_fern[:, 0], left_leaning_fern[:, 1], s=0.5, color='#c69c11')

for i in range(6):
    # Right-leaning smaller ferns
    rotated_fern = transform_points(fern_points, 30 + i * 60, 1.3)
    ax.scatter(rotated_fern[:, 0], rotated_fern[:, 1], s=0.3, color='#006400')
    # Left-leaning smaller ferns
    left_leaning_fern = transform_points(fern_points, -(60 + i * 60), 1.3, flip=True)
    ax.scatter(left_leaning_fern[:, 0], left_leaning_fern[:, 1], s=0.3, color='#006400')

for i in range(3):
    # Right-leaning smallest ferns
    smallest_rotated_fern = transform_points(fern_points, 30 + i * 120, 1)
    ax.scatter(smallest_rotated_fern[:, 0], smallest_rotated_fern[:, 1], s=0.5, color='#78AB46')
    # Left-leaning smallest ferns
    left_leaning_fern = transform_points(fern_points, -(300 + i * 120), 1, flip=True)
    ax.scatter(left_leaning_fern[:, 0], left_leaning_fern[:, 1], s=0.5, color='#78AB46')

ax.set_aspect('equal')
plt.axis('off')
plt.show()
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm

# Assuming the barnsley_fern function is defined above

def transform_points(points, angle_deg, scale, flip=False):
    angle_rad = np.radians(angle_deg)
    if flip:
        transformation_matrix = np.array([
            [-np.cos(angle_rad), np.sin(angle_rad)], 
            [np.sin(angle_rad), np.cos(angle_rad)]
        ]) * scale
    else:
        transformation_matrix = np.array([
            [np.cos(angle_rad), -np.sin(angle_rad)],
            [np.sin(angle_rad), np.cos(angle_rad)]
        ]) * scale
    return np.dot(points, transformation_matrix)

# Generate fern points
iterations = 100000
fern_points = np.array(barnsley_fern(iterations))

fig, ax = plt.subplots(figsize=(14, 14))
colors = cm.autumn(np.linspace(0, 1, 12 + 9 + 6 + 3))  

# Plot transformed ferns with the largest ones in the background
index = 0
for i in range(12):
    color = colors[index]
    index += 1
    # Right-leaning large ferns
    largest_rotated_fern = transform_points(fern_points, 15 + i * 30, 1.9)
    ax.scatter(largest_rotated_fern[:, 0], largest_rotated_fern[:, 1], s=0.1, color=color)
    # Left-leaning large ferns
    left_leaning_fern = transform_points(fern_points, -(15 + i * 30), 1.9, flip=True)
    ax.scatter(left_leaning_fern[:, 0], left_leaning_fern[:, 1], s=0.1, color=color)

for i in range(9):
    color = colors[index]
    index += 1
    # Right-leaning medium ferns
    larger_rotated_fern = transform_points(fern_points, 20 + i * 40, 1.6)
    ax.scatter(larger_rotated_fern[:, 0], larger_rotated_fern[:, 1], s=0.2, color=color)
    # Left-leaning medium ferns
    left_leaning_fern = transform_points(fern_points, -(31 + i * 40), 1.6, flip=True)
    ax.scatter(left_leaning_fern[:, 0], left_leaning_fern[:, 1], s=0.2, color=color)

for i in range(6):
    color = colors[index]
    index += 1
    # Right-leaning smaller ferns
    rotated_fern = transform_points(fern_points, 30 + i * 60, 1.3)
    ax.scatter(rotated_fern[:, 0], rotated_fern[:, 1], s=0.3, color=color)
    # Left-leaning smaller ferns
    left_leaning_fern = transform_points(fern_points, -(60 + i * 60), 1.3, flip=True)
    ax.scatter(left_leaning_fern[:, 0], left_leaning_fern[:, 1], s=0.3, color=color)

for i in range(3):
    color = colors[index]
    index += 1
    # Right-leaning smallest ferns
    smallest_rotated_fern = transform_points(fern_points, 30 + i * 120, 1)
    ax.scatter(smallest_rotated_fern[:, 0], smallest_rotated_fern[:, 1], s=0.5, color=color)
    # Left-leaning smallest ferns
    left_leaning_fern = transform_points(fern_points, -(300 + i * 120), 1, flip=True)
    ax.scatter(left_leaning_fern[:, 0], left_leaning_fern[:, 1], s=0.5, color=color)

ax.set_aspect('equal')
plt.axis('off')
plt.show()

The Geometry of Nature: Crafting Mandala Designs with Python and Fractals

Imagine you're an artist with a palette of mathematical tools, and your canvas is the Python environment. Your main tool is the transformation function that shifts, scales, and flips the basic points of a Barnsley Fern—a well-known fractal that resembles a natural fern. Using these transformations, you create a series of ferns, each modified slightly in orientation, size, and position to form a complex, layered, mandala-like pattern.

Explaining the Code

  1. Function Definition: transform_points function: This function takes a set of points (the coordinates of the basic fern shape) and applies geometric transformations. These transformations include rotation (defined by angle_deg), scaling (scale), and optional horizontal flipping (flip). The rotation and scaling are achieved through a transformation matrix which is a standard technique in computer graphics for manipulating 2D shapes.

  2. Generating the Base Fern Points: The barnsley_fern function (presumed previously defined) generates the fractal points of the basic fern shape through a stochastic algorithm. The variable fern_points stores these coordinates.

  3. Plotting:

    • The code uses matplotlib to plot multiple layers of transformed ferns. Each layer has its distinct properties (size and orientation) and color, stacking from the largest in the background to the smallest in the foreground, creating depth.
    • Each set of transformations and plotting calls are looped multiple times to create symmetrical pairs of ferns (right-leaning and left-leaning), achieving the radial symmetry characteristic of mandalas.
  4. Transformation Details:

    • The loops vary the angle and scale incrementally. Larger angles and scales are used for ferns that are meant to appear in the background, decreasing in size and angle variation as they move towards the foreground. This staged design enhances the visual complexity and texture.
    • The use of colors like deep green, yellow, and other shades helps differentiate the layers and adds visual interest.
  5. Final Touches:

    • Aspect ratio is set to equal to maintain the circular symmetry.
    • The axes are turned off to highlight the mandala without any distraction from grid lines or coordinates.

Resulting Image

The final image is a multi-layered, richly detailed, symmetrical mandala that uses fractal geometry as its base. It's a perfect blend of mathematics and art, showcasing how code can be used creatively to produce intricate designs that are not only visually appealing but also mathematically fascinating. This code is a beautiful example of how programming can transcend traditional boundaries to explore the interplay between nature-inspired patterns and digital art. Each iteration and transformation brings the static points of a fractal to life, creating a dynamic, organic artwork that is both precise and free-flowing.

⌛️ Time is ticking. Good luck!