Propagation: Trouver le plus court chemin dans une matrice
Bonjour à toutes et tous
Une nouvelle version de Propagation (la version 2.2 en date du 26 septembre 2024) est maintenant disponible aux téléchargements.
Elle est beaucoup plus rapide et surtout beaucoup moins gloutonne (càd que le temps de calcul par cellule augmente moins vite au fur et à mesure que les dimensions de la matrice augmentent) que la précédente.
Elle s’appuie sur une nouvelle logique qui écarte toute recherche de minima et tout tri d’où un gain de temps évident.
Elle n’utilise ni heuristique ni probabilités et l’algorithme continue d’être admissible càd fournit à coup sûr le meilleur résultat (du moins en termes de coût du chemin pour ce qui est du nombre d’étapes rien n’est prouvé).
J’espère que vous serez nombreux à venir l’essayer et me faire part de vos commentaires !
Merci
Bonsoir,
j'ai testé et je dois dire que cela fonctionne bien.
J'ai sur ma machine un code similaire fourni par h2so4, je vous l'ai fourni pour vous amuser à faire quelque tests de rapidité de l'un par rapport à l'autre. Attention ! C'est juste pour voir, mais si j'ai bien compris le votre il y a des accès "feuilles Excel" pour mettre en mémoire des données de parcours, donc cela devrait le ralentir quelque peu, non ?
Une fois de plus bienvenu dans le "petit" monde des contributeurs !
@ bientôt
LouReeD
Bonjour LouReeD et merci beaucoup pour tes retours. Je commence seulement à chercher à comprendre (j'étais trop absorbé par la finalisation de la version 2 de Propagation) le code de Hero Quest que tu m'as envoyé en MP et vu mon petit niveau en VBA cela risque de me prendre un peu de temps. Ton jeu me semble très prometteur je suis impatient de l'essayer, bravo pour le soin apporté à la présentation ! Et merci de ne m'avoir envoyé que le code qui m'intéresse le plus. Une fois que j'aurai assimilé le code de h2so4 je mènerai un banc d'essai Chrono sur des plateaux de grandes dimensions.
Effectivement le fait d'utiliser des feuilles Excel pour stocker des données n'est peut-être pas le plus judicieux en termes de temps de calcul. Si le code de h2so4 est significativement plus rapide j'essaierai de trouver un autre procédé plutôt que d'utiliser ces feuilles.
A bientôt pour un compte-rendu du comparatif
Merci beaucoup pour ce nouveau Post. Non je ne connaissais pas ce sujet très riche. Je commence à peine à me familiariser avec le code que tu m'as envoyé et avec ce nouveau sujet je sens que je n'ai pas fini de m'amuser… Merci aussi de m'avoir sensibilisé au problème du temps des accès feuille je pense qu'il ne devrait pas être trop difficile de procéder autrement; à voir les résultats en termes de temps de calcul.
Donc très reconnaissant à vous monsieur LouReeD
Bonsoir,
Alors ces temps de calculs, c'en est où ?
@ bientôt
LouReeD
Bonsoir,
Ca avance modestement disons que la matrice que j'avais prise pour exemple et qui était solvée en 8 minutes tout rond l'est maintenant en 24,31 secondes !
Tu sais que tu m'auras donné beaucoup de boulot avec ton foutu lien sur le Post précédent ! Cela aura pour avantage que je pourrai fournir une page d'aide plus documentée mais j'ambitionne maintenant de programmer un estimateur de durée réaliste.
Propagation v2.1 te devras beaucoup
Bonjour ,
Ce Post est en lien avec un autre déposé sur maths-forum.com. Il est destiné principalement aux matheux venant de ce forum. Il contient les fichiers qu'il est impossible de déposer dans les Posts de maths-forum.
En plus de ces fichiers je vais tenter d'expliquer un peu mieux le principe de Propagation:
La matrice Excel est convertie en Cubes. Une périphérie isolante (Cubes.Matrix = -1) s'ajoute aux Cubes de la matrice. Ainsi pour une matrice NbX x Nby x NbZ cellules il y a (NbX+2) x (NbY+2) x (NbZ+2) Cubes
Les Cubes sont des Arrays à trois dimensions : Cube(X, Y, Z) et ont trois propriétés définies par un type personnalisé :
- .Matrix correspond aux valeurs de résistance telles qu'on les retrouve dans la feuille : "Matrice"
- .Cumulation est la somme résistive du Cube par rapport au point de départ. Une fois que celui-ci a été calculé une première fois on ne revient plus dessus. C'est ce qui permet à l'algorithme d'avancer.
- .Path stocke les adresses du chemin et ne sert à rien d'autre qu'à permettre à Excel de tracer le chemin en vert, c'est pour cela que je ne l'ai même pas présenté dans le logigramme
L'Array : "UnderStudy" comporte tous les Cubes en cours d'étude. Le premier élément confié à UnderStudy est le point de départ et il est solutionné niveau Cumulation. Viendront s'y ajouter ensuite et en premier lieu les Cubes immédiatement adjacents ou périphériques à ce point. Ces derniers sont dès lors solutionnés niveau Cumulation.
Une fois tous les Cubes autour du point de départ calculés celui-ci est exclu de UnderStudy. Le Cube appartenant à UnderStudy et de résistance minimale au niveau Cumulation devient alors le centre du même calcul que pour le point de départ.
Le calcul prend fin lorsque le Cube d'arrivée a été atteint. S'il ne l'est pas c'est qu'il ne peut pas l'être et le calcul prend fin une fois tous les Cubes de la matrice calculables calculés, le résultat est alors : "Sans solution"
Merci à tous ceux qui auront pris la peine de lire ce Post et un remerciement beaucoup plus appuyé à celui qui saura ou m'aidera à trouver l'équation juste pour l'estimateur de durées. Il figurera avec LouReeD de ce forum dans les remerciements de cette app !
Bonjour le forum,
Voici en avant-première la dernière version de mon Pathfinder. La mauvaise nouvelle c’est qu’il ne sait plus traiter que des entiers. Les bonnes nouvelles c’est que :
- Il traite maintenant une grille de 16384x16384 (mon ordinateur est doté de 32Go de RAM) avec points de départ et d’arrivée aux deux coins opposés en seulement 5 minutes et 6 secondes*
- Son code est devenu plus conventionnel, fini les variables à nom à rallonge et les rappels de type de données partout dans le code (type &, %) qui je le pensais amélioreraient la lisibilité du code mais c’était une erreur de ma part.
- Son code est abondamment commenté.
- Son code est plus court.
Pour info la plus grosse matrice adjacente que j’avais testé avec Propagation v2.1 était une 10000x10000 adja et elle avait réclamé plus de 21 heures de calcul ! Et avec un random à priori plus favorable.
Propagation v2.2 reste un algorithme admissible (càd qu’il fournit à coup sûr le meilleur chemin) ; il n’utilise aucune heuristique et aucune probabilité. Son gain de temps s’explique par le fait que j’ai pu supprimer tout tri et même toute recherche de minima si coûteuses en temps…
J’espère que vous serez curieux de l’essayer et avoir vos retours !
* Avec mon PC Icore5, en mode adjacent et avec un random =ENT(ALEA()*51) et hors temps de lecture de la feuille qui avoisine les 22 minutes.
Voici le code du module de Pathfinding :
' sfPathFinder2D v2.2.1 libre de droits, dernière révision : 05 oct 2024
' par Stéphane Fillon
' Ce module comporte toutes les procédures nécessaires à la lecture de la Worksheet et au Pathfinding. Vous pourrez l'utiliser seul dans vos applications
' L'intégralité des paramètres à ajuster à votre application est suivi d'un commentaire commençant par '# , facilitant leur recherche...
' La procédure VotreCode est un exemple de procédure minimaliste permettant d'utiliser les autres procédures de ce module.
Option Explicit
Option Private Module
Private Type Node
IC As Integer ' IC: Individual Cost; coût individuel du Node(= valeurs de la Worksheet)
TC As Long ' TC: Total Cost; addition des IC depuis le point de départ en passant par le plus court-chemin
path As Byte ' Info de parcours d'où on peut extraire un x et un y qui peuvent prendre les valeurs -1, 0 ou 1
Calc As Boolean ' Indique si le Node a été atteint
End Type
Private Type Point2D
x As Integer
y As Long
End Type
Private Type lp ' lp comme Liste de Points
p2d() As Point2D
b As Boolean
End Type
Private Const Ocol As Byte = 1 '# Numéro de colonne de l'origine (cellule supérieure gauche) de la matrice, (ex: 5 pour colonne "E")
Private Const Orow As Byte = 2 '# Numéro de Ligne de l'origine...
' Private WsMatrix As Worksheet '# Décommenter cette ligne
Private Nodes() As Node ' Nodes(X, Y) est un Array 2D contenant tous les points de la matrice
' La procédure suivante est un exemple de code exécutable faisant appel aux trois procédures suivantes:
' - Sub Swallow -> Lecture des valeurs de la WorkSheet pour les intégrer en tableau de type Node
' - Function fPath( -> La fonction de Pathfinding renvoyant les adresses du chemin sous forme de String ex: "A3,B3,C3,C4,D4,...".
' - Function fRange( -> Retourne l'objet Range correspondant à la chaine retournée par fPath.
Public Sub VotreCode()
Dim StartAddress$, ArrivalAddress$, path$
Dim NbCol%, NbRow&
Dim rngPath As Range
Dim SommeChemin&, NbEtapes&
On Error GoTo ErrorHandler
StartAddress = "D3" '# Adresse cellule de départ
ArrivalAddress = "M12" '# Adresse cellule d'arrivée
NbCol = 10 '# Nombre de colonnes de la matrice
NbRow = 10 '# Nombre de lignes de la matrice
Set WsMatrix = Feuil1 '# Le nom de la feuille (ici par son codename)
WsMatrix.Cells.Interior.Pattern = xlNone
WsMatrix.Range(StartAddress).Interior.Color = vbRed
WsMatrix.Range(ArrivalAddress).Interior.Color = 192
Call Swallow(NbCol, NbRow) ' Ingurgitation données WorkSheet dans objets de type Node
' Calcul du + court-chemin renvoyé sous forme de String contenant les adresses ex: "E5,E6,F6,G6,..., L21"
path = fPath(StartAddress, ArrivalAddress, PeripheralMode:=False)
' Gestion des cas particuliers : sans solution et départ et arrivée accolés
If path = "WithoutSolution" Then MsgBox "Sans Solution !": Exit Sub
If path = "" Then MsgBox "Arrivée et départ sont accolés !": Exit Sub
Set rngPath = fRange(path) ' fRange(Path) équivaut à Range(Path) sans la limitation à 255 caractères de l'argument Path
' Ce qui suit est un exemple d'exploitation de Path et rngPath
rngPath.Interior.Color = vbGreen ' 1ère exploitation du Range: rngPath, Tracé du chemin en vert
SommeChemin = Application.WorksheetFunction.Sum(rngPath) ' 2ème exploitation: calcul de la somme des coûts rencontrés
NbEtapes = rngPath.Count ' 3ème exploitation: calcul du nombre d'étapes
' Affichage du MsgBox récapitulatif
MsgBox "Coût total= " & SommeChemin & vbCr & _
"Nombre d'étapes= " & NbEtapes
Erase Nodes
Exit Sub
ErrorHandler:
Erase Nodes
MsgBox "VotreCode() Erreur lors de l'exécution de cette procédure", vbExclamation, "Erreur"
End Sub
' Intégration des données WorkSheet en variable tableau Nodes(X, Y).
Public Sub Swallow(ByVal NbCol%, ByVal NbRow&, Optional ByVal Lecture As Boolean = True)
Dim x%, y&
If Lecture Then ' Lecture de la feuille
ReDim Nodes(NbCol + 1, NbRow + 1)
' On parcourt l'intégralité de la matrice
For y = 1 To NbRow ' De ligne à ligne
For x = 1 To NbCol ' Et de colonne à colonne à l'interieur d'une ligne
With Nodes(x, y)
.IC = WsMatrix.Cells(y + Orow - 1, x + Ocol - 1)
If .IC >= 0 Then .TC = -1 ' TC = -1 signifie à ce stade que le Node n'a pas un coût infini et pourra être intégré à US
End With
Next x, y
Else ' Si la matrice n'a pas été modifiée il n'est pas nécessaire de relire la feuille
For y = 1 To NbRow
For x = 1 To NbCol
With Nodes(x, y)
If .IC >= 0 Then .TC = -1
.Calc = False
End With
Next x, y
End If
End Sub
' fPath assure le Pathfinding et renvoie une chaine de caractère composée des adresses des cellules entrant dans le plus court chemin.
Public Function fPath$(ByVal StartAddress$, ByVal ArrivalAddress$, Optional ByVal PeripheralMode As Boolean = False)
Dim US() As lp ' US comme UnderStudy = les cellules atteintes lors de la première sous-boucle mais dont on a pas encore calculé le voisinage.
Dim VZ As lp ' VZ comme : Valeurs Zéro
Dim cnd As Point2D ' cnd comme : Current NoDe
Dim p2dn As Point2D ' p2dn comme : Point2D Neighbour
Dim TC& ' TC comme Total Cost soit le cumul des coûts individuels en passant par le plus court-chemin.
Dim a&, i&, m&, uba&, TCmin&, TCmax&
Dim x%
Dim Xn% ' Xn = x du Neighbour
Dim Xsp%, Xap% ' Xsp = x du Start Point: Xap = x de Arrival Point.
Dim y&, Yn&, Ysp&, Yap& ' Mêmes principes pour y que pour x.
Dim o As Byte ' Valeur de décalage dont on pourra extraire le x et le y.
Dim ubo As Byte ' ubo comme : UBound Offsets.
Dim w As Byte
Dim Offsets() ' Les valeurs de décalage en x et y codées pour être stockées sous forme de byte.
On Error GoTo ErrorHandler
' Détermination du point de départ
Xsp = WsMatrix.Range(StartAddress).Column - Ocol + 1
Ysp = WsMatrix.Range(StartAddress).Row - Orow + 1
' Détermination du point d'arrivée
Xap = WsMatrix.Range(ArrivalAddress).Column - Ocol + 1
Yap = WsMatrix.Range(ArrivalAddress).Row - Orow + 1
' Si le point d'arrivée se trouve sur une case infiniment chère fPath retourne : "WithoutSolution" et s'interrompt.
If Nodes(Xap, Yap).IC < 0 Then fPath = "WithoutSolution": Exit Function
ReDim US(0) ' initialisation US.
ReDim US(0).p2d(0)
' le premier élément d'US est le point de départ.
US(0).p2d(0).x = Xsp
US(0).p2d(0).y = Ysp
US(0).b = True 'US(0) contient des points
Nodes(Xsp, Ysp).TC = 0 ' Le TC du point de départ est égal à zéro
If PeripheralMode Then ' 8 Nodes autour du Node principal
Offsets = Array(1, 3, 5, 7, 0, 2, 6, 8)
Else ' 4 Nodes partageant une face avec le Node central (Adjacent)
Offsets = Array(1, 3, 5, 7)
End If
ubo = UBound(Offsets)
' BOUCLE PRINCIPALE '
Do
a = 0
VZ.b = False ' Il n'y a pas de valeurs zéro
uba = UBound(US(TCmin).p2d)
Do
cnd = US(TCmin).p2d(a) ' Déclaration du noeud courant
' Parcours de toutes les cellules au voisinage immédiat du Current Node (cnd).
For w = 0 To ubo
o = Offsets(w) ' Lecture de l'Offset
Xn = cnd.x + Int(o Mod 3) - 1 ' Int(o Mod 3) - 1 peut prendre 3 valeurs : -1, 0 ou 1
Yn = cnd.y + Int(o / 3) - 1 ' Int(o / 3) - 1 peut prendre 3 valeurs : -1, 0 ou 1
p2dn.x = Xn: p2dn.y = Yn ' Création du Point2D Neighbour
With Nodes(Xn, Yn) ' Un des Nodes voisins de cnd.
' Le TC est < à 0 pour tous les Nodes ne présentant pas un coût infini...
'... voir procédure Swallow.
If .TC < 0 Then
' le TC du Node voisin de cnd est égal à son coût individuel IC ajouté au...
' ... Total Cost de ce MainNode.
TC = Nodes(cnd.x, cnd.y).TC + .IC ' le TC du voisin est égal à son IC ajouté au TC du Current Node
.TC = TC
.path = o ' path stocke l'offset tel quel. On l'inversera en fin de procédure...
'... dans le label PathFound ce qui permettra de remonter le chemin.
.Calc = True ' Calc indique que le Node à été atteint.
If .IC = 0 Then ' si l'Individual Cost du voisin est égal à zéro
If VZ.b Then ' si il y a déjà des valeurs zéro
m = UBound(VZ.p2d) + 1
' ajouter ce point à la liste de points Valeur Zéro
ReDim Preserve VZ.p2d(m)
VZ.p2d(m) = p2dn ' Ajout du Neighbour à la liste de points de VZ
Else
' Créer le premier point de Valeur Zéro
ReDim VZ.p2d(0)
VZ.p2d(0) = p2dn ' Le premier point de VZ est le Neighbour
VZ.b = True ' Il y a des Valeurs Zero
End If
Else ' Si l'Individual Cost du voisin est > à 0
' si le TC du Neighbour est supérieur au TC maxi déjà connu redimmensionner US à ce nouveau TC
If TC > TCmax Then TCmax = TC: ReDim Preserve US(TC)
' Ajout du Neighbour à la liste de points de US(TC)
If US(TC).b Then
i = UBound(US(TC).p2d) + 1
ReDim Preserve US(TC).p2d(i)
US(TC).p2d(i) = p2dn
Else
ReDim US(TC).p2d(0)
US(TC).p2d(0) = p2dn
US(TC).b = True
End If
End If
' Cells(Orow + Yn - 1, Ocol + Xn - 1).Interior.Color = vbMagenta ' Pour visualisation des Nodes atteints
End If
End With
Next w ' on peut passer au prochain point voisin
If Nodes(Xap, Yap).Calc Then GoTo PathFound ' SORTIE AVEC SOLUTION
' Cells(Orow + cnd.y - 1, Ocol + cnd.x - 1).Interior.Color = vbYellow ' Pour visualisation des Nodes finis de calculer
a = a + 1 ' on peut passer au Current Node suivant.
Loop Until a > uba
If VZ.b Then ' Si l'on a atteint des valeurs zéro dans la sous-boucle Do Loop précédente...
' US(TCmin) récupère la liste de points de VZ
ReDim US(TCmin).p2d(UBound(VZ.p2d))
US(TCmin).p2d = VZ.p2d
Else
Erase US(TCmin).p2d ' Sinon on efface la liste de points de US(TCmin)
' et l'on recherche le prochain US non vide de points
For i = TCmin + 1 To TCmax ' Nota : TCmax = Ubound(US)
If US(i).b Then TCmin = i: Exit For ' si US(i) contient des points
Next i
If i > TCmax Then fPath = "WithoutSolution": Exit Function ' SORTIE SANS SOLUTION
End If
Loop ' Fin de la boucle principale.
PathFound:
Dim adr$, path$
x = Xap: y = Yap
' On remonte du point d'arrivée jusqu'au point de départ en utilisant l'inverse de l'offset des Nodes
Do Until x = Xsp And y = Ysp
adr = Cells(1, Ocol + x - 1).Address(False, False)
path = Left$(adr, Len(adr) - 1) & Orow + y - 1 & "," & path
o = 8 - Nodes(x, y).path ' récupération de l'offset inversé
x = x + Int(o Mod 3) - 1 ' extraction de la composante x
y = y + Int(o / 3) - 1 ' extraction de la composante y
Loop
If path <> "" Then path = Left$(path, Len(path) - 1) ' Suppression de la virgule finale
fPath = path
Exit Function
ErrorHandler:
MsgBox "Une erreur s'est produite lors de l'exécution de cette macro", vbExclamation, "sfPathFinder2D.fPath()"
Exit Function
End Function
' fRange renvoie un objet Range l'argument RefCel$ pouvant excéder 255 caractères ce
' qui justifie cette fonction. Cette fonction est loin d'être optimale !
Public Function fRange(ByVal RefCel$) As Range
If RefCel = "" Then Exit Function
' Si "RefCel" fait moins de 256 caractères inutile de s'engager dans de long calculs...
If Len(RefCel) < 256 Then Set fRange = WsMatrix.Range(RefCel): Exit Function
Dim y As Byte, cpt%, v(), rng As Range
Do While RefCel <> ""
y = InStrRev(Left(RefCel, 244), ",")
cpt = cpt + 1: ReDim Preserve v(cpt)
If y = 0 Then
v(cpt) = RefCel
RefCel = ""
Exit Do
Else
v(cpt) = Left$(RefCel, y - 1)
RefCel = Mid$(RefCel, y + 1)
End If
Loop
If RefCel <> "" Then v(cpt) = v(cpt) & "," & RefCel
Set rng = WsMatrix.Range(v(1))
For cpt = 2 To UBound(v)
Set rng = Union(rng, WsMatrix.Range(v(cpt)))
Next
Set fRange = rng
End FunctionBonjour Stéphane1972 !
Deux choses : Lors de l'utilisation du USF en augmentant la taille de la matrice, les colonnes au-dessus de 10, n'ont pas la même largeur !
la deuxième : un très grand merci pour la référence de mon Pseudo ! Cela me fait plaisir ! Si je vous dis que je ne pense pas que ce soit nécessaire vous me répondrez que si ! Alors j'en prend la joie de le voir !
Bon je ne comprend pas tout dans le code. Sinon comment fait-on pour créer des obstacle sur le chemin afin de forcer un détour ? J'ai réussi en mettant des 999 dans certaines cellules... Peut-être n'ai-je pas vu "l'option".
Bon et beau travail et cette distribution Free
@ bientôt
LouReeD
Bonjour LouReed,
Je suis heureux que nos chemins se recroisent !
Oui ça Bugge un peu sur la largeur des colonnes je vais devoir me repencher un peu sur la procédure NewMatrix pour l’instant il y a quelque chose qui m’échappe…
C’est vous que je remercie encore pour les raisons que j’ai évoqué dans la page d’aide.
Pour créer des obstacles il faut rentrer un entier négatif par exemple -1
Sinon la page d’aide n’est pas à jour le mode de calcul en arrivée flottante a été supprimé et il faut impérativement spécifier un point d’arrivée sinon bug.
Bonne journée et @+
Stéphane
Juste un petit ColumnWidth avec une valeur de 20 et un RowHeight avec une valeur de 13.
Mais il est vrai qu'il y a sur ma machine un petit temps avant de voir les cellules se mettre en place... Pourtant au niveau de la feuille il n'y a que le masquage de colonnes et lignes non ?
@ bientôt
LouReeD
Oui la procédure NewMatrix est assez décevante je vais essayer de revoir ma copie ! Avez-vous testé de grosses matrices et si oui que pensez-vous des performances du Pathfinder à proprement parler ?
@+
Bonsoir,
je n'ai pas fait de gros tests encore... désolé.
Je pourrais essayer de la comparer au code h2so4
Je vous tiens au courant, restez sur la fréquence !
@ bientôt
LouReeD
Bonsoir et merci d'avance,
Je reste sur la fréquence
Bonjour,
Je tiens d’abord à remercier (bien tardivement…) Sébastien pour le gros travail qu’il a effectué pour l’amélioration de la mise à jour de ma description longue.
Obligé de relativiser sur les performances de mon Pathfinder qui ne sont bonnes qu’à condition que les valeurs dans la grille soient basses (ordre d’idée : étagées de 0 à 250). L’intérêt de ce Pathfinder reste :
- D’être rapide si l’on respecte à peu près cette condition.
- De ne pas « bloquer » quand la taille de la grille augmente.
- De savoir résoudre efficacement les systèmes 3D.
Dans les précédentes versions de Propagation on recherchait par une boucle la case présentant le Coût Cumulé minimal et l’on pouvait alors calculer le coût cumulé de ses cases voisines et établir le lien de parenté. Le nombre de cases grappillées augmentant vite cette boucle se retrouvait en inflation plus ou moins rapide selon le mode Adjacent/Périphérique et 2D/3D. Ceci a pour effet de limiter la taille des grilles calculables dans un temps raisonnable.
Avec la version actuellement en ligne on ne recherche plus de coût cumulé minimal mais on enregistre ces coûts en tant qu’index dans une variable tableau de type liste de points :
Private Type Point2D
X As Integer
Y As Integer
End Type
Private Type lp ' lp comme : Liste de Points
p2d() As Point2D
cp As Boolean ' cp comme : Contient des Points
End TypeAprès calcul du voisinage des nœuds courants on progresse dans le coût cumulé jusqu’à atteindre un booléen cp à True et on peut alors entamer une nouvelle itération de la boucle principale avec le nouveau coût cumulé incrémenté. Plus aucune boucle en inflation dans ce système donc un temps de calcul par cellule relativement stable quelles-que soient les dimensions de la grille.
Le fichier suivant (qui ne gère plus les obstacles et que les Bytes) offre la possibilité de tester des grilles de dimensions nettement supérieures à celles prises en charge par une feuille Excel classique.