From 900c36c95b351919b53237c12ed7096c75837013 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Sun, 20 Feb 2022 15:26:31 +0900 Subject: [PATCH] Reviewer changes simplifying plot implementation, more robust inputs, and misc doc changes. --- .../hyperbolic_space/hyperbolic_geodesic.py | 46 +++- .../hyperbolic_space/hyperbolic_model.py | 50 +++- src/sage/plot/hyperbolic_arc.py | 245 +++++++----------- src/sage/plot/hyperbolic_polygon.py | 175 +++++++------ 4 files changed, 265 insertions(+), 251 deletions(-) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index 0a7eea4ea30..ef8035cadd0 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -696,15 +696,27 @@ def complete(self): :: sage: KM = H.KM() - sage: KM.get_geodesic(-I, 1).complete() + sage: KM.get_geodesic((0,0), (0,1/2)).complete() Geodesic in KM from (0, -1) to (0, 1) .. PLOT:: - g = HyperbolicPlane().KM().get_geodesic(CC(0,-1), CC(0, 1)) + g = HyperbolicPlane().KM().get_geodesic(CC(0,0), CC(0, 0.5)) + h = g.complete() + sphinx_plot(g.plot()+h.plot(linestyle='dashed')) + + :: + + sage: KM.get_geodesic(-I, 1).complete() + Geodesic in KM from -I to 1 + + .. PLOT:: + + g = HyperbolicPlane().KM().get_geodesic(CC(0,-1), CC(1, 0)) h = g.complete() sphinx_plot(g.plot()+h.plot(linestyle='dashed')) + :: sage: HM = H.HM() @@ -2177,11 +2189,9 @@ class HyperbolicGeodesicPD(HyperbolicGeodesic): g = PD.get_geodesic(I,-I/2) h = PD.get_geodesic(-0.5+I*0.5,0.5+I*0.5) sphinx_plot(g.plot()+h.plot(color='green')) - """ def plot(self, boundary=True, **options): - r""" Plot ``self``. @@ -2279,13 +2289,11 @@ class HyperbolicGeodesicKM(HyperbolicGeodesic): EXAMPLES:: sage: KM = HyperbolicPlane().KM() - sage: g = KM.get_geodesic(KM.get_point((0.1,0.9)), KM.get_point((-0.1,-0.9))) sage: g = KM.get_geodesic((0.1,0.9),(-0.1,-0.9)) sage: h = KM.get_geodesic((-0.707106781,-0.707106781),(0.707106781,-0.707106781)) sage: P = g.plot(color='orange')+h.plot(); P # optional - sage.plot Graphics object consisting of 4 graphics primitives - .. PLOT:: KM = HyperbolicPlane().KM() @@ -2315,7 +2323,11 @@ def plot(self, boundary=True, **options): 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()] + def map_pt(pt): + if pt in CC: + return CC(pt) + return CC(*pt) + end_1, end_2 = [map_pt(k.coordinates()) for k in self.endpoints()] pic = bezier_path([[(real(end_1), imag(end_1)), (real(end_2), imag(end_2))]], **opts) if boundary: pic += self._model.get_background_graphic() @@ -2338,7 +2350,6 @@ class HyperbolicGeodesicHM(HyperbolicGeodesic): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * sage: HM = HyperbolicPlane().HM() sage: p1 = HM.get_point((4, -4, sqrt(33))) sage: p2 = HM.get_point((-3,-3,sqrt(19))) @@ -2356,12 +2367,23 @@ class HyperbolicGeodesicHM(HyperbolicGeodesic): """ def _plot_vertices(self, points=75): r""" - Return ``self`` plotting vertices in R^3. + Return ``self`` plotting vertices in `\RR^3`. Auxiliary function needed to plot polygons. - """ + EXAMPLES:: + sage: HM = HyperbolicPlane().HM() + sage: p1 = HM.get_point((4, -4, sqrt(33))) + sage: p2 = HM.get_point((-3,-3,sqrt(19))) + sage: g = HM.get_geodesic(p1, p2) + sage: g._plot_vertices(5) + [(4.0, -4.0, 5.744562646538029), + (1.3632131724692114, -1.6370738298435326, 2.353372235315133), + (0.13856858387448234, -0.9699800871213693, 1.4000223647674197), + (-0.9425338542843988, -1.3076813974501533, 1.8969450977056184), + (-3.0, -3.0, 4.358898943540652)] + """ from sage.plot.misc import setup_for_eval_on_grid from sage.arith.srange import xsrange @@ -2380,15 +2402,13 @@ def _plot_vertices(self, points=75): # Now v1 and v2 are Lorentz orthogonal, and |v1| = -1, |v2|=1 # That is, v1 is unit timelike and v2 is unit spacelike. # This means that cosh(x)*v1 + sinh(x)*v2 is unit timelike. - hyperbola = cosh(x)*v1 + sinh(x)*v2 + hyperbola = tuple(cosh(x)*v1 + sinh(x)*v2) endtime = arcsinh(v2_ldot_u2) # mimic the function _parametric_plot3d_curve using a bezier3d instead of a line3d # this is required in order to be able to plot hyperbolic polygons whithin the plot library g, ranges = setup_for_eval_on_grid(hyperbola, [(x, 0, endtime)], points) f_x, f_y, f_z = g points = [(f_x(u), f_y(u), f_z(u)) for u in xsrange(*ranges[0], include_endpoint=True)] - # convert points to a path3d - points = list(points) return points def plot(self, show_hyperboloid=True, **graphics_options): diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py index 503ab39f798..46e911e9a58 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py @@ -109,8 +109,8 @@ lazy_import('sage.modules.free_module_element', 'vector') -# #################################################################### -# Abstract model +##################################################################### +## Abstract model class HyperbolicModel(Parent, UniqueRepresentation, BindableClass): @@ -776,7 +776,7 @@ def isometry_from_fixed_points(self, repel, attract): ##################################################################### -# Upper half plane model +## Upper half plane model class HyperbolicModelUHP(HyperbolicModel): r""" @@ -854,7 +854,11 @@ def point_in_model(self, p): """ if isinstance(p, HyperbolicPoint): return p.is_boundary() - return bool(imag(CC(p)) > 0) + if p in CC: + p = CC(p) + else: + p = CC(*p) + return bool(imag(p) > 0) def boundary_point_in_model(self, p): r""" @@ -888,7 +892,11 @@ def boundary_point_in_model(self, p): """ if isinstance(p, HyperbolicPoint): return p.is_boundary() - im = abs(imag(CC(p)).n()) + if p in CC: + p = CC(p) + else: + p = CC(*p) + im = abs(imag(p).n()) return (im < EPSILON) or bool(p == infinity) def isometry_in_model(self, A): @@ -1142,8 +1150,8 @@ def _moebius_sending(z, w): # UHP B = HyperbolicGeodesicUHP._crossratio_matrix(w[0], w[1], w[2]) return B.inverse() * A -# #################################################################### -# Poincaré disk model +##################################################################### +## Poincaré disk model class HyperbolicModelPD(HyperbolicModel): @@ -1210,7 +1218,11 @@ def point_in_model(self, p): """ if isinstance(p, HyperbolicPoint): return p.is_boundary() - return bool(abs(CC(p)) < 1) + if p in CC: + p = CC(p) + else: + p = CC(*p) + return bool(abs(p) < 1) def boundary_point_in_model(self, p): r""" @@ -1228,7 +1240,11 @@ def boundary_point_in_model(self, p): """ if isinstance(p, HyperbolicPoint): return p.is_boundary() - return bool(abs(abs(CC(p)) - 1) < EPSILON) + if p in CC: + p = CC(p) + else: + p = CC(*p) + return bool(abs(abs(p) - 1) < EPSILON) def isometry_in_model(self, A): r""" @@ -1264,7 +1280,7 @@ def get_background_graphic(self, **bdry_options): ##################################################################### -# Klein disk model +## Klein disk model class HyperbolicModelKM(HyperbolicModel): r""" @@ -1327,8 +1343,12 @@ def point_in_model(self, p): """ if isinstance(p, HyperbolicPoint): return p.is_boundary() + if p in CC: + p = CC(p) + else: + p = CC(*p) # return len(p) == 2 and bool(p[0]**2 + p[1]**2 < 1) - return bool(abs(CC(p)) < 1) + return bool(abs(p) < 1) def boundary_point_in_model(self, p): r""" @@ -1347,8 +1367,12 @@ def boundary_point_in_model(self, p): """ if isinstance(p, HyperbolicPoint): return p.is_boundary() + if p in CC: + p = CC(p) + else: + p = CC(*p) # return len(p) == 2 and bool(abs(p[0]**2 + p[1]**2 - 1) < EPSILON) - return bool(abs(abs(CC(p)) - 1) < EPSILON) + return bool(abs(abs(p) - 1) < EPSILON) def isometry_in_model(self, A): r""" @@ -1379,7 +1403,7 @@ def get_background_graphic(self, **bdry_options): return circle((0, 0), 1, axes=False, color='black') ##################################################################### -# Hyperboloid model +## Hyperboloid model class HyperbolicModelHM(HyperbolicModel): diff --git a/src/sage/plot/hyperbolic_arc.py b/src/sage/plot/hyperbolic_arc.py index d61fd4e4e97..097b4239946 100644 --- a/src/sage/plot/hyperbolic_arc.py +++ b/src/sage/plot/hyperbolic_arc.py @@ -5,52 +5,50 @@ - Hartmut Monien (2011 - 08) -Two models of the hyperbolic plane are implemented: Upper Half Plane and Poincare Disc, each with +Two models of the hyperbolic plane are implemented: Upper Half Plane, +Poincaré Disk, and Klein Disk, each with its different domain and metric tensor. UPPER HALF PLANE (UHP) -In this model, hyperbolic points are described by two coordinates, which we will represent -by a complex number in the domain +In this model, hyperbolic points are described by two coordinates, which +we will represent by a complex number in the domain .. MATH:: - H = \{z \in \CC \colon \Im(z)>0\} + H = \{ z \in \CC \mid \Im(z)>0 \} with the corresponding metric tensor .. MATH:: - ds^2=\frac{dzd\bar{z}}{\Im(z)^2} + ds^2 = \frac{dzd\bar{z}}{\Im(z)^2}. -POINCARE DISC (PD) +POINCARÉ DISK (PD) -In this model, hyperbolic points are described by two coordinates, which we will represent -by a complex number within the unit circle, having therefore the following domain +In this model, hyperbolic points are described by two coordinates, which we +will represent by a complex number within the unit circle, having therefore +the following domain .. MATH:: - D = \{ z \in \CC \colon \vert z \vert < 1\} + D = \{ z \in \CC \mid \lvert z \rvert < 1\} with the corresponding metric tensor .. MATH:: - ds^2 = 4 \frac{dzd\bar{z}}{(1-\vert z \vert^2)^2} + ds^2 = 4 \frac{dzd\bar{z}}{(1-\lvert z \rvert^2)^2}. .. SEEALSO:: - :mod:`link to the geodesics in hyperbolic geometry module ` + :mod:`sage.geometry.hyperbolic_space.hyperbolic_geodesic` REFERENCES: -For additional models of the hyperbolic plane and its relationship see - -[CFKP1997]_ - -And for a more detailed explanation on hyperbolic arcs see - -[Sta1993]_ +For additional models of the hyperbolic plane and its relationship +see [CFKP1997]_. For a more detailed explanation on hyperbolic arcs +see [Sta1993]_. """ # ***************************************************************************** @@ -81,125 +79,51 @@ class HyperbolicArcCore(BezierPath): """ Base class for Hyperbolic arcs and hyperbolic polygons in the - hyperbolic plane. Both Upper Half Model and Poincare Disc Model - are supported. - - INPUT: - - - ``A, B`` -- end points of the hyperbolic arc - - - ``model`` -- (default: ``'UHP'``) hyperbolic model used, - which is one of the following: + hyperbolic plane. - * ``'UHP'`` - upper half plane - * ``'PD'`` - Poincare disc + The Upper Half Model, Poincaré Disk Model, and Klein Disk model + are supported. """ - def __init__(self, A, B, model, options): + def _bezier_path(self, z0, z1, model, first=False): """ - Initialize ``self``. - """ - A, B = (CC(A), CC(B)) - self.path = [] - if model == "UHP": - if A.imag() < 0: - raise ValueError("%s is not a valid point in the UHP model" % (A,)) - if B.imag() < 0: - raise ValueError("%s is not a valid point in the UHP model" % (B,)) - self._UHP_hyperbolic_arc(A, B, True) - elif model == "PD": - if A.abs() > 1: - raise ValueError("%s is not a valid point in the PD model" % (A,)) - if B.abs() > 1: - raise ValueError("%s is not a valid point in the PD model" % (B,)) - self._PD_hyperbolic_arc(A, B, True) - elif model == "KM": - if A.abs() > 1: - raise ValueError("%s is not a valid point in the KM model" % (A,)) - if B.abs() > 1: - raise ValueError("%s is not a valid point in the KM model" % (B,)) - self._KM_hyperbolic_arc(A, B, True) - else: - raise ValueError("%s is not a valid model for Hyperbolic plane" % model) + Construct a bezier path from a given arc object and store it + in the ``path`` attribute. - BezierPath.__init__(self, self.path, options) - self.A, self.B = (A, B) + INPUT: - def _repr_(self): - """ - String representation of HyperbolicArc. + - ``z0``, ``z1`` -- the points of the arc + - ``model`` -- an model for the hyperbolic arc TESTS:: - sage: from sage.plot.hyperbolic_arc import HyperbolicArcCore - sage: HyperbolicArcCore(0, 1/2+I*sqrt(3)/2, "UHP", {})._repr_() - 'Hyperbolic arc (0.000000000000000, 0.500000000000000 + 0.866025403784439*I)' - """ - return "Hyperbolic arc (%s, %s)" % (self.A, self.B) - - def _UHP_hyperbolic_arc(self, z0, z3, first=False): - """ - Construct Bezier path as an approximation to - the hyperbolic arc between the complex numbers ``z0`` and ``z3`` - in the hyperbolic plane. - """ - from sage.geometry.hyperbolic_space.hyperbolic_interface import HyperbolicPlane - UHP = HyperbolicPlane().UHP() - g = UHP.get_geodesic(z0, z3) - p = g.plot() - the_arc = p[0] - self._bezier_path(the_arc, z0, z3, first) - - def _PD_hyperbolic_arc(self, z0, z3, first=False): - """ - Construct a hyperbolic arc between the complez numbers ``z0`` - and ``z3`` in the Poincare Disc model - """ - from sage.geometry.hyperbolic_space.hyperbolic_interface import HyperbolicPlane - PD = HyperbolicPlane().PD() - g = PD.get_geodesic(z0, z3) - p = g.plot() - the_arc = p[0] - self._bezier_path(the_arc, z0, z3, first) - - def _KM_hyperbolic_arc(self, z0, z3, first=False): - """ - Construct Bezier path as an approximation to - the hyperbolic arc between the complex numbers ``z0`` and ``z3`` - in the hyperbolic plane. - """ - from sage.geometry.hyperbolic_space.hyperbolic_interface import HyperbolicPlane - KM = HyperbolicPlane().KM() - g = KM.get_geodesic(z0, z3) - p = g.plot() - the_arc = p[0] - self._bezier_path(the_arc, z0, z3, first) - - def _bezier_path(self, arc0, z0, z3, first=False): - """ - Construct a bezier path from a given arc object and store it - in the ``path`` attribute - - INPUT: - - - ``arc0`` -- an arc object representing a hyperbolic arc - - ```z0, z3`` -- hyperbolic arc end points + sage: from sage.plot.hyperbolic_arc import HyperbolicArc + sage: HyperbolicArc(0, 1/2+I*sqrt(3)/2, "UHP", {}) # indirect doctest + Hyperbolic arc (0.000000000000000, 0.500000000000000 + 0.866025403784439*I) """ import numpy as np from sage.rings.infinity import infinity - if(isinstance(arc0, BezierPath)): + EPSILON = 10 ** -5 + + arc0 = model.get_geodesic(z0, z1).plot()[0] + + if isinstance(arc0, BezierPath): points = arc0.vertices else: points = arc0.bezier_path()[0].vertices if ( - ((z0.is_infinity() or z0 == infinity) and abs(CC(points[0][0], points[0][1]) - z3) < 0.00001) - or ((z3.is_infinity() or z3 == infinity) and abs(CC(points[1][0], points[1][1]) - z0) < 0.00001) - or (abs(CC(points[0][0], points[0][1]) - z0) > 0.00001 and not(z0.is_infinity() or z0 == infinity or z3.is_infinity() or z3 == infinity)) - ): + ((z0.is_infinity() or z0 == infinity) + and abs(CC(points[0][0], points[0][1]) - z1) < EPSILON) + or ((z1.is_infinity() or z1 == infinity) + and abs(CC(points[1][0], points[1][1]) - z0) < EPSILON) + or (abs(CC(points[0][0], points[0][1]) - z0) >= EPSILON + and not (z0.is_infinity() or z0 == infinity or z1.is_infinity() + or z1 == infinity)) + ): points = np.flipud(points) # order is important if first: - self.path.append(points[0: 4]) # if it is a line it will append only two control points - if (isinstance(arc0, BezierPath)): + self.path.append(points[0:4]) # if it is a line it will append only two control points + if isinstance(arc0, BezierPath): self.last_plotted = "line" else: N = 4 @@ -212,10 +136,10 @@ def _bezier_path(self, arc0, z0, z3, first=False): # the first point is equal to the last of the previous arc points = np.delete(points, 0, 0) N = 0 - if (isinstance(arc0, BezierPath)): + if isinstance(arc0, BezierPath): self.path.append(points[0:1]) self.last_plotted = "line" - elif (self.last_plotted == "line"): # actual segment is an arc + elif self.last_plotted == "line": # actual segment is an arc # Add new triplets while N < len(points): self.path.append(points[N: N + 3]) @@ -239,33 +163,61 @@ def _bezier_path(self, arc0, z0, z3, first=False): class HyperbolicArc(HyperbolicArcCore): r""" - Primitive class for hyberbolic arc type. See ``hyperbolic_arc?`` for - information about plotting a hyperbolic arc in the complex plane. + Primitive class for hyberbolic arc type. + + See ``hyperbolic_arc?`` for information about plotting a hyperbolic + arc in the complex plane. INPUT: - - ``a, b`` -- coordinates of the hyperbolic arc in the complex plane - - ``options`` -- dict of valid plot options to pass to constructor - - ``model`` -- (default: ``'UHP'``) hyperbolic model used, - which is one of the following: + - ``A, B`` -- end points of the hyperbolic arc + - ``model`` -- the hyperbolic model used, which is one of the following: * ``'UHP'`` - upper half plane - * ``'PD'`` - Poincare disc + * ``'PD'`` - Poincaré disk + * ``'KM'`` - Klein disk - EXAMPLES: - - Note that constructions should use ``hyperbolic_arc``:: + TESTS:: sage: from sage.plot.hyperbolic_arc import HyperbolicArc - sage: print(HyperbolicArc(0, 1/2+I*sqrt(3)/2, "UHP", {})) + sage: HyperbolicArc(0, 1/2+I*sqrt(3)/2, "UHP", {}) Hyperbolic arc (0.000000000000000, 0.500000000000000 + 0.866025403784439*I) """ def __init__(self, A, B, model, options): - r""" + """ Initialize ``self``. + + EXAMPLES:: + + sage: from sage.plot.hyperbolic_arc import HyperbolicArc + sage: arc = HyperbolicArc(0, 1/2+I*sqrt(3)/2, "UHP", {}) + sage: TestSuite(arc).run(skip="_test_pickling") # no equality implemented """ - HyperbolicArcCore.__init__(self, A, B, model, options) + if model == "HM": + raise ValueError("the hyperboloid model is not supported") + from sage.geometry.hyperbolic_space.hyperbolic_interface import HyperbolicPlane + HP = HyperbolicPlane() + M = getattr(HP, model)() + self.A = CC(A) + self.B = CC(B) + self.model = model + M.point_test(self.A) + M.point_test(self.B) + self.path = [] + self._bezier_path(self.A, self.B, M, True) + BezierPath.__init__(self, self.path, options) + def _repr_(self): + """ + String representation of HyperbolicArc. + + TESTS:: + + sage: from sage.plot.hyperbolic_arc import HyperbolicArc + sage: HyperbolicArc(0, 1/2+I*sqrt(3)/2, "UHP", {}) + Hyperbolic arc (0.000000000000000, 0.500000000000000 + 0.866025403784439*I) + """ + return "Hyperbolic arc (%s, %s)" % (self.A, self.B) @rename_keyword(color='rgbcolor') @options(alpha=1, fill=False, thickness=1, rgbcolor="blue", zorder=2, linestyle='solid') @@ -281,7 +233,9 @@ def hyperbolic_arc(a, b, model="UHP", **options): which is one of the following: * ``'UHP'`` - upper half plane - * ``'PD'`` - Poincare disc + * ``'PD'`` - Poincaré disk + * ``'KM'`` - Klein disk + * ``'HM'`` - hyperboloid model OPTIONS: @@ -348,7 +302,7 @@ def hyperbolic_arc(a, b, model="UHP", **options): Show a hyperbolic arc from `i` to `-1` in red, another hyperbolic arc from `e^{i\pi/3}` to `0.6*e^{i 3\pi/4}` with dashed style in green, and finally a hyperbolic arc from `-0.5+0.5i` to `0.5-0.5i` together - with the disc frontier in the PD model:: + with the disk frontier in the PD model:: sage: z1 = CC(0,1) sage: z2 = CC(-1,0) @@ -379,7 +333,7 @@ def hyperbolic_arc(a, b, model="UHP", **options): Show a hyperbolic arc from `i` to `-1` in red, another hyperbolic arc from `e^{i\pi/3}` to `0.6*e^{i 3\pi/4}` with dashed style in green, and finally a hyperbolic arc from `-0.5+0.5i` to `0.5-0.5i` together - with the disc frontier in the KM model:: + with the disk frontier in the KM model:: sage: z1 = CC(0,1) sage: z2 = CC(-1,0) @@ -412,7 +366,7 @@ def hyperbolic_arc(a, b, model="UHP", **options): sage: a = (1,2,sqrt(6)) sage: b = (-2,-3,sqrt(14)) sage: hyperbolic_arc(a, b, model="HM") - Launched html viewer for Graphics3d Object + Graphics3d Object ..PLOT:: @@ -421,13 +375,11 @@ def hyperbolic_arc(a, b, model="UHP", **options): sphinx_plot(hyperbolic_arc(a, b, model="HM")) """ - from sage.plot.all import Graphics + from sage.plot.graphics import Graphics g = Graphics() g._set_extra_kwds(g._extract_kwds_for_show(options)) - if model != "HM": - g.add_primitive(HyperbolicArc(a, b, model, options)) - else: + if model == "HM": # since KM is 3d we can not use HyperbolicArc class we plot it directly # and we also handle the hyperbolic_polygon in direct way from sage.geometry.hyperbolic_space.hyperbolic_interface import HyperbolicPlane @@ -441,7 +393,10 @@ def hyperbolic_arc(a, b, model="UHP", **options): HM = HyperbolicPlane().HM() geodesic = HM.get_geodesic(a, b) g = g + geodesic.plot(show_hyperboloid=True, graphics_options=options) - if model == "PD" or model == "KM": - g = g + circle((0, 0), 1, axes=False, color='black') - g.set_aspect_ratio(1) + else: + g.add_primitive(HyperbolicArc(a, b, model, options)) + if model == "PD" or model == "KM": + g = g + circle((0, 0), 1, axes=False, color='black') + g.set_aspect_ratio(1) return g + diff --git a/src/sage/plot/hyperbolic_polygon.py b/src/sage/plot/hyperbolic_polygon.py index 652791caf81..7566421a73a 100644 --- a/src/sage/plot/hyperbolic_polygon.py +++ b/src/sage/plot/hyperbolic_polygon.py @@ -63,35 +63,40 @@ def __init__(self, pts, model, options): sage: HP = HyperbolicPolygon([0, 1/2, I], "UHP", {}) sage: TestSuite(HP).run(skip ="_test_pickling") """ - pts = [CC(_) for _ in pts] + if model == "HM": + raise ValueError("the hyperboloid model is not supported") + if not pts: + raise ValueError("cannot plot the empty polygon") + from sage.geometry.hyperbolic_space.hyperbolic_interface import HyperbolicPlane + HP = HyperbolicPlane() + M = getattr(HP, model)() + + def build_arc(z0, z1, first): + g = M.get_geodesic(z0, z1) + self._bezier_path(z0, z1, M, first) + + pts = [CC(p) for p in pts] + for p in pts: + M.point_test(p) self.path = [] if model == "UHP": - if(pts[0].is_infinity()): + if pts[0].is_infinity(): # Check for more than one Infinite vertex - for i in range(1, len(pts)): - if(pts[i].is_infinity()): - raise ValueError("No more than one infinite vertex allowed") + if any(pts[i].is_infinity() for i in range(1, len(pts))): + raise ValueError("no more than one infinite vertex allowed") else: # If any Infinity vertex exist it must be the first - for i in range(1, len(pts)): - if(pts[i].is_infinity()): - pts = pts[i:] + pts[0:i] - self._UHP_hyperbolic_arc(pts[0], pts[1], True) - for i in range(1, len(pts) - 1): - self._UHP_hyperbolic_arc(pts[i], pts[i + 1]) - self._UHP_hyperbolic_arc(pts[-1], pts[0]) - elif model == "PD": - self._PD_hyperbolic_arc(pts[0], pts[1], True) - for i in range(1, len(pts) - 1): - self._PD_hyperbolic_arc(pts[i], pts[i + 1]) - self._PD_hyperbolic_arc(pts[-1], pts[0]) - elif model == "KM": - self._KM_hyperbolic_arc(pts[0], pts[1], True) - for i in range(1, len(pts) - 1): - self._KM_hyperbolic_arc(pts[i], pts[i + 1]) - self._KM_hyperbolic_arc(pts[-1], pts[0]) - else: - raise ValueError("%s is not a valid model for Hyperbolic plane" % model) + for i, p in enumerate(pts): + if p.is_infinity(): + if any(pt.is_infinity() for pt in pts[i+1:]): + raise ValueError("no more than one infinite vertex allowed") + pts = pts[i:] + pts[:i] + break + + self._bezier_path(pts[0], pts[1], M, True) + for i in range(1, len(pts) - 1): + self._bezier_path(pts[i], pts[i + 1], M, False) + self._bezier_path(pts[-1], pts[0], M, False) self._pts = pts BezierPath.__init__(self, self.path, options) @@ -107,25 +112,9 @@ def _repr_(self): """ return "Hyperbolic polygon ({})".format(", ".join(map(str, self._pts))) - -def _intersects(start, end, y0): - if end[1] < start[1]: - start, end = end, start - return start[1] < y0 < end[1] - - -def _is_left(point, side): - start, end = side[0], side[1] - if (end[1] == start[1]): - return 0 - else: - x_in = start[0] + (point[1] - start[1])*(end[0] - start[0])/(end[1] - start[1]) - return x_in > point[0] - - def _winding_number(vertices, point): - """ - Compute the winding number of the given point in the plane z=0 + r""" + Compute the winding number of the given point in the plane `z = 0`. TESTS:: @@ -135,6 +124,19 @@ def _winding_number(vertices, point): sage: _winding_number([(0,0,4),(1,0,3),(1,1,2),(0,1,1)],(10,10,10)) 0 """ + # Helper functions + def _intersects(start, end, y0): + if end[1] < start[1]: + start, end = end, start + return start[1] < y0 < end[1] + + def _is_left(point, edge): + start, end = edge[0], edge[1] + if end[1] == start[1]: + return False + x_in = start[0] + (point[1] - start[1]) * (end[0] - start[0]) / (end[1] - start[1]) + return x_in > point[0] + sides = [] wn = 0 for i in range(0, len(vertices)-1): @@ -152,8 +154,7 @@ def _winding_number(vertices, point): @rename_keyword(color='rgbcolor') -@options(alpha=1, fill=False, thickness=1, rgbcolor="blue", zorder=2, - linestyle='solid') +@options(alpha=1, fill=False, thickness=1, rgbcolor="blue", zorder=2, linestyle='solid') def hyperbolic_polygon(pts, model="UHP", resolution=200, **options): r""" Return a hyperbolic polygon in the hyperbolic plane with vertices ``pts``. @@ -201,9 +202,9 @@ def hyperbolic_polygon(pts, model="UHP", resolution=200, **options): sphinx_plot(hyperbolic_polygon([-1,3*I,2+2*I,1+I], fill=True, color='red')) - With vertex at Infinity:: + With a vertex at `\infty`:: - sage: hyperbolic_polygon([-1,0,1,Infinity], color='green')) + sage: hyperbolic_polygon([-1,0,1,Infinity], color='green') Graphics object consisting of 1 graphics primitive .. PLOT:: @@ -234,15 +235,15 @@ def hyperbolic_polygon(pts, model="UHP", resolution=200, **options): Klein model is also supported via the paraeter ``model``. Show a hyperbolic polygon in the Klein model with coordinates - `1`, `e^{i\pi/3}` , `e^{i2\pi/3}` , `-1` , `e^{i4\pi/3}` , `e^{i5\pi/3}`:: - - sage: p1=1 - sage: p2=(cos(pi/3),sin(pi/3)) - sage: p3=(cos(2*pi/3),sin(2*pi/3)) - sage: p4=-1 - sage: p5=(cos(4*pi/3),sin(4*pi/3)) - sage: p6=(cos(5*pi/3),sin(5*pi/3)) - hyperbolic_polygon([p1,p2,p3,p4,p5,p6],model="KM", fill=True, color='purple') + `1`, `e^{i\pi/3}`, `e^{i2\pi/3}`, `-1`, `e^{i4\pi/3}`, `e^{i5\pi/3}`:: + + sage: p1 = 1 + sage: p2 = (cos(pi/3), sin(pi/3)) + sage: p3 = (cos(2*pi/3), sin(2*pi/3)) + sage: p4 = -1 + sage: p5 = (cos(4*pi/3), sin(4*pi/3)) + sage: p6 = (cos(5*pi/3), sin(5*pi/3)) + sage: hyperbolic_polygon([p1,p2,p3,p4,p5,p6], model="KM", fill=True, color='purple') Graphics object consisting of 2 graphics primitives .. PLOT:: @@ -253,30 +254,39 @@ def hyperbolic_polygon(pts, model="UHP", resolution=200, **options): p4=-1 p5=(cos(4*pi/3),sin(4*pi/3)) p6=(cos(5*pi/3),sin(5*pi/3)) - P = hyperbolic_polygon([p1,p2,p3,p4,p5,p6],model="KM", fill=True, color='purple') + P = hyperbolic_polygon([p1,p2,p3,p4,p5,p6], model="KM", fill=True, color='purple') sphinx_plot(P) - Hiperboloid model is supported partially, via the paraeter ``model``. - Show a hyperbolic polygon in the Hiperboloid model with coordinates - `(3,3,sqrt(19)),(3,-3,sqrt(19)),(-3,-3,sqrt(19)),(-3,3,sqrt(19))`:: + Hyperboloid model is supported partially, via the paraeter ``model``. + Show a hyperbolic polygon in the hyperboloid model with coordinates + `(3,3,\sqrt(19))`, `(3,-3,\sqrt(19))`, `(-3,-3,\sqrt(19))`, + `(-3,3,\sqrt(19))`:: - sage: hyperbolic_polygon([(3,3,sqrt(19)),(3,-3,sqrt(19)),(-3,-3,sqrt(19)),(-3,3,sqrt(19))],model = "HM") - Launched html viewer for Graphics3d Object + sage: pts = [(3,3,sqrt(19)),(3,-3,sqrt(19)),(-3,-3,sqrt(19)),(-3,3,sqrt(19))] + sage: hyperbolic_polygon(pts, model="HM") + Graphics3d Object .. PLOT:: - P = hyperbolic_polygon([(3,3,sqrt(19)),(3,-3,sqrt(19)),(-3,-3,sqrt(19)),(-3,3,sqrt(19))],model = "HM") + pts = [(3,3,sqrt(19)),(3,-3,sqrt(19)),(-3,-3,sqrt(19)),(-3,3,sqrt(19))] + P = hyperbolic_polygon(pts, model="HM") sphinx_plot(P) - Filling a hyperbolic_polygon in hiperboloid model is possible although jaggy. - Show a filled hyperbolic polygon in the Hiperboloid model with coordinates - `(1,1,sqrt(3)),(0,2,sqrt(5)),(2,0,sqrt(5))`:: + Filling a hyperbolic_polygon in hyperboloid model is possible although + jaggy. We show a filled hyperbolic polygon in the hyperboloid model + with coordinates `(1,1,\sqrt(3))`, `(0,2,\sqrt(5))`, `(2,0,\sqrt(5))`. + (The doctest is done at lower resolution than the picture below to + give a faster result.) :: - sage: hyperbolic_polygon([(1,1,sqrt(3)),(0,2,sqrt(5)),(2,0,sqrt(5))],model="HM",color='yellow',fill=True) + sage: pts = [(1,1,sqrt(3)), (0,2,sqrt(5)), (2,0,sqrt(5))] + sage: hyperbolic_polygon(pts, model="HM", resolution=50, + ....: color='yellow', fill=True) + Graphics3d Object .. PLOT:: - P = hyperbolic_polygon([(1,1,sqrt(3)),(0,2,sqrt(5)),(2,0,sqrt(5))],model="HM",color='yellow',fill=True) + pts = [(1,1,sqrt(3)),(0,2,sqrt(5)),(2,0,sqrt(5))] + P = hyperbolic_polygon(pts, model="HM", color='yellow', fill=True) sphinx_plot(P) """ @@ -284,9 +294,7 @@ def hyperbolic_polygon(pts, model="UHP", resolution=200, **options): g = Graphics() g._set_extra_kwds(g._extract_kwds_for_show(options)) - if model != "HM": - g.add_primitive(HyperbolicPolygon(pts, model, options)) - else: + if model == "HM": from sage.geometry.hyperbolic_space.hyperbolic_interface import HyperbolicPlane from sage.plot.plot3d.implicit_plot3d import implicit_plot3d from sage.symbolic.ring import SR @@ -301,17 +309,24 @@ def hyperbolic_polygon(pts, model="UHP", resolution=200, **options): g = g + line.plot(color=options['rgbcolor'], thickness=options['thickness']) arc_points = arc_points + line._plot_vertices(resolution) if options['fill']: - xlist, ylist, zlist = [p[0] for p in pts], [p[1] for p in pts], [p[2] for p in pts] + xlist = [p[0] for p in pts] + ylist = [p[1] for p in pts] + zlist = [p[2] for p in pts] + def region(x, y, z): + return _winding_number(arc_points, (x, y, z)) != 0 g = g + implicit_plot3d(x**2 + y**2 - z**2 == -1, (x, min(xlist), max(xlist)), (y, min(ylist), max(ylist)), (z, 0, max(zlist)), - region=lambda x, y, z: (_winding_number(arc_points, (x, y, z)) != 0), + region=region, plot_points=resolution, color=options['rgbcolor']) # the less points the more jaggy the picture - if model == "PD" or model == "KM": - g = g + circle((0, 0), 1, rgbcolor='black') - g.set_aspect_ratio(1) + else: + g.add_primitive(HyperbolicPolygon(pts, model, options)) + if model == "PD" or model == "KM": + g = g + circle((0, 0), 1, rgbcolor='black') + g.set_aspect_ratio(1) + return g @@ -342,8 +357,8 @@ def hyperbolic_triangle(a, b, c, model="UHP", **options): EXAMPLES: - Show a hyperbolic triangle with coordinates `0`, `1/2+i\sqrt{3}/2` and - `-1/2+i\sqrt{3}/2`:: + Show a hyperbolic triangle with coordinates `0`, `1/2 + i\sqrt{3}/2` and + `-1/2 + i\sqrt{3}/2`:: sage: hyperbolic_triangle(0, -1/2+I*sqrt(3)/2, 1/2+I*sqrt(3)/2) Graphics object consisting of 1 graphics primitive @@ -363,7 +378,7 @@ def hyperbolic_triangle(a, b, c, model="UHP", **options): P = hyperbolic_triangle(0, 1, 2+i, fill=true, rgbcolor='red', linestyle='--') sphinx_plot(P) - A hyperbolic triangle with a vertex at Infinity:: + A hyperbolic triangle with a vertex at `\infty`:: sage: hyperbolic_triangle(-5,Infinity,5) Graphics object consisting of 1 graphics primitive @@ -373,8 +388,7 @@ def hyperbolic_triangle(a, b, c, model="UHP", **options): from sage.rings.infinity import infinity sphinx_plot(hyperbolic_triangle(-5,infinity,5)) - - It can also plot a hyperbolic triangle in the Poincare Disc model:: + It can also plot a hyperbolic triangle in the Poincaré disk model:: sage: z1 = CC((cos(pi/3),sin(pi/3))) sage: z2 = CC((0.6*cos(3*pi/4),0.6*sin(3*pi/4))) @@ -402,3 +416,4 @@ def hyperbolic_triangle(a, b, c, model="UHP", **options): """ return hyperbolic_polygon((a, b, c), model, **options) +