A script to simulate odd 1D Wolfram cellular automata

For scripts to aid with computation or simulation in cellular automata.
Post Reply
User avatar
SuperSupermario24
Posts: 120
Joined: July 22nd, 2014, 12:59 pm
Location: Within the infinite expanses of the Life universe

A script to simulate odd 1D Wolfram cellular automata

Post by SuperSupermario24 » August 30th, 2017, 3:21 am

***EDIT: THIS SCRIPT IS OUTDATED, SEE SECOND POST FOR MUCH-IMPROVED VERSION***

I barely have any experience with Lua, so this no doubt contains a ton of inefficiencies and unnecessary nonsense, but I've attempted to create a script that can simulate any 1D Wolfram rule in a bounded universe in Golly, including odd-numbered ones:

Code: Select all

-- Creates a rule file for 1D elementary cellular automata, and then optionally runs
-- it for a given number of generations in a torus.

-- Note: This will create a .rule file in your Rules folder, specified in Preferences.

-- The rules can be run by themselves when the background is state 1, but the script
-- will run them closer to how Golly simulates even rules.

-- Also note that while this script does support even-numbered elementary rules, Golly
-- natively supports these, so it's mostly pointless to use it with even rules.

local g = golly()

local clipboard = g.getclipstr()

-- Decimal to binary converter function from http://bit.ly/2wQ9yqr because I have
-- absolutely no idea where to begin making my own
local function tobits(num,bits)
    -- returns a table of bits, most significant first.
    bits = bits or math.max(1, select(2, math.frexp(num)))
    local t = {} -- will contain the bits        
    for b = bits, 1, -1 do
        t[b] = math.fmod(num, 2)
        num = math.floor((num - t[b]) / 2)
    end
    return t
end

local rule = tonumber(g.getstring("Enter 1D rule (0 to 255).\n\nRule \"W-<#>\" will be created, and overwritten\nif it exists.", "73", "Set rule"))
local rulename = "W-"..tostring(rule)

if rule == nil or rule < 0 or rule > 255 then
	g.exit("Invalid rule specified.")
end

local rulestring = tobits(rule, 8)
local a, b, c, d, e, f, h, i = table.unpack(rulestring)

local function a1(x)
	x = tostring(tonumber(x)+1)
	return x
end
local trans = "1,1,1,"..a1(i).."\n1,1,2,"..a1(h).."\n2,1,1,"..a1(f).."\n2,1,2,"..a1(e).."\n1,2,1,"..a1(d).."\n1,2,2,"..a1(c).."\n2,2,1,"..a1(b).."\n2,2,2,"..a1(a).."\n"

local comment = "Automatically generated by a Lua script.\nThe rule will only work when the background state is 1."
local rulefile = "@RULE "..rulename.."\n\n"..comment.."\n\n@TABLE\n\nn_states:3\nneighborhood:oneDimensional\nsymmetries:none\n\n"..trans.."\n@COLORS\n1 0 0 0\n2 255 255 255"

local ruledir = g.getdir("rules")
file = io.open(ruledir..rulename..".rule", "w")
file:write(rulefile)
file:close()
g.show("Rule \""..rulename.."\" created.")

local patternrect = g.getrect()
local pattern
local usepattern = g.getstring("Enter 1 to run the current pattern; anything\nelse will start with a single cell.\n\nIf doing this, the pattern should be 1 cell high\nand roughly centered on the y-axis.\n\n(Note: Use state 0 as the background state for this.)", "", "Use current pattern?")
if usepattern == "1" then
	local rect = g.getrect()
	if rect[4] ~= 1 then
		g.warn("Pattern is either empty or more than one cell high.\nDefaulting to single cell.")
		pattern = {0, 0, 2}
	else
		pattern = g.getcells(g.getrect())
	end
else
	pattern = {0, 0, 2}
end

local width = tonumber(g.getstring("Set width of pattern generation:", "500", "Set width"))
local height = math.tointeger(g.getstring("Set number of generations to run:", "250") + 1) -- +1 because top is gen 0

g.setrule(rulename..":T"..tostring(width)..","..tostring(height))

g.new(rulename)

local rect = {math.ceil(-g.getwidth() / 2), math.ceil(-g.getheight() / 2), g.getwidth(), g.getheight()}

for j = rect[2], rect[2] + rect[4] - 1 do
	for k = rect[1], rect[1] + rect[3] - 1 do
		g.setcell(k, j, 1)
	end
end

g.putcells(pattern, 0, rect[2] - pattern[2])

-- there has got to be a better way to do this absolute mess of a thing but i
-- can't think of anything right now
g.select({rect[1], rect[2], rect[3], 1})
for l = rect[2], rect[2]+rect[4] - 2 do
	g.copy()
	g.paste(rect[1], l+1, "or")
	g.select({rect[1], l+1, rect[3], 1})
	g.advance(0, 1)
end

g.select({})
g.fit()
g.setclipstr(clipboard)
The special quiescent state 1 is due to the fact that RuleLoader won't accurately simulate B0 in rule tables.

As I said, there are surely lots of ways this could be improved. One rather obvious thing is that when the script completes, it spits out a bunch of copies of this error message:

Code: Select all

Failed to get data from the clipboard (error -2147221040: openClipboard Failed)
I have no idea why this happens, especially since the script seems to successfully generate the correct pattern each time. It seems to happen completely at random: if I run it for only 5 generations, regardless of the rule used, it'll sometimes give no errors, sometimes give me 1, or sometimes give me 2 or 3, even running with the same parameters every time.

If anyone knows of any ways to fix that issue or improve on the script in general (for example, not requiring that stupid copy-paste-advance loop), please let me know.

(Also note that I've tested this on 3.0b2 on Windows 10, and haven't made any attempts to test it on other versions.)
Last edited by SuperSupermario24 on August 30th, 2017, 4:49 pm, edited 2 times in total.

Code: Select all

bobo2b3o2b2o2bo3bobo$obobobo3bo2bobo3bobo$obobob2o2bo2bobo3bobo$o3bobo3bo2bobobobo$o3bob3o2b2o3bobo2bo!

User avatar
gameoflifemaniac
Posts: 1032
Joined: January 22nd, 2017, 11:17 am
Location: There too

Re: A script to simulate any 1D Wolfram cellular automaton

Post by gameoflifemaniac » August 30th, 2017, 1:49 pm

Your script is great!
I was so socially awkward in the past and it will haunt me for my entire life.

User avatar
SuperSupermario24
Posts: 120
Joined: July 22nd, 2014, 12:59 pm
Location: Within the infinite expanses of the Life universe

Re: A script to simulate odd 1D Wolfram cellular automata

Post by SuperSupermario24 » August 30th, 2017, 1:59 pm

So I've come up with a much better version of the script:

Code: Select all

local g = golly()
local gp = require "gplus"

-- Decimal to binary converter function from http://bit.ly/2wQ9yqr because I have
-- absolutely no idea where to begin making my own
local function tobits(num,bits)
    -- returns a table of bits, most significant first.
    bits = bits or math.max(1, select(2, math.frexp(num)))
    local t = {} -- will contain the bits        
    for b = bits, 1, -1 do
        t[b] = math.fmod(num, 2)
        num = math.floor((num - t[b]) / 2)
    end
    return t
end

local rule = gp.int(tonumber(g.getstring([[
Enter Wolfram 1D rule (0 to 255).

If the rule is even, the current rule will be switched to
the corresponding rule in QuickLife or HashLife.

If the rule is odd, a new rule "W-<#>" will be created
in the Rules folder, and overwritten if it exists.]], "73", "Set rule")))
local rulename = "W-"..tostring(rule)


if rule == nil or rule < 0 or rule > 255 then
	g.exit("Invalid rule specified.")
end
if rule % 2 == 0 then
	g.setrule("W"..tostring(rule))
	g.exit("Switched to rule W"..tostring(rule).." in "..g.getalgo()..".")
