Documentation de Pyromaths 18.7

2. Tutoriel : Créer un nouvel exercice

«  1. Introduction   ::   Contenu   ::   3. Classes et Fonctions  »

2. Tutoriel : Créer un nouvel exercice

Cette article décrit la procédure pour créer un nouvel exercice pour Pyromaths. Nous prendrons comme exemple la création d’un exercice de résolution d’équations du premier degré du type \(ax+b=cx+d\) où les nombres \(a\), \(b\), \(c\), \(d\) sont des entiers relatifs.

Note

Certains termes techniques anglais (comme template, merge, etc.) n’ont volontairement pas été traduits. Cette documentation renvoit à d’autres documentation en anglais, et pour que la personne lisant ces lignes s’y retrouve d’une documentation à l’autre, nous avons fait le choix de conserver les termes anglais plutôt que d’utiliser leur équivalent français.

Note

Cette documentation a été écrite en se mettant à la place d’une personne utilisant GNU/Linux. Il est également possible d’écrire un nouvel exercice depusi Windows ou MacOS, mais certaines commandes décrites dans ce document changent un peu. À vous de les adapter.

2.1. Préambule

Les personnes pressées peuvent jeter un œil à deux exercices implémentés dans Pyromaths :

2.2. Prérequis

2.2.1. Loi

Pyromaths est publié sous licence publique GNU version 3 (GPLv3). Si vous souhaitez contribuer à Pyromaths en partageant votre exercice, celui-ci devra impérativement être également publié sous cette même licence.

2.2.2. Connaissances

Créer un exercice pour Pyromaths nécessite de savoir utiliser un minimum :

  • \(LaTeX\) ;
  • Python (version 3) ;
  • git.

Une connaissance de la bibliothèque Python jinja2 est un plus, mais les bases s’apprennent rapidement et sont décrites plus loins dans ce document.

2.2.3. Outils

À part git, tous les outils nécessaires pour créer un exercice sont des dépendances de Pyromaths. Installez la version de développement, et faites fonctionner Pyromath : les outils nécessaires pour ce tutoriel seront alors disponibles.

2.3. Environnement de travail

Commençons par télécharger les sources de Pyromaths, en utilisant le logiciel git. Si vous avez un compte Framagit, utilisez :

$ git clone git@framagit.org/pyromaths/pyromaths.git

Si vous n’avez pas de tel compte, utilisez :

$ git clone https://framagit.org/pyromaths/pyromaths.git

Puis déplacez vous dans le répertoire pyromaths ainsi créé. À partir de maintenant, sauf mention contraire, toutes les commandes sont à exécuter depuis ce répertoire.

La version de développement de Pyromaths se trouve dans la branche develop :

$ git checkout develop

Il sera plus confortable, pour vous comme pour nous, que vous travailliez dans une branche séparée, par exemple EquationPremierDegre :

$ git branch EquationPremierDegre
$ git checkout EquationPremierDegre

2.4. Brouillon

La première étape est d’écrire un exercice en \(LaTeX\), sans passer par Python, sans aléa : juste pour observer le rendu final. Utilisez l’outil en ligne de commandes pyromaths.

$ pyromaths dummy

Note

Si pyromaths n’est pas (encore) installé chez vous, ou une version trop ancienne, vous pouvez télécharger le dépôt et utiliser le binaire utils/pyromaths à la place de pyromaths pour utiliser la version de développement.

Cette commande a pour effet de créer un modèle d’exercice, sous la forme d’un PDF qui est affiché à l’écran, et d’un fichier \(LaTeX\) exercices.tex.

Déplacez ce fichier dans un répertoire temporaire, et modifiez-le pour écrire le sujet de votre énoncé, à la place de ÉNONCÉ DE L'EXERCICE et CORRIGÉ DE L'EXERCICE. Ne vous souciez pas de la manière dont cela sera intégré à Pyromaths ; ne vous souciez pas de la manière dont l’aléa sera intégré : nous verrons cela plus tard. C’est l’occasion de travailler la formulation de l’énoncé et de la solution pour qu’ils soient le plus clair possible.

