Outils pour utilisateurs

Outils du site


tp:python:julia

📓 Ensemble de Julia

Ensemble de Julia (Solution)

Nous proposons ici de tracer des ensembles de Julia en noir et blanc, puis en couleur.

Prérequis

La réalisation de ce travail nécessite d'avoir compris :

  • ce qu'est l'ensemble de Julia (vu en travaux dirigés) ;
  • comment créer une image, et l'afficher sur l'écran en Python (vous avez plusieurs méthodes à disposition pour cela). Voyez la documentation Travailler avec des images en Python.

Références

Travail à rendre

Chaque fois que vous rendez un fichier, insérez votre login ENT dans le nom du fichier comme c'est indiqué plus loin (ne mettez pas login dans les noms de fichier, mais votre propre login bien sûr…). Évitez toujours les espaces et les caractères accentués ou les cédilles dans les noms de fichiers.

Vous devez rendre :

  1. Une image couleur, au format PNG d'un ensemble de Julia original (ne rendez pas tous la même image…) ayant pour nom : login_juliaimg.png et pour taille 400×400. Le fichier doit contenir uniquement votre image, avec éventuellement les bords de la fenêtre, mais pas tout l'écran. Si vous ne savez pas faire, demandez…
  2. Le fichier python correspondant à la copie d'écran rendue au point ci-dessus. Le fichier s'appellera : login_julia.py

