xxxxxxxxxx
364
const s = 620;
function mulberry32(a) {
return function() {
var t = a += 0x6D2B79F5;
t = Math.imul(t ^ t >>> 15, t | 1);
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
return ((t ^ t >>> 14) >>> 0) / 4294967296;
}
}
var rand = mulberry32(3984143);
function scircle(x,y,r) {
circle(s*x,s*y,s*r);
}
function stext(str,x,y) {
text(str, s*x, s*y);
}
function sline(x0,y0,x1,y1) {
line(s*x0,s*y0,s*x1,s*y1);
}
// Input values to brain
// t equals brain generation number (integer) for testing purposes
// Training to get desired outputs as a function of inputs (and state)
// Neuron index should be stable
function inputs(k,t) {
return Math.sin(2*pi*0.1*t);
}
function nonlinearity(x) {
return Math.tanh(x);
}
//TODO: Don't allow multiple connections to same neuron
class brain {
constructor(neuronCount,inputCount,outputCount) {
this.generation = 0;
this.neuronCount = neuronCount;
this.neurons = [];
this.inputCount = inputCount;
this.outputCount = outputCount;
let nn = neuronCount;
//Fixed connection count per neuron
const connections = Math.ceil(2);
//Connections per neuron
let neurons = [];
for(let ni = 0; ni < nn; ni++) {
let parameters = {
x:rand(),
y:rand(),
value: rand()-1/2,
bias: rand()-1/2,
cost: 0,
outputs: [],
weights: [],
externalInput: ni<inputCount,
externalOutput: (ni>=inputCount)&&(ni<inputCount+outputCount),
};
let n = new neuron(parameters);
//Neuron output connections
let o = [];
let w = [];
if (!n.externalOutput) {
for (let oi = 0; oi < connections; oi++) {
let target = Math.floor(neuronCount*rand());
let collision = true;
// Avoid some conditions for connecting
while (collision) {
target = Math.floor(neuronCount*rand());
collision = false;
//No double connections
for(let oj = 0; oj < oi; oj++) {
if(target == o[oj]) {
collision = true;
}
}
//No self-connections (is this good?)
if (target == ni) {
collision = true;
}
}
o[oi] = target;
w[oi] = 25*(rand()-1/2)/Math.pow((Math.log2(neuronCount)),1.5);
}
}
n.outputs = o;
n.weights = w;
neurons[ni] = n;
}
/*
for (let ci = 0; ci < connections; ci++ ) {
//Unconnected neurons get a random connection each
if ( ci < nn ) {
random_neuron = Math.floor(neuron_count*rand());
let n = neurons[ci];
n.connections.append(random_neuron);
}
//Afterwards:
//Choose a random neuron: less connected neurons are more likely
random_neuron = Math.floor(neuron_count*rand());
}
*/
this.neurons = neurons;
}
}
//Spatial neural network:
//each neuron has a position
//connection algorithms favor proximate neurons
//probability decays as 1/d2
class neuron {
constructor(parameters) {
const p = parameters;
//Position
this.x = p.x?p.x:rand();
this.y = p.y?p.y:rand();
// Value that's passed to other neurons
this.value = p.value?p.value:0;
// Constant value input
this.bias = p.bias?p.bias:0;
// Input buffer
this.input = p.input?p.input:0;
// Cost for neuron pruning decisions
this.cost = p.cost?p.cost:0;
// Output connections of each neuron
this.outputs = p.outputs?p.outputs:[];
// Weight of each output
this.weights = p.weights?p.weights:[];
// Flags external output (e.g. to control a muscle)
this.externalOutput = p.externalOutput?p.externalOutput:false;
// Flags external input (e.g. from a sensor)
this.externalInput = p.externalInput?p.externalInput:false;
}
propagate(brain) {
const connections = this.outputs.length;
for(let oi = 0; oi < connections; oi++) {
let o = this.outputs[oi];
let outputNeuron = brain.neurons[o];
outputNeuron.input += this.value*this.weights[oi];
}
}
updateValue() {
if(this.externalInput)
return;
this.value = Math.tanh(this.input+this.bias);
this.input = 0;
}
//static displayName = "neuron";
static distance(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.hypot(dx, dy);
}
}
function evolveBrain(brain,input,output) {
}
function brainCost(brain) {
let cost = 0;
for(let n in brain.neurons) {
cost += n.cost;
}
}
function drawBrain(brain) {
const neurons = brain.neurons.length;
for(let ni = 0; ni < neurons; ni++) {
let n = brain.neurons[ni];
c = "white";
if (n.externalOutput) {
c = "green";
}
if (n.externalInput) {
c = "blue";
}
fill(c);
scircle(n.x,n.y,0.1/sqrt(neurons));
//Value text
textSize(32*2/sqrt(neurons));
//stext(n.value.toFixed(2), n.x+0.1/sqrt(neurons), n.y);
//Weight text size
textSize(27*2/sqrt(neurons));
const connections = n.outputs.length;
for(let oi = 0; oi < connections; oi++) {
let n2i = n.outputs[oi];
let n2 = brain.neurons[n2i];
let v = {
x:n2.x-n.x,
y:n2.y-n.y,
};
let norm = sqrt(v.x*v.x+v.y*v.y);
let vperp = {
x:-v.y/norm,
y: v.x/norm,
}
sline(n.x,n.y,n2.x,n2.y);
sline(n.x+v.x/2,n.y+v.y/2,
n.x+v.x/2*0.9+vperp.x/40/sqrt(neurons),n.y+v.y/2*0.9+vperp.y/40/sqrt(neurons));
sline(n.x+v.x/2,n.y+v.y/2,
n.x+v.x/2*0.9-vperp.x/40/sqrt(neurons),n.y+v.y/2*0.9-vperp.y/40/sqrt(neurons));
// Weight text
//stext(n.weights[oi].toFixed(2), n.x+v.x/2+0.1/sqrt(neurons), n.y+v.y/2);
//scircle(n2.x,n2.y,0.02/sqrt(neurons));
}
}
stext('generation:',0.05,0.05);
stext(brain.generation,0.29,0.05);
}
function think(brain,t) {
let nn = brain.neuronCount;
//Weight propagation
for(let ni = 0; ni < nn; ni++) {
let n = brain.neurons[ni];
n.propagate(brain);
}
//Value update
for(let ni = 0; ni < nn; ni++) {
let n = brain.neurons[ni];
n.updateValue();
}
//Set input values
for (let ni = 0; ni < brain.inputCount; ni++) {
brain.neurons[ni].value = sin(brain.generation*0.01)/2;
}
brain.generation += 1;
}
let maxDelta = 0;
//TODO: force connected neurons together
function repositionNeurons(brain, rate) {
maxDelta = 0;
let r = rate?rate:1;
r = 3;
let k = 0.6;
r = 20/100;//pow(k,10/k);//pow(0.5,1/k)
let nn = brain.neuronCount;
for (let mi = 0; mi <= 1; mi ++) {
for(let ni = 0; ni < nn; ni++) {
let n1 = brain.neurons[ni];
for(let nj = 0; nj < nn; nj++) {
let n2 = brain.neurons[nj];
if (ni == nj) {
continue;
}
let m = 1;
let d2 = (n2.x-n1.x)*(n2.x-n1.x)+(n2.y-n1.y)*(n2.y-n1.y);
let dk = pow(d2, 1/k);
let connected = false
let o = n1.outputs;
for (let oi = 0; oi < o.length; oi++) {
if(nj == o[oi]) {
connected = true;
}
}
if (connected) {
m=-3200*(sqrt(d2)-0.8/sqrt(nn))*pow(abs(sqrt(d2)-0.8/sqrt(nn)),0.33)/log(nn);
}
if(n1.externalInput && n2.externalInput) {
//m=-256*sqrt(nn)*pow((sqrt(d2)-0.8/sqrt(nn)),1.5);
}
if(n1.externalOutput && n2.externalOutput) {
//m=-256*sqrt(nn)*pow((sqrt(d2)-0.8/sqrt(nn)),1.5);
}
if ( sqrt(d2) < 1 ) {
let fx = m*r*0.0064*(n1.x-n2.x)/(dk*pow(nn,1));
let fy = m*r*0.0064*(n1.y-n2.y)/(dk*pow(nn,1));
if ( mi == 1) {
n1.x += fx/sqrt(maxDelta)*0.0001*log(time)*log(log(time)+1);
n1.y += fy/sqrt(maxDelta)*0.0001*log(time)*log(log(time)+1);
}
else {
let f = fx*fx + fy*fy;
if ( f > maxDelta) {
maxDelta = f;
}
}
}
}
let w = r*0.0032*1.2/4;//pow(abs(k-0.7),2);
if(n1.externalInput) {
n1.x -= r*0.009;
}
if(n1.externalOutput) {
n1.x += r*0.004;
}
n1.x += w*(0.5-n1.x)*2;
n1.y += w*(0.5-n1.y)*2;
/*
if (n1.x < 0.5) {
n1.x += w;
}
else if (n1.x > 0.5) {
n1.x -= w;
}
if (n1.y < 0.5) {
n1.y += w;
}
else if (n1.y > 0.5) {
n1.y -= w;
}
*/
if (n1.x < 0.14) {
n1.x += r*0.015*(0.14-n1.x)/0.14;
}
else if (n1.x > 0.86) {
n1.x -= r*0.015*(n1.x-0.86)/0.14;
}
if (n1.y < 0.14) {
n1.y += r*0.015*(0.14-n1.y)/0.14;
}
else if (n1.y > 0.86) {
n1.y -= r*0.015*(n1.y-0.86)/0.14;
}
}
}
}
let brain1 = new brain(32,2,2);
function setup() {
createCanvas(s, s);
background(220);
for (let i = 0; i < 72000; i++) {
}
}
let think_done = false;
let last_done = 0;
let time = 0;
function draw() {
if(!think_done) {
time++;
background(220);
//think(brain1,t);
drawBrain(brain1);
repositionNeurons(brain1,0.3);
//repositionNeurons(brain1);
brain1.generation = time;
think_done = true;
last_done = millis();
}
else {
if(millis() - last_done > 32) {
think_done = false;
}
}
}