Busy Beaver code (version 0.1)

Background

This is the code used to derive the current results. Please note though that this is very much a backend system, and an experimental system rather than a finished product; any pretence to nice interfaces and general useability issues are long gone! There is probably a project in the development of something vaguely usable from this backend for an appropriately qualified enthusiast ...

Please also note that this code is sometimes ugly, and can probaby be optimised and improved in all sorts of ways. Please let me know of any ideas you may have. I will be continually adding both the code and documentation, which will hopefully make things less obscure as time goes on ...

The terminology below is consistent with my papers on the topic. What follows is likely to be nearly incomprehensible if you haven't read at least one of these ...

Software

The program is written in Ciao Prolog, which you will need to download and install before using this code. It may work in other versions of Prolog as well, but I haven't tested this.

Download code

The above link will download a zip file. Put this in a directory somewhere, unzip it, and once you have Ciao Prolog running, you are ready to go.

Getting Started

This implementation is written in Ciao Prolog. There are six files:
search.pl:Routines for generating and filtering Turing machines of a given number of states and symbols.
analyse.pl: Routines for analysing machines. This includes the inductive non-termination prover.
emulate.pl: Routines for emulating machines, including naive, compact, and hypothetical modes.
convert.pl: Routines for various conversions between machines and representations (including graphical output for machines).
machines.pl:Data for a number of machines, including some known 5-state and 6-state "monsters".
engine.pl:File which loads all the others; this is the one to load into the Prolog system to make things run.

To start things up, start Ciao Prolog and load the file engine.pl, as below.

?- ensure_loaded('engine.pl').

Depending on your system, you may need to use an absolute path name, for example something like 'D:/busybeaver/engine.pl' on a Windows system.

Thss will load and compile several files. You may find that there are some warning messages; you can safely ignore these (these are basically comments on my coding style :-)

You can now use the program by making queries to the Prolog shell.

Emulating a particular machine.

This can be done in two ways. The first way is the naive one, ie to execute each step in exactly the way that the Turing machine would. To do this, a query such as the following should be used.

?- m7(5,M,_,_), emulate(M, 10000000, [naive], Ones, Hops, Status, Outputs).

This runs the machine M (which is a 5-state machine of productivity 1,471 which terminates in 2,358,064 steps) for a maximum of 10 million steps in naive mode, and if successful returns the values Ones (the productivity), Hops (the number of steps), Status (halts, going, etc.) and Outputs (sundry information, generally of interest only to hard-core specialists).

The second way is an implementation of Marxen and Buntrock's macro machines. Roughly speaking, the tape is divided into sections of length k, and each "macro" step is found by running the machine on a tape of length k. As many of the monster machines repeat such macro steps many times, this can result in a significant speed improvement over the naive machine. We also use the tape repetition counts of Marxen and Buntrock, which mean that a string of symbols such as 1111111 is represented as 17, which in our sytem is represented as tape(1,7).

Currently, k must be fixed in advance, and so a small amount of experimentation may need to be done to find the right value of k for each machine. I am currently working on an "adaptive" mode, which will overcome this limitation.

For example, to run the m7 machine above with k = 2, one would use

?- m7(5,M,_,_), emulate(M, 10000000, [comp(2)], Ones, Hops, Status, Outputs).

The general form of a call to emulate is

emulate(Machine, Bound, Options, Ones, Hops, Status, Outputs)

Bound can be either given explicitly, or as mill(N) for N million, or bill(N) for N billion (for a particular natural number N). Options is a list of zero or more of the following:

OptionDescription
naive Use naive emulation
comp(K) Use k-step emulation [for a given natural number K)]
flex Run naively for a while, and then switch to the value of K for which comp(K) gives the shortest representation (for K &le 4).
trace Print out the machine configuration for every step during execution.
pivotal Print out the machine configuration for only when the machine is in the pivotal state. This is very useful for establishing inductive conjectures by hand.
trace(Start, S) Print out the machine configuration at step Start, and then every S steps after that.
hist Keep a history of execution. This is used in the analysis phase for establishing inductive conjectures.
loop Check for loops. This requires keeping a history of execution.
watch(State, I) Print out the machine state only when the machine is in state State with input I.
max Keep track of the maximum productivity during execution. This can be much greater than the final productivity for some 5-state monsters.

Hence to run the above machine for up to 10 billion steps with a macro machine of size 3 whilst printing out the machine configuration whenever the machine is in state b with input 1, we could use

?- m7(5,M,_,_), emulate(M, bill(10), [comp(3),watch(b,1)], Ones, Hops, Status, Outputs).

Analysing a particular machine

Analysing a machine include emulation, but also some techniques, including establishing non-termination by various heuristics. This include:

Hence to analyse the above machine, we would use the query

?- m7(5,M,_,_), analyse(M, Ones, Hops, Status).

Important Note:There are some machines, unfortunately, for which this routine does not terminate. These are of dimension 8 or more, and are relatively few in number, but still very annoying! I am working some extensions to the hypothetical engine which I hope will entail the termination of all machines of dimension 8 or less.

Searching a class of machines

