diff --git a/MyIA.AI.Notebooks/Sudoku/Sudoku-01-Backtracking-Csharp.ipynb b/MyIA.AI.Notebooks/Sudoku/Sudoku-01-Backtracking-Csharp.ipynb index 37bf218ed0..1db767885b 100644 --- a/MyIA.AI.Notebooks/Sudoku/Sudoku-01-Backtracking-Csharp.ipynb +++ b/MyIA.AI.Notebooks/Sudoku/Sudoku-01-Backtracking-Csharp.ipynb @@ -585,6 +585,27 @@ "\n" ] }, + { + "cell_type": "markdown", + "id": "9ef4239f", + "metadata": {}, + "source": [ + "### Lecture : `122` -> `490304` -> `12625368` appels, pour le même code\n", + "\n", + "Les trois tests lancent exactement le même `BacktrackingDotNetSolver` ; seule\n", + "la densité d'indices change (`45` indices pour la grille facile, `22` pour la\n", + "moyenne, `17` pour la difficile). Le compteur d'appels rend la combinatoire\n", + "visible : passer de `36` à `64` cases vides multiplie l'exploration par cent\n", + "mille. L'algorithme n'a pas ralenti ; l'espace de recherche a grandi, et le\n", + "backtracking le parcourt sans autre guide que l'ordre fixe des cellules.\n", + "\n", + "C'est la motivation de toute la suite : l'exercice guidé MRV ci-dessous, puis\n", + "les notebooks 06 et 09, ne changent pas la correction de la recherche — ils\n", + "changent l'ordre d'exploration pour couper les branches stériles plus tôt. Le\n", + "temps total (`0,9` ms / `285` ms / `8,7` s) reste proportionnel aux appels :\n", + "chaque appel coûte le même ordre de grandeur, c'est leur nombre qui explose." + ] + }, { "cell_type": "markdown", "id": "4261aa01", diff --git a/MyIA.AI.Notebooks/Sudoku/Sudoku-03-Genetic-Csharp.ipynb b/MyIA.AI.Notebooks/Sudoku/Sudoku-03-Genetic-Csharp.ipynb index ff2960fca3..245e942c6c 100644 --- a/MyIA.AI.Notebooks/Sudoku/Sudoku-03-Genetic-Csharp.ipynb +++ b/MyIA.AI.Notebooks/Sudoku/Sudoku-03-Genetic-Csharp.ipynb @@ -98,6 +98,24 @@ "#r \"nuget: GeneticSharp\"" ] }, + { + "cell_type": "markdown", + "id": "7dbacb83", + "metadata": {}, + "source": [ + "### Lecture : `#r nuget: GeneticSharp` — le framework évolutionnaire, pas une réimplémentation\n", + "\n", + "La première cellule de code installe GeneticSharp par NuGet : sélection,\n", + "crossover, mutation et gestion de population viennent d'une bibliothèque .NET\n", + "dédiée, et tout ce que ce notebook écrit lui-même tourne autour — chromosome,\n", + "fitness, tests. C'est la position inverse du jumeau Python, qui code le GA à la\n", + "main sur numpy : le même enseignement algorithmique, deux niveaux\n", + "d'abstraction. Le choix rend visible ce qu'un framework apporte et ce qu'il ne\n", + "remplace pas : la qualité de la recherche dépend presque entièrement des deux\n", + "classes définies ici-même, le chromosome (comment encoder une grille) et la\n", + "fitness (comment la noter)." + ] + }, { "cell_type": "markdown", "id": "218e1883", @@ -239,6 +257,22 @@ "#!import Sudoku-00-Environment-Csharp.ipynb" ] }, + { + "cell_type": "markdown", + "id": "7a941af5", + "metadata": {}, + "source": [ + "### Lecture : une instance partagée par toute la série\n", + "\n", + "L'import de `Sudoku-00-Environment` charge les grilles et helpers communs : le\n", + "puzzle facile testé plus bas est exactement celui des notebooks 01 et 09\n", + "(reconnaissable à sa première ligne `9 · 2 | · 5 | 4 · 3`). Cette convention a\n", + "une conséquence directe sur la lecture des résultats : quand le solveur\n", + "génétique mettra douze secondes là où le backtracking de Sudoku-01 mettait\n", + "`0,9` ms, la différence ne viendra ni des données ni du langage — uniquement\n", + "de l'algorithme. Toute la série compare des méthodes sur un terrain fixe." + ] + }, { "cell_type": "markdown", "id": "47ce5ce8", @@ -308,6 +342,23 @@ "Console.WriteLine(\"Interface ISudokuChromosome definie (chromosome Sudoku avec TargetSudoku + GetSolution)\");" ] }, + { + "cell_type": "markdown", + "id": "2c165d34", + "metadata": {}, + "source": [ + "### Lecture : le contrat du chromosome\n", + "\n", + "L'interface pose deux membres : `TargetSudoku` (la grille à résoudre) et\n", + "`GetSolution()` (reconstruire une grille complète depuis les gènes). Tout l'art\n", + "du chromosome tient dans cette seconde opération : les gènes forment un espace\n", + "de recherche arbitraire — 81 valeurs libres, 9 permutations, autre chose — et\n", + "`GetSolution` traduit cet espace en grille candidate. La fitness jugera la\n", + "candidate, jamais les gènes directement. Cette séparation est ce qui permettra\n", + "en fin de notebook de changer complètement d'espace de recherche sans toucher\n", + "ni au solveur ni à la fitness." + ] + }, { "cell_type": "markdown", "id": "dc758960", @@ -423,6 +474,24 @@ "Console.WriteLine(\"Classe SudokuFitness definie (evaluation par nombre d'erreurs negatif)\");" ] }, + { + "cell_type": "markdown", + "id": "8ae3278f", + "metadata": {}, + "source": [ + "### Lecture : la fitness vaut `-erreurs` — un paysage sans pente\n", + "\n", + "`SudokuFitness` évalue un chromosome par l'opposé de son nombre d'erreurs :\n", + "grille parfaite = 0 conflit = fitness 0, et tout est négatif en dessous. Ce\n", + "choix simple façonne toute la recherche : il n'existe aucune gradation d'«\n", + "presque bon ». Une grille à 3 conflits et une à 30 conflits disent toutes deux\n", + "« négatif » sans indiquer dans quelle direction améliorer. C'est un paysage en\n", + "marches d'escalier, sans gradient — exactement le terrain où l'exploration\n", + "stochastique (mutation, crossover) a du sens et où un simple hill-climber\n", + "piétine. L'exercice de fitness pondérée ci-dessus propose précisément de\n", + "raffiner cette mesure." + ] + }, { "cell_type": "markdown", "id": "7838974a", @@ -567,6 +636,24 @@ "Console.WriteLine(\"Classe SudokuGeneticSolver definie (solver avec population dynamique et parallelisme TPL)\");" ] }, + { + "cell_type": "markdown", + "id": "2821b9f6", + "metadata": {}, + "source": [ + "### Lecture : population dynamique et parallélisme TPL\n", + "\n", + "Le solver annonce deux mécanismes au-delà du cycle GA standard. La population\n", + "dynamique ajuste la taille de la population en cours de route : élargir quand\n", + "la recherche stagne (plus de diversité génétique), resserrer quand elle\n", + "progresse (concentrer l'effort sur les meilleures lignées). Le parallélisme TPL\n", + "exploite le fait que l'évaluation d'un chromosome est indépendante de celle de\n", + "ses congénères : toute une population s'évalue simultanément sur les cœurs\n", + "disponibles. Notez ce que cela suppose : la fitness est une fonction pure du\n", + "chromosome, sans état partagé — c'est cette propriété qui rend le parallélisme\n", + "trivial." + ] + }, { "cell_type": "markdown", "id": "a21f9896", @@ -704,6 +791,24 @@ "Console.WriteLine(\"Classe SudokuCellsChromosome definie (81 genes, un par cellule)\");" ] }, + { + "cell_type": "markdown", + "id": "379be542", + "metadata": {}, + "source": [ + "### Lecture : première représentation — un gène par cellule\n", + "\n", + "`SudokuCellsChromosome` encode la grille de la manière la plus directe qui\n", + "soit : 81 gènes, un par cellule, chacun libre de valoir 1 à 9. La force de\n", + "cette représentation est sa généralité — elle peut exprimer n'importe quelle\n", + "grille ; sa faiblesse est qu'elle n'en exprime aucune contrainte : rien ne\n", + "distingue génétiquement une ligne valide d'une ligne en doublon. Toute la\n", + "pression vers la validité repose sur la fitness. Le test suivant montre ce que\n", + "cela coûte : l'algorithme finit par trouver la solution, mais il doit\n", + "apprendre par sélection ce que d'autres représentations obtiennent par\n", + "construction." + ] + }, { "cell_type": "markdown", "id": "54f71b49", @@ -812,6 +917,24 @@ "SudokuHelper.SolveSudoku(easySudoku, solver);" ] }, + { + "cell_type": "markdown", + "id": "6844df54", + "metadata": {}, + "source": [ + "### Lecture : résolu — mais à quel prix\n", + "\n", + "`Nombre d'erreurs restantes: 0` : le chromosome par cellules finit par\n", + "produire la grille exacte. Le nombre à côté est tout aussi instructif :\n", + "`12727` ms. Rappelez-vous le terrain — c'est la grille facile, la même que le\n", + "backtracking de Sudoku-01 résolvait en `0,9` ms, quatorze mille fois plus\n", + "vite. Le GA n'est pas « plus lent » dans l'absolu : il paie ici le prix de sa\n", + "représentation permissive. La population part de grilles aléatoires et doit\n", + "éliminer des millions de grilles invalides avant d'approcher la région valide.\n", + "La question devient : peut-on coder la validité dans les gènes eux-mêmes ?\n", + "C'est l'objet de la seconde représentation." + ] + }, { "cell_type": "markdown", "id": "77671ef0", @@ -1150,6 +1273,24 @@ "Console.WriteLine(\"Classe SudokuPermutationsChromosome definie (9 genes, permutations de lignes validees)\");" ] }, + { + "cell_type": "markdown", + "id": "48d96f82", + "metadata": {}, + "source": [ + "### Lecture : seconde représentation — neuf gènes de permutation\n", + "\n", + "Le changement est radical : plus 81 gènes libres, mais 9 gènes — un par ligne\n", + "— où chaque gène encode une permutation des chiffres 1 à 9. Conséquence\n", + "immédiate : chaque ligne d'une grille candidate contient exactement une fois\n", + "chaque chiffre, par construction. Une famille entière de conflits — les\n", + "doublons de ligne — ne peut plus jamais exister, quel que soit le crossover ou\n", + "la mutation : la recherche ne vit plus que dans l'espace des grilles à lignes\n", + "valides. C'est la leçon structurelle du notebook : la représentation est\n", + "elle-même un algorithme — réduire l'espace de recherche vaut mieux que chercher\n", + "mieux dans un espace trop grand." + ] + }, { "cell_type": "markdown", "id": "5347dc92", @@ -1303,6 +1444,27 @@ "SudokuHelper.SolveSudoku(mediumSudoku, solver);" ] }, + { + "cell_type": "markdown", + "id": "d8c2dc3d", + "metadata": {}, + "source": [ + "### Lecture : `186` ms contre `12727` — puis `6` erreurs : la représentation choisit son terrain\n", + "\n", + "Sur la grille facile, la comparaison est sans appel : même solveur, même\n", + "fitness, seule la représentation change — `186` ms contre `12727`, un facteur\n", + "68, pour la même solution exacte. Mais la seconde moitié de la sortie est la\n", + "contrepartie : sur la grille moyenne (`22` indices), le même chromosome par\n", + "permutations bute à `6` erreurs restantes après `42` secondes de sélection.\n", + "Les lignes sont valides par construction ; les contraintes de colonne et de\n", + "bloc, elles, n'ont aucun encodage génétique — permuter une ligne peut les\n", + "satisfaire ou les violer sans que le gène en porte la trace, et les\n", + "croisements de permutations défont facilement des alignements acquis. La\n", + "leçon est complète : une représentation puissante sur un terrain peut être\n", + "pauvre sur un autre ; elle choisit quelles contraintes l'algorithme doit\n", + "encore découvrir." + ] + }, { "cell_type": "markdown", "id": "6f705b5b", diff --git a/MyIA.AI.Notebooks/Sudoku/Sudoku-09-GraphColoring-Csharp.ipynb b/MyIA.AI.Notebooks/Sudoku/Sudoku-09-GraphColoring-Csharp.ipynb index fed0d1b34d..b0e08285fd 100644 --- a/MyIA.AI.Notebooks/Sudoku/Sudoku-09-GraphColoring-Csharp.ipynb +++ b/MyIA.AI.Notebooks/Sudoku/Sudoku-09-GraphColoring-Csharp.ipynb @@ -648,6 +648,24 @@ "Console.WriteLine(\"Classe SudokuGraph definie (graphe de contraintes 81 sommets)\");" ] }, + { + "cell_type": "markdown", + "id": "cd8eb28d", + "metadata": {}, + "source": [ + "### Lecture : reformuler le Sudoku en coloration\n", + "\n", + "`SudokuGraph` change la nature du problème sans en changer une donnée : chaque\n", + "cellule devient un sommet, chaque contrainte d'exclusion (deux cellules\n", + "alignées ne peuvent porter le même chiffre) devient une arête, et résoudre la\n", + "grille devient colorer le graphe avec 9 couleurs de façon propre — un chiffre\n", + "= une couleur. Le vocabulaire change, et la difficulté aussi : là où le\n", + "backtracking de Sudoku-01 choisissait des valeurs de cellules, la coloration\n", + "raisonne sur des conflits entre sommets voisins. C'est ce déplacement qui rend\n", + "les heuristiques de degré et de saturation (DSATUR) pensables — elles\n", + "n'existent pas dans le vocabulaire grille par grille." + ] + }, { "cell_type": "markdown", "id": "cell-a865d9d5", @@ -1075,6 +1093,23 @@ "Console.WriteLine($\" Valide: {solved.IsValid(easySudoku)}\");" ] }, + { + "cell_type": "markdown", + "id": "13d6fb28", + "metadata": {}, + "source": [ + "### Lecture : `49` nœuds, `12` backtracks — lire les statistiques\n", + "\n", + "La sortie vaut moins pour le Sudoku résolu (identique aux autres notebooks,\n", + "terrain commun oblige) que pour ses trois compteurs. `49` nœuds explorés : le\n", + "nombre de sommets sur lesquels l'algorithme a effectivement pris une décision.\n", + "`12` backtracks : les fois où la décision s'est révélée stérile et a été\n", + "défaite — un quart de l'exploration. `2,01` ms : le produit. Ces compteurs\n", + "sont la monnaie du notebook : le benchmark final comparera les algorithmes sur\n", + "ces unités-là, pas seulement en temps — car le temps dépend de la machine, pas\n", + "le nombre de nœuds." + ] + }, { "cell_type": "markdown", "id": "3f26f8c9", @@ -1360,6 +1395,24 @@ "Console.WriteLine(\"Classe GraphColoringMRV definie (coloration avec heuristique MRV)\");" ] }, + { + "cell_type": "markdown", + "id": "8dc3c6db", + "metadata": {}, + "source": [ + "### Lecture : MRV — commencer par la cellule la plus contrainte\n", + "\n", + "L'heuristique MRV (Minimum Remaining Values) réordonne une seule chose : le\n", + "choix du prochain sommet à colorer. Au lieu de parcourir les cellules dans un\n", + "ordre fixe, elle traite d'abord celle dont le domaine de couleurs encore\n", + "possibles est le plus petit. L'intuition : une cellule presque forcée — une\n", + "seule couleur possible — est une décision gratuite à prendre maintenant, et\n", + "chaque décision prise réduit les domaines des voisins, révélant plus tôt les\n", + "impasses. Le benchmark mesurera l'effet exactement : moins de nœuds, et\n", + "surtout beaucoup moins de backtracks, car les branches stériles sont démasquées\n", + "quand elles sont encore courtes." + ] + }, { "cell_type": "markdown", "id": "776c897f", @@ -1688,6 +1741,24 @@ "Console.WriteLine(\"Classe GraphColoringDSATUR definie (coloration DSATUR)\");" ] }, + { + "cell_type": "markdown", + "id": "94b207ac", + "metadata": {}, + "source": [ + "### Lecture : DSATUR — suivre la saturation, pas seulement le domaine\n", + "\n", + "DSATUR raffine MRV d'un cran : le prochain sommet est celui de plus forte\n", + "saturation — le plus grand nombre de couleurs différentes déjà portées par ses\n", + "voisins — à égalité, le plus haut degré. La nuance est réelle : deux cellules\n", + "peuvent avoir autant de couleurs disponibles l'une que l'autre, mais si les\n", + "voisins de la première n'utilisent qu'une couleur alors que ceux de la seconde\n", + "en étalent six, c'est la seconde qu'il est dangereux de différer. DSATUR\n", + "regarde la diversité de la pression, pas seulement son intensité. C'est aussi\n", + "une heuristique classique de coloration de graphes en général — le Sudoku\n", + "n'est ici qu'un terrain d'application." + ] + }, { "cell_type": "markdown", "id": "9d5a3c72", @@ -1864,6 +1935,23 @@ "Console.WriteLine(\"Classes SolverResult + BenchmarkSolvers definies (framework de benchmark)\");" ] }, + { + "cell_type": "markdown", + "id": "3348224b", + "metadata": {}, + "source": [ + "### Lecture : un benchmark, quatre colonnes\n", + "\n", + "Le framework de benchmark fixe le protocole avant de produire un chiffre :\n", + "mêmes grilles pour tous, succès compté, temps moyen, nœuds et backtracks\n", + "cumulés. Cette discipline est ce qui rend les tableaux suivants\n", + "interprétables : un temps seul ne dit rien (machine, charge), un temps relié à\n", + "son nombre de nœuds dit si l'algorithme visite moins ou traverse plus vite.\n", + "Retenez aussi la limite du protocole : « temps moyen » sur 5 grilles faciles\n", + "puis 3 difficiles — la difficulté Sudoku n'est pas une échelle linéaire, et\n", + "les moyennes cachent les écarts par grille." + ] + }, { "cell_type": "markdown", "id": "1f220cff", @@ -2000,6 +2088,23 @@ "}" ] }, + { + "cell_type": "markdown", + "id": "a730604c", + "metadata": {}, + "source": [ + "### Lecture : grilles faciles — la hiérarchie est posée, pas encore départagée\n", + "\n", + "Sur les grilles faciles, le tableau raconte une hiérarchie nette : backtracking\n", + "simple `4250` nœuds pour `4201` backtracks — un backtrack par décision\n", + "quasiment, l'algorithme avance en défonçant des murs ; MRV `63` nœuds et `14`\n", + "backtracks ; DSATUR `74` nœuds et `25` backtracks. Deux ordres de grandeur\n", + "d'écart en backtracks (`4201` contre `14`), pour des temps tous sous la\n", + "dizaine de millisecondes. À garder pour la suite : sur terrain facile, les\n", + "deux heuristiques se valent — le terrain est trop mou pour les départager.\n", + "L'épreuve de vérité est le benchmark difficile, où l'écart devient structurel." + ] + }, { "cell_type": "markdown", "id": "ec99b21e", @@ -2123,6 +2228,24 @@ "}" ] }, + { + "cell_type": "markdown", + "id": "c9848a91", + "metadata": {}, + "source": [ + "### Lecture : grilles difficiles — `4356440` nœuds contre `1202`, l'heuristique comme multiplicateur\n", + "\n", + "C'est le tableau central du notebook. Sur les grilles difficiles, le\n", + "backtracking simple explose : `4356440` nœuds et `4356375` backtracks — chaque\n", + "décision est quasi systématiquement défaite, l'algorithme explore une forêt\n", + "d'impasses. Les heuristiques changent l'ordre de grandeur, pas un pourcentage :\n", + "MRV visite `1406` nœuds, DSATUR `1202` — trois mille fois moins. En temps :\n", + "`2650` ms contre `19,3` et `12,7`. DSATUR bat MRV (`12,67` contre `19,33` ms),\n", + "conformément à sa lecture plus fine de la saturation : sur terrain difficile,\n", + "les deux heuristiques ne sont plus interchangeables. Moteur identique, grilles\n", + "identiques — seul l'ordre des décisions a varié, et il vaut un facteur `209`." + ] + }, { "cell_type": "markdown", "id": "bfd1fa8f", @@ -2264,6 +2387,26 @@ "Console.WriteLine($\"\\nTemps de resolution DSATUR : {sw.Elapsed.TotalMilliseconds:F4} ms\");" ] }, + { + "cell_type": "markdown", + "id": "d9906d2c", + "metadata": {}, + "source": [ + "### Lecture : l'intégration — le solver devient un composant\n", + "\n", + "L'intégration à `SudokuHelper` montre l'aboutissement pratique :\n", + "`GraphColoringDSATUR` résout la grille commune avec `0` erreur, via la même\n", + "interface que tous les solveurs de la série. Les deux temps affichés se\n", + "lisent comme un garde-fou de mesure : `0,3028` ms pour le helper, `3,0956` ms\n", + "pour la résolution DSATUR nue — l'écart dit que les deux chronomètres\n", + "n'encerclent pas le même périmètre (construction du graphe comprise ou non).\n", + "Comparer des temps exige de savoir ce qu'ils englobent. Sur les grilles\n", + "faciles, tous les solveurs de la série restent sous la dizaine de\n", + "millisecondes ; l'écart se creuse sur les grilles difficiles — et l'intérêt de\n", + "DSATUR est aussi structurel : sa lecture en saturation se généralise à tout\n", + "problème de coloration, pas seulement au Sudoku." + ] + }, { "cell_type": "markdown", "id": "dd4a96db", @@ -2530,6 +2673,26 @@ "Console.WriteLine($\"Coloration propre : {(conflicts == 0 ? \"OUI\" : \"NON\")} (DSATUR sur structure QuikGraph = valide)\");" ] }, + { + "cell_type": "markdown", + "id": "cb458105", + "metadata": {}, + "source": [ + "### Lecture : le graphe vérifié par le moteur .NET — et le degré `20` décomposé\n", + "\n", + "La tranche 2 rebâtit le graphe sur QuikGraph, la bibliothèque de graphes de\n", + "référence du monde .NET, et la sortie réconcilie tout : `81` sommets et `810`\n", + "arêtes — exactement les valeurs du jumeau Python `nx.sudoku_graph()` citées en\n", + "regard. Le degré uniforme `20` se décompose proprement : chaque cellule entre\n", + "en conflit avec ses `8` congénères de ligne, ses `8` de colonne, et `4` de son\n", + "bloc 3x3 que ligne et colonne n'ont pas déjà couverts (`20 = 8 + 8 + 4`) ; et\n", + "`81 x 20 / 2 = 810` arêtes, chaque arête comptée deux fois. La composante\n", + "unique confirme un graphe connexe : aucune région du Sudoku n'est résoluble\n", + "isolément. Enfin la vérification finale — `0` conflit sur les `810` arêtes —\n", + "est la preuve par le graphe que la coloration DSATUR est propre, contrôle\n", + "indépendant du solveur qui l'a produite." + ] + }, { "cell_type": "markdown", "id": "223184fe", diff --git a/scripts/notebook_tools/twin_pairs.d/sudoku-01-backtracking/0010-2026-09-13-myia-po-2026-CoursIA.yaml b/scripts/notebook_tools/twin_pairs.d/sudoku-01-backtracking/0010-2026-09-13-myia-po-2026-CoursIA.yaml new file mode 100644 index 0000000000..919b07e849 --- /dev/null +++ b/scripts/notebook_tools/twin_pairs.d/sudoku-01-backtracking/0010-2026-09-13-myia-po-2026-CoursIA.yaml @@ -0,0 +1,6 @@ +date: '2026-09-13' +by: myia-po-2026:CoursIA +python_sha: 3a75c3b46fda76ff606110365482a3258ade9d78 +csharp_sha: 1db767885bdbf9896a67d8cbad29949f2eff7051 +content_python_sha: 2d78a4636ff7276454db3936ca0df62814371d89765ea4cfffe508daf3403c3b +content_csharp_sha: 85f56a55576876006bbc32a2ada6c1d3cbd5a87683bf8799fc90237b3a4333cc diff --git a/scripts/notebook_tools/twin_pairs.d/sudoku-03-genetic/0010-2026-09-13-myia-po-2026-CoursIA.yaml b/scripts/notebook_tools/twin_pairs.d/sudoku-03-genetic/0010-2026-09-13-myia-po-2026-CoursIA.yaml new file mode 100644 index 0000000000..8ef1612e71 --- /dev/null +++ b/scripts/notebook_tools/twin_pairs.d/sudoku-03-genetic/0010-2026-09-13-myia-po-2026-CoursIA.yaml @@ -0,0 +1,6 @@ +date: '2026-09-13' +by: myia-po-2026:CoursIA +python_sha: 6c75a0e2f0b9848515d96cb2c8554f715ebb4786 +csharp_sha: 245e942c6cf5511c35c703feac04e940e37c015d +content_python_sha: baf9bbd11aaf1ec6c10e935b614fb223fee8bacd869b6aba315a8c9c2d817e52 +content_csharp_sha: ba1cf5785aa6f7bdd140f768875048fb404202afa27db435c82f192a92cfe61f diff --git a/scripts/notebook_tools/twin_pairs.d/sudoku-09-graphcoloring/0011-2026-09-13-myia-po-2026-CoursIA.yaml b/scripts/notebook_tools/twin_pairs.d/sudoku-09-graphcoloring/0011-2026-09-13-myia-po-2026-CoursIA.yaml new file mode 100644 index 0000000000..5ab196e85a --- /dev/null +++ b/scripts/notebook_tools/twin_pairs.d/sudoku-09-graphcoloring/0011-2026-09-13-myia-po-2026-CoursIA.yaml @@ -0,0 +1,6 @@ +date: '2026-09-13' +by: myia-po-2026:CoursIA +python_sha: 88c6f5ae56c75b604ebefa0be61d6060f961ecef +csharp_sha: b0e08285fd93ac0232886e9b8191cc4195fb6f11 +content_python_sha: 76a7a64fade11ace3b667bf109a939f27aea9b12ad24e4e5f8b3a60418fc2e4b +content_csharp_sha: b1e12794563025ce139160487e0981435ad7d5ad6b294d62de922b0b5e90697c