def order_at_cusp(self, cusp: 'CuspFamily') -> Integer: r""" Return the order of vanishing of ``self`` at the given cusp. INPUT: - ``cusp`` -- a :class:`CuspFamily` object OUTPUT: - an integer EXAMPLES:: sage: e = EtaProduct(2, {2:24, 1:-24}) sage: e.order_at_cusp(CuspFamily(2, 1)) # cusp at infinity 1 sage: e.order_at_cusp(CuspFamily(2, 2)) # cusp 0 -1 """ if not isinstance(cusp, CuspFamily): raise TypeError("argument (=%s) should be a CuspFamily" % cusp) if cusp.level() != self._N: raise ValueError("cusp not on right curve") sigma = sum(ell * self._rdict[ell] / cusp.width() * (gcd(cusp.width(), self._N // ell))**2 for ell in self._rdict) return sigma / ZZ(24) / gcd(cusp.width(), self._N // cusp.width())
def kernel_vector(self, way='LLL', verbose=False): r""" todo: clean this EXAMPLES:: sage: from slabbe import ChristoffelGraph sage: C = ChristoffelGraph((2,5,7)) sage: C.kernel_vector() [(-1, -1, 1), (3, -4, 0)] """ from sage.arith.misc import gcd if way == 'vect_gcd': a,b,c = self._v gcd_ac = gcd(a,c) gcd_bc = gcd(b,c) U = ua,ub,uc = vector((c,0,-a)) / gcd(a,c) V = va,vb,vc = vector((0,c,-b)) / gcd(b,c) rows = U,V elif way == 'echelon': a,b,c = self._v m = matrix(ZZ, 4, [1,1,1,c,0,-a,0,c,-b,b,-a,0]) me = m.echelon_form() if verbose: print(me) rows = me[1],me[2] elif way == 'LLL': dim = self.dimension() if dim == 3: a,b,c = self._v M = matrix(ZZ, 4, [1,1,1,c,0,-a,0,c,-b,b,-a,0]) elif dim == 4: a,b,c,d = self._v M = matrix(ZZ, 7, (1,1,1,1,b,-a,0,0,c,0,-a,0,0,c,-b,0,d,0,0,-a,0,d,0,-b,0,0,d,-c)) else: raise ValueError("dimension (=%s) must be 3 or 4" % dim) rows = M.LLL().rows() VS = rows[0].parent() zero = VS(0) un = VS((1,)*dim) assert zero in rows, "(0,0,0) not in LLL result" assert un in rows, "(1,1,1) not in LLL result" while zero in rows: rows.remove(zero) while un in rows: rows.remove(un) elif way == 'vect': a,b,c = self._v U = ua,ub,uc = vector((c,0,-a)) V = va,vb,vc = vector((0,c,-b)) rows = U,V else: raise ValueError("unknown way") R = matrix(rows) if sum(map(abs, R.minors(dim-1))) != sum(map(abs,self._v)): print(R) print(R.minors(dim-1)) print(sum(map(abs, R.minors(dim)))) print(sum(map(abs,self._v))) raise Exception("The result (=%s) is false " % rows) return rows
def __init__(self, V, W, gens=None, modulus=None, modulus_qf=None, check=True): r""" Initialize ``self``. TESTS:: sage: from sage.modules.torsion_quadratic_module import TorsionQuadraticModule sage: T = TorsionQuadraticModule(ZZ^3, 6*ZZ^3) sage: TestSuite(T).run() """ if check: if V.rank() != W.rank(): raise ValueError("modules must be of the same rank") if V.base_ring() is not ZZ: raise NotImplementedError("only currently implemented over ZZ") if V.inner_product_matrix() != V.inner_product_matrix().transpose( ): raise ValueError( "the cover must have a symmetric inner product") if gens is not None and V.span(gens) + W != V: raise ValueError("provided gens do not generate the quotient") FGP_Module_class.__init__(self, V, W, check=check) if gens is not None: self._gens_user = tuple(self(v) for v in gens) else: # this is taken care of in the .gens method # we do not want this at initialization self._gens_user = None # compute the modulus - this may be expensive if modulus is None or check: # The inner product of two elements `b(v1+W,v2+W)` # is defined `mod (V,W)` num = V.basis_matrix() * V.inner_product_matrix() * W.basis_matrix( ).T self._modulus = gcd(num.list()) if modulus is not None: if check and self._modulus / modulus not in self.base_ring(): raise ValueError("the modulus must divide (V, W)") self._modulus = modulus if modulus_qf is None or check: # The quadratic_product of an element `q(v+W)` is defined # `\mod 2(V,W) + ZZ\{ (w,w) | w in w\}` norm = gcd(self.W().gram_matrix().diagonal()) self._modulus_qf = gcd(norm, 2 * self._modulus) if modulus_qf is not None: if check and self._modulus_qf / modulus_qf not in self.base_ring(): raise ValueError("the modulus_qf must divide (V, W)") self._modulus_qf = modulus_qf
def is_irreducible_2cyl(w1,h1,t1,w2,h2,t2): r""" Test of irreducibility. """ print "testing", w1, h1, t1, w2, h2, t2 print " result heights gcd(%d,%d) = %d" %(h1,h2,gcd(h1,h2)), "and", gcd([w1, w2, h2*t1-h1*t2]) == 1 return gcd(h1,h2) == 1 and gcd([w1, w2, h2*t1-h1*t2]) == 1
def __classcall__(cls, V, W, gens=None, modulus=None, modulus_qf=None, check=True): r""" Return a :class:`TorsionQuadraticModule`. This method does the preprocessing for :meth:`sage.structure.CachedRepresentation`. TESTS:: sage: q = matrix([1/2]) sage: D1 = TorsionQuadraticForm(q) sage: D2 = TorsionQuadraticForm(q) sage: D1 is D2 True """ if check: if V.rank() != W.rank(): raise ValueError("modules must be of the same rank") if V.base_ring() is not ZZ: raise NotImplementedError("only currently implemented over ZZ") if V.inner_product_matrix() != V.inner_product_matrix().transpose( ): raise ValueError( "the cover must have a symmetric inner product") if gens is not None and V.span(gens) + W != V: raise ValueError("provided gens do not generate the quotient") # compute the modulus - this may be expensive if modulus is None or check: # The inner product of two elements `b(v1+W,v2+W)` # is defined `mod (V,W)` num = V.basis_matrix() * V.inner_product_matrix() * W.basis_matrix( ).T max_modulus = gcd(num.list()) if modulus is None: modulus = max_modulus elif check and max_modulus / modulus not in V.base_ring(): raise ValueError("the modulus must divide (V, W)") if modulus_qf is None or check: # The quadratic_product of an element `q(v+W)` is defined # `\mod 2(V,W) + ZZ\{ (w,w) | w in w\}` norm = gcd(W.gram_matrix().diagonal()) max_modulus_qf = gcd(norm, 2 * modulus) if modulus_qf is None: modulus_qf = max_modulus_qf elif check and max_modulus_qf / modulus_qf not in V.base_ring(): raise ValueError("the modulus_qf must divide (V, W)") return super(TorsionQuadraticModule, cls).__classcall__(cls, V, W, gens, modulus, modulus_qf)
def shift_cusp(gamma, t): r""" Returns gamma_2, delta such that B_t * gamma = gamma_2 * delta INPUT: - gamma - SL_2(Z) matrix - t - int, corresponds to the level shift B_t OUTPUT: - gamma, delta - gamma in SL_2(Z), delta triangular with B_t * gamma = gamma_2 * delta """ a, c = gamma[0][0], gamma[1][0] gamma_2 = complete_column(a * t / gcd(a * t, c), c / gcd(a * t, c)) delta = gamma_2**(-1) * Matrix([[t, 0], [0, 1]]) * gamma return gamma_2, delta
def is_primitive(self, M): r""" Return whether ``M`` is a primitive submodule of this lattice. A `\ZZ`-submodule ``M`` of a `\ZZ`-module ``L`` is called primitive if the quotient ``L/M`` is torsion free. INPUT: - ``M`` -- a submodule of this lattice EXAMPLES:: sage: U = IntegralLattice("U") sage: L1 = U.span([vector([1,1])]) sage: L2 = U.span([vector([1,-1])]) sage: U.is_primitive(L1) True sage: U.is_primitive(L2) True sage: U.is_primitive(L1+L2) False We can also compute the index:: sage: (L1+L2).index_in(U) 2 """ return (gcd((self / M).invariants()) == 0)
def is_primitive(self, M): r""" Return whether ``M`` is a primitive submodule of this lattice. A `\ZZ`-submodule ``M`` of a `\ZZ`-module ``L`` is called primitive if the quotient ``L/M`` is torsion free. INPUT: - ``M`` -- a submodule of this lattice EXAMPLES:: sage: U = IntegralLattice("U") sage: L1 = U.span([vector([1,1])]) sage: L2 = U.span([vector([1,-1])]) sage: U.is_primitive(L1) True sage: U.is_primitive(L2) True sage: U.is_primitive(L1+L2) False We can also compute the index:: sage: (L1+L2).index_in(U) 2 """ return (gcd((self/M).invariants()) == 0)
def is_primitive(self, M): r""" Return whether ``M`` is a primitive submodule of this lattice. A `\Z`-submodule ``M`` of a `\Z`-module ``L`` is called primitive if the quotient ``L/M`` is torsion free. INPUT: - ``M`` -- a submodule of this lattice EXAMPLES:: sage: from sage.modules.free_quadratic_module_integer_symmetric import IntegralLattice sage: U = IntegralLattice(Matrix(ZZ,2,2,[0,1,1,0])) sage: L1 = U.span([vector([1,1])]) sage: L2 = U.span([vector([1,-1])]) sage: U.is_primitive(L1) True sage: U.is_primitive(L2) True sage: U.is_primitive(L1+L2) False We can also compute the index:: sage: (L1+L2).index_in(U) 2 """ return (gcd((self/M).invariants()) == 0)
def trace_of_frobenius_mod_2(E): F = E.base_field() x = PolynomialRing(F, 'x').gen() f = x ** 3 + E.a4() * x + E.a6() q = F.order() if gcd(f, power_mod(x, q, f) - x).is_constant(): return 1 else: return 0
def taut_polynomial(tri, angle, cycles = [], alpha = True, mode = "taut"): # set up ZH = group_ring(tri, angle, cycles, alpha = alpha) P = ZH.polynomial_ring() ET = edges_to_triangles_matrix(tri, angle, cycles, ZH, P, mode = mode) # compute via minors minors = ET.minors(tri.countTetrahedra()) return normalise_poly(gcd(minors), ZH, P)
def complete_row(a, c, i=1): """ Completes a row a,c with gcd(a,c) = 1 to an SL_2(Z)-matrix. INPUT: - a - int, the top entry of the column. - c - int, the bottom entry of the column. - i - int. OUTPUT: - A matrix in SL_2(Z) with i-th column [a,c] """ if gcd(a, c) > 1: raise ValueError('Input needs to be coprime') return complete_column(a, c, i).T
def origami_H2_2cyl_iterator(n, reducible=False, output="coordinates"): r""" INPUT: - ``n`` - positive integer - the number of squares - ``reducible`` - bool (default: False) - if True returns also the reducible origamis. - ``output`` - either "coordinates" or "origami" or "standard origami" """ for n1,n2 in OrderedPartitions(n,k=2): for w1 in divisors(n1): h1 = n1//w1 for w2 in filter(lambda x: x < w1, divisors(n2)): h2 = n2//w2 if reducible or gcd(h1,h2) == 1: d = gcd(w1,w2) if d == 1: for t1 in xrange(w1): for t2 in xrange(w2): if output == "coordinates": yield w1,h1,t1,w2,h2,t2 elif output == "origami": yield origami_H2_2cyl(w1,h1,t1,w2,h2,t2) elif output == "standard_origami": yield origami_H2_2cyl(w1,h1,t1,w2,h2,t2).standard_form() else: for t1 in xrange(w1): for t2 in xrange(w2): if reducible or gcd(d,h2*t1-h1*t2) == 1: if output == "coordinates": yield w1,h1,t1,w2,h2,t2 elif output == "origami": yield origami_H2_2cyl(w1,h1,t1,w2,h2,t2) elif output == "standard_origami": yield origami_H2_2cyl(w1,h1,t1,w2,h2,t2).standard_form()
def __init__(self, V, W, gens=None, modulus=None, modulus_qf=None, check=True): r""" Initialize ``self``. TESTS:: sage: from sage.modules.torsion_quadratic_module import TorsionQuadraticModule sage: T = TorsionQuadraticModule(ZZ^3, 6*ZZ^3) sage: TestSuite(T).run() """ if check: if V.rank() != W.rank(): raise ValueError("modules must be of the same rank") if V.base_ring() is not ZZ: raise NotImplementedError("only currently implemented over ZZ") if V.inner_product_matrix() != V.inner_product_matrix().transpose(): raise ValueError("the cover must have a symmetric inner product") if gens is not None and V.span(gens) + W != V: raise ValueError("provided gens do not generate the quotient") FGP_Module_class.__init__(self, V, W, check=check) if gens is None: self._gens = FGP_Module_class.gens(self) else: self._gens = [self(v) for v in gens] if modulus is not None: if check: # The inner product of two elements `b(v1+W,v2+W)` is defined `mod (V,W)` num = gcd([x.inner_product(y) for x in V.gens() for y in W.gens()]) if num / modulus not in self.base_ring(): raise ValueError("the modulus must divide (V, W)") self._modulus = modulus else: # The inner product of two elements `b(v1+W,v2+W)` is defined `mod (V,W)` self._modulus = gcd([x.inner_product(y) for x in V.gens() for y in W.gens()]) if modulus_qf is not None: if check: # The quadratic_product of an element `q(v+W)` is defined # `\mod 2(V,W) + ZZ\{ (w,w) | w in w\}` norm = gcd(self.W().gram_matrix().diagonal()) num = gcd(norm, 2 * self._modulus) if num / modulus_qf not in self.base_ring(): raise ValueError("the modulus_qf must divide (V, W)") self._modulus_qf = modulus_qf else: # The quadratic_product of an element `q(v+W)` is defined # `\mod 2(V,W) + ZZ\{ (w,w) | w in w\}` norm = gcd(self.W().gram_matrix().diagonal()) self._modulus_qf = gcd(norm, 2 * self._modulus)
def __init__(self, base, names, degrees, max_degree, category=None, **kwargs): r""" Construct a commutative graded algebra with finite degree. TESTS:: sage: A.<x,y,z,t> = GradedCommutativeAlgebra(QQ, max_degree=6) sage: TestSuite(A).run() sage: A = GradedCommutativeAlgebra(QQ, ('x','y','z'), [2,3,4], max_degree=8) sage: TestSuite(A).run() sage: A = GradedCommutativeAlgebra(QQ, ('x','y','z','t'), [1,2,3,4], max_degree=10) sage: TestSuite(A).run() """ from sage.arith.misc import gcd if max_degree not in ZZ: raise TypeError('max_degree must be an integer') if max_degree < max(degrees): raise ValueError(f'max_degree must not deceed {max(degrees)}') self._names = names self.__ngens = len(self._names) self._degrees = degrees self._max_deg = max_degree self._weighted_vectors = WeightedIntegerVectors(degrees) self._mul_symbol = kwargs.pop('mul_symbol', '*') self._mul_latex_symbol = kwargs.pop('mul_latex_symbol', '') step = gcd(degrees) universe = DisjointUnionEnumeratedSets( self._weighted_vectors.subset(k) for k in range(0, max_degree, step)) base_cat = Algebras( base).WithBasis().Super().Supercommutative().FiniteDimensional() category = base_cat.or_subcategory(category, join=True) indices = ConditionSet(universe, self._valid_index) sorting_key = self._weighted_vectors.grading CombinatorialFreeModule.__init__(self, base, indices, sorting_key=sorting_key, category=category)
def complete_column(a, c, i=1): """ Completes a column a,c with gcd(a,c) = 1 to an SL_2(Z)-matrix. INPUT: - a - int, the top entry of the column. - c - int, the bottom entry of the column. - i - int. OUTPUT: - A matrix in SL_2(Z) with i-th column [a,c] """ if gcd(a, c) > 1: raise ValueError('Input needs to be coprime') _, d, b = xgcd(a, c) b = -b if i == 1: return Matrix([[a, b], [c, d]]) elif i == 2: return Matrix([[-b, a], [-d, c]])
def primitive_index(self): """ Return the primitive index. .. SEEALSO:: :meth:`is_primitive`, :meth:`primitive_data` EXAMPLES:: sage: from sage.modular.hypergeometric_motive import HypergeometricData as Hyp sage: Hyp(cyclotomic=([3],[4])).primitive_index() 1 sage: Hyp(gamma_list=[-2, 4, 6, -8]).primitive_index() 2 sage: Hyp(gamma_list=[-3, 6, 9, -12]).primitive_index() 3 """ return gcd(self.gamma_list())
def primitive_data(self): """ Return a primitive version. .. SEEALSO:: :meth:`is_primitive`, :meth:`primitive_index`, EXAMPLES:: sage: from sage.modular.hypergeometric_motive import HypergeometricData as Hyp sage: H = Hyp(cyclotomic=([3],[4])) sage: H2 = Hyp(gamma_list=[-2, 4, 6, -8]) sage: H2.primitive_data() == H True """ g = self.gamma_list() d = gcd(g) return HypergeometricData(gamma_list=[x / d for x in g])
def primitive_data(self): """ Return a primitive version. .. SEEALSO:: :meth:`is_primitive`, :meth:`primitive_index` EXAMPLES:: sage: from sage.modular.hypergeometric_motive import HypergeometricData as Hyp sage: H = Hyp(cyclotomic=([3],[4])) sage: H2 = Hyp(gamma_list=[-2, 4, 6, -8]) sage: H2.primitive_data() == H True """ g = self.gamma_list() d = gcd(g) return HypergeometricData(gamma_list=[x / d for x in g])
def _reduce_invariants(invariants, weights): """ Reduce a list of invariants of given weights. Reduces the given invariants over a field whose rings of integers is a gcd domain, in such a way that their greatest common weighted divisor is a unit in this ring. INPUT: - ``invariants`` -- The values of the invariants. - ``weights`` -- The respective weights of the invariants. OUTPUT: A list of invariants that is equivalent to the input and has no common weighted divisor. EXAMPLES:: sage: from sage.rings.invariants.reconstruction import _reduce_invariants sage: invariants = [6/5, 12, 16] sage: weights = [1, 2, 3] sage: _reduce_invariants(invariants, weights) [3, 75, 250] """ from sage.rings.integer_ring import ZZ factors = [dict(I.factor()) for I in invariants] scalar = ZZ(1) n = len(weights) from sage.arith.misc import gcd for prime in gcd(invariants).factor(): p = prime[0] for D in factors: if p not in D: D[p] = 0 scalar = scalar * p**min( [factors[i][p] // weights[i] for i in range(n)]) return [invariants[i] * scalar**-weights[i] for i in range(n)]
def taut_polynomial_via_tree(tri, angle, cycles = [], alpha = True, mode = "taut"): """ If cycles = [] then this is the taut polynomial. If cycles != [] then this is the zero-Fitting invariant of the module obtained by 'extension of scalars' - that is, form the epimorphism obtained by killing the given boundary cycles, apply it to the presentation matrix, and only then compute the gcd of the (correct minors). """ # set up ZH = group_ring(tri, angle, cycles, alpha = alpha) P = ZH.polynomial_ring() ET = edges_to_triangles_matrix(tri, angle, cycles, ZH, P, mode = mode) _, non_tree_faces, _ = spanning_dual_tree(tri) ET = ET.transpose() ET = Matrix([row for i, row in enumerate(ET) if i in non_tree_faces]).transpose() # compute via minors minors = ET.minors(tri.countTetrahedra()) return normalise_poly(gcd(minors), ZH, P)
def AL(N, S): r""" INPUT: - N - a positive integer - S - a maximal divisor of N or a set of primes OUPUT: - A matrix representing the partial Atkin-Lehner operator W_S """ try: S = prod([p**(N.valuation(p)) for p in S]) except TypeError: if gcd(S, N / S) > 1: S = S.prime_divisors() S = prod([p**(N.valuation(p)) for p in S]) _, w, z = xgcd(S, N / S) z = -z return Matrix([[S, 1], [N * z, S * w]])
def origami_H2_1cyl_iterator(n, reducible=False, output='coordinates'): r""" INPUT: - ``n`` - positive integer - the number of squares - ``reducible`` - bool (default: False) - if True returns also the reducible origamis. - ``output`` - either "coordinates" or "origami" or "standard origami" """ if reducible: hlist = divisors(n) else: hlist = [1] for h in hlist: for p in Partitions(Integer(n/h),length=3): for l in CyclicPermutationsOfPartition([p]): l1,l2,l3 = l[0] if l1 == l2 and l2 == l3: if reducible or l1 == 1: for t in xrange(l1): if output == "coordinates": yield l1,l2,l3,h,t elif output == "origami": yield origami_H2_1cyl(l1,l2,l3,h,t) elif output == "standard_origami": yield origami_H2_1cyl(l1,l2,l3,h,t).standard_form() elif reducible or gcd([l1,l2,l3]) == 1: for t in xrange(n): if output == "coordinates": yield l1,l2,l3,h,t elif output == "origami": yield origami_H2_1cyl(l1,l2,l3,h,t) elif output == "standard_origami": yield origami_H2_1cyl(l1,l2,l3,h,t).standard_form()
def is_irreducible_1cyl(l1,l2,l3,h,t): r""" Test of irreducibility. """ return h == 1 and gcd([l1,l2,l3]) == 1
def order_of_euler_class(delta, E): """ Given the coboundary operator delta and an Euler two-cocycle E, returns k if [E] is k--torsion. By convention, returns zero if [E] is non-torsion. Note that the trivial element is 1--torsion. """ delta = Matrix(delta) E = vector(E) # Note that E is a coboundary if there is a one-cocycle C solving # # E = C*delta # # We can find C (if it exists at all) using Smith normal form. D, U, V = delta.smith_form() assert D == U*delta*V # So we are trying to solve # # C*delta = C*U.inverse()*D*V.inverse() = E # # for a one-cochain C. Multiply by V to get # # C*delta*V = C*U.inverse()*D = E*V # # Now set # # B = C*U.inverse(), and so B*U = C # # and rewrite to get # # B*U*delta*V = B*D = E*V # # So define E' by: Ep = E*V # Finally we attempt to solve B * D = Ep. Note that D is # diagonal: so if we can solve all of the equations # B[i] * D[i][i] == Ep[i] # with B[i] integers, then [E] = 0 in cohomology. diag = diagonal(D) if any( (diag[i] == 0 and Ep[i] != 0) for i in range(len(Ep)) ): return 0 # All zeros are at the end in Smith normal form. Since we've # passed the above we can now remove them. first_zero = diag.index(0) diag = diag[:first_zero] Ep = Ep[:first_zero] # Since diag[i] is (now) never zero we can divide to get the # fractions Ep[i]/diag[i] and then find the scaling that makes # them simultaneously integral. denoms = [ diag[i] / gcd(Ep[i], diag[i]) for i in range(len(Ep)) ] return lcm(denoms)
def extract_a_solution(L, m, s, A, homogeneous=False, sol_type="any"): r""" Return a single solution from the kernel of the matrix A. INPUT: - ``L`` -- a free Lie algebra quotient - ``m`` -- the lowest degree of covectors in the matrix - ``s`` -- the highest degree of covectors in the matrix - ``A`` -- a matrix of the form given by :func:`abnormal_factor_system` - ``homogeneous`` -- a boolean; whether the solved polynomial is homogeneous - ``sol_type`` -- one of "any", "all", "nice" With sol_type="any", the output is the solution from the first nonpivot column of the echelon form of A. With sol_type="all", the output is the basis of the kernel given by the nonpivot columns of the echelon form of A. With sol_type="nice", the output is a linear combination that attempts to decrease the number of nonzero coefficients and their magnitudes. OUTPUT: A dictionary {X: c} describing a covector. Each `X` is an element of the Lie algebra `L` and each `c` is the coefficient of the covector for the dual element of `X`. """ # extract names of the covector variables if homogeneous: vecs = list(L.graded_basis(s)) else: vecs = [X for k in range(m, s + 1) for X in L.graded_basis(k)] # check that the dimensions are correct assert A.subdivisions()[1][0] == len(vecs) covecdim = len(vecs) # compute the echelon form of A EA = A.echelon_form() def covec_to_dict(v): covec = {} for k, vk in enumerate(v): if vk: covec[vecs[k]] = vk return covec nonpivot = covecdim covecs = [] while True: # find next non-pivot column while nonpivot in A.pivots(): nonpivot += 1 if nonpivot >= A.dimensions()[1]: if sol_type != "any": break # no solution found, something is wrong raise ValueError( "something went wrong: no nonpivot column in the multiplier variables" ) if sol_type == "any": verbose("computing a solution") else: verbose("computing solution #%d" % (len(covecs) + 1)) # the non-pivot columns of the echelon form define the valid solutions # rescale the column so that the covector has simplified coefficients cv = EA.column(nonpivot)[:covecdim] cv = cv / gcd(cv) if sol_type == "any": return covec_to_dict(cv) covecs.append(cv) nonpivot += 1 if sol_type == "all": return [covec_to_dict(v) for v in covecs] if A.base_ring() == QQ: # over rationals, start with the covector with smallest coefficients covecs = sorted(covecs, key=lambda v: min(abs(vk) for vk in v)) covec = covecs[0] covecs = covecs[1:] while covecs: other = covecs[0] covecs = covecs[1:] # eliminate the coefficient with the largest gcd gcdvec = [gcd(a, b) for a, b in zip(covec, other)] i = gcdvec.index(max(gcdvec)) covec = covec[i] * other - other[i] * covec covec = covec / gcd(covec) # make leading coefficient positive i = 0 while not covec[i]: i += 1 if covec[i] < 0: covec = -covec return covec_to_dict(covec)
def test_norm_gcd(): for K, q, p, result in test_cases_true + test_cases_false: qq = (q * K).factor()[0][0] is_coprime = gcd(qq.absolute_norm(), p) == 1 assert is_coprime == result
def binary_quintic_coefficients_from_invariants(invariants, K=None, invariant_choice='default', scaling='none'): r""" Reconstruct a binary quintic from the values of its (Clebsch) invariants. INPUT: - ``invariants`` -- A list or tuple of values of the three or four invariants. The default option requires the Clebsch invariants `A`, `B`, `C` and `R` of the binary quintic. - ``K`` -- The field over which the quintic is defined. - ``invariant_choice`` -- The type of invariants provided. The accepted options are ``'clebsch'`` and ``'default'``, which are the same. No other options are implemented. - ``scaling`` -- How the coefficients should be scaled. The accepted values are ``'none'`` for no scaling, ``'normalized'`` to scale in such a way that the resulting coefficients are independent of the scaling of the input invariants and ``'coprime'`` which scales the input invariants by dividing them by their gcd. OUTPUT: A set of coefficients of a binary quintic, whose invariants are equal to the given ``invariants`` up to a scaling. EXAMPLES: First we check the general case, where the invariant `M` is non-zero:: sage: R.<x0, x1> = QQ[] sage: p = 3*x1^5 + 6*x1^4*x0 + 3*x1^3*x0^2 + 4*x1^2*x0^3 - 5*x1*x0^4 + 4*x0^5 sage: quintic = invariant_theory.binary_quintic(p, x0, x1) sage: invs = quintic.clebsch_invariants(as_tuple=True) sage: reconstructed = invariant_theory.binary_form_from_invariants(5, invs, variables=quintic.variables()) # indirect doctest sage: reconstructed Binary quintic with coefficients (9592267437341790539005557/244140625000000, 2149296928207625556323004064707/610351562500000000, 11149651890347700974453304786783/76293945312500000, 122650775751894638395648891202734239/47683715820312500000, 323996630945706528474286334593218447/11920928955078125000, 1504506503644608395841632538558481466127/14901161193847656250000) We can see that the invariants of the reconstructed form match the ones of the original form by scaling the invariants `B` and `C`:: sage: scale = invs[0]/reconstructed.A_invariant() sage: invs[1] == reconstructed.B_invariant()*scale^2 True sage: invs[2] == reconstructed.C_invariant()*scale^3 True If we compare the form obtained by this reconstruction to the one found by letting the covariants `\alpha` and `\beta` be the coordinates of the form, we find the forms are the same up to a power of the determinant of `\alpha` and `\beta`:: sage: alpha = quintic.alpha_covariant() sage: beta = quintic.beta_covariant() sage: g = matrix([[alpha(x0=1,x1=0),alpha(x0=0,x1=1)],[beta(x0=1,x1=0),beta(x0=0,x1=1)]])^-1 sage: transformed = tuple([g.determinant()^-5*x for x in quintic.transformed(g).coeffs()]) sage: transformed == reconstructed.coeffs() True This can also be seen by computing the `\alpha` covariant of the obtained form:: sage: reconstructed.alpha_covariant().coefficient(x1) 0 sage: reconstructed.alpha_covariant().coefficient(x0) != 0 True If the invariant `M` vanishes, then the coefficients are computed in a different way:: sage: [A,B,C] = [3,1,2] sage: M = 2*A*B - 3*C sage: M 0 sage: from sage.rings.invariants.reconstruction import binary_quintic_coefficients_from_invariants sage: reconstructed = binary_quintic_coefficients_from_invariants([A,B,C]) sage: reconstructed (-66741943359375/2097152, -125141143798828125/134217728, 0, 52793920040130615234375/34359738368, 19797720015048980712890625/1099511627776, -4454487003386020660400390625/17592186044416) sage: newform = sum([ reconstructed[i]*x0^i*x1^(5-i) for i in range(6) ]) sage: newquintic = invariant_theory.binary_quintic(newform, x0, x1) sage: scale = 3/newquintic.A_invariant() sage: [3, newquintic.B_invariant()*scale^2, newquintic.C_invariant()*scale^3] [3, 1, 2] Several special cases:: sage: quintic = invariant_theory.binary_quintic(x0^5 - x1^5, x0, x1) sage: invs = quintic.clebsch_invariants(as_tuple=True) sage: binary_quintic_coefficients_from_invariants(invs) (1, 0, 0, 0, 0, 1) sage: quintic = invariant_theory.binary_quintic(x0*x1*(x0^3-x1^3), x0, x1) sage: invs = quintic.clebsch_invariants(as_tuple=True) sage: binary_quintic_coefficients_from_invariants(invs) (0, 1, 0, 0, 1, 0) sage: quintic = invariant_theory.binary_quintic(x0^5 + 10*x0^3*x1^2 - 15*x0*x1^4, x0, x1) sage: invs = quintic.clebsch_invariants(as_tuple=True) sage: binary_quintic_coefficients_from_invariants(invs) (1, 0, 10, 0, -15, 0) sage: quintic = invariant_theory.binary_quintic(x0^2*(x0^3 + x1^3), x0, x1) sage: invs = quintic.clebsch_invariants(as_tuple=True) sage: binary_quintic_coefficients_from_invariants(invs) (1, 0, 0, 1, 0, 0) sage: quintic = invariant_theory.binary_quintic(x0*(x0^4 + x1^4), x0, x1) sage: invs = quintic.clebsch_invariants(as_tuple=True) sage: binary_quintic_coefficients_from_invariants(invs) (1, 0, 0, 0, 1, 0) For fields of characteristic 2, 3 or 5, there is no reconstruction implemented. This is part of :trac:`26786`.:: sage: binary_quintic_coefficients_from_invariants([3,1,2], K=GF(5)) Traceback (most recent call last): ... NotImplementedError: no reconstruction of binary quintics implemented for fields of characteristic 2, 3 or 5 TESTS:: sage: from sage.rings.invariants.reconstruction import binary_quintic_coefficients_from_invariants sage: binary_quintic_coefficients_from_invariants([1,2,3], scaling='unknown') Traceback (most recent call last): ... ValueError: unknown scaling option 'unknown' """ if invariant_choice not in ['default', 'clebsch']: raise ValueError('unknown choice of invariants {} for a binary quintic' .format(invariant_choice)) if scaling not in ['none', 'normalized', 'coprime']: raise ValueError("unknown scaling option '%s'" % scaling) if scaling == 'coprime': if len(invariants) == 3: invariants = _reduce_invariants(invariants, [1,2,3]) elif len(invariants) == 4: invariants = _reduce_invariants(invariants, [2,4,6,9]) A, B, C = invariants[0:3] if K is None: from sage.rings.fraction_field import FractionField K = FractionField(A.parent()) if K.characteristic() in [2, 3, 5]: raise NotImplementedError('no reconstruction of binary quintics ' 'implemented for fields of characteristic 2, 3 or 5') M = 2*A*B - 3*C N = K(2)**-1 * (A*C-B**2) R2 = -K(2)**-1 * (A*N**2-2*B*M*N+C*M**2) scale = [1,1,1,1,1,1] from sage.functions.all import binomial, sqrt if len(invariants) == 3: if R2.is_square(): R = sqrt(R2) else: # if R2 is not a square, we scale the invariants in a suitable way # so that the 'new' R2 is a square [A, B, C] = [R2*A, R2**2*B, R2**3*C] [M, N] = [R2**3*M, R2**4*N] R = R2**5 elif len(invariants) == 4: if invariants[3]**2 != R2: raise ValueError('provided invariants do not satisfy the syzygy ' 'for Clebsch invariants of a binary quintic') R = invariants[3] else: raise ValueError('incorrect number of invariants provided, this ' 'method requires 3 or 4 invariants') if M == 0: if N == 0: if A == 0: raise ValueError('no unique reconstruction possible for ' 'quintics with a treefold linear factor') else: if B == 0: return (1,0,0,0,0,1) else: return (0,1,0,0,1,0) else: # case corresponding to using alpha and gamma as coordinates if A == 0: return (1,0,0,0,1,0) else: if scaling == 'normalized': # scaling z by (R/A**3) scale = [ (-N)**-5*A**6*(R/A**3)**i for i in range(6) ] D = -N Delta = C a = [0] a.append((2*K(3)**-1*A**2-B)*N*B*K(2)**-1 - N**2*K(2)**-1) B0 = 2*K(3)**-1*A*R B1 = A*N*B*K(3)**-1 C0 = 2*K(3)**-1*R C1 = B*N else: # case corresponding to using alpha and beta as coordinates if R == 0: if A == 0: return (1,0,10,0,-15,0) elif scaling == 'normalized': # scaling x by A and z by sqrt(A) scale = [ (-M)**(-5)*sqrt(A)**(12+i) for i in range(6) ] else: if A == 0: if B == 0: return (1,0,0,1,0,0) elif scaling == 'normalized': # scaling y by R/B**2 scale = [ (-M)**(-3)*(R/B**2)**i for i in range(6) ] elif scaling == 'normalized': # scaling y by R/A**4 scale = [ (-M)**(-3)*(R/A**4)**i for i in range(6) ] D = -M Delta = A a = [0] a.append((2*K(3)**-1*A**2-B)*(N*A-M*B)*K(2)**-1 \ - M*(N*K(2)**-1-M*A*K(3)**-1)) B0 = R B1 = K(2)**-1*(N*A-M*B) C0 = 0 C1 = -M a[0] = (2*K(3)**-1*A**2-B)*R a.append(-D*B0 - K(2)**-1*Delta*a[0]) a.append(-D*B1 - K(2)**-1*Delta*a[1]) a.append(D**2*C0 + D*Delta*B0 + K(4)**-1*Delta**2*a[0]) a.append(D**2*C1 + D*Delta*B1 + K(4)**-1*Delta**2*a[1]) coeffs = tuple([K((-1)**i*binomial(5,i)*scale[5-i]*a[i]) for i in range(6)]) if scaling == 'coprime': from sage.arith.misc import gcd return tuple([coeffs[i]/gcd(coeffs) for i in range(6)]) else: return coeffs
def basis(self, reduce=True): r""" Produce a basis for the free abelian group of eta-products of level N (under multiplication), attempting to find basis vectors of the smallest possible degree. INPUT: - ``reduce`` - a boolean (default True) indicating whether or not to apply LLL-reduction to the calculated basis EXAMPLES:: sage: EtaGroup(5).basis() [Eta product of level 5 : (eta_1)^6 (eta_5)^-6] sage: EtaGroup(12).basis() [Eta product of level 12 : (eta_1)^-3 (eta_2)^2 (eta_3)^1 (eta_4)^-1 (eta_6)^-2 (eta_12)^3, Eta product of level 12 : (eta_1)^-4 (eta_2)^2 (eta_3)^4 (eta_6)^-2, Eta product of level 12 : (eta_1)^6 (eta_2)^-9 (eta_3)^-2 (eta_4)^3 (eta_6)^3 (eta_12)^-1, Eta product of level 12 : (eta_1)^-1 (eta_2)^3 (eta_3)^3 (eta_4)^-2 (eta_6)^-9 (eta_12)^6, Eta product of level 12 : (eta_1)^3 (eta_3)^-1 (eta_4)^-3 (eta_12)^1] sage: EtaGroup(12).basis(reduce=False) # much bigger coefficients [Eta product of level 12 : (eta_1)^384 (eta_2)^-576 (eta_3)^-696 (eta_4)^216 (eta_6)^576 (eta_12)^96, Eta product of level 12 : (eta_2)^24 (eta_12)^-24, Eta product of level 12 : (eta_1)^-40 (eta_2)^116 (eta_3)^96 (eta_4)^-30 (eta_6)^-80 (eta_12)^-62, Eta product of level 12 : (eta_1)^-4 (eta_2)^-33 (eta_3)^-4 (eta_4)^1 (eta_6)^3 (eta_12)^37, Eta product of level 12 : (eta_1)^15 (eta_2)^-24 (eta_3)^-29 (eta_4)^9 (eta_6)^24 (eta_12)^5] ALGORITHM: An eta product of level `N` is uniquely determined by the integers `r_d` for `d | N` with `d < N`, since `\sum_{d | N} r_d = 0`. The valid `r_d` are those that satisfy two congruences modulo 24, and one congruence modulo 2 for every prime divisor of N. We beef up the congruences modulo 2 to congruences modulo 24 by multiplying by 12. To calculate the kernel of the ensuing map `\ZZ^m \to (\ZZ/24\ZZ)^n` we lift it arbitrarily to an integer matrix and calculate its Smith normal form. This gives a basis for the lattice. This lattice typically contains "large" elements, so by default we pass it to the reduce_basis() function which performs LLL-reduction to give a more manageable basis. """ N = self.level() divs = divisors(N)[:-1] s = len(divs) primedivs = prime_divisors(N) rows = [] for di in divs: # generate a row of relation matrix row = [Mod(di, 24) - Mod(N, 24), Mod(N // di, 24) - Mod(1, 24)] for p in primedivs: row.append(Mod(12 * (N // di).valuation(p), 24)) rows.append(row) M = matrix(IntegerModRing(24), rows) Mlift = M.change_ring(ZZ) # now we compute elementary factors of Mlift S, U, V = Mlift.smith_form() good_vects = [] for i in range(U.nrows()): vect = U.row(i) nf = (i < S.ncols() and S[i, i]) or 0 # ? good_vects.append((vect * 24 / gcd(nf, 24)).list()) for v in good_vects: v.append(-sum([r for r in v])) dicts = [] for v in good_vects: dicts.append({}) for i in range(s): dicts[-1][divs[i]] = v[i] dicts[-1][N] = v[-1] if reduce: return self.reduce_basis([self(d) for d in dicts]) else: return [self(d) for d in dicts]
def prime_norm_representative(I, O, D, ell): """ Given an order O and a left O-ideal I return another left O-ideal J in the same class, but with prime norm. This corresponds to Step 1 in the notes. So given an ideal I it returns an ideal in the same class but with reduced norm N where N != ell is a large prime coprime to both D and p, and ell is a quadratic nonresidue module N. Args: I: A left O-ideal. O: An order in a quaternion algebra. D: An integer. ell: A prime. Returns: A pair (J, gamma) where J = I * gamma is a left O-ideal in the same class with prime norm N. N will be coprime to both D and p, and ell will be a nonquadratic residue module N. """ # TODO: Change so O is not an argument. if not is_minkowski_basis(I.basis()): print("Warning: The ideal I does not have a minkowski basis" " precomputed and Sage can not do it for you.") nrd_I = I.norm() B = I.quaternion_algebra() p = B.discriminant() alpha = B(0) normalized_norm = Integer(alpha.reduced_norm() / nrd_I) # Choose random elements in I until one is found with norm N*nrd(I) where N # is prime. m_power = 3 m = Integer(2)**m_power # TODO: Change this to a proper bound. count = 0 while (not is_prime(normalized_norm) or normalized_norm.divides(D) or normalized_norm == ell or normalized_norm == p or mod(ell, normalized_norm).is_square()): # Make a new random element. alpha = random_combination(I.basis(), bound=m) normalized_norm = Integer(alpha.reduced_norm() / nrd_I) # Increase the box we search in if we've been trying for too long. Note # this was just a random heuristic I came up with, it's not in the # paper. count += 1 if count > 4 * m_power: m_power += 1 m = Integer(2)**m_power count = 0 # We now have an element alpha with norm N*nrd(I) where N is prime. The # ideal J = I*gamma has prime norm where gamma = conjugate(alpha) / nrd(I). gamma = alpha.conjugate() / nrd_I J = I.scale(gamma) assert is_prime(Integer(J.norm())) assert not mod(ell, Integer(J.norm())).is_square() assert gcd(Integer(J.norm()), D) == 1 return J, gamma
def __init__(self, V, W, gens=None, modulus=None, modulus_qf=None, check=True): r""" Initialize ``self``. TESTS:: sage: from sage.modules.torsion_quadratic_module import TorsionQuadraticModule sage: T = TorsionQuadraticModule(ZZ^3, 6*ZZ^3) sage: TestSuite(T).run() """ if check: if V.rank() != W.rank(): raise ValueError("modules must be of the same rank") if V.base_ring() is not ZZ: raise NotImplementedError("only currently implemented over ZZ") if V.inner_product_matrix() != V.inner_product_matrix().transpose( ): raise ValueError( "the cover must have a symmetric inner product") if gens is not None and V.span(gens) + W != V: raise ValueError("provided gens do not generate the quotient") FGP_Module_class.__init__(self, V, W, check=check) if gens is None: self._gens = FGP_Module_class.gens(self) else: self._gens = [self(v) for v in gens] if modulus is not None: if check: # The inner product of two elements `b(v1+W,v2+W)` is defined `mod (V,W)` num = gcd( [x.inner_product(y) for x in V.gens() for y in W.gens()]) if num / modulus not in self.base_ring(): raise ValueError("the modulus must divide (V, W)") self._modulus = modulus else: # The inner product of two elements `b(v1+W,v2+W)` is defined `mod (V,W)` self._modulus = gcd( [x.inner_product(y) for x in V.gens() for y in W.gens()]) if modulus_qf is not None: if check: # The quadratic_product of an element `q(v+W)` is defined # `\mod 2(V,W) + ZZ\{ (w,w) | w in w\}` norm = gcd(self.W().gram_matrix().diagonal()) num = gcd(norm, 2 * self._modulus) if num / modulus_qf not in self.base_ring(): raise ValueError("the modulus_qf must divide (V, W)") self._modulus_qf = modulus_qf else: # The quadratic_product of an element `q(v+W)` is defined # `\mod 2(V,W) + ZZ\{ (w,w) | w in w\}` norm = gcd(self.W().gram_matrix().diagonal()) self._modulus_qf = gcd(norm, 2 * self._modulus)