Mahler-rep: A self-replicating Universal Turing Machine built with the Devore ruleset

For discussion of other cellular automata.
Post Reply
Letters
Posts: 6
Joined: October 15th, 2019, 4:50 am

Mahler-rep: A self-replicating Universal Turing Machine built with the Devore ruleset

Post by Letters » October 15th, 2019, 4:57 am

https://github.com/ejmahler/mahler-replicator

I'm very interested in the idea of a computer built out of cellular automata, so it's been my pleasure to work on this project.

I originally started by making tweaks to the "Devore-rep" replicator packaged with Golly. Over time, I found myself making more and more substantial changes until I had completely redesigned the machine. As of this writing, it finishes a copy of itself 160x faster than Devore-rep!

"Mahler-rep.rle" has the replication ready to go if you want to test it out. If you'd like to experiment with the machine, "Mahler-body.rle" has a copy of the machine without any program encoded into it, and "make-Mahler-tape.py" contains a program and compiler for the machine.

Image

This screenshot shows the whole machine: In the lower right, we have the program for the machine, encoded as binary. The four things poking out are four parallel read heads which will read four bits of the program at once. In the upper right, we have the "data tape", which can be thought of as the machine's main memory. In the upper left, extending out of the frame is the "construction arm", which can be maneuvered to print out arbitrary patterns, including a copy of the machine.

Please let me know what you think!


User avatar
Tim Hutton
Posts: 64
Joined: May 20th, 2010, 7:30 am
Contact:

Re: Mahler-rep: A self-replicating Universal Turing Machine built with the Devore ruleset

Post by Tim Hutton » October 15th, 2019, 9:22 am

Hi Elliott, this is fantastic work. It's great to see a Devore-style machine replicate so quickly.

Thanks for the extensive documentation on the changes you made to the machine and the script and the rules.

We should definitely include this pattern in Golly.

Tim

Letters
Posts: 6
Joined: October 15th, 2019, 4:50 am

Re: Mahler-rep: A self-replicating Universal Turing Machine built with the Devore ruleset

Post by Letters » October 16th, 2019, 1:17 am

PHPBB12345 wrote:
October 15th, 2019, 5:42 am
How to make Hutton32-like LoopRep?
I honestly don't know too much about how it works. My gut says that it's possible and would be faster the replicator I've shared, but I don't have the slightest idea how to start, what kind of circuity would be needed, etc.

I guess the core would be a loop tens of thousands of cells long full of extend, mark, retract etc sequences, plus a switch on the end? the switch is directed one way to power a construction arm that builds the next loop+circuitry, and when construction is done, the switch is flipped to a different circuit that fills the new loop. All of the signals in the loop would have to cycle around the loop twice per replication, once to do the construction, and then once to fill the newly constructed replicator's loop.

The biggest unknown I have is how to decide when to inject the sheath for the newly constructed machine and begin filling its loop. I guess you could have a discriminator for 6 signals, and behind, that, count up to whatever power of 2 is closest to the number of 6's we expect our construction loop to contain? (I suggest powers of 2 because circuits that count powers of 2 are very compact, but other numbers work i guess) And pad the loop signal with 6's out to that power of 2? It would be fine to send the 6's down the construction arm, since as long as we set it up right, all they'll do is toggle the end of the sheath on and off.

As I typed this out, I realized that setup won't work - an extend signal requires a total of 8 cells of space, so if we're putting that into the loop, then we need to make the loop 8 units longer. But in order to make the loop 8 units longer, we'd have to include at least 4 extend signals, plus at least 4 Mark signals, which will require their own space in the loop.

So I still think it's possible, but it's more complicated than I thought.
Tim Hutton wrote:
October 15th, 2019, 9:22 am
Hi Elliott, this is fantastic work. It's great to see a Devore-style machine replicate so quickly.

Thanks for the extensive documentation on the changes you made to the machine and the script and the rules.

We should definitely include this pattern in Golly.

Tim
Thanks. I don't know if you remember, but I emailed you some 8-9 years ago about this exact project. At the time, I might have had it going 5x faster.

Question for you: How much of Devore-rep did you actually make? Did you have the ruleset and machine and you just figured out the program? Did you have the ruleset, but had to create the machine from scratch? Even now, knowing the whole machine and program top to bottom, it boggles my mind that anyone could invent that pattern and ruleset from scratch.

User avatar
Tim Hutton
Posts: 64
Joined: May 20th, 2010, 7:30 am
Contact:

Re: Mahler-rep: A self-replicating Universal Turing Machine built with the Devore ruleset

Post by Tim Hutton » October 16th, 2019, 9:41 am

Letters wrote:
October 16th, 2019, 1:17 am
Question for you: How much of Devore-rep did you actually make? Did you have the ruleset and machine and you just figured out the program? Did you have the ruleset, but had to create the machine from scratch? Even now, knowing the whole machine and program top to bottom, it boggles my mind that anyone could invent that pattern and ruleset from scratch.
In April 2009, while working on the Codd replicator I made contact with Ron Hightower and John Devore. Ron dug out the Devore ruleset and the body and some sample tapes and ported them to Golly. He found some physical notes on the machine including the attached image and we worked out what the program tape encoding was.

There followed some discussion of how to get the machine to copy itself and in May Ron sent the attached SLIM.rle, which implemented one approach to self-replicate in a slightly modified version of John's original design (and includes two hidden messages). I then wrote the Python script that's now in Golly and came up with a more efficient way to replicate, and got it running on John's original machine.

