JS - Error: "Cannot read property of undefined"

Mare_413 picture Mare_413 · Feb 13, 2017 · Viewed 7.2k times · Source

I'm attempting to make a genetic algorithm simulation in JavaScript using the P5.JS library, but i'm experiencing some issues. Here's what i have so far:

//var popS = 2;
var popu;
//var balls = [];
var target;

function setup() {
  createCanvas(800, 300);
  popu = new Population();
  target = createVector(width - 15, height / 2);
}

function draw() {
  background(50);
  popu.run();

  var ballsC = 0;
  for (var a = 0; a < popu.balls.length; a++) {
    if (popu.balls[a].done == true){ 
      ballsC ++;
    }
  }
  if (ballsC >= popu.popS) {
    //popu = new Population();
    popu.evaluate();
    //popu.selection();
  }

  fill(255, 0, 30);
  noStroke();
  ellipse(target.x, target.y, 20, 20);
}

function DNA() {
  this.genes = [];
  this.changes = 7;//random(2, 50);

  for (var a = 0; a < this.changes; a++) {
    this.genes[a] = random(0, 15);
  }

  this.crossover = function (pB) {
    var newdna = new DNA();
    var mid = floor(random(0, this.genes.length));
    for (var a = 0; a < this.genes.length; a++) {
      if (a < mid) {
        newdna.genes[a] = this.genes[a];
      }else {
        newdna.genes[a] = pB.genes[a];
      }
    }
    return newdna;
  }
}

function Population() {
  this.balls = [];
  this.popS = 50;
  this.maxfit = 0;
  this.matingpool = [];

  for (var a = 0; a < this.popS; a++) {
    this.balls[a] = new Ball();
  }

  this.evaluate = function() {
    for (var a = 0; a < this.balls.length; a++) {
      this.balls[a].calcF();
      if (this.balls[a].fitness > this.maxfit) {
        this.maxfit = this.balls[a].fitness;
      }
    }
    this.matingpool = [];
    for (var b = 0; b < this.balls.length; b++) {
      var n = this.balls[b].fitness * 100;
      for (var c = 0; c < n; c++) {
        this.matingpool.push(this.balls[c]);
      }
    }

    this.selection();
  }

  this.selection = function () {
    var newBalls = [];
    for (var a = 0; a < this.balls.length; a++) {
      var parentA = this.matingpool[floor(random(0, this.matingpool.length))];
      var parentB = this.matingpool[floor(random(0, this.matingpool.length))];
      var child = parentA.dna.crossover(parentB.dna);
      newBalls[a] = new Ball(child);
    }
    this.balls = newBalls;
  }

  this.run = function() {
    for (var a = 0; a < this.balls.length; a++) {
      this.balls[a].update();
      this.balls[a].checkCol();
      this.balls[a].show();
    }
  }
}

function Ball(dna) {
  this.pos = createVector(10, height / 2);
  this.speed = createVector(2, 2.5);
  this.mul = -1;
  this.time = 0;
  this.a = 0;
  if (dna) {
    this.dna = dna;
  } else {
    this.dna = new DNA();
  }
  this.done = false;
  this.fitness = 0;
  this.reached;

  this.update = function() {
    if (this.done == false) {
      if (this.time >= this.dna.genes[this.a]) {
        this.a++;
        this.time = 0;
        this.mul *= -1;
      }
      this.speed.set(2, 2.5 * this.mul);
      this.pos.add(this.speed);
    }
  }

  this.show = function() {
    this.time += 0.1;
    fill(255, 70);
    noStroke();
    ellipse(this.pos.x, this.pos.y, 10, 10);
  }

  this.checkCol = function() {
    if (this.pos.y > height || this.pos.y < 0 || this.pos.x > width) {
      //print("col");
      this.done = true;
    }
    if (dist(this.pos.x, this.pos.y, target.x, target.y) <= (10 / 2) + (20 / 2)) {
      //print("done!");
      this.done = true;
      this.reached = true;
    }
  }

  this.calcF = function() {
    var a = dist(this.pos.x, this.pos.y, target.x, target.y);
    var b = this.dna.genes.length;
    var c = 0;
    if (this.reached){
      c = 1;
    }

    this.fitness = map(map(a, 0, width, 1, 0) + map(b, 2, 50, 1, 0) + c, 0, 3, 0, 1);
  }
}

This is the most essential part of the code:

var popu;

function setup() {
  createCanvas(800, 300);
  popu = new Population();
}

function draw() {
  background(50);
  //popu = new Population();
  popu.evaluate();
  //popu.selection();
}

