You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于p5.js的图形化遗传算法繁殖功能故障排查求助

Troubleshooting Your p5.js Genetic Algorithm Breeding Function

Hey David, let's work through the breeding issues in your genetic algorithm—there are several key mistakes in the breed() method and related code that are throwing off your evolution logic. Let's break down each problem and fix them step by step:

1. Critical Scope & Chromosome Crossbreeding Issues

  • Missing Gene Length Property: In your Population constructor, you accept g (gene length) but don't store it as an instance property. This means when you use g inside the breed() method, it's undefined (since g is only in the constructor's local scope). Fix this by adding this.geneLength = g; to the Population constructor.
  • Broken Probability Group Integrity: Your sheep's chromosome is structured in groups of 4 genes that sum to 1 (for hungry/eaten movement states). Your current crossbreeding logic splits chromosomes at a random midpoint, which can cut these groups in half, breaking the probability sum constraint. Instead, cross at group boundaries (every 4 genes) to keep each movement state's probabilities valid.

2. Invalid Mutation Logic

Your current mutation sets a gene to Math.floor(Math.random()*10+1)—this creates values between 1 and 10, which completely breaks your probability-based movement logic (your genes should be between 0 and 1, and each group of 4 must sum to 1). Instead, when mutating, we need to adjust a gene slightly while preserving the sum of its group.

3. Typo & Elite Retention Flaws

  • Wrong Constructor: You're creating new offspring with new Eater(g, newchromosome) but your class is named Sheep—this is a typo that will throw errors.
  • Elite Handling: Your current code mutates the elite sheep first, then copies its chromosomes to a random offspring. A better approach is to clone the elite's chromosomes, apply a low-probability gentle mutation (if desired), then replace a random offspring with this modified elite.

4. Simplified Population Replacement

Instead of looping to copy each sheep from newsheep to this.sheep, you can directly assign this.sheep = newsheep; for cleaner, more efficient code.


Fixed Full Code

Here's the revised code with all these issues addressed, plus a few quality-of-life improvements:

var settings = {
 populationSize : 25,
 geneLength : 8,
 mutationProbability : 0.01,
 forestSize : 1500,
 rows : 124,
 cols : 249,
 year : 250,
 end : 20,
};

function onCanvas(position){
 return position*4+2;
}

function randombetween(min, max){
 return Math.random()*(max - min) + min; // Fixed: now properly respects min/max range
}

//set up sheep
var population = new Population(settings.populationSize, settings.geneLength);

function Sheep(g, dna){
 this.genLen = g;
 this.state = 0;
 this.fitness=0;
 this.xpos = Math.floor(Math.random()*settings.cols);
 this.ypos = Math.floor(Math.random()*settings.rows);
 this.chromosome = new Array(this.genLen);
 if (dna != null){
 this.chromosome = [...dna]; // Create a copy to avoid reference sharing
 } else{
 for(var x=0; x<this.genLen; x+=4){
 this.chromosome[x] = Math.random();
 this.chromosome[x+1] = randombetween(0, 1-this.chromosome[x]);
 this.chromosome[x+2] = randombetween(0, 1-this.chromosome[x]-this.chromosome[x+1]);
 this.chromosome[x+3] = 1-this.chromosome[x]-this.chromosome[x+1]-this.chromosome[x+2];
 }
 }
}

function Population(p, g){
 this.popSize = p;
 this.geneLength = g; // Store gene length as instance property
 this.sheep = [];
 this.matingPool = [];
 this.maxFit;
 this.maxFitIndex;

 for (var x = 0; x < this.popSize; x++) {
 this.sheep[x] = new Sheep(g, null);
 }

 this.evaluate = function() {
 //find maximum fitness in generation
 this.maxFit = 0;
 this.maxFitIndex = 0;
 for (var x = 0; x < this.popSize; x++) {
 if (this.sheep[x].fitness > this.maxFit){
 this.maxFitIndex = x;
 this.maxFit = this.sheep[x].fitness;
 }
 }
 //normalize fitness
 for (var i = 0; i < this.popSize; i++) {
 this.sheep[i].fitness /= this.maxFit;
 }
 //reset mating pool every generation
 this.matingPool = [];
 //higher fitness means more representation in the pool (increased multiplier for better selection)
 for (var i = 0; i < this.popSize; i++) {
 var n = this.sheep[i].fitness * 100;
 for (var j = 0; j < n; j++) {
 this.matingPool.push(this.sheep[i]);
 }
 }
 }

 //create children sheep
 this.breed = function (){
 var newsheep = [];
 for (var i = 0; i < this.popSize; i++){
 //pick random parents from the mating pool
 let parentA = this.matingPool[Math.floor(Math.random()*this.matingPool.length)];
 let parentB = this.matingPool[Math.floor(Math.random()*this.matingPool.length)];
 //parent genes are crossed at group boundaries (preserve probability sums)
 var newchromosome = [];
 // Choose to cross after the first movement state group or keep it whole
 var crossGroup = Math.random() > 0.5 ? 4 : 0;
 for (var j = 0; j < this.geneLength; j++){
 newchromosome[j] = j < crossGroup ? parentA.chromosome[j] : parentB.chromosome[j];
 }
 //offspring may be mutated (preserving probability group sums)
 if(Math.random() <= settings.mutationProbability){
 // Pick a random group of 4 genes
 var groupStart = Math.floor(Math.random() * (this.geneLength / 4)) * 4;
 // Mutate one gene slightly
 var mutateIndex = groupStart + Math.floor(Math.random()*4);
 var mutationAmount = randombetween(-0.1, 0.1);
 newchromosome[mutateIndex] += mutationAmount;
 // Clamp to valid 0-1 range
 newchromosome[mutateIndex] = Math.max(0, Math.min(1, newchromosome[mutateIndex]));
 // Adjust last gene in group to keep sum at 1
 var sum = newchromosome[groupStart] + newchromosome[groupStart+1] + newchromosome[groupStart+2];
 newchromosome[groupStart+3] = 1 - sum;
 }
 newsheep[i] = new Sheep(this.geneLength, newchromosome); // Fixed typo: Eater → Sheep
 }
 //elite offspring survive into next generation, replacing a random offspring
 var randomIndex = Math.floor(Math.random()*this.popSize);
 // Clone elite's chromosome to avoid reference issues
 var eliteChromosome = [...this.sheep[this.maxFitIndex].chromosome];
 // Optional: low-probability gentle mutation for elite
 if(Math.random() <= settings.mutationProbability * 0.5){
 var groupStart = Math.floor(Math.random() * (this.geneLength / 4)) * 4;
 var mutateIndex = groupStart + Math.floor(Math.random()*4);
 var mutationAmount = randombetween(-0.05, 0.05);
 eliteChromosome[mutateIndex] += mutationAmount;
 eliteChromosome[mutateIndex] = Math.max(0, Math.min(1, eliteChromosome[mutateIndex]));
 var sum = eliteChromosome[groupStart] + eliteChromosome[groupStart+1] + eliteChromosome[groupStart+2];
 eliteChromosome[groupStart+3] = 1 - sum;
 }
 newsheep[randomIndex] = new Sheep(this.geneLength, eliteChromosome);
 //update array of sheep
 this.sheep = newsheep; // Simplified assignment
 }
}

//set up trees
var forest = new Forest(settings.forestSize);

function radialTreePopulation(x,y,r,count){
 let trees = [];
 for(let i = 0;i < count; i++){
 trees.push({
 posx : (x + Math.floor((Math.random()* r) * (Math.random() < 0.5 ? -1 : 1))),
 posy : (y + Math.floor((Math.random()* r) * (Math.random() < 0.5 ? -1 : 1)))
 });
 }
 return trees;
}

function Forest(f){
 this.forSize = f/ 75;
 this.trees = [];
 for (var x = 0; x < this.forSize ; x++) {
 this.trees.push(...radialTreePopulation(
 (Math.floor(Math.random()*(settings.cols-20)+10))| 0,
 (Math.floor(Math.random()*(settings.rows-20)+10))| 0,
 11,
 75)
 );
 }
}

//evaluate how to move
function moveHungry(x, move){
 if(move < population.sheep[x].chromosome[0]){
 return 0;
 } else if(move - population.sheep[x].chromosome[0] < population.sheep[x].chromosome[1]){
 return 1;
 } else if(move - population.sheep[x].chromosome[0] - population.sheep[x].chromosome[1] < population.sheep[x].chromosome[2]){
 return 2;
 } else{
 return 3;
 }
}

function moveEaten(x,move){
 if(move < population.sheep[x].chromosome[4]){
 return 0;
 } else if(move - population.sheep[x].chromosome[4] < population.sheep[x].chromosome[5]){
 return 1;
 } else if(move - population.sheep[x].chromosome[4] - population.sheep[x].chromosome[5] < population.sheep[x].chromosome[6]){
 return 2;
 } else{
 return 3;
 }
}

//count generations and days
var generation=0;
var counter = 0;

//create world
function createWorld(){
 background("lightblue");
 fill(0,255,0);
 for(var x=0; x<settings.forestSize; x++){
 // Only draw trees that haven't been eaten
 if(forest.trees[x].posx !== null && forest.trees[x].posy !== null){
 rect(onCanvas(forest.trees[x].posx), onCanvas(forest.trees[x].posy), 4, 4);
 }
 }
 fill(255,0,0);
 for(var x=0; x<settings.populationSize; x++){
 population.sheep[x].state=0;
 rect(onCanvas(population.sheep[x].xpos), onCanvas(population.sheep[x].ypos), 4, 4);
 }
 //remove eaten trees
 for(var x=0; x<settings.populationSize; x++){
 for(var y=0; y<settings.forestSize; y++){
 if(population.sheep[x].xpos === forest.trees[y].posx && population.sheep[x].ypos === forest.trees[y].posy && forest.trees[y].posx !== null){
 forest.trees[y].posx=null;
 forest.trees[y].posy=null;
 population.sheep[x].state=1;
 population.sheep[x].fitness++;
 }
 }
 }
 //move sheep based on chromosome
 for(var x=0; x<settings.populationSize; x++){
 var move = Math.random();
 if(population.sheep[x].state==0){
 switch(moveHungry(x, move)){
 case 0: //up
 if(population.sheep[x].ypos>0) population.sheep[x].ypos-=1;
 break;
 case 1: //down
 if(population.sheep[x].ypos<settings.rows-1) population.sheep[x].ypos+=1;
 break;
 case 2: //right
 if(population.sheep[x].xpos<settings.cols-1) population.sheep[x].xpos+=1;
 break;
 case 3: //left
 if(population.sheep[x].xpos>0) population.sheep[x].xpos-=1;
 }
 } else {
 switch(moveEaten(x, move)){
 case 0: //up
 if(population.sheep[x].ypos>0) population.sheep[x].ypos-=1;
 break;
 case 1: //down
 if(population.sheep[x].ypos<settings.rows-1) population.sheep[x].ypos+=1;
 break;
 case 2: //right
 if(population.sheep[x].xpos<settings.cols-1) population.sheep[x].xpos+=1;
 break;
 case 3: //left
 if(population.sheep[x].xpos>0) population.sheep[x].xpos-=1;
 }
 }
 }
 counter++;
}

function reset(){
 counter=0;
 //regrow forest
 forest = new Forest(settings.forestSize);
 //reset locations and fitness values
 for(var x=0; x<settings.populationSize; x++){
 population.sheep[x].xpos = Math.floor(Math.random()*settings.cols);
 population.sheep[x].ypos = Math.floor(Math.random()*settings.rows);
 population.sheep[x].fitness=0;
 }
}

function setup() {
 createCanvas(1000, 500);
}

function draw() {
 createWorld();
 if(counter>=settings.year){
 population.evaluate();
 population.breed();
 reset();
 generation++;
 if(generation>=settings.end){
 noLoop();
 }
 }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.min.js"></script>

Additional Notes

  • Fixed the randombetween function to properly respect the min/max range (original code ignored the min parameter).
  • Increased the mating pool multiplier from 10 to 100 to give higher-fitness sheep more representation in selection.
  • Added checks in createWorld to avoid drawing trees that have already been eaten.
  • Used spread syntax ([...dna]) to clone chromosomes, preventing unintended reference sharing between parents and offspring.

内容的提问来源于stack exchange,提问作者David Wu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 04:57:03