On the mind-bogglingness: Devore's machine inherited much (construction arms, flowing command sequences, static tapes with read-heads, encoders, decoders) from Codd's design, which inherited them from von Neumann's original work. To me the amazing thing is that von Neumann not only had to *imagine* all this (because there were not yet any computers to run it on) but had to pretty much invent cellular automata to do so. The conceptual leaps are dizzying.
Attachments
overview.jpg
overview.jpg (56.08 KiB) Viewed 3814 times
SLIM.rle
The first version of a Devore self-replicating machine, by Ron Hightower.
(116.04 KiB) Downloaded 142 times

Letters
Posts: 6
Joined: October 15th, 2019, 4:50 am

Re: Mahler-rep: A self-replicating Universal Turing Machine built with the Devore ruleset

Post by Letters » October 16th, 2019, 11:50 pm

Thanks for sharing! That's a pretty fascinating image. I did notice that in devore-rep, each of the coder circuits in the top left appears to have been copy+pasted into other sections exactly as-is, as opposed to merging circuits where possible, compacting things, etc. All of the equivalent circuits in mahler-rep are tailor-made for each location they appear in, and it took hours and hours of work to make each one work correctly, to get timings correct, etc.

So it totally makes sense that without access to rapid iteration like I had, the safest way to get something like "extend right" working is to time it out once, then paste it to everywhere that needs it. I'm assuming that somewhere near that image is another image showing the layout of "XR", and the layout of "MRM", etc?

I find it interesting that the section that reads the label tape while jumping is unnamed.

And I find it interesting that what I consider to be the most complicated part of the machine, the tangle of circuits surrounding MRS, isn't broken down into anything smaller. I bet I could come up with a set of circuits like that, but not without 100+ cycles of trial and error and hours/days of incremental progress. It's very impressive.

And thanks also for sharing that SLIM replicator. It definitely looks familiar - it looks like the only difference is that SLIM omits an instruction that both the diagram and devore-rep include.

Letters
Posts: 6
Joined: October 15th, 2019, 4:50 am

Re: Mahler-rep: A self-replicating Universal Turing Machine built with the Devore ruleset

Post by Letters » October 17th, 2019, 2:22 am

PHPBB12345 wrote:
October 15th, 2019, 5:42 am
How to make Hutton32-like LoopRep?
This post got me curious, so I looked more into how hutton replicator works, and came up with this:

devore-loop-rep.rle

Code: Select all

x = 38, y = 11, rule = Devore2
.26A2.5A2.2A$.A24.A2.A3.A3.A$.A24.A2.A2.3A2.A$.A3.5A2.5A2.5A2.A2.A3.A
3.A$.A3.A3.A2.A3.A2.A3.A2.8A3.A$6A3.4A3.4A3.4A10.A$.A3.A3.A2.A3.A2.A
3.A13.A$.A3.5A2.5A2.5A2.12A$.A24.A$.A24.A2.A$.29A!
inject_counter.png
inject_counter.png (5.06 KiB) Viewed 3750 times
make-devore-loop.py

Code: Select all

extend =        [7,6]
extend_left =   [4,4,5,6]
extend_right =  [5,5,4,6]
retract =       [4,5,6,6]
retract_left =  [5,6,6,6]
retract_right = [4,6,6,6]
mark =          [7,6,4,5,7,6]
inject =        [7,7]

signal_cells = {
    4: [4,0,1,1,1],
    5: [5,0,1,1], # if the sequence ends up ever being passed through an "on" switch, we need an extra '1' cell at the end of this list, for extra buffer
    6: [6,0,1,1],
    7: [7,0,1,1],
}

sequence_loop_delay = 88

import golly

rect = golly.getrect()
x = rect[0]
y = rect[1]
width = rect[2]
height = rect[3]

instruction_sequence = [extend, extend, extend, extend]
for row in reversed(xrange(y, y+height)):
	# find the rightmost 1 bit in this row 
	far_right = x+width-1
	while golly.getcell(far_right,row) != 1:
		far_right -= 1
		if far_right<x:
			golly.exit('Empty rows forbidden: '+str(row))

	# begin a new row
	instruction_sequence.extend([extend, extend_right, extend, extend])

	# loop over each cell in the row and add mark instructions for cells that are marked
	for col in xrange(x, far_right + 1):
		instruction_sequence.append(extend)
		if golly.getcell(col, row) == 1:
			instruction_sequence.extend([extend_right, mark, retract_right])

	# retract as far as we extended
	instruction_sequence.extend([retract]*(far_right - x + 3))
	instruction_sequence.append(retract_right)

#move the construction arm to the injection point and inject
instruction_sequence.extend([retract]*8)
instruction_sequence.extend([extend_right, extend, extend, inject])


#now that we have the full list of instructions, conver them down to a list of signals, and then down to a list of cells
signal_sequence = [signal for instruction in instruction_sequence for signal in instruction]
cell_sequence = [cell for signal in signal_sequence for cell in signal_cells[signal]]
cell_sequence += [1] * sequence_loop_delay

# our cell sequence must have even length, since we're going to be creating a loop
if len(cell_sequence) % 2 == 1:
	cell_sequence.append(1)
	pass

# we're going to write the cell sequence backwards in a loop. first we're going to write the final "small leg".
remaining_cells = len(cell_sequence)
for (y, cell) in enumerate(reversed(cell_sequence[-4:])):
	golly.setcell(-1, y, 2)
	golly.setcell( 0, y, cell)
	golly.setcell( 1, y, 2)
	remaining_cells -= 1

golly.setcell( 0, y+1, 2)

