Un arbre fractal de Pythagore est une figure plutôt esthétique dans certaines conditions.

Nous allons voir ce que c’est…

Je précise que cet article est rédigé en s’appuyant sur la vidéo de Mickaël Launay visible sur Youtube.

Notion de similitude

Considérons:

  • un point \(\Omega\) du plan,
  • un réel \(k\),
  • un ange $\alpha$.

À tout point $A$ du plan, on peut considérer successivement:

  • son image $A’$ par la rotation de centre $\Omega$ et d’angle $\alpha$,
  • puis $A”$ l’image de $A’$ par l’homothétie de centre $\Omega$ et de rapport $k$.

À l’aide de la trigonométrie, on démontre que: $$\left.\begin{array}{l}\Omega(x_\Omega;y_\Omega)\\A(a;b)\end{array}\right\rbrace \Longrightarrow\begin{cases}A'(x_\Omega+(a-x_\Omega)\cos\alpha-(b-y_\Omega)\sin\alpha; y_\Omega+(a-x_\Omega)\sin\alpha+(b-y_\Omega)\cos\alpha)\\A”(x_\Omega + k(a-x_\Omega)\cos\alpha-k(b-y_\Omega)\sin\alpha;y_\Omega+k(a-x_\Omega)\sin\alpha+k(b-y_\Omega)\cos\alpha)\end{cases}$$

On dit ici que $A”$ est obtenu à partir du point $A$ par similitude de centre $\Omega$, d’angle $\alpha$ et de rapport $k$.

Introduction à l’arbre de Pythagore

Partons d’un carré $ABCD$ de côté $L$, puis traçons un triangle rectangle $ECD$ tel que $\widehat{CDE}=\theta$, sur lequel nous traçons des carrés:

arbre fractal de Pythagore: étape 1
  • $E$ est obtenu par similitude de centre $D$, d’angle $\theta$ et de rapport $k=L\cos\theta$ car $\dfrac{DE}{DC}=\cos\theta$.
  • $F$ est obtenu à partir de $D$ par rotation de centre $E$ et d’angle $-\frac{\pi}{2}$.
  • $G$ est obtenu à partir de $E$ par rotation de centre $D$ et d’angle $\frac{\pi}{2}$.

On peut construire de même le carré sur $|EC]$.

Construction de l’arbre de Pythagore

L’idée est de reproduire ce qui vient d’être expliqué sur les carrés obtenus, et sur ceux qui vont l’être par la suite. Nous allons faire cela à l’aide de Python:

from math import pi, cos, sin
from PIL import Image, ImageDraw 

width = 1748
height = 1240

tree = Image.new('RGB', (width,height), (255,255,255))
draw = ImageDraw.Draw(tree,'RGBA')

theta = pi/180 * 40 # 40 degrés
L = 200 # longueur de l'arête du premier carré
A = (width/2 - L/2 , height-250)
B = (width/2 + L/2 , height-250)

def sim(centre, point, angle, k):
    # angle devient "-angle" dans les sinus à cause de la logique graphique de PIL
    x = centre[0] + k*(point[0]-centre[0])*cos(angle)-k*(point[1]-centre[1])*sin(-angle)
    y = centre[1] + k*(point[0]-centre[0])*sin(-angle)+k*(point[1]-centre[1])*cos(angle)
    
    return x,y

def arbre(A,B,angle,n):
    if n==0: return 
    
    C = sim(B,A,-pi/2,1)
    D = sim(A,B,pi/2,1)
    E = sim(D,C,angle,cos(angle))
    
    draw.polygon([A,B,C,D], fill=(0,0,0,125))
    
    arbre(D, E, angle, n-1)
    arbre(E, C, angle, n-1)
    

arbre(A,B,theta,10)
tree.show()
arbre fractal de Pythagore: version 1

Si l’on souhaite mettre un peu de couleur, on peut utiliser le script suivant:

from math import pi, cos, sin
from PIL import Image, ImageDraw 
from random import randint

width = 1748
height = 1240

tree = Image.new('RGB', (width,height), (255,255,255))
draw = ImageDraw.Draw(tree,'RGBA')

