class RootDatumGenerator: def __init__(self, R): self.R = R # don't really need to keep this, but might as well self.Pvee = R.coweight_lattice() basis = self.Pvee.basis() basis_keys = tuple(basis.keys()) dim = len(basis_keys) self.MPvee = FreeModule(ZZ, dim) self.MQvee = self.MPvee.submodule([ vector([alpha_vee[j] for j in basis_keys]) for alpha_vee in self.Pvee.simple_roots() ]) self.fundamental_group = self.MPvee / self.MQvee def Pvee_to_MPvee(x): return vector([x[k] for k in basis_keys ]) # using x[k] is way faster than x.coefficient(k) def MPvee_to_Pvee(x): return sum_in_module( self.Pvee, [x[i] * basis[basis_keys[i]] for i in range(dim)]) def action52(g, x): return Pvee_to_MPvee(g.action(MPvee_to_Pvee(x))) self.action = action52 self.Pvee_to_MPvee = Pvee_to_MPvee self.MPvee_to_Pvee = MPvee_to_Pvee # returns an element of Pvee that maps to x under Pvee -->> fundamental_group def lift_to_Pvee(self, x): return self.MPvee_to_Pvee(x.lift()) # returns the image of the element x under the map Pvee -->> fundamental_group def map_to_fundamental_group(self, x): return self.Pvee_to_MPvee(x) # returns the sublattice X of Pvee corresponding to the subgroup Omega of the fundamental_group def cocharacter_lattice(self, Omega): return self.MPvee.submodule( list(self.MQvee.gens()) + [x.lift() for x in Omega.gens()])
class ConvexHullPalp(ConvexHull): r""" Compute convex hull using PALP. Note: the points must be lattice points (ie integer coordinates) and generate the ambient vector space. """ _name = 'PALP' def __init__(self, dim): from sage.modules.free_module import FreeModule self._free_module = FreeModule(ZZ, int(dim)) def __eq__(self, other): return type(self) is type(other) and self._free_module == other._free_module def __call__(self, pts): filename = tmp_filename() pts = list(pts) n = len(pts) if n <= 2: return tuple(pts) d = len(pts[0]) assert d == self._free_module.rank() # PALP only works with full dimension polyhedra!! ppts = [x-pts[0] for x in pts] U = self._free_module.submodule(ppts) d2 = U.rank() if d2 != d: # not full dim # we compute two matrices # M1: small space -> big space (makes decomposition) # M2: big space -> small space (i.e. basis of the module) # warning: matrices act on row vectors, i.e. left action from sage.matrix.constructor import matrix from sage.modules.free_module import FreeModule V2 = FreeModule(ZZ,d2) M1 = U.matrix() assert M1.nrows() == d2 assert M1.ncols() == d M2 = matrix(QQ,d) M2[:d2,:] = M1 i = d2 U = U.change_ring(QQ) F = self._free_module.change_ring(QQ) for b in F.basis(): if b not in U: M2.set_row(i, b) U = F.submodule(U.basis() + [b]) i += 1 assert i == self._free_module.rank() M2 = (~M2)[:,:d2] assert M2.nrows() == d assert M2.ncols() == d2 assert (M1*M2).is_one() pts2 = [p * M2 for p in ppts] else: pts2 = pts d2 = d with open(filename, "w") as output: output.write("{} {}\n".format(n,d2)) for p in pts2: output.write(" ".join(map(str,p))) output.write("\n") args = ['poly.x', '-v', filename] try: palp_proc = Popen(args, stdin=PIPE, stdout=PIPE, stderr=None, cwd=str(SAGE_TMP)) except OSError: raise RuntimeError("Problem with calling PALP") ans, err = palp_proc.communicate() ret_code = palp_proc.poll() if ret_code: raise RuntimeError("PALP return code is {} from input {}".format(ret_code, pts)) a = ans.split('\n') try: dd,nn = a[0].split(' ')[:2] dd = int(dd) nn = int(nn) if dd > nn: dd,nn = nn,dd except (TypeError,ValueError): raise RuntimeError("PALP got wrong:\n{}".format(ans)) if d2 != int(dd): raise RuntimeError("dimension changed... have d={} but PALP answered dd={} and nn={}".format(d2,dd,nn)) n2 = int(nn) coords = [] for i in xrange(1,d2+1): coords.append(map(ZZ,a[i].split())) new_pts = zip(*coords) if d2 != d: new_pts = [pts[0] + V2(p)*M1 for p in new_pts] t = [self._free_module(p) for p in new_pts] for v in t: v.set_immutable() t.sort() return tuple(t)
def maximal_grading(L): r""" Return a maximal grading of a Lie algebra defined over an algebraically closed field. A maximal grading of a Lie algebra `\mathfrak{g}` is the finest possible grading of `\mathfrak{g}` over torsion free abelian groups. If `\mathfrak{g} = \bigoplus_{n\in \mathbb{Z}^k} \mathfrak{g}_n` is a maximal grading, then there exists no other grading `\mathfrak{g} = \bigoplus_{a\in A} \mathfrak{g}_a` over a torsion free abelian group `A` such that every `\mathfrak{g}_a` is contained in some `\mathfrak{g}_n`. EXAMPLES: A maximal grading of an abelian Lie algebra puts each basis element into an independent layer:: sage: import sys, pathlib sage: sys.path.append(str(pathlib.Path().absolute())) sage: from lie_gradings.gradings.grading import maximal_grading sage: L = LieAlgebra(QQbar, 4, abelian=True) sage: maximal_grading(L) Grading over Additive abelian group isomorphic to Z + Z + Z + Z of Abelian Lie algebra on 4 generators (L[0], L[1], L[2], L[3]) over Algebraic Field with nonzero layers (1, 0, 0, 0) : (L[3],) (0, 1, 0, 0) : (L[2],) (0, 0, 1, 0) : (L[1],) (0, 0, 0, 1) : (L[0],) A maximal grading of a free nilpotent Lie algebra decomposes the Lie algebra based on how many times each generator appears in the defining Lie bracket:: sage: L = LieAlgebra(QQbar, 3, step=3) sage: maximal_grading(L) Grading over Additive abelian group isomorphic to Z + Z + Z of Free Nilpotent Lie algebra on 14 generators (X_1, X_2, X_3, X_12, X_13, X_23, X_112, X_113, X_122, X_123, X_132, X_133, X_223, X_233) over Algebraic Field with nonzero layers (1, 0, 0) : (X_1,) (0, 1, 0) : (X_2,) (1, 1, 0) : (X_12,) (1, 2, 0) : (X_122,) (2, 1, 0) : (X_112,) (0, 0, 1) : (X_3,) (0, 1, 1) : (X_23,) (0, 1, 2) : (X_233,) (0, 2, 1) : (X_223,) (1, 0, 1) : (X_13,) (1, 0, 2) : (X_133,) (1, 1, 1) : (X_123, X_132) (2, 0, 1) : (X_113,) """ # define utilities to convert from matrices to vectors and back R = L.base_ring() n = L.dimension() MS = MatrixSpace(R, n, n) def matrix_to_vec(A): return vector(R, sum((list(Ar) for Ar in A.rows()), [])) db = L.derivations_basis() def derivation_lincomb(vec): return sum((vk * dk for vk, dk in zip(vec, db)), MS.zero()) # iteratively construct larger and larger tori of derivations t = [] while True: # compute the centralizer of the torus in the derivation algebra ker = FreeModule(R, len(db)) for der in t: # form the matrix of ad(der) with rows # the images of basis derivations A = matrix([matrix_to_vec(der * X - X * der) for X in db]) ker = ker.intersection(A.left_kernel()) cb = [derivation_lincomb(v) for v in ker.basis()] # check the basis of the centralizer for semisimple parts outside of t gl = FreeModule(R, n * n) t_submodule = gl.submodule([matrix_to_vec(der) for der in t]) for A in cb: As, An = jordan_decomposition(A) if matrix_to_vec(As) not in t_submodule: # extend the torus by As t.append(As) break else: # no new elements found, so the torus is maximal break # compute the eigenspace intersections to get the concrete grading common_eigenspaces = [([], FreeModule(R, n))] for A in t: new_eigenspaces = [] eig = A.right_eigenspaces() for ev, V in common_eigenspaces: for ew, W in eig: VW = V.intersection(W) if VW.dimension() > 0: new_eigenspaces.append((ev + [ew], VW)) common_eigenspaces = new_eigenspaces if not t: # zero dimensional maximal torus # the only grading is the trivial grading magma = AdditiveAbelianGroup([]) layers = {magma.zero(): L.basis().list()} return grading(L, layers, magma=magma) # define a grading with layers indexed by tuples of eigenvalues cm = get_coercion_model() all_eigenvalues = sum((ev for ev, V in common_eigenspaces), []) k = len(common_eigenspaces[0][0]) evR = cm.common_parent(*all_eigenvalues) layers = { tuple(ev): [L.from_vector(v) for v in V.basis()] for ev, V in common_eigenspaces } magma = evR.cartesian_product(*[evR] * (k - 1)) gr = grading(L, layers, magma=magma) # convert to a grading over Z^k return gr.universal_realization()
def stratification(L): r""" Return a stratification of the Lie algebra if one exists. INPUT: - ``L`` -- a Lie algebra OUTPUT: A grading of the Lie algebra `\mathfrak{g}` over the integers such that the layer `\mathfrak{g}_1` generates the full Lie algebra. EXAMPLES:: A stratification for a free nilpotent Lie algebra is the one based on the length of the defining bracket:: sage: from lie_gradings.gradings.grading import stratification sage: from lie_gradings.gradings.utilities import in_new_basis sage: L = LieAlgebra(QQ, 3, step=3) sage: strat = stratification(L) sage: strat Grading over Additive abelian group isomorphic to Z of Free Nilpotent Lie algebra on 14 generators (X_1, X_2, X_3, X_12, X_13, X_23, X_112, X_113, X_122, X_123, X_132, X_133, X_223, X_233) over Rational Field with nonzero layers (1) : (X_1, X_2, X_3) (2) : (X_12, X_13, X_23) (3) : (X_112, X_113, X_122, X_123, X_132, X_133, X_223, X_233) The main use case is when the original basis of the stratifiable Lie algebra is not adapted to a stratification. Consider the following quotient Lie algebra:: sage: X_1, X_2, X_3 = L.basis().list()[:3] sage: Q = L.quotient(L[X_2, X_3]) sage: Q Lie algebra quotient L/I of dimension 10 over Rational Field where L: Free Nilpotent Lie algebra on 14 generators (X_1, X_2, X_3, X_12, X_13, X_23, X_112, X_113, X_122, X_123, X_132, X_133, X_223, X_233) over Rational Field I: Ideal (X_23) We switch to a basis which does not define a stratification:: sage: Y_1 = Q(X_1) sage: Y_2 = Q(X_2) + Q[X_1, X_2] sage: Y_3 = Q(X_3) sage: basis = [Y_1, Y_2, Y_3] + Q.basis().list()[3:] sage: Y_labels = ["Y_%d"%(k+1) for k in range(len(basis))] sage: K = in_new_basis(Q, basis,Y_labels) sage: K.inject_variables() Defining Y_1, Y_2, Y_3, Y_4, Y_5, Y_6, Y_7, Y_8, Y_9, Y_10 sage: K[Y_2, Y_3] Y_9 sage: K[[Y_1, Y_3], Y_2] Y_9 We may reconstruct a stratification in the new basis without any knowledge of the original stratification:: sage: stratification(K) Grading over Additive abelian group isomorphic to Z of Nilpotent Lie algebra on 10 generators (Y_1, Y_2, Y_3, Y_4, Y_5, Y_6, Y_7, Y_8, Y_9, Y_10) over Rational Field with nonzero layers (1) : (Y_1, Y_2 - Y_4, Y_3) (2) : (Y_4, Y_5) (3) : (Y_10, Y_6, Y_7, Y_8, Y_9) sage: K[Y_1, Y_2 - Y_4] Y_4 sage: K[Y_1, Y_3] Y_5 sage: K[Y_2 - Y_4, Y_3] 0 A non-stratifiable Lie algebra raises an error:: sage: L = LieAlgebra(QQ, {('X_1','X_3'): {'X_4': 1}, ....: ('X_1','X_4'): {'X_5': 1}, ....: ('X_2','X_3'): {'X_5': 1}}, ....: names='X_1,X_2,X_3,X_4,X_5') sage: stratification(L) Traceback (most recent call last): ... ValueError: Lie algebra on 5 generators (X_1, X_2, X_3, X_4, X_5) over Rational Field is not a stratifiable Lie algebra """ lcs = L.lower_central_series(submodule=True) quots = [V.quotient(W) for V, W in zip(lcs, lcs[1:])] # find a basis adapted to the filtration by the lower central series adapted_basis = [] weights = [] for k, q in enumerate(quots): weights += [k + 1] * q.dimension() for v in q.basis(): b = q.lift(v) adapted_basis.append(b) # define a submodule to compute structural # coefficients in the filtration adapted basis try: m = L.module() except AttributeError: m = FreeModule(L.base_ring(), L.dimension()) sm = m.submodule_with_basis(adapted_basis) # form the linear system Ax=b of constraints from the Leibniz rule paramspace = [(k, h) for k in range(L.dimension()) for h in range(L.dimension()) if weights[h] > weights[k]] Arows = [] bvec = [] zerovec = m.zero() for i in range(L.dimension()): Y_i = adapted_basis[i] w_i = weights[i] for j in range(i + 1, L.dimension()): Y_j = adapted_basis[j] w_j = weights[j] Y_ij = L.bracket(Y_i, Y_j) c_ij = sm.coordinate_vector(Y_ij.to_vector()) bcomp = L.zero() for k in range(L.dimension()): w_k = weights[k] Y_k = adapted_basis[k] bcomp += (w_k - w_i - w_j) * c_ij[k] * Y_k bv = bcomp.to_vector() Acomp = {} for k, h in paramspace: w_k = weights[k] Y_h = adapted_basis[h] if k == i: Acomp[(k, h)] = L.bracket(Y_h, Y_j).to_vector() elif k == j: Acomp[(k, h)] = L.bracket(Y_i, Y_h).to_vector() elif w_k >= w_i + w_j: Acomp[(k, h)] = -c_ij[k] * Y_h for r in range(L.dimension()): Arows.append( [Acomp.get((k, h), zerovec)[r] for k, h in paramspace]) bvec.append(bv[r]) A = matrix(L.base_ring(), Arows) b = vector(L.base_ring(), bvec) # solve the linear system Ax=b if possible try: coeffs_flat = A.solve_right(b) except ValueError: raise ValueError("%s is not a stratifiable Lie algebra" % L) coeffs = {(k, h): ckh for (k, h), ckh in zip(paramspace, coeffs_flat)} # define the matrix of the derivation determined by the solution # in the adapted basis cols = [] for k in range(L.dimension()): w_k = weights[k] Y_k = adapted_basis[k] hspace = [h for (l, h) in paramspace if l == k] Yk_im = w_k * Y_k + sum( (coeffs[(k, h)] * adapted_basis[h] for h in hspace), L.zero()) cols.append(sm.coordinate_vector(Yk_im.to_vector())) der = matrix(L.base_ring(), cols).transpose() # the layers of the stratification are V_k = ker(der - kI) layers = {} for k in range(len(quots)): degree = k + 1 B = der - degree * matrix.identity(L.dimension()) adapted_kernel = B.right_kernel() # convert back to the original basis Vk_basis = [sm.from_vector(X) for X in adapted_kernel.basis()] layers[(degree, )] = [ L.from_vector(v) for v in m.submodule(Vk_basis).basis() ] return grading(L, layers, magma=AdditiveAbelianGroup([0]), projections=True)
def torsion_free_gradings(L): r""" Return a complete list of gradings of the Lie algebra over torsion free abelian groups. The list is guaranteed to be complete in the following sense: If `\mathfrak{g} = \bigoplus_{a\in A} \mathfrak{g}_a` is any grading of the Lie algebra `\mathfrak{g}` over a torsion free abelian group `A`, then there exists - a grading `\mathfrak{g} = \bigoplus_{n\in \mathbb{Z}^m} \mathfrak{g}_n`, - an automorphism `\Phi\in\mathrm{Aut}(\mathfrak{g})`, and - a homomorphism `\varphi\colon\mathbb{Z}^m\to A` such that the grading `\mathfrak{g} = \bigoplus_{n\in \mathbb{Z}^m} \Phi(\mathfrak{g}_{\varphi(n)})` is exactly the same as the original `A`-grading. However, the list is not guaranteed to be reduced up to automorphism, so the above choices are not in general unique. EXAMPLES: We list all gradings of the Heisenberg Lie algebra over torsion free abelian groups:: sage: from lie_gradings.gradings.grading import torsion_free_gradings sage: L = lie_algebras.Heisenberg(QQ, 1) sage: torsion_free_gradings(L) [Grading over Additive abelian group isomorphic to Z + Z of Heisenberg algebra of rank 1 over Rational Field with nonzero layers (1, 0) : (p1,) (0, 1) : (q1,) (1, 1) : (z,) , Grading over Additive abelian group isomorphic to Z of Heisenberg algebra of rank 1 over Rational Field with nonzero layers (1) : (p1, z) (0) : (q1,) , Grading over Additive abelian group isomorphic to Z of Heisenberg algebra of rank 1 over Rational Field with nonzero layers (1) : (q1, z) (0) : (p1,) , Grading over Additive abelian group isomorphic to Z of Heisenberg algebra of rank 1 over Rational Field with nonzero layers (1) : (p1, q1) (2) : (z,) , Grading over Trivial group of Heisenberg algebra of rank 1 over Rational Field with nonzero layers () : (p1, q1, z) ] """ maxgrading = maximal_grading(L) V = FreeModule(ZZ, len(maxgrading.magma().gens())) weights = maxgrading.layers().keys() # The torsion-free gradings are enumerated by torsion-free quotients # of the grading group of the maximal grading. diffset = set([tuple(b - a) for a, b in combinations(weights, 2)]) subspaces = [] for d in range(len(diffset) + 1): for B in combinations(diffset, d): W = V.submodule(B) if W not in subspaces: subspaces.append(W) # for each subspace, define the quotient grading projected_gradings = [] for W in subspaces: Q = V.quotient(W) # check if quotient is not torsion-free if any(qi > 0 for qi in Q.invariants()): continue quot_layers = {} for n in weights: pi_n = tuple(Q(V(tuple(n)))) if pi_n not in quot_layers: quot_layers[pi_n] = [] quot_layers[pi_n].extend(maxgrading.layers()[n]) # expand away denominators to get an integer vector grading A = AdditiveAbelianGroup(Q.invariants()) denoms = [ pi_n_k.denominator() for pi_n in quot_layers for pi_n_k in pi_n ] mult = lcm(denoms) proj_layers = { tuple(mult * pi_nk for pi_nk in pi_n): l for pi_n, l in quot_layers.items() } proj_grading = grading(L, proj_layers, magma=A, projections=True) projected_gradings.append(proj_grading) return projected_gradings