# write the final long leg
leg_length = len(cell_sequence) // 2 - 3
for (x, cell) in enumerate(reversed(cell_sequence[remaining_cells - leg_length:remaining_cells])):
	golly.setcell(x+1,2, 2)
	golly.setcell(x+1,3, cell)
	golly.setcell(x+1,4, 2)
	remaining_cells -= 1

golly.setcell(x+2,3,2)

# write the first short leg
for (y, cell) in reversed(list(enumerate(cell_sequence[remaining_cells - 3:remaining_cells]))):
	golly.setcell(leg_length - 1, y, 2)
	golly.setcell(leg_length,     y, cell)
	golly.setcell(leg_length + 1, y, 2)
	remaining_cells -= 1

golly.setcell(leg_length, -1, 2)

# write the first long leg
for (x, cell) in reversed(list(enumerate(cell_sequence[:remaining_cells]))):
	golly.setcell(x+1, -1, 2)
	golly.setcell(x+1,  0, cell)
	golly.setcell(x+1,  1, 2)
	remaining_cells -= 1

# write a stub for the construction arm
golly.setcell( 0, -1, 1)
golly.setcell(-1, -1, 2)
golly.setcell( 0, -2, 2)

golly.show("Instruction sequence count: {}, Signal sequence count: {}, cell sequence count: {}".format(len(instruction_sequence), len(signal_sequence), len(cell_sequence)))
It's not a full replicator. The script will take whatever pattern is in the file and create a loop to construct it, and then inject an initialization signal into it, which in this case is 2 7's in a row.
inject_counter_running.png
inject_counter_running.png (4.52 KiB) Viewed 3750 times
All the pattern does is filter the loop signals for the injection signal, and count how many injection signals it's seen. After seeing 8 injection signals, it turns on a gate that stops the machine from listening to input. After this, the other big missing piece of the puzzle is how to construct the new loop.

It's pretty easy to make a circuit that will spit out extend signals in an infinite loop, but I'm not sure how to count out how many extends to do before stopping it and turning around to close the loop. hutton-replicator.rle does it by counting out loop iterations - after N iterations, turn around and start extending the other way. After N more iterations, stop extending altogether and let the old loop fill the new loop.

It looks like N is 3 for hutton-replicator? That adds up to a total of 8 total iterations. If we got the same thing working on a pattern like this, I estimate the circuitry involved might triple the size of the loop from 25k to 75k. 75k * 8 = 600,000 cycles to complete a replication. Not bad.

User avatar
Redstoneboi
Posts: 433
Joined: May 14th, 2018, 3:57 am

Re: Mahler-rep: A self-replicating Universal Turing Machine built with the Devore ruleset

Post by Redstoneboi » October 17th, 2019, 6:52 am

I must say I am very much fascinated at the completely genius way some of your components abuse the rule's boundaries.
I like it.
Your replicator is the best example of how I would've optimized the Devore rep, if I was as capable of understanding others' work as you!
Using outside-the-box design elements by pushing the limitations of a Codd/Devore rule is a difficult task, for me, at least.

But anyway,
Let's get that replicator loopin'

EDIT 1
Aha!
Inspired by my own Flow6 Reploopcator, I discovered a way to extend both tentacles at once and connect them into a loop at the end, requiring only 1 phase for the loop building mechanism:

Code: Select all

x = 61, y = 25, rule = Devore2
15.B3.B.3B.3B.3B$15.2B.2B.B3.B4.B$15.B.B.B.2B2.2B3.B$15.B3.B.B3.B4.B$
15.B3.B.3B.3B2.B2$17.12B$17.B10.B2$16.44B$16.B2A.D2A.E2A.E2A.2A.2A.F
2A.G2A.F2A.G9AB$16.44B2$4.56B$4.B2A.F2A.G2A.F2A.E2A.D2A.D2A.2A.2A.F2A
.G2A.F2A.G9AB$4.56B2$5.B10.B18.B6.B$5.12B18.8B2$2B2.2B2.3B.2B3.2B.3B
6.2B2.3B3.3B.B.B.3B$B.B.B.B2.B2.B.B.B3.B8.B.B.B.B3.B3.B.B2.B$2B2.2B3.
B2.B.B.B.B.2B7.2B2.3B3.2B3.B3.B$B.B.B.B2.B2.B.B.B.B.B8.B3.B.B3.B3.B.B
2.B$2B2.B.B.3B.2B3.2B.3B6.B3.3B3.3B.B.B2.B!
Now, the coders for that sequence should be relatively simple.
Not sure which way the loop should go, though.

EDIT 2
There we go!
I constructed my own version of a double-7 (meta) signal detector, thankfully we won't be using a double-7 in normal construction, proven because the entire construction toolkit is used as the input, in segments; the only time a double-7 appears is at the end, which is the meta signal.

Code: Select all

x = 13, y = 60, rule = Devore2
3.5A4.A$3.A3.A4.A$3.A3.A4.A$3.A2.2A4.A$3.A2.4A2.A$3.A2.A5.A$3.A2.A5.A
$3.10A$3.A$3.A$3.5A$3.A3.A$3.A3.A$3.A2.3A$3.A3.A$3.A3.A$3.5A$6.BFB$6.
B.B$6.BAB$6.BAB$6.BAB$6.BAB$5.2BGB$4.B2A.B$4.BF2B$4.B.2AB$5.2BGB$4.B
2A.B$4.BF2B$4.B.2AB$.6BDB$B2A.E2A.B$BF6B$B.2AF.2AB$.6BDB$B2A.E2A.B$BG
6B$B.2AF.2AB$.6BDB$B2A.D2A.B$BE6B$B.2AF.2AB$.6BEB$B2A.E2A.B$BD6B$B.2A
F.2AB$.6BEB$B2A.F2A.B$BF6B$B.2AF.2AB$.6BDB$B2A.F2A.B$BF6B$B.2AF.2AB$.
6BGB$4.B2A.B$4.BG2B$4.B.2AB$5.4B!
Now the phase changing circuits can be built.
c(>^w^<c)~*
This is 「Fluffy」
「Fluffy」is my sutando.
「Fluffy」has the ability to engineer r e p l i c a t o r s.
「Fluffy」likes to watch spaceship guns in Golly.
「Fluffy」knows Natsuki best girl.

