summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEric Gourgoulhon <eric.gourgoulhon@obspm.fr>2015-06-29 16:57:07 +0200
committerEric Gourgoulhon <eric.gourgoulhon@obspm.fr>2015-06-29 16:57:07 +0200
commit4c72118b8be5be766944439c53df6bda370e7ae2 (patch)
treed61e96d7b6f920f8a105cf5649305ec107c9ab2b
parentImprove documentation of classes Chart and ContinuousMap (diff)
Add method plot() to RealChart and TopManifoldPoint
-rw-r--r--src/sage/manifolds/chart.py550
-rw-r--r--src/sage/manifolds/point.py183
-rw-r--r--src/sage/manifolds/utilities.py41
3 files changed, 773 insertions, 1 deletions
diff --git a/src/sage/manifolds/chart.py b/src/sage/manifolds/chart.py
index 9de211c..910cc2e 100644
--- a/src/sage/manifolds/chart.py
+++ b/src/sage/manifolds/chart.py
@@ -1,5 +1,5 @@
r"""
-Coordinate charts on topological manifolds
+Coordinate charts
The class :class:`Chart` implements coordinate charts on a topological manifold
over a topological field `K`. The subclass :class:`RealChart` is devoted
@@ -1808,6 +1808,554 @@ class RealChart(Chart):
# All tests have been passed:
return True
+ def plot(self, chart=None, ambient_coords=None, mapping=None,
+ fixed_coords=None, ranges=None, max_value=8, nb_values=None,
+ steps=None, parameters=None, color='red', style='-', thickness=1,
+ plot_points=75, label_axes=True):
+ r"""
+ Plot the current chart as a "grid" in a Cartesian graph
+ based on the coordinates of some ambient chart.
+
+ The "grid" is formed by curves along which a chart coordinate
+ varies, the other coordinates being kept fixed; it is drawn in terms of
+ two (2D graphics) or three (3D graphics) coordinates of another chart,
+ called hereafter the *ambient chart*.
+
+ The ambient chart is related to the current chart either by
+ a transition map if both charts are defined on the same manifold, or by
+ the coordinate expression of some continuous map (typically an
+ immersion). In the latter case, the two charts may be defined on two
+ different manifolds.
+
+ INPUT:
+
+ - ``chart`` -- (default: ``None``) the ambient chart (see above); if
+ ``None``, the ambient chart is set to the current chart
+ - ``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``) continuous manifold map (instance
+ of :class:`~sage.manifolds.continuous_map.ContinuousMap`)
+ providing the link between the current chart and the ambient chart
+ (cf. above); if ``None``, both charts are supposed to be defined on
+ the same manifold and related by some transition map (see
+ :meth:`transition_map`)
+ - ``fixed_coords`` -- (default: ``None``) dictionary with keys the
+ chart coordinates that are not drawn and with values the fixed
+ value of these coordinates; if ``None``, all the coordinates of the
+ current chart are drawn
+ - ``ranges`` -- (default: ``None``) dictionary with keys the coordinates
+ to be drawn and values tuples ``(x_min,x_max)`` specifying the
+ coordinate range for the plot; if ``None``, the entire coordinate
+ range declared during the chart construction 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 to be drawn and values the number of
+ constant values of the coordinate to be considered; if ``nb_values``
+ is a single integer, it represents the number of constant 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
+ to be drawn 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 constant 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 relation between
+ the two coordinate systems
+ - ``color`` -- (default: 'red') either a single color or a dictionary
+ of colors, with keys the coordinates to be drawn, representing the
+ colors of the lines along which the coordinate varies, the other
+ being kept constant; if ``color`` is a single color, it is used for
+ all coordinate lines
+ - ``style`` -- (default: '-') either a single line style or a dictionary
+ of line styles, with keys the coordinates to be drawn, representing
+ the style of the lines along which the coordinate varies, the other
+ being kept constant; if ``style`` is a single style, it is used for
+ all coordinate lines; NB: ``style`` is effective only for 2D plots
+ - ``thickness`` -- (default: 1) either a single line thickness or a
+ dictionary of line thicknesses, with keys the coordinates to be drawn,
+ representing the thickness of the lines along which the coordinate
+ varies, the other being kept constant; if ``thickness`` is a single
+ value, it is used for all coordinate lines
+ - ``plot_points`` -- (default: 75) either a single number of points or
+ a dictionary of integers, with keys the coordinates to be drawn,
+ representing the number of points to plot the lines along which the
+ coordinate varies, the other being kept constant; if ``plot_points``
+ is a single integer, it is used for all coordinate lines
+ - ``label_axes`` -- (default: ``True``) boolean determining whether the
+ labels of the ambient coordinate axes shall be added to the graph;
+ can be set to False if the graph is 3D and must be superposed with
+ another graph.
+
+ OUTPUT:
+
+ - a graphic object, either an instance of
+ :class:`~sage.plot.graphics.Graphics` for a 2D plot (i.e. based on
+ 2 coordinates of the ambient chart) or an instance of
+ :class:`~sage.plot.plot3d.base.Graphics3d` for a 3D plot (i.e.
+ based on 3 coordinates of the ambient chart)
+
+ EXAMPLES:
+
+ Grid of polar coordinates in terms of Cartesian coordinates in the
+ Euclidean plane::
+
+ sage: R2 = TopManifold(2, 'R^2') # the Euclidean plane
+ sage: c_cart.<x,y> = R2.chart() # Cartesian coordinates
+ sage: U = R2.open_subset('U', coord_def={c_cart: (y!=0, x<0)}) # the complement of the segment y=0 and x>0
+ sage: c_pol.<r,ph> = U.chart(r'r:(0,+oo) ph:(0,2*pi):\phi') # polar coordinates on U
+ sage: pol_to_cart = c_pol.transition_map(c_cart, [r*cos(ph), r*sin(ph)])
+ sage: g = c_pol.plot(c_cart)
+ sage: type(g)
+ <class 'sage.plot.graphics.Graphics'>
+ sage: show(g) # graphical display
+
+ .. PLOT::
+
+ R2 = TopManifold(2, 'R^2')
+ c_cart = R2.chart('x y'); x, y = c_cart[:]
+ U = R2.open_subset('U', coord_def={c_cart: (y!=0, x<0)})
+ c_pol = U.chart(r'r:(0,+oo) ph:(0,2*pi):\phi'); r, ph = c_pol[:]
+ pol_to_cart = c_pol.transition_map(c_cart, [r*cos(ph), r*sin(ph)])
+ g = c_pol.plot(c_cart)
+ sphinx_plot(g)
+
+ Call with non-default values::
+
+ sage: g = c_pol.plot(c_cart, ranges={ph:(pi/4,pi)}, nb_values={r:7, ph:17},
+ ....: color={r:'red', ph:'green'}, style={r:'-', ph:'--'})
+
+ .. PLOT::
+
+ R2 = TopManifold(2, 'R^2')
+ c_cart = R2.chart('x y'); x, y = c_cart[:]
+ U = R2.open_subset('U', coord_def={c_cart: (y!=0, x<0)})
+ c_pol = U.chart(r'r:(0,+oo) ph:(0,2*pi):\phi'); r, ph = c_pol[:]
+ pol_to_cart = c_pol.transition_map(c_cart, [r*cos(ph), r*sin(ph)])
+ g = c_pol.plot(c_cart, ranges={ph:(pi/4,pi)}, nb_values={r:7, ph:17}, \
+ color={r:'red', ph:'green'}, style={r:'-', ph:'--'})
+ sphinx_plot(g)
+
+ A single coordinate line can be drawn::
+
+ sage: g = c_pol.plot(c_cart, fixed_coords={r: 2}) # draw a circle of radius r=2
+
+ .. PLOT::
+
+ R2 = TopManifold(2, 'R^2')
+ c_cart = R2.chart('x y'); x, y = c_cart[:]
+ U = R2.open_subset('U', coord_def={c_cart: (y!=0, x<0)})
+ c_pol = U.chart(r'r:(0,+oo) ph:(0,2*pi):\phi'); r, ph = c_pol[:]
+ pol_to_cart = c_pol.transition_map(c_cart, [r*cos(ph), r*sin(ph)])
+ g = c_pol.plot(c_cart, fixed_coords={r: 2})
+ sphinx_plot(g)
+
+ ::
+
+ sage: g = c_pol.plot(c_cart, fixed_coords={ph: pi/4}) # draw a segment at phi=pi/4
+
+ .. PLOT::
+
+ R2 = TopManifold(2, 'R^2')
+ c_cart = R2.chart('x y'); x, y = c_cart[:]
+ U = R2.open_subset('U', coord_def={c_cart: (y!=0, x<0)})
+ c_pol = U.chart(r'r:(0,+oo) ph:(0,2*pi):\phi'); r, ph = c_pol[:]
+ pol_to_cart = c_pol.transition_map(c_cart, [r*cos(ph), r*sin(ph)])
+ g = c_pol.plot(c_cart, fixed_coords={ph: pi/4})
+ sphinx_plot(g)
+
+ A chart can be plot in terms of itself, resulting in a rectangular grid::
+
+ sage: g = c_cart.plot() # equivalent to c_cart.plot(c_cart)
+ sage: show(g) # a rectangular grid
+
+ .. PLOT::
+
+ R2 = TopManifold(2, 'R^2')
+ c_cart = R2.chart('x y'); x, y = c_cart[:]
+ g = c_cart.plot()
+ sphinx_plot(g)
+
+ An example with the ambient chart given by the coordinate expression of
+ some manifold map: 3D plot of the stereographic charts on the
+ 2-sphere::
+
+ sage: S2 = TopManifold(2, 'S^2') # the 2-sphere
+ sage: U = S2.open_subset('U') ; V = S2.open_subset('V') # complement of the North and South pole, respectively
+ sage: S2.declare_union(U,V)
+ sage: c_xy.<x,y> = U.chart() # stereographic coordinates from the North pole
+ sage: c_uv.<u,v> = V.chart() # stereographic coordinates from the South pole
+ sage: xy_to_uv = c_xy.transition_map(c_uv, (x/(x^2+y^2), y/(x^2+y^2)),
+ ....: intersection_name='W', restrictions1= x^2+y^2!=0,
+ ....: restrictions2= u^2+v^2!=0)
+ sage: uv_to_xy = xy_to_uv.inverse()
+ sage: R3 = TopManifold(3, 'R^3') # the Euclidean space R^3
+ sage: c_cart.<X,Y,Z> = R3.chart() # Cartesian coordinates on R^3
+ sage: Phi = S2.continuous_map(R3, {(c_xy, c_cart): [2*x/(1+x^2+y^2),
+ ....: 2*y/(1+x^2+y^2), (x^2+y^2-1)/(1+x^2+y^2)],
+ ....: (c_uv, c_cart): [2*u/(1+u^2+v^2),
+ ....: 2*v/(1+u^2+v^2), (1-u^2-v^2)/(1+u^2+v^2)]},
+ ....: name='Phi', latex_name=r'\Phi') # Embedding of S^2 in R^3
+ sage: g = c_xy.plot(c_cart, mapping=Phi)
+ sage: show(g) # 3D graphic display
+ sage: type(g)
+ <class 'sage.plot.plot3d.base.Graphics3dGroup'>
+
+ The same plot without the (X,Y,Z) axes labels::
+
+ sage: g = c_xy.plot(c_cart, mapping=Phi, label_axes=False)
+
+ The North and South stereographic charts on the same plot::
+
+ sage: g2 = c_uv.plot(c_cart, mapping=Phi, color='green')
+ sage: show(g+g2)
+
+ South stereographic chart drawned in terms of the North one (we split
+ the plot in four parts to avoid the singularity at (u,v)=(0,0))::
+
+ sage: W = U.intersection(V) # the subset common to both charts
+ sage: c_uvW = c_uv.restrict(W) # chart (W,(u,v))
+ sage: gSN1 = c_uvW.plot(c_xy, ranges={u:[-6.,-0.02], v:[-6.,-0.02]}, nb_values=20, plot_points=100)
+ sage: gSN2 = c_uvW.plot(c_xy, ranges={u:[-6.,-0.02], v:[0.02,6.]}, nb_values=20, plot_points=100)
+ sage: gSN3 = c_uvW.plot(c_xy, ranges={u:[0.02,6.], v:[-6.,-0.02]}, nb_values=20, plot_points=100)
+ sage: gSN4 = c_uvW.plot(c_xy, ranges={u:[0.02,6.], v:[0.02,6.]}, nb_values=20, plot_points=100)
+ sage: show(gSN1+gSN2+gSN3+gSN4, xmin=-3, xmax=3, ymin=-3, ymax=3)
+
+ .. PLOT::
+
+ S2 = TopManifold(2, 'S^2')
+ U = S2.open_subset('U'); V = S2.open_subset('V'); S2.declare_union(U,V)
+ c_xy = U.chart('x y'); x, y = c_xy[:]
+ c_uv = V.chart('u v'); u, v = c_uv[:]
+ xy_to_uv = c_xy.transition_map(c_uv, (x/(x**2+y**2), y/(x**2+y**2)), \
+ intersection_name='W', restrictions1= x**2+y**2!=0, \
+ restrictions2= u**2+v**2!=0)
+ uv_to_xy = xy_to_uv.inverse()
+ c_uvW = c_uv.restrict(U.intersection(V))
+ gSN1 = c_uvW.plot(c_xy, ranges={u:[-6.,-0.02], v:[-6.,-0.02]}, nb_values=20, plot_points=100)
+ gSN2 = c_uvW.plot(c_xy, ranges={u:[-6.,-0.02], v:[0.02,6.]}, nb_values=20, plot_points=100)
+ gSN3 = c_uvW.plot(c_xy, ranges={u:[0.02,6.], v:[-6.,-0.02]}, nb_values=20, plot_points=100)
+ gSN4 = c_uvW.plot(c_xy, ranges={u:[0.02,6.], v:[0.02,6.]}, nb_values=20, plot_points=100)
+ g = gSN1+gSN2+gSN3+gSN4; g.set_axes_range(-3, 3, -3, 3)
+ sphinx_plot(g)
+
+ The coordinate line u=1 (red) and the coordinate line v=1 (green) on
+ the same plot::
+
+ sage: gu1 = c_uvW.plot(c_xy, fixed_coords={u:1}, max_value=20, plot_points=200)
+ sage: gv1 = c_uvW.plot(c_xy, fixed_coords={v:1}, max_value=20, plot_points=200, color='green')
+ sage: show(gu1+gv1)
+
+ .. PLOT::
+
+ S2 = TopManifold(2, 'S^2')
+ U = S2.open_subset('U'); V = S2.open_subset('V'); S2.declare_union(U,V)
+ c_xy = U.chart('x y'); x, y = c_xy[:]
+ c_uv = V.chart('u v'); u, v = c_uv[:]
+ xy_to_uv = c_xy.transition_map(c_uv, (x/(x**2+y**2), y/(x**2+y**2)), \
+ intersection_name='W', restrictions1= x**2+y**2!=0, \
+ restrictions2= u**2+v**2!=0)
+ uv_to_xy = xy_to_uv.inverse()
+ c_uvW = c_uv.restrict(U.intersection(V))
+ gu1 = c_uvW.plot(c_xy, fixed_coords={u:1}, max_value=20, plot_points=200)
+ gv1 = c_uvW.plot(c_xy, fixed_coords={v:1}, max_value=20, plot_points=200, color='green')
+ sphinx_plot(gu1+gv1)
+
+ Note that we have set ``max_value=20`` to have a wider range for the
+ coordinates u and v, i.e. to have [-20,20] instead of the default
+ [-8,8].
+
+ A 3-dimensional chart plotted in terms of itself results in a 3D
+ rectangular grid::
+
+ sage: g = c_cart.plot() # equivalent to c_cart.plot(c_cart)
+ sage: show(g) # a 3D mesh cube
+
+ A 4-dimensional chart plotted in terms of itself (the plot is
+ performed for at most 3 coordinates, which must be specified via
+ the argument ``ambient_coords``)::
+
+ sage: M = TopManifold(4, 'M')
+ sage: X.<t,x,y,z> = M.chart()
+ sage: g = X.plot(ambient_coords=(t,x,y)) # the coordinate z is not depicted
+ sage: show(g) # a 3D mesh cube
+ sage: g = X.plot(ambient_coords=(t,y)) # the coordinates x and z are not depicted
+ sage: show(g) # a 2D mesh square
+
+ .. PLOT::
+
+ M = TopManifold(4, 'M')
+ X = M.chart('t x y z'); t,x,y,z = X[:]
+ g = X.plot(ambient_coords=(t,y))
+ sphinx_plot(g)
+
+ """
+ from sage.misc.functional import numerical_approx
+ from sage.plot.graphics import Graphics
+ from sage.plot.line import line
+ from sage.manifolds.continuous_map import ContinuousMap
+ from utilities import set_axes_labels
+
+ def _plot_xx_list(xx_list, rem_coords, ranges, steps, nb_values):
+ r"""
+ Helper function to plot the coordinate grid.
+ """
+ coord = rem_coords[0]
+ xmin = ranges[coord][0]
+ sx = steps[coord]
+ resu = []
+ for xx in xx_list:
+ xc = xmin
+ for i in range(nb_values[coord]):
+ nxx = list(xx)
+ nxx[self._xx.index(coord)] = xc
+ resu.append(nxx)
+ xc += sx
+ if len(rem_coords) == 1:
+ return resu
+ else:
+ rem_coords.remove(coord)
+ return _plot_xx_list(resu, rem_coords, ranges, steps,
+ nb_values)
+
+ if chart is None:
+ chart = self
+ elif not isinstance(chart, Chart):
+ raise TypeError("the argument 'chart' must be a coordinate chart")
+ #
+ # 1/ Determination of the relation between self and chart
+ # ------------------------------------------------------------
+ nc = self._manifold.dimension()
+ if chart is self:
+ transf = self.multifunction(*(self._xx))
+ if nc > 3:
+ if ambient_coords is None:
+ raise TypeError("the argument 'ambient_coords' must be " +
+ "provided")
+ if len(ambient_coords) > 3:
+ raise ValueError("too many ambient coordinates")
+ fixed_coords = {}
+ for coord in self._xx:
+ if coord not in ambient_coords:
+ fixed_coords[coord] = 0
+ else:
+ transf = None # to be the MultiFunctionChart relating self to
+ # the ambient chart
+ if mapping is None:
+ if not self._domain.is_subset(chart._domain):
+ raise TypeError("the domain of {} is not ".format(self) +
+ "included in that of {}".format(chart))
+ coord_changes = chart._domain._coord_changes
+ for chart_pair in coord_changes:
+ if chart_pair == (self, chart):
+ transf = coord_changes[chart_pair]._transf
+ break
+ else:
+ # Search for a subchart
+ for chart_pair in coord_changes:
+ for schart in chart._subcharts:
+ if chart_pair == (self, schart):
+ transf = coord_changes[chart_pair]._transf
+ else:
+ if not isinstance(mapping, ContinuousMap):
+ raise TypeError("the argument 'mapping' must be a " +
+ "continuous manifold map")
+ if not self._domain.is_subset(mapping._domain):
+ raise TypeError("the domain of {} is not ".format(self) +
+ "included in that of {}".format(mapping))
+ if not chart._domain.is_subset(mapping._codomain):
+ raise TypeError("the domain of {} is not ".format(chart) +
+ "included in the codomain of {}".format(
+ mapping))
+ try:
+ transf = mapping.coord_functions(chart1=self, chart2=chart)
+ except ValueError:
+ pass
+ if transf is None:
+ raise ValueError("no relation has been found between " +
+ "{} and {}".format(self, chart))
+ #
+ # 2/ Treatment of input parameters
+ # -----------------------------
+ if fixed_coords is None:
+ coords = self._xx
+ else:
+ fixed_coord_list = fixed_coords.keys()
+ coords = []
+ for coord in self._xx:
+ if coord not in fixed_coord_list:
+ coords.append(coord)
+ coords = tuple(coords)
+ if ambient_coords is None:
+ ambient_coords = chart._xx
+ elif not isinstance(ambient_coords, tuple):
+ ambient_coords = tuple(ambient_coords)
+ nca = len(ambient_coords)
+ if nca != 2 and nca !=3:
+ raise TypeError("bad number of ambient coordinates: {}".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 = self._bounds[self._xx.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])
+ if not isinstance(color, dict):
+ color0 = {}
+ for coord in coords:
+ color0[coord] = color
+ color = color0
+ if not isinstance(style, dict):
+ style0 = {}
+ for coord in coords:
+ style0[coord] = style
+ style = style0
+ if not isinstance(thickness, dict):
+ thickness0 = {}
+ for coord in coords:
+ thickness0[coord] = thickness
+ thickness = thickness0
+ if not isinstance(plot_points, dict):
+ plot_points0 = {}
+ for coord in coords:
+ plot_points0[coord] = plot_points
+ plot_points = plot_points0
+ #
+ # 3/ Plots
+ # -----
+ xx0 = [0] * nc
+ if fixed_coords is not None:
+ if len(fixed_coords) != nc - len(coords):
+ raise ValueError("Bad number of fixed coordinates.")
+ for fc, val in fixed_coords.iteritems():
+ xx0[self._xx.index(fc)] = val
+ ind_a = [chart._xx.index(ac) for ac in ambient_coords]
+ resu = Graphics()
+ for coord in coords:
+ color_c, style_c = color[coord], style[coord]
+ thickness_c = thickness[coord]
+ rem_coords = list(coords)
+ rem_coords.remove(coord)
+ xx_list = [xx0]
+ if len(rem_coords) >= 1:
+ xx_list = _plot_xx_list(xx_list, rem_coords, ranges, steps,
+ nb_values)
+ xmin, xmax = ranges[coord]
+ nbp = plot_points[coord]
+ dx = (xmax - xmin) / (nbp-1)
+ ind_coord = self._xx.index(coord)
+ for xx in xx_list:
+ curve = []
+ first_invalid = False # initialization
+ xc = xmin
+ xp = list(xx)
+ if parameters is None:
+ for i in range(nbp):
+ xp[ind_coord] = xc
+ if self.valid_coordinates(*xp, tolerance=1e-13):
+ yp = transf(*xp, simplify=False)
+ curve.append( [numerical_approx(yp[j])
+ for j in ind_a] )
+ first_invalid = True # next invalid point will be
+ # the first one
+ else:
+ if first_invalid:
+ # the curve is stopped at previous point and
+ # added to the graph:
+ resu += line(curve, color=color_c,
+ linestyle=style_c,
+ thickness=thickness_c)
+ curve = [] # a new curve will start at the
+ # next valid point
+ first_invalid = False # next invalid point will not
+ # be the first one
+ xc += dx
+ else:
+ for i in range(nbp):
+ xp[ind_coord] = xc
+ if self.valid_coordinates(*xp, tolerance=1e-13,
+ parameters=parameters):
+ yp = transf(*xp, simplify=False)
+ curve.append(
+ [numerical_approx( yp[j].substitute(parameters) )
+ for j in ind_a] )
+ first_invalid = True # next invalid point will be
+ # the first one
+ else:
+ if first_invalid:
+ # the curve is stopped at previous point and
+ # added to the graph:
+ resu += line(curve, color=color_c,
+ linestyle=style_c,
+ thickness=thickness_c)
+ curve = [] # a new curve will start at the
+ # next valid point
+ first_invalid = False # next invalid point will not
+ # be the first one
+ xc += dx
+ if curve != []:
+ resu += line(curve, color=color_c,
+ linestyle=style_c,
+ thickness=thickness_c)
+ if nca==2: # 2D graphic
+ resu.set_aspect_ratio(1)
+ 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(ac)+r'$'
+ for ac in ambient_coords]
+ else: # 3D graphic
+ resu.aspect_ratio(1)
+ if label_axes:
+ labels = [str(ac) for ac in ambient_coords]
+ resu = set_axes_labels(resu, *labels)
+ return resu
#*****************************************************************************
diff --git a/src/sage/manifolds/point.py b/src/sage/manifolds/point.py
index 997250d..95c7c32 100644
--- a/src/sage/manifolds/point.py
+++ b/src/sage/manifolds/point.py
@@ -698,3 +698,186 @@ class TopManifoldPoint(Element):
"""
return self._manifold.__hash__()
+
+ def plot(self, chart=None, ambient_coords=None, mapping=None, size=10,
+ color='black', label=None, label_color=None, fontsize=10,
+ label_offset=0.1, parameters=None):
+ r"""
+ Plot the current point in a Cartesian graph based on the
+ coordinates of some ambient chart.
+
+ The point is drawn in terms of two (2D graphics) or three (3D graphics)
+ coordinates of a given chart, called hereafter the *ambient chart*.
+ The domain of the ambient chart must contain the point, or its image
+ by a continuous manifold map `\Phi`.
+
+ INPUT:
+
+ - ``chart`` -- (default: ``None``) the ambient chart (see above); if
+ ``None``, the ambient chart is set the default chart of
+ ``self.containing_set()``
+ - ``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``) continuous manifold map `\Phi`
+ (instance of :class:`~sage.manifolds.continuous_map.ContinuousMap`)
+ providing the link between the current point `p` and the ambient
+ chart ``chart``: the domain of ``chart`` must contain `\Phi(p)`;
+ if ``None``, the identity map is assumed
+ - ``size`` -- (default: 10) size of the point once drawn as a small
+ disk or sphere
+ - ``color`` -- (default: 'black') color of the point
+ - ``label`` -- (default: ``None``) label printed next to the point;
+ if ``None``, the point's name is used.
+ - ``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 point and its label
+ - ``parameters`` -- (default: ``None``) dictionary giving the numerical
+ values of the parameters that may appear in the point coordinates
+
+ OUTPUT:
+
+ - a graphic object, either an instance of
+ :class:`~sage.plot.graphics.Graphics` for a 2D plot (i.e. based on
+ 2 coordinates of the ambient chart) or an instance of
+ :class:`~sage.plot.plot3d.base.Graphics3d` for a 3D plot (i.e.
+ based on 3 coordinates of the ambient chart)
+
+ EXAMPLES:
+
+ Drawing a point on a 2-dimensional manifold::
+
+ sage: M = TopManifold(2, 'M')
+ sage: X.<x,y> = M.chart()
+ sage: p = M.point((1,3), name='p')
+ sage: g = p.plot(X)
+ sage: print g
+ Graphics object consisting of 2 graphics primitives
+ sage: gX = X.plot() # plot of the coordinate grid
+ sage: show(g+gX) # display of the point atop the coordinate grid
+
+ Actually, since ``X`` is the default chart of the open set in which
+ ``p`` has been defined, it can be skipped in the arguments of
+ ``plot``::
+
+ sage: g = p.plot()
+ sage: show(g+gX)
+
+ Call with some options::
+
+ sage: g = p.plot(chart=X, size=40, color='green', label='$P$',
+ ....: label_color='blue', fontsize=20, label_offset=0.4)
+ sage: show(g+gX)
+
+ Use of the ``parameters`` option to set a numerical value of some
+ symbolic variable::
+
+ sage: a = var('a')
+ sage: q = M.point((a,2*a), name='q')
+ sage: gq = q.plot(parameters={a:-2})
+ sage: show(g+gX+gq)
+
+ The numerical value is used only for the plot::
+
+ sage: q.coord()
+ (a, 2*a)
+
+ Drawing a point on a 3-dimensional manifold::
+
+ sage: M = TopManifold(3, 'M')
+ sage: X.<x,y,z> = M.chart()
+ sage: p = M.point((2,1,3), name='p')
+ sage: g = p.plot()
+ sage: print g
+ Graphics3d Object
+ sage: gX = X.plot(nb_values=5) # coordinate mesh cube
+ sage: show(g+gX) # display of the point atop the coordinate mesh
+
+ Call with some options::
+
+ sage: g = p.plot(chart=X, size=40, color='green', label='P_1',
+ ....: label_color='blue', fontsize=20, label_offset=0.3)
+ sage: show(g+gX)
+
+ An example of plot via a mapping: plot of a point on a 2-sphere viewed
+ in the 3-dimensional space ``M``::
+
+ sage: S2 = TopManifold(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: p = U.point((pi/4, pi/8), name='p')
+ sage: F = S2.continuous_map(M, {(XS, X): [sin(th)*cos(ph),
+ ....: sin(th)*sin(ph), cos(th)]}, name='F')
+ sage: F.display()
+ F: S^2 --> M
+ on U: (th, ph) |--> (x, y, z) = (cos(ph)*sin(th), sin(ph)*sin(th), cos(th))
+ sage: g = p.plot(chart=X, mapping=F)
+ sage: gS2 = XS.plot(chart=X, mapping=F, nb_values=9)
+ sage: show(g+gS2)
+
+ Use of the option ``ambient_coords`` for plots on a 4-dimensional
+ manifold::
+
+ sage: M = TopManifold(4, 'M')
+ sage: X.<t,x,y,z> = M.chart()
+ sage: p = M.point((1,2,3,4), name='p')
+ sage: g = p.plot(X, ambient_coords=(t,x,y)) # the coordinate z is skipped
+ sage: gX = X.plot(X, ambient_coords=(t,x,y), nb_values=5)
+ sage: show(g+gX) # 3D plot
+ sage: g = p.plot(X, ambient_coords=(t,y,z)) # the coordinate x is skipped
+ sage: gX = X.plot(X, ambient_coords=(t,y,z), nb_values=5)
+ sage: show(g+gX) # 3D plot
+ sage: g = p.plot(X, ambient_coords=(y,z)) # the coordinates t and x are skipped
+ sage: gX = X.plot(X, ambient_coords=(y,z))
+ sage: show(g+gX) # 2D plot
+
+ """
+ from sage.plot.point import point2d
+ from sage.plot.text import text
+ from sage.plot.graphics import Graphics
+ from sage.plot.plot3d.shapes2 import point3d, text3d
+ from sage.manifolds.chart import Chart
+ # The ambient chart:
+ if chart is None:
+ chart = self.containing_set().default_chart()
+ elif not isinstance(chart, Chart):
+ raise TypeError("the argument 'chart' must be a coordinate chart")
+ # The effective point to be plotted:
+ if mapping is None:
+ eff_point = self
+ else:
+ eff_point = mapping(self)
+ # The coordinates of the ambient chart used for the plot:
+ 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 TypeError("bad number of ambient coordinates: {}".format(nca))
+ # The point coordinates:
+ coords = eff_point.coord(chart)
+ xx = chart[:]
+ xp = [coords[xx.index(c)] for c in ambient_coords]
+ if parameters is not None:
+ xps = [coord.substitute(parameters) for coord in xp]
+ xp = xps
+ xlab = [coord + label_offset for coord in xp]
+ if label_color is None:
+ label_color = color
+ resu = Graphics()
+ if nca == 2:
+ if label is None:
+ label = r'$' + self._latex_name + r'$'
+ resu += point2d(xp, color=color, size=size) + \
+ text(label, xlab, fontsize=fontsize, color=label_color)
+ else:
+ if label is None:
+ label = self._name
+ resu += point3d(xp, color=color, size=size) + \
+ text3d(label, xlab, fontsize=fontsize, color=label_color)
+ return resu
diff --git a/src/sage/manifolds/utilities.py b/src/sage/manifolds/utilities.py
index 12fa42c..732a4a7 100644
--- a/src/sage/manifolds/utilities.py
+++ b/src/sage/manifolds/utilities.py
@@ -901,3 +901,44 @@ def omit_function_args(status):
raise TypeError("the argument must be a boolean")
CoordFunctionSymb._omit_fargs = status
+#******************************************************************************
+
+def set_axes_labels(graph, xlabel, ylabel, zlabel, **kwds):
+ r"""
+ Set axes labels for a 3D graphics object.
+
+ This is a workaround for the lack of axes labels in Sage 3D plots; it
+ sets the labels as text3d objects at locations determined from the
+ bounding box of the graphic object ``graph``.
+
+ INPUT:
+
+ - ``graph`` -- a 3D graphic object, as an instance of
+ :class:`~sage.plot.plot3d.base.Graphics3d`
+ - ``xlabel`` -- string for the x-axis label
+ - ``ylabel`` -- string for the y-axis label
+ - ``zlabel`` -- string for the z-axis label
+ - ``**kwds`` -- options (e.g. color) for text3d
+
+ OUTPUT:
+
+ - the 3D graphic object with text3d labels added.
+
+ """
+ from sage.plot.plot3d.shapes2 import text3d
+ xmin, ymin, zmin = graph.bounding_box()[0]
+ xmax, ymax, zmax = graph.bounding_box()[1]
+ dx = xmax - xmin
+ dy = ymax - ymin
+ dz = zmax - zmin
+ x1 = xmin + dx / 2
+ y1 = ymin + dy / 2
+ z1 = zmin + dz / 2
+ xmin1 = xmin - dx / 20
+ xmax1 = xmax + dx / 20
+ ymin1 = ymin - dy / 20
+ zmin1 = zmin - dz / 20
+ graph += text3d(' ' + xlabel, (x1, ymin1, zmin1), **kwds)
+ graph += text3d(' ' + ylabel, (xmax1, y1, zmin1), **kwds)
+ graph += text3d(' ' + zlabel, (xmin1, ymin1, z1), **kwds)
+ return graph