12. TP - Calcul symbolique et Nerdle



12.1. Introduction

Le calcul symbolique (parfois appelé calcul formel) permet de travailler sur des formules mathématiques dotées de variables et de réaliser des calculs sur ses formules sans forcément affecter une valeur aux variables. Les exemples les plus parlant sont, pour une fonction $f(x)$ donnée, de calculer sa dérivée, son intégrale ou la limite de la fonction.

Python propose le package sympy (symbolic mathematics) qui permet de faire du calcul symbolique.

Voici quelques exemples d'utilisation de sympy :

  1. """
  2.     Calcul de la dérivée de la fonction
  3.     f(x) = x^3 + 2x^2 + 3x + 4
  4. """
  5. import sympy as sp
  6.  
  7. # Permet l'affichage stylisé avec Unicode
  8. sp.init_printing(use_unicode=True)
  9.  
  10. # Définir x comme symbole/variable
  11. x = sp.Symbol('x')
  12.  
  13. # Definir la fonction x^3 + 2x^2 + 3x + 4
  14. f = x**3 + 2*x**2 + 3*x + 4
  15.  
  16. # Calculer la derivée ("diff"erentiate) par rapport à x
  17. f_derivee = sp.diff(f, x)
  18.  
  19. # Afficher les résultats
  20. print("Fonction f(x):")
  21. sp.pprint(f)
  22. print("Derivée f'(x):")
  23. sp.pprint(f_derivee)
  24.  
  25.  
Fonction f(x):
 3      2          
x  + 2⋅x  + 3⋅x + 4
Derivée f'(x):
   2          
3⋅x  + 4⋅x + 3
  1. """
  2.     Calcul de la limite quand x tend vers l'infini
  3.     de f(x) = 1/x
  4. """
  5. import sympy as sp
  6.  
  7. # Permet l'affichage stylisé avec Unicode
  8. sp.init_printing(use_unicode=True)
  9.  
  10. # Définir la variable et la fonction
  11. x = sp.Symbol('x')
  12. f = 1 / x
  13.  
  14. # Calcul de la limite lorsque x tend vers l'infini sp.oo
  15. limite = sp.limit(f, x, sp.oo)
  16.  
  17. # Afficher les résultats
  18. print("Fonction f(x):")
  19. sp.pprint(f)
  20.  
  21. print("\nLimite de f(x) lorsque x tend vers l'infini:")
  22. sp.pprint(limite)
  23.  
  24.  
Fonction f(x):
1
─
x

Limite de f(x) lorsque x tend vers l'infini:
0
  1. """
  2.     Evaluation de la fonction f(x) = 3x-5
  3.     pour x=1/2
  4. """
  5. import sympy as sp
  6.  
  7. # Permet l'affichage stylisé avec Unicode
  8. sp.init_printing(use_unicode=True)
  9.  
  10. # Définir la variable et la fonction
  11. x = sp.Symbol('x')
  12. f = 3 * x - 5
  13.  
  14. # Évaluer la fonction pour x = 1/2
  15. # "subs"titute (substituer) x par 1/2
  16. valeur = f.subs(x, 1/2)
  17.  
  18. # Afficher les résultats
  19. print("Fonction f(x):")
  20. sp.pprint(f)
  21.  
  22. print("\nValeur de f(x) pour x = 1/2 :")
  23. sp.pprint(valeur)
  24.  
  25.  
Fonction f(x):
3⋅x - 5

Valeur de f(x) pour x = 1/2 :
-3.50000000000000

12.2. Nerdle

Le jeu Nerdle consiste à résoudre un casse-tête basé sur des nombres.

Nerdle

On dispose de 8 cases à remplir avec des symboles (chiffres, opérateurs et symbol '=') de manière à générer une formule correcte. Par exemple 54/9-4=2.

Le but du jeu consiste à découvrir la formule cachée en 6 coups maximum.

Pour gagner en jouant à Nerdle il suffit de commencer par saisir les formules suivantes :

  • 3*9+0=27
  • 54/6-8=1