Letters
Posts: 6
Joined: October 15th, 2019, 4:50 am

Re: Mahler-rep: A self-replicating Universal Turing Machine built with the Devore ruleset

Post by Letters » October 18th, 2019, 5:03 am

Extending both ends of the loop at once is a great idea. It inspired me make an attempt at it, and I've made a lot of progress.
looprep.png
looprep.png (75.19 KiB) Viewed 3680 times
devore-loop-rep.rle

Code: Select all

x = 63, y = 38, rule = Devore2
9A$A7.A$A7.A$A4.A2.A16.5A$9A16.A3.A11.5A$A4.A2.8A9.A2.2A5.4A2.A3.A$A
7.A6.A9.A2.5A2.A2.4A3.6A$A7.A6.A9.4A3.A2.A2.A2.A3.A3.2A$A7.5A2.A12.A
2.2A2.A2.A2.5A4.A$A7.A2.5A12.A2.5A2.A11.A8.A$A7.A2.A16.4A3.4A11.A8.A$
A7.A2.A16.A14.5A2.A7.2A$A7.A15.2A2.A14.A3.A2.10A$A2.3A2.A15.A3.7A5.A
2.A3.A11.A$A2.A4.17A3.A4.2A2.4A2.A2.2A10.2A$A2.A7.A6.A5.5A5.A2.A2.A2.
A2.7A5.5A$A2.A7.A6.A5.A3.A2.A2.A2.A2.7A4.8A$A2.A2.2A2.2A2.A2.2A2.A2.A
3.4A2.7A10.A2.A3.A$A2.A3.A2.5A2.5A2.A3.A18.2A2.A2.A3.A$A2.A3.A2.A6.A
6.2A2.A18.A3.A2.A2.2A2.2A$A2.5A2.A6.A6.A3.17A2.A3.A2.A2.5A$A6.A2.8A2.
2A2.A8.A10.A2.A3.4A2.A$A6.A9.A3.A2.A8.A10.4A9.A$A2.2A2.A9.A3.A2.A8.A
23.A$A2.A3.A9.8A5.5A19.A2.A$A2.A3.A9.A12.A3.A6.4A6.4A2.A$A2.15A9.4A3.
A6.A2.5A2.A2.A2.A$A3.A3.2A16.2A2.5A6.A2.A2.2A2.A2.A2.A$A7.A11.8A3.A9.
A2.A3.7A2.A$A3.A2.2A11.A2.A7.A6.4A2.A9.A2.A$18A2.A2.A7.A2.5A2.4A8.2A
2.A$A3.A11.2A2.4A7.A2.A3.A14.5A$A16.A2.A2.A7.A2.A2.2A14.A$2A2.A12.A5.
A7.A2.A2.17A$5A12.A5.A7.7A14.3A$17.A5.A30.A$17.A4.2A30.A$17.38A!
make-DevoreLoop-tape.py

Code: Select all

extend =        [7,6]
extend_left =   [4,4,5,6]
extend_right =  [5,5,4,6]
retract =       [4,5,6,6]
retract_left =  [5,6,6,6]
retract_right = [4,6,6,6]
mark =          [7,6,4,5,7,6]
inject =        [1,1,1,1,7,7]

signal_cells = {
    1: [1],
    4: [4,0,1,1,1],
    5: [5,0,1,1], # if the sequence ends up ever being passed through an "on" switch, we need an extra '1' cell at the end of this list, for extra buffer
    6: [6,0,1,1],
    7: [7,0,1,1],
}

sequence_loop_delay = 64

import golly

rect = golly.getrect()
x = rect[0]
y = rect[1]
width = rect[2]
height = rect[3]

instruction_sequence = [extend, extend, extend, extend]
for row in reversed(xrange(y, y+height)):
	# find the rightmost 1 bit in this row 
	far_right = x+width-1
	while golly.getcell(far_right,row) != 1:
		far_right -= 1
		if far_right<x:
			golly.exit('Empty rows forbidden: '+str(row))

	# begin a new row
	instruction_sequence.extend([extend, extend_right, extend, extend])

	# loop over each cell in the row and add mark instructions for cells that are marked
	for col in xrange(x, far_right + 1):
		instruction_sequence.append(extend)
		if golly.getcell(col, row) == 1:
			instruction_sequence.extend([extend_right, mark, retract_right])

	# retract as far as we extended
	instruction_sequence.extend([retract]*(far_right - x + 3))
	instruction_sequence.append(retract_right)

#move the construction arm to the injection point and inject
instruction_sequence.extend([retract]*3)
instruction_sequence.extend([extend_right, extend, extend, inject])


#now that we have the full list of instructions, conver them down to a list of signals, and then down to a list of cells
signal_sequence = [signal for instruction in instruction_sequence for signal in instruction]
cell_sequence = [cell for signal in signal_sequence for cell in signal_cells[signal]]
cell_sequence += [1] * sequence_loop_delay

# our cell sequence must have even length, since we're going to be creating a loop
if len(cell_sequence) % 2 == 1:
	cell_sequence.append(1)

