# Pictogenesis: Register Machine

Okay. First Pictogeneis machine: a register based machine. Today we’re going to create a very small language with a small number of registers that can read from the outside world, write colors, and act as temporary variables.

Something like this:

gt? t0 b y x r
abs b x
inv t0 g
sub t0 b r
mul x r b
abs y x


In each case, the first argument is the output and the rest are inputs. So:

# gt? t0 b y x r
if (b > y) {
t0 = x;
} else {
t0 = r;
}

g = y + x

# abs b x
b = |x|
...


Where x and y are the input point x and y mapped to the range [0, 1]; r, g, b are the output colors in the same range and t{n} are temporary registers just used during the program.

First up, let’s decide on some instructions for the language:

const instructions = [
// Basic math
{name: "id", function: (x) => x},
{name: "add", function: (x, y) => x + y},
{name: "sub", function: (x, y) => x - y},
{name: "mul", function: (x, y) => x * y},
{name: "div", function: (x, y) => x / y},
{name: "max", function: (x, y) => Math.max(x, y)},
{name: "min", function: (x, y) => Math.min(x, y)},
{name: "abs", function: (x) => Math.abs(x)},
{name: "inv", function: (x) => 1 / x},
{name: "invsub", function: (x) => 1 - x},
{name: "neg", function: (x) => -x},
{name: "sin", function: (x) => Math.sin(x)},
{name: "exp", function: (x) => Math.exp(x)},
{name: "log", function: (x) => Math.log(x)},
{name: "sqrt", function: (x) => Math.sqrt(x)},
{name: "clamp", function: (x) => constrain(x, 0, 1)},
{name: "ceilfloor", function: (x) => x > 0.5 ? 1.0 : 0.0},

// Polar coordinate conversions
{name: "polR", function: (x, y) => Math.sqrt(x * x + y * y)},
{name: "polT", function: (x, y) => Math.atan2(x, y)},

// Constants
{name: "ZERO", function: () => 0},
{name: "ONE", function: () => 1},

// Conditionals
{name: "zero?", function: (c, t, f) => c === 0 ? t : f},
{name: "equal?", function: (a, b, t, f) => a === b ? t : f},
{name: "gt?", function: (a, b, t, f) => a > b ? t : f},
{name: "gt.5?", function: (c, t, f) => c > 0.5 ? t : f},
{name: "xgt.5?", function: function(t, f) { return this.x > 0.5 ? t : f; }},
{name: "ygt.5?", function: function(t, f) { return this.y > 0.5 ? t : f; }},
];


I started with just basic add, sub, mul, div, but then it sort of grew from there. Every function I expect will be able to take a small number of paramaters (even zero) and output one value. For the most part, we can use Lambda functions to create nice inline functions, but in a few cases, I expanded to use the full function syntax, I’ll get back to why in a bit.

Next, let’s take a genome (from the first post) and turn it into a program:

class RegisterMachine {
constructor(genome) {
// Low registers are input, high output, middle are temporary
let registers = ['x', 'y', 'r', 'g', 'b'];
while (registers.length < params.registerCount) {
registers.splice(2, 0, 't' + (registers.length - 5));
}

// Pull off one command for instruction and then as many args as needed
this.program = [];
for (var i = 0; i < genome.data.length;) {
// Copy the instruction to add params
let instruction = {
...gendex(instructions, genome.data[i++])
};
instruction.params = []
for (var j = 0; j < instruction.function.length + 1; j++) {
instruction.params.push(gendex(registers, genome.data[i++]));
}
this.program.push(instruction);
}
}
...
}


As we’re assembling the program (really more disassembling I guess), we’ll eat one real number for the opcode and then figure out how many parameters we need (function.length gives the arity of a function in JavaScript) and pull those off as well.

Now, how do we run it?

class RegisterMachine {
...
run(x, y) {
let registers = {
x: x,
y: y,
r: 0,
g: 0,
b: 0
};

// Run each command in the program
for (var command of this.program) {
// Collect input registers (all but the first)
let args = [];
for (var param of command.params.slice(1)) {
args.push(registers[param] || 0);
}

// Run the function, store in the first param
var result = command.function.apply(this, args);
result = isNaN(result) ? 0 : result;
if (params.clampPerStep) result = constrain(result, 0, 1);

if (params.readonlyXY && (command.params[0] == 'x' || command.params[0] == 'y')) {
// Do nothing, trying to write to x/y
} else {
registers[command.params[0]] = result;
}
}

// Return the color from the r/g/b registers
return [
registers.r,
registers.g,
registers.b
];
}
}


