def get_normal(self, N=None, K=None, inverse=False, force=False): # remove null summands, and recurse assert N is None or N.n == 0 assert K is None or K.n == 1 ring = self.ring spaces = list(self.items) assert len(spaces) > 1, str(self) # depth-first recurse fs = [space.get_normal(N, K, force=force) for space in spaces] f = reduce(Lin.direct_sum, fs) assert isinstance(f.tgt, AddSpace) spaces = list(f.tgt.items) idx = 0 while idx < len(spaces): if force and spaces[idx].n == 0 or spaces[idx] == N: spaces.pop(idx) tgt, src = AddSpace(ring, *spaces, N=N), f.tgt g = Lin(tgt, src, elim.identity(ring, tgt.n)) f = g * f done = False else: idx += 1 if inverse: f = f.transpose() # permutation matrix return f
def iso(cls, tgt, src): assert isinstance(tgt, Space) assert isinstance(src, Space) assert tgt.ring is src.ring assert tgt.n == src.n ring = tgt.ring A = elim.identity(ring, tgt.n) return Lin(tgt, src, A)
def counit(K, U): # method of K ? assert isinstance(K, Space) assert isinstance(U, Space) ring = K.ring assert K.n == 1, "not tensor identity" tgt = K src = U @ U.dual A = elim.identity(ring, U.n) A.shape = (tgt.n, src.n) lin = Lin(tgt, src, A) return lin
def right_distributor(lhs, rhs, inverse=False): "A@C+B@C <---- (A+B)@C" # These turn out to be identity matrices (on the nose). if not isinstance(lhs, AddSpace): # There's nothing to distribute return (lhs @ rhs).identity() src = lhs @ rhs tgt = AddSpace(lhs.ring, *[l @ rhs for l in lhs.items]) if inverse: tgt, src = src, tgt A = elim.identity(src.ring, src.n) return Lin(tgt, src, A)
def unitor(self, inverse=False): "remove all tensor units" items = self.items src = self tgt = [item for item in self.items if item.n != 1] if len(tgt) > 1: tgt = MulSpace(self.ring, *tgt) elif len(tgt): tgt = tgt[0] else: assert 0, "wah?" A = elim.identity(self.ring, self.n) if inverse: src, tgt = tgt, src # the same A works return Lin(tgt, src, A)
def nullitor(self, inverse=False): "remove all zero (null) summands" items = self.items src = self tgt = [item for item in self.items if item.n] if len(tgt) > 1: tgt = AddSpace(self.ring, *tgt) elif len(tgt): tgt = tgt[0] else: assert 0, "wah?" A = elim.identity(self.ring, self.n) if inverse: src, tgt = tgt, src # the same A works return Lin(tgt, src, A)
def get_swap(self, perm=(1, 0)): perm = tuple(perm) items = self.items assert len(perm) == len(items), ("len(%s)!=%d" % (perm, len(items))) assert set(perm) == set(range(len(items))) tgt = reduce(add, [items[i] for i in perm]) N = len(items) rows = [] for i in perm: row = [] for j in range(N): if i == j: A = elim.identity(self.ring, items[i].n) else: A = elim.zeros(self.ring, items[i].n, items[j].n) row.append(A) rows.append(row) rows = [numpy.concatenate(row, axis=1) for row in rows] A = numpy.concatenate(rows) return Lin(tgt, self, A)
def get_swap(self, perm=(1, 0)): perm = tuple(perm) items = self.items assert self.grade is not None assert len(perm) == len(items) assert set(perm) == set(range(len(items))) sign = self.ring.one N = len(items) for i in range(N): for j in range(i + 1, N): if perm[i] > perm[j] and (items[i].grade * items[j].grade) % 2: sign *= -1 A = sign * elim.identity(self.ring, self.n) shape = tuple(item.n for item in items) A.shape = shape + shape axes = list(perm) for i in range(N): axes.append(i + len(perm)) A = A.transpose(axes) tgt = reduce(matmul, [self.items[i] for i in perm]) A = A.reshape((tgt.n, self.n)) # its a square matrix return Lin(tgt, self, A)
def get_normal(self, N=None, K=None, inverse=False, force=False): #print("\nMulSpace.get_normal", self.name) assert N is None or N.n == 0 assert K is None or K.n == 1 ring = self.ring # depth-first recurse spaces = list(self.items) assert len(spaces) > 1, str(self) fs = [space.get_normal(N, K, force=force) for space in spaces] f = reduce(matmul, fs) assert isinstance(f.tgt, MulSpace) assert f.src == self spaces = list(f.tgt.items) #print("spaces:") #print([space.name for space in spaces]) # first deal with N, K factors (*) idx = 0 while idx < len(spaces): space = spaces[idx] if force and space.n == 0 or space == N: # kills everything tgt = N g = Lin.zero(tgt, f.tgt) f = g * f if inverse: f = f.transpose() # permutation matrix return f # <-------------- return elif force and space.n == 1 or space == K: spaces.pop(idx) else: idx += 1 #print("spaces:", [space.name for space in spaces]) #print("self.items:", [space.name for space in self.items]) if len(spaces) < len(f.tgt.items): tgt, src = MulSpace(ring, *spaces, K=K), f.tgt g = Lin(tgt, src, elim.identity(ring, tgt.n)) f = g * f #print(tgt) #print("f.tgt", f.tgt) if spaces: gs = [space.get_normal(N, K, force=force) for space in spaces] # <--------- recurse spaces = [g.tgt for g in gs] g = reduce(matmul, gs) assert f.tgt == g.src f = g * f # go back to (*) ? # now distribute over AddSpace's for idx, space in enumerate(spaces): if isinstance(space, AddSpace): break else: if inverse: f = f.transpose() # permutation matrix return f # <---------------- return if idx + 1 < len(spaces): head = spaces[:idx] lhs = spaces[idx] rhs = MulSpace(ring, *spaces[idx + 1:], K=K) g = Lin.right_distributor(lhs, rhs) if head: head = MulSpace(ring, *head) g = head.identity() @ g f = g * f elif len(spaces) > 1: lhs = MulSpace(ring, *spaces[:idx], K=K) rhs = spaces[idx] #print("f =", f.homstr()) assert f.tgt == lhs @ rhs #print("left_distributor", lhs, rhs) g = Lin.left_distributor(lhs, rhs) f = g * f g = f.tgt.get_normal(N, K, force=force) # <-------------- recurse f = g * f if inverse: f = f.transpose() # permutation matrix return f
def identity(self): A = elim.identity(self.ring, self.n) return Lin(self, self, A)
def main(): ring = element.Q zero = ring.zero one = ring.one n = argv.get("n", 3) if argv.cyclic: G = Group.cyclic(n) else: G = Group.symmetric(n) comm = G.is_abelian() print(G) d = len(G) K = Space(ring, 1, name="K") V = Space(ring, d, name="V") VV = V @ V scalar = K.identity() I = V.identity() swap = VV.get_swap() lunit = Lin(V, K @ V, elim.identity(ring, d)) runit = Lin(V, V @ K, elim.identity(ring, d)) cap = Lin(K, V @ V) # tgt, src cup = Lin(V @ V, K) # tgt, src for i in range(d): cup[i + d * i, 0] = one cap[0, i + d * i] = one # green spiders g_ = Lin(K, V) # uniform discard _g = Lin(V, K) # uniform create g_gg = Lin(VV, V) # copy gg_g = Lin(V, VV) # pointwise mul for i in range(d): g_[0, i] = one _g[i, 0] = one g_gg[i + d * i, i] = one gg_g[i, i + d * i] = one eq = lambda lhs, rhs: lhs.weak_eq(rhs) assert eq(g_gg >> (g_ @ I), I) # counit assert eq(g_gg >> (I @ g_), I) # counit assert eq(g_gg >> (g_gg @ I), g_gg >> (I @ g_gg)) # coassoc assert eq(gg_g * (_g @ I), I) # unit assert eq(gg_g * (I @ _g), I) # unit assert eq(gg_g * (gg_g @ I), gg_g * (I @ gg_g)) # assoc assert eq((g_gg @ I) >> (I @ gg_g), (I @ g_gg) >> (gg_g @ I)) # frobenius assert eq((g_gg @ I) >> (I @ gg_g), gg_g >> g_gg) # extended frobenius assert eq(_g >> g_, d * scalar) assert eq(gg_g >> g_, cap) assert eq(_g >> g_gg, cup) # red spiders r_ = Lin(K, V) # discard unit _r = Lin(V, K) # create unit r_rr = Lin(VV, V) # comul rr_r = Lin(V, VV) # mul # hopf involution inv = Lin(V, V) lookup = dict((v, k) for (k, v) in enumerate(G)) for i in range(d): g = G[i] if g.is_identity(): r_[0, i] = one _r[i, 0] = one inv[lookup[~g], i] = one for j in range(d): h = G[j] gh = g * h k = lookup[gh] rr_r[k, i + j * d] = one r_rr[i + j * d, k] = one assert eq(r_rr >> (r_ @ I), I) # unit assert eq(r_rr >> (I @ r_), I) # unit assert eq(r_rr >> (r_rr @ I), r_rr >> (I @ r_rr)) # assoc assert eq(rr_r * (_r @ I), I) # unit assert eq(rr_r * (I @ _r), I) # unit assert eq(rr_r * (rr_r @ I), rr_r * (I @ rr_r)) # assoc assert eq((r_rr @ I) >> (I @ rr_r), (I @ r_rr) >> (rr_r @ I)) # frobenius assert eq((r_rr @ I) >> (I @ rr_r), rr_r >> r_rr) # extended frobenius assert eq((_r >> r_), scalar) assert not eq(rr_r >> r_, cap) assert not eq(_r >> r_rr, cup) # K[G] is a bialgebra assert eq(rr_r >> g_, g_ @ g_) assert eq(_r >> g_gg, _r @ _r) assert eq(_r >> g_, scalar) if not argv.skip: assert eq(rr_r >> g_gg, (g_gg @ g_gg) >> (I @ swap @ I) >> (rr_r @ rr_r)) print("K[G] is comm ", eq(swap >> rr_r, rr_r)) print("K[G] is cocomm", eq(g_gg >> swap, g_gg)) # K[G] is hopf rhs = g_ >> _r assert eq(g_gg >> (I @ inv) >> rr_r, rhs) assert eq(g_gg >> (inv @ I) >> rr_r, rhs) # k^G is a bialgebra assert eq(gg_g >> r_, r_ @ r_) assert eq(_g >> r_rr, _g @ _g) assert eq(_g >> r_, scalar) if not argv.skip: assert eq(gg_g >> r_rr, (r_rr @ r_rr) >> (I @ swap @ I) >> (gg_g @ gg_g)) # k^G is hopf rhs = r_ >> _g assert eq(r_rr >> (I @ inv) >> gg_g, rhs) assert eq(r_rr >> (inv @ I) >> gg_g, rhs) print("k^G is comm ", eq(swap >> gg_g, gg_g)) print("k^G is cocomm ", eq(r_rr >> swap, r_rr)) #print(rr_r) #print(r_rr) # unimodular r_cup = _r >> r_rr g_cap = gg_g >> g_ assert eq(r_cup >> (I @ g_), _g) assert eq(r_cup >> (g_ @ I), _g) assert eq((I @ _r) >> g_cap, r_) assert eq((_r @ I) >> g_cap, r_) assert eq(inv, (I @ r_cup) >> (swap @ I) >> (I @ g_cap)) assert eq(inv, (r_cup @ I) >> (I @ swap) >> (g_cap @ I)) assert eq(r_rr >> rr_r, d * I) assert eq(g_gg >> gg_g, I) # special # complementary frobenius structures ? # Heunen & Vicary eq (6.4) lhs = (_r @ I) >> (r_rr @ g_gg) >> (I @ gg_g @ I) >> (I @ g_ @ I) >> ( I @ lunit) >> rr_r rhs = g_ >> _r assert eq(lhs, rhs) lhs = (I @ _r) >> (r_rr @ g_gg) >> (I @ gg_g @ I) >> (I @ g_ @ I) >> ( I @ lunit) >> rr_r #assert eq(lhs, rhs) # FAIL lhs = (_g @ I) >> (g_gg @ r_rr) >> (I @ rr_r @ I) >> (I @ r_ @ I) >> ( I @ lunit) >> gg_g rhs = r_ >> _g assert eq(lhs, rhs) lhs = (I @ _g) >> (g_gg @ r_rr) >> (I @ rr_r @ I) >> (I @ r_ @ I) >> ( I @ lunit) >> gg_g #assert eq(lhs, rhs) # FAIL # Heunen & Vicary eq (6.5) lhs = (_r @ I) >> (r_rr @ I) >> (I @ gg_g) >> (I @ g_) rhs = (I @ _r) >> (I @ r_rr) >> (gg_g @ I) >> (g_ @ I) assert eq(lhs, rhs) lhs = (_g @ I) >> (g_gg @ I) >> (I @ rr_r) >> (I @ r_) rhs = (I @ _g) >> (I @ g_gg) >> (rr_r @ I) >> (r_ @ I) assert eq(lhs, rhs) assert eq(r_rr, r_rr >> swap) == G.is_abelian() assert eq(rr_r, swap >> rr_r) == G.is_abelian() assert eq(_r >> r_rr, _r >> r_rr >> swap) assert eq(rr_r >> r_, swap >> rr_r >> r_)
def identity(self, *args): return elim.identity(self.ring, *args)