# we're going to write the cell sequence backwards in a loop. first we're going to write the final "small leg".
remaining_cells = len(cell_sequence)
for (y, cell) in enumerate(reversed(cell_sequence[-4:])):
	golly.setcell(-1, y, 2)
	golly.setcell( 0, y, cell)
	golly.setcell( 1, y, 2)
	remaining_cells -= 1

golly.setcell( 0, y+1, 2)

# write the final long leg
leg_length = len(cell_sequence) // 2 - 3
for (x, cell) in enumerate(reversed(cell_sequence[remaining_cells - leg_length:remaining_cells])):
	golly.setcell(x+1,2, 2)
	golly.setcell(x+1,3, cell)
	golly.setcell(x+1,4, 2)
	remaining_cells -= 1

golly.setcell(x+2,3,2)

# write the first short leg
for (y, cell) in reversed(list(enumerate(cell_sequence[remaining_cells - 3:remaining_cells]))):
	golly.setcell(leg_length - 1, y, 2)
	golly.setcell(leg_length,     y, cell)
	golly.setcell(leg_length + 1, y, 2)
	remaining_cells -= 1

golly.setcell(leg_length, -1, 2)

# write the first long leg
for (x, cell) in reversed(list(enumerate(cell_sequence[:remaining_cells]))):
	golly.setcell(x+1, -1, 2)
	golly.setcell(x+1,  0, cell)
	golly.setcell(x+1,  1, 2)
	remaining_cells -= 1

# write a stub for the construction arm
golly.setcell( 0, -1, 1)
golly.setcell(-1, -1, 2)
golly.setcell( 0, -2, 2)

golly.show("Instruction sequence count: {}, Signal sequence count: {}, cell sequence count: {}".format(len(instruction_sequence), len(signal_sequence), len(cell_sequence)))
These two together *almost* make a complete replicator. The loop length is 92k, and it runs in 6 loop iterations, so the total replication time is around 550k cycles. The only problem now is that each new loop is 18 cells longer than the last. I was hoping by messing with the sequence_loop_delay variable i could grow or shrink it, but it appears to be a constant: no matter how i change it, the error is 18 cells. So we need to figure out how to make it extend 18 cells fewer, meaning 9 fewer extend commansd, which means either starting the extend generator 8 * 9 = 72 cycles later, or shutting it off 72 cycles earlier. Adding a 72-cycle delay into the middle of all of this would be pretty painful, so maybe there's another way?

After that, the only thing left would be to make sure the python script prints out the loop in the same shape as the copied loop, and making it print out the same stub that copied loops start with, and we'll have a devore loop replicator!

User avatar
Redstoneboi
Posts: 433
Joined: May 14th, 2018, 3:57 am

Re: Mahler-rep: A self-replicating Universal Turing Machine built with the Devore ruleset

Post by Redstoneboi » October 18th, 2019, 8:57 am

Letters wrote:
October 18th, 2019, 5:03 am
These two together *almost* make a complete replicator. The loop length is 92k, and it runs in 6 loop iterations, so the total replication time is around 550k cycles. The only problem now is that each new loop is 18 cells longer than the last. I was hoping by messing with the sequence_loop_delay variable i could grow or shrink it, but it appears to be a constant: no matter how i change it, the error is 18 cells. So we need to figure out how to make it extend 18 cells fewer, meaning 9 fewer extend commansd, which means either starting the extend generator 8 * 9 = 72 cycles later, or shutting it off 72 cycles earlier. Adding a 72-cycle delay into the middle of all of this would be pretty painful, so maybe there's another way?
How about using those extensions?
I tried adding an extension trombone slide, but this doesn't solve anything cause the loop is still longer.

Code: Select all

x = 88, y = 50, rule = Devore2
12A$A2.A7.A$A2.A7.A$A2.A4.A2.A16.5A$A2.9A16.A3.A11.5A$A2.A4.A2.8A9.A
2.2A5.4A2.A3.A$A2.A7.A6.A9.A2.5A2.A2.4A3.6A$A2.A7.A6.A9.4A3.A2.A2.A2.
A3.A3.2A11.4A$A2.A7.5A2.A12.A2.2A2.A2.A2.5A4.A14.A$A2.A7.A2.5A12.A2.
5A2.A11.A8.A$A2.A7.A2.A16.4A3.4A11.A8.A$A2.A7.A2.A16.A14.5A2.A7.2A$A
2.A7.A15.2A2.A14.A3.A2.10A$A2.A2.3A2.A15.A3.7A5.A2.A3.A11.A24.B$A2.A
2.A4.17A3.A4.2A2.4A2.A2.2A10.2A2.A21.B$A2.A2.A7.A6.A5.5A5.A2.A2.A2.A
2.5A7.5A2.2A17.B$A2.A2.A7.A6.A5.A3.A2.A2.A2.A2.7A2.10A25.B$A2.A2.A2.
2A2.2A2.A2.2A2.A2.A3.4A2.7A8.A2.A5.A25.B$A2.A2.A3.A2.5A2.5A2.A3.A20.A
2.A5.A25.B$A2.A2.A3.A2.A6.A6.2A2.A20.A2.A4.2A4.4A17.B$A2.A2.5A2.A6.A
6.A3.19A2.A2.A4.4A2.A20.B$A2.A6.A2.8A2.2A2.A8.A15.4A4.A2.A23.B$A2.A6.
A9.A3.A2.A8.A23.A26.B$A2.A2.2A2.A9.A3.A2.A8.A23.A26.B$A2.A2.A3.A9.8A
5.5A19.A2.A26.B$A2.A2.A3.A9.A12.A3.A6.4A6.4A2.A26.B$A2.A2.15A9.4A3.A
6.A2.5A2.A2.A2.A26.B$A2.A3.A3.2A16.2A2.5A6.A2.A2.2A2.A2.A2.A5.A20.B$A
2.A7.A11.8A3.A9.A2.A3.7A2.A2.4A20.B$A2.A3.A2.2A11.A2.A7.A6.4A2.A9.A2.
A26.B$A2.18A2.A2.A7.A2.5A2.4A8.2A2.A26.B$A2.A3.A11.2A2.4A7.A2.A3.A14.
5A26.B$A2.A16.A2.A2.A7.A2.A2.2A14.A30.B$A2.2A2.A12.A5.A7.A2.A2.17A30.
B$A2.5A12.A5.A7.7A14.3A29.B$A19.A5.A30.A29.B$A19.A4.2A30.A29.B$A19.
38A29.B$A86.B$A86.B$63A24.B$62.A24.B$61.BA22B2.B$60.B4AF.6AG.2AG.6AB.
B$60.BA22BAB.B$60.BAB20.BAB.B$60.BAB.20BAB.B$60.BA2B21AB.B$60.B4A20B
2.B$61.4B22.B!
EDIT 1: SHEATHABLE CONNECTOR DISCOVERY!
Inspired primarily by my Flow6 RepLoopCator, I made an extension connector.
This thing here is sheathable, AND connects to extend commands.
Just place this thing 9 cells away from your extender and you've got 9 less extend commands.