We’ll initialize the registers and then run through each command. There are a few variables that I made for tweaking parameters that come up here:

• clampPerStep: Every register we write is clamped to the range [0, 1]
• readonlyXY: The registers x and y cannot be written to, only read from

Once we’ve run the entire program, output the colors. I’ll get to the wrapper in a bit.

Finally, we have a nice string representation for debugging:

toString() {
return this.program.map((cmd) => cmd.name + ' ' + cmd.params.join(' ')).join('\n');
}


That’s what prints what I shared up at the top of the post.

Okay, so what’s the wrapper that actually turns this into code?

function setup() {
createCanvas(400, 400);
gui = createGuiPanel();
gui.setPosition(420, 0);

background(0);

g = new Genome(params.genomeSize);
p = new RegisterMachine(g);

let block = createElement('div');
code = createElement('textarea');
code.style('width', '400px');
code.style('height', '400px');
code.value(p.toString());
}

function draw() {
if (rendering) {
for (var i = 0; i < params.renderPerFrame; i++) {
let c = p.run(1.0 * renderingX / width, 1.0 * renderingY / height);
c = c.map((el) => int(255 * constrain(el, 0, 1)));

fill(c);
noStroke();
rect(renderingX, renderingY, 1, 1);

renderingX++;
if (renderingX >= width) {
renderingX = 0;
renderingY++;
}
if (renderingY >= height) {
rendering = false;
}
}
}
}


Originally I had the entire thing drawing in setup, but this code is … slow. I’ll work on that at some point, but we’re doing very inefficient interpretation. Maybe I can compile it to JavaScript at some point. That’d be neat!

So that’s all we need for this, let’s p5js it!


let gui;
let params = {
registerCount: 7,
registerCountMin: 5, // x, y, ..., r, g, b
registerCountMax: 30,
genomeSize: 30,
genomeSizeMin: 10,
genomeSizeMax: 1000,
clampPerStap: false,
renderPerFrame: 100,
renderPerFrameMax: 1000,
};

let g, p;
let rendering = true,
renderingX = 0,
renderingY = 0;
let code;

function resetRendering() {
background(0);
rendering = false;
renderingX = 0;
renderingY = 0;
}

function updateP(kls) {
if (kls) {
p = new kls(g);
} else {
p = new p.constructor(g);
}
code.value(p.toString());
resetRendering();
rendering = true;
}

// Helper function to index an array by a real number
gendex = (arr, id) => arr[int(arr.length * id)];

function setup() {
createCanvas(400, 400);
gui = createGuiPanel();
gui.setPosition(420, 0);

background(0);

g = new Genome(params.genomeSize);
p = new RegisterMachine(g);

let divMachine = createDiv("machines");

divMachine.child(createButton("registers").mousePressed(() => {
g = new Genome(params.genomeSize);
updateP(RegisterMachine);
}));

let divMutations = createDiv("mutations");

divMutations.child(createButton("point").mousePressed(() => {
g.mutatePoint();
updateP();
}));

divMutations.child(createButton("insert").mousePressed(() => {
g.mutateInsertion();
updateP();
}));

divMutations.child(createButton("delete").mousePressed(() => {
g.mutateDeletion();
updateP();
}));

divMutations.child(createButton("duplicate").mousePressed(() => {
g.mutateDuplication();
updateP();
}));

createButton("rerender").mousePressed(() => {
resetRendering();
rendering = true;
});

let block = createElement('div');
code = createElement('textarea');
code.style('width', '400px');
code.style('height', '400px');
code.value(p.toString());
block.child(code);
}

function draw() {
if (rendering) {
for (var i = 0; i < params.renderPerFrame; i++) {
let c = p.run(1.0 * renderingX / width, 1.0 * renderingY / height);
c = c.map((el) => int(255 * constrain(el, 0, 1)));

fill(c);
noStroke();
rect(renderingX, renderingY, 1, 1);

renderingX++;
if (renderingX >= width) {
renderingX = 0;
renderingY++;
}
if (renderingY >= height) {
rendering = false;
}
}
}
}