Ne modifiez que les lignes qui correspondent à l’énoncé ou au corrigé. En particulier, ne modifiez pas le préambule.

Ce fichier doit être compilé avec latex, puis converti en pdf avec dvipdf. À la fin de cette étape, nous obtenons l’énoncé suivant (tex, pdf).

115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  % DÉBUT DE L'ÉNONCÉ
  \exercice

  Déterminer les solutions de l'équation $3x+2=-x-1$. Si nécessaire, les solutions seront arrondies au centième.

  % FIN DE L'ÉNONCÉ
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

  \label{LastPage}
  \newpage
  \currentpdfbookmark{Le corrigé des exercices}{Corrigé}
  \lhead{\textsl{{\footnotesize Page \thepage/ \pageref{LastCorPage}}}}
  \setcounter{page}{1} \setcounter{exo}{0}

  \
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  % DÉBUT DU CORRIGÉ
  \exercice*

  \begin{align*}
    3x+2 &= -x-1 \\
    3x+x &= -1-2 \\
    4x &= -3 \\
    x &= -\frac{3}{4} \\
    x &= -0,75
  \end{align*}

  L'unique solution est $x=-0,75$.

  % FIN DU CORRIGÉ
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

2.5. Première version (sans aléa)

Nous allons maintenant intégrer cet exercice à Pyromaths, sans aléa pour le moment.

Choisissez un identifiant pour votre exercice : un nom composé uniquement de lettres sans accents et de chiffres, sans espaces, comme ConversionDegresRadians, TheoremeDePythagore, CoordonneesDuMilieu, etc. Pour notre exemple, nous choissons EquationPremierDegre (qui sera décliné en EquationPremierDegre2, EquationPremierDegre3, etc. au fil de ce tutoriel).

Notez que si le nom commence par un tiret bas (par exemple _TheoremeDePythagore), l’exercice sera ignoré (cela peut-être utile pour « désactiver » un exercice en cours de création).

2.5.1. Code Python

Le code Python de l’exercice doit être placé dans un des sous-dossiers de pyromaths/ex/. Dans notre cas, ce sera pyromaths/ex/troisiemes. Ensuite, modifiez un des fichiers .py déjà existant, ou créez-en un nouveau. Gardez une certaine logique : un exercice sur Pythagore a sa place dans le même fichier qu’un autre exercice sur Pythagore ; un exercice de trigonométrie n’a pas sa place dans un fichier matrices.py. Dans notre cas, nous crréons un nouveau fichier contenant le code suivant.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# Pyromaths
#
# Un programme en Python qui permet de créer des fiches d'exercices types de
# mathématiques niveau collège ainsi que leur corrigé en LaTeX.
#
# Copyright (C) 2018 -- Louis Paternault (spalax+python@gresille.org)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#

"""Équations du premier degré"""

from pyromaths.ex import Jinja2Exercise

class EquationPremierDegre2(Jinja2Exercise):
    """Résolution d'équations du premier degré à coefficients entiers."""

    tags = ['Troisième', 'Équation', 'Premier degré']

Modifiez les parties suivantes :

  • ligne 8 : votre nom, et l’année courante ;
  • ligne 27 : l’identifiant de l’exercice ;
  • ligne 28 : la description de l’exercice ;
  • ligne 30 : les tags (étiquettes) de l’exercice (ceux-ci doivent contenir le niveau, plus d’autres à votre guise ; pour connaître la liste des tags déjà utilisés, utilisez pyromaths tags).

2.5.2. Code \(LaTeX\)

Le code \(LaTeX\), quant à lui, doit être placé dans le répertoire pyromaths/data/exercices/templates, dans deux fichiers au nom de votre exercices. Reprenez votre fichier exercices.tex, et extrayez les lignes correspondant à l’énoncé, que vous écrivez dans le fichier EquationPremierDegre2-statement.tex, et celles correspondant au corrigé dans le fichier EquationPremierDegre2-answer.tex.

L’énoncé est alors dans le fichier EquationPremierDegre2-statement.tex.

1
2
3
\exercice

Déterminer les solutions de l'équation $3x+2=-x-1$. Si nécessaire, les solutions seront arrondies au centième.