Code: Select all

x = 28, y = 10, rule = Devore2
24.B$19.4AF.3A$.11B10.A.B$BF.9AB9.A$BA2BG7B10.A$BA2B.B13.4A$B.2BAB$BG
2BAB$B2A.FB$.4B!
EDIT 2: THE TRUE DEVORE LOOPREP BODY!
I took a peek at the body and oh boy, that's inherently buggy. (not unlike some of my own designs :P)
A few edits were made here and there cause it gets even buggier with the connector.
Anyway, here's the new body:

Code: Select all

x = 71, y = 38, rule = Devore2
9A$A7.A$A7.A$A4.A2.A16.5A$9A16.A3.A11.5A$A4.A2.8A9.A2.2A5.4A2.A3.A$A
7.A6.A9.A2.5A2.A2.4A3.6A$A7.A6.A9.4A3.A2.A2.A2.A3.A3.2A$A7.5A2.A12.A
2.2A2.A2.A2.5A4.A18.A$A7.A2.5A12.A2.5A2.A11.A18.A$A7.A2.A16.4A3.4A11.
A17.2A$A7.A2.A16.A14.5A2.20A$A7.A15.2A2.A14.A3.A21.A$A2.3A2.A15.A3.7A
5.A2.A3.A21.A$A2.A4.17A3.A4.2A2.4A2.A2.2A20.2A$A2.A7.A6.A5.5A5.A2.A2.
A2.A2.5A17.3A$A2.A7.A6.A5.A3.A2.A2.A2.A2.7A2.7A5.8A$A2.A2.2A2.2A2.A2.
2A2.A2.A3.4A2.7A8.A2.A.2A8.A3.A$A2.A3.A2.5A2.5A2.A3.A20.A2.A2.2A7.A2.
2A$A2.A3.A2.A6.A6.2A2.A20.A2.A11.A2.4A$A2.5A2.A6.A6.A3.19A2.A2.A8.4A
2.A$A6.A2.8A2.2A2.A8.A15.4A14.A$A6.A9.A3.A2.A8.A33.A$A2.2A2.A9.A3.A2.
A8.A23.11A$A2.A3.A9.8A5.5A19.A2.A$A2.A3.A9.A12.A3.A6.4A6.4A2.A$A2.15A
9.4A3.A6.A2.5A2.A2.A2.A$A3.A3.2A16.2A2.5A6.A2.A2.2A2.A2.A2.A$A7.A11.
8A3.A9.A2.A3.7A2.A$A3.A2.2A11.A2.A7.A6.4A2.A9.A2.A$18A2.A2.A7.A2.5A2.
4A8.2A2.A$A3.A11.2A2.4A7.A2.A3.A14.5A$A16.A2.A2.A7.A2.A2.2A14.A$2A2.A
12.A5.A7.A2.A2.17A$5A12.A5.A7.7A14.3A$17.A5.A30.A$17.A4.2A30.A$17.38A
!
EDIT 3: BETTER CONNECTOR
This connector is smaller, and doesn't send a 6 when connecting anymore.
Compare: (Top: 2nd design, Bottom: 1st design)

Code: Select all

x = 25, y = 12, rule = Devore2
9.2B$8.B2AB5.A$8.BAB11.2B$8.BAB6.4AF.AB$.8BAB11.2B$BF.7AB$BA2BG4BAB
11.2B$BA2B.B2.BAB6.4AF.AB$B.2BAB2.BAB9.A.2B$BG2BAB2.B2AB8.A$B2A.FB3.
2B9.A$.4B12.4A!
c(>^w^<c)~*
This is 「Fluffy」
「Fluffy」is my sutando.
「Fluffy」has the ability to engineer r e p l i c a t o r s.
「Fluffy」likes to watch spaceship guns in Golly.
「Fluffy」knows Natsuki best girl.

Letters
Posts: 6
Joined: October 15th, 2019, 4:50 am

Re: Mahler-rep: A self-replicating Universal Turing Machine built with the Devore ruleset

Post by Letters » October 21st, 2019, 2:46 am