const instructions = [
// Basic math
{name: "id", function: (x) => x},
{name: "add", function: (x, y) => x + y},
{name: "sub", function: (x, y) => x - y},
{name: "mul", function: (x, y) => x * y},
{name: "div", function: (x, y) => x / y},
{name: "max", function: (x, y) => Math.max(x, y)},
{name: "min", function: (x, y) => Math.min(x, y)},
{name: "abs", function: (x) => Math.abs(x)},
{name: "inv", function: (x) => 1 / x},
{name: "invsub", function: (x) => 1 - x},
{name: "neg", function: (x) => -x},
{name: "sin", function: (x) => Math.sin(x)},
{name: "exp", function: (x) => Math.exp(x)},
{name: "log", function: (x) => Math.log(x)},
{name: "sqrt", function: (x) => Math.sqrt(x)},
{name: "clamp", function: (x) => constrain(x, 0, 1)},
{name: "ceilfloor", function: (x) => x > 0.5 ? 1.0 : 0.0},

// Polar coordinate conversions
{name: "polR", function: (x, y) => Math.sqrt(x * x + y * y)},
{name: "polT", function: (x, y) => Math.atan2(x, y)},

// Constants
{name: "ZERO", function: () => 0},
{name: "ONE", function: () => 1},

// Conditionals
{name: "zero?", function: (c, t, f) => c === 0 ? t : f},
{name: "equal?", function: (a, b, t, f) => a === b ? t : f},
{name: "gt?", function: (a, b, t, f) => a > b ? t : f},
{name: "gt.5?", function: (c, t, f) => c > 0.5 ? t : f},
{name: "xgt.5?", function: function(t, f) { return this.x > 0.5 ? t : f; }},
{name: "ygt.5?", function: function(t, f) { return this.y > 0.5 ? t : f; }},
];


class Genome {
constructor(length) {
length = length || 10;
this.data = [];
while (this.data.length < length) {
this.data.push(random());
}
}

// Apply up to one of each kind of mutation to this genome
mutate() {
var index;

if (random() < params.mutationRate_point) mutatePoint();
if (random() < params.mutationRate_insertion) mutateInsertion();
if (random() < params.mutationRate_deletion) mutateDeletion();
if (random() < params.mutationRate_duplication) mutateDuplication();
}

mutatePoint() {
var index = Math.floor(random() * this.data.length);
this.data[index] = random();
}

mutateInsertion() {
var index = Math.floor(random() * this.data.length);
this.data.splice(index, 0, random());
}

mutateDeletion() {
var index = Math.floor(random() * this.data.length);
this.data.splice(index, 1);
}

mutateDuplication() {
var index = Math.floor(random() * this.data.length);
this.data.splice(index, 0, this.data[index]);
}

crossover(other) {
var child = new Genome();
var thisIndex = Math.floor(random() * this.data.length);
var otherIndex = Math.floor(random() * other.data.length);

child.data = this.data.slice(0, thisIndex).concat(other.data.slice(otherIndex));
return child;
}
}


class RegisterMachine {
constructor(genome) {
// Low registers are input, high output, middle are temporary
let registers = ['x', 'y', 'r', 'g', 'b'];
while (registers.length < params.registerCount) {
registers.splice(2, 0, 't' + (registers.length - 5));
}

// Pull off one command for instruction and then as many args as needed
this.program = [];
for (var i = 0; i < genome.data.length;) {
// Copy the instruction to add params
let instruction = {
...gendex(instructions, genome.data[i++])
};
instruction.params = []
for (var j = 0; j < instruction.function.length + 1; j++) {
instruction.params.push(gendex(registers, genome.data[i++]));
}
this.program.push(instruction);
}
}

run(x, y) {
let registers = {
x: x,
y: y,
r: 0,
g: 0,
b: 0
};

// Run each command in the program
for (var command of this.program) {
// Collect input registers (all but the first)
let args = [];
for (var param of command.params.slice(1)) {
args.push(registers[param] || 0);
}

// Run the function, store in the first param
var result = command.function.apply(this, args);
result = isNaN(result) ? 0 : result;
if (params.clampPerStep) result = constrain(result, 0, 1);

if (params.readonlyXY && (command.params[0] == 'x' || command.params[0] == 'y')) {
// Do nothing, trying to write to x/y
} else {
registers[command.params[0]] = result;
}
}

// Return the color from the r/g/b registers
return [
registers.r,
registers.g,
registers.b
];
}

toString() {
return this.program.map((cmd) => cmd.name + ' ' + cmd.params.join(' ')).join('\n');
}
}