Le corrigé est dans le fichier EquationPremierDegre2-answer.tex

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
\exercice*

\begin{align*}
  3x+2 &= -x-1 \\
  3x+x &= -1-2 \\
  4x &= -3 \\
  x &= -\frac{3}{4} \\
  x &= -0,75
\end{align*}

L'unique solution est $x=-0,75$.

2.5.3. Génération de l’exercice

Vous pouvez maintenant tester la génération de votre exercice, en exécutant la commande suivante.

$ pyromaths generate EquationPremierDegre2

Vous obtenez alors le fichier exercice.pdf.

2.5.4. Bilan

Nous avons écrit notre premier exercice, qui est intégré à Pyromaths. Par contre, il n’y a pas d’aléa : les valeurs numériques sont toujours les mêmes. Cela sera résolu dans la partie suivante.

2.6. Ajout du hasard

Dans cette partie, pour générer l’exercice et suivre votre travail, la commande à utiliser est la suivante.

$ pyromaths generate EquationPremierDegre3:2

Remarquez que par rapport à la commande utilisée dans la partie précédente, un :2 a été ajouté à la fin de la ligne. Il correspond à la graine (seed) du générateur pseudo-aléatoire.

Note

Un ordinateur ne sait pas générer du hasard. Il faut ruser.

Dans notre exercice, nous avons besoin de nombres entiers entre 0 et 9. Pour avoir des nombres aléatoires, à chaque fois que nous utilisons un nombre aléatoire, nous prenons une décimale de π : d’abord 1, puis 4, puis 1, puis 5, et ainsi de suite. Cela à l’aire aléatoire à première vue, mais deux exécutions successives donneront exactement le même exercice. Améliorons cela.

Nous gardons le même système, mais au lieu de commencer à la première décimale de π, nous utilisons désormais sur l’heure courante : si le programme est lancé à 13h37, nous utilisons alors les décimales de π à partir de la 1337e. Ainsi, deux exécutions successives donneront deux exercices différents.

C’est mieux. Mais quand nous créerons notre exercices, nous allons générer encore et encore un exercice, et nous aimerions toujours générer le même (cela facilitera le développement, pour ne pas être perturbé par des valeurs numériques qui changent ; pour qu’un bug introduit par une valeur numérique spécifique n’apparaisse et ne disparaisse pas aléatoirement). Du coup, nous imposons le début de la séquence aléatoire : c’est la signification du :2 ajouté à la fin de la ligne de commande.

C’est un peu plus compliqué en réalité, mais dans les grande lignes, c’est ainsi qu’un ordinateur génère du hasard. Plus d’informations, par exemple, dans l’article de Wikipédia Pseudorandom generator.

Si nous voulons générer un autre exercice, il suffit de transformer le EquationPremierDegre3:2 en EquationPremierDegre3:1729, EquationPremierDegre3:0123456789, ou n’importe quel nombre de votre choix.

2.6.1. Code Python

Du côté de Python, il faut tirer au hasard quatre nombres entiers entre -10 et 10 (sauf 0 et 1, qui sont des cas particuliers), et les rendre disponible depuis le code \(LaTeX\). Cela se fait avec le contexte. Toutes les variables présentes dans ce dictionnaire seront accessibles depuis le template jinja2.

1
2
3
4
5
6
7
8
9
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.context = {
            "a": random.choice([1, -1]) * random.randint(2, 9),
            "b": random.choice([1, -1]) * random.randint(2, 9),
            "c": random.choice([1, -1]) * random.randint(2, 9),
            "d": random.choice([1, -1]) * random.randint(2, 9),
            }

2.6.2. Code \(LaTeX\)

Du côté de \(LaTeX\), nous allons profiter de la bibliothèque jinja2 pour utiliser les variables rendues disponibles dans le contexte.

Note

Cette note se veut une courte introduction à Jinja2. Pour aller plus loins, rendez-vous sur le site du projet.

