r""" Relational Structures: search for finite algebras and relations AUTHOR: -- Peter Jipsen (2007-02-25): initial version TODO: convert output of the search routines to RelStructure objects. NOTE: currently this file is Python code with no SAGE dependencies (except the use of sloane_find to test the searches) TUTORIAL: I. The Basics 1. Relational Structure Format A. The SAGE RelStructure Class Finite (universal) algebras and relational structures in SAGE are implemented as dictionaries of operation tables and relations. The underlying set for a relational structure of size n is {0,1,...,n-1}. A key "labels" (if defined) contains a dictionary with a printable label for each element. A key "algtype" contains a dictionary with the arity of each operation. A key "reltype" contains a dictionary with the arity of each relation. Operations and relations are accessed by keys that represent their names. E.g. binary operations "+", "*", "//", "\\", "^^", "vv" unary operations "~", "-", "inv" constant operations "e", "top", "bot" binary relations "<=", "-<" (covering) 2. Search routines The current file contains code to search for nonisomorphic groupoids and binary relations. A groupoid is given by an nxn (python) matrix with entires 0,...,n-1 A binary relation is given by an nxn (python) 0-1-matrix A partial groupoid or binary relation may also have -1 = undefined as entry Permutations of {0,...,n-1} are represented by lists of length n. Searches are done in a simple minded way by backtracking. Examples: Find all nonisomorphic algebras of a given size. find_groupoids(3) find_semigroups(5) # associative groupoids find_commutative_semigroups(5) find_bands(6) # idempotent semigroups find_monoids(6) # semigroups with identity find_commutative_monoids(7) find_semilattices(7) # commutative bands find_lattices(8) # semilattices with identity find_algebras_test(True) # test all find_ methods and compare # lengths of lists with sloane_find Find all nonisomorphic binary relations of a given size. find_binary_relations(5) find_simple_digraphs(6) #binary relations without loops find_simple_graphs(7) #simple digraphs with arrows in both directions find_relations_test(True) # test all find_ methods and compare # lengths of lists with sloane_find """ #***************************************************************************** # Copyright (C) 2007 Peter Jipsen # # Distributed under the terms of the GNU General Public License (GPL) # http://www.gnu.org/licenses/ #***************************************************************************** from sage.structure.sage_object import SageObject import datetime class RelStructure(SageObject): def __len__(self): return len(self[0]) def __str__(self): if self.name != None: return self.name else: return repr(self) def _repr_(self): return "{"+",\n".join([str(k)+":"+\ (opstr(v) if isinstance(v,list) else str(v))\ for k,v in vars(self).iteritems()])+"}" def _latex_(self): return repr(self) ### General properties def order(self): """ Returns the number of elements. """ return len(self[0]) def size(self): """ Returns the number of elements. """ return len(self[0]) def permutations(m,n): """ return generator for all permutations of {m,...,n} """ p = [m+i for i in range(n-m)] yield p n = len(p) j = 1 while j>=0: q = range(n) j = n-2 while j>=0 and p[j]>p[j+1]: j = j-1 if j>=0: for k in range(j): q[k] = p[k] k = n-1 while p[j]>p[k]: k = k-1 q[j] = p[k] i = n-1 while i>j: q[i] = p[j+n-i] i = i-1 q[j+n-k]=p[j] p = q yield q def inverse_permutation(p): q = range(len(p)) for i in range(len(p)): q[p[i]]=i return q def check_permutations(m): """ Return False if every completion of some isomorphic copy q(m) of m will be lexicographically smaller than m Return True otherwise """ global perms, invperms, pflags, pflags_list n = len(m) for k in range(len(perms)): if pflags[k]: q = perms[k] qi = invperms[k] equal = True for i in range(n): if equal: #equal means q[m[qi[i]][qi[j]]] == m[i][j] != -1 for j in range(n): if equal: mqi = m[qi[i]][qi[j]] mij = m[i][j] equal = (mqi>-1 and mij==q[mqi]) if mqi>-1 and q[mqi]-1 and mij>-1 and q[mqi]>mij: #get rid of q pflags[k] = False pflags_list += [k] return True def check_associativity(m): """ Return False if the partial operation table m violates associativity Return True otherwise """ global entries_list n = len(m) for x in range(n): for y in range(n): xy = m[x][y] if xy>-1: for z in range(n): xyz = m[xy][z]; if xyz>-1: if m[y][z]>-1: if m[x][m[y][z]]>-1: if xyz!=m[x][m[y][z]]: return False else: m[x][m[y][z]] = xyz entries_list += [(x,m[y][z])] else: if m[y][z]>-1 and m[x][m[y][z]]>-1: m[xy][z] = m[x][m[y][z]] entries_list += [(xy,z)] return True def check_selfdual(m): """ Return False if the partial meet table m is not selfdual wrt sd map Return True otherwise sdl is a global list s.t. sdl[i][sdl[i][x]]=x selfdual means m[x][y]=x => m[s[x]][s[y]]=s[y] for some s in sdl """ global entries_list,sd n = len(m) for x in range(n): for y in range(n): xy = m[x][y] if xy==x: if x>y: return False #force linear extension sxsy = m[sd[x]][sd[y]] if sxsy>-1: if sxsy!=sd[y]: return False else: m[sd[x]][sd[y]] = sd[y] entries_list += [(sd[x],sd[y])] return True def complete_operation(m,i,j,associative=False,commutative=False,selfdual=False): """ find next i,j where alg[i][j] = -1 = undefined; for each val=0..n-1 set alg[i][j] = val, check permutations if ok, complete_operation(m,i,j+1) then restore and return """ global algebra_list,entries_list,pflags,pflags_list ok = True n = len(m) while ok and i-1: j = j+1 if j>=n: j = 0 i = i+1 else: ok = False if ok: if not selfdual or check_selfdual(m): algebra_list.append([m[i][:] for i in range(n)]) else: for val in range(n): ok = True el = len(entries_list) m[i][j] = val entries_list += [(i,j)] if commutative: m[j][i] = val entries_list += [(j,i)] if selfdual: ok = check_selfdual(m) if ok and associative: ok = check_associativity(m) pl = len(pflags_list) if ok: ok = check_permutations(m) if ok: complete_operation(m,i,j+1,associative,commutative,selfdual) while len(entries_list)>el: e = entries_list.pop() m[e[0]][e[1]] = -1 while len(pflags_list)>pl: p = pflags_list.pop() pflags[p] = True def find_groupoids(n,associative=False,commutative=False,idempotent=False,identity=False): """ Return list of nonisomorphic groupoids of size n as nxn matrices """ global algebra_list,perms,invperms,entries_list,pflags,pflags_list algebra_list=[] entries_list=[] m = [[-1 for x in range(n)] for x in range(n)] if idempotent: for x in range(n): m[x][x] = x if identity: perms = [[0]+p for p in permutations(1,n)] for x in range(n): m[0][x] = x m[x][0] = x elif associative and commutative and idempotent: perms = [[0]+p for p in permutations(1,n)] #requires lex order for x in range(n): m[0][x] = 0 m[x][0] = 0 else: perms = [p for p in permutations(0,n)] invperms = [inverse_permutation(p) for p in perms] pflags = [True for i in range(len(perms))] pflags_list = [] complete_operation(m,0,0,associative,commutative) return algebra_list def find_commutative_groupoids(n): """ Return list of nonisomorphic commutative groupoids of size n as nxn matrices """ return find_groupoids(n,commutative=True) def find_semigroups(n): """ Return list of nonisomorphic semigroups of size n as nxn matrices """ return find_groupoids(n,associative=True) def find_commutative_semigroups(n): """ Return list of nonisomorphic commutative semigroups of size n as nxn matrices """ return find_groupoids(n,associative=True,commutative=True) def find_bands(n): """ Return list of nonisomorphic bands of size n as nxn matrices """ return find_groupoids(n,associative=True,idempotent=True) def find_semilattices(n): """ Return list of nonisomorphic semilattices of size n as nxn matrices 0 is absorbtive so best to consider these algebras as meet-semilattices """ return find_groupoids(n,associative=True,commutative=True,idempotent=True) def find_lattices(n): """ Return list of nonisomorphic lattices of size n as nxn matrices """ if n==0: return [[]] alg_list = find_semilattices(n-1) alg_list = [[m[i]+[i] for i in range(n-1)]+[range(n)] for m in alg_list] return [Poset(meet2uc(m)) for m in alg_list] def sdmap(p,n): k = len(p) return [n-1-p[k-1-i] for i in range(k)] def find_selfdual_lattices(n): """ Return list of nonisomorphic selfdual lattices of size n as nxn matrices """ global algebra_list,perms,invperms,entries_list,pflags,pflags_list,sd algebra_list=[] entries_list=[] associative = True commutative = True m = [[-1 for x in range(n)] for x in range(n)] sd = range(n) pairs = int(n)/2 for x in range(pairs): sd[x] = n-1-x sd[n-1-x] = x perms = [[0]+p for p in permutations(1,pairs)] perms =[p+q+sdmap(p,n) for p in perms for q in permutations(pairs,n-pairs)] for x in range(n): m[x][x] = x m[n-1][x] = x m[x][n-1] = x m[0][x] = 0 m[x][0] = 0 invperms = [inverse_permutation(p) for p in perms] pflags = [True for i in range(len(perms))] pflags_list = [] complete_operation(m,0,0,associative,commutative,True) return [Poset(meet2uc(m)) for m in algebra_list] def find_monoids(n): """ Return list of nonisomorphic monoids of size n as nxn matrices """ return find_groupoids(n,associative=True,identity=True) def find_commutative_monoids(n): """ Return list of nonisomorphic commutative monoids of size n as nxn matrices """ return find_groupoids(n,associative=True,commutative=True,identity=True) def find_algebras_test(check_sloane=False): if check_sloane: n = 7 else: n = 6 tl = [ [len(find_groupoids(i)) for i in range(1,4)], [len(find_semigroups(i)) for i in range(1,5)], #associative groupoids [len(find_commutative_semigroups(i)) for i in range(1,5)], [len(find_bands(i)) for i in range(1,6)], #idempotent semigroups [len(find_monoids(i)) for i in range(1,n)], #semigroups with identity [len(find_commutative_monoids(i)) for i in range(1,7)], [len(find_semilattices(i)) for i in range(1,7)],#commutative bands [len(find_lattices(i)) for i in range(1,7)]] #semilattices with identity if not check_sloane: return tl sl = [[t]+sloane_find(t,1)[0] for t in tl] return [[s[0],s[3][:8],s[2]] for s in sl] ############################################################################## # Search for linearly ordered structures def is_ordered(m): for x in range(len(m)-1): for y in range(len(m)): if m[x+1][y]>-1 and m[x][y]>m[x+1][y]: return False if m[y][x+1]>-1 and m[y][x]>m[y][x+1]: return False return True def is_associative(m): for x in range(len(m)): for y in range(len(m)): for z in range(len(m)): if m[m[x][y]][z]!=m[x][m[y][z]]: return False return True def is_commutative(m): for x in range(len(m)): for y in range(len(m)): if m[x][y]!=m[y][x]: return False return True def is_idempotent(m): for x in range(len(m)): if m[x][x]!=x: return False return True def is_partial(m): for x in range(len(m)): for y in range(len(m)): if m[x][y]<0 or m[x][y]>=len(m): return True return False def is_ordered_semigroup(m): return is_ordered(m) and is_associative(m) def complete_ordered_operation(m,i,j,associative=False,commutative=False): """ find next i,j where alg[i][j] = -1 = undefined; for each val=0..n-1 set alg[i][j] = val, check axioms and permutations if ok, complete_ordered_operation(m,i,j+1) then restore and return """ global entries_list ok = True n = len(m) while ok and i-1: j = j+1 if j>=n: j = 0 i = i+1 else: ok = False if ok: algebra_list.append([m[i][:] for i in range(n)]) else: ok = True k = 0 if i>0: k = m[i-1][j] if j>0: k = max(k,m[i][j-1]) for val in range(k,n): el = len(entries_list) m[i][j] = val entries_list += [(i,j)] if commutative: m[j][i] = val entries_list += [(j,i)] if associative: ok = check_associativity(m) if ok: ok = is_ordered(m) if ok: complete_ordered_operation(m,i,j+1,associative,commutative) while len(entries_list)>el: e = entries_list.pop() m[e[0]][e[1]] = -1 def find_ordered_groupoids(n,associative=False,commutative=False,idempotent=False,identity=False,id=None,zero=False): """ Return list of linearly ordered groupoids of size n as nxn matrices. The ordering is the natural one 0<1<2<... x <= y (topologically sorted) n = len(le) uc = [] for a in range(n): S = [] # accumulate upper covers of a for x in range(a+1,n): if le[a][x]==1: y = len(S)-1 while y>=0 and le[S[y]][x]==0: y = y-1 if y<0: S.append(x) uc.append(S) return uc def meet2uc(m): n = len(m) uc = [] for a in range(n): S = [] for x in range(a+1,n): if m[a][x]==a: y = len(S)-1 while y>=0 and m[S[y]][x]!=S[y]: y = y-1 if y<0: S.append(x) uc.append(S) return uc class Poset(RelStructure): def __init__(self,uppercovers): self.uc = dict([(x,uppercovers[x]) for x in range(len(uppercovers))]) def size(self): return len(self.uc) def get_leq(self): if hasattr(self,'leq'): return self.leq n = self.size() leq = [[0 for y in range(n)] for x in range(n)] for i in range(n): leq[i][i] = 1 for j in self.uc[i]: leq[i][j] = 1 for i in range(n): for j in range(i+1,n): if leq[i][j]: for k in range(j+1,n): if leq[j][k]: leq[i][k] = 1 self.leq = leq return leq def get_downsets(self): if hasattr(self,'down'): return self.down n = self.size() le = self.get_leq() down = [[x for x in range(n) if le[x][y]==1] for y in range(n)] self.down = down return down def get_lowercovers(self): if hasattr(self,'lc'): return self.lc lc = [[] for x in range(self.size())] for i in range(self.size()): for j in self.uc[i]: lc[j].append(i) self.lc = lc return lc def get_join(self): # Freese-Jezek-Nation p217 if hasattr(self,'join'): return self.join n = self.size() join = [[0 for x in range(n)] for x in range(n)] le = self.get_leq() if not all([le[x][n-1]==1 for x in range(n)]): return "poset has no top element" p = range(n-1,-1,-1) uc = [sorted([p[y] for y in self.uc[x]]) for x in p] S = [] for x in range(n): # x=x_k join[x][x] = x for y in S: T = [] for z in uc[x]: T.append(join[y][z]) # T = {x_i \vee z : z>-x_k} q = T[0] for z in T[1:]: if z>q: q = z #error in Listing 11.9 for z in T: if not le[p[q]][p[z]]: return "poset is not a semilattice: x="+x+" y="+y join[x][y] = q join[y][x] = q S.append(x) self.join = permuted_binary_op(join,p) return self.join def get_meet(self): # Freese-Jezek-Nation p217 if hasattr(self,'meet'): return self.meet n = self.size() meet = [[0 for x in range(n)] for x in range(n)] le = self.get_leq() if not all([le[0][x]==1 for x in range(n)]): return "poset has no bottom element" lc = self.get_lowercovers() S = [] for x in range(n): # x=x_k meet[x][x] = x for y in S: T = [] for z in lc[x]: T.append(meet[y][z]) # T = {x_i \wedge z : z>-x_k} q = T[0] for z in T[1:]: if z>q: q = z for z in T: if not le[z][q]: return "poset is not a semilattice: x="+x+" y="+y meet[x][y] = q meet[y][x] = q S.append(x) self.meet = meet return meet def is_distributive(self): jn = self.get_join() mt = self.get_meet() n = self.size() for x in range(n): for y in range(n): for z in range(n): if mt[x][jn[y][z]]!=jn[mt[x][y]][mt[x][z]]: return False return True def is_selfdual(self): lc = self.get_lowercovers() n = self.size() perms = [[n-1]+p+[0] for p in permutations(1,n-1)] for p in perms: plc = {} for i in range(n): plc[i] = [p[y] for y in lc[p[i]]] plc[i].sort() if plc == self.uc: return True return False def get_uniqueLC(self): # find subset of elements with unique lower cover lc = self.get_lowercovers() return [x for x in range(self.size()) if len(lc[x])==1] def get_uniqueUC(self): return [x for x in range(self.size()) if len(self.uc[x])==1] def get_basicsets(self): n = self.size() le = self.get_leq() ji = self.get_uniqueLC() mi = self.get_uniqueUC() return [[x for x in ji if le[x][y]==1] for y in mi] def subposet(self,S): # S a subset of the elements of self leq = self.get_leq() leq = [[leq[x][y] for y in S] for x in S] Q = Poset(leq2uc(leq)) if hasattr(self,'labels'): Q.label = [self.labels[x] for x in S] return Q def get_labels(self): if hasattr(self,'labels'): return self.labels return range(self.size()) def heights(self): # assumes P is topologically sorted l=[0 for x in range(self.size())] for i in range(self.size()): for j in self.uc[i]: if l[j]\n\n\n\n\n\n\n\n\n\n""" print st def Chain(n): c = [[x+1] for x in range(n)] c[n-1] = [] return Poset(c) def Antichain(n): c = [[] for x in range(n)] return Poset(c) def Pentagon(): p = Poset([[1,2],[4],[3],[4],[]]) p.pos = [[2,0],[0,2],[3,1],[3,3],[2,4]] return p def Diamond(n): c = [[n-1] for x in range(n)] c[0] = [x for x in range(1,n-1)] c[n-1] = [] return Poset(c) def opstr(m): # convert 2-dim list to a compact string for display nr = len(m) if nr == 0: return "[]" nc = len(m[0]) s = [[str(y).replace(' ','') for y in x] for x in m] width = [max([len(s[x][y]) for x in range(nr)]) for y in range(nc)] s = [[" "*(width[y]-len(s[x][y]))+s[x][y] for y in range(nc)]\ for x in range(nr)] rows = ["["+",".join(x)+"]" for x in s] s = "[\n"+",\n".join(rows)+"]" return s def is_automorphism(p,m): #p[m[i][j]] == m[p[i]][p[j]] n = len(m) for i in range(n): for j in range(n): if p[m[i][j]] != m[p[i]][p[j]]: return False return True def flat(m): return reduce(lambda x,y:x+y,m) def printto(fname,obj): fh = open(fname,'w') fh.write(str(obj)) fh.close() def savelist(fname,lst,doc=""): fh = open(fname,'w') if doc!="": fh.write('# '+doc) fh.write('[\n%s]'%(',\n'.join([repr(a) for a in lst]))) fh.close() def findandsave(clsname,clsabbr,clsfind,n): algs = [clsfind(i) for i in range(1,n+1)] count = [len(x) for x in algs] algs = reduce(lambda x,y:x+y,algs) for i in range(len(algs)): algs[i].num = i fh = open(clsabbr+str(n)+'.txt','w') fh.write('# %s up to size %s\n\n%s_count = %s # total = %s\n%s_index = %s\ \n\n%s_list = [\n%s]\n\n# Computed by Peter Jipsen with %s on %s\ \n# This file can be used in Python with execfile("filename")'\ %(clsname,n,clsabbr,count,len(algs),clsabbr,\ [sum(count[:i]) for i in range(n)],clsabbr,\ ',\n'.join([repr(a) for a in algs]),version()[0:version()\ .index(',')],datetime.datetime.today().date())) fh.close() return algs class POGroupoid(Poset): def __init__(self,uc,mult): self.uc = dict([(x,uc[x]) for x in range(len(uc))]) self.mult = mult def get_backslash(self): #calculate the left residual (assumed to exist) if hasattr(self,'bs'): return self.bs # xz <= y iff z <= x\y mult = self.mult join = self.get_join() n = self.size() bs = [[0 for x in range(n)] for y in range(n)] for x in range(n): for y in range(n): z = n-1 # print (join,mult) while z>=0 and join[mult[x][z]][y]!=y: z = z-1 if z<0: raise "x\y not defined for x="+str(x)+" y="+str(y) bs[x][y] = z self.bs = bs return bs def get_slash(self): #calculate the right residual (assumed to exist) if hasattr(self,'sl'): return self.sl # zy <= x iff z <= x/y mult = self.mult join = self.get_join() n = self.size() sl = [[0 for x in range(n)] for y in range(n)] for x in range(n): for y in range(n): z = n-1 while z>=0 and join[mult[z][y]][x]!=x: z = z-1 if z<0: raise "x/y not defined for x="+x+" y="+y sl[x][y] = z self.sl = sl return sl def is_lattice_ordered(self): jn = self.get_join() m = self.mult n = self.size() for x in range(n): for y in range(n): for z in range(n): if m[x][jn[y][z]]!=jn[m[x][y]][m[x][z]]: return False if m[jn[x][y]][z]!=jn[m[x][z]][m[y][z]]: return False return True def is_GBL(self): bs = self.get_backslash() sl = self.get_slash() n = self.size() for x in range(n): for y in range(n): if self.join[x][y]==y and \ not (x==self.mult[y][bs[y][x]] and \ x==self.mult[sl[x][y]][y]): return False return True def is_GMV(self): bs = self.get_backslash() sl = self.get_slash() n = self.size() for x in range(n): for y in range(n): if self.join[x][y]==y and \ not (y==sl[x][bs[y][x]] and \ y==bs[sl[x][y]][x]): return False return True def is_MV(self): return self.is_commutative() and self.is_GMV() def is_involutive(self,d=None): bs = self.get_backslash() sl = self.get_slash() if d == None: d = self.d for x in range(self.size()): if sl[d][bs[x][d]]!=x: return False if bs[sl[d][x]][d]!=x: return False return True def is_cyclic(self,d=None): bs = self.get_backslash() sl = self.get_slash() if d == None: d = self.d for x in range(self.size()): if bs[x][d]!=sl[d][x]: return False return True def get_automorphisms(self): # assumes 0=bottom, n-1=top n = self.size() join = self.get_join() perms = [[0]+p+[n-1] for p in permutations(1,n-1)] return [p for p in perms if is_automorphism(p,join) and\ is_automorphism(p,self.mult)] def all_involutive(self): # return all involutive FL-algs with the RL reduct "self" n = self.size() infl_list = [] aut = self.get_automorphisms() for d in range(n): if self.is_involutive(d) and all([p[d]>=d for p in aut]): infl_list.append(InFL(self.uc,self.mult,d)) return infl_list def is_reduct_of_involutive(self): return self.all_involutive()!=[] class RL(POGroupoid): def __init__(self,uc,mult,e=None,num=None): self.mult = mult self.uc = dict([(x,uc[x]) for x in range(len(uc))]) self.e = mult.index(range(len(mult))) if num!=None: self.num = num def _repr_(self): return "\nRL(e = %s, "%self.e+\ ("num = %s,"%self.num if hasattr(self,'num') else "")+\ "\nuc = %s,\nmult = %s)"%(self.uc,opstr(self.mult)) @staticmethod def find(n): return \ findandsave('Residuated lattices','RL',find_residuated_lattices,n) def get_rl_frame(self): le = self.get_leq() W = self.get_uniqueLC() E = [z for z in W if le[z][self.e]] bclo = self.get_basicsets() circ = [[[z for z in W if le[z][self.mult[x][y]]] for y in W]\ for x in W] return RLFrame(W,bclo,circ,E,self.num) class RLFrame(RelStructure): def __init__(self,W,bclo,circ,E,num=None): self.W = W self.E = E self.bclo = bclo # basic closed sets self.circ = dict([(W[x],dict([(W[y],circ[x][y])\ for y in range(len(W))])) for x in range(len(W))]) if num!=None: self.num = num def _repr_(self): return "\nRLFrame(W = %s, E = %s, "%(self.W,self.E)+\ ("num = %s,"%self.num if hasattr(self,'num') else "")+\ "\nbclo = %s,\ncirc = %s)"%(self.bclo,\ opstr([[self.circ[x][y] for y in self.W] for x in self.W])) class InFL(POGroupoid): def __init__(self,uc,mult,d,e=None,num=None): self.mult = mult self.uc = dict([(x,uc[x]) for x in range(len(uc))]) self.e = mult.index(range(len(mult))) self.d = d if num!=None: self.num = num def _repr_(self): bs = self.get_backslash() sl = self.get_slash() tilde = dict([(x,bs[x][self.d]) for x in range(self.size())]) minus = dict([(x,sl[self.d][x]) for x in range(self.size())]) return "\nInFL(e = %s, d = %s, "%(self.e,self.d)+\ ("num = %s,"%self.num if hasattr(self,'num') else "")+\ "\nuc = %s,\n# tilde = %s%s\nmult = %s)"\ %(self.uc,tilde,"\n# minus = "+str(minus) \ if minus!=tilde else " = minus",opstr(self.mult)) @staticmethod def find(n): return \ findandsave('Involutive FL-algebras','InFL',find_involutive_FL,n) def get_dualmult(self): n = self.size() bs = self.get_backslash() sl = self.get_slash() return [[bs[self.mult[sl[self.d][y]][sl[self.d][x]]][self.d]\ for y in range(n)] for x in range(n)] def get_infl_frame(self): le = self.get_leq() W = self.get_uniqueLC() E = [z for z in W if le[z][self.e]] D = [z for z in W if le[z][self.d]] bclo = self.get_basicsets() circ = [[[z for z in W if le[z][self.mult[x][y]]] for y in W]\ for x in W] bs = self.get_backslash() sl = self.get_slash() return InFLFrame(W,bclo,circ,E,D,(self.num if hasattr(self,'num') \ else None),\ dict([(x,[z for z in W if le[z][bs[x][self.d]]]) for x in W]),\ dict([(x,[z for z in W if le[z][sl[self.d][x]]]) for x in W])) class InFLFrame(RLFrame): def __init__(self,W,bclo,circ,E,D,num=None,tilde=None,minus=None): RLFrame.__init__(self,W,bclo,circ,E,num) self.D = D self.tilde = tilde self.minus = minus def _repr_(self): return "\nInFLFrame(W = %s, E = %s, D = %s, "%(self.W,self.E,self.D)+\ ("num = %s,"%self.num if hasattr(self,'num') else "")+\ "\nbclo = %s,\n# tilde = %s%s\ncirc = %s)"%(self.bclo,\ self.tilde,'n# minus = '+str(self.minus) \ if self.tilde != self.minus else ' = minus',\ opstr([[self.circ[x][y] for y in self.W] for x in self.W])) def get_external_negations(self): """ find all pairs t,m such that (circ,t,m) is a bi-involutive monoid """ W = self.W n = len(W) perms = permutations(0,n) biinv = [] for p in perms: q = inverse_permutation(p) pw = dict([(W[i],W[p[i]]) for i in range(n)]) if p <= q: if all([[pw[z] for z in self.circ[x][y]]==\ self.circ[pw[y]][pw[x]] for x in W for y in W]): biinv.append([p,q]) return biinv def is_partially_ordered(m,uc,le): n = len(m) for x in range(n): for y in uc[x]: for z in range(n): if m[x][z]>-1 and m[y][z]>-1 and le[m[x][z]][m[y][z]]==0: return False if m[z][x]>-1 and m[z][y]>-1 and le[m[z][x]][m[z][y]]==0: return False return True def is_op_automorphism(m,q): n = len(m) for i in range(n): for j in range(n): if m[q[i]][q[j]]!=q[m[i][j]]: return false return true def is_rel_automorphism(m,q): n = len(m) for i in range(n): for j in range(n): if m[q[i]][q[j]]!=m[i][j]: return false return true def is_po_automorphism(uc,q): n = len(uc) for i in range(n): for j in uc[i]: if not q[j] in uc[q[i]]: return false return true def complete_po_operation(m,uc,le,i,j,associative=False,commutative=False): """ find next i,j where alg[i][j] = -1 = undefined; for each val=0..n-1 set alg[i][j] = val, check axioms and permutations if ok, complete_ordered_operation(m,i,j+1) then restore and return """ global algebra_list,entries_list,pflags,pflags_list ok = True n = len(m) while ok and i-1: j = j+1 if j>=n: j = 0 i = i+1 else: ok = False if ok: algebra_list.append([m[i][:] for i in range(n)]) else: for val in range(n): ok = True el = len(entries_list) m[i][j] = val entries_list += [(i,j)] if commutative: m[j][i] = val entries_list += [(j,i)] if associative: ok = check_associativity(m) if ok: ok = is_partially_ordered(m,uc,le) pl = len(pflags_list) if ok: ok = check_permutations(m) if ok: complete_po_operation(m,uc,le,i,j+1,associative,commutative) while len(entries_list)>el: e = entries_list.pop() m[e[0]][e[1]] = -1 while len(pflags_list)>pl: p = pflags_list.pop() pflags[p] = True def find_po_groupoids(po,associative=False,commutative=False,idempotent=False,identity=False,id=None,zero=False): """ Return list of partially ordered groupoids of size n as nxn matrices. The ordering is given by the list uc of upper covers: y in uc[x] iff x--1: for z in range(n): xz = m[x][z] if xz>-1: yz = join[y][z] xyz = m[x][yz] if xyz>-1: if xyz!=join[xy][xz]: return False else: m[x][yz] = join[xy][xz] entries_list += [(x,yz)] zy = m[z][y] if zy>-1: xz = join[x][z] xyz = m[xz][y] if xyz>-1: if xyz!=join[xy][zy]: return False else: m[xz][y] = join[xy][zy] entries_list += [(xz,y)] return True def check_divisible(m,down): """ Return False if partial optable m is not divisible i.e. x<=y implies x=m[y][u]=m[v][y] for some u,v Return True otherwise Compute list S of elements in y-row (col). Compute k = |down[y] - S|. if k>#undefined in y-row, return False if #undefined=1 and k=1 let undefined = this elt. """ global entries_list n = len(m) for y in range(n): S = set([m[y][u] for u in range(n) if m[y][u]>-1]) E = set(down[y]).difference(S) U = [u for u in range(n) if m[y][u]==-1] if len(E)>len(U): return False if len(U)==1 and len(E)==1: m[y][U[0]] = E.pop() entries_list += [(y,U[0])] for y in range(n): S = set([m[u][y] for u in range(n) if m[u][y]>-1]) E = set(down[y]).difference(S) U = [u for u in range(n) if m[u][y]==-1] if len(E)>len(U): return False if len(U)==1 and len(E)==1: m[U[0]][y] = E.pop() entries_list += [(U[0],y)] return True def complete_l_operation(m,join,down,uc,i,j,associative=False,commutative=False,divisible=False): """ find next i,j where alg[i][j] = -1 = undefined; for each val=0..n-1 set alg[i][j] = val, check axioms and permutations if ok, complete_ordered_operation(m,i,j+1) then restore and return """ global algebra_list,entries_list,pflags,pflags_list ok = True n = len(m) while ok and i-1: j = j+1 if j>=n: j = 0 i = i+1 else: ok = False if ok: if check_lattice_ordered(m,join) and \ (not divisible or check_divisible(m,down)): algebra_list.append(POGroupoid(uc,[x[:] for x in m])) else: for val in range(n): if all([join[x][i]!=i or m[x][j]==-1 or join[m[x][j]][val]==val \ for x in range(i)]) and \ all([join[x][j]!=j or m[i][x]==-1 or join[m[i][x]][val]==val \ for x in range(j)]): ok = True el = len(entries_list) m[i][j] = val entries_list += [(i,j)] if commutative: m[j][i] = val entries_list += [(j,i)] if ok: ok = check_lattice_ordered(m,join) if ok and divisible: ok = check_divisible(m,down) if ok and associative: ok = check_associativity(m) pl = len(pflags_list) if ok: ok = check_permutations(m) if ok: complete_l_operation(m,join,down,uc,i,j+1,associative,commutative,divisible) while len(entries_list)>el: e = entries_list.pop() m[e[0]][e[1]] = -1 while len(pflags_list)>pl: p = pflags_list.pop() pflags[p] = True def find_l_groupoids(l,associative=False,commutative=False,divisible=False,idempotent=False,identity=False,id=None,zero=False): """ Return list of partially ordered groupoids of size n as nxn matrices. The ordering is given by the list uc of upper covers: y in uc[x] iff x-1: s = 1 else: s = 0 else: s = id; t = id+1 for e in range(s,t): for x in range(n): m[e][x] = x m[x][e] = x perms = [[0]+p+[n-1] for p in permutations(1,n-1) \ if is_po_automorphism(l.uc,[0]+p+[n-1])] if all([p[e]>=e for p in perms]): # eliminate isomorphic (P,e) perms = [p for p in perms if p[e]==e] #keep e fixed invperms = [inverse_permutation(p) for p in perms] pflags = [True for i in range(len(perms))] pflags_list = [] complete_l_operation(m,join,down,l.uc,0,0,associative,commutative,divisible) for x in range(n): m[e][x] = -1 m[x][e] = -1 else: invperms = [inverse_permutation(p) for p in perms] pflags = [True for i in range(len(perms))] pflags_list = [] complete_l_operation(m,join,down,l.uc,0,0,associative,commutative,divisible) return algebra_list def find_l_semigroups(l): return find_l_groupoids(l,associative=True) def find_l_commutative_semigroups(l): return find_l_groupoids(l,associative=True,commutative=True) def find_l_bands(l): return find_l_groupoids(l,associative=True,idempotent=True) def find_l_semilattices(l): return find_l_groupoids(l,associative=True,commutative=True,idempotent=True) def find_l_monoids(l,e=None): return find_l_groupoids(l,associative=True,identity=True,id=e) def find_l_commutative_monoids(l,e=None): return find_l_groupoids(l,associative=True,commutative=True,identity=True,id=e) def find_l_monoids_with_zero(l,e=None): return find_l_groupoids(l,associative=True,identity=True,id=e,zero=True) def find_commutative_l_monoids_with_zero(l,e=None): return find_l_groupoids(l,associative=True,commutative=True,identity=True,id=e,zero=True) def find_integral_l_monoids_with_zero(l): return find_l_monoids_with_zero(l,l.size()-1) def find_commutative_integral_l_monoids_with_zero(l): return find_l_commutative_monoids_with_zero(l,l.size()-1) def find_residuated_lattices(n): algs = [] for l in find_lattices(n): algs.extend(find_l_monoids_with_zero(l)) return [RL(l.uc,l.mult) for l in algs] def find_selfdual_lattices_slow(n): return [x for x in find_lattices(n) if x.is_selfdual()] def find_selfdual_residuated_lattices(n): alg_list = [] for l in find_selfdual_lattices_slow(n): alg_list.extend(find_l_monoids_with_zero(l)) return alg_list def find_involutive_FL(n): alg_list = [] for l in find_selfdual_lattices_slow(n): alg_list.extend(find_l_monoids_with_zero(l)) fl = [] for l in alg_list: fl.extend(l.all_involutive()) return fl def find_GBL(l): # of course they are all commutative [Jipsen-Montagna 2006] return find_l_groupoids(l,associative=True,divisible=True,identity=True,id=l.size()-1,zero=True) def find_basic_logic_algebras(l): return find_l_groupoids(l,associative=True,commutative=True,divisible=True,identity=True,id=l.size()-1,zero=True) ############################################################################### # Search for binary relations def check_relation_permutations(m): """ Return False if every completion of some isomorphic copy q(m) of m will be lexicographically smaller than m Return True otherwise """ global perms, invperms, pflags, pflags_list n = len(m) for k in range(len(perms)): if pflags[k]: q = perms[k] qi = invperms[k] equal = True for i in range(n): if equal: #equal means m[qi[i]][qi[j]] == m[i][j] != -1 for j in range(n): if equal: mqi = m[qi[i]][qi[j]] mij = m[i][j] equal = (mqi>-1 and mij==mqi) if mqi>-1 and mqi-1 and mij>-1 and mqi>mij: #get rid of q pflags[k] = False pflags_list += [k] return True def complete_relation(m,i,j,symmetric=False): """ find next i,j where alg[i][j] = -1 = undefined; for each val=0..n-1 set alg[i][j] = val, check permutations if ok, complete_operation(m,i,j+1) then restore and return """ global pflags_list ok = True n = len(m) while ok and i-1: j = j+1 if j>=n: j = 0 i = i+1 else: ok = False if ok: relation_list.append([m[i][:] for i in range(n)]) else: for val in range(2): m[i][j] = val if symmetric: m[j][i] = val pl = len(pflags_list) ok = check_relation_permutations(m) if ok: complete_relation(m,i,j+1,symmetric) m[i][j] = -1 if symmetric: m[j][i] = -1 while len(pflags_list)>pl: p = pflags_list.pop() pflags[p] = True def find_binary_relations(n,irreflexive=False,symmetric=False): """ Return list of nonisomorphic binary relations of size n as 0-1-matrices """ global relation_list,perms,invperms,entries_list,pflags,pflags_list relation_list=[] perms = [p for p in permutations(0,n)] invperms = [inverse_permutation(p) for p in perms] pflags = [True for i in range(len(perms))] pflags_list = [] m = [[-1 for x in range(n)] for x in range(n)] if irreflexive: for x in range(n): m[x][x]=0 complete_relation(m,0,0,symmetric) return relation_list def find_simple_digraphs(n): """ Return list of nonisomorphic simple digraphs of size n as adjacency matrices Simple digraphs are binary relations without loops """ return find_binary_relations(n,irreflexive=True) def find_symmetric_relations(n): """ Return list of nonisomorphic symmetric binary relations of size n as adjacency matrices Symmetric binary relations correspond to graphs with loops allowed """ return find_binary_relations(n,symmetric=True) def find_simple_graphs(n): """ Return list of nonisomorphic graphs of size n as adjacency matrices Here graphs are symmetric binary relations without loops """ return find_binary_relations(n,irreflexive=True,symmetric=True) def find_relations_test(check_sloane=False): tl = [ [len(find_binary_relations(i)) for i in range(1,5)], [len(find_simple_digraphs(i)) for i in range(1,6)], #binary relations without loops [len(find_symmetric_relations(i)) for i in range(1,6)], [len(find_simple_graphs(i)) for i in range(1,7)]] #simple digraphs with arrows in both directions if not check_sloane: return tl sl = [[t]+sloane_find(t,1)[0] for t in tl] return [[s[0],s[3][:8],s[2]] for s in sl]