Sat, 31 May 2008
In part 1 I talked about the layer that takes in a stream of bits and spits out a series of nuances. To complete the nuance engine we will need more code on both ends of this. On the bitstream end there will have to be a system managing the streams, performing mutations and crossover, and managing the population. On the other end there has to be a system in place which takes the stream of nuances and translates it into some kind of execution of a program. That's what this episode of the podcast talks about.
We start with a NuanceStream where successive nuances can be consumed, and we want instructions executed. For this I have employed two great features of Java: Annotations and Generics. The result is really nicely pluggable/configurable system where a class corresponds to an instruction set.
The main annotation is @Instruction which marks a method to be a "nuanced instruction", meaning that it takes zero or more nuances and it returns a nuance. What work it actually does is of no concern to the Nuance Engine. It may be making an observation using senses and adjusting memory, or it may be observing memory and actuating a muscle. The whole thing may be trying to solve a math problem, or build a tensegrity, we don't know and we don't have to know.
It may be the case that a particular nuanced instruction has fairly meaningless behavior if there isn't enough smoothness in the nuances that it receives as parameters. If your job is to pick a screen location, a two-bit nuance may not be "precise" enough. This is why the @Instruction annotation requires an integer argument, which indicates the minimum precision of the nuances the instruction requires. This is a silly example of an instruction method:
@Instruction(3)
public Nuance printSomething(Nuance nuance) {
System.out.println("At least three bits precise: "+nuance.value(0,10000));
return howFinished;
}
There are two possible bit strings which cannot reasonably be translated into nuances: "", the empty string, and "0", the string of precision 1. Beyond that there are only two possible values for a precision 2 string, "00" (false) and "10" (true), and for precision 3 there is "000" (false), "010" (maybe), and "100" (true). All of these low-precision nuances would be not terribly "nuanced", so there is an option to use them for something else: controlling the nuance engine. For this, the second annotation @MetaInstruction was introduced. The instruction set can have any one of these seven bit string values "", "0", "00", "10", "000", "010", "100". Higher precision bit strings are always treated as nuances, and these are as well (precision 2 and 3) when there is no meta-instruction assigned to them.
@MetaInstruction("")
public Nuance mainControl(NuanceEngineControl control) {
System.out.println("I can control the engine");
return howFinished;
}
@MetaInstruction("0")
public Nuance lessFrequentControl(NuanceEngineControl control) {
System.out.println("I can control as well");
return howFinished;
}
The NuanceEngine iteself is a generic class, which means that you have a nuance engine for every instruction set. As you can see, the execute instruction takes a nuance stream and an instruction set, which indicates that the bit stream is used to execute instructions.
public interface NuanceEngine<InstructionSet> {
void setTerminationThreshold(Nuance threshold);
void execute(NuanceStream nuanceStream, InstructionSet instructionSet)
throws NuanceException;
}
Which works like this:
NuanceEngine<SimpleInstructionSet> engine = new Engine<SimpleInstructionSet>();
engine.setTerminationThreshold(new FloatNuance(1f));
SimpleInstructionSet instructionSet = new SimpleInstructionSet();
engine.execute(nuanceInputStream,instructionSet);
The type-safety introduced by the generics approach makes for nice refactoring! Also, the tidiness of the annotations makes gluing things together really easy. Your job as evolver is to extend the phenotype by building an instruction set and annotating its nuanced instruction methods and a few meta-instructions methods. The rest is handed over to the blind watchmaker so that bit strings can climb mount improbable and you can maybe find an ancestor's tale about whatever domain you are evolving.
InstructionSetContainer.java - the real workhorse here
I'm almost surprised by how little code is involved with the Nuance Engine so far! It's clearly distinguished in layers, and the only code that breaks a sweat is the InstructionSetContainer, because it has to interpret the annotations and execute the instruction methods according to nuances from a bit stream. I think I'm speaking another language right about now. Hope you're enjoying it. Don't forget that there's also a podcast where I chat about this stuff as the code develops.
Technorati Tags: evolution artificial life nuance darwin binary