sage/docs/intro.rst
2019-03-25 09:19:04 +01:00

484 lines
18 KiB
ReStructuredText
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

########
À propos
########
*************
Problématique
*************
*Contexte: Élèves d'une classe, places assises dans une salle*
*Contraintes:
Le niveau de bavardage d'un élève, les liens sociaux entre élèves*
Comme tout enseignant j'ai été et je suis encore confronté au problème de la
gestion de classe et plus précisément aux interactions efficaces ou inefficaces
des élèves au sein d'une classe.
En effet, les associations d´élèves bavards entre eux peuvent mettre à mal une séance.
Dissocier une binôme productif peut l'être également.
Pour "régler" cela, la démarche consiste à créer un plan de classe afin de diriger
ou limiter les liens que peuvent avoir les élèves entre eux durant une séance.
Ainsi, j'ai tenté plusieurs fois de casser les bavardages entre plusieurs
camarades en les disposant dans la classe à divers endroits et ce dans le
but d'arriver à un équilibre entre bonne dynamique du groupe classe du point
de vue de l'ambiance et du travail et la limitation des bavardages, de l'inattention.
Voulant également passer la certification ISN, il me fallait trouver un projet
qui m'intéressait et qui semblait pertinent pour le dossier à présenter.
*****************
Solution proposée
*****************
*La solution proposée doit:
- utiliser Python
- être simple d'utilisation (IHM, import de la liste des élèves,...)
- proposer un placement automatique des élèves en fonction de certaines contraintes
- permettre d'éditer cette solution de positionnement ou d'en créer une
- permettre de modifier les contraintes*
**********
Conception
**********
1ère phase de recherche
=======================
J'ai tout d'abord commencé par faire des recherches sur cette thématique.
Au hasard de mes recherches, je suis tombé sur une application web, `xperprof`_,
permettant de génerer un plan de classe.
Cette application propose une fonction de positionnement aléatoire des élèves et
notamment la capacité de ne pas soumettre certaines tables à l'algorithme et ainsi
créer des zones vide autour de certains élèves.
Une première idée d'algorithme m'est venue : le positionnement aléatoire.
J'ai toutefois continué mes recherches et ai vu une analogie de mon projet avec
un jeu d'échec.
Un article du blog de `Jean-François GAZET`_ au sujet la création d'un jeu d'échec m'a apporter quelques idées
pour mon développement. Il m'a aider à définir quelques éléments de l'implémentation, à savoir :
- l'IHM: mode console ou graphique ?
- la façon de représenter un élève, un plan de classe
- la notion de "critères" que j'ai requalifié de "contraintes" pour mon projet
- la stratégie algorithmique pour proposer une solution
Restait un élément à préciser : le format de données utilisé pour contenir la liste des élèves.
Mais ce dernier point n'était qu'une formalité.
IHM
===
J'ai tout de suite souhaité que mon application fonctionne en mode console pour deux raisons :
- la première :
Pour moi une application doit pouvoir fonctionner en mode console et pouvoir proposer
une interface graphique au besoin.
J'espérais ainsi passer la majeure partie du temps pour développer l'API et passer le reste sur
l'Interface-Homme-Machine.
- la seconde (qui est une conséquence de la première) :
Compte-tenu du délai qui m'était imposé je ne voulais pas passer trop de temps dans l'apprentissage
d'une librairie graphique.
Format de la liste des élèves
=============================
Afin de charger directement dans l'application une liste des élèves à manipuler,
il est nécessaire de choisir un format de fichier.
Le format **.csv** est celui retenu, de part sa simplicité à le générer.
Le séparateur de champ sera le point-virgule.
L'en-tête du ficher élèves est la suivante :
.. figure:: _static/figure2.png
:align: center
:scale: 60 %
Classes ``Student`` et ``SeatingPlan``
======================================
Dans le contexte de la classe, un **élève** est un individu portant certains attributs, comme
un nom, un prénom, une liste d'amis dans cette même classe ou à l'extérieur,...
Un **plan de classe** également peut porter certaines propriétés, comme ses dimensions,
la liste des sièges disponibles, leurs occupants,...
Ces deux éléments peuvent de surcroît interagir.
Il est alors apparu évident de considérer, d'un point de vue informatique, ces notions
comme des structures de données à part entière.
Ainsi leur définition, leur création, leur manipulation s'en trouveraient facilitées.
Dès lors, le choix du paradigme de programmation orientée objet a été décidé.
Deux classes, ont donc été définies pour cela :
- la classe ``Student``, pour définir les élèves
- la classe ``SeatingPlan``, pour définir le plan de classe
Student
-------
Un élève est défini par :
- son nom
- un niveau de bavardage
- une liste d'amis
::
def __init__(self, name, chat_lvl=0, friends=[]):
""" Instancie un objet plan de classe.
:param name:
Nom de l'élève.
:type name: str
:param chat_lvl:
Coefficient ou niveau de bavardage.
:type chat_lvl: int
:param friends:
Liste d'amis.
:type friends: list
"""
self.name = name
self.chat_lvl = chat_lvl
self.friends = friends
SeatingPlan
-----------
Un plan de classe est défini par :
- un nombre de rangées
- un nombre de colonnes
- une "cartographie" de la classe
À l'origine, la cartographie de mon plan de classe prenait la forme d'une liste
imbriquée, comme dans `jepychess`_.
Cette représentation a évolué pour une structure plus souple et surtout en adéquation
avec celle utilisée dans l'algorithme de calcul.
::
def __init__(self, row=5, col=8):
""" Instancie un objet plan de classe.
:param row:
Nombre de rangées dans la classe.
:type row: int
:param col:
Nombre de colonnes dans la classe.
:type col: int
:param mapping:
Représentation du plan de la classe.
:type mapping: dict
"""
self.row = row
self.col = col
self.mapping = {(i, j): None for i in range(row)
for j in range(col)}
Les contraintes
===============
Les contraintes sont les conditions suivant lesquelles on accepte que deux
élèves soient voisins dans le plan de classe.
Pour commencer, celles-ci seront "naïves". C'est-à-dire qu'elles seront basées
sur une valeur entière représentant le niveau de bavardage de l'élève.
Puis, elles sreont redéfinies pour prendre appui sur les relations sociales
entre les élèves (qui est ami avec ami).
Ainsi, deux élèves pourront être voisins si la différence entre leur niveaux
respectifs de bavardage est d'au moins une valeur fixée.
De la même manière, deux élèves pourront être voisins, si leur lien social
est minimal.
Les niveaux ou coefficients de bavardage seront définis comme suit :
.. figure:: _static/figure1.png
:align: center
:scale: 60 %
2nde phase de recherches : l'algorithme ?
=========================================
Balbutiements
-------------
Initialement, en me basant sur l'exemple de `xperprof`_, j'ai réalisé un placement
aléatoire sous contrainte, des élèves dans le plan de classe.
Le résultat fût concluant mais chaque lancement de cet algorithme produisait une seule proposition
qui pouvait ou non, être complète en raison de contraintes parfois trop fortes.
La première difficulté est ainsi apparue: satisfaire des contraintes tout en proposant des solutions possibles.
L'algorithme **alpha-beta** cité dans le blog de `Jean-François GAZET`_ m'a dirigé vers les méthode de résolution
de jeu d'échec, de dames ou plus généralement de problèmes NP-complet.
Mais également sur le fait de considérer les différentes étapes de recherche de complétion du plan de classe comme
un arbre de recherche.
Mes diverses lectures ainsi que des recherches sur l'Internet m'ont amené au problème des N-Reines.
Diverses méthodes de programmation permettent de résoudre ce problème : comme la programmation par contraintes.
Résoudre un problème de satisfaction de contraintes... ?
--------------------------------------------------------
J'ai vu la résolution de ma problématique comme un problème de satisfaction de contraintes et me suis mis en
quête de réponses concernant la programmation par contraintes.
Plusieurs possibilités
Je me suis alors tourné vers un algorithme avec retour sur trace.
L'algorithme au final
---------------------
Validateur de contraintes
"""""""""""""""""""""""""
::
def respect_constraints(self, seat, student, seatingplan, solution):
"""Indique si une place associée à une élève satisfait aux
contraintes, à savoir si :
- l'élève n'a pas déjà été positionné
- la place n'est pas déjà occupée
- la place est libre, que son voisinage est composé de bons voisins
en termes de bavardages.
:param seat:
Place que l'on veut associer à un élève.
:type seat: tuple
:param student:
Élève que l'on veut associer à une place.
:type student: Student
:param seatingplan:
Plan de classe.
:type seatingplan: SeatingPlan
:param solution:
Associations 'place - élève' valides.
:type solution: dict
:rtype: bool
.. todo::
- Modifier le traitement des contraintes de façon à tolérer
un certains nombre de voisins ne satisfaisants pas aux
contraintes.
- Ajouter les liens sociaux comme contraintes supplémentaires.
"""
# L'association en cours est supposée valide
result = True
# Si l'élève en cours a déjà été traité, l'association est invalide
if student in solution.values():
result = False
# return False
# Si la place en cours a déjà été traitée, l'association est invalide
elif seat in solution.keys():
result = False
# return False
else:
# Si non, veŕifions le voisinage de la place
# Récupérons le voisinage de la place en cours
neighbourhood = self.get_neighbourhood(seatingplan,
seat,
self.NEIGHBOURHOOD_RADIUS)
for neighbour_seat in neighbourhood:
# On ne teste les contraintes que sur les sièges déjà occupés
# donc sur ceux faisant déjà partie des solutions
if neighbour_seat in list(solution.keys()):
# On récupère l'élève du siège voisin
neighbour = solution[neighbour_seat]
# Lequel de l'élève en cours ou du voisin
# est le plus bavard ?
max_chat_lvl = max(student.chat_lvl,
neighbour.chat_lvl)
# Les contraintes portent sur la différence de
# coefficient de bavardage entre deux voisins:
if max_chat_lvl == 5:
result = result and self.are_safe_neighbours(
student,
neighbour,
self.DELTA_FOR_MAX_CHAT_LVL_5)
elif max_chat_lvl == 4:
result = result and self.are_safe_neighbours(
student,
neighbour,
self.DELTA_FOR_MAX_CHAT_LVL_4)
elif max_chat_lvl == 3:
result = result and self.are_safe_neighbours(
student,
neighbour,
self.DELTA_FOR_MAX_CHAT_LVL_3)
elif max_chat_lvl == 2:
result = result and self.are_safe_neighbours(
student,
neighbour,
self.DELTA_FOR_MAX_CHAT_LVL_2)
elif max_chat_lvl == 1:
result = result and self.are_safe_neighbours(
student,
neighbour,
self.DELTA_FOR_MAX_CHAT_LVL_1)
else:
result = result and True
# Le siège voisin n'est pas occupé, il est donc valide
result = result and True
# retournons la validité de l'association
return result
Le solveur
""""""""""
::
def solve(self, seatingplan, students_list):
"""Cherche pour une liste de places et une liste d'élèves, un ensemble
de dispositions dans un plan de classe respectueuses de certaines
contraintes décrites dans la fonction 'respect_constraints' ci-dessus.
:param seatingplan:
Plan de classe.
:type seatingplan: SeatingPlan
:param students_list:
Liste d'élèves à positionner dans le plan de classe.
:type students_list: list
:param solution:
Solution de placement proposée.
:type solutions: dict
:return:
Disposition des élèves dans le plan de classe.
:rtype: generator
"""
# Liste des places
seats_list = list(seatingplan.mapping.keys())
# Quantités de places et d'élèves à traiter
number_of_seats = len(seats_list)
number_of_students = len(students_list)
# On stocke nos associations 'place - élève' dans un dictionnaire
solution = {}
# Indexes de départ dans nos listes de places et d'élèves
idx_seat = 0
idx_student = 0
# Retour sur trace (backtracking)
backtrack = False
# Fin du parcours
end = False
# On commence notre recherche
while not end:
# On ne revient pas encore en arrière
while not backtrack:
# Place à traiter
current_seat = seats_list[idx_seat]
# Élève à traiter
current_student = students_list[idx_student]
# L'association 'place - élève' est-elle satisfaisante ?
if self.respect_constraints(current_seat,
current_student,
seatingplan,
solution):
# Oui, alors associons l'élève à cette place
solution[current_seat] = current_student
# Est-ce qu'on a traité tous les élèves dans
# notre parcourt ?
if (idx_student == number_of_students-1):
# Oui, alors renvoyons notre solution
yield {p: s for p, s in solution.items()}
del solution[current_seat]
# A-t'on traité toutes les places ?
if (idx_seat != number_of_seats-1):
# Non, on passe à la suivante
idx_seat = idx_seat + 1
else:
# Oui, on revient en arrière
backtrack = True
else:
# Non, on passe au suivant en recommençant à la
# première place
idx_seat = 0
idx_student = idx_student + 1
# Les contraintes n'ont pas été satisfaites,
# il nous reste des places à traiter
elif (idx_seat != number_of_seats-1):
# On passe à la suivante
idx_seat = idx_seat + 1
else:
# Si non, on revient en arrière
backtrack = True
# Sommes-nous remonté jusqu'au premier élève ?
end = (idx_student == 0)
# On revient sur nos pas...
while (backtrack and not end):
# Prenons l'élève précédent
idx_student = idx_student - 1
current_student = students_list[idx_student]
# On récupère (et on enlève des solutions) la place qui lui
# est associée
current_seat = list(solution.keys())[list(solution.values())
.index(current_student)]
solution.pop(current_seat)
idx_seat = seats_list.index(current_seat)
# Il nous reste des places
if (idx_seat != number_of_seats-1):
# Prenons la place suivante et stoppons notre retour
# en arrière
idx_seat = idx_seat + 1
backtrack = False
# Ou nous sommes revenu au premier élève, on stop là
elif idx_student == 0:
end = True
********************************
Bilan et améliorations possibles
********************************
L'algorithme fonctionne bien dès lors que l'échantillon d'élèves n'est pas trop grand.
.. _xperprof: https://www.xperprof.fr/generateur_plan_de_classe
.. _Jean-François GAZET: https://fr.jeffprod.com/blog/2014/comment-programmer-un-jeu-dechecs.html
.. _jepychess: `Jean-François GAZET`_