def _graphics(self, plot_curve, ambient_coords, thickness=1, aspect_ratio='automatic', color='red', style='-', label_axes=True): r""" Plot a 2D or 3D curve in a Cartesian graph with axes labeled by the ambient coordinates; it is invoked by the methods :meth:`plot` of :class:`~sage.manifolds.differentiable.curve.DifferentiableCurve`, and its subclasses (:class:`~sage.manifolds.differentiable.integrated_curve.IntegratedCurve`, :class:`~sage.manifolds.differentiable.integrated_curve.IntegratedAutoparallelCurve`, and :class:`~sage.manifolds.differentiable.integrated_curve.IntegratedGeodesic`). TESTS:: sage: M = Manifold(2, 'R^2') sage: X.<x,y> = M.chart() sage: R.<t> = RealLine() sage: c = M.curve([cos(t), sin(t)], (t, 0, 2*pi), name='c') sage: graph = c._graphics([[1,2], [3,4]], [x,y]) sage: graph._objects[0].xdata == [1,3] True sage: graph._objects[0].ydata == [2,4] True sage: graph._objects[0]._options['thickness'] == 1 True sage: graph._extra_kwds['aspect_ratio'] == 'automatic' True sage: graph._objects[0]._options['rgbcolor'] == 'red' True sage: graph._objects[0]._options['linestyle'] == '-' True sage: l = [r'$'+latex(x)+r'$', r'$'+latex(y)+r'$'] sage: graph._extra_kwds['axes_labels'] == l True """ from sage.plot.graphics import Graphics from sage.plot.line import line from sage.manifolds.utilities import set_axes_labels # # The plot # n_pc = len(ambient_coords) resu = Graphics() resu += line(plot_curve, color=color, linestyle=style, thickness=thickness) if n_pc == 2: # 2D graphic resu.set_aspect_ratio(aspect_ratio) if label_axes: # We update the dictionary _extra_kwds (options to be passed # to show()), instead of using the method # Graphics.axes_labels() since the latter is not robust w.r.t. # graph addition resu._extra_kwds['axes_labels'] = [r'$'+latex(pc)+r'$' for pc in ambient_coords] else: # 3D graphic if aspect_ratio == 'automatic': aspect_ratio = 1 resu.aspect_ratio(aspect_ratio) if label_axes: labels = [str(pc) for pc in ambient_coords] resu = set_axes_labels(resu, *labels) return resu
def plot(self, chart=None, ambient_coords=None, mapping=None, prange=None, include_end_point=(True, True), end_point_offset=(0.001, 0.001), parameters=None, color='red', style='-', label_axes=True, **kwds): r""" Plot the current curve in a Cartesian graph based on the coordinates of some ambient chart. The curve is drawn in terms of two (2D graphics) or three (3D graphics) coordinates of a given chart, called hereafter the *ambient chart*. The ambient chart's domain must overlap with the curve's codomain or with the codomain of the composite curve `\Phi\circ c`, where `c` is the current curve and `\Phi` some manifold differential map (argument ``mapping`` below). INPUT: - ``chart`` -- (default: ``None``) the ambient chart (see above); if ``None``, the default chart of the codomain of the curve (or of the curve composed with `\Phi`) is used - ``ambient_coords`` -- (default: ``None``) tuple containing the 2 or 3 coordinates of the ambient chart in terms of which the plot is performed; if ``None``, all the coordinates of the ambient chart are considered - ``mapping`` -- (default: ``None``) differentiable mapping `\Phi` (instance of :class:`~sage.manifolds.differentiable.diff_map.DiffMap`) providing the link between the curve and the ambient chart ``chart`` (cf. above); if ``None``, the ambient chart is supposed to be defined on the codomain of the curve. - ``prange`` -- (default: ``None``) range of the curve parameter for the plot; if ``None``, the entire parameter range declared during the curve construction is considered (with -Infinity replaced by ``-max_range`` and +Infinity by ``max_range``) - ``include_end_point`` -- (default: ``(True, True)``) determines whether the end points of ``prange`` are included in the plot - ``end_point_offset`` -- (default: ``(0.001, 0.001)``) offsets from the end points when they are not included in the plot: if ``include_end_point[0] == False``, the minimal value of the curve parameter used for the plot is ``prange[0] + end_point_offset[0]``, while if ``include_end_point[1] == False``, the maximal value is ``prange[1] - end_point_offset[1]``. - ``max_range`` -- (default: 8) numerical value substituted to +Infinity if the latter is the upper bound of the parameter range; similarly ``-max_range`` is the numerical valued substituted for -Infinity - ``parameters`` -- (default: ``None``) dictionary giving the numerical values of the parameters that may appear in the coordinate expression of the curve - ``color`` -- (default: 'red') color of the drawn curve - ``style`` -- (default: '-') color of the drawn curve; NB: ``style`` is effective only for 2D plots - ``thickness`` -- (default: 1) thickness of the drawn curve - ``plot_points`` -- (default: 75) number of points to plot the curve - ``label_axes`` -- (default: ``True``) boolean determining whether the labels of the coordinate axes of ``chart`` shall be added to the graph; can be set to ``False`` if the graph is 3D and must be superposed with another graph. - ``aspect_ratio`` -- (default: ``'automatic'``) aspect ratio of the plot; the default value (``'automatic'``) applies only for 2D plots; for 3D plots, the default value is ``1`` instead OUTPUT: - a graphic object, either an instance of :class:`~sage.plot.graphics.Graphics` for a 2D plot (i.e. based on 2 coordinates of ``chart``) or an instance of :class:`~sage.plot.plot3d.base.Graphics3d` for a 3D plot (i.e. based on 3 coordinates of ``chart``) EXAMPLES: Plot of the lemniscate of Gerono:: sage: R2 = Manifold(2, 'R^2') sage: X.<x,y> = R2.chart() sage: R.<t> = RealLine() sage: c = R2.curve([sin(t), sin(2*t)/2], (t, 0, 2*pi), name='c') sage: c.plot() # 2D plot Graphics object consisting of 1 graphics primitive .. PLOT:: R2 = Manifold(2, 'R^2') X = R2.chart('x y') t = RealLine().canonical_coordinate() c = R2.curve([sin(t), sin(2*t)/2], (t, 0, 2*pi), name='c') g = c.plot() sphinx_plot(g) Plot for a subinterval of the curve's domain:: sage: c.plot(prange=(0,pi)) Graphics object consisting of 1 graphics primitive .. PLOT:: R2 = Manifold(2, 'R^2') X = R2.chart('x y') t = RealLine().canonical_coordinate() c = R2.curve([sin(t), sin(2*t)/2], (t, 0, 2*pi), name='c') g = c.plot(prange=(0,pi)) sphinx_plot(g) Plot with various options:: sage: c.plot(color='green', style=':', thickness=3, aspect_ratio=1) Graphics object consisting of 1 graphics primitive .. PLOT:: R2 = Manifold(2, 'R^2') X = R2.chart('x y') t = RealLine().canonical_coordinate() c = R2.curve([sin(t), sin(2*t)/2], (t, 0, 2*pi), name='c') g = c.plot(color='green', style=':', thickness=3, aspect_ratio=1) sphinx_plot(g) Plot via a mapping to another manifold: loxodrome of a sphere viewed in `\RR^3`:: sage: S2 = Manifold(2, 'S^2') sage: U = S2.open_subset('U') sage: XS.<th,ph> = U.chart(r'th:(0,pi):\theta ph:(0,2*pi):\phi') sage: R3 = Manifold(3, 'R^3') sage: X3.<x,y,z> = R3.chart() sage: F = S2.diff_map(R3, {(XS, X3): [sin(th)*cos(ph), ....: sin(th)*sin(ph), cos(th)]}, name='F') sage: F.display() F: S^2 --> R^3 on U: (th, ph) |--> (x, y, z) = (cos(ph)*sin(th), sin(ph)*sin(th), cos(th)) sage: c = S2.curve([2*atan(exp(-t/10)), t], (t, -oo, +oo), name='c') sage: graph_c = c.plot(mapping=F, max_range=40, ....: plot_points=200, thickness=2, label_axes=False) # 3D plot sage: graph_S2 = XS.plot(X3, mapping=F, number_values=11, color='black') # plot of the sphere sage: show(graph_c + graph_S2) # the loxodrome + the sphere .. PLOT:: S2 = Manifold(2, 'S^2') U = S2.open_subset('U') XS = U.chart(r'th:(0,pi):\theta ph:(0,2*pi):\phi') th, ph = XS[:] R3 = Manifold(3, 'R^3') X3 = R3.chart('x y z') F = S2.diff_map(R3, {(XS, X3): [sin(th)*cos(ph), sin(th)*sin(ph), cos(th)]}, name='F') t = RealLine().canonical_coordinate() c = S2.curve([2*atan(exp(-t/10)), t], (t, -oo, +oo), name='c') graph_c = c.plot(mapping=F, max_range=40, plot_points=200, thickness=2, label_axes=False) graph_S2 = XS.plot(X3, mapping=F, number_values=11, color='black') sphinx_plot(graph_c + graph_S2) Example of use of the argument ``parameters``: we define a curve with some symbolic parameters ``a`` and ``b``:: sage: a, b = var('a b') sage: c = R2.curve([a*cos(t) + b, a*sin(t)], (t, 0, 2*pi), name='c') To make a plot, we set spectific values for ``a`` and ``b`` by means of the Python dictionary ``parameters``:: sage: c.plot(parameters={a: 2, b: -3}, aspect_ratio=1) Graphics object consisting of 1 graphics primitive .. PLOT:: R2 = Manifold(2, 'R^2') X = R2.chart('x y') t = RealLine().canonical_coordinate() a, b = var('a b') c = R2.curve([a*cos(t) + b, a*sin(t)], (t, 0, 2*pi), name='c') g = c.plot(parameters={a: 2, b: -3}, aspect_ratio=1) sphinx_plot(g) """ from sage.rings.infinity import Infinity from sage.misc.functional import numerical_approx from sage.plot.graphics import Graphics from sage.plot.line import line from sage.manifolds.chart import RealChart from sage.manifolds.utilities import set_axes_labels # # Get the @options from kwds # thickness = kwds.pop('thickness') plot_points = kwds.pop('plot_points') max_range = kwds.pop('max_range') aspect_ratio = kwds.pop('aspect_ratio') # # The "effective" curve to be plotted # if mapping is None: eff_curve = self else: eff_curve = mapping.restrict(self.codomain()) * self # # The chart w.r.t. which the curve is plotted # if chart is None: chart = eff_curve._codomain.default_chart() elif not isinstance(chart, RealChart): raise TypeError("{} is not a real chart".format(chart)) # # Coordinates of the above chart w.r.t. which the curve is plotted # if ambient_coords is None: ambient_coords = chart[:] # all chart coordinates are used n_pc = len(ambient_coords) if n_pc != 2 and n_pc != 3: raise ValueError("the number of coordinates involved in the " + "plot must be either 2 or 3, not {}".format(n_pc)) # indices of plot coordinates ind_pc = [chart[:].index(pc) for pc in ambient_coords] # # Parameter range for the plot # if prange is None: prange = (self._domain.lower_bound(), self._domain.upper_bound()) elif not isinstance(prange, (tuple, list)): raise TypeError("{} is neither a tuple nor a list".format(prange)) elif len(prange) != 2: raise ValueError("the argument prange must be a tuple/list " + "of 2 elements") tmin = prange[0] tmax = prange[1] if tmin == -Infinity: tmin = -max_range elif not include_end_point[0]: tmin = tmin + end_point_offset[0] if tmax == Infinity: tmax = max_range elif not include_end_point[1]: tmax = tmax - end_point_offset[1] tmin = numerical_approx(tmin) tmax = numerical_approx(tmax) # # The coordinate expression of the effective curve # canon_chart = self._domain.canonical_chart() transf = None for chart_pair in eff_curve._coord_expression: if chart_pair == (canon_chart, chart): transf = eff_curve._coord_expression[chart_pair] break else: # Search for a subchart for chart_pair in eff_curve._coord_expression: for schart in chart._subcharts: if chart_pair == (canon_chart, schart): transf = eff_curve._coord_expression[chart_pair] if transf is None: raise ValueError("No expression has been found for " + "{} in terms of {}".format(self, chart)) # # List of points for the plot curve # plot_curve = [] dt = (tmax - tmin) / (plot_points - 1) t = tmin if parameters is None: for i in range(plot_points): x = transf(t, simplify=False) plot_curve.append([numerical_approx(x[j]) for j in ind_pc]) t += dt else: for i in range(plot_points): x = transf(t, simplify=False) plot_curve.append([ numerical_approx(x[j].substitute(parameters)) for j in ind_pc ]) t += dt # # The plot # resu = Graphics() resu += line(plot_curve, color=color, linestyle=style, thickness=thickness) if n_pc == 2: # 2D graphic resu.set_aspect_ratio(aspect_ratio) if label_axes: # We update the dictionary _extra_kwds (options to be passed # to show()), instead of using the method # Graphics.axes_labels() since the latter is not robust w.r.t. # graph addition resu._extra_kwds['axes_labels'] = [ r'$' + latex(pc) + r'$' for pc in ambient_coords ] else: # 3D graphic if aspect_ratio == 'automatic': aspect_ratio = 1 resu.aspect_ratio(aspect_ratio) if label_axes: labels = [str(pc) for pc in ambient_coords] resu = set_axes_labels(resu, *labels) return resu
def plot(self, chart=None, ambient_coords=None, mapping=None, chart_domain=None, fixed_coords=None, ranges=None, number_values=None, steps=None, parameters=None, label_axes=True, **extra_options): r""" Plot the vector field in a Cartesian graph based on the coordinates of some ambient chart. The vector field is drawn in terms of two (2D graphics) or three (3D graphics) coordinates of a given chart, called hereafter the *ambient chart*. The vector field's base points `p` (or their images `\Phi(p)` by some differentiable mapping `\Phi`) must lie in the ambient chart's domain. INPUT: - ``chart`` -- (default: ``None``) the ambient chart (see above); if ``None``, the default chart of the vector field's domain is used - ``ambient_coords`` -- (default: ``None``) tuple containing the 2 or 3 coordinates of the ambient chart in terms of which the plot is performed; if ``None``, all the coordinates of the ambient chart are considered - ``mapping`` -- :class:`~sage.manifolds.differentiable.diff_map.DiffMap` (default: ``None``); differentiable map `\Phi` providing the link between the vector field's domain and the ambient chart ``chart``; if ``None``, the identity map is assumed - ``chart_domain`` -- (default: ``None``) chart on the vector field's domain to define the points at which vector arrows are to be plotted; if ``None``, the default chart of the vector field's domain is used - ``fixed_coords`` -- (default: ``None``) dictionary with keys the coordinates of ``chart_domain`` that are kept fixed and with values the value of these coordinates; if ``None``, all the coordinates of ``chart_domain`` are used - ``ranges`` -- (default: ``None``) dictionary with keys the coordinates of ``chart_domain`` to be used and values tuples ``(x_min, x_max)`` specifying the coordinate range for the plot; if ``None``, the entire coordinate range declared during the construction of ``chart_domain`` is considered (with ``-Infinity`` replaced by ``-max_range`` and ``+Infinity`` by ``max_range``) - ``number_values`` -- (default: ``None``) either an integer or a dictionary with keys the coordinates of ``chart_domain`` to be used and values the number of values of the coordinate for sampling the part of the vector field's domain involved in the plot ; if ``number_values`` is a single integer, it represents the number of values for all coordinates; if ``number_values`` is ``None``, it is set to 9 for a 2D plot and to 5 for a 3D plot - ``steps`` -- (default: ``None``) dictionary with keys the coordinates of ``chart_domain`` to be used and values the step between each constant value of the coordinate; if ``None``, the step is computed from the coordinate range (specified in ``ranges``) and ``number_values``; on the contrary, if the step is provided for some coordinate, the corresponding number of values is deduced from it and the coordinate range - ``parameters`` -- (default: ``None``) dictionary giving the numerical values of the parameters that may appear in the coordinate expression of the vector field (see example below) - ``label_axes`` -- (default: ``True``) boolean determining whether the labels of the coordinate axes of ``chart`` shall be added to the graph; can be set to ``False`` if the graph is 3D and must be superposed with another graph - ``color`` -- (default: 'blue') color of the arrows representing the vectors - ``max_range`` -- (default: 8) numerical value substituted to ``+Infinity`` if the latter is the upper bound of the range of a coordinate for which the plot is performed over the entire coordinate range (i.e. for which no specific plot range has been set in ``ranges``); similarly ``-max_range`` is the numerical valued substituted for ``-Infinity`` - ``scale`` -- (default: 1) value by which the lengths of the arrows representing the vectors is multiplied - ``**extra_options`` -- extra options for the arrow plot, like ``linestyle``, ``width`` or ``arrowsize`` (see :func:`~sage.plot.arrow.arrow2d` and :func:`~sage.plot.plot3d.shapes.arrow3d` for details) OUTPUT: - a graphic object, either an instance of :class:`~sage.plot.graphics.Graphics` for a 2D plot (i.e. based on 2 coordinates of ``chart``) or an instance of :class:`~sage.plot.plot3d.base.Graphics3d` for a 3D plot (i.e. based on 3 coordinates of ``chart``) EXAMPLES: Plot of a vector field on a 2-dimensional manifold:: sage: M = Manifold(2, 'M') sage: X.<x,y> = M.chart() sage: v = M.vector_field(name='v') sage: v[:] = -y, x ; v.display() v = -y d/dx + x d/dy sage: v.plot() Graphics object consisting of 80 graphics primitives .. PLOT:: M = Manifold(2, 'M') X = M.chart('x y'); x, y = X[:] v = M.vector_field(name='v'); v[:] = -y, x g = v.plot() sphinx_plot(g) Plot with various options:: sage: v.plot(scale=0.5, color='green', linestyle='--', width=1, ....: arrowsize=6) Graphics object consisting of 80 graphics primitives .. PLOT:: M = Manifold(2, 'M') X = M.chart('x y'); x, y = X[:] v = M.vector_field(name='v'); v[:] = -y, x g = v.plot(scale=0.5, color='green', linestyle='--', width=1, arrowsize=6) sphinx_plot(g) :: sage: v.plot(max_range=4, number_values=5, scale=0.5) Graphics object consisting of 24 graphics primitives .. PLOT:: M = Manifold(2, 'M') X = M.chart('x y'); x, y = X[:] v = M.vector_field(name='v'); v[:] = -y, x g = v.plot(max_range=4, number_values=5, scale=0.5) sphinx_plot(g) Plot using parallel computation:: sage: Parallelism().set(nproc=2) sage: v.plot(scale=0.5, number_values=10, linestyle='--', width=1, ....: arrowsize=6) Graphics object consisting of 100 graphics primitives .. PLOT:: M = Manifold(2, 'M') X = M.chart('x y'); x, y = X[:] v = M.vector_field(name='v'); v[:] = -y, x g = v.plot(scale=0.5, number_values=10, linestyle='--', width=1, arrowsize=6) sphinx_plot(g) :: sage: Parallelism().set(nproc=1) # switch off parallelization Plots along a line of fixed coordinate:: sage: v.plot(fixed_coords={x: -2}) Graphics object consisting of 9 graphics primitives .. PLOT:: M = Manifold(2, 'M') X = M.chart('x y'); x, y = X[:] v = M.vector_field(name='v'); v[:] = -y, x g = v.plot(fixed_coords={x: -2}) sphinx_plot(g) :: sage: v.plot(fixed_coords={y: 1}) Graphics object consisting of 9 graphics primitives .. PLOT:: M = Manifold(2, 'M') X = M.chart('x y'); x, y = X[:] v = M.vector_field(name='v'); v[:] = -y, x g = v.plot(fixed_coords={y: 1}) sphinx_plot(g) Let us now consider a vector field on a 4-dimensional manifold:: sage: M = Manifold(4, 'M') sage: X.<t,x,y,z> = M.chart() sage: v = M.vector_field(name='v') sage: v[:] = (t/8)^2, -t*y/4, t*x/4, t*z/4 ; v.display() v = 1/64*t^2 d/dt - 1/4*t*y d/dx + 1/4*t*x d/dy + 1/4*t*z d/dz We cannot make a 4D plot directly:: sage: v.plot() Traceback (most recent call last): ... ValueError: the number of ambient coordinates must be either 2 or 3, not 4 Rather, we have to select some coordinates for the plot, via the argument ``ambient_coords``. For instance, for a 3D plot:: sage: v.plot(ambient_coords=(x, y, z), fixed_coords={t: 1}, # long time ....: number_values=4) Graphics3d Object .. PLOT:: M = Manifold(4, 'M') X = M.chart('t x y z') ; t,x,y,z = X[:] v = M.vector_field(name='v') v[:] = (t/8)**2, -t*y/4, t*x/4, t*z/4 sphinx_plot(v.plot(ambient_coords=(x, y, z), fixed_coords={t: 1}, number_values=4)) :: sage: v.plot(ambient_coords=(x, y, t), fixed_coords={z: 0}, # long time ....: ranges={x: (-2,2), y: (-2,2), t: (-1, 4)}, ....: number_values=4) Graphics3d Object .. PLOT:: M = Manifold(4, 'M') X = M.chart('t x y z'); t,x,y,z = X[:] v = M.vector_field(name='v') v[:] = (t/8)**2, -t*y/4, t*x/4, t*z/4 sphinx_plot(v.plot(ambient_coords=(x, y, t), fixed_coords={z: 0}, ranges={x: (-2,2), y: (-2,2), t: (-1, 4)}, number_values=4)) or, for a 2D plot:: sage: v.plot(ambient_coords=(x, y), fixed_coords={t: 1, z: 0}) # long time Graphics object consisting of 80 graphics primitives .. PLOT:: M = Manifold(4, 'M') X = M.chart('t x y z'); t,x,y,z = X[:] v = M.vector_field(name='v') v[:] = (t/8)**2, -t*y/4, t*x/4, t*z/4 g = v.plot(ambient_coords=(x, y), fixed_coords={t: 1, z: 0}) sphinx_plot(g) :: sage: v.plot(ambient_coords=(x, t), fixed_coords={y: 1, z: 0}) # long time Graphics object consisting of 72 graphics primitives .. PLOT:: M = Manifold(4, 'M') X = M.chart('t x y z'); t,x,y,z = X[:] v = M.vector_field(name='v') v[:] = v[:] = (t/8)**2, -t*y/4, t*x/4, t*z/4 g = v.plot(ambient_coords=(x, t), fixed_coords={y: 1, z: 0}) sphinx_plot(g) An example of plot via a differential mapping: plot of a vector field tangent to a 2-sphere viewed in `\RR^3`:: sage: S2 = Manifold(2, 'S^2') sage: U = S2.open_subset('U') # the open set covered by spherical coord. sage: XS.<th,ph> = U.chart(r'th:(0,pi):\theta ph:(0,2*pi):\phi') sage: R3 = Manifold(3, 'R^3') sage: X3.<x,y,z> = R3.chart() sage: F = S2.diff_map(R3, {(XS, X3): [sin(th)*cos(ph), ....: sin(th)*sin(ph), cos(th)]}, name='F') sage: F.display() # the standard embedding of S^2 into R^3 F: S^2 --> R^3 on U: (th, ph) |--> (x, y, z) = (cos(ph)*sin(th), sin(ph)*sin(th), cos(th)) sage: v = XS.frame()[1] ; v # the coordinate vector d/dphi Vector field d/dph on the Open subset U of the 2-dimensional differentiable manifold S^2 sage: graph_v = v.plot(chart=X3, mapping=F, label_axes=False) sage: graph_S2 = XS.plot(chart=X3, mapping=F, number_values=9) sage: graph_v + graph_S2 Graphics3d Object .. PLOT:: S2 = Manifold(2, 'S^2') U = S2.open_subset('U') XS = U.chart(r'th:(0,pi):\theta ph:(0,2*pi):\phi') th, ph = XS[:] R3 = Manifold(3, 'R^3') X3 = R3.chart('x y z') F = S2.diff_map(R3, {(XS, X3): [sin(th)*cos(ph), sin(th)*sin(ph), cos(th)]}, name='F') v = XS.frame()[1] graph_v = v.plot(chart=X3, mapping=F, label_axes=False) graph_S2 = XS.plot(chart=X3, mapping=F, number_values=9) sphinx_plot(graph_v + graph_S2) Note that the default values of some arguments of the method ``plot`` are stored in the dictionary ``plot.options``:: sage: v.plot.options # random (dictionary output) {'color': 'blue', 'max_range': 8, 'scale': 1} so that they can be adjusted by the user:: sage: v.plot.options['color'] = 'red' From now on, all plots of vector fields will use red as the default color. To restore the original default options, it suffices to type:: sage: v.plot.reset() """ from sage.rings.infinity import Infinity from sage.misc.functional import numerical_approx from sage.misc.latex import latex from sage.plot.graphics import Graphics from sage.manifolds.chart import RealChart from sage.manifolds.utilities import set_axes_labels from sage.parallel.decorate import parallel from sage.parallel.parallelism import Parallelism # # 1/ Treatment of input parameters # ----------------------------- max_range = extra_options.pop("max_range") scale = extra_options.pop("scale") color = extra_options.pop("color") if chart is None: chart = self._domain.default_chart() elif not isinstance(chart, RealChart): raise TypeError("{} is not a chart on a real ".format(chart) + "manifold") if chart_domain is None: chart_domain = self._domain.default_chart() elif not isinstance(chart_domain, RealChart): raise TypeError("{} is not a chart on a ".format(chart_domain) + "real manifold") elif not chart_domain.domain().is_subset(self._domain): raise ValueError("the domain of {} is not ".format(chart_domain) + "included in the domain of {}".format(self)) coords_full = tuple(chart_domain[:]) # all coordinates of chart_domain if fixed_coords is None: coords = coords_full else: fixed_coord_list = fixed_coords.keys() coords = [] for coord in coords_full: if coord not in fixed_coord_list: coords.append(coord) coords = tuple(coords) if ambient_coords is None: ambient_coords = chart[:] elif not isinstance(ambient_coords, tuple): ambient_coords = tuple(ambient_coords) nca = len(ambient_coords) if nca != 2 and nca != 3: raise ValueError("the number of ambient coordinates must be " + "either 2 or 3, not {}".format(nca)) if ranges is None: ranges = {} ranges0 = {} for coord in coords: if coord in ranges: ranges0[coord] = (numerical_approx(ranges[coord][0]), numerical_approx(ranges[coord][1])) else: bounds = chart_domain._bounds[coords_full.index(coord)] xmin0 = bounds[0][0] xmax0 = bounds[1][0] if xmin0 == -Infinity: xmin = numerical_approx(-max_range) elif bounds[0][1]: xmin = numerical_approx(xmin0) else: xmin = numerical_approx(xmin0 + 1.e-3) if xmax0 == Infinity: xmax = numerical_approx(max_range) elif bounds[1][1]: xmax = numerical_approx(xmax0) else: xmax = numerical_approx(xmax0 - 1.e-3) ranges0[coord] = (xmin, xmax) ranges = ranges0 if number_values is None: if nca == 2: # 2D plot number_values = 9 else: # 3D plot number_values = 5 if not isinstance(number_values, dict): number_values0 = {} for coord in coords: number_values0[coord] = number_values number_values = number_values0 if steps is None: steps = {} for coord in coords: if coord not in steps: steps[coord] = (ranges[coord][1] - ranges[coord][0])/ \ (number_values[coord]-1) else: number_values[coord] = 1 + int( (ranges[coord][1] - ranges[coord][0]) / steps[coord]) # # 2/ Plots # ----- dom = chart_domain.domain() vector = self.restrict(dom) if vector.parent().destination_map() is dom.identity_map(): if mapping is not None: vector = mapping.pushforward(vector) mapping = None nc = len(coords_full) ncp = len(coords) xx = [0] * nc if fixed_coords is not None: if len(fixed_coords) != nc - ncp: raise ValueError("bad number of fixed coordinates") for fc, val in fixed_coords.items(): xx[coords_full.index(fc)] = val ind_coord = [] for coord in coords: ind_coord.append(coords_full.index(coord)) resu = Graphics() ind = [0] * ncp ind_max = [0] * ncp ind_max[0] = number_values[coords[0]] xmin = [ranges[cd][0] for cd in coords] step_tab = [steps[cd] for cd in coords] nproc = Parallelism().get('tensor') if nproc != 1 and nca == 2: # parallel plot construct : Only for 2D plot (at moment) ! # creation of the list of parameters list_xx = [] while ind != ind_max: for i in range(ncp): xx[ind_coord[i]] = xmin[i] + ind[i] * step_tab[i] if chart_domain.valid_coordinates(*xx, tolerance=1e-13, parameters=parameters): # needed a xx*1 to copy the list by value list_xx.append(xx * 1) # Next index: ret = 1 for pos in range(ncp - 1, -1, -1): imax = number_values[coords[pos]] - 1 if ind[pos] != imax: ind[pos] += ret ret = 0 elif ret == 1: if pos == 0: ind[pos] = imax + 1 # end point reached else: ind[pos] = 0 ret = 1 lol = lambda lst, sz: [ lst[i:i + sz] for i in range(0, len(lst), sz) ] ind_step = max(1, int(len(list_xx) / nproc / 2)) local_list = lol(list_xx, ind_step) # definition of the list of input parameters listParalInput = [ (vector, dom, ind_part, chart_domain, chart, ambient_coords, mapping, scale, color, parameters, extra_options) for ind_part in local_list ] # definition of the parallel function @parallel(p_iter='multiprocessing', ncpus=nproc) def add_point_plot(vector, dom, xx_list, chart_domain, chart, ambient_coords, mapping, scale, color, parameters, extra_options): count = 0 for xx in xx_list: point = dom(xx, chart=chart_domain) part = vector.at(point).plot(chart=chart, ambient_coords=ambient_coords, mapping=mapping, scale=scale, color=color, print_label=False, parameters=parameters, **extra_options) if count == 0: local_resu = part else: local_resu += part count += 1 return local_resu # parallel execution and reconstruction of the plot for ii, val in add_point_plot(listParalInput): resu += val else: # sequential plot while ind != ind_max: for i in range(ncp): xx[ind_coord[i]] = xmin[i] + ind[i] * step_tab[i] if chart_domain.valid_coordinates(*xx, tolerance=1e-13, parameters=parameters): point = dom(xx, chart=chart_domain) resu += vector.at(point).plot( chart=chart, ambient_coords=ambient_coords, mapping=mapping, scale=scale, color=color, print_label=False, parameters=parameters, **extra_options) # Next index: ret = 1 for pos in range(ncp - 1, -1, -1): imax = number_values[coords[pos]] - 1 if ind[pos] != imax: ind[pos] += ret ret = 0 elif ret == 1: if pos == 0: ind[pos] = imax + 1 # end point reached else: ind[pos] = 0 ret = 1 if label_axes: if nca == 2: # 2D graphic # We update the dictionary _extra_kwds (options to be passed # to show()), instead of using the method # Graphics.axes_labels() since the latter is not robust w.r.t. # graph addition resu._extra_kwds['axes_labels'] = [ r'$' + latex(ac) + r'$' for ac in ambient_coords ] else: # 3D graphic labels = [str(ac) for ac in ambient_coords] resu = set_axes_labels(resu, *labels) return resu
def plot(self, chart=None, ambient_coords=None, mapping=None, prange=None, include_end_point=(True, True), end_point_offset=(0.001, 0.001), parameters=None, color='red', style='-', label_axes=True, **kwds): r""" Plot the current curve in a Cartesian graph based on the coordinates of some ambient chart. The curve is drawn in terms of two (2D graphics) or three (3D graphics) coordinates of a given chart, called hereafter the *ambient chart*. The ambient chart's domain must overlap with the curve's codomain or with the codomain of the composite curve `\Phi\circ c`, where `c` is the current curve and `\Phi` some manifold differential map (argument ``mapping`` below). INPUT: - ``chart`` -- (default: ``None``) the ambient chart (see above); if ``None``, the default chart of the codomain of the curve (or of the curve composed with `\Phi`) is used - ``ambient_coords`` -- (default: ``None``) tuple containing the 2 or 3 coordinates of the ambient chart in terms of which the plot is performed; if ``None``, all the coordinates of the ambient chart are considered - ``mapping`` -- (default: ``None``) differentiable mapping `\Phi` (instance of :class:`~sage.manifolds.differentiable.diff_map.DiffMap`) providing the link between the curve and the ambient chart ``chart`` (cf. above); if ``None``, the ambient chart is supposed to be defined on the codomain of the curve. - ``prange`` -- (default: ``None``) range of the curve parameter for the plot; if ``None``, the entire parameter range declared during the curve construction is considered (with -Infinity replaced by ``-max_range`` and +Infinity by ``max_range``) - ``include_end_point`` -- (default: ``(True, True)``) determines whether the end points of ``prange`` are included in the plot - ``end_point_offset`` -- (default: ``(0.001, 0.001)``) offsets from the end points when they are not included in the plot: if ``include_end_point[0] == False``, the minimal value of the curve parameter used for the plot is ``prange[0] + end_point_offset[0]``, while if ``include_end_point[1] == False``, the maximal value is ``prange[1] - end_point_offset[1]``. - ``max_range`` -- (default: 8) numerical value substituted to +Infinity if the latter is the upper bound of the parameter range; similarly ``-max_range`` is the numerical valued substituted for -Infinity - ``parameters`` -- (default: ``None``) dictionary giving the numerical values of the parameters that may appear in the coordinate expression of the curve - ``color`` -- (default: 'red') color of the drawn curve - ``style`` -- (default: '-') color of the drawn curve; NB: ``style`` is effective only for 2D plots - ``thickness`` -- (default: 1) thickness of the drawn curve - ``plot_points`` -- (default: 75) number of points to plot the curve - ``label_axes`` -- (default: ``True``) boolean determining whether the labels of the coordinate axes of ``chart`` shall be added to the graph; can be set to ``False`` if the graph is 3D and must be superposed with another graph. - ``aspect_ratio`` -- (default: ``'automatic'``) aspect ratio of the plot; the default value (``'automatic'``) applies only for 2D plots; for 3D plots, the default value is ``1`` instead OUTPUT: - a graphic object, either an instance of :class:`~sage.plot.graphics.Graphics` for a 2D plot (i.e. based on 2 coordinates of ``chart``) or an instance of :class:`~sage.plot.plot3d.base.Graphics3d` for a 3D plot (i.e. based on 3 coordinates of ``chart``) EXAMPLES: Plot of the lemniscate of Gerono:: sage: R2 = Manifold(2, 'R^2') sage: X.<x,y> = R2.chart() sage: R.<t> = RealLine() sage: c = R2.curve([sin(t), sin(2*t)/2], (t, 0, 2*pi), name='c') sage: c.plot() # 2D plot Graphics object consisting of 1 graphics primitive .. PLOT:: R2 = Manifold(2, 'R^2') X = R2.chart('x y') t = RealLine().canonical_coordinate() c = R2.curve([sin(t), sin(2*t)/2], (t, 0, 2*pi), name='c') g = c.plot() sphinx_plot(g) Plot for a subinterval of the curve's domain:: sage: c.plot(prange=(0,pi)) Graphics object consisting of 1 graphics primitive .. PLOT:: R2 = Manifold(2, 'R^2') X = R2.chart('x y') t = RealLine().canonical_coordinate() c = R2.curve([sin(t), sin(2*t)/2], (t, 0, 2*pi), name='c') g = c.plot(prange=(0,pi)) sphinx_plot(g) Plot with various options:: sage: c.plot(color='green', style=':', thickness=3, aspect_ratio=1) Graphics object consisting of 1 graphics primitive .. PLOT:: R2 = Manifold(2, 'R^2') X = R2.chart('x y') t = RealLine().canonical_coordinate() c = R2.curve([sin(t), sin(2*t)/2], (t, 0, 2*pi), name='c') g = c.plot(color='green', style=':', thickness=3, aspect_ratio=1) sphinx_plot(g) Plot via a mapping to another manifold: loxodrome of a sphere viewed in `\RR^3`:: sage: S2 = Manifold(2, 'S^2') sage: U = S2.open_subset('U') sage: XS.<th,ph> = U.chart(r'th:(0,pi):\theta ph:(0,2*pi):\phi') sage: R3 = Manifold(3, 'R^3') sage: X3.<x,y,z> = R3.chart() sage: F = S2.diff_map(R3, {(XS, X3): [sin(th)*cos(ph), ....: sin(th)*sin(ph), cos(th)]}, name='F') sage: F.display() F: S^2 --> R^3 on U: (th, ph) |--> (x, y, z) = (cos(ph)*sin(th), sin(ph)*sin(th), cos(th)) sage: c = S2.curve([2*atan(exp(-t/10)), t], (t, -oo, +oo), name='c') sage: graph_c = c.plot(mapping=F, max_range=40, ....: plot_points=200, thickness=2, label_axes=False) # 3D plot sage: graph_S2 = XS.plot(X3, mapping=F, number_values=11, color='black') # plot of the sphere sage: show(graph_c + graph_S2) # the loxodrome + the sphere .. PLOT:: S2 = Manifold(2, 'S^2') U = S2.open_subset('U') XS = U.chart(r'th:(0,pi):\theta ph:(0,2*pi):\phi') th, ph = XS[:] R3 = Manifold(3, 'R^3') X3 = R3.chart('x y z') F = S2.diff_map(R3, {(XS, X3): [sin(th)*cos(ph), sin(th)*sin(ph), cos(th)]}, name='F') t = RealLine().canonical_coordinate() c = S2.curve([2*atan(exp(-t/10)), t], (t, -oo, +oo), name='c') graph_c = c.plot(mapping=F, max_range=40, plot_points=200, thickness=2, label_axes=False) graph_S2 = XS.plot(X3, mapping=F, number_values=11, color='black') sphinx_plot(graph_c + graph_S2) Example of use of the argument ``parameters``: we define a curve with some symbolic parameters ``a`` and ``b``:: sage: a, b = var('a b') sage: c = R2.curve([a*cos(t) + b, a*sin(t)], (t, 0, 2*pi), name='c') To make a plot, we set spectific values for ``a`` and ``b`` by means of the Python dictionary ``parameters``:: sage: c.plot(parameters={a: 2, b: -3}, aspect_ratio=1) Graphics object consisting of 1 graphics primitive .. PLOT:: R2 = Manifold(2, 'R^2') X = R2.chart('x y') t = RealLine().canonical_coordinate() a, b = var('a b') c = R2.curve([a*cos(t) + b, a*sin(t)], (t, 0, 2*pi), name='c') g = c.plot(parameters={a: 2, b: -3}, aspect_ratio=1) sphinx_plot(g) """ from sage.rings.infinity import Infinity from sage.misc.functional import numerical_approx from sage.plot.graphics import Graphics from sage.plot.line import line from sage.manifolds.chart import RealChart from sage.manifolds.utilities import set_axes_labels # # Get the @options from kwds # thickness = kwds.pop('thickness') plot_points = kwds.pop('plot_points') max_range = kwds.pop('max_range') aspect_ratio = kwds.pop('aspect_ratio') # # The "effective" curve to be plotted # if mapping is None: eff_curve = self else: eff_curve = mapping.restrict(self.codomain()) * self # # The chart w.r.t. which the curve is plotted # if chart is None: chart = eff_curve._codomain.default_chart() elif not isinstance(chart, RealChart): raise TypeError("{} is not a real chart".format(chart)) # # Coordinates of the above chart w.r.t. which the curve is plotted # if ambient_coords is None: ambient_coords = chart[:] # all chart coordinates are used n_pc = len(ambient_coords) if n_pc != 2 and n_pc !=3: raise ValueError("the number of coordinates involved in the " + "plot must be either 2 or 3, not {}".format(n_pc)) # indices of plot coordinates ind_pc = [chart[:].index(pc) for pc in ambient_coords] # # Parameter range for the plot # if prange is None: prange = (self._domain.lower_bound(), self._domain.upper_bound()) elif not isinstance(prange, (tuple, list)): raise TypeError("{} is neither a tuple nor a list".format(prange)) elif len(prange) != 2: raise ValueError("the argument prange must be a tuple/list " + "of 2 elements") tmin = prange[0] tmax = prange[1] if tmin == -Infinity: tmin = -max_range elif not include_end_point[0]: tmin = tmin + end_point_offset[0] if tmax == Infinity: tmax = max_range elif not include_end_point[1]: tmax = tmax - end_point_offset[1] tmin = numerical_approx(tmin) tmax = numerical_approx(tmax) # # The coordinate expression of the effective curve # canon_chart = self._domain.canonical_chart() transf = None for chart_pair in eff_curve._coord_expression: if chart_pair == (canon_chart, chart): transf = eff_curve._coord_expression[chart_pair] break else: # Search for a subchart for chart_pair in eff_curve._coord_expression: for schart in chart._subcharts: if chart_pair == (canon_chart, schart): transf = eff_curve._coord_expression[chart_pair] if transf is None: raise ValueError("No expression has been found for " + "{} in terms of {}".format(self, chart)) # # List of points for the plot curve # plot_curve = [] dt = (tmax - tmin) / (plot_points - 1) t = tmin if parameters is None: for i in range(plot_points): x = transf(t, simplify=False) plot_curve.append( [numerical_approx(x[j]) for j in ind_pc] ) t += dt else: for i in range(plot_points): x = transf(t, simplify=False) plot_curve.append( [numerical_approx( x[j].substitute(parameters) ) for j in ind_pc] ) t += dt # # The plot # resu = Graphics() resu += line(plot_curve, color=color, linestyle=style, thickness=thickness) if n_pc == 2: # 2D graphic resu.set_aspect_ratio(aspect_ratio) if label_axes: # We update the dictionary _extra_kwds (options to be passed # to show()), instead of using the method # Graphics.axes_labels() since the latter is not robust w.r.t. # graph addition resu._extra_kwds['axes_labels'] = [r'$'+latex(pc)+r'$' for pc in ambient_coords] else: # 3D graphic if aspect_ratio == 'automatic': aspect_ratio = 1 resu.aspect_ratio(aspect_ratio) if label_axes: labels = [str(pc) for pc in ambient_coords] resu = set_axes_labels(resu, *labels) return resu
def plot(self, chart=None, ambient_coords=None, mapping=None, chart_domain=None, fixed_coords=None, ranges=None, number_values=None, steps=None, parameters=None, label_axes=True, **extra_options): r""" Plot the vector field in a Cartesian graph based on the coordinates of some ambient chart. The vector field is drawn in terms of two (2D graphics) or three (3D graphics) coordinates of a given chart, called hereafter the *ambient chart*. The vector field's base points `p` (or their images `\Phi(p)` by some differentiable mapping `\Phi`) must lie in the ambient chart's domain. INPUT: - ``chart`` -- (default: ``None``) the ambient chart (see above); if ``None``, the default chart of the vector field's domain is used - ``ambient_coords`` -- (default: ``None``) tuple containing the 2 or 3 coordinates of the ambient chart in terms of which the plot is performed; if ``None``, all the coordinates of the ambient chart are considered - ``mapping`` -- :class:`~sage.manifolds.differentiable.diff_map.DiffMap` (default: ``None``); differentiable map `\Phi` providing the link between the vector field's domain and the ambient chart ``chart``; if ``None``, the identity map is assumed - ``chart_domain`` -- (default: ``None``) chart on the vector field's domain to define the points at which vector arrows are to be plotted; if ``None``, the default chart of the vector field's domain is used - ``fixed_coords`` -- (default: ``None``) dictionary with keys the coordinates of ``chart_domain`` that are kept fixed and with values the value of these coordinates; if ``None``, all the coordinates of ``chart_domain`` are used - ``ranges`` -- (default: ``None``) dictionary with keys the coordinates of ``chart_domain`` to be used and values tuples ``(x_min, x_max)`` specifying the coordinate range for the plot; if ``None``, the entire coordinate range declared during the construction of ``chart_domain`` is considered (with ``-Infinity`` replaced by ``-max_range`` and ``+Infinity`` by ``max_range``) - ``number_values`` -- (default: ``None``) either an integer or a dictionary with keys the coordinates of ``chart_domain`` to be used and values the number of values of the coordinate for sampling the part of the vector field's domain involved in the plot ; if ``number_values`` is a single integer, it represents the number of values for all coordinates; if ``number_values`` is ``None``, it is set to 9 for a 2D plot and to 5 for a 3D plot - ``steps`` -- (default: ``None``) dictionary with keys the coordinates of ``chart_domain`` to be used and values the step between each constant value of the coordinate; if ``None``, the step is computed from the coordinate range (specified in ``ranges``) and ``number_values``; on the contrary, if the step is provided for some coordinate, the corresponding number of values is deduced from it and the coordinate range - ``parameters`` -- (default: ``None``) dictionary giving the numerical values of the parameters that may appear in the coordinate expression of the vector field (see example below) - ``label_axes`` -- (default: ``True``) boolean determining whether the labels of the coordinate axes of ``chart`` shall be added to the graph; can be set to ``False`` if the graph is 3D and must be superposed with another graph - ``color`` -- (default: 'blue') color of the arrows representing the vectors - ``max_range`` -- (default: 8) numerical value substituted to ``+Infinity`` if the latter is the upper bound of the range of a coordinate for which the plot is performed over the entire coordinate range (i.e. for which no specific plot range has been set in ``ranges``); similarly ``-max_range`` is the numerical valued substituted for ``-Infinity`` - ``scale`` -- (default: 1) value by which the lengths of the arrows representing the vectors is multiplied - ``**extra_options`` -- extra options for the arrow plot, like ``linestyle``, ``width`` or ``arrowsize`` (see :func:`~sage.plot.arrow.arrow2d` and :func:`~sage.plot.plot3d.shapes.arrow3d` for details) OUTPUT: - a graphic object, either an instance of :class:`~sage.plot.graphics.Graphics` for a 2D plot (i.e. based on 2 coordinates of ``chart``) or an instance of :class:`~sage.plot.plot3d.base.Graphics3d` for a 3D plot (i.e. based on 3 coordinates of ``chart``) EXAMPLES: Plot of a vector field on a 2-dimensional manifold:: sage: M = Manifold(2, 'M') sage: X.<x,y> = M.chart() sage: v = M.vector_field(name='v') sage: v[:] = -y, x ; v.display() v = -y d/dx + x d/dy sage: v.plot() Graphics object consisting of 80 graphics primitives .. PLOT:: M = Manifold(2, 'M') X = M.chart('x y'); x, y = X[:] v = M.vector_field(name='v'); v[:] = -y, x g = v.plot() sphinx_plot(g) Plot with various options:: sage: v.plot(scale=0.5, color='green', linestyle='--', width=1, ....: arrowsize=6) Graphics object consisting of 80 graphics primitives .. PLOT:: M = Manifold(2, 'M') X = M.chart('x y'); x, y = X[:] v = M.vector_field(name='v'); v[:] = -y, x g = v.plot(scale=0.5, color='green', linestyle='--', width=1, arrowsize=6) sphinx_plot(g) :: sage: v.plot(max_range=4, number_values=5, scale=0.5) Graphics object consisting of 24 graphics primitives .. PLOT:: M = Manifold(2, 'M') X = M.chart('x y'); x, y = X[:] v = M.vector_field(name='v'); v[:] = -y, x g = v.plot(max_range=4, number_values=5, scale=0.5) sphinx_plot(g) Plot using parallel computation:: sage: Parallelism().set(nproc=2) sage: v.plot(scale=0.5, number_values=10, linestyle='--', width=1, ....: arrowsize=6) Graphics object consisting of 100 graphics primitives .. PLOT:: M = Manifold(2, 'M') X = M.chart('x y'); x, y = X[:] v = M.vector_field(name='v'); v[:] = -y, x g = v.plot(scale=0.5, number_values=10, linestyle='--', width=1, arrowsize=6) sphinx_plot(g) :: sage: Parallelism().set(nproc=1) # switch off parallelization Plots along a line of fixed coordinate:: sage: v.plot(fixed_coords={x: -2}) Graphics object consisting of 9 graphics primitives .. PLOT:: M = Manifold(2, 'M') X = M.chart('x y'); x, y = X[:] v = M.vector_field(name='v'); v[:] = -y, x g = v.plot(fixed_coords={x: -2}) sphinx_plot(g) :: sage: v.plot(fixed_coords={y: 1}) Graphics object consisting of 9 graphics primitives .. PLOT:: M = Manifold(2, 'M') X = M.chart('x y'); x, y = X[:] v = M.vector_field(name='v'); v[:] = -y, x g = v.plot(fixed_coords={y: 1}) sphinx_plot(g) Let us now consider a vector field on a 4-dimensional manifold:: sage: M = Manifold(4, 'M') sage: X.<t,x,y,z> = M.chart() sage: v = M.vector_field(name='v') sage: v[:] = (t/8)^2, -t*y/4, t*x/4, t*z/4 ; v.display() v = 1/64*t^2 d/dt - 1/4*t*y d/dx + 1/4*t*x d/dy + 1/4*t*z d/dz We cannot make a 4D plot directly:: sage: v.plot() Traceback (most recent call last): ... ValueError: the number of ambient coordinates must be either 2 or 3, not 4 Rather, we have to select some coordinates for the plot, via the argument ``ambient_coords``. For instance, for a 3D plot:: sage: v.plot(ambient_coords=(x, y, z), fixed_coords={t: 1}, # long time ....: number_values=4) Graphics3d Object .. PLOT:: M = Manifold(4, 'M') X = M.chart('t x y z') ; t,x,y,z = X[:] v = M.vector_field(name='v') v[:] = (t/8)**2, -t*y/4, t*x/4, t*z/4 sphinx_plot(v.plot(ambient_coords=(x, y, z), fixed_coords={t: 1}, number_values=4)) :: sage: v.plot(ambient_coords=(x, y, t), fixed_coords={z: 0}, # long time ....: ranges={x: (-2,2), y: (-2,2), t: (-1, 4)}, ....: number_values=4) Graphics3d Object .. PLOT:: M = Manifold(4, 'M') X = M.chart('t x y z'); t,x,y,z = X[:] v = M.vector_field(name='v') v[:] = (t/8)**2, -t*y/4, t*x/4, t*z/4 sphinx_plot(v.plot(ambient_coords=(x, y, t), fixed_coords={z: 0}, ranges={x: (-2,2), y: (-2,2), t: (-1, 4)}, number_values=4)) or, for a 2D plot:: sage: v.plot(ambient_coords=(x, y), fixed_coords={t: 1, z: 0}) # long time Graphics object consisting of 80 graphics primitives .. PLOT:: M = Manifold(4, 'M') X = M.chart('t x y z'); t,x,y,z = X[:] v = M.vector_field(name='v') v[:] = (t/8)**2, -t*y/4, t*x/4, t*z/4 g = v.plot(ambient_coords=(x, y), fixed_coords={t: 1, z: 0}) sphinx_plot(g) :: sage: v.plot(ambient_coords=(x, t), fixed_coords={y: 1, z: 0}) # long time Graphics object consisting of 72 graphics primitives .. PLOT:: M = Manifold(4, 'M') X = M.chart('t x y z'); t,x,y,z = X[:] v = M.vector_field(name='v') v[:] = v[:] = (t/8)**2, -t*y/4, t*x/4, t*z/4 g = v.plot(ambient_coords=(x, t), fixed_coords={y: 1, z: 0}) sphinx_plot(g) An example of plot via a differential mapping: plot of a vector field tangent to a 2-sphere viewed in `\RR^3`:: sage: S2 = Manifold(2, 'S^2') sage: U = S2.open_subset('U') # the open set covered by spherical coord. sage: XS.<th,ph> = U.chart(r'th:(0,pi):\theta ph:(0,2*pi):\phi') sage: R3 = Manifold(3, 'R^3') sage: X3.<x,y,z> = R3.chart() sage: F = S2.diff_map(R3, {(XS, X3): [sin(th)*cos(ph), ....: sin(th)*sin(ph), cos(th)]}, name='F') sage: F.display() # the standard embedding of S^2 into R^3 F: S^2 --> R^3 on U: (th, ph) |--> (x, y, z) = (cos(ph)*sin(th), sin(ph)*sin(th), cos(th)) sage: v = XS.frame()[1] ; v # the coordinate vector d/dphi Vector field d/dph on the Open subset U of the 2-dimensional differentiable manifold S^2 sage: graph_v = v.plot(chart=X3, mapping=F, label_axes=False) sage: graph_S2 = XS.plot(chart=X3, mapping=F, number_values=9) sage: graph_v + graph_S2 Graphics3d Object .. PLOT:: S2 = Manifold(2, 'S^2') U = S2.open_subset('U') XS = U.chart(r'th:(0,pi):\theta ph:(0,2*pi):\phi') th, ph = XS[:] R3 = Manifold(3, 'R^3') X3 = R3.chart('x y z') F = S2.diff_map(R3, {(XS, X3): [sin(th)*cos(ph), sin(th)*sin(ph), cos(th)]}, name='F') v = XS.frame()[1] graph_v = v.plot(chart=X3, mapping=F, label_axes=False) graph_S2 = XS.plot(chart=X3, mapping=F, number_values=9) sphinx_plot(graph_v + graph_S2) Note that the default values of some arguments of the method ``plot`` are stored in the dictionary ``plot.options``:: sage: v.plot.options # random (dictionary output) {'color': 'blue', 'max_range': 8, 'scale': 1} so that they can be adjusted by the user:: sage: v.plot.options['color'] = 'red' From now on, all plots of vector fields will use red as the default color. To restore the original default options, it suffices to type:: sage: v.plot.reset() """ from sage.rings.infinity import Infinity from sage.misc.functional import numerical_approx from sage.misc.latex import latex from sage.plot.graphics import Graphics from sage.manifolds.chart import RealChart from sage.manifolds.utilities import set_axes_labels from sage.parallel.decorate import parallel from sage.parallel.parallelism import Parallelism # # 1/ Treatment of input parameters # ----------------------------- max_range = extra_options.pop("max_range") scale = extra_options.pop("scale") color = extra_options.pop("color") if chart is None: chart = self._domain.default_chart() elif not isinstance(chart, RealChart): raise TypeError("{} is not a chart on a real ".format(chart) + "manifold") if chart_domain is None: chart_domain = self._domain.default_chart() elif not isinstance(chart_domain, RealChart): raise TypeError("{} is not a chart on a ".format(chart_domain) + "real manifold") elif not chart_domain.domain().is_subset(self._domain): raise ValueError("the domain of {} is not ".format(chart_domain) + "included in the domain of {}".format(self)) coords_full = tuple(chart_domain[:]) # all coordinates of chart_domain if fixed_coords is None: coords = coords_full else: fixed_coord_list = fixed_coords.keys() coords = [] for coord in coords_full: if coord not in fixed_coord_list: coords.append(coord) coords = tuple(coords) if ambient_coords is None: ambient_coords = chart[:] elif not isinstance(ambient_coords, tuple): ambient_coords = tuple(ambient_coords) nca = len(ambient_coords) if nca != 2 and nca !=3: raise ValueError("the number of ambient coordinates must be " + "either 2 or 3, not {}".format(nca)) if ranges is None: ranges = {} ranges0 = {} for coord in coords: if coord in ranges: ranges0[coord] = (numerical_approx(ranges[coord][0]), numerical_approx(ranges[coord][1])) else: bounds = chart_domain._bounds[coords_full.index(coord)] xmin0 = bounds[0][0] xmax0 = bounds[1][0] if xmin0 == -Infinity: xmin = numerical_approx(-max_range) elif bounds[0][1]: xmin = numerical_approx(xmin0) else: xmin = numerical_approx(xmin0 + 1.e-3) if xmax0 == Infinity: xmax = numerical_approx(max_range) elif bounds[1][1]: xmax = numerical_approx(xmax0) else: xmax = numerical_approx(xmax0 - 1.e-3) ranges0[coord] = (xmin, xmax) ranges = ranges0 if number_values is None: if nca == 2: # 2D plot number_values = 9 else: # 3D plot number_values = 5 if not isinstance(number_values, dict): number_values0 = {} for coord in coords: number_values0[coord] = number_values number_values = number_values0 if steps is None: steps = {} for coord in coords: if coord not in steps: steps[coord] = (ranges[coord][1] - ranges[coord][0])/ \ (number_values[coord]-1) else: number_values[coord] = 1 + int( (ranges[coord][1] - ranges[coord][0])/ steps[coord]) # # 2/ Plots # ----- dom = chart_domain.domain() vector = self.restrict(dom) if vector.parent().destination_map() is dom.identity_map(): if mapping is not None: vector = mapping.pushforward(vector) mapping = None nc = len(coords_full) ncp = len(coords) xx = [0] * nc if fixed_coords is not None: if len(fixed_coords) != nc - ncp: raise ValueError("bad number of fixed coordinates") for fc, val in fixed_coords.items(): xx[coords_full.index(fc)] = val ind_coord = [] for coord in coords: ind_coord.append(coords_full.index(coord)) resu = Graphics() ind = [0] * ncp ind_max = [0] * ncp ind_max[0] = number_values[coords[0]] xmin = [ranges[cd][0] for cd in coords] step_tab = [steps[cd] for cd in coords] nproc = Parallelism().get('tensor') if nproc != 1 and nca == 2: # parallel plot construct : Only for 2D plot (at moment) ! # creation of the list of parameters list_xx = [] while ind != ind_max: for i in range(ncp): xx[ind_coord[i]] = xmin[i] + ind[i]*step_tab[i] if chart_domain.valid_coordinates(*xx, tolerance=1e-13, parameters=parameters): # needed a xx*1 to copy the list by value list_xx.append(xx*1) # Next index: ret = 1 for pos in range(ncp-1,-1,-1): imax = number_values[coords[pos]] - 1 if ind[pos] != imax: ind[pos] += ret ret = 0 elif ret == 1: if pos == 0: ind[pos] = imax + 1 # end point reached else: ind[pos] = 0 ret = 1 lol = lambda lst, sz: [lst[i:i+sz] for i in range(0, len(lst), sz)] ind_step = max(1, int(len(list_xx)/nproc/2)) local_list = lol(list_xx,ind_step) # definition of the list of input parameters listParalInput = [(vector, dom, ind_part, chart_domain, chart, ambient_coords, mapping, scale, color, parameters, extra_options) for ind_part in local_list] # definition of the parallel function @parallel(p_iter='multiprocessing', ncpus=nproc) def add_point_plot(vector, dom, xx_list, chart_domain, chart, ambient_coords, mapping, scale, color, parameters, extra_options): count = 0 for xx in xx_list: point = dom(xx, chart=chart_domain) part = vector.at(point).plot(chart=chart, ambient_coords=ambient_coords, mapping=mapping,scale=scale, color=color, print_label=False, parameters=parameters, **extra_options) if count == 0: local_resu = part else: local_resu += part count += 1 return local_resu # parallel execution and reconstruction of the plot for ii, val in add_point_plot(listParalInput): resu += val else: # sequential plot while ind != ind_max: for i in range(ncp): xx[ind_coord[i]] = xmin[i] + ind[i]*step_tab[i] if chart_domain.valid_coordinates(*xx, tolerance=1e-13, parameters=parameters): point = dom(xx, chart=chart_domain) resu += vector.at(point).plot(chart=chart, ambient_coords=ambient_coords, mapping=mapping, scale=scale, color=color, print_label=False, parameters=parameters, **extra_options) # Next index: ret = 1 for pos in range(ncp-1, -1, -1): imax = number_values[coords[pos]] - 1 if ind[pos] != imax: ind[pos] += ret ret = 0 elif ret == 1: if pos == 0: ind[pos] = imax + 1 # end point reached else: ind[pos] = 0 ret = 1 if label_axes: if nca == 2: # 2D graphic # We update the dictionary _extra_kwds (options to be passed # to show()), instead of using the method # Graphics.axes_labels() since the latter is not robust w.r.t. # graph addition resu._extra_kwds['axes_labels'] = [r'$'+latex(ac)+r'$' for ac in ambient_coords] else: # 3D graphic labels = [str(ac) for ac in ambient_coords] resu = set_axes_labels(resu, *labels) return resu