A (one dimensional) cellular automaton is a function1 F : Σ → Σ with the property that there is a K > 0 such that F (x)i depends only on the 2K + 1 coordinates xi−K , xi−K+1, . . . , xi−1, xi, xi+1, . . . , xi+K . A periodic point of σ is any x such that σ^p (x) = x for some p ∈ N, and a periodic point of F is any x such that F^q (x) = x for some q ∈ N. Given a cellular automaton F, a point x ∈ Σ is jointly periodic if there are p, q ∈ N such that σ^p (x) = F^q (x) = x, that is, it is a periodic point under both functions.
This project aims to explore the nature of one-dimensional Cellular Automata, in the hope of finding the structure of cellular automata through its periodic points.
License: MIT
ubuntu2004
from copy import deepcopy12#Regex validation3def is_valid_regex(regex):4return valid_brackets(regex) and valid_operations(regex)567def valid_brackets(regex):8opened_brackets = 09for c in regex:10if c == '(':11opened_brackets += 112if c == ')':13opened_brackets -= 114if opened_brackets < 0:15print('ERROR missing bracket')16return False17if opened_brackets == 0:18return True19print('ERROR unclosed brackets')20return False212223def valid_operations(regex):24for i, c in enumerate(regex):25if c == '*':26if i == 0:27print('ERROR * with no argument at', i)28return False29if regex[i - 1] in '(|':30print('ERROR * with no argument at', i)31return False32if c == '|':33if i == 0 or i == len(regex) - 1:34print('ERROR | with missing argument at', i)35return False36if regex[i - 1] in '(|':37print('ERROR | with missing argument at', i)38return False39if regex[i + 1] in ')|':40print('ERROR | with missing argument at', i)41return False42return True4344class RegexNode:4546@staticmethod47def trim_brackets(regex):48while regex[0] == '(' and regex[-1] == ')' and is_valid_regex(regex[1:-1]):49regex = regex[1:-1]50return regex5152@staticmethod53def is_concat(c):54return c == '(' or RegexNode.is_letter(c)5556@staticmethod57def is_letter(c):58return c in alphabet5960def __init__(self, regex):61self.nullable = None62self.firstpos = []63self.lastpos = []64self.item = None65self.position = None66self.children = []6768if DEBUG:69print('Current : '+regex)70#Check if it is leaf71if len(regex) == 1 and self.is_letter(regex):72#Leaf73self.item = regex74#Lambda checking75if use_lambda:76if self.item == lambda_symbol:77self.nullable = True78else:79self.nullable = False80else:81self.nullable = False82return8384#It is an internal node85#Finding the leftmost operators in all three86kleene = -187or_operator = -188concatenation = -189i = 09091#Getting the rest of terms92while i < len(regex):93if regex[i] == '(':94#Composed block95bracketing_level = 196#Skipping the entire term97i+=198while bracketing_level != 0 and i < len(regex):99if regex[i] == '(':100bracketing_level += 1101if regex[i] == ')':102bracketing_level -= 1103i+=1104else:105#Going to the next char106i+=1107108#Found a concatenation in previous iteration109#And also it was the last element check if breaking110if i == len(regex):111break112113#Testing if concatenation114if self.is_concat(regex[i]):115if concatenation == -1:116concatenation = i117continue118#Testing for kleene119if regex[i] == '*':120if kleene == -1:121kleene = i122continue123#Testing for or operator124if regex[i] == '|':125if or_operator == -1:126or_operator = i127128#Setting the current operation by priority129if or_operator != -1:130#Found an or operation131self.item = '|'132self.children.append(RegexNode(self.trim_brackets(regex[:or_operator])))133self.children.append(RegexNode(self.trim_brackets(regex[(or_operator+1):])))134elif concatenation != -1:135#Found a concatenation136self.item = '.'137self.children.append(RegexNode(self.trim_brackets(regex[:concatenation])))138self.children.append(RegexNode(self.trim_brackets(regex[concatenation:])))139elif kleene != -1:140#Found a kleene141self.item = '*'142self.children.append(RegexNode(self.trim_brackets(regex[:kleene])))143144def calc_functions(self, pos, followpos):145if self.is_letter(self.item):146#Is a leaf147self.firstpos = [pos]148self.lastpos = [pos]149self.position = pos150#Add the position in the followpos list151followpos.append([self.item,[]])152return pos+1153#Is an internal node154for child in self.children:155pos = child.calc_functions(pos, followpos)156#Calculate current functions157158if self.item == '.':159#Is concatenation160#Firstpos161if self.children[0].nullable:162self.firstpos = sorted(list(set(self.children[0].firstpos + self.children[1].firstpos)))163else:164self.firstpos = deepcopy(self.children[0].firstpos)165#Lastpos166if self.children[1].nullable:167self.lastpos = sorted(list(set(self.children[0].lastpos + self.children[1].lastpos)))168else:169self.lastpos = deepcopy(self.children[1].lastpos)170#Nullable171self.nullable = self.children[0].nullable and self.children[1].nullable172#Followpos173for i in self.children[0].lastpos:174for j in self.children[1].firstpos:175if j not in followpos[i][1]:176followpos[i][1] = sorted(followpos[i][1] + [j])177178elif self.item == '|':179#Is or operator180#Firstpos181self.firstpos = sorted(list(set(self.children[0].firstpos + self.children[1].firstpos)))182#Lastpos183self.lastpos = sorted(list(set(self.children[0].lastpos + self.children[1].lastpos)))184#Nullable185self.nullable = self.children[0].nullable or self.children[1].nullable186187elif self.item == '*':188#Is kleene189#Firstpos190self.firstpos = deepcopy(self.children[0].firstpos)191#Lastpos192self.lastpos = deepcopy(self.children[0].lastpos)193#Nullable194self.nullable = True195#Followpos196for i in self.children[0].lastpos:197for j in self.children[0].firstpos:198if j not in followpos[i][1]:199followpos[i][1] = sorted(followpos[i][1] + [j])200201return pos202203def write_level(self, level):204print(str(level) + ' ' + self.item, self.firstpos, self.lastpos, self.nullable, '' if self.position == None else self.position)205for child in self.children:206child.write_level(level+1)207208class RegexTree:209210def __init__(self, regex):211self.root = RegexNode(regex)212self.followpos = []213self.functions()214215def write(self):216self.root.write_level(0)217218def functions(self):219positions = self.root.calc_functions(0, self.followpos)220if DEBUG == True:221print(self.followpos)222223def toDfa(self):224225def contains_hashtag(q):226for i in q:227if self.followpos[i][0] == '#':228return True229return False230231M = [] #Marked states232Q = [] #States list in the followpos form ( array of positions )233V = alphabet - {'#', lambda_symbol if use_lambda else ''} #Automata alphabet234d = [] #Delta function, an array of dictionaries d[q] = {x1:q1, x2:q2 ..} where d(q,x1) = q1, d(q,x2) = q2..235F = [] #FInal states list in the form of indexes (int)236q0 = self.root.firstpos237238Q.append(q0)239if contains_hashtag(q0):240F.append(Q.index(q0))241242while len(Q) - len(M) > 0:243#There exists one unmarked244#We take one of those245q = [i for i in Q if i not in M][0]246#Generating the delta dictionary for the new state247d.append({})248#We mark it249M.append(q)250#For each letter in the automata's alphabet251for a in V:252# Compute destination state ( d(q,a) = U )253U = []254#Compute U255#foreach position in state256for i in q:257#if i has label a258if self.followpos[i][0] == a:259#We add the position to U's composition260U = U + self.followpos[i][1]261U = sorted(list(set(U)))262#Checking if this is a valid state263if len(U) == 0:264#No positions, skipping, it won't produce any new states ( also won't be final )265continue266if U not in Q:267Q.append(U)268if contains_hashtag(U):269F.append(Q.index(U))270#d(q,a) = U271d[Q.index(q)][a] = Q.index(U)272273return Dfa(Q,V,d,Q.index(q0),F)274275276class Dfa:277278def __init__(self,Q,V,d,q0,F):279self.Q = Q280self.V = V281self.d = d282self.q0 = q0283self.F = F284285def run(self, text):286#Checking if the input is in the current alphabet287if len(set(text) - self.V) != 0:288#Not all the characters are in the language289print('ERROR characters',(set(text)-self.V),'are not in the automata\'s alphabet')290exit(0)291292#Running the automata293q = self.q0294for i in text:295#Check if transition exists296if q >= len(self.d):297print('Message NOT accepted, state has no transitions')298exit(0)299if i not in self.d[q].keys():300print('Message NOT accepted, state has no transitions with the character')301exit(0)302#Execute transition303q = self.d[q][i]304305if q in self.F:306print('Message accepted!')307else:308print('Message NOT accepted, stopped in an unfinal state')309310def write(self):311for i in range(len(self.Q)):312#Printing index, the delta fuunction for that transition and if it's final state313print(i,self.d[i],'F' if i in self.F else '')314315#Preprocessing Functions316def preprocess(regex):317regex = clean_kleene(regex)318regex = regex.replace(' ','')319regex = '(' + regex + ')' + '#'320while '()' in regex:321regex = regex.replace('()','')322return regex323324def clean_kleene(regex):325for i in range(0, len(regex) - 1):326while i < len(regex) - 1 and regex[i + 1] == regex[i] and regex[i] == '*':327regex = regex[:i] + regex[i + 1:]328return regex329330def gen_alphabet(regex):331return set(regex) - set('()|*')332333#Settings334DEBUG = False335use_lambda = False336lambda_symbol = '_'337alphabet = None338339#Main340regex = '(0)*1(0|10)'341342#Check343if not is_valid_regex(regex):344exit(0)345346#Preprocess regex and generate the alphabet347p_regex = preprocess(regex)348alphabet = gen_alphabet(p_regex)349#add optional letters that don't appear in the expression350extra = ''351alphabet = alphabet.union(set(extra))352353#Construct354tree = RegexTree(p_regex)355if DEBUG:356tree.write()357dfa = tree.toDfa()358359#Test360message = '000110'361print('This is the regex : ' + regex)362print('This is the alphabet : ' + ''.join(sorted(alphabet)))363print('This is the automata : \n')364dfa.write()365print('\nTesting for : "'+message+'" : ')366dfa.run(message)367368