end

local a, b, c, d, e, f, h, i = table.unpack(tobits(rule, 8))

local function a1(x)
	x = tostring(tonumber(x)+1)
	return x
end

local trans = [[
0,1,1,0,0,0,0,0,1,]]..a1(i)..[[

0,1,2,0,0,0,0,0,1,]]..a1(h)..[[

0,2,1,0,0,0,0,0,1,]]..a1(f)..[[

0,2,2,0,0,0,0,0,1,]]..a1(e)..[[

0,1,1,0,0,0,0,0,2,]]..a1(d)..[[

0,1,2,0,0,0,0,0,2,]]..a1(c)..[[

0,2,1,0,0,0,0,0,2,]]..a1(b)..[[

0,2,2,0,0,0,0,0,2,]]..a1(a)..[[

1,a,b,c,d,e,f,g,h,0]]

local rulefile = [[
@RULE ]]..rulename..[[


Automatically generated by a Lua script. It is recommended that
you run it in a torus with a line of state 1 and/or 2 across its
width, otherwise it will act strangely.

@TABLE

n_states:3
neighborhood:Moore
symmetries:none

var a={0,1,2}
var b=a
var c=a
var d=a
var e=a
var f=a
var g=a
var h=a

]]..trans..[[


@COLORS
1 0   0   0
2 255 255 255
]]

local ruledir = g.getdir("rules")
file = io.open(ruledir..rulename..".rule", "w")
file:write(rulefile)
file:close()

g.setrule(rulename)

g.note([[
Rule "]]..rulename..[[" created.

Create a torus with an appropriate starting configuration?
]])

local pattern
local usepattern = g.getstring([[
Enter "1" to run the current pattern; anything
else will start with a single cell.

If doing this, the pattern should be 1 cell high
and roughly centered on the y-axis.
(Note: Use state 0 as the background state for this.)]], "", "Use current pattern?")
if usepattern == "1" then
	local rect = g.getrect()
	if rect[4] ~= 1 then
		g.warn("Pattern is either empty or more than one cell high.\nDefaulting to single cell.")
		pattern = {0, 0, 2}
	else
		pattern = g.getcells(g.getrect())
	end
else
	pattern = {0, 0, 2}
end

g.new(rulename)

local box = g.getstring("Enter width and height (w,h):", "500,250", "Set width and height")

local j, k = gp.split(box, ",")

g.setrule(rulename..":T"..j..","..k)

local rect = {math.ceil(-g.getwidth() / 2), math.ceil(-g.getheight() / 2), g.getwidth(), g.getheight()}

for l = rect[1], rect[1] + rect[3] - 1 do
	g.setcell(l, rect[2], 1)
end

g.putcells(pattern, 0, rect[2] - pattern[2])