The connector idea is great. It makes it really easy to increase/decrease the new loop size, because you can expand or decrease the buffer zone without affecting the actual loop size (Aside from the signals needed to construct it obviously)

I spent some time today crunching down the machine we've come up with. I made this first one before I saw your 3rd edit with the simplified connector. Its loop size is 82268, and its replication time is 493,752 cycles.

Code: Select all

#CXRLE Pos=31,58
x = 61, y = 36, rule = Devore2
8A$A6.A$A6.A$A3.A2.A16.5A$8A16.A3.A5.4A2.4A$A3.A2.8A9.A2.2A5.A2.4A2.A
$A6.A6.A9.A2.5A2.A2.A2.A2.A$A6.A6.A9.4A3.A2.A2.A2.A2.5A11.A$A6.5A2.A
12.A2.2A2.A2.A2.4A2.2A11.A$A6.A2.5A12.A2.5A2.A9.A10.2A$A6.A2.A16.4A3.
4A9.13A$A6.A2.A16.A11.6A14.A$A6.A15.2A2.A11.A3.4A12.A$A2.2A2.A15.A3.
4A5.A2.A3.A2.A11.2A$A2.A3.17A3.A2.A2.4A2.A3.A2.A11.3A$A2.A6.A6.A5.5A
5.A2.A2.A3.A2.A10.2A$A2.A2.2A2.A6.A5.A3.A5.A2.4A2.2A2.A$A2.A3.A2.A2.A
2.2A2.A2.A3.10A2.A2.6A$A2.A3.A2.4A2.5A2.2A2.A11.4A16.2A$A2.5A4.5A6.A
3.A29.3A$A6.A8.A6.A2.5A2.5A8.A11.2A$A6.A8.A2.5A2.A3.4A3.7A2.A12.A$A2.
2A2.A8.A3.A5.A3.A12.4A12.A$A2.A3.A8.A3.A4.6A4.A15.A2.6A$A2.A3.14A4.A
3.A3.9A6.4A2.2A$A2.5A4.A12.A3.A3.A.A2.A2.5A2.A2.A2.A$A23.2A3.A3.A4.A
2.A2.2A2.A2.A2.A$A11.A6.7A2.2A8.A2.A3.7A2.A$A2.15A.A3.A4.A6.4A2.A9.A
2.A$4A8.A3.4A3.A4.A2.5A2.4A8.2A2.A$16.A2.A3.A4.A2.A3.A14.5A$12.A3.A2.
5A4.A2.A2.2A14.A$11.6A2.A7.2A2.A2.17A$12.A3.A10.8A$16.A10.A$16.12A!
A notable thing here is that "loop stub" on the right isn't actually connected - that's because I used the two ends of the connector to join the near ends of the loop. It's a lot more compact than squeezing in the first connector you posted, and imo it's just pretty fun conceptually.

looprep_connected.png
looprep_connected.png (5.56 KiB) Viewed 3561 times
This second one uses the smaller connector you posted. I was hoping that it would require a smaller "extend buffer" and lead to a smaller loop, but its loop size is 83028 and it copies itself in 498,318 cycles. I suspect that the extra "6" you referred to is actually an *advantage* for this use case, because it eats the subsequent extend signal, which would have otherwise gone through, and the whole point of this setup is to eat extend signals. The second connector definitely seems like it might have useful applications elsewhere though.

Code: Select all

#CXRLE Pos=31,58
x = 62, y = 36, rule = Devore2
8A$A6.A$A6.A$A3.A2.A16.5A$8A16.A3.A5.4A2.4A$A3.A2.8A9.A2.2A5.A2.4A2.A
$A6.A6.A9.A2.5A2.A2.A2.A2.A$A6.A6.A9.4A3.A2.A2.A2.A2.5A12.A$A6.5A2.A
12.A2.2A2.A2.A2.4A2.2A12.A$A6.A2.5A12.A2.5A2.A9.A11.2A$A6.A2.A16.4A3.
4A9.14A$A6.A2.A16.A11.6A15.A$A6.A15.2A2.A11.A3.4A13.A$A2.2A2.A15.A3.
4A5.A2.A3.A2.A12.2A$A2.A3.17A3.A2.A2.4A2.A3.A2.A12.3A$A2.A6.A6.A5.5A
5.A2.A2.A3.A2.A8.5A$A2.A2.2A2.A6.A5.A3.A5.A2.4A2.2A2.A12.A$A2.A3.A2.A
2.A2.2A2.A2.A3.10A2.A2.6A7.A2.2A$A2.A3.A2.4A2.5A2.2A2.A11.4A15.4A$A2.
5A4.5A6.A3.A30.A$A6.A8.A6.A2.5A2.5A8.A7.5A$A6.A8.A2.5A2.A3.4A3.7A2.A
7.A$A2.2A2.A8.A3.A5.A3.A12.4A7.A$A2.A3.A8.A3.A4.6A4.A15.A2.A$A2.A3.
14A4.A3.A3.9A6.4A2.A$A2.5A4.A12.A3.A3.A.A2.A2.5A2.A2.A2.A$A23.2A3.A3.
A4.A2.A2.2A2.A2.A2.A$A11.A6.7A2.2A8.A2.A3.7A2.A$A2.15A.A3.A4.A6.4A2.A
9.A2.A$4A8.A3.4A3.A4.A2.5A2.4A8.2A2.A$16.A2.A3.A4.A2.A3.A14.5A$12.A3.
A2.5A4.A2.A2.2A14.A$11.6A2.A7.2A2.A2.17A$12.A3.A10.8A$16.A10.A$16.12A
!
And here's a python script with cleaner code for generating the starter loop, and it generates the correct shape+stub now!