In order to determine busy beaver or placid platypus values, it is necessary to search through a potentially very large number of machines. Our implementation uses the standard tree normal form generation techniques (see Pascal Michel's web page or the paper by Marxen and Buntrock for a description of this). In addition, we apply tests for loops, returning the tape to blank and some redundancy checks before considering the machine worthy of analysis. In particular, we keep all machines that pass these simple checks (as we are interested in the space of machines itself, and not just one extreme value such as the busy beaver function).

For searches of dimension 8 or less (ie the number of states in the machine multiplied by the number of tape symbols is 8 or less), the machines generated can be kept in one file. For larger searches (5 states 2 symbols, 3 states 3 symbols, and 2 states 5 symbols), there are millions of machines, and hence the machines have to be stored in several files. We have not attempted any searches of dimension more than 10.

To generate and analyse all machines with say 3 states and 2 symbols, use

?- search(3, 2).

This will produce results of the form below in a file named busybeaver32.pl in the directory specified by the beaverbase predicate.

...
bb(3,[t(a,0,1,r,b),t(b,0,1,l,c),t(c,1,1,r,c),t(c,0,1,l,a),t(a,1,0,l,a),t(b,1,1,r,h)],0,0,meander).
bb(3,[t(a,0,1,r,b),t(b,0,1,l,c),t(c,1,1,r,c),t(c,0,1,l,a),t(a,1,1,l,a),t(b,1,1,r,h)],4,9,halts).
bb(3,[t(a,0,1,r,b),t(b,0,1,l,c),t(c,1,1,r,c),t(c,0,1,l,a),t(a,1,0,r,a),t(b,1,1,r,h)],2,9,loops(dizzyduck(7))).
bb(3,[t(a,0,1,r,b),t(b,0,1,l,c),t(c,1,1,l,c),t(c,0,1,r,a),t(a,1,1,r,a),t(b,1,1,r,h)],0,1,loops(induction(p([],8),pos(1,2),state([1,tape([1,1,1],v(_46542,0,1,1,3))],b,l,[0]),state([1,1,1,1,tape([1,1,1],v(_46542,0,1,1,3))],b,l,[0])))).
...

The most important piece of data is the final argument, which is the analysis status. If this is "halts", the previous two arguments are the productivity and number of steps to termination respectively. In other cases, they are generally meaningless. Note that the last case also records the successful inductive conjecture, which in this case was

(111)m 1 {b} 0 -> (111)m 1111 {b} 0

Some summary information can also be found at the of the file.

This process also generates the file busybeaver32-going.pl, which contains only those machines whose status was unable to be determined. All these are present in the original; this has often been handy for identifying problem cases.

One "sanity check" that is possible for searches of small dimension (certainly 6 or less and possibly up to 8) is to freely generate all machines, and then compare the results of the analysis of this case with the previous one. For example, when searching the 2-state 2-symbol case (the simplest by far!), we find that the restricted method (ie the one described above with filters for machines which make the tape blank, or loop) generates 12 machines, but the free one generates 64 machines. By comparing these two lists, we can conclude that at least in this case, our pruning heuristics are correct.

To do this, we use the query

?- search(2,2,free).

The results file is then busybeaver22free.pl.

For searches of dimension more than 8, the overall results are split into many files. For example, the search for 5-state 2-symbol machines results in the files busybeaver521.pl, busybeaver522.pl, busybeaver523.pl, etc., all limited to contain no more than 500,000 machines each. The problem cases are written to one file, in this case busybeaver52-going.pl.

Sometimes it is useful to perform one search, and use the results to improve the search heuristics. Rather than re-generate many known results, it is possible to refine a particular output file (which is the reason for the rather esoteric format of the results files).

For example, let us say that we have generated the output file busybeaver42.pl, and have since made some changes to the analysis method, which we wish to try out. We could run the process all over again; or we could use the following query

?- refineone('C:/busybeaver/busybeaver42.pl', 'C:/busybeaver/busybeaver42-refined.pl', 'C:/busybeaver/busybeaver42-going.pl').

This will take the machines in busybeaver42.pl, and apply the new analysis only to those which were not classified by the earlier analysis. Hence previous results, such as machines which terminate or loop, will not be re-computed; only the ones that defeated the previous analysis will be re-tried. The output is put in the file busybeaver42-refined.pl; any cases still undetermined by the new analysis are put in busybeaver42-going.pl.

Compiling Particular Machines

The emulate routine described above is a general one; it will take any machine (in the right format) and emulate it. Sometimes for some monster machines it is more appropriate (purely on efficiency grounds) to compile these particular machines, rather than emulate them. In other words, we generate a particular program from the machine specification, rather than use the machine specification as the input to a program which is equivalent to a universal Turing machine.

The particular implementation here is rather simplistic version of the macro machines of Marxen and Buntrock. The translator produces a number of Prolog clauses, equivalent to the original machine, which can then be loaded into the Prolog system and the machine executed. For example, to compile 3-state 3-symbol machine (recently discovered by the Ligockis), use the following query.

compile("ligocki33", 2, [t(a,0,1,r,b),t(a,1,2,r,c),t(a,2,1,l,a),t(b,0,2,l,a),t(b,1,1,r,b),t(b,2,1,r,h),t(c,0,2,r,b),t(c,1,2,r,a),t(c,2,1,l,c)]).

The second argument specifies the macro size; the first the name of the file in which the output will be written; the third is the machine itself.

Once the file has been generated, load it into the same Prolog shell as the existing code. You can do this as below.

?- ensure_loaded('ligocki33.pl').

You can then execute the machine with the query below.

?- go_ligocki33.

There are many improvements that can be made here! One that I am planning to do is to make this into a "stand-alone" compilation, rather than one that gets loaded back into the Prolog interpreter. More ambitiously, one could compile the machine into C or perhaps Mercury, in the hope of a quantum leap in performance. However, that is a task for later ...