theta = pi/180 * 40 # 40 degrés
L = 200 # longueur de l'arête du premier carré
A = (width/2 - L/2 , height-250)
B = (width/2 + L/2 , height-250)

def sim(centre, point, angle, k):
    # angle devient "-angle" dans les sinus à cause de la logique graphique de PIL
    x = centre[0] + k*(point[0]-centre[0])*cos(angle)-k*(point[1]-centre[1])*sin(-angle)
    y = centre[1] + k*(point[0]-centre[0])*sin(-angle)+k*(point[1]-centre[1])*cos(angle)
    
    return x,y

def arbre(A,B,angle,n):
    if n==0: return 
    
    C = sim(B,A,-pi/2,1)
    D = sim(A,B,pi/2,1)
    E = sim(D,C,angle,cos(angle))
    
    draw.polygon([A,B,C,D], fill=(randint(0,255),randint(0,255),randint(0,255),125))
    
    arbre(D, E, angle, n-1)
    arbre(E, C, angle, n-1)
    

arbre(A,B,theta,10)
tree.show()
arbre fractal de Pythagore en couleurs

L’arbre est-il “infini” ?

Nous allons noter $C_n$ le centre des carrés allant vers la gauche.

On peut observer que $$\frac{C_2C_1}{C_1C_0}=\cos\theta.$$ En effet, $DEFG$ est l’image de $ABCD$ ($DC$ se transforme en $DG$) par la similitude de centre $D$, d’angle $\theta+\frac{\pi}{2}$ et de rapport $k=\cos\theta$ (par construction). De même, le carré vert est l’image du carré rouge par la similitude de centre $G$ et de mêmes angles et rapport.

Toutes les longueurs sont donc multipliées par $\cos\theta$ pour passer d’un carré au suivant.

La suite $(C_{n-1}C_n)$ est donc géométrique de raison $\cos\theta$.

Calculons alors longueur:$$\begin{align*}L_n & = \sum_{k=0}^{n-1} C_kC_{k+1}= C_0C_1 + C_1C_2 + \cdots + C_{n-1}C_{n}\\& = C_0C_1 \times (1 + \cos\theta + \cos^2\theta + \cdots + \cos^n\theta)\\& = C_0C_1\times\dfrac{1-\cos^{n+1}\theta}{1-\cos\theta}.\end{align*}$$

Comme $0 \leqslant \cos\theta < 1$, la limite de $L_n$ quand $n$ tend vers $+\infty$ est finie et vaut $\frac{C_0C_1}{1-\cos\theta}$.

L’arbre de Pythagore (noté $\mathcal{A}$) ne peut donc pas être \og infini \fg{} (dans le sens où la distance entre $C_0$ et $C_\infty$ ne peut pas être infinie).

On appellera diamètre le maximum de la distance entre $C_0$ et $C_n$, pour tout entier naturel $n$: $$\text{diam}(\mathcal{A})=\max\{C_0C_n,\ n\geqslant1\}.$$

On peut ainsi modifier le script précédent pour déterminer une valeur approchée du diamètre (en pixels) de l’arbre:

from math import pi, cos, sin
from PIL import Image, ImageDraw, ImageFont
from random import randint

width = 1748
height = 1240

tree = Image.new('RGB', (width,height), (255,255,255))
draw = ImageDraw.Draw(tree,'RGBA')

theta = pi/180 * 40 # 40 degrés
L = 200 # longueur de l'arête du premier carré
A = (width/2 - L/2 , height-250)
B = (width/2 + L/2 , height-250)

centres = []

def sim(centre, point, angle, k):
    # angle devient "-angle" dans les sinus à cause de la logique graphique de PIL
    x = centre[0] + k*(point[0]-centre[0])*cos(angle)-k*(point[1]-centre[1])*sin(-angle)
    y = centre[1] + k*(point[0]-centre[0])*sin(-angle)+k*(point[1]-centre[1])*cos(angle)
    
    return x,y
 