Code: Select all

import golly

extend =        [7,6]
extend_left =   [4,4,5,6]
extend_right =  [5,5,4,6]
retract =       [4,5,6,6]
retract_left =  [5,6,6,6]
retract_right = [4,6,6,6]
mark =          [7,6,4,5,7,6]
inject =        [1,1,1,1,7,7]

signal_cells = {
    1: [1],
    4: [4,0,1,1,1],
    5: [5,0,1,1], # if the sequence ends up ever being passed through an "on" switch, we need an extra '1' cell at the end of this list, for extra buffer
    6: [6,0,1,1],
    7: [7,0,1,1],
}

sequence_loop_delay = 58

rect = golly.getrect()
x = rect[0]
y = rect[1]
width = rect[2]
height = rect[3]

instruction_sequence = []
for row in reversed(xrange(y, y+height)):
	# find the rightmost 1 bit in this row 
	far_right = x+width-1
	while golly.getcell(far_right,row) != 1:
		far_right -= 1
		if far_right<x:
			golly.exit('Empty rows forbidden: '+str(row))

	# prepare to start printing out cells from this row
	instruction_sequence.extend([extend_right, extend, extend])

	# loop over each cell in the row and add mark instructions for cells that are marked
	for col in xrange(x, far_right + 1):
		instruction_sequence.append(extend)
		if golly.getcell(col, row) == 1:
			instruction_sequence.extend([extend_right, mark, retract_right])

	# retract as far as we extended, then move up one to start the next row
	instruction_sequence.extend([retract]*(far_right - x + 3))
	instruction_sequence.extend([retract_right, extend])

# we're done. the most recent thing in the list should be an extend. if so, get rid of it, because we're just going to retract immediately
if len(instruction_sequence) > 0 and instruction_sequence[-1] == extend:
	instruction_sequence.pop()

# move the construction arm to the injection point and inject
instruction_sequence.extend([retract]*3)
instruction_sequence.extend([extend_right, extend, extend, inject])


#now that we have the full list of instructions, conver them down to a list of signals, and then down to a list of cells
signal_sequence = [signal for instruction in instruction_sequence for signal in instruction]
cell_sequence = [cell for signal in signal_sequence for cell in signal_cells[signal]]
cell_sequence += [1] * sequence_loop_delay

# our cell sequence must have even length, since we're going to be creating a loop
if len(cell_sequence) % 2 == 1:
	cell_sequence.append(1)


# to write out the starter loop, we're going to keep track of loop cells and loop neighbor cells. After writing the loop cells, we'll go back and mark all the neigbor cells as the sheath
loop_cells = set()
loop_neighbor_cells = set()
def mark_loop_cell(x,y,value):
	golly.setcell(x, y, value)
	loop_cells.add((x,y))
	loop_neighbor_cells.add((x-1,y))
	loop_neighbor_cells.add((x+1,y))
	loop_neighbor_cells.add((x  ,y-1))
	loop_neighbor_cells.add((x  ,y+1))


# Write out the actual cell sequence. First, write out the 
mark_loop_cell(-1,0,cell_sequence[-1])
mark_loop_cell(-2,0,cell_sequence[-2])
mark_loop_cell(-2,1,cell_sequence[-3])
mark_loop_cell(-3,1,cell_sequence[-4])
mark_loop_cell(-4,1,cell_sequence[-5])
mark_loop_cell(-4,2,cell_sequence[-6])
mark_loop_cell(-4,3,cell_sequence[-7])
mark_loop_cell(-4,4,cell_sequence[-8])
mark_loop_cell(-4,5,cell_sequence[-9])
mark_loop_cell(-3,5,cell_sequence[-10])
mark_loop_cell(-2,5,cell_sequence[-11])
mark_loop_cell(-1,5,cell_sequence[-12])
mark_loop_cell(-1,4,cell_sequence[-13])

short_leg_length = 3
long_leg_length = (len(cell_sequence) - len(loop_cells) - short_leg_length) // 2

# draw the top long leg
for x in xrange(0, long_leg_length):
	mark_loop_cell(x, 0, cell_sequence[x])

# draw the far short leg 
for y in xrange(0, short_leg_length):
	mark_loop_cell(long_leg_length-1, y+1, cell_sequence[long_leg_length + y])

# draw the bottom long leg
for x in xrange(long_leg_length):
	mark_loop_cell(x, short_leg_length+1, cell_sequence[long_leg_length + short_leg_length + long_leg_length - x - 1])

# finally, draw a stub circuit for the starting point of our construction arm
stub_height = 7
for i in xrange(0, stub_height):
	mark_loop_cell(-1, -(i+1), 1)

# now that all loop cells have been drawn, go back and fill out the sheath cells
for neighbor_cell in loop_neighbor_cells:
	if neighbor_cell not in loop_cells:
		(x,y) = neighbor_cell
		golly.setcell(x, y, 2)



golly.show("Instruction sequence count: {}, Signal sequence count: {}, cell sequence count: {}".format(len(instruction_sequence), len(signal_sequence), len(cell_sequence)))
Reflecting on the experience of making this vs making the turing machine replicator, I've learned that there's a constraint on the turing machine's circuits that I wasn't even aware of: They need to be re-usable. you have to assumethat the machine will do any particular thing over and over again, so everything needs to reset itself when it finishes. Compare that to this thing, where almost everything is only used once. So several of my crunching techniques involved leaving the circuits in a broken state, because why not? It's a very different mindset.

Post Reply