def plot(self, max_points=2500, **args): r""" Create a visualization of this `p`-adic ring as a fractal similar to a generalization of the Sierpi\'nski triangle. The resulting image attempts to capture the algebraic and topological characteristics of `\mathbb{Z}_p`. INPUT: - ``max_points`` -- the maximum number or points to plot, which controls the depth of recursion (default 2500) - ``**args`` -- color, size, etc. that are passed to the underlying point graphics objects REFERENCES: - Cuoco, A. ''Visualizing the `p`-adic Integers'', The American Mathematical Monthly, Vol. 98, No. 4 (Apr., 1991), pp. 355-364 EXAMPLES:: sage: Zp(3).plot() Graphics object consisting of 1 graphics primitive sage: Zp(5).plot(max_points=625) Graphics object consisting of 1 graphics primitive sage: Zp(23).plot(rgbcolor=(1,0,0)) Graphics object consisting of 1 graphics primitive """ if 'pointsize' not in args: args['pointsize'] = 1 from sage.misc.mrange import cartesian_product_iterator from sage.rings.real_double import RDF from sage.plot.all import points, circle, Graphics p = self.prime() phi = 2*RDF.pi()/p V = RDF**2 vs = [V([(phi*t).sin(), (phi*t).cos()]) for t in range(p)] all = [] depth = max(RDF(max_points).log(p).floor(), 1) scale = min(RDF(1.5/p), 1/RDF(3)) pts = [vs]*depth if depth == 1 and 23 < p < max_points: extras = int(max_points/p) if p/extras > 5: pts = [vs]*depth + [vs[::extras]] for digits in cartesian_product_iterator(pts): p = sum([v * scale**n for n, v in enumerate(digits)]) all.append(tuple(p)) g = points(all, **args) # Set default plotting options g.axes(False) g.set_aspect_ratio(1) return g
def log(x, b=None): r""" Return the log of ``x`` to the base `b`. The default base is `e`. DEPRECATED by :trac:`19444` INPUT: - ``x`` -- number - `b` -- base (default: ``None``, which means natural log) OUTPUT: number .. NOTE:: In Magma, the order of arguments is reversed from in Sage, i.e., the base is given first. We use the opposite ordering, so the base can be viewed as an optional second argument. EXAMPLES:: sage: from sage.misc.functional import log sage: log(e^2) doctest:warning... DeprecationWarning: use .log() or log() from sage.functions.log instead See http://trac.sagemath.org/19444 for details. 2 sage: log(16,2) 4 sage: log(3.) 1.09861228866811 sage: log(float(3)) # abs tol 1e-15 1.0986122886681098 """ deprecation(19444, 'use .log() or log() from sage.functions.log instead') if b is None: if hasattr(x, 'log'): return x.log() return RDF(x).log() else: if hasattr(x, 'log'): return x.log(b) return RDF(x).log(b)
def set_transform(self, s, tx, ty): r""" Set the parts of the transformation which convert to screen coordinates. """ s = QQ(s) tx = QQ(tx) ty = QQ(ty) ratio = self._s / s if (ratio > QQ(999) / 1000) and (ratio < QQ(1001) / 1000): # ignore negligible change in scale! self._editor.get_canvas().move(ALL, RDF(tx - self._tx), RDF(ty - self._ty)) self._tx = self._field(tx) self._ty = self._field(ty) else: self.before_zoom_change() scale = 1 / ratio offset_x = ((self._s * tx) - (self._tx * s)) / (self._s - s) offset_y = ((self._s * ty) - (self._ty * s)) / (self._s - s) self._editor.get_canvas().scale(ALL, RDF(offset_x), RDF(offset_y), RDF(scale), RDF(scale)) self._s = self._field(s) self._tx = self._field(tx) self._ty = self._field(ty) self.after_zoom_change()
def round(x, ndigits=0): """ round(number[, ndigits]) - double-precision real number Round a number to a given precision in decimal digits (default 0 digits). This always returns a real double field element. EXAMPLES:: sage: round(sqrt(2),2) 1.41 sage: round(sqrt(2),5) 1.41421 sage: round(pi) 3.0 sage: b = 5.4999999999999999 sage: round(b) 5.0 Since we use floating-point with a limited range, some roundings can't be performed:: sage: round(sqrt(Integer('1'*500))) Traceback (most recent call last): ... OverflowError: cannot convert float infinity to long IMPLEMENTATION: If ndigits is specified, it calls Python's builtin round function, and converts the result to a real double field element. Otherwise, it tries the argument's .round() method, and if that fails, it falls back to the builtin round function. .. note:: This is currently slower than the builtin round function, since it does more work - i.e., allocating an RDF element and initializing it. To access the builtin version do ``import __builtin__; __builtin__.round``. """ try: if ndigits: return RealDoubleElement(__builtin__.round(x, ndigits)) else: try: return RealDoubleElement(x.round()) except AttributeError: return RealDoubleElement(__builtin__.round(x, 0)) except ArithmeticError: if not isinstance(x, RealDoubleElement): return round(RDF(x), ndigits) else: raise
def power_spectral_density(freq): r""" Return the effective power spectral density (PSD) of the detector noise at a given frequency. INPUT: - ``freq`` -- frequency `f` (in `\mathrm{Hz}`) OUTPUT: - effective power spectral density `S(f)` (in `\mathrm{Hz}^{-1}`) EXAMPLES:: sage: from kerrgeodesic_gw import lisa_detector sage: Sn = lisa_detector.power_spectral_density sage: Sn(1.e-1) # tol 1.0e-13 3.3944027493062926e-39 sage: Sn(1.e-2) # tol 1.0e-13 2.738383947022306e-40 sage: Sn(1.e-3) # tol 1.0e-13 3.269807574220045e-38 """ global _psd_spline if not _psd_spline: data = [] file_name = os.path.join(os.path.dirname(__file__), "data/Sensitivity_LISA_SciRD1806_Alloc.dat") with open(file_name, "r") as data_file: for dline in data_file: f, s = dline.split('\t') data.append((log(RDF(f), 10), log(RDF(s), 10))) _psd_spline = spline(data) if freq < 1.e-5 or freq > 1.: raise ValueError("frequency {} Hz is out of range".format(freq)) freq = RDF(freq) return RDF(10)**(_psd_spline(log(freq, 10)))
def log(x,b=None): r""" Returns the log of x to the base b. The default base is e. INPUT: - ``x`` - number - ``b`` - base (default: None, which means natural log) OUTPUT: number .. note:: In Magma, the order of arguments is reversed from in Sage, i.e., the base is given first. We use the opposite ordering, so the base can be viewed as an optional second argument. EXAMPLES:: sage: log(e^2) 2 sage: log(16,2) 4 sage: log(3.) 1.09861228866811 """ if b is None: if hasattr(x, 'log'): return x.log() return RDF(x)._log_base(1) else: if hasattr(x, 'log'): return x.log(b) return RDF(x).log(b)
def acos(x): """ Returns the arc cosine of x. EXAMPLES:: sage: acos(.5) 1.04719755119660 sage: acos(sin(pi/3)) arccos(1/2*sqrt(3)) sage: acos(sin(pi/3)).simplify_full() 1/6*pi """ try: return x.acos() except AttributeError: return RDF(x).acos()
def asin(x): """ Returns the arc sine of x. EXAMPLES:: sage: asin(.5) 0.523598775598299 sage: asin(sin(pi/3)) arcsin(1/2*sqrt(3)) sage: asin(sin(pi/3)).simplify_full() 1/3*pi """ try: return x.asin() except AttributeError: return RDF(x).asin()
def atan(x): """ Returns the arc tangent of x. EXAMPLES:: sage: z = atan(3);z arctan(3) sage: n(z) 1.24904577239825 sage: atan(tan(pi/4)) 1/4*pi """ try: return x.atan() except AttributeError: return RDF(x).atan()
def exp(x): """ Returns the value of the exponentiation function at x. EXAMPLES:: sage: exp(3) e^3 sage: exp(0) 1 sage: exp(2.5) 12.1824939607035 sage: exp(pi*i) -1 """ try: return x.exp() except AttributeError: return RDF(x).exp()
def sqrt(x): """ Return a square root of x. EXAMPLES:: sage: sqrt(10.1) 3.17804971641414 sage: sqrt(9) 3 """ try: return x.sqrt() except (AttributeError, ValueError): try: return RDF(x).sqrt() except TypeError: return CDF(x).sqrt()
def plot_zero_flag(self, **options): r""" Draw a line segment from the zero vertex toward the baricenter. A real parameter ``t`` can be provided. If t=1, then the segment will go all the way to the baricenter. The value of ``t`` is linear in the length of the segment. Defaults to t=0.5. Other options are processed as in sage.plot.line.line2d. """ if "t" in options: t = RDF(options.pop("t")) else: t = 0.5 return line2d([ self._v[0], self._v[0] + t * (sum(self._v) / len(self._v) - self._v[0]) ], **options)
def sqrt(x): """ Returns a square root of x. This function (``numerical_sqrt``) is deprecated. Use ``sqrt(x, prec=n)`` instead. EXAMPLES:: sage: numerical_sqrt(10.1) doctest:1: DeprecationWarning: numerical_sqrt is deprecated, use sqrt(x, prec=n) instead 3.17804971641414 sage: numerical_sqrt(9) 3 """ from sage.misc.misc import deprecation deprecation("numerical_sqrt is deprecated, use sqrt(x, prec=n) instead") try: return x.sqrt() except (AttributeError, ValueError): try: return RDF(x).sqrt() except TypeError: return CDF(x).sqrt()
def sqrt(x): """ Returns a square root of x. This function (``numerical_sqrt``) is deprecated. Use ``sqrt(x, prec=n)`` instead. EXAMPLES:: sage: numerical_sqrt(10.1) doctest:1: DeprecationWarning: numerical_sqrt is deprecated, use sqrt(x, prec=n) instead See http://trac.sagemath.org/5404 for details. 3.17804971641414 sage: numerical_sqrt(9) 3 """ from sage.misc.superseded import deprecation deprecation(5404, "numerical_sqrt is deprecated, use sqrt(x, prec=n) instead") try: return x.sqrt() except (AttributeError, ValueError): try: return RDF(x).sqrt() except TypeError: return CDF(x).sqrt()
def k2(s, l, m, gamma): return (gamma**2 * sqrt( RDF((1 + l - m) * (2 + l - m) * (1 + l + m) * (2 + l + m) * (1 + l - s) * (2 + l - s) * (1 + l + s) * (2 + l + s)) / RDF( (1 + 2 * l) * (5 + 2 * l)))) / RDF( (1 + l) * (2 + l) * (3 + 2 * l))
def h_toy_model_semi_analytic(u, theta, phi, a, r0, phi0, lam, Dphi, l_max=10): r""" Return the gravitational wave emitted by a matter blob orbiting a Kerr black hole (semi-analytic computation based on a toy model surface density). The surface density of the matter blob is that given by :func:`surface_density_toy_model`. The gravitational wave is computed according to the formula .. MATH:: h = \frac{2\mu}{r} \, \sum_{\ell=2}^{\infty} \sum_{m=-\ell}^\ell \frac{Z^\infty_{\ell m}(r_0)}{(m\omega_0)^2} \; \text{sinc}\left( \frac{m}{2} \Delta\varphi \right) \, \text{sinc}\left( \frac{3}{4} \varepsilon \, m \omega_0 (1-a\omega_0)u \right) e^{- i m (\omega_0 u + \phi_0)} \, _{-2}S_{\ell m}^{a m \omega_0}(\theta,\varphi) INPUT: - ``u`` -- retarded time coordinate of the observer (in units of `M`, the BH mass): `u = t - r_*`, where `t` is the Boyer-Lindquist time coordinate and `r_*` is the tortoise coordinate - ``theta`` -- Boyer-Lindquist colatitute `\theta` of the observer - ``phi`` -- Boyer-Lindquist azimuthal coordinate `\phi` of the observer - ``a`` -- BH angular momentum parameter (in units of `M`) - ``r0`` -- mean radius `r_0` of the matter blob (Boyer-Lindquist coordinate) - ``phi0`` -- mean azimuthal angle `\phi_0` of the matter blob (Boyer-Lindquist coordinate) - ``lam`` -- radial extent `\lambda` of the matter blob - ``Dphi``-- opening angle `\Delta\phi` of the matter blob - ``l_max`` -- (default: 10) upper bound in the summation over the harmonic degree `\ell` OUTPUT: - a pair ``(hp, hc)``, where ``hp`` (resp. ``hc``) is `(r / \mu) h_+` (resp. `(r / \mu) h_\times`), `\mu` being the blob's mass and `r` is the Boyer-Lindquist radial coordinate of the observer EXAMPLES: Schwarzschild black hole:: sage: from kerrgeodesic_gw import h_toy_model_semi_analytic sage: a = 0 sage: r0, phi0, lam, Dphi = 6.5, 0, 0.6, 0.1 sage: u = 60. sage: h_toy_model_semi_analytic(u, pi/4, 0., a, r0, phi0, lam, Dphi) # tol 1.0e-13 (0.2999183296797872, 0.36916647790743246) sage: hp, hc = _ Comparison with the exact value:: sage: from kerrgeodesic_gw import (h_blob, blob_mass, ....: surface_density_toy_model) sage: param_surf_dens = [r0, phi0, lam, Dphi] sage: integ_range = [6.2, 6.8, -0.05, 0.05] sage: mu = blob_mass(a, surface_density_toy_model, param_surf_dens, ....: integ_range)[0] sage: hp0 = h_blob(u, pi/4, 0., a, surface_density_toy_model, ....: param_surf_dens, integ_range)[0] / mu sage: hc0 = h_blob(u, pi/4, 0., a, surface_density_toy_model, ....: param_surf_dens, integ_range, mode='x')[0] / mu sage: hp0, hc0 # tol 1.0e-13 (0.2951163078053617, 0.3743683023327848) sage: (hp - hp0) / hp0 # tol 1.0e-13 0.01627162494047128 sage: (hc - hc0) / hc0 # tol 1.0e-13 -0.013894938201066784 """ import numpy from sage.rings.real_double import RDF from sage.rings.complex_double import CDF from sage.symbolic.all import i as I from .spin_weighted_spherical_harm import spin_weighted_spherical_harmonic from .spin_weighted_spheroidal_harm import spin_weighted_spheroidal_harmonic from .zinf import Zinf u = RDF(u) theta = RDF(theta) phi = RDF(phi) a = RDF(a) omega0 = RDF(1. / (r0**1.5 + a)) eps = lam/r0 resu = CDF(0) for l in range(2, l_max+1): for m in range(-l, l+1): if m == 0: # m=0 is skipped continue # m_omega0 = RDF(m*omega0) if a == 0: Slm = spin_weighted_spherical_harmonic(-2, l, m, theta, phi, numerical=RDF) else: a = RDF(a) Slm = spin_weighted_spheroidal_harmonic(-2, l, m, a*m_omega0, theta, phi) # Division by pi in the Sinc function due to the defintion used by numpy resu += Zinf(a, l, m, r0) / m_omega0**2 \ * numpy.sinc(m*Dphi/2./numpy.pi) \ * numpy.sinc(0.75*eps*m_omega0*(1-a*omega0)*u/numpy.pi) \ * CDF(exp(-I*(m_omega0*u + m*phi0))) * Slm resu *= 2 return (resu.real(), -resu.imag())
def _sage_(self): """ Convert self to a Sage object. EXAMPLES:: sage: a = axiom(1/2); a #optional - axiom 1 - 2 sage: a.sage() #optional - axiom 1/2 sage: _.parent() #optional - axiom Rational Field sage: gp(axiom(1/2)) #optional - axiom 1/2 DoubleFloat's in Axiom are converted to be in RDF in Sage. :: sage: axiom(2.0).as_type('DoubleFloat').sage() #optional - axiom 2.0 sage: _.parent() #optional - axiom Real Double Field sage: axiom(2.1234)._sage_() #optional - axiom 2.12340000000000 sage: _.parent() #optional - axiom Real Field with 53 bits of precision sage: a = RealField(100)(pi) sage: axiom(a)._sage_() #optional - axiom 3.1415926535897932384626433833 sage: _.parent() #optional - axiom Real Field with 100 bits of precision sage: axiom(a)._sage_() == a #optional - axiom True sage: axiom(2.0)._sage_() #optional - axiom 2.00000000000000 sage: _.parent() #optional - axiom Real Field with 53 bits of precision We can also convert Axiom's polynomials to Sage polynomials. sage: a = axiom(x^2 + 1) #optional - axiom sage: a.type() #optional - axiom Polynomial Integer sage: a.sage() #optional - axiom x^2 + 1 sage: _.parent() #optional - axiom Univariate Polynomial Ring in x over Integer Ring sage: axiom('x^2 + y^2 + 1/2').sage() #optional - axiom y^2 + x^2 + 1/2 sage: _.parent() #optional - axiom Multivariate Polynomial Ring in y, x over Rational Field """ P = self._check_valid() type = str(self.type()) if type in ["Type", "Domain"]: return self._sage_domain() if type == "Float": from sage.rings.all import RealField, ZZ prec = max(self.mantissa().length()._sage_(), 53) R = RealField(prec) x, e, b = self.unparsed_input_form().lstrip('float(').rstrip( ')').split(',') return R(ZZ(x) * ZZ(b)**ZZ(e)) elif type == "DoubleFloat": from sage.rings.real_double import RDF return RDF(repr(self)) elif type in ["PositiveInteger", "Integer"]: from sage.rings.integer_ring import ZZ return ZZ(repr(self)) elif type.startswith('Polynomial'): from sage.rings.all import PolynomialRing base_ring = P(type.lstrip('Polynomial '))._sage_domain() vars = str(self.variables())[1:-1] R = PolynomialRing(base_ring, vars) return R(self.unparsed_input_form()) elif type.startswith('Fraction'): return self.numer().sage() / self.denom().sage() #If all else fails, try using the unparsed input form try: import sage.misc.sage_eval vars = sage.symbolic.ring.var(str(self.variables())[1:-1]) if isinstance(vars, tuple): return sage.misc.sage_eval.sage_eval( self.unparsed_input_form(), locals={str(x): x for x in vars}) else: return sage.misc.sage_eval.sage_eval( self.unparsed_input_form(), locals={str(vars): vars}) except Exception: raise NotImplementedError
def math_to_screen_coordinates(self, v): return (RDF(self._s * v[0] + self._tx), RDF(-self._s * v[1] + self._ty))
def polyhedron_from_Hrep(A, b, base_ring=QQ): r"""Builds a polytope given the H-representation, in the form `Ax \leq b` INPUT: * ``A`` - matrix of size m x n, in RDF or QQ ring. Accepts generic Sage matrix, and also a Numpy arrays with a matrix shape. * ``b`` - vector of size m, in RDF or QQ ring. Accepts generic Sage matrix, and also a Numpy array. * ``base_ring`` - (default: ``QQ``). Specifies the ring (base_ring) for the Polyhedron constructor. Valid choices are: * ``QQ`` - rational. Uses ``'ppl'`` (Parma Polyhedra Library) backend * ``RDF`` - Real double field. Uses ``'cdd'`` backend. OUTPUT: * "P" - a Polyhedron object TO-DO: * accept numpy arrays. notice that we often handle numpy arrays (for instance if we load some data from matlab using the function ``scipy.io.loadmat(..)``, then the data will be loaded as a dictionary of numpy arrays) EXAMPLES:: sage: A = matrix(RDF, [[-1.0, 0.0, 0.0, 0.0, 0.0, 0.0], ....: [ 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], ....: [ 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], ....: [ 0.0, -1.0, 0.0, 0.0, 0.0, 0.0], ....: [ 0.0, 0.0, -1.0, 0.0, 0.0, 0.0], ....: [ 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], ....: [ 0.0, 0.0, 0.0, -1.0, 0.0, 0.0], ....: [ 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], ....: [ 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], ....: [ 0.0, 0.0, 0.0, 0.0, -1.0, 0.0], ....: [ 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], ....: [ 0.0, 0.0, 0.0, 0.0, 0.0, -1.0]]) sage: b = vector(RDF, [0.0, 10.0, 0.0, 0.0, 0.2, 0.2, 0.1, 0.1, 0.0, 0.0, 0.0, 0.0]) sage: from polyhedron_tools.misc import polyhedron_from_Hrep sage: P = polyhedron_from_Hrep(A, b, base_ring=QQ); P A 3-dimensional polyhedron in QQ^6 defined as the convex hull of 8 vertices NOTES: - This function is useful especially when the input matrices `A`, `b` are ill-defined (constraints that differ by tiny amounts making the input data to be degenerate or almost degenerate), causing problems to Polyhedron(...). - In this case it is recommended to use ``base_ring = QQ``. Each element of `A` and `b` will be converted to rational, and this will be sent to Polyhedron. Note that Polyhedron automatically removes redundant constraints. """ if (base_ring == RDF): if 'numpy.ndarray' in str(type(A)): # assuming that b is also a numpy array m = A.shape[0] n = A.shape[1] b_RDF = vector(RDF, m, [RDF(bi) for bi in b]) A_RDF = matrix(RDF, m, n) for i in range(m): A_RDF.set_row(i, [RDF(A[i][j]) for j in range(n)]) A = copy(A_RDF) b = copy(b_RDF) ambient_dim = A.ncols() # transform to real, if needed if A.base_ring() != RDF: A.change_ring(RDF) if b.base_ring() != RDF: b.change_ring(RDF) ieqs_list = [] for i in range(A.nrows()): ieqs_list.append( list(-A.row(i)) ) #change in sign, necessary since Polyhedron receives Ax+b>=0 ieqs_list[i].insert(0, b[i]) P = Polyhedron(ieqs=ieqs_list, base_ring=RDF, ambient_dim=A.ncols(), backend='cdd') elif (base_ring == QQ): if 'numpy.ndarray' in str(type(A)): # assuming that b is also a numpy array m = A.shape[0] n = A.shape[1] b_QQ = vector(QQ, m, [QQ(b[i]) for i in range(m)]) A_QQ = matrix(QQ, m, n) for i in range(m): A_QQ.set_row(i, [QQ(A[i][j]) for j in range(n)]) A = copy(A_QQ) b = copy(b_QQ) ambient_dim = A.ncols() # transform to rational, if needed if A.base_ring() != QQ: #for i in range(A.nrows()): # A.set_row(i,[QQ(A.row(i)[j]) for j in range(ambient_dim)]); A.change_ring(QQ) if b.base_ring() != QQ: #b = vector(QQ, [QQ(bi) for bi in b]); b.change_ring(QQ) ieqs_list = [] for i in range(A.nrows()): ieqs_list.append( list(-A.row(i)) ) #change in sign, necessary since Polyhedron receives Ax+b>=0 ieqs_list[i].insert(0, b[i]) P = Polyhedron(ieqs=ieqs_list, base_ring=QQ, ambient_dim=A.ncols(), backend='ppl') else: raise ValueError('Base ring not supported. Try with RDF or QQ.') return P
def Zinf(a, l, m, r, algorithm='spline'): r""" Amplitude factor of the mode `(\ell,m)`. The factor `Z^\infty_{\ell m}(r)` is obtained by spline interpolation of tabulated numerical solutions of the radial component of the Teukolsky equation. INPUT: - ``a`` -- BH angular momentum parameter (in units of `M`, the BH mass) - ``l`` -- integer >= 2; the harmonic degree `\ell` - ``m`` -- integer within the range ``[-l, l]``; the azimuthal number `m` - ``r`` -- areal radius of the orbit (in units of `M`) - ``algorithm`` -- (default: ``'spline'``) string describing the computational method; allowed values are - ``'spline'``: spline interpolation of tabulated data - ``'1.5PN'`` (only for ``a=0``): 1.5-post-Newtonian expansion following E. Poisson, Phys. Rev. D **47**, 1497 (1993) [:doi:`10.1103/PhysRevD.47.1497`], with a minus one factor accounting for a different convention for the metric signature. OUTPUT: - coefficient `Z^\infty_{\ell m}(r)` (in units of `M^{-2}`) EXAMPLES:: sage: from kerrgeodesic_gw import Zinf sage: Zinf(0.98, 2, 2, 1.7) # tol 1.0e-13 -0.04302234478778856 + 0.28535368610053824*I sage: Zinf(0., 2, 2, 10.) # tol 1.0e-13 0.0011206407919254163 - 0.0003057608384581628*I sage: Zinf(0., 2, 2, 10., algorithm='1.5PN') # tol 1.0e-13 0.0011971529546749354 - 0.0003551610880408921*I """ if m < 0: return (-1)**l * Zinf(a, l, -m, r, algorithm=algorithm).conjugate() if algorithm == '1.5PN': if a == 0: # the factor (-1) below accounts for a difference of signature with # Poisson (1993): return -Zinf_Schwarzchild_PN(l, m, r) raise ValueError("a must be zero for algorithm='1.5PN'") a = RDF(a) param = (a, l, m) if param in _cached_splines: splines = _cached_splines[param] else: file_name = "data/Zinf_a{:.1f}.dat".format(float(a)) if a <= 0.9 \ else "data/Zinf_a{:.2f}.dat".format(float(a)) file_name = os.path.join(os.path.dirname(__file__), file_name) r_high_l = 20. if a <= 0.9 else 10. with open(file_name, "r") as data_file: lm_values = [] # l values up to 10 (for r <= r_high_l) lm_values_low = [] # l values up to 5 only (for r > r_high_l) for ld in range(2, 6): for md in range(1, ld + 1): lm_values_low.append((ld, md)) for ld in range(2, 11): for md in range(1, ld + 1): lm_values.append((ld, md)) Zreal = {} Zimag = {} for (ld, md) in lm_values: Zreal[(ld, md)] = [] Zimag[(ld, md)] = [] for line in data_file: items = line.split('\t') rd = RDF(items.pop(0)) if rd <= r_high_l: for (ld, md) in lm_values: Zreal[(ld, md)].append((rd, RDF(items.pop(0)))) Zimag[(ld, md)].append((rd, RDF(items.pop(0)))) else: for (ld, md) in lm_values_low: Zreal[(ld, md)].append((rd, RDF(items.pop(0)))) Zimag[(ld, md)].append((rd, RDF(items.pop(0)))) for (ld, md) in lm_values: _cached_splines[(a, ) + (ld, md)] = (spline(Zreal[(ld, md)]), spline(Zimag[(ld, md)])) if param not in _cached_splines: raise ValueError( "Zinf: case (a, l, m) = {} not implemented".format(param)) splines = _cached_splines[param] # The factor (-1)**(l+m) below accounts for a difference of convention # in the C++ code used to produce the data files return (-1)**(l + m) * CDF(splines[0](r), splines[1](r))
def __init__(self, points): r""" See ``VoronoiDiagram`` for full documentation. EXAMPLES:: sage: V = VoronoiDiagram([[1, 3, 3], [2, -2, 1], [-1 ,2, -1]]); V The Voronoi diagram of 3 points of dimension 3 in the Rational Field """ self._P = {} self._points = PointConfiguration(points) self._n = self._points.n_points() if not self._n or self._points.base_ring().is_subring(QQ): self._base_ring = QQ elif isinstance(self._points.base_ring(), sage.rings.abc.RealDoubleField) or self._points.base_ring() == AA: self._base_ring = self._points.base_ring() elif isinstance(self._points.base_ring(), sage.rings.abc.RealField): from sage.rings.real_double import RDF self._base_ring = RDF self._points = PointConfiguration([[RDF(cor) for cor in poi] for poi in self._points]) else: raise NotImplementedError('Base ring of the Voronoi diagram must ' 'be one of QQ, RDF, AA.') if self._n > 0: self._d = self._points.ambient_dim() e = [([sum(vector(i)[k] ** 2 for k in range(self._d))] + [(-2) * vector(i)[l] for l in range(self._d)] + [1]) for i in self._points] # we attach hyperplane to the paraboloid e = [[self._base_ring(i) for i in k] for k in e] p = Polyhedron(ieqs=e, base_ring=self._base_ring) # To understand the reordering that takes place when # defining a rational polyhedron, we generate two sorted # lists, that are used a few lines below if self.base_ring() == QQ: enormalized = [] for ineq in e: if ineq[0] == 0: enormalized.append(ineq) else: enormalized.append([i / ineq[0] for i in ineq[1:]]) # print enormalized hlist = [list(ineq) for ineq in p.Hrepresentation()] hlistnormalized = [] for ineq in hlist: if ineq[0] == 0: hlistnormalized.append(ineq) else: hlistnormalized.append([i / ineq[0] for i in ineq[1:]]) # print hlistnormalized for i in range(self._n): # for base ring RDF and AA, Polyhedron keeps the order of the # points in the input, for QQ we resort if self.base_ring() == QQ: equ = p.Hrepresentation(hlistnormalized.index(enormalized[i])) else: equ = p.Hrepresentation(i) pvert = [[u[k] for k in range(self._d)] for u in equ.incident() if u.is_vertex()] prays = [[u[k] for k in range(self._d)] for u in equ.incident() if u.is_ray()] pline = [[u[k] for k in range(self._d)] for u in equ.incident() if u.is_line()] (self._P)[self._points[i]] = Polyhedron(vertices=pvert, lines=pline, rays=prays, base_ring=self._base_ring)
def signal_to_noise_particle(a, r0, theta, psd, t_obs, BH_time_scale, m_min=1, m_max=None, scale=1, approximation=None): r""" Evaluate the signal-to-noise ratio of gravitational radiation emitted by a single orbiting particle observed in a detector of a given power spectral density. INPUT: - ``a`` -- BH angular momentum parameter (in units of `M`, the BH mass) - ``r0`` -- Boyer-Lindquist radius of the orbit (in units of `M`) - ``theta`` -- Boyer-Lindquist colatitute `\theta` of the observer - ``psd`` -- function with a single argument (`f`) representing the detector's one-sided noise power spectral density `S_n(f)` (see e.g. :func:`.lisa_detector.power_spectral_density`) - ``t_obs`` -- observation period, in the same time unit as `S_n(f)` - ``BH_time_scale`` -- value of `M` in the same time unit as `S_n(f)`; if `S_n(f)` is provided in `\mathrm{Hz}^{-1}`, then ``BH_time_scale`` must be `M` expressed in seconds. - ``m_min`` -- (default: 1) lower bound in the summation over the Fourier mode `m` - ``m_max`` -- (default: ``None``) upper bound in the summation over the Fourier mode `m`; if ``None``, ``m_max`` is set to 10 for `r_0 \leq 20 M` and to 5 for `r_0 > 20 M` - ``scale`` -- (default: ``1``) scale factor by which `h(t)` must be multiplied to get the actual signal; this should by `\mu/r`, where `\mu` is the particle mass and `r` the radial coordinate of the detector - ``approximation`` -- (default: ``None``) string describing the computational method for the signal; allowed values are - ``None``: exact computation - ``'quadrupole'``: quadrupole approximation; see :func:`.gw_particle.h_particle_quadrupole` - ``'1.5PN'`` (only for ``a=0``): 1.5-post-Newtonian expansion following E. Poisson, Phys. Rev. D **47**, 1497 (1993) [:doi:`10.1103/PhysRevD.47.1497`] OUTPUT: - the signal-to-noise ratio `\rho` EXAMPLES: Let us evaluate the SNR of the gravitational signal generated by a 1-solar mass object orbiting at the ISCO of Sgr A* observed by LISA during 1 day:: sage: from kerrgeodesic_gw import (signal_to_noise_particle, ....: lisa_detector, astro_data) sage: a, r0 = 0., 6. sage: theta = pi/2 sage: t_obs = 24*3600 # 1 day in seconds sage: BH_time_scale = astro_data.SgrA_mass_s # Sgr A* mass in seconds sage: psd = lisa_detector.power_spectral_density_RCLfit sage: mu_ov_r = astro_data.Msol_m / astro_data.dSgrA # mu/r sage: signal_to_noise_particle(a, r0, theta, psd, t_obs, # tol 1.0e-13 ....: BH_time_scale, scale=mu_ov_r) 7565.6612762972445 Using the quadrupole approximation:: sage: signal_to_noise_particle(a, r0, theta, psd, t_obs, # tol 1.0e-13 ....: BH_time_scale, scale=mu_ov_r, ....: approximation='quadrupole') 5230.403692883996 Using the 1.5-PN approximation (``m_max`` has to be at most 5):: sage: signal_to_noise_particle(a, r0, theta, psd, t_obs, # tol 1.0e-13 ....: BH_time_scale, scale=mu_ov_r, ....: approximation='1.5PN', m_max=5) 7601.344521598601 For large values of `r_0`, the 1.5-PN approximation and the quadrupole one converge:: sage: r0 = 100 sage: signal_to_noise_particle(a, r0, theta, psd, t_obs, # tol 1.0e-13 ....: BH_time_scale, scale=mu_ov_r, ....: approximation='quadrupole') 0.0030532227165507805 sage: signal_to_noise_particle(a, r0, theta, psd, t_obs, # tol 1.0e-13 ....: BH_time_scale, scale=mu_ov_r, ....: approximation='1.5PN') 0.0031442135473417616 :: sage: r0 = 1000 sage: signal_to_noise_particle(a, r0, theta, psd, t_obs, # tol 1.0e-13 ....: BH_time_scale, scale=mu_ov_r, ....: approximation='quadrupole') 9.663790254603111e-09 sage: signal_to_noise_particle(a, r0, theta, psd, t_obs, # tol 1.0e-13 ....: BH_time_scale, scale=mu_ov_r, ....: approximation='1.5PN') 9.687469292984858e-09 """ from .gw_particle import h_amplitude_particle_fourier from .zinf import _lmax if approximation == 'quadrupole': fm2 = RDF(1. / (pi * r0**1.5) / BH_time_scale) return RDF(2. * scale / r0 * sqrt(t_obs / psd(fm2) * (1 + 6 * cos(theta)**2 + cos(theta)**4))) if m_max is None: m_max = _lmax(a, r0) # Orbital frequency in the same time units as S_n(f) (generally seconds): f0 = RDF(1. / (2 * pi * (r0**1.5 + a)) / BH_time_scale) rho2 = 0 for m in range(m_min, m_max + 1): hmp, hmc = h_amplitude_particle_fourier(m, a, r0, theta, l_max=m_max, algorithm_Zinf=approximation) rho2 += (hmp**2 + hmc**2) / psd(m * f0) return sqrt(rho2 * t_obs) * scale
def round(x, ndigits=0): """ round(number[, ndigits]) - double-precision real number Round a number to a given precision in decimal digits (default 0 digits). If no precision is specified this just calls the element's .round() method. EXAMPLES:: sage: round(sqrt(2),2) 1.41 sage: q = round(sqrt(2),5); q 1.41421 sage: type(q) <type 'sage.rings.real_double.RealDoubleElement'> sage: q = round(sqrt(2)); q 1 sage: type(q) <type 'sage.rings.integer.Integer'> sage: round(pi) 3 sage: b = 5.4999999999999999 sage: round(b) 5 This example addresses :trac:`23502`:: sage: n = round(6); type(n) <type 'sage.rings.integer.Integer'> Since we use floating-point with a limited range, some roundings can't be performed:: sage: round(sqrt(Integer('1'*1000)),2) +infinity IMPLEMENTATION: If ndigits is specified, it calls Python's builtin round function, and converts the result to a real double field element. Otherwise, it tries the argument's .round() method; if that fails, it reverts to the builtin round function, converted to a real double field element. .. NOTE:: This is currently slower than the builtin round function, since it does more work - i.e., allocating an RDF element and initializing it. To access the builtin version do ``from six.moves import builtins; builtins.round``. """ try: if ndigits: x = float(x) return RealDoubleElement(builtins.round(x, ndigits)) else: try: return x.round() except AttributeError: return RealDoubleElement(builtins.round(x, 0)) except ArithmeticError: if not isinstance(x, RealDoubleElement): return round(RDF(x), ndigits) else: raise
def density_plot(f, xrange, yrange, **options): r""" ``density_plot`` takes a function of two variables, `f(x,y)` and plots the height of the function over the specified ``xrange`` and ``yrange`` as demonstrated below. ``density_plot(f, (xmin,xmax), (ymin,ymax), ...)`` INPUT: - ``f`` -- a function of two variables - ``(xmin,xmax)`` -- 2-tuple, the range of ``x`` values OR 3-tuple ``(x,xmin,xmax)`` - ``(ymin,ymax)`` -- 2-tuple, the range of ``y`` values OR 3-tuple ``(y,ymin,ymax)`` The following inputs must all be passed in as named parameters: - ``plot_points`` -- integer (default: 25); number of points to plot in each direction of the grid - ``cmap`` -- a colormap (default: ``'gray'``), the name of a predefined colormap, a list of colors or an instance of a matplotlib Colormap. Type: ``import matplotlib.cm; matplotlib.cm.datad.keys()`` for available colormap names. - ``interpolation`` -- string (default: ``'catrom'``), the interpolation method to use: ``'bilinear'``, ``'bicubic'``, ``'spline16'``, ``'spline36'``, ``'quadric'``, ``'gaussian'``, ``'sinc'``, ``'bessel'``, ``'mitchell'``, ``'lanczos'``, ``'catrom'``, ``'hermite'``, ``'hanning'``, ``'hamming'``, ``'kaiser'`` EXAMPLES: Here we plot a simple function of two variables. Note that since the input function is an expression, we need to explicitly declare the variables in 3-tuples for the range:: sage: x,y = var('x,y') sage: density_plot(sin(x) * sin(y), (x,-2,2), (y,-2,2)) Graphics object consisting of 1 graphics primitive .. PLOT:: x,y = var('x,y') g = density_plot(sin(x) * sin(y), (x,-2,2), (y,-2,2)) sphinx_plot(g) Here we change the ranges and add some options; note that here ``f`` is callable (has variables declared), so we can use 2-tuple ranges:: sage: x,y = var('x,y') sage: f(x,y) = x^2 * cos(x*y) sage: density_plot(f, (x,-10,5), (y,-5,5), interpolation='sinc', plot_points=100) Graphics object consisting of 1 graphics primitive .. PLOT:: x,y = var('x,y') def f(x,y): return x**2 * cos(x*y) g = density_plot(f, (x,-10,5), (y,-5,5), interpolation='sinc', plot_points=100) sphinx_plot(g) An even more complicated plot:: sage: x,y = var('x,y') sage: density_plot(sin(x^2+y^2) * cos(x) * sin(y), (x,-4,4), (y,-4,4), cmap='jet', plot_points=100) Graphics object consisting of 1 graphics primitive .. PLOT:: x,y = var('x,y') g = density_plot(sin(x**2 + y**2)*cos(x)*sin(y), (x,-4,4), (y,-4,4), cmap='jet', plot_points=100) sphinx_plot(g) This should show a "spotlight" right on the origin:: sage: x,y = var('x,y') sage: density_plot(1/(x^10 + y^10), (x,-10,10), (y,-10,10)) Graphics object consisting of 1 graphics primitive .. PLOT:: x,y = var('x,y') g = density_plot(1/(x**10 + y**10), (x,-10,10), (y,-10,10)) sphinx_plot(g) Some elliptic curves, but with symbolic endpoints. In the first example, the plot is rotated 90 degrees because we switch the variables `x`, `y`:: sage: density_plot(y^2 + 1 - x^3 - x, (y,-pi,pi), (x,-pi,pi)) Graphics object consisting of 1 graphics primitive .. PLOT:: x,y = var('x,y') g = density_plot(y**2 + 1 - x**3 - x, (y,-pi,pi), (x,-pi,pi)) sphinx_plot(g) :: sage: density_plot(y^2 + 1 - x^3 - x, (x,-pi,pi), (y,-pi,pi)) Graphics object consisting of 1 graphics primitive .. PLOT:: x,y = var('x,y') g = density_plot(y**2 + 1 - x**3 - x, (x,-pi,pi), (y,-pi,pi)) sphinx_plot(g) Extra options will get passed on to show(), as long as they are valid:: sage: density_plot(log(x) + log(y), (x,1,10), (y,1,10), dpi=20) Graphics object consisting of 1 graphics primitive .. PLOT:: x,y = var('x,y') g = density_plot(log(x) + log(y), (x,1,10), (y,1,10), dpi=20) sphinx_plot(g) :: sage: density_plot(log(x) + log(y), (x,1,10), (y,1,10)).show(dpi=20) # These are equivalent TESTS: Check that :trac:`15315` is fixed, i.e., density_plot respects the ``aspect_ratio`` parameter. Without the fix, it looks like a thin line of width a few mm. With the fix it should look like a nice fat layered image:: sage: density_plot((x*y)^(1/2), (x,0,3), (y,0,500), aspect_ratio=.01) Graphics object consisting of 1 graphics primitive Default ``aspect_ratio`` is ``"automatic"``, and that should work too:: sage: density_plot((x*y)^(1/2), (x,0,3), (y,0,500)) Graphics object consisting of 1 graphics primitive Check that :trac:`17684` is fixed, i.e., symbolic values can be plotted:: sage: def f(x,y): ....: return SR(x) sage: density_plot(f, (0,1), (0,1)) Graphics object consisting of 1 graphics primitive """ from sage.plot.all import Graphics from sage.plot.misc import setup_for_eval_on_grid from sage.rings.real_double import RDF g, ranges = setup_for_eval_on_grid([f], [xrange, yrange], options['plot_points']) g = g[0] xrange, yrange = [r[:2] for r in ranges] xy_data_array = [[ RDF(g(x, y)) for x in xsrange(*ranges[0], include_endpoint=True) ] for y in xsrange(*ranges[1], include_endpoint=True)] g = Graphics() g._set_extra_kwds( Graphics._extract_kwds_for_show(options, ignore=['xmin', 'xmax'])) g.add_primitive(DensityPlot(xy_data_array, xrange, yrange, options)) return g
def Polyhedra(ambient_space_or_base_ring=None, ambient_dim=None, backend=None, *, ambient_space=None, base_ring=None): r""" Construct a suitable parent class for polyhedra INPUT: - ``base_ring`` -- A ring. Currently there are backends for `\ZZ`, `\QQ`, and `\RDF`. - ``ambient_dim`` -- integer. The ambient space dimension. - ``ambient_space`` -- A free module. - ``backend`` -- string. The name of the backend for computations. There are several backends implemented: * ``backend="ppl"`` uses the Parma Polyhedra Library * ``backend="cdd"`` uses CDD * ``backend="normaliz"`` uses normaliz * ``backend="polymake"`` uses polymake * ``backend="field"`` a generic Sage implementation OUTPUT: A parent class for polyhedra over the given base ring if the backend supports it. If not, the parent base ring can be larger (for example, `\QQ` instead of `\ZZ`). If there is no implementation at all, a ``ValueError`` is raised. EXAMPLES:: sage: from sage.geometry.polyhedron.parent import Polyhedra sage: Polyhedra(AA, 3) Polyhedra in AA^3 sage: Polyhedra(ZZ, 3) Polyhedra in ZZ^3 sage: type(_) <class 'sage.geometry.polyhedron.parent.Polyhedra_ZZ_ppl_with_category'> sage: Polyhedra(QQ, 3, backend='cdd') Polyhedra in QQ^3 sage: type(_) <class 'sage.geometry.polyhedron.parent.Polyhedra_QQ_cdd_with_category'> CDD does not support integer polytopes directly:: sage: Polyhedra(ZZ, 3, backend='cdd') Polyhedra in QQ^3 Using a more general form of the constructor:: sage: V = VectorSpace(QQ, 3) sage: Polyhedra(V) is Polyhedra(QQ, 3) True sage: Polyhedra(V, backend='field') is Polyhedra(QQ, 3, 'field') True sage: Polyhedra(backend='field', ambient_space=V) is Polyhedra(QQ, 3, 'field') True sage: M = FreeModule(ZZ, 2) sage: Polyhedra(M, backend='ppl') is Polyhedra(ZZ, 2, 'ppl') True TESTS:: sage: Polyhedra(RR, 3, backend='field') Traceback (most recent call last): ... ValueError: the 'field' backend for polyhedron cannot be used with non-exact fields sage: Polyhedra(RR, 3) Traceback (most recent call last): ... ValueError: no default backend for computations with Real Field with 53 bits of precision sage: Polyhedra(QQ[I], 2) Traceback (most recent call last): ... ValueError: invalid base ring: Number Field in I with defining polynomial x^2 + 1 with I = 1*I cannot be coerced to a real field sage: Polyhedra(AA, 3, backend='polymake') # optional - polymake Traceback (most recent call last): ... ValueError: the 'polymake' backend for polyhedron cannot be used with Algebraic Real Field sage: Polyhedra(QQ, 2, backend='normaliz') # optional - pynormaliz Polyhedra in QQ^2 sage: Polyhedra(SR, 2, backend='normaliz') # optional - pynormaliz # optional - sage.symbolic Polyhedra in (Symbolic Ring)^2 sage: SCR = SR.subring(no_variables=True) # optional - sage.symbolic sage: Polyhedra(SCR, 2, backend='normaliz') # optional - pynormaliz # optional - sage.symbolic Polyhedra in (Symbolic Constants Subring)^2 """ if ambient_space_or_base_ring is not None: if ambient_space_or_base_ring in Rings(): base_ring = ambient_space_or_base_ring else: ambient_space = ambient_space_or_base_ring if ambient_space is not None: if ambient_space not in Modules: # There is no category of free modules, unfortunately # (see https://trac.sagemath.org/ticket/30164)... raise ValueError('ambient_space must be a free module') if base_ring is None: base_ring = ambient_space.base_ring() if ambient_dim is None: try: ambient_dim = ambient_space.rank() except AttributeError: # ... so we test whether it is free using the existence of # a rank method raise ValueError('ambient_space must be a free module') if ambient_space is not FreeModule(base_ring, ambient_dim): raise NotImplementedError( 'ambient_space must be a standard free module') if backend is None: if base_ring is ZZ or base_ring is QQ: backend = 'ppl' elif base_ring is RDF: backend = 'cdd' elif base_ring.is_exact(): # TODO: find a more robust way of checking that the coefficients are indeed # real numbers if not RDF.has_coerce_map_from(base_ring): raise ValueError( "invalid base ring: {} cannot be coerced to a real field". format(base_ring)) backend = 'field' else: raise ValueError( "no default backend for computations with {}".format( base_ring)) try: from sage.symbolic.ring import SR except ImportError: SR = None if backend == 'ppl' and base_ring is QQ: return Polyhedra_QQ_ppl(base_ring, ambient_dim, backend) elif backend == 'ppl' and base_ring is ZZ: return Polyhedra_ZZ_ppl(base_ring, ambient_dim, backend) elif backend == 'normaliz' and base_ring is QQ: return Polyhedra_QQ_normaliz(base_ring, ambient_dim, backend) elif backend == 'normaliz' and base_ring is ZZ: return Polyhedra_ZZ_normaliz(base_ring, ambient_dim, backend) elif backend == 'normaliz' and (isinstance( base_ring, sage.rings.abc.SymbolicRing) or base_ring.is_exact()): return Polyhedra_normaliz(base_ring, ambient_dim, backend) elif backend == 'cdd' and base_ring in (ZZ, QQ): return Polyhedra_QQ_cdd(QQ, ambient_dim, backend) elif backend == 'cdd' and base_ring is RDF: return Polyhedra_RDF_cdd(RDF, ambient_dim, backend) elif backend == 'polymake': base_field = base_ring.fraction_field() try: from sage.interfaces.polymake import polymake polymake_base_field = polymake(base_field) except TypeError: raise ValueError( f"the 'polymake' backend for polyhedron cannot be used with {base_field}" ) return Polyhedra_polymake(base_field, ambient_dim, backend) elif backend == 'field': if not base_ring.is_exact(): raise ValueError( "the 'field' backend for polyhedron cannot be used with non-exact fields" ) return Polyhedra_field(base_ring.fraction_field(), ambient_dim, backend) else: raise ValueError('No such backend (=' + str(backend) + ') implemented for given basering (=' + str(base_ring) + ').')
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 point3d(v, size=5, **kwds): """ Plot a point or list of points in 3d space. INPUT: - ``v`` -- a point or list of points - ``size`` -- (default: 5) size of the point (or points) - ``color`` -- a string (``"red"``, ``"green"`` etc) or a tuple (r, g, b) with r, g, b numbers between 0 and 1 - ``opacity`` -- (default: 1) if less than 1 then is transparent EXAMPLES:: sage: sum([point3d((i,i^2,i^3), size=5) for i in range(10)]) Graphics3d Object We check to make sure this works with vectors and other iterables:: sage: pl = point3d([vector(ZZ,(1, 0, 0)), vector(ZZ,(0, 1, 0)), (-1, -1, 0)]) sage: print(point(vector((2,3,4)))) Graphics3d Object sage: c = polytopes.hypercube(3) sage: v = c.vertices()[0]; v A vertex at (-1, -1, -1) sage: print(point(v)) Graphics3d Object We check to make sure the options work:: sage: point3d((4,3,2),size=20,color='red',opacity=.5) Graphics3d Object numpy arrays can be provided as input:: sage: import numpy sage: point3d(numpy.array([1,2,3])) Graphics3d Object sage: point3d(numpy.array([[1,2,3], [4,5,6], [7,8,9]])) Graphics3d Object We check that iterators of points are accepted (:trac:`13890`):: sage: point3d(iter([(1,1,2),(2,3,4),(3,5,8)]),size=20,color='red') Graphics3d Object TESTS:: sage: point3d([]) Graphics3d Object """ try: l = len(v) except TypeError: # argument is an iterator v = list(v) l = len(v) if l == 0: from sage.plot.plot3d.base import Graphics3d return Graphics3d() if l == 3: try: # check if the first element can be changed to a float tmp = RDF(v[0]) return Point(v, size, **kwds) except TypeError: pass A = sum([Point(z, size, **kwds) for z in v]) A._set_extra_kwds(kwds) return A
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
def Sequence(x, universe=None, check=True, immutable=False, cr=False, cr_str=None, use_sage_types=False): """ A mutable list of elements with a common guaranteed universe, which can be set immutable. A universe is either an object that supports coercion (e.g., a parent), or a category. INPUT: - ``x`` - a list or tuple instance - ``universe`` - (default: None) the universe of elements; if None determined using canonical coercions and the entire list of elements. If list is empty, is category Objects() of all objects. - ``check`` -- (default: True) whether to coerce the elements of x into the universe - ``immutable`` - (default: True) whether or not this sequence is immutable - ``cr`` - (default: False) if True, then print a carriage return after each comma when printing this sequence. - ``cr_str`` - (default: False) if True, then print a carriage return after each comma when calling ``str()`` on this sequence. - ``use_sage_types`` -- (default: False) if True, coerce the built-in Python numerical types int, long, float, complex to the corresponding Sage types (this makes functions like vector() more flexible) OUTPUT: - a sequence EXAMPLES:: sage: v = Sequence(range(10)) sage: v.universe() <type 'int'> sage: v [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] We can request that the built-in Python numerical types be coerced to Sage objects:: sage: v = Sequence(range(10), use_sage_types=True) sage: v.universe() Integer Ring sage: v [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] You can also use seq for "Sequence", which is identical to using Sequence:: sage: v = seq([1,2,1/1]); v [1, 2, 1] sage: v.universe() Rational Field sage: v.parent() Category of sequences in Rational Field sage: v.parent()([3,4/3]) [3, 4/3] Note that assignment coerces if possible,:: sage: v = Sequence(range(10), ZZ) sage: a = QQ(5) sage: v[3] = a sage: parent(v[3]) Integer Ring sage: parent(a) Rational Field sage: v[3] = 2/3 Traceback (most recent call last): ... TypeError: no conversion of this rational to integer Sequences can be used absolutely anywhere lists or tuples can be used:: sage: isinstance(v, list) True Sequence can be immutable, so entries can't be changed:: sage: v = Sequence([1,2,3], immutable=True) sage: v.is_immutable() True sage: v[0] = 5 Traceback (most recent call last): ... ValueError: object is immutable; please change a copy instead. Only immutable sequences are hashable (unlike Python lists), though the hashing is potentially slow, since it first involves conversion of the sequence to a tuple, and returning the hash of that.:: sage: v = Sequence(range(10), ZZ, immutable=True) sage: hash(v) 1591723448 # 32-bit -4181190870548101704 # 64-bit If you really know what you are doing, you can circumvent the type checking (for an efficiency gain):: sage: list.__setitem__(v, int(1), 2/3) # bad circumvention sage: v [0, 2/3, 2, 3, 4, 5, 6, 7, 8, 9] sage: list.__setitem__(v, int(1), int(2)) # not so bad circumvention You can make a sequence with a new universe from an old sequence.:: sage: w = Sequence(v, QQ) sage: w [0, 2, 2, 3, 4, 5, 6, 7, 8, 9] sage: w.universe() Rational Field sage: w[1] = 2/3 sage: w [0, 2/3, 2, 3, 4, 5, 6, 7, 8, 9] Sequences themselves live in a category, the category of all sequences in the given universe.:: sage: w.category() Category of sequences in Rational Field This is also the parent of any sequence:: sage: w.parent() Category of sequences in Rational Field The default universe for any sequence, if no compatible parent structure can be found, is the universe of all Sage objects. This example illustrates how every element of a list is taken into account when constructing a sequence.:: sage: v = Sequence([1,7,6,GF(5)(3)]); v [1, 2, 1, 3] sage: v.universe() Finite Field of size 5 sage: v.parent() Category of sequences in Finite Field of size 5 sage: v.parent()([7,8,9]) [2, 3, 4] """ from sage.rings.polynomial.multi_polynomial_ideal import MPolynomialIdeal if isinstance(x, Sequence_generic) and universe is None: universe = x.universe() x = list(x) if isinstance(x, MPolynomialIdeal) and universe is None: universe = x.ring() x = x.gens() if universe is None: if not isinstance(x, (list, tuple)): x = list(x) #raise TypeError("x must be a list or tuple") if len(x) == 0: import sage.categories.all universe = sage.categories.all.Objects() else: import sage.structure.element as coerce y = x x = list(x) # make a copy, or we'd change the type of the elements of x, which would be bad. if use_sage_types: # convert any Python built-in numerical types to Sage objects from sage.rings.integer_ring import ZZ from sage.rings.real_double import RDF from sage.rings.complex_double import CDF for i in range(len(x)): if isinstance(x[i], int) or isinstance(x[i], long): x[i] = ZZ(x[i]) elif isinstance(x[i], float): x[i] = RDF(x[i]) elif isinstance(x[i], complex): x[i] = CDF(x[i]) # start the pairwise coercion for i in range(len(x)-1): try: x[i], x[i+1] = coerce.canonical_coercion(x[i],x[i+1]) except TypeError: import sage.categories.all universe = sage.categories.all.Objects() x = list(y) check = False # no point break if universe is None: # no type errors raised. universe = coerce.parent(x[len(x)-1]) from sage.rings.polynomial.multi_polynomial_ring import is_MPolynomialRing from sage.rings.quotient_ring import is_QuotientRing from sage.rings.polynomial.pbori import BooleanMonomialMonoid if is_MPolynomialRing(universe) or \ (is_QuotientRing(universe) and is_MPolynomialRing(universe.cover_ring())) or \ isinstance(universe, BooleanMonomialMonoid): from sage.rings.polynomial.multi_polynomial_sequence import PolynomialSequence try: return PolynomialSequence(x, universe, immutable=immutable, cr=cr, cr_str=cr_str) except (TypeError,AttributeError): return Sequence_generic(x, universe, check, immutable, cr, cr_str, use_sage_types) else: return Sequence_generic(x, universe, check, immutable, cr, cr_str, use_sage_types)
def Zinf_Schwarzchild_PN(l, m, r): r""" Amplitude factor of the mode `(\ell,m)` for a Schwarzschild BH at the 1.5PN level. The 1.5PN formulas are taken from E. Poisson, Phys. Rev. D **47**, 1497 (1993), :doi:`10.1103/PhysRevD.47.1497`. INPUT: - ``l`` -- integer >= 2; the harmonic degree `\ell` - ``m`` -- integer within the range ``[-l, l]``; the azimuthal number `m` - ``r`` -- areal radius of the orbit (in units of `M`, the BH mass) OUTPUT: - coefficient `Z^\infty_{\ell m}(r)` (in units of `M^{-2}`) EXAMPLES:: sage: from kerrgeodesic_gw import Zinf_Schwarzchild_PN sage: Zinf_Schwarzchild_PN(2, 2, 6.) # tol 1.0e-13 -0.00981450418730346 + 0.003855681972781947*I sage: Zinf_Schwarzchild_PN(5, 3, 6.) # tol 1.0e-13 -6.958527913913504e-05*I """ if m < 0: return (-1)**l * Zinf_Schwarzchild_PN(l, -m, r).conjugate() v = 1. / sqrt(r) if l == 2: b = RDF(sqrt(pi / 5.) / r**4) if m == 1: return CDF(4. / 3. * I * b * v * (1 - 17. / 28. * v**2)) if m == 2: return CDF(-16 * b * (1 - 107. / 42. * v**2 + (2 * pi + 4 * I * (3 * ln(2 * v) - 0.839451001765134)) * v**3)) if l == 3: b = RDF(sqrt(pi / 7.) / r**(4.5)) if m == 1: return CDF(I / 3. * b / sqrt(10.) * (1 - 8. / 3. * v**2)) if m == 2: return CDF(-16. / 3. * b * v) if m == 3: return CDF(-81 * I * b / sqrt(8.) * (1 - 4 * v**2)) if l == 4: b = RDF(sqrt(pi) / r**5) if m == 1: return CDF(I / 105. * b / sqrt(2) * v) if m == 2: return CDF(-16. / 63. * b) if m == 3: return CDF(-81. / 5. * I * b / sqrt(14.) * v) if m == 4: return CDF(512. / 9. * b / sqrt(7.)) if l == 5: b = RDF(sqrt(pi) / r**(5.5)) if m == 1: return CDF(I / 360. * b / sqrt(77.)) if m == 2: return CDF(0) if m == 3: return CDF(-81. / 40. * I * b * sqrt(3. / 22.)) if m == 4: return CDF(0) if m == 5: return CDF(3125. / 24. * I * b * sqrt(5. / 66.)) raise NotImplementedError("{} not implemented".format((l, m)))
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: list(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: list(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 edges_intersection(P, i, cmatrix=None): r"""Return the point in the plane where the edges adjacent to the input edge intersect. INPUT: ``P`` - a polygon (Polyhedron in 2d). ``i`` - integer, index of edge in ``P.inequalities_list()``. ``cmatrix`` - (optional) if None, the constraints matrix corresponding to P is computed inside the function. OUTPUT: ``p`` - coordinates of the intersection of the edges that are adjacent to i. ``neighbor_constraints`` - indices of the edges that are adjacent to it. The edges are indexed according to P.inequalities_list(). NOTES: - This has been tested for P in QQ and RDF. """ from sage.symbolic.ring import SR from sage.symbolic.relation import solve if cmatrix is None: cmatrix = vertex_connections(P) got_QQ = True if P.base_ring() == QQ else False #constraint_i = P.inequalities_list()[i] neighbor_constraints = [] # vertices associated to the given edge i vert_i = cmatrix[i] for j, cj in enumerate(cmatrix): if (vert_i[0] in cj or vert_i[1] in cj) and (j != i): neighbor_constraints.append(j) # first one constr_1 = P.inequalities_list()[neighbor_constraints[0]] # second one constr_2 = P.inequalities_list()[neighbor_constraints[1]] # write and solve the intersection of the two lines x1 = SR.var('x1') x2 = SR.var('x2') eq1 = constr_1[1] * x1 + constr_1[2] * x2 == -constr_1[0] eq2 = constr_2[1] * x1 + constr_2[2] * x2 == -constr_2[0] p = solve([eq1, eq2], x1, x2) p = [p[0][0].right_hand_side(), p[0][1].right_hand_side()] if not got_QQ: # assuming RDF # transform to RDF (because solve produces in general rational answers) p = [RDF(pi) for pi in p] return p, neighbor_constraints