One part of this year's task was to build circuits in ternary logic. The ternary gates have two ins and two outs, with different functions calculated by the two out pins. In contrast to real-world circuits, the circuits to be built here cannot branch any pins at all. So the circuits form a closed loop from the externa input to external output. Gate functions The gate functions computed are known from finite algebra, they can (for instance) be viewed as generalizations of XOR and NAND . The functions are: l' = l + 2r r' = 2 + lr where l and r are the left and right inputs (respectively), and l' and r' are the left and right outputs. All operations are on Z/3Z, that is, addition and multiplication are mod 3. Building circuits The basic technique here is to build components with the following two properties: They output a known sequence the first cycle(s) they output whatever input they had afterwards For example, the circuit



will output a "2" the first cycle, and thereafter whichever input it got the cycle before. There are similar three gate circuits for all three trits. This technique, combined with code that appends circuits (by connecting the output of one to the input of the other) makes it possible to build circuits producing arbitrary output, by placing several such circuits in a sequence. The last circuit decides the first output, and so on. This approach requires a circuit of size 3k for k output trits. Circuit representation The search for circuits is most likely NP-hard (there are many similarities with both the Hamiltonian cycle and Quadratic Assignment Problem). The goal in a three-day competition is therefore is to try to create heuristics to produce decent solutions in reasonable time. The way I chose to represent circuits is by using two arrays. These arrays contain for each gate the value of the outputs, and the indices of the two inputs. The indices of the inputs refer to the output array, since most gates take their input from other gates' output. More specific, the even indices in the output array state[2*i] codes the left output (a state is either "0","1" or "2") of gate number i, and state[2*i+1] contain the right output of gate i. Similarily the inputs are stored in an array, where inp[2i] and inp[2i+1] refers to which output the i:th gate reads its left and right input from, respectively. The external input and output to a circuit are modeled as one extra entry in the input array and one extra state. This means that state[2n] should be set to the external input to the circuit, and inp[2n] to the index of the circuits output, where n refers to the number of gates in the circuit. In contrast what is common in real-world (boolean) circuit optimization, i use indices rather than pointers to the outputs. This makes circuit merging, gate removing and general debugging easier. This way, circuits of n gates can be enumerated by permutations of the inp array. The number of circuits to consider is less than (2n+1)!. Speeding up exhaustive searches In principle the technique used is by exhaustive search of smaller circuits, and I'll try to explain what I did to facilitate that. The most basic approach to generate circuits of a size n is brute force enumeration of all permutations of [ 2n+1 ], and using those as the input indices. This is a decent way to enumerate all circuits of, say, up to 6 gates. This can be done a little better. The first trick is to note that there is no use of searching for gates of, say size 7, that consist of one circuit of size 4 and another of size 3, and no connections between these two. That is the circuit should not contain cycles. The catch is that doing a full connecticity check for every permutation is expensive. A very simple cut technique is to just check for nodes only connected to themselves. That is inp[2*i]/2 == i && inp[2*i+1]/2 == i for each node (whose inputs was changed the last iteration). When this happens the next permutation has to modify either inp[2*i] or inp[2*i+1] . Despite its simplicity, this gives a notable increase in speed. When searching for prefixes of a certain length, it is desired that the circuits have a "memory" of some length m. It is therefore possible to prune the branch once there is a path of length < m between the external input and primary output. One more trick could be to fix the input (or output gate) to some index, say 0. This to avoid searching for that many isomorphic circuits but with reordered gate indices. This should give a huge (factor 2n) speedup? Right? Wrong. This will prune too much. The explanation is that the gates are evaluated from 0 to n, and the backwards arcs (memory) are sometimes necessary to find solutions with the smallest number of gates. Tricks for generating smaller circuits The first trick for producing smaller circuits is to search for circuits able to produce combinations of trits, rather than just one. It turns out that there are six-gate circuits able to output any three trit combination. This already reduces the size of a circuit producing an k-trit output to 2k gates. An example is the following 6-gate circuit:

5R:2R3R0#2L3L, 4R5R0#5L4L, 0L3L0#X0L, 0R4L0#2R0R, 1R5L0#3R1L, 1LX0#4R1R: 2L this outputs "210", and then whatever input it got the from the first cycle and onwards. All 27 three-trit combinations have 6-gate representations. Another trick is to use the input sequence to your advantage. Say that you want to produce the sequence: 11021210112101221 1111 I think many contestants will recognize that, it is a fuel for the simple one-fuel car (no, you don't need to specify the last digit -- whatever it is, it's a valid one). Now, consider the following 6-gate circuit: 5R:4R1L0#5L2R, 5R1R0#0R1R, 3R0R0#4L3L, 2R3L0#3R2L, 2L5L0#X0L, 0LX0#4R1L: 4L This produces the output: 102121011210122121 11110 The only thing missing is a "1" in front! Prepending 2L:1R2R0#2R1R, 2L0R0#X0L, X0L0#1L0R: 1L gives a neat 9-circuit fuel for this car. Curious facts The gate description allows for a "node type" to be specified, presumably to incorporate other gate functions. Looping through the 69647 first node type values revealed no other valid node types. The input sequence from the server is 01202101210201202 01202101210201202... and this sequence repeats ad infinitum (I did not figure this out, someone on irc said it after the competition). Update: I have been told by team THIRTEEN that the input sequence from the server is not repeating, and the information spread on irc is wrong. Instead the server input can be described in blocks of four trits. Let seq[] be an array representation of the server sequence, then for all k in N:

seq[4k] = 0 or 2 (chosen uniformly at random?)

seq[4k+1] = 1

seq[4k+2] = 2 - seq[4k]

seq[4k+3] = seq[k]

This allows the construction of some really tiny fuels for the simple one fuel cars. There were rumours on irc about a highly effective circuit compression scheme requiring only one gate per trit, based not on pure prefixes, but on building blocks that output some sequence, and then the input plus one (or so). Three such building blocks in a row would restore the original sequence. My solvers are available here as linux x86 (core2) 32-bit binaries. An exhaustive search of all 6-gate circuits for producing the key prefix takes 21 seconds. My binaries are not only faster, they are smaller than yours too! ;-) Real world circuit optimization Circuit optimization is a very real problem. The less gates used to produce an equivalent circuit is cheaper to manufacture (not to mention heat and timing disadvantages of larger chips). There is both money and publications in good solutions to this problem Anyone who is interested in trying, see here. I got this data from a friend after explaining ICFP, so I have not yet tried to tune it. It is the plan for today though.