Conversion d'un exercice au nouveau format

Les propositions de correctifs ou d'exercices pour Pyromaths.

Modérateur : Développeurs

spalax
Messages : 86
Inscription : 20 juil. 2011, 02:23
Localisation : Isère
Contact :

Conversion d'un exercice au nouveau format

Message par spalax » 08 oct. 2014, 11:21

Bonjour,
souhaitant créer un nouvel exercice en m'inspirant d'un autre (factorisation en troisième), j'ai converti l'ancien exercice au nouveau format.
Cordialement,
LP
diff --git a/src/pyromaths/ex/troisiemes/factorisations.py b/src/pyromaths/ex/troisiemes/factorisations.py
index 1187cd4..e415c31 100644
--- a/src/pyromaths/ex/troisiemes/factorisations.py
+++ b/src/pyromaths/ex/troisiemes/factorisations.py
@@ -21,46 +21,56 @@
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 #
 from random import randrange, shuffle
+from pyromaths import ex
 from pyromaths.outils import Priorites3
 from pyromaths.classes.PolynomesCollege import factoriser
 
-def factorisation():
+class Factorisation(ex.TexExercise):
     """Génère un exercice de factorisation utilisant les identités remarquables ou
     la distributivité
     """
-    l = [randrange(1, 11) for dummy in range(21)]
-    diff = [True, False, False]
-    shuffle(diff)
-    exo = [id_rem1, id_rem2]
-    lexo = [exo[randrange(2)](l[0], l[1])]
-    lexo.append(id_rem3(l[2], l[3]))
-    lexo.append(id_rem3bis(l[4], l[5], l[6]))
-    lexo.append(facteur_commun1(l[7:13], diff=diff.pop()))
-    shuffle(lexo)
-    exo = [facteur_commun2, facteur_commun3]
-    shuffle(exo)
-    lexo.append(exo[0](l[13:17], diff=diff.pop()))
-    lexo.append(exo[1](l[17:21], diff=diff.pop()))
 