function DNA() {
  this.genes = [];
  this.changes = 7; //random(2, 50);

  for (var a = 0; a < this.changes; a++) {
    this.genes[a] = random(0, 15);
  }

  this.crossover = function(pB) {
    var newdna = new DNA();
    var mid = floor(random(0, this.genes.length));
    for (var a = 0; a < this.genes.length; a++) {
      if (a < mid) {
        newdna.genes[a] = this.genes[a];
      } else {
        newdna.genes[a] = pB.genes[a];
      }
    }
    return newdna;
  }
}

function Population() {
  this.balls = [];
  this.popS = 50;
  this.maxfit = 0;
  this.matingpool = [];

  for (var a = 0; a < this.popS; a++) {
    this.balls[a] = new Ball();
  }

  this.evaluate = function() {
    this.matingpool = [];
    for (var b = 0; b < this.balls.length; b++) {
      var n = this.balls[b].fitness * 100;
      for (var c = 0; c < n; c++) {
        this.matingpool.push(this.balls[c]);
      }
    }

    this.selection();
  }

  this.selection = function() {
    var newBalls = [];
    for (var a = 0; a < this.balls.length; a++) {
      var parentA = this.matingpool[floor(random(0, this.matingpool.length))];
      var parentB = this.matingpool[floor(random(0, this.matingpool.length))];
      var child = parentA.dna.crossover(parentB.dna);
      newBalls[a] = new Ball(child);
    }
    this.balls = newBalls;
  }
}

function Ball(dna) {
  this.pos = createVector(10, height / 2);
  this.speed = createVector(2, 2.5);
  this.mul = -1;
  this.time = 0;
  this.a = 0;
  if (dna) {
    this.dna = dna;
  } else {
    this.dna = new DNA();
  }
  this.done = false;
  this.fitness = 0;
  this.reached;

}

So whenever it gets to here:

this.selection = function () {
    var newBalls = [];
    for (var a = 0; a < this.balls.length; a++) {
      var parentA = random(this.matingpool);
      var parentB = random(this.matingpool);
      var child = parentA.dna.crossover(parentB.dna);
      newBalls[a] = new Ball(child);
    }
    this.balls = newBalls;
  }

i get the error: "Cannot read property 'dna' of undefined", why on earth is this happening?? When i use the debugger in chrome i can clearly see that matingpool has 2000 elements but when i try to get a random one it returns "undefined".

var parentA = random(this.matingpool);
var parentB = random(this.matingpool);

The weird thing is that parentB works, but parentA dosn't.

enter image description here

Any help is much appreciated. Entire code running here: http://codepen.io/felipe_mare/pen/bgOYMN

If it helps, i sometimes get the error: "Cannot read property '0' of undefined" instead, at line 138

this.update = function() {
    if (this.done == false) {
      //line 138
      if (this.time >= this.dna.genes[this.a]) {
      //line 138
        this.a++;
        this.time = 0;
        this.mul *= -1;
      }
      this.speed.set(2, 2.5 * this.mul);
      this.pos.add(this.speed);
    }
  }

Answer

Kevin Workman picture Kevin Workman · Feb 13, 2017

In the future, please please please try to narrow your questions down to an MCVE. I understand that this was a complicated problem to debug, but you'll have much better luck if you try to narrow your problem down to a minimal (under 20 lines) example. Most of the time, you'll end up finding your error in the process of creating the MCVE.

But your problem is actually when you create the matingpool array, here:

this.matingpool = [];
for (var b = 0; b < this.balls.length; b++) {
  var n = this.balls[b].fitness * 100;
  for (var c = 0; c < n; c++) {
    this.matingpool.push(this.balls[c]);
  }
}

If I add a print statement inside the inner for loop, like this:

this.matingpool = [];
for (var b = 0; b < this.balls.length; b++) {
  var n = this.balls[b].fitness * 100;
  for (var c = 0; c < n; c++) {
    console.log("pushing: " + this.balls[c]);
    this.matingpool.push(this.balls[c]);
  }
}

Then I see that you're pushing undefined into the array a bunch of times:

(1178) pushing: [object Object]
(3) pushing: undefined
(482) pushing: [object Object]
(3) pushing: undefined
(216) pushing: [object Object]

Then you choose randomly from this array, which is why your error shows up in random places in your code.

You're going to have to debug this further to figure out why that's happening- it seems weird that you're looping based on a fitness instead of an array length, but I don't really understand the code well enough to be sure. In any case, hopefully this gets you on the right track.