def plot_fan_stereographically(rays, walls, northsign=1, north=vector((-1,-1,-1)), right=vector((1,0,0)), colors=None, thickness=None): from sage.plot.graphics import Graphics from sage.plot.point import point from sage.misc.flatten import flatten from sage.plot.line import line from sage.misc.functional import norm if colors == None: colors = dict([('walls','black'),('rays','red')]) if thickness == None: thickness = dict([('walls',0.5),('rays',20)]) G = Graphics() for (u,v) in walls: G += _stereo_arc(vector(u),vector(v),vector(u+v),north=northsign*north,right=right,color=colors['walls'],thickness=thickness['walls'],zorder=len(G)) for v in rays: G += point(_stereo_coordinates(vector(v),north=northsign*north,right=right),color=colors['rays'],zorder=len(G),size=thickness['rays']) G.set_aspect_ratio(1) G._show_axes = False return G
def plot2d(self, depth=None): # FIXME: refactor this before publishing from sage.plot.line import line from sage.plot.graphics import Graphics if self._n != 2: raise ValueError("Can only 2d plot fans.") if depth == None: depth = self._depth if not self.is_finite() and depth == infinity: raise ValueError( "For infinite algebras you must specify the depth.") colors = dict([(0, 'red'), (1, 'green')]) G = Graphics() for i in range(2): orbit = self.ith_orbit(i, depth=depth) for j in orbit: G += line([(0, 0), vector(orbit[j])], color=colors[i], thickness=0.5, zorder=2 * j + 1) G.set_aspect_ratio(1) G._show_axes = False return G
def bar_chart(datalist, **options): """ A bar chart of (currently) one list of numerical data. Support for more data lists in progress. EXAMPLES: A bar_chart with blue bars:: sage: bar_chart([1,2,3,4]) Graphics object consisting of 1 graphics primitive A bar_chart with thinner bars:: sage: bar_chart([x^2 for x in range(1,20)], width=0.2) Graphics object consisting of 1 graphics primitive A bar_chart with negative values and red bars:: sage: bar_chart([-3,5,-6,11], rgbcolor=(1,0,0)) Graphics object consisting of 1 graphics primitive A bar chart with a legend (it's possible, not necessarily useful):: sage: bar_chart([-1,1,-1,1], legend_label='wave') Graphics object consisting of 1 graphics primitive Extra options will get passed on to show(), as long as they are valid:: sage: bar_chart([-2,8,-7,3], rgbcolor=(1,0,0), axes=False) Graphics object consisting of 1 graphics primitive sage: bar_chart([-2,8,-7,3], rgbcolor=(1,0,0)).show(axes=False) # These are equivalent """ dl = len(datalist) #if dl > 1: # print "WARNING, currently only 1 data set allowed" # datalist = datalist[0] if dl == 3: datalist = datalist + [0] #bardata = [] #cnt = 1 #for pnts in datalist: #ind = [i+cnt/dl for i in range(len(pnts))] #bardata.append([ind, pnts, xrange, yrange]) #cnt += 1 g = Graphics() g._set_extra_kwds(Graphics._extract_kwds_for_show(options)) #TODO: improve below for multiple data sets! #cnt = 1 #for ind, pnts, xrange, yrange in bardata: #options={'rgbcolor':hue(cnt/dl),'width':0.5/dl} # g._bar_chart(ind, pnts, xrange, yrange, options=options) # cnt += 1 #else: ind = list(range(len(datalist))) g.add_primitive(BarChart(ind, datalist, options=options)) if options['legend_label']: g.legend(True) return g
def legend_3d(hyperplane_arrangement, hyperplane_colors, length): r""" Create plot of a 3d legend for an arrangement of planes in 3-space. The ``length`` parameter determines whether short or long labels are used in the legend. INPUT: - ``hyperplane_arrangement`` -- a hyperplane arrangement - ``hyperplane_colors`` -- list of colors - ``length`` -- either ``'short'`` or ``'long'`` OUTPUT: - A graphics object. EXAMPLES:: sage: a = hyperplane_arrangements.semiorder(3) sage: from sage.geometry.hyperplane_arrangement.plot import legend_3d sage: legend_3d(a, list(colors.values())[:6],length='long') Graphics object consisting of 6 graphics primitives sage: b = hyperplane_arrangements.semiorder(4) sage: c = b.essentialization() sage: legend_3d(c, list(colors.values())[:12], length='long') Graphics object consisting of 12 graphics primitives sage: legend_3d(c, list(colors.values())[:12], length='short') Graphics object consisting of 12 graphics primitives sage: p = legend_3d(c, list(colors.values())[:12], length='short') sage: p.set_legend_options(ncol=4) sage: type(p) <class 'sage.plot.graphics.Graphics'> """ if hyperplane_arrangement.dimension() != 3: raise ValueError('arrangements must be in 3-space') hyps = hyperplane_arrangement.hyperplanes() N = len(hyperplane_arrangement) if length == 'short': labels = [' ' + str(i) for i in range(N)] else: labels = [' ' + hyps[i]._repr_linear(include_zero=False) for i in range(N)] p = Graphics() for i in range(N): p += line([(0,0),(0,0)], color=hyperplane_colors[i], thickness=8, legend_label=labels[i], axes=False) p.set_legend_options(title='Hyperplanes', loc='center', labelspacing=0.4, fancybox=True, font_size='x-large', ncol=2) p.legend(True) return p
def bezier_path(self): """ Return ``self`` as a Bezier path. This is needed to concatenate arcs, in order to create hyperbolic polygons. EXAMPLES:: sage: from sage.plot.arc import Arc sage: op = {'alpha':1,'thickness':1,'rgbcolor':'blue','zorder':0, ....: 'linestyle':'--'} sage: Arc(2,3,2.2,2.2,0,2,3,op).bezier_path() Graphics object consisting of 1 graphics primitive sage: a = arc((0,0),2,1,0,(pi/5,pi/2+pi/12), linestyle="--", color="red") sage: b = a[0].bezier_path() sage: b[0] Bezier path from (1.133..., 0.8237...) to (-0.2655..., 0.9911...) """ from sage.plot.bezier_path import BezierPath from sage.plot.graphics import Graphics from matplotlib.path import Path import numpy as np ma = self._matplotlib_arc() def theta_stretch(theta, scale): theta = np.deg2rad(theta) x = np.cos(theta) y = np.sin(theta) return np.rad2deg(np.arctan2(scale * y, x)) theta1 = theta_stretch(ma.theta1, ma.width / ma.height) theta2 = theta_stretch(ma.theta2, ma.width / ma.height) pa = ma pa._path = Path.arc(theta1, theta2) transform = pa.get_transform().get_matrix() cA, cC, cE = transform[0] cB, cD, cF = transform[1] points = [] for u in pa._path.vertices: x, y = list(u) points += [(cA * x + cC * y + cE, cB * x + cD * y + cF)] cutlist = [points[0:4]] N = 4 while N < len(points): cutlist += [points[N:N + 3]] N += 3 g = Graphics() opt = self.options() opt['fill'] = False g.add_primitive(BezierPath(cutlist, opt)) return g
def bezier_path(self): """ Return ``self`` as a Bezier path. This is needed to concatenate arcs, in order to create hyperbolic polygons. EXAMPLES:: sage: from sage.plot.arc import Arc sage: op = {'alpha':1,'thickness':1,'rgbcolor':'blue','zorder':0, ....: 'linestyle':'--'} sage: Arc(2,3,2.2,2.2,0,2,3,op).bezier_path() Graphics object consisting of 1 graphics primitive sage: a = arc((0,0),2,1,0,(pi/5,pi/2+pi/12), linestyle="--", color="red") sage: b = a[0].bezier_path() sage: b[0] Bezier path from (1.133..., 0.8237...) to (-0.2655..., 0.9911...) """ from sage.plot.bezier_path import BezierPath from sage.plot.graphics import Graphics from matplotlib.path import Path import numpy as np ma = self._matplotlib_arc() def theta_stretch(theta, scale): theta = np.deg2rad(theta) x = np.cos(theta) y = np.sin(theta) return np.rad2deg(np.arctan2(scale * y, x)) theta1 = theta_stretch(ma.theta1, ma.width / ma.height) theta2 = theta_stretch(ma.theta2, ma.width / ma.height) pa = ma pa._path = Path.arc(theta1, theta2) transform = pa.get_transform().get_matrix() cA, cC, cE = transform[0] cB, cD, cF = transform[1] points = [] for u in pa._path.vertices: x, y = list(u) points += [(cA * x + cC * y + cE, cB * x + cD * y + cF)] cutlist = [points[0: 4]] N = 4 while N < len(points): cutlist += [points[N: N + 3]] N += 3 g = Graphics() opt = self.options() opt['fill'] = False g.add_primitive(BezierPath(cutlist, opt)) return g
def plot3d(self, depth=None): # FIXME: refactor this before publishing from sage.plot.graphics import Graphics from sage.plot.point import point from sage.misc.flatten import flatten from sage.plot.plot3d.shapes2 import sphere if self._n != 3: raise ValueError("Can only 3d plot fans.") if depth == None: depth = self._depth if not self.is_finite() and depth == infinity: raise ValueError( "For infinite algebras you must specify the depth.") colors = dict([(0, 'red'), (1, 'green'), (2, 'blue'), (3, 'cyan')]) G = Graphics() roots = self.d_vectors(depth=depth) compatible = [] while roots: x = roots.pop() for y in roots: if self.compatibility_degree(x, y) == 0: compatible.append((x, y)) for (u, v) in compatible: G += _arc3d((_normalize(vector(u)), _normalize(vector(v))), thickness=0.5, color='black') for i in range(3): orbit = self.ith_orbit(i, depth=depth) for j in orbit: G += point(_normalize(vector(orbit[j])), color=colors[i], size=10, zorder=len(G.all)) if self.is_affine(): tube_vectors = map(vector, flatten(self.affine_tubes())) tube_vectors = map(_normalize, tube_vectors) for v in tube_vectors: G += point(v, color=colors[3], size=10, zorder=len(G.all)) G += _arc3d((tube_vectors[0], tube_vectors[1]), thickness=5, color='gray', zorder=0) G += sphere((0, 0, 0), opacity=0.1, zorder=0) G._extra_kwds['frame'] = False G._extra_kwds['aspect_ratio'] = 1 return G
def bar_chart(datalist, **options): """ A bar chart of (currently) one list of numerical data. Support for more data lists in progress. EXAMPLES: A bar_chart with blue bars:: sage: bar_chart([1,2,3,4]) Graphics object consisting of 1 graphics primitive A bar_chart with thinner bars:: sage: bar_chart([x^2 for x in range(1,20)], width=0.2) Graphics object consisting of 1 graphics primitive A bar_chart with negative values and red bars:: sage: bar_chart([-3,5,-6,11], rgbcolor=(1,0,0)) Graphics object consisting of 1 graphics primitive A bar chart with a legend (it's possible, not necessarily useful):: sage: bar_chart([-1,1,-1,1], legend_label='wave') Graphics object consisting of 1 graphics primitive Extra options will get passed on to show(), as long as they are valid:: sage: bar_chart([-2,8,-7,3], rgbcolor=(1,0,0), axes=False) Graphics object consisting of 1 graphics primitive sage: bar_chart([-2,8,-7,3], rgbcolor=(1,0,0)).show(axes=False) # These are equivalent """ dl = len(datalist) #if dl > 1: # print "WARNING, currently only 1 data set allowed" # datalist = datalist[0] if dl == 3: datalist = datalist+[0] #bardata = [] #cnt = 1 #for pnts in datalist: #ind = [i+cnt/dl for i in range(len(pnts))] #bardata.append([ind, pnts, xrange, yrange]) #cnt += 1 g = Graphics() g._set_extra_kwds(Graphics._extract_kwds_for_show(options)) #TODO: improve below for multiple data sets! #cnt = 1 #for ind, pnts, xrange, yrange in bardata: #options={'rgbcolor':hue(cnt/dl),'width':0.5/dl} # g._bar_chart(ind, pnts, xrange, yrange, options=options) # cnt += 1 #else: ind = list(range(len(datalist))) g.add_primitive(BarChart(ind, datalist, options=options)) if options['legend_label']: g.legend(True) return g
def plot(self, **options): r""" Plot this cylinder in coordinates used by a graphical surface. This plots this cylinder as a union of subpolygons. Only the intersections with polygons visible in the graphical surface are shown. Parameters other than `graphical_surface` are passed to `polygon2d` which is called to render the polygons. Parameters ---------- graphical_surface : a GraphicalSurface If not provided or `None`, the plot method uses the default graphical surface for the surface. """ if "graphical_surface" in options and options[ "graphical_surface"] is not None: gs = options["graphical_surface"] assert gs.get_surface( ) == self._s, "Graphical surface for the wrong surface." del options["graphical_surface"] else: gs = self._s.graphical_surface() plt = Graphics() for l, p in self.polygons(): if gs.is_visible(l): gp = gs.graphical_polygon(l) t = gp.transformation() pp = t(p) poly = polygon2d(pp.vertices(), **options) plt += poly.plot() return plt
def circle_image(A, B): G = Graphics() G += circle((0, 0), 1, color='grey') from collections import defaultdict tmp = defaultdict(int) for a in A: for j in range(a): if gcd(j, a) == 1: rational = Rational(j) / Rational(a) tmp[(rational.numerator(), rational.denominator())] += 1 for b in B: for j in range(b): if gcd(j, b) == 1: rational = Rational(j) / Rational(b) tmp[(rational.numerator(), rational.denominator())] -= 1 C = ComplexField() for val in tmp: if tmp[val] > 0: G += text(str(tmp[val]), exp(C(-.2 + 2 * 3.14159 * I * val[0] / val[1])), fontsize=30, axes=False, color="green") if tmp[val] < 0: G += text(str(abs(tmp[val])), exp(C(.2 + 2 * 3.14159 * I * val[0] / val[1])), fontsize=30, axes=False, color="blue") return G
def plot_error_function(model_filename, N, x0, Tfrac=0.8): """ Plot the estimated error of the linearized ODE as a function of time. INPUT: - ``model_filename`` -- string containing the model in text format - ``N`` -- truncation order - ``x0`` -- initial point, a list - ``Tfrac`` -- (optional, default: `0.8`): fraction of the convergence radius, to specify the plotting range in the time axis NOTE: This function calls ``error_function`` for the error computations. """ from sage.plot.graphics import Graphics from sage.plot.plot import plot from sage.plot.line import line [Ts, eps] = error_function(model_filename, N, x0) P = Graphics() P = plot(eps, 0, Ts * Tfrac, axes_labels=["$t$", r"$\mathcal{E}(t)$"]) P += line([[Ts, 0], [Ts, eps(t=Ts * Tfrac)]], linestyle='dotted', color='black') return P
def plot_n_cylinders(self, n, labels=True): r""" EXAMPLES:: sage: from slabbe.markov_transformation import markov_transformations sage: T = markov_transformations.Selmer() sage: G = T.plot_n_cylinders(3) TESTS:: sage: G = T.plot_n_cylinders(0) """ from sage.plot.graphics import Graphics from sage.plot.polygon import polygon from sage.plot.text import text M3to2 = projection_matrix(3, 2) G = Graphics() for w, cyl in self.n_cylinders_iterator(n): columns = cyl.columns() G += polygon((M3to2 * col / col.norm(1) for col in columns), fill=False) if labels: sum_cols = sum(columns) G += text("{}".format(w), M3to2 * sum_cols / sum_cols.norm(1)) return G
def plot(self, **kwargs): """ Plot this Newton polygon. .. NOTE:: All usual rendering options (color, thickness, etc.) are available. EXAMPLES: sage: from sage.geometry.newton_polygon import NewtonPolygon sage: NP = NewtonPolygon([ (0,0), (1,1), (2,6) ]) sage: polygon = NP.plot() """ vertices = self.vertices() if len(vertices) == 0: from sage.plot.graphics import Graphics return Graphics() else: from sage.plot.line import line (xstart, ystart) = vertices[0] (xend, yend) = vertices[-1] if self.last_slope() is Infinity: return line([(xstart, ystart+1), (xstart,ystart+0.5)], linestyle="--", **kwargs) \ + line([(xstart, ystart+0.5)] + vertices + [(xend, yend+0.5)], **kwargs) \ + line([(xend, yend+0.5), (xend, yend+1)], linestyle="--", **kwargs) else: return line([(xstart, ystart+1), (xstart,ystart+0.5)], linestyle="--", **kwargs) \ + line([(xstart, ystart+0.5)] + vertices + [(xend+0.5, yend + 0.5*self.last_slope())], **kwargs) \ + line([(xend+0.5, yend + 0.5*self.last_slope()), (xend+1, yend+self.last_slope())], linestyle="--", **kwargs)
def plot_n_matrices_eigenvectors(self, n, side='right', color_index=0, draw_line=False): r""" INPUT: - ``n`` -- integer, length - ``side`` -- ``'left'`` or ``'right'``, drawing left or right eigenvectors - ``color_index`` -- 0 for first letter, -1 for last letter - ``draw_line`` -- boolean EXAMPLES:: sage: from slabbe.matrix_cocycle import cocycles sage: ARP = cocycles.ARP() sage: G = ARP.plot_n_matrices_eigenvectors(2) """ from sage.plot.graphics import Graphics from sage.plot.point import point from sage.plot.line import line from sage.plot.text import text from sage.plot.colors import hue from sage.modules.free_module_element import vector from .matrices import M3to2 R = self.n_matrices_eigenvectors(n) L = [(w, M3to2*(a/sum(a)), M3to2*(b/sum(b))) for (w,a,b) in R] G = Graphics() alphabet = self._language._alphabet color_ = dict( (letter, hue(i/float(len(alphabet)))) for i,letter in enumerate(alphabet)) for letter in alphabet: L_filtered = [(w,p1,p2) for (w,p1,p2) in L if w[color_index] == letter] words,rights,lefts = zip(*L_filtered) if side == 'right': G += point(rights, color=color_[letter], legend_label=letter) elif side == 'left': G += point(lefts, color=color_[letter], legend_label=letter) else: raise ValueError("side(=%s) should be left or right" % side) if draw_line: for (a,b) in L: G += line([a,b], color='black', linestyle=":") G += line([M3to2*vector(a) for a in [(1,0,0), (0,1,0), (0,0,1), (1,0,0)]]) title = "%s eigenvectors, colored by letter w[%s] of cylinder w" % (side, color_index) G += text(title, (0.5, 1.05), axis_coords=True) G.axes(False) return G
def plot_n_matrices_eigenvectors(self, n, side='right', color_index=0, draw_line=False): r""" INPUT: - ``n`` -- integer, length - ``side`` -- ``'left'`` or ``'right'``, drawing left or right eigenvectors - ``color_index`` -- 0 for first letter, -1 for last letter - ``draw_line`` -- boolean EXAMPLES:: sage: from slabbe.matrix_cocycle import cocycles sage: ARP = cocycles.ARP() sage: G = ARP.plot_n_matrices_eigenvectors(2) """ from sage.plot.graphics import Graphics from sage.plot.point import point from sage.plot.line import line from sage.plot.text import text from sage.plot.colors import hue from sage.modules.free_module_element import vector from matrices import M3to2 R = self.n_matrices_eigenvectors(n) L = [(w, M3to2*(a/sum(a)), M3to2*(b/sum(b))) for (w,a,b) in R] G = Graphics() alphabet = self._language._alphabet color_ = dict( (letter, hue(i/float(len(alphabet)))) for i,letter in enumerate(alphabet)) for letter in alphabet: L_filtered = [(w,p1,p2) for (w,p1,p2) in L if w[color_index] == letter] words,rights,lefts = zip(*L_filtered) if side == 'right': G += point(rights, color=color_[letter], legend_label=letter) elif side == 'left': G += point(lefts, color=color_[letter], legend_label=letter) else: raise ValueError("side(=%s) should be left or right" % side) if draw_line: for (a,b) in L: G += line([a,b], color='black', linestyle=":") G += line([M3to2*vector(a) for a in [(1,0,0), (0,1,0), (0,0,1), (1,0,0)]]) title = "%s eigenvectors, colored by letter w[%s] of cylinder w" % (side, color_index) G += text(title, (0.5, 1.05), axis_coords=True) G.axes(False) return G
def piecewise_constant_image(A, B): # Jumps up and down going around circle, not used v = circle_drops(A, B) G = Graphics() w = ((Rational(i) / len(v), j) for i, j in enumerate(v)) for p0, p1 in w: G += line([(p0, p1), (p0 + Rational(1) / len(w), p1)]) return G
def piecewise_linear_image(A,B): # Jumps up and down going around circle, not used v = circle_drops(A,B) G = Graphics() w = [(Rational(i)/len(v), j) for i,j in enumerate(v)] for pt in w: G += line([(pt[0],pt[1]),(pt[0]+Rational(1)/len(w),pt[1])]) return G
def plot(self, geosub, color=None): r""" EXAMPLES:: sage: from EkEkstar import kFace, kPatch, GeoSub sage: sub = {1:[1,2], 2:[1,3], 3:[1]} sage: geosub = GeoSub(sub,2, dual=True) sage: P = kPatch([kFace((0,0,0),(1,2),dual=True), ....: kFace((0,0,1),(1,3),dual=True), ....: kFace((0,1,0),(2,1),dual=True), ....: kFace((0,0,0),(3,1),dual=True)]) sage: _ = P.plot(geosub) """ G = Graphics() for face, m in self: G += face._plot(geosub, color) G.set_aspect_ratio(1) return G
def plot3d(self,depth=None): # FIXME: refactor this before publishing from sage.plot.graphics import Graphics from sage.plot.point import point from sage.misc.flatten import flatten from sage.plot.plot3d.shapes2 import sphere if self._n !=3: raise ValueError("Can only 3d plot fans.") if depth == None: depth = self._depth if not self.is_finite() and depth==infinity: raise ValueError("For infinite algebras you must specify the depth.") colors = dict([(0,'red'),(1,'green'),(2,'blue'),(3,'cyan')]) G = Graphics() roots = self.d_vectors(depth=depth) compatible = [] while roots: x = roots.pop() for y in roots: if self.compatibility_degree(x,y) == 0: compatible.append((x,y)) for (u,v) in compatible: G += _arc3d((_normalize(vector(u)),_normalize(vector(v))),thickness=0.5,color='black') for i in range(3): orbit = self.ith_orbit(i,depth=depth) for j in orbit: G += point(_normalize(vector(orbit[j])),color=colors[i],size=10,zorder=len(G.all)) if self.is_affine(): tube_vectors=map(vector,flatten(self.affine_tubes())) tube_vectors=map(_normalize,tube_vectors) for v in tube_vectors: G += point(v,color=colors[3],size=10,zorder=len(G.all)) G += _arc3d((tube_vectors[0],tube_vectors[1]),thickness=5,color='gray',zorder=0) G += sphere((0,0,0),opacity=0.1,zorder=0) G._extra_kwds['frame']=False G._extra_kwds['aspect_ratio']=1 return G
def plot(self, pointsize=20): from sage.plot.graphics import Graphics G = Graphics() m = len(self._scales) for i, (name, scale) in enumerate(zip(self._scale_names, self._scales)): G += list_plot(scale, color=hue(1. * i / m), legend_label=name, pointsize=pointsize) return G
def plot(self, m, pointsize=100, thickness=3, axes=False): r""" Return 2d graphics object contained in the primal box [-m,m]^d. INPUT: - ``pointsize``, integer (default:``100``), - ``thickness``, integer (default:``3``), - ``axes``, bool (default:``False``), EXAMPLES:: sage: from slabbe import BondPercolationSample sage: S = BondPercolationSample(0.5,2) sage: S.plot(2) # optional long It works in 3d!!:: sage: S = BondPercolationSample(0.5,3) sage: S.plot(3, pointsize=10, thickness=1) # optional long Graphics3d Object """ s = "" s += "\\begin{tikzpicture}\n" s += "[inner sep=0pt,thick,\n" s += "reddot/.style={fill=red,draw=red,circle,minimum size=5pt}]\n" s += "\\clip %s rectangle %s;\n" % ((-m - .4, -m - .4), (m + .4, m + .4)) G = Graphics() for u in self.cluster_in_box(m + 1): G += point(u, color='blue', size=pointsize) for (u, v) in self.edges_in_box(m + 1): G += line((u, v), thickness=thickness, alpha=0.8) G += text("p=%.3f" % self._p, (0.5, 1.03), axis_coords=True, color='black') G += circle((0, 0), 0.5, color='red', thickness=thickness) if self._dimension == 2: G.axes(axes) return G
def plot2d(self,depth=None): # FIXME: refactor this before publishing from sage.plot.line import line from sage.plot.graphics import Graphics if self._n !=2: raise ValueError("Can only 2d plot fans.") if depth == None: depth = self._depth if not self.is_finite() and depth==infinity: raise ValueError("For infinite algebras you must specify the depth.") colors = dict([(0,'red'),(1,'green')]) G = Graphics() for i in range(2): orbit = self.ith_orbit(i,depth=depth) for j in orbit: G += line([(0,0),vector(orbit[j])],color=colors[i],thickness=0.5, zorder=2*j+1) G.set_aspect_ratio(1) G._show_axes = False return G
def bezier_path(self): """ Return ``self`` as a Bezier path. This is needed to concatenate arcs, in order to create hyperbolic polygons. EXAMPLES:: sage: from sage.plot.arc import Arc sage: op = {'alpha':1,'thickness':1,'rgbcolor':'blue','zorder':0, ....: 'linestyle':'--'} sage: Arc(2,3,2.2,2.2,0,2,3,op).bezier_path() Graphics object consisting of 1 graphics primitive sage: a = arc((0,0),2,1,0,(pi/5,pi/2+pi/12), linestyle="--", color="red") sage: b = a[0].bezier_path() sage: b[0] Bezier path from (1.618..., 0.5877...) to (-0.5176..., 0.9659...) """ from sage.plot.bezier_path import BezierPath from sage.plot.graphics import Graphics ma = self._matplotlib_arc() transform = ma.get_transform().get_matrix() cA, cC, cE = transform[0] cB, cD, cF = transform[1] points = [] for u in ma._path.vertices: x, y = list(u) points += [(cA * x + cC * y + cE, cB * x + cD * y + cF)] cutlist = [points[0: 4]] N = 4 while N < len(points): cutlist += [points[N: N + 3]] N += 3 g = Graphics() opt = self.options() opt['fill'] = False g.add_primitive(BezierPath(cutlist, opt)) return g
def bezier_path(self): """ Return ``self`` as a Bezier path. This is needed to concatenate arcs, in order to create hyperbolic polygons. EXAMPLES:: sage: from sage.plot.arc import Arc sage: op = {'alpha':1,'thickness':1,'rgbcolor':'blue','zorder':0, ....: 'linestyle':'--'} sage: Arc(2,3,2.2,2.2,0,2,3,op).bezier_path() Graphics object consisting of 1 graphics primitive sage: a = arc((0,0),2,1,0,(pi/5,pi/2+pi/12), linestyle="--", color="red") sage: b = a[0].bezier_path() sage: b[0] Bezier path from (1.618..., 0.5877...) to (-0.5176..., 0.9659...) """ from sage.plot.bezier_path import BezierPath from sage.plot.graphics import Graphics ma = self._matplotlib_arc() transform = ma.get_transform().get_matrix() cA, cC, cE = transform[0] cB, cD, cF = transform[1] points = [] for u in ma._path.vertices: x, y = list(u) points += [(cA * x + cC * y + cE, cB * x + cD * y + cF)] cutlist = [points[0:4]] N = 4 while N < len(points): cutlist += [points[N:N + 3]] N += 3 g = Graphics() opt = self.options() opt['fill'] = False g.add_primitive(BezierPath(cutlist, opt)) return g
def legend_3d(hyperplane_arrangement, hyperplane_colors, length): r""" Create plot of a 3d legend for an arrangement of planes in 3-space. The ``length`` parameter determines whether short or long labels are used in the legend. INPUT: - ``hyperplane_arrangement`` -- a hyperplane arrangement - ``hyperplane_colors`` -- list of colors - ``length`` -- either ``'short'`` or ``'long'`` OUTPUT: - A graphics object. EXAMPLES:: sage: a = hyperplane_arrangements.semiorder(3) sage: from sage.geometry.hyperplane_arrangement.plot import legend_3d sage: legend_3d(a, colors.values()[:6],length='long') sage: b = hyperplane_arrangements.semiorder(4) sage: c = b.essentialization() sage: legend_3d(c, colors.values()[:12], length='long') sage: legend_3d(c, colors.values()[:12], length='short') sage: p = legend_3d(c, colors.values()[:12], length='short') sage: p.set_legend_options(ncol=4) sage: type(p) <class 'sage.plot.graphics.Graphics'> """ if hyperplane_arrangement.dimension() != 3: raise ValueError('arrangements must be in 3-space') hyps = hyperplane_arrangement.hyperplanes() N = len(hyperplane_arrangement) if length == 'short': labels = [' ' + str(i) for i in range(N)] else: labels = [ ' ' + hyps[i]._repr_linear(include_zero=False) for i in range(N) ] p = Graphics() for i in range(N): p += line([(0, 0), (0, 0)], color=hyperplane_colors[i], thickness=8, legend_label=labels[i], axes=False) p.set_legend_options(title='Hyperplanes', loc='center', labelspacing=0.4, fancybox=True, font_size='x-large', ncol=2) p.legend(True) return p
def plot(self, m, pointsize=100, thickness=3, axes=False): r""" Return 2d graphics object contained in the primal box [-m,m]^d. INPUT: - ``pointsize``, integer (default:``100``), - ``thickness``, integer (default:``3``), - ``axes``, bool (default:``False``), EXAMPLES:: sage: from slabbe import BondPercolationSample sage: S = BondPercolationSample(0.5,2) sage: S.plot(2) # optional long It works in 3d!!:: sage: S = BondPercolationSample(0.5,3) sage: S.plot(3, pointsize=10, thickness=1) # optional long Graphics3d Object """ s = "" s += "\\begin{tikzpicture}\n" s += "[inner sep=0pt,thick,\n" s += "reddot/.style={fill=red,draw=red,circle,minimum size=5pt}]\n" s += "\\clip %s rectangle %s;\n" % ((-m-.4,-m-.4), (m+.4,m+.4)) G = Graphics() for u in self.cluster_in_box(m+1): G += point(u, color='blue', size=pointsize) for (u,v) in self.edges_in_box(m+1): G += line((u,v), thickness=thickness, alpha=0.8) G += text("p=%.3f" % self._p, (0.5,1.03), axis_coords=True, color='black') G += circle((0,0), 0.5, color='red', thickness=thickness) if self._dimension == 2: G.axes(axes) return G
def plot_cluster_fan_stereographically(self, northsign=1, north=None, right=None, colors=None): from sage.plot.graphics import Graphics from sage.plot.point import point from sage.misc.flatten import flatten from sage.plot.line import line from sage.misc.functional import norm if self.rk !=3: raise ValueError("Can only stereographically project fans in 3d.") if not self.is_finite() and self._depth == infinity: raise ValueError("For infinite algebras you must specify the depth.") if north == None: if self.is_affine(): north = vector(self.delta()) else: north = vector( (-1,-1,-1) ) if right == None: if self.is_affine(): right = vector(self.gamma()) else: right = vector( (1,0,0) ) if colors == None: colors = dict([(0,'red'),(1,'green'),(2,'blue'),(3,'cyan'),(4,'yellow')]) G = Graphics() roots = list(self.g_vectors()) compatible = [] while roots: x = roots.pop() for y in roots: if self.compatibility_degree(x,y) == 0: compatible.append((x,y)) for (u,v) in compatible: G += _stereo_arc(vector(u),vector(v),vector(u+v),north=northsign*north,right=right,thickness=0.5,color='black') for i in range(3): orbit = self.ith_orbit(i) for j in orbit: G += point(_stereo_coordinates(vector(orbit[j]),north=northsign*north,right=right),color=colors[i],zorder=len(G)) if self.is_affine(): tube_vectors = map(vector,flatten(self.affine_tubes())) for v in tube_vectors: G += point(_stereo_coordinates(v,north=northsign*north,right=right),color=colors[3],zorder=len(G)) if north != vector(self.delta()): G += _stereo_arc(tube_vectors[0],tube_vectors[1],vector(self.delta()),north=northsign*north,right=right,thickness=2,color=colors[4],zorder=0) else: # FIXME: refactor this before publishing tube_projections = [ _stereo_coordinates(v,north=northsign*north,right=right) for v in tube_vectors ] t=min((G.get_minmax_data()['xmax'],G.get_minmax_data()['ymax'])) G += line([tube_projections[0],tube_projections[0]+t*(_normalize(tube_projections[0]-tube_projections[1]))],thickness=2,color=colors[4],zorder=0) G += line([tube_projections[1],tube_projections[1]+t*(_normalize(tube_projections[1]-tube_projections[0]))],thickness=2,color=colors[4],zorder=0) G.set_aspect_ratio(1) G._show_axes = False return G
def make_chart(self, region=None): G = Graphics() smin, smax, nmin, nmax = [], [], [], [] for d in self.chartgens(region): x, y = self.get_coords(d) G += point2d((x, y), zorder=10) smin.append(y) smax.append(y) nmin.append(x) nmax.append(x) smin = [ min(smin), ] smax = [ max(smax), ] nmin = [ min(nmin), ] nmax = [ max(nmax), ] for l in self.chartlines(): try: pts, lineargs = self.lineinfo[l["line"]] x1, y1 = self.get_coords(self.geninfo(int(l["src"]))) x2, y2 = self.get_coords(self.geninfo(int(l["tar"]))) G += line2d([(x1, y1), (x2, y2)], **lineargs) except: print("line not understood:", l) off = 0.5 G.ymin(smin[0] - off) G.ymax(smax[0] + off) G.xmin(nmin[0] - off) G.xmax(nmax[0] + off) return G
def plot_n_cylinders(self, n, labels=True): r""" EXAMPLES:: sage: from slabbe.matrix_cocycle import cocycles sage: C = cocycles.Sorted_ARP() sage: G = C.plot_n_cylinders(3) """ from sage.plot.graphics import Graphics from sage.plot.polygon import polygon from sage.plot.text import text from matrices import M3to2 G = Graphics() for w,cyl in self.n_cylinders_iterator(n): columns = cyl.columns() G += polygon((M3to2*col/col.norm(1) for col in columns), fill=False) if labels: sum_cols = sum(columns) G += text("{}".format(w), M3to2*sum_cols/sum_cols.norm(1)) return G
def render_2d(projection, point_opts={}, line_opts={}, polygon_opts={}): """ Return 2d rendering of the projection of a polyhedron into 2-dimensional ambient space. EXAMPLES:: sage: p1 = Polyhedron(vertices=[[1,1]], rays=[[1,1]]) sage: q1 = p1.projection() sage: p2 = Polyhedron(vertices=[[1,0], [0,1], [0,0]]) sage: q2 = p2.projection() sage: p3 = Polyhedron(vertices=[[1,2]]) sage: q3 = p3.projection() sage: p4 = Polyhedron(vertices=[[2,0]], rays=[[1,-1]], lines=[[1,1]]) sage: q4 = p4.projection() sage: q1.show() + q2.show() + q3.show() + q4.show() sage: from sage.geometry.polyhedron.plot import render_2d sage: q = render_2d(p1.projection()) sage: q._objects [Point set defined by 1 point(s), Arrow from (1.0,1.0) to (2.0,2.0), Polygon defined by 3 points] """ if is_Polyhedron(projection): projection = Projection(projection) from sage.plot.graphics import Graphics plt = Graphics() if isinstance(point_opts, dict): point_opts.setdefault('zorder', 2) point_opts.setdefault('pointsize', 10) plt += projection.render_points_2d(**point_opts) if isinstance(line_opts, dict): line_opts.setdefault('zorder', 1) plt += projection.render_outline_2d(**line_opts) if isinstance(polygon_opts, dict): polygon_opts.setdefault('zorder', 0) plt += projection.render_fill_2d(**polygon_opts) return plt
def circle_image(A, B): G = Graphics() G += circle((0, 0), 1, color='black', thickness=3) G += circle( (0, 0), 1.4, color='black', alpha=0 ) # This adds an invisible framing circle to the plot, which protects the aspect ratio from being skewed. from collections import defaultdict tmp = defaultdict(int) for a in A: for j in range(a): if gcd(j, a) == 1: rational = Rational(j) / Rational(a) tmp[(rational.numerator(), rational.denominator())] += 1 for b in B: for j in range(b): if gcd(j, b) == 1: rational = Rational(j) / Rational(b) tmp[(rational.numerator(), rational.denominator())] -= 1 C = ComplexField() color1 = (41 / 255, 95 / 255, 45 / 255) color2 = (0 / 255, 0 / 255, 150 / 255) for val in tmp: if tmp[val] > 0: G += text(str(tmp[val]), exp(C(-.2 + 2 * 3.14159 * I * val[0] / val[1])), fontsize=30, axes=False, color=color1) if tmp[val] < 0: G += text(str(abs(tmp[val])), exp(C(.2 + 2 * 3.14159 * I * val[0] / val[1])), fontsize=30, axes=False, color=color2) return G
def plot(self, edge_labels=False, polygon_labels=False): from sage.plot.graphics import Graphics surface = self._surface polygons = surface.polygons() if not polygons.is_finite(): raise NotImplementedError( "no implementation for surface built from infinitely many polygons" ) if self._pos is None: self._set_positions() if self._edge_labels is None: self._set_edge_labels() G = Graphics() for a in polygons.keys(): p = polygons[a] v = p.vertices(translation=self._pos[a]) G += p.plot(translation=self._pos[a]) if edge_labels: for i in xrange(p.num_edges()): G += text(str(self._edge_labels[a, i]), (v[i] + v[(i + 1) % p.num_edges()]) / 2, color='black') # then we possibly plot the lable inside each polygon if len(self._index_set) != 1: for a in polygons.keys(): p = polygons[a] if polygon_labels: m = sum( p.vertices(translation=self._pos[a])) / p.num_edges() G += text(str(a), m, color='black') return G
def _graphics(self, plot_curve, ambient_coords, thickness=1, aspect_ratio='automatic', color='red', style='-', label_axes=True): r""" Plot a 2D or 3D curve in a Cartesian graph with axes labeled by the ambient coordinates; it is invoked by the methods :meth:`plot` of :class:`~sage.manifolds.differentiable.curve.DifferentiableCurve`, and its subclasses (:class:`~sage.manifolds.differentiable.integrated_curve.IntegratedCurve`, :class:`~sage.manifolds.differentiable.integrated_curve.IntegratedAutoparallelCurve`, and :class:`~sage.manifolds.differentiable.integrated_curve.IntegratedGeodesic`). TESTS:: sage: M = Manifold(2, 'R^2') sage: X.<x,y> = M.chart() sage: R.<t> = RealLine() sage: c = M.curve([cos(t), sin(t)], (t, 0, 2*pi), name='c') sage: graph = c._graphics([[1,2], [3,4]], [x,y]) sage: graph._objects[0].xdata == [1,3] True sage: graph._objects[0].ydata == [2,4] True sage: graph._objects[0]._options['thickness'] == 1 True sage: graph._extra_kwds['aspect_ratio'] == 'automatic' True sage: graph._objects[0]._options['rgbcolor'] == 'red' True sage: graph._objects[0]._options['linestyle'] == '-' True sage: l = [r'$'+latex(x)+r'$', r'$'+latex(y)+r'$'] sage: graph._extra_kwds['axes_labels'] == l True """ from sage.plot.graphics import Graphics from sage.plot.line import line from sage.manifolds.utilities import set_axes_labels # # The plot # n_pc = len(ambient_coords) resu = Graphics() resu += line(plot_curve, color=color, linestyle=style, thickness=thickness) if n_pc == 2: # 2D graphic resu.set_aspect_ratio(aspect_ratio) if label_axes: # We update the dictionary _extra_kwds (options to be passed # to show()), instead of using the method # Graphics.axes_labels() since the latter is not robust w.r.t. # graph addition resu._extra_kwds['axes_labels'] = [r'$'+latex(pc)+r'$' for pc in ambient_coords] else: # 3D graphic if aspect_ratio == 'automatic': aspect_ratio = 1 resu.aspect_ratio(aspect_ratio) if label_axes: labels = [str(pc) for pc in ambient_coords] resu = set_axes_labels(resu, *labels) return resu
def plot(self, chart=None, ambient_coords=None, mapping=None, prange=None, include_end_point=(True, True), end_point_offset=(0.001, 0.001), max_value=8, parameters=None, color='red', style='-', thickness=1, plot_points=75, label_axes=True, aspect_ratio='automatic'): r""" Plot the current curve (``self``) in a Cartesian graph based on the coordinates of some ambient chart. The curve is drawn in terms of two (2D graphics) or three (3D graphics) coordinates of a given chart, called hereafter the *ambient chart*. The ambient chart's domain must overlap with the curve's codomain or with the codomain of the composite curve `\Phi\circ c`, where `c` is ``self`` and `\Phi` some manifold differential mapping (argument ``mapping`` below). INPUT: - ``chart`` -- (default: ``None``) the ambient chart (see above); if ``None``, the default chart of the codomain of the curve (or of the curve composed with `\Phi`) is used - ``ambient_coords`` -- (default: ``None``) tuple containing the 2 or 3 coordinates of the ambient chart in terms of which the plot is performed; if ``None``, all the coordinates of the ambient chart are considered - ``mapping`` -- (default: ``None``) differentiable mapping `\Phi` (instance of :class:`~sage.geometry.manifolds.diffmapping.DiffMapping`) providing the link between ``self`` and the ambient chart ``chart`` (cf. above); if ``None``, the ambient chart is supposed to be defined on the codomain of the curve ``self``. - ``prange`` -- (default: ``None``) range of the curve parameter for the plot; if ``None``, the entire parameter range declared during the curve construction is considered (with -Infinity replaced by ``-max_value`` and +Infinity by ``max_value``) - ``include_end_point`` -- (default: ``(True, True)``) determines whether the end points of ``prange`` are included in the plot - ``end_point_offset`` -- (default: ``(0.001, 0.001)``) offsets from the end points when they are not included in the plot: if ``include_end_point[0] == False``, the minimal value of the curve parameter used for the plot is ``prange[0] + end_point_offset[0]``, while if ``include_end_point[1] == False``, the maximal value is ``prange[1] - end_point_offset[1]``. - ``max_value`` -- (default: 8) numerical value substituted to +Infinity if the latter is the upper bound of the parameter range; similarly ``-max_value`` is the numerical valued substituted for -Infinity - ``parameters`` -- (default: ``None``) dictionary giving the numerical values of the parameters that may appear in the coordinate expression of ``self`` - ``color`` -- (default: 'red') color of the drawn curve - ``style`` -- (default: '-') color of the drawn curve; NB: ``style`` is effective only for 2D plots - ``thickness`` -- (default: 1) thickness of the drawn curve - ``plot_points`` -- (default: 75) number of points to plot the curve - ``label_axes`` -- (default: ``True``) boolean determining whether the labels of the coordinate axes of ``chart`` shall be added to the graph; can be set to ``False`` if the graph is 3D and must be superposed with another graph. - ``aspect_ratio`` -- (default: 'automatic') aspect ratio of the plot; the default value ('automatic') applies only for 2D plots; for 3D plots, the default value is ``1`` instead. OUTPUT: - a graphic object, either an instance of :class:`~sage.plot.graphics.Graphics` for a 2D plot (i.e. based on 2 coordinates of ``chart``) or an instance of :class:`~sage.plot.plot3d.base.Graphics3d` for a 3D plot (i.e. based on 3 coordinates of ``chart``) EXAMPLES: Plot of the lemniscate of Gerono:: sage: R2 = Manifold(2, 'R^2') sage: X.<x,y> = R2.chart() sage: R.<t> = RealLine() sage: c = R2.curve([sin(t), sin(2*t)/2], (t, 0, 2*pi), name='c') sage: c.plot() # 2D plot Graphics object consisting of 1 graphics primitive Plot for a subinterval of the curve's domain:: sage: c.plot(prange=(0,pi)) Graphics object consisting of 1 graphics primitive Plot with various options:: sage: c.plot(color='green', style=':', thickness=3, aspect_ratio=1) Graphics object consisting of 1 graphics primitive Plot via a mapping to another manifold: loxodrome of a sphere viewed in `\RR^3`:: sage: S2 = Manifold(2, 'S^2') sage: U = S2.open_subset('U') sage: XS.<th,ph> = U.chart(r'th:(0,pi):\theta ph:(0,2*pi):\phi') sage: R3 = Manifold(3, 'R^3') sage: X3.<x,y,z> = R3.chart() sage: F = S2.diff_mapping(R3, {(XS, X3): [sin(th)*cos(ph), ....: sin(th)*sin(ph), cos(th)]}, name='F') sage: F.display() F: S^2 --> R^3 on U: (th, ph) |--> (x, y, z) = (cos(ph)*sin(th), sin(ph)*sin(th), cos(th)) sage: c = S2.curve([2*atan(exp(-t/10)), t], (t, -oo, +oo), name='c') sage: graph_c = c.plot(mapping=F, max_value=40, ....: plot_points=200, thickness=2, label_axes=False) # 3D plot sage: graph_S2 = XS.plot(X3, mapping=F, nb_values=11, color='black') # plot of the sphere sage: show(graph_c + graph_S2) # the loxodrome + the sphere Example of use of the argument ``parameters``: we define a curve with some symbolic parameters ``a`` and ``b``:: sage: a, b = var('a b') sage: c = R2.curve([a*cos(t) + b, a*sin(t)], (t, 0, 2*pi), name='c') To make a plot, we set spectific values for ``a`` and ``b`` by means of the Python dictionary ``parameters``:: sage: c.plot(parameters={a: 2, b: -3}, aspect_ratio=1) Graphics object consisting of 1 graphics primitive """ from sage.rings.infinity import Infinity from sage.misc.functional import numerical_approx from sage.plot.graphics import Graphics from sage.plot.line import line from sage.geometry.manifolds.chart import Chart from sage.geometry.manifolds.utilities import set_axes_labels # # The "effective" curve to be plotted # if mapping is None: eff_curve = self else: eff_curve = mapping.restrict(self.codomain()) * self # # The chart w.r.t. which the curve is plotted # if chart is None: chart = eff_curve._codomain.default_chart() elif not isinstance(chart, Chart): raise TypeError("{} is not a chart".format(chart)) # # Coordinates of the above chart w.r.t. which the curve is plotted # if ambient_coords is None: ambient_coords = chart[:] # all chart coordinates are used n_pc = len(ambient_coords) if n_pc != 2 and n_pc !=3: raise ValueError("The number of coordinates involved in the " + "plot must be either 2 or 3, not {}".format(n_pc)) ind_pc = [chart[:].index(pc) for pc in ambient_coords] # indices of plot # coordinates # # Parameter range for the plot # if prange is None: prange = (self._domain.lower_bound(), self._domain.upper_bound()) elif not isinstance(prange, (tuple, list)): raise TypeError("{} is neither a tuple nor a list".format(prange)) elif len(prange) != 2: raise ValueError("the argument prange must be a tuple/list " + "of 2 elements") tmin = prange[0] tmax = prange[1] if tmin == -Infinity: tmin = -max_value elif not include_end_point[0]: tmin = tmin + end_point_offset[0] if tmax == Infinity: tmax = max_value elif not include_end_point[1]: tmax = tmax - end_point_offset[1] tmin = numerical_approx(tmin) tmax = numerical_approx(tmax) # # The coordinate expression of the effective curve # canon_chart = self._domain.canonical_chart() transf = None for chart_pair in eff_curve._coord_expression: if chart_pair == (canon_chart, chart): transf = eff_curve._coord_expression[chart_pair] break else: # Search for a subchart for chart_pair in eff_curve._coord_expression: for schart in chart._subcharts: if chart_pair == (canon_chart, schart): transf = eff_curve._coord_expression[chart_pair] if transf is None: raise ValueError("No expression has been found for " + "{} in terms of {}".format(self, format)) # # List of points for the plot curve # plot_curve = [] dt = (tmax - tmin) / (plot_points - 1) t = tmin if parameters is None: for i in range(plot_points): x = transf(t, simplify=False) plot_curve.append( [numerical_approx(x[j]) for j in ind_pc] ) t += dt else: for i in range(plot_points): x = transf(t, simplify=False) plot_curve.append( [numerical_approx( x[j].substitute(parameters) ) for j in ind_pc] ) t += dt # # The plot # resu = Graphics() resu += line(plot_curve, color=color, linestyle=style, thickness=thickness) if n_pc==2: # 2D graphic resu.set_aspect_ratio(aspect_ratio) if label_axes: # We update the dictionary _extra_kwds (options to be passed # to show()), instead of using the method # Graphics.axes_labels() since the latter is not robust w.r.t. # graph addition resu._extra_kwds['axes_labels'] = [r'$'+latex(pc)+r'$' for pc in ambient_coords] else: # 3D graphic if aspect_ratio == 'automatic': aspect_ratio = 1 resu.aspect_ratio(aspect_ratio) if label_axes: labels = [str(pc) for pc in ambient_coords] resu = set_axes_labels(resu, *labels) return resu
def plot(self, color='sign'): """ Return a plot of ``self``. INPUT: - ``color`` -- can be any of the following: * ``4`` - use 4 colors: black, red, blue, and green with each corresponding to up, right, down, and left respectively * ``2`` - use 2 colors: red for horizontal, blue for vertical arrows * ``'sign'`` - use red for right and down arrows, blue for left and up arrows * a list of 4 colors for each direction * a function which takes a direction and a boolean corresponding to the sign EXAMPLES:: sage: M = SixVertexModel(2, boundary_conditions='ice') sage: print(M[0].plot().description()) Arrow from (-1.0,0.0) to (0.0,0.0) Arrow from (-1.0,1.0) to (0.0,1.0) Arrow from (0.0,0.0) to (0.0,-1.0) Arrow from (0.0,0.0) to (1.0,0.0) Arrow from (0.0,1.0) to (0.0,0.0) Arrow from (0.0,1.0) to (0.0,2.0) Arrow from (1.0,0.0) to (1.0,-1.0) Arrow from (1.0,0.0) to (1.0,1.0) Arrow from (1.0,1.0) to (0.0,1.0) Arrow from (1.0,1.0) to (1.0,2.0) Arrow from (2.0,0.0) to (1.0,0.0) Arrow from (2.0,1.0) to (1.0,1.0) """ from sage.plot.graphics import Graphics from sage.plot.arrow import arrow if color == 4: color_list = ['black', 'red', 'blue', 'green'] cfunc = lambda d,pm: color_list[d] elif color == 2: cfunc = lambda d,pm: 'red' if d % 2 == 0 else 'blue' elif color == 1 or color is None: cfunc = lambda d,pm: 'black' elif color == 'sign': cfunc = lambda d,pm: 'red' if pm else 'blue' # RD are True elif isinstance(color, (list, tuple)): cfunc = lambda d,pm: color[d] else: cfunc = color G = Graphics() for j,row in enumerate(reversed(self)): for i,entry in enumerate(row): if entry == 0: # LR G += arrow((i,j+1), (i,j), color=cfunc(2, True)) G += arrow((i,j), (i+1,j), color=cfunc(1, True)) if j == 0: G += arrow((i,j-1), (i,j), color=cfunc(0, False)) if i == 0: G += arrow((i,j), (i-1,j), color=cfunc(3, False)) elif entry == 1: # LU G += arrow((i,j), (i,j+1), color=cfunc(0, False)) G += arrow((i+1,j), (i,j), color=cfunc(3, False)) if j == 0: G += arrow((i,j-1), (i,j), color=cfunc(0, False)) if i == 0: G += arrow((i,j), (i-1,j), color=cfunc(3, False)) elif entry == 2: # LD G += arrow((i,j+1), (i,j), color=cfunc(2, True)) G += arrow((i+1,j), (i,j), color=cfunc(3, False)) if j == 0: G += arrow((i,j), (i,j-1), color=cfunc(2, True)) if i == 0: G += arrow((i,j), (i-1,j), color=cfunc(3, False)) elif entry == 3: # UD G += arrow((i,j), (i,j+1), color=cfunc(0, False)) G += arrow((i+1,j), (i,j), color=cfunc(3, False)) if j == 0: G += arrow((i,j), (i,j-1), color=cfunc(2, True)) if i == 0: G += arrow((i-1,j), (i,j), color=cfunc(1, True)) elif entry == 4: # UR G += arrow((i,j), (i,j+1), color=cfunc(0, False)) G += arrow((i,j), (i+1,j), color=cfunc(1, True)) if j == 0: G += arrow((i,j-1), (i,j), color=cfunc(0, False)) if i == 0: G += arrow((i-1,j), (i,j), color=cfunc(1, True)) elif entry == 5: # RD G += arrow((i,j+1), (i,j), color=cfunc(2, True)) G += arrow((i,j), (i+1,j), color=cfunc(1, True)) if j == 0: G += arrow((i,j), (i,j-1), color=cfunc(2, True)) if i == 0: G += arrow((i-1,j), (i,j), color=cfunc(1, True)) G.axes(False) return G
def plot(self): r""" Return a graphical object of the Fully Packed Loop EXAMPLES: Here is the fully packed loop for .. MATH:: \begin{pmatrix} 0&1&1 \\ 1&-1&1 \\ 0&1&0 \end{pmatrix}: .. PLOT:: :width: 200 px A = AlternatingSignMatrix([[0, 1, 0], [1, -1, 1], [0, 1, 0]]) fpl = FullyPackedLoop(A) p = fpl.plot() sphinx_plot(p) Here is how Sage represents this:: sage: A = AlternatingSignMatrix([[0, 1, 0], [1, -1, 1], [0, 1, 0]]) sage: fpl = FullyPackedLoop(A) sage: print(fpl.plot().description()) Line defined by 2 points: [(-1.0, 1.0), (0.0, 1.0)] Line defined by 2 points: [(0.0, 0.0), (0.0, -1.0)] Line defined by 2 points: [(0.0, 0.0), (1.0, 0.0)] Line defined by 2 points: [(0.0, 2.0), (0.0, 3.0)] Line defined by 2 points: [(0.0, 2.0), (0.0, 3.0)] Line defined by 2 points: [(0.0, 2.0), (1.0, 2.0)] Line defined by 2 points: [(1.0, 1.0), (0.0, 1.0)] Line defined by 2 points: [(1.0, 1.0), (2.0, 1.0)] Line defined by 2 points: [(2.0, 0.0), (1.0, 0.0)] Line defined by 2 points: [(2.0, 0.0), (2.0, -1.0)] Line defined by 2 points: [(2.0, 2.0), (1.0, 2.0)] Line defined by 2 points: [(2.0, 2.0), (2.0, 3.0)] Line defined by 2 points: [(2.0, 2.0), (2.0, 3.0)] Line defined by 2 points: [(3.0, 1.0), (2.0, 1.0)] Line defined by 2 points: [(3.0, 1.0), (2.0, 1.0)] Here are the other 3 by 3 Alternating Sign Matrices and their corresponding fully packed loops: .. MATH:: A = \begin{pmatrix} 1&0&0 \\ 0&1&0 \\ 0&0&1 \\ \end{pmatrix} gives: .. PLOT:: :width: 200 px A = AlternatingSignMatrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) fpl = FullyPackedLoop(A) p = fpl.plot() sphinx_plot(p) .. MATH:: A = \begin{pmatrix} 1&0&0 \\ 0&0&1 \\ 0&1&0 \\ \end{pmatrix} gives: .. PLOT:: :width: 200 px A = AlternatingSignMatrix([[1, 0, 0], [0, 0, 1], [0, 1, 0]]) fpl = FullyPackedLoop(A) p = fpl.plot() sphinx_plot(p) .. MATH:: A = \begin{pmatrix} 0&1&0\\ 1&0&0\\ 0&0&1\\ \end{pmatrix} gives: .. PLOT:: :width: 200 px A = AlternatingSignMatrix([[0, 1, 0], [1, 0, 0], [0, 0, 1]]) fpl = FullyPackedLoop(A) p = fpl.plot() sphinx_plot(p) .. MATH:: A = \begin{pmatrix} 0&1&0\\ 0&0&1\\ 1&0&0\\ \end{pmatrix} gives: .. PLOT:: :width: 200 px A = AlternatingSignMatrix([[0, 1, 0], [0, 0, 1], [1, 0, 0]]) fpl = FullyPackedLoop(A) p = fpl.plot() sphinx_plot(p) .. MATH:: A = \begin{pmatrix} 0&0&1\\ 1&0&0\\ 0&1&0\\ \end{pmatrix} gives: .. PLOT:: :width: 200 px A = AlternatingSignMatrix([[0, 0, 1], [1, 0, 0], [0, 1, 0]]) fpl = FullyPackedLoop(A) p = fpl.plot() sphinx_plot(p) .. MATH:: A = \begin{pmatrix} 0&0&1\\ 0&1&0\\ 1&0&0\\ \end{pmatrix} gives: .. PLOT:: :width: 200 px A = AlternatingSignMatrix([[0, 0, 1], [0, 1, 0], [1, 0, 0]]) fpl = FullyPackedLoop(A) p = fpl.plot() sphinx_plot(p) EXAMPLES:: sage: A = AlternatingSignMatrix([[0, 1, 0, 0], [1, -1, 0, 1], \ [0, 1, 0, 0],[0, 0, 1, 0]]) sage: fpl = FullyPackedLoop(A) sage: print(fpl.plot().description()) Line defined by 2 points: [(-1.0, 0.0), (0.0, 0.0)] Line defined by 2 points: [(-1.0, 2.0), (0.0, 2.0)] Line defined by 2 points: [(0.0, 1.0), (0.0, 0.0)] Line defined by 2 points: [(0.0, 1.0), (1.0, 1.0)] Line defined by 2 points: [(0.0, 3.0), (0.0, 4.0)] Line defined by 2 points: [(0.0, 3.0), (0.0, 4.0)] Line defined by 2 points: [(0.0, 3.0), (1.0, 3.0)] Line defined by 2 points: [(1.0, 0.0), (1.0, -1.0)] Line defined by 2 points: [(1.0, 0.0), (2.0, 0.0)] Line defined by 2 points: [(1.0, 2.0), (0.0, 2.0)] Line defined by 2 points: [(1.0, 2.0), (2.0, 2.0)] Line defined by 2 points: [(2.0, 1.0), (1.0, 1.0)] Line defined by 2 points: [(2.0, 1.0), (2.0, 2.0)] Line defined by 2 points: [(2.0, 3.0), (1.0, 3.0)] Line defined by 2 points: [(2.0, 3.0), (2.0, 4.0)] Line defined by 2 points: [(2.0, 3.0), (2.0, 4.0)] Line defined by 2 points: [(3.0, 0.0), (2.0, 0.0)] Line defined by 2 points: [(3.0, 0.0), (3.0, -1.0)] Line defined by 2 points: [(3.0, 2.0), (3.0, 1.0)] Line defined by 2 points: [(3.0, 2.0), (3.0, 3.0)] Line defined by 2 points: [(4.0, 1.0), (3.0, 1.0)] Line defined by 2 points: [(4.0, 1.0), (3.0, 1.0)] Line defined by 2 points: [(4.0, 3.0), (3.0, 3.0)] Line defined by 2 points: [(4.0, 3.0), (3.0, 3.0)] Here is the plot: .. PLOT:: :width: 300 px A = AlternatingSignMatrix([[0, 1, 0, 0], [1, -1, 0, 1], [0, 1, 0, 0],[0, 0, 1, 0]]) fpl = FullyPackedLoop(A) p = fpl.plot() sphinx_plot(p) """ G = Graphics() n=len(self._six_vertex_model)-1 for j,row in enumerate(reversed(self._six_vertex_model)): for i,entry in enumerate(row): if i == 0 and (i+j+n+1) % 2 ==0: G+= line([(i-1,j),(i,j)]) if i == n and (i+j+n+1) % 2 ==0: G+= line([(i+1,j),(i,j)]) if j == 0 and (i+j+n) % 2 ==0: G+= line([(i,j),(i,j-1)]) if j == n and (i+j+n) % 2 ==0: G+= line([(i,j),(i,j+1)]) if entry == 0: # LR if (i+j+n) % 2==0: G += line([(i,j), (i+1,j)]) else: G += line([(i,j),(i,j+1)]) elif entry == 1: # LU if (i+j+n) % 2 ==0: G += line([(i,j), (i,j+1)]) else: G += line([(i+1,j), (i,j)]) elif entry == 2: # LD if (i+j+n) % 2 == 0: pass else: G += line([(i,j+1), (i,j)]) G += line([(i+1,j), (i,j)]) elif entry == 3: # UD if (i+j+n) % 2 == 0: G += line([(i,j), (i,j+1)]) else: G += line([(i+1,j), (i,j)]) elif entry == 4: # UR if (i+j+n) % 2 ==0: G += line([(i,j), (i,j+1)]) G += line([(i,j), (i+1,j)]) else: pass elif entry == 5: # RD if (i+j+n) % 2 ==0: G += line([(i,j), (i+1,j)]) else: G += line([(i,j+1), (i,j)]) G.axes(False) return G
def plot(self, chart=None, ambient_coords=None, mapping=None, chart_domain=None, fixed_coords=None, ranges=None, max_value=8, nb_values=None, steps=None, scale=1, color='blue', parameters=None, label_axes=True, **extra_options): r""" Plot the vector field in a Cartesian graph based on the coordinates of some ambient chart. The vector field is drawn in terms of two (2D graphics) or three (3D graphics) coordinates of a given chart, called hereafter the *ambient chart*. The vector field's base points `p` (or their images `\Phi(p)` by some differentiable mapping `\Phi`) must lie in the ambient chart's domain. INPUT: - ``chart`` -- (default: ``None``) the ambient chart (see above); if ``None``, the default chart of the vector field's domain is used - ``ambient_coords`` -- (default: ``None``) tuple containing the 2 or 3 coordinates of the ambient chart in terms of which the plot is performed; if ``None``, all the coordinates of the ambient chart are considered - ``mapping`` -- (default: ``None``) differentiable mapping `\Phi` (instance of :class:`~sage.geometry.manifolds.diffmapping.DiffMapping`) providing the link between the vector field's domain and the ambient chart ``chart``; if ``None``, the identity mapping is assumed - ``chart_domain`` -- (default: ``None``) chart on the vector field's domain to define the points at which vector arrows are to be plotted; if ``None``, the default chart of the vector field's domain is used - ``fixed_coords`` -- (default: ``None``) dictionary with keys the coordinates of ``chart_domain`` that are kept fixed and with values the value of these coordinates; if ``None``, all the coordinates of ``chart_domain`` are used - ``ranges`` -- (default: ``None``) dictionary with keys the coordinates of ``chart_domain`` to be used and values tuples ``(x_min,x_max)`` specifying the coordinate range for the plot; if ``None``, the entire coordinate range declared during the construction of ``chart_domain`` is considered (with ``-Infinity`` replaced by ``-max_value`` and ``+Infinity`` by ``max_value``) - ``max_value`` -- (default: 8) numerical value substituted to ``+Infinity`` if the latter is the upper bound of the range of a coordinate for which the plot is performed over the entire coordinate range (i.e. for which no specific plot range has been set in ``ranges``); similarly ``-max_value`` is the numerical valued substituted for ``-Infinity`` - ``nb_values`` -- (default: ``None``) either an integer or a dictionary with keys the coordinates of ``chart_domain`` to be used and values the number of values of the coordinate for sampling the part of the vector field's domain involved in the plot ; if ``nb_values`` is a single integer, it represents the number of values for all coordinates; if ``nb_values`` is ``None``, it is set to 9 for a 2D plot and to 5 for a 3D plot - ``steps`` -- (default: ``None``) dictionary with keys the coordinates of ``chart_domain`` to be used and values the step between each constant value of the coordinate; if ``None``, the step is computed from the coordinate range (specified in ``ranges``) and ``nb_values``. On the contrary, if the step is provided for some coordinate, the corresponding number of values is deduced from it and the coordinate range. - ``scale`` -- (default: 1) value by which the lengths of the arrows representing the vectors is multiplied - ``color`` -- (default: 'blue') color of the arrows representing the vectors - ``parameters`` -- (default: ``None``) dictionary giving the numerical values of the parameters that may appear in the coordinate expression of the vector field (see example below) - ``label_axes`` -- (default: ``True``) boolean determining whether the labels of the coordinate axes of ``chart`` shall be added to the graph; can be set to ``False`` if the graph is 3D and must be superposed with another graph. - ``**extra_options`` -- extra options for the arrow plot, like ``linestyle``, ``width`` or ``arrowsize`` (see :func:`~sage.plot.arrow.arrow2d` and :func:`~sage.plot.plot3d.shapes.arrow3d` for details) OUTPUT: - a graphic object, either an instance of :class:`~sage.plot.graphics.Graphics` for a 2D plot (i.e. based on 2 coordinates of ``chart``) or an instance of :class:`~sage.plot.plot3d.base.Graphics3d` for a 3D plot (i.e. based on 3 coordinates of ``chart``) EXAMPLES: Plot of a vector field on a 2-dimensional manifold:: sage: Manifold._clear_cache_() # for doctests only sage: M = Manifold(2, 'M') sage: X.<x,y> = M.chart() sage: v = M.vector_field(name='v') sage: v[:] = -y, x ; v.display() v = -y d/dx + x d/dy sage: v.plot() Graphics object consisting of 80 graphics primitives Plot with various options:: sage: v.plot(scale=0.5, color='green', linestyle='--', width=1, arrowsize=6) Graphics object consisting of 80 graphics primitives sage: v.plot(max_value=4, nb_values=5, scale=0.5) Graphics object consisting of 24 graphics primitives Plots along a line of fixed coordinate:: sage: v.plot(fixed_coords={x: -2}) Graphics object consisting of 9 graphics primitives sage: v.plot(fixed_coords={y: 1}) Graphics object consisting of 9 graphics primitives Let us now consider a vector field on a 4-dimensional manifold:: sage: Manifold._clear_cache_() # for doctests only sage: M = Manifold(4, 'M') sage: X.<t,x,y,z> = M.chart() sage: v = M.vector_field(name='v') sage: v[:] = (t/8)^2, -t*y/4, t*x/4, t*z/4 ; v.display() v = 1/64*t^2 d/dt - 1/4*t*y d/dx + 1/4*t*x d/dy + 1/4*t*z d/dz We cannot make a 4D plot directly:: sage: v.plot() Traceback (most recent call last): ... ValueError: the number of ambient coordinates must be either 2 or 3, not 4 Rather, we have to select some coordinates for the plot, via the argument ``ambient_coords``. For instance, for a 3D plot:: sage: v.plot(ambient_coords=(x, y, z), fixed_coords={t: 1}) Graphics3d Object sage: v.plot(ambient_coords=(x, y, t), fixed_coords={z: 0}, ....: ranges={x: (-2,2), y: (-2,2), t: (-1, 4)}, nb_values=4) Graphics3d Object or, for a 2D plot:: sage: v.plot(ambient_coords=(x, y), fixed_coords={t: 1, z: 0}) Graphics object consisting of 80 graphics primitives sage: v.plot(ambient_coords=(x, t), fixed_coords={y: 1, z: 0}) Graphics object consisting of 72 graphics primitives An example of plot via a differential mapping: plot of a vector field tangent to a 2-sphere viewed in `\RR^3`:: sage: S2 = Manifold(2, 'S^2') sage: U = S2.open_subset('U') # the open set covered by spherical coord. sage: XS.<th,ph> = U.chart(r'th:(0,pi):\theta ph:(0,2*pi):\phi') sage: R3 = Manifold(3, 'R^3') sage: X3.<x,y,z> = R3.chart() sage: F = S2.diff_mapping(R3, {(XS, X3): [sin(th)*cos(ph), ....: sin(th)*sin(ph), cos(th)]}, name='F') sage: F.display() # the standard embedding of S^2 into R^3 F: S^2 --> R^3 on U: (th, ph) |--> (x, y, z) = (cos(ph)*sin(th), sin(ph)*sin(th), cos(th)) sage: v = XS.frame()[1] ; v vector field 'd/dph' on the open subset 'U' of the 2-dimensional manifold 'S^2' sage: graph_v = v.plot(chart=X3, mapping=F, label_axes=False) sage: graph_S2 = XS.plot(chart=X3, mapping=F, nb_values=9) sage: show(graph_v + graph_S2) """ from sage.rings.infinity import Infinity from sage.misc.functional import numerical_approx from sage.misc.latex import latex from sage.plot.graphics import Graphics from sage.geometry.manifolds.chart import Chart from sage.geometry.manifolds.utilities import set_axes_labels # # 1/ Treatment of input parameters # ----------------------------- if chart is None: chart = self._domain.default_chart() elif not isinstance(chart, Chart): raise TypeError("{} is not a chart".format(chart)) if chart_domain is None: chart_domain = self._domain.default_chart() elif not isinstance(chart_domain, Chart): raise TypeError("{} is not a chart".format(chart_domain)) elif not chart_domain.domain().is_subset(self._domain): raise ValueError("The domain of {} is not ".format(chart_domain) + "included in the domain of {}".format(self)) if fixed_coords is None: coords = chart_domain._xx else: fixed_coord_list = fixed_coords.keys() coords = [] for coord in chart_domain._xx: if coord not in fixed_coord_list: coords.append(coord) coords = tuple(coords) if ambient_coords is None: ambient_coords = chart[:] elif not isinstance(ambient_coords, tuple): ambient_coords = tuple(ambient_coords) nca = len(ambient_coords) if nca != 2 and nca != 3: raise ValueError("the number of ambient coordinates must be " + "either 2 or 3, not {}".format(nca)) if ranges is None: ranges = {} ranges0 = {} for coord in coords: if coord in ranges: ranges0[coord] = (numerical_approx(ranges[coord][0]), numerical_approx(ranges[coord][1])) else: bounds = chart_domain._bounds[chart_domain[:].index(coord)] if bounds[0][0] == -Infinity: xmin = numerical_approx(-max_value) elif bounds[0][1]: xmin = numerical_approx(bounds[0][0]) else: xmin = numerical_approx(bounds[0][0] + 1.e-3) if bounds[1][0] == Infinity: xmax = numerical_approx(max_value) elif bounds[1][1]: xmax = numerical_approx(bounds[1][0]) else: xmax = numerical_approx(bounds[1][0] - 1.e-3) ranges0[coord] = (xmin, xmax) ranges = ranges0 if nb_values is None: if nca == 2: # 2D plot nb_values = 9 else: # 3D plot nb_values = 5 if not isinstance(nb_values, dict): nb_values0 = {} for coord in coords: nb_values0[coord] = nb_values nb_values = nb_values0 if steps is None: steps = {} for coord in coords: if coord not in steps: steps[coord] = (ranges[coord][1] - ranges[coord][0])/ \ (nb_values[coord]-1) else: nb_values[coord] = 1 + int( (ranges[coord][1] - ranges[coord][0]) / steps[coord]) # # 2/ Plots # ----- dom = chart_domain.domain() nc = len(chart_domain[:]) ncp = len(coords) xx = [0] * nc if fixed_coords is not None: if len(fixed_coords) != nc - ncp: raise ValueError("Bad number of fixed coordinates.") for fc, val in fixed_coords.iteritems(): xx[chart_domain[:].index(fc)] = val index_p = [chart_domain[:].index(cd) for cd in coords] resu = Graphics() ind = [0] * ncp ind_max = [0] * ncp ind_max[0] = nb_values[coords[0]] xmin = [ranges[cd][0] for cd in coords] step_tab = [steps[cd] for cd in coords] while ind != ind_max: for i in range(ncp): xx[index_p[i]] = xmin[i] + ind[i] * step_tab[i] if chart_domain.valid_coordinates(*xx, tolerance=1e-13, parameters=parameters): point = dom(xx, chart=chart_domain) resu += self.at(point).plot(chart=chart, ambient_coords=ambient_coords, mapping=mapping, scale=scale, color=color, print_label=False, parameters=parameters, **extra_options) # Next index: ret = 1 for pos in range(ncp - 1, -1, -1): imax = nb_values[coords[pos]] - 1 if ind[pos] != imax: ind[pos] += ret ret = 0 elif ret == 1: if pos == 0: ind[pos] = imax + 1 # end point reached else: ind[pos] = 0 ret = 1 if label_axes: if nca == 2: # 2D graphic # We update the dictionary _extra_kwds (options to be passed # to show()), instead of using the method # Graphics.axes_labels() since the latter is not robust w.r.t. # graph addition resu._extra_kwds['axes_labels'] = [ r'$' + latex(ac) + r'$' for ac in ambient_coords ] else: # 3D graphic labels = [str(ac) for ac in ambient_coords] resu = set_axes_labels(resu, *labels) return resu
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() # long time 2s Graphics object consisting of 219 graphics primitives """ # 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): # 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 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 plot(self, chart=None, ambient_coords=None, mapping=None, prange=None, include_end_point=(True, True), end_point_offset=(0.001, 0.001), parameters=None, color='red', style='-', label_axes=True, **kwds): r""" Plot the current curve in a Cartesian graph based on the coordinates of some ambient chart. The curve is drawn in terms of two (2D graphics) or three (3D graphics) coordinates of a given chart, called hereafter the *ambient chart*. The ambient chart's domain must overlap with the curve's codomain or with the codomain of the composite curve `\Phi\circ c`, where `c` is the current curve and `\Phi` some manifold differential map (argument ``mapping`` below). INPUT: - ``chart`` -- (default: ``None``) the ambient chart (see above); if ``None``, the default chart of the codomain of the curve (or of the curve composed with `\Phi`) is used - ``ambient_coords`` -- (default: ``None``) tuple containing the 2 or 3 coordinates of the ambient chart in terms of which the plot is performed; if ``None``, all the coordinates of the ambient chart are considered - ``mapping`` -- (default: ``None``) differentiable mapping `\Phi` (instance of :class:`~sage.manifolds.differentiable.diff_map.DiffMap`) providing the link between the curve and the ambient chart ``chart`` (cf. above); if ``None``, the ambient chart is supposed to be defined on the codomain of the curve. - ``prange`` -- (default: ``None``) range of the curve parameter for the plot; if ``None``, the entire parameter range declared during the curve construction is considered (with -Infinity replaced by ``-max_range`` and +Infinity by ``max_range``) - ``include_end_point`` -- (default: ``(True, True)``) determines whether the end points of ``prange`` are included in the plot - ``end_point_offset`` -- (default: ``(0.001, 0.001)``) offsets from the end points when they are not included in the plot: if ``include_end_point[0] == False``, the minimal value of the curve parameter used for the plot is ``prange[0] + end_point_offset[0]``, while if ``include_end_point[1] == False``, the maximal value is ``prange[1] - end_point_offset[1]``. - ``max_range`` -- (default: 8) numerical value substituted to +Infinity if the latter is the upper bound of the parameter range; similarly ``-max_range`` is the numerical valued substituted for -Infinity - ``parameters`` -- (default: ``None``) dictionary giving the numerical values of the parameters that may appear in the coordinate expression of the curve - ``color`` -- (default: 'red') color of the drawn curve - ``style`` -- (default: '-') color of the drawn curve; NB: ``style`` is effective only for 2D plots - ``thickness`` -- (default: 1) thickness of the drawn curve - ``plot_points`` -- (default: 75) number of points to plot the curve - ``label_axes`` -- (default: ``True``) boolean determining whether the labels of the coordinate axes of ``chart`` shall be added to the graph; can be set to ``False`` if the graph is 3D and must be superposed with another graph. - ``aspect_ratio`` -- (default: ``'automatic'``) aspect ratio of the plot; the default value (``'automatic'``) applies only for 2D plots; for 3D plots, the default value is ``1`` instead OUTPUT: - a graphic object, either an instance of :class:`~sage.plot.graphics.Graphics` for a 2D plot (i.e. based on 2 coordinates of ``chart``) or an instance of :class:`~sage.plot.plot3d.base.Graphics3d` for a 3D plot (i.e. based on 3 coordinates of ``chart``) EXAMPLES: Plot of the lemniscate of Gerono:: sage: R2 = Manifold(2, 'R^2') sage: X.<x,y> = R2.chart() sage: R.<t> = RealLine() sage: c = R2.curve([sin(t), sin(2*t)/2], (t, 0, 2*pi), name='c') sage: c.plot() # 2D plot Graphics object consisting of 1 graphics primitive .. PLOT:: R2 = Manifold(2, 'R^2') X = R2.chart('x y') t = RealLine().canonical_coordinate() c = R2.curve([sin(t), sin(2*t)/2], (t, 0, 2*pi), name='c') g = c.plot() sphinx_plot(g) Plot for a subinterval of the curve's domain:: sage: c.plot(prange=(0,pi)) Graphics object consisting of 1 graphics primitive .. PLOT:: R2 = Manifold(2, 'R^2') X = R2.chart('x y') t = RealLine().canonical_coordinate() c = R2.curve([sin(t), sin(2*t)/2], (t, 0, 2*pi), name='c') g = c.plot(prange=(0,pi)) sphinx_plot(g) Plot with various options:: sage: c.plot(color='green', style=':', thickness=3, aspect_ratio=1) Graphics object consisting of 1 graphics primitive .. PLOT:: R2 = Manifold(2, 'R^2') X = R2.chart('x y') t = RealLine().canonical_coordinate() c = R2.curve([sin(t), sin(2*t)/2], (t, 0, 2*pi), name='c') g = c.plot(color='green', style=':', thickness=3, aspect_ratio=1) sphinx_plot(g) Plot via a mapping to another manifold: loxodrome of a sphere viewed in `\RR^3`:: sage: S2 = Manifold(2, 'S^2') sage: U = S2.open_subset('U') sage: XS.<th,ph> = U.chart(r'th:(0,pi):\theta ph:(0,2*pi):\phi') sage: R3 = Manifold(3, 'R^3') sage: X3.<x,y,z> = R3.chart() sage: F = S2.diff_map(R3, {(XS, X3): [sin(th)*cos(ph), ....: sin(th)*sin(ph), cos(th)]}, name='F') sage: F.display() F: S^2 --> R^3 on U: (th, ph) |--> (x, y, z) = (cos(ph)*sin(th), sin(ph)*sin(th), cos(th)) sage: c = S2.curve([2*atan(exp(-t/10)), t], (t, -oo, +oo), name='c') sage: graph_c = c.plot(mapping=F, max_range=40, ....: plot_points=200, thickness=2, label_axes=False) # 3D plot sage: graph_S2 = XS.plot(X3, mapping=F, number_values=11, color='black') # plot of the sphere sage: show(graph_c + graph_S2) # the loxodrome + the sphere .. PLOT:: S2 = Manifold(2, 'S^2') U = S2.open_subset('U') XS = U.chart(r'th:(0,pi):\theta ph:(0,2*pi):\phi') th, ph = XS[:] R3 = Manifold(3, 'R^3') X3 = R3.chart('x y z') F = S2.diff_map(R3, {(XS, X3): [sin(th)*cos(ph), sin(th)*sin(ph), cos(th)]}, name='F') t = RealLine().canonical_coordinate() c = S2.curve([2*atan(exp(-t/10)), t], (t, -oo, +oo), name='c') graph_c = c.plot(mapping=F, max_range=40, plot_points=200, thickness=2, label_axes=False) graph_S2 = XS.plot(X3, mapping=F, number_values=11, color='black') sphinx_plot(graph_c + graph_S2) Example of use of the argument ``parameters``: we define a curve with some symbolic parameters ``a`` and ``b``:: sage: a, b = var('a b') sage: c = R2.curve([a*cos(t) + b, a*sin(t)], (t, 0, 2*pi), name='c') To make a plot, we set spectific values for ``a`` and ``b`` by means of the Python dictionary ``parameters``:: sage: c.plot(parameters={a: 2, b: -3}, aspect_ratio=1) Graphics object consisting of 1 graphics primitive .. PLOT:: R2 = Manifold(2, 'R^2') X = R2.chart('x y') t = RealLine().canonical_coordinate() a, b = var('a b') c = R2.curve([a*cos(t) + b, a*sin(t)], (t, 0, 2*pi), name='c') g = c.plot(parameters={a: 2, b: -3}, aspect_ratio=1) sphinx_plot(g) """ from sage.rings.infinity import Infinity from sage.misc.functional import numerical_approx from sage.plot.graphics import Graphics from sage.plot.line import line from sage.manifolds.chart import RealChart from sage.manifolds.utilities import set_axes_labels # # Get the @options from kwds # thickness = kwds.pop('thickness') plot_points = kwds.pop('plot_points') max_range = kwds.pop('max_range') aspect_ratio = kwds.pop('aspect_ratio') # # The "effective" curve to be plotted # if mapping is None: eff_curve = self else: eff_curve = mapping.restrict(self.codomain()) * self # # The chart w.r.t. which the curve is plotted # if chart is None: chart = eff_curve._codomain.default_chart() elif not isinstance(chart, RealChart): raise TypeError("{} is not a real chart".format(chart)) # # Coordinates of the above chart w.r.t. which the curve is plotted # if ambient_coords is None: ambient_coords = chart[:] # all chart coordinates are used n_pc = len(ambient_coords) if n_pc != 2 and n_pc != 3: raise ValueError("the number of coordinates involved in the " + "plot must be either 2 or 3, not {}".format(n_pc)) # indices of plot coordinates ind_pc = [chart[:].index(pc) for pc in ambient_coords] # # Parameter range for the plot # if prange is None: prange = (self._domain.lower_bound(), self._domain.upper_bound()) elif not isinstance(prange, (tuple, list)): raise TypeError("{} is neither a tuple nor a list".format(prange)) elif len(prange) != 2: raise ValueError("the argument prange must be a tuple/list " + "of 2 elements") tmin = prange[0] tmax = prange[1] if tmin == -Infinity: tmin = -max_range elif not include_end_point[0]: tmin = tmin + end_point_offset[0] if tmax == Infinity: tmax = max_range elif not include_end_point[1]: tmax = tmax - end_point_offset[1] tmin = numerical_approx(tmin) tmax = numerical_approx(tmax) # # The coordinate expression of the effective curve # canon_chart = self._domain.canonical_chart() transf = None for chart_pair in eff_curve._coord_expression: if chart_pair == (canon_chart, chart): transf = eff_curve._coord_expression[chart_pair] break else: # Search for a subchart for chart_pair in eff_curve._coord_expression: for schart in chart._subcharts: if chart_pair == (canon_chart, schart): transf = eff_curve._coord_expression[chart_pair] if transf is None: raise ValueError("No expression has been found for " + "{} in terms of {}".format(self, chart)) # # List of points for the plot curve # plot_curve = [] dt = (tmax - tmin) / (plot_points - 1) t = tmin if parameters is None: for i in range(plot_points): x = transf(t, simplify=False) plot_curve.append([numerical_approx(x[j]) for j in ind_pc]) t += dt else: for i in range(plot_points): x = transf(t, simplify=False) plot_curve.append([ numerical_approx(x[j].substitute(parameters)) for j in ind_pc ]) t += dt # # The plot # resu = Graphics() resu += line(plot_curve, color=color, linestyle=style, thickness=thickness) if n_pc == 2: # 2D graphic resu.set_aspect_ratio(aspect_ratio) if label_axes: # We update the dictionary _extra_kwds (options to be passed # to show()), instead of using the method # Graphics.axes_labels() since the latter is not robust w.r.t. # graph addition resu._extra_kwds['axes_labels'] = [ r'$' + latex(pc) + r'$' for pc in ambient_coords ] else: # 3D graphic if aspect_ratio == 'automatic': aspect_ratio = 1 resu.aspect_ratio(aspect_ratio) if label_axes: labels = [str(pc) for pc in ambient_coords] resu = set_axes_labels(resu, *labels) return resu
def plot(self,radius=1, orientation=1, cyclic_order_0=None, cyclic_order_1=None,verbose=False): """Plot the set of ideal curves on the surface S=S(g,1) of genus g with one puncture. The free group has rank N=2g, the trees T0 and T1 are roses transverse to a maximal set of ideal curves on S. The convex core is transverse to the two collections of curves: vertices are connected components of the complement of the union of the two sets of curves, edges are arcs separating two regions and squares are around intersections points of curves. For instance T0 can be set to be the rose with the trivial marking, while T1 is obtained from T0 by applying a mapping class (and not a general automorphism). The embedding of the mapping class group is that generated by the ``surface_dehn_twist()`` method of the ``FreeGroup`` class. The set of ideal curves of T0 is drawn as the boundary of a regular 2N-gone, and the set of ideal curves of T1 is drawn inside this 2N-gone. INPUT: - ``radius``: (default 1) the radius of the regular 2N-gone which is the fondamental domain of the surface. -``cyclic_order_0``: (default None) List of edges outgoing from the sole vertex of T0 ordered according to the embedding in the surface. A typical value in rank 4, compatible with the definition of ``FreeGroup.surface_dehn_twist()`` is : ['A','B','a','C','D','c','d','b'] -``cyclic_order_1``: (default None) List of edges outgoing from the sole vertex of T1 ordered according to the embedding in the surface. EXAMPLES:: sage: F=FreeGroup(4) sage: phi=mul([F.surface_dehn_twist(i) for i in [2,1,1,4]]) sage: C=ConvexCore(phi) sage: C.plot_ideal_curve_diagram(cyclic_order_0=['A','B','a','C','D','c','d','b']) """ from sage.plot.graphics import Graphics from sage.plot.line import Line from sage.plot.arrow import Arrow T0=self.tree(0) T1=self.tree(1) A0=T0.alphabet() N=len(A0) A1=T1.alphabet() # Let ``self`` be the convex core of trees T0 and T1. T0 and # T1 need to be roses. The trees T0 and T1 may be given as # embedded inside the surface. In this case the edges outgoing # from the sole vertex are cyclically ordered. if len(T0.vertices())!=1: raise ValueError('The tree on side 0 must be a rose') if len(T1.vertices())!=1: raise ValueError('The tree on side 1 must be a rose') if cyclic_order_0 is None: cyclic_order_0=getattr(T0,'cyclic_order',None) if cyclic_order_1 is None: cyclic_order_1=getattr(T1,'cyclic_order',None) if verbose: if cyclic_order_0 is not None: print "The tree on side 0 is embedded in the surface:",cyclic_order_0 else: print "The tree on side 0 is not embedded in the surface, we will try to guess an embedding" if cyclic_order_1 is not None: print "The tree on side 1 is embedded in the surface:",cyclic_order_1 else: print "The tree on side 1 is not embedded in the surface, we will try to guess an embedding" squares=self.squares() # Coherent orientation of the squares orientation=self.squares_orientation(orientation=orientation,verbose=verbose and verbose>1 and verbose-1) if verbose: print "Orientation of the squares:" if verbose>1: for i,sq in enumerate(squares): print i,":",sq,":",orientation[i] boundary=self.surface_boundary(orientation=orientation,verbose=verbose and verbose>1 and verbose-1) if verbose: print "Edges of the boundary:" print boundary # The boundary of the surface is an Eulerian circuit in the surface_boundary_graph eulerian_circuits=[] boundary_length=2*(self.tree(side=0).alphabet().cardinality()+self.tree(side=1).alphabet().cardinality()) next=[(boundary[0],0)] if boundary[0][2][2]!=0: next.append((boundary[1],0)) if verbose: print "Looking for an eulerian circuit in the boundary" while len(next)>0: e,current=next.pop() if verbose: print e,current for i in xrange(current+1,len(boundary)): if boundary[i]==e: boundary[i],boundary[current]=boundary[current],boundary[i] # now the boundary is the beginning of an eulerian # circuit up to current break # We check that the boundary up to current is acceptable acceptable=True # First, we check that two edges bounding a strongly # connected component of squares share the same # orientation oriented=set() for i in xrange(current+1): e=boundary[i] if e[2][2]!=0 and -e[2][2] in oriented: # edges with orientation 0 are isolated edges acceptable=False if verbose: print "The current boundary does not respect orientation",e[2][2] break else: oriented.add(e[2][2]) if not acceptable: continue # Next we check that this is compatible with the given # cyclic order if cyclic_order_0 is not None: i=0 j0=None while i<current+1 and boundary[i][2][1]!=0: i+=1 if i<current+1: j0=0 while j0<len(cyclic_order_0) and cyclic_order_0[j0]!=boundary[i][2][0]: j0+=1 while i<current: i+=1 while i<current+1 and boundary[i][2][1]!=0: i+=1 if i<current+1: j0+=1 if j0==len(cyclic_order_0): j0=0 if boundary[i][2][0]!=cyclic_order_0[j0]: acceptable=False if verbose: print "The current boundary does not respect the given cyclic order on side 0" break if not acceptable: continue if cyclic_order_1 is not None: i=0 j1=None while i<current+1 and boundary[i][2][1]!=1: i+=1 if i<current+1: j1=0 while j1<len(cyclic_order_1) and cyclic_order_1[j1]!=boundary[i][2][0]: j1+=1 while i<current: i+=1 while i<current+1 and boundary[i][2][1]!=1: i+=1 if i<current+1: j1+=1 if j1==len(cyclic_order_1): j1=0 if boundary[i][2][0]!=cyclic_order_1[j1]: acceptable=False if verbose: print "The current boundary does not respect the given cyclic order on side 1" break if not acceptable: continue # If there are no given cyclic orders we check that on # both side there is only one connected component. if (cyclic_order_0 is None) and (cyclic_order_1 is None): tmp_cyclic_0=[boundary[i][2][0] for i in xrange(current+1) if boundary[i][2][1]==0] i=0 if len(tmp_cyclic_0)<2*len(A0): while i<len(tmp_cyclic_0): j=i done=False while True: aa=A0.inverse_letter(tmp_cyclic_0[j]) j=0 while j<len(tmp_cyclic_0) and tmp_cyclic_0[j]!=aa: j+=1 if j==len(tmp_cyclic_0) or j==0: i+=1 break j-=1 if i==j: acceptable=False if verbose: print "There is more than one boundary component on side 0" print "Cyclic order on side 0:",tmp_cyclic_0 i=len(tmp_cyclic_0) break if not acceptable: continue tmp_cyclic_1=[boundary[i][2][0] for i in xrange(current+1) if boundary[i][2][1]==1] i=0 if len(tmp_cyclic_1)<2*len(A1): while i<len(tmp_cyclic_1): j=i done=False while True: aa=A1.inverse_letter(tmp_cyclic_1[j]) j=0 while j<len(tmp_cyclic_1) and tmp_cyclic_1[j]!=aa: j+=1 if j==len(tmp_cyclic_1) or j==0: i+=1 break j-=1 if i==j: acceptable=False if verbose: print "There is more than one boundary component on side 1" print "Cyclic order on side 1:",tmp_cyclic_1 i=len(tmp_cyclic_1) break if not acceptable: continue if current+1==boundary_length: eulerian_circuits.append(boundary[:current+1]) for i in xrange(current+1,len(boundary)): e=boundary[i] if e[0]!=boundary[current][1] or (e[2][2]!=0 and -e[2][2] in oriented): continue if e[2][1]==0 and (cyclic_order_0 is not None) and (j0 is not None): jj0=j0+1 if jj0==len(cyclic_order_0): jj0=0 if e[2][0]!=cyclic_order_0[jj0]: continue elif e[2][1]==1 and (cyclic_order_1 is not None) and (j1 is not None): jj1=j1+1 if jj1==len(cyclic_order_1): jj1=0 if e[2][0]!=cyclic_order_1[jj1]: continue next.append((e,current+1)) if verbose: print "Possible boundaries:",eulerian_circuits if len(eulerian_circuits)>1: print "There is an ambiguity on the choice of the boundary of the surface." print "Specify using optionnal argument cyclic_order_0 and cyclic_order_1." print "Possible choices:" for cyclic_order in eulerian_circuits: print "side 0:",[e[2][0] for e in cyclic_order if e[2][1]==0] print "side 1:",[e[2][0] for e in cyclic_order if e[2][1]==1] print "The first one is chosen" elif len(eulerian_circuits)==0: print "There are no eulerian circuit in the boundary compatible with the given cyclic orders." print "Probably changing the orientation will solve this problem" return False cyclic_order=eulerian_circuits[0] # Now we can fix the orientation of the squares according to # the chosen boundary direct_orientation=set(e[2][2] for e in cyclic_order if e[2][2]!=0) for i in xrange(len(self.squares())): if orientation[i] in direct_orientation: orientation[i]=-1 else: orientation[i]=1 if verbose: print "Orientation of the squares coherent with the choice of the boundary" print orientation self._squares_orientation=orientation # We put the edges in the fundamental domain initial_vertex=dict() terminal_vertex=dict() for a in A0.positive_letters(): aa=A0.inverse_letter(a) slicea=[i for i in xrange(len(squares)) if squares[i][4]==a] size=len(slicea)+1 if size==1: continue # sort the edges of the slice i=1 sqi=slicea[0] sq=squares[sqi] if orientation[sqi]==1: start0=(sq[0],sq[1]) endi=(sq[3],sq[2]) else: start0=(sq[3],sq[2]) endi=(sq[0],sq[1]) while i<len(slicea): j=i while j<len(slicea): sqjj=slicea[j] sqj=squares[sqjj] if orientation[sqjj]==1: startj=(sqj[0],sqj[1]) endj=(sqj[3],sqj[2]) else: startj=(sqj[3],sqj[2]) endj=(sqj[0],sqj[1]) if endi==startj: # next(es[i-1])==es[j]: slicea[j],slicea[i]=slicea[i],slicea[j] i+=1 endi=endj if i<j: j=j-1 elif endj==start0: #next(es[j])==es[0]: slicea=[slicea[j]]+slicea[:j]+slicea[j+1:] i+=1 start0=startj if i<j: j=j-1 j+=1 if verbose: print "Slice of", a,":",slicea # put a curve for each edge of the slice for i,sqi in enumerate(slicea): sq=squares[sqi] if orientation[sqi]==1: initial_vertex[(sq[0],sq[3],sq[5])]=(a,(i+1.0)/size) terminal_vertex[(sq[1],sq[2],sq[5])]=(aa,(size-i-1.0)/size) else: terminal_vertex[(sq[0],sq[3],sq[5])]=(a,(i+1.0)/size) initial_vertex[(sq[1],sq[2],sq[5])]=(aa,(size-i-1.0)/size) # We compute the regular 2N-gone that is the foundamental domain # of the surface on side 0 i=0 while cyclic_order[i][2][1]==1: i+=1 a=A0.inverse_letter(cyclic_order[i][2][0]) polygon_side_0=[a] for j in xrange(2*N-1): k=0 while cyclic_order[k][2][1]==1 or cyclic_order[k][2][0]!=a: k+=1 k-=1 while cyclic_order[k][2][1]==1: k-=1 if k==0: k=boundary_length-1 a=A0.inverse_letter(cyclic_order[k][2][0]) polygon_side_0.append(a) if verbose: print "Polygon bounding the fundamental domain of the surface:",polygon_side_0 i=0 while polygon_side_0[i]!=A0[0]: i+=1 polygon_side_0=polygon_side_0[i:]+polygon_side_0[:i] g=Graphics() boundary_initial_vertex=dict() boundary_terminal_vertex=dict() for i,a in enumerate(polygon_side_0): boundary_initial_vertex[a]=(RR(radius*cos(i*pi/N)),RR(radius*sin(i*pi/N))) boundary_terminal_vertex[a]=(RR(radius*cos((i+1)*pi/N)),RR(radius*sin((i+1)*pi/N))) # Regular polygon text_decalage=1.05 for a in polygon_side_0: x,y=boundary_initial_vertex[a] xx,yy=boundary_terminal_vertex[a] g+=line([(x,y),(xx,yy)],alpha=1,thickness=2,hue=5.0/6) g+=text(a,((x+xx)/2*text_decalage**2,(y+yy)/2*text_decalage**2),hue=5.0/6) for e in initial_vertex: a,p=initial_vertex[e] b=e[2] x=boundary_initial_vertex[a][0]+p*(boundary_terminal_vertex[a][0]-boundary_initial_vertex[a][0]) y=boundary_initial_vertex[a][1]+p*(boundary_terminal_vertex[a][1]-boundary_initial_vertex[a][1]) if e in terminal_vertex: aa,pp=terminal_vertex[e] else: # the end of e is at the singularity i=0 j=0 while cyclic_order[i][2][1]==0 or cyclic_order[i][2][0]!=b: if cyclic_order[i][2][1]==0: j=i i+=1 if j==0 and cyclic_order[j][2][1]==1: j=len(cyclic_order)-1 while cyclic_order[j][2][1]==1: j-=1 aa=A0.inverse_letter(cyclic_order[j][2][0]) pp=0 xx=boundary_initial_vertex[aa][0]+pp*(boundary_terminal_vertex[aa][0]-boundary_initial_vertex[aa][0]) yy=boundary_initial_vertex[aa][1]+pp*(boundary_terminal_vertex[aa][1]-boundary_initial_vertex[aa][1]) g+=line([(x,y),(xx,yy)],alpha=1,thickness=2,hue=RR(A1.rank(b))/N) for e in terminal_vertex: if e not in initial_vertex: # the initial vertex of e is the singularity aa,pp=terminal_vertex[e] xx=boundary_initial_vertex[aa][0]+pp*(boundary_terminal_vertex[aa][0]-boundary_initial_vertex[aa][0]) yy=boundary_initial_vertex[aa][1]+pp*(boundary_terminal_vertex[aa][1]-boundary_initial_vertex[aa][1]) b=A1.inverse_letter(e[2][0]) i=0 j=0 while cyclic_order[i][2][1]==0 or cyclic_order[i][2][0]!=b: if cyclic_order[i][2][1]==0: j=i i+=1 if j==0 and cyclic_order[j][2][1]==1: j=len(cyclic_order)-1 while cyclic_order[j][2][1]==1: j-=1 a=A0.inverse_letter(cyclic_order[j][2][0]) x=boundary_initial_vertex[a][0] y=boundary_initial_vertex[a][1] g+=line([(x,y),(xx,yy)],alpha=1,thickness=2,hue=RR(A1.rank(b))/N) g+=text(e[2][0],(text_decalage*xx,text_decalage*yy),hue=RR(A1.rank(b))/N) for e in self.isolated_edges(): if e[2][1]==1: #The end of e is at the singularity b=e[2][0] i=0 j=0 while cyclic_order[i][2][1]==0 or cyclic_order[i][2][0]!=b: if cyclic_order[i][2][1]==0: j=i i+=1 if j==0 and cyclic_order[j][2][1]==1: j=len(cyclic_order)-1 while cyclic_order[j][2][1]==1: j-=1 a=A0.inverse_letter(cyclic_order[j][2][0]) x=boundary_initial_vertex[a][0] y=boundary_initial_vertex[a][1] #The start of e is also at the singularity bb=A1.inverse_letter(b) i=0 j=0 while cyclic_order[i][2][1]==0 or cyclic_order[i][2][0]!=bb: if cyclic_order[i][2][1]==0: j=i i+=1 if j==0 and cyclic_order[j][2][1]==1: j=len(cyclic_order)-1 while cyclic_order[j][2][1]==1: j-=1 aa=A0.inverse_letter(cyclic_order[j][2][0]) xx=boundary_initial_vertex[aa][0] yy=boundary_initial_vertex[aa][1] g+=line([(x,y),(xx,yy)],alpha=1,thickness=2,hue=RR(A1.rank(b))/N) g+=text(b,(text_decalage*x,text_decalage*y),hue=RR(A1.rank(b))/N) for sq in self.twice_light_squares(): b=A1.to_positive_letter(sq[5]) #The end of b is at the singularity i=0 j=0 while cyclic_order[i][2][1]==0 or cyclic_order[i][2][0]!=b: if cyclic_order[i][2][1]==0: j=i i+=1 if j==0 and cyclic_order[j][2][1]==1: j=len(cyclic_order)-1 while cyclic_order[j][2][1]==1: j-=1 a=A0.inverse_letter(cyclic_order[j][2][0]) x=boundary_initial_vertex[a][0] y=boundary_initial_vertex[a][1] #The start of b is also at the singularity bb=A1.inverse_letter(b) i=0 j=0 while cyclic_order[i][2][1]==0 or cyclic_order[i][2][0]!=bb: if cyclic_order[i][2][1]==0: j=i i+=1 if j==0 and cyclic_order[j][2][1]==1: j=len(cyclic_order)-1 while cyclic_order[j][2][1]==1: j-=1 aa=A0.inverse_letter(cyclic_order[j][2][0]) xx=boundary_initial_vertex[aa][0] yy=boundary_initial_vertex[aa][1] g+=line([(x,y),(xx,yy)],alpha=1,thickness=2,hue=RR(A1.rank(b))/N) g+=text(b,(text_decalage*x,text_decalage*y),hue=RR(A1.rank(b))/N) g.axes(False) return g
def plot(self, chart=None, ambient_coords=None, mapping=None, color='blue', print_label=True, label=None, label_color=None, fontsize=10, label_offset=0.1, parameters=None, **extra_options): r""" Plot the vector in a Cartesian graph based on the coordinates of some ambient chart. The vector is drawn in terms of two (2D graphics) or three (3D graphics) coordinates of a given chart, called hereafter the *ambient chart*. The vector's base point `p` (or its image `\Phi(p)` by some differentiable mapping `\Phi`) must lie in the ambient chart's domain. If `\Phi` is different from the identity mapping, the vector actually depicted is `\mathrm{d}\Phi_p(v)`, where `v` is the current vector (``self``) (see the example of a vector tangent to the 2-sphere below, where `\Phi: S^2 \to \RR^3`). INPUT: - ``chart`` -- (default: ``None``) the ambient chart (see above); if ``None``, it is set to the default chart of the open set containing the point at which the vector (or the vector image via the differential `\mathrm{d}\Phi_p` of ``mapping``) is defined - ``ambient_coords`` -- (default: ``None``) tuple containing the 2 or 3 coordinates of the ambient chart in terms of which the plot is performed; if ``None``, all the coordinates of the ambient chart are considered - ``mapping`` -- (default: ``None``) :class:`~sage.manifolds.differentiable.diff_map.DiffMap`; differentiable mapping `\Phi` providing the link between the point `p` at which the vector is defined and the ambient chart ``chart``: the domain of ``chart`` must contain `\Phi(p)`; if ``None``, the identity mapping is assumed - ``scale`` -- (default: 1) value by which the length of the arrow representing the vector is multiplied - ``color`` -- (default: 'blue') color of the arrow representing the vector - ``print_label`` -- (boolean; default: ``True``) determines whether a label is printed next to the arrow representing the vector - ``label`` -- (string; default: ``None``) label printed next to the arrow representing the vector; if ``None``, the vector's symbol is used, if any - ``label_color`` -- (default: ``None``) color to print the label; if ``None``, the value of ``color`` is used - ``fontsize`` -- (default: 10) size of the font used to print the label - ``label_offset`` -- (default: 0.1) determines the separation between the vector arrow and the label - ``parameters`` -- (default: ``None``) dictionary giving the numerical values of the parameters that may appear in the coordinate expression of ``self`` (see example below) - ``**extra_options`` -- extra options for the arrow plot, like ``linestyle``, ``width`` or ``arrowsize`` (see :func:`~sage.plot.arrow.arrow2d` and :func:`~sage.plot.plot3d.shapes.arrow3d` for details) OUTPUT: - a graphic object, either an instance of :class:`~sage.plot.graphics.Graphics` for a 2D plot (i.e. based on 2 coordinates of ``chart``) or an instance of :class:`~sage.plot.plot3d.base.Graphics3d` for a 3D plot (i.e. based on 3 coordinates of ``chart``) EXAMPLES: Vector tangent to a 2-dimensional manifold:: sage: M = Manifold(2, 'M') sage: X.<x,y> = M.chart() sage: p = M((2,2), name='p') sage: Tp = M.tangent_space(p) sage: v = Tp((2, 1), name='v') ; v Tangent vector v at Point p on the 2-dimensional differentiable manifold M Plot of the vector alone (arrow + label):: sage: v.plot() Graphics object consisting of 2 graphics primitives Plot atop of the chart grid:: sage: X.plot() + v.plot() Graphics object consisting of 20 graphics primitives .. PLOT:: M = Manifold(2, 'M') X = M.chart('x y'); x, y = X[:] p = M((2,2), name='p'); Tp = M.tangent_space(p) v = Tp((2, 1), name='v') g = X.plot() + v.plot() sphinx_plot(g) Plots with various options:: sage: X.plot() + v.plot(color='green', scale=2, label='V') Graphics object consisting of 20 graphics primitives .. PLOT:: M = Manifold(2, 'M') X = M.chart('x y'); x, y = X[:] p = M((2,2), name='p'); Tp = M.tangent_space(p) v = Tp((2, 1), name='v') g = X.plot() + v.plot(color='green', scale=2, label='V') sphinx_plot(g) :: sage: X.plot() + v.plot(print_label=False) Graphics object consisting of 19 graphics primitives .. PLOT:: M = Manifold(2, 'M') X = M.chart('x y'); x, y = X[:] p = M((2,2), name='p'); Tp = M.tangent_space(p) v = Tp((2, 1), name='v') g = X.plot() + v.plot(print_label=False) sphinx_plot(g) :: sage: X.plot() + v.plot(color='green', label_color='black', ....: fontsize=20, label_offset=0.2) Graphics object consisting of 20 graphics primitives .. PLOT:: M = Manifold(2, 'M') X = M.chart('x y'); x, y = X[:] p = M((2,2), name='p'); Tp = M.tangent_space(p) v = Tp((2, 1), name='v') g = X.plot() + v.plot(color='green', label_color='black', fontsize=20, label_offset=0.2) sphinx_plot(g) :: sage: X.plot() + v.plot(linestyle=':', width=4, arrowsize=8, ....: fontsize=20) Graphics object consisting of 20 graphics primitives .. PLOT:: M = Manifold(2, 'M') X = M.chart('x y'); x, y = X[:] p = M((2,2), name='p'); Tp = M.tangent_space(p) v = Tp((2, 1), name='v') g = X.plot() + v.plot(linestyle=':', width=4, arrowsize=8, fontsize=20) sphinx_plot(g) Plot with specific values of some free parameters:: sage: var('a b') (a, b) sage: v = Tp((1+a, -b^2), name='v') ; v.display() v = (a + 1) d/dx - b^2 d/dy sage: X.plot() + v.plot(parameters={a: -2, b: 3}) Graphics object consisting of 20 graphics primitives Special case of the zero vector:: sage: v = Tp.zero() ; v Tangent vector zero at Point p on the 2-dimensional differentiable manifold M sage: X.plot() + v.plot() Graphics object consisting of 19 graphics primitives Vector tangent to a 4-dimensional manifold:: sage: M = Manifold(4, 'M') sage: X.<t,x,y,z> = M.chart() sage: p = M((0,1,2,3), name='p') sage: Tp = M.tangent_space(p) sage: v = Tp((5,4,3,2), name='v') ; v Tangent vector v at Point p on the 4-dimensional differentiable manifold M We cannot make a 4D plot directly:: sage: v.plot() Traceback (most recent call last): ... ValueError: the number of coordinates involved in the plot must be either 2 or 3, not 4 Rather, we have to select some chart coordinates for the plot, via the argument ``ambient_coords``. For instance, for a 2-dimensional plot in terms of the coordinates `(x, y)`:: sage: v.plot(ambient_coords=(x,y)) Graphics object consisting of 2 graphics primitives .. PLOT:: M = Manifold(4, 'M') X = M.chart('t x y z'); t,x,y,z = X[:] p = M((0,1,2,3), name='p'); Tp = M.tangent_space(p) v = Tp((5,4,3,2), name='v') g = X.plot(ambient_coords=(x,y)) + v.plot(ambient_coords=(x,y)) sphinx_plot(g) This plot involves only the components `v^x` and `v^y` of `v`. Similarly, for a 3-dimensional plot in terms of the coordinates `(t, x, y)`:: sage: g = v.plot(ambient_coords=(t,x,z)) sage: print(g) Graphics3d Object This plot involves only the components `v^t`, `v^x` and `v^z` of `v`. A nice 3D view atop the coordinate grid is obtained via:: sage: (X.plot(ambient_coords=(t,x,z)) # long time ....: + v.plot(ambient_coords=(t,x,z), ....: label_offset=0.5, width=6)) Graphics3d Object .. PLOT:: M = Manifold(4, 'M') X = M.chart('t x y z'); t,x,y,z = X[:] p = M((0,1,2,3), name='p'); Tp = M.tangent_space(p) v = Tp((5,4,3,2), name='v') g = X.plot(ambient_coords=(t,x,z)) + v.plot(ambient_coords=(t,x,z), label_offset=0.5, width=6) sphinx_plot(g) An example of plot via a differential mapping: plot of a vector tangent to a 2-sphere viewed in `\RR^3`:: sage: S2 = Manifold(2, 'S^2') sage: U = S2.open_subset('U') # the open set covered by spherical coord. sage: XS.<th,ph> = U.chart(r'th:(0,pi):\theta ph:(0,2*pi):\phi') sage: R3 = Manifold(3, 'R^3') sage: X3.<x,y,z> = R3.chart() sage: F = S2.diff_map(R3, {(XS, X3): [sin(th)*cos(ph), ....: sin(th)*sin(ph), ....: cos(th)]}, name='F') sage: F.display() # the standard embedding of S^2 into R^3 F: S^2 --> R^3 on U: (th, ph) |--> (x, y, z) = (cos(ph)*sin(th), sin(ph)*sin(th), cos(th)) sage: p = U.point((pi/4, 7*pi/4), name='p') sage: v = XS.frame()[1].at(p) ; v # the coordinate vector d/dphi at p Tangent vector d/dph at Point p on the 2-dimensional differentiable manifold S^2 sage: graph_v = v.plot(mapping=F) sage: graph_S2 = XS.plot(chart=X3, mapping=F, number_values=9) # long time sage: graph_v + graph_S2 # long time Graphics3d Object .. PLOT:: S2 = Manifold(2, 'S^2') U = S2.open_subset('U') XS = U.chart(r'th:(0,pi):\theta ph:(0,2*pi):\phi') th, ph = XS[:] R3 = Manifold(3, 'R^3') X3 = R3.chart('x y z') F = S2.diff_map(R3, {(XS, X3): [sin(th)*cos(ph), sin(th)*sin(ph), cos(th)]}, name='F') p = U.point((pi/4, 7*pi/4), name='p') v = XS.frame()[1].at(p) graph_v = v.plot(mapping=F) graph_S2 = XS.plot(chart=X3, mapping=F, number_values=9) sphinx_plot(graph_v + graph_S2) """ from sage.plot.arrow import arrow2d from sage.plot.text import text from sage.plot.graphics import Graphics from sage.plot.plot3d.shapes import arrow3d from sage.plot.plot3d.shapes2 import text3d from sage.misc.functional import numerical_approx from sage.manifolds.differentiable.chart import DiffChart scale = extra_options.pop("scale") # # The "effective" vector to be plotted # if mapping is None: eff_vector = self base_point = self._point else: #!# check # For efficiency, the method FiniteRankFreeModuleMorphism._call_() # is called instead of FiniteRankFreeModuleMorphism.__call__() eff_vector = mapping.differential(self._point)._call_(self) base_point = mapping(self._point) # # The chart w.r.t. which the vector is plotted # if chart is None: chart = base_point.parent().default_chart() elif not isinstance(chart, DiffChart): raise TypeError("{} is not a chart".format(chart)) # # Coordinates of the above chart w.r.t. which the vector is plotted # if ambient_coords is None: ambient_coords = chart[:] # all chart coordinates are used n_pc = len(ambient_coords) if n_pc != 2 and n_pc != 3: raise ValueError("the number of coordinates involved in the " + "plot must be either 2 or 3, not {}".format(n_pc)) # indices coordinates involved in the plot: ind_pc = [chart[:].index(pc) for pc in ambient_coords] # # Components of the vector w.r.t. the chart frame # basis = chart.frame().at(base_point) vcomp = eff_vector.comp(basis=basis)[:] xp = base_point.coord(chart=chart) # # The arrow # resu = Graphics() if parameters is None: coord_tail = [numerical_approx(xp[i]) for i in ind_pc] coord_head = [ numerical_approx(xp[i] + scale * vcomp[i]) for i in ind_pc ] else: coord_tail = [ numerical_approx(xp[i].substitute(parameters)) for i in ind_pc ] coord_head = [ numerical_approx( (xp[i] + scale * vcomp[i]).substitute(parameters)) for i in ind_pc ] if coord_head != coord_tail: if n_pc == 2: resu += arrow2d(tailpoint=coord_tail, headpoint=coord_head, color=color, **extra_options) else: resu += arrow3d(coord_tail, coord_head, color=color, **extra_options) # # The label # if print_label: if label is None: if n_pc == 2 and self._latex_name is not None: label = r'$' + self._latex_name + r'$' if n_pc == 3 and self._name is not None: label = self._name if label is not None: xlab = [xh + label_offset for xh in coord_head] if label_color is None: label_color = color if n_pc == 2: resu += text(label, xlab, fontsize=fontsize, color=label_color) else: resu += text3d(label, xlab, fontsize=fontsize, color=label_color) return resu
def plot(self, chart=None, ambient_coords=None, mapping=None, chart_domain=None, fixed_coords=None, ranges=None, max_value=8, nb_values=None, steps=None,scale=1, color='blue', parameters=None, label_axes=True, **extra_options): r""" Plot the vector field in a Cartesian graph based on the coordinates of some ambient chart. The vector field is drawn in terms of two (2D graphics) or three (3D graphics) coordinates of a given chart, called hereafter the *ambient chart*. The vector field's base points `p` (or their images `\Phi(p)` by some differentiable mapping `\Phi`) must lie in the ambient chart's domain. INPUT: - ``chart`` -- (default: ``None``) the ambient chart (see above); if ``None``, the default chart of the vector field's domain is used - ``ambient_coords`` -- (default: ``None``) tuple containing the 2 or 3 coordinates of the ambient chart in terms of which the plot is performed; if ``None``, all the coordinates of the ambient chart are considered - ``mapping`` -- (default: ``None``) differentiable mapping `\Phi` (instance of :class:`~sage.geometry.manifolds.diffmapping.DiffMapping`) providing the link between the vector field's domain and the ambient chart ``chart``; if ``None``, the identity mapping is assumed - ``chart_domain`` -- (default: ``None``) chart on the vector field's domain to define the points at which vector arrows are to be plotted; if ``None``, the default chart of the vector field's domain is used - ``fixed_coords`` -- (default: ``None``) dictionary with keys the coordinates of ``chart_domain`` that are kept fixed and with values the value of these coordinates; if ``None``, all the coordinates of ``chart_domain`` are used - ``ranges`` -- (default: ``None``) dictionary with keys the coordinates of ``chart_domain`` to be used and values tuples ``(x_min,x_max)`` specifying the coordinate range for the plot; if ``None``, the entire coordinate range declared during the construction of ``chart_domain`` is considered (with ``-Infinity`` replaced by ``-max_value`` and ``+Infinity`` by ``max_value``) - ``max_value`` -- (default: 8) numerical value substituted to ``+Infinity`` if the latter is the upper bound of the range of a coordinate for which the plot is performed over the entire coordinate range (i.e. for which no specific plot range has been set in ``ranges``); similarly ``-max_value`` is the numerical valued substituted for ``-Infinity`` - ``nb_values`` -- (default: ``None``) either an integer or a dictionary with keys the coordinates of ``chart_domain`` to be used and values the number of values of the coordinate for sampling the part of the vector field's domain involved in the plot ; if ``nb_values`` is a single integer, it represents the number of values for all coordinates; if ``nb_values`` is ``None``, it is set to 9 for a 2D plot and to 5 for a 3D plot - ``steps`` -- (default: ``None``) dictionary with keys the coordinates of ``chart_domain`` to be used and values the step between each constant value of the coordinate; if ``None``, the step is computed from the coordinate range (specified in ``ranges``) and ``nb_values``. On the contrary, if the step is provided for some coordinate, the corresponding number of values is deduced from it and the coordinate range. - ``scale`` -- (default: 1) value by which the lengths of the arrows representing the vectors is multiplied - ``color`` -- (default: 'blue') color of the arrows representing the vectors - ``parameters`` -- (default: ``None``) dictionary giving the numerical values of the parameters that may appear in the coordinate expression of the vector field (see example below) - ``label_axes`` -- (default: ``True``) boolean determining whether the labels of the coordinate axes of ``chart`` shall be added to the graph; can be set to ``False`` if the graph is 3D and must be superposed with another graph. - ``**extra_options`` -- extra options for the arrow plot, like ``linestyle``, ``width`` or ``arrowsize`` (see :func:`~sage.plot.arrow.arrow2d` and :func:`~sage.plot.plot3d.shapes.arrow3d` for details) OUTPUT: - a graphic object, either an instance of :class:`~sage.plot.graphics.Graphics` for a 2D plot (i.e. based on 2 coordinates of ``chart``) or an instance of :class:`~sage.plot.plot3d.base.Graphics3d` for a 3D plot (i.e. based on 3 coordinates of ``chart``) EXAMPLES: Plot of a vector field on a 2-dimensional manifold:: sage: Manifold._clear_cache_() # for doctests only sage: M = Manifold(2, 'M') sage: X.<x,y> = M.chart() sage: v = M.vector_field(name='v') sage: v[:] = -y, x ; v.display() v = -y d/dx + x d/dy sage: v.plot() Graphics object consisting of 80 graphics primitives Plot with various options:: sage: v.plot(scale=0.5, color='green', linestyle='--', width=1, arrowsize=6) Graphics object consisting of 80 graphics primitives sage: v.plot(max_value=4, nb_values=5, scale=0.5) Graphics object consisting of 24 graphics primitives Plots along a line of fixed coordinate:: sage: v.plot(fixed_coords={x: -2}) Graphics object consisting of 9 graphics primitives sage: v.plot(fixed_coords={y: 1}) Graphics object consisting of 9 graphics primitives Let us now consider a vector field on a 4-dimensional manifold:: sage: Manifold._clear_cache_() # for doctests only sage: M = Manifold(4, 'M') sage: X.<t,x,y,z> = M.chart() sage: v = M.vector_field(name='v') sage: v[:] = (t/8)^2, -t*y/4, t*x/4, t*z/4 ; v.display() v = 1/64*t^2 d/dt - 1/4*t*y d/dx + 1/4*t*x d/dy + 1/4*t*z d/dz We cannot make a 4D plot directly:: sage: v.plot() Traceback (most recent call last): ... ValueError: the number of ambient coordinates must be either 2 or 3, not 4 Rather, we have to select some coordinates for the plot, via the argument ``ambient_coords``. For instance, for a 3D plot:: sage: v.plot(ambient_coords=(x, y, z), fixed_coords={t: 1}) Graphics3d Object sage: v.plot(ambient_coords=(x, y, t), fixed_coords={z: 0}, ....: ranges={x: (-2,2), y: (-2,2), t: (-1, 4)}, nb_values=4) Graphics3d Object or, for a 2D plot:: sage: v.plot(ambient_coords=(x, y), fixed_coords={t: 1, z: 0}) Graphics object consisting of 80 graphics primitives sage: v.plot(ambient_coords=(x, t), fixed_coords={y: 1, z: 0}) Graphics object consisting of 72 graphics primitives An example of plot via a differential mapping: plot of a vector field tangent to a 2-sphere viewed in `\RR^3`:: sage: S2 = Manifold(2, 'S^2') sage: U = S2.open_subset('U') # the open set covered by spherical coord. sage: XS.<th,ph> = U.chart(r'th:(0,pi):\theta ph:(0,2*pi):\phi') sage: R3 = Manifold(3, 'R^3') sage: X3.<x,y,z> = R3.chart() sage: F = S2.diff_mapping(R3, {(XS, X3): [sin(th)*cos(ph), ....: sin(th)*sin(ph), cos(th)]}, name='F') sage: F.display() # the standard embedding of S^2 into R^3 F: S^2 --> R^3 on U: (th, ph) |--> (x, y, z) = (cos(ph)*sin(th), sin(ph)*sin(th), cos(th)) sage: v = XS.frame()[1] ; v vector field 'd/dph' on the open subset 'U' of the 2-dimensional manifold 'S^2' sage: graph_v = v.plot(chart=X3, mapping=F, label_axes=False) sage: graph_S2 = XS.plot(chart=X3, mapping=F, nb_values=9) sage: show(graph_v + graph_S2) """ from sage.rings.infinity import Infinity from sage.misc.functional import numerical_approx from sage.misc.latex import latex from sage.plot.graphics import Graphics from sage.geometry.manifolds.chart import Chart from sage.geometry.manifolds.utilities import set_axes_labels # # 1/ Treatment of input parameters # ----------------------------- if chart is None: chart = self._domain.default_chart() elif not isinstance(chart, Chart): raise TypeError("{} is not a chart".format(chart)) if chart_domain is None: chart_domain = self._domain.default_chart() elif not isinstance(chart_domain, Chart): raise TypeError("{} is not a chart".format(chart_domain)) elif not chart_domain.domain().is_subset(self._domain): raise ValueError("The domain of {} is not ".format(chart_domain) + "included in the domain of {}".format(self)) if fixed_coords is None: coords = chart_domain._xx else: fixed_coord_list = fixed_coords.keys() coords = [] for coord in chart_domain._xx: if coord not in fixed_coord_list: coords.append(coord) coords = tuple(coords) if ambient_coords is None: ambient_coords = chart[:] elif not isinstance(ambient_coords, tuple): ambient_coords = tuple(ambient_coords) nca = len(ambient_coords) if nca != 2 and nca !=3: raise ValueError("the number of ambient coordinates must be " + "either 2 or 3, not {}".format(nca)) if ranges is None: ranges = {} ranges0 = {} for coord in coords: if coord in ranges: ranges0[coord] = (numerical_approx(ranges[coord][0]), numerical_approx(ranges[coord][1])) else: bounds = chart_domain._bounds[chart_domain[:].index(coord)] if bounds[0][0] == -Infinity: xmin = numerical_approx(-max_value) elif bounds[0][1]: xmin = numerical_approx(bounds[0][0]) else: xmin = numerical_approx(bounds[0][0] + 1.e-3) if bounds[1][0] == Infinity: xmax = numerical_approx(max_value) elif bounds[1][1]: xmax = numerical_approx(bounds[1][0]) else: xmax = numerical_approx(bounds[1][0] - 1.e-3) ranges0[coord] = (xmin, xmax) ranges = ranges0 if nb_values is None: if nca == 2: # 2D plot nb_values = 9 else: # 3D plot nb_values = 5 if not isinstance(nb_values, dict): nb_values0 = {} for coord in coords: nb_values0[coord] = nb_values nb_values = nb_values0 if steps is None: steps = {} for coord in coords: if coord not in steps: steps[coord] = (ranges[coord][1] - ranges[coord][0])/ \ (nb_values[coord]-1) else: nb_values[coord] = 1 + int( (ranges[coord][1] - ranges[coord][0])/ steps[coord]) # # 2/ Plots # ----- dom = chart_domain.domain() nc = len(chart_domain[:]) ncp = len(coords) xx = [0] * nc if fixed_coords is not None: if len(fixed_coords) != nc - ncp: raise ValueError("Bad number of fixed coordinates.") for fc, val in fixed_coords.iteritems(): xx[chart_domain[:].index(fc)] = val index_p = [chart_domain[:].index(cd) for cd in coords] resu = Graphics() ind = [0] * ncp ind_max = [0] * ncp ind_max[0] = nb_values[coords[0]] xmin = [ranges[cd][0] for cd in coords] step_tab = [steps[cd] for cd in coords] while ind != ind_max: for i in range(ncp): xx[index_p[i]] = xmin[i] + ind[i]*step_tab[i] if chart_domain.valid_coordinates(*xx, tolerance=1e-13, parameters=parameters): point = dom(xx, chart=chart_domain) resu += self.at(point).plot(chart=chart, ambient_coords=ambient_coords, mapping=mapping, scale=scale, color=color, print_label=False, parameters=parameters, **extra_options) # Next index: ret = 1 for pos in range(ncp-1,-1,-1): imax = nb_values[coords[pos]] - 1 if ind[pos] != imax: ind[pos] += ret ret = 0 elif ret == 1: if pos == 0: ind[pos] = imax + 1 # end point reached else: ind[pos] = 0 ret = 1 if label_axes: if nca==2: # 2D graphic # We update the dictionary _extra_kwds (options to be passed # to show()), instead of using the method # Graphics.axes_labels() since the latter is not robust w.r.t. # graph addition resu._extra_kwds['axes_labels'] = [r'$'+latex(ac)+r'$' for ac in ambient_coords] else: # 3D graphic labels = [str(ac) for ac in ambient_coords] resu = set_axes_labels(resu, *labels) return resu
def plot(hyperplane_arrangement, **kwds): r""" Return a plot of the hyperplane arrangement. If the arrangement is in 4 dimensions but inessential, a plot of the essentialization is returned. .. NOTE:: This function is available as the :meth:`~sage.geometry.hyperplane_arrangement.arrangement.HyperplaneArrangementElement.plot` method of hyperplane arrangements. You should not call this function directly, only through the method. INPUT: - ``hyperplane_arrangement`` -- the hyperplane arrangement to plot - ``**kwds`` -- plot options: see :mod:`sage.geometry.hyperplane_arrangement.plot`. OUTPUT: A graphics object of the plot. EXAMPLES:: sage: B = hyperplane_arrangements.semiorder(4) sage: B.plot() Displaying the essentialization. Graphics3d Object """ N = len(hyperplane_arrangement) dim = hyperplane_arrangement.dimension() if hyperplane_arrangement.base_ring().characteristic() != 0: raise NotImplementedError('must be a field of characteristic 0') elif dim == 4: if not hyperplane_arrangement.is_essential(): print('Displaying the essentialization.') hyperplane_arrangement = hyperplane_arrangement.essentialization() elif dim not in [1, 2, 3]: # revise to handle 4d return # silently # handle extra keywords if 'hyperplane_colors' in kwds: hyp_colors = kwds.pop('hyperplane_colors') if not isinstance(hyp_colors, list): # we assume its a single color then hyp_colors = [hyp_colors] * N else: HSV_tuples = [(i * 1.0 / N, 0.8, 0.9) for i in range(N)] hyp_colors = [hsv_to_rgb(*x) for x in HSV_tuples] if 'hyperplane_labels' in kwds: hyp_labels = kwds.pop('hyperplane_labels') has_hyp_label = True if not isinstance(hyp_labels, list): # we assume its a boolean then hyp_labels = [hyp_labels] * N relabeled = [] for i in range(N): if hyp_labels[i] in [True, 'long']: relabeled.append(True) else: relabeled.append(str(i)) hyp_labels = relabeled else: has_hyp_label = False if 'label_colors' in kwds: label_colors = kwds.pop('label_colors') has_label_color = True if not isinstance(label_colors, list): # we assume its a single color then label_colors = [label_colors] * N else: has_label_color = False if 'label_fontsize' in kwds: label_fontsize = kwds.pop('label_fontsize') has_label_fontsize = True if not isinstance(label_fontsize, list): # we assume its a single size then label_fontsize = [label_fontsize] * N else: has_label_fontsize = False if 'label_offsets' in kwds: has_offsets = True offsets = kwds.pop('label_offsets') else: has_offsets = False # give default values below hyperplane_legend = kwds.pop('hyperplane_legend', 'long' if dim < 3 else False) if 'hyperplane_opacities' in kwds: hyperplane_opacities = kwds.pop('hyperplane_opacities') has_opacity = True if not isinstance(hyperplane_opacities, list): # we assume a single number then hyperplane_opacities = [hyperplane_opacities] * N else: has_opacity = False point_sizes = kwds.pop('point_sizes', 50) if not isinstance(point_sizes, list): point_sizes = [point_sizes] * N if 'ranges' in kwds: ranges_set = True ranges = kwds.pop('ranges') if not type(ranges) in [list, tuple]: # ranges is a single number ranges = [ranges] * N # So ranges is some type of list. elif dim == 2: # arrangement of lines in the plane if not type(ranges[0]) in [list, tuple]: # a single interval ranges = [ranges] * N elif dim == 3: # arrangement of planes in 3-space if not type(ranges[0][0]) in [list, tuple]: ranges = [ranges] * N elif dim not in [2, 3]: # ranges is not an option unless dim is 2 or 3 ranges_set = False else: # a list of intervals, one for each hyperplane is given pass # ranges does not need to be modified else: ranges_set = False # give default values below # the extra keywords have now been handled # now handle the legend if dim in [1, 2]: # points on a line or lines in the plane if hyperplane_legend in [True, 'long']: hyps = hyperplane_arrangement.hyperplanes() legend_labels = [hyps[i]._latex_() for i in range(N)] elif hyperplane_legend == 'short': legend_labels = [str(i) for i in range(N)] else: # dim==3, arrangement of planes in 3-space if hyperplane_legend in [True, 'long']: legend3d = legend_3d(hyperplane_arrangement, hyp_colors, 'long') elif hyperplane_legend == 'short': legend3d = legend_3d(hyperplane_arrangement, hyp_colors, 'short') ## done handling the legend ## now create the plot p = Graphics() for i in range(N): newk = copy(kwds) if has_hyp_label: newk['hyperplane_label'] = hyp_labels[i] if has_offsets: if not isinstance(offsets, list): newk['label_offset'] = offsets else: newk['label_offset'] = offsets[i] else: newk['hyperplane_label'] = False if has_label_color: newk['label_color'] = label_colors[i] if has_label_fontsize: newk['label_fontsize'] = label_fontsize[i] if has_opacity: newk['opacity'] = hyperplane_opacities[i] if dim == 1: newk['point_size'] = point_sizes[i] if dim in [1, 2] and hyperplane_legend: # more options than T/F newk['legend_label'] = legend_labels[i] if ranges_set: newk['ranges'] = ranges[i] p += plot_hyperplane(hyperplane_arrangement[i], rgbcolor=hyp_colors[i], **newk) if dim == 1: if hyperplane_legend: # there are more options than T/F p.legend(True) return p elif dim == 2: if hyperplane_legend: # there are more options than T/F p.legend(True) return p else: # dim==3 if hyperplane_legend: # there are more options than T/F return p, legend3d else: return p
def arc(center, r1, r2=None, angle=0.0, sector=(0.0, 2 * pi), **options): r""" An arc (that is a portion of a circle or an ellipse) Type ``arc.options`` to see all options. INPUT: - ``center`` - 2-tuple of real numbers - position of the center. - ``r1``, ``r2`` - positive real numbers - radii of the ellipse. If only ``r1`` is set, then the two radii are supposed to be equal and this function returns an arc of circle. - ``angle`` - real number - angle between the horizontal and the axis that corresponds to ``r1``. - ``sector`` - 2-tuple (default: (0,2*pi))- angles sector in which the arc will be drawn. OPTIONS: - ``alpha`` - float (default: 1) - transparency - ``thickness`` - float (default: 1) - thickness of the arc - ``color``, ``rgbcolor`` - string or 2-tuple (default: 'blue') - the color of the arc - ``linestyle`` - string (default: ``'solid'``) - The style of the line, which is one of ``'dashed'``, ``'dotted'``, ``'solid'``, ``'dashdot'``, or ``'--'``, ``':'``, ``'-'``, ``'-.'``, respectively. EXAMPLES: Plot an arc of circle centered at (0,0) with radius 1 in the sector `(\pi/4,3*\pi/4)`:: sage: arc((0,0), 1, sector=(pi/4,3*pi/4)) Graphics object consisting of 1 graphics primitive Plot an arc of an ellipse between the angles 0 and `\pi/2`:: sage: arc((2,3), 2, 1, sector=(0,pi/2)) Graphics object consisting of 1 graphics primitive Plot an arc of a rotated ellipse between the angles 0 and `\pi/2`:: sage: arc((2,3), 2, 1, angle=pi/5, sector=(0,pi/2)) Graphics object consisting of 1 graphics primitive Plot an arc of an ellipse in red with a dashed linestyle:: sage: arc((0,0), 2, 1, 0, (0,pi/2), linestyle="dashed", color="red") Graphics object consisting of 1 graphics primitive sage: arc((0,0), 2, 1, 0, (0,pi/2), linestyle="--", color="red") Graphics object consisting of 1 graphics primitive The default aspect ratio for arcs is 1.0:: sage: arc((0,0), 1, sector=(pi/4,3*pi/4)).aspect_ratio() 1.0 It is not possible to draw arcs in 3D:: sage: A = arc((0,0,0), 1) Traceback (most recent call last): ... NotImplementedError """ from sage.plot.all import Graphics # Reset aspect_ratio to 'automatic' in case scale is 'semilog[xy]'. # Otherwise matplotlib complains. scale = options.get('scale', None) if isinstance(scale, (list, tuple)): scale = scale[0] if scale == 'semilogy' or scale == 'semilogx': options['aspect_ratio'] = 'automatic' if len(center) == 2: if r2 is None: r2 = r1 g = Graphics() g._set_extra_kwds(Graphics._extract_kwds_for_show(options)) if len(sector) != 2: raise ValueError("the sector must consist of two angles") g.add_primitive( Arc(center[0], center[1], r1, r2, angle, sector[0], sector[1], options)) return g elif len(center) == 3: raise NotImplementedError
def arc(center, r1, r2=None, angle=0.0, sector=(0.0, 2 * pi), **options): r""" An arc (that is a portion of a circle or an ellipse) Type ``arc.options`` to see all options. INPUT: - ``center`` - 2-tuple of real numbers - position of the center. - ``r1``, ``r2`` - positive real numbers - radii of the ellipse. If only ``r1`` is set, then the two radii are supposed to be equal and this function returns an arc of of circle. - ``angle`` - real number - angle between the horizontal and the axis that corresponds to ``r1``. - ``sector`` - 2-tuple (default: (0,2*pi))- angles sector in which the arc will be drawn. OPTIONS: - ``alpha`` - float (default: 1) - transparency - ``thickness`` - float (default: 1) - thickness of the arc - ``color``, ``rgbcolor`` - string or 2-tuple (default: 'blue') - the color of the arc - ``linestyle`` - string (default: ``'solid'``) - The style of the line, which is one of ``'dashed'``, ``'dotted'``, ``'solid'``, ``'dashdot'``, or ``'--'``, ``':'``, ``'-'``, ``'-.'``, respectively. EXAMPLES: Plot an arc of circle centered at (0,0) with radius 1 in the sector `(\pi/4,3*\pi/4)`:: sage: arc((0,0), 1, sector=(pi/4,3*pi/4)) Graphics object consisting of 1 graphics primitive Plot an arc of an ellipse between the angles 0 and `\pi/2`:: sage: arc((2,3), 2, 1, sector=(0,pi/2)) Graphics object consisting of 1 graphics primitive Plot an arc of a rotated ellipse between the angles 0 and `\pi/2`:: sage: arc((2,3), 2, 1, angle=pi/5, sector=(0,pi/2)) Graphics object consisting of 1 graphics primitive Plot an arc of an ellipse in red with a dashed linestyle:: sage: arc((0,0), 2, 1, 0, (0,pi/2), linestyle="dashed", color="red") Graphics object consisting of 1 graphics primitive sage: arc((0,0), 2, 1, 0, (0,pi/2), linestyle="--", color="red") Graphics object consisting of 1 graphics primitive The default aspect ratio for arcs is 1.0:: sage: arc((0,0), 1, sector=(pi/4,3*pi/4)).aspect_ratio() 1.0 It is not possible to draw arcs in 3D:: sage: A = arc((0,0,0), 1) Traceback (most recent call last): ... NotImplementedError """ from sage.plot.all import Graphics # Reset aspect_ratio to 'automatic' in case scale is 'semilog[xy]'. # Otherwise matplotlib complains. scale = options.get('scale', None) if isinstance(scale, (list, tuple)): scale = scale[0] if scale == 'semilogy' or scale == 'semilogx': options['aspect_ratio'] = 'automatic' if len(center) == 2: if r2 is None: r2 = r1 g = Graphics() g._set_extra_kwds(Graphics._extract_kwds_for_show(options)) if len(sector) != 2: raise ValueError("the sector must consist of two angles") g.add_primitive(Arc( center[0], center[1], r1, r2, angle, sector[0], sector[1], options)) return g elif len(center) == 3: raise NotImplementedError
def finalize(self, G): r""" Finalize a root system plot. INPUT: - ``G`` -- a root system plot or ``0`` This sets the aspect ratio to 1 and remove the axes. This should be called by all the user-level plotting methods of root systems. This will become mostly obsolete when customization options won't be lost anymore upon addition of graphics objects and there will be a proper empty object for 2D and 3D plots. EXAMPLES:: sage: L = RootSystem(["B",2,1]).ambient_space() sage: options = L.plot_parse_options() sage: p = L.plot_roots(plot_options=options) sage: p += L.plot_coroots(plot_options=options) sage: p.axes() True sage: p = options.finalize(p) sage: p.axes() False sage: p.aspect_ratio() 1.0 sage: options = L.plot_parse_options(affine=False) sage: p = L.plot_roots(plot_options=options) sage: p += point([[1,1,0]]) sage: p = options.finalize(p) sage: p.aspect_ratio() [1.0, 1.0, 1.0] If the input is ``0``, this returns an empty graphics object:: sage: type(options.finalize(0)) <class 'sage.plot.plot3d.base.Graphics3dGroup'> sage: options = L.plot_parse_options() sage: type(options.finalize(0)) <class 'sage.plot.graphics.Graphics'> sage: list(options.finalize(0)) [] """ from sage.plot.graphics import Graphics if self.dimension == 2: if G == 0: G = Graphics() G.set_aspect_ratio(1) # TODO: make this customizable G.axes(False) elif self.dimension == 3: if G == 0: from sage.plot.plot3d.base import Graphics3dGroup G = Graphics3dGroup() G.aspect_ratio(1) # TODO: Configuration axes return G
def plot(self, chart=None, ambient_coords=None, mapping=None, chart_domain=None, fixed_coords=None, ranges=None, number_values=None, steps=None, parameters=None, label_axes=True, **extra_options): r""" Plot the vector field in a Cartesian graph based on the coordinates of some ambient chart. The vector field is drawn in terms of two (2D graphics) or three (3D graphics) coordinates of a given chart, called hereafter the *ambient chart*. The vector field's base points `p` (or their images `\Phi(p)` by some differentiable mapping `\Phi`) must lie in the ambient chart's domain. INPUT: - ``chart`` -- (default: ``None``) the ambient chart (see above); if ``None``, the default chart of the vector field's domain is used - ``ambient_coords`` -- (default: ``None``) tuple containing the 2 or 3 coordinates of the ambient chart in terms of which the plot is performed; if ``None``, all the coordinates of the ambient chart are considered - ``mapping`` -- :class:`~sage.manifolds.differentiable.diff_map.DiffMap` (default: ``None``); differentiable map `\Phi` providing the link between the vector field's domain and the ambient chart ``chart``; if ``None``, the identity map is assumed - ``chart_domain`` -- (default: ``None``) chart on the vector field's domain to define the points at which vector arrows are to be plotted; if ``None``, the default chart of the vector field's domain is used - ``fixed_coords`` -- (default: ``None``) dictionary with keys the coordinates of ``chart_domain`` that are kept fixed and with values the value of these coordinates; if ``None``, all the coordinates of ``chart_domain`` are used - ``ranges`` -- (default: ``None``) dictionary with keys the coordinates of ``chart_domain`` to be used and values tuples ``(x_min, x_max)`` specifying the coordinate range for the plot; if ``None``, the entire coordinate range declared during the construction of ``chart_domain`` is considered (with ``-Infinity`` replaced by ``-max_range`` and ``+Infinity`` by ``max_range``) - ``number_values`` -- (default: ``None``) either an integer or a dictionary with keys the coordinates of ``chart_domain`` to be used and values the number of values of the coordinate for sampling the part of the vector field's domain involved in the plot ; if ``number_values`` is a single integer, it represents the number of values for all coordinates; if ``number_values`` is ``None``, it is set to 9 for a 2D plot and to 5 for a 3D plot - ``steps`` -- (default: ``None``) dictionary with keys the coordinates of ``chart_domain`` to be used and values the step between each constant value of the coordinate; if ``None``, the step is computed from the coordinate range (specified in ``ranges``) and ``number_values``; on the contrary, if the step is provided for some coordinate, the corresponding number of values is deduced from it and the coordinate range - ``parameters`` -- (default: ``None``) dictionary giving the numerical values of the parameters that may appear in the coordinate expression of the vector field (see example below) - ``label_axes`` -- (default: ``True``) boolean determining whether the labels of the coordinate axes of ``chart`` shall be added to the graph; can be set to ``False`` if the graph is 3D and must be superposed with another graph - ``color`` -- (default: 'blue') color of the arrows representing the vectors - ``max_range`` -- (default: 8) numerical value substituted to ``+Infinity`` if the latter is the upper bound of the range of a coordinate for which the plot is performed over the entire coordinate range (i.e. for which no specific plot range has been set in ``ranges``); similarly ``-max_range`` is the numerical valued substituted for ``-Infinity`` - ``scale`` -- (default: 1) value by which the lengths of the arrows representing the vectors is multiplied - ``**extra_options`` -- extra options for the arrow plot, like ``linestyle``, ``width`` or ``arrowsize`` (see :func:`~sage.plot.arrow.arrow2d` and :func:`~sage.plot.plot3d.shapes.arrow3d` for details) OUTPUT: - a graphic object, either an instance of :class:`~sage.plot.graphics.Graphics` for a 2D plot (i.e. based on 2 coordinates of ``chart``) or an instance of :class:`~sage.plot.plot3d.base.Graphics3d` for a 3D plot (i.e. based on 3 coordinates of ``chart``) EXAMPLES: Plot of a vector field on a 2-dimensional manifold:: sage: M = Manifold(2, 'M') sage: X.<x,y> = M.chart() sage: v = M.vector_field(name='v') sage: v[:] = -y, x ; v.display() v = -y d/dx + x d/dy sage: v.plot() Graphics object consisting of 80 graphics primitives .. PLOT:: M = Manifold(2, 'M') X = M.chart('x y'); x, y = X[:] v = M.vector_field(name='v'); v[:] = -y, x g = v.plot() sphinx_plot(g) Plot with various options:: sage: v.plot(scale=0.5, color='green', linestyle='--', width=1, ....: arrowsize=6) Graphics object consisting of 80 graphics primitives .. PLOT:: M = Manifold(2, 'M') X = M.chart('x y'); x, y = X[:] v = M.vector_field(name='v'); v[:] = -y, x g = v.plot(scale=0.5, color='green', linestyle='--', width=1, arrowsize=6) sphinx_plot(g) :: sage: v.plot(max_range=4, number_values=5, scale=0.5) Graphics object consisting of 24 graphics primitives .. PLOT:: M = Manifold(2, 'M') X = M.chart('x y'); x, y = X[:] v = M.vector_field(name='v'); v[:] = -y, x g = v.plot(max_range=4, number_values=5, scale=0.5) sphinx_plot(g) Plot using parallel computation:: sage: Parallelism().set(nproc=2) sage: v.plot(scale=0.5, number_values=10, linestyle='--', width=1, ....: arrowsize=6) Graphics object consisting of 100 graphics primitives .. PLOT:: M = Manifold(2, 'M') X = M.chart('x y'); x, y = X[:] v = M.vector_field(name='v'); v[:] = -y, x g = v.plot(scale=0.5, number_values=10, linestyle='--', width=1, arrowsize=6) sphinx_plot(g) :: sage: Parallelism().set(nproc=1) # switch off parallelization Plots along a line of fixed coordinate:: sage: v.plot(fixed_coords={x: -2}) Graphics object consisting of 9 graphics primitives .. PLOT:: M = Manifold(2, 'M') X = M.chart('x y'); x, y = X[:] v = M.vector_field(name='v'); v[:] = -y, x g = v.plot(fixed_coords={x: -2}) sphinx_plot(g) :: sage: v.plot(fixed_coords={y: 1}) Graphics object consisting of 9 graphics primitives .. PLOT:: M = Manifold(2, 'M') X = M.chart('x y'); x, y = X[:] v = M.vector_field(name='v'); v[:] = -y, x g = v.plot(fixed_coords={y: 1}) sphinx_plot(g) Let us now consider a vector field on a 4-dimensional manifold:: sage: M = Manifold(4, 'M') sage: X.<t,x,y,z> = M.chart() sage: v = M.vector_field(name='v') sage: v[:] = (t/8)^2, -t*y/4, t*x/4, t*z/4 ; v.display() v = 1/64*t^2 d/dt - 1/4*t*y d/dx + 1/4*t*x d/dy + 1/4*t*z d/dz We cannot make a 4D plot directly:: sage: v.plot() Traceback (most recent call last): ... ValueError: the number of ambient coordinates must be either 2 or 3, not 4 Rather, we have to select some coordinates for the plot, via the argument ``ambient_coords``. For instance, for a 3D plot:: sage: v.plot(ambient_coords=(x, y, z), fixed_coords={t: 1}, # long time ....: number_values=4) Graphics3d Object .. PLOT:: M = Manifold(4, 'M') X = M.chart('t x y z') ; t,x,y,z = X[:] v = M.vector_field(name='v') v[:] = (t/8)**2, -t*y/4, t*x/4, t*z/4 sphinx_plot(v.plot(ambient_coords=(x, y, z), fixed_coords={t: 1}, number_values=4)) :: sage: v.plot(ambient_coords=(x, y, t), fixed_coords={z: 0}, # long time ....: ranges={x: (-2,2), y: (-2,2), t: (-1, 4)}, ....: number_values=4) Graphics3d Object .. PLOT:: M = Manifold(4, 'M') X = M.chart('t x y z'); t,x,y,z = X[:] v = M.vector_field(name='v') v[:] = (t/8)**2, -t*y/4, t*x/4, t*z/4 sphinx_plot(v.plot(ambient_coords=(x, y, t), fixed_coords={z: 0}, ranges={x: (-2,2), y: (-2,2), t: (-1, 4)}, number_values=4)) or, for a 2D plot:: sage: v.plot(ambient_coords=(x, y), fixed_coords={t: 1, z: 0}) # long time Graphics object consisting of 80 graphics primitives .. PLOT:: M = Manifold(4, 'M') X = M.chart('t x y z'); t,x,y,z = X[:] v = M.vector_field(name='v') v[:] = (t/8)**2, -t*y/4, t*x/4, t*z/4 g = v.plot(ambient_coords=(x, y), fixed_coords={t: 1, z: 0}) sphinx_plot(g) :: sage: v.plot(ambient_coords=(x, t), fixed_coords={y: 1, z: 0}) # long time Graphics object consisting of 72 graphics primitives .. PLOT:: M = Manifold(4, 'M') X = M.chart('t x y z'); t,x,y,z = X[:] v = M.vector_field(name='v') v[:] = v[:] = (t/8)**2, -t*y/4, t*x/4, t*z/4 g = v.plot(ambient_coords=(x, t), fixed_coords={y: 1, z: 0}) sphinx_plot(g) An example of plot via a differential mapping: plot of a vector field tangent to a 2-sphere viewed in `\RR^3`:: sage: S2 = Manifold(2, 'S^2') sage: U = S2.open_subset('U') # the open set covered by spherical coord. sage: XS.<th,ph> = U.chart(r'th:(0,pi):\theta ph:(0,2*pi):\phi') sage: R3 = Manifold(3, 'R^3') sage: X3.<x,y,z> = R3.chart() sage: F = S2.diff_map(R3, {(XS, X3): [sin(th)*cos(ph), ....: sin(th)*sin(ph), cos(th)]}, name='F') sage: F.display() # the standard embedding of S^2 into R^3 F: S^2 --> R^3 on U: (th, ph) |--> (x, y, z) = (cos(ph)*sin(th), sin(ph)*sin(th), cos(th)) sage: v = XS.frame()[1] ; v # the coordinate vector d/dphi Vector field d/dph on the Open subset U of the 2-dimensional differentiable manifold S^2 sage: graph_v = v.plot(chart=X3, mapping=F, label_axes=False) sage: graph_S2 = XS.plot(chart=X3, mapping=F, number_values=9) sage: graph_v + graph_S2 Graphics3d Object .. PLOT:: S2 = Manifold(2, 'S^2') U = S2.open_subset('U') XS = U.chart(r'th:(0,pi):\theta ph:(0,2*pi):\phi') th, ph = XS[:] R3 = Manifold(3, 'R^3') X3 = R3.chart('x y z') F = S2.diff_map(R3, {(XS, X3): [sin(th)*cos(ph), sin(th)*sin(ph), cos(th)]}, name='F') v = XS.frame()[1] graph_v = v.plot(chart=X3, mapping=F, label_axes=False) graph_S2 = XS.plot(chart=X3, mapping=F, number_values=9) sphinx_plot(graph_v + graph_S2) Note that the default values of some arguments of the method ``plot`` are stored in the dictionary ``plot.options``:: sage: v.plot.options # random (dictionary output) {'color': 'blue', 'max_range': 8, 'scale': 1} so that they can be adjusted by the user:: sage: v.plot.options['color'] = 'red' From now on, all plots of vector fields will use red as the default color. To restore the original default options, it suffices to type:: sage: v.plot.reset() """ from sage.rings.infinity import Infinity from sage.misc.functional import numerical_approx from sage.misc.latex import latex from sage.plot.graphics import Graphics from sage.manifolds.chart import RealChart from sage.manifolds.utilities import set_axes_labels from sage.parallel.decorate import parallel from sage.parallel.parallelism import Parallelism # # 1/ Treatment of input parameters # ----------------------------- max_range = extra_options.pop("max_range") scale = extra_options.pop("scale") color = extra_options.pop("color") if chart is None: chart = self._domain.default_chart() elif not isinstance(chart, RealChart): raise TypeError("{} is not a chart on a real ".format(chart) + "manifold") if chart_domain is None: chart_domain = self._domain.default_chart() elif not isinstance(chart_domain, RealChart): raise TypeError("{} is not a chart on a ".format(chart_domain) + "real manifold") elif not chart_domain.domain().is_subset(self._domain): raise ValueError("the domain of {} is not ".format(chart_domain) + "included in the domain of {}".format(self)) coords_full = tuple(chart_domain[:]) # all coordinates of chart_domain if fixed_coords is None: coords = coords_full else: fixed_coord_list = fixed_coords.keys() coords = [] for coord in coords_full: if coord not in fixed_coord_list: coords.append(coord) coords = tuple(coords) if ambient_coords is None: ambient_coords = chart[:] elif not isinstance(ambient_coords, tuple): ambient_coords = tuple(ambient_coords) nca = len(ambient_coords) if nca != 2 and nca !=3: raise ValueError("the number of ambient coordinates must be " + "either 2 or 3, not {}".format(nca)) if ranges is None: ranges = {} ranges0 = {} for coord in coords: if coord in ranges: ranges0[coord] = (numerical_approx(ranges[coord][0]), numerical_approx(ranges[coord][1])) else: bounds = chart_domain._bounds[coords_full.index(coord)] xmin0 = bounds[0][0] xmax0 = bounds[1][0] if xmin0 == -Infinity: xmin = numerical_approx(-max_range) elif bounds[0][1]: xmin = numerical_approx(xmin0) else: xmin = numerical_approx(xmin0 + 1.e-3) if xmax0 == Infinity: xmax = numerical_approx(max_range) elif bounds[1][1]: xmax = numerical_approx(xmax0) else: xmax = numerical_approx(xmax0 - 1.e-3) ranges0[coord] = (xmin, xmax) ranges = ranges0 if number_values is None: if nca == 2: # 2D plot number_values = 9 else: # 3D plot number_values = 5 if not isinstance(number_values, dict): number_values0 = {} for coord in coords: number_values0[coord] = number_values number_values = number_values0 if steps is None: steps = {} for coord in coords: if coord not in steps: steps[coord] = (ranges[coord][1] - ranges[coord][0])/ \ (number_values[coord]-1) else: number_values[coord] = 1 + int( (ranges[coord][1] - ranges[coord][0])/ steps[coord]) # # 2/ Plots # ----- dom = chart_domain.domain() vector = self.restrict(dom) if vector.parent().destination_map() is dom.identity_map(): if mapping is not None: vector = mapping.pushforward(vector) mapping = None nc = len(coords_full) ncp = len(coords) xx = [0] * nc if fixed_coords is not None: if len(fixed_coords) != nc - ncp: raise ValueError("bad number of fixed coordinates") for fc, val in fixed_coords.items(): xx[coords_full.index(fc)] = val ind_coord = [] for coord in coords: ind_coord.append(coords_full.index(coord)) resu = Graphics() ind = [0] * ncp ind_max = [0] * ncp ind_max[0] = number_values[coords[0]] xmin = [ranges[cd][0] for cd in coords] step_tab = [steps[cd] for cd in coords] nproc = Parallelism().get('tensor') if nproc != 1 and nca == 2: # parallel plot construct : Only for 2D plot (at moment) ! # creation of the list of parameters list_xx = [] while ind != ind_max: for i in range(ncp): xx[ind_coord[i]] = xmin[i] + ind[i]*step_tab[i] if chart_domain.valid_coordinates(*xx, tolerance=1e-13, parameters=parameters): # needed a xx*1 to copy the list by value list_xx.append(xx*1) # Next index: ret = 1 for pos in range(ncp-1,-1,-1): imax = number_values[coords[pos]] - 1 if ind[pos] != imax: ind[pos] += ret ret = 0 elif ret == 1: if pos == 0: ind[pos] = imax + 1 # end point reached else: ind[pos] = 0 ret = 1 lol = lambda lst, sz: [lst[i:i+sz] for i in range(0, len(lst), sz)] ind_step = max(1, int(len(list_xx)/nproc/2)) local_list = lol(list_xx,ind_step) # definition of the list of input parameters listParalInput = [(vector, dom, ind_part, chart_domain, chart, ambient_coords, mapping, scale, color, parameters, extra_options) for ind_part in local_list] # definition of the parallel function @parallel(p_iter='multiprocessing', ncpus=nproc) def add_point_plot(vector, dom, xx_list, chart_domain, chart, ambient_coords, mapping, scale, color, parameters, extra_options): count = 0 for xx in xx_list: point = dom(xx, chart=chart_domain) part = vector.at(point).plot(chart=chart, ambient_coords=ambient_coords, mapping=mapping,scale=scale, color=color, print_label=False, parameters=parameters, **extra_options) if count == 0: local_resu = part else: local_resu += part count += 1 return local_resu # parallel execution and reconstruction of the plot for ii, val in add_point_plot(listParalInput): resu += val else: # sequential plot while ind != ind_max: for i in range(ncp): xx[ind_coord[i]] = xmin[i] + ind[i]*step_tab[i] if chart_domain.valid_coordinates(*xx, tolerance=1e-13, parameters=parameters): point = dom(xx, chart=chart_domain) resu += vector.at(point).plot(chart=chart, ambient_coords=ambient_coords, mapping=mapping, scale=scale, color=color, print_label=False, parameters=parameters, **extra_options) # Next index: ret = 1 for pos in range(ncp-1, -1, -1): imax = number_values[coords[pos]] - 1 if ind[pos] != imax: ind[pos] += ret ret = 0 elif ret == 1: if pos == 0: ind[pos] = imax + 1 # end point reached else: ind[pos] = 0 ret = 1 if label_axes: if nca == 2: # 2D graphic # We update the dictionary _extra_kwds (options to be passed # to show()), instead of using the method # Graphics.axes_labels() since the latter is not robust w.r.t. # graph addition resu._extra_kwds['axes_labels'] = [r'$'+latex(ac)+r'$' for ac in ambient_coords] else: # 3D graphic labels = [str(ac) for ac in ambient_coords] resu = set_axes_labels(resu, *labels) return resu
def plot(self, color='sign'): """ Return a plot of ``self``. INPUT: - ``color`` -- can be any of the following: * ``4`` - use 4 colors: black, red, blue, and green with each corresponding to up, right, down, and left respectively * ``2`` - use 2 colors: red for horizontal, blue for vertical arrows * ``'sign'`` - use red for right and down arrows, blue for left and up arrows * a list of 4 colors for each direction * a function which takes a direction and a boolean corresponding to the sign EXAMPLES:: sage: M = SixVertexModel(2, boundary_conditions='ice') sage: print(M[0].plot().description()) Arrow from (-1.0,0.0) to (0.0,0.0) Arrow from (-1.0,1.0) to (0.0,1.0) Arrow from (0.0,0.0) to (0.0,-1.0) Arrow from (0.0,0.0) to (1.0,0.0) Arrow from (0.0,1.0) to (0.0,0.0) Arrow from (0.0,1.0) to (0.0,2.0) Arrow from (1.0,0.0) to (1.0,-1.0) Arrow from (1.0,0.0) to (1.0,1.0) Arrow from (1.0,1.0) to (0.0,1.0) Arrow from (1.0,1.0) to (1.0,2.0) Arrow from (2.0,0.0) to (1.0,0.0) Arrow from (2.0,1.0) to (1.0,1.0) """ from sage.plot.graphics import Graphics from sage.plot.circle import circle from sage.plot.arrow import arrow if color == 4: color_list = ['black', 'red', 'blue', 'green'] cfunc = lambda d,pm: color_list[d] elif color == 2: cfunc = lambda d,pm: 'red' if d % 2 == 0 else 'blue' elif color == 1 or color is None: cfunc = lambda d,pm: 'black' elif color == 'sign': cfunc = lambda d,pm: 'red' if pm else 'blue' # RD are True elif isinstance(color, (list, tuple)): cfunc = lambda d,pm: color[d] else: cfunc = color G = Graphics() for j,row in enumerate(reversed(self)): for i,entry in enumerate(row): if entry == 0: # LR G += arrow((i,j+1), (i,j), color=cfunc(2, True)) G += arrow((i,j), (i+1,j), color=cfunc(1, True)) if j == 0: G += arrow((i,j-1), (i,j), color=cfunc(0, False)) if i == 0: G += arrow((i,j), (i-1,j), color=cfunc(3, False)) elif entry == 1: # LU G += arrow((i,j), (i,j+1), color=cfunc(0, False)) G += arrow((i+1,j), (i,j), color=cfunc(3, False)) if j == 0: G += arrow((i,j-1), (i,j), color=cfunc(0, False)) if i == 0: G += arrow((i,j), (i-1,j), color=cfunc(3, False)) elif entry == 2: # LD G += arrow((i,j+1), (i,j), color=cfunc(2, True)) G += arrow((i+1,j), (i,j), color=cfunc(3, False)) if j == 0: G += arrow((i,j), (i,j-1), color=cfunc(2, True)) if i == 0: G += arrow((i,j), (i-1,j), color=cfunc(3, False)) elif entry == 3: # UD G += arrow((i,j), (i,j+1), color=cfunc(0, False)) G += arrow((i+1,j), (i,j), color=cfunc(3, False)) if j == 0: G += arrow((i,j), (i,j-1), color=cfunc(2, True)) if i == 0: G += arrow((i-1,j), (i,j), color=cfunc(1, True)) elif entry == 4: # UR G += arrow((i,j), (i,j+1), color=cfunc(0, False)) G += arrow((i,j), (i+1,j), color=cfunc(1, True)) if j == 0: G += arrow((i,j-1), (i,j), color=cfunc(0, False)) if i == 0: G += arrow((i-1,j), (i,j), color=cfunc(1, True)) elif entry == 5: # RD G += arrow((i,j+1), (i,j), color=cfunc(2, True)) G += arrow((i,j), (i+1,j), color=cfunc(1, True)) if j == 0: G += arrow((i,j), (i,j-1), color=cfunc(2, True)) if i == 0: G += arrow((i-1,j), (i,j), color=cfunc(1, True)) G.axes(False) return G
def plot(hyperplane_arrangement, **kwds): r""" Return a plot of the hyperplane arrangement. If the arrangement is in 4 dimensions but inessential, a plot of the essentialization is returned. .. NOTE:: This function is available as the :meth:`~sage.geometry.hyperplane_arrangement.arrangement.HyperplaneArrangementElement.plot` method of hyperplane arrangements. You should not call this function directly, only through the method. INPUT: - ``hyperplane_arrangement`` -- the hyperplane arrangement to plot - ``**kwds`` -- plot options: see :mod:`sage.geometry.hyperplane_arrangement.plot`. OUTPUT: A graphics object of the plot. EXAMPLES:: sage: B = hyperplane_arrangements.semiorder(4) sage: B.plot() Displaying the essentialization. Graphics3d Object """ N = len(hyperplane_arrangement) dim = hyperplane_arrangement.dimension() if hyperplane_arrangement.base_ring().characteristic() != 0: raise NotImplementedError('must be a field of characteristic 0') elif dim == 4: if not hyperplane_arrangement.is_essential(): print('Displaying the essentialization.') hyperplane_arrangement = hyperplane_arrangement.essentialization() elif dim not in [1,2,3]: # revise to handle 4d return # silently # handle extra keywords if 'hyperplane_colors' in kwds: hyp_colors = kwds.pop('hyperplane_colors') if not isinstance(hyp_colors, list): # we assume its a single color then hyp_colors = [hyp_colors] * N else: HSV_tuples = [(i*1.0/N, 0.8, 0.9) for i in range(N)] hyp_colors = [hsv_to_rgb(*x) for x in HSV_tuples] if 'hyperplane_labels' in kwds: hyp_labels = kwds.pop('hyperplane_labels') has_hyp_label = True if not isinstance(hyp_labels, list): # we assume its a boolean then hyp_labels = [hyp_labels] * N relabeled = [] for i in range(N): if hyp_labels[i] in [True,'long']: relabeled.append(True) else: relabeled.append(str(i)) hyp_labels = relabeled else: has_hyp_label = False if 'label_colors' in kwds: label_colors = kwds.pop('label_colors') has_label_color = True if not isinstance(label_colors, list): # we assume its a single color then label_colors = [label_colors] * N else: has_label_color = False if 'label_fontsize' in kwds: label_fontsize = kwds.pop('label_fontsize') has_label_fontsize = True if not isinstance(label_fontsize, list): # we assume its a single size then label_fontsize = [label_fontsize] * N else: has_label_fontsize = False if 'label_offsets' in kwds: has_offsets = True offsets = kwds.pop('label_offsets') else: has_offsets = False # give default values below hyperplane_legend = kwds.pop('hyperplane_legend', 'long' if dim < 3 else False) if 'hyperplane_opacities' in kwds: hyperplane_opacities = kwds.pop('hyperplane_opacities') has_opacity = True if not isinstance(hyperplane_opacities, list): # we assume a single number then hyperplane_opacities = [hyperplane_opacities] * N else: has_opacity = False point_sizes = kwds.pop('point_sizes', 50) if not isinstance(point_sizes, list): point_sizes = [point_sizes] * N if 'ranges' in kwds: ranges_set = True ranges = kwds.pop('ranges') if not type(ranges) in [list,tuple]: # ranges is a single number ranges = [ranges] * N # So ranges is some type of list. elif dim == 2: # arrangement of lines in the plane if not type(ranges[0]) in [list,tuple]: # a single interval ranges = [ranges] * N elif dim == 3: # arrangement of planes in 3-space if not type(ranges[0][0]) in [list,tuple]: ranges = [ranges] * N elif dim not in [2,3]: # ranges is not an option unless dim is 2 or 3 ranges_set = False else: # a list of intervals, one for each hyperplane is given pass # ranges does not need to be modified else: ranges_set = False # give default values below # the extra keywords have now been handled # now handle the legend if dim in [1,2]: # points on a line or lines in the plane if hyperplane_legend in [True,'long']: hyps = hyperplane_arrangement.hyperplanes() legend_labels = [hyps[i]._latex_() for i in range(N)] elif hyperplane_legend == 'short' : legend_labels = [str(i) for i in range(N)] else: # dim==3, arrangement of planes in 3-space if hyperplane_legend in [True, 'long']: legend3d = legend_3d(hyperplane_arrangement, hyp_colors, 'long') elif hyperplane_legend == 'short': legend3d = legend_3d(hyperplane_arrangement, hyp_colors, 'short') ## done handling the legend ## now create the plot p = Graphics() for i in range(N): newk = copy(kwds) if has_hyp_label: newk['hyperplane_label'] = hyp_labels[i] if has_offsets: if not isinstance(offsets, list): newk['label_offset'] = offsets else: newk['label_offset'] = offsets[i] else: newk['hyperplane_label'] = False if has_label_color: newk['label_color'] = label_colors[i] if has_label_fontsize: newk['label_fontsize'] = label_fontsize[i] if has_opacity: newk['opacity'] = hyperplane_opacities[i] if dim == 1: newk['point_size'] = point_sizes[i] if dim in [1,2] and hyperplane_legend: # more options than T/F newk['legend_label'] = legend_labels[i] if ranges_set: newk['ranges'] = ranges[i] p += plot_hyperplane(hyperplane_arrangement[i], rgbcolor=hyp_colors[i], **newk) if dim == 1: if hyperplane_legend: # there are more options than T/F p.legend(True) return p elif dim == 2: if hyperplane_legend: # there are more options than T/F p.legend(True) return p else: # dim==3 if hyperplane_legend: # there are more options than T/F return p, legend3d else: return p