1235 lines
40 KiB
Python
1235 lines
40 KiB
Python
"""Plotting module for SymPy.
|
|
|
|
A plot is represented by the ``Plot`` class that contains a reference to the
|
|
backend and a list of the data series to be plotted. The data series are
|
|
instances of classes meant to simplify getting points and meshes from SymPy
|
|
expressions. ``plot_backends`` is a dictionary with all the backends.
|
|
|
|
This module gives only the essential. For all the fancy stuff use directly
|
|
the backend. You can get the backend wrapper for every plot from the
|
|
``_backend`` attribute. Moreover the data series classes have various useful
|
|
methods like ``get_points``, ``get_meshes``, etc, that may
|
|
be useful if you wish to use another plotting library.
|
|
|
|
Especially if you need publication ready graphs and this module is not enough
|
|
for you - just get the ``_backend`` attribute and add whatever you want
|
|
directly to it. In the case of matplotlib (the common way to graph data in
|
|
python) just copy ``_backend.fig`` which is the figure and ``_backend.ax``
|
|
which is the axis and work on them as you would on any other matplotlib object.
|
|
|
|
Simplicity of code takes much greater importance than performance. Do not use it
|
|
if you care at all about performance. A new backend instance is initialized
|
|
every time you call ``show()`` and the old one is left to the garbage collector.
|
|
"""
|
|
|
|
from sympy.concrete.summations import Sum
|
|
from sympy.core.containers import Tuple
|
|
from sympy.core.expr import Expr
|
|
from sympy.core.function import Function, AppliedUndef
|
|
from sympy.core.symbol import (Dummy, Symbol, Wild)
|
|
from sympy.external import import_module
|
|
from sympy.functions import sign
|
|
from sympy.plotting.backends.base_backend import Plot
|
|
from sympy.plotting.backends.matplotlibbackend import MatplotlibBackend
|
|
from sympy.plotting.backends.textbackend import TextBackend
|
|
from sympy.plotting.series import (
|
|
LineOver1DRangeSeries, Parametric2DLineSeries, Parametric3DLineSeries,
|
|
ParametricSurfaceSeries, SurfaceOver2DRangeSeries, ContourSeries)
|
|
from sympy.plotting.utils import _check_arguments, _plot_sympify
|
|
from sympy.tensor.indexed import Indexed
|
|
# to maintain back-compatibility
|
|
from sympy.plotting.plotgrid import PlotGrid # noqa: F401
|
|
from sympy.plotting.series import BaseSeries # noqa: F401
|
|
from sympy.plotting.series import Line2DBaseSeries # noqa: F401
|
|
from sympy.plotting.series import Line3DBaseSeries # noqa: F401
|
|
from sympy.plotting.series import SurfaceBaseSeries # noqa: F401
|
|
from sympy.plotting.series import List2DSeries # noqa: F401
|
|
from sympy.plotting.series import GenericDataSeries # noqa: F401
|
|
from sympy.plotting.series import centers_of_faces # noqa: F401
|
|
from sympy.plotting.series import centers_of_segments # noqa: F401
|
|
from sympy.plotting.series import flat # noqa: F401
|
|
from sympy.plotting.backends.base_backend import unset_show # noqa: F401
|
|
from sympy.plotting.backends.matplotlibbackend import _matplotlib_list # noqa: F401
|
|
from sympy.plotting.textplot import textplot # noqa: F401
|
|
|
|
|
|
__doctest_requires__ = {
|
|
('plot3d',
|
|
'plot3d_parametric_line',
|
|
'plot3d_parametric_surface',
|
|
'plot_parametric'): ['matplotlib'],
|
|
# XXX: The plot doctest possibly should not require matplotlib. It fails at
|
|
# plot(x**2, (x, -5, 5)) which should be fine for text backend.
|
|
('plot',): ['matplotlib'],
|
|
}
|
|
|
|
|
|
def _process_summations(sum_bound, *args):
|
|
"""Substitute oo (infinity) in the lower/upper bounds of a summation with
|
|
some integer number.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
sum_bound : int
|
|
oo will be substituted with this integer number.
|
|
*args : list/tuple
|
|
pre-processed arguments of the form (expr, range, ...)
|
|
|
|
Notes
|
|
=====
|
|
Let's consider the following summation: ``Sum(1 / x**2, (x, 1, oo))``.
|
|
The current implementation of lambdify (SymPy 1.12 at the time of
|
|
writing this) will create something of this form:
|
|
``sum(1 / x**2 for x in range(1, INF))``
|
|
The problem is that ``type(INF)`` is float, while ``range`` requires
|
|
integers: the evaluation fails.
|
|
Instead of modifying ``lambdify`` (which requires a deep knowledge), just
|
|
replace it with some integer number.
|
|
"""
|
|
def new_bound(t, bound):
|
|
if (not t.is_number) or t.is_finite:
|
|
return t
|
|
if sign(t) >= 0:
|
|
return bound
|
|
return -bound
|
|
|
|
args = list(args)
|
|
expr = args[0]
|
|
|
|
# select summations whose lower/upper bound is infinity
|
|
w = Wild("w", properties=[
|
|
lambda t: isinstance(t, Sum),
|
|
lambda t: any((not a[1].is_finite) or (not a[2].is_finite) for i, a in enumerate(t.args) if i > 0)
|
|
])
|
|
|
|
for t in list(expr.find(w)):
|
|
sums_args = list(t.args)
|
|
for i, a in enumerate(sums_args):
|
|
if i > 0:
|
|
sums_args[i] = (a[0], new_bound(a[1], sum_bound),
|
|
new_bound(a[2], sum_bound))
|
|
s = Sum(*sums_args)
|
|
expr = expr.subs(t, s)
|
|
args[0] = expr
|
|
return args
|
|
|
|
|
|
def _build_line_series(*args, **kwargs):
|
|
"""Loop over the provided arguments and create the necessary line series.
|
|
"""
|
|
series = []
|
|
sum_bound = int(kwargs.get("sum_bound", 1000))
|
|
for arg in args:
|
|
expr, r, label, rendering_kw = arg
|
|
kw = kwargs.copy()
|
|
if rendering_kw is not None:
|
|
kw["rendering_kw"] = rendering_kw
|
|
# TODO: _process_piecewise check goes here
|
|
if not callable(expr):
|
|
arg = _process_summations(sum_bound, *arg)
|
|
series.append(LineOver1DRangeSeries(*arg[:-1], **kw))
|
|
return series
|
|
|
|
|
|
def _create_series(series_type, plot_expr, **kwargs):
|
|
"""Extract the rendering_kw dictionary from the provided arguments and
|
|
create an appropriate data series.
|
|
"""
|
|
series = []
|
|
for args in plot_expr:
|
|
kw = kwargs.copy()
|
|
if args[-1] is not None:
|
|
kw["rendering_kw"] = args[-1]
|
|
series.append(series_type(*args[:-1], **kw))
|
|
return series
|
|
|
|
|
|
def _set_labels(series, labels, rendering_kw):
|
|
"""Apply the `label` and `rendering_kw` keyword arguments to the series.
|
|
"""
|
|
if not isinstance(labels, (list, tuple)):
|
|
labels = [labels]
|
|
if len(labels) > 0:
|
|
if len(labels) == 1 and len(series) > 1:
|
|
# if one label is provided and multiple series are being plotted,
|
|
# set the same label to all data series. It maintains
|
|
# back-compatibility
|
|
labels *= len(series)
|
|
if len(series) != len(labels):
|
|
raise ValueError("The number of labels must be equal to the "
|
|
"number of expressions being plotted.\nReceived "
|
|
f"{len(series)} expressions and {len(labels)} labels")
|
|
|
|
for s, l in zip(series, labels):
|
|
s.label = l
|
|
|
|
if rendering_kw:
|
|
if isinstance(rendering_kw, dict):
|
|
rendering_kw = [rendering_kw]
|
|
if len(rendering_kw) == 1:
|
|
rendering_kw *= len(series)
|
|
elif len(series) != len(rendering_kw):
|
|
raise ValueError("The number of rendering dictionaries must be "
|
|
"equal to the number of expressions being plotted.\nReceived "
|
|
f"{len(series)} expressions and {len(labels)} labels")
|
|
for s, r in zip(series, rendering_kw):
|
|
s.rendering_kw = r
|
|
|
|
|
|
def plot_factory(*args, **kwargs):
|
|
backend = kwargs.pop("backend", "default")
|
|
if isinstance(backend, str):
|
|
if backend == "default":
|
|
matplotlib = import_module('matplotlib',
|
|
min_module_version='1.1.0', catch=(RuntimeError,))
|
|
if matplotlib:
|
|
return MatplotlibBackend(*args, **kwargs)
|
|
return TextBackend(*args, **kwargs)
|
|
return plot_backends[backend](*args, **kwargs)
|
|
elif (type(backend) == type) and issubclass(backend, Plot):
|
|
return backend(*args, **kwargs)
|
|
else:
|
|
raise TypeError("backend must be either a string or a subclass of ``Plot``.")
|
|
|
|
|
|
plot_backends = {
|
|
'matplotlib': MatplotlibBackend,
|
|
'text': TextBackend,
|
|
}
|
|
|
|
|
|
####New API for plotting module ####
|
|
|
|
# TODO: Add color arrays for plots.
|
|
# TODO: Add more plotting options for 3d plots.
|
|
# TODO: Adaptive sampling for 3D plots.
|
|
|
|
def plot(*args, show=True, **kwargs):
|
|
"""Plots a function of a single variable as a curve.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
args :
|
|
The first argument is the expression representing the function
|
|
of single variable to be plotted.
|
|
|
|
The last argument is a 3-tuple denoting the range of the free
|
|
variable. e.g. ``(x, 0, 5)``
|
|
|
|
Typical usage examples are in the following:
|
|
|
|
- Plotting a single expression with a single range.
|
|
``plot(expr, range, **kwargs)``
|
|
- Plotting a single expression with the default range (-10, 10).
|
|
``plot(expr, **kwargs)``
|
|
- Plotting multiple expressions with a single range.
|
|
``plot(expr1, expr2, ..., range, **kwargs)``
|
|
- Plotting multiple expressions with multiple ranges.
|
|
``plot((expr1, range1), (expr2, range2), ..., **kwargs)``
|
|
|
|
It is best practice to specify range explicitly because default
|
|
range may change in the future if a more advanced default range
|
|
detection algorithm is implemented.
|
|
|
|
show : bool, optional
|
|
The default value is set to ``True``. Set show to ``False`` and
|
|
the function will not display the plot. The returned instance of
|
|
the ``Plot`` class can then be used to save or display the plot
|
|
by calling the ``save()`` and ``show()`` methods respectively.
|
|
|
|
line_color : string, or float, or function, optional
|
|
Specifies the color for the plot.
|
|
See ``Plot`` to see how to set color for the plots.
|
|
Note that by setting ``line_color``, it would be applied simultaneously
|
|
to all the series.
|
|
|
|
title : str, optional
|
|
Title of the plot. It is set to the latex representation of
|
|
the expression, if the plot has only one expression.
|
|
|
|
label : str, optional
|
|
The label of the expression in the plot. It will be used when
|
|
called with ``legend``. Default is the name of the expression.
|
|
e.g. ``sin(x)``
|
|
|
|
xlabel : str or expression, optional
|
|
Label for the x-axis.
|
|
|
|
ylabel : str or expression, optional
|
|
Label for the y-axis.
|
|
|
|
xscale : 'linear' or 'log', optional
|
|
Sets the scaling of the x-axis.
|
|
|
|
yscale : 'linear' or 'log', optional
|
|
Sets the scaling of the y-axis.
|
|
|
|
axis_center : (float, float), optional
|
|
Tuple of two floats denoting the coordinates of the center or
|
|
{'center', 'auto'}
|
|
|
|
xlim : (float, float), optional
|
|
Denotes the x-axis limits, ``(min, max)```.
|
|
|
|
ylim : (float, float), optional
|
|
Denotes the y-axis limits, ``(min, max)```.
|
|
|
|
annotations : list, optional
|
|
A list of dictionaries specifying the type of annotation
|
|
required. The keys in the dictionary should be equivalent
|
|
to the arguments of the :external:mod:`matplotlib`'s
|
|
:external:meth:`~matplotlib.axes.Axes.annotate` method.
|
|
|
|
markers : list, optional
|
|
A list of dictionaries specifying the type the markers required.
|
|
The keys in the dictionary should be equivalent to the arguments
|
|
of the :external:mod:`matplotlib`'s :external:func:`~matplotlib.pyplot.plot()` function
|
|
along with the marker related keyworded arguments.
|
|
|
|
rectangles : list, optional
|
|
A list of dictionaries specifying the dimensions of the
|
|
rectangles to be plotted. The keys in the dictionary should be
|
|
equivalent to the arguments of the :external:mod:`matplotlib`'s
|
|
:external:class:`~matplotlib.patches.Rectangle` class.
|
|
|
|
fill : dict, optional
|
|
A dictionary specifying the type of color filling required in
|
|
the plot. The keys in the dictionary should be equivalent to the
|
|
arguments of the :external:mod:`matplotlib`'s
|
|
:external:meth:`~matplotlib.axes.Axes.fill_between` method.
|
|
|
|
adaptive : bool, optional
|
|
The default value is set to ``True``. Set adaptive to ``False``
|
|
and specify ``n`` if uniform sampling is required.
|
|
|
|
The plotting uses an adaptive algorithm which samples
|
|
recursively to accurately plot. The adaptive algorithm uses a
|
|
random point near the midpoint of two points that has to be
|
|
further sampled. Hence the same plots can appear slightly
|
|
different.
|
|
|
|
depth : int, optional
|
|
Recursion depth of the adaptive algorithm. A depth of value
|
|
`n` samples a maximum of `2^{n}` points.
|
|
|
|
If the ``adaptive`` flag is set to ``False``, this will be
|
|
ignored.
|
|
|
|
n : int, optional
|
|
Used when the ``adaptive`` is set to ``False``. The function
|
|
is uniformly sampled at ``n`` number of points. If the ``adaptive``
|
|
flag is set to ``True``, this will be ignored.
|
|
This keyword argument replaces ``nb_of_points``, which should be
|
|
considered deprecated.
|
|
|
|
size : (float, float), optional
|
|
A tuple in the form (width, height) in inches to specify the size of
|
|
the overall figure. The default value is set to ``None``, meaning
|
|
the size will be set by the default backend.
|
|
|
|
Examples
|
|
========
|
|
|
|
.. plot::
|
|
:context: close-figs
|
|
:format: doctest
|
|
:include-source: True
|
|
|
|
>>> from sympy import symbols
|
|
>>> from sympy.plotting import plot
|
|
>>> x = symbols('x')
|
|
|
|
Single Plot
|
|
|
|
.. plot::
|
|
:context: close-figs
|
|
:format: doctest
|
|
:include-source: True
|
|
|
|
>>> plot(x**2, (x, -5, 5))
|
|
Plot object containing:
|
|
[0]: cartesian line: x**2 for x over (-5.0, 5.0)
|
|
|
|
Multiple plots with single range.
|
|
|
|
.. plot::
|
|
:context: close-figs
|
|
:format: doctest
|
|
:include-source: True
|
|
|
|
>>> plot(x, x**2, x**3, (x, -5, 5))
|
|
Plot object containing:
|
|
[0]: cartesian line: x for x over (-5.0, 5.0)
|
|
[1]: cartesian line: x**2 for x over (-5.0, 5.0)
|
|
[2]: cartesian line: x**3 for x over (-5.0, 5.0)
|
|
|
|
Multiple plots with different ranges.
|
|
|
|
.. plot::
|
|
:context: close-figs
|
|
:format: doctest
|
|
:include-source: True
|
|
|
|
>>> plot((x**2, (x, -6, 6)), (x, (x, -5, 5)))
|
|
Plot object containing:
|
|
[0]: cartesian line: x**2 for x over (-6.0, 6.0)
|
|
[1]: cartesian line: x for x over (-5.0, 5.0)
|
|
|
|
No adaptive sampling.
|
|
|
|
.. plot::
|
|
:context: close-figs
|
|
:format: doctest
|
|
:include-source: True
|
|
|
|
>>> plot(x**2, adaptive=False, n=400)
|
|
Plot object containing:
|
|
[0]: cartesian line: x**2 for x over (-10.0, 10.0)
|
|
|
|
See Also
|
|
========
|
|
|
|
Plot, LineOver1DRangeSeries
|
|
|
|
"""
|
|
args = _plot_sympify(args)
|
|
plot_expr = _check_arguments(args, 1, 1, **kwargs)
|
|
params = kwargs.get("params", None)
|
|
free = set()
|
|
for p in plot_expr:
|
|
if not isinstance(p[1][0], str):
|
|
free |= {p[1][0]}
|
|
else:
|
|
free |= {Symbol(p[1][0])}
|
|
if params:
|
|
free = free.difference(params.keys())
|
|
x = free.pop() if free else Symbol("x")
|
|
kwargs.setdefault('xlabel', x)
|
|
kwargs.setdefault('ylabel', Function('f')(x))
|
|
|
|
labels = kwargs.pop("label", [])
|
|
rendering_kw = kwargs.pop("rendering_kw", None)
|
|
series = _build_line_series(*plot_expr, **kwargs)
|
|
_set_labels(series, labels, rendering_kw)
|
|
|
|
plots = plot_factory(*series, **kwargs)
|
|
if show:
|
|
plots.show()
|
|
return plots
|
|
|
|
|
|
def plot_parametric(*args, show=True, **kwargs):
|
|
"""
|
|
Plots a 2D parametric curve.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
args
|
|
Common specifications are:
|
|
|
|
- Plotting a single parametric curve with a range
|
|
``plot_parametric((expr_x, expr_y), range)``
|
|
- Plotting multiple parametric curves with the same range
|
|
``plot_parametric((expr_x, expr_y), ..., range)``
|
|
- Plotting multiple parametric curves with different ranges
|
|
``plot_parametric((expr_x, expr_y, range), ...)``
|
|
|
|
``expr_x`` is the expression representing $x$ component of the
|
|
parametric function.
|
|
|
|
``expr_y`` is the expression representing $y$ component of the
|
|
parametric function.
|
|
|
|
``range`` is a 3-tuple denoting the parameter symbol, start and
|
|
stop. For example, ``(u, 0, 5)``.
|
|
|
|
If the range is not specified, then a default range of (-10, 10)
|
|
is used.
|
|
|
|
However, if the arguments are specified as
|
|
``(expr_x, expr_y, range), ...``, you must specify the ranges
|
|
for each expressions manually.
|
|
|
|
Default range may change in the future if a more advanced
|
|
algorithm is implemented.
|
|
|
|
adaptive : bool, optional
|
|
Specifies whether to use the adaptive sampling or not.
|
|
|
|
The default value is set to ``True``. Set adaptive to ``False``
|
|
and specify ``n`` if uniform sampling is required.
|
|
|
|
depth : int, optional
|
|
The recursion depth of the adaptive algorithm. A depth of
|
|
value $n$ samples a maximum of $2^n$ points.
|
|
|
|
n : int, optional
|
|
Used when the ``adaptive`` flag is set to ``False``. Specifies the
|
|
number of the points used for the uniform sampling.
|
|
This keyword argument replaces ``nb_of_points``, which should be
|
|
considered deprecated.
|
|
|
|
line_color : string, or float, or function, optional
|
|
Specifies the color for the plot.
|
|
See ``Plot`` to see how to set color for the plots.
|
|
Note that by setting ``line_color``, it would be applied simultaneously
|
|
to all the series.
|
|
|
|
label : str, optional
|
|
The label of the expression in the plot. It will be used when
|
|
called with ``legend``. Default is the name of the expression.
|
|
e.g. ``sin(x)``
|
|
|
|
xlabel : str, optional
|
|
Label for the x-axis.
|
|
|
|
ylabel : str, optional
|
|
Label for the y-axis.
|
|
|
|
xscale : 'linear' or 'log', optional
|
|
Sets the scaling of the x-axis.
|
|
|
|
yscale : 'linear' or 'log', optional
|
|
Sets the scaling of the y-axis.
|
|
|
|
axis_center : (float, float), optional
|
|
Tuple of two floats denoting the coordinates of the center or
|
|
{'center', 'auto'}
|
|
|
|
xlim : (float, float), optional
|
|
Denotes the x-axis limits, ``(min, max)```.
|
|
|
|
ylim : (float, float), optional
|
|
Denotes the y-axis limits, ``(min, max)```.
|
|
|
|
size : (float, float), optional
|
|
A tuple in the form (width, height) in inches to specify the size of
|
|
the overall figure. The default value is set to ``None``, meaning
|
|
the size will be set by the default backend.
|
|
|
|
Examples
|
|
========
|
|
|
|
.. plot::
|
|
:context: reset
|
|
:format: doctest
|
|
:include-source: True
|
|
|
|
>>> from sympy import plot_parametric, symbols, cos, sin
|
|
>>> u = symbols('u')
|
|
|
|
A parametric plot with a single expression:
|
|
|
|
.. plot::
|
|
:context: close-figs
|
|
:format: doctest
|
|
:include-source: True
|
|
|
|
>>> plot_parametric((cos(u), sin(u)), (u, -5, 5))
|
|
Plot object containing:
|
|
[0]: parametric cartesian line: (cos(u), sin(u)) for u over (-5.0, 5.0)
|
|
|
|
A parametric plot with multiple expressions with the same range:
|
|
|
|
.. plot::
|
|
:context: close-figs
|
|
:format: doctest
|
|
:include-source: True
|
|
|
|
>>> plot_parametric((cos(u), sin(u)), (u, cos(u)), (u, -10, 10))
|
|
Plot object containing:
|
|
[0]: parametric cartesian line: (cos(u), sin(u)) for u over (-10.0, 10.0)
|
|
[1]: parametric cartesian line: (u, cos(u)) for u over (-10.0, 10.0)
|
|
|
|
A parametric plot with multiple expressions with different ranges
|
|
for each curve:
|
|
|
|
.. plot::
|
|
:context: close-figs
|
|
:format: doctest
|
|
:include-source: True
|
|
|
|
>>> plot_parametric((cos(u), sin(u), (u, -5, 5)),
|
|
... (cos(u), u, (u, -5, 5)))
|
|
Plot object containing:
|
|
[0]: parametric cartesian line: (cos(u), sin(u)) for u over (-5.0, 5.0)
|
|
[1]: parametric cartesian line: (cos(u), u) for u over (-5.0, 5.0)
|
|
|
|
Notes
|
|
=====
|
|
|
|
The plotting uses an adaptive algorithm which samples recursively to
|
|
accurately plot the curve. The adaptive algorithm uses a random point
|
|
near the midpoint of two points that has to be further sampled.
|
|
Hence, repeating the same plot command can give slightly different
|
|
results because of the random sampling.
|
|
|
|
If there are multiple plots, then the same optional arguments are
|
|
applied to all the plots drawn in the same canvas. If you want to
|
|
set these options separately, you can index the returned ``Plot``
|
|
object and set it.
|
|
|
|
For example, when you specify ``line_color`` once, it would be
|
|
applied simultaneously to both series.
|
|
|
|
.. plot::
|
|
:context: close-figs
|
|
:format: doctest
|
|
:include-source: True
|
|
|
|
>>> from sympy import pi
|
|
>>> expr1 = (u, cos(2*pi*u)/2 + 1/2)
|
|
>>> expr2 = (u, sin(2*pi*u)/2 + 1/2)
|
|
>>> p = plot_parametric(expr1, expr2, (u, 0, 1), line_color='blue')
|
|
|
|
If you want to specify the line color for the specific series, you
|
|
should index each item and apply the property manually.
|
|
|
|
.. plot::
|
|
:context: close-figs
|
|
:format: doctest
|
|
:include-source: True
|
|
|
|
>>> p[0].line_color = 'red'
|
|
>>> p.show()
|
|
|
|
See Also
|
|
========
|
|
|
|
Plot, Parametric2DLineSeries
|
|
"""
|
|
args = _plot_sympify(args)
|
|
plot_expr = _check_arguments(args, 2, 1, **kwargs)
|
|
|
|
labels = kwargs.pop("label", [])
|
|
rendering_kw = kwargs.pop("rendering_kw", None)
|
|
series = _create_series(Parametric2DLineSeries, plot_expr, **kwargs)
|
|
_set_labels(series, labels, rendering_kw)
|
|
|
|
plots = plot_factory(*series, **kwargs)
|
|
if show:
|
|
plots.show()
|
|
return plots
|
|
|
|
|
|
def plot3d_parametric_line(*args, show=True, **kwargs):
|
|
"""
|
|
Plots a 3D parametric line plot.
|
|
|
|
Usage
|
|
=====
|
|
|
|
Single plot:
|
|
|
|
``plot3d_parametric_line(expr_x, expr_y, expr_z, range, **kwargs)``
|
|
|
|
If the range is not specified, then a default range of (-10, 10) is used.
|
|
|
|
Multiple plots.
|
|
|
|
``plot3d_parametric_line((expr_x, expr_y, expr_z, range), ..., **kwargs)``
|
|
|
|
Ranges have to be specified for every expression.
|
|
|
|
Default range may change in the future if a more advanced default range
|
|
detection algorithm is implemented.
|
|
|
|
Arguments
|
|
=========
|
|
|
|
expr_x : Expression representing the function along x.
|
|
|
|
expr_y : Expression representing the function along y.
|
|
|
|
expr_z : Expression representing the function along z.
|
|
|
|
range : (:class:`~.Symbol`, float, float)
|
|
A 3-tuple denoting the range of the parameter variable, e.g., (u, 0, 5).
|
|
|
|
Keyword Arguments
|
|
=================
|
|
|
|
Arguments for ``Parametric3DLineSeries`` class.
|
|
|
|
n : int
|
|
The range is uniformly sampled at ``n`` number of points.
|
|
This keyword argument replaces ``nb_of_points``, which should be
|
|
considered deprecated.
|
|
|
|
Aesthetics:
|
|
|
|
line_color : string, or float, or function, optional
|
|
Specifies the color for the plot.
|
|
See ``Plot`` to see how to set color for the plots.
|
|
Note that by setting ``line_color``, it would be applied simultaneously
|
|
to all the series.
|
|
|
|
label : str
|
|
The label to the plot. It will be used when called with ``legend=True``
|
|
to denote the function with the given label in the plot.
|
|
|
|
If there are multiple plots, then the same series arguments are applied to
|
|
all the plots. If you want to set these options separately, you can index
|
|
the returned ``Plot`` object and set it.
|
|
|
|
Arguments for ``Plot`` class.
|
|
|
|
title : str
|
|
Title of the plot.
|
|
|
|
size : (float, float), optional
|
|
A tuple in the form (width, height) in inches to specify the size of
|
|
the overall figure. The default value is set to ``None``, meaning
|
|
the size will be set by the default backend.
|
|
|
|
Examples
|
|
========
|
|
|
|
.. plot::
|
|
:context: reset
|
|
:format: doctest
|
|
:include-source: True
|
|
|
|
>>> from sympy import symbols, cos, sin
|
|
>>> from sympy.plotting import plot3d_parametric_line
|
|
>>> u = symbols('u')
|
|
|
|
Single plot.
|
|
|
|
.. plot::
|
|
:context: close-figs
|
|
:format: doctest
|
|
:include-source: True
|
|
|
|
>>> plot3d_parametric_line(cos(u), sin(u), u, (u, -5, 5))
|
|
Plot object containing:
|
|
[0]: 3D parametric cartesian line: (cos(u), sin(u), u) for u over (-5.0, 5.0)
|
|
|
|
|
|
Multiple plots.
|
|
|
|
.. plot::
|
|
:context: close-figs
|
|
:format: doctest
|
|
:include-source: True
|
|
|
|
>>> plot3d_parametric_line((cos(u), sin(u), u, (u, -5, 5)),
|
|
... (sin(u), u**2, u, (u, -5, 5)))
|
|
Plot object containing:
|
|
[0]: 3D parametric cartesian line: (cos(u), sin(u), u) for u over (-5.0, 5.0)
|
|
[1]: 3D parametric cartesian line: (sin(u), u**2, u) for u over (-5.0, 5.0)
|
|
|
|
|
|
See Also
|
|
========
|
|
|
|
Plot, Parametric3DLineSeries
|
|
|
|
"""
|
|
args = _plot_sympify(args)
|
|
plot_expr = _check_arguments(args, 3, 1, **kwargs)
|
|
kwargs.setdefault("xlabel", "x")
|
|
kwargs.setdefault("ylabel", "y")
|
|
kwargs.setdefault("zlabel", "z")
|
|
|
|
labels = kwargs.pop("label", [])
|
|
rendering_kw = kwargs.pop("rendering_kw", None)
|
|
series = _create_series(Parametric3DLineSeries, plot_expr, **kwargs)
|
|
_set_labels(series, labels, rendering_kw)
|
|
|
|
plots = plot_factory(*series, **kwargs)
|
|
if show:
|
|
plots.show()
|
|
return plots
|
|
|
|
|
|
def _plot3d_plot_contour_helper(Series, *args, **kwargs):
|
|
"""plot3d and plot_contour are structurally identical. Let's reduce
|
|
code repetition.
|
|
"""
|
|
# NOTE: if this import would be at the top-module level, it would trigger
|
|
# SymPy's optional-dependencies tests to fail.
|
|
from sympy.vector import BaseScalar
|
|
|
|
args = _plot_sympify(args)
|
|
plot_expr = _check_arguments(args, 1, 2, **kwargs)
|
|
|
|
free_x = set()
|
|
free_y = set()
|
|
_types = (Symbol, BaseScalar, Indexed, AppliedUndef)
|
|
for p in plot_expr:
|
|
free_x |= {p[1][0]} if isinstance(p[1][0], _types) else {Symbol(p[1][0])}
|
|
free_y |= {p[2][0]} if isinstance(p[2][0], _types) else {Symbol(p[2][0])}
|
|
x = free_x.pop() if free_x else Symbol("x")
|
|
y = free_y.pop() if free_y else Symbol("y")
|
|
kwargs.setdefault("xlabel", x)
|
|
kwargs.setdefault("ylabel", y)
|
|
kwargs.setdefault("zlabel", Function('f')(x, y))
|
|
|
|
# if a polar discretization is requested and automatic labelling has ben
|
|
# applied, hide the labels on the x-y axis.
|
|
if kwargs.get("is_polar", False):
|
|
if callable(kwargs["xlabel"]):
|
|
kwargs["xlabel"] = ""
|
|
if callable(kwargs["ylabel"]):
|
|
kwargs["ylabel"] = ""
|
|
|
|
labels = kwargs.pop("label", [])
|
|
rendering_kw = kwargs.pop("rendering_kw", None)
|
|
series = _create_series(Series, plot_expr, **kwargs)
|
|
_set_labels(series, labels, rendering_kw)
|
|
plots = plot_factory(*series, **kwargs)
|
|
if kwargs.get("show", True):
|
|
plots.show()
|
|
return plots
|
|
|
|
|
|
def plot3d(*args, show=True, **kwargs):
|
|
"""
|
|
Plots a 3D surface plot.
|
|
|
|
Usage
|
|
=====
|
|
|
|
Single plot
|
|
|
|
``plot3d(expr, range_x, range_y, **kwargs)``
|
|
|
|
If the ranges are not specified, then a default range of (-10, 10) is used.
|
|
|
|
Multiple plot with the same range.
|
|
|
|
``plot3d(expr1, expr2, range_x, range_y, **kwargs)``
|
|
|
|
If the ranges are not specified, then a default range of (-10, 10) is used.
|
|
|
|
Multiple plots with different ranges.
|
|
|
|
``plot3d((expr1, range_x, range_y), (expr2, range_x, range_y), ..., **kwargs)``
|
|
|
|
Ranges have to be specified for every expression.
|
|
|
|
Default range may change in the future if a more advanced default range
|
|
detection algorithm is implemented.
|
|
|
|
Arguments
|
|
=========
|
|
|
|
expr : Expression representing the function along x.
|
|
|
|
range_x : (:class:`~.Symbol`, float, float)
|
|
A 3-tuple denoting the range of the x variable, e.g. (x, 0, 5).
|
|
|
|
range_y : (:class:`~.Symbol`, float, float)
|
|
A 3-tuple denoting the range of the y variable, e.g. (y, 0, 5).
|
|
|
|
Keyword Arguments
|
|
=================
|
|
|
|
Arguments for ``SurfaceOver2DRangeSeries`` class:
|
|
|
|
n1 : int
|
|
The x range is sampled uniformly at ``n1`` of points.
|
|
This keyword argument replaces ``nb_of_points_x``, which should be
|
|
considered deprecated.
|
|
|
|
n2 : int
|
|
The y range is sampled uniformly at ``n2`` of points.
|
|
This keyword argument replaces ``nb_of_points_y``, which should be
|
|
considered deprecated.
|
|
|
|
Aesthetics:
|
|
|
|
surface_color : Function which returns a float
|
|
Specifies the color for the surface of the plot.
|
|
See :class:`~.Plot` for more details.
|
|
|
|
If there are multiple plots, then the same series arguments are applied to
|
|
all the plots. If you want to set these options separately, you can index
|
|
the returned ``Plot`` object and set it.
|
|
|
|
Arguments for ``Plot`` class:
|
|
|
|
title : str
|
|
Title of the plot.
|
|
|
|
size : (float, float), optional
|
|
A tuple in the form (width, height) in inches to specify the size of the
|
|
overall figure. The default value is set to ``None``, meaning the size will
|
|
be set by the default backend.
|
|
|
|
Examples
|
|
========
|
|
|
|
.. plot::
|
|
:context: reset
|
|
:format: doctest
|
|
:include-source: True
|
|
|
|
>>> from sympy import symbols
|
|
>>> from sympy.plotting import plot3d
|
|
>>> x, y = symbols('x y')
|
|
|
|
Single plot
|
|
|
|
.. plot::
|
|
:context: close-figs
|
|
:format: doctest
|
|
:include-source: True
|
|
|
|
>>> plot3d(x*y, (x, -5, 5), (y, -5, 5))
|
|
Plot object containing:
|
|
[0]: cartesian surface: x*y for x over (-5.0, 5.0) and y over (-5.0, 5.0)
|
|
|
|
|
|
Multiple plots with same range
|
|
|
|
.. plot::
|
|
:context: close-figs
|
|
:format: doctest
|
|
:include-source: True
|
|
|
|
>>> plot3d(x*y, -x*y, (x, -5, 5), (y, -5, 5))
|
|
Plot object containing:
|
|
[0]: cartesian surface: x*y for x over (-5.0, 5.0) and y over (-5.0, 5.0)
|
|
[1]: cartesian surface: -x*y for x over (-5.0, 5.0) and y over (-5.0, 5.0)
|
|
|
|
|
|
Multiple plots with different ranges.
|
|
|
|
.. plot::
|
|
:context: close-figs
|
|
:format: doctest
|
|
:include-source: True
|
|
|
|
>>> plot3d((x**2 + y**2, (x, -5, 5), (y, -5, 5)),
|
|
... (x*y, (x, -3, 3), (y, -3, 3)))
|
|
Plot object containing:
|
|
[0]: cartesian surface: x**2 + y**2 for x over (-5.0, 5.0) and y over (-5.0, 5.0)
|
|
[1]: cartesian surface: x*y for x over (-3.0, 3.0) and y over (-3.0, 3.0)
|
|
|
|
|
|
See Also
|
|
========
|
|
|
|
Plot, SurfaceOver2DRangeSeries
|
|
|
|
"""
|
|
kwargs.setdefault("show", show)
|
|
return _plot3d_plot_contour_helper(
|
|
SurfaceOver2DRangeSeries, *args, **kwargs)
|
|
|
|
|
|
def plot3d_parametric_surface(*args, show=True, **kwargs):
|
|
"""
|
|
Plots a 3D parametric surface plot.
|
|
|
|
Explanation
|
|
===========
|
|
|
|
Single plot.
|
|
|
|
``plot3d_parametric_surface(expr_x, expr_y, expr_z, range_u, range_v, **kwargs)``
|
|
|
|
If the ranges is not specified, then a default range of (-10, 10) is used.
|
|
|
|
Multiple plots.
|
|
|
|
``plot3d_parametric_surface((expr_x, expr_y, expr_z, range_u, range_v), ..., **kwargs)``
|
|
|
|
Ranges have to be specified for every expression.
|
|
|
|
Default range may change in the future if a more advanced default range
|
|
detection algorithm is implemented.
|
|
|
|
Arguments
|
|
=========
|
|
|
|
expr_x : Expression representing the function along ``x``.
|
|
|
|
expr_y : Expression representing the function along ``y``.
|
|
|
|
expr_z : Expression representing the function along ``z``.
|
|
|
|
range_u : (:class:`~.Symbol`, float, float)
|
|
A 3-tuple denoting the range of the u variable, e.g. (u, 0, 5).
|
|
|
|
range_v : (:class:`~.Symbol`, float, float)
|
|
A 3-tuple denoting the range of the v variable, e.g. (v, 0, 5).
|
|
|
|
Keyword Arguments
|
|
=================
|
|
|
|
Arguments for ``ParametricSurfaceSeries`` class:
|
|
|
|
n1 : int
|
|
The ``u`` range is sampled uniformly at ``n1`` of points.
|
|
This keyword argument replaces ``nb_of_points_u``, which should be
|
|
considered deprecated.
|
|
|
|
n2 : int
|
|
The ``v`` range is sampled uniformly at ``n2`` of points.
|
|
This keyword argument replaces ``nb_of_points_v``, which should be
|
|
considered deprecated.
|
|
|
|
Aesthetics:
|
|
|
|
surface_color : Function which returns a float
|
|
Specifies the color for the surface of the plot. See
|
|
:class:`~Plot` for more details.
|
|
|
|
If there are multiple plots, then the same series arguments are applied for
|
|
all the plots. If you want to set these options separately, you can index
|
|
the returned ``Plot`` object and set it.
|
|
|
|
|
|
Arguments for ``Plot`` class:
|
|
|
|
title : str
|
|
Title of the plot.
|
|
|
|
size : (float, float), optional
|
|
A tuple in the form (width, height) in inches to specify the size of the
|
|
overall figure. The default value is set to ``None``, meaning the size will
|
|
be set by the default backend.
|
|
|
|
Examples
|
|
========
|
|
|
|
.. plot::
|
|
:context: reset
|
|
:format: doctest
|
|
:include-source: True
|
|
|
|
>>> from sympy import symbols, cos, sin
|
|
>>> from sympy.plotting import plot3d_parametric_surface
|
|
>>> u, v = symbols('u v')
|
|
|
|
Single plot.
|
|
|
|
.. plot::
|
|
:context: close-figs
|
|
:format: doctest
|
|
:include-source: True
|
|
|
|
>>> plot3d_parametric_surface(cos(u + v), sin(u - v), u - v,
|
|
... (u, -5, 5), (v, -5, 5))
|
|
Plot object containing:
|
|
[0]: parametric cartesian surface: (cos(u + v), sin(u - v), u - v) for u over (-5.0, 5.0) and v over (-5.0, 5.0)
|
|
|
|
|
|
See Also
|
|
========
|
|
|
|
Plot, ParametricSurfaceSeries
|
|
|
|
"""
|
|
|
|
args = _plot_sympify(args)
|
|
plot_expr = _check_arguments(args, 3, 2, **kwargs)
|
|
kwargs.setdefault("xlabel", "x")
|
|
kwargs.setdefault("ylabel", "y")
|
|
kwargs.setdefault("zlabel", "z")
|
|
|
|
labels = kwargs.pop("label", [])
|
|
rendering_kw = kwargs.pop("rendering_kw", None)
|
|
series = _create_series(ParametricSurfaceSeries, plot_expr, **kwargs)
|
|
_set_labels(series, labels, rendering_kw)
|
|
|
|
plots = plot_factory(*series, **kwargs)
|
|
if show:
|
|
plots.show()
|
|
return plots
|
|
|
|
def plot_contour(*args, show=True, **kwargs):
|
|
"""
|
|
Draws contour plot of a function
|
|
|
|
Usage
|
|
=====
|
|
|
|
Single plot
|
|
|
|
``plot_contour(expr, range_x, range_y, **kwargs)``
|
|
|
|
If the ranges are not specified, then a default range of (-10, 10) is used.
|
|
|
|
Multiple plot with the same range.
|
|
|
|
``plot_contour(expr1, expr2, range_x, range_y, **kwargs)``
|
|
|
|
If the ranges are not specified, then a default range of (-10, 10) is used.
|
|
|
|
Multiple plots with different ranges.
|
|
|
|
``plot_contour((expr1, range_x, range_y), (expr2, range_x, range_y), ..., **kwargs)``
|
|
|
|
Ranges have to be specified for every expression.
|
|
|
|
Default range may change in the future if a more advanced default range
|
|
detection algorithm is implemented.
|
|
|
|
Arguments
|
|
=========
|
|
|
|
expr : Expression representing the function along x.
|
|
|
|
range_x : (:class:`Symbol`, float, float)
|
|
A 3-tuple denoting the range of the x variable, e.g. (x, 0, 5).
|
|
|
|
range_y : (:class:`Symbol`, float, float)
|
|
A 3-tuple denoting the range of the y variable, e.g. (y, 0, 5).
|
|
|
|
Keyword Arguments
|
|
=================
|
|
|
|
Arguments for ``ContourSeries`` class:
|
|
|
|
n1 : int
|
|
The x range is sampled uniformly at ``n1`` of points.
|
|
This keyword argument replaces ``nb_of_points_x``, which should be
|
|
considered deprecated.
|
|
|
|
n2 : int
|
|
The y range is sampled uniformly at ``n2`` of points.
|
|
This keyword argument replaces ``nb_of_points_y``, which should be
|
|
considered deprecated.
|
|
|
|
Aesthetics:
|
|
|
|
surface_color : Function which returns a float
|
|
Specifies the color for the surface of the plot. See
|
|
:class:`sympy.plotting.Plot` for more details.
|
|
|
|
If there are multiple plots, then the same series arguments are applied to
|
|
all the plots. If you want to set these options separately, you can index
|
|
the returned ``Plot`` object and set it.
|
|
|
|
Arguments for ``Plot`` class:
|
|
|
|
title : str
|
|
Title of the plot.
|
|
|
|
size : (float, float), optional
|
|
A tuple in the form (width, height) in inches to specify the size of
|
|
the overall figure. The default value is set to ``None``, meaning
|
|
the size will be set by the default backend.
|
|
|
|
See Also
|
|
========
|
|
|
|
Plot, ContourSeries
|
|
|
|
"""
|
|
kwargs.setdefault("show", show)
|
|
return _plot3d_plot_contour_helper(ContourSeries, *args, **kwargs)
|
|
|
|
|
|
def check_arguments(args, expr_len, nb_of_free_symbols):
|
|
"""
|
|
Checks the arguments and converts into tuples of the
|
|
form (exprs, ranges).
|
|
|
|
Examples
|
|
========
|
|
|
|
.. plot::
|
|
:context: reset
|
|
:format: doctest
|
|
:include-source: True
|
|
|
|
>>> from sympy import cos, sin, symbols
|
|
>>> from sympy.plotting.plot import check_arguments
|
|
>>> x = symbols('x')
|
|
>>> check_arguments([cos(x), sin(x)], 2, 1)
|
|
[(cos(x), sin(x), (x, -10, 10))]
|
|
|
|
>>> check_arguments([x, x**2], 1, 1)
|
|
[(x, (x, -10, 10)), (x**2, (x, -10, 10))]
|
|
"""
|
|
if not args:
|
|
return []
|
|
if expr_len > 1 and isinstance(args[0], Expr):
|
|
# Multiple expressions same range.
|
|
# The arguments are tuples when the expression length is
|
|
# greater than 1.
|
|
if len(args) < expr_len:
|
|
raise ValueError("len(args) should not be less than expr_len")
|
|
for i in range(len(args)):
|
|
if isinstance(args[i], Tuple):
|
|
break
|
|
else:
|
|
i = len(args) + 1
|
|
|
|
exprs = Tuple(*args[:i])
|
|
free_symbols = list(set().union(*[e.free_symbols for e in exprs]))
|
|
if len(args) == expr_len + nb_of_free_symbols:
|
|
#Ranges given
|
|
plots = [exprs + Tuple(*args[expr_len:])]
|
|
else:
|
|
default_range = Tuple(-10, 10)
|
|
ranges = []
|
|
for symbol in free_symbols:
|
|
ranges.append(Tuple(symbol) + default_range)
|
|
|
|
for i in range(len(free_symbols) - nb_of_free_symbols):
|
|
ranges.append(Tuple(Dummy()) + default_range)
|
|
plots = [exprs + Tuple(*ranges)]
|
|
return plots
|
|
|
|
if isinstance(args[0], Expr) or (isinstance(args[0], Tuple) and
|
|
len(args[0]) == expr_len and
|
|
expr_len != 3):
|
|
# Cannot handle expressions with number of expression = 3. It is
|
|
# not possible to differentiate between expressions and ranges.
|
|
#Series of plots with same range
|
|
for i in range(len(args)):
|
|
if isinstance(args[i], Tuple) and len(args[i]) != expr_len:
|
|
break
|
|
if not isinstance(args[i], Tuple):
|
|
args[i] = Tuple(args[i])
|
|
else:
|
|
i = len(args) + 1
|
|
|
|
exprs = args[:i]
|
|
assert all(isinstance(e, Expr) for expr in exprs for e in expr)
|
|
free_symbols = list(set().union(*[e.free_symbols for expr in exprs
|
|
for e in expr]))
|
|
|
|
if len(free_symbols) > nb_of_free_symbols:
|
|
raise ValueError("The number of free_symbols in the expression "
|
|
"is greater than %d" % nb_of_free_symbols)
|
|
if len(args) == i + nb_of_free_symbols and isinstance(args[i], Tuple):
|
|
ranges = Tuple(*list(args[
|
|
i:i + nb_of_free_symbols]))
|
|
plots = [expr + ranges for expr in exprs]
|
|
return plots
|
|
else:
|
|
# Use default ranges.
|
|
default_range = Tuple(-10, 10)
|
|
ranges = []
|
|
for symbol in free_symbols:
|
|
ranges.append(Tuple(symbol) + default_range)
|
|
|
|
for i in range(nb_of_free_symbols - len(free_symbols)):
|
|
ranges.append(Tuple(Dummy()) + default_range)
|
|
ranges = Tuple(*ranges)
|
|
plots = [expr + ranges for expr in exprs]
|
|
return plots
|
|
|
|
elif isinstance(args[0], Tuple) and len(args[0]) == expr_len + nb_of_free_symbols:
|
|
# Multiple plots with different ranges.
|
|
for arg in args:
|
|
for i in range(expr_len):
|
|
if not isinstance(arg[i], Expr):
|
|
raise ValueError("Expected an expression, given %s" %
|
|
str(arg[i]))
|
|
for i in range(nb_of_free_symbols):
|
|
if not len(arg[i + expr_len]) == 3:
|
|
raise ValueError("The ranges should be a tuple of "
|
|
"length 3, got %s" % str(arg[i + expr_len]))
|
|
return args
|