Thông tin

Hạn chót 15/04/2026 23:00:00
Giới hạn nộp bài Không có giới hạn

Đăng nhập

Derivate

Dans cette partie du projet, les fonctions serialize et unserialize ne sont pas disponibles. Les fonctions evaluate et simplify ont été importées pour vous dans l'environnement même si, dans un premier temps, il est conseillé de ne pas les utiliser (voir ci-dessous).

Votre tâche est d'implémenter la fonction derivate qui construit un nouvel arbre dérivé à partir d'un arbre original qui ne peut pas être modifié.

Les règles de dérivation vous sont rappelées dans la table ci-dessous. La plus simple est que la dérivée d'une valeur numérique vaut 0.0. C'est logique puisque la dérivée d'une fonction représente comment la fonction évolue et si cette fonction est une constante numérique, par définition d'une constante, elle n'évolue pas !

Par ailleurs, les arbres auxquels nous nous intéressons peuvent inclure plusieurs variables distinctes x, y, etc. Par exemple, l'arbre dont la représentation parenthésée est (x + y) représente la fonction mathématique \(f(x, y) = (x + y)\). La fonction derivate prend pour second argument la variable par rapport à laquelle la dérivée doit être calculée. Mathématiquement, nous notons \(\large{\frac{d}{dx}}\) si la dérivée se calcule par rapport à la variable x. La dérivée d'une variable par rapport à elle-même vaut simplement 1.0. Mathématiquement, cela s'écrit \(\large{\frac{d}{dx} x = 1}\). En revanche, la dérivée d'une variable x par rapport à une autre variable y vaut simplement 0.0, puisque si on dérive \(x\) par rapport à une autre variable, cela veut dire que \(x\) est laissé constant et donc ne varie pas. En résumé, \(\large{\frac{d}{dy} x = 0}\) et aussi \(\large{\frac{d}{dx} y = 0}\). Les règles de dérivation que nous venons de présenter constituent les cas de base à considérer.

Ensuite, même si une fonction dépend de plusieurs variables, nous calculons ici la dérivée par rapport à une variable particulière, par exemple x. Donc, lorsque nous dérivons par rapport à x, une fonction \(f(x, y, z, \dots)\) peut-être vue simplement comme \(f(x)\), c'est-à-dire une fonction de x seulement, puisque les autres variables jouent le rôle de constantes numériques. Dans la suite, nous noterons donc la fonction dérivée simplement par \(f'(x)\) comme notation compacte pour \(\frac{d}{dx}f(x, y, z, \dots)\).

Plus généralement, la table ci-dessous résume toutes les opérations de dérivation à implémenter pour un arbre (et par récursion, pour ses sous-arbres). En plus des cas de base, elle présente ce qu'il y a lieu de faire pour calculer la dérivée avec un opérateur ou une fonction. Par exemple, la dérivée d'une somme de 2 fonctions est la somme des dérivées. En mathématiques, cela donne \(\frac{d}{dx} \left(f_1(x) + f_2(x)\right) = f'_1(x) + f'_2(x)\). En supposant que les fonctions \(f_1(x)\) et \(f_2(x)\) soient représentées en python respectivement par les arbres t1 et t2, nous nous intéressons donc ici au calcul de la dérivée pour l'arbre t == Tree('+', [t1, t2]). Et nous pourrions écrire :

derivate(t, 'x') == Tree('+', [derivate(t1, 'x'), derivate(t2, 'x')])

Notons que nous avons utilisé ici un test d'égalité == pour illustrer une propriété de la valeur renvoyée par la fonction derivate. Votre tâche ne vise pas directement à coder ce genre de test d'égalité mais à calculer effectivement un arbre dérivé, c'est-à-dire à vous concentrer sur la partie droite de cette égalité. Vous pouvez déjà vous rendre compte que l'on construit effectivement un nouvel arbre Tree(...) et que, pour le construire, nous avons besoin du résultat de la dérivation pour les 2 sous-arbres t1 et t2 de l'arbre original t.

https://inginious.info.ucl.ac.be/course/LINFO1103/projet-4/derivate.jpg