Un template jinja2 est du code \(LaTeX\) qui sera reproduit tel quel dans le document final, sauf que :

  • les variables peuvent être évaluées avec des doubles parenthèses. Pour insérer la valeur de la variable a du contexte, il faut utiliser (( a )) ;
  • des structures de contrôle (condition, boucle) peuvent être utilisées entourées par (* et *).

Notons que les chaînes définissant ces blocs ont été modifiées par rapport aux chaînes initiales, car trop proches de la syntaxe \(LaTeX\). Ceci est documenté sur le site officiel, et mis en œuvre dans la classe pyromaths.outils.jinja2tex.LatexEnvironment.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class LatexEnvironment(Environment):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.block_start_string = '(*'
        self.block_end_string = '*)'
        self.variable_start_string = '(('
        self.variable_end_string = '))'
        self.comment_start_string = '(% '
        self.comment_end_string = ' %)'
        self.trim_blocks = True
        self.lstrip_blocks = True

L’énoncé est assez simple : il suffit de faire appel aux variables du contexte.

1
2
3
4
\exercice

Déterminer les solutions de l'équation $(( a )) x (( "%+d"|format(b) ))= (( c )) x (( "%+d"|format(d) ))$.
Si nécessaire, les solutions seront arrondies au centième.

Dans ce code, (( a )) et (( c )) sont remplacés par les valeurs des variables a et c du contexte, et (( "%+d"|format(b) )) est remplacé par le résultat du code Python "%+d" % b, ce qui a pour effet d’écrire l’entier b avec son signe (qu’il soit positif ou négatif).

La rédaction du corrigé se fait de la même manière, en remarquant que le code (( d - b )), par exemple, est remplacé par le résultat du calcul d - b. Notons également l’utilisation de (( ((d-b)/(a-c)) | round(2) )), qui permet d’arrondir le résultat du calcul (d-b)/(a-c) à deux chiffres après la virgule. L’ensemble de ces fonctions (format, round, etc.), que jinja2 appelle filters, est décrit dans la documentation officielle.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
\exercice*

\begin{align*}
  (( a )) x (( "%+d"|format(b) )) &= (( c )) x (( "%+d"|format(d) )) \\
  (( a )) x (( "%+d"|format(-c) ))x &= (( d )) (( "%+d"|format(-b) )) \\
  (( a - c )) x &= (( d - b )) \\
  x &= \frac{(( d - b ))}{(( a - c ))} \\
  x &\simeq \numprint{(( ((d-b)/(a-c)) | round(2) ))}
\end{align*}

L'unique solution est $x\simeq\numprint{(( ((d-b)/(a-c)) | round(2) ))}$.

2.6.3. Débuggage

Durant cette phase, il est probable que le code \(LaTeX\) produit soit un peu compliqué, et contienne des erreurs. Il serait alors pratique de pouvoir observer (si ce n’est plus) ce code avant compilation. C’est possible avec l’option --pipe de cli.

Cette option permet de définir des commandes (du shell) qui seront executées sur le fichier \(LaTeX\), avant sa compilation. Par exemple :

  • --pipe cat exécute cat FICHIER.tex, et permet d’observer le fichier avant compilation ;
  • --pipe vim exécute vim FICHIER.tex, et permet de modifier le fichier avant compilation ;
  • --pipe "cp {} draft.tex" exécute cp FICHIER.tex draft.tex, et permet d’obtenir une copie du fichier \(LaTeX\), si le problème est trop complexe pour pouvoir être résolu avec les options ci-dessous ;
  • et n’importe quelle commande du shell peut-être exécutée, au gré de votre imagination.

2.6.4. Bilan

Nous avons produit l’exercice exercice.pdf. Il fonctionne, mais il y a trois problèmes dans le corrigé : premièrement, alors que la solution est exacte, le signe \(\simeq\) est utilisé ; ensuite, bien que la solution soit entière, le code a produit 3,0 plutôt que 3 ; enfin, un troisième problème n’apparaît pas ici, mais sera expliqué et résolu plus loin dans ce document.

../_images/corrige.png

2.7. Structures de contrôle

Dans la correction de l’exercice, le signe utilisé pour donner la solution est \(\simeq\), que la solution soit exacte ou non. Cela peut être corrigé avec une structure de contrôle.