En effet, ces deux formules permettent de déterminer :

  • quels chiffres sont utilisés, puisqu'on passe en revue 0, 1, ..., 9
  • quels opérateurs sont utilisés puisqu'on a utilisé +, -, *, /
  • où se trouve l'opérateur d'égalité : en position 6 ou 7, voire en position 5 si les positions 6 et 7 ne sont pas validées

12.3. Travail demandé

12.3.1. Première version

On considère que la formule cachée connue de Nerdle est la formule précédente (54/9-4=2). On imagine que le joueur a pu déterminer :

Exercice 12.1

On vous demande d'écrire un programme Python nommé assistant_nerdle.py qui va déterminer toutes les variantes pour lesquelles la formule x/y-z=2 est correcte. Il faut s'inspirer des programmes précédents

Pour cela, vous devez fournir en paramètres du programme :

  • la formule de calcul : "x/y-z=2"
  • les intervalles dans lesquels $x$, $y$ et $z$ prennent leurs valeurs, par exemple : "2|1|1"

La notation "2|1|1" signifie que $x$ comporte deux chiffres, que $y$ et $z$ ne comportent qu'un seul chiffre. Cela implique que l'on fera varier $x$ dans l'intervalle $[0-99]$, $y$ et $z$ quant à eux varieront dans l'intervalle $[0-9]$.

Afin d'évaluer la formule, il faudra la transformer pour vérifier qu'elle est égale à 0. Pour cela on modifie la formule initiale en "x/y-z=2" en "x/y-z-(2)" :

expression = sp.sympify(expression.replace("=", "-(") + ")")

Testez votre programme avec :

\$ python3 assistant_nerdle.py "x/y-z=2" "2|1|1"

Vous devriez obtenir 90 solutions :

ces premières solutions ne sont pas valides car x ne comporte qu'un chiffre 
cela sera corrigé dans la version améliorée
{'x': 2, 'y': 1, 'z': 0}
{'x': 3, 'y': 1, 'z': 1}
{'x': 4, 'y': 1, 'z': 2}
{'x': 4, 'y': 2, 'z': 0}
{'x': 5, 'y': 1, 'z': 3}
{'x': 6, 'y': 1, 'z': 4}
{'x': 6, 'y': 2, 'z': 1}
{'x': 6, 'y': 3, 'z': 0}
{'x': 7, 'y': 1, 'z': 5}
{'x': 8, 'y': 1, 'z': 6}
{'x': 8, 'y': 2, 'z': 2}
{'x': 8, 'y': 4, 'z': 0}
{'x': 9, 'y': 1, 'z': 7}
{'x': 9, 'y': 3, 'z': 1}
{'x': 10, 'y': 1, 'z': 8}
{'x': 10, 'y': 2, 'z': 3}
{'x': 10, 'y': 5, 'z': 0}
{'x': 11, 'y': 1, 'z': 9}
{'x': 12, 'y': 2, 'z': 4}
{'x': 12, 'y': 3, 'z': 2}
{'x': 12, 'y': 4, 'z': 1}
{'x': 12, 'y': 6, 'z': 0}
{'x': 14, 'y': 2, 'z': 5}
{'x': 14, 'y': 7, 'z': 0}
{'x': 15, 'y': 3, 'z': 3}
{'x': 15, 'y': 5, 'z': 1}
{'x': 16, 'y': 2, 'z': 6}
{'x': 16, 'y': 4, 'z': 2}
{'x': 16, 'y': 8, 'z': 0}
{'x': 18, 'y': 2, 'z': 7}
{'x': 18, 'y': 3, 'z': 4}
{'x': 18, 'y': 6, 'z': 1}
{'x': 18, 'y': 9, 'z': 0}
{'x': 20, 'y': 2, 'z': 8}
{'x': 20, 'y': 4, 'z': 3}
{'x': 20, 'y': 5, 'z': 2}
{'x': 21, 'y': 3, 'z': 5}
{'x': 21, 'y': 7, 'z': 1}
{'x': 22, 'y': 2, 'z': 9}
{'x': 24, 'y': 3, 'z': 6}
{'x': 24, 'y': 4, 'z': 4}
{'x': 24, 'y': 6, 'z': 2}
{'x': 24, 'y': 8, 'z': 1}
{'x': 25, 'y': 5, 'z': 3}
{'x': 27, 'y': 3, 'z': 7}
{'x': 27, 'y': 9, 'z': 1}
{'x': 28, 'y': 4, 'z': 5}
{'x': 28, 'y': 7, 'z': 2}
{'x': 30, 'y': 3, 'z': 8}
{'x': 30, 'y': 5, 'z': 4}
{'x': 30, 'y': 6, 'z': 3}
{'x': 32, 'y': 4, 'z': 6}
{'x': 32, 'y': 8, 'z': 2}
{'x': 33, 'y': 3, 'z': 9}
{'x': 35, 'y': 5, 'z': 5}
{'x': 35, 'y': 7, 'z': 3}
{'x': 36, 'y': 4, 'z': 7}
{'x': 36, 'y': 6, 'z': 4}
{'x': 36, 'y': 9, 'z': 2}
{'x': 40, 'y': 4, 'z': 8}
{'x': 40, 'y': 5, 'z': 6}
{'x': 40, 'y': 8, 'z': 3}
{'x': 42, 'y': 6, 'z': 5}
{'x': 42, 'y': 7, 'z': 4}
{'x': 44, 'y': 4, 'z': 9}
{'x': 45, 'y': 5, 'z': 7}
{'x': 45, 'y': 9, 'z': 3}
{'x': 48, 'y': 6, 'z': 6}
{'x': 48, 'y': 8, 'z': 4}
{'x': 49, 'y': 7, 'z': 5}
{'x': 50, 'y': 5, 'z': 8}
{'x': 54, 'y': 6, 'z': 7}
{'x': 54, 'y': 9, 'z': 4}
{'x': 55, 'y': 5, 'z': 9}
{'x': 56, 'y': 7, 'z': 6}
{'x': 56, 'y': 8, 'z': 5}
{'x': 60, 'y': 6, 'z': 8}
{'x': 63, 'y': 7, 'z': 7}
{'x': 63, 'y': 9, 'z': 5}
{'x': 64, 'y': 8, 'z': 6}
{'x': 66, 'y': 6, 'z': 9}
{'x': 70, 'y': 7, 'z': 8}
{'x': 72, 'y': 8, 'z': 7}
{'x': 72, 'y': 9, 'z': 6}
{'x': 77, 'y': 7, 'z': 9}
{'x': 80, 'y': 8, 'z': 8}
{'x': 81, 'y': 9, 'z': 7}
{'x': 88, 'y': 8, 'z': 9}
{'x': 90, 'y': 9, 'z': 8}
{'x': 99, 'y': 9, 'z': 9}
il y a  90  solutions

