diff --git a/LemmingSim.html b/LemmingSim.html
index 578c4e8..b054427 100644
--- a/LemmingSim.html
+++ b/LemmingSim.html
@@ -53,7 +53,7 @@ along with this program. If not, see .
-
x
+
x
diff --git a/lemming_sim_student.js b/lemming_sim_student.js
index d5071ae..97ab518 100644
--- a/lemming_sim_student.js
+++ b/lemming_sim_student.js
@@ -30,11 +30,11 @@ Feel free to use any of this code.
"use strict"
// Simulation settings; please change anything that you think makes sense.
var simInfo = {
- maxSteps: 50000, // maximal number of simulation steps to run
+ maxSteps: 20000, // maximal number of simulation steps to run
airDrag: 0.1, // "air" friction of enviroment; 0 is vacuum, 0.9 is molasses
- boxFric: 0.001, // friction between boxes during collisions
+ boxFric: 0.005, // friction between boxes during collisions
boxMass: 0.01, // mass of boxes
- boxSize: 10, // size of the boxes, in pixels
+ boxSize: 15, // size of the boxes, in pixels
robotSize: 13, // approximate robot radius, in pixels (note the SVG gets scaled down)
robotMass: 0.4, // robot mass (a.u)
gravity: 0, // constant acceleration in Y-direction
@@ -43,8 +43,97 @@ var simInfo = {
debugMouse: true, // allow dragging any object with the mouse
wanderRate: 0.0002,
};
+class SVGRobot extends Robot {
+ makeBody(robotInfo) {
+ // load robot's body shape from SVG file
+ const bodySVGpoints = loadFromSVG();
+ return Matter.Bodies.fromVertices(robotInfo.init.x,
+ robotInfo.init.y,
+ bodySVGpoints, {
+ frictionAir: simInfo.airDrag,
+ mass: simInfo.robotMass,
+ color: [255, 255, 255],
+ role: 'robot'
+ }, true);
+ }
+}
+class HebbDidaBot extends Robot {
+ constructor(props) {
+ const angle = Math.PI/3.5
+ super(Object.assign({
+ learningRate: 0.001,
+ forgettingRate: 0.001,
+ sensors: [
+ new CollisionSensor('collL', {
+ attachAngle: -angle
+ }),
+ new CollisionSensor('collR', {
+ attachAngle: angle
+ }),
+ new DistanceSensor('distL', {
+ attachAngle: -angle
+ }),
+ new DistanceSensor('distR', {
+ attachAngle: angle
+ }),
+ ].concat(props.sensors || [])
+ }, props))
+ this.display = document.createElement('canvas')
+ this.display.width = this.display.height = 100
+ this.display.style.border = '1px solid black'
+ this.weights = [[Math.random()/10,Math.random()/10],[Math.random()/10,Math.random()/10]]
+ }
+ move() {
+ const {distL, distR, collL, collR} = this.getSensors()
+ // feed forward: perception
+ // collision
+ function feedforward(weights, activations, intrinsic) {
+ return intrinsic.map((x, i) => {
+ return x + sum(zip(weights[i], activations).map(([a,b]) => a*b))
+ })
+ }
+ function hebb(weight, lrate, frate, act_src, act_dst) {
+ const avg = sum(act_src) / act_src.length
+ return weight.map((row, i) => row.map((wij, j) =>
+ wij + (lrate * (act_src[j] * act_dst[i]) - frate * avg * wij) / act_src.length
+ ))
+ }
+ // input:
+ const motorweights = [[0,1],[1,0]]
+ const collisionAct = [+collL, +collR]
+ const threshold = theta => x => x > theta
+ const percact = [distL, distR].map(x => x == Infinity ? 0 : (50 - x)/50)
+ const collact = [+collL, +collR]
+ const collisionlayer = feedforward(this.weights, percact, collact)
+ const motorLayer = feedforward(motorweights, collisionlayer, [0,0])
+ this.weights = hebb(this.weights, this.info.learningRate, this.info.forgettingRate, percact, collisionlayer)
+ const [left, right] = motorLayer.map(threshold(0.5))
+ // output: 2 neurons. if left is activated, turn left. if right is activated, turn right.
+ // if both, go backwards. otherwise: forward
+ if (left && !right) this.rotate(-0.003)
+ else if (!left && right) this.rotate(+0.003)
+ else if (left && right) this.drive(-2e-4)
+ else this.drive(2e-4)
+ this.layers = [percact, collisionlayer, motorLayer]
+ }
+ getDisplay() {
+ return this.layers
+ }
+ updateDisplay() {
+ const ctx = this.display.getContext('2d')
+ if (!this.weights) return
+ ctx.clearRect(0,0,100,100)
+ this.weights.forEach((a, i) => {
+ a.forEach((b, j) => {
+ ctx.fillStyle = convrgb([b*128, b*128, b*128].map(x => 255-x))
+ const [x,y] = [j*50,i*50]
+ ctx.fillRect(x, y, x+50, y+50)
+ })
+ })
+ }
+}
// our own lemming implementation
-class Lemming extends Robot {
+class Lemming extends SVGRobot {
constructor(props) {
super(Object.assign({
sensors: [
@@ -321,7 +410,7 @@ function initSimulation() {
sim.init()
let noRobots = +document.getElementById('robotAmnt').value
while(noRobots--) {
- sim.addRobot(new Lemming({
+ sim.addRobot(new HebbDidaBot({
// random color
color: [Math.random()*255, Math.random()*255, Math.random()*255], // color of the robot shape
init: {x: 50*noRobots + 50, y: 50, angle: 0}, // initial position and orientation
@@ -359,10 +448,33 @@ function redblocksatwall(sim) {
})
return result
}
+function countclusters(sim) {
+ const boxes = sim.world.composites[0].bodies
+ function dist(box1, box2) {
+ return Vec2.dist(box1.position, box2.position)
+ }
+ const clusters = []
+ const maxdist = 2 * Math.sqrt(2) * simInfo.boxSize
+ const unclaimed = new Set(boxes)
+ while(unclaimed.size) {
+ const box = unclaimed.entries().next().value[0]
+ const queue = new Set([box])
+ let no_boxes = 0
+ while(queue.size) {
+ const box2 = queue.entries().next().value[0]
+ unclaimed.delete(box2)
+ queue.delete(box2)
+ no_boxes++
+ Array.from(unclaimed).filter(box3 => (dist(box2, box3) < maxdist)).forEach(x => queue.add(x))
+ }
+ clusters.push(no_boxes)
+ }
+ return clusters.sort().reverse()
+}
// automatically run the experiments based on the values from the html
-async function experimentRunner(stopCondition=blocksSorted) {
+async function experimentRunner(stopCondition) {
initSimulation()
- while(!stopCondition(sim) && sim.curSteps < simInfo.maxSteps) {
+ while((stopCondition ? !stopCondition(sim) : true) && sim.curSteps < simInfo.maxSteps) {
sim.runSteps(500)
// take a break for the gui to stay responsive
await promiseTimeout(50)
@@ -371,8 +483,9 @@ async function experimentRunner(stopCondition=blocksSorted) {
// save data
const rv = {
steps: sim.curSteps,
- done: stopCondition(sim),
- redblocks: redblocksatwall(sim),
+ //done: stopCondition(sim),
+ //redblocks: redblocksatwall(sim),
+ clusters: countclusters(sim),
robots: sim.robots.length
}
sim.destroy()
@@ -394,15 +507,15 @@ class ResultsTable {
clear() {
this.elem.innerHTML = "