2.7.1. Structures de contrôles

Pour corriger la relation d’équivalence (égalité ou approximation), il suffit de tester si la solution est exacte ou non. Pour cela, nous testons si la solution (multipliée par 100) est égale à la partie entière de la solution, multipliée par 100 elle aussi.

 9
10
11
12
13
        (* if 100*(d-b)/(a-c) == (100*(d-b)/(a-c))|int *)
            =
        (* else *)
            \simeq
        (* endif *)

Pour tester si la solution est exacte, nous aurions aussi pu définir un test personnalisé.

D’autres structures de contrôle sont disponibles ; elles sont détaillées dans la documentation officielle.

2.7.2. Bilan

La source de la correction est maintenant celle-ci.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
\exercice*

\begin{align*}
  (( a )) x (( b|facteur("so") )) &= (( c )) x (( d|facteur("so") )) \\
  (( a )) x (( -c|facteur("so") ))x &= (( d )) (( -b|facteur("so") )) \\
  (( a - c )) x &= (( d - b )) \\
  x &= \frac{(( d - b ))}{(( a - c ))} \\
  x &
        (* if 100*(d-b)/(a-c) == (100*(d-b)/(a-c))|int *)
            =
        (* else *)
            \simeq
        (* endif *)
        \numprint{(( ((d-b)/(a-c)) | round(2) ))}
\end{align*}

L'unique solution est
$x
(* if 100*(d-b)/(a-c) == (100*(d-b)/(a-c))|int *)
    =
(* else *)
    \simeq
(* endif *)
\numprint{(( ((d-b)/(a-c)) | round(2) ))}$.

Elle produit ce résultat. Reste à traiter le problème de l’affichage des nombres entiers (3,0 au lieu de 3).

2.8. Affichage des nombres, et filters personnalisés

2.8.1. Problème

Afficher un nombre n’est pas aussi simple qu’il n’y paraît. Dans notre exemple, le code produit 3,0 plutôt que 3, à cause de Python qui manipule des flottants, et écrit donc la première version pour insister sur le type flottant plutôt qu’entier. Mais il y a bien pire.

Supposons par exemple que nous voulons afficher l’équation de la droite d’équation \(y=ax+b\), où \(a\) et \(b\) sont des nombres entiers. A priori, utiliser y=\numprint{(( a ))}x+\numprint{(( b ))} dans notre template devrait faire l’affaire, non ? Non. Plusieurs problèmes peuvent se poser.

  • Si \(a=0\) et \(b=2\), nous obtenons y=0x+2 au lieu de y=2.
  • Si \(a=1\) et \(b=2\), nous obtenons y=1x+2 au lieu de y=x+2.
  • Si \(a=-1\) et \(b=2\), nous obtenons y=-1x+2 au lieu de y=-x+2.
  • Si \(a=2\) et \(b=0\), nous obtenons y=2x+0 au lieu de y=2x.
  • Si \(a=2\) et \(b=-2\), nous obtenons y=2x+-2 au lieu de y=2x-2.
  • Si \(a=0\) et \(b=0\), nous obtenons y=0x+0 au lieu de y=0.

Cela fait beaucoup de cas à traiter. Tous (sauf le dernier) peuvent être résolu en utilisant le filter pyromaths.outils.jinja2utils.facteur().

2.8.2. Filters personnalisés

Un filter est une fonction qui peut être transmise au template afin d’être appelée depuis le template. Ils sont décrits sur la documentation officielle.

Une fonction du module pyromaths.outils.jinja2utils existe pour corriger les problèmes cités plus haut : facteur() permet de formatter correctement les facteurs dans une expression. Encore faut-il que cette fonction soit accessible depuis le template \(LaTeX\).

Ajoutons la méthode suivante à la classe EquationPremierDegre4 :

1
2
3
4
5
6
7
    @property
    def environment(self):
        environment = super().environment
        environment.filters.update({
            'facteur': facteur,
            })
        return environment

