• Séminaire du 22 avril 2026

    14h00 — 18h00, Salle T15, bâtiment T, Campus du Tampon

    Lien pour nous rejoindre en visio

    • 14h-14h30 Le rendez-vous Jeu d’Alain busser: Retour aux jeux de Nim
    • 14h30-15h00 Retour sur la semaine des Maths: 323 élèves au tournoi d’Alquerkonané,Patrick Schilli, Alain Busser, Julien Bominthe
    • 15h-16h10 Atelier de réflexion/débat: C’est quoi le problème avec les probas? Pan des mathématiques souvent mal aimé des élèves, des étudiants et il faut le dire, de certains enseignants aussi, les probabilités posent des problèmes à beaucoup de monde. D’Alembert ne vous aurait pas jeté la pierre… A l’heure où les probabilité et les statistiques sont partout, diagnostiquer pourquoi elles sont boudées et/ou redoutées sera notre point de départ pour une réflexion pour savoir comment réconcilier les élèves et leurs enseignants avec l’apprentissage (et la beauté!) des probabilités. Marion Le Gonidec

    PAUSE

    • 16h30-18h Atelier de réflexion/test: Résolution de problèmes. Présentation et tests de ressources autour de la résolution de problèmes au cycle 3, en lien avec les programmes, Karim Bouasla, Pascal Dorr, Claire Lagarde.

  • Modélisation Python du jeu de Welter

    Le jeu de Welter

    C’est dans le cadre de recherches sur les algèbres de Kac-Moody en physique théorique, que dans les années 1950, P.C. Welter d’une part, Mikio Sato de l’autre, ont inventé le jeu étudié ici. Le jeu se joue sur un plateau similaire à celui de Sowing, avec des cases alignées. Des pions ou des jetons sont posés sur le plateau de jeu, de telle manière qu’il n’y ait jamais plus d’un pion par case.

    Un tour de jeu consiste à déplacer un pion vers la gauche, d’au moins une case, et vers une case vide (sinon il y aurait plusieurs pions dans une même case, ce qui est interdit par la règle du jeu). Par exemple à partir de la situation ci-dessus il y a 8 possibilités dont celle-ci :

    (le pion le plus dextre est parti dans la troisième case)

    Le coup ci-dessus est d’ailleurs un mauvais coup, parce que l’adversaire peut alors jouer à son tour le pion le plus dextre et arriver à cette situation bloquante :

    En effet, dès que tous les pions sont tassés à gauche comme ci-dessus, le jeu est terminé, et le premier qui ne peut plus bouger aucun pion a perdu le jeu.

    Ce jeu est praticable dès lors qu’on sait

    • distinguer le nombre 1 des nombres supérieurs à 1 (il est interdit d’avoir plus d’un pion par case),
    • distinguer la gauche et la droite (on joue vers la gauche),

    ce qui vise la fin du CP et les classes suivantes.

    Conway consacre à ce jeu un chapitre entier (le 13) de son livre On Numbers And Games :

    La stratégie gagnante est en effet basée sur des notions subtiles de théorie des groupes et de physique théorique, et n’est pas très simple à décrire. Ceci dit, le cadre du jeu est lui-même assez simple avec des jetons sur une bande, comme cette figure de Conway :

    (on constate que Conway utilise bien des jetons plats, empilables, mais des pions comme ceux du jeu d’échecs sont également possibles, d’autant que ci-dessus il n’y en a que 8, en effet il est matériellement difficile de mettre des pions du jeu d’échecs l’un au-dessus de l’autre)

    Le jeu de Welter est impartial, donc on peut chercher une stratégie gagnante sans utiliser l’arsenal théorique de Conway : il suffit de tracer le graphe du jeu et de le colorier pour trouver une stratégie gagnante. Pour cela, il est intéressant de modéliser le jeu (et son plateau) en Python.

    Codage du plateau

    Le plateau du départ :

    est assez naturellement codé par un tableau de booléens :

    [True,False,False,True,False,True,True,False]

    ou (ce qui revient au même) par un tuple d’entiers :

    (1,0,0,1,0,1,1,0)

    ou encore (c’est ce qu’on ferait en C ou Rust) par un octet 10010110 soit le nombre entier 150) mais aucune de ces représentations ne permet facilement de coder un tour de jeu. Ceci dit, on constate la présence

    • d’un variant : la somme des indices des cases où il y a un pion (un tour de jeu consiste à faire décroître l’un de ces indices, donc leur somme),
    • d’un invariant : le nombre de pions est toujours égal au nombre initial de pions, puisqu’on ne retire ni ne rajoute un pion au cours du jeu (comme disait Lavoisier, rien ne se perd, rien ne se crée, tout se transforme).

    Or il y a un moyen de coder cet invariant, non dans une variable, mais comme longueur d’une collection, celle des abscisses des jetons. Finalement le plateau

    sera codé par l’expression (0,3,5,6) donnant (dans l’ordre croissant) les abscisses des 4 pions, le pion le plus à gauche ayant pour abscisse 0. C’est cette modélisation qui sera choisie dans la suite, ne serait-ce que parce que le tuple

    (0,3,5,6)

    occupe 56 octets de mémoire, alors que la chaîne de caractères

    |o| | |o| |o|o|

    en occupe 64.

    Utilitaires

    Tout d’abord, on regarde les pions de gauche à droite, sans tenir compte de l’historique de leurs déplacements, donc (0,3,5,2) peut aussi être représenté sous la forme (0,2,3,5) où les abscisses sont triées dans l’ordre croissant. On définit alors une fonction can donnant la forme canonique d’un tuple (ou plateau de jeu) :

    def can(p):
        return tuple(sorted(list(p)))

    (on convertit le tuple en une liste, qui est mutable, on trie cette liste et on convertit le résultat à nouveau en un tuple)

    Ainsi, dans le dessin du graphe d’un jeu de Welter, on évitera d’avoir plusieurs sommets correspondant à la même situation de jeu.

    Ensuite, on définit une fonction str1, destinée à remplacer str, qui donne une chaîne de caractères plus lisible que le tuple, avec des pions représentés par la lettre o et des bords de cases représentés par des traits verticaux :

    def str1(p):
        tab = [' ']*(1+max(p))
        for x in p:
            tab[x] = 'o'
        return '|'+'|'.join(tab)+'|'

    avec ça on dessine des tuples lisibles :

    >>> str1((0,3,5,6))
    '|o| | |o| |o|o|'
    >>> str1(can((0,3,5,2)))
    '|o| |o|o| |o|'

    On constate au passage que les cases vides à droite ne sont plus représentées, en effet elles ne servent plus à rien dans le jeu.

    Enfin, un dernier utilitaire pour la suite est la vérification que chaque case ne contient au maximum qu’un seul pion :

    def ok(p):
        return all(p[i]!=p[j] for i in range(len(p)) for j in range(i))

    En effet, dire qu’aucune case ne contient plus d’un pion, c’est vérifier qu’aucune abscisse n’est présente plus d’une fois dans le tuple. A priori, c’était le point faible de cette modélisation (avec un tableau d’entiers on aurait juste eu à écrire all (x in (0,1) for x in tab)) et le fait d’avoir réussi à exprimer la condition en termes d’abscisses est garante de la réussite du modèle.

    Règle du jeu

    On définit maintenant une fonction tenter qui modifie le plateau (un tuple d’abscisses entières) en respectant la règle du jeu :

    def tenter(p,i,j):
        if j<p[i]:
            q = list(p)
            q[i] = j
            if ok(q):
                return can(q)

    La fonction prend en entrée un plateau de jeu p (un tuple d’abscisses de pions), un indice de départ i (p[i] est l’abscisse du pion à bouger) et une abscisse j d’arrivée (là où ira le pion si possible). Si on peut bouger le pion d’indice i, son abscisse sera j, et si on ne peut pas (parce que j est trop grand – trajet vers la droite ce qui est interdit par la règle du jeu – ou parce que cela aboutirait à plus d’un pion sur une case) alors la fonction ne renvoie rien, ou plutôt elle renvoie None. Si le mouvement est possible, la fonction renvoie le nouveau plateau can(q).

    La modélisation du jeu de Welter en Python est, à ce stade, terminée. Il reste juste à construire le graphe du jeu.

    Graphe du jeu

    Fidèle au programme de NSI (Terminale), on choisit de représenter le graphe par listes d’adjacences. Dans un graphe orienté, on dit qu’un sommet est enfant d’un autre s’il y a un arc les joignant, et la liste d’adjacence d’un sommet est la liste de ses enfants. On la calcule par une fonction, en tenant compte du fait que l’indice i ne peut pas dépasser la longueur du tuple, et que l’abscisse j ne peut pas dépasser p[i] :

    def enfants(p):
        tab = []
        for i in range(len(p)):
            for j in range(p[i]):
                t = tenter(p,i,j)
                if t is not None:
                    tab.append(t)
        return tab

    Dans la liste tab, on n’a gardé que les positions possibles à partir de p, c’est-à-dire celles pour lesquelles la tentative renvoie autre chose que None.

    On a alors les 8 coups possibles :

    >>> enfants((0,3,5,6))
    [(0, 1, 5, 6), (0, 2, 5, 6), (0, 1, 3, 6), (0, 2, 3, 6), (0, 3, 4, 6), (0, 1, 3, 5), (0, 2, 3, 5), (0, 3, 4, 5)]

    Pour construire le graphe (un dictionnaire dont les valeurs sont des listes d’adjacence), on utilise le fait que sum(p) est un variant, donc que sa valeur initiale majore le nombre de boucles à parcourir :

    def graphe(p):
        g = {p: enfants(p)}
        for n in range(sum(p)):
            gr = {k:v for k,v in g.items()}
            for k,v in g.items():
                for u in v:
                    if u not in gr:
                        gr[u] = enfants(u)
            g = gr.copy()
        return g

    On travaille sur une copie gr du graphe, parce que Python interdit (à juste titre) de modifier un objet mutable en cours de parcours dudit objet. Avec cette fonction on peut construire le graphe d’un jeu (utile pour chercher la stratégie gagnante) :

    >>> graphe((0,3,5,6))
    {(0, 3, 5, 6): [(0, 1, 5, 6), (0, 2, 5, 6), (0, 1, 3, 6), (0, 2, 3, 6), (0, 3, 4, 6), (0, 1, 3, 5), (0, 2, 3, 5), (0, 3, 4, 5)], (0, 1, 5, 6): [(0, 1, 2, 6), (0, 1, 3, 6), (0, 1, 4, 6), (0, 1, 2, 5), (0, 1, 3, 5), (0, 1, 4, 5)], (0, 2, 5, 6): [(0, 1, 5, 6), (0, 1, 2, 6), (0, 2, 3, 6), (0, 2, 4, 6), (0, 1, 2, 5), (0, 2, 3, 5), (0, 2, 4, 5)], (0, 1, 3, 6): [(0, 1, 2, 6), (0, 1, 2, 3), (0, 1, 3, 4), (0, 1, 3, 5)], (0, 2, 3, 6): [(0, 1, 3, 6), (0, 1, 2, 6), (0, 1, 2, 3), (0, 2, 3, 4), (0, 2, 3, 5)], (0, 3, 4, 6): [(0, 1, 4, 6), (0, 2, 4, 6), (0, 1, 3, 6), (0, 2, 3, 6), (0, 1, 3, 4), (0, 2, 3, 4), (0, 3, 4, 5)], (0, 1, 3, 5): [(0, 1, 2, 5), (0, 1, 2, 3), (0, 1, 3, 4)], (0, 2, 3, 5): [(0, 1, 3, 5), (0, 1, 2, 5), (0, 1, 2, 3), (0, 2, 3, 4)], (0, 3, 4, 5): [(0, 1, 4, 5), (0, 2, 4, 5), (0, 1, 3, 5), (0, 2, 3, 5), (0, 1, 3, 4), (0, 2, 3, 4)], (0, 1, 2, 6): [(0, 1, 2, 3), (0, 1, 2, 4), (0, 1, 2, 5)], (0, 1, 4, 6): [(0, 1, 2, 6), (0, 1, 3, 6), (0, 1, 2, 4), (0, 1, 3, 4), (0, 1, 4, 5)], (0, 1, 2, 5): [(0, 1, 2, 3), (0, 1, 2, 4)], (0, 1, 4, 5): [(0, 1, 2, 5), (0, 1, 3, 5), (0, 1, 2, 4), (0, 1, 3, 4)], (0, 2, 4, 6): [(0, 1, 4, 6), (0, 1, 2, 6), (0, 2, 3, 6), (0, 1, 2, 4), (0, 2, 3, 4), (0, 2, 4, 5)], (0, 2, 4, 5): [(0, 1, 4, 5), (0, 1, 2, 5), (0, 2, 3, 5), (0, 1, 2, 4), (0, 2, 3, 4)], (0, 1, 2, 3): [], (0, 1, 3, 4): [(0, 1, 2, 4), (0, 1, 2, 3)], (0, 2, 3, 4): [(0, 1, 3, 4), (0, 1, 2, 4), (0, 1, 2, 3)], (0, 1, 2, 4): [(0, 1, 2, 3)]}

    Mais un graphe, ça se dessine, du coup avec

    from graphviz import Digraph

    on peut définir une fonction de dessin :

    def dessin(p):
        g = graphe(p)
        gr = Digraph(engine='dot')
        for k,v in g.items():
            for u in v:
                gr.edge(str1(k),str1(u))
        return gr

    qui définit un graphe en essayant autant que possible de mettre au même niveau des sommets de même nombre de Grundy. Alors

    gragra = dessin((0,3,5,6))
    gragra.render('welt0356',format='pdf')

    donne le graphe suivant :

    Pour trouver la stratégie gagnante, il n’y a même pas besoin de calculer tous les nombres de Grundy, mais seulement colorier en vert les sommets de nombre de Grundy nul (et en rouge les autres). Cela revient à l’algorithme suivant :

    • si un sommet a au moins un enfant vert, alors il est rouge,
    • si un sommet n’a que des enfants rouges (ou pas d’enfant du tout) alors il est vert.

    La stratégie gagnante est alors de systématiquement viser un sommet vert. Mais avec le jeu

    qui est vert :

    il n’y a pas de stratégie gagnante puisque tous les enfants de la position de départ sont, par définition, rouges.

    Exemples

    Voici d’autres exemples de jeux de Welter, où on peut calculer la stratégie gagnante. Nombre de Grundy 1 :

    nombre de Grundy 0 :

    Nombre de Grundy 2 :

    Jeu du dollar d’argent

    C’est à Nicholas de Bruijn que Conway et Gardner attribuent ce jeu. La variante sans le dollar d’argent est peut-être inspirée d’un jeu que Conway attribue à un certain Northcott, mais que Charles Béart décrit comme traditionnel de l’Ouest africain, sous le nom de Tiouk Tiouk. Quoiqu’il en soit, le jeu du dollar d’argent sans le dollar est une variante du jeu de Welter, où il n’est plus permis de sauter par-dessus un jeton, mais seulement de glisser un jeton (et il est toujours interdit d’empiler les jetons, contrairement à la version avec le dollar). Le plateau de jeu est donc le même que pour le jeu de Welter, comme en témoigne cette figure de Conway :

    La théorie de ce jeu est plus simple que celle du jeu de Welter : dans la position ci-dessus, on est face à un jeu de Nim dont les tas ont pour hauteurs respectives 0, 2, 4, 5 et 3. Pour ce qui est de la modélisation en Python, la seule différence avec le jeu de Welter est qu’on ne peut pas sauter par-dessus un pion, ce qui s’exprime par une contrainte supplémentaire dans la fonction tenter :

    def tenter(p,i,j):
        if j<p[i] and (i==0 or j>p[i-1]):
            q = list(p)
            q[i] = j
            if ok(q):
                return can(q)

    (j doit être plus petit que p[i] parce qu’on va toujours vers la gauche, mais en plus il doit être plus grand que l’abscisse p[i-1] du pion précédent – à condition que celui-ci existe c’est-à-dire que i soit non nul – parce que sinon on a sauté par-dessus un pion, ce qui est autorisé dans le jeu de Welter mais pas celui du dollar d’argent sans le dollar)

    Avec cette contrainte supplémentaire, les graphes de jeu ne sont pas les mêmes que pour Welter. Par exemple avec

    le graphe est :

    et sa coloration

    montre que, cette fois-ci, il y a une stratégie gagnante, elle consiste à glisser le plus à gauche possible le second jeton.

    Voici d’autres graphes du jeu du dollar d’argent sans le dollar :

    Nombre de Grundy 1 :

    Nombre de Grundy 0 :

    Nombre de Grundy 3 :

    Nombre de Grundy 1 :

  • Dénombrer sans compter

    Les experts du m’raha wa n’tso à Mayotte ne comptent jamais les graines (Tiennot communication privée). Pour le katro de Madagascar, seuls les enfants ont le droit de compter les graines, sinon le seul fait d’avoir touché ne serait-ce qu’une graine, oblige à semer depuis la case où se trouvait cette graine. Toujours à Madagascar (Tiennot communication privée) les marchands de fruits connaissent avec une grande précision le cardinal d’un tas de fruits sans compter ceux-ci. Quand à Mayotte, les champions du m’raha wa n’tso savent avec précision (cette connaissance étant d’importance stratégique) le nombre de graines contenu dans la nyumba (la case de forme carrée) sans jamais compter ces graines.

    Comment font-ils ? Une première possibilité est qu’ils aient mémorisé les nombres de graines au cours de la partie (ou alors qu’ils aient compté les coups joués, et les graines des cases peu remplies, et retrouvé le cardinal des nyumbas par déduction), ce qui suppose une mémoire de travail impressionnante. Une seconde est qu’ils savent estimer avec précision un grand nombre entier.

    Dans cet article, on se propose d’explorer la manière dont des machines (vous avez dit IA ?) peuvent dénombrer sans compter. On peut dénombrer par pesée, par estimation du volume, voire par le regard.

    Premier projet : dénombrement par pesage

    Un plateau de jeu de semailles (par exemple, Sowing à 6 cases) pédagogique est un alignement de cases (où on sème des graines), chacune d’entre elles ayant en son fond une jauge de contrainte, permettant de mesurer la masse du tas de graines ; de plus, à côté de chaque case, figure un afficheur LED indiquant le nombre de graines actuellement dans la case. Quelque chose comme

    oooooo
    120102

    qui peut aider les jeunes élèves à apprendre à associer les chiffres et les nombres. Voici un exemple de ce qui est attendu :

    Un problème qui se pose avec la pesée de graines, est que celles-ci ne sont pas calibrées avec précision. Une mesure effectuée sur les 48 graines d’un jeu de katro indique une masse moyenne de 2,583g et un écart-type (estimé) de 0,414g. Le tracé d’une droite de Henry :

    suggère, avec un coefficient de corrélation de carré 0,988 que la modélisation de la masse d’une graine par une variable aléatoire normale de paramètres 2,583g et 0,414g est pertinente dans ce contexte. En supposant qu’il y a indépendance entre les masses des graines, la masse du tas de nn graines est alors normale de paramètres n×2,583n \times 2,583 et 0,414×n0,414 \times \sqrt{n} et le quotient de cette masse totale par 2,583 suit lui aussi une loi normale, de paramètres nn et 0,16×n0,16 \times \sqrt{n}. La probabilité qu’elle soit comprise entre n−12n-\frac{1}{2} et n+12n + \frac{1}{2} (c’est-à-dire que son arrondi entier soit correct) est donnée dans le tableau suivant :

    10,998221949401783
    20,972874613820933
    30,928803110724748
    40,881829754134665
    50,837749500152768
    60,797965253549604
    70,762452482209559
    80,730777292410847
    90,702433751636496
    100,676951054399075
    110,653921494553211
    120,633001454164905

    On voit que, pour des grands tas (ceux-là même où l’humain est peu performant pour ce qui est de dénombrer), la probabilité de se tromper est d’une chance sur trois, ce qui n’est pas négligeable.

    Pour réaliser ce projet, par exemple sur un Sowing pédagogique à 6 cases, il faut

    • un microcontrôleur type Arduino ou ESP-32,
    • 6 capteurs de type jauge de contrainte, dont les signaux sont multiplexés pour pouvoir n’utiliser qu’un seul convertisseur analogique-numérique du ESP-32 (lequel dispose de 8 canaux tout de même),
    • 6 actuateurs de type affichage LED 7 segments, un (deux s’il risque d’y avoir plus de 9 graines) par case, avec interfaces séries permettant de les programmer en binaire, et chacun branché sur une sortie numérique du ESP-32.

    Le module ESP-32 peut être programmé en micropython. Le plateau serait plus pédagogique si les chiffres étaient affichés en calligraphie : ce sont les formes que les enfants doivent apprendre à reconnaître.

    Variante avec machine learning

    L’initiation au machine learning commence souvent par la régression linéaire (qui est un apprentissage supervisé pour une prédiction) et il se trouve que justement le tableur permet de calculer une droite de régression, et qu’en plus les fonctions affines sont au programme de mathématiques de Seconde.

    Le matériel nécessaire est composé de

    • machines ESP-32 (programmables en micropython ; idéalement une par élève),
    • jauges de contrainte (à brancher sur une entrée analogique de l’ESP-32),
    • ordinateurs, sur chacun desquel est installé Thonny et un tableur.

    Ci-dessous, à gauche c’est la carte ESP-32, au centre l’amplificateur hx-711, et à droite la jauge de contrainte avec 10 graines de soja noir posées dessus :

    Une fois lancé Thonny et branché (par USB) l’ESP-32, il faut changer l’interpréteur Python pour choisir celui de l’ESP-32, pour cela on va dans exécuter :

    Là, on choisit la version ESP-32 :

    on clique dessus :

    puis sur OK, ce qui a pour effet de redémarrer Python, mais en mode ESP-32 :

    Ensuite, on déclare un objet de type broche, avec Pin (importé du module machine), comme entrée analogique, Puis on commence l’apprentissage, dont chaque étape consiste à

    • choisir un entier n⩾0n \geqslant 0,
    • poser nn graines sur le plateau,
    • lire (avec la méthode read de la broche) la masse du tas de graines,
    • entrer dans la colonne A le résultat de la lecture,
    • entrer dans la colonne B le nombre nn.

    En recommençant sur plusieurs lignes du tableur des données de ce genre, on réalise l’apprentissage. Il est conseillé de choisir au moins une fois chaque valeur de nn entre 0 et 12 (compris). La phase suivante consiste à dessiner le nuage de points, puis tracer la doite de régression, avec son équation.

    L’utilisation consiste à mettre des graines sur le plateau, lire la tension aux bornes du plateau, puis calculer son image par la fonction affine construite dans l’apprentissage, et arrondir à l’entier le plus proche pour avoir une estimation du nombre de graines. Ensuite on vérifie si l’estimation correspond au nombre réel de graines. En munissant la jauge de contrainte d’un amplificateur hx-711, et à l’aide de la bibliothèque Python permettant de gérer le tout, on peut effectuer des mesures en lançant ce script :

    from hx711_gpio import HX711
    from machine import Pin
    
    pin_sck = Pin(22, Pin.OUT)
    pin_dout = Pin(23, Pin.IN)
    hx = HX711(pin_sck, pin_dout, gain=128)

    Ensuite, dans la console Python, il suffit de

    • poser quelques graines sur la balance,
    • évaluer dans la console l’expression hx.get_value(),
    • noter le résultat dans une cellule du tableur (colonne 1) et le nombre de graines dans la cellule voisine (colonne 2)

    Au bout de quelques mesures notées (phase d’apprentissage supervisé), il est possible de dessiner le nuage de points, puis de demander la droite de régression (donnant n fonction de la mesure) :

    Ensuite on peut utiliser le modèle pour une estimation en programmant la fonction affine en Python :

    def f(x):
       return round(0.00419133506877983*x-13484.4216461023,0)

    (on arrondit à zéro chiffre après la virgule parce qu’il y a un nombre entier de graines)

    Pour utiliser le modèle, on pose des graines sur la balance puis on évalue dans la console l’expression f(hx.get_value()) pour estimer le nombre de graines. On ne s’attend pas à avoir une estimation correcte à chaque fois, par exemple ci-dessous on a mis trois graines pour en estimer 5 :

    Second projet : dénombrement par regard

    Avec ImageJ

    Usuellement, quand on veut estimer le nombre de manifestants dans une manif ou le nombre de globules rouges dans une goutte de sang, on ne le fait ni par pesée ni par comptage. Une application intéressante sur le smartphone serait celle qui afficherait sur l’écran de la webcam des cadres de ce genre :

    Avec le logiciel libre ImageJ, on peut compter ces graines :

    Les ombres gênent la visibilité, au point que si on essaye d’analyser cette image sans retouche, ImageJ ne compte que les constellations :

    Il faut donc d’abord séparer les graines et les ombres. Comme ici les graines sont rougeâtres, on peut effectuer un filtrage passe-bande :

    Le rouge ressort bien par rapport à l’ombre grise, et du coup la composante verte de l’image porte des graines plus foncées que le reste :

    Ensuite on choisit un seuil au-delà duquel la couleur est suffisamment foncée pour être une graine :

    La suite est de la topologie : on demande à ImageJ de compter les composantes connexes (analyse particles) de la partie noire de l’image. Le résultat (pour les « particules » de 400 pixels² à l’infini) est ici :

    AreaMean
    11423246.936
    21474253.789
    31154254.337
    41415250.855
    51670247.671
    61209241.079
    71029248.309
    81520253.658
    91450255.000

    ImageJ compte 9 composantes connexes, donc 9 graines dans l’image. Elles peuvent être numérotées :

    ou schématisées par des ellipses :

    Très bien : l’ordinateur sait compter au moins jusqu’à 9. Sauf que la suite d’opérations précédentes n’est pas automatisable (dans un script), notamment parce que le choix du seuil a été fait manuellement, et il n’est pas facile de déléguer ce choix à un algorithme. Par exemple, avec la nyumba qui ouvre cet article, on voit que seules quelques ombres (et aucune graine, celles-ci sont trop claires) ont été comptées :

    Ce projet peut néanmoins être tenté en cours de SNT (thèmes : les objets connectés, et la photographie numérique),

    • soit directement avec ImageJ,
    • soit avec le module Python openCV,
    • soit avec appInventor, du MIT, en programmation par blocs, pour les smartphones Android, ou l’équivalent Apple pour les iPhones.

    Avec openCV

    Python possède un module opencv permettant de faire du traitement d’images (y compris en vidéo). Avec

    import cv2
    import numpy as np

    on dispose d’une fonction cv2.imread qui permet d’importer une image (comme celle de nyumba déjà vue) dans une variable img, puis de la mettre en noir et blanc et lui appliquer une transformée de Hough circulaire avec

    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    gray = cv2.GaussianBlur(gray, (9,9), 2)
    
    circles = cv2.HoughCircles(
        gray,
        cv2.HOUGH_GRADIENT,
        dp=1.2,
        minDist=20,
        param1=100,
        param2=30,
        minRadius=30,
        maxRadius=60
    )

    Avec ces paramètres seules 2 graines sur les 12 n’ont pas été détectées :

    En effet elles sont partiellement cachées par le bord inférieur du plateau et la partie visible n’a pas une forme circulaire. D’ailleurs cet algorithme (dû à Serge Bays) ne fonctionne pas avec des graines de forme allongée.

    Avec un réseau de neurones

    Les nombreuses caméras chinoises sont dotées de réseaux de neurones qui ont appris à reconnaître des visages. Sauraient-elles reconnaître (et dénombrer) des graines ? Pour simuler un réseau de neurones, on utilise le module keras de Python. Pour un apprentissage supervisé, a été créé un jeu de données artificiel, composé d’images de 256 pixels comportant des images de graines ne se chevauchant pas trop :

    ainsi que le nombre de graines de chaque image (ci-dessus [5, 7, 9, 3, 4, 9, 1, 6, 6, 3]). Après plusieurs essais, ce réseau de neurones a été choisi :

    p = 8
    modele = Sequential([
        Conv2D(p,3,input_shape=(16,16,1)),
        MaxPooling2D(pool_size=2),
        Flatten(),
        Dense(10,activation='softmax')])
    modele.compile(loss='categorical_crossentropy',optimizer='sgd',metrics=['accuracy'])

    Avec la descente du gradient comme optimiseur, on est déçu par la faible précision 0,5206666588783264 : le réseau prédit correctement le nombre seulement une fois sur deux. Par exemple cette image a été vue comme représentant le nombre 4 :

    La distribution (pour cette image) a en effet 4 comme mode :

    Le problème vient de ce que ⠌ et ⠡ sont des images différentes (les pixels noirs ne sont pas du tout au même endroit) alors qu’elles représentent le même nombre 2. Il faudrait entraîner un réseau de neurones sur des images qui se ressemblent à translation et rotation près. C’est le cas pour la transformée de Fourier bidimensionnelle. En lançant le réseau de neurones convolutif sur des transformées de Fourier de constellations, on obtient plus de succès, avec une précision de 0,6243333220481873. Le réseau de neurones ne se trompe qu’une fois sur trois, et pas de beaucoup, puisqu’au lieu de

    [3, 3, 4, 8, 2, 7, 4, 5, 3, 4]

    il trouve

    [3, 4, 5, 9, 3, 6, 4, 6, 3, 4]

    donc (ici) 6 erreurs sur 10 mais des erreurs d’une seule valeur à chaque fois (par exemple 4 au lieu de 3). Dans ce cas (deuxième valeur), la distribution est presque précise :

    Le mode 4 (erroné) dépasse de peu la valeur correcte 3.

    Et si on comptait quand même ?

    L’algorithme par transformée de Hough ne fait que repérer les graines (des cercles) et les compter. Mais cela peut être utile à un joueur de m’raha wa n’tso parce que, comme lui, la machine compte les graines sans les toucher. Seulement il faut d’abord qu’elle reconnaisse les graines, indépendamment de leur orientation, ce qui va passer par l’apprentissage : la machine doit apprendre à reconnaître une graine. L’affichage de la caméra intelligente après apprentissage pourrait ressembler à ceci :

    Cela mènerait à un autre type de TP, portant sur une caméra intelligente (smart cam) où les élèves n’auraient qu’à apprendre à la machine à reconnaître un nouvel objet (une graine) et après cette phase d’apprentissage supervisé, on testerait la machine sur des constellations : la machine compte correctement si elle reconnaît précisément les graines, et

    • sous-compte si elle ne reconnaît pas certaines graines (elle ne les voit pas bien),
    • sur-compte en cas de faux positifs (une tache d’ombre prise pour une graine par exemple).
  • Variation sur un concept d’Aviezri Fraenkel

    Dans cet article, on va s’intéresser à des jeux impartiaux, dont l’archétype est le jeu de Nim. Dans ce jeu, on dispose de jetons, disposés en tas comme ici (jeu de Nim à deux tas) :

    Ci-dessous ce jeu sera noté (3,2) car il y a un tas de 3 jetons et un tas de 2 jetons. Un coup du jeu de Nim consiste à

    • choisir un tas (par exemple celui de droite),
    • en retirer autant de jetons qu’on souhaite (par exemple un seul) :

    Celui qui prend le dernier jeton gagne le jeu. Ci-dessus ce sera celui qui a joué en premier, parce qu’il dispose maintenant d’une stratégie gagnante (jouer dans un tas ce que son adversaire vient de jouer dans l’autre tas). Mais dans le cas général, on calcule ce qu’on appelle nombre de Grundy du jeu, et ce nombre (un entier résumant le jeu) se calcule récursivement sur le graphe du jeu. Pour construire ce graphe, on assimilera la position

    avec la position

    car l’emplacement des tas importe peu pour la stratégie gagnante, tout ce qu’il faut connaître, c’est la hauteur de chacun des tas. Les deux positions ci-dessus seront toutes deux notées (2,1) : on convient de mettre dans l’ordre décroissant les hauteurs des tas de jetons. De plus, on ne note pas les tas devenus vides. Avec ces conventions le graphe du jeu est :

    Le calcul des nombres de Grundy de chaque sommet donne la valeur 1 pour le sommet qui est en haut. Ce nombre de Grundy étant différent de 0, il y a une stratégie gagnante consistant à viser un sommet dont le nombre de Grundy est nul, et ce sommet est (2,2). L’idée fondamentale de Fraenkel est d’utiliser ce graphe, non pour modéliser le jeu, mais pour jouer, par le biais d’un pion, commun aux deux joueurs, et qu’à chaque tour de jeu on glisse le long d’une flèche, jusqu’à ce que ce ne soit plus possible (parce que le pion est tombé dans un puits en bas), auquel cas le dernier à avoir bougé le pion (le joueur qui a fait tomber le pion dans le puits) a gagné le jeu. Quelques définitions :

    • un graphe orienté est connexe s’il est d’un seul tenant (on ne peut pas le séparer en deux morceaux sans enlever un sommet ou un arc),
    • un cycle est un chemin permettant d’aller d’un sommet à lui-même en suivant des flèches,
    • un graphe n’ayant pas de cycle est un graphe acyclique,
    • un sommet où n’arrive aucune flèche est une source,
    • un sommet dont ne part aucune flèche est un puits.

    Les jeux de Fraenkel se jouent sur des graphes orientés connexes et acycliques. De tels graphes ont au moins une source et au moins un puits. Pour savoir où mettre le pion au départ, il faut qu’ils n’y ait qu’une source, et le jeu est plus facile à jouer s’il n’y a qu’un puits, comme sur ce graphe d’enfant (dont le nombre de Grundy est 2) :

    On énonce alors le

    Théorème de Fraenkel : tout jeu impartial est équivalent à un jeu où on déplace un pion sur un graphe orienté connexe acyclique ayant une source et un puits, le pion étant placé au départ sur la source et le gagnant étant celui qui amène le pion dans le puits.

    Atelier de création de jeux impartiaux

    Un atelier de création de jeux impartiaux revient à un atelier de dessins de graphes orientés, ce qui suggère ce genre de tournoi :

    • chaque enfant se voit remettre du papier et un crayon, et dessine un graphe orienté,
    • si le graphe comporte un cycle, il est disqualifié,
    • s’il comporte plusieurs sources ou plusieurs puits, il est pénalisé,
    • les graphes restants sont alors classés par nombre de Grundy, et le graphe ayant le plus grand nombre de Grundy est gagnant du tournoi.

    La récompense pour le gagnant est l’organisation d’un tournoi de jeu impartial sur son graphe. Un outil d’aide à la conception de graphes orientés est disponible en ligne sur Nirina974. Un bêta-test a permis à une enfant de 8 ans de rapidement produire ce graphe dont le nombre de Grundy est 3 :

    (le départ est en bas à gauche). Et voici un graphe créé par Aviezri Fraenkel (il a deux puits, coloriés en vert ; son nombre de Grundy est 0) :

    Autres concepts d’Aviezri Fraenkel

    Fraenkel s’est beaucoup intéressé au calcul de nombres de Grundy sur certains graphes ayant un ou plusieurs cycles comme

    dont le nombre de Grundy est aussi égal à 0.

    Conway et Fraenkel ont classé les interactions possibles entre pions sur un graphe orienté connexe (avec ou sans cycle) :

    • Les pions peuvent être des bosons, auquel cas il n’y a pas de limite supérieure au nombre de pions présents sur un sommet du graphe.
    • Les pions peuvent s’exclure mutuellement comme des fermions, ce qui occasionne des blocages ; Conway attribue à un certain Welter la création d’un cas particulier de ce genre de jeu, où le graphe est formé de sommets alignés, chacun étant relié à tous les sommets qui sont à sa gauche.
    • Les pions peuvent se comporter comme des antiparticules : lorsqu’un pion arrive sur un sommet déjà occupé, les deux pions sont retirés du jeu.
    • les pions peuvent être de deux couleurs, et avoir un comportement mixte : un pion ne peut pas aller sur un sommet où se trouve un pion de la même couleur, mais peut occuper un sommet où se trouve un pion de l’autre couleur, auquel cas il va le clobber, c’est-à-dire qu’il prend sa place en le retirant du graphe, comme aux échecs.

    Dans ce dernier cas, Fraenkel a conçu un jeu impartial :

    et même un jeu partisan (chaque joueur ayant ses propres 7 pions) sur un graphe pas entièrement orienté :

    Ce jeu a été commercialisé dans les années 1970 sous le nom de on the line, puis dans les années 2000 sous le nom de arrows. Dans ce dernier cas, le plateau de jeu était en bois, avec des creux pour les pions, lesquels étaient des billes, transparentes pour un joueur, et métalliques pour l’autre joueur. Sur le site https://mathpickle.com/ (consacré à l’enseignement des maths par la ludologie), des jeux similaires ont été proposés sur des graphes plus simples :

    https://mathpickle.com/wp-content/uploads/2015/08/Arrows-Game.pdf

    Certains de ces graphes semblent permettre d’introduire la notion de parité (apparemment en cycle 2).

    Variation sur un thème de Fraenkel

    Revenons un peu en arrière, là où Fraenkel suggère de mettre plusieurs pions sur un graphe orienté connexe acyclique, le mode d’interaction des pions faisant partie de la règle du jeu. La carte des cartes donne envie d’explorer cette variante :

    Avant de jouer, on place un pion sur l’unique source de chaque composante connexe d’un graphe orienté acyclique. Un tour de jeu consiste à choisir un des pions qui n’est pas encore bloqué, puis le bouger suivant une flèche. Le premier qui ne peut plus bouger a perdu le jeu.

    Les composantes connexes

    En vrai, avant de jouer, on construit les composantes connexes du graphe, par exemple en les puisant d’une collection établie lors de l’activité décrite dans la partie atelier de création de jeux impartiaux plus haut, ou comme modèles de jeux impartiaux connus comme par exemple le jeu de Sowing impartial (graphes simplifiables en mettant ensemble tous les puits et en tenant compte des symétries) :

    ou alors des graphes orientés connexes acycliques créés sans chercher à modéliser un autre jeu impartial, juste en dessinant le graphe, comme par exemple le graphe d’enfant vu en début d’article, que voici rendu en Python :

    ou, enfin, des graphes qui ont été créés spécialement pour cette activité cartes des cartes impartiales :

    Le jeu est à deux joueurs :

    • les deux joueurs choisissent des graphes orientés connexes acycliques ayant une source et un puits parmi la collection (iles choisissent des cartes dans le jeu de cartes),
    • puis posent ces cartes sur la carte des cartes (la table : ils construisent le plateau de jeu avant de jouer),
    • puis posent un pion à chaque départ d’une carte,
    • et, enfin, jouent : chacun son tour, déplace un des pions en lui faisant suivre une flèche.
    • Le premier qui ne peut plus bouger (parce que tous les pions sont dans des puits) a perdu.

    Application au jeu de Nim

    On rappelle que pour le jeu de Nim à deux tas (3,2), le graphe possède 9 sommets :

    Mais la version à deux composantes connexes n’en a plus que 7 (par contre il y a deux pions) :

    et on peut simplifier encore : comme la composante connexe de gauche a un nombre de Grundy égal à 3 (c’est un tas de 3 jetons) et celle de droite a un nombre de Grundy égal à 2 (c’est un tas de 2 jetons), le jeu est, d’après les théorèmes de Sprague-Grundy et de Fraenkel, LE jeu de nombre de Grundy 1 (Nim-somme de 3 et 2), c’est-à-dire ce jeu (à seul pion pour le coup) :

    Comment jouer/faire jouer

    Comme il s’agit de jeux à deux joueurs, chaque table de jeu occupe deux joueurs. Mais en plus il y a une table centrale où se trouvent posées des cartes, dont chacune porte un graphe orienté connexe acyclique avec une source et un puits. Les joueurs d’une table de jeu commencent par choisir quelques cartes de la table centrale (en essayant de ne pas frustrer les joueurs d’autres tables) puis les posent sur leur table de jeu, en mettant un pion au départ de chaque carte.

    Puis ils jouent au jeu qu’ils ont créé, c’est-à-dire que chacun son tour bouge un pion le long d’une flèche. Lorsque le jeu est terminé (plus aucun pion ne peut bouger, donc celui qui devait jouer a perdu), ils notent les nombres de Grundy des cartes choisies et leur Nim-somme.

    Ensuite ils préparent un autre jeu, en choisissant parmi ces opérations :

    • enlever une carte jugée inintéressante,
    • ajouter une carte qui semblait manquer,
    • échanger une carte peu intéressante contre une carte plus intéressante (comme au poker) au niveau de la table centrale.

    Après quelques tours de ce métajeu, on compare les jeux les plus intéressants, et les nombres de Grundy obtenus. La version sans nombres de Grundy est plus simple mais fait appel au jugement humain pour ranger les jeux construits par qualité subjective.

    Lors du séminaire IREMI du 22 avril 2026

    Voici des jeux jugés intéressants par leurs créateurs :

    Les nombres de Grundy des 5 termes de cette somme sont :

    43
    332

    et le nombre de Grundy des 5 cartes est 4+3+3+3+2 = 5.

    Les nombres de Grundy des 4 cartes sont :

    10
    33

    dont la nim-somme est 1+0+3+3 = 1.

    Les nombres de Grundy sont :

    241
    3

    Le nombre de Grundy du tout est donc 2+4+1+3 = 4.

    Les nombres de Grundy sont :

    1232

    dont la somme est 1+2+3+2 = 2.

    Les nombres de Grundy sont :

    03
    133

    dont la somme n’est égale qu’à 0+3+1+3+3 = 2, mais trois des cartes (différentes entre elles) valent 3, donc deux d’entre elles s’annulent mutuellement, sans qu’il soit aisé de voir comment.

  • P4, un environnement d’apprentissage de la programmation au cycle 4 conforme aux nouveaux programmes

    Voir le site :

    https://iremi974.gitlab.io/p4

    Issu d’une démarche collaborative, le site présente :

    • un environnement d’apprentissage de la programmation adapté à chaque classe du cycle
    • des exemples de tâches pour chaque classe
    • des conseils didactiques.

    P4 a été présenté en atelier à la conférence DIDAPRO 11 à Grenoble le 11 mars 2026. Lien vers l’article.

  • Blocks4school – des environnements d’apprentissage de la programmation par blocs pour l’école primaire

    Lien vers le site : https://iremi974.gitlab.io/blocks4school/

    Programmation de déplacements aux cycles 1 et 2

    Conçus d’abord en mode débranché et présentés au forum Mathnipulez, les environnements pour le cycle 1 et 2 permettent l’apprentissage de la programmation par le déplacement d’un pion ou d’un robot sur une scène. La représentation du programme peut se faire grâce à la tablette de programmation. Le déplacement d’un pion pour le cycle 1 utilise les orientations absolues : haut, bas, gauche, droite. Le déplacement d’un robot pour le cycle 2 utilise les orientations relatives : avancer, pivoter à droite, pivoter à gauche, reculer.

    Les scènes peuvent être contextualisées par rapport à l’environnement de l’élève (scènes à imprimer en A3 puis à plastifier) :

    Les scènes peuvent aussi être imprimées sur bâche au format 1m par 1m et constituent alors une aire de déplacement pour le robot BlueBot.

    Ces environnements débranchés et en ligne ont été conçus pour simplifier l’apprentissage de la programmation et le rendre accessible à tou.te.s les professeurs d’école et à leurs élèves.

    Les versions en ligne peuvent être utilisées par l’enseignant pour montrer ou corriger une activité au vidéoprojecteur. Elles peuvent aussi être utilisées par les élèves sur ordinateur ou tablette dans le cas ou la classe est équipée.

    Les études réalisées ont montré un renforcement positif entre les trois modes d’apprentissage : activité débranchée, robotique pédagogique et activité en ligne sur ordinateur.

    Programmation de dessins avec PixelArt au cycle 3

    L’environnement PixelArt dédié au cycle 3 permet l’apprentissage de la boucle répéter n fois dans un cadre créatif.

    L’environnement est disponible dans deux modes, un mode guidé avec un enchaînement de tâches à réaliser, et un mode créatif entièrement libre.

  • Forum Ma(th)nipulez – Édition 2026

    Forum Ma(th)nipulez – Édition 2026

    La 4ème édition du Forum Ma(th)nipulez 2026 se tiendra le mercredi 18 mars 2026 (après-midi) au collège de CAMBUSTON (Saint ANDRÉ).

    Vous pourrez y  découvrir des activités de manipulation en mathématiques, du cycle 2 à la terminale (parfois courtes ou à utiliser sur l’année) de découverte ou de mise en application des concepts mathématiques présents dans les programmes, avec ou sans matériel spécifique et directement exploitables en classe. 

    Entrée, sortie et déambulation libres. élèves testeur(euse)s bienvenu(e)s

    Retrouvez les fiches des activités présentés :

  • Programmation Orientée Objet et égalités

    Le thème de la semaine des mathématiques 2026 est égalités. Ce concept, très présent en mathématiques, l’est aussi dans la programmation objet, et plus généralement en informatique où il existe plusieurs définitions de l’égalité.

    Les différents sens de l’égalité en maths

    Le premier mathématicien à parler d’égalité est Euclide. Il évoque des propriétés de l’égalité qui en font une relation d’équivalence :

    • a=a
    • si a=b alors b=a
    • si a=c et b=c alors a=b

    Mais l’égalité n’est pas une relation d’équivalence comme les autres, en plus de a = a il faut aussi a ≠ n’importe quoi d’autre que a. Par exemple une droite est toujours parallèle à elle-même mais n’est pas pour autant égale à toutes les droites qui lui sont parallèles. L’idée sous-jacente est de considérer comme égaux des objets mathématiques indistinguables l’un de l’autre.

    Égalité comme résultat d’un calcul

    Lorsque j’écris 2+2=4, je ne dis pas seulement que le nombre 2+2 est le même nombre que 4, je sous-entends que si on effectue le calcul 2+2, le résultat est 4. D’ailleurs sur certaines calculatrices 4 opérations, le bouton permettant d’obtenir le résultat de l’évaluation d’une expression porte le signe =. Cela perturbe des élèves à partir du cycle 2, car ils finissent par se fixer sur cette interprétation de l’égalité, et ne comprennent pas qu’on puisse écrire 4=2+2 ou 2+2=3+1.

    Cas d’égalité des triangles

    Chez Euclide, lorsqu’on dit que les triangles ABC et DEF sont égaux, on sous-entend qu’ils sont isométriques, et si par exemple AB = DE, on compare des distances et pas des points : que AB = DE ne signifie ni que A=D ni que B=E mais seulement que la distance entre A et B est la même que la distance entre D et E.

    Égalité approchée

    En cycle 3, beaucoup d’élèves apprennent que π=3,14 et ont beaucoup de mal par la suite à se défaire de cette idée, qui donne à la fois une fausse image de π, et une fausse image de l’égalité.

    Un problème similaire apparaît insidieusement lorsqu’on enseigne les fractions, notamment lorsqu’on dit que si on partage équitablement le contenu d’une bouteille en trois parties (égales !), chaque verre contient le tiers du contenu de la bouteille : en fait on ne peut pas forcément partager équitablement une quantité (par exemple si le nombre de molécules d’eau n’est pas divisible par 3, on sait grâce à la division euclidienne -encore Euclide- que le partage équitable est impossible) même si cette quantité est continue, du moins dans la pratique : les fractions obtenues en coupant des pizzas ou des segments ne peuvent être qu’approchées.

    Équations

    L’énoncé d’une équation s’écrit avec le signe égal. Par exemple lorsqu’on écrit que x+1=4, on pose implicitement la question de savoir non pas si x+1=4 mais quand x+1=4 : quelles sont les valeurs de x pour lesquelles x+1=4 ? Une équation est donc un problème, et résoudre ce problème c’est donner l’ensemble des x pour lesquels l’égalité est vérifiée.

    Un problème nouveau apparaît lorsqu’on parle d’équation d’une courbe algébrique : on ne peut pas lister toutes les solutions d’une équation de droite comme 2x+5y=3 parce qu’en caractéristique nulle il y a une infinité de solutions de l’équation.

    Fonctions

    Dans l’écriture d’une relation, en particulier d’une fonction, on utilise aussi le signe d’égalité comme dans y = sin(x). Cela sous-entend que pour toute valeur de la variable x, la valeur correspondante de y est le sinus de la valeur correspondante de x. Une notion mathématique qui précède celle de fonction est celle de variable. Lorsque je dis que x=3, je ne dis pas que la variable x est égale à 3 (sinon ce ne serait pas une variable) mais que sa valeur est égale à 3. Une variable est en fait une donnée formée de deux choses : son nom (quoiqu’il existe, en logique et en informatique, des variables anonymes) et sa valeur, et l’écriture x=3 est un abus de langage pour dire que x a pour valeur 3.

    L’égalité en informatique

    Pour savoir si x=3, je dois donc récupérer la valeur de la variable x (cela s’appelle évaluer x, et suppose donc que la variable x est une expression) et la comparer avec 3. Le résultat de la comparaison est un booléen (False ou True).

    Affectation

    Python hérite d’un choix malheureux datant de Fortran, un des premiers langages informatiques compilés. Dans ces langages, lorsque je modifie une variable, par exemple en injectant la valeur 5 dans la variable x, je n’écris pas 5→x pour suggérer cette affectation (ne serait-ce que parce que le caractère → n’existe pas sur un clavier azerty…) mais x=5, qui montre non pas un constat d’égalité, mais une volonté (que x soit égal à 5). Il y a eu des langages de programmation utilisant une suite non symétrique de symboles pour désigner l’affectation (:= dans les langages issus d’Algol, <- pour R…) et dans ces langages (y compris les langages purement fonctionnels -dépourvus de variables- comme Lisp, OCaml ou Haskell, et SQL qui n’est pas à proprement parler un langage de programmation) il est possible d’utiliser le signe d’égalité pour une comparaison, mais pour Python on ne peut pas, ce signe étant déjà réservé à l’affectation. Comment faire alors ?

    Comparaison en Python

    Pour savoir si deux objets sont égaux, en Python, on utilise un signe d’égalité dédoublé comme dans 2+2==4. Ou pas ! On peut aussi écrire 2+2 is 4, avec le même résultat (évaluation à True). Il y a donc deux égalités en Python, ce qui inscrit Python pleinement dans le thème de la semaine des maths 2025 (égalités, au pluriel) ! En quoi ces deux égalités diffèrent-elles ? En rien, avec des objets immuables : la première évalue les deux membres de l’égalité et compare les résultats des deux évaluations, la seconde compare les adresses mémoire des deux objets. Les réponses sont les mêmes, mais avec des objets mutables comme des listes, les égalités diffèrent :

    >>> tab1 = [3,1,4]
    >>> tab2 = [3,1,4]
    >>> tab1 is tab2
    False
    >>> tab1 == tab2
    True

    Égalité approchée

    Pour comparer deux entiers, on sait depuis le cycle 2 comment faire avec leur écriture décimale. Pour les réels c’est plus compliqué, parce que les réels sont modélisés par des flottants, et que des flottants, il n’y en a que 18446744073709551616 (et encore, certains flottants, comme NaN et l’infini, ne sont pas des réels). A priori, si on veut comparer 8\sqrt{8} avec 222 \sqrt{2}, il ne devrait pas y avoir de souci puisque les deux nombres ne sont présents en mémoire que sous forme approchée, et devraient être approchés par le même flottant :

    >>> a =8**0.5
    >>> b = 2*2**0.5
    >>> a == b
    True
    >>> a
    2.8284271247461903
    >>> b
    2.8284271247461903

    Mais les flottants sont des fractions dyadiques, ensemble strictement inclus dans celui des décimaux, et même les nombres décimaux ne peuvent être comparés exactement :

    >>> a = 0.1 + 0.2
    >>> b = 0.3
    >>> a == b
    False
    >>> a
    0.30000000000000004
    >>> b
    0.3
    >>> a-b
    5.551115123125783e-17

    Cela pose un problème en géométrie, où un déterminant censé être nul peut ne pas valoir exactement 0. On y reviendra plus tard, parce qu’une solution possible utilise la POO. En attendant, on constate que l’entier 3 et le réel 3 ne sont pas considérés comme égaux par Python :

    >>> 3 == 3.0
    True
    >>> 3 is 3.0
    False

    C’est donc bien le double égal qui est le plus pratique pour la comparaison de réels, et pas le is.

    POO et égalités

    Comment comparer deux réels ? On essaye

    print(dir(3.0))

    et on obtient (sous SofusPy974 en l’occurrence) quelque chose comme

    ['__eq__', '__ge__', '__gt__', '__le__', '__lt__', '__ne__', 'clone', 'numberCompare', 'toFixed']

    montrant qu’il est possible de savoir, respectivement, si un flottant est égal, supérieur ou égal, supérieur, inférieur ou égal, inférieur, ou différent d’un autre flottant, et qu’il est possible de faire ces comparaisons algorithmiquement (pour les nombres décimaux, ces algorithmes sont abordés en cycle 3).

    Pour faire de la programmation objet, on a parfois besoin de redéfinir l’égalité, et donc de se poser la question qui entame cet article : quand doit-on considérer comme indistinguables deux objets ? Par exemple dans un annuaire téléphonique, lorsqu’une personne change de numéro de téléphone, elle est quand même toujours la même personne. Si on veut mettre à jour un abonné dans un annuaire, il est prudent de commencer par vérifier si l’abonné est déjà dans l’annuaire. Or en testant ('Dupont','Jean','1/1/1970',01234567) in annuaire on risque de ne pas trouver cet abonné parce que dans l’annuaire il y a bien ('Dupont','Jean','1/1/1970',01222233) mais il ne sera considéré comme égal à l’abonné recherché, que s’il a aussi le même numéro de téléphone, ce qui n’est pas le cas. Ainsi, même pour une recherche dans une liste, il peut s’avérer nécessaire de redéfinir l’égalité.

    En SQL, il est obligatoire de choisir des champs qui, à eux tous, constituent une clé primaire, c’est-à-dire qu’il n’y a par exemple pas deux abonnés ayant le même prénom, le même nom et la même date de naissance. En SQL, typiquement, on crée une clé entière déclarée explicitement comme clé primaire.

    En Python, la méthode d’égalité est appelée __eq__ (on prononce dundereq) et on peut la surcharger, par exemple au sein d’une classe Reel modélisant les nombres réels :

    class Reel(float):
        def __init__(self,valeur):
            self.v = valeur
        def __eq__(self,other):
            return abs(self.v-other.v) < 1e-12
        def __add__(self,other):
            return Reel(self.v+other.v)
        def __sub__(self,other):
            return Reel(self.v-other.v)
        def __mul__(self,other):
            return Reel(self.v*other.v)

    Le float entre parenthèses évite d’avoir à redéfinir les opérations : a priori, un réel se comporte comme un flottant, sauf mention contraire. La mention contraire porte sur l’égalité, qui est redéfinie comme une égalité à 10-12 près. La première méthode, __init__, permet de donner une valeur à un réel. Cette valeur est un flottant. Les réels fonctionnent mieux que les flottants, concernant l’égalité :

    >>> x = Reel(0.3)
    >>> y = Reel(0.1) + Reel(0.2)
    >>> x == y
    True
    >>> x
    0.3
    >>> y
    0.30000000000000004

    Python possède un module decimal qui peut aussi faire le job :

    >>> from decimal import *
    >>> x = Decimal('0.3')
    >>> y = Decimal('0.1') + Decimal('0.2')
    >>> x == y
    True
    >>> x
    Decimal('0.3')
    >>> y
    Decimal('0.3')

    Mais si on veut comparer des hypoténuses calculées par Pythagore, on ne peut pas utiliser ce module, parce que les racines carrées ne sont pas nécessairement des décimaux. On utilisera donc des égalités approchées pour aborder les vecteurs.

    Vecteurs et égalité

    Dans le projet de programme pour la rentrée 2026, en Seconde, il est prévu, comme exemple d’algorithme, le suivant :

    Étudier l’alignement de trois points dans le plan.

    Comme, en géométrie, il est également précisé ceci :

    Déterminant de deux vecteurs dans une base orthonormée, critère de colinéarité. Application à l’alignement, au parallélisme.

    Caractériser alignement et parallélisme par la colinéarité de vecteurs.

    Caractérisations de la colinéarité de deux vecteurs non nuls : nullité du déterminant ; proportionnalité des coordonnées.

    on voit qu’il va être nécessaire, à un moment ou à un autre, de définir une fonction determinant en Python, et de comparer deux réels (en fait, comparer un réel avec 0).

    Vecteurs en Python

    Comme le programme précise que le déterminant est une fonction qui, à deux vecteurs, associe un réel, il est nécessaire de voir comment on peut modéliser un vecteur en Python. Le plus simple est de prendre un couple de flottants, mais alors on a ce phénomène :

    def déterminant(u,v):
        (xu,yu)=u
        (xv,yv)=v
        return xu*yv-xv*yu
    
    >>> u = (2,3)
    >>> v = (0.2,0.3)
    >>> déterminant(u,v)
    -1.1102230246251565e-16

    La nullité (c’est-à-dire l’égalité avec 0) du déterminant est un critère de colinéarité. Or, bien que v soit le dixième de u, il n’est pas considéré comme colinéaire à u.

    Colinéarité de deux vecteurs

    Deux vecteurs ayant des coordonnées réelles, leur déterminant doit être un réel :

    def sont_colinéaires(u,v):
        return déterminant(u,v) == 0
    >>> sont_colinéaires(u,v)
    False

    Pour résoudre ce problème on va renoncer à l’égalité stricte et lui préférer une presqu’égalité (à 10-9 près) :

    from math import isclose
    def sont_colinéaires(u,v):
        return isclose(déterminant(u,v),0.0,abs_tol=1e-9)
    
    >>> sont_colinéaires(u,v)
    True

    Mais cela est peu satisfaisant : si le déterminant de u et v est approximativement nul, u et v sont approximativement colinéaires. Pour faire les choses avec plus de précision, on peut utiliser le calcul formel.

    Calcul formel

    En Python le calcul formel se fait avec

    from sympy import *

    Ensuite, on peut redéfinir le vecteur v comme vecteur de fractions :

    >>> v = (Integer(2)/10,Integer(3)/10)
    >>> v
    (1/5, 3/10)
    >>> déterminant(u,v)
    0
    >>> sont_colinéaires(u,v)
    True
    

    La précision du calcul formel est infinie :

    >>> w = (sqrt(8),sqrt(18))
    >>> w
    (2*sqrt(2), 3*sqrt(2))
    >>> sont_colinéaires(v,w)
    True
    >>> sont_colinéaires(u,w)
    True

    Cela peut servir à une découverte des équations de droites, en prenant par exemple un vecteur (x,y) et en charchant s’il est colinéaire avec u :

    >>> x,y = symbols('x y')
    >>> x
    x
    >>> y
    y
    >>> sont_colinéaires(u,(x,y))
    False
    >>> déterminant(u,(x,y))
    -3*x + 2*y

    Ils ne sont pas colinéaires, mais le calcul de leur déterminant montre que l’équation de la droite passant par l’origine et de vecteur directeur u, a pour équation −3×x+2×y=0-3 \times x + 2 \times y = 0

    Et pour l’équation de la droite passant par le point de coordonnées (5,4), et dirigée par le vecteur u, on peut tenter :

    >>> déterminant(u,(5-x,4-y))
    3*x - 2*y - 7

    qui donne l’équation 3x−2y=73x – 2 y = 7.

    Cependant, avec le calcul formel, il convient de se demander ce que signifie l’égalité :

    >>> (x-2)*(x+2) == x**2-4
    False
    >>> (x-2)*(x+2)
    (x - 2)*(x + 2)
    >>> simplify((x-2)*(x+2))
    x**2 - 4

    Des expressions ne doivent être considérées comme égales, que si leurs versions simplifiées (ici, développées) le sont. Il est donc parfois nécessaire de simplifier des expressions avant de les comparer.

  • Séminaire du 18 février 2026

    13h30 — 18h00, PTU, Saint Denis

    Lien pour nous rejoindre en visio

    • 14h-14h45 Le rendez-vous Jeu d’Alain busser: Retour aux jeux de Nim
    • 14h45-15h45 Enseigner avec des tables graphiques au lycée, Dominique Tournès
    • PAUSE
    • 16h15-18h Groupes de travail.
      • Manipulation & diagrammes en barres pour la résolution de problèmes: Depuis les travaux de Bednarz et Janvier (1996), les problèmes de partage en parts inégale de type « déconnecté » permettent une initiation à la pensée algébrique dans le cadre de la résolution de problèmes.La schématisation en barres permet de représenter ce type de problèmes et nous défendons l’idée que c’est la construction du schéma en barres mais surtout sa manipulation qui serait avorable au développement de la pensée algébrique (Lebreton, 2025). Nous vous proposons de tester la manipulation des schémas dans le cadre de la résolution de problèmes sur quelques exemples pour poursuivre la réflexion sur les limites et intérêts d’un tel outil.Format exposé/test/table ronde. Animateur Olivier Lebreton
      • Modèles mémoire: outils pour comprendre l’exécution d’un programme Python. Ces outils seront présentés  en mars prochain à Grenoble lors de la 11e édition de la conférence DIDAPRO, à la fois en présentation d’article et en atelier  et en avant première le 18 février !  Vous aurez l’occasion de manipuler les 2 modèles sur des programmes Python de niveaux différents (fin cycle 4, classe de 2nde, 1re NSI, Tle NSI et supérieur)
        Animateurs: Christophe Declercq & Sebastien Hoarau