def _factor_univariate_polynomial(self, f): r""" TESTS:: sage: sys.path.append(os.getcwd()); from mac_lane import * # optional: standalone sage: K = GF(2) sage: R.<x> = K[] sage: L.<x> = K.extension(x^2 + x + 1) sage: R.<y> = L[] sage: L.<y> = L.extension(y^2 + y + x) sage: R.<T> = L[] sage: (T^2 + T + x).factor() # indirect doctest (T + y) * (T + y + 1) """ from sage.structure.factorization import Factorization if f.is_zero(): raise ValueError("factorization of 0 is not defined") elif f.degree() <= 1: return Factorization([(f,1)]) from_absolute_field, to_absolute_field, absolute_field = self.absolute_extension() F = f.map_coefficients(lambda c:to_absolute_field(c), absolute_field).factor() return Factorization([(g.map_coefficients(lambda c:from_absolute_field(c), self), e) for g,e in F], unit=from_absolute_field(F.unit()))
def factor(self): """ Return the prime factorization of the graph. EXAMPLES:: sage: KG = KontsevichGraph([(1, 'F', 'L'), (1, 'G', 'R'), \ ....: (2, 'F', 'L'), (2, 'G', 'R')], ground_vertices=('F','G')) sage: KG.factor() (Kontsevich graph with 1 vertices on 2 ground vertices)^2 ALGORITHM:: Delete ground vertices; the remaining connected components correspond to the prime factors. """ floorless = self.copy(immutable=False) floorless.delete_vertices(self.ground_vertices()) factors = [] for C in floorless.connected_components(): P = self.subgraph(vertices=C + list(self.ground_vertices())) P_KG = KontsevichGraph(P, ground_vertices=self.ground_vertices()) P_KG.normalize_vertex_labels() factors.append(P_KG) return Factorization([(f, 1) for f in factors])
def factor(self, absprec=None): if self == 0: raise ValueError, "Factorization of 0 not defined" if absprec is None: absprec = min([x.precision_absolute() for x in self.list()]) else: absprec = integer.Integer(absprec) if absprec <= 0: raise ValueError, "absprec must be positive" G = self._pari_().factorpadic(self.base_ring().prime(), absprec) pols = G[0] exps = G[1] F = [] R = self.parent() for i in xrange(len(pols)): f = R(pols[i], absprec=absprec) e = int(exps[i]) F.append((f, e)) if R.base_ring().is_field(): # When the base ring is a field we normalize # the irreducible factors so they have leading # coefficient 1. for i in range(len(F)): cur = F[i][0].leading_coefficient() if cur != 1: F[i] = (F[i][0].monic(), F[i][1]) return Factorization(F, self.leading_coefficient()) else: # When the base ring is not a field, we normalize # the irreducible factors so that the leading term # is a power of p. We also ensure that the gcd of # the coefficients of each term is 1. c = self.leading_coefficient().valuation() u = self.base_ring()(1) for i in range(len(F)): upart = F[i][0].leading_coefficient().unit_part() lval = F[i][0].leading_coefficient().valuation() if upart != 1: F[i] = (F[i][0] // upart, F[i][1]) u *= upart**F[i][1] c -= lval if c != 0: F.append((self.parent()(self.base_ring().prime_pow(c)), 1)) return Factorization(F, u)
def slope_factorization(self): """ Return a factorization of ``self`` into a product of factors corresponding to each slope in the Newton polygon. EXAMPLES:: sage: K = Qp(5) sage: R.<x> = K[] sage: K = Qp(5) sage: R.<t> = K[] sage: f = 5 + 3*t + t^4 + 25*t^10 sage: f.newton_slopes() [1, 0, 0, 0, -1/3, -1/3, -1/3, -1/3, -1/3, -1/3] sage: F = f.slope_factorization() sage: F.prod() == f True sage: for (f,_) in F: ....: print(f.newton_slopes()) [-1/3, -1/3, -1/3, -1/3, -1/3, -1/3] [0, 0, 0] [1] TESTS:: sage: S.<x> = PowerSeriesRing(GF(5)) sage: R.<y> = S[] sage: p = x^2+y+x*y^2 sage: p.slope_factorization() (x) * ((x + O(x^22))*y + 1 + 4*x^3 + 4*x^6 + 3*x^9 + x^15 + 3*x^18 + O(x^21)) * ((x^-1 + O(x^20))*y + x + x^4 + 2*x^7 + 4*x^13 + 2*x^16 + 2*x^19 + O(x^22)) AUTHOR: - Xavier Caruso (2013-03-20) """ vertices = self.newton_polygon().vertices(copy=False) unit = self.leading_coefficient() P = ~unit * self deg_first = vertices[0][0] factors = [ ] if deg_first > 0: P >>= deg_first factors.append((self._parent.gen(), deg_first)) if len(vertices) > 2: for i in range(1, len(vertices)-1): deg = vertices[i][0] div = P._factor_of_degree(deg-deg_first) factors.append((div,1)) P,_ = P.quo_rem(div) deg_first = deg if len(vertices) > 1: factors.append((P, 1)) factors.reverse() return Factorization(factors, sort=False, unit=unit)
def _factor_univariate_polynomial(self, f, proof=True): """ Factor the univariate polynomial f over self. EXAMPLES:: We do a factorization over the function field over the rationals:: sage: R.<t> = FunctionField(QQ) sage: S.<X> = R[] sage: f = (1/t)*(X^4 - 1/t^2)*(X^3 - t^3) sage: f.factor() # indirect doctest (1/t) * (X - t) * (X^2 - 1/t) * (X^2 + 1/t) * (X^2 + t*X + t^2) sage: f.factor().prod() == f True You must pass in proof=False over finite fields, due to Singular's factoring algorithm being incomplete:: sage: R.<t> = FunctionField(GF(7)) sage: S.<X> = R[] sage: f = (1/t)*(X^4 - 1/t^2)*(X^3 - t^3) sage: f.factor() Traceback (most recent call last): ... NotImplementedError: proof = True factorization not implemented. Call factor with proof=False. sage: f.factor(proof=False) (1/t) * (X + 3*t) * (X + 5*t) * (X + 6*t) * (X^2 + 1/t) * (X^2 + 6/t) sage: f.factor(proof=False).prod() == f True Factoring over a function field over a non-prime finite field:: sage: k.<a> = GF(9) sage: R.<t> = FunctionField(k) sage: S.<X> = R[] sage: f = (1/t)*(X^3 - a*t^3) sage: f.factor(proof=False) (1/t) * (X + (a + 2)*t)^3 sage: f.factor(proof=False).prod() == f True """ F, d = self._to_bivariate_polynomial(f) fac = F.factor(proof=proof) x = f.parent().gen() t = f.parent().base_ring().gen() phi = F.parent().hom([x, t]) v = [(phi(P), e) for P, e in fac] unit = phi(fac.unit()) / d w = [] for a, e in v: c = a.leading_coefficient() a = a / c unit *= (c**e) w.append((a, e)) from sage.structure.factorization import Factorization return Factorization(w, unit=unit)
def _pari_padic_factorization_to_sage(G, R, leading_coeff): """ Given a PARI factorization matrix `G` representing a factorization of some polynomial in the `p`-adic polynomial ring `R`, return the corresponding Sage factorization. All factors in `G` are assumed to have content 1 (this is how PARI returns its factorizations). INPUT: - ``G`` -- PARI factorization matrix, returned by ``factorpadic``. - ``R`` -- polynomial ring to be used as parent ring of the factors - ``leading_coeff`` -- leading coefficient of the polynomial which was factored. This can belong to any ring which can be coerced into ``R.base_ring()``. OUTPUT: - A Sage :class:`Factorization`. """ B = R.base_ring() p = B.prime() leading_coeff = B(leading_coeff) pols = [R(f, absprec=f.padicprec(p)) for f in G[0]] exps = [int(e) for e in G[1]] # Determine unit part (which is discarded by PARI) if B.is_field(): # When the base ring is a field, we normalize # the irreducible factors so they have leading # coefficient 1. for i in xrange(len(pols)): lc = pols[i].leading_coefficient() lc = lc.lift_to_precision() # Ensure we don't lose precision pols[i] *= ~lc else: # When the base ring is not a field, we normalize # the irreducible factors so that the leading term # is a power of p. c, leading_coeff = leading_coeff.val_unit() for i in xrange(len(pols)): v, upart = pols[i].leading_coefficient().val_unit() upart = upart.lift_to_precision() # Ensure we don't lose precision pols[i] *= ~upart c -= exps[i] * v if c: # Add factor p^c pols.append(R(p)) exps.append(c) from sage.structure.factorization import Factorization return Factorization(zip(pols, exps), leading_coeff)
def _factor_univariate_polynomial(self, f, proof=True): """ Factor the univariate polynomial ``f`` over self. EXAMPLES:: We do a factorization over the function field over the rationals:: sage: R.<t> = FunctionField(QQ) sage: S.<X> = R[] sage: f = (1/t)*(X^4 - 1/t^2)*(X^3 - t^3) sage: f.factor() # indirect doctest (1/t) * (X - t) * (X^2 - 1/t) * (X^2 + 1/t) * (X^2 + t*X + t^2) sage: f.factor().prod() == f True We do a factorization over a finite prime field:: sage: R.<t> = FunctionField(GF(7)) sage: S.<X> = R[] sage: f = (1/t)*(X^4 - 1/t^2)*(X^3 - t^3) sage: f.factor() (1/t) * (X + 3*t) * (X + 5*t) * (X + 6*t) * (X^2 + 1/t) * (X^2 + 6/t) sage: f.factor().prod() == f True Factoring over a function field over a non-prime finite field:: sage: k.<a> = GF(9) sage: R.<t> = FunctionField(k) sage: S.<X> = R[] sage: f = (1/t)*(X^3 - a*t^3) sage: f.factor() (1/t) * (X + (a + 2)*t)^3 sage: f.factor().prod() == f True """ F, d = self._to_bivariate_polynomial(f) fac = F.factor() x = f.parent().gen() t = f.parent().base_ring().gen() phi = F.parent().hom([x, t]) v = [(phi(P), e) for P, e in fac] unit = phi(fac.unit()) / d w = [] for a, e in v: c = a.leading_coefficient() a = a / c unit *= (c**e) w.append((a, e)) from sage.structure.factorization import Factorization return Factorization(w, unit=unit)
def _acted_upon_(self, scalar, self_on_left=False): """ Return the action of a scalar on ``self``. EXAMPLES:: sage: R.<x,y> = FreeAlgebra(QQ,2) sage: f = Factorization([(x,2),(y,3)]); f x^2 * y^3 sage: x * f x^3 * y^3 sage: f * x x^2 * y^3 * x """ from sage.structure.factorization import Factorization # FIXME: Make factorization work properly in the coercion framework # Keep factorization since we want to "coerce" into a factorization if isinstance(scalar, Factorization): if self_on_left: return Factorization([(self, 1)]) * scalar return scalar * Factorization([(self, 1)]) return super(FreeAlgebraElement, self)._acted_upon_(scalar, self_on_left)
def _factor_univariate_polynomial(self, f): """ Factor the univariate polynomial ``f``. INPUT: - ``f`` -- a univariate polynomial defined over the complex numbers OUTPUT: - A factorization of ``f`` over the complex numbers into a unit and monic irreducible factors .. NOTE:: This is a helper method for :meth:`sage.rings.polynomial.polynomial_element.Polynomial.factor`. This method calls PARI to compute the factorization. TESTS:: sage: k = ComplexField(100) sage: R.<x> = k[] sage: k._factor_univariate_polynomial( x ) x sage: k._factor_univariate_polynomial( 2*x ) (2.0000000000000000000000000000) * x sage: k._factor_univariate_polynomial( x^2 ) x^2 sage: k._factor_univariate_polynomial( x^2 + 3 ) (x - 1.7320508075688772935274463415*I) * (x + 1.7320508075688772935274463415*I) sage: k._factor_univariate_polynomial( x^2 + 1 ) (x - I) * (x + I) sage: k._factor_univariate_polynomial( k(I) * (x^2 + 1) ) (1.0000000000000000000000000000*I) * (x - I) * (x + I) """ R = f.parent() # if the polynomial does not have complex coefficients, PARI will # factor it over the reals. To make sure it has complex coefficients we # multiply with I. I = R.base_ring().gen() g = f * I if f.leading_coefficient() != I else f F = list(g._pari_with_name().factor()) from sage.structure.factorization import Factorization return Factorization([(R(g).monic(), e) for g, e in zip(*F)], f.leading_coefficient())
def factor(self): # This will eventually be improved. if self == 0: raise ValueError, "Factorization of the zero polynomial not defined" from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.padics.factory import ZpCA base = self.base_ring() #print self.list() m = min([x.precision_absolute() for x in self.list()]) #print m R = ZpCA(base.prime(), prec = m) S = PolynomialRing(R, self.parent().variable_name()) F = S(self).factor() return Factorization([(self.parent()(a), b) for (a, b) in F], base(F.unit()))
def slope_factorization(self): """ Return a factorization of ``self`` into a product of factors corresponding to each slope in the Newton polygon. EXAMPLES:: sage: K = Qp(5) sage: R.<x> = K[] sage: K = Qp(5) sage: R.<t> = K[] sage: f = 5 + 3*t + t^4 + 25*t^10 sage: f.newton_slopes() [1, 0, 0, 0, -1/3, -1/3, -1/3, -1/3, -1/3, -1/3] sage: F = f.slope_factorization() sage: F.prod() == f True sage: for (f,_) in F: ....: print f.newton_slopes() [-1/3, -1/3, -1/3, -1/3, -1/3, -1/3] [0, 0, 0] [1] AUTHOR: - Xavier Caruso (2013-03-20) """ vertices = self.newton_polygon().vertices(copy=False) unit = self.leading_coefficient() P = ~unit * self deg_first = vertices[0][0] factors = [ ] if deg_first > 0: P >>= deg_first factors.append((self._parent.gen(), deg_first)) if len(vertices) > 2: for i in range(1, len(vertices)-1): deg = vertices[i][0] div = P._factor_of_degree(deg-deg_first) factors.append((div,1)) P,_ = P.quo_rem(div) deg_first = deg if len(vertices) > 1: factors.append((P, 1)) factors.reverse() return Factorization(factors, sort=False, unit=unit)
def _factor_univariate_polynomial(self, f): """ Factor the univariate polynomial ``f``. INPUT: - ``f`` -- a univariate polynomial defined over the rationals OUTPUT: - A factorization of ``f`` over the rationals into a unit and monic irreducible factors .. NOTE:: This is a helper method for :meth:`sage.rings.polynomial.polynomial_element.Polynomial.factor`. This method calls PARI to compute the factorization. TESTS:: sage: R.<x> = QQ[] sage: QQ._factor_univariate_polynomial( x ) x sage: QQ._factor_univariate_polynomial( 2*x ) (2) * x sage: QQ._factor_univariate_polynomial( (x^2 - 1/4)^4 ) (x - 1/2)^4 * (x + 1/2)^4 sage: QQ._factor_univariate_polynomial( (2*x + 1) * (3*x^2 - 5)^2 ) (18) * (x + 1/2) * (x^2 - 5/3)^2 sage: f = prod((k^2*x^k + k)^(k-1) for k in primes(10)) sage: QQ._factor_univariate_polynomial(f) (1751787911376562500) * (x^2 + 1/2) * (x^3 + 1/3)^2 * (x^5 + 1/5)^4 * (x^7 + 1/7)^6 sage: QQ._factor_univariate_polynomial( 10*x^5 - 1 ) (10) * (x^5 - 1/10) sage: QQ._factor_univariate_polynomial( 10*x^5 - 10 ) (10) * (x - 1) * (x^4 + x^3 + x^2 + x + 1) """ G = list(f._pari_with_name().factor()) # normalize the leading coefficients F = [(f.parent()(g).monic(), int(e)) for (g, e) in zip(*G)] from sage.structure.factorization import Factorization return Factorization(F, f.leading_coefficient())
def PolynomialQuotientRing_generic__factor_multivariate_polynomial( self, f, proof=True): from sage.structure.factorization import Factorization if f.is_zero(): raise ValueError("factorization of 0 not defined") from_isomorphic_ring, to_isomorphic_ring, isomorphic_ring = self._isomorphic_ring( ) g = f.map_coefficients(to_isomorphic_ring) F = g.factor() unit = f.parent( from_isomorphic_ring(F.unit().constant_coefficient())) return Factorization( [(factor.map_coefficients(from_isomorphic_ring), e) for factor, e in F], unit=unit)
def factored_unit_order(self): """ Returns a list of Factorization objects, each the factorization of the order of the units in a `\ZZ / p^n \ZZ` component of this group (using the Chinese Remainder Theorem). EXAMPLES:: sage: R = Integers(8*9*25*17*29) sage: R.factored_unit_order() [2^2, 2 * 3, 2^2 * 5, 2^4, 2^2 * 7] """ ans = [] from sage.structure.factorization import Factorization for p, e in self.factored_order(): ans.append(Factorization([(p,e-1)]) * factor(p-1, int_=(self.__order < 2**31))) return ans
def factor_polynomial_over_function_field(f): r""" Factor the polynomial f over the function field K. INPUT: - ``K`` -- a global function field - ``f`` -- a nonzero univariate polynomial over ``K`` OUTPUT: the complete factorization of ``f`` """ K = f.base_ring() if K is K.rational_function_field(): return f.factor() K0 = K.rational_function_field() old_variable_name = f.variable_name() if K.variable_name() == f.variable_name() or K0.variable_name() == f.variable_name(): # replace x with xx to make the variable names distinct f = f.change_variable_name(old_variable_name + old_variable_name) F, d = to_trivariate_polynomial(K, f) R = F.parent() G = R(defining_polynomial(K)) factorization = [] g = f J = R.ideal(F, G) for Q, P in J.primary_decomposition_complete(): prime_factor = prime_factor_from_prime_ideal(K, P) if prime_factor.degree() > 0: e = 0 while True: q, r = g.quo_rem(prime_factor) if r == 0: g = q e += 1 else: break factorization.append((prime_factor, e)) assert g.degree() == 0, "f did not factor properly" from sage.structure.factorization import Factorization ret = Factorization(factorization, g[0]) return ret
def factor(self): """ Return a factorization of ``self``. Since ``self`` is either a unit or zero, this function is trivial. EXAMPLES:: sage: x = GF(7)(5) sage: x.factor() 5 sage: RR(0).factor() Traceback (most recent call last): ... ArithmeticError: factorization of 0.000000000000000 is not defined """ if not self: raise ArithmeticError("factorization of {!r} is not defined".format(self)) from sage.structure.factorization import Factorization return Factorization([], self) # No factor; "self" as unit
def _factor_univariate_polynomial(self, p, **kwds): r""" Factorization of univariate polynomials. EXAMPLES:: sage: K = GF(3).algebraic_closure() sage: R = PolynomialRing(K, 'T') sage: T = R.gen() sage: (K.gen(2) * T^2 - 1).factor() (z2) * (T + z4^3 + z4^2 + z4) * (T + 2*z4^3 + 2*z4^2 + 2*z4) sage: for d in range(10): ....: p = R.random_element(degree=randint(2,8)) ....: assert p.factor().prod() == p, "error in the factorization of p={}".format(p) """ from sage.structure.factorization import Factorization R = p.parent() return Factorization([(R([-root, self.one()]), m) for root, m in p.roots()], unit=p[p.degree()])
def factor(self): """ Factor the ideal by factoring the corresponding ideal in the absolute number field. EXAMPLES:: sage: K.<a, b> = QQ.extension([x^2 + 11, x^2 - 5]) sage: K.factor(5) (Fractional ideal (5, (-1/4*b - 1/4)*a + 1/4*b - 3/4))^2 * (Fractional ideal (5, (-1/4*b - 1/4)*a + 1/4*b - 7/4))^2 sage: K.ideal(5).factor() (Fractional ideal (5, (-1/4*b - 1/4)*a + 1/4*b - 3/4))^2 * (Fractional ideal (5, (-1/4*b - 1/4)*a + 1/4*b - 7/4))^2 sage: K.ideal(5).prime_factors() [Fractional ideal (5, (-1/4*b - 1/4)*a + 1/4*b - 3/4), Fractional ideal (5, (-1/4*b - 1/4)*a + 1/4*b - 7/4)] sage: PQ.<X> = QQ[] sage: F.<a, b> = NumberFieldTower([X^2 - 2, X^2 - 3]) sage: PF.<Y> = F[] sage: K.<c> = F.extension(Y^2 - (1 + a)*(a + b)*a*b) sage: I = K.ideal(c) sage: P = K.ideal((b*a - b - 1)*c/2 + a - 1) sage: Q = K.ideal((b*a - b - 1)*c/2) sage: list(I.factor()) == [(P, 2), (Q, 1)] True sage: I == P^2*Q True sage: [p.is_prime() for p in [P, Q]] [True, True] """ F = self.number_field() abs_ideal = self.absolute_ideal() to_F = abs_ideal.number_field().structure()[0] factor_list = [(F.ideal([to_F(_) for _ in p.gens()]), e) for p, e in abs_ideal.factor()] # sorting and simplification will already have been done return Factorization(factor_list, sort=False, simplify=False)
def _squarefree_decomposition_univariate_polynomial(self, f): r""" Return the square-free decomposition of ``f`` over this field. This is a helper method for :meth:`sage.rings.polynomial.squarefree_decomposition`. INPUT: - ``f`` -- a univariate non-zero polynomial over this field ALGORITHM: For rings of characteristic zero, we use the algorithm described in [Yun1976]_. Other fields may provide their own implementation by overriding this method. EXAMPLES:: sage: x = polygen(QQ) sage: p = 37 * (x-1)^3 * (x-2)^3 * (x-1/3)^7 * (x-3/7) sage: p.squarefree_decomposition() (37*x - 111/7) * (x^2 - 3*x + 2)^3 * (x - 1/3)^7 sage: p = 37 * (x-2/3)^2 sage: p.squarefree_decomposition() (37) * (x - 2/3)^2 sage: x = polygen(GF(3)) sage: x.squarefree_decomposition() x sage: f = QQbar['x'](1) sage: f.squarefree_decomposition() 1 """ from sage.structure.factorization import Factorization if f.degree() == 0: return Factorization([], unit=f[0]) if self.characteristic() != 0: raise NotImplementedError( "square-free decomposition not implemented for this polynomial." ) factors = [] cur = f f = [f] while cur.degree() > 0: cur = cur.gcd(cur.derivative()) f.append(cur) g = [] for i in range(len(f) - 1): g.append(f[i] // f[i + 1]) a = [] for i in range(len(g) - 1): a.append(g[i] // g[i + 1]) a.append(g[-1]) unit = f[-1] for i in range(len(a)): if a[i].degree() > 0: factors.append((a[i], i + 1)) else: unit = unit * a[i].constant_coefficient()**(i + 1) return Factorization(factors, unit=unit, sort=False)
def word_problem(self, gens=None): r""" Solve the word problem. This method writes the group element as a product of the elements of the list ``gens``, or the standard generators of the parent of self if ``gens`` is None. INPUT: - ``gens`` -- a list/tuple/iterable of elements (or objects that can be converted to group elements), or ``None`` (default). By default, the generators of the parent group are used. OUTPUT: A factorization object that contains information about the order of factors and the exponents. A ``ValueError`` is raised if the group element cannot be written as a word in ``gens``. ALGORITHM: Use GAP, which has optimized algorithms for solving the word problem (the GAP functions ``EpimorphismFromFreeGroup`` and ``PreImagesRepresentative``). EXAMPLE:: sage: G = GL(2,5); G General Linear Group of degree 2 over Finite Field of size 5 sage: G.gens() ( [2 0] [4 1] [0 1], [4 0] ) sage: G(1).word_problem([G.gen(0)]) 1 sage: type(_) <class 'sage.structure.factorization.Factorization'> sage: g = G([0,4,1,4]) sage: g.word_problem() ([4 1] [4 0])^-1 Next we construct a more complicated element of the group from the generators:: sage: s,t = G.0, G.1 sage: a = (s * t * s); b = a.word_problem(); b ([2 0] [0 1]) * ([4 1] [4 0]) * ([2 0] [0 1]) sage: flatten(b) [ [2 0] [4 1] [2 0] [0 1], 1, [4 0], 1, [0 1], 1 ] sage: b.prod() == a True We solve the word problem using some different generators:: sage: s = G([2,0,0,1]); t = G([1,1,0,1]); u = G([0,-1,1,0]) sage: a.word_problem([s,t,u]) ([2 0] [0 1])^-1 * ([1 1] [0 1])^-1 * ([0 4] [1 0]) * ([2 0] [0 1])^-1 We try some elements that don't actually generate the group:: sage: a.word_problem([t,u]) Traceback (most recent call last): ... ValueError: word problem has no solution AUTHORS: - David Joyner and William Stein - David Loeffler (2010): fixed some bugs - Volker Braun (2013): LibGAP """ from sage.libs.gap.libgap import libgap G = self.parent() if gens: gen = lambda i: gens[i] H = libgap.Group([G(x).gap() for x in gens]) else: gen = G.gen H = G.gap() hom = H.EpimorphismFromFreeGroup() preimg = hom.PreImagesRepresentative(self.gap()) if preimg.is_bool(): assert preimg == libgap.eval('fail') raise ValueError('word problem has no solution') result = [] n = preimg.NumberSyllables().sage() exponent_syllable = libgap.eval('ExponentSyllable') generator_syllable = libgap.eval('GeneratorSyllable') for i in range(n): exponent = exponent_syllable(preimg, i + 1).sage() generator = gen(generator_syllable(preimg, i + 1).sage() - 1) result.append((generator, exponent)) from sage.structure.factorization import Factorization result = Factorization(result) result._set_cr(True) return result
def word_problem(self, gens=None): r""" Write this group element in terms of the elements of the list ``gens``, or the standard generators of the parent of self if ``gens`` is None. INPUT: - ``gens`` - a list of elements that can be coerced into the parent of self, or None. ALGORITHM: Use GAP, which has optimized algorithms for solving the word problem (the GAP functions EpimorphismFromFreeGroup and PreImagesRepresentative). EXAMPLE:: sage: G = GL(2,5); G General Linear Group of degree 2 over Finite Field of size 5 sage: G.gens() [ [2 0] [0 1], [4 1] [4 0] ] sage: G(1).word_problem([G.1, G.0]) 1 Next we construct a more complicated element of the group from the generators:: sage: s,t = G.0, G.1 sage: a = (s * t * s); b = a.word_problem(); b ([2 0] [0 1]) * ([4 1] [4 0]) * ([2 0] [0 1]) sage: b.prod() == a True We solve the word problem using some different generators:: sage: s = G([2,0,0,1]); t = G([1,1,0,1]); u = G([0,-1,1,0]) sage: a.word_problem([s,t,u]) ([2 0] [0 1])^-1 * ([1 1] [0 1])^-1 * ([0 4] [1 0]) * ([2 0] [0 1])^-1 We try some elements that don't actually generate the group:: sage: a.word_problem([t,u]) Traceback (most recent call last): ... ValueError: Could not solve word problem AUTHORS: - David Joyner and William Stein - David Loeffler (2010): fixed some bugs """ G = self.parent() gg = gap(G) if not gens: gens = gg.GeneratorsOfGroup() else: gens = gap([G(x) for x in gens]) H = gens.Group() hom = H.EpimorphismFromFreeGroup() in_terms_of_gens = str(hom.PreImagesRepresentative(self)) if 'identity' in in_terms_of_gens: return Factorization([]) elif in_terms_of_gens == 'fail': raise ValueError, "Could not solve word problem" v = list(H.GeneratorsOfGroup()) F = G.field_of_definition() w = [G(x._matrix_(F)) for x in v] factors = [Factorization([(x,1)]) for x in w] d = dict([('x%s'%(i+1),factors[i]) for i in range(len(factors))]) from sage.misc.sage_eval import sage_eval F = sage_eval(str(in_terms_of_gens), d) F._set_cr(True) return F
def montes_factorization(self, G, assume_squarefree=False, required_precision=None): """ Factor ``G`` over the completion of the domain of this valuation. INPUT: - ``G`` -- a monic polynomial over the domain of this valuation - ``assume_squarefree`` -- a boolean (default: ``False``), whether to assume ``G`` to be squarefree - ``required_precision`` -- a number or infinity (default: infinity); if ``infinity``, the returned polynomials are actual factors of ``G``, otherwise they are only factors with precision at least ``required_precision``. ALGORITHM: We compute :meth:`mac_lane_approximants` with ``required_precision``. The key polynomials approximate factors of ``G``. EXAMPLES:: sage: sys.path.append(os.getcwd()); from mac_lane import * # optional: standalone sage: k=Qp(5,4) sage: v = pAdicValuation(k) sage: R.<x>=k[] sage: G = x^2 + 1 sage: v.montes_factorization(G) ((1 + O(5^4))*x + (2 + 5 + 2*5^2 + 5^3 + O(5^4))) * ((1 + O(5^4))*x + (3 + 3*5 + 2*5^2 + 3*5^3 + O(5^4))) The computation might not terminate over incomplete fields (in particular because the factors can not be represented there):: sage: R.<x> = QQ[] sage: v = pAdicValuation(QQ, 2) sage: v.montes_factorization(x^2 + 1) x^2 + 1 sage: v.montes_factorization(x^2 - 1) (x - 1) * (x + 1) sage: v.montes_factorization(x^2 - 1, required_precision=5) (x + 1) * (x + 31) REFERENCES: .. [GMN2008] Jordi Guardia, Jesus Montes, Enric Nart (2008). Newton polygons of higher order in algebraic number theory. arXiv:0807.2620 [math.NT] """ if required_precision is None: from sage.rings.all import infinity required_precision = infinity R = G.parent() if R.base_ring() is not self.domain(): raise ValueError( "G must be defined over the domain of this valuation") if not G.is_monic(): raise ValueError("G must be monic") if not all([self(c) >= 0 for c in G.coefficients()]): raise ValueError("G must be integral") # W contains approximate factors of G W = self.mac_lane_approximants(G, required_precision=required_precision, require_maximal_degree=True, assume_squarefree=assume_squarefree) ret = [w.phi() for w in W] from sage.structure.factorization import Factorization return Factorization([(g, 1) for g in ret], simplify=False)
def factor(self): r""" Return the factorization of this polynomial. EXAMPLES:: sage: R.<t> = PolynomialRing(Qp(3,3,print_mode='terse',print_pos=False)) sage: pol = t^8 - 1 sage: for p,e in pol.factor(): ....: print("{} {}".format(e, p)) 1 (1 + O(3^3))*t + 1 + O(3^3) 1 (1 + O(3^3))*t - 1 + O(3^3) 1 (1 + O(3^3))*t^2 + (5 + O(3^3))*t - 1 + O(3^3) 1 (1 + O(3^3))*t^2 + (-5 + O(3^3))*t - 1 + O(3^3) 1 (1 + O(3^3))*t^2 + O(3^3)*t + 1 + O(3^3) sage: R.<t> = PolynomialRing(Qp(5,6,print_mode='terse',print_pos=False)) sage: pol = 100 * (5*t - 1) * (t - 5) sage: pol (500 + O(5^9))*t^2 + (-2600 + O(5^8))*t + 500 + O(5^9) sage: pol.factor() (500 + O(5^9)) * ((1 + O(5^5))*t - 1/5 + O(5^5)) * ((1 + O(5^6))*t - 5 + O(5^6)) sage: pol.factor().value() (500 + O(5^8))*t^2 + (-2600 + O(5^8))*t + 500 + O(5^8) The same factorization over `\ZZ_p`. In this case, the "unit" part is a `p`-adic unit and the power of `p` is considered to be a factor:: sage: R.<t> = PolynomialRing(Zp(5,6,print_mode='terse',print_pos=False)) sage: pol = 100 * (5*t - 1) * (t - 5) sage: pol (500 + O(5^9))*t^2 + (-2600 + O(5^8))*t + 500 + O(5^9) sage: pol.factor() (4 + O(5^6)) * (5 + O(5^7))^2 * ((1 + O(5^6))*t - 5 + O(5^6)) * ((5 + O(5^6))*t - 1 + O(5^6)) sage: pol.factor().value() (500 + O(5^8))*t^2 + (-2600 + O(5^8))*t + 500 + O(5^8) In the following example, the discriminant is zero, so the `p`-adic factorization is not well defined:: sage: factor(t^2) Traceback (most recent call last): ... PrecisionError: p-adic factorization not well-defined since the discriminant is zero up to the requestion p-adic precision An example of factoring a constant polynomial (see :trac:`26669`):: sage: R.<x> = Qp(5)[] sage: R(2).factor() 2 + O(5^20) More examples over `\ZZ_p`:: sage: R.<w> = PolynomialRing(Zp(5, prec=6, type = 'capped-abs', print_mode = 'val-unit')) sage: f = w^5-1 sage: f.factor() ((1 + O(5^6))*w + 3124 + O(5^6)) * ((1 + O(5^6))*w^4 + (12501 + O(5^6))*w^3 + (9376 + O(5^6))*w^2 + (6251 + O(5^6))*w + 3126 + O(5^6)) See :trac:`4038`:: sage: E = EllipticCurve('37a1') sage: K =Qp(7,10) sage: EK = E.base_extend(K) sage: E = EllipticCurve('37a1') sage: K = Qp(7,10) sage: EK = E.base_extend(K) sage: g = EK.division_polynomial_0(3) sage: g.factor() (3 + O(7^10)) * ((1 + O(7^10))*x + 1 + 2*7 + 4*7^2 + 2*7^3 + 5*7^4 + 7^5 + 5*7^6 + 3*7^7 + 5*7^8 + 3*7^9 + O(7^10)) * ((1 + O(7^10))*x^3 + (6 + 4*7 + 2*7^2 + 4*7^3 + 7^4 + 5*7^5 + 7^6 + 3*7^7 + 7^8 + 3*7^9 + O(7^10))*x^2 + (6 + 3*7 + 5*7^2 + 2*7^4 + 7^5 + 7^6 + 2*7^8 + 3*7^9 + O(7^10))*x + 2 + 5*7 + 4*7^2 + 2*7^3 + 6*7^4 + 3*7^5 + 7^6 + 4*7^7 + O(7^10)) TESTS: Check that :trac:`13293` is fixed:: sage: R.<T> = Qp(3)[] sage: f = 1926*T^2 + 312*T + 387 sage: f.factor() (3^2 + 2*3^3 + 2*3^4 + 3^5 + 2*3^6 + O(3^22)) * ((1 + O(3^19))*T + 2*3^-1 + 3 + 3^2 + 2*3^5 + 2*3^6 + 2*3^7 + 3^8 + 3^9 + 2*3^11 + 3^15 + 3^17 + O(3^19)) * ((1 + O(3^20))*T + 2*3 + 3^2 + 3^3 + 3^5 + 2*3^6 + 2*3^7 + 3^8 + 3^10 + 3^11 + 2*3^12 + 2*3^14 + 2*3^15 + 2*3^17 + 2*3^18 + O(3^20)) Check that :trac:`24065` is fixed:: sage: R = Zp(2, type='fixed-mod', prec=3) sage: P.<x> = R[] sage: ((1 + 2)*x + (1 + 2)*x^2).factor() (1 + 2) * (x + 1) * x """ if self == 0: raise ArithmeticError( "factorization of {!r} is not defined".format(self)) elif self.is_constant(): return Factorization((), self.constant_coefficient()) # Scale self such that 0 is the lowest valuation # amongst the coefficients try: val = self.valuation(val_of_var=0) except TypeError: val = min([c.valuation() for c in self]) self_normal = self / self.base_ring().uniformizer_pow(val) absprec = min([x.precision_absolute() for x in self_normal]) if self_normal.discriminant().valuation() >= absprec: raise PrecisionError( "p-adic factorization not well-defined since the discriminant is zero up to the requestion p-adic precision" ) G = self_normal.__pari__().factorpadic(self.base_ring().prime(), absprec) return _pari_padic_factorization_to_sage(G, self.parent(), self.leading_coefficient())
def factor(self, absprec = None): r""" Returns the factorization of this Polynomial_padic_flat. EXAMPLES: sage: R.<w> = PolynomialRing(Zp(5, prec=5, type = 'capped-abs', print_mode = 'val-unit')) sage: f = w^5-1 sage: f.factor() ((1 + O(5^5))*w + (624 + O(5^5))) * ((1 + O(5^5))*w^4 + (2501 + O(5^5))*w^3 + (1876 + O(5^5))*w^2 + (1251 + O(5^5))*w + (626 + O(5^5))) See \#4038: sage: E = EllipticCurve('37a1') sage: K =Qp(7,10) sage: EK = E.base_extend(K) sage: E = EllipticCurve('37a1') sage: K = Qp(7,10) sage: EK = E.base_extend(K) sage: g = EK.division_polynomial_0(3) sage: g.factor() (3 + O(7^10)) * ((1 + O(7^10))*x + (1 + 2*7 + 4*7^2 + 2*7^3 + 5*7^4 + 7^5 + 5*7^6 + 3*7^7 + 5*7^8 + 3*7^9 + O(7^10))) * ((1 + O(7^10))*x^3 + (6 + 4*7 + 2*7^2 + 4*7^3 + 7^4 + 5*7^5 + 7^6 + 3*7^7 + 7^8 + 3*7^9 + O(7^10))*x^2 + (6 + 3*7 + 5*7^2 + 2*7^4 + 7^5 + 7^6 + 2*7^8 + 3*7^9 + O(7^10))*x + (2 + 5*7 + 4*7^2 + 2*7^3 + 6*7^4 + 3*7^5 + 7^6 + 4*7^7 + O(7^10))) """ if self == 0: raise ValueError, "Factorization of 0 not defined" if absprec is None: absprec = min([x.precision_absolute() for x in self.list()]) else: absprec = Integer(absprec) if absprec <= 0: raise ValueError, "absprec must be positive" G = self._pari_().factorpadic(self.base_ring().prime(), absprec) pols = G[0] exps = G[1] F = [] R = self.parent() for i in xrange(len(pols)): f = R(pols[i], absprec = absprec) e = int(exps[i]) F.append((f,e)) #if R.base_ring().is_field(): # # When the base ring is a field we normalize # # the irreducible factors so they have leading # # coefficient 1. # for i in range(len(F)): # cur = F[i][0].leading_coefficient() # if cur != 1: # F[i] = (F[i][0].monic(), F[i][1]) # return Factorization(F, self.leading_coefficient()) #else: # # When the base ring is not a field, we normalize # # the irreducible factors so that the leading term # # is a power of p. We also ensure that the gcd of # # the coefficients of each term is 1. c = self.leading_coefficient().valuation() u = self.base_ring()(1) for i in range(len(F)): upart = F[i][0].leading_coefficient().unit_part().lift_to_precision(absprec) lval = F[i][0].leading_coefficient().valuation() if upart != 1: F[i] = (F[i][0] / upart, F[i][1]) u *= upart ** F[i][1] c -= lval * F[i][1] if c != 0: F.append((self.parent()(self.base_ring().prime_pow(c)), 1)) u = u.add_big_oh(absprec - c) return Factorization(F, u)
def _factor_univariate_polynomial(self, f): """ Factor the univariate polynomial ``f``. INPUT: - ``f`` -- a univariate polynomial defined over self OUTPUT: - A factorization of ``f`` over self into a unit and monic irreducible factors .. NOTE:: This is a helper method for :meth:`sage.rings.polynomial.polynomial_element.Polynomial.factor`. EXAMPLES:: sage: UCF = UniversalCyclotomicField() sage: x = polygen(UCF) sage: p = x^2 +x +1 sage: p.factor() # indirect doctest (x - E(3)) * (x - E(3)^2) sage: p.roots() # indirect doctest [(E(3), 1), (E(3)^2, 1)] sage: (p^2).factor() (x - E(3))^2 * (x - E(3)^2)^2 sage: cyclotomic_polynomial(12).change_ring(UCF).factor() (x + E(12)^7) * (x - E(12)^11) * (x + E(12)^11) * (x - E(12)^7) sage: p = (UCF.zeta(5) + 1) * (x^2 - 2)^2 * (x^2 - 3) * (x - 5)**2 * (x^2 - x + 1) sage: p.factor() (-E(5)^2 - E(5)^3 - E(5)^4) * (x + E(12)^7 - E(12)^11) * (x + E(3)^2) * (x + E(3)) * (x - E(12)^7 + E(12)^11) * (x - 5)^2 * (x - E(8) + E(8)^3)^2 * (x + E(8) - E(8)^3)^2 sage: p.factor().value() == p True sage: (x^3 - 8).factor() (x - 2) * (x - 2*E(3)) * (x - 2*E(3)^2) In most situations, the factorization will fail with a ``NotImplementedError``:: sage: (x^3 - 2).factor() Traceback (most recent call last): ... NotImplementedError: no known factorization for this polynomial TESTS:: sage: UCF = UniversalCyclotomicField() sage: x = polygen(UCF) sage: p = (x - 2/7) * (x - 3/5) sage: sorted(p.roots(multiplicities=False)) [2/7, 3/5] sage: p = UCF.zeta(3) * x - 1 + UCF.zeta(5,2) sage: r = p.roots() sage: r [(-2*E(15) - E(15)^4 - E(15)^7 - E(15)^13, 1)] sage: p(r[0][0]) 0 """ from sage.structure.factorization import Factorization UCF = self x = f.parent().gen() # make the polynomial monic unit = f.leading_coefficient() f /= unit # trivial degree one case if f.degree() == 1: return Factorization([(f, 1)], unit) # From now on, we restrict to polynomial with rational cofficients. The # factorization is provided only in the case it is a product of # cyclotomic polynomials and quadratic polynomials. In this situation # the roots belong to UCF and the polynomial factorizes as a product of # degree one factors. if any(ZZ(cf._obj.Conductor()) != 1 for cf in f): raise NotImplementedError( 'no known factorization for this polynomial') f = f.change_ring(QQ) factors = [] for p, e in f.factor(): if p.degree() == 1: factors.append((x + p[0], e)) elif p.degree() == 2: c = p[0] b = p[1] a = p[2] D = UCF(b**2 - 4 * a * c).sqrt() r1 = (-b - D) / (2 * a) r2 = (-b + D) / (2 * a) factors.append((x - r1, e)) factors.append((x - r2, e)) else: m = p.is_cyclotomic(certificate=True) if not m: raise NotImplementedError( 'no known factorization for this polynomial') for i in m.coprime_integers(m): factors.append((x - UCF.zeta(m, i), e)) return Factorization(factors, unit)
def word_problem(self, gens=None): r""" Solve the word problem. This method writes the group element as a product of the elements of the list ``gens``, or the standard generators of the parent of self if ``gens`` is None. INPUT: - ``gens`` -- a list/tuple/iterable of elements (or objects that can be converted to group elements), or ``None`` (default). By default, the generators of the parent group are used. OUTPUT: A factorization object that contains information about the order of factors and the exponents. A ``ValueError`` is raised if the group element cannot be written as a word in ``gens``. ALGORITHM: Use GAP, which has optimized algorithms for solving the word problem (the GAP functions ``EpimorphismFromFreeGroup`` and ``PreImagesRepresentative``). EXAMPLE:: sage: G = GL(2,5); G General Linear Group of degree 2 over Finite Field of size 5 sage: G.gens() ( [2 0] [4 1] [0 1], [4 0] ) sage: G(1).word_problem([G.gen(0)]) 1 sage: type(_) <class 'sage.structure.factorization.Factorization'> sage: g = G([0,4,1,4]) sage: g.word_problem() ([4 1] [4 0])^-1 Next we construct a more complicated element of the group from the generators:: sage: s,t = G.0, G.1 sage: a = (s * t * s); b = a.word_problem(); b ([2 0] [0 1]) * ([4 1] [4 0]) * ([2 0] [0 1]) sage: flatten(b) [ [2 0] [4 1] [2 0] [0 1], 1, [4 0], 1, [0 1], 1 ] sage: b.prod() == a True We solve the word problem using some different generators:: sage: s = G([2,0,0,1]); t = G([1,1,0,1]); u = G([0,-1,1,0]) sage: a.word_problem([s,t,u]) ([2 0] [0 1])^-1 * ([1 1] [0 1])^-1 * ([0 4] [1 0]) * ([2 0] [0 1])^-1 We try some elements that don't actually generate the group:: sage: a.word_problem([t,u]) Traceback (most recent call last): ... ValueError: word problem has no solution AUTHORS: - David Joyner and William Stein - David Loeffler (2010): fixed some bugs - Volker Braun (2013): LibGAP """ from sage.libs.gap.libgap import libgap G = self.parent() if gens: gen = lambda i:gens[i] H = libgap.Group([G(x).gap() for x in gens]) else: gen = G.gen H = G.gap() hom = H.EpimorphismFromFreeGroup() preimg = hom.PreImagesRepresentative(self.gap()) if preimg.is_bool(): assert preimg == libgap.eval('fail') raise ValueError('word problem has no solution') result = [] n = preimg.NumberSyllables().sage() exponent_syllable = libgap.eval('ExponentSyllable') generator_syllable = libgap.eval('GeneratorSyllable') for i in range(n): exponent = exponent_syllable(preimg, i+1).sage() generator = gen(generator_syllable(preimg, i+1).sage() - 1) result.append( (generator, exponent) ) from sage.structure.factorization import Factorization result = Factorization(result) result._set_cr(True) return result
def montes_factorization(self, G, assume_squarefree=False, required_precision=None): """ Factor ``G`` over the completion of the domain of this valuation. INPUT: - ``G`` -- a monic polynomial over the domain of this valuation - ``assume_squarefree`` -- a boolean (default: ``False``), whether to assume ``G`` to be squarefree - ``required_precision`` -- a number or infinity (default: infinity); if ``infinity``, the returned polynomials are actual factors of ``G``, otherwise they are only factors with precision at least ``required_precision``. ALGORITHM: We compute :meth:`mac_lane_approximants` with ``required_precision``. The key polynomials approximate factors of ``G``. This can be very slow unless ``required_precision`` is set to zero. Single factor lifting could improve this significantly. EXAMPLES:: sage: k=Qp(5,4) sage: v = k.valuation() sage: R.<x>=k[] sage: G = x^2 + 1 sage: v.montes_factorization(G) ((1 + O(5^4))*x + 2 + 5 + 2*5^2 + 5^3 + O(5^4)) * ((1 + O(5^4))*x + 3 + 3*5 + 2*5^2 + 3*5^3 + O(5^4)) The computation might not terminate over incomplete fields (in particular because the factors can not be represented there):: sage: R.<x> = QQ[] sage: v = QQ.valuation(2) sage: v.montes_factorization(x^6 - 1) (x - 1) * (x + 1) * (x^2 - x + 1) * (x^2 + x + 1) sage: v.montes_factorization(x^7 - 1) # not tested, does not terminate sage: v.montes_factorization(x^7 - 1, required_precision=5) (x - 1) * (x^3 - 5*x^2 - 6*x - 1) * (x^3 + 6*x^2 + 5*x - 1) TESTS: Some examples that Sebastian Pauli used in a talk at Sage Days 87. In this example, ``f`` factors as three factors of degree 50 over an unramified extension:: sage: R.<u> = ZqFM(125) sage: S.<x> = R[] sage: f = (x^6+2)^25 + 5 sage: v = R.valuation() sage: v.montes_factorization(f, assume_squarefree=True, required_precision=0) (x^50 + 2*5*x^45 + 5*x^40 + 5*x^30 + 2*x^25 + 3*5*x^20 + 2*5*x^10 + 2*5*x^5 + 5*x + 3 + 5) * (x^50 + 3*5*x^45 + 5*x^40 + 5*x^30 + (3 + 4*5)*x^25 + 3*5*x^20 + 2*5*x^10 + 3*5*x^5 + 4*5*x + 3 + 5) * (x^50 + 3*5*x^40 + 3*5*x^30 + 4*5*x^20 + 5*x^10 + 3 + 5) In this case, ``f`` factors into degrees 1, 2, and 5 over a totally ramified extension:: sage: R = Zp(5) sage: S.<w> = R[] sage: R.<w> = R.extension(w^3 + 5) sage: S.<x> = R[] sage: f = (x^3 + 5)*(x^5 + w) + 625 sage: v = R.valuation() sage: v.montes_factorization(f, assume_squarefree=True, required_precision=0) ((1 + O(w^60))*x + 4*w + O(w^61)) * ((1 + O(w^60))*x^2 + (w + O(w^61))*x + w^2 + O(w^62)) * ((1 + O(w^60))*x^5 + w + O(w^61)) REFERENCES: The underlying algorithm is described in [Mac1936II]_ and thoroughly analyzed in [GMN2008]_. """ if required_precision is None: from sage.rings.all import infinity required_precision = infinity R = G.parent() if R.base_ring() is not self.domain(): raise ValueError( "G must be defined over the domain of this valuation") if not G.is_monic(): raise ValueError("G must be monic") if not all([self(c) >= 0 for c in G.coefficients()]): raise ValueError("G must be integral") # W contains approximate factors of G W = self.mac_lane_approximants(G, required_precision=required_precision, require_maximal_degree=True, assume_squarefree=assume_squarefree) ret = [w.phi() for w in W] from sage.structure.factorization import Factorization return Factorization([(g, 1) for g in ret], simplify=False)
def MacMahonOmega(var, expression, denominator=None, op=operator.ge, Factorization_sort=False, Factorization_simplify=True): r""" Return `\Omega_{\mathrm{op}}` of ``expression`` with respect to ``var``. To be more precise, calculate .. MATH:: \Omega_{\mathrm{op}} \frac{n}{d_1 \dots d_n} for the numerator `n` and the factors `d_1`, ..., `d_n` of the denominator, all of which are Laurent polynomials in ``var`` and return a (partial) factorization of the result. INPUT: - ``var`` -- a variable or a representation string of a variable - ``expression`` -- a :class:`~sage.structure.factorization.Factorization` of Laurent polynomials or, if ``denominator`` is specified, a Laurent polynomial interpreted as the numerator of the expression - ``denominator`` -- a Laurent polynomial or a :class:`~sage.structure.factorization.Factorization` (consisting of Laurent polynomial factors) or a tuple/list of factors (Laurent polynomials) - ``op`` -- (default: ``operator.ge``) an operator At the moment only ``operator.ge`` is implemented. - ``Factorization_sort`` (default: ``False``) and ``Factorization_simplify`` (default: ``True``) -- are passed on to :class:`sage.structure.factorization.Factorization` when creating the result OUTPUT: A (partial) :class:`~sage.structure.factorization.Factorization` of the result whose factors are Laurent polynomials .. NOTE:: The numerator of the result may not be factored. REFERENCES: - [Mac1915]_ - [APR2001]_ EXAMPLES:: sage: L.<mu, x, y, z, w> = LaurentPolynomialRing(ZZ) sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y/mu]) 1 * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y/mu, 1 - z/mu]) 1 * (-x + 1)^-1 * (-x*y + 1)^-1 * (-x*z + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y*mu, 1 - z/mu]) (-x*y*z + 1) * (-x + 1)^-1 * (-y + 1)^-1 * (-x*z + 1)^-1 * (-y*z + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y/mu^2]) 1 * (-x + 1)^-1 * (-x^2*y + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu^2, 1 - y/mu]) (x*y + 1) * (-x + 1)^-1 * (-x*y^2 + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y*mu, 1 - z/mu^2]) (-x^2*y*z - x*y^2*z + x*y*z + 1) * (-x + 1)^-1 * (-y + 1)^-1 * (-x^2*z + 1)^-1 * (-y^2*z + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y/mu^3]) 1 * (-x + 1)^-1 * (-x^3*y + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y/mu^4]) 1 * (-x + 1)^-1 * (-x^4*y + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu^3, 1 - y/mu]) (x*y^2 + x*y + 1) * (-x + 1)^-1 * (-x*y^3 + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu^4, 1 - y/mu]) (x*y^3 + x*y^2 + x*y + 1) * (-x + 1)^-1 * (-x*y^4 + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu^2, 1 - y/mu, 1 - z/mu]) (x*y*z + x*y + x*z + 1) * (-x + 1)^-1 * (-x*y^2 + 1)^-1 * (-x*z^2 + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu^2, 1 - y*mu, 1 - z/mu]) (-x*y*z^2 - x*y*z + x*z + 1) * (-x + 1)^-1 * (-y + 1)^-1 * (-x*z^2 + 1)^-1 * (-y*z + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y*mu, 1 - z*mu, 1 - w/mu]) (x*y*z*w^2 + x*y*z*w - x*y*w - x*z*w - y*z*w + 1) * (-x + 1)^-1 * (-y + 1)^-1 * (-z + 1)^-1 * (-x*w + 1)^-1 * (-y*w + 1)^-1 * (-z*w + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y*mu, 1 - z/mu, 1 - w/mu]) (x^2*y*z*w + x*y^2*z*w - x*y*z*w - x*y*z - x*y*w + 1) * (-x + 1)^-1 * (-y + 1)^-1 * (-x*z + 1)^-1 * (-x*w + 1)^-1 * (-y*z + 1)^-1 * (-y*w + 1)^-1 sage: MacMahonOmega(mu, mu^-2, [1 - x*mu, 1 - y/mu]) x^2 * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu^-1, [1 - x*mu, 1 - y/mu]) x * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu, [1 - x*mu, 1 - y/mu]) (-x*y + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu^2, [1 - x*mu, 1 - y/mu]) (-x*y^2 - x*y + y^2 + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 We demonstrate the different allowed input variants:: sage: MacMahonOmega(mu, ....: Factorization([(mu, 2), (1 - x*mu, -1), (1 - y/mu, -1)])) (-x*y^2 - x*y + y^2 + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu^2, ....: Factorization([(1 - x*mu, 1), (1 - y/mu, 1)])) (-x*y^2 - x*y + y^2 + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu^2, [1 - x*mu, 1 - y/mu]) (-x*y^2 - x*y + y^2 + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu^2, (1 - x*mu)*(1 - y/mu)) # not tested because not fully implemented (-x*y^2 - x*y + y^2 + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu^2 / ((1 - x*mu)*(1 - y/mu))) # not tested because not fully implemented (-x*y^2 - x*y + y^2 + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 TESTS:: sage: MacMahonOmega(mu, 1, [1 - x*mu]) 1 * (-x + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x/mu]) 1 sage: MacMahonOmega(mu, 0, [1 - x*mu]) 0 sage: MacMahonOmega(mu, L(1), []) 1 sage: MacMahonOmega(mu, L(0), []) 0 sage: MacMahonOmega(mu, 2, []) 2 sage: MacMahonOmega(mu, 2*mu, []) 2 sage: MacMahonOmega(mu, 2/mu, []) 0 :: sage: MacMahonOmega(mu, Factorization([(1/mu, 1), (1 - x*mu, -1), ....: (1 - y/mu, -2)], unit=2)) 2*x * (-x + 1)^-1 * (-x*y + 1)^-2 sage: MacMahonOmega(mu, Factorization([(mu, -1), (1 - x*mu, -1), ....: (1 - y/mu, -2)], unit=2)) 2*x * (-x + 1)^-1 * (-x*y + 1)^-2 sage: MacMahonOmega(mu, Factorization([(mu, -1), (1 - x, -1)])) 0 sage: MacMahonOmega(mu, Factorization([(2, -1)])) 1 * 2^-1 :: sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - z, 1 - y/mu]) 1 * (-z + 1)^-1 * (-x + 1)^-1 * (-x*y + 1)^-1 :: sage: MacMahonOmega(mu, 1, [1 - x*mu], op=operator.lt) Traceback (most recent call last): ... NotImplementedError: At the moment, only Omega_ge is implemented. sage: MacMahonOmega(mu, 1, Factorization([(1 - x*mu, -1)])) Traceback (most recent call last): ... ValueError: Factorization (-mu*x + 1)^-1 of the denominator contains negative exponents. sage: MacMahonOmega(2*mu, 1, [1 - x*mu]) Traceback (most recent call last): ... ValueError: 2*mu is not a variable. sage: MacMahonOmega(mu, 1, Factorization([(0, 2)])) Traceback (most recent call last): ... ZeroDivisionError: Denominator contains a factor 0. sage: MacMahonOmega(mu, 1, [2 - x*mu]) Traceback (most recent call last): ... NotImplementedError: Factor 2 - x*mu is not normalized. sage: MacMahonOmega(mu, 1, [1 - x*mu - mu^2]) Traceback (most recent call last): ... NotImplementedError: Cannot handle factor 1 - x*mu - mu^2. :: sage: L.<mu, x, y, z, w> = LaurentPolynomialRing(QQ) sage: MacMahonOmega(mu, 1/mu, ....: Factorization([(1 - x*mu, 1), (1 - y/mu, 2)], unit=2)) 1/2*x * (-x + 1)^-1 * (-x*y + 1)^-2 """ from sage.arith.misc import factor from sage.misc.misc_c import prod from sage.rings.integer_ring import ZZ from sage.rings.polynomial.laurent_polynomial_ring \ import LaurentPolynomialRing, LaurentPolynomialRing_univariate from sage.structure.factorization import Factorization if op != operator.ge: raise NotImplementedError( 'At the moment, only Omega_ge is implemented.') if denominator is None: if isinstance(expression, Factorization): numerator = expression.unit() * \ prod(f**e for f, e in expression if e > 0) denominator = tuple(f for f, e in expression if e < 0 for _ in range(-e)) else: numerator = expression.numerator() denominator = expression.denominator() else: numerator = expression # at this point we have numerator/denominator if isinstance(denominator, (list, tuple)): factors_denominator = denominator else: if not isinstance(denominator, Factorization): denominator = factor(denominator) if not denominator.is_integral(): raise ValueError( 'Factorization {} of the denominator ' 'contains negative exponents.'.format(denominator)) numerator *= ZZ(1) / denominator.unit() factors_denominator = tuple(factor for factor, exponent in denominator for _ in range(exponent)) # at this point we have numerator/factors_denominator P = var.parent() if isinstance(P, LaurentPolynomialRing_univariate) and P.gen() == var: L = P L0 = L.base_ring() elif var in P.gens(): var = repr(var) L0 = LaurentPolynomialRing( P.base_ring(), tuple(v for v in P.variable_names() if v != var)) L = LaurentPolynomialRing(L0, var) var = L.gen() else: raise ValueError('{} is not a variable.'.format(var)) other_factors = [] to_numerator = [] decoded_factors = [] for factor in factors_denominator: factor = L(factor) D = factor.dict() if not D: raise ZeroDivisionError('Denominator contains a factor 0.') elif len(D) == 1: exponent, coefficient = next(iteritems(D)) if exponent == 0: other_factors.append(L0(factor)) else: to_numerator.append(factor) elif len(D) == 2: if D.get(0, 0) != 1: raise NotImplementedError( 'Factor {} is not normalized.'.format(factor)) D.pop(0) exponent, coefficient = next(iteritems(D)) decoded_factors.append((-coefficient, exponent)) else: raise NotImplementedError( 'Cannot handle factor {}.'.format(factor)) numerator = L(numerator) / prod(to_numerator) result_numerator, result_factors_denominator = \ _Omega_(numerator.dict(), decoded_factors) if result_numerator == 0: return Factorization([], unit=result_numerator) return Factorization([(result_numerator, 1)] + list( (f, -1) for f in other_factors) + list( (1 - f, -1) for f in result_factors_denominator), sort=Factorization_sort, simplify=Factorization_simplify)
def sieve(p, n, r=0, accept=None): ''' Given p, n and r, find the smallest integer m such that there exists an element of multiplicative order r in ℤ/m. This is equivalent to the condition: for each prime power r'|r, there exists a prime power m'|m such that r'|φ(m'). One immediate consequence is that r|φ(m). The output is a list of pairs of integers (m_i, r_i, o_i), such that - m_i is a prime power; - the m_i are pairwise coprime and m = ∏ m_i; - the r_i are pairwise coprime and r = ∏ r_i; - r_i · o_i = φ(m_i). The optional parameter `accept` can be passed in order to put additional constraints on m. If it is given, it must be a function satisfying: - it takes six arguments (p, n, r, l, e, s), where p, n, r, l and e are integers and s is the factorization of an integer (a `Factorization` object); - it returns a boolean; - let s and t be coprime, accept(p, n, r, l, e, s·t) returns `True` iff accept(p, n, r, l, e, s) and accept(p, n, r, l, e, t) also return `True`. Then, assuming m_i = l_i^e_i, with l_i prime, the output also satisfies - accept(p, n, r, l_i, e_i, r_i) returns `True` for any i. The obvious use case for `accept` is to ensure that a specific element of ℤ/m has order r (or divisible by r), instead of any element, e.g. p in the case of Rains' algorithm. ## Algorithm The algorithm starts by factoring r into prime powers r_i. For each r_i, it finds the smallest prime power m_i = l_i^e_i such that r_i|φ(m_i) and `accept(p, n, r, l_i, e_i, r_i)` returns `True` (if `accept` is provided). At this point, the lcm of all the m_i is an acceptable value for m, although not necessarily the smallest one. More generally, let r_{1,...,s} = r₁ · r₂ ··· r_s for some subset of the r_i (up to renumbering), then the lcm of m₁, ..., m_s is an acceptable value for r_{1,...,s}, although not necessarily the smallest one. Call m_{1,...,s} this optimal value. The algorithm goes on by computing the optimal values m_X for larger and larger subsets X ⊂ {r_i}, until r is reached. This is done by testing all the possible prime powers between the largest m_Y already computed for any proper subset Y ⊂ X, and the smallest lcm(m_W, m_Z) for any proper partition X = W ∪ Z with W ∩ Z = ∅. For any given r' = r_{1,...,s}, the algorithm looks for primes of the form k·r' + 1. If r_s = l_s^e_s is the largest prime factor of r' and if r₁ · r₂ ··· r_{s-1} | l_s - 1 the algorithm also looks for powers of l_s. It is easy to show that the algorithm needs consider no other integer. ### Example Let r = 60 = 4·3·5 The algorithm starts by computing m_{4} = 1·4 + 1 = 5, m_{3} = 2·3 + 1 = 7, m_{5} = 2·5 + 1 = 11. Then it considers all possible pairs of factors. For {4,3}, one possible value is 5·7 = 35. 2 divides 3-1, hence the algorithm looks for primes and powers of 3 comprised between 7 and 35. It finds that 13 is a suitable value, hence m_{4,3} = 1·12 + 1 = 13, and similarly m_{4,5} = 25, m_{3,5} = 31. Finally, it considers all three factors. It is useless to test prime powers below 31, because 3 and 5 must divide them. There are three possible partitions of {4,3,5} into two disjoints sets: lcm(m_{4}, m_{3,5}) = 155 lcm(m_{3}, m_{4,5}) = 175 lcm(m_{5}, m_{4,3}) = 143 hence 143 is an acceptable value, and the algorithm needs to test no further. The first value tried is 1·60 + 1 = 61, and it turns out it is a prime, hence the algorithm returns [(61, 60)]. ### Complexity The complexity is obviously polynomial in r. Heuristically, it should be sublinear, the dominating step being the factorization of r, but it is not so easy to prove it. If c is the number of primary factors of r, the main loop is executed 2^c times, which is clearly sublinear in r. At each iteration, all partitions of the current subset into two disjoint subsets must be considered, hence a very crude lower bound for this combinatorial step is ∑_{i=1}^{c} binom(c,i) (2^{i-1} - 1) < 3^c << e^{o(log r)} The most expensive operation of each cycle is the primality testing (and, eventually, the `accept` function). Heuristically, at each iteration O(log r) primality tests are needed, each with a polynomial cost in log r. As noted in `find_root_order`, the best bounds under GRH give O(r^{1.4 + ε}) primality tests, instead. Whatever the provable complexity is, this algorithm is extremely fast in practice, and can handle sizes which are way beyond the tractability of the other steps of Rains' algorithm. ''' # Degrees of the the ambient fields. ngcd, nlcm = n # Actual extension degree within the ambient fields if r == 0: r = ngcd # If accept is not given, always accept if accept is None: accept = lambda p, n, r, l, e, s: True class factorization: ''' This class represents the factorization of an integer, carrying one more piece piece of information along each primary factor: factors are pairs (p^e, o), with o an integer. ''' def __init__(self, f): ''' f must be a dictionary with entries of the form l : (e, o) ''' self.factors = f def lcm(self, other): ''' Compute the lcm of two factorizations. The auxiliary information is multiplied together: lcm ( (l^e, s), (l^d, t) ) = (l^max(e,d), s·t) ''' lcm = self.factors.copy() for (l, (e, s)) in other.factors.iteritems(): try: E, S = lcm[l] lcm[l] = (max(E, e), S * s) except KeyError: lcm[l] = (e, s) return factorization(lcm) @cached_method def expand(self): 'Return the integer represented by this factorization' return prod(map(lambda (l, (e, _)): l**e, self.factors.items())) def __str__(self): return ' * '.join('%d^%d<--%d' % (l, e, s) for (l, (e, s)) in self.factors.iteritems()) def __repr__(self): return 'factorization(%s)' % repr(self.factors) # Represent the factorization of r as a set of primary factors fact = Set(list(r.factor())) # This dictionary holds the values r_X for each subset of `fact` optima = {} # Main loop, execute for each subset `S` ⊂ `fact` # It assumes subsets are enumerated by growing size for S in fact.subsets(): # ignore the empty set if S.is_empty(): continue # A Factorization object corresponding to S Sfact = Factorization(S.list()) # find `L` the greatest prime in `S` L, E = max(S, key=lambda (l, e): l) # the product of the remaining prime powers c = prod(l**e for l, e in S if l != L) # a boolean, True only if it is worth testing powers of `L` in # the sequel powers = (L - 1) % c == 0 # the product of all the factors of `S` s = c * L**E # For singletons, we don't have an upper bound if S.cardinality() == 1: start = 1 end = None # For larger subsets, we compute the minimum and maximum value # to test else: parts = SetPartitions(S, 2) # Start from the largest m_X already computed for any # strict subset of `S` start = max(optima[T].expand() for T in S.subsets() if (not T.is_empty()) and (T != S)) # Stop at the smallest lcm of any 2-partition of `S` end = min((optima[T0].lcm(optima[T1]) for T0, T1 in parts), key=lambda x: x.expand()) # We only consider primes of the form # k·s + 1 # and powers of `L` of the form # k·s + L^E # if `powers` is true. # We determine the starting `k` k = start // s or 1 while True: m = k * s + 1 # Once the upper bound, is reached, it is used with no # further test if end is not None and m >= end.expand(): optima[S] = end break # Test primes elif m.is_prime() and accept(p, n, r, m, 1, Sfact): optima[S] = factorization({m: (1, s)}) break # Test powers of `L` # notice the correction for L = 2 on the second line d = k * c + 1 if (powers and d.is_power_of(L) and (L != 2 or E == 1 or k > 1) and accept(p, n, r, L, E + d.valuation(L), Sfact)): optima[S] = factorization({L: (E + d.valuation(L), s)}) break k += 1 # the last computed m_X is the optimum for r return [(l**e, s, (l - 1) * l**(e - 1) // s) for (l, (e, s)) in optima[fact].factors.iteritems()]