Celle-ci a pour effet d’ajouter à l’environnement jinja2 la fonction facteur() comme un filter, qui est alors accessible depuis le template. Dans cette ligne, le nombre \(\frac{d-b}{a-c}\) est arrondi à deux décimales après la virgule, et affiché en respectant les règles françaises (notamment avec une virgule comme séparateur décimal).

14
        (( ((d-b)/(a-c))|facteur("2") ))

Un filter n’est rien d’autre qu’une fonction python. D’autres filters peuvent donc être définis et utilisés à votre convenance.

Note

Pour revenir au problème du début de cette partie, afficher l’équation d’une droite peut se faire en utilisant le code suivant, qui prend en charge tous les cas particuliers décrits plus haut.

(* if a == 0 and b == 0 *)
    y = 0
(* else *)
    y = (( a|facteur("*x") )) (( b|facteur("*so") ))
(* endif *)
  • Si \(a=2\) et \(b=3\), le code \(\LaTeX\) produit est y=2x+3.
  • Si \(a=0\) et \(b=0\), le test if permet d’afficher la bonne équation.
  • Si \(a=0\) l’option "*x" permet de ne rien afficher.
  • Si \(a=1\) l’option "*x" permet d’afficher x plutôt que 1x.
  • Si \(a=-1\) l’option "*x" permet d’afficher x plutôt que -1x.
  • Si \(b=0\), l’option "*so" n’affiche pas l’ordonnée à l’origine.
  • Si \(b=-2\), le signe négatif est correctement affiché.

Des exemples d’utilisation de cette fonction sont fournis avec sa documentation.

2.8.3. Arrondis étranges

Un autre problème pouvant survenir avec l’affichage des nombres est la présence d’arrondis pour le moins étranges. Par exemple, en Python, l’expression 1.1 + 2.2 produit 3.3000000000000003. C’est normal, car les nombres étant stockés en binaire, les nombres 1,1 et 2,2, bien qu’étant des nombres « ronds » en décimal, ne le sont pas en binaire, et les approximations utilisées produisent ce résultat.

Pour éviter ce genre d’arrondi, il est possible d’utiliser le type decimal.Decimal pour les nombres à virgule. Cette classe est conçue comme étant une représentation « pour les humains » d’un nombre à virgule (contrairement au type float, qui est une représentation « pour les ordinateurs »), et arrondi correctement les calculs. Nous n’en avons a priori pas besoin ici.

2.8.4. Bilan

Nous obtenons cet exercice. Il est quasiment terminé, non ? Non ! Car voici… (musique terrifiante) les cas particuliers…

2.9. Gestion des cas particuliers

