Commit 655dd8f9 authored by Adam P. Goucher's avatar Adam P. Goucher 💬

Merge branch 'forestry' into 'master'

Automatically detect 2-state isotropic Moore-neighbourhood rule trees

See merge request !7
parents e5ccd0db 1c11ea07
Pipeline #55071320 passed with stages
in 8 minutes and 12 seconds
......@@ -3,7 +3,7 @@ import os
import re
from importlib import import_module
from .genuslist import genus_list
from .exceptions import NonCanonicalError
from .exceptions import NonCanonicalError, SurplusTreeError
from .rulefiles import rule2files
def obtain_genus(rulestring):
......@@ -36,9 +36,12 @@ def create_rule(rulestring):
def sanirule(rulestring, drop_history=False):
if '.' in rulestring:
rulestring = rule2files(rulestring)
if (rulestring[0] != 'x'):
raise ValueError("Rules specified by table/tree/code files must begin with a capital letter.")
try:
rulestring = rule2files(rulestring)
if (rulestring[0] != 'x'):
raise ValueError("Rules specified by table/tree/code files must begin with a capital letter.")
except SurplusTreeError as e:
rulestring = str(e)
rulestring = rulestring.lower()
......
class NonCanonicalError(ValueError):
pass
class SurplusTreeError(ValueError):
pass
......@@ -23,6 +23,76 @@ def create_rule(rulestring):
f.write('}\n')
def squarelord():
lord = ""
lord += "_ceaccaieaeaknja_ceaccaieaeaknjaekejanaairerririekejanaairerriri";
lord += "ccknncqnaijaqnwaccknncqnaijaqnwakykkqyqjrtjnzrqakykkqyqjrtjnzrqa";
lord += "ekirkyrtejerkkjnekirkyrtejerkkjnekejjkrnejecjyccekejjkrnejecjycc";
lord += "anriqyzraariqjqaanriqyzraariqjqajkjywkqkrnccqkncjkjywkqkrnccqknc";
lord += "cnkqccnnkqkqyykjcnkqccnnkqkqyykjaqjwinaarzjqtrnaaqjwinaarzjqtrna";
lord += "ccyyccyennkjyekeccyyccyennkjyekenykknejeirykrikenykknejeirykrike";
lord += "aqrznyirjwjqkkykaqrznyirjwjqkkykaqrqajiarqcnnkccaqrqajiarqcnnkcc";
lord += "intrneriaanajekeintrneriaanajekeajnkaeaeiaccaec_ajnkaeaeiaccaec_";
return lord
def hexlord():
lord = ""
prelord = "___o_moo" + "_pmmomoo" + "_mpmmpmm" + "ommpomo_";
prelord += prelord[::-1];
for i in range(512):
j = (i & 3) + ((i & 32) >> 3) + ((i & 256) >> 5) + ((i & 128) >> 3) + ((i & 8) << 2);
lord += prelord[j]
return lord
def tab2str(lord2):
lord = squarelord()
rule_letters = {}
rule_letters[1] = "ce" ;
rule_letters[2] = "ceaikn" ;
rule_letters[3] = "ceaiknjqry" ;
rule_letters[4] = "ceaiknjqrytwz" ;
rule_letters[5] = "ceaiknjqry" ;
rule_letters[6] = "ceaikn" ;
rule_letters[7] = "ce" ;
popcounts = [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4]
canonicals = [['' for i in range(9)] for j in range(2)]
canstring = ''
for i in range(512):
centre = (i >> 4) & 1
ncount = popcounts[i & 15] + popcounts[i >> 5]
if (lord2[i] == 1):
canonicals[centre][ncount] += lord[i]
for (centre, z) in enumerate('bs'):
canstring += z
for ncount in range(9):
goodies = set(canonicals[centre][ncount])
if (len(goodies) == 0):
continue
canstring += str(ncount)
if ncount not in rule_letters:
continue
a = ''.join(sorted([c for c in rule_letters[ncount] if c in goodies]))
b = '-' + (''.join(sorted([c for c in rule_letters[ncount] if c not in goodies])))
if (len(b) == 1):
continue
elif (len(b) < len(a)):
canstring += b
else:
canstring += a
return canstring
def str2tab(rulestring, hexagonal='unknown'):
if (hexagonal == 'unknown'):
......@@ -44,26 +114,13 @@ def str2tab(rulestring, hexagonal='unknown'):
elif c in ('omp-' if hexagonal else 'ceaiknjqrytwz-'):
isotrans[(centre, lastloc)] += c
lord = "";
if not hexagonal:
lord += "_ceaccaieaeaknja_ceaccaieaeaknjaekejanaairerririekejanaairerriri";
lord += "ccknncqnaijaqnwaccknncqnaijaqnwakykkqyqjrtjnzrqakykkqyqjrtjnzrqa";
lord += "ekirkyrtejerkkjnekirkyrtejerkkjnekejjkrnejecjyccekejjkrnejecjycc";
lord += "anriqyzraariqjqaanriqyzraariqjqajkjywkqkrnccqkncjkjywkqkrnccqknc";
lord += "cnkqccnnkqkqyykjcnkqccnnkqkqyykjaqjwinaarzjqtrnaaqjwinaarzjqtrna";
lord += "ccyyccyennkjyekeccyyccyennkjyekenykknejeirykrikenykknejeirykrike";
lord += "aqrznyirjwjqkkykaqrznyirjwjqkkykaqrqajiarqcnnkccaqrqajiarqcnnkcc";
lord += "intrneriaanajekeintrneriaanajekeajnkaeaeiaccaec_ajnkaeaeiaccaec_";
lord = squarelord()
else:
prelord = "___o_moo" + "_pmmomoo" + "_mpmmpmm" + "ommpomo_";
prelord += prelord[::-1];
for i in range(512):
j = (i & 3) + ((i & 32) >> 3) + ((i & 256) >> 5) + ((i & 128) >> 3) + ((i & 8) << 2);
lord += prelord[j]
lord = hexlord()
import re
......@@ -102,42 +159,7 @@ def str2tab(rulestring, hexagonal='unknown'):
if hexagonal:
return lord2
rule_letters = {}
rule_letters[1] = "ce" ;
rule_letters[2] = "ceaikn" ;
rule_letters[3] = "ceaiknjqry" ;
rule_letters[4] = "ceaiknjqrytwz" ;
rule_letters[5] = "ceaiknjqry" ;
rule_letters[6] = "ceaikn" ;
rule_letters[7] = "ce" ;
canonicals = [['' for i in range(9)] for j in range(2)]
canstring = ''
for i in range(512):
centre = (i >> 4) & 1
ncount = popcounts[i & 15] + popcounts[i >> 5]
if (lord2[i] == 1):
canonicals[centre][ncount] += lord[i]
for (centre, z) in enumerate('bs'):
canstring += z
for ncount in range(9):
goodies = set(canonicals[centre][ncount])
if (len(goodies) == 0):
continue
canstring += str(ncount)
if ncount not in rule_letters:
continue
a = ''.join(sorted([c for c in rule_letters[ncount] if c in goodies]))
b = '-' + (''.join(sorted([c for c in rule_letters[ncount] if c not in goodies])))
if (len(b) == 1):
continue
elif (len(b) < len(a)):
canstring += b
else:
canstring += a
canstring = tab2str(lord2)
if (rulestring != canstring):
raise NonCanonicalError('%s is a non-canonical version of %s' % (rulestring, canstring))
......
from .parsetable import ReadRuleTable
from .parsetree import ReadRuleTree
from .parsetree import ReadRuleTree, SurplusTreeError
from .writetree import TransitionsToTree
from .writecode import NodesToCode
......@@ -130,11 +130,6 @@ def rule2segs(rulefile):
grulename = segments['@RULE'][0].strip().split()[1]
rulename = decapitalise(grulename)
if rulename != grulename:
message = "Rule name \033[31;1m%s (Golly)\033[0m converted to \033[32;1m%s (lifelib)\033[0m."
print(message % (grulename, rulename))
while '@CODE' not in segments:
# Apply transformations
......
......@@ -4,6 +4,8 @@ from ast import literal_eval
from .writetree import RuleTree
from .automorph import matperms
from ..isotropic import tab2str, str2tab
from ..exceptions import SurplusTreeError
# Named neighbourhoods:
nhoods = {'2': [(-1, 0), (1, 0), (0, 0), (0, 0)],
......@@ -144,6 +146,29 @@ def optimise_tree(n_states, nhood, list_of_nodes):
return n_states, nhood, list_of_nodes
def TreeToArray(nhood, list_of_nodes):
inputs = nhood[:GetNumberOfInputs(nhood)]
lord2 = []
indices = {(-1, -1): 0, (0, -1): 1, (1, -1): 2,
(-1, 0): 3, (0, 0): 4, (1, 0): 5,
(-1, 1): 6, (0, 1): 7, (1, 1): 8}
for i in range(512):
p = len(list_of_nodes) - 1
for k in inputs:
s = (i >> indices[k]) & 1
p = list_of_nodes[p][s+1]
lord2.append(p)
return lord2
def ReadRuleTree(list_of_lines):
# Parse the actual file into a rule tree:
......@@ -152,6 +177,15 @@ def ReadRuleTree(list_of_lines):
# Optimise the resulting rule tree:
n_states, nhood, list_of_nodes = optimise_tree(*ruletree)
# Determine whether rule is 2-state isotropic:
if (n_states == 2) and (GetNumberOfInputs(nhood) + 1 == len(nhood)):
chebyshev_range = max([abs(x) for y in nhood for x in y])
if (chebyshev_range <= 1):
rarray = TreeToArray(nhood, list_of_nodes)
rstring = tab2str(rarray)
if (rarray == str2tab(rstring)):
raise SurplusTreeError(rstring)
# Serialise tree to an array:
num_nodes = len(list_of_nodes)
......
num_states=2
num_neighbors=8
num_nodes=32
1 0 0
2 0 0
1 0 1
2 0 2
3 1 3
1 1 1
2 2 5
3 3 6
4 4 7
2 5 0
3 6 9
4 7 10
5 8 11
3 9 1
4 10 13
5 11 14
6 12 15
3 1 1
4 13 17
5 14 18
6 15 19
7 16 20
4 17 17
5 18 22
6 19 23
7 20 24
8 21 25
5 22 22
6 23 27
7 24 28
8 25 29
9 26 30
import os
import lifelib
import unittest
from lifelib.autocompile import sanirule
class TestSanirule(unittest.TestCase):
def test_goltree(self):
filename = os.path.join(lifelib.lifelib_dir, 'rules', 'source', 'Life.tree')
srule = sanirule(filename)
self.assertEqual(srule, 'b3s23')
if __name__ == '__main__':
unittest.main()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment