def find_bounding_cycle(self, G, npow=1): r''' Use recursively that: - x^a g = a x + g - del(x^a|g) - del(x|(x + x^2 + ... + x^(a-1))) - x^(-a) g = -a x + g + del(1|1) + del(x^(a)|(x^-a)) - del(x^(-a)|g) + del(x|(x + x^2 + ... + x^(a-1))) ''' B = G.Gn.B gprimeq = self.quaternion_rep gprime = G.Gn(gprimeq) gword = tietze_to_syllables(gprime.word_rep) rels = G.Gn._calculate_relation(G.Gn.get_weight_vector(gprime**npow), separated=True) # If npow > 1 then add the boundary relating self^npow with npow*self ans = [(-1, [gprimeq**j for j in range(1, npow)], gprime)] if npow > 1 else [] num_terms = len(gword) jj = 0 for i, a in gword: jj += 1 # Decompose gword as g^a*gprime, where g is a generator g = G.Gn.gen(i) gq = g.quaternion_rep gaq = gq**a ga = g**a # Add the boundary relating g^a*gprime with g^a + gprime (unless we are in the last step) ans.extend([(-npow, [gaq], G.Gn(gword[jj:]))] if jj < num_terms else []) # If a < 0 use the relation g^a = -g^(-a) + del(g^a|g^(-a)) ans.extend([(npow, [gaq**-1], ga)] if a < 0 else []) # By the above line we have to deal with g^a with -g^abs(a) if a <0 # We add the corresponding boundaries, which we will substract if a > 0 and add if a < 0 ans.extend([(-sgn(a) * npow, [gq**j for j in range(1, abs(a))], g)] if abs(a) > 1 else []) for m, rel in rels: # we deal with the relation rel^m # it is equivalent to deal with m*rel, because these two differ by a boundary that integrates to 1 assert m > 0 num_terms = len(rel) jj = 0 for i, a in rel: jj += 1 # we deal with a part of the relation of the form g*g' g = G.Gn.gen(i) gq = g.quaternion_rep ga = g**a gaq = gq**a # add the boundary relating g and g' ans.extend([(-m, [gaq], G.Gn(rel[jj:]))] if jj < num_terms else []) # If a < 0 use the relation g^a = -g^(-a) + del(g^a|g^(-a)) ans.extend([(m, [gaq**-1], ga)] if a < 0 else []) # add the boundaries of g^abs(a) ans.extend([(-sgn(a) * m, [gq**j for j in range(1, abs(a))], g)] if abs(a) > 1 else []) return ans
def octants(self): assert self._degrees.is_finite() from sage.combinat.posets.posets import Poset def pocom(a, b): t1, e1, s1 = a t2, e2, s2 = b if (t1 in (t2, 0)) and (e1 in (e2, 0)) and (s1 in (s2, 0)): return True return False X = Poset(([(sgn(t), sgn(e), sgn(s)) for (t, e, s) in self._degrees], pocom)) return [list(u) for u in X.maximal_elements()]
def neighbor(self, pt, d): r""" Return the neighbors of the point pt in direction d. INPUT: - ``pt`` - tuple, point in Z^d - ``direction`` - integer, possible values are 1, 2, ..., d and -1, -2, ..., -d. EXAMPLES: sage: from slabbe import BondPercolationSample sage: S = BondPercolationSample(0.5,2) sage: S.neighbor((2,3),1) (3, 3) sage: S.neighbor((2,3),2) (2, 4) sage: S.neighbor((2,3),-1) (1, 3) sage: S.neighbor((2,3),-2) (2, 2) """ R = xrange(self._dimension) a = sgn(d) d = abs(d) return tuple(pt[k]+a if k==d-1 else pt[k] for k in R)
def neighbor(self, pt, d): r""" Return the neighbors of the point pt in direction d. INPUT: - ``pt`` - tuple, point in Z^d - ``direction`` - integer, possible values are 1, 2, ..., d and -1, -2, ..., -d. EXAMPLES: sage: from slabbe import BondPercolationSample sage: S = BondPercolationSample(0.5,2) sage: S.neighbor((2,3),1) (3, 3) sage: S.neighbor((2,3),2) (2, 4) sage: S.neighbor((2,3),-1) (1, 3) sage: S.neighbor((2,3),-2) (2, 2) """ R = xrange(self._dimension) a = sgn(d) d = abs(d) return tuple(pt[k] + a if k == d - 1 else pt[k] for k in R)
def polygon_compare(poly1,poly2): r""" Compare two polygons first by area, then by number of sides, then by lexigraphical ording on edge vectors.""" from sage.functions.generalized import sgn res = int(sgn(-poly1.area()+poly2.area())) if res!=0: return res res = int(sgn(poly1.num_edges()-poly2.num_edges())) if res!=0: return res ne=poly1.num_edges() for i in range(0,ne-1): edge_diff = poly1.edge(i) - poly2.edge(i) res = int(sgn(edge_diff[0])) if res!=0: return res res = int(sgn(edge_diff[1])) if res!=0: return res return 0
def _test_generator_degrees(self, tester=None, **options): from sage.misc.sage_unittest import TestSuite from itertools import islice is_sub_testsuite = tester is not None tester = self._tester(tester=tester, **options) if self._is_sorted_by_t_degrees(): gdegs = [g[0] for g in islice(self._degrees, 30)] gd = gdegs for u in gd: if u < 0: gd = [-u for u in gdegs] break for (u, v) in zip(gd, gd[1:]): tester.assertTrue( u <= v, LazyFormat( "generators not sorted by internal degree, degrees= %s..." ) % (gdegs,), ) octs = self.octants() for (t, e, s) in islice(self._degrees, 30): if 0 != (e & 1): # allow mismatch for exterior generators continue ok = False for (ts, es, ss) in octs: if (sgn(t) in (ts, 0)) and (sgn(e) in (es, 0)) and (sgn(s) in (ss, 0)): ok = True break tester.assertTrue( ok, LazyFormat("generator degree (%d,%d,%d) not in one of the octants %s") % (t, e, s, octs), )
def gamma_list_to_cyclotomic(galist): r""" Convert a quotient of products of polynomials `x^n - 1` to a quotient of products of cyclotomic polynomials. INPUT: - ``galist`` -- a list of integers, where an integer `n` represents the power `(x^{|n|} - 1)^{\operatorname{sgn}(n)}` OUTPUT: a pair of list of integers, where `k` represents the cyclotomic polynomial `\Phi_k` EXAMPLES:: sage: from sage.modular.hypergeometric_motive import gamma_list_to_cyclotomic sage: gamma_list_to_cyclotomic([-1, -1, 2]) ([2], [1]) sage: gamma_list_to_cyclotomic([-1, -1, -1, -3, 6]) ([2, 6], [1, 1, 1]) sage: gamma_list_to_cyclotomic([-1, 2, 3, -4]) ([3], [4]) sage: gamma_list_to_cyclotomic([8,2,2,2,-6,-4,-3,-1]) ([2, 2, 8], [3, 3, 6]) """ resu = defaultdict(int) for n in galist: eps = sgn(n) for d in divisors(abs(n)): resu[d] += eps return (sorted(d for d in resu for k in range(resu[d])), sorted(d for d in resu for k in range(-resu[d])))
def gamma_list_to_cyclotomic(galist): r""" Convert a quotient of products of polynomials `x^n - 1` to a quotient of products of cyclotomic polynomials. INPUT: - ``galist`` -- a list of integers, where an integer `n` represents the power `(x^{|n|} - 1)^{\operatorname{sgn}(n)}` OUTPUT: a pair of list of integers, where `k` represents the cyclotomic polynomial `\Phi_k` EXAMPLES:: sage: from sage.modular.hypergeometric_motive import gamma_list_to_cyclotomic sage: gamma_list_to_cyclotomic([-1, -1, 2]) ([2], [1]) sage: gamma_list_to_cyclotomic([-1, -1, -1, -3, 6]) ([2, 6], [1, 1, 1]) sage: gamma_list_to_cyclotomic([-1, 2, 3, -4]) ([3], [4]) sage: gamma_list_to_cyclotomic([8,2,2,2,-6,-4,-3,-1]) ([2, 2, 8], [3, 3, 6]) """ resu = defaultdict(int) for n in galist: eps = sgn(n) for d in divisors(abs(n)): resu[d] += eps return (sorted(d for d in resu for k in range(resu[d])), sorted(d for d in resu for k in range(-resu[d])))
def gamma_list(self): r""" Return a list of integers describing the `x^n - 1` factors. Each integer `n` stands for `(x^{|n|} - 1)^{\operatorname{sgn}(n)}`. EXAMPLES:: sage: from sage.modular.hypergeometric_motive import HypergeometricData as Hyp sage: Hyp(alpha_beta=([1/2],[0])).gamma_list() [-1, -1, 2] sage: Hyp(cyclotomic=([6,2],[1,1,1])).gamma_list() [-1, -1, -1, -3, 6] sage: Hyp(cyclotomic=([3],[4])).gamma_list() [-1, 2, 3, -4] """ gamma = self.gamma_array() resu = [] for v, n in sorted(gamma.items()): resu += [sgn(n) * v] * abs(n) return resu
def gamma_list(self): r""" Return a list of integers describing the `x^n - 1` factors. Each integer `n` stands for `(x^{|n|} - 1)^{\operatorname{sgn}(n)}`. EXAMPLES:: sage: from sage.modular.hypergeometric_motive import HypergeometricData as Hyp sage: Hyp(alpha_beta=([1/2],[0])).gamma_list() [-1, -1, 2] sage: Hyp(cyclotomic=([6,2],[1,1,1])).gamma_list() [-1, -1, -1, -3, 6] sage: Hyp(cyclotomic=([3],[4])).gamma_list() [-1, 2, 3, -4] """ gamma = self.gamma_array() resu = [] for v, n in gamma.items(): resu += [sgn(n) * v] * abs(n) return resu
def spin_weighted_spheroidal_harmonic(s, l, m, gamma, theta, phi, verbose=False, cached=True, min_nmax=8): r""" Return the spin-weighted oblate spheroidal harmonic of spin weight ``s``, degree ``l``, azimuthal order ``m`` and spheroidicity ``gamma``. INPUT: - ``s`` -- integer; the spin weight - ``l`` -- non-negative integer; the harmonic degree - ``m`` -- integer within the range ``[-l, l]``; the azimuthal number - ``gamma`` -- spheroidicity parameter - ``theta`` -- colatitude angle - ``phi`` -- azimuthal angle - ``verbose`` -- (default: ``False``) determines whether some details of the computation are printed out - ``cached`` -- (default: ``True``) determines whether the eigenvectors shall be cached; setting ``cached`` to ``False`` forces a new computation, without caching the result - ``min_nmax`` -- (default: 8) integer; floor for the evaluation of the parameter ``nmax``, which sets the highest degree of the spherical harmonic expansion as ``l+nmax``. OUTPUT: - value of `{}_s S_{lm}^\gamma(\theta,\phi)` ALGORITHM: The spin-weighted oblate spheroidal harmonics are computed by an expansion over spin-weighted *spherical* harmonics, the coefficients of the expansion being obtained by solving an eigenvalue problem, as exposed in Appendix A of S.A. Hughes, Phys. Rev. D **61**, 084004 (2000) [:doi:`10.1103/PhysRevD.61.084004`]. EXAMPLES:: sage: from kerrgeodesic_gw import spin_weighted_spheroidal_harmonic sage: spin_weighted_spheroidal_harmonic(-2, 2, 2, 1.1, pi/2, 0) # tol 1.0e-13 0.08702532727529422 sage: spin_weighted_spheroidal_harmonic(-2, 2, 2, 1.1, pi/3, pi/3) # tol 1.0e-13 -0.14707166027821453 + 0.25473558795537715*I sage: spin_weighted_spheroidal_harmonic(-2, 2, 2, 1.1, pi/3, pi/3, cached=False) # tol 1.0e-13 -0.14707166027821453 + 0.25473558795537715*I sage: spin_weighted_spheroidal_harmonic(-2, 2, 2, 1.1, pi/3, pi/4) # tol 1.0e-13 1.801108380050024e-17 + 0.2941433205564291*I sage: spin_weighted_spheroidal_harmonic(-2, 2, -1, 1.1, pi/3, pi/3, cached=False) # tol 1.0e-13 0.11612826056899399 - 0.20114004750009495*I Test that the relation `{}_s S_{lm}^\gamma(\theta,\phi) = (-1)^{l+s}\, {}_s S_{l,-m}^{-\gamma}(\pi-\theta,-\phi)` [cf. Eq. (2.3) of :arxiv:`1810.00432`], which is used to evaluate `{}_s S_{lm}^\gamma(\theta,\phi)` when `m<0` and ``cached`` is ``True``, is correctly implemented:: sage: spin_weighted_spheroidal_harmonic(-2, 2, -2, 1.1, pi/3, pi/3) # tol 1.0e-13 -0.04097260436590737 - 0.07096663248016997*I sage: abs(_ - spin_weighted_spheroidal_harmonic(-2, 2, -2, 1.1, pi/3, pi/3, ....: cached=False)) < 1.e-13 True sage: spin_weighted_spheroidal_harmonic(-2, 3, -1, 1.1, pi/3, pi/3) # tol 1.0e-13 0.1781880511506843 - 0.3086307578946672*I sage: abs(_ - spin_weighted_spheroidal_harmonic(-2, 3, -1, 1.1, pi/3, pi/3, ....: cached=False)) < 1.e-13 True """ global _eigenvectors if m < 0 and cached: # We use the symmetry formula # {}_s S_{lm}^\gamma(\theta,\phi) = # (-1)^{l+s} {}_s S_{l,-m}^{-\gamma}(\pi-\theta,-\phi) # cf. Eq. (2.3) of https://arxiv.org/abs/1810.00432 return (-1)**(l + s) * spin_weighted_spheroidal_harmonic( s, l, -m, -gamma, pi - theta, -phi, verbose=verbose, cached=cached, min_nmax=min_nmax) s = ZZ(s) # ensure that we are dealing with Sage integers l = ZZ(l) m = ZZ(m) gamma = RDF(gamma) # all computations are performed with RDF param = (s, l, m, gamma) if cached: if param not in _eigenvectors: _eigenvectors[param] = _compute_eigenvector(*param, verbose=verbose, min_nmax=min_nmax) data = _eigenvectors[param] else: data = _compute_eigenvector(*param, verbose=verbose, min_nmax=min_nmax) egvec = data[1] nmin = data[2] nmax = data[3] lmin = data[4] # If neither theta nor phi is symbolic, we convert both of them to RDF: if not ((isinstance(theta, Expression) and theta.variables()) or (isinstance(phi, Expression) and phi.variables())): theta = RDF(theta) phi = RDF(phi) resu = RDF(0) for k in range(-nmin, nmax + 1): resu += egvec[k + nmin] \ * spin_weighted_spherical_harmonic(s, l+k, m, theta, phi, condon_shortley=True) resu *= sgn(egvec[min(l - lmin + 1, (nmax + nmin) / 2 + 1) - 1]) return resu