def arbre(A,B,angle,n):
    if n==0: return 
    
    global centres
    
    C = sim(B,A,-pi/2,1)
    D = sim(A,B,pi/2,1)
    E = sim(D,C,angle,cos(angle))
    
    centres.append(((A[0]+C[0])/2,(A[1]+C[1])/2))
    
    draw.polygon([A,B,C,D], fill=(randint(0,255),randint(0,255),randint(0,255),125))
    
    arbre(D, E, angle, n-1)
    arbre(E, C, angle, n-1)
    
def diam():
    global centres
    origine = centres[0] # premier centre
    d = 0
    
    for c in centres:
        if c != origine:
            distance = (origine[0]-c[0])**2 + (origine[1]-c[1])**2
            if distance > d:
                d = distance
                faraway = c
                
    return d**0.5, faraway

arbre(A,B,theta,15)
diametre = f"Diamètre de l'arbre: {diam()[0]}"
draw.text((10,10), diametre, font=ImageFont.truetype("arial.ttf", size=30), fill=(0, 0, 0, 255))
# on trace le diamètre
draw.line([centres[0],diam()[1]],fill=(100,100,100),width=1) 

tree.show()

Ce dernier exemple est intéressant (j’ai pris $n=20$ et un angle initial de 22.5 degrés): on aimerait que le nombre d’itérations soit plus grand afin que la “spirale” continue davantage… Nous allons donc légèrement modifier le script afin de le permettre car avec ce que nous avons, pour $n=25$, ça plante…

from math import pi, cos, sin
from PIL import Image, ImageDraw, ImageFont
from random import randint

width = 1748
height = 1240

tree = Image.new('RGB', (width,height), (255,255,255))
draw = ImageDraw.Draw(tree,'RGBA')

theta = pi/180 * 22.5
L = 150 # longueur de l'arête du premier carré
A = (width/2 - L/2 +100, height-250)
B = (width/2 + L/2 +100, height-250)

centres = []

def sim(centre, point, angle, k):
    # angle devient "-angle" dans les sinus à cause de la logique graphique de PIL
    x = centre[0] + k*(point[0]-centre[0])*cos(angle)-k*(point[1]-centre[1])*sin(-angle)
    y = centre[1] + k*(point[0]-centre[0])*sin(-angle)+k*(point[1]-centre[1])*cos(angle)
    
    return x,y
 
def arbre(A,B,angle,n):
    if (n==0) or (length(A,B)<8): return 
    
    global centres
    
    C = sim(B,A,-pi/2,1)
    D = sim(A,B,pi/2,1)
    E = sim(D,C,angle,cos(angle))
    
    centres.append(((A[0]+C[0])/2,(A[1]+C[1])/2))
    
    draw.polygon([A,B,C,D], fill=(randint(0,255),randint(0,255),randint(0,255),125))
    
    arbre(D, E, angle, n-1)
    arbre(E, C, angle, n-1)

def length(A,B):
    return (A[0]-B[0])**2 + (A[1]-B[1])**2
    
def diam():
    global centres
    origine = centres[0] # premier centre
    d = 0
    
    for c in centres:
        if c != origine:
            distance = length(origine,c) #(origine[0]-c[0])**2 + (origine[1]-c[1])**2
            if distance > d:
                d = distance
                faraway = c
                
    return d**0.5, faraway

arbre(A,B,theta,50)
diametre = f"Diamètre de l'arbre: {diam()[0]}"
draw.text((10,10), diametre, font=ImageFont.truetype("arial.ttf", size=30), fill=(0, 0, 0, 255))
draw.line([centres[0],diam()[1]],fill=(100,100,100),width=1) 

tree.show()

Nous avons introduit:

  • une nouvelle fonction “length” qui permet de renvoyer le carré de la distance entre deux points;
  • un test dans la fonction “arbre“: on n’exécute la fonction que si $AB^2 > 8$ (par exemple). En effet, inutile d’aller plus loin si l’on ne voit plus les points.

Cette modification donne:


0 0 votes
Évaluation de l'article
S’abonner
Notification pour
guest
0 Commentaires
Le plus ancien
Le plus récent Le plus populaire
Commentaires en ligne
Afficher tous les commentaires
0
Nous aimerions avoir votre avis, veuillez laisser un commentaire.x