def _dim_new_eisenstein(self): """ Return the dimension of the new Eisenstein subspace, computed by enumerating all Eisenstein series of the appropriate level. EXAMPLES:: sage: m = ModularForms(Gamma0(11), 4) sage: m._dim_new_eisenstein() 0 sage: m = ModularForms(Gamma0(11), 2) sage: m._dim_new_eisenstein() 1 sage: m = ModularForms(DirichletGroup(36).0,5); m Modular Forms space of dimension 28, character [-1, 1] and weight 5 over Rational Field sage: m._dim_new_eisenstein() 2 sage: m._dim_eisenstein() 8 """ if arithgroup.is_Gamma0(self.group()) and self.weight() == 2: if is_prime(self.level()): d = 1 else: d = 0 else: E = self.eisenstein_series() d = len([g for g in E if g.new_level() == self.level()]) return d
def _dim_new_eisenstein(self): """ Compute the dimension of the Eisenstein submodule. EXAMPLES:: sage: m = ModularForms(Gamma0(11), 4) sage: m._dim_new_eisenstein() 0 sage: m = ModularForms(Gamma0(11), 2) sage: m._dim_new_eisenstein() 1 """ try: return self.__the_dim_new_eisenstein except AttributeError: if arithgroup.is_Gamma0(self.group()) and self.weight() == 2: if is_prime(self.level()): d = 1 else: d = 0 else: E = self.eisenstein_series() d = len([g for g in E if g.new_level() == self.level()]) self.__the_dim_new_eisenstein = d return self.__the_dim_new_eisenstein
def apply_sparse(self, x): """ Return the image of x under self. If x is not in self.domain(), raise a TypeError. EXAMPLES: sage: M = ModularSymbols(17,4,-1) sage: T = M.hecke_operator(4) sage: T.apply_sparse(M.0) 64*[X^2,(1,8)] + 24*[X^2,(1,10)] - 9*[X^2,(1,13)] + 37*[X^2,(1,16)] sage: [ T.apply_sparse(x) == T.hecke_module_morphism()(x) for x in M.basis() ] [True, True, True, True] sage: N = ModularSymbols(17,4,1) sage: T.apply_sparse(N.0) Traceback (most recent call last): ... TypeError: x (=[X^2,(0,1)]) must be in Modular Symbols space of dimension 4 for Gamma_0(17) of weight 4 with sign -1 over Rational Field """ if x not in self.domain(): raise TypeError("x (=%s) must be in %s"%(x, self.domain())) # old version just to check for correctness #return self.hecke_module_morphism()(x) p = self.index() if is_prime(p): H = heilbronn.HeilbronnCremona(p) else: H = heilbronn.HeilbronnMerel(p) M = self.parent().module() mod2term = M._mod2term syms = M.manin_symbols() K = M.base_ring() R = M.manin_gens_to_basis() W = R.new_matrix(nrows=1, ncols = R.nrows()) B = M.manin_basis() #from sage.all import cputime #t = cputime() v = x.element() for i in v.nonzero_positions(): for h in H: entries = syms.apply(B[i], h) for k, w in entries: f, s = mod2term[k] if s: W[0,f] += s*K(w)*v[i] #print 'sym', cputime(t) #t = cputime() ans = M( v.parent()((W * R).row(0)) ) #print 'mul', cputime(t) #print 'density: ', len(W.nonzero_positions())/(W.nrows()*float(W.ncols())) return ans
def is_primitive(self, return_base=False): r""" A pillowcase cover is primitive if it does not cover an other pillowcase cover. """ from sage.arith.all import is_prime if is_prime(self.degree()): return True return bool(gap.IsPrimitive(self.monodromy()))
def apply_sparse(self, x): """ Return the image of ``x`` under ``self``. If ``x`` is not in ``self.domain()``, raise a ``TypeError``. EXAMPLES:: sage: M = ModularSymbols(17,4,-1) sage: T = M.hecke_operator(4) sage: T.apply_sparse(M.0) 64*[X^2,(1,8)] + 24*[X^2,(1,10)] - 9*[X^2,(1,13)] + 37*[X^2,(1,16)] sage: [T.apply_sparse(x) == T.hecke_module_morphism()(x) for x in M.basis()] [True, True, True, True] sage: N = ModularSymbols(17,4,1) sage: T.apply_sparse(N.0) Traceback (most recent call last): ... TypeError: x (=[X^2,(0,1)]) must be in Modular Symbols space of dimension 4 for Gamma_0(17) of weight 4 with sign -1 over Rational Field """ if x not in self.domain(): raise TypeError("x (={}) must be in {}".format(x, self.domain())) p = self.index() if is_prime(p): H = heilbronn.HeilbronnCremona(p) else: H = heilbronn.HeilbronnMerel(p) M = self.parent().module() mod2term = M._mod2term syms = M.manin_symbols() K = M.base_ring() R = M.manin_gens_to_basis() W = R.new_matrix(nrows=1, ncols=R.nrows()) B = M.manin_basis() v = x.element() for i in v.nonzero_positions(): for h in H: entries = syms.apply(B[i], h) for k, w in entries: f, s = mod2term[k] if s: W[0, f] += s * K(w) * v[i] return M(v.parent()((W * R).row(0)))
def __init__(self, order, prime, varnames, category=None): """ mod p cohomology of ZZ/p*ZZ """ assert prime == order # TODO: allow prime != order assert is_prime(prime) self._n = order self._varnames = varnames self._prime = prime if category is None: category = YacopLeftModuleAlgebras( SteenrodAlgebra(prime, generic=True)) SteenrodModuleBase.__init__(self, BZpBasis(self), category=category)
def CO_delta(r, p, N, eps): r""" This is used as an intermediate value in computations related to the paper of Cohen-Oesterle. INPUT: - ``r`` - positive integer - ``p`` - a prime - ``N`` - positive integer - ``eps`` - character OUTPUT: element of the base ring of the character EXAMPLES:: sage: G.<eps> = DirichletGroup(7) sage: sage.modular.dims.CO_delta(1,5,7,eps^3) 2 """ if not is_prime(p): raise ValueError("p must be prime") K = eps.base_ring() if p % 4 == 3: return K(0) if p == 2: if r == 1: return K(1) return K(0) # interesting case: p=1(mod 4). # omega is a primitive 4th root of unity mod p. omega = (IntegerModRing(p).unit_gens()[0])**((p - 1) // 4) # this n is within a p-power root of a "local" 4th root of 1 modulo p. n = Mod(int(omega.crt(Mod(1, N // (p**r)))), N) n = n**(p**(r - 1)) # this is correct now t = eps(n) if t == K(1): return K(2) if t == K(-1): return K(-2) return K(0)
def CO_delta(r,p,N,eps): r""" This is used as an intermediate value in computations related to the paper of Cohen-Oesterle. INPUT: - ``r`` - positive integer - ``p`` - a prime - ``N`` - positive integer - ``eps`` - character OUTPUT: element of the base ring of the character EXAMPLES:: sage: G.<eps> = DirichletGroup(7) sage: sage.modular.dims.CO_delta(1,5,7,eps^3) 2 """ if not is_prime(p): raise ValueError("p must be prime") K = eps.base_ring() if p%4 == 3: return K(0) if p==2: if r==1: return K(1) return K(0) # interesting case: p=1(mod 4). # omega is a primitive 4th root of unity mod p. omega = (IntegerModRing(p).unit_gens()[0])**((p-1)//4) # this n is within a p-power root of a "local" 4th root of 1 modulo p. n = Mod(int(omega.crt(Mod(1,N//(p**r)))),N) n = n**(p**(r-1)) # this is correct now t = eps(n) if t==K(1): return K(2) if t==K(-1): return K(-2) return K(0)
def _element_constructor_(self, e): """ TESTS:: sage: P = Sets().example() sage: P._element_constructor_(13) == 13 True sage: P._element_constructor_(13).parent() Integer Ring sage: P._element_constructor_(14) Traceback (most recent call last): ... AssertionError: 14 is not a prime number """ p = self.element_class(e) assert is_prime(p), "%s is not a prime number"%(p) return p
def _element_constructor_(self, e): """ TESTS:: sage: P = Sets().example() sage: P._element_constructor_(13) == 13 True sage: P._element_constructor_(13).parent() Integer Ring sage: P._element_constructor_(14) Traceback (most recent call last): ... AssertionError: 14 is not a prime number """ p = self.element_class(e) assert is_prime(p), "%s is not a prime number" % (p) return p
def eisen(p): """ Return the Eisenstein number `n` which is the numerator of `(p-1)/12`. INPUT: - ``p`` -- a prime OUTPUT: Integer EXAMPLES:: sage: [(p, sage.modular.dims.eisen(p)) for p in prime_range(24)] [(2, 1), (3, 1), (5, 1), (7, 1), (11, 5), (13, 1), (17, 4), (19, 3), (23, 11)] """ if not is_prime(p): raise ValueError("p must be prime") return frac(p - 1, 12).numerator()
def eisen(p): """ Return the Eisenstein number `n` which is the numerator of `(p-1)/12`. INPUT: - ``p`` - a prime OUTPUT: Integer EXAMPLES:: sage: [(p,sage.modular.dims.eisen(p)) for p in prime_range(24)] [(2, 1), (3, 1), (5, 1), (7, 1), (11, 5), (13, 1), (17, 4), (19, 3), (23, 11)] """ if not is_prime(p): raise ValueError("p must be prime") return frac(p-1,12).numerator()
def is_blum_prime(n): r""" Determine whether or not ``n`` is a Blum prime. INPUT: - ``n`` a positive prime. OUTPUT: - ``True`` if ``n`` is a Blum prime; ``False`` otherwise. Let `n` be a positive prime. Then `n` is a Blum prime if `n` is congruent to 3 modulo 4, i.e. `n \equiv 3 \pmod{4}`. EXAMPLES: Testing some integers to see if they are Blum primes:: sage: from sage.crypto.util import is_blum_prime sage: from sage.crypto.util import random_blum_prime sage: is_blum_prime(101) False sage: is_blum_prime(7) True sage: p = random_blum_prime(10**3, 10**5) sage: is_blum_prime(p) True """ if n < 0: return False if is_prime(n): if mod(n, 4).lift() == 3: return True else: return False else: return False
def benchmark(pbound=[3, 2**10], nbound=[3, 2**8], cbound=[1, Infinity], obound=[1, Infinity], loops=10, tloop=Infinity, tmax=Infinity, prime=False, even=False, check=False, fname=None, write=False, overwrite=False, verbose=True, skip_pari=False, skip_magma=False, skip_rains=False, skip_kummer=False): if write: mode = 'w' if overwrite else 'a' f = open(fname, mode, 0) else: f = sys.stdout pmin, pmax = pbound nmin, nmax = nbound omin, omax = obound cmin, cmax = cbound M = Magma() for p in xrange(pmin, pmax): p = ZZ(p) if not p.is_prime(): continue for n in xrange(nmin, nmax): n = ZZ(n) if (prime == 1 and not is_prime(n)) or (prime == 2 and not is_prime_power(n)): continue if n < 2: continue if n % p == 0: continue if (not even) and (n % 2 == 0): continue o, G = find_root_order(p, [n, n], n, verbose=False) m = G[0][0].parent().order() c = Mod(p, n).multiplicative_order() if verbose: sys.stdout.write("\r" + " " * 79) print("\rp = {}, n = {}, (o = {}, c = {})".format(p, n, o, c)) if verbose: t = mytime() sys.stdout.write("Constructing fields ({})".format( time.strftime("%c"))) sys.stdout.flush() q = p**n k = GF(q, name='z') k_rand = GF(q, modulus='random', name='z') k_flint = GF_flint(p, k.modulus(), name='z') if verbose > 1: sys.stdout.write("\ntotal: {}s\n".format(mytime(t))) sys.stdout.flush() # Magma if verbose: sys.stdout.write("\r" + " " * 79) sys.stdout.write("\rMagma ({})".format(time.strftime("%c"))) sys.stdout.flush() tloops = 0 for l in xrange(loops): if skip_magma: break if (o > omax) or (o == p): break # let's assume that launching a new Magma instance is cheaper # than computing random irreducible polynomials try: M._start() except OSError as err: # but it can also cause fork issues... # let's accept this # and fail as the situation will only worsen # unless it is "just" a memory issue # which should be mitigated by COW but is not #print(err) if err.errno == errno.ENOMEM: break else: raise try: k_magma = M(k) k_rand_magma = M(k_rand) if tloop is not Infinity: alarm(tloop) t = mytime() k_magma.Embed(k_rand_magma, nvals=0) #M._eval_line("Embed(k_magma, k_rand_magma);", wait_for_prompt=False) tloops += mytime(t) except TypeError: # sage/magma interface sometimes gets confused pass except (KeyboardInterrupt, AlarmInterrupt): # sage interface eats KeyboardInterrupt # and AlarmInterrupt derives from it tloops = 0 break finally: if tloop is not Infinity: cancel_alarm() M.quit() # sage pexpect interface leaves zombies around try: while os.waitpid(-1, os.WNOHANG)[0]: pass # but sometimes every child is already buried # and we get an ECHILD error... except OSError: pass if tloops > tmax: break tmagma = tloops / (l + 1) if verbose > 1: sys.stdout.write("\ntotal: {}s, per loop: {}s\n".format( tloops, tloops / (l + 1))) sys.stdout.flush() # Rains algorithms if verbose: sys.stdout.write("\r" + " " * 79) sys.stdout.write("\rCyclotomic Rains ({})".format( time.strftime("%c"))) sys.stdout.flush() trains = [] tloops = 0 for l in xrange(loops): if skip_rains: break if (o > omax) or (o == p): break try: if tloop is not Infinity: alarm(tloop) t = mytime() a, b = find_gens_cyclorains(k_flint, k_flint, use_lucas=False) tloops += mytime(t) except (KeyboardInterrupt, AlarmInterrupt): tloops = 0 break finally: if tloop is not Infinity: cancel_alarm() if check and (l == 0 or check > 1): g = a.minpoly() if g.degree() != n: raise RuntimeError("wrong degree") if g != b.minpoly(): raise RuntimeError("different minpolys") if tloops > tmax: break trains.append(tloops / (l + 1)) if verbose > 1: sys.stdout.write("\ntotal: {}s, per loop: {}s\n".format( tloops, tloops / (l + 1))) sys.stdout.flush() # Conic Rains if verbose: sys.stdout.write("\r" + " " * 79) sys.stdout.write("\rConic Rains ({})".format( time.strftime("%c"))) sys.stdout.flush() tloops = 0 for l in xrange(loops): if skip_rains: break if (o != 2) or (o > omax) or (o == p): break try: if tloop is not Infinity: alarm(tloop) t = mytime() a, b = find_gens_cyclorains(k_flint, k_flint, use_lucas=True) tloops += mytime(t) except (KeyboardInterrupt, AlarmInterrupt): tloops = 0 break finally: if tloop is not Infinity: cancel_alarm() if check and (l == 0 or check > 1): g = a.minpoly() if g.degree() != n: raise RuntimeError("wrong degree") if g != b.minpoly(): raise RuntimeError("different minpolys") if tloops > tmax: break trains.append(tloops / (l + 1)) if verbose > 1: sys.stdout.write("\ntotal: {}s, per loop: {}s\n".format( tloops, tloops / (l + 1))) sys.stdout.flush() # Elliptic Rains if verbose: sys.stdout.write("\r" + " " * 79) sys.stdout.write("\rElliptic Rains ({})".format( time.strftime("%c"))) sys.stdout.flush() tloops = 0 for l in xrange(loops): if skip_rains: break try: if tloop is not Infinity: alarm(tloop) t = mytime() a, b = find_gens_ellrains(k_flint, k_flint) tloops += mytime(t) except RuntimeError: # sometimes no suitable elliptic curve exists pass except (KeyboardInterrupt, AlarmInterrupt): tloops = 0 break finally: if tloop is not Infinity: cancel_alarm() if check and (l == 0 or check > 1): g = a.minpoly() if g.degree() != n: raise RuntimeError("wrong degree") if g != b.minpoly(): raise RuntimeError("different minpolys") if tloops > tmax: break trains.append(tloops / (l + 1)) if verbose > 1: sys.stdout.write("\ntotal: {}s, per loop: {}s\n".format( tloops, tloops / (l + 1))) sys.stdout.flush() # PARI/GP if verbose: sys.stdout.write("\r" + " " * 79) sys.stdout.write("\rPARI/GP ({})".format(time.strftime("%c"))) sys.stdout.flush() tloops = 0 for l in xrange(loops): if skip_pari: break if c < cmin or c > cmax: break try: if tloop is not Infinity: alarm(tloop) t = mytime() a, b = find_gens_pari(k, k) tloops += mytime(t) except (KeyboardInterrupt, AlarmInterrupt): tloops = 0 break finally: if tloop is not Infinity: cancel_alarm() if check and (l == 0 or check > 1): g = a.minpoly() if g.degree() != n: raise RuntimeError("wrong degree") if g != b.minpoly(): raise RuntimeError("different minpolys") if tloops > tmax: break tpari = tloops / (l + 1) # Kummer algorithms tkummer = [] # only linalg and modcomp implemented for c==1 for i, algo in enumerate(kummer_algolist[2 * (c == 1):-2 * (c == 1) - 1]): if verbose: sys.stdout.write("\r" + " " * 79) sys.stdout.write("\rKummer {} ({})".format( kummer_namelist[2 * (c == 1) + i], time.strftime("%c"))) sys.stdout.flush() tloops = 0 for l in xrange(loops): if skip_kummer: break if c < cmin or c > cmax: break try: if tloop is not Infinity: alarm(tloop) t = mytime() a, b = find_gens_kummer(k_flint, k_flint, n, algo) tloops += mytime(t) except (KeyboardInterrupt, AlarmInterrupt): tloops = 0 break finally: if tloop is not Infinity: cancel_alarm() if check and (l == 0 or check > 1): g = a.minpoly() if g.degree() != n: raise RuntimeError("wrong degree") if g != b.minpoly(): raise RuntimeError("different minpolys") if tloops > tmax: break tkummer.append(tloops / (l + 1)) if verbose > 1: sys.stdout.write("\ntotal: {}s, per loop: {}s\n".format( tloops, tloops / (l + 1))) sys.stdout.flush() tkummer = 2 * (c == 1) * [0] + tkummer + 2 * (c == 1) * [0] if verbose: sys.stdout.write("\r") sys.stdout.flush() f.write(("{} {} ({}, {})" + " {}" + " {}" * len(trains) + " {}" + " {}" * len(tkummer) + "\n").format( p, n, o, c, *([tmagma] + trains + [tpari] + tkummer))) if write: f.close()
def pthpowers(self, p, Bound): """ Find the indices of proveably all pth powers in the recurrence sequence bounded by Bound. Let `u_n` be a binary recurrence sequence. A ``p`` th power in `u_n` is a solution to `u_n = y^p` for some integer `y`. There are only finitely many ``p`` th powers in any recurrence sequence [SS]. INPUT: - ``p`` - a rational prime integer (the fixed p in `u_n = y^p`) - ``Bound`` - a natural number (the maximum index `n` in `u_n = y^p` that is checked). OUTPUT: - A list of the indices of all ``p`` th powers less bounded by ``Bound``. If the sequence is degenerate and there are many ``p`` th powers, raises ``ValueError``. EXAMPLES:: sage: R = BinaryRecurrenceSequence(1,1) #the Fibonacci sequence sage: R.pthpowers(2, 10**30) # long time (7 seconds) -- in fact these are all squares, c.f. [BMS06] [0, 1, 2, 12] sage: S = BinaryRecurrenceSequence(8,1) #a Lucas sequence sage: S.pthpowers(3,10**30) # long time (3 seconds) -- provably finds the indices of all 3rd powers less than 10^30 [0, 1, 2] sage: Q = BinaryRecurrenceSequence(3,3,2,1) sage: Q.pthpowers(11,10**30) # long time (7.5 seconds) [1] If the sequence is degenerate, and there are are no ``p`` th powers, returns `[]`. Otherwise, if there are many ``p`` th powers, raises ``ValueError``. :: sage: T = BinaryRecurrenceSequence(2,0,1,2) sage: T.is_degenerate() True sage: T.is_geometric() True sage: T.pthpowers(7,10**30) Traceback (most recent call last): ... ValueError: The degenerate binary recurrence sequence is geometric or quasigeometric and has many pth powers. sage: L = BinaryRecurrenceSequence(4,0,2,2) sage: [L(i).factor() for i in range(10)] [2, 2, 2^3, 2^5, 2^7, 2^9, 2^11, 2^13, 2^15, 2^17] sage: L.is_quasigeometric() True sage: L.pthpowers(2,10**30) [] NOTE: This function is primarily optimized in the range where ``Bound`` is much larger than ``p``. """ #Thanks to Jesse Silliman for helpful conversations! #Reset the dictionary of good primes, as this depends on p self._PGoodness = {} #Starting lower bound on good primes self._ell = 1 #If the sequence is geometric, then the `n`th term is `a*r^n`. Thus the #property of being a ``p`` th power is periodic mod ``p``. So there are either #no ``p`` th powers if there are none in the first ``p`` terms, or many if there #is at least one in the first ``p`` terms. if self.is_geometric() or self.is_quasigeometric(): no_powers = True for i in range(1,6*p+1): if _is_p_power(self(i), p) : no_powers = False break if no_powers: if _is_p_power(self.u0,p): return [0] return [] else : raise ValueError("The degenerate binary recurrence sequence is geometric or quasigeometric and has many pth powers.") #If the sequence is degenerate without being geometric or quasigeometric, there #may be many ``p`` th powers or no ``p`` th powers. elif (self.b**2+4*self.c) == 0 : #This is the case if the matrix F is not diagonalizable, ie b^2 +4c = 0, and alpha/beta = 1. alpha = self.b/2 #In this case, u_n = u_0*alpha^n + (u_1 - u_0*alpha)*n*alpha^(n-1) = alpha^(n-1)*(u_0 +n*(u_1 - u_0*alpha)), #that is, it is a geometric term (alpha^(n-1)) times an arithmetic term (u_0 + n*(u_1-u_0*alpha)). #Look at classes n = k mod p, for k = 1,...,p. for k in range(1,p+1): #The linear equation alpha^(k-1)*u_0 + (k+pm)*(alpha^(k-1)*u1 - u0*alpha^k) #must thus be a pth power. This is a linear equation in m, namely, A + B*m, where A = (alpha**(k-1)*self.u0 + k*(alpha**(k-1)*self.u1 - self.u0*alpha**k)) B = p*(alpha**(k-1)*self.u1 - self.u0*alpha**k) #This linear equation represents a pth power iff A is a pth power mod B. if _is_p_power_mod(A, p, B): raise ValueError("The degenerate binary recurrence sequence has many pth powers.") return [] #We find ``p`` th powers using an elementary sieve. Term `u_n` is a ``p`` th #power if and only if it is a ``p`` th power modulo every prime `\\ell`. This condition #gives nontrivial information if ``p`` divides the order of the multiplicative group of #`\\Bold(F)_{\\ell}`, i.e. if `\\ell` is ` 1 \mod{p}`, as then only `1/p` terms are ``p`` th #powers modulo `\\ell``. #Thus, given such an `\\ell`, we get a set of necessary congruences for the index modulo the #the period of the sequence mod `\\ell`. Then we intersect these congruences for many primes #to get a tight list modulo a growing modulus. In order to keep this step manageable, we #only use primes `\\ell` that are have particularly smooth periods. #Some congruences in the list will remain as the modulus grows. If a congruence remains through #7 rounds of increasing the modulus, then we check if this corresponds to a perfect power (if #it does, we add it to our list of indices corresponding to ``p`` th powers). The rest of the congruences #are transient and grow with the modulus. Once the smallest of these is greater than the bound, #the list of known indices corresponding to ``p`` th powers is complete. else: if Bound < 3 * p : powers = [] ell = p + 1 while not is_prime(ell): ell = ell + p F = GF(ell) a0 = F(self.u0); a1 = F(self.u1) #a0 and a1 are variables for terms in sequence bf, cf = F(self.b), F(self.c) for n in range(Bound): # n is the index of the a0 #Check whether a0 is a perfect power mod ell if _is_p_power_mod(a0, p, ell) : #if a0 is a perfect power mod ell, check if nth term is ppower if _is_p_power(self(n), p): powers.append(n) a0, a1 = a1, bf*a1 + cf*a0 #step up the variables else : powers = [] #documents the indices of the sequence that provably correspond to pth powers cong = [0] #list of necessary congruences on the index for it to correspond to pth powers Possible_count = {} #keeps track of the number of rounds a congruence lasts in cong #These parameters are involved in how we choose primes to increase the modulus qqold = 1 #we believe that we know complete information coming from primes good by qqold M1 = 1 #we have congruences modulo M1, this may not be the tightest list M2 = p #we want to move to have congruences mod M2 qq = 1 #the largest prime power divisor of M1 is qq #This loop ups the modulus. while True: #Try to get good data mod M2 #patience of how long we should search for a "good prime" patience = 0.01 * _estimated_time(lcm(M2,p*next_prime_power(qq)), M1, len(cong), p) tries = 0 #This loop uses primes to get a small set of congruences mod M2. while True: #only proceed if took less than patience time to find the next good prime ell = _next_good_prime(p, self, qq, patience, qqold) if ell: #gather congruence data for the sequence mod ell, which will be mod period(ell) = modu cong1, modu = _find_cong1(p, self, ell) CongNew = [] #makes a new list from cong that is now mod M = lcm(M1, modu) instead of M1 M = lcm(M1, modu) for k in range(M // M1): for i in cong: CongNew.append(k * M1 + i) cong = set(CongNew) M1 = M killed_something = False #keeps track of when cong1 can rule out a congruence in cong #CRT by hand to gain speed for i in list(cong): if not (i % modu in cong1): #congruence in cong is inconsistent with any in cong1 cong.remove(i) #remove that congruence killed_something = True if M1 == M2: if not killed_something: tries += 1 if tries == 2: #try twice to rule out congruences cong = list(cong) qqold = qq qq = next_prime_power(qq) M2 = lcm(M2,p*qq) break else : qq = next_prime_power(qq) M2 = lcm(M2,p*qq) cong = list(cong) break #Document how long each element of cong has been there for i in cong: if i in Possible_count: Possible_count[i] = Possible_count[i] + 1 else : Possible_count[i] = 1 #Check how long each element has persisted, if it is for at least 7 cycles, #then we check to see if it is actually a perfect power for i in Possible_count: if Possible_count[i] == 7: n = Integer(i) if n < Bound: if _is_p_power(self(n),p): powers.append(n) #check for a contradiction if len(cong) > len(powers): if cong[len(powers)] > Bound: break elif M1 > Bound: break return powers
def local_genus_symbol(self, p): """ Returns the Conway-Sloane genus symbol of 2 times a quadratic form defined over ZZ at a prime number p. This is defined (in the Genus_Symbol_p_adic_ring() class in the quadratic_forms/genera subfolder) to be a list of tuples (one for each Jordan component p^m*A at p, where A is a unimodular symmetric matrix with coefficients the p-adic integers) of the following form: 1. If p>2 then return triples of the form [`m`, `n`, `d`] where `m` = valuation of the component `n` = rank of A `d` = det(A) in {1,u} for normalized quadratic non-residue u. 2. If p=2 then return quintuples of the form [`m`,`n`,`s`, `d`, `o`] where `m` = valuation of the component `n` = rank of A `d` = det(A) in {1,3,5,7} `s` = 0 (or 1) if A is even (or odd) `o` = oddity of A (= 0 if s = 0) in Z/8Z = the trace of the diagonalization of A NOTE: The Conway-Sloane convention for describing the prime 'p = -1' is not supported here, and neither is the convention for including the 'prime' Infinity. See note on p370 of Conway-Sloane (3rd ed) for a discussion of this convention. INPUT: -`p` -- a prime number > 0 OUTPUT: Returns a Conway-Sloane genus symbol at p, which is an instance of the Genus_Symbol_p_adic_ring class. EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,2,3,4]) sage: Q.local_genus_symbol(2) Genus symbol at 2 : [[1, 2, 3, 1, 4], [2, 1, 1, 1, 1], [3, 1, 1, 1, 1]] sage: Q.local_genus_symbol(3) Genus symbol at 3 : [[0, 3, 1], [1, 1, -1]] sage: Q.local_genus_symbol(5) Genus symbol at 5 : [[0, 4, 1]] """ ## Check that p is prime and that the form is defined over ZZ. if not is_prime(p): raise TypeError("Oops! The number " + str(p) + " isn't prime.") if not self.base_ring() == IntegerRing(): raise TypeError("Oops! The quadratic form is not defined over the integers.") ## Return the result try: M = self.Hessian_matrix() return LocalGenusSymbol(M, p) except Exception: raise TypeError("Oops! There is a problem computing the local genus symbol at the prime " + str(p) + " for this form.")
def local_normal_form(self, p): """ Returns the a locally integrally equivalent quadratic form over the p-adic integers Z_p which gives the Jordan decomposition. The Jordan components are written as sums of blocks of size <= 2 and are arranged by increasing scale, and then by increasing norm. (This is equivalent to saying that we put the 1x1 blocks before the 2x2 blocks in each Jordan component.) INPUT: `p` -- a positive prime number. OUTPUT: a quadratic form over ZZ WARNING: Currently this only works for quadratic forms defined over ZZ. EXAMPLES:: sage: Q = QuadraticForm(ZZ, 2, [10,4,1]) sage: Q.local_normal_form(5) Quadratic form in 2 variables over Integer Ring with coefficients: [ 1 0 ] [ * 6 ] :: sage: Q.local_normal_form(3) Quadratic form in 2 variables over Integer Ring with coefficients: [ 10 0 ] [ * 15 ] sage: Q.local_normal_form(2) Quadratic form in 2 variables over Integer Ring with coefficients: [ 1 0 ] [ * 6 ] """ ## Sanity Checks if (self.base_ring() != IntegerRing()): raise NotImplementedError( "Oops! This currently only works for quadratic forms defined over IntegerRing(). =(" ) if not ((p >= 2) and is_prime(p)): raise TypeError("Oops! p is not a positive prime number. =(") ## Some useful local variables Q = copy.deepcopy(self) Q.__init__(self.base_ring(), self.dim(), self.coefficients()) ## Prepare the final form to return Q_Jordan = copy.deepcopy(self) Q_Jordan.__init__(self.base_ring(), 0) while Q.dim() > 0: n = Q.dim() ## Step 1: Find the minimally p-divisible matrix entry, preferring diagonals ## ------------------------------------------------------------------------- (min_i, min_j) = Q.find_entry_with_minimal_scale_at_prime(p) if min_i == min_j: min_val = valuation(2 * Q[min_i, min_j], p) else: min_val = valuation(Q[min_i, min_j], p) ## Error if we still haven't seen non-zero coefficients! if (min_val == Infinity): raise RuntimeError("Oops! The original matrix is degenerate. =(") ## Step 2: Arrange for the upper leftmost entry to have minimal valuation ## ---------------------------------------------------------------------- if (min_i == min_j): block_size = 1 Q.swap_variables(0, min_i, in_place=True) else: ## Work in the upper-left 2x2 block, and replace it by its 2-adic equivalent form Q.swap_variables(0, min_i, in_place=True) Q.swap_variables(1, min_j, in_place=True) ## 1x1 => make upper left the smallest if (p != 2): block_size = 1 Q.add_symmetric(1, 0, 1, in_place=True) ## 2x2 => replace it with the appropriate 2x2 matrix else: block_size = 2 ## DIAGNOSTIC #print "\n Finished Step 2 \n"; #print "\n Q is: \n" + str(Q) + "\n"; #print " p is: " + str(p) #print " min_val is: " + str( min_val) #print " block_size is: " + str(block_size) #print "\n Starting Step 3 \n" ## Step 3: Clear out the remaining entries ## --------------------------------------- min_scale = p**min_val ## This is the minimal valuation of the Hessian matrix entries. ##DIAGNOSTIC #print "Starting Step 3:" #print "----------------" #print " min_scale is: " + str(min_scale) ## Perform cancellation over Z by ensuring divisibility if (block_size == 1): a = 2 * Q[0, 0] for j in range(block_size, n): b = Q[0, j] g = GCD(a, b) ## DIAGNSOTIC #print "Cancelling from a 1x1 block:" #print "----------------------------" #print " Cancelling entry with index (" + str(upper_left) + ", " + str(j) + ")" #print " entry = " + str(b) #print " gcd = " + str(g) #print " a = " + str(a) #print " b = " + str(b) #print " a/g = " + str(a/g) + " (used for stretching)" #print " -b/g = " + str(-b/g) + " (used for cancelling)" ## Sanity Check: a/g is a p-unit if valuation(g, p) != valuation(a, p): raise RuntimeError( "Oops! We have a problem with our rescaling not preserving p-integrality!" ) Q.multiply_variable( ZZ(a / g), j, in_place=True ) ## Ensures that the new b entry is divisible by a Q.add_symmetric(ZZ(-b / g), j, 0, in_place=True) ## Performs the cancellation elif (block_size == 2): a1 = 2 * Q[0, 0] a2 = Q[0, 1] b1 = Q[1, 0] ## This is the same as a2 b2 = 2 * Q[1, 1] big_det = (a1 * b2 - a2 * b1) small_det = big_det / (min_scale * min_scale) ## Cancels out the rows/columns of the 2x2 block for j in range(block_size, n): a = Q[0, j] b = Q[1, j] ## Ensures an integral result (scale jth row/column by big_det) Q.multiply_variable(big_det, j, in_place=True) ## Performs the cancellation (by producing -big_det * jth row/column) Q.add_symmetric(ZZ(-(a * b2 - b * a2)), j, 0, in_place=True) Q.add_symmetric(ZZ(-(-a * b1 + b * a1)), j, 1, in_place=True) ## Now remove the extra factor (non p-unit factor) in big_det we introduced above Q.divide_variable(ZZ(min_scale * min_scale), j, in_place=True) ## DIAGNOSTIC #print "Cancelling out a 2x2 block:" #print "---------------------------" #print " a1 = " + str(a1) #print " a2 = " + str(a2) #print " b1 = " + str(b1) #print " b2 = " + str(b2) #print " big_det = " + str(big_det) #print " min_scale = " + str(min_scale) #print " small_det = " + str(small_det) #print " Q = \n", Q ## Uses Cassels's proof to replace the remaining 2 x 2 block if (((1 + small_det) % 8) == 0): Q[0, 0] = 0 Q[1, 1] = 0 Q[0, 1] = min_scale elif (((5 + small_det) % 8) == 0): Q[0, 0] = min_scale Q[1, 1] = min_scale Q[0, 1] = min_scale else: raise RuntimeError( "Error in LocalNormal: Impossible behavior for a 2x2 block! \n" ) ## Check that the cancellation worked, extract the upper-left block, and trim Q to handle the next block. for i in range(block_size): for j in range(block_size, n): if Q[i, j] != 0: raise RuntimeError( "Oops! The cancellation didn't work properly at entry (" + str(i) + ", " + str(j) + ").") Q_Jordan = Q_Jordan + Q.extract_variables(range(block_size)) Q = Q.extract_variables(range(block_size, n)) return Q_Jordan
def benchmark(pbound = [3, 2**10], nbound = [3, 2**8], cbound = [1, Infinity], obound = [1, Infinity], loops = 10, tloop = Infinity, tmax = Infinity, prime = False, even = False, check = False, fname = None, write = False, overwrite = False, verbose = True, skip_pari = False, skip_magma = False, skip_rains = False, skip_kummer = False): if write: mode = 'w' if overwrite else 'a' f = open(fname, mode, 0) else: f = sys.stdout pmin, pmax = pbound nmin, nmax = nbound omin, omax = obound cmin, cmax = cbound M = Magma() for p in xrange(pmin, pmax): p = ZZ(p) if not p.is_prime(): continue for n in xrange(nmin, nmax): n = ZZ(n) if (prime == 1 and not is_prime(n)) or (prime == 2 and not is_prime_power(n)): continue if n < 2: continue if n % p == 0: continue if (not even) and (n % 2 == 0): continue o, G = find_root_order(p, [n, n], n, verbose=False) m = G[0][0].parent().order() c = Mod(p,n).multiplicative_order() if verbose: sys.stdout.write("\r"+" "*79) print("\rp = {}, n = {}, (o = {}, c = {})".format(p, n, o, c)) if verbose: t = mytime() sys.stdout.write("Constructing fields ({})".format(time.strftime("%c"))) sys.stdout.flush() q = p**n k = GF(q, name='z') k_rand = GF(q, modulus='random', name='z') k_flint = GF_flint(p, k.modulus(), name='z') if verbose > 1: sys.stdout.write("\ntotal: {}s\n".format(mytime(t))) sys.stdout.flush() # Magma if verbose: sys.stdout.write("\r"+" "*79) sys.stdout.write("\rMagma ({})".format(time.strftime("%c"))) sys.stdout.flush() tloops = 0 for l in xrange(loops): if skip_magma: break if (o > omax) or (o == p): break # let's assume that launching a new Magma instance is cheaper # than computing random irreducible polynomials try: M._start() except OSError as err: # but it can also cause fork issues... # let's accept this # and fail as the situation will only worsen # unless it is "just" a memory issue # which should be mitigated by COW but is not #print(err) if err.errno == errno.ENOMEM: break else: raise try: k_magma = M(k) k_rand_magma = M(k_rand) if tloop is not Infinity: alarm(tloop) t = mytime() k_magma.Embed(k_rand_magma, nvals=0) #M._eval_line("Embed(k_magma, k_rand_magma);", wait_for_prompt=False) tloops += mytime(t) except TypeError: # sage/magma interface sometimes gets confused pass except (KeyboardInterrupt, AlarmInterrupt): # sage interface eats KeyboardInterrupt # and AlarmInterrupt derives from it tloops = 0 break finally: if tloop is not Infinity: cancel_alarm() M.quit() # sage pexpect interface leaves zombies around try: while os.waitpid(-1, os.WNOHANG)[0]: pass # but sometimes every child is already buried # and we get an ECHILD error... except OSError: pass if tloops > tmax: break tmagma = tloops / (l+1) if verbose > 1: sys.stdout.write("\ntotal: {}s, per loop: {}s\n".format(tloops, tloops/(l+1))) sys.stdout.flush() # Rains algorithms if verbose: sys.stdout.write("\r"+" "*79) sys.stdout.write("\rCyclotomic Rains ({})".format(time.strftime("%c"))) sys.stdout.flush() trains = [] tloops = 0 for l in xrange(loops): if skip_rains: break if (o > omax) or (o == p): break try: if tloop is not Infinity: alarm(tloop) t = mytime() a, b = find_gens_cyclorains(k_flint, k_flint, use_lucas = False) tloops += mytime(t) except (KeyboardInterrupt, AlarmInterrupt): tloops = 0 break finally: if tloop is not Infinity: cancel_alarm() if check and (l == 0 or check > 1): g = a.minpoly() if g.degree() != n: raise RuntimeError("wrong degree") if g != b.minpoly(): raise RuntimeError("different minpolys") if tloops > tmax: break trains.append(tloops / (l+1)) if verbose > 1: sys.stdout.write("\ntotal: {}s, per loop: {}s\n".format(tloops, tloops/(l+1))) sys.stdout.flush() # Conic Rains if verbose: sys.stdout.write("\r"+" "*79) sys.stdout.write("\rConic Rains ({})".format(time.strftime("%c"))) sys.stdout.flush() tloops = 0 for l in xrange(loops): if skip_rains: break if (o != 2) or (o > omax) or (o == p): break try: if tloop is not Infinity: alarm(tloop) t = mytime() a, b = find_gens_cyclorains(k_flint, k_flint, use_lucas = True) tloops += mytime(t) except (KeyboardInterrupt, AlarmInterrupt): tloops = 0 break finally: if tloop is not Infinity: cancel_alarm() if check and (l == 0 or check > 1): g = a.minpoly() if g.degree() != n: raise RuntimeError("wrong degree") if g != b.minpoly(): raise RuntimeError("different minpolys") if tloops > tmax: break trains.append(tloops / (l+1)) if verbose > 1: sys.stdout.write("\ntotal: {}s, per loop: {}s\n".format(tloops, tloops/(l+1))) sys.stdout.flush() # Elliptic Rains if verbose: sys.stdout.write("\r"+" "*79) sys.stdout.write("\rElliptic Rains ({})".format(time.strftime("%c"))) sys.stdout.flush() tloops = 0 for l in xrange(loops): if skip_rains: break try: if tloop is not Infinity: alarm(tloop) t = mytime() a, b = find_gens_ellrains(k_flint, k_flint) tloops += mytime(t) except RuntimeError: # sometimes no suitable elliptic curve exists pass except (KeyboardInterrupt, AlarmInterrupt): tloops = 0 break finally: if tloop is not Infinity: cancel_alarm() if check and (l == 0 or check > 1): g = a.minpoly() if g.degree() != n: raise RuntimeError("wrong degree") if g != b.minpoly(): raise RuntimeError("different minpolys") if tloops > tmax: break trains.append(tloops / (l+1)) if verbose > 1: sys.stdout.write("\ntotal: {}s, per loop: {}s\n".format(tloops, tloops/(l+1))) sys.stdout.flush() # PARI/GP if verbose: sys.stdout.write("\r"+" "*79) sys.stdout.write("\rPARI/GP ({})".format(time.strftime("%c"))) sys.stdout.flush() tloops = 0 for l in xrange(loops): if skip_pari: break if c < cmin or c > cmax: break try: if tloop is not Infinity: alarm(tloop) t = mytime() a, b = find_gens_pari(k, k) tloops += mytime(t) except (KeyboardInterrupt, AlarmInterrupt): tloops = 0 break finally: if tloop is not Infinity: cancel_alarm() if check and (l == 0 or check > 1): g = a.minpoly() if g.degree() != n: raise RuntimeError("wrong degree") if g != b.minpoly(): raise RuntimeError("different minpolys") if tloops > tmax: break tpari = tloops / (l+1) # Kummer algorithms tkummer = [] # only linalg and modcomp implemented for c==1 for i, algo in enumerate(kummer_algolist[2*(c==1):-2*(c==1)-1]): if verbose: sys.stdout.write("\r"+" "*79) sys.stdout.write("\rKummer {} ({})".format(kummer_namelist[2*(c==1)+i], time.strftime("%c"))) sys.stdout.flush() tloops = 0 for l in xrange(loops): if skip_kummer: break if c < cmin or c > cmax: break try: if tloop is not Infinity: alarm(tloop) t = mytime() a, b = find_gens_kummer(k_flint, k_flint, n, algo) tloops += mytime(t) except (KeyboardInterrupt, AlarmInterrupt): tloops = 0 break finally: if tloop is not Infinity: cancel_alarm() if check and (l == 0 or check > 1): g = a.minpoly() if g.degree() != n: raise RuntimeError("wrong degree") if g != b.minpoly(): raise RuntimeError("different minpolys") if tloops > tmax: break tkummer.append(tloops / (l+1)) if verbose > 1: sys.stdout.write("\ntotal: {}s, per loop: {}s\n".format(tloops, tloops/(l+1))) sys.stdout.flush() tkummer = 2*(c == 1)*[0] + tkummer + 2*(c == 1)*[0] if verbose: sys.stdout.write("\r") sys.stdout.flush() f.write(("{} {} ({}, {})" + " {}" + " {}"*len(trains) + " {}" + " {}"*len(tkummer)+"\n").format(p, n, o, c, *([tmagma] + trains + [tpari] + tkummer))) if write: f.close()
def local_genus_symbol(self, p): """ Returns the Conway-Sloane genus symbol of 2 times a quadratic form defined over ZZ at a prime number p. This is defined (in the Genus_Symbol_p_adic_ring() class in the quadratic_forms/genera subfolder) to be a list of tuples (one for each Jordan component p^m*A at p, where A is a unimodular symmetric matrix with coefficients the p-adic integers) of the following form: 1. If p>2 then return triples of the form [`m`, `n`, `d`] where `m` = valuation of the component `n` = rank of A `d` = det(A) in {1,u} for normalized quadratic non-residue u. 2. If p=2 then return quintuples of the form [`m`,`n`,`s`, `d`, `o`] where `m` = valuation of the component `n` = rank of A `d` = det(A) in {1,3,5,7} `s` = 0 (or 1) if A is even (or odd) `o` = oddity of A (= 0 if s = 0) in Z/8Z = the trace of the diagonalization of A NOTE: The Conway-Sloane convention for describing the prime 'p = -1' is not supported here, and neither is the convention for including the 'prime' Infinity. See note on p370 of Conway-Sloane (3rd ed) for a discussion of this convention. INPUT: -`p` -- a prime number > 0 OUTPUT: Returns a Conway-Sloane genus symbol at p, which is an instance of the Genus_Symbol_p_adic_ring class. EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,2,3,4]) sage: Q.local_genus_symbol(2) Genus symbol at 2: [2^-2 4^1 8^1]_6 sage: Q.local_genus_symbol(3) Genus symbol at 3: 1^3 3^-1 sage: Q.local_genus_symbol(5) Genus symbol at 5: 1^4 """ ## Check that p is prime and that the form is defined over ZZ. if not is_prime(p): raise TypeError("Oops! The number " + str(p) + " isn't prime.") if not self.base_ring() == IntegerRing(): raise TypeError("Oops! The quadratic form is not defined over the integers.") ## Return the result try: M = self.Hessian_matrix() return LocalGenusSymbol(M, p) except Exception: raise TypeError("Oops! There is a problem computing the local genus symbol at the prime " + str(p) + " for this form.")
def pthpowers(self, p, Bound): """ Find the indices of proveably all pth powers in the recurrence sequence bounded by Bound. Let `u_n` be a binary recurrence sequence. A ``p`` th power in `u_n` is a solution to `u_n = y^p` for some integer `y`. There are only finitely many ``p`` th powers in any recurrence sequence [SS]. INPUT: - ``p`` - a rational prime integer (the fixed p in `u_n = y^p`) - ``Bound`` - a natural number (the maximum index `n` in `u_n = y^p` that is checked). OUTPUT: - A list of the indices of all ``p`` th powers less bounded by ``Bound``. If the sequence is degenerate and there are many ``p`` th powers, raises ``ValueError``. EXAMPLES:: sage: R = BinaryRecurrenceSequence(1,1) #the Fibonacci sequence sage: R.pthpowers(2, 10**30) # long time (7 seconds) -- in fact these are all squares, c.f. [BMS06] [0, 1, 2, 12] sage: S = BinaryRecurrenceSequence(8,1) #a Lucas sequence sage: S.pthpowers(3,10**30) # long time (3 seconds) -- provably finds the indices of all 3rd powers less than 10^30 [0, 1, 2] sage: Q = BinaryRecurrenceSequence(3,3,2,1) sage: Q.pthpowers(11,10**30) # long time (7.5 seconds) [1] If the sequence is degenerate, and there are are no ``p`` th powers, returns `[]`. Otherwise, if there are many ``p`` th powers, raises ``ValueError``. :: sage: T = BinaryRecurrenceSequence(2,0,1,2) sage: T.is_degenerate() True sage: T.is_geometric() True sage: T.pthpowers(7,10**30) Traceback (most recent call last): ... ValueError: The degenerate binary recurrence sequence is geometric or quasigeometric and has many pth powers. sage: L = BinaryRecurrenceSequence(4,0,2,2) sage: [L(i).factor() for i in range(10)] [2, 2, 2^3, 2^5, 2^7, 2^9, 2^11, 2^13, 2^15, 2^17] sage: L.is_quasigeometric() True sage: L.pthpowers(2,10**30) [] NOTE: This function is primarily optimized in the range where ``Bound`` is much larger than ``p``. """ #Thanks to Jesse Silliman for helpful conversations! #Reset the dictionary of good primes, as this depends on p self._PGoodness = {} #Starting lower bound on good primes self._ell = 1 #If the sequence is geometric, then the `n`th term is `a*r^n`. Thus the #property of being a ``p`` th power is periodic mod ``p``. So there are either #no ``p`` th powers if there are none in the first ``p`` terms, or many if there #is at least one in the first ``p`` terms. if self.is_geometric() or self.is_quasigeometric(): no_powers = True for i in range(1, 6 * p + 1): if _is_p_power(self(i), p): no_powers = False break if no_powers: if _is_p_power(self.u0, p): return [0] return [] else: raise ValueError( "The degenerate binary recurrence sequence is geometric or quasigeometric and has many pth powers." ) #If the sequence is degenerate without being geometric or quasigeometric, there #may be many ``p`` th powers or no ``p`` th powers. elif (self.b**2 + 4 * self.c) == 0: #This is the case if the matrix F is not diagonalizable, ie b^2 +4c = 0, and alpha/beta = 1. alpha = self.b / 2 #In this case, u_n = u_0*alpha^n + (u_1 - u_0*alpha)*n*alpha^(n-1) = alpha^(n-1)*(u_0 +n*(u_1 - u_0*alpha)), #that is, it is a geometric term (alpha^(n-1)) times an arithmetic term (u_0 + n*(u_1-u_0*alpha)). #Look at classes n = k mod p, for k = 1,...,p. for k in range(1, p + 1): #The linear equation alpha^(k-1)*u_0 + (k+pm)*(alpha^(k-1)*u1 - u0*alpha^k) #must thus be a pth power. This is a linear equation in m, namely, A + B*m, where A = (alpha**(k - 1) * self.u0 + k * (alpha**(k - 1) * self.u1 - self.u0 * alpha**k)) B = p * (alpha**(k - 1) * self.u1 - self.u0 * alpha**k) #This linear equation represents a pth power iff A is a pth power mod B. if _is_p_power_mod(A, p, B): raise ValueError( "The degenerate binary recurrence sequence has many pth powers." ) return [] #We find ``p`` th powers using an elementary sieve. Term `u_n` is a ``p`` th #power if and only if it is a ``p`` th power modulo every prime `\\ell`. This condition #gives nontrivial information if ``p`` divides the order of the multiplicative group of #`\\Bold(F)_{\\ell}`, i.e. if `\\ell` is ` 1 \mod{p}`, as then only `1/p` terms are ``p`` th #powers modulo `\\ell``. #Thus, given such an `\\ell`, we get a set of necessary congruences for the index modulo the #the period of the sequence mod `\\ell`. Then we intersect these congruences for many primes #to get a tight list modulo a growing modulus. In order to keep this step manageable, we #only use primes `\\ell` that are have particularly smooth periods. #Some congruences in the list will remain as the modulus grows. If a congruence remains through #7 rounds of increasing the modulus, then we check if this corresponds to a perfect power (if #it does, we add it to our list of indices corresponding to ``p`` th powers). The rest of the congruences #are transient and grow with the modulus. Once the smallest of these is greater than the bound, #the list of known indices corresponding to ``p`` th powers is complete. else: if Bound < 3 * p: powers = [] ell = p + 1 while not is_prime(ell): ell = ell + p F = GF(ell) a0 = F(self.u0) a1 = F(self.u1) #a0 and a1 are variables for terms in sequence bf, cf = F(self.b), F(self.c) for n in range(Bound): # n is the index of the a0 #Check whether a0 is a perfect power mod ell if _is_p_power_mod(a0, p, ell): #if a0 is a perfect power mod ell, check if nth term is ppower if _is_p_power(self(n), p): powers.append(n) a0, a1 = a1, bf * a1 + cf * a0 #step up the variables else: powers = [ ] #documents the indices of the sequence that provably correspond to pth powers cong = [ 0 ] #list of necessary congruences on the index for it to correspond to pth powers Possible_count = { } #keeps track of the number of rounds a congruence lasts in cong #These parameters are involved in how we choose primes to increase the modulus qqold = 1 #we believe that we know complete information coming from primes good by qqold M1 = 1 #we have congruences modulo M1, this may not be the tightest list M2 = p #we want to move to have congruences mod M2 qq = 1 #the largest prime power divisor of M1 is qq #This loop ups the modulus. while True: #Try to get good data mod M2 #patience of how long we should search for a "good prime" patience = 0.01 * _estimated_time( lcm(M2, p * next_prime_power(qq)), M1, len(cong), p) tries = 0 #This loop uses primes to get a small set of congruences mod M2. while True: #only proceed if took less than patience time to find the next good prime ell = _next_good_prime(p, self, qq, patience, qqold) if ell: #gather congruence data for the sequence mod ell, which will be mod period(ell) = modu cong1, modu = _find_cong1(p, self, ell) CongNew = [ ] #makes a new list from cong that is now mod M = lcm(M1, modu) instead of M1 M = lcm(M1, modu) for k in range(M // M1): for i in cong: CongNew.append(k * M1 + i) cong = set(CongNew) M1 = M killed_something = False #keeps track of when cong1 can rule out a congruence in cong #CRT by hand to gain speed for i in list(cong): if not ( i % modu in cong1 ): #congruence in cong is inconsistent with any in cong1 cong.remove(i) #remove that congruence killed_something = True if M1 == M2: if not killed_something: tries += 1 if tries == 2: #try twice to rule out congruences cong = list(cong) qqold = qq qq = next_prime_power(qq) M2 = lcm(M2, p * qq) break else: qq = next_prime_power(qq) M2 = lcm(M2, p * qq) cong = list(cong) break #Document how long each element of cong has been there for i in cong: if i in Possible_count: Possible_count[i] = Possible_count[i] + 1 else: Possible_count[i] = 1 #Check how long each element has persisted, if it is for at least 7 cycles, #then we check to see if it is actually a perfect power for i in Possible_count: if Possible_count[i] == 7: n = Integer(i) if n < Bound: if _is_p_power(self(n), p): powers.append(n) #check for a contradiction if len(cong) > len(powers): if cong[len(powers)] > Bound: break elif M1 > Bound: break return powers
def local_normal_form(self, p): """ Returns the a locally integrally equivalent quadratic form over the p-adic integers Z_p which gives the Jordan decomposition. The Jordan components are written as sums of blocks of size <= 2 and are arranged by increasing scale, and then by increasing norm. (This is equivalent to saying that we put the 1x1 blocks before the 2x2 blocks in each Jordan component.) INPUT: `p` -- a positive prime number. OUTPUT: a quadratic form over ZZ WARNING: Currently this only works for quadratic forms defined over ZZ. EXAMPLES:: sage: Q = QuadraticForm(ZZ, 2, [10,4,1]) sage: Q.local_normal_form(5) Quadratic form in 2 variables over Integer Ring with coefficients: [ 1 0 ] [ * 6 ] :: sage: Q.local_normal_form(3) Quadratic form in 2 variables over Integer Ring with coefficients: [ 10 0 ] [ * 15 ] sage: Q.local_normal_form(2) Quadratic form in 2 variables over Integer Ring with coefficients: [ 1 0 ] [ * 6 ] """ ## Sanity Checks if (self.base_ring() != IntegerRing()): raise NotImplementedError("Oops! This currently only works for quadratic forms defined over IntegerRing(). =(") if not ((p>=2) and is_prime(p)): raise TypeError("Oops! p is not a positive prime number. =(") ## Some useful local variables Q = copy.deepcopy(self) Q.__init__(self.base_ring(), self.dim(), self.coefficients()) ## Prepare the final form to return Q_Jordan = copy.deepcopy(self) Q_Jordan.__init__(self.base_ring(), 0) while Q.dim() > 0: n = Q.dim() ## Step 1: Find the minimally p-divisible matrix entry, preferring diagonals ## ------------------------------------------------------------------------- (min_i, min_j) = Q.find_entry_with_minimal_scale_at_prime(p) if min_i == min_j: min_val = valuation(2 * Q[min_i, min_j], p) else: min_val = valuation(Q[min_i, min_j], p) ## Error if we still haven't seen non-zero coefficients! if (min_val == Infinity): raise RuntimeError("Oops! The original matrix is degenerate. =(") ## Step 2: Arrange for the upper leftmost entry to have minimal valuation ## ---------------------------------------------------------------------- if (min_i == min_j): block_size = 1 Q.swap_variables(0, min_i, in_place = True) else: ## Work in the upper-left 2x2 block, and replace it by its 2-adic equivalent form Q.swap_variables(0, min_i, in_place = True) Q.swap_variables(1, min_j, in_place = True) ## 1x1 => make upper left the smallest if (p != 2): block_size = 1; Q.add_symmetric(1, 0, 1, in_place = True) ## 2x2 => replace it with the appropriate 2x2 matrix else: block_size = 2 ## DIAGNOSTIC #print "\n Finished Step 2 \n"; #print "\n Q is: \n" + str(Q) + "\n"; #print " p is: " + str(p) #print " min_val is: " + str( min_val) #print " block_size is: " + str(block_size) #print "\n Starting Step 3 \n" ## Step 3: Clear out the remaining entries ## --------------------------------------- min_scale = p ** min_val ## This is the minimal valuation of the Hessian matrix entries. ##DIAGNOSTIC #print "Starting Step 3:" #print "----------------" #print " min_scale is: " + str(min_scale) ## Perform cancellation over Z by ensuring divisibility if (block_size == 1): a = 2 * Q[0,0] for j in range(block_size, n): b = Q[0, j] g = GCD(a, b) ## DIAGNSOTIC #print "Cancelling from a 1x1 block:" #print "----------------------------" #print " Cancelling entry with index (" + str(upper_left) + ", " + str(j) + ")" #print " entry = " + str(b) #print " gcd = " + str(g) #print " a = " + str(a) #print " b = " + str(b) #print " a/g = " + str(a/g) + " (used for stretching)" #print " -b/g = " + str(-b/g) + " (used for cancelling)" ## Sanity Check: a/g is a p-unit if valuation (g, p) != valuation(a, p): raise RuntimeError("Oops! We have a problem with our rescaling not preserving p-integrality!") Q.multiply_variable(ZZ(a/g), j, in_place = True) ## Ensures that the new b entry is divisible by a Q.add_symmetric(ZZ(-b/g), j, 0, in_place = True) ## Performs the cancellation elif (block_size == 2): a1 = 2 * Q[0,0] a2 = Q[0, 1] b1 = Q[1, 0] ## This is the same as a2 b2 = 2 * Q[1, 1] big_det = (a1*b2 - a2*b1) small_det = big_det / (min_scale * min_scale) ## Cancels out the rows/columns of the 2x2 block for j in range(block_size, n): a = Q[0, j] b = Q[1, j] ## Ensures an integral result (scale jth row/column by big_det) Q.multiply_variable(big_det, j, in_place = True) ## Performs the cancellation (by producing -big_det * jth row/column) Q.add_symmetric(ZZ(-(a*b2 - b*a2)), j, 0, in_place = True) Q.add_symmetric(ZZ(-(-a*b1 + b*a1)), j, 1, in_place = True) ## Now remove the extra factor (non p-unit factor) in big_det we introduced above Q.divide_variable(ZZ(min_scale * min_scale), j, in_place = True) ## DIAGNOSTIC #print "Cancelling out a 2x2 block:" #print "---------------------------" #print " a1 = " + str(a1) #print " a2 = " + str(a2) #print " b1 = " + str(b1) #print " b2 = " + str(b2) #print " big_det = " + str(big_det) #print " min_scale = " + str(min_scale) #print " small_det = " + str(small_det) #print " Q = \n", Q ## Uses Cassels's proof to replace the remaining 2 x 2 block if (((1 + small_det) % 8) == 0): Q[0, 0] = 0 Q[1, 1] = 0 Q[0, 1] = min_scale elif (((5 + small_det) % 8) == 0): Q[0, 0] = min_scale Q[1, 1] = min_scale Q[0, 1] = min_scale else: raise RuntimeError("Error in LocalNormal: Impossible behavior for a 2x2 block! \n") ## Check that the cancellation worked, extract the upper-left block, and trim Q to handle the next block. for i in range(block_size): for j in range(block_size, n): if Q[i,j] != 0: raise RuntimeError("Oops! The cancellation didn't work properly at entry (" + str(i) + ", " + str(j) + ").") Q_Jordan = Q_Jordan + Q.extract_variables(range(block_size)) Q = Q.extract_variables(range(block_size, n)) return Q_Jordan
def has_equivalent_Jordan_decomposition_at_prime(self, other, p): """ Determines if the given quadratic form has a Jordan decomposition equivalent to that of self. INPUT: a QuadraticForm OUTPUT: boolean EXAMPLES:: sage: Q1 = QuadraticForm(ZZ, 3, [1, 0, -1, 1, 0, 3]) sage: Q2 = QuadraticForm(ZZ, 3, [1, 0, 0, 2, -2, 6]) sage: Q3 = QuadraticForm(ZZ, 3, [1, 0, 0, 1, 0, 11]) sage: [Q1.level(), Q2.level(), Q3.level()] [44, 44, 44] sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q2,2) False sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q2,11) False sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q3,2) False sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q3,11) True sage: Q2.has_equivalent_Jordan_decomposition_at_prime(Q3,2) True sage: Q2.has_equivalent_Jordan_decomposition_at_prime(Q3,11) False """ ## Sanity Checks #if not isinstance(other, QuadraticForm): if not isinstance(other, type(self)): raise TypeError("Oops! The first argument must be of type QuadraticForm.") if not is_prime(p): raise TypeError("Oops! The second argument must be a prime number.") ## Get the relevant local normal forms quickly self_jordan = self.jordan_blocks_by_scale_and_unimodular(p, safe_flag= False) other_jordan = other.jordan_blocks_by_scale_and_unimodular(p, safe_flag=False) ## Check for the same number of Jordan components if len(self_jordan) != len(other_jordan): return False ## Deal with odd primes: Check that the Jordan component scales, dimensions, and discriminants are the same if p != 2: for i in range(len(self_jordan)): if (self_jordan[i][0] != other_jordan[i][0]) \ or (self_jordan[i][1].dim() != other_jordan[i][1].dim()) \ or (legendre_symbol(self_jordan[i][1].det() * other_jordan[i][1].det(), p) != 1): return False ## All tests passed for an odd prime. return True ## For p = 2: Check that all Jordan Invariants are the same. elif p == 2: ## Useful definition t = len(self_jordan) ## Define t = Number of Jordan components ## Check that all Jordan Invariants are the same (scale, dim, and norm) for i in range(t): if (self_jordan[i][0] != other_jordan[i][0]) \ or (self_jordan[i][1].dim() != other_jordan[i][1].dim()) \ or (valuation(GCD(self_jordan[i][1].coefficients()), p) != valuation(GCD(other_jordan[i][1].coefficients()), p)): return False ## Use O'Meara's isometry test 93:29 on p277. ## ------------------------------------------ ## List of norms, scales, and dimensions for each i scale_list = [ZZ(2)**self_jordan[i][0] for i in range(t)] norm_list = [ZZ(2)**(self_jordan[i][0] + valuation(GCD(self_jordan[i][1].coefficients()), 2)) for i in range(t)] dim_list = [(self_jordan[i][1].dim()) for i in range(t)] ## List of Hessian determinants and Hasse invariants for each Jordan (sub)chain ## (Note: This is not the same as O'Meara's Gram determinants, but ratios are the same!) -- NOT SO GOOD... ## But it matters in condition (ii), so we multiply all by 2 (instead of dividing by 2 since only square-factors matter, and it's easier.) j = 0 self_chain_det_list = [ self_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j])] other_chain_det_list = [ other_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j])] self_hasse_chain_list = [ self_jordan[j][1].scale_by_factor(ZZ(2)**self_jordan[j][0]).hasse_invariant__OMeara(2) ] other_hasse_chain_list = [ other_jordan[j][1].scale_by_factor(ZZ(2)**other_jordan[j][0]).hasse_invariant__OMeara(2) ] for j in range(1, t): self_chain_det_list.append(self_chain_det_list[j-1] * self_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j])) other_chain_det_list.append(other_chain_det_list[j-1] * other_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j])) self_hasse_chain_list.append(self_hasse_chain_list[j-1] \ * hilbert_symbol(self_chain_det_list[j-1], self_jordan[j][1].Gram_det(), 2) \ * self_jordan[j][1].hasse_invariant__OMeara(2)) other_hasse_chain_list.append(other_hasse_chain_list[j-1] \ * hilbert_symbol(other_chain_det_list[j-1], other_jordan[j][1].Gram_det(), 2) \ * other_jordan[j][1].hasse_invariant__OMeara(2)) ## SANITY CHECK -- check that the scale powers are strictly increasing for i in range(1, len(scale_list)): if scale_list[i-1] >= scale_list[i]: raise RuntimeError("Oops! There is something wrong with the Jordan Decomposition -- the given scales are not strictly increasing!") ## Test O'Meara's two conditions for i in range(t-1): ## Condition (i): Check that their (unit) ratio is a square (but it suffices to check at most mod 8). modulus = norm_list[i] * norm_list[i+1] / (scale_list[i] ** 2) if modulus > 8: modulus = 8 if (modulus > 1) and (((self_chain_det_list[i] / other_chain_det_list[i]) % modulus) != 1): return False ## Check O'Meara's condition (ii) when appropriate if norm_list[i+1] % (4 * norm_list[i]) == 0: if self_hasse_chain_list[i] * hilbert_symbol(norm_list[i] * other_chain_det_list[i], -self_chain_det_list[i], 2) \ != other_hasse_chain_list[i] * hilbert_symbol(norm_list[i], -other_chain_det_list[i], 2): ## Nipp conditions return False ## All tests passed for the prime 2. return True else: raise TypeError("Oops! This should not have happened.")
def has_equivalent_Jordan_decomposition_at_prime(self, other, p): """ Determines if the given quadratic form has a Jordan decomposition equivalent to that of self. INPUT: a QuadraticForm OUTPUT: boolean EXAMPLES:: sage: Q1 = QuadraticForm(ZZ, 3, [1, 0, -1, 1, 0, 3]) sage: Q2 = QuadraticForm(ZZ, 3, [1, 0, 0, 2, -2, 6]) sage: Q3 = QuadraticForm(ZZ, 3, [1, 0, 0, 1, 0, 11]) sage: [Q1.level(), Q2.level(), Q3.level()] [44, 44, 44] sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q2,2) False sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q2,11) False sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q3,2) False sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q3,11) True sage: Q2.has_equivalent_Jordan_decomposition_at_prime(Q3,2) True sage: Q2.has_equivalent_Jordan_decomposition_at_prime(Q3,11) False """ ## Sanity Checks #if not isinstance(other, QuadraticForm): if not isinstance(other, type(self)): raise TypeError( "Oops! The first argument must be of type QuadraticForm.") if not is_prime(p): raise TypeError("Oops! The second argument must be a prime number.") ## Get the relevant local normal forms quickly self_jordan = self.jordan_blocks_by_scale_and_unimodular(p, safe_flag=False) other_jordan = other.jordan_blocks_by_scale_and_unimodular(p, safe_flag=False) ## DIAGNOSTIC #print "self_jordan = ", self_jordan #print "other_jordan = ", other_jordan ## Check for the same number of Jordan components if len(self_jordan) != len(other_jordan): return False ## Deal with odd primes: Check that the Jordan component scales, dimensions, and discriminants are the same if p != 2: for i in range(len(self_jordan)): if (self_jordan[i][0] != other_jordan[i][0]) \ or (self_jordan[i][1].dim() != other_jordan[i][1].dim()) \ or (legendre_symbol(self_jordan[i][1].det() * other_jordan[i][1].det(), p) != 1): return False ## All tests passed for an odd prime. return True ## For p = 2: Check that all Jordan Invariants are the same. elif p == 2: ## Useful definition t = len(self_jordan) ## Define t = Number of Jordan components ## Check that all Jordan Invariants are the same (scale, dim, and norm) for i in range(t): if (self_jordan[i][0] != other_jordan[i][0]) \ or (self_jordan[i][1].dim() != other_jordan[i][1].dim()) \ or (valuation(GCD(self_jordan[i][1].coefficients()), p) != valuation(GCD(other_jordan[i][1].coefficients()), p)): return False ## DIAGNOSTIC #print "Passed the Jordan invariant test." ## Use O'Meara's isometry test 93:29 on p277. ## ------------------------------------------ ## List of norms, scales, and dimensions for each i scale_list = [ZZ(2)**self_jordan[i][0] for i in range(t)] norm_list = [ ZZ(2)**(self_jordan[i][0] + valuation(GCD(self_jordan[i][1].coefficients()), 2)) for i in range(t) ] dim_list = [(self_jordan[i][1].dim()) for i in range(t)] ## List of Hessian determinants and Hasse invariants for each Jordan (sub)chain ## (Note: This is not the same as O'Meara's Gram determinants, but ratios are the same!) -- NOT SO GOOD... ## But it matters in condition (ii), so we multiply all by 2 (instead of dividing by 2 since only square-factors matter, and it's easier.) j = 0 self_chain_det_list = [ self_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j]) ] other_chain_det_list = [ other_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j]) ] self_hasse_chain_list = [ self_jordan[j][1].scale_by_factor( ZZ(2)**self_jordan[j][0]).hasse_invariant__OMeara(2) ] other_hasse_chain_list = [ other_jordan[j][1].scale_by_factor( ZZ(2)**other_jordan[j][0]).hasse_invariant__OMeara(2) ] for j in range(1, t): self_chain_det_list.append(self_chain_det_list[j - 1] * self_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j])) other_chain_det_list.append(other_chain_det_list[j - 1] * other_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j])) self_hasse_chain_list.append(self_hasse_chain_list[j-1] \ * hilbert_symbol(self_chain_det_list[j-1], self_jordan[j][1].Gram_det(), 2) \ * self_jordan[j][1].hasse_invariant__OMeara(2)) other_hasse_chain_list.append(other_hasse_chain_list[j-1] \ * hilbert_symbol(other_chain_det_list[j-1], other_jordan[j][1].Gram_det(), 2) \ * other_jordan[j][1].hasse_invariant__OMeara(2)) ## SANITY CHECK -- check that the scale powers are strictly increasing for i in range(1, len(scale_list)): if scale_list[i - 1] >= scale_list[i]: raise RuntimeError( "Oops! There is something wrong with the Jordan Decomposition -- the given scales are not strictly increasing!" ) ## DIAGNOSTIC #print "scale_list = ", scale_list #print "norm_list = ", norm_list #print "dim_list = ", dim_list #print #print "self_chain_det_list = ", self_chain_det_list #print "other_chain_det_list = ", other_chain_det_list #print "self_hasse_chain_list = ", self_hasse_chain_list #print "other_hasse_chain_det_list = ", other_hasse_chain_list ## Test O'Meara's two conditions for i in range(t - 1): ## Condition (i): Check that their (unit) ratio is a square (but it suffices to check at most mod 8). modulus = norm_list[i] * norm_list[i + 1] / (scale_list[i]**2) if modulus > 8: modulus = 8 if (modulus > 1) and (( (self_chain_det_list[i] / other_chain_det_list[i]) % modulus) != 1): #print "Failed when i =", i, " in condition 1." return False ## Check O'Meara's condition (ii) when appropriate if norm_list[i + 1] % (4 * norm_list[i]) == 0: if self_hasse_chain_list[i] * hilbert_symbol(norm_list[i] * other_chain_det_list[i], -self_chain_det_list[i], 2) \ != other_hasse_chain_list[i] * hilbert_symbol(norm_list[i], -other_chain_det_list[i], 2): ## Nipp conditions #print "Failed when i =", i, " in condition 2." return False ## All tests passed for the prime 2. return True else: raise TypeError("Oops! This should not have happened.")