A la fin de ce notebook, vous saurez : 1. Comprendre le concept d’eukaryote : des individus multi-chromosomes dont chaque partie est evoluee independamment 2. UtiliserEukaryoteChromosome pour decouper un genome en sous-chromosomes (caryotype) et les reconstruire 3. Configurer des sous-populations isolees avec des stratégies différentes (agressive vs conservatrice) 4. Orchestrer l’ensemble avec EukaryoteMetaHeuristic et comparer avec une approche uniforme
Prerequis
MGS-1 : Introduction a MetaGeneticSharp et au moteur autonome
MGS-2 : Composition de métaheuristiques (Match, primitives de contrôle)
Notions de base en algorithmes génétiques (sélection, crossover, mutation)
C# .NET 9.0 et .NET Interactive
Duree estimee : 45 minutes
1. Architecture eucaryote
Types fondamentaux
Le concept eucaryote repose sur trois types fondamentaux qui collaboraient pour decomposer un genome, isoler l’evolution de chaque partie, puis reagreger les résultats :
Type
Rôle
Parent dans la hiérarchie
EukaryoteChromosome
Sous-chromosome : vue sur une partition du genome parent
ChromosomeBase
SubPopulation
Population isolee pour une partition, avec contexte indépendant
MetaPopulation
EukaryoteMetaHeuristic
Orchestrateur : créé les sous-populations et applique les sous-heuristiques
SubPopulationMetaHeuristicBase<SubPopulation>
Flux de données
Population parente (N individus x G genes)
|
| EukaryoteChromosome.GetSubPopulations(parents, [k1, k2])
v
Sous-population 0 Sous-population 1
(N x k1 genes) (N x k2 genes)
Stratégie A Stratégie B
| |
+-------- PerformSubOperator --------+
|
v
EukaryoteChromosome.GetNewIndividuals()
|
v
Population evoluee (N individus x G genes)
Analogie biologique
En biologie, une cellule eucaryote contient un noyau et des organites, chacun avec un rôle specialise. De même, l’eukaryote de MetaGeneticSharp decompose le genome d’un individu en chromosomes enfants (le caryotype), chacun evolue dans une sous-population avec sa propre stratégie, avant d’etre recombine en un individu complet.
La cellule suivante charge les DLL MetaGeneticSharp et GeneticSharp depuis le build du sous-module. Assurez-vous que le build est a jour avant d’executer ce notebook.
Note : si le wiring echoue, verifiez que le sous-module GeneticSharp est initialise (git submodule update --init GeneticSharp) et que le build est a jour (dotnet build depuis ../MetaGeneticSharp).
// Setup: fitness function, helpers, and EukaryoteMetaHeuristic factory// Fitness: minimize distance to targets (position=50, velocity=25)// f(x, v) = -(|x - 50| + |v - 25|) -- GeneticSharp maximizes, so we negatepublicclass PositionVelocityFitness : IFitness{publicdoubleEvaluate(IChromosome chromosome){var fc =(FloatingPointChromosome)chromosome;var genes = fc.ToFloatingPoints();return-(Math.Abs(genes[0]-50)+ Math.Abs(genes[1]-25));}}// Helper: build an EukaryoteMetaHeuristic with heterogeneous strategies// Sub-population 0 (position): DefaultMetaHeuristic (crossover + mutation actifs)// Sub-population 1 (velocity): NoOpMetaHeuristic (parents pass through unchanged)// NOTE: Eukaryote calls MatchParentsAndCross with probability=1 internally.// Setting ProbabilityConfig.Crossover.OverwriteProbability on sub-heuristics// can cause NullReferenceException in GetNewIndividuals if it rejects some crossovers.// Instead, we use DefaultMetaHeuristic (active) vs NoOpMetaHeuristic (no-op).IMetaHeuristic BuildEukaryote(){// Sub-pop 0: DefaultMetaHeuristic (standard crossover + mutation)var active =newDefaultMetaHeuristic();// Sub-pop 1: NoOpMetaHeuristic (crossover and mutation are no-ops)var noOp =newNoOpMetaHeuristic();returnnewEukaryoteMetaHeuristic(16, active, noOp){ Scope = EvolutionStage.Crossover| EvolutionStage.Mutation};}// Helper: build an EukaryoteMetaHeuristic with two DefaultMetaHeuristic (same strategy)IMetaHeuristic BuildEukaryoteUniform(){returnnewEukaryoteMetaHeuristic(16,newDefaultMetaHeuristic(),newDefaultMetaHeuristic()){ Scope = EvolutionStage.Crossover| EvolutionStage.Mutation};}// Helper: run GA with any IMetaHeuristic on the position-velocity problem// Uses FuncFitness + EliteSelection (canonical pattern from EukaryoteMetaHeuristicTests)doubleRunWithEukaryote(IMetaHeuristic mh,string label,int popSize =40,int generations =50){var adam =newFloatingPointChromosome(newdouble[]{0,0},newdouble[]{100,100},newint[]{16,16},newint[]{2,2});var pop =newMetaPopulation(popSize, popSize, adam);var ga =newMetaGeneticAlgorithm( pop,newFuncFitness(c =>{var values =((FloatingPointChromosome)c).ToFloatingPoints();return-(Math.Abs(values[0]-50)+ Math.Abs(values[1]-25));}),newEliteSelection(),newUniformCrossover(0.5f),newUniformMutation(true), mh); ga.Termination=newGenerationNumberTermination(generations); ga.Start();var best =((FloatingPointChromosome)ga.BestChromosome).ToFloatingPoints();return Math.Abs(best[0]-50)+ Math.Abs(best[1]-25);}Console.WriteLine("Setup OK : PositionVelocityFitness, BuildEukaryote, RunWithEukaryote definis");Console.WriteLine(" EukaryoteChromosome : "+typeof(EukaryoteChromosome).Name);Console.WriteLine(" SubPopulation : "+typeof(SubPopulation).Name);Console.WriteLine(" SubPopulationContext : "+typeof(SubPopulationContext).Name);Console.WriteLine(" EukaryoteMetaHeuristic : "+typeof(EukaryoteMetaHeuristic).Name);Console.WriteLine(" SubPopulationMetaHeuristicBase : "+typeof(SubPopulationMetaHeuristicBase<SubPopulation>).Name);
Le coeur du concept eucaryote est le chromosome composite : un individu dont le genome est decoupe en caryotypes (partitions). Chaque partition est un EukaryoteChromosome qui reference son parent et connait son decalage dans le genome complet.
Cycle de vie d’un EukaryoteChromosome
1. Creation du parent
FloatingPointChromosome([0,0], [100,100], [16,16], [2,2])
-> 2 genes = 32 bits au total
2. Decoupage en karyotype (GetKaryotype)
EukaryoteChromosome.GetKaryotype(parent, [16, 16])
-> Karyotype[0] : genes [0..15] (position)
-> Karyotype[1] : genes [16..31] (vitesse)
3. Evolution independante de chaque sous-chromosome
(chaque sous-population applique sa stratégie)
4. Resynchronisation (UpdateParent)
Les genes modifiés du sous-chromosome sont ecrits dans le parent
5. Reconstruction (GetNewIndividual)
Un nouvel individu complet est créé a partir du caryotype evolue
Méthodes cles
Méthode
Rôle
GetKaryotype(parent, lengths)
Decoupe un parent en sous-chromosomes
GetSubPopulations(parents, lengths)
Decoupe une population entiere, groupee par position
UpdateParent()
Resynchronise les genes du sous-chromosome vers le parent
GetNewIndividual(karyotype)
Créé un nouvel individu a partir d’un caryotype evolue
GetNewIndividuals(subPops)
Reconstruit une population complete a partir de sous-populations
// Demonstration: EukaryoteChromosome karyotype decomposition// We create a parent chromosome and slice it into 2 sub-chromosomes// Parent: 2 floating-point genes, each 16 bits with 2 decimal places, range [0, 100]// Graine fixe : valeurs du chromosome parent reproductibles d une execution a l autreGeneticSharp.FastRandomRandomization.ResetSeed(7);var parent =newFloatingPointChromosome(newdouble[]{0,0},newdouble[]{100,100},newint[]{16,16},newint[]{2,2});// Evaluate fitness so the children inherit itvar fitnessDemo =newPositionVelocityFitness();parent.Fitness= fitnessDemo.Evaluate(parent);// Get karyotype: split into 2 sub-chromosomes of 16 genes each// (each FloatingPoint gene uses 16 bits, so subChromosomeLengths = [16, 16])var karyotype = EukaryoteChromosome.GetKaryotype(parent,new List<int>{16,16});Console.WriteLine("Chromosome parent :");var parentGenes =((FloatingPointChromosome)parent).ToFloatingPoints();Console.WriteLine(string.Format(" Type : {0}", parent.GetType().Name));Console.WriteLine(string.Format(" Longueur : {0} genes", parent.Length));Console.WriteLine(string.Format(" Valeurs : [{0:F2}, {1:F2}]", parentGenes[0], parentGenes[1]));Console.WriteLine(string.Format(" Fitness : {0:F4}", parent.Fitness));Console.WriteLine();Console.WriteLine("Karyotype (2 sous-chromosomes) :");for(int i =0; i < karyotype.Count; i++){var sub =(EukaryoteChromosome)karyotype[i]; Console.WriteLine(string.Format(" Karyotype[{0}] :", i)); Console.WriteLine(string.Format(" Type : {0}", sub.GetType().Name)); Console.WriteLine(string.Format(" StartGeneIndex : {0}", sub.StartGeneIndex)); Console.WriteLine(string.Format(" Longueur : {0} genes", sub.Length)); Console.WriteLine(string.Format(" Meme reference : {0}",object.ReferenceEquals(sub.ParentIndividual, parent))); Console.WriteLine(string.Format(" Fitness herite : {0:F4}", sub.Fitness));}// Rebuild a new individual from the karyotypevar rebuilt = EukaryoteChromosome.GetNewIndividual(karyotype.Cast<EukaryoteChromosome>().ToList());var rebuiltGenes =((FloatingPointChromosome)rebuilt).ToFloatingPoints();Console.WriteLine();Console.WriteLine("Individu reconstruit :");Console.WriteLine(string.Format(" Type : {0}", rebuilt.GetType().Name));Console.WriteLine(string.Format(" Valeurs : [{0:F2}, {1:F2}]", rebuiltGenes[0], rebuiltGenes[1]));Console.WriteLine(string.Format(" Genes identiques au parent : {0}", parent.GetGenes().SequenceEqual(rebuilt.GetGenes())));
Sortie obtenue : Le chromosome parent (2 genes, 32 bits) est decoupe en deux sous-chromosomes de 16 genes chacun. Chaque sous-chromosome reference son parent via ParentIndividual et connait son decalage via StartGeneIndex.
Propriete
Karyotype[0] (Position)
Karyotype[1] (Vitesse)
StartGeneIndex
0
16
Length
16
16
ParentIndividual
Le chromosome parent
Le même chromosome parent
Genes copies
Genes [0..15] du parent
Genes [16..31] du parent
Points cles : 1. Le karyotype créé des vues sur le chromosome parent : les genes sont copies mais le parent est reference 2. UpdateParent() resynchronise les genes du sous-chromosome vers le parent – c’est le mécanisme de retour après evolution 3. GetNewIndividual(karyotype) créé un nouvel individu a partir du caryotype, en copiant les genes de chaque sous-chromosome 4. Le fitness du parent est herite par les sous-chromosomes (utilise pour la sélection dans les sous-populations)
3. Sub-Population : isolation et contexte
La sous-population (SubPopulation) est le mécanisme d’isolation de l’eukaryote. Elle encapsule une projection de la population parente (un sous-chromosome par individu) dans un objet IPopulation autonome, avec son propre contexte d’evolution.
Sélection : les parents sont selectionnes dans la population parente, puis decomposes en sous-chromosomes
Crossover : chaque sous-population applique son crossover via sa sous-métaheuristique (DefaultMetaHeuristic ou NoOpMetaHeuristic)
Mutation : chaque sous-chromosome est mute independamment selon la stratégie de sa sous-population
Reinsertion : les individus reconstruits sont reinseres dans la population parente (reinsertion globale, non scopee)
// Demonstration: SubPopulation creation and isolation// We create a population, slice it into sub-populations, and inspect the structure// Create parent population// Graine fixe : population initiale reproductible d une execution a l autreGeneticSharp.FastRandomRandomization.ResetSeed(8);var adamSub =newFloatingPointChromosome(newdouble[]{0,0},newdouble[]{100,100},newint[]{16,16},newint[]{2,2});var parentPop =newMetaPopulation(40,40, adamSub);parentPop.CreateInitialGeneration();// Assign fitness to all chromosomes (required for sub-population creation)foreach(var c in parentPop.CurrentGeneration.Chromosomes){ c.Fitness=newPositionVelocityFitness().Evaluate(c);}// Slice into 2 sub-populations of 16 genes eachvar subChromosomes = EukaryoteChromosome.GetSubPopulations( parentPop.CurrentGeneration.Chromosomes,new List<int>{16,16});Console.WriteLine("Decomposition en sous-populations :");Console.WriteLine(string.Format(" Population parente : {0} individus", parentPop.CurrentGeneration.Chromosomes.Count));Console.WriteLine(string.Format(" Nombre de sous-populations : {0}", subChromosomes.Count));for(int i =0; i < subChromosomes.Count; i++){ Console.WriteLine(); Console.WriteLine(string.Format(" Sous-population {0} :", i)); Console.WriteLine(string.Format(" Taille : {0} chromosomes", subChromosomes[i].Count)); Console.WriteLine(string.Format(" Type : {0}", subChromosomes[i][0].GetType().Name));var firstSub =(EukaryoteChromosome)subChromosomes[i][0]; Console.WriteLine(string.Format(" StartGeneIndex : {0}", firstSub.StartGeneIndex)); Console.WriteLine(string.Format(" Longueur (genes) : {0}", firstSub.Length));}// Create SubPopulation objects with isolated contextsvar subPops =new List<SubPopulation>();for(int i =0; i < subChromosomes.Count; i++){ subPops.Add(newSubPopulation(parentPop, subChromosomes[i].Cast<IChromosome>().ToList()));}Console.WriteLine();Console.WriteLine("SubPopulation isolees :");for(int i =0; i < subPops.Count; i++){ Console.WriteLine(string.Format(" SubPop[{0}].MinSize = {1}, ParentPopulation = MetaPopulation({2})", i, subPops[i].MinSize, subPops[i].ParentPopulation.MinSize));}// Rebuild complete individuals from sub-populationsvar rebuilt = EukaryoteChromosome.GetNewIndividuals( subPops.Select(sp => sp.CurrentGeneration.Chromosomes).Cast<IList<IChromosome>>().ToList());Console.WriteLine();Console.WriteLine(string.Format("Individus reconstruits : {0}", rebuilt.Count));Console.WriteLine(string.Format(" Premier individu type : {0}", rebuilt[0].GetType().Name));var rebuiltGenes =((FloatingPointChromosome)rebuilt[0]).ToFloatingPoints();Console.WriteLine(string.Format(" Genes : [{0:F2}, {1:F2}]", rebuiltGenes[0], rebuiltGenes[1]));Console.WriteLine(" (Identiques aux genes du parent -- aucune evolution n'a encore eu lieu)");
Decomposition en sous-populations :
Population parente : 40 individus
Nombre de sous-populations : 2
Sous-population 0 :
Taille : 40 chromosomes
Type : EukaryoteChromosome
StartGeneIndex : 0
Longueur (genes) : 16
Sous-population 1 :
Taille : 40 chromosomes
Type : EukaryoteChromosome
StartGeneIndex : 16
Longueur (genes) : 16
SubPopulation isolees :
SubPop[0].MinSize = 40, ParentPopulation = MetaPopulation(40)
SubPop[1].MinSize = 40, ParentPopulation = MetaPopulation(40)
Individus reconstruits : 40
Premier individu type : FloatingPointChromosome
Genes : [43,56, 89,66]
(Identiques aux genes du parent -- aucune evolution n'a encore eu lieu)
Interpretation : SubPopulation et contexte isole
Sortie obtenue : La population parente de 40 individus est decomposee en 2 sous-populations de 40 EukaryoteChromosome chacune, puis reconstruite en individus complets.
Aspect
Valeur
Signification
Population parente
40 individus
La population complete du GA
Sous-population 0
40 sous-chromosomes
Un EukaryoteChromosome par individu, contenant les genes [0..15]
Sous-population 1
40 sous-chromosomes
Un EukaryoteChromosome par individu, contenant les genes [16..31]
SubPopulationContext
Indépendant par sous-pop
Cache de paramètres isole, pas de partage avec le parent
Reconstruction
GetNewIndividuals()
Les genes des sous-chromosomes sont resynchronises dans les parents
Points cles : 1. SubPopulation herite de MetaPopulation : pas de tri implicite par fitness, l’ordre est stable 2. SubPopulationContext herite de SubEvolutionContext : il delegue les proprietes non-locales (generation, stage) au contexte parent, mais le cache de paramètres et la population sont locaux 3. Le GenerationsNumber de la sous-population est aligne sur celui de la population parente 4. Chaque sous-population peut utiliser des opérateurs différents via les PhaseHeuristics de SizeBasedMetaHeuristic
4. EukaryoteMetaHeuristic : exécution complete
Nous allons maintenant executer un GA complet avec EukaryoteMetaHeuristic sur un problème a deux dimensions :
Position (gene 0, cible = 50) : evoluee par une sous-population avec DefaultMetaHeuristic (crossover et mutation actifs)
Vitesse (gene 1, cible = 25) : evoluee par une sous-population avec NoOpMetaHeuristic (les parents passent inchanges, seul l’elitisme agit)
Le fitness est : \(f(x, v) = -(|x - 50| + |v - 25|)\) (a maximiser).
Configuration : - Chromosome : FloatingPointChromosome a 2 genes dans \([0, 100]\) - Sous-chromosomes : 2 partitions de 16 genes chacune - Sélection : EliteSelection - Crossover : UniformCrossover(0.5) (le crossover est applique dans chaque sous-population) - Mutation : UniformMutation(true) - Terminaison : 50 generations
// Run EukaryoteMetaHeuristic with 2 sub-populations on the position-velocity problem// Sub-population 0 (position): DefaultMetaHeuristic (crossover + mutation actifs)// Sub-population 1 (velocity): NoOpMetaHeuristic (parents pass through unchanged)// Uses same pattern as EukaryoteMetaHeuristicTests (EliteSelection, UniformCrossover, UniformMutation)// Graine fixe : trajectoire de la demo reproductible d une execution a l autreGeneticSharp.FastRandomRandomization.ResetSeed(9);var eukaryoteMh =BuildEukaryote();var adamEuk =newFloatingPointChromosome(newdouble[]{0,0},newdouble[]{100,100},newint[]{16,16},newint[]{2,2});var popEuk =newMetaPopulation(40,40, adamEuk);// Use FuncFitness for the GA-level fitness (same pattern as tests)var gaEuk =newMetaGeneticAlgorithm( popEuk,newFuncFitness(c =>{var values =((FloatingPointChromosome)c).ToFloatingPoints();return-(Math.Abs(values[0]-50)+ Math.Abs(values[1]-25));}),newEliteSelection(),newUniformCrossover(0.5f),newUniformMutation(true), eukaryoteMh);gaEuk.Termination=newGenerationNumberTermination(50);Console.WriteLine("Execution EukaryoteMetaHeuristic (2 sous-populations, 50 generations)...");Console.WriteLine(" Sous-pop 0 (position) : DefaultMetaHeuristic (crossover actif)");Console.WriteLine(" Sous-pop 1 (vitesse) : NoOpMetaHeuristic (parents inchanges)");Console.WriteLine(" Scope : Crossover | Mutation");Console.WriteLine();gaEuk.Start();var bestEuk =((FloatingPointChromosome)gaEuk.BestChromosome).ToFloatingPoints();var bestObjEuk = Math.Abs(bestEuk[0]-50)+ Math.Abs(bestEuk[1]-25);Console.WriteLine(string.Format(" Meilleur chromosome : position={0:F2}, vitesse={1:F2}", bestEuk[0], bestEuk[1]));Console.WriteLine(string.Format(" Cibles : position=50, vitesse=25"));Console.WriteLine(string.Format(" Distance totale : {0:F4}", bestObjEuk));Console.WriteLine(string.Format(" Generations : {0}", gaEuk.GenerationsNumber));Console.WriteLine(string.Format(" Etat : {0}", gaEuk.State));
Interpretation : Premier run avec EukaryoteMetaHeuristic
Sortie obtenue : L’EukaryoteMetaHeuristic decompose le chromosome en deux sous-chromosomes de 16 genes chacun, applique des stratégies différentes, puis reconstruit un individu complet.
Aspect
Valeur
Signification
Sous-populations
2
Caryotype : [0..15] pour la position, [16..31] pour la vitesse
Stratégie gene 0
DefaultMetaHeuristic
Crossover et mutation actifs sur la position
Stratégie gene 1
NoOpMetaHeuristic
Les parents passent inchanges, seul l’elitisme agit sur la vitesse
Scope
Crossover \| Mutation
Seuls crossover et mutation passent par les sous-populations
Reinsertion
Globale (par defaut)
L’elitisme s’applique sur les individus reconstruits
Points cles : 1. L’EukaryoteMetaHeuristic créé des SubPopulation isolees avec des SubPopulationContext independants 2. NoOpMetaHeuristic sur la vitesse signifie que cette dimension n’evolue pas par crossover/mutation : elle s’appuie uniquement sur la sélection d’elitisme pour converger 3. La reinsertion n’est pas scopee a l’eukaryote : ScopedReinsert leve une exception. Le scope canonique est Crossover | Mutation 4. Les résultats des sous-populations sont reagreges via EukaryoteChromosome.GetNewIndividuals() qui reconstruit des individus complets
Demonstration : Eukaryote heterogene vs uniforme
Nous comparons deux configurations d’EukaryoteMetaHeuristic : - Heterogene : DefaultMetaHeuristic (position) + NoOpMetaHeuristic (vitesse) – seule la position evolue par crossover - Uniforme : DefaultMetaHeuristic + DefaultMetaHeuristic – les deux dimensions evoluent activement
Le problème position-vitesse a deux dimensions avec des cibles différentes : la position (gene 0) cible 50 et la vitesse (gene 1) cible 25.
// Compare: EukaryoteMetaHeuristic (heterogeneous: Default + NoOp) vs uniform (Default + Default)// We use 5 runs to smooth out randomnessConsole.WriteLine("Comparaison Eukaryote heterogene vs uniforme (5 runs, 50 generations)");Console.WriteLine("=================================================================");Console.WriteLine(string.Format("{0,-25} {1,-12} {2,-12} {3,-12} {4,-12} {5,-12}","Config","Run1","Run2","Run3","Run4","Run5"));Console.WriteLine("-----------------------------------------------------------------");var eukResults =new List<double>();var uniResults =new List<double>();// Graines appairees : au run k, les deux configurations repartent du meme flux aleatoire// (seed 42+k), et ce flux est reproductible d'une execution a l'autre. FastRandomRandomization// est le provider du moteur ; BasicRandomization.ResetSeed n'affecterait pas le GA.for(int run =0; run <5; run++){ GeneticSharp.FastRandomRandomization.ResetSeed(42+ run); eukResults.Add(RunWithEukaryote(BuildEukaryote(),"Eukaryote")); GeneticSharp.FastRandomRandomization.ResetSeed(42+ run); uniResults.Add(RunWithEukaryote(BuildEukaryoteUniform(),"Uniform"));}Console.Write(string.Format("{0,-25}","Uniform (Default+Default)"));foreach(var r in uniResults) Console.Write(string.Format(" {0,-11:F4}", r));Console.WriteLine();Console.Write(string.Format("{0,-25}","Heterogen (Default+NoOp)"));foreach(var r in eukResults) Console.Write(string.Format(" {0,-11:F4}", r));Console.WriteLine();Console.WriteLine("-----------------------------------------------------------------");Console.WriteLine(string.Format(" Uniforme moyenne : {0:F4}", uniResults.Average()));Console.WriteLine(string.Format(" Heterogene moyenne: {0:F4}", eukResults.Average()));Console.WriteLine(string.Format(" Amelioration : {0:F1}%",(uniResults.Average()- eukResults.Average())/ uniResults.Average()*100));Console.WriteLine("=================================================================");Console.WriteLine("Position-Velocity fitness : f(x) = -(|x - 50| + |v - 25|)");Console.WriteLine(" Heterogene: gene 0 (position) DefaultMetaHeuristic, gene 1 (vitesse) NoOpMetaHeuristic");
Sortie obtenue : L’EukaryoteMetaHeuristic uniforme (Default+Default) converge mieux que la version heterogene (Default+NoOp).
Configuration
Sous-pop 0 (position)
Sous-pop 1 (vitesse)
Résultat
Uniforme (Default+Default)
Crossover actif
Crossover actif
Meilleure convergence globale
Heterogene (Default+NoOp)
Crossover actif
Parents inchanges
La vitesse converge moins bien
Pourquoi l’uniforme est meilleur ici : la version heterogene utilise NoOpMetaHeuristic sur la vitesse, ce qui signifie que cette dimension ne beneficie ni de crossover ni de mutation – seul l’elitisme la fait evoluer. Sur un problème simple ou les deux dimensions ont le même comportement (fonction de distance), l’uniforme est naturellement avantagé.
Quand l’heterogene est pertinente : 1. L’eukaryote montre tout son potentiel sur des problemes ou les dimensions ont des caractéristiques très différentes (ex: une dimension avec un paysage rugueux qui beneficie d’une exploration forte, et une dimension lisse qui profite de l’exploitation) 2. Le Scope = EvolutionStage.Crossover | EvolutionStage.Mutation assure que seuls le crossover et la mutation passent par les sous-populations ; la reinsertion reste globale 3. Les sous-populations sont recalculees a chaque generation (cache ParamScope.Generation) pour refleter les changements de la population parente
Mesure : la configuration Default+NoOp peut-elle gagner ?
La cellule precedente montre l’heterogene (Default+NoOp) perdante d’un facteur ~14 en moyenne sur le cone lisse (moyennes 1,8260 vs 0,1320, soit une Amelioration de -1283,3% – graines appairees 42-46, reproductibles d’une execution a l’autre). La prose ci-dessus suggerait que l’heterogene devient pertinente “quand les dimensions ont des caracteristiques tres differentes”. Verifions cette affirmation empiriquement : construisons un paysage ou la position est rugueuse (Rastrigin1D, nombreux optima locaux) et la vitesse est un pic etroit (Gaussienne centree en 25), puis comparons Default+NoOp vs Default+Default en balayant la largeur du pic. Si l’heterogene a un avantage structurel, il devrait apparaitre sur au moins une de ces largeurs.
// Mesure empirique : Default+NoOp (heterogene) vs Default+Default (uniforme)// sur un paysage position-rugueuse + vitesse-pic-etroit, en balayant sigma (largeur du pic).// Reutilise BuildEukaryote() (Default+NoOp) et BuildEukaryoteUniform() (Default+Default)// definis dans la cellule de setup.doubleRastrigin1D(double x)=>10.0+(x -50)*(x -50)-10.0* Math.Cos(2* Math.PI*(x -50));doublePeakMiss(double v,double sigma)=>1.0- Math.Exp(-((v -25)/ sigma)*((v -25)/ sigma));// Objectif combine (lower = better) : Rastrigin/100 (position) + PeakMiss*10 (vitesse)doubleRunObjective(IMetaHeuristic mh,double sigma){var adam =newFloatingPointChromosome(newdouble[]{0,0},newdouble[]{100,100},newint[]{16,16},newint[]{2,2});var pop =newMetaPopulation(40,40, adam);var ga =newMetaGeneticAlgorithm(pop,newFuncFitness(c =>{var g =((FloatingPointChromosome)c).ToFloatingPoints();return-(Rastrigin1D(g[0])/100.0+PeakMiss(g[1], sigma)*10.0);}),newEliteSelection(),newUniformCrossover(0.5f),newUniformMutation(true), mh); ga.Termination=newGenerationNumberTermination(50); ga.Start();var b =((FloatingPointChromosome)ga.BestChromosome).ToFloatingPoints();returnRastrigin1D(b[0])/100.0+PeakMiss(b[1], sigma)*10.0;}Console.WriteLine("Paysage : position=Rastrigin1D (rugueux), vitesse=pic Gaussien etroit en 25");Console.WriteLine("Objectif combine (lower = better). Moyenne sur 3 runs, 50 generations.");Console.WriteLine(newstring('-',52));Console.WriteLine(string.Format("{0,-8} {1,-14} {2,-14} {3,-12}","sigma","Uniform","Hetero","Gagnant"));Console.WriteLine(newstring('-',52));foreach(var sigma innewdouble[]{0.5,1.0,2.0,5.0}){double u =0, h =0;// Graines appairees : les 3 runs d'une ligne sigma partagent leurs flux entre les deux// configurations (seed 100+i), reproductibles d'une execution a l'autre.for(int i =0; i <3; i++){ GeneticSharp.FastRandomRandomization.ResetSeed(100+ i); u +=RunObjective(BuildEukaryoteUniform(), sigma)/3.0; GeneticSharp.FastRandomRandomization.ResetSeed(100+ i); h +=RunObjective(BuildEukaryote(), sigma)/3.0;} Console.WriteLine(string.Format("{0,-8} {1,-14:F3} {2,-14:F3} {3,-12}", sigma, u, h, u < h ?"Uniform":"Hetero"));}Console.WriteLine(newstring('-',52));
Interpretation : NoOp est un handicap structurel, pas une specialisation
Resultat : sur chaque largeur de pic (sigma 0.5 a 5), l’uniforme (Default+Default) bat l’heterogene (Default+NoOp). La vitesse figee par NoOpMetaHeuristic ne peut pas atteindre un pic etroit que la vitesse evoluee (DefaultMetaHeuristic) rejoint systematiquement : l’elitisme combine au crossover fait converger Default vers la cible meme sous mutation, tandis que NoOp fige la vitesse a sa valeur initiale et ne peut l’ameliorer.
Conclusion mesuree : le couplage Default+NoOp utilise dans ce notebook est structurellement desavantage sur toute dimension a cible unique. Il ne peut donc PAS illustrer la victoire heterogene decrite dans la prose ci-dessus. L’ecart de -1283,3% observe sur le cone lisse n’est pas un defaut du moteur eukaryote : c’est la consequence attendue d’avoir assigne NoOpMetaHeuristic (une non-strategie) a une dimension qui aurait besoin d’evoluer.
Quand l’heterogene gagne vraiment : il faut une specialisation reelle des sous-heuristiques – une exploration forte (mutation amplifiee) sur une partition au paysage rugueux, et une exploitation fine (faible mutation) sur une partition au paysage lisse. Ce genre de specialisation par partition est precisement l’objet de l’Exercice 3 (implementer un SubPopulationMetaHeuristic personnalise). Le demo ci-dessus, lui, isole proprement l’effet de chaque sous-heuristique par partition – capacite distinctive de l’eukaryote – et montre que NoOp est un point de comparaison (le pire cas), pas une strategie gagnante.
5. Resume
Ce notebook a introduit le concept eucaryote de MetaGeneticSharp : des individus multi-chromosomes dont chaque partie est evoluee independamment par des sous-populations specialisees.
Concept
Rôle
Point cle
EukaryoteChromosome
Decoupe un chromosome parent en sous-chromosomes (caryotype)
GetKaryotype() decoupe, UpdateParent() resync
SubPopulation
Population isolee pour une partition du genome
Herite de MetaPopulation, contextes separe
SubPopulationContext
Contexte d’evolution local a une sous-population
Cache de paramètres indépendant
EukaryoteMetaHeuristic
Orchestrateur : applique une sous-heuristique par partition
Scope canonique : Crossover \| Mutation
PerformSubOperator
Recombine les résultats de chaque sous-population
Reconstruit des individus complets
Principes de conception
Partition du genome : chaque sous-chromosome represente une dimension sémantique différente du problème
Stratégies heterogenes : chaque sous-population peut utiliser ses propres paramètres (crossover, mutation, match)
Isolation + recombinaison : les sous-populations evoluent independamment, puis les résultats sont reagreges en individus complets
Scope restreint : la reinsertion n’est pas supportee par l’eukaryote – elle tombe sur la sous-métaheuristique par defaut
Exercice 1 : Créer un chromosome eucaryote a 3 parties
L’objectif est d’etendre le concept eucaryote a trois sous-chromosomes : position, vitesse et acceleration, chacun avec une stratégie d’evolution différente.
Enonce : Definissez un FloatingPointChromosome a 3 genes, un fitness qui minimise la distance a des cibles différentes pour chaque variable, et un EukaryoteMetaHeuristic avec 3 sous-populations.
Indices : - Le chromosome Adam a 3 genes, chacun dans \([0, 100]\), avec 16 bits et 2 decimales par gene - EukaryoteMetaHeuristic(16, mh1, mh2, mh3) créé 3 sous-populations de 16 genes chacune - Le fitness peut etre : \(f(x, v, a) = -(|x - 50| + |v - 25| + |a - 10|)\) (cibles différentes par variable) - Chaque sous-métaheuristique peut avoir des probabilites différentes (exploration forte pour la position, moderee pour la vitesse, exploitation pour l’acceleration)
// Exercice 1 : Creer un chromosome eucaryote a 3 parties// TODO: Definir un chromosome a 3 genes (position, vitesse, acceleration)// TODO: Creer un EukaryoteMetaHeuristic avec 3 sous-populations de tailles egales// TODO: Attribuer des strategies differentes a chaque sous-population// Indice: EukaryoteMetaHeuristic(subChromosomeSize, mh1, mh2, mh3) avec subChromosomeSize = geneCount / 3// Indice: Pour un FloatingPointChromosome a 3 genes, chaque gene = 16 bits -> subChromosomeSize = 16// Indice: Adaptez le fitness pour 3 variables : f(x, v, a) = -(|x-50| + |v-25| + |a-10|)// Etape 1 : Definir le chromosome Adam (3 genes dans [0, 100])// var adam = new FloatingPointChromosome(// new double[] { 0, 0, 0 },// new double[] { 100, 100, 100 },// new int[] { 16, 16, 16 },// new int[] { 2, 2, 2 });// Etape 2 : Definir le fitness// public class ThreePartFitness : IFitness { ... }// Etape 3 : Configurer les 3 sous-metaheuristiques// var mh1 = new DefaultMetaHeuristic(); // position : exploration// var mh2 = ... ; // vitesse : equilibrage// var mh3 = ... ; // acceleration : exploitation// Etape 4 : Creer et executer l'EukaryoteMetaHeuristic// var eukaryote = new EukaryoteMetaHeuristic(16, mh1, mh2, mh3)// {// Scope = EvolutionStage.Crossover | EvolutionStage.Mutation// };object result =null;// TODO etudiant : lancer le GA et afficher le resultatConsole.WriteLine("Exercice a completer : chromosome eucaryote a 3 parties");
Exercice a completer : chromosome eucaryote a 3 parties
Exercice 2 : Comparer Eukaryote avec 2 sous-pops vs 4 sous-pops
L’objectif est de comparer l’impact du nombre de sous-populations sur la convergence. Avec plus de sous-populations, chaque partition est plus petite et peut utiliser une stratégie plus specialisee, mais la complexite de coordination augmente.
Enonce : Configurez un chromosome a 32 genes et comparez : - Configuration 2 sous-pops : deux partitions de 16 genes chacune - Configuration 4 sous-pops : quatre partitions de 8 genes chacune
Indices : - Le chromosome Adam doit avoir 32 genes pour les deux tests - Pour 2 sous-pops : EukaryoteMetaHeuristic(16, mh1, mh2) – deux partitions egales - Pour 4 sous-pops : EukaryoteMetaHeuristic(8, mh1, mh2, mh3, mh4) – quatre partitions egales - Adaptez le fitness pour un chromosome a 4 variables (ex: \(f(x) = -\sum |x_i - c_i|\) pour des cibles différentes) - Observez si plus de sous-populations ameliore ou degrade la convergence
// Exercice 2 : Comparer Eukaryote avec 2 sous-pops vs 4 sous-pops// TODO: Creez un chromosome a 4 genes (32 bits par gene, 4 x 8 bits par sous-pop en mode 4)// TODO: Comparez la convergence de 2 sous-populations vs 4 sous-populations// Indice: EukaryoteMetaHeuristic(16, mh1, mh2) pour 2 sous-pops (16+16 genes)// Indice: EukaryoteMetaHeuristic(8, mh1, mh2, mh3, mh4) pour 4 sous-pops (8+8+8+8 genes)// Indice: Le chromosome Adam doit avoir 32 genes pour les deux cas// Etape 1 : Definir le fitness (reutilisez PositionVelocityFitness ou un nouveau)// var fitness = new PositionVelocityFitness();// Etape 2 : Configurer 2 sous-populations// var mh_aggressive = new DefaultMetaHeuristic(); ...// var mh_conservative = new DefaultMetaHeuristic(); ...// var eukaryote2 = new EukaryoteMetaHeuristic(16, mh_aggressive, mh_conservative) { ... };// Etape 3 : Configurer 4 sous-populations// var eukaryote4 = new EukaryoteMetaHeuristic(8, mh1, mh2, mh3, mh4) { ... };// Etape 4 : Comparer les deux configurations sur plusieurs runsobject result =null;// TODO etudiant : lancer les comparaisonsConsole.WriteLine("Exercice a completer : 2 sous-pops vs 4 sous-pops");
Exercice a completer : 2 sous-pops vs 4 sous-pops
Exercice 3 : Implementer un SubPopulationMetaHeuristic personnalise avec des Match différents
L’objectif est de créer une métaheuristique qui combine l’approche eucaryote avec des stratégies de match différentes pour chaque sous-population : un match Random pour la première sous-population (exploration de la position) et un match Best pour la seconde (exploitation de la vitesse).
Enonce : Composez un EukaryoteMetaHeuristic avec deux sous-métaheuristiques utilisant chacune un MatchMetaHeuristic différent.
Indices : - MatchMetaHeuristic peut etre utilise comme sous-métaheuristique dans EukaryoteMetaHeuristic - Construisez un MatchMetaHeuristic avec Current + Random pour le gene 0 et Current + Best pour le gene 1 - EukaryoteMetaHeuristic(16, matchRandom, matchBest) créé deux sous-populations de 16 genes chacune - Comparez avec l’Eukaryote standard (DefaultMetaHeuristic pour les deux sous-populations)
// Exercice 3 : Implementer un SubPopulationMetaHeuristic personnalise// TODO: Creez une classe qui utilise des strategies Match differentes par sous-population// Indice: Heritez d'une classe existante ou composez avec MatchMetaHeuristic// Indice: L'idee est d'avoir un match Random sur le premier gene et Best sur le second// Etape 1 : Definir la classe personnalisee// public class CustomMatchEukaryote : ... { ... }// Etape 2 : L'utiliser avec EukaryoteMetaHeuristic// var mh = new CustomMatchEukaryote(...);// Etape 3 : Executer et comparer avec l'Eukaryote standard// var result = RunWithEukaryote(mh, "CustomMatch", generations: 50);object result =null;// TODO etudiant : implementer et testerConsole.WriteLine("Exercice a completer : SubPopulationMetaHeuristic personnalise");
Exercice a completer : SubPopulationMetaHeuristic personnalise