Vos programmes Python doivent être autonomes (s'exécuter sans intervention dans le shell), et avoir une importation silencieuse. Autrement dit, il faut que :

  • si on double-clique sur le programme depuis le gestionnaire de fichier de Windows (ou si exécute depuis l'environnement de développement), le programme se lance, calcule et affiche une image sans autre intervention.
  • si on importe le module, rien ne s'exécute.

Pour parvenir à cela, vous devrez utiliser la construction :

if __name__=='__main__' : XXXX

Vous devez rendre :

  1. un fichier PDF (uniquement PDF) nommé login_julia.pdf et contenant votre rapport (lorsque vous trouvez un encadré d'exercice, il s'agit soit d'un travail à faire sur machine, soit d'une question à traiter dans le rapport)
  2. le fichier Python correspondant à la version monochrome de l'ensemble de Julia pour la valeur de c proposée dans l'énoncé et le cadrage -1.25,1.25, -1.25 1.25. Ce fichier devra s'appeler login_monojulia.py
  3. Une image couleur (vous n'avez pas à rendre l'image de la version monochrome), au format PNG d'un ensemble de Julia original (votre propre palette, votre propre valeur de c et votre propre zone de tracé (pas -1.25,1.25, -1.25 1.25…)) ayant pour nom : login_juliaimg.png. Vous devez ici rendre une image différente de celle des autres (Le fichier doit contenir uniquement votre image, avec éventuellement les bords de la fenêtre, mais pas tout l'écran. Si vous ne savez pas faire, demandez…)
  4. le fichier python correspondant à la copie d'écran rendue au point 3. Le fichier s'appellera : login_julia.py

Vos programmes Python doivent être autonomes (s'exécuter sans intervention dans le shell), mais aussi importables de manière silencieuse. Faites comme indiqué dans ce guide : Notes sur Python - Programme propre. Si vous ne comprenez pas ce point, demandez à l'encadrant.

Version monochrome

Rappels sur les ensembles de Julia

Notons tout d'abord qu'il existe plusieurs ensembles de Julia. Pour chaque nombre complexe $c$ choisi, il y a un ensemble. Nous noterons $J©$ l'ensemble des points du plan appartenant à l'ensemble de Julia paramétré par $c$.

L'ensemble de Julia $J©$ étant un ensemble de points du plan, nous pouvons le voir comme un ensemble de nombres complexes. Les nombres complexes appartenant à $J©$ sont les nombres $u_0$ tels que la suite : $$\forall n>0, u_n=u_{n-1}^2+c$$ a un module qui reste fini.

Dans un premier temps, nous tracerons l'ensemble de Julia correspondant à $c=-0.85 + 0.2 i$. Il faut que cette valeur soit facile à modifier par la suite\ : gardez ceci à l'esprit tout au long de la construction du programme (car nous testerons d'autres valeurs).

On ne peut pas déterminer sur une machine si les termes de la suite précédente ont un module qui reste fini ou non en calculant simplement un grand nombre de termes. Il faudra donc faire quelques concessions à l'exactitude. Une bonne approximation consiste à calculer les 100 premiers termes de la suite et à utiliser cette propriété (valable si le module de c est strictement inférieur à 2) :

S'il existe $N$, entier, tel que le module de $u_N$ soit strictement plus grand que 2, alors la suite n'a pas un module qui reste fini.

Ainsi, si nous trouvons un terme de module supérieur à deux, nous pourrons conclure que le point n'est pas dans l'ensemble de Julia. Si au bout du 100ème terme, aucun n'a dépassé 2 en module, on conclura (parfois faussement) que le point est dans l'ensemble de Julia.

Première fonction

En vous aidant des indications données précédement, et de la manière de manipuler les nombres complexes en Python (voir [[stu:python:python_notes|Aide mémoire / Notes sur Python 3]]), écrivez une fonction julia(u,c) qui indique en renvoyant un booléen si u est dans ''J(c). Pour bien faire, la fonction doit posséder une docstring. Par exemple : <code python > def julia(u,c) : “”“ Indique si le complexe u appartient (True) ou non (False) à J© ”“” … </code>

Testez votre fonction en comparant vos résultats avec ceux-ci, obtenus pour $c=-0.85+0.2i$ :

  • les 3 points : 0, 0.3j et 1 sont dans l'ensemble de Julia
  • les 2 points 0.3 et 1+j ne sont pas dans l'ensemble de Julia

==== Changement de repère ==== Ce qui précède nous permet, étant donné une nombre complexe $u_0$, de savoir s'il est ou non dans l'ensemble de Julia. Pour cela, nous devons maintenant représenter ces points sur l'écran, par exemple en noir ou blanc, selon qu'ils sont ou pas dans l'ensemble de Julia. Pour cela, nous devons donc faire correspondre un nombre complexe $u_0$ à un pixel (i,j) de l'écran (avec i et j deux entiers entre 0 et 400 (399 plus précisément) si la fenêtre fait 400 pixels de côté). Ceci s'apparente à un simple changement de repère. Nous devons décider quelles sont les limites (parties réelles et imaginaires) de $u_0$, et en déduire le changement de repère (mise à l'échelle et translation).

En admettant que nous désirions tracer l'ensemble de Julia pour $u_0=a+ib$ avec $-2\le a\le 1$ et $-1.5\le b\le 1.5$, dans une fenêtre 400x400, le changement de repère adéquat est :

$$\left\{\begin{array}{l}a=\frac{i}{400}\times3-2\\b=\frac{j}{400}\times3-1.5\end{array}\right.$$

Vérifiez (par le calcul ou mentalement) que le point (0,0) est bien envoyé sur (-2,-1.5) (les valeurs mini pour a et b) et que le point (400,400) est envoyé sur (1,1.5) (les valeurs maxi pour a et b).

Dans votre programme, dans un premier temps, vous utiliserez le cadrage suivant : $-1.25\le a\le 1.25$ et $-1.25\le b\le 1.25$. Quelles sont les formules de changement de repère à utiliser ?

Écrivez une fonction conversion qui convertit des coordonnées de pixels en nombres complexes en utilisant le cadrage $-1.25\le a\le 1.25$ et $-1.25\le b\le 1.25$. Votre fonction prendra donc deux entiers en paramètres et renverra un nombre complexe.

Vérifiez que votre fonction conversion est correcte (pour des entrées dont vous pouvez facilement calculer la sortie…).

Les tests (que vous choisirez bien) devront figurer dans votre rapport.

==== Tracé et programme principal ==== Nous savons maintenant faire les choses suivantes : * associer un nombre complexe $u_0$ à des coordonnées (i,j) de pixels sur l'écran : fonction conversion, prenant en paramètres deux entiers et renvoyant un nombre complexe * décider si un nombre complexe $u_0$ appartient à $J©$ ou pas : fonction julia prenant en paramètres deux complexes $u_0$ et $c$ et décidant si $u_0$ appartient à $J©$ ou non. Cette fonction renvoie un booléen. Nous pouvons maintenant proposer un algorithme pour le tracé, qui crée une image, par exemple avec le module PIL et trace l'ensemble de Julia pour une valeur de c dans cette image : <code> def trace© | img ← création d'une image à la bonne taille | Pour chaque pixel (i,j) de l'image : | | u ← conversion(i,j) | | r ← julia(u,c) | | Si r : | | | afficher le point (i,j) en noir | | Sinon : | | | afficher le point (i,j) en blanc | retourner img </code> Attention à la boucle de tracé qui en cache en fait 2 : une pour les abcisses et une pour les ordonnées. Le programme principal, se résumera donc à : <code> c ← -0.85+0.2i img ← tracer© afficher l'image </code> Pour afficher l'image, il est conseillé d'utiliser le module ImageWindow, décrit dans la documentation : Travailler avec des images en Python Voici le programme principal utilisant le module ImageWindow (il vous faudra peut être le téélcharger et le placer dans votre répertoire de travail) : <code python> def main() : c=-0.85+0.2j img = trace© win = Visu() # Création de la fenêtre win.setImage(img) # Affiche l'image chargée dans la fenêtre # Pas nécessaire depuis un shell interactif comme IEP, mais # il faut le mettre dans une appli indépendante. win.run() </code> Il reste à écrire la fonction trace et à tester le programme. Vous devriez obtenir une image similaire à celle-ci :  Ensemble de Julia La suite du travail va être réalisée à partir du programme traçant l'image en noir et blanc. Il est prudent d'en faire une copie (du programme donnant l'image en monochrome) pour conserver une version fonctionnelle. =====Version en couleur===== Nous allons maintenant essayer d'obtenir des images en couleur, modifier la valeur de c et modifier éventuellement le cadrage. ==== Obtenir de la couleur ==== Le principe est simple: la fonction julia renvoyait jusqu'à présent False ou True, ce qui correspond aux deux couleurs utilisées. Nous allons donc modifier la fonction julia de telle manière qu'elle puisse renvoyer un plus grand nombre de valeurs différentes. Considérez le fait suivant : Pour un point donné $u_0$ qui conduit à une suite dont le module ne reste pas fini, nous nous en apercevons si le module d'un terme de la suite dépasse 2. Mais nous pouvons nous en apercevoir dans les premiers termes ou au contraire au bout du cinquantième etc… Nous pouvons donc savoir si une suite a rapidement eu un terme dont le module dépasse 2. Autrement dit, ce qui nous intéresse lorqu'un terme $u_i$ de la suite a un module qui dépasse 2, c'est : combien vaut $i$ à ce moment là?

Modifiez la fonction julia de telle manière que :

  • si au bout de 100 termes, tous les modules sont restés inférieurs à 2, elle renvoie -1
  • si lors du calcul du terme numéro i, le module dépasse 2, la fonction renvoie i

Nous disposons maintenant d'une fonction julia qui renvoie -1 si le point est dans l'ensemble, et qui renvoie un entier positif plus petit que 100 sinon.

Assurez vous d'avoir bien compris la raison pour laquelle nous avons choisi la valeur -1 et pas, par exemple la valeur 1, dans le cas où la suite semble ne pas diverger.

Sachant qu'une couleur est définie par ses trois composantes rouges, vertes et bleues, qui sont trois entiers compris entre 0 et 255 (attention à ne pas dépasser), vous pouvez imaginer n'importe quel calcul, qui à partir de l'entier fourni pas la fonction julia donne un triplet de valeurs entre 0 et 255. Selon le calcul choisi vous aurez telle ou telle palette de couleurs. Le mélange des couleurs sur un écran est fait par synthèse additive. Les couleurs «primaires» sont le rouge, le vert et le bleu. Vous trouverez d'autres information ici : Rouge Vert Bleu, Codage informatique des couleurs

Dans un premier temps, essayez d'obtenir un dégradé correct. Ce n'est pas évident. Pour comprendre les problèmes qui se posent, vous devrez peut être vous faire une idée des valeurs typiques renvoyées par la fonction julia. La procédure suivante, par exemple, vous indique les valeurs renvoyées par votre fonction le long s'un segment vertical :

def testvals() :
    for j in range(0,400,20) :
        print(julia(conversion(200,j),-0.85+0.2j))

Comprenez ce programme, analysez les résultats, et la répartition des nombres obtenus.

En fonction de ça, vous aurez une idée plus précise des transformations à appliquer à la valeur renvoyer par la fonction julia pour obtenir une palette étendu de couleurs.

Voici un exemple d'image obtenue par ce principe : Vous devez maintenant avoir une nouvelle version de votre code source avec les nouveaux algorithmes (fonction julia'' et calcul de la couleur).

Comprendre comment sont fabriquées les couleurs à partir d'un nombre unique et la manière dont elles se succèdent dans le dégradé est une part importante de ce travail qui pourrait donner lieu à des questions en fin de TP. Assurez-vous d'en avoir compris tous les détails. Pour cela, vous pouvez tout à fait montrer votre fonction de calcul des couleurs avec le chargé de TP et en discuter.

Modification de la valeur de c

Nous avons fixé une valeur de $c$ de manière arbitraire. Vous pouvez changer cette valeur pour obtenir d'autres images (aidez vous de la cartographie de l'ensemble de Julia indiquée en début de sujet).

Modification du cadrage

Nous avons fixé les bornes de tracé de l'ensemble de Julia à -1.25, 1.25 en abscisse et en ordonnées. Vous pouvez modifier ces bornes pour obtenir un «zoom» sur certaines parties de l'image.

Résultats obtenus

Voici quelques échantillons de résultats possible.

Donnez le calcul des couleurs que vous avez utilisé et justifiez ce qui n'est pas arbitraire dans ce calcul.

Essayez d'obtenir une belle image, avec vos propres couleurs, votre propre valeur de c et un cadrage éventuellement différent de celui proposé en début de TP. Vous devrez rendre cette image (voyez les instructions en début de sujet).

Le fichier Python que vous devrez rendre est celui qui permet de tracer cette image. Avant de passer à la suite, faites-en une copie…

Lapin de Douady

Pensez à mettre de côté votre fichier Python à rendre avant d'y apporter des retouches pour réaliser le travail qui suit (pour lequel vous n'avez pas besoin de rendre de fichier).

Parmi les nombreux ensembles de Julia, certains ont des noms. C'est le cas du lapin de Douady qui vérifie la propriété suivante : la trajectoire de l'origine (la suite obtenue pour $u_0=0$) a pour longueur 3. Autrement dit, si $u_0=0$, $u_3=0$. Calculez la valeur de $c$ qui permet de tracer le lapin de Douady. Mis en équation, vous obtenez un polynome de degré 3 ou 4 en $c$ dont vous pourrez chercher les racines avec un logiciel de calcul numérique ou avec l'extension NumPy. Tracez ensuite l'image correspondante.

Racines d'un polynôme avec NumPy

import numpy
# création du polynôme x^2-3x+2
p=numpy.poly1d([1,-3,2])
# calcul des racines
numpy.roots(p)
# ou bien 
p.roots

Tracez l'image représentant le lapin de Douady

Indiquez dans votre rapport la valeur de c que vous avez obtenue, et la manière dont vous l'avez calculée.

tp/python/julia.txt · Dernière modification: 2021/05/04 15:12 (modification externe)