Fichier:Application contractante animation.gif
Taille de cet aperçu : 300 × 350 pixels.
Fichier d’origine (300 × 350 pixels, taille du fichier : 213 kio, type MIME : image/gif, en boucle, 226 trames, 14 s)
Ce fichier et sa description proviennent de Wikimedia Commons.
Description
| DescriptionApplication contractante animation.gif |
Français : Itération de l'application -contractante depuis deux points initiaux. Les deux orbites convergent vers l'unique point fixe |
| Date | |
| Source | Travail personnel |
| Auteur ou autrice | Global Donald |
Conditions d’utilisation
Moi, en tant que détenteur des droits d’auteur sur cette œuvre, je la publie sous la licence suivante :
| Ce fichier est dans le domaine public selon les termes de la licence Creative Commons CC0 1.0 Universel. | |
| La personne qui a associé une œuvre avec cet acte l’a placée dans le domaine public en renonçant mondialement à tous ses droits sur cette œuvre en vertu des lois relatives au droit d’auteur, ainsi qu’à tous les droits juridiques connexes et voisins qu’elle possédait sur l’œuvre, sans autre limite que celles imposées par la loi. Vous pouvez copier, modifier, distribuer et utiliser cette œuvre, y compris à des fins commerciales, sans qu’il soit nécessaire d’en demander la permission.
http://creativecommons.org/publicdomain/zero/1.0/deed.enCC0Creative Commons Zero, Public Domain Dedicationfalsefalse |
Source code
Python script used to generate this animation
|
|---|
import numpy as np
from PIL import Image, ImageDraw, ImageFont
DOSSIER = os.path.dirname(os.path.abspath(__file__))
SORTIE = os.path.join(DOSSIER, "Application_contractante_animation.gif")
W, H = 300, 350
SS = 3
FPS = 20
DUREE = 14.0
NFRAMES = int(DUREE * FPS)
INTRO = 1.5
DUREE_PAS = 1.55
NITER = 6
FIN_PAS = INTRO + NITER * DUREE_PAS
A0, B0 = 2.7, -2.2
K = 2.0 / 3.0
FOND = (251, 250, 247, 255)
BLANC = (255, 255, 255, 255)
ENCRE = (31, 35, 42, 255)
GRIS = (104, 111, 120, 255)
GRIS_CLAIR = (218, 219, 214, 255)
GRILLE = (233, 233, 228, 255)
TEAL = (7, 122, 117, 255)
TEAL_SOMBRE = (5, 91, 88, 255)
ROUGE = (217, 79, 48, 255)
ROUGE_PALE = (246, 190, 171, 255)
Bleu = (39, 111, 191, 255)
Bleu_PALE = (183, 211, 243, 255)
OR = (222, 145, 32, 255)
OR_SOMBRE = (151, 91, 13, 255)
POL = "/usr/share/fonts/truetype/dejavu/"
FTITRE = ImageFont.truetype(POL + "DejaVuSans-Bold.ttf", 14 * SS)
FSOUS = ImageFont.truetype(POL + "DejaVuSans.ttf", 10.5 * SS)
FPETIT = ImageFont.truetype(POL + "DejaVuSans.ttf", 8.0 * SS)
FPETITG = ImageFont.truetype(POL + "DejaVuSans-Bold.ttf", 8.2 * SS)
FFORM = ImageFont.truetype(POL + "DejaVuSerif.ttf", 10.0 * SS)
FMINI = ImageFont.truetype(POL + "DejaVuSans.ttf", 6.8 * SS)
PLOT = (34.0, 56.0, 266.0, 288.0) # x0, y0, x1, y1 écran
ECHELLE = (PLOT[2] - PLOT[0]) / 6.0
OX = (PLOT[0] + PLOT[2]) / 2.0
OY = (PLOT[1] + PLOT[3]) / 2.0
def f(x):
return (x + math.sin(x)) / 3.0
def orbite(x0):
vals = [float(x0)]
for _ in range(NITER):
vals.append(f(vals[-1]))
return vals
ORB_A = orbite(A0)
ORB_B = orbite(B0)
DIST = [abs(a - b) for a, b in zip(ORB_A, ORB_B)]
def P(x, y):
return (x * SS, y * SS)
def B(x0, y0, x1, y1):
return (x0 * SS, y0 * SS, x1 * SS, y1 * SS)
def XY(x, y):
return (OX + ECHELLE * x, OY - ECHELLE * y)
def borne(x, a=0.0, b=1.0):
return max(a, min(b, x))
def doux(a, b, x):
q = borne((x - a) / (b - a))
return q * q * (3.0 - 2.0 * q)
def lerp(a, b, q):
return a + (b - a) * q
def lerp_p(p, q, t):
return (lerp(p[0], q[0], t), lerp(p[1], q[1], t))
def trait(d, pts, couleur, largeur=2.0):
if len(pts) >= 2:
d.line([P(x, y) for x, y in pts], fill=couleur,
width=max(1, int(round(largeur * SS))), joint="curve")
def point(d, centre, rayon, couleur, bord=BLANC, largeur=1.2):
x, y = centre
d.ellipse(B(x - rayon, y - rayon, x + rayon, y + rayon),
fill=couleur, outline=bord,
width=max(1, int(round(largeur * SS))))
def etat_animation(t):
"""Indice du pas courant, fraction dans le pas et nombre affiché."""
if t < INTRO:
return 0, 0.0, 0
if t >= FIN_PAS:
return NITER, 1.0, NITER
idx = int((t - INTRO) // DUREE_PAS)
u = ((t - INTRO) - idx * DUREE_PAS) / DUREE_PAS
n_aff = idx + (1 if u >= 0.94 else 0)
return idx, float(u), n_aff
def chemin_orbite(vals, idx, u):
"""
Retourne (segments terminés, segment courant partiel, point mobile,
abscisse scalaire courante).
Les segments sont dans le plan (x,y) mathématique.
"""
segments = []
# Point de départ du pas j.
def depart(j):
return (vals[j], 0.0) if j == 0 else (vals[j], vals[j])
for j in range(min(idx, NITER)):
s = depart(j)
v = (vals[j], vals[j + 1])
h = (vals[j + 1], vals[j + 1])
segments.extend([(s, v), (v, h)])
if idx >= NITER:
p = (vals[-1], vals[-1])
return segments, None, p, vals[-1]
s = depart(idx)
v = (vals[idx], vals[idx + 1])
h = (vals[idx + 1], vals[idx + 1])
qv = doux(0.05, 0.45, u)
qh = doux(0.52, 0.92, u)
courant = []
if qv > 0:
courant.append((s, lerp_p(s, v, qv)))
if qh > 0:
courant.append((v, lerp_p(v, h, qh)))
if u < 0.52:
mobile = lerp_p(s, v, qv)
scalaire = vals[idx]
else:
mobile = lerp_p(v, h, qh)
scalaire = lerp(vals[idx], vals[idx + 1], qh)
return segments, courant, mobile, scalaire
def dessiner_fond(img, t):
d = ImageDraw.Draw(img, "RGBA")
# Ombre et panneau du graphique.
d.rounded_rectangle(B(PLOT[0] + 2, PLOT[1] + 3, PLOT[2] + 2, PLOT[3] + 3),
radius=8 * SS, fill=(226, 224, 217, 255))
d.rounded_rectangle(B(*PLOT), radius=8 * SS, fill=BLANC,
outline=GRIS_CLAIR, width=1 * SS)
# Grille.
for v in (-2, -1, 1, 2):
x, _ = XY(v, 0)
trait(d, [(x, PLOT[1] + 5), (x, PLOT[3] - 5)], GRILLE, 0.8)
_, y = XY(0, v)
trait(d, [(PLOT[0] + 5, y), (PLOT[2] - 5, y)], GRILLE, 0.8)
# Axes.
trait(d, [(PLOT[0] + 4, OY), (PLOT[2] - 4, OY)], (178, 183, 190, 255), 1.0)
trait(d, [(OX, PLOT[1] + 4), (OX, PLOT[3] - 4)], (178, 183, 190, 255), 1.0)
for v in (-3, 0, 3):
x, _ = XY(v, 0)
d.text(P(x, PLOT[3] - 14), str(v), font=FMINI, fill=GRIS, anchor="ma")
_, y = XY(0, v)
if v != 0:
d.text(P(PLOT[0] + 6, y), str(v), font=FMINI, fill=GRIS, anchor="lm")
# Diagonale y=x, en tirets.
xx = np.linspace(-3, 3, 25)
for i in range(0, len(xx) - 1, 2):
trait(d, [XY(xx[i], xx[i]), XY(xx[i + 1], xx[i + 1])],
(158, 164, 171, 255), 1.2)
d.text(P(*XY(2.47, 2.68)), "y = x", font=FPETIT, fill=GRIS, anchor="ma")
# Courbe de f.
xs = np.linspace(-3, 3, 241)
trait(d, [XY(float(x), f(float(x))) for x in xs], TEAL, 2.4)
d.text(P(43, 66), "f(x) = (x + sin x)/3", font=FFORM,
fill=TEAL_SOMBRE, anchor="la")
d.text(P(43, 80), "k = 2/3", font=FPETITG, fill=TEAL_SOMBRE, anchor="la")
def dessiner_orbite(d, vals, idx, u, couleur, pale):
segments, courant, mobile, scalaire = chemin_orbite(vals, idx, u)
for a, b in segments:
trait(d, [XY(*a), XY(*b)], couleur, 1.8)
if courant:
for a, b in courant:
trait(d, [XY(*a), XY(*b)], pale, 4.2)
trait(d, [XY(*a), XY(*b)], couleur, 2.0)
# Itérés déjà atteints sur la diagonale.
nb = min(idx + (1 if u >= 0.94 else 0), NITER) if idx < NITER else NITER
for j in range(1, nb + 1):
point(d, XY(vals[j], vals[j]), 2.6, couleur)
point(d, XY(*mobile), 4.6, couleur, largeur=1.4)
return scalaire
def sous_titre(t, idx):
if t < INTRO:
return "Deux départs, une même application"
if t < FIN_PAS:
return "À chaque pas, l’écart est multiplié par au plus 2/3"
return "Point fixe unique : x* = 0"
def faire_image(t):
img = Image.new("RGBA", (W * SS, H * SS), FOND)
dessiner_fond(img, t)
d = ImageDraw.Draw(img, "RGBA")
idx, u, n_aff = etat_animation(t)
# Orbites.
a_cur = dessiner_orbite(d, ORB_A, idx, u, ROUGE, ROUGE_PALE)
b_cur = dessiner_orbite(d, ORB_B, idx, u, Bleu, Bleu_PALE)
# Points initiaux sur l'axe des abscisses et étiquettes au début.
point(d, XY(A0, 0), 3.4, ROUGE)
point(d, XY(B0, 0), 3.4, Bleu)
if t < 3.4:
d.text(P(*XY(A0, 0.18)), "a₀", font=FPETITG, fill=ROUGE, anchor="ma")
d.text(P(*XY(B0, 0.18)), "b₀", font=FPETITG, fill=Bleu, anchor="ma")
# Point fixe, avec un léger anneau pulsant dans le final.
if t >= FIN_PAS:
phase = (t - FIN_PAS) % 1.2
r = 7.0 + 5.0 * (phase / 1.2)
coul = tuple(int(round(lerp(c, 255, phase / 1.2))) for c in OR[:3]) + (255,)
x0, y0 = XY(0, 0)
d.ellipse(B(x0 - r, y0 - r, x0 + r, y0 + r), outline=coul,
width=max(1, int(round(1.2 * SS))))
point(d, XY(0, 0), 4.2, OR, largeur=1.3)
d.text(P(OX + 8, OY + 7), "x* = 0", font=FPETITG,
fill=OR_SOMBRE, anchor="la")
# Titre, sous-titre et compteur.
d.text(P(16, 11), "APPLICATION CONTRACTANTE", font=FTITRE,
fill=ENCRE, anchor="la")
d.text(P(284, 13), f"n = {n_aff}", font=FPETITG, fill=GRIS, anchor="ra")
d.text(P(150, 33), sous_titre(t, idx), font=FSOUS,
fill=ENCRE, anchor="ma")
# Règle des distances, alignée sur l'échelle du graphique.
yregle = 318.0
d.text(P(16, 297), "distance entre les orbites", font=FPETIT,
fill=GRIS, anchor="la")
dist = abs(a_cur - b_cur)
d.text(P(284, 297), f"d = {dist:.2f}".replace(".", ","),
font=FPETITG, fill=ENCRE, anchor="ra")
trait(d, [(PLOT[0], yregle), (PLOT[2], yregle)], (190, 194, 198, 255), 1.0)
for v in (-3, 0, 3):
x = XY(v, 0)[0]
trait(d, [(x, yregle - 3), (x, yregle + 3)], (160, 165, 171, 255), 1.0)
xa = XY(a_cur, 0)[0]
xb = XY(b_cur, 0)[0]
ybra = yregle - 9
trait(d, [(xb, ybra), (xa, ybra)], ENCRE, 1.7)
trait(d, [(xb, ybra - 3), (xb, ybra + 3)], ENCRE, 1.4)
trait(d, [(xa, ybra - 3), (xa, ybra + 3)], ENCRE, 1.4)
point(d, (xa, yregle), 3.8, ROUGE)
point(d, (xb, yregle), 3.8, Bleu)
if n_aff >= 1:
ratio = DIST[n_aff] / DIST[n_aff - 1]
txt = f"dernier pas : × {ratio:.2f}".replace(".", ",")
d.text(P(150, 329), txt, font=FPETIT, fill=GRIS, anchor="ma")
else:
d.text(P(150, 329), "d₀ = 4,90", font=FPETIT, fill=GRIS, anchor="ma")
return img.resize((W, H), Image.Resampling.LANCZOS).convert("RGB")
def encoder(frames):
echant = [fr.resize((150, 175)) for fr in frames[::10][:28]]
mos = Image.new("RGB", (7 * 150, 4 * 175))
for i, fr in enumerate(echant):
mos.paste(fr, ((i % 7) * 150, (i // 7) * 175))
pal = mos.quantize(colors=160, method=Image.Quantize.MEDIANCUT)
pframes = [fr.quantize(palette=pal, dither=Image.Dither.NONE) for fr in frames]
pframes[0].save(SORTIE, save_all=True, append_images=pframes[1:],
duration=[50] * len(pframes), loop=0, optimize=True)
def aperçus():
temps = [0.0, 1.0, 2.25, 4.0, 6.5, 9.5, 11.5, 13.4]
noms = ["départ", "intro", "pas 1", "pas 2", "milieu", "pas 6", "final", "boucle"]
planche = Image.new("RGB", (4 * W, 2 * (H + 22)), (235, 235, 235))
d = ImageDraw.Draw(planche)
ft = ImageFont.truetype(POL + "DejaVuSans.ttf", 11)
for i, (t, nom) in enumerate(zip(temps, noms)):
fr = faire_image(t)
x, y = (i % 4) * W, (i // 4) * (H + 22)
d.text((x + 6, y + 4), f"t={t:.2f} — {nom}", font=ft, fill=(0, 0, 0))
planche.paste(fr, (x, y + 22))
if nom in ("pas 1", "final"):
fr.save(os.path.join(DOSSIER, f"design_{nom.replace(' ', '_')}.png"))
planche.save(os.path.join(DOSSIER, "design_planche.png"))
def main():
if "--previews" in sys.argv:
aperçus()
print("aperçus écrits")
print("orbites A", [round(x, 6) for x in ORB_A])
print("orbites B", [round(x, 6) for x in ORB_B])
print("distances", [round(x, 6) for x in DIST])
print("ratios", [round(DIST[i] / DIST[i - 1], 6) for i in range(1, len(DIST))])
return
frames = []
for i in range(NFRAMES):
frames.append(faire_image(i / FPS))
if i % 25 == 0 or i == NFRAMES - 1:
print(f"rendu {i+1}/{NFRAMES}", flush=True)
# Fondu enchaîné final vers le premier état, sans première image vide.
nfondu = int(0.5 * FPS)
for j in range(nfondu):
idx = NFRAMES - nfondu + j
q = doux(0.0, 1.0, (j + 1) / nfondu)
frames[idx] = Image.blend(frames[idx], frames[0], q)
encoder(frames)
with Image.open(SORTIE) as im:
print(f"GIF : {SORTIE}")
print(f"{im.width}x{im.height}, {im.n_frames} images stockées, "
f"{os.path.getsize(SORTIE):,} octets, "
f"produit={im.width*im.height*im.n_frames:,}")
if __name__ == "__main__":
main()
|
Ce fichier n'est pas catégorisé.
S'il vous plaît, aidez à améliorer ce fichier en ajoutant une ou plusieurs catégories, en relation avec d'autres fichiers (comment ?), et il sera donc plus facile à retrouver parmi tous les fichiers parallèles.
Merci d'avertir l'utilisateur avec : {{subst:Please link images|File:Application contractante animation.gif}} ~~~~ |
Légendes
Itération de l'application <math>2/3</math>-contractante <math>f(x) = (x + \sin x)/3</math> depuis deux points initiaux. Les deux orbites convergent vers l'unique point fixe <math>0</math>
Éléments décrits dans ce fichier
dépeint
27 octobre 2026
218 041 octet
350 pixel
300 pixel
image/gif
0acea03d45d3009e28483bd5cb7dd31b153ef664
Historique du fichier
Cliquer sur une date et heure pour voir le fichier tel qu'il était à ce moment-là.
| Date et heure | Vignette | Dimensions | Utilisateur | Commentaire | |
|---|---|---|---|---|---|
| actuel | 27 octobre 2026 à 15:23 | 300 × 350 (213 kio) | Global Donald | Uploaded own work with UploadWizard |
Utilisation du fichier
La page suivante utilise ce fichier :
Ce document provient de « https://fr.wikipedia.org/wiki/Fichier:Application_contractante_animation.gif ».