-    exo = ["\\exercice", u"Factoriser chacune des expressions littérales suivantes :"]
-    exo.append("\\begin{multicols}{2}")
-    cor = ["\\exercice*", u"Factoriser chacune des expressions littérales suivantes :"]
-    cor.append("\\begin{multicols}{2}")
-    for i in range(len(lexo)):
-        p = [lexo]
-        while True:
-            fact = factoriser(p[-1])
-            if fact:
-                p.append(fact)
-            else: break
-        p = Priorites3.texify([Priorites3.splitting(p[j]) for j in range(len(p))])
-        cor.append('\\\\\n'.join(['$%s=%s$' % (chr(i + 65), p[j]) for j in range(len(p) - 1)]))
-        cor.append('\\\\')
-        cor.append('\\fbox{$%s=%s$}\\\\\n' % (chr(i + 65), p[-1]))
-    exo.append('\\\\\n'.join(['$%s=%s$' % (chr(i + 65), Priorites3.texify([Priorites3.splitting(lexo)])[0]) for i in range(len(lexo))]))
-    exo.append("\\end{multicols}")
-    cor.append("\\end{multicols}")
-    return exo, cor
+    description = u'Factorisations'
+
+    def __init__(self):
+        l = [randrange(1, 11) for dummy in range(21)]
+        diff = [True, False, False]
+        shuffle(diff)
+        exo = [id_rem1, id_rem2]
+        self.lexo = [exo[randrange(2)](l[0], l[1])]
+        self.lexo.append(id_rem3(l[2], l[3]))
+        self.lexo.append(id_rem3bis(l[4], l[5], l[6]))
+        self.lexo.append(facteur_commun1(l[7:13], diff=diff.pop()))
+        shuffle(self.lexo)
+        exo = [facteur_commun2, facteur_commun3]
+        shuffle(exo)
+        self.lexo.append(exo[0](l[13:17], diff=diff.pop()))
+        self.lexo.append(exo[1](l[17:21], diff=diff.pop()))
+
+
+    def tex_statement(self):
+        exo = ["\\exercice", u"Factoriser chacune des expressions littérales suivantes :"]
+        exo.append("\\begin{multicols}{2}")
+        exo.append('\\\\\n'.join(['$%s=%s$' % (chr(i + 65), Priorites3.texify([Priorites3.splitting(self.lexo)])[0]) for i in range(len(self.lexo))]))
+        exo.append("\\end{multicols}")
+        return exo
+
+    def tex_answer(self):
+        cor = ["\\exercice*", u"Factoriser chacune des expressions littérales suivantes :"]
+        cor.append("\\begin{multicols}{2}")
+        for i in range(len(self.lexo)):
+            p = [self.lexo]
+            while True:
+                fact = factoriser(p[-1])
+                if fact:
+                    p.append(fact)
+                else: break
+            p = Priorites3.texify([Priorites3.splitting(p[j]) for j in range(len(p))])
+            cor.append('\\\\\n'.join(['$%s=%s$' % (chr(i + 65), p[j]) for j in range(len(p) - 1)]))
+            cor.append('\\\\')
+            cor.append('\\fbox{$%s=%s$}\\\\\n' % (chr(i + 65), p[-1]))
+        cor.append("\\end{multicols}")
+        return cor
 
 def id_rem1(a, b, details=1):
     """Construit un Polynome de la forme ax^2+2abx+b^2
@@ -78,7 +88,6 @@ def id_rem3(a, b, details=1):
 def id_rem3bis(a, b, c, details=1):
     """Construit un Polynome de la forme (ax+b)^2-c^2
     Renvoie une chaine"""
-    sgn = randrange(2)
     lpolynomes = ['Polynome([[%r, 1], [%r, 0]], details=%s)**2' % (a * (-1) ** randrange(3), b * (-1) ** randrange(3), details)]
     lpolynomes.append('Polynome([[%r, %r]], details=%s)' % (c ** 2, 2 * randrange(2), details))
     shuffle(lpolynomes)
@@ -147,4 +156,3 @@ def facteur_commun3(lcoeff, details=1, diff=False):
         poly += lpolynomes
     return poly
 
-factorisation.description = u'Factorisations'

Avatar de l’utilisateur
Jérôme
Administrateur - Site Admin
Messages : 1083
Inscription : 26 août 2006, 13:10
Localisation : Nantes
Contact :

Re: Conversion d'un exercice au nouveau format

Message par Jérôme » 08 oct. 2014, 11:24

Bonjour et merci pour cette contribution.
Peux-tu nous dire quelles sont les différences entre ton exercice et celui qui existait précédemment ?
Pyromaths génère des fiches d'exercices et leur corrigé en toute simplicité.
Un programme multi-plateformes libre et gratuit sous licence GPL

spalax
Messages : 86
Inscription : 20 juil. 2011, 02:23
Localisation : Isère
Contact :

Re: Conversion d'un exercice au nouveau format

Message par spalax » 08 oct. 2014, 11:53

Jérôme a écrit :Peux-tu nous dire quelles sont les différences entre ton exercice et celui qui existait précédemment ?
Aucune : l'exercice est exactement le même.

Mais je vais créer un nouvel exercice pour mes secondes : résoudre l'équation (4x-4)^2-9=0. La première étape est de la factorisation (qui réutilisera l'exercice de troisième dont nous parlons ici), et la seconde étape est la résolution de l'équation produit.

Je veux leur imprimer des fiches d'exercices pour demain : j'espère avoir fini dans la journée :P

Avatar de l’utilisateur
Jérôme
Administrateur - Site Admin
Messages : 1083
Inscription : 26 août 2006, 13:10
Localisation : Nantes
Contact :

Re: Conversion d'un exercice au nouveau format

Message par Jérôme » 08 oct. 2014, 15:25

Super :)
Bon courage alors. On intégrera cela dès que possible.
Pyromaths génère des fiches d'exercices et leur corrigé en toute simplicité.
Un programme multi-plateformes libre et gratuit sous licence GPL

spalax
Messages : 86
Inscription : 20 juil. 2011, 02:23
Localisation : Isère
Contact :

Re: Conversion d'un exercice au nouveau format

Message par spalax » 09 oct. 2014, 16:04

J'ai un problème lors de la résolution des équations : arrive un moment où je me retrouve avec une expression mathématique sous la forme d'une chaîne de caractères Polynome([[2.0, 1], [9.0, 0]], "x", 1). J'ai vu ailleurs dans le code qu'une manière de manipuler ces chaînes de caractère est par un eval(expression). Mais le problème est que la classe polynôme n'accepte que deux arguments. En fait, je ne sais pas à quoi sert ce troisième argument 1. Dans l'exercice de factorisation en troisième (fichier ex/troisiemes/factorisation.py), les polynômes sont créés (sous forme de chaîne de caractères) avec un argument details=1, argument qui n'apparait pas dans la classe Polynomes.

Questions :

- Comment faire pour manipuler mon polynôme que je récupère sous forme de chaîne de caractères ?
- À quoi sert cet argument details ?

Et la dernière question, qui m'éntéresse particulièrement :

Pourquoi manipuler des expressions mathématiques sous forme de chaînes de caractères, qu'il faut analyser à nouveau à chaque fois qu'on s'en sert ? Pourquoi ne pas manipuler ces expressions sous la forme d'arbres ? Par exemple, '(3x+2)×(x-1)+1' serait représenté par l'arbre (j'assimile ici éléments de l'arbre et constructeur de la classe manipulant cet arbre) : Somme(Produit(Polynome((2, 3), 'x'), Polynome((-1, 1), 'x')), Entier(1)) (où Polynome est une classe héritant de la classe Somme). Naïvement, si je devais écrire Pyromaths à partir de zéro, je manipulerais ce genre d'expressions plutôt que des chaînes de caractères. Peut-être avez vous pensé à ce genre de chose, et l'avez abandonnée pour une raison qui m'échappe ?

Avatar de l’utilisateur
Jérôme
Administrateur - Site Admin
Messages : 1083
Inscription : 26 août 2006, 13:10
Localisation : Nantes
Contact :

Re: Conversion d'un exercice au nouveau format

Message par Jérôme » 09 oct. 2014, 17:31

Bonjour,
voici le contenu de PolynomeCollege.py
class Polynome():
    """Cette classe crée la notion de polynômes.

        >>> from pyromaths.classes.PolynomesCollege import Polynome
        >>> Polynome([[2,2],[3,1],[4,0]], 'z')
        Polynome([[2, 2], [3, 1], [4, 0]], "z", 0)
        >>> Polynome("2y^2+3y+4")
        Polynome([[2, 2], [3, 1], [4, 0]], "y", 0)

    Les variables e, i, j, l, o, O sont à éviter pour des raisons de lisibilité (l, o, O) ou parce qu'elles
    sont utilisées comme constantes (e, i, j).
    """

    def __init__(self, monomes, var=None, details=0):
        """Crée un polynôme. Si ``var == None`` alors la variable est ``x``.

        *details* donne le niveau de détails attendus dans les développements et réductions :

        - 0 : pas de détails : 3x+4x => 7x
        - 1 : ordonne avant de réduire
        - 2 : ordonne puis factorise les réductions : 3x+4x=(3+4)x
        - 3 : comme 2 et détaille les produits : 2x*3x=2*3*x*x

            >>> from pyromaths.classes.PolynomesCollege import Polynome
            >>> Polynome([[2,2],[3,1],[4,0]], 'z')
            Polynome([[2, 2], [3, 1], [4, 0]], "z", 0)
            >>> Polynome("2y^2+3y+4")
            Polynome([[2, 2], [3, 1], [4, 0]], "y", 0)
            >>> Polynome([[1, 1], [2, 2]])
            Polynome([[1, 1], [2, 2]], "x", 0)
            >>> Polynome("Fraction(1,7)x^2-Fraction(3,8)x-1")
            Polynome([[Fraction(1, 7), 2], [Fraction(-3, 8), 1], [-1, 0]], "x", 0)
        """
J'espère que ça suffira à t'éclairer.

Concernant le dernier point, je n'y ai tout simplement jamais pensé... J'en suis un peu gêné, car à première vue, ça paraît très performant comme méthode.
Pyromaths génère des fiches d'exercices et leur corrigé en toute simplicité.
Un programme multi-plateformes libre et gratuit sous licence GPL

spalax
Messages : 86
Inscription : 20 juil. 2011, 02:23
Localisation : Isère
Contact :

Re: Conversion d'un exercice au nouveau format

Message par spalax » 09 oct. 2014, 20:16

Jérôme a écrit :Bonjour,
voici le contenu de PolynomeCollege.py
(...)
J'espère que ça suffira à t'éclairer.
Honte à moi : je n'avais regardé que la classe Polynome de Polynome.py.
Jérôme a écrit : Concernant le dernier point, je n'y ai tout simplement jamais pensé... J'en suis un peu gêné, car à première vue, ça paraît très performant comme méthode.
Ça vous intéresse si je commence à implémenter ça, pour voir à quoi ça pourrait ressembler ?

Avatar de l’utilisateur
Jérôme
Administrateur - Site Admin
Messages : 1083
Inscription : 26 août 2006, 13:10
Localisation : Nantes
Contact :

Re: Conversion d'un exercice au nouveau format

Message par Jérôme » 09 oct. 2014, 21:10

Ben oui, mais ça veut dire tout réécrire derrière... :'(
Moi, je ne m'en chargerai pas avant 2016, c'est sur.
Pyromaths génère des fiches d'exercices et leur corrigé en toute simplicité.
Un programme multi-plateformes libre et gratuit sous licence GPL

spalax
Messages : 86
Inscription : 20 juil. 2011, 02:23
Localisation : Isère
Contact :

Re: Conversion d'un exercice au nouveau format

Message par spalax » 12 nov. 2014, 10:45

Bonjour,
je propose une preuve de concept pour une version du moteur de Pyromaths avec des objets, et un arbre syntaxique abstrait. C'est une première ébauche, pour voir à quoi ça pourrait ressembler. Notamment, le rendu n'est pas encore très bon (les étapes de calcul ne sont pas toujours celles que nous utilisons, ni que nous voulons que nos élèves utilisent, et la gestion des colonnes est parfois chaotique). Mais avant de continuer et corriger tout ça, j'attends de voir ce que vous en pensez. C'est un exercice sur les équations produit, en seconde.

J'espère ne pas avoir reproduit ce travers.

J'ai essayé de concevoir cela dans une optique de factorisation de code, et de ré-utilisabilité.

Du coup, j'ai plusieurs questions en lien avec ce travail :
  • ça me rassurerait d'ajouter des tests de non-régression, et je n'en vois pas. Que pensez vous de tester la génération des exercices ? Chaque classe d'exercice pourrait fournir une liste de tests, contenant trois valeurs : le TeX de l'énoncé, celui de la correction, et la graine du générateur aléatoire utilisée pour obtenir ceci. Ensuite, une classe unittest se chargerait de compiler et exécuter tous ces tests.
  • Comment faites-vous pour tester les exercices que vous êtes en train de concevoir ? Pour ma part, je lance Pyromaths, sélectionne l'onglet de mon exercice, ajoute un exercice, lance la génération, choisis le répertoire de destination, encore une fois, accepte d'écraser l'ancien exercice, pour enfin voir le résultat. Je trouve que cela fait beaucoup trop de clics de souris. Y a-t-il un autre moyen que j'ai raté ? Sinon, que pensez vous d'une interface en ligne de commande ? Quelque chose comme "pyromaths -s 0 lycee.factorisation", signifiant à générer (et compiler) un exercice de la classe contenue dans le module "pyromaths.ex.lycéee.factorisation", avec une graine de 0 pour le générateur de nombres aléatoires (pour toujours tester le même exercice) ?
Voici le code, sous la forme d'un diff avec la version 14.06.

-- Louis
diff --git a/src/pyromaths/ast/__init__.py b/src/pyromaths/ast/__init__.py
new file mode 100644
index 0000000..e33e721
--- /dev/null
+++ b/src/pyromaths/ast/__init__.py
@@ -0,0 +1,164 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# 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) 2014 -- Louis Paternault
+#
+# 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
+
+"""Arbre syntaxique représentant des expressions mathématiques."""
+
+import itertools
+
+from pyromaths.ast import erreurs, niveau
+
+def tex(arg):
+    """Renvoie la code LaTeX d'un objet, si possible.
+
+    Renvoie `str(arg)` sinon.
+
+    Le code LaTeX est le résultat de la méthode `arg.__tex__`, si elle existe.
+    """
+    return getattr(arg, '__tex__', arg.__str__)()
+
+def simplifie(arg, details):
+    """Renvoie un itérateur sur les étapes de simplification d'une expression
+
+    Renvoie un itérateur sur un unique élément : l'objet lui-même, si ça n'est
+    pas possible.
+
+    L'itérateur renvoyé est le résultat de `arg.__simplifie__`, si cette
+    méthode existe.
+    """
+    if hasattr(arg, '__simplifie__'):
+        return arg.__simplifie__(details)
+    else:
+        return itertools.repeat(arg, 1)
+
+def iter_couples(*iterators):
+    """Renvoie un itérateur de tuples de l'ensemble des itérateurs en argument.
+
+    Si un itérateur termine avant les autres, son dernier élément est répété
+    jusqu'à la fin.
+
+    Tous les itérateurs donnés en argument doivent contenir au moins un élément.
+
+    >>> for (a, b) in iter_couples([1, 2, 3], ['A', 'B', 'C', 'D', 'E', 'F']):
+    ...     print(a, b)
+    (1, 'A')
+    (2, 'B')
+    (3, 'C')
+    (3, 'D')
+    (3, 'E')
+    (3, 'F')
+    """
+    counter = [len(iterators) - 1]
+    class Finished(Exception):
+        """Itérateur terminé"""
+        pass
+    def repeat_last(iterator):
+        """Itérateur identique à l'argument, avec le dernier élément répété.
+
+        - Lorsque le dernier élément est rencontré, l'exception `Finished` est
+          levée.
+        - Le dernier élément est répété à l'inifi ;
+        """
+        value = None
+        for value in iterator:
+            yield value
+        if value is None:
+            raise Exception(
+                "Chaque itérateur doit contenir au moins un élément"
+                ) # TODO être plus précis.
+        if not counter[0]:
+            raise Finished()
+        counter[0] -= 1
+        while True:
+            yield value
+    iterators = [repeat_last(iterator) for iterator in iterators]
+    try:
+        while iterators:
+            yield tuple([next(item) for item in iterators])
+    except Finished:
+        pass
+
+
+class Noeud(object):
+    """Base de l'AST. Tous les nœuds d'un AST dérivent de cette classe."""
+
+    __simplifie = None
+
+    def shuffle(self):
+        """Renvoie un objet ayant ses opérandes mélangées
+
+        L'objet renvoyé est mathématiquement identique à `self` (par exemple,
+        les opérandes d'un opérateur non commutatif comme une fraction ne sont
+        pas mélangées).
+        """
+        return self
+
+    def degre_polynome(self):
+        """Renvoie le degré de l'objet courant, considéré comme un polynôme."""
+        if self.est_nombre():
+            if float(self) == 0:
+                return -1
+            else:
+                return 0
+        raise erreurs.PasUnPolynome()
+
+    def __float__(self):
+        raise NotImplementedError(float, self)
+
+    def __int__(self):
+        raise NotImplementedError(int, self)
+
+    def __div__(self, denom):
+        from pyromaths.ast.operateurs import Fraction
+        return Fraction(self, denom)
+
+    def __simplifie__(self, __niveau):
+        self.simplifie = self
+        yield self
+
+    @property
+    def simplifie(self):
+        """Renvoie la forme simplifiée de `self`."""
+        if self.__simplifie is None:
+            self.__simplifie = list(simplifie(self, niveau.GAUSS))[-1]
+        return self.__simplifie
+
+    @simplifie.setter
+    def simplifie(self, value):
+        """Définie la forme simplifiée de `self`."""
+        self.__simplifie = value
+
+    @staticmethod
+    def produit_implicite():
+        r"""Renvoie True si le produit de `self` par un objet est implicite.
+
+        Par exemple, le produit de 2 par quelque chose est implicite (on
+        écrira $2x$ plutôt que $2\times x$.
+        """
+        return False
+
+    @staticmethod
+    def est_nombre():
+        """Renvoie True si et seulement si `self` est un nombre.
+
+        C'est-à-dire s'il ne contient pas de variables.
+        """
+        return False
diff --git a/src/pyromaths/ast/equation.py b/src/pyromaths/ast/equation.py
new file mode 100644
index 0000000..c428250
--- /dev/null
+++ b/src/pyromaths/ast/equation.py
@@ -0,0 +1,147 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# 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) 2014 -- Louis Paternault
+#
+# 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
+#
+
+
+"""Résolution d'équations
+
+TODO : Voir comment factoriser du code avec les sytèmes d'équations, les
+inéquations, et les systèmes d'inéquations.
+"""
+
+from pyromaths.ast import erreurs, niveau
+from pyromaths.ast.operateurs import Produit, Puissance, Oppose, Fraction
+from pyromaths.ast.monomes import Nombre
+from pyromaths.ast import tex, simplifie, iter_couples
+
+class Equation(object):
+    #pylint: disable=too-few-public-methods
+    """Classe permettant la résolution d'équations"""
+
+    _solutions = None
+
+    def __init__(self, gauche, droite=0):
+        self.gauche = gauche
+        if droite == 0:
+            self.droite = Nombre(0)
+        else:
+            self.droite = droite
+
+    def resout(self, details):
+        """Résout l'équation.
+
+        Renvoie une chaîne de caractères correspondant à la résolution, en
+        LaTeX.
+        """
+        if self.droite != Nombre(0):
+            raise NotImplementedError()
+
+        try:
+            degre = self.gauche.degre_polynome()
+        except erreurs.PasUnPolynome:
+            raise NotImplementedError()
+        if not self.gauche.est_reduit():
+            raise NotImplementedError()
+        if degre != 1:
+            raise NotImplementedError()
+
+        etapes = []
+        gauche = self.gauche.operandes[0]
+        droite = Oppose(self.gauche.operandes[1])
+        etapes.append((gauche, droite))
+
+        for (gauche, droite) in iter_couples(
+                simplifie(gauche, details),
+                simplifie(droite, details)
+            ):
+            etapes.append((gauche, droite))
+
+        (coef, variable) = gauche.coef_variable()
+        if float(coef) != 1:
+            droite = Fraction(droite, coef)
+            etapes.append((variable, droite))
+
+        etapes.extend([
+            (variable, fraction)
+            for fraction
+            in simplifie(droite, details)
+            ])
+
+        self.solutions = [droite.simplifie]
+
+        return (
+            u"\\begin{align*}\n" +
+            u"\\\\\n".join([
+                u"{} &= {}".format(tex(gauche), tex(droite))
+                for (gauche, droite) in etapes
+                ]) +
+            u'\n\\end{align*}\n'
+            )
+
+    @property
+    def solutions(self):
+        """Liste des solutions de l'équation."""
+        if self._solutions is None:
+            list(self.resout(niveau.GAUSS))
+        return self._solutions
+
+    @solutions.setter
+    def solutions(self, value):
+        """Affecte les solutions de l'équation."""
+        self._solutions = value
+
+
+
+class EquationProduit(Equation):
+    #pylint: disable=too-few-public-methods
+    """Résout une équation produit."""
+
+    def __init__(self, expression):
+        super(EquationProduit, self).__init__(expression)
+        self.solutions = []
+
+    def resout(self, details):
+        texte = []
+        if isinstance(self.gauche, Produit):
+            texte.append(
+                ur'\begin{{multicols}}{{{}}}'.format(len(self.gauche))
+                )
+
+            for facteur in self.gauche:
+                equation = Equation(facteur)
+                texte.append(equation.resout(details))
+                self.solutions.extend(equation.solutions)
+                texte.append('\n\\columnbreak\n')
+
+            texte.append(ur'\end{multicols}')
+
+            return "\\\\\n".join(texte)
+        if isinstance(self.gauche, Oppose):
+            self.gauche = self.gauche.operande
+        if (
+                isinstance(self.gauche, Puissance)
+                and self.gauche.degre.est_nombre()
+            ):
+            equation = Equation(self.gauche.operande)
+            texte.append(equation.resout(details))
+            self.solutions.extend(equation.solutions)
+            return "\\\\\n".join(texte)
diff --git a/src/pyromaths/ast/erreurs.py b/src/pyromaths/ast/erreurs.py
new file mode 100644
index 0000000..4e277af
--- /dev/null
+++ b/src/pyromaths/ast/erreurs.py
@@ -0,0 +1,27 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# 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) 2014 -- Louis Paternault
+#
+# 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
+
+"""Erreurs relatives aux arbres syntaxiques abstrats."""
+
+class PasUnPolynome(Exception):
+    """La forme considérée n'est pas un polynôme."""
+    pass
diff --git a/src/pyromaths/ast/factorisation.py b/src/pyromaths/ast/factorisation.py
new file mode 100644
index 0000000..eb3801a
--- /dev/null
+++ b/src/pyromaths/ast/factorisation.py
@@ -0,0 +1,120 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# 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) 2014 -- Louis Paternault
+#
+# 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
+#
+
+"""Factorisation d'AST."""
+
+from pyromaths.ast.operateurs import \
+        Produit, Somme, Puissance, RacineCarree, Oppose
+from pyromaths.ast.monomes import Entier, Nombre
+from pyromaths.ast.polynome import Polynome
+
+class IdentiteRemarquable(object):
+    #pylint: disable=too-few-public-methods
+    """Polynôme, analysé comme une identité remarquable."""
+
+    def __init__(self, expression):
+        self.expression = expression
+        self.final = None
+
+    def iter_etapes(self):
+        """Renvoie un itérateur sur les étapes de factorisation.
+
+        Les éléments renvoyés sont de type AST.
+        """
+        if self.expression.degre != 2:
+            raise NotImplementedError()
+        (coef0, coef1, coef2) = self.expression.coefficients
+        variable = self.expression.variables[0]
+
+        if coef1 == 0:
+            # Identitié remarquable de la forme a²-b²=0
+            if coef2 * coef0 >= 0:
+                # Polynome de la forme ax^2+b^2 (avec a et b positifs) non
+                # factorisable
+                raise NotImplementedError()
+
+            if coef2 > 0:
+                yield Somme(
+                    Puissance(
+                        Produit(RacineCarree(Nombre(coef2)), variable),
+                        Nombre(2)
+                        ),
+                    Oppose(Puissance(RacineCarree(Nombre(-coef0)), Nombre(2))),
+                    )
+                self.final = Produit(
+                    Somme(
+                        Produit(RacineCarree(Nombre(coef2)), variable),
+                        Oppose(RacineCarree(Nombre(-coef0))),
+                        ),
+                    Somme(
+                        Produit(RacineCarree(Nombre(coef2)), variable),
+                        RacineCarree(Nombre(-coef0)),
+                        ),
+                    )
+                yield self.final
+            else:
+                oppose = Polynome(-coef0, -coef1, -coef2)
+                yield oppose.simplifie
+
+                etape = None
+                for etape in IdentiteRemarquable(oppose).iter_etapes():
+                    yield etape
+                self.final = etape
+        elif coef1 != 0:
+            if coef0 < 0:
+                for etape in IdentiteRemarquable(
+                        Polynome(-coef0, -coef1, -coef2)
+                    ):
+                    yield Oppose(etape).simplifie
+            else:
+                if coef1 > 0:
+                    signe = lambda x: x
+                else:
+                    signe = lambda x: Oppose(x) #pylint: disable=unnecessary-lambda
+                yield Somme(
+                    Puissance(
+                        RacineCarree(Nombre(coef0)),
+                        Entier(2)
+                        ),
+                    Produit(
+                        Entier(signe(2)),
+                        RacineCarree(Nombre(coef0)),
+                        RacineCarree(Nombre(coef2)),
+                        variable
+                        ),
+                    Puissance(
+                        Produit(RacineCarree(Nombre(coef2)), variable),
+                        Entier(2)
+                        )
+                    )
+                self.final = Puissance(
+                    Somme(
+                        Produit(
+                            RacineCarree(Nombre(coef2)),
+                            variable,
+                            ),
+                        signe(RacineCarree(Nombre(coef0))),
+                        ),
+                    Nombre(2),
+                    )
+                yield self.final
diff --git a/src/pyromaths/ast/monomes.py b/src/pyromaths/ast/monomes.py
new file mode 100644
index 0000000..6f1299d
--- /dev/null
+++ b/src/pyromaths/ast/monomes.py
@@ -0,0 +1,88 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# 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) 2014 -- Louis Paternault
+#
+# 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
+
+"""Monomes (nombres, variables, etc.)"""
+
+from pyromaths.ast import Noeud, tex
+
+class Nombre(Noeud):
+    """Classe générique pour les nombres."""
+
+    def __init__(self, valeur):
+        super(Nombre, self).__init__()
+        self.valeur = valeur
+
+    @staticmethod
+    def est_nombre():
+        return True
+
+    def __float__(self):
+        return float(self.valeur)
+
+    def __int__(self):
+        return int(self.valeur)
+
+    def __tex__(self):
+        return tex(self.valeur)
+
+    def __eq__(self, other):
+        if isinstance(other, Nombre):
+            return self.valeur == other.valeur
+        else:
+            return False # TODO Calculer la valeur de `other` ?
+
+    def __ne__(self, other):
+        return not self == other
+
+class Reel(Nombre):
+    """Nombre réel"""
+
+    def __init__(self, valeur):
+        super(Reel, self).__init__(valeur)
+
+class Entier(Reel):
+    """Nombre entier"""
+
+    def __init__(self, valeur):
+        super(Entier, self).__init__(int(valeur))
+
+class Variable(Noeud):
+    #pylint: disable=abstract-method
+    """Variable"""
+
+    def __init__(self, variable):
+        super(Variable, self).__init__()
+        self.variable = variable
+
+    @staticmethod
+    def est_nombre():
+        return False
+
+    def degre_polynome(self):
+        return 1
+
+    def __tex__(self):
+        return self.variable
+
+    @staticmethod
+    def produit_implicite():
+        return True
diff --git a/src/pyromaths/ast/niveau.py b/src/pyromaths/ast/niveau.py
new file mode 100644
index 0000000..40fb597
--- /dev/null
+++ b/src/pyromaths/ast/niveau.py
@@ -0,0 +1,32 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# 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) 2014 -- Louis Paternault
+#
+# 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
+
+"""Niveau de résolution"""
+
+GAUSS = -float('inf') # Niveau le plus élevé
+
+CM2 = 7
+SIXIEME = 6
+CINQUIEME = 5
+QUATRIEME = 4
+TROISIEME = 3
+SECONDE = 2
diff --git a/src/pyromaths/ast/operateurs.py b/src/pyromaths/ast/operateurs.py
new file mode 100644
index 0000000..a9ce4d8
--- /dev/null
+++ b/src/pyromaths/ast/operateurs.py
@@ -0,0 +1,440 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# 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) 2014 -- Louis Paternault
+#
+# 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
+
+#pylint: disable=abstract-method, star-args
+"""Opérateurs."""
+
+import random
+import math
+
+from pyromaths.outils.Arithmetique import factor_tuples, pgcd
+from pyromaths.ast import Noeud, tex, iter_couples, simplifie
+from pyromaths.ast.monomes import Variable, Entier, Nombre
+
+class Operateur(Noeud):
+    """Opérateur, avec un nombre arbitraire d'opérandes
+
+    Aucune supposition n'est faite sur le caractère commutatif ou non de
+    l'opérateur.
+    """
+    nargs = float("inf")
+
+    def __init__(self, *operandes):
+        super(Operateur, self).__init__()
+        if len(operandes) > self.nargs:
+            raise Exception("Trop d'arguments") # TODO être plus précis
+        self.operandes = operandes
+
+    def __len__(self):
+        return len(self.operandes)
+
+    def __iter__(self):
+        return iter(self.operandes)
+
+class OperateurBinaire(Operateur):
+    """Opérateur binaire, c'est-à-dire avec deux opérandes."""
+    nargs = 2
+
+    def __init__(self, *operandes):
+        super(OperateurBinaire, self).__init__()
+        if len(operandes) > self.nargs:
+            raise Exception(
+                "Erreur : l'opérateur n'accepte que {} arguments.".format(
+                    self.nargs
+                    )) # TODO Améliorer le message d'erreur
+        self.operandes = list(operandes)
+
+    def shuffle(self):
+        """Mélange la liste des opérandes. Renvoie *self*."""
+        random.shuffle(self.operandes)
+        return self
+
+class OperateurUnaire(Operateur):
+    """Opérateur unaire, c'est-à-dire opérateur ayant une unique opérande."""
+    nargs = 1
+
+    @property
+    def operande(self):
+        """Renvoit l'unique opérande"""
+        return self.operandes[0]
+
+    @operande.setter
+    def operande(self, valeur):
+        """Affecte une valeur à l'opérande."""
+        self.operandes = [valeur]
+
+    def est_nombre(self):
+        """Renvoit `True` si `self` est un nombre."""
+        return self.operande.est_nombre()
+
+
+class Somme(Operateur):
+    """Somme d'expressions"""
+
+    def degre_polynome(self):
+        """Renvoit le degré de `self`, considéré comme un polynôme."""
+        return max([operande.degre_polynome() for operande in self])
+
+    def est_reduit(self):
+        """Renvoit `True` si `self`, considéré comme un polynôme, est réduit."""
+        degres = [operande.degre_polynome() for operande in self]
+        return len(set(degres)) == len(degres)
+
+    def __tex__(self):
+        elements = []
+        for operande in self:
+            if isinstance(operande, Oppose):
+                elements.append('-')
+                elements.append(operande.operande)
+            else:
+                elements.append('+')
+                elements.append(operande)
+        if elements[0] == '+':
+            elements = elements[1:]
+        return u"".join([tex(elem) for elem in elements])
+
+    def supprime_nuls(self):
+        """Renvoit `self`, sans les opérandes égales à `Nombre(0)`.
+
+        `self` n'est pas supprimé.
+        """
+        return Somme(*[op for op in self if op != Nombre(0)])
+
+    def __simplifie__(self, details):
+        operandes = self.operandes
+        for operandes in iter_couples(*[
+                simplifie(op, details) for op in self
+            ]):
+            yield Somme(*operandes).supprime_nuls()
+        self.simplifie = Somme(*operandes).supprime_nuls()
+
+    def produit_implicite(self):
+        """Renvoit si le produit par cet objet est implicite"""
+        if len(self.operandes) == 1:
+            return self.operandes[0].produit_implicite()
+        else:
+            return False
+
+
+class Produit(Operateur):
+    """Produit d'expressions."""
+
+    def degre_polynome(self):
+        """Renvoit le degré de `self`, considéré comme un polynôme."""
+        for operande in self:
+            if operande.est_nombre():
+                if float(operande) == 0:
+                    return -1
+        return sum([operande.degre_polynome() for operande in self])
+
+    def coef_variable(self):
+        """Renvoit un couple `(coefficient, variable)`.
+
+        - coefficient est le produit des opérandes qui sont des nombres ;
+        - variable est une variable.
+
+        Lève une exception si ça n'est pas possible.
+        """
+        coef = []
+        variables = []
+        for operande in self:
+            if operande.est_nombre():
+                coef.append(operande)
+            elif isinstance(operande, Variable):
+                variables.append(operande.variable)
+            else:
+                raise Exception() # TODO plus précis
+        if len(variables) != 1:
+            raise Exception() # TODO Plus précis
+        if len(coef) == 0:
+            coef = Nombre(1)
+        elif len(coef) == 1:
+            coef = coef[0]
+        else:
+            coef = Produit(*coef)
+        return (coef, variables[0])
+
+    def __tex__(self):
+        tex_str = ""
+        dernier = None
+        for operande in self:
+            if isinstance(operande, Somme) and len(operande.operandes) > 1:
+                tex_operande = ur'\left({}\right)'.format(tex(operande))
+            else:
+                tex_operande = tex(operande)
+            tex_str += " "
+            if not (
+                    (dernier is None)
+                    or (
+                        isinstance(dernier, Nombre)
+                        and operande.produit_implicite()
+                    )
+                ):
+                tex_str += r" \times "
+            tex_str += tex_operande
+            dernier = operande
+        return tex_str
+
+    def __int__(self):
+        return reduce(
+            lambda a, b: a*b,
+            [int(item) for item in self]
+            )
+
+    def supprime_unites(self):
+        """Supprime les opérandes égales à l'unité `Nombre(1)`
+
+        `self` n'est pas modifié : un nouvel objet est renvoyé.
+        """
+        return Produit(*[op for op in self if op != Nombre(1)])
+
+    def __simplifie__(self, details):
+        for operandes in iter_couples(*[
+                simplifie(op, details) for op in self
+            ]):
+            self.simplifie = Produit(*operandes).supprime_unites()
+            yield self.simplifie
+            if Nombre(0) in self.operandes:
+                self.simplifie = Nombre(0)
+                yield self.simplifie
+                break
+        if len(self.operandes) == 0:
+            self.simplifie = Nombre(1)
+        elif len(self.operandes) == 1:
+            self.simplifie = self.operandes[0]
+        else:
+            pass
+
+def produit_ou_monome(*args):
+    """Renvoit le produit des arguments, ou l'argument seul s'il est seul."""
+    if len(args) == 1:
+        return args[0]
+    else:
+        return Produit(*args)
+
+class Puissance(OperateurBinaire):
+    """Puissance d'une expression"""
+
+    def __init__(self, operande, degre):
+        super(Puissance, self).__init__(operande, degre)
+
+    @property
+    def operande(self):
+        """Renvoit l'expression qui est élevée à la puissance `self.degre`."""
+        return self.operandes[0]
+
+    @property
+    def degre(self):
+        """Renvoit le degré auquel est élevé l'expression"""
+        return self.operandes[1]
+
+    def __tex__(self):
+        if (
+                isinstance(self.operande, Nombre)
+                or isinstance(self.operande, Variable)
+            ):
+            operande = tex(self.operande)
+        else:
+            operande = r'\left({}\right)'.format(tex(self.operande))
+        return "{}^{{{}}}".format(tex(operande), tex(self.degre))
+
+    def __simplifie__(self, details):
+        """
+
+        TODO Faire une vrai simplification
+        """
+        if self.degre.est_nombre():
+            if int(self.degre) == 0:
+                self.simplifie = Nombre(1)
+            elif int(self.degre) == 1:
+                self.simplifie = self.operande
+            else:
+                self.simplifie = self
+        yield self.simplifie
+
+    def __int__(self):
+        return int(self.operande) ** int(self.degre)
+
+    def produit_implicite(self):
+        """Renvoit si le produit par cet objet est implicite"""
+        return self.operande.produit_implicite()
+
+class RacineCarree(OperateurUnaire):
+    """Racine carrée d'un expression
+
+    TODO : En faire la sous-classe d'une classe Racine(), ou Puissance() ?
+    """
+    def __init__(self, operande):
+        super(RacineCarree, self).__init__()
+        self.operande = operande
+
+    def __float__(self):
+        return math.sqrt(float(self.operande))
+
+    def __int__(self):
+        return int(math.sqrt(int(self.operande)))
+
+    def __tex__(self):
+        return ur'\sqrt{{{}}}'.format(tex(self.operande))
+
+    def __simplifie__(self, details):
+        """Simplification
+
+        TODO Faire une version où on effectue le moins de multiplications
+        possibles.
+        """
+        valeur = int(self.operande)
+        if not self.operande.est_nombre():
+            yield Nombre(valeur)
+
+        facteurs = factor_tuples(valeur)
+        yield RacineCarree(Produit(*[
+            Puissance(Entier(premier), Entier(puissance))
+            for (premier, puissance)
+            in facteurs
+            ]))
+
+        carres = []
+        non_carres = []
+        for (premier, puissance) in facteurs:
+            if puissance // 2:
+                carres.append((premier, puissance // 2))
+            if puissance % 2:
+                non_carres.append((premier, puissance % 2))
+        carres_ast = [
+            Puissance(Entier(premier), Produit(Entier(2), Entier(puissance)))
+            for (premier, puissance)
+            in carres
+            ]
+        non_carres_ast = [
+            Puissance(Entier(premier), produit_ou_monome(Entier(puissance)))
+            for (premier, puissance)
+            in non_carres
+            ]
+        if not carres:
+            self.simplifie = RacineCarree(Produit(*non_carres_ast))
+            yield self.simplifie
+            return
+
+        yield RacineCarree(produit_ou_monome(*(
+            carres_ast
+            +
+            non_carres_ast
+            )))
+
+        carres_ast = produit_ou_monome(*[
+            Puissance(Entier(premier), Entier(puissance))
+            for (premier, puissance)
+            in carres
+            ])
+        yield RacineCarree(produit_ou_monome(*(
+            [Puissance(carres_ast, Nombre(2))]
+            +
+            non_carres_ast
+            )))
+
+        if non_carres:
+            self.simplifie = Produit(
+                carres_ast,
+                RacineCarree(Produit(*non_carres_ast))
+                )
+        else:
+            yield Produit(*carres_ast)
+            self.simplifie = Nombre(int(carres_ast))
+        yield self.simplifie
+
+
+class Oppose(OperateurUnaire):
+    """Oppose d'une expression"""
+
+    def __init__(self, operande):
+        super(Oppose, self).__init__()
+        self.operande = operande
+
+    def __float__(self):
+        return -float(self.operande)
+
+    def __int__(self):
+        return -int(self.operande)
+
+    def __tex__(self):
+        if isinstance(self.operande, Somme):
+            return ur'-\left({}\right)'.format(tex(self.operande))
+        else:
+            return ur'-{}'.format(tex(self.operande))
+
+    def __simplifie__(self, details):
+        valeur = self.operande
+        for valeur in simplifie(self.operande, details):
+            yield Oppose(valeur)
+        if isinstance(valeur, Oppose):
+            self.simplifie = valeur.operande
+            yield self.simplifie
+        else:
+            self.simplifie = Oppose(valeur)
+
+class Fraction(OperateurBinaire):
+    """Fraction"""
+
+    def __int__(self):
+        return int(self.operandes[0])/int(self.operandes[1])
+
+    def __tex__(self):
+        return u"\\frac{{{}}}{{{}}}".format(
+            *[tex(item) for item in self.operandes]
+            )
+
+    @property
+    def numer(self):
+        """Renvoit le numérateur"""
+        return self.operandes[0]
+
+    @property
+    def denom(self):
+        """Renvoit le dénominateur"""
+        return self.operandes[1]
+
+    def __simplifie__(self, details):
+        """Simplification
+
+        Ne fonctionne (pour le moment) que pour les fractions qui, au final,
+        sont des fractions d'entiers
+        """
+        for (numer, denom) in iter_couples(
+                simplifie(self.numer, details),
+                simplifie(self.denom, details)
+            ):
+            yield Fraction(numer, denom)
+        diviseur = pgcd(int(self.numer), int(self.denom))
+        if diviseur != 1:
+            yield Fraction(
+                Produit(Entier(self.numer/Entier(diviseur)), Entier(diviseur)),
+                Produit(Entier(self.denom/Entier(diviseur)), Entier(diviseur)),
+                )
+        self.simplifie = Fraction(
+            Entier(self.numer / Entier(diviseur)),
+            Entier(self.denom / Entier(diviseur)),
+            )
+        yield self.simplifie
+        if int(self.denom/diviseur) == 1:
+            self.simplifie = Entier(self.numer/diviseur)
+            yield self.simplifie
diff --git a/src/pyromaths/ast/polynome.py b/src/pyromaths/ast/polynome.py
new file mode 100644
index 0000000..72078be
--- /dev/null
+++ b/src/pyromaths/ast/polynome.py
@@ -0,0 +1,87 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# 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) 2014 -- Louis Paternault
+#
+# 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
+#
+
+"""Polynômes."""
+
+import random
+import itertools
+
+from pyromaths.ast.operateurs import Produit, Somme, Puissance
+from pyromaths.ast.monomes import Variable, Reel, Entier
+
+class Polynome(Somme):
+    #pylint: disable=abstract-method
+    """Polynôme d'une ou plusieurs variables.
+
+    La version à plusieurs variables n'est pas implémentée.
+    """
+
+    def __init__(self, *coef):
+        super(Polynome, self).__init__(*[ #pylint: disable=star-args
+            Produit(Reel(valeur), Puissance(Variable('x'), Entier(degre)))
+            for (valeur, degre)
+            in itertools.izip(coef, itertools.count(0))
+            ])
+        self.coefficients = list(coef)
+
+    @property
+    def degre(self):
+        """Renvoie le degré du polynôme."""
+        return len(self.coefficients) - 1
+
+    @property
+    def variables(self):
+        """Renvoie la liste des variables de ce polynôme."""
+        # TODO Chercher récursivement les noms des variables
+        return [Variable('x')]
+
+
+def rand_identite_remarquable_developpee(degre=2):
+    # pylint: disable=invalid-name
+    u"""Renvoie une identité remarquable (ax+b)^degre.
+
+    Cette fonction renvoie un polynôme correspondant à une identité remarquable
+    *(ax+b)^2*, *(ax-b)^2* ou *(ax+b)(ax-b)*, où *a* et *b* sont des entiers
+    non nuls compris entre -10 et 10.
+
+    :param int degre: Degré de l'indentité remarquable.
+    :return: L'identité remarquable.
+    :rtype: Somme
+    """
+    if degre == 2:
+        (a, b) = (0, 0)
+        while a*b == 0:
+            (a, b) = tuple([random.randint(-10, 10) for __ignored in range(2)])
+        identite = random.randint(0, 2)
+        if identite == 0:
+            if random.choice([True, False]):
+                return Polynome(-b**2, 0, a**2)
+            else:
+                return Polynome(b**2, 0, -a**2)
+        elif identite == 1:
+            return Polynome(b**2, 2*a*b, a**2)
+        else:
+            return Polynome(b**2, -2*a*b, a**2)
+    else:
+        raise NotImplementedError()
+
diff --git a/src/pyromaths/ex/lycee/equations_produit.py b/src/pyromaths/ex/lycee/equations_produit.py
new file mode 100644
index 0000000..3e80b4d
--- /dev/null
+++ b/src/pyromaths/ex/lycee/equations_produit.py
@@ -0,0 +1,85 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# 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) 2014 -- Jérôme Ortais (jerome.ortais@pyromaths.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
+#
+import random
+
+from pyromaths import ex
+from pyromaths.ast import polynome, tex, simplifie, niveau
+from pyromaths.ast.factorisation import IdentiteRemarquable
+from pyromaths.ast.equation import EquationProduit as Equation
+
+class EquationProduit(ex.TexExercise):
+    """Génère un exercice de résolution d'équations utilisant les identités remarquables.
+    """
+
+    enonce = u"Résoudre chacune des équations suivantes, après avoir factorisé le membre de gauche."
+    description = u'Équations produit'
+    level = u'2.Seconde'
+
+    def __init__(self):
+        self.questions = [
+                polynome.rand_identite_remarquable_developpee()
+                for __ignored
+                in range(4)
+                ]
+
+    def tex_statement(self):
+        exo = ["\\exercice", self.enonce]
+        exo.append("\\begin{multicols}{2}")
+        exo.append("\\begin{enumerate}")
+        exo.append('\n'.join([r'\item ${}=0$'.format(tex(expression.simplifie)) for expression in self.questions]))
+        exo.append("\\end{enumerate}")
+        exo.append("\\end{multicols}")
+        return exo
+
+    def tex_answer(self):
+        cor = ["\\exercice*", self.enonce]
+        cor.append("\\begin{enumerate}")
+        for question in self.questions:
+            cor.append('\\item\n')
+
+            # Factorisation
+            cor.append(u'Nous commençons par factoriser le membre de gauche.\n')
+            factorisation = IdentiteRemarquable(question)
+            cor.append(ur'\begin{align*}')
+            cor.append(ur'{} &= 0 \\'.format(tex(factorisation.expression.simplifie)))
+            for etape in factorisation.iter_etapes():
+                cor.append(ur'{} &= 0 \\'.format(tex(etape)))
+            cor.append(ur'\end{align*}')
+
+            # Résolution
+            resolution = Equation(factorisation.final)
+            cor.append(resolution.resout(details = niveau.SECONDE))
+
+            # Solutions
+            cor.append('\\\\')
+            cor.append(
+                    '\\fbox{{{}}}\\\\\n'.format(
+                        " ou ".join([
+                            '$x={}$'.format(tex(racine)) for racine in resolution.solutions
+                            ])
+                        )
+                    )
+
+        cor.append("\\end{enumerate}")
+        return cor
+
diff --git a/src/pyromaths/outils/Arithmetique.py b/src/pyromaths/outils/Arithmetique.py
index 9c111f5..ae2a55d 100644
--- a/src/pyromaths/outils/Arithmetique.py
+++ b/src/pyromaths/outils/Arithmetique.py
@@ -138,6 +138,28 @@ def factor(n):
         candidat += 1
     return premiers
 
+def factor_tuples(n):
+    """**factor_tuples**\ (*n*)
+
+    Retourne la liste des couples (facteur, puissance), de la décomposition en facteurs prmeires du nombre n.
+
+    >>> from pyromaths.outils import Arithmetique
+    >>> Arithmetique.factor_tuples(2673)
+    [(3, 5), (11, 1)]
+    >>> Arithmetique.factor_tuples(23)
+    [(23, 1)]
+    """
+    if n == 1:
+        return [(1, 1)]
+    tuples = []
+    facteurs = factor(n)
+    tuples = [(facteurs.pop(0), 1)]
+    for facteur in facteurs:
+        if facteur == tuples[-1][0]:
+            tuples[-1] = (facteur, tuples[-1][1]+1)
+        else:
+            tuples.append((facteur, 1))
+    return tuples
 
 def factorise(n):
     """**factorise**\ (*n*)

Avatar de l’utilisateur
Jérôme
Administrateur - Site Admin
Messages : 1083
Inscription : 26 août 2006, 13:10
Localisation : Nantes
Contact :

Re: Conversion d'un exercice au nouveau format

Message par Jérôme » 18 nov. 2014, 20:54

Bonsoir,
désolé pour cette réponse tardive. Comme je te l'ai déjà dit, je n'ai que peu de temps en ce moment. Je te propose un accès au serveur git. Pour cela, j'ai besoin de tes nom et prénom ainsi que d'une clé ssh.
Concernant les tests, je lance le scritp test/test_creation_de_fichiers_tex.py afin de générer un grand nombre d'exercices et vérifier qu'ils se compilent tous. Ça permet en général de voir si un exercice à des bugs. Par contre, ça ne permet pas de vérifier que le code donne toujours le même résultat. Ce que tu proposes serait par conséquent très intéressant.
À bientôt.
Pyromaths génère des fiches d'exercices et leur corrigé en toute simplicité.
Un programme multi-plateformes libre et gratuit sous licence GPL

Répondre