def Torus(r=2, R=3, name="Torus"): r""" Returns a torus obtained by revolving a circle of radius ``r`` around a coplanar axis ``R`` units away from the center of the circle. The parametrization used is .. MATH:: \begin{aligned} x(u, v) & = (R + r \cos(v)) \cos(u); \\ y(u, v) & = (R + r \cos(v)) \sin(u); \\ z(u, v) & = r \sin(v). \end{aligned} INPUT: - ``r``, ``R`` -- Minor and major radius of the torus. - ``name`` -- string. Name of the surface. EXAMPLES:: sage: torus = surfaces.Torus(); torus Parametrized surface ('Torus') with equation ((2*cos(v) + 3)*cos(u), (2*cos(v) + 3)*sin(u), 2*sin(v)) sage: torus.plot() Graphics3d Object """ u, v = var("u, v") torus_eq = [(R + r * cos(v)) * cos(u), (R + r * cos(v)) * sin(u), r * sin(v)] coords = ((u, 0, 2 * pi), (v, 0, 2 * pi)) return ParametrizedSurface3D(torus_eq, coords, name)
def Klein(r=1, name="Klein bottle"): r""" Returns the Klein bottle, in the figure-8 parametrization given by .. MATH:: \begin{aligned} x(u, v) & = (r + \cos(u/2)\cos(v) - \sin(u/2)\sin(2v)) \cos(u); \\ y(u, v) & = (r + \cos(u/2)\cos(v) - \sin(u/2)\sin(2v)) \sin(u); \\ z(u, v) & = \sin(u/2)\cos(v) + \cos(u/2)\sin(2v). \end{aligned} INPUT: - ``r`` -- radius of the "figure-8" circle. - ``name`` -- string. Name of the surface. EXAMPLES:: sage: klein = surfaces.Klein(); klein Parametrized surface ('Klein bottle') with equation (-(sin(1/2*u)*sin(2*v) - cos(1/2*u)*sin(v) - 1)*cos(u), -(sin(1/2*u)*sin(2*v) - cos(1/2*u)*sin(v) - 1)*sin(u), cos(1/2*u)*sin(2*v) + sin(1/2*u)*sin(v)) sage: klein.plot() Graphics3d Object """ u, v = var("u, v") x = (r + cos(u / 2) * sin(v) - sin(u / 2) * sin(2 * v)) * cos(u) y = (r + cos(u / 2) * sin(v) - sin(u / 2) * sin(2 * v)) * sin(u) z = sin(u / 2) * sin(v) + cos(u / 2) * sin(2 * v) klein_eq = [x, y, z] coords = ((u, 0, 2 * pi), (v, 0, 2 * pi)) return ParametrizedSurface3D(klein_eq, coords, name)
def Dini(a=1, b=1, name="Dini's surface"): r""" Returns Dini's surface, with parametrization .. MATH:: \begin{aligned} x(u, v) & = a \cos(u)\sin(v); \\ y(u, v) & = a \sin(u)\sin(v); \\ z(u, v) & = u + \log(\tan(v/2)) + \cos(v). \end{aligned} INPUT: - ``a, b`` -- surface parameters. - ``name`` -- string. Name of the surface. EXAMPLES:: sage: dini = surfaces.Dini(a=3, b=4); dini Parametrized surface ('Dini's surface') with equation (3*cos(u)*sin(v), 3*sin(u)*sin(v), 4*u + 3*cos(v) + 3*log(tan(1/2*v))) sage: dini.plot() # not tested -- known bug (see #10132) """ u, v = var("u, v") dini_eq = [a * cos(u) * sin(v), a * sin(u) * sin(v), a * (cos(v) + log(tan(v / 2))) + b * u] coords = ((u, 0, 2 * pi), (v, 0, 2 * pi)) return ParametrizedSurface3D(dini_eq, coords, name)
def Crosscap(r=1, name="Crosscap"): r""" Returns a crosscap surface, with parametrization .. MATH:: \begin{aligned} x(u, v) & = r(1 + \cos(v)) \cos(u); \\ y(u, v) & = r(1 + \cos(v)) \sin(u); \\ z(u, v) & = - r\tanh(u - \pi) \sin(v). \end{aligned} INPUT: - ``r`` -- surface parameter. - ``name`` -- string. Name of the surface. EXAMPLES:: sage: crosscap = surfaces.Crosscap(); crosscap Parametrized surface ('Crosscap') with equation ((cos(v) + 1)*cos(u), (cos(v) + 1)*sin(u), -sin(v)*tanh(-pi + u)) sage: crosscap.plot() Graphics3d Object """ u, v = var("u, v") crosscap_eq = [r * (1 + cos(v)) * cos(u), r * (1 + cos(v)) * sin(u), -tanh(u - pi) * r * sin(v)] coords = ((u, 0, 2 * pi), (v, 0, 2 * pi)) return ParametrizedSurface3D(crosscap_eq, coords, name)
def show(self, boundary=True, **options): r""" Plot ``self``. EXAMPLES:: sage: HyperbolicPlane().PD().get_geodesic(0, 1).show() Graphics object consisting of 2 graphics primitives """ opts = dict([('axes', False), ('aspect_ratio', 1)]) opts.update(self.graphics_options()) opts.update(options) end_1, end_2 = [CC(k.coordinates()) for k in self.endpoints()] bd_1, bd_2 = [CC(k.coordinates()) for k in self.ideal_endpoints()] # Check to see if it's a line if bool(real(bd_1)*imag(bd_2) - real(bd_2)*imag(bd_1))**2 < EPSILON: from sage.plot.line import line pic = line([(real(bd_1),imag(bd_1)),(real(bd_2),imag(bd_2))], **opts) else: # If we are here, we know it's not a line # So we compute the center and radius of the circle center = (1/(real(bd_1)*imag(bd_2) - real(bd_2)*imag(bd_1)) * ((imag(bd_2)-imag(bd_1)) + (real(bd_1)-real(bd_2))*I)) radius = RR(abs(bd_1 - center)) # abs is Euclidean distance # Now we calculate the angles for the parametric plot theta1 = CC(end_1 - center).arg() theta2 = CC(end_2 - center).arg() if theta2 < theta1: theta1, theta2 = theta2, theta1 from sage.calculus.var import var from sage.plot.plot import parametric_plot x = var('x') mid = (theta1 + theta2)/2.0 if (radius*cos(mid) + real(center))**2 + \ (radius*sin(mid) + imag(center))**2 > 1.0: # Swap theta1 and theta2 tmp = theta1 + 2*pi theta1 = theta2 theta2 = tmp pic = parametric_plot((radius*cos(x) + real(center), radius*sin(x) + imag(center)), (x, theta1, theta2), **opts) else: pic = parametric_plot((radius*cos(x) + real(center), radius*sin(x) + imag(center)), (x, theta1, theta2), **opts) if boundary: bd_pic = self._model.get_background_graphic() pic = bd_pic + pic return pic
def transform(self, radius=None, azimuth=None, elevation=None): """ A spherical elevation coordinates transform. EXAMPLE:: sage: T = SphericalElevation('radius', ['azimuth', 'elevation']) sage: T.transform(radius=var('r'), azimuth=var('theta'), elevation=var('phi')) (r*cos(phi)*cos(theta), r*cos(phi)*sin(theta), r*sin(phi)) """ return (radius * cos(elevation) * cos(azimuth), radius * cos(elevation) * sin(azimuth), radius * sin(elevation))
def plot_arc(radius, p, q, **opts): # TODO: THIS SHOULD USE THE EXISTING PLOT OF ARCS! # plot the arc from p to q differently depending on the type of self p = ZZ(p) q = ZZ(q) t = var('t') if p - q in [1, -1]: def f(t): return (radius * cos(t), radius * sin(t)) (p, q) = sorted([p, q]) angle_p = vertex_to_angle(p) angle_q = vertex_to_angle(q) return parametric_plot(f(t), (t, angle_q, angle_p), **opts) if self.type() == 'A': angle_p = vertex_to_angle(p) angle_q = vertex_to_angle(q) if angle_p < angle_q: angle_p += 2 * pi internal_angle = angle_p - angle_q if internal_angle > pi: (angle_p, angle_q) = (angle_q + 2 * pi, angle_p) internal_angle = angle_p - angle_q angle_center = (angle_p+angle_q) / 2 hypotenuse = radius / cos(internal_angle / 2) radius_arc = hypotenuse * sin(internal_angle / 2) center = (hypotenuse * cos(angle_center), hypotenuse * sin(angle_center)) center_angle_p = angle_p + pi / 2 center_angle_q = angle_q + 3 * pi / 2 def f(t): return (radius_arc * cos(t) + center[0], radius_arc * sin(t) + center[1]) return parametric_plot(f(t), (t, center_angle_p, center_angle_q), **opts) elif self.type() == 'D': if p >= q: q += self.r() px = -2 * pi * p / self.r() + pi / 2 qx = -2 * pi * q / self.r() + pi / 2 arc_radius = (px - qx) / 2 arc_center = qx + arc_radius def f(t): return exp(I * ((cos(t) + I * sin(t)) * arc_radius + arc_center)) * radius return parametric_plot((real_part(f(t)), imag_part(f(t))), (t, 0, pi), **opts)
def Catenoid(c=1, name="Catenoid"): r""" Returns a catenoid surface, with parametric representation .. MATH:: \begin{aligned} x(u, v) & = c \cosh(v/c) \cos(u); \\ y(u, v) & = c \cosh(v/c) \sin(u); \\ z(u, v) & = v. \end{aligned} INPUT: - ``c`` -- surface parameter. - ``name`` -- string. Name of the surface. EXAMPLES:: sage: cat = surfaces.Catenoid(); cat Parametrized surface ('Catenoid') with equation (cos(u)*cosh(v), cosh(v)*sin(u), v) sage: cat.plot() Graphics3d Object """ u, v = var("u, v") catenoid_eq = [c * cosh(v / c) * cos(u), c * cosh(v / c) * sin(u), v] coords = ((u, 0, 2 * pi), (v, -1, 1)) return ParametrizedSurface3D(catenoid_eq, coords, name)
def Helicoid(h=1, name="Helicoid"): r""" Returns a helicoid surface, with parametrization .. MATH:: \begin{aligned} x(\rho, \theta) & = \rho \cos(\theta); \\ y(\rho, \theta) & = \rho \sin(\theta); \\ z(\rho, \theta) & = h\theta/(2\pi). \end{aligned} INPUT: - ``h`` -- distance along the z-axis between two successive turns of the helicoid. - ``name`` -- string. Name of the surface. EXAMPLES:: sage: helicoid = surfaces.Helicoid(h=2); helicoid Parametrized surface ('Helicoid') with equation (rho*cos(theta), rho*sin(theta), theta/pi) sage: helicoid.plot() Graphics3d Object """ rho, theta = var("rho, theta") helicoid_eq = [rho * cos(theta), rho * sin(theta), h * theta / (2 * pi)] coords = ((rho, -2, 2), (theta, 0, 2 * pi)) return ParametrizedSurface3D(helicoid_eq, [rho, theta], name)
def _eval_(self, a, z): """ EXAMPLES:: sage: struve_H(0,0) 0 sage: struve_H(pi,0) 0 sage: struve_H(-1/2,x) sqrt(2)*sqrt(1/(pi*x))*sin(x) sage: struve_H(1/2,-1) -sqrt(2)*sqrt(-1/pi)*(cos(1) - 1) sage: struve_H(1/2,pi) 2*sqrt(2)/pi sage: struve_H(2,x) struve_H(2, x) sage: struve_H(-3/2,x) -bessel_J(3/2, x) """ from sage.symbolic.ring import SR if z.is_zero() \ and (SR(a).is_numeric() or SR(a).is_constant()) \ and a.real() >= -1: return ZZ(0) if a == -Integer(1)/2: from sage.functions.trig import sin return sqrt(2/(pi*z)) * sin(z) if a == Integer(1)/2: from sage.functions.trig import cos return sqrt(2/(pi*z)) * (1-cos(z)) if a < 0 and not SR(a).is_integer() and SR(2*a).is_integer(): from sage.rings.rational_field import QQ n = (a*(-2) - 1)/2 return Integer(-1)**n * bessel_J(n+QQ(1)/2, z)
def _eval_(self, n, m, theta, phi, **kwargs): r""" TESTS:: sage: x, y = var('x y') sage: spherical_harmonic(1, 2, x, y) 0 sage: spherical_harmonic(1, -2, x, y) 0 sage: spherical_harmonic(1/2, 2, x, y) spherical_harmonic(1/2, 2, x, y) sage: spherical_harmonic(3, 2, x, y) 1/8*sqrt(30)*sqrt(7)*cos(x)*e^(2*I*y)*sin(x)^2/sqrt(pi) sage: spherical_harmonic(3, 2, 1, 2) 1/8*sqrt(30)*sqrt(7)*cos(1)*e^(4*I)*sin(1)^2/sqrt(pi) sage: spherical_harmonic(3 + I, 2., 1, 2) -0.351154337307488 - 0.415562233975369*I Check that :trac:`20939` is fixed:: sage: ex = spherical_harmonic(3,2,1,2*pi/3) sage: QQbar(ex * sqrt(pi)/cos(1)/sin(1)^2).minpoly() x^4 + 105/32*x^2 + 11025/1024 """ if n in ZZ and m in ZZ and n > -1: if abs(m) > n: return ZZ(0) if m == 0 and theta.is_zero(): return sqrt((2*n+1)/4/pi) from sage.arith.misc import factorial from sage.functions.trig import cos from sage.functions.orthogonal_polys import gen_legendre_P return (sqrt(factorial(n-m) * (2*n+1) / (4*pi * factorial(n+m))) * exp(I*m*phi) * gen_legendre_P(n, m, cos(theta)) * (-1)**m).simplify_trig()
def _i_rotation(self, z, alpha): r""" Return the resulting point after applying a hyperbolic rotation centered at `0 + i` and angle ``alpha`` to ``z``. INPUT: - ``z``-- point in the upper complex halfplane to which apply the isometry - ``alpha``-- angle of rotation (radians,counterwise) OUTPUT: - rotated point in the upper complex halfplane TESTS:: sage: from sage.plot.hyperbolic_regular_polygon import HyperbolicRegularPolygon sage: P = HyperbolicRegularPolygon(4, pi/4, 1+I, {}) sage: P._i_rotation(2+I, pi/2) I - 2 """ _a = alpha / 2 _c = cos(_a) _s = sin(_a) G = matrix([[_c, _s], [-_s, _c]]) return (G[0][0] * z + G[0][1]) / (G[1][0] * z + G[1][1])
def __init__(self, coxeter_matrix, base_ring, index_set): """ Initialize ``self``. EXAMPLES:: sage: W = CoxeterGroup([[1,3,2],[3,1,3],[2,3,1]]) sage: TestSuite(W).run() # long time sage: W = CoxeterGroup([[1,3,2],[3,1,4],[2,4,1]], base_ring=QQbar) sage: TestSuite(W).run() # long time sage: W = CoxeterGroup([[1,3,2],[3,1,6],[2,6,1]]) sage: TestSuite(W).run(max_runs=30) # long time sage: W = CoxeterGroup([[1,3,2],[3,1,-1],[2,-1,1]]) sage: TestSuite(W).run(max_runs=30) # long time """ self._matrix = coxeter_matrix self._index_set = index_set n = ZZ(coxeter_matrix.nrows()) MS = MatrixSpace(base_ring, n, sparse=True) # FIXME: Hack because there is no ZZ \cup \{ \infty \}: -1 represents \infty if base_ring is UniversalCyclotomicField(): val = lambda x: base_ring.gen(2*x) + ~base_ring.gen(2*x) if x != -1 else base_ring(2) else: from sage.functions.trig import cos from sage.symbolic.constants import pi val = lambda x: base_ring(2*cos(pi / x)) if x != -1 else base_ring(2) gens = [MS.one() + MS({(i, j): val(coxeter_matrix[i, j]) for j in range(n)}) for i in range(n)] FinitelyGeneratedMatrixGroup_generic.__init__(self, n, base_ring, gens, category=CoxeterGroups())
def _circ_arc(t0, t1, c, r, num_pts=500): r""" Circular arc INPUTS: - ''t0'' -- starting parameter - ''t1'' -- ending parameter - ''c'' -- center point of the circle - ''r'' -- radius of circle - ''num_pts'' -- (default 100) number of points on polygon OUTPUT: - ''ca'' -- a polygonal approximation of a circular arc centered at c and radius r, starting at t0 and ending at t1 EXAMPLES:: sage: ca=_circ_arc(0.1,0.2,0.0,1.0,100) """ from sage.plot.plot import parametric_plot from sage.functions.trig import cos, sin from sage.all import var t00 = t0 t11 = t1 ## To make sure the line is correct we reduce all arguments to the same branch, ## e.g. [0,2pi] pi = RR.pi() while t00 < 0.0: t00 = t00 + RR(2.0 * pi) while t11 < 0: t11 = t11 + RR(2.0 * pi) while t00 > 2 * pi: t00 = t00 - RR(2.0 * pi) while t11 > 2 * pi: t11 = t11 - RR(2.0 * pi) xc = CC(c).real() yc = CC(c).imag() # L0 = # [[RR(r*cos(t00+i*(t11-t00)/num_pts))+xc,RR(r*sin(t00+i*(t11-t00)/num_pts))+yc] # for i in range(0 ,num_pts)] t = var("t") if t11 > t00: ca = parametric_plot((r * cos(t) + xc, r * sin(t) + yc), (t, t00, t11)) else: ca = parametric_plot((r * cos(t) + xc, r * sin(t) + yc), (t, t11, t00)) return ca
def show(self, boundary=True, **options): r""" Plot ``self``. EXAMPLES:: sage: HyperbolicPlane().UHP().get_geodesic(0, 1).show() Graphics object consisting of 2 graphics primitives """ opts = {'axes': False, 'aspect_ratio': 1} opts.update(self.graphics_options()) opts.update(options) end_1, end_2 = [CC(k.coordinates()) for k in self.endpoints()] bd_1, bd_2 = [CC(k.coordinates()) for k in self.ideal_endpoints()] if (abs(real(end_1) - real(end_2)) < EPSILON) \ or CC(infinity) in [end_1, end_2]: #on same vertical line # If one of the endpoints is infinity, we replace it with a # large finite point if end_1 == CC(infinity): end_1 = (real(end_2), (imag(end_2) + 10)) end_2 = (real(end_2), imag(end_2)) elif end_2 == CC(infinity): end_2 = (real(end_1), (imag(end_1) + 10)) end_1 = (real(end_1), imag(end_1)) from sage.plot.line import line pic = line((end_1, end_2), **opts) if boundary: cent = min(bd_1, bd_2) bd_dict = {'bd_min': cent - 3, 'bd_max': cent + 3} bd_pic = self._model.get_background_graphic(**bd_dict) pic = bd_pic + pic return pic else: center = (bd_1 + bd_2)/2 # Circle center radius = abs(bd_1 - bd_2)/2 theta1 = CC(end_1 - center).arg() theta2 = CC(end_2 - center).arg() if abs(theta1 - theta2) < EPSILON: theta2 += pi [theta1, theta2] = sorted([theta1, theta2]) from sage.calculus.var import var from sage.plot.plot import parametric_plot x = var('x') pic = parametric_plot((radius*cos(x) + real(center), radius*sin(x) + imag(center)), (x, theta1, theta2), **opts) if boundary: # We want to draw a segment of the real line. The # computations below compute the projection of the # geodesic to the real line, and then draw a little # to the left and right of the projection. shadow_1, shadow_2 = [real(k) for k in [end_1, end_2]] midpoint = (shadow_1 + shadow_2)/2 length = abs(shadow_1 - shadow_2) bd_dict = {'bd_min': midpoint - length, 'bd_max': midpoint + length} bd_pic = self._model.get_background_graphic(**bd_dict) pic = bd_pic + pic return pic
def _draw_funddom(coset_reps,format="S"): r""" Draw a fundamental domain for G. INPUT: - ``format`` -- (default 'Disp') How to present the f.d. - ``S`` -- Display directly on the screen EXAMPLES:: sage: G=MySubgroup(Gamma0(3)) sage: G._draw_funddom() """ pi=RR.pi() pi_3 = pi / RR(3.0) from sage.plot.plot import (Graphics,line) from sage.functions.trig import (cos,sin) g=Graphics() x1=RR(-0.5) ; y1=RR(sqrt(3 )/2 ) x2=RR(0.5) ; y2=RR(sqrt(3 )/2 ) xmax=RR(20.0) l1 = line([[x1,y1],[x1,xmax]]) l2 = line([[x2,y2],[x2,xmax]]) l3 = line([[x2,xmax],[x1,xmax]]) # This is added to make a closed contour c0=_circ_arc(RR(pi/3.0) ,RR(2.0*pi)/RR(3.0) ,0 ,1 ,100 ) tri=c0+l1+l3+l2 g=g+tri for A in coset_reps: [a,b,c,d]=A if(a==1 and b==0 and c==0 and d==1 ): continue if(a<0 ): a=RR(-a); b=RR(-b); c=RR(-c); d=RR(-d) else: a=RR(a); b=RR(b); c=RR(c); d=RR(d) if(c==0 ): # then this is easier L0 = [[cos(pi_3*RR(i/100.0))+b,sin(pi_3*RR(i/100.0))] for i in range(100 ,201 )] L1 = [[x1+b,y1],[x1+b,xmax]] L2 = [[x2+b,y2],[x2+b,xmax]] L3 = [[x2+b,xmax],[x1+b,xmax]] c0=line(L0); l1=line(L1); l2=line(L2); l3=line(L3) tri=c0+l1+l3+l2 g=g+tri else: den=(c*x1+d)**2 +c**2 *y1**2 x1_t=(a*c*(x1**2 +y1**2 )+(a*d+b*c)*x1+b*d)/den y1_t=y1/den den=(c*x2+d)**2 +c**2 *y2**2 x2_t=(a*c*(x2**2 +y2**2 )+(a*d+b*c)*x2+b*d)/den y2_t=y2/den inf_t=a/c c0=_geodesic_between_two_points(x1_t,y1_t,x2_t,y2_t) c1=_geodesic_between_two_points(x1_t,y1_t,inf_t,0. ) c2=_geodesic_between_two_points(x2_t,y2_t,inf_t,0.0) tri=c0+c1+c2 g=g+tri return g
def plot(self): from sage.functions.trig import sin, cos from sage.plot.circle import circle from sage.plot.point import point from sage.plot.text import text p = circle((0,0),1) for i in range(self._dimension): a = self._alpha[i] p += point([cos(2*pi*a),sin(2*pi*a)], color='blue', size=100) p += text(r"$\alpha_%i$"%(self._i_alpha[i]+1), [1.2*cos(2*pi*a),1.2*sin(2*pi*a)],fontsize=40) for i in range(self._dimension): b = self._beta[i] p += point([cos(2*pi*b),sin(2*pi*b)], color='red', size=100) p += text(r"$\beta_%i$"%(self._i_beta[i]+1), [1.2*cos(2*pi*b),1.2*sin(2*pi*b)],fontsize=40) p.show(axes=False, xmin=-1, xmax=1, ymin=-1, ymax=1)
def plot(self, show_box=False, colors=["white","lightgray","darkgray"]): r""" Return a plot of ``self``. INPUT: - ``show_box`` -- boolean (default: ``False``); if ``True``, also shows the visible tiles on the `xy`-, `yz`-, `zx`-planes - ``colors`` -- (default: ``["white", "lightgray", "darkgray"]``) list ``[A, B, C]`` of 3 strings representing colors EXAMPLES:: sage: PP = PlanePartition([[4,3,3,1],[2,1,1],[1,1]]) sage: PP.plot() Graphics object consisting of 27 graphics primitives """ x = self._max_x y = self._max_y z = self._max_z from sage.functions.trig import cos, sin from sage.plot.polygon import polygon from sage.symbolic.constants import pi from sage.plot.plot import plot Uside = [[0,0], [cos(-pi/6),sin(-pi/6)], [0,-1], [cos(7*pi/6),sin(7*pi/6)]] Lside = [[0,0], [cos(-pi/6),sin(-pi/6)], [cos(pi/6),sin(pi/6)], [0,1]] Rside = [[0,0], [0,1], [cos(5*pi/6),sin(5*pi/6)], [cos(7*pi/6),sin(7*pi/6)]] Xdir = [cos(7*pi/6), sin(7*pi/6)] Ydir = [cos(-pi/6), sin(-pi/6)] Zdir = [0, 1] def move(side, i, j, k): return [[P[0]+i*Xdir[0]+j*Ydir[0]+k*Zdir[0], P[1]+i*Xdir[1]+j*Ydir[1]+k*Zdir[1]] for P in side] def add_topside(i, j, k): return polygon(move(Uside,i,j,k), edgecolor="black", color=colors[0]) def add_leftside(i, j, k): return polygon(move(Lside,i,j,k), edgecolor="black", color=colors[1]) def add_rightside(i, j, k): return polygon(move(Rside,i,j,k), edgecolor="black", color=colors[2]) TP = plot([]) for r in range(len(self.z_tableau())): for c in range(len(self.z_tableau()[r])): if self.z_tableau()[r][c] > 0 or show_box: TP += add_topside(r, c, self.z_tableau()[r][c]) for r in range(len(self.y_tableau())): for c in range(len(self.y_tableau()[r])): if self.y_tableau()[r][c] > 0 or show_box: TP += add_rightside(c, self.y_tableau()[r][c], r) for r in range(len(self.x_tableau())): for c in range(len(self.x_tableau()[r])): if self.x_tableau()[r][c] > 0 or show_box: TP += add_leftside(self.x_tableau()[r][c], r, c) TP.axes(show=False) return TP
def __init__(self, sides, i_angle, center, options): """ Initialize HyperbolicRegularPolygon. EXAMPLES:: sage: from sage.plot.hyperbolic_regular_polygon import HyperbolicRegularPolygon sage: print(HyperbolicRegularPolygon(5,pi/2,I, {})) Hyperbolic regular polygon (sides=5, i_angle=1/2*pi, center=1.00000000000000*I) """ self.center = CC(center) if self.center.imag() <= 0 : raise ValueError("center: %s is not a valid point in the upper half plane model of the hyperbolic plane"%(self.center)) if sides < 3 : raise ValueError("degenerated polygons (sides<=2) are not supported") if i_angle <=0 or i_angle >= pi: raise ValueError("interior angle %s must be in (0, pi) interval"%(i_angle)) if pi*(sides-2) - sides*i_angle <= 0 : raise ValueError("there exists no hyperbolic regular compact polygon, for sides=%s the interior angle must be less than %s"%(sides, pi * (sides-2) / sides)) self.sides = sides self.i_angle = i_angle beta = 2 * pi / self.sides # compute the rotation angle to be used ahead alpha = self.i_angle / Integer(2) I = CC(0, 1) # compute using cosine theorem the radius of the circumscribed circle # using the triangle formed by the radius and the three known angles r = arccosh(cot(alpha) * (1 + cos(beta)) / sin(beta)) # The first point will be always on the imaginary axis limited # to 8 digits for efficiency in the subsequent calculations. z_0 = [I*(e**r).n(digits=8)] # Compute the dilation isometry used to move the center # from I to the imaginary part of the given center. scale = self.center.imag() # Compute the parabolic isometry to move the center to the # real part of the given center. h_disp = self.center.real() d_z_k = [z_0[0]*scale + h_disp] #d_k has the points for the polygon in the given center z_k = z_0 #z_k has the Re(z)>0 vertices for the I centered polygon r_z_k = [] #r_z_k has the Re(z)<0 vertices if is_odd(self.sides): vert = (self.sides - 1) / 2 else: vert = self.sides / 2 - 1 for k in range(0, vert): # Compute with 8 digits to accelerate calculations new_z_k = self._i_rotation(z_k[-1], beta).n(digits=8) z_k = z_k + [new_z_k] d_z_k = d_z_k + [new_z_k * scale + h_disp] r_z_k=[-(new_z_k).conjugate() * scale + h_disp] + r_z_k if is_odd(self.sides): HyperbolicPolygon.__init__(self, d_z_k + r_z_k, options) else: z_opo = [I * (e**(-r)).n(digits=8) * scale + h_disp] HyperbolicPolygon.__init__(self, d_z_k + z_opo + r_z_k, options)
def bilinear_form(self, R=None): """ Return the bilinear form over ``R`` associated to ``self``. INPUT: - ``R`` -- (default: universal cyclotomic field) a ring used to compute the bilinear form EXAMPLES:: sage: CoxeterType(['A', 2, 1]).bilinear_form() [ 1 -1/2 -1/2] [-1/2 1 -1/2] [-1/2 -1/2 1] sage: CoxeterType(['H', 3]).bilinear_form() [ 1 -1/2 0] [ -1/2 1 1/2*E(5)^2 + 1/2*E(5)^3] [ 0 1/2*E(5)^2 + 1/2*E(5)^3 1] sage: C = CoxeterMatrix([[1,-1,-1],[-1,1,-1],[-1,-1,1]]) sage: C.bilinear_form() [ 1 -1 -1] [-1 1 -1] [-1 -1 1] """ n = self.rank() mat = self.coxeter_matrix()._matrix base_ring = mat.base_ring() from sage.rings.universal_cyclotomic_field import UniversalCyclotomicField UCF = UniversalCyclotomicField() if UCF.has_coerce_map_from(base_ring): R = UCF else: R = base_ring # Compute the matrix with entries `- \cos( \pi / m_{ij} )`. if R is UCF: val = lambda x: (R.gen(2 * x) + ~R.gen(2 * x)) / R(-2) if x > -1 else R.one() * x else: from sage.functions.trig import cos from sage.symbolic.constants import pi val = lambda x: -R(cos(pi / SR(x))) if x > -1 else x MS = MatrixSpace(R, n, sparse=True) MC = MS._get_matrix_class() bilinear = MC( MS, entries={(i, j): val(mat[i, j]) for i in range(n) for j in range(n) if mat[i, j] != 2}, coerce=True, copy=True, ) bilinear.set_immutable() return bilinear
def plot(self, **kwargs): """ Return a graphics object representing the Kontsevich graph. INPUT: - ``edge_labels`` (boolean, default True) -- if True, show edge labels. - ``indices`` (boolean, default False) -- if True, show indices as edge labels instead of L and R; see :meth:`._latex_`. - ``upright`` (boolean, default False) -- if True, try to plot the graph with the ground vertices on the bottom and the rest on top. """ if not 'edge_labels' in kwargs: kwargs['edge_labels'] = True # show edge labels by default if 'indices' in kwargs: del kwargs['indices'] KG = DiGraph(self) for (k,e) in enumerate(self.edges()): KG.delete_edge(e) KG.add_edge((e[0], e[1], chr(97 + k))) return KG.plot(**kwargs) if len(self.ground_vertices()) == 2 and 'upright' in kwargs: del kwargs['upright'] kwargs['save_pos'] = True DiGraph.plot(self, **kwargs) positions = self.get_pos() # translate F to origin: F_pos = vector(positions[self.ground_vertices()[0]]) for p in positions: positions[p] = list(vector(positions[p]) - F_pos) # scale F - G distance to 1: G_len = abs(vector(positions[self.ground_vertices()[1]])) for p in positions: positions[p] = list(vector(positions[p])/G_len) # rotate the vector F - G to (1,0) from math import atan2 theta = -atan2(positions[self.ground_vertices()[1]][1], positions[self.ground_vertices()[1]][0]) for p in positions: positions[p] = list(matrix([[cos(theta),-(sin(theta))],[sin(theta),cos(theta)]]) * vector(positions[p])) # flip if most things are below the x-axis: if len([(x,y) for (x,y) in positions.values() if y < 0])/len(self.internal_vertices()) > 0.5: for p in positions: positions[p] = [positions[p][0], -positions[p][1]] return DiGraph.plot(self, **kwargs)
def _derivative_(self, x, diff_param=None): """ EXAMPLES:: sage: x = var('x') sage: fresnel_cos(x).diff(x) cos(1/2*pi*x^2) """ from sage.functions.trig import cos return cos(pi*x**2/2)
def Ellipsoid(center=(0,0,0), axes=(1,1,1), name="Ellipsoid"): r""" Returns an ellipsoid centered at ``center`` whose semi-principal axes have lengths given by the components of ``axes``. The parametrization of the ellipsoid is given by .. MATH:: \begin{aligned} x(u, v) & = x_0 + a \cos(u) \cos(v); \\ y(u, v) & = y_0 + b \sin(u) \cos(v); \\ z(u, v) & = z_0 + c \sin(v). \end{aligned} INPUT: - ``center`` -- 3-tuple. Coordinates of the center of the ellipsoid. - ``axes`` -- 3-tuple. Lengths of the semi-principal axes. - ``name`` -- string. Name of the ellipsoid. EXAMPLES:: sage: ell = surfaces.Ellipsoid(axes=(1, 2, 3)); ell Parametrized surface ('Ellipsoid') with equation (cos(u)*cos(v), 2*cos(v)*sin(u), 3*sin(v)) sage: ell.plot() """ u, v = var ('u, v') x, y, z = center a, b, c = axes ellipsoid_parametric_eq = [x + a*cos(u)*cos(v), y + b*sin(u)*cos(v), z + c*sin(v)] coords = ((u, 0, 2*pi), (v, -pi/2, pi/2)) return ParametrizedSurface3D(ellipsoid_parametric_eq, coords, name)
def transform(self, radius=None, azimuth=None, height=None): """ A cylindrical coordinates transform. EXAMPLE:: sage: T = Cylindrical('height', ['azimuth', 'radius']) sage: T.transform(radius=var('r'), azimuth=var('theta'), height=var('z')) (r*cos(theta), r*sin(theta), z) """ return (radius * cos(azimuth), radius * sin(azimuth), height)
def _derivative_(self, z, diff_param=None): r""" The derivative of `\operatorname{Ci}(z)` is `\cos(z)/z` if `z` is not zero. EXAMPLES:: sage: x = var('x') sage: f = cos_integral(x) sage: f.diff(x) cos(x)/x sage: f = cos_integral(x^2) sage: f.diff(x) 2*cos(x^2)/x """ return cos(z)/z
def photon_orbit_radius(self, retrograde=False): r""" Return the Boyer-Lindquist radial coordinate of the circular orbit of photons in the equatorial plane. INPUT: - ``retrograde`` -- (default: ``False``) boolean determining whether retrograde or prograde (direct) orbits are considered OUTPUT: - Boyer-Lindquist radial coordinate `r` of the circular orbit of photons in the equatorial plane EXAMPLES:: sage: from kerrgeodesic_gw import KerrBH sage: a, m = var('a m') sage: BH = KerrBH(a, m) sage: BH.photon_orbit_radius() 2*m*(cos(2/3*arccos(-a/m)) + 1) sage: BH.photon_orbit_radius(retrograde=True) 2*m*(cos(2/3*arccos(a/m)) + 1) Photon orbit in Schwarzschild spacetime:: sage: KerrBH(0, m).photon_orbit_radius() 3*m Photon orbits in extreme Kerr spacetime (`a=m`):: sage: KerrBH(m, m).photon_orbit_radius() m sage: KerrBH(m, m).photon_orbit_radius(retrograde=True) 4*m """ m = self._m a = self._a eps = -1 if not retrograde else 1 # Eq. (2.18) in Bardeen, Press & Teukolsky, ApJ 178, 347 (1972) return 2 * m * (1 + cos(2 * acos(eps * a / m) / 3))
def _circ_arc(t0, t1, c, r, num_pts=5000): r""" Circular arc INPUTS: - ''t0'' -- starting parameter - ''t1'' -- ending parameter - ''c'' -- center point of the circle - ''r'' -- radius of circle - ''num_pts'' -- (default 100) number of points on polygon OUTPUT: - ''ca'' -- a polygonal approximation of a circular arc centered at c and radius r, starting at t0 and ending at t1 EXAMPLES:: sage: ca=_circ_arc(0.1,0.2,0.0,1.0,100) """ from sage.plot.plot import line from sage.functions.trig import (cos, sin) t00 = t0 t11 = t1 ## To make sure the line is correct we reduce all arguments to the same branch, ## e.g. [0,2pi] pi = RR.pi() while (t00 < 0.0): t00 = t00 + RR(2.0 * pi) while (t11 < 0): t11 = t11 + RR(2.0 * pi) while (t00 > 2 * pi): t00 = t00 - RR(2.0 * pi) while (t11 > 2 * pi): t11 = t11 - RR(2.0 * pi) xc = CC(c).real() yc = CC(c).imag() L0 = [[ RR(r * cos(t00 + i * (t11 - t00) / num_pts)) + xc, RR(r * sin(t00 + i * (t11 - t00) / num_pts)) + yc ] for i in range(0, num_pts)] ca = line(L0) return ca
def draw_transformed_triangle_H(A,xmax=20): r""" Draw the modular triangle translated by A=[a,b,c,d] """ #print "A=",A,type(A) pi=RR.pi() pi_3 = pi / RR(3.0) from sage.plot.plot import (Graphics,line) from sage.functions.trig import (cos,sin) x1=RR(-0.5) ; y1=RR(sqrt(3 )/2 ) x2=RR(0.5) ; y2=RR(sqrt(3 )/2 ) a,b,c,d = A #[0,0]; b=A[0,1]; c=A[1,0]; d=A[1,1] if a<0: a=RR(-a); b=RR(-b); c=RR(-c); d=RR(-d) else: a=RR(a); b=RR(b); c=RR(c); d=RR(d) if c==0: # then this is easier if a*d<>0: a=a/d; b=b/d; L0 = [[a*cos(pi_3*RR(i/100.0))+b,a*sin(pi_3*RR(i/100.0))] for i in range(100 ,201 )] L1 = [[a*x1+b,a*y1],[a*x1+b,xmax]] L2 = [[a*x2+b,a*y2],[a*x2+b,xmax]] L3 = [[a*x2+b,xmax],[a*x1+b,xmax]] c0=line(L0); l1=line(L1); l2=line(L2); l3=line(L3) tri=c0+l1+l3+l2 else: den=(c*x1+d)**2 +c**2 *y1**2 x1_t=(a*c*(x1**2 +y1**2 )+(a*d+b*c)*x1+b*d)/den y1_t=y1/den den=(c*x2+d)**2 +c**2 *y2**2 x2_t=(a*c*(x2**2 +y2**2 )+(a*d+b*c)*x2+b*d)/den y2_t=y2/den inf_t=a/c #print "A=",A #print "arg1=",x1_t,y1_t,x2_t,y2_t c0=_geodesic_between_two_points(x1_t,y1_t,x2_t,y2_t) #print "arg1=",x1_t,y1_t,inf_t c1=_geodesic_between_two_points(x1_t,y1_t,inf_t,0. ) #print "arg1=",x2_t,y2_t,inf_t c2=_geodesic_between_two_points(x2_t,y2_t,inf_t,0.0) tri=c0+c1+c2 return tri
def __init__(self, coxeter_matrix, base_ring, index_set): """ Initialize ``self``. EXAMPLES:: sage: W = CoxeterGroup([[1,3,2],[3,1,3],[2,3,1]]) sage: TestSuite(W).run() # long time sage: W = CoxeterGroup([[1,3,2],[3,1,4],[2,4,1]], base_ring=QQbar) sage: TestSuite(W).run() # long time sage: W = CoxeterGroup([[1,3,2],[3,1,6],[2,6,1]]) sage: TestSuite(W).run(max_runs=30) # long time sage: W = CoxeterGroup([[1,3,2],[3,1,-1],[2,-1,1]]) sage: TestSuite(W).run(max_runs=30) # long time """ self._matrix = coxeter_matrix self._index_set = index_set n = ZZ(coxeter_matrix.nrows()) MS = MatrixSpace(base_ring, n, sparse=True) # FIXME: Hack because there is no ZZ \cup \{ \infty \}: -1 represents \infty if base_ring is UniversalCyclotomicField(): val = lambda x: base_ring.gen(2 * x) + ~base_ring.gen( 2 * x) if x != -1 else base_ring(2) else: from sage.functions.trig import cos from sage.symbolic.constants import pi val = lambda x: base_ring(2 * cos(pi / x) ) if x != -1 else base_ring(2) gens = [ MS.one() + MS({(i, j): val(coxeter_matrix[i, j]) for j in range(n)}) for i in range(n) ] FinitelyGeneratedMatrixGroup_generic.__init__(self, n, base_ring, gens, category=CoxeterGroups())
def regular_polygon(self, n, base_ring=QQ): """ Return a regular polygon with n vertices. Over the rational field the vertices may not be exact. INPUT: - ``n`` -- a positive integer, the number of vertices. - ``field`` -- either ``QQ`` or ``RDF``. EXAMPLES:: sage: octagon = polytopes.regular_polygon(8) sage: len(octagon.vertices()) 8 """ npi = 3.14159265359 verts = [] for i in range(n): t = 2*npi*i/n verts.append([sin(t),cos(t)]) verts = [[base_ring(RDF(x)) for x in y] for y in verts] return Polyhedron(vertices=verts, base_ring=base_ring)
def _eval_(self, n, m, theta, phi, **kwargs): r""" TESTS:: sage: x, y = var('x y') sage: spherical_harmonic(1, 2, x, y) 0 sage: spherical_harmonic(1, -2, x, y) 0 sage: spherical_harmonic(1/2, 2, x, y) spherical_harmonic(1/2, 2, x, y) sage: spherical_harmonic(3, 2, x, y) 1/8*sqrt(30)*sqrt(7)*cos(x)*e^(2*I*y)*sin(x)^2/sqrt(pi) sage: spherical_harmonic(3, 2, 1, 2) 1/8*sqrt(30)*sqrt(7)*cos(1)*e^(4*I)*sin(1)^2/sqrt(pi) sage: spherical_harmonic(3 + I, 2., 1, 2) -0.351154337307488 - 0.415562233975369*I Check that :trac:`20939` is fixed:: sage: ex = spherical_harmonic(3,2,1,2*pi/3) sage: QQbar(ex * sqrt(pi)/cos(1)/sin(1)^2).minpoly() x^4 + 105/32*x^2 + 11025/1024 """ if n in ZZ and m in ZZ and n > -1: if abs(m) > n: return ZZ(0) if m == 0 and theta.is_zero(): return sqrt((2 * n + 1) / 4 / pi) from sage.arith.misc import factorial from sage.functions.trig import cos from sage.functions.orthogonal_polys import gen_legendre_P return (sqrt( factorial(n - m) * (2 * n + 1) / (4 * pi * factorial(n + m))) * exp(I * m * phi) * gen_legendre_P(n, m, cos(theta)) * (-1)**m).simplify_trig()
def regular_polygon(self, n, base_ring=QQ): """ Return a regular polygon with n vertices. Over the rational field the vertices may not be exact. INPUT: - ``n`` -- a positive integer, the number of vertices. - ``field`` -- either ``QQ`` or ``RDF``. EXAMPLES:: sage: octagon = polytopes.regular_polygon(8) sage: len(octagon.vertices()) 8 """ npi = 3.14159265359 verts = [] for i in range(n): t = 2 * npi * i / n verts.append([sin(t), cos(t)]) verts = [[base_ring(RDF(x)) for x in y] for y in verts] return Polyhedron(vertices=verts, base_ring=base_ring)
def f(t): return exp(I * ((cos(t) + I * sin(t)) * arc_radius + arc_center)) * radius
raise ValueError("Ellipse not implemented") first_unit_vector = first_vector / radius second_unit_vector = second_vector / radius normal_vector = second_vector - (second_vector * first_unit_vector) * first_unit_vector if norm(normal_vector) == 0: print(first_point, second_point) return normal_unit_vector = normal_vector / norm(normal_vector) scalar_product = first_unit_vector * second_unit_vector if abs(scalar_product) == 1: raise ValueError("The points are alligned") angle = arccos(scalar_product) var('t') return parametric_plot3d( center + first_vector * cos(t) + radius * normal_unit_vector * sin(t), (0, angle), **kwds) def _arc(p, q, s, **kwds): #rewrite this to use polar_plot and get points to do filled triangles from sage.misc.functional import det from sage.plot.line import line from sage.misc.functional import norm from sage.symbolic.all import pi from sage.plot.arc import arc p, q, s = map(lambda x: vector(x), [p, q, s]) # to avoid running into division by 0 we set to be colinear vectors that are # almost colinear
def surface_density_gaussian(r, phi, param): r""" Surface density of a matter blob with a Gaussian profile INPUT: - ``r`` -- Boyer-Lindquist radial coordinate `\bar{r}` in the matter blob - ``phi`` -- Boyer-Lindquist azimuthal coordinate `\bar{\phi}` in the matter blob - ``param`` -- list of parameters defining the position and width of matter blob: - ``param[0]``: mean radius `r_0` (Boyer-Lindquist coordinate) - ``param[1]``: mean azimuthal angle `\phi_0` (Boyer-Lindquist coordinate) - ``param[2]``: width `\lambda` of the Gaussian profile - ``param[3]`` (optional): amplitude `\Sigma_0`; if not provided, then `\Sigma_0=1` is used OUTPUT: - surface density `\Sigma(\bar{r}, \bar{\phi})` EXAMPLES:: sage: from kerrgeodesic_gw import surface_density_gaussian sage: param = [6.5, 0., 0.3] sage: surface_density_gaussian(6.5, 0, param) 1.0 sage: surface_density_gaussian(8., 0, param) # tol 1.0e-13 1.3887943864964021e-11 sage: surface_density_gaussian(6.5, pi/16, param) # tol 1.0e-13 1.4901161193847656e-08 3D representation: `z=\Sigma(\bar{r}, \bar{\phi})` in terms of `x:=\bar{r}\cos\bar\phi` and `y:=\bar{r}\sin\bar\phi`:: sage: s_plot = lambda r, phi: surface_density_gaussian(r, phi, param) sage: r, phi, z = var('r phi z') sage: plot3d(s_plot, (r, 6, 8), (phi, -0.4, 0.4), ....: transformation=(r*cos(phi), r*sin(phi), z)) Graphics3d Object .. PLOT:: from kerrgeodesic_gw import surface_density_gaussian param = param = [6.5, 0., 0.3] s_plot = lambda r, phi: surface_density_gaussian(r, phi, param) r, phi, z = var('r phi z') g = plot3d(s_plot, (r, 6, 8), (phi, -0.4, 0.4), \ transformation=(r*cos(phi), r*sin(phi), z)) sphinx_plot(g) Use with a non-default amplitude (`\Sigma_0=10^{-5}`):: sage: sigma0 = 1.e-5 sage: param = [6.5, 0., 0.3, sigma0] sage: surface_density_gaussian(6.5, 0, param) 1e-05 """ r0, phi0, lam = param[0], param[1], param[2] Sigma0 = param[3] if len(param) == 4 else float(1) return float(Sigma0*exp(-((r - r0*cos(phi-phi0))**2 + (r0*sin(phi-phi0))**2)/lam**2))
def _geodesic_between_two_points_d(x1,y1,x2,y2,z0=I): r""" Geodesic path between two points represented in the unit disc by the map w = (z-I)/(z+I) INPUTS: - ''(x1,y1)'' -- starting point (0<y1<=infinity) - ''(x2,y2)'' -- ending point (0<y2<=infinity) - ''z0'' -- (default I) the point in the upper corresponding to the point 0 in the disc. I.e. the transform is w -> (z-I)/(z+I) OUTPUT: - ''ca'' -- a polygonal approximation of a circular arc centered at c and radius r, starting at t0 and ending at t1 EXAMPLES:: sage: l=_geodesic_between_two_points_d(0.1,0.2,0.0,0.5) """ pi=RR.pi() from sage.plot.plot import line from sage.functions.trig import (cos,sin) # First compute the points if(y1<0 or y2<0 ): raise ValueError,"Need points in the upper half-plane! Got y1=%s, y2=%s" %(y1,y2) if(y1==infinity): P1=CC(1 ) else: P1=CC((x1+I*y1-z0)/(x1+I*y1-z0.conjugate())) if(y2==infinity): P2=CC(1 ) else: P2=CC((x2+I*y2-z0)/(x2+I*y2-z0.conjugate())) # First find the endpoints of the completed geodesic in D if(x1==x2): a=CC((x1-z0)/(x1-z0.conjugate())) b=CC(1 ) else: c=RR(y1**2 -y2**2 +x1**2 -x2**2 )/RR(2 *(x1-x2)) r=RR(sqrt(y1**2 +(x1-c)**2 )) a=c-r b=c+r a=CC((a-z0)/(a-z0.conjugate())) b=CC((b-z0)/(b-z0.conjugate())) if( abs(a+b) < 1E-10 ): # On a diagonal return line([[P1.real(),P1.imag()],[P2.real(),P2.imag()]]) th_a=a.argument() th_b=b.argument() # Compute the center of the circle in the disc model if( min(abs(b-1 ),abs(b+1 ))< 1E-10 and min(abs(a-1 ),abs(a+1 ))>1E-10 ): c=b+I*(1 -b*cos(th_a))/sin(th_a) elif( min(abs(b-1 ),abs(b+1 ))> 1E-10 and min(abs(a-1 ),abs(a+1 ))<1E-10 ): c=a+I*(1 -a*cos(th_b))/RR(sin(th_b)) else: cx=(sin(th_b)-sin(th_a))/sin(th_b-th_a) c=cx+I*(1 -cx*cos(th_b))/RR(sin(th_b)) # First find the endpoints of the completed geodesic r=abs(c-a) t1=CC(P1-c).argument() t2=CC(P2-c).argument() #print "t1,t2=",t1,t2 return _circ_arc(t1,t2,c,r)
def revolution_plot3d(curve, trange, phirange=None, parallel_axis='z', axis=(0, 0), print_vector=False, show_curve=False, **kwds): r""" Return a plot of a revolved curve. There are three ways to call this function: - ``revolution_plot3d(f,trange)`` where `f` is a function located in the `x z` plane. - ``revolution_plot3d((f_x,f_z),trange)`` where `(f_x,f_z)` is a parametric curve on the `x z` plane. - ``revolution_plot3d((f_x,f_y,f_z),trange)`` where `(f_x,f_y,f_z)` can be any parametric curve. INPUT: - ``curve`` - A curve to be revolved, specified as a function, a 2-tuple or a 3-tuple. - ``trange`` - A 3-tuple `(t,t_{\min},t_{\max})` where t is the independent variable of the curve. - ``phirange`` - A 2-tuple of the form `(\phi_{\min},\phi_{\max})`, (default `(0,\pi)`) that specifies the angle in which the curve is to be revolved. - ``parallel_axis`` - A string (Either 'x', 'y', or 'z') that specifies the coordinate axis parallel to the revolution axis. - ``axis`` - A 2-tuple that specifies the position of the revolution axis. If parallel is: - 'z' - then axis is the point in which the revolution axis intersects the `x y` plane. - 'x' - then axis is the point in which the revolution axis intersects the `y z` plane. - 'y' - then axis is the point in which the revolution axis intersects the `x z` plane. - ``print_vector`` - If True, the parametrization of the surface of revolution will be printed. - ``show_curve`` - If True, the curve will be displayed. EXAMPLES: Let's revolve a simple function around different axes:: sage: u = var('u') sage: f = u^2 sage: revolution_plot3d(f, (u,0,2), show_curve=True, opacity=0.7).show(aspect_ratio=(1,1,1)) .. PLOT:: u = var('u') f = u**2 P = revolution_plot3d(f, (u,0,2), show_curve=True, opacity=0.7).plot() sphinx_plot(P) If we move slightly the axis, we get a goblet-like surface:: sage: revolution_plot3d(f, (u,0,2), axis=(1,0.2), show_curve=True, opacity=0.5).show(aspect_ratio=(1,1,1)) .. PLOT:: u = var('u') f = u**2 P = revolution_plot3d(f, (u,0,2), axis=(1,0.2), show_curve=True, opacity=0.5).plot() sphinx_plot(P) A common problem in calculus books, find the volume within the following revolution solid:: sage: line = u sage: parabola = u^2 sage: sur1 = revolution_plot3d(line, (u,0,1), opacity=0.5, rgbcolor=(1,0.5,0), show_curve=True, parallel_axis='x') sage: sur2 = revolution_plot3d(parabola, (u,0,1), opacity=0.5, rgbcolor=(0,1,0), show_curve=True, parallel_axis='x') sage: (sur1+sur2).show() .. PLOT:: u = var('u') line = u parabola = u**2 sur1 = revolution_plot3d(line, (u,0,1), opacity=0.5, rgbcolor=(1,0.5,0), show_curve=True, parallel_axis='x') sur2 = revolution_plot3d(parabola, (u,0,1), opacity=0.5, rgbcolor=(0,1,0), show_curve=True, parallel_axis='x') P = sur1 + sur2 sphinx_plot(P) Now let's revolve a parametrically defined circle. We can play with the topology of the surface by changing the axis, an axis in `(0,0)` (as the previous one) will produce a sphere-like surface:: sage: u = var('u') sage: circle = (cos(u), sin(u)) sage: revolution_plot3d(circle, (u,0,2*pi), axis=(0,0), show_curve=True, opacity=0.5).show(aspect_ratio=(1,1,1)) .. PLOT:: u = var('u') circle = (cos(u), sin(u)) P = revolution_plot3d(circle, (u,0,2*pi), axis=(0,0), show_curve=True, opacity=0.5) sphinx_plot(P) An axis on `(0,y)` will produce a cylinder-like surface:: sage: revolution_plot3d(circle, (u,0,2*pi), axis=(0,2), show_curve=True, opacity=0.5).show(aspect_ratio=(1,1,1)) .. PLOT:: u = var('u') circle = (cos(u), sin(u)) P = revolution_plot3d(circle, (u,0,2*pi), axis=(0,2), show_curve=True, opacity=0.5) sphinx_plot(P) And any other axis will produce a torus-like surface:: sage: revolution_plot3d(circle, (u,0,2*pi), axis=(2,0), show_curve=True, opacity=0.5).show(aspect_ratio=(1,1,1)) .. PLOT:: u = var('u') circle = (cos(u), sin(u)) P = revolution_plot3d(circle, (u,0,2*pi), axis=(2,0), show_curve=True, opacity=0.5) sphinx_plot(P) Now, we can get another goblet-like surface by revolving a curve in 3d:: sage: u = var('u') sage: curve = (u, cos(4*u), u^2) sage: P = revolution_plot3d(curve, (u,0,2), show_curve=True, parallel_axis='z',axis=(1,.2), opacity=0.5) sage: P.show(aspect_ratio=(1,1,1)) .. PLOT:: u = var('u') curve = (u, cos(4*u), u**2) P = revolution_plot3d(curve, (u,0,2), show_curve=True, parallel_axis='z', axis=(1,.2), opacity=0.5) sphinx_plot(P) A curvy curve with only a quarter turn:: sage: u = var('u') sage: curve = (sin(3*u), .8*cos(4*u), cos(u)) sage: revolution_plot3d(curve, (u,0,pi), (0,pi/2), show_curve=True, parallel_axis='z', opacity=0.5).show(aspect_ratio=(1,1,1),frame=False) .. PLOT:: u = var('u') curve = (sin(3*u), .8*cos(4*u), cos(u)) P = revolution_plot3d(curve, (u,0,pi), (0,pi/2), show_curve=True, parallel_axis='z', opacity=0.5) sphinx_plot(P) One can also color the surface using a coloring function of two parameters and a colormap as follows. Note that the coloring function must take values in the interval [0,1]. :: sage: u, phi = var('u,phi') sage: def cf(u,phi): return sin(phi+u) ^ 2 sage: curve = (1+u^2/4, 0, u) sage: revolution_plot3d(curve, (u,-2,2), (0,2*pi), parallel_axis='z', color=(cf, colormaps.PiYG)).show(aspect_ratio=(1,1,1)) .. PLOT:: u, phi = var('u,phi') def cf(u, phi): return sin(phi+u) ** 2 curve = (1+u**2/4, 0, u) P = revolution_plot3d(curve, (u,-2,2), (0,2*pi), parallel_axis='z', color=(cf, colormaps.PiYG)) sphinx_plot(P) The first parameter of the coloring function will be identified with the parameter of the curve, and the second with the angle parameter. .. WARNING:: This kind of coloring using a colormap can be visualized using Jmol, Tachyon (option ``viewer='tachyon'``) and Canvas3D (option ``viewer='canvas3d'`` in the notebook). Another colored example, illustrating that one can use (colormap, color function) instead of (color function, colormap):: sage: u, phi = var('u,phi') sage: def cf(u, phi): return float(2 * u / pi) % 1 sage: curve = (sin(u), 0, u) sage: revolution_plot3d(curve, (u,0,pi), (0,2*pi), parallel_axis ....: ='z', color=(colormaps.brg, cf)).show(aspect_ratio=1) .. PLOT:: u, phi = var('u,phi') def cf(u, phi): return float(2 * u / pi) % 1 curve = (sin(u), 0, u) P = revolution_plot3d(curve, (u,0,pi), (0,2*pi), parallel_axis='z', color=(colormaps.brg, cf)) sphinx_plot(P) """ from sage.symbolic.ring import SR from sage.symbolic.constants import pi from sage.misc.functional import sqrt from sage.functions.trig import sin from sage.functions.trig import cos from sage.functions.trig import atan2 if parallel_axis not in ['x', 'y', 'z']: raise ValueError("parallel_axis must be either 'x', 'y', or 'z'.") vart = trange[0] if str(vart) == 'phi': phi = SR.var('fi') else: phi = SR.var('phi') if phirange is None: # this if-else provides a phirange phirange = (phi, 0, 2 * pi) elif len(phirange) == 3: phi = phirange[0] else: phirange = (phi, phirange[0], phirange[1]) if isinstance(curve, (tuple, list)): #this if-else provides a vector v to be plotted #if curve is a tuple or a list of length 2, it is interpreted as a parametric curve #in the x-z plane. #if it is of length 3 it is interpreted as a parametric curve in 3d space if len(curve) == 2: x = curve[0] y = 0 z = curve[1] elif len(curve) == 3: x = curve[0] y = curve[1] z = curve[2] else: x = vart y = 0 z = curve phase = 0 if parallel_axis == 'z': x0 = axis[0] y0 = axis[1] # (0,0) must be handled separately for the phase value if x0 != 0 or y0 != 0: phase = atan2(y - y0, x - x0) R = sqrt((x - x0)**2 + (y - y0)**2) v = (R * cos(phi + phase) + x0, R * sin(phi + phase) + y0, z) elif parallel_axis == 'x': y0 = axis[0] z0 = axis[1] # (0,0) must be handled separately for the phase value if z0 != 0 or y0 != 0: phase = atan2(z - z0, y - y0) R = sqrt((y - y0)**2 + (z - z0)**2) v = (x, R * cos(phi + phase) + y0, R * sin(phi + phase) + z0) elif parallel_axis == 'y': x0 = axis[0] z0 = axis[1] # (0,0) must be handled separately for the phase value if z0 != 0 or x0 != 0: phase = atan2(z - z0, x - x0) R = sqrt((x - x0)**2 + (z - z0)**2) v = (R * cos(phi + phase) + x0, y, R * sin(phi + phase) + z0) if print_vector: print(v) if show_curve: curveplot = parametric_plot3d((x, y, z), trange, thickness=2, rgbcolor=(1, 0, 0)) return parametric_plot3d(v, trange, phirange, **kwds) + curveplot return parametric_plot3d(v, trange, phirange, **kwds)
def Sphere(dim=None, radius=1, names=None, stereo2d=False, stereo_lim=None): """ Generate a sphere embedded in Euclidean space. The shortcut operator ``.<,>`` can be used to specify the coordinates. INPUT: - ``dim`` -- (optional) the dimension of the sphere; if not specified, equals to the number of coordinate names - ``radius`` -- (default: ``1``) radius of the sphere - ``names`` -- (default: ``None``) name of the coordinates, automatically set by the shortcut operator - ``stereo2d`` -- (default: ``False``) if ``True``, defines only the stereographic charts, only implemented in 2d - ``stereo_lim`` -- (default: ``None``) parameter used to restrict the span of the stereographic charts, so that they don't cover the whole sphere; valid domain will be ``x**2 + y**2 < stereo_lim**2`` OUTPUT: - Riemannian manifold EXAMPLES:: sage: S.<th, ph> = manifolds.Sphere() sage: S 2-dimensional Riemannian submanifold S embedded in the Euclidean space E^3 sage: S.atlas() [Chart (S, (th, ph))] sage: S.metric().display() gamma = dth*dth + sin(th)^2 dph*dph sage: S = manifolds.Sphere(2, stereo2d=True) # long time sage: S # long time 2-dimensional Riemannian submanifold S embedded in the Euclidean space E^3 sage: S.metric().display() # long time gamma = 4/(x^4 + y^4 + 2*(x^2 + 1)*y^2 + 2*x^2 + 1) dx*dx + 4/(x^4 + y^4 + 2*(x^2 + 1)*y^2 + 2*x^2 + 1) dy*dy """ from sage.functions.trig import cos, sin, atan, atan2 from sage.functions.other import sqrt from sage.symbolic.constants import pi from sage.misc.misc_c import prod from sage.manifolds.manifold import Manifold from sage.manifolds.differentiable.euclidean import EuclideanSpace if dim is None: if names is None: raise ValueError("either the names or the dimension must be specified") dim = len(names) else: if names is not None and dim != len(names): raise ValueError("the number of coordinates does not match the dimension") if stereo2d: if dim != 2: raise NotImplementedError("stereographic charts only " "implemented for 2d spheres") E = EuclideanSpace(3, names=("X", "Y", "Z")) S2 = Manifold(dim, 'S', ambient=E, structure='Riemannian') U = S2.open_subset('U') V = S2.open_subset('V') stereoN = U.chart(names=("x", "y")) x, y = stereoN[:] stereoS = V.chart(names=("xp", "yp")) xp, yp = stereoS[:] if stereo_lim is not None: stereoN.add_restrictions(x**2+y**2 < stereo_lim**2) stereoS.add_restrictions(xp**2+yp**2 < stereo_lim**2) stereoN_to_S = stereoN.transition_map(stereoS, (x / (x**2 + y**2), y / (x**2 + y**2)), intersection_name='W', restrictions1=x**2 + y**2 != 0, restrictions2=xp**2+yp**2!=0) stereoN_to_S.set_inverse(xp / (xp**2 + yp**2), yp / (xp**2 + yp**2), check=False) W = U.intersection(V) stereoN_W = stereoN.restrict(W) stereoS_W = stereoS.restrict(W) A = W.open_subset('A', coord_def={stereoN_W: (y != 0, x < 0), stereoS_W: (yp != 0, xp < 0)}) stereoN_A = stereoN_W.restrict(A) if names is None: names = tuple(["phi_{}:(0,pi)".format(i) for i in range(dim - 1)] + ["phi_{}:(-pi,pi):periodic".format(dim - 1)]) else: names = tuple([names[i] + ":(0,pi)" for i in range(dim - 1)] + [names[dim - 1] + ":(-pi,pi):periodic"]) spher = A.chart(names=names) th, ph = spher[:] spher_to_stereoN = spher.transition_map(stereoN_A, (sin(th)*cos(ph) / (1-cos(th)), sin(th)*sin(ph) / (1-cos(th)))) spher_to_stereoN.set_inverse(2*atan(1/sqrt(x**2+y**2)), atan2(-y, -x)+pi, check=False) stereoN_to_S_A = stereoN_to_S.restrict(A) stereoN_to_S_A * spher_to_stereoN # generates spher_to_stereoS stereoS_to_N_A = stereoN_to_S.inverse().restrict(A) spher_to_stereoN.inverse() * stereoS_to_N_A # generates stereoS_to_spher coordfunc1 = [sin(th)*cos(ph), sin(th)*sin(ph), cos(th)] coordfunc2 = [2*x/(1+x**2+y**2), 2*y/(1+x**2+y**2), (x**2+y**2-1)/(1+x**2+y**2)] coordfunc3 = [2*xp/(1+xp**2+yp**2), 2*yp/(1+xp**2+yp**2),(1-xp**2-yp**2)/(1+xp**2+yp**2)] imm = S2.diff_map(E, {(spher, E.default_chart()): coordfunc1, (stereoN, E.default_chart()): coordfunc2, (stereoS, E.default_chart()): coordfunc3}) S2.set_embedding(imm) S2.induced_metric() return S2 if dim != 2: raise NotImplementedError("only implemented for 2 dimensional spheres") E = EuclideanSpace(3, symbols='X Y Z') M = Manifold(dim, 'S', ambient=E, structure='Riemannian') if names is None: names = tuple(["phi_{}:(0,pi)".format(i) for i in range(dim-1)] + ["phi_{}:(-pi,pi):periodic".format(dim-1)]) else: names = tuple([names[i]+":(0,pi)"for i in range(dim - 1)] + [names[dim-1]+":(-pi,pi):periodic"]) C = M.chart(names=names) M._first_ngens = C._first_ngens phi = M._first_ngens(dim)[:] coordfunc = ([radius * prod(sin(phi[j]) for j in range(dim))] + [radius * cos(phi[i]) * prod(sin(phi[j]) for j in range(i)) for i in range(dim)]) imm = M.diff_map(E, coordfunc) M.set_embedding(imm) M.induced_metric() return M
def val(x): if x == -1: return 2 else: return base_ring(2 * cos(pi / x))
def barycentric_projection_matrix(n, angle=0): r""" Returns a family of `n+1` vectors evenly spaced in a real vector space of dimension `n` Those vectors are of norm `1`, the scalar product between any two vector is `1/n`, thus the distance between two tips is constant. The family is built recursively and uniquely determined by the following property: the last vector is `(0,\dots,0,-1)`, and the projection of the first `n` vectors in dimension `n-1`, after appropriate rescaling to norm `1`, retrieves the family for `n-1`. OUTPUT: A matrix with `n+1` columns of height `n` with rational or symbolic coefficients. EXAMPLES: One vector in dimension `0`:: sage: from sage.combinat.root_system.root_lattice_realizations import barycentric_projection_matrix sage: m = barycentric_projection_matrix(0); m [] sage: matrix(QQ,0,1).nrows() 0 sage: matrix(QQ,0,1).ncols() 1 Two vectors in dimension 1:: sage: barycentric_projection_matrix(1) [ 1 -1] Three vectors in dimension 2:: sage: barycentric_projection_matrix(2) [ 1/2*sqrt(3) -1/2*sqrt(3) 0] [ 1/2 1/2 -1] Four vectors in dimension 3:: sage: m = barycentric_projection_matrix(3); m [ 1/3*sqrt(3)*sqrt(2) -1/3*sqrt(3)*sqrt(2) 0 0] [ 1/3*sqrt(2) 1/3*sqrt(2) -2/3*sqrt(2) 0] [ 1/3 1/3 1/3 -1] The columns give four vectors that sum up to zero:: sage: sum(m.columns()) (0, 0, 0) and have regular mutual angles:: sage: m.transpose()*m [ 1 -1/3 -1/3 -1/3] [-1/3 1 -1/3 -1/3] [-1/3 -1/3 1 -1/3] [-1/3 -1/3 -1/3 1] Here is a plot of them:: sage: sum(arrow((0,0,0),x) for x in m.columns()) For 2D drawings of root systems, it is desirable to rotate the result to match with the usual conventions:: sage: barycentric_projection_matrix(2, angle=2*pi/3) [ 1/2 -1 1/2] [ 1/2*sqrt(3) 0 -1/2*sqrt(3)] TESTS:: sage: for n in range(1, 7): ... m = barycentric_projection_matrix(n) ... assert sum(m.columns()).is_zero() ... assert matrix(QQ, n+1,n+1, lambda i,j: 1 if i==j else -1/n) == m.transpose()*m """ from sage.matrix.constructor import matrix from sage.functions.other import sqrt n = ZZ(n) if n == 0: return matrix(QQ, 0, 1) a = 1 / n b = sqrt(1 - a**2) result = b * barycentric_projection_matrix(n - 1) result = result.augment(vector([0] * (n - 1))) result = result.stack(matrix([[a] * n + [-1]])) assert sum(result.columns()).is_zero() if angle and n == 2: from sage.functions.trig import sin from sage.functions.trig import cos rotation = matrix([[sin(angle), cos(angle)], [-cos(angle), sin(angle)]]) result = rotation * result result.set_immutable() return result
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 plot(self, **kwds): r""" Plot the initial triangulation associated to ``self``. INPUT: - ``radius`` - the radius of the disk; by default the length of the circle is the number of vertices - ``points_color`` - the color of the vertices; default 'black' - ``points_size`` - the size of the vertices; default 7 - ``triangulation_color`` - the color of the arcs; default 'black' - ``triangulation_thickness`` - the thickness of the arcs; default 0.5 - ``shading_color`` - the color of the shading used on neuter intervals; default 'lightgray' - ``reflections_color`` - the color of the reflection axes; default 'blue' - ``reflections_thickness`` - the thickness of the reflection axes; default 1 EXAMPLES:: sage: Y = SineGordonYsystem('A',(6,4,3)); sage: Y.plot() # not tested """ # Set up plotting options if 'radius' in kwds: radius = kwds['radius'] else: radius = ceil(self.r() / (2 * pi)) points_opts = {} if 'points_color' in kwds: points_opts['color'] = kwds['points_color'] else: points_opts['color'] = 'black' if 'points_size' in kwds: points_opts['size'] = kwds['points_size'] else: points_opts['size'] = 7 triangulation_opts = {} if 'triangulation_color' in kwds: triangulation_opts['color'] = kwds['triangulation_color'] else: triangulation_opts['color'] = 'black' if 'triangulation_thickness' in kwds: triangulation_opts['thickness'] = kwds['triangulation_thickness'] else: triangulation_opts['thickness'] = 0.5 shading_opts = {} if 'shading_color' in kwds: shading_opts['color'] = kwds['shading_color'] else: shading_opts['color'] = 'lightgray' reflections_opts = {} if 'reflections_color' in kwds: reflections_opts['color'] = kwds['reflections_color'] else: reflections_opts['color'] = 'blue' if 'reflections_thickness' in kwds: reflections_opts['thickness'] = kwds['reflections_thickness'] else: reflections_opts['thickness'] = 1 # Helper functions def triangle(x): (a, b) = sorted(x[:2]) for p in self.vertices(): if (p, a) in self.triangulation() or (a, p) in self.triangulation(): if (p, b) in self.triangulation() or (b, p) in self.triangulation(): if p < a or p > b: return sorted((a, b, p)) def plot_arc(radius, p, q, **opts): # plot the arc from p to q differently depending on the type of self p = ZZ(p) q = ZZ(q) t = var('t') if p - q in [1, -1]: def f(t): return (radius * cos(t), radius * sin(t)) (p, q) = sorted([p, q]) angle_p = vertex_to_angle(p) angle_q = vertex_to_angle(q) return parametric_plot(f(t), (t, angle_q, angle_p), **opts) if self.type() == 'A': angle_p = vertex_to_angle(p) angle_q = vertex_to_angle(q) if angle_p < angle_q: angle_p += 2 * pi internal_angle = angle_p - angle_q if internal_angle > pi: (angle_p, angle_q) = (angle_q + 2 * pi, angle_p) internal_angle = angle_p - angle_q angle_center = (angle_p+angle_q) / 2 hypotenuse = radius / cos(internal_angle / 2) radius_arc = hypotenuse * sin(internal_angle / 2) center = (hypotenuse * cos(angle_center), hypotenuse * sin(angle_center)) center_angle_p = angle_p + pi / 2 center_angle_q = angle_q + 3 * pi / 2 def f(t): return (radius_arc * cos(t) + center[0], radius_arc * sin(t) + center[1]) return parametric_plot(f(t), (t, center_angle_p, center_angle_q), **opts) elif self.type() == 'D': if p >= q: q += self.r() px = -2 * pi * p / self.r() + pi / 2 qx = -2 * pi * q / self.r() + pi / 2 arc_radius = (px - qx) / 2 arc_center = qx + arc_radius def f(t): return exp(I * ((cos(t) + I * sin(t)) * arc_radius + arc_center)) * radius return parametric_plot((real_part(f(t)), imag_part(f(t))), (t, 0, pi), **opts) def vertex_to_angle(v): # v==0 corresponds to pi/2 return -2 * pi * RR(v) / self.r() + 5 * pi / 2 # Begin plotting P = Graphics() # Shade neuter intervals neuter_intervals = [x for x in flatten(self.intervals()[:-1], max_level=1) if x[2] in ["NR", "NL"]] shaded_triangles = map(triangle, neuter_intervals) for (p, q, r) in shaded_triangles: points = list(plot_arc(radius, p, q)[0]) points += list(plot_arc(radius, q, r)[0]) points += list(reversed(plot_arc(radius, p, r)[0])) P += polygon2d(points, **shading_opts) # Disk boundary P += circle((0, 0), radius, **triangulation_opts) # Triangulation for (p, q) in self.triangulation(): P += plot_arc(radius, p, q, **triangulation_opts) if self.type() == 'D': s = radius / 50.0 P += polygon2d([(s, 5 * s), (s, 7 * s), (3 * s, 5 * s), (3 * s, 7 * s)], color=triangulation_opts['color']) P += bezier_path([[(0, 0), (2 * s, 1 * s), (2 * s, 6 * s)], [(2 * s, 10 * s), (s, 20 * s)], [(0, 30 * s), (0, radius)]], **triangulation_opts) P += bezier_path([[(0, 0), (-2 * s, 1 * s), (-2 * s, 6 * s)], [(-2 * s, 10 * s), (-s, 20 * s)], [(0, 30 * s), (0, radius)]], **triangulation_opts) P += point((0, 0), zorder=len(P), **points_opts) # Vertices v_points = {x: (radius * cos(vertex_to_angle(x)), radius * sin(vertex_to_angle(x))) for x in self.vertices()} for v in v_points: P += point(v_points[v], zorder=len(P), **points_opts) # Reflection axes P += line([(0, 1.1 * radius), (0, -1.1 * radius)], zorder=len(P), **reflections_opts) axis_angle = vertex_to_angle(-0.5 * (self.rk() + (1, 1))[1]) (a, b) = (1.1 * radius * cos(axis_angle), 1.1 * radius * sin(axis_angle)) P += line([(a, b), (-a, -b)], zorder=len(P), **reflections_opts) # Wrap up P.set_aspect_ratio(1) P.axes(False) return P
def f(t): return (radius * cos(t), radius * sin(t))
def f(t): return (radius_arc * cos(t) + center[0], radius_arc * sin(t) + center[1])
def val(x): if x > -1: return -R(cos(pi / SR(x))) else: return R(x)
def _geodesic_between_two_points_d(x1, y1, x2, y2, z0=I): r""" Geodesic path between two points represented in the unit disc by the map w = (z-I)/(z+I) INPUTS: - ''(x1,y1)'' -- starting point (0<y1<=infinity) - ''(x2,y2)'' -- ending point (0<y2<=infinity) - ''z0'' -- (default I) the point in the upper corresponding to the point 0 in the disc. I.e. the transform is w -> (z-I)/(z+I) OUTPUT: - ''ca'' -- a polygonal approximation of a circular arc centered at c and radius r, starting at t0 and ending at t1 EXAMPLES:: sage: l=_geodesic_between_two_points_d(0.1,0.2,0.0,0.5) """ pi = RR.pi() from sage.plot.plot import line from sage.functions.trig import (cos, sin) # First compute the points if (y1 < 0 or y2 < 0): raise ValueError, "Need points in the upper half-plane! Got y1=%s, y2=%s" % ( y1, y2) if (y1 == infinity): P1 = CC(1) else: P1 = CC((x1 + I * y1 - z0) / (x1 + I * y1 - z0.conjugate())) if (y2 == infinity): P2 = CC(1) else: P2 = CC((x2 + I * y2 - z0) / (x2 + I * y2 - z0.conjugate())) # First find the endpoints of the completed geodesic in D if (x1 == x2): a = CC((x1 - z0) / (x1 - z0.conjugate())) b = CC(1) else: c = RR(y1**2 - y2**2 + x1**2 - x2**2) / RR(2 * (x1 - x2)) r = RR(sqrt(y1**2 + (x1 - c)**2)) a = c - r b = c + r a = CC((a - z0) / (a - z0.conjugate())) b = CC((b - z0) / (b - z0.conjugate())) if (abs(a + b) < 1E-10): # On a diagonal return line([[P1.real(), P1.imag()], [P2.real(), P2.imag()]]) th_a = a.argument() th_b = b.argument() # Compute the center of the circle in the disc model if (min(abs(b - 1), abs(b + 1)) < 1E-10 and min(abs(a - 1), abs(a + 1)) > 1E-10): c = b + I * (1 - b * cos(th_a)) / sin(th_a) elif (min(abs(b - 1), abs(b + 1)) > 1E-10 and min(abs(a - 1), abs(a + 1)) < 1E-10): c = a + I * (1 - a * cos(th_b)) / RR(sin(th_b)) else: cx = (sin(th_b) - sin(th_a)) / sin(th_b - th_a) c = cx + I * (1 - cx * cos(th_b)) / RR(sin(th_b)) # First find the endpoints of the completed geodesic r = abs(c - a) t1 = CC(P1 - c).argument() t2 = CC(P2 - c).argument() #print "t1,t2=",t1,t2 return _circ_arc(t1, t2, c, r)
def __init__(self, coxeter_matrix, base_ring, index_set): """ Initialize ``self``. EXAMPLES:: sage: W = CoxeterGroup([[1,3,2],[3,1,3],[2,3,1]]) sage: TestSuite(W).run() # long time sage: W = CoxeterGroup([[1,3,2],[3,1,4],[2,4,1]], base_ring=QQbar) sage: TestSuite(W).run() # long time sage: W = CoxeterGroup([[1,3,2],[3,1,6],[2,6,1]]) sage: TestSuite(W).run(max_runs=30) # long time sage: W = CoxeterGroup([[1,3,2],[3,1,-1],[2,-1,1]]) sage: TestSuite(W).run(max_runs=30) # long time We check that :trac:`16630` is fixed:: sage: CoxeterGroup(['D',4], base_ring=QQ).category() Category of finite coxeter groups sage: CoxeterGroup(['H',4], base_ring=QQbar).category() Category of finite coxeter groups sage: F = CoxeterGroups().Finite() sage: all(CoxeterGroup([letter,i]) in F ....: for i in range(2,5) for letter in ['A','B','D']) True sage: all(CoxeterGroup(['E',i]) in F for i in range(6,9)) True sage: CoxeterGroup(['F',4]).category() Category of finite coxeter groups sage: CoxeterGroup(['G',2]).category() Category of finite coxeter groups sage: all(CoxeterGroup(['H',i]) in F for i in range(3,5)) True sage: all(CoxeterGroup(['I',i]) in F for i in range(2,5)) True """ self._matrix = coxeter_matrix n = coxeter_matrix.rank() # Compute the matrix with entries `2 \cos( \pi / m_{ij} )`. MS = MatrixSpace(base_ring, n, sparse=True) MC = MS._get_matrix_class() # FIXME: Hack because there is no ZZ \cup \{ \infty \}: -1 represents \infty if base_ring is UniversalCyclotomicField(): val = lambda x: base_ring.gen(2*x) + ~base_ring.gen(2*x) if x != -1 else base_ring(2) else: from sage.functions.trig import cos from sage.symbolic.constants import pi val = lambda x: base_ring(2*cos(pi / x)) if x != -1 else base_ring(2) gens = [MS.one() + MC(MS, entries={(i, j): val(coxeter_matrix[index_set[i], index_set[j]]) for j in range(n)}, coerce=True, copy=True) for i in range(n)] category = CoxeterGroups() # Now we shall see if the group is finite, and, if so, refine # the category to ``category.Finite()``. Otherwise the group is # infinite and we refine the category to ``category.Infinite()``. if self._matrix.is_finite(): category = category.Finite() else: category = category.Infinite() FinitelyGeneratedMatrixGroup_generic.__init__(self, ZZ(n), base_ring, gens, category=category)
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 plot(self, show_box=False, colors=["white", "lightgray", "darkgray"]): r""" Return a plot of ``self``. INPUT: - ``show_box`` -- boolean (default: ``False``); if ``True``, also shows the visible tiles on the `xy`-, `yz`-, `zx`-planes - ``colors`` -- (default: ``["white", "lightgray", "darkgray"]``) list ``[A, B, C]`` of 3 strings representing colors EXAMPLES:: sage: PP = PlanePartition([[4,3,3,1],[2,1,1],[1,1]]) sage: PP.plot() Graphics object consisting of 27 graphics primitives """ from sage.functions.trig import cos, sin from sage.plot.polygon import polygon from sage.symbolic.constants import pi from sage.plot.plot import plot Uside = [[0, 0], [cos(-pi / 6), sin(-pi / 6)], [0, -1], [cos(7 * pi / 6), sin(7 * pi / 6)]] Lside = [[0, 0], [cos(-pi / 6), sin(-pi / 6)], [cos(pi / 6), sin(pi / 6)], [0, 1]] Rside = [[0, 0], [0, 1], [cos(5 * pi / 6), sin(5 * pi / 6)], [cos(7 * pi / 6), sin(7 * pi / 6)]] Xdir = [cos(7 * pi / 6), sin(7 * pi / 6)] Ydir = [cos(-pi / 6), sin(-pi / 6)] Zdir = [0, 1] def move(side, i, j, k): return [[ P[0] + i * Xdir[0] + j * Ydir[0] + k * Zdir[0], P[1] + i * Xdir[1] + j * Ydir[1] + k * Zdir[1] ] for P in side] def add_topside(i, j, k): return polygon(move(Uside, i, j, k), edgecolor="black", color=colors[0]) def add_leftside(i, j, k): return polygon(move(Lside, i, j, k), edgecolor="black", color=colors[1]) def add_rightside(i, j, k): return polygon(move(Rside, i, j, k), edgecolor="black", color=colors[2]) TP = plot([]) for r in range(len(self.z_tableau())): for c in range(len(self.z_tableau()[r])): if self.z_tableau()[r][c] > 0 or show_box: TP += add_topside(r, c, self.z_tableau()[r][c]) for r in range(len(self.y_tableau())): for c in range(len(self.y_tableau()[r])): if self.y_tableau()[r][c] > 0 or show_box: TP += add_rightside(c, self.y_tableau()[r][c], r) for r in range(len(self.x_tableau())): for c in range(len(self.x_tableau()[r])): if self.x_tableau()[r][c] > 0 or show_box: TP += add_leftside(self.x_tableau()[r][c], r, c) TP.axes(show=False) return TP
def __init__(self, sides, i_angle, center, options): """ Initialize HyperbolicRegularPolygon. EXAMPLES:: sage: from sage.plot.hyperbolic_regular_polygon import HyperbolicRegularPolygon sage: print(HyperbolicRegularPolygon(5,pi/2,I, {})) Hyperbolic regular polygon (sides=5, i_angle=1/2*pi, center=1.00000000000000*I) """ self.center = CC(center) if self.center.imag() <= 0: raise ValueError( "center: %s is not a valid point in the upper half plane model of the hyperbolic plane" % (self.center)) if sides < 3: raise ValueError( "degenerated polygons (sides<=2) are not supported") if i_angle <= 0 or i_angle >= pi: raise ValueError("interior angle %s must be in (0, pi) interval" % (i_angle)) if pi * (sides - 2) - sides * i_angle <= 0: raise ValueError( "there exists no hyperbolic regular compact polygon, for sides=%s the interior angle must be less than %s" % (sides, pi * (sides - 2) / sides)) self.sides = sides self.i_angle = i_angle beta = 2 * pi / self.sides # compute the rotation angle to be used ahead alpha = self.i_angle / Integer(2) I = CC(0, 1) # compute using cosine theorem the radius of the circumscribed circle # using the triangle formed by the radius and the three known angles r = arccosh(cot(alpha) * (1 + cos(beta)) / sin(beta)) # The first point will be always on the imaginary axis limited # to 8 digits for efficiency in the subsequent calculations. z_0 = [I * (e**r).n(digits=8)] # Compute the dilation isometry used to move the center # from I to the imaginary part of the given center. scale = self.center.imag() # Compute the parabolic isometry to move the center to the # real part of the given center. h_disp = self.center.real() d_z_k = [z_0[0] * scale + h_disp ] #d_k has the points for the polygon in the given center z_k = z_0 #z_k has the Re(z)>0 vertices for the I centered polygon r_z_k = [] #r_z_k has the Re(z)<0 vertices if is_odd(self.sides): vert = (self.sides - 1) // 2 else: vert = self.sides // 2 - 1 for k in range(vert): # Compute with 8 digits to accelerate calculations new_z_k = self._i_rotation(z_k[-1], beta).n(digits=8) z_k = z_k + [new_z_k] d_z_k = d_z_k + [new_z_k * scale + h_disp] r_z_k = [-(new_z_k).conjugate() * scale + h_disp] + r_z_k if is_odd(self.sides): HyperbolicPolygon.__init__(self, d_z_k + r_z_k, options) else: z_opo = [I * (e**(-r)).n(digits=8) * scale + h_disp] HyperbolicPolygon.__init__(self, d_z_k + z_opo + r_z_k, options)
def barycentric_projection_matrix(n, angle=0): r""" Returns a family of `n+1` vectors evenly spaced in a real vector space of dimension `n` Those vectors are of norm `1`, the scalar product between any two vector is `1/n`, thus the distance between two tips is constant. The family is built recursively and uniquely determined by the following property: the last vector is `(0,\dots,0,-1)`, and the projection of the first `n` vectors in dimension `n-1`, after appropriate rescaling to norm `1`, retrieves the family for `n-1`. OUTPUT: A matrix with `n+1` columns of height `n` with rational or symbolic coefficients. EXAMPLES: One vector in dimension `0`:: sage: from sage.combinat.root_system.root_lattice_realizations import barycentric_projection_matrix sage: m = barycentric_projection_matrix(0); m [] sage: matrix(QQ,0,1).nrows() 0 sage: matrix(QQ,0,1).ncols() 1 Two vectors in dimension 1:: sage: barycentric_projection_matrix(1) [ 1 -1] Three vectors in dimension 2:: sage: barycentric_projection_matrix(2) [ 1/2*sqrt(3) -1/2*sqrt(3) 0] [ 1/2 1/2 -1] Four vectors in dimension 3:: sage: m = barycentric_projection_matrix(3); m [ 1/3*sqrt(3)*sqrt(2) -1/3*sqrt(3)*sqrt(2) 0 0] [ 1/3*sqrt(2) 1/3*sqrt(2) -2/3*sqrt(2) 0] [ 1/3 1/3 1/3 -1] The columns give four vectors that sum up to zero:: sage: sum(m.columns()) (0, 0, 0) and have regular mutual angles:: sage: m.transpose()*m [ 1 -1/3 -1/3 -1/3] [-1/3 1 -1/3 -1/3] [-1/3 -1/3 1 -1/3] [-1/3 -1/3 -1/3 1] Here is a plot of them:: sage: sum(arrow((0,0,0),x) for x in m.columns()) For 2D drawings of root systems, it is desirable to rotate the result to match with the usual conventions:: sage: barycentric_projection_matrix(2, angle=2*pi/3) [ 1/2 -1 1/2] [ 1/2*sqrt(3) 0 -1/2*sqrt(3)] TESTS:: sage: for n in range(1, 7): ... m = barycentric_projection_matrix(n) ... assert sum(m.columns()).is_zero() ... assert matrix(QQ, n+1,n+1, lambda i,j: 1 if i==j else -1/n) == m.transpose()*m """ from sage.matrix.constructor import matrix from sage.functions.other import sqrt n = ZZ(n) if n == 0: return matrix(QQ, 0, 1) a = 1/n b = sqrt(1-a**2) result = b * barycentric_projection_matrix(n-1) result = result.augment(vector([0]*(n-1))) result = result.stack(matrix([[a]*n+[-1]])) assert sum(result.columns()).is_zero() if angle and n == 2: from sage.functions.trig import sin from sage.functions.trig import cos rotation = matrix([[sin(angle), cos(angle)],[-cos(angle), sin(angle)]]) result = rotation * result result.set_immutable() return result
def subexpressions_list(f, pars=None): """ Construct the lists with the intermediate steps on the evaluation of the function. INPUT: - ``f`` -- a symbolic function of several components. - ``pars`` -- a list of the parameters that appear in the function this should be the symbolic constants that appear in f but are not arguments. OUTPUT: - a list of the intermediate subexpressions that appear in the evaluation of f. - a list with the operations used to construct each of the subexpressions. each element of this list is a tuple, formed by a string describing the operation made, and the operands. For the trigonometric functions, some extra expressions will be added. These extra expressions will be used later to compute their derivatives. EXAMPLES:: sage: from sage.interfaces.tides import subexpressions_list sage: var('x,y') (x, y) sage: f(x,y) = [x^2+y, cos(x)/log(y)] sage: subexpressions_list(f) ([x^2, x^2 + y, sin(x), cos(x), log(y), cos(x)/log(y)], [('mul', x, x), ('add', y, x^2), ('sin', x), ('cos', x), ('log', y), ('div', log(y), cos(x))]) :: sage: f(a)=[cos(a), arctan(a)] sage: from sage.interfaces.tides import subexpressions_list sage: subexpressions_list(f) ([sin(a), cos(a), a^2, a^2 + 1, arctan(a)], [('sin', a), ('cos', a), ('mul', a, a), ('add', 1, a^2), ('atan', a)]) :: sage: from sage.interfaces.tides import subexpressions_list sage: var('s,b,r') (s, b, r) sage: f(t,x,y,z)= [s*(y-x),x*(r-z)-y,x*y-b*z] sage: subexpressions_list(f,[s,b,r]) ([-y, x - y, s*(x - y), -s*(x - y), -z, r - z, (r - z)*x, -y, (r - z)*x - y, x*y, b*z, -b*z, x*y - b*z], [('mul', -1, y), ('add', -y, x), ('mul', x - y, s), ('mul', -1, s*(x - y)), ('mul', -1, z), ('add', -z, r), ('mul', x, r - z), ('mul', -1, y), ('add', -y, (r - z)*x), ('mul', y, x), ('mul', z, b), ('mul', -1, b*z), ('add', -b*z, x*y)]) :: sage: var('x, y') (x, y) sage: f(x,y)=[exp(x^2+sin(y))] sage: from sage.interfaces.tides import * sage: subexpressions_list(f) ([x^2, sin(y), cos(y), x^2 + sin(y), e^(x^2 + sin(y))], [('mul', x, x), ('sin', y), ('cos', y), ('add', sin(y), x^2), ('exp', x^2 + sin(y))]) """ from sage.functions.trig import sin, cos, arcsin, arctan, arccos variables = f[0].arguments() if not pars: parameters = [] else: parameters = pars varpar = list(parameters) + list(variables) F = symbolic_expression([i(*variables) for i in f]).function(*varpar) lis = flatten([fast_callable(i,vars=varpar).op_list() for i in F], max_level=1) stack = [] const =[] stackcomp=[] detail=[] for i in lis: if i[0] == 'load_arg': stack.append(varpar[i[1]]) elif i[0] == 'ipow': if i[1] in NN: basis = stack[-1] for j in range(i[1]-1): a=stack.pop(-1) detail.append(('mul', a, basis)) stack.append(a*basis) stackcomp.append(stack[-1]) else: detail.append(('pow',stack[-1],i[1])) stack[-1]=stack[-1]**i[1] stackcomp.append(stack[-1]) elif i[0] == 'load_const': const.append(i[1]) stack.append(i[1]) elif i == 'mul': a=stack.pop(-1) b=stack.pop(-1) detail.append(('mul', a, b)) stack.append(a*b) stackcomp.append(stack[-1]) elif i == 'div': a=stack.pop(-1) b=stack.pop(-1) detail.append(('div', a, b)) stack.append(b/a) stackcomp.append(stack[-1]) elif i == 'add': a=stack.pop(-1) b=stack.pop(-1) detail.append(('add',a,b)) stack.append(a+b) stackcomp.append(stack[-1]) elif i == 'pow': a=stack.pop(-1) b=stack.pop(-1) detail.append(('pow', b, a)) stack.append(b**a) stackcomp.append(stack[-1]) elif i[0] == 'py_call' and str(i[1])=='log': a=stack.pop(-1) detail.append(('log', a)) stack.append(log(a)) stackcomp.append(stack[-1]) elif i[0] == 'py_call' and str(i[1])=='exp': a=stack.pop(-1) detail.append(('exp', a)) stack.append(exp(a)) stackcomp.append(stack[-1]) elif i[0] == 'py_call' and str(i[1])=='sin': a=stack.pop(-1) detail.append(('sin', a)) detail.append(('cos', a)) stackcomp.append(sin(a)) stackcomp.append(cos(a)) stack.append(sin(a)) elif i[0] == 'py_call' and str(i[1])=='cos': a=stack.pop(-1) detail.append(('sin', a)) detail.append(('cos', a)) stackcomp.append(sin(a)) stackcomp.append(cos(a)) stack.append(cos(a)) elif i[0] == 'py_call' and str(i[1])=='tan': a=stack.pop(-1) b = sin(a) c = cos(a) detail.append(('sin', a)) detail.append(('cos', a)) detail.append(('div', b, c)) stackcomp.append(b) stackcomp.append(c) stackcomp.append(b/c) stack.append(b/c) elif i[0] == 'py_call' and str(i[1])=='arctan': a=stack.pop(-1) detail.append(('mul', a, a)) detail.append(('add', 1, a*a)) detail.append(('atan', a)) stackcomp.append(a*a) stackcomp.append(1+a*a) stackcomp.append(arctan(a)) stack.append(arctan(a)) elif i[0] == 'py_call' and str(i[1])=='arcsin': a=stack.pop(-1) detail.append(('mul', a, a)) detail.append(('mul', -1, a*a)) detail.append(('add', 1, -a*a)) detail.append(('pow', 1- a*a, 0.5)) detail.append(('asin', a)) stackcomp.append(a*a) stackcomp.append(-a*a) stackcomp.append(1-a*a) stackcomp.append(sqrt(1-a*a)) stackcomp.append(arcsin(a)) stack.append(arcsin(a)) elif i[0] == 'py_call' and str(i[1])=='arccos': a=stack.pop(-1) detail.append(('mul', a, a)) detail.append(('mul', -1, a*a)) detail.append(('add', 1, -a*a)) detail.append(('pow', 1- a*a, 0.5)) detail.append(('mul', -1, sqrt(1-a*a))) detail.append(('acos', a)) stackcomp.append(a*a) stackcomp.append(-a*a) stackcomp.append(1-a*a) stackcomp.append(sqrt(1-a*a)) stackcomp.append(-sqrt(1-a*a)) stackcomp.append(arccos(a)) stack.append(arccos(a)) elif i[0] == 'py_call' and 'sqrt' in str(i[1]): a=stack.pop(-1) detail.append(('pow', a, 0.5)) stackcomp.append(sqrt(a)) stack.append(sqrt(a)) elif i == 'neg': a = stack.pop(-1) detail.append(('mul', -1, a)) stack.append(-a) stackcomp.append(-a) return stackcomp,detail
def Kerr(m=1, a=0, coordinates="BL", names=None): """ Generate a Kerr spacetime. A Kerr spacetime is a 4 dimensional manifold describing a rotating black hole. Two coordinate systems are implemented: Boyer-Lindquist and Kerr (3+1 version). The shortcut operator ``.<,>`` can be used to specify the coordinates. INPUT: - ``m`` -- (default: ``1``) mass of the black hole in natural units (`c=1`, `G=1`) - ``a`` -- (default: ``0``) angular momentum in natural units; if set to ``0``, the resulting spacetime corresponds to a Schwarzschild black hole - ``coordinates`` -- (default: ``"BL"``) either ``"BL"`` for Boyer-Lindquist coordinates or ``"Kerr"`` for Kerr coordinates (3+1 version) - ``names`` -- (default: ``None``) name of the coordinates, automatically set by the shortcut operator OUTPUT: - Lorentzian manifold EXAMPLES:: sage: m, a = var('m, a') sage: K = manifolds.Kerr(m, a) sage: K 4-dimensional Lorentzian manifold M sage: K.atlas() [Chart (M, (t, r, th, ph))] sage: K.metric().display() g = (2*m*r/(a^2*cos(th)^2 + r^2) - 1) dt⊗dt + 2*a*m*r*sin(th)^2/(a^2*cos(th)^2 + r^2) dt⊗dph + (a^2*cos(th)^2 + r^2)/(a^2 - 2*m*r + r^2) dr⊗dr + (a^2*cos(th)^2 + r^2) dth⊗dth + 2*a*m*r*sin(th)^2/(a^2*cos(th)^2 + r^2) dph⊗dt + (2*a^2*m*r*sin(th)^2/(a^2*cos(th)^2 + r^2) + a^2 + r^2)*sin(th)^2 dph⊗dph sage: K.<t, r, th, ph> = manifolds.Kerr() sage: K 4-dimensional Lorentzian manifold M sage: K.metric().display() g = (2/r - 1) dt⊗dt + r^2/(r^2 - 2*r) dr⊗dr + r^2 dth⊗dth + r^2*sin(th)^2 dph⊗dph sage: K.default_chart().coord_range() t: (-oo, +oo); r: (0, +oo); th: (0, pi); ph: [-pi, pi] (periodic) sage: m, a = var('m, a') sage: K.<t, r, th, ph> = manifolds.Kerr(m, a, coordinates="Kerr") sage: K 4-dimensional Lorentzian manifold M sage: K.atlas() [Chart (M, (t, r, th, ph))] sage: K.metric().display() g = (2*m*r/(a^2*cos(th)^2 + r^2) - 1) dt⊗dt + 2*m*r/(a^2*cos(th)^2 + r^2) dt⊗dr - 2*a*m*r*sin(th)^2/(a^2*cos(th)^2 + r^2) dt⊗dph + 2*m*r/(a^2*cos(th)^2 + r^2) dr⊗dt + (2*m*r/(a^2*cos(th)^2 + r^2) + 1) dr⊗dr - a*(2*m*r/(a^2*cos(th)^2 + r^2) + 1)*sin(th)^2 dr⊗dph + (a^2*cos(th)^2 + r^2) dth⊗dth - 2*a*m*r*sin(th)^2/(a^2*cos(th)^2 + r^2) dph⊗dt - a*(2*m*r/(a^2*cos(th)^2 + r^2) + 1)*sin(th)^2 dph⊗dr + (2*a^2*m*r*sin(th)^2/(a^2*cos(th)^2 + r^2) + a^2 + r^2)*sin(th)^2 dph⊗dph sage: K.default_chart().coord_range() t: (-oo, +oo); r: (0, +oo); th: (0, pi); ph: [-pi, pi] (periodic) """ from sage.misc.functional import sqrt from sage.functions.trig import cos, sin from sage.manifolds.manifold import Manifold M = Manifold(4, 'M', structure="Lorentzian") if coordinates == "Kerr": if names is None: names = (r't:(-oo,+oo)', r'r:(0,+oo)', r'th:(0,pi):\theta', r'ph:(-pi,pi):periodic:\phi') else: names = (names[0] + r':(-oo,+oo)', names[1] + r':(0,+oo)', names[2] + r':(0,pi):\theta', names[3] + r':(-pi,pi):periodic:\phi') C = M.chart(names=names) M._first_ngens = C._first_ngens g = M.metric('g') t, r, th, ph = C[:] rho = sqrt(r**2 + a**2 * cos(th)**2) g[0, 0], g[1, 1], g[2, 2], g[3, 3] = -(1-2*m*r/rho**2), 1+2*m*r/rho**2,\ rho**2, (r**2+a**2+2*a**2*m*r*sin(th)**2/rho**2)*sin(th)**2 g[0, 1] = 2 * m * r / rho**2 g[0, 3] = -2 * a * m * r / rho**2 * sin(th)**2 g[1, 3] = -a * sin(th)**2 * (1 + 2 * m * r / rho**2) return M if coordinates == "BL": if names is None: names = (r't:(-oo,+oo)', r'r:(0,+oo)', r'th:(0,pi):\theta', r'ph:(-pi,pi):periodic:\phi') else: names = (names[0] + r':(-oo,+oo)', names[1] + r':(0,+oo)', names[2] + r':(0,pi):\theta', names[3] + r':(-pi,pi):periodic:\phi') C = M.chart(names=names) M._first_ngens = C._first_ngens g = M.metric('g') t, r, th, ph = C[:] rho = sqrt(r**2 + a**2 * cos(th)**2) g[0, 0], g[1, 1], g[2, 2], g[3, 3] = -(1-2*m*r/rho**2), \ rho**2/(r**2-2*m*r+a**2), rho**2, \ (r**2+a**2+2*m*r*a**2/rho**2*sin(th)**2)*sin(th)**2 g[0, 3] = 2 * m * r * a * sin(th)**2 / rho**2 return M raise NotImplementedError("coordinates system not implemented, see help" " for details")
def _compute_init_vector(self, point, pt0, pr0, pth0, pph0, r_increase, th_increase, verbose): r""" Computes the initial 4-momentum vector `p` from the constants of motion """ BLchart = self._spacetime.boyer_lindquist_coordinates() basis = BLchart.frame().at(point) r, th = BLchart(point)[1:3] a, m = self._a, self._m r2 = r**2 a2 = a**2 rho2 = r2 + (a * cos(th))**2 Delta = r2 - 2 * m * r + a2 if pt0 is None: if (self._mu is not None and pr0 is not None and pth0 is not None and pph0 is not None): xxx = SR.var('xxx') v = self._spacetime.tangent_space(point)( (xxx, pr0, pth0, pph0), basis=basis) muv2 = -self._spacetime.metric().at(point)(v, v) muv2 = muv2.substitute(self._numerical_substitutions()) solutions = solve(muv2 == self._mu**2, xxx, solution_dict=True) if verbose: print("Solutions for p^t:") pretty_print(solutions) for sol in solutions: if sol[xxx] > 0: pt0 = sol[xxx] break else: # pt0 <= 0 might occur in the ergoregion pt0 = solutions[0][xxx] try: pt0 = RR(pt0) except TypeError: # pt0 contains some symbolic expression pass else: if self._E is None: raise ValueError("the constant E must be provided") if self._L is None: raise ValueError("the constant L must be provided") E, L = self._E, self._L pt0 = ((r2 + a2) / Delta * ((r2 + a2) * E - a * L) + a * (L - a * E * sin(th)**2)) / rho2 if pph0 is None: if self._E is None: raise ValueError("the constant E must be provided") if self._L is None: raise ValueError("the constant L must be provided") E, L = self._E, self._L pph0 = (L / sin(th)**2 - a * E + a / Delta * ((r2 + a2) * E - a * L)) / rho2 if pr0 is None: if self._E is None: raise ValueError("the constant E must be provided") if self._L is None: raise ValueError("the constant L must be provided") if self._mu is None: raise ValueError("the constant mu must be provided") if self._Q is None: raise ValueError("the constant Q must be provided") E, L, Q = self._E, self._L, self._Q mu2 = self._mu**2 E2_mu2 = E**2 - mu2 pr0 = sqrt((E2_mu2) * r**4 + 2 * m * mu2 * r**3 + (a2 * E2_mu2 - L**2 - Q) * r**2 + 2 * m * (Q + (L - a * E)**2) * r - a2 * Q) / rho2 if not r_increase: pr0 = -pr0 if pth0 is None: if self._E is None: raise ValueError("the constant E must be provided") if self._L is None: raise ValueError("the constant L must be provided") if self._mu is None: raise ValueError("the constant mu must be provided") if self._Q is None: raise ValueError("the constant Q must be provided") E2 = self._E**2 L2 = self._L**2 mu2 = self._mu**2 Q = self._Q pth0 = sqrt(Q + cos(th)**2 * (a2 * (E2 - mu2) - L2 / sin(th)**2)) / rho2 if not th_increase: pth0 = -pth0 return self._spacetime.tangent_space(point)((pt0, pr0, pth0, pph0), basis=basis, name='p')
def _init_spherical(self, names=None): r""" Construct the chart of spherical coordinates. TESTS: Spherical coordinates on a 2-sphere:: sage: S2 = manifolds.Sphere(2) sage: S2.spherical_coordinates() Chart (A, (theta, phi)) Spherical coordinates on a 1-sphere:: sage: S1 = manifolds.Sphere(1, coordinates='stereographic') sage: spher = S1.spherical_coordinates(); spher # create coords Chart (A, (phi,)) sage: S1.atlas() [Chart (S^1-{NP}, (y1,)), Chart (S^1-{SP}, (yp1,)), Chart (S^1-{NP,SP}, (y1,)), Chart (S^1-{NP,SP}, (yp1,)), Chart (A, (phi,)), Chart (A, (y1,)), Chart (A, (yp1,))] """ # speed-up via simplification method... self.set_simplify_function(lambda expr: expr.simplify_trig()) # get domain... A = self._spher_dom # initialize coordinates... n = self._dim if names: # add interval: names = tuple([x + ':(0,pi)' for x in names[:-1]] + [names[-1] + ':(-pi,pi):periodic']) else: if n == 1: names = ('phi:(-pi,pi):periodic', ) elif n == 2: names = ('theta:(0,pi)', 'phi:(-pi,pi):periodic') elif n == 3: names = ('chi:(0,pi)', 'theta:(0,pi)', 'phi:(-pi,pi):periodic') else: names = tuple(["phi_{}:(0,pi)".format(i) for i in range(1, n)] + ["phi_{}:(-pi,pi):periodic".format(n)]) spher = A.chart(names=names) coord = spher[:] # manage embedding... from sage.misc.misc_c import prod from sage.functions.trig import cos, sin R = self._radius coordfunc = [ R * cos(coord[n - 1]) * prod(sin(coord[i]) for i in range(n - 1)) ] coordfunc += [R * prod(sin(coord[i]) for i in range(n))] for k in reversed(range(n - 1)): c = R * cos(coord[k]) * prod(sin(coord[i]) for i in range(k)) coordfunc.append(c) cart = self._ambient.cartesian_coordinates() # shift coordinates to barycenter: coordfunc = self._shift_coords(coordfunc, s='+') # add expression to embedding: self._immersion.add_expr(spher, cart, coordfunc) self.clear_cache() # clear cache of extrinsic information # finish process... self._coordinates['spherical'] = [spher] # adapt other coordinates... if 'stereographic' in self._coordinates: self._transition_spher_stereo() # reset simplification method... self.set_simplify_function('default')
def revolution_plot3d(curve, trange, phirange=None, parallel_axis='z', axis=(0, 0), print_vector=False, show_curve=False, **kwds): """ Return a plot of a revolved curve. There are three ways to call this function: - ``revolution_plot3d(f,trange)`` where `f` is a function located in the `x z` plane. - ``revolution_plot3d((f_x,f_z),trange)`` where `(f_x,f_z)` is a parametric curve on the `x z` plane. - ``revolution_plot3d((f_x,f_y,f_z),trange)`` where `(f_x,f_y,f_z)` can be any parametric curve. INPUT: - ``curve`` - A curve to be revolved, specified as a function, a 2-tuple or a 3-tuple. - ``trange`` - A 3-tuple `(t,t_{\min},t_{\max})` where t is the independent variable of the curve. - ``phirange`` - A 2-tuple of the form `(\phi_{\min},\phi_{\max})`, (default `(0,\pi)`) that specifies the angle in which the curve is to be revolved. - ``parallel_axis`` - A string (Either 'x', 'y', or 'z') that specifies the coordinate axis parallel to the revolution axis. - ``axis`` - A 2-tuple that specifies the position of the revolution axis. If parallel is: - 'z' - then axis is the point in which the revolution axis intersects the `x y` plane. - 'x' - then axis is the point in which the revolution axis intersects the `y z` plane. - 'y' - then axis is the point in which the revolution axis intersects the `x z` plane. - ``print_vector`` - If True, the parametrization of the surface of revolution will be printed. - ``show_curve`` - If True, the curve will be displayed. EXAMPLES: Let's revolve a simple function around different axes:: sage: u = var('u') sage: f=u^2 sage: revolution_plot3d(f,(u,0,2),show_curve=True,opacity=0.7).show(aspect_ratio=(1,1,1)) If we move slightly the axis, we get a goblet-like surface:: sage: revolution_plot3d(f,(u,0,2),axis=(1,0.2),show_curve=True,opacity=0.5).show(aspect_ratio=(1,1,1)) A common problem in calculus books, find the volume within the following revolution solid:: sage: line=u sage: parabola=u^2 sage: sur1=revolution_plot3d(line,(u,0,1),opacity=0.5,rgbcolor=(1,0.5,0),show_curve=True,parallel_axis='x') sage: sur2=revolution_plot3d(parabola,(u,0,1),opacity=0.5,rgbcolor=(0,1,0),show_curve=True,parallel_axis='x') sage: (sur1+sur2).show() Now let's revolve a parametrically defined circle. We can play with the topology of the surface by changing the axis, an axis in `(0,0)` (as the previous one) will produce a sphere-like surface:: sage: u = var('u') sage: circle=(cos(u),sin(u)) sage: revolution_plot3d(circle,(u,0,2*pi),axis=(0,0),show_curve=True,opacity=0.5).show(aspect_ratio=(1,1,1)) An axis on `(0,y)` will produce a cylinder-like surface:: sage: revolution_plot3d(circle,(u,0,2*pi),axis=(0,2),show_curve=True,opacity=0.5).show(aspect_ratio=(1,1,1)) And any other axis will produce a torus-like surface:: sage: revolution_plot3d(circle,(u,0,2*pi),axis=(2,0),show_curve=True,opacity=0.5).show(aspect_ratio=(1,1,1)) Now, we can get another goblet-like surface by revolving a curve in 3d:: sage: u = var('u') sage: curve=(u,cos(4*u),u^2) sage: revolution_plot3d(curve,(u,0,2),show_curve=True,parallel_axis='z',axis=(1,.2),opacity=0.5).show(aspect_ratio=(1,1,1)) A curvy curve with only a quarter turn:: sage: u = var('u') sage: curve=(sin(3*u),.8*cos(4*u),cos(u)) sage: revolution_plot3d(curve,(u,0,pi),(0,pi/2),show_curve=True,parallel_axis='z',opacity=0.5).show(aspect_ratio=(1,1,1),frame=False) """ from sage.symbolic.ring import SR from sage.symbolic.constants import pi from sage.functions.other import sqrt from sage.functions.trig import sin from sage.functions.trig import cos from sage.functions.trig import atan2 if parallel_axis not in ['x', 'y', 'z']: raise ValueError("parallel_axis must be either 'x', 'y', or 'z'.") vart = trange[0] if str(vart) == 'phi': phi = SR.var('fi') else: phi = SR.var('phi') if phirange is None: #this if-else provides a phirange phirange = (phi, 0, 2 * pi) elif len(phirange) == 3: phi = phirange[0] pass else: phirange = (phi, phirange[0], phirange[1]) if isinstance(curve, tuple) or isinstance(curve, list): #this if-else provides a vector v to be plotted #if curve is a tuple or a list of length 2, it is interpreted as a parametric curve #in the x-z plane. #if it is of length 3 it is interpreted as a parametric curve in 3d space if len(curve) == 2: x = curve[0] y = 0 z = curve[1] elif len(curve) == 3: x = curve[0] y = curve[1] z = curve[2] else: x = vart y = 0 z = curve if parallel_axis == 'z': x0 = axis[0] y0 = axis[1] # (0,0) must be handled separately for the phase value phase = 0 if x0 != 0 or y0 != 0: phase = atan2((y - y0), (x - x0)) R = sqrt((x - x0)**2 + (y - y0)**2) v = (R * cos(phi + phase) + x0, R * sin(phi + phase) + y0, z) elif parallel_axis == 'x': y0 = axis[0] z0 = axis[1] # (0,0) must be handled separately for the phase value phase = 0 if z0 != 0 or y0 != 0: phase = atan2((z - z0), (y - y0)) R = sqrt((y - y0)**2 + (z - z0)**2) v = (x, R * cos(phi + phase) + y0, R * sin(phi + phase) + z0) elif parallel_axis == 'y': x0 = axis[0] z0 = axis[1] # (0,0) must be handled separately for the phase value phase = 0 if z0 != 0 or x0 != 0: phase = atan2((z - z0), (x - x0)) R = sqrt((x - x0)**2 + (z - z0)**2) v = (R * cos(phi + phase) + x0, y, R * sin(phi + phase) + z0) if print_vector: print(v) if show_curve: curveplot = parametric_plot3d((x, y, z), trange, thickness=2, rgbcolor=(1, 0, 0)) return parametric_plot3d(v, trange, phirange, **kwds) + curveplot return parametric_plot3d(v, trange, phirange, **kwds)
def _draw_funddom(coset_reps, format="S"): r""" Draw a fundamental domain for G. INPUT: - ``format`` -- (default 'Disp') How to present the f.d. - ``S`` -- Display directly on the screen EXAMPLES:: sage: G=MySubgroup(Gamma0(3)) sage: G._draw_funddom() """ pi = RR.pi() pi_3 = pi / RR(3.0) from sage.plot.plot import (Graphics, line) from sage.functions.trig import (cos, sin) g = Graphics() x1 = RR(-0.5) y1 = RR(sqrt(3) / 2) x2 = RR(0.5) y2 = RR(sqrt(3) / 2) xmax = RR(20.0) l1 = line([[x1, y1], [x1, xmax]]) l2 = line([[x2, y2], [x2, xmax]]) l3 = line([[x2, xmax], [x1, xmax]]) # This is added to make a closed contour c0 = _circ_arc(RR(pi / 3.0), RR(2.0 * pi) / RR(3.0), 0, 1, 100) tri = c0 + l1 + l3 + l2 g = g + tri for A in coset_reps: [a, b, c, d] = A if (a == 1 and b == 0 and c == 0 and d == 1): continue if (a < 0): a = RR(-a) b = RR(-b) c = RR(-c) d = RR(-d) else: a = RR(a) b = RR(b) c = RR(c) d = RR(d) if (c == 0): # then this is easier L0 = [[cos(pi_3 * RR(i / 100.0)) + b, sin(pi_3 * RR(i / 100.0))] for i in range(100, 201)] L1 = [[x1 + b, y1], [x1 + b, xmax]] L2 = [[x2 + b, y2], [x2 + b, xmax]] L3 = [[x2 + b, xmax], [x1 + b, xmax]] c0 = line(L0) l1 = line(L1) l2 = line(L2) l3 = line(L3) tri = c0 + l1 + l3 + l2 g = g + tri else: den = (c * x1 + d)**2 + c**2 * y1**2 x1_t = (a * c * (x1**2 + y1**2) + (a * d + b * c) * x1 + b * d) / den y1_t = y1 / den den = (c * x2 + d)**2 + c**2 * y2**2 x2_t = (a * c * (x2**2 + y2**2) + (a * d + b * c) * x2 + b * d) / den y2_t = y2 / den inf_t = a / c c0 = _geodesic_between_two_points(x1_t, y1_t, x2_t, y2_t) c1 = _geodesic_between_two_points(x1_t, y1_t, inf_t, 0.) c2 = _geodesic_between_two_points(x2_t, y2_t, inf_t, 0.0) tri = c0 + c1 + c2 g = g + tri return g
def simplify_abs_trig(expr): r""" Simplify ``abs(sin(...))`` and ``abs(cos(...))`` in symbolic expressions. EXAMPLES:: sage: M = Manifold(3, 'M', structure='topological') sage: X.<x,y,z> = M.chart(r'x y:(0,pi) z:(-pi/3,0)') sage: X.coord_range() x: (-oo, +oo); y: (0, pi); z: (-1/3*pi, 0) Since `x` spans all `\RR`, no simplification of ``abs(sin(x))`` occurs, while ``abs(sin(y))`` and ``abs(sin(3*z))`` are correctly simplified, given that `y \in (0,\pi)` and `z \in (-\pi/3,0)`:: sage: from sage.manifolds.utilities import simplify_abs_trig sage: simplify_abs_trig( abs(sin(x)) + abs(sin(y)) + abs(sin(3*z)) ) abs(sin(x)) + sin(y) + sin(-3*z) Note that neither :meth:`~sage.symbolic.expression.Expression.simplify_trig` nor :meth:`~sage.symbolic.expression.Expression.simplify_full` works in this case:: sage: s = abs(sin(x)) + abs(sin(y)) + abs(sin(3*z)) sage: s.simplify_trig() abs(4*cos(-z)^2 - 1)*abs(sin(-z)) + abs(sin(x)) + abs(sin(y)) sage: s.simplify_full() abs(4*cos(-z)^2 - 1)*abs(sin(-z)) + abs(sin(x)) + abs(sin(y)) despite the following assumptions hold:: sage: assumptions() [x is real, y is real, y > 0, y < pi, z is real, z > -1/3*pi, z < 0] Additional checks are:: sage: simplify_abs_trig( abs(sin(y/2)) ) # shall simplify sin(1/2*y) sage: simplify_abs_trig( abs(sin(2*y)) ) # must not simplify abs(sin(2*y)) sage: simplify_abs_trig( abs(sin(z/2)) ) # shall simplify sin(-1/2*z) sage: simplify_abs_trig( abs(sin(4*z)) ) # must not simplify abs(sin(-4*z)) Simplification of ``abs(cos(...))``:: sage: forget() sage: M = Manifold(3, 'M', structure='topological') sage: X.<x,y,z> = M.chart(r'x y:(0,pi/2) z:(pi/4,3*pi/4)') sage: X.coord_range() x: (-oo, +oo); y: (0, 1/2*pi); z: (1/4*pi, 3/4*pi) sage: simplify_abs_trig( abs(cos(x)) + abs(cos(y)) + abs(cos(2*z)) ) abs(cos(x)) + cos(y) - cos(2*z) Additional tests:: sage: simplify_abs_trig(abs(cos(y-pi/2))) # shall simplify cos(-1/2*pi + y) sage: simplify_abs_trig(abs(cos(y+pi/2))) # shall simplify -cos(1/2*pi + y) sage: simplify_abs_trig(abs(cos(y-pi))) # shall simplify -cos(-pi + y) sage: simplify_abs_trig(abs(cos(2*y))) # must not simplify abs(cos(2*y)) sage: simplify_abs_trig(abs(cos(y/2)) * abs(sin(z))) # shall simplify cos(1/2*y)*sin(z) TESTS: Simplification of expressions involving some symbolic derivatives:: sage: f = function('f') sage: s = abs(cos(x)) + abs(cos(y))*diff(f(x),x) + abs(cos(2*z)) sage: simplify_abs_trig(s) cos(y)*diff(f(x), x) + abs(cos(x)) - cos(2*z) sage: s = abs(sin(x))*diff(f(x),x).subs(x=y^2) + abs(cos(y)) sage: simplify_abs_trig(s) abs(sin(x))*D[0](f)(y^2) + cos(y) sage: forget() # for doctests below """ w0 = SR.wild() if expr.has(abs_symbolic(sin(w0))) or expr.has(abs_symbolic(cos(w0))): return SimplifyAbsTrig(expr)() return expr
def composition(self, ex, operator): r""" This is the only method of the base class :class:`~sage.symbolic.expression_conversions.ExpressionTreeWalker` that is reimplemented, since it manages the composition of ``abs`` with ``cos`` or ``sin``. INPUT: - ``ex`` -- a symbolic expression - ``operator`` -- an operator OUTPUT: - a symbolic expression, equivalent to ``ex`` with ``abs(cos(...))`` and ``abs(sin(...))`` simplified, according to the range of their argument. EXAMPLES:: sage: from sage.manifolds.utilities import SimplifyAbsTrig sage: assume(-pi/2 < x, x<0) sage: a = abs(sin(x)) sage: s = SimplifyAbsTrig(a) sage: a.operator() abs sage: s.composition(a, a.operator()) sin(-x) :: sage: a = exp(function('f')(x)) # no abs(sin_or_cos(...)) sage: a.operator() exp sage: s.composition(a, a.operator()) e^f(x) :: sage: forget() # no longer any assumption on x sage: a = abs(cos(sin(x))) # simplifiable since -1 <= sin(x) <= 1 sage: s.composition(a, a.operator()) cos(sin(x)) sage: a = abs(sin(cos(x))) # not simplifiable sage: s.composition(a, a.operator()) abs(sin(cos(x))) """ if operator is abs_symbolic: argum = ex.operands()[0] # argument of abs if argum.operator() is sin: # Case of abs(sin(...)) x = argum.operands()[0] # argument of sin w0 = SR.wild() if x.has(abs_symbolic(sin(w0))) or x.has(abs_symbolic( cos(w0))): x = self(x) # treatment of nested abs(sin_or_cos(...)) # Simplifications for values of x in the range [-pi, 2*pi]: if x >= 0 and x <= pi: ex = sin(x) elif (x > pi and x <= 2 * pi) or (x >= -pi and x < 0): ex = -sin(x) return ex if argum.operator() is cos: # Case of abs(cos(...)) x = argum.operands()[0] # argument of cos w0 = SR.wild() if x.has(abs_symbolic(sin(w0))) or x.has(abs_symbolic( cos(w0))): x = self(x) # treatment of nested abs(sin_or_cos(...)) # Simplifications for values of x in the range [-pi, 2*pi]: if (x >= -pi / 2 and x <= pi / 2) or (x >= 3 * pi / 2 and x <= 2 * pi): ex = cos(x) elif (x > pi / 2 and x <= 3 * pi / 2) or (x >= -pi and x < -pi / 2): ex = -cos(x) return ex # If no pattern is found, we default to ExpressionTreeWalker: return super(SimplifyAbsTrig, self).composition(ex, operator)