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 [GKZ1994]_ 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>, <1,2,3>) sage: N = triangulation.normal_cone(); N 4-d cone in 4-d lattice sage: N.rays() ( 0, 0, 0, -1), ( 0, 0, 1, 1), ( 0, 0, -1, -1), ( 1, 0, 0, 1), (-1, 0, 0, -1), ( 0, 1, 0, -1), ( 0, -1, 0, 1) 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 ppl import Constraint_System, Linear_Expression, C_Polyhedron from sage.matrix.constructor import matrix from sage.arith.all 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 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.")
def _frob_sparse(self, i, j, N0): r""" Compute `Frob(x^i y^(-j) dx ) / dx` for y^r = f(x) with N0 terms INPUT: - ``i`` - The power of x in the expression `Frob(x^i dx/y^j) / dx` - ``j`` - The (negative) power of y in the expression `Frob(x^i dx/y^j) / dx` OUTPUT: ``frobij`` - a Matrix of size (d * (N0 - 1) + ) x (N0) that represents the Frobenius expansion of x^i dx/y^j modulo p^(N0 + 1) the entry (l, s) corresponds to the coefficient associated to the monomial x**(p * (i + 1 + l) -1) * y**(p * -(j + r*s)) (l, s) --> (p * (i + 1 + l) -1, p * -(j + r*s)) ALGORITHM: Compute: Frob(x^i dx/y^j) / dx = p * x ** (p * (i+1) - 1) * y ** (-j*p) * Sigma where: .. MATH:: Sigma = \sum_{k = 0} ^{N0-1} \sum_{s = 0} ^k (-1) ** (k-s) * binomial(k, s) * binomial(-j/r, k) * self._frobpow[s] * self._y ** (-self._r * self._p * s) = \sum_{s = 0} ^{N0 - 1} \sum_{k = s} ^N0 (-1) ** (k-s) * binomial(k, s) * binomial(-j/self._r, k) * self._frobpow[s] * self._y ** (-self._r*self._p*s) = \sum_{s = 0} ^{N0-1} D_{j, s} * self._frobpow[s] * self._y ** (-self._r * self._p * s) = \sum_{s = 0} ^N0 \sum_{l = 0} ^(d*s) D_{j, s} * self._frobpow[s][l] * x ** (self._p ** l) * y ** (-self._r * self._p ** s) and: .. MATH:: D_{j, s} = \sum_{k = s} ^N0 (-1) ** (k-s) * binomial(k, s) * binomial(-j/self._r, k) ) TESTS:: sage: p = 499 sage: x = PolynomialRing(GF(p),"x").gen() sage: C = CyclicCover(3, x^4 + 4*x^3 + 9*x^2 + 3*x + 1) sage: C._init_frob() sage: C._frob_sparse(2, 0, 1) [499] sage: C._frob_sparse(2, 0, 2) [499 0] [ 0 0] [ 0 0] [ 0 0] [ 0 0] sage: C._frob_sparse(2, 1, 1) [499] sage: C._frob_sparse(2, 1, 2) [ 82834998 41417000] [ 0 124251000] [ 0 124250002] [ 0 41416501] [ 0 41417000] sage: C._frob_sparse(2, 2, 1) [499] """ def _extend_frobpow(power): if power < len(self._frobpow_list): pass else: frobpow = self._Zqx(self._frobpow_list[-1]) for k in range(len(self._frobpow_list), power + 1): frobpow *= self._frobf self._frobpow_list.extend([frobpow.list()]) assert power < len(self._frobpow_list) _extend_frobpow(N0) r = self._r Dj = [ self._Zq( sum([(-1)**(k - l) * binomial(k, l) * binomial(-ZZ(j) / r, k) for k in range(l, N0)])) for l in range(N0) ] frobij = matrix(self._Zq, self._d * (N0 - 1) + 1, N0) for s in range(N0): for l in range(self._d * s + 1): frobij[l, s] = self._p * Dj[s] * self._frobpow_list[s][l] return frobij
def logpp_gam(p, p_prec): """returns the (integral) power series log_p(1+p*z)*(1/log_p(1+p)) where the denominator is computed with some accuracy""" L = logpp(p, p_prec) ZZp = Zp(p, 2 * p_prec) loggam = ZZ(ZZp(1 + p).log(0)) return ps_normalize(L / loggam, p, p_prec)
def Krawtchouk(n, q, l, x, check=True): """ Compute ``K^{n,q}_l(x)``, the Krawtchouk (a.k.a. Kravchuk) polynomial. See :wikipedia:`Kravchuk_polynomials`; It is defined by the generating function `(1+(q-1)z)^{n-x}(1-z)^x=\sum_{l} K^{n,q}_l(x)z^l` and is equal to .. math:: K^{n,q}_l(x)=\sum_{j=0}^l (-1)^j(q-1)^{(l-j)}{x \choose j}{n-x \choose l-j}, INPUT: - ``n, q, x`` -- arbitrary numbers - ``l`` -- a nonnegative integer - ``check`` -- check the input for correctness. ``True`` by default. Otherwise, pass it as it is. Use ``check=False`` at your own risk. EXAMPLES:: sage: Krawtchouk(24,2,5,4) 2224 sage: Krawtchouk(12300,4,5,6) 567785569973042442072 TESTS: check that the bug reported on :trac:`19561` is fixed:: sage: Krawtchouk(3,2,3,3) -1 sage: Krawtchouk(int(3),int(2),int(3),int(3)) -1 sage: Krawtchouk(int(3),int(2),int(3),int(3),check=False) -5 sage: Kravchuk(24,2,5,4) 2224 other unusual inputs :: sage: Krawtchouk(sqrt(5),1-I*sqrt(3),3,55.3).n() 211295.892797... + 1186.42763...*I sage: Krawtchouk(-5/2,7*I,3,-1/10) 480053/250*I - 357231/400 sage: Krawtchouk(1,1,-1,1) Traceback (most recent call last): ... ValueError: l must be a nonnegative integer sage: Krawtchouk(1,1,3/2,1) Traceback (most recent call last): ... TypeError: no conversion of this rational to integer """ from sage.arith.all import binomial from sage.arith.srange import srange # Use the expression in equation (55) of MacWilliams & Sloane, pg 151 # We write jth term = some_factor * (j-1)th term if check: from sage.rings.integer_ring import ZZ l0 = ZZ(l) if l0 != l or l0 < 0: raise ValueError('l must be a nonnegative integer') l = l0 kraw = jth_term = (q - 1)**l * binomial(n, l) # j=0 for j in srange(1, l + 1): jth_term *= -q * (l - j + 1) * (x - j + 1) / ((q - 1) * j * (n - j + 1)) kraw += jth_term return kraw
def __init__(self, E, sign, normalize="L_ratio"): """ Modular symbols attached to `E` using ``sage``. INPUT: - ``E`` -- an elliptic curve - ``sign`` -- an integer, -1 or 1 - ``normalize`` -- either 'L_ratio' (default), 'period', or 'none'; For 'L_ratio', the modular symbol is correctly normalized by comparing it to the quotient of `L(E,1)` by the least positive period for the curve and some small twists. The normalization 'period' uses the integral_period_map for modular symbols and is known to be equal to the above normalization up to the sign and a possible power of 2. For 'none', the modular symbol is almost certainly not correctly normalized, i.e. all values will be a fixed scalar multiple of what they should be. But the initial computation of the modular symbol is much faster, though evaluation of it after computing it won't be any faster. EXAMPLES:: sage: E=EllipticCurve('11a1') sage: import sage.schemes.elliptic_curves.ell_modular_symbols sage: M=sage.schemes.elliptic_curves.ell_modular_symbols.ModularSymbolSage(E,+1) sage: M Modular symbol with sign 1 over Rational Field attached to Elliptic Curve defined by y^2 + y = x^3 - x^2 - 10*x - 20 over Rational Field sage: M(0) 1/5 sage: E=EllipticCurve('11a2') sage: M=sage.schemes.elliptic_curves.ell_modular_symbols.ModularSymbolSage(E,+1) sage: M(0) 1 sage: M=sage.schemes.elliptic_curves.ell_modular_symbols.ModularSymbolSage(E,-1) sage: M(1/3) 1 This is a rank 1 case with vanishing positive twists. The modular symbol is adjusted by -2:: sage: E=EllipticCurve('121b1') sage: M=sage.schemes.elliptic_curves.ell_modular_symbols.ModularSymbolSage(E,-1,normalize='L_ratio') sage: M(1/3) 2 sage: M._scaling -2 sage: M = EllipticCurve('121d1').modular_symbol(use_eclib=False) sage: M(0) 2 sage: M = EllipticCurve('121d1').modular_symbol(use_eclib=False,normalize='none') sage: M(0) 1 sage: E = EllipticCurve('15a1') sage: [C.modular_symbol(use_eclib=False, normalize='L_ratio')(0) for C in E.isogeny_class()[0]] [1/4, 1/8, 1/4, 1/2, 1/8, 1/16, 1/2, 1] sage: [C.modular_symbol(use_eclib=False, normalize='period')(0) for C in E.isogeny_class()[0]] [1/8, 1/16, 1/8, 1/4, 1/16, 1/32, 1/4, 1/2] sage: [C.modular_symbol(use_eclib=False, normalize='none')(0) for C in E.isogeny_class()[0]] [1, 1, 1, 1, 1, 1, 1, 1] """ self._sign = ZZ(sign) if self._sign != sign: raise TypeError, 'sign must be an integer' if self._sign != -1 and self._sign != 1: raise TypeError, 'sign must -1 or 1' self._E = E self._use_eclib = False self._normalize = normalize self._modsym = E.modular_symbol_space(sign=self._sign) self._base_ring = self._modsym.base_ring() self._ambient_modsym = self._modsym.ambient_module() if normalize == "L_ratio": self._e = self._modsym.dual_eigenvector() self._find_scaling_L_ratio() self._e *= self._scaling elif normalize == "period": self._find_scaling_period() # this will set _e and _scaling elif normalize == "none": self._scaling = 1 self._e = self._modsym.dual_eigenvector() else: raise ValueError, "no normalization %s known for modular symbols" % normalize
def variance(v, bias=False): """ Returns the variance of the elements of `v` We define the variance of the empty list to be NaN, following the convention of MATLAB, Scipy, and R. INPUT: - `v` -- a list of numbers - ``bias`` -- bool (default: False); if False, divide by len(v) - 1 instead of len(v) to give a less biased estimator (sample) for the standard deviation. OUTPUT: - a number EXAMPLES:: sage: variance([1..6]) 7/2 sage: variance([1..6], bias=True) 35/12 sage: variance([e, pi]) 1/2*(pi - e)^2 sage: variance([]) NaN sage: variance([I, sqrt(2), 3/5]) 1/450*(-5*sqrt(2) - 5*I + 6)^2 + 1/450*(10*sqrt(2) - 5*I - 3)^2 + 1/450*(-5*sqrt(2) + 10*I - 3)^2 sage: variance([RIF(1.0103, 1.0103), RIF(2)]) 0.4897530450000000? sage: import numpy sage: x = numpy.array([1,2,3,4,5]) sage: variance(x, bias=False) 2.5 sage: x = finance.TimeSeries([1..100]) sage: variance(x) 841.6666666666666 sage: variance(x, bias=True) 833.25 sage: class MyClass: ... def variance(self, bias = False): ... return 1 sage: stats.variance(MyClass()) 1 sage: class SillyPythonList: ... def __init__(self): ... self.__list = [2L,4L] ... def __len__(self): ... return len(self.__list) ... def __iter__(self): ... return self.__list.__iter__() ... def mean(self): ... return 3L sage: R = SillyPythonList() sage: variance(R) 2 sage: variance(R, bias=True) 1 TESTS: The performance issue from #10019 is solved:: sage: variance([1] * 2^18) 0 """ if hasattr(v, 'variance'): return v.variance(bias=bias) import numpy x = 0 if isinstance(v, numpy.ndarray): # accounts for numpy arrays if bias == True: return v.var() elif bias == False: return v.var(ddof=1) if len(v) == 0: # variance of empty set defined as NaN return NaN mu = mean(v) for vi in v: x += (vi - mu)**2 if bias: # population variance if isinstance(x, (int, long)): return x / ZZ(len(v)) return x / len(v) else: # sample variance if isinstance(x, (int, long)): return x / ZZ(len(v) - 1) return x / (len(v) - 1)
def spin_weighted_spherical_harmonic(s, l, m, theta, phi, condon_shortley=True, cached=True, numerical=None): r""" Return the spin-weighted spherical harmonic of spin weight ``s`` and indices ``(l,m)``. INPUT: - ``s`` -- integer; the spin weight - ``l`` -- non-negative integer; the harmonic degree - ``m`` -- integer within the range ``[-l, l]``; the azimuthal number - ``theta`` -- colatitude angle - ``phi`` -- azimuthal angle - ``condon_shortley`` -- (default: ``True``) determines whether the Condon-Shortley phase of `(-1)^m` is taken into account (see below) - ``cached`` -- (default: ``True``) determines whether the result shall be cached; setting ``cached`` to ``False`` forces a new computation, without caching the result - ``numerical`` -- (default: ``None``) determines whether a symbolic or a numerical computation of a given type is performed; allowed values are - ``None``: the type of computation is deduced from the type of ``theta`` - ``RDF``: Sage's machine double precision floating-point numbers (``RealDoubleField``) - ``RealField(n)``, where ``n`` is a number of bits: Sage's floating-point numbers with an arbitrary precision; note that ``RR`` is a shortcut for ``RealField(53)``. - ``float``: Python's floating-point numbers OUTPUT: - the value of `{}_s Y_l^m(\theta,\phi)`, either as a symbolic expression or as floating-point complex number of the type determined by ``numerical`` ALGORITHM: The spin-weighted spherical harmonic is evaluated according to Eq. (3.1) of J. N. Golberg et al., J. Math. Phys. **8**, 2155 (1967) [:doi:`10.1063/1.1705135`], with an extra `(-1)^m` factor (the so-called *Condon-Shortley phase*) if ``condon_shortley`` is ``True``, the actual formula being then the one given in :wikipedia:`Spin-weighted_spherical_harmonics#Calculating` EXAMPLES:: sage: from kerrgeodesic_gw import spin_weighted_spherical_harmonic sage: theta, phi = var('theta phi') sage: spin_weighted_spherical_harmonic(-2, 2, 1, theta, phi) 1/4*(sqrt(5)*cos(theta) + sqrt(5))*e^(I*phi)*sin(theta)/sqrt(pi) sage: spin_weighted_spherical_harmonic(-2, 2, 1, theta, phi, ....: condon_shortley=False) -1/4*(sqrt(5)*cos(theta) + sqrt(5))*e^(I*phi)*sin(theta)/sqrt(pi) sage: spin_weighted_spherical_harmonic(-2, 2, 1, pi/3, pi/4) (3/32*I + 3/32)*sqrt(5)*sqrt(3)*sqrt(2)/sqrt(pi) Evaluation as floating-point numbers: the type of the output is deduced from the input:: sage: spin_weighted_spherical_harmonic(-2, 2, 1, 1.0, 2.0) # tol 1.0e-13 -0.170114676286891 + 0.371707349012686*I sage: parent(_) Complex Field with 53 bits of precision sage: spin_weighted_spherical_harmonic(-2, 2, 1, RDF(2.0), RDF(3.0)) # tol 1.0e-13 -0.16576451879564585 + 0.023629159118690464*I sage: parent(_) Complex Double Field sage: spin_weighted_spherical_harmonic(-2, 2, 1, float(3.0), float(4.0)) # tol 1.0e-13 (-0.0002911423884400524-0.00033709085352998027j) sage: parent(_) <type 'complex'> Computation with arbitrary precision are possible (here 100 bits):: sage: R100 = RealField(100); R100 Real Field with 100 bits of precision sage: spin_weighted_spherical_harmonic(-2, 2, 1, R100(1.5), R100(2.0)) # tol 1.0e-28 -0.14018136537676185317636108802 + 0.30630187143465275236861476906*I Even when the entry is symbolic, numerical evaluation can be enforced via the argument ``numerical``. For instance, setting ``numerical`` to ``RDF`` (SageMath's Real Double Field):: sage: spin_weighted_spherical_harmonic(-2, 2, 1, pi/3, pi/4, numerical=RDF) # tol 1.0e-13 0.2897056515173923 + 0.28970565151739225*I sage: parent(_) Complex Double Field One can also use ``numerical=RR`` (SageMath's Real Field with precision set to 53 bits):: sage: spin_weighted_spherical_harmonic(-2, 2, 1, pi/3, pi/4, numerical=RR) # tol 1.0e-13 0.289705651517392 + 0.289705651517392*I sage: parent(_) Complex Field with 53 bits of precision Another option is to use Python floats:: sage: spin_weighted_spherical_harmonic(-2, 2, 1, pi/3, pi/4, numerical=float) # tol 1.0e-13 (0.28970565151739225+0.2897056515173922j) sage: parent(_) <type 'complex'> One can go beyond double precision, for instance using 100 bits of precision:: sage: spin_weighted_spherical_harmonic(-2, 2, 1, pi/3, pi/4, ....: numerical=RealField(100)) # tol 1.0e-28 0.28970565151739218525664455148 + 0.28970565151739218525664455148*I sage: parent(_) Complex Field with 100 bits of precision """ global _cached_functions, _cached_values s = ZZ(s) # ensure that we are dealing with Sage integers l = ZZ(l) m = ZZ(m) if abs(s) > l: return ZZ(0) if (isinstance(theta, Expression) and theta.variables() == (theta, ) and isinstance(phi, Expression) and phi.variables() == (phi, )): # Evaluation as a symbolic function if cached: param = (s, l, m, condon_shortley) if param not in _cached_functions: _cached_functions[param] = _compute_sw_spherical_harm( s, l, m, theta, phi, condon_shortley=condon_shortley, numerical=None).function(theta, phi) return _cached_functions[param](theta, phi) return _compute_sw_spherical_harm(s, l, m, theta, phi, condon_shortley=condon_shortley, numerical=None) # Numerical or symbolic evaluation param = (s, l, m, theta, phi, condon_shortley, str(numerical)) if not cached or param not in _cached_values: # a new computation is required if numerical: # theta and phi are enforced to be of the type defined by numerical theta = numerical(theta) phi = numerical(phi) else: # the type of computation is deduced from that of theta if type(theta) is float: numerical = float elif (theta.parent() is RDF or isinstance(theta.parent(), RealField_class)): numerical = theta.parent() res = _compute_sw_spherical_harm(s, l, m, theta, phi, condon_shortley=condon_shortley, numerical=numerical) if numerical is RDF or isinstance(numerical, RealField_class): res = numerical.complex_field()(res) elif numerical is float: res = complex(*res) if cached: _cached_values[param] = res return res return _cached_values[param]
def _compute_sw_spherical_harm(s, l, m, theta, phi, condon_shortley=True, numerical=None): r""" Compute the spin-weighted spherical harmonic of spin weight ``s`` and indices ``(l,m)`` as a callable symbolic expression in (theta,phi) INPUT: - ``s`` -- integer; the spin weight - ``l`` -- non-negative integer; the harmonic degree - ``m`` -- integer within the range ``[-l, l]``; the azimuthal number - ``theta`` -- colatitude angle - ``phi`` -- azimuthal angle - ``condon_shortley`` -- (default: ``True``) determines whether the Condon-Shortley phase of `(-1)^m` is taken into account (see below) - ``numerical`` -- (default: ``None``) determines whether a symbolic or a numerical computation of a given type is performed; allowed values are - ``None``: a symbolic computation is performed - ``RDF``: Sage's machine double precision floating-point numbers (``RealDoubleField``) - ``RealField(n)``, where ``n`` is a number of bits: Sage's floating-point numbers with an arbitrary precision; note that ``RR`` is a shortcut for ``RealField(53)``. - ``float``: Python's floating-point numbers OUTPUT: - `{}_s Y_l^m(\theta,\phi)` either - as a symbolic expression if ``numerical`` is ``None`` - or a pair of floating-point numbers, each of them being of the type corresponding to ``numerical`` and representing respectively the real and imaginary parts of `{}_s Y_l^m(\theta,\phi)` ALGORITHM: The spin-weighted spherical harmonic is evaluated according to Eq. (3.1) of J. N. Golberg et al., J. Math. Phys. **8**, 2155 (1967) [:doi:`10.1063/1.1705135`], with an extra `(-1)^m` factor (the so-called *Condon-Shortley phase*) if ``condon_shortley`` is ``True``, the actual formula being then the one given in :wikipedia:`Spin-weighted_spherical_harmonics#Calculating` TESTS:: sage: from kerrgeodesic_gw.spin_weighted_spherical_harm import _compute_sw_spherical_harm sage: theta, phi = var("theta phi") sage: _compute_sw_spherical_harm(-2, 2, 1, theta, phi) 1/4*(sqrt(5)*cos(theta) + sqrt(5))*e^(I*phi)*sin(theta)/sqrt(pi) """ if abs(s) > l: return ZZ(0) if abs(theta) < 1.e-6: # TODO: fix the treatment of small theta values if theta < 0: # possibly with exact formula for theta=0 theta = -1.e-6 # else: # theta = 1.e-6 # cott2 = cos(theta / 2) / sin(theta / 2) res = 0 for r in range(l - s + 1): res += (-1)**(l - r - s) * (binomial(l - s, r) * binomial( l + s, r + s - m) * cott2**(2 * r + s - m)) res *= sin(theta / 2)**(2 * l) ff = factorial(l + m) * factorial(l - m) * (2 * l + 1) / ( factorial(l + s) * factorial(l - s)) if numerical: pre = sqrt(numerical(ff) / numerical(pi)) / 2 else: pre = sqrt(ff) / (2 * sqrt(pi)) res *= pre if condon_shortley: res *= (-1)**m if numerical: return (numerical(res * cos(m * phi)), numerical(res * sin(m * phi))) # Symbolic case: res = res.simplify_full() res = res.reduce_trig() # get rid of cos(theta/2) and sin(theta/2) res = res.simplify_trig() # further trigonometric simplifications res *= exp(I * m * phi) return res
def __init__(self, number_field, proof=True, S=None): """ Create a unit group of a number field. INPUT: - ``number_field`` - a number field - ``proof`` - boolean (default True): proof flag - ``S`` - tuple of prime ideals, or an ideal, or a single ideal or element from which an ideal can be constructed, in which case the support is used. If None, the global unit group is constructed; otherwise, the S-unit group is constructed. The proof flag is passed to pari via the ``pari_bnf()`` function which computes the unit group. See the documentation for the number_field module. EXAMPLES:: sage: x = polygen(QQ) sage: K.<a> = NumberField(x^2-38) sage: UK = K.unit_group(); UK Unit group with structure C2 x Z of Number Field in a with defining polynomial x^2 - 38 sage: UK.gens() (u0, u1) sage: UK.gens_values() [-1, 6*a - 37] sage: K.<a> = QuadraticField(-3) sage: UK = K.unit_group(); UK Unit group with structure C6 of Number Field in a with defining polynomial x^2 + 3 sage: UK.gens() (u,) sage: UK.gens_values() [-1/2*a + 1/2] sage: K.<z> = CyclotomicField(13) sage: UK = K.unit_group(); UK Unit group with structure C26 x Z x Z x Z x Z x Z of Cyclotomic Field of order 13 and degree 12 sage: UK.gens() (u0, u1, u2, u3, u4, u5) sage: UK.gens_values() # random [-z^11, z^5 + z^3, z^6 + z^5, z^9 + z^7 + z^5, z^9 + z^5 + z^4 + 1, z^5 + z] sage: SUK = UnitGroup(K,S=2); SUK S-unit group with structure C26 x Z x Z x Z x Z x Z x Z of Cyclotomic Field of order 13 and degree 12 with S = (Fractional ideal (2),) """ proof = get_flag(proof, "number_field") K = number_field pK = K.pari_bnf(proof) self.__number_field = K self.__pari_number_field = pK # process the parameter S: if not S: S = self.__S = () else: if type(S) == list: S = tuple(S) if not type(S) == tuple: try: S = tuple(K.ideal(S).prime_factors()) except (NameError, TypeError, ValueError): raise ValueError("Cannot make a set of primes from %s" % (S, )) else: try: S = tuple(K.ideal(P) for P in S) except (NameError, TypeError, ValueError): raise ValueError("Cannot make a set of primes from %s" % (S, )) if not all([P.is_prime() for P in S]): raise ValueError( "Not all elements of %s are prime ideals" % (S, )) self.__S = S self.__pS = pS = [P.pari_prime() for P in S] # compute the fundamental units via pari: fu = [K(u) for u in pK.bnfunit()] self.__nfu = len(fu) # compute the additional S-unit generators: if S: self.__S_unit_data = pK.bnfsunit(pS) su = [K(u) for u in self.__S_unit_data[0]] else: su = [] self.__nsu = len(su) self.__rank = self.__nfu + self.__nsu # compute a torsion generator and pick the 'simplest' one: n, z = pK.nfrootsof1() n = ZZ(n) self.__ntu = n z = K(z) # If we replaced z by another torsion generator we would need # to allow for this in the dlog function! So we do not. # Store the actual generators (torsion first): gens = [z] + fu + su values = Sequence(gens, immutable=True, universe=self, check=False) # Construct the abtract group: gens_orders = tuple([ZZ(n)] + [ZZ(0)] * (self.__rank)) AbelianGroupWithValues_class.__init__(self, gens_orders, 'u', values, number_field)
def zeta(self, n=2, all=False): """ Return one, or a list of all, primitive n-th root of unity in this unit group. EXAMPLES:: sage: x = polygen(QQ) sage: K.<z> = NumberField(x^2 + 3) sage: U = UnitGroup(K) sage: U.zeta(1) 1 sage: U.zeta(2) -1 sage: U.zeta(2, all=True) [-1] sage: U.zeta(3) -1/2*z - 1/2 sage: U.zeta(3, all=True) [-1/2*z - 1/2, 1/2*z - 1/2] sage: U.zeta(4) Traceback (most recent call last): ... ValueError: n (=4) does not divide order of generator sage: r.<x> = QQ[] sage: K.<b> = NumberField(x^2+1) sage: U = UnitGroup(K) sage: U.zeta(4) -b sage: U.zeta(4,all=True) [-b, b] sage: U.zeta(3) Traceback (most recent call last): ... ValueError: n (=3) does not divide order of generator sage: U.zeta(3,all=True) [] """ N = self.__ntu K = self.number_field() n = ZZ(n) if n <= 0: raise ValueError, "n (=%s) must be positive" % n if n == 1: if all: return [K(1)] else: return K(1) elif n == 2: if all: return [K(-1)] else: return K(-1) if n.divides(N): z = self.torsion_generator().value()**(N // n) if all: return [z**i for i in n.coprime_integers(n)] else: return z else: if all: return [] else: raise ValueError, "n (=%s) does not divide order of generator" % n
def __init__(self, *args, **kwargs): """ Construct a substitution box (S-box) for a given lookup table `S`. INPUT: - ``S`` - a finite iterable defining the S-box with integer or finite field elements - ``big_endian`` - controls whether bits shall be ordered in big endian order (default: ``True``) EXAMPLE: We construct a 3-bit S-box where e.g. the bits (0,0,1) are mapped to (1,1,1).:: sage: S = mq.SBox(7,6,0,4,2,5,1,3); S (7, 6, 0, 4, 2, 5, 1, 3) sage: S(0) 7 TESTS:: sage: S = mq.SBox() Traceback (most recent call last): ... TypeError: No lookup table provided. sage: S = mq.SBox(1, 2, 3) Traceback (most recent call last): ... TypeError: Lookup table length is not a power of 2. sage: S = mq.SBox(5, 6, 0, 3, 4, 2, 1, 2) sage: S.n 3 """ if "S" in kwargs: S = kwargs["S"] elif len(args) == 1: S = args[0] elif len(args) > 1: S = args else: raise TypeError("No lookup table provided.") _S = [] for e in S: if is_FiniteFieldElement(e): e = e.polynomial().change_ring(ZZ).subs( e.parent().characteristic()) _S.append(e) S = _S if not ZZ(len(S)).is_power_of(2): raise TypeError("Lookup table length is not a power of 2.") self._S = S self.m = ZZ(len(S)).exact_log(2) self.n = ZZ(max(S)).nbits() self._F = GF(2) self._big_endian = kwargs.get("big_endian", True)
def __call__(self, X): """ Apply substitution to ``X``. If ``X`` is a list, it is interpreted as a sequence of bits depending on the bit order of this S-box. INPUT: - ``X`` - either an integer, a tuple of `\GF{2}` elements of length ``len(self)`` or a finite field element in `\GF{2^n}`. As a last resort this function tries to convert ``X`` to an integer. EXAMPLE:: sage: S = mq.SBox([7,6,0,4,2,5,1,3]) sage: S(7) 3 sage: S((0,2,3)) [0, 1, 1] sage: S[0] 7 sage: S[(0,0,1)] [1, 1, 0] sage: k.<a> = GF(2^3) sage: S(a^2) a sage: S(QQ(3)) 4 sage: S([1]*10^6) Traceback (most recent call last): ... TypeError: Cannot apply SBox to provided element. sage: S(1/2) Traceback (most recent call last): ... TypeError: Cannot apply SBox to 1/2. sage: S = mq.SBox(3, 0, 1, 3, 1, 0, 2, 2) sage: S(0) 3 sage: S([0,0,0]) [1, 1] """ if isinstance(X, (int, long, Integer)): return self._S[ZZ(X)] try: from sage.modules.free_module_element import vector K = X.parent() if K.order() == 2**self.n: X = vector(X) else: raise TypeError if not self._big_endian: X = list(reversed(X)) else: X = list(X) X = ZZ(map(ZZ, X), 2) out = self.to_bits(self._S[X], self.n) if self._big_endian: out = list(reversed(out)) return K(vector(GF(2), out)) except (AttributeError, TypeError): pass try: if len(X) == self.m: if self._big_endian: X = list(reversed(X)) X = ZZ(map(ZZ, X), 2) out = self._S[X] return self.to_bits(out, self.n) except TypeError: pass try: return self._S[ZZ(X)] except TypeError: pass if len(str(X)) > 50: raise TypeError("Cannot apply SBox to provided element.") else: raise TypeError("Cannot apply SBox to %s." % (X, ))
def guess(self, sequence, algorithm='sage'): """ Return the minimal CFiniteSequence that generates the sequence. Assume the first value has index 0. INPUT: - ``sequence`` -- list of integers - ``algorithm`` -- string - 'sage' - the default is to use Sage's matrix kernel function - 'pari' - use Pari's implementation of LLL - 'bm' - use Sage's Berlekamp-Massey algorithm OUTPUT: - a CFiniteSequence, or 0 if none could be found With the default kernel method, trailing zeroes are chopped off before a guessing attempt. This may reduce the data below the accepted length of six values. EXAMPLES:: sage: C.<x> = CFiniteSequences(QQ) sage: C.guess([1,2,4,8,16,32]) C-finite sequence, generated by -1/2/(x - 1/2) sage: r = C.guess([1,2,3,4,5]) Traceback (most recent call last): ... ValueError: Sequence too short for guessing. With Berlekamp-Massey, if an odd number of values is given, the last one is dropped. So with an odd number of values the result may not generate the last value:: sage: r = C.guess([1,2,4,8,9], algorithm='bm'); r C-finite sequence, generated by -1/2/(x - 1/2) sage: r[0:5] [1, 2, 4, 8, 16] """ S = self.polynomial_ring() if algorithm == 'bm': from sage.matrix.berlekamp_massey import berlekamp_massey if len(sequence) < 2: raise ValueError('Sequence too short for guessing.') R = PowerSeriesRing(QQ, 'x') if len(sequence) % 2 == 1: sequence = sequence[:-1] l = len(sequence) - 1 denominator = S(berlekamp_massey(sequence).list()[::-1]) numerator = R(S(sequence) * denominator, prec=l).truncate() return CFiniteSequence(numerator / denominator) elif algorithm == 'pari': global _gp if len(sequence) < 6: raise ValueError('Sequence too short for guessing.') if _gp is None: _gp = Gp() _gp("ggf(v)=local(l,m,p,q,B);l=length(v);B=floor(l/2);\ if(B<3,return(0));m=matrix(B,B,x,y,v[x-y+B+1]);\ q=qflll(m,4)[1];if(length(q)==0,return(0));\ p=sum(k=1,B,x^(k-1)*q[k,1]);\ q=Pol(Pol(vector(l,n,v[l-n+1]))*p+O(x^(B+1)));\ if(polcoeff(p,0)<0,q=-q;p=-p);q=q/p;p=Ser(q+O(x^(l+1)));\ for(m=1,l,if(polcoeff(p,m-1)!=v[m],return(0)));q") _gp.set('gf', sequence) _gp("gf=ggf(gf)") num = S(sage_eval(_gp.eval("Vec(numerator(gf))"))[::-1]) den = S(sage_eval(_gp.eval("Vec(denominator(gf))"))[::-1]) if num == 0: return 0 else: return CFiniteSequence(num / den) else: from sage.matrix.constructor import matrix from sage.functions.other import floor, ceil from numpy import trim_zeros l = len(sequence) while l > 0 and sequence[l-1] == 0: l -= 1 sequence = sequence[:l] if l == 0: return 0 if l < 6: raise ValueError('Sequence too short for guessing.') hl = ceil(ZZ(l)/2) A = matrix([sequence[k:k+hl] for k in range(hl)]) K = A.kernel() if K.dimension() == 0: return 0 R = PolynomialRing(QQ, 'x') den = R(trim_zeros(K.basis()[-1].list()[::-1])) if den == 1: return 0 offset = next((i for i, x in enumerate(sequence) if x!=0), None) S = PowerSeriesRing(QQ, 'x', default_prec=l-offset) num = S(R(sequence)*den).add_bigoh(floor(ZZ(l)/2+1)).truncate() if num == 0 or sequence != S(num/den).list(): return 0 else: return CFiniteSequence(num / den)
def _find_scaling_L_ratio(self): r""" This function is use to set ``_scaling``, the factor used to adjust the scalar multiple of the modular symbol. If `[0]`, the modular symbol evaluated at 0, is non-zero, we can just scale it with respect to the approximation of the L-value. It is known that the quotient is a rational number with small denominator. Otherwise we try to scale using quadratic twists. ``_scaling`` will be set to a rational non-zero multiple if we succeed and to 1 otherwise. Even if we fail we scale at least to make up the difference between the periods of the `X_0`-optimal curve and our given curve `E` in the isogeny class. EXAMPLES:: sage : m = EllipticCurve('11a1').modular_symbol(use_eclib=True) sage : m._scaling 1 sage: m = EllipticCurve('11a2').modular_symbol(use_eclib=True) sage: m._scaling 5/2 sage: m = EllipticCurve('11a3').modular_symbol(use_eclib=True) sage: m._scaling 1/10 sage: m = EllipticCurve('11a1').modular_symbol(use_eclib=False) sage: m._scaling 1/5 sage: m = EllipticCurve('11a2').modular_symbol(use_eclib=False) sage: m._scaling 1 sage: m = EllipticCurve('11a3').modular_symbol(use_eclib=False) sage: m._scaling 1/25 sage: m = EllipticCurve('37a1').modular_symbol(use_eclib=False) sage: m._scaling 1 sage: m = EllipticCurve('37a1').modular_symbol(use_eclib=True) sage: m._scaling -1 sage: m = EllipticCurve('389a1').modular_symbol(use_eclib=True) sage: m._scaling -1/2 sage: m = EllipticCurve('389a1').modular_symbol(use_eclib=False) sage: m._scaling 2 sage: m = EllipticCurve('196a1').modular_symbol(use_eclib=False) sage: m._scaling 1/2 Some harder cases fail:: sage: m = EllipticCurve('121b1').modular_symbol(use_eclib=False) Warning : Could not normalize the modular symbols, maybe all further results will be multiplied by -1, 2 or -2. sage: m._scaling 1 TESTS:: sage: rk0 = ['11a1', '11a2', '15a1', '27a1', '37b1'] sage: for la in rk0: # long time (3s on sage.math, 2011) ... E = EllipticCurve(la) ... me = E.modular_symbol(use_eclib = True) ... ms = E.modular_symbol(use_eclib = False) ... print E.lseries().L_ratio()*E.real_components(), me(0), ms(0) 1/5 1/5 1/5 1 1 1 1/4 1/4 1/4 1/3 1/3 1/3 2/3 2/3 2/3 sage: rk1 = ['37a1','43a1','53a1', '91b1','91b2','91b3'] sage: [EllipticCurve(la).modular_symbol(use_eclib=True)(0) for la in rk1] # long time (1s on sage.math, 2011) [0, 0, 0, 0, 0, 0] sage: for la in rk1: # long time (8s on sage.math, 2011) ... E = EllipticCurve(la) ... m = E.modular_symbol(use_eclib = True) ... lp = E.padic_lseries(5) ... for D in [5,17,12,8]: ... ED = E.quadratic_twist(D) ... md = sum([kronecker(D,u)*m(ZZ(u)/D) for u in range(D)]) ... etaa = lp._quotient_of_periods_to_twist(D) ... assert ED.lseries().L_ratio()*ED.real_components()*etaa == md """ E = self._E self._scaling = 1 # by now. self._failed_to_scale = False if self._sign == 1: at0 = self(0) # print 'modular symbol evaluates to ',at0,' at 0' if at0 != 0: l1 = self.__lalg__(1) if at0 != l1: verbose('scale modular symbols by %s' % (l1 / at0)) self._scaling = l1 / at0 else: # if [0] = 0, we can still hope to scale it correctly by considering twists of E Dlist = [ 5, 8, 12, 13, 17, 21, 24, 28, 29, 33, 37, 40, 41, 44, 53, 56, 57, 60, 61, 65, 69, 73, 76, 77, 85, 88, 89, 92, 93, 97 ] # a list of positive fundamental discriminants j = 0 at0 = 0 # computes [0]+ for the twist of E by D until one value is non-zero while j < 30 and at0 == 0: D = Dlist[j] # the following line checks if the twist of the newform of E by D is a newform # this is to avoid that we 'twist back' if all( valuation(E.conductor(), ell) <= valuation(D, ell) for ell in prime_divisors(D)): at0 = sum([ kronecker_symbol(D, u) * self(ZZ(u) / D) for u in range(1, abs(D)) ]) j += 1 if j == 30 and at0 == 0: # curves like "121b1", "225a1", "225e1", "256a1", "256b1", "289a1", "361a1", "400a1", "400c1", "400h1", "441b1", "441c1", "441d1", "441f1 .. will arrive here self.__scale_by_periods_only__() else: l1 = self.__lalg__(D) if at0 != l1: verbose('scale modular symbols by %s found at D=%s ' % (l1 / at0, D), level=2) self._scaling = l1 / at0 else: # that is when sign = -1 Dlist = [ -3, -4, -7, -8, -11, -15, -19, -20, -23, -24, -31, -35, -39, -40, -43, -47, -51, -52, -55, -56, -59, -67, -68, -71, -79, -83, -84, -87, -88, -91 ] # a list of negative fundamental discriminants j = 0 at0 = 0 while j < 30 and at0 == 0: # computes [0]+ for the twist of E by D until one value is non-zero D = Dlist[j] if all( valuation(E.conductor(), ell) <= valuation(D, ell) for ell in prime_divisors(D)): at0 = -sum([ kronecker_symbol(D, u) * self(ZZ(u) / D) for u in range(1, abs(D)) ]) j += 1 if j == 30 and at0 == 0: # no more hope for a normalization # we do at least a scaling with the quotient of the periods self.__scale_by_periods_only__() else: l1 = self.__lalg__(D) if at0 != l1: verbose('scale modular symbols by %s' % (l1 / at0)) self._scaling = l1 / at0
def _singularity_analysis_(self, var, zeta, precision): r""" Perform singularity analysis on this growth element. INPUT: - ``var`` -- a string denoting the variable - ``zeta`` -- a number - ``precision`` -- an integer OUTPUT: An asymptotic expansion for `[z^n] f` where `n` is ``var`` and `f` has this growth element as a singular expansion in `T=\frac{1}{1-\frac{z}{\zeta}}\to \infty` where this element is a growth element in `T`. EXAMPLES:: sage: from sage.rings.asymptotic.growth_group import GrowthGroup sage: G = GrowthGroup('exp(x)^QQ * x^QQ * log(x)^QQ') sage: G(x^(1/2))._singularity_analysis_('n', 2, precision=2) 1/sqrt(pi)*(1/2)^n*n^(-1/2) - 1/8/sqrt(pi)*(1/2)^n*n^(-3/2) + O((1/2)^n*n^(-5/2)) sage: G(log(x))._singularity_analysis_('n', 1, precision=5) n^(-1) + O(n^(-3)) sage: G(x*log(x))._singularity_analysis_('n', 1, precision=5) log(n) + euler_gamma + 1/2*n^(-1) + O(n^(-2)) TESTS:: sage: G('exp(x)*log(x)')._singularity_analysis_('n', 1, precision=5) Traceback (most recent call last): ... NotImplementedError: singularity analysis of exp(x)*log(x) not implemented sage: G('exp(x)*x*log(x)')._singularity_analysis_('n', 1, precision=5) Traceback (most recent call last): ... NotImplementedError: singularity analysis of exp(x)*x*log(x) not yet implemented since it has more than two factors sage: G(1)._singularity_analysis_('n', 2, precision=3) Traceback (most recent call last): ... NotImplementedOZero: The error term in the result is O(0) which means 0 for sufficiently large n. sage: G('exp(x)')._singularity_analysis_('n', 2, precision=3) Traceback (most recent call last): ... NotImplementedError: singularity analysis of exp(x) not implemented """ factors = self.factors() if len(factors) == 0: from .asymptotic_expansion_generators import asymptotic_expansions from .misc import NotImplementedOZero raise NotImplementedOZero(var=var) elif len(factors) == 1: return factors[0]._singularity_analysis_(var=var, zeta=zeta, precision=precision) elif len(factors) == 2: from .growth_group import MonomialGrowthGroup from sage.rings.integer_ring import ZZ a, b = factors if all(isinstance(f.parent(), MonomialGrowthGroup) for f in factors) \ and a.parent().gens_monomial() \ and b.parent().gens_logarithmic() \ and a.parent().variable_name() == \ b.parent().variable_name(): if b.exponent not in ZZ: raise NotImplementedError( 'singularity analysis of {} not implemented ' 'since exponent {} of {} is not an integer'.format( self, b.exponent, b.parent().gen())) from sage.rings.asymptotic.asymptotic_expansion_generators import \ asymptotic_expansions return asymptotic_expansions.SingularityAnalysis( var=var, zeta=zeta, alpha=a.exponent, beta=ZZ(b.exponent), delta=0, precision=precision, normalized=False) else: raise NotImplementedError( 'singularity analysis of {} not implemented'.format( self)) else: raise NotImplementedError( 'singularity analysis of {} not yet implemented ' 'since it has more than two factors'.format(self))
def __init__(self, E, sign, normalize="L_ratio"): r""" Modular symbols attached to `E` using ``eclib``. INPUT: - ``E`` - an elliptic curve - ``sign`` - an integer, -1 or 1 - ``normalize`` - either 'L_ratio' (default) or 'none'; For 'L_ratio', the modular symbol is correctly normalized by comparing it to the quotient of `L(E,1)` by the least positive period for the curve and some small twists. For 'none', the modular symbol is almost certainly not correctly normalized, i.e. all values will be a fixed scalar multiple of what they should be. EXAMPLES:: sage: import sage.schemes.elliptic_curves.ell_modular_symbols sage: E=EllipticCurve('11a1') sage: M=sage.schemes.elliptic_curves.ell_modular_symbols.ModularSymbolECLIB(E,+1) sage: M Modular symbol with sign 1 over Rational Field attached to Elliptic Curve defined by y^2 + y = x^3 - x^2 - 10*x - 20 over Rational Field sage: M(0) 1/5 sage: E=EllipticCurve('11a2') sage: M=sage.schemes.elliptic_curves.ell_modular_symbols.ModularSymbolECLIB(E,+1) sage: M(0) 1 This is a rank 1 case with vanishing positive twists. The modular symbol can not be adjusted:: sage: E=EllipticCurve('121b1') sage: M=sage.schemes.elliptic_curves.ell_modular_symbols.ModularSymbolECLIB(E,+1) Warning : Could not normalize the modular symbols, maybe all further results will be multiplied by -1, 2 or -2. sage: M(0) 0 sage: M(1/7) -2 sage: M = EllipticCurve('121d1').modular_symbol(use_eclib=True) sage: M(0) 2 sage: M = EllipticCurve('121d1').modular_symbol(use_eclib=True,normalize='none') sage: M(0) 8 sage: E = EllipticCurve('15a1') sage: [C.modular_symbol(use_eclib=True,normalize='L_ratio')(0) for C in E.isogeny_class()[0]] [1/4, 1/8, 1/4, 1/2, 1/8, 1/16, 1/2, 1] sage: [C.modular_symbol(use_eclib=True,normalize='none')(0) for C in E.isogeny_class()[0]] [1/4, 1/4, 1/4, 1/4, 1/4, 1/4, 1/4, 1/4] Currently, the interface for negative modular symbols in eclib is not yet written:: sage: E.modular_symbol(use_eclib=True,sign=-1) Traceback (most recent call last): ... NotImplementedError: Despite that eclib has now -1 modular symbols the interface to them is not yet written. TESTS (for trac 10236):: sage: E = EllipticCurve('11a1') sage: m = E.modular_symbol(use_eclib=True) sage: m(1/7) 7/10 sage: m(0) 1/5 """ self._sign = ZZ(sign) if self._sign != sign: raise TypeError, 'sign must be an integer' if self._sign != -1 and self._sign != 1: raise TypeError, 'sign must -1 or 1' if self._sign == -1: raise NotImplementedError, "Despite that eclib has now -1 modular symbols the interface to them is not yet written." self._E = E self._use_eclib = True self._base_ring = QQ self._normalize = normalize self._modsym = ECModularSymbol(E) p = ZZ(2) while not E.is_good(p): p = p.next_prime() # this computes {0,oo} using the Hecke-operator at p self._atzero = sum([self._modsym(ZZ(a) / p) for a in range(p)]) / E.Np(p) if normalize == "L_ratio": self._find_scaling_L_ratio() elif normalize == "none": self._scaling = ZZ(1) else: raise ValueError, "no normalization '%s' known for modular symbols using John Cremona's eclib" % normalize
def random_element(self, M=None): r""" EXAMPLES:: sage: D = OverconvergentDistributions(0, 7, base=Qp(7,5)) sage: MS = OverconvergentModularSymbols(14, coefficients=D) sage: Phi = MS.random_element() sage: Phi._consistency_check() This modular symbol satisfies the manin relations sage: D = OverconvergentDistributions(2, base=ZpCA(5,10)) sage: MS = OverconvergentModularSymbols(3, coefficients=D) sage: Phi = MS.random_element() sage: Phi._consistency_check() This modular symbol satisfies the manin relations """ if M is None: M = self.precision_cap() #if M == 1? p = self.prime() k = self.weight() k_val = ZZ(k).valuation(p) if k != 0 else ZZ.zero() manin = self.source() gens = manin.gens() Id = gens[0] gammas = manin.gammas gam_shift = 0 if k != 0: ## We will now a pick a generator to later alter in order to solve the difference equation if len(gammas) > 1: verbose("There is a non-torsion generator") gam_keys = gammas.keys() gam_keys.remove(Id) g0 = gam_keys[0] gam0 = gammas[g0] else: verbose("All generators are torsion") g0 = gens[1] if g0 in manin.reps_with_two_torsion(): verbose("Using a two torsion generator") gam0 = manin.two_torsion_matrix(g0) else: verbose("Using a three torsion generator") gam0 = manin.three_torsion_matrix(g0) a = gam0.matrix()[0, 0] c = gam0.matrix()[1, 0] if g0 in manin.reps_with_three_torsion(): aa = (gam0**2).matrix()[0, 0] cc = (gam0**2).matrix()[1, 0] # gam_shift = max(c.valuation(p),cc.valuation(p)) gam_shift = (a**(k - 1) * c + aa**(k - 1) * cc).valuation(p) else: gam_shift = c.valuation(p) M_in = _prec_for_solve_diff_eqn(M, p) + k_val + gam_shift verbose( "Working with precision %s (M, p, k, gam_shift) = (%s, %s, %s, %s)" % (M_in, M, p, k, gam_shift)) CM = self.coefficient_module().change_precision(M_in) R = CM.base_ring() verbose("M_in, new base ring R = %s, %s" % (M_in, R)) ## this loop runs thru all of the generators (except (0)-(infty)) and randomly chooses a distribution ## to assign to this generator (in the 2,3-torsion cases care is taken to satisfy the relevant relation) D = {} t = CM(0) for g in gens[1:]: #print "CM._prec_cap", CM.precision_cap() D[g] = CM.random_element(M_in) # print "pre:",D[g] if g in manin.reps_with_two_torsion(): if g in manin.reps_with_three_torsion(): raise ValueError("Level 1 not implemented") gamg = manin.two_torsion_matrix(g) D[g] = D[g] - D[g] * gamg t -= D[g] else: if g in manin.reps_with_three_torsion(): gamg = manin.three_torsion_matrix(g) D[g] = 2 * D[g] - D[g] * gamg - D[g] * gamg**2 t -= D[g] else: t += D[g] * gammas[g] - D[g] verbose("t after first random choices: %s" % (t)) ## If k = 0, then t has total measure zero. However, this is not true when k != 0 ## (unlike Prop 5.1 of [PS1] this is not a lift of classical symbol). ## So instead we simply add (const)*mu_1 to some (non-torsion) v[j] to fix this ## here since (mu_1 |_k ([a,b,c,d]-1))(trival char) = chi(a) k a^{k-1} c , ## we take the constant to be minus the total measure of t divided by (chi(a) k a^{k-1} c) ## Something a little different is done in the two and three torsion case. shift = 0 if k != 0: if CM._character != None: chara = CM._character(a) else: chara = 1 if not g0 in manin.reps_with_three_torsion(): err = -t.moment(0) / (chara * k * a**(k - 1) * c) else: err = -t.moment(0) / (chara * k * (a**(k - 1) * c + aa**(k - 1) * cc)) err_val = err.valuation() if err_val < 0: shift -= err_val verbose("err_val: %s, shift: %s, err: %s" % (err_val, shift, err)) err = err << shift verbose("New err: %s" % (err)) t.ordp += shift v = [R(0)] * M_in v[1] = R(err) err = CM(v) verbose("err is: %s" % (err)) ## In the two and three torsion case, we now adjust err to make it satisfy the torsion Manin relations if g0 in manin.reps_with_two_torsion(): err = err - err * gam0 t -= err elif g0 in manin.reps_with_three_torsion(): err = 2 * err - err * gam0 - err * gam0**2 t -= err else: t += err * gam0 - err verbose("The parent of this dist is %s" % (t.parent())) verbose("Before adjusting: %s, %s" % (t.ordp, t._moments)) #try: # mu = t.solve_diff_eqn() #except PrecisionError: # verbose("t"%(t.ordp, t._moments, t.precision_absolute() verbose("Shift before mu = %s" % (shift)) #if shift > 0: # t = t.reduce_precision(t.precision_relative() - k.valuation(p) - gam_shift) # t = t.reduce_precision_absolute(t.precision_absolute() - k_val - gam_shift) t = t.reduce_precision_absolute(_prec_for_solve_diff_eqn(M, p)) verbose("About to solve diff_eqn with %s, %s" % (t.ordp, t._moments)) t.normalize() verbose("After normalize: About to solve diff_eqn with %s, %s" % (t.ordp, t._moments)) mu = t.solve_diff_eqn() verbose("Check difference equation (right after): %s" % (mu * gammas[Id] - mu - t)) mu_val = mu.valuation() verbose("mu_val, mu_ordp, mu_moments and mu: %s, %s, %s, %s" % (mu_val, mu.ordp, mu._moments, mu)) if mu_val < 0: shift -= mu_val mu.ordp -= mu_val if k != 0: err.ordp -= mu_val verbose("Desired M, mu's M: %s, %s" % (M, mu.precision_relative())) verbose("mu.ordp, mu._moments and mu: %s, %s, %s" % (mu.ordp, mu._moments, mu)) mu = mu.reduce_precision_absolute(M) mu.normalize() verbose("Desired M, mu's M: %s, %s" % (M, mu.precision_relative())) verbose("mu.ordp, mu._moments: %s, %s" % (mu.ordp, mu._moments)) if mu.precision_absolute( ) < M: #Eventually, should just remove this check raise ValueError( "Insufficient precision after solving the difference equation." ) D[Id] = -mu if shift > 0: for h in gens[1:]: D[h].ordp += shift #Should the absolute precision of the other values be lowered as well? if k != 0: D[g0] += err ret = self(D) verbose("ret_mu.ordp, ret_mu._moments, ret_mu._prec_rel: %s, %s, %s" % (ret._map[Id].ordp, ret._map[Id]._moments, ret._map[Id].precision_relative())) t.ordp -= mu_val #only for verbose verbose("Check difference equation (at end): %s" % (mu * gammas[Id] - mu - t.reduce_precision(M).normalize())) ## NEED TO BE CAREFUL HERE WITH p=2 AND RANDOMNESS BECAUSE OF THE ISSUE OF PLUS/MINUS AND CHAR 2 if self.sign() == 1: return ret.plus_part() if self.sign() == -1: return ret.minus_part() return ret
def vectors_by_length(self, bound): """ Returns a list of short vectors together with their values. This is a naive algorithm which uses the Cholesky decomposition, but does not use the LLL-reduction algorithm. INPUT: bound -- an integer >= 0 OUTPUT: A list L of length (bound + 1) whose entry L `[i]` is a list of all vectors of length `i`. Reference: This is a slightly modified version of Cohn's Algorithm 2.7.5 in "A Course in Computational Number Theory", with the increment step moved around and slightly re-indexed to allow clean looping. Note: We could speed this up for very skew matrices by using LLL first, and then changing coordinates back, but for our purposes the simpler method is efficient enough. =) EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,1]) sage: Q.vectors_by_length(5) [[[0, 0]], [[0, -1], [-1, 0]], [[-1, -1], [1, -1]], [], [[0, -2], [-2, 0]], [[-1, -2], [1, -2], [-2, -1], [2, -1]]] :: sage: Q1 = DiagonalQuadraticForm(ZZ, [1,3,5,7]) sage: Q1.vectors_by_length(5) [[[0, 0, 0, 0]], [[-1, 0, 0, 0]], [], [[0, -1, 0, 0]], [[-1, -1, 0, 0], [1, -1, 0, 0], [-2, 0, 0, 0]], [[0, 0, -1, 0]]] :: sage: Q = QuadraticForm(ZZ, 4, [1,1,1,1, 1,0,0, 1,0, 1]) sage: map(len, Q.vectors_by_length(2)) [1, 12, 12] :: sage: Q = QuadraticForm(ZZ, 4, [1,-1,-1,-1, 1,0,0, 4,-3, 4]) sage: map(len, Q.vectors_by_length(3)) [1, 3, 0, 3] """ # pari uses eps = 1e-6 ; nothing bad should happen if eps is too big # but if eps is too small, roundoff errors may knock off some # vectors of norm = bound (see #7100) eps = RDF(1e-6) bound = ZZ(floor(max(bound, 0))) Theta_Precision = bound + eps n = self.dim() ## Make the vector of vectors which have a given value ## (So theta_vec[i] will have all vectors v with Q(v) = i.) theta_vec = [[] for i in range(bound + 1)] ## Initialize Q with zeros and Copy the Cholesky array into Q Q = self.cholesky_decomposition() ## 1. Initialize T = n * [RDF(0)] ## Note: We index the entries as 0 --> n-1 U = n * [RDF(0)] i = n-1 T[i] = RDF(Theta_Precision) U[i] = RDF(0) L = n * [0] x = n * [0] Z = RDF(0) ## 2. Compute bounds Z = (T[i] / Q[i][i]).sqrt(extend=False) L[i] = ( Z - U[i]).floor() x[i] = (-Z - U[i]).ceil() done_flag = False Q_val_double = RDF(0) Q_val = 0 ## WARNING: Still need a good way of checking overflow for this value... ## Big loop which runs through all vectors while not done_flag: ## 3b. Main loop -- try to generate a complete vector x (when i=0) while (i > 0): #print " i = ", i #print " T[i] = ", T[i] #print " Q[i][i] = ", Q[i][i] #print " x[i] = ", x[i] #print " U[i] = ", U[i] #print " x[i] + U[i] = ", (x[i] + U[i]) #print " T[i-1] = ", T[i-1] T[i-1] = T[i] - Q[i][i] * (x[i] + U[i]) * (x[i] + U[i]) #print " T[i-1] = ", T[i-1] #print " x = ", x #print i = i - 1 U[i] = 0 for j in range(i+1, n): U[i] = U[i] + Q[i][j] * x[j] ## Now go back and compute the bounds... ## 2. Compute bounds Z = (T[i] / Q[i][i]).sqrt(extend=False) L[i] = ( Z - U[i]).floor() x[i] = (-Z - U[i]).ceil() # carry if we go out of bounds -- when Z is so small that # there aren't any integral vectors between the bounds # Note: this ensures T[i-1] >= 0 in the next iteration while (x[i] > L[i]): i += 1 x[i] += 1 ## 4. Solution found (This happens when i = 0) #print "-- Solution found! --" #print " x = ", x #print " Q_val = Q(x) = ", Q_val Q_val_double = Theta_Precision - T[0] + Q[0][0] * (x[0] + U[0]) * (x[0] + U[0]) Q_val = Q_val_double.round() ## SANITY CHECK: Roundoff Error is < 0.001 if abs(Q_val_double - Q_val) > 0.001: print(" x = ", x) print(" Float = ", Q_val_double, " Long = ", Q_val) raise RuntimeError("The roundoff error is bigger than 0.001, so we should use more precision somewhere...") #print " Float = ", Q_val_double, " Long = ", Q_val, " XX " #print " The float value is ", Q_val_double #print " The associated long value is ", Q_val if (Q_val <= bound): #print " Have vector ", x, " with value ", Q_val theta_vec[Q_val].append(deepcopy(x)) ## 5. Check if x = 0, for exit condition. =) j = 0 done_flag = True while (j < n): if (x[j] != 0): done_flag = False j += 1 ## 3a. Increment (and carry if we go out of bounds) x[i] += 1 while (x[i] > L[i]) and (i < n-1): i += 1 x[i] += 1 #print " Leaving ThetaVectors()" return theta_vec
def linear_relation(self, List): r""" Finds a linear relation between the given list of OMSs. If they are LI, returns a list of all 0's. INPUT: - ``List`` -- a list of OMSs OUTPUT: - A list of p-adic numbers describing the linear relation of the list of OMSs """ for Phi in List: assert Phi.valuation() >= 0, "Symbols must be integral" R = self.base() ## NEED A COMMAND FOR RELATIVE PRECISION OF OMS M = List[0].precision_relative() p = self.prime() d = len(List) V = R**d if d == 1: if List[0].is_zero(): return [R(1)] else: return [R(0)] # Would be better to use the library as follows. Unfortunately, matsolvemod # is not included in the gen class!! #from sage.libs.pari.gen import pari #cols = [List[c].list_of_total_measures() for c in range(len(List) - 1)] #A = pari(Matrix(ZZ, len(cols[0]), d - 1, lambda i, j : cols[j][i].lift())) #aug_col = pari([ZZ(a) for a in List[-1].list_of_total_measures()]).Col() #v = A.matsolvemod(p ** M, aug_col) s = '[' for c in range(d - 1): v = List[c].list_of_total_measures() for r in range(len(v)): s += str(ZZ(v[r])) if r < len(v) - 1: s += ',' if c < d - 2: s += ';' s = s + ']' verbose("s = %s" % (s)) A = gp(s) verbose("A = %s" % (A)) if len(List) == 2: A = A.Mat() s = '[' v = List[d - 1].list_of_total_measures() for r in range(len(v)): s += str(ZZ(v[r])) if r < len(v) - 1: s += ',' s += ']~' verbose("s = %s" % (s)) B = gp(s) verbose("B = %s" % (B)) v = A.mattranspose().matsolvemod(p**M, B) verbose("v = %s" % (v)) if v == 0: return [R(0) for a in range(len(v))] else: ## Move back to SAGE from Pari v = [R(v[a]) for a in range(1, len(v) + 1)] return v + [R(-1)]
def find_primitive_p_divisible_vector__next(self, p, v=None): """ Finds the next `p`-primitive vector (up to scaling) in `L/pL` whose value is `p`-divisible, where the last vector returned was `v`. For an intial call, no `v` needs to be passed. Returns vectors whose last non-zero entry is normalized to 0 or 1 (so no lines are counted repeatedly). The ordering is by increasing the first non-normalized entry. If we have tested all (lines of) vectors, then return None. OUTPUT: vector or None EXAMPLES:: sage: Q = QuadraticForm(ZZ, 2, [10,1,4]) sage: v = Q.find_primitive_p_divisible_vector__next(5); v (1, 1) sage: v = Q.find_primitive_p_divisible_vector__next(5, v); v (1, 0) sage: v = Q.find_primitive_p_divisible_vector__next(5, v); v """ ## Initialize n = self.dim() if v == None: w = vector([ZZ(0) for i in range(n - 1)] + [ZZ(1)]) else: w = deepcopy(v) ## Handle n = 1 separately. if n <= 1: raise RuntimeError, "Sorry -- Not implemented yet!" ## Look for the last non-zero entry (which must be 1) nz = n - 1 while w[nz] == 0: nz += -1 ## Test that the last non-zero entry is 1 (to detect tampering). if w[nz] != 1: print "Warning: The input vector to QuadraticForm.find_primitive_p_divisible_vector__next() is not normalized properly." ## Look for the next vector, until w == 0 while True: ## Look for the first non-maximal (non-normalized) entry ind = 0 while (ind < nz) and (w[ind] == p - 1): ind += 1 #print ind, nz, w ## Increment if (ind < nz): w[ind] += 1 for j in range(ind): w[j] = 0 else: for j in range(ind + 1): ## Clear all entries w[j] = 0 if nz != 0: ## Move the non-zero normalized index over by one, or return the zero vector w[nz - 1] = 1 nz += -1 ## Test for zero vector if w == 0: return None ## Test for p-divisibility if (self(w) % p == 0): return w
def to_int(triple): return ZZ(list(reversed(triple)), base=2)
def MacMahonOmega(var, expression, denominator=None, op=operator.ge, Factorization_sort=False, Factorization_simplify=True): r""" Return `\Omega_{\mathrm{op}}` of ``expression`` with respect to ``var``. To be more precise, calculate .. MATH:: \Omega_{\mathrm{op}} \frac{n}{d_1 \dots d_n} for the numerator `n` and the factors `d_1`, ..., `d_n` of the denominator, all of which are Laurent polynomials in ``var`` and return a (partial) factorization of the result. INPUT: - ``var`` -- a variable or a representation string of a variable - ``expression`` -- a :class:`~sage.structure.factorization.Factorization` of Laurent polynomials or, if ``denominator`` is specified, a Laurent polynomial interpreted as the numerator of the expression - ``denominator`` -- a Laurent polynomial or a :class:`~sage.structure.factorization.Factorization` (consisting of Laurent polynomial factors) or a tuple/list of factors (Laurent polynomials) - ``op`` -- (default: ``operator.ge``) an operator At the moment only ``operator.ge`` is implemented. - ``Factorization_sort`` (default: ``False``) and ``Factorization_simplify`` (default: ``True``) -- are passed on to :class:`sage.structure.factorization.Factorization` when creating the result OUTPUT: A (partial) :class:`~sage.structure.factorization.Factorization` of the result whose factors are Laurent polynomials .. NOTE:: The numerator of the result may not be factored. REFERENCES: - [Mac1915]_ - [APR2001]_ EXAMPLES:: sage: L.<mu, x, y, z, w> = LaurentPolynomialRing(ZZ) sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y/mu]) 1 * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y/mu, 1 - z/mu]) 1 * (-x + 1)^-1 * (-x*y + 1)^-1 * (-x*z + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y*mu, 1 - z/mu]) (-x*y*z + 1) * (-x + 1)^-1 * (-y + 1)^-1 * (-x*z + 1)^-1 * (-y*z + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y/mu^2]) 1 * (-x + 1)^-1 * (-x^2*y + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu^2, 1 - y/mu]) (x*y + 1) * (-x + 1)^-1 * (-x*y^2 + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y*mu, 1 - z/mu^2]) (-x^2*y*z - x*y^2*z + x*y*z + 1) * (-x + 1)^-1 * (-y + 1)^-1 * (-x^2*z + 1)^-1 * (-y^2*z + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y/mu^3]) 1 * (-x + 1)^-1 * (-x^3*y + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y/mu^4]) 1 * (-x + 1)^-1 * (-x^4*y + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu^3, 1 - y/mu]) (x*y^2 + x*y + 1) * (-x + 1)^-1 * (-x*y^3 + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu^4, 1 - y/mu]) (x*y^3 + x*y^2 + x*y + 1) * (-x + 1)^-1 * (-x*y^4 + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu^2, 1 - y/mu, 1 - z/mu]) (x*y*z + x*y + x*z + 1) * (-x + 1)^-1 * (-x*y^2 + 1)^-1 * (-x*z^2 + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu^2, 1 - y*mu, 1 - z/mu]) (-x*y*z^2 - x*y*z + x*z + 1) * (-x + 1)^-1 * (-y + 1)^-1 * (-x*z^2 + 1)^-1 * (-y*z + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y*mu, 1 - z*mu, 1 - w/mu]) (x*y*z*w^2 + x*y*z*w - x*y*w - x*z*w - y*z*w + 1) * (-x + 1)^-1 * (-y + 1)^-1 * (-z + 1)^-1 * (-x*w + 1)^-1 * (-y*w + 1)^-1 * (-z*w + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - y*mu, 1 - z/mu, 1 - w/mu]) (x^2*y*z*w + x*y^2*z*w - x*y*z*w - x*y*z - x*y*w + 1) * (-x + 1)^-1 * (-y + 1)^-1 * (-x*z + 1)^-1 * (-x*w + 1)^-1 * (-y*z + 1)^-1 * (-y*w + 1)^-1 sage: MacMahonOmega(mu, mu^-2, [1 - x*mu, 1 - y/mu]) x^2 * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu^-1, [1 - x*mu, 1 - y/mu]) x * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu, [1 - x*mu, 1 - y/mu]) (-x*y + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu^2, [1 - x*mu, 1 - y/mu]) (-x*y^2 - x*y + y^2 + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 We demonstrate the different allowed input variants:: sage: MacMahonOmega(mu, ....: Factorization([(mu, 2), (1 - x*mu, -1), (1 - y/mu, -1)])) (-x*y^2 - x*y + y^2 + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu^2, ....: Factorization([(1 - x*mu, 1), (1 - y/mu, 1)])) (-x*y^2 - x*y + y^2 + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu^2, [1 - x*mu, 1 - y/mu]) (-x*y^2 - x*y + y^2 + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu^2, (1 - x*mu)*(1 - y/mu)) # not tested because not fully implemented (-x*y^2 - x*y + y^2 + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 sage: MacMahonOmega(mu, mu^2 / ((1 - x*mu)*(1 - y/mu))) # not tested because not fully implemented (-x*y^2 - x*y + y^2 + y + 1) * (-x + 1)^-1 * (-x*y + 1)^-1 TESTS:: sage: MacMahonOmega(mu, 1, [1 - x*mu]) 1 * (-x + 1)^-1 sage: MacMahonOmega(mu, 1, [1 - x/mu]) 1 sage: MacMahonOmega(mu, 0, [1 - x*mu]) 0 sage: MacMahonOmega(mu, L(1), []) 1 sage: MacMahonOmega(mu, L(0), []) 0 sage: MacMahonOmega(mu, 2, []) 2 sage: MacMahonOmega(mu, 2*mu, []) 2 sage: MacMahonOmega(mu, 2/mu, []) 0 :: sage: MacMahonOmega(mu, Factorization([(1/mu, 1), (1 - x*mu, -1), ....: (1 - y/mu, -2)], unit=2)) 2*x * (-x + 1)^-1 * (-x*y + 1)^-2 sage: MacMahonOmega(mu, Factorization([(mu, -1), (1 - x*mu, -1), ....: (1 - y/mu, -2)], unit=2)) 2*x * (-x + 1)^-1 * (-x*y + 1)^-2 sage: MacMahonOmega(mu, Factorization([(mu, -1), (1 - x, -1)])) 0 sage: MacMahonOmega(mu, Factorization([(2, -1)])) 1 * 2^-1 :: sage: MacMahonOmega(mu, 1, [1 - x*mu, 1 - z, 1 - y/mu]) 1 * (-z + 1)^-1 * (-x + 1)^-1 * (-x*y + 1)^-1 :: sage: MacMahonOmega(mu, 1, [1 - x*mu], op=operator.lt) Traceback (most recent call last): ... NotImplementedError: At the moment, only Omega_ge is implemented. sage: MacMahonOmega(mu, 1, Factorization([(1 - x*mu, -1)])) Traceback (most recent call last): ... ValueError: Factorization (-mu*x + 1)^-1 of the denominator contains negative exponents. sage: MacMahonOmega(2*mu, 1, [1 - x*mu]) Traceback (most recent call last): ... ValueError: 2*mu is not a variable. sage: MacMahonOmega(mu, 1, Factorization([(0, 2)])) Traceback (most recent call last): ... ZeroDivisionError: Denominator contains a factor 0. sage: MacMahonOmega(mu, 1, [2 - x*mu]) Traceback (most recent call last): ... NotImplementedError: Factor 2 - x*mu is not normalized. sage: MacMahonOmega(mu, 1, [1 - x*mu - mu^2]) Traceback (most recent call last): ... NotImplementedError: Cannot handle factor 1 - x*mu - mu^2. :: sage: L.<mu, x, y, z, w> = LaurentPolynomialRing(QQ) sage: MacMahonOmega(mu, 1/mu, ....: Factorization([(1 - x*mu, 1), (1 - y/mu, 2)], unit=2)) 1/2*x * (-x + 1)^-1 * (-x*y + 1)^-2 """ from sage.arith.misc import factor from sage.misc.misc_c import prod from sage.rings.integer_ring import ZZ from sage.rings.polynomial.laurent_polynomial_ring \ import LaurentPolynomialRing, LaurentPolynomialRing_univariate from sage.structure.factorization import Factorization if op != operator.ge: raise NotImplementedError( 'At the moment, only Omega_ge is implemented.') if denominator is None: if isinstance(expression, Factorization): numerator = expression.unit() * \ prod(f**e for f, e in expression if e > 0) denominator = tuple(f for f, e in expression if e < 0 for _ in range(-e)) else: numerator = expression.numerator() denominator = expression.denominator() else: numerator = expression # at this point we have numerator/denominator if isinstance(denominator, (list, tuple)): factors_denominator = denominator else: if not isinstance(denominator, Factorization): denominator = factor(denominator) if not denominator.is_integral(): raise ValueError( 'Factorization {} of the denominator ' 'contains negative exponents.'.format(denominator)) numerator *= ZZ(1) / denominator.unit() factors_denominator = tuple(factor for factor, exponent in denominator for _ in range(exponent)) # at this point we have numerator/factors_denominator P = var.parent() if isinstance(P, LaurentPolynomialRing_univariate) and P.gen() == var: L = P L0 = L.base_ring() elif var in P.gens(): var = repr(var) L0 = LaurentPolynomialRing( P.base_ring(), tuple(v for v in P.variable_names() if v != var)) L = LaurentPolynomialRing(L0, var) var = L.gen() else: raise ValueError('{} is not a variable.'.format(var)) other_factors = [] to_numerator = [] decoded_factors = [] for factor in factors_denominator: factor = L(factor) D = factor.dict() if not D: raise ZeroDivisionError('Denominator contains a factor 0.') elif len(D) == 1: exponent, coefficient = next(iteritems(D)) if exponent == 0: other_factors.append(L0(factor)) else: to_numerator.append(factor) elif len(D) == 2: if D.get(0, 0) != 1: raise NotImplementedError( 'Factor {} is not normalized.'.format(factor)) D.pop(0) exponent, coefficient = next(iteritems(D)) decoded_factors.append((-coefficient, exponent)) else: raise NotImplementedError( 'Cannot handle factor {}.'.format(factor)) numerator = L(numerator) / prod(to_numerator) result_numerator, result_factors_denominator = \ _Omega_(numerator.dict(), decoded_factors) if result_numerator == 0: return Factorization([], unit=result_numerator) return Factorization([(result_numerator, 1)] + list( (f, -1) for f in other_factors) + list( (1 - f, -1) for f in result_factors_denominator), sort=Factorization_sort, simplify=Factorization_simplify)
def jordan_blocks_by_scale_and_unimodular(self, p, safe_flag=True): r""" Return a list of pairs `(s_i, L_i)` where `L_i` is a maximal `p^{s_i}`-unimodular Jordan component which is further decomposed into block diagonals of block size `\le 2`. For each `L_i` the 2x2 blocks are listed after the 1x1 blocks (which follows from the convention of the :meth:`local_normal_form` method). .. NOTE:: The decomposition of each `L_i` into smaller blocks is not unique! The ``safe_flag`` argument allows us to select whether we want a copy of the output, or the original output. By default ``safe_flag = True``, so we return a copy of the cached information. If this is set to ``False``, then the routine is much faster but the return values are vulnerable to being corrupted by the user. INPUT: - `p` -- a prime number > 0. OUTPUT: A list of pairs `(s_i, L_i)` where: - `s_i` is an integer, - `L_i` is a block-diagonal unimodular quadratic form over `\ZZ_p`. .. note:: These forms `L_i` are defined over the `p`-adic integers, but by a matrix over `\ZZ` (or `\QQ`?). EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,9,5,7]) sage: Q.jordan_blocks_by_scale_and_unimodular(3) [(0, Quadratic form in 3 variables over Integer Ring with coefficients: [ 1 0 0 ] [ * 5 0 ] [ * * 7 ]), (2, Quadratic form in 1 variables over Integer Ring with coefficients: [ 1 ])] :: sage: Q2 = QuadraticForm(ZZ, 2, [1,1,1]) sage: Q2.jordan_blocks_by_scale_and_unimodular(2) [(-1, Quadratic form in 2 variables over Integer Ring with coefficients: [ 2 2 ] [ * 2 ])] sage: Q = Q2 + Q2.scale_by_factor(2) sage: Q.jordan_blocks_by_scale_and_unimodular(2) [(-1, Quadratic form in 2 variables over Integer Ring with coefficients: [ 2 2 ] [ * 2 ]), (0, Quadratic form in 2 variables over Integer Ring with coefficients: [ 2 2 ] [ * 2 ])] """ ## Try to use the cached result try: if safe_flag: return copy.deepcopy( self.__jordan_blocks_by_scale_and_unimodular_dict[p]) else: return self.__jordan_blocks_by_scale_and_unimodular_dict[p] except Exception: ## Initialize the global dictionary if it doesn't exist if not hasattr(self, '__jordan_blocks_by_scale_and_unimodular_dict'): self.__jordan_blocks_by_scale_and_unimodular_dict = {} ## Deal with zero dim'l forms if self.dim() == 0: return [] ## Find the Local Normal form of Q at p Q1 = self.local_normal_form(p) ## Parse this into Jordan Blocks n = Q1.dim() tmp_Jordan_list = [] i = 0 start_ind = 0 if (n >= 2) and (Q1[0, 1] != 0): start_scale = valuation(Q1[0, 1], p) - 1 else: start_scale = valuation(Q1[0, 0], p) while (i < n): ## Determine the size of the current block if (i == n - 1) or (Q1[i, i + 1] == 0): block_size = 1 else: block_size = 2 ## Determine the valuation of the current block if block_size == 1: block_scale = valuation(Q1[i, i], p) else: block_scale = valuation(Q1[i, i + 1], p) - 1 ## Process the previous block if the valuation increased if block_scale > start_scale: tmp_Jordan_list += [ (start_scale, Q1.extract_variables( range(start_ind, i)).scale_by_factor(ZZ(1) / (QQ(p)**(start_scale)))) ] start_ind = i start_scale = block_scale ## Increment the index i += block_size ## Add the last block tmp_Jordan_list += [(start_scale, Q1.extract_variables(range( start_ind, n)).scale_by_factor(ZZ(1) / QQ(p)**(start_scale)))] ## Cache the result self.__jordan_blocks_by_scale_and_unimodular_dict[p] = tmp_Jordan_list ## Return the result return tmp_Jordan_list
def is_locally_equivalent_to(self, other, check_primes_only=False, force_jordan_equivalence_test=False): """ Determines if the current quadratic form (defined over ZZ) is locally equivalent to the given form over the real numbers and the `p`-adic integers for every prime p. This works by comparing the local Jordan decompositions at every prime, and the dimension and signature at the real place. INPUT: a QuadraticForm OUTPUT: boolean EXAMPLES:: sage: Q1 = QuadraticForm(ZZ, 3, [1, 0, -1, 2, -1, 5]) sage: Q2 = QuadraticForm(ZZ, 3, [2, 1, 2, 2, 1, 3]) sage: Q1.is_globally_equivalent_to(Q2) False sage: Q1.is_locally_equivalent_to(Q2) True """ ## TO IMPLEMENT: if self.det() == 0: raise NotImplementedError( "OOps! We need to think about whether this still works for degenerate forms... especially check the signature." ) ## Check that both forms have the same dimension and base ring if (self.dim() != other.dim()) or (self.base_ring() != other.base_ring()): return False ## Check that the determinant and level agree if (self.det() != other.det()) or (self.level() != other.level()): return False ## ----------------------------------------------------- ## Test equivalence over the real numbers if self.signature() != other.signature(): return False ## Test equivalence over Z_p for all primes if (self.base_ring() == ZZ) and (force_jordan_equivalence_test == False): ## Test equivalence with Conway-Sloane genus symbols (default over ZZ) if self.CS_genus_symbol_list() != other.CS_genus_symbol_list(): return False else: ## Test equivalence via the O'Meara criterion. for p in prime_divisors(ZZ(2) * self.det()): #print "checking the prime p = ", p if not self.has_equivalent_Jordan_decomposition_at_prime(other, p): return False ## All tests have passed! return True
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 _N0_RH(): return ceil( log(2 * binomial(2 * self._genus, self._genus), self._p) + self._genus * self._n / ZZ(2))
def regular_symmetric_hadamard_matrix_with_constant_diagonal( n, e, existence=False): r""" Return a Regular Symmetric Hadamard Matrix with Constant Diagonal. A Hadamard matrix is said to be *regular* if its rows all sum to the same value. For `\epsilon\in\{-1,+1\}`, we say that `M` is a `(n,\epsilon)-RSHCD` if `M` is a regular symmetric Hadamard matrix with constant diagonal `\delta\in\{-1,+1\}` and row sums all equal to `\delta \epsilon \sqrt(n)`. For more information, see [HX2010]_ or 10.5.1 in [BH2012]_. For the case `n=324`, see :func:`RSHCD_324` and [CP2016]_. INPUT: - ``n`` (integer) -- side of the matrix - ``e`` -- one of `-1` or `+1`, equal to the value of `\epsilon` EXAMPLES:: sage: from sage.combinat.matrices.hadamard_matrix import regular_symmetric_hadamard_matrix_with_constant_diagonal sage: regular_symmetric_hadamard_matrix_with_constant_diagonal(4,1) [ 1 1 1 -1] [ 1 1 -1 1] [ 1 -1 1 1] [-1 1 1 1] sage: regular_symmetric_hadamard_matrix_with_constant_diagonal(4,-1) [ 1 -1 -1 -1] [-1 1 -1 -1] [-1 -1 1 -1] [-1 -1 -1 1] Other hardcoded values:: sage: for n,e in [(36,1),(36,-1),(100,1),(100,-1),(196, 1)]: # long time ....: print(repr(regular_symmetric_hadamard_matrix_with_constant_diagonal(n,e))) 36 x 36 dense matrix over Integer Ring 36 x 36 dense matrix over Integer Ring 100 x 100 dense matrix over Integer Ring 100 x 100 dense matrix over Integer Ring 196 x 196 dense matrix over Integer Ring sage: for n,e in [(324,1),(324,-1)]: # not tested - long time, tested in RSHCD_324 ....: print(repr(regular_symmetric_hadamard_matrix_with_constant_diagonal(n,e))) 324 x 324 dense matrix over Integer Ring 324 x 324 dense matrix over Integer Ring From two close prime powers:: sage: regular_symmetric_hadamard_matrix_with_constant_diagonal(64,-1) 64 x 64 dense matrix over Integer Ring (use the '.str()' method to see the entries) From a prime power and a conference matrix:: sage: regular_symmetric_hadamard_matrix_with_constant_diagonal(676,1) # long time 676 x 676 dense matrix over Integer Ring (use the '.str()' method to see the entries) Recursive construction:: sage: regular_symmetric_hadamard_matrix_with_constant_diagonal(144,-1) 144 x 144 dense matrix over Integer Ring (use the '.str()' method to see the entries) REFERENCE: - [BH2012]_ - [HX2010]_ """ if existence and (n, e) in _rshcd_cache: return _rshcd_cache[n, e] from sage.graphs.strongly_regular_db import strongly_regular_graph def true(): _rshcd_cache[n, e] = True return True M = None if abs(e) != 1: raise ValueError sqn = None if is_square(n): sqn = int(sqrt(n)) if n < 0: if existence: return False raise ValueError elif n == 4: if existence: return true() if e == 1: M = J(4) - 2 * matrix(4, [[int(i + j == 3) for i in range(4)] for j in range(4)]) else: M = -J(4) + 2 * I(4) elif n == 36: if existence: return true() if e == 1: M = strongly_regular_graph(36, 15, 6, 6).adjacency_matrix() M = J(36) - 2 * M else: M = strongly_regular_graph(36, 14, 4, 6).adjacency_matrix() M = -J(36) + 2 * M + 2 * I(36) elif n == 100: if existence: return true() if e == -1: M = strongly_regular_graph(100, 44, 18, 20).adjacency_matrix() M = 2 * M - J(100) + 2 * I(100) else: M = strongly_regular_graph(100, 45, 20, 20).adjacency_matrix() M = J(100) - 2 * M elif n == 196 and e == 1: if existence: return true() M = strongly_regular_graph(196, 91, 42, 42).adjacency_matrix() M = J(196) - 2 * M elif n == 324: if existence: return true() M = RSHCD_324(e) elif (e == 1 and n % 16 == 0 and not sqn is None and is_prime_power(sqn - 1) and is_prime_power(sqn + 1)): if existence: return true() M = -rshcd_from_close_prime_powers(sqn) elif (e == 1 and not sqn is None and sqn % 4 == 2 and strongly_regular_graph(sqn - 1, (sqn - 2) // 2, (sqn - 6) // 4, existence=True) is True and is_prime_power(ZZ(sqn + 1))): if existence: return true() M = rshcd_from_prime_power_and_conference_matrix(sqn + 1) # Recursive construction: the Kronecker product of two RSHCD is a RSHCD else: from itertools import product for n1, e1 in product(divisors(n)[1:-1], [-1, 1]): e2 = e1 * e n2 = n // n1 if (regular_symmetric_hadamard_matrix_with_constant_diagonal( n1, e1, existence=True) is True and regular_symmetric_hadamard_matrix_with_constant_diagonal( n2, e2, existence=True)) is True: if existence: return true() M1 = regular_symmetric_hadamard_matrix_with_constant_diagonal( n1, e1) M2 = regular_symmetric_hadamard_matrix_with_constant_diagonal( n2, e2) M = M1.tensor_product(M2) break if M is None: from sage.misc.unknown import Unknown _rshcd_cache[n, e] = Unknown if existence: return Unknown raise ValueError("I do not know how to build a {}-RSHCD".format( (n, e))) assert M * M.transpose() == n * I(n) assert set(map(sum, M)) == {ZZ(e * sqn)} return M
def _reduce_vector_horizontal_BSGS(self, G, e, s): r""" INPUT: - a vector -- G \in W_{e, s} OUTPUT: - a vector -- H \in W_{e - p, s} such that G x^e y^{-s} dx \cong H x^{e - p} y^{-s} dx TESTS:: sage: p = 4999 sage: x = PolynomialRing(GF(p),"x").gen() sage: C = CyclicCover(3, x^4 + 4*x^3 + 9*x^2 + 3*x + 1) sage: C._init_frob() sage: C._initialize_fat_horizontal(p, 3) sage: C._reduce_vector_horizontal_BSGS((0, 0, 0, 0), 2*p - 1, p) (0, 0, 0, 0) sage: C._reduce_vector_horizontal_BSGS((83283349998, 0, 0, 0), 2*p - 1, p) (23734897071, 84632332850, 44254975407, 23684517017) sage: C._reduce_vector_horizontal_BSGS((98582524551, 3200841460, 6361495378, 98571346457), 2*p - 1, p) (96813533420, 61680190736, 123292559950, 96786566978) """ if G == 0: return G if self._verbose > 2: print("_reduce_vector_horizontal_BSGS(self, %s, %s, %s)" % (vector(self._Qq, G), e, s)) assert (e + 1) % self._p == 0 (m0, m1), (M0, M1) = self._horizontal_matrix_reduction(s) vect = vector(self._Zq, G) # we do the first d reductions carefully D = 1 for i in reversed(range(e - self._d + 1, e + 1)): Mi = M0 + i * M1 Di = m0 + i * m1 vect = Mi * vect D *= Di assert Di % self._p == 0 iD = 1 / self._Zq0(D.lift() / self._p) vect = vector(self._Zq0, [iD * ZZ(elt.lift() / self._p) for elt in vect]) # use BSGS iDH, MH = self._horizontal_fat_s[s][(e + 1) / self._p - 1] vect = iDH * (MH * vect.change_ring(self._Zq0)) # last reduction i = e - self._p + 1 Mi = M0 + i * M1 Di = 1 / (m0 + i * m1) vect = Di * (Mi * vect.change_ring(self._Zq)) if self._verbose > 2: print("done _reduce_vector_horizontal_BSGS(self, %s, %s, %s)" % (vector(self._Qq, G), e, s)) print("return %s\n" % (vector(self._Qq, vect), )) return vect
def d_basis(F, strat=True): r""" Return the `d`-basis for the Ideal ``F`` as defined in [BW93]_. INPUT: - ``F`` - an ideal - ``strat`` - use update strategy (default: ``True``) EXAMPLE:: sage: from sage.rings.polynomial.toy_d_basis import d_basis sage: A.<x,y> = PolynomialRing(ZZ, 2) sage: f = -y^2 - y + x^3 + 7*x + 1 sage: fx = f.derivative(x) sage: fy = f.derivative(y) sage: I = A.ideal([f,fx,fy]) sage: gb = d_basis(I); gb [x - 2020, y - 11313, 22627] """ R = F.ring() K = R.base_ring() G = set(inter_reduction(F.gens())) B = set( filter(lambda x_y: x_y[0] != x_y[1], [(f1, f2) for f1 in G for f2 in G])) D = set() C = set(B) LCM = R.monomial_lcm divides = R.monomial_divides divides_ZZ = lambda x, y: ZZ(x).divides(ZZ(y)) while B != set(): while C != set(): f1, f2 = select(C) C.remove((f1, f2)) lcm_lmf1_lmf2 = LCM(LM(f1), LM(f2)) if not any( divides(LM(g), lcm_lmf1_lmf2) and \ divides_ZZ( LC(g), LC(f1) ) and \ divides_ZZ( LC(g), LC(f2) ) \ for g in G): h = gpol(f1, f2) h0 = h.reduce(G) if h0.lc() < 0: h0 *= -1 if not strat: D = D.union([(g, h0) for g in G]) G.add(h0) else: G, D = update(G, D, h0) G = inter_reduction(G) f1, f2 = select(B) B.remove((f1, f2)) h = spol(f1, f2) h0 = h.reduce(G) if h0 != 0: if h0.lc() < 0: h0 *= -1 if not strat: D = D.union([(g, h0) for g in G]) G.add(h0) else: G, D = update(G, D, h0) B = B.union(D) C = D D = set() return Sequence(sorted(inter_reduction(G), reverse=True))