MGS-3 : L’Eukaryote – sous-populations et chromosomes composites

Navigation : Index | << MGS-2 Composition | MGS-4 Islands >>

Objectifs d’apprentissage

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. Utiliser EukaryoteChromosome 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.

Concept biologique Equivalent MetaGeneticSharp
Cellule eucaryote Individu avec genome composite
Chromosome EukaryoteChromosome (partition du genome)
Caryotype (ensemble des chromosomes) Liste des sous-chromosomes
Tissu specialise SubPopulation avec sa stratégie
Organisme complet Individu reconstruit via GetNewIndividuals()
// Wiring: load MetaGeneticSharp + GeneticSharp DLLs from submodule build
// Build prerequisite: dotnet build ../MetaGeneticSharp/MetaGeneticSharp.sln
// Requires: git -C ../MetaGeneticSharp submodule update --init GeneticSharp
#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"

using MetaGeneticSharp;
using GeneticSharp;

Console.WriteLine("MetaGeneticSharp + GeneticSharp charges avec succes.");
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($"  EvolutionStage.Crossover   : {EvolutionStage.Crossover}");
Console.WriteLine($"  EvolutionStage.Mutation    : {EvolutionStage.Mutation}");
MetaGeneticSharp + GeneticSharp charges avec succes.
  EukaryoteChromosome       : EukaryoteChromosome
  SubPopulation              : SubPopulation
  SubPopulationContext       : SubPopulationContext
  EukaryoteMetaHeuristic     : EukaryoteMetaHeuristic
  EvolutionStage.Crossover   : Crossover
  EvolutionStage.Mutation    : Mutation