12.3.2. Version améliorée

Exercice 12.2

Dans un second temps, on modifiera le programme précédent afin d'introduire un troisième paramètre qui indique la forme de $x$, $y$ et $z$, comme par exemple "5.|.|."

Où ici, "5.|.|." indique que $x$ commence par $5$ et est suivi d'un chiffre (compris entre 0 et 9) et que $y$ et $z$ sont composés d'un chiffre quelconque.

Modifiez le programme comme suit :

Testez votre programme modifié avec les alternatives suivantes, vous devriez obtenir les résultats indiqués :

\$ python3 assistant_nerdle.py "x/y-z=2" "2|1|1" "5.|.|."
il y a 90 solution(s)
il y a 6 solution(s) après filtrage
{'x': 50, 'y': 5, 'z': 8}
{'x': 54, 'y': 6, 'z': 7}
{'x': 54, 'y': 9, 'z': 4}
{'x': 55, 'y': 5, 'z': 9}
{'x': 56, 'y': 7, 'z': 6}
{'x': 56, 'y': 8, 'z': 5}


\$ python3 assistant_nerdle.py "x/y-z=2" "2|1|1" "7.|.|."
il y a 90 solution(s)
il y a 4 solution(s) après filtrage
{'x': 70, 'y': 7, 'z': 8}
{'x': 72, 'y': 8, 'z': 7}
{'x': 72, 'y': 9, 'z': 6}
{'x': 77, 'y': 7, 'z': 9}

\$ python3 assistant_nerdle.py "x/y-z=2" "2|1|1" "5.|6|."
il y a 90 solution(s)
il y a 1 solution(s) après filtrage
{'x': 54, 'y': 6, 'z': 7}