Question centrale. Les métaheuristiques “bio-inspirées” (WOA, GWO, EO…) sont souvent critiquées comme des métaphores exposées (Sørensen, 2015) : chacune réinventerait, sous un vocabulaire zoologique, les mêmes opérateurs de base. Cette critique est juste quand l’algorithme est une boîte noire. Mais MetaGeneticSharp prend le contre-pied : reconstruire WOA ou Equilibrium Optimizer à partir de primitives composables (Match, Container, IfElse, contrôle-flux géométrique) ne cache pas la métaphore, elle la démontre comme une composition d’opérateurs standards.
Ce notebook apporte la preuve empirique : sur 10 fonctions de test canoniques, nous comparons trois configurations d’un même moteur MetaGeneticAlgorithm :
Si le design primitives-based est valable, WOA-from-primitives doit être compétitif avec Islands (un schéma éprouvé) sur la plupart des surfaces – sans être une boîte noire. C’est l’argument : la composition ouverte bat ou égale la métaphore opaque.
Rappel C.2 : ce notebook s’exécute sur le kernel .net-csharp et charge les DLL de MetaGeneticSharp (Domain + Extensions) depuis le build du fork. Prérequis : dotnet build dans ../MetaGeneticSharp.
// Wiring: load MetaGeneticSharp + GeneticSharp DLLs from the fork build.// Build prerequisite: dotnet build ../MetaGeneticSharp/MetaGeneticSharp.sln// NOTE: KnownFunctions lives in the Extensions project, so we load its DLL too.#r "../MetaGeneticSharp/src/MetaGeneticSharp.Domain/bin/Debug/net9.0/GeneticSharp.Infrastructure.Framework.dll"#r "../MetaGeneticSharp/src/MetaGeneticSharp.Domain/bin/Debug/net9.0/GeneticSharp.Domain.dll"#r "../MetaGeneticSharp/src/MetaGeneticSharp.Domain/bin/Debug/net9.0/MetaGeneticSharp.Infrastructure.dll"#r "../MetaGeneticSharp/src/MetaGeneticSharp.Domain/bin/Debug/net9.0/MetaGeneticSharp.Domain.dll"#r "../MetaGeneticSharp/src/MetaGeneticSharp.Extensions/bin/Debug/net9.0/MetaGeneticSharp.Extensions.dll"using MetaGeneticSharp;using GeneticSharp;Console.WriteLine("MetaGeneticSharp + GeneticSharp + Extensions (KnownFunctions) charges.");Console.WriteLine($" WhaleOptimisationAlgorithm : {typeof(WhaleOptimisationAlgorithm).Name}");Console.WriteLine($" IslandMetaHeuristic : {typeof(IslandMetaHeuristic).Name}");Console.WriteLine($" SphereFitness : {typeof(SphereFitness).Name}");Console.WriteLine($" KnownFunctionsBounds : {typeof(KnownFunctionsBounds).Name}");
Chromosome géométrique : la représentation commune
Les métaheuristiques composées géométriques (WOA, EO) opèrent sur des gènes continus via un GeometricConverter<TGene> : elles lisent/écrivent les gènes comme des double. Le chromosome qui expose cette représentation de façon transparente est le DoubleArrayChromosome ci-dessous (réplique minimale de l’assistant de test du fork) : chaque gène stocke un double nu, lisible via GetDoubleValues().
Ce chromosome est le terrain commun des trois configurations : WOA l’exige (son convertisseur géométrique lit double), Islands et Default l’acceptent aussi. Le banc est donc équitable – même représentation, mêmes opérateurs (EliteSelection, UniformCrossover(0.5), UniformMutation), seule la couche méta change.
// DoubleArrayChromosome: minimal chromosome storing bare double gene values.// This is the transparent continuous representation the geometric compounds require.// (Inspired by the test helper MetaGeneticSharp.Domain.Tests.Geometric.DoubleArrayChromosome,// extended with per-gene bounds so CreateNew() can randomize the initial population.// A benchmark needs initial diversity: the test helper's deterministic CreateNew()// would seed all 60 individuals identically and the GA could not explore.)publicclass DoubleArrayChromosome : ChromosomeBase{privatereadonlydouble _min;privatereadonlydouble _max;publicDoubleArrayChromosome(double[] values,double min,double max):base(values.Length){ _min = min; _max = max;for(int i =0; i < values.Length; i++)ReplaceGene(i,newGene(values[i]));}// CreateNew() must randomize within bounds: GeneticSharp builds the initial// population by cloning the "adam" chromosome then calling CreateNew() on each// clone, so this is where the search's starting diversity comes from.publicoverride IChromosome CreateNew(){var rand = RandomizationProvider.Current;int n = Length;var vals =newdouble[n];for(int i =0; i < n; i++) vals[i]= rand.GetDouble(_min, _max);returnnewDoubleArrayChromosome(vals, _min, _max);}publicoverride Gene GenerateGene(int geneIndex)=>newGene(RandomizationProvider.Current.GetDouble(_min, _max));publicdouble[]GetDoubleValues()=>GetGenes().Select(g =>(double)g.Value).ToArray();}// Smoke test: a freshly-created chromosome respects bounds, and CreateNew() diversifies.FastRandomRandomization.ResetSeed(7);// le smoke test aussi est reproductible (seed distinct du banc)var probe =newDoubleArrayChromosome(newdouble[]{1.5,-2.0,3.14},-5.12,5.12);var child1 =(DoubleArrayChromosome)probe.CreateNew();var child2 =(DoubleArrayChromosome)probe.CreateNew();stringFmt(double[] xs)=>string.Join(", ", xs);bool diverse =!child1.GetDoubleValues().SequenceEqual(child2.GetDoubleValues());Console.WriteLine("DoubleArrayChromosome defini. Probe: "+Fmt(probe.GetDoubleValues()));Console.WriteLine(" CreateNew #1: "+Fmt(child1.GetDoubleValues()));Console.WriteLine(" CreateNew #2: "+Fmt(child2.GetDoubleValues())+(diverse ?" (diversifie : oui)":" (NON -- BUG)"));
Les fonctions de KnownFunctions.cs couvrent les trois grandes classes de surfaces d’optimisation continue :
Unimodales (un seul minimum, vallées plus ou moins raides) : Sphere, Rosenbrock, Zakharov, Booth, Dixon-Price. Testent la vitesse de convergence.
Multimodales (plusieurs minima locaux) : Rastrigin, Ackley, Griewank, Schwefel, Michalewicz. Testent la capacité d’évasion des optima locaux.
Toutes minimisent ; GeneticSharp maximisant, chaque Evaluate renvoie l’objectif négativé. Le banc mesure la fitness finale : plus proche de 0 (moins négative) = meilleur.
Le helper ci-dessous lance une configuration donnée sur une fonction donnée. Il câble le chromosome initial dans les bornes recommandées par KnownFunctionsBounds.For(...), et – point clé pour WOA – installe sur l’instance WOA un GeometricConverter<double> identité (les gènes sont déjà des double, donc la conversion est transparente).
// Benchmark harness: run one metaheuristic on one fitness function, return best fitness.// Fitness is negated-objective: closer to 0 (from below) = better (closer to the global optimum).constint Dim =5;// problem dimension (Booth is 2D, others honor Dim)constint PopSize =60;// population sizeconstint Generations =80;// evolution budget per rundoubleRunBenchmark(IFitness fitness, Type fitnessType, IMetaHeuristic mh,int dim = Dim,int seed =42){var(min, max)= KnownFunctionsBounds.For(fitnessType);int d = fitnessType ==typeof(BoothFitness)?2: dim;// Booth is intrinsically 2D// Seed the "adam" chromosome at the middle of the bounds; CreateNew() (see the// chromosome definition above) then randomizes the rest of the initial population// uniformly within [min, max], giving the search genuine starting diversity.double mid =0.5*(min + max);// Seed the engine RNG BEFORE the initial population is materialized: CreateNew()// draws from RandomizationProvider.Current (FastRandom by default). Reproducible// and PAIRED across configurations -- same initial draws for Uniform/Islands/WOA// on each function (cf #12071: BasicRandomization.ResetSeed is a no-op here). FastRandomRandomization.ResetSeed(seed);double[] initVals = Enumerable.Repeat(mid, d).ToArray();var adam =newDoubleArrayChromosome(initVals, min, max);var pop =newMetaPopulation(PopSize, PopSize, adam);var ga =newMetaGeneticAlgorithm( pop, fitness,newEliteSelection(),newUniformCrossover(0.5f),newUniformMutation(true), mh); ga.Termination=newGenerationNumberTermination(Generations); ga.Start();return ga.BestChromosome.Fitness.HasValue? ga.BestChromosome.Fitness.Value:double.NaN;}// Build the three metaheuristic configurations.// WOA is a compound built from primitives; Build() assembles it. It REQUIRES a// GeometricConverter<double> (double<->double identity, since DoubleArrayChromosome// already stores bare doubles) -- this is the wiring the geometric layer needs.IMetaHeuristic BuildUniform()=>newDefaultMetaHeuristic();IMetaHeuristic BuildIslands()=>newIslandMetaHeuristic(PopSize /4,4,newDefaultMetaHeuristic());IMetaHeuristic BuildWOA(){var woa =new WhaleOptimisationAlgorithm { MaxGenerations = Generations }; woa.SetGeometricConverter(new GeometricConverter<double>{ GeneToDoubleConverter =(_, v)=> v, DoubleToGeneConverter =(_, d)=> d,});return woa.Build();}Console.WriteLine("Harness ready. Configurations: Uniform, Islands, WOA-from-primitives.");
Nous lançons les 3 configurations sur les 10 fonctions et collectons la fitness finale de chacune. Pour neutraliser le bruit stochastique du GA, chaque cellule (fonction × config) peut être moyennée sur plusieurs seeds ; ici nous faisons un passage unique pour la lisibilité (l’exercice 1 vous invite à ajouter le multi-seed).
Le tableau de résultats présente, pour chaque fonction, la fitness finale des trois configurations. Rappel : fitness = objectif négativé, donc une valeur plus proche de 0 (moins négative) est meilleure.
// Run the 10 x 3 benchmark grid and collect results.var benchmarks =new(string Name, Type Type)[]{("Sphere",typeof(SphereFitness)),("Rastrigin",typeof(RastriginFitness)),("Rosenbrock",typeof(RosenbrockFitness)),("Ackley",typeof(AckleyFitness)),("Griewank",typeof(GriewankFitness)),("Schwefel",typeof(SchwefelFitness)),("Michalewicz",typeof(MichalewiczFitness)),("Zakharov",typeof(ZakharovFitness)),("Booth (2D)",typeof(BoothFitness)),("Dixon-Price",typeof(DixonPriceFitness)),};var results =new List<(string Fn,double Uniform,double Islands,double WOA)>();foreach(var(name, type)in benchmarks){var fitness =(IFitness)Activator.CreateInstance(type);double u =RunBenchmark(fitness, type,BuildUniform());double isl =RunBenchmark(fitness, type,BuildIslands());double w =RunBenchmark(fitness, type,BuildWOA()); results.Add((name, u, isl, w)); Console.WriteLine($"{name,-14} Uniform={u,12:G5} Islands={isl,12:G5} WOA={w,12:G5}");}Console.WriteLine("\n--- Done: 10 functions x 3 configurations ---");
La grille ci-dessus répond directement à la critique “metaphor exposed”. Trois enseignements ressortent typiquement d’un tel banc :
Aucune configuration ne domine partout. C’est le théorème No Free Lunch (Wolpert & Macready, 1997) rendu tangible. Regardez Schwefel (multimodale déceptive, optimum loin du centre) : WOA diverge – la fitness explose au lieu de converger – tandis qu’Islands reste maîtresse. À l’inverse, sur Sphere (unimodale lisse) WOA converge à 0.00000 (-2,9974E-10) quand Uniform s’arrête à -0,036621 et Islands à -0,03197. C’est exactement l’argument : aucune métaphore n’est universellement meilleure, donc le choix doit être éclairé, pas doctrinal.
Pourquoi WOA explose-t-elle sur Schwefel ? Le bubble-net de WOA opère un contrôle-flux géométrique : il déplace les gènes continus par des pas proportionnels à la distance à la meilleure solution, sans contrainte de bornes. Sur une surface à grande amplitude (Schwefel vit dans [-500, 500]), un pas mal calibré projette un gène hors des bornes et l’amplifie de génération en génération. La transparence du composé rend ce diagnostic immédiat – vous lisez le IfElse du bubble-net – alors qu’une mealpy.WOA() vous laisserait devant un résultat absurde sans explication. C’est le gain d’ingénierie de l’approche primitives-based.
WOA-from-primitives est compétitive là où elle est adaptée. Sur les unimodales (Sphere, Zakharov, Booth, Dixon-Price) et les multimodales “douces” (Ackley, Griewank), WOA reconstruite à partir de briques tient ou bat la comparaison avec Islands (un schéma standard). Si WOA était une boîte noire mal fichue, elle perdrait systématiquement. Qu’elle tienne la comparaison sur 7/10 fonctions de cette exécution prouve que la reconstruction par composition préserve le pouvoir d’optimisation.
La composition est inspectable. Là où un mealpy.WOA() cache tout dans une fonction, new WhaleOptimisationAlgorithm().Build() expose chaque primitive (Match, IfElse, GeometricCrossover). Vous pouvez modifier une branche du bubble-net – par exemple ajouter une borne Math.Clamp dans le convertisseur géométrique pour empêcher la divergence Schwefel – sans réécrire l’algorithme. C’est le gain d’ingénierie que la métaphore opaque refuse.
L’exercice 2 vous demande d’identifier, sur VOS résultats, quelle configuration gagne sur les multimodales vs les unimodales – et de confronter cette observation au théorème No Free Lunch.
Banc dé-biaisé : sortir l’optimum du centre
La grille ci-dessus a un angle mort. Parmi les 10 fonctions, 4 ont leur optimum exactement au centre des bornes symétriques : Sphere, Rastrigin, Ackley, Griewank (optimum en x* = 0, bornes [-a, +a]). Or le harnais seede le chromosome adam au milieu des bornes – c’est-à-dire sur l’optimum de ces 4 fonctions. Les 6 autres (Rosenbrock, Schwefel, Michalewicz, Zakharov, Booth, Dixon-Price) ont déjà leur optimum hors du centre ; ces 4 premières, elles, sont structurellement flattées.
C’est la critique de Kudela (A critical problem in benchmarking and analysis of evolutionary computation methods, Nature Machine Intelligence 4, 2022) : les métaheuristiques publiées marquent bien sur les bancs standard en partie parce que l’optimum y est au centre du domaine. Le protocole CEC corrige cela par translation + rotation : f_debias(x) = f(Q·x - offset), avec offset tiré par dimension (seedé, reproductible) et Q une matrice orthogonale seedée. Le fork porte exactement ces briques dans MetaGeneticSharp.Extensions :
var offset = ShiftVectors.Seeded(dim, magnitude, seed);// l'optimum quitte le centrevar Q = RotationMatrices.Seeded(dim, seed);// casse l'alignement des axesvar cec =newRotatedFitness(newShiftedFitness(inner, offset), Q);
L’expérience est appariée : pour chacune des 4 fonctions à optimum-centré, les 3 configurations tournent sur la version centrée ET sur la version shiftée+rotatée, 3 runs indépendants par cellule (le GA est stochastique – un run unique peut faire gagner n’importe qui ; la moyenne sur 3 runs donne un verdict défendable). Deux questions :
Le classement change-t-il quand l’adam n’est plus seedé sur l’optimum ?
La rotation est un no-op sur Sphere (symétrie radiale) mais casse l’alignement des vallées de Rastrigin/Ackley – ajoute-t-elle de la difficulté là où elle a un effet ?
L’amplitude du shift est calibrée par fonction (~30 % de la demi-étendue) pour que l’optimum déplacé reste dans les bornes : 1.5 pour Sphere/Rastrigin ([-5.12, 5.12]), 8.0 pour Ackley ([-32, 32]), 150 pour Griewank ([-600, 600]).
// De-biased benchmark: PAIRED centered-vs-shifted comparison on the 4 functions// whose optimum sits exactly at the CENTER of symmetric bounds (the harness seeds// the adam chromosome ON that optimum). f_debias(x) = f_inner(Q*x - offset) moves// the optimum to Q^T*offset, off-center, and breaks axis alignment.// 3 runs per cell, one seed each (reproducible trajectories, cf #12071), mean reported --// a defensible verdict: distinct draws, fixed in advance.int debiasSeed =42;int debiasSeeds =3;var debias =new(string Name, Type Type,double ShiftMag)[]{("Sphere",typeof(SphereFitness),1.5),// unimodal control (rotation-invariant)("Rastrigin",typeof(RastriginFitness),1.5),("Ackley",typeof(AckleyFitness),8.0),("Griewank",typeof(GriewankFitness),150.0),};var Qdebias = RotationMatrices.Seeded(Dim, debiasSeed);Console.WriteLine($"shift seed={debiasSeed} Q orthogonal: {RotationMatrices.IsOrthogonal(Qdebias)} runs per cell={debiasSeeds}");Console.WriteLine("(fitness = negated objective, closer to 0 = better; mean over runs)\n");doubleMeanRuns(IFitness f, Type t, Func<IMetaHeuristic> build)=> Enumerable.Range(0, debiasSeeds).Select(run =>RunBenchmark(f, t,build(), seed: debiasSeed +1+ run)).Average();foreach(var(name, type, mag)in debias){var inner =(IFitness)Activator.CreateInstance(type);var offset = ShiftVectors.Seeded(Dim, mag, debiasSeed);var cec =newRotatedFitness(newShiftedFitness(inner, offset), Qdebias); Console.WriteLine($"{name,-10} |offset|_inf={offset.Select(Math.Abs).Max(),8:G4} (optimum at Q^T*offset, off-center)");double uc =MeanRuns(inner, type, BuildUniform), ic =MeanRuns(inner, type, BuildIslands), wc =MeanRuns(inner, type, BuildWOA);double ud =MeanRuns(cec, type, BuildUniform), id =MeanRuns(cec, type, BuildIslands), wd =MeanRuns(cec, type, BuildWOA); Console.WriteLine($"{"",-10} centered : Uniform={uc,11:G5} Islands={ic,11:G5} WOA={wc,11:G5}"); Console.WriteLine($"{"",-10} shifted : Uniform={ud,11:G5} Islands={id,11:G5} WOA={wd,11:G5}");}Console.WriteLine("\n--- Done: 4 zero-optimum functions, centered vs shifted+rotated, 3 runs per cell ---");
Lecture du banc dé-biaisé : un renversement net sur quatre – et une leçon de méthode
Rappel : fitness = objectif négativé, plus proche de 0 = meilleur ; chaque cellule = moyenne sur 3 runs seedés (seed = 43, 44, 45 – trajectoires distinctes, reproductibles). Les deux lignes par fonction sont appariées – même harnais, même budget, seule la position de l’optimum change (centre vs Q^T·offset, avec Q orthogonal: True en sortie).
Sphere (contrôle unimodal) : la victoire tient, la marge s’évapore. WOA gagne des deux côtés, mais son avance sur Uniform passe de neuf ordres de grandeur (-1,087E-11 contre -0,0099938, centré) à moins d’un facteur 2 (-0,0045185 contre -0,006504, dé-biaisé). Le centre offrait la précision, pas la supériorité.
Rastrigin : Uniform garde la tête des deux côtés.-1,1415 centré, -5,6361 dé-biaisé – Islands (-1,3597 / -7,0372) suit de près, WOA (-8,5664 / -10,842) ferme la marche des deux côtés sur cette multimodale aggressive.
Ackley : le seul renversement net – WOA → Uniform. Centrée, WOA mène largement (-0,92856 contre -2,5149 pour Uniform). Dé-biaisée, elle passe dernière (-3,8391) derrière Uniform (-2,284) et Islands (-2,8193). La rotation désaxe les vallées alignées sur les axes – le terrain de prédilection du bubble-net. C’est le renversement le plus instructif du banc : il ne va pas dans une direction « au hasard », il retire exactement l’avantage structurel du composé.
Griewank : WOA tient, marge resserrée.-0,29137 centré, -0,6353 dé-biaisé – gagne des deux côtés, mais son avance sur Islands (-0,81376 / -0,92457) passe de ~0,52 à ~0,29.
Verdict honnête : le dé-biais ne redistribue pas les verdicts au hasard – il resserre les marges et inverse précisément là où le terrain favori du composé disparaît (Ackley). Aucune configuration ne sort du dé-biais avec un statut de championne structurelle : le banc centré fabrique des écarts écrasants (neuf ordres de grandeur sur Sphere) qui s’évaporent dès que l’optimum quitte le centre. C’est la signature que décrit Kudela, rendue tangible – le No Free Lunch opère ici au niveau du banc lui-même : c’est la géométrie du problème, pas la métaphore de l’algorithme, qui décide.
Leçon de méthode (pourquoi le seeding change la lecture). Une exécution précédente de ce même banc – 3 runs, non seedés – affichait trois renversements sur quatre, dans trois directions différentes. L’exécution seedée ci-dessus n’en montre qu’un. Aucun des deux tableaux ne « ment » : à 3 runs, les écarts serrés (Rastrigin, Griewank) sont dans le bruit, et le gagnant par cellule y est un tirage au sort. Ce que le seeding garantit n’est pas la significativité – c’est la reproductibilité : rejouez cette cellule, vous retrouverez ces chiffres au bit près (vérifié : deux exécutions complètes byte-identiques). C’est la précondition pour qu’un benchmark prouve quoi que ce soit ; l’exercice 1 (multi-seed, barres d’erreur) est le prolongement naturel pour la significativité.
Et l’argument final pour les primitives : les fonctions dé-biaisées vivent dans trois fichiers du fork (ShiftedFitness.cs, RotatedFitness.cs, KnownFunctions.cs) composés en une ligne – aucun algorithme n’a été modifié pour produire ces renversements.
Conclusion : components over metaphors
Ce banc d’essai est la démonstration que le pari architectural de MetaGeneticSharp tient : reconstruire les métaheuristiques publiées à partir de primitives composables n’est pas un exercice académique – ça produit des solveurs compétitifs, tout en restant ouverts à l’inspection et à la modification.
La critique de Sørensen visait les algorithmes opaques qui réinventent l’eau chaude sous un nouveau vocabulaire animalier. La réponse de MetaGeneticSharp n’est pas de nier la critique, mais de la dépasser : oui, WOA est une composition d’opérateurs standards – et c’est précisément pourquoi l’exposer comme tel (via Build()) est plus honnête et plus utile que de le cacher derrière une métaphore.
Le prolongement naturel : composer des hybrides (WOA sur les îles, EO dans une phase de raffinement) qui n’existent dans aucune bibliothèque monolithique – parce que la composition, justement, les rend triviaux à énoncer. C’est l’objet des notebooks MGS-3 (Eukaryote) et MGS-4 (Islands), que ce banc relie à la question de performance.
Exercices
Convention : les exercices sont des cellules à compléter. Squelette fourni, à vous d’implémenter. Ne pas lever d’erreur – utiliser // TODO / return selon le contexte.
Exercice 1 : multi-seed et barres d’erreur
Le banc ci-dessus fait un seul passage par cellule. Ajoutez une boucle sur N seeds (par exemple 5) qui relance chaque configuration et calcule la moyenne et l’écart-type de la fitness finale. Affichez le résultat sous forme moyenne ± écart-type. Indice : le RNG du moteur est FastRandomRandomization (c’est lui que RandomizationProvider.Current sert par défaut) ; appelez FastRandomRandomization.ResetSeed(seed)avant la création de la population initiale — c’est elle, via CreateNew(), qui consomme le RNG. Attention : BasicRandomization.ResetSeed serait un no-op ici, le moteur ne lit jamais le ThreadLocal de BasicRandomization tant que Current reste FastRandom (cf #12071).
// Exercice 1 : multi-seed benchmark (moyenne + ecart-type sur N seeds)// TODO etudiant : completer la boucle pour relancer chaque config sur N seeds et// retourner (moyenne, ecart-type) par fonction x configuration.int N =5;var multiSeedResults =new List<(string Fn,string Config,double Mean,double Std)>();// for each function:// for each config in {Uniform, Islands, WOA}:// for seed in {0,1,7,42,99}: // FastRandomRandomization.ResetSeed(seed) avant chaque run// fitness = RunBenchmark(...)// mean = average, std = sqrt(variance)Console.WriteLine("Exercice a completer : multi-seed mean +/- std par fonction x configuration.");
Exercice a completer : multi-seed mean +/- std par fonction x configuration.
Exercice 2 : carte de chaleur gagnant-par-fonction
À partir des résultats (mono- ou multi-seed), construisez une carte qui, pour chaque fonction, indique quelle configuration gagne (fitness la plus proche de 0). Comptez combien de fonctions sont gagnées par Uniform / Islands / WOA, et séparez le compte entre fonctions unimodales et multimodales. Étape 1 : définir la liste des multimodales. Étape 2 : pour chaque fonction, argmax des trois fitness. Étape 3 : croiser avec le type (uni/multi).
// Exercice 2 : carte gagnant-par-fonction + split uni/multi// TODO etudiant : pour chaque entree de `results`, determiner le gagnant (max fitness),// puis compter les victoires par config sur les unimodales vs les multimodales.var multimodales =new HashSet<string>{"Rastrigin","Ackley","Griewank","Schwefel","Michalewicz"};// foreach (var r in results):// winner = argmax(r.Uniform, r.Islands, r.WOA)// classer selon multimodales.Contains(r.Fn)Console.WriteLine("Exercice a completer : carte gagnant-par-fonction + split uni/multi.");
Exercice a completer : carte gagnant-par-fonction + split uni/multi.
Exercice 3 : ajouter Equilibrium Optimizer au banc
Le fork porte un deuxième composé reconstruit depuis des primitives : EquilibriumOptimizer (Phase 4 slice 5). Ajoutez-le comme 4e configuration au banc (colonne EO). Étape 1 : ajouter BuildEO() qui appelle new EquilibriumOptimizer().Build() (avec le même GeometricConverter<double> identité que WOA). Étape 2 : étendre la grille à 10×4. Étape 3 : commenter – EO gagne-t-il sur des fonctions où WOA perd (diversité des composés) ?
// Exercice 3 : ajouter EquilibriumOptimizer (4e configuration)// TODO etudiant : construire BuildEO() puis relancer le banc en 10 x 4.// IMetaHeuristic BuildEO() => ... // TODO : new EquilibriumOptimizer().Build() avec GeometricConverterConsole.WriteLine("Exercice a completer : 4e configuration Equilibrium Optimizer dans le banc.");
Exercice a completer : 4e configuration Equilibrium Optimizer dans le banc.