Chargement des DLL et configuration du notebook

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 negate
public class PositionVelocityFitness : IFitness
{
    public double Evaluate(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 = new DefaultMetaHeuristic();

    // Sub-pop 1: NoOpMetaHeuristic (crossover and mutation are no-ops)
    var noOp = new NoOpMetaHeuristic();

    return new EukaryoteMetaHeuristic(16, active, noOp)
    {
        Scope = EvolutionStage.Crossover | EvolutionStage.Mutation
    };
}

// Helper: build an EukaryoteMetaHeuristic with two DefaultMetaHeuristic (same strategy)
IMetaHeuristic BuildEukaryoteUniform()
{
    return new EukaryoteMetaHeuristic(16, new DefaultMetaHeuristic(), new DefaultMetaHeuristic())
    {
        Scope = EvolutionStage.Crossover | EvolutionStage.Mutation
    };
}

// Helper: run GA with any IMetaHeuristic on the position-velocity problem
// Uses FuncFitness + EliteSelection (canonical pattern from EukaryoteMetaHeuristicTests)
double RunWithEukaryote(IMetaHeuristic mh, string label, int popSize = 40, int generations = 50)
{
    var adam = new FloatingPointChromosome(
        new double[] { 0, 0 },
        new double[] { 100, 100 },
        new int[] { 16, 16 },
        new int[] { 2, 2 });

    var pop = new MetaPopulation(popSize, popSize, adam);
    var ga = new MetaGeneticAlgorithm(
        pop,
        new FuncFitness(c =>
        {
            var values = ((FloatingPointChromosome)c).ToFloatingPoints();
            return -(Math.Abs(values[0] - 50) + Math.Abs(values[1] - 25));
        }),
        new EliteSelection(),
        new UniformCrossover(0.5f),
        new UniformMutation(true),
        mh);

    ga.Termination = new GenerationNumberTermination(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);
Setup OK : PositionVelocityFitness, BuildEukaryote, RunWithEukaryote definis
  EukaryoteChromosome       : EukaryoteChromosome
  SubPopulation              : SubPopulation
  SubPopulationContext       : SubPopulationContext
  EukaryoteMetaHeuristic     : EukaryoteMetaHeuristic
  SubPopulationMetaHeuristicBase : SubPopulationMetaHeuristicBase`1

2. EukaryoteChromosome : le chromosome composite

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 autre
GeneticSharp.FastRandomRandomization.ResetSeed(7);
var parent = new FloatingPointChromosome(
    new double[] { 0, 0 },
    new double[] { 100, 100 },
    new int[] { 16, 16 },
    new int[] { 2, 2 });

// Evaluate fitness so the children inherit it
var fitnessDemo = new PositionVelocityFitness();
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 karyotype
var 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())));
Chromosome parent :
  Type          : FloatingPointChromosome
  Longueur      : 32 genes
  Valeurs       : [12,73, 23,87]
  Fitness       : -38,4000

Karyotype (2 sous-chromosomes) :
  Karyotype[0] :
    Type            : EukaryoteChromosome
    StartGeneIndex  : 0
    Longueur        : 16 genes
    Meme reference  : True
    Fitness herite  : -38,4000
  Karyotype[1] :
    Type            : EukaryoteChromosome
    StartGeneIndex  : 16
    Longueur        : 16 genes
    Meme reference  : True
    Fitness herite  : -38,4000

Individu reconstruit :
  Type    : FloatingPointChromosome
  Valeurs : [12,73, 23,87]
  Genes identiques au parent : True

Interpretation : Karyotype et sous-chromosomes

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.

Architecture de l’isolation

Population parente (40 individus x 32 genes)
  |
  +-- GetSubPopulations([16, 16])
  |     |
  |     +-- SubPopulation 0 : 40 EukaryoteChromosome (genes [0..15])
  |     |     Contexte isole (SubPopulationContext)
  |     |     Stratégie : DefaultMetaHeuristic (crossover + mutation actifs)
  |     |
  |     +-- SubPopulation 1 : 40 EukaryoteChromosome (genes [16..31])
  |           Contexte isole (SubPopulationContext)
  |           Stratégie : NoOpMetaHeuristic (parents inchanges)
  |
  +-- PerformSubOperator() : applique chaque sous-heuristique a sa sous-population
  |
  +-- GetNewIndividuals() : reconstruit les individus complets

Cycle de vie d’une generation eucaryote

  1. Sélection : les parents sont selectionnes dans la population parente, puis decomposes en sous-chromosomes
  2. Crossover : chaque sous-population applique son crossover via sa sous-métaheuristique (DefaultMetaHeuristic ou NoOpMetaHeuristic)
  3. Mutation : chaque sous-chromosome est mute independamment selon la stratégie de sa sous-population
  4. 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 autre
GeneticSharp.FastRandomRandomization.ResetSeed(8);
var adamSub = new FloatingPointChromosome(
    new double[] { 0, 0 },
    new double[] { 100, 100 },
    new int[] { 16, 16 },
    new int[] { 2, 2 });

var parentPop = new MetaPopulation(40, 40, adamSub);
parentPop.CreateInitialGeneration();
// Assign fitness to all chromosomes (required for sub-population creation)
foreach (var c in parentPop.CurrentGeneration.Chromosomes)
{
    c.Fitness = new PositionVelocityFitness().Evaluate(c);
}

// Slice into 2 sub-populations of 16 genes each
var 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 contexts
var subPops = new List<SubPopulation>();
for (int i = 0; i < subChromosomes.Count; i++)
{
    subPops.Add(new SubPopulation(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-populations
var 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 autre
GeneticSharp.FastRandomRandomization.ResetSeed(9);
var eukaryoteMh = BuildEukaryote();

var adamEuk = new FloatingPointChromosome(
    new double[] { 0, 0 },
    new double[] { 100, 100 },
    new int[] { 16, 16 },
    new int[] { 2, 2 });

var popEuk = new MetaPopulation(40, 40, adamEuk);

// Use FuncFitness for the GA-level fitness (same pattern as tests)
var gaEuk = new MetaGeneticAlgorithm(
    popEuk,
    new FuncFitness(c =>
    {
        var values = ((FloatingPointChromosome)c).ToFloatingPoints();
        return -(Math.Abs(values[0] - 50) + Math.Abs(values[1] - 25));
    }),
    new EliteSelection(),
    new UniformCrossover(0.5f),
    new UniformMutation(true),
    eukaryoteMh);

gaEuk.Termination = new GenerationNumberTermination(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));
Execution EukaryoteMetaHeuristic (2 sous-populations, 50 generations)...
  Sous-pop 0 (position) : DefaultMetaHeuristic (crossover actif)
  Sous-pop 1 (vitesse)  : NoOpMetaHeuristic (parents inchanges)
  Scope : Crossover | Mutation

  Meilleur chromosome : position=50,00, vitesse=33,52
  Cibles              : position=50, vitesse=25
  Distance totale     : 8,5200
  Generations         : 50
  Etat                : TerminationReached

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 randomness

Console.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");
Comparaison Eukaryote heterogene vs uniforme (5 runs, 50 generations)
=================================================================
Config                    Run1         Run2         Run3         Run4         Run5        
-----------------------------------------------------------------
Uniform (Default+Default) 0,0000      0,0500      0,0100      0,0000      0,6000     
Heterogen (Default+NoOp)  1,9500      0,6600      4,1900      1,2900      1,0400     
-----------------------------------------------------------------
  Uniforme moyenne  : 0,1320
  Heterogene moyenne: 1,8260
  Amelioration      : -1283,3%
=================================================================
Position-Velocity fitness : f(x) = -(|x - 50| + |v - 25|)
  Heterogene: gene 0 (position) DefaultMetaHeuristic, gene 1 (vitesse) NoOpMetaHeuristic

Interpretation : Eukaryote heterogene vs uniforme

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.
double Rastrigin1D(double x) => 10.0 + (x - 50) * (x - 50) - 10.0 * Math.Cos(2 * Math.PI * (x - 50));
double PeakMiss(double v, double sigma) => 1.0 - Math.Exp(-((v - 25) / sigma) * ((v - 25) / sigma));

// Objectif combine (lower = better) : Rastrigin/100 (position) + PeakMiss*10 (vitesse)
double RunObjective(IMetaHeuristic mh, double sigma)
{
    var adam = new FloatingPointChromosome(new double[] { 0, 0 }, new double[] { 100, 100 }, new int[] { 16, 16 }, new int[] { 2, 2 });
    var pop = new MetaPopulation(40, 40, adam);
    var ga = new MetaGeneticAlgorithm(pop,
        new FuncFitness(c =>
        {
            var g = ((FloatingPointChromosome)c).ToFloatingPoints();
            return -(Rastrigin1D(g[0]) / 100.0 + PeakMiss(g[1], sigma) * 10.0);
        }),
        new EliteSelection(), new UniformCrossover(0.5f), new UniformMutation(true), mh);
    ga.Termination = new GenerationNumberTermination(50);
    ga.Start();
    var b = ((FloatingPointChromosome)ga.BestChromosome).ToFloatingPoints();
    return Rastrigin1D(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(new string('-', 52));
Console.WriteLine(string.Format("{0,-8} {1,-14} {2,-14} {3,-12}", "sigma", "Uniform", "Hetero", "Gagnant"));
Console.WriteLine(new string('-', 52));
foreach (var sigma in new double[] { 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(new string('-', 52));
Paysage : position=Rastrigin1D (rugueux), vitesse=pic Gaussien etroit en 25
Objectif combine (lower = better). Moyenne sur 3 runs, 50 generations.
----------------------------------------------------
sigma    Uniform        Hetero         Gagnant     
----------------------------------------------------
0,5      5,104          10,000         Uniform     
1        0,007          10,000         Uniform     
2        0,030          7,829          Uniform     
5        0,000          3,324          Uniform     
----------------------------------------------------

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

  1. Partition du genome : chaque sous-chromosome represente une dimension sémantique différente du problème
  2. Stratégies heterogenes : chaque sous-population peut utiliser ses propres paramètres (crossover, mutation, match)
  3. Isolation + recombinaison : les sous-populations evoluent independamment, puis les résultats sont reagreges en individus complets
  4. Scope restreint : la reinsertion n’est pas supportee par l’eukaryote – elle tombe sur la sous-métaheuristique par defaut

Pour aller plus loin

  • Notebook suivant : MGS-4 Islands – populations structurees (iles, migration)
  • Code source : https://github.com/jsboige/MetaGeneticSharp

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 resultat
Console.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 runs

object result = null; // TODO etudiant : lancer les comparaisons
Console.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 tester
Console.WriteLine("Exercice a completer : SubPopulationMetaHeuristic personnalise");
Exercice a completer : SubPopulationMetaHeuristic personnalise

Navigation : Index | << MGS-2 Composition | MGS-4 Islands >>

Retour au sommet