summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEric Gourgoulhon <eric.gourgoulhon@obspm.fr>2016-01-03 17:10:08 +0100
committerEric Gourgoulhon <eric.gourgoulhon@obspm.fr>2016-01-03 17:10:08 +0100
commitf6c6a3495170bbeded949d02c0ba076b03f250eb (patch)
tree7ed9a47ee929b48158069edb1c1211f0b803260e
parentScalarFieldAlgebra does not longer inherit from AbstractNamedObject (diff)
parentMorphisms on the refactored topoological manifolds (diff)
Topological manifold morphisms with AbstractNamedObject without full_name
-rw-r--r--src/doc/en/reference/manifolds/continuous_map.rst9
-rw-r--r--src/doc/en/reference/manifolds/manifold.rst2
-rw-r--r--src/sage/manifolds/chart.py555
-rw-r--r--src/sage/manifolds/continuous_map.py1830
-rw-r--r--src/sage/manifolds/manifold.py297
-rw-r--r--src/sage/manifolds/manifold_homset.py422
-rw-r--r--src/sage/manifolds/point.py228
-rw-r--r--src/sage/manifolds/structure.py3
-rw-r--r--src/sage/manifolds/utilities.py54
9 files changed, 3399 insertions, 1 deletions
diff --git a/src/doc/en/reference/manifolds/continuous_map.rst b/src/doc/en/reference/manifolds/continuous_map.rst
new file mode 100644
index 0000000..4b67487
--- /dev/null
+++ b/src/doc/en/reference/manifolds/continuous_map.rst
@@ -0,0 +1,9 @@
+Continuous maps
+===============
+
+.. toctree::
+ :maxdepth: 2
+
+ sage/manifolds/manifold_homset
+
+ sage/manifolds/continuous_map
diff --git a/src/doc/en/reference/manifolds/manifold.rst b/src/doc/en/reference/manifolds/manifold.rst
index 0c4be1f..e9cb7e7 100644
--- a/src/doc/en/reference/manifolds/manifold.rst
+++ b/src/doc/en/reference/manifolds/manifold.rst
@@ -17,3 +17,5 @@ Topological manifolds
chart
scalarfield
+
+ continuous_map
diff --git a/src/sage/manifolds/chart.py b/src/sage/manifolds/chart.py
index 05ea5c3..2a6c57f 100644
--- a/src/sage/manifolds/chart.py
+++ b/src/sage/manifolds/chart.py
@@ -4,6 +4,8 @@ Coordinate charts
The class :class:`Chart` implements coordinate charts on a topological manifold
over a topological field `K`. The subclass :class:`RealChart` is devoted
to the case `K=\RR`, for which the concept of coordinate range is meaningful.
+Moreover, :class:`RealChart` is endowed with some plotting
+capabilities (cf. method :meth:`~sage.manifolds.chart.RealChart.plot`).
Transition maps between charts are implemented via the class
:class:`CoordChange`.
@@ -1277,6 +1279,9 @@ class RealChart(Chart):
sage: c_cart.valid_coordinates(1,0,2)
True
+ Chart grids can be drawn in 2D or 3D graphics thanks to the method
+ :meth:`plot`.
+
"""
def __init__(self, domain, coordinates='', names=None):
r"""
@@ -1819,6 +1824,556 @@ 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_range=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:`~sage.manifolds.chart.Chart.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_range`` and +Infinity by ``max_range``)
+ - ``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
+ - ``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 = Manifold(2, 'R^2', structure='topological') # 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 = Manifold(2, 'R^2', structure='topological')
+ 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 = Manifold(2, 'R^2', structure='topological')
+ 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 = Manifold(2, 'R^2', structure='topological')
+ 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 = Manifold(2, 'R^2', structure='topological')
+ 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 plotted 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 = Manifold(2, 'R^2', structure='topological')
+ 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 = Manifold(2, 'S^2', structure='topological') # 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 = Manifold(3, 'R^3', structure='topological') # 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]})
+ sage: gSN2 = c_uvW.plot(c_xy, ranges={u:[-6.,-0.02], v:[0.02,6.]})
+ sage: gSN3 = c_uvW.plot(c_xy, ranges={u:[0.02,6.], v:[-6.,-0.02]})
+ sage: gSN4 = c_uvW.plot(c_xy, ranges={u:[0.02,6.], v:[0.02,6.]})
+ sage: show(gSN1+gSN2+gSN3+gSN4, xmin=-1.5, xmax=1.5, ymin=-1.5, ymax=1.5)
+
+ .. PLOT::
+
+ S2 = Manifold(2, 'S^2', structure='topological')
+ 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]})
+ gSN2 = c_uvW.plot(c_xy, ranges={u:[-6.,-0.02], v:[0.02,6.]})
+ gSN3 = c_uvW.plot(c_xy, ranges={u:[0.02,6.], v:[-6.,-0.02]})
+ gSN4 = c_uvW.plot(c_xy, ranges={u:[0.02,6.], v:[0.02,6.]})
+ g = gSN1+gSN2+gSN3+gSN4; g.set_axes_range(-1.5, 1.5, -1.5, 1.5)
+ 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_range=20, plot_points=300)
+ sage: gv1 = c_uvW.plot(c_xy, fixed_coords={v: 1}, max_range=20, plot_points=300, color='green')
+ sage: show(gu1+gv1)
+
+ .. PLOT::
+
+ S2 = Manifold(2, 'S^2', structure='topological')
+ 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_range=20, plot_points=300)
+ gv1 = c_uvW.plot(c_xy, fixed_coords={v: 1}, max_range=20, plot_points=300, color='green')
+ sphinx_plot(gu1+gv1)
+
+ Note that we have set ``max_range=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 = Manifold(4, 'M', structure='topological')
+ 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 = Manifold(4, 'M', structure='topological')
+ 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 MultiCoordFunction object relating self
+ # to the ambient chart
+ if mapping is None:
+ if not self._domain.is_subset(chart._domain):
+ raise ValueError("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 ValueError("the domain of {} is not ".format(self) +
+ "included in that of {}".format(mapping))
+ if not chart._domain.is_subset(mapping._codomain):
+ raise ValueError("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 ValueError("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_range)
+ 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_range)
+ 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
+
#*****************************************************************************
class CoordChange(SageObject):
diff --git a/src/sage/manifolds/continuous_map.py b/src/sage/manifolds/continuous_map.py
new file mode 100644
index 0000000..7277406
--- /dev/null
+++ b/src/sage/manifolds/continuous_map.py
@@ -0,0 +1,1830 @@
+r"""
+Continuous maps between topological manifolds
+
+The class :class:`ContinuousMap` implements continuous maps from a topological
+manifold `M` to some topological manifold `N` over the same topological field
+`K` as `M`.
+
+AUTHORS:
+
+- Eric Gourgoulhon, Michal Bejger (2013-2015): initial version
+
+REFERENCES:
+
+- Chap. 1 of S. Kobayashi & K. Nomizu : *Foundations of Differential Geometry*,
+ vol. 1, Interscience Publishers (New York) (1963)
+- J.M. Lee : *Introduction to Topological Manifolds*, 2nd ed., Springer (New
+ York) (2011)
+
+"""
+
+#*****************************************************************************
+# Copyright (C) 2015 Eric Gourgoulhon <eric.gourgoulhon@obspm.fr>
+# Copyright (C) 2015 Michal Bejger <bejger@camk.edu.pl>
+#
+# Distributed under the terms of the GNU General Public License (GPL)
+# as published by the Free Software Foundation; either version 2 of
+# the License, or (at your option) any later version.
+# http://www.gnu.org/licenses/
+#*****************************************************************************
+
+from sage.categories.homset import Hom
+from sage.categories.morphism import Morphism
+
+class ContinuousMap(Morphism):
+ r"""
+ Continuous map between two topological manifolds.
+
+ This class implements continuous maps of the type
+
+ .. MATH::
+
+ \Phi: M \longrightarrow N
+
+ where `M` and `N` are topological manifolds over the same topological
+ field `K`.
+
+ Continuous maps are the *morphisms* of the *category* of topological
+ manifolds. The set of all continuous maps from `M` to `N` is therefore the
+ homset between `M` and `N`, which is denoted by `\mathrm{Hom}(M,N)`.
+
+ The class :class:`ContinuousMap` is a Sage *element* class, whose *parent*
+ class is :class:`~sage.manifolds.manifold_homset.TopologicalManifoldHomset`.
+
+ INPUT:
+
+ - ``parent`` -- homset `\mathrm{Hom}(M,N)` to which the continuous
+ map belongs
+ - ``coord_functions`` -- (default: ``None``) if not ``None``, must be
+ a dictionary of the coordinate expressions (as lists (or tuples) of the
+ coordinates of the image expressed in terms of the coordinates of
+ the considered point) with the pairs of charts (chart1, chart2)
+ as keys (chart1 being a chart on `M` and chart2 a chart on `N`).
+ If the dimension of the map's codomain is 1, a single coordinate
+ expression can be passed instead of a tuple with a single element
+ - ``name`` -- (default: ``None``) name given to the continuous map
+ - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the
+ continuous map; if ``None``, the LaTeX symbol is set to
+ ``name``
+ - ``is_isomorphism`` -- (default: ``False``) determines whether the
+ constructed object is a isomorphism (i.e. a homeomorphism); if set to
+ ``True``, then the manifolds `M` and `N` must have the same dimension.
+ - ``is_identity`` -- (default: ``False``) determines whether the
+ constructed object is the identity map; if set to ``True``,
+ then `N` must be `M` and the entry ``coord_functions`` is not used.
+
+ .. NOTE::
+
+ If the information passed by means of the argument ``coord_functions``
+ is not sufficient to fully specify the continuous map,
+ further coordinate expressions, in other charts, can be subsequently
+ added by means of the method :meth:`add_expr`
+
+ EXAMPLES:
+
+ The standard embedding of the sphere `S^2` into `\RR^3`::
+
+ sage: M = Manifold(2, 'S^2', structure='topological') # the 2-dimensional sphere S^2
+ sage: U = M.open_subset('U') # complement of the North pole
+ sage: c_xy.<x,y> = U.chart() # stereographic coordinates from the North pole
+ sage: V = M.open_subset('V') # complement of the South pole
+ sage: c_uv.<u,v> = V.chart() # stereographic coordinates from the South pole
+ sage: M.declare_union(U,V) # S^2 is the union of U and V
+ 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: N = Manifold(3, 'R^3', latex_name=r'\RR^3', structure='topological') # R^3
+ sage: c_cart.<X,Y,Z> = N.chart() # Cartesian coordinates on R^3
+ sage: Phi = M.continuous_map(N,
+ ....: {(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')
+ sage: Phi
+ Continuous map Phi from the 2-dimensional topological manifold S^2 to
+ the 3-dimensional topological manifold R^3
+ sage: Phi.parent()
+ Set of Morphisms from 2-dimensional topological manifold S^2 to
+ 3-dimensional topological manifold R^3 in Category of manifolds over
+ Real Field with 53 bits of precision
+ sage: Phi.parent() is Hom(M, N)
+ True
+ sage: type(Phi)
+ <class 'sage.manifolds.continuous_map.TopologicalManifoldHomset_with_category.element_class'>
+ sage: Phi.display()
+ Phi: S^2 --> R^3
+ on U: (x, y) |--> (X, Y, Z) = (2*x/(x^2 + y^2 + 1), 2*y/(x^2 + y^2 + 1), (x^2 + y^2 - 1)/(x^2 + y^2 + 1))
+ on V: (u, v) |--> (X, Y, Z) = (2*u/(u^2 + v^2 + 1), 2*v/(u^2 + v^2 + 1), -(u^2 + v^2 - 1)/(u^2 + v^2 + 1))
+
+ It is possible to create the map via the method
+ :meth:`~sage.manifolds.manifold.TopologicalManifold.continuous_map`
+ only in a single pair of charts: the argument ``coord_functions`` is then
+ a mere list of coordinate expressions (and not a dictionary) and the
+ arguments ``chart1`` and ``chart2`` have to be provided if the charts
+ differ from the default ones on the domain and/or the codomain::
+
+ sage: Phi1 = M.continuous_map(N, [2*x/(1+x^2+y^2), 2*y/(1+x^2+y^2), (x^2+y^2-1)/(1+x^2+y^2)], \
+ ....: chart1=c_xy, chart2=c_cart, name='Phi', latex_name=r'\Phi')
+
+ Since c_xy and c_cart are the default charts on respectively ``M`` and
+ ``N``, they can be omitted, so that the above declaration is equivalent
+ to::
+
+ sage: Phi1 = M.continuous_map(N, [2*x/(1+x^2+y^2), 2*y/(1+x^2+y^2), (x^2+y^2-1)/(1+x^2+y^2)], \
+ ....: name='Phi', latex_name=r'\Phi')
+
+ With such a declaration, the continuous map is only partially defined
+ on the manifold `S^2`, being known in only one chart::
+
+ sage: Phi1.display()
+ Phi: S^2 --> R^3
+ on U: (x, y) |--> (X, Y, Z) = (2*x/(x^2 + y^2 + 1), 2*y/(x^2 + y^2 + 1), (x^2 + y^2 - 1)/(x^2 + y^2 + 1))
+
+ The definition can be completed by means of the method :meth:`add_expr`::
+
+ sage: Phi1.add_expr(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)])
+ sage: Phi1.display()
+ Phi: S^2 --> R^3
+ on U: (x, y) |--> (X, Y, Z) = (2*x/(x^2 + y^2 + 1), 2*y/(x^2 + y^2 + 1), (x^2 + y^2 - 1)/(x^2 + y^2 + 1))
+ on V: (u, v) |--> (X, Y, Z) = (2*u/(u^2 + v^2 + 1), 2*v/(u^2 + v^2 + 1), -(u^2 + v^2 - 1)/(u^2 + v^2 + 1))
+
+ At this stage, ``Phi1`` and ``Phi`` are fully equivalent::
+
+ sage: Phi1 == Phi
+ True
+
+ The test suite is passed::
+
+ sage: TestSuite(Phi).run()
+ sage: TestSuite(Phi1).run()
+
+ The map acts on points::
+
+ sage: np = M.point((0,0), chart=c_uv) # the North pole
+ sage: Phi(np)
+ Point on the 3-dimensional topological manifold R^3
+ sage: Phi(np).coord() # Cartesian coordinates
+ (0, 0, 1)
+ sage: sp = M.point((0,0), chart=c_xy) # the South pole
+ sage: Phi(sp).coord() # Cartesian coordinates
+ (0, 0, -1)
+
+ Continuous maps can be composed by means of the operator ``*``: let
+ us introduce the map `\RR^3\rightarrow \RR^2` corresponding to
+ the projection from the point `(X,Y,Z)=(0,0,1)` onto the equatorial plane
+ `Z=0`::
+
+ sage: P = Manifold(2, 'R^2', latex_name=r'\RR^2', structure='topological') # R^2 (equatorial plane)
+ sage: cP.<xP, yP> = P.chart()
+ sage: Psi = N.continuous_map(P, (X/(1-Z), Y/(1-Z)), name='Psi',
+ ....: latex_name=r'\Psi')
+ sage: Psi
+ Continuous map Psi from the 3-dimensional topological manifold R^3 to
+ the 2-dimensional topological manifold R^2
+ sage: Psi.display()
+ Psi: R^3 --> R^2
+ (X, Y, Z) |--> (xP, yP) = (-X/(Z - 1), -Y/(Z - 1))
+
+ Then we compose ``Psi`` with ``Phi``, thereby getting a map
+ `S^2\rightarrow \RR^2`::
+
+ sage: ster = Psi*Phi ; ster
+ Continuous map from the 2-dimensional topological manifold S^2 to the
+ 2-dimensional topological manifold R^2
+
+ Let us test on the South pole (``sp``) that ``ster`` is indeed the
+ composite of ``Psi`` and ``Phi``::
+
+ sage: ster(sp) == Psi(Phi(sp))
+ True
+
+ Actually ``ster`` is the stereographic projection from the North pole, as
+ its coordinate expression reveals::
+
+ sage: ster.display()
+ S^2 --> R^2
+ on U: (x, y) |--> (xP, yP) = (x, y)
+ on V: (u, v) |--> (xP, yP) = (u/(u^2 + v^2), v/(u^2 + v^2))
+
+ If its codomain is 1-dimensional, a continuous map must be
+ defined by a single symbolic expression for each pair of charts, and not
+ by a list/tuple with a single element::
+
+ sage: N = Manifold(1, 'N', structure='topological')
+ sage: c_N = N.chart('X')
+ sage: Phi = M.continuous_map(N, {(c_xy, c_N): x^2+y^2, \
+ ....: (c_uv, c_N): 1/(u^2+v^2)}) # not ...[1/(u^2+v^2)] or (1/(u^2+v^2),)
+
+ An example of continuous map `\RR \rightarrow \RR^2`::
+
+ sage: R = Manifold(1, 'R', structure='topological') # field R
+ sage: T.<t> = R.chart() # canonical chart on R
+ sage: R2 = Manifold(2, 'R^2', structure='topological') # R^2
+ sage: c_xy.<x,y> = R2.chart() # Cartesian coordinates on R^2
+ sage: Phi = R.continuous_map(R2, [cos(t), sin(t)], name='Phi') ; Phi
+ Continuous map Phi from the 1-dimensional topological manifold R to
+ the 2-dimensional topological manifold R^2
+ sage: Phi.parent()
+ Set of Morphisms from 1-dimensional topological manifold R to
+ 2-dimensional topological manifold R^2 in Category of manifolds over
+ Real Field with 53 bits of precision
+ sage: Phi.parent() is Hom(R, R2)
+ True
+ sage: Phi.display()
+ Phi: R --> R^2
+ t |--> (x, y) = (cos(t), sin(t))
+
+ An example of homeomorphism between the unit open disk and the Euclidean
+ plane `\RR^2`::
+
+ sage: D = R2.open_subset('D', coord_def={c_xy: x^2+y^2<1}) # the open unit disk
+ sage: Phi = D.homeomorphism(R2, [x/sqrt(1-x^2-y^2), y/sqrt(1-x^2-y^2)],
+ ....: name='Phi', latex_name=r'\Phi')
+ sage: Phi
+ Homeomorphism Phi from the Open subset D of the 2-dimensional
+ topological manifold R^2 to the 2-dimensional topological manifold R^2
+ sage: Phi.parent()
+ Set of Morphisms from Open subset D of the 2-dimensional topological
+ manifold R^2 to 2-dimensional topological manifold R^2 in Join of
+ Category of subobjects of sets and Category of manifolds over Real
+ Field with 53 bits of precision
+ sage: Phi.parent() is Hom(D, R2)
+ True
+ sage: Phi.display()
+ Phi: D --> R^2
+ (x, y) |--> (x, y) = (x/sqrt(-x^2 - y^2 + 1), y/sqrt(-x^2 - y^2 + 1))
+
+ The image of a point::
+
+ sage: p = D.point((1/2,0))
+ sage: q = Phi(p) ; q
+ Point on the 2-dimensional topological manifold R^2
+ sage: q.coord()
+ (1/3*sqrt(3), 0)
+
+ The inverse homeomorphism is computed by means of the method
+ :meth:`inverse`::
+
+ sage: Phi.inverse()
+ Homeomorphism Phi^(-1) from the 2-dimensional topological manifold R^2
+ to the Open subset D of the 2-dimensional topological manifold R^2
+ sage: Phi.inverse().display()
+ Phi^(-1): R^2 --> D
+ (x, y) |--> (x, y) = (x/sqrt(x^2 + y^2 + 1), y/sqrt(x^2 + y^2 + 1))
+
+ Equivalently, one may use the notations ``^(-1)`` or ``~`` to get the
+ inverse::
+
+ sage: Phi^(-1) is Phi.inverse()
+ True
+ sage: ~Phi is Phi.inverse()
+ True
+
+ Check that ``~Phi`` is indeed the inverse of ``Phi``::
+
+ sage: (~Phi)(q) == p
+ True
+ sage: Phi * ~Phi == R2.identity_map()
+ True
+ sage: ~Phi * Phi == D.identity_map()
+ True
+
+ The coordinate expression of the inverse homeomorphism::
+
+ sage: (~Phi).display()
+ Phi^(-1): R^2 --> D
+ (x, y) |--> (x, y) = (x/sqrt(x^2 + y^2 + 1), y/sqrt(x^2 + y^2 + 1))
+
+ A special case of homeomorphism: the identity map of the open unit disk::
+
+ sage: id = D.identity_map() ; id
+ Identity map Id_D of the Open subset D of the 2-dimensional topological
+ manifold R^2
+ sage: latex(id)
+ \mathrm{Id}_{D}
+ sage: id.parent()
+ Set of Morphisms from Open subset D of the 2-dimensional topological
+ manifold R^2 to Open subset D of the 2-dimensional topological
+ manifold R^2 in Join of Category of subobjects of sets and Category of
+ manifolds over Real Field with 53 bits of precision
+ sage: id.parent() is Hom(D, D)
+ True
+ sage: id is Hom(D,D).one() # the identity element of the monoid Hom(D,D)
+ True
+
+ The identity map acting on a point::
+
+ sage: id(p)
+ Point on the 2-dimensional topological manifold R^2
+ sage: id(p) == p
+ True
+ sage: id(p) is p
+ True
+
+ The coordinate expression of the identity map::
+
+ sage: id.display()
+ Id_D: D --> D
+ (x, y) |--> (x, y)
+
+ The identity map is its own inverse::
+
+ sage: id^(-1) is id
+ True
+ sage: ~id is id
+ True
+
+ """
+ def __init__(self, parent, coord_functions=None, name=None, latex_name=None,
+ is_isomorphism=False, is_identity=False):
+ r"""
+ Construct a continuous map.
+
+ TESTS::
+
+ sage: M = Manifold(2, 'M', structure='topological')
+ sage: X.<x,y> = M.chart()
+ sage: N = Manifold(3, 'N', structure='topological')
+ sage: Y.<u,v,w> = N.chart()
+ sage: f = Hom(M,N)({(X,Y): (x+y, x*y, x-y)}, name='f') ; f
+ Continuous map f from the 2-dimensional topological manifold M to
+ the 3-dimensional topological manifold N
+ sage: f.display()
+ f: M --> N
+ (x, y) |--> (u, v, w) = (x + y, x*y, x - y)
+ sage: TestSuite(f).run()
+
+ The identity map::
+
+ sage: f = Hom(M,M)({}, is_identity=True) ; f
+ Identity map Id_M of the 2-dimensional topological manifold M
+ sage: f.display()
+ Id_M: M --> M
+ (x, y) |--> (x, y)
+ sage: TestSuite(f).run()
+
+ """
+ Morphism.__init__(self, parent)
+ domain = parent.domain()
+ codomain = parent.codomain()
+ self._domain = domain
+ self._codomain = codomain
+ self._coord_expression = {} # dict. of coordinate expressions of the
+ # map:
+ # - key: pair of charts
+ # - value: instance of MultiCoordFunction
+ self._is_isomorphism = False # default value; may be redefined below
+ self._is_identity = False # default value; may be redefined below
+ if is_identity:
+ # Construction of the identity map
+ self._is_identity = True
+ self._is_isomorphism = True
+ if domain != codomain:
+ raise ValueError("the domain and codomain must coincide " + \
+ "for the identity map")
+ if name is None:
+ name = 'Id_' + domain._name
+ if latex_name is None:
+ latex_name = r'\mathrm{Id}_{' + domain._latex_name + r'}'
+ self._name = name
+ self._latex_name = latex_name
+ for chart in domain.atlas():
+ coord_funct = chart[:]
+ self._coord_expression[(chart, chart)] = chart.multifunction(
+ *coord_funct)
+ else:
+ # Construction of a generic continuous map
+ if is_isomorphism:
+ self._is_isomorphism = True
+ if domain.dim() != codomain.dim():
+ raise ValueError("for an isomorphism, the source " +
+ "manifold and target manifold must " +
+ "have the same dimension")
+ if coord_functions is not None:
+ n2 = self._codomain.dim()
+ for chart_pair, expression in coord_functions.iteritems():
+ if chart_pair[0] not in self._domain.atlas():
+ raise ValueError("{} is not a chart ".format(
+ chart_pair[0]) +
+ "defined on the {}".format(self._domain))
+ if chart_pair[1] not in self._codomain.atlas():
+ raise ValueError("{} is not a chart ".format(
+ chart_pair[1]) +
+ "defined on the {}".format(self._codomain))
+ if n2 == 1:
+ # a single expression entry is allowed (instead of a
+ # tuple)
+ if not isinstance(expression, (tuple, list)):
+ expression = (expression,)
+ if len(expression) != n2:
+ raise ValueError("{} coordinate ".format(n2) +
+ "functions must be provided")
+ self._coord_expression[chart_pair] = \
+ chart_pair[0].multifunction(*expression)
+ self._name = name
+ if latex_name is None:
+ self._latex_name = self._name
+ else:
+ self._latex_name = latex_name
+ self._init_derived() # initialization of derived quantities
+
+ #
+ # SageObject methods
+ #
+
+ def _repr_(self):
+ r"""
+ String representation of the object.
+
+ TESTS::
+
+ sage: M = Manifold(2, 'M', structure='topological')
+ sage: X.<x,y> = M.chart()
+ sage: N = Manifold(2, 'N', structure='topological')
+ sage: Y.<u,v> = N.chart()
+ sage: f = Hom(M,N)({(X,Y): (x+y,x*y)})
+ sage: f._repr_()
+ 'Continuous map from the 2-dimensional topological manifold M to
+ the 2-dimensional topological manifold N'
+ sage: f = Hom(M,N)({(X,Y): (x+y,x*y)}, name='f')
+ sage: f._repr_()
+ 'Continuous map f from the 2-dimensional topological manifold M to
+ the 2-dimensional topological manifold N'
+ sage: f = Hom(M,N)({(X,Y): (x+y,x-y)}, name='f', is_isomorphism=True)
+ sage: f._repr_()
+ 'Homeomorphism f from the 2-dimensional topological manifold M to
+ the 2-dimensional topological manifold N'
+ sage: f = Hom(M,M)({(X,X): (x+y,x-y)}, name='f', is_isomorphism=True)
+ sage: f._repr_()
+ 'Homeomorphism f of the 2-dimensional topological manifold M'
+ sage: f = Hom(M,M)({}, name='f', is_identity=True)
+ sage: f._repr_()
+ 'Identity map f of the 2-dimensional topological manifold M'
+
+ """
+ if self._is_identity:
+ return "Identity map " + self._name + \
+ " of the {}".format(self._domain)
+ if self._is_isomorphism:
+ description = "Homeomorphism"
+ else:
+ description = "Continuous map"
+ if self._name is not None:
+ description += " " + self._name
+ if self._domain == self._codomain:
+ if self._is_isomorphism:
+ description += " of the {}".format(self._domain)
+ else:
+ description += " from the {} to itself".format(self._domain)
+ else:
+ description += " from the {} to the {}".format(self._domain,
+ self._codomain)
+ return description
+
+ def _latex_(self):
+ r"""
+ LaTeX representation of the object.
+
+ TESTS::
+
+ sage: M = Manifold(2, 'M', structure='topological')
+ sage: X.<x,y> = M.chart()
+ sage: f = Hom(M,M)({(X,X): (x+y,x*y)}, name='f')
+ sage: f._latex_()
+ 'f'
+ sage: f = Hom(M,M)({(X,X): (x+y,x*y)}, name='f', latex_name=r'\Phi')
+ sage: f._latex_()
+ '\\Phi'
+
+ """
+ if self._latex_name is None:
+ return r'\mbox{' + str(self) + r'}'
+ else:
+ return self._latex_name
+
+ def __eq__(self, other):
+ r"""
+ Comparison (equality) operator.
+
+ INPUT:
+
+ - ``other`` -- another instance of :class:`ContinuousMap` to compare
+ with
+
+ OUTPUT:
+
+ - True if ``self`` is equal to ``other``, or False otherwise
+
+ TESTS::
+
+ sage: M = Manifold(3, 'M', structure='topological')
+ sage: X.<x,y,z> = M.chart()
+ sage: N = Manifold(2, 'N', structure='topological')
+ sage: Y.<u,v> = N.chart()
+ sage: f = M.continuous_map(N, {(X,Y): [x+y+z, 2*x*y*z]}, name='f')
+ sage: g = M.continuous_map(N, {(X,Y): [x+y+z, 2*x*y*z]}, name='g')
+ sage: f == g
+ True
+ sage: g = M.continuous_map(N, {(X,Y): [x+y+z, 1]}, name='g')
+ sage: f == g
+ False
+
+ """
+ if other is self:
+ return True
+ if not isinstance(other, type(self)):
+ return False
+ if self.parent() != other.parent():
+ return False
+ if self._is_identity:
+ return other.is_identity()
+ if other._is_identity:
+ return self.is_identity()
+ for charts, coord_functions in self._coord_expression.iteritems():
+ try:
+ if coord_functions.expr() != other.expr(*charts):
+ return False
+ except ValueError:
+ return False
+ return True
+
+ def __ne__(self, other):
+ r"""
+ Inequality operator.
+
+ INPUT:
+
+ - ``other`` -- another instance of :class:`ContinuousMap` to compare
+ with
+
+ OUTPUT:
+
+ - True if ``self`` is different from ``other``, or False otherwise
+
+ TESTS::
+
+ sage: M = Manifold(3, 'M', structure='topological')
+ sage: X.<x,y,z> = M.chart()
+ sage: N = Manifold(2, 'N', structure='topological')
+ sage: Y.<u,v> = N.chart()
+ sage: f = M.continuous_map(N, {(X,Y): [x+y+z, 2*x*y*z]}, name='f')
+ sage: g = M.continuous_map(N, {(X,Y): [x+y+z, 2*x*y*z]}, name='g')
+ sage: f != g
+ False
+ sage: g = M.continuous_map(N, {(X,Y): [x+y+z, 1]}, name='g')
+ sage: f != g
+ True
+
+ """
+ return not (self == other)
+
+ def __cmp__(self, other):
+ r"""
+ Old-style (Python 2) comparison operator.
+
+ This is provisory, until migration to Python 3 is achieved.
+
+ TESTS::
+
+ sage: M = Manifold(3, 'M', structure='topological')
+ sage: X.<x,y,z> = M.chart()
+ sage: N = Manifold(2, 'N', structure='topological')
+ sage: Y.<u,v> = N.chart()
+ sage: f = M.continuous_map(N, {(X,Y): [x+y+z, 2*x*y*z]}, name='f')
+ sage: g = M.continuous_map(N, {(X,Y): [x+y+z, 2*x*y*z]}, name='g')
+ sage: f.__cmp__(g)
+ 0
+ sage: g = M.continuous_map(N, {(X,Y): [x+y+z, 1]}, name='g')
+ sage: f.__cmp__(g)
+ -1
+
+ """
+ if self == other:
+ return 0
+ else:
+ return -1
+
+ #
+ # Map methods
+ #
+
+ def _call_(self, point):
+ r"""
+ Compute the image of a point by the continous map.
+
+ INPUT:
+
+ - ``point`` -- point in the domain of ``self``, as an instance of
+ :class:`~sage.manifolds.point.TopologicalManifoldPoint`
+
+ OUTPUT:
+
+ - image of the point by ``self`` (instance of
+ :class:`~sage.manifolds.point.TopologicalManifoldPoint`)
+
+ EXAMPLES:
+
+ Planar rotation acting on a point::
+
+ sage: M = Manifold(2, 'R^2', latex_name=r'\RR^2', structure='topological') # Euclidean plane
+ sage: c_cart.<x,y> = M.chart() # Cartesian coordinates
+ sage: # A pi/3 rotation around the origin defined in Cartesian coordinates:
+ sage: rot = M.continuous_map(M, ((x - sqrt(3)*y)/2, (sqrt(3)*x + y)/2),
+ ....: name='R')
+ sage: p = M.point((1,2), name='p')
+ sage: q = rot(p) ; q
+ Point R(p) on the 2-dimensional topological manifold R^2
+ sage: q.coord()
+ (-sqrt(3) + 1/2, 1/2*sqrt(3) + 1)
+
+ Image computed after some change of coordinates::
+
+ sage: c_spher.<r,ph> = M.chart(r'r:(0,+oo) ph:(0,2*pi):\phi') # spherical coord. on the plane
+ sage: ch = c_spher.transition_map(c_cart, [r*cos(ph), r*sin(ph)])
+ sage: p1 = M.point((sqrt(5), arctan(2)), chart=c_spher) # p1 is defined only in terms of c_spher
+ sage: q1 = rot(p1) # but the computation of the action of rot is still possible
+ sage: q1 == q
+ True
+
+ Image computed by means of spherical coordinates::
+
+ sage: rot.add_expr(c_spher, c_spher, (r, ph+pi/3)) # now rot is known in terms of c_spher
+ sage: p2 = M.point((sqrt(5), arctan(2)), chart=c_spher)
+ sage: q2 = rot(p2) # computation on c_spher
+ sage: q2 == q
+ True
+
+ """
+ # NB: checking that ``point`` belongs to the map's domain has been
+ # already performed by Map.__call__(); this check is therefore not
+ # repeated here.
+ if self._is_identity:
+ return point
+ dom = self._domain
+ chart1, chart2 = None, None
+ for chart in point._coordinates:
+ for chart_pair in self._coord_expression:
+ if chart_pair[0] is chart:
+ chart1 = chart
+ chart2 = chart_pair[1]
+ break
+ if chart1 is not None:
+ break
+ else:
+ # attempt to perform a change of coordinate on the point
+ for chart_pair in self._coord_expression:
+ try:
+ point.coord(chart_pair[0])
+ chart1, chart2 = chart_pair
+ except ValueError:
+ pass
+ if chart1 is not None:
+ break
+ else:
+ raise ValueError("no pair of charts has been found to " +
+ "compute the action of the {} on the {}".format(self, point))
+ coord_map = self._coord_expression[(chart1, chart2)]
+ y = coord_map(*(point._coordinates[chart1]))
+ if point._name is None or self._name is None:
+ res_name = None
+ else:
+ res_name = self._name + '(' + point._name + ')'
+ if point._latex_name is None or self._latex_name is None:
+ res_latex_name = None
+ else:
+ res_latex_name = self._latex_name + r'\left(' + \
+ point._latex_name + r'\right)'
+ # The image point is created as an element of the domain of chart2:
+ dom2 = chart2.domain()
+ return dom2.element_class(dom2, coords=y, chart=chart2,
+ name=res_name, latex_name=res_latex_name,
+ check_coords=False)
+ #
+ # Morphism methods
+ #
+
+ def is_identity(self):
+ r"""
+ Check whether the continuous map is an identity map.
+
+ EXAMPLES:
+
+ Tests on continuous maps of a 2-dimensional manifold::
+
+ sage: M = Manifold(2, 'M', structure='topological')
+ sage: X.<x,y> = M.chart()
+ sage: M.identity_map().is_identity() # obviously...
+ True
+ sage: Hom(M, M).one().is_identity() # a variant of the obvious
+ True
+ sage: a = M.continuous_map(M, coord_functions={(X,X): (x, y)})
+ sage: a.is_identity()
+ True
+ sage: a = M.continuous_map(M, coord_functions={(X,X): (x, y+1)})
+ sage: a.is_identity()
+ False
+
+ Of course, if the codomain of the map does not coincide with its
+ domain, the outcome is ``False``::
+
+ sage: N = Manifold(2, 'N', structure='topological')
+ sage: Y.<u,v> = N.chart()
+ sage: a = M.continuous_map(N, {(X,Y): (x, y)})
+ sage: a.display()
+ M --> N
+ (x, y) |--> (u, v) = (x, y)
+ sage: a.is_identity()
+ False
+
+ """
+ if self._is_identity:
+ return True
+ if self._codomain != self._domain:
+ return False
+ for chart in self._domain._top_charts:
+ try:
+ if chart[:] != self.expr(chart, chart):
+ return False
+ except ValueError:
+ return False
+ # If this point is reached, ``self`` must be the identity:
+ self._is_identity = True
+ return True
+
+ def _composition_(self, other, homset):
+ r"""
+ Composition of ``self`` with another morphism.
+
+ The composition is performed on the right, i.e. the returned
+ morphism is ``self*other``.
+
+ INPUT:
+
+ - ``other`` -- a continuous map, whose codomain is the domain
+ of ``self``
+ - ``homset`` -- the homset of the continuous map ``self*other``;
+ this argument is required to follow the prototype of
+ :meth:`~sage.categories.map.Map._composition_` and is determined by
+ :meth:`~sage.categories.map.Map._composition` (single underscore),
+ that is supposed to call the current method
+
+ OUTPUT:
+
+ - the composite map ``self*other``, as an instance of
+ :class:`~sage.manifolds.continuous_map.ContinuousMap`
+
+ TESTS::
+
+ sage: M = Manifold(3, 'M', structure='topological')
+ sage: X.<x,y,z> = M.chart()
+ sage: N = Manifold(2, 'N', structure='topological')
+ sage: Y.<u,v> = N.chart()
+ sage: Q = Manifold(4, 'Q', structure='topological')
+ sage: Z.<a,b,c,d> = Q.chart()
+ sage: f = N.continuous_map(Q, [u+v, u*v, 1+u, 2-v])
+ sage: g = M.continuous_map(N, [x+y+z, x*y*z])
+ sage: s = f._composition_(g, Hom(M,Q)); s
+ Continuous map from the 3-dimensional topological manifold M to the 4-dimensional topological manifold Q
+ sage: s.display()
+ M --> Q
+ (x, y, z) |--> (a, b, c, d) = ((x*y + 1)*z + x + y, x*y*z^2 + (x^2*y + x*y^2)*z, x + y + z + 1, -x*y*z + 2)
+ sage: s == f*g
+ True
+
+ """
+ # This method is invoked by Map._composition (single underscore),
+ # which is itself invoked by Map.__mul__ . The latter performs the
+ # check other._codomain == self._domain. There is therefore no need
+ # to perform it here.
+ if self._is_identity:
+ return other
+ if other._is_identity:
+ return self
+ resu_funct = {}
+ for chart1 in other._domain._top_charts:
+ for chart2 in self._domain._top_charts:
+ for chart3 in self._codomain._top_charts:
+ try:
+ self23 = self.coord_functions(chart2, chart3)
+ resu_funct[(chart1, chart3)] = \
+ self23(*(other.expr(chart1, chart2)),
+ simplify=True)
+ except ValueError:
+ pass
+ return homset(resu_funct)
+
+ #
+ # Monoid methods
+ #
+
+ def _mul_(self, other):
+ r"""
+ Composition of ``self`` with another morphism (endomorphism case).
+
+ This applies only when the parent of ``self`` is a monoid, i.e. when
+ ``self`` is an endomorphism of the category of topological manifolds,
+ i.e. a continuous map M --> M, where M is a topological manifold.
+
+ INPUT:
+
+ - ``other`` -- a continuous map, whose codomain is the domain
+ of ``self``
+
+ OUTPUT:
+
+ - the composite map ``self*other``, as an instance of
+ :class:`~sage.manifolds.continuous_map.ContinuousMap`
+
+ TESTS::
+
+ sage: M = Manifold(2, 'M', structure='topological')
+ sage: X.<x,y> = M.chart()
+ sage: f = M.continuous_map(M, [x+y, x*y], name='f')
+ sage: g = M.continuous_map(M, [1-y, 2+x], name='g')
+ sage: s = f._mul_(g); s
+ Continuous map from the 2-dimensional topological manifold M to
+ itself
+ sage: s.display()
+ M --> M
+ (x, y) |--> (x - y + 3, -(x + 2)*y + x + 2)
+ sage: s == f*g
+ True
+ sage: f._mul_(M.identity_map()) == f
+ True
+ sage: M.identity_map()._mul_(f) == f
+ True
+
+ """
+ from sage.categories.homset import Hom
+ dom = self._domain
+ return self._composition_(other, Hom(dom, dom))
+
+ #
+ # Other methods
+ #
+
+ def _init_derived(self):
+ r"""
+ Initialize the derived quantities
+
+ TEST::
+
+ sage: M = Manifold(2, 'M', structure='topological')
+ sage: X.<x,y> = M.chart()
+ sage: f = M.homeomorphism(M, [x+y, x-y])
+ sage: f._init_derived()
+ sage: f._restrictions
+ {}
+ sage: f._inverse
+
+ """
+ self._restrictions = {} # dict. of restrictions to subdomains of
+ # self._domain
+ if self._is_identity:
+ self._inverse = self
+ else:
+ self._inverse = None
+
+ def _del_derived(self):
+ r"""
+ Delete the derived quantities
+
+ TEST::
+
+ sage: M = Manifold(2, 'M', structure='topological')
+ sage: X.<x,y> = M.chart()
+ sage: f = M.homeomorphism(M, [x+y, x-y])
+ sage: f^(-1)
+ Homeomorphism of the 2-dimensional topological manifold M
+ sage: f._inverse # was set by f^(-1)
+ Homeomorphism of the 2-dimensional topological manifold M
+ sage: f._del_derived()
+ sage: f._inverse # has been set to None by _del_derived()
+
+ """
+ self._restrictions.clear()
+ if not self._is_identity:
+ self._inverse = None
+
+ def display(self, chart1=None, chart2=None):
+ r"""
+ Display the expression of the continuous map in one or more
+ pair of charts.
+
+ If the expression is not known already, it is computed from some
+ expression in other charts by means of change-of-coordinate formulas.
+
+ INPUT:
+
+ - ``chart1`` -- (default: ``None``) chart on the map's domain; if
+ ``None``, the display is performed on all the charts on the domain
+ in which the map is known or computable via some change of
+ coordinates
+ - ``chart2`` -- (default: ``None``) chart on the map's codomain; if
+ ``None``, the display is performed on all the charts on the codomain
+ in which the map is known or computable via some change of
+ coordinates
+
+ The output is either text-formatted (console mode) or LaTeX-formatted
+ (notebook mode).
+
+ EXAMPLES:
+
+ Standard embedding of the sphere `S^2` in `\RR^3`::
+
+ sage: M = Manifold(2, 'S^2', structure='topological') # the 2-dimensional sphere S^2
+ sage: U = M.open_subset('U') # complement of the North pole
+ sage: c_xy.<x,y> = U.chart() # stereographic coordinates from the North pole
+ sage: V = M.open_subset('V') # complement of the South pole
+ sage: c_uv.<u,v> = V.chart() # stereographic coordinates from the South pole
+ sage: M.declare_union(U,V) # S^2 is the union of U and V
+ sage: N = Manifold(3, 'R^3', latex_name=r'\RR^3', structure='topological') # R^3
+ sage: c_cart.<X,Y,Z> = N.chart() # Cartesian coordinates on R^3
+ sage: Phi = M.continuous_map(N, \
+ ....: {(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')
+ sage: Phi.display(c_xy, c_cart)
+ Phi: S^2 --> R^3
+ on U: (x, y) |--> (X, Y, Z) = (2*x/(x^2 + y^2 + 1), 2*y/(x^2 + y^2 + 1), (x^2 + y^2 - 1)/(x^2 + y^2 + 1))
+ sage: Phi.display(c_uv, c_cart)
+ Phi: S^2 --> R^3
+ on V: (u, v) |--> (X, Y, Z) = (2*u/(u^2 + v^2 + 1), 2*v/(u^2 + v^2 + 1), -(u^2 + v^2 - 1)/(u^2 + v^2 + 1))
+
+ The LaTeX output::
+
+ sage: latex(Phi.display(c_xy, c_cart))
+ \begin{array}{llcl} \Phi:& S^2 & \longrightarrow & \RR^3 \\ \mbox{on}\ U : & \left(x, y\right) & \longmapsto & \left(X, Y, Z\right) = \left(\frac{2 \, x}{x^{2} + y^{2} + 1}, \frac{2 \, y}{x^{2} + y^{2} + 1}, \frac{x^{2} + y^{2} - 1}{x^{2} + y^{2} + 1}\right) \end{array}
+
+ If the argument ``chart2`` is not specified, the display is performed
+ on all the charts on the codomain in which the map is known
+ or computable via some change of coordinates (here only one chart:
+ c_cart)::
+
+ sage: Phi.display(c_xy)
+ Phi: S^2 --> R^3
+ on U: (x, y) |--> (X, Y, Z) = (2*x/(x^2 + y^2 + 1), 2*y/(x^2 + y^2 + 1), (x^2 + y^2 - 1)/(x^2 + y^2 + 1))
+
+ Similarly, if the argument ``chart1`` is omitted, the display is
+ performed on all the charts on the map's domain in which the
+ map is known or computable via some change of coordinates::
+
+ sage: Phi.display(chart2=c_cart)
+ Phi: S^2 --> R^3
+ on U: (x, y) |--> (X, Y, Z) = (2*x/(x^2 + y^2 + 1), 2*y/(x^2 + y^2 + 1), (x^2 + y^2 - 1)/(x^2 + y^2 + 1))
+ on V: (u, v) |--> (X, Y, Z) = (2*u/(u^2 + v^2 + 1), 2*v/(u^2 + v^2 + 1), -(u^2 + v^2 - 1)/(u^2 + v^2 + 1))
+
+ If neither ``chart1`` nor ``chart2`` is specified, the display is
+ performed on all the pair of charts in which the map is known or
+ computable via some change of coordinates::
+
+ sage: Phi.display()
+ Phi: S^2 --> R^3
+ on U: (x, y) |--> (X, Y, Z) = (2*x/(x^2 + y^2 + 1), 2*y/(x^2 + y^2 + 1), (x^2 + y^2 - 1)/(x^2 + y^2 + 1))
+ on V: (u, v) |--> (X, Y, Z) = (2*u/(u^2 + v^2 + 1), 2*v/(u^2 + v^2 + 1), -(u^2 + v^2 - 1)/(u^2 + v^2 + 1))
+
+ If a chart covers entirely the map's domain, the mention "on ..."
+ is omitted::
+
+ sage: Phi.restrict(U).display()
+ Phi: U --> R^3
+ (x, y) |--> (X, Y, Z) = (2*x/(x^2 + y^2 + 1), 2*y/(x^2 + y^2 + 1), (x^2 + y^2 - 1)/(x^2 + y^2 + 1))
+
+ A shortcut of ``display()`` is ``disp()``::
+
+ sage: Phi.disp()
+ Phi: S^2 --> R^3
+ on U: (x, y) |--> (X, Y, Z) = (2*x/(x^2 + y^2 + 1), 2*y/(x^2 + y^2 + 1), (x^2 + y^2 - 1)/(x^2 + y^2 + 1))
+ on V: (u, v) |--> (X, Y, Z) = (2*u/(u^2 + v^2 + 1), 2*v/(u^2 + v^2 + 1), -(u^2 + v^2 - 1)/(u^2 + v^2 + 1))
+
+ """
+ from sage.misc.latex import latex
+ from sage.tensor.modules.format_utilities import FormattedExpansion
+
+ def _display_expression(self, chart1, chart2, result):
+ r"""
+ Helper function for :meth:`display`.
+ """
+ from sage.misc.latex import latex
+ try:
+ expression = self.expr(chart1, chart2)
+ coords1 = chart1[:]
+ if len(coords1) == 1:
+ coords1 = coords1[0]
+ coords2 = chart2[:]
+ if len(coords2) == 1:
+ coords2 = coords2[0]
+ if chart1._domain == self._domain:
+ result._txt += " "
+ result._latex += " & "
+ else:
+ result._txt += "on " + chart1._domain._name + ": "
+ result._latex += r"\mbox{on}\ " + latex(chart1._domain) + \
+ r": & "
+ result._txt += repr(coords1) + " |--> "
+ result._latex += latex(coords1) + r"& \longmapsto & "
+ if chart2 == chart1:
+ if len(expression) == 1:
+ result._txt += repr(expression[0]) + "\n"
+ result._latex += latex(expression[0]) + r"\\"
+ else:
+ result._txt += repr(expression) + "\n"
+ result._latex += latex(expression) + r"\\"
+ else:
+ if len(expression) == 1:
+ result._txt += repr(coords2[0]) + " = " + \
+ repr(expression[0]) + "\n"
+ result._latex += latex(coords2[0]) + " = " + \
+ latex(expression[0]) + r"\\"
+ else:
+ result._txt += repr(coords2) + " = " + \
+ repr(expression) + "\n"
+ result._latex += latex(coords2) + " = " + \
+ latex(expression) + r"\\"
+ except (TypeError, ValueError):
+ pass
+
+ result = FormattedExpansion()
+ if self._name is None:
+ symbol = ""
+ else:
+ symbol = self._name + ": "
+ result._txt = symbol + self._domain._name + " --> " + \
+ self._codomain._name + "\n"
+ if self._latex_name is None:
+ symbol = ""
+ else:
+ symbol = self._latex_name + ":"
+ result._latex = r"\begin{array}{llcl} " + symbol + r"&" + \
+ latex(self._domain) + r"& \longrightarrow & " + \
+ latex(self._codomain) + r"\\"
+ if chart1 is None:
+ if chart2 is None:
+ for ch1 in self._domain._top_charts:
+ for ch2 in self._codomain.atlas():
+ _display_expression(self, ch1, ch2, result)
+ else:
+ for ch1 in self._domain._top_charts:
+ _display_expression(self, ch1, chart2, result)
+ else:
+ if chart2 is None:
+ for ch2 in self._codomain.atlas():
+ _display_expression(self, chart1, ch2, result)
+ else:
+ _display_expression(self, chart1, chart2, result)
+ result._txt = result._txt[:-1]
+ result._latex = result._latex[:-2] + r"\end{array}"
+ return result
+
+ disp = display
+
+ def coord_functions(self, chart1=None, chart2=None):
+ r"""
+ Return the functions of the coordinates representing the continuous
+ map in a given pair of charts.
+
+ If these functions are not already known, they are computed from known
+ ones by means of change-of-chart formulas.
+
+ INPUT:
+
+ - ``chart1`` -- (default: ``None``) chart on the map's domain;
+ if ``None``, the domain's default chart is assumed
+ - ``chart2`` -- (default: ``None``) chart on the map's codomain;
+ if ``None``, the codomain's default chart is assumed
+
+ OUTPUT:
+
+ - instance of class
+ :class:`~sage.manifolds.coord_func.MultiCoordFunction`
+ representing the continuous map in the above two charts
+
+ EXAMPLES:
+
+ Continuous map from a 2-dimensional manifold to a 3-dimensional
+ one::
+
+ sage: M = Manifold(2, 'M', structure='topological')
+ sage: N = Manifold(3, 'N', structure='topological')
+ sage: c_uv.<u,v> = M.chart()
+ sage: c_xyz.<x,y,z> = N.chart()
+ sage: Phi = M.continuous_map(N, (u*v, u/v, u+v), name='Phi',
+ ....: latex_name=r'\Phi')
+ sage: Phi.display()
+ Phi: M --> N
+ (u, v) |--> (x, y, z) = (u*v, u/v, u + v)
+ sage: Phi.coord_functions(c_uv, c_xyz)
+ Coordinate functions (u*v, u/v, u + v) on the Chart (M, (u, v))
+ sage: Phi.coord_functions() # equivalent to above since 'uv' and 'xyz' are default charts
+ Coordinate functions (u*v, u/v, u + v) on the Chart (M, (u, v))
+ sage: type(Phi.coord_functions())
+ <class 'sage.manifolds.coord_func.MultiCoordFunction'>
+
+ Coordinate representation in other charts::
+
+ sage: c_UV.<U,V> = M.chart() # new chart on M
+ sage: ch_uv_UV = c_uv.transition_map(c_UV, [u-v, u+v])
+ sage: ch_uv_UV.inverse()(U,V)
+ (1/2*U + 1/2*V, -1/2*U + 1/2*V)
+ sage: c_XYZ.<X,Y,Z> = N.chart() # new chart on N
+ sage: ch_xyz_XYZ = c_xyz.transition_map(c_XYZ,
+ ....: [2*x-3*y+z, y+z-x, -x+2*y-z])
+ sage: ch_xyz_XYZ.inverse()(X,Y,Z)
+ (3*X + Y + 4*Z, 2*X + Y + 3*Z, X + Y + Z)
+ sage: Phi.coord_functions(c_UV, c_xyz)
+ Coordinate functions (-1/4*U^2 + 1/4*V^2, -(U + V)/(U - V), V) on
+ the Chart (M, (U, V))
+ sage: Phi.coord_functions(c_uv, c_XYZ)
+ Coordinate functions (((2*u + 1)*v^2 + u*v - 3*u)/v,
+ -((u - 1)*v^2 - u*v - u)/v, -((u + 1)*v^2 + u*v - 2*u)/v) on the
+ Chart (M, (u, v))
+ sage: Phi.coord_functions(c_UV, c_XYZ)
+ Coordinate functions
+ (-1/2*(U^3 - (U - 2)*V^2 + V^3 - (U^2 + 2*U + 6)*V - 6*U)/(U - V),
+ 1/4*(U^3 - (U + 4)*V^2 + V^3 - (U^2 - 4*U + 4)*V - 4*U)/(U - V),
+ 1/4*(U^3 - (U - 4)*V^2 + V^3 - (U^2 + 4*U + 8)*V - 8*U)/(U - V))
+ on the Chart (M, (U, V))
+
+ Coordinate representation w.r.t. a subchart in the domain::
+
+ sage: A = M.open_subset('A', coord_def={c_uv: u>0})
+ sage: Phi.coord_functions(c_uv.restrict(A), c_xyz)
+ Coordinate functions (u*v, u/v, u + v) on the Chart (A, (u, v))
+
+ Coordinate representation w.r.t. a superchart in the codomain::
+
+ sage: B = N.open_subset('B', coord_def={c_xyz: x<0})
+ sage: c_xyz_B = c_xyz.restrict(B)
+ sage: Phi1 = M.continuous_map(B, {(c_uv, c_xyz_B): (u*v, u/v, u+v)})
+ sage: Phi1.coord_functions(c_uv, c_xyz_B) # definition charts
+ Coordinate functions (u*v, u/v, u + v) on the Chart (M, (u, v))
+ sage: Phi1.coord_functions(c_uv, c_xyz) # c_xyz = superchart of c_xyz_B
+ Coordinate functions (u*v, u/v, u + v) on the Chart (M, (u, v))
+
+ Coordinate representation w.r.t. a pair (subchart, superchart)::
+
+ sage: Phi1.coord_functions(c_uv.restrict(A), c_xyz)
+ Coordinate functions (u*v, u/v, u + v) on the Chart (A, (u, v))
+
+ """
+ dom1 = self._domain; dom2 = self._codomain
+ def_chart1 = dom1._def_chart; def_chart2 = dom2._def_chart
+ if chart1 is None:
+ chart1 = def_chart1
+ if chart2 is None:
+ chart2 = def_chart2
+ if (chart1, chart2) not in self._coord_expression:
+ # Check whether (chart1, chart2) are (subchart, superchart) of
+ # a pair of charts where the expression of self is known:
+ for (ochart1, ochart2) in self._coord_expression:
+ if chart1 in ochart1._subcharts and \
+ ochart2 in chart2._subcharts:
+ coord_functions = \
+ self._coord_expression[(ochart1, ochart2)].expr()
+ self._coord_expression[(chart1, chart2)] = \
+ chart1.multifunction(*coord_functions)
+ return self._coord_expression[(chart1, chart2)]
+ # Special case of the identity in a single chart:
+ if self._is_identity and chart1 == chart2:
+ coord_functions = chart1[:]
+ self._coord_expression[(chart1, chart1)] = \
+ chart1.multifunction(*coord_functions)
+ return self._coord_expression[(chart1, chart2)]
+ # Some change of coordinates must be performed
+ change_start = [] ; change_arrival = []
+ for (ochart1, ochart2) in self._coord_expression:
+ if chart1 == ochart1:
+ change_arrival.append(ochart2)
+ if chart2 == ochart2:
+ change_start.append(ochart1)
+ # 1/ Trying to make a change of chart only on the codomain:
+ # the codomain's default chart is privileged:
+ sel_chart2 = None # selected chart2
+ if def_chart2 in change_arrival \
+ and (def_chart2, chart2) in dom2._coord_changes:
+ sel_chart2 = def_chart2
+ else:
+ for ochart2 in change_arrival:
+ if (ochart2, chart2) in dom2._coord_changes:
+ sel_chart2 = ochart2
+ break
+ if sel_chart2 is not None:
+ oexpr = self._coord_expression[(chart1, sel_chart2)]
+ chg2 = dom2._coord_changes[(sel_chart2, chart2)]
+ self._coord_expression[(chart1, chart2)] = \
+ chart1.multifunction(*(chg2(*(oexpr.expr()))))
+ return self._coord_expression[(chart1, chart2)]
+
+ # 2/ Trying to make a change of chart only on the start domain:
+ # the domain's default chart is privileged:
+ sel_chart1 = None # selected chart1
+ if def_chart1 in change_start \
+ and (chart1, def_chart1) in dom1._coord_changes:
+ sel_chart1 = def_chart1
+ else:
+ for ochart1 in change_start:
+ if (chart1, ochart1) in dom1._coord_changes:
+ sel_chart1 = ochart1
+ break
+ if sel_chart1 is not None:
+ oexpr = self._coord_expression[(sel_chart1, chart2)]
+ chg1 = dom1._coord_changes[(chart1, sel_chart1)]
+ self._coord_expression[(chart1, chart2)] = \
+ chart1.multifunction(*(oexpr( *(chg1._transf.expr()) )))
+ return self._coord_expression[(chart1, chart2)]
+
+ # 3/ If this point is reached, it is necessary to perform some
+ # coordinate change both on the start domain and the arrival one
+ # the default charts are privileged:
+ if (def_chart1, def_chart2) in self._coord_expression \
+ and (chart1, def_chart1) in dom1._coord_changes \
+ and (def_chart2, chart2) in dom2._coord_changes:
+ sel_chart1 = def_chart1
+ sel_chart2 = def_chart2
+ else:
+ for (ochart1, ochart2) in self._coord_expression:
+ if (chart1, ochart1) in dom1._coord_changes \
+ and (ochart2, chart2) in dom2._coord_changes:
+ sel_chart1 = ochart1
+ sel_chart2 = ochart2
+ break
+ if (sel_chart1 is not None) and (sel_chart2 is not None):
+ oexpr = self._coord_expression[(sel_chart1, sel_chart2)]
+ chg1 = dom1._coord_changes[(chart1, sel_chart1)]
+ chg2 = dom2._coord_changes[(sel_chart2, chart2)]
+ self._coord_expression[(chart1, chart2)] = \
+ chart1.multifunction(
+ *(chg2( *(oexpr(*(chg1._transf.expr()))) )) )
+ return self._coord_expression[(chart1, chart2)]
+
+ # 4/ If this point is reached, the demanded value cannot be
+ # computed
+ raise ValueError("the expression of the map in the pair " +
+ "({}, {})".format(chart1, chart2) + " cannot " +
+ "be computed by means of known changes of charts")
+
+ return self._coord_expression[(chart1, chart2)]
+
+ def expr(self, chart1=None, chart2=None):
+ r"""
+ Return the expression of the continuous map in terms of
+ specified coordinates.
+
+ If the expression is not already known, it is computed from some known
+ expression by means of change-of-chart formulas.
+
+ INPUT:
+
+ - ``chart1`` -- (default: ``None``) chart on the map's domain;
+ if ``None``, the domain's default chart is assumed
+ - ``chart2`` -- (default: ``None``) chart on the map's codomain;
+ if ``None``, the codomain's default chart is assumed
+
+ OUTPUT:
+
+ - symbolic expression representing the continuous map in the
+ above two charts
+
+ EXAMPLES:
+
+ Continuous map from a 2-dimensional manifold to a 3-dimensional
+ one::
+
+ sage: M = Manifold(2, 'M', structure='topological')
+ sage: N = Manifold(3, 'N', structure='topological')
+ sage: c_uv.<u,v> = M.chart()
+ sage: c_xyz.<x,y,z> = N.chart()
+ sage: Phi = M.continuous_map(N, (u*v, u/v, u+v), name='Phi',
+ ....: latex_name=r'\Phi')
+ sage: Phi.display()
+ Phi: M --> N
+ (u, v) |--> (x, y, z) = (u*v, u/v, u + v)
+ sage: Phi.expr(c_uv, c_xyz)
+ (u*v, u/v, u + v)
+ sage: Phi.expr() # equivalent to above since 'uv' and 'xyz' are default charts
+ (u*v, u/v, u + v)
+ sage: type(Phi.expr()[0])
+ <type 'sage.symbolic.expression.Expression'>
+
+ Expressions in other charts::
+
+ sage: c_UV.<U,V> = M.chart() # new chart on M
+ sage: ch_uv_UV = c_uv.transition_map(c_UV, [u-v, u+v])
+ sage: ch_uv_UV.inverse()(U,V)
+ (1/2*U + 1/2*V, -1/2*U + 1/2*V)
+ sage: c_XYZ.<X,Y,Z> = N.chart() # new chart on N
+ sage: ch_xyz_XYZ = c_xyz.transition_map(c_XYZ,
+ ....: [2*x-3*y+z, y+z-x, -x+2*y-z])
+ sage: ch_xyz_XYZ.inverse()(X,Y,Z)
+ (3*X + Y + 4*Z, 2*X + Y + 3*Z, X + Y + Z)
+ sage: Phi.expr(c_UV, c_xyz)
+ (-1/4*U^2 + 1/4*V^2, -(U + V)/(U - V), V)
+ sage: Phi.expr(c_uv, c_XYZ)
+ (((2*u + 1)*v^2 + u*v - 3*u)/v,
+ -((u - 1)*v^2 - u*v - u)/v,
+ -((u + 1)*v^2 + u*v - 2*u)/v)
+ sage: Phi.expr(c_UV, c_XYZ)
+ (-1/2*(U^3 - (U - 2)*V^2 + V^3 - (U^2 + 2*U + 6)*V - 6*U)/(U - V),
+ 1/4*(U^3 - (U + 4)*V^2 + V^3 - (U^2 - 4*U + 4)*V - 4*U)/(U - V),
+ 1/4*(U^3 - (U - 4)*V^2 + V^3 - (U^2 + 4*U + 8)*V - 8*U)/(U - V))
+
+ A rotation in some Euclidean plane::
+
+ sage: M = Manifold(2, 'M', structure='topological') # the plane (minus a segment to have global regular spherical coordinates)
+ sage: c_spher.<r,ph> = M.chart(r'r:(0,+oo) ph:(0,2*pi):\phi') # spherical coordinates on the plane
+ sage: rot = M.continuous_map(M, (r, ph+pi/3), name='R') # pi/3 rotation around r=0
+ sage: rot.expr()
+ (r, 1/3*pi + ph)
+
+ Expression of the rotation in terms of Cartesian coordinates::
+
+ sage: c_cart.<x,y> = M.chart() # Declaration of Cartesian coordinates
+ sage: ch_spher_cart = c_spher.transition_map(c_cart,
+ ....: [r*cos(ph), r*sin(ph)]) # relation to spherical coordinates
+ sage: ch_spher_cart.set_inverse(sqrt(x^2+y^2), atan2(y,x))
+ Check of the inverse coordinate transformation:
+ r == r
+ ph == arctan2(r*sin(ph), r*cos(ph))
+ x == x
+ y == y
+ sage: rot.expr(c_cart, c_cart)
+ (-1/2*sqrt(3)*y + 1/2*x, 1/2*sqrt(3)*x + 1/2*y)
+
+ """
+ return self.coord_functions(chart1, chart2).expr()
+
+ def set_expr(self, chart1, chart2, coord_functions):
+ r"""
+ Set a new coordinate representation of the continuous map.
+
+ The expressions with respect to other charts are deleted, in order to
+ avoid any inconsistency. To keep them, use :meth:`add_expr` instead.
+
+ INPUT:
+
+ - ``chart1`` -- chart for the coordinates on the map's domain
+ - ``chart2`` -- chart for the coordinates on the map's codomain
+ - ``coord_functions`` -- the coordinate symbolic expression of the
+ map in the above charts: list (or tuple) of the coordinates of
+ the image expressed in terms of the coordinates of the considered
+ point; if the dimension of the codomain is 1, a single
+ expression is expected (not a list with a single element)
+
+ EXAMPLES:
+
+ Polar representation of a planar rotation initally defined in
+ Cartesian coordinates::
+
+ sage: M = Manifold(2, 'R^2', latex_name=r'\RR^2', structure='topological') # the Euclidean plane R^2
+ sage: c_xy.<x,y> = M.chart() # Cartesian coordinate on R^2
+ sage: U = M.open_subset('U', coord_def={c_xy: (y!=0, x<0)}) # the complement of the segment y=0 and x>0
+ sage: c_cart = c_xy.restrict(U) # Cartesian coordinates on U
+ sage: c_spher.<r,ph> = U.chart(r'r:(0,+oo) ph:(0,2*pi):\phi') # spherical coordinates on U
+ sage: # Links between spherical coordinates and Cartesian ones:
+ sage: ch_cart_spher = c_cart.transition_map(c_spher,
+ ....: [sqrt(x*x+y*y), atan2(y,x)])
+ sage: ch_cart_spher.set_inverse(r*cos(ph), r*sin(ph))
+ Check of the inverse coordinate transformation:
+ x == x
+ y == y
+ r == r
+ ph == arctan2(r*sin(ph), r*cos(ph))
+ sage: rot = U.continuous_map(U, ((x - sqrt(3)*y)/2, (sqrt(3)*x + y)/2),
+ ....: name='R')
+ sage: rot.display(c_cart, c_cart)
+ R: U --> U
+ (x, y) |--> (-1/2*sqrt(3)*y + 1/2*x, 1/2*sqrt(3)*x + 1/2*y)
+
+ Let us use the method :meth:`set_expr` to set the
+ spherical-coordinate expression by hand::
+
+ sage: rot.set_expr(c_spher, c_spher, (r, ph+pi/3))
+ sage: rot.display(c_spher, c_spher)
+ R: U --> U
+ (r, ph) |--> (r, 1/3*pi + ph)
+
+ The expression in Cartesian coordinates has been erased::
+
+ sage: rot._coord_expression
+ {(Chart (U, (r, ph)),
+ Chart (U, (r, ph))): Coordinate functions (r, 1/3*pi + ph)
+ on the Chart (U, (r, ph))}
+
+ It is recovered (thanks to the known change of coordinates) by a call
+ to :meth:`display`::
+
+ sage: rot.display(c_cart, c_cart)
+ R: U --> U
+ (x, y) |--> (-1/2*sqrt(3)*y + 1/2*x, 1/2*sqrt(3)*x + 1/2*y)
+
+ sage: rot._coord_expression # random (dictionary output)
+ {(Chart (U, (x, y)),
+ Chart (U, (x, y))): Coordinate functions (-1/2*sqrt(3)*y + 1/2*x,
+ 1/2*sqrt(3)*x + 1/2*y) on the Chart (U, (x, y)),
+ (Chart (U, (r, ph)),
+ Chart (U, (r, ph))): Coordinate functions (r, 1/3*pi + ph)
+ on the Chart (U, (r, ph))}
+
+ """
+ if self._is_identity:
+ raise NotImplementedError("set_expr() must not be used for the " +
+ "identity map")
+ if chart1 not in self._domain.atlas():
+ raise ValueError("the {}".format(chart1) +
+ " has not been defined on the {}".format(self._domain))
+ if chart2 not in self._codomain.atlas():
+ raise ValueError("the {}".format(chart2) +
+ " has not been defined on the {}".format(self._codomain))
+ self._coord_expression.clear()
+ self._del_derived()
+ n2 = self._codomain.dim()
+ if n2 > 1:
+ if len(coord_functions) != n2:
+ raise ValueError("{} coordinate functions must ".format(n2) +
+ "be provided.")
+ self._coord_expression[(chart1, chart2)] = \
+ chart1.multifunction(*coord_functions)
+ else:
+ self._coord_expression[(chart1, chart2)] = \
+ chart1.multifunction(coord_functions)
+
+ def add_expr(self, chart1, chart2, coord_functions):
+ r"""
+ Set a new coordinate representation of the continuous map.
+
+ The previous expressions with respect to other charts are kept. To
+ clear them, use :meth:`set_expr` instead.
+
+ INPUT:
+
+ - ``chart1`` -- chart for the coordinates on the map's domain
+ - ``chart2`` -- chart for the coordinates on the map's codomain
+ - ``coord_functions`` -- the coordinate symbolic expression of the
+ map in the above charts: list (or tuple) of the coordinates of
+ the image expressed in terms of the coordinates of the considered
+ point; if the dimension of the codomain is 1, a single
+ expression is expected (not a list with a single element)
+
+ .. WARNING::
+
+ If the map has already expressions in other charts, it
+ is the user's responsibility to make sure that the expression
+ to be added is consistent with them.
+
+ EXAMPLES:
+
+ Polar representation of a planar rotation initally defined in
+ Cartesian coordinates::
+
+ sage: M = Manifold(2, 'R^2', latex_name=r'\RR^2', structure='topological') # the Euclidean plane R^2
+ sage: c_xy.<x,y> = M.chart() # Cartesian coordinate on R^2
+ sage: U = M.open_subset('U', coord_def={c_xy: (y!=0, x<0)}) # the complement of the segment y=0 and x>0
+ sage: c_cart = c_xy.restrict(U) # Cartesian coordinates on U
+ sage: c_spher.<r,ph> = U.chart(r'r:(0,+oo) ph:(0,2*pi):\phi') # spherical coordinates on U
+ sage: # Links between spherical coordinates and Cartesian ones:
+ sage: ch_cart_spher = c_cart.transition_map(c_spher, [sqrt(x*x+y*y), atan2(y,x)])
+ sage: ch_cart_spher.set_inverse(r*cos(ph), r*sin(ph))
+ Check of the inverse coordinate transformation:
+ x == x
+ y == y
+ r == r
+ ph == arctan2(r*sin(ph), r*cos(ph))
+ sage: rot = U.continuous_map(U, ((x - sqrt(3)*y)/2, (sqrt(3)*x + y)/2),
+ ....: name='R')
+ sage: rot.display(c_cart, c_cart)
+ R: U --> U
+ (x, y) |--> (-1/2*sqrt(3)*y + 1/2*x, 1/2*sqrt(3)*x + 1/2*y)
+
+ If we make Sage calculate the expression in terms of spherical
+ coordinates, via the method :meth:`display`, we notice some difficulties
+ in arctan2 simplifications::
+
+ sage: rot.display(c_spher, c_spher)
+ R: U --> U
+ (r, ph) |--> (r, arctan2(1/2*(sqrt(3)*cos(ph) + sin(ph))*r, -1/2*(sqrt(3)*sin(ph) - cos(ph))*r))
+
+ Therefore, we use the method :meth:`add_expr` to set the
+ spherical-coordinate expression by hand::
+
+ sage: rot.add_expr(c_spher, c_spher, (r, ph+pi/3))
+ sage: rot.display(c_spher, c_spher)
+ R: U --> U
+ (r, ph) |--> (r, 1/3*pi + ph)
+
+ The call to :meth:`add_expr` has not deleted the expression in
+ terms of Cartesian coordinates, as we can check by printing the
+ dictionary :attr:`_coord_expression`, which stores the various internal
+ representations of the continuous map::
+
+ sage: rot._coord_expression # random (dictionary output)
+ {(Chart (U, (x, y)),
+ Chart (U, (x, y))): Coordinate functions (-1/2*sqrt(3)*y + 1/2*x,
+ 1/2*sqrt(3)*x + 1/2*y) on the Chart (U, (x, y)),
+ (Chart (U, (r, ph)),
+ Chart (U, (r, ph))): Coordinate functions (r, 1/3*pi + ph)
+ on the Chart (U, (r, ph))}
+
+ If, on the contrary, we use :meth:`set_expr`, the expression in
+ Cartesian coordinates is lost::
+
+ sage: rot.set_expr(c_spher, c_spher, (r, ph+pi/3))
+ sage: rot._coord_expression
+ {(Chart (U, (r, ph)),
+ Chart (U, (r, ph))): Coordinate functions (r, 1/3*pi + ph)
+ on the Chart (U, (r, ph))}
+
+ It is recovered (thanks to the known change of coordinates) by a call
+ to :meth:`display`::
+
+ sage: rot.display(c_cart, c_cart)
+ R: U --> U
+ (x, y) |--> (-1/2*sqrt(3)*y + 1/2*x, 1/2*sqrt(3)*x + 1/2*y)
+
+ sage: rot._coord_expression # random (dictionary output)
+ {(Chart (U, (x, y)),
+ Chart (U, (x, y))): Coordinate functions (-1/2*sqrt(3)*y + 1/2*x,
+ 1/2*sqrt(3)*x + 1/2*y) on the Chart (U, (x, y)),
+ (Chart (U, (r, ph)),
+ Chart (U, (r, ph))): Coordinate functions (r, 1/3*pi + ph)
+ on the Chart (U, (r, ph))}
+
+ The rotation can be applied to a point by means of either coordinate
+ system::
+
+ sage: p = M.point((1,2)) # p defined by its Cartesian coord.
+ sage: q = rot(p) # q is computed by means of Cartesian coord.
+ sage: p1 = M.point((sqrt(5), arctan(2)), chart=c_spher) # p1 is defined only in terms of c_spher
+ sage: q1 = rot(p1) # computation by means of spherical coordinates
+ sage: q1 == q
+ True
+
+ """
+ if self._is_identity:
+ raise NotImplementedError("add_expr() must not be used for the " +
+ "identity map")
+ if chart1 not in self._domain.atlas():
+ raise ValueError("the {}".format(chart1) +
+ " has not been defined on the {}".format(self._domain))
+ if chart2 not in self._codomain.atlas():
+ raise ValueError("the {}".format(chart2) +
+ " has not been defined on the {}".format(self._codomain))
+ self._del_derived()
+ n2 = self._codomain.dim()
+ if n2 > 1:
+ if len(coord_functions) != n2:
+ raise ValueError("{} coordinate functions must ".format(n2) +
+ "be provided")
+ self._coord_expression[(chart1, chart2)] = \
+ chart1.multifunction(*coord_functions)
+ else:
+ self._coord_expression[(chart1, chart2)] = \
+ chart1.multifunction(coord_functions)
+
+ def restrict(self, subdomain, subcodomain=None):
+ r"""
+ Restriction of the continuous map to some open subset of its
+ domain of definition.
+
+ INPUT:
+
+ - ``subdomain`` -- an open subset of the domain of the continuous map
+ (must be an instance of
+ :class:`~sage.manifolds.manifold.TopologicalManifold`)
+ - ``subcodomain`` -- (default: ``None``) an open subset of the codomain
+ of the continuous map (must be an instance of
+ :class:`~sage.manifolds.manifold.TopologicalManifold`); if ``None``,
+ the codomain of the continuous map is assumed.
+
+ OUTPUT:
+
+ - the restriction of the continuous map to ``subdomain``, as an
+ instance of class :class:`ContinuousMap`
+
+ EXAMPLE:
+
+ Restriction to an annulus of a homeomorphism between the open unit
+ disk and `\RR^2`::
+
+ sage: M = Manifold(2, 'R^2', structure='topological') # R^2
+ sage: c_xy.<x,y> = M.chart() # Cartesian coord. on R^2
+ sage: D = M.open_subset('D', coord_def={c_xy: x^2+y^2<1}) # the open unit disk
+ sage: Phi = D.continuous_map(M, [x/sqrt(1-x^2-y^2), y/sqrt(1-x^2-y^2)],
+ ....: name='Phi', latex_name=r'\Phi')
+ sage: Phi.display()
+ Phi: D --> R^2
+ (x, y) |--> (x, y) = (x/sqrt(-x^2 - y^2 + 1), y/sqrt(-x^2 - y^2 + 1))
+ sage: c_xy_D = c_xy.restrict(D)
+ sage: U = D.open_subset('U', coord_def={c_xy_D: x^2+y^2>1/2}) # the annulus 1/2 < r < 1
+ sage: Phi.restrict(U)
+ Continuous map Phi from the Open subset U of the 2-dimensional
+ topological manifold R^2 to the 2-dimensional topological
+ manifold R^2
+ sage: Phi.restrict(U).parent()
+ Set of Morphisms from Open subset U of the 2-dimensional
+ topological manifold R^2 to 2-dimensional topological manifold R^2
+ in Join of Category of subobjects of sets and Category of
+ manifolds over Real Field with 53 bits of precision
+ sage: Phi.domain()
+ Open subset D of the 2-dimensional topological manifold R^2
+ sage: Phi.restrict(U).domain()
+ Open subset U of the 2-dimensional topological manifold R^2
+ sage: Phi.restrict(U).display()
+ Phi: U --> R^2
+ (x, y) |--> (x, y) = (x/sqrt(-x^2 - y^2 + 1), y/sqrt(-x^2 - y^2 + 1))
+
+ The result is cached::
+
+ sage: Phi.restrict(U) is Phi.restrict(U)
+ True
+
+ The restriction of the identity map::
+
+ sage: id = D.identity_map() ; id
+ Identity map Id_D of the Open subset D of the 2-dimensional
+ topological manifold R^2
+ sage: id.restrict(U)
+ Identity map Id_U of the Open subset U of the 2-dimensional
+ topological manifold R^2
+ sage: id.restrict(U) is U.identity_map()
+ True
+
+ The codomain can be restricted (i.e. made tighter)::
+
+ sage: Phi = D.continuous_map(M, [x/sqrt(1+x^2+y^2), y/sqrt(1+x^2+y^2)])
+ sage: Phi
+ Continuous map from the Open subset D of the 2-dimensional
+ topological manifold R^2 to the 2-dimensional topological manifold R^2
+ sage: Phi.restrict(D, subcodomain=D)
+ Continuous map from the Open subset D of the 2-dimensional
+ topological manifold R^2 to itself
+
+ """
+ from sage.categories.homset import Hom
+ if subcodomain is None:
+ if self._is_identity:
+ subcodomain = subdomain
+ else:
+ subcodomain = self._codomain
+ if subdomain == self._domain and subcodomain == self._codomain:
+ return self
+ if (subdomain, subcodomain) not in self._restrictions:
+ if not subdomain.is_subset(self._domain):
+ raise ValueError("the specified domain is not a subset " +
+ "of the domain of definition of the " +
+ "continuous map")
+ if not subcodomain.is_subset(self._codomain):
+ raise ValueError("the specified codomain is not a subset " +
+ "of the codomain of the continuous map")
+ # Special case of the identity map:
+ if self._is_identity:
+ self._restrictions[(subdomain, subcodomain)] = \
+ subdomain.identity_map()
+ return self._restrictions[(subdomain, subcodomain)]
+ # Generic case:
+ homset = Hom(subdomain, subcodomain)
+ resu = type(self)(homset, name=self._name,
+ latex_name=self._latex_name)
+ for charts in self._coord_expression:
+ for ch1 in charts[0]._subcharts:
+ if ch1._domain.is_subset(subdomain):
+ for ch2 in charts[1]._subcharts:
+ if ch2._domain.is_subset(subcodomain):
+ for sch2 in ch2._supercharts:
+ if (ch1, sch2) in resu._coord_expression:
+ break
+ else:
+ for sch2 in ch2._subcharts:
+ if (ch1, sch2) in resu._coord_expression:
+ del resu._coord_expression[(ch1,
+ sch2)]
+ coord_functions = \
+ self._coord_expression[charts].expr()
+ resu._coord_expression[(ch1, ch2)] = \
+ ch1.multifunction(*coord_functions)
+ self._restrictions[(subdomain, subcodomain)] = resu
+ return self._restrictions[(subdomain, subcodomain)]
+
+
+ def __invert__(self):
+ r"""
+ Return the inverse of the map whenever it is an isomorphism.
+
+ OUTPUT:
+
+ - the inverse isomorphism
+
+ EXAMPLES:
+
+ The inverse of a rotation in the Euclidean plane::
+
+ sage: M = Manifold(2, 'R^2', latex_name=r'\RR^2', structure='topological')
+ sage: c_cart.<x,y> = M.chart()
+ sage: # A pi/3 rotation around the origin:
+ sage: rot = M.homeomorphism(M, ((x - sqrt(3)*y)/2, (sqrt(3)*x + y)/2),
+ ....: name='R')
+ sage: rot.inverse()
+ Homeomorphism R^(-1) of the 2-dimensional topological manifold R^2
+ sage: rot.inverse().display()
+ R^(-1): R^2 --> R^2
+ (x, y) |--> (1/2*sqrt(3)*y + 1/2*x, -1/2*sqrt(3)*x + 1/2*y)
+
+ Checking that applying successively the homeomorphism and its
+ inverse results in the identity::
+
+ sage: (a, b) = var('a b')
+ sage: p = M.point((a,b)) # a generic point on M
+ sage: q = rot(p)
+ sage: p1 = rot.inverse()(q)
+ sage: p1 == p
+ True
+
+ The result is cached::
+
+ sage: rot.inverse() is rot.inverse()
+ True
+
+ The notations ``^(-1)`` or ``~`` can also be used for the inverse::
+
+ sage: rot^(-1) is rot.inverse()
+ True
+ sage: ~rot is rot.inverse()
+ True
+
+ An example with multiple charts: the equatorial symmetry on the
+ 2-sphere::
+
+ sage: M = Manifold(2, 'M', structure='topological') # the 2-dimensional sphere S^2
+ sage: U = M.open_subset('U') # complement of the North pole
+ sage: c_xy.<x,y> = U.chart() # stereographic coordinates from the North pole
+ sage: V = M.open_subset('V') # complement of the South pole
+ sage: c_uv.<u,v> = V.chart() # stereographic coordinates from the South pole
+ sage: M.declare_union(U,V) # S^2 is the union of U and V
+ 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: s = M.homeomorphism(M, {(c_xy, c_uv): [x, y], (c_uv, c_xy): [u, v]},
+ ....: name='s')
+ sage: s.display()
+ s: M --> M
+ on U: (x, y) |--> (u, v) = (x, y)
+ on V: (u, v) |--> (x, y) = (u, v)
+ sage: si = s.inverse(); si
+ Homeomorphism s^(-1) of the 2-dimensional topological manifold M
+ sage: si.display()
+ s^(-1): M --> M
+ on U: (x, y) |--> (u, v) = (x, y)
+ on V: (u, v) |--> (x, y) = (u, v)
+
+ The equatorial symmetry is of course an involution::
+
+ sage: si == s
+ True
+
+ """
+ from sage.symbolic.ring import SR
+ from sage.symbolic.relation import solve
+ if self._inverse is not None:
+ return self._inverse
+ if not self._is_isomorphism:
+ raise ValueError("the {} is not an isomorphism".format(self))
+ coord_functions = {} # coordinate expressions of the result
+ for (chart1, chart2) in self._coord_expression:
+ coord_map = self._coord_expression[(chart1, chart2)]
+ n1 = len(chart1._xx)
+ n2 = len(chart2._xx)
+ # New symbolic variables (different from chart2._xx to allow for a
+ # correct solution even when chart2 = chart1):
+ x2 = [ SR.var('xxxx' + str(i)) for i in range(n2) ]
+ equations = [ x2[i] == coord_map._functions[i]._express
+ for i in range(n2) ]
+ solutions = solve(equations, chart1._xx, solution_dict=True)
+ if len(solutions) == 0:
+ raise ValueError("no solution found")
+ if len(solutions) > 1:
+ raise ValueError("non-unique solution found")
+ substitutions = dict(zip(x2, chart2._xx))
+ inv_functions = [solutions[0][chart1._xx[i]].subs(substitutions)
+ for i in range(n1)]
+ for i in range(n1):
+ x = inv_functions[i]
+ try:
+ inv_functions[i] = chart2._simplify(x)
+ except AttributeError:
+ pass
+ coord_functions[(chart2, chart1)] = inv_functions
+ if self._name is None:
+ name = None
+ else:
+ name = self._name + '^(-1)'
+ if self._latex_name is None:
+ latex_name = None
+ else:
+ latex_name = self._latex_name + r'^{-1}'
+ homset = Hom(self._codomain, self._domain)
+ self._inverse = type(self)(homset, coord_functions=coord_functions,
+ name=name, latex_name=latex_name,
+ is_isomorphism=True)
+ return self._inverse
+
+ inverse = __invert__
diff --git a/src/sage/manifolds/manifold.py b/src/sage/manifolds/manifold.py
index 08b7fb2..72718d5 100644
--- a/src/sage/manifolds/manifold.py
+++ b/src/sage/manifolds/manifold.py
@@ -327,7 +327,7 @@ REFERENCES:
from sage.categories.fields import Fields
from sage.categories.manifolds import Manifolds
-from sage.categories.sets_cat import Sets
+from sage.categories.homset import Hom
from sage.rings.all import CC
from sage.rings.real_mpfr import RR, RealField_class
from sage.rings.complex_field import ComplexField_class
@@ -559,6 +559,10 @@ class TopologicalManifold(AbstractSet):
self._zero_scalar_field = self._scalar_field_algebra.zero()
# The unit scalar field:
self._one_scalar_field = self._scalar_field_algebra.one()
+ # Dictionary of sets of morphisms to over manifolds (keys: codomains):
+ self._homsets = {} # to be populated by self._Hom_
+ # The identity map on self:
+ self._identity_map = Hom(self, self).one()
def _repr_(self):
r"""
@@ -1841,6 +1845,297 @@ class TopologicalManifold(AbstractSet):
"""
return self._one_scalar_field
+ def _Hom_(self, other, category=None):
+ r"""
+ Construct the set of morphisms (i.e. continuous maps)
+ ``self`` --> ``other``.
+
+ INPUT:
+
+ - ``other`` -- an open subset of some topological manifold over the
+ same field as ``self``
+ - ``category`` -- (default: ``None``) not used here (to ensure
+ compatibility with generic hook ``_Hom_``)
+
+ OUTPUT:
+
+ - the homset Hom(U,V), where U is ``self`` and V is ``other``
+
+ See class
+ :class:`~sage.manifolds.manifold_homset.TopologicalManifoldHomset`
+ for more documentation.
+
+ TESTS::
+
+ sage: M = Manifold(2, 'M', structure='topological')
+ sage: N = Manifold(3, 'N', structure='topological')
+ sage: H = M._Hom_(N); H
+ Set of Morphisms from 2-dimensional topological manifold M to
+ 3-dimensional topological manifold N in Category of manifolds over
+ Real Field with 53 bits of precision
+
+ The result is cached::
+
+ sage: H is Hom(M, N)
+ True
+
+ """
+ if other not in self._homsets:
+ self._homsets[other] = self._structure.homset(self, other)
+ return self._homsets[other]
+
+ def continuous_map(self, codomain, coord_functions=None, chart1=None,
+ chart2=None, name=None, latex_name=None):
+ r"""
+ Define a continuous map between the current topological manifold
+ and another topological manifold over the same topological field.
+
+ See :class:`~sage.manifolds.continuous_map.ContinuousMap` for a
+ complete documentation.
+
+ INPUT:
+
+ - ``codomain`` -- the map's codomain (must be an instance
+ of :class:`TopologicalManifold`)
+ - ``coord_functions`` -- (default: ``None``) if not ``None``, must be
+ either
+
+ - (i) a dictionary of
+ the coordinate expressions (as lists (or tuples) of the
+ coordinates of the image expressed in terms of the coordinates of
+ the considered point) with the pairs of charts (chart1, chart2)
+ as keys (chart1 being a chart on ``self`` and chart2 a chart on
+ ``codomain``)
+ - (ii) a single coordinate expression in a given pair of charts, the
+ latter being provided by the arguments ``chart1`` and ``chart2``
+
+ In both cases, if the dimension of the codomain is 1, a single
+ coordinate expression can be passed instead of a tuple with a single
+ element
+ - ``chart1`` -- (default: ``None``; used only in case (ii) above) chart
+ on the current manifold defining the start coordinates involved in
+ ``coord_functions`` for case (ii); if none is provided, the
+ coordinates are assumed to refer to the manifold's default chart
+ - ``chart2`` -- (default: ``None``; used only in case (ii) above) chart
+ on ``codomain`` defining the target coordinates involved in
+ ``coord_functions`` for case (ii); if none is provided, the
+ coordinates are assumed to refer to the default chart of ``codomain``
+ - ``name`` -- (default: ``None``) name given to the continuous
+ map
+ - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the
+ continuous map; if none is provided, the LaTeX symbol is set to
+ ``name``
+
+ OUTPUT:
+
+ - the continuous map, as an instance of
+ :class:`~sage.manifolds.continuous_map.ContinuousMap`
+
+ EXAMPLES:
+
+ A continuous map between an open subset of `S^2` covered by regular
+ spherical coordinates and `\RR^3`::
+
+ sage: M = Manifold(2, 'S^2', structure='topological')
+ sage: U = M.open_subset('U')
+ sage: c_spher.<th,ph> = U.chart(r'th:(0,pi):\theta ph:(0,2*pi):\phi')
+ sage: N = Manifold(3, 'R^3', latex_name=r'\RR^3', structure='topological')
+ sage: c_cart.<x,y,z> = N.chart() # Cartesian coord. on R^3
+ sage: Phi = U.continuous_map(N, (sin(th)*cos(ph), sin(th)*sin(ph), cos(th)),
+ ....: name='Phi', latex_name=r'\Phi')
+ sage: Phi
+ Continuous map Phi from the Open subset U of the 2-dimensional topological manifold S^2 to the 3-dimensional topological manifold R^3
+
+ The same definition, but with a dictionary with pairs of charts as
+ keys (case (i) above)::
+
+ sage: Phi1 = U.continuous_map(N,
+ ....: {(c_spher, c_cart): (sin(th)*cos(ph), sin(th)*sin(ph), cos(th))},
+ ....: name='Phi', latex_name=r'\Phi')
+ sage: Phi1 == Phi
+ True
+
+ The continuous map acting on a point::
+
+ sage: p = U.point((pi/2, pi)) ; p
+ Point on the 2-dimensional topological manifold S^2
+ sage: Phi(p)
+ Point on the 3-dimensional topological manifold R^3
+ sage: Phi(p).coord(c_cart)
+ (-1, 0, 0)
+ sage: Phi1(p) == Phi(p)
+ True
+
+ See the documentation of class
+ :class:`~sage.manifolds.continuous_map.ContinuousMap` for more
+ examples.
+
+ """
+ homset = Hom(self, codomain)
+ if coord_functions is None:
+ coord_functions = {}
+ if not isinstance(coord_functions, dict):
+ # Turn coord_functions into a dictionary:
+ if chart1 is None:
+ chart1 = self._def_chart
+ elif chart1 not in self._atlas:
+ raise ValueError("{} is not a chart ".format(chart1) +
+ "defined on the {}".format(self))
+ if chart2 is None:
+ chart2 = codomain._def_chart
+ elif chart2 not in codomain._atlas:
+ raise ValueError("{} is not a chart ".format(chart2) +
+ " defined on the {}".format(codomain))
+ coord_functions = {(chart1, chart2): coord_functions}
+ return homset(coord_functions, name=name, latex_name=latex_name)
+
+ def homeomorphism(self, codomain, coord_functions=None, chart1=None,
+ chart2=None, name=None, latex_name=None):
+ r"""
+ Define a homeomorphism between the current manifold and another one.
+
+ See :class:`~sage.manifolds.continuous_map.ContinuousMap` for a
+ complete documentation.
+
+ INPUT:
+
+ - ``codomain`` -- codomain of the homeomorphism (must be an instance
+ of :class:`TopologicalManifold`)
+ - ``coord_functions`` -- (default: ``None``) if not ``None``, must be
+ either
+
+ - (i) a dictionary of
+ the coordinate expressions (as lists (or tuples) of the
+ coordinates of the image expressed in terms of the coordinates of
+ the considered point) with the pairs of charts (chart1, chart2)
+ as keys (chart1 being a chart on ``self`` and chart2 a chart on
+ ``codomain``)
+ - (ii) a single coordinate expression in a given pair of charts, the
+ latter being provided by the arguments ``chart1`` and ``chart2``
+
+ In both cases, if the dimension of the codomain is 1, a single
+ coordinate expression can be passed instead of a tuple with
+ a single element
+ - ``chart1`` -- (default: ``None``; used only in case (ii) above) chart
+ on the current manifold defining the start coordinates involved in
+ ``coord_functions`` for case (ii); if none is provided, the
+ coordinates are assumed to refer to the manifold's default chart
+ - ``chart2`` -- (default: ``None``; used only in case (ii) above) chart
+ on ``codomain`` defining the target coordinates involved in
+ ``coord_functions`` for case (ii); if none is provided, the
+ coordinates are assumed to refer to the default chart of ``codomain``
+ - ``name`` -- (default: ``None``) name given to the homeomorphism
+ - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the
+ homeomorphism; if none is provided, the LaTeX symbol is set to
+ ``name``
+
+ OUTPUT:
+
+ - the homeomorphism, as an instance of
+ :class:`~sage.manifolds.continuous_map.ContinuousMap`
+
+ EXAMPLE:
+
+ Homeomorphism between the open unit disk in `\RR^2` and `\RR^2`::
+
+ sage: forget() # for doctests only
+ sage: M = Manifold(2, 'M', structure='topological') # the open unit disk
+ sage: c_xy.<x,y> = M.chart('x:(-1,1) y:(-1,1)') # Cartesian coord on M
+ sage: c_xy.add_restrictions(x^2+y^2<1)
+ sage: N = Manifold(2, 'N', structure='topological') # R^2
+ sage: c_XY.<X,Y> = N.chart() # canonical coordinates on R^2
+ sage: Phi = M.homeomorphism(N, [x/sqrt(1-x^2-y^2), y/sqrt(1-x^2-y^2)],
+ ....: name='Phi', latex_name=r'\Phi')
+ sage: Phi
+ Homeomorphism Phi from the 2-dimensional topological manifold M to
+ the 2-dimensional topological manifold N
+ sage: Phi.display()
+ Phi: M --> N
+ (x, y) |--> (X, Y) = (x/sqrt(-x^2 - y^2 + 1), y/sqrt(-x^2 - y^2 + 1))
+
+ The inverse homeomorphism::
+
+ sage: Phi^(-1)
+ Homeomorphism Phi^(-1) from the 2-dimensional topological
+ manifold N to the 2-dimensional topological manifold M
+ sage: (Phi^(-1)).display()
+ Phi^(-1): N --> M
+ (X, Y) |--> (x, y) = (X/sqrt(X^2 + Y^2 + 1), Y/sqrt(X^2 + Y^2 + 1))
+
+ See the documentation of class
+ :class:`~sage.manifolds.continuous_map.ContinuousMap` for more
+ examples.
+
+ """
+ homset = Hom(self, codomain)
+ if coord_functions is None:
+ coord_functions = {}
+ if not isinstance(coord_functions, dict):
+ # Turn coord_functions into a dictionary:
+ if chart1 is None:
+ chart1 = self._def_chart
+ elif chart1 not in self._atlas:
+ raise ValueError("{} is not a chart ".format(chart1) +
+ "defined on the {}".format(self))
+ if chart2 is None:
+ chart2 = codomain._def_chart
+ elif chart2 not in codomain._atlas:
+ raise ValueError("{} is not a chart ".format(chart2) +
+ " defined on the {}".format(codomain))
+ coord_functions = {(chart1, chart2): coord_functions}
+ return homset(coord_functions, name=name, latex_name=latex_name,
+ is_isomorphism=True)
+
+ def identity_map(self):
+ r"""
+ Identity map of the manifold.
+
+ The identity map of a topological manifold `M` is the trivial
+ homeomorphism
+
+ .. MATH::
+
+ \begin{array}{cccc}
+ \mathrm{Id}_M: & M & \longrightarrow & M \\
+ & p & \longmapsto & p
+ \end{array}
+
+ See :class:`~sage.manifolds.continuous_map.ContinuousMap` for a
+ complete documentation.
+
+ OUTPUT:
+
+ - the identity map, as an instance of
+ :class:`~sage.manifolds.continuous_map.ContinuousMap`
+
+ EXAMPLE:
+
+ Identity map of a complex manifold::
+
+ sage: M = Manifold(2, 'M', structure='topological', field='complex')
+ sage: X.<x,y> = M.chart()
+ sage: id = M.identity_map(); id
+ Identity map Id_M of the Complex 2-dimensional topological manifold M
+ sage: id.parent()
+ Set of Morphisms from Complex 2-dimensional topological manifold M
+ to Complex 2-dimensional topological manifold M in Category of
+ manifolds over Complex Field with 53 bits of precision
+ sage: id.display()
+ Id_M: M --> M
+ (x, y) |--> (x, y)
+
+ The identity map acting on a point::
+
+ sage: p = M((1+I, 3-I), name='p'); p
+ Point p on the Complex 2-dimensional topological manifold M
+ sage: id(p)
+ Point p on the Complex 2-dimensional topological manifold M
+ sage: id(p) == p
+ True
+
+ """
+ return self._identity_map
+
##############################################################################
## Constructor function
diff --git a/src/sage/manifolds/manifold_homset.py b/src/sage/manifolds/manifold_homset.py
new file mode 100644
index 0000000..ecab5e6
--- /dev/null
+++ b/src/sage/manifolds/manifold_homset.py
@@ -0,0 +1,422 @@
+r"""
+Sets of morphisms between topological manifolds
+
+The class :class:`TopologicalManifoldHomset` implements sets of morphisms between
+two topological manifolds over the same topological field `K`, a morphism
+being a *continuous map* for the category of topological manifolds.
+
+AUTHORS:
+
+- Eric Gourgoulhon (2015): initial version
+
+REFERENCES:
+
+- J.M. Lee : *Introduction to Topological Manifolds*, 2nd ed., Springer (New
+ York) (2011)
+- S. Kobayashi & K. Nomizu : *Foundations of Differential Geometry*, vol. 1,
+ Interscience Publishers (New York) (1963)
+
+"""
+#******************************************************************************
+# Copyright (C) 2015 Eric Gourgoulhon <eric.gourgoulhon@obspm.fr>
+#
+# Distributed under the terms of the GNU General Public License (GPL)
+# as published by the Free Software Foundation; either version 2 of
+# the License, or (at your option) any later version.
+# http://www.gnu.org/licenses/
+#******************************************************************************
+
+from sage.categories.homset import Homset
+from sage.structure.parent import Parent
+from sage.structure.unique_representation import UniqueRepresentation
+from sage.manifolds.continuous_map import ContinuousMap
+
+class TopologicalManifoldHomset(UniqueRepresentation, Homset):
+ r"""
+ Set of continuous maps between two topological manifolds.
+
+ Given two topological manifolds `M` and `N` over a topological field `K`,
+ the class :class:`TopologicalManifoldHomset` implements the set
+ `\mathrm{Hom}(M,N)` of morphisms (i.e. continuous maps) `M\rightarrow N`.
+
+ This is a Sage *parent* class, whose *element* class is
+ :class:`~sage.manifolds.continuous_map.ContinuousMap`.
+
+ INPUT:
+
+ - ``domain`` -- topological manifold `M` (domain of the morphisms belonging
+ to the homset), as an instance of
+ :class:`~sage.manifolds.manifold.TopologicalManifold`
+ - ``codomain`` -- topological manifold `N` (codomain of the morphisms
+ belonging to the homset), as an instance of
+ :class:`~sage.manifolds.manifold.TopologicalManifold`
+ - ``name`` -- (default: ``None``) string; name given to the homset; if
+ ``None``, Hom(M,N) will be used
+ - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to denote the
+ homset; if ``None``, `\mathrm{Hom}(M,N)` will be used
+
+ EXAMPLES:
+
+ Set of continuous maps between a 2-dimensional manifold and a
+ 3-dimensional one::
+
+ sage: M = Manifold(2, 'M', structure='topological')
+ sage: X.<x,y> = M.chart()
+ sage: N = Manifold(3, 'N', structure='topological')
+ sage: Y.<u,v,w> = N.chart()
+ sage: H = Hom(M, N) ; H
+ Set of Morphisms from 2-dimensional topological manifold M to
+ 3-dimensional topological manifold N in Category of manifolds over
+ Real Field with 53 bits of precision
+ sage: type(H)
+ <class 'sage.manifolds.manifold_homset.TopologicalManifoldHomset_with_category'>
+ sage: H.category()
+ Category of homsets of topological spaces
+ sage: latex(H)
+ \mathrm{Hom}\left(M,N\right)
+ sage: H.domain()
+ 2-dimensional topological manifold M
+ sage: H.codomain()
+ 3-dimensional topological manifold N
+
+ An element of ``H`` is a continuous map from ``M`` to ``N``::
+
+ sage: H.Element
+ <class 'sage.manifolds.continuous_map.ContinuousMap'>
+ sage: f = H.an_element() ; f
+ Continuous map from the 2-dimensional topological manifold M to the
+ 3-dimensional topological manifold N
+ sage: f.display()
+ M --> N
+ (x, y) |--> (u, v, w) = (0, 0, 0)
+
+ The test suite is passed::
+
+ sage: TestSuite(H).run()
+
+ When the codomain coincides with the domain, the homset is a set of
+ *endomorphisms* in the category of topological manifolds::
+
+ sage: E = Hom(M, M) ; E
+ Set of Morphisms from 2-dimensional topological manifold M to
+ 2-dimensional topological manifold M in Category of manifolds over
+ Real Field with 53 bits of precision
+ sage: E.category()
+ Category of endsets of topological spaces
+ sage: E.is_endomorphism_set()
+ True
+ sage: E is End(M)
+ True
+
+ In this case, the homset is a monoid for the law of morphism composition::
+
+ sage: E in Monoids()
+ True
+
+ This was of course not the case of ``H = Hom(M, N)``::
+
+ sage: H in Monoids()
+ False
+
+ The identity element of the monoid is of course the identity map of ``M``::
+
+ sage: E.one()
+ Identity map Id_M of the 2-dimensional topological manifold M
+ sage: E.one() is M.identity_map()
+ True
+ sage: E.one().display()
+ Id_M: M --> M
+ (x, y) |--> (x, y)
+
+ The test suite is passed by ``E``::
+
+ sage: TestSuite(E).run()
+
+ This test suite includes more tests than in the case of ``H``, since ``E``
+ has some extra structure (monoid).
+
+ """
+
+ Element = ContinuousMap
+
+ def __init__(self, domain, codomain, name=None, latex_name=None):
+ r"""
+ TESTS::
+
+ sage: M = Manifold(2, 'M', structure='topological')
+ sage: X.<x,y> = M.chart()
+ sage: N = Manifold(3, 'N', structure='topological')
+ sage: Y.<u,v,w> = N.chart()
+ sage: H = Hom(M, N) ; H
+ Set of Morphisms from 2-dimensional topological manifold M to
+ 3-dimensional topological manifold N in Category of manifolds
+ over Real Field with 53 bits of precision
+ sage: TestSuite(H).run()
+
+ Test for an endomorphism set::
+
+ sage: E = Hom(M, M) ; E
+ Set of Morphisms from 2-dimensional topological manifold M to
+ 2-dimensional topological manifold M in Category of manifolds over
+ Real Field with 53 bits of precision
+ sage: TestSuite(E).run()
+
+ """
+ from sage.manifolds.manifold import TopologicalManifold
+ if not isinstance(domain, TopologicalManifold):
+ raise TypeError("domain = {} is not an ".format(domain) +
+ "instance of TopologicalManifold")
+ if not isinstance(codomain, TopologicalManifold):
+ raise TypeError("codomain = {} is not an ".format(codomain) +
+ "instance of TopologicalManifold")
+ Homset.__init__(self, domain, codomain)
+ if name is None:
+ self._name = "Hom(" + domain._name + "," + codomain._name + ")"
+ else:
+ self._name = name
+ if latex_name is None:
+ self._latex_name = \
+ r"\mathrm{Hom}\left(" + domain._latex_name + "," + \
+ codomain._latex_name + r"\right)"
+ else:
+ self._latex_name = latex_name
+ self._one = None # to be set by self.one() if self is a set of
+ # endomorphisms (codomain = domain)
+
+ def _latex_(self):
+ r"""
+ LaTeX representation of the object.
+
+ EXAMPLE::
+
+ sage: M = Manifold(2, 'M', structure='topological')
+ sage: X.<x,y> = M.chart()
+ sage: N = Manifold(3, 'N', structure='topological')
+ sage: H = Hom(M, N)
+ sage: H._latex_()
+ '\\mathrm{Hom}\\left(M,N\\right)'
+
+ """
+ if self._latex_name is None:
+ return r'\mbox{' + str(self) + r'}'
+ else:
+ return self._latex_name
+
+ #### Parent methods ####
+
+ def _element_constructor_(self, coord_functions, name=None, latex_name=None,
+ is_isomorphism=False, is_identity=False):
+ r"""
+ Construct an element of the homset, i.e. a continuous map
+ M --> N, where M is the domain of the homset and N its codomain.
+
+ INPUT:
+
+ - ``coord_functions`` -- a dictionary of the coordinate expressions
+ (as lists (or tuples) of the coordinates of the image expressed in
+ terms of the coordinates of the considered point) with the pairs of
+ charts (chart1, chart2) as keys (chart1 being a chart on `M` and
+ chart2 a chart on `N`). If the dimension of the arrival manifold
+ is 1, a single coordinate expression can be passed instead of a tuple
+ with a single element
+ - ``name`` -- (default: ``None``) name given to the continuous map
+ - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the
+ continuous map; if ``None``, the LaTeX symbol is set to ``name``
+ - ``is_isomorphism`` -- (default: ``False``) determines whether the
+ constructed object is a isomorphism (i.e. a homeomorphism); if set to
+ ``True``, then the manifolds `M` and `N` must have the same dimension.
+ - ``is_identity`` -- (default: ``False``) determines whether the
+ constructed object is the identity map; if set to ``True``,
+ then `N` must be `M` and the entry ``coord_functions`` is not used.
+
+ .. NOTE::
+
+ If the information passed by means of the argument
+ ``coord_functions`` is not sufficient to fully specify the
+ continuous map, further coordinate expressions, in other charts,
+ can be subsequently added by means of the method :meth:`add_expr`
+
+ OUTPUT:
+
+ - instance of
+ :class:`~sage.manifolds.continuous_map.ContinuousMap`
+
+ EXAMPLES::
+
+ sage: M = Manifold(2, 'M', structure='topological')
+ sage: X.<x,y> = M.chart()
+ sage: N = Manifold(3, 'N', structure='topological')
+ sage: Y.<u,v,w> = N.chart()
+ sage: H = Hom(M, N)
+ sage: f = H({(X, Y): [x+y, x-y, x*y]}, name='f'); f
+ Continuous map f from the 2-dimensional topological manifold M to
+ the 3-dimensional topological manifold N
+ sage: f.display()
+ f: M --> N
+ (x, y) |--> (u, v, w) = (x + y, x - y, x*y)
+ sage: id = Hom(M, M)({}, is_identity=True)
+ sage: id
+ Identity map Id_M of the 2-dimensional topological manifold M
+ sage: id.display()
+ Id_M: M --> M
+ (x, y) |--> (x, y)
+
+ """
+ # Standard construction
+ return self.element_class(self, coord_functions=coord_functions,
+ name=name, latex_name=latex_name,
+ is_isomorphism=is_isomorphism,
+ is_identity=is_identity)
+
+ def _an_element_(self):
+ r"""
+ Construct some element.
+
+ OUTPUT:
+
+ - instance of
+ :class:`~sage.manifolds.continuous_map.ContinuousMap`
+
+ EXAMPLE::
+
+ sage: M = Manifold(2, 'M', structure='topological')
+ sage: X.<x,y> = M.chart()
+ sage: N = Manifold(3, 'N', structure='topological')
+ sage: Y.<u,v,w> = N.chart()
+ sage: H = Hom(M,N)
+ sage: f = H._an_element_() ; f
+ Continuous map from the 2-dimensional topological manifold M to the
+ 3-dimensional topological manifold N
+ sage: f.display()
+ M --> N
+ (x, y) |--> (u, v, w) = (0, 0, 0)
+ sage: p = M((-2,3)) ; p
+ Point on the 2-dimensional topological manifold M
+ sage: f(p)
+ Point on the 3-dimensional topological manifold N
+ sage: f(p).coord(Y)
+ (0, 0, 0)
+ sage: TestSuite(f).run()
+
+ """
+ dom = self.domain()
+ codom = self.codomain()
+ # A constant map is constructed:
+ chart2 = codom.default_chart()
+ target_point = chart2.domain().an_element()
+ target_coord = target_point.coord(chart2)
+ coord_functions = {}
+ for chart in dom.atlas():
+ coord_functions[(chart, chart2)] = target_coord
+ return self.element_class(self, coord_functions=coord_functions)
+
+ def _coerce_map_from_(self, other):
+ r"""
+ Determine whether coercion to self exists from other parent.
+
+ EXAMPLE::
+
+ sage: M = Manifold(2, 'M', structure='topological')
+ sage: X.<x,y> = M.chart()
+ sage: N = Manifold(3, 'N', structure='topological')
+ sage: Y.<u,v,w> = N.chart()
+ sage: H = Hom(M,N)
+ sage: H._coerce_map_from_(ZZ)
+ False
+ sage: H._coerce_map_from_(M)
+ False
+ sage: H._coerce_map_from_(N)
+ False
+
+ """
+ #!# for the time being:
+ return False
+
+ #!# check
+ def __call__(self, *args, **kwds):
+ r"""
+ To bypass Homset.__call__, enforcing Parent.__call__ instead.
+
+ EXAMPLE::
+
+ sage: M = Manifold(2, 'M', structure='topological')
+ sage: X.<x,y> = M.chart()
+ sage: N = Manifold(3, 'N', structure='topological')
+ sage: Y.<u,v,w> = N.chart()
+ sage: H = Hom(M,N)
+ sage: f = H.__call__({(X, Y): [x+y, x-y, x*y]}, name='f') ; f
+ Continuous map f from the 2-dimensional topological manifold M to
+ the 3-dimensional topological manifold N
+ sage: f.display()
+ f: M --> N
+ (x, y) |--> (u, v, w) = (x + y, x - y, x*y)
+
+ """
+ return Parent.__call__(self, *args, **kwds)
+
+ #### End of parent methods ####
+
+ #### Monoid methods (case of an endomorphism set) ####
+
+ def one(self):
+ r"""
+ Return the identity element of the homset considered as a monoid (case
+ of a set of endomorphisms).
+
+ This applies only when the codomain of the homset is equal to its
+ domain, i.e. when the homset is of the type `\mathrm{Hom}(M,M)`.
+ Indeed, `\mathrm{Hom}(M,M)` equipped with the law of morphisms
+ composition is a monoid, whose identity element is nothing but the
+ identity map of `M`.
+
+ OUTPUT:
+
+ - the identity map of `M`, as an instance of
+ :class:`~sage.manifolds.continuous_map.ContinuousMap`
+
+ EXAMPLE:
+
+ The identity map of a 2-dimensional manifold::
+
+ sage: M = Manifold(2, 'M', structure='topological')
+ sage: X.<x,y> = M.chart()
+ sage: H = Hom(M, M) ; H
+ Set of Morphisms from 2-dimensional topological manifold M to
+ 2-dimensional topological manifold M in Category of manifolds over
+ Real Field with 53 bits of precision
+ sage: H in Monoids()
+ True
+ sage: H.one()
+ Identity map Id_M of the 2-dimensional topological manifold M
+ sage: H.one().parent() is H
+ True
+ sage: H.one().display()
+ Id_M: M --> M
+ (x, y) |--> (x, y)
+
+ The identity map is cached::
+
+ sage: H.one() is H.one()
+ True
+
+ If the homset is not a set of endomorphisms, the identity element is
+ meaningless::
+
+ sage: N = Manifold(3, 'N', structure='topological')
+ sage: Y.<u,v,w> = N.chart()
+ sage: Hom(M, N).one()
+ Traceback (most recent call last):
+ ...
+ TypeError: the Set of Morphisms from 2-dimensional topological
+ manifold M to 3-dimensional topological manifold N in Category of
+ manifolds over Real Field with 53 bits of precision is not a
+ monoid
+
+ """
+ if self._one is None:
+ if self.codomain() != self.domain():
+ raise TypeError("the {} is not a monoid".format(self))
+ self._one = self.element_class(self, is_identity=True)
+ return self._one
+
+ #### End of monoid methods ####
diff --git a/src/sage/manifolds/point.py b/src/sage/manifolds/point.py
index 2f35145..42d5ab2 100644
--- a/src/sage/manifolds/point.py
+++ b/src/sage/manifolds/point.py
@@ -652,3 +652,231 @@ class ManifoldPoint(Element):
"""
return hash(self.parent().manifold())
+
+
+ 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"""
+ For real manifolds, 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.parent()``
+ - ``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 = Manifold(2, 'M', structure='topological')
+ 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(max_range=4) # plot of the coordinate grid
+ sage: show(g+gX) # display of the point atop the coordinate grid
+
+ .. PLOT::
+
+ M = Manifold(2, 'M', structure='topological')
+ X = M.chart('x y'); x,y = X[:]
+ p = M.point((1,3), name='p')
+ g = p.plot(X)
+ gX = X.plot(max_range=4)
+ sphinx_plot(g+gX)
+
+ 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.3)
+ sage: show(g+gX)
+
+ .. PLOT::
+
+ M = Manifold(2, 'M', structure='topological')
+ X = M.chart('x y'); x,y = X[:]
+ p = M.point((1,3), name='p')
+ g = p.plot(chart=X, size=40, color='green', label='$P$', \
+ label_color='blue', fontsize=20, label_offset=0.3)
+ gX = X.plot(max_range=4)
+ sphinx_plot(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}, label_offset=0.2)
+ sage: show(g+gX+gq)
+
+ .. PLOT::
+
+ M = Manifold(2, 'M', structure='topological')
+ X = M.chart('x y'); x,y = X[:]
+ p = M.point((1,3), name='p')
+ g = p.plot(chart=X, size=40, color='green', label='$P$', \
+ label_color='blue', fontsize=20, label_offset=0.3)
+ var('a')
+ q = M.point((a,2*a), name='q')
+ gq = q.plot(parameters={a:-2}, label_offset=0.2)
+ gX = X.plot(max_range=4)
+ sphinx_plot(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 = Manifold(3, 'M', structure='topological')
+ 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 = Manifold(2, 'S^2', structure='topological')
+ 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 = Manifold(4, 'M', structure='topological')
+ 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), label_offset=0.4) # 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), label_offset=0.4) # 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), label_offset=0.4) # the coordinates t and x are skipped
+ sage: gX = X.plot(X, ambient_coords=(y,z))
+ sage: show(g+gX) # 2D plot
+
+ .. PLOT::
+
+ M = Manifold(4, 'M', structure='topological')
+ X = M.chart('t x y z'); t,x,y,z = X[:]
+ p = M.point((1,2,3,4), name='p')
+ g = p.plot(X, ambient_coords=(y,z), label_offset=0.4)
+ gX = X.plot(X, ambient_coords=(y,z))
+ sphinx_plot(g+gX)
+
+ """
+ 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
+ if self._manifold.base_field_type() != 'real':
+ raise NotImplementedError('plot of points on manifolds over ' +
+ 'fields different from R is not implemented')
+ # The ambient chart:
+ if chart is None:
+ chart = self.parent().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/structure.py b/src/sage/manifolds/structure.py
index 735410a..09a5f2d 100644
--- a/src/sage/manifolds/structure.py
+++ b/src/sage/manifolds/structure.py
@@ -22,6 +22,7 @@ AUTHORS:
from sage.misc.fast_methods import Singleton
from sage.manifolds.chart import Chart, RealChart
from sage.manifolds.scalarfield_algebra import ScalarFieldAlgebra
+from sage.manifolds.manifold_homset import TopologicalManifoldHomset
# This is a slight abuse by making this a Singleton, but there is no
# need to have different copies of this object.
@@ -32,6 +33,7 @@ class TopologicalStructure(Singleton):
chart = Chart
name = "topological"
scalar_field_algebra = ScalarFieldAlgebra
+ homset = TopologicalManifoldHomset
def subcategory(self, cat):
"""
@@ -55,6 +57,7 @@ class RealTopologicalStructure(Singleton):
chart = RealChart
name = "topological"
scalar_field_algebra = ScalarFieldAlgebra
+ homset = TopologicalManifoldHomset
def subcategory(self, cat):
"""
diff --git a/src/sage/manifolds/utilities.py b/src/sage/manifolds/utilities.py
index 37731aa..7e82c3c 100644
--- a/src/sage/manifolds/utilities.py
+++ b/src/sage/manifolds/utilities.py
@@ -913,3 +913,57 @@ def omit_function_args(status):
if not isinstance(status, bool):
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.
+
+ EXAMPLE::
+
+ sage: g = sphere()
+ sage: print g
+ Graphics3d Object
+ sage: show(g) # no axes labels
+ sage: from sage.manifolds.utilities import set_axes_labels
+ sage: ga = set_axes_labels(g, 'X', 'Y', 'Z', color='red')
+ sage: print ga
+ Graphics3d Object
+ sage: show(ga) # the 3D frame has now axes labels
+
+ """
+ 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