Zelkins
Zelkins

Reputation: 759

Occasional error in word finding genetic algorithm

I (almost) successfully created a genetic algorithm to find a given string, but for some reason, I semi-frequently get an error when running the program, which otherwise works perfectly.

 Cannot read property 'dna' of undefined at Population.populate

I can't seem to figure out what causes this issue only a handful of times.

<!DOCTYPE html>
<html>
<head>
	<title>Word Search</title>
	<link href='https://fonts.googleapis.com/css?family=Ubuntu' rel='stylesheet'>
</head>
<body style ='font-family: Ubuntu, sans-serif'>
	Target: <input type='text' value='Hello World!'  id='target'> <br>
	Population Size: <input type='number' id='size' min='0' max='100' step='1' value='10'><br>
	Mutation Rate: <input type='number' id='rate' min='0' max='100' step='1' value='10'>%<br>
	<input type='submit' id='submit' onclick='evolution()'>
	<div  style='border-width: 2px; border-style: dashed; width: 250px'>
		<div style='text-align: center;' id='value'>
			<p id='generation';>Generation | 0</p>
			<div id='pop'>
			</div>
		</div>
	</div>

<script type="text/javascript">



window.onload = function() {
	evolution();
}

function evolution() {

	var population = new Population(
		document.getElementById('target').value, 
		document.getElementById('rate').value/100, 
		document.getElementById('size').value);

	var running = setInterval(function() {
		document.getElementById('submit').disabled = true;
		population.natSelection();
		population.populate();
		population.evaluate();
		population.display();
		if(population.completed) {
			clearInterval(running);
			document.getElementById('submit').disabled = false;
		}
	}, 50);
}

function Population(target, mutationRate, size) {
	this.target = target;
	this.mutationRate = mutationRate;
	this.size = size;
	this.members = [];
	this.genePool = [];
	this.completed = false;
	this.generation = 0;

	for(var i = 0; i < this.size; i++)
		this.members.push(new Genome(this.target, this.mutationRate));

	this.natSelection = function() {
		for(var i = 0; i < this.members.length; i++)
			this.members[i].calcFitness();
		
		this.genePool = [];

		for(var i = 0; i < this.members.length; i++)
			for(var j = 0; j < this.members[i].fitness*10; j++)
				this.genePool.push(this.members[i]);
	}

	this.populate = function() {
		this.generation++;
		this.members = [];
		for(var i = 0; i < this.size; i++) {
			var a = this.genePool[Math.floor(Math.random()*this.genePool.length)].dna;
			var b = this.genePool[Math.floor(Math.random()*this.genePool.length)].dna;
			this.members.push(new Genome(this.target, this.mutationRate, a, b));
		}
	}

	this.evaluate = function() {
		for(var i = 0; i < this.members.length; i++) {
			if(this.members[i].dna === this.target)
				this.completed = true;
		}
	}

	this.calcMaxFitness = function() {
		var fittest = this.memebers[0].fitness; 
		for(var i = 1; i < this.members.length; i++)
			if(this.memebers[i].fitness > fittest)
				fittest = this.memebers[i].fitness;
		return fittest;
	}

	this.display = function() {
		document.getElementById('generation').innerHTML = 'Generation | '+this.generation;
		var div = document.getElementById('pop');
		div.innerHTML = '';
		for(var i = 0; i < this.members.length; i++) {
			div.innerHTML += this.members[i].dna+'<br>'
		}
	}
}

function Genome(target, mutationRate, parentA, parentB) {
	this.dna = '';
	this.fitness = 0;
	this.target = target;
	this.mutationRate = mutationRate;

	this.mutate = function() {
		for(var i = 0; i < this.target.length; i++) {
			if(this.dna.charCodeAt(i) != this.target.charCodeAt(i))
				if(Math.random() > this.mutationRate)
					this.dna = this.dna.replaceAt(i, String.fromCharCode(Math.floor(Math.random()*94+32)));
		}
	}

	if(!parentA && !parentB) {
		for(var i = 0; i < this.target.length; i++)
			this.dna += String.fromCharCode(Math.floor(Math.random()*94+32));
	} else {
		var mid = Math.floor(Math.random()*this.target.length); 
		this.dna = parentA.substr(0, mid) + parentB.substr(mid, parentB.length);
		this.mutate();
	}
	
	this.calcFitness = function() {
		this.fitness = 0;
		for(var i = 0; i < this.target.length; i++) {
			if(this.dna.charCodeAt(i) === this.target.charCodeAt(i))
				this.fitness++;
		}
	}

}

Number.prototype.map = function (in_min, in_max, out_min, out_max) {
  return (this - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

String.prototype.replaceAt=function(index, replacement) {
    return this.substr(0, index) + replacement+ this.substr(index + replacement.length);
}

</script>

</body>
</html>

Upvotes: 2

Views: 54

Answers (2)

Racil Hilan
Racil Hilan

Reputation: 25361

This error happens when all members have a fitness of zero, then the loop in the natSelection function is looping until j < this.members[i].fitness*10 which is zero, so no loop and nothing is added to genePool.

Now when that happens, obviously this.genePool[Math.floor(Math.random()*this.genePool.length)] will be undefined and when you try to access the property dna, you get the error.

To solve this, you must add some validation so when all members have fitness of zero, you do something about it. Alternatively, at the end of the natSelection function, check genePool.length and if it is still zero, then add at least one item.

Upvotes: 3

Raphael Rafatpanah
Raphael Rafatpanah

Reputation: 20007

First, let's make sure we fully understand the error.

You are trying to read property dna of undefined in your populate method.

Said another way:

// Your populate method
this.populate = function() {
    this.generation++;
    this.members = [];
    for(var i = 0; i < this.size; i++) {
        var a = this.genePool[Math.floor(Math.random()*this.genePool.length)].dna;
        var b = this.genePool[Math.floor(Math.random()*this.genePool.length)].dna;
        this.members.push(new Genome(this.target, this.mutationRate, a, b));
    }
}

JavasScript is seeing

this.genePool[Math.floor(Math.random()*this.genePool.length)]

as undefined

and then, you're asking to JavaScript to read the dna property of undefined.

undefined.dna

Now, let's figure out how we can debug the error.

Personally, I would log the following:

// Your populate method
this.populate = function() {
    this.generation++;
    this.members = [];

    console.log('SIZE', this.size);

    for(var i = 0; i < this.size; i++) {

        console.log('I', i);
        console.log('this.genePool', this.genePool);
        console.log('this.genePool.length', this.genePool.length);

        var a = this.genePool[Math.floor(Math.random()*this.genePool.length)].dna;
        var b = this.genePool[Math.floor(Math.random()*this.genePool.length)].dna;
        this.members.push(new Genome(this.target, this.mutationRate, a, b));
    }
}

Next, let's analyze the logs.

Maybe we have using i < this.size when we should be using i < this.size - 1, or something similar elsewhere (aka off-by-one error).

Maybe this.genePool is undefined sometimes.

If you still have trouble, update your question with some log output.

Upvotes: 1

Related Questions