def godement_sheaf(self): """ Returns the Godement sheaf of ``self``. """ g0_stalks = dict() g0_res = dict() for p in self._domain_poset.list(): g0_stalks[p] = sum(self._stalk_dict[x] for x in self._domain_poset.order_filter([p])) for relation in self._domain_poset.cover_relations(): frombase = sorted(self._domain_poset.order_filter([relation[0]])) tobase = sorted(self._domain_poset.order_filter([relation[1]])) rows = [] for row in tobase: m = self._stalk_dict[row] blocks = [] for x in frombase: if x == row: blocks.append(identity_matrix(m)) else: blocks.append(Matrix(m, self._stalk_dict[x])) rows.append(blocks) g0_res[tuple(relation)] = block_matrix(rows, subdivide=False) G0 = LocallyFreeSheafFinitePoset(g0_stalks, g0_res, self._base_ring, self._domain_poset) hom = Hom(self, G0) eps_dict = dict() for p in self._domain_poset.list(): rows = [] for x in sorted(self._domain_poset.order_filter([p])): if x == p: rows.append([identity_matrix(self._base_ring, self._stalk_dict[p])]) else: rows.append([self.restriction(p, x).matrix()]) eps_dict[p] = block_matrix(rows, subdivide=False) epsilon = hom(eps_dict) return epsilon, G0
def _godement_complex_differential(self, start_base, end_base): """ Construct the differential of the godement cochain complex from the free module with basis ``start_base`` to the free module with basis ``end_base``. """ rows = [] for to_chain in end_base: blocks = [] m = self._stalk_dict[to_chain[-1]] for from_chain in start_base: n = self._stalk_dict[from_chain[-1]] if all(x in to_chain for x in from_chain): for point in to_chain: if point not in from_chain: index = to_chain.index(point) sign = 1 if index % 2 == 0 else - 1 if index != len(to_chain) - 1: blocks.append(sign*identity_matrix(self._base_ring, m)) else: mat = self.restriction(from_chain[-1], point).matrix() blocks.append(sign*mat) break else: blocks.append(Matrix(self._base_ring, m, n)) rows.append(blocks) return block_matrix(rows, subdivide=False)
def isotypic_projection_matrix(self, i): r""" TESTS:: sage: from surface_dynamics.all import * sage: p = iet.GeneralizedPermutation('a b b','c c a') sage: c = p.cover(['(1,2,3)','(1,3,2)','()']) sage: c.isotypic_projection_matrix(0) [1/3 0 0 1/3 0 0 1/3 0 0] [ 0 1/3 0 0 1/3 0 0 1/3 0] [ 0 0 1/3 0 0 1/3 0 0 1/3] [1/3 0 0 1/3 0 0 1/3 0 0] [ 0 1/3 0 0 1/3 0 0 1/3 0] [ 0 0 1/3 0 0 1/3 0 0 1/3] [1/3 0 0 1/3 0 0 1/3 0 0] [ 0 1/3 0 0 1/3 0 0 1/3 0] [ 0 0 1/3 0 0 1/3 0 0 1/3] """ from sage.matrix.special import identity_matrix from surface_dynamics.misc.group_representation import isotypic_projection_matrix m = isotypic_projection_matrix( self.automorphism_group(), self._degree_cover, self._real_characters()[0][i], self._real_characters()[1][i], self._cc_mats()) return m.tensor_product(identity_matrix(len(self._base)), subdivide=False)
def isotypic_projection_matrix(self, i): r""" TESTS:: sage: from surface_dynamics.all import * sage: p = iet.GeneralizedPermutation('a b b','c c a') sage: c = p.cover(['(1,2,3)','(1,3,2)','()']) sage: c.isotypic_projection_matrix(0) [1/3 0 0 1/3 0 0 1/3 0 0] [ 0 1/3 0 0 1/3 0 0 1/3 0] [ 0 0 1/3 0 0 1/3 0 0 1/3] [1/3 0 0 1/3 0 0 1/3 0 0] [ 0 1/3 0 0 1/3 0 0 1/3 0] [ 0 0 1/3 0 0 1/3 0 0 1/3] [1/3 0 0 1/3 0 0 1/3 0 0] [ 0 1/3 0 0 1/3 0 0 1/3 0] [ 0 0 1/3 0 0 1/3 0 0 1/3] """ from sage.matrix.special import identity_matrix from surface_dynamics.misc.group_representation import isotypic_projection_matrix m = isotypic_projection_matrix( self.automorphism_group(), self._degree_cover, self._real_characters()[0][i], self._real_characters()[1][i], self._cc_mats()) return m.tensor_product(identity_matrix(len(self._base)), subdivide=False)
def centralizer_basis(self, S): """ Return a basis of the centralizer of ``S`` in ``self``. INPUT: - ``S`` -- a subalgebra of ``self`` or a list of elements that represent generators for a subalgebra .. SEEALSO:: :meth:`centralizer` EXAMPLES:: sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() sage: a,b,c = L.lie_algebra_generators() sage: L.centralizer_basis([a + b, 2*a + c]) [(1, 0, 0), (0, 1, 0), (0, 0, 1)] sage: H = lie_algebras.Heisenberg(QQ, 2) sage: H.centralizer_basis(H) [z] sage: D = DescentAlgebra(QQ, 4).D() sage: L = LieAlgebra(associative=D) sage: L.centralizer_basis(L) [D{}, D{1} + D{1, 2} + D{2, 3} + D{3}, D{1, 2, 3} + D{1, 3} + D{2}] sage: D.center_basis() (D{}, D{1} + D{1, 2} + D{2, 3} + D{3}, D{1, 2, 3} + D{1, 3} + D{2}) """ #from sage.algebras.lie_algebras.subalgebra import LieSubalgebra #if isinstance(S, LieSubalgebra) or S is self: if S is self: from sage.matrix.special import identity_matrix m = identity_matrix(self.base_ring(), self.dimension()) elif isinstance(S, (list, tuple)): m = matrix([v.to_vector() for v in self.echelon_form(S)]) else: m = self.subalgebra(S).basis_matrix() S = self.structure_coefficients() sc = {} for k in S.keys(): v = S[k].to_vector() sc[k] = v sc[k[1], k[0]] = -v X = self.basis().keys() d = len(X) c_mat = matrix(self.base_ring(), [[ sum(m[i, j] * sc[x, xp][k] for j, xp in enumerate(X) if (x, xp) in sc) for x in X ] for i in range(d) for k in range(d)]) C = c_mat.right_kernel().basis_matrix() return [self.from_vector(v) for v in C]
def component_matrix(self, point): if self._components[point] == 0: return matrix(self._base_ring, self.codomain()._stalk_dict[point], self.domain()._stalk_dict[point]) if self._components[point] == 1: return identity_matrix(self._base_ring, self.domain()._stalk_dict[point]) return matrix(self._components[point])
def __init__(self, R, ngens=None, gram_matrix=None, names=None, index_set=None): """ Initialize self. TESTS:: sage: V = lie_conformal_algebras.FreeFermions(QQ) sage: TestSuite(V).run() """ from sage.matrix.matrix_space import MatrixSpace from sage.matrix.special import identity_matrix if (gram_matrix is not None): if ngens is None: ngens = gram_matrix.dimensions()[0] try: assert (gram_matrix in MatrixSpace(R,ngens,ngens)) except AssertionError: raise ValueError("The gram_matrix should be a symmetric " + "{0} x {0} matrix, got {1}".format(ngens,gram_matrix)) if not gram_matrix.is_symmetric(): raise ValueError("The gram_matrix should be a symmetric " + "{0} x {0} matrix, got {1}".format(ngens,gram_matrix)) else: if ngens is None: ngens = 1; gram_matrix = identity_matrix(R,ngens,ngens) latex_names=None if (names is None) and (index_set is None): if ngens==1: names = 'psi' else: names = 'psi_' latex_names = tuple(r"\psi_{%d}" % i \ for i in range (ngens)) + ('K',) from sage.structure.indexed_generators import \ standardize_names_index_set names,index_set = standardize_names_index_set(names=names, index_set=index_set, ngens=ngens) fermiondict = { (i,j): {0: {('K',0): gram_matrix[index_set.rank(i), index_set.rank(j)]}} for i in index_set for j in index_set} from sage.rings.rational_field import QQ weights = (QQ(1/2),)*ngens parity = (1,)*ngens GradedLieConformalAlgebra.__init__(self,R,fermiondict,names=names, latex_names=latex_names, index_set=index_set,weights=weights, parity=parity, central_elements=('K',)) self._gram_matrix = gram_matrix
def dual(self): T = block_matrix(ZZ, [[self._A[:, [self._groundset_to_index[e] for e in self._E]], self._Q]]).transpose() I = identity_matrix(ZZ, T.nrows()) # create temporary names for new groundset elements temp_elements = [-i for i in xrange(self._Q.ncols())] while len(frozenset(temp_elements).intersection(self.groundset())) > 0: temp_elements = [e-1 for e in temp_elements] M = ToricArithmeticMatroid(arrangement_matrix=I, torus_matrix=T, ordered_groundset=list(self._E)+temp_elements) return M.minor(deletions=temp_elements)
def _cover_relation_to_restriction(self, relation): """ Returns the restriction morphism associated to a cover relation of the domain poset of ``self``. """ hom = Hom(self.stalk(relation[0]), self.stalk(relation[1])) if self._res_dict[relation] == 0: return hom.zero() elif self._res_dict[relation] == 1: return hom(identity_matrix(self._stalk_dict[relation[0]])) else: return hom(self._res_dict[relation])
def diameter_support_function(A, b): r"""Compute the diameter of a polytope using the H-representation. The diameter is computed in the supremum norm. INPUT: Polyhedron in H-representation, `Ax \leq b`. OUTPUT: - ``diam`` -- scalar, diameter of the polyhedron in the supremum norm - ``u`` -- vector with components `u_j = \max x_j` for `x` in `P` - ``l`` -- vector with components `l_j = \min x_j` for `x` in `P` EXAMPLES:: sage: from polyhedron_tools.misc import diameter_support_function, polyhedron_to_Hrep sage: [A, b] = polyhedron_to_Hrep(7*polytopes.hypercube(5)) sage: diameter_support_function(A, b) (14.0, (7.0, 7.0, 7.0, 7.0, 7.0), (-7.0, -7.0, -7.0, -7.0, -7.0)) """ from polyhedron_tools.misc import support_function # ambient dimension n = A.ncols() # number of constraints m = A.rows() In = identity_matrix(n) # lower : min x_j l = [] for j in range(n): l += [-support_function([A, b], -In.column(j))] l = vector(l) # upper : max x_j u = [] for j in range(n): u += [support_function([A, b], In.column(j))] u = vector(u) diam = max(u - l) return diam, u, l
def __init__(self, R, ngens=2, names=None, index_set=None): """ Initialize self. TESTS:: sage: V = lie_conformal_algebras.BosonicGhosts(QQ) sage: TestSuite(V).run() """ try: assert (ngens > 0 and ngens % 2 == 0) except AssertionError: raise ValueError("ngens should be an even positive integer, " + "got {}".format(ngens)) latex_names = None if (names is None) and (index_set is None): from sage.misc.defaults import variable_names as varnames from sage.misc.defaults import latex_variable_names as laxnames names = varnames(ngens / 2, 'b') + varnames(ngens / 2, 'c') latex_names = tuple(laxnames(ngens/2,'b') +\ laxnames(ngens/2,'c')) + ('K',) from sage.structure.indexed_generators import \ standardize_names_index_set names, index_set = standardize_names_index_set(names=names, index_set=index_set, ngens=ngens) from sage.matrix.special import identity_matrix A = identity_matrix(R, ngens / 2) from sage.matrix.special import block_matrix gram_matrix = block_matrix([[R.zero(), A], [A, R.zero()]]) ghostsdict = {(i, j): { 0: { ('K', 0): gram_matrix[index_set.rank(i), index_set.rank(j)] } } for i in index_set for j in index_set} weights = (1, ) * (ngens // 2) + (0, ) * (ngens // 2) parity = (1, ) * ngens super(FermionicGhostsLieConformalAlgebra, self).__init__(R, ghostsdict, names=names, latex_names=latex_names, index_set=index_set, weights=weights, parity=parity, central_elements=('K', ))
def conjugate_matrix_Z(M): r""" Return the conjugate matrix Z as defined in [1]. EXAMPLES:: sage: from slabbe.matrices import conjugate_matrix_Z sage: M = matrix(2, [11,29,14,-1]) sage: conjugate_matrix_Z(M) # abs tol 1e-8 [11.674409930010482 27.69820597163912] [14.349386111618157 -1.67440993001048] sage: conjugate_matrix_Z(M)^2 # abs tol 1e-8 [533.7440993001048 276.9820597163913] [143.4938611161816 400.2559006998952] :: sage: M = matrix(2, [-11,14,-26,29]) sage: conjugate_matrix_Z(M) # abs tol 1e-8 [ 7.200000000000004 4.199999999999998] [ 7.799999999999995 10.800000000000002] sage: conjugate_matrix_Z(M) * 5 # abs tol 1e-8 [ 36.00000000000002 20.999999999999993] [ 38.99999999999998 54.000000000000014] :: sage: M = matrix(2, [-11,26,-14,29]) / 15 sage: conjugate_matrix_Z(M) # abs tol 1e-8 [ 0.5999999999999999 0.3999999999999999] [0.39999999999999986 0.5999999999999999] REFERENCES: [1] Labbé, Jean-Philippe, et Sébastien Labbé. « A Perron theorem for matrices with negative entries and applications to Coxeter groups ». arXiv:1511.04975 [math], 16 novembre 2015. http://arxiv.org/abs/1511.04975. """ from sage.matrix.special import identity_matrix eig, v = perron_right_eigenvector(M) v /= sum(v) nrows = M.nrows() UNtop = matrix([1] * nrows) v = matrix.column(v) id = identity_matrix(nrows) Z = eig * v * UNtop + (id - v * UNtop) * M return Z
def conjugate_matrix_Z(M): r""" Return the conjugate matrix Z as defined in [1]. EXAMPLES:: sage: from slabbe.matrices import conjugate_matrix_Z sage: M = matrix(2, [11,29,14,-1]) sage: conjugate_matrix_Z(M) # abs tol 1e-8 [11.674409930010482 27.69820597163912] [14.349386111618157 -1.67440993001048] sage: conjugate_matrix_Z(M)^2 # abs tol 1e-8 [533.7440993001048 276.9820597163913] [143.4938611161816 400.2559006998952] :: sage: M = matrix(2, [-11,14,-26,29]) sage: conjugate_matrix_Z(M) # abs tol 1e-8 [ 7.200000000000004 4.199999999999998] [ 7.799999999999995 10.800000000000002] sage: conjugate_matrix_Z(M) * 5 # abs tol 1e-8 [ 36.00000000000002 20.999999999999993] [ 38.99999999999998 54.000000000000014] :: sage: M = matrix(2, [-11,26,-14,29]) / 15 sage: conjugate_matrix_Z(M) # abs tol 1e-8 [ 0.5999999999999999 0.3999999999999999] [0.39999999999999986 0.5999999999999999] REFERENCES: [1] Labbé, Jean-Philippe, et Sébastien Labbé. « A Perron theorem for matrices with negative entries and applications to Coxeter groups ». arXiv:1511.04975 [math], 16 novembre 2015. http://arxiv.org/abs/1511.04975. """ from sage.matrix.special import identity_matrix eig, v = perron_right_eigenvector(M) v /= sum(v) nrows = M.nrows() UNtop = matrix([1]*nrows) v = matrix.column(v) id = identity_matrix(nrows) Z = eig*v*UNtop + (id - v*UNtop)*M return Z
def dualizing_complex(poset, base_ring=ZZ, rank=1): dim = poset.height() - 1 bound_below = -1 * dim data = [bound_below] end_base = sorted( filter(lambda c: len(c) == dim + 1, poset.maximal_chains())) for p in range(-1 * dim, 0): start_base = end_base end_base = sorted(filter(lambda c: len(c) == -1 * p, poset.chains())) #print("making differential from degree {}\n start: {}\n end:{}".format(p, start_base, end_base)) differential = dict() for x in poset.list(): point_start_base = filter(lambda c: poset.is_lequal(x, c[-1]), start_base) point_end_base = filter(lambda c: poset.is_lequal(x, c[-1]), end_base) #print("------for point {}: \n---------start:{}\n---------end:{}".format(x, point_start_base, point_end_base)) rows = [] for end_chain in point_end_base: blocks = [] for start_chain in point_start_base: if all(p in start_chain for p in end_chain): for y in start_chain: if y not in end_chain and y != start_chain[-1]: sign = 1 if start_chain.index( y) % 2 == 0 else -1 blocks.append(sign * identity_matrix(base_ring, rank)) break else: blocks.append(matrix(base_ring, rank, rank)) else: blocks.append(matrix(base_ring, rank, rank)) rows.append(blocks) differential[x] = block_matrix(rows, subdivide=False) data.append(_dualizing_sheaf(poset, p, base_ring, rank)) data.append(differential) data.append(_dualizing_sheaf(poset, 0, base_ring, rank)) if rank == 1: name = "dualizing complex of ({}, {})".format(poset, base_ring) else: name = "dualizing complex of ({}, rank-{} free module over {})".format( poset, rank, base_ring) return LocFreeSheafComplex(data, name=name)
def __init__(self, R, ngens=None, gram_matrix=None, names=None, index_set=None): """ Initialize self. TESTS:: sage: V = lie_conformal_algebras.FreeBosons(QQ) sage: TestSuite(V).run() """ from sage.matrix.matrix_space import MatrixSpace if (gram_matrix is not None): if ngens is None: ngens = gram_matrix.dimensions()[0] try: assert (gram_matrix in MatrixSpace(R,ngens,ngens)) except AssertionError: raise ValueError("the gram_matrix should be a symmetric " + "{0} x {0} matrix, got {1}".format(ngens,gram_matrix)) if not gram_matrix.is_symmetric(): raise ValueError("the gram_matrix should be a symmetric " + "{0} x {0} matrix, got {1}".format(ngens,gram_matrix)) else: if ngens is None: ngens = 1; gram_matrix = identity_matrix(R,ngens,ngens) latex_names = None if (names is None) and (index_set is None): names = 'alpha' latex_names = tuple(r'\alpha_{%d}' % i \ for i in range (ngens)) + ('K',) names,index_set = standardize_names_index_set(names=names, index_set=index_set, ngens=ngens) bosondict = { (i,j): {1: {('K',0): gram_matrix[index_set.rank(i), index_set.rank(j)]}} for i in index_set for j in index_set} GradedLieConformalAlgebra.__init__(self,R,bosondict,names=names, latex_names=latex_names, index_set=index_set, central_elements=('K',)) self._gram_matrix = gram_matrix
def reflection_length(self, in_unitary_group=False): r""" Return the reflection length of ``self``. This is the minimal numbers of reflections needed to obtain ``self``. INPUT: - ``in_unitary_group`` -- (default: ``False``) if ``True``, the reflection length is computed in the unitary group which is the dimension of the move space of ``self`` EXAMPLES:: sage: W = ReflectionGroup((1,1,3)) # optional - gap3 sage: sorted([t.reflection_length() for t in W]) # optional - gap3 [0, 1, 1, 1, 2, 2] sage: W = ReflectionGroup((2,1,2)) # optional - gap3 sage: sorted([t.reflection_length() for t in W]) # optional - gap3 [0, 1, 1, 1, 1, 2, 2, 2] sage: W = ReflectionGroup((2,2,2)) # optional - gap3 sage: sorted([t.reflection_length() for t in W]) # optional - gap3 [0, 1, 1, 2] sage: W = ReflectionGroup((3,1,2)) # optional - gap3 sage: sorted([t.reflection_length() for t in W]) # optional - gap3 [0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2] """ W = self.parent() if in_unitary_group or W.is_real(): from sage.matrix.special import identity_matrix I = identity_matrix(self.parent().rank()) return W.rank() - (self.canonical_matrix() - I).right_nullity() else: return len(self.reduced_word_in_reflections())
def reflection_length(self, in_unitary_group=False): r""" Return the reflection length of ``self``. This is the minimal numbers of reflections needed to obtain ``self``. INPUT: - ``in_unitary_group`` -- (default: ``False``) if ``True``, the reflection length is computed in the unitary group which is the dimension of the move space of ``self`` EXAMPLES:: sage: W = ReflectionGroup((1,1,3)) # optional - gap3 sage: sorted([t.reflection_length() for t in W]) # optional - gap3 [0, 1, 1, 1, 2, 2] sage: W = ReflectionGroup((2,1,2)) # optional - gap3 sage: sorted([t.reflection_length() for t in W]) # optional - gap3 [0, 1, 1, 1, 1, 2, 2, 2] sage: W = ReflectionGroup((2,2,2)) # optional - gap3 sage: sorted([t.reflection_length() for t in W]) # optional - gap3 [0, 1, 1, 2] sage: W = ReflectionGroup((3,1,2)) # optional - gap3 sage: sorted([t.reflection_length() for t in W]) # optional - gap3 [0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2] """ W = self.parent() if in_unitary_group or W.is_real(): from sage.matrix.special import identity_matrix I = identity_matrix(self.parent().rank()) return W.rank() - (self.canonical_matrix() - I).right_nullity() else: return len(self.reduced_word_in_reflections())
def lift_for_SL(A, N=None): r""" Lift a matrix `A` from `SL_m(\ZZ / N\ZZ)` to `SL_m(\ZZ)`. This follows [Shi1971]_, Lemma 1.38, p. 21. INPUT: - ``A`` -- a square matrix with coefficients in `\ZZ / N\ZZ` (or `\ZZ`) - ``N`` -- the modulus (optional) required only if the matrix ``A`` has coefficients in `\ZZ` EXAMPLES:: sage: from sage.modular.local_comp.liftings import lift_for_SL sage: A = matrix(Zmod(11), 4, 4, [6, 0, 0, 9, 1, 6, 9, 4, 4, 4, 8, 0, 4, 0, 0, 8]) sage: A.det() 1 sage: L = lift_for_SL(A) sage: L.det() 1 sage: (L - A) == 0 True sage: B = matrix(Zmod(19), 4, 4, [1, 6, 10, 4, 4, 14, 15, 4, 13, 0, 1, 15, 15, 15, 17, 10]) sage: B.det() 1 sage: L = lift_for_SL(B) sage: L.det() 1 sage: (L - B) == 0 True TESTS:: sage: lift_for_SL(matrix(3,3,[1,2,0,3,4,0,0,0,1]),3) [10 14 3] [ 9 10 3] [ 3 3 1] sage: A = matrix(Zmod(7), 2, [1,0,0,1]) sage: L = lift_for_SL(A) sage: L.parent() Full MatrixSpace of 2 by 2 dense matrices over Integer Ring sage: A = matrix(Zmod(7), 1, [1]) sage: L = lift_for_SL(A); L [1] sage: A = matrix(ZZ, 2, [1,0,0,1]) sage: lift_for_SL(A) Traceback (most recent call last): ... ValueError: you must choose the modulus sage: for _ in range(100): ....: d = randint(0, 10) ....: p = choice([2,3,5,7,11]) ....: M = random_matrix(Zmod(p), d, algorithm='unimodular') ....: assert lift_for_SL(M).det() == 1 """ from sage.matrix.special import (identity_matrix, diagonal_matrix, block_diagonal_matrix) from sage.misc.misc_c import prod ring = A.parent().base_ring() if N is None: if ring is ZZ: raise ValueError('you must choose the modulus') else: N = ring.characteristic() m = A.nrows() if m <= 1: return identity_matrix(ZZ, m) AZZ = A.change_ring(ZZ) D, U, V = AZZ.smith_form() diag = diagonal_matrix([-1] + [1] * (m - 1)) if U.det() == -1: U = diag * U if V.det() == -1: V = V * diag a = [U.row(i) * AZZ * V.column(i) for i in range(m)] b = prod(a[1:]) Winv = identity_matrix(m) Winv[1, 0] = 1 - b Winv[0, 1] = -1 Winv[1, 1] = b Xinv = identity_matrix(m) Xinv[0, 1] = a[1] Cp = diagonal_matrix(a[1:]) Cp[0, 0] *= a[0] C = lift_for_SL(Cp, N) Cpp = block_diagonal_matrix(identity_matrix(1), C) Cpp[1, 0] = 1 - a[0] return (~U * Winv * Cpp * Xinv * ~V).change_ring(ZZ)
def __init__(self, R, ngens=None, gram_matrix=None, names=None, index_set=None): """ Initialize self. TESTS:: sage: V = lie_conformal_algebras.Weyl(QQ) sage: TestSuite(V).run() """ from sage.matrix.matrix_space import MatrixSpace if ngens: try: from sage.rings.integer_ring import ZZ assert ngens in ZZ and ngens % 2 == 0 except AssertionError: raise ValueError("ngens needs to be an even positive " + "Integer, got {}".format(ngens)) if (gram_matrix is not None): if ngens is None: ngens = gram_matrix.dimensions()[0] try: assert (gram_matrix in MatrixSpace(R, ngens, ngens)) except AssertionError: raise ValueError( "The gram_matrix should be a skew-symmetric " + "{0} x {0} matrix, got {1}".format(ngens, gram_matrix)) if (not gram_matrix.is_skew_symmetric()) or \ (gram_matrix.is_singular()): raise ValueError("The gram_matrix should be a non degenerate " + "skew-symmetric {0} x {0} matrix, got {1}"\ .format(ngens,gram_matrix)) elif (gram_matrix is None): if ngens is None: ngens = 2 A = identity_matrix(R, ngens // 2) from sage.matrix.special import block_matrix gram_matrix = block_matrix([[R.zero(), A], [-A, R.zero()]]) latex_names = None if (names is None) and (index_set is None): names = 'alpha' latex_names = tuple(r'\alpha_{%d}' % i \ for i in range (ngens)) + ('K',) names, index_set = standardize_names_index_set(names=names, index_set=index_set, ngens=ngens) weyldict = {(i, j): { 0: { ('K', 0): gram_matrix[index_set.rank(i), index_set.rank(j)] } } for i in index_set for j in index_set} super(WeylLieConformalAlgebra, self).__init__(R, weyldict, names=names, latex_names=latex_names, index_set=index_set, central_elements=('K', )) self._gram_matrix = gram_matrix
def _identity_matrix(point, eps): Scalars = ComplexBallField(utilities.prec_from_eps(eps)) return matrix.identity_matrix(Scalars, point.dop.order())
def test_monodromy(self, nsteps_x, nsteps_y, save=False): import os.path, csv from csv import DictReader, DictWriter import sage.all from sage.matrix.special import identity_matrix from sage.rings.complex_double import CDF from sage.symbolic.constants import e x_list = lin_space(self._xmin, self._xmax, nsteps_x) y_list = lin_space(self._ymin, self._ymax, nsteps_y) tab = [[self.section_f(x, y) for y in y_list] for x in x_list] fieldnames = [ 'Exp', 'i', 'j', 'x_plot', 'y_plot', 'dist_id', 'dist_ev' ] f_name = self.f_name(nsteps_x, nsteps_y, -1, -1) if os.path.isfile(f_name): with open(f_name, 'r') as csvfile: reader = csv.DictReader(csvfile, delimiter=',') test = 1 for row in reader: test = 0 if test: last_i, last_j = 0, 0 else: last_i, last_j = int(row['i']), int(row['j']) else: with open(f_name, 'w') as csvfile: writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() last_i, last_j = 0, 0 with open(f_name, 'a') as csvfile: from cmath import exp, pi from __builtin__ import set from sage.misc.functional import numerical_approx writer = csv.DictWriter(csvfile, fieldnames=fieldnames) if not ((last_i == nsteps_x - 1) and (last_i == nsteps_y - 1)): for i in range(last_i, nsteps_x): for j in range(last_j + 1 if i == last_i else 0, nsteps_y): print i, j M0, M1, MInfty = tab[i][j].monodromy_matrices() n = tab[i][j]._dimension beta = tab[i][j]._beta dist_id = (M0 * M1 * MInfty - identity_matrix(CDF, n)).norm() ev = set(MInfty.eigenvalues()) d = [] for b in beta: val = exp(-2 * 1j * pi * b) c_val = min(ev, key=lambda z: (z - val).norm()) d.append((c_val - val).norm()) ev.remove(c_val) dist_ev = max(d) print max(dist_ev, dist_id) writer.writerow({ 'Exp': tab[i][j], 'i': i, 'j': j, 'x_plot': tab[i][j].x_plot, 'y_plot': tab[i][j].y_plot, 'dist_id': dist_id, 'dist_ev': dist_ev }) return True
def signed_hermite_normal_form(A): """ Signed Hermite normal form of an integer matrix A, see [PP19, Section 6]. This is a normal form up to left-multiplication by invertible matrices and change of sign of the columns. A matrix in signed Hermite normal form is also in left Hermite normal form. """ r = A.nrows() n = A.ncols() G_basis = [] # Z_2-basis of G m = 0 # rank of A[:,:j] for j in range(n): A = A.echelon_form() q = A[m, j] if m < r else 0 # pivot phi = [] for S in G_basis: H, U = (A[:, :j] * S[:j, :j]).echelon_form(transformation=True) assert H == A[:, :j] phi.append(U) G_basis.append(diagonal_matrix([-1 if i == j else 1 for i in range(n)])) phi.append(diagonal_matrix([-1 if i < m else 1 for i in range(r)])) assert len(G_basis) == len(phi) for i in reversed(range(m)): # find possible values of A[i,j] x = A[i, j] if q > 0: x %= q orbit = [x] columns = [A[:, j]] construction = [identity_matrix(n)] # which element of G gives a certain element of the orbit new_elements = True while new_elements: new_elements = False for h, U in enumerate(phi): for k, v in enumerate(columns): w = U * v y = w[i, 0] if q > 0: y %= q if y not in orbit: orbit.append(y) columns.append(w) construction.append(G_basis[h] * construction[k]) new_elements = True assert len(orbit) in [1, 2, 4] # find action of G on the orbit action = [] for h, U in enumerate(phi): if q > 0: action.append({x: (U * columns[k])[i, 0] % q for k, x in enumerate(orbit)}) else: action.append({x: (U * columns[k])[i, 0] for k, x in enumerate(orbit)}) # select the minimal possible value u = min(orbit) k = orbit.index(u) S = construction[k] # change A A = (A * S).echelon_form() assert A[i, j] == u # update the stabilizer G G_new_basis = [] # basis for the new stabilizer new_phi = [] # value of phi on the new basis elements complement_basis = {} # dictionary of the form {x: h}, where G_basis[h] sends u to x for h, S in enumerate(G_basis): if action[h][u] == u: # this basis element fixes u G_new_basis.append(S) new_phi.append(phi[h]) elif action[h][u] in complement_basis: # we already encountered a basis element which sends u to action[h][u] G_new_basis.append(S * G_basis[complement_basis[action[h][u]]]) new_phi.append(phi[h] * phi[complement_basis[action[h][u]]]) elif len(complement_basis) == 2: # the product of the two basis elements of the complement sends u to action[h][u] x, y = list(complement_basis) G_new_basis.append(S * G_basis[complement_basis[x]] * G_basis[complement_basis[y]]) new_phi.append(phi[h] * phi[complement_basis[x]] * phi[complement_basis[y]]) else: # add S to the basis of the complement complement_basis[action[h][u]] = h assert len(G_new_basis) + len(complement_basis) == len(G_basis) G_basis = G_new_basis phi = new_phi assert len(G_basis) == len(phi) if q != 0: m += 1 return A
def test_monodromy(self, nsteps_x, nsteps_y, save=False): import os.path, csv from csv import DictReader, DictWriter import sage.all from sage.matrix.special import identity_matrix from sage.rings.complex_double import CDF from sage.symbolic.constants import e x_list = lin_space(self._xmin, self._xmax, nsteps_x) y_list = lin_space(self._ymin, self._ymax, nsteps_y) tab = [[self.section_f(x,y) for y in y_list] for x in x_list] fieldnames = ['Exp', 'i', 'j', 'x_plot', 'y_plot', 'dist_id', 'dist_ev'] f_name = self.f_name(nsteps_x, nsteps_y, -1, -1) if os.path.isfile(f_name) : with open(f_name, 'r') as csvfile : reader= csv.DictReader(csvfile, delimiter=',') test = 1 for row in reader : test = 0 if test: last_i, last_j = 0, 0 else: last_i, last_j = int(row['i']), int(row['j']) else : with open(f_name, 'w') as csvfile : writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() last_i, last_j = 0, 0 with open(f_name, 'a') as csvfile : from cmath import exp, pi from __builtin__ import set from sage.misc.functional import numerical_approx writer = csv.DictWriter(csvfile, fieldnames=fieldnames) if not ((last_i == nsteps_x-1) and (last_i == nsteps_y-1)): for i in range(last_i,nsteps_x): for j in range(last_j+1 if i == last_i else 0, nsteps_y): print i,j M0, M1, MInfty = tab[i][j].monodromy_matrices() n = tab[i][j]._dimension beta = tab[i][j]._beta dist_id = (M0*M1*MInfty - identity_matrix(CDF,n)).norm() ev = set(MInfty.eigenvalues()) d = [] for b in beta: val = exp(-2*1j*pi*b) c_val = min(ev, key=lambda z: (z-val).norm()) d.append((c_val-val).norm()) ev.remove(c_val) dist_ev = max(d) print max(dist_ev, dist_id) writer.writerow({'Exp' : tab[i][j], 'i' : i, 'j' : j, 'x_plot' : tab[i][j].x_plot, 'y_plot' : tab[i][j].y_plot, 'dist_id' : dist_id, 'dist_ev' : dist_ev}) return True
def amortized_padic_H_values(self, t, testp=None, verbose=False, use_c=False): """ INPUT: - `t` -- a rational number OUTPUT: - a dictionary with inputs `p` and outputs the corresponding p-adic H value at `t`. TESTS:: sage: for cyca, cycb, t in [ ...: ([6], [1, 1], 331), ...: ([4, 2, 2], [3, 1, 1], 3678), ...: ([22], [1, 1, 20], 1337/507734), ...: ([5],[1,1,1,1], 2313), ...: ([12],[2,2,1,1], 313) ...:]: ...: H = AmortizingHypergeometricData(1000, cyclotomic=(cyca, cycb)) ...: for p, v in H.amortized_padic_H_values(t).items(): ...: if v != H.naive_padic_H_value(t=t, p=p, verbose=False): ...: print(p, cyca, cycb, t) """ ## TODO: skip over intermediate ranges with positive p-shift #pshift = self.pshift #for last_zero, s in reversed(list(enumerate(pshift))): # if s == 0: # break #breaks = self.breaks[:last_zero+2] # also include the endpoint of the last interval #pshift = pshift[:last_zero+1] #starts = breaks[:-1] #ends = breaks[1:] # TODO: fix global sign from (-p)^eta # forests = {} tmp = identity_matrix(2) vectors = {p: tmp for p in self._prime_range(t)[1][0]} # def update(p, A): # # If the interval was empty we get A=1 # if not (isinstance(A, Integer) and A == 1): # vectors[p][0] *= A # vectors[p][1] *= A[0,0] # vectors[p] %= p def multiplier(x): return -x if x else -1 def functional_eqn(a, g, c, d): return (multiplier(a - g - c / d) if a >= g else 1) * (multiplier(a - g - c / d + 1) if a <= g else 1) # feq_seed = prod(a if a else -1 for a in self._alpha) / prod(b if b else -1 for b in self._beta) # for p in self._prime_range(t)[1][0]: # R = vectors[p].base_ring() # update(p, self.fix_break(t, ZZ(0), p, R, feq_seed)) # if p == testp: # P_start = R(t)**1 * H.pochhammer_quotient(p, 1) # assert vectors[p][1] == P_start, "brk = 0, %s != %s" % (vectors[p][1], P_start) for start, end in zip(self.starts, self.ends): d = start.denominator() for pclass in range(d): if d.gcd(pclass) != 1: continue c = (d * start * (pclass - 1)) % d feq_seed = t * prod( functional_eqn(a, start, c, d) for a in self._alpha) / prod( functional_eqn(b, start, c, d) for b in self._beta) feq_seed_num = feq_seed.numer() feq_seed_den = feq_seed.denom() if pclass == 1: pmult = self.break_mults_p1[start] else: pmult = self.break_mults[start] fixbreak = matrix( ZZ, 2, 2, [feq_seed_den, 0, feq_seed_den * pmult, feq_seed_num]) # this updates vectors self.amortized_padic_H_values_interval( vectors, t, start, end, pclass, fixbreak, testp=testp, use_c=use_c, ) return {p: (vectors[p][1, 0] / vectors[p][0, 0]) % p for p in vectors}
def _matrix_smith_form(self, M, transformation, integral, exact): r""" Return the Smith normal form of the matrix `M`. This method gets called by :meth:`sage.matrix.matrix2.Matrix.smith_form` to compute the Smith normal form over local rings and fields. The entries of the Smith normal form are normalized such that non-zero entries of the diagonal are powers of the distinguished uniformizer. INPUT: - ``M`` -- a matrix over this ring - ``transformation`` -- a boolean; whether the transformation matrices are returned - ``integral`` -- a subring of the base ring or ``True``; the entries of the transformation matrices are in this ring. If ``True``, the entries are in the ring of integers of the base ring. - ``exact`` -- boolean. If ``True``, the diagonal smith form will be exact, or raise a ``PrecisionError`` if this is not posssible. If ``False``, the diagonal entries will be inexact, but the transformation matrices will be exact. EXAMPLES:: sage: A = Zp(5, prec=10, print_mode="digits") sage: M = matrix(A, 2, 2, [2, 7, 1, 6]) sage: S, L, R = M.smith_form() # indirect doctest sage: S [ ...1 0] [ 0 ...10] sage: L [...222222223 ...] [...444444444 ...2] sage: R [...0000000001 ...2222222214] [ 0 ...0000000001] If not needed, it is possible to avoid the computation of the transformations matrices `L` and `R`:: sage: M.smith_form(transformation=False) # indirect doctest [ ...1 0] [ 0 ...10] This method works for rectangular matrices as well:: sage: M = matrix(A, 3, 2, [2, 7, 1, 6, 3, 8]) sage: S, L, R = M.smith_form() # indirect doctest sage: S [ ...1 0] [ 0 ...10] [ 0 0] sage: L [...222222223 ... ...] [...444444444 ...2 ...] [...444444443 ...1 ...1] sage: R [...0000000001 ...2222222214] [ 0 ...0000000001] If some of the elementary divisors have valuation larger than the minimum precision of any entry in the matrix, then they are reported as an inexact zero:: sage: A = ZpCA(5, prec=10) sage: M = matrix(A, 2, 2, [5, 5, 5, 5]) sage: M.smith_form(transformation=False, exact=False) # indirect doctest [5 + O(5^10) O(5^10)] [ O(5^10) O(5^10)] However, an error is raised if the precision on the entries is not enough to determine which column to use as a pivot at some point:: sage: M = matrix(A, 2, 2, [A(0,5), A(5^6,10), A(0,8), A(5^7,10)]); M [ O(5^5) 5^6 + O(5^10)] [ O(5^8) 5^7 + O(5^10)] sage: M.smith_form(transformation=False, exact=False) # indirect doctest Traceback (most recent call last): ... PrecisionError: not enough precision to compute Smith normal form TESTS:: sage: A = ZpCR(5, prec=10) sage: M = zero_matrix(A, 2) sage: M.smith_form(transformation=False) # indirect doctest [0 0] [0 0] sage: M = matrix(2, 2, [ A(0,10), 0, 0, 0] ) sage: M.smith_form(transformation=False) # indirect doctest Traceback (most recent call last): ... PrecisionError: some elementary divisors indistinguishable from zero (try exact=False) sage: M.smith_form(transformation=False, exact=False) # indirect doctest [O(5^10) O(5^10)] [O(5^10) O(5^10)] """ from sage.rings.all import infinity from sage.matrix.constructor import matrix from .precision_error import PrecisionError from copy import copy n = M.nrows() m = M.ncols() if m > n: ## It's easier below if we can always deal with precision on left. if transformation: d, u, v = self._matrix_smith_form(M.transpose(), True, integral, exact) return d.transpose(), v.transpose(), u.transpose() else: return self._matrix_smith_form(M.transpose(), False, integral, exact).transpose() smith = M.parent()(0) S = copy(M) Z = self.integer_ring() if integral is None or integral is self or integral is ( not self.is_field()): integral = not self.is_field() R = self elif integral is True or integral is Z: # This is a field, but we want the integral smith form # The diagonal matrix may not be integral, but the transformations should be R = Z integral = True elif integral is False or integral is self.fraction_field(): # This is a ring, but we want the field smith form # The diagonal matrix should be over this ring, but the transformations should not R = self.fraction_field() integral = False else: raise NotImplementedError("Smith normal form over this subring") ## the difference between ball_prec and inexact_ring is just for lattice precision. ball_prec = R._prec_type() in ['capped-rel', 'capped-abs'] inexact_ring = R._prec_type() not in ['fixed-mod', 'floating-point'] precM = min(x.precision_absolute() for x in M.list()) if transformation: from sage.matrix.special import identity_matrix left = identity_matrix(R, n) right = identity_matrix(R, m) if ball_prec and precM is infinity: # capped-rel and M = 0 exactly return (smith, left, right) if transformation else smith val = -infinity for piv in range(m): # m <= n curval = infinity pivi = pivj = piv # allzero tracks whether every possible pivot is zero. # if so, we can stop. allzero is also used in detecting some # precision problems: if we can't determine what pivot has # the smallest valuation, or if exact=True and some elementary # divisor is zero modulo the working precision allzero = True # allexact is tracked because there is one case where we can correctly # deduce the exact smith form even with some elementary divisors zero: # if the bottom right block consists entirely of exact zeros. allexact = True for i in range(piv, n): for j in range(piv, m): Sij = S[i, j] v = Sij.valuation() allzero = allzero and Sij.is_zero() if exact: # we only care in this case allexact = allexact and Sij.precision_absolute( ) is infinity if v < curval: pivi = i pivj = j curval = v if v == val: break else: continue break val = curval if inexact_ring and not allzero and val >= precM: if ball_prec: raise PrecisionError( "not enough precision to compute Smith normal form") precM = min([ S[i, j].precision_absolute() for i in range(piv, n) for j in range(piv, m) ]) if val >= precM: raise PrecisionError( "not enough precision to compute Smith normal form") if allzero: if exact: if allexact: # We need to finish checking allexact since we broke out of the loop early for i in range(i, n): for j in range(piv, m): allexact = allexact and S[ i, j].precision_absolute() is infinity if not allexact: break else: continue break if not allexact: raise PrecisionError( "some elementary divisors indistinguishable from zero (try exact=False)" ) break # We swap the lowest valuation pivot into position S.swap_rows(pivi, piv) S.swap_columns(pivj, piv) if transformation: left.swap_rows(pivi, piv) right.swap_columns(pivj, piv) # ... and clear out this row and column. Note that we # will deal with precision later, thus the call to lift_to_precision smith[piv, piv] = self(1) << val inv = (S[piv, piv] >> val).inverse_of_unit() if ball_prec: inv = inv.lift_to_precision() for i in range(piv + 1, n): scalar = -inv * Z(S[i, piv] >> val) if ball_prec: scalar = scalar.lift_to_precision() S.add_multiple_of_row(i, piv, scalar, piv + 1) if transformation: left.add_multiple_of_row(i, piv, scalar) if transformation: left.rescale_row(piv, inv) for j in range(piv + 1, m): scalar = -inv * Z(S[piv, j] >> val) if ball_prec: scalar = scalar.lift_to_precision() right.add_multiple_of_column(j, piv, scalar) else: # We use piv as an upper bound on a range below, and need to set it correctly # in the case that we didn't break out of the loop piv = m # We update the precision on left # The bigoh measures the effect of multiplying by row operations # on the left in order to clear out the digits in the smith form # with valuation at least precM if ball_prec and exact and transformation: for j in range(n): delta = min(left[i, j].valuation() - smith[i, i].valuation() for i in range(piv)) if delta is not infinity: for i in range(n): left[i, j] = left[i, j].add_bigoh(precM + delta) ## Otherwise, we update the precision on smith if ball_prec and not exact: smith = smith.apply_map(lambda x: x.add_bigoh(precM)) ## We now have to adjust the elementary divisors (and precision) in the non-integral case if not integral: for i in range(piv): v = smith[i, i].valuation() if transformation: for j in range(n): left[i, j] = left[i, j] >> v if exact: smith[i, i] = self(1) else: for j in range(n): smith[i, j] = smith[i, j] >> v if transformation: return smith, left, right else: return smith
def _monodromy_matrices(dop, base, eps=1e-16, sing=None): r""" EXAMPLES:: sage: from ore_algebra import * sage: from ore_algebra.analytic.monodromy import _monodromy_matrices sage: Dops, x, Dx = DifferentialOperators() sage: rat = 1/(x^2-1) sage: dop = (rat*Dx - rat.derivative()).lclm(Dx*x*Dx) sage: [rec.point for rec in _monodromy_matrices(dop, 0) if not rec.is_scalar] [0] """ dop = DifferentialOperator(dop) base = QQbar.coerce(base) eps = RBF(eps) if sing is None: sing = dop._singularities(QQbar) else: sing = [QQbar.coerce(s) for s in sing] todo = { x: PointWithMonodromyData(x, dop, want_self=True, want_conj=False) for x in sing } base = todo.setdefault(base, PointWithMonodromyData(base, dop)) if not base.is_regular(): raise ValueError("irregular singular base point") # If the coefficients are rational, reduce to handling singularities in the # same half-plane as the base point. need_conjugates = _try_merge_conjugate_singularities(dop, sing, base, todo) Scalars = ComplexBallField(utilities.prec_from_eps(eps)) id_mat = matrix.identity_matrix(Scalars, dop.order()) def matprod(elts): return prod(reversed(elts), id_mat) for key, point in list(todo.items()): # We could call _local_monodromy_loop() if point is irregular, but # delaying it may allow us to start returning results earlier. if point.is_regular(): mon, scalar = _formal_monodromy_naive(dop.shift(point), Scalars) if scalar: # No need to compute the connection matrices then! # XXX When we do need them, though, it would be better to get # the formal monodromy as a byproduct of their computation. if point.want_self: yield LocalMonodromyData(QQbar(point), mon, True) if point.want_conj: conj = point.conjugate() logger.info( "Computing local monodromy around %s by " "complex conjugation", conj) conj_mat = ~mon.conjugate() yield LocalMonodromyData(QQbar(conj), conj_mat, True) if point is not base: del todo[key] continue point.local_monodromy = [mon] point.polygon = [point] if need_conjugates: base_conj_mat = dop.numerical_transition_matrix( [base, base.conjugate()], eps, assume_analytic=True) def conjugate_monodromy(mat): return ~base_conj_mat * ~mat.conjugate() * base_conj_mat tree = _spanning_tree(base, todo.values()) def dfs(x, path, path_mat): logger.info("Computing local monodromy around %s via %s", x, path) local_mat = matprod(x.local_monodromy) based_mat = (~path_mat) * local_mat * path_mat if x.want_self: yield LocalMonodromyData(QQbar(x), based_mat, False) if x.want_conj: conj = x.conjugate() logger.info( "Computing local monodromy around %s by complex " "conjugation", conj) conj_mat = conjugate_monodromy(based_mat) yield LocalMonodromyData(QQbar(x), conj_mat, False) x.done = True for y in tree.neighbors(x): if y.done: continue if y.local_monodromy is None: y.polygon, y.local_monodromy = _local_monodromy_loop( dop, y, eps) new_path_mat = _extend_path_mat(dop, path_mat, x, y, eps, matprod) yield from dfs(y, path + [y], new_path_mat) yield from dfs(base, [base], id_mat)
def _monodromy_matrices(dop, base, eps=1e-16, sing=None): r""" EXAMPLES:: sage: from ore_algebra import * sage: from ore_algebra.analytic.monodromy import _monodromy_matrices sage: Dops, x, Dx = DifferentialOperators() sage: rat = 1/(x^2-1) sage: dop = (rat*Dx - rat.derivative()).lclm(Dx*x*Dx) sage: [rec.point for rec in _monodromy_matrices(dop, 0) if not rec.is_scalar] [0] TESTS:: sage: from ore_algebra.examples import fcc sage: mon = list(_monodromy_matrices(fcc.dop5, -1, 2**(-2**7))) # long time (2.3 s) sage: [rec.monodromy[0][0] for rec in mon if rec.point == -5/3] # long time [[1.01088578589319884254557667137848...]] Thanks to Alexandre Goyer for this example:: sage: L1 = ((x^5 - x^4 + x^3)*Dx^3 + (27/8*x^4 - 25/9*x^3 + 8*x^2)*Dx^2 ....: + (37/24*x^3 - 25/9*x^2 + 14*x)*Dx - 2*x^2 - 3/4*x + 4) sage: L2 = ((x^5 - 9/4*x^4 + x^3)*Dx^3 + (11/6*x^4 - 31/4*x^3 + 7*x^2)*Dx^2 ....: + (7/30*x^3 - 101/20*x^2 + 10*x)*Dx + 4/5*x^2 + 5/6*x + 2) sage: L = L1*L2 sage: L = L.parent()(L.annihilator_of_composition(x+1)) sage: mon = list(_monodromy_matrices(L, 0, eps=1e-30)) # long time (1.3-1.7 s) sage: mon[-1][0], mon[-1][1][0][0] # long time (0.6403882032022075?, [1.15462187280628880820271...] + [-0.018967673022432256251718...]*I) """ dop = DifferentialOperator(dop) base = QQbar.coerce(base) eps = RBF(eps) if sing is None: sing = dop._singularities(QQbar) else: sing = [QQbar.coerce(s) for s in sing] todo = {x: TodoItem(x, dop, want_self=True, want_conj=False) for x in sing} base = todo.setdefault(base, TodoItem(base, dop)) if not base.point().is_regular(): raise ValueError("irregular singular base point") # If the coefficients are rational, reduce to handling singularities in the # same half-plane as the base point, and share some computations between # Galois conjugates. need_conjugates = False crit_cache = None if all(c in QQ for pol in dop for c in pol): need_conjugates = _merge_conjugate_singularities(dop, sing, base, todo) # TODO: do something like that even over number fields? # XXX this is actually a bit costly: do it only after checking that the # monodromy is not scalar? # XXX keep the cache from one run to the next when increasing prec? crit_cache = {} Scalars = ComplexBallField(utilities.prec_from_eps(eps)) id_mat = matrix.identity_matrix(Scalars, dop.order()) def matprod(elts): return prod(reversed(elts), id_mat) for key, todoitem in list(todo.items()): point = todoitem.point() # We could call _local_monodromy_loop() if point is irregular, but # delaying it may allow us to start returning results earlier. if point.is_regular(): if crit_cache is None or point.algdeg() == 1: crit = _critical_monomials(dop.shift(point)) emb = point.value.parent().hom(Scalars) else: mpol = point.value.minpoly() try: NF, crit = crit_cache[mpol] except KeyError: NF = point.value.parent() crit = _critical_monomials(dop.shift(point)) # Only store the critical monomials for reusing when all # local exponents are rational. We need to restrict to this # case because we do not have the technology in place to # follow algebraic exponents along the embedding of NF in ℂ. # (They are represented as elements of "new" number fields # given by as_embedded_number_field_element(), even when # they actually lie in NF itself as opposed to a further # algebraic extension. XXX: Ideally, LocalBasisMapper should # give us access to the tower of extensions in which the # exponents "naturally" live.) if all(sol.leftmost.parent() is QQ for sol in crit): crit_cache[mpol] = NF, crit emb = NF.hom([Scalars(point.value.parent().gen())], check=False) mon, scalar = _formal_monodromy_from_critical_monomials(crit, emb) if scalar: # No need to compute the connection matrices then! # XXX When we do need them, though, it would be better to get # the formal monodromy as a byproduct of their computation. if todoitem.want_self: yield LocalMonodromyData(key, mon, True) if todoitem.want_conj: conj = key.conjugate() logger.info( "Computing local monodromy around %s by " "complex conjugation", conj) conj_mat = ~mon.conjugate() yield LocalMonodromyData(conj, conj_mat, True) if todoitem is not base: del todo[key] continue todoitem.local_monodromy = [mon] todoitem.polygon = [point] if need_conjugates: base_conj_mat = dop.numerical_transition_matrix( [base.point(), base.point().conjugate()], eps, assume_analytic=True) def conjugate_monodromy(mat): return ~base_conj_mat * ~mat.conjugate() * base_conj_mat tree = _spanning_tree(base, todo.values()) def dfs(x, path, path_mat): logger.info("Computing local monodromy around %s via %s", x, path) local_mat = matprod(x.local_monodromy) based_mat = (~path_mat) * local_mat * path_mat if x.want_self: yield LocalMonodromyData(x.alg, based_mat, False) if x.want_conj: conj = x.alg.conjugate() logger.info( "Computing local monodromy around %s by complex " "conjugation", conj) conj_mat = conjugate_monodromy(based_mat) yield LocalMonodromyData(conj, conj_mat, False) x.done = True for y in tree.neighbors(x): if y.done: continue if y.local_monodromy is None: y.polygon, y.local_monodromy = _local_monodromy_loop( dop, y.point(), eps) new_path_mat = _extend_path_mat(dop, path_mat, x, y, eps, matprod) yield from dfs(y, path + [y], new_path_mat) yield from dfs(base, [base], id_mat)
def _monodromy_matrices(dop, base, eps=1e-16, sing=None): r""" EXAMPLES:: sage: from ore_algebra import * sage: from ore_algebra.analytic.monodromy import _monodromy_matrices sage: Dops, x, Dx = DifferentialOperators() sage: rat = 1/(x^2-1) sage: dop = (rat*Dx - rat.derivative()).lclm(Dx*x*Dx) sage: [rec.point for rec in _monodromy_matrices(dop, 0) if not rec.is_scalar] [0] TESTS:: sage: from ore_algebra.examples import fcc sage: mon = list(_monodromy_matrices(fcc.dop5, -1, 2**(-2**7))) # long time (2.3 s) sage: [rec.monodromy[0][0] for rec in mon if rec.point == -5/3] # long time [[1.01088578589319884254557667137848...]] """ dop = DifferentialOperator(dop) base = QQbar.coerce(base) eps = RBF(eps) if sing is None: sing = dop._singularities(QQbar) else: sing = [QQbar.coerce(s) for s in sing] todo = {x: TodoItem(x, dop, want_self=True, want_conj=False) for x in sing} base = todo.setdefault(base, TodoItem(base, dop)) if not base.point().is_regular(): raise ValueError("irregular singular base point") # If the coefficients are rational, reduce to handling singularities in the # same half-plane as the base point, and share some computations between # Galois conjugates. need_conjugates = False crit_cache = None if all(c in QQ for pol in dop for c in pol): need_conjugates = _merge_conjugate_singularities(dop, sing, base, todo) # TODO: do something like that even over number fields? # XXX this is actually a bit costly: do it only after checking that the # monodromy is not scalar? # XXX keep the cache from one run to the next when increasing prec? crit_cache = {} Scalars = ComplexBallField(utilities.prec_from_eps(eps)) id_mat = matrix.identity_matrix(Scalars, dop.order()) def matprod(elts): return prod(reversed(elts), id_mat) for key, todoitem in list(todo.items()): point = todoitem.point() # We could call _local_monodromy_loop() if point is irregular, but # delaying it may allow us to start returning results earlier. if point.is_regular(): if crit_cache is None or point.algdeg() == 1: crit = _critical_monomials(dop.shift(point)) emb = point.value.parent().hom(Scalars) else: mpol = point.value.minpoly() try: NF, crit = crit_cache[mpol] except KeyError: NF = point.value.parent() crit = _critical_monomials(dop.shift(point)) crit_cache[mpol] = NF, crit emb = NF.hom([Scalars(point.value.parent().gen())], check=False) mon, scalar = _formal_monodromy_from_critical_monomials(crit, emb) if scalar: # No need to compute the connection matrices then! # XXX When we do need them, though, it would be better to get # the formal monodromy as a byproduct of their computation. if todoitem.want_self: yield LocalMonodromyData(key, mon, True) if todoitem.want_conj: conj = key.conjugate() logger.info( "Computing local monodromy around %s by " "complex conjugation", conj) conj_mat = ~mon.conjugate() yield LocalMonodromyData(conj, conj_mat, True) if todoitem is not base: del todo[key] continue todoitem.local_monodromy = [mon] todoitem.polygon = [point] if need_conjugates: base_conj_mat = dop.numerical_transition_matrix( [base.point(), base.point().conjugate()], eps, assume_analytic=True) def conjugate_monodromy(mat): return ~base_conj_mat * ~mat.conjugate() * base_conj_mat tree = _spanning_tree(base, todo.values()) def dfs(x, path, path_mat): logger.info("Computing local monodromy around %s via %s", x, path) local_mat = matprod(x.local_monodromy) based_mat = (~path_mat) * local_mat * path_mat if x.want_self: yield LocalMonodromyData(x.alg, based_mat, False) if x.want_conj: conj = x.alg.conjugate() logger.info( "Computing local monodromy around %s by complex " "conjugation", conj) conj_mat = conjugate_monodromy(based_mat) yield LocalMonodromyData(conj, conj_mat, False) x.done = True for y in tree.neighbors(x): if y.done: continue if y.local_monodromy is None: y.polygon, y.local_monodromy = _local_monodromy_loop( dop, y.point(), eps) new_path_mat = _extend_path_mat(dop, path_mat, x, y, eps, matprod) yield from dfs(y, path + [y], new_path_mat) yield from dfs(base, [base], id_mat)
def centralizer_basis(self, S): """ Return a basis of the centralizer of ``S`` in ``self``. INPUT: - ``S`` -- a subalgebra of ``self`` or a list of elements that represent generators for a subalgebra .. SEEALSO:: :meth:`centralizer` EXAMPLES:: sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example() sage: a,b,c = L.lie_algebra_generators() sage: L.centralizer_basis([a + b, 2*a + c]) [(1, 0, 0), (0, 1, 0), (0, 0, 1)] sage: H = lie_algebras.Heisenberg(QQ, 2) sage: H.centralizer_basis(H) [z] sage: D = DescentAlgebra(QQ, 4).D() sage: L = LieAlgebra(associative=D) sage: L.centralizer_basis(L) [D{}, D{1} + D{1, 2} + D{2, 3} + D{3}, D{1, 2, 3} + D{1, 3} + D{2}] sage: D.center_basis() (D{}, D{1} + D{1, 2} + D{2, 3} + D{3}, D{1, 2, 3} + D{1, 3} + D{2}) """ #from sage.algebras.lie_algebras.subalgebra import LieSubalgebra #if isinstance(S, LieSubalgebra) or S is self: if S is self: from sage.matrix.special import identity_matrix m = identity_matrix(self.base_ring(), self.dimension()) elif isinstance(S, (list, tuple)): m = matrix([v.to_vector() for v in self.echelon_form(S)]) else: m = self.subalgebra(S).basis_matrix() S = self.structure_coefficients() sc = {} for k in S.keys(): v = S[k].to_vector() sc[k] = v sc[k[1],k[0]] = -v X = self.basis().keys() d = len(X) c_mat = matrix(self.base_ring(), [[sum(m[i,j] * sc[x,xp][k] for j,xp in enumerate(X) if (x, xp) in sc) for x in X] for i in range(d) for k in range(d)]) C = c_mat.right_kernel().basis_matrix() return [self.from_vector(v) for v in C]
def _matrix_smith_form(self, M, transformation, integral, exact): r""" Return the Smith normal form of the matrix `M`. This method gets called by :meth:`sage.matrix.matrix2.Matrix.smith_form` to compute the Smith normal form over local rings and fields. The entries of the Smith normal form are normalized such that non-zero entries of the diagonal are powers of the distinguished uniformizer. INPUT: - ``M`` -- a matrix over this ring - ``transformation`` -- a boolean; whether the transformation matrices are returned - ``integral`` -- a subring of the base ring or ``True``; the entries of the transformation matrices are in this ring. If ``True``, the entries are in the ring of integers of the base ring. - ``exact`` -- boolean. If ``True``, the diagonal smith form will be exact, or raise a ``PrecisionError`` if this is not posssible. If ``False``, the diagonal entries will be inexact, but the transformation matrices will be exact. EXAMPLES:: sage: A = Zp(5, prec=10, print_mode="digits") sage: M = matrix(A, 2, 2, [2, 7, 1, 6]) sage: S, L, R = M.smith_form() # indirect doctest sage: S [ ...1 0] [ 0 ...10] sage: L [...222222223 ...] [...444444444 ...2] sage: R [...0000000001 ...2222222214] [ 0 ...0000000001] If not needed, it is possible to avoid the computation of the transformations matrices `L` and `R`:: sage: M.smith_form(transformation=False) # indirect doctest [ ...1 0] [ 0 ...10] This method works for rectangular matrices as well:: sage: M = matrix(A, 3, 2, [2, 7, 1, 6, 3, 8]) sage: S, L, R = M.smith_form() # indirect doctest sage: S [ ...1 0] [ 0 ...10] [ 0 0] sage: L [...222222223 ... ...] [...444444444 ...2 ...] [...444444443 ...1 ...1] sage: R [...0000000001 ...2222222214] [ 0 ...0000000001] If some of the elementary divisors have valuation larger than the minimum precision of any entry in the matrix, then they are reported as an inexact zero:: sage: A = ZpCA(5, prec=10) sage: M = matrix(A, 2, 2, [5, 5, 5, 5]) sage: M.smith_form(transformation=False, exact=False) # indirect doctest [5 + O(5^10) O(5^10)] [ O(5^10) O(5^10)] However, an error is raised if the precision on the entries is not enough to determine which column to use as a pivot at some point:: sage: M = matrix(A, 2, 2, [A(0,5), A(5^6,10), A(0,8), A(5^7,10)]); M [ O(5^5) 5^6 + O(5^10)] [ O(5^8) 5^7 + O(5^10)] sage: M.smith_form(transformation=False, exact=False) # indirect doctest Traceback (most recent call last): ... PrecisionError: not enough precision to compute Smith normal form TESTS:: sage: A = ZpCR(5, prec=10) sage: M = zero_matrix(A, 2) sage: M.smith_form(transformation=False) # indirect doctest [0 0] [0 0] sage: M = matrix(2, 2, [ A(0,10), 0, 0, 0] ) sage: M.smith_form(transformation=False) # indirect doctest Traceback (most recent call last): ... PrecisionError: some elementary divisors indistinguishable from zero (try exact=False) sage: M.smith_form(transformation=False, exact=False) # indirect doctest [O(5^10) O(5^10)] [O(5^10) O(5^10)] """ from sage.rings.all import infinity from sage.matrix.constructor import matrix from .precision_error import PrecisionError from copy import copy n = M.nrows() m = M.ncols() if m > n: ## It's easier below if we can always deal with precision on left. if transformation: d, u, v = self._matrix_smith_form(M.transpose(), True, integral, exact) return d.transpose(), v.transpose(), u.transpose() else: return self._matrix_smith_form(M.transpose(), False, integral, exact).transpose() smith = M.parent()(0) S = copy(M) Z = self.integer_ring() if integral is None or integral is self or integral is (not self.is_field()): integral = not self.is_field() R = self elif integral is True or integral is Z: # This is a field, but we want the integral smith form # The diagonal matrix may not be integral, but the transformations should be R = Z integral = True elif integral is False or integral is self.fraction_field(): # This is a ring, but we want the field smith form # The diagonal matrix should be over this ring, but the transformations should not R = self.fraction_field() integral = False else: raise NotImplementedError("Smith normal form over this subring") ## the difference between ball_prec and inexact_ring is just for lattice precision. ball_prec = R._prec_type() in ['capped-rel','capped-abs'] inexact_ring = R._prec_type() not in ['fixed-mod','floating-point'] precM = min(x.precision_absolute() for x in M.list()) if transformation: from sage.matrix.special import identity_matrix left = identity_matrix(R,n) right = identity_matrix(R,m) if ball_prec and precM is infinity: # capped-rel and M = 0 exactly return (smith, left, right) if transformation else smith val = -infinity for piv in range(m): # m <= n curval = infinity pivi = pivj = piv # allzero tracks whether every possible pivot is zero. # if so, we can stop. allzero is also used in detecting some # precision problems: if we can't determine what pivot has # the smallest valuation, or if exact=True and some elementary # divisor is zero modulo the working precision allzero = True # allexact is tracked because there is one case where we can correctly # deduce the exact smith form even with some elementary divisors zero: # if the bottom right block consists entirely of exact zeros. allexact = True for i in range(piv,n): for j in range(piv,m): Sij = S[i,j] v = Sij.valuation() allzero = allzero and Sij.is_zero() if exact: # we only care in this case allexact = allexact and Sij.precision_absolute() is infinity if v < curval: pivi = i; pivj = j curval = v if v == val: break else: continue break val = curval if inexact_ring and not allzero and val >= precM: if ball_prec: raise PrecisionError("not enough precision to compute Smith normal form") precM = min([ S[i,j].precision_absolute() for i in range(piv,n) for j in range(piv,m) ]) if val >= precM: raise PrecisionError("not enough precision to compute Smith normal form") if allzero: if exact: if allexact: # We need to finish checking allexact since we broke out of the loop early for i in range(i,n): for j in range(piv,m): allexact = allexact and S[i,j].precision_absolute() is infinity if not allexact: break else: continue break if not allexact: raise PrecisionError("some elementary divisors indistinguishable from zero (try exact=False)") break # We swap the lowest valuation pivot into position S.swap_rows(pivi,piv) S.swap_columns(pivj,piv) if transformation: left.swap_rows(pivi,piv) right.swap_columns(pivj,piv) # ... and clear out this row and column. Note that we # will deal with precision later, thus the call to lift_to_precision smith[piv,piv] = self(1) << val inv = (S[piv,piv] >> val).inverse_of_unit() if ball_prec: inv = inv.lift_to_precision() for i in range(piv+1,n): scalar = -inv * Z(S[i,piv] >> val) if ball_prec: scalar = scalar.lift_to_precision() S.add_multiple_of_row(i,piv,scalar,piv+1) if transformation: left.add_multiple_of_row(i,piv,scalar) if transformation: left.rescale_row(piv,inv) for j in range(piv+1,m): scalar = -inv * Z(S[piv,j] >> val) if ball_prec: scalar = scalar.lift_to_precision() right.add_multiple_of_column(j,piv,scalar) else: # We use piv as an upper bound on a range below, and need to set it correctly # in the case that we didn't break out of the loop piv = m # We update the precision on left # The bigoh measures the effect of multiplying by row operations # on the left in order to clear out the digits in the smith form # with valuation at least precM if ball_prec and exact and transformation: for j in range(n): delta = min(left[i,j].valuation() - smith[i,i].valuation() for i in range(piv)) if delta is not infinity: for i in range(n): left[i,j] = left[i,j].add_bigoh(precM + delta) ## Otherwise, we update the precision on smith if ball_prec and not exact: smith = smith.apply_map(lambda x: x.add_bigoh(precM)) ## We now have to adjust the elementary divisors (and precision) in the non-integral case if not integral: for i in range(piv): v = smith[i,i].valuation() if transformation: for j in range(n): left[i,j] = left[i,j] >> v if exact: smith[i,i] = self(1) else: for j in range(n): smith[i,j] = smith[i,j] >> v if transformation: return smith, left, right else: return smith