Deux problèmes subsistent.

  • Dans certains cas (par exemple pyromaths generate EquationPremierDegre4:15, les deux coefficients \(a\) et \(c\) de l’équation \(ax+b=cx+d\) sont égaux, et notre programme, qui suppose qu’il existe une solution unique, essaye de la calculer, et divise par 0.

  • Lorsque nous arrivons (par exemple) à l’équation 2x=6, l’étape suivante est de diviser les deux membres par deux. Mais cette étape est inutile lorsque, par hasard, x est multiplié par 1, comme dans l’exemple suivant.

    ../_images/1x.png

Il y a trois manières de résoudre ces problèmes. Elles ne sont pas exclusives, et il en existe d’autres.

2.9.1. Prise en compte avec Python

Dans ce cas-là, la résolution se fait en Python. Le code \(LaTeX\) est donc réduit au minimum.

1
2
3
4
5
6
7
\exercice*

\begin{align*}
    (( calculs|join("\n") ))
\end{align*}

(( conclusion ))

C’est en Python, en revanche, que tous les cas particuliers sont traités. Nous avons donc ajouté deux variables au contexte : calculs contenant la liste des étapes de calcul, et conclusion contenant la phrase de conclusion.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class EquationPremierDegre61(Jinja2Exercise):
    """Résolution d'équations du premier degré à coefficients entiers."""
    tags = ['Troisième', 'Équation', 'Premier degré']

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        a = random.choice([1, -1]) * random.randint(2, 9)
        b = random.choice([1, -1]) * random.randint(2, 9)
        c = random.choice([1, -1]) * random.randint(2, 9)
        d = random.choice([1, -1]) * random.randint(2, 9)

        calculs = [
                r"{a}x{b:+d} &= {c}x{d:+d}\\".format(a=a, b=b, c=c, d=d),
                r"{a}x{c:+d}x &= {d}{b:+d}\\".format(a=a, b=-b, c=-c, d=d),
            ]
        if a-c == 0:
            calculs.append(r"0 &= {}\\".format(d-b))
            if d == b:
                conclusion = u"Puisque $0=0$ est toujours vrai, alors l'équation a une infinité de solutions : tous les nombres réels sont des solutions."
            else:
                conclusion = u"Puisque $0={}$ est toujours faux, alors l'équation n'a aucune solution.".format(d-b)
        elif a-c == -1:
            calculs.append(r"-x &= {}\\".format(d-b))
            calculs.append(r"(-1)\times -x&= (-1)\times {}\\".format(d-b))
            calculs.append(r"x&={}\\".format(b-d))
            conclusion = r"L'unique solution est $x = {}$.".format(b-d)
        elif a-c == 1:
            calculs.append(r"x &= {}\\".format(d-b))
            conclusion = r"L'unique solution est $x = {}$.".format(d-b)
        else:
            calculs.append(r"{}x &= {}\\".format(a-c, d-b))
            calculs.append(r"x&=\frac{{ {} }}{{ {} }}\\".format(d-b, a-c))
            solution = facteur(float(d-b)/float(a-c), "2")
            if 100*(float(d-b)/float(a-c)) == int(100 * float(d-b)/float(a-c)):
                calculs.append(r"x&={}\\".format(solution))
                conclusion = r"L'unique solution est $x = {}$.".format(solution)
            else:
                calculs.append(r"x&\simeq{}\\".format(solution))
                conclusion = r"L'unique solution est $x \simeq {}$.".format(solution)
        self.context = {
            "a": a,
            "b": b,
            "c": c,
            "d": d,
            "calculs": calculs,
            "conclusion": conclusion,
            }

Le code est plus complet, mais plus difficile à lire.

2.9.2. Prise en compte avec Jinja2

Puisque les cas particuliers sont traités avec Jinja2, le code Python est réduit au minimum (il n’a pas été modifié depuis la version précédente).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class EquationPremierDegre62(Jinja2Exercise):
    """Résolution d'équations du premier degré à coefficients entiers."""
    tags = ['Troisième', 'Équation', 'Premier degré']

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.context = {
            "a": random.choice([1, -1]) * random.randint(2, 9),
            "b": random.choice([1, -1]) * random.randint(2, 9),
            "c": random.choice([1, -1]) * random.randint(2, 9),
            "d": random.choice([1, -1]) * random.randint(2, 9),
            }

    @property
    def environment(self):
        environment = super().environment
        environment.filters.update({
            'facteur': facteur,
            })
        return environment

Le code \(LaTeX\), en revanche, est plus fourni.

\exercice*

\begin{align*}
  (( a )) x (( b|facteur("so") )) &= (( c )) x (( d|facteur("so") )) \\
  (( a )) x (( -c|facteur("so") ))x &= (( d )) (( -b|facteur("so") )) \\
  (* if a == c *)
    0 &= (( d - b )) \\
  (* else *)
    (* if a - c == 1 *)
      % Rien
    (* elif a - c == -1 *)
      -x &= (( d - b )) \\
      -1 \times -x &= -1 \times (( d - b )) \\
    (* else *)
      (( a - c )) x &= (( d - b )) \\
      x &= \frac{(( d - b ))}{(( a - c ))} \\
    (* endif *)
    x &
         (* if 100*(d-b)/(a-c) == (100*(d-b)/(a-c))|int *)
             =
         (* else *)
             \simeq
         (* endif *)
         (( ((d-b)/(a-c)) | facteur("2") ))
  (* endif *)
\end{align*}

(* if a == c and b == d *)
  Puisque $0=0$ est toujours vrai, l'équation a une infinité de solutions : tous les nombres réels.
(* elif a == c and b != d *)
  Puisque $0=((d - b))$ est toujours faux, l'équation n'a pas de solutions.
(* else *)
  L'unique solution est
  $x
  (* if 100*(d-b)/(a-c) == (100*(d-b)/(a-c))|int *)
      =
  (* else *)
      \simeq
  (* endif *)
  (( ((d-b)/(a-c)) | facteur("2") ))$.
(* endif *)

Encore une fois, le code est plus complet, mais plus difficile à lire.

2.9.3. Suppression des cas particuliers

La méthode la plus confortable dans les cas simples est d’exclure les cas particuliers. Pour cela, au lieu d’accepter n’importe quel tirage de nos coefficients, s’ils ne nous conviennent pas, nous recommençons.

Avec cette méthode, pas besoin de toucher aux templates : nous modifions simplement le constructeur de la classe EquationPremierDegre63.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        while True:
            a = random.choice([1, -1]) * random.randint(2, 9)
            b = random.choice([1, -1]) * random.randint(2, 9)
            c = random.choice([1, -1]) * random.randint(2, 9)
            d = random.choice([1, -1]) * random.randint(2, 9)

            if abs(a-c) == 1:
                # 1x ou -1x sera affiché à un moment ou à un autre. Nous excluons ce cas.
                continue
            if a == c:
                # Aucune solution, ou une infinité de solutions. Nous excluons ce cas.
                continue
            break

        self.context = {
            "a": a,
            "b": b,
            "c": c,
            "d": d,
            }

2.9.4. Bilan

Supprimer les cas particuliers est sans doute le plus confortable pour écrire un exercice. Mais c’est aussi moins riche pour les élèves.

Il n’y a pas de meilleure solution ici ; faites ce qui vous paraît le moins pire.

2.10. Finalisation

L’exercice est bientôt prêt à être publié.

2.10.1. Créer les vignettes

Si vous lancez la version graphique de Pyromaths, vous remarquez que l’aperçu de votre exercice n’est pas disponible ; il manquera aussi sur la version en ligne. C’est normal : il n’a pas encore été généré. Corrigez cela avec la commande suivante.

$ utils/creer-vignettes.py

Cette commande détecte les vignettes manquantes (ou celles pour lesquelles l’exercice a été modifié), et les génère.

2.10.2. Ajouter des tests

Dans quelques mois ou années, quelqu’un (peut-être vous) voudrait modifier quelque chose de Pyromaths, en se demandant si cela va « casser » un exercice. Pour être sûr que votre exercice soit préservé, il serait sage de le tester. L’ajout d’un test se fait avec la commande suivante.

$ pyromaths test create EquationPremierDegre

Cette commande va générer un exercice, l’afficher (dans un lecteur de pdf externe), et vous demander confirmation. Si l’exercice est correct, validez, et cet exercice sera ajouté aux tests.

Si vous voulez ajouter un exercice particulier (car vous savez qu’il correspond à un cas très particulier), ajouter ce numéro d’exercice à votre commande (1729 ici).

$ pyromaths test create EquationPremierDegre:1729

Plus tard, pour vérifier que votre exercice n’a pas été modifier, vérifiez les tests en utilisant ce même programme.

$ pyromaths test check

2.11. Publication !

2.11.1. Ajout des fichiers créés ou modifiés

Utilisez git add pour ajouter les fichiers créés ou modifiés. À priori, cela concerne au moins :

  • un fichier python contenant la classe de votre exercice (dans un des dossiers pyromaths/ex/*) ;
  • deux fichiers de template \(LaTeX\) (dans le dossier pyromaths/data/exercices/templates) ;
  • la vignette, et le fichier md5sum.json (dans le dossier pyromaths/data/exercices/img) ;
  • les fichiers de test (dans le dossier pyromaths/data/exercices/tests) ;
  • et peut-être d’autre, selon votre travail.

2.11.2. Proposition de l’exercice

Il ne reste qu’à nous proposer votre exercice, de préférence en utilisant une pull request sur Gitlab, à défaut en prenant contact avec nous sur le forum.

2.12. Conclusion

Merci de contribuer à Pyromaths !

«  1. Introduction   ::   Contenu   ::   3. Classes et Fonctions  »