def subfield_from_elements(self, alpha, name=None, polred=True, threshold=None): r""" Return the subfield generated by the elements ``alpha``. INPUT: - ``alpha`` - list of elements in this number field - ``name`` - a name for the generator of the new number field - ``polred`` (boolean, default ``True``) - whether to optimize the generator of the newly created field - ``threshold`` (positive number, default ``None``) - threshold to be passed to the ``do_polred`` function EXAMPLES:: sage: from flatsurf.geometry.subfield import subfield_from_elements sage: x = polygen(QQ) sage: poly = x^4 - 4*x^2 + 1 sage: emb = AA.polynomial_root(poly, RIF(0.51, 0.52)) sage: K.<a> = NumberField(poly, embedding=emb) sage: sqrt2 = -a^3 + 3*a sage: sqrt3 = -a^2 + 2 sage: assert sqrt2 ** 2 == 2 and sqrt3 ** 2 == 3 sage: L, elts, phi = subfield_from_elements(K, [sqrt2, 1 - sqrt2/3]) sage: L Number Field in a0 with defining polynomial x^2 - 2 with a0 = 1.414213562373095? sage: elts [a0, -1/3*a0 + 1] sage: phi Ring morphism: From: Number Field in a0 with defining polynomial x^2 - 2 with a0 = 1.414213562373095? To: Number Field in a with defining polynomial x^4 - 4*x^2 + 1 with a = 0.5176380902050415? Defn: a0 |--> -a^3 + 3*a sage: assert phi(elts[0]) == sqrt2 sage: assert phi(elts[1]) == 1 - sqrt2/3 sage: L, elts, phi = subfield_from_elements(K, [1, sqrt3]) sage: assert phi(elts[0]) == 1 sage: assert phi(elts[1]) == sqrt3 TESTS:: sage: from flatsurf.geometry.subfield import subfield_from_elements sage: x = polygen(QQ) sage: p = x^8 - 12*x^6 + 23*x^4 - 12*x^2 + 1 sage: K.<a> = NumberField(p) sage: sqrt2 = 6/7*a^7 - 71/7*a^5 + 125/7*a^3 - 43/7*a sage: sqrt3 = 3/7*a^6 - 32/7*a^4 + 24/7*a^2 + 10/7 sage: sqrt5 = 8/7*a^6 - 90/7*a^4 + 120/7*a^2 - 27/7 sage: assert sqrt2**2 == 2 and sqrt3**2 == 3 and sqrt5**2 == 5 sage: L, elts, phi = subfield_from_elements(K, [sqrt2, sqrt3], name='b') sage: assert phi(elts[0]) == sqrt2 sage: assert phi(elts[1]) == sqrt3 sage: L, elts, phi = subfield_from_elements(K, [sqrt2, sqrt5], name='b') sage: assert phi(elts[0]) == sqrt2 sage: assert phi(elts[1]) == sqrt5 sage: L, elts, phi = subfield_from_elements(K, [sqrt3, sqrt5], name='b') sage: assert phi(elts[0]) == sqrt3 sage: assert phi(elts[1]) == sqrt5 sage: L, elts, phi = subfield_from_elements(K, [-149582/214245 + 21423/5581*sqrt2], name='b') sage: assert L.polynomial() == x^2 - 2 sage: L, elts, phi = subfield_from_elements(K, [131490/777 - 1359/22*sqrt3], name='b') sage: assert L.polynomial() == x^2 - 3 sage: L, elts, phi = subfield_from_elements(K, [12241829/177 - 321121/22459 * sqrt5], name='b') sage: assert L.polynomial() == x^2 - x - 1 sage: from sage.rings.qqbar import number_field_elements_from_algebraics sage: R.<x> = QQ[] sage: p1 = x^3 - x - 1 sage: roots1 = p1.roots(QQbar, False) sage: for _ in range(10): ....: p2 = R.random_element(degree=2) ....: while not p2.is_irreducible(): p2 = R.random_element(degree=2) ....: roots2 = p2.roots(QQbar, False) ....: K, (a1,b1,c1,a2,b2), phi = number_field_elements_from_algebraics(roots1 + roots2) ....: u1 = 1 - a1/17 + 3/7*a1**2 ....: u2 = 2 + 33/35 * a1 ....: L, (v1,v2), phi = subfield_from_elements(K, [u1, u2], threshold=100) ....: assert L.polynomial() == p1 ....: assert phi(v1) == u1 and phi(v2) == u2 """ V = VectorSpace(QQ, self.degree()) alpha = [self(a) for a in alpha] # Rational case if all(a.is_rational() for a in alpha): return (QQ, [QQ(a) for a in alpha], self.coerce_map_from(QQ)) # Saturate with multiplication vecs = [a.vector() for a in alpha] U = V.subspace(vecs) modified = True while modified: modified = False d = U.dimension() if d == self.degree(): return (self, alpha, Hom(self, self, Fields()).identity()) B = U.basis() for i in range(d): for j in range(i, d): v = (self(B[i]) * self(B[j])).vector() if v not in U: U += V.subspace([v]) modified = True # Strict subfield, find a generator vgen = None for b in U.basis(): if self(b).minpoly().degree() == d: vgen = b break if vgen is None: s = 1 while True: vgen = U.random_element(proba=0.5, x=-s, y=s) if self(vgen).minpoly().degree() == d: break s *= 2 # Minimize the generator via PARI polred gen = self(vgen) p = gen.minpoly() if polred: if threshold: fwd, back, q = do_polred(p, threshold) else: fwd, back, q = do_polred(p) else: q = p fwd = back = self.polynomial_ring().gen() new_gen = fwd(gen) assert new_gen.minpoly() == q K, hom = self.subfield(new_gen, name=name) # need to express the elements in the basis 1, a, a^2, ..., a^(d-1) M = matrix(QQ, [(new_gen**i).vector() for i in range(d)]) new_alpha = [K(M.solve_left(elt.vector())) for elt in alpha] return (K, new_alpha, hom)
def __getitem__(self, a): if a == 1: return 1 / (1 - x) else: return QQ(1 - a).sign() / (x - a)
def to_sage(self): """ EXAMPLES: sage: macaulay2(ZZ).to_sage() #optional Integer Ring sage: macaulay2(QQ).to_sage() #optional Rational Field sage: macaulay2(2).to_sage() #optional 2 sage: macaulay2(1/2).to_sage() #optional 1/2 sage: macaulay2(2/1).to_sage() #optional 2 sage: _.parent() #optional Rational Field sage: macaulay2([1,2,3]).to_sage() #optional [1, 2, 3] sage: m = matrix([[1,2],[3,4]]) sage: macaulay2(m).to_sage() #optional [1 2] [3 4] sage: macaulay2(QQ['x,y']).to_sage() #optional Multivariate Polynomial Ring in x, y over Rational Field sage: macaulay2(QQ['x']).to_sage() #optional Univariate Polynomial Ring in x over Rational Field sage: macaulay2(GF(7)['x,y']).to_sage() #optional Multivariate Polynomial Ring in x, y over Finite Field of size 7 sage: macaulay2(GF(7)).to_sage() #optional Finite Field of size 7 sage: macaulay2(GF(49, 'a')).to_sage() #optional Finite Field in a of size 7^2 sage: R.<x,y> = QQ[] sage: macaulay2(x^2+y^2+1).to_sage() #optional x^2 + y^2 + 1 sage: R = macaulay2("QQ[x,y]") #optional sage: I = macaulay2("ideal (x,y)") #optional sage: I.to_sage() #optional Ideal (x, y) of Multivariate Polynomial Ring in x, y over Rational Field sage: X = R/I #optional sage: X.to_sage() #optional Quotient of Multivariate Polynomial Ring in x, y over Rational Field by the ideal (x, y) sage: R = macaulay2("QQ^2") #optional sage: R.to_sage() #optional Vector space of dimension 2 over Rational Field sage: m = macaulay2('"hello"') #optional sage: m.to_sage() #optional 'hello' """ repr_str = str(self) cls_str = str(self.cls()) cls_cls_str = str(self.cls().cls()) if repr_str == "ZZ": from sage.rings.all import ZZ return ZZ elif repr_str == "QQ": from sage.rings.all import QQ return QQ if cls_cls_str == "Type": if cls_str == "List": return [entry.to_sage() for entry in self] elif cls_str == "Matrix": from sage.matrix.all import matrix base_ring = self.ring().to_sage() entries = self.entries().to_sage() return matrix(base_ring, entries) elif cls_str == "Ideal": parent = self.ring().to_sage() gens = self.gens().entries().flatten().to_sage() return parent.ideal(*gens) elif cls_str == "QuotientRing": #Handle the ZZ/n case if "ZZ" in repr_str and "--" in repr_str: from sage.rings.all import ZZ, GF external_string = self.external_string() zz, n = external_string.split("/") #Note that n must be prime since it is #coming from Macaulay 2 return GF(ZZ(n)) ambient = self.ambient().to_sage() ideal = self.ideal().to_sage() return ambient.quotient(ideal) elif cls_str == "PolynomialRing": from sage.rings.all import PolynomialRing from sage.rings.polynomial.term_order import inv_macaulay2_name_mapping #Get the base ring base_ring = self.coefficientRing().to_sage() #Get a string list of generators gens = str(self.gens())[1:-1] # Check that we are dealing with default degrees, i.e. 1's. if self.degrees().any("x -> x != {1}").to_sage(): raise ValueError, "cannot convert Macaulay2 polynomial ring with non-default degrees to Sage" #Handle the term order external_string = self.external_string() order = None if "MonomialOrder" not in external_string: order = "degrevlex" else: for order_name in inv_macaulay2_name_mapping: if order_name in external_string: order = inv_macaulay2_name_mapping[order_name] if len(gens) > 1 and order is None: raise ValueError, "cannot convert Macaulay2's term order to a Sage term order" return PolynomialRing(base_ring, order=order, names=gens) elif cls_str == "GaloisField": from sage.rings.all import ZZ, GF gf, n = repr_str.split(" ") n = ZZ(n) if n.is_prime(): return GF(n) else: gen = str(self.gens())[1:-1] return GF(n, gen) elif cls_str == "Boolean": if repr_str == "true": return True elif repr_str == "false": return False elif cls_str == "String": return str(repr_str) elif cls_str == "Module": from sage.modules.all import FreeModule if self.isFreeModule().to_sage(): ring = self.ring().to_sage() rank = self.rank().to_sage() return FreeModule(ring, rank) else: #Handle the integers and rationals separately if cls_str == "ZZ": from sage.rings.all import ZZ return ZZ(repr_str) elif cls_str == "QQ": from sage.rings.all import QQ repr_str = self.external_string() if "/" not in repr_str: repr_str = repr_str + "/1" return QQ(repr_str) m2_parent = self.cls() parent = m2_parent.to_sage() if cls_cls_str == "PolynomialRing": from sage.misc.sage_eval import sage_eval gens_dict = parent.gens_dict() return sage_eval(self.external_string(), gens_dict) from sage.misc.sage_eval import sage_eval try: return sage_eval(repr_str) except Exception: raise NotImplementedError, "cannot convert %s to a Sage object" % repr_str
def enumerate_totallyreal_fields_all(n, B, verbose=0, return_seqs=False, return_pari_objects=True): r""" Enumerates *all* totally real fields of degree ``n`` with discriminant at most ``B``, primitive or otherwise. INPUT: - ``n`` -- integer, the degree - ``B`` -- integer, the discriminant bound - ``verbose`` -- boolean or nonnegative integer or string (default: 0) give a verbose description of the computations being performed. If ``verbose`` is set to ``2`` or more then it outputs some extra information. If ``verbose`` is a string then it outputs to a file specified by ``verbose`` - ``return_seqs`` -- (boolean, default False) If ``True``, then return the polynomials as sequences (for easier exporting to a file). This also returns a list of four numbers, as explained in the OUTPUT section below. - ``return_pari_objects`` -- (boolean, default: True) if both ``return_seqs`` and ``return_pari_objects`` are ``False`` then it returns the elements as Sage objects; otherwise it returns pari objects. EXAMPLES:: sage: enumerate_totallyreal_fields_all(4, 2000) [[725, x^4 - x^3 - 3*x^2 + x + 1], [1125, x^4 - x^3 - 4*x^2 + 4*x + 1], [1600, x^4 - 6*x^2 + 4], [1957, x^4 - 4*x^2 - x + 1], [2000, x^4 - 5*x^2 + 5]] sage: enumerate_totallyreal_fields_all(1, 10) [[1, x - 1]] TESTS: Each of the outputs must be elements of Sage if ``return_pari_objects`` is set to ``False``:: sage: enumerate_totallyreal_fields_all(2, 10) [[5, x^2 - x - 1], [8, x^2 - 2]] sage: type(enumerate_totallyreal_fields_all(2, 10)[0][1]) <type 'sage.libs.cypari2.gen.Gen'> sage: enumerate_totallyreal_fields_all(2, 10, return_pari_objects=False)[0][1].parent() Univariate Polynomial Ring in x over Rational Field In practice most of these will be found by :func:`~sage.rings.number_field.totallyreal.enumerate_totallyreal_fields_prim`, which is guaranteed to return all primitive fields but often returns many non-primitive ones as well. For instance, only one of the five fields in the example above is primitive, but :func:`~sage.rings.number_field.totallyreal.enumerate_totallyreal_fields_prim` finds four out of the five (the exception being `x^4 - 6x^2 + 4`). The following was fixed in :trac:`13101`:: sage: enumerate_totallyreal_fields_all(8, 10^6) # long time (about 2 s) [] """ S = [] counts = [0, 0, 0, 0] if len(divisors(n)) > 4: raise ValueError("Only implemented for n = p*q with p,q prime") for d in divisors(n): if d > 1 and d < n: Sds = enumerate_totallyreal_fields_prim( d, int(math.floor((1. * B)**(1. * d / n))), verbose=verbose) for i in range(len(Sds)): if verbose: print("=" * 80) print("Taking F =", Sds[i][1]) F = NumberField(ZZx(Sds[i][1]), 't') T = enumerate_totallyreal_fields_rel(F, n / d, B, verbose=verbose, return_seqs=return_seqs) if return_seqs: for i in range(4): counts[i] += T[0][i] S += [[t[0], pari(t[1]).Polrev()] for t in T[1]] else: S += [[t[0], t[1]] for t in T] j = i + 1 for E in enumerate_totallyreal_fields_prim( n / d, int( math.floor((1. * B)**(1. / d) / (1. * Sds[i][0])**(n * 1. / d**2)))): for EF in F.composite_fields(NumberField(ZZx(E[1]), 'u')): if EF.degree() == n and EF.disc() <= B: S.append( [EF.disc(), pari(EF.absolute_polynomial())]) S += enumerate_totallyreal_fields_prim(n, B, verbose=verbose) S.sort(cmp=lambda x, y: cmp(x[0], y[0]) or cmp(x[1], y[1])) weed_fields(S) # Output. if verbose: saveout = sys.stdout if isinstance(verbose, str): fsock = open(verbose, 'w') sys.stdout = fsock # Else, print to screen print("=" * 80) print("Polynomials tested: {}".format(counts[0])) print("Polynomials with discriminant with large enough square" " divisor: {}".format(counts[1])) print("Irreducible polynomials: {}".format(counts[2])) print("Polynomials with nfdisc <= B: {}".format(counts[3])) for i in range(len(S)): print(S[i]) if isinstance(verbose, str): fsock.close() sys.stdout = saveout # Make sure to return elements that belong to Sage if return_seqs: return [[ZZ(_) for _ in counts], [[ZZ(s[0]), [QQ(_) for _ in s[1].polrecip().Vec()]] for s in S]] elif return_pari_objects: return S else: Px = PolynomialRing(QQ, 'x') return [[ZZ(s[0]), Px([QQ(_) for _ in s[1].list()])] for s in S]
def next_state(self, val): r""" Build the next state for type `A_{2n}^{(2)\dagger}`. TESTS:: sage: KRT = crystals.TensorProductOfKirillovReshetikhinTableaux(CartanType(['A', 4, 2]).dual(), [[2,1]]) sage: from sage.combinat.rigged_configurations.bij_type_A2_dual import KRTToRCBijectionTypeA2Dual sage: bijection = KRTToRCBijectionTypeA2Dual(KRT(pathlist=[[-1,2]])) sage: bijection.cur_path.insert(0, []) sage: bijection.cur_dims.insert(0, [0, 1]) sage: bijection.cur_path[0].insert(0, [2]) sage: bijection.next_state(2) """ n = self.n tableau_height = len(self.cur_path[0]) - 1 if val > 0: # If it is a regular value, we follow the A_n rules KRTToRCBijectionTypeA.next_state(self, val) return pos_val = -val if pos_val == 0: if len(self.ret_rig_con[pos_val - 1]) > 0: max_width = self.ret_rig_con[n - 1][0] else: max_width = 1 max_width = self.ret_rig_con[n - 1].insert_cell(max_width) width_n = max_width + 1 # Follow regular A_n rules for a in reversed(range(tableau_height, n - 1)): max_width = self.ret_rig_con[a].insert_cell(max_width) self._update_vacancy_nums(a + 1) self._update_partition_values(a + 1) self._update_vacancy_nums(tableau_height) self._update_partition_values(tableau_height) if tableau_height > 0: self._update_vacancy_nums(tableau_height - 1) self._update_partition_values(tableau_height - 1) # Make the new string at n quasi-singular p = self.ret_rig_con[n - 1] for i in range(len(p)): if p._list[i] == width_n: p.rigging[i] = p.rigging[i] - QQ(1) / QQ(2) break return case_S = [None] * n pos_val = -val # Always add a cell to the first singular value in the first # tableau we are updating. if len(self.ret_rig_con[pos_val - 1]) > 0: max_width = self.ret_rig_con[pos_val - 1][0] else: max_width = 1 # Add cells similar to type A_n but we move to the right until we # reach the value of n-1 for a in range(pos_val - 1, n - 1): max_width = self.ret_rig_con[a].insert_cell(max_width) case_S[a] = max_width # Special case for n # If we find a quasi-singular string first, then we are in case (Q, S) # otherwise we will find a singular string and insert 2 cells partition = self.ret_rig_con[n - 1] num_rows = len(partition) case_QS = False for i in range(num_rows + 1): if i == num_rows: max_width = 0 if case_QS: partition._list.append(1) partition.vacancy_numbers.append(None) # Go through our partition until we find a length of greater than 1 j = len(partition._list) - 1 while j >= 0 and partition._list[j] == 1: j -= 1 partition.rigging.insert(j + 1, None) width_n = 1 else: # Go through our partition until we find a length of greater than 2 j = len(partition._list) - 1 while j >= 0 and partition._list[j] <= 2: j -= 1 partition._list.insert(j + 1, 2) partition.vacancy_numbers.insert(j + 1, None) partition.rigging.insert(j + 1, None) break elif partition._list[i] <= max_width: if partition.vacancy_numbers[i] == partition.rigging[i]: max_width = partition._list[i] if case_QS: partition._list[i] += 1 width_n = partition._list[i] partition.rigging[i] = None else: j = i - 1 while j >= 0 and partition._list[j] <= max_width + 2: partition.rigging[j + 1] = partition.rigging[ j] # Shuffle it along j -= 1 partition._list.pop(i) partition._list.insert(j + 1, max_width + 2) partition.rigging[j + 1] = None break elif partition.vacancy_numbers[i] - QQ(1) / QQ( 2) == partition.rigging[i] and not case_QS: case_QS = True partition._list[i] += 1 partition.rigging[i] = None # No need to set max_width here since we will find a singular string # Now go back following the regular C_n (ish) rules for a in reversed(range(tableau_height, n - 1)): if case_S[a] == max_width: self._insert_cell_case_S(self.ret_rig_con[a]) else: max_width = self.ret_rig_con[a].insert_cell(max_width) self._update_vacancy_nums(a + 1) self._update_partition_values(a + 1) # Update the final rigged partitions if tableau_height < n: self._update_vacancy_nums(tableau_height) self._update_partition_values(tableau_height) if pos_val <= tableau_height: for a in range(pos_val - 1, tableau_height): self._update_vacancy_nums(a) self._update_partition_values(a) if pos_val > 1: self._update_vacancy_nums(pos_val - 2) self._update_partition_values(pos_val - 2) elif tableau_height > 0: self._update_vacancy_nums(tableau_height - 1) self._update_partition_values(tableau_height - 1) if case_QS: # Make the new string quasi-singular num_rows = len(partition) for i in range(num_rows): if partition._list[i] == width_n: partition.rigging[i] = partition.rigging[i] - QQ(1) / QQ(2) break
def multiple_of_order(self, maxp=None, proof=True): """ Return a multiple of the order. INPUT: - ``proof`` -- a boolean (default: True) The computation of the rational torsion order of J1(p) is conjectural and will only be used if proof=False. See Section 6.2.3 of [CES2003]_. EXAMPLES:: sage: J = J1(11); J Abelian variety J1(11) of dimension 1 sage: J.rational_torsion_subgroup().multiple_of_order() 5 sage: J = J0(17) sage: J.rational_torsion_subgroup().order() 4 This is an example where proof=False leads to a better bound and better performance. :: sage: J = J1(23) sage: J.rational_torsion_subgroup().multiple_of_order() # long time (2s) 9406793 sage: J.rational_torsion_subgroup().multiple_of_order(proof=False) 408991 """ try: if proof: return self._multiple_of_order else: return self._multiple_of_order_proof_false except: pass A = self.abelian_variety() N = A.level() if A.dimension() == 0: self._multiple_of_order = ZZ(1) self._multiple_of_order_proof_false = self._multiple_of_order return self._multiple_of_order # return the order of the cuspidal subgroup in the J0(p) case if A.is_J0() and N.is_prime(): self._multiple_of_order = QQ((A.level() - 1) / 12).numerator() self._multiple_of_order_proof_false = self._multiple_of_order return self._multiple_of_order # The elliptic curve case if A.dimension() == 1: self._multiple_of_order = A.elliptic_curve().torsion_order() self._multiple_of_order_proof_false = self._multiple_of_order return self._multiple_of_order # The conjectural J1(p) case if not proof and A.is_J1() and N.is_prime(): epsilons = [ epsilon for epsilon in DirichletGroup(N) if not epsilon.is_trivial() and epsilon.is_even() ] bernoullis = [epsilon.bernoulli(2) for epsilon in epsilons] self._multiple_of_order_proof_false = ZZ(N / (2**(N - 3)) * prod(bernoullis)) return self._multiple_of_order_proof_false # The Gamma0 and Gamma1 case if all((is_Gamma0(G) or is_Gamma1(G) for G in A.groups())): self._multiple_of_order = self.multiple_of_order_using_frobp() return self._multiple_of_order # Unhandled case raise NotImplementedError("No implemented algorithm")
def descend_to(self, K, f=None): r""" Given an elliptic curve self defined over a field `L` and a subfield `K` of `L`, return all elliptic curves over `K` which are isomorphic over `L` to self. INPUT: - `K` -- a field which embeds into the base field `L` of self. - `f` (optional) -- an embedding of `K` into `L`. Ignored if `K` is `\QQ`. OUTPUT: A list (possibly empty) of elliptic curves defined over `K` which are isomorphic to self over `L`, up to isomorphism over `K`. .. NOTE:: Currently only implemented over number fields. To extend to other fields of characteristic not 2 or 3, what is needed is a method giving the preimages in `K^*/(K^*)^m` of an element of the base field, for `m=2,4,6`. EXAMPLES:: sage: E = EllipticCurve([1,2,3,4,5]) sage: E.descend_to(ZZ) Traceback (most recent call last): ... TypeError: Input must be a field. :: sage: F.<b> = QuadraticField(23) sage: G.<a> = F.extension(x^3+5) sage: E = EllipticCurve(j=1728*b).change_ring(G) sage: EF = E.descend_to(F); EF [Elliptic Curve defined by y^2 = x^3 + (27*b-621)*x + (-1296*b+2484) over Number Field in b with defining polynomial x^2 - 23] sage: all([Ei.change_ring(G).is_isomorphic(E) for Ei in EF]) True :: sage: L.<a> = NumberField(x^4 - 7) sage: K.<b> = NumberField(x^2 - 7, embedding=a^2) sage: E = EllipticCurve([a^6,0]) sage: EK = E.descend_to(K); EK [Elliptic Curve defined by y^2 = x^3 + b*x over Number Field in b with defining polynomial x^2 - 7, Elliptic Curve defined by y^2 = x^3 + 7*b*x over Number Field in b with defining polynomial x^2 - 7] sage: all([Ei.change_ring(L).is_isomorphic(E) for Ei in EK]) True :: sage: K.<a> = QuadraticField(17) sage: E = EllipticCurve(j = 2*a) sage: E.descend_to(QQ) [] TESTS: Check that :trac:`16456` is fixed:: sage: K.<a> = NumberField(x^3-2) sage: E = EllipticCurve('11a1').quadratic_twist(2) sage: EK = E.change_ring(K) sage: EK2 = EK.change_weierstrass_model((a,a,a,a+1)) sage: EK2.descend_to(QQ) [Elliptic Curve defined by y^2 = x^3 + x^2 - 41*x - 199 over Rational Field] sage: k.<i> = QuadraticField(-1) sage: E = EllipticCurve(k,[0,0,0,1,0]) sage: E.descend_to(QQ) [Elliptic Curve defined by y^2 = x^3 + x over Rational Field, Elliptic Curve defined by y^2 = x^3 - 4*x over Rational Field] """ if not K.is_field(): raise TypeError("Input must be a field.") L = self.base_field() if L is K: return self elif L == K: # number fields can be equal but not identical return self.base_extend(K) # Construct an embedding f of K in L, and check that the # j-invariant is in the image, otherwise return an empty list: j = self.j_invariant() from sage.rings.all import QQ if K == QQ: try: jK = QQ(j) except (ValueError, TypeError): return [] elif f is None: embeddings = K.embeddings(L) if len(embeddings) == 0: raise TypeError( "Input must be a subfield of the base field of the curve.") for g in embeddings: try: jK = g.preimage(j) f = g break except Exception: pass if f is None: return [] else: try: if f.domain() != K: raise ValueError("embedding has wrong domain") if f.codomain() != L: raise ValueError("embedding has wrong codomain") except AttributeError: raise ValueError("invalid embedding: %s" % s) try: jK = f.preimage(j) except Exception: return [] # Now we have the j-invariant in K and must find all twists # which work, separating the cases of j=0 and j=1728. if L.characteristic(): raise NotImplementedError( "Not implemented in positive characteristic") if jK == 0: t = -54 * self.c6() try: dlist = t.descend_mod_power(K, 6) # list of d in K such that t/d is in L*^6 except AttributeError: raise NotImplementedError("Not implemented over %s" % L) Elist = [EllipticCurve([0, 0, 0, 0, d]) for d in dlist] elif jK == 1728: t = -27 * self.c4() try: dlist = t.descend_mod_power(K, 4) # list of d in K such that t/d is in L*^4 except AttributeError: raise NotImplementedError("Not implemented over %s" % L) Elist = [EllipticCurve([0, 0, 0, d, 0]) for d in dlist] else: c4, c6 = self.c_invariants() t = c6 / c4 try: dlist = t.descend_mod_power(K, 2) # list of d in K such that t/d is in L*^2 except AttributeError: raise NotImplementedError("Not implemented over %s" % L) c = -27 * jK / (jK - 1728) # =-27c4^3/c6^2 a4list = [c * d**2 for d in dlist] a6list = [2 * a4 * d for a4, d in zip(a4list, dlist)] Elist = [ EllipticCurve([0, 0, 0, a4, a6]) for a4, a6 in zip(a4list, a6list) ] if K is QQ: Elist = [E.minimal_model() for E in Elist] return Elist
def FormsSpace(analytic_type, group=3, base_ring=ZZ, k=QQ(0), ep=None): r""" Return the FormsSpace with the given ``analytic_type``, ``group`` ``base_ring`` and degree (``k``, ``ep``). INPUT: - ``analytic_type`` -- An element of ``AnalyticType()`` describing the analytic type of the space. - ``group`` -- The index of the (Hecke triangle) group of the space (default: `3`). - ``base_ring`` -- The base ring of the space (default: ``ZZ``). - ``k`` -- The weight of the space, a rational number (default: ``0``). - ``ep`` -- The multiplier of the space, `1`, `-1` or ``None`` (in case ``ep`` should be determined from ``k``). Default: ``None``. For the variables ``group``, ``base_ring``, ``k``, ``ep`` the same arguments as for the class ``FormsSpace_abstract`` can be used. The variables will then be put in canonical form. In particular the multiplier ``ep`` is calculated as usual from ``k`` if ``ep == None``. OUTPUT: The FormsSpace with the given properties. EXAMPLES:: sage: from sage.modular.modform_hecketriangle.constructor import FormsSpace sage: FormsSpace([]) ZeroForms(n=3, k=0, ep=1) over Integer Ring sage: FormsSpace(["quasi"]) # not implemented sage: FormsSpace("cusp", group=5, base_ring=CC, k=12, ep=1) CuspForms(n=5, k=12, ep=1) over Complex Field with 53 bits of precision sage: FormsSpace("holo") ModularForms(n=3, k=0, ep=1) over Integer Ring sage: FormsSpace("weak", group=6, base_ring=ZZ, k=0, ep=-1) WeakModularForms(n=6, k=0, ep=-1) over Integer Ring sage: FormsSpace("mero", group=7, base_ring=ZZ, k=2, ep=-1) MeromorphicModularForms(n=7, k=2, ep=-1) over Integer Ring sage: FormsSpace(["quasi", "cusp"], group=5, base_ring=CC, k=12, ep=1) QuasiCuspForms(n=5, k=12, ep=1) over Complex Field with 53 bits of precision sage: FormsSpace(["quasi", "holo"]) QuasiModularForms(n=3, k=0, ep=1) over Integer Ring sage: FormsSpace(["quasi", "weak"], group=6, base_ring=ZZ, k=0, ep=-1) QuasiWeakModularForms(n=6, k=0, ep=-1) over Integer Ring sage: FormsSpace(["quasi", "mero"], group=7, base_ring=ZZ, k=2, ep=-1) QuasiMeromorphicModularForms(n=7, k=2, ep=-1) over Integer Ring sage: FormsSpace(["quasi", "cusp"], group=infinity, base_ring=ZZ, k=2, ep=-1) QuasiCuspForms(n=+Infinity, k=2, ep=-1) over Integer Ring """ from .space import canonical_parameters (group, base_ring, k, ep, n) = canonical_parameters(group, base_ring, k, ep) from .analytic_type import AnalyticType AT = AnalyticType() analytic_type = AT(analytic_type) if analytic_type <= AT("mero"): if analytic_type <= AT("weak"): if analytic_type <= AT("holo"): if analytic_type <= AT("cusp"): if analytic_type <= AT([]): from .space import ZeroForm return ZeroForm(group=group, base_ring=base_ring, k=k, ep=ep) else: from .space import CuspForms return CuspForms(group=group, base_ring=base_ring, k=k, ep=ep) else: from .space import ModularForms return ModularForms(group=group, base_ring=base_ring, k=k, ep=ep) else: from .space import WeakModularForms return WeakModularForms(group=group, base_ring=base_ring, k=k, ep=ep) else: from .space import MeromorphicModularForms return MeromorphicModularForms(group=group, base_ring=base_ring, k=k, ep=ep) elif analytic_type <= AT(["mero", "quasi"]): if analytic_type <= AT(["weak", "quasi"]): if analytic_type <= AT(["holo", "quasi"]): if analytic_type <= AT(["cusp", "quasi"]): if analytic_type <= AT(["quasi"]): raise ValueError("There should be only non-quasi ZeroForms. That could be changed but then this exception should be removed.") from .space import ZeroForm return ZeroForm(group=group, base_ring=base_ring, k=k, ep=ep) else: from .space import QuasiCuspForms return QuasiCuspForms(group=group, base_ring=base_ring, k=k, ep=ep) else: from .space import QuasiModularForms return QuasiModularForms(group=group, base_ring=base_ring, k=k, ep=ep) else: from .space import QuasiWeakModularForms return QuasiWeakModularForms(group=group, base_ring=base_ring, k=k, ep=ep) else: from .space import QuasiMeromorphicModularForms return QuasiMeromorphicModularForms(group=group, base_ring=base_ring, k=k, ep=ep) else: raise NotImplementedError("Analytic type not implemented.")
def neighbor_iteration(seeds, p, mass=None, max_classes=ZZ(10)**3, algorithm=None, max_neighbors=1000, verbose=False): r""" Return all classes in the `p`-neighbor graph of ``self``. Starting from the given seeds, this function successively finds p-neighbors untill no new quadratic form (class) is obtained. INPUT: - ``seeds`` -- a list of quadratic forms in the same genus - ``p`` -- a prime number - ``mass`` -- (optional) a rational number; the mass of this genus - ``max_classes`` -- (default: ``1000``) break the computation when ``max_classes`` are found - ``algorithm`` -- (optional) one of 'orbits', 'random', 'exaustion' - ``max_random_trys`` -- (default: ``1000``) the maximum number of neigbors computed for a single lattice OUTPUT: - a list of quadratic forms EXAMPLES:: sage: from sage.quadratic_forms.quadratic_form__neighbors import neighbor_iteration sage: Q = QuadraticForm(ZZ,3,[1,0,0,2,1,3]) sage: Q.det() 46 sage: mass = Q.conway_mass() sage: g1 = neighbor_iteration([Q],3, mass=mass, algorithm = 'random') # long time sage: g2 = neighbor_iteration([Q],3, algorithm = 'exaustion') # long time sage: g3 = neighbor_iteration([Q],3, algorithm = 'orbits') sage: mass == sum(1/q.number_of_automorphisms() for q in g1) # long time True sage: mass == sum(1/q.number_of_automorphisms() for q in g2) # long time True sage: mass == sum(1/q.number_of_automorphisms() for q in g3) True """ p = ZZ(p) from sage.quadratic_forms.quadratic_form import QuadraticForm if not all(isinstance(s, QuadraticForm) for s in seeds): raise ValueError("seeds must be a list of quadratic forms") if algorithm is None: n = seeds[0].dim() if p**n > ZZ(2)**18: # too many lines to compute the orbits fast algorithm = 'random' else: algorithm = 'orbits' if mass is None: # no mass bound mass = max_classes if algorithm == 'orbits': def p_divisible_vectors(Q, max_neighbors): yield from iter(v.lift() for v in Q.orbits_lines_mod_p(p) if v != 0 and Q(v.lift()).valuation(p) > 0) return elif algorithm == 'exaustion': def p_divisible_vectors(Q, max_neighbors): k = 0 v = Q.find_primitive_p_divisible_vector__next(p) while k < max_neighbors: k = k + 1 v = Q.find_primitive_p_divisible_vector__next(p, v) if v is not None: yield v elif algorithm == 'random': def p_divisible_vectors(Q, max_neighbors): k = 0 while k < max_neighbors: k = k + 1 v = Q.find_primitive_p_divisible_vector__random(p) yield v else: raise ValueError("unknown algorithm") waiting_list = list(seeds) isom_classes = [] mass_count = QQ(0) n_isom_classes = ZZ(0) while len(waiting_list ) > 0 and mass_count < mass and n_isom_classes < max_classes: # find all p-neighbors of Q Q = waiting_list.pop() for v in p_divisible_vectors(Q, max_neighbors): Q_neighbor = Q.find_p_neighbor_from_vec(p, v) if not any( Q_neighbor.is_globally_equivalent_to(S) for S in isom_classes): Q_neighbor = Q_neighbor.lll() isom_classes.append(Q_neighbor) waiting_list.append(Q_neighbor) n_isom_classes += 1 mass_count += Q_neighbor.number_of_automorphisms()**(-1) if verbose: print(max_neighbors) print(len(waiting_list)) if mass_count == mass and n_isom_classes >= max_classes: break if len(isom_classes) >= max_classes: Warning( "reached the maximum number of isometry classes=%s. Increase the optional argument max_classes to obtain more." % max_classes) if mass is not None: assert mass_count <= mass if mass < mass_count: raise Warning("not all classes in the genus were found") return isom_classes
def descend_to(self, K, f=None): r""" Given a subfield `K` and an elliptic curve self defined over a field `L`, this function determines whether there exists an elliptic curve over `K` which is isomorphic over `L` to self. If one exists, it finds it. INPUT: - `K` -- a subfield of the base field of self. - `f` -- an embedding of `K` into the base field of self. OUTPUT: Either an elliptic curve defined over `K` which is isomorphic to self or None if no such curve exists. .. NOTE:: This only works over number fields and QQ. EXAMPLES:: sage: E = EllipticCurve([1,2,3,4,5]) sage: E.descend_to(ZZ) Traceback (most recent call last): ... TypeError: Input must be a field. :: sage: F.<b> = QuadraticField(23) sage: G.<a> = F.extension(x^3+5) sage: E = EllipticCurve(j=1728*b).change_ring(G) sage: E.descend_to(F) Elliptic Curve defined by y^2 = x^3 + (8957952*b-206032896)*x + (-247669456896*b+474699792384) over Number Field in b with defining polynomial x^2 - 23 :: sage: L.<a> = NumberField(x^4 - 7) sage: K.<b> = NumberField(x^2 - 7) sage: E = EllipticCurve([a^6,0]) sage: E.descend_to(K) Elliptic Curve defined by y^2 = x^3 + 1296/49*b*x over Number Field in b with defining polynomial x^2 - 7 :: sage: K.<a> = QuadraticField(17) sage: E = EllipticCurve(j = 2*a) sage: print E.descend_to(QQ) None """ if not K.is_field(): raise TypeError, "Input must be a field." if self.base_field() == K: return self j = self.j_invariant() from sage.rings.all import QQ if K == QQ: f = QQ.embeddings(self.base_field())[0] if j in QQ: jbase = QQ(j) else: return None elif f == None: embeddings = K.embeddings(self.base_field()) if len(embeddings) == 0: raise TypeError, "Input must be a subfield of the base field of the curve." for g in embeddings: try: jbase = g.preimage(j) f = g break except StandardError: pass if f == None: return None else: try: jbase = f.preimage(j) except StandardError: return None E = EllipticCurve(j=jbase) E2 = EllipticCurve(self.base_field(), [f(a) for a in E.a_invariants()]) if jbase == 0: d = self.is_sextic_twist(E2) if d == 1: return E if d == 0: return None Etwist = E2.sextic_twist(d) elif jbase == 1728: d = self.is_quartic_twist(E2) if d == 1: return E if d == 0: return None Etwist = E2.quartic_twist(d) else: d = self.is_quadratic_twist(E2) if d == 1: return E if d == 0: return None Etwist = E2.quadratic_twist(d) if Etwist.is_isomorphic(self): try: Eout = EllipticCurve( K, [f.preimage(a) for a in Etwist.a_invariants()]) except StandardError: return None else: return Eout
def rational_type(f, n=ZZ(3), base_ring=ZZ): r""" Return the basic analytic properties that can be determined directly from the specified rational function ``f`` which is interpreted as a representation of an element of a FormsRing for the Hecke Triangle group with parameter ``n`` and the specified ``base_ring``. In particular the following degree of the generators is assumed: `deg(1) := (0, 1)` `deg(x) := (4/(n-2), 1)` `deg(y) := (2n/(n-2), -1)` `deg(z) := (2, -1)` The meaning of homogeneous elements changes accordingly. INPUT: - ``f`` -- A rational function in ``x,y,z,d`` over ``base_ring``. - ``n`` -- An integer greater or equal to `3` corresponding to the ``HeckeTriangleGroup`` with that parameter (default: `3`). - ``base_ring`` -- The base ring of the corresponding forms ring, resp. polynomial ring (default: ``ZZ``). OUTPUT: A tuple ``(elem, h**o, k, ep, analytic_type)`` describing the basic analytic properties of `f` (with the interpretation indicated above). - ``elem`` -- ``True`` if `f` has a homogeneous denominator. - ``h**o`` -- ``True`` if `f` also has a homogeneous numerator. - ``k`` -- ``None`` if `f` is not homogeneous, otherwise the weight of `f` (which is the first component of its degree). - ``ep`` -- ``None`` if `f` is not homogeneous, otherwise the multiplier of `f` (which is the second component of its degree) - ``analytic_type`` -- The ``AnalyticType`` of `f`. For the zero function the degree `(0, 1)` is choosen. This function is (heavily) used to determine the type of elements and to check if the element really is contained in its parent. EXAMPLES:: sage: from sage.modular.modform_hecketriangle.constructor import rational_type sage: (x,y,z,d) = var("x,y,z,d") sage: rational_type(0, n=4) (True, True, 0, 1, zero) sage: rational_type(1, n=12) (True, True, 0, 1, modular) sage: rational_type(x^3 - y^2) (True, True, 12, 1, cuspidal) sage: rational_type(x * z, n=7) (True, True, 14/5, -1, quasi modular) sage: rational_type(1/(x^3 - y^2) + z/d) (True, False, None, None, quasi weakly holomorphic modular) sage: rational_type(x^3/(x^3 - y^2)) (True, True, 0, 1, weakly holomorphic modular) sage: rational_type(1/(x + z)) (False, False, None, None, None) sage: rational_type(1/x + 1/z) (True, False, None, None, quasi meromorphic modular) sage: rational_type(d/x, n=10) (True, True, -1/2, 1, meromorphic modular) sage: rational_type(1.1 * z * (x^8-y^2), n=8, base_ring=CC) (True, True, 22/3, -1, quasi cuspidal) sage: rational_type(x-y^2, n=infinity) (True, True, 4, 1, modular) sage: rational_type(x*(x-y^2), n=infinity) (True, True, 8, 1, cuspidal) sage: rational_type(1/x, n=infinity) (True, True, -4, 1, weakly holomorphic modular) """ from .analytic_type import AnalyticType AT = AnalyticType() # Determine whether f is zero if (f == 0): # elem, h**o, k, ep, analytic_type return (True, True, QQ(0), ZZ(1), AT([])) analytic_type = AT(["quasi", "mero"]) R = PolynomialRing(base_ring,'x,y,z,d') F = FractionField(R) (x,y,z,d) = R.gens() R2 = PolynomialRing(PolynomialRing(base_ring, 'd'), 'x,y,z') dhom = R.hom( R2.gens() + (R2.base().gen(),), R2) f = F(f) num = R(f.numerator()) denom = R(f.denominator()) ep_num = set([ZZ(1) - 2*(( sum([g.exponents()[0][m] for m in [1,2]]) )%2) for g in dhom(num).monomials()]) ep_denom = set([ZZ(1) - 2*(( sum([g.exponents()[0][m] for m in [1,2]]) )%2) for g in dhom(denom).monomials()]) if (n == infinity): hom_num = R( num.subs(x=x**4, y=y**2, z=z**2) ) hom_denom = R( denom.subs(x=x**4, y=y**2, z=z**2) ) else: n = ZZ(n) hom_num = R( num.subs(x=x**4, y=y**(2*n), z=z**(2*(n-2))) ) hom_denom = R( denom.subs(x=x**4, y=y**(2*n), z=z**(2*(n-2))) ) # Determine whether the denominator of f is homogeneous if (len(ep_denom) == 1 and dhom(hom_denom).is_homogeneous()): elem = True else: # elem, h**o, k, ep, analytic_type return (False, False, None, None, None) # Determine whether f is homogeneous if (len(ep_num) == 1 and dhom(hom_num).is_homogeneous()): h**o = True if (n == infinity): weight = (dhom(hom_num).degree() - dhom(hom_denom).degree()) else: weight = (dhom(hom_num).degree() - dhom(hom_denom).degree()) / (n-2) ep = ep_num.pop() / ep_denom.pop() # TODO: decompose f (resp. its degrees) into homogeneous parts else: h**o = False weight = None ep = None # Note that we intentionally leave out the d-factor! if (n == infinity): finf_pol = (x-y**2) else: finf_pol = x**n-y**2 # Determine whether f is modular if not ( (num.degree(z) > 0) or (denom.degree(z) > 0) ): analytic_type = analytic_type.reduce_to("mero") # Determine whether f is holomorphic if (dhom(denom).is_constant()): analytic_type = analytic_type.reduce_to(["quasi", "holo"]) # Determine whether f is cuspidal in the sense that finf divides it... # Bug in singular: finf_pol.divides(1.0) fails over RR if (not dhom(num).is_constant() and finf_pol.divides(num)): if (n != infinity or x.divides(num)): analytic_type = analytic_type.reduce_to(["quasi", "cusp"]) else: # -> Because of a bug with singular in some cases try: while (finf_pol.divides(denom)): # a simple "denom /= finf_pol" is strangely not enough for non-exact rings # and dividing would/may result with an element of the quotient ring of the polynomial ring denom = denom.quo_rem(finf_pol)[0] denom = R(denom) if (n == infinity): while (x.divides(denom)): # a simple "denom /= x" is strangely not enough for non-exact rings # and dividing would/may result with an element of the quotient ring of the polynomial ring denom = denom.quo_rem(x)[0] denom = R(denom) except TypeError: pass # Determine whether f is weakly holomorphic in the sense that at most powers of finf occur in denom if (dhom(denom).is_constant()): analytic_type = analytic_type.reduce_to(["quasi", "weak"]) return (elem, h**o, weight, ep, analytic_type)
def dimension__vector_valued(k, L, conjugate=False): r""" Compute the dimension of the space of weight `k` vector valued modular forms for the Weil representation (or its conjugate) attached to the lattice `L`. See [Borcherds, Borcherds - Reflection groups of Lorentzian lattices] for a proof of the formula that we use here. INPUT: - `k` -- A half-integer. - ``L`` -- An quadratic form. - ``conjugate`` -- A boolean; If ``True``, then compute the dimension for the conjugated Weil representation. OUTPUT: An integer. TESTS:: sage: dimension__vector_valued(3, QuadraticForm(-matrix(2, [2, 1, 1, 2]))) 1 sage: dimension__vector_valued(3, QuadraticForm(-matrix(2, [2, 0, 0, 2]))) 1 sage: dimension__vector_valued(3, QuadraticForm(-matrix(2, [2, 0, 0, 4]))) 1 """ if 2 * k not in ZZ: raise ValueError("Weight must be half-integral") if k <= 0: return 0 if k < 2: raise NotImplementedError("Weight <2 is not implemented.") if L.matrix().rank() != L.matrix().nrows(): raise ValueError( "The lattice (={0}) must be non-degenerate.".format(L)) L_dimension = L.matrix().nrows() if L_dimension % 2 != ZZ(2 * k) % 2: return 0 plus_basis = ZZ(L_dimension + 2 * k) % 4 == 0 ## The bilinear and the quadratic form attached to L quadratic = lambda x: L(x) // 2 bilinear = lambda x, y: L(x + y) - L(x) - L(y) ## A dual basis for L (elementary_divisors, dual_basis_pre, _) = L.matrix().smith_form() elementary_divisors = elementary_divisors.diagonal() dual_basis = map(operator.div, list(dual_basis_pre), elementary_divisors) L_level = ZZ(lcm([b.denominator() for b in dual_basis])) (elementary_divisors, _, discriminant_basis_pre) = ( L_level * matrix(dual_basis)).change_ring(ZZ).smith_form() elementary_divisors = filter(lambda d: d not in ZZ, (elementary_divisors / L_level).diagonal()) elementary_divisors_inv = map(ZZ, [ed**-1 for ed in elementary_divisors]) discriminant_basis = matrix( map(operator.mul, discriminant_basis_pre.inverse().rows()[:len(elementary_divisors)], elementary_divisors)).transpose() ## This is a form over QQ, so that we cannot use an instance of QuadraticForm discriminant_form = discriminant_basis.transpose() * L.matrix( ) * discriminant_basis if conjugate: discriminant_form = -discriminant_form if prod(elementary_divisors_inv) > 100: disc_den = discriminant_form.denominator() disc_bilinear_pre = \ cython_lambda( ', '.join( ['int a{0}'.format(i) for i in range(discriminant_form.nrows())] + ['int b{0}'.format(i) for i in range(discriminant_form.nrows())] ), ' + '.join('{0} * a{1} * b{2}'.format(disc_den * discriminant_form[i,j], i, j) for i in range(discriminant_form.nrows()) for j in range(discriminant_form.nrows())) ) disc_bilinear = lambda *a: disc_bilinear_pre(*a) / disc_den else: disc_bilinear = lambda *xy: vector(ZZ, xy[:discriminant_form.nrows( )]) * discriminant_form * vector(ZZ, xy[discriminant_form.nrows():]) disc_quadratic = lambda *a: disc_bilinear(*(2 * a)) / 2 ## red gives a normal form for elements in the discriminant group red = lambda x: map(operator.mod, x, elementary_divisors_inv) def is_singl(x): y = red(map(operator.neg, x)) for (e, f) in zip(x, y): if e < f: return -1 elif e > f: return 1 return 0 ## singls and pairs are elements of the discriminant group that are, respectively, ## fixed and not fixed by negation. singls = list() pairs = list() for x in mrange(elementary_divisors_inv): si = is_singl(x) if si == 0: singls.append(x) elif si == 1: pairs.append(x) if plus_basis: subspace_dimension = len(singls + pairs) else: subspace_dimension = len(pairs) ## 200 bits are, by far, sufficient to distinguish 12-th roots of unity ## by increasing the precision by 4 for each additional dimension, we ## compensate, by far, the errors introduced by the QR decomposition, ## which are of the size of (absolute error) * dimension CC = ComplexIntervalField(200 + subspace_dimension * 4) zeta_order = ZZ( lcm([8, 12] + map(lambda ed: 2 * ed, elementary_divisors_inv))) zeta = CC(exp(2 * pi * I / zeta_order)) sqrt2 = CC(sqrt(2)) drt = CC(sqrt(L.det())) Tmat = diagonal_matrix(CC, [ zeta**(zeta_order * disc_quadratic(*a)) for a in (singls + pairs if plus_basis else pairs) ]) if plus_basis: Smat = zeta**(zeta_order / 8 * L_dimension) / drt \ * matrix( CC, [ [zeta**(-zeta_order * disc_bilinear(*(gamma + delta))) for delta in singls] + [sqrt2 * zeta**(-zeta_order * disc_bilinear(*(gamma + delta))) for delta in pairs] for gamma in singls] \ + [ [sqrt2 * zeta**(-zeta_order * disc_bilinear(*(gamma + delta))) for delta in singls] + [zeta**(-zeta_order * disc_bilinear(*(gamma + delta))) + zeta**(-zeta_order * disc_bilinear(*(gamma + map(operator.neg, delta)))) for delta in pairs] for gamma in pairs] ) else: Smat = zeta**(zeta_order / 8 * L_dimension) / drt \ * matrix( CC, [ [zeta**(-zeta_order * disc_bilinear(*(gamma + delta))) - zeta**(-zeta_order * disc_bilinear(*(gamma + map(operator.neg,delta)))) for delta in pairs] for gamma in pairs ] ) STmat = Smat * Tmat ## This function overestimates the number of eigenvalues, if it is not correct def eigenvalue_multiplicity(mat, ev): mat = matrix(CC, mat - ev * identity_matrix(subspace_dimension)) return len( filter(lambda row: all(e.contains_zero() for e in row), _qr(mat).rows())) rti = CC(exp(2 * pi * I / 8)) S_ev_multiplicity = [ eigenvalue_multiplicity(Smat, rti**n) for n in range(8) ] ## Together with the fact that eigenvalue_multiplicity overestimates the multiplicities ## this asserts that the computed multiplicities are correct assert sum(S_ev_multiplicity) == subspace_dimension rho = CC(exp(2 * pi * I / 12)) ST_ev_multiplicity = [ eigenvalue_multiplicity(STmat, rho**n) for n in range(12) ] ## Together with the fact that eigenvalue_multiplicity overestimates the multiplicities ## this asserts that the computed multiplicities are correct assert sum(ST_ev_multiplicity) == subspace_dimension T_evs = [ ZZ((zeta_order * disc_quadratic(*a)) % zeta_order) / zeta_order for a in (singls + pairs if plus_basis else pairs) ] return subspace_dimension * (1 + QQ(k) / 12) \ - ZZ(sum( (ST_ev_multiplicity[n] * ((-2 * k - n) % 12)) for n in range(12) )) / 12 \ - ZZ(sum( (S_ev_multiplicity[n] * ((2 * k + n) % 8)) for n in range(8) )) / 8 \ - sum(T_evs)
def _rad(self, center): return RBF.one() << QQ(center).valuation(2)
def is_genus(self, signature_pair, even=True): r""" Return ``True`` if there is a lattice with this signature and discriminant form. .. TODO:: implement the same for odd lattices INPUT: - signature_pair -- a tuple of non negative integers ``(s_plus, s_minus)`` - even -- bool (default: ``True``) EXAMPLES:: sage: L = IntegralLattice("D4").direct_sum(IntegralLattice(3 * Matrix(ZZ,2,[2,1,1,2]))) sage: D = L.discriminant_group() sage: D.is_genus((6,0)) True Let us see if there is a lattice in the genus defined by the same discriminant form but with a different signature:: sage: D.is_genus((4,2)) False sage: D.is_genus((16,2)) True """ s_plus = ZZ(signature_pair[0]) s_minus = ZZ(signature_pair[1]) if s_plus < 0 or s_minus < 0: raise ValueError("signature invariants must be non negative") rank = s_plus + s_minus signature = s_plus - s_minus D = self.cardinality() det = (-1)**s_minus * D if rank < len(self.invariants()): return False if even and self._modulus_qf != 2: raise ValueError("the discriminant form of an even lattice has" "values modulo 2.") if (not even) and not (self._modulus == self._modulus_qf == 1): raise ValueError("the discriminant form of an odd lattice has" "values modulo 1.") if not even: raise NotImplementedError("at the moment sage knows how to do this only for even genera. " + " Help us to implement this for odd genera.") for p in D.prime_divisors(): # check the determinant conditions Q_p = self.primary_part(p) gram_p = Q_p.gram_matrix_quadratic() length_p = len(Q_p.invariants()) u = det.prime_to_m_part(p) up = gram_p.det().numerator().prime_to_m_part(p) if p != 2 and length_p == rank: if legendre_symbol(u, p) != legendre_symbol(up, p): return False if p == 2: if rank % 2 != length_p % 2: return False n = (rank - length_p) / 2 if u % 4 != (-1)**(n % 2) * up % 4: return False if rank == length_p: a = QQ(1) / QQ(2) b = QQ(3) / QQ(2) diag = gram_p.diagonal() if not (a in diag or b in diag): if u % 8 != up % 8: return False if self.brown_invariant() != signature: return False return True
def normal_cone(self): r""" Return the (closure of the) normal cone of the triangulation. Recall that a regular triangulation is one that equals the "crease lines" of a convex piecewise-linear function. This support function is not unique, for example, you can scale it by a positive constant. The set of all piecewise-linear functions with fixed creases forms an open cone. This cone can be interpreted as the cone of normal vectors at a point of the secondary polytope, which is why we call it normal cone. See [GKZ]_ Section 7.1 for details. OUTPUT: The closure of the normal cone. The `i`-th entry equals the value of the piecewise-linear function at the `i`-th point of the configuration. For an irregular triangulation, the normal cone is empty. In this case, a single point (the origin) is returned. EXAMPLES:: sage: triangulation = polytopes.hypercube(2).triangulate(engine='internal') sage: triangulation (<0,1,3>, <0,2,3>) sage: N = triangulation.normal_cone(); N 4-d cone in 4-d lattice sage: N.rays() (-1, 0, 0, 0), ( 1, 0, 1, 0), (-1, 0, -1, 0), ( 1, 0, 0, -1), (-1, 0, 0, 1), ( 1, 1, 0, 0), (-1, -1, 0, 0) in Ambient free module of rank 4 over the principal ideal domain Integer Ring sage: N.dual().rays() (-1, 1, 1, -1) in Ambient free module of rank 4 over the principal ideal domain Integer Ring TESTS:: sage: polytopes.simplex(2).triangulate().normal_cone() 3-d cone in 3-d lattice sage: _.dual().is_trivial() True """ if not self.point_configuration().base_ring().is_subring(QQ): raise NotImplementedError('Only base rings ZZ and QQ are supported') from sage.libs.ppl import Variable, Constraint, Constraint_System, Linear_Expression, C_Polyhedron from sage.matrix.constructor import matrix from sage.misc.misc import uniq from sage.rings.arith import lcm pc = self.point_configuration() cs = Constraint_System() for facet in self.interior_facets(): s0, s1 = self._boundary_simplex_dictionary()[facet] p = set(s0).difference(facet).pop() q = set(s1).difference(facet).pop() origin = pc.point(p).reduced_affine_vector() base_indices = [ i for i in s0 if i!=p ] base = matrix([ pc.point(i).reduced_affine_vector()-origin for i in base_indices ]) sol = base.solve_left( pc.point(q).reduced_affine_vector()-origin ) relation = [0]*pc.n_points() relation[p] = sum(sol)-1 relation[q] = 1 for i, base_i in enumerate(base_indices): relation[base_i] = -sol[i] rel_denom = lcm([QQ(r).denominator() for r in relation]) relation = [ ZZ(r*rel_denom) for r in relation ] ex = Linear_Expression(relation,0) cs.insert(ex >= 0) from sage.modules.free_module import FreeModule ambient = FreeModule(ZZ, self.point_configuration().n_points()) if cs.empty(): cone = C_Polyhedron(ambient.dimension(), 'universe') else: cone = C_Polyhedron(cs) from sage.geometry.cone import _Cone_from_PPL return _Cone_from_PPL(cone, lattice=ambient)
def angles(self, numerical=False, return_adjacent_edges=False): r""" Return the set of angles around the vertices of the surface. These are given as multiple of `2 \pi`. EXAMPLES:: sage: import flatsurf.geometry.similarity_surface_generators as sfg sage: sfg.translation_surfaces.regular_octagon().angles() [3] sage: S = sfg.translation_surfaces.veech_2n_gon(5) sage: S.angles() [2, 2] sage: S.angles(numerical=True) [2.0, 2.0] sage: S.angles(return_adjacent_edges=True) [(2, [(0, 1), (0, 5), (0, 9), (0, 3), (0, 7)]), (2, [(0, 0), (0, 4), (0, 8), (0, 2), (0, 6)])] sage: S.angles(numerical=True, return_adjacent_edges=True) [(2.0, [(0, 1), (0, 5), (0, 9), (0, 3), (0, 7)]), (2.0, [(0, 0), (0, 4), (0, 8), (0, 2), (0, 6)])] sage: sfg.translation_surfaces.veech_2n_gon(6).angles() [5] sage: sfg.translation_surfaces.veech_double_n_gon(5).angles() [3] sage: sfg.translation_surfaces.cathedral(1, 1).angles() [3, 3, 3] sage: from flatsurf import polygons, similarity_surfaces sage: B = similarity_surfaces.billiard(polygons.triangle(1, 2, 5)) sage: H = B.minimal_cover(cover_type="half-translation") sage: S = B.minimal_cover(cover_type="translation") sage: H.angles() [1/2, 5/2, 1/2, 1/2] sage: S.angles() [1, 5, 1, 1] sage: H.angles(return_adjacent_edges=True) [(1/2, [...]), (5/2, [...]), (1/2, [...]), (1/2, [...])] sage: S.angles(return_adjacent_edges=True) [(1, [...]), (5, [...]), (1, [...]), (1, [...])] """ if not self.is_finite(): raise NotImplementedError("the set of edges is infinite!") edges = set(self.edge_iterator()) angles = [] if return_adjacent_edges: while edges: pair = p, e = next(iter(edges)) ve = self.polygon(p).edge(e) angle = 0 adjacent_edges = [] while pair in edges: adjacent_edges.append(pair) edges.remove(pair) f = (e - 1) % self.polygon(p).num_edges() ve = self.polygon(p).edge(e) vf = -self.polygon(p).edge(f) ppair = pp, ff = self.opposite_edge(p, f) angle += (ve[0] > 0 and vf[0] <= 0) or ( ve[0] < 0 and vf[0] >= 0) or (ve[0] == vf[0] == 0) pair, p, e = ppair, pp, ff if numerical: angles.append((float(angle) / 2, adjacent_edges)) else: angles.append((QQ((angle, 2)), adjacent_edges)) else: while edges: pair = p, e = next(iter(edges)) angle = 0 while pair in edges: edges.remove(pair) f = (e - 1) % self.polygon(p).num_edges() ve = self.polygon(p).edge(e) vf = -self.polygon(p).edge(f) ppair = pp, ff = self.opposite_edge(p, f) angle += (ve[0] > 0 and vf[0] <= 0) or ( ve[0] < 0 and vf[0] >= 0) or (ve[0] == vf[0] == 0) pair, p, e = ppair, pp, ff if numerical: angles.append(float(angle) / 2) else: angles.append(QQ((angle, 2))) return angles
def possible_orders(self, proof=True): """ Return the possible orders of this torsion subgroup. Outside of special cases, this is done by computing a divisor and multiple of the order. INPUT: - ``proof`` -- a boolean (default: True) OUTPUT: - an array of positive integers The computation of the rational torsion order of J1(p) is conjectural and will only be used if proof=False. See Section 6.2.3 of [CES2003]_. EXAMPLES:: sage: J0(11).rational_torsion_subgroup().possible_orders() [5] sage: J0(33).rational_torsion_subgroup().possible_orders() [100, 200] sage: J1(13).rational_torsion_subgroup().possible_orders() [19] sage: J1(16).rational_torsion_subgroup().possible_orders() [1, 2, 4, 5, 10, 20] """ try: if proof: return self._possible_orders else: return self._possible_orders_proof_false except AttributeError: pass A = self.abelian_variety() N = A.level() # return the order of the cuspidal subgroup in the J0(p) case if A.is_J0() and N.is_prime(): self._possible_orders = [QQ((A.level() - 1) / 12).numerator()] self._possible_orders_proof_false = self._possible_orders return self._possible_orders # the elliptic curve case if A.dimension() == 1: self._possible_orders = [A.elliptic_curve().torsion_order()] self._possible_orders_proof_false = self._possible_orders return self._possible_orders # the conjectural J1(p) case if not proof and A.is_J1() and N.is_prime(): epsilons = [ epsilon for epsilon in DirichletGroup(N) if not epsilon.is_trivial() and epsilon.is_even() ] bernoullis = [epsilon.bernoulli(2) for epsilon in epsilons] self._possible_orders_proof_false = [ ZZ(N / (2**(N - 3)) * prod(bernoullis)) ] return self._possible_orders_proof_false u = self.multiple_of_order() l = self.divisor_of_order() assert u % l == 0 O = [l * d for d in divisors(u // l)] self._possible_orders = O if u == l: self._possible_orders_proof_false = O return O
def _test_monodromy_matrices(): r""" TESTS:: sage: from ore_algebra.analytic.monodromy import _test_monodromy_matrices sage: _test_monodromy_matrices() """ from sage.all import matrix from ore_algebra import DifferentialOperators Dops, x, Dx = DifferentialOperators() h = QQ(1) / 2 i = QQi.gen() def norm(m): return sum(c.abs()**2 for c in m.list()).sqrtpos() mon = monodromy_matrices((x**2 + 1) * Dx - 1, QQ(1000000)) assert norm(mon[0] - CBF(pi).exp()) < RBF(1e-10) assert norm(mon[1] - CBF(-pi).exp()) < RBF(1e-10) mon = monodromy_matrices((x**2 - 1) * Dx - 1, QQ(0)) assert all(m == -1 for m in mon) dop = (x**2 + 1) * Dx**2 + 2 * x * Dx mon = monodromy_matrices(dop, QQbar(i + 1)) # mon[0] <--> i assert norm(mon[0] - matrix(CBF, [[1, pi * (1 + 2 * i)], [0, 1]])) < RBF(1e-10) assert norm(mon[1] - matrix(CBF, [[1, -pi * (1 + 2 * i)], [0, 1]])) < RBF(1e-10) mon = monodromy_matrices(dop, QQbar(-i + 1)) # mon[0] <--> -i assert norm(mon[0] - matrix(CBF, [[1, pi * (-1 + 2 * i)], [0, 1]])) < RBF(1e-10) assert norm(mon[1] - matrix(CBF, [[1, pi * (1 - 2 * i)], [0, 1]])) < RBF(1e-10) mon = monodromy_matrices(dop, QQbar(i)) # mon[0] <--> i assert norm(mon[0] - matrix(CBF, [[1, 0], [2 * pi * i, 1]])) < RBF(1e-10) assert norm(mon[1] - matrix(CBF, [[1, 0], [-2 * pi * i, 1]])) < RBF(1e-10) mon = monodromy_matrices(dop, QQbar(i), sing=[QQbar(i)]) assert len(mon) == 1 assert norm(mon[0] - matrix(CBF, [[1, 0], [2 * pi * i, 1]])) < RBF(1e-10) mon = monodromy_matrices(dop, QQbar(i), sing=[QQbar(-i)]) assert len(mon) == 1 assert norm(mon[0] - matrix(CBF, [[1, 0], [-2 * pi * i, 1]])) < RBF(1e-10) mon = monodromy_matrices(dop, QQbar(-i), sing=[QQbar(i)]) assert len(mon) == 1 assert norm(mon[0] - matrix(CBF, [[1, 0], [-2 * pi * i, 1]])) < RBF(1e-10) mon = monodromy_matrices(dop, QQbar(i), sing=[]) assert mon == [] dop = (x**2 + 1) * (x**2 - 1) * Dx**2 + 1 mon = monodromy_matrices(dop, QQ(0), sing=[QQ(1), QQbar(i)]) m0 = dop.numerical_transition_matrix([0, i + 1, 2 * i, i - 1, 0]) assert norm(m0 - mon[0]) < RBF(1e-10) m1 = dop.numerical_transition_matrix([0, 1 - i, 2, 1 + i, 0]) assert norm(m1 - mon[1]) < RBF(1e-10) dop = x * (x - 3) * (x - 4) * (x**2 - 6 * x + 10) * Dx**2 - 1 mon = monodromy_matrices(dop, QQ(-1)) m0 = dop.numerical_transition_matrix([-1, -i, 1, i, -1]) assert norm(m0 - mon[0]) < RBF(1e-10) m1 = dop.numerical_transition_matrix( [-1, i / 2, 3 - i / 2, 3 + h, 3 + i / 2, i / 2, -1]) assert norm(m1 - mon[1]) < RBF(1e-10) m2 = dop.numerical_transition_matrix( [-1, i / 2, 3 + i / 2, 4 - i / 2, 4 + h, 4 + i / 2, i / 2, -1]) assert norm(m2 - mon[2]) < RBF(1e-10) m3 = dop.numerical_transition_matrix([-1, 3 + i + h, 3 + 2 * i, -1]) assert norm(m3 - mon[3]) < RBF(1e-10) m4 = dop.numerical_transition_matrix( [-1, 3 - 2 * i, 3 - i + h, 3 - i / 2, -1]) assert norm(m4 - mon[4]) < RBF(1e-10) dop = (x - i)**2 * (x + i) * Dx - 1 mon = monodromy_matrices(dop, 0) assert norm(mon[0] + i) < RBF(1e-10) assert norm(mon[1] - i) < RBF(1e-10) dop = (x - i)**2 * (x + i) * Dx**2 - 1 mon = monodromy_matrices(dop, 0) m0 = dop.numerical_transition_matrix([0, i + 1, 2 * i, i - 1, 0]) assert norm(m0 - mon[0]) < RBF(1e-10) m1 = dop.numerical_transition_matrix([0, -i - 1, -2 * i, -i + 1, 0]) assert norm(m1 - mon[1]) < RBF(1e-10)
def check(self, cb, sing, n, ini_tb, est, next_stride): r""" Test if it is time to halt the computation of the sum of a series. INPUT: - cb: BoundCallbacks object, see the documentation of that class; - sing: boolean, True signals a singular index where we should return infinity without even trying to compute a better bound (this is useful to avoid special-casing singular indices elsewhere); - n: current index; - ini_tb: previous tail bound (can be infinite); - est: real interval, heuristic estimate of the absolute value of the tail of the series, **whose lower bound must tend to zero or eventually become negative**, and whose width can be used to provide an indication of interval blow-up in the computation; typically something like abs(first nonzero neglected term); - next_stride: indication of how many additional terms the caller intends to sum before calling us again. OUTPUT: (done, bound) where done is a boolean indicating if it is time to stop the computation, and bound is a rigorous (possibly infinite) bound on the tail of the series. TESTS:: sage: from ore_algebra import DifferentialOperators sage: from ore_algebra.analytic.bounds import DiffOpBound sage: from ore_algebra.analytic.naive_sum import series_sum sage: Dops, x, Dx = DifferentialOperators() sage: maj = DiffOpBound(Dx-1, max_effort=0) sage: series_sum(Dx-1, [1], 2, 1e-50, stride=1) ([7.3890560989306502272304274605750078131803155705...]) """ if sing: return False, IR('inf') eps = self.eps # XXX We shouldn't force the caller to give a full-precision estimate... accuracy = max(est.accuracy(), -int((est.rad() or RR(1)).log(2))) width = IR(est.rad()) est = IR(est) intervals_blowing_up = (accuracy < self.prec or safe_le(self.eps >> 2, width)) if intervals_blowing_up: if self.fast_fail: logger.debug("n=%d, est=%s, width=%s", n, est, width) raise PrecisionError if safe_le(self.eps, width): # Aim for tail_bound < width instead of tail_bound < self.eps eps = width if safe_lt(eps, est) and accuracy >= self.prec and not self.force: # It is important to test the inequality with the *interval* est, to # avoid getting caught in an infinite loop when est increases # indefinitely due to interval blow-up. When however the lower bound # of est increases too, we are probably climbing a term hump (think # exp(1000)): it is better not to halt the computation yet, in the # hope of getting at least a reasonable relative error. logger.debug("n=%d, est=%s, width=%s", n, est, width) assert width.is_finite() return False, IR('inf') resid = cb.get_residuals() tb = IR('inf') while True: prev_tb = tb tb = cb.get_bound(resid) logger.debug("n=%d, est=%s, width=%s, tail_bound=%s", n, est, width, tb) bound_getting_worse = ini_tb.is_finite() and not safe_lt( tb, ini_tb) if safe_lt(tb, eps): logger.debug("--> ok") return True, tb elif self.force and not self.maj.can_refine(): break elif (prev_tb.is_finite() and not safe_le(tb, prev_tb >> 8) or not self.maj.can_refine()): if bound_getting_worse: # The bounds are out of control, stop asap. # Subtle point: We could also end up here because of a hump. # But then, typically, est > ε, so that we shouldn't even # have entered the rigorous phase unless the intervals are # blowing up badly. # Note: it seems best *not* to do this when prev_tb is # non-finite, because we may be waiting to get past a # singularity of the recurrence. (XXX: Unfortunately, we # have no way of deciding if the bound is infinite because # of a genuine mathematical reason or an evaluation issue.) logger.debug( "--> bounds out of control ({} became {})".format( ini_tb, tb)) return True, tb else: # Refining no longer seems to help: sum more terms logger.debug("--> refining doesn't help") break elif intervals_blowing_up or bound_getting_worse: # Adding more terms is likely to make the result worse and # worse, but we can try refining the majorant as much as # possible before giving up. (Note that unlike in the previous # branch, we do not stop asap if the bound is getting worse in # the present case.) logger.debug("--> intervals blowing up or bound getting worse") self.maj.refine() else: thr = tb * est**(QQ(next_stride * (self.maj.effort()**2 + 2)) / (n + 1)) if safe_le(thr, eps): # Try summing a few more terms before refining logger.debug( "--> above refinement threshold ({} <= {})".format( thr, eps)) break else: logger.debug("--> bad bound but refining may help") self.maj.refine() logger.debug("--> ko") return False, tb
def lift_map(target): r""" Create a lift map, to be used for lifting the cross ratios of a matroid representation. .. SEEALSO:: :meth:`lift_cross_ratios() <sage.matroids.utilities.lift_cross_ratios>` INPUT: - ``target`` -- a string describing the target (partial) field. OUTPUT: - a dictionary Depending on the value of ``target``, the following lift maps will be created: - "reg": a lift map from `\GF3` to the regular partial field `(\ZZ, <-1>)`. - "sru": a lift map from `\GF7` to the sixth-root-of-unity partial field `(\QQ(z), <z>)`, where `z` is a sixth root of unity. The map sends 3 to `z`. - "dyadic": a lift map from `\GF{11}` to the dyadic partial field `(\QQ, <-1, 2>)`. - "gm": a lift map from `\GF{19}` to the golden mean partial field `(\QQ(t), <-1,t>)`, where `t` is a root of `t^2-t-1`. The map sends `5` to `t`. The example below shows that the latter map satisfies three necessary conditions stated in :meth:`lift_cross_ratios() <sage.matroids.utilities.lift_cross_ratios>` EXAMPLES:: sage: from sage.matroids.utilities import lift_map sage: lm = lift_map('gm') sage: for x in lm: ....: if (x == 1) is not (lm[x] == 1): ....: print('not a proper lift map') ....: for y in lm: ....: if (x+y == 0) and not (lm[x]+lm[y] == 0): ....: print('not a proper lift map') ....: if (x+y == 1) and not (lm[x]+lm[y] == 1): ....: print('not a proper lift map') ....: for z in lm: ....: if (x*y==z) and not (lm[x]*lm[y]==lm[z]): ....: print('not a proper lift map') """ if target == "reg": R = GF(3) return {R(1): ZZ(1)} if target == "sru": R = GF(7) z = ZZ['z'].gen() S = NumberField(z * z - z + 1, 'z') z = S(z) return {R.one(): S.one(), R(3): z, R(3)**(-1): z**5} if target == "dyadic": R = GF(11) return {R(1): QQ(1), R(-1): QQ(-1), R(2): QQ(2), R(6): QQ((1, 2))} if target == "gm": R = GF(19) t = QQ['t'].gen() G = NumberField(t * t - t - 1, 't') return { R(1): G(1), R(5): G(t), R(1) / R(5): G(1) / G(t), R(-5): G(-t), R(-5)**(-1): G(-t)**(-1), R(5)**2: G(t)**2, R(5)**(-2): G(t)**(-2) } raise NotImplementedError(target)
def enumerate_totallyreal_fields_rel(F, m, B, a=[], verbose=0, return_seqs=False, return_pari_objects=True): r""" This function enumerates (primitive) totally real field extensions of degree `m>1` of the totally real field F with discriminant `d \leq B`; optionally one can specify the first few coefficients, where the sequence ``a`` corresponds to a polynomial by :: a[d]*x^n + ... + a[0]*x^(n-d) if ``length(a) = d+1``, so in particular always ``a[d] = 1``. .. note:: This is guaranteed to give all primitive such fields, and seems in practice to give many imprimitive ones. INPUT: - ``F`` -- number field, the base field - ``m`` -- integer, the degree - ``B`` -- integer, the discriminant bound - ``a`` -- list (default: []), the coefficient list to begin with - ``verbose`` -- boolean or nonnegative integer or string (default: 0) give a verbose description of the computations being performed. If ``verbose`` is set to ``2`` or more then it outputs some extra information. If ``verbose`` is a string then it outputs to a file specified by ``verbose`` - ``return_seqs`` -- (boolean, default False) If ``True``, then return the polynomials as sequences (for easier exporting to a file). This also returns a list of four numbers, as explained in the OUTPUT section below. - ``return_pari_objects`` -- (boolean, default: True) if both ``return_seqs`` and ``return_pari_objects`` are ``False`` then it returns the elements as Sage objects; otherwise it returns pari objects. OUTPUT: - the list of fields with entries ``[d,fabs,f]``, where ``d`` is the discriminant, ``fabs`` is an absolute defining polynomial, and ``f`` is a defining polynomial relative to ``F``, sorted by discriminant. - if ``return_seqs`` is ``True``, then the first field of the list is a list containing the count of four items as explained below - the first entry gives the number of polynomials tested - the second entry gives the number of polynomials with its discriminant having a large enough square divisor - the third entry is the number of irreducible polynomials - the fourth entry is the number of irreducible polynomials with discriminant at most ``B`` EXAMPLES:: sage: ZZx = ZZ['x'] sage: F.<t> = NumberField(x^2-2) sage: enumerate_totallyreal_fields_rel(F, 1, 2000) [[1, [-2, 0, 1], xF - 1]] sage: enumerate_totallyreal_fields_rel(F, 2, 2000) [[1600, x^4 - 6*x^2 + 4, xF^2 + xF - 1]] sage: enumerate_totallyreal_fields_rel(F, 2, 2000, return_seqs=True) [[9, 6, 5, 0], [[1600, [4, 0, -6, 0, 1], [-1, 1, 1]]]] TESTS: Each of the outputs must be elements of Sage if ``return_pari_objects`` is set to ``False``:: sage: type(enumerate_totallyreal_fields_rel(F, 2, 2000)[0][1]) <type 'sage.libs.cypari2.gen.Gen'> sage: enumerate_totallyreal_fields_rel(F, 2, 2000, return_pari_objects=False)[0][0].parent() Integer Ring sage: enumerate_totallyreal_fields_rel(F, 2, 2000, return_pari_objects=False)[0][1].parent() Univariate Polynomial Ring in x over Rational Field sage: enumerate_totallyreal_fields_rel(F, 2, 2000, return_pari_objects=False)[0][2].parent() Univariate Polynomial Ring in xF over Number Field in t with defining polynomial x^2 - 2 sage: enumerate_totallyreal_fields_rel(F, 2, 2000, return_seqs=True)[1][0][1][0].parent() Rational Field AUTHORS: - John Voight (2007-11-01) """ if not isinstance(m, Integer): try: m = Integer(m) except TypeError: raise TypeError("cannot coerce m (= %s) to an integer" % m) if (m < 1): raise ValueError("m must be at least 1.") n = F.degree() * m # Initialize S = {} # dictionary of the form {(d, fabs): f, ...} dB_odlyzko = odlyzko_bound_totallyreal(n) dB = math.ceil(40000 * dB_odlyzko**n) counts = [0, 0, 0, 0] # Trivial case if m == 1: g = pari(F.defining_polynomial()).polrecip().Vec() if return_seqs: return [[0, 0, 0, 0], [1, [-1, 1], g]] elif return_pari_objects: return [[1, g, pari('xF-1')]] else: Px = PolynomialRing(QQ, 'xF') return [[ZZ(1), [QQ(_) for _ in g], Px.gen() - 1]] if verbose: saveout = sys.stdout if isinstance(verbose, str): fsock = open(verbose, 'w') sys.stdout = fsock # Else, print to screen f_out = [0] * m + [1] T = tr_data_rel(F, m, B, a) if verbose == 2: T.incr(f_out, verbose) else: T.incr(f_out) Fx = PolynomialRing(F, 'xF') nfF = pari( str(F.defining_polynomial()).replace('x', str(F.primitive_element()))) parit = pari(str(F.primitive_element())) while f_out[m] != 0: counts[0] += 1 if verbose: print("==>", f_out, end="") f_str = '' for i in range(len(f_out)): f_str += '(' + str(f_out[i]) + ')*x^' + str(i) if i < len(f_out) - 1: f_str += '+' nf = pari(f_str) if nf.poldegree('t') == 0: nf = nf.subst('x', 'x-t') nf = nf.polresultant(nfF, parit) d = nf.poldisc() #counts[0] += 1 if d > 0 and nf.polsturm() == n: da = int_has_small_square_divisor(Integer(d)) if d > dB or d <= B * da: counts[1] += 1 if nf.polisirreducible(): counts[2] += 1 [zk, d] = nf.nfbasis_d() if d <= B: if verbose: print("has discriminant", d, end="") # Find a minimal lattice element counts[3] += 1 ng = pari([nf, zk]).polredabs() # Check if K is contained in the list. if (d, ng) in S: if verbose: print("but is not new") else: if verbose: print("and is new!") S[(d, ng)] = Fx(f_out) else: if verbose: print("has discriminant", abs(d), "> B") else: if verbose: print("is not absolutely irreducible") else: if verbose: print("has discriminant", abs(d), "with no large enough square divisor") else: if verbose: if d == 0: print("is not squarefree") else: print("is not totally real") if verbose == 2: T.incr(f_out, verbose=verbose) else: T.incr(f_out) # In the application of Smyth's theorem above, we exclude finitely # many possibilities which we must now throw back in. if m == 2: if Fx([-1, 1, 1]).is_irreducible(): K = F.extension(Fx([-1, 1, 1]), 'tK') Kabs = K.absolute_field('tKabs') Kabs_pari = pari(Kabs.defining_polynomial()) d = K.absolute_discriminant() if abs(d) <= B: ng = Kabs_pari.polredabs() S[(d, ng)] = Fx([-1, 1, 1]) elif F.degree() == 2: for ff in [[1, -7, 13, -7, 1], [1, -8, 14, -7, 1]]: f = Fx(ff).factor()[0][0] K = F.extension(f, 'tK') Kabs = K.absolute_field('tKabs') Kabs_pari = pari(Kabs.defining_polynomial()) d = K.absolute_discriminant() if abs(d) <= B: ng = Kabs_pari.polredabs() S[(d, ng)] = f elif m == 3: if Fx([-1, 6, -5, 1]).is_irreducible(): K = F.extension(Fx([-1, 6, -5, 1]), 'tK') Kabs = K.absolute_field('tKabs') Kabs_pari = pari(Kabs.defining_polynomial()) d = K.absolute_discriminant() if abs(d) <= B: ng = Kabs_pari.polredabs() S[(d, ng)] = Fx([-1, 6, -5, 1]) # Convert S to a sorted list of triples [d, fabs, f], taking care # to use cmp() and not the comparison operators on PARI polynomials. S = [[s[0], s[1], t] for s, t in S.items()] S.sort(cmp=lambda x, y: cmp(x[0], y[0]) or cmp(x[1], y[1])) # Now check for isomorphic fields weed_fields(S) # Output. if verbose: print("=" * 80) print("Polynomials tested: {}".format(counts[0])) print("Polynomials with discriminant with large enough square" " divisor: {}".format(counts[1])) print("Irreducible polynomials: {}".format(counts[2])) print("Polynomials with nfdisc <= B: {}".format(counts[3])) for i in range(len(S)): print(S[i]) if isinstance(verbose, str): fsock.close() sys.stdout = saveout # Make sure to return elements that belong to Sage if return_seqs: return [[ZZ(x) for x in counts], [[ s[0], [QQ(x) for x in s[1].polrecip().Vec()], s[2].coefficients(sparse=False) ] for s in S]] elif return_pari_objects: return S else: Px = PolynomialRing(QQ, 'x') return [[s[0], Px([QQ(_) for _ in s[1].list()]), s[2]] for s in S]
def egros_get_j(S=[], proof=None, verbose=False): r""" Returns a list of rational `j` such that all elliptic curves defined over `Q` with good reduction outside `S` have `j`-invariant in the list, sorted by height. INPUT: - ``S`` - list of primes (default: empty list). - ``proof`` - True/False (default True): the MW basis for auxiliary curves will be computed with this proof flag. - ``verbose`` - True/False (default False): if True, some details of the computation will be output. .. note:: Proof flag: The algorithm used requires determining all S-integral points on several auxiliary curves, which in turn requires the computation of their generators. This is not always possible (even in theory) using current knowledge. The value of this flag is passed to the function which computes generators of various auxiliary elliptic curves, in order to find their S-integral points. Set to False if the default (True) causes warning messages, but note that you can then not rely on the set of invariants returned being complete. EXAMPLES:: sage: from sage.schemes.elliptic_curves.ell_egros import egros_get_j sage: egros_get_j([]) [1728] sage: egros_get_j([2]) [128, 432, -864, 1728, 3375/2, -3456, 6912, 8000, 10976, -35937/4, 287496, -784446336, -189613868625/128] sage: egros_get_j([3]) [0, -576, 1536, 1728, -5184, -13824, 21952/9, -41472, 140608/3, -12288000] sage: jlist=egros_get_j([2,3]); len(jlist) # long time (30s) 83 """ if not all([p.is_prime() for p in S]): raise ValueError, "Elements of S must be prime." if proof is None: from sage.structure.proof.proof import get_flag proof = get_flag(proof, "elliptic_curve") else: proof = bool(proof) if verbose: import sys # so we can flush stdout for debugging SS = [-1] + S jlist = [] wcount = 0 nw = 6**len(S) * 2 if verbose: print "Finding possible j invariants for S = ", S print "Using ", nw, " twists of base curve" sys.stdout.flush() for ei in xmrange([6] * len(S) + [2]): w = prod([p**e for p, e in zip(reversed(SS), ei)], QQ(1)) wcount += 1 if verbose: print "Curve #", wcount, "/", nw, ":" print "w = ", w, "=", w.factor() sys.stdout.flush() a6 = -1728 * w d2 = 0 d3 = 0 u0 = (2**d2) * (3**d3) E = EllipticCurve([0, 0, 0, 0, a6]) # This curve may not be minimal at 2 or 3, but the # S-integral_points function requires minimality at primes in # S, so we find a new model which is p-minimal at both 2 and 3 # if they are in S. Note that the isomorphism between models # will preserve S-integrality of points. E2 = E.local_minimal_model(2) if 2 in S else E E23 = E2.local_minimal_model(3) if 3 in S else E2 urst = E23.isomorphism_to(E) try: pts = E23.S_integral_points(S, proof=proof) except RuntimeError: pts = [] print "Failed to find S-integral points on ", E23.ainvs() if proof: if verbose: print "--trying again with proof=False" sys.stdout.flush() pts = E23.S_integral_points(S, proof=False) if verbose: print "--done" if verbose: print len(pts), " S-integral points: ", pts sys.stdout.flush() for P in pts: P = urst(P) x = P[0] y = P[1] j = x**3 / w assert j - 1728 == y**2 / w if is_possible_j(j, S): if not j in jlist: if verbose: print "Adding possible j = ", j sys.stdout.flush() jlist += [j] else: if True: #verbose: print "Discarding illegal j = ", j sys.stdout.flush() height_cmp = lambda j1, j2: cmp(j1.height(), j2.height()) jlist.sort(cmp=height_cmp) return jlist
def next_state(self, height): r""" Build the next state for type `A_{2n}^{(2)\dagger}`. TESTS:: sage: RC = RiggedConfigurations(CartanType(['A', 4, 2]).dual(), [[2,1]]) sage: from sage.combinat.rigged_configurations.bij_type_A2_dual import RCToKRTBijectionTypeA2Dual sage: bijection = RCToKRTBijectionTypeA2Dual(RC(partition_list=[[2],[2,2]])) sage: bijection.next_state(2) -1 """ height -= 1 # indexing n = self.n ell = [None] * (2 * n) case_S = [False] * n case_Q = False b = None # Calculate the rank and ell values last_size = 0 for a in range(height, n - 1): ell[a] = self._find_singular_string(self.cur_partitions[a], last_size) if ell[a] is None: b = a + 1 break else: last_size = self.cur_partitions[a][ell[a]] if b is None: partition = self.cur_partitions[n - 1] # Special case for n for i in reversed(range(len(partition))): if partition[i] >= last_size: if partition.vacancy_numbers[i] == partition.rigging[i]: last_size = partition[i] case_S[n - 1] = True ell[2 * n - 1] = i break elif partition.vacancy_numbers[i] - QQ(1) / QQ( 2) == partition.rigging[i] and not case_Q: case_Q = True # This will never be singular last_size = partition[i] + 1 ell[n - 1] = i if ell[2 * n - 1] is None: if not case_Q: b = n else: b = 0 if b is None: # Now go back for a in reversed(range(n - 1)): if a >= height and self.cur_partitions[a][ell[a]] == last_size: ell[n + a] = ell[a] case_S[a] = True else: ell[n + a] = self._find_singular_string( self.cur_partitions[a], last_size) if ell[n + a] is None: b = -(a + 2) break else: last_size = self.cur_partitions[a][ell[n + a]] if b is None: b = -1 # Determine the new rigged configuration by removing boxes from the # selected string and then making the new string singular if n > 1: if case_S[0]: row_num = None row_num_bar = self.cur_partitions[0].remove_cell(ell[n], 2) else: row_num = self.cur_partitions[0].remove_cell(ell[0]) row_num_bar = self.cur_partitions[0].remove_cell(ell[n]) for a in range(1, n - 1): if case_S[a]: row_num_next = None row_num_bar_next = self.cur_partitions[a].remove_cell( ell[n + a], 2) else: row_num_next = self.cur_partitions[a].remove_cell(ell[a]) row_num_bar_next = self.cur_partitions[a].remove_cell(ell[n + a]) self._update_vacancy_numbers(a - 1) if row_num is not None: self.cur_partitions[a - 1].rigging[row_num] = self.cur_partitions[ a - 1].vacancy_numbers[row_num] if row_num_bar is not None: self.cur_partitions[ a - 1].rigging[row_num_bar] = self.cur_partitions[ a - 1].vacancy_numbers[row_num_bar] row_num = row_num_next row_num_bar = row_num_bar_next if case_Q: row_num_next = self.cur_partitions[n - 1].remove_cell(ell[n - 1]) if case_S[n - 1]: row_num_bar_next = self.cur_partitions[n - 1].remove_cell( ell[2 * n - 1]) else: row_num_bar_next = None elif case_S[n - 1]: row_num_next = None row_num_bar_next = self.cur_partitions[n - 1].remove_cell( ell[2 * n - 1], 2) else: row_num_next = None row_num_bar_next = None if n > 1: self._update_vacancy_numbers(n - 2) if row_num is not None: self.cur_partitions[n - 2].rigging[row_num] = self.cur_partitions[ n - 2].vacancy_numbers[row_num] if row_num_bar is not None: self.cur_partitions[ n - 2].rigging[row_num_bar] = self.cur_partitions[ n - 2].vacancy_numbers[row_num_bar] self._update_vacancy_numbers(n - 1) if row_num_next is not None: self.cur_partitions[n - 1].rigging[row_num_next] = self.cur_partitions[ n - 1].vacancy_numbers[row_num_next] if row_num_bar_next is not None: if case_Q: # This will always be the largest value self.cur_partitions[ n - 1].rigging[row_num_bar_next] = self.cur_partitions[ n - 1].vacancy_numbers[row_num_bar_next] - QQ(1) / QQ(2) else: self.cur_partitions[ n - 1].rigging[row_num_bar_next] = self.cur_partitions[ n - 1].vacancy_numbers[row_num_bar_next] return (b)
def parse_NFelt(K, s): r""" Returns an element of K defined by the string s. """ return K([QQ(c) for c in s.split(",")])
def __init__(self, a, b=None, parent=None, check=True): r""" Create the cusp a/b in `\mathbb{P}^1(\QQ)`, where if b=0 this is the cusp at infinity. When present, b must either be Infinity or coercible to an Integer. EXAMPLES:: sage: Cusp(2,3) 2/3 sage: Cusp(3,6) 1/2 sage: Cusp(1,0) Infinity sage: Cusp(infinity) Infinity sage: Cusp(5) 5 sage: Cusp(1/2) 1/2 sage: Cusp(1.5) 3/2 sage: Cusp(int(7)) 7 sage: Cusp(1, 2, check=False) 1/2 sage: Cusp('sage', 2.5, check=False) # don't do this! sage/2.50000000000000 :: sage: I**2 -1 sage: Cusp(I) Traceback (most recent call last): ... TypeError: unable to convert I to a cusp :: sage: a = Cusp(2,3) sage: loads(a.dumps()) == a True :: sage: Cusp(1/3,0) Infinity sage: Cusp((1,0)) Infinity TESTS:: sage: Cusp("1/3", 5) 1/15 sage: Cusp(Cusp(3/5), 7) 3/35 sage: Cusp(5/3, 0) Infinity sage: Cusp(3,oo) 0 sage: Cusp((7,3), 5) 7/15 sage: Cusp(int(5), 7) 5/7 :: sage: Cusp(0,0) Traceback (most recent call last): ... TypeError: unable to convert (0, 0) to a cusp :: sage: Cusp(oo,oo) Traceback (most recent call last): ... TypeError: unable to convert (+Infinity, +Infinity) to a cusp :: sage: Cusp(Cusp(oo),oo) Traceback (most recent call last): ... TypeError: unable to convert (Infinity, +Infinity) to a cusp """ if parent is None: parent = Cusps Element.__init__(self, parent) if not check: self.__a = a self.__b = b return if b is None: if isinstance(a, Integer): self.__a = a self.__b = ZZ.one() elif isinstance(a, Rational): self.__a = a.numer() self.__b = a.denom() elif is_InfinityElement(a): self.__a = ZZ.one() self.__b = ZZ.zero() elif isinstance(a, Cusp): self.__a = a.__a self.__b = a.__b elif isinstance(a, integer_types): self.__a = ZZ(a) self.__b = ZZ.one() elif isinstance(a, (tuple, list)): if len(a) != 2: raise TypeError("unable to convert %r to a cusp" % a) if ZZ(a[1]) == 0: self.__a = ZZ.one() self.__b = ZZ.zero() return try: r = QQ((a[0], a[1])) self.__a = r.numer() self.__b = r.denom() except (ValueError, TypeError): raise TypeError("unable to convert %r to a cusp" % a) else: try: r = QQ(a) self.__a = r.numer() self.__b = r.denom() except (ValueError, TypeError): raise TypeError("unable to convert %r to a cusp" % a) return if is_InfinityElement(b): if is_InfinityElement(a) or (isinstance(a, Cusp) and a.is_infinity()): raise TypeError("unable to convert (%r, %r) to a cusp" % (a, b)) self.__a = ZZ.zero() self.__b = ZZ.one() return elif not b: if not a: raise TypeError("unable to convert (%r, %r) to a cusp" % (a, b)) self.__a = ZZ.one() self.__b = ZZ.zero() return if isinstance(a, Integer) or isinstance(a, Rational): r = a / ZZ(b) elif is_InfinityElement(a): self.__a = ZZ.one() self.__b = ZZ.zero() return elif isinstance(a, Cusp): if a.__b: r = a.__a / (a.__b * b) else: self.__a = ZZ.one() self.__b = ZZ.zero() return elif isinstance(a, integer_types): r = ZZ(a) / b elif isinstance(a, (tuple, list)): if len(a) != 2: raise TypeError("unable to convert (%r, %r) to a cusp" % (a, b)) r = ZZ(a[0]) / (ZZ(a[1]) * b) else: try: r = QQ(a) / b except (ValueError, TypeError): raise TypeError("unable to convert (%r, %r) to a cusp" % (a, b)) self.__a = r.numer() self.__b = r.denom()
def NFelt_list(a): r""" Return the list representation of the NFelt string. """ return [QorZ_list(QQ(c)) for c in a.split(",")]
import collections from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing from sage.rings.all import NumberField from sage.rings.all import ZZ, QQ, AA, RIF, RR from ore_algebra import DifferentialOperators IVP = collections.namedtuple("IVP", ["dop", "ini"]) DiffOps_a, a, Da = DifferentialOperators(QQ, 'a') koutschan1 = IVP( dop = (1315013644371957611900*a**2+263002728874391522380*a+13150136443719576119)*Da**3 + (2630027288743915223800*a**2+16306169190212274387560*a+1604316646133788286518)*Da**2 + (1315013644371957611900*a**2-39881765316802329075320*a+35449082663034775873349)*Da + (-278967152068515080896550+6575068221859788059500*a), ini = [ QQ(5494216492395559)/3051757812500000000000000000000, QQ(6932746783438351)/610351562500000000000000000000, 1/QQ(2) * QQ(1142339612827789)/19073486328125000000000000000 ] ) y, z = PolynomialRing(QQ, ['y', 'z']).gens() salvy1_pol = (16*y**6*z**2 + 8*y**5*z**3 + y**4*z**4 + 128*y**5*z**2 + 48*y**4*z**3 + 4*y**3*z**4 + 32*y**5* z + 372*y**4*z**2 + 107*y**3*z**3 + 6*y**2*z**4 + 88*y**4*z + 498*y**3*z**2 + 113*y**2*z**3 + 4*y*z**4 + 16*y**4 + 43*y**3*z + 311*y**2*z**2 + 57*y*z**3 + z**4 + 24*y**3 - 43*y**2*z + 72*y*z **2 + 11*z**3 + 12*y**2 - 30*y*z - z**2 + 2*y) DiffOps_z, z, Dz = DifferentialOperators(QQ, 'z') salvy1_dop = ((71820*z**41 + 22638420*z**40 + 706611850*z**39 - 27125189942*z**38 - 1014313164418*z**37 - 2987285491626*z**36 + 143256146804484*z**35 + 595033302717820*z**34 - 8471861990006953*z**33 + 22350994766224977*z**32 + 199821041784996648*z**31 - 11402401137364528368*z**30 -
def simplify(self, x, error=None, force=False, size_heuristic_bound=32): r""" Return a simplified version of ``x``. Produce an element which differs from ``x`` by an element of valuation strictly greater than the valuation of ``x`` (or strictly greater than ``error`` if set.) INPUT: - ``x`` -- an element in the domain of this valuation - ``error`` -- a rational, infinity, or ``None`` (default: ``None``), the error allowed to introduce through the simplification - ``force`` -- ignored - ``size_heuristic_bound`` -- when ``force`` is not set, the expected factor by which the ``x`` need to shrink to perform an actual simplification (default: 32) EXAMPLES:: sage: v = ZZ.valuation(2) sage: v.simplify(6, force=True) 2 sage: v.simplify(6, error=0, force=True) 0 """ if not force and self._relative_size(x) <= size_heuristic_bound: return x x = self.domain().coerce(x) v = self(x) if error is None: error = v from sage.rings.all import infinity if error is infinity: return x if error < v: return self.domain().zero() from sage.rings.all import QQ error = QQ(error).ceil() from sage.rings.all import Qp precision_ring = Qp(self.p(), error + 1 - v) reduced = precision_ring(x) if error - v >= 5: # If there is not much relative precision left, it is better to # just go with the integer/rational lift. The rational # reconstruction is likely not smaller. try: reconstruction = reduced.rational_reconstruction() if reconstruction in self.domain(): return self.domain()(reconstruction) except ArithmeticError: pass return self.domain()(reduced.lift())
def __call__(self, Q, P): """ Compute and return the :class:`ReductionData` corresponding to the genus 2 curve `y^2 + Q(x) y = P(x)`. EXAMPLES:: sage: x = polygen(QQ) sage: genus2reduction(x^3 - 2*x^2 - 2*x + 1, -5*x^5) Reduction data about this proper smooth genus 2 curve: y^2 + (x^3 - 2*x^2 - 2*x + 1)*y = -5*x^5 A Minimal Equation (away from 2): y^2 = x^6 - 240*x^4 - 2550*x^3 - 11400*x^2 - 24100*x - 19855 Minimal Discriminant (away from 2): 56675000 Conductor (away from 2): 1416875 Local Data: p=2 (potential) stable reduction: (II), j=1 p=5 (potential) stable reduction: (I) reduction at p: [V] page 156, (3), f=4 p=2267 (potential) stable reduction: (II), j=432 reduction at p: [I{1-0-0}] page 170, (1), f=1 :: sage: genus2reduction(x^2 + 1, -5*x^5) Reduction data about this proper smooth genus 2 curve: y^2 + (x^2 + 1)*y = -5*x^5 A Minimal Equation (away from 2): y^2 = -20*x^5 + x^4 + 2*x^2 + 1 Minimal Discriminant (away from 2): 48838125 Conductor: 32025 Local Data: p=3 (potential) stable reduction: (II), j=1 reduction at p: [I{1-0-0}] page 170, (1), f=1 p=5 (potential) stable reduction: (IV) reduction at p: [I{1-1-2}] page 182, (5), f=2 p=7 (potential) stable reduction: (II), j=4 reduction at p: [I{1-0-0}] page 170, (1), f=1 p=61 (potential) stable reduction: (II), j=57 reduction at p: [I{2-0-0}] page 170, (2), f=1 Verify that we fix :trac:`5573`:: sage: genus2reduction(x^3 + x^2 + x,-2*x^5 + 3*x^4 - x^3 - x^2 - 6*x - 2) Reduction data about this proper smooth genus 2 curve: y^2 + (x^3 + x^2 + x)*y = -2*x^5 + 3*x^4 - x^3 - x^2 - 6*x - 2 A Minimal Equation (away from 2): y^2 = x^6 + 18*x^3 + 36*x^2 - 27 Minimal Discriminant (away from 2): 1520984142 Conductor: 954 Local Data: p=2 (potential) stable reduction: (II), j=1 reduction at p: [I{1-0-0}] page 170, (1), f=1 p=3 (potential) stable reduction: (I) reduction at p: [II] page 155, (1), f=2 p=53 (potential) stable reduction: (II), j=12 reduction at p: [I{1-0-0}] page 170, (1), f=1 """ R = PolynomialRing(QQ, 'x') P = R(P) Q = R(Q) if P.degree() > 6: raise ValueError("P (=%s) must have degree at most 6"%P) if Q.degree() >=4: raise ValueError("Q (=%s) must have degree at most 3"%Q) res = pari.genus2red([P,Q]) conductor = ZZ(res[0]) minimal_equation = R(res[2]) minimal_disc = QQ(res[2].poldisc()).abs() if minimal_equation.degree() == 5: minimal_disc *= minimal_equation[5]**2 # Multiply with suitable power of 2 of the form 2^(2*(d-1) - 12) b = 2 * (minimal_equation.degree() - 1) k = QQ((12 - minimal_disc.valuation(2), b)).ceil() minimal_disc >>= 12 - b*k minimal_disc = ZZ(minimal_disc) local_data = {} for red in res[3]: p = ZZ(red[0]) t = red[1] data = "(potential) stable reduction: (%s)" % roman_numeral[int(t[0])] t = t[1] if len(t) == 1: data += ", j=%s" % t[0].lift() elif len(t) == 2: data += ", j1+j2=%s, j1*j2=%s" % (t[0].lift(), t[1].lift()) t = red[2] if t: data += "\nreduction at p: %s, " % str(t[0]).replace('"', '').replace("(tame) ", "") data += divisors_to_string(t[1]) + ", f=" + str(res[0].valuation(red[0])) local_data[p] = data prime_to_2_conductor_only = (-1 in res[1].component(2)) return ReductionData(res, P, Q, minimal_equation, minimal_disc, local_data, conductor, prime_to_2_conductor_only)
def kneading_sequence(theta): r""" Determines the kneading sequence for an angle theta in RR/ZZ which is periodic under doubling. We use the definition for the kneading sequence given in Definition 3.2 of [LS1994]_. INPUT: - ``theta`` -- a rational number with odd denominator OUTPUT: a string representing the kneading sequence of theta in RR/ZZ REFERENCES: [LS1994]_ EXAMPLES:: sage: kneading_sequence(0) '*' :: sage: kneading_sequence(1/3) '1*' Since 1/3 and 7/3 are the same in RR/ZZ, they have the same kneading sequence:: sage: kneading_sequence(7/3) '1*' We can also use (finite) decimal inputs, as long as the denominator in reduced form is odd:: sage: kneading_sequence(1.2) '110*' Since rationals with even denominator are not periodic under doubling, we have not implemented kneading sequences for such rationals:: sage: kneading_sequence(1/4) Traceback (most recent call last): ... ValueError: input must be a rational number with odd denominator """ if theta not in QQ: raise TypeError('input must be a rational number with odd denominator') elif QQ(theta).valuation(2) < 0: raise ValueError( 'input must be a rational number with odd denominator') else: theta = QQ(theta) theta = theta - floor(theta) KS = [] not_done = True left = theta / 2 right = (theta + 1) / 2 y = theta while not_done: if ((y < left) or (y > right)): KS.append('0') elif ((y > left) and (y < right)): KS.append('1') else: not_done = False y = 2 * y - floor(2 * y) KS_str = ''.join(KS) + '*' return KS_str