Notons que pour la dérivée d'une puissance, l'exposant \((a-1)\) dans l'expression dérivée doit être évalué à une valeur numérique (et non pas être representé avec une opération de soustraction). De la même manière, le facteur \(a\) représente une valeur numérique.

Les 4 dernières lignes de cette table illustre le calcul de la dérivée d'une composition de fonctions. Par exemple, l'arbre représenté par l'expression (sin (2.0 * x)) correspond à la fonction \(\sin(2*x)\) qui est une composition de la fonction sinus \(\sin\) et de son argument qui est la fonction \((2*x)\) dans cet exemple. La règle de dérivation nous dit que la dérivée de cette fonction composée \(\sin(2*x)\) se calcule comme le cosinus de son argument \(\cos{(2*x)}\) multiplié par la dérivée de cet argument. La dérivée de cet argument vaut simplement \(2\) et donc le résultat global de cette dérivée est \(\cos{(2*x)}*2\). Cet exemple illustre que pour calculer la dérivée d'une composition de 2 fonctions \(g(f(x))\) (avec \(f(x) = (2*x)\) dans notre exemple), nous avons besoin notamment de la dérivée \(f'(x)\).

En termes de code python, cela veut dire que pour calculer la dérivée d'un arbre qui serait :

t = Tree('sin', [Tree('*', [Tree(2), Tree('x')])])

nous avons besoin de la dérivée du fils de cet arbre:

derivate(t.children[0], var='x')

Cela illustre une fois encore que ce problème est récursif.

Trucs et astuces

  • Implémentez d'abord les cas de base.
  • Comme déjà illustré ci-dessus avec la dérivation d'un arbre représentant la somme de 2 fonctions ou la composition de 2 fonctions, pour pouvoir dériver un arbre il est particulièrement pertinent que son ou ses sous-arbre(s) soi(en)t déjà derivé(s). Cette propriété doit vous guider dans la manière d'organiser la récursion.
  • La signature de la fonction derivate impose que la valeur renvoyée soit toujours un arbre. Si la dérivation vous conduit à une valeur numérique pour la dérivation de l'ensemble de l'arbre (ce serait le cas, par exemple, s'il ne contient pas la variable par rapport à laquelle on dérive), l'arbre dérivé doit être réduit à une feuille contenant cette valeur numérique (égale à 0.0 dans cet exemple).
  • L'arbre original passé en paramètre ne doit pas être modifié mais un nouvel arbre doit être créé et renvoyé.
  • Testez votre fonction de dérivation sur des cas de complexité croissante. Commencez par tester les cas de base. Passez ensuite à des arbres de hauteur 1 et tester la dérivée pour les différents opérateurs et fonctions possibles. Passez ensuite à des arbres de hauteur supérieure 2, 3, 4, ..., pour vérifier que la récursion fonctionne correctement.
  • La table présentée ci-dessus reprend les règles de dérivation telles quelles. Il est fortement conseillé de les implémenter exactement comme elles sont formulées, sans chercher à simplifier le résultat. Une fois, l'arbre derivé renvoyé par la fonction derivate, il est possible d'imprimer sa représentation parenthésée, de le simplifier (en appelant alors simplify) ou de l'évaluer (en appelant alors evaluate). Toutes ces opérations sont utiles et recommandées pour vos tests mais le code de la fonction derivate elle-même n'a pas lieu de faire ce travail. A des fins de debugging de la fonction derivate, il est bien sûr possible d'ajouter des instructions pour imprimer l'arbre courant ou, par exemple, le résultat de la dérivée de l'un de ses sous-arbres. L'usage d'un 'debogueur' est aussi possible.
  • Votre programme est-il capable à présent de calculer (et de simplifier, ensuite !) la dérivée de la fonction suivante ?
\begin{equation*} f(x) = \frac{(\cos x)^2}{1 + \tan(x^3)} \end{equation*}

Veuillez soumettre le corps de la fonction suivante:

def derivate(tree: Tree, var: str) -> Tree:
    """
    :pre: tree est un arbre syntaxique valide et non vide
    :return: un arbre correspondant à la dérivée (symbolique) de l'arbre original,
             par rapport à la variable `var`.
    :post: l'arbre original n'est *pas* modifié, un nouvel arbre (dérivé) est renvoyé.
    """