g.fit()
There are a number of notable changes. For one thing, only odd rules are supported now (since really, why wouldn't you just use HashLife or QuickLife for even rules?) [EDIT: Even rules no longer give an "invalid rule" message, but will instead simply change the current rule to the corresponding rule in QuickLife/HashLife.] Additionally, the need for state 1 has been reduced to the "active" row, and the script is no longer needed to actually simulate the pattern.

I've also made some attempts to improve the readability of the script, particularly for multi-line strings.

I'm pretty happy with how this is now, unlike the first version of the script, although I'd still love to know if there are any ways this can be improved.

EDIT: Confirmed to be working on Golly 3.0.
Last edited by SuperSupermario24 on August 31st, 2017, 11:51 am, edited 6 times in total.

Code: Select all

bobo2b3o2b2o2bo3bobo$obobobo3bo2bobo3bobo$obobob2o2bo2bobo3bobo$o3bobo3bo2bobobobo$o3bob3o2b2o3bobo2bo!

User avatar
gameoflifemaniac
Posts: 1032
Joined: January 22nd, 2017, 11:17 am
Location: There too

Re: A script to simulate odd 1D Wolfram cellular automata

Post by gameoflifemaniac » August 30th, 2017, 4:16 pm

It works on my laptop, but it doesn't work on my computer!
The error is:
C:\Users...blahblahblah...\Roaming\Golly\golly_clip.lua:19: unexpected symbol near '<\194>'
I was so socially awkward in the past and it will haunt me for my entire life.

User avatar
SuperSupermario24
Posts: 120
Joined: July 22nd, 2014, 12:59 pm
Location: Within the infinite expanses of the Life universe

Re: A script to simulate odd 1D Wolfram cellular automata

Post by SuperSupermario24 » August 30th, 2017, 4:47 pm

...How can the issue be on line 19? It's nothing more than a comment in the first script, and literally blank (not to mention in the middle of a long string) in the second one.

I just verified that my second script works completely fine on both the 32- and 64-bit versions of Golly 2.8 on Windows, as well as both corresponding versions of Golly 3.0b3. And the error message indicates you're using Windows, so it's probably not somehow an OS issue.

Code: Select all

bobo2b3o2b2o2bo3bobo$obobobo3bo2bobo3bobo$obobob2o2bo2bobo3bobo$o3bobo3bo2bobobobo$o3bob3o2b2o3bobo2bo!

User avatar
SuperSupermario24
Posts: 120
Joined: July 22nd, 2014, 12:59 pm
Location: Within the infinite expanses of the Life universe

Re: A script to simulate odd 1D Wolfram cellular automata

Post by SuperSupermario24 » August 31st, 2017, 2:33 am

Made an adjustment to the script, so that now instead of just exiting with an "invalid state" message upon entering an even rule, it'll instead switch the current rule to the corresponding rule in QuickLife (or HashLife, if that is the current algorithm) and then exit.

It is worth noting that there is currently a glitch in Golly (present in 2.8 through 3.0b3) that causes it to fail to recognize Wolfram rules with the digit "9" in them (for example, W90 or W196), and instead give an error message. This is slated to be fixed in the next release of Golly, however, so I'm not going to bother accounting for this in my script. (If you really want to run those rules, for now you can remove or comment out the "if rule % 2 == 0" section on lines 31-34 and then run the script.)

EDIT: This has now been fixed in Golly 3.0.

Also, 100 posts woo!
Last edited by SuperSupermario24 on August 31st, 2017, 11:46 am, edited 1 time in total.

Code: Select all

bobo2b3o2b2o2bo3bobo$obobobo3bo2bobo3bobo$obobob2o2bo2bobo3bobo$o3bobo3bo2bobobobo$o3bob3o2b2o3bobo2bo!

User avatar
gameoflifemaniac
Posts: 1032
Joined: January 22nd, 2017, 11:17 am
Location: There too

Re: A script to simulate odd 1D Wolfram cellular automata

Post by gameoflifemaniac » August 31st, 2017, 3:54 am

Error reading W-73.rule on line 22:
0,1,1,0,0,0,0,0,1,20,1,2,0,0,0,0,0,1,10,2,1,0,0,0,0,0,1,10,2,2,0,0,0,0,0,1,20,1,1,0,0,0,0,0,2,10,1,2,0,0,...
- state out of range
I don't see any state 20 or 10 cells in the transition part.
I was so socially awkward in the past and it will haunt me for my entire life.

User avatar
SuperSupermario24
Posts: 120
Joined: July 22nd, 2014, 12:59 pm
Location: Within the infinite expanses of the Life universe

Re: A script to simulate odd 1D Wolfram cellular automata

Post by SuperSupermario24 » August 31st, 2017, 11:52 am

Oh, I see the issue there.

I put some trailing spaces at the end of the transition lines to force Lua to put a newline while still keeping all of them close together in the script, but it seems the forum is automatically removing those, causing Lua not to put newlines there and messing everything up.

Try it now.

Code: Select all

bobo2b3o2b2o2bo3bobo$obobobo3bo2bobo3bobo$obobob2o2bo2bobo3bobo$o3bobo3bo2bobobobo$o3bob3o2b2